@expofp/renderer 3.2.4 → 3.27.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 (228) hide show
  1. package/README.md +111 -0
  2. package/dist/Renderer.d.ts +88 -0
  3. package/dist/Renderer.js +53 -0
  4. package/dist/controls/CameraControls.d.ts +102 -0
  5. package/dist/controls/CameraControls.js +239 -0
  6. package/dist/controls/camera-api.d.ts +206 -0
  7. package/dist/controls/camera-api.js +181 -0
  8. package/dist/controls/camera-move.d.ts +13 -0
  9. package/dist/controls/camera-move.js +18 -0
  10. package/dist/controls/camera-state.d.ts +40 -0
  11. package/dist/controls/camera-state.js +65 -0
  12. package/dist/controls/gestures.d.ts +70 -0
  13. package/dist/controls/gestures.js +84 -0
  14. package/dist/controls/handlers/Inertia.d.ts +111 -0
  15. package/dist/controls/handlers/Inertia.js +215 -0
  16. package/dist/controls/handlers/Pan.d.ts +23 -0
  17. package/dist/controls/handlers/Pan.js +32 -0
  18. package/dist/controls/handlers/Pitch.d.ts +94 -0
  19. package/dist/controls/handlers/Pitch.js +191 -0
  20. package/dist/controls/handlers/Roll.d.ts +103 -0
  21. package/dist/controls/handlers/Roll.js +182 -0
  22. package/dist/controls/handlers/Zoom.d.ts +15 -0
  23. package/dist/controls/handlers/Zoom.js +26 -0
  24. package/dist/controls/index.d.ts +56 -0
  25. package/dist/controls/index.js +58 -0
  26. package/dist/controls/zoom-to.d.ts +89 -0
  27. package/dist/controls/zoom-to.js +91 -0
  28. package/dist/core/InstanceUniforms.d.ts +93 -0
  29. package/dist/core/InstanceUniforms.js +266 -0
  30. package/dist/core/SquareDataTexture.d.ts +277 -0
  31. package/dist/core/SquareDataTexture.js +476 -0
  32. package/dist/core/batch/BatchedMesh.d.ts +74 -0
  33. package/dist/core/batch/BatchedMesh.js +149 -0
  34. package/dist/core/instanced/FrustumCulling.d.ts +141 -0
  35. package/dist/core/instanced/FrustumCulling.js +246 -0
  36. package/dist/core/instanced/InstancedMesh.d.ts +302 -0
  37. package/dist/core/instanced/InstancedMesh.js +497 -0
  38. package/dist/core/instanced/Instances.d.ts +119 -0
  39. package/dist/core/instanced/Instances.js +230 -0
  40. package/dist/core/instanced/Raycasting.d.ts +118 -0
  41. package/dist/core/instanced/Raycasting.js +134 -0
  42. package/dist/core/instanced/instanced-material.d.ts +77 -0
  43. package/dist/core/instanced/instanced-material.js +224 -0
  44. package/dist/core/lines/BatchedLines.d.ts +135 -0
  45. package/dist/core/lines/BatchedLines.js +213 -0
  46. package/dist/core/lines/FrustumCulling.d.ts +43 -0
  47. package/dist/core/lines/FrustumCulling.js +85 -0
  48. package/dist/core/lines/Raycasting.d.ts +44 -0
  49. package/dist/core/lines/Raycasting.js +91 -0
  50. package/dist/core/lines/lines-material.d.ts +47 -0
  51. package/dist/core/lines/lines-material.js +115 -0
  52. package/dist/core/shader-source.d.ts +50 -0
  53. package/dist/core/shader-source.js +84 -0
  54. package/dist/core/text/BatchedText.d.ts +227 -0
  55. package/dist/core/text/BatchedText.js +467 -0
  56. package/dist/core/text/MemberTexture.d.ts +90 -0
  57. package/dist/core/text/MemberTexture.js +194 -0
  58. package/dist/core/text/Text.d.ts +73 -0
  59. package/dist/core/text/Text.js +215 -0
  60. package/dist/core/text/batched-text-material.d.ts +56 -0
  61. package/dist/core/text/batched-text-material.js +121 -0
  62. package/dist/core/text/glyph-packing.d.ts +17 -0
  63. package/dist/core/text/glyph-packing.js +61 -0
  64. package/dist/core/typed-array.d.ts +16 -0
  65. package/dist/core/typed-array.js +20 -0
  66. package/dist/debug/components/DebugPanelProvider.d.ts +20 -0
  67. package/dist/debug/components/DebugPanelProvider.js +38 -0
  68. package/dist/debug/components/DebugTools.d.ts +22 -0
  69. package/dist/debug/components/DebugTools.js +28 -0
  70. package/dist/debug/components/Gui.d.ts +20 -0
  71. package/dist/debug/components/Gui.js +32 -0
  72. package/dist/debug/components/MemoryInfo.d.ts +22 -0
  73. package/dist/debug/components/MemoryInfo.js +42 -0
  74. package/dist/debug/components/Panel.d.ts +31 -0
  75. package/dist/debug/components/Panel.js +98 -0
  76. package/dist/debug/components/Stats.d.ts +9 -0
  77. package/dist/debug/components/Stats.js +24 -0
  78. package/dist/debug/debug-context.d.ts +41 -0
  79. package/dist/debug/debug-context.js +59 -0
  80. package/dist/debug/index.d.ts +51 -0
  81. package/dist/debug/index.js +64 -0
  82. package/dist/events/LayoutDriver.d.ts +31 -0
  83. package/dist/events/LayoutDriver.js +88 -0
  84. package/dist/events/PointerMissed.d.ts +23 -0
  85. package/dist/events/PointerMissed.js +58 -0
  86. package/dist/events/def-culling.d.ts +46 -0
  87. package/dist/events/def-culling.js +69 -0
  88. package/dist/events/layout-targets.d.ts +104 -0
  89. package/dist/events/layout-targets.js +245 -0
  90. package/dist/events/pointer.d.ts +118 -0
  91. package/dist/events/pointer.js +168 -0
  92. package/dist/external/ExternalCanvas.d.ts +56 -0
  93. package/dist/external/ExternalCanvas.js +178 -0
  94. package/dist/external/external-camera.d.ts +17 -0
  95. package/dist/external/external-camera.js +82 -0
  96. package/dist/external/external-events.d.ts +14 -0
  97. package/dist/external/external-events.js +33 -0
  98. package/dist/index.d.ts +11 -783
  99. package/dist/index.js +12 -8028
  100. package/dist/objects/Background.d.ts +31 -0
  101. package/dist/objects/Background.js +37 -0
  102. package/dist/objects/Lighting.d.ts +20 -0
  103. package/dist/objects/Lighting.js +85 -0
  104. package/dist/objects/containers/SlotContainer.d.ts +266 -0
  105. package/dist/objects/containers/SlotContainer.js +251 -0
  106. package/dist/objects/containers/hooks.d.ts +46 -0
  107. package/dist/objects/containers/hooks.js +118 -0
  108. package/dist/objects/containers/index.d.ts +6 -0
  109. package/dist/objects/containers/index.js +4 -0
  110. package/dist/objects/containers/keys.d.ts +10 -0
  111. package/dist/objects/containers/keys.js +18 -0
  112. package/dist/objects/containers/mesh-writes.d.ts +106 -0
  113. package/dist/objects/containers/mesh-writes.js +97 -0
  114. package/dist/objects/containers/props.d.ts +23 -0
  115. package/dist/objects/containers/props.js +1 -0
  116. package/dist/objects/images/ImageInstance.d.ts +29 -0
  117. package/dist/objects/images/ImageInstance.js +83 -0
  118. package/dist/objects/images/Images.d.ts +37 -0
  119. package/dist/objects/images/Images.js +204 -0
  120. package/dist/objects/images/atlas-packer.d.ts +128 -0
  121. package/dist/objects/images/atlas-packer.js +315 -0
  122. package/dist/objects/images/image-container.d.ts +51 -0
  123. package/dist/objects/images/image-container.js +70 -0
  124. package/dist/objects/images/index.d.ts +2 -0
  125. package/dist/objects/images/index.js +1 -0
  126. package/dist/objects/images/texture-budget.d.ts +120 -0
  127. package/dist/objects/images/texture-budget.js +215 -0
  128. package/dist/objects/layer/Layer.d.ts +27 -0
  129. package/dist/objects/layer/Layer.js +114 -0
  130. package/dist/objects/layer/index.d.ts +2 -0
  131. package/dist/objects/layer/index.js +1 -0
  132. package/dist/objects/layer/mount-scheduler.d.ts +102 -0
  133. package/dist/objects/layer/mount-scheduler.js +145 -0
  134. package/dist/objects/layer/visibility-gui.d.ts +19 -0
  135. package/dist/objects/layer/visibility-gui.js +93 -0
  136. package/dist/objects/lines/LineInstance.d.ts +21 -0
  137. package/dist/objects/lines/LineInstance.js +73 -0
  138. package/dist/objects/lines/Lines.d.ts +17 -0
  139. package/dist/objects/lines/Lines.js +29 -0
  140. package/dist/objects/lines/index.d.ts +2 -0
  141. package/dist/objects/lines/index.js +1 -0
  142. package/dist/objects/lines/line-container.d.ts +35 -0
  143. package/dist/objects/lines/line-container.js +40 -0
  144. package/dist/objects/materials/ColorMaterial.d.ts +85 -0
  145. package/dist/objects/materials/ColorMaterial.js +89 -0
  146. package/dist/objects/materials/LineMaterial.d.ts +44 -0
  147. package/dist/objects/materials/LineMaterial.js +72 -0
  148. package/dist/objects/materials/ModelMaterial.d.ts +41 -0
  149. package/dist/objects/materials/ModelMaterial.js +84 -0
  150. package/dist/objects/materials/TextureMaterial.d.ts +30 -0
  151. package/dist/objects/materials/TextureMaterial.js +29 -0
  152. package/dist/objects/materials/dim.d.ts +123 -0
  153. package/dist/objects/materials/dim.js +213 -0
  154. package/dist/objects/materials/shared.d.ts +6 -0
  155. package/dist/objects/materials/shared.js +51 -0
  156. package/dist/objects/models/ModelInstance.d.ts +22 -0
  157. package/dist/objects/models/ModelInstance.js +33 -0
  158. package/dist/objects/models/Models.d.ts +22 -0
  159. package/dist/objects/models/Models.js +177 -0
  160. package/dist/objects/models/gltf-cache.d.ts +17 -0
  161. package/dist/objects/models/gltf-cache.js +50 -0
  162. package/dist/objects/models/index.d.ts +2 -0
  163. package/dist/objects/models/index.js +1 -0
  164. package/dist/objects/models/model-asset.d.ts +48 -0
  165. package/dist/objects/models/model-asset.js +77 -0
  166. package/dist/objects/models/model-container.d.ts +139 -0
  167. package/dist/objects/models/model-container.js +227 -0
  168. package/dist/objects/shapes/ShapeInstance.d.ts +26 -0
  169. package/dist/objects/shapes/ShapeInstance.js +40 -0
  170. package/dist/objects/shapes/Shapes.d.ts +21 -0
  171. package/dist/objects/shapes/Shapes.js +120 -0
  172. package/dist/objects/shapes/index.d.ts +2 -0
  173. package/dist/objects/shapes/index.js +1 -0
  174. package/dist/objects/shapes/shape-container.d.ts +165 -0
  175. package/dist/objects/shapes/shape-container.js +272 -0
  176. package/dist/objects/shapes/shape-geometry.d.ts +27 -0
  177. package/dist/objects/shapes/shape-geometry.js +94 -0
  178. package/dist/objects/text/TextInstance.d.ts +22 -0
  179. package/dist/objects/text/TextInstance.js +95 -0
  180. package/dist/objects/text/Texts.d.ts +30 -0
  181. package/dist/objects/text/Texts.js +41 -0
  182. package/dist/objects/text/index.d.ts +2 -0
  183. package/dist/objects/text/index.js +1 -0
  184. package/dist/objects/text/text-container.d.ts +105 -0
  185. package/dist/objects/text/text-container.js +206 -0
  186. package/dist/objects/text/text-layout.d.ts +83 -0
  187. package/dist/objects/text/text-layout.js +154 -0
  188. package/dist/space/Camera.d.ts +103 -0
  189. package/dist/space/Camera.js +321 -0
  190. package/dist/space/Scene.d.ts +44 -0
  191. package/dist/space/Scene.js +112 -0
  192. package/dist/space/Space.d.ts +288 -0
  193. package/dist/space/Space.js +329 -0
  194. package/dist/space/VisibleRect.d.ts +72 -0
  195. package/dist/space/VisibleRect.js +129 -0
  196. package/dist/space/camera-handle.d.ts +67 -0
  197. package/dist/space/camera-handle.js +63 -0
  198. package/dist/space/camera-kind.d.ts +26 -0
  199. package/dist/space/camera-kind.js +30 -0
  200. package/dist/space/canvas-size.d.ts +24 -0
  201. package/dist/space/canvas-size.js +20 -0
  202. package/dist/space/clipping.d.ts +55 -0
  203. package/dist/space/clipping.js +108 -0
  204. package/dist/space/layout-measure.d.ts +65 -0
  205. package/dist/space/layout-measure.js +173 -0
  206. package/dist/space/viewport.d.ts +112 -0
  207. package/dist/space/viewport.js +67 -0
  208. package/dist/types/defs.d.ts +169 -0
  209. package/dist/types/defs.js +1 -0
  210. package/dist/types/events.d.ts +183 -0
  211. package/dist/types/events.js +1 -0
  212. package/dist/types/guards.d.ts +14 -0
  213. package/dist/types/guards.js +42 -0
  214. package/dist/update/index.d.ts +3 -0
  215. package/dist/update/index.js +2 -0
  216. package/dist/update/slot-writers.d.ts +30 -0
  217. package/dist/update/slot-writers.js +72 -0
  218. package/dist/update/store.d.ts +41 -0
  219. package/dist/update/store.js +98 -0
  220. package/dist/utils/arrays.d.ts +13 -0
  221. package/dist/utils/arrays.js +20 -0
  222. package/dist/utils/colors.d.ts +19 -0
  223. package/dist/utils/colors.js +27 -0
  224. package/dist/utils/logging.d.ts +26 -0
  225. package/dist/utils/logging.js +47 -0
  226. package/dist/utils/math.d.ts +24 -0
  227. package/dist/utils/math.js +27 -0
  228. package/package.json +67 -19
