Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-05-20 18:48:49 +00:00
parent 56d1ca4222
commit a93db3db7a
+129
View File
@@ -504,9 +504,138 @@ function GridAutoFollower() {
}, 100); }, 100);
return () => clearTimeout(id); return () => clearTimeout(id);
}, [models, scene]); }, [models, scene]);
/** Mouse drag handler for desktop "Posicionar" mode: translates/rotates the active model. */
function PositionDragHandler() {
const { camera, gl, scene } = useThree();
const positionMode = useModelStore((s) => s.positionMode);
const activeId = useModelStore((s) => s.activeModelId);
useEffect(() => {
if (!positionMode) {
gl.domElement.style.cursor = 'grab';
return;
}
gl.domElement.style.cursor = 'move';
const canvas = gl.domElement;
let dragging = false;
let button = 0;
let shiftKey = false;
let lastX = 0;
let lastY = 0;
let pixelsPerWorldUnit = 1;
const computePixelScale = () => {
const st = useModelStore.getState();
const active = st.models.find((m) => m.id === st.activeModelId);
if (!active) return;
// Find the model's group position (approx world): use renderFactor + fineTuning pos
const factor = st.scaleRatio?.factor ?? 1;
const wp = new THREE.Vector3(
active.fineTuning.posX * factor,
active.fineTuning.posY * factor,
active.fineTuning.posZ * factor,
);
const dist = camera.position.distanceTo(wp);
const perspCam = camera as THREE.PerspectiveCamera;
const fov = (perspCam.fov ?? 50) * (Math.PI / 180);
const worldPerPixel = (2 * dist * Math.tan(fov / 2)) / canvas.clientHeight;
pixelsPerWorldUnit = worldPerPixel; // world units per pixel
};
const onDown = (e: PointerEvent) => {
const st = useModelStore.getState();
if (!st.positionMode || !st.activeModelId) return;
dragging = true;
button = e.button;
shiftKey = e.shiftKey;
lastX = e.clientX;
lastY = e.clientY;
computePixelScale();
(e.target as Element).setPointerCapture?.(e.pointerId);
e.preventDefault();
};
const onMove = (e: PointerEvent) => {
if (!dragging) return;
const st = useModelStore.getState();
const active = st.models.find((m) => m.id === st.activeModelId);
if (!active) return;
const dx = e.clientX - lastX;
const dy = e.clientY - lastY;
lastX = e.clientX;
lastY = e.clientY;
shiftKey = e.shiftKey;
const ft = active.fineTuning;
const factor = st.scaleRatio?.factor ?? 1;
if (button === 2) {
// Right button: rotate
const sens = 0.4; // deg per pixel
if (shiftKey) {
useModelStore.getState().setFineTuning({ rotZ: ft.rotZ + dx * sens });
} else {
useModelStore.getState().setFineTuning({
rotY: ft.rotY + dx * sens,
rotX: ft.rotX + dy * sens,
});
}
} else if (button === 1 || (button === 0 && shiftKey)) {
// Middle or Shift+left: depth (Z in camera space)
const worldDelta = dy * pixelsPerWorldUnit;
const camDir = new THREE.Vector3();
camera.getWorldDirection(camDir);
const move = camDir.multiplyScalar(worldDelta).divideScalar(factor);
useModelStore.getState().setFineTuning({
posX: ft.posX + move.x,
posY: ft.posY + move.y,
posZ: ft.posZ + move.z,
});
} else {
// Left: translate in camera plane
const right = new THREE.Vector3();
const up = new THREE.Vector3();
camera.matrixWorld.extractBasis(right, up, new THREE.Vector3());
const worldDX = dx * pixelsPerWorldUnit;
const worldDY = -dy * pixelsPerWorldUnit;
const move = right.multiplyScalar(worldDX).add(up.multiplyScalar(worldDY)).divideScalar(factor);
useModelStore.getState().setFineTuning({
posX: ft.posX + move.x,
posY: ft.posY + move.y,
posZ: ft.posZ + move.z,
});
}
};
const onUp = (e: PointerEvent) => {
dragging = false;
(e.target as Element).releasePointerCapture?.(e.pointerId);
};
const onContext = (e: MouseEvent) => e.preventDefault();
canvas.addEventListener('pointerdown', onDown);
canvas.addEventListener('pointermove', onMove);
canvas.addEventListener('pointerup', onUp);
canvas.addEventListener('pointercancel', onUp);
canvas.addEventListener('contextmenu', onContext);
return () => {
canvas.removeEventListener('pointerdown', onDown);
canvas.removeEventListener('pointermove', onMove);
canvas.removeEventListener('pointerup', onUp);
canvas.removeEventListener('pointercancel', onUp);
canvas.removeEventListener('contextmenu', onContext);
gl.domElement.style.cursor = 'grab';
};
}, [positionMode, activeId, camera, gl, scene]);
return null; return null;
} }
export function ModelViewerCanvas({ url }: ModelViewerProps) { export function ModelViewerCanvas({ url }: ModelViewerProps) {
return ( return (
<Canvas <Canvas