@opsydyn/effect-three 0.3.0 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +15 -1
- package/dist/{index-8cf8t6by.js → index-re3rrfwd.js} +2935 -104
- package/dist/index-re3rrfwd.js.map +56 -0
- package/dist/index-ttwy0znv.js +145 -0
- package/dist/index-ttwy0znv.js.map +12 -0
- package/dist/index.js +557 -147
- package/dist/index.js.map +8 -6
- package/dist/kernel/bodies.d.ts +20 -1
- package/dist/kernel/bodies.d.ts.map +1 -1
- package/dist/kernel/boxSelect.d.ts +22 -0
- package/dist/kernel/boxSelect.d.ts.map +1 -0
- package/dist/kernel/build.d.ts +165 -7
- package/dist/kernel/build.d.ts.map +1 -1
- package/dist/kernel/cameraShake.d.ts.map +1 -1
- package/dist/kernel/describeScene.d.ts +50 -0
- package/dist/kernel/describeScene.d.ts.map +1 -0
- package/dist/kernel/description.d.ts +223 -1
- package/dist/kernel/description.d.ts.map +1 -1
- package/dist/kernel/diff.d.ts +15 -0
- package/dist/kernel/diff.d.ts.map +1 -1
- package/dist/kernel/equality.d.ts +11 -1
- package/dist/kernel/equality.d.ts.map +1 -1
- package/dist/kernel/gizmo.d.ts +69 -0
- package/dist/kernel/gizmo.d.ts.map +1 -0
- package/dist/kernel/history.d.ts +61 -0
- package/dist/kernel/history.d.ts.map +1 -0
- package/dist/kernel/index.d.ts +19 -6
- package/dist/kernel/index.d.ts.map +1 -1
- package/dist/kernel/index.js +124 -3
- package/dist/kernel/index.js.map +1 -1
- package/dist/kernel/inspect.d.ts +85 -0
- package/dist/kernel/inspect.d.ts.map +1 -0
- package/dist/kernel/lineGeometry.d.ts +25 -0
- package/dist/kernel/lineGeometry.d.ts.map +1 -0
- package/dist/kernel/lockstep.d.ts +114 -0
- package/dist/kernel/lockstep.d.ts.map +1 -0
- package/dist/kernel/loop.d.ts.map +1 -1
- package/dist/kernel/materials.d.ts.map +1 -1
- package/dist/kernel/paintOrder.d.ts +53 -0
- package/dist/kernel/paintOrder.d.ts.map +1 -0
- package/dist/kernel/physics.d.ts +39 -0
- package/dist/kernel/physics.d.ts.map +1 -1
- package/dist/kernel/pivot.d.ts +54 -0
- package/dist/kernel/pivot.d.ts.map +1 -0
- package/dist/kernel/pointBuffers.d.ts +17 -0
- package/dist/kernel/pointBuffers.d.ts.map +1 -0
- package/dist/kernel/pointer.d.ts +31 -1
- package/dist/kernel/pointer.d.ts.map +1 -1
- package/dist/kernel/preview.d.ts +32 -0
- package/dist/kernel/preview.d.ts.map +1 -0
- package/dist/kernel/renderKit.d.ts +21 -1
- package/dist/kernel/renderKit.d.ts.map +1 -1
- package/dist/kernel/renderer.d.ts +6 -0
- package/dist/kernel/renderer.d.ts.map +1 -1
- package/dist/kernel/sceneChanges.d.ts +87 -0
- package/dist/kernel/sceneChanges.d.ts.map +1 -0
- package/dist/kernel/sceneGraph.d.ts +9 -1
- package/dist/kernel/sceneGraph.d.ts.map +1 -1
- package/dist/kernel/sceneIndex.d.ts +5 -0
- package/dist/kernel/sceneIndex.d.ts.map +1 -1
- package/dist/kernel/seeded.d.ts +9 -0
- package/dist/kernel/seeded.d.ts.map +1 -0
- package/dist/kernel/shots.d.ts +26 -0
- package/dist/kernel/shots.d.ts.map +1 -0
- package/dist/kernel/tape.d.ts +38 -0
- package/dist/kernel/tape.d.ts.map +1 -0
- package/dist/kernel/timeline.d.ts +60 -0
- package/dist/kernel/timeline.d.ts.map +1 -0
- package/dist/kernel/trail.d.ts +31 -0
- package/dist/kernel/trail.d.ts.map +1 -0
- package/dist/kernel/viewportGizmo.d.ts +25 -0
- package/dist/kernel/viewportGizmo.d.ts.map +1 -0
- package/dist/runtime/Inspector.d.ts +20 -0
- package/dist/runtime/Inspector.d.ts.map +1 -0
- package/dist/runtime/SceneFile.d.ts +462 -0
- package/dist/runtime/SceneFile.d.ts.map +1 -0
- package/dist/runtime/SceneSchema.d.ts +404 -0
- package/dist/runtime/SceneSchema.d.ts.map +1 -1
- package/dist/runtime/errors.d.ts +9 -0
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/index.d.ts +3 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/webgl/index.d.ts.map +1 -1
- package/dist/webgl/index.js +228 -3
- package/dist/webgl/index.js.map +6 -4
- package/dist/webgl/line.d.ts +4 -0
- package/dist/webgl/line.d.ts.map +1 -0
- package/dist/webgl/points.d.ts +4 -0
- package/dist/webgl/points.d.ts.map +1 -0
- package/dist/webgpu/index.d.ts.map +1 -1
- package/dist/webgpu/index.js +206 -26
- package/dist/webgpu/index.js.map +6 -4
- package/dist/webgpu/line.d.ts +4 -0
- package/dist/webgpu/line.d.ts.map +1 -0
- package/dist/webgpu/points.d.ts +4 -0
- package/dist/webgpu/points.d.ts.map +1 -0
- package/package.json +4 -2
- package/src/kernel/bodies.ts +299 -17
- package/src/kernel/boxSelect.ts +73 -0
- package/src/kernel/build.ts +374 -1
- package/src/kernel/cameraShake.ts +3 -13
- package/src/kernel/describeScene.ts +224 -0
- package/src/kernel/description.ts +243 -0
- package/src/kernel/diff.ts +39 -0
- package/src/kernel/equality.ts +123 -0
- package/src/kernel/gizmo.ts +478 -0
- package/src/kernel/history.ts +118 -0
- package/src/kernel/index.ts +110 -1
- package/src/kernel/inspect.ts +214 -0
- package/src/kernel/lineGeometry.ts +75 -0
- package/src/kernel/lockstep.ts +237 -0
- package/src/kernel/loop.ts +7 -0
- package/src/kernel/materials.ts +3 -0
- package/src/kernel/paintOrder.ts +274 -0
- package/src/kernel/physics.ts +86 -1
- package/src/kernel/pivot.ts +418 -0
- package/src/kernel/pointBuffers.ts +115 -0
- package/src/kernel/pointer.ts +98 -17
- package/src/kernel/preview.ts +467 -0
- package/src/kernel/renderKit.ts +23 -1
- package/src/kernel/renderer.ts +6 -0
- package/src/kernel/sceneChanges.ts +265 -0
- package/src/kernel/sceneGraph.ts +354 -11
- package/src/kernel/sceneIndex.ts +8 -0
- package/src/kernel/seeded.ts +18 -0
- package/src/kernel/shots.ts +167 -0
- package/src/kernel/tape.ts +124 -0
- package/src/kernel/timeline.ts +93 -0
- package/src/kernel/trail.ts +170 -0
- package/src/kernel/viewportGizmo.ts +194 -0
- package/src/runtime/Inspector.ts +43 -0
- package/src/runtime/SceneFile.ts +46 -0
- package/src/runtime/SceneSchema.ts +319 -0
- package/src/runtime/errors.ts +6 -0
- package/src/runtime/index.ts +3 -1
- package/src/webgl/index.ts +4 -0
- package/src/webgl/line.ts +66 -0
- package/src/webgl/points.ts +205 -0
- package/src/webgpu/index.ts +4 -0
- package/src/webgpu/line.ts +67 -0
- package/src/webgpu/points.ts +193 -0
- package/dist/index-8cf8t6by.js.map +0 -41
package/src/kernel/sceneGraph.ts
CHANGED
|
@@ -30,14 +30,21 @@ import { clone as cloneSkinned } from 'three/addons/utils/SkeletonUtils.js';
|
|
|
30
30
|
|
|
31
31
|
import { clipRunning, clipTime } from './animation.ts';
|
|
32
32
|
import { writeRotation } from './behaviours.ts';
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
type BodyCollision,
|
|
35
|
+
type BodyContactForce,
|
|
36
|
+
createBodyHost,
|
|
37
|
+
type PhysicsReached,
|
|
38
|
+
} from './bodies.ts';
|
|
34
39
|
import { type BodySource, instanceSources, nodeSource, placeObject } from './bodySources.ts';
|
|
35
|
-
import { renderSettings, scene as describeScene } from './build.ts';
|
|
40
|
+
import { orthographicCamera, renderSettings, scene as describeScene } from './build.ts';
|
|
36
41
|
import { type CameraMotion, createCameraMotion } from './cameraMotion.ts';
|
|
37
42
|
import { type CameraShaker, createCameraShaker } from './cameraShake.ts';
|
|
38
43
|
import type {
|
|
44
|
+
Hud,
|
|
39
45
|
AmbientLightNode,
|
|
40
46
|
BorrowedNode,
|
|
47
|
+
Edges,
|
|
41
48
|
DirectionalLightNode,
|
|
42
49
|
GridNode,
|
|
43
50
|
HemisphereLightNode,
|
|
@@ -45,6 +52,8 @@ import type {
|
|
|
45
52
|
Instance,
|
|
46
53
|
InstancesNode,
|
|
47
54
|
LabelNode,
|
|
55
|
+
LineNode,
|
|
56
|
+
PointsNode,
|
|
48
57
|
MaterialRef,
|
|
49
58
|
ShaderGridNode,
|
|
50
59
|
MeshNode,
|
|
@@ -68,9 +77,16 @@ import type {
|
|
|
68
77
|
ViewRect,
|
|
69
78
|
} from './description.ts';
|
|
70
79
|
import { diff, type Field, type Op } from './diff.ts';
|
|
71
|
-
import {
|
|
80
|
+
import {
|
|
81
|
+
contactShadowsEquals,
|
|
82
|
+
edgesEquals,
|
|
83
|
+
environmentEquals,
|
|
84
|
+
overlaysEqual,
|
|
85
|
+
skyEquals,
|
|
86
|
+
} from './equality.ts';
|
|
72
87
|
import { createInstances, fits, type HostInstances, writeInstances } from './instances.ts';
|
|
73
88
|
import type { HostedLabel, LabelEngine, LabelLayout } from './labels.ts';
|
|
89
|
+
import { edgeLine, edgePoints } from './lineGeometry.ts';
|
|
74
90
|
import type { HostMaterial } from './materials.ts';
|
|
75
91
|
import {
|
|
76
92
|
type Behaviour,
|
|
@@ -80,7 +96,14 @@ import {
|
|
|
80
96
|
resetPose,
|
|
81
97
|
} from './motion.ts';
|
|
82
98
|
import type { BodyPose, PhysicsEngine } from './physics.ts';
|
|
83
|
-
import
|
|
99
|
+
import { animatedLook } from './pointBuffers.ts';
|
|
100
|
+
import type {
|
|
101
|
+
HostedLine,
|
|
102
|
+
HostedPoints,
|
|
103
|
+
HostedShaderGrid,
|
|
104
|
+
HostedSky,
|
|
105
|
+
RenderKit,
|
|
106
|
+
} from './renderKit.ts';
|
|
84
107
|
import { createResourceStore, type ResourceStore } from './resources.ts';
|
|
85
108
|
import {
|
|
86
109
|
type DescriptionError,
|
|
@@ -89,6 +112,7 @@ import {
|
|
|
89
112
|
type ParentKey,
|
|
90
113
|
type SceneIndex,
|
|
91
114
|
} from './sceneIndex.ts';
|
|
115
|
+
import { createTrailRibbon, type TrailRibbon } from './trail.ts';
|
|
92
116
|
import { applyTween, startTween, type Tween } from './tween.ts';
|
|
93
117
|
|
|
94
118
|
/** The mesh type the kernel hosts. */
|
|
@@ -142,6 +166,20 @@ type Entry =
|
|
|
142
166
|
readonly _tag: 'HemisphereLight';
|
|
143
167
|
node: HemisphereLightNode;
|
|
144
168
|
readonly object: HemisphereLight;
|
|
169
|
+
}
|
|
170
|
+
// `hosted` is the points the render kit drew, inside the wrapper; none without a kit.
|
|
171
|
+
| {
|
|
172
|
+
readonly _tag: 'Points';
|
|
173
|
+
node: PointsNode;
|
|
174
|
+
readonly object: Group;
|
|
175
|
+
readonly hosted: HostedPoints | undefined;
|
|
176
|
+
}
|
|
177
|
+
// `hosted` is the line the render kit drew, inside the wrapper; none without a kit.
|
|
178
|
+
| {
|
|
179
|
+
readonly _tag: 'Line';
|
|
180
|
+
node: LineNode;
|
|
181
|
+
readonly object: Group;
|
|
182
|
+
readonly hosted: HostedLine | undefined;
|
|
145
183
|
};
|
|
146
184
|
|
|
147
185
|
/** Entries with a transform: position, rotation, scale and transitions. */
|
|
@@ -154,12 +192,26 @@ type Player = {
|
|
|
154
192
|
readonly startedAt: number;
|
|
155
193
|
};
|
|
156
194
|
|
|
195
|
+
/** A mesh's edge lines: the kit's line, and the geometry and look it was built from. */
|
|
196
|
+
type EdgeLines = {
|
|
197
|
+
readonly hosted: HostedLine;
|
|
198
|
+
geometry: BufferGeometry | undefined;
|
|
199
|
+
edges: Edges | undefined;
|
|
200
|
+
};
|
|
201
|
+
|
|
157
202
|
/** Descriptions with a transform. */
|
|
158
|
-
type PlacedNode =
|
|
203
|
+
type PlacedNode =
|
|
204
|
+
| GroupNode
|
|
205
|
+
| MeshNode
|
|
206
|
+
| InstancesNode
|
|
207
|
+
| BorrowedNode
|
|
208
|
+
| LabelNode
|
|
209
|
+
| LineNode
|
|
210
|
+
| PointsNode;
|
|
159
211
|
|
|
160
212
|
type Placed = Extract<
|
|
161
213
|
Entry,
|
|
162
|
-
{ readonly _tag: 'Group' | 'Mesh' | 'Instances' | 'Borrowed' | 'Label' }
|
|
214
|
+
{ readonly _tag: 'Group' | 'Mesh' | 'Instances' | 'Borrowed' | 'Label' | 'Line' | 'Points' }
|
|
163
215
|
>;
|
|
164
216
|
|
|
165
217
|
/** Entries drawing a geometry with a material. */
|
|
@@ -170,12 +222,17 @@ const isPlaced = (entry: Entry): entry is Placed =>
|
|
|
170
222
|
entry._tag === 'Mesh' ||
|
|
171
223
|
entry._tag === 'Instances' ||
|
|
172
224
|
entry._tag === 'Borrowed' ||
|
|
173
|
-
entry._tag === 'Label'
|
|
225
|
+
entry._tag === 'Label' ||
|
|
226
|
+
entry._tag === 'Line' ||
|
|
227
|
+
entry._tag === 'Points';
|
|
174
228
|
|
|
175
229
|
const isSurfaced = (entry: Entry): entry is Surfaced =>
|
|
176
230
|
entry._tag === 'Mesh' || entry._tag === 'Instances';
|
|
177
231
|
|
|
178
232
|
/** A render texture as hosted: its id, size and the nested graph that draws it. */
|
|
233
|
+
/** The HUD as hosted: its nested graph, drawn over the scene by the frame loop. */
|
|
234
|
+
export type HostedHud = { readonly graph: SceneGraph };
|
|
235
|
+
|
|
179
236
|
export type HostedTexture = {
|
|
180
237
|
readonly id: string;
|
|
181
238
|
readonly graph: SceneGraph;
|
|
@@ -232,6 +289,8 @@ export type SceneGraph = {
|
|
|
232
289
|
readonly turned: (view: string | undefined) => Vec3 | undefined;
|
|
233
290
|
/** Render textures, each a nested graph to draw into its texture (by the frame loop). */
|
|
234
291
|
readonly renderTextures: () => ReadonlyArray<HostedTexture>;
|
|
292
|
+
/** The HUD, a nested graph drawn over the scene (by the frame loop), if there is one. */
|
|
293
|
+
readonly hud: () => HostedHud | undefined;
|
|
235
294
|
/** Whether the scene claims the pointer's gestures from the page. */
|
|
236
295
|
readonly gestures: () => Gestures;
|
|
237
296
|
/** Points to show HTML at; the frame loop places them after drawing. */
|
|
@@ -244,6 +303,8 @@ export type SceneGraph = {
|
|
|
244
303
|
readonly collisions: () => ReadonlyArray<BodyCollision>;
|
|
245
304
|
/** Contact forces over their bodies' thresholds since the last call. */
|
|
246
305
|
readonly contactForces: () => ReadonlyArray<BodyContactForce>;
|
|
306
|
+
/** Steps a world on `physics({ until })` has reached since the last call. */
|
|
307
|
+
readonly physicsReached: () => ReadonlyArray<PhysicsReached>;
|
|
247
308
|
/** How the scene is presented immersively, for the host; `null` if it never is. */
|
|
248
309
|
readonly xr: () => Xr | null;
|
|
249
310
|
/** Keys of the nodes whose bounds the frame loop reports. */
|
|
@@ -448,6 +509,9 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
448
509
|
{ readonly graph: SceneGraph; description: RenderTexture }
|
|
449
510
|
>();
|
|
450
511
|
let textureList: ReadonlyArray<HostedTexture> = [];
|
|
512
|
+
let hudHosted:
|
|
513
|
+
| { readonly graph: SceneGraph; description: Hud; parent: SceneDescription }
|
|
514
|
+
| undefined;
|
|
451
515
|
let viewDescriptions: ReadonlyArray<View> = [];
|
|
452
516
|
let gestures: Gestures = emptyScene.gestures;
|
|
453
517
|
let overlayList: ReadonlyArray<Overlay> = emptyScene.overlays;
|
|
@@ -557,8 +621,30 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
557
621
|
const spinning = new Set<Entry>();
|
|
558
622
|
// Billboards: turned towards the camera each frame, after everything else has moved.
|
|
559
623
|
const facing = new Set<Entry>();
|
|
624
|
+
// Parents first, so a billboard inside a billboard reads this frame's parent turn.
|
|
625
|
+
const facingOrder: Array<Entry> = [];
|
|
560
626
|
const cameraTurn = new Quaternion();
|
|
561
627
|
const parentTurn = new Quaternion();
|
|
628
|
+
// Points that move by themselves (sparkles, stars), ticked in scene time while animating.
|
|
629
|
+
const sparkling = new Set<Entry>();
|
|
630
|
+
const trackSparkle = (entry: Entry, node: PointsNode): void => {
|
|
631
|
+
if (animatedLook(node.look)) sparkling.add(entry);
|
|
632
|
+
else sparkling.delete(entry);
|
|
633
|
+
if (entry._tag === 'Points') entry.hosted?.tick(animating ? elapsed : 0);
|
|
634
|
+
};
|
|
635
|
+
const sparkle = (entry: Entry): void => {
|
|
636
|
+
if (entry._tag === 'Points') entry.hosted?.tick(elapsed);
|
|
637
|
+
};
|
|
638
|
+
// Groups and meshes with trails: each ribbon, in the scene's space.
|
|
639
|
+
const trailing = new Map<Entry, TrailRibbon>();
|
|
640
|
+
const trailPlace = new Vector3();
|
|
641
|
+
const eye = new Vector3();
|
|
642
|
+
const trailsMoving = (): boolean => {
|
|
643
|
+
for (const ribbon of trailing.values()) if (!ribbon.settled()) return true;
|
|
644
|
+
return false;
|
|
645
|
+
};
|
|
646
|
+
// Meshes with edge lines: rebuilt when the mesh's geometry or the edges' look changes.
|
|
647
|
+
const edged = new Map<Entry, EdgeLines>();
|
|
562
648
|
// Groups with levels of detail: one child shown at a time, by the camera's distance.
|
|
563
649
|
const detailing = new Set<Entry>();
|
|
564
650
|
const cameraPlace = new Vector3();
|
|
@@ -639,6 +725,99 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
639
725
|
if (lockZ) object.rotation.z = 0;
|
|
640
726
|
};
|
|
641
727
|
|
|
728
|
+
const depthOf = (object: Object3D): number => {
|
|
729
|
+
let depth = 0;
|
|
730
|
+
for (let parent = object.parent; parent !== null; parent = parent.parent) depth += 1;
|
|
731
|
+
return depth;
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Faces every billboard, shallowest first: a nested one is turned against its parent's
|
|
736
|
+
* world rotation, so the parent must have turned this frame. Drei gets this from React
|
|
737
|
+
* running a parent's frame callback before its child's.
|
|
738
|
+
*/
|
|
739
|
+
const faceCameras = (): void => {
|
|
740
|
+
facingOrder.length = 0;
|
|
741
|
+
facing.forEach((entry) => facingOrder.push(entry));
|
|
742
|
+
facingOrder.sort((a, b) => depthOf(a.object) - depthOf(b.object));
|
|
743
|
+
facingOrder.forEach(faceCamera);
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
/** Where a trail follows its node: the world position, or with `local` its own. */
|
|
747
|
+
const trailTarget = (object: Object3D, local: boolean): Vector3 =>
|
|
748
|
+
local ? trailPlace.copy(object.position) : object.getWorldPosition(trailPlace);
|
|
749
|
+
|
|
750
|
+
/** Adds, restyles or removes a node's trail as its description says. */
|
|
751
|
+
const trackTrail = (entry: Entry, node: GroupNode | MeshNode): void => {
|
|
752
|
+
const current = trailing.get(entry);
|
|
753
|
+
if (node.trail === null) {
|
|
754
|
+
if (current === undefined) return;
|
|
755
|
+
current.object.removeFromParent();
|
|
756
|
+
current.dispose();
|
|
757
|
+
trailing.delete(entry);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const start = trailTarget(entry.object, node.trail.local);
|
|
761
|
+
if (current !== undefined) {
|
|
762
|
+
current.restyle(node.trail, start, elapsed);
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
const ribbon = createTrailRibbon(node.trail, start, elapsed);
|
|
766
|
+
scene.add(ribbon.object);
|
|
767
|
+
trailing.set(entry, ribbon);
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
/** Records where a trailing node is now, and turns its ribbon to the camera. */
|
|
771
|
+
const followTrail = (ribbon: TrailRibbon, entry: Entry): void => {
|
|
772
|
+
if (entry._tag !== 'Group' && entry._tag !== 'Mesh') return;
|
|
773
|
+
if (entry.node.trail === null) return;
|
|
774
|
+
ribbon.record(trailTarget(entry.object, entry.node.trail.local), elapsed);
|
|
775
|
+
ribbon.face(eye);
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
/** Drei's `Edges` turns off picking on its line: hits belong to the mesh. */
|
|
779
|
+
const unpickable = (part: Object3D): void => {
|
|
780
|
+
part.raycast = () => undefined;
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
/** Adds, keeps or removes a mesh's edge lines as its description says. */
|
|
784
|
+
const trackEdges = (entry: Entry, node: MeshNode): void => {
|
|
785
|
+
const current = edged.get(entry);
|
|
786
|
+
const draw = options.kit?.line;
|
|
787
|
+
if (node.edges === null || draw === undefined) {
|
|
788
|
+
if (current === undefined) return;
|
|
789
|
+
current.hosted.object.removeFromParent();
|
|
790
|
+
current.hosted.dispose();
|
|
791
|
+
edged.delete(entry);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
if (current !== undefined) return;
|
|
795
|
+
const hosted = draw(edgeLine(node.key, node.edges, []));
|
|
796
|
+
entry.object.add(hosted.object);
|
|
797
|
+
edged.set(entry, { hosted, geometry: undefined, edges: undefined });
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Builds the edge lines again when the mesh shows another geometry (its own changed, a
|
|
802
|
+
* shared one was replaced, a lent one arrived) or the edges' look changed.
|
|
803
|
+
*/
|
|
804
|
+
const refreshEdges = (lines: EdgeLines, entry: Entry): void => {
|
|
805
|
+
if (entry._tag !== 'Mesh' || entry.node.edges === null) return;
|
|
806
|
+
const { geometry } = entry.object;
|
|
807
|
+
const look = entry.node.edges;
|
|
808
|
+
if (
|
|
809
|
+
geometry === lines.geometry &&
|
|
810
|
+
lines.edges !== undefined &&
|
|
811
|
+
edgesEquals(look, lines.edges)
|
|
812
|
+
) {
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
lines.hosted.update(edgeLine(entry.node.key, look, edgePoints(geometry, look.threshold)));
|
|
816
|
+
lines.hosted.object.traverse(unpickable);
|
|
817
|
+
lines.geometry = geometry;
|
|
818
|
+
lines.edges = look;
|
|
819
|
+
};
|
|
820
|
+
|
|
642
821
|
/** The object of a group's `index`th child, as described. */
|
|
643
822
|
const childObject = (node: GroupNode, index: number): Object3D | undefined => {
|
|
644
823
|
const child = node.children[index];
|
|
@@ -704,6 +883,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
704
883
|
// A group following a tracked space shows only once the space is found.
|
|
705
884
|
if (node.follow !== null) entry.object.visible = false;
|
|
706
885
|
trackDetail(entry, node);
|
|
886
|
+
trackTrail(entry, node);
|
|
707
887
|
return entry;
|
|
708
888
|
}
|
|
709
889
|
case 'Mesh': {
|
|
@@ -719,6 +899,8 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
719
899
|
trackInteractive(object, node.interactive);
|
|
720
900
|
object.castShadow = node.castShadow;
|
|
721
901
|
object.receiveShadow = node.receiveShadow;
|
|
902
|
+
trackEdges(entry, node);
|
|
903
|
+
trackTrail(entry, node);
|
|
722
904
|
return entry;
|
|
723
905
|
}
|
|
724
906
|
case 'Instances': {
|
|
@@ -799,6 +981,28 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
799
981
|
const hosted = options.kit?.shaderGrid(node);
|
|
800
982
|
return { _tag: 'ShaderGrid', node, object: hosted?.object ?? new Group(), hosted };
|
|
801
983
|
}
|
|
984
|
+
case 'Points': {
|
|
985
|
+
const hosted = options.kit?.points?.(node);
|
|
986
|
+
const entry: Entry = { _tag: 'Points', node, object: new Group(), hosted };
|
|
987
|
+
if (hosted !== undefined) entry.object.add(hosted.object);
|
|
988
|
+
writeVec3(entry.object.position, node.position);
|
|
989
|
+
writeVec3(entry.object.scale, node.scale);
|
|
990
|
+
trackRotation(entry, node);
|
|
991
|
+
trackBehaviour(entry, node);
|
|
992
|
+
trackSparkle(entry, node);
|
|
993
|
+
return entry;
|
|
994
|
+
}
|
|
995
|
+
case 'Line': {
|
|
996
|
+
const hosted = options.kit?.line?.(node);
|
|
997
|
+
const entry: Entry = { _tag: 'Line', node, object: new Group(), hosted };
|
|
998
|
+
if (hosted !== undefined) entry.object.add(hosted.object);
|
|
999
|
+
writeVec3(entry.object.position, node.position);
|
|
1000
|
+
writeVec3(entry.object.scale, node.scale);
|
|
1001
|
+
trackRotation(entry, node);
|
|
1002
|
+
trackBehaviour(entry, node);
|
|
1003
|
+
trackInteractive(entry.object, node.interactive);
|
|
1004
|
+
return entry;
|
|
1005
|
+
}
|
|
802
1006
|
case 'HemisphereLight': {
|
|
803
1007
|
const object = new HemisphereLight();
|
|
804
1008
|
writeHemisphereLight(object, node);
|
|
@@ -973,6 +1177,17 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
973
1177
|
spinning.delete(entry);
|
|
974
1178
|
facing.delete(entry);
|
|
975
1179
|
detailing.delete(entry);
|
|
1180
|
+
const lines = edged.get(entry);
|
|
1181
|
+
if (lines !== undefined) {
|
|
1182
|
+
lines.hosted.dispose();
|
|
1183
|
+
edged.delete(entry);
|
|
1184
|
+
}
|
|
1185
|
+
const ribbon = trailing.get(entry);
|
|
1186
|
+
if (ribbon !== undefined) {
|
|
1187
|
+
ribbon.object.removeFromParent();
|
|
1188
|
+
ribbon.dispose();
|
|
1189
|
+
trailing.delete(entry);
|
|
1190
|
+
}
|
|
976
1191
|
moving.delete(entry);
|
|
977
1192
|
reparented.delete(entry);
|
|
978
1193
|
trackInteractive(entry.object, false);
|
|
@@ -984,7 +1199,10 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
984
1199
|
if (entry._tag === 'Instances') entry.object.dispose();
|
|
985
1200
|
// A grid's lines are its own.
|
|
986
1201
|
if (entry._tag === 'Grid') disposeGridLines(entry.helper);
|
|
987
|
-
if (entry._tag === 'ShaderGrid'
|
|
1202
|
+
if (entry._tag === 'ShaderGrid' || entry._tag === 'Line' || entry._tag === 'Points') {
|
|
1203
|
+
entry.hosted?.dispose();
|
|
1204
|
+
}
|
|
1205
|
+
sparkling.delete(entry);
|
|
988
1206
|
if (entry._tag === 'Label') {
|
|
989
1207
|
entry.hosted?.dispose();
|
|
990
1208
|
if (entry.node.material !== null && entry.material !== undefined) {
|
|
@@ -1114,6 +1332,12 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1114
1332
|
entry.node = next;
|
|
1115
1333
|
if (movesTransform(entry, fields)) retarget(entry, next);
|
|
1116
1334
|
if (fields.includes('rotation')) trackRotation(entry, next);
|
|
1335
|
+
if (entry._tag === 'Mesh' && next._tag === 'Mesh' && fields.includes('edges')) {
|
|
1336
|
+
trackEdges(entry, next);
|
|
1337
|
+
}
|
|
1338
|
+
if (entry._tag === 'Mesh' && next._tag === 'Mesh' && fields.includes('trail')) {
|
|
1339
|
+
trackTrail(entry, next);
|
|
1340
|
+
}
|
|
1117
1341
|
if (fields.includes('behaviour')) trackBehaviour(entry, next);
|
|
1118
1342
|
if (fields.includes('interactive')) trackInteractive(mesh, next.interactive);
|
|
1119
1343
|
mesh.castShadow = next.castShadow;
|
|
@@ -1150,6 +1374,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1150
1374
|
if (fields.includes('behaviour')) trackBehaviour(entry, next);
|
|
1151
1375
|
if (fields.includes('interactive')) trackInteractive(entry.object, next.interactive);
|
|
1152
1376
|
if (fields.includes('detail')) trackDetail(entry, next);
|
|
1377
|
+
if (fields.includes('trail')) trackTrail(entry, next);
|
|
1153
1378
|
if (fields.includes('follow')) {
|
|
1154
1379
|
// Back to its own transform, or hidden until the new space is found.
|
|
1155
1380
|
entry.object.visible = next.follow === null;
|
|
@@ -1186,6 +1411,26 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1186
1411
|
entry.node = next;
|
|
1187
1412
|
return;
|
|
1188
1413
|
}
|
|
1414
|
+
if (entry._tag === 'Points' && next._tag === 'Points') {
|
|
1415
|
+
entry.node = next;
|
|
1416
|
+
if (movesTransform(entry, fields)) retarget(entry, next);
|
|
1417
|
+
if (fields.includes('rotation')) trackRotation(entry, next);
|
|
1418
|
+
if (fields.includes('behaviour')) trackBehaviour(entry, next);
|
|
1419
|
+
if (fields.includes('look')) {
|
|
1420
|
+
entry.hosted?.update(next);
|
|
1421
|
+
trackSparkle(entry, next);
|
|
1422
|
+
}
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
if (entry._tag === 'Line' && next._tag === 'Line') {
|
|
1426
|
+
entry.node = next;
|
|
1427
|
+
if (movesTransform(entry, fields)) retarget(entry, next);
|
|
1428
|
+
if (fields.includes('rotation')) trackRotation(entry, next);
|
|
1429
|
+
if (fields.includes('behaviour')) trackBehaviour(entry, next);
|
|
1430
|
+
if (fields.includes('interactive')) trackInteractive(entry.object, next.interactive);
|
|
1431
|
+
if (fields.includes('line')) entry.hosted?.update(next);
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1189
1434
|
if (entry._tag === 'ShaderGrid' && next._tag === 'ShaderGrid') {
|
|
1190
1435
|
entry.hosted?.update(entry.node, next);
|
|
1191
1436
|
entry.node = next;
|
|
@@ -1317,6 +1562,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1317
1562
|
gestures: 'page',
|
|
1318
1563
|
views: [],
|
|
1319
1564
|
renderTextures: [],
|
|
1565
|
+
hud: null,
|
|
1320
1566
|
overlays: [],
|
|
1321
1567
|
environment: null,
|
|
1322
1568
|
sky: null,
|
|
@@ -1324,11 +1570,84 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1324
1570
|
physics: null,
|
|
1325
1571
|
xr: null,
|
|
1326
1572
|
measure: [],
|
|
1573
|
+
assets: [],
|
|
1327
1574
|
render: parent.render,
|
|
1328
1575
|
resources: parent.resources,
|
|
1329
1576
|
children: texture.children,
|
|
1330
1577
|
});
|
|
1331
1578
|
|
|
1579
|
+
/**
|
|
1580
|
+
* The HUD's camera: orthographic in CSS pixels, from in front, aimed so that the HUD's
|
|
1581
|
+
* origin falls `margin` in from the aligned edges (as Drei's `GizmoHelper` places its
|
|
1582
|
+
* group). The camera moves rather than the nodes, so their positions stay the Model's.
|
|
1583
|
+
*/
|
|
1584
|
+
const hudCamera = (hud: Hud): CameraDescription => {
|
|
1585
|
+
const [marginX, marginY] = hud.margin;
|
|
1586
|
+
const [vertical, horizontal] = hud.alignment.split('-');
|
|
1587
|
+
const across = { left: -viewport.width / 2 + marginX, right: viewport.width / 2 - marginX };
|
|
1588
|
+
const up = { top: viewport.height / 2 - marginY, bottom: -viewport.height / 2 + marginY };
|
|
1589
|
+
const x = across[horizontal as keyof typeof across] ?? 0;
|
|
1590
|
+
const y = up[vertical as keyof typeof up] ?? 0;
|
|
1591
|
+
return orthographicCamera({
|
|
1592
|
+
zoom: 1,
|
|
1593
|
+
near: 0.1,
|
|
1594
|
+
far: 2000,
|
|
1595
|
+
position: [-x, -y, 200],
|
|
1596
|
+
lookAt: [-x, -y, 0],
|
|
1597
|
+
});
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
/** The HUD as a scene of its own, sharing the parent's resources. */
|
|
1601
|
+
const hudScene = (hud: Hud, parent: SceneDescription): SceneDescription =>
|
|
1602
|
+
textureScene(
|
|
1603
|
+
{
|
|
1604
|
+
id: 'hud',
|
|
1605
|
+
width: viewport.width,
|
|
1606
|
+
height: viewport.height,
|
|
1607
|
+
camera: hudCamera(hud),
|
|
1608
|
+
background: null,
|
|
1609
|
+
children: hud.children,
|
|
1610
|
+
},
|
|
1611
|
+
parent,
|
|
1612
|
+
);
|
|
1613
|
+
|
|
1614
|
+
/**
|
|
1615
|
+
* Brings the HUD in line with the description, `parent` the scene it is over: a nested
|
|
1616
|
+
* graph on the same store, created, reconciled, re-aimed or disposed.
|
|
1617
|
+
*/
|
|
1618
|
+
const syncHud = (next: SceneDescription): ReadonlyArray<Op> => {
|
|
1619
|
+
if (next.hud === null) {
|
|
1620
|
+
if (hudHosted === undefined) return [];
|
|
1621
|
+
hudHosted.graph.dispose();
|
|
1622
|
+
hudHosted = undefined;
|
|
1623
|
+
return [{ _tag: 'RemoveHud' }];
|
|
1624
|
+
}
|
|
1625
|
+
const created = hudHosted === undefined;
|
|
1626
|
+
const hosted = hudHosted ?? {
|
|
1627
|
+
graph: createSceneGraph({
|
|
1628
|
+
store,
|
|
1629
|
+
...(options.behaviours === undefined ? {} : { behaviours: options.behaviours }),
|
|
1630
|
+
...(options.kit === undefined ? {} : { kit: options.kit }),
|
|
1631
|
+
}),
|
|
1632
|
+
description: next.hud,
|
|
1633
|
+
parent: next,
|
|
1634
|
+
};
|
|
1635
|
+
if (created) hosted.graph.resize(viewport.width, viewport.height);
|
|
1636
|
+
const result = hosted.graph.reconcile(hudScene(next.hud, next));
|
|
1637
|
+
hosted.description = next.hud;
|
|
1638
|
+
hosted.parent = next;
|
|
1639
|
+
hudHosted = hosted;
|
|
1640
|
+
const ops = result._tag === 'Applied' ? result.ops : [];
|
|
1641
|
+
return created || ops.length > 0 ? [{ _tag: 'UpdateHud', ops }] : [];
|
|
1642
|
+
};
|
|
1643
|
+
|
|
1644
|
+
/** The HUD fitted to a resized canvas: its camera re-aimed at the aligned point. */
|
|
1645
|
+
const refitHud = (): void => {
|
|
1646
|
+
if (hudHosted === undefined) return;
|
|
1647
|
+
hudHosted.graph.resize(viewport.width, viewport.height);
|
|
1648
|
+
hudHosted.graph.reconcile(hudScene(hudHosted.description, hudHosted.parent));
|
|
1649
|
+
};
|
|
1650
|
+
|
|
1332
1651
|
/** A hosted render texture as the loop sees it; none if it is not hosted. */
|
|
1333
1652
|
function hostedTexture(texture: RenderTexture): ReadonlyArray<HostedTexture> {
|
|
1334
1653
|
const hosted = textureGraphs.get(texture.id);
|
|
@@ -1389,6 +1708,10 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1389
1708
|
const checked = indexScene(textureScene(texture, next), knownBehaviours);
|
|
1390
1709
|
if (checked._tag === 'Invalid') return checked;
|
|
1391
1710
|
}
|
|
1711
|
+
if (next.hud !== null) {
|
|
1712
|
+
const checked = indexScene(hudScene(next.hud, next), knownBehaviours);
|
|
1713
|
+
if (checked._tag === 'Invalid') return checked;
|
|
1714
|
+
}
|
|
1392
1715
|
const result = diff(index, next, knownBehaviours);
|
|
1393
1716
|
if (result._tag === 'Invalid') return result;
|
|
1394
1717
|
for (const op of result.ops) apply(op);
|
|
@@ -1417,12 +1740,14 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1417
1740
|
if ((physicsDescription?.debug ?? false) !== (next.physics?.debug ?? false)) invalidate();
|
|
1418
1741
|
physicsDescription = next.physics;
|
|
1419
1742
|
bodyHost.sync(next.physics, bodySources());
|
|
1743
|
+
// A world on `until` that owes steps needs frames, though nothing else may be moving.
|
|
1744
|
+
if (bodyHost.owesSteps()) invalidate();
|
|
1420
1745
|
// Read by the host when it presents the scene immersively: no operation, no frame.
|
|
1421
1746
|
xrDescription = next.xr;
|
|
1422
1747
|
// Measured after the next drawn frame; a new list needs one.
|
|
1423
1748
|
if (next.measure.join('\u0000') !== measured.join('\u0000')) invalidate();
|
|
1424
1749
|
measured = next.measure;
|
|
1425
|
-
const ops = [...result.ops, ...syncTextures(next)];
|
|
1750
|
+
const ops = [...result.ops, ...syncTextures(next), ...syncHud(next)];
|
|
1426
1751
|
if (ops.length > 0) invalidate();
|
|
1427
1752
|
return { _tag: 'Applied', ops };
|
|
1428
1753
|
};
|
|
@@ -1435,6 +1760,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1435
1760
|
const tick = (elapsedSeconds: number): void => {
|
|
1436
1761
|
elapsed = elapsedSeconds;
|
|
1437
1762
|
for (const texture of textureList) texture.graph.tick(elapsedSeconds);
|
|
1763
|
+
hudHosted?.graph.tick(elapsedSeconds);
|
|
1438
1764
|
for (const entry of spinning) {
|
|
1439
1765
|
if (isPlaced(entry)) {
|
|
1440
1766
|
writeRotation(entry.object.rotation, entry.node.rotation, elapsed);
|
|
@@ -1453,7 +1779,14 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1453
1779
|
// After the camera and everything a billboard may sit on have moved.
|
|
1454
1780
|
if (facing.size > 0) {
|
|
1455
1781
|
camera.getWorldQuaternion(cameraTurn);
|
|
1456
|
-
|
|
1782
|
+
faceCameras();
|
|
1783
|
+
}
|
|
1784
|
+
edged.forEach(refreshEdges);
|
|
1785
|
+
// Reduced motion holds sparkles and stars where they are.
|
|
1786
|
+
if (animating) sparkling.forEach(sparkle);
|
|
1787
|
+
if (trailing.size > 0) {
|
|
1788
|
+
camera.getWorldPosition(eye);
|
|
1789
|
+
trailing.forEach(followTrail);
|
|
1457
1790
|
}
|
|
1458
1791
|
if (detailing.size > 0) {
|
|
1459
1792
|
camera.getWorldPosition(cameraPlace);
|
|
@@ -1468,6 +1801,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1468
1801
|
const setAnimating = (next: boolean): void => {
|
|
1469
1802
|
if (next !== animating) invalidate();
|
|
1470
1803
|
for (const texture of textureList) texture.graph.setAnimating(next);
|
|
1804
|
+
hudHosted?.graph.setAnimating(next);
|
|
1471
1805
|
animating = next;
|
|
1472
1806
|
if (next) return;
|
|
1473
1807
|
moving.forEach(finishTween);
|
|
@@ -1482,6 +1816,8 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1482
1816
|
for (const texture of textureList) texture.graph.dispose();
|
|
1483
1817
|
textureGraphs.clear();
|
|
1484
1818
|
textureList = [];
|
|
1819
|
+
hudHosted?.graph.dispose();
|
|
1820
|
+
hudHosted = undefined;
|
|
1485
1821
|
for (const entry of entries.values()) {
|
|
1486
1822
|
entry.object.removeFromParent();
|
|
1487
1823
|
release(entry);
|
|
@@ -1512,12 +1848,14 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1512
1848
|
return viewMotions.get(view)?.turned();
|
|
1513
1849
|
},
|
|
1514
1850
|
renderTextures: () => textureList,
|
|
1851
|
+
hud: () => hudHosted,
|
|
1515
1852
|
gestures: () => gestures,
|
|
1516
1853
|
overlays: () => overlayList,
|
|
1517
1854
|
environment: () => environmentDescription,
|
|
1518
1855
|
contactShadows: () => contactDescription,
|
|
1519
1856
|
collisions: bodyHost.drainCollisions,
|
|
1520
1857
|
contactForces: bodyHost.drainContactForces,
|
|
1858
|
+
physicsReached: bodyHost.drainReached,
|
|
1521
1859
|
xr: () => xrDescription,
|
|
1522
1860
|
measured: () => measured,
|
|
1523
1861
|
labelLayout: (key) => {
|
|
@@ -1540,7 +1878,9 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1540
1878
|
renderSettings: () => settings,
|
|
1541
1879
|
needsFrame: () =>
|
|
1542
1880
|
dirty ||
|
|
1881
|
+
trailsMoving() ||
|
|
1543
1882
|
textureList.some((texture) => texture.graph.needsFrame()) ||
|
|
1883
|
+
(hudHosted?.graph.needsFrame() ?? false) ||
|
|
1544
1884
|
(animating &&
|
|
1545
1885
|
(spinning.size > 0 ||
|
|
1546
1886
|
moving.size > 0 ||
|
|
@@ -1549,7 +1889,9 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1549
1889
|
cameraMotion.moving() ||
|
|
1550
1890
|
viewsMoving() ||
|
|
1551
1891
|
shaking() ||
|
|
1552
|
-
|
|
1892
|
+
sparkling.size > 0 ||
|
|
1893
|
+
bodyHost.needsFrame())) ||
|
|
1894
|
+
bodyHost.owesSteps(),
|
|
1553
1895
|
drawn: () => {
|
|
1554
1896
|
dirty = false;
|
|
1555
1897
|
},
|
|
@@ -1576,6 +1918,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1576
1918
|
viewport = { width, height };
|
|
1577
1919
|
fitCamera(camera, viewport);
|
|
1578
1920
|
hostViews(viewDescriptions);
|
|
1921
|
+
refitHud();
|
|
1579
1922
|
invalidate();
|
|
1580
1923
|
},
|
|
1581
1924
|
dispose,
|
package/src/kernel/sceneIndex.ts
CHANGED
|
@@ -32,6 +32,8 @@ export type DescriptionError =
|
|
|
32
32
|
| { readonly _tag: 'UnknownOverlayNode'; readonly key: string; readonly node: string }
|
|
33
33
|
| { readonly _tag: 'UnknownOverlayView'; readonly key: string; readonly view: string }
|
|
34
34
|
| { readonly _tag: 'UnknownMeasuredNode'; readonly key: string }
|
|
35
|
+
/** Two assets the scene asks for share an id. */
|
|
36
|
+
| { readonly _tag: 'DuplicateAssetId'; readonly id: string }
|
|
35
37
|
/** Two joints share a key. */
|
|
36
38
|
| { readonly _tag: 'DuplicateJointKey'; readonly key: string }
|
|
37
39
|
/** A joint names a node that is not in the scene. */
|
|
@@ -104,6 +106,12 @@ export const indexScene = (
|
|
|
104
106
|
if (duplicateView !== undefined) {
|
|
105
107
|
return { _tag: 'Invalid', error: { _tag: 'DuplicateViewKey', key: duplicateView } };
|
|
106
108
|
}
|
|
109
|
+
const duplicateAsset = firstDuplicateInstance(
|
|
110
|
+
description.assets.map((asset) => ({ key: asset.id })),
|
|
111
|
+
);
|
|
112
|
+
if (duplicateAsset !== undefined) {
|
|
113
|
+
return { _tag: 'Invalid', error: { _tag: 'DuplicateAssetId', id: duplicateAsset } };
|
|
114
|
+
}
|
|
107
115
|
const duplicateOverlay = firstDuplicateInstance(description.overlays);
|
|
108
116
|
if (duplicateOverlay !== undefined) {
|
|
109
117
|
return { _tag: 'Invalid', error: { _tag: 'DuplicateOverlayKey', key: duplicateOverlay } };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small seeded random generator (mulberry32): the same numbers for the same seed on
|
|
3
|
+
* every page load, so randomly scattered scene parts are reproducible.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** A generator of numbers in [0, 1) from `seed`, as `Math.random` would give them. */
|
|
9
|
+
export const seededRandom = (seed: number): (() => number) => {
|
|
10
|
+
let state = seed >>> 0;
|
|
11
|
+
return () => {
|
|
12
|
+
state = (state + 0x6d2b79f5) >>> 0;
|
|
13
|
+
let t = state;
|
|
14
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
15
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
16
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
17
|
+
};
|
|
18
|
+
};
|