@@ -0,0 +1,17 @@
1
+ import { type GlyphsGeometry, type TextRenderInfo } from 'troika-three-text';
2
+ import { type TextExt } from './Text';
3
+ /**
4
+ * Rewrites a batch's glyph attributes from its members' render info.
5
+ *
6
+ * Two orders meet here, which is the whole point of the pass: glyph runs are appended in *paint*
7
+ * order, since draw order is simply the order runs appear in the batched attributes, while the
8
+ * member index each run carries is the member’s stable *id*, which is the row it reads its matrix
9
+ * and uniforms from.
10
+ * @param geometry - The batch's glyph geometry, whose attributes are replaced.
11
+ * @param ordered - The ids to draw, in paint order.
12
+ * @param textAt - The member holding an id, or `undefined` when it holds none.
13
+ * @returns The render info the batch should adopt as its own — any member's will do, since a batch
14
+ * shares one SDF atlas — or `undefined` when no member has synced.
15
+ */
16
+ export declare function packGlyphs(geometry: GlyphsGeometry, ordered: readonly number[], textAt: (memberId: number) => TextExt | undefined): TextRenderInfo | undefined;
17
+ //# sourceMappingURL=glyph-packing.d.ts.map
@@ -0,0 +1,61 @@
1
+ import { DynamicDrawUsage } from 'three';
2
+ import { resizeTypedArray } from '../typed-array';
3
+ import { MEMBER_INDEX_ATTRIBUTE } from './batched-text-material';
4
+ /**
5
+ * Rewrites a batch's glyph attributes from its members' render info.
6
+ *
7
+ * Two orders meet here, which is the whole point of the pass: glyph runs are appended in *paint*
8
+ * order, since draw order is simply the order runs appear in the batched attributes, while the
9
+ * member index each run carries is the member’s stable *id*, which is the row it reads its matrix
10
+ * and uniforms from.
11
+ * @param geometry - The batch's glyph geometry, whose attributes are replaced.
12
+ * @param ordered - The ids to draw, in paint order.
13
+ * @param textAt - The member holding an id, or `undefined` when it holds none.
14
+ * @returns The render info the batch should adopt as its own — any member's will do, since a batch
15
+ * shares one SDF atlas — or `undefined` when no member has synced.
16
+ */
17
+ export function packGlyphs(geometry, ordered, textAt) {
18
+ const attributes = geometry.attributes;
19
+ let memberIndexes = attributes[MEMBER_INDEX_ATTRIBUTE]?.array ?? new Uint16Array(0);
20
+ let glyphIndexes = attributes[GLYPH_INDEX_ATTRIBUTE]?.array ?? new Float32Array(0);
21
+ let glyphBounds = attributes[GLYPH_BOUNDS_ATTRIBUTE]?.array ?? new Float32Array(0);
22
+ // Initial pass to collect total glyph count and resize the arrays if needed.
23
+ let totalGlyphCount = 0;
24
+ // The inherited `Text` code reads `textRenderInfo` for whole-batch state (the SDF atlas, its glyph
25
+ // size and exponent); any member's will do.
26
+ let batchRenderInfo;
27
+ for (const memberId of ordered) {
28
+ const renderInfo = textAt(memberId)?.textRenderInfo;
29
+ if (!renderInfo)
30
+ continue;
31
+ totalGlyphCount += renderInfo.glyphAtlasIndices.length;
32
+ batchRenderInfo = renderInfo;
33
+ }
34
+ if (totalGlyphCount !== memberIndexes.length) {
35
+ memberIndexes = resizeTypedArray(memberIndexes, totalGlyphCount);
36
+ glyphIndexes = resizeTypedArray(glyphIndexes, totalGlyphCount);
37
+ glyphBounds = resizeTypedArray(glyphBounds, totalGlyphCount * 4);
38
+ }
39
+ // Populate batch arrays.
40
+ let glyphIndex = 0;
41
+ for (const memberId of ordered) {
42
+ const renderInfo = textAt(memberId)?.textRenderInfo;
43
+ if (!renderInfo)
44
+ continue;
45
+ const glyphCount = renderInfo.glyphAtlasIndices.length;
46
+ memberIndexes.fill(memberId, glyphIndex, glyphIndex + glyphCount);
47
+ glyphIndexes.set(renderInfo.glyphAtlasIndices, glyphIndex);
48
+ glyphBounds.set(renderInfo.glyphBounds, glyphIndex * 4);
49
+ glyphIndex += glyphCount;
50
+ }
51
+ // Update the geometry attributes.
52
+ geometry.updateAttributeData(MEMBER_INDEX_ATTRIBUTE, memberIndexes, 1);
53
+ geometry.getAttribute(MEMBER_INDEX_ATTRIBUTE).setUsage(DynamicDrawUsage);
54
+ geometry.updateAttributeData(GLYPH_INDEX_ATTRIBUTE, glyphIndexes, 1);
55
+ geometry.updateAttributeData(GLYPH_BOUNDS_ATTRIBUTE, glyphBounds, 4);
56
+ return batchRenderInfo;
57
+ }
58
+ /** One SDF atlas index per glyph. */
59
+ const GLYPH_INDEX_ATTRIBUTE = 'aTroikaGlyphIndex';
60
+ /** Four floats per glyph, bounding the quad it is drawn on. */
61
+ const GLYPH_BOUNDS_ATTRIBUTE = 'aTroikaGlyphBounds';
@@ -0,0 +1,16 @@
1
+ import { type TypedArray } from 'three';
2
+ /**
3
+ * Copies a typed array into a fresh one of a different length, keeping as much of the contents as
4
+ * fits: growing zero-fills the tail, shrinking drops it.
5
+ *
6
+ * The element type is taken from the source's own constructor rather than a parameter, so a caller
7
+ * resizing a `Uint16Array` of member indexes and one resizing a `Float32Array` of glyph bounds share
8
+ * the same call. Always a new array — an in-place `ArrayBuffer.resize` would not do here, because
9
+ * both callers hand the result to something that captured the old buffer (three's `BufferAttribute`,
10
+ * or a `DataTexture`'s image) and must see a distinct one to notice the change.
11
+ * @param source - The array to copy.
12
+ * @param length - The new array's length, in elements.
13
+ * @returns The resized copy.
14
+ */
15
+ export declare function resizeTypedArray<T extends TypedArray>(source: T, length: number): T;
16
+ //# sourceMappingURL=typed-array.d.ts.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Copies a typed array into a fresh one of a different length, keeping as much of the contents as
3
+ * fits: growing zero-fills the tail, shrinking drops it.
4
+ *
5
+ * The element type is taken from the source's own constructor rather than a parameter, so a caller
6
+ * resizing a `Uint16Array` of member indexes and one resizing a `Float32Array` of glyph bounds share
7
+ * the same call. Always a new array — an in-place `ArrayBuffer.resize` would not do here, because
8
+ * both callers hand the result to something that captured the old buffer (three's `BufferAttribute`,
9
+ * or a `DataTexture`'s image) and must see a distinct one to notice the change.
10
+ * @param source - The array to copy.
11
+ * @param length - The new array's length, in elements.
12
+ * @returns The resized copy.
13
+ */
14
+ export function resizeTypedArray(source, length) {
15
+ const constructor = source.constructor;
16
+ const resized = new constructor(length);
17
+ // `subarray` clamps to the source's own length, so this is also the growing case.
18
+ resized.set(source.subarray(0, length));
19
+ return resized;
20
+ }
@@ -0,0 +1,20 @@
1
+ import { type ReactNode } from 'react';
2
+ /** Props for {@link DebugPanelProvider}. */
3
+ export interface DebugPanelProviderProps {
4
+ /** The scene to publish from. */
5
+ children: ReactNode;
6
+ }
7
+ /**
8
+ * Owns the debug panel's entries and the write API that fills them.
9
+ *
10
+ * Deliberately *inside* the `<Canvas>`. R3F re-runs its configuration on every render of the canvas
11
+ * element, and that path replaces the `size` object in its store even when the dimensions have not
12
+ * changed — so state held above the canvas and written once per frame, as a camera publishing its
13
+ * position is, hands every `useThree(state => state.size)` subscriber a new object every frame. Held
14
+ * here, a push re-renders this component and the panel below it and nothing else: `children` arrives
15
+ * as an unchanged element, so React skips the scene entirely.
16
+ * @param props - The scene to publish from.
17
+ * @returns The scene, under the panel's write API, with the panel beside it.
18
+ */
19
+ export declare function DebugPanelProvider(props: DebugPanelProviderProps): ReactNode;
20
+ //# sourceMappingURL=DebugPanelProvider.d.ts.map
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { DebugPanelContext } from '../debug-context';
4
+ import { Panel } from './Panel';
5
+ /**
6
+ * Owns the debug panel's entries and the write API that fills them.
7
+ *
8
+ * Deliberately *inside* the `<Canvas>`. R3F re-runs its configuration on every render of the canvas
9
+ * element, and that path replaces the `size` object in its store even when the dimensions have not
10
+ * changed — so state held above the canvas and written once per frame, as a camera publishing its
11
+ * position is, hands every `useThree(state => state.size)` subscriber a new object every frame. Held
12
+ * here, a push re-renders this component and the panel below it and nothing else: `children` arrives
13
+ * as an unchanged element, so React skips the scene entirely.
14
+ * @param props - The scene to publish from.
15
+ * @returns The scene, under the panel's write API, with the panel beside it.
16
+ */
17
+ export function DebugPanelProvider(props) {
18
+ const { children } = props;
19
+ const [entries, setEntries] = useState({});
20
+ // Not memoized by hand: the React Compiler keeps this object stable, so DebugPanelContext
21
+ // consumers don't re-render when `entries` changes. Both updaters take the functional form, so
22
+ // nothing here closes over the entries themselves.
23
+ const panel = {
24
+ set(key, value) {
25
+ setEntries((prev) => key in prev && Object.is(prev[key], value) ? prev : { ...prev, [key]: value });
26
+ },
27
+ remove(key) {
28
+ setEntries((prev) => {
29
+ if (!(key in prev))
30
+ return prev;
31
+ const next = { ...prev };
32
+ delete next[key];
33
+ return next;
34
+ });
35
+ },
36
+ };
37
+ return (_jsxs(DebugPanelContext, { value: panel, children: [children, _jsx(Panel, { corner: "right", label: "Debug", value: entries })] }));
38
+ }
@@ -0,0 +1,22 @@
1
+ import 'webgl-memory';
2
+ import { type ReactNode } from 'react';
3
+ /** Props for {@link DebugTools}. */
4
+ export interface DebugToolsProps {
5
+ /** The scene to instrument. */
6
+ children?: ReactNode;
7
+ }
8
+ /**
9
+ * The debug overlay, canvas-free — the heavy, lazily-loaded chunk. Wraps the scene in the debug
10
+ * GUI (shared through context so scene components can add folders) and the debug-panel write API
11
+ * (so scene components can publish values via `useDebugPanel`), plus the frame-stats, GPU-memory,
12
+ * and combined debug-values panels. All of it attaches to the document imperatively, outside the
13
+ * R3F reconciler, which is what lets the same component serve both mounts: inside the renderer's
14
+ * own `<Canvas>` and inside an external host's root alike.
15
+ *
16
+ * lil-gui, stats-gl and webgl-memory are reachable only through here, so this module is loaded
17
+ * exclusively via the debug entry's dynamic import and never lands in the main bundle.
18
+ * @param props - The scene to instrument.
19
+ * @returns The scene wrapped in the debug overlay.
20
+ */
21
+ export declare function DebugTools(props: DebugToolsProps): ReactNode;
22
+ //# sourceMappingURL=DebugTools.d.ts.map
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Patches `HTMLCanvasElement.getContext` during chunk eval, so any context created after this
3
+ // chunk loads is born instrumented. The internal mount guarantees the ordering by suspending its
4
+ // Canvas behind this chunk (see `CanvasWithDebug`); an external host has to arrange it itself by
5
+ // awaiting `prepareDebug()` before creating its map — its context predates this code otherwise,
6
+ // and the memory panel stays empty.
7
+ import 'webgl-memory';
8
+ import { DebugPanelProvider } from './DebugPanelProvider';
9
+ import { Gui } from './Gui';
10
+ import { MemoryInfo } from './MemoryInfo';
11
+ import { Stats } from './Stats';
12
+ /**
13
+ * The debug overlay, canvas-free — the heavy, lazily-loaded chunk. Wraps the scene in the debug
14
+ * GUI (shared through context so scene components can add folders) and the debug-panel write API
15
+ * (so scene components can publish values via `useDebugPanel`), plus the frame-stats, GPU-memory,
16
+ * and combined debug-values panels. All of it attaches to the document imperatively, outside the
17
+ * R3F reconciler, which is what lets the same component serve both mounts: inside the renderer's
18
+ * own `<Canvas>` and inside an external host's root alike.
19
+ *
20
+ * lil-gui, stats-gl and webgl-memory are reachable only through here, so this module is loaded
21
+ * exclusively via the debug entry's dynamic import and never lands in the main bundle.
22
+ * @param props - The scene to instrument.
23
+ * @returns The scene wrapped in the debug overlay.
24
+ */
25
+ export function DebugTools(props) {
26
+ const { children } = props;
27
+ return (_jsx(Gui, { children: _jsxs(DebugPanelProvider, { children: [children, _jsx(Stats, {}), _jsx(MemoryInfo, {})] }) }));
28
+ }
@@ -0,0 +1,20 @@
1
+ import './Gui.module.css';
2
+ import { type PropsWithChildren } from 'react';
3
+ /**
4
+ * Initializes a GUI panel and shares it with the scene through context.
5
+ *
6
+ * The panel is created during render (with `autoPlace: false`, so the
7
+ * constructor has no DOM side effect), which makes it available to the scene
8
+ * synchronously on first render — no `null` phase to guard. The effect only
9
+ * attaches its `domElement` to the document and, on cleanup, calls `destroy()`,
10
+ * which detaches the element and clears child folders without invalidating the
11
+ * instance or its element. That keeps it StrictMode-safe: after the dev-only
12
+ * unmount/remount, the same element is re-attached and the scene's own effects
13
+ * re-add their folders. Rendered as an ancestor of the scene, so on unmount
14
+ * React tears the scene's folders down before this component destroys the panel.
15
+ * @param props - Component props; `children` is the scene subtree that consumes
16
+ * the GUI via `useGui`.
17
+ * @returns The children wrapped with the GUI provided through context.
18
+ */
19
+ export declare function Gui(props: PropsWithChildren): import("react").JSX.Element;
20
+ //# sourceMappingURL=Gui.d.ts.map
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import './Gui.module.css'; // Global overrides of lil-gui's stock styling (checkbox row layout).
3
+ import { GUI } from 'lil-gui';
4
+ import { useEffect, useState } from 'react';
5
+ import { GuiContext } from '../debug-context';
6
+ /**
7
+ * Initializes a GUI panel and shares it with the scene through context.
8
+ *
9
+ * The panel is created during render (with `autoPlace: false`, so the
10
+ * constructor has no DOM side effect), which makes it available to the scene
11
+ * synchronously on first render — no `null` phase to guard. The effect only
12
+ * attaches its `domElement` to the document and, on cleanup, calls `destroy()`,
13
+ * which detaches the element and clears child folders without invalidating the
14
+ * instance or its element. That keeps it StrictMode-safe: after the dev-only
15
+ * unmount/remount, the same element is re-attached and the scene's own effects
16
+ * re-add their folders. Rendered as an ancestor of the scene, so on unmount
17
+ * React tears the scene's folders down before this component destroys the panel.
18
+ * @param props - Component props; `children` is the scene subtree that consumes
19
+ * the GUI via `useGui`.
20
+ * @returns The children wrapped with the GUI provided through context.
21
+ */
22
+ export function Gui(props) {
23
+ const { children } = props;
24
+ const [gui] = useState(() => new GUI({ autoPlace: false, closeFolders: true }));
25
+ useEffect(() => {
26
+ gui.domElement.classList.add('lil-auto-place', 'autoPlace');
27
+ document.body.appendChild(gui.domElement);
28
+ gui.close();
29
+ return () => gui.destroy();
30
+ }, [gui]);
31
+ return _jsx(GuiContext, { value: gui, children: children });
32
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * GPU-memory debug panel backed by webgl-memory, shown bottom-left.
3
+ *
4
+ * The component lives inside the R3F `<Canvas>` tree so it can reach the
5
+ * renderer via `useThree`. The figures come from the `GMAN_webgl_memory` context
6
+ * extension that webgl-memory installs (see the debug entry, which loads it
7
+ * before the canvas context is created); this component only reads it, never
8
+ * imports the library.
9
+ *
10
+ * webgl-memory exposes no change event, so it samples the extension after every
11
+ * rendered frame and hands the snapshot to {@link Panel}. Without the
12
+ * extension — a context created before webgl-memory could instrument it —
13
+ * nothing ever fills the panel, and it stays visibly empty on purpose: a wrong
14
+ * readout is diagnosable where a missing one is not, and `prepareDebug` is what
15
+ * keeps that state out of practice. `getMemoryInfo` is
16
+ * cheap, and the panel skips its DOM update whenever the serialized readout is
17
+ * unchanged, so sampling unconditionally (rather than only while the panel is
18
+ * open) costs nothing visible.
19
+ * @returns The bottom-left GPU-memory panel.
20
+ */
21
+ export declare function MemoryInfo(): import("react").JSX.Element;
22
+ //# sourceMappingURL=MemoryInfo.d.ts.map
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { addAfterEffect, useThree } from '@react-three/fiber';
3
+ import { useEffect, useState } from 'react';
4
+ import { Panel } from './Panel';
5
+ /**
6
+ * GPU-memory debug panel backed by webgl-memory, shown bottom-left.
7
+ *
8
+ * The component lives inside the R3F `<Canvas>` tree so it can reach the
9
+ * renderer via `useThree`. The figures come from the `GMAN_webgl_memory` context
10
+ * extension that webgl-memory installs (see the debug entry, which loads it
11
+ * before the canvas context is created); this component only reads it, never
12
+ * imports the library.
13
+ *
14
+ * webgl-memory exposes no change event, so it samples the extension after every
15
+ * rendered frame and hands the snapshot to {@link Panel}. Without the
16
+ * extension — a context created before webgl-memory could instrument it —
17
+ * nothing ever fills the panel, and it stays visibly empty on purpose: a wrong
18
+ * readout is diagnosable where a missing one is not, and `prepareDebug` is what
19
+ * keeps that state out of practice. `getMemoryInfo` is
20
+ * cheap, and the panel skips its DOM update whenever the serialized readout is
21
+ * unchanged, so sampling unconditionally (rather than only while the panel is
22
+ * open) costs nothing visible.
23
+ * @returns The bottom-left GPU-memory panel.
24
+ */
25
+ export function MemoryInfo() {
26
+ const gl = useThree((state) => state.gl);
27
+ const [memoryInfo, setMemoryInfo] = useState(null);
28
+ useEffect(() => {
29
+ const ext = gl.getContext().getExtension('GMAN_webgl_memory');
30
+ if (!ext)
31
+ return;
32
+ return addAfterEffect(() => {
33
+ setMemoryInfo({ ...ext.getMemoryInfo(), drawCalls: gl.info.render.calls });
34
+ });
35
+ }, [gl]);
36
+ return _jsx(Panel, { corner: "left", label: "Memory Info", value: memoryInfo, replacer: localizeNumbers });
37
+ }
38
+ // `JSON.stringify` replacer: render numbers with locale thousands separators
39
+ // (e.g. `1,048,576`).
40
+ function localizeNumbers(_key, value) {
41
+ return typeof value === 'number' ? value.toLocaleString() : value;
42
+ }
@@ -0,0 +1,31 @@
1
+ /** Props for {@link Panel}. */
2
+ export interface PanelProps {
3
+ /** Which bottom corner the panel is pinned to. */
4
+ corner: 'left' | 'right';
5
+ /** Shown on the toggle button after "Show"/"Hide". */
6
+ label: string;
7
+ /** Value serialized to JSON and shown while the panel is open. */
8
+ value: unknown;
9
+ /** Optional `JSON.stringify` replacer, for custom formatting of the value. */
10
+ replacer?: (key: string, value: unknown) => unknown;
11
+ }
12
+ /**
13
+ * A collapsible debug readout pinned to a bottom corner, rendered as regular DOM
14
+ * outside the R3F reconciler. The reusable chrome extracted from the GPU-memory
15
+ * panel: a caller only supplies a corner, a label, and the `value` to display.
16
+ *
17
+ * A component inside the `<Canvas>` tree cannot return DOM — R3F's reconciler
18
+ * only understands Three.js objects, so a returned `<div>` (even via
19
+ * `createPortal`) never becomes real DOM. So, like drei's `<Html>`, it renders
20
+ * into a plain element created with `document.createElement` using react-dom's
21
+ * own root, and returns `null` to R3F.
22
+ *
23
+ * The interactive open state and serialization live in {@link PanelView}, inside
24
+ * that react-dom root, so toggling, style HMR, and value changes reconcile there
25
+ * without depending on the parent (or R3F) re-running an effect. This component
26
+ * only pushes the latest props into the root whenever they change.
27
+ * @param props - Corner, toggle label, and the value to display.
28
+ * @returns `null`.
29
+ */
30
+ export declare function Panel(props: PanelProps): null;
31
+ //# sourceMappingURL=Panel.d.ts.map
@@ -0,0 +1,98 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { createRoot } from 'react-dom/client';
4
+ import styles from './Panel.module.css';
5
+ /**
6
+ * The panel's DOM, rendered *inside* the detached react-dom root. The open state
7
+ * and the serialization live here — not on the parent {@link Panel} — so the
8
+ * toggle click, HMR of these styles, and value changes all reconcile as an
9
+ * ordinary react-dom subtree. If the parent diffed the markup by hand instead
10
+ * (keyed on a fixed dep list), anything outside that list — opening the panel,
11
+ * editing this CSS module — wouldn't show until the next value push happened to
12
+ * re-run it.
13
+ * @param props - Corner, toggle label, value to display, and optional replacer.
14
+ * @returns The panel element.
15
+ */
16
+ function PanelView(props) {
17
+ const { corner, label, value } = props;
18
+ const [isOpen, setIsOpen] = useState(false);
19
+ function replacer(key, value) {
20
+ if (Array.isArray(value))
21
+ return `[${value.join(', ')}]`;
22
+ if (props.replacer)
23
+ return props.replacer(key, value);
24
+ return value;
25
+ }
26
+ function serialize() {
27
+ // Serialize only while open — the readout is hidden otherwise.
28
+ if (!isOpen)
29
+ return '';
30
+ // A single-key wrapper (e.g. `{ camera: {…} }`) is unwrapped to its value, so
31
+ // the readout shows the payload directly rather than one level of nesting.
32
+ let valueToSerialize = value;
33
+ if (typeof value === 'object' && value) {
34
+ const keys = Object.keys(value);
35
+ valueToSerialize = keys.length === 1 ? value[keys[0]] : value;
36
+ }
37
+ return JSON.stringify(valueToSerialize, replacer, 2).replaceAll('"', '');
38
+ }
39
+ return (_jsxs("div", { className: `${styles.container} ${styles[corner]}`, children: [_jsxs("button", { className: styles.toggle, onClick: () => setIsOpen((open) => !open), children: [isOpen ? 'Hide' : 'Show', " ", label] }), isOpen && _jsx("div", { className: styles.info, children: serialize() })] }));
40
+ }
41
+ /**
42
+ * A collapsible debug readout pinned to a bottom corner, rendered as regular DOM
43
+ * outside the R3F reconciler. The reusable chrome extracted from the GPU-memory
44
+ * panel: a caller only supplies a corner, a label, and the `value` to display.
45
+ *
46
+ * A component inside the `<Canvas>` tree cannot return DOM — R3F's reconciler
47
+ * only understands Three.js objects, so a returned `<div>` (even via
48
+ * `createPortal`) never becomes real DOM. So, like drei's `<Html>`, it renders
49
+ * into a plain element created with `document.createElement` using react-dom's
50
+ * own root, and returns `null` to R3F.
51
+ *
52
+ * The interactive open state and serialization live in {@link PanelView}, inside
53
+ * that react-dom root, so toggling, style HMR, and value changes reconcile there
54
+ * without depending on the parent (or R3F) re-running an effect. This component
55
+ * only pushes the latest props into the root whenever they change.
56
+ * @param props - Corner, toggle label, and the value to display.
57
+ * @returns `null`.
58
+ */
59
+ export function Panel(props) {
60
+ const { corner, label, value, replacer } = props;
61
+ // Created imperatively, so R3F's reconciler never sees these DOM elements.
62
+ const [container] = useState(() => document.createElement('div'));
63
+ const rootRef = useRef(null);
64
+ const pendingUnmountRef = useRef(false);
65
+ // Mount the container and spin up a react-dom root once.
66
+ //
67
+ // Unmounting a nested root synchronously during the parent's commit warns
68
+ // "unmount while rendering", so the unmount is deferred to a microtask
69
+ // (Promise, not queueMicrotask — old WebViews lack it). That deferral has to
70
+ // be cancellable: under StrictMode the effect runs mount → unmount → mount,
71
+ // and the queued unmount can fire *after* the remount has already reused this
72
+ // root (react-dom flushes the microtask late, R3F's reconciler earlier). Left
73
+ // unguarded, the stale microtask would tear down the live panel. Every
74
+ // (re)mount clears `pendingUnmountRef`, so a superseded unmount skips itself;
75
+ // only a real unmount — with no remount to clear the flag — destroys the root.
76
+ useEffect(() => {
77
+ pendingUnmountRef.current = false;
78
+ document.body.appendChild(container);
79
+ const root = rootRef.current ?? createRoot(container);
80
+ rootRef.current = root;
81
+ return () => {
82
+ container.remove();
83
+ pendingUnmountRef.current = true;
84
+ void Promise.resolve().then(() => {
85
+ if (!pendingUnmountRef.current)
86
+ return;
87
+ rootRef.current?.unmount();
88
+ rootRef.current = null;
89
+ });
90
+ };
91
+ }, [container]);
92
+ // Push the latest props into the detached root. PanelView reconciles as its own
93
+ // react-dom subtree, keeping its open state across these updates.
94
+ useEffect(() => {
95
+ rootRef.current?.render(_jsx(PanelView, { corner: corner, label: label, value: value, replacer: replacer }));
96
+ }, [corner, label, value, replacer]);
97
+ return null;
98
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Performance-stats overlay (frame rate, GPU timing, refresh rate) attached to
3
+ * the document body and refreshed after every rendered frame.
4
+ *
5
+ * The overlay is mounted imperatively, so the component renders nothing.
6
+ * @returns `null`.
7
+ */
8
+ export declare function Stats(): null;
9
+ //# sourceMappingURL=Stats.d.ts.map
@@ -0,0 +1,24 @@
1
+ import { addAfterEffect, useThree } from '@react-three/fiber';
2
+ import { useEffect } from 'react';
3
+ import StatsGl from 'stats-gl';
4
+ /**
5
+ * Performance-stats overlay (frame rate, GPU timing, refresh rate) attached to
6
+ * the document body and refreshed after every rendered frame.
7
+ *
8
+ * The overlay is mounted imperatively, so the component renders nothing.
9
+ * @returns `null`.
10
+ */
11
+ export function Stats() {
12
+ const gl = useThree((state) => state.gl);
13
+ useEffect(() => {
14
+ const stats = new StatsGl({ trackGPU: true, trackHz: true });
15
+ void stats.init(gl);
16
+ document.body.appendChild(stats.domElement);
17
+ const removeAfterEffect = addAfterEffect(() => stats.update());
18
+ return () => {
19
+ stats.dispose();
20
+ removeAfterEffect();
21
+ };
22
+ }, [gl]);
23
+ return null;
24
+ }
@@ -0,0 +1,41 @@
1
+ import type { GUI } from 'lil-gui';
2
+ /**
3
+ * Context carrying the active debug GUI, or `null` when the debug UI is
4
+ * disabled or has not finished loading. Isolated in its own module (with a
5
+ * type-only GUI import) so components can read the GUI without pulling the
6
+ * debug UI bundle into the main chunk.
7
+ */
8
+ export declare const GuiContext: import("react").Context<GUI | null>;
9
+ /**
10
+ * Reads the active debug GUI from context.
11
+ *
12
+ * Returns `null` while the debug UI is off or still loading, so guard on a
13
+ * non-null value before adding folders or controllers.
14
+ * @returns The current GUI instance, or `null`.
15
+ */
16
+ export declare const useGui: () => GUI | null;
17
+ /** Write API for the debug panel: components publish key/value entries that the panel combines into one object and renders. */
18
+ export interface DebugPanel {
19
+ /** Adds or updates the entry under `key`. */
20
+ set(key: string, value: unknown): void;
21
+ /** Removes the entry under `key`. */
22
+ remove(key: string): void;
23
+ }
24
+ /**
25
+ * Context carrying the debug panel's write API, or `null` when the debug UI is
26
+ * disabled. Kept separate from {@link GuiContext} so panel updates never
27
+ * re-render GUI consumers and vice versa.
28
+ */
29
+ export declare const DebugPanelContext: import("react").Context<DebugPanel | null>;
30
+ /**
31
+ * Returns a setter that publishes a key/value entry to the debug panel.
32
+ *
33
+ * The returned `set(key, value)` is stable and safe to call from anywhere,
34
+ * including a per-frame loop — it never re-renders the calling tree, and the
35
+ * panel itself re-renders only when its combined, serialized value changes.
36
+ * Every key a component writes is removed automatically when it unmounts.
37
+ * Outside the debug provider the setter is a no-op.
38
+ * @returns A `set(key, value)` function for the debug panel.
39
+ */
40
+ export declare function useDebugPanel(): (key: string, value: unknown) => void;
41
+ //# sourceMappingURL=debug-context.d.ts.map
@@ -0,0 +1,59 @@
1
+ import { createContext, use, useEffect, useRef } from 'react';
2
+ /**
3
+ * Context carrying the active debug GUI, or `null` when the debug UI is
4
+ * disabled or has not finished loading. Isolated in its own module (with a
5
+ * type-only GUI import) so components can read the GUI without pulling the
6
+ * debug UI bundle into the main chunk.
7
+ */
8
+ export const GuiContext = createContext(null);
9
+ /**
10
+ * Reads the active debug GUI from context.
11
+ *
12
+ * Returns `null` while the debug UI is off or still loading, so guard on a
13
+ * non-null value before adding folders or controllers.
14
+ * @returns The current GUI instance, or `null`.
15
+ */
16
+ export const useGui = () => use(GuiContext);
17
+ /**
18
+ * Context carrying the debug panel's write API, or `null` when the debug UI is
19
+ * disabled. Kept separate from {@link GuiContext} so panel updates never
20
+ * re-render GUI consumers and vice versa.
21
+ */
22
+ export const DebugPanelContext = createContext(null);
23
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
24
+ const noop = () => { };
25
+ /**
26
+ * Returns a setter that publishes a key/value entry to the debug panel.
27
+ *
28
+ * The returned `set(key, value)` is stable and safe to call from anywhere,
29
+ * including a per-frame loop — it never re-renders the calling tree, and the
30
+ * panel itself re-renders only when its combined, serialized value changes.
31
+ * Every key a component writes is removed automatically when it unmounts.
32
+ * Outside the debug provider the setter is a no-op.
33
+ * @returns A `set(key, value)` function for the debug panel.
34
+ */
35
+ export function useDebugPanel() {
36
+ const panel = use(DebugPanelContext);
37
+ // One stable Set per consumer, tracking every key it has published. The
38
+ // compiler memoizes the allocation, so subsequent renders don't churn it; even
39
+ // without the compiler, useRef discards the extra `new Set()` after mount.
40
+ const keysRef = useRef(new Set());
41
+ useEffect(() => {
42
+ // Outside the debug provider there is nothing to clean up.
43
+ if (!panel)
44
+ return;
45
+ // Capture the (stable) Set locally so the cleanup closure doesn't read a ref
46
+ // in teardown. Every key this component published is removed on unmount.
47
+ const tracked = keysRef.current;
48
+ return () => tracked.forEach((key) => panel.remove(key));
49
+ }, [panel]);
50
+ // Non-debug consumers share one stable no-op — no per-consumer closure.
51
+ if (!panel)
52
+ return noop;
53
+ // Stable identity comes from the React Compiler — no hand-written useCallback —
54
+ // so callers can put `set` in effect deps without churn.
55
+ return (key, value) => {
56
+ keysRef.current.add(key);
57
+ panel.set(key, value);
58
+ };
59
+ }