@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.
Files changed (143) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +15 -1
  3. package/dist/{index-8cf8t6by.js → index-re3rrfwd.js} +2935 -104
  4. package/dist/index-re3rrfwd.js.map +56 -0
  5. package/dist/index-ttwy0znv.js +145 -0
  6. package/dist/index-ttwy0znv.js.map +12 -0
  7. package/dist/index.js +557 -147
  8. package/dist/index.js.map +8 -6
  9. package/dist/kernel/bodies.d.ts +20 -1
  10. package/dist/kernel/bodies.d.ts.map +1 -1
  11. package/dist/kernel/boxSelect.d.ts +22 -0
  12. package/dist/kernel/boxSelect.d.ts.map +1 -0
  13. package/dist/kernel/build.d.ts +165 -7
  14. package/dist/kernel/build.d.ts.map +1 -1
  15. package/dist/kernel/cameraShake.d.ts.map +1 -1
  16. package/dist/kernel/describeScene.d.ts +50 -0
  17. package/dist/kernel/describeScene.d.ts.map +1 -0
  18. package/dist/kernel/description.d.ts +223 -1
  19. package/dist/kernel/description.d.ts.map +1 -1
  20. package/dist/kernel/diff.d.ts +15 -0
  21. package/dist/kernel/diff.d.ts.map +1 -1
  22. package/dist/kernel/equality.d.ts +11 -1
  23. package/dist/kernel/equality.d.ts.map +1 -1
  24. package/dist/kernel/gizmo.d.ts +69 -0
  25. package/dist/kernel/gizmo.d.ts.map +1 -0
  26. package/dist/kernel/history.d.ts +61 -0
  27. package/dist/kernel/history.d.ts.map +1 -0
  28. package/dist/kernel/index.d.ts +19 -6
  29. package/dist/kernel/index.d.ts.map +1 -1
  30. package/dist/kernel/index.js +124 -3
  31. package/dist/kernel/index.js.map +1 -1
  32. package/dist/kernel/inspect.d.ts +85 -0
  33. package/dist/kernel/inspect.d.ts.map +1 -0
  34. package/dist/kernel/lineGeometry.d.ts +25 -0
  35. package/dist/kernel/lineGeometry.d.ts.map +1 -0
  36. package/dist/kernel/lockstep.d.ts +114 -0
  37. package/dist/kernel/lockstep.d.ts.map +1 -0
  38. package/dist/kernel/loop.d.ts.map +1 -1
  39. package/dist/kernel/materials.d.ts.map +1 -1
  40. package/dist/kernel/paintOrder.d.ts +53 -0
  41. package/dist/kernel/paintOrder.d.ts.map +1 -0
  42. package/dist/kernel/physics.d.ts +39 -0
  43. package/dist/kernel/physics.d.ts.map +1 -1
  44. package/dist/kernel/pivot.d.ts +54 -0
  45. package/dist/kernel/pivot.d.ts.map +1 -0
  46. package/dist/kernel/pointBuffers.d.ts +17 -0
  47. package/dist/kernel/pointBuffers.d.ts.map +1 -0
  48. package/dist/kernel/pointer.d.ts +31 -1
  49. package/dist/kernel/pointer.d.ts.map +1 -1
  50. package/dist/kernel/preview.d.ts +32 -0
  51. package/dist/kernel/preview.d.ts.map +1 -0
  52. package/dist/kernel/renderKit.d.ts +21 -1
  53. package/dist/kernel/renderKit.d.ts.map +1 -1
  54. package/dist/kernel/renderer.d.ts +6 -0
  55. package/dist/kernel/renderer.d.ts.map +1 -1
  56. package/dist/kernel/sceneChanges.d.ts +87 -0
  57. package/dist/kernel/sceneChanges.d.ts.map +1 -0
  58. package/dist/kernel/sceneGraph.d.ts +9 -1
  59. package/dist/kernel/sceneGraph.d.ts.map +1 -1
  60. package/dist/kernel/sceneIndex.d.ts +5 -0
  61. package/dist/kernel/sceneIndex.d.ts.map +1 -1
  62. package/dist/kernel/seeded.d.ts +9 -0
  63. package/dist/kernel/seeded.d.ts.map +1 -0
  64. package/dist/kernel/shots.d.ts +26 -0
  65. package/dist/kernel/shots.d.ts.map +1 -0
  66. package/dist/kernel/tape.d.ts +38 -0
  67. package/dist/kernel/tape.d.ts.map +1 -0
  68. package/dist/kernel/timeline.d.ts +60 -0
  69. package/dist/kernel/timeline.d.ts.map +1 -0
  70. package/dist/kernel/trail.d.ts +31 -0
  71. package/dist/kernel/trail.d.ts.map +1 -0
  72. package/dist/kernel/viewportGizmo.d.ts +25 -0
  73. package/dist/kernel/viewportGizmo.d.ts.map +1 -0
  74. package/dist/runtime/Inspector.d.ts +20 -0
  75. package/dist/runtime/Inspector.d.ts.map +1 -0
  76. package/dist/runtime/SceneFile.d.ts +462 -0
  77. package/dist/runtime/SceneFile.d.ts.map +1 -0
  78. package/dist/runtime/SceneSchema.d.ts +404 -0
  79. package/dist/runtime/SceneSchema.d.ts.map +1 -1
  80. package/dist/runtime/errors.d.ts +9 -0
  81. package/dist/runtime/errors.d.ts.map +1 -1
  82. package/dist/runtime/index.d.ts +3 -1
  83. package/dist/runtime/index.d.ts.map +1 -1
  84. package/dist/webgl/index.d.ts.map +1 -1
  85. package/dist/webgl/index.js +228 -3
  86. package/dist/webgl/index.js.map +6 -4
  87. package/dist/webgl/line.d.ts +4 -0
  88. package/dist/webgl/line.d.ts.map +1 -0
  89. package/dist/webgl/points.d.ts +4 -0
  90. package/dist/webgl/points.d.ts.map +1 -0
  91. package/dist/webgpu/index.d.ts.map +1 -1
  92. package/dist/webgpu/index.js +206 -26
  93. package/dist/webgpu/index.js.map +6 -4
  94. package/dist/webgpu/line.d.ts +4 -0
  95. package/dist/webgpu/line.d.ts.map +1 -0
  96. package/dist/webgpu/points.d.ts +4 -0
  97. package/dist/webgpu/points.d.ts.map +1 -0
  98. package/package.json +4 -2
  99. package/src/kernel/bodies.ts +299 -17
  100. package/src/kernel/boxSelect.ts +73 -0
  101. package/src/kernel/build.ts +374 -1
  102. package/src/kernel/cameraShake.ts +3 -13
  103. package/src/kernel/describeScene.ts +224 -0
  104. package/src/kernel/description.ts +243 -0
  105. package/src/kernel/diff.ts +39 -0
  106. package/src/kernel/equality.ts +123 -0
  107. package/src/kernel/gizmo.ts +478 -0
  108. package/src/kernel/history.ts +118 -0
  109. package/src/kernel/index.ts +110 -1
  110. package/src/kernel/inspect.ts +214 -0
  111. package/src/kernel/lineGeometry.ts +75 -0
  112. package/src/kernel/lockstep.ts +237 -0
  113. package/src/kernel/loop.ts +7 -0
  114. package/src/kernel/materials.ts +3 -0
  115. package/src/kernel/paintOrder.ts +274 -0
  116. package/src/kernel/physics.ts +86 -1
  117. package/src/kernel/pivot.ts +418 -0
  118. package/src/kernel/pointBuffers.ts +115 -0
  119. package/src/kernel/pointer.ts +98 -17
  120. package/src/kernel/preview.ts +467 -0
  121. package/src/kernel/renderKit.ts +23 -1
  122. package/src/kernel/renderer.ts +6 -0
  123. package/src/kernel/sceneChanges.ts +265 -0
  124. package/src/kernel/sceneGraph.ts +354 -11
  125. package/src/kernel/sceneIndex.ts +8 -0
  126. package/src/kernel/seeded.ts +18 -0
  127. package/src/kernel/shots.ts +167 -0
  128. package/src/kernel/tape.ts +124 -0
  129. package/src/kernel/timeline.ts +93 -0
  130. package/src/kernel/trail.ts +170 -0
  131. package/src/kernel/viewportGizmo.ts +194 -0
  132. package/src/runtime/Inspector.ts +43 -0
  133. package/src/runtime/SceneFile.ts +46 -0
  134. package/src/runtime/SceneSchema.ts +319 -0
  135. package/src/runtime/errors.ts +6 -0
  136. package/src/runtime/index.ts +3 -1
  137. package/src/webgl/index.ts +4 -0
  138. package/src/webgl/line.ts +66 -0
  139. package/src/webgl/points.ts +205 -0
  140. package/src/webgpu/index.ts +4 -0
  141. package/src/webgpu/line.ts +67 -0
  142. package/src/webgpu/points.ts +193 -0
  143. package/dist/index-8cf8t6by.js.map +0 -41
