diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/SomhairlesDream.Frontend/App.fs | 42 | ||||
| -rw-r--r-- | src/SomhairlesDream.Frontend/Bindings.fs | 22 |
2 files changed, 57 insertions, 7 deletions
diff --git a/src/SomhairlesDream.Frontend/App.fs b/src/SomhairlesDream.Frontend/App.fs index ec9e273..e6edd51 100644 --- a/src/SomhairlesDream.Frontend/App.fs +++ b/src/SomhairlesDream.Frontend/App.fs @@ -47,7 +47,14 @@ type ViewState = Scene: ThreeNode Camera: ThreeCamera Group: ThreeNode - Canvas: HTMLCanvasElement } + Canvas: HTMLCanvasElement + Controls: obj } + +// Auto-spin pauses while the visitor is dragging or zooming the model and +// resumes a few seconds after the last interaction, so inspecting a stage +// (dish, RTG, mast) is never fought by the idle animation. +let mutable lastInteractionMs = 0.0 +let idleSpinDelayMs = 3500.0 type LoadOwner = { ProjectId: string @@ -199,6 +206,14 @@ let frameObject (view: ViewState) (node: ThreeNode) = camera.lookAt(0.0, 0.0, 0.0) camera.updateProjectionMatrix () + // Re-sync the orbit target after every auto-fit so drag/zoom + // pivots around the freshly centered model. + match view.Controls with + | null -> () + | controls -> + setControlsTarget controls 0.0 0.0 0.0 + updateControls controls + let resizeView (view: ViewState) = let width = max 1.0 (float view.Canvas.clientWidth) let height = max 1.0 (float view.Canvas.clientHeight) @@ -230,7 +245,7 @@ let initializeView () = scene.background <- createColor("#11171a") let camera = createPerspectiveCamera(34.0, width / height, 0.1, 100.0) camera.position.set(6.4, 4.6, 7.2) |> ignore - camera.lookAt(0.0, 1.2, 0.0) + camera.lookAt(0.0, 0.0, 0.0) let group = createGroup() scene.add(group) scene.add(createAmbientLight("#f0e6c8", 1.9)) @@ -238,23 +253,40 @@ let initializeView () = keyLight.position.set(4.0, 7.0, 5.0) |> ignore scene.add(keyLight) + let controls = createOrbitControls camera canvas + setControlsTarget controls 0.0 0.0 0.0 + setControlsDamping controls true + setControlsAutoRotateSpeed controls 0.7 + let view = { Renderer = renderer Scene = scene Camera = camera Group = group - Canvas = canvas } + Canvas = canvas + Controls = controls } viewState <- Some view + freezeRotation <- (queryParam "freeze") = "1" - exposeViewerDebug camera scene group canvas + setControlsAutoRotate controls (not freezeRotation) + exposeViewerDebug camera scene group canvas controls renderFrame selectedFrame window.addEventListener("resize", fun _ -> resizeView view) - let rec animate (_: float) = + canvas.addEventListener("pointerdown", fun _ -> lastInteractionMs <- JS.Constructors.Date.now ()) + canvas.addEventListener("wheel", fun _ -> lastInteractionMs <- JS.Constructors.Date.now ()) + canvas.addEventListener("touchstart", fun _ -> lastInteractionMs <- JS.Constructors.Date.now ()) + + let rec animate (nowMs: float) = + updateControls controls + if not freezeRotation then group.rotation.y <- group.rotation.y + 0.003 + let interacting = nowMs - lastInteractionMs < idleSpinDelayMs + setControlsAutoRotate controls (not freezeRotation && not interacting) + renderer.render(scene, camera) requestAnimationFrame animate |> ignore diff --git a/src/SomhairlesDream.Frontend/Bindings.fs b/src/SomhairlesDream.Frontend/Bindings.fs index e7c567d..19056a5 100644 --- a/src/SomhairlesDream.Frontend/Bindings.fs +++ b/src/SomhairlesDream.Frontend/Bindings.fs @@ -65,6 +65,24 @@ let createGLTFLoader () : obj = jsNative [<Emit("$0.load($1, $2, null, $3)")>] let loadGLTF (loader: obj) (url: string) (onLoad: GLTFScene -> unit) (onError: obj -> unit) : unit = jsNative +[<Emit("new OrbitControls($0, $1)")>] +let createOrbitControls (camera: obj) (canvas: obj) : obj = jsNative + +[<Emit("$0.target.copy({ x: $1, y: $2, z: $3 })")>] +let setControlsTarget (controls: obj) (x: float) (y: float) (z: float) : unit = jsNative + +[<Emit("$0.enableDamping = $1")>] +let setControlsDamping (controls: obj) (enabled: bool) : unit = jsNative + +[<Emit("$0.autoRotate = $1")>] +let setControlsAutoRotate (controls: obj) (auto: bool) : unit = jsNative + +[<Emit("$0.autoRotateSpeed = $1")>] +let setControlsAutoRotateSpeed (controls: obj) (speed: float) : unit = jsNative + +[<Emit("$0.update()")>] +let updateControls (controls: obj) : unit = jsNative + [<Emit("new URLSearchParams(window.location.search).get($0)")>] let queryParam (name: string) : string = jsNative @@ -100,8 +118,8 @@ let createBox3 () : ThreeBox3 = jsNative [<Emit("new THREE.Vector3()")>] let createVector3 () : ThreeVector = jsNative -[<Emit("window.SOMHAIRLES_VIEWER = { camera: $0, scene: $1, group: $2, canvas: $3 }")>] -let exposeViewerDebug (camera: ThreeCamera) (scene: ThreeNode) (group: ThreeNode) (canvas: obj) : unit = jsNative +[<Emit("window.SOMHAIRLES_VIEWER = { camera: $0, scene: $1, group: $2, canvas: $3, controls: $4 }")>] +let exposeViewerDebug (camera: ThreeCamera) (scene: ThreeNode) (group: ThreeNode) (canvas: obj) (controls: obj) : unit = jsNative [<Emit("var child; while ((child = $0.firstChild)) { $0.removeChild(child); }")>] let clearChildren (node: obj) : unit = jsNative |
