/*! * Tectum * Copyright © 2026 Machiel Heinen. * Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming. * Zie LICENTIE.md. */ // ============================================================ // core/walk-controls.js — lopen in de 3D-weergave (first person). // W/S of pijl omhoog/omlaag vooruit/achteruit // A/D opzij stappen // pijl links/rechts draaien // Shift rennen // slepen met de muis rondkijken // Botsen (optioneel) en vloerhoogte komen van de aanroeper: // opts.blocked(x, z) -> true als je daar niet kunt staan (m); standaard nooit // opts.floorAt(x, z) -> hoogte van de ondergrond (m) // opts.eye -> ooghoogte boven de ondergrond (m, standaard 1,65) // Plain script (geen ES-module); gebruikt THREE. // // const walk = createWalkControls(camera, domElement, opts); // walk.start({ x, z, yaw }); walk.update(dt); walk.stop(); // ============================================================ function createWalkControls(camera, domElement, opts) { const o = Object.assign({ eye: 1.65, speed: 1.68, run: 3.84, turn: Math.PI / 2, blocked: () => false, floorAt: () => 0 }, opts || {}); const keys = new Set(); let active = false; let x = 0, z = 0, yaw = 0, pitch = 0; let drag = null; const inField = () => ['INPUT', 'SELECT', 'TEXTAREA'].includes(document.activeElement && document.activeElement.tagName); const MOVE_KEYS = ['w', 'a', 's', 'd', 'arrowup', 'arrowdown', 'arrowleft', 'arrowright', 'shift']; function onKeyDown(e) { if (!active || inField() || e.ctrlKey || e.metaKey || e.altKey) return; const k = e.key.toLowerCase(); if (!MOVE_KEYS.includes(k)) return; keys.add(k); e.preventDefault(); } function onKeyUp(e) { keys.delete(e.key.toLowerCase()); } function onDown(e) { if (active && e.button === 0) drag = { x: e.clientX, y: e.clientY }; } function onMove(e) { if (!active || !drag) return; const w = domElement.clientWidth || 1; yaw -= (e.clientX - drag.x) / w * Math.PI; pitch = Math.max(-1.2, Math.min(1.2, pitch - (e.clientY - drag.y) / w * Math.PI)); drag = { x: e.clientX, y: e.clientY }; } function onUp() { drag = null; } window.addEventListener('keydown', onKeyDown); window.addEventListener('keyup', onKeyUp); domElement.addEventListener('pointerdown', onDown); window.addEventListener('pointermove', onMove); window.addEventListener('pointerup', onUp); function apply() { camera.position.set(x, o.floorAt(x, z) + o.eye, z); // yaw 0 = kijkrichting +Z; pitch > 0 = omhoog. const dir = new THREE.Vector3(Math.sin(yaw) * Math.cos(pitch), Math.sin(pitch), Math.cos(yaw) * Math.cos(pitch)); camera.lookAt(camera.position.clone().add(dir)); } // Stap met botsen: eerst de hele stap, anders per as (langs een muur glijden). function step(dx, dz) { if (!o.blocked(x + dx, z + dz)) { x += dx; z += dz; return; } if (!o.blocked(x + dx, z)) x += dx; else if (!o.blocked(x, z + dz)) z += dz; } return { get active() { return active; }, start(pose) { x = pose.x; z = pose.z; yaw = pose.yaw || 0; pitch = 0; keys.clear(); active = true; apply(); }, stop() { active = false; keys.clear(); drag = null; }, update(dt) { if (!active) return; const t = Math.min(dt, 0.1); if (keys.has('arrowleft')) yaw += o.turn * t; if (keys.has('arrowright')) yaw -= o.turn * t; const f = (keys.has('w') || keys.has('arrowup') ? 1 : 0) - (keys.has('s') || keys.has('arrowdown') ? 1 : 0); const r = (keys.has('d') ? 1 : 0) - (keys.has('a') ? 1 : 0); if (f || r) { const v = (keys.has('shift') ? o.run : o.speed) * t / Math.hypot(f, r); const fx = Math.sin(yaw), fz = Math.cos(yaw); // vooruit const rx = -fz, rz = fx; // rechts (kijkend langs +Z is rechts -X) step((fx * f + rx * r) * v, (fz * f + rz * r) * v); } apply(); }, dispose() { window.removeEventListener('keydown', onKeyDown); window.removeEventListener('keyup', onKeyUp); domElement.removeEventListener('pointerdown', onDown); window.removeEventListener('pointermove', onMove); window.removeEventListener('pointerup', onUp); } }; }