@@ -0,0 +1,224 @@
1
+ /**
2
+ * What a scene contains, for an assistant (roadmap, "Beyond parity" 3): its camera, its
3
+ * things with their kind, size, colour and place, its lights, the box they fill and its
4
+ * staging, as data (`describeScene`) and as a few lines of text (`formatSummary`) an
5
+ * agent reads before it acts. An agent that reads the Model can describe a scene
6
+ * without three.js, so sizes come from the geometry's parameters and the node's scale:
7
+ * close enough to place things beside one another and to frame a shot.
8
+ *
9
+ * @module
10
+ */
11
+ import type {
12
+ GeometryRef,
13
+ Rgb,
14
+ SceneDescription,
15
+ SceneNode,
16
+ ShapeGeometry,
17
+ Vec3,
18
+ } from './description.ts';
19
+ import type { Box } from './orbit.ts';
20
+
21
+ export type ThingSummary = {
22
+ readonly key: string;
23
+ /** The node's kind, with its geometry's for a mesh: `Mesh (Box)`. */
24
+ readonly kind: string;
25
+ /** In its parent's space; for things at the top, the world's. */
26
+ readonly position: Vec3;
27
+ /** Width, height and depth, from the geometry and scale; `null` when not known. */
28
+ readonly size: Vec3 | null;
29
+ readonly colour: Rgb | null;
30
+ /** The group it is in, `null` at the top. */
31
+ readonly parent: string | null;
32
+ };
33
+
34
+ export type LightSummary = {
35
+ readonly key: string;
36
+ readonly kind: string;
37
+ readonly colour: Rgb;
38
+ readonly intensity: number;
39
+ readonly position: Vec3 | null;
40
+ };
41
+
42
+ export type SceneSummary = {
43
+ readonly camera: {
44
+ readonly kind: 'Perspective' | 'Orthographic';
45
+ readonly position: Vec3;
46
+ readonly lookAt: Vec3;
47
+ };
48
+ readonly things: ReadonlyArray<ThingSummary>;
49
+ readonly lights: ReadonlyArray<LightSummary>;
50
+ /** The box the top-level things of known size fill, `null` with none. */
51
+ readonly bounds: Box | null;
52
+ readonly staging: ReadonlyArray<string>;
53
+ };
54
+
55
+ /** Width, height and depth of a shape, unscaled; `undefined` for text and shapes. */
56
+ const extentOf = (geometry: ShapeGeometry): Vec3 | undefined => {
57
+ switch (geometry._tag) {
58
+ case 'Box':
59
+ case 'RoundedBox':
60
+ return [geometry.width, geometry.height, geometry.depth];
61
+ case 'Sphere':
62
+ return [geometry.radius * 2, geometry.radius * 2, geometry.radius * 2];
63
+ case 'Cone':
64
+ return [geometry.radius * 2, geometry.height, geometry.radius * 2];
65
+ case 'Cylinder': {
66
+ const across = Math.max(geometry.radiusTop, geometry.radiusBottom) * 2;
67
+ return [across, geometry.height, across];
68
+ }
69
+ case 'Capsule':
70
+ return [geometry.radius * 2, geometry.height + geometry.radius * 2, geometry.radius * 2];
71
+ case 'Torus': {
72
+ const across = (geometry.radius + geometry.tube) * 2;
73
+ return [across, across, geometry.tube * 2];
74
+ }
75
+ case 'Plane':
76
+ return [geometry.width, geometry.height, 0];
77
+ case 'Circle':
78
+ return [geometry.radius * 2, geometry.radius * 2, 0];
79
+ case 'Ring':
80
+ return [geometry.outerRadius * 2, geometry.outerRadius * 2, 0];
81
+ default:
82
+ return undefined;
83
+ }
84
+ };
85
+
86
+ const scaled = (extent: Vec3, scale: Vec3): Vec3 => [
87
+ extent[0] * Math.abs(scale[0]),
88
+ extent[1] * Math.abs(scale[1]),
89
+ extent[2] * Math.abs(scale[2]),
90
+ ];
91
+
92
+ /** The shape a geometry reference draws: its own, a shared one's, or none (lent). */
93
+ const shapeOf = (
94
+ geometry: GeometryRef,
95
+ description: SceneDescription,
96
+ ): ShapeGeometry | undefined => {
97
+ if (geometry._tag === 'LentGeometry') return undefined;
98
+ if (geometry._tag !== 'SharedGeometry') return geometry;
99
+ return description.resources.geometries.find((entry) => entry.id === geometry.id)?.geometry;
100
+ };
101
+
102
+ const sizeOf = (node: SceneNode, description: SceneDescription): Vec3 | null => {
103
+ if (node._tag !== 'Mesh') return null;
104
+ const shape = shapeOf(node.geometry, description);
105
+ const extent = shape === undefined ? undefined : extentOf(shape);
106
+ return extent === undefined ? null : scaled(extent, node.scale);
107
+ };
108
+
109
+ const colourOf = (node: SceneNode): Rgb | null =>
110
+ node._tag === 'Mesh' && 'color' in node.material ? node.material.color : null;
111
+
112
+ const kindOf = (node: SceneNode): string =>
113
+ node._tag === 'Mesh' ? `Mesh (${node.geometry._tag})` : node._tag;
114
+
115
+ const isLight = (node: SceneNode): boolean => node._tag.endsWith('Light');
116
+
117
+ const lightOf = (node: SceneNode): ReadonlyArray<LightSummary> => {
118
+ if (!isLight(node) || !('color' in node) || !('intensity' in node)) return [];
119
+ return [
120
+ {
121
+ key: node.key,
122
+ kind: node._tag,
123
+ colour: node.color,
124
+ intensity: node.intensity,
125
+ position: 'position' in node ? node.position : null,
126
+ },
127
+ ];
128
+ };
129
+
130
+ const thingsIn = (
131
+ nodes: ReadonlyArray<SceneNode>,
132
+ parent: string | null,
133
+ description: SceneDescription,
134
+ ): ReadonlyArray<ThingSummary> =>
135
+ nodes.flatMap((node) => [
136
+ ...(isLight(node)
137
+ ? []
138
+ : [
139
+ {
140
+ key: node.key,
141
+ kind: kindOf(node),
142
+ position: 'position' in node ? node.position : ([0, 0, 0] as const),
143
+ size: sizeOf(node, description),
144
+ colour: colourOf(node),
145
+ parent,
146
+ },
147
+ ]),
148
+ ...(node._tag === 'Group' ? thingsIn(node.children, node.key, description) : []),
149
+ ]);
150
+
151
+ const lightsIn = (nodes: ReadonlyArray<SceneNode>): ReadonlyArray<LightSummary> =>
152
+ nodes.flatMap((node) => [
153
+ ...lightOf(node),
154
+ ...(node._tag === 'Group' ? lightsIn(node.children) : []),
155
+ ]);
156
+
157
+ /** The box top-level things of known size fill, each taken as a box round its place. */
158
+ export const thingsBounds = (things: ReadonlyArray<ThingSummary>): Box | null => {
159
+ const sized = things.filter((thing) => thing.parent === null && thing.size !== null);
160
+ if (sized.length === 0) return null;
161
+ const edge = (axis: 0 | 1 | 2, sign: 1 | -1): ReadonlyArray<number> =>
162
+ sized.map((thing) => thing.position[axis] + sign * ((thing.size?.[axis] ?? 0) / 2));
163
+ const low = (axis: 0 | 1 | 2) => Math.min(...edge(axis, -1));
164
+ const high = (axis: 0 | 1 | 2) => Math.max(...edge(axis, 1));
165
+ return { min: [low(0), low(1), low(2)], max: [high(0), high(1), high(2)] };
166
+ };
167
+
168
+ const stagingOf = (description: SceneDescription): ReadonlyArray<string> => [
169
+ ...(description.environment === null ? [] : ['environment lighting']),
170
+ ...(description.sky === null ? [] : ['sky']),
171
+ ...(description.render.shadows === 'off' ? [] : [`${description.render.shadows} shadows`]),
172
+ ...(description.contactShadows === null ? [] : ['contact shadows']),
173
+ ...description.render.effects.map((effect) => effect._tag.toLowerCase()),
174
+ ...(description.physics === null ? [] : ['physics']),
175
+ ];
176
+
177
+ /** What `description` contains, for an assistant to read. */
178
+ export const describeScene = (description: SceneDescription): SceneSummary => {
179
+ const things = thingsIn(description.children, null, description);
180
+ return {
181
+ camera: {
182
+ kind: description.camera._tag,
183
+ position: description.camera.position,
184
+ lookAt: description.camera.lookAt,
185
+ },
186
+ things,
187
+ lights: lightsIn(description.children),
188
+ bounds: thingsBounds(things),
189
+ staging: stagingOf(description),
190
+ };
191
+ };
192
+
193
+ const number = (value: number): string => String(Math.round(value * 100) / 100);
194
+ const point = (at: Vec3): string => `(${at.map(number).join(', ')})`;
195
+ const hex = (colour: Rgb): string => `#${colour.toString(16).padStart(6, '0')}`;
196
+
197
+ const thingLine = (thing: ThingSummary): string =>
198
+ [
199
+ `- ${thing.key}: ${thing.kind}`,
200
+ thing.size === null ? '' : `, ${thing.size.map(number).join(' × ')}`,
201
+ thing.colour === null ? '' : `, ${hex(thing.colour)}`,
202
+ `, at ${point(thing.position)}`,
203
+ thing.parent === null ? '' : ` in ${thing.parent}`,
204
+ ].join('');
205
+
206
+ const lightLine = (light: LightSummary): string =>
207
+ `- ${light.key}: ${light.kind}, ${hex(light.colour)} × ${number(light.intensity)}${
208
+ light.position === null ? '' : ` at ${point(light.position)}`
209
+ }`;
210
+
211
+ /** The summary as a few lines of text, for a prompt or a tool's answer. */
212
+ export const formatSummary = (summary: SceneSummary): string =>
213
+ [
214
+ `Camera: ${summary.camera.kind.toLowerCase()} at ${point(summary.camera.position)}, looking at ${point(summary.camera.lookAt)}`,
215
+ `Things (${summary.things.length})${
216
+ summary.bounds === null
217
+ ? ''
218
+ : `, within ${point(summary.bounds.min)} to ${point(summary.bounds.max)}`
219
+ }:`,
220
+ ...summary.things.map(thingLine),
221
+ `Lights (${summary.lights.length}):`,
222
+ ...summary.lights.map(lightLine),
223
+ `Staging: ${summary.staging.length === 0 ? 'none' : summary.staging.join(', ')}`,
224
+ ].join('\n');
@@ -208,6 +208,11 @@ export type BasicMaterial = {
208
208
  readonly transparent: boolean;
209
209
  readonly side: MaterialSide;
210
210
  readonly wireframe: boolean;
211
+ /**
212
+ * Whether it is hidden behind what is nearer, as usual; `false` draws it over
213
+ * everything (and writes no depth), as three's gizmos and helpers are drawn.
214
+ */
215
+ readonly depthTest: boolean;
211
216
  };
