@expofp/renderer 3.44.8 → 3.45.1

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 (33) hide show
  1. package/dist/Renderer.js +7 -1
  2. package/dist/controls/hooks.d.ts +2 -1
  3. package/dist/core/sprites/BatchedSprites.js +2 -34
  4. package/dist/core/text/MemberTexture.js +49 -8
  5. package/dist/events/LayoutDriver.d.ts +5 -0
  6. package/dist/events/LayoutDriver.js +136 -29
  7. package/dist/events/layout-targets.d.ts +45 -3
  8. package/dist/events/layout-targets.js +129 -9
  9. package/dist/events/placement/box.d.ts +59 -0
  10. package/dist/events/placement/box.js +96 -0
  11. package/dist/events/placement/grid.d.ts +161 -0
  12. package/dist/events/placement/grid.js +235 -0
  13. package/dist/events/placement/index.d.ts +2 -0
  14. package/dist/events/placement/index.js +1 -0
  15. package/dist/events/placement/pass.d.ts +70 -0
  16. package/dist/events/placement/pass.js +617 -0
  17. package/dist/objects/images/ImageInstance.js +1 -0
  18. package/dist/objects/images/Images.js +2 -2
  19. package/dist/objects/images/image-container.d.ts +12 -0
  20. package/dist/objects/images/image-container.js +20 -4
  21. package/dist/objects/materials/TextureMaterial.d.ts +11 -8
  22. package/dist/objects/materials/TextureMaterial.js +24 -17
  23. package/dist/objects/text/text-container.d.ts +16 -1
  24. package/dist/objects/text/text-container.js +23 -1
  25. package/dist/space/layout-measure.d.ts +12 -10
  26. package/dist/space/layout-measure.js +18 -16
  27. package/dist/types/defs.d.ts +157 -147
  28. package/dist/types/events.d.ts +5 -1
  29. package/dist/types/guards.d.ts +7 -0
  30. package/dist/types/guards.js +10 -0
  31. package/dist/utils/math.d.ts +18 -1
  32. package/dist/utils/math.js +35 -0
  33. package/package.json +2 -2
@@ -1,224 +1,234 @@
1
1
  import { type Box, type LineLike, type Rect, type Shape } from '@expofp/geometry';
2
- import { type LayoutHandlers, type PointerHandlers } from './events';
3
- /** A color as a css string or a packed numeric value. */
2
+ import { type LayoutHandlers, type LayoutView, type PointerHandlers } from './events';
3
+ /** A color as a CSS string or a packed number. */
4
4
  export type ColorInput = string | number;
5
5
  /**
6
- * Any image-like source that the renderer can upload as a texture.
7
- *
8
- * A `Blob` is how anything fetched over the network arrives. It is a handle to an immutable byte
9
- * store that the browser reference-counts. `postMessage` clones the handle, not the bytes. That is
10
- * what lets atlas packing move to a worker. The renderer decodes the `Blob` itself, off the main
11
- * thread wherever the environment allows it.
12
- *
13
- * `HTMLImageElement` is deliberately absent. Decoding one costs the main thread more than the
14
- * packing it feeds: at a thousand logos on a throttled CPU, it stalled the page for two seconds.
15
- * Leaving the member in the union would keep that slow path reachable. Pass a `Blob` instead.
16
- *
17
- * There is no `string` member either. The caller keeps all networking, so the renderer never
18
- * learns about URLs, retries, or CORS.
6
+ * An image the renderer can upload as a texture. Pass fetched images as a `Blob`: the renderer
7
+ * decodes it off the main thread. `HTMLImageElement` and URL strings are not accepted.
19
8
  */
20
9
  export type ImageSource = HTMLCanvasElement | ImageBitmap | OffscreenCanvas | Blob;
21
- /** Scene definition */
10
+ /** Scene definition. */
22
11
  export interface SceneDef {
23
- /** Root layer of the scene tree */
12
+ /** The root layer of the scene tree. */
24
13
  rootLayer: LayerDef;
25
- /** Background color in css format */
14
+ /** The background color. */
26
15
  background?: ColorInput;
27
- /** SVG viewbox */
16
+ /** The SVG viewbox. */
28
17
  viewbox: Box;
29
18
  /**
30
- * Static transform matrix: plan → the host's world, in place of the viewbox fit and in the host's
31
- * own coordinates. Assumed to share plan space's handedness — see `HANDEDNESS_FLIP` in Space.ts, which the
32
- * renderer adds on top to reach three's.
19
+ * A plan-to-world matrix that replaces the viewbox fit, in the host's coordinates. It must keep
20
+ * plan space's handedness.
33
21
  */
34
22
  staticTransform?: number[];
35
23
  }
