190 lines
8.1 KiB
JavaScript
190 lines
8.1 KiB
JavaScript
// ============================================================
|
|
// js/vendor/OrbitControls.js — eigen, compacte vervanging van de officiële
|
|
// three.js OrbitControls-addon. Geschreven omdat we geen netwerktoegang
|
|
// hebben om het origineel te vendoren; dekt precies de subset die wall.js
|
|
// gebruikt (`new THREE.OrbitControls(camera, domElement)`,
|
|
// `controls.target.set(...)`, `controls.update()`), plus interactie:
|
|
// links-slepen = roteren, scrollwiel = zoomen; zijwaarts schuiven (pannen) met rechts- of middelste-knop-slepen,
|
|
// Shift/Ctrl/⌘ + links-slepen, of de pijltjestoetsen terwijl de muis boven de weergave staat;
|
|
// touch: één vinger = roteren, twee vingers = knijpen (zoomen) en samen schuiven (pannen).
|
|
//
|
|
// Moet NA three.min.js geladen worden (leunt op window.THREE).
|
|
// ============================================================
|
|
(function () {
|
|
if (typeof THREE === 'undefined') {
|
|
console.error('OrbitControls.js (eigen versie): THREE is niet geladen — controleer de scriptvolgorde.');
|
|
return;
|
|
}
|
|
|
|
function OrbitControls(camera, domElement) {
|
|
const scope = this;
|
|
this.object = camera;
|
|
this.domElement = domElement || document;
|
|
this.target = new THREE.Vector3();
|
|
|
|
this.minDistance = 0.1;
|
|
this.maxDistance = 1000;
|
|
this.minPolarAngle = 0.001;
|
|
this.maxPolarAngle = Math.PI - 0.001;
|
|
this.rotateSpeed = 1.0;
|
|
this.zoomSpeed = 1.0;
|
|
this.panSpeed = 1.0;
|
|
this.enableRotate = true;
|
|
this.enableZoom = true;
|
|
this.enablePan = true;
|
|
|
|
const offset = new THREE.Vector3();
|
|
const spherical = new THREE.Spherical();
|
|
let isRotating = false;
|
|
let isPanning = false;
|
|
let lastX = 0, lastY = 0;
|
|
|
|
function getZoomScale() { return Math.pow(0.95, scope.zoomSpeed); }
|
|
|
|
this.update = function () {
|
|
offset.copy(scope.object.position).sub(scope.target);
|
|
spherical.setFromVector3(offset);
|
|
offset.setFromSpherical(spherical);
|
|
scope.object.position.copy(scope.target).add(offset);
|
|
scope.object.lookAt(scope.target);
|
|
return true;
|
|
};
|
|
|
|
function onPointerDown(event) {
|
|
const panButton = event.button === 2 || event.button === 1 || (event.button === 0 && (event.shiftKey || event.ctrlKey || event.metaKey));
|
|
if (event.button === 1) event.preventDefault(); // geen autoscroll
|
|
if (panButton) {
|
|
if (!scope.enablePan) return;
|
|
isPanning = true;
|
|
} else {
|
|
if (!scope.enableRotate) return;
|
|
isRotating = true;
|
|
}
|
|
lastX = event.clientX;
|
|
lastY = event.clientY;
|
|
}
|
|
|
|
function rotate(dx, dy) {
|
|
const w = scope.domElement.clientWidth || 1;
|
|
const h = scope.domElement.clientHeight || 1;
|
|
|
|
offset.copy(scope.object.position).sub(scope.target);
|
|
spherical.setFromVector3(offset);
|
|
spherical.theta -= (dx / w) * Math.PI * scope.rotateSpeed;
|
|
spherical.phi -= (dy / h) * Math.PI * scope.rotateSpeed;
|
|
spherical.phi = Math.max(scope.minPolarAngle, Math.min(scope.maxPolarAngle, spherical.phi));
|
|
spherical.makeSafe();
|
|
offset.setFromSpherical(spherical);
|
|
scope.object.position.copy(scope.target).add(offset);
|
|
scope.object.lookAt(scope.target);
|
|
}
|
|
function pan(dx, dy) {
|
|
const distance = scope.object.position.distanceTo(scope.target);
|
|
const fov = (scope.object.fov || 50) * Math.PI / 180;
|
|
const h = scope.domElement.clientHeight || 1;
|
|
const factor = 2 * Math.tan(fov / 2) * distance / h;
|
|
|
|
const te = scope.object.matrix.elements;
|
|
const xAxis = new THREE.Vector3(te[0], te[1], te[2]).multiplyScalar(-dx * factor * scope.panSpeed);
|
|
const yAxis = new THREE.Vector3(te[4], te[5], te[6]).multiplyScalar(dy * factor * scope.panSpeed);
|
|
scope.target.add(xAxis).add(yAxis);
|
|
scope.object.position.add(xAxis).add(yAxis);
|
|
}
|
|
function zoom(scale) {
|
|
offset.copy(scope.object.position).sub(scope.target);
|
|
const distance = Math.max(scope.minDistance, Math.min(scope.maxDistance, offset.length() * scale));
|
|
offset.setLength(distance);
|
|
scope.object.position.copy(scope.target).add(offset);
|
|
}
|
|
|
|
function onPointerMove(event) {
|
|
if (!isRotating && !isPanning) return;
|
|
const dx = event.clientX - lastX;
|
|
const dy = event.clientY - lastY;
|
|
lastX = event.clientX;
|
|
lastY = event.clientY;
|
|
if (isRotating) rotate(dx, dy);
|
|
else pan(dx, dy);
|
|
}
|
|
|
|
function onPointerUp() { isRotating = false; isPanning = false; }
|
|
|
|
function onWheel(event) {
|
|
if (!scope.enableZoom) return;
|
|
event.preventDefault();
|
|
zoom(event.deltaY > 0 ? 1 / getZoomScale() : getZoomScale());
|
|
}
|
|
|
|
// Touch: midden en afstand van de vingers; bij een wissel van het aantal vingers opnieuw beginnen.
|
|
let touch = null;
|
|
function touchState(list) {
|
|
const a = list[0], b = list[1] || list[0];
|
|
return { n: list.length, x: (a.clientX + b.clientX) / 2, y: (a.clientY + b.clientY) / 2, d: Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY) };
|
|
}
|
|
function onTouchStart(event) {
|
|
event.preventDefault();
|
|
touch = touchState(event.touches);
|
|
}
|
|
function onTouchMove(event) {
|
|
if (!touch) return;
|
|
event.preventDefault();
|
|
const t = touchState(event.touches);
|
|
if (t.n !== touch.n) { touch = t; return; }
|
|
if (t.n === 1) { if (scope.enableRotate) rotate(t.x - touch.x, t.y - touch.y); }
|
|
else {
|
|
if (scope.enablePan) pan(t.x - touch.x, t.y - touch.y);
|
|
if (scope.enableZoom && t.d > 0 && touch.d > 0) zoom(touch.d / t.d);
|
|
}
|
|
touch = t;
|
|
}
|
|
function onTouchEnd(event) { touch = event.touches.length ? touchState(event.touches) : null; }
|
|
|
|
function onContextMenu(event) { event.preventDefault(); }
|
|
|
|
// Pijltjestoetsen: schuiven terwijl de muis boven de weergave staat (niet in een invoerveld).
|
|
let hover = false;
|
|
const onEnter = () => { hover = true; };
|
|
const onLeave = () => { hover = false; };
|
|
const KEY_PAN = { ArrowLeft: [1, 0], ArrowRight: [-1, 0], ArrowUp: [0, 1], ArrowDown: [0, -1] };
|
|
function onKeyDown(event) {
|
|
const d = KEY_PAN[event.key];
|
|
if (!d || !hover || !scope.enablePan || event.altKey || event.ctrlKey || event.metaKey) return;
|
|
const t = event.target, tag = t && t.tagName;
|
|
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || (t && t.isContentEditable)) return;
|
|
event.preventDefault();
|
|
const step = (scope.domElement.clientHeight || 400) * (event.shiftKey ? 0.25 : 0.08);
|
|
pan(d[0] * step, d[1] * step);
|
|
}
|
|
|
|
this.domElement.addEventListener('mousedown', onPointerDown);
|
|
window.addEventListener('mousemove', onPointerMove);
|
|
window.addEventListener('mouseup', onPointerUp);
|
|
this.domElement.addEventListener('wheel', onWheel, { passive: false });
|
|
this.domElement.addEventListener('contextmenu', onContextMenu);
|
|
this.domElement.addEventListener('mouseenter', onEnter);
|
|
this.domElement.addEventListener('mouseleave', onLeave);
|
|
window.addEventListener('keydown', onKeyDown);
|
|
this.domElement.addEventListener('touchstart', onTouchStart, { passive: false });
|
|
this.domElement.addEventListener('touchmove', onTouchMove, { passive: false });
|
|
this.domElement.addEventListener('touchend', onTouchEnd);
|
|
this.domElement.addEventListener('touchcancel', onTouchEnd);
|
|
|
|
this.dispose = function () {
|
|
scope.domElement.removeEventListener('mousedown', onPointerDown);
|
|
window.removeEventListener('mousemove', onPointerMove);
|
|
window.removeEventListener('mouseup', onPointerUp);
|
|
scope.domElement.removeEventListener('wheel', onWheel);
|
|
scope.domElement.removeEventListener('contextmenu', onContextMenu);
|
|
scope.domElement.removeEventListener('mouseenter', onEnter);
|
|
scope.domElement.removeEventListener('mouseleave', onLeave);
|
|
window.removeEventListener('keydown', onKeyDown);
|
|
scope.domElement.removeEventListener('touchstart', onTouchStart);
|
|
scope.domElement.removeEventListener('touchmove', onTouchMove);
|
|
scope.domElement.removeEventListener('touchend', onTouchEnd);
|
|
scope.domElement.removeEventListener('touchcancel', onTouchEnd);
|
|
};
|
|
}
|
|
|
|
THREE.OrbitControls = OrbitControls;
|
|
})();
|