zhuyuan-ice-gl-zy001/ZCODE-DEV-20260826/public-spine/shell-frozen-src/audit/rejected-language-world-v2-20260825/LivingPlanet.tsx

89 lines
3.3 KiB
TypeScript

import { useEffect, useRef } from 'react'
import * as THREE from 'three'
import surfaceUrl from '../../assets/language-world-v2/hololake-world-surface.png'
interface LivingPlanetProps { awake: boolean }
export function LivingPlanet({ awake }: LivingPlanetProps) {
const mountRef = useRef<HTMLDivElement>(null)
const awakeRef = useRef(awake)
awakeRef.current = awake
useEffect(() => {
const mount = mountRef.current
if (!mount) return
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(34, 1, 0.1, 100)
camera.position.z = 3.2
const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, powerPreference: 'high-performance' })
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.outputColorSpace = THREE.SRGBColorSpace
renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.toneMappingExposure = 0.92
mount.appendChild(renderer.domElement)
const group = new THREE.Group()
group.rotation.z = -0.08
scene.add(group)
const texture = new THREE.TextureLoader().load(surfaceUrl)
texture.colorSpace = THREE.SRGBColorSpace
texture.anisotropy = renderer.capabilities.getMaxAnisotropy()
const planet = new THREE.Mesh(
new THREE.SphereGeometry(1, 128, 128),
new THREE.MeshStandardMaterial({ map: texture, roughness: 0.78, metalness: 0.02 }),
)
group.add(planet)
scene.add(new THREE.HemisphereLight('#bfefff', '#010711', 1.25))
const sun = new THREE.DirectionalLight('#e8fbff', 2.75)
sun.position.set(-3.2, 2.2, 4.8)
scene.add(sun)
const lakeLight = new THREE.PointLight('#47d8e9', 0.72, 8)
lakeLight.position.set(2.8, 1.8, 3)
scene.add(lakeLight)
let pointerX = 0
let pointerY = 0
const onPointer = (event: PointerEvent) => {
pointerX = (event.clientX / window.innerWidth - 0.5) * 0.12
pointerY = (event.clientY / window.innerHeight - 0.5) * 0.08
}
window.addEventListener('pointermove', onPointer, { passive: true })
const resize = () => {
const width = mount.clientWidth || 720
const height = mount.clientHeight || 720
renderer.setSize(width, height, false)
camera.aspect = width / height
camera.updateProjectionMatrix()
}
const observer = new ResizeObserver(resize)
observer.observe(mount)
resize()
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches
let animation = 0
let previous = performance.now()
const frame = (now: number) => {
const delta = Math.min((now - previous) / 1000, 0.05)
previous = now
if (!reduced) planet.rotation.y += delta * (awakeRef.current ? 0.065 : 0.038)
group.rotation.y += (pointerX - group.rotation.y) * 0.025
group.rotation.x += (-pointerY - group.rotation.x) * 0.025
renderer.render(scene, camera)
animation = requestAnimationFrame(frame)
}
animation = requestAnimationFrame(frame)
return () => {
cancelAnimationFrame(animation)
observer.disconnect()
window.removeEventListener('pointermove', onPointer)
texture.dispose()
planet.geometry.dispose()
;(planet.material as THREE.Material).dispose()
renderer.dispose()
renderer.domElement.remove()
}
}, [])
return <div className="living-planet" ref={mountRef} aria-label="正在转动的光湖世界星球"/>
}