36
- /** Shared properties for all renderable defs */
24
+ /** Fields every renderable def shares. */
37
25
  export interface SharedDef extends LayoutHandlers {
38
- /** Whether the def is hidden */
26
+ /** Whether the def is hidden. */
39
27
  hidden?: boolean;
40
28
  /**
41
- * How strongly the def is dimmed — desaturated toward gray and darkened — from `0` (undimmed) to
42
- * `1` (fully dimmed), interpolating in between.
43
- *
44
- * `undefined` inherits from the nearest ancestor that sets one, and only `undefined` inherits: a
45
- * def's own `0` un-dims it inside a dimmed layer, and its own `1` dims it inside an undimmed one.
46
- * A layer and a single def carry the same field with the same meaning, so dimming a whole floor
47
- * and un-dimming one booth on it are the same operation at two levels. The root defaults to `0`.
48
- *
49
- * Values outside `[0, 1]` are clamped. Being a number rather than a flag is what lets a dim be
50
- * animated — easing a layer's value from `0` to `1` costs one uniform per layer per frame,
51
- * whatever the layer holds.
29
+ * How much the def is dimmed, from `0` (none) to `1` (full). `undefined` inherits from the
30
+ * nearest ancestor, so a def can set `0` to stay undimmed inside a dimmed layer. Animate it
31
+ * freely: a change costs one uniform write per layer.
52
32
  */
53
33
  dim?: number;
54
34
  }
55
35
  /** Any def that can be rendered into a scene. */
56
36
  export type RenderableDef = LayerDef | ShapeDef | ImageDef | TextDef | LineDef | ModelDef;
57
- /**
58
- * An array of a single {@link RenderableDef} kind (never a mixed union). The conditional
59
- * distributes over {@link RenderableDef}, so even the default is a union of single-kind arrays —
60
- * a mixed array is unrepresentable, which is what lets a first-element guard speak for the whole
61
- * collection. `Readonly<RenderableDefCollection<T>>` distributes the same way, into readonly
62
- * single-kind arrays.
63
- */
37
+ /** An array of one {@link RenderableDef} kind. Mixed arrays do not type-check. */
64
38
  export type RenderableDefCollection<T = RenderableDef> = T extends RenderableDef ? T[] : never;
65
39
  /**
66
- * Layer definition.
67
- *
68
- * A layer is also where interaction lives: the handlers it inherits from {@link PointerHandlers} are
69
- * what make its subtree pickable, and each event names the def it hit. Layout is the other way round
70
- * — a layer carries its own {@link LayoutHandlers} through {@link SharedDef}, like any other def, and
71
- * that is how a layer whose contents depend on the camera re-derives them.
40
+ * Layer definition. Its {@link PointerHandlers} make its subtree pickable, and its
41
+ * {@link LayoutHandlers} run like any other def's.
72
42
  */
