summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-21 23:52:23 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-21 23:52:23 +0800
commit027025e4af3217bd1e026f614f47a1c92fcbd8b9 (patch)
tree9b1247fc4938e5c7f8159ccebc6a4efaafbc6d63
parent5d73943e584b1c0c0753dda85fbda34d27726986 (diff)
downloadsomhairles-dream-fsharp-027025e4af3217bd1e026f614f47a1c92fcbd8b9.tar.gz
feat(viewer): starfield backdrop for deep-space context
-rw-r--r--public/App.js20
-rw-r--r--public/index.html2
-rw-r--r--src/SomhairlesDream.Frontend/App.fs22
-rw-r--r--src/SomhairlesDream.Frontend/Bindings.fs6
4 files changed, 46 insertions, 4 deletions
diff --git a/public/App.js b/public/App.js
index 7e73305..f9e8de3 100644
--- a/public/App.js
+++ b/public/App.js
@@ -2,10 +2,11 @@ import { Union, Record } from "./fable_modules/fable-library-js.4.24.0/Types.js"
import { union_type, obj_type, class_type, record_type, bool_type, string_type } from "./fable_modules/fable-library-js.4.24.0/Reflection.js";
import { defaultOf, equals, comparePrimitives, createAtom } from "./fable_modules/fable-library-js.4.24.0/Util.js";
import { Replay_frames } from "./SomhairlesDream.Shared/Replay.js";
-import { sortBy, map, equalsWith, tryFind, iterateIndexed, sort, item } from "./fable_modules/fable-library-js.4.24.0/Array.js";
+import { sortBy, map, equalsWith, tryFind, iterateIndexed, setItem, sort, item } from "./fable_modules/fable-library-js.4.24.0/Array.js";
import { join, isNullOrWhiteSpace, printf, toText } from "./fable_modules/fable-library-js.4.24.0/String.js";
import { MeshData_triangleIndices, MeshData_positionData } from "./SomhairlesDream.Shared/Domain.js";
import { min, max } from "./fable_modules/fable-library-js.4.24.0/Double.js";
+import { seeded } from "./fable_modules/fable-library-js.4.24.0/Random.js";
import { some, defaultArg } from "./fable_modules/fable-library-js.4.24.0/Option.js";
import { awaitPromise, startImmediate } from "./fable_modules/fable-library-js.4.24.0/Async.js";
import { singleton } from "./fable_modules/fable-library-js.4.24.0/AsyncBuilder.js";
@@ -269,7 +270,7 @@ export function frameObject(view, node) {
const norm = Math.sqrt(((0.3 * 0.3) + (0.44 * 0.44)) + (0.85 * 0.85));
camera.position.set((0.3 / norm) * distance, (0.44 / norm) * distance, (0.85 / norm) * distance);
camera.near = max(1, distance * 0.05);
- camera.far = (distance + (longest * 3));
+ camera.far = max(700, distance + (longest * 3));
camera.lookAt(0, 0, 0);
camera.updateProjectionMatrix();
const matchValue_3 = view.Controls;
@@ -334,6 +335,21 @@ export function initializeView() {
scene.add(rimLight);
const bounceLight = new THREE.HemisphereLight("#3d4a56", "#141a1e", 0.9);
scene.add(bounceLight);
+ const starPositions = new Float64Array(2600 * 3);
+ const starRng = seeded(4242);
+ for (let index = 0; index <= (2600 - 1); index++) {
+ const theta = (starRng.NextDouble() * 2) * 3.141592653589793;
+ const phi = Math.acos((2 * starRng.NextDouble()) - 1);
+ const radius = 260 + (starRng.NextDouble() * 90);
+ setItem(starPositions, index * 3, (radius * Math.sin(phi)) * Math.cos(theta));
+ setItem(starPositions, (index * 3) + 1, radius * Math.cos(phi));
+ setItem(starPositions, (index * 3) + 2, (radius * Math.sin(phi)) * Math.sin(theta));
+ }
+ const starGeometry = new THREE.BufferGeometry();
+ starGeometry.setAttribute("position", new THREE.Float32BufferAttribute(starPositions, 3));
+ const starMaterial = new THREE.PointsMaterial({ color: "#c9d6e6", size: 1.7, sizeAttenuation: true, transparent: true, opacity: 0.9, depthWrite: false });
+ const stars = new THREE.Points(starGeometry, starMaterial);
+ scene.add(stars);
const grid = new THREE.GridHelper(24, 48, "#5a4a2a", "#2a2f33");
grid.position.y = -1.35;
scene.add(grid);
diff --git a/public/index.html b/public/index.html
index 84dd147..71f67b1 100644
--- a/public/index.html
+++ b/public/index.html
@@ -140,6 +140,6 @@
<span>heritage 参考线保留占位演示 · F# / OPEN CODE</span>
</footer>
</main>
- <script type="module" src="/App.js?v=ui10"></script>
+ <script type="module" src="/App.js?v=ui11"></script>
</body>
</html>
diff --git a/src/SomhairlesDream.Frontend/App.fs b/src/SomhairlesDream.Frontend/App.fs
index 72d1be2..2aa78eb 100644
--- a/src/SomhairlesDream.Frontend/App.fs
+++ b/src/SomhairlesDream.Frontend/App.fs
@@ -210,7 +210,7 @@ let frameObject (view: ViewState) (node: ThreeNode) =
camera.position.set(dirX / norm * distance, dirY / norm * distance, dirZ / norm * distance)
|> ignore
camera.near <- max 1.0 (distance * 0.05)
- camera.far <- distance + longest * 3.0
+ camera.far <- max 700.0 (distance + longest * 3.0)
camera.lookAt(0.0, 0.0, 0.0)
camera.updateProjectionMatrix ()
@@ -277,6 +277,26 @@ let initializeView () =
let bounceLight = createHemisphereLight("#3d4a56", "#141a1e", 0.9)
scene.add(bounceLight)
+ // Starfield: gives deep-space context so the vehicle reads as a ship in
+ // the void instead of a model on a grey stage.
+ let starCount = 2600
+ let starPositions = Array.zeroCreate (starCount * 3)
+ let starRng = System.Random(4242)
+
+ for index in 0 .. starCount - 1 do
+ let theta = starRng.NextDouble() * 2.0 * System.Math.PI
+ let phi = System.Math.Acos(2.0 * starRng.NextDouble() - 1.0)
+ let radius = 260.0 + starRng.NextDouble() * 90.0
+ starPositions.[index * 3] <- radius * System.Math.Sin(phi) * System.Math.Cos(theta)
+ starPositions.[index * 3 + 1] <- radius * System.Math.Cos(phi)
+ starPositions.[index * 3 + 2] <- radius * System.Math.Sin(phi) * System.Math.Sin(theta)
+
+ let starGeometry = createBufferGeometry ()
+ starGeometry.setAttribute("position", createFloat32BufferAttribute(starPositions, 3)) |> ignore
+ let starMaterial = createPointsMaterial "#c9d6e6" 1.7 0.9
+ let stars = createPoints starGeometry starMaterial
+ scene.add(stars)
+
// Coordinate ground grid so the model reads as anchored in a workspace
// instead of floating in a void.
let grid = createGridHelper (24.0, 48, "#5a4a2a", "#2a2f33")
diff --git a/src/SomhairlesDream.Frontend/Bindings.fs b/src/SomhairlesDream.Frontend/Bindings.fs
index 05d8e95..df94777 100644
--- a/src/SomhairlesDream.Frontend/Bindings.fs
+++ b/src/SomhairlesDream.Frontend/Bindings.fs
@@ -59,6 +59,12 @@ type ServerEvent =
type GLTFScene =
abstract scene: ThreeNode with get
+[<Emit("new THREE.PointsMaterial({ color: $0, size: $1, sizeAttenuation: true, transparent: true, opacity: $2, depthWrite: false })")>]
+let createPointsMaterial (color: string) (size: float) (opacity: float) : obj = jsNative
+
+[<Emit("new THREE.Points($0, $1)")>]
+let createPoints (geometry: obj) (material: obj) : ThreeNode = jsNative
+
[<Emit("new DRACOLoader()")>]
let createDRACOLoader () : obj = jsNative