diff --git a/src/components/three/viewCubeBus.ts b/src/components/three/viewCubeBus.ts new file mode 100644 index 0000000..72d42fe --- /dev/null +++ b/src/components/three/viewCubeBus.ts @@ -0,0 +1,53 @@ +import * as THREE from 'three'; + +/** + * Shared bus between the main 3D viewer and the floating ViewCube widget. + * - mainCameraRef / mainControlsRef: written by ModelViewer each frame + * - requestView(dir): asks the main camera to animate to a canonical view + * along the given world-space direction (unit vector pointing FROM target + * TO camera). e.g. (0,1,0) = top view, (0,0,1) = front. + */ +export const mainCameraRef: { current: THREE.Camera | null } = { current: null }; +// OrbitControls instance — typed loosely to avoid drei type churn +export const mainControlsRef: { current: any | null } = { current: null }; + +export interface ViewAnim { + active: boolean; + startPos: THREE.Vector3; + endPos: THREE.Vector3; + startUp: THREE.Vector3; + endUp: THREE.Vector3; + t: number; + duration: number; +} + +export const viewAnim: ViewAnim = { + active: false, + startPos: new THREE.Vector3(), + endPos: new THREE.Vector3(), + startUp: new THREE.Vector3(0, 1, 0), + endUp: new THREE.Vector3(0, 1, 0), + t: 0, + duration: 0.5, +}; + +export function requestView(dir: THREE.Vector3) { + const cam = mainCameraRef.current; + const controls = mainControlsRef.current; + if (!cam || !controls) return; + const target: THREE.Vector3 = controls.target ?? new THREE.Vector3(); + const distance = cam.position.distanceTo(target); + const d = dir.clone().normalize(); + const endPos = target.clone().add(d.multiplyScalar(distance)); + // Up vector: keep Y up unless we're looking straight up/down + const up = new THREE.Vector3(0, 1, 0); + if (Math.abs(dir.y) > 0.99) { + up.set(0, 0, dir.y > 0 ? -1 : 1); + } + viewAnim.startPos.copy(cam.position); + viewAnim.endPos.copy(endPos); + viewAnim.startUp.copy(cam.up); + viewAnim.endUp.copy(up); + viewAnim.t = 0; + viewAnim.active = true; +}