@expofp/renderer 3.38.0 → 3.39.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.
- package/dist/controls/camera-api.d.ts +28 -0
- package/dist/controls/camera-api.js +33 -10
- package/dist/controls/hooks.js +2 -0
- package/dist/core/InstanceUniforms.d.ts +15 -0
- package/dist/core/InstanceUniforms.js +32 -0
- package/dist/core/batch/BatchedMesh.js +6 -0
- package/dist/core/instanced/instanced-material.js +5 -1
- package/dist/core/lines/BatchedLines.d.ts +9 -7
- package/dist/core/lines/BatchedLines.js +9 -7
- package/dist/core/lines/{FrustumCulling.d.ts → FrustumCullingLines.d.ts} +7 -6
- package/dist/core/lines/{FrustumCulling.js → FrustumCullingLines.js} +2 -2
- package/dist/core/lines/{Raycasting.d.ts → RaycastingLines.d.ts} +7 -6
- package/dist/core/lines/{Raycasting.js → RaycastingLines.js} +2 -2
- package/dist/core/lines/lines-material.js +10 -1
- package/dist/core/sprites/BatchedSprites.d.ts +115 -0
- package/dist/core/sprites/BatchedSprites.js +205 -0
- package/dist/core/sprites/RaycastingSprites.d.ts +41 -0
- package/dist/core/sprites/RaycastingSprites.js +71 -0
- package/dist/core/sprites/sprite-material.d.ts +59 -0
- package/dist/core/sprites/sprite-material.js +192 -0
- package/dist/core/text/BatchedText.d.ts +6 -0
- package/dist/core/text/BatchedText.js +6 -0
- package/dist/core/text/MemberTexture.js +55 -1
- package/dist/core/text/Text.d.ts +16 -1
- package/dist/core/text/Text.js +15 -0
- package/dist/core/text/batched-text-material.js +4 -2
- package/dist/events/LayoutDriver.d.ts +6 -6
- package/dist/events/LayoutDriver.js +9 -12
- package/dist/events/layout-targets.d.ts +35 -34
- package/dist/events/layout-targets.js +127 -107
- package/dist/objects/containers/hooks.d.ts +19 -4
- package/dist/objects/containers/hooks.js +101 -23
- package/dist/objects/containers/mesh-writes.js +3 -3
- package/dist/objects/images/ImageInstance.js +104 -36
- package/dist/objects/images/Images.d.ts +8 -7
- package/dist/objects/images/Images.js +94 -28
- package/dist/objects/images/image-container.d.ts +14 -3
- package/dist/objects/images/image-container.js +19 -5
- package/dist/objects/layer/Layer.d.ts +7 -6
- package/dist/objects/layer/Layer.js +77 -57
- package/dist/objects/layer/layer-context.d.ts +74 -0
- package/dist/objects/layer/layer-context.js +82 -0
- package/dist/objects/materials/ColorMaterial.js +16 -3
- package/dist/objects/materials/LineMaterial.js +4 -2
- package/dist/objects/materials/TextureMaterial.js +4 -2
- package/dist/objects/materials/effects/color-dim.d.ts +3 -11
- package/dist/objects/materials/effects/color-dim.js +17 -17
- package/dist/objects/materials/effects/effects-context.d.ts +6 -5
- package/dist/objects/materials/shared.d.ts +18 -0
- package/dist/objects/materials/shared.js +21 -0
- package/dist/objects/models/Models.js +1 -8
- package/dist/objects/shapes/ShapeInstance.js +1 -2
- package/dist/objects/shapes/Shapes.js +2 -9
- package/dist/objects/text/TextInstance.js +2 -2
- package/dist/objects/text/text-layout.d.ts +13 -4
- package/dist/objects/text/text-layout.js +30 -4
- package/dist/space/Space.d.ts +15 -0
- package/dist/space/Space.js +16 -10
- package/dist/types/defs.d.ts +37 -0
- package/dist/utils/logging.d.ts +6 -3
- package/dist/utils/logging.js +15 -7
- package/dist/utils/math.d.ts +11 -0
- package/dist/utils/math.js +14 -0
- package/package.json +2 -2
|
@@ -147,6 +147,14 @@ interface CameraContext {
|
|
|
147
147
|
height: number;
|
|
148
148
|
/** Device pixels per css pixel. */
|
|
149
149
|
dpr: number;
|
|
150
|
+
/**
|
|
151
|
+
* Asks React Three Fiber to draw another frame.
|
|
152
|
+
*
|
|
153
|
+
* A command changes the controls directly. That change happens outside a React commit. React
|
|
154
|
+
* Three Fiber cannot see it on its own. Calling this schedules the frame that shows the change,
|
|
155
|
+
* under a demand frame loop that would otherwise never draw one.
|
|
156
|
+
*/
|
|
157
|
+
invalidate: () => void;
|
|
150
158
|
}
|
|
151
159
|
/**
|
|
152
160
|
* The camera commands, over whatever the scene currently holds.
|
|
@@ -181,6 +189,26 @@ export declare class CameraCommands implements CameraAPI {
|
|
|
181
189
|
pitchBy(degrees: number, options?: TransitionOptions): Promise<void>;
|
|
182
190
|
/** @inheritdoc */
|
|
183
191
|
reset(axes?: CameraAxes, options?: TransitionOptions): Promise<void>;
|
|
192
|
+
/**
|
|
193
|
+
* Runs a move against the controls and asks for the frame that shows it.
|
|
194
|
+
*
|
|
195
|
+
* Every method above that takes {@link TransitionOptions} funnels its move through this one
|
|
196
|
+
* helper. A method that calls `controls` directly instead skips it, and looks obviously unlike
|
|
197
|
+
* its siblings.
|
|
198
|
+
*
|
|
199
|
+
* The invalidate happens before the move settles, not after, and every time, not only when the
|
|
200
|
+
* move is immediate. An eased move already triggers its own invalidate through `transitionstart`.
|
|
201
|
+
* An immediate move fires no event at all. `camera-controls` only applies an immediate move inside
|
|
202
|
+
* its own `update`, which runs from a frame that only an invalidate schedules. Invalidating after
|
|
203
|
+
* the move's promise settles would be too late to schedule that frame.
|
|
204
|
+
*
|
|
205
|
+
* A field and not a method, so it never lands on `CameraCommands.prototype`. The guard spec walks
|
|
206
|
+
* that prototype and calls everything it finds there, and an implementation detail would fail that
|
|
207
|
+
* walk for no reason.
|
|
208
|
+
* @param move - Issues the move against the controls and returns its promise.
|
|
209
|
+
* @returns The same promise `move` returned.
|
|
210
|
+
*/
|
|
211
|
+
private withInvalidate;
|
|
184
212
|
}
|
|
185
213
|
/**
|
|
186
214
|
* How far the plan is turned from north: clockwise, in radians, in `[0, 2π)`.
|
|
@@ -35,17 +35,17 @@ export class CameraCommands {
|
|
|
35
35
|
if (!fit)
|
|
36
36
|
return;
|
|
37
37
|
const transition = !options?.immediate;
|
|
38
|
-
await Promise.all([
|
|
38
|
+
await this.withInvalidate(() => Promise.all([
|
|
39
39
|
controls.moveTo(fit.center.x, fit.center.y, 0, transition),
|
|
40
40
|
controls.dollyTo(camera.zoomFactorToDistance(fit.zoom), transition),
|
|
41
|
-
]);
|
|
41
|
+
]));
|
|
42
42
|
}
|
|
43
43
|
/** @inheritdoc */
|
|
44
44
|
zoomBy(factor, options) {
|
|
45
45
|
const { controls, camera } = this.context;
|
|
46
46
|
if (!controls || !camera)
|
|
47
47
|
return RESOLVED;
|
|
48
|
-
return controls.dollyTo(camera.zoomFactorToDistance(camera.zoomFactor * factor), !options?.immediate);
|
|
48
|
+
return this.withInvalidate(() => controls.dollyTo(camera.zoomFactorToDistance(camera.zoomFactor * factor), !options?.immediate));
|
|
49
49
|
}
|
|
50
50
|
/** @inheritdoc */
|
|
51
51
|
panTo(point, options) {
|
|
@@ -53,7 +53,7 @@ export class CameraCommands {
|
|
|
53
53
|
if (!controls)
|
|
54
54
|
return RESOLVED;
|
|
55
55
|
const world = toWorld(space, point);
|
|
56
|
-
return controls.moveTo(world.x, world.y, world.z, !options?.immediate);
|
|
56
|
+
return this.withInvalidate(() => controls.moveTo(world.x, world.y, world.z, !options?.immediate));
|
|
57
57
|
}
|
|
58
58
|
/** @inheritdoc */
|
|
59
59
|
panBy(offset, options) {
|
|
@@ -65,7 +65,7 @@ export class CameraCommands {
|
|
|
65
65
|
const origin = toWorld(space, ORIGIN, _origin);
|
|
66
66
|
const shifted = toWorld(space, offset);
|
|
67
67
|
const current = controls.getTarget(_current);
|
|
68
|
-
return controls.moveTo(current.x + shifted.x - origin.x, current.y + shifted.y - origin.y, current.z + shifted.z - origin.z, !options?.immediate);
|
|
68
|
+
return this.withInvalidate(() => controls.moveTo(current.x + shifted.x - origin.x, current.y + shifted.y - origin.y, current.z + shifted.z - origin.z, !options?.immediate));
|
|
69
69
|
}
|
|
70
70
|
/** @inheritdoc */
|
|
71
71
|
rollTo(degrees, options) {
|
|
@@ -75,14 +75,14 @@ export class CameraCommands {
|
|
|
75
75
|
// Whichever way round is shorter, which is what keeps a turn from 350° to 10° going the long
|
|
76
76
|
// way about.
|
|
77
77
|
const delta = angleDiff(-degToRad(degrees), controls.azimuthAngle);
|
|
78
|
-
return controls.rotateAzimuthTo(controls.azimuthAngle + delta, !options?.immediate);
|
|
78
|
+
return this.withInvalidate(() => controls.rotateAzimuthTo(controls.azimuthAngle + delta, !options?.immediate));
|
|
79
79
|
}
|
|
80
80
|
/** @inheritdoc */
|
|
81
81
|
rollBy(degrees, options) {
|
|
82
82
|
const { controls } = this.context;
|
|
83
83
|
if (!controls)
|
|
84
84
|
return RESOLVED;
|
|
85
|
-
return controls.rotateAzimuthTo(controls.azimuthAngle - degToRad(degrees), !options?.immediate);
|
|
85
|
+
return this.withInvalidate(() => controls.rotateAzimuthTo(controls.azimuthAngle - degToRad(degrees), !options?.immediate));
|
|
86
86
|
}
|
|
87
87
|
/** @inheritdoc */
|
|
88
88
|
pitchTo(degrees, options) {
|
|
@@ -90,14 +90,14 @@ export class CameraCommands {
|
|
|
90
90
|
if (!controls)
|
|
91
91
|
return RESOLVED;
|
|
92
92
|
// Held inside the pitch limits by the controls themselves, which is where those limits live.
|
|
93
|
-
return controls.rotatePolarTo(degToRad(degrees), !options?.immediate);
|
|
93
|
+
return this.withInvalidate(() => controls.rotatePolarTo(degToRad(degrees), !options?.immediate));
|
|
94
94
|
}
|
|
95
95
|
/** @inheritdoc */
|
|
96
96
|
pitchBy(degrees, options) {
|
|
97
97
|
const { controls } = this.context;
|
|
98
98
|
if (!controls)
|
|
99
99
|
return RESOLVED;
|
|
100
|
-
return controls.rotatePolarTo(controls.polarAngle + degToRad(degrees), !options?.immediate);
|
|
100
|
+
return this.withInvalidate(() => controls.rotatePolarTo(controls.polarAngle + degToRad(degrees), !options?.immediate));
|
|
101
101
|
}
|
|
102
102
|
/** @inheritdoc */
|
|
103
103
|
async reset(axes = ALL_AXES, options) {
|
|
@@ -119,8 +119,31 @@ export class CameraCommands {
|
|
|
119
119
|
moves.push(controls.normalizeRotations().rotateAzimuthTo(0, transition));
|
|
120
120
|
if (axes.pitch)
|
|
121
121
|
moves.push(controls.rotatePolarTo(controls.minPolarAngle, transition));
|
|
122
|
-
await Promise.all(moves);
|
|
122
|
+
await this.withInvalidate(() => Promise.all(moves));
|
|
123
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Runs a move against the controls and asks for the frame that shows it.
|
|
126
|
+
*
|
|
127
|
+
* Every method above that takes {@link TransitionOptions} funnels its move through this one
|
|
128
|
+
* helper. A method that calls `controls` directly instead skips it, and looks obviously unlike
|
|
129
|
+
* its siblings.
|
|
130
|
+
*
|
|
131
|
+
* The invalidate happens before the move settles, not after, and every time, not only when the
|
|
132
|
+
* move is immediate. An eased move already triggers its own invalidate through `transitionstart`.
|
|
133
|
+
* An immediate move fires no event at all. `camera-controls` only applies an immediate move inside
|
|
134
|
+
* its own `update`, which runs from a frame that only an invalidate schedules. Invalidating after
|
|
135
|
+
* the move's promise settles would be too late to schedule that frame.
|
|
136
|
+
*
|
|
137
|
+
* A field and not a method, so it never lands on `CameraCommands.prototype`. The guard spec walks
|
|
138
|
+
* that prototype and calls everything it finds there, and an implementation detail would fail that
|
|
139
|
+
* walk for no reason.
|
|
140
|
+
* @param move - Issues the move against the controls and returns its promise.
|
|
141
|
+
* @returns The same promise `move` returned.
|
|
142
|
+
*/
|
|
143
|
+
withInvalidate = (move) => {
|
|
144
|
+
this.context.invalidate();
|
|
145
|
+
return move();
|
|
146
|
+
};
|
|
124
147
|
}
|
|
125
148
|
/**
|
|
126
149
|
* How far the plan is turned from north: clockwise, in radians, in `[0, 2π)`.
|
package/dist/controls/hooks.js
CHANGED
|
@@ -182,6 +182,7 @@ const _target = new Vector3();
|
|
|
182
182
|
*/
|
|
183
183
|
export function useCameraAPI() {
|
|
184
184
|
const get = useThree((state) => state.get);
|
|
185
|
+
const invalidate = useThree((state) => state.invalidate);
|
|
185
186
|
const controls = useCameraControls();
|
|
186
187
|
const camera = useCamera();
|
|
187
188
|
const space = useSpace();
|
|
@@ -194,5 +195,6 @@ export function useCameraAPI() {
|
|
|
194
195
|
width,
|
|
195
196
|
height,
|
|
196
197
|
dpr,
|
|
198
|
+
invalidate,
|
|
197
199
|
});
|
|
198
200
|
}
|
|
@@ -29,6 +29,21 @@ export declare class InstanceUniforms {
|
|
|
29
29
|
texture: SquareDataTexture | null;
|
|
30
30
|
/** Everything declared so far, merged across calls, so a rebuild recomputes the whole layout. */
|
|
31
31
|
private declared;
|
|
32
|
+
/** Backs {@link shaderKey}, rebuilt whenever {@link declared} changes. */
|
|
33
|
+
private key;
|
|
34
|
+
/**
|
|
35
|
+
* What the declared uniforms generate, as one string.
|
|
36
|
+
*
|
|
37
|
+
* A material that fetches per-instance uniforms compiles different GLSL for every schema, and
|
|
38
|
+
* three caches a compiled program under `customProgramCacheKey` alone, with no way to compare
|
|
39
|
+
* source. So every patcher that injects this store's prologue folds this into its own key.
|
|
40
|
+
* Without it, two meshes whose schemas differ share one program and the second silently draws
|
|
41
|
+
* against the first's layout.
|
|
42
|
+
*
|
|
43
|
+
* Empty while nothing is declared, which is itself a distinct key: that program has no prologue.
|
|
44
|
+
* @returns The key, stable for as long as the schema is.
|
|
45
|
+
*/
|
|
46
|
+
get shaderKey(): string;
|
|
32
47
|
/**
|
|
33
48
|
* Declares uniforms, merging with any already declared and rebuilding the texture.
|
|
34
49
|
*
|
|
@@ -21,6 +21,23 @@ export class InstanceUniforms {
|
|
|
21
21
|
texture = null;
|
|
22
22
|
/** Everything declared so far, merged across calls, so a rebuild recomputes the whole layout. */
|
|
23
23
|
declared = {};
|
|
24
|
+
/** Backs {@link shaderKey}, rebuilt whenever {@link declared} changes. */
|
|
25
|
+
key = '';
|
|
26
|
+
/**
|
|
27
|
+
* What the declared uniforms generate, as one string.
|
|
28
|
+
*
|
|
29
|
+
* A material that fetches per-instance uniforms compiles different GLSL for every schema, and
|
|
30
|
+
* three caches a compiled program under `customProgramCacheKey` alone, with no way to compare
|
|
31
|
+
* source. So every patcher that injects this store's prologue folds this into its own key.
|
|
32
|
+
* Without it, two meshes whose schemas differ share one program and the second silently draws
|
|
33
|
+
* against the first's layout.
|
|
34
|
+
*
|
|
35
|
+
* Empty while nothing is declared, which is itself a distinct key: that program has no prologue.
|
|
36
|
+
* @returns The key, stable for as long as the schema is.
|
|
37
|
+
*/
|
|
38
|
+
get shaderKey() {
|
|
39
|
+
return this.key;
|
|
40
|
+
}
|
|
24
41
|
/**
|
|
25
42
|
* Declares uniforms, merging with any already declared and rebuilding the texture.
|
|
26
43
|
*
|
|
@@ -42,6 +59,7 @@ export class InstanceUniforms {
|
|
|
42
59
|
}
|
|
43
60
|
this.texture = texture;
|
|
44
61
|
this.declared = { vertex, fragment };
|
|
62
|
+
this.key = schemaKey(this.declared);
|
|
45
63
|
}
|
|
46
64
|
/**
|
|
47
65
|
* Reads one instance's uniform.
|
|
@@ -106,6 +124,7 @@ export class InstanceUniforms {
|
|
|
106
124
|
vertex: { ...source.declared.vertex },
|
|
107
125
|
fragment: { ...source.declared.fragment },
|
|
108
126
|
};
|
|
127
|
+
this.key = source.key;
|
|
109
128
|
this.texture = source.texture ? source.texture.clone() : null;
|
|
110
129
|
}
|
|
111
130
|
/** Frees the texture. */
|
|
@@ -210,6 +229,19 @@ function uniformSize(type) {
|
|
|
210
229
|
throw new Error(`InstanceUniforms: invalid uniform type "${String(type)}".`);
|
|
211
230
|
}
|
|
212
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Builds the key that stands for the GLSL a schema generates. See {@link InstanceUniforms.shaderKey}.
|
|
234
|
+
*
|
|
235
|
+
* Names, types and the stage each is read in, because all three reach the generated source. Left in
|
|
236
|
+
* declaration order rather than sorted, since that is the order the layout packs them in, so two
|
|
237
|
+
* schemas that differ only in order still pack differently and have to compile apart.
|
|
238
|
+
* @param schema - The declared uniforms.
|
|
239
|
+
* @returns The key.
|
|
240
|
+
*/
|
|
241
|
+
function schemaKey(schema) {
|
|
242
|
+
const stage = (prefix, uniforms = {}) => Object.entries(uniforms).map(([name, type]) => `${prefix}${name}:${type}`);
|
|
243
|
+
return [...stage('v', schema.vertex), ...stage('f', schema.fragment)].join(',');
|
|
244
|
+
}
|
|
213
245
|
/**
|
|
214
246
|
* Flattens a schema into the name-to-type pairs a rebuild has to carry across.
|
|
215
247
|
* @param schema - The declared uniforms.
|
|
@@ -131,6 +131,12 @@ export class BatchedMeshImpl extends BatchedMeshBase {
|
|
|
131
131
|
const batchIndexGlsl = /* glsl */ `float ${batchIndex} = getIndirectIndex( gl_DrawID ) + 0.5;`;
|
|
132
132
|
this.patchedMaterial = material;
|
|
133
133
|
const onBeforeCompileBase = material.onBeforeCompile.bind(material);
|
|
134
|
+
// three caches a compiled program under this key alone and compares no source. The prologue
|
|
135
|
+
// below changes the GLSL, and the schema decides how, so both have to reach the key. Without
|
|
136
|
+
// it, two batches whose schemas differ share one program and the second draws against the
|
|
137
|
+
// first's layout. Bound rather than saved raw, because this patch is one-shot and never undone.
|
|
138
|
+
const cacheKeyBase = material.customProgramCacheKey.bind(material);
|
|
139
|
+
material.customProgramCacheKey = () => `efpBatched_${this.uniforms.shaderKey}_${cacheKeyBase()}`;
|
|
134
140
|
material.onBeforeCompile = (shader, renderer) => {
|
|
135
141
|
if (this.uniforms.patchShader(shader, batchIndex)) {
|
|
136
142
|
// Declares the index the prologue just injected reads. Written after it precisely so it runs
|
|
@@ -154,7 +154,11 @@ export function patchInstancedMaterial(material, textures) {
|
|
|
154
154
|
// before a texture was added, since each changes the generated GLSL. Every texture that can arrive
|
|
155
155
|
// late is named: the matrices are allocated by the first transform written, and a program built
|
|
156
156
|
// before that reads identity.
|
|
157
|
-
|
|
157
|
+
//
|
|
158
|
+
// The uniforms contribute their whole schema and not just their presence. Two meshes that declare
|
|
159
|
+
// different uniforms generate different GLSL, so a key that recorded only "has uniforms" would let
|
|
160
|
+
// the second draw against the first's layout.
|
|
161
|
+
const key = () => `${!!textures.matricesTexture}_${!!textures.colorsTexture}_${textures.uniforms.shaderKey}`;
|
|
158
162
|
material.customProgramCacheKey = () => `efp_${key()}_${baseCacheKey.call(material)}`;
|
|
159
163
|
material.onBeforeCompile = (shader, renderer) => {
|
|
160
164
|
// Instancing first, the material's own handler after — the same order the batched meshes use,
|
|
@@ -2,8 +2,8 @@ import { type Box3, type BufferGeometry, type Camera, type Group, type Material,
|
|
|
2
2
|
import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
|
|
3
3
|
import { LineSegmentsGeometry } from 'three/addons/lines/LineSegmentsGeometry.js';
|
|
4
4
|
import { InstancedMeshImpl, type InstancedMeshImplParams } from '../instanced/InstancedMesh';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { FrustumCullingLines } from './FrustumCullingLines';
|
|
6
|
+
import { RaycastingLines } from './RaycastingLines';
|
|
7
7
|
/**
|
|
8
8
|
* An instanced mesh whose instances are placed by two endpoints instead of a matrix.
|
|
9
9
|
*
|
|
@@ -21,7 +21,7 @@ import { Raycasting } from './Raycasting';
|
|
|
21
21
|
* names and needs no rewriting beyond dropping the declarations — see `objects/materials/LineMaterial`.
|
|
22
22
|
* - **The bounds**, swept from the endpoints, because the template geometry's own are in the
|
|
23
23
|
* parametric space the shader expands from and mean nothing in the plan.
|
|
24
|
-
* - **Picking**, which has to run in screen space at each member's own width — see {@link
|
|
24
|
+
* - **Picking**, which has to run in screen space at each member's own width — see {@link RaycastingLines}.
|
|
25
25
|
*
|
|
26
26
|
* And what it stops needing by being one: the endpoints are addressed by id, so paint order is a
|
|
27
27
|
* `customSort` over the draw list like every other batch's rather than a packing of the buffer;
|
|
@@ -32,6 +32,8 @@ import { Raycasting } from './Raycasting';
|
|
|
32
32
|
* Render it with {@link BatchedLines}.
|
|
33
33
|
*/
|
|
34
34
|
export declare class BatchedLinesImpl extends InstancedMeshImpl {
|
|
35
|
+
/** Marks this as a line batch. three's brand pattern, so a holder can tell the batch kinds apart. */
|
|
36
|
+
readonly isBatchedLines = true;
|
|
35
37
|
/**
|
|
36
38
|
* The segment template every member is drawn from, and the whole of what the geometry carries: the
|
|
37
39
|
* per-instance data three would put in instanced attributes is in the uniforms instead.
|
|
@@ -51,10 +53,10 @@ export declare class BatchedLinesImpl extends InstancedMeshImpl {
|
|
|
51
53
|
material: LineMaterial;
|
|
52
54
|
/** Draw order is paint order, which a container decides through `customSort` rather than depth. */
|
|
53
55
|
sortObjects: boolean;
|
|
54
|
-
/** Screen space, at each member's own width — see {@link
|
|
55
|
-
protected raycasting:
|
|
56
|
-
/** By the ribbon each member draws rather than the segment it runs — see {@link
|
|
57
|
-
protected culling:
|
|
56
|
+
/** Screen space, at each member's own width — see {@link RaycastingLines}. */
|
|
57
|
+
protected raycasting: RaycastingLines;
|
|
58
|
+
/** By the ribbon each member draws rather than the segment it runs — see {@link FrustumCullingLines}. */
|
|
59
|
+
protected culling: FrustumCullingLines;
|
|
58
60
|
/**
|
|
59
61
|
* @param params - Instance capacity; the buffers grow past it on demand.
|
|
60
62
|
*/
|
|
@@ -3,9 +3,9 @@ import { Vector3, Vector4, } from 'three';
|
|
|
3
3
|
import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
|
|
4
4
|
import { LineSegmentsGeometry } from 'three/addons/lines/LineSegmentsGeometry.js';
|
|
5
5
|
import { InstancedMeshImpl } from '../instanced/InstancedMesh';
|
|
6
|
-
import {
|
|
6
|
+
import { FrustumCullingLines } from './FrustumCullingLines';
|
|
7
7
|
import { END_UNIFORM, patchLineMaterial, START_UNIFORM, WIDTH_UNIFORM } from './lines-material';
|
|
8
|
-
import {
|
|
8
|
+
import { RaycastingLines } from './RaycastingLines';
|
|
9
9
|
/**
|
|
10
10
|
* An instanced mesh whose instances are placed by two endpoints instead of a matrix.
|
|
11
11
|
*
|
|
@@ -23,7 +23,7 @@ import { Raycasting } from './Raycasting';
|
|
|
23
23
|
* names and needs no rewriting beyond dropping the declarations — see `objects/materials/LineMaterial`.
|
|
24
24
|
* - **The bounds**, swept from the endpoints, because the template geometry's own are in the
|
|
25
25
|
* parametric space the shader expands from and mean nothing in the plan.
|
|
26
|
-
* - **Picking**, which has to run in screen space at each member's own width — see {@link
|
|
26
|
+
* - **Picking**, which has to run in screen space at each member's own width — see {@link RaycastingLines}.
|
|
27
27
|
*
|
|
28
28
|
* And what it stops needing by being one: the endpoints are addressed by id, so paint order is a
|
|
29
29
|
* `customSort` over the draw list like every other batch's rather than a packing of the buffer;
|
|
@@ -34,6 +34,8 @@ import { Raycasting } from './Raycasting';
|
|
|
34
34
|
* Render it with {@link BatchedLines}.
|
|
35
35
|
*/
|
|
36
36
|
export class BatchedLinesImpl extends InstancedMeshImpl {
|
|
37
|
+
/** Marks this as a line batch. three's brand pattern, so a holder can tell the batch kinds apart. */
|
|
38
|
+
isBatchedLines = true;
|
|
37
39
|
/**
|
|
38
40
|
* The segment template every member is drawn from, and the whole of what the geometry carries: the
|
|
39
41
|
* per-instance data three would put in instanced attributes is in the uniforms instead.
|
|
@@ -53,10 +55,10 @@ export class BatchedLinesImpl extends InstancedMeshImpl {
|
|
|
53
55
|
material = new LineMaterial();
|
|
54
56
|
/** Draw order is paint order, which a container decides through `customSort` rather than depth. */
|
|
55
57
|
sortObjects = true;
|
|
56
|
-
/** Screen space, at each member's own width — see {@link
|
|
57
|
-
raycasting = new
|
|
58
|
-
/** By the ribbon each member draws rather than the segment it runs — see {@link
|
|
59
|
-
culling = new
|
|
58
|
+
/** Screen space, at each member's own width — see {@link RaycastingLines}. */
|
|
59
|
+
raycasting = new RaycastingLines();
|
|
60
|
+
/** By the ribbon each member draws rather than the segment it runs — see {@link FrustumCullingLines}. */
|
|
61
|
+
culling = new FrustumCullingLines();
|
|
60
62
|
/**
|
|
61
63
|
* @param params - Instance capacity; the buffers grow past it on demand.
|
|
62
64
|
*/
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type Camera, type Sphere } from 'three';
|
|
2
2
|
import { type LineMaterial } from 'three/addons/lines/LineMaterial.js';
|
|
3
|
-
import { type CullingHost
|
|
4
|
-
/** What {@link
|
|
5
|
-
|
|
3
|
+
import { type CullingHost, FrustumCulling } from '../instanced/FrustumCulling';
|
|
4
|
+
/** What {@link FrustumCullingLines} reads off the batch it culls, beyond what every instanced mesh offers. */
|
|
5
|
+
interface CullingHostLines extends CullingHost {
|
|
6
6
|
/** The material, whose width a member with none of its own draws at, and whose units they are in. */
|
|
7
7
|
material: LineMaterial;
|
|
8
8
|
/**
|
|
@@ -25,8 +25,8 @@ export interface CullingHost extends InstancedCullingHost {
|
|
|
25
25
|
* per member rather than one figure for the batch — a member already knows its own width, and a batch
|
|
26
26
|
* of hairlines beside one wide route would otherwise be padded by the widest of them.
|
|
27
27
|
*/
|
|
28
|
-
export declare class
|
|
29
|
-
protected instanceSphere(host:
|
|
28
|
+
export declare class FrustumCullingLines extends FrustumCulling {
|
|
29
|
+
protected instanceSphere(host: CullingHostLines, id: number, camera: Camera, target: Sphere): Sphere;
|
|
30
30
|
/**
|
|
31
31
|
* How far past its endpoints one member draws, in the units its bounds are measured in.
|
|
32
32
|
*
|
|
@@ -40,4 +40,5 @@ export declare class FrustumCulling extends InstancedFrustumCulling {
|
|
|
40
40
|
*/
|
|
41
41
|
private drawnMargin;
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=FrustumCullingLines.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Vector3, Vector4 } from 'three';
|
|
2
|
-
import { FrustumCulling
|
|
2
|
+
import { FrustumCulling } from '../instanced/FrustumCulling';
|
|
3
3
|
/**
|
|
4
4
|
* Culling for a line batch: each member tested by where it is *drawn*, not where it runs.
|
|
5
5
|
*
|
|
@@ -13,7 +13,7 @@ import { FrustumCulling as InstancedFrustumCulling, } from '../instanced/Frustum
|
|
|
13
13
|
* per member rather than one figure for the batch — a member already knows its own width, and a batch
|
|
14
14
|
* of hairlines beside one wide route would otherwise be padded by the widest of them.
|
|
15
15
|
*/
|
|
16
|
-
export class
|
|
16
|
+
export class FrustumCullingLines extends FrustumCulling {
|
|
17
17
|
instanceSphere(host, id, camera, target) {
|
|
18
18
|
const sphere = super.instanceSphere(host, id, camera, target);
|
|
19
19
|
sphere.radius += this.drawnMargin(host, id, camera, sphere);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type Object3D, Vector3 } from 'three';
|
|
2
2
|
import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
|
|
3
|
-
import { type RaycastHost
|
|
4
|
-
/** What {@link
|
|
5
|
-
|
|
3
|
+
import { type RaycastHost, Raycasting } from '../instanced/Raycasting';
|
|
4
|
+
/** What {@link RaycastingLines} reads off the batch it tests, beyond what every instanced mesh offers. */
|
|
5
|
+
interface RaycastHostLines extends RaycastHost {
|
|
6
6
|
/** The material, whose width a member with none of its own draws at. */
|
|
7
7
|
material: LineMaterial;
|
|
8
8
|
/**
|
|
@@ -38,7 +38,8 @@ export interface RaycastHost extends InstancedRaycastHost {
|
|
|
38
38
|
* scratch carries a material of its own, written per test, and nothing touches the batch's — which
|
|
39
39
|
* is in use by the draw the pointer is moving over.
|
|
40
40
|
*/
|
|
41
|
-
export declare class
|
|
42
|
-
protected prepareInstance(host:
|
|
41
|
+
export declare class RaycastingLines extends Raycasting {
|
|
42
|
+
protected prepareInstance(host: RaycastHostLines, objectIndex: number): Object3D;
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=RaycastingLines.d.ts.map
|
|
@@ -2,7 +2,7 @@ import { Box3, Sphere, Vector3 } from 'three';
|
|
|
2
2
|
import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
|
|
3
3
|
import { LineSegments2 } from 'three/addons/lines/LineSegments2.js';
|
|
4
4
|
import { LineSegmentsGeometry } from 'three/addons/lines/LineSegmentsGeometry.js';
|
|
5
|
-
import { Raycasting
|
|
5
|
+
import { Raycasting } from '../instanced/Raycasting';
|
|
6
6
|
/**
|
|
7
7
|
* Picking for a line batch: each member tested in screen space, at the width it actually draws at.
|
|
8
8
|
*
|
|
@@ -22,7 +22,7 @@ import { Raycasting as InstancedRaycasting, } from '../instanced/Raycasting';
|
|
|
22
22
|
* scratch carries a material of its own, written per test, and nothing touches the batch's — which
|
|
23
23
|
* is in use by the draw the pointer is moving over.
|
|
24
24
|
*/
|
|
25
|
-
export class
|
|
25
|
+
export class RaycastingLines extends Raycasting {
|
|
26
26
|
prepareInstance(host, objectIndex) {
|
|
27
27
|
host.getPointsAt(objectIndex, _start, _end);
|
|
28
28
|
// Written straight into the interleaved buffer the geometry already owns. `setPositions` would
|
|
@@ -28,14 +28,23 @@ export function patchLineMaterial(material) {
|
|
|
28
28
|
seedColorChunk(material);
|
|
29
29
|
// Saved raw rather than bound, for the reason `patchInstancedMaterial` spells out: restoring a
|
|
30
30
|
// bound copy would put a *different* function back, and the next draw would bind that one in turn.
|
|
31
|
-
/* eslint-disable
|
|
31
|
+
/* eslint-disable @typescript-eslint/unbound-method -- restored verbatim; called with `.call`. */
|
|
32
32
|
const baseOnBeforeCompile = material.onBeforeCompile;
|
|
33
|
+
const baseCacheKey = material.customProgramCacheKey;
|
|
34
|
+
/* eslint-enable @typescript-eslint/unbound-method */
|
|
35
|
+
// three caches a compiled program under this key alone. A patched line material compiles source a
|
|
36
|
+
// plain one does not, so it needs a key of its own. A constant suffix is enough, because what this
|
|
37
|
+
// patch injects is fixed. `seedColorChunk` happens to change the source three derives its own
|
|
38
|
+
// shader id from, which has masked the absence of this until now. That is incidental, and a
|
|
39
|
+
// material that stopped needing the seed would lose the protection with it.
|
|
40
|
+
material.customProgramCacheKey = () => `efpLine_${baseCacheKey.call(material)}`;
|
|
33
41
|
material.onBeforeCompile = (shader, renderer) => {
|
|
34
42
|
patchLineShader(shader);
|
|
35
43
|
baseOnBeforeCompile.call(material, shader, renderer);
|
|
36
44
|
};
|
|
37
45
|
return () => {
|
|
38
46
|
material.onBeforeCompile = baseOnBeforeCompile;
|
|
47
|
+
material.customProgramCacheKey = baseCacheKey;
|
|
39
48
|
};
|
|
40
49
|
}
|
|
41
50
|
/**
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { type BufferGeometry, type Camera, type Group, type Material, PlaneGeometry, type Scene, type Sphere, Vector2, type WebGLRenderer } from 'three';
|
|
2
|
+
import { InstancedMeshImpl, type InstancedMeshImplParams } from '../instanced/InstancedMesh';
|
|
3
|
+
import { RaycastingSprites } from './RaycastingSprites';
|
|
4
|
+
/**
|
|
5
|
+
* Whether an instanced mesh is a sprite batch.
|
|
6
|
+
*
|
|
7
|
+
* three's own brand pattern, which `isLineMaterial` in `BatchedLines` already follows here. A holder
|
|
8
|
+
* that keeps one mesh field for several batch kinds narrows it with this, instead of keeping a
|
|
9
|
+
* second field that says which kind it stored.
|
|
10
|
+
* @param object - The instanced mesh to test.
|
|
11
|
+
* @returns Whether it is a sprite batch.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isBatchedSprites(object: InstancedMeshImpl): object is BatchedSpritesImpl;
|
|
14
|
+
/**
|
|
15
|
+
* An instanced mesh whose every member faces the camera.
|
|
16
|
+
*
|
|
17
|
+
* The name means what it says, which is why this is a class and not a flag. A batch of sprites is
|
|
18
|
+
* homogeneous. One atlas is then drawn by two meshes over two disjoint subsets of its images,
|
|
19
|
+
* instead of one mesh learning about orientation, and the packing never hears about the distinction.
|
|
20
|
+
*
|
|
21
|
+
* What facing the camera changes, and nothing else does:
|
|
22
|
+
*
|
|
23
|
+
* - The projection, in the shader. See `sprite-material.ts`. Everything else an instanced mesh
|
|
24
|
+
* does applies unchanged: slots, visibility, growth, per-instance uniforms, the draw list that is
|
|
25
|
+
* also the paint order, and the material wiring.
|
|
26
|
+
* - The anchor, as a per-instance uniform rather than a fold into the matrix. A sprite pivots
|
|
27
|
+
* about its anchor, so the matrix translation has to be the anchor.
|
|
28
|
+
* - The bounds, which enclose the quad about that anchor. See `getBoundingSphereAt`.
|
|
29
|
+
* - Picking, which has to run three's own sprite algorithm through a scratch object. See
|
|
30
|
+
* `RaycastingSprites`.
|
|
31
|
+
*
|
|
32
|
+
* Members are ids, not `Sprite` objects. `BatchedTextImpl` takes `TextImpl` members because troika's
|
|
33
|
+
* layout is object shaped and asynchronous, and the batch has to pack what those objects produce. A
|
|
34
|
+
* sprite carries none of that. It is a matrix, an anchor and a color, and every one of those
|
|
35
|
+
* already lives in a data texture this mesh owns. Reusing three's type as the member representation
|
|
36
|
+
* would reinstate the object-per-instance layer the port dropped, and would import `SpriteMaterial`
|
|
37
|
+
* semantics this package does not implement. Three's type is reused where it costs nothing, as
|
|
38
|
+
* picking scratch.
|
|
39
|
+
*
|
|
40
|
+
* This class knows nothing about textures. A uv region belongs to `TextureMaterial` and to the layer
|
|
41
|
+
* that packs an atlas. A batch drawn with a plain color material works exactly as well, and tints
|
|
42
|
+
* its members through `setColorAt` like any instanced mesh.
|
|
43
|
+
*
|
|
44
|
+
* Render it with `BatchedSprites`.
|
|
45
|
+
*/
|
|
46
|
+
export declare class BatchedSpritesImpl extends InstancedMeshImpl {
|
|
47
|
+
/** Marks this as a sprite batch. See `isBatchedSprites`. */
|
|
48
|
+
readonly isBatchedSprites = true;
|
|
49
|
+
/**
|
|
50
|
+
* The quad every member is drawn from, shared by every sprite batch in the process. That is
|
|
51
|
+
* three's own arrangement, which builds one module-level geometry and lets every `Sprite` share it.
|
|
52
|
+
*
|
|
53
|
+
* Seeded here rather than mounted as a child element, because it is not a choice the way an atlas's
|
|
54
|
+
* quad is. A sprite is a quad, any z is flattened by the projection, and every non-quad
|
|
55
|
+
* silhouette is a matter of texture alpha. `InstancedMeshImpl` already seeds a throwaway geometry
|
|
56
|
+
* expecting R3F to replace it, and not rendering a replacement is what makes this one stand.
|
|
57
|
+
*
|
|
58
|
+
* Two consequences. Nothing may dispose it, so this class does not override `dispose`. Nothing may
|
|
59
|
+
* write its bounding sphere, so `getBoundingSphereAt` computes rather than inflating.
|
|
60
|
+
*/
|
|
61
|
+
geometry: PlaneGeometry;
|
|
62
|
+
/** Three's own sprite algorithm, reached through a scratch object. See `RaycastingSprites`. */
|
|
63
|
+
protected raycasting: RaycastingSprites;
|
|
64
|
+
/**
|
|
65
|
+
* @param params - Instance capacity. The buffers grow past it on demand.
|
|
66
|
+
*/
|
|
67
|
+
constructor(params?: InstancedMeshImplParams);
|
|
68
|
+
/**
|
|
69
|
+
* A member's anchor. It is how far the pivot sits from the quad's middle, in unit-quad lengths, y
|
|
70
|
+
* down, and zero is the middle.
|
|
71
|
+
* @param id - The member to read.
|
|
72
|
+
* @param target - Receives the result.
|
|
73
|
+
* @returns The anchor.
|
|
74
|
+
*/
|
|
75
|
+
getAnchorAt(id: number, target?: Vector2): Vector2;
|
|
76
|
+
/**
|
|
77
|
+
* Sets a member's anchor, and invalidates the bounds it decides.
|
|
78
|
+
* @param id - The member to write.
|
|
79
|
+
* @param x - How far right of the middle the pivot sits, in unit-quad lengths.
|
|
80
|
+
* @param y - How far below the middle the pivot sits, in unit-quad lengths.
|
|
81
|
+
*/
|
|
82
|
+
setAnchorAt(id: number, x: number, y: number): void;
|
|
83
|
+
/**
|
|
84
|
+
* Encloses one member: the sphere about its anchor that reaches the quad's furthest corner.
|
|
85
|
+
*
|
|
86
|
+
* The anchor is not folded into the matrix, so the matrix translation already is the center and
|
|
87
|
+
* only the radius needs correcting. That is the exact enclosing sphere for a quad pivoting about
|
|
88
|
+
* its anchor, up to the max-axis approximation `Sphere.applyMatrix4` already makes on the flat
|
|
89
|
+
* path. It also holds at every camera angle, which is what a sprite's bounds have to do.
|
|
90
|
+
*
|
|
91
|
+
* Two alternatives were rejected. Inflating the shared quad's own bounding sphere replaces a
|
|
92
|
+
* minimal sphere with a non-minimal one, needs a per-instance factor on a per-geometry field, and
|
|
93
|
+
* would propagate through every sprite batch in the process. Reading the anchor inside
|
|
94
|
+
* `InstancedMeshImpl.getBoundingSphereAt` is quad math in a class whose instances are arbitrary
|
|
95
|
+
* geometry, where `ModelContainer` is a live counter-example.
|
|
96
|
+
* @param id - The member.
|
|
97
|
+
* @param target - Receives the result.
|
|
98
|
+
* @returns The bounds.
|
|
99
|
+
*/
|
|
100
|
+
getBoundingSphereAt(id: number, target: Sphere): Sphere;
|
|
101
|
+
onBeforeRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group): void;
|
|
102
|
+
protected patchMaterial(material: Material): () => void;
|
|
103
|
+
/**
|
|
104
|
+
* Clears a member as it is handed out, so a reused slot keeps nothing of the one that held it. The
|
|
105
|
+
* anchor included, which a caller that writes only a matrix would otherwise inherit.
|
|
106
|
+
* @param id - The member being handed out.
|
|
107
|
+
*/
|
|
108
|
+
protected resetInstance(id: number): void;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Registers the class as an R3F element, by value so the generated key cannot collide with the
|
|
112
|
+
* `Sprite` three puts in the catalog when the canvas mounts.
|
|
113
|
+
*/
|
|
114
|
+
export declare const BatchedSprites: import("react").ExoticComponent<import("@react-three/fiber/dist/declarations/src/core/utils").Mutable<import("@react-three/fiber/dist/declarations/src/core/utils").Overwrite<Partial<import("@react-three/fiber/dist/declarations/src/core/utils").Overwrite<BatchedSpritesImpl, import("@react-three/fiber").MathProps<BatchedSpritesImpl> & import("@react-three/fiber").ReactProps<BatchedSpritesImpl> & Partial<import("@react-three/fiber").EventHandlers>>>, Omit<import("@react-three/fiber").InstanceProps<BatchedSpritesImpl, typeof BatchedSpritesImpl>, "object">>>>;
|
|
115
|
+
//# sourceMappingURL=BatchedSprites.d.ts.map
|