Skip to content

Commit d7e5933

Browse files
committed
複数の弾道を比較可能に
1 parent bd98078 commit d7e5933

4 files changed

Lines changed: 292 additions & 240 deletions

File tree

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import React, { useEffect, useState } from "react";
2+
import type { Condition } from "./type";
3+
import { reynoldsNumber, dragCoefficient } from "./utils";
4+
5+
export const InputCard = ({
6+
setCondition,
7+
}: {
8+
setCondition: React.Dispatch<React.SetStateAction<Condition | undefined>>;
9+
}) => {
10+
// 発射諸元
11+
const [initV, setInitV] = useState(1);
12+
const [angle, setAngle] = useState(45);
13+
const [yoffset, setYoffset] = useState(0);
14+
15+
// 球の諸元
16+
const [radius, setRadius] = useState(1);
17+
const [bulletDensity, setBulletDensity] = useState(1); // t/m^3 = g/cm^3
18+
const [mass, setMass] = useState(
19+
(4 / 3) * Math.PI * radius ** 3 * bulletDensity * 1000
20+
);
21+
22+
// 環境の諸元
23+
const [g, setG] = useState(9.81);
24+
const [fluidDensity, setFluidDensity] = useState(1.205); // kg/m^3 20℃1気圧の空気
25+
const [viscosity, setViscosity] = useState(1.822e-5); // Pa s 20℃1気圧の空気
26+
27+
// シミュレーション諸元
28+
const [deltaT, setDeltaT] = useState(1e-3);
29+
30+
// conditionの更新
31+
useEffect(() => {
32+
setCondition({
33+
initV,
34+
angle,
35+
yoffset,
36+
radius,
37+
bulletDensity,
38+
mass,
39+
g,
40+
fluidDensity,
41+
viscosity,
42+
deltaT,
43+
});
44+
}, [
45+
initV,
46+
angle,
47+
yoffset,
48+
radius,
49+
bulletDensity,
50+
mass,
51+
g,
52+
fluidDensity,
53+
viscosity,
54+
deltaT,
55+
]);
56+
57+
return (
58+
<div className="card flex flex-col gap-4">
59+
<h2 className="font-bold text-xl">諸元入力</h2>
60+
<div className="flex flex-col gap-4">
61+
<div>
62+
<h3 className="font-bold">発射諸元</h3>
63+
<div className="flex flex-row gap-4">
64+
<div>
65+
<label className="caption">初速 (m/s): </label>
66+
<input
67+
type="number"
68+
value={initV}
69+
onChange={(e) => setInitV(parseFloat(e.target.value))}
70+
className="border p-1 rounded w-full"
71+
/>
72+
</div>
73+
<div>
74+
<label className="caption">発射角度 (度): </label>
75+
<input
76+
type="number"
77+
value={angle}
78+
onChange={(e) => setAngle(parseFloat(e.target.value))}
79+
className="border p-1 rounded w-full"
80+
/>
81+
</div>
82+
<div>
83+
<label className="caption">発射高さ (m): </label>
84+
<input
85+
type="number"
86+
value={yoffset}
87+
onChange={(e) => setYoffset(parseFloat(e.target.value))}
88+
className="border p-1 rounded w-full"
89+
/>
90+
</div>
91+
</div>
92+
</div>
93+
<div>
94+
<h3 className="font-bold">球の諸元</h3>
95+
<div className="flex flex-row gap-4">
96+
<div>
97+
<label className="caption">球の半径 (m): </label>
98+
<input
99+
type="number"
100+
value={radius}
101+
onChange={(e) => {
102+
const r = parseFloat(e.target.value);
103+
setRadius(r);
104+
setMass((4 / 3) * Math.PI * r ** 3 * bulletDensity * 1000);
105+
}}
106+
className="border p-1 rounded w-full"
107+
/>
108+
</div>
109+
<div>
110+
<label className="caption">球の密度 (g/cm³): </label>
111+
<input
112+
type="number"
113+
value={bulletDensity}
114+
onChange={(e) => {
115+
const d = parseFloat(e.target.value);
116+
setBulletDensity(d);
117+
setMass((4 / 3) * Math.PI * radius ** 3 * d * 1000);
118+
}}
119+
className="border p-1 rounded w-full"
120+
/>
121+
</div>
122+
<div>
123+
<label className="caption">球の質量 (kg): </label>
124+
<input
125+
type="number"
126+
value={mass}
127+
onChange={(e) => {
128+
const m = parseFloat(e.target.value);
129+
setMass(m);
130+
setBulletDensity(
131+
m / ((4 / 3) * Math.PI * radius ** 3) / 1000
132+
);
133+
}}
134+
className="border p-1 rounded w-full"
135+
/>
136+
</div>
137+
</div>
138+
</div>
139+
<div>
140+
<h3 className="font-bold">環境諸元</h3>
141+
<div className="flex flex-row gap-4">
142+
<div>
143+
<label className="caption">重力加速度 (m/s²): </label>
144+
<input
145+
type="number"
146+
value={g}
147+
onChange={(e) => setG(parseFloat(e.target.value))}
148+
className="border p-1 rounded w-full"
149+
/>
150+
</div>
151+
<div>
152+
<label className="caption">流体密度 (kg/m³): </label>
153+
<input
154+
type="number"
155+
value={fluidDensity}
156+
onChange={(e) => setFluidDensity(parseFloat(e.target.value))}
157+
className="border p-1 rounded w-full"
158+
/>
159+
</div>
160+
<div>
161+
<label className="caption">粘性係数 (Pa·s): </label>
162+
<input
163+
type="number"
164+
value={viscosity}
165+
onChange={(e) => setViscosity(parseFloat(e.target.value))}
166+
className="border p-1 rounded w-full"
167+
/>
168+
</div>
169+
</div>
170+
</div>
171+
<div>
172+
<h3 className="font-bold">シミュレーション諸元</h3>
173+
<div>
174+
<label className="caption">Δt (s): </label>
175+
<input
176+
type="number"
177+
value={deltaT}
178+
onChange={(e) => setDeltaT(parseFloat(e.target.value))}
179+
className="border p-1 rounded w-full"
180+
/>
181+
</div>
182+
</div>
183+
<div className="hidden">
184+
<h3>debug</h3>
185+
<div>
186+
レイノルズ数:
187+
{reynoldsNumber(fluidDensity, initV, radius * 2, viscosity).toFixed(
188+
2
189+
)}
190+
</div>
191+
<div>
192+
抗力係数:
193+
{dragCoefficient(
194+
reynoldsNumber(fluidDensity, initV, radius * 2, viscosity)
195+
).toFixed(4)}
196+
</div>
197+
</div>
198+
</div>
199+
</div>
200+
);
201+
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import React, { useEffect, useState } from "react";
2+
import { TrajectoryChart } from "./TrajectoryChart";
3+
import type { Condition } from "./type";
4+
import { calculate } from "./calculate";
5+
6+
export const ResultCard = ({ condition }: { condition: Condition }) => {
7+
const [results, setResults] = useState<
8+
{ x_list: number[]; y_list: number[]; condition: Condition }[]
9+
>([]);
10+
useEffect(() => {
11+
if (!condition) return;
12+
setResults((prev) => {
13+
const exists = prev.some((r) => isSameCondition(r.condition, condition));
14+
if (exists) return prev;
15+
const res = calculate(condition);
16+
let len = res.x_list.length;
17+
res.x_list = res.x_list.filter((_, i) => i % Math.ceil(len / 500) === 0);
18+
res.y_list = res.y_list.filter((_, i) => i % Math.ceil(len / 500) === 0);
19+
return [...prev, { ...res, condition }];
20+
});
21+
}, [condition]);
22+
return (
23+
<div className="card flex flex-col gap-4">
24+
<h2 className="font-bold text-xl">計算結果</h2>
25+
<ol className="list-none list-inside flex flex-row flex-wrap gap-4 text-xs">
26+
{results.map((res, idx) => {
27+
const color = `hsl(${(idx * 251) % 360}, 80%, ${
28+
((Math.floor(idx / 5) * 5 * 23 + 40) % 60) + 30
29+
}%)`;
30+
return (
31+
<li
32+
key={idx}
33+
className="rounded-full px-3 py-1 text-white flex items-center justify-center gap-2 hover:shadow-lg hover:opacity-80 transition-all duration-200 ease-in-out"
34+
style={{ backgroundColor: color }}
35+
>
36+
<div>{idx}</div>
37+
<div
38+
className="bg-white w-3 h-3 rounded-full flex items-center justify-center font-bold"
39+
style={{ color: color }}
40+
onClick={() => {
41+
const newResults = results.filter((_, i) => i !== idx);
42+
setResults(newResults);
43+
}}
44+
>
45+
×
46+
</div>
47+
</li>
48+
);
49+
})}
50+
</ol>
51+
<div className="caption w-full min-h-60">
52+
<TrajectoryChart results={results} />
53+
</div>
54+
</div>
55+
);
56+
};
57+
58+
const isSameCondition = (a: Condition, b: Condition) =>
59+
a.initV === b.initV &&
60+
a.angle === b.angle &&
61+
a.yoffset === b.yoffset &&
62+
a.radius === b.radius &&
63+
a.bulletDensity === b.bulletDensity &&
64+
a.mass === b.mass &&
65+
a.g === b.g &&
66+
a.fluidDensity === b.fluidDensity &&
67+
a.viscosity === b.viscosity &&
68+
a.deltaT === b.deltaT;

src/components/Trajectory/TrajectoryChart.tsx

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,27 @@ export const TrajectoryChart: React.FC<Props> = ({ results }) => {
8585
stroke="#e6e6e6"
8686
/>
8787

88-
<LinePath
89-
curve={curveNatural}
90-
data={points}
91-
x={(d) => xScale(d.x) ?? 0}
92-
y={(d) => yScale(d.y) ?? 0}
93-
stroke="#2563eb"
94-
strokeWidth={2}
95-
fill="none"
96-
/>
88+
{results.map((result, idx) => {
89+
const pts = result.x_list.map((vx, i) => ({
90+
x: vx,
91+
y: result.y_list[i] ?? 0,
92+
}));
93+
const color = `hsl(${(idx * 251) % 360}, 80%, ${
94+
((Math.floor(idx / 5) * 5 * 23 + 40) % 60) + 30
95+
}%)`;
96+
return (
97+
<LinePath
98+
key={idx}
99+
curve={curveNatural}
100+
data={pts}
101+
x={(d) => xScale(d.x) ?? 0}
102+
y={(d) => yScale(d.y) ?? 0}
103+
stroke={color}
104+
strokeWidth={2}
105+
fill="none"
106+
/>
107+
);
108+
})}
97109

98110
<AxisLeft
99111
scale={yScale}

0 commit comments

Comments
 (0)