summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SomhairlesDream.Frontend/App.fs32
-rw-r--r--src/SomhairlesDream.Frontend/Bindings.fs9
-rw-r--r--src/SomhairlesDream.Modeling/Definitions.fs102
-rw-r--r--src/SomhairlesDream.Server/Program.fs13
4 files changed, 83 insertions, 73 deletions
diff --git a/src/SomhairlesDream.Frontend/App.fs b/src/SomhairlesDream.Frontend/App.fs
index 82bae88..72d1be2 100644
--- a/src/SomhairlesDream.Frontend/App.fs
+++ b/src/SomhairlesDream.Frontend/App.fs
@@ -184,25 +184,33 @@ let frameObject (view: ViewState) (node: ThreeNode) =
let size = createVector3 ()
box.getCenter center |> ignore
box.getSize size |> ignore
- let radius = 0.5 * size.length ()
+ // Fit the longest axis horizontally (it runs across the screen in the
+ // side-on hero camera) and the next-longest vertically.
+ let axes = [| size.x; size.y; size.z |] |> Array.sort
+ let longest = axes.[2]
+ let smallest = axes.[0]
- if radius > 0.000001 then
+ if longest > 0.000001 then
node.position.set(-center.x, -center.y, -center.z) |> ignore
let camera = view.Camera
let width = max 1.0 (float view.Canvas.clientWidth)
let height = max 1.0 (float view.Canvas.clientHeight)
- let aspect = width / height
+ let aspect = max 0.3 (width / height)
camera.aspect <- aspect
let fovY = camera.fov * Math.PI / 180.0
- let fovX = 2.0 * atan (tan (fovY / 2.0) * aspect)
- let limitingFov = min fovY fovX
- let distance = 1.28 * radius / sin (limitingFov / 2.0)
- let dirX, dirY, dirZ = 0.62, 0.45, 0.70
+ let tanHalf = tan (fovY / 2.0)
+ // Longest axis lies across the screen; the smallest axis is the
+ // one stacking vertically in a side-on view of a slender vehicle.
+ let halfW = 0.5 * longest / (tanHalf * aspect)
+ let halfH = 0.5 * smallest / tanHalf
+ let distance = 1.25 * max halfW halfH
+ // Side-on hero camera: look across the hull, not down its nose.
+ let dirX, dirY, dirZ = 0.30, 0.44, 0.85
let norm = sqrt (dirX * dirX + dirY * dirY + dirZ * dirZ)
camera.position.set(dirX / norm * distance, dirY / norm * distance, dirZ / norm * distance)
|> ignore
- camera.near <- max (distance * 0.01) (distance - radius * 2.5)
- camera.far <- distance + radius * 4.0
+ camera.near <- max 1.0 (distance * 0.05)
+ camera.far <- distance + longest * 3.0
camera.lookAt(0.0, 0.0, 0.0)
camera.updateProjectionMatrix ()
@@ -374,6 +382,10 @@ let startStepLoad (view: ViewState) (owner: LoadOwner) (historical: bool) =
loadedStep <- None
toggleHidden ("viewport-loading", false)
let loader = createGLTFLoader ()
+ // Attach the Draco decoder so geometry-compressed GLBs stream in small.
+ let draco = createDRACOLoader ()
+ setDracoDecoderPath draco "/vendor/addons/libs/draco/"
+ attachDracoLoader loader draco
let projectId = JS.encodeURIComponent owner.ProjectId
let runId = JS.encodeURIComponent owner.RunId
// Cache-buster: the GLB is always re-fetched from the server.
@@ -723,7 +735,7 @@ let probeSelection : RunSelection =
let shipSelection : RunSelection =
{ ProjectId = "ship-001"
- RunId = "run-ship-001"
+ RunId = "run-ship-002"
BaseId = "ship-base-000"
TargetId = "ship-target-007"
Render = false }
diff --git a/src/SomhairlesDream.Frontend/Bindings.fs b/src/SomhairlesDream.Frontend/Bindings.fs
index 7f0ce31..05d8e95 100644
--- a/src/SomhairlesDream.Frontend/Bindings.fs
+++ b/src/SomhairlesDream.Frontend/Bindings.fs
@@ -59,6 +59,15 @@ type ServerEvent =
type GLTFScene =
abstract scene: ThreeNode with get
+[<Emit("new DRACOLoader()")>]
+let createDRACOLoader () : obj = jsNative
+
+[<Emit("$0.setDecoderPath($1)")>]
+let setDracoDecoderPath (draco: obj) (path: string) : unit = jsNative
+
+[<Emit("$0.setDRACOLoader($1)")>]
+let attachDracoLoader (loader: obj) (draco: obj) : unit = jsNative
+
[<Emit("new GLTFLoader()")>]
let createGLTFLoader () : obj = jsNative
diff --git a/src/SomhairlesDream.Modeling/Definitions.fs b/src/SomhairlesDream.Modeling/Definitions.fs
index e1d2739..44e1998 100644
--- a/src/SomhairlesDream.Modeling/Definitions.fs
+++ b/src/SomhairlesDream.Modeling/Definitions.fs
@@ -92,87 +92,65 @@ module Design =
ObjectIds = prior (prior (prior (prior (prior (prior bus booms) rtgs) hga) instruments) mast) [||]
Changes = liveryChanges } |]
- // HNX-01 crewed interstellar starship (Concept A "Keel"): fusion-torch
- // study with a spin-gravity habitat torus, water-shadow radiation
- // shield and an axial keel separating crew from the reactor section.
- // Display-scale visual research; no mass/thrust/shielding closure is
- // claimed — the ship.py module header lists the deliberately open
- // engineering problems.
+ // HNX-02 "Copernicus-class" crewed interplanetary transfer vehicle:
+ // nuclear-thermal propulsion stage, spin-gravity centrifuge ring,
+ // water-wall radiation shielding, Whipple MMOD shielding, and an
+ // Orion-class shuttle docked as the scale reference. Built from
+ // procedural engineering structures (trusses, pressure vessels, bell
+ // nozzles), not stacked primitives. Display-scale visual research;
+ // no mass/thrust/shielding closure is claimed.
let shipSteps : DesignStep array =
let keel =
- [| "starship.keel.spine"; "starship.nose.cone"; "starship.dock.ring" |]
+ [| "hnx02.hull.core"; "hnx02.spine.truss"; "hnx02.node.forward" |]
let hab =
- [| "starship.hab.torus"; "starship.hub.core"
- "starship.spoke.00"; "starship.spoke.01"; "starship.spoke.02"
- "starship.spoke.03"; "starship.spoke.04"; "starship.spoke.05" |]
+ [| "hnx02.hab.a"; "hnx02.hab.b"; "hnx02.hab.whipple" |]
let radiation =
- [| "starship.shadow.collar"; "starship.storm.cellar" |]
+ [| "hnx02.rad.waterwall"; "hnx02.centrifuge.ring" |]
let reactor =
- [| "starship.core.pressure"; "starship.core.shield.plug"
- "starship.nozzle.magnetic"
- "starship.radiator.00"; "starship.radiator.01"; "starship.radiator.02"
- "starship.radiator.03"; "starship.radiator.04"; "starship.radiator.05" |]
+ [| "hnx02.prop.tank"; "hnx02.reactor.core"; "hnx02.thrust.cone"
+ "hnx02.nozzle.cluster" |]
let power =
- [| "starship.power.truss"; "starship.capacitor.ring"
- "starship.comm.dish"; "starship.comm.feed" |]
+ [| "hnx02.power.solar"; "hnx02.power.radiator" |]
let cabin =
- [| "starship.window.00"; "starship.window.01"; "starship.window.02"
- "starship.window.03"
- "starship.airlock.ring"; "starship.airlock.tunnel"
- "starship.cargo.pod.00"; "starship.cargo.pod.01" |]
+ [| "hnx02.cabin.airlock"; "hnx02.shuttle.orion" |]
+ let livery =
+ [| "hnx02.detail.greeble"; "hnx02.detail.antenna" |]
let prior (prefix: string array) (current: string array) =
Array.append prefix current
let keelChanges =
- [| { ObjectId = "starship.keel.spine"; Kind = "add"; Summary = "Add axial keel spine from bow to nozzle bay" }
- { ObjectId = "starship.nose.cone"; Kind = "add"; Summary = "Add forward nose cone" }
- { ObjectId = "starship.dock.ring"; Kind = "add"; Summary = "Add forward docking ring" } |]
+ [| { ObjectId = "hnx02.hull.core"; Kind = "add"; Summary = "Add continuous 16 m pressure hull with viewport bands and ring frames" }
+ { ObjectId = "hnx02.spine.truss"; Kind = "add"; Summary = "Add outrigger trusses with utility conduits and equipment rows" }
+ { ObjectId = "hnx02.node.forward"; Kind = "add"; Summary = "Add five-port forward docking node" } |]
let habChanges =
- Array.append
- [| { ObjectId = "starship.hab.torus"; Kind = "add"; Summary = "Add rotating spin-gravity habitat torus" }
- { ObjectId = "starship.hub.core"; Kind = "add"; Summary = "Add zero-g hub with bearing core" } |]
- (Array.init 6 (fun index ->
- { ObjectId = sprintf "starship.spoke.%02d" index; Kind = "add"; Summary = "Add pressurized transfer spoke" }))
+ [| { ObjectId = "hnx02.hab.a"; Kind = "add"; Summary = "Add forward habitat module with viewport rows" }
+ { ObjectId = "hnx02.hab.b"; Kind = "add"; Summary = "Add aft habitat module" }
+ { ObjectId = "hnx02.hab.whipple"; Kind = "add"; Summary = "Add Whipple MMOD shield panels" } |]
let radiationChanges =
- [| { ObjectId = "starship.shadow.collar"; Kind = "add"; Summary = "Add water-shadow radiation collar" }
- { ObjectId = "starship.storm.cellar"; Kind = "add"; Summary = "Add storm cellar shelter volume" } |]
+ [| { ObjectId = "hnx02.rad.waterwall"; Kind = "add"; Summary = "Add water-wall radiation shield tanks" }
+ { ObjectId = "hnx02.centrifuge.ring"; Kind = "add"; Summary = "Add spin-gravity centrifuge ring with crew cars" } |]
let reactorChanges =
- Array.append
- [| { ObjectId = "starship.core.pressure"; Kind = "add"; Summary = "Add fusion core pressure vessel" }
- { ObjectId = "starship.core.shield.plug"; Kind = "add"; Summary = "Add reactor shield plug" }
- { ObjectId = "starship.nozzle.magnetic"; Kind = "add"; Summary = "Add magnetic exhaust nozzle" } |]
- (Array.init 6 (fun index ->
- { ObjectId = sprintf "starship.radiator.%02d" index; Kind = "add"; Summary = "Add waste-heat radiator panel" }))
+ [| { ObjectId = "hnx02.prop.tank"; Kind = "add"; Summary = "Add cryogenic propellant tanks with MLI blankets" }
+ { ObjectId = "hnx02.reactor.core"; Kind = "add"; Summary = "Add reactor core with control drums" }
+ { ObjectId = "hnx02.thrust.cone"; Kind = "add"; Summary = "Add thrust structure and shadow shield" }
+ { ObjectId = "hnx02.nozzle.cluster"; Kind = "add"; Summary = "Add nuclear-thermal engine cluster" } |]
let powerChanges =
- [| { ObjectId = "starship.power.truss"; Kind = "add"; Summary = "Add power conditioning truss" }
- { ObjectId = "starship.capacitor.ring"; Kind = "add"; Summary = "Add pulsed-power capacitor hoop" }
- { ObjectId = "starship.comm.dish"; Kind = "add"; Summary = "Add high-gain communication dish" }
- { ObjectId = "starship.comm.feed"; Kind = "add"; Summary = "Add communication feed horn" } |]
+ [| { ObjectId = "hnx02.power.solar"; Kind = "add"; Summary = "Add deployable solar arrays" }
+ { ObjectId = "hnx02.power.radiator"; Kind = "add"; Summary = "Add finned heat-rejection radiators" } |]
let cabinChanges =
- Array.append
- (Array.init 4 (fun index ->
- { ObjectId = sprintf "starship.window.%02d" index; Kind = "add"; Summary = "Add habitat observation window" }))
- [| { ObjectId = "starship.airlock.ring"; Kind = "add"; Summary = "Add forward airlock ring" }
- { ObjectId = "starship.airlock.tunnel"; Kind = "add"; Summary = "Add pressurized airlock tunnel" }
- { ObjectId = "starship.cargo.pod.00"; Kind = "add"; Summary = "Add port cargo pod" }
- { ObjectId = "starship.cargo.pod.01"; Kind = "add"; Summary = "Add starboard cargo pod" } |]
+ [| { ObjectId = "hnx02.cabin.airlock"; Kind = "add"; Summary = "Add EVA airlock with external hatch" }
+ { ObjectId = "hnx02.shuttle.orion"; Kind = "add"; Summary = "Add docked Orion-class crew shuttle" } |]
let liveryChanges =
- [| { ObjectId = "starship.keel.spine"; Kind = "update"; Summary = "Livery pass: bone-white hull structure" }
- { ObjectId = "starship.hab.torus"; Kind = "update"; Summary = "Livery pass: habitat ring bone finish" }
- { ObjectId = "starship.shadow.collar"; Kind = "update"; Summary = "Livery pass: ceramic shield darkening" }
- { ObjectId = "starship.storm.cellar"; Kind = "update"; Summary = "Livery pass: ceramic cellar finish" }
- { ObjectId = "starship.dock.ring"; Kind = "update"; Summary = "Livery pass: amber accent on dock ring" }
- { ObjectId = "starship.capacitor.ring"; Kind = "update"; Summary = "Livery pass: amber capacitor hoop" }
- { ObjectId = "starship.window.00"; Kind = "update"; Summary = "Livery pass: glass glazing" }
- { ObjectId = "starship.nozzle.magnetic"; Kind = "update"; Summary = "Livery pass: nozzle coil graphite" } |]
+ [| { ObjectId = "hnx02.detail.greeble"; Kind = "add"; Summary = "Add equipment rows, cable looms and navigation lights" }
+ { ObjectId = "hnx02.detail.antenna"; Kind = "add"; Summary = "Add high-gain communication antennas" } |]
[| { StepId = "ship-01-keel"; Index = 0; ObjectIds = keel; Changes = keelChanges }
- { StepId = "ship-02-hab"; Index = 1; ObjectIds = Array.append keel hab; Changes = habChanges }
- { StepId = "ship-03-radiation"; Index = 2; ObjectIds = Array.append (Array.append keel hab) radiation; Changes = radiationChanges }
- { StepId = "ship-04-reactor"; Index = 3; ObjectIds = Array.append (Array.append (Array.append keel hab) radiation) reactor; Changes = reactorChanges }
- { StepId = "ship-05-power"; Index = 4; ObjectIds = Array.append (Array.append (Array.append (Array.append keel hab) radiation) reactor) power; Changes = powerChanges }
- { StepId = "ship-06-cabin"; Index = 5; ObjectIds = Array.append (Array.append (Array.append (Array.append (Array.append keel hab) radiation) reactor) power) cabin; Changes = cabinChanges }
- { StepId = "ship-07-livery"; Index = 6; ObjectIds = Array.append (Array.append (Array.append (Array.append (Array.append (Array.append keel hab) radiation) reactor) power) cabin) [||]; Changes = liveryChanges } |]
+ { StepId = "ship-02-hab"; Index = 1; ObjectIds = prior keel hab; Changes = habChanges }
+ { StepId = "ship-03-radiation"; Index = 2; ObjectIds = prior (prior keel hab) radiation; Changes = radiationChanges }
+ { StepId = "ship-04-reactor"; Index = 3; ObjectIds = prior (prior (prior keel hab) radiation) reactor; Changes = reactorChanges }
+ { StepId = "ship-05-power"; Index = 4; ObjectIds = prior (prior (prior (prior keel hab) radiation) reactor) power; Changes = powerChanges }
+ { StepId = "ship-06-cabin"; Index = 5; ObjectIds = prior (prior (prior (prior (prior keel hab) radiation) reactor) power) cabin; Changes = cabinChanges }
+ { StepId = "ship-07-livery"; Index = 6; ObjectIds = prior (prior (prior (prior (prior (prior keel hab) radiation) reactor) power) cabin) livery; Changes = liveryChanges } |]
let forProject (projectId: string) : DesignStep array =
if projectId.StartsWith("probe-") then probeSteps
diff --git a/src/SomhairlesDream.Server/Program.fs b/src/SomhairlesDream.Server/Program.fs
index 53cfef6..b2ea8a8 100644
--- a/src/SomhairlesDream.Server/Program.fs
+++ b/src/SomhairlesDream.Server/Program.fs
@@ -6,6 +6,7 @@ open System.Threading.Tasks
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting
open Microsoft.AspNetCore.Http
+open Microsoft.AspNetCore.StaticFiles
open Microsoft.Extensions.FileProviders
open Microsoft.Extensions.Hosting
open SomhairlesDream.Server
@@ -57,7 +58,17 @@ let events (context: HttpContext) : Task =
} :> Task)
app.UseDefaultFiles() |> ignore
-app.UseStaticFiles() |> ignore
+
+// Serve geometry and decoder binaries with correct MIME types: ASP.NET's
+// default map has no entry for .glb/.wasm, so those requests would 404.
+let staticFileOptions =
+ let provider = FileExtensionContentTypeProvider()
+ provider.Mappings.[".glb"] <- "model/gltf-binary"
+ provider.Mappings.[".gltf"] <- "model/gltf+json"
+ provider.Mappings.[".wasm"] <- "application/wasm"
+ StaticFileOptions(ContentTypeProvider = provider)
+
+app.UseStaticFiles(staticFileOptions) |> ignore
app.MapGet("/health", Func<IResult>(fun () -> Results.Ok({| status = "ok" |}))) |> ignore