/*! * Tectum * Copyright © 2026 Machiel Heinen. * Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming. * Zie LICENTIE.md. */ function createWalkControls(camera, domElement, opts) { const o = Object.assign({ eye: 1.65, speed: 2.1, run: 4.8, 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)); } 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); const rx = -fz, rz = fx; 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); } }; }