Better accuracy
This commit is contained in:
@@ -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"
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user