@expofp/renderer 0.0.0-experimental.2d6c681

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 (280) hide show
  1. package/README.md +145 -0
  2. package/dist/Renderer.d.ts +102 -0
  3. package/dist/Renderer.js +96 -0
  4. package/dist/controls/CameraControls.d.ts +108 -0
  5. package/dist/controls/CameraControls.js +203 -0
  6. package/dist/controls/Controls.d.ts +53 -0
  7. package/dist/controls/Controls.js +54 -0
  8. package/dist/controls/camera-api.d.ts +226 -0
  9. package/dist/controls/camera-api.js +179 -0
  10. package/dist/controls/gestures.d.ts +71 -0
  11. package/dist/controls/gestures.js +98 -0
  12. package/dist/controls/handlers/DoubleTapZoom.d.ts +23 -0
  13. package/dist/controls/handlers/DoubleTapZoom.js +82 -0
  14. package/dist/controls/handlers/Inertia.d.ts +60 -0
  15. package/dist/controls/handlers/Inertia.js +222 -0
  16. package/dist/controls/handlers/Pan.d.ts +23 -0
  17. package/dist/controls/handlers/Pan.js +212 -0
  18. package/dist/controls/handlers/Pitch.d.ts +19 -0
  19. package/dist/controls/handlers/Pitch.js +200 -0
  20. package/dist/controls/handlers/Roll.d.ts +30 -0
  21. package/dist/controls/handlers/Roll.js +196 -0
  22. package/dist/controls/handlers/Zoom.d.ts +28 -0
  23. package/dist/controls/handlers/Zoom.js +34 -0
  24. package/dist/controls/hooks.d.ts +136 -0
  25. package/dist/controls/hooks.js +200 -0
  26. package/dist/controls/index.d.ts +9 -0
  27. package/dist/controls/index.js +7 -0
  28. package/dist/controls/zoom-to.d.ts +89 -0
  29. package/dist/controls/zoom-to.js +91 -0
  30. package/dist/core/InstanceUniforms.d.ts +108 -0
  31. package/dist/core/InstanceUniforms.js +298 -0
  32. package/dist/core/SquareDataTexture.d.ts +277 -0
  33. package/dist/core/SquareDataTexture.js +476 -0
  34. package/dist/core/batch/BatchedMesh.d.ts +74 -0
  35. package/dist/core/batch/BatchedMesh.js +155 -0
  36. package/dist/core/instanced/FrustumCulling.d.ts +141 -0
  37. package/dist/core/instanced/FrustumCulling.js +246 -0
  38. package/dist/core/instanced/InstancedMesh.d.ts +302 -0
  39. package/dist/core/instanced/InstancedMesh.js +497 -0
  40. package/dist/core/instanced/Instances.d.ts +119 -0
  41. package/dist/core/instanced/Instances.js +230 -0
  42. package/dist/core/instanced/Raycasting.d.ts +118 -0
  43. package/dist/core/instanced/Raycasting.js +134 -0
  44. package/dist/core/instanced/instanced-material.d.ts +77 -0
  45. package/dist/core/instanced/instanced-material.js +228 -0
  46. package/dist/core/lines/BatchedLines.d.ts +137 -0
  47. package/dist/core/lines/BatchedLines.js +215 -0
  48. package/dist/core/lines/FrustumCullingLines.d.ts +44 -0
  49. package/dist/core/lines/FrustumCullingLines.js +85 -0
  50. package/dist/core/lines/RaycastingLines.d.ts +45 -0
  51. package/dist/core/lines/RaycastingLines.js +91 -0
  52. package/dist/core/lines/lines-material.d.ts +47 -0
  53. package/dist/core/lines/lines-material.js +124 -0
  54. package/dist/core/shader-source.d.ts +70 -0
  55. package/dist/core/shader-source.js +110 -0
  56. package/dist/core/sprites/BatchedSprites.d.ts +115 -0
  57. package/dist/core/sprites/BatchedSprites.js +173 -0
  58. package/dist/core/sprites/RaycastingSprites.d.ts +41 -0
  59. package/dist/core/sprites/RaycastingSprites.js +71 -0
  60. package/dist/core/sprites/sprite-material.d.ts +59 -0
  61. package/dist/core/sprites/sprite-material.js +192 -0
  62. package/dist/core/text/BatchedText.d.ts +233 -0
  63. package/dist/core/text/BatchedText.js +473 -0
  64. package/dist/core/text/MemberTexture.d.ts +90 -0
  65. package/dist/core/text/MemberTexture.js +289 -0
  66. package/dist/core/text/Text.d.ts +88 -0
  67. package/dist/core/text/Text.js +230 -0
  68. package/dist/core/text/batched-text-material.d.ts +56 -0
  69. package/dist/core/text/batched-text-material.js +123 -0
  70. package/dist/core/text/glyph-packing.d.ts +17 -0
  71. package/dist/core/text/glyph-packing.js +61 -0
  72. package/dist/core/typed-array.d.ts +16 -0
  73. package/dist/core/typed-array.js +20 -0
  74. package/dist/debug/components/DebugPanelProvider.d.ts +20 -0
  75. package/dist/debug/components/DebugPanelProvider.js +50 -0
  76. package/dist/debug/components/DebugTools.d.ts +22 -0
  77. package/dist/debug/components/DebugTools.js +28 -0
  78. package/dist/debug/components/Gui.d.ts +22 -0
  79. package/dist/debug/components/Gui.js +42 -0
  80. package/dist/debug/components/MemoryInfo.d.ts +22 -0
  81. package/dist/debug/components/MemoryInfo.js +42 -0
  82. package/dist/debug/components/Panel.d.ts +31 -0
  83. package/dist/debug/components/Panel.js +104 -0
  84. package/dist/debug/components/Stats.d.ts +9 -0
  85. package/dist/debug/components/Stats.js +24 -0
  86. package/dist/debug/debug-chunk.d.ts +26 -0
  87. package/dist/debug/debug-chunk.js +32 -0
  88. package/dist/debug/debug-context.d.ts +41 -0
  89. package/dist/debug/debug-context.js +59 -0
  90. package/dist/debug/index.d.ts +41 -0
  91. package/dist/debug/index.js +43 -0
  92. package/dist/events/LayoutDriver.d.ts +36 -0
  93. package/dist/events/LayoutDriver.js +192 -0
  94. package/dist/events/PointerMissed.d.ts +23 -0
  95. package/dist/events/PointerMissed.js +58 -0
  96. package/dist/events/def-culling.d.ts +46 -0
  97. package/dist/events/def-culling.js +69 -0
  98. package/dist/events/layout-targets.d.ts +151 -0
  99. package/dist/events/layout-targets.js +397 -0
  100. package/dist/events/placement/box.d.ts +59 -0
  101. package/dist/events/placement/box.js +96 -0
  102. package/dist/events/placement/grid.d.ts +161 -0
  103. package/dist/events/placement/grid.js +235 -0
  104. package/dist/events/placement/index.d.ts +2 -0
  105. package/dist/events/placement/index.js +1 -0
  106. package/dist/events/placement/pass.d.ts +70 -0
  107. package/dist/events/placement/pass.js +617 -0
  108. package/dist/events/pointer.d.ts +194 -0
  109. package/dist/events/pointer.js +356 -0
  110. package/dist/external/ExternalCanvas.d.ts +56 -0
  111. package/dist/external/ExternalCanvas.js +177 -0
  112. package/dist/external/external-camera.d.ts +18 -0
  113. package/dist/external/external-camera.js +103 -0
  114. package/dist/external/external-events.d.ts +14 -0
  115. package/dist/external/external-events.js +33 -0
  116. package/dist/index.d.ts +13 -0
  117. package/dist/index.js +14 -0
  118. package/dist/objects/Background.d.ts +31 -0
  119. package/dist/objects/Background.js +37 -0
  120. package/dist/objects/containers/SlotContainer.d.ts +312 -0
  121. package/dist/objects/containers/SlotContainer.js +311 -0
  122. package/dist/objects/containers/hooks.d.ts +61 -0
  123. package/dist/objects/containers/hooks.js +196 -0
  124. package/dist/objects/containers/index.d.ts +6 -0
  125. package/dist/objects/containers/index.js +4 -0
  126. package/dist/objects/containers/keys.d.ts +10 -0
  127. package/dist/objects/containers/keys.js +18 -0
  128. package/dist/objects/containers/mesh-writes.d.ts +106 -0
  129. package/dist/objects/containers/mesh-writes.js +97 -0
  130. package/dist/objects/containers/props.d.ts +33 -0
  131. package/dist/objects/containers/props.js +1 -0
  132. package/dist/objects/images/ImageInstance.d.ts +29 -0
  133. package/dist/objects/images/ImageInstance.js +152 -0
  134. package/dist/objects/images/Images.d.ts +49 -0
  135. package/dist/objects/images/Images.js +394 -0
  136. package/dist/objects/images/atlas-packer.d.ts +129 -0
  137. package/dist/objects/images/atlas-packer.js +287 -0
  138. package/dist/objects/images/image-container.d.ts +74 -0
  139. package/dist/objects/images/image-container.js +100 -0
  140. package/dist/objects/images/index.d.ts +2 -0
  141. package/dist/objects/images/index.js +1 -0
  142. package/dist/objects/images/pack-sources.d.ts +190 -0
  143. package/dist/objects/images/pack-sources.js +272 -0
  144. package/dist/objects/images/texture-budget.d.ts +120 -0
  145. package/dist/objects/images/texture-budget.js +215 -0
  146. package/dist/objects/images/worker/atlas-worker.d.ts +56 -0
  147. package/dist/objects/images/worker/atlas-worker.js +123 -0
  148. package/dist/objects/images/worker/client.d.ts +62 -0
  149. package/dist/objects/images/worker/client.js +249 -0
  150. package/dist/objects/images/worker/index.d.ts +10 -0
  151. package/dist/objects/images/worker/index.js +1 -0
  152. package/dist/objects/layer/Layer.d.ts +28 -0
  153. package/dist/objects/layer/Layer.js +134 -0
  154. package/dist/objects/layer/index.d.ts +2 -0
  155. package/dist/objects/layer/index.js +1 -0
  156. package/dist/objects/layer/layer-context.d.ts +74 -0
  157. package/dist/objects/layer/layer-context.js +82 -0
  158. package/dist/objects/layer/mount-scheduler.d.ts +131 -0
  159. package/dist/objects/layer/mount-scheduler.js +289 -0
  160. package/dist/objects/layer/visibility-gui.d.ts +19 -0
  161. package/dist/objects/layer/visibility-gui.js +93 -0
  162. package/dist/objects/lighting/Lighting.d.ts +92 -0
  163. package/dist/objects/lighting/Lighting.js +384 -0
  164. package/dist/objects/lighting/OcclusionPasses.d.ts +23 -0
  165. package/dist/objects/lighting/OcclusionPasses.js +271 -0
  166. package/dist/objects/lighting/index.d.ts +11 -0
  167. package/dist/objects/lighting/index.js +10 -0
  168. package/dist/objects/lighting/lighting-gui.d.ts +24 -0
  169. package/dist/objects/lighting/lighting-gui.js +114 -0
  170. package/dist/objects/lighting/occluder.d.ts +34 -0
  171. package/dist/objects/lighting/occluder.js +62 -0
  172. package/dist/objects/lighting/occlusion-targets.d.ts +143 -0
  173. package/dist/objects/lighting/occlusion-targets.js +276 -0
  174. package/dist/objects/lines/LineInstance.d.ts +21 -0
  175. package/dist/objects/lines/LineInstance.js +73 -0
  176. package/dist/objects/lines/Lines.d.ts +17 -0
  177. package/dist/objects/lines/Lines.js +31 -0
  178. package/dist/objects/lines/index.d.ts +2 -0
  179. package/dist/objects/lines/index.js +1 -0
  180. package/dist/objects/lines/line-container.d.ts +35 -0
  181. package/dist/objects/lines/line-container.js +40 -0
  182. package/dist/objects/materials/ColorMaterial.d.ts +92 -0
  183. package/dist/objects/materials/ColorMaterial.js +124 -0
  184. package/dist/objects/materials/LineMaterial.d.ts +44 -0
  185. package/dist/objects/materials/LineMaterial.js +74 -0
  186. package/dist/objects/materials/ModelMaterial.d.ts +53 -0
  187. package/dist/objects/materials/ModelMaterial.js +111 -0
  188. package/dist/objects/materials/TextureMaterial.d.ts +33 -0
  189. package/dist/objects/materials/TextureMaterial.js +48 -0
  190. package/dist/objects/materials/effects/ao/ambient-occlusion-pass.d.ts +77 -0
  191. package/dist/objects/materials/effects/ao/ambient-occlusion-pass.js +296 -0
  192. package/dist/objects/materials/effects/ao/ambient-occlusion-patch.d.ts +29 -0
  193. package/dist/objects/materials/effects/ao/ambient-occlusion-patch.js +115 -0
  194. package/dist/objects/materials/effects/ao/blur-pass.d.ts +61 -0
  195. package/dist/objects/materials/effects/ao/blur-pass.js +101 -0
  196. package/dist/objects/materials/effects/ao/index.d.ts +129 -0
  197. package/dist/objects/materials/effects/ao/index.js +72 -0
  198. package/dist/objects/materials/effects/color-dim.d.ts +115 -0
  199. package/dist/objects/materials/effects/color-dim.js +206 -0
  200. package/dist/objects/materials/effects/contact-shadow.d.ts +100 -0
  201. package/dist/objects/materials/effects/contact-shadow.js +167 -0
  202. package/dist/objects/materials/effects/effects-context.d.ts +53 -0
  203. package/dist/objects/materials/effects/effects-context.js +24 -0
  204. package/dist/objects/materials/shared.d.ts +24 -0
  205. package/dist/objects/materials/shared.js +72 -0
  206. package/dist/objects/models/ModelInstance.d.ts +22 -0
  207. package/dist/objects/models/ModelInstance.js +33 -0
  208. package/dist/objects/models/Models.d.ts +22 -0
  209. package/dist/objects/models/Models.js +186 -0
  210. package/dist/objects/models/gltf-cache.d.ts +17 -0
  211. package/dist/objects/models/gltf-cache.js +50 -0
  212. package/dist/objects/models/index.d.ts +2 -0
  213. package/dist/objects/models/index.js +1 -0
  214. package/dist/objects/models/model-asset.d.ts +48 -0
  215. package/dist/objects/models/model-asset.js +77 -0
  216. package/dist/objects/models/model-container.d.ts +123 -0
  217. package/dist/objects/models/model-container.js +207 -0
  218. package/dist/objects/shapes/ShapeInstance.d.ts +26 -0
  219. package/dist/objects/shapes/ShapeInstance.js +39 -0
  220. package/dist/objects/shapes/Shapes.d.ts +21 -0
  221. package/dist/objects/shapes/Shapes.js +131 -0
  222. package/dist/objects/shapes/index.d.ts +2 -0
  223. package/dist/objects/shapes/index.js +1 -0
  224. package/dist/objects/shapes/shape-container.d.ts +141 -0
  225. package/dist/objects/shapes/shape-container.js +242 -0
  226. package/dist/objects/shapes/shape-geometry.d.ts +27 -0
  227. package/dist/objects/shapes/shape-geometry.js +94 -0
  228. package/dist/objects/text/TextInstance.d.ts +22 -0
  229. package/dist/objects/text/TextInstance.js +95 -0
  230. package/dist/objects/text/Texts.d.ts +30 -0
  231. package/dist/objects/text/Texts.js +43 -0
  232. package/dist/objects/text/index.d.ts +2 -0
  233. package/dist/objects/text/index.js +1 -0
  234. package/dist/objects/text/text-container.d.ts +121 -0
  235. package/dist/objects/text/text-container.js +229 -0
  236. package/dist/objects/text/text-layout.d.ts +92 -0
  237. package/dist/objects/text/text-layout.js +180 -0
  238. package/dist/space/Camera.d.ts +103 -0
  239. package/dist/space/Camera.js +321 -0
  240. package/dist/space/Scene.d.ts +47 -0
  241. package/dist/space/Scene.js +127 -0
  242. package/dist/space/Space.d.ts +325 -0
  243. package/dist/space/Space.js +359 -0
  244. package/dist/space/VisibleRect.d.ts +45 -0
  245. package/dist/space/VisibleRect.js +129 -0
  246. package/dist/space/camera-handle.d.ts +67 -0
  247. package/dist/space/camera-handle.js +63 -0
  248. package/dist/space/camera-kind.d.ts +26 -0
  249. package/dist/space/camera-kind.js +30 -0
  250. package/dist/space/canvas-size.d.ts +24 -0
  251. package/dist/space/canvas-size.js +20 -0
  252. package/dist/space/clipping.d.ts +69 -0
  253. package/dist/space/clipping.js +130 -0
  254. package/dist/space/layout-measure.d.ts +67 -0
  255. package/dist/space/layout-measure.js +175 -0
  256. package/dist/space/viewport.d.ts +112 -0
  257. package/dist/space/viewport.js +67 -0
  258. package/dist/types/defs.d.ts +234 -0
  259. package/dist/types/defs.js +1 -0
  260. package/dist/types/events.d.ts +198 -0
  261. package/dist/types/events.js +1 -0
  262. package/dist/types/guards.d.ts +21 -0
  263. package/dist/types/guards.js +52 -0
  264. package/dist/update/index.d.ts +3 -0
  265. package/dist/update/index.js +2 -0
  266. package/dist/update/slot-writers.d.ts +30 -0
  267. package/dist/update/slot-writers.js +76 -0
  268. package/dist/update/store.d.ts +41 -0
  269. package/dist/update/store.js +104 -0
  270. package/dist/utils/arrays.d.ts +13 -0
  271. package/dist/utils/arrays.js +20 -0
  272. package/dist/utils/colors.d.ts +19 -0
  273. package/dist/utils/colors.js +27 -0
  274. package/dist/utils/hot-state.d.ts +41 -0
  275. package/dist/utils/hot-state.js +37 -0
  276. package/dist/utils/logging.d.ts +29 -0
  277. package/dist/utils/logging.js +55 -0
  278. package/dist/utils/math.d.ts +52 -0
  279. package/dist/utils/math.js +76 -0
  280. package/package.json +79 -0
@@ -0,0 +1,271 @@
1
+ import { useFrame } from '@react-three/fiber';
2
+ import { useEffect, useState } from 'react';
3
+ import { DoubleSide, Matrix4, Mesh, MeshBasicMaterial, NoBlending, PlaneGeometry, Scene, ShaderMaterial, } from 'three';
4
+ import { useSpace } from '../../space/Space';
5
+ import { AO_FRAGMENT_SHADER, AO_VERTEX_SHADER, BLUR_FRAGMENT_SHADER, createAoPassUniforms, createBlurPassUniforms, NOISE_TILE, } from '../materials/effects/ao';
6
+ import { useSceneEffects } from '../materials/effects/effects-context';
7
+ import { OCCLUSION_LAYER } from './occluder';
8
+ import { OcclusionTargets } from './occlusion-targets';
9
+ /**
10
+ * Screen-space ambient occlusion, in three passes a frame.
11
+ *
12
+ * The prepass draws {@link OCCLUSION_LAYER} into the one target that carries the depth texture. The
13
+ * AO pass reads that depth and writes how closed in each fragment is. The blur pass reads that
14
+ * output and the same depth, and writes what the lit materials read. Both quad passes bind a target
15
+ * with no depth attachment, because WebGL2 leaves a draw that samples its own framebuffer undefined.
16
+ * See `occlusion-targets.ts` for the three targets and the two textures under them.
17
+ *
18
+ * All three run on a frustum wider than the visible one. Geometry just outside the view then still
19
+ * occludes what is inside it, so no seam of missing darkening runs down the screen edges.
20
+ *
21
+ * Three things switch the passes off, and each one clears `aoActive` first. The debug GUI clears
22
+ * `enabled`, or the `occluders` registry is empty, and both of those free the render targets. A failed
23
+ * `renderTargets.sync()` is a capability gate that frees nothing, because it built nothing.
24
+ *
25
+ * The default frame priority puts this after the camera, which settles at negative priorities. It also
26
+ * puts this before R3F's own draw, so the texture is ready before any material samples it.
27
+ * @returns Nothing. This renders no scene node.
28
+ */
29
+ export function OcclusionPasses() {
30
+ const occlusion = useSceneEffects();
31
+ const space = useSpace();
32
+ const [renderTargets] = useState(() => new OcclusionTargets());
33
+ // Both passes are built here rather than declared as `<shaderMaterial uniforms={...}>`, because
34
+ // R3F copies a uniform record into the material's own to keep that target stable — which leaves
35
+ // the frame writing wrappers no shader reads. `ShaderMaterial` takes the record as given. The
36
+ // quads join them here for the same reason: neither ever changes after this, and a portal would
37
+ // spin up a nested R3F root to reconcile a subtree that never does.
38
+ const [aoPass] = useState(() => createPass('occlusion-ao', AO_FRAGMENT_SHADER, createAoPassUniforms()));
39
+ const [blurPass] = useState(() => createPass('occlusion-blur', BLUR_FRAGMENT_SHADER, createBlurPassUniforms(occlusion)));
40
+ useEffect(() => {
41
+ return () => {
42
+ renderTargets.dispose();
43
+ // Everything freed here is this mount's own, for the reason the uniform objects are: a
44
+ // texture, a program and a buffer upload into whichever context drew with them, and a second
45
+ // mounted scene has a second one.
46
+ aoPass.uniforms.uNoise.value.dispose();
47
+ disposePass(aoPass);
48
+ disposePass(blurPass);
49
+ unpublish(occlusion);
50
+ };
51
+ }, [aoPass, blurPass, occlusion, renderTargets]);
52
+ useFrame((state) => {
53
+ // First, so every path out of here leaves the shader reading nothing rather than a stale
54
+ // texture. Every write below goes through a function of this module rather than landing inline,
55
+ // because the React Compiler treats what a hook returned as frozen for the callback's own body.
56
+ unpublish(occlusion);
57
+ // The master switch and the registry, in one branch because they free the same thing: the debug
58
+ // GUI turned the term off, or nothing on this plan asked for it. Released once, not every
59
+ // frame — disposing the targets is what makes `held` false, and nothing rebuilds them until one
60
+ // of the two conditions lifts.
61
+ if (!occlusion.enabled || occlusion.occluders.size === 0) {
62
+ if (renderTargets.held)
63
+ renderTargets.dispose();
64
+ return;
65
+ }
66
+ // A capability gate, not a switch: see `OcclusionTargets`. Nothing to free on this path, since
67
+ // a failed sync never built anything.
68
+ if (!renderTargets.sync(state.gl))
69
+ return;
70
+ draw(state.gl, state.scene, state.camera, renderTargets, aoPass, blurPass, occlusion.aoGuard.value, occlusion.radius * space.scale, occlusion.taps);
71
+ publish(occlusion, renderTargets, state.gl);
72
+ });
73
+ return null;
74
+ }
75
+ /**
76
+ * Builds one full-screen pass: a clip-space quad in a scene of its own, drawn with a material over
77
+ * the uniforms it is handed.
78
+ * @param name - The quad's name, for a debugger's scene graph.
79
+ * @param fragmentShader - The pass's fragment stage.
80
+ * @param uniforms - The uniforms that stage reads, whose identity outlives every write into it.
81
+ * @returns The pass, ready to draw and to dispose.
82
+ */
83
+ function createPass(name, fragmentShader, uniforms) {
84
+ const material = new ShaderMaterial({
85
+ uniforms,
86
+ vertexShader: AO_VERTEX_SHADER,
87
+ fragmentShader,
88
+ depthTest: false,
89
+ depthWrite: false,
90
+ blending: NoBlending,
91
+ });
92
+ const quad = new Mesh(new PlaneGeometry(2, 2), material);
93
+ quad.name = name;
94
+ // Culling would test the untransformed unit quad, not where the vertex shader puts it — which is
95
+ // clip space, exactly as `Background` places its own.
96
+ quad.frustumCulled = false;
97
+ const scene = new Scene();
98
+ scene.add(quad);
99
+ return { scene, material, quad, uniforms };
100
+ }
101
+ /**
102
+ * Draws all three passes, and refreshes every uniform that moves with the frame.
103
+ *
104
+ * The guard band is built here rather than published, because the prepass has to be drawn through it
105
+ * and the AO pass has to unproject through the very same matrix. Both are written in place into the
106
+ * AO pass's own uniforms, so nothing here allocates.
107
+ * @param gl - The renderer, whose bound target this borrows and puts back.
108
+ * @param scene - The scene to draw the occlusion layer of.
109
+ * @param camera - The camera the frame will be drawn with.
110
+ * @param renderTargets - The three targets to draw into, already synced.
111
+ * @param aoPass - The AO pass, whose uniforms are refreshed here.
112
+ * @param blurPass - The blur pass, whose uniforms are refreshed here.
113
+ * @param guard - How much wider than the main frustum every pass runs.
114
+ * @param viewRadius - How far the kernel reaches, in view units.
115
+ * @param taps - How many of the kernel's offsets the AO pass walks.
116
+ */
117
+ function draw(gl, scene, camera, renderTargets, aoPass, blurPass, guard, viewRadius, taps) {
118
+ const depth = renderTargets.depth;
119
+ const ao = renderTargets.ao;
120
+ const blur = renderTargets.blur;
121
+ if (!depth?.depthTexture || !ao || !blur)
122
+ return;
123
+ // P_guard = scale(1 / (1 + g), 1 / (1 + g), 1) · P_main. The scale touches the x and y clip rows
124
+ // and leaves the depth rows alone, which is what makes a depth value the prepass stores the value
125
+ // the main pass would have stored for the same point — and it works on any projection matrix,
126
+ // including one three never built.
127
+ const widen = 1 / (1 + guard);
128
+ aoPass.uniforms.uProjection.value
129
+ .copy(camera.projectionMatrix)
130
+ .premultiply(_widen.makeScale(widen, widen, 1));
131
+ aoPass.uniforms.uUnprojection.value.copy(aoPass.uniforms.uProjection.value).invert();
132
+ aoPass.uniforms.uDepth.value = depth.depthTexture;
133
+ aoPass.uniforms.uRadius.value = viewRadius;
134
+ aoPass.uniforms.uTaps.value = taps;
135
+ aoPass.uniforms.uTexel.value.set(1 / renderTargets.size.x, 1 / renderTargets.size.y);
136
+ aoPass.uniforms.uNoiseScale.value.set(renderTargets.size.x / NOISE_TILE, renderTargets.size.y / NOISE_TILE);
137
+ // The blur runs at the very size the AO pass wrote, so it walks the same texel step — its own,
138
+ // not the one a lit fragment used to walk. Its kernel side is the scene's own uniform object,
139
+ // which the debug GUI writes directly, so nothing here copies it.
140
+ blurPass.uniforms.uOcclusion.value = ao.texture;
141
+ blurPass.uniforms.uDepth.value = depth.depthTexture;
142
+ blurPass.uniforms.uTexel.value.copy(aoPass.uniforms.uTexel.value);
143
+ drawPrepass(gl, scene, camera, depth, aoPass.uniforms);
144
+ const previous = gl.getRenderTarget();
145
+ try {
146
+ drawQuad(gl, ao, aoPass.scene, camera);
147
+ drawQuad(gl, blur, blurPass.scene, camera);
148
+ }
149
+ finally {
150
+ gl.setRenderTarget(previous);
151
+ }
152
+ }
153
+ /**
154
+ * Draws the plan's opaque solid content into the depth target, through the guard band's projection.
155
+ *
156
+ * The camera is narrowed to one channel rather than the scene filtered, because a channel is the
157
+ * only membership three's own traversal reads — `projectObject` and `renderObjects` both test it.
158
+ * Everything borrowed is put back in a `finally`: the layer mask, the override material, the
159
+ * projection, and the bound target, which is what keeps a shared context unaware this ran.
160
+ * @param gl - The renderer.
161
+ * @param scene - The scene to draw.
162
+ * @param camera - The camera to draw it with, borrowed and restored.
163
+ * @param target - The depth target to draw into.
164
+ * @param uniforms - The AO pass's uniforms, for the guard band already built into them.
165
+ */
166
+ function drawPrepass(gl, scene, camera, target, uniforms) {
167
+ const previous = gl.getRenderTarget();
168
+ const mask = camera.layers.mask;
169
+ const override = scene.overrideMaterial;
170
+ const projection = _projection.copy(camera.projectionMatrix);
171
+ const unprojection = _unprojection.copy(camera.projectionMatrixInverse);
172
+ camera.layers.set(OCCLUSION_LAYER);
173
+ scene.overrideMaterial = PREPASS_MATERIAL;
174
+ camera.projectionMatrix.copy(uniforms.uProjection.value);
175
+ camera.projectionMatrixInverse.copy(uniforms.uUnprojection.value);
176
+ gl.setRenderTarget(target);
177
+ try {
178
+ // By hand rather than through `autoClear`, which the MapLibre embed turns off so its renderer
179
+ // never wipes the host's own layers — and which would otherwise leave the previous frame's
180
+ // depth standing here. Depth alone: this target's color attachment is the texture the blur pass
181
+ // owns, which nothing writes in this pass and the blur pass overwrites in full.
182
+ gl.clear(false, true, false);
183
+ gl.render(scene, camera);
184
+ }
185
+ finally {
186
+ camera.projectionMatrix.copy(projection);
187
+ camera.projectionMatrixInverse.copy(unprojection);
188
+ scene.overrideMaterial = override;
189
+ camera.layers.mask = mask;
190
+ gl.setRenderTarget(previous);
191
+ }
192
+ }
193
+ /**
194
+ * Draws one full-screen quad into one target.
195
+ *
196
+ * No clear: the quad covers every texel with the depth test off, so whatever was there is
197
+ * overwritten. Any camera does, too — the quad's vertex shader writes clip space and reads no
198
+ * matrix. The bound target is not put back here, because the caller draws more than one of these
199
+ * and restores it once.
200
+ * @param gl - The renderer.
201
+ * @param target - The target to bind.
202
+ * @param scene - The scene holding this pass's quad and nothing else.
203
+ * @param camera - Any camera, since the quad reads no matrix.
204
+ */
205
+ function drawQuad(gl, target, scene, camera) {
206
+ gl.setRenderTarget(target);
207
+ gl.render(scene, camera);
208
+ }
209
+ /**
210
+ * Frees what one pass allocated in a GL context.
211
+ * @param pass - The pass to free.
212
+ */
213
+ function disposePass(pass) {
214
+ pass.material.dispose();
215
+ pass.quad.geometry.dispose();
216
+ }
217
+ /**
218
+ * Publishes what the lit materials read, for the passes just drawn.
219
+ *
220
+ * The map is the blur target's texture, which is the smoothed term, and never the AO target's raw
221
+ * one — that is the whole of what the third pass bought.
222
+ * @param occlusion - The scene's shared occlusion state.
223
+ * @param renderTargets - The targets just drawn into.
224
+ * @param gl - The renderer, for the drawing buffer's size.
225
+ */
226
+ function publish(occlusion, renderTargets, gl) {
227
+ const depth = renderTargets.depth;
228
+ const blur = renderTargets.blur;
229
+ if (!depth?.depthTexture || !blur)
230
+ return;
231
+ occlusion.aoMap.value = blur.texture;
232
+ occlusion.aoDepth.value = depth.depthTexture;
233
+ gl.getDrawingBufferSize(occlusion.aoViewport.value);
234
+ occlusion.aoActive.value = 1;
235
+ }
236
+ /**
237
+ * Puts the shared state back to the off position, so nothing reads a texture that no longer exists.
238
+ * @param occlusion - The scene's shared occlusion state.
239
+ */
240
+ function unpublish(occlusion) {
241
+ occlusion.aoActive.value = 0;
242
+ occlusion.aoMap.value = null;
243
+ occlusion.aoDepth.value = null;
244
+ }
245
+ /**
246
+ * What the solid batches are drawn with into the depth target.
247
+ *
248
+ * Unlit and color-blind: `colorWrite` is off, because that target's color attachment is the texture
249
+ * the blur pass owns in full, so the cheapest fragment shader that still writes depth is the right
250
+ * one — and `MeshBasicMaterial` carries the batching and instancing chunks a `BatchedMesh` and an
251
+ * `InstancedMesh` need. `DoubleSide` matches the material the volumes really draw with, so the depth
252
+ * the taps read is the depth the main pass tests against.
253
+ *
254
+ * One for the process rather than one per mount: it holds no per-scene state and owns no texture,
255
+ * and three compiles a program for it per context that draws with it.
256
+ */
257
+ const PREPASS_MATERIAL = new MeshBasicMaterial({
258
+ side: DoubleSide,
259
+ colorWrite: false,
260
+ depthWrite: true,
261
+ depthTest: true,
262
+ blending: NoBlending,
263
+ polygonOffset: false,
264
+ toneMapped: false,
265
+ });
266
+ /** Scratch for the camera's own projection, borrowed for the length of the prepass. */
267
+ const _projection = new Matrix4();
268
+ /** Scratch for its inverse. */
269
+ const _unprojection = new Matrix4();
270
+ /** Scratch for the guard band's own scale, rebuilt per frame so a GUI edit takes effect at once. */
271
+ const _widen = new Matrix4();
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The lighting folder's public face inside the package.
3
+ *
4
+ * `Scene` mounts both of these and nothing else here, so the folder exports the two components, plus
5
+ * the props one of them takes from a caller through `RendererProps.lighting`. The light settings, the
6
+ * derived intensities, the occlusion pipeline's own render targets and the GUI folder stay behind
7
+ * it.
8
+ */
9
+ export { Lighting, type LightingProps, type ShadowProps } from './Lighting';
10
+ export { OcclusionPasses } from './OcclusionPasses';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The lighting folder's public face inside the package.
3
+ *
4
+ * `Scene` mounts both of these and nothing else here, so the folder exports the two components, plus
5
+ * the props one of them takes from a caller through `RendererProps.lighting`. The light settings, the
6
+ * derived intensities, the occlusion pipeline's own render targets and the GUI folder stay behind
7
+ * it.
8
+ */
9
+ export { Lighting } from './Lighting';
10
+ export { OcclusionPasses } from './OcclusionPasses';
@@ -0,0 +1,24 @@
1
+ import { type LightSettings } from './Lighting';
2
+ /**
3
+ * Builds the debug folder that settles this design's open values.
4
+ *
5
+ * A development tool. While the debug UI is off, `useGui()` returns `null`, and the folder is a
6
+ * no-op that never reaches the main chunk.
7
+ *
8
+ * Most of these values are also a prop, through `RendererProps.lighting`, and both write the very
9
+ * same objects. The key light's aim is the exception and stays here alone: where it points is the
10
+ * design's decision rather than a caller's. See `LightingProps`.
11
+ *
12
+ * No controller asks for a redraw: `Gui` registers one handler on the root panel and lil-gui walks
13
+ * every change up to it. Nor does any controller ask for a React render — every write lands on a
14
+ * uniform's own `value` or on a plain field a frame callback reads, which is what lets the whole
15
+ * folder move a mounted scene with no recompile and no reconciliation. Nothing here polls, either:
16
+ * every controller is a control, so what it shows is the value last written through it.
17
+ *
18
+ * A controller that cannot affect the current state is hidden rather than disabled, because a value
19
+ * it could show would mean nothing.
20
+ * @param light - The mounted light rig, handed over rather than imported, so this module needs no
21
+ * runtime import back into `Lighting`.
22
+ */
23
+ export declare function useLightingGui(light: LightSettings): void;
24
+ //# sourceMappingURL=lighting-gui.d.ts.map
@@ -0,0 +1,114 @@
1
+ import { useEffect } from 'react';
2
+ import { useGui } from '../../debug/debug-context';
3
+ import { useSceneEffects } from '../materials/effects/effects-context';
4
+ /**
5
+ * Builds the debug folder that settles this design's open values.
6
+ *
7
+ * A development tool. While the debug UI is off, `useGui()` returns `null`, and the folder is a
8
+ * no-op that never reaches the main chunk.
9
+ *
10
+ * Most of these values are also a prop, through `RendererProps.lighting`, and both write the very
11
+ * same objects. The key light's aim is the exception and stays here alone: where it points is the
12
+ * design's decision rather than a caller's. See `LightingProps`.
13
+ *
14
+ * No controller asks for a redraw: `Gui` registers one handler on the root panel and lil-gui walks
15
+ * every change up to it. Nor does any controller ask for a React render — every write lands on a
16
+ * uniform's own `value` or on a plain field a frame callback reads, which is what lets the whole
17
+ * folder move a mounted scene with no recompile and no reconciliation. Nothing here polls, either:
18
+ * every controller is a control, so what it shows is the value last written through it.
19
+ *
20
+ * A controller that cannot affect the current state is hidden rather than disabled, because a value
21
+ * it could show would mean nothing.
22
+ * @param light - The mounted light rig, handed over rather than imported, so this module needs no
23
+ * runtime import back into `Lighting`.
24
+ */
25
+ export function useLightingGui(light) {
26
+ const gui = useGui();
27
+ const effects = useSceneEffects();
28
+ useEffect(() => {
29
+ if (!gui)
30
+ return undefined;
31
+ const folder = addLightingFolder(gui, light, effects);
32
+ return () => {
33
+ folder.destroy();
34
+ };
35
+ }, [effects, gui, light]);
36
+ }
37
+ /**
38
+ * The Lighting folder: the light's aim, the two lights' colors, the contrast, and the Occlusion
39
+ * folder nested inside it.
40
+ *
41
+ * `contrast` needs no moving bound the way the intensity ratio it replaced did: its range is `0`–`1`
42
+ * at every pitch, so there is nothing here to keep in sync with the pitch controller.
43
+ * @param gui - The GUI to add the folder to.
44
+ * @param light - The rig each controller binds to.
45
+ * @param effects - The mounted scene's effect state, which the nested folder binds to.
46
+ * @returns The folder.
47
+ */
48
+ function addLightingFolder(gui, light, effects) {
49
+ const folder = gui.addFolder('Lighting');
50
+ const track = folder.add(light, 'trackPitch').name('track camera pitch');
51
+ const pitch = folder.add(light, 'pitchDegrees', 0, 89, 1).name('pitch°');
52
+ track.onChange((on) => toggle(pitch, !on));
53
+ toggle(pitch, !light.trackPitch);
54
+ folder.addColor(light, 'skyColor').name('sky color');
55
+ folder.addColor(light, 'groundColor').name('ground color');
56
+ folder.addColor(light, 'directionalColor').name('directional color');
57
+ folder.add(light, 'contrast', 0, 1, 0.01).name('contrast');
58
+ addDarkening(folder, effects);
59
+ return folder;
60
+ }
61
+ /**
62
+ * Adds the darkening controls to the Lighting folder.
63
+ *
64
+ * They belong beside the lights because darkening describes the plan's shading, not the values of
65
+ * any one material. They sit in the Lighting folder itself rather than a folder of their own, so a
66
+ * reader finds every shading control in one list.
67
+ *
68
+ * The two effects share a shade and keep their own strengths. The names say which effect each
69
+ * strength belongs to, because a plain `strength` beside `ao strength` invites a change to the wrong
70
+ * one.
71
+ *
72
+ * The switch hides the ambient occlusion controls, the way `track camera pitch` hides `pitch°`. The
73
+ * contact shadow's own numbers stay visible, because the contact shadow does not switch off with
74
+ * ambient occlusion. It is the darkening every device gets.
75
+ * @param folder - The Lighting folder.
76
+ * @param effects - The mounted scene's effect state. Every controller binds a uniform's `value` or a
77
+ * plain field on this one object.
78
+ */
79
+ function addDarkening(folder, effects) {
80
+ folder
81
+ .addColor(effects, 'shadowHex')
82
+ .name('shadow color')
83
+ // lil-gui writes display sRGB and three holds the working space, so `Color.set` converts. Both
84
+ // effects read this one object, so the shade moves for both or for neither.
85
+ .onChange((hex) => effects.shadowColor.value.set(hex));
86
+ folder.add(effects.shadowDistance, 'value', 1, 5, 0.1).name('contact distance');
87
+ folder.add(effects.shadowStrength, 'value', 0, 1, 0.01).name('contact strength');
88
+ const term = folder.add(effects, 'enabled').name('ambient occlusion');
89
+ // The controls ambient occlusion owns, hidden together while it is off. `enabled` is a plain
90
+ // boolean rather than a uniform. `OcclusionPasses` reads it in its frame callback and frees the
91
+ // render targets once when it goes false, so a click takes effect on the next frame and no React
92
+ // render follows.
93
+ const owned = [
94
+ folder.add(effects, 'radius', 1, 30, 0.5).name('ao radius'),
95
+ folder.add(effects.aoStrength, 'value', 0, 1, 0.01).name('ao strength'),
96
+ ];
97
+ term.onChange((on) => {
98
+ for (const controller of owned)
99
+ toggle(controller, on);
100
+ });
101
+ for (const controller of owned)
102
+ toggle(controller, effects.enabled);
103
+ }
104
+ /**
105
+ * Shows or hides a controller.
106
+ * @param controller - The controller to move.
107
+ * @param visible - Whether it should show.
108
+ */
109
+ function toggle(controller, visible) {
110
+ if (visible)
111
+ controller.show();
112
+ else
113
+ controller.hide();
114
+ }
@@ -0,0 +1,34 @@
1
+ import { type Object3D } from 'three';
2
+ /**
3
+ * Asks the scene for occlusion, on behalf of one mesh.
4
+ *
5
+ * Two things follow from a call, and they are one decision rather than two: the mesh joins
6
+ * {@link OCCLUSION_LAYER}, which is what the depth prepass draws, and it joins the scene's registry,
7
+ * which is what tells `OcclusionPasses` that anything at all wants the passes run. A caller enabling
8
+ * the layer by hand would leave the two free to drift, and a plan with no 3D content would pay for a
9
+ * pair of render targets and a full-screen pass over an empty depth buffer.
10
+ *
11
+ * **The registry is a set, so React's StrictMode double mount cannot make it drift.** Adding the
12
+ * same mesh twice is adding it once, where a count would climb; the mesh last bound is remembered
13
+ * here rather than by the caller, because React hands a detaching `ref` a `null` and nothing else.
14
+ *
15
+ * **Not a traversal of the scene.** The renderer avoids per-frame traversal deliberately, and a
16
+ * registry is exact where a traversal is a guess about what will draw.
17
+ * @returns The function a `ref` callback calls: with the mesh while it occludes, and with `null`
18
+ * when it stops — which is exactly what React hands a `ref` on detach.
19
+ */
20
+ export declare function useOccluder(): (occluder: Object3D | null) => void;
21
+ /**
22
+ * The layer a mesh joins to be drawn into the depth prepass.
23
+ *
24
+ * A channel rather than a flag because that is the only membership three's own traversal reads: the
25
+ * prepass narrows the camera to this one channel, and `projectObject` and `renderObjects` both test
26
+ * it.
27
+ *
28
+ * Opaque solid shape batches and opaque model meshes join it, through {@link useOccluder}.
29
+ * Translucent batches of either kind stay out, because a surface you can see through must not cast
30
+ * full occlusion. Planar batches stay out, which is what keeps this term and the contact shadow
31
+ * disjoint.
32
+ */
33
+ export declare const OCCLUSION_LAYER = 1;
34
+ //# sourceMappingURL=occluder.d.ts.map
@@ -0,0 +1,62 @@
1
+ import { useRef } from 'react';
2
+ import { useSceneEffects } from '../materials/effects/effects-context';
3
+ /**
4
+ * Asks the scene for occlusion, on behalf of one mesh.
5
+ *
6
+ * Two things follow from a call, and they are one decision rather than two: the mesh joins
7
+ * {@link OCCLUSION_LAYER}, which is what the depth prepass draws, and it joins the scene's registry,
8
+ * which is what tells `OcclusionPasses` that anything at all wants the passes run. A caller enabling
9
+ * the layer by hand would leave the two free to drift, and a plan with no 3D content would pay for a
10
+ * pair of render targets and a full-screen pass over an empty depth buffer.
11
+ *
12
+ * **The registry is a set, so React's StrictMode double mount cannot make it drift.** Adding the
13
+ * same mesh twice is adding it once, where a count would climb; the mesh last bound is remembered
14
+ * here rather than by the caller, because React hands a detaching `ref` a `null` and nothing else.
15
+ *
16
+ * **Not a traversal of the scene.** The renderer avoids per-frame traversal deliberately, and a
17
+ * registry is exact where a traversal is a guess about what will draw.
18
+ * @returns The function a `ref` callback calls: with the mesh while it occludes, and with `null`
19
+ * when it stops — which is exactly what React hands a `ref` on detach.
20
+ */
21
+ export function useOccluder() {
22
+ const effects = useSceneEffects();
23
+ // What this caller last bound, so the `null` a detaching `ref` is handed still names something to
24
+ // remove. A ref rather than state, because no render depends on it.
25
+ const boundRef = useRef(null);
26
+ // Through a function of this module rather than a write inside the closure, because the React
27
+ // Compiler treats what a hook returned as frozen for the closure's own body — the discipline
28
+ // `lighting-gui.ts` and `OcclusionPasses` both keep for their own writes.
29
+ return (occluder) => bindOccluder(effects.occluders, boundRef, occluder);
30
+ }
31
+ /**
32
+ * Moves one caller's membership from whatever it last bound to whatever it binds now.
33
+ * @param registry - The scene's occluder registry.
34
+ * @param boundRef - What this caller last bound; read and rewritten here.
35
+ * @param occluder - The mesh that occludes from now on, or `null` for none.
36
+ */
37
+ function bindOccluder(registry, boundRef, occluder) {
38
+ if (boundRef.current === occluder)
39
+ return;
40
+ if (boundRef.current)
41
+ registry.delete(boundRef.current);
42
+ boundRef.current = occluder;
43
+ if (!occluder)
44
+ return;
45
+ // Enabling a second channel leaves channel `0` enabled, so the mesh still draws in the main pass
46
+ // and still picks — `Raycaster` tests channel `0` alone.
47
+ occluder.layers.enable(OCCLUSION_LAYER);
48
+ registry.add(occluder);
49
+ }
50
+ /**
51
+ * The layer a mesh joins to be drawn into the depth prepass.
52
+ *
53
+ * A channel rather than a flag because that is the only membership three's own traversal reads: the
54
+ * prepass narrows the camera to this one channel, and `projectObject` and `renderObjects` both test
55
+ * it.
56
+ *
57
+ * Opaque solid shape batches and opaque model meshes join it, through {@link useOccluder}.
58
+ * Translucent batches of either kind stay out, because a surface you can see through must not cast
59
+ * full occlusion. Planar batches stay out, which is what keeps this term and the contact shadow
60
+ * disjoint.
61
+ */
62
+ export const OCCLUSION_LAYER = 1;