Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-05-21 12:55:53 +00:00
parent 921fb8f12e
commit 812a829c9d
+67 -35
View File
@@ -459,61 +459,93 @@ function HoverDetector() {
return null; return null;
} }
/** Raycasting click handler for measurement mode uses snap point when available */ /** Pointer handler for measurement mode uses snap point when available.
* Uses pointerdown/up with a drag gate so accidental drags don't add points. */
function MeasureClickHandler() { function MeasureClickHandler() {
const { camera, scene, gl } = useThree(); const { camera, scene, gl } = useThree();
const measureMode = useModelStore((s) => s.measureMode); const measureMode = useModelStore((s) => s.measureMode);
const addMeasurePoint = useModelStore((s) => s.addMeasurePoint);
const snapPoint = useModelStore((s) => s.snapPoint);
const raycaster = useMemo(() => new THREE.Raycaster(), []); const raycaster = useMemo(() => new THREE.Raycaster(), []);
const mouse = useMemo(() => new THREE.Vector2(), []); const mouse = useMemo(() => new THREE.Vector2(), []);
const handleClick = useCallback((event: MouseEvent) => { useEffect(() => {
if (!measureMode) return; if (!measureMode) {
gl.domElement.style.cursor = 'grab';
// Use snap point if available
if (snapPoint) {
addMeasurePoint({ x: snapPoint.x, y: snapPoint.y, z: snapPoint.z });
return; return;
} }
gl.domElement.style.cursor = 'crosshair';
const rect = gl.domElement.getBoundingClientRect(); const canvas = gl.domElement;
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; let downX = 0, downY = 0, downBtn = 0;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; let armed = false;
raycaster.setFromCamera(mouse, camera); const onDown = (e: PointerEvent) => {
const intersects = raycaster.intersectObjects(scene.children, true); if (e.button !== 0) { armed = false; return; }
downX = e.clientX;
downY = e.clientY;
downBtn = e.button;
armed = true;
};
const hit = intersects.find(i => { const onUp = (e: PointerEvent) => {
const obj = i.object; if (!armed || e.button !== downBtn) return;
if (obj instanceof THREE.GridHelper) return false; armed = false;
if (obj instanceof THREE.Mesh) { const dx = e.clientX - downX;
const dy = e.clientY - downY;
if (Math.hypot(dx, dy) > 4) return; // it was a drag, not a click
const st = useModelStore.getState();
if (!st.measureMode) return;
// Prefer snap
if (st.snapPoint) {
st.addMeasurePoint({ x: st.snapPoint.x, y: st.snapPoint.y, z: st.snapPoint.z });
return;
}
const rect = canvas.getBoundingClientRect();
mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
const hit = intersects.find(i => {
const obj = i.object;
if (!(obj instanceof THREE.Mesh)) return false;
if (obj.userData.__edgeLine) return false;
if (obj.geometry instanceof THREE.SphereGeometry) return false; if (obj.geometry instanceof THREE.SphereGeometry) return false;
if (obj.geometry instanceof THREE.RingGeometry) return false; if (obj.geometry instanceof THREE.RingGeometry) return false;
if (obj.geometry instanceof THREE.PlaneGeometry) return false;
return true;
});
if (hit) {
st.addMeasurePoint({ x: hit.point.x, y: hit.point.y, z: hit.point.z });
} }
return true; };
});
if (hit) { const onKey = (e: KeyboardEvent) => {
addMeasurePoint({ x: hit.point.x, y: hit.point.y, z: hit.point.z }); const st = useModelStore.getState();
} if (!st.measureMode) return;
}, [measureMode, addMeasurePoint, snapPoint, camera, scene, gl, raycaster, mouse]); if (e.key === 'Escape') {
if (st.measurePoints.length > 0) st.undoLastMeasurePoint();
} else if ((e.key === 'z' || e.key === 'Z') && !e.ctrlKey && !e.metaKey) {
st.undoLastMeasurement();
}
};
useEffect(() => { canvas.addEventListener('pointerdown', onDown);
const canvas = gl.domElement; canvas.addEventListener('pointerup', onUp);
canvas.addEventListener('click', handleClick); window.addEventListener('keydown', onKey);
return () => canvas.removeEventListener('click', handleClick); return () => {
}, [gl, handleClick]); canvas.removeEventListener('pointerdown', onDown);
canvas.removeEventListener('pointerup', onUp);
// Change cursor when in measure mode window.removeEventListener('keydown', onKey);
useEffect(() => { gl.domElement.style.cursor = 'grab';
gl.domElement.style.cursor = measureMode ? 'crosshair' : 'grab'; };
return () => { gl.domElement.style.cursor = 'grab'; }; }, [measureMode, camera, scene, gl, raycaster, mouse]);
}, [measureMode, gl]);
return null; return null;
} }
function GridLayer() { function GridLayer() {
const showGrid = useModelStore((s) => s.showGrid); const showGrid = useModelStore((s) => s.showGrid);
const gridY = useModelStore((s) => s.gridY); const gridY = useModelStore((s) => s.gridY);