73
43
  export interface LayerDef<T extends RenderableDef = RenderableDef> extends SharedDef, PointerHandlers<T> {
74
- /** Layer's name */
44
+ /** The layer's name. */
75
45
  readonly name: string;
76
- /** Collection of layer's children. Mixing different types of children is not supported. */
46
+ /** The layer's children, all of one kind. */
77
47
  children: RenderableDefCollection<T>;
78
48
  /**
79
- * Draws this layer and its subtree above 3D content. Depth testing goes off, and the layer is
80
- * ordered by the overlay tier rather than by the plan tier.
49
+ * Draws the subtree above 3D content, with depth testing off. For flat content only: solid
50
+ * shapes and models ignore it. Descendants cannot opt out.
51
+ */
52
+ overlay?: boolean;
53
+ /**
54
+ * Placement defs compete only with defs in layers that share this key. Without it, the layer
55
+ * competes only with itself. Prefix the key with its owner.
56
+ */
57
+ collisionGroup?: string;
58
+ /**
59
+ * Turns collisions in this layer's collision scope into clusters. Both colliding defs are hidden
60
+ * through their own `onPlace`, and the cluster is reported to {@link ClusterOptions.onCluster}.
61
+ */
62
+ clustering?: ClusterOptions;
63
+ }
64
+ /** Clustering options of a layer. */
65
+ export interface ClusterOptions {
66
+ /**
67
+ * The size of the marker you draw for a cluster. Any def or cluster that this marker would
68
+ * cover joins the cluster. Without it, the marker takes the size of the cluster's first member.
69
+ * When several clustering layers share a scope, the first layer's marker is used.
70
+ */
71
+ marker?: ClusterMarker;
72
+ /**
73
+ * Receives the full current set of clusters whenever it changes. A layer that loses all its
74
+ * clusters receives an empty array once.
81
75
  *
82
- * A layer property, and a forced one. Depth mode is per material and render order is per group, so
83
- * no implementation can vary either per instance. A def-level override could not be honored even
84
- * if a caller wrote one.
76
+ * Changes are held while the plan moves on screen and are reported once it has stood still for a
77
+ * moment. A registration, an unregistration or a def whose bounds you update is reported at once.
78
+ * `onPlace` calls arrive first, in the same synchronous call.
85
79
  *
86
- * **This is for planar content.** A volume that stops depth testing loses its own self occlusion,
87
- * because its far faces then paint over its near ones in pack order. So a solid shape batch and a
88
- * model batch ignore this flag. A solid batch also warns once that it did.
80
+ * Return the defs that changed. Never call `update()` here. Keep the options object stable: a
81
+ * new object is not noticed while the set stays the same.
82
+ * @param clusters - A new array on each call, in placement order.
83
+ * @param view - The current camera view.
84
+ * @returns The defs that changed, or nothing.
85
+ */
86
+ onCluster(this: void, clusters: readonly Cluster[], view: LayoutView): readonly RenderableDef[] | void;
87
+ }
88
+ /** The screen box of a cluster's marker. */
89
+ export interface ClusterMarker {
90
+ /** The width in CSS pixels. */
91
+ width: number;
92
+ /** The height in CSS pixels. */
93
+ height: number;
94
+ /** The point that stands on the cluster's anchor, as in {@link ImageDef.origin}. */
95
+ origin?: [number, number];
96
+ }
97
+ /** One cluster of colliding defs. */
98
+ export interface Cluster {
99
+ /** How many units it holds, at least two. A `placement.group` counts as one unit. */
100
+ readonly unitCount: number;
101
+ /** The mean x of the units' anchors, in plan units. */
102
+ readonly x: number;
103
+ /** The mean y of the units' anchors, in plan units. */
104
+ readonly y: number;
105
+ /** The greatest elevation of its members, in plan units. */
106
+ readonly elevation: number;
107
+ }
108
+ /**
109
+ * How a def competes for room on screen.
110
+ *
111
+ * - `never` (default): it is placed only where nothing placed before covers it, and then it
112
+ * blocks the defs after it.
113
+ * - `always`: it is always placed, blocks nothing and never clusters. In a group, it takes the
114
+ * group's verdict.
115
+ */
116
+ export type OverlapMode = 'never' | 'always';
117
+ /** Placement options of a screen-space def. */
118
+ export interface PlacementOptions {
119
+ /**
120
+ * Receives the placement verdict: on the first pass after the def registers, then each time the
121
+ * verdict changes. Changes are reported when the camera stops, as for
122
+ * {@link ClusterOptions.onCluster}.
89
123
  *
90
- * **A descendant cannot opt back out**: three propagates a group's render order to its whole
91
- * subtree, so a descendant left in the plan tier would invert the nesting.
124
+ * Return the defs that changed. Never call `update()` here.
125
+ * @param placed - Whether the def got room on screen.
126
+ * @param view - The current camera view.
127
+ * @returns The defs that changed, or nothing.
92
128
  */
93
- overlay?: boolean;
129
+ onPlace(this: void, placed: boolean, view: LayoutView): readonly RenderableDef[] | void;
130
+ /**
131
+ * Defs that share this key compete and cluster as one unit, with the union of their boxes. All
132
+ * members get the same verdict. The first member in order sets the unit's priority. Keys apply
133
+ * per collision scope. Prefix the key with its owner, and unregister old defs before you reuse
134
+ * their key.
135
+ */
136
+ group?: string;
137
+ /** CSS pixels added around the tested box: one value, or `[horizontal, vertical]`. */
138
+ padding?: number | [number, number];
139
+ /** How the def competes. Defaults to `never`. */
140
+ overlap?: OverlapMode;
141
+ }
142
+ /** Fields of the def kinds that can face the camera: {@link ImageDef} and {@link TextDef}. */
143
+ export interface ScreenSpaceDef extends SharedDef {
144
+ /**
145
+ * Faces the def toward the camera. It pivots about `bounds.center` at `bounds.elevation`, sized
146
+ * by `bounds.size` in plan units. `bounds.rotation` spins it in the screen plane. Use an
147
+ * `onScale` rule to keep a constant pixel size.
148
+ */
149
+ screenSpace?: boolean;
150
+ /**
151
+ * Enters the def into the placement pass, which hides it where a def earlier in order already
152
+ * covers the screen. Works only when {@link screenSpace} is true.
153
+ */
154
+ placement?: PlacementOptions;
94
155
  }
