@opsydyn/effect-three 0.0.0-stage → 0.2.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.
Files changed (166) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/LICENSE +21 -0
  3. package/README.md +189 -2
  4. package/dist/index-01kwfekm.js +5118 -0
  5. package/dist/index-01kwfekm.js.map +40 -0
  6. package/dist/index-4045wme0.js +129 -0
  7. package/dist/index-4045wme0.js.map +10 -0
  8. package/dist/index.d.ts +12 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +1361 -0
  11. package/dist/index.js.map +14 -0
  12. package/dist/kernel/animation.d.ts +14 -0
  13. package/dist/kernel/animation.d.ts.map +1 -0
  14. package/dist/kernel/behaviours.d.ts +16 -0
  15. package/dist/kernel/behaviours.d.ts.map +1 -0
  16. package/dist/kernel/bodies.d.ts +60 -0
  17. package/dist/kernel/bodies.d.ts.map +1 -0
  18. package/dist/kernel/bodySources.d.ts +47 -0
  19. package/dist/kernel/bodySources.d.ts.map +1 -0
  20. package/dist/kernel/build.d.ts +564 -0
  21. package/dist/kernel/build.d.ts.map +1 -0
  22. package/dist/kernel/cameraMotion.d.ts +39 -0
  23. package/dist/kernel/cameraMotion.d.ts.map +1 -0
  24. package/dist/kernel/clock.d.ts +17 -0
  25. package/dist/kernel/clock.d.ts.map +1 -0
  26. package/dist/kernel/contactShadowPasses.d.ts +47 -0
  27. package/dist/kernel/contactShadowPasses.d.ts.map +1 -0
  28. package/dist/kernel/description.d.ts +1120 -0
  29. package/dist/kernel/description.d.ts.map +1 -0
  30. package/dist/kernel/diff.d.ts +90 -0
  31. package/dist/kernel/diff.d.ts.map +1 -0
  32. package/dist/kernel/environmentPresets.d.ts +30 -0
  33. package/dist/kernel/environmentPresets.d.ts.map +1 -0
  34. package/dist/kernel/equality.d.ts +64 -0
  35. package/dist/kernel/equality.d.ts.map +1 -0
  36. package/dist/kernel/frames.d.ts +55 -0
  37. package/dist/kernel/frames.d.ts.map +1 -0
  38. package/dist/kernel/index.d.ts +37 -0
  39. package/dist/kernel/index.d.ts.map +1 -0
  40. package/dist/kernel/index.js +335 -0
  41. package/dist/kernel/index.js.map +9 -0
  42. package/dist/kernel/instances.d.ts +26 -0
  43. package/dist/kernel/instances.d.ts.map +1 -0
  44. package/dist/kernel/labels.d.ts +51 -0
  45. package/dist/kernel/labels.d.ts.map +1 -0
  46. package/dist/kernel/loop.d.ts +49 -0
  47. package/dist/kernel/loop.d.ts.map +1 -0
  48. package/dist/kernel/materials.d.ts +24 -0
  49. package/dist/kernel/materials.d.ts.map +1 -0
  50. package/dist/kernel/motion.d.ts +36 -0
  51. package/dist/kernel/motion.d.ts.map +1 -0
  52. package/dist/kernel/orbit.d.ts +127 -0
  53. package/dist/kernel/orbit.d.ts.map +1 -0
  54. package/dist/kernel/overlays.d.ts +16 -0
  55. package/dist/kernel/overlays.d.ts.map +1 -0
  56. package/dist/kernel/performance.d.ts +41 -0
  57. package/dist/kernel/performance.d.ts.map +1 -0
  58. package/dist/kernel/physics.d.ts +166 -0
  59. package/dist/kernel/physics.d.ts.map +1 -0
  60. package/dist/kernel/physicsDebug.d.ts +18 -0
  61. package/dist/kernel/physicsDebug.d.ts.map +1 -0
  62. package/dist/kernel/pointer.d.ts +357 -0
  63. package/dist/kernel/pointer.d.ts.map +1 -0
  64. package/dist/kernel/renderKit.d.ts +49 -0
  65. package/dist/kernel/renderKit.d.ts.map +1 -0
  66. package/dist/kernel/renderer.d.ts +83 -0
  67. package/dist/kernel/renderer.d.ts.map +1 -0
  68. package/dist/kernel/resources.d.ts +112 -0
  69. package/dist/kernel/resources.d.ts.map +1 -0
  70. package/dist/kernel/sceneGraph.d.ts +155 -0
  71. package/dist/kernel/sceneGraph.d.ts.map +1 -0
  72. package/dist/kernel/sceneIndex.d.ts +90 -0
  73. package/dist/kernel/sceneIndex.d.ts.map +1 -0
  74. package/dist/kernel/stage.d.ts +87 -0
  75. package/dist/kernel/stage.d.ts.map +1 -0
  76. package/dist/kernel/textLayout.d.ts +29 -0
  77. package/dist/kernel/textLayout.d.ts.map +1 -0
  78. package/dist/kernel/tween.d.ts +35 -0
  79. package/dist/kernel/tween.d.ts.map +1 -0
  80. package/dist/kernel/xrInput.d.ts +107 -0
  81. package/dist/kernel/xrInput.d.ts.map +1 -0
  82. package/dist/kernel/xrLocomotion.d.ts +38 -0
  83. package/dist/kernel/xrLocomotion.d.ts.map +1 -0
  84. package/dist/runtime/FrameSource.d.ts +29 -0
  85. package/dist/runtime/FrameSource.d.ts.map +1 -0
  86. package/dist/runtime/ResourceRegistry.d.ts +18 -0
  87. package/dist/runtime/ResourceRegistry.d.ts.map +1 -0
  88. package/dist/runtime/SceneRuntime.d.ts +68 -0
  89. package/dist/runtime/SceneRuntime.d.ts.map +1 -0
  90. package/dist/runtime/SceneSchema.d.ts +2536 -0
  91. package/dist/runtime/SceneSchema.d.ts.map +1 -0
  92. package/dist/runtime/errors.d.ts +19 -0
  93. package/dist/runtime/errors.d.ts.map +1 -0
  94. package/dist/runtime/index.d.ts +11 -0
  95. package/dist/runtime/index.d.ts.map +1 -0
  96. package/dist/webgl/contactShadows.d.ts +5 -0
  97. package/dist/webgl/contactShadows.d.ts.map +1 -0
  98. package/dist/webgl/index.d.ts +11 -0
  99. package/dist/webgl/index.d.ts.map +1 -0
  100. package/dist/webgl/index.js +239 -0
  101. package/dist/webgl/index.js.map +13 -0
  102. package/dist/webgl/shaderGrid.d.ts +27 -0
  103. package/dist/webgl/shaderGrid.d.ts.map +1 -0
  104. package/dist/webgl/sky.d.ts +4 -0
  105. package/dist/webgl/sky.d.ts.map +1 -0
  106. package/dist/webgpu/contactShadows.d.ts +5 -0
  107. package/dist/webgpu/contactShadows.d.ts.map +1 -0
  108. package/dist/webgpu/index.d.ts +10 -0
  109. package/dist/webgpu/index.d.ts.map +1 -0
  110. package/dist/webgpu/index.js +216 -0
  111. package/dist/webgpu/index.js.map +13 -0
  112. package/dist/webgpu/shaderGrid.d.ts +5 -0
  113. package/dist/webgpu/shaderGrid.d.ts.map +1 -0
  114. package/dist/webgpu/sky.d.ts +4 -0
  115. package/dist/webgpu/sky.d.ts.map +1 -0
  116. package/package.json +82 -4
  117. package/src/index.ts +11 -0
  118. package/src/kernel/animation.ts +24 -0
  119. package/src/kernel/behaviours.ts +36 -0
  120. package/src/kernel/bodies.ts +338 -0
  121. package/src/kernel/bodySources.ts +203 -0
  122. package/src/kernel/build.ts +1325 -0
  123. package/src/kernel/cameraMotion.ts +202 -0
  124. package/src/kernel/clock.ts +35 -0
  125. package/src/kernel/contactShadowPasses.ts +207 -0
  126. package/src/kernel/description.ts +1197 -0
  127. package/src/kernel/diff.ts +383 -0
  128. package/src/kernel/environmentPresets.ts +38 -0
  129. package/src/kernel/equality.ts +604 -0
  130. package/src/kernel/frames.ts +124 -0
  131. package/src/kernel/index.ts +274 -0
  132. package/src/kernel/instances.ts +97 -0
  133. package/src/kernel/labels.ts +54 -0
  134. package/src/kernel/loop.ts +248 -0
  135. package/src/kernel/materials.ts +258 -0
  136. package/src/kernel/motion.ts +75 -0
  137. package/src/kernel/orbit.ts +554 -0
  138. package/src/kernel/overlays.ts +130 -0
  139. package/src/kernel/performance.ts +101 -0
  140. package/src/kernel/physics.ts +361 -0
  141. package/src/kernel/physicsDebug.ts +68 -0
  142. package/src/kernel/pointer.ts +785 -0
  143. package/src/kernel/renderKit.ts +54 -0
  144. package/src/kernel/renderer.ts +136 -0
  145. package/src/kernel/resources.ts +504 -0
  146. package/src/kernel/sceneGraph.ts +1442 -0
  147. package/src/kernel/sceneIndex.ts +219 -0
  148. package/src/kernel/stage.ts +157 -0
  149. package/src/kernel/textLayout.ts +102 -0
  150. package/src/kernel/tween.ts +80 -0
  151. package/src/kernel/xrInput.ts +230 -0
  152. package/src/kernel/xrLocomotion.ts +72 -0
  153. package/src/runtime/FrameSource.ts +41 -0
  154. package/src/runtime/ResourceRegistry.ts +24 -0
  155. package/src/runtime/SceneRuntime.ts +229 -0
  156. package/src/runtime/SceneSchema.ts +983 -0
  157. package/src/runtime/errors.ts +18 -0
  158. package/src/runtime/index.ts +10 -0
  159. package/src/webgl/contactShadows.ts +67 -0
  160. package/src/webgl/index.ts +19 -0
  161. package/src/webgl/shaderGrid.ts +196 -0
  162. package/src/webgl/sky.ts +42 -0
  163. package/src/webgpu/contactShadows.ts +83 -0
  164. package/src/webgpu/index.ts +19 -0
  165. package/src/webgpu/shaderGrid.ts +158 -0
  166. package/src/webgpu/sky.ts +35 -0
