Better accuracy

This commit is contained in:
2026-03-04 19:40:46 +00:00
parent 6885801078
commit 6af56f478f
5 changed files with 371 additions and 71 deletions

View File

@@ -30,6 +30,91 @@ function Section({ yCenter, yLen, rTop, rBot, color, opacity = 1 }) {
)
}
// ── Domed tank section ─────────────────────────────────────────────────
// Renders a cylindrical tank with hemispherical domes on both ends.
// yCenter = center of the entire tank, L_cyl = cylindrical body length, R = radius
function TankSection({ yCenter, L_cyl, R, color, opacity = 1 }) {
const cylGeo = useMemo(
() => new THREE.CylinderGeometry(R, R, L_cyl, 48, 1, false),
[R, L_cyl],
)
// Top dome: hemisphere pointing upward (positive Y)
const topDomeGeo = useMemo(
() => new THREE.SphereGeometry(R, 32, 16, 0, Math.PI * 2, 0, Math.PI / 2),
[R],
)
// Bottom dome: hemisphere pointing downward (negative Y)
const botDomeGeo = useMemo(
() => new THREE.SphereGeometry(R, 32, 16, 0, Math.PI * 2, Math.PI / 2, Math.PI / 2),
[R],
)
const material = (
<meshStandardMaterial
color={color}
metalness={0.45}
roughness={0.5}
side={THREE.DoubleSide}
transparent={opacity < 1}
opacity={opacity}
depthWrite={opacity >= 1}
/>
)
return (
<>
{/* Cylindrical body */}
<mesh geometry={cylGeo} position={[0, yCenter, 0]}>
{material}
</mesh>
{/* Top dome */}
<mesh geometry={topDomeGeo} position={[0, yCenter + L_cyl / 2 + R / 2, 0]}>
{material}
</mesh>
{/* Bottom dome */}
<mesh geometry={botDomeGeo} position={[0, yCenter - L_cyl / 2 - R / 2, 0]}>
{material}
</mesh>
</>
)
}
// ── Nose cone with profile (LatheGeometry) ────────────────────────────────
// Uses profile points {x, r} to generate a smooth surface of revolution.
// x is mapped to height (y-axis), r is the radius at each point.
// Height is inverted so tip (r=0) ends up at the top of the rocket.
function NoseCone({ yCenter, profilePoints, color, opacity = 1 }) {
const geo = useMemo(() => {
if (!profilePoints || profilePoints.length < 2) return null
const L = profilePoints[profilePoints.length - 1].x
// Map points so height = L - x (inverted), so tip (x=0) becomes height L
// This makes tip point upward when positioned in the rocket
const curvePoints = profilePoints.map(p => new THREE.Vector2(p.r, L - p.x))
return new THREE.LatheGeometry(curvePoints, 32)
}, [profilePoints])
if (!geo) return null
const L = profilePoints[profilePoints.length - 1].x
return (
<mesh geometry={geo} position={[0, yCenter - L / 2, 0]}>
<meshStandardMaterial
color={color}
metalness={0.45}
roughness={0.5}
side={THREE.DoubleSide}
transparent={opacity < 1}
opacity={opacity}
depthWrite={opacity >= 1}
/>
</mesh>
)
}
// ── Rocket composed of stacked vertical sections ───────────────────────
// +Y = nose tip (up) Y = nozzle exit (down / toward ground)
function RocketShape({ geometry: geo }) {
@@ -45,7 +130,7 @@ function RocketShape({ geometry: geo }) {
const {
outerRadius: R,
L_nose, L_payload, L_tank, L_tank_fuel, L_tank_ox, L_engine,
totalLength, r_inner, arrangement, innerPropellant,
totalLength, r_inner, arrangement, innerPropellant, noseProfilePoints,
} = geo
const mid = totalLength / 2
@@ -59,12 +144,20 @@ function RocketShape({ geometry: geo }) {
return (
<group ref={groupRef}>
{/* Nose cone — tip at +Y, base joins body at Y */}
<Section
yCenter={yc(0, L_nose)} yLen={L_nose}
rTop={0.001} rBot={R}
color="#94a3b8"
/>
{/* Nose cone — profile-based shape */}
{noseProfilePoints && noseProfilePoints.length > 0 ? (
<NoseCone
yCenter={yc(0, L_nose)}
profilePoints={noseProfilePoints}
color="#94a3b8"
/>
) : (
<Section
yCenter={yc(0, L_nose)} yLen={L_nose}
rTop={0.001} rBot={R}
color="#94a3b8"
/>
)}
{/* Payload bay */}
{L_payload > 0 && (
@@ -75,18 +168,18 @@ function RocketShape({ geometry: geo }) {
/>
)}
{/* Tanks — tandem: oxidizer above fuel */}
{arrangement === 'tandem' && L_tank_ox > 0 && (
<Section
yCenter={yc(tankY0, L_tank_ox)} yLen={L_tank_ox}
rTop={R} rBot={R}
{/* Tanks — tandem: oxidizer above fuel (with domes) */}
{arrangement === 'tandem' && L_tank_ox_cyl > 0 && (
<TankSection
yCenter={yc(tankY0, L_tank_ox)} L_cyl={L_tank_ox_cyl}
R={R}
color="#06b6d4"
/>
)}
{arrangement === 'tandem' && L_tank_fuel > 0 && (
<Section
yCenter={yc(tankY0 + L_tank_ox, L_tank_fuel)} yLen={L_tank_fuel}
rTop={R} rBot={R}
{arrangement === 'tandem' && L_tank_fuel_cyl > 0 && (
<TankSection
yCenter={yc(tankY0 + L_tank_ox, L_tank_fuel)} L_cyl={L_tank_fuel_cyl}
R={R}
color="#f59e0b"
/>
)}