Skip to content
Open
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
6 changes: 6 additions & 0 deletions src/data/planets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
17 changes: 12 additions & 5 deletions src/pages/Simulation/components/Explosion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<THREE.Group | null>(null);
const [fragments, setFragments] = useState<Fragment[]>([]);

Expand Down Expand Up @@ -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[] = [];
Expand All @@ -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);
Expand Down
28 changes: 24 additions & 4 deletions src/pages/Simulation/components/PlanetMesh.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,24 @@ type PlanetMeshProps = {
planetRegistry: React.MutableRefObject<
Map<
string,
{ mesh: THREE.Mesh; position: React.MutableRefObject<number[]> }
{
mesh: THREE.Mesh;
position: React.MutableRefObject<number[]>;
velocity: React.MutableRefObject<number[]>;
}
>
>;
onExplosion: (position: THREE.Vector3, radius: number) => void;
onSelect: (planetId: string) => void;
timeScale: number;
};

export function PlanetMesh({
planet,
planetRegistry,
onExplosion,
onSelect,
timeScale,
}: PlanetMeshProps) {
const [ref, api] = useSphere<THREE.Mesh>(
() => ({
Expand Down Expand Up @@ -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(() => {
Expand All @@ -79,6 +96,7 @@ export function PlanetMesh({
planetRegistry.current.set(planet.id, {
mesh: ref.current,
position,
velocity,
});
}
return () => {
Expand All @@ -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(), []);
Expand All @@ -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); // 毎フレームリセットして使い回す
Expand All @@ -125,6 +144,7 @@ export function PlanetMesh({
}

// 計算した力を重心に適用
forceAccumulator.multiplyScalar(timeScale * timeScale);
api.applyForce(forceAccumulator.toArray(), myPosVec.toArray());
});

Expand Down
80 changes: 76 additions & 4 deletions src/pages/Simulation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ export default function Page() {
const planetRegistry = useRef<
Map<
string,
{ mesh: THREE.Mesh; position: React.MutableRefObject<number[]> }
{
mesh: THREE.Mesh;
position: React.MutableRefObject<number[]>;
velocity: React.MutableRefObject<number[]>;
}
>
>(new Map());

Expand Down Expand Up @@ -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 },
}),
);

Expand All @@ -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(
Expand All @@ -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,
Expand All @@ -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,
},
]);
Expand All @@ -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],
Expand All @@ -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) => {
Expand Down Expand Up @@ -192,12 +242,13 @@ export default function Page() {

<Physics gravity={[0, 0, 0]}>
{planets.map((planet) => (
<Suspense key={planet.id} fallback={null}>
<Suspense key={`${planet.id}-${planet.radius}`} fallback={null}>
<PlanetMesh
planet={planet}
planetRegistry={planetRegistry}
onExplosion={handleExplosion}
onSelect={(id) => setFollowedPlanetId(id)}
timeScale={timeScale}
/>
</Suspense>
))}
Expand All @@ -219,7 +270,7 @@ export default function Page() {
{showAxes && <axesHelper args={[20]} />}

{explosions.map((exp) => (
<Explosion key={exp.id} explosion={exp} />
<Explosion key={exp.id} explosion={exp} timeScale={timeScale} />
))}

{/* Optional background and controls */}
Expand Down Expand Up @@ -306,6 +357,27 @@ export default function Page() {
{planet.position.y.toFixed(1)},{" "}
{planet.position.z.toFixed(1)})
</div>
<div className="mt-2 flex items-center gap-2 text-xs">
<label className="flex flex-1 items-center gap-2">
半径
<input
type="range"
min={0.2}
max={20}
step={0.1}
value={planet.radius}
onChange={(event) =>
updatePlanetRadius(
planet.id,
Number(event.target.value),
)
}
/>
</label>
<span className="w-10 text-right">
{planet.radius.toFixed(1)}
</span>
</div>
</div>
<div className="flex shrink-0 items-center gap-2">
{followedPlanetId === planet.id ? (
Expand Down
4 changes: 4 additions & 0 deletions src/types/planet.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down