diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/SomhairlesDream.Frontend/App.fs | 32 | ||||
| -rw-r--r-- | src/SomhairlesDream.Frontend/Bindings.fs | 9 | ||||
| -rw-r--r-- | src/SomhairlesDream.Modeling/Definitions.fs | 102 | ||||
| -rw-r--r-- | src/SomhairlesDream.Server/Program.fs | 13 |
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 |