212
217
 
213
218
  /** A matte (Lambertian) material: cheap per-vertex lighting, no highlights. */
@@ -390,6 +395,8 @@ export type GroupNode = {
390
395
  * distance (Drei's `Detailed`, three's `LOD`), or `null` to show them all.
391
396
  */
392
397
  readonly detail: Detail | null;
398
+ /** A ribbon behind the group as it moves (Drei's `Trail`), or `null` for none. */
399
+ readonly trail: Trail | null;
393
400
  };
394
401
 
395
402
  /**
@@ -470,6 +477,47 @@ export type MeshNode = {
470
477
  readonly receiveShadow: boolean;
471
478
  /** A rigid body simulating the mesh, when the scene has `physics`; `null` for none. */
472
479
  readonly body: Body | null;
480
+ /** Lines along the mesh's hard edges (Drei's `Edges`), or `null` for none. */
481
+ readonly edges: Edges | null;
482
+ /** A ribbon behind the mesh as it moves (Drei's `Trail`), or `null` for none. */
483
+ readonly trail: Trail | null;
484
+ };
485
+
486
+ /**
487
+ * A trail behind a moving node, as Drei's `Trail`: `length × 10` points of where it has
488
+ * been, a new one every `interval` steps (60 a second of scene time) when it has moved at
489
+ * least `stride`, `decay` of them at a time; drawn as a ribbon facing the camera, a tenth
490
+ * of `width` wide in scene units, narrowing (`taper`) and fading (`fade`) to the tail.
491
+ * `local` follows the node's own position rather than its world position, as Drei's
492
+ * does; the ribbon is drawn in the scene's space either way.
493
+ */
494
+ export type Trail = {
495
+ readonly width: number;
496
+ readonly length: number;
497
+ readonly decay: number;
498
+ readonly local: boolean;
499
+ readonly stride: number;
500
+ readonly interval: number;
501
+ readonly color: Rgb;
502
+ readonly taper: 'none' | 'linear' | 'quadratic';
503
+ readonly fade: boolean;
504
+ };
505
+
506
+ /**
507
+ * Lines along a mesh's edges where its faces meet at more than `threshold` degrees
508
+ * (three's `EdgesGeometry`, as Drei's `Edges`), drawn as a line (`width` pixels, or scene
509
+ * units with `worldUnits`) by the render kit. Never picked: hits go to the mesh.
510
+ */
511
+ export type Edges = {
512
+ readonly threshold: number;
513
+ readonly color: Rgb;
514
+ readonly width: number;
515
+ readonly worldUnits: boolean;
516
+ readonly dashed: boolean;
517
+ readonly dashSize: number;
518
+ readonly gapSize: number;
519
+ readonly opacity: number;
520
+ readonly transparent: boolean;
473
521
  };
