@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,105 @@
1
+ import { type Intersection } from 'three';
2
+ import { type BatchedTextExt } from '../../core/text/BatchedText';
3
+ import { TextExt } from '../../core/text/Text';
4
+ import { type RenderableDef, type TextDef } from '../../types/defs';
5
+ import { SlotContainer } from '../containers';
6
+ /** One def's members: a `TextExt` per line, and the batch slot each of them occupies. */
7
+ export interface LineGroup {
8
+ /** The members, in the def's line order. */
9
+ readonly texts: readonly TextExt[];
10
+ /** Each member's batch slot, index-parallel to {@link texts}. */
11
+ readonly slots: readonly number[];
12
+ }
13
+ /**
14
+ * One text layer's bookkeeping: a group of members per def, and the paint order across them.
15
+ *
16
+ * The slot a def claims here is a **group**, not a batch member — a `TextDef` holds an array of
17
+ * lines and each line is its own `TextExt`. That is the one place this differs from the shape and
18
+ * image containers, and it falls out of the def type rather than being a choice: the lines of one
19
+ * def are laid out together (a centered block needs every line's height before it can place the
20
+ * first), so they cannot be independent instances.
21
+ *
22
+ * Group size is the slot's resource, so a def that gains or loses a line re-claims automatically —
23
+ * which is what makes `TextDef.lines` updatable here, where the imperative renderer required it to
24
+ * be fixed.
25
+ *
26
+ * Ordering differs too: a text batch draws through packed glyph attributes rather than a sortable
27
+ * draw list, so {@link applyOrder} hands the batch a rebuilt member list instead of labeling slots,
28
+ * and the inherited `customSort` goes unused.
29
+ */
30
+ export declare class TextContainer extends SlotContainer<number, BatchedTextExt, TextDef> {
31
+ /** Groups by slot, with holes for released ones. */
32
+ private readonly groups;
33
+ private readonly freeGroups;
34
+ /**
35
+ * Batch member to the group holding it, so a hit — which names a member — resolves to a def. The
36
+ * batch reuses a released member's slot, so entries are dropped with the group that owned them.
37
+ */
38
+ private readonly groupByMember;
39
+ private groupCount;
40
+ /** The last order applied to the batch, kept so an unchanged one can skip the repack. */
41
+ private appliedOrder;
42
+ private syncScheduled;
43
+ private readonly invalidate;
44
+ /**
45
+ * Builds a text layer's bookkeeping. The batch itself is R3F's — the component renders
46
+ * `<BatchedText>` and hands it over through {@link SlotContainer.bind} — so R3F disposes it on
47
+ * unmount.
48
+ * @param invalidate - R3F's frame request, called when an asynchronous sync completes. Stable for
49
+ * the lifetime of a canvas, so capturing it here is safe.
50
+ */
51
+ constructor(invalidate: () => void);
52
+ /**
53
+ * The members backing one def.
54
+ * @param slot - The def's group.
55
+ * @returns Its members and their batch slots.
56
+ */
57
+ linesAt(slot: number): LineGroup;
58
+ /**
59
+ * Requests one batch sync, coalescing every request made in the same tick.
60
+ *
61
+ * Eager rather than waiting for `onBeforeRender`, because syncing is asynchronous — troika loads
62
+ * and parses the font — and under on-demand rendering a sync started during a frame has nothing to
63
+ * request the *next* frame when it completes. The callback carries R3F's `invalidate`, which is
64
+ * what closes that loop; without it new text appears only when something else happens to
65
+ * invalidate.
66
+ */
67
+ scheduleSync(): void;
68
+ claim(def: RenderableDef, lineCount: number): number;
69
+ setVisibleAt(slot: number, visible: boolean): void;
70
+ /**
71
+ * Sets the draw order for the whole layer, then syncs.
72
+ *
73
+ * The sync rides along so one structural change costs exactly one sync — a mount writes content
74
+ * through its instances in the same effect flush, and every request in the tick coalesces.
75
+ * @param defs - The layer's texts, in paint order.
76
+ * @param layerDimmed - Whether the layer is dimmed, inherited by defs without a `dim` of their own.
77
+ */
78
+ setPaintOrder(defs: readonly TextDef[], layerDimmed: boolean): void;
79
+ /**
80
+ * A hit names the batch member it struck, and a def owns one member per line.
81
+ * @param hit - The intersection to trace.
82
+ * @returns The def, or `undefined` when no claimed group holds that member.
83
+ */
84
+ defFromHit(hit: Intersection): RenderableDef | undefined;
85
+ /**
86
+ * Rebuilds the flat member order from the current defs and hands it to the batch, unless nothing
87
+ * moved.
88
+ *
89
+ * Skipping an unchanged order matters more here than for the other containers: a text batch has no
90
+ * `customSort`, so reordering means repacking the glyph attributes, and `useStagedChildren` hands out
91
+ * a fresh snapshot on *every* layer update — including ones that moved nothing.
92
+ */
93
+ protected applyOrder(): void;
94
+ /**
95
+ * Per member, not per def: a def's lines are separate batch members, and the uniform row is
96
+ * addressed by member. One `dim` on the def therefore writes each of its lines, exactly as
97
+ * visibility does.
98
+ * @param slot - The def's group.
99
+ * @param amount - The encoded amount.
100
+ */
101
+ protected writeDim(slot: number, amount: number): void;
102
+ protected addInstance(lineCount: number): number;
103
+ protected removeInstance(slot: number): void;
104
+ }
105
+ //# sourceMappingURL=text-container.d.ts.map
@@ -0,0 +1,206 @@
1
+ import { TextExt } from '../../core/text/Text';
2
+ import { sameSequence } from '../../utils/arrays';
3
+ import { SlotContainer } from '../containers';
4
+ import { dimOrdered, isDimmed } from '../materials/dim';
5
+ import { LAYOUT_FONT_SIZE, LINE_HEIGHT } from './text-layout';
6
+ /**
7
+ * One text layer's bookkeeping: a group of members per def, and the paint order across them.
8
+ *
9
+ * The slot a def claims here is a **group**, not a batch member — a `TextDef` holds an array of
10
+ * lines and each line is its own `TextExt`. That is the one place this differs from the shape and
11
+ * image containers, and it falls out of the def type rather than being a choice: the lines of one
12
+ * def are laid out together (a centered block needs every line's height before it can place the
13
+ * first), so they cannot be independent instances.
14
+ *
15
+ * Group size is the slot's resource, so a def that gains or loses a line re-claims automatically —
16
+ * which is what makes `TextDef.lines` updatable here, where the imperative renderer required it to
17
+ * be fixed.
18
+ *
19
+ * Ordering differs too: a text batch draws through packed glyph attributes rather than a sortable
20
+ * draw list, so {@link applyOrder} hands the batch a rebuilt member list instead of labeling slots,
21
+ * and the inherited `customSort` goes unused.
22
+ */
23
+ export class TextContainer extends SlotContainer {
24
+ /** Groups by slot, with holes for released ones. */
25
+ groups = [];
26
+ freeGroups = [];
27
+ /**
28
+ * Batch member to the group holding it, so a hit — which names a member — resolves to a def. The
29
+ * batch reuses a released member's slot, so entries are dropped with the group that owned them.
30
+ */
31
+ groupByMember = new Map();
32
+ groupCount = 0;
33
+ /** The last order applied to the batch, kept so an unchanged one can skip the repack. */
34
+ appliedOrder = [];
35
+ syncScheduled = false;
36
+ invalidate;
37
+ /**
38
+ * Builds a text layer's bookkeeping. The batch itself is R3F's — the component renders
39
+ * `<BatchedText>` and hands it over through {@link SlotContainer.bind} — so R3F disposes it on
40
+ * unmount.
41
+ * @param invalidate - R3F's frame request, called when an asynchronous sync completes. Stable for
42
+ * the lifetime of a canvas, so capturing it here is safe.
43
+ */
44
+ constructor(invalidate) {
45
+ super();
46
+ this.invalidate = invalidate;
47
+ }
48
+ /**
49
+ * The members backing one def.
50
+ * @param slot - The def's group.
51
+ * @returns Its members and their batch slots.
52
+ */
53
+ linesAt(slot) {
54
+ return this.groups[slot] ?? EMPTY_GROUP;
55
+ }
56
+ /**
57
+ * Requests one batch sync, coalescing every request made in the same tick.
58
+ *
59
+ * Eager rather than waiting for `onBeforeRender`, because syncing is asynchronous — troika loads
60
+ * and parses the font — and under on-demand rendering a sync started during a frame has nothing to
61
+ * request the *next* frame when it completes. The callback carries R3F's `invalidate`, which is
62
+ * what closes that loop; without it new text appears only when something else happens to
63
+ * invalidate.
64
+ */
65
+ scheduleSync() {
66
+ if (this.syncScheduled)
67
+ return;
68
+ this.syncScheduled = true;
69
+ // `Promise.resolve().then`, not `queueMicrotask`: the SDK targets Android WebViews as old as
70
+ // Chrome 66, which predates it. Same microtask timing.
71
+ void Promise.resolve().then(() => {
72
+ this.syncScheduled = false;
73
+ // Unmounted between the request and the microtask; the batch is R3F's and already disposed.
74
+ if (!this.mesh)
75
+ return;
76
+ this.mesh.sync(this.invalidate);
77
+ });
78
+ }
79
+ claim(def, lineCount) {
80
+ const slot = super.claim(def, lineCount);
81
+ // `addText` appends to the batch's order, so a first mount draws in claim order and the layer's
82
+ // own pass relabels it in the same effect flush. A *re*-claim after that pass has to re-run it,
83
+ // or the new members would stay stuck at the end of the paint order.
84
+ if (this.layerDefs.length)
85
+ this.applyOrder();
86
+ return slot;
87
+ }
88
+ setVisibleAt(slot, visible) {
89
+ const group = this.groups[slot];
90
+ if (!group)
91
+ return;
92
+ for (const member of group.slots) {
93
+ this.mesh.setVisibleAt(member, visible);
94
+ }
95
+ }
96
+ /**
97
+ * Sets the draw order for the whole layer, then syncs.
98
+ *
99
+ * The sync rides along so one structural change costs exactly one sync — a mount writes content
100
+ * through its instances in the same effect flush, and every request in the tick coalesces.
101
+ * @param defs - The layer's texts, in paint order.
102
+ * @param layerDimmed - Whether the layer is dimmed, inherited by defs without a `dim` of their own.
103
+ */
104
+ setPaintOrder(defs, layerDimmed) {
105
+ super.setPaintOrder(defs, layerDimmed);
106
+ this.scheduleSync();
107
+ }
108
+ /**
109
+ * A hit names the batch member it struck, and a def owns one member per line.
110
+ * @param hit - The intersection to trace.
111
+ * @returns The def, or `undefined` when no claimed group holds that member.
112
+ */
113
+ defFromHit(hit) {
114
+ const member = hit.instanceId;
115
+ return member === undefined ? undefined : this.defAt(this.groupByMember.get(member));
116
+ }
117
+ /**
118
+ * Rebuilds the flat member order from the current defs and hands it to the batch, unless nothing
119
+ * moved.
120
+ *
121
+ * Skipping an unchanged order matters more here than for the other containers: a text batch has no
122
+ * `customSort`, so reordering means repacking the glyph attributes, and `useStagedChildren` hands out
123
+ * a fresh snapshot on *every* layer update — including ones that moved nothing.
124
+ */
125
+ applyOrder() {
126
+ const order = [];
127
+ for (const def of dimOrdered(this.layerDefs, this.layerDimmed)) {
128
+ const slot = this.slotOf(def);
129
+ if (slot === undefined)
130
+ continue;
131
+ const group = this.groups[slot];
132
+ if (!group)
133
+ continue;
134
+ order.push(...group.texts);
135
+ this.dimmedBySlot[slot] = isDimmed(def.dim, this.layerDimmed);
136
+ }
137
+ if (sameSequence(order, this.appliedOrder))
138
+ return;
139
+ this.appliedOrder = order;
140
+ this.mesh.setOrder(order);
141
+ }
142
+ /**
143
+ * Per member, not per def: a def's lines are separate batch members, and the uniform row is
144
+ * addressed by member. One `dim` on the def therefore writes each of its lines, exactly as
145
+ * visibility does.
146
+ * @param slot - The def's group.
147
+ * @param amount - The encoded amount.
148
+ */
149
+ writeDim(slot, amount) {
150
+ const group = this.groups[slot];
151
+ if (!group)
152
+ return;
153
+ for (const member of group.slots) {
154
+ super.writeDim(member, amount);
155
+ }
156
+ }
157
+ addInstance(lineCount) {
158
+ const slot = this.freeGroups.pop() ?? this.groupCount++;
159
+ const texts = [];
160
+ const slots = [];
161
+ for (let line = 0; line < lineCount; line++) {
162
+ const text = makeMember();
163
+ texts.push(text);
164
+ slots.push(this.mesh.addText(text));
165
+ }
166
+ this.groups[slot] = { texts, slots };
167
+ for (const member of slots)
168
+ this.groupByMember.set(member, slot);
169
+ return slot;
170
+ }
171
+ removeInstance(slot) {
172
+ const group = this.groups[slot];
173
+ if (!group)
174
+ return;
175
+ for (const member of group.slots)
176
+ this.groupByMember.delete(member);
177
+ for (const text of group.texts) {
178
+ this.mesh.removeText(text);
179
+ // The member's own `GlyphsGeometry`, which only ever held its bounds — the batch draws from
180
+ // its own. Nothing was uploaded, so this is bookkeeping, not a GPU free.
181
+ text.dispose();
182
+ }
183
+ this.groups[slot] = undefined;
184
+ this.freeGroups.push(slot);
185
+ // Members left the batch's order with them; drop the stale copy so the next pass reapplies.
186
+ this.appliedOrder = [];
187
+ }
188
+ }
189
+ /**
190
+ * Builds a batch member with the properties that never change for its lifetime.
191
+ *
192
+ * All three are syncable, so writing them once here — rather than per update — is what keeps a
193
+ * layout change from re-running troika's async layout and repacking the batch.
194
+ * @returns The member.
195
+ */
196
+ function makeMember() {
197
+ const text = new TextExt();
198
+ text.fontSize = LAYOUT_FONT_SIZE;
199
+ text.lineHeight = LINE_HEIGHT;
200
+ // Wrapping is the floorplan's job: it measures and breaks lines itself, then hands over the
201
+ // result with explicit newlines. Letting troika re-wrap on top would fight that.
202
+ text.whiteSpace = 'nowrap';
203
+ return text;
204
+ }
205
+ /** Returned for an unclaimed slot, so a caller reading one iterates nothing rather than throwing. */
206
+ const EMPTY_GROUP = { texts: [], slots: [] };
@@ -0,0 +1,83 @@
1
+ import { type Rect } from '@expofp/geometry';
2
+ import { type TextExt } from '../../core/text/Text';
3
+ import { type TextDef, type TextLineDef } from '../../types/defs';
4
+ /**
5
+ * The em size every member lays out at, fixed for its lifetime.
6
+ *
7
+ * `fontSize` is a syncable property: changing it re-runs troika's async layout and repacks the whole
8
+ * batch. A label's size changes on every zoom step, so paying that would make zooming a sequence of
9
+ * repacks. Laying out at a constant 1 em and carrying the real size in the member's `scale` costs a
10
+ * matrix write instead — the scale is not syncable, so nothing re-lays-out.
11
+ *
12
+ * One, not zero: troika reads `this.fontSize || 0.1`, so a member left at zero lays out at a tenth of
13
+ * an em and every scale below would have to carry a compensating factor of ten.
14
+ */
15
+ export declare const LAYOUT_FONT_SIZE = 1;
16
+ /** Line box height as a multiple of the font size, matching the `lineHeight` members are given. */
17
+ export declare const LINE_HEIGHT = 1.1;
18
+ /**
19
+ * A line's placement **in its def's own unrotated frame**, as an offset from the bounds' center.
20
+ *
21
+ * Layout stops here on purpose: where the bounds sit in the plan, and how they are turned, is a
22
+ * separate step ({@link applyPlacement}). Keeping the two apart is what lets the stacking be
23
+ * asserted as plain numbers, with no trigonometry in the test.
24
+ */
25
+ export interface LaidOutLine {
26
+ /** Which horizontal point of the line's box sits at its origin. */
27
+ anchorX: 'left' | 'center' | 'right';
28
+ /** Which vertical point of the line's box sits at its origin; only the outer edges are used. */
29
+ anchorY: 'top' | 'bottom';
30
+ /** How wrapped rows align against each other within this line. */
31
+ textAlign: 'left' | 'center' | 'right';
32
+ /** Offset from the bounds' center along the bounds' own x axis. */
33
+ offsetX: number;
34
+ /** Offset from the bounds' center along the bounds' own y axis. */
35
+ offsetY: number;
36
+ /** Horizontal scale: the line's font size, which is its em height in plan units. */
37
+ scaleX: number;
38
+ /** Vertical scale: negative, because plan space runs y-down and text lays out y-up. */
39
+ scaleY: number;
40
+ /** `[minX, minY, maxX, maxY]` in the line's own em space, clipping it to the def's bounds. */
41
+ clipRect: [number, number, number, number];
42
+ }
43
+ /**
44
+ * Stacks a text def's lines inside its bounds, aligned as the def asks.
45
+ *
46
+ * Works entirely in the bounds' unrotated frame, so a rotated def lays out identically to an
47
+ * unrotated one — the rotation is applied afterwards, to the whole stack at once. That is also what
48
+ * lets the clip rect be a plain offset from the center: the member carries the same rotation, so the
49
+ * rect turns with it.
50
+ *
51
+ * **The y flip lives in `scaleY`.** Plan space runs y-down; troika lays text out y-up. Rather than
52
+ * troika's `orientation` — a `lookAt` rotation, which would leave the clip rect to be flipped
53
+ * separately — the flip rides on the scale the lines need anyway for their font size, so the clip
54
+ * rect, which divides by that scale, inherits it for free.
55
+ *
56
+ * **Plan units throughout**, font sizes included. A plan that wants a label to hold an on-screen size
57
+ * converts for itself, against the pixel size the renderer publishes — the same way it sizes every
58
+ * other pixel-authored thing it draws.
59
+ * @param def - The text to lay out.
60
+ * @returns One placement per line, in the def's line order.
61
+ */
62
+ export declare function layoutText(def: TextDef): LaidOutLine[];
63
+ /**
64
+ * Writes a laid-out line onto its member: the bounds' rotation and position applied to the local
65
+ * offset, the line's scale, and its clip rect.
66
+ *
67
+ * The post-transform half of {@link layoutText}. `updateMatrix` is what reports the move to the
68
+ * batch — members are not in the scene graph, so nothing else would.
69
+ * @param text - The member to place.
70
+ * @param line - The line's local placement.
71
+ * @param bounds - The def's bounds, supplying the rotation, center and elevation.
72
+ */
73
+ export declare function applyPlacement(text: TextExt, line: LaidOutLine, bounds: Rect): void;
74
+ /**
75
+ * The height one line's box occupies.
76
+ *
77
+ * A single `TextLineDef` can hold embedded newlines — the floorplan joins an exhibitor's name rows
78
+ * into one def — so its box is as many line boxes tall as it has rows.
79
+ * @param line - The line to measure.
80
+ * @returns The height, in plan units.
81
+ */
82
+ export declare function lineHeight(line: TextLineDef): number;
83
+ //# sourceMappingURL=text-layout.d.ts.map
@@ -0,0 +1,154 @@
1
+ import { Vector2 } from 'three';
2
+ /**
3
+ * The em size every member lays out at, fixed for its lifetime.
4
+ *
5
+ * `fontSize` is a syncable property: changing it re-runs troika's async layout and repacks the whole
6
+ * batch. A label's size changes on every zoom step, so paying that would make zooming a sequence of
7
+ * repacks. Laying out at a constant 1 em and carrying the real size in the member's `scale` costs a
8
+ * matrix write instead — the scale is not syncable, so nothing re-lays-out.
9
+ *
10
+ * One, not zero: troika reads `this.fontSize || 0.1`, so a member left at zero lays out at a tenth of
11
+ * an em and every scale below would have to carry a compensating factor of ten.
12
+ */
13
+ export const LAYOUT_FONT_SIZE = 1;
14
+ /** Line box height as a multiple of the font size, matching the `lineHeight` members are given. */
15
+ export const LINE_HEIGHT = 1.1;
16
+ /**
17
+ * Stacks a text def's lines inside its bounds, aligned as the def asks.
18
+ *
19
+ * Works entirely in the bounds' unrotated frame, so a rotated def lays out identically to an
20
+ * unrotated one — the rotation is applied afterwards, to the whole stack at once. That is also what
21
+ * lets the clip rect be a plain offset from the center: the member carries the same rotation, so the
22
+ * rect turns with it.
23
+ *
24
+ * **The y flip lives in `scaleY`.** Plan space runs y-down; troika lays text out y-up. Rather than
25
+ * troika's `orientation` — a `lookAt` rotation, which would leave the clip rect to be flipped
26
+ * separately — the flip rides on the scale the lines need anyway for their font size, so the clip
27
+ * rect, which divides by that scale, inherits it for free.
28
+ *
29
+ * **Plan units throughout**, font sizes included. A plan that wants a label to hold an on-screen size
30
+ * converts for itself, against the pixel size the renderer publishes — the same way it sizes every
31
+ * other pixel-authored thing it draws.
32
+ * @param def - The text to lay out.
33
+ * @returns One placement per line, in the def's line order.
34
+ */
35
+ export function layoutText(def) {
36
+ const { bounds, lines, alignment, padding } = def;
37
+ const half = _half.set(bounds.size.x, bounds.size.y).multiplyScalar(0.5);
38
+ // Where the first line's anchor sits. A centered block starts half the leftover space in from the
39
+ // top edge, so the stack ends up centered as a whole; every other alignment starts at the padded
40
+ // edge itself.
41
+ const offset = _offset.copy(half);
42
+ if (alignment.vertical === 'center') {
43
+ let stackHeight = 0;
44
+ for (const line of lines)
45
+ stackHeight += lineHeight(line);
46
+ offset.y -= (bounds.size.y - stackHeight) / 2;
47
+ }
48
+ else {
49
+ offset.sub(_padding.fromArray(padding));
50
+ }
51
+ offset.multiply(alignmentFactors(alignment));
52
+ const placed = [];
53
+ for (const line of lines) {
54
+ const scaleX = line.fontSize;
55
+ // Negative: the y flip. Also why the clip rect below comes out correctly ordered — dividing by a
56
+ // negative scale swaps its min and max back.
57
+ const scaleY = -scaleX;
58
+ placed.push({
59
+ anchorX: alignment.horizontal,
60
+ // Only the outer edges: a middle-anchored line would stack against its own center, so the
61
+ // block's vertical alignment is carried by the starting offset above instead.
62
+ anchorY: alignment.vertical === 'bottom' ? 'bottom' : 'top',
63
+ textAlign: alignment.text ?? alignment.horizontal,
64
+ offsetX: offset.x,
65
+ offsetY: offset.y,
66
+ scaleX,
67
+ scaleY,
68
+ // The bounds' corners relative to this line's origin, converted from plan units into the
69
+ // line's em space. A zero-size line would divide by zero, but those are hidden before they
70
+ // draw (see `TextInstance`), so the clip rect is never read.
71
+ clipRect: [
72
+ (-half.x - offset.x) / scaleX,
73
+ (half.y - offset.y) / scaleY,
74
+ (half.x - offset.x) / scaleX,
75
+ (-half.y - offset.y) / scaleY,
76
+ ],
77
+ });
78
+ offset.y += lineHeight(line);
79
+ }
80
+ return placed;
81
+ }
82
+ /**
83
+ * Writes a laid-out line onto its member: the bounds' rotation and position applied to the local
84
+ * offset, the line's scale, and its clip rect.
85
+ *
86
+ * The post-transform half of {@link layoutText}. `updateMatrix` is what reports the move to the
87
+ * batch — members are not in the scene graph, so nothing else would.
88
+ * @param text - The member to place.
89
+ * @param line - The line's local placement.
90
+ * @param bounds - The def's bounds, supplying the rotation, center and elevation.
91
+ */
92
+ export function applyPlacement(text, line, bounds) {
93
+ const { rotation } = bounds;
94
+ const position = _position
95
+ .set(line.offsetX, line.offsetY)
96
+ .rotateAround(ORIGIN, rotation)
97
+ .add(bounds.center);
98
+ text.position.set(position.x, position.y, bounds.elevation);
99
+ text.rotation.set(0, 0, rotation);
100
+ text.scale.set(line.scaleX, line.scaleY, 1);
101
+ text.updateMatrix();
102
+ // Held as one array for the member's life: `clipRect` is not a syncable property, so this is a
103
+ // plain per-frame read and mutating it in place avoids a per-update allocation.
104
+ const clipRect = (text.clipRect ??= [0, 0, 0, 0]);
105
+ clipRect[0] = line.clipRect[0];
106
+ clipRect[1] = line.clipRect[1];
107
+ clipRect[2] = line.clipRect[2];
108
+ clipRect[3] = line.clipRect[3];
109
+ }
110
+ /**
111
+ * The height one line's box occupies.
112
+ *
113
+ * A single `TextLineDef` can hold embedded newlines — the floorplan joins an exhibitor's name rows
114
+ * into one def — so its box is as many line boxes tall as it has rows.
115
+ * @param line - The line to measure.
116
+ * @returns The height, in plan units.
117
+ */
118
+ export function lineHeight(line) {
119
+ return line.fontSize * LINE_HEIGHT * line.text.split('\n').length;
120
+ }
121
+ /**
122
+ * Which way each axis is offset from the bounds' center for a given alignment.
123
+ *
124
+ * Vertical `center` shares `top`'s factor deliberately: a centered block still stacks downward from
125
+ * a start position, and it is that start position the caller moves.
126
+ * @param alignment - The def's alignment.
127
+ * @returns The scratch factors, valid until the next call.
128
+ */
129
+ function alignmentFactors(alignment) {
130
+ return _factors.set(HORIZONTAL_FACTORS[alignment.horizontal], VERTICAL_FACTORS[alignment.vertical]);
131
+ }
132
+ /** Which edge a horizontal alignment offsets toward: `-1` left, `0` centered, `1` right. */
133
+ const HORIZONTAL_FACTORS = {
134
+ left: -1,
135
+ center: 0,
136
+ right: 1,
137
+ };
138
+ /** Which edge a vertical alignment offsets toward; `center` shares `top`'s sign (see above). */
139
+ const VERTICAL_FACTORS = {
140
+ top: -1,
141
+ center: -1,
142
+ bottom: 1,
143
+ };
144
+ /**
145
+ * Scratch reused across calls: layout runs once per def per update and never escapes a synchronous
146
+ * block, so it allocates nothing.
147
+ */
148
+ const _half = new Vector2();
149
+ const _offset = new Vector2();
150
+ const _padding = new Vector2();
151
+ const _factors = new Vector2();
152
+ const _position = new Vector2();
153
+ /** The point a rotated block turns about — its own bounds center, which the offsets are relative to. */
154
+ const ORIGIN = new Vector2(0, 0);
@@ -0,0 +1,103 @@
1
+ import { type Box } from '@expofp/geometry';
2
+ import { type PropsWithChildren } from 'react';
3
+ /**
4
+ * How far the camera may go: the limits every move is held to, dragged or programmatic.
5
+ *
6
+ * Distinct from what a *user* may do, which is `ControlsConfig` — a caller switches a gesture off
7
+ * without changing where the camera may go, and constrains the camera without taking a gesture
8
+ * away. `RendererProps` carries both, and these are the half that ends up here.
9
+ *
10
+ * Every one is optional and defaulted where it is used, a few lines below, so nothing else in the
11
+ * renderer has to know or import what an omitted limit means.
12
+ */
13
+ export interface CameraProps {
14
+ /**
15
+ * How far the plan may be zoomed out, as a factor of the framing that fits the whole viewbox;
16
+ * defaults to `0.1`. Which viewbox zoom factor `1` frames is the {@link Space}'s business, not
17
+ * this component's — all it needs is the bounds.
18
+ */
19
+ minZoom?: number;
20
+ /**
21
+ * How far the plan may be zoomed in, as a factor of the framing that fits the whole viewbox;
22
+ * defaults to `35`.
23
+ */
24
+ maxZoom?: number;
25
+ /**
26
+ * The shallowest the plan may be pitched away from face-on, in degrees — `0` looks straight down
27
+ * at it; defaults to `0`.
28
+ */
29
+ minPitch?: number;
30
+ /**
31
+ * The steepest the plan may be pitched away from face-on, in degrees; defaults to
32
+ * {@link MAX_PITCH}, which is also the most that may be asked for — the depth guarantee is sized
33
+ * against that angle, so a steeper view is one it never allowed for.
34
+ */
35
+ maxPitch?: number;
36
+ /**
37
+ * Where the camera may look: the region its target is held inside, in plan units. Omitted — the
38
+ * default — lets it look anywhere.
39
+ *
40
+ * A limit like the four above rather than a command, which is what makes it a prop: a host whose
41
+ * bounds follow the floor on show changes them by rendering, with no reaction to write.
42
+ */
43
+ bounds?: Box;
44
+ /**
45
+ * How long a programmatic move takes to settle; see {@link SmoothTime}. Any duration left out
46
+ * takes {@link DEFAULT_SMOOTH_TIME}, so this prop always says the whole truth rather than adding
47
+ * to what was set before.
48
+ *
49
+ * Only programmatic moves: how a *dragged* one feels is the gesture's business and is set where
50
+ * the controls are mounted.
51
+ */
52
+ smoothTime?: SmoothTime;
53
+ }
54
+ /**
55
+ * How long each kind of programmatic camera move takes to settle, in seconds.
56
+ *
57
+ * Named for what a caller asked the plan to do rather than for how `camera-controls` does it, which
58
+ * is why this is the renderer's own type and not the library's: a host turning the plan asked for a
59
+ * roll*, and has no reason to know that it is driven by an azimuth. The same four durations the
60
+ * imperative renderer took, under the same four names.
61
+ */
62
+ export interface SmoothTime {
63
+ /** How long {@link CameraAPI.zoomTo} and {@link CameraAPI.zoomBy} take. */
64
+ zoomTime?: number;
65
+ /** How long {@link CameraAPI.panTo} and {@link CameraAPI.panBy} take. */
66
+ panTime?: number;
67
+ /** How long {@link CameraAPI.rollTo} and {@link CameraAPI.rollBy} take. */
68
+ rollTime?: number;
69
+ /** How long {@link CameraAPI.pitchTo} and {@link CameraAPI.pitchBy} take. */
70
+ pitchTime?: number;
71
+ }
72
+ /**
73
+ * The camera the plan is drawn through, and the zoom that camera is read as.
74
+ *
75
+ * Everything the imperative renderer's camera system did, with one simplification the viewbox fit
76
+ * pays for: because {@link Space} sizes a world unit to a device pixel, the distance that frames the
77
+ * plan follows from the drawing buffer alone and none of the arithmetic here has to know what the
78
+ * scene holds. The near plane is the one exception, its guarantee being stated in plan units.
79
+ *
80
+ * **Zoom is the state and distance is derived.** The camera stores a distance, which the buffer
81
+ * height converts to a factor — so a resize would silently change the zoom if nothing held it. This
82
+ * remembers the factor and dollies back to it, which is what the imperative renderer kept a previous
83
+ * viewport height for; a user dolly writes the factor back on the controls' own update.
84
+ *
85
+ * **The one thing it needs from the scene is `space.scale`,** and only for the near plane. The
86
+ * imperative renderer kept its camera and scene independent because it applied its depth guarantee
87
+ * to world distances; that guarantee is stated in plan units, so converting it is what makes it hold
88
+ * on a plan drawn at any scale (see {@link nearPlane}). Nothing else here reads the scene.
89
+ *
90
+ * **All four limits are written here**, zoom and pitch alike, because they are the same kind of
91
+ * thing: a constraint on where the camera may go, holding for a programmatic move as much as for a
92
+ * dragged one. What the *user* may do is `Controls`'s business and nothing to do with these. Keeping
93
+ * them together also puts each limit beside the clipping plane derived from it — `maxPolarAngle`
94
+ * sizes the near plane, the maximum distance the far one.
95
+ *
96
+ * Configures R3F's camera rather than replacing it, which leaves `aspect` to R3F — it writes that on
97
+ * every resize, from the same size this reads. Mutations go through the store getter because the
98
+ * React Compiler refuses to lower a write to anything a hook returned.
99
+ * @param props - How far the plan may be zoomed and pitched, and what should reach the handle.
100
+ * @returns The children, under the camera handle.
101
+ */
102
+ export declare function Camera(props: PropsWithChildren<CameraProps>): import("react").JSX.Element;
103
+ //# sourceMappingURL=Camera.d.ts.map