summaryrefslogtreecommitdiff
path: root/src/SomhairlesDream.Frontend/App.fs
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-21 23:47:42 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-21 23:47:42 +0800
commit5d73943e584b1c0c0753dda85fbda34d27726986 (patch)
tree5126127692f3cd399345aae8ade5ec66dd247668 /src/SomhairlesDream.Frontend/App.fs
parent7c3a0728570f84f5c7d5f396c5a5d5c11f5fb661 (diff)
downloadsomhairles-dream-fsharp-5d73943e584b1c0c0753dda85fbda34d27726986.tar.gz
feat(ship): HNX-02 Copernicus rework — continuous hull, few-large-element scale, Draco pipeline, side-on framing
Diffstat (limited to 'src/SomhairlesDream.Frontend/App.fs')
-rw-r--r--src/SomhairlesDream.Frontend/App.fs32
1 files changed, 22 insertions, 10 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 }