474
522
 
475
523
  /**
@@ -577,12 +625,56 @@ export type Physics = {
577
625
  readonly paused: boolean;
578
626
  readonly timeStep: number;
579
627
  readonly interpolate: boolean;
628
+ /**
629
+ * The step the Model lets the world reach, for a simulation that must run alike
630
+ * wherever it runs (lockstep, replays; ADR-014). The world advances toward it in fixed
631
+ * steps at scene time's pace (faster when far behind), stops there and reports
632
+ * `PhysicsReached`; whatever the description changes while it waits applies at that
633
+ * step, on every peer. Steps count from 0 when the world is made, and the world never
634
+ * goes back: a lower `until` holds it where it is. `null` (the default): the world runs
635
+ * on scene time alone, as react-three-rapier's does.
636
+ */
637
+ readonly until: number | null;
638
+ /**
639
+ * Which run of the simulation this is: a new number throws the world away and starts
640
+ * another at step 0, for a peer that takes a session's record and replays it, or a
641
+ * replay starting over. `0` by default.
642
+ */
643
+ readonly run: number;
644
+ /**
645
+ * A step the world also stops at on its way to `until`, to report there with a
646
+ * snapshot of itself (`PhysicsReached.snapshot`) for another world to start `from`. It
647
+ * does not wait there. `null` (the default): no snapshot. An engine that cannot take
648
+ * one reports there without.
649
+ */
650
+ readonly snapshotAt: number | null;
651
+ /**
652
+ * Where a run starts: a snapshot and the step it was taken at. The world is made from
653
+ * it, holding the bodies and joints it held, and counts on from that step, going
654
+ * straight to `until`; bodies the description has and it lacks are added where
655
+ * described. Read only when a run starts; `null` (the default): from the description,
656
+ * at step 0.
657
+ */
658
+ readonly from: PhysicsStart | null;
659
+ /**
660
+ * How many of its latest stops (each step it reported at) the world keeps a snapshot
661
+ * of, itself, for a run to start `from` without one in the description: to roll back
662
+ * a few steps and go again with an input that came late. `0` (the default): none.
663
+ */
664
+ readonly keep: number;
580
665
  /** Draw every collider and joint as lines, as react-three-rapier's `<Physics debug>`. */