@@ -0,0 +1,604 @@
1
+ /**
2
+ * Structural equality for description values. Reference equality is checked first, so
3
+ * unchanged subtrees reused between renders cost almost nothing.
4
+ *
5
+ * @module
6
+ */
7
+ import type {
8
+ Animation,
9
+ Behave,
10
+ Body,
11
+ BodyRef,
12
+ GeometryRef,
13
+ Instance,
14
+ Joint,
15
+ JointKind,
16
+ JointLimits,
17
+ JointMotor,
18
+ InteractionGroups,
19
+ XrAnchor,
20
+ XrSpace,
21
+ ColliderDescription,
22
+ ColliderShapeDescription,
23
+ LabelColorRange,
24
+ LabelNode,
25
+ ShaderGridNode,
26
+ MaterialRef,
27
+ Overlay,
28
+ Physics,
29
+ Camera,
30
+ ContactShadows,
31
+ Environment,
32
+ PostEffect,
33
+ RenderSettings,
34
+ ResourceDefinitions,
35
+ Rotation,
36
+ ShapeGeometry,
37
+ Sky,
38
+ Xr,
39
+ TextBevel,
40
+ Transition,
41
+ Vec3,
42
+ View,
43
+ } from './description.ts';
44
+
45
+ /** Component-wise equality using `Object.is`, so `NaN` equals `NaN` and `0` differs from `-0`. */
46
+ export const vec3Equals = (a: Vec3, b: Vec3): boolean =>
47
+ a === b || (Object.is(a[0], b[0]) && Object.is(a[1], b[1]) && Object.is(a[2], b[2]));
48
+
49
+ /** Rotation equality, including behaviour parameters. */
50
+ export const rotationEquals = (a: Rotation, b: Rotation): boolean => {
51
+ if (a === b) return true;
52
+ if (a._tag === 'Fixed' && b._tag === 'Fixed') return vec3Equals(a.euler, b.euler);
53
+ if (a._tag === 'Spin' && b._tag === 'Spin') {
54
+ return (
55
+ a.axis === b.axis &&
56
+ Object.is(a.radiansPerSecond, b.radiansPerSecond) &&
57
+ Object.is(a.phase, b.phase)
58
+ );
59
+ }
60
+ return false;
61
+ };
62
+
63
+ /** Shape equality: same kind and same parameters. */
64
+ export const shapeEquals = (a: ShapeGeometry, b: ShapeGeometry): boolean => {
65
+ if (a === b) return true;
66
+ switch (a._tag) {
67
+ case 'Box':
68
+ return (
69
+ b._tag === 'Box' &&
70
+ Object.is(a.width, b.width) &&
71
+ Object.is(a.height, b.height) &&
72
+ Object.is(a.depth, b.depth)
73
+ );
74
+ case 'Sphere':
75
+ return (
76
+ b._tag === 'Sphere' &&
77
+ Object.is(a.radius, b.radius) &&
78
+ a.widthSegments === b.widthSegments &&
79
+ a.heightSegments === b.heightSegments
80
+ );
81
+ case 'Plane':
82
+ return b._tag === 'Plane' && Object.is(a.width, b.width) && Object.is(a.height, b.height);
83
+ case 'Cylinder':
84
+ return (
85
+ b._tag === 'Cylinder' &&
86
+ Object.is(a.radiusTop, b.radiusTop) &&
87
+ Object.is(a.radiusBottom, b.radiusBottom) &&
88
+ Object.is(a.height, b.height) &&
89
+ a.radialSegments === b.radialSegments
90
+ );
91
+ case 'Torus':
92
+ return (
93
+ b._tag === 'Torus' &&
94
+ Object.is(a.radius, b.radius) &&
95
+ Object.is(a.tube, b.tube) &&
96
+ a.radialSegments === b.radialSegments &&
97
+ a.tubularSegments === b.tubularSegments
98
+ );
99
+ case 'Cone':
100
+ return (
101
+ b._tag === 'Cone' &&
102
+ Object.is(a.radius, b.radius) &&
103
+ Object.is(a.height, b.height) &&
104
+ a.radialSegments === b.radialSegments
105
+ );
106
+ case 'Capsule':
107
+ return (
108
+ b._tag === 'Capsule' &&
109
+ Object.is(a.radius, b.radius) &&
110
+ Object.is(a.height, b.height) &&
111
+ a.capSegments === b.capSegments &&
112
+ a.radialSegments === b.radialSegments
113
+ );
114
+ case 'Ring':
115
+ return (
116
+ b._tag === 'Ring' &&
117
+ Object.is(a.innerRadius, b.innerRadius) &&
118
+ Object.is(a.outerRadius, b.outerRadius) &&
119
+ a.thetaSegments === b.thetaSegments
120
+ );
121
+ case 'Circle':
122
+ return b._tag === 'Circle' && Object.is(a.radius, b.radius) && a.segments === b.segments;
123
+ case 'Text':
124
+ return (
125
+ b._tag === 'Text' &&
126
+ a.text === b.text &&
127
+ a.font === b.font &&
128
+ Object.is(a.size, b.size) &&
129
+ Object.is(a.depth, b.depth) &&
130
+ a.curveSegments === b.curveSegments &&
131
+ a.center === b.center &&
132
+ Object.is(a.letterSpacing, b.letterSpacing) &&
133
+ Object.is(a.lineHeight, b.lineHeight) &&
134
+ a.align === b.align &&
135
+ bevelEquals(a.bevel, b.bevel)
136
+ );
137
+ }
138
+ };
139
+
140
+ const bevelEquals = (a: TextBevel | null, b: TextBevel | null): boolean =>
141
+ a === b ||
142
+ (a !== null &&
143
+ b !== null &&
144
+ Object.is(a.thickness, b.thickness) &&
145
+ Object.is(a.size, b.size) &&
146
+ Object.is(a.offset, b.offset) &&
147
+ a.segments === b.segments);
148
+
149
+ /** Geometry reference equality: same ownership mode and same shape or id. */
150
+ export const geometryRefEquals = (a: GeometryRef, b: GeometryRef): boolean => {
151
+ if (a === b) return true;
152
+ if (a._tag === 'SharedGeometry' || b._tag === 'SharedGeometry') {
153
+ return a._tag === 'SharedGeometry' && b._tag === 'SharedGeometry' && a.id === b.id;
154
+ }
155
+ if (a._tag === 'LentGeometry' || b._tag === 'LentGeometry') {
156
+ return a._tag === 'LentGeometry' && b._tag === 'LentGeometry' && a.id === b.id;
157
+ }
158
+ return shapeEquals(a, b);
159
+ };
160
+
161
+ /** Instance equality: same key, placement and colour. */
162
+ export const instanceEquals = (a: Instance, b: Instance): boolean =>
163
+ a === b ||
164
+ (a.key === b.key &&
165
+ Object.is(a.color, b.color) &&
166
+ vec3Equals(a.position, b.position) &&
167
+ vec3Equals(a.rotation, b.rotation) &&
168
+ vec3Equals(a.scale, b.scale));
169
+
170
+ /** Instance list equality, slot by slot. */
171
+ export const instancesEqual = (a: ReadonlyArray<Instance>, b: ReadonlyArray<Instance>): boolean => {
172
+ if (a === b) return true;
173
+ if (a.length !== b.length) return false;
174
+ for (let i = 0; i < a.length; i++) {
175
+ const x = a[i];
176
+ const y = b[i];
177
+ if (x === undefined || y === undefined || !instanceEquals(x, y)) return false;
178
+ }
179
+ return true;
180
+ };
181
+
182
+ /** Behaviour equality: both none, or the same name and parameters. */
183
+ export const behaviourEquals = (a: Behave | null, b: Behave | null): boolean =>
184
+ a === b || (a !== null && b !== null && a.name === b.name && sameFlatRecord(a.params, b.params));
185
+
186
+ /** Animation equality: both none, or the same clip, speed and looping. */
187
+ export const animationEquals = (a: Animation | null, b: Animation | null): boolean =>
188
+ a === b ||
189
+ (a !== null &&
190
+ b !== null &&
191
+ a.clip === b.clip &&
192
+ Object.is(a.speed, b.speed) &&
193
+ a.loop === b.loop);
194
+
195
+ /** Transition equality. */
196
+ export const transitionEquals = (a: Transition, b: Transition): boolean => {
197
+ if (a === b) return true;
198
+ if (a._tag === 'Instant' || b._tag === 'Instant') return a._tag === b._tag;
199
+ return Object.is(a.seconds, b.seconds) && a.easing === b.easing;
200
+ };
201
+
202
+ /**
203
+ * Equality of two flat description records: same keys, each value `Object.is`-equal.
204
+ * Material descriptions are flat (numbers, booleans and string literals), so this is
205
+ * exact for them and needs no per-kind code.
206
+ */
207
+ const sameFlatRecord = (a: object, b: object): boolean => {
208
+ const keys = Object.keys(a);
209
+ if (keys.length !== Object.keys(b).length) return false;
210
+ for (const key of keys) {
211
+ if (!Object.is(Reflect.get(a, key), Reflect.get(b, key))) return false;
212
+ }
213
+ return true;
214
+ };
215
+
216
+ /** Material reference equality: same ownership mode and same kind and parameters, or id. */
217
+ export const materialRefEquals = (a: MaterialRef, b: MaterialRef): boolean => {
218
+ if (a === b) return true;
219
+ if (a._tag === 'SharedMaterial' || b._tag === 'SharedMaterial') {
220
+ return a._tag === 'SharedMaterial' && b._tag === 'SharedMaterial' && a.id === b.id;
221
+ }
222
+ return sameFlatRecord(a, b);
223
+ };
224
+
225
+ /** Camera equality: same kind, same lens and same placement. */
226
+ export const cameraEquals = (a: Camera, b: Camera): boolean => {
227
+ if (a === b) return true;
228
+ const sameLens =
229
+ (a._tag === 'Perspective' && b._tag === 'Perspective' && Object.is(a.fov, b.fov)) ||
230
+ (a._tag === 'Orthographic' && b._tag === 'Orthographic' && Object.is(a.zoom, b.zoom));
231
+ return (
232
+ sameLens &&
233
+ Object.is(a.near, b.near) &&
234
+ Object.is(a.far, b.far) &&
235
+ Object.is(a.damping, b.damping) &&
236
+ Object.is(a.autoRotate, b.autoRotate) &&
237
+ vec3Equals(a.position, b.position) &&
238
+ vec3Equals(a.lookAt, b.lookAt)
239
+ );
240
+ };
241
+
242
+ /** Render settings equality. */
243
+ export const renderSettingsEquals = (a: RenderSettings, b: RenderSettings): boolean =>
244
+ a === b ||
245
+ (a.frames === b.frames &&
246
+ Object.is(a.resolution, b.resolution) &&
247
+ a.shadows === b.shadows &&
248
+ a.toneMapping === b.toneMapping &&
249
+ Object.is(a.exposure, b.exposure) &&
250
+ a.colorSpace === b.colorSpace &&
251
+ effectsEqual(a.effects, b.effects));
252
+
253
+ /** Post-processing equality: the same effects, in order, with the same parameters. */
254
+ export const effectsEqual = (
255
+ a: ReadonlyArray<PostEffect>,
256
+ b: ReadonlyArray<PostEffect>,
257
+ ): boolean => {
258
+ if (a === b) return true;
259
+ if (a.length !== b.length) return false;
260
+ for (let i = 0; i < a.length; i++) {
261
+ const x = a[i];
262
+ const y = b[i];
263
+ if (x === undefined || y === undefined || !sameFlatRecord(x, y)) return false;
264
+ }
265
+ return true;
266
+ };
267
+
268
+ /** View list equality: the same views, in order, with equal cameras and rects. */
269
+ export const viewsEqual = (a: ReadonlyArray<View>, b: ReadonlyArray<View>): boolean => {
270
+ if (a === b) return true;
271
+ if (a.length !== b.length) return false;
272
+ for (let i = 0; i < a.length; i++) {
273
+ const x = a[i];
274
+ const y = b[i];
275
+ if (x === undefined || y === undefined) return false;
276
+ if (x.key !== y.key || !cameraEquals(x.camera, y.camera) || !sameFlatRecord(x.rect, y.rect)) {
277
+ return false;
278
+ }
279
+ }
280
+ return true;
281
+ };
282
+
283
+ /** Order-sensitive equality of shared resource definitions. */
284
+ export const resourcesEqual = (a: ResourceDefinitions, b: ResourceDefinitions): boolean => {
285
+ if (a === b) return true;
286
+ if (a.geometries.length !== b.geometries.length) return false;
287
+ if (a.materials.length !== b.materials.length) return false;
288
+ for (let i = 0; i < a.geometries.length; i++) {
289
+ const x = a.geometries[i];
290
+ const y = b.geometries[i];
291
+ if (x === undefined || y === undefined) return false;
292
+ if (x.id !== y.id || !geometryRefEquals(x.geometry, y.geometry)) return false;
293
+ }
294
+ for (let i = 0; i < a.materials.length; i++) {
295
+ const x = a.materials[i];
296
+ const y = b.materials[i];
297
+ if (x === undefined || y === undefined) return false;
298
+ if (x.id !== y.id || !materialRefEquals(x.material, y.material)) return false;
299
+ }
300
+ return true;
301
+ };
302
+
303
+ /** Overlay equality, field by field. */
304
+ const overlayEquals = (x: Overlay, y: Overlay): boolean =>
305
+ x.key === y.key &&
306
+ x.node === y.node &&
307
+ vec3Equals(x.position, y.position) &&
308
+ x.center === y.center &&
309
+ Object.is(x.distanceFactor, y.distanceFactor) &&
310
+ x.occlude === y.occlude &&
311
+ x.zIndexRange[0] === y.zIndexRange[0] &&
312
+ x.zIndexRange[1] === y.zIndexRange[1] &&
313
+ x.view === y.view;
314
+
315
+ /** Overlay list equality: the same overlays, in order, with equal fields. */
316
+ export const overlaysEqual = (a: ReadonlyArray<Overlay>, b: ReadonlyArray<Overlay>): boolean =>
317
+ a === b ||
318
+ (a.length === b.length && a.every((x, i) => b[i] !== undefined && overlayEquals(x, b[i])));
319
+
320
+ /** Environment equality. */
321
+ export const environmentEquals = (a: Environment | null, b: Environment | null): boolean =>
322
+ a === b ||
323
+ (a !== null &&
324
+ b !== null &&
325
+ a.map === b.map &&
326
+ a.background === b.background &&
327
+ Object.is(a.backgroundBlurriness, b.backgroundBlurriness) &&
328
+ Object.is(a.backgroundIntensity, b.backgroundIntensity) &&
329
+ Object.is(a.environmentIntensity, b.environmentIntensity));
330
+
331
+ /** Sky equality. */
332
+ export const skyEquals = (a: Sky | null, b: Sky | null): boolean =>
333
+ a === b ||
334
+ (a !== null &&
335
+ b !== null &&
336
+ vec3Equals(a.sunPosition, b.sunPosition) &&
337
+ Object.is(a.turbidity, b.turbidity) &&
338
+ Object.is(a.rayleigh, b.rayleigh) &&
339
+ Object.is(a.mieCoefficient, b.mieCoefficient) &&
340
+ Object.is(a.mieDirectionalG, b.mieDirectionalG) &&
341
+ Object.is(a.clouds, b.clouds) &&
342
+ Object.is(a.distance, b.distance));
343
+
344
+ /** Contact shadow equality. */
345
+ export const contactShadowsEquals = (a: ContactShadows | null, b: ContactShadows | null): boolean =>
346
+ a === b ||
347
+ (a !== null &&
348
+ b !== null &&
349
+ vec3Equals(a.position, b.position) &&
350
+ a.size[0] === b.size[0] &&
351
+ a.size[1] === b.size[1] &&
352
+ Object.is(a.near, b.near) &&
353
+ Object.is(a.far, b.far) &&
354
+ Object.is(a.blur, b.blur) &&
355
+ a.smooth === b.smooth &&
356
+ Object.is(a.opacity, b.opacity) &&
357
+ a.color === b.color &&
358
+ a.resolution === b.resolution);
359
+
360
+ /** Body equality, field by field. */
361
+ export const bodyEquals = (a: Body | null, b: Body | null): boolean =>
362
+ a === b ||
363
+ (a !== null &&
364
+ b !== null &&
365
+ a.type === b.type &&
366
+ a.collider === b.collider &&
367
+ Object.is(a.mass, b.mass) &&
368
+ Object.is(a.friction, b.friction) &&
369
+ Object.is(a.restitution, b.restitution) &&
370
+ Object.is(a.linearDamping, b.linearDamping) &&
371
+ Object.is(a.angularDamping, b.angularDamping) &&
372
+ Object.is(a.gravityScale, b.gravityScale) &&
373
+ a.ccd === b.ccd &&
374
+ a.sensor === b.sensor &&
375
+ vec3Equals(a.linearVelocity, b.linearVelocity) &&
376
+ vec3Equals(a.angularVelocity, b.angularVelocity) &&
377
+ (a.impulse === b.impulse ||
378
+ (a.impulse !== null &&
379
+ b.impulse !== null &&
380
+ a.impulse.id === b.impulse.id &&
381
+ vec3Equals(a.impulse.impulse, b.impulse.impulse))) &&
382
+ collidersEqual(a.colliders, b.colliders) &&
383
+ Object.is(a.contactForces, b.contactForces) &&
384
+ groupsEqual(a.collisionGroups, b.collisionGroups) &&
385
+ groupsEqual(a.solverGroups, b.solverGroups));
386
+
387
+ const limitsEqual = (a: JointLimits | null, b: JointLimits | null): boolean =>
388
+ a === b || (a !== null && b !== null && Object.is(a[0], b[0]) && Object.is(a[1], b[1]));
389
+
390
+ /** Joint kind equality. */
391
+ const jointKindEquals = (a: JointKind, b: JointKind): boolean => {
392
+ switch (a._tag) {
393
+ case 'Fixed':
394
+ return b._tag === 'Fixed' && vec3Equals(a.frameA, b.frameA) && vec3Equals(a.frameB, b.frameB);
395
+ case 'Spherical':
396
+ return b._tag === 'Spherical';
397
+ case 'Revolute':
398
+ case 'Prismatic':
399
+ return (
400
+ b._tag === a._tag &&
401
+ vec3Equals(a.axis, b.axis) &&
402
+ limitsEqual(a.limits, b.limits) &&
403
+ motorEquals(a.motor, b.motor)
404
+ );
405
+ case 'Rope':
406
+ return b._tag === 'Rope' && Object.is(a.length, b.length);
407
+ case 'Spring':
408
+ return (
409
+ b._tag === 'Spring' &&
410
+ Object.is(a.restLength, b.restLength) &&
411
+ Object.is(a.stiffness, b.stiffness) &&
412
+ Object.is(a.damping, b.damping)
413
+ );
414
+ }
415
+ };
416
+
417
+ const numbersEqual = (a: ReadonlyArray<number> | null, b: ReadonlyArray<number> | null) =>
418
+ a === b ||
419
+ (a !== null && b !== null && a.length === b.length && a.every((value, i) => value === b[i]));
420
+
421
+ const groupsEqual = (a: InteractionGroups | null, b: InteractionGroups | null): boolean =>
422
+ a === b ||
423
+ (a !== null &&
424
+ b !== null &&
425
+ numbersEqual(a.memberships, b.memberships) &&
426
+ numbersEqual(a.filters, b.filters));
427
+
428
+ const motorEquals = (a: JointMotor | null, b: JointMotor | null): boolean =>
429
+ a === b ||
430
+ (a !== null &&
431
+ b !== null &&
432
+ Object.is(a.position, b.position) &&
433
+ Object.is(a.velocity, b.velocity) &&
434
+ Object.is(a.stiffness, b.stiffness) &&
435
+ Object.is(a.damping, b.damping));
436
+
437
+ const colliderShapeEquals = (a: ColliderShapeDescription, b: ColliderShapeDescription): boolean => {
438
+ switch (a._tag) {
439
+ case 'Cuboid':
440
+ return b._tag === 'Cuboid' && vec3Equals(a.halfExtents, b.halfExtents);
441
+ case 'Ball':
442
+ return b._tag === 'Ball' && Object.is(a.radius, b.radius);
443
+ case 'Capsule':
444
+ case 'Cylinder':
445
+ case 'Cone':
446
+ return (
447
+ b._tag === a._tag && Object.is(a.halfHeight, b.halfHeight) && Object.is(a.radius, b.radius)
448
+ );
449
+ }
450
+ };
451
+
452
+ /** Equality of a body's own colliders (`null` for colliders from its geometry). */
453
+ export const collidersEqual = (
454
+ a: ReadonlyArray<ColliderDescription> | null,
455
+ b: ReadonlyArray<ColliderDescription> | null,
456
+ ): boolean => {
457
+ if (a === b) return true;
458
+ if (a === null || b === null || a.length !== b.length) return false;
459
+ for (let i = 0; i < a.length; i++) {
460
+ const x = a[i];
461
+ const y = b[i];
462
+ if (
463
+ x === undefined ||
464
+ y === undefined ||
465
+ !colliderShapeEquals(x.shape, y.shape) ||
466
+ !vec3Equals(x.position, y.position) ||
467
+ !vec3Equals(x.rotation, y.rotation) ||
468
+ x.name !== y.name
469
+ ) {
470
+ return false;
471
+ }
472
+ }
473
+ return true;
474
+ };
475
+
476
+ const refEquals = (a: BodyRef, b: BodyRef): boolean => a.key === b.key && a.instance === b.instance;
477
+
478
+ /** Joint equality. */
479
+ export const jointEquals = (a: Joint, b: Joint): boolean =>
480
+ a === b ||
481
+ (a.key === b.key &&
482
+ refEquals(a.a, b.a) &&
483
+ refEquals(a.b, b.b) &&
484
+ vec3Equals(a.anchorA, b.anchorA) &&
485
+ vec3Equals(a.anchorB, b.anchorB) &&
486
+ jointKindEquals(a.kind, b.kind) &&
487
+ a.contacts === b.contacts);
488
+
489
+ const jointsEqual = (a: ReadonlyArray<Joint>, b: ReadonlyArray<Joint>): boolean => {
490
+ if (a === b) return true;
491
+ if (a.length !== b.length) return false;
492
+ for (let i = 0; i < a.length; i++) {
493
+ const x = a[i];
494
+ const y = b[i];
495
+ if (x === undefined || y === undefined || !jointEquals(x, y)) return false;
496
+ }
497
+ return true;
498
+ };
499
+
500
+ /** Label text and type settings equality: everything but the transform and picking. */
501
+ export const labelTypeEquals = (a: LabelNode, b: LabelNode): boolean =>
502
+ a.text === b.text &&
503
+ a.font === b.font &&
504
+ Object.is(a.fontSize, b.fontSize) &&
505
+ a.color === b.color &&
506
+ Object.is(a.fillOpacity, b.fillOpacity) &&
507
+ Object.is(a.anchorX, b.anchorX) &&
508
+ Object.is(a.anchorY, b.anchorY) &&
509
+ Object.is(a.maxWidth, b.maxWidth) &&
510
+ Object.is(a.lineHeight, b.lineHeight) &&
511
+ Object.is(a.letterSpacing, b.letterSpacing) &&
512
+ a.textAlign === b.textAlign &&
513
+ Object.is(a.outlineWidth, b.outlineWidth) &&
514
+ a.outlineColor === b.outlineColor &&
515
+ Object.is(a.outlineOpacity, b.outlineOpacity) &&
516
+ Object.is(a.outlineBlur, b.outlineBlur) &&
517
+ Object.is(a.curveRadius, b.curveRadius) &&
518
+ a.colorRanges.length === b.colorRanges.length &&
519
+ a.colorRanges.every((range, i) => colorRangeEquals(range, b.colorRanges[i]));
520
+
521
+ const colorRangeEquals = (a: LabelColorRange, b: LabelColorRange | undefined): boolean =>
522
+ b !== undefined && a.start === b.start && a.color === b.color;
523
+
524
+ /** A label's material reference equality (`null` for troika's own). */
525
+ export const labelMaterialEquals = (a: MaterialRef | null, b: MaterialRef | null): boolean =>
526
+ a === b || (a !== null && b !== null && materialRefEquals(a, b));
527
+
528
+ /** Shader grid equality, apart from its position and rotation. */
529
+ export const shaderGridLookEquals = (a: ShaderGridNode, b: ShaderGridNode): boolean =>
530
+ Object.is(a.size[0], b.size[0]) &&
531
+ Object.is(a.size[1], b.size[1]) &&
532
+ Object.is(a.cellSize, b.cellSize) &&
533
+ Object.is(a.cellThickness, b.cellThickness) &&
534
+ a.cellColor === b.cellColor &&
535
+ Object.is(a.sectionSize, b.sectionSize) &&
536
+ Object.is(a.sectionThickness, b.sectionThickness) &&
537
+ a.sectionColor === b.sectionColor &&
538
+ a.infinite === b.infinite &&
539
+ a.followCamera === b.followCamera &&
540
+ Object.is(a.fadeDistance, b.fadeDistance) &&
541
+ Object.is(a.fadeStrength, b.fadeStrength) &&
542
+ Object.is(a.fadeFrom, b.fadeFrom) &&
543
+ a.side === b.side;
544
+
545
+ /** Physics world equality. */
546
+ export const physicsEquals = (a: Physics | null, b: Physics | null): boolean =>
547
+ a === b ||
548
+ (a !== null &&
549
+ b !== null &&
550
+ vec3Equals(a.gravity, b.gravity) &&
551
+ a.paused === b.paused &&
552
+ Object.is(a.timeStep, b.timeStep) &&
553
+ a.interpolate === b.interpolate &&
554
+ a.debug === b.debug &&
555
+ jointsEqual(a.joints, b.joints));
556
+
557
+ /** Anchor equality (`b` may be missing). */
558
+ const xrAnchorEquals = (a: XrAnchor, b: XrAnchor | undefined): boolean =>
559
+ b !== undefined &&
560
+ a.id === b.id &&
561
+ vec3Equals(a.position, b.position) &&
562
+ vec3Equals(a.rotation, b.rotation);
563
+
564
+ /** Immersive presentation equality. */
565
+ export const xrEquals = (a: Xr | null, b: Xr | null): boolean =>
566
+ a === b ||
567
+ (a !== null &&
568
+ b !== null &&
569
+ vec3Equals(a.origin, b.origin) &&
570
+ Object.is(a.heading, b.heading) &&
571
+ a.referenceSpace === b.referenceSpace &&
572
+ Object.is(a.framebufferScale, b.framebufferScale) &&
573
+ a.hitTest === b.hitTest &&
574
+ a.showInputs === b.showInputs &&
575
+ (a.glide === b.glide ||
576
+ (a.glide !== null &&
577
+ b.glide !== null &&
578
+ Object.is(a.glide.speed, b.glide.speed) &&
579
+ a.glide.turn === b.glide.turn &&
580
+ Object.is(a.glide.turnSpeed, b.glide.turnSpeed) &&
581
+ Object.is(a.glide.snapDegrees, b.glide.snapDegrees))) &&
582
+ a.planes === b.planes &&
583
+ a.meshes === b.meshes &&
584
+ a.domOverlay === b.domOverlay &&
585
+ a.anchors.length === b.anchors.length &&
586
+ a.anchors.every((anchor, i) => xrAnchorEquals(anchor, b.anchors[i])));
587
+
588
+ /** Tracked space equality. */
589
+ export const xrSpaceEquals = (a: XrSpace | null, b: XrSpace | null): boolean => {
590
+ if (a === b) return true;
591
+ if (a === null || b === null) return false;
592
+ switch (a._tag) {
593
+ case 'Head':
594
+ return b._tag === 'Head';
595
+ case 'Joint':
596
+ return b._tag === 'Joint' && a.hand === b.hand && a.joint === b.joint;
597
+ case 'Controller':
598
+ return b._tag === 'Controller' && a.hand === b.hand && a.space === b.space;
599
+ case 'Anchor':
600
+ case 'Plane':
601
+ case 'Mesh':
602
+ return b._tag === a._tag && a.id === b.id;
603
+ }
604
+ };