@expofp/renderer 1.4.2 → 2.0.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.
- package/dist/index.d.ts +112 -32
- package/dist/index.js +1578 -1202
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
var _a;
|
|
5
|
-
import { DataTexture, FloatType, UnsignedIntType, IntType,
|
|
5
|
+
import { Color, Matrix4, Vector3, DataTexture, RGBAFormat, FloatType, RedFormat, UnsignedIntType, IntType, RGBAIntegerFormat, RGFormat, RGIntegerFormat, RedIntegerFormat, BatchedMesh as BatchedMesh$1, BufferAttribute, StreamDrawUsage, Vector4, AlwaysDepth, DoubleSide, MeshBasicMaterial, Texture, Group, PlaneGeometry, SRGBColorSpace, Vector2, Mesh, LessEqualDepth, Quaternion, BufferGeometry, LinearSRGBColorSpace, Plane, Raycaster, Sphere, Box3, Spherical, PerspectiveCamera, Camera, Scene, MathUtils, Clock, WebGLRenderer } from "three";
|
|
6
6
|
import { traverseAncestorsGenerator } from "three/examples/jsm/utils/SceneUtils.js";
|
|
7
7
|
import { BatchedText as BatchedText$1, Text as Text$1 } from "troika-three-text";
|
|
8
8
|
import { LineMaterial, LineSegmentsGeometry } from "three/examples/jsm/Addons.js";
|
|
@@ -10,351 +10,703 @@ import { MaxRectsPacker, Rectangle } from "maxrects-packer";
|
|
|
10
10
|
import { converter, parse } from "culori";
|
|
11
11
|
import { RAD2DEG, DEG2RAD as DEG2RAD$1 } from "three/src/math/MathUtils.js";
|
|
12
12
|
import { EventManager, Rotate, Pan } from "mjolnir.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
deepMerge(target[key], source[key]);
|
|
29
|
-
} else {
|
|
30
|
-
Object.assign(target, { [key]: source[key] });
|
|
31
|
-
}
|
|
32
|
-
}
|
|
13
|
+
const floatsPerMember = 32;
|
|
14
|
+
const tempColor = new Color();
|
|
15
|
+
const defaultStrokeColor = 8421504;
|
|
16
|
+
const tempMat4 = new Matrix4();
|
|
17
|
+
const tempVec3a = new Vector3();
|
|
18
|
+
const tempVec3b = new Vector3();
|
|
19
|
+
const origin = new Vector3();
|
|
20
|
+
const defaultOrient = "+x+y";
|
|
21
|
+
class BatchedText extends BatchedText$1 {
|
|
22
|
+
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
23
|
+
constructor() {
|
|
24
|
+
super();
|
|
25
|
+
__publicField(this, "mapInstanceIdToText", /* @__PURE__ */ new Map());
|
|
26
|
+
__publicField(this, "textArray", []);
|
|
27
|
+
__publicField(this, "textureNeedsUpdate", false);
|
|
33
28
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return Math.max(pixelsPerInstance, Math.ceil(Math.sqrt(capacity / pixelsPerInstance)) * pixelsPerInstance);
|
|
38
|
-
}
|
|
39
|
-
function getSquareTextureInfo(arrayType, channels, pixelsPerInstance, capacity) {
|
|
40
|
-
if (channels === 3) {
|
|
41
|
-
console.warn('"channels" cannot be 3. Set to 4. More info: https://github.com/mrdoob/three.js/pull/23228');
|
|
42
|
-
channels = 4;
|
|
29
|
+
/** Number of texts in the batch */
|
|
30
|
+
get size() {
|
|
31
|
+
return this._members.size;
|
|
43
32
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const isUnsignedInt = arrayType.name.includes("Uint");
|
|
48
|
-
const type = isFloat ? FloatType : isUnsignedInt ? UnsignedIntType : IntType;
|
|
49
|
-
let format;
|
|
50
|
-
switch (channels) {
|
|
51
|
-
case 1:
|
|
52
|
-
format = isFloat ? RedFormat : RedIntegerFormat;
|
|
53
|
-
break;
|
|
54
|
-
case 2:
|
|
55
|
-
format = isFloat ? RGFormat : RGIntegerFormat;
|
|
56
|
-
break;
|
|
57
|
-
case 4:
|
|
58
|
-
format = isFloat ? RGBAFormat : RGBAIntegerFormat;
|
|
59
|
-
break;
|
|
33
|
+
/** Base material before patching */
|
|
34
|
+
get baseMaterial() {
|
|
35
|
+
return this._baseMaterial;
|
|
60
36
|
}
|
|
61
|
-
return { array, size, type, format };
|
|
62
|
-
}
|
|
63
|
-
class SquareDataTexture extends DataTexture {
|
|
64
37
|
/**
|
|
65
|
-
* @
|
|
66
|
-
* @param
|
|
67
|
-
* @
|
|
68
|
-
* @param capacity The total number of instances.
|
|
69
|
-
* @param uniformMap Optional map for handling uniform values.
|
|
70
|
-
* @param fetchInFragmentShader Optional flag that determines if uniform values should be fetched in the fragment shader instead of the vertex shader.
|
|
38
|
+
* Get the {@link Text} object by instance id
|
|
39
|
+
* @param instanceId Instance id
|
|
40
|
+
* @returns Text object
|
|
71
41
|
*/
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const { array, format, size, type } = getSquareTextureInfo(arrayType, channels, pixelsPerInstance, capacity);
|
|
75
|
-
super(array, size, size, format, type);
|
|
76
|
-
__publicField(this, "data");
|
|
77
|
-
__publicField(this, "channels");
|
|
78
|
-
__publicField(this, "pixelsPerInstance");
|
|
79
|
-
__publicField(this, "stride");
|
|
80
|
-
__publicField(this, "uniformMap");
|
|
81
|
-
__publicField(this, "fetchUniformsInFragmentShader");
|
|
82
|
-
__publicField(this, "uniformPrefix", "batch_");
|
|
83
|
-
this.data = array;
|
|
84
|
-
this.channels = channels;
|
|
85
|
-
this.pixelsPerInstance = pixelsPerInstance;
|
|
86
|
-
this.stride = pixelsPerInstance * channels;
|
|
87
|
-
this.uniformMap = uniformMap;
|
|
88
|
-
this.fetchUniformsInFragmentShader = fetchInFragmentShader;
|
|
89
|
-
this.needsUpdate = true;
|
|
42
|
+
getText(instanceId) {
|
|
43
|
+
return this.mapInstanceIdToText.get(instanceId);
|
|
90
44
|
}
|
|
91
45
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* @param
|
|
95
|
-
* @param
|
|
46
|
+
* Set the visibility of the {@link Text} object by instance id.
|
|
47
|
+
* This is for interface compatibility with {@link BatchedMesh}.
|
|
48
|
+
* @param instanceId Instance id
|
|
49
|
+
* @param visible Visibility flag
|
|
96
50
|
*/
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
console.warn(`SquareDataTexture.setUniformAt: uniform ${name} not found`);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
const { offset, size } = schema;
|
|
104
|
-
const stride = this.stride;
|
|
105
|
-
if (size === 1) {
|
|
106
|
-
this.data[id * stride + offset] = value;
|
|
107
|
-
} else {
|
|
108
|
-
value.toArray(this.data, id * stride + offset);
|
|
109
|
-
}
|
|
51
|
+
setVisibleAt(instanceId, visible) {
|
|
52
|
+
const text = this.getText(instanceId);
|
|
53
|
+
text.visible = visible;
|
|
110
54
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
* @param target Optional target object to store the uniform value.
|
|
116
|
-
* @returns The uniform value for the specified instance.
|
|
117
|
-
*/
|
|
118
|
-
getUniformAt(id, name, target) {
|
|
119
|
-
const schema = this.uniformMap.get(name);
|
|
120
|
-
if (!schema) {
|
|
121
|
-
console.warn(`SquareDataTexture.getUniformAt: uniform ${name} not found`);
|
|
122
|
-
return 0;
|
|
123
|
-
}
|
|
124
|
-
const { offset, size } = schema;
|
|
125
|
-
const stride = this.stride;
|
|
126
|
-
if (size === 1) {
|
|
127
|
-
return this.data[id * stride + offset];
|
|
55
|
+
addText(text, instanceId) {
|
|
56
|
+
super.addText(text);
|
|
57
|
+
if (instanceId !== void 0) {
|
|
58
|
+
this.mapInstanceIdToText.set(instanceId, text);
|
|
128
59
|
}
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
/** Mark the texture as needing an update. */
|
|
132
|
-
update() {
|
|
133
|
-
this.needsUpdate = true;
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Generates the GLSL code for accessing the uniform data stored in the texture.
|
|
137
|
-
* @param textureName The name of the texture in the GLSL shader.
|
|
138
|
-
* @param indexName The name of the index in the GLSL shader.
|
|
139
|
-
* @param indexType The type of the index in the GLSL shader.
|
|
140
|
-
* @returns An object containing the GLSL code for the vertex and fragment shaders.
|
|
141
|
-
*/
|
|
142
|
-
getUniformsGLSL(textureName, indexName, indexType) {
|
|
143
|
-
const vertex = this.getUniformsVertexGLSL(textureName, indexName, indexType);
|
|
144
|
-
const fragment = this.getUniformsFragmentGLSL(textureName, indexName, indexType);
|
|
145
|
-
return { vertex, fragment };
|
|
60
|
+
this.textArray.push(text);
|
|
146
61
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
/*glsl*/
|
|
151
|
-
`
|
|
152
|
-
flat varying ${indexType} ${this.uniformPrefix}${indexName};
|
|
153
|
-
void main() {
|
|
154
|
-
${this.uniformPrefix}${indexName} = ${indexName};
|
|
155
|
-
`
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
const texelsFetch = this.texelsFetchGLSL(textureName, indexName);
|
|
159
|
-
const getFromTexels = this.getFromTexelsGLSL();
|
|
160
|
-
const { assignVarying, declareVarying } = this.getVarying();
|
|
161
|
-
return (
|
|
162
|
-
/*glsl*/
|
|
163
|
-
`
|
|
164
|
-
uniform highp sampler2D ${textureName};
|
|
165
|
-
${declareVarying}
|
|
166
|
-
void main() {
|
|
167
|
-
#ifdef USE_BATCHING
|
|
168
|
-
${indexType} ${indexName} = ${indexType}(getIndirectIndex(gl_DrawID));
|
|
169
|
-
#endif
|
|
170
|
-
${texelsFetch}
|
|
171
|
-
${getFromTexels}
|
|
172
|
-
${assignVarying}`
|
|
173
|
-
);
|
|
62
|
+
dispose() {
|
|
63
|
+
super.dispose();
|
|
64
|
+
this.dispatchEvent({ type: "dispose" });
|
|
174
65
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
66
|
+
// TODO: Check performance
|
|
67
|
+
_prepareForRender(material) {
|
|
68
|
+
var _a2;
|
|
69
|
+
const isOutline = material.isTextOutlineMaterial;
|
|
70
|
+
material.uniforms.uTroikaIsOutline.value = isOutline;
|
|
71
|
+
let texture = this._dataTextures[isOutline ? "outline" : "main"];
|
|
72
|
+
const dataLength = Math.pow(2, Math.ceil(Math.log2(this._members.size * floatsPerMember)));
|
|
73
|
+
if (!texture || dataLength !== texture.image.data.length) {
|
|
74
|
+
if (texture) texture.dispose();
|
|
75
|
+
const width = Math.min(dataLength / 4, 1024);
|
|
76
|
+
texture = this._dataTextures[isOutline ? "outline" : "main"] = new DataTexture(
|
|
77
|
+
new Float32Array(dataLength),
|
|
78
|
+
width,
|
|
79
|
+
dataLength / 4 / width,
|
|
80
|
+
RGBAFormat,
|
|
81
|
+
FloatType
|
|
184
82
|
);
|
|
185
83
|
}
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
84
|
+
const texData = texture.image.data;
|
|
85
|
+
this.textureNeedsUpdate = false;
|
|
86
|
+
for (const text of this.textArray) {
|
|
87
|
+
const index = ((_a2 = this._members.get(text)) == null ? void 0 : _a2.index) ?? -1;
|
|
88
|
+
const textRenderInfo = text.textRenderInfo;
|
|
89
|
+
if (index < 0 || !textRenderInfo) continue;
|
|
90
|
+
const startIndex = index * floatsPerMember;
|
|
91
|
+
if (!text.visible) {
|
|
92
|
+
for (let i = 0; i < 16; i++) {
|
|
93
|
+
this.setTexData(startIndex + i, 0, texData);
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const matrix = text.matrix.elements;
|
|
98
|
+
for (let i = 0; i < 16; i++) {
|
|
99
|
+
this.setTexData(startIndex + i, matrix[i], texData);
|
|
100
|
+
}
|
|
101
|
+
text._prepareForRender(material);
|
|
102
|
+
const {
|
|
103
|
+
uTroikaTotalBounds,
|
|
104
|
+
uTroikaClipRect,
|
|
105
|
+
uTroikaPositionOffset,
|
|
106
|
+
uTroikaEdgeOffset,
|
|
107
|
+
uTroikaBlurRadius,
|
|
108
|
+
uTroikaStrokeWidth,
|
|
109
|
+
uTroikaStrokeColor,
|
|
110
|
+
uTroikaStrokeOpacity,
|
|
111
|
+
uTroikaFillOpacity,
|
|
112
|
+
uTroikaCurveRadius
|
|
113
|
+
} = material.uniforms;
|
|
114
|
+
for (let i = 0; i < 4; i++) {
|
|
115
|
+
this.setTexData(startIndex + 16 + i, uTroikaTotalBounds.value.getComponent(i), texData);
|
|
116
|
+
}
|
|
117
|
+
for (let i = 0; i < 4; i++) {
|
|
118
|
+
this.setTexData(startIndex + 20 + i, uTroikaClipRect.value.getComponent(i), texData);
|
|
119
|
+
}
|
|
120
|
+
let color = isOutline ? text.outlineColor || 0 : text.color;
|
|
121
|
+
color ?? (color = this.color);
|
|
122
|
+
color ?? (color = this.material.color);
|
|
123
|
+
color ?? (color = 16777215);
|
|
124
|
+
this.setTexData(startIndex + 24, tempColor.set(color).getHex(), texData);
|
|
125
|
+
this.setTexData(startIndex + 25, uTroikaFillOpacity.value, texData);
|
|
126
|
+
this.setTexData(startIndex + 26, uTroikaCurveRadius.value, texData);
|
|
127
|
+
if (isOutline) {
|
|
128
|
+
this.setTexData(startIndex + 28, uTroikaPositionOffset.value.x, texData);
|
|
129
|
+
this.setTexData(startIndex + 29, uTroikaPositionOffset.value.y, texData);
|
|
130
|
+
this.setTexData(startIndex + 30, uTroikaEdgeOffset.value, texData);
|
|
131
|
+
this.setTexData(startIndex + 31, uTroikaBlurRadius.value, texData);
|
|
229
132
|
} else {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
`;
|
|
133
|
+
this.setTexData(startIndex + 28, uTroikaStrokeWidth.value, texData);
|
|
134
|
+
this.setTexData(startIndex + 29, tempColor.set(uTroikaStrokeColor.value).getHex(), texData);
|
|
135
|
+
this.setTexData(startIndex + 30, uTroikaStrokeOpacity.value, texData);
|
|
234
136
|
}
|
|
235
137
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
getVarying() {
|
|
239
|
-
const uniforms = this.uniformMap;
|
|
240
|
-
let declareVarying = "";
|
|
241
|
-
let assignVarying = "";
|
|
242
|
-
let getVarying = "";
|
|
243
|
-
for (const [name, { type }] of uniforms) {
|
|
244
|
-
declareVarying += /*glsl*/
|
|
245
|
-
`flat varying ${type} ${this.uniformPrefix}${name};
|
|
246
|
-
`;
|
|
247
|
-
assignVarying += /*glsl*/
|
|
248
|
-
`${this.uniformPrefix}${name} = ${name};
|
|
249
|
-
`;
|
|
250
|
-
getVarying += /*glsl*/
|
|
251
|
-
`${type} ${name} = ${this.uniformPrefix}${name};
|
|
252
|
-
`;
|
|
253
|
-
}
|
|
254
|
-
return { declareVarying, assignVarying, getVarying };
|
|
138
|
+
texture.needsUpdate = this.textureNeedsUpdate;
|
|
139
|
+
material.setMatrixTexture(texture);
|
|
255
140
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
components += componentsArray[startIndex + i];
|
|
141
|
+
setTexData(index, value, texData) {
|
|
142
|
+
if (value !== texData[index]) {
|
|
143
|
+
texData[index] = value;
|
|
144
|
+
this.textureNeedsUpdate = true;
|
|
261
145
|
}
|
|
262
|
-
return components;
|
|
263
146
|
}
|
|
264
147
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
this.
|
|
148
|
+
class Text extends Text$1 {
|
|
149
|
+
_prepareForRender(material) {
|
|
150
|
+
const isOutline = material.isTextOutlineMaterial;
|
|
151
|
+
const uniforms = material.uniforms;
|
|
152
|
+
const textInfo = this.textRenderInfo;
|
|
153
|
+
if (textInfo) {
|
|
154
|
+
const { sdfTexture, blockBounds } = textInfo;
|
|
155
|
+
const { width, height } = sdfTexture.image;
|
|
156
|
+
uniforms.uTroikaSDFTexture.value = sdfTexture;
|
|
157
|
+
uniforms.uTroikaSDFTextureSize.value.set(width, height);
|
|
158
|
+
uniforms.uTroikaSDFGlyphSize.value = textInfo.sdfGlyphSize;
|
|
159
|
+
uniforms.uTroikaSDFExponent.value = textInfo.sdfExponent;
|
|
160
|
+
uniforms.uTroikaTotalBounds.value.fromArray(blockBounds);
|
|
161
|
+
uniforms.uTroikaUseGlyphColors.value = !isOutline && !!textInfo.glyphColors;
|
|
162
|
+
let distanceOffset = 0;
|
|
163
|
+
let blurRadius = 0;
|
|
164
|
+
let strokeWidth = 0;
|
|
165
|
+
let fillOpacity;
|
|
166
|
+
let strokeOpacity = 1;
|
|
167
|
+
let strokeColor;
|
|
168
|
+
let offsetX = 0;
|
|
169
|
+
let offsetY = 0;
|
|
170
|
+
if (isOutline) {
|
|
171
|
+
const { outlineWidth, outlineOffsetX, outlineOffsetY, outlineBlur, outlineOpacity } = this;
|
|
172
|
+
distanceOffset = this._parsePercent(outlineWidth) || 0;
|
|
173
|
+
blurRadius = Math.max(0, this._parsePercent(outlineBlur) || 0);
|
|
174
|
+
fillOpacity = outlineOpacity;
|
|
175
|
+
offsetX = this._parsePercent(outlineOffsetX) || 0;
|
|
176
|
+
offsetY = this._parsePercent(outlineOffsetY) || 0;
|
|
177
|
+
} else {
|
|
178
|
+
strokeWidth = Math.max(0, this._parsePercent(this.strokeWidth) || 0);
|
|
179
|
+
if (strokeWidth) {
|
|
180
|
+
strokeColor = this.strokeColor;
|
|
181
|
+
uniforms.uTroikaStrokeColor.value.set(strokeColor ?? defaultStrokeColor);
|
|
182
|
+
strokeOpacity = this.strokeOpacity;
|
|
183
|
+
strokeOpacity ?? (strokeOpacity = 1);
|
|
184
|
+
}
|
|
185
|
+
fillOpacity = this.fillOpacity;
|
|
294
186
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (this.useMultiDraw) return super.addGeometry(geometry, reservedVertexRange, reservedIndexRange);
|
|
316
|
-
this.addBatchIdBuffer(geometry, this.instanceCount);
|
|
317
|
-
const geometryId = super.addGeometry(geometry, reservedVertexRange, reservedIndexRange);
|
|
318
|
-
this.geometryById.set(geometryId, geometry);
|
|
319
|
-
return geometryId;
|
|
320
|
-
}
|
|
321
|
-
addInstance(geometryId) {
|
|
322
|
-
if (this.useMultiDraw) return super.addInstance(geometryId);
|
|
323
|
-
if (this.mapGeometryToInstanceId.has(geometryId)) {
|
|
324
|
-
const geometry = this.geometryById.get(geometryId);
|
|
325
|
-
this.resizeToFitGeometry(geometry);
|
|
326
|
-
geometryId = this.addGeometry(geometry);
|
|
187
|
+
uniforms.uTroikaEdgeOffset.value = distanceOffset;
|
|
188
|
+
uniforms.uTroikaPositionOffset.value.set(offsetX, offsetY);
|
|
189
|
+
uniforms.uTroikaBlurRadius.value = blurRadius;
|
|
190
|
+
uniforms.uTroikaStrokeWidth.value = strokeWidth;
|
|
191
|
+
uniforms.uTroikaStrokeOpacity.value = strokeOpacity;
|
|
192
|
+
uniforms.uTroikaFillOpacity.value = fillOpacity ?? 1;
|
|
193
|
+
uniforms.uTroikaCurveRadius.value = this.curveRadius || 0;
|
|
194
|
+
const clipRect = this.clipRect;
|
|
195
|
+
if (clipRect && Array.isArray(clipRect) && clipRect.length === 4) {
|
|
196
|
+
uniforms.uTroikaClipRect.value.fromArray(clipRect);
|
|
197
|
+
} else {
|
|
198
|
+
const pad = (this.fontSize || 0.1) * 100;
|
|
199
|
+
uniforms.uTroikaClipRect.value.set(
|
|
200
|
+
blockBounds[0] - pad,
|
|
201
|
+
blockBounds[1] - pad,
|
|
202
|
+
blockBounds[2] + pad,
|
|
203
|
+
blockBounds[3] + pad
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
this.geometry.applyClipRect(uniforms.uTroikaClipRect.value);
|
|
327
207
|
}
|
|
328
|
-
|
|
329
|
-
this.
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
208
|
+
uniforms.uTroikaSDFDebug.value = !!this.debugSDF;
|
|
209
|
+
material.polygonOffset = !!this.depthOffset;
|
|
210
|
+
material.polygonOffsetFactor = material.polygonOffsetUnits = this.depthOffset || 0;
|
|
211
|
+
const color = isOutline ? this.outlineColor || 0 : this.color;
|
|
212
|
+
if (color == null) {
|
|
213
|
+
delete material.color;
|
|
214
|
+
} else {
|
|
215
|
+
const colorObj = material.hasOwnProperty("color") ? material.color : material.color = new Color();
|
|
216
|
+
if (color !== colorObj._input || typeof color === "object") {
|
|
217
|
+
colorObj.set(colorObj._input = color);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
let orient = this.orientation || defaultOrient;
|
|
221
|
+
if (orient !== material._orientation) {
|
|
222
|
+
const rotMat = uniforms.uTroikaOrient.value;
|
|
223
|
+
orient = orient.replace(/[^-+xyz]/g, "");
|
|
224
|
+
const match = orient !== defaultOrient && /^([-+])([xyz])([-+])([xyz])$/.exec(orient);
|
|
225
|
+
if (match) {
|
|
226
|
+
const [, hSign, hAxis, vSign, vAxis] = match;
|
|
227
|
+
tempVec3a.set(0, 0, 0)[hAxis] = hSign === "-" ? 1 : -1;
|
|
228
|
+
tempVec3b.set(0, 0, 0)[vAxis] = vSign === "-" ? -1 : 1;
|
|
229
|
+
tempMat4.lookAt(origin, tempVec3a.cross(tempVec3b), tempVec3b);
|
|
230
|
+
rotMat.setFromMatrix4(tempMat4);
|
|
231
|
+
} else {
|
|
232
|
+
rotMat.identity();
|
|
233
|
+
}
|
|
234
|
+
material._orientation = orient;
|
|
340
235
|
}
|
|
341
|
-
super.onBeforeRender(renderer, scene, camera, geometry, material, group);
|
|
342
|
-
this.batchCount = this.updateIndexBuffer(geometry);
|
|
343
|
-
this._multiDrawCount = 0;
|
|
344
236
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
geometry.setIndex(null);
|
|
237
|
+
}
|
|
238
|
+
function setDimming(root, dim) {
|
|
239
|
+
root.userData["uDim"] = dim === void 0 ? void 0 : +dim;
|
|
240
|
+
}
|
|
241
|
+
function toggleInstanceDim(object, instanceId, dim) {
|
|
242
|
+
const value = dim === void 0 ? 0 : (+dim - 0.5) * 2;
|
|
243
|
+
if (object instanceof BatchedMesh) {
|
|
244
|
+
object.setUniformAt(instanceId, "skipDimInstance", value);
|
|
245
|
+
return;
|
|
355
246
|
}
|
|
356
|
-
|
|
357
|
-
|
|
247
|
+
const skipDimTexture = object.userData["skipDimTexture"];
|
|
248
|
+
if (skipDimTexture) {
|
|
249
|
+
const skipDimData = skipDimTexture.image.data;
|
|
250
|
+
skipDimData[instanceId] = value;
|
|
251
|
+
skipDimTexture.needsUpdate = true;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function addDimToMaterial(material) {
|
|
255
|
+
if (material.userData.hasDimShader) return;
|
|
256
|
+
const onBeforeCompile = material.onBeforeCompile.bind(material);
|
|
257
|
+
const onBeforeRender = material.onBeforeRender.bind(material);
|
|
258
|
+
material.onBeforeCompile = (shader, renderer) => {
|
|
259
|
+
onBeforeCompile(shader, renderer);
|
|
260
|
+
shader.uniforms["uDim"] = { value: material.userData.uDim ?? 0 };
|
|
261
|
+
shader.uniforms["skipDimTexture"] = { value: material.userData.skipDimTexture ?? null };
|
|
262
|
+
shader.vertexShader = shader.vertexShader.replace("void main() {", `${dimColorVertexDefs}
|
|
263
|
+
void main() {`).replace(
|
|
264
|
+
"#include <fog_vertex>",
|
|
265
|
+
/*glsl*/
|
|
266
|
+
`
|
|
267
|
+
#include <fog_vertex>
|
|
268
|
+
setDimAmount();
|
|
269
|
+
`
|
|
270
|
+
).concat(dimColorVertexImpl);
|
|
271
|
+
shader.fragmentShader = /*glsl*/
|
|
272
|
+
`
|
|
273
|
+
${dimColorFrag}
|
|
274
|
+
${shader.fragmentShader}
|
|
275
|
+
`.replace(
|
|
276
|
+
"#include <colorspace_fragment>",
|
|
277
|
+
/*glsl*/
|
|
278
|
+
`
|
|
279
|
+
gl_FragColor = dimColor(gl_FragColor);
|
|
280
|
+
#include <colorspace_fragment>
|
|
281
|
+
`
|
|
282
|
+
);
|
|
283
|
+
material.userData.shader = shader;
|
|
284
|
+
};
|
|
285
|
+
material.onBeforeRender = (renderer, scene, camera, geometry, object, group) => {
|
|
286
|
+
onBeforeRender(renderer, scene, camera, geometry, object, group);
|
|
287
|
+
const skipDimTexture = object.userData["skipDimTexture"];
|
|
288
|
+
let uDim = object.userData["uDim"];
|
|
289
|
+
if (uDim === void 0) {
|
|
290
|
+
for (const ancestor of traverseAncestorsGenerator(object)) {
|
|
291
|
+
if (ancestor.userData["uDim"] !== void 0) {
|
|
292
|
+
uDim = ancestor.userData["uDim"];
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const shader = material.userData.shader;
|
|
298
|
+
if (!shader) {
|
|
299
|
+
material.userData.uDim = uDim;
|
|
300
|
+
material.userData.skipDimTexture = object.userData["skipDimTexture"];
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
shader.uniforms["uDim"].value = uDim ?? 0;
|
|
304
|
+
shader.uniforms["skipDimTexture"].value = skipDimTexture ?? null;
|
|
305
|
+
};
|
|
306
|
+
material.userData.hasDimShader = true;
|
|
307
|
+
}
|
|
308
|
+
function addDim(mesh) {
|
|
309
|
+
if (mesh instanceof BatchedMesh) mesh.addPerInstanceUniforms({ vertex: { skipDimInstance: "float" } });
|
|
310
|
+
if (mesh instanceof BatchedText) addSkipDimTexture(mesh);
|
|
311
|
+
}
|
|
312
|
+
function addSkipDimTexture(text) {
|
|
313
|
+
const count = text.size;
|
|
314
|
+
const size = Math.ceil(Math.sqrt(count));
|
|
315
|
+
const array = new Float32Array(size * size);
|
|
316
|
+
array.fill(0);
|
|
317
|
+
const texture = new DataTexture(array, size, size, RedFormat, FloatType);
|
|
318
|
+
texture.needsUpdate = true;
|
|
319
|
+
text.userData["skipDimTexture"] = texture;
|
|
320
|
+
text.addEventListener("dispose", () => texture.dispose());
|
|
321
|
+
return texture;
|
|
322
|
+
}
|
|
323
|
+
const dimColorVertexDefs = (
|
|
324
|
+
/*glsl*/
|
|
325
|
+
`
|
|
326
|
+
uniform float uDim;
|
|
327
|
+
out float dimAmount;
|
|
328
|
+
#ifdef TROIKA_DERIVED_MATERIAL_1
|
|
329
|
+
uniform sampler2D skipDimTexture;
|
|
330
|
+
#endif
|
|
331
|
+
void setDimAmount();
|
|
332
|
+
`
|
|
333
|
+
);
|
|
334
|
+
const dimColorVertexImpl = (
|
|
335
|
+
/*glsl*/
|
|
336
|
+
`
|
|
337
|
+
void setDimAmount() {
|
|
338
|
+
float instanceDim = 0.;
|
|
339
|
+
#ifdef USE_BATCH_UNIFORMS
|
|
340
|
+
instanceDim = batch_skipDimInstance;
|
|
341
|
+
#endif
|
|
342
|
+
#ifdef TROIKA_DERIVED_MATERIAL_1
|
|
343
|
+
float indirectIndex = aTroikaTextBatchMemberIndex;
|
|
344
|
+
int size = textureSize(skipDimTexture, 0).x;
|
|
345
|
+
int i = int(indirectIndex);
|
|
346
|
+
int x = i % size;
|
|
347
|
+
int y = i / size;
|
|
348
|
+
instanceDim = texelFetch(skipDimTexture, ivec2(x, y), 0).r;
|
|
349
|
+
#endif
|
|
350
|
+
dimAmount = instanceDim == 0. ? uDim : instanceDim / 2. + 0.5;
|
|
351
|
+
}
|
|
352
|
+
`
|
|
353
|
+
);
|
|
354
|
+
const dimColorFrag = (
|
|
355
|
+
/*glsl*/
|
|
356
|
+
`
|
|
357
|
+
in float dimAmount;
|
|
358
|
+
|
|
359
|
+
const vec3 grayWeights = vec3(0.299, 0.587, 0.114);
|
|
360
|
+
const float darkenFactor = pow(2., 2.2); // Gamma corrected
|
|
361
|
+
|
|
362
|
+
vec4 dimColor(vec4 col) {
|
|
363
|
+
vec3 color = col.rgb / col.a;
|
|
364
|
+
vec3 gray = vec3(dot(grayWeights, color));
|
|
365
|
+
vec3 m = mix(color, gray / darkenFactor, dimAmount);
|
|
366
|
+
return vec4(m * col.a, col.a);
|
|
367
|
+
}`
|
|
368
|
+
);
|
|
369
|
+
function isObject(item) {
|
|
370
|
+
return !!item && typeof item === "object" && !Array.isArray(item);
|
|
371
|
+
}
|
|
372
|
+
function deepMerge(target, ...sources) {
|
|
373
|
+
if (!sources.length) return target;
|
|
374
|
+
const source = sources.shift();
|
|
375
|
+
if (source === void 0) {
|
|
376
|
+
return target;
|
|
377
|
+
}
|
|
378
|
+
if (isObject(target) && isObject(source)) {
|
|
379
|
+
for (const key in source) {
|
|
380
|
+
if (isObject(source[key])) {
|
|
381
|
+
if (!target[key]) {
|
|
382
|
+
Object.assign(target, { [key]: {} });
|
|
383
|
+
}
|
|
384
|
+
deepMerge(target[key], source[key]);
|
|
385
|
+
} else {
|
|
386
|
+
Object.assign(target, { [key]: source[key] });
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return deepMerge(target, ...sources);
|
|
391
|
+
}
|
|
392
|
+
function getSquareTextureSize(capacity, pixelsPerInstance) {
|
|
393
|
+
return Math.max(pixelsPerInstance, Math.ceil(Math.sqrt(capacity / pixelsPerInstance)) * pixelsPerInstance);
|
|
394
|
+
}
|
|
395
|
+
function getSquareTextureInfo(arrayType, channels, pixelsPerInstance, capacity) {
|
|
396
|
+
if (channels === 3) {
|
|
397
|
+
console.warn('"channels" cannot be 3. Set to 4. More info: https://github.com/mrdoob/three.js/pull/23228');
|
|
398
|
+
channels = 4;
|
|
399
|
+
}
|
|
400
|
+
const size = getSquareTextureSize(capacity, pixelsPerInstance);
|
|
401
|
+
const array = new arrayType(size * size * channels);
|
|
402
|
+
const isFloat = arrayType.name.includes("Float");
|
|
403
|
+
const isUnsignedInt = arrayType.name.includes("Uint");
|
|
404
|
+
const type = isFloat ? FloatType : isUnsignedInt ? UnsignedIntType : IntType;
|
|
405
|
+
let format;
|
|
406
|
+
switch (channels) {
|
|
407
|
+
case 1:
|
|
408
|
+
format = isFloat ? RedFormat : RedIntegerFormat;
|
|
409
|
+
break;
|
|
410
|
+
case 2:
|
|
411
|
+
format = isFloat ? RGFormat : RGIntegerFormat;
|
|
412
|
+
break;
|
|
413
|
+
case 4:
|
|
414
|
+
format = isFloat ? RGBAFormat : RGBAIntegerFormat;
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
return { array, size, type, format };
|
|
418
|
+
}
|
|
419
|
+
class SquareDataTexture extends DataTexture {
|
|
420
|
+
/**
|
|
421
|
+
* @param arrayType The constructor for the TypedArray.
|
|
422
|
+
* @param channels The number of channels in the texture.
|
|
423
|
+
* @param pixelsPerInstance The number of pixels required for each instance.
|
|
424
|
+
* @param capacity The total number of instances.
|
|
425
|
+
* @param uniformMap Optional map for handling uniform values.
|
|
426
|
+
* @param fetchInFragmentShader Optional flag that determines if uniform values should be fetched in the fragment shader instead of the vertex shader.
|
|
427
|
+
*/
|
|
428
|
+
constructor(arrayType, channels, pixelsPerInstance, capacity, uniformMap, fetchInFragmentShader) {
|
|
429
|
+
if (channels === 3) channels = 4;
|
|
430
|
+
const { array, format, size, type } = getSquareTextureInfo(arrayType, channels, pixelsPerInstance, capacity);
|
|
431
|
+
super(array, size, size, format, type);
|
|
432
|
+
__publicField(this, "data");
|
|
433
|
+
__publicField(this, "channels");
|
|
434
|
+
__publicField(this, "pixelsPerInstance");
|
|
435
|
+
__publicField(this, "stride");
|
|
436
|
+
__publicField(this, "uniformMap");
|
|
437
|
+
__publicField(this, "fetchUniformsInFragmentShader");
|
|
438
|
+
__publicField(this, "uniformPrefix", "batch_");
|
|
439
|
+
this.data = array;
|
|
440
|
+
this.channels = channels;
|
|
441
|
+
this.pixelsPerInstance = pixelsPerInstance;
|
|
442
|
+
this.stride = pixelsPerInstance * channels;
|
|
443
|
+
this.uniformMap = uniformMap;
|
|
444
|
+
this.fetchUniformsInFragmentShader = fetchInFragmentShader;
|
|
445
|
+
this.needsUpdate = true;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Sets a uniform value at the specified instance ID in the texture.
|
|
449
|
+
* @param id The instance ID to set the uniform for.
|
|
450
|
+
* @param name The name of the uniform.
|
|
451
|
+
* @param value The value to set for the uniform.
|
|
452
|
+
*/
|
|
453
|
+
setUniformAt(id, name, value) {
|
|
454
|
+
const schema = this.uniformMap.get(name);
|
|
455
|
+
if (!schema) {
|
|
456
|
+
console.warn(`SquareDataTexture.setUniformAt: uniform ${name} not found`);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
const { offset, size } = schema;
|
|
460
|
+
const stride = this.stride;
|
|
461
|
+
if (size === 1) {
|
|
462
|
+
this.data[id * stride + offset] = value;
|
|
463
|
+
} else {
|
|
464
|
+
value.toArray(this.data, id * stride + offset);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Retrieves a uniform value at the specified instance ID from the texture.
|
|
469
|
+
* @param id The instance ID to retrieve the uniform from.
|
|
470
|
+
* @param name The name of the uniform.
|
|
471
|
+
* @param target Optional target object to store the uniform value.
|
|
472
|
+
* @returns The uniform value for the specified instance.
|
|
473
|
+
*/
|
|
474
|
+
getUniformAt(id, name, target) {
|
|
475
|
+
const schema = this.uniformMap.get(name);
|
|
476
|
+
if (!schema) {
|
|
477
|
+
console.warn(`SquareDataTexture.getUniformAt: uniform ${name} not found`);
|
|
478
|
+
return 0;
|
|
479
|
+
}
|
|
480
|
+
const { offset, size } = schema;
|
|
481
|
+
const stride = this.stride;
|
|
482
|
+
if (size === 1) {
|
|
483
|
+
return this.data[id * stride + offset];
|
|
484
|
+
}
|
|
485
|
+
return target.fromArray(this.data, id * stride + offset);
|
|
486
|
+
}
|
|
487
|
+
/** Mark the texture as needing an update. */
|
|
488
|
+
update() {
|
|
489
|
+
this.needsUpdate = true;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Generates the GLSL code for accessing the uniform data stored in the texture.
|
|
493
|
+
* @param textureName The name of the texture in the GLSL shader.
|
|
494
|
+
* @param indexName The name of the index in the GLSL shader.
|
|
495
|
+
* @param indexType The type of the index in the GLSL shader.
|
|
496
|
+
* @returns An object containing the GLSL code for the vertex and fragment shaders.
|
|
497
|
+
*/
|
|
498
|
+
getUniformsGLSL(textureName, indexName, indexType) {
|
|
499
|
+
const vertex = this.getUniformsVertexGLSL(textureName, indexName, indexType);
|
|
500
|
+
const fragment = this.getUniformsFragmentGLSL(textureName, indexName, indexType);
|
|
501
|
+
return { vertex, fragment };
|
|
502
|
+
}
|
|
503
|
+
getUniformsVertexGLSL(textureName, indexName, indexType) {
|
|
504
|
+
if (this.fetchUniformsInFragmentShader) {
|
|
505
|
+
return (
|
|
506
|
+
/*glsl*/
|
|
507
|
+
`
|
|
508
|
+
varying ${indexType} ${this.uniformPrefix}${indexName};
|
|
509
|
+
void main() {
|
|
510
|
+
${this.uniformPrefix}${indexName} = ${indexName};
|
|
511
|
+
`
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
const texelsFetch = this.texelsFetchGLSL(textureName, indexName);
|
|
515
|
+
const getFromTexels = this.getFromTexelsGLSL();
|
|
516
|
+
const { assignVarying, declareVarying } = this.getVarying();
|
|
517
|
+
return (
|
|
518
|
+
/*glsl*/
|
|
519
|
+
`
|
|
520
|
+
uniform highp sampler2D ${textureName};
|
|
521
|
+
${declareVarying}
|
|
522
|
+
void main() {
|
|
523
|
+
#ifdef USE_BATCHING
|
|
524
|
+
${indexType} ${indexName} = ${indexType}(getIndirectIndex(gl_DrawID));
|
|
525
|
+
#endif
|
|
526
|
+
${texelsFetch}
|
|
527
|
+
${getFromTexels}
|
|
528
|
+
${assignVarying}`
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
getUniformsFragmentGLSL(textureName, indexName, indexType) {
|
|
532
|
+
if (!this.fetchUniformsInFragmentShader) {
|
|
533
|
+
const { declareVarying, getVarying } = this.getVarying();
|
|
534
|
+
return (
|
|
535
|
+
/*glsl*/
|
|
536
|
+
`
|
|
537
|
+
${declareVarying}
|
|
538
|
+
void main() {
|
|
539
|
+
${getVarying}`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
const texelsFetch = this.texelsFetchGLSL(textureName, `${this.uniformPrefix}${indexName}`);
|
|
543
|
+
const getFromTexels = this.getFromTexelsGLSL();
|
|
544
|
+
return (
|
|
545
|
+
/*glsl*/
|
|
546
|
+
`
|
|
547
|
+
uniform highp sampler2D ${textureName};
|
|
548
|
+
varying ${indexType} ${this.uniformPrefix}${indexName};
|
|
549
|
+
void main() {
|
|
550
|
+
${texelsFetch}
|
|
551
|
+
${getFromTexels}`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
texelsFetchGLSL(textureName, indexName) {
|
|
555
|
+
const pixelsPerInstance = this.pixelsPerInstance;
|
|
556
|
+
let texelsFetch = (
|
|
557
|
+
/*glsl*/
|
|
558
|
+
`
|
|
559
|
+
int size = textureSize(${textureName}, 0).x;
|
|
560
|
+
int j = int(${indexName}) * ${pixelsPerInstance};
|
|
561
|
+
int x = j % size;
|
|
562
|
+
int y = j / size;
|
|
563
|
+
`
|
|
564
|
+
);
|
|
565
|
+
for (let i = 0; i < pixelsPerInstance; i++) {
|
|
566
|
+
texelsFetch += /*glsl*/
|
|
567
|
+
`vec4 ${this.uniformPrefix}texel${i} = texelFetch(${textureName}, ivec2(x + ${i}, y), 0);
|
|
568
|
+
`;
|
|
569
|
+
}
|
|
570
|
+
return texelsFetch;
|
|
571
|
+
}
|
|
572
|
+
getFromTexelsGLSL() {
|
|
573
|
+
const uniforms = this.uniformMap;
|
|
574
|
+
let getFromTexels = "";
|
|
575
|
+
for (const [name, { type, offset, size }] of uniforms) {
|
|
576
|
+
const tId = Math.floor(offset / this.channels);
|
|
577
|
+
if (type === "mat3") {
|
|
578
|
+
getFromTexels += /*glsl*/
|
|
579
|
+
`mat3 ${name} = mat3(${this.uniformPrefix}texel${tId}.rgb, vec3(${this.uniformPrefix}texel${tId}.a, ${this.uniformPrefix}texel${tId + 1}.rg), vec3(${this.uniformPrefix}texel${tId + 1}.ba, ${this.uniformPrefix}texel${tId + 2}.r));
|
|
580
|
+
`;
|
|
581
|
+
} else if (type === "mat4") {
|
|
582
|
+
getFromTexels += /*glsl*/
|
|
583
|
+
`mat4 ${name} = mat4(${this.uniformPrefix}texel${tId}, ${this.uniformPrefix}texel${tId + 1}, ${this.uniformPrefix}texel${tId + 2}, ${this.uniformPrefix}texel${tId + 3});
|
|
584
|
+
`;
|
|
585
|
+
} else {
|
|
586
|
+
const components = this.getUniformComponents(offset, size);
|
|
587
|
+
getFromTexels += /*glsl*/
|
|
588
|
+
`${type} ${name} = ${this.uniformPrefix}texel${tId}.${components};
|
|
589
|
+
`;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return getFromTexels;
|
|
593
|
+
}
|
|
594
|
+
getVarying() {
|
|
595
|
+
const uniforms = this.uniformMap;
|
|
596
|
+
let declareVarying = "";
|
|
597
|
+
let assignVarying = "";
|
|
598
|
+
let getVarying = "";
|
|
599
|
+
for (const [name, { type }] of uniforms) {
|
|
600
|
+
declareVarying += /*glsl*/
|
|
601
|
+
`varying ${type} ${this.uniformPrefix}${name};
|
|
602
|
+
`;
|
|
603
|
+
assignVarying += /*glsl*/
|
|
604
|
+
`${this.uniformPrefix}${name} = ${name};
|
|
605
|
+
`;
|
|
606
|
+
getVarying += /*glsl*/
|
|
607
|
+
`${type} ${name} = ${this.uniformPrefix}${name};
|
|
608
|
+
`;
|
|
609
|
+
}
|
|
610
|
+
return { declareVarying, assignVarying, getVarying };
|
|
611
|
+
}
|
|
612
|
+
getUniformComponents(offset, size) {
|
|
613
|
+
const startIndex = offset % this.channels;
|
|
614
|
+
let components = "";
|
|
615
|
+
for (let i = 0; i < size; i++) {
|
|
616
|
+
components += componentsArray[startIndex + i];
|
|
617
|
+
}
|
|
618
|
+
return components;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
const componentsArray = ["r", "g", "b", "a"];
|
|
622
|
+
const batchIdName = "batchId";
|
|
623
|
+
const _BatchedMesh = class _BatchedMesh extends BatchedMesh$1 {
|
|
624
|
+
/**
|
|
625
|
+
* @param instanceCount the max number of individual geometries planned to be added.
|
|
626
|
+
* @param vertexCount the max number of vertices to be used by all geometries.
|
|
627
|
+
* @param indexCount the max number of indices to be used by all geometries.
|
|
628
|
+
* @param material an instance of {@link Material}. Default is a new {@link MeshBasicMaterial}.
|
|
629
|
+
*/
|
|
630
|
+
constructor(instanceCount, vertexCount, indexCount, material) {
|
|
631
|
+
super(instanceCount, vertexCount, indexCount, material);
|
|
632
|
+
__publicField(this, "uniformsTexture");
|
|
633
|
+
__publicField(this, "uniformSchema", {});
|
|
634
|
+
__publicField(this, "isMaterialPatched", false);
|
|
635
|
+
__publicField(this, "boundsNeedsUpdate", false);
|
|
636
|
+
__publicField(this, "batchCount", 0);
|
|
637
|
+
__publicField(this, "indexBuffer");
|
|
638
|
+
__publicField(this, "geometryById", /* @__PURE__ */ new Map());
|
|
639
|
+
__publicField(this, "mapGeometryToInstanceId", /* @__PURE__ */ new Map());
|
|
640
|
+
material.forceSinglePass = true;
|
|
641
|
+
addDim(this);
|
|
642
|
+
this.addEventListener("added", () => {
|
|
643
|
+
if (!_BatchedMesh.useMultiDraw && this.geometry.index !== null) {
|
|
644
|
+
this.geometry = this.geometry.toNonIndexed();
|
|
645
|
+
this.resizeToFitGeometry(this.geometry);
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Appends uniform definitions to the current schema, and creates a new {@link SquareDataTexture} if needed.
|
|
651
|
+
* @param schema description of per-instance uniforms by shader stage (vertex/fragment)
|
|
652
|
+
*/
|
|
653
|
+
addPerInstanceUniforms(schema) {
|
|
654
|
+
this.uniformSchema = deepMerge(this.uniformSchema, schema);
|
|
655
|
+
const parsedSchema = this.parseUniformSchema(this.uniformSchema);
|
|
656
|
+
if (this.uniformsTexture) this.uniformsTexture.dispose();
|
|
657
|
+
this.uniformsTexture = new SquareDataTexture(
|
|
658
|
+
Float32Array,
|
|
659
|
+
parsedSchema.channels,
|
|
660
|
+
parsedSchema.texelsPerInstance,
|
|
661
|
+
this.maxInstanceCount,
|
|
662
|
+
parsedSchema.uniformMap,
|
|
663
|
+
parsedSchema.fetchInFragmentShader
|
|
664
|
+
);
|
|
665
|
+
if (!this.isMaterialPatched) this.patchMaterial(this.material);
|
|
666
|
+
}
|
|
667
|
+
addGeometry(geometry, reservedVertexRange, reservedIndexRange) {
|
|
668
|
+
if (_BatchedMesh.useMultiDraw) return super.addGeometry(geometry, reservedVertexRange, reservedIndexRange);
|
|
669
|
+
this.addBatchIdBuffer(geometry, this.instanceCount);
|
|
670
|
+
const geometryId = super.addGeometry(geometry, reservedVertexRange, reservedIndexRange);
|
|
671
|
+
this.geometryById.set(geometryId, geometry);
|
|
672
|
+
return geometryId;
|
|
673
|
+
}
|
|
674
|
+
addInstance(geometryId) {
|
|
675
|
+
if (_BatchedMesh.useMultiDraw) return super.addInstance(geometryId);
|
|
676
|
+
if (this.mapGeometryToInstanceId.has(geometryId)) {
|
|
677
|
+
const geometry = this.geometryById.get(geometryId);
|
|
678
|
+
this.resizeToFitGeometry(geometry);
|
|
679
|
+
geometryId = this.addGeometry(geometry);
|
|
680
|
+
}
|
|
681
|
+
const instanceId = super.addInstance(geometryId);
|
|
682
|
+
this.mapGeometryToInstanceId.set(geometryId, instanceId);
|
|
683
|
+
return instanceId;
|
|
684
|
+
}
|
|
685
|
+
onBeforeRender(renderer, scene, camera, geometry, material, group) {
|
|
686
|
+
var _a2;
|
|
687
|
+
(_a2 = this.uniformsTexture) == null ? void 0 : _a2.update();
|
|
688
|
+
if (_BatchedMesh.useMultiDraw) return super.onBeforeRender(renderer, scene, camera, geometry, material, group);
|
|
689
|
+
if (!this.indexBuffer) {
|
|
690
|
+
const vertexCount = geometry.getAttribute("position").count;
|
|
691
|
+
this.indexBuffer = new BufferAttribute(new Uint32Array(vertexCount), 1).setUsage(StreamDrawUsage);
|
|
692
|
+
}
|
|
693
|
+
super.onBeforeRender(renderer, scene, camera, geometry, material, group);
|
|
694
|
+
this.batchCount = this.updateIndexBuffer(geometry);
|
|
695
|
+
this._multiDrawCount = 0;
|
|
696
|
+
}
|
|
697
|
+
onAfterRender(renderer, scene, camera, geometry) {
|
|
698
|
+
var _a2;
|
|
699
|
+
if (_BatchedMesh.useMultiDraw) return;
|
|
700
|
+
const batchCount = this.batchCount;
|
|
701
|
+
const gl = renderer.getContext();
|
|
702
|
+
if (geometry.index == null) return console.warn("No index buffer", (_a2 = this.parent) == null ? void 0 : _a2.name);
|
|
703
|
+
const type = this.getIndexType(gl, geometry.index);
|
|
704
|
+
gl.drawElements(gl.TRIANGLES, batchCount, type, 0);
|
|
705
|
+
renderer.info.update(batchCount, gl.TRIANGLES, 1);
|
|
706
|
+
geometry.setIndex(null);
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Retrieves the value of a uniform at the specified instance ID.
|
|
358
710
|
* @param id instance ID
|
|
359
711
|
* @param name uniform name
|
|
360
712
|
* @param target Optional target object to store the uniform value
|
|
@@ -388,530 +740,179 @@ class BatchedMesh extends BatchedMesh$1 {
|
|
|
388
740
|
this.boundsNeedsUpdate = false;
|
|
389
741
|
}
|
|
390
742
|
}
|
|
391
|
-
setMatrixAt(instanceId, matrix) {
|
|
392
|
-
super.setMatrixAt(instanceId, matrix);
|
|
393
|
-
this.boundsNeedsUpdate = true;
|
|
394
|
-
return this;
|
|
395
|
-
}
|
|
396
|
-
dispose() {
|
|
397
|
-
var _a2;
|
|
398
|
-
this.geometry.setIndex(this.indexBuffer ?? null);
|
|
399
|
-
super.dispose();
|
|
400
|
-
this.uniformSchema = {};
|
|
401
|
-
(_a2 = this.uniformsTexture) == null ? void 0 : _a2.dispose();
|
|
402
|
-
this.uniformsTexture = void 0;
|
|
403
|
-
this.indexBuffer = void 0;
|
|
404
|
-
this.geometryById.forEach((geometry) => geometry.dispose());
|
|
405
|
-
this.geometryById.clear();
|
|
406
|
-
this.mapGeometryToInstanceId.clear();
|
|
407
|
-
return this;
|
|
408
|
-
}
|
|
409
|
-
resizeToFitGeometry(geometry) {
|
|
410
|
-
var _a2;
|
|
411
|
-
const vertexCount = geometry.attributes["position"].count;
|
|
412
|
-
const indexCount = ((_a2 = geometry.index) == null ? void 0 : _a2.count) ?? 0;
|
|
413
|
-
this._maxVertexCount += vertexCount;
|
|
414
|
-
this._maxIndexCount += indexCount;
|
|
415
|
-
this.setGeometrySize(this._maxVertexCount, this._maxIndexCount);
|
|
416
|
-
}
|
|
417
|
-
updateIndexBuffer(geometry) {
|
|
418
|
-
const { _multiDrawStarts, _multiDrawCounts, _multiDrawCount } = this;
|
|
419
|
-
const indexBuffer = this.indexBuffer;
|
|
420
|
-
const indexArray = indexBuffer.array;
|
|
421
|
-
const batchIdBuffer = geometry.getAttribute(batchIdName);
|
|
422
|
-
const batchIdArray = batchIdBuffer.array;
|
|
423
|
-
const indirectArray = this._indirectTexture.image.data;
|
|
424
|
-
let indexCount = 0;
|
|
425
|
-
for (let i = 0; i < _multiDrawCount; i++) {
|
|
426
|
-
const start = _multiDrawStarts[i];
|
|
427
|
-
const count = _multiDrawCounts[i];
|
|
428
|
-
const batchId = batchIdArray[start];
|
|
429
|
-
indirectArray[batchId] = batchId;
|
|
430
|
-
for (let j = start; j < start + count; j++) {
|
|
431
|
-
indexArray[indexCount++] = j;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
indexBuffer.needsUpdate = true;
|
|
435
|
-
geometry.setIndex(indexBuffer);
|
|
436
|
-
return indexCount;
|
|
437
|
-
}
|
|
438
|
-
addBatchIdBuffer(geometry, geometryId) {
|
|
439
|
-
const hasAttribute = geometry.hasAttribute(batchIdName);
|
|
440
|
-
const vertexCount = geometry.getAttribute("position").count;
|
|
441
|
-
const batchIdArray = hasAttribute ? geometry.getAttribute(batchIdName).array : new Int32Array(vertexCount);
|
|
442
|
-
for (let i = 0; i < vertexCount; i++) {
|
|
443
|
-
batchIdArray[i] = geometryId;
|
|
444
|
-
}
|
|
445
|
-
if (!hasAttribute) {
|
|
446
|
-
const batchIdBuffer = new BufferAttribute(batchIdArray, 1).setUsage(StreamDrawUsage);
|
|
447
|
-
geometry.setAttribute(batchIdName, batchIdBuffer);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
patchMaterial(material) {
|
|
451
|
-
const onBeforeCompile = material.onBeforeCompile.bind(material);
|
|
452
|
-
const customProgramCacheKey = material.customProgramCacheKey.bind(material);
|
|
453
|
-
material.onBeforeCompile = (shader, renderer) => {
|
|
454
|
-
var _a2;
|
|
455
|
-
onBeforeCompile(shader, renderer);
|
|
456
|
-
shader.defines ?? (shader.defines = {});
|
|
457
|
-
shader.defines["USE_BATCH_UNIFORMS"] = "";
|
|
458
|
-
shader.uniforms["uniformsTexture"] = { value: this.uniformsTexture };
|
|
459
|
-
const { vertex, fragment } = ((_a2 = this.uniformsTexture) == null ? void 0 : _a2.getUniformsGLSL("uniformsTexture", "instanceId", "int")) ?? {
|
|
460
|
-
vertex: "",
|
|
461
|
-
fragment: ""
|
|
462
|
-
};
|
|
463
|
-
const patch = (
|
|
464
|
-
/*glsl*/
|
|
465
|
-
`
|
|
466
|
-
#ifdef gl_DrawID
|
|
467
|
-
#define _gl_DrawID ${batchIdName}
|
|
468
|
-
#else
|
|
469
|
-
#define gl_DrawID ${batchIdName}
|
|
470
|
-
#endif
|
|
471
|
-
in int ${batchIdName};
|
|
472
|
-
`
|
|
473
|
-
);
|
|
474
|
-
const main = "void main() {";
|
|
475
|
-
if (!this.useMultiDraw) shader.vertexShader = shader.vertexShader.replace(main, `${patch}${main}`);
|
|
476
|
-
if (vertex) shader.vertexShader = shader.vertexShader.replace(main, vertex);
|
|
477
|
-
if (fragment) shader.fragmentShader = shader.fragmentShader.replace(main, fragment);
|
|
478
|
-
};
|
|
479
|
-
material.customProgramCacheKey = () => {
|
|
480
|
-
return `batch_${this.id}_${!!this.uniformsTexture}_${customProgramCacheKey()}`;
|
|
481
|
-
};
|
|
482
|
-
this.isMaterialPatched = true;
|
|
483
|
-
}
|
|
484
|
-
// Taken from https://github.com/agargaro/instanced-mesh/blob/master/src/core/feature/Uniforms.ts
|
|
485
|
-
parseUniformSchema(schema) {
|
|
486
|
-
let totalSize = 0;
|
|
487
|
-
const uniformMap = /* @__PURE__ */ new Map();
|
|
488
|
-
const uniforms = [];
|
|
489
|
-
const vertexSchema = schema.vertex ?? {};
|
|
490
|
-
const fragmentSchema = schema.fragment ?? {};
|
|
491
|
-
let fetchInFragmentShader = true;
|
|
492
|
-
for (const name in vertexSchema) {
|
|
493
|
-
const type = vertexSchema[name];
|
|
494
|
-
const size = this.getUniformSize(type);
|
|
495
|
-
totalSize += size;
|
|
496
|
-
uniforms.push({ name, type, size });
|
|
497
|
-
fetchInFragmentShader = false;
|
|
498
|
-
}
|
|
499
|
-
for (const name in fragmentSchema) {
|
|
500
|
-
if (!vertexSchema[name]) {
|
|
501
|
-
const type = fragmentSchema[name];
|
|
502
|
-
const size = this.getUniformSize(type);
|
|
503
|
-
totalSize += size;
|
|
504
|
-
uniforms.push({ name, type, size });
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
uniforms.sort((a, b) => b.size - a.size);
|
|
508
|
-
const tempOffset = [];
|
|
509
|
-
for (const { name, size, type } of uniforms) {
|
|
510
|
-
const offset = this.getUniformOffset(size, tempOffset);
|
|
511
|
-
uniformMap.set(name, { offset, size, type });
|
|
512
|
-
}
|
|
513
|
-
const pixelsPerInstance = Math.ceil(totalSize / 4);
|
|
514
|
-
const channels = Math.min(totalSize, 4);
|
|
515
|
-
return { channels, texelsPerInstance: pixelsPerInstance, uniformMap, fetchInFragmentShader };
|
|
516
|
-
}
|
|
517
|
-
getUniformOffset(size, tempOffset) {
|
|
518
|
-
if (size < 4) {
|
|
519
|
-
for (let i = 0; i < tempOffset.length; i++) {
|
|
520
|
-
if (tempOffset[i] + size <= 4) {
|
|
521
|
-
const offset2 = i * 4 + tempOffset[i];
|
|
522
|
-
tempOffset[i] += size;
|
|
523
|
-
return offset2;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
const offset = tempOffset.length * 4;
|
|
528
|
-
for (; size > 0; size -= 4) {
|
|
529
|
-
tempOffset.push(size);
|
|
530
|
-
}
|
|
531
|
-
return offset;
|
|
532
|
-
}
|
|
533
|
-
getUniformSize(type) {
|
|
534
|
-
switch (type) {
|
|
535
|
-
case "float":
|
|
536
|
-
return 1;
|
|
537
|
-
case "vec2":
|
|
538
|
-
return 2;
|
|
539
|
-
case "vec3":
|
|
540
|
-
return 3;
|
|
541
|
-
case "vec4":
|
|
542
|
-
return 4;
|
|
543
|
-
case "mat3":
|
|
544
|
-
return 9;
|
|
545
|
-
case "mat4":
|
|
546
|
-
return 16;
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
getIndexType(gl, index) {
|
|
550
|
-
const array = index.array;
|
|
551
|
-
if (array instanceof Uint16Array) return gl.UNSIGNED_SHORT;
|
|
552
|
-
if (array instanceof Uint32Array) return gl.UNSIGNED_INT;
|
|
553
|
-
return gl.UNSIGNED_BYTE;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
const floatsPerMember = 32;
|
|
557
|
-
const tempColor = new Color();
|
|
558
|
-
const defaultStrokeColor = 8421504;
|
|
559
|
-
const tempMat4 = new Matrix4();
|
|
560
|
-
const tempVec3a = new Vector3();
|
|
561
|
-
const tempVec3b = new Vector3();
|
|
562
|
-
const origin = new Vector3();
|
|
563
|
-
const defaultOrient = "+x+y";
|
|
564
|
-
class BatchedText extends BatchedText$1 {
|
|
565
|
-
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
566
|
-
constructor() {
|
|
567
|
-
super();
|
|
568
|
-
__publicField(this, "mapInstanceIdToText", /* @__PURE__ */ new Map());
|
|
569
|
-
__publicField(this, "textArray", []);
|
|
570
|
-
__publicField(this, "textureNeedsUpdate", false);
|
|
571
|
-
}
|
|
572
|
-
/** Number of texts in the batch */
|
|
573
|
-
get size() {
|
|
574
|
-
return this._members.size;
|
|
575
|
-
}
|
|
576
|
-
/** Base material before patching */
|
|
577
|
-
get baseMaterial() {
|
|
578
|
-
return this._baseMaterial;
|
|
579
|
-
}
|
|
580
|
-
/**
|
|
581
|
-
* Get the {@link Text} object by instance id
|
|
582
|
-
* @param instanceId Instance id
|
|
583
|
-
* @returns Text object
|
|
584
|
-
*/
|
|
585
|
-
getText(instanceId) {
|
|
586
|
-
return this.mapInstanceIdToText.get(instanceId);
|
|
587
|
-
}
|
|
588
|
-
/**
|
|
589
|
-
* Set the visibility of the {@link Text} object by instance id.
|
|
590
|
-
* This is for interface compatibility with {@link BatchedMesh}.
|
|
591
|
-
* @param instanceId Instance id
|
|
592
|
-
* @param visible Visibility flag
|
|
593
|
-
*/
|
|
594
|
-
setVisibleAt(instanceId, visible) {
|
|
595
|
-
const text = this.getText(instanceId);
|
|
596
|
-
text.visible = visible;
|
|
597
|
-
}
|
|
598
|
-
addText(text, instanceId) {
|
|
599
|
-
super.addText(text);
|
|
600
|
-
if (instanceId !== void 0) {
|
|
601
|
-
this.mapInstanceIdToText.set(instanceId, text);
|
|
602
|
-
}
|
|
603
|
-
this.textArray.push(text);
|
|
743
|
+
setMatrixAt(instanceId, matrix) {
|
|
744
|
+
super.setMatrixAt(instanceId, matrix);
|
|
745
|
+
this.boundsNeedsUpdate = true;
|
|
746
|
+
return this;
|
|
604
747
|
}
|
|
605
748
|
dispose() {
|
|
749
|
+
var _a2;
|
|
750
|
+
if (this.indexBuffer) this.geometry.setIndex(this.indexBuffer);
|
|
606
751
|
super.dispose();
|
|
607
|
-
this.
|
|
752
|
+
this.uniformSchema = {};
|
|
753
|
+
(_a2 = this.uniformsTexture) == null ? void 0 : _a2.dispose();
|
|
754
|
+
this.uniformsTexture = void 0;
|
|
755
|
+
this.indexBuffer = void 0;
|
|
756
|
+
this.geometryById.forEach((geometry) => geometry.dispose());
|
|
757
|
+
this.geometryById.clear();
|
|
758
|
+
this.mapGeometryToInstanceId.clear();
|
|
759
|
+
return this;
|
|
608
760
|
}
|
|
609
|
-
|
|
761
|
+
resizeToFitGeometry(geometry) {
|
|
610
762
|
var _a2;
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
const startIndex = index * floatsPerMember;
|
|
633
|
-
if (!text.visible) {
|
|
634
|
-
for (let i = 0; i < 16; i++) {
|
|
635
|
-
this.setTexData(startIndex + i, 0, texData);
|
|
636
|
-
}
|
|
637
|
-
continue;
|
|
638
|
-
}
|
|
639
|
-
const matrix = text.matrix.elements;
|
|
640
|
-
for (let i = 0; i < 16; i++) {
|
|
641
|
-
this.setTexData(startIndex + i, matrix[i], texData);
|
|
642
|
-
}
|
|
643
|
-
text._prepareForRender(material);
|
|
644
|
-
const {
|
|
645
|
-
uTroikaTotalBounds,
|
|
646
|
-
uTroikaClipRect,
|
|
647
|
-
uTroikaPositionOffset,
|
|
648
|
-
uTroikaEdgeOffset,
|
|
649
|
-
uTroikaBlurRadius,
|
|
650
|
-
uTroikaStrokeWidth,
|
|
651
|
-
uTroikaStrokeColor,
|
|
652
|
-
uTroikaStrokeOpacity,
|
|
653
|
-
uTroikaFillOpacity,
|
|
654
|
-
uTroikaCurveRadius
|
|
655
|
-
} = material.uniforms;
|
|
656
|
-
for (let i = 0; i < 4; i++) {
|
|
657
|
-
this.setTexData(startIndex + 16 + i, uTroikaTotalBounds.value.getComponent(i), texData);
|
|
658
|
-
}
|
|
659
|
-
for (let i = 0; i < 4; i++) {
|
|
660
|
-
this.setTexData(startIndex + 20 + i, uTroikaClipRect.value.getComponent(i), texData);
|
|
661
|
-
}
|
|
662
|
-
let color = isOutline ? text.outlineColor || 0 : text.color;
|
|
663
|
-
color ?? (color = this.color);
|
|
664
|
-
color ?? (color = this.material.color);
|
|
665
|
-
color ?? (color = 16777215);
|
|
666
|
-
this.setTexData(startIndex + 24, tempColor.set(color).getHex(), texData);
|
|
667
|
-
this.setTexData(startIndex + 25, uTroikaFillOpacity.value, texData);
|
|
668
|
-
this.setTexData(startIndex + 26, uTroikaCurveRadius.value, texData);
|
|
669
|
-
if (isOutline) {
|
|
670
|
-
this.setTexData(startIndex + 28, uTroikaPositionOffset.value.x, texData);
|
|
671
|
-
this.setTexData(startIndex + 29, uTroikaPositionOffset.value.y, texData);
|
|
672
|
-
this.setTexData(startIndex + 30, uTroikaEdgeOffset.value, texData);
|
|
673
|
-
this.setTexData(startIndex + 31, uTroikaBlurRadius.value, texData);
|
|
674
|
-
} else {
|
|
675
|
-
this.setTexData(startIndex + 28, uTroikaStrokeWidth.value, texData);
|
|
676
|
-
this.setTexData(startIndex + 29, tempColor.set(uTroikaStrokeColor.value).getHex(), texData);
|
|
677
|
-
this.setTexData(startIndex + 30, uTroikaStrokeOpacity.value, texData);
|
|
763
|
+
const vertexCount = geometry.attributes["position"].count;
|
|
764
|
+
const indexCount = ((_a2 = geometry.index) == null ? void 0 : _a2.count) ?? 0;
|
|
765
|
+
this._maxVertexCount += vertexCount;
|
|
766
|
+
this._maxIndexCount += indexCount;
|
|
767
|
+
this.setGeometrySize(this._maxVertexCount, this._maxIndexCount);
|
|
768
|
+
}
|
|
769
|
+
updateIndexBuffer(geometry) {
|
|
770
|
+
const { _multiDrawStarts, _multiDrawCounts, _multiDrawCount } = this;
|
|
771
|
+
const indexBuffer = this.indexBuffer;
|
|
772
|
+
const indexArray = indexBuffer.array;
|
|
773
|
+
const batchIdBuffer = geometry.getAttribute(batchIdName);
|
|
774
|
+
const batchIdArray = batchIdBuffer.array;
|
|
775
|
+
const indirectArray = this._indirectTexture.image.data;
|
|
776
|
+
let indexCount = 0;
|
|
777
|
+
for (let i = 0; i < _multiDrawCount; i++) {
|
|
778
|
+
const start = _multiDrawStarts[i];
|
|
779
|
+
const count = _multiDrawCounts[i];
|
|
780
|
+
const batchId = batchIdArray[start];
|
|
781
|
+
indirectArray[batchId] = batchId;
|
|
782
|
+
for (let j = start; j < start + count; j++) {
|
|
783
|
+
indexArray[indexCount++] = j;
|
|
678
784
|
}
|
|
679
785
|
}
|
|
680
|
-
|
|
681
|
-
|
|
786
|
+
indexBuffer.needsUpdate = true;
|
|
787
|
+
geometry.setIndex(indexBuffer);
|
|
788
|
+
return indexCount;
|
|
682
789
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
790
|
+
addBatchIdBuffer(geometry, geometryId) {
|
|
791
|
+
const hasAttribute = geometry.hasAttribute(batchIdName);
|
|
792
|
+
const vertexCount = geometry.getAttribute("position").count;
|
|
793
|
+
const batchIdArray = hasAttribute ? geometry.getAttribute(batchIdName).array : new Int32Array(vertexCount);
|
|
794
|
+
for (let i = 0; i < vertexCount; i++) {
|
|
795
|
+
batchIdArray[i] = geometryId;
|
|
687
796
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
_prepareForRender(material) {
|
|
692
|
-
const isOutline = material.isTextOutlineMaterial;
|
|
693
|
-
const uniforms = material.uniforms;
|
|
694
|
-
const textInfo = this.textRenderInfo;
|
|
695
|
-
if (textInfo) {
|
|
696
|
-
const { sdfTexture, blockBounds } = textInfo;
|
|
697
|
-
const { width, height } = sdfTexture.image;
|
|
698
|
-
uniforms.uTroikaSDFTexture.value = sdfTexture;
|
|
699
|
-
uniforms.uTroikaSDFTextureSize.value.set(width, height);
|
|
700
|
-
uniforms.uTroikaSDFGlyphSize.value = textInfo.sdfGlyphSize;
|
|
701
|
-
uniforms.uTroikaSDFExponent.value = textInfo.sdfExponent;
|
|
702
|
-
uniforms.uTroikaTotalBounds.value.fromArray(blockBounds);
|
|
703
|
-
uniforms.uTroikaUseGlyphColors.value = !isOutline && !!textInfo.glyphColors;
|
|
704
|
-
let distanceOffset = 0;
|
|
705
|
-
let blurRadius = 0;
|
|
706
|
-
let strokeWidth = 0;
|
|
707
|
-
let fillOpacity;
|
|
708
|
-
let strokeOpacity = 1;
|
|
709
|
-
let strokeColor;
|
|
710
|
-
let offsetX = 0;
|
|
711
|
-
let offsetY = 0;
|
|
712
|
-
if (isOutline) {
|
|
713
|
-
const { outlineWidth, outlineOffsetX, outlineOffsetY, outlineBlur, outlineOpacity } = this;
|
|
714
|
-
distanceOffset = this._parsePercent(outlineWidth) || 0;
|
|
715
|
-
blurRadius = Math.max(0, this._parsePercent(outlineBlur) || 0);
|
|
716
|
-
fillOpacity = outlineOpacity;
|
|
717
|
-
offsetX = this._parsePercent(outlineOffsetX) || 0;
|
|
718
|
-
offsetY = this._parsePercent(outlineOffsetY) || 0;
|
|
719
|
-
} else {
|
|
720
|
-
strokeWidth = Math.max(0, this._parsePercent(this.strokeWidth) || 0);
|
|
721
|
-
if (strokeWidth) {
|
|
722
|
-
strokeColor = this.strokeColor;
|
|
723
|
-
uniforms.uTroikaStrokeColor.value.set(strokeColor ?? defaultStrokeColor);
|
|
724
|
-
strokeOpacity = this.strokeOpacity;
|
|
725
|
-
strokeOpacity ?? (strokeOpacity = 1);
|
|
726
|
-
}
|
|
727
|
-
fillOpacity = this.fillOpacity;
|
|
728
|
-
}
|
|
729
|
-
uniforms.uTroikaEdgeOffset.value = distanceOffset;
|
|
730
|
-
uniforms.uTroikaPositionOffset.value.set(offsetX, offsetY);
|
|
731
|
-
uniforms.uTroikaBlurRadius.value = blurRadius;
|
|
732
|
-
uniforms.uTroikaStrokeWidth.value = strokeWidth;
|
|
733
|
-
uniforms.uTroikaStrokeOpacity.value = strokeOpacity;
|
|
734
|
-
uniforms.uTroikaFillOpacity.value = fillOpacity ?? 1;
|
|
735
|
-
uniforms.uTroikaCurveRadius.value = this.curveRadius || 0;
|
|
736
|
-
const clipRect = this.clipRect;
|
|
737
|
-
if (clipRect && Array.isArray(clipRect) && clipRect.length === 4) {
|
|
738
|
-
uniforms.uTroikaClipRect.value.fromArray(clipRect);
|
|
739
|
-
} else {
|
|
740
|
-
const pad = (this.fontSize || 0.1) * 100;
|
|
741
|
-
uniforms.uTroikaClipRect.value.set(
|
|
742
|
-
blockBounds[0] - pad,
|
|
743
|
-
blockBounds[1] - pad,
|
|
744
|
-
blockBounds[2] + pad,
|
|
745
|
-
blockBounds[3] + pad
|
|
746
|
-
);
|
|
747
|
-
}
|
|
748
|
-
this.geometry.applyClipRect(uniforms.uTroikaClipRect.value);
|
|
797
|
+
if (!hasAttribute) {
|
|
798
|
+
const batchIdBuffer = new BufferAttribute(batchIdArray, 1).setUsage(StreamDrawUsage);
|
|
799
|
+
geometry.setAttribute(batchIdName, batchIdBuffer);
|
|
749
800
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
}
|
|
801
|
+
}
|
|
802
|
+
patchMaterial(material) {
|
|
803
|
+
const onBeforeCompile = material.onBeforeCompile.bind(material);
|
|
804
|
+
const customProgramCacheKey = material.customProgramCacheKey.bind(material);
|
|
805
|
+
material.onBeforeCompile = (shader, renderer) => {
|
|
806
|
+
var _a2;
|
|
807
|
+
onBeforeCompile(shader, renderer);
|
|
808
|
+
shader.defines ?? (shader.defines = {});
|
|
809
|
+
shader.defines["USE_BATCH_UNIFORMS"] = "";
|
|
810
|
+
shader.uniforms["uniformsTexture"] = { value: this.uniformsTexture };
|
|
811
|
+
const { vertex, fragment } = ((_a2 = this.uniformsTexture) == null ? void 0 : _a2.getUniformsGLSL("uniformsTexture", "instanceId", "int")) ?? {
|
|
812
|
+
vertex: "",
|
|
813
|
+
fragment: ""
|
|
814
|
+
};
|
|
815
|
+
const patch = (
|
|
816
|
+
/*glsl*/
|
|
817
|
+
`
|
|
818
|
+
#ifdef gl_DrawID
|
|
819
|
+
#define _gl_DrawID ${batchIdName}
|
|
820
|
+
#else
|
|
821
|
+
#define gl_DrawID ${batchIdName}
|
|
822
|
+
#endif
|
|
823
|
+
in int ${batchIdName};
|
|
824
|
+
`
|
|
825
|
+
);
|
|
826
|
+
const main = "void main() {";
|
|
827
|
+
if (!_BatchedMesh.useMultiDraw) shader.vertexShader = shader.vertexShader.replace(main, `${patch}${main}`);
|
|
828
|
+
if (vertex) shader.vertexShader = shader.vertexShader.replace(main, vertex);
|
|
829
|
+
if (fragment) shader.fragmentShader = shader.fragmentShader.replace(main, fragment);
|
|
830
|
+
};
|
|
831
|
+
material.customProgramCacheKey = () => {
|
|
832
|
+
return `batch_${this.id}_${!!this.uniformsTexture}_${customProgramCacheKey()}`;
|
|
833
|
+
};
|
|
834
|
+
this.isMaterialPatched = true;
|
|
835
|
+
}
|
|
836
|
+
// Taken from https://github.com/agargaro/instanced-mesh/blob/master/src/core/feature/Uniforms.ts
|
|
837
|
+
parseUniformSchema(schema) {
|
|
838
|
+
let totalSize = 0;
|
|
839
|
+
const uniformMap = /* @__PURE__ */ new Map();
|
|
840
|
+
const uniforms = [];
|
|
841
|
+
const vertexSchema = schema.vertex ?? {};
|
|
842
|
+
const fragmentSchema = schema.fragment ?? {};
|
|
843
|
+
let fetchInFragmentShader = true;
|
|
844
|
+
for (const name in vertexSchema) {
|
|
845
|
+
const type = vertexSchema[name];
|
|
846
|
+
const size = this.getUniformSize(type);
|
|
847
|
+
totalSize += size;
|
|
848
|
+
uniforms.push({ name, type, size });
|
|
849
|
+
fetchInFragmentShader = false;
|
|
761
850
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
const [, hSign, hAxis, vSign, vAxis] = match;
|
|
769
|
-
tempVec3a.set(0, 0, 0)[hAxis] = hSign === "-" ? 1 : -1;
|
|
770
|
-
tempVec3b.set(0, 0, 0)[vAxis] = vSign === "-" ? -1 : 1;
|
|
771
|
-
tempMat4.lookAt(origin, tempVec3a.cross(tempVec3b), tempVec3b);
|
|
772
|
-
rotMat.setFromMatrix4(tempMat4);
|
|
773
|
-
} else {
|
|
774
|
-
rotMat.identity();
|
|
851
|
+
for (const name in fragmentSchema) {
|
|
852
|
+
if (!vertexSchema[name]) {
|
|
853
|
+
const type = fragmentSchema[name];
|
|
854
|
+
const size = this.getUniformSize(type);
|
|
855
|
+
totalSize += size;
|
|
856
|
+
uniforms.push({ name, type, size });
|
|
775
857
|
}
|
|
776
|
-
material._orientation = orient;
|
|
777
858
|
}
|
|
859
|
+
uniforms.sort((a, b) => b.size - a.size);
|
|
860
|
+
const tempOffset = [];
|
|
861
|
+
for (const { name, size, type } of uniforms) {
|
|
862
|
+
const offset = this.getUniformOffset(size, tempOffset);
|
|
863
|
+
uniformMap.set(name, { offset, size, type });
|
|
864
|
+
}
|
|
865
|
+
const pixelsPerInstance = Math.ceil(totalSize / 4);
|
|
866
|
+
const channels = Math.min(totalSize, 4);
|
|
867
|
+
return { channels, texelsPerInstance: pixelsPerInstance, uniformMap, fetchInFragmentShader };
|
|
778
868
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
object.setUniformAt(instanceId, "skipDimInstance", value);
|
|
787
|
-
return;
|
|
788
|
-
}
|
|
789
|
-
const skipDimTexture = object.userData["skipDimTexture"];
|
|
790
|
-
if (skipDimTexture) {
|
|
791
|
-
const skipDimData = skipDimTexture.image.data;
|
|
792
|
-
skipDimData[instanceId] = value;
|
|
793
|
-
skipDimTexture.needsUpdate = true;
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
function addDimToMaterial(material) {
|
|
797
|
-
if (material.userData.hasDimShader) return;
|
|
798
|
-
const onBeforeCompile = material.onBeforeCompile.bind(material);
|
|
799
|
-
const onBeforeRender = material.onBeforeRender.bind(material);
|
|
800
|
-
material.onBeforeCompile = (shader, renderer) => {
|
|
801
|
-
onBeforeCompile(shader, renderer);
|
|
802
|
-
shader.uniforms["uDim"] = { value: material.userData.uDim ?? 0 };
|
|
803
|
-
shader.uniforms["skipDimTexture"] = { value: material.userData.skipDimTexture ?? null };
|
|
804
|
-
shader.vertexShader = shader.vertexShader.replace("void main() {", `${dimColorVertexDefs}
|
|
805
|
-
void main() {`).replace(
|
|
806
|
-
"#include <fog_vertex>",
|
|
807
|
-
/*glsl*/
|
|
808
|
-
`
|
|
809
|
-
#include <fog_vertex>
|
|
810
|
-
setDimAmount();
|
|
811
|
-
`
|
|
812
|
-
).concat(dimColorVertexImpl);
|
|
813
|
-
shader.fragmentShader = /*glsl*/
|
|
814
|
-
`
|
|
815
|
-
${dimColorFrag}
|
|
816
|
-
${shader.fragmentShader}
|
|
817
|
-
`.replace(
|
|
818
|
-
"#include <colorspace_fragment>",
|
|
819
|
-
/*glsl*/
|
|
820
|
-
`
|
|
821
|
-
gl_FragColor = dimColor(gl_FragColor);
|
|
822
|
-
#include <colorspace_fragment>
|
|
823
|
-
`
|
|
824
|
-
);
|
|
825
|
-
material.userData.shader = shader;
|
|
826
|
-
};
|
|
827
|
-
material.onBeforeRender = (renderer, scene, camera, geometry, object, group) => {
|
|
828
|
-
onBeforeRender(renderer, scene, camera, geometry, object, group);
|
|
829
|
-
const skipDimTexture = object.userData["skipDimTexture"];
|
|
830
|
-
let uDim = object.userData["uDim"];
|
|
831
|
-
if (uDim === void 0) {
|
|
832
|
-
for (const ancestor of traverseAncestorsGenerator(object)) {
|
|
833
|
-
if (ancestor.userData["uDim"] !== void 0) {
|
|
834
|
-
uDim = ancestor.userData["uDim"];
|
|
835
|
-
break;
|
|
869
|
+
getUniformOffset(size, tempOffset) {
|
|
870
|
+
if (size < 4) {
|
|
871
|
+
for (let i = 0; i < tempOffset.length; i++) {
|
|
872
|
+
if (tempOffset[i] + size <= 4) {
|
|
873
|
+
const offset2 = i * 4 + tempOffset[i];
|
|
874
|
+
tempOffset[i] += size;
|
|
875
|
+
return offset2;
|
|
836
876
|
}
|
|
837
877
|
}
|
|
838
878
|
}
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
material.userData.skipDimTexture = object.userData["skipDimTexture"];
|
|
843
|
-
return;
|
|
879
|
+
const offset = tempOffset.length * 4;
|
|
880
|
+
for (; size > 0; size -= 4) {
|
|
881
|
+
tempOffset.push(size);
|
|
844
882
|
}
|
|
845
|
-
|
|
846
|
-
shader.uniforms["skipDimTexture"].value = skipDimTexture ?? null;
|
|
847
|
-
};
|
|
848
|
-
material.userData.hasDimShader = true;
|
|
849
|
-
}
|
|
850
|
-
function addDim(mesh) {
|
|
851
|
-
if (mesh instanceof BatchedMesh) mesh.addPerInstanceUniforms({ vertex: { skipDimInstance: "float" } });
|
|
852
|
-
if (mesh instanceof BatchedText) addSkipDimTexture(mesh);
|
|
853
|
-
}
|
|
854
|
-
function addSkipDimTexture(text) {
|
|
855
|
-
const count = text.size;
|
|
856
|
-
const size = Math.ceil(Math.sqrt(count));
|
|
857
|
-
const array = new Float32Array(size * size);
|
|
858
|
-
array.fill(0);
|
|
859
|
-
const texture = new DataTexture(array, size, size, RedFormat, FloatType);
|
|
860
|
-
texture.needsUpdate = true;
|
|
861
|
-
text.userData["skipDimTexture"] = texture;
|
|
862
|
-
text.addEventListener("dispose", () => texture.dispose());
|
|
863
|
-
return texture;
|
|
864
|
-
}
|
|
865
|
-
const dimColorVertexDefs = (
|
|
866
|
-
/*glsl*/
|
|
867
|
-
`
|
|
868
|
-
uniform float uDim;
|
|
869
|
-
out float dimAmount;
|
|
870
|
-
#ifdef TROIKA_DERIVED_MATERIAL_1
|
|
871
|
-
uniform sampler2D skipDimTexture;
|
|
872
|
-
#endif
|
|
873
|
-
void setDimAmount();
|
|
874
|
-
`
|
|
875
|
-
);
|
|
876
|
-
const dimColorVertexImpl = (
|
|
877
|
-
/*glsl*/
|
|
878
|
-
`
|
|
879
|
-
void setDimAmount() {
|
|
880
|
-
float instanceDim = 0.;
|
|
881
|
-
#ifdef USE_BATCHING
|
|
882
|
-
instanceDim = batch_skipDimInstance;
|
|
883
|
-
#endif
|
|
884
|
-
#ifdef TROIKA_DERIVED_MATERIAL_1
|
|
885
|
-
float indirectIndex = aTroikaTextBatchMemberIndex;
|
|
886
|
-
int size = textureSize(skipDimTexture, 0).x;
|
|
887
|
-
int i = int(indirectIndex);
|
|
888
|
-
int x = i % size;
|
|
889
|
-
int y = i / size;
|
|
890
|
-
instanceDim = texelFetch(skipDimTexture, ivec2(x, y), 0).r;
|
|
891
|
-
#endif
|
|
892
|
-
dimAmount = instanceDim == 0. ? uDim : instanceDim / 2. + 0.5;
|
|
883
|
+
return offset;
|
|
893
884
|
}
|
|
894
|
-
|
|
895
|
-
)
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
}
|
|
910
|
-
)
|
|
885
|
+
getUniformSize(type) {
|
|
886
|
+
switch (type) {
|
|
887
|
+
case "float":
|
|
888
|
+
return 1;
|
|
889
|
+
case "vec2":
|
|
890
|
+
return 2;
|
|
891
|
+
case "vec3":
|
|
892
|
+
return 3;
|
|
893
|
+
case "vec4":
|
|
894
|
+
return 4;
|
|
895
|
+
case "mat3":
|
|
896
|
+
return 9;
|
|
897
|
+
case "mat4":
|
|
898
|
+
return 16;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
getIndexType(gl, index) {
|
|
902
|
+
const array = index.array;
|
|
903
|
+
if (array instanceof Uint16Array) return gl.UNSIGNED_SHORT;
|
|
904
|
+
if (array instanceof Uint32Array) return gl.UNSIGNED_INT;
|
|
905
|
+
return gl.UNSIGNED_BYTE;
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
/** Whether to use WebGL_multi_draw extension or less performant fallback (Firefox only) */
|
|
909
|
+
__publicField(_BatchedMesh, "useMultiDraw", true);
|
|
910
|
+
let BatchedMesh = _BatchedMesh;
|
|
911
911
|
const sharedParameters = {
|
|
912
912
|
side: DoubleSide,
|
|
913
913
|
transparent: true,
|
|
914
|
-
|
|
914
|
+
forceSinglePass: true,
|
|
915
|
+
depthFunc: AlwaysDepth
|
|
915
916
|
};
|
|
916
917
|
class MaterialSystem {
|
|
917
918
|
constructor() {
|
|
@@ -950,7 +951,6 @@ class MaterialSystem {
|
|
|
950
951
|
uniforms["resolution"].value.set(this.viewport.z, this.viewport.w);
|
|
951
952
|
}
|
|
952
953
|
};
|
|
953
|
-
this.addPolygonOffset(material);
|
|
954
954
|
addDimToMaterial(material);
|
|
955
955
|
return material;
|
|
956
956
|
}
|
|
@@ -969,7 +969,6 @@ class MaterialSystem {
|
|
|
969
969
|
color: params.color,
|
|
970
970
|
opacity: params.opacity
|
|
971
971
|
});
|
|
972
|
-
this.addPolygonOffset(material);
|
|
973
972
|
addDimToMaterial(material);
|
|
974
973
|
return material;
|
|
975
974
|
}
|
|
@@ -993,7 +992,6 @@ class MaterialSystem {
|
|
|
993
992
|
);
|
|
994
993
|
};
|
|
995
994
|
}
|
|
996
|
-
this.addPolygonOffset(material);
|
|
997
995
|
addDimToMaterial(material);
|
|
998
996
|
return material;
|
|
999
997
|
}
|
|
@@ -1014,14 +1012,10 @@ class MaterialSystem {
|
|
|
1014
1012
|
`
|
|
1015
1013
|
);
|
|
1016
1014
|
};
|
|
1017
|
-
this.addPolygonOffset(this.backgroundMaterial);
|
|
1018
1015
|
addDimToMaterial(this.backgroundMaterial);
|
|
1019
1016
|
}
|
|
1020
1017
|
return this.backgroundMaterial;
|
|
1021
1018
|
}
|
|
1022
|
-
addPolygonOffset(material) {
|
|
1023
|
-
return;
|
|
1024
|
-
}
|
|
1025
1019
|
}
|
|
1026
1020
|
function isShapeDef(def) {
|
|
1027
1021
|
return def.shape !== void 0;
|
|
@@ -1053,6 +1047,31 @@ function isLineLayer(layer) {
|
|
|
1053
1047
|
function isLayerLayer(layer) {
|
|
1054
1048
|
return layer.children[0] && isLayerDef(layer.children[0]);
|
|
1055
1049
|
}
|
|
1050
|
+
function groupBy(list, keyGetter) {
|
|
1051
|
+
const map = /* @__PURE__ */ new Map();
|
|
1052
|
+
list.forEach((item) => {
|
|
1053
|
+
const key = keyGetter(item);
|
|
1054
|
+
const collection = map.get(key);
|
|
1055
|
+
if (!collection) {
|
|
1056
|
+
map.set(key, [item]);
|
|
1057
|
+
} else {
|
|
1058
|
+
collection.push(item);
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
return map;
|
|
1062
|
+
}
|
|
1063
|
+
function partition(list, pred) {
|
|
1064
|
+
const truthy = [];
|
|
1065
|
+
const falsy = [];
|
|
1066
|
+
for (const item of list) {
|
|
1067
|
+
if (pred(item)) {
|
|
1068
|
+
truthy.push(item);
|
|
1069
|
+
} else {
|
|
1070
|
+
falsy.push(item);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
return [truthy, falsy];
|
|
1074
|
+
}
|
|
1056
1075
|
const INTERACTIVE_LAYER = 1;
|
|
1057
1076
|
function setInteractive(object, isInteractive) {
|
|
1058
1077
|
if (isInteractive) object.layers.enable(INTERACTIVE_LAYER);
|
|
@@ -1063,19 +1082,6 @@ function isVisible(object) {
|
|
|
1063
1082
|
if (!object.visible) return false;
|
|
1064
1083
|
return [...traverseAncestorsGenerator(object)].every((obj) => obj.visible);
|
|
1065
1084
|
}
|
|
1066
|
-
function printTree(object, fullName = false) {
|
|
1067
|
-
object.traverse((obj) => {
|
|
1068
|
-
let s = "";
|
|
1069
|
-
let obj2 = obj;
|
|
1070
|
-
while (obj2 !== object) {
|
|
1071
|
-
s = "|___ " + s;
|
|
1072
|
-
obj2 = (obj2 == null ? void 0 : obj2.parent) ?? null;
|
|
1073
|
-
}
|
|
1074
|
-
const renderOrder = obj.isGroup ? "" : `, RO: ${obj.renderOrder}`;
|
|
1075
|
-
const name = fullName ? obj.name : obj.name.split(":").at(-1);
|
|
1076
|
-
console.log(`${s}${name}<${obj.type}>${renderOrder}`);
|
|
1077
|
-
});
|
|
1078
|
-
}
|
|
1079
1085
|
class RenderableSystem {
|
|
1080
1086
|
/**
|
|
1081
1087
|
* @param type readable name of the system's type for debugging
|
|
@@ -1126,6 +1132,15 @@ class RenderableSystem {
|
|
|
1126
1132
|
const shapeDef = this.getDefsByObject(mesh)[batchId];
|
|
1127
1133
|
return shapeDef;
|
|
1128
1134
|
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Dispose all objects and clear the mappings
|
|
1137
|
+
*/
|
|
1138
|
+
dispose() {
|
|
1139
|
+
for (const object of this.getAllObjects()) {
|
|
1140
|
+
this.disposeObject(object);
|
|
1141
|
+
}
|
|
1142
|
+
this.clearMappings();
|
|
1143
|
+
}
|
|
1129
1144
|
/**
|
|
1130
1145
|
* Protected implementation method that subclasses can override.
|
|
1131
1146
|
* This ensures logging always happens even when the method is overridden.
|
|
@@ -1298,10 +1313,10 @@ class ImageSystem extends RenderableSystem {
|
|
|
1298
1313
|
/** Textures memory limit in megabytes */
|
|
1299
1314
|
__publicField(this, "memoryLimitMb");
|
|
1300
1315
|
__publicField(this, "packer");
|
|
1301
|
-
__publicField(this, "
|
|
1302
|
-
__publicField(this, "
|
|
1303
|
-
__publicField(this, "
|
|
1304
|
-
__publicField(this, "
|
|
1316
|
+
__publicField(this, "globalTranslationMatrix", new Matrix4());
|
|
1317
|
+
__publicField(this, "originTranslationMatrix", new Matrix4());
|
|
1318
|
+
__publicField(this, "rotationMatrix", new Matrix4());
|
|
1319
|
+
__publicField(this, "scaleMatrix", new Matrix4());
|
|
1305
1320
|
this.materialSystem = materialSystem;
|
|
1306
1321
|
const atlasTextureSize = renderer.context.capabilities.maxTextureSize;
|
|
1307
1322
|
console.log(`Max texture size: ${atlasTextureSize}`);
|
|
@@ -1401,17 +1416,13 @@ class ImageSystem extends RenderableSystem {
|
|
|
1401
1416
|
}
|
|
1402
1417
|
updateDefImpl(imageDef, mesh, instanceIds) {
|
|
1403
1418
|
const bounds = imageDef.bounds;
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
this.scale.set(bounds.size.width, bounds.size.height, 1);
|
|
1412
|
-
this.rotation.setFromAxisAngle(new Vector3(0, 0, 1), bounds.rotation);
|
|
1413
|
-
this.matrix.compose(this.position, this.rotation, this.scale);
|
|
1414
|
-
mesh.setMatrixAt(instanceIds[0], this.matrix);
|
|
1419
|
+
const origin2 = imageDef.origin ?? [0.5, 0.5];
|
|
1420
|
+
this.originTranslationMatrix.makeTranslation(0.5 - origin2[0], 0.5 - origin2[1], 0);
|
|
1421
|
+
this.globalTranslationMatrix.makeTranslation(bounds.center.x, bounds.center.y, 0);
|
|
1422
|
+
this.scaleMatrix.makeScale(bounds.size.width, bounds.size.height, 1);
|
|
1423
|
+
this.rotationMatrix.makeRotationZ(bounds.rotation);
|
|
1424
|
+
const matrix = this.originTranslationMatrix.premultiply(this.scaleMatrix).premultiply(this.rotationMatrix).premultiply(this.globalTranslationMatrix);
|
|
1425
|
+
mesh.setMatrixAt(instanceIds[0], matrix);
|
|
1415
1426
|
}
|
|
1416
1427
|
packImages(images) {
|
|
1417
1428
|
this.packer.reset();
|
|
@@ -1545,7 +1556,15 @@ class LineSystem extends RenderableSystem {
|
|
|
1545
1556
|
}
|
|
1546
1557
|
}
|
|
1547
1558
|
function createVector2(vector2) {
|
|
1548
|
-
|
|
1559
|
+
if (vector2 instanceof Vector2) return vector2;
|
|
1560
|
+
if (Array.isArray(vector2)) return new Vector2(vector2[0], vector2[1]);
|
|
1561
|
+
return new Vector2(vector2.x, vector2.y);
|
|
1562
|
+
}
|
|
1563
|
+
function createVector3(vector3) {
|
|
1564
|
+
if (vector3 instanceof Vector2) return new Vector3(vector3.x, vector3.y, 0);
|
|
1565
|
+
if (vector3 instanceof Vector3) return vector3;
|
|
1566
|
+
if (Array.isArray(vector3)) return new Vector3(vector3[0], vector3[1], vector3[2] ?? 0);
|
|
1567
|
+
return new Vector3(vector3.x, vector3.y, "z" in vector3 ? vector3.z : 0);
|
|
1549
1568
|
}
|
|
1550
1569
|
class Rect {
|
|
1551
1570
|
/**
|
|
@@ -1613,7 +1632,7 @@ class Polygon {
|
|
|
1613
1632
|
__publicField(this, "vertices");
|
|
1614
1633
|
/** Array of polygon indices. Each index is a triplet of vertex indices forming a triangle. */
|
|
1615
1634
|
__publicField(this, "indices");
|
|
1616
|
-
this.vertices = vertices.map(
|
|
1635
|
+
this.vertices = vertices.map(createVector3);
|
|
1617
1636
|
this.indices = indices;
|
|
1618
1637
|
}
|
|
1619
1638
|
/**
|
|
@@ -1652,41 +1671,19 @@ class Polygon {
|
|
|
1652
1671
|
}
|
|
1653
1672
|
/**
|
|
1654
1673
|
* Rotates all vertices of the polygon around the given center.
|
|
1655
|
-
* @param rotation Rotation angle in radians. Positive values rotate clockwise.
|
|
1656
|
-
* @param center Center of the rotation.
|
|
1657
|
-
* @returns this {@link Polygon} instance
|
|
1658
|
-
*/
|
|
1659
|
-
rotate(rotation, center) {
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
const map = /* @__PURE__ */ new Map();
|
|
1668
|
-
list.forEach((item) => {
|
|
1669
|
-
const key = keyGetter(item);
|
|
1670
|
-
const collection = map.get(key);
|
|
1671
|
-
if (!collection) {
|
|
1672
|
-
map.set(key, [item]);
|
|
1673
|
-
} else {
|
|
1674
|
-
collection.push(item);
|
|
1675
|
-
}
|
|
1676
|
-
});
|
|
1677
|
-
return map;
|
|
1678
|
-
}
|
|
1679
|
-
function partition(list, pred) {
|
|
1680
|
-
const truthy = [];
|
|
1681
|
-
const falsy = [];
|
|
1682
|
-
for (const item of list) {
|
|
1683
|
-
if (pred(item)) {
|
|
1684
|
-
truthy.push(item);
|
|
1685
|
-
} else {
|
|
1686
|
-
falsy.push(item);
|
|
1687
|
-
}
|
|
1674
|
+
* @param rotation Rotation angle in radians. Positive values rotate clockwise.
|
|
1675
|
+
* @param center Center of the rotation.
|
|
1676
|
+
* @returns this {@link Polygon} instance
|
|
1677
|
+
*/
|
|
1678
|
+
rotate(rotation, center) {
|
|
1679
|
+
const tempVec2 = new Vector2();
|
|
1680
|
+
this.vertices.forEach((vertex) => {
|
|
1681
|
+
tempVec2.set(vertex.x, vertex.y);
|
|
1682
|
+
tempVec2.rotateAround(center, rotation);
|
|
1683
|
+
vertex.set(tempVec2.x, tempVec2.y, vertex.z);
|
|
1684
|
+
});
|
|
1685
|
+
return this;
|
|
1688
1686
|
}
|
|
1689
|
-
return [truthy, falsy];
|
|
1690
1687
|
}
|
|
1691
1688
|
class MeshSystem extends RenderableSystem {
|
|
1692
1689
|
/**
|
|
@@ -1725,6 +1722,12 @@ class MeshSystem extends RenderableSystem {
|
|
|
1725
1722
|
opaqueMesh.name = "opaque";
|
|
1726
1723
|
group.add(opaqueMesh);
|
|
1727
1724
|
}
|
|
1725
|
+
if (layer.mode === "3d") {
|
|
1726
|
+
group.children.filter((child) => child instanceof Mesh).forEach((mesh) => {
|
|
1727
|
+
const material = mesh.material;
|
|
1728
|
+
material.depthFunc = LessEqualDepth;
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1728
1731
|
return group;
|
|
1729
1732
|
}
|
|
1730
1733
|
updateDefImpl(shapeDef, mesh, instanceIds) {
|
|
@@ -1741,12 +1744,10 @@ class MeshSystem extends RenderableSystem {
|
|
|
1741
1744
|
let rectGeometry = void 0;
|
|
1742
1745
|
const shapeDefToGeometry = /* @__PURE__ */ new Map();
|
|
1743
1746
|
for (const shapeDef of shapes) {
|
|
1744
|
-
if (shapeDef.shape instanceof Rect) {
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
rectGeometry.deleteAttribute("uv");
|
|
1749
|
-
}
|
|
1747
|
+
if (shapeDef.shape instanceof Rect && !rectGeometry) {
|
|
1748
|
+
rectGeometry = new PlaneGeometry(1, 1);
|
|
1749
|
+
rectGeometry.deleteAttribute("normal");
|
|
1750
|
+
rectGeometry.deleteAttribute("uv");
|
|
1750
1751
|
vertexCount += rectGeometry.getAttribute("position").count;
|
|
1751
1752
|
indexCount += ((_a2 = rectGeometry.index) == null ? void 0 : _a2.count) ?? 0;
|
|
1752
1753
|
} else if (shapeDef.shape instanceof Polygon) {
|
|
@@ -1758,6 +1759,7 @@ class MeshSystem extends RenderableSystem {
|
|
|
1758
1759
|
}
|
|
1759
1760
|
const material = this.materialSystem.createColorMaterial({ opacity });
|
|
1760
1761
|
const batchedMesh = new BatchedMesh(shapes.length, vertexCount, indexCount, material);
|
|
1762
|
+
const rectGeometryId = rectGeometry ? batchedMesh.addGeometry(rectGeometry) : void 0;
|
|
1761
1763
|
batchedMesh.setCustomSort((list) => this.sortInstances(batchedMesh, list));
|
|
1762
1764
|
const position = new Vector3();
|
|
1763
1765
|
const rotation = new Quaternion();
|
|
@@ -1765,8 +1767,7 @@ class MeshSystem extends RenderableSystem {
|
|
|
1765
1767
|
const matrix = new Matrix4();
|
|
1766
1768
|
for (const shapeDef of shapes) {
|
|
1767
1769
|
let instanceId = void 0;
|
|
1768
|
-
if (shapeDef.shape instanceof Rect) {
|
|
1769
|
-
const rectGeometryId = rectGeometry ? batchedMesh.addGeometry(rectGeometry) : void 0;
|
|
1770
|
+
if (shapeDef.shape instanceof Rect && rectGeometryId !== void 0) {
|
|
1770
1771
|
instanceId = batchedMesh.addInstance(rectGeometryId);
|
|
1771
1772
|
position.set(shapeDef.shape.center.x, shapeDef.shape.center.y, 0);
|
|
1772
1773
|
rotation.setFromAxisAngle(new Vector3(0, 0, 1), shapeDef.shape.rotation ?? 0);
|
|
@@ -1811,6 +1812,7 @@ class TextSystem extends RenderableSystem {
|
|
|
1811
1812
|
__publicField(this, "initialTextScale", new Vector2(1, -1));
|
|
1812
1813
|
__publicField(this, "textColor", new Color());
|
|
1813
1814
|
__publicField(this, "pendingUpdates", /* @__PURE__ */ new Map());
|
|
1815
|
+
__publicField(this, "sdfAtlases", /* @__PURE__ */ new Set());
|
|
1814
1816
|
__publicField(this, "alignmentOffset", new Vector2());
|
|
1815
1817
|
__publicField(this, "alignmentDirection", new Vector2());
|
|
1816
1818
|
__publicField(this, "localPosition", new Vector2());
|
|
@@ -1820,6 +1822,10 @@ class TextSystem extends RenderableSystem {
|
|
|
1820
1822
|
__publicField(this, "localToMax", new Vector2());
|
|
1821
1823
|
this.materialSystem = materialSystem;
|
|
1822
1824
|
}
|
|
1825
|
+
dispose() {
|
|
1826
|
+
super.dispose();
|
|
1827
|
+
this.sdfAtlases.forEach((texture) => texture.dispose());
|
|
1828
|
+
}
|
|
1823
1829
|
buildLayer(layer) {
|
|
1824
1830
|
const group = new Group();
|
|
1825
1831
|
const batchedText = this.buildBatchedText(layer);
|
|
@@ -1897,7 +1903,12 @@ class TextSystem extends RenderableSystem {
|
|
|
1897
1903
|
for (const { textDef, instanceIds } of mappingData) {
|
|
1898
1904
|
this.registerDefObject(textDef, batchedText, instanceIds);
|
|
1899
1905
|
}
|
|
1900
|
-
batchedText.addEventListener("synccomplete", () =>
|
|
1906
|
+
batchedText.addEventListener("synccomplete", () => {
|
|
1907
|
+
var _a2;
|
|
1908
|
+
const sdfTexture = (_a2 = batchedText.textRenderInfo) == null ? void 0 : _a2.sdfTexture;
|
|
1909
|
+
if (sdfTexture) this.sdfAtlases.add(sdfTexture);
|
|
1910
|
+
this.renderer.update();
|
|
1911
|
+
});
|
|
1901
1912
|
return batchedText;
|
|
1902
1913
|
}
|
|
1903
1914
|
// TODO: Simplify
|
|
@@ -1990,6 +2001,7 @@ class LayerSystem {
|
|
|
1990
2001
|
* @param renderer {@link Renderer}
|
|
1991
2002
|
*/
|
|
1992
2003
|
constructor(renderer) {
|
|
2004
|
+
__publicField(this, "backgroundMesh");
|
|
1993
2005
|
__publicField(this, "materialSystem");
|
|
1994
2006
|
__publicField(this, "meshSystem");
|
|
1995
2007
|
__publicField(this, "imageSystem");
|
|
@@ -2029,6 +2041,7 @@ class LayerSystem {
|
|
|
2029
2041
|
}
|
|
2030
2042
|
/**
|
|
2031
2043
|
* Update the given defs immediately, or queue them for update if update buffering is enabled.
|
|
2044
|
+
* NOTE: Currently update buffering is disabled, as observed performance gains are negligible. Need to revisit this.
|
|
2032
2045
|
* @param defs {@link RenderableDef} array
|
|
2033
2046
|
*/
|
|
2034
2047
|
updateDefs(defs) {
|
|
@@ -2089,6 +2102,18 @@ class LayerSystem {
|
|
|
2089
2102
|
}
|
|
2090
2103
|
return rootGroup;
|
|
2091
2104
|
}
|
|
2105
|
+
/**
|
|
2106
|
+
* Dispose all objects and clear the mappings
|
|
2107
|
+
*/
|
|
2108
|
+
disposeScene() {
|
|
2109
|
+
var _a2, _b, _c;
|
|
2110
|
+
for (const system of this.systems) {
|
|
2111
|
+
system.dispose();
|
|
2112
|
+
}
|
|
2113
|
+
(_a2 = this.backgroundMesh) == null ? void 0 : _a2.geometry.dispose();
|
|
2114
|
+
(_c = (_b = this.backgroundMesh) == null ? void 0 : _b.material) == null ? void 0 : _c.dispose();
|
|
2115
|
+
this.backgroundMesh = void 0;
|
|
2116
|
+
}
|
|
2092
2117
|
updateDef(def) {
|
|
2093
2118
|
if (isShapeDef(def)) this.meshSystem.updateDef(def);
|
|
2094
2119
|
else if (isImageDef(def)) this.imageSystem.updateDef(def);
|
|
@@ -2153,6 +2178,7 @@ class LayerSystem {
|
|
|
2153
2178
|
backgroundMesh.frustumCulled = false;
|
|
2154
2179
|
backgroundMesh.renderOrder = 0;
|
|
2155
2180
|
backgroundMesh.name = "background";
|
|
2181
|
+
this.backgroundMesh = backgroundMesh;
|
|
2156
2182
|
return backgroundMesh;
|
|
2157
2183
|
}
|
|
2158
2184
|
initRenderOrder(rootLayer) {
|
|
@@ -2170,6 +2196,8 @@ class LayerSystem {
|
|
|
2170
2196
|
this.layerDefRenderOrder.push(layer);
|
|
2171
2197
|
}
|
|
2172
2198
|
}
|
|
2199
|
+
const [threeDLayers, twoDLayers] = partition(this.layerDefRenderOrder, (layer) => layer.mode === "3d");
|
|
2200
|
+
this.layerDefRenderOrder = [...twoDLayers, ...threeDLayers];
|
|
2173
2201
|
}
|
|
2174
2202
|
getFullLayerName(layerDef) {
|
|
2175
2203
|
let fullName = layerDef.name;
|
|
@@ -2181,6 +2209,19 @@ class LayerSystem {
|
|
|
2181
2209
|
return fullName;
|
|
2182
2210
|
}
|
|
2183
2211
|
}
|
|
2212
|
+
function printTree(object, fullName = false) {
|
|
2213
|
+
object.traverse((obj) => {
|
|
2214
|
+
let s = "";
|
|
2215
|
+
let obj2 = obj;
|
|
2216
|
+
while (obj2 !== object) {
|
|
2217
|
+
s = "|___ " + s;
|
|
2218
|
+
obj2 = (obj2 == null ? void 0 : obj2.parent) ?? null;
|
|
2219
|
+
}
|
|
2220
|
+
const renderOrder = obj.isGroup ? "" : `, RO: ${obj.renderOrder}`;
|
|
2221
|
+
const name = fullName ? obj.name : obj.name.split(":").at(-1);
|
|
2222
|
+
console.log(`${s}${name}<${obj.type}>${renderOrder}`);
|
|
2223
|
+
});
|
|
2224
|
+
}
|
|
2184
2225
|
/*!
|
|
2185
2226
|
* camera-controls
|
|
2186
2227
|
* https://github.com/yomotsu/camera-controls
|
|
@@ -4717,6 +4758,20 @@ class CameraController extends CameraControls {
|
|
|
4717
4758
|
constructor(camera, renderer) {
|
|
4718
4759
|
super(camera);
|
|
4719
4760
|
this.renderer = renderer;
|
|
4761
|
+
this.dollyToCursor = true;
|
|
4762
|
+
this.draggingSmoothTime = 0;
|
|
4763
|
+
void this.rotatePolarTo(0, false);
|
|
4764
|
+
this.mouseButtons = {
|
|
4765
|
+
left: CameraController.ACTION.NONE,
|
|
4766
|
+
middle: CameraController.ACTION.NONE,
|
|
4767
|
+
right: CameraController.ACTION.NONE,
|
|
4768
|
+
wheel: CameraController.ACTION.NONE
|
|
4769
|
+
};
|
|
4770
|
+
this.touches = {
|
|
4771
|
+
one: CameraController.ACTION.NONE,
|
|
4772
|
+
two: CameraController.ACTION.NONE,
|
|
4773
|
+
three: CameraController.ACTION.NONE
|
|
4774
|
+
};
|
|
4720
4775
|
}
|
|
4721
4776
|
update(delta) {
|
|
4722
4777
|
var _a2;
|
|
@@ -4737,31 +4792,30 @@ class CameraSystem {
|
|
|
4737
4792
|
* @param renderer {@link Renderer} instance
|
|
4738
4793
|
*/
|
|
4739
4794
|
constructor(renderer) {
|
|
4740
|
-
/**
|
|
4741
|
-
__publicField(this, "
|
|
4795
|
+
/** {@link PerspectiveCamera} instance. Used to render the scene in internal mode. */
|
|
4796
|
+
__publicField(this, "camera");
|
|
4742
4797
|
/** {@link CameraController} instance. Used to smoothly animate the camera. */
|
|
4743
4798
|
__publicField(this, "controller");
|
|
4744
|
-
|
|
4745
|
-
|
|
4799
|
+
/**
|
|
4800
|
+
* Cached previous viewport height used to preserve zoom across resizes.
|
|
4801
|
+
* Note: we intentionally keep this separate from the derived identity distance.
|
|
4802
|
+
*/
|
|
4803
|
+
__publicField(this, "prevViewportHeightPx");
|
|
4804
|
+
/** [min, max] zoom factors */
|
|
4746
4805
|
__publicField(this, "zoomBounds");
|
|
4806
|
+
/** Default FOV for the camera. Taken from Mapbox GL JS. */
|
|
4807
|
+
__publicField(this, "defaultFov", 36.87);
|
|
4747
4808
|
this.renderer = renderer;
|
|
4748
|
-
const
|
|
4749
|
-
this.
|
|
4809
|
+
const h = renderer.size[1];
|
|
4810
|
+
this.prevViewportHeightPx = h;
|
|
4811
|
+
this.camera = new PerspectiveCamera(this.defaultFov);
|
|
4750
4812
|
this.camera.up.set(0, 0, -1);
|
|
4751
|
-
this.
|
|
4752
|
-
this.camera.position.z = this.zoomIdentityDistance;
|
|
4753
|
-
this.controller = new CameraController(this.camera, renderer);
|
|
4754
|
-
this.controller.polarAngle = 0;
|
|
4813
|
+
this.controller = new CameraController(this.camera, this.renderer);
|
|
4755
4814
|
this.controller.distance = this.zoomIdentityDistance;
|
|
4756
4815
|
}
|
|
4757
|
-
/** Current camera instance. */
|
|
4758
|
-
get currentCamera() {
|
|
4759
|
-
return this.externalCamera ?? this.camera;
|
|
4760
|
-
}
|
|
4761
4816
|
/** Current camera zoom factor. */
|
|
4762
4817
|
get zoomFactor() {
|
|
4763
|
-
|
|
4764
|
-
return distance ? this.zoomIdentityDistance / distance : 1;
|
|
4818
|
+
return this.zoomFactorForHeight(this.renderer.size[1]);
|
|
4765
4819
|
}
|
|
4766
4820
|
/**
|
|
4767
4821
|
* Calculates the camera distance from the scene's plane for a given zoom factor.
|
|
@@ -4769,7 +4823,7 @@ class CameraSystem {
|
|
|
4769
4823
|
* @returns Corresponding camera distance on the Z axis
|
|
4770
4824
|
*/
|
|
4771
4825
|
zoomFactorToDistance(zoomFactor) {
|
|
4772
|
-
return
|
|
4826
|
+
return this.zoomIdentityDistance / zoomFactor;
|
|
4773
4827
|
}
|
|
4774
4828
|
/**
|
|
4775
4829
|
* Initializes the camera with the given zoom bounds.
|
|
@@ -4783,41 +4837,188 @@ class CameraSystem {
|
|
|
4783
4837
|
updateCamera() {
|
|
4784
4838
|
if (!this.zoomBounds) return;
|
|
4785
4839
|
const [w, h] = this.renderer.size;
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
const
|
|
4789
|
-
const
|
|
4840
|
+
if (w <= 0 || h <= 0) return;
|
|
4841
|
+
const zoomFactor = this.zoomFactorForHeight(this.prevViewportHeightPx);
|
|
4842
|
+
const newZoomIdentity = this.zoomIdentityDistanceForHeight(h);
|
|
4843
|
+
const maxDistance = Math.abs(newZoomIdentity / this.zoomBounds[0]);
|
|
4844
|
+
const minDistance = Math.abs(newZoomIdentity / this.zoomBounds[1]);
|
|
4790
4845
|
this.camera.aspect = w / (h || 1);
|
|
4791
|
-
this.
|
|
4846
|
+
this.camera.near = 0.01;
|
|
4847
|
+
this.camera.far = Math.max(maxDistance, this.camera.near) * 2;
|
|
4792
4848
|
this.camera.updateProjectionMatrix();
|
|
4793
|
-
this.syncController(minDistance, maxDistance, zoomFactor);
|
|
4794
|
-
}
|
|
4795
|
-
computeCameraClipPlanes(minDistance, maxDistance, nearSafetyFactor = 0.5, farSafetyFactor = 1.5) {
|
|
4796
|
-
const fov = this.camera.fov * DEG2RAD$1;
|
|
4797
|
-
const aspect = this.camera.aspect;
|
|
4798
|
-
const maxPolarAngle = 85 * DEG2RAD$1;
|
|
4799
|
-
const halfFovY = fov / 2;
|
|
4800
|
-
const halfFovX = Math.atan(Math.tan(halfFovY) * aspect);
|
|
4801
|
-
const diagonalFov = 2 * Math.atan(Math.sqrt(Math.tan(halfFovX) ** 2 + Math.tan(halfFovY) ** 2));
|
|
4802
|
-
const minHeight = minDistance * Math.cos(maxPolarAngle);
|
|
4803
|
-
const near = Math.max(0.1, minHeight * nearSafetyFactor);
|
|
4804
|
-
const criticalHeight = minHeight;
|
|
4805
|
-
const horizontalDistToOrbit = minDistance * Math.sin(maxPolarAngle);
|
|
4806
|
-
const distToOrbit = minDistance;
|
|
4807
|
-
const visibleRadiusAtOrbit = distToOrbit * Math.tan(diagonalFov / 2);
|
|
4808
|
-
const planeExtent = Math.max(maxDistance, visibleRadiusAtOrbit);
|
|
4809
|
-
const horizontalDistToFarEdge = horizontalDistToOrbit + planeExtent;
|
|
4810
|
-
const maxViewDistance = Math.sqrt(criticalHeight ** 2 + horizontalDistToFarEdge ** 2);
|
|
4811
|
-
const far = maxViewDistance * farSafetyFactor;
|
|
4812
|
-
this.camera.near = near;
|
|
4813
|
-
this.camera.far = far;
|
|
4814
|
-
if (this.renderer.debugLog) console.log("camera clip planes", near, far);
|
|
4815
|
-
}
|
|
4816
|
-
syncController(minDistance, maxDistance, zoomFactor) {
|
|
4817
|
-
if (this.renderer.debugLog) console.log("syncController", minDistance, maxDistance, zoomFactor);
|
|
4818
4849
|
this.controller.minDistance = minDistance;
|
|
4819
4850
|
this.controller.maxDistance = maxDistance;
|
|
4820
4851
|
void this.controller.dollyTo(this.zoomFactorToDistance(zoomFactor), false);
|
|
4852
|
+
this.prevViewportHeightPx = h;
|
|
4853
|
+
}
|
|
4854
|
+
/**
|
|
4855
|
+
* Distance from the scene plane corresponding to zoomFactor = 1 for the current viewport height.
|
|
4856
|
+
* Derived from camera FOV and renderer height (in pixels).
|
|
4857
|
+
*/
|
|
4858
|
+
get zoomIdentityDistance() {
|
|
4859
|
+
return this.zoomIdentityDistanceForHeight(this.renderer.size[1]);
|
|
4860
|
+
}
|
|
4861
|
+
/**
|
|
4862
|
+
* Calculates the zoom identity distance for a given viewport height.
|
|
4863
|
+
* @param viewportHeightPx Renderer height in pixels
|
|
4864
|
+
* @returns Zoom identity distance
|
|
4865
|
+
*/
|
|
4866
|
+
zoomIdentityDistanceForHeight(viewportHeightPx) {
|
|
4867
|
+
if (viewportHeightPx <= 0) return 0;
|
|
4868
|
+
return viewportHeightPx * 0.5 / Math.tan(this.camera.fov * DEG2RAD$1 / 2);
|
|
4869
|
+
}
|
|
4870
|
+
/**
|
|
4871
|
+
* Calculates the zoom factor for a given viewport height.
|
|
4872
|
+
* @param viewportHeightPx Renderer height in pixels
|
|
4873
|
+
* @returns Zoom factor
|
|
4874
|
+
*/
|
|
4875
|
+
zoomFactorForHeight(viewportHeightPx) {
|
|
4876
|
+
const zid = this.zoomIdentityDistanceForHeight(viewportHeightPx);
|
|
4877
|
+
if (zid === 0) return 1;
|
|
4878
|
+
return zid / (this.controller.distance || zid);
|
|
4879
|
+
}
|
|
4880
|
+
}
|
|
4881
|
+
class ExternalSystem {
|
|
4882
|
+
/**
|
|
4883
|
+
* @param pickingSystem {@link PickingSystem} instance
|
|
4884
|
+
*/
|
|
4885
|
+
constructor(pickingSystem) {
|
|
4886
|
+
/** External camera instance */
|
|
4887
|
+
__publicField(this, "camera", new Camera());
|
|
4888
|
+
__publicField(this, "staticTransformMatrix", new Matrix4());
|
|
4889
|
+
__publicField(this, "intersectionPoint", new Vector3());
|
|
4890
|
+
/**
|
|
4891
|
+
* Scratch NDC coordinate used by external ptScale. Kept as a field to avoid allocating a Vector2 each frame.
|
|
4892
|
+
* (This is always screen center: NDC (0,0))
|
|
4893
|
+
*/
|
|
4894
|
+
__publicField(this, "ndcCenter", new Vector2(0, 0));
|
|
4895
|
+
/**
|
|
4896
|
+
* Scratch clip-space vector used when projecting SVG points through the external camera matrix.
|
|
4897
|
+
* Kept as a field to avoid allocating a Vector4 each frame.
|
|
4898
|
+
*/
|
|
4899
|
+
__publicField(this, "clipPoint", new Vector4());
|
|
4900
|
+
/**
|
|
4901
|
+
* Scratch pixel points used by external ptScale estimation.
|
|
4902
|
+
* p0 is the screen center in drawing-buffer pixels; p1/p2 are projected offsets from the anchor point.
|
|
4903
|
+
*/
|
|
4904
|
+
__publicField(this, "px0", new Vector2());
|
|
4905
|
+
__publicField(this, "px1", new Vector2());
|
|
4906
|
+
__publicField(this, "px2", new Vector2());
|
|
4907
|
+
this.pickingSystem = pickingSystem;
|
|
4908
|
+
}
|
|
4909
|
+
/**
|
|
4910
|
+
* Set static part of an svg -> px transform matrix
|
|
4911
|
+
* @param staticTransformMatrix static transform matrix to apply to the scene
|
|
4912
|
+
*/
|
|
4913
|
+
setStaticTransform(staticTransformMatrix) {
|
|
4914
|
+
if (!this.validateMatrix(staticTransformMatrix, "setStaticTransform")) return;
|
|
4915
|
+
this.staticTransformMatrix.fromArray(staticTransformMatrix);
|
|
4916
|
+
}
|
|
4917
|
+
/**
|
|
4918
|
+
* Set dynamic part of an svg -> px transform matrix. Should be called every frame.
|
|
4919
|
+
* @param dynamicTransformMatrix dynamic transform matrix (changes every frame)
|
|
4920
|
+
*/
|
|
4921
|
+
setDynamicTransform(dynamicTransformMatrix) {
|
|
4922
|
+
if (!this.validateMatrix(dynamicTransformMatrix, "setDynamicTransform")) return;
|
|
4923
|
+
this.camera.projectionMatrix.fromArray(dynamicTransformMatrix).multiply(this.staticTransformMatrix);
|
|
4924
|
+
this.camera.projectionMatrixInverse.copy(this.camera.projectionMatrix).invert();
|
|
4925
|
+
}
|
|
4926
|
+
/**
|
|
4927
|
+
* Estimates pixel→SVG scale for external context rendering (e.g. Mapbox).
|
|
4928
|
+
*
|
|
4929
|
+
* In external mode we don't own a camera rig/controller, so we can't derive scale from "camera distance".
|
|
4930
|
+
* Instead, we:
|
|
4931
|
+
* 1) Find the point on the SVG plane that is currently under the screen center.
|
|
4932
|
+
* 2) Measure how much the screen position changes when moving 1 SVG unit in X/Y around that point.
|
|
4933
|
+
* 3) Convert that local plane→screen mapping into a single "zoom-like" scalar that is stable under tilt+rotate.
|
|
4934
|
+
*
|
|
4935
|
+
* This matches internal mode semantics better because the internal orbit target is also kept under screen center.
|
|
4936
|
+
* @param viewportSize Size of the viewport in drawing-buffer pixels
|
|
4937
|
+
* @returns Pixel-to-SVG scale factor (px → svg units)
|
|
4938
|
+
*/
|
|
4939
|
+
pxToSvgScale(viewportSize) {
|
|
4940
|
+
const M = this.camera.projectionMatrix;
|
|
4941
|
+
const [viewportW, viewportH] = viewportSize;
|
|
4942
|
+
if (viewportW <= 0 || viewportH <= 0) return;
|
|
4943
|
+
const intersectionPoint = this.pickingSystem.intersectPlane(this.ndcCenter, this.camera, this.intersectionPoint);
|
|
4944
|
+
if (!intersectionPoint) return;
|
|
4945
|
+
const anchorX = intersectionPoint.x;
|
|
4946
|
+
const anchorY = intersectionPoint.y;
|
|
4947
|
+
const clip = this.clipPoint;
|
|
4948
|
+
const p0 = this.px0.set(viewportW * 0.5, viewportH * 0.5);
|
|
4949
|
+
const p1 = this.px1;
|
|
4950
|
+
const p2 = this.px2;
|
|
4951
|
+
const svgToPixels = (x, y, out) => {
|
|
4952
|
+
clip.set(x, y, 0, 1).applyMatrix4(M);
|
|
4953
|
+
if (clip.w === 0) return false;
|
|
4954
|
+
const ndcX = clip.x / clip.w;
|
|
4955
|
+
const ndcY = clip.y / clip.w;
|
|
4956
|
+
out.set((ndcX + 1) * 0.5 * viewportW, (1 - ndcY) * 0.5 * viewportH);
|
|
4957
|
+
return true;
|
|
4958
|
+
};
|
|
4959
|
+
const svgStep = 1;
|
|
4960
|
+
if (!svgToPixels(anchorX + svgStep, anchorY, p1)) return;
|
|
4961
|
+
if (!svgToPixels(anchorX, anchorY + svgStep, p2)) return;
|
|
4962
|
+
const pxDeltaPerSvgX = p1.sub(p0).divideScalar(svgStep);
|
|
4963
|
+
const pxDeltaPerSvgY = p2.sub(p0).divideScalar(svgStep);
|
|
4964
|
+
const pixelsSqPerSvgX = pxDeltaPerSvgX.dot(pxDeltaPerSvgX);
|
|
4965
|
+
const pixelsSqPerSvgY = pxDeltaPerSvgY.dot(pxDeltaPerSvgY);
|
|
4966
|
+
const pixelsSqCross = pxDeltaPerSvgX.dot(pxDeltaPerSvgY);
|
|
4967
|
+
const sumPixelsSq = pixelsSqPerSvgX + pixelsSqPerSvgY;
|
|
4968
|
+
const areaPixelsSq = pixelsSqPerSvgX * pixelsSqPerSvgY - pixelsSqCross * pixelsSqCross;
|
|
4969
|
+
const maxStretchDiscriminant = Math.max(0, sumPixelsSq * sumPixelsSq - 4 * areaPixelsSq);
|
|
4970
|
+
const maxPixelsSqPerSvg = 0.5 * (sumPixelsSq + Math.sqrt(maxStretchDiscriminant));
|
|
4971
|
+
const pxPerSvg = Math.sqrt(maxPixelsSqPerSvg);
|
|
4972
|
+
if (!Number.isFinite(pxPerSvg) || pxPerSvg <= 0) return;
|
|
4973
|
+
return 1 / pxPerSvg;
|
|
4974
|
+
}
|
|
4975
|
+
validateMatrix(matrix, name) {
|
|
4976
|
+
if (matrix.length !== 16) {
|
|
4977
|
+
console.warn(`[ViewportSystem.${name}]: Matrix must be 16 elements long`);
|
|
4978
|
+
return false;
|
|
4979
|
+
}
|
|
4980
|
+
return true;
|
|
4981
|
+
}
|
|
4982
|
+
}
|
|
4983
|
+
class PickingSystem {
|
|
4984
|
+
/** */
|
|
4985
|
+
constructor() {
|
|
4986
|
+
__publicField(this, "raycaster", new Raycaster());
|
|
4987
|
+
__publicField(this, "ndcPoint", new Vector2());
|
|
4988
|
+
__publicField(this, "viewboxPlane", new Plane(new Vector3(0, 0, 1), 0));
|
|
4989
|
+
this.raycaster.layers.set(INTERACTIVE_LAYER);
|
|
4990
|
+
}
|
|
4991
|
+
/**
|
|
4992
|
+
* Gets the objects intersected by the raycaster.
|
|
4993
|
+
* @param ndcCoords raycast point in NDC (normalized device coordinates)
|
|
4994
|
+
* @param scene {@link Scene} instance
|
|
4995
|
+
* @param camera {@link Camera} instance
|
|
4996
|
+
* @returns Array of {@link Intersection} instances
|
|
4997
|
+
*/
|
|
4998
|
+
getIntersectedObjects(ndcCoords, scene, camera) {
|
|
4999
|
+
this.setRaycasterFromCamera(ndcCoords, camera);
|
|
5000
|
+
const intersections = this.raycaster.intersectObject(scene, true);
|
|
5001
|
+
return intersections.filter((i) => isVisible(i.object));
|
|
5002
|
+
}
|
|
5003
|
+
/**
|
|
5004
|
+
* Intersects the xy-plane with the raycaster.
|
|
5005
|
+
* @param ndcCoords raycast point in NDC (normalized device coordinates
|
|
5006
|
+
* @param camera {@link Camera} instance
|
|
5007
|
+
* @param out Output vector
|
|
5008
|
+
* @returns Intersection point in world space or null if no intersection.
|
|
5009
|
+
*/
|
|
5010
|
+
intersectPlane(ndcCoords, camera, out) {
|
|
5011
|
+
this.setRaycasterFromCamera(ndcCoords, camera);
|
|
5012
|
+
return this.raycaster.ray.intersectPlane(this.viewboxPlane, out) ?? void 0;
|
|
5013
|
+
}
|
|
5014
|
+
setRaycasterFromCamera(ndcCoords, camera) {
|
|
5015
|
+
if (camera.isPerspectiveCamera || camera.isOrthographicCamera) {
|
|
5016
|
+
this.ndcPoint.set(ndcCoords.x, ndcCoords.y);
|
|
5017
|
+
this.raycaster.setFromCamera(this.ndcPoint, camera);
|
|
5018
|
+
} else {
|
|
5019
|
+
this.raycaster.ray.origin.set(0, 0, 0).unproject(camera);
|
|
5020
|
+
this.raycaster.ray.direction.set(ndcCoords.x, ndcCoords.y, 1).unproject(camera).sub(this.raycaster.ray.origin).normalize();
|
|
5021
|
+
}
|
|
4821
5022
|
}
|
|
4822
5023
|
}
|
|
4823
5024
|
class SceneSystem {
|
|
@@ -4827,29 +5028,22 @@ class SceneSystem {
|
|
|
4827
5028
|
constructor(renderer) {
|
|
4828
5029
|
/** {@link Scene} instance */
|
|
4829
5030
|
__publicField(this, "scene");
|
|
4830
|
-
/** World matrix -
|
|
5031
|
+
/** World matrix - model → world transform */
|
|
4831
5032
|
__publicField(this, "worldMatrix", new Matrix4());
|
|
4832
|
-
/** Inverse world matrix -
|
|
5033
|
+
/** Inverse world matrix - world → model transform */
|
|
4833
5034
|
__publicField(this, "inverseWorldMatrix", new Matrix4());
|
|
5035
|
+
__publicField(this, "tempVector3", new Vector3());
|
|
4834
5036
|
__publicField(this, "translationMatrix", new Matrix4());
|
|
4835
5037
|
__publicField(this, "scaleMatrix", new Matrix4());
|
|
4836
|
-
__publicField(this, "scaleVector", new Vector3());
|
|
4837
5038
|
__publicField(this, "visibleRectOffsetMatrix", new Matrix4());
|
|
4838
5039
|
__publicField(this, "viewbox");
|
|
4839
5040
|
this.renderer = renderer;
|
|
4840
5041
|
this.scene = new Scene();
|
|
4841
5042
|
this.scene.matrixAutoUpdate = false;
|
|
4842
5043
|
}
|
|
4843
|
-
/** Scene scale factor (
|
|
5044
|
+
/** Scene scale factor (model space to world space) */
|
|
4844
5045
|
get scaleFactor() {
|
|
4845
|
-
this.
|
|
4846
|
-
if (this.scaleVector.z === 1) {
|
|
4847
|
-
return this.scaleVector.x;
|
|
4848
|
-
} else {
|
|
4849
|
-
const perspectiveW = this.scene.matrix.elements[15];
|
|
4850
|
-
const halfViewportWidth = this.renderer.size[0] / 2;
|
|
4851
|
-
return halfViewportWidth * this.scaleVector.x / perspectiveW;
|
|
4852
|
-
}
|
|
5046
|
+
return this.scene.matrix.elements[0];
|
|
4853
5047
|
}
|
|
4854
5048
|
/**
|
|
4855
5049
|
* Initializes the scene with the given SVG viewbox.
|
|
@@ -4859,28 +5053,48 @@ class SceneSystem {
|
|
|
4859
5053
|
this.viewbox = viewbox;
|
|
4860
5054
|
this.updateScene();
|
|
4861
5055
|
}
|
|
4862
|
-
/**
|
|
4863
|
-
* Updates the scene transform when the renderer size changes.
|
|
4864
|
-
*/
|
|
5056
|
+
/** Updates the scene transform from the current viewbox and renderer size. */
|
|
4865
5057
|
updateScene() {
|
|
4866
5058
|
if (!this.viewbox) return;
|
|
5059
|
+
this.composeMatrices(this.viewbox);
|
|
5060
|
+
}
|
|
5061
|
+
/**
|
|
5062
|
+
* Converts a point from model coordinates to world coordinates.
|
|
5063
|
+
* @param modelCoords Point in model coordinates
|
|
5064
|
+
* @param out Output vector
|
|
5065
|
+
* @returns Point in world coordinates
|
|
5066
|
+
*/
|
|
5067
|
+
modelToWorld(modelCoords, out) {
|
|
5068
|
+
const worldPoint = this.tempVector3.set(modelCoords.x, modelCoords.y, 0).applyMatrix4(this.worldMatrix);
|
|
5069
|
+
out.set(worldPoint.x, worldPoint.y, 0);
|
|
5070
|
+
return out;
|
|
5071
|
+
}
|
|
5072
|
+
/**
|
|
5073
|
+
* Converts a point from world coordinates to model coordinates. Z axis is ignored.
|
|
5074
|
+
* @param worldCoords Point in world coordinates
|
|
5075
|
+
* @param out Output vector
|
|
5076
|
+
* @returns Point in SVG coordinates
|
|
5077
|
+
*/
|
|
5078
|
+
worldToModel(worldCoords, out) {
|
|
5079
|
+
const modelPoint = this.tempVector3.copy(worldCoords).applyMatrix4(this.inverseWorldMatrix);
|
|
5080
|
+
out.set(modelPoint.x, modelPoint.y);
|
|
5081
|
+
return out;
|
|
5082
|
+
}
|
|
5083
|
+
composeMatrices(viewbox) {
|
|
4867
5084
|
const dpr = this.renderer.context.getPixelRatio();
|
|
4868
5085
|
const visibleRect = this.renderer.visibleRect;
|
|
4869
|
-
const [viewBoxWidth, viewBoxHeight] =
|
|
5086
|
+
const [viewBoxWidth, viewBoxHeight] = viewbox.size;
|
|
4870
5087
|
const [visibleRectWidth, visibleRectHeight] = (visibleRect == null ? void 0 : visibleRect.size.clone().multiplyScalar(dpr)) ?? this.renderer.size;
|
|
4871
5088
|
const scaleFactor = Math.min(visibleRectWidth / viewBoxWidth, visibleRectHeight / viewBoxHeight);
|
|
4872
|
-
const [centerX, centerY] =
|
|
5089
|
+
const [centerX, centerY] = viewbox.center;
|
|
4873
5090
|
this.translationMatrix.makeTranslation(-centerX, -centerY, 0);
|
|
4874
5091
|
this.scaleMatrix.makeScale(scaleFactor, scaleFactor, 1);
|
|
4875
5092
|
if (visibleRect) {
|
|
4876
5093
|
const visibleRectCenter = visibleRect.center.clone().multiplyScalar(dpr);
|
|
4877
|
-
const canvasCenter =
|
|
5094
|
+
const canvasCenter = { x: this.renderer.size[0] / 2, y: this.renderer.size[1] / 2 };
|
|
4878
5095
|
const offset = visibleRectCenter.sub(canvasCenter);
|
|
4879
5096
|
this.visibleRectOffsetMatrix.makeTranslation(offset.x, offset.y, 0);
|
|
4880
5097
|
}
|
|
4881
|
-
this.composeMatrices();
|
|
4882
|
-
}
|
|
4883
|
-
composeMatrices() {
|
|
4884
5098
|
this.worldMatrix.copy(this.translationMatrix).premultiply(this.scaleMatrix);
|
|
4885
5099
|
if (this.renderer.visibleRect) this.worldMatrix.premultiply(this.visibleRectOffsetMatrix);
|
|
4886
5100
|
this.scene.matrix.copy(this.worldMatrix);
|
|
@@ -4894,19 +5108,18 @@ class ViewportSystem {
|
|
|
4894
5108
|
* @param eventSystem {@link EventSystem} instance
|
|
4895
5109
|
*/
|
|
4896
5110
|
constructor(renderer, eventSystem) {
|
|
5111
|
+
__publicField(this, "pickingSystem");
|
|
5112
|
+
__publicField(this, "externalSystem");
|
|
4897
5113
|
__publicField(this, "sceneSystem");
|
|
4898
5114
|
__publicField(this, "cameraSystem");
|
|
4899
|
-
__publicField(this, "
|
|
4900
|
-
__publicField(this, "intersectionPoint", new Vector3());
|
|
4901
|
-
__publicField(this, "viewboxPlane", new Plane(new Vector3(0, 0, 1), 0));
|
|
4902
|
-
__publicField(this, "pxToSvgScaleThreshold", 1e-3);
|
|
5115
|
+
__publicField(this, "pxToSvgScaleThreshold", 1e-4);
|
|
4903
5116
|
__publicField(this, "prevPxToSvgScale");
|
|
4904
|
-
__publicField(this, "externalStaticTransformMatrix", new Matrix4());
|
|
4905
5117
|
this.renderer = renderer;
|
|
4906
5118
|
this.eventSystem = eventSystem;
|
|
5119
|
+
this.pickingSystem = new PickingSystem();
|
|
4907
5120
|
this.sceneSystem = new SceneSystem(renderer);
|
|
4908
5121
|
this.cameraSystem = new CameraSystem(renderer);
|
|
4909
|
-
this.
|
|
5122
|
+
this.externalSystem = new ExternalSystem(this.pickingSystem);
|
|
4910
5123
|
}
|
|
4911
5124
|
/** {@link Scene} instance */
|
|
4912
5125
|
get scene() {
|
|
@@ -4914,10 +5127,10 @@ class ViewportSystem {
|
|
|
4914
5127
|
}
|
|
4915
5128
|
/** Current {@link Camera} instance */
|
|
4916
5129
|
get camera() {
|
|
4917
|
-
return this.cameraSystem.
|
|
5130
|
+
return this.renderer.isExternalMode ? this.externalSystem.camera : this.cameraSystem.camera;
|
|
4918
5131
|
}
|
|
4919
5132
|
/** {@link CameraController} instance */
|
|
4920
|
-
get
|
|
5133
|
+
get controller() {
|
|
4921
5134
|
return this.cameraSystem.controller;
|
|
4922
5135
|
}
|
|
4923
5136
|
/** Current camera zoom factor. */
|
|
@@ -4930,14 +5143,22 @@ class ViewportSystem {
|
|
|
4930
5143
|
}
|
|
4931
5144
|
/** Pixel to SVG scale factor */
|
|
4932
5145
|
get pxToSvgScale() {
|
|
4933
|
-
return 1 / (this.scaleFactor * this.zoomFactor);
|
|
5146
|
+
return this.renderer.isExternalMode ? this.externalSystem.pxToSvgScale(this.renderer.size) ?? this.prevPxToSvgScale ?? 1 : 1 / (this.scaleFactor * this.zoomFactor);
|
|
5147
|
+
}
|
|
5148
|
+
/**
|
|
5149
|
+
* Get bearing angle between current camera orientation and true north (in radians).
|
|
5150
|
+
* Angle is in range [0, 2π), going clockwise from north.
|
|
5151
|
+
*/
|
|
5152
|
+
get bearing() {
|
|
5153
|
+
const tau = Math.PI * 2;
|
|
5154
|
+
return MathUtils.euclideanModulo(-this.controller.azimuthAngle, tau);
|
|
4934
5155
|
}
|
|
4935
5156
|
/**
|
|
4936
5157
|
* Initializes the viewport and zoom bounds with the given scene definition.
|
|
4937
5158
|
* @param sceneDef {@link SceneDef} scene definition
|
|
4938
5159
|
*/
|
|
4939
5160
|
initViewport(sceneDef) {
|
|
4940
|
-
this.sceneSystem.initScene(sceneDef.viewbox);
|
|
5161
|
+
if (!this.renderer.isExternalMode) this.sceneSystem.initScene(sceneDef.viewbox);
|
|
4941
5162
|
this.cameraSystem.initCamera([0.1, sceneDef.viewbox.size.width > 1e5 ? 100 : 35]);
|
|
4942
5163
|
}
|
|
4943
5164
|
/** Updates the viewport when the renderer size changes. */
|
|
@@ -4957,71 +5178,99 @@ class ViewportSystem {
|
|
|
4957
5178
|
}
|
|
4958
5179
|
/**
|
|
4959
5180
|
* Gets the objects intersected by the raycaster.
|
|
4960
|
-
* @param
|
|
5181
|
+
* @param ndcCoords raycast point in NDC (normalized device coordinates)
|
|
4961
5182
|
* @returns Array of {@link Intersection} instances
|
|
4962
5183
|
*/
|
|
4963
|
-
getIntersectedObjects(
|
|
4964
|
-
|
|
4965
|
-
this.raycaster.setFromCamera(normalizedCoords, camera);
|
|
4966
|
-
const intersections = this.raycaster.intersectObject(scene, true).filter((i) => isVisible(i.object));
|
|
4967
|
-
return intersections;
|
|
5184
|
+
getIntersectedObjects(ndcCoords) {
|
|
5185
|
+
return this.pickingSystem.getIntersectedObjects(ndcCoords, this.scene, this.camera);
|
|
4968
5186
|
}
|
|
4969
5187
|
/**
|
|
4970
|
-
* Converts a point from
|
|
4971
|
-
* @param
|
|
5188
|
+
* Converts a point from model coordinates to world coordinates.
|
|
5189
|
+
* @param modelCoords Point in model coordinates
|
|
5190
|
+
* @param out Optional output vector
|
|
4972
5191
|
* @returns Point in world coordinates
|
|
4973
5192
|
*/
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
svg3D.applyMatrix4(this.sceneSystem.worldMatrix);
|
|
4977
|
-
return new Vector2(svg3D.x, svg3D.y);
|
|
5193
|
+
modelToWorld(modelCoords, out = new Vector3()) {
|
|
5194
|
+
return this.sceneSystem.modelToWorld(modelCoords, out);
|
|
4978
5195
|
}
|
|
4979
5196
|
/**
|
|
4980
|
-
* Converts a point from world coordinates to
|
|
5197
|
+
* Converts a point from world coordinates to model coordinates. Z axis is ignored.
|
|
4981
5198
|
* @param worldCoords Point in world coordinates
|
|
4982
|
-
* @
|
|
5199
|
+
* @param out Optional output vector
|
|
5200
|
+
* @returns Point in model coordinates
|
|
4983
5201
|
*/
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
return new Vector2(svgCoords.x, svgCoords.y);
|
|
5202
|
+
worldToModel(worldCoords, out = new Vector2()) {
|
|
5203
|
+
return this.sceneSystem.worldToModel(worldCoords, out);
|
|
4987
5204
|
}
|
|
4988
5205
|
/**
|
|
4989
|
-
* Converts a point from screen coordinates to
|
|
4990
|
-
* @param
|
|
4991
|
-
* @param
|
|
4992
|
-
* @returns Point in
|
|
5206
|
+
* Converts a point from screen coordinates to world space.
|
|
5207
|
+
* @param ndcCoords Point in NDC (normalized device coordinates)
|
|
5208
|
+
* @param out Optional output vector
|
|
5209
|
+
* @returns Point in world space
|
|
4993
5210
|
*/
|
|
4994
|
-
|
|
4995
|
-
this.
|
|
4996
|
-
this.raycaster.ray.intersectPlane(this.viewboxPlane, this.intersectionPoint);
|
|
4997
|
-
if (space === "svg") this.intersectionPoint.applyMatrix4(this.sceneSystem.inverseWorldMatrix);
|
|
4998
|
-
return { x: this.intersectionPoint.x, y: this.intersectionPoint.y };
|
|
5211
|
+
ndcToWorld(ndcCoords, out = new Vector3()) {
|
|
5212
|
+
return this.pickingSystem.intersectPlane(ndcCoords, this.camera, out);
|
|
4999
5213
|
}
|
|
5000
5214
|
/**
|
|
5001
|
-
*
|
|
5002
|
-
*
|
|
5003
|
-
* @
|
|
5215
|
+
* Convert canvas coordinates (relative to the canvas's top left corner)
|
|
5216
|
+
* to NDC (normalized device coordinates).
|
|
5217
|
+
* @param point object defining the coordinates relative to the canvas's top left corner
|
|
5218
|
+
* @param out Optional output vector
|
|
5219
|
+
* @returns Point in NDC space
|
|
5004
5220
|
*/
|
|
5005
|
-
|
|
5006
|
-
|
|
5221
|
+
canvasToNDC(point, out = new Vector2()) {
|
|
5222
|
+
const dpr = this.renderer.context.getPixelRatio();
|
|
5223
|
+
const [width, height] = [this.renderer.size[0] / dpr, this.renderer.size[1] / dpr];
|
|
5224
|
+
const uv = [point.x / width, point.y / height];
|
|
5225
|
+
const ndc = [uv[0] * 2 - 1, -uv[1] * 2 + 1];
|
|
5226
|
+
return out.set(MathUtils.clamp(ndc[0], -1, 1), MathUtils.clamp(ndc[1], -1, 1));
|
|
5007
5227
|
}
|
|
5008
5228
|
/**
|
|
5009
|
-
*
|
|
5229
|
+
* Convert canvas coordinates (CSS pixels, relative to canvas top-left) to SVG coordinates.
|
|
5230
|
+
* @param point point in canvas space (CSS pixels)
|
|
5231
|
+
* @returns point in SVG coordinates or undefined if point is outside the SVG plane
|
|
5232
|
+
*/
|
|
5233
|
+
canvasToSvg(point) {
|
|
5234
|
+
const vec2 = new Vector2();
|
|
5235
|
+
const vec3 = new Vector3();
|
|
5236
|
+
const ndcPoint = this.canvasToNDC(point, vec2);
|
|
5237
|
+
const worldPoint = this.ndcToWorld(ndcPoint, vec3);
|
|
5238
|
+
if (!worldPoint) return;
|
|
5239
|
+
return this.worldToModel(worldPoint, vec2);
|
|
5240
|
+
}
|
|
5241
|
+
/**
|
|
5242
|
+
* Set static part of an svg -> px transform matrix
|
|
5010
5243
|
* @param staticTransformMatrix static transform matrix to apply to the scene
|
|
5011
5244
|
*/
|
|
5012
|
-
|
|
5013
|
-
this.
|
|
5014
|
-
this.externalStaticTransformMatrix.fromArray(staticTransformMatrix);
|
|
5245
|
+
setStaticTransform(staticTransformMatrix) {
|
|
5246
|
+
this.externalSystem.setStaticTransform(staticTransformMatrix);
|
|
5015
5247
|
}
|
|
5016
5248
|
/**
|
|
5017
|
-
*
|
|
5018
|
-
* @param dynamicTransformMatrix dynamic transform matrix
|
|
5249
|
+
* Set dynamic part of an svg -> px transform matrix. Should be called every frame.
|
|
5250
|
+
* @param dynamicTransformMatrix dynamic transform matrix (changes every frame)
|
|
5019
5251
|
*/
|
|
5020
|
-
|
|
5021
|
-
this.
|
|
5022
|
-
|
|
5252
|
+
setDynamicTransform(dynamicTransformMatrix) {
|
|
5253
|
+
this.externalSystem.setDynamicTransform(dynamicTransformMatrix);
|
|
5254
|
+
}
|
|
5255
|
+
/**
|
|
5256
|
+
* Calculates the camera distance from the scene's plane for a given zoom factor.
|
|
5257
|
+
* @param zoomFactor Zoom factor
|
|
5258
|
+
* @returns Corresponding camera distance on the Z axis
|
|
5259
|
+
*/
|
|
5260
|
+
zoomFactorToDistance(zoomFactor) {
|
|
5261
|
+
return this.cameraSystem.zoomFactorToDistance(zoomFactor);
|
|
5023
5262
|
}
|
|
5024
5263
|
}
|
|
5264
|
+
function asViewportAPI(viewportSystem) {
|
|
5265
|
+
return {
|
|
5266
|
+
canvasToSvg: viewportSystem.canvasToSvg.bind(viewportSystem),
|
|
5267
|
+
setStaticTransform: viewportSystem.setStaticTransform.bind(viewportSystem),
|
|
5268
|
+
setDynamicTransform: viewportSystem.setDynamicTransform.bind(viewportSystem)
|
|
5269
|
+
};
|
|
5270
|
+
}
|
|
5271
|
+
function eventToCanvas(event) {
|
|
5272
|
+
return { x: event.offsetX, y: event.offsetY };
|
|
5273
|
+
}
|
|
5025
5274
|
class ControlsSystem {
|
|
5026
5275
|
/**
|
|
5027
5276
|
* @param renderer {@link Renderer} instance
|
|
@@ -5033,7 +5282,7 @@ class ControlsSystem {
|
|
|
5033
5282
|
this.renderer = renderer;
|
|
5034
5283
|
this.viewportSystem = viewportSystem;
|
|
5035
5284
|
this.interactionsSystem = interactionsSystem;
|
|
5036
|
-
this.controller = viewportSystem.
|
|
5285
|
+
this.controller = viewportSystem.controller;
|
|
5037
5286
|
}
|
|
5038
5287
|
/** Gesture handlers for camera controls. */
|
|
5039
5288
|
get handlers() {
|
|
@@ -5062,7 +5311,9 @@ class ControlsSystem {
|
|
|
5062
5311
|
const dpr = this.renderer.context.getPixelRatio();
|
|
5063
5312
|
const visibleRect = this.renderer.visibleRect;
|
|
5064
5313
|
const bearingAngle = -this.controller.azimuthAngle;
|
|
5065
|
-
const
|
|
5314
|
+
const worldMin = this.viewportSystem.modelToWorld(rect.min);
|
|
5315
|
+
const worldMax = this.viewportSystem.modelToWorld(rect.max);
|
|
5316
|
+
const worldRect = new Rect(worldMin, worldMax);
|
|
5066
5317
|
const worldPolygon = Polygon.fromRect(worldRect).rotate(bearingAngle, worldRect.center);
|
|
5067
5318
|
const xValues = worldPolygon.vertices.map((p) => p.x);
|
|
5068
5319
|
const yValues = worldPolygon.vertices.map((p) => p.y);
|
|
@@ -5070,7 +5321,7 @@ class ControlsSystem {
|
|
|
5070
5321
|
[Math.min(...xValues), Math.min(...yValues)],
|
|
5071
5322
|
[Math.max(...xValues), Math.max(...yValues)]
|
|
5072
5323
|
);
|
|
5073
|
-
const targetRect = visibleRect ? new Rect(visibleRect.min.clone().multiplyScalar(dpr), visibleRect.max.clone().multiplyScalar(dpr)) : new Rect([0, 0], this.renderer.size);
|
|
5324
|
+
const targetRect = visibleRect ? new Rect(visibleRect.min.clone().multiplyScalar(dpr), visibleRect.max.clone().multiplyScalar(dpr)) : new Rect([0, 0], [...this.renderer.size]);
|
|
5074
5325
|
if (paddingPercent) targetRect.addPadding(targetRect.size.x * paddingPercent, targetRect.size.y * paddingPercent);
|
|
5075
5326
|
const zoomByWidth = targetRect.size.x / sourceRect.size.x;
|
|
5076
5327
|
const zoomByHeight = targetRect.size.y / sourceRect.size.y;
|
|
@@ -5095,8 +5346,8 @@ class ControlsSystem {
|
|
|
5095
5346
|
* @returns Promise that resolves when the pan animation completes
|
|
5096
5347
|
*/
|
|
5097
5348
|
panBy(x, y, immediate) {
|
|
5098
|
-
const svgOrigin = this.viewportSystem.
|
|
5099
|
-
const svgOffset = this.viewportSystem.
|
|
5349
|
+
const svgOrigin = this.viewportSystem.modelToWorld({ x: 0, y: 0 });
|
|
5350
|
+
const svgOffset = this.viewportSystem.modelToWorld({ x, y });
|
|
5100
5351
|
const worldOffset = new Vector3(svgOffset.x - svgOrigin.x, svgOffset.y - svgOrigin.y, 0);
|
|
5101
5352
|
const currentTarget = this.controller.getTarget(new Vector3());
|
|
5102
5353
|
const newTarget = currentTarget.add(worldOffset);
|
|
@@ -5110,7 +5361,7 @@ class ControlsSystem {
|
|
|
5110
5361
|
* @returns Promise that resolves when the pan animation completes
|
|
5111
5362
|
*/
|
|
5112
5363
|
panTo(x, y, immediate) {
|
|
5113
|
-
const worldCoords = this.viewportSystem.
|
|
5364
|
+
const worldCoords = this.viewportSystem.modelToWorld({ x, y });
|
|
5114
5365
|
return this.controller.moveTo(worldCoords.x, worldCoords.y, 0, !immediate);
|
|
5115
5366
|
}
|
|
5116
5367
|
/**
|
|
@@ -5208,8 +5459,8 @@ class ControlsSystem {
|
|
|
5208
5459
|
this.controller.setBoundary(void 0);
|
|
5209
5460
|
return;
|
|
5210
5461
|
}
|
|
5211
|
-
const worldMin = this.viewportSystem.
|
|
5212
|
-
const worldMax = this.viewportSystem.
|
|
5462
|
+
const worldMin = this.viewportSystem.modelToWorld(rect.min);
|
|
5463
|
+
const worldMax = this.viewportSystem.modelToWorld(rect.max);
|
|
5213
5464
|
const boundary = new Box3(new Vector3(worldMin.x, worldMin.y, 0), new Vector3(worldMax.x, worldMax.y, 0));
|
|
5214
5465
|
this.controller.setBoundary(boundary);
|
|
5215
5466
|
}
|
|
@@ -5219,9 +5470,9 @@ class ControlsSystem {
|
|
|
5219
5470
|
*/
|
|
5220
5471
|
getCameraState() {
|
|
5221
5472
|
const target = this.controller.getTarget(new Vector3());
|
|
5222
|
-
const center = this.viewportSystem.
|
|
5473
|
+
const center = this.viewportSystem.worldToModel(target);
|
|
5223
5474
|
const zoom = this.viewportSystem.zoomFactor;
|
|
5224
|
-
const roll = this.
|
|
5475
|
+
const roll = this.viewportSystem.bearing * RAD2DEG;
|
|
5225
5476
|
const pitch = this.controller.polarAngle * RAD2DEG;
|
|
5226
5477
|
const ptScale = this.viewportSystem.pxToSvgScale;
|
|
5227
5478
|
return { center, zoom, roll, pitch, ptScale };
|
|
@@ -5318,21 +5569,6 @@ function asEventAPI(system) {
|
|
|
5318
5569
|
clear: system.clear.bind(system)
|
|
5319
5570
|
};
|
|
5320
5571
|
}
|
|
5321
|
-
function clientToCanvas(event, domElement, target) {
|
|
5322
|
-
target = target ?? new Vector2();
|
|
5323
|
-
const { left, top } = domElement.getBoundingClientRect();
|
|
5324
|
-
const clientX = "clientX" in event ? event.clientX : event.x;
|
|
5325
|
-
const clientY = "clientY" in event ? event.clientY : event.y;
|
|
5326
|
-
target.set(clientX - left, clientY - top);
|
|
5327
|
-
return target;
|
|
5328
|
-
}
|
|
5329
|
-
function canvasToNDC(coordinates, canvas, target) {
|
|
5330
|
-
target = target ?? new Vector2();
|
|
5331
|
-
const { width, height } = canvas.getBoundingClientRect();
|
|
5332
|
-
const uv = [coordinates.x / width, coordinates.y / height];
|
|
5333
|
-
target.set(uv[0] * 2 - 1, -uv[1] * 2 + 1);
|
|
5334
|
-
return target;
|
|
5335
|
-
}
|
|
5336
5572
|
class Handler {
|
|
5337
5573
|
/**
|
|
5338
5574
|
* @param viewportSystem The viewport system instance
|
|
@@ -5347,7 +5583,7 @@ class Handler {
|
|
|
5347
5583
|
this.viewportSystem = viewportSystem;
|
|
5348
5584
|
this.domElement = domElement;
|
|
5349
5585
|
this.eventManager = eventManager;
|
|
5350
|
-
this.controller = viewportSystem.
|
|
5586
|
+
this.controller = viewportSystem.controller;
|
|
5351
5587
|
}
|
|
5352
5588
|
/**
|
|
5353
5589
|
* Per-frame update for this handler.
|
|
@@ -5558,13 +5794,15 @@ class PitchHandler extends Handler {
|
|
|
5558
5794
|
__publicField(this, "isValid");
|
|
5559
5795
|
__publicField(this, "firstMove");
|
|
5560
5796
|
__publicField(this, "lastPoints");
|
|
5797
|
+
__publicField(this, "p0", new Vector2());
|
|
5798
|
+
__publicField(this, "p1", new Vector2());
|
|
5561
5799
|
__publicField(this, "prevTwoFingerAction");
|
|
5562
5800
|
__publicField(this, "onPitchStart", (e) => {
|
|
5563
5801
|
const pointers = e.pointers.sort((a, b) => a.pointerId - b.pointerId);
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
this.lastPoints = [p0, p1];
|
|
5567
|
-
if (this.isVertical(p0.
|
|
5802
|
+
this.p0.copy(eventToCanvas(pointers[0]));
|
|
5803
|
+
this.p1.copy(eventToCanvas(pointers[1]));
|
|
5804
|
+
this.lastPoints = [new Vector2().copy(this.p0), new Vector2().copy(this.p1)];
|
|
5805
|
+
if (this.isVertical(this.p0.sub(this.p1))) {
|
|
5568
5806
|
this.isValid = false;
|
|
5569
5807
|
}
|
|
5570
5808
|
});
|
|
@@ -5579,18 +5817,19 @@ class PitchHandler extends Handler {
|
|
|
5579
5817
|
const lastPoints = this.lastPoints;
|
|
5580
5818
|
if (!lastPoints) return;
|
|
5581
5819
|
const pointers = e.pointers.sort((a, b) => a.pointerId - b.pointerId);
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
this.isValid = this.gestureBeginsVertically(
|
|
5820
|
+
this.p0.copy(eventToCanvas(pointers[0]));
|
|
5821
|
+
this.p1.copy(eventToCanvas(pointers[1]));
|
|
5822
|
+
this.p0.sub(lastPoints[0]);
|
|
5823
|
+
this.p1.sub(lastPoints[1]);
|
|
5824
|
+
this.isValid = this.gestureBeginsVertically(this.p0, this.p1, e.timeStamp);
|
|
5587
5825
|
if (!this.isValid) return;
|
|
5588
5826
|
if (this.prevTwoFingerAction === void 0) {
|
|
5589
5827
|
this.prevTwoFingerAction = this.controller.touches.two;
|
|
5590
5828
|
this.controller.touches.two = CameraController.ACTION.NONE;
|
|
5591
5829
|
}
|
|
5592
|
-
this.
|
|
5593
|
-
|
|
5830
|
+
lastPoints[0].add(this.p0);
|
|
5831
|
+
lastPoints[1].add(this.p1);
|
|
5832
|
+
const yDeltaAverage = (this.p0.y + this.p1.y) / 2;
|
|
5594
5833
|
const degreesPerPixelMoved = -0.5;
|
|
5595
5834
|
const deltaAngle = yDeltaAverage * degreesPerPixelMoved * DEG2RAD$1;
|
|
5596
5835
|
void this.controller.rotatePolarTo(this.controller.polarAngle + deltaAngle, false);
|
|
@@ -5635,12 +5874,10 @@ class PitchHandler extends Handler {
|
|
|
5635
5874
|
updatePolarAngles() {
|
|
5636
5875
|
this.controller.minPolarAngle = this.minPitch * DEG2RAD$1;
|
|
5637
5876
|
this.controller.maxPolarAngle = this.maxPitch * DEG2RAD$1;
|
|
5638
|
-
if (this.controller.polarAngle < this.controller.minPolarAngle)
|
|
5877
|
+
if (this.controller.polarAngle < this.controller.minPolarAngle)
|
|
5639
5878
|
void this.controller.rotatePolarTo(this.controller.minPolarAngle, false);
|
|
5640
|
-
|
|
5641
|
-
if (this.controller.polarAngle > this.controller.maxPolarAngle) {
|
|
5879
|
+
if (this.controller.polarAngle > this.controller.maxPolarAngle)
|
|
5642
5880
|
void this.controller.rotatePolarTo(this.controller.maxPolarAngle, false);
|
|
5643
|
-
}
|
|
5644
5881
|
}
|
|
5645
5882
|
gestureBeginsVertically(vectorA, vectorB, timeStamp) {
|
|
5646
5883
|
if (this.isValid !== void 0) return this.isValid;
|
|
@@ -5672,35 +5909,36 @@ class RollHandler extends Handler {
|
|
|
5672
5909
|
__publicField(this, "rotationThreshold", 25);
|
|
5673
5910
|
// Threshold tracking (Mapbox-style)
|
|
5674
5911
|
__publicField(this, "startVector");
|
|
5675
|
-
__publicField(this, "vector");
|
|
5912
|
+
__publicField(this, "vector", new Vector2());
|
|
5913
|
+
__publicField(this, "p0", new Vector2());
|
|
5914
|
+
__publicField(this, "p1", new Vector2());
|
|
5676
5915
|
__publicField(this, "minDiameter", 0);
|
|
5677
5916
|
__publicField(this, "prevAngle", 0);
|
|
5678
5917
|
// Camera and pivot vectors
|
|
5918
|
+
__publicField(this, "pivotNDC", new Vector2());
|
|
5679
5919
|
__publicField(this, "pivotWorld", new Vector3());
|
|
5680
5920
|
__publicField(this, "targetWorld", new Vector3());
|
|
5681
5921
|
__publicField(this, "cameraPosition", new Vector3());
|
|
5682
5922
|
__publicField(this, "cameraForward", new Vector3());
|
|
5683
5923
|
__publicField(this, "rotationMatrix", new Matrix4());
|
|
5684
5924
|
__publicField(this, "onRotateStart", (e) => {
|
|
5685
|
-
console.log("onRotateStart");
|
|
5686
5925
|
const pointers = e.pointers;
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
this.startVector = p0.sub(p1);
|
|
5926
|
+
this.p0.copy(eventToCanvas(pointers[0]));
|
|
5927
|
+
this.p1.copy(eventToCanvas(pointers[1]));
|
|
5928
|
+
this.startVector = new Vector2().copy(this.p0).sub(this.p1);
|
|
5690
5929
|
this.minDiameter = this.startVector.length();
|
|
5691
5930
|
});
|
|
5692
5931
|
__publicField(this, "onRotateEnd", () => {
|
|
5693
5932
|
this.isRolling = false;
|
|
5694
5933
|
this.startVector = void 0;
|
|
5695
|
-
this.vector = void 0;
|
|
5696
5934
|
this.minDiameter = 0;
|
|
5697
5935
|
});
|
|
5698
5936
|
__publicField(this, "onRotate", (e) => {
|
|
5699
5937
|
const pointers = e.pointers;
|
|
5700
5938
|
if (!this.isRolling) {
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
this.vector
|
|
5939
|
+
this.p0.copy(eventToCanvas(pointers[0]));
|
|
5940
|
+
this.p1.copy(eventToCanvas(pointers[1]));
|
|
5941
|
+
this.vector.copy(this.p0).sub(this.p1);
|
|
5704
5942
|
if (this.isBelowThreshold(this.vector)) return;
|
|
5705
5943
|
this.isRolling = true;
|
|
5706
5944
|
this.prevAngle = e.rotation;
|
|
@@ -5708,7 +5946,7 @@ class RollHandler extends Handler {
|
|
|
5708
5946
|
const deltaAngle = (e.rotation - this.prevAngle) * -DEG2RAD$1;
|
|
5709
5947
|
this.prevAngle = e.rotation;
|
|
5710
5948
|
if (Math.abs(deltaAngle) < 1e-3) return;
|
|
5711
|
-
this.setPivot(e);
|
|
5949
|
+
if (!this.setPivot(e)) return;
|
|
5712
5950
|
this.rotationMatrix.makeRotationZ(deltaAngle);
|
|
5713
5951
|
this.cameraPosition.sub(this.pivotWorld).applyMatrix4(this.rotationMatrix).add(this.pivotWorld);
|
|
5714
5952
|
this.cameraForward.applyMatrix4(this.rotationMatrix);
|
|
@@ -5756,13 +5994,12 @@ class RollHandler extends Handler {
|
|
|
5756
5994
|
this.eventManager.off("rotateend", this.onRotateEnd);
|
|
5757
5995
|
}
|
|
5758
5996
|
setPivot(e) {
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
const pivotWorld2D = this.viewportSystem.screenTo("world", pivotNDC);
|
|
5762
|
-
this.pivotWorld.set(pivotWorld2D.x, pivotWorld2D.y, 0);
|
|
5997
|
+
this.viewportSystem.canvasToNDC(e.offsetCenter, this.pivotNDC);
|
|
5998
|
+
if (!this.viewportSystem.ndcToWorld(this.pivotNDC, this.pivotWorld)) return false;
|
|
5763
5999
|
this.controller.getPosition(this.cameraPosition);
|
|
5764
6000
|
this.controller.getTarget(this.targetWorld);
|
|
5765
6001
|
this.cameraForward.copy(this.targetWorld).sub(this.cameraPosition);
|
|
6002
|
+
return true;
|
|
5766
6003
|
}
|
|
5767
6004
|
/**
|
|
5768
6005
|
* Check if rotation is below threshold (Mapbox-style).
|
|
@@ -5782,7 +6019,6 @@ class RollHandler extends Handler {
|
|
|
5782
6019
|
const startVector = this.startVector;
|
|
5783
6020
|
if (!startVector) return false;
|
|
5784
6021
|
const bearingDeltaSinceStart = this.getBearingDelta(vector, startVector);
|
|
5785
|
-
console.log("bearingDeltaSinceStart", vector, startVector);
|
|
5786
6022
|
return Math.abs(bearingDeltaSinceStart) < threshold;
|
|
5787
6023
|
}
|
|
5788
6024
|
/**
|
|
@@ -5794,16 +6030,6 @@ class RollHandler extends Handler {
|
|
|
5794
6030
|
getBearingDelta(a, b) {
|
|
5795
6031
|
return a.angleTo(b) * RAD2DEG;
|
|
5796
6032
|
}
|
|
5797
|
-
/**
|
|
5798
|
-
* Normalize angle to be between -π and π
|
|
5799
|
-
* @param angle Angle in radians
|
|
5800
|
-
* @returns Normalized angle in radians
|
|
5801
|
-
*/
|
|
5802
|
-
normalizeAngle(angle) {
|
|
5803
|
-
while (angle > Math.PI) angle -= 2 * Math.PI;
|
|
5804
|
-
while (angle < -Math.PI) angle += 2 * Math.PI;
|
|
5805
|
-
return angle;
|
|
5806
|
-
}
|
|
5807
6033
|
}
|
|
5808
6034
|
class ZoomHandler extends Handler {
|
|
5809
6035
|
reset(enableTransition = true) {
|
|
@@ -5838,12 +6064,16 @@ class InteractionsSystem {
|
|
|
5838
6064
|
__publicField(this, "handlers");
|
|
5839
6065
|
__publicField(this, "handlerArray");
|
|
5840
6066
|
__publicField(this, "canvas");
|
|
5841
|
-
__publicField(this, "mousePointer", new Vector2());
|
|
5842
6067
|
__publicField(this, "eventManager");
|
|
6068
|
+
__publicField(this, "mousePointerNDC", new Vector2());
|
|
6069
|
+
__publicField(this, "mousePointerWorld", new Vector3());
|
|
6070
|
+
__publicField(this, "mousePointerModel", new Vector2());
|
|
6071
|
+
__publicField(this, "canvasListeners");
|
|
5843
6072
|
__publicField(this, "dragStart");
|
|
5844
6073
|
__publicField(this, "dragThreshold", 15);
|
|
5845
6074
|
__publicField(this, "isDragging", false);
|
|
5846
6075
|
__publicField(this, "prevBearing", 0);
|
|
6076
|
+
this.renderer = renderer;
|
|
5847
6077
|
this.events = events;
|
|
5848
6078
|
this.viewportSystem = viewportSystem;
|
|
5849
6079
|
this.layerSystem = layerSystem;
|
|
@@ -5851,8 +6081,6 @@ class InteractionsSystem {
|
|
|
5851
6081
|
this.eventManager = new EventManager(this.canvas, {
|
|
5852
6082
|
recognizers: [Rotate, [Pan, { event: "pitch", pointers: 2 }, "rotate"]]
|
|
5853
6083
|
});
|
|
5854
|
-
this.configureCameraControls();
|
|
5855
|
-
this.attachCanvasListeners();
|
|
5856
6084
|
const handlers = {
|
|
5857
6085
|
pan: new PanHandler(viewportSystem, this.canvas, this.eventManager),
|
|
5858
6086
|
zoom: new ZoomHandler(viewportSystem, this.canvas, this.eventManager),
|
|
@@ -5861,17 +6089,32 @@ class InteractionsSystem {
|
|
|
5861
6089
|
};
|
|
5862
6090
|
this.handlers = handlers;
|
|
5863
6091
|
this.handlerArray = Object.values(handlers);
|
|
5864
|
-
this.handlers.pan.enable();
|
|
5865
|
-
this.handlers.zoom.enable();
|
|
5866
6092
|
}
|
|
5867
6093
|
/**
|
|
5868
|
-
*
|
|
5869
|
-
*
|
|
6094
|
+
* Initializes the interactions system and attaches event listeners.
|
|
6095
|
+
* Should be called once as part of renderer initialization.
|
|
5870
6096
|
*/
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
6097
|
+
init() {
|
|
6098
|
+
this.attachCanvasListeners();
|
|
6099
|
+
if (!this.renderer.isExternalMode) {
|
|
6100
|
+
const controller = this.viewportSystem.controller;
|
|
6101
|
+
controller.connect(this.canvas);
|
|
6102
|
+
controller.addEventListener("transitionstart", () => {
|
|
6103
|
+
this.events.emit("navigation:change");
|
|
6104
|
+
});
|
|
6105
|
+
this.handlers.pan.enable();
|
|
6106
|
+
this.handlers.zoom.enable();
|
|
6107
|
+
}
|
|
6108
|
+
}
|
|
6109
|
+
/**
|
|
6110
|
+
* Disposes the interactions system.
|
|
6111
|
+
* WARNING: This method is final and cannot be undone. To re-enable interactions, create a new renderer instance.
|
|
6112
|
+
*/
|
|
6113
|
+
dispose() {
|
|
6114
|
+
for (const handler of this.handlerArray) handler.disable();
|
|
6115
|
+
this.viewportSystem.controller.disconnect();
|
|
6116
|
+
this.detachCanvasListeners();
|
|
6117
|
+
this.eventManager.destroy();
|
|
5875
6118
|
}
|
|
5876
6119
|
/**
|
|
5877
6120
|
* Update camera position and directions.
|
|
@@ -5880,77 +6123,67 @@ class InteractionsSystem {
|
|
|
5880
6123
|
* @returns true if re-rendering is needed
|
|
5881
6124
|
*/
|
|
5882
6125
|
updateControls(delta) {
|
|
5883
|
-
let needsUpdate = this.viewportSystem.
|
|
6126
|
+
let needsUpdate = this.viewportSystem.controller.update(delta);
|
|
5884
6127
|
for (const handler of this.handlerArray) {
|
|
5885
6128
|
if (handler.isEnabled()) {
|
|
5886
6129
|
needsUpdate = handler.update(delta) || needsUpdate;
|
|
5887
6130
|
}
|
|
5888
6131
|
}
|
|
5889
|
-
if (this.bearing !== this.prevBearing) {
|
|
5890
|
-
this.prevBearing = this.bearing;
|
|
5891
|
-
this.events.emit("navigation:roll", this.bearing);
|
|
6132
|
+
if (this.viewportSystem.bearing !== this.prevBearing) {
|
|
6133
|
+
this.prevBearing = this.viewportSystem.bearing;
|
|
6134
|
+
this.events.emit("navigation:roll", this.viewportSystem.bearing);
|
|
5892
6135
|
}
|
|
5893
6136
|
return needsUpdate;
|
|
5894
6137
|
}
|
|
5895
|
-
/** Disconnect the interactions system. */
|
|
5896
|
-
disconnect() {
|
|
5897
|
-
this.viewportSystem.cameraController.disconnect();
|
|
5898
|
-
for (const handler of this.handlerArray) {
|
|
5899
|
-
handler.disable();
|
|
5900
|
-
}
|
|
5901
|
-
}
|
|
5902
|
-
configureCameraControls() {
|
|
5903
|
-
const controller = this.viewportSystem.cameraController;
|
|
5904
|
-
controller.draggingSmoothTime = 0;
|
|
5905
|
-
controller.dollyToCursor = true;
|
|
5906
|
-
controller.mouseButtons = {
|
|
5907
|
-
left: CameraController.ACTION.NONE,
|
|
5908
|
-
middle: CameraController.ACTION.NONE,
|
|
5909
|
-
right: CameraController.ACTION.NONE,
|
|
5910
|
-
wheel: CameraController.ACTION.NONE
|
|
5911
|
-
};
|
|
5912
|
-
controller.touches = {
|
|
5913
|
-
one: CameraController.ACTION.NONE,
|
|
5914
|
-
two: CameraController.ACTION.NONE,
|
|
5915
|
-
three: CameraController.ACTION.NONE
|
|
5916
|
-
};
|
|
5917
|
-
controller.connect(this.canvas);
|
|
5918
|
-
controller.addEventListener("transitionstart", () => {
|
|
5919
|
-
this.events.emit("navigation:change");
|
|
5920
|
-
});
|
|
5921
|
-
}
|
|
5922
6138
|
attachCanvasListeners() {
|
|
5923
|
-
this.
|
|
6139
|
+
if (this.canvasListeners) return;
|
|
6140
|
+
const pointerdown = (event) => {
|
|
5924
6141
|
this.isDragging = false;
|
|
5925
6142
|
this.dragStart = { x: event.offsetX, y: event.offsetY };
|
|
5926
|
-
}
|
|
5927
|
-
|
|
6143
|
+
};
|
|
6144
|
+
const pointerup = (event) => {
|
|
5928
6145
|
if (!this.dragStart) return;
|
|
5929
6146
|
const dX = event.offsetX - this.dragStart.x;
|
|
5930
6147
|
const dY = event.offsetY - this.dragStart.y;
|
|
5931
6148
|
this.dragStart = void 0;
|
|
5932
6149
|
if (dX * dX + dY * dY > this.dragThreshold) this.isDragging = true;
|
|
5933
|
-
}
|
|
6150
|
+
};
|
|
5934
6151
|
const mouseEventsMap = {
|
|
5935
6152
|
mousemove: "pointer:move",
|
|
5936
6153
|
mouseout: "pointer:out",
|
|
5937
6154
|
click: "pointer:click"
|
|
5938
6155
|
};
|
|
5939
6156
|
const mouseEventKeys = Object.keys(mouseEventsMap);
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
6157
|
+
const sharedMouseHandler = (type, event) => {
|
|
6158
|
+
const eventType = mouseEventsMap[type];
|
|
6159
|
+
const isDragging = type === "click" && this.isDragging;
|
|
6160
|
+
const hasListeners = this.events.hasListeners(eventType);
|
|
6161
|
+
if (isDragging || !hasListeners) return;
|
|
6162
|
+
const mousePointer = eventToCanvas(event);
|
|
6163
|
+
this.viewportSystem.canvasToNDC(mousePointer, this.mousePointerNDC);
|
|
6164
|
+
if (!this.viewportSystem.ndcToWorld(this.mousePointerNDC, this.mousePointerWorld)) return;
|
|
6165
|
+
const intersections = this.viewportSystem.getIntersectedObjects(this.mousePointerNDC);
|
|
6166
|
+
const point = this.viewportSystem.worldToModel(this.mousePointerWorld, this.mousePointerModel);
|
|
6167
|
+
const defs = this.layerSystem.getIntersectedDefs(intersections);
|
|
6168
|
+
this.events.emit(eventType, { event, point: { x: point.x, y: point.y }, defs });
|
|
6169
|
+
};
|
|
6170
|
+
const mousemove = (event) => sharedMouseHandler("mousemove", event);
|
|
6171
|
+
const mouseout = (event) => sharedMouseHandler("mouseout", event);
|
|
6172
|
+
const click = (event) => sharedMouseHandler("click", event);
|
|
6173
|
+
const canvasListeners = { pointerdown, pointerup, mousemove, mouseout, click };
|
|
6174
|
+
this.canvas.addEventListener("pointerdown", pointerdown);
|
|
6175
|
+
this.canvas.addEventListener("pointerup", pointerup);
|
|
6176
|
+
mouseEventKeys.forEach((type) => this.canvas.addEventListener(type, canvasListeners[type]));
|
|
6177
|
+
this.canvasListeners = canvasListeners;
|
|
6178
|
+
}
|
|
6179
|
+
detachCanvasListeners() {
|
|
6180
|
+
if (!this.canvasListeners) return;
|
|
6181
|
+
this.canvas.removeEventListener("pointerdown", this.canvasListeners.pointerdown);
|
|
6182
|
+
this.canvas.removeEventListener("pointerup", this.canvasListeners.pointerup);
|
|
6183
|
+
this.canvas.removeEventListener("mousemove", this.canvasListeners.mousemove);
|
|
6184
|
+
this.canvas.removeEventListener("mouseout", this.canvasListeners.mouseout);
|
|
6185
|
+
this.canvas.removeEventListener("click", this.canvasListeners.click);
|
|
6186
|
+
this.canvasListeners = void 0;
|
|
5954
6187
|
}
|
|
5955
6188
|
}
|
|
5956
6189
|
class Renderer {
|
|
@@ -5960,6 +6193,7 @@ class Renderer {
|
|
|
5960
6193
|
constructor(opts) {
|
|
5961
6194
|
/** Whether to log debug information */
|
|
5962
6195
|
__publicField(this, "debugLog");
|
|
6196
|
+
//FIXME: Add https://www.npmjs.com/package/debug
|
|
5963
6197
|
/** {@link HTMLCanvasElement} that this renderer is rendering to */
|
|
5964
6198
|
__publicField(this, "canvas");
|
|
5965
6199
|
__publicField(this, "ui");
|
|
@@ -5969,13 +6203,17 @@ class Renderer {
|
|
|
5969
6203
|
__publicField(this, "viewportSystem");
|
|
5970
6204
|
__publicField(this, "interactionsSystem");
|
|
5971
6205
|
__publicField(this, "controlsSystem");
|
|
6206
|
+
__publicField(this, "controlsAPI");
|
|
6207
|
+
__publicField(this, "eventsAPI");
|
|
6208
|
+
__publicField(this, "viewportAPI");
|
|
5972
6209
|
__publicField(this, "clock");
|
|
5973
6210
|
__publicField(this, "renderer");
|
|
5974
|
-
__publicField(this, "
|
|
6211
|
+
__publicField(this, "visibleRectValue");
|
|
6212
|
+
__publicField(this, "memoryInfoExtension", null);
|
|
6213
|
+
__publicField(this, "memoryInfo");
|
|
6214
|
+
__publicField(this, "initialized", false);
|
|
6215
|
+
__publicField(this, "disposed", false);
|
|
5975
6216
|
__publicField(this, "needsRedraw", true);
|
|
5976
|
-
__publicField(this, "memoryInfoExtension");
|
|
5977
|
-
__publicField(this, "memoryInfo", "");
|
|
5978
|
-
var _a2, _b;
|
|
5979
6217
|
const { canvas, gl, debugLog = false, ui } = opts;
|
|
5980
6218
|
this.canvas = canvas;
|
|
5981
6219
|
this.debugLog = debugLog;
|
|
@@ -5990,41 +6228,84 @@ class Renderer {
|
|
|
5990
6228
|
this.renderer = new WebGLRenderer(rendererOptions);
|
|
5991
6229
|
this.renderer.setSize(this.canvas.clientWidth, this.canvas.clientHeight, false);
|
|
5992
6230
|
this.renderer.setPixelRatio(window.devicePixelRatio);
|
|
6231
|
+
this.renderer.autoClear = !this.isExternalMode;
|
|
5993
6232
|
this.eventSystem = new EventSystem();
|
|
5994
6233
|
this.viewportSystem = new ViewportSystem(this, this.eventSystem);
|
|
5995
6234
|
this.layerSystem = new LayerSystem(this);
|
|
5996
6235
|
this.interactionsSystem = new InteractionsSystem(this, this.eventSystem, this.viewportSystem, this.layerSystem);
|
|
5997
6236
|
this.controlsSystem = new ControlsSystem(this, this.viewportSystem, this.interactionsSystem);
|
|
5998
|
-
this.
|
|
5999
|
-
this.
|
|
6000
|
-
this.canvas.addEventListener("webglcontextrestored", (e) => this.onContextRestored(e), false);
|
|
6001
|
-
void ((_b = (_a2 = this.ui) == null ? void 0 : _a2.stats) == null ? void 0 : _b.init(this.renderer.getContext()));
|
|
6237
|
+
this.initContext(this.renderer.getContext());
|
|
6238
|
+
BatchedMesh.useMultiDraw = this.renderer.extensions.has("WEBGL_multi_draw");
|
|
6002
6239
|
}
|
|
6003
6240
|
/**
|
|
6004
6241
|
* {@link ControlsAPI} instance for controlling the viewport
|
|
6005
6242
|
*/
|
|
6006
6243
|
get controls() {
|
|
6007
|
-
|
|
6244
|
+
if (this.controlsAPI) return this.controlsAPI;
|
|
6245
|
+
const api = asControlsAPI(this.controlsSystem);
|
|
6246
|
+
const guard = (name) => this.assertInitialized(`controls.${name}`) && this.assertNotDisposed(`controls.${name}`) && this.assertNotExternalMode(`controls.${name}`);
|
|
6247
|
+
this.controlsAPI = {
|
|
6248
|
+
handlers: api.handlers,
|
|
6249
|
+
configure: api.configure,
|
|
6250
|
+
zoomBy: guardFn(guard, api.zoomBy, Promise.resolve()),
|
|
6251
|
+
zoomTo: guardFn(guard, api.zoomTo, Promise.resolve()),
|
|
6252
|
+
panBy: guardFn(guard, api.panBy, Promise.resolve()),
|
|
6253
|
+
panTo: guardFn(guard, api.panTo, Promise.resolve()),
|
|
6254
|
+
rollBy: guardFn(guard, api.rollBy, Promise.resolve()),
|
|
6255
|
+
rollTo: guardFn(guard, api.rollTo, Promise.resolve()),
|
|
6256
|
+
pitchBy: guardFn(guard, api.pitchBy, Promise.resolve()),
|
|
6257
|
+
pitchTo: guardFn(guard, api.pitchTo, Promise.resolve()),
|
|
6258
|
+
resetCamera: guardFn(guard, api.resetCamera, Promise.resolve()),
|
|
6259
|
+
setCameraBounds: guardFn(guard, api.setCameraBounds),
|
|
6260
|
+
getCameraState: guardFn(guard, api.getCameraState, {
|
|
6261
|
+
center: { x: 0, y: 0 },
|
|
6262
|
+
roll: 0,
|
|
6263
|
+
pitch: 0,
|
|
6264
|
+
zoom: 1,
|
|
6265
|
+
ptScale: 1
|
|
6266
|
+
})
|
|
6267
|
+
};
|
|
6268
|
+
return this.controlsAPI;
|
|
6008
6269
|
}
|
|
6009
6270
|
/**
|
|
6010
6271
|
* {@link EventsAPI} instance for subscribing to internal events
|
|
6011
6272
|
*/
|
|
6012
6273
|
get events() {
|
|
6013
|
-
|
|
6274
|
+
if (this.eventsAPI) return this.eventsAPI;
|
|
6275
|
+
this.eventsAPI = asEventAPI(this.eventSystem);
|
|
6276
|
+
return this.eventsAPI;
|
|
6277
|
+
}
|
|
6278
|
+
/**
|
|
6279
|
+
* {@link ViewportAPI} instance for view transforms and external transforms.
|
|
6280
|
+
*/
|
|
6281
|
+
get viewport() {
|
|
6282
|
+
if (this.viewportAPI) return this.viewportAPI;
|
|
6283
|
+
const api = asViewportAPI(this.viewportSystem);
|
|
6284
|
+
const guard = (name) => this.assertInitialized(`viewport.${name}`) && this.assertNotDisposed(`viewport.${name}`);
|
|
6285
|
+
const guardExternal = (name) => guard(name) && this.assertExternalMode(`viewport.${name}`);
|
|
6286
|
+
this.viewportAPI = {
|
|
6287
|
+
canvasToSvg: guardFn(guard, api.canvasToSvg, { x: 0, y: 0 }),
|
|
6288
|
+
setStaticTransform: guardFn(guardExternal, api.setStaticTransform),
|
|
6289
|
+
setDynamicTransform: guardFn(guardExternal, api.setDynamicTransform)
|
|
6290
|
+
};
|
|
6291
|
+
return this.viewportAPI;
|
|
6014
6292
|
}
|
|
6015
6293
|
/**
|
|
6016
6294
|
* Optional sub-rectangle of the viewport that is used for positioning scene's viewbox
|
|
6017
6295
|
*/
|
|
6018
6296
|
get visibleRect() {
|
|
6019
|
-
return this.
|
|
6297
|
+
return this.visibleRectValue;
|
|
6020
6298
|
}
|
|
6021
6299
|
/**
|
|
6022
6300
|
* Optional sub-rectangle of the viewport that is used for positioning scene's viewbox
|
|
6023
6301
|
*/
|
|
6024
6302
|
set visibleRect(rect) {
|
|
6025
|
-
this.
|
|
6026
|
-
this.
|
|
6027
|
-
this.
|
|
6303
|
+
if (!this.assertNotExternalMode("visibleRect")) return;
|
|
6304
|
+
this.visibleRectValue = rect;
|
|
6305
|
+
if (this.initialized && !this.disposed) {
|
|
6306
|
+
this.viewportSystem.updateViewport();
|
|
6307
|
+
this.update();
|
|
6308
|
+
}
|
|
6028
6309
|
}
|
|
6029
6310
|
/**
|
|
6030
6311
|
* Underlying {@link WebGLRenderer} instance
|
|
@@ -6040,64 +6321,73 @@ class Renderer {
|
|
|
6040
6321
|
return [this.canvas.width, this.canvas.height];
|
|
6041
6322
|
}
|
|
6042
6323
|
/**
|
|
6043
|
-
*
|
|
6044
|
-
*
|
|
6324
|
+
* Returns true if the renderer is in external mode, meaning that webgl context is managed outside of the renderer
|
|
6325
|
+
* (for example, when using Mapbox GL JS as a host context). In this mode renderer will not clear the canvas before
|
|
6326
|
+
* rendering, and will not automatically compute scene and camera transformations. Clients are responsible for setting
|
|
6327
|
+
* the matrices manually by using {@link Renderer.viewport} methods.
|
|
6045
6328
|
*/
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
this.renderer.autoClear = false;
|
|
6049
|
-
this.interactionsSystem.disconnect();
|
|
6050
|
-
this.viewportSystem.setExternalTransform(staticTransformMatrix);
|
|
6329
|
+
get isExternalMode() {
|
|
6330
|
+
return this.gl !== void 0;
|
|
6051
6331
|
}
|
|
6052
6332
|
/**
|
|
6053
|
-
*
|
|
6054
|
-
* @param dynamicTransformMatrix dynamic transform matrix (changes every frame)
|
|
6333
|
+
* Returns true if the renderer is initialized, meaning that the viewport and scene have been set up.
|
|
6055
6334
|
*/
|
|
6056
|
-
|
|
6057
|
-
this.
|
|
6335
|
+
get isInitialized() {
|
|
6336
|
+
return this.initialized;
|
|
6058
6337
|
}
|
|
6059
6338
|
/**
|
|
6060
|
-
*
|
|
6339
|
+
* Returns true if the renderer is disposed, meaning that all WebGL resources have been released.
|
|
6340
|
+
*/
|
|
6341
|
+
get isDisposed() {
|
|
6342
|
+
return this.disposed;
|
|
6343
|
+
}
|
|
6344
|
+
/**
|
|
6345
|
+
* Initializes viewport and scene with the given scene definition.
|
|
6346
|
+
* Should be called once on startup. Repeated calls will produce console warnings.
|
|
6061
6347
|
* @param sceneDef {@link SceneDef} to render
|
|
6062
|
-
* @param startLoop whether to start the rendering loop
|
|
6063
6348
|
*/
|
|
6064
|
-
|
|
6065
|
-
this.
|
|
6349
|
+
init(sceneDef) {
|
|
6350
|
+
if (!this.assertNotDisposed("init")) return;
|
|
6351
|
+
if (!this.assertNotInitialized("init")) return;
|
|
6066
6352
|
this.viewportSystem.initViewport(sceneDef);
|
|
6353
|
+
this.interactionsSystem.init();
|
|
6067
6354
|
this.viewportSystem.scene.add(this.layerSystem.buildScene(sceneDef));
|
|
6068
|
-
|
|
6355
|
+
this.initialized = true;
|
|
6356
|
+
}
|
|
6357
|
+
/**
|
|
6358
|
+
* Start the rendering loop
|
|
6359
|
+
*/
|
|
6360
|
+
start() {
|
|
6361
|
+
if (!this.assertNotDisposed("start")) return;
|
|
6362
|
+
if (!this.assertInitialized("start")) return;
|
|
6363
|
+
if (this.clock.running) return;
|
|
6364
|
+
this.clock.start();
|
|
6365
|
+
this.renderer.setAnimationLoop(() => this.render());
|
|
6069
6366
|
}
|
|
6070
6367
|
/**
|
|
6071
6368
|
* Update the given defs to make them reflect the current state
|
|
6072
6369
|
* @param defs {@link RenderableDef} array to update
|
|
6073
6370
|
*/
|
|
6074
6371
|
update(...defs) {
|
|
6372
|
+
if (!this.assertNotDisposed("update")) return;
|
|
6373
|
+
if (!this.assertInitialized("update")) return;
|
|
6075
6374
|
this.layerSystem.updateDefs(defs);
|
|
6076
6375
|
this.needsRedraw = true;
|
|
6077
6376
|
}
|
|
6078
6377
|
/**
|
|
6079
|
-
*
|
|
6080
|
-
* @param point point in canvas space (relative to the canvas's top left corner), in css pixels
|
|
6081
|
-
* @returns point in SVG space
|
|
6082
|
-
*/
|
|
6083
|
-
screenToSvg(point) {
|
|
6084
|
-
const vector2 = new Vector2(point.x, point.y);
|
|
6085
|
-
canvasToNDC(vector2, this.canvas, vector2);
|
|
6086
|
-
return this.viewportSystem.screenTo("svg", vector2);
|
|
6087
|
-
}
|
|
6088
|
-
/**
|
|
6089
|
-
* Main rendering loop
|
|
6378
|
+
* Render a single frame
|
|
6090
6379
|
*/
|
|
6091
6380
|
render() {
|
|
6092
6381
|
var _a2, _b, _c, _d, _e, _f;
|
|
6382
|
+
if (!this.assertNotDisposed("render")) return;
|
|
6383
|
+
if (!this.assertInitialized("render")) return;
|
|
6093
6384
|
(_b = (_a2 = this.ui) == null ? void 0 : _a2.stats) == null ? void 0 : _b.begin();
|
|
6094
|
-
if (this.
|
|
6385
|
+
if (this.isExternalMode) this.renderer.resetState();
|
|
6095
6386
|
else this.resizeCanvasToDisplaySize();
|
|
6096
6387
|
this.viewportSystem.updatePtScale();
|
|
6097
|
-
const delta = this.clock.getDelta();
|
|
6098
6388
|
const hasDefsUpdated = this.layerSystem.processPendingUpdates();
|
|
6099
|
-
const hasControlsUpdated = this.interactionsSystem.updateControls(
|
|
6100
|
-
const needsRedraw = this.needsRedraw || hasControlsUpdated || hasDefsUpdated || this.ui;
|
|
6389
|
+
const hasControlsUpdated = this.interactionsSystem.updateControls(this.clock.getDelta());
|
|
6390
|
+
const needsRedraw = this.needsRedraw || hasControlsUpdated || hasDefsUpdated || this.isExternalMode || this.ui;
|
|
6101
6391
|
if (needsRedraw) {
|
|
6102
6392
|
this.renderer.render(this.viewportSystem.scene, this.viewportSystem.camera);
|
|
6103
6393
|
this.needsRedraw = false;
|
|
@@ -6106,6 +6396,30 @@ class Renderer {
|
|
|
6106
6396
|
(_f = (_e = this.ui) == null ? void 0 : _e.stats) == null ? void 0 : _f.update();
|
|
6107
6397
|
this.updateMemoryInfo();
|
|
6108
6398
|
}
|
|
6399
|
+
/**
|
|
6400
|
+
* Stop the rendering loop
|
|
6401
|
+
*/
|
|
6402
|
+
stop() {
|
|
6403
|
+
this.renderer.setAnimationLoop(null);
|
|
6404
|
+
this.clock.stop();
|
|
6405
|
+
}
|
|
6406
|
+
/**
|
|
6407
|
+
* Dispose all WebGL resources. This calls {@link Renderer.stop} internally.
|
|
6408
|
+
* WARNING: This method is final and cannot be undone. Attempting to use the renderer after calling this method
|
|
6409
|
+
* will result in a console warning and no-op methods. If you need to re-initialize the renderer, create a new instance.
|
|
6410
|
+
*/
|
|
6411
|
+
dispose() {
|
|
6412
|
+
if (this.disposed) return;
|
|
6413
|
+
this.stop();
|
|
6414
|
+
this.interactionsSystem.dispose();
|
|
6415
|
+
this.layerSystem.disposeScene();
|
|
6416
|
+
this.viewportSystem.scene.clear();
|
|
6417
|
+
this.renderer.clear();
|
|
6418
|
+
this.renderer.info.reset();
|
|
6419
|
+
this.renderer.dispose();
|
|
6420
|
+
this.updateMemoryInfo();
|
|
6421
|
+
this.disposed = true;
|
|
6422
|
+
}
|
|
6109
6423
|
// https://webgl2fundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
|
|
6110
6424
|
resizeCanvasToDisplaySize() {
|
|
6111
6425
|
const dpr = window.devicePixelRatio;
|
|
@@ -6121,38 +6435,100 @@ class Renderer {
|
|
|
6121
6435
|
}
|
|
6122
6436
|
}
|
|
6123
6437
|
updateMemoryInfo() {
|
|
6124
|
-
var _a2;
|
|
6438
|
+
var _a2, _b, _c, _d;
|
|
6125
6439
|
if (this.memoryInfoExtension && ((_a2 = this.ui) == null ? void 0 : _a2.memoryInfoPanel)) {
|
|
6126
6440
|
const memoryInfo = this.memoryInfoExtension.getMemoryInfo();
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6441
|
+
memoryInfo.resources["drawCalls"] = this.renderer.info.render.calls;
|
|
6442
|
+
if (memoryInfo.memory["texture"] !== ((_b = this.memoryInfo) == null ? void 0 : _b.memory["texture"]) || memoryInfo.memory["buffer"] !== ((_c = this.memoryInfo) == null ? void 0 : _c.memory["buffer"]) || memoryInfo.memory["renderbuffer"] !== ((_d = this.memoryInfo) == null ? void 0 : _d.memory["renderbuffer"])) {
|
|
6443
|
+
const elapsedTime = this.clock.getElapsedTime() * 1e3;
|
|
6130
6444
|
const logMarker = `memoryInfo [${elapsedTime.toFixed(2)}ms since start]`;
|
|
6131
6445
|
if (this.debugLog) console.log(logMarker, memoryInfo);
|
|
6132
|
-
this.memoryInfo =
|
|
6446
|
+
this.memoryInfo = memoryInfo;
|
|
6447
|
+
this.ui.memoryInfoPanel.textContent = JSON.stringify(memoryInfo, null, 2);
|
|
6133
6448
|
}
|
|
6134
|
-
this.ui.memoryInfoPanel.textContent = JSON.stringify(memoryInfo, null, 2);
|
|
6135
6449
|
}
|
|
6136
6450
|
}
|
|
6137
6451
|
onContextLost(event) {
|
|
6452
|
+
var _a2, _b;
|
|
6138
6453
|
event.preventDefault();
|
|
6139
6454
|
console.log("webglcontextlost event", event);
|
|
6140
|
-
this.
|
|
6141
|
-
this.
|
|
6142
|
-
if (
|
|
6455
|
+
const stats = (_a2 = this.ui) == null ? void 0 : _a2.stats;
|
|
6456
|
+
const context = this.renderer.getContext();
|
|
6457
|
+
if (stats && "deleteQuery" in context) {
|
|
6458
|
+
const gpuQueries = stats.gpuQueries;
|
|
6459
|
+
for (const queryInfo of gpuQueries) {
|
|
6460
|
+
this.renderer.getContext().deleteQuery(queryInfo.query);
|
|
6461
|
+
}
|
|
6462
|
+
stats.gpuQueries = [];
|
|
6463
|
+
if (stats.gpuPanel) {
|
|
6464
|
+
stats.dom.removeChild((_b = stats.gpuPanel) == null ? void 0 : _b.canvas);
|
|
6465
|
+
stats.gpuPanel = null;
|
|
6466
|
+
stats._panelId--;
|
|
6467
|
+
}
|
|
6468
|
+
}
|
|
6469
|
+
this.stop();
|
|
6143
6470
|
}
|
|
6144
6471
|
onContextRestored(event) {
|
|
6145
6472
|
event.preventDefault();
|
|
6146
6473
|
console.log("webglcontextrestored event", event);
|
|
6147
|
-
this.
|
|
6148
|
-
this.
|
|
6474
|
+
this.initContext(this.renderer.getContext());
|
|
6475
|
+
this.needsRedraw = true;
|
|
6476
|
+
this.start();
|
|
6477
|
+
}
|
|
6478
|
+
initContext(context) {
|
|
6479
|
+
var _a2, _b;
|
|
6480
|
+
this.memoryInfoExtension = context.getExtension("GMAN_webgl_memory");
|
|
6481
|
+
void ((_b = (_a2 = this.ui) == null ? void 0 : _a2.stats) == null ? void 0 : _b.init(context));
|
|
6482
|
+
}
|
|
6483
|
+
assertNotDisposed(funcName) {
|
|
6484
|
+
if (this.disposed) {
|
|
6485
|
+
console.warn(`[Renderer.${funcName}]: Renderer is used after being disposed. Please create a new instance.`);
|
|
6486
|
+
return false;
|
|
6487
|
+
}
|
|
6488
|
+
return true;
|
|
6489
|
+
}
|
|
6490
|
+
assertInitialized(funcName) {
|
|
6491
|
+
if (!this.initialized) {
|
|
6492
|
+
console.warn(`[Renderer.${funcName}]: Renderer is not initialized. Please call init() before using it.`);
|
|
6493
|
+
return false;
|
|
6494
|
+
}
|
|
6495
|
+
return true;
|
|
6496
|
+
}
|
|
6497
|
+
assertNotInitialized(funcName) {
|
|
6498
|
+
if (this.initialized) {
|
|
6499
|
+
console.warn(`[Renderer.${funcName}]: Renderer is already initialized. Please call init() only once.`);
|
|
6500
|
+
return false;
|
|
6501
|
+
}
|
|
6502
|
+
return true;
|
|
6149
6503
|
}
|
|
6504
|
+
assertNotExternalMode(funcName) {
|
|
6505
|
+
if (this.isExternalMode) {
|
|
6506
|
+
console.warn(`[Renderer.${funcName}]: This operation is not supported in external mode.`);
|
|
6507
|
+
return false;
|
|
6508
|
+
}
|
|
6509
|
+
return true;
|
|
6510
|
+
}
|
|
6511
|
+
assertExternalMode(funcName) {
|
|
6512
|
+
if (!this.isExternalMode) {
|
|
6513
|
+
console.warn(`[Renderer.${funcName}]: This operation is only supported in external mode.`);
|
|
6514
|
+
return false;
|
|
6515
|
+
}
|
|
6516
|
+
return true;
|
|
6517
|
+
}
|
|
6518
|
+
}
|
|
6519
|
+
function guardFn(guard, fn, ...fallback) {
|
|
6520
|
+
return (...args) => {
|
|
6521
|
+
const name = fn.name.split(" ").at(-1);
|
|
6522
|
+
if (!guard(name)) return fallback[0];
|
|
6523
|
+
return fn(...args);
|
|
6524
|
+
};
|
|
6150
6525
|
}
|
|
6151
6526
|
export {
|
|
6152
6527
|
Polygon,
|
|
6153
6528
|
Rect,
|
|
6154
6529
|
Renderer,
|
|
6155
6530
|
createVector2,
|
|
6531
|
+
createVector3,
|
|
6156
6532
|
isImageDef,
|
|
6157
6533
|
isImageLayer,
|
|
6158
6534
|
isLayerDef,
|