581
666
  readonly debug: boolean;
582
667
  /** Joints between bodies, each active while both its bodies exist. */
583
668
  readonly joints: ReadonlyArray<Joint>;
584
669
  };
585
670
 
671
+ /**
672
+ * Where a run starts: the step, and a world's snapshot there (base64, as
673
+ * `PhysicsReached` carries it), or `null` for the one the world kept at that step itself
674
+ * (`keep`). A step it did not keep starts from the description, at step 0.
675
+ */
676
+ export type PhysicsStart = { readonly step: number; readonly snapshot: string | null };
677
+
586
678
  /** A body a joint holds: a node's, or one instance's of an instances node. */
587
679
  export type BodyRef = { readonly key: string; readonly instance: string | null };
588
680
 
@@ -928,8 +1020,106 @@ export type ShaderGridNode = {
928
1020
  readonly side: MaterialSide;
929
1021
  };
930
1022
 
1023
+ /**
1024
+ * A line through points, as Drei's `Line` (three's fat lines, `Line2`): `width` pixels
1025
+ * wide on screen, or scene units with `worldUnits`; one colour, or one per point; solid
1026
+ * or dashed. With `segments` each pair of points is its own segment (Drei's `Segments`).
1027
+ * A render kit draws it; without one it is an empty group.
1028
+ */
1029
+ export type LineNode = {
1030
+ readonly _tag: 'Line';
1031
+ readonly key: string;
1032
+ readonly position: Vec3;
1033
+ readonly rotation: Rotation;
1034
+ readonly scale: Vec3;
1035
+ readonly transition: Transition;
1036
+ readonly behaviour: Behave | null;
1037
+ /** Makes the line pickable, within its width. */
1038
+ readonly interactive: boolean;
1039
+ readonly points: ReadonlyArray<Vec3>;
1040
+ readonly segments: boolean;
1041
+ readonly color: Rgb;
1042
+ /** A colour for each point, blended along the line, or `null` for `color` throughout. */
1043
+ readonly vertexColors: ReadonlyArray<Rgb> | null;
1044
+ readonly width: number;
1045
+ readonly worldUnits: boolean;
1046
+ readonly dashed: boolean;
1047
+ readonly dashSize: number;
1048
+ readonly gapSize: number;
1049
+ readonly dashScale: number;
1050
+ readonly dashOffset: number;
1051
+ readonly opacity: number;
1052
+ readonly transparent: boolean;
1053
+ };
1054
+
1055
+ /**
1056
+ * Points, drawn by the render kit as round or square dots, Drei's sparkles or its
1057
+ * stars. The look says which, and carries everything needed to draw them: given
1058
+ * points, or a seed that scatters them the same way every time.
1059
+ */
1060
+ export type PointsNode = {
1061
+ readonly _tag: 'Points';
1062
+ readonly key: string;
1063
+ readonly position: Vec3;
1064
+ readonly rotation: Rotation;
1065
+ readonly scale: Vec3;
1066
+ readonly transition: Transition;
1067
+ readonly behaviour: Behave | null;
1068
+ readonly look: PointLook;
1069
+ };
1070
+
1071
+ /**
1072
+ * What points look like:
1073
+ *
1074
+ * - `Dots`: Drei's `Points` with its `PointMaterial` (three's `PointsMaterial`, `round`
1075
+ * to circles): `size` pixels across, smaller with distance under `sizeAttenuation`;
1076
+ * one colour or one a point, one size or a factor a point.
1077
+ * - `Sparkles`: Drei's `Sparkles`: `count` glowing motes scattered through a box of
1078
+ * `scale`, drifting by `noise` at `speed`.
1079
+ * - `Stars`: Drei's `Stars`: `count` stars in a shell from `radius` to `radius + depth`,
1080
+ * `factor` sizing them, twinkling at `speed`, `fade` softening their edges.
1081
+ */
1082
+ export type PointLook =
1083
+ | {
1084
+ readonly _tag: 'Dots';
1085
+ readonly positions: ReadonlyArray<Vec3>;
1086
+ readonly colors: ReadonlyArray<Rgb> | null;
1087
+ readonly sizes: ReadonlyArray<number> | null;
1088
+ readonly color: Rgb;
1089
+ readonly size: number;
1090
+ readonly sizeAttenuation: boolean;
1091
+ readonly round: boolean;
1092
+ readonly opacity: number;
1093
+ readonly transparent: boolean;
1094
+ }
1095
+ | {
1096
+ readonly _tag: 'Sparkles';
1097
+ readonly count: number;
1098
+ readonly speed: number;
1099
+ readonly opacity: number;
1100
+ readonly scale: Vec3;
1101
+ /** Each mote's size, or `null` for Drei's: random, up to 1. */
1102
+ readonly size: number | null;
1103
+ readonly color: Rgb;
1104
+ readonly noise: Vec3;
1105
+ readonly seed: number;
1106
+ }
1107
+ | {
1108
+ readonly _tag: 'Stars';
1109
+ readonly radius: number;
1110
+ readonly depth: number;
1111
+ readonly count: number;
1112
+ readonly factor: number;
1113
+ readonly saturation: number;
1114
+ readonly fade: boolean;
1115
+ readonly speed: number;
1116
+ readonly seed: number;
1117
+ };
1118
+
931
1119
  /** Any node in a scene. Keys are unique across the whole scene. */