95
156
  /** Shape definition. */
96
157
  export interface ShapeDef extends SharedDef {
97
158
  /**
98
- * Shape to render. To change it, **replace** this with a new `Shape` and call `update(def)`: the
99
- * new object re-claims the instance against fresh geometry, so even the shape *type* may change
100
- * (rect ↔ polygon). Mutating the existing shape in place is more limited — a rect's or box's
101
- * transform (center, size, rotation) is re-read every update, but a polygon's vertices and indices
102
- * are cached by shape identity and will not rebuild in place.
159
+ * The shape to render. To change it, assign a new `Shape` and call `update(def)`. A polygon
160
+ * changed in place does not rebuild.
103
161
  */
104
162
  shape: Shape;
105
- /** Shape's color in css format */
163
+ /** The shape's color. */
106
164
  color: ColorInput;
107
165
  }
108
- /** Image definition */
109
- export interface ImageDef extends SharedDef {
110
- /**
111
- * Image source. Write-once — a def's pixels are fixed for its lifetime: the atlas packs and
112
- * uploads them at claim time and never re-reads the source, so a new image is a new def, not a
113
- * reassignment. `readonly` makes that the type's guarantee (see {@link SceneDef.memoryLimit},
114
- * which relies on a pack's byte totals staying stable until a repack).
115
- */
166
+ /** Image definition. */
167
+ export interface ImageDef extends ScreenSpaceDef {
168
+ /** The image source. Fixed for the def's life: use a new def for a new image. */
116
169
  readonly source: ImageSource;
117
- /** Image's bounds */
170
+ /** The image's bounds. */
118
171
  bounds: Rect;
119
172
  /**
120
- * Image's anchor point. Defines the point on the image that is used as an origin point for transformations.
121
- * [0, 0] is the top-left corner, [0.5, 0.5] is the center (default), [1, 1] is the bottom-right corner.
173
+ * The point of the image that `bounds.center` names. `[0, 0]` is the top-left corner,
174
+ * `[0.5, 0.5]` the center (default), `[1, 1]` the bottom-right corner.
122
175
  */
123
176
  origin?: [number, number];
124
- /**
125
- * Faces the camera, pivoting about `bounds.center` at `bounds.elevation`, at the size `bounds.size`
126
- * gives in **plan units**. That is the same meaning `bounds.size` has for a flat def. A caller that
127
- * wants a constant on-screen size keeps doing what markers do today, which is an `onScale` rule
128
- * that rebuilds the bounds from the pixel size.
129
- *
130
- * `bounds.rotation` keeps its meaning, which is a spin about the content plane's normal. For a
131
- * screen-space def that plane is the screen, so there is no separate spin field. An `onRoll` rule
132
- * still runs, so keep this off where the rotation carries a plan bearing. Such a def would point
133
- * at one screen direction however the plan is turned.
134
- *
135
- * Changing this at runtime is supported. The def moves between the layer's flat mesh and its
136
- * camera-facing mesh, which never touches the atlas packing.
137
- */
138
- screenSpace?: boolean;
177
+ /** How opaque the image is, from `0` to `1`. Defaults to `1`. It blends as alpha does. */
178
+ opacity?: number;
139
179
  }
140
- /** Text definition */
141
- export interface TextDef extends SharedDef {
142
- /**
143
- * Array of text lines, drawn as a block stacked inside {@link bounds}.
144
- *
145
- * Updatable in both directions: a line's own fields are re-read on every `update(def)`, and
146
- * changing the array's *length* re-claims the def's batch members against the new count. (The
147
- * imperative renderer allocated members once and so required this to be fixed; React reconciles
148
- * it instead.) A line whose `fontSize` is `0` is not drawn.
149
- */
180
+ /** Text definition. */
181
+ export interface TextDef extends ScreenSpaceDef {
182
+ /** The text lines, stacked inside {@link bounds}. A line with `fontSize` `0` is not drawn. */
150
183
  lines: TextLineDef[];
151
- /** Text's bounds */
184
+ /** The text's bounds. */
152
185
  bounds: Rect;
153
- /** Text's padding [horizontal, vertical] */
186
+ /** The text's padding, `[horizontal, vertical]`. */
154
187
  padding: [number, number];
155
- /** {@link TextAlignment} object describing text's alignment */
188
+ /** How the text is aligned. */
156
189
  alignment: TextAlignment;
157
- /**
158
- * As `ImageDef.screenSpace`. The block faces the camera and keeps its plan anchor and its
159
- * plan-unit font sizes. The whole block pivots about `bounds.center`, on one anchor shared by
160
- * every line, which is what keeps a stack from splaying under tilt.
161
- */
162
- screenSpace?: boolean;
163
190
  }
164
- /** Single text line definition */
191
+ /** One line of a text def. */
165
192
  export interface TextLineDef {
166
- /** Line's text */
193
+ /** The line's text. */
167
194
  text: string;
168
- /** Line's color in css format */
195
+ /** The line's color. */
169
196
  color: ColorInput;
170
- /** Line's font URL. Not updatable. Supported formats: .ttf, .otf, .woff (.woff2 is not supported). */
197
+ /** The font URL. Not updatable. Supports .ttf, .otf and .woff, not .woff2. */
171
198
  fontUrl: string;
172
- /**
173
- * Line's font size: its em height in plan units, the same units as the def's `bounds` and
174
- * `padding`.
175
- *
176
- * Nothing here converts from pixels. A plan sizing a line to hold an on-screen size does that
177
- * itself, against the pixel size the renderer publishes — as it does for every other
178
- * pixel-authored thing it draws. `0` means the line is not drawn.
179
- */
199
+ /** The em height in plan units. `0` hides the line. */
180
200
  fontSize: number;
181
- /** Line's stroke color & width, the width in the same units as {@link fontSize}. Optional. */
201
+ /** The stroke color, and its width in the same units as {@link fontSize}. */
182
202
  stroke?: {
183
203
  color: ColorInput;
184
204
  width: number;
185
205
  };
186
206
  }
187
- /** Text alignment */
207
+ /** Text alignment. */
188
208
  export interface TextAlignment {
189
- /** Horizontal alignment */
209
+ /** The horizontal alignment. */
190
210
  horizontal: 'left' | 'center' | 'right';
191
- /** Vertical alignment */
211
+ /** The vertical alignment. */
192
212
  vertical: 'top' | 'center' | 'bottom';
193
- /** Text alignment within the line. Optional. */
213
+ /** The alignment of text inside a line. */
194
214
  text?: 'left' | 'center' | 'right';
195
215
  }
196
- /** Line definition */
216
+ /** Line definition. */
197
217
  export interface LineDef extends SharedDef {
198
- /**
199
- * The segment's endpoints, with an optional `elevation` placing it on its layer's plane.
200
- *
201
- * Updatable: both ends are re-read on every `update(def)` and written only when they moved,
202
- * so a line can be animated in place rather than replaced. A `LineLike` exposes readonly coordinates,
203
- * so an update means replacing this with a new one — or moving the `Line` behind it,
204
- * whose own setters do the same job.
205
- */
218
+ /** The endpoints, with an optional elevation. Assign a new value and call `update(def)`. */
206
219
  points: LineLike;
207
- /** Line's color in css format */
220
+ /** The line's color. */
208
221
  color: ColorInput;
209
- /**
210
- * Line's width in screen-space units (pixels), so it holds its thickness as the plan zooms.
211
- * `0` draws nothing.
212
- */
222
+ /** The width in screen pixels, constant at every zoom. `0` draws nothing. */
213
223
  width: number;
214
224
  }
215
- /** External 3D model definition */
225
+ /** External 3D model definition. */
216
226
  export interface ModelDef extends SharedDef {
217
- /** Model's URL. Not updatable. */
227
+ /** The model URL. Not updatable. */
218
228
  url: string;
219
- /** Model's bounds */
229
+ /** The model's bounds. */
220
230
  bounds: Rect;
221
- /** Model's opacity. Optional. */
231
+ /** The model's opacity, from `0` to `1`. */
222
232
  opacity?: number;
223
233
  }
224
234
  //# sourceMappingURL=defs.d.ts.map
@@ -119,8 +119,12 @@ export type RollHandler = NonNullable<LayoutHandlers['onRoll']>;
119
119
  */
120
120
  export interface LayoutView {
121
121
  /**
122
- * How much of the plan one device pixel spans, in plan units — the factor anything sized in
122
+ * How much of the plan one CSS pixel spans, in plan units — the factor anything sized in
123
123
  * pixels is laid out against, and the quantity the imperative renderer published as `ptScale`.
124
+ *
125
+ * CSS pixels, so a size authored in them comes out the same at every screen density. The device
126
+ * ratio never reaches a rule because a rule never needs it: the ratio sets how crisp a def is
127
+ * drawn, not how big.
124
128
  */
125
129
  readonly pixelSize: number;
126
130
  /**
@@ -5,6 +5,13 @@ export declare function isTextDef(def: RenderableDef): def is TextDef;
5
5
  export declare function isLineDef(def: RenderableDef): def is LineDef;
6
6
  export declare function isModelDef(def: RenderableDef): def is ModelDef;
7
7
  export declare function isLayerDef(def: RenderableDef): def is LayerDef;
8
+ /**
9
+ * Whether a def takes part in the placement pass: it declares placement options, and it faces the
10
+ * camera. A flat def carrying options is not a target, which is what the pass warns about.
11
+ * @param def - The def to test.
12
+ * @returns Whether the pass tests it.
13
+ */
14
+ export declare function isPlacementTarget(def: RenderableDef): def is ImageDef | TextDef;
8
15
  export declare function isLayerDefs(defs: Readonly<RenderableDefCollection>): defs is readonly LayerDef[];
9
16
  export declare function isShapeDefs(defs: Readonly<RenderableDefCollection>): defs is readonly ShapeDef[];
10
17
  export declare function isImageDefs(defs: Readonly<RenderableDefCollection>): defs is readonly ImageDef[];
@@ -16,6 +16,16 @@ export function isModelDef(def) {
16
16
  export function isLayerDef(def) {
17
17
  return def.children !== undefined;
18
18
  }
19
+ /**
20
+ * Whether a def takes part in the placement pass: it declares placement options, and it faces the
21
+ * camera. A flat def carrying options is not a target, which is what the pass warns about.
22
+ * @param def - The def to test.
23
+ * @returns Whether the pass tests it.
24
+ */
25
+ export function isPlacementTarget(def) {
26
+ const target = def;
27
+ return target.placement !== undefined && target.screenSpace === true;
28
+ }
19
29
  // The array guards below take the collection type, not `readonly RenderableDef[]`: the collection
20
30
  // is a union of single-kind arrays, so a first-element check is sound for the whole array. A plain
21
31
  // `RenderableDef[]` parameter would admit mixed arrays the check cannot speak for. The `Readonly`
@@ -1,4 +1,4 @@
1
- import { type Matrix4 } from 'three';
1
+ import { type Camera, type Matrix4 } from 'three';
2
2
  /**
3
3
  * Holds a value inside an inclusive range.
4
4
  *
@@ -32,4 +32,21 @@ export declare function euclideanModulo(value: number, modulus: number): number;
32
32
  * @returns The transformed axis's length.
33
33
  */
34
34
  export declare function axisLength(matrix: Matrix4, axis: number): number;
35
+ /**
36
+ * How far the camera's own axis travels before it meets a batch's own plane, in world units.
37
+ *
38
+ * That is the depth a plan-unit size is exact at, so it is what the shader cancels the perspective
39
+ * divide against. It has to agree with the depth `measurePixelSize` in `space/layout-measure.ts`
40
+ * works at, because a caller's `onScale` rule sizes a def in plan units from the pixel size that
41
+ * function reports. Without controls mounted that function reads this same crossing point. With
42
+ * them it reads their distance to their target, and the two agree while the target sits on the
43
+ * plan, which is what orbiting a floor plan keeps true.
44
+ *
45
+ * The plane is the batch's own, taken from its world matrix, because a sprite batch draws at plan
46
+ * `z = 0` and whatever places the plan places the mesh with it.
47
+ * @param camera - The camera this draw uses.
48
+ * @param planToWorld - The batch's world matrix.
49
+ * @returns The distance in world units, or `0` for a camera that never meets the plane ahead of it.
50
+ */
51
+ export declare function aimDepth(camera: Camera, planToWorld: Matrix4): number;
35
52
  //# sourceMappingURL=math.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { Vector3 } from 'three';
1
2
  /**
2
3
  * Holds a value inside an inclusive range.
3
4
  *
@@ -39,3 +40,37 @@ export function axisLength(matrix, axis) {
39
40
  const at = axis * 4;
40
41
  return Math.hypot(elements[at], elements[at + 1], elements[at + 2]);
41
42
  }
43
+ /**
44
+ * How far the camera's own axis travels before it meets a batch's own plane, in world units.
45
+ *
46
+ * That is the depth a plan-unit size is exact at, so it is what the shader cancels the perspective
47
+ * divide against. It has to agree with the depth `measurePixelSize` in `space/layout-measure.ts`
48
+ * works at, because a caller's `onScale` rule sizes a def in plan units from the pixel size that
49
+ * function reports. Without controls mounted that function reads this same crossing point. With
50
+ * them it reads their distance to their target, and the two agree while the target sits on the
51
+ * plan, which is what orbiting a floor plan keeps true.
52
+ *
53
+ * The plane is the batch's own, taken from its world matrix, because a sprite batch draws at plan
54
+ * `z = 0` and whatever places the plan places the mesh with it.
55
+ * @param camera - The camera this draw uses.
56
+ * @param planToWorld - The batch's world matrix.
57
+ * @returns The distance in world units, or `0` for a camera that never meets the plane ahead of it.
58
+ */
59
+ export function aimDepth(camera, planToWorld) {
60
+ // A camera looks down its own negative z.
61
+ const direction = _direction.setFromMatrixColumn(camera.matrixWorld, 2).normalize().negate();
62
+ const normal = _normal.setFromMatrixColumn(planToWorld, 2).normalize();
63
+ const facing = direction.dot(normal);
64
+ // The camera looks along the plane and never meets it.
65
+ if (facing === 0)
66
+ return 0;
67
+ const eye = _eye.setFromMatrixPosition(camera.matrixWorld);
68
+ const depth = _origin.setFromMatrixPosition(planToWorld).sub(eye).dot(normal) / facing;
69
+ // Behind the camera is no measurement, so the shader keeps its plan-unit sizing instead.
70
+ return depth > 0 ? depth : 0;
71
+ }
72
+ // Scratch for aimDepth, filled and consumed within one call.
73
+ const _direction = new Vector3();
74
+ const _normal = new Vector3();
75
+ const _eye = new Vector3();
76
+ const _origin = new Vector3();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expofp/renderer",
3
- "version": "3.44.8",
3
+ "version": "3.45.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/expofp/efp-app.git",
@@ -42,7 +42,7 @@
42
42
  "troika-three-text": "^0.52.5",
43
43
  "tslib": "^2.3.0",
44
44
  "webgl-memory": "^1.1.3",
45
- "@expofp/geometry": "3.44.8"
45
+ "@expofp/geometry": "3.45.1"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@react-three/test-renderer": "^9.1.0",