@opsydyn/effect-three 0.3.0 → 0.4.0
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 +15 -0
- package/README.md +12 -0
- package/dist/{index-8cf8t6by.js → index-h3vf50fn.js} +2113 -83
- package/dist/index-h3vf50fn.js.map +53 -0
- package/dist/index-ttwy0znv.js +145 -0
- package/dist/index-ttwy0znv.js.map +12 -0
- package/dist/index.js +488 -147
- package/dist/index.js.map +8 -6
- package/dist/kernel/boxSelect.d.ts +22 -0
- package/dist/kernel/boxSelect.d.ts.map +1 -0
- package/dist/kernel/build.d.ts +142 -6
- 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 +155 -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 +9 -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 +13 -2
- package/dist/kernel/index.d.ts.map +1 -1
- package/dist/kernel/index.js +106 -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/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 +12 -0
- package/dist/kernel/pointer.d.ts.map +1 -1
- 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 +6 -0
- package/dist/kernel/sceneGraph.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/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 +448 -0
- package/dist/runtime/SceneFile.d.ts.map +1 -0
- package/dist/runtime/SceneSchema.d.ts +318 -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 +2 -1
- package/src/kernel/boxSelect.ts +73 -0
- package/src/kernel/build.ts +332 -0
- package/src/kernel/cameraShake.ts +3 -13
- package/src/kernel/describeScene.ts +224 -0
- package/src/kernel/description.ts +175 -0
- package/src/kernel/diff.ts +39 -0
- package/src/kernel/equality.ts +101 -0
- package/src/kernel/gizmo.ts +478 -0
- package/src/kernel/history.ts +118 -0
- package/src/kernel/index.ts +97 -0
- 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 +5 -0
- package/src/kernel/materials.ts +3 -0
- package/src/kernel/pivot.ts +418 -0
- package/src/kernel/pointBuffers.ts +115 -0
- package/src/kernel/pointer.ts +55 -12
- 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 +319 -8
- package/src/kernel/seeded.ts +18 -0
- package/src/kernel/shots.ts +167 -0
- package/src/kernel/tape.ts +124 -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 +247 -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
|
@@ -32,12 +32,14 @@ import { clipRunning, clipTime } from './animation.ts';
|
|
|
32
32
|
import { writeRotation } from './behaviours.ts';
|
|
33
33
|
import { type BodyCollision, type BodyContactForce, createBodyHost } from './bodies.ts';
|
|
34
34
|
import { type BodySource, instanceSources, nodeSource, placeObject } from './bodySources.ts';
|
|
35
|
-
import { renderSettings, scene as describeScene } from './build.ts';
|
|
35
|
+
import { orthographicCamera, renderSettings, scene as describeScene } from './build.ts';
|
|
36
36
|
import { type CameraMotion, createCameraMotion } from './cameraMotion.ts';
|
|
37
37
|
import { type CameraShaker, createCameraShaker } from './cameraShake.ts';
|
|
38
38
|
import type {
|
|
39
|
+
Hud,
|
|
39
40
|
AmbientLightNode,
|
|
40
41
|
BorrowedNode,
|
|
42
|
+
Edges,
|
|
41
43
|
DirectionalLightNode,
|
|
42
44
|
GridNode,
|
|
43
45
|
HemisphereLightNode,
|
|
@@ -45,6 +47,8 @@ import type {
|
|
|
45
47
|
Instance,
|
|
46
48
|
InstancesNode,
|
|
47
49
|
LabelNode,
|
|
50
|
+
LineNode,
|
|
51
|
+
PointsNode,
|
|
48
52
|
MaterialRef,
|
|
49
53
|
ShaderGridNode,
|
|
50
54
|
MeshNode,
|
|
@@ -68,9 +72,16 @@ import type {
|
|
|
68
72
|
ViewRect,
|
|
69
73
|
} from './description.ts';
|
|
70
74
|
import { diff, type Field, type Op } from './diff.ts';
|
|
71
|
-
import {
|
|
75
|
+
import {
|
|
76
|
+
contactShadowsEquals,
|
|
77
|
+
edgesEquals,
|
|
78
|
+
environmentEquals,
|
|
79
|
+
overlaysEqual,
|
|
80
|
+
skyEquals,
|
|
81
|
+
} from './equality.ts';
|
|
72
82
|
import { createInstances, fits, type HostInstances, writeInstances } from './instances.ts';
|
|
73
83
|
import type { HostedLabel, LabelEngine, LabelLayout } from './labels.ts';
|
|
84
|
+
import { edgeLine, edgePoints } from './lineGeometry.ts';
|
|
74
85
|
import type { HostMaterial } from './materials.ts';
|
|
75
86
|
import {
|
|
76
87
|
type Behaviour,
|
|
@@ -80,7 +91,14 @@ import {
|
|
|
80
91
|
resetPose,
|
|
81
92
|
} from './motion.ts';
|
|
82
93
|
import type { BodyPose, PhysicsEngine } from './physics.ts';
|
|
83
|
-
import
|
|
94
|
+
import { animatedLook } from './pointBuffers.ts';
|
|
95
|
+
import type {
|
|
96
|
+
HostedLine,
|
|
97
|
+
HostedPoints,
|
|
98
|
+
HostedShaderGrid,
|
|
99
|
+
HostedSky,
|
|
100
|
+
RenderKit,
|
|
101
|
+
} from './renderKit.ts';
|
|
84
102
|
import { createResourceStore, type ResourceStore } from './resources.ts';
|
|
85
103
|
import {
|
|
86
104
|
type DescriptionError,
|
|
@@ -89,6 +107,7 @@ import {
|
|
|
89
107
|
type ParentKey,
|
|
90
108
|
type SceneIndex,
|
|
91
109
|
} from './sceneIndex.ts';
|
|
110
|
+
import { createTrailRibbon, type TrailRibbon } from './trail.ts';
|
|
92
111
|
import { applyTween, startTween, type Tween } from './tween.ts';
|
|
93
112
|
|
|
94
113
|
/** The mesh type the kernel hosts. */
|
|
@@ -142,6 +161,20 @@ type Entry =
|
|
|
142
161
|
readonly _tag: 'HemisphereLight';
|
|
143
162
|
node: HemisphereLightNode;
|
|
144
163
|
readonly object: HemisphereLight;
|
|
164
|
+
}
|
|
165
|
+
// `hosted` is the points the render kit drew, inside the wrapper; none without a kit.
|
|
166
|
+
| {
|
|
167
|
+
readonly _tag: 'Points';
|
|
168
|
+
node: PointsNode;
|
|
169
|
+
readonly object: Group;
|
|
170
|
+
readonly hosted: HostedPoints | undefined;
|
|
171
|
+
}
|
|
172
|
+
// `hosted` is the line the render kit drew, inside the wrapper; none without a kit.
|
|
173
|
+
| {
|
|
174
|
+
readonly _tag: 'Line';
|
|
175
|
+
node: LineNode;
|
|
176
|
+
readonly object: Group;
|
|
177
|
+
readonly hosted: HostedLine | undefined;
|
|
145
178
|
};
|
|
146
179
|
|
|
147
180
|
/** Entries with a transform: position, rotation, scale and transitions. */
|
|
@@ -154,12 +187,26 @@ type Player = {
|
|
|
154
187
|
readonly startedAt: number;
|
|
155
188
|
};
|
|
156
189
|
|
|
190
|
+
/** A mesh's edge lines: the kit's line, and the geometry and look it was built from. */
|
|
191
|
+
type EdgeLines = {
|
|
192
|
+
readonly hosted: HostedLine;
|
|
193
|
+
geometry: BufferGeometry | undefined;
|
|
194
|
+
edges: Edges | undefined;
|
|
195
|
+
};
|
|
196
|
+
|
|
157
197
|
/** Descriptions with a transform. */
|
|
158
|
-
type PlacedNode =
|
|
198
|
+
type PlacedNode =
|
|
199
|
+
| GroupNode
|
|
200
|
+
| MeshNode
|
|
201
|
+
| InstancesNode
|
|
202
|
+
| BorrowedNode
|
|
203
|
+
| LabelNode
|
|
204
|
+
| LineNode
|
|
205
|
+
| PointsNode;
|
|
159
206
|
|
|
160
207
|
type Placed = Extract<
|
|
161
208
|
Entry,
|
|
162
|
-
{ readonly _tag: 'Group' | 'Mesh' | 'Instances' | 'Borrowed' | 'Label' }
|
|
209
|
+
{ readonly _tag: 'Group' | 'Mesh' | 'Instances' | 'Borrowed' | 'Label' | 'Line' | 'Points' }
|
|
163
210
|
>;
|
|
164
211
|
|
|
165
212
|
/** Entries drawing a geometry with a material. */
|
|
@@ -170,12 +217,17 @@ const isPlaced = (entry: Entry): entry is Placed =>
|
|
|
170
217
|
entry._tag === 'Mesh' ||
|
|
171
218
|
entry._tag === 'Instances' ||
|
|
172
219
|
entry._tag === 'Borrowed' ||
|
|
173
|
-
entry._tag === 'Label'
|
|
220
|
+
entry._tag === 'Label' ||
|
|
221
|
+
entry._tag === 'Line' ||
|
|
222
|
+
entry._tag === 'Points';
|
|
174
223
|
|
|
175
224
|
const isSurfaced = (entry: Entry): entry is Surfaced =>
|
|
176
225
|
entry._tag === 'Mesh' || entry._tag === 'Instances';
|
|
177
226
|
|
|
178
227
|
/** A render texture as hosted: its id, size and the nested graph that draws it. */
|
|
228
|
+
/** The HUD as hosted: its nested graph, drawn over the scene by the frame loop. */
|
|
229
|
+
export type HostedHud = { readonly graph: SceneGraph };
|
|
230
|
+
|
|
179
231
|
export type HostedTexture = {
|
|
180
232
|
readonly id: string;
|
|
181
233
|
readonly graph: SceneGraph;
|
|
@@ -232,6 +284,8 @@ export type SceneGraph = {
|
|
|
232
284
|
readonly turned: (view: string | undefined) => Vec3 | undefined;
|
|
233
285
|
/** Render textures, each a nested graph to draw into its texture (by the frame loop). */
|
|
234
286
|
readonly renderTextures: () => ReadonlyArray<HostedTexture>;
|
|
287
|
+
/** The HUD, a nested graph drawn over the scene (by the frame loop), if there is one. */
|
|
288
|
+
readonly hud: () => HostedHud | undefined;
|
|
235
289
|
/** Whether the scene claims the pointer's gestures from the page. */
|
|
236
290
|
readonly gestures: () => Gestures;
|
|
237
291
|
/** Points to show HTML at; the frame loop places them after drawing. */
|
|
@@ -448,6 +502,9 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
448
502
|
{ readonly graph: SceneGraph; description: RenderTexture }
|
|
449
503
|
>();
|
|
450
504
|
let textureList: ReadonlyArray<HostedTexture> = [];
|
|
505
|
+
let hudHosted:
|
|
506
|
+
| { readonly graph: SceneGraph; description: Hud; parent: SceneDescription }
|
|
507
|
+
| undefined;
|
|
451
508
|
let viewDescriptions: ReadonlyArray<View> = [];
|
|
452
509
|
let gestures: Gestures = emptyScene.gestures;
|
|
453
510
|
let overlayList: ReadonlyArray<Overlay> = emptyScene.overlays;
|
|
@@ -559,6 +616,26 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
559
616
|
const facing = new Set<Entry>();
|
|
560
617
|
const cameraTurn = new Quaternion();
|
|
561
618
|
const parentTurn = new Quaternion();
|
|
619
|
+
// Points that move by themselves (sparkles, stars), ticked in scene time while animating.
|
|
620
|
+
const sparkling = new Set<Entry>();
|
|
621
|
+
const trackSparkle = (entry: Entry, node: PointsNode): void => {
|
|
622
|
+
if (animatedLook(node.look)) sparkling.add(entry);
|
|
623
|
+
else sparkling.delete(entry);
|
|
624
|
+
if (entry._tag === 'Points') entry.hosted?.tick(animating ? elapsed : 0);
|
|
625
|
+
};
|
|
626
|
+
const sparkle = (entry: Entry): void => {
|
|
627
|
+
if (entry._tag === 'Points') entry.hosted?.tick(elapsed);
|
|
628
|
+
};
|
|
629
|
+
// Groups and meshes with trails: each ribbon, in the scene's space.
|
|
630
|
+
const trailing = new Map<Entry, TrailRibbon>();
|
|
631
|
+
const trailPlace = new Vector3();
|
|
632
|
+
const eye = new Vector3();
|
|
633
|
+
const trailsMoving = (): boolean => {
|
|
634
|
+
for (const ribbon of trailing.values()) if (!ribbon.settled()) return true;
|
|
635
|
+
return false;
|
|
636
|
+
};
|
|
637
|
+
// Meshes with edge lines: rebuilt when the mesh's geometry or the edges' look changes.
|
|
638
|
+
const edged = new Map<Entry, EdgeLines>();
|
|
562
639
|
// Groups with levels of detail: one child shown at a time, by the camera's distance.
|
|
563
640
|
const detailing = new Set<Entry>();
|
|
564
641
|
const cameraPlace = new Vector3();
|
|
@@ -639,6 +716,81 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
639
716
|
if (lockZ) object.rotation.z = 0;
|
|
640
717
|
};
|
|
641
718
|
|
|
719
|
+
/** Where a trail follows its node: the world position, or with `local` its own. */
|
|
720
|
+
const trailTarget = (object: Object3D, local: boolean): Vector3 =>
|
|
721
|
+
local ? trailPlace.copy(object.position) : object.getWorldPosition(trailPlace);
|
|
722
|
+
|
|
723
|
+
/** Adds, restyles or removes a node's trail as its description says. */
|
|
724
|
+
const trackTrail = (entry: Entry, node: GroupNode | MeshNode): void => {
|
|
725
|
+
const current = trailing.get(entry);
|
|
726
|
+
if (node.trail === null) {
|
|
727
|
+
if (current === undefined) return;
|
|
728
|
+
current.object.removeFromParent();
|
|
729
|
+
current.dispose();
|
|
730
|
+
trailing.delete(entry);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
const start = trailTarget(entry.object, node.trail.local);
|
|
734
|
+
if (current !== undefined) {
|
|
735
|
+
current.restyle(node.trail, start, elapsed);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
const ribbon = createTrailRibbon(node.trail, start, elapsed);
|
|
739
|
+
scene.add(ribbon.object);
|
|
740
|
+
trailing.set(entry, ribbon);
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
/** Records where a trailing node is now, and turns its ribbon to the camera. */
|
|
744
|
+
const followTrail = (ribbon: TrailRibbon, entry: Entry): void => {
|
|
745
|
+
if (entry._tag !== 'Group' && entry._tag !== 'Mesh') return;
|
|
746
|
+
if (entry.node.trail === null) return;
|
|
747
|
+
ribbon.record(trailTarget(entry.object, entry.node.trail.local), elapsed);
|
|
748
|
+
ribbon.face(eye);
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
/** Drei's `Edges` turns off picking on its line: hits belong to the mesh. */
|
|
752
|
+
const unpickable = (part: Object3D): void => {
|
|
753
|
+
part.raycast = () => undefined;
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
/** Adds, keeps or removes a mesh's edge lines as its description says. */
|
|
757
|
+
const trackEdges = (entry: Entry, node: MeshNode): void => {
|
|
758
|
+
const current = edged.get(entry);
|
|
759
|
+
const draw = options.kit?.line;
|
|
760
|
+
if (node.edges === null || draw === undefined) {
|
|
761
|
+
if (current === undefined) return;
|
|
762
|
+
current.hosted.object.removeFromParent();
|
|
763
|
+
current.hosted.dispose();
|
|
764
|
+
edged.delete(entry);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (current !== undefined) return;
|
|
768
|
+
const hosted = draw(edgeLine(node.key, node.edges, []));
|
|
769
|
+
entry.object.add(hosted.object);
|
|
770
|
+
edged.set(entry, { hosted, geometry: undefined, edges: undefined });
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Builds the edge lines again when the mesh shows another geometry (its own changed, a
|
|
775
|
+
* shared one was replaced, a lent one arrived) or the edges' look changed.
|
|
776
|
+
*/
|
|
777
|
+
const refreshEdges = (lines: EdgeLines, entry: Entry): void => {
|
|
778
|
+
if (entry._tag !== 'Mesh' || entry.node.edges === null) return;
|
|
779
|
+
const { geometry } = entry.object;
|
|
780
|
+
const look = entry.node.edges;
|
|
781
|
+
if (
|
|
782
|
+
geometry === lines.geometry &&
|
|
783
|
+
lines.edges !== undefined &&
|
|
784
|
+
edgesEquals(look, lines.edges)
|
|
785
|
+
) {
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
lines.hosted.update(edgeLine(entry.node.key, look, edgePoints(geometry, look.threshold)));
|
|
789
|
+
lines.hosted.object.traverse(unpickable);
|
|
790
|
+
lines.geometry = geometry;
|
|
791
|
+
lines.edges = look;
|
|
792
|
+
};
|
|
793
|
+
|
|
642
794
|
/** The object of a group's `index`th child, as described. */
|
|
643
795
|
const childObject = (node: GroupNode, index: number): Object3D | undefined => {
|
|
644
796
|
const child = node.children[index];
|
|
@@ -704,6 +856,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
704
856
|
// A group following a tracked space shows only once the space is found.
|
|
705
857
|
if (node.follow !== null) entry.object.visible = false;
|
|
706
858
|
trackDetail(entry, node);
|
|
859
|
+
trackTrail(entry, node);
|
|
707
860
|
return entry;
|
|
708
861
|
}
|
|
709
862
|
case 'Mesh': {
|
|
@@ -719,6 +872,8 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
719
872
|
trackInteractive(object, node.interactive);
|
|
720
873
|
object.castShadow = node.castShadow;
|
|
721
874
|
object.receiveShadow = node.receiveShadow;
|
|
875
|
+
trackEdges(entry, node);
|
|
876
|
+
trackTrail(entry, node);
|
|
722
877
|
return entry;
|
|
723
878
|
}
|
|
724
879
|
case 'Instances': {
|
|
@@ -799,6 +954,28 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
799
954
|
const hosted = options.kit?.shaderGrid(node);
|
|
800
955
|
return { _tag: 'ShaderGrid', node, object: hosted?.object ?? new Group(), hosted };
|
|
801
956
|
}
|
|
957
|
+
case 'Points': {
|
|
958
|
+
const hosted = options.kit?.points?.(node);
|
|
959
|
+
const entry: Entry = { _tag: 'Points', node, object: new Group(), hosted };
|
|
960
|
+
if (hosted !== undefined) entry.object.add(hosted.object);
|
|
961
|
+
writeVec3(entry.object.position, node.position);
|
|
962
|
+
writeVec3(entry.object.scale, node.scale);
|
|
963
|
+
trackRotation(entry, node);
|
|
964
|
+
trackBehaviour(entry, node);
|
|
965
|
+
trackSparkle(entry, node);
|
|
966
|
+
return entry;
|
|
967
|
+
}
|
|
968
|
+
case 'Line': {
|
|
969
|
+
const hosted = options.kit?.line?.(node);
|
|
970
|
+
const entry: Entry = { _tag: 'Line', node, object: new Group(), hosted };
|
|
971
|
+
if (hosted !== undefined) entry.object.add(hosted.object);
|
|
972
|
+
writeVec3(entry.object.position, node.position);
|
|
973
|
+
writeVec3(entry.object.scale, node.scale);
|
|
974
|
+
trackRotation(entry, node);
|
|
975
|
+
trackBehaviour(entry, node);
|
|
976
|
+
trackInteractive(entry.object, node.interactive);
|
|
977
|
+
return entry;
|
|
978
|
+
}
|
|
802
979
|
case 'HemisphereLight': {
|
|
803
980
|
const object = new HemisphereLight();
|
|
804
981
|
writeHemisphereLight(object, node);
|
|
@@ -973,6 +1150,17 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
973
1150
|
spinning.delete(entry);
|
|
974
1151
|
facing.delete(entry);
|
|
975
1152
|
detailing.delete(entry);
|
|
1153
|
+
const lines = edged.get(entry);
|
|
1154
|
+
if (lines !== undefined) {
|
|
1155
|
+
lines.hosted.dispose();
|
|
1156
|
+
edged.delete(entry);
|
|
1157
|
+
}
|
|
1158
|
+
const ribbon = trailing.get(entry);
|
|
1159
|
+
if (ribbon !== undefined) {
|
|
1160
|
+
ribbon.object.removeFromParent();
|
|
1161
|
+
ribbon.dispose();
|
|
1162
|
+
trailing.delete(entry);
|
|
1163
|
+
}
|
|
976
1164
|
moving.delete(entry);
|
|
977
1165
|
reparented.delete(entry);
|
|
978
1166
|
trackInteractive(entry.object, false);
|
|
@@ -984,7 +1172,10 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
984
1172
|
if (entry._tag === 'Instances') entry.object.dispose();
|
|
985
1173
|
// A grid's lines are its own.
|
|
986
1174
|
if (entry._tag === 'Grid') disposeGridLines(entry.helper);
|
|
987
|
-
if (entry._tag === 'ShaderGrid'
|
|
1175
|
+
if (entry._tag === 'ShaderGrid' || entry._tag === 'Line' || entry._tag === 'Points') {
|
|
1176
|
+
entry.hosted?.dispose();
|
|
1177
|
+
}
|
|
1178
|
+
sparkling.delete(entry);
|
|
988
1179
|
if (entry._tag === 'Label') {
|
|
989
1180
|
entry.hosted?.dispose();
|
|
990
1181
|
if (entry.node.material !== null && entry.material !== undefined) {
|
|
@@ -1114,6 +1305,12 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1114
1305
|
entry.node = next;
|
|
1115
1306
|
if (movesTransform(entry, fields)) retarget(entry, next);
|
|
1116
1307
|
if (fields.includes('rotation')) trackRotation(entry, next);
|
|
1308
|
+
if (entry._tag === 'Mesh' && next._tag === 'Mesh' && fields.includes('edges')) {
|
|
1309
|
+
trackEdges(entry, next);
|
|
1310
|
+
}
|
|
1311
|
+
if (entry._tag === 'Mesh' && next._tag === 'Mesh' && fields.includes('trail')) {
|
|
1312
|
+
trackTrail(entry, next);
|
|
1313
|
+
}
|
|
1117
1314
|
if (fields.includes('behaviour')) trackBehaviour(entry, next);
|
|
1118
1315
|
if (fields.includes('interactive')) trackInteractive(mesh, next.interactive);
|
|
1119
1316
|
mesh.castShadow = next.castShadow;
|
|
@@ -1150,6 +1347,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1150
1347
|
if (fields.includes('behaviour')) trackBehaviour(entry, next);
|
|
1151
1348
|
if (fields.includes('interactive')) trackInteractive(entry.object, next.interactive);
|
|
1152
1349
|
if (fields.includes('detail')) trackDetail(entry, next);
|
|
1350
|
+
if (fields.includes('trail')) trackTrail(entry, next);
|
|
1153
1351
|
if (fields.includes('follow')) {
|
|
1154
1352
|
// Back to its own transform, or hidden until the new space is found.
|
|
1155
1353
|
entry.object.visible = next.follow === null;
|
|
@@ -1186,6 +1384,26 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1186
1384
|
entry.node = next;
|
|
1187
1385
|
return;
|
|
1188
1386
|
}
|
|
1387
|
+
if (entry._tag === 'Points' && next._tag === 'Points') {
|
|
1388
|
+
entry.node = next;
|
|
1389
|
+
if (movesTransform(entry, fields)) retarget(entry, next);
|
|
1390
|
+
if (fields.includes('rotation')) trackRotation(entry, next);
|
|
1391
|
+
if (fields.includes('behaviour')) trackBehaviour(entry, next);
|
|
1392
|
+
if (fields.includes('look')) {
|
|
1393
|
+
entry.hosted?.update(next);
|
|
1394
|
+
trackSparkle(entry, next);
|
|
1395
|
+
}
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
if (entry._tag === 'Line' && next._tag === 'Line') {
|
|
1399
|
+
entry.node = next;
|
|
1400
|
+
if (movesTransform(entry, fields)) retarget(entry, next);
|
|
1401
|
+
if (fields.includes('rotation')) trackRotation(entry, next);
|
|
1402
|
+
if (fields.includes('behaviour')) trackBehaviour(entry, next);
|
|
1403
|
+
if (fields.includes('interactive')) trackInteractive(entry.object, next.interactive);
|
|
1404
|
+
if (fields.includes('line')) entry.hosted?.update(next);
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1189
1407
|
if (entry._tag === 'ShaderGrid' && next._tag === 'ShaderGrid') {
|
|
1190
1408
|
entry.hosted?.update(entry.node, next);
|
|
1191
1409
|
entry.node = next;
|
|
@@ -1317,6 +1535,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1317
1535
|
gestures: 'page',
|
|
1318
1536
|
views: [],
|
|
1319
1537
|
renderTextures: [],
|
|
1538
|
+
hud: null,
|
|
1320
1539
|
overlays: [],
|
|
1321
1540
|
environment: null,
|
|
1322
1541
|
sky: null,
|
|
@@ -1329,6 +1548,78 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1329
1548
|
children: texture.children,
|
|
1330
1549
|
});
|
|
1331
1550
|
|
|
1551
|
+
/**
|
|
1552
|
+
* The HUD's camera: orthographic in CSS pixels, from in front, aimed so that the HUD's
|
|
1553
|
+
* origin falls `margin` in from the aligned edges (as Drei's `GizmoHelper` places its
|
|
1554
|
+
* group). The camera moves rather than the nodes, so their positions stay the Model's.
|
|
1555
|
+
*/
|
|
1556
|
+
const hudCamera = (hud: Hud): CameraDescription => {
|
|
1557
|
+
const [marginX, marginY] = hud.margin;
|
|
1558
|
+
const [vertical, horizontal] = hud.alignment.split('-');
|
|
1559
|
+
const across = { left: -viewport.width / 2 + marginX, right: viewport.width / 2 - marginX };
|
|
1560
|
+
const up = { top: viewport.height / 2 - marginY, bottom: -viewport.height / 2 + marginY };
|
|
1561
|
+
const x = across[horizontal as keyof typeof across] ?? 0;
|
|
1562
|
+
const y = up[vertical as keyof typeof up] ?? 0;
|
|
1563
|
+
return orthographicCamera({
|
|
1564
|
+
zoom: 1,
|
|
1565
|
+
near: 0.1,
|
|
1566
|
+
far: 2000,
|
|
1567
|
+
position: [-x, -y, 200],
|
|
1568
|
+
lookAt: [-x, -y, 0],
|
|
1569
|
+
});
|
|
1570
|
+
};
|
|
1571
|
+
|
|
1572
|
+
/** The HUD as a scene of its own, sharing the parent's resources. */
|
|
1573
|
+
const hudScene = (hud: Hud, parent: SceneDescription): SceneDescription =>
|
|
1574
|
+
textureScene(
|
|
1575
|
+
{
|
|
1576
|
+
id: 'hud',
|
|
1577
|
+
width: viewport.width,
|
|
1578
|
+
height: viewport.height,
|
|
1579
|
+
camera: hudCamera(hud),
|
|
1580
|
+
background: null,
|
|
1581
|
+
children: hud.children,
|
|
1582
|
+
},
|
|
1583
|
+
parent,
|
|
1584
|
+
);
|
|
1585
|
+
|
|
1586
|
+
/**
|
|
1587
|
+
* Brings the HUD in line with the description, `parent` the scene it is over: a nested
|
|
1588
|
+
* graph on the same store, created, reconciled, re-aimed or disposed.
|
|
1589
|
+
*/
|
|
1590
|
+
const syncHud = (next: SceneDescription): ReadonlyArray<Op> => {
|
|
1591
|
+
if (next.hud === null) {
|
|
1592
|
+
if (hudHosted === undefined) return [];
|
|
1593
|
+
hudHosted.graph.dispose();
|
|
1594
|
+
hudHosted = undefined;
|
|
1595
|
+
return [{ _tag: 'RemoveHud' }];
|
|
1596
|
+
}
|
|
1597
|
+
const created = hudHosted === undefined;
|
|
1598
|
+
const hosted = hudHosted ?? {
|
|
1599
|
+
graph: createSceneGraph({
|
|
1600
|
+
store,
|
|
1601
|
+
...(options.behaviours === undefined ? {} : { behaviours: options.behaviours }),
|
|
1602
|
+
...(options.kit === undefined ? {} : { kit: options.kit }),
|
|
1603
|
+
}),
|
|
1604
|
+
description: next.hud,
|
|
1605
|
+
parent: next,
|
|
1606
|
+
};
|
|
1607
|
+
if (created) hosted.graph.resize(viewport.width, viewport.height);
|
|
1608
|
+
const result = hosted.graph.reconcile(hudScene(next.hud, next));
|
|
1609
|
+
hosted.description = next.hud;
|
|
1610
|
+
hosted.parent = next;
|
|
1611
|
+
hudHosted = hosted;
|
|
1612
|
+
const ops = result._tag === 'Applied' ? result.ops : [];
|
|
1613
|
+
return created || ops.length > 0 ? [{ _tag: 'UpdateHud', ops }] : [];
|
|
1614
|
+
};
|
|
1615
|
+
|
|
1616
|
+
/** The HUD fitted to a resized canvas: its camera re-aimed at the aligned point. */
|
|
1617
|
+
const refitHud = (): void => {
|
|
1618
|
+
if (hudHosted === undefined) return;
|
|
1619
|
+
hudHosted.graph.resize(viewport.width, viewport.height);
|
|
1620
|
+
hudHosted.graph.reconcile(hudScene(hudHosted.description, hudHosted.parent));
|
|
1621
|
+
};
|
|
1622
|
+
|
|
1332
1623
|
/** A hosted render texture as the loop sees it; none if it is not hosted. */
|
|
1333
1624
|
function hostedTexture(texture: RenderTexture): ReadonlyArray<HostedTexture> {
|
|
1334
1625
|
const hosted = textureGraphs.get(texture.id);
|
|
@@ -1389,6 +1680,10 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1389
1680
|
const checked = indexScene(textureScene(texture, next), knownBehaviours);
|
|
1390
1681
|
if (checked._tag === 'Invalid') return checked;
|
|
1391
1682
|
}
|
|
1683
|
+
if (next.hud !== null) {
|
|
1684
|
+
const checked = indexScene(hudScene(next.hud, next), knownBehaviours);
|
|
1685
|
+
if (checked._tag === 'Invalid') return checked;
|
|
1686
|
+
}
|
|
1392
1687
|
const result = diff(index, next, knownBehaviours);
|
|
1393
1688
|
if (result._tag === 'Invalid') return result;
|
|
1394
1689
|
for (const op of result.ops) apply(op);
|
|
@@ -1422,7 +1717,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1422
1717
|
// Measured after the next drawn frame; a new list needs one.
|
|
1423
1718
|
if (next.measure.join('\u0000') !== measured.join('\u0000')) invalidate();
|
|
1424
1719
|
measured = next.measure;
|
|
1425
|
-
const ops = [...result.ops, ...syncTextures(next)];
|
|
1720
|
+
const ops = [...result.ops, ...syncTextures(next), ...syncHud(next)];
|
|
1426
1721
|
if (ops.length > 0) invalidate();
|
|
1427
1722
|
return { _tag: 'Applied', ops };
|
|
1428
1723
|
};
|
|
@@ -1435,6 +1730,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1435
1730
|
const tick = (elapsedSeconds: number): void => {
|
|
1436
1731
|
elapsed = elapsedSeconds;
|
|
1437
1732
|
for (const texture of textureList) texture.graph.tick(elapsedSeconds);
|
|
1733
|
+
hudHosted?.graph.tick(elapsedSeconds);
|
|
1438
1734
|
for (const entry of spinning) {
|
|
1439
1735
|
if (isPlaced(entry)) {
|
|
1440
1736
|
writeRotation(entry.object.rotation, entry.node.rotation, elapsed);
|
|
@@ -1455,6 +1751,13 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1455
1751
|
camera.getWorldQuaternion(cameraTurn);
|
|
1456
1752
|
facing.forEach(faceCamera);
|
|
1457
1753
|
}
|
|
1754
|
+
edged.forEach(refreshEdges);
|
|
1755
|
+
// Reduced motion holds sparkles and stars where they are.
|
|
1756
|
+
if (animating) sparkling.forEach(sparkle);
|
|
1757
|
+
if (trailing.size > 0) {
|
|
1758
|
+
camera.getWorldPosition(eye);
|
|
1759
|
+
trailing.forEach(followTrail);
|
|
1760
|
+
}
|
|
1458
1761
|
if (detailing.size > 0) {
|
|
1459
1762
|
camera.getWorldPosition(cameraPlace);
|
|
1460
1763
|
detailing.forEach(showLevel);
|
|
@@ -1468,6 +1771,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1468
1771
|
const setAnimating = (next: boolean): void => {
|
|
1469
1772
|
if (next !== animating) invalidate();
|
|
1470
1773
|
for (const texture of textureList) texture.graph.setAnimating(next);
|
|
1774
|
+
hudHosted?.graph.setAnimating(next);
|
|
1471
1775
|
animating = next;
|
|
1472
1776
|
if (next) return;
|
|
1473
1777
|
moving.forEach(finishTween);
|
|
@@ -1482,6 +1786,8 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1482
1786
|
for (const texture of textureList) texture.graph.dispose();
|
|
1483
1787
|
textureGraphs.clear();
|
|
1484
1788
|
textureList = [];
|
|
1789
|
+
hudHosted?.graph.dispose();
|
|
1790
|
+
hudHosted = undefined;
|
|
1485
1791
|
for (const entry of entries.values()) {
|
|
1486
1792
|
entry.object.removeFromParent();
|
|
1487
1793
|
release(entry);
|
|
@@ -1512,6 +1818,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1512
1818
|
return viewMotions.get(view)?.turned();
|
|
1513
1819
|
},
|
|
1514
1820
|
renderTextures: () => textureList,
|
|
1821
|
+
hud: () => hudHosted,
|
|
1515
1822
|
gestures: () => gestures,
|
|
1516
1823
|
overlays: () => overlayList,
|
|
1517
1824
|
environment: () => environmentDescription,
|
|
@@ -1540,7 +1847,9 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1540
1847
|
renderSettings: () => settings,
|
|
1541
1848
|
needsFrame: () =>
|
|
1542
1849
|
dirty ||
|
|
1850
|
+
trailsMoving() ||
|
|
1543
1851
|
textureList.some((texture) => texture.graph.needsFrame()) ||
|
|
1852
|
+
(hudHosted?.graph.needsFrame() ?? false) ||
|
|
1544
1853
|
(animating &&
|
|
1545
1854
|
(spinning.size > 0 ||
|
|
1546
1855
|
moving.size > 0 ||
|
|
@@ -1549,6 +1858,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1549
1858
|
cameraMotion.moving() ||
|
|
1550
1859
|
viewsMoving() ||
|
|
1551
1860
|
shaking() ||
|
|
1861
|
+
sparkling.size > 0 ||
|
|
1552
1862
|
bodyHost.needsFrame())),
|
|
1553
1863
|
drawn: () => {
|
|
1554
1864
|
dirty = false;
|
|
@@ -1576,6 +1886,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1576
1886
|
viewport = { width, height };
|
|
1577
1887
|
fitCamera(camera, viewport);
|
|
1578
1888
|
hostViews(viewDescriptions);
|
|
1889
|
+
refitHud();
|
|
1579
1890
|
invalidate();
|
|
1580
1891
|
},
|
|
1581
1892
|
dispose,
|
|
@@ -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
|
+
};
|