932
1120
  export type SceneNode =
1121
+ | PointsNode
1122
+ | LineNode
933
1123
  | GridNode
934
1124
  | ShaderGridNode
935
1125
  | GroupNode
@@ -1080,6 +1270,33 @@ export type ViewRect = {
1080
1270
  */
1081
1271
  export type View = { readonly key: string; readonly camera: Camera; readonly rect: ViewRect };
1082
1272
 
1273
+ /** Where a HUD's origin sits on the canvas: a corner, an edge's middle, or the centre. */
1274
+ export type HudAlignment =
1275
+ | 'top-left'
1276
+ | 'top-center'
1277
+ | 'top-right'
1278
+ | 'center-left'
1279
+ | 'center-center'
1280
+ | 'center-right'
1281
+ | 'bottom-left'
1282
+ | 'bottom-center'
1283
+ | 'bottom-right';
1284
+
1285
+ /**
1286
+ * Nodes drawn over the scene, Drei's `Hud`, as data: a scene of their own (keys apart
1287
+ * from the scene's), drawn after it with only its depth cleared, through an orthographic
1288
+ * camera measured in CSS pixels, y up, whose origin sits `margin` pixels in from the
1289
+ * `alignment`'s edges (Drei's `GizmoHelper` places its gizmo so). Pointer events reach
1290
+ * its interactive meshes before anything behind them; their points and rays are in the
1291
+ * HUD's own space. Scene lighting and fog do not reach it.
1292
+ */
1293
+ export type Hud = {
1294
+ readonly alignment: HudAlignment;
1295
+ /** CSS pixels in from the aligned edges, `[x, y]`: unused for a centred direction. */
1296
+ readonly margin: readonly [number, number];
1297
+ readonly children: ReadonlyArray<SceneNode>;
1298
+ };
1299
+
1083
1300
  /**
1084
1301
  * A small scene rendered into a texture that materials use as `map: id`: Drei's
1085
1302
  * `RenderTexture`, as data. Its nodes have their own key namespace and may use the
@@ -1253,6 +1470,22 @@ export type XrGlide = {
1253
1470
  readonly snapDegrees: number;
1254
1471
  };
1255
1472
 
1473
+ /** What an asset is: a glTF or GLB model, a colour texture, an HDR panorama, a typeface font. */
1474
+ export type AssetKind = 'model' | 'texture' | 'environment' | 'font';
1475
+
1476
+ /**
1477
+ * An asset a scene asks for: its kind, the id it is lent under once loaded, and where it
1478
+ * comes from. `levels` are coarser versions, coarsest first, to show while `url` (the
1479
+ * finest) is on its way: each is lent as it arrives, if finer than what is shown, and
1480
+ * reported as a level of detail (ADR-015).
1481
+ */
1482
+ export type Asset = {
1483
+ readonly kind: AssetKind;
1484
+ readonly id: string;
1485
+ readonly url: string;
1486
+ readonly levels: ReadonlyArray<string>;
1487
+ };
1488
+
1256
1489
  export type SceneDescription = {
1257
1490
  readonly camera: Camera;
1258
1491
  /** Whether the scene claims the pointer's gestures from the page. */
@@ -1264,6 +1497,8 @@ export type SceneDescription = {
1264
1497
  readonly views: ReadonlyArray<View>;
1265
1498
  /** Scenes rendered into textures for materials to use. */
1266
1499
  readonly renderTextures: ReadonlyArray<RenderTexture>;
1500
+ /** Nodes drawn over the scene, or `null` for none. */
1501
+ readonly hud: Hud | null;
1267
1502
  /** Points to show HTML at, placed every frame the scene draws. */
1268
1503
  readonly overlays: ReadonlyArray<Overlay>;
1269
1504
  /** Image-based lighting and background, or `null` for none. */
@@ -1281,6 +1516,14 @@ export type SceneDescription = {
1281
1516
  * changes: what Drei's `Bounds` measures, for `fitCamera`. Loaded models included.
1282
1517
  */
1283
1518
  readonly measure: ReadonlyArray<string>;
1519
+ /**
1520
+ * Assets the scene asks for, each lent under its id once loaded: a `borrowed({ id })`
1521
+ * node shows a model, a material's `map: id` a texture, `environment({ map: id })` a
1522
+ * panorama and `text({ font: id })` a font. The host loads each once however many
1523
+ * scenes ask, cancels a load no scene asks for any more, and frees an asset when the
1524
+ * last scene stops asking (ADR-015); it reports each as loaded or failed.
1525
+ */
1526
+ readonly assets: ReadonlyArray<Asset>;
1284
1527
  readonly render: RenderSettings;
1285
1528
  readonly resources: ResourceDefinitions;
1286
1529
  readonly children: ReadonlyArray<SceneNode>;
@@ -31,6 +31,10 @@ import {
31
31
  labelMaterialEquals,
32
32
  labelTypeEquals,
33
33
  xrSpaceEquals,
34
+ lineLookEquals,
35
+ edgesEquals,
36
+ trailEquals,
37
+ pointLookEquals,
34
38
  detailEquals,
35
39
  shaderGridLookEquals,
36
40
  instancesEqual,
@@ -82,6 +86,14 @@ export type Field =
82
86
  | 'text'
83
87
  /** A shader grid's look: anything but where it is. */
84
88
  | 'grid'
89
+ /** A line's points or look: anything but where it is. */
90
+ | 'line'
91
+ /** A mesh's edge lines. */
92
+ | 'edges'
93
+ /** A group's or mesh's trail. */
94
+ | 'trail'
95
+ /** Points' look: what they are and where, within the node. */
96
+ | 'look'
85
97
  /** The tracked space a group follows. */
86
98
  | 'follow'
87
99
  /** A group's levels of detail. */
@@ -94,6 +106,9 @@ export type Op =
94
106
  /** A render texture was created, resized or its scene changed (its own operations). */
95
107
  | { readonly _tag: 'UpdateRenderTexture'; readonly id: string; readonly ops: ReadonlyArray<Op> }
96
108
  | { readonly _tag: 'RemoveRenderTexture'; readonly id: string }
109
+ /** The HUD was created or its scene changed (its own operations). */
110
+ | { readonly _tag: 'UpdateHud'; readonly ops: ReadonlyArray<Op> }
111
+ | { readonly _tag: 'RemoveHud' }
97
112
  | { readonly _tag: 'SetRenderSettings'; readonly settings: RenderSettings }
98
113
  | { readonly _tag: 'DefineResources'; readonly resources: ResourceDefinitions }
99
114
  | { readonly _tag: 'Create'; readonly node: SceneNode; readonly parent: ParentKey }
@@ -135,6 +150,7 @@ export const changedFields = (
135
150
  if (!bodyEquals(prev.body, next.body)) fields.push('body');
136
151
  if (!xrSpaceEquals(prev.follow, next.follow)) fields.push('follow');
137
152
  if (!detailEquals(prev.detail, next.detail)) fields.push('detail');
153
+ if (!trailEquals(prev.trail, next.trail)) fields.push('trail');
138
154
  return fields;
139
155
  }
140
156
  case 'Mesh': {
@@ -150,6 +166,8 @@ export const changedFields = (
150
166
  if (prev.castShadow !== next.castShadow) fields.push('castShadow');
151
167
  if (prev.receiveShadow !== next.receiveShadow) fields.push('receiveShadow');
152
168
  if (!bodyEquals(prev.body, next.body)) fields.push('body');
169
+ if (!edgesEquals(prev.edges, next.edges)) fields.push('edges');
170
+ if (!trailEquals(prev.trail, next.trail)) fields.push('trail');
153
171
  return fields;
154
172
  }
155
173
  case 'Instances': {
@@ -239,6 +257,27 @@ export const changedFields = (
239
257
  if (!Object.is(prev.shadowBias, next.shadowBias)) fields.push('shadowBias');
240
258
  return fields;
241
259
  }
260
+ case 'Points': {
261
+ if (prev._tag !== 'Points') return undefined;
262
+ if (!behaviourEquals(prev.behaviour, next.behaviour)) fields.push('behaviour');
263
+ if (!vec3Equals(prev.position, next.position)) fields.push('position');
264
+ if (!rotationEquals(prev.rotation, next.rotation)) fields.push('rotation');
265
+ if (!vec3Equals(prev.scale, next.scale)) fields.push('scale');
266
+ if (!transitionEquals(prev.transition, next.transition)) fields.push('transition');
267
+ if (!pointLookEquals(prev.look, next.look)) fields.push('look');
268
+ return fields;
269
+ }
270
+ case 'Line': {
271
+ if (prev._tag !== 'Line') return undefined;
272
+ if (!behaviourEquals(prev.behaviour, next.behaviour)) fields.push('behaviour');
273
+ if (!vec3Equals(prev.position, next.position)) fields.push('position');
274
+ if (!rotationEquals(prev.rotation, next.rotation)) fields.push('rotation');
275
+ if (!vec3Equals(prev.scale, next.scale)) fields.push('scale');
276
+ if (!transitionEquals(prev.transition, next.transition)) fields.push('transition');
277
+ if (prev.interactive !== next.interactive) fields.push('interactive');
278
+ if (!lineLookEquals(prev, next)) fields.push('line');
279
+ return fields;
280
+ }
242
281
  case 'ShaderGrid': {
243
282
  if (prev._tag !== 'ShaderGrid') return undefined;
244
283
  if (!vec3Equals(prev.position, next.position)) fields.push('position');