Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 15 additions & 48 deletions src/pages/Simulation/components/CameraController.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import { useFrame, useThree } from "@react-three/fiber";
import { useMemo, useRef } from "react";
import * as THREE from "three";
import { useEffect, useMemo } from "react";
import type { OrbitControls } from "three-stdlib";

// 惑星レジストリのエントリの型を定義
type PlanetRegistryEntry = {
mesh: THREE.Mesh;
position: React.MutableRefObject<number[]>;
};
import { CameraFollowController } from "../core/CameraFollowController";
import type { PlanetRegistry } from "../core/PlanetRegistry";

type CameraControllerProps = {
followedPlanetId: string | null;
planetRegistry: React.MutableRefObject<Map<string, PlanetRegistryEntry>>;
planetRegistry: PlanetRegistry;
orbitControlsRef: React.MutableRefObject<OrbitControls | null>;
};

Expand All @@ -21,47 +16,19 @@ export function CameraController({
orbitControlsRef,
}: CameraControllerProps) {
const { camera } = useThree();
// useMemo を使ってベクターを初期化し、毎フレームのインスタンス生成を避ける
const previousPos = useMemo(() => new THREE.Vector3(), []);
const currentPos = useMemo(() => new THREE.Vector3(), []);
const delta = useMemo(() => new THREE.Vector3(), []);
const hasPrev = useRef(false);
// 前回のフレームで追尾していた惑星IDを保持する
const prevFollowedPlanetId = useRef<string | null>(null);

useFrame(() => {
// 追尾対象が変更されたかチェック
if (prevFollowedPlanetId.current !== followedPlanetId) {
// 変更された場合、カメラ移動の差分計算をリセット
hasPrev.current = false;
prevFollowedPlanetId.current = followedPlanetId;
}

const controls = orbitControlsRef.current;
if (!controls) return;
const followController = useMemo(() => new CameraFollowController(), []);

// 追尾対象がなければ何もしない
if (!followedPlanetId) return;
useEffect(() => {
return () => followController.reset();
}, [followController]);

// 毎フレーム、レジストリから最新の追尾対象を取得
const target = planetRegistry.current.get(followedPlanetId);
if (!target) return;

// number[] を Vector3 にセット (fromArray を使用してより簡潔に)
currentPos.fromArray(target.position.current);

if (hasPrev.current) {
delta.copy(currentPos).sub(previousPos);
camera.position.add(delta);
}

// OrbitControlsのターゲット更新
controls.target.copy(currentPos);
controls.update();

// 前回位置更新
previousPos.copy(currentPos);
hasPrev.current = true;
useFrame(() => {
followController.update({
followedPlanetId,
planetRegistry,
camera,
controls: orbitControlsRef.current,
});
});
return null;
}
5 changes: 2 additions & 3 deletions src/pages/Simulation/components/Explosion.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useFrame } from "@react-three/fiber";
import { useEffect, useRef, useState } from "react";
import { useEffect, useState } from "react";
import * as THREE from "three";
import type { ExplosionData } from "@/types/Explosion";

Expand All @@ -17,7 +17,6 @@ type ExplosionProps = {
};

export function Explosion({ explosion, onComplete }: ExplosionProps) {
const groupRef = useRef<THREE.Group | null>(null);
const [fragments, setFragments] = useState<Fragment[]>([]);

// 爆発初期化
Expand Down Expand Up @@ -118,7 +117,7 @@ export function Explosion({ explosion, onComplete }: ExplosionProps) {
});

return (
<group ref={groupRef}>
<group>
{fragments.map((f) => (
<primitive key={f.id} object={f.mesh} />
))}
Expand Down
112 changes: 44 additions & 68 deletions src/pages/Simulation/components/PlanetMesh.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import { useSphere } from "@react-three/cannon";
import { Trail, useTexture } from "@react-three/drei";
import { useFrame } from "@react-three/fiber";
import type React from "react";
import { useEffect, useMemo, useRef } from "react";
import { useEffect, useMemo } from "react";
import * as THREE from "three";
import type { Planet } from "@/types/planet";
import { calcGravityForce } from "../utils/gravityUtils";
import { GravitySystem } from "../core/GravitySystem";
import type { PlanetRegistry, PositionRef } from "../core/PlanetRegistry";

type PlanetMeshProps = {
planet: Planet;
planetRegistry: React.MutableRefObject<
Map<
string,
{ mesh: THREE.Mesh; position: React.MutableRefObject<number[]> }
>
>;
planetRegistry: PlanetRegistry;
onExplosion: (position: THREE.Vector3, radius: number) => void;
onSelect: (planetId: string) => void;
};
Expand All @@ -25,104 +20,85 @@ export function PlanetMesh({
onExplosion,
onSelect,
}: PlanetMeshProps) {
const [ref, api] = useSphere<THREE.Mesh>(
() => ({
mass: planet.mass,
args: [planet.radius],
position: [planet.position.x, planet.position.y, planet.position.z],
velocity: [planet.velocity.x, planet.velocity.y, planet.velocity.z],
angularVelocity: [0, planet.rotationSpeedY, 0], // 物理エンジンでY軸周りの角速度を設定
linearDamping: 0, // 宇宙空間なので抵抗なし
angularDamping: 0, // 宇宙空間なので回転の減衰もない
onCollide: (e) => {
// 衝突時の衝撃が一定以上なら爆発とみなす
if (e.contact.impactVelocity > 0.5) {
const contactPoint = new THREE.Vector3(
e.contact.contactPoint[0],
e.contact.contactPoint[1],
e.contact.contactPoint[2],
);
onExplosion(contactPoint, planet.radius);
}
},
}),
useRef<THREE.Mesh>(null),
);
const [ref, api] = useSphere<THREE.Mesh>(() => ({
mass: planet.mass,
args: [planet.radius],
position: [planet.position.x, planet.position.y, planet.position.z],
velocity: [planet.velocity.x, planet.velocity.y, planet.velocity.z],
angularVelocity: [0, planet.rotationSpeedY, 0], // 物理エンジンでY軸周りの角速度を設定
linearDamping: 0, // 宇宙空間なので抵抗なし
angularDamping: 0, // 宇宙空間なので回転の減衰もない
onCollide: (e) => {
// 衝突時の衝撃が一定以上なら爆発とみなす
if (e.contact.impactVelocity > 0.5) {
const contactPoint = new THREE.Vector3(
e.contact.contactPoint[0],
e.contact.contactPoint[1],
e.contact.contactPoint[2],
);
onExplosion(contactPoint, planet.radius);
}
},
}));

// Load the texture (you can use any public Earth texture URL)
const [colorMap] = useTexture([planet.texturePath]);

// 物理エンジンの位置を追跡するためのref
const position = useRef([
planet.position.x,
planet.position.y,
planet.position.z,
]);
const position = useMemo<PositionRef>(
() => ({
current: [planet.position.x, planet.position.y, planet.position.z],
}),
[planet.position.x, planet.position.y, planet.position.z],
);
useEffect(() => {
const unsubscribe = api.position.subscribe((v) => {
position.current = v;
});
return () => unsubscribe(); // アンマウント時に購読解除
}, [api.position]);
}, [api.position, position]);

// マウント時に自分のMeshをレジストリに登録し、他の惑星から参照できるようにする
useEffect(() => {
if (!planetRegistry.current) return;

if (ref.current) {
// 質量計算用にuserDataに保存
ref.current.userData = {
mass: planet.mass,
id: planet.id,
radius: planet.radius,
};
planetRegistry.current.set(planet.id, {
planetRegistry.register(planet.id, {
mesh: ref.current,
position,
});
}
return () => {
if (planetRegistry.current) {
planetRegistry.current.delete(planet.id);
}
planetRegistry.unregister(planet.id);
};
}, [planet.id, planetRegistry, planet.mass, planet.radius, ref]);
}, [planet.id, planetRegistry, planet.mass, planet.radius, ref, position]);

// 計算用ベクトルをメモリに保持しておく(毎フレームnewしないため)
const gravitySystem = useMemo(() => new GravitySystem(), []);
const forceAccumulator = useMemo(() => new THREE.Vector3(), []);
const myPosVec = useMemo(() => new THREE.Vector3(), []);
const otherPosVec = useMemo(() => new THREE.Vector3(), []);

// This hook runs every frame (approx 60fps)
useFrame(() => {
if (!ref.current || !planetRegistry.current) return;
if (!ref.current) return;

// 誤差による自転速度の異常上昇を防ぐ
api.angularVelocity.set(0, planet.rotationSpeedY, 0);

// ref.current.positionの代わりに、物理エンジンから取得した位置を使用
myPosVec.fromArray(position.current);
forceAccumulator.set(0, 0, 0); // 毎フレームリセットして使い回す

// 他のすべての惑星からの引力を計算して合算
for (const [otherId, other] of planetRegistry.current) {
if (otherId === planet.id) continue;

const { mesh: otherMesh, position: otherPosition } = other;
otherPosVec.fromArray(otherPosition.current);
const otherMass = otherMesh.userData.mass || 1;
const otherRadius = otherMesh.userData.radius || 0.1;

const force = calcGravityForce(
myPosVec,
planet.mass,
planet.radius,
otherPosVec,
otherMass,
otherRadius,
);
forceAccumulator.add(force);
}
gravitySystem.accumulateForPlanet({
planetId: planet.id,
targetMass: planet.mass,
targetRadius: planet.radius,
targetPosition: myPosVec,
planetRegistry,
outForce: forceAccumulator,
});

// 計算した力を重心に適用
api.applyForce(forceAccumulator.toArray(), myPosVec.toArray());
Expand Down
Loading