Z-up orbit default and three-finger touch pan
camera.up defaulted to three.js's Y-up while the actual data (ROS/FAST- LIVO2) and WALK mode are Z-up. Orbiting with the pole misaligned from the cloud's real vertical made yaw look like a diagonal tumble instead of a clean spin around what's visually "up" in the rendered cloud. Set Z-up at camera creation and on WALK exit (previously reverted to Y-up). OrbitControls only tracks 1-2 simultaneous touches — a 3rd pointer makes it go idle rather than doing anything. Added a three-finger pan on top, replicating OrbitControls' own (private, unexported) screen-space pan math so it feels identical to the existing right-drag/two-finger-pan.
This commit is contained in:
+54
-3
@@ -541,6 +541,10 @@ scene.background = new THREE.Color(THEME_BG[currentTheme]);
|
|||||||
|
|
||||||
const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.01, 10000);
|
const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.01, 10000);
|
||||||
camera.position.set(0, 0, 10);
|
camera.position.set(0, 0, 10);
|
||||||
|
// Z-up everywhere, matching the actual data (ROS/FAST-LIVO2) and WALK mode —
|
||||||
|
// orbiting with camera.up misaligned from the cloud's real vertical makes yaw
|
||||||
|
// look like a diagonal tumble instead of a clean spin. See also exitFP().
|
||||||
|
camera.up.set(0, 0, 1);
|
||||||
|
|
||||||
const renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: 'high-performance' });
|
const renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: 'high-performance' });
|
||||||
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
|
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
|
||||||
@@ -553,10 +557,57 @@ controls.enableDamping = true;
|
|||||||
controls.dampingFactor = 0.08;
|
controls.dampingFactor = 0.08;
|
||||||
controls.zoomToCursor = true; // zoom toward the cursor, not the orbit target — matches CAD/CloudCompare
|
controls.zoomToCursor = true; // zoom toward the cursor, not the orbit target — matches CAD/CloudCompare
|
||||||
// two-finger touch: pinch still zooms, but the drag also free-rotates (any direction the
|
// two-finger touch: pinch still zooms, but the drag also free-rotates (any direction the
|
||||||
// fingers move, not locked to one axis) instead of panning — trades away touch-panning,
|
// fingers move, not locked to one axis) — OrbitControls only handles 1-2 simultaneous
|
||||||
// which double-click-to-recenter (below) mostly covers instead.
|
// touches (a 3rd pointer makes it go idle), so three-finger pan below covers panning.
|
||||||
controls.touches.TWO = THREE.TOUCH.DOLLY_ROTATE;
|
controls.touches.TWO = THREE.TOUCH.DOLLY_ROTATE;
|
||||||
|
|
||||||
|
// ─── three-finger pan — replicates OrbitControls' own (private, unexported) screen-
|
||||||
|
// space pan math so it feels identical to right-drag/two-finger-pan elsewhere ──
|
||||||
|
const panTouches = new Map(); // pointerId -> {x,y}
|
||||||
|
function touchCentroid(map) {
|
||||||
|
let x = 0, y = 0;
|
||||||
|
for (const p of map.values()) { x += p.x; y += p.y; }
|
||||||
|
return { x: x / map.size, y: y / map.size };
|
||||||
|
}
|
||||||
|
function panByScreenDelta(deltaX, deltaY) {
|
||||||
|
const offset = new THREE.Vector3().copy(camera.position).sub(controls.target);
|
||||||
|
const targetDistance = offset.length() * Math.tan((camera.fov / 2) * Math.PI / 180);
|
||||||
|
const panLeftDist = 2 * deltaX * targetDistance / renderer.domElement.clientHeight * controls.panSpeed;
|
||||||
|
const panUpDist = 2 * deltaY * targetDistance / renderer.domElement.clientHeight * controls.panSpeed;
|
||||||
|
|
||||||
|
const panOffset = new THREE.Vector3().setFromMatrixColumn(camera.matrix, 0).multiplyScalar(-panLeftDist);
|
||||||
|
const yCol = new THREE.Vector3();
|
||||||
|
if (controls.screenSpacePanning) yCol.setFromMatrixColumn(camera.matrix, 1);
|
||||||
|
else { yCol.setFromMatrixColumn(camera.matrix, 0); yCol.crossVectors(camera.up, yCol); }
|
||||||
|
panOffset.add(yCol.multiplyScalar(panUpDist));
|
||||||
|
|
||||||
|
camera.position.add(panOffset);
|
||||||
|
controls.target.add(panOffset);
|
||||||
|
controls.update();
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
let panCentroid = null;
|
||||||
|
renderer.domElement.addEventListener('pointerdown', e => {
|
||||||
|
if (e.pointerType !== 'touch') return;
|
||||||
|
panTouches.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
||||||
|
panCentroid = panTouches.size === 3 ? touchCentroid(panTouches) : null;
|
||||||
|
});
|
||||||
|
renderer.domElement.addEventListener('pointermove', e => {
|
||||||
|
if (e.pointerType !== 'touch' || !panTouches.has(e.pointerId)) return;
|
||||||
|
panTouches.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
||||||
|
if (panTouches.size !== 3) return;
|
||||||
|
const c = touchCentroid(panTouches);
|
||||||
|
if (panCentroid) panByScreenDelta(c.x - panCentroid.x, c.y - panCentroid.y);
|
||||||
|
panCentroid = c;
|
||||||
|
});
|
||||||
|
function releaseTouchPan(e) {
|
||||||
|
if (e.pointerType !== 'touch' || !panTouches.has(e.pointerId)) return;
|
||||||
|
panTouches.delete(e.pointerId);
|
||||||
|
panCentroid = panTouches.size === 3 ? touchCentroid(panTouches) : null;
|
||||||
|
}
|
||||||
|
renderer.domElement.addEventListener('pointerup', releaseTouchPan);
|
||||||
|
renderer.domElement.addEventListener('pointercancel', releaseTouchPan);
|
||||||
|
|
||||||
// ─── on-demand rendering (perf: idle frames cost nothing) ────────────────
|
// ─── on-demand rendering (perf: idle frames cost nothing) ────────────────
|
||||||
let needsRender = true;
|
let needsRender = true;
|
||||||
const invalidate = () => { needsRender = true; };
|
const invalidate = () => { needsRender = true; };
|
||||||
@@ -1023,7 +1074,7 @@ function exitFP() {
|
|||||||
const d = new THREE.Vector3(); camera.getWorldDirection(d);
|
const d = new THREE.Vector3(); camera.getWorldDirection(d);
|
||||||
const r = points?.geometry.boundingSphere?.radius || 5;
|
const r = points?.geometry.boundingSphere?.radius || 5;
|
||||||
controls.target.copy(camera.position).addScaledVector(d, Math.max(r, 5));
|
controls.target.copy(camera.position).addScaledVector(d, Math.max(r, 5));
|
||||||
camera.up.set(0, 1, 0);
|
camera.up.set(0, 0, 1); // back to the app-wide Z-up default, not three.js's Y-up
|
||||||
controls.enabled = true;
|
controls.enabled = true;
|
||||||
controls.update();
|
controls.update();
|
||||||
fpStartBtn.disabled = false; fpExitBtn.disabled = true;
|
fpStartBtn.disabled = false; fpExitBtn.disabled = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user