working on panning, currently kinda glitchy

This commit is contained in:
csawatzky 2026-04-13 14:46:39 -06:00
parent f5db701d3c
commit e7ab6a2d76

View file

@ -1,5 +1,6 @@
import { useThree } from "@react-three/fiber"; import { useThree } from "@react-three/fiber";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { Plane, Raycaster, Vector2, Vector3 } from "three";
interface CameraTarget { interface CameraTarget {
x: number; x: number;
@ -25,8 +26,18 @@ interface Props {
export default function OrbitCameraControls(props: Props) { export default function OrbitCameraControls(props: Props) {
const { target, clampVerticalRotation, minPhi = 0.01, maxPhi = Math.PI - 0.01 } = props; const { target, clampVerticalRotation, minPhi = 0.01, maxPhi = Math.PI - 0.01 } = props;
const { camera, gl } = useThree(); const { camera, gl } = useThree();
const panPlane = useRef(new Plane());
const panStartPoint = useRef(new Vector3());
const raycaster = useRef(new Raycaster());
const mouse = useRef(new Vector2());
const targetRef = useRef({
x: target?.x ?? 0,
y: target?.y ?? 0,
z: target?.z ?? 0,
});
const isDragging = useRef(false); const isDragging = useRef(false);
const isPanning = useRef(false);
const lastPos = useRef({ x: 0, y: 0 }); const lastPos = useRef({ x: 0, y: 0 });
// Spherical coords // Spherical coords
@ -38,7 +49,7 @@ export default function OrbitCameraControls(props: Props) {
useEffect(() => { useEffect(() => {
const canvas = gl.domElement; const canvas = gl.domElement;
canvas.addEventListener("contextmenu", e => e.preventDefault());
const updateCamera = () => { const updateCamera = () => {
const { radius, theta, phi } = spherical.current; const { radius, theta, phi } = spherical.current;
@ -47,41 +58,111 @@ export default function OrbitCameraControls(props: Props) {
const z = radius * Math.sin(phi) * Math.cos(theta); const z = radius * Math.sin(phi) * Math.cos(theta);
camera.position.set(x, y, z); camera.position.set(x, y, z);
camera.lookAt(target?.x ?? 0, target?.y ?? 0, target?.z ?? 0); camera.lookAt(
targetRef.current.x,
targetRef.current.y,
targetRef.current.z
);
}; };
updateCamera(); updateCamera();
const handleMouseDown = (e: MouseEvent) => { const handleMouseDown = (e: MouseEvent) => {
if (e.target !== canvas) return; if (e.target !== canvas) return;
isDragging.current = true;
lastPos.current = { x: e.clientX, y: e.clientY }; lastPos.current = { x: e.clientX, y: e.clientY };
// Left click = rotate
if (e.button === 0) {
isDragging.current = true;
isPanning.current = false;
}
// Right click = pan
if (e.button === 2) {
isPanning.current = true;
isDragging.current = false;
// plane facing camera through target
const camDir = new Vector3();
camera.getWorldDirection(camDir);
panPlane.current.setFromNormalAndCoplanarPoint(
camDir,
new Vector3(
targetRef.current.x,
targetRef.current.y,
targetRef.current.z
)
);
// starting intersection point
raycaster.current.setFromCamera(
new Vector2(
(e.clientX / window.innerWidth) * 2 - 1,
-(e.clientY / window.innerHeight) * 2 + 1
),
camera
);
raycaster.current.ray.intersectPlane(
panPlane.current,
panStartPoint.current
);
}
}; };
const handleMouseMove = (e: MouseEvent) => {
if (!isDragging.current) return;
const handleMouseMove = (e: MouseEvent) => {
const deltaX = e.clientX - lastPos.current.x; const deltaX = e.clientX - lastPos.current.x;
const deltaY = e.clientY - lastPos.current.y; const deltaY = e.clientY - lastPos.current.y;
spherical.current.theta -= deltaX * 0.005; // ROTATE
spherical.current.phi -= deltaY * 0.005; if (isDragging.current) {
spherical.current.theta -= deltaX * 0.005;
spherical.current.phi -= deltaY * 0.005;
// Clamp vertical rotation (prevents flipping) if (clampVerticalRotation) {
if (clampVerticalRotation) { spherical.current.phi = Math.max(
spherical.current.phi = Math.max( minPhi,
minPhi, Math.min(maxPhi, spherical.current.phi)
Math.min(maxPhi, spherical.current.phi) );
}
}
// PAN
if (isPanning.current) {
const raycaster = new Raycaster();
mouse.current.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.current.y = -(e.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse.current, camera);
const currentPoint = new Vector3();
raycaster.ray.intersectPlane(panPlane.current, currentPoint);
const delta = new Vector3().subVectors(
panStartPoint.current,
currentPoint
); );
targetRef.current.x += delta.x;
targetRef.current.y += delta.y;
targetRef.current.z += delta.z;
panStartPoint.current.copy(currentPoint);
} }
lastPos.current = { x: e.clientX, y: e.clientY }; lastPos.current = { x: e.clientX, y: e.clientY };
updateCamera(); updateCamera();
}; };
const handleMouseUp = () => { const handleMouseUp = () => {
isDragging.current = false; isDragging.current = false;
isPanning.current = false;
}; };
const handleWheel = (e: WheelEvent) => { const handleWheel = (e: WheelEvent) => {