diff --git a/src/data/planets.ts b/src/data/planets.ts index 0cad35f..2380ff4 100644 --- a/src/data/planets.ts +++ b/src/data/planets.ts @@ -9,6 +9,7 @@ import type { Planet } from "@/types/planet"; export const earth: Planet = { id: "initial-earth", name: "Earth", + kind: "rocky", texturePath: earthTexture, rotationSpeedY: 2, radius: 2, @@ -22,6 +23,7 @@ export const earth: Planet = { export const testPlanet: Planet = { id: "test-planet", name: "TestPlanet", + kind: "rocky", texturePath: earthTexture, rotationSpeedY: 2, radius: 2, @@ -35,6 +37,7 @@ export const testPlanet: Planet = { export const sun: Planet = { id: "sun", name: "Sun", + kind: "star", texturePath: sunTexture, rotationSpeedY: 0.1, radius: 10, @@ -48,6 +51,7 @@ export const sun: Planet = { export const mars: Planet = { id: "mars", name: "Mars", + kind: "rocky", texturePath: marsTexture, rotationSpeedY: 1.5, radius: 1.5, @@ -61,6 +65,7 @@ export const mars: Planet = { export const jupiter: Planet = { id: "jupiter", name: "Jupiter", + kind: "gas", texturePath: jupiterTexture, rotationSpeedY: 1.2, radius: 5, @@ -74,6 +79,7 @@ export const jupiter: Planet = { export const venus: Planet = { id: "venus", name: "Venus", + kind: "rocky", texturePath: venusTexture, rotationSpeedY: 1.8, radius: 1.8, diff --git a/src/pages/Simulation/components/Explosion.tsx b/src/pages/Simulation/components/Explosion.tsx index 65d2218..80f1031 100644 --- a/src/pages/Simulation/components/Explosion.tsx +++ b/src/pages/Simulation/components/Explosion.tsx @@ -14,9 +14,14 @@ type Fragment = { type ExplosionProps = { explosion: ExplosionData; onComplete?: () => void; + timeScale: number; }; -export function Explosion({ explosion, onComplete }: ExplosionProps) { +export function Explosion({ + explosion, + onComplete, + timeScale, +}: ExplosionProps) { const groupRef = useRef(null); const [fragments, setFragments] = useState([]); @@ -82,6 +87,8 @@ export function Explosion({ explosion, onComplete }: ExplosionProps) { // フレームごとの更新 useFrame((_, delta) => { if (fragments.length === 0) return; + const scaledDelta = delta * timeScale; + const damping = 0.98 ** timeScale; setFragments((prev) => { const alive: Fragment[] = []; @@ -90,16 +97,16 @@ export function Explosion({ explosion, onComplete }: ExplosionProps) { const f = prev[i]; // 位置更新 - f.mesh.position.addScaledVector(f.velocity, delta); + f.mesh.position.addScaledVector(f.velocity, scaledDelta); // 回転 - f.mesh.rotateOnAxis(f.rotationAxis, delta * 5); + f.mesh.rotateOnAxis(f.rotationAxis, scaledDelta * 5); // 減速(摩擦的) - f.velocity.multiplyScalar(0.98); + f.velocity.multiplyScalar(damping); // 減衰 - f.lifetime -= delta; + f.lifetime -= scaledDelta; if (f.lifetime > 0) { alive.push(f); diff --git a/src/pages/Simulation/components/PlanetMesh.tsx b/src/pages/Simulation/components/PlanetMesh.tsx index 7c310d4..846b5ce 100644 --- a/src/pages/Simulation/components/PlanetMesh.tsx +++ b/src/pages/Simulation/components/PlanetMesh.tsx @@ -12,11 +12,16 @@ type PlanetMeshProps = { planetRegistry: React.MutableRefObject< Map< string, - { mesh: THREE.Mesh; position: React.MutableRefObject } + { + mesh: THREE.Mesh; + position: React.MutableRefObject; + velocity: React.MutableRefObject; + } > >; onExplosion: (position: THREE.Vector3, radius: number) => void; onSelect: (planetId: string) => void; + timeScale: number; }; export function PlanetMesh({ @@ -24,6 +29,7 @@ export function PlanetMesh({ planetRegistry, onExplosion, onSelect, + timeScale, }: PlanetMeshProps) { const [ref, api] = useSphere( () => ({ @@ -58,12 +64,23 @@ export function PlanetMesh({ planet.position.y, planet.position.z, ]); + const velocity = useRef([ + planet.velocity.x, + planet.velocity.y, + planet.velocity.z, + ]); useEffect(() => { const unsubscribe = api.position.subscribe((v) => { position.current = v; }); return () => unsubscribe(); // アンマウント時に購読解除 }, [api.position]); + useEffect(() => { + const unsubscribe = api.velocity.subscribe((v) => { + velocity.current = v; + }); + return () => unsubscribe(); + }, [api.velocity]); // マウント時に自分のMeshをレジストリに登録し、他の惑星から参照できるようにする useEffect(() => { @@ -79,6 +96,7 @@ export function PlanetMesh({ planetRegistry.current.set(planet.id, { mesh: ref.current, position, + velocity, }); } return () => { @@ -88,6 +106,10 @@ export function PlanetMesh({ }; }, [planet.id, planetRegistry, planet.mass, planet.radius, ref]); + useEffect(() => { + api.angularVelocity.set(0, planet.rotationSpeedY * timeScale, 0); + }, [api.angularVelocity, planet.rotationSpeedY, timeScale]); + // 計算用ベクトルをメモリに保持しておく(毎フレームnewしないため) const forceAccumulator = useMemo(() => new THREE.Vector3(), []); const myPosVec = useMemo(() => new THREE.Vector3(), []); @@ -97,9 +119,6 @@ export function PlanetMesh({ useFrame(() => { if (!ref.current || !planetRegistry.current) return; - // 誤差による自転速度の異常上昇を防ぐ - api.angularVelocity.set(0, planet.rotationSpeedY, 0); - // ref.current.positionの代わりに、物理エンジンから取得した位置を使用 myPosVec.fromArray(position.current); forceAccumulator.set(0, 0, 0); // 毎フレームリセットして使い回す @@ -125,6 +144,7 @@ export function PlanetMesh({ } // 計算した力を重心に適用 + forceAccumulator.multiplyScalar(timeScale * timeScale); api.applyForce(forceAccumulator.toArray(), myPosVec.toArray()); }); diff --git a/src/pages/Simulation/index.tsx b/src/pages/Simulation/index.tsx index 6d964c2..1e8a68c 100644 --- a/src/pages/Simulation/index.tsx +++ b/src/pages/Simulation/index.tsx @@ -37,7 +37,11 @@ export default function Page() { const planetRegistry = useRef< Map< string, - { mesh: THREE.Mesh; position: React.MutableRefObject } + { + mesh: THREE.Mesh; + position: React.MutableRefObject; + velocity: React.MutableRefObject; + } > >(new Map()); @@ -75,6 +79,9 @@ export default function Page() { posY: { value: 0, min: -200, max: 200, step: 0.2 }, posZ: { value: 0, min: -200, max: 200, step: 0.2 }, rotationSpeedY: { value: 0.6, min: 0, max: 10, step: 0.1 }, + velX: { value: 0, min: -10, max: 10, step: 0.1 }, + velY: { value: 0, min: -10, max: 10, step: 0.1 }, + velZ: { value: 0, min: -10, max: 10, step: 0.1 }, }), ); @@ -90,6 +97,9 @@ export default function Page() { posY: getPlanetControl("posY"), posZ: getPlanetControl("posZ"), rotationSpeedY: getPlanetControl("rotationSpeedY"), + velX: getPlanetControl("velX"), + velY: getPlanetControl("velY"), + velZ: getPlanetControl("velZ"), }; const newMass = computeMass( @@ -103,6 +113,7 @@ export default function Page() { { id: crypto.randomUUID(), name: template.name, + kind: template.kind, texturePath: template.texturePath, rotationSpeedY: settings.rotationSpeedY, radius: settings.radius, @@ -113,7 +124,11 @@ export default function Page() { settings.posY, settings.posZ, ), - velocity: new THREE.Vector3(0, 0, 0), + velocity: new THREE.Vector3( + settings.velX, + settings.velY, + settings.velZ, + ), mass: newMass, }, ]); @@ -133,6 +148,10 @@ export default function Page() { }), }); + const { timeScale } = useControls("Simulation", { + timeScale: { value: 1, min: 0.1, max: 5, step: 0.1 }, + }); + const previewPosition = useMemo<[number, number, number]>( () => [planetControls.posX, planetControls.posY, planetControls.posZ], [planetControls.posX, planetControls.posY, planetControls.posZ], @@ -153,6 +172,37 @@ export default function Page() { } }; + const updatePlanetRadius = (planetId: string, radius: number) => { + const registryEntry = planetRegistry.current.get(planetId); + const nextPosition = registryEntry?.position.current; + const nextVelocity = registryEntry?.velocity.current; + + setPlanets((prev) => + prev.map((planet) => { + if (planet.id !== planetId) return planet; + + return { + ...planet, + radius, + position: nextPosition + ? new THREE.Vector3( + nextPosition[0], + nextPosition[1], + nextPosition[2], + ) + : planet.position, + velocity: nextVelocity + ? new THREE.Vector3( + nextVelocity[0], + nextVelocity[1], + nextVelocity[2], + ) + : planet.velocity, + }; + }), + ); + }; + const handleExplosion = (position: THREE.Vector3, radius: number) => { // 連続爆発を防ぐための簡易的なデバウンス処理などをここに追加しても良い setExplosions((prev) => { @@ -192,12 +242,13 @@ export default function Page() { {planets.map((planet) => ( - + setFollowedPlanetId(id)} + timeScale={timeScale} /> ))} @@ -219,7 +270,7 @@ export default function Page() { {showAxes && } {explosions.map((exp) => ( - + ))} {/* Optional background and controls */} @@ -306,6 +357,27 @@ export default function Page() { {planet.position.y.toFixed(1)},{" "} {planet.position.z.toFixed(1)}) +
+ + + {planet.radius.toFixed(1)} + +
{followedPlanetId === planet.id ? ( diff --git a/src/types/planet.ts b/src/types/planet.ts index 9cf5d91..2b4ec51 100644 --- a/src/types/planet.ts +++ b/src/types/planet.ts @@ -1,7 +1,11 @@ import type * as THREE from "three"; + +export type PlanetKind = "rocky" | "gas" | "star"; + export type Planet = { id: string; name: string; + kind: PlanetKind; texturePath: string; rotationSpeedY: number; radius: number;