Before the doublework change
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import * as THREE from 'three'
|
||||
|
||||
// A self-contained three.js stage that slowly turns a single model and lets the viewer
|
||||
// swipe to spin it faster, the momentum easing back to the idle rate — the same feel as
|
||||
// the inventory rack, reused for one-off showpieces like a merit award.
|
||||
export function SpinningModel({ build, className, cameraPos = [0.6, 0.4, 5], baseSpin = 0.008 }: {
|
||||
build: () => THREE.Group
|
||||
className?: string
|
||||
cameraPos?: [number, number, number]
|
||||
baseSpin?: number
|
||||
}) {
|
||||
const stageRef = useRef<HTMLDivElement>(null)
|
||||
const [camX, camY, camZ] = cameraPos
|
||||
|
||||
useEffect(() => {
|
||||
const stage = stageRef.current
|
||||
if (!stage) return
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
|
||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
|
||||
stage.appendChild(renderer.domElement)
|
||||
renderer.domElement.style.cssText = 'position:absolute;inset:0;width:100%;height:100%'
|
||||
|
||||
const scene = new THREE.Scene()
|
||||
const camera = new THREE.PerspectiveCamera(34, 1, 0.1, 100)
|
||||
camera.position.set(camX, camY, camZ)
|
||||
camera.lookAt(0, 0, 0)
|
||||
scene.add(new THREE.AmbientLight(0x40483a, 0.9))
|
||||
const key = new THREE.DirectionalLight(0xfff4e0, 1.1); key.position.set(2, 3, 3); scene.add(key)
|
||||
const rim = new THREE.DirectionalLight(0x9fd020, 0.4); rim.position.set(-2, 1, -2); scene.add(rim)
|
||||
const holder = new THREE.Group(); holder.add(build()); scene.add(holder)
|
||||
|
||||
const resize = () => { const w = stage.clientWidth, h = stage.clientHeight; if (w && h) { camera.aspect = w / h; camera.updateProjectionMatrix(); renderer.setSize(w, h, false) } }
|
||||
resize(); const ro = new ResizeObserver(resize); ro.observe(stage)
|
||||
|
||||
// Drag to spin (see inventory rack): a swipe rotates 1:1 and hands its speed to `spin`.
|
||||
let spin = baseSpin
|
||||
let dragging = false
|
||||
let lastX = 0
|
||||
const dom = renderer.domElement
|
||||
dom.style.touchAction = 'none'
|
||||
dom.style.cursor = 'grab'
|
||||
const onDown = (event: PointerEvent) => { dragging = true; lastX = event.clientX; dom.style.cursor = 'grabbing'; dom.setPointerCapture(event.pointerId) }
|
||||
const onMove = (event: PointerEvent) => {
|
||||
if (!dragging) return
|
||||
const delta = (event.clientX - lastX) * 0.01
|
||||
lastX = event.clientX
|
||||
holder.rotation.y += delta
|
||||
spin = THREE.MathUtils.clamp(delta, -0.35, 0.35)
|
||||
}
|
||||
const onUp = (event: PointerEvent) => { dragging = false; dom.style.cursor = 'grab'; try { dom.releasePointerCapture(event.pointerId) } catch { /* already released */ } }
|
||||
dom.addEventListener('pointerdown', onDown)
|
||||
dom.addEventListener('pointermove', onMove)
|
||||
dom.addEventListener('pointerup', onUp)
|
||||
dom.addEventListener('pointercancel', onUp)
|
||||
|
||||
let raf = 0
|
||||
const tick = () => {
|
||||
if (!dragging) { holder.rotation.y += spin; spin += (baseSpin - spin) * 0.04 }
|
||||
renderer.render(scene, camera); raf = requestAnimationFrame(tick)
|
||||
}
|
||||
tick()
|
||||
return () => {
|
||||
cancelAnimationFrame(raf); ro.disconnect()
|
||||
dom.removeEventListener('pointerdown', onDown); dom.removeEventListener('pointermove', onMove)
|
||||
dom.removeEventListener('pointerup', onUp); dom.removeEventListener('pointercancel', onUp)
|
||||
renderer.dispose(); dom.remove()
|
||||
scene.traverse(o => { if (o instanceof THREE.Mesh) { o.geometry.dispose(); (o.material as THREE.Material).dispose() } })
|
||||
}
|
||||
}, [build, camX, camY, camZ, baseSpin])
|
||||
|
||||
return <div ref={stageRef} className={className || 'model3d-stage'} />
|
||||
}
|
||||
Reference in New Issue
Block a user