@opsydyn/effect-three-foldkit 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 (96) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/LICENSE +21 -0
  3. package/README.md +99 -2
  4. package/dist/assets.d.ts +53 -0
  5. package/dist/assets.d.ts.map +1 -0
  6. package/dist/dom/animationFrames.d.ts +12 -0
  7. package/dist/dom/animationFrames.d.ts.map +1 -0
  8. package/dist/dom/canvas.d.ts +26 -0
  9. package/dist/dom/canvas.d.ts.map +1 -0
  10. package/dist/dom/latch.d.ts +15 -0
  11. package/dist/dom/latch.d.ts.map +1 -0
  12. package/dist/dom/models.d.ts +10 -0
  13. package/dist/dom/models.d.ts.map +1 -0
  14. package/dist/dom/overlays.d.ts +24 -0
  15. package/dist/dom/overlays.d.ts.map +1 -0
  16. package/dist/dom/pointer.d.ts +49 -0
  17. package/dist/dom/pointer.d.ts.map +1 -0
  18. package/dist/dom/postprocessing.d.ts +22 -0
  19. package/dist/dom/postprocessing.d.ts.map +1 -0
  20. package/dist/dom/property.d.ts +15 -0
  21. package/dist/dom/property.d.ts.map +1 -0
  22. package/dist/dom/reconciled.d.ts +10 -0
  23. package/dist/dom/reconciled.d.ts.map +1 -0
  24. package/dist/dom/renderSettings.d.ts +58 -0
  25. package/dist/dom/renderSettings.d.ts.map +1 -0
  26. package/dist/dom/typedScenes.d.ts +14 -0
  27. package/dist/dom/typedScenes.d.ts.map +1 -0
  28. package/dist/dom/updates.d.ts +8 -0
  29. package/dist/dom/updates.d.ts.map +1 -0
  30. package/dist/dom/viewport.d.ts +40 -0
  31. package/dist/dom/viewport.d.ts.map +1 -0
  32. package/dist/dom/webgl.d.ts +12 -0
  33. package/dist/dom/webgl.d.ts.map +1 -0
  34. package/dist/dom/webgpu.d.ts +24 -0
  35. package/dist/dom/webgpu.d.ts.map +1 -0
  36. package/dist/dom/webgpuPostprocessing.d.ts +4 -0
  37. package/dist/dom/webgpuPostprocessing.d.ts.map +1 -0
  38. package/dist/dom/xr.d.ts +53 -0
  39. package/dist/dom/xr.d.ts.map +1 -0
  40. package/dist/dom/xrGlide.d.ts +49 -0
  41. package/dist/dom/xrGlide.d.ts.map +1 -0
  42. package/dist/dom/xrHub.d.ts +45 -0
  43. package/dist/dom/xrHub.d.ts.map +1 -0
  44. package/dist/dom/xrInputs.d.ts +40 -0
  45. package/dist/dom/xrInputs.d.ts.map +1 -0
  46. package/dist/dom/xrSpaces.d.ts +33 -0
  47. package/dist/dom/xrSpaces.d.ts.map +1 -0
  48. package/dist/dom/xrTarget.d.ts +36 -0
  49. package/dist/dom/xrTarget.d.ts.map +1 -0
  50. package/dist/element.d.ts +771 -0
  51. package/dist/element.d.ts.map +1 -0
  52. package/dist/environment.d.ts +64 -0
  53. package/dist/environment.d.ts.map +1 -0
  54. package/dist/index.d.ts +32 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +2041 -0
  57. package/dist/index.js.map +38 -0
  58. package/dist/mount.d.ts +14 -0
  59. package/dist/mount.d.ts.map +1 -0
  60. package/dist/overlayView.d.ts +23 -0
  61. package/dist/overlayView.d.ts.map +1 -0
  62. package/dist/view.d.ts +179 -0
  63. package/dist/view.d.ts.map +1 -0
  64. package/dist/xr.d.ts +34 -0
  65. package/dist/xr.d.ts.map +1 -0
  66. package/package.json +72 -4
  67. package/src/assets.ts +151 -0
  68. package/src/dom/animationFrames.ts +32 -0
  69. package/src/dom/canvas.ts +54 -0
  70. package/src/dom/latch.ts +36 -0
  71. package/src/dom/models.ts +37 -0
  72. package/src/dom/overlays.ts +63 -0
  73. package/src/dom/pointer.ts +163 -0
  74. package/src/dom/postprocessing.ts +116 -0
  75. package/src/dom/property.ts +43 -0
  76. package/src/dom/reconciled.ts +51 -0
  77. package/src/dom/renderSettings.ts +117 -0
  78. package/src/dom/typedScenes.ts +21 -0
  79. package/src/dom/updates.ts +14 -0
  80. package/src/dom/viewport.ts +97 -0
  81. package/src/dom/webgl.ts +216 -0
  82. package/src/dom/webgpu.ts +205 -0
  83. package/src/dom/webgpuPostprocessing.ts +102 -0
  84. package/src/dom/xr.ts +211 -0
  85. package/src/dom/xrGlide.ts +114 -0
  86. package/src/dom/xrHub.ts +118 -0
  87. package/src/dom/xrInputs.ts +309 -0
  88. package/src/dom/xrSpaces.ts +287 -0
  89. package/src/dom/xrTarget.ts +71 -0
  90. package/src/element.ts +114 -0
  91. package/src/environment.ts +78 -0
  92. package/src/index.ts +71 -0
  93. package/src/mount.ts +305 -0
  94. package/src/overlayView.ts +71 -0
  95. package/src/view.ts +254 -0
  96. package/src/xr.ts +36 -0
package/dist/index.js ADDED
@@ -0,0 +1,2041 @@
1
+ // src/assets.ts
2
+ import { Data, Effect } from "effect";
3
+ import {
4
+ EquirectangularReflectionMapping,
5
+ SRGBColorSpace,
6
+ TextureLoader
7
+ } from "three";
8
+ import { FontLoader } from "three/addons/loaders/FontLoader.js";
9
+ import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
10
+ import { HDRLoader } from "three/addons/loaders/HDRLoader.js";
11
+
12
+ // src/dom/models.ts
13
+ import { Mesh, Texture } from "three";
14
+ var disposeMaterial = (material) => {
15
+ for (const value of Object.values(material)) {
16
+ if (value instanceof Texture)
17
+ value.dispose();
18
+ }
19
+ material.dispose();
20
+ };
21
+ function disposeMesh(object) {
22
+ if (!(object instanceof Mesh))
23
+ return;
24
+ object.geometry.dispose();
25
+ const materials = Array.isArray(object.material) ? object.material : [object.material];
26
+ for (const material of materials)
27
+ disposeMaterial(material);
28
+ }
29
+ var disposeModel = (root) => {
30
+ root.traverse(disposeMesh);
31
+ };
32
+ var namesIn = (root) => {
33
+ const names = [];
34
+ root.traverse((object) => {
35
+ if (object !== root && object.name !== "")
36
+ names.push(object.name);
37
+ });
38
+ return names;
39
+ };
40
+
41
+ // src/assets.ts
42
+ class AssetLoadError extends Data.TaggedError("AssetLoadError") {
43
+ }
44
+ var makeAssets = (store) => {
45
+ const releases = new Map;
46
+ const own = (id, release) => {
47
+ releases.get(id)?.();
48
+ releases.set(id, release);
49
+ };
50
+ const lendTexture = (id) => (texture) => {
51
+ texture.colorSpace = SRGBColorSpace;
52
+ const withdraw = store.lendTexture(id, texture);
53
+ own(id, () => {
54
+ withdraw();
55
+ texture.dispose();
56
+ });
57
+ return id;
58
+ };
59
+ const lendModel = (id) => (gltf) => {
60
+ gltf.scene.animations = gltf.animations;
61
+ const withdraw = store.lend(id, gltf.scene);
62
+ own(id, () => {
63
+ withdraw();
64
+ disposeModel(gltf.scene);
65
+ });
66
+ const info = {
67
+ id,
68
+ parts: namesIn(gltf.scene),
69
+ clips: gltf.animations.map((clip) => clip.name)
70
+ };
71
+ return info;
72
+ };
73
+ const lendEnvironment = (id) => (texture) => {
74
+ texture.mapping = EquirectangularReflectionMapping;
75
+ const withdraw = store.lendTexture(id, texture);
76
+ own(id, () => {
77
+ withdraw();
78
+ texture.dispose();
79
+ });
80
+ return id;
81
+ };
82
+ const lendFont = (id) => (font) => {
83
+ own(id, store.lendFont(id, font));
84
+ return id;
85
+ };
86
+ const releaseAll = () => {
87
+ for (const release of releases.values())
88
+ release();
89
+ releases.clear();
90
+ };
91
+ return {
92
+ loadTexture: (id, url) => {
93
+ const load = Effect.tryPromise({
94
+ try: () => new TextureLoader().loadAsync(url),
95
+ catch: (cause) => new AssetLoadError({ url, cause })
96
+ });
97
+ return Effect.map(load, lendTexture(id));
98
+ },
99
+ loadModel: (id, url) => {
100
+ const load = Effect.tryPromise({
101
+ try: () => new GLTFLoader().loadAsync(url),
102
+ catch: (cause) => new AssetLoadError({ url, cause })
103
+ });
104
+ return Effect.map(load, lendModel(id));
105
+ },
106
+ loadEnvironment: (id, url) => {
107
+ const load = Effect.tryPromise({
108
+ try: () => new HDRLoader().loadAsync(url),
109
+ catch: (cause) => new AssetLoadError({ url, cause })
110
+ });
111
+ return Effect.map(load, lendEnvironment(id));
112
+ },
113
+ loadFont: (id, url) => {
114
+ const load = Effect.tryPromise({
115
+ try: () => new FontLoader().loadAsync(url),
116
+ catch: (cause) => new AssetLoadError({ url, cause })
117
+ });
118
+ return Effect.map(load, lendFont(id));
119
+ },
120
+ unload: (id) => Effect.sync(() => {
121
+ releases.get(id)?.();
122
+ releases.delete(id);
123
+ }),
124
+ dispose: releaseAll
125
+ };
126
+ };
127
+ // src/dom/animationFrames.ts
128
+ var animationFrameScheduler = (host) => {
129
+ const callbacks = new Set;
130
+ let handle;
131
+ const tick = () => {
132
+ handle = host.requestAnimationFrame(tick);
133
+ for (const onFrame of Array.from(callbacks))
134
+ onFrame();
135
+ };
136
+ return {
137
+ subscribe: (onFrame) => {
138
+ callbacks.add(onFrame);
139
+ handle ??= host.requestAnimationFrame(tick);
140
+ return () => {
141
+ callbacks.delete(onFrame);
142
+ if (callbacks.size > 0 || handle === undefined)
143
+ return;
144
+ host.cancelAnimationFrame(handle);
145
+ handle = undefined;
146
+ };
147
+ }
148
+ };
149
+ };
150
+ // src/dom/pointer.ts
151
+ var toNdc = (rect, clientX, clientY) => {
152
+ if (rect.width === 0 || rect.height === 0)
153
+ return { x: 0, y: 0 };
154
+ return {
155
+ x: (clientX - rect.left) / rect.width * 2 - 1,
156
+ y: -((clientY - rect.top) / rect.height * 2 - 1)
157
+ };
158
+ };
159
+ var modifiersOf = (event) => ({
160
+ shift: event.shiftKey,
161
+ control: event.ctrlKey,
162
+ meta: event.metaKey,
163
+ alt: event.altKey
164
+ });
165
+ var pointerIdOf = (event) => event instanceof PointerEvent ? event.pointerId : 0;
166
+ var wheelScale = [1, 16, 100];
167
+ var toPointerInput = (event, rect) => {
168
+ if (event.type === "pointerleave" || event.type === "pointercancel") {
169
+ return { _tag: "PointerLeave" };
170
+ }
171
+ const { x, y } = toNdc(rect, event.clientX, event.clientY);
172
+ const pointer = pointerIdOf(event);
173
+ switch (event.type) {
174
+ case "pointermove":
175
+ return { _tag: "PointerMove", x, y, pointer };
176
+ case "pointerdown":
177
+ return {
178
+ _tag: "PointerDown",
179
+ x,
180
+ y,
181
+ button: event.button,
182
+ pointer,
183
+ modifiers: modifiersOf(event)
184
+ };
185
+ case "pointerup":
186
+ return { _tag: "PointerUp", x, y, button: event.button, pointer };
187
+ case "dblclick":
188
+ return { _tag: "DoubleClick", x, y, button: event.button };
189
+ case "contextmenu":
190
+ return { _tag: "ContextMenu", x, y };
191
+ case "wheel":
192
+ return event instanceof WheelEvent ? {
193
+ _tag: "Wheel",
194
+ x,
195
+ y,
196
+ deltaX: event.deltaX * (wheelScale[event.deltaMode] ?? 1),
197
+ deltaY: event.deltaY * (wheelScale[event.deltaMode] ?? 1),
198
+ modifiers: modifiersOf(event)
199
+ } : undefined;
200
+ default:
201
+ return;
202
+ }
203
+ };
204
+ var capturesPointer = (events) => events.some((event) => event._tag === "PointerDown");
205
+ var claimsContextMenu = (events) => events.some((event) => event._tag === "ContextMenu");
206
+ var eventTypes = [
207
+ "pointermove",
208
+ "pointerdown",
209
+ "pointerup",
210
+ "pointerleave",
211
+ "pointercancel",
212
+ "dblclick",
213
+ "contextmenu",
214
+ "wheel"
215
+ ];
216
+ var listenForPointer = (element, onInput, claimsGestures = () => false) => {
217
+ const listener = (event) => {
218
+ if (!(event instanceof MouseEvent))
219
+ return;
220
+ const input = toPointerInput(event, element.getBoundingClientRect());
221
+ if (input === undefined)
222
+ return;
223
+ const events = onInput(input);
224
+ const claimed = claimsGestures();
225
+ if (claimed && (event.type === "wheel" || event.type === "contextmenu")) {
226
+ event.preventDefault();
227
+ }
228
+ if (event.type === "contextmenu" && claimsContextMenu(events))
229
+ event.preventDefault();
230
+ if (event instanceof PointerEvent && event.type === "pointerdown" && (claimed || capturesPointer(events))) {
231
+ element.setPointerCapture(event.pointerId);
232
+ }
233
+ };
234
+ for (const type of eventTypes)
235
+ element.addEventListener(type, listener, { passive: false });
236
+ return () => {
237
+ for (const type of eventTypes)
238
+ element.removeEventListener(type, listener);
239
+ };
240
+ };
241
+ var claimsGestures = (owner) => () => owner.gestures() === "scene";
242
+ var touchActionSync = (element, owner) => () => {
243
+ const touchAction = claimsGestures(owner)() ? "none" : "";
244
+ if (element.style.touchAction !== touchAction)
245
+ element.style.touchAction = touchAction;
246
+ };
247
+ // src/dom/viewport.ts
248
+ var defaultPixelRatio = [1, 2];
249
+ var resolvePixelRatio = (devicePixelRatio, setting) => typeof setting === "number" ? setting : Math.min(Math.max(setting[0], devicePixelRatio), setting[1]);
250
+ var measureViewport = (element, devicePixelRatio, pixelRatio = defaultPixelRatio) => {
251
+ const rect = element.getBoundingClientRect();
252
+ return {
253
+ width: Math.max(1, rect.width),
254
+ height: Math.max(1, rect.height),
255
+ pixelRatio: resolvePixelRatio(devicePixelRatio, pixelRatio)
256
+ };
257
+ };
258
+ var observeSize = (element, onResize) => {
259
+ const observer = new ResizeObserver(onResize);
260
+ observer.observe(element);
261
+ return () => observer.disconnect();
262
+ };
263
+ var observeVisibility = (element, onVisible) => {
264
+ const observer = new IntersectionObserver((entries) => {
265
+ for (const entry of entries)
266
+ onVisible(entry.isIntersecting);
267
+ });
268
+ observer.observe(element);
269
+ return () => observer.disconnect();
270
+ };
271
+ var observeReducedMotion = (view, onChange) => {
272
+ const query = view.matchMedia("(prefers-reduced-motion: reduce)");
273
+ const listener = (event) => onChange(event.matches);
274
+ onChange(query.matches);
275
+ query.addEventListener("change", listener);
276
+ return () => query.removeEventListener("change", listener);
277
+ };
278
+ var observePixelRatio = (view, onChange) => {
279
+ let query;
280
+ const listener = () => {
281
+ query?.removeEventListener("change", listener);
282
+ arm();
283
+ onChange();
284
+ };
285
+ const arm = () => {
286
+ query = view.matchMedia(`(resolution: ${view.devicePixelRatio}dppx)`);
287
+ query.addEventListener("change", listener);
288
+ };
289
+ arm();
290
+ return () => query?.removeEventListener("change", listener);
291
+ };
292
+ // src/dom/webgl.ts
293
+ import { webglKit } from "@opsydyn/effect-three/webgl";
294
+ import {
295
+ HalfFloatType as HalfFloatType2,
296
+ LinearFilter,
297
+ PMREMGenerator,
298
+ Vector2 as Vector22,
299
+ WebGLRenderer,
300
+ WebGLRenderTarget as WebGLRenderTarget2
301
+ } from "three";
302
+ import { RoomEnvironment } from "three/addons/environments/RoomEnvironment.js";
303
+
304
+ // src/dom/postprocessing.ts
305
+ import { effectsEqual } from "@opsydyn/effect-three";
306
+ import {
307
+ HalfFloatType,
308
+ PerspectiveCamera,
309
+ Scene as EmptyScene,
310
+ Vector2,
311
+ WebGLRenderTarget
312
+ } from "three";
313
+ import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
314
+ import { FilmPass } from "three/addons/postprocessing/FilmPass.js";
315
+ import { OutputPass } from "three/addons/postprocessing/OutputPass.js";
316
+ import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
317
+ import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
318
+ import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";
319
+ import { VignetteShader } from "three/addons/shaders/VignetteShader.js";
320
+ var passFor = (effect, size) => {
321
+ switch (effect._tag) {
322
+ case "Bloom":
323
+ return new UnrealBloomPass(new Vector2(size.width, size.height), effect.strength, effect.radius, effect.threshold);
324
+ case "Vignette": {
325
+ const pass = new ShaderPass(VignetteShader);
326
+ pass.uniforms["offset"] = { value: effect.offset };
327
+ pass.uniforms["darkness"] = { value: effect.darkness };
328
+ return pass;
329
+ }
330
+ case "Film":
331
+ return new FilmPass(effect.intensity, effect.grayscale);
332
+ }
333
+ };
334
+ var createPostProcessing = (renderer) => {
335
+ let effects = [];
336
+ let composer;
337
+ let renderPass;
338
+ const size = { width: 1, height: 1, pixelRatio: 1 };
339
+ const dispose = () => {
340
+ if (composer === undefined)
341
+ return;
342
+ for (const pass of composer.passes)
343
+ pass.dispose();
344
+ composer.dispose();
345
+ composer = undefined;
346
+ renderPass = undefined;
347
+ };
348
+ const build = () => {
349
+ dispose();
350
+ if (effects.length === 0)
351
+ return;
352
+ const target = new WebGLRenderTarget(size.width, size.height, {
353
+ type: HalfFloatType,
354
+ samples: 4
355
+ });
356
+ const next = new EffectComposer(renderer, target);
357
+ const first = new RenderPass(new EmptyScene, new PerspectiveCamera);
358
+ next.addPass(first);
359
+ for (const effect of effects)
360
+ next.addPass(passFor(effect, size));
361
+ next.addPass(new OutputPass);
362
+ next.setPixelRatio(size.pixelRatio);
363
+ next.setSize(size.width, size.height);
364
+ composer = next;
365
+ renderPass = first;
366
+ };
367
+ return {
368
+ configure: (next) => {
369
+ if (effectsEqual(next, effects))
370
+ return;
371
+ effects = next;
372
+ build();
373
+ },
374
+ setSize: (width, height, pixelRatio) => {
375
+ size.width = width;
376
+ size.height = height;
377
+ size.pixelRatio = pixelRatio;
378
+ composer?.setPixelRatio(pixelRatio);
379
+ composer?.setSize(width, height);
380
+ },
381
+ render: (scene, camera) => {
382
+ if (composer === undefined || renderPass === undefined)
383
+ return false;
384
+ renderPass.scene = scene;
385
+ renderPass.camera = camera;
386
+ composer.render();
387
+ return true;
388
+ },
389
+ dispose
390
+ };
391
+ };
392
+
393
+ // src/dom/renderSettings.ts
394
+ import {
395
+ ACESFilmicToneMapping,
396
+ AgXToneMapping,
397
+ BasicShadowMap,
398
+ CineonToneMapping,
399
+ LinearSRGBColorSpace,
400
+ LinearToneMapping,
401
+ Material,
402
+ NeutralToneMapping,
403
+ NoToneMapping,
404
+ PCFShadowMap,
405
+ ReinhardToneMapping,
406
+ SRGBColorSpace as SRGBColorSpace2,
407
+ VSMShadowMap
408
+ } from "three";
409
+ var shadowTypes = {
410
+ basic: BasicShadowMap,
411
+ percentage: PCFShadowMap,
412
+ soft: PCFShadowMap,
413
+ variance: VSMShadowMap
414
+ };
415
+ var toneMappings = {
416
+ none: NoToneMapping,
417
+ linear: LinearToneMapping,
418
+ reinhard: ReinhardToneMapping,
419
+ cineon: CineonToneMapping,
420
+ aces: ACESFilmicToneMapping,
421
+ agx: AgXToneMapping,
422
+ neutral: NeutralToneMapping
423
+ };
424
+ var colorSpaces = {
425
+ srgb: SRGBColorSpace2,
426
+ linear: LinearSRGBColorSpace
427
+ };
428
+ var recompileMaterials = (scene) => {
429
+ scene.traverse((object) => {
430
+ const material = Reflect.get(object, "material");
431
+ const materials = Array.isArray(material) ? material : [material];
432
+ for (const each of materials)
433
+ if (each instanceof Material)
434
+ each.needsUpdate = true;
435
+ });
436
+ };
437
+ var presentable = new WeakMap;
438
+ var drawing = new WeakMap;
439
+ var registerPresentable = (canvas, renderer, presenting) => {
440
+ presentable.set(canvas, presenting);
441
+ drawing.set(canvas, renderer);
442
+ };
443
+ var forgetPresentable = (canvas) => {
444
+ presentable.delete(canvas);
445
+ drawing.delete(canvas);
446
+ };
447
+ var presentableRendererOf = (canvas) => presentable.get(canvas);
448
+
449
+ // src/dom/webgl.ts
450
+ var configure = (renderer, settings) => {
451
+ const shadowMap = renderer.shadowMap;
452
+ const enabled = settings.shadows !== "off";
453
+ const switched = shadowMap.enabled !== enabled;
454
+ const type = enabled ? shadowTypes[settings.shadows] : shadowMap.type;
455
+ if (switched || shadowMap.type !== type)
456
+ shadowMap.needsUpdate = true;
457
+ shadowMap.enabled = enabled;
458
+ shadowMap.type = type;
459
+ renderer.toneMapping = toneMappings[settings.toneMapping];
460
+ renderer.toneMappingExposure = settings.exposure;
461
+ renderer.outputColorSpace = colorSpaces[settings.colorSpace];
462
+ return switched;
463
+ };
464
+ var drawingSize = new Vector22;
465
+ var createTarget = (targets, width, height) => {
466
+ const target = new WebGLRenderTarget2(width, height, {
467
+ minFilter: LinearFilter,
468
+ magFilter: LinearFilter,
469
+ type: HalfFloatType2,
470
+ samples: 8
471
+ });
472
+ const handle = { texture: target.texture, dispose: () => target.dispose() };
473
+ targets.set(handle, target);
474
+ return handle;
475
+ };
476
+ var createRoom = (renderer) => {
477
+ const generator = new PMREMGenerator(renderer);
478
+ const room = new RoomEnvironment;
479
+ const target = generator.fromScene(room, 0.04);
480
+ room.dispose();
481
+ generator.dispose();
482
+ return { texture: target.texture, dispose: () => target.dispose() };
483
+ };
484
+ var drawPresenting = (renderer, scene, camera) => {
485
+ const passthrough = renderer.xr.getEnvironmentBlendMode() !== "opaque";
486
+ if (!passthrough || scene.background === null) {
487
+ renderer.render(scene, camera);
488
+ return;
489
+ }
490
+ const background = scene.background;
491
+ scene.background = null;
492
+ renderer.render(scene, camera);
493
+ scene.background = background;
494
+ };
495
+ var webglRendererFactory = (canvas, options = {}) => ({
496
+ kit: webglKit,
497
+ create: () => {
498
+ const renderer = new WebGLRenderer({ antialias: true, ...options, canvas });
499
+ registerPresentable(canvas, renderer, {
500
+ xr: renderer.xr,
501
+ setXrLoop: (callback) => renderer.xr.setAnimationLoop(callback)
502
+ });
503
+ const post = createPostProcessing(renderer);
504
+ const targets = new WeakMap;
505
+ let pixelRatio = 1;
506
+ let resolution = 1;
507
+ let scissored = false;
508
+ let shadowSwitches = 0;
509
+ const compiledAt = new WeakMap;
510
+ const applyPixelRatio = () => {
511
+ if (renderer.getPixelRatio() !== pixelRatio * resolution) {
512
+ renderer.setPixelRatio(pixelRatio * resolution);
513
+ }
514
+ const { width, height } = renderer.getSize(drawingSize);
515
+ post.setSize(width, height, pixelRatio * resolution);
516
+ };
517
+ return {
518
+ render: (scene, camera, area, target) => {
519
+ if (shadowSwitches > 0 && compiledAt.get(scene) !== shadowSwitches) {
520
+ recompileMaterials(scene);
521
+ compiledAt.set(scene, shadowSwitches);
522
+ }
523
+ const webglTarget = target === undefined ? undefined : targets.get(target);
524
+ if (webglTarget !== undefined) {
525
+ const previous = renderer.getRenderTarget();
526
+ const clearAlpha = renderer.getClearAlpha();
527
+ const xrEnabled = renderer.xr.enabled;
528
+ renderer.xr.enabled = false;
529
+ renderer.setClearAlpha(0);
530
+ renderer.setRenderTarget(webglTarget);
531
+ renderer.render(scene, camera);
532
+ renderer.setRenderTarget(previous);
533
+ renderer.setClearAlpha(clearAlpha);
534
+ renderer.xr.enabled = xrEnabled;
535
+ return;
536
+ }
537
+ if (renderer.xr.isPresenting) {
538
+ drawPresenting(renderer, scene, camera);
539
+ return;
540
+ }
541
+ const { width, height } = renderer.getSize(drawingSize);
542
+ if (area === undefined) {
543
+ if (scissored) {
544
+ renderer.setScissorTest(false);
545
+ renderer.setViewport(0, 0, width, height);
546
+ scissored = false;
547
+ }
548
+ if (!post.render(scene, camera))
549
+ renderer.render(scene, camera);
550
+ return;
551
+ }
552
+ const bottom = height - area.y - area.height;
553
+ renderer.setViewport(area.x, bottom, area.width, area.height);
554
+ renderer.setScissor(area.x, bottom, area.width, area.height);
555
+ renderer.setScissorTest(true);
556
+ scissored = true;
557
+ renderer.render(scene, camera);
558
+ },
559
+ createTarget: (width, height) => createTarget(targets, width, height),
560
+ createEnvironment: () => createRoom(renderer),
561
+ configure: (settings) => {
562
+ if (configure(renderer, settings))
563
+ shadowSwitches++;
564
+ resolution = settings.resolution;
565
+ post.configure(settings.effects);
566
+ applyPixelRatio();
567
+ },
568
+ setViewport: (viewport) => {
569
+ pixelRatio = viewport.pixelRatio;
570
+ applyPixelRatio();
571
+ renderer.setSize(viewport.width, viewport.height, false);
572
+ post.setSize(viewport.width, viewport.height, pixelRatio * resolution);
573
+ },
574
+ memory: () => ({
575
+ geometries: renderer.info.memory.geometries,
576
+ textures: renderer.info.memory.textures
577
+ }),
578
+ dispose: () => {
579
+ forgetPresentable(canvas);
580
+ renderer.xr.getSession()?.end();
581
+ post.dispose();
582
+ renderer.dispose();
583
+ renderer.forceContextLoss();
584
+ }
585
+ };
586
+ }
587
+ });
588
+ // src/dom/webgpu.ts
589
+ import { webgpuKit } from "@opsydyn/effect-three/webgpu";
590
+ import {
591
+ HalfFloatType as HalfFloatType3,
592
+ LinearFilter as LinearFilter2,
593
+ RenderTarget as ThreeRenderTarget,
594
+ Vector2 as Vector23
595
+ } from "three";
596
+ import { RoomEnvironment as RoomEnvironment2 } from "three/addons/environments/RoomEnvironment.js";
597
+ import { PMREMGenerator as PMREMGenerator2, WebGPURenderer } from "three/webgpu";
598
+
599
+ // src/dom/webgpuPostprocessing.ts
600
+ import { effectsEqual as effectsEqual2 } from "@opsydyn/effect-three";
601
+ import { bloom } from "three/addons/tsl/display/BloomNode.js";
602
+ import {
603
+ clamp,
604
+ dot,
605
+ fract,
606
+ luminance,
607
+ max,
608
+ mix,
609
+ pass,
610
+ rand,
611
+ screenUV,
612
+ time,
613
+ uv,
614
+ vec3,
615
+ vec4
616
+ } from "three/tsl";
617
+ import { RenderPipeline } from "three/webgpu";
618
+ var applied = (input, effect) => {
619
+ switch (effect._tag) {
620
+ case "Bloom": {
621
+ const glow = bloom(input, effect.strength, effect.radius, effect.threshold);
622
+ const shine = max(glow.r, max(glow.g, glow.b));
623
+ return vec4(input.rgb.add(glow.rgb), clamp(input.a.add(shine), 0, 1));
624
+ }
625
+ case "Vignette": {
626
+ const centred = screenUV.sub(0.5).mul(effect.offset);
627
+ return vec4(mix(input.rgb, vec3(1 - effect.darkness), dot(centred, centred)), input.a);
628
+ }
629
+ case "Film": {
630
+ const base = input.rgb;
631
+ const noise = rand(fract(uv().add(time)));
632
+ const grained = mix(base, base.add(base.mul(clamp(noise.add(0.1), 0, 1))), effect.intensity);
633
+ return vec4(effect.grayscale ? vec3(luminance(grained)) : grained, input.a);
634
+ }
635
+ }
636
+ };
637
+ var createWebGPUPostProcessing = (renderer) => {
638
+ let effects = [];
639
+ let pipeline;
640
+ let drawn;
641
+ const dispose = () => {
642
+ pipeline?.dispose();
643
+ pipeline = undefined;
644
+ drawn = undefined;
645
+ };
646
+ const build = (scene, camera) => {
647
+ dispose();
648
+ const output = effects.reduce(applied, pass(scene, camera));
649
+ const next = new RenderPipeline(renderer, output);
650
+ pipeline = next;
651
+ drawn = { scene, camera };
652
+ return next;
653
+ };
654
+ return {
655
+ configure: (next) => {
656
+ if (effectsEqual2(next, effects))
657
+ return;
658
+ effects = next;
659
+ dispose();
660
+ },
661
+ setSize: () => {
662
+ return;
663
+ },
664
+ render: (scene, camera) => {
665
+ if (effects.length === 0)
666
+ return false;
667
+ const current = pipeline !== undefined && drawn?.scene === scene && drawn.camera === camera ? pipeline : build(scene, camera);
668
+ current.render();
669
+ return true;
670
+ },
671
+ dispose
672
+ };
673
+ };
674
+
675
+ // src/dom/webgpu.ts
676
+ var configure2 = (renderer, settings) => {
677
+ const shadowMap = renderer.shadowMap;
678
+ const enabled = settings.shadows !== "off";
679
+ const switched = shadowMap.enabled !== enabled;
680
+ shadowMap.enabled = enabled;
681
+ if (enabled)
682
+ shadowMap.type = shadowTypes[settings.shadows];
683
+ renderer.toneMapping = toneMappings[settings.toneMapping];
684
+ renderer.toneMappingExposure = settings.exposure;
685
+ renderer.outputColorSpace = colorSpaces[settings.colorSpace];
686
+ return switched;
687
+ };
688
+ var drawingSize2 = new Vector23;
689
+ var createTarget2 = (targets, width, height) => {
690
+ const target = new ThreeRenderTarget(width, height, {
691
+ minFilter: LinearFilter2,
692
+ magFilter: LinearFilter2,
693
+ type: HalfFloatType3,
694
+ samples: 4
695
+ });
696
+ const handle = { texture: target.texture, dispose: () => target.dispose() };
697
+ targets.set(handle, target);
698
+ return handle;
699
+ };
700
+ var createRoom2 = (renderer) => {
701
+ const generator = new PMREMGenerator2(renderer);
702
+ const room = new RoomEnvironment2;
703
+ const target = generator.fromScene(room, 0.04);
704
+ room.dispose();
705
+ generator.dispose();
706
+ return { texture: target.texture, dispose: () => target.dispose() };
707
+ };
708
+ var drawPresenting2 = (renderer, scene, camera) => {
709
+ const passthrough = renderer.xr.getEnvironmentBlendMode() !== "opaque";
710
+ if (!passthrough || scene.background === null) {
711
+ renderer.render(scene, camera);
712
+ return;
713
+ }
714
+ const background = scene.background;
715
+ scene.background = null;
716
+ renderer.render(scene, camera);
717
+ scene.background = background;
718
+ };
719
+ var webgpuRendererFactory = (canvas, options = {}) => ({
720
+ kit: webgpuKit,
721
+ create: () => {
722
+ const renderer = new WebGPURenderer({ antialias: true, alpha: false, ...options, canvas });
723
+ registerPresentable(canvas, renderer, {
724
+ xr: renderer.xr,
725
+ setXrLoop: (callback) => {
726
+ renderer.setAnimationLoop(callback);
727
+ }
728
+ });
729
+ const post = createWebGPUPostProcessing(renderer);
730
+ const targets = new WeakMap;
731
+ let pixelRatio = 1;
732
+ let resolution = 1;
733
+ let scissored = false;
734
+ let shadowSwitches = 0;
735
+ const compiledAt = new WeakMap;
736
+ const applyPixelRatio = () => {
737
+ if (renderer.getPixelRatio() !== pixelRatio * resolution) {
738
+ renderer.setPixelRatio(pixelRatio * resolution);
739
+ }
740
+ };
741
+ return {
742
+ ready: () => renderer.init().then(() => {
743
+ return;
744
+ }),
745
+ render: (scene, camera, area, target) => {
746
+ if (shadowSwitches > 0 && compiledAt.get(scene) !== shadowSwitches) {
747
+ recompileMaterials(scene);
748
+ compiledAt.set(scene, shadowSwitches);
749
+ }
750
+ const offscreen = target === undefined ? undefined : targets.get(target);
751
+ if (offscreen !== undefined) {
752
+ const previous = renderer.getRenderTarget();
753
+ const clearAlpha = renderer.getClearAlpha();
754
+ const xrEnabled = renderer.xr.enabled;
755
+ renderer.xr.enabled = false;
756
+ renderer.setClearAlpha(0);
757
+ renderer.setRenderTarget(offscreen);
758
+ renderer.render(scene, camera);
759
+ renderer.setRenderTarget(previous);
760
+ renderer.setClearAlpha(clearAlpha);
761
+ renderer.xr.enabled = xrEnabled;
762
+ return;
763
+ }
764
+ if (renderer.xr.isPresenting) {
765
+ drawPresenting2(renderer, scene, camera);
766
+ return;
767
+ }
768
+ const { width, height } = renderer.getSize(drawingSize2);
769
+ if (area === undefined) {
770
+ if (scissored) {
771
+ renderer.setScissorTest(false);
772
+ renderer.setViewport(0, 0, width, height);
773
+ scissored = false;
774
+ }
775
+ if (!post.render(scene, camera))
776
+ renderer.render(scene, camera);
777
+ return;
778
+ }
779
+ const bottom = height - area.y - area.height;
780
+ renderer.setViewport(area.x, bottom, area.width, area.height);
781
+ renderer.setScissor(area.x, bottom, area.width, area.height);
782
+ renderer.setScissorTest(true);
783
+ scissored = true;
784
+ renderer.render(scene, camera);
785
+ },
786
+ createTarget: (width, height) => createTarget2(targets, width, height),
787
+ createEnvironment: () => createRoom2(renderer),
788
+ configure: (settings) => {
789
+ if (configure2(renderer, settings))
790
+ shadowSwitches++;
791
+ resolution = settings.resolution;
792
+ post.configure(settings.effects);
793
+ applyPixelRatio();
794
+ },
795
+ setViewport: (viewport) => {
796
+ pixelRatio = viewport.pixelRatio;
797
+ applyPixelRatio();
798
+ renderer.setSize(viewport.width, viewport.height, false);
799
+ },
800
+ memory: () => ({
801
+ geometries: renderer.info.memory.geometries,
802
+ textures: renderer.info.memory.textures
803
+ }),
804
+ dispose: () => {
805
+ forgetPresentable(canvas);
806
+ renderer.xr.getSession()?.end();
807
+ post.dispose();
808
+ renderer.dispose();
809
+ }
810
+ };
811
+ }
812
+ });
813
+ // src/element.ts
814
+ import { SceneSchema } from "@opsydyn/effect-three";
815
+ import { Schema } from "effect";
816
+ import { CustomElement } from "foldkit";
817
+ import { defineMessageUnion } from "foldkit/message";
818
+ var SceneError = Schema.Union([
819
+ ...SceneSchema.DescriptionError.members,
820
+ Schema.TaggedStruct("MalformedScene", { issue: Schema.String })
821
+ ]);
822
+ var Counts = Schema.Struct({ live: Schema.Int, created: Schema.Int, disposed: Schema.Int });
823
+ var ReconciledDetail = Schema.Struct({
824
+ ops: Schema.Struct({
825
+ created: Schema.Int,
826
+ updated: Schema.Int,
827
+ moved: Schema.Int,
828
+ removed: Schema.Int,
829
+ replaced: Schema.Int,
830
+ reordered: Schema.Int,
831
+ configured: Schema.Int
832
+ }),
833
+ resources: Schema.Struct({ geometries: Counts, materials: Counts })
834
+ });
835
+ var XrSessionDetail = Schema.Struct({
836
+ mode: Schema.NullOr(Schema.Literals(["immersive-vr", "immersive-ar"]))
837
+ });
838
+ var sceneElement = CustomElement.define({
839
+ tag: "effect-three-scene",
840
+ properties: { scene: SceneSchema.SceneDescription },
841
+ events: {
842
+ "scene-click": SceneSchema.ButtonDetail,
843
+ "scene-double-click": SceneSchema.ButtonDetail,
844
+ "scene-context-menu": SceneSchema.HitDetail,
845
+ "scene-pointer-down": SceneSchema.ButtonDetail,
846
+ "scene-pointer-up": SceneSchema.ButtonDetail,
847
+ "scene-pointer-enter": SceneSchema.HitDetail,
848
+ "scene-pointer-move": SceneSchema.HitDetail,
849
+ "scene-pointer-leave": SceneSchema.KeyDetail,
850
+ "scene-pointer-missed": SceneSchema.MissDetail,
851
+ "scene-wheel": SceneSchema.WheelDetail,
852
+ "scene-drag": SceneSchema.DragDetail,
853
+ "scene-drag-end": SceneSchema.DragDetail,
854
+ "scene-gesture": SceneSchema.GestureDetail,
855
+ "scene-auto-rotated": SceneSchema.AutoRotatedDetail,
856
+ "scene-collision": SceneSchema.CollisionDetail,
857
+ "scene-contact-force": SceneSchema.ContactForceDetail,
858
+ "scene-measured": SceneSchema.MeasuredDetail,
859
+ "scene-label-measured": SceneSchema.LabelMeasuredDetail,
860
+ "scene-xr": XrSessionDetail,
861
+ "scene-xr-stick": SceneSchema.XrStickDetail,
862
+ "scene-xr-button": SceneSchema.XrButtonDetail,
863
+ "scene-xr-surface": SceneSchema.XrSurfaceDetail,
864
+ "scene-xr-moved": SceneSchema.XrMovedDetail,
865
+ "scene-xr-planes": SceneSchema.XrPlanesDetail,
866
+ "scene-xr-meshes": SceneSchema.XrMeshesDetail,
867
+ "scene-performance": SceneSchema.PerformanceDetail,
868
+ "scene-error": Schema.Struct({ error: SceneError }),
869
+ "scene-reconciled": ReconciledDetail
870
+ }
871
+ });
872
+ var domEventName = {
873
+ Click: "scene-click",
874
+ DoubleClick: "scene-double-click",
875
+ ContextMenu: "scene-context-menu",
876
+ PointerDown: "scene-pointer-down",
877
+ PointerUp: "scene-pointer-up",
878
+ PointerEnter: "scene-pointer-enter",
879
+ PointerMove: "scene-pointer-move",
880
+ PointerLeave: "scene-pointer-leave",
881
+ PointerMissed: "scene-pointer-missed",
882
+ Wheel: "scene-wheel",
883
+ Drag: "scene-drag",
884
+ DragEnd: "scene-drag-end",
885
+ Gesture: "scene-gesture",
886
+ AutoRotated: "scene-auto-rotated",
887
+ Collision: "scene-collision",
888
+ ContactForce: "scene-contact-force",
889
+ Measured: "scene-measured",
890
+ LabelMeasured: "scene-label-measured",
891
+ XrStick: "scene-xr-stick",
892
+ XrButton: "scene-xr-button",
893
+ XrSurface: "scene-xr-surface",
894
+ XrMoved: "scene-xr-moved",
895
+ XrPlanes: "scene-xr-planes",
896
+ XrMeshes: "scene-xr-meshes",
897
+ PerformanceChanged: "scene-performance"
898
+ };
899
+ var SceneLifecycle = defineMessageUnion({
900
+ StartedScene: {},
901
+ FailedScene: { reason: Schema.String }
902
+ });
903
+ // src/xr.ts
904
+ import { Data as Data2, Effect as Effect2 } from "effect";
905
+
906
+ class XrUnavailable extends Data2.TaggedError("XrUnavailable") {
907
+ }
908
+ var makeXr = (hub) => ({
909
+ supported: (mode) => Effect2.promise(() => hub.supported(mode)),
910
+ enter: (mode) => Effect2.tryPromise({
911
+ try: () => hub.enter(mode),
912
+ catch: (cause) => new XrUnavailable({ mode, cause })
913
+ }),
914
+ exit: Effect2.promise(() => hub.exit())
915
+ });
916
+ // src/dom/reconciled.ts
917
+ var summarise = (ops, stats) => {
918
+ const counts = {
919
+ created: 0,
920
+ updated: 0,
921
+ moved: 0,
922
+ removed: 0,
923
+ replaced: 0,
924
+ reordered: 0,
925
+ configured: 0
926
+ };
927
+ for (const op of ops) {
928
+ switch (op._tag) {
929
+ case "Create":
930
+ counts.created++;
931
+ break;
932
+ case "Update":
933
+ counts.updated++;
934
+ break;
935
+ case "Move":
936
+ counts.moved++;
937
+ break;
938
+ case "Remove":
939
+ counts.removed++;
940
+ break;
941
+ case "Replace":
942
+ counts.replaced++;
943
+ break;
944
+ case "Order":
945
+ counts.reordered++;
946
+ break;
947
+ case "SetCamera":
948
+ case "DefineResources":
949
+ counts.configured++;
950
+ break;
951
+ }
952
+ }
953
+ return {
954
+ ops: counts,
955
+ resources: { geometries: { ...stats.geometries }, materials: { ...stats.materials } }
956
+ };
957
+ };
958
+ // src/environment.ts
959
+ import {
960
+ createResourceStore
961
+ } from "@opsydyn/effect-three";
962
+
963
+ // src/dom/xr.ts
964
+ import { Group as Group2 } from "three";
965
+
966
+ // src/dom/xrGlide.ts
967
+ var deadZone = 0.15;
968
+ var turnZone = 0.5;
969
+ var startGlider = (pose) => ({ pose, snapReady: true, moved: false });
970
+ var floorFacing = (q) => {
971
+ const x = -2 * (q.x * q.z + q.w * q.y);
972
+ const z = -(1 - 2 * (q.x * q.x + q.y * q.y));
973
+ const length = Math.hypot(x, z);
974
+ return length < 0.000001 ? [0, -1] : [x / length, z / length];
975
+ };
976
+ var still = (stick, zone) => Math.abs(stick[0]) < zone && Math.abs(stick[1]) < zone;
977
+ var moveBy = (origin, stick, facing, distance) => {
978
+ const [fx, fz] = facing;
979
+ const forward = -stick[1] * distance;
980
+ const right = stick[0] * distance;
981
+ return [origin[0] + fx * forward - fz * right, origin[1], origin[2] + fz * forward + fx * right];
982
+ };
983
+ var turnBy = (glider, glide, stick, seconds) => {
984
+ const { heading } = glider.pose;
985
+ const pushed = Math.abs(stick[0]) >= turnZone;
986
+ if (glide.turn === "smooth") {
987
+ return {
988
+ heading: pushed ? heading - stick[0] * glide.turnSpeed * seconds : heading,
989
+ snapReady: true
990
+ };
991
+ }
992
+ if (!pushed)
993
+ return { heading, snapReady: true };
994
+ if (!glider.snapReady)
995
+ return { heading, snapReady: false };
996
+ return {
997
+ heading: heading - Math.sign(stick[0]) * (glide.snapDegrees * Math.PI) / 180,
998
+ snapReady: false
999
+ };
1000
+ };
1001
+ var glideFrame = (glider, glide, sticks, facing, seconds) => {
1002
+ const moving = !still(sticks.move, deadZone);
1003
+ const origin = moving ? moveBy(glider.pose.origin, sticks.move, facing, glide.speed * seconds) : glider.pose.origin;
1004
+ const { heading, snapReady } = turnBy(glider, glide, sticks.turn, seconds);
1005
+ const changed = moving || heading !== glider.pose.heading;
1006
+ const resting = !moving && still(sticks.turn, turnZone);
1007
+ const moved = glider.moved || changed;
1008
+ const pose = { origin, heading };
1009
+ if (resting && moved)
1010
+ return { glider: { pose, snapReady, moved: false }, stopped: pose };
1011
+ return { glider: { pose, snapReady, moved }, stopped: undefined };
1012
+ };
1013
+
1014
+ // src/dom/xrInputs.ts
1015
+ import {
1016
+ BufferGeometry,
1017
+ Float32BufferAttribute,
1018
+ InstancedMesh,
1019
+ Line,
1020
+ LineBasicMaterial,
1021
+ Matrix4,
1022
+ MeshBasicMaterial,
1023
+ Quaternion,
1024
+ SphereGeometry,
1025
+ Vector3
1026
+ } from "three";
1027
+ var rayOf = (pose) => {
1028
+ const { position, orientation } = pose.transform;
1029
+ const { x, y, z, w } = orientation;
1030
+ return {
1031
+ origin: [position.x, position.y, position.z],
1032
+ direction: [-2 * (x * z + w * y), -2 * (y * z - w * x), -(1 - 2 * (x * x + y * y))]
1033
+ };
1034
+ };
1035
+ var buttonNames = [
1036
+ "trigger",
1037
+ "squeeze",
1038
+ "touchpad",
1039
+ "thumbstick",
1040
+ "primary",
1041
+ "secondary"
1042
+ ];
1043
+ var stickThresholds = { push: 0.6, release: 0.3 };
1044
+ var stickDirection = (x, y, previous) => {
1045
+ if (Math.max(Math.abs(x), Math.abs(y)) < stickThresholds.release)
1046
+ return "center";
1047
+ if (Math.max(Math.abs(x), Math.abs(y)) < stickThresholds.push)
1048
+ return previous;
1049
+ if (Math.abs(x) >= Math.abs(y))
1050
+ return x < 0 ? "left" : "right";
1051
+ return y < 0 ? "forward" : "back";
1052
+ };
1053
+ var handOf = (source) => source.handedness;
1054
+ var rayLength = 1.5;
1055
+ var jointCount = 25;
1056
+ var rayMaterial = new LineBasicMaterial({ color: 16777215, transparent: true, opacity: 0.6 });
1057
+ var jointMaterial = new MeshBasicMaterial({ color: 15262940 });
1058
+ var jointGeometry = new SphereGeometry(1, 10, 8);
1059
+ var makeVisual = (source, group) => {
1060
+ const geometry = new BufferGeometry;
1061
+ geometry.setAttribute("position", new Float32BufferAttribute([0, 0, 0, 0, 0, -rayLength], 3));
1062
+ const ray = new Line(geometry, rayMaterial);
1063
+ ray.raycast = () => {
1064
+ return;
1065
+ };
1066
+ ray.matrixAutoUpdate = false;
1067
+ group.add(ray);
1068
+ const joints = source.hand === undefined ? undefined : new InstancedMesh(jointGeometry, jointMaterial, jointCount);
1069
+ if (joints !== undefined) {
1070
+ joints.raycast = () => {
1071
+ return;
1072
+ };
1073
+ joints.frustumCulled = false;
1074
+ group.add(joints);
1075
+ }
1076
+ return { ray, joints };
1077
+ };
1078
+ var disposeVisual = (visual) => {
1079
+ visual.ray.removeFromParent();
1080
+ visual.ray.geometry.dispose();
1081
+ visual.joints?.removeFromParent();
1082
+ visual.joints?.dispose();
1083
+ };
1084
+ var matrix = new Matrix4;
1085
+ var up = new Vector3(0, 1, 0);
1086
+ var position = new Vector3;
1087
+ var turn = new Quaternion;
1088
+ var size = new Vector3;
1089
+ var placeJoints = (joints, hand, frame, space) => {
1090
+ let slot = 0;
1091
+ for (const jointSpace of hand.values()) {
1092
+ const pose = frame.getJointPose?.(jointSpace, space);
1093
+ if (pose !== undefined && pose !== null && slot < jointCount) {
1094
+ const { x, y, z } = pose.transform.position;
1095
+ const o = pose.transform.orientation;
1096
+ const radius = pose.radius ?? 0.008;
1097
+ matrix.compose(position.set(x, y, z), turn.set(o.x, o.y, o.z, o.w), size.setScalar(radius));
1098
+ joints.setMatrixAt(slot, matrix);
1099
+ slot++;
1100
+ }
1101
+ }
1102
+ joints.count = slot;
1103
+ joints.instanceMatrix.needsUpdate = true;
1104
+ };
1105
+ var surfaceOf = (pose) => {
1106
+ const { position: p, orientation: q } = pose.transform;
1107
+ return {
1108
+ point: [p.x, p.y, p.z],
1109
+ normal: [
1110
+ 2 * (q.x * q.y - q.w * q.z),
1111
+ 1 - 2 * (q.x * q.x + q.z * q.z),
1112
+ 2 * (q.y * q.z + q.w * q.x)
1113
+ ]
1114
+ };
1115
+ };
1116
+ var trackInputs = (session, group, settings, input) => {
1117
+ const tracked = new Map;
1118
+ let nextId = 0;
1119
+ const trackedOf = (source) => {
1120
+ const known = tracked.get(source);
1121
+ if (known !== undefined)
1122
+ return known;
1123
+ const created = {
1124
+ id: nextId++,
1125
+ visual: undefined,
1126
+ stick: "center",
1127
+ axes: [0, 0],
1128
+ pressed: [],
1129
+ hitTest: undefined,
1130
+ surface: undefined
1131
+ };
1132
+ tracked.set(source, created);
1133
+ if (settings()?.hitTest === true && session.requestHitTestSource !== undefined) {
1134
+ session.requestHitTestSource({ space: source.targetRaySpace })?.then((hitTest) => {
1135
+ if (tracked.get(source) === created)
1136
+ created.hitTest = hitTest;
1137
+ else
1138
+ hitTest.cancel();
1139
+ }, () => {
1140
+ return;
1141
+ });
1142
+ }
1143
+ return created;
1144
+ };
1145
+ const forget = (source, state) => {
1146
+ tracked.delete(source);
1147
+ if (state.visual !== undefined)
1148
+ disposeVisual(state.visual);
1149
+ state.hitTest?.cancel();
1150
+ input({ _tag: "Lost", source: state.id });
1151
+ };
1152
+ const gamepad = (source, state) => {
1153
+ const pad = source.gamepad;
1154
+ if (pad === undefined || pad === null || pad.mapping !== "xr-standard")
1155
+ return;
1156
+ const hand = handOf(source);
1157
+ buttonNames.forEach((button, index) => {
1158
+ const pressed = pad.buttons[index]?.pressed ?? false;
1159
+ if (pressed !== (state.pressed[index] ?? false)) {
1160
+ state.pressed[index] = pressed;
1161
+ input({ _tag: "Button", source: state.id, hand, button, pressed });
1162
+ }
1163
+ });
1164
+ state.axes = [pad.axes[2] ?? 0, pad.axes[3] ?? 0];
1165
+ const direction = stickDirection(state.axes[0], state.axes[1], state.stick);
1166
+ if (direction !== state.stick) {
1167
+ state.stick = direction;
1168
+ input({ _tag: "Stick", source: state.id, hand, direction });
1169
+ }
1170
+ };
1171
+ const show = (source, state, ray, frame, space) => {
1172
+ if (settings()?.showInputs !== true) {
1173
+ if (state.visual !== undefined)
1174
+ disposeVisual(state.visual);
1175
+ state.visual = undefined;
1176
+ return;
1177
+ }
1178
+ state.visual ??= makeVisual(source, group);
1179
+ const [ox, oy, oz] = ray.origin;
1180
+ const [dx, dy, dz] = ray.direction;
1181
+ matrix.lookAt(position.set(0, 0, 0), size.set(dx, dy, dz), up);
1182
+ matrix.setPosition(ox, oy, oz);
1183
+ state.visual.ray.matrix.copy(matrix);
1184
+ state.visual.ray.visible = source.hand === undefined;
1185
+ if (state.visual.joints !== undefined && source.hand !== undefined) {
1186
+ placeJoints(state.visual.joints, source.hand, frame, space);
1187
+ }
1188
+ };
1189
+ return {
1190
+ frame: (frame, space) => {
1191
+ const present = new Set;
1192
+ for (const source of session.inputSources) {
1193
+ present.add(source);
1194
+ const state = trackedOf(source);
1195
+ const pose = frame.getPose(source.targetRaySpace, space);
1196
+ if (pose !== undefined && pose !== null) {
1197
+ const ray = rayOf(pose);
1198
+ input({ _tag: "Aim", source: state.id, ray });
1199
+ show(source, state, ray, frame, space);
1200
+ }
1201
+ gamepad(source, state);
1202
+ if (state.hitTest !== undefined) {
1203
+ const result = frame.getHitTestResults(state.hitTest)[0]?.getPose(space);
1204
+ state.surface = result === undefined || result === null ? undefined : surfaceOf(result);
1205
+ }
1206
+ }
1207
+ for (const [source, state] of tracked)
1208
+ if (!present.has(source))
1209
+ forget(source, state);
1210
+ },
1211
+ idOf: (source) => trackedOf(source).id,
1212
+ selected: (source) => {
1213
+ const state = tracked.get(source);
1214
+ if (state?.surface === undefined)
1215
+ return;
1216
+ input({ _tag: "Surface", source: state.id, hand: handOf(source), ...state.surface });
1217
+ },
1218
+ sticks: () => {
1219
+ const axesOf = (hand) => {
1220
+ for (const [source, state] of tracked)
1221
+ if (source.handedness === hand)
1222
+ return state.axes;
1223
+ return [0, 0];
1224
+ };
1225
+ return { left: axesOf("left"), right: axesOf("right") };
1226
+ },
1227
+ stop: () => {
1228
+ for (const [source, state] of tracked)
1229
+ forget(source, state);
1230
+ }
1231
+ };
1232
+ };
1233
+
1234
+ // src/dom/xrSpaces.ts
1235
+ import {
1236
+ xrMeshGeometryId
1237
+ } from "@opsydyn/effect-three";
1238
+ import { BufferAttribute, BufferGeometry as BufferGeometry2, Euler, Quaternion as Quaternion2 } from "three";
1239
+ var turn2 = new Quaternion2;
1240
+ var angles = new Euler;
1241
+ var poseOf = (pose) => {
1242
+ if (pose === null || pose === undefined)
1243
+ return;
1244
+ const { position: p, orientation: o } = pose.transform;
1245
+ return { position: [p.x, p.y, p.z], quaternion: [o.x, o.y, o.z, o.w] };
1246
+ };
1247
+ var sameVec3 = (a, b) => a.every((value, i) => value === b[i]);
1248
+ var sameAnchor = (a, b) => sameVec3(a.position, b.position) && sameVec3(a.rotation, b.rotation);
1249
+ var transformOf = ({ position: [x, y, z], rotation }) => {
1250
+ turn2.setFromEuler(angles.fromArray([...rotation]));
1251
+ return new XRRigidTransform({ x, y, z }, { x: turn2.x, y: turn2.y, z: turn2.z, w: turn2.w });
1252
+ };
1253
+ var planeOf = (id, plane, pose) => {
1254
+ angles.setFromQuaternion(turn2.fromArray([...pose.quaternion]));
1255
+ return {
1256
+ id,
1257
+ orientation: plane.orientation,
1258
+ label: plane.semanticLabel ?? "",
1259
+ position: pose.position,
1260
+ rotation: [angles.x, angles.y, angles.z],
1261
+ polygon: plane.polygon.map((point) => [point.x, point.y, point.z])
1262
+ };
1263
+ };
1264
+ var signatureOf = (plane) => [plane.id, plane.orientation, plane.label, ...plane.position, ...plane.rotation].concat(plane.polygon.flat()).join(",");
1265
+ var meshSignatureOf = (mesh) => [mesh.id, mesh.label, ...mesh.position, ...mesh.rotation].join(",");
1266
+ var meshGeometryOf = (mesh) => {
1267
+ const geometry = new BufferGeometry2;
1268
+ geometry.setIndex(new BufferAttribute(mesh.indices, 1));
1269
+ geometry.setAttribute("position", new BufferAttribute(mesh.vertices, 3));
1270
+ geometry.computeVertexNormals();
1271
+ return geometry;
1272
+ };
1273
+ var withdrawLent = (lent) => {
1274
+ lent.withdraw();
1275
+ lent.geometry.dispose();
1276
+ };
1277
+ var trackSpaces = (session, settings, follow, input, lend) => {
1278
+ const anchors = new Map;
1279
+ const planeIds = new WeakMap;
1280
+ const planes = new Map;
1281
+ let nextPlane = 0;
1282
+ let reported;
1283
+ const meshIds = new WeakMap;
1284
+ const meshes = new Map;
1285
+ const lentMeshes = new Map;
1286
+ let nextMesh = 0;
1287
+ let reportedMeshes;
1288
+ const syncAnchors = (frame, space) => {
1289
+ const wanted = settings()?.anchors ?? [];
1290
+ const seen = new Set;
1291
+ for (const spec of wanted) {
1292
+ seen.add(spec.id);
1293
+ const made = anchors.get(spec.id);
1294
+ if (made !== undefined && sameAnchor(made.spec, spec))
1295
+ continue;
1296
+ made?.anchor?.delete();
1297
+ const entry = { spec, anchor: undefined };
1298
+ anchors.set(spec.id, entry);
1299
+ frame.createAnchor?.(transformOf(spec), space).then((anchor) => {
1300
+ if (anchors.get(spec.id) === entry)
1301
+ entry.anchor = anchor;
1302
+ else
1303
+ anchor.delete();
1304
+ }, () => {
1305
+ return;
1306
+ });
1307
+ }
1308
+ for (const [id, made] of anchors) {
1309
+ if (seen.has(id))
1310
+ continue;
1311
+ made.anchor?.delete();
1312
+ anchors.delete(id);
1313
+ }
1314
+ };
1315
+ const syncPlanes = (frame, space) => {
1316
+ planes.clear();
1317
+ if (settings()?.planes !== true || frame.detectedPlanes === undefined)
1318
+ return;
1319
+ const found = [];
1320
+ for (const plane of frame.detectedPlanes) {
1321
+ const id = planeIds.get(plane) ?? `plane-${nextPlane++}`;
1322
+ planeIds.set(plane, id);
1323
+ planes.set(id, plane);
1324
+ const pose = poseOf(frame.getPose(plane.planeSpace, space));
1325
+ if (pose !== undefined)
1326
+ found.push(planeOf(id, plane, pose));
1327
+ }
1328
+ const now = found.map(signatureOf).join(" ");
1329
+ if (now === reported)
1330
+ return;
1331
+ reported = now;
1332
+ input({ _tag: "Planes", planes: found });
1333
+ };
1334
+ const lendMesh = (id, mesh) => {
1335
+ const lent = lentMeshes.get(id);
1336
+ if (lent?.vertices === mesh.vertices && lent.indices === mesh.indices)
1337
+ return;
1338
+ if (lent !== undefined)
1339
+ withdrawLent(lent);
1340
+ const geometry = meshGeometryOf(mesh);
1341
+ lentMeshes.set(id, {
1342
+ vertices: mesh.vertices,
1343
+ indices: mesh.indices,
1344
+ geometry,
1345
+ withdraw: lend(xrMeshGeometryId(id), geometry)
1346
+ });
1347
+ };
1348
+ const syncMeshes = (frame, space) => {
1349
+ meshes.clear();
1350
+ if (settings()?.meshes === true && frame.detectedMeshes !== undefined) {
1351
+ const found = [];
1352
+ for (const mesh of frame.detectedMeshes) {
1353
+ const id = meshIds.get(mesh) ?? `mesh-${nextMesh++}`;
1354
+ meshIds.set(mesh, id);
1355
+ meshes.set(id, mesh);
1356
+ lendMesh(id, mesh);
1357
+ const pose = poseOf(frame.getPose(mesh.meshSpace, space));
1358
+ if (pose !== undefined) {
1359
+ angles.setFromQuaternion(turn2.fromArray([...pose.quaternion]));
1360
+ found.push({
1361
+ id,
1362
+ label: mesh.semanticLabel ?? "",
1363
+ position: pose.position,
1364
+ rotation: [angles.x, angles.y, angles.z]
1365
+ });
1366
+ }
1367
+ }
1368
+ const now = found.map(meshSignatureOf).join(" ");
1369
+ if (now !== reportedMeshes) {
1370
+ reportedMeshes = now;
1371
+ input({ _tag: "Meshes", meshes: found });
1372
+ }
1373
+ }
1374
+ for (const [id, lent] of lentMeshes) {
1375
+ if (meshes.has(id))
1376
+ continue;
1377
+ withdrawLent(lent);
1378
+ lentMeshes.delete(id);
1379
+ }
1380
+ };
1381
+ const sourceOf = (hand, tracked) => {
1382
+ for (const source of session.inputSources) {
1383
+ if (source.handedness === hand && source.hand !== undefined === tracked)
1384
+ return source;
1385
+ }
1386
+ return;
1387
+ };
1388
+ const locateIn = (frame, space) => (target) => {
1389
+ switch (target._tag) {
1390
+ case "Head":
1391
+ return poseOf(frame.getViewerPose(space));
1392
+ case "Joint": {
1393
+ const joint = sourceOf(target.hand, true)?.hand?.get(target.joint);
1394
+ return joint === undefined ? undefined : poseOf(frame.getJointPose?.(joint, space));
1395
+ }
1396
+ case "Controller": {
1397
+ const source = sourceOf(target.hand, false);
1398
+ const located = target.space === "grip" ? source?.gripSpace : source?.targetRaySpace;
1399
+ return located === undefined ? undefined : poseOf(frame.getPose(located, space));
1400
+ }
1401
+ case "Anchor": {
1402
+ const anchor = anchors.get(target.id)?.anchor;
1403
+ return anchor === undefined ? undefined : poseOf(frame.getPose(anchor.anchorSpace, space));
1404
+ }
1405
+ case "Plane": {
1406
+ const plane = planes.get(target.id);
1407
+ return plane === undefined ? undefined : poseOf(frame.getPose(plane.planeSpace, space));
1408
+ }
1409
+ case "Mesh": {
1410
+ const mesh = meshes.get(target.id);
1411
+ return mesh === undefined ? undefined : poseOf(frame.getPose(mesh.meshSpace, space));
1412
+ }
1413
+ }
1414
+ };
1415
+ return {
1416
+ frame: (frame, space) => {
1417
+ syncAnchors(frame, space);
1418
+ syncPlanes(frame, space);
1419
+ syncMeshes(frame, space);
1420
+ follow(locateIn(frame, space));
1421
+ },
1422
+ stop: () => {
1423
+ for (const made of anchors.values())
1424
+ made.anchor?.delete();
1425
+ anchors.clear();
1426
+ planes.clear();
1427
+ meshes.clear();
1428
+ for (const lent of lentMeshes.values())
1429
+ withdrawLent(lent);
1430
+ lentMeshes.clear();
1431
+ follow(() => {
1432
+ return;
1433
+ });
1434
+ }
1435
+ };
1436
+ };
1437
+
1438
+ // src/dom/xr.ts
1439
+ var sessionInit = (settings, mode = "immersive-vr", overlay) => ({
1440
+ requiredFeatures: [settings.referenceSpace],
1441
+ optionalFeatures: [
1442
+ ...["local-floor", "bounded-floor", "hand-tracking", "anchors"].filter((feature) => feature !== settings.referenceSpace),
1443
+ ...settings.planes ? ["plane-detection"] : [],
1444
+ ...settings.meshes ? ["mesh-detection"] : [],
1445
+ ...settings.domOverlay && overlay !== undefined ? ["dom-overlay"] : [],
1446
+ ...mode === "immersive-ar" && settings.hitTest ? ["hit-test"] : []
1447
+ ],
1448
+ ...settings.domOverlay && overlay !== undefined ? { domOverlay: { root: overlay } } : {}
1449
+ });
1450
+ var originOffset = (settings) => {
1451
+ const [x, y, z] = settings.origin;
1452
+ const half = -settings.heading / 2;
1453
+ const cos = Math.cos(-settings.heading);
1454
+ const sin = Math.sin(-settings.heading);
1455
+ return new XRRigidTransform({ x: -x * cos - z * sin, y: -y, z: x * sin - z * cos }, { x: 0, y: Math.sin(half), z: 0, w: Math.cos(half) });
1456
+ };
1457
+ var listenForSelects = (session, space, inputs, input) => {
1458
+ const forward = (tag) => (event) => {
1459
+ const reference = space();
1460
+ const pose = reference === null ? undefined : event.frame.getPose(event.inputSource.targetRaySpace, reference);
1461
+ if (pose !== undefined && pose !== null) {
1462
+ input({ _tag: tag, source: inputs.idOf(event.inputSource), ray: rayOf(pose) });
1463
+ }
1464
+ if (tag === "SelectEnd")
1465
+ inputs.selected(event.inputSource);
1466
+ };
1467
+ const started = forward("SelectStart");
1468
+ const ended = forward("SelectEnd");
1469
+ session.addEventListener("selectstart", started);
1470
+ session.addEventListener("selectend", ended);
1471
+ return () => {
1472
+ session.removeEventListener("selectstart", started);
1473
+ session.removeEventListener("selectend", ended);
1474
+ };
1475
+ };
1476
+ var present = async (renderer, session, settings, presented, ended) => {
1477
+ const { scene, draw, input } = presented;
1478
+ const { xr } = renderer;
1479
+ xr.enabled = true;
1480
+ xr.setReferenceSpaceType(settings.referenceSpace);
1481
+ xr.setFramebufferScaleFactor(settings.framebufferScale);
1482
+ let inputs;
1483
+ const spaces = trackSpaces(session, () => current, presented.follow, input, presented.lend);
1484
+ let current = settings;
1485
+ let glider = startGlider({ origin: settings.origin, heading: settings.heading });
1486
+ let lastTime;
1487
+ let base = null;
1488
+ const place = (pose) => {
1489
+ if (base !== null)
1490
+ xr.setReferenceSpace(base.getOffsetReferenceSpace(originOffset(pose)));
1491
+ };
1492
+ const glideStep = (time, frame, space) => {
1493
+ const seconds = Math.min(0.1, Math.max(0, (time - (lastTime ?? time)) / 1000));
1494
+ lastTime = time;
1495
+ if (current.glide === null || inputs === undefined)
1496
+ return;
1497
+ const head = frame.getViewerPose(space);
1498
+ const facing = floorFacing(head?.transform.orientation ?? { x: 0, y: 0, z: 0, w: 1 });
1499
+ const { left, right } = inputs.sticks();
1500
+ const next = glideFrame(glider, current.glide, { move: left, turn: right }, facing, seconds);
1501
+ if (next.glider.pose !== glider.pose)
1502
+ place(next.glider.pose);
1503
+ glider = next.glider;
1504
+ if (next.stopped !== undefined)
1505
+ input({ _tag: "Moved", ...next.stopped });
1506
+ };
1507
+ function sessionFrame(time, frame) {
1508
+ if (!xr.isPresenting)
1509
+ return;
1510
+ const space = xr.getReferenceSpace();
1511
+ if (frame !== undefined && space !== null) {
1512
+ inputs?.frame(frame, space);
1513
+ glideStep(time, frame, space);
1514
+ spaces.frame(frame, xr.getReferenceSpace() ?? space);
1515
+ }
1516
+ draw();
1517
+ }
1518
+ renderer.setXrLoop(sessionFrame);
1519
+ await xr.setSession(session);
1520
+ base = xr.getReferenceSpace();
1521
+ place(glider.pose);
1522
+ const shown = new Group2;
1523
+ shown.name = "xr-inputs";
1524
+ scene.add(shown);
1525
+ const tracked = trackInputs(session, shown, () => current, input);
1526
+ inputs = tracked;
1527
+ const stopSelects = listenForSelects(session, () => xr.getReferenceSpace(), tracked, input);
1528
+ session.addEventListener("end", () => {
1529
+ stopSelects();
1530
+ tracked.stop();
1531
+ spaces.stop();
1532
+ shown.removeFromParent();
1533
+ renderer.setXrLoop(null);
1534
+ xr.enabled = false;
1535
+ ended();
1536
+ }, { once: true });
1537
+ return {
1538
+ session,
1539
+ update: (next) => {
1540
+ const moved = next.origin.some((value, i) => value !== current.origin[i]) || next.heading !== current.heading;
1541
+ current = next;
1542
+ if (!moved)
1543
+ return;
1544
+ glider = startGlider({ origin: next.origin, heading: next.heading });
1545
+ place(glider.pose);
1546
+ }
1547
+ };
1548
+ };
1549
+
1550
+ // src/dom/xrHub.ts
1551
+ var createXrHub = (system) => {
1552
+ const targets = new Set;
1553
+ let current;
1554
+ const presentable = () => {
1555
+ for (const target of targets) {
1556
+ const settings = target.settings();
1557
+ if (settings !== null)
1558
+ return { target, settings };
1559
+ }
1560
+ return;
1561
+ };
1562
+ const enter = async (mode) => {
1563
+ if (current !== undefined)
1564
+ return;
1565
+ const xr = system();
1566
+ if (xr === undefined)
1567
+ throw new Error("WebXR is not available in this browser");
1568
+ const chosen = presentable();
1569
+ if (chosen === undefined)
1570
+ throw new Error("no mounted scene describes xr");
1571
+ const renderer = presentableRendererOf(chosen.target.canvas);
1572
+ if (renderer === undefined)
1573
+ throw new Error("the scene is not drawn by a three.js renderer");
1574
+ const session = await xr.requestSession(mode, sessionInit(chosen.settings, mode, chosen.target.element));
1575
+ const { target } = chosen;
1576
+ const draw = () => {
1577
+ const settings = target.settings();
1578
+ if (settings !== null)
1579
+ current?.presentation.update(settings);
1580
+ target.draw();
1581
+ };
1582
+ const ended = () => {
1583
+ current = undefined;
1584
+ target.presenting(null);
1585
+ };
1586
+ const presentation = await present(renderer, session, chosen.settings, {
1587
+ scene: target.scene,
1588
+ draw,
1589
+ follow: target.follow,
1590
+ lend: target.lend,
1591
+ input: target.input
1592
+ }, ended);
1593
+ current = { target, presentation };
1594
+ target.presenting(mode);
1595
+ };
1596
+ return {
1597
+ register: (target) => {
1598
+ targets.add(target);
1599
+ return () => {
1600
+ targets.delete(target);
1601
+ if (current?.target === target)
1602
+ current.presentation.session.end();
1603
+ };
1604
+ },
1605
+ supported: async (mode) => await system()?.isSessionSupported(mode) ?? false,
1606
+ enter,
1607
+ exit: async () => {
1608
+ await current?.presentation.session.end();
1609
+ }
1610
+ };
1611
+ };
1612
+
1613
+ // src/environment.ts
1614
+ var makeEnvironment = (host, options = {}) => {
1615
+ const store = createResourceStore();
1616
+ const assets = makeAssets(store);
1617
+ const xrHub = createXrHub(() => host.navigator?.xr);
1618
+ return {
1619
+ host,
1620
+ store,
1621
+ frames: animationFrameScheduler(host),
1622
+ renderer: options.renderer ?? ((canvas) => webglRendererFactory(canvas)),
1623
+ pixelRatio: options.pixelRatio ?? defaultPixelRatio,
1624
+ behaviours: options.behaviours ?? {},
1625
+ physics: Promise.resolve(options.physics),
1626
+ labels: options.labels,
1627
+ assets,
1628
+ xr: makeXr(xrHub),
1629
+ xrHub,
1630
+ dispose: () => {
1631
+ assets.dispose();
1632
+ store.dispose();
1633
+ }
1634
+ };
1635
+ };
1636
+ // src/mount.ts
1637
+ import {
1638
+ createGatedFrames,
1639
+ FrameSource,
1640
+ ResourceRegistry,
1641
+ SceneRuntime,
1642
+ SceneSchema as SceneSchema2
1643
+ } from "@opsydyn/effect-three";
1644
+ import { Data as Data3, Effect as Effect3, Option, Queue, Schema as Schema2, Stream } from "effect";
1645
+ import { Mount } from "foldkit";
1646
+
1647
+ // src/dom/canvas.ts
1648
+ var appendCanvas = (host) => {
1649
+ const canvas = host.ownerDocument.createElement("canvas");
1650
+ canvas.style.display = "block";
1651
+ canvas.style.width = "100%";
1652
+ canvas.style.height = "100%";
1653
+ canvas.style.touchAction = "none";
1654
+ host.append(canvas);
1655
+ return canvas;
1656
+ };
1657
+ var dispatchSceneEvent = (element, event) => {
1658
+ element.dispatchEvent(new CustomEvent(domEventName[event._tag], { detail: event }));
1659
+ };
1660
+ var dispatchSceneError = (element, error) => {
1661
+ element.dispatchEvent(new CustomEvent("scene-error", { detail: { error } }));
1662
+ };
1663
+ var canvasIn = (host) => () => appendCanvas(host);
1664
+ var removal = (node) => () => node.remove();
1665
+ var sceneEventDispatch = (element, live) => (event) => () => {
1666
+ if (live.isOpen())
1667
+ dispatchSceneEvent(element, event);
1668
+ };
1669
+ var sceneErrorDispatch = (element) => (error) => () => dispatchSceneError(element, error);
1670
+ var reconciledDispatch = (element, store) => (ops) => () => {
1671
+ if (ops.length === 0)
1672
+ return;
1673
+ element.dispatchEvent(new CustomEvent("scene-reconciled", { detail: summarise(ops, store.stats()) }));
1674
+ };
1675
+
1676
+ // src/dom/latch.ts
1677
+ var createLatch = (open) => {
1678
+ let current = open;
1679
+ return {
1680
+ isOpen: () => current,
1681
+ set: (next) => {
1682
+ current = next;
1683
+ }
1684
+ };
1685
+ };
1686
+ var nothing = [];
1687
+ var routedWhileOpen = (latch, route) => (value) => {
1688
+ if (!latch.isOpen())
1689
+ return nothing;
1690
+ return route(value);
1691
+ };
1692
+ var applying = (f, value) => () => f(value);
1693
+
1694
+ // src/dom/overlays.ts
1695
+ var overlayData = {
1696
+ key: "effect-three-overlay",
1697
+ attribute: "data-effect-three-overlay"
1698
+ };
1699
+ var transformOf2 = (placement) => `translate3d(${placement.x}px,${placement.y}px,0) scale(${placement.scale})${placement.center ? " translate(-50%,-50%)" : ""}`;
1700
+ var write = (element, placement) => {
1701
+ const { style } = element;
1702
+ const transform = transformOf2(placement);
1703
+ if (style.transform !== transform)
1704
+ style.transform = transform;
1705
+ const zIndex = String(placement.zIndex);
1706
+ if (style.zIndex !== zIndex)
1707
+ style.zIndex = zIndex;
1708
+ const visibility = placement.visible ? "visible" : "hidden";
1709
+ if (style.visibility !== visibility)
1710
+ style.visibility = visibility;
1711
+ };
1712
+ var createOverlayWriter = (host) => {
1713
+ let latest = [];
1714
+ const rewrite = () => {
1715
+ for (const placement of latest) {
1716
+ const element = host.querySelector(`[${overlayData.attribute}="${CSS.escape(placement.key)}"]`);
1717
+ if (element instanceof HTMLElement)
1718
+ write(element, placement);
1719
+ }
1720
+ };
1721
+ return {
1722
+ place: (placements) => {
1723
+ latest = placements;
1724
+ rewrite();
1725
+ },
1726
+ rewrite
1727
+ };
1728
+ };
1729
+ var containOverlays = (host) => () => {
1730
+ if (!(host instanceof HTMLElement))
1731
+ return;
1732
+ if (getComputedStyle(host).position === "static")
1733
+ host.style.position = "relative";
1734
+ };
1735
+
1736
+ // src/dom/property.ts
1737
+ var interceptProperty = (element, key, onSet) => {
1738
+ const initial = Reflect.get(element, key);
1739
+ let current = initial;
1740
+ Object.defineProperty(element, key, {
1741
+ configurable: true,
1742
+ enumerable: true,
1743
+ get: () => current,
1744
+ set: (value) => {
1745
+ current = value;
1746
+ onSet(value);
1747
+ }
1748
+ });
1749
+ return {
1750
+ initial,
1751
+ restore: () => {
1752
+ Object.defineProperty(element, key, {
1753
+ configurable: true,
1754
+ enumerable: true,
1755
+ writable: true,
1756
+ value: current
1757
+ });
1758
+ }
1759
+ };
1760
+ };
1761
+
1762
+ // src/dom/typedScenes.ts
1763
+ var typed = new WeakMap;
1764
+ var markTyped = (scene) => {
1765
+ typed.set(scene, scene);
1766
+ return scene;
1767
+ };
1768
+ var typedScene = (value) => typeof value === "object" && value !== null ? typed.get(value) : undefined;
1769
+
1770
+ // src/dom/updates.ts
1771
+ var afterUpdate = (report, steps) => (value) => () => {
1772
+ report(value)();
1773
+ for (const step of steps)
1774
+ step();
1775
+ };
1776
+
1777
+ // src/dom/xrTarget.ts
1778
+ var createFrameGate = (frames) => {
1779
+ let visible = true;
1780
+ let presenting = false;
1781
+ const apply = () => frames.setOpen(visible && !presenting);
1782
+ return {
1783
+ setVisible: (next) => {
1784
+ visible = next;
1785
+ apply();
1786
+ },
1787
+ setPresenting: (next) => {
1788
+ presenting = next;
1789
+ apply();
1790
+ }
1791
+ };
1792
+ };
1793
+ var dispatchXrSession = (element, live, mode) => {
1794
+ if (live.isOpen())
1795
+ element.dispatchEvent(new CustomEvent("scene-xr", { detail: { mode } }));
1796
+ };
1797
+ var xrTarget = (scene) => ({
1798
+ canvas: scene.canvas,
1799
+ element: scene.element,
1800
+ follow: scene.follow,
1801
+ lend: scene.lend,
1802
+ settings: scene.settings,
1803
+ draw: scene.draw,
1804
+ scene: scene.scene,
1805
+ input: (input) => {
1806
+ if (scene.live.isOpen())
1807
+ scene.input(input);
1808
+ },
1809
+ presenting: (mode) => {
1810
+ scene.gate.setPresenting(mode !== null);
1811
+ if (mode === null)
1812
+ scene.draw();
1813
+ dispatchXrSession(scene.element, scene.live, mode);
1814
+ }
1815
+ });
1816
+
1817
+ // src/mount.ts
1818
+ var decodeUnknownScene = Schema2.decodeUnknownEffect(SceneSchema2.SceneDescription);
1819
+ var decodeScene = (value) => Option.fromNullishOr(typedScene(value)).pipe(Option.match({ onNone: () => decodeUnknownScene(value), onSome: Effect3.succeed }));
1820
+ var isLive = Schema2.is(Schema2.Literal("Live"));
1821
+ var acquireCanvas = (element) => {
1822
+ const create = Effect3.sync(canvasIn(element));
1823
+ return Effect3.acquireRelease(create, (canvas) => Effect3.sync(removal(canvas)));
1824
+ };
1825
+ var acquireSceneUpdates = () => {
1826
+ const create = Queue.sliding(1);
1827
+ return Effect3.acquireRelease(create, Queue.shutdown);
1828
+ };
1829
+ var interception = (element, updates) => () => interceptProperty(element, "scene", (value) => Queue.offerUnsafe(updates, value));
1830
+ var acquireInterception = (element, updates) => {
1831
+ const intercept = Effect3.sync(interception(element, updates));
1832
+ return Effect3.acquireRelease(intercept, (intercepted) => Effect3.sync(intercepted.restore));
1833
+ };
1834
+ var pointerListening = (canvas, onInput, claimsGestures) => () => listenForPointer(canvas, onInput, claimsGestures);
1835
+ var acquirePointer = (canvas, mounted) => {
1836
+ const listen = Effect3.sync(pointerListening(canvas, routedWhileOpen(mounted.live, mounted.handle.pointer), claimsGestures(mounted.handle.graph)));
1837
+ return Effect3.acquireRelease(listen, Effect3.sync);
1838
+ };
1839
+ var xrRegistration = (mounted) => () => mounted.environment.xrHub.register(xrTarget({
1840
+ element: mounted.element,
1841
+ canvas: mounted.canvas,
1842
+ live: mounted.live,
1843
+ gate: mounted.gate,
1844
+ settings: mounted.handle.graph.xr,
1845
+ draw: mounted.handle.drawNow,
1846
+ scene: mounted.handle.graph.scene,
1847
+ follow: mounted.handle.graph.follow,
1848
+ lend: mounted.handle.graph.store.lendGeometry,
1849
+ input: mounted.handle.xrInput
1850
+ }));
1851
+ var acquireXrTarget = (mounted) => {
1852
+ const register = Effect3.sync(xrRegistration(mounted));
1853
+ return Effect3.acquireRelease(register, Effect3.sync);
1854
+ };
1855
+ var connectObserver = (observe) => (queue) => {
1856
+ const connect = Effect3.sync(applying(observe, (value) => Queue.offerUnsafe(queue, value)));
1857
+ return Effect3.acquireRelease(connect, Effect3.sync);
1858
+ };
1859
+ var observed = (observe) => Stream.callback(connectObserver(observe));
1860
+ var applyUpdate = (mounted) => (value) => {
1861
+ const reportError = sceneErrorDispatch(mounted.element);
1862
+ const reportReconciled = afterUpdate(reconciledDispatch(mounted.element, mounted.environment.store), [touchActionSync(mounted.canvas, mounted.handle.graph), mounted.overlays.rewrite]);
1863
+ const decoded = decodeScene(value);
1864
+ const applied = decoded.pipe(Effect3.flatMap(mounted.handle.update), Effect3.flatMap((ops) => Effect3.sync(reportReconciled(ops))));
1865
+ return applied.pipe(Effect3.catchTags({
1866
+ SchemaError: (error) => Effect3.sync(reportError({ _tag: "MalformedScene", issue: error.message })),
1867
+ InvalidScene: ({ error }) => Effect3.sync(reportError(error))
1868
+ }));
1869
+ };
1870
+ var resize = (mounted) => () => mounted.handle.resize(measureViewport(mounted.element, mounted.environment.host.devicePixelRatio, mounted.environment.pixelRatio));
1871
+ var watchers = (mounted, updates, viewStateChanges) => {
1872
+ const dispatchEvent = sceneEventDispatch(mounted.element, mounted.live);
1873
+ const sizes = observed((emit) => observeSize(mounted.element, emit));
1874
+ const ratios = observed((emit) => observePixelRatio(mounted.environment.host, emit));
1875
+ const visibility = observed((emit) => observeVisibility(mounted.element, emit));
1876
+ const reducedMotion = observed((emit) => observeReducedMotion(mounted.environment.host, emit));
1877
+ return [
1878
+ Stream.runForEach(Stream.fromQueue(updates), applyUpdate(mounted)),
1879
+ Stream.runForEach(mounted.handle.events, (event) => Effect3.sync(dispatchEvent(event))),
1880
+ Stream.runForEach(viewStateChanges, (state) => Effect3.sync(applying(mounted.live.set, isLive(state)))),
1881
+ Stream.runForEach(sizes, resize(mounted)),
1882
+ Stream.runForEach(ratios, resize(mounted)),
1883
+ Stream.runForEach(visibility, (visible) => Effect3.sync(applying(mounted.gate.setVisible, visible))),
1884
+ Stream.runForEach(reducedMotion, (reduced) => mounted.handle.setAnimating(!reduced))
1885
+ ];
1886
+ };
1887
+
1888
+ class PhysicsUnavailable extends Data3.TaggedError("PhysicsUnavailable") {
1889
+ }
1890
+ var sceneRuntimeFor = (environment, element, canvas, overlays, physics) => (scene) => {
1891
+ const frames = createGatedFrames(environment.frames);
1892
+ const started = SceneRuntime.make({
1893
+ scene,
1894
+ renderer: environment.renderer(canvas),
1895
+ behaviours: environment.behaviours,
1896
+ physics,
1897
+ labels: environment.labels,
1898
+ placeOverlays: overlays.place,
1899
+ viewport: measureViewport(element, environment.host.devicePixelRatio, environment.pixelRatio)
1900
+ }).pipe(Effect3.provideService(FrameSource.FrameSource, FrameSource.fromScheduler(frames)), Effect3.provideService(ResourceRegistry.ResourceRegistry, { store: environment.store }));
1901
+ return Effect3.map(started, (handle) => ({ handle, frames }));
1902
+ };
1903
+ function* runScene(environment, element, viewStateChanges) {
1904
+ const canvas = yield* acquireCanvas(element);
1905
+ const updates = yield* acquireSceneUpdates();
1906
+ const intercepted = yield* acquireInterception(element, updates);
1907
+ const initial = yield* decodeScene(intercepted.initial);
1908
+ const overlays = createOverlayWriter(element);
1909
+ yield* Effect3.sync(containOverlays(element));
1910
+ const physics = yield* Effect3.tryPromise({
1911
+ try: () => environment.physics,
1912
+ catch: (cause) => new PhysicsUnavailable({ cause })
1913
+ });
1914
+ const { handle, frames } = yield* sceneRuntimeFor(environment, element, canvas, overlays, physics)(initial);
1915
+ const mounted = {
1916
+ environment,
1917
+ element,
1918
+ canvas,
1919
+ overlays,
1920
+ handle,
1921
+ frames,
1922
+ gate: createFrameGate(frames),
1923
+ live: createLatch(true)
1924
+ };
1925
+ yield* acquirePointer(canvas, mounted);
1926
+ yield* acquireXrTarget(mounted);
1927
+ yield* Effect3.sync(touchActionSync(canvas, handle.graph));
1928
+ yield* Effect3.sync(reconciledDispatch(element, environment.store)(handle.startupOps));
1929
+ const forks = watchers(mounted, updates, viewStateChanges).map((watcher) => Effect3.forkScoped(watcher));
1930
+ yield* Effect3.all(forks, { concurrency: "unbounded" });
1931
+ return SceneLifecycle.StartedScene();
1932
+ }
1933
+ var failure = (reason) => SceneLifecycle.FailedScene({ reason });
1934
+ var recoverStart = {
1935
+ SchemaError: (error) => Effect3.succeed(failure(`malformed scene: ${error.message}`)),
1936
+ InvalidScene: ({ error }) => Effect3.succeed(failure(`invalid scene: ${error._tag}`)),
1937
+ RendererUnavailable: () => Effect3.succeed(failure("renderer unavailable")),
1938
+ PhysicsUnavailable: () => Effect3.succeed(failure("physics unavailable"))
1939
+ };
1940
+ var mountScene = (environment) => ({ element, viewStateChanges }) => {
1941
+ const scene = Effect3.gen(() => runScene(environment, element, viewStateChanges));
1942
+ return scene.pipe(Effect3.catchTags(recoverStart));
1943
+ };
1944
+ var makeSceneMount = (environment) => Mount.define("EffectThreeScene", {
1945
+ messages: [SceneLifecycle.StartedScene, SceneLifecycle.FailedScene],
1946
+ execute: mountScene(environment)
1947
+ });
1948
+ // src/view.ts
1949
+ import { Array as Arr, Option as Option2 } from "effect";
1950
+ import { Mount as Mount2 } from "foldkit";
1951
+
1952
+ // src/overlayView.ts
1953
+ var withOverlays = (scene, overlays) => overlays.length === 0 ? scene : { ...scene, overlays: [...scene.overlays, ...overlays.map((view) => view.overlay)] };
1954
+ var layerStyle = {
1955
+ position: "absolute",
1956
+ inset: "0",
1957
+ overflow: "hidden",
1958
+ pointerEvents: "none",
1959
+ isolation: "isolate"
1960
+ };
1961
+ var overlayLayer = (h, overlays) => overlays.length === 0 ? [] : [
1962
+ h.div([h.Style(layerStyle)], overlays.map(({ overlay, content, pointerEvents = "auto" }) => h.keyed("div")(overlay.key, [
1963
+ h.DataAttribute(overlayData.key, overlay.key),
1964
+ h.Style({
1965
+ position: "absolute",
1966
+ top: "0",
1967
+ left: "0",
1968
+ transformOrigin: "0 0",
1969
+ visibility: "hidden",
1970
+ pointerEvents
1971
+ })
1972
+ ], [content])))
1973
+ ];
1974
+
1975
+ // src/view.ts
1976
+ var given = Option2.fromNullishOr;
1977
+ var view = (config, h) => {
1978
+ const element = sceneElement.withMessage(h);
1979
+ const optional = Arr.getSomes([
1980
+ Option2.map(given(config.onClick), element.OnSceneClick),
1981
+ Option2.map(given(config.onDoubleClick), element.OnSceneDoubleClick),
1982
+ Option2.map(given(config.onContextMenu), element.OnSceneContextMenu),
1983
+ Option2.map(given(config.onPointerDown), element.OnScenePointerDown),
1984
+ Option2.map(given(config.onPointerUp), element.OnScenePointerUp),
1985
+ Option2.map(given(config.onPointerEnter), element.OnScenePointerEnter),
1986
+ Option2.map(given(config.onPointerMove), element.OnScenePointerMove),
1987
+ Option2.map(given(config.onPointerLeave), element.OnScenePointerLeave),
1988
+ Option2.map(given(config.onPointerMissed), element.OnScenePointerMissed),
1989
+ Option2.map(given(config.onWheel), element.OnSceneWheel),
1990
+ Option2.map(given(config.onDrag), element.OnSceneDrag),
1991
+ Option2.map(given(config.onDragEnd), element.OnSceneDragEnd),
1992
+ Option2.map(given(config.onCollision), element.OnSceneCollision),
1993
+ Option2.map(given(config.onContactForce), element.OnSceneContactForce),
1994
+ Option2.map(given(config.onMeasured), element.OnSceneMeasured),
1995
+ Option2.map(given(config.onLabelMeasured), element.OnSceneLabelMeasured),
1996
+ Option2.map(given(config.onXRSession), element.OnSceneXr),
1997
+ Option2.map(given(config.onXRStick), element.OnSceneXrStick),
1998
+ Option2.map(given(config.onXRButton), element.OnSceneXrButton),
1999
+ Option2.map(given(config.onXRSurface), element.OnSceneXrSurface),
2000
+ Option2.map(given(config.onXRMoved), element.OnSceneXrMoved),
2001
+ Option2.map(given(config.onXRPlanes), element.OnSceneXrPlanes),
2002
+ Option2.map(given(config.onXRMeshes), element.OnSceneXrMeshes),
2003
+ Option2.map(given(config.onAutoRotated), element.OnSceneAutoRotated),
2004
+ Option2.map(given(config.onGesture), (toMessage) => element.OnSceneGesture((detail) => toMessage(detail.gesture))),
2005
+ Option2.map(given(config.onPerformanceChange), element.OnScenePerformance),
2006
+ Option2.map(given(config.onError), element.OnSceneError),
2007
+ Option2.map(given(config.onReconciled), element.OnSceneReconciled),
2008
+ Option2.map(given(config.className), h.Class)
2009
+ ]);
2010
+ const lifecycle = Mount2.mapMessage(config.mount(), config.onLifecycle);
2011
+ const overlays = config.overlays ?? [];
2012
+ const scene = markTyped(withOverlays(config.scene, overlays));
2013
+ return element([element.Scene(scene), h.OnMount(lifecycle), ...optional], overlayLayer(h, overlays));
2014
+ };
2015
+ export {
2016
+ AssetLoadError,
2017
+ ReconciledDetail,
2018
+ SceneError,
2019
+ SceneLifecycle,
2020
+ XrSessionDetail,
2021
+ XrUnavailable,
2022
+ animationFrameScheduler,
2023
+ capturesPointer,
2024
+ claimsContextMenu,
2025
+ defaultPixelRatio,
2026
+ domEventName,
2027
+ makeAssets,
2028
+ makeEnvironment,
2029
+ makeSceneMount,
2030
+ resolvePixelRatio,
2031
+ sceneElement,
2032
+ summarise,
2033
+ toNdc,
2034
+ toPointerInput,
2035
+ view,
2036
+ webglRendererFactory,
2037
+ webgpuRendererFactory
2038
+ };
2039
+
2040
+ //# debugId=4118984BABDF337D64756E2164756E21
2041
+ //# sourceMappingURL=index.js.map