@opsydyn/effect-three 0.0.0-stage → 0.1.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 +58 -0
  2. package/LICENSE +21 -0
  3. package/README.md +191 -2
  4. package/dist/index-4045wme0.js +129 -0
  5. package/dist/index-4045wme0.js.map +10 -0
  6. package/dist/index-ef8pbv9g.js +5118 -0
  7. package/dist/index-ef8pbv9g.js.map +40 -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 +45 -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 +48 -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 +48 -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 +53 -0
  144. package/src/kernel/renderer.ts +136 -0
  145. package/src/kernel/resources.ts +504 -0
  146. package/src/kernel/sceneGraph.ts +1440 -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
package/CHANGELOG.md ADDED
@@ -0,0 +1,58 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-10-18)
4
+
5
+ ### Features
6
+
7
+ - **effect-three-foldkit:** Foldkit adapter with WebGL renderer and browser tests ([5e2816d](https://github.com/opsydyn/fold-kit-experiments/commit/5e2816d4a46138777db0cb656a9c3436328c8be4))
8
+ - **effect-three:** a rigid body per instance ([cc39435](https://github.com/opsydyn/fold-kit-experiments/commit/cc39435b878c781c7bf35e00e4b50ddc6d92b035))
9
+ - **effect-three:** a WebGPU renderer ([94a0964](https://github.com/opsydyn/fold-kit-experiments/commit/94a0964c723c9c8bea555a0054d52c2224fcc00c))
10
+ - **effect-three:** after R5 — WebGPU, render kits, glide, physics groups, XR spaces ([e5bde71](https://github.com/opsydyn/fold-kit-experiments/commit/e5bde71212353879d74aa32567c3eb9cbbeb38ac))
11
+ - **effect-three:** animation clips on borrowed objects ([719e73e](https://github.com/opsydyn/fold-kit-experiments/commit/719e73ed8053ba60ff1ecf7902d89735c1d74e31))
12
+ - **effect-three:** basic, lambert, phong, standard, physical and normal materials ([5baa690](https://github.com/opsydyn/fold-kit-experiments/commit/5baa690ef51dce7c1ae9ea53cfc0b6c70b9565c3))
13
+ - **effect-three:** benchmarks, API reports, package checks and CI browser tests ([5640b2d](https://github.com/opsydyn/fold-kit-experiments/commit/5640b2dc2a078642f15f157fa77b491e1443305d))
14
+ - **effect-three:** bodies on groups and loaded models ([b06179c](https://github.com/opsydyn/fold-kit-experiments/commit/b06179c4baa878fb65dc2f15a43a3e275b7bf682))
15
+ - **effect-three:** borrowed objects and lent textures ([6e4c010](https://github.com/opsydyn/fold-kit-experiments/commit/6e4c010bc21da24aadea61bee76b3a39963cda4a))
16
+ - **effect-three:** camera auto-rotate, and damping for view cameras ([c563b69](https://github.com/opsydyn/fold-kit-experiments/commit/c563b697753d8d2c0eed05ecfc935ad0d48f693a))
17
+ - **effect-three:** close the parity gaps and prepare the packages for npm ([994fe5d](https://github.com/opsydyn/fold-kit-experiments/commit/994fe5dd640583c787746a95e8465d8f051d78df))
18
+ - **effect-three:** colliders of a body's own, motors, contact forces and debug lines ([2a16c9e](https://github.com/opsydyn/fold-kit-experiments/commit/2a16c9e0f8432872e008bbd747eede1472c6ba44))
19
+ - **effect-three:** collision groups and colliders named in collisions ([9531a01](https://github.com/opsydyn/fold-kit-experiments/commit/9531a01f54e9e19b8ccc301152167d0ac52c3229))
20
+ - **effect-three:** declarative, diffed instancing ([26f70b3](https://github.com/opsydyn/fold-kit-experiments/commit/26f70b3ea5f20d5d4c33e36d3056bb15d2053334))
21
+ - **effect-three:** demo app, package builds and visual tests ([1649d49](https://github.com/opsydyn/fold-kit-experiments/commit/1649d496c3387eb63233665da4ccc6e1def2aa64))
22
+ - **effect-three:** Drei's shader grid, centring and environment presets ([a9467c7](https://github.com/opsydyn/fold-kit-experiments/commit/a9467c7580e24c0e93ee6742bb71ebd1da08a5e5))
23
+ - **effect-three:** eased transitions and sphere, plane and cylinder geometry ([26187f1](https://github.com/opsydyn/fold-kit-experiments/commit/26187f12e8ffed91993802c0128e2be03ea3ba41))
24
+ - **effect-three:** Effect runtime with scoped scenes, frame source and pointer events ([e7dec61](https://github.com/opsydyn/fold-kit-experiments/commit/e7dec61ce9fa57594fe606f9453a3115566a4cf8))
25
+ - **effect-three:** environment lighting and a shadow catcher ([023d2de](https://github.com/opsydyn/fold-kit-experiments/commit/023d2dedc8ab7b6a46a6ef23a1dc0f364af80816))
26
+ - **effect-three:** event bubbling paths, interactive groups and drag capture ([546a946](https://github.com/opsydyn/fold-kit-experiments/commit/546a94631923532bf5c11ff5b4ad7b19be64195e))
27
+ - **effect-three:** every hit carries the pointer's world-space ray ([a7b3a46](https://github.com/opsydyn/fold-kit-experiments/commit/a7b3a46aa09eecaa04d7c73158b5fbf388a12cb6))
28
+ - **effect-three:** frame-rate monitoring and render resolution ([64f9027](https://github.com/opsydyn/fold-kit-experiments/commit/64f902794d58bc5ef7bfbb19c0e2cbfa753e6f8b))
29
+ - **effect-three:** full pointer event set with R3F semantics ([d9c6366](https://github.com/opsydyn/fold-kit-experiments/commit/d9c6366931c5e6c7414ce3a4a04bd1ef8da6b019))
30
+ - **effect-three:** headless core kernel with property-tested keyed reconciliation ([a579695](https://github.com/opsydyn/fold-kit-experiments/commit/a5796951fc9545236a360ad62dc50c523c017b8c))
31
+ - **effect-three:** HTML overlays at points in the scene ([d0d016f](https://github.com/opsydyn/fold-kit-experiments/commit/d0d016f1896eb05927f154b0941662b7afc6112e))
32
+ - **effect-three:** immersive sessions (WebXR) ([9e003d1](https://github.com/opsydyn/fold-kit-experiments/commit/9e003d125813e2ce72477fd544242bc5b03cac73))
33
+ - **effect-three:** joints, and bodies no longer remade by rounding ([66ebf4d](https://github.com/opsydyn/fold-kit-experiments/commit/66ebf4d0a4bcd580699e9a7f371919397e95585c))
34
+ - **effect-three:** label colour ranges, curves, materials and layout ([ad9942e](https://github.com/opsydyn/fold-kit-experiments/commit/ad9942e49a540ab346a62fa00481c8c4d40ede11))
35
+ - **effect-three:** lent geometries and XR detected meshes ([832388b](https://github.com/opsydyn/fold-kit-experiments/commit/832388bbe6833d248d1d437f01c6e1a31aba08ce))
36
+ - **effect-three:** multiple views into one canvas ([b732a7c](https://github.com/opsydyn/fold-kit-experiments/commit/b732a7c4b6ddb50f846f71d4fa4401786a76d2a3))
37
+ - **effect-three:** orbit controls as data ([067af99](https://github.com/opsydyn/fold-kit-experiments/commit/067af99a443c1c3000bfb3e424ae43e5995366d0))
38
+ - **effect-three:** orthographic camera ([ac05a7e](https://github.com/opsydyn/fold-kit-experiments/commit/ac05a7e60307150e7707533b9ee14457ec5110ab))
39
+ - **effect-three:** physics as data, simulated by Rapier ([dd99e1c](https://github.com/opsydyn/fold-kit-experiments/commit/dd99e1cbde789e5b578ec6b28278893e5725b326))
40
+ - **effect-three:** post-processing effects ([1e2aa60](https://github.com/opsydyn/fold-kit-experiments/commit/1e2aa60533696b4db9158fd3de5d978bc601a063))
41
+ - **effect-three:** registered behaviours as data ([68eef53](https://github.com/opsydyn/fold-kit-experiments/commit/68eef530b7f43cb59ac45822064b0d1518887cc4))
42
+ - **effect-three:** render frames on demand ([9df4a06](https://github.com/opsydyn/fold-kit-experiments/commit/9df4a06adc9493a64fd0ef183e562c06568285fd))
43
+ - **effect-three:** render textures as data ([cbc5f3d](https://github.com/opsydyn/fold-kit-experiments/commit/cbc5f3d54a844ea623c28b9223a0561586771737))
44
+ - **effect-three:** scaffold Effect-native Three.js packages with Phase 0 ADRs ([02fa947](https://github.com/opsydyn/fold-kit-experiments/commit/02fa9476801e8609e311174023016b262e108427))
45
+ - **effect-three:** shadows and scene render settings ([4140595](https://github.com/opsydyn/fold-kit-experiments/commit/41405952fbe709ab6453f51b9cfd53b7e1adb40e))
46
+ - **effect-three:** sky and grid staging ([03e9f4f](https://github.com/opsydyn/fold-kit-experiments/commit/03e9f4f5c81a6dbe2a0e29720f768380ff86e737))
47
+ - **effect-three:** stage lighting as scene parts ([8686f13](https://github.com/opsydyn/fold-kit-experiments/commit/8686f13da31425c49e8d9f73e0baf78b0ce82eb2))
48
+ - **effect-three:** text geometry from lent typeface fonts ([c573453](https://github.com/opsydyn/fold-kit-experiments/commit/c573453ae3acef5356a184eb3030495ed08389af))
49
+ - **effect-three:** torus, cone, capsule, ring and circle; point, spot and hemisphere lights ([3d695fe](https://github.com/opsydyn/fold-kit-experiments/commit/3d695fe6f9d85ffd70e3762dcdf586ce7cbb94de))
50
+ - **effect-three:** update telemetry, shared samples package and apps/web samples ([f7cd64d](https://github.com/opsydyn/fold-kit-experiments/commit/f7cd64da19496e6137d8daa669ada99cae01d546))
51
+ - **effect-three:** XR controllers, and VR in the toybox ([861f008](https://github.com/opsydyn/fold-kit-experiments/commit/861f0082819ad3b1898be9e86dd9c537de637cee))
52
+ - **effect-three:** XR hover and drag, hands, sticks, AR surfaces ([35597b9](https://github.com/opsydyn/fold-kit-experiments/commit/35597b937b2721b823040b2c67468d6ab9224fd0))
53
+ - **effect-three:** XR spaces, anchors, planes and DOM overlay ([0041c7c](https://github.com/opsydyn/fold-kit-experiments/commit/0041c7c9ebbe066f2c2300ae23283dfa68cc7451))
54
+ - **effect-three:** zoom to cursor, arrow keys and camera fitting ([e14224b](https://github.com/opsydyn/fold-kit-experiments/commit/e14224b5dc11ac1d371ce1061ad41ca73879fc6f))
55
+
56
+ ### Performance Improvements
57
+
58
+ - **effect-three:** reconcile 1,000 unchanged meshes in about 95 µs ([1069d8a](https://github.com/opsydyn/fold-kit-experiments/commit/1069d8a1a9cbd344eb6d59fd6dc290d5c436f121))
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alan P Currie
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,192 @@
1
- # Temporary Holding Version
1
+ # @opsydyn/effect-three
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Declarative Three.js scenes with Effect owning every lifetime: GPU resources, renderers, frame subscriptions and event queues are acquired in a `Scope` and released when it closes. Scenes are plain data, so they can be derived from an application model, diffed and tested without a browser.
4
+
5
+ The core is independent of any UI framework and of the DOM. For Foldkit, use [`@opsydyn/effect-three-foldkit`](../effect-three-foldkit/README.md).
6
+
7
+ > Status: `0.x` (see [Stability](#stability)). The API is documented in [`etc/effect-three.api.md`](etc/effect-three.api.md).
8
+
9
+ ```sh
10
+ npm install @opsydyn/effect-three effect three
11
+ ```
12
+
13
+ ## Describe a scene
14
+
15
+ ```ts
16
+ import {
17
+ ambientLight,
18
+ box,
19
+ directionalLight,
20
+ mesh,
21
+ scene,
22
+ sharedGeometry,
23
+ sharedMaterial,
24
+ spin,
25
+ standard,
26
+ } from '@opsydyn/effect-three';
27
+
28
+ const description = scene({
29
+ resources: {
30
+ geometries: [{ id: 'cube', geometry: box() }],
31
+ materials: [{ id: 'paint', material: standard({ color: 0x14b8a6 }) }],
32
+ },
33
+ children: [
34
+ ambientLight({ key: 'ambient', intensity: 0.4 }),
35
+ directionalLight({ key: 'sun', position: [3, 4, 5] }),
36
+ mesh({
37
+ key: 'cube',
38
+ geometry: sharedGeometry('cube'),
39
+ material: sharedMaterial('paint'),
40
+ rotation: spin({ axis: 'y', radiansPerSecond: 1 }),
41
+ interactive: true,
42
+ }),
43
+ ],
44
+ });
45
+ ```
46
+
47
+ - **Keys are unique across the whole scene.** A node keeps its three.js object while its key and kind stay the same, including when it moves to another group. A different kind under the same key replaces the object in place.
48
+ - **Descriptions are fully specified data.** The constructors fill in defaults, and nothing in a description is a closure or a three.js object.
49
+ - **Shapes:** `box`, `sphere`, `plane`, `cylinder`, `torus`, `cone`, `capsule`, `ring` and `circle`, owned by one mesh or shared through `resources`. Parameter names and defaults are three.js's own.
50
+ - **Materials:** `basic`, `lambert`, `phong`, `standard`, `physical` and `normalMaterial`, each with opacity, transparency, `side`, wireframe and (lit kinds) flat shading and emission. Defaults are three.js's own. Changing a material within its kind updates it in place; changing its kind replaces it.
51
+ - **Shadows and render settings:** `scene({ render: renderSettings({ shadows: 'soft', toneMapping: 'aces' }) })`, then `castShadow`/`receiveShadow` on meshes and `castShadow` on directional, point and spot lights. Tone mapping defaults to `none` (React Three Fiber defaults to ACES) so colours render exactly as given.
52
+ - **Instancing:** `instances({ key, geometry, material, instances: [instance({ key, position, color })] })` draws many copies in one call. Updates rewrite only the instances that changed, and hits name the instance (`instance` on the event).
53
+ - **Views:** `scene({ views: [view({ key: 'front', camera, rect: { width: 0.5 } }), ...] })` draws several cameras into parts of one canvas; picks use the view under the pointer and report it as `view`.
54
+ - **Post-processing:** `renderSettings({ effects: [bloom({ strength: 1.5 }), vignette()] })` applies three.js's composer passes in order (`bloom`, `vignette`, `film`); with no effects the scene renders straight to the screen.
55
+ - **Render textures:** `scene({ renderTextures: [renderTexture({ id: 'screen', camera, children })] })` draws a little scene of its own into a texture that any material uses with `map: 'screen'`; it redraws only when its scene changes.
56
+ - **Orbit controls:** `scene({ gestures: 'scene', camera: model.camera })` claims drags, wheel and pinches from the page and reports each as a `Gesture`; `orbit(model.camera, gesture)` returns the next camera, as three.js's `OrbitControls` would move it. A camera's `damping` eases the rendered camera there (a view's too), and `autoRotate` turns it round its target until a gesture, which an `AutoRotated` event precedes: keep it with `autoRotated(model.camera, detail)`. See [ADR-006](../../docs/effect-three/adr/006-controls-as-data.md).
57
+ - **Fitting:** `scene({ measure: ['model'] })` reports the node's world box as a `Measured` event whenever it changes; `fitCamera(camera, box, area)` frames it as Drei's `Bounds` does, and `orbitKey` moves the camera for arrow keys.
58
+ - **HTML overlays:** `overlay({ key: 'tag', node: 'cone', position: [0, 1, 0], center: true })` is a point the scene places after every drawn frame, as Drei's `Html`; the Foldkit view's `overlays: [{ overlay, content }]` moves Model-rendered HTML there.
59
+ - **Text:** `text({ text: 'Hello', font: 'inter', depth: 0.2 })` is extruded (or, at depth 0, flat) text from a typeface JSON font lent with `store.lendFont` (`assets.loadFont` in Foldkit), as Drei's `Text3D`, with `letterSpacing`, `lineHeight` and `align` applied (three ignores them).
60
+ - **Labels:** `label({ key: 'title', text: 'Hello', font: '/inter.woff', fontSize: 0.5 })` is crisp distance-field text at any size, as Drei's `Text`: wrapping (`maxWidth`), anchors, alignment, spacing, outlines, coloured runs (`colorRanges`), curving (`curveRadius`) and a scene `material` to light it; measured (`scene({ measure })`), it reports its laid-out size as `LabelMeasured`. Pass an engine (`troikaLabels()` from `@opsydyn/effect-three-troika`); fonts are TTF, OTF or WOFF (troika 0.52 cannot read WOFF2).
61
+ - **Staging:** `scene({ environment: environment(), sky: sky() })` adds image-based lighting (three's `RoomEnvironment`, or a lent panorama with `map`) and a daylight sky, as Drei's `Environment` and `Sky`; `grid({ key })` draws three's `GridHelper`, `contactShadows()` draws Drei's soft ground shadows, and `shadowMaterial()` shows only the shadows cast onto it.
62
+ - **Stage:** `const { lights, contactShadows, environment } = stage({ radius: 1.2, height: 2 })` is Drei's `Stage` lighting (presets `rembrandt`, `portrait`, `upfront`, `soft`) for content centred on the origin; spread the parts into `scene`. Measure the content (`scene({ measure })`), place it at `centerOffset(box)` (Drei's `Center`), light it with `stage(stageBounds(box))` and frame it with `fitCamera`.
63
+ - **Grids and presets:** `shaderGrid({ key, infinite: true })` is Drei's fading shader `Grid`; `environmentPresetUrl('sunset')` is one of Drei's ten environment HDRs, for `assets.loadEnvironment`.
64
+ - **Physics:** `mesh({ body: body({ collider: 'ball', restitution: 0.6 }) })` under `scene({ physics: physics() })` makes a rigid body the simulation moves; kicks are `impulse: { id, impulse }`, applied once per id, and collisions arrive as `Collision` events. Bodies sit on groups, models and instances too, take colliders of their own (`colliders: [capsuleCollider(…)]`) and report `ContactForce` over a threshold; joints (with motors) go in `physics({ joints })`, and `physics({ debug: true })` draws the colliders. Pass an engine (`loadRapier()` from `@opsydyn/effect-three-rapier`) to the scene graph or the Foldkit environment. See [ADR-007](../../docs/effect-three/adr/007-physics-as-a-pose-source.md).
65
+ - **XR:** `scene({ xr: xr({ origin: [0, 0, 4] }) })` lets a scene be presented in a WebXR session; in Foldkit, run `environment.xr.enter('immersive-vr')` from a click's Command. The session drives frames, the player stands at `origin`, and controllers and hands hover, click and drag along their rays. Thumbsticks arrive as `XrStick` events (`locomote(model.xr, stick)` snap-turns and steps), buttons as `XrButton`, and in AR with `xr({ hitTest: true })` a select reports the real surface it met (`XrSurface`). `xr({ glide })` glides with the sticks and reports `XrMoved`. `group({ follow: jointSpace('right', 'index-finger-tip') })` rides a tracked space (also `headSpace()`, `controllerSpace(hand)`, `anchorSpace(id)`, `planeSpace(id)`), hidden while it is not tracked; `xr({ anchors: [xrAnchor({ id, position })] })` anchors places in the room, `xr({ planes: true })` reports detected planes as `XrPlanes`, `xr({ meshes: true })` detected meshes as `XrMeshes` (draw one with `mesh({ geometry: xrMesh(id) })` in a group following `meshSpace(id)`), and `xr({ domOverlay: true })` overlays the scene's element. See [ADR-008](../../docs/effect-three/adr/008-immersive-sessions.md).
66
+ - **Cameras:** `perspectiveCamera({ fov })` or `orthographicCamera({ zoom })`. As in React Three Fiber, an orthographic frustum follows the viewport in CSS pixels, with `zoom` in pixels per world unit.
67
+ - **Lights:** `ambientLight`, `directionalLight`, `pointLight`, `spotLight` (aimed at a world-space `target`) and `hemisphereLight`, with three.js defaults.
68
+
69
+ ## Run it
70
+
71
+ ```ts
72
+ import { Effect, Layer } from 'effect';
73
+ import { FrameSource, ResourceRegistry, SceneRuntime } from '@opsydyn/effect-three';
74
+
75
+ const program = Effect.gen(function* () {
76
+ const handle = yield* SceneRuntime.make({ scene: description, renderer, viewport });
77
+ yield* handle.update(nextDescription); // diff and apply; equal scenes cost no host changes
78
+ handle.pointer({ _tag: 'PointerDown', x: 0, y: 0, button: 0 }); // normalised device coords
79
+ // handle.events: Stream of PointerEnter | PointerMove | PointerLeave | PointerDown | PointerUp
80
+ // | Click | DoubleClick | ContextMenu | Wheel | PointerMissed | Drag | DragEnd | Gesture
81
+ }).pipe(Effect.scoped);
82
+
83
+ const environment = Layer.mergeAll(ResourceRegistry.layer, FrameSource.layer(scheduler));
84
+ ```
85
+
86
+ - `renderer` is a `RendererFactory`: a plain object whose `create()` returns a renderer bound to one drawing surface. The WebGL2 implementation lives in the browser adapter. A WebGPU one can be added without touching the core.
87
+ - `FrameSource.layer(scheduler)` takes any `FrameScheduler`: `requestAnimationFrame`, three's `setAnimationLoop`, or `createManualFrames()` in tests.
88
+ - Pointer events follow React Three Fiber's semantics: click, double-click and context menu go only to the mesh that was pressed; a click that hits nothing and moves at most 2 CSS pixels is a `PointerMissed`. Hits carry the key, world-space point, distance, face normal and uv.
89
+ - `make` is atomic. If the description is invalid (`InvalidScene`), the renderer can't be created (`RendererUnavailable`), or startup is interrupted, everything already acquired is released before it returns.
90
+
91
+ ## Resource ownership
92
+
93
+ | Mode | Declared as | Lifetime |
94
+ | ------ | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
95
+ | Owned | `geometry: box(...)`, `material: standard(...)` | Created for one mesh, disposed with it. Owned material changes are applied in place. |
96
+ | Shared | `sharedGeometry(id)`, `sharedMaterial(id)` plus a `resources` definition | Created on first use, reference-counted across every scene using the same `ResourceRegistry`, disposed when the last user releases it. Changing a shared material updates it in place; changing a shared geometry swaps a new one onto every user and disposes the old one. |
97
+
98
+ Borrowed objects, textures and geometries are the application's own: lend them to the store and the library shows them but never disposes them.
99
+
100
+ ```ts
101
+ const withdraw = store.lend('car', gltf.scene); // then: borrowed({ key: 'car', id: 'car' })
102
+ store.lendTexture('bricks', texture); // then: standard({ map: 'bricks' })
103
+ store.lendGeometry('terrain', geometry); // then: mesh({ geometry: lentGeometry('terrain'), ... })
104
+ ```
105
+
106
+ A `borrowed` node may name an id before it is lent, so a description can refer to a model that is still loading; it appears when lent and disappears when withdrawn. Hits inside a borrowed object carry the `part` name of the mesh hit.
107
+
108
+ ## Animation
109
+
110
+ Behaviours own animated properties: a mesh's rotation is either model-owned (`fixed`) or behaviour-owned (`spin`), never both. A behaviour's output is a pure function of scene time, so frames are deterministic and frame-rate independent.
111
+
112
+ Behaviours replace React Three Fiber's `useFrame` with data: a node names one, `behaviour: behave('float', { speed: 2 })`, and the behaviour (a pure function of scene time registered with the scene) adds a position offset, a rotation and a scale factor on top of the node's transform each frame. `float` (Drei's `Float`) is built in; register your own through `SceneRuntime.make({ behaviours })` or `makeEnvironment(window, { behaviours })`:
113
+
114
+ ```ts
115
+ const orbit: Behaviour = (pose, seconds, params) =>
116
+ pose.offset.set(Math.cos(seconds) * (params['radius'] ?? 1), 0, Math.sin(seconds));
117
+ ```
118
+
119
+ Transitions decide how `position` and `scale` reach new values. The Model always owns the target; a transition only shapes the journey:
120
+
121
+ ```ts
122
+ mesh({
123
+ key: 'cube',
124
+ position: [model.column, 0, 0],
125
+ transition: ease({ seconds: 0.35, easing: 'easeOutBack' }), // default: instant()
126
+ geometry: box(),
127
+ material: standard({ color: 0x14b8a6 }),
128
+ });
129
+ ```
130
+
131
+ - A new target mid-flight restarts from the pose on screen, so nothing jumps. A node moving to another group keeps its world position and travels to its new place.
132
+ - Easings: `linear`, `easeInOutCubic` (default), `easeOutCubic`, `easeOutBack`. Tweens run in the frame loop without allocating and never reach `update`.
133
+
134
+ Frames render on demand by default: a scene draws after any change and while anything animates, then stops requesting frames until the next change, so nothing ever calls `invalidate`. `render: renderSettings({ frames: 'always' })` draws every display frame, as React Three Fiber's default does; `frames: 'never'` draws only on `handle.advance`.
135
+
136
+ When frames run persistently slow, the scene emits `PerformanceChanged { fps, factor }` (Drei's `PerformanceMonitor` algorithm). Store the factor in the Model and pass it back as `renderSettings({ resolution })` to lower the pixel ratio until frames recover.
137
+
138
+ `handle.setAnimating(false)` freezes scene time (for `prefers-reduced-motion`) while frames keep rendering; transitions then land at once, including any in flight.
139
+
140
+ ## Testing
141
+
142
+ Everything needed for deterministic headless tests ships with the package:
143
+
144
+ - `recordingRendererFactory()`: counts renders and records viewport and disposal.
145
+ - `createManualFrames()`: frames run when you call `step()`.
146
+ - Effect's `TestClock`: scene time comes from the `Clock` service's monotonic reading.
147
+ - **Renderers:** `webglRendererFactory` (the default) or `webgpuRendererFactory` (three's `WebGPURenderer`: WebGPU where available, its WebGL 2 backend otherwise), each carrying its render kit (`@opsydyn/effect-three/webgl` or `/webgpu`). Labels need WebGL. See [ADR-010](../../docs/effect-three/adr/010-webgpu-renderer.md).
148
+ - `createSceneGraph()`: the synchronous kernel on its own, with `object(key)` and resource `stats()` for assertions. Pass `kit: webglKit` (from `@opsydyn/effect-three/webgl`) to draw skies, shader grids and contact shadows; `webglRendererFactory` carries it for you.
149
+
150
+ ## Stability
151
+
152
+ `0.x`. The four packages (`@opsydyn/effect-three`, `-foldkit`, `-rapier`, `-troika`) are
153
+ released together at one version, and each adapter needs the core at exactly its own
154
+ version. The public surface is what the API reports in `etc/` record, checked in CI: a
155
+ change that breaks it bumps the minor version (0.1 to 0.2); additions and fixes bump the
156
+ patch.
157
+
158
+ ## Limits
159
+
160
+ - **WebXR on `WebGPURenderer`** is wired but untested: the headset emulator the tests use
161
+ (IWER) cannot render into it. WebXR on the WebGL renderer is tested.
162
+ - **Labels are not drawn under WebGPU**: troika builds WebGL shaders and has no WebGPU
163
+ path. A scene with labels draws everything else.
164
+ - **DOM overlay** (`xr({ domOverlay: true })`) is checked only in the session request; the
165
+ emulator has no overlay.
166
+
167
+ ## Architecture
168
+
169
+ - `src/kernel`: plain TypeScript (descriptions, keyed diff, applier, resource store, behaviours, pointer hit testing). It must not import `effect` ([ADR-003](../../docs/effect-three/adr/003-kernel-runtime-layering.md)).
170
+ - `src/runtime`: Effect services, scopes and typed errors.
171
+
172
+ Decisions and their evidence are in [`docs/effect-three`](../../docs/effect-three/).
173
+
174
+ ## Development
175
+
176
+ ```sh
177
+ bun run test # unit and property tests (effect/Arbitrary)
178
+ bun run check # strict lint, layering and single-effect checks
179
+ bun run typecheck # TypeScript 7
180
+ bun run bench:check # benchmarks against the plan's budgets
181
+ bun run build # dist JS (bun build) and declarations (tsc)
182
+ bun run api:check # fail if the public API changed without updating etc/
183
+ ```
184
+
185
+ Current benchmark results (Apple Silicon, 1,000 meshes):
186
+
187
+ | Case | Median | Budget |
188
+ | ---------------------------- | ---------------------- | ------ |
189
+ | No-op reconcile | ~95 µs | 200 µs |
190
+ | One changed property | ~95 µs, 1 op | 500 µs |
191
+ | Runtime mount + unmount | ~22 µs | 1 ms |
192
+ | Frame with one animated mesh | ~55 ns, no heap growth | 50 µs |
@@ -0,0 +1,129 @@
1
+ // src/kernel/contactShadowPasses.ts
2
+ import {
3
+ Color,
4
+ Mesh,
5
+ MeshBasicMaterial,
6
+ OrthographicCamera,
7
+ PlaneGeometry,
8
+ Scene,
9
+ Vector3
10
+ } from "three";
11
+ var up = new Vector3(0, 1, 0);
12
+ var aim = new Vector3;
13
+ var groundGeometry = (width, depth, flipped) => {
14
+ const geometry = new PlaneGeometry(width, depth).rotateX(-Math.PI / 2);
15
+ if (flipped)
16
+ return geometry;
17
+ const uv = geometry.getAttribute("uv");
18
+ for (let i = 0;i < uv.count; i++)
19
+ uv.setY(i, 1 - uv.getY(i));
20
+ return geometry;
21
+ };
22
+ var reshaped = (a, b) => a.resolution !== b.resolution || a.size[0] !== b.size[0] || a.size[1] !== b.size[1];
23
+ var createContactShadowPasses = (graph, renderer, materials) => {
24
+ let shown;
25
+ const release = () => {
26
+ if (shown === undefined)
27
+ return;
28
+ shown.plane.removeFromParent();
29
+ shown.plane.geometry.dispose();
30
+ shown.plane.material.dispose();
31
+ shown.target.dispose();
32
+ shown.blurTarget.dispose();
33
+ shown.depthMaterial.dispose();
34
+ shown.horizontal.material.dispose();
35
+ shown.vertical.material.dispose();
36
+ shown.quad.geometry.dispose();
37
+ shown = undefined;
38
+ };
39
+ const build = (description) => {
40
+ const [width, depth] = description.size;
41
+ const target = renderer.createTarget(description.resolution, description.resolution);
42
+ const blurTarget = renderer.createTarget(description.resolution, description.resolution);
43
+ const material = new MeshBasicMaterial({
44
+ map: target.texture,
45
+ transparent: true,
46
+ depthWrite: false
47
+ });
48
+ const plane = new Mesh(groundGeometry(width, depth, materials.flippedTargets), material);
49
+ plane.raycast = () => {
50
+ return;
51
+ };
52
+ graph.scene.add(plane);
53
+ const color = new Color;
54
+ const quad = new Mesh(new PlaneGeometry(2, 2));
55
+ quad.position.z = -0.5;
56
+ const quadScene = new Scene;
57
+ quadScene.add(quad);
58
+ return {
59
+ description,
60
+ plane,
61
+ camera: new OrthographicCamera(-width / 2, width / 2, depth / 2, -depth / 2, 0, 1),
62
+ target,
63
+ blurTarget,
64
+ color,
65
+ depthMaterial: materials.depth(color),
66
+ horizontal: materials.blur("horizontal"),
67
+ vertical: materials.blur("vertical"),
68
+ quad,
69
+ quadScene,
70
+ quadCamera: new OrthographicCamera(-1, 1, 1, -1, 0, 1)
71
+ };
72
+ };
73
+ const update = (current, description) => {
74
+ current.plane.position.fromArray(description.position);
75
+ current.plane.material.opacity = description.opacity;
76
+ current.color.setHex(description.color);
77
+ current.camera.position.fromArray(description.position);
78
+ current.camera.up.set(0, 0, 1);
79
+ current.camera.lookAt(aim.copy(current.camera.position).add(up));
80
+ current.camera.near = description.near;
81
+ current.camera.far = description.far;
82
+ current.camera.updateProjectionMatrix();
83
+ current.camera.updateMatrixWorld();
84
+ return { ...current, description };
85
+ };
86
+ const blurPass = (current, amount) => {
87
+ const { horizontal, vertical, quad, quadScene, quadCamera } = current;
88
+ quad.material = horizontal.material;
89
+ horizontal.set(current.target.texture, amount / 256);
90
+ renderer.render(quadScene, quadCamera, undefined, current.blurTarget);
91
+ quad.material = vertical.material;
92
+ vertical.set(current.blurTarget.texture, amount / 256);
93
+ renderer.render(quadScene, quadCamera, undefined, current.target);
94
+ };
95
+ const render = (current) => {
96
+ const { scene } = graph;
97
+ const background = scene.background;
98
+ const override = scene.overrideMaterial;
99
+ current.plane.visible = false;
100
+ scene.background = null;
101
+ scene.overrideMaterial = current.depthMaterial;
102
+ renderer.render(scene, current.camera, undefined, current.target);
103
+ scene.overrideMaterial = override;
104
+ scene.background = background;
105
+ current.plane.visible = true;
106
+ blurPass(current, current.description.blur);
107
+ if (current.description.smooth)
108
+ blurPass(current, current.description.blur * 0.4);
109
+ };
110
+ return {
111
+ draw: () => {
112
+ const description = graph.contactShadows();
113
+ if (description === null) {
114
+ release();
115
+ return;
116
+ }
117
+ if (shown !== undefined && reshaped(shown.description, description))
118
+ release();
119
+ shown = update(shown ?? build(description), description);
120
+ render(shown);
121
+ },
122
+ dispose: release
123
+ };
124
+ };
125
+
126
+ export { createContactShadowPasses };
127
+
128
+ //# debugId=AFE8D134F596683564756E2164756E21
129
+ //# sourceMappingURL=index-4045wme0.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/kernel/contactShadowPasses.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * Contact shadows, drawn by the frame loop with the scene's renderer: the passes, shared\n * by every render kit, which supplies the materials (`ShadowMaterials`).\n *\n * Behavioural reference: Drei `src/core/ContactShadows.tsx` at `bf6f4ad`, itself after\n * three.js's `webgl_shadow_contact` example by mrdoob (both MIT). As there, every drawn\n * frame:\n *\n * 1. renders the scene from below with an orthographic camera into a texture, through a\n * depth material that turns nearness to the ground into shadow (alpha `1 − depth`,\n * colour `color · depth · 2`), with the background off and the shadow plane hidden;\n * 2. blurs it horizontally and vertically (`blur / 256`), twice with `smooth` (the second\n * at 0.4 of the blur);\n * 3. shows it on a transparent plane facing up.\n *\n * One difference: the blur passes draw a quad with their own camera, so they fill the\n * texture wherever the shadows are placed. (Drei draws its quad at the world origin with\n * the shadow camera, which leaves the blur empty when the shadows sit above `y = 0`.)\n *\n * @module\n */\nimport {\n Color,\n type Material,\n Mesh,\n MeshBasicMaterial,\n OrthographicCamera,\n PlaneGeometry,\n Scene,\n type Texture,\n Vector3,\n} from 'three';\n\nimport type { ContactShadows } from './description.ts';\nimport type { RenderTarget, SceneRenderer } from './renderer.ts';\nimport type { ContactShadowPasses, ShadowedScene } from './renderKit.ts';\n\n/** One blur direction's material, and how to point it at a texture by an amount. */\nexport type BlurPass = {\n readonly material: Material;\n /** Reads `source`, blurring by `amount` of the texture's width (or height). */\n readonly set: (source: Texture, amount: number) => void;\n};\n\n/** What a render kit draws contact shadows with. */\nexport type ShadowMaterials = {\n /**\n * The material the scene is drawn through from below: colour `color · depth · 2`, alpha\n * `1 − depth`, no depth test or write.\n */\n readonly depth: (color: Color) => Material;\n readonly blur: (direction: 'horizontal' | 'vertical') => BlurPass;\n /**\n * Whether the renderer reads a target's texture with v running the other way from\n * WebGL's: three's WebGPU renderer does, on either backend.\n */\n readonly flippedTargets: boolean;\n};\n\n/** Everything one set of contact shadows holds while shown. */\ntype Shown = {\n readonly description: ContactShadows;\n readonly plane: Mesh<PlaneGeometry, MeshBasicMaterial>;\n readonly camera: OrthographicCamera;\n readonly target: RenderTarget;\n readonly blurTarget: RenderTarget;\n readonly color: Color;\n readonly depthMaterial: Material;\n readonly horizontal: BlurPass;\n readonly vertical: BlurPass;\n readonly quad: Mesh<PlaneGeometry, Material>;\n readonly quadScene: Scene;\n readonly quadCamera: OrthographicCamera;\n};\n\nconst up = new Vector3(0, 1, 0);\nconst aim = new Vector3();\n\n/**\n * The plane's geometry: flat, facing up, its texture's v running towards +z as the shadow\n * camera's image does (already so where the renderer reads targets flipped).\n */\nconst groundGeometry = (width: number, depth: number, flipped: boolean): PlaneGeometry => {\n const geometry = new PlaneGeometry(width, depth).rotateX(-Math.PI / 2);\n if (flipped) return geometry;\n const uv = geometry.getAttribute('uv');\n for (let i = 0; i < uv.count; i++) uv.setY(i, 1 - uv.getY(i));\n return geometry;\n};\n\n/** Whether a change needs new targets, geometry or camera rather than an update. */\nconst reshaped = (a: ContactShadows, b: ContactShadows): boolean =>\n a.resolution !== b.resolution || a.size[0] !== b.size[0] || a.size[1] !== b.size[1];\n\n/** Creates the contact shadow passes for one scene graph and renderer, with `materials`. */\nexport const createContactShadowPasses = (\n graph: ShadowedScene,\n renderer: SceneRenderer,\n materials: ShadowMaterials,\n): ContactShadowPasses => {\n let shown: Shown | undefined;\n\n const release = (): void => {\n if (shown === undefined) return;\n shown.plane.removeFromParent();\n shown.plane.geometry.dispose();\n shown.plane.material.dispose();\n shown.target.dispose();\n shown.blurTarget.dispose();\n shown.depthMaterial.dispose();\n shown.horizontal.material.dispose();\n shown.vertical.material.dispose();\n shown.quad.geometry.dispose();\n shown = undefined;\n };\n\n const build = (description: ContactShadows): Shown => {\n const [width, depth] = description.size;\n const target = renderer.createTarget(description.resolution, description.resolution);\n const blurTarget = renderer.createTarget(description.resolution, description.resolution);\n const material = new MeshBasicMaterial({\n map: target.texture,\n transparent: true,\n depthWrite: false,\n });\n const plane = new Mesh(groundGeometry(width, depth, materials.flippedTargets), material);\n // Shadows are scenery: never picked, never in the way of an overlay.\n plane.raycast = () => undefined;\n graph.scene.add(plane);\n const color = new Color();\n const quad = new Mesh<PlaneGeometry, Material>(new PlaneGeometry(2, 2));\n // In front of the quad camera, between its near and far planes.\n quad.position.z = -0.5;\n const quadScene = new Scene();\n quadScene.add(quad);\n return {\n description,\n plane,\n camera: new OrthographicCamera(-width / 2, width / 2, depth / 2, -depth / 2, 0, 1),\n target,\n blurTarget,\n color,\n depthMaterial: materials.depth(color),\n horizontal: materials.blur('horizontal'),\n vertical: materials.blur('vertical'),\n quad,\n quadScene,\n quadCamera: new OrthographicCamera(-1, 1, 1, -1, 0, 1),\n };\n };\n\n /** Applies the fields that change in place: placement, reach, colour and opacity. */\n const update = (current: Shown, description: ContactShadows): Shown => {\n current.plane.position.fromArray(description.position);\n current.plane.material.opacity = description.opacity;\n current.color.setHex(description.color);\n current.camera.position.fromArray(description.position);\n // Looking straight up, with +z as the image's up: its v runs towards +z, as the plane's.\n current.camera.up.set(0, 0, 1);\n current.camera.lookAt(aim.copy(current.camera.position).add(up));\n current.camera.near = description.near;\n current.camera.far = description.far;\n current.camera.updateProjectionMatrix();\n current.camera.updateMatrixWorld();\n return { ...current, description };\n };\n\n /** One blur: horizontally into the spare target, then vertically back. */\n const blurPass = (current: Shown, amount: number): void => {\n const { horizontal, vertical, quad, quadScene, quadCamera } = current;\n quad.material = horizontal.material;\n horizontal.set(current.target.texture, amount / 256);\n renderer.render(quadScene, quadCamera, undefined, current.blurTarget);\n quad.material = vertical.material;\n vertical.set(current.blurTarget.texture, amount / 256);\n renderer.render(quadScene, quadCamera, undefined, current.target);\n };\n\n const render = (current: Shown): void => {\n const { scene } = graph;\n const background = scene.background;\n const override = scene.overrideMaterial;\n current.plane.visible = false;\n scene.background = null;\n scene.overrideMaterial = current.depthMaterial;\n renderer.render(scene, current.camera, undefined, current.target);\n scene.overrideMaterial = override;\n scene.background = background;\n current.plane.visible = true;\n blurPass(current, current.description.blur);\n if (current.description.smooth) blurPass(current, current.description.blur * 0.4);\n };\n\n return {\n draw: () => {\n const description = graph.contactShadows();\n if (description === null) {\n release();\n return;\n }\n if (shown !== undefined && reshaped(shown.description, description)) release();\n shown = update(shown ?? build(description), description);\n render(shown);\n },\n dispose: release,\n };\n};\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "AFE8D134F596683564756E2164756E21",
9
+ "names": []
10
+ }