@expofp/renderer 3.28.1 → 3.28.3
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.
|
@@ -7,7 +7,8 @@ import { type Camera } from 'three';
|
|
|
7
7
|
* (P′, V′) pairs multiply to the same M — but the *rigid* split is the one three's `Camera` class
|
|
8
8
|
* assumes for `getWorldDirection`, frustum culling, raycasting and the lighting that reads the
|
|
9
9
|
* camera's world matrix. Any extra scale or skew in M (mercator-unit conversion, say) ends up
|
|
10
|
-
* absorbed into P, where it breaks none of those
|
|
10
|
+
* absorbed into P, where it breaks none of those — a *reflection* being the exception, which is why
|
|
11
|
+
* the host's world is mirrored into three's before any of this runs.
|
|
11
12
|
* @param camera - The external camera to publish onto; `matrixAutoUpdate` and
|
|
12
13
|
* `matrixWorldAutoUpdate` must be off, since everything here is written by hand.
|
|
13
14
|
* @param mainMatrix - The combined matrix mapping world space directly to clip space, sixteen
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Matrix3, Matrix4, Vector3 } from 'three';
|
|
2
|
+
import { HANDEDNESS_FLIP } from '../space/Space';
|
|
2
3
|
/**
|
|
3
4
|
* Decomposes the host's combined world-to-clip matrix into a rigid view matrix and a projection
|
|
4
5
|
* matrix, publishing all four matrices (and their inverses) on the camera.
|
|
@@ -7,14 +8,19 @@ import { Matrix3, Matrix4, Vector3 } from 'three';
|
|
|
7
8
|
* (P′, V′) pairs multiply to the same M — but the *rigid* split is the one three's `Camera` class
|
|
8
9
|
* assumes for `getWorldDirection`, frustum culling, raycasting and the lighting that reads the
|
|
9
10
|
* camera's world matrix. Any extra scale or skew in M (mercator-unit conversion, say) ends up
|
|
10
|
-
* absorbed into P, where it breaks none of those
|
|
11
|
+
* absorbed into P, where it breaks none of those — a *reflection* being the exception, which is why
|
|
12
|
+
* the host's world is mirrored into three's before any of this runs.
|
|
11
13
|
* @param camera - The external camera to publish onto; `matrixAutoUpdate` and
|
|
12
14
|
* `matrixWorldAutoUpdate` must be off, since everything here is written by hand.
|
|
13
15
|
* @param mainMatrix - The combined matrix mapping world space directly to clip space, sixteen
|
|
14
16
|
* elements column-major.
|
|
15
17
|
*/
|
|
16
18
|
export function setExternalCameraProjection(camera, mainMatrix) {
|
|
17
|
-
|
|
19
|
+
// The host's matrix reads the host's world; the space mirrors that world into three's, so undo
|
|
20
|
+
// the mirror here. The two cancel: the plan lands exactly where the host put it, and what is left
|
|
21
|
+
// is a plan → world matrix that reverses orientation, which is the whole point — see
|
|
22
|
+
// {@link HANDEDNESS_FLIP}.
|
|
23
|
+
const M = _main.fromArray(mainMatrix).multiply(HANDEDNESS_FLIP);
|
|
18
24
|
const e = M.elements; // column-major storage: M[row, col] = e[row + 4 * col]
|
|
19
25
|
// Step 1: camera world position c.
|
|
20
26
|
//
|
package/dist/objects/Lighting.js
CHANGED
|
@@ -47,8 +47,8 @@ export function Lighting() {
|
|
|
47
47
|
.setFromMatrixColumn(state.camera.matrixWorld, 2)
|
|
48
48
|
.transformDirection(space.matrixInverse);
|
|
49
49
|
// The plan's own up axis, as a point whose world position lands back on it — which is what three
|
|
50
|
-
// reads a hemisphere light's sky axis from.
|
|
51
|
-
//
|
|
50
|
+
// reads a hemisphere light's sky axis from. It is world -z either way, the side the camera is
|
|
51
|
+
// on: every path into world space ends with handedness flip.
|
|
52
52
|
//
|
|
53
53
|
// Computed here rather than memoized against the space because the space is refitted in place
|
|
54
54
|
// and so never changes identity — and it is four vector operations on a frame that was going to
|
package/dist/space/Space.d.ts
CHANGED
|
@@ -22,8 +22,8 @@ export type PlanPointLike = PointLike & {
|
|
|
22
22
|
__space: 'plan';
|
|
23
23
|
};
|
|
24
24
|
/**
|
|
25
|
-
* A point in **world space**: three's own space, the one the camera is placed in.
|
|
26
|
-
*
|
|
25
|
+
* A point in **world space**: three's own space, the one the camera is placed in. Right-handed, so
|
|
26
|
+
* world z grows *away* from the viewer — which is why {@link Space} mirrors the plan's z.
|
|
27
27
|
*
|
|
28
28
|
* World and NDC points are three's vectors instead of `@expofp/geometry`: neither is part of the
|
|
29
29
|
* public API, and both exist only to be multiplied by matrices and unprojected through cameras.
|
|
@@ -81,9 +81,9 @@ export interface SpaceOptions {
|
|
|
81
81
|
*/
|
|
82
82
|
dpr: number;
|
|
83
83
|
/**
|
|
84
|
-
* A caller-supplied plan → world matrix, 16 elements column-major
|
|
85
|
-
* viewbox fit rather than composing with it
|
|
86
|
-
*
|
|
84
|
+
* A caller-supplied plan → world matrix, 16 elements column-major, in the host's own coordinates.
|
|
85
|
+
* When present it *replaces* the viewbox fit rather than composing with it; the z mirror both
|
|
86
|
+
* paths end with is not the caller's to supply (see {@link HANDEDNESS_FLIP}).
|
|
87
87
|
*/
|
|
88
88
|
staticTransform?: number[];
|
|
89
89
|
}
|
|
@@ -97,13 +97,16 @@ export declare function useSpace(): Space;
|
|
|
97
97
|
* conversions that ride on it.
|
|
98
98
|
*
|
|
99
99
|
* The mapping is one matrix. Without a `staticTransform` it is the **viewbox fit**: centre the
|
|
100
|
-
* viewbox on the world origin and scale it uniformly to best fill the drawing buffer
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
100
|
+
* viewbox on the world origin and scale it uniformly to best fill the drawing buffer. That fit is
|
|
101
|
+
* what makes one world unit one device pixel when the plan exactly fills the canvas, which in turn
|
|
102
|
+
* is what lets the camera's zoom math be written without reference to the scene at all — see
|
|
103
|
+
* {@link Camera}.
|
|
104
104
|
*
|
|
105
105
|
* With a `staticTransform` the matrix is whatever the caller supplied, and the fit is not computed:
|
|
106
|
-
* a host like maplibre places the plan itself, in its own space
|
|
106
|
+
* a host like maplibre places the plan itself, in its own space.
|
|
107
|
+
*
|
|
108
|
+
* Either way the matrix ends with {@link HANDEDNESS_FLIP}, which is what makes the space three's
|
|
109
|
+
* rather than the plan's or a host's — a positive elevation then lands nearer the camera.
|
|
107
110
|
*
|
|
108
111
|
* **Stable for the scene's lifetime.** A resize does not build a new space — {@link Scene} refits
|
|
109
112
|
* this one in place — so every holder reads the current fit through the same object and none of
|
|
@@ -285,4 +288,23 @@ export declare const SpaceContext: import("react").Context<Space>;
|
|
|
285
288
|
* @param camera - The camera to cast from.
|
|
286
289
|
*/
|
|
287
290
|
export declare function castRay(raycaster: Raycaster, ndc: NDCPointLike, camera: Camera): void;
|
|
291
|
+
/**
|
|
292
|
+
* The last step of both paths into world space: mirror z.
|
|
293
|
+
*
|
|
294
|
+
* Plan space is left-handed — x right, y **down**, z up — where three's world is right-handed, so
|
|
295
|
+
* one axis has to flip and z is it: the plan's up is world `-z`, the side the camera is on.
|
|
296
|
+
*
|
|
297
|
+
* The flip belongs in the plan → world matrix and nowhere else. three reads a face's winding from
|
|
298
|
+
* that matrix's determinant alone (`matrixWorld.determinantAffine() < 0`) and never from the
|
|
299
|
+
* projection, so a reflection anywhere else is invisible to it: every `DoubleSide` lit surface
|
|
300
|
+
* negates its normal and shades from behind.
|
|
301
|
+
*
|
|
302
|
+
* **Assumes a host's world shares the plan's handedness**, which mercator does — x east, y south,
|
|
303
|
+
* z up — resolving the clash inside its own projection instead (`MercatorTransform._calcMatrices`
|
|
304
|
+
* scales by `[1, -1, 1]`). So the same flip serves both paths, and external mode undoes it on the
|
|
305
|
+
* host's own matrix (`setExternalCameraProjection`). A host with a right-handed world would
|
|
306
|
+
* need it skipped on both — a `leftHandedWorld` flag on the external contract is where that choice
|
|
307
|
+
* would go.
|
|
308
|
+
*/
|
|
309
|
+
export declare const HANDEDNESS_FLIP: Matrix4;
|
|
288
310
|
//# sourceMappingURL=Space.d.ts.map
|
package/dist/space/Space.js
CHANGED
|
@@ -17,13 +17,16 @@ export function useSpace() {
|
|
|
17
17
|
* conversions that ride on it.
|
|
18
18
|
*
|
|
19
19
|
* The mapping is one matrix. Without a `staticTransform` it is the **viewbox fit**: centre the
|
|
20
|
-
* viewbox on the world origin and scale it uniformly to best fill the drawing buffer
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* viewbox on the world origin and scale it uniformly to best fill the drawing buffer. That fit is
|
|
21
|
+
* what makes one world unit one device pixel when the plan exactly fills the canvas, which in turn
|
|
22
|
+
* is what lets the camera's zoom math be written without reference to the scene at all — see
|
|
23
|
+
* {@link Camera}.
|
|
24
24
|
*
|
|
25
25
|
* With a `staticTransform` the matrix is whatever the caller supplied, and the fit is not computed:
|
|
26
|
-
* a host like maplibre places the plan itself, in its own space
|
|
26
|
+
* a host like maplibre places the plan itself, in its own space.
|
|
27
|
+
*
|
|
28
|
+
* Either way the matrix ends with {@link HANDEDNESS_FLIP}, which is what makes the space three's
|
|
29
|
+
* rather than the plan's or a host's — a positive elevation then lands nearer the camera.
|
|
27
30
|
*
|
|
28
31
|
* **Stable for the scene's lifetime.** A resize does not build a new space — {@link Scene} refits
|
|
29
32
|
* this one in place — so every holder reads the current fit through the same object and none of
|
|
@@ -91,8 +94,10 @@ export class Space {
|
|
|
91
94
|
// product to come out as scale∘translate — the order the imperative renderer built it in.
|
|
92
95
|
this.matrix
|
|
93
96
|
.makeTranslation(-centerX * fit, -centerY * fit, 0)
|
|
94
|
-
.scale(new Vector3(fit, fit,
|
|
97
|
+
.scale(new Vector3(fit, fit, fit));
|
|
95
98
|
}
|
|
99
|
+
// Both paths land in a host's space or ours; this is what makes either one three's.
|
|
100
|
+
this.matrix.premultiply(HANDEDNESS_FLIP);
|
|
96
101
|
this.matrixInverse.copy(this.matrix).invert();
|
|
97
102
|
this._scale = Math.sqrt(axisLength(this.matrix, 0) * axisLength(this.matrix, 1));
|
|
98
103
|
}
|
|
@@ -306,6 +311,25 @@ export function castRay(raycaster, ndc, camera) {
|
|
|
306
311
|
function axisLength(matrix, axis) {
|
|
307
312
|
return new Vector3().setFromMatrixColumn(matrix, axis).length();
|
|
308
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* The last step of both paths into world space: mirror z.
|
|
316
|
+
*
|
|
317
|
+
* Plan space is left-handed — x right, y **down**, z up — where three's world is right-handed, so
|
|
318
|
+
* one axis has to flip and z is it: the plan's up is world `-z`, the side the camera is on.
|
|
319
|
+
*
|
|
320
|
+
* The flip belongs in the plan → world matrix and nowhere else. three reads a face's winding from
|
|
321
|
+
* that matrix's determinant alone (`matrixWorld.determinantAffine() < 0`) and never from the
|
|
322
|
+
* projection, so a reflection anywhere else is invisible to it: every `DoubleSide` lit surface
|
|
323
|
+
* negates its normal and shades from behind.
|
|
324
|
+
*
|
|
325
|
+
* **Assumes a host's world shares the plan's handedness**, which mercator does — x east, y south,
|
|
326
|
+
* z up — resolving the clash inside its own projection instead (`MercatorTransform._calcMatrices`
|
|
327
|
+
* scales by `[1, -1, 1]`). So the same flip serves both paths, and external mode undoes it on the
|
|
328
|
+
* host's own matrix (`setExternalCameraProjection`). A host with a right-handed world would
|
|
329
|
+
* need it skipped on both — a `leftHandedWorld` flag on the external contract is where that choice
|
|
330
|
+
* would go.
|
|
331
|
+
*/
|
|
332
|
+
export const HANDEDNESS_FLIP = new Matrix4().makeScale(1, 1, -1);
|
|
309
333
|
/** The plan's own plane in plan space, which every pointer position is reported on. */
|
|
310
334
|
const PLAN_PLANE = new Plane(new Vector3(0, 0, 1), 0);
|
|
311
335
|
/**
|
package/dist/types/defs.d.ts
CHANGED
|
@@ -12,7 +12,11 @@ export interface SceneDef {
|
|
|
12
12
|
background?: ColorInput;
|
|
13
13
|
/** SVG viewbox */
|
|
14
14
|
viewbox: Box;
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Static transform matrix: plan → the host's world, in place of the viewbox fit and in the host's
|
|
17
|
+
* own coordinates. Assumed to share plan space's handedness — see `HANDEDNESS_FLIP` in Space.ts, which the
|
|
18
|
+
* renderer adds on top to reach three's.
|
|
19
|
+
*/
|
|
16
20
|
staticTransform?: number[];
|
|
17
21
|
}
|
|
18
22
|
/** Shared properties for all renderable defs */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expofp/renderer",
|
|
3
|
-
"version": "3.28.
|
|
3
|
+
"version": "3.28.3",
|
|
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.28.
|
|
45
|
+
"@expofp/geometry": "3.28.3"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@react-three/test-renderer": "^9.1.0",
|