@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,384 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { degToRad } from '@expofp/geometry';
3
+ import { useFrame, useThree } from '@react-three/fiber';
4
+ import { useEffect, useLayoutEffect, useRef, useState } from 'react';
5
+ import { Color, Vector3, } from 'three';
6
+ import { useSpace } from '../../space/Space';
7
+ import { clamp } from '../../utils/math';
8
+ import { useSceneEffects } from '../materials/effects/effects-context';
9
+ import { useLightingGui } from './lighting-gui';
10
+ /**
11
+ * Scene lighting: a hemisphere fill along the plan's up axis, and a warm directional light at a
12
+ * fixed pitch on the camera's heading. The one intensity control is `contrast`, and `Lighting`
13
+ * derives both `π`-unit intensities from it every frame — so an up-facing surface (a roof) lands on
14
+ * its authored color and no surface the design accounts for clips.
15
+ *
16
+ * That is also why the renderer skips tone mapping (see `Renderer.tsx` and `ExternalCanvas.tsx`): a
17
+ * curve brings an unbounded light sum back into range, and `contrast` keeps this rig's sum in range
18
+ * to begin with, at every pitch.
19
+ *
20
+ * Only lit materials see the lights — solid shape batches and models. Every 2D primitive draws
21
+ * through a `MeshBasicMaterial`, so lines, text and images are unaffected.
22
+ *
23
+ * **The lights sit inside the scene's world matrix**, so every direction below is in the plan's own
24
+ * space, taken there through {@link Space.matrixInverse}. Three reads a light's direction from where
25
+ * it ends up in the *world*, so the two transforms cancel and the light points where it was aimed
26
+ * under any fit. The directional light needs its target under the same matrix for that, since its
27
+ * direction is the difference of two world positions.
28
+ * @param props - The values a caller set, any subset; see {@link LightingProps}.
29
+ * @returns The two lights, and the directional light's target.
30
+ */
31
+ export function Lighting(props) {
32
+ const { contrast, skyColor, groundColor, directionalColor } = props;
33
+ // Down to the primitives here rather than in the effect below, so the effect runs when a value
34
+ // really moved. A caller writing `lighting={{ shadow: {...} }}` inline hands a fresh object every
35
+ // render, and an object in the dependency list would re-apply and redraw on each of them.
36
+ const { color: shadowColor, contactDistance, contactStrength, ambientOcclusion, aoRadius, aoStrength, } = props.shadow ?? {};
37
+ const fillRef = useRef(null);
38
+ const directionalRef = useRef(null);
39
+ const targetRef = useRef(null);
40
+ const space = useSpace();
41
+ const effects = useSceneEffects();
42
+ const invalidate = useThree((state) => state.invalidate);
43
+ // `useState` rather than `useRef` so the render body may read it, the way `useDimPatch` holds its
44
+ // patch.
45
+ const [settings] = useState(() => ({ ...LIGHT_DEFAULTS }));
46
+ // What each light's color was last set from, so a hex string is parsed on change, not per frame.
47
+ const appliedRef = useRef({ sky: '', ground: '', directional: '' });
48
+ useLayoutEffect(() => {
49
+ directionalRef.current.target = targetRef.current;
50
+ }, []);
51
+ // The one place a prop reaches the two mutable objects the frame and the shaders read. Both are
52
+ // written through a function of this module rather than inline, because the React Compiler treats
53
+ // what a hook returned as frozen for the closure's own body — the discipline `useOccluder` and
54
+ // `OcclusionPasses` keep for their own writes.
55
+ useEffect(() => {
56
+ applyLightValues(settings, { contrast, skyColor, groundColor, directionalColor });
57
+ applyShadowValues(effects, {
58
+ color: shadowColor,
59
+ contactDistance,
60
+ contactStrength,
61
+ ambientOcclusion,
62
+ aoRadius,
63
+ aoStrength,
64
+ });
65
+ // Nothing here re-renders and nothing here draws: the frame reads the rig and the shaders read
66
+ // the uniforms, so a change needs a frame asking for it.
67
+ invalidate();
68
+ }, [
69
+ settings,
70
+ effects,
71
+ invalidate,
72
+ contrast,
73
+ skyColor,
74
+ groundColor,
75
+ directionalColor,
76
+ shadowColor,
77
+ contactDistance,
78
+ contactStrength,
79
+ ambientOcclusion,
80
+ aoRadius,
81
+ aoStrength,
82
+ ]);
83
+ // Built here because `Lighting` is mounted once per scene and owns the visual rig. A no-op while
84
+ // the debug UI is off.
85
+ useLightingGui(settings);
86
+ // Runs only on frames something already asked for, and camera movement is what asks for them.
87
+ useFrame((state) => {
88
+ const fill = fillRef.current;
89
+ const directional = directionalRef.current;
90
+ aimDirectionalLight(state.camera, space, settings, directional.position);
91
+ // The plan's up axis, as a point whose world position lands back on it — which is what three
92
+ // reads a hemisphere light's sky axis from. Recomputed rather than memoized, because the space
93
+ // is refitted in place and never changes identity.
94
+ fill.position
95
+ .setFromMatrixColumn(space.matrix, 2)
96
+ .normalize()
97
+ .applyMatrix4(space.matrixInverse);
98
+ // Here rather than as JSX props: the GUI mutates `settings` in place and asks for a redraw, not
99
+ // for a React render, so a prop would hold whatever the value was at mount. `applyColor` parses
100
+ // a hex string only when it changed, so a still rig costs three comparisons a frame.
101
+ applyColor(fill.color, settings.skyColor, appliedRef.current, 'sky');
102
+ applyColor(fill.groundColor, settings.groundColor, appliedRef.current, 'ground');
103
+ applyColor(directional.color, settings.directionalColor, appliedRef.current, 'directional');
104
+ // The pitch as aimed, not `settings.pitchDegrees`: `trackPitch` takes it from the camera instead.
105
+ const pitch = Math.acos(clamp(directional.position.z, -1, 1));
106
+ // Defensive only: the slider never leaves `0`–`1`, but a value set out of band must not hand an
107
+ // over-range contrast to a ceiling that assumes one.
108
+ const contrast = clamp(settings.contrast, 0, 1);
109
+ const { hemisphereIntensity, directionalIntensity } = intensitiesFromContrast(contrast, pitch, fill.color, fill.groundColor, directional.color);
110
+ fill.intensity = hemisphereIntensity * Math.PI;
111
+ directional.intensity = directionalIntensity * Math.PI;
112
+ });
113
+ return (_jsxs(_Fragment, { children: [_jsx("hemisphereLight", { ref: fillRef, color: LIGHT_DEFAULTS.skyColor, groundColor: LIGHT_DEFAULTS.groundColor, intensity: DEFAULT_INTENSITIES.hemisphereIntensity * Math.PI }), _jsx("directionalLight", { ref: directionalRef, color: LIGHT_DEFAULTS.directionalColor, intensity: DEFAULT_INTENSITIES.directionalIntensity * Math.PI }), _jsx("object3D", { ref: targetRef })] }));
114
+ }
115
+ /**
116
+ * Writes the rig values a caller gave, and leaves every value the caller omitted as it stands.
117
+ *
118
+ * One named write per value rather than an `Object.assign`, because a destructured prop is present
119
+ * and `undefined` when the caller left it out, and assigning that would wipe the default under it.
120
+ * @param settings - The mount's own rig, written in place.
121
+ * @param values - What the caller gave, any subset of it.
122
+ */
123
+ function applyLightValues(settings, values) {
124
+ if (values.contrast !== undefined)
125
+ settings.contrast = values.contrast;
126
+ if (values.skyColor !== undefined)
127
+ settings.skyColor = values.skyColor;
128
+ if (values.groundColor !== undefined)
129
+ settings.groundColor = values.groundColor;
130
+ if (values.directionalColor !== undefined)
131
+ settings.directionalColor = values.directionalColor;
132
+ }
133
+ /**
134
+ * Writes the darkening values a caller gave onto the scene's own effect state, and leaves every
135
+ * value the caller omitted as it stands.
136
+ *
137
+ * Every write lands on a uniform's `value` or on a plain field a frame callback reads, which is what
138
+ * lets a caller move the shade of a mounted scene with no shader recompile behind it. The lit
139
+ * programs captured these very objects when they were compiled.
140
+ *
141
+ * The shade is written twice on purpose. three holds a `Color` in the working space and a color
142
+ * controller reads a hex string, so the uniform and the string beside it are one value in two forms.
143
+ * @param effects - The mounted scene's own effect state, written in place.
144
+ * @param values - What the caller gave, any subset of it.
145
+ */
146
+ function applyShadowValues(effects, values) {
147
+ if (values.color !== undefined) {
148
+ effects.shadowHex = values.color;
149
+ effects.shadowColor.value.set(values.color);
150
+ }
151
+ if (values.contactDistance !== undefined)
152
+ effects.shadowDistance.value = values.contactDistance;
153
+ if (values.contactStrength !== undefined)
154
+ effects.shadowStrength.value = values.contactStrength;
155
+ if (values.ambientOcclusion !== undefined)
156
+ effects.enabled = values.ambientOcclusion;
157
+ if (values.aoRadius !== undefined)
158
+ effects.radius = values.aoRadius;
159
+ if (values.aoStrength !== undefined)
160
+ effects.aoStrength.value = values.aoStrength;
161
+ }
162
+ /**
163
+ * Aims the directional light, in the plan's own space, where up is `+z`.
164
+ *
165
+ * With `trackPitch` on it copies the camera's backward axis, which is the pre-overhaul behavior.
166
+ * Otherwise it takes the plan heading from the camera and holds the configured pitch. A fixed pitch
167
+ * pins the exact-albedo surface to the up-facing one, which on a floor plan carries the brand color.
168
+ *
169
+ * **The heading comes from the camera's up axis.** The forward axis points straight down at pitch
170
+ * `0`, the default resting pitch, so its ground projection is zero there and the heading would be
171
+ * undefined. The up axis is the other way round: its ground projection is longest at pitch `0` and
172
+ * shortest at `MAX_PITCH`, where it is still `cos(85°)` of unit length. It never vanishes, so
173
+ * nothing here needs a guard, and the light turns with the plan's heading even from overhead.
174
+ *
175
+ * That reading holds in external mode too, where the camera is a decomposition of the host's own
176
+ * matrix rather than one this renderer placed: `setExternalCameraProjection` measures the roll off
177
+ * that matrix instead of choosing an up axis, precisely so the axis means here what it means under
178
+ * the fit.
179
+ *
180
+ * The ground projection points *against* the heading. At tilt `θ` the camera's up axis is
181
+ * `(-cos θ · heading, sin θ)`, so negating recovers the direction the camera is on.
182
+ * @param camera - The scene camera.
183
+ * @param space - The scene's space, for the plan-to-world mapping.
184
+ * @param settings - The rig, for `trackPitch` and the pitch.
185
+ * @param target - Receives the direction.
186
+ * @returns `target`, unit length.
187
+ */
188
+ function aimDirectionalLight(camera, space, settings, target) {
189
+ // Columns 1 and 2 of `matrixWorld` are the camera's up and backward axes. Read directly rather
190
+ // than through `Camera.getWorldDirection`, which re-runs `updateWorldMatrix` and leaves
191
+ // `matrixWorldNeedsUpdate` set on every call — which kept the scene dirty and no screenshot ever
192
+ // settled.
193
+ if (settings.trackPitch) {
194
+ return target
195
+ .setFromMatrixColumn(camera.matrixWorld, 2)
196
+ .transformDirection(space.matrixInverse);
197
+ }
198
+ const up = _up.setFromMatrixColumn(camera.matrixWorld, 1).transformDirection(space.matrixInverse);
199
+ const pitch = degToRad(settings.pitchDegrees);
200
+ return target
201
+ .set(-up.x, -up.y, 0)
202
+ .normalize()
203
+ .multiplyScalar(Math.sin(pitch))
204
+ .setZ(Math.cos(pitch));
205
+ }
206
+ /**
207
+ * Sets a light's color from a hex string, parsing only when the string changed.
208
+ *
209
+ * `Color.set` converts sRGB to the working space, and a string moves only when a color controller
210
+ * writes a new one.
211
+ * @param target - The color to write.
212
+ * @param hex - The CSS hex string to set it from.
213
+ * @param applied - What each color was last set from.
214
+ * @param key - Which entry of `applied` this color owns.
215
+ */
216
+ function applyColor(target, hex, applied, key) {
217
+ if (applied[key] === hex)
218
+ return;
219
+ target.set(hex);
220
+ applied[key] = hex;
221
+ }
222
+ /**
223
+ * The largest directional intensity (`d`, in `π` units) a wall facing the light can take at this
224
+ * pitch and these colors before some channel of it clips — `contrast = 1`'s own `d`.
225
+ *
226
+ * **No closed form.** {@link intensitiesFromContrast} ties `h` to `d` through a `min` over channels,
227
+ * which folds a channel selection into the middle of the wall's own expression; solving that
228
+ * algebraically means guessing which channel governs *before* knowing `d`, and the answer can change
229
+ * with `d`. Bisecting sidesteps the guess entirely and is exact for any sky, ground and directional
230
+ * color, at the cost of a loop three's own frame budget does not notice.
231
+ *
232
+ * **The search interval's top is where `h` would reach `0`** — pure directional light, nothing from
233
+ * the hemisphere — which is `1 / (max_c(k_c) · cos θ)`. Past it {@link intensitiesFromContrast} would
234
+ * have to hand back a negative hemisphere intensity, which no real light has, so the search never
235
+ * looks there. `litWallPeak` is monotone increasing in `d` once `h(d)` is substituted in (a fact
236
+ * about this rig's own shape, not something this function re-derives), so a single bisection over
237
+ * this interval finds the point where the lit wall reaches exactly `1`.
238
+ *
239
+ * **A shallow pitch may never reach `1` at all** — at or below `45°` a wall never receives more
240
+ * directional light than a roof, so raising `d` only ever pulls the wall *toward* the roof's own
241
+ * value, never past it. Detected by checking the interval's own top rather than by an angle test,
242
+ * so the same check also covers a pitch just above `45°` whose colors still cannot clip a wall within
243
+ * this interval. When that happens the interval's top is `d_max` outright: it is the most directional
244
+ * light the rig can take at all, clipping or not, so it is also the most `contrast = 1` can mean.
245
+ *
246
+ * **The ceiling falls as the pitch steepens past that point**, which reads backward on first glance:
247
+ * a steeper pitch sends relatively more of a fixed `d` into the wall (`sin θ` rising) and relatively
248
+ * less into the roof (`cos θ` falling), so less directional intensity is needed before the wall
249
+ * clips. `contrast = 1` is always the most the rig can take at its own pitch, but "the most" is a
250
+ * smaller number at a grazing pitch than at a gentle one.
251
+ * @param pitchRadians - The directional light's angle from the plan's up axis, as aimed.
252
+ * @param sky - The hemisphere's sky color, linear.
253
+ * @param ground - The hemisphere's ground color, linear.
254
+ * @param directionalColor - The directional light's color, linear.
255
+ * @returns The directional intensity ceiling, in `π` units.
256
+ */
257
+ function maxDirectional(pitchRadians, sky, ground, directionalColor) {
258
+ const cos = Math.cos(pitchRadians);
259
+ let peakDirectionalColor = 0;
260
+ for (const channel of CHANNELS) {
261
+ peakDirectionalColor = Math.max(peakDirectionalColor, directionalColor[channel]);
262
+ }
263
+ // Guards a grazing or past-vertical pitch, where `cos` is at or below `0`: without this the
264
+ // interval's top would be infinite, negative, or both.
265
+ const ceiling = 1 / Math.max(peakDirectionalColor * cos, MIN_PEAK);
266
+ if (litWallPeak(ceiling, pitchRadians, sky, ground, directionalColor) < 1)
267
+ return ceiling;
268
+ let low = 0;
269
+ let high = ceiling;
270
+ for (let iteration = 0; iteration < BISECTION_ITERATIONS; iteration++) {
271
+ const mid = (low + high) / 2;
272
+ if (litWallPeak(mid, pitchRadians, sky, ground, directionalColor) < 1)
273
+ low = mid;
274
+ else
275
+ high = mid;
276
+ }
277
+ return (low + high) / 2;
278
+ }
279
+ /**
280
+ * The hemisphere and directional intensities, in `π` units, that pin an up-facing surface (a roof)
281
+ * to exactly the authored sky color on whichever channel would clip first.
282
+ *
283
+ * `d` is `contrast` of the way from `0` to {@link maxDirectional}'s own ceiling for this pitch and
284
+ * these colors — the normalization that gives `contrast` its fixed `0`–`1` range, in place of an
285
+ * intensity ratio whose usable span moved with the pitch. `h` follows by solving three's own roof
286
+ * expression, `h·s + d·k·cos θ`, for the value that lands each channel on exactly `1` and taking the
287
+ * **minimum** across channels: the same "pin the brightest channel, leave the rest a shade under"
288
+ * rule the roof pin has always used, read off in the other direction — the channel needing the least
289
+ * `h` to reach `1` is the one that would otherwise run past it, so giving every channel that same,
290
+ * smaller `h` leaves it exactly on `1` and the others a shade under, which reads as a faint tint
291
+ * rather than a lost highlight, and holds even for a tinted sky.
292
+ * @param contrast - `0` to `1`, already assumed in range.
293
+ * @param pitchRadians - The directional light's angle from the plan's up axis, as aimed.
294
+ * @param sky - The hemisphere's sky color, linear.
295
+ * @param ground - The hemisphere's ground color, linear.
296
+ * @param directionalColor - The directional light's color, linear.
297
+ * @returns The hemisphere and directional intensities, in `π` units.
298
+ */
299
+ function intensitiesFromContrast(contrast, pitchRadians, sky, ground, directionalColor) {
300
+ const directionalIntensity = contrast * maxDirectional(pitchRadians, sky, ground, directionalColor);
301
+ const hemisphereIntensity = hemisphereForDirectional(directionalIntensity, pitchRadians, sky, directionalColor);
302
+ return { hemisphereIntensity, directionalIntensity };
303
+ }
304
+ /**
305
+ * The hemisphere intensity that pins a roof to exactly `1` on its brightest channel, given a
306
+ * directional intensity already chosen.
307
+ *
308
+ * Solving `h·s_c + d·k_c·cos θ = 1` for `h` gives one candidate per channel; the **minimum** of them
309
+ * is what leaves every channel at or under `1` (see {@link intensitiesFromContrast}'s own doc for
310
+ * why the minimum, not the maximum, is the pinning rule stated in terms of `h`).
311
+ * @param directionalIntensity - `d`, in `π` units.
312
+ * @param pitchRadians - The directional light's angle from the plan's up axis, as aimed.
313
+ * @param sky - The hemisphere's sky color, linear.
314
+ * @param directionalColor - The directional light's color, linear.
315
+ * @returns The hemisphere intensity, in `π` units.
316
+ */
317
+ function hemisphereForDirectional(directionalIntensity, pitchRadians, sky, directionalColor) {
318
+ const cos = Math.cos(pitchRadians);
319
+ let hemisphereIntensity = Infinity;
320
+ for (const channel of CHANNELS) {
321
+ // Guards a black sky channel, which would otherwise divide by zero. A channel that dark can
322
+ // never carry a meaningful hemisphere contribution anyway, so flooring the divisor costs nothing
323
+ // real.
324
+ const skyChannel = Math.max(sky[channel], MIN_SKY);
325
+ hemisphereIntensity = Math.min(hemisphereIntensity, (1 - directionalIntensity * directionalColor[channel] * cos) / skyChannel);
326
+ }
327
+ return hemisphereIntensity;
328
+ }
329
+ /**
330
+ * The peak channel of a wall facing the light, given a directional intensity already chosen and the
331
+ * hemisphere intensity that pairs with it.
332
+ * @param directionalIntensity - `d`, in `π` units.
333
+ * @param pitchRadians - The directional light's angle from the plan's up axis, as aimed.
334
+ * @param sky - The hemisphere's sky color, linear.
335
+ * @param ground - The hemisphere's ground color, linear.
336
+ * @param directionalColor - The directional light's color, linear.
337
+ * @returns The lit wall's brightest channel.
338
+ */
339
+ function litWallPeak(directionalIntensity, pitchRadians, sky, ground, directionalColor) {
340
+ const hemisphereIntensity = hemisphereForDirectional(directionalIntensity, pitchRadians, sky, directionalColor);
341
+ const sin = Math.sin(pitchRadians);
342
+ let peak = -Infinity;
343
+ for (const channel of CHANNELS) {
344
+ const wallMix = (ground[channel] + sky[channel]) / 2;
345
+ peak = Math.max(peak, hemisphereIntensity * wallMix + directionalIntensity * directionalColor[channel] * sin);
346
+ }
347
+ return peak;
348
+ }
349
+ /**
350
+ * The rig this design agreed, and what each mount starts from.
351
+ *
352
+ * At any `contrast` the roof lands on `1.00`. At `contrast = 1` the lit wall reaches `1.00` as well,
353
+ * and the separation section 03 of the design target asks for disappears, so the default stays well
354
+ * under it.
355
+ */
356
+ const LIGHT_DEFAULTS = {
357
+ trackPitch: false,
358
+ pitchDegrees: 38,
359
+ contrast: 0.3,
360
+ skyColor: '#ffffff',
361
+ groundColor: '#b9c1cc',
362
+ directionalColor: '#fff3e6',
363
+ };
364
+ /** The color channels clipping is decided on, one at a time. */
365
+ const CHANNELS = ['r', 'g', 'b'];
366
+ /** Bisection steps {@link maxDirectional} takes — cheap on a frame, and far past the precision the GUI's own slider step can show. */
367
+ const BISECTION_ITERATIONS = 20;
368
+ /**
369
+ * The smallest sky channel {@link hemisphereForDirectional} divides by, so a black sky channel hands
370
+ * back a large, finite hemisphere intensity rather than `Infinity`.
371
+ */
372
+ const MIN_SKY = 1e-6;
373
+ /**
374
+ * The smallest `directionalColor · cos θ` peak {@link maxDirectional} divides by, so a grazing or
375
+ * past-vertical pitch hands back a large, finite ceiling rather than `Infinity` or a negative number.
376
+ */
377
+ const MIN_PEAK = 1e-6;
378
+ /**
379
+ * {@link LIGHT_DEFAULTS}' own intensities, computed once so a tree that mounts and never draws still
380
+ * holds a sensible rig.
381
+ */
382
+ const DEFAULT_INTENSITIES = intensitiesFromContrast(LIGHT_DEFAULTS.contrast, degToRad(LIGHT_DEFAULTS.pitchDegrees), new Color(LIGHT_DEFAULTS.skyColor), new Color(LIGHT_DEFAULTS.groundColor), new Color(LIGHT_DEFAULTS.directionalColor));
383
+ /** The camera's up axis, reused each frame so aiming allocates nothing. */
384
+ const _up = new Vector3();
@@ -0,0 +1,23 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * Screen-space ambient occlusion, in three passes a frame.
4
+ *
5
+ * The prepass draws {@link OCCLUSION_LAYER} into the one target that carries the depth texture. The
6
+ * AO pass reads that depth and writes how closed in each fragment is. The blur pass reads that
7
+ * output and the same depth, and writes what the lit materials read. Both quad passes bind a target
8
+ * with no depth attachment, because WebGL2 leaves a draw that samples its own framebuffer undefined.
9
+ * See `occlusion-targets.ts` for the three targets and the two textures under them.
10
+ *
11
+ * All three run on a frustum wider than the visible one. Geometry just outside the view then still
12
+ * occludes what is inside it, so no seam of missing darkening runs down the screen edges.
13
+ *
14
+ * Three things switch the passes off, and each one clears `aoActive` first. The debug GUI clears
15
+ * `enabled`, or the `occluders` registry is empty, and both of those free the render targets. A failed
16
+ * `renderTargets.sync()` is a capability gate that frees nothing, because it built nothing.
17
+ *
18
+ * The default frame priority puts this after the camera, which settles at negative priorities. It also
19
+ * puts this before R3F's own draw, so the texture is ready before any material samples it.
20
+ * @returns Nothing. This renders no scene node.
21
+ */
22
+ export declare function OcclusionPasses(): ReactNode;
23
+ //# sourceMappingURL=OcclusionPasses.d.ts.map