@expofp/renderer 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +70 -26
  2. package/dist/index.js +666 -566
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,372 +2,16 @@ 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 { 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";
5
+ import { DataTexture, FloatType, UnsignedIntType, IntType, RGBAFormat, RGBAIntegerFormat, RGFormat, RGIntegerFormat, RedFormat, RedIntegerFormat, BatchedMesh as BatchedMesh$1, BufferAttribute, StreamDrawUsage, Color, Matrix4, Vector3, Vector4, AlwaysDepth, DoubleSide, MeshBasicMaterial, Texture, Group, PlaneGeometry, SRGBColorSpace, Vector2, Quaternion, BufferGeometry, Mesh, LessEqualDepth, 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
- import { BatchedText as BatchedText$1, Text as Text$1 } from "troika-three-text";
8
7
  import createLog from "debug";
8
+ import { BatchedText as BatchedText$1, Text as Text$1 } from "troika-three-text";
9
9
  import { LineMaterial, LineSegmentsGeometry } from "three/examples/jsm/Addons.js";
10
10
  import { MaxRectsPacker, Rectangle } from "maxrects-packer";
11
11
  import { extend, colord } from "colord";
12
12
  import namesPlugin from "colord/plugins/names";
13
13
  import { RAD2DEG, DEG2RAD as DEG2RAD$1 } from "three/src/math/MathUtils.js";
14
14
  import { EventManager, Rotate, Pan } from "mjolnir.js";
15
- const floatsPerMember = 32;
16
- const tempColor = new Color();
17
- const defaultStrokeColor = 8421504;
18
- const tempMat4 = new Matrix4();
19
- const tempVec3a = new Vector3();
20
- const tempVec3b = new Vector3();
21
- const origin = new Vector3();
22
- const defaultOrient = "+x+y";
23
- class BatchedText extends BatchedText$1 {
24
- // eslint-disable-next-line jsdoc/require-jsdoc
25
- constructor() {
26
- super();
27
- __publicField(this, "mapInstanceIdToText", /* @__PURE__ */ new Map());
28
- __publicField(this, "textArray", []);
29
- __publicField(this, "textureNeedsUpdate", false);
30
- }
31
- /** Number of texts in the batch */
32
- get size() {
33
- return this._members.size;
34
- }
35
- /** Base material before patching */
36
- get baseMaterial() {
37
- return this._baseMaterial;
38
- }
39
- /**
40
- * Get the {@link Text} object by instance id
41
- * @param instanceId Instance id
42
- * @returns Text object
43
- */
44
- getText(instanceId) {
45
- return this.mapInstanceIdToText.get(instanceId);
46
- }
47
- /**
48
- * Set the visibility of the {@link Text} object by instance id.
49
- * This is for interface compatibility with {@link BatchedMesh}.
50
- * @param instanceId Instance id
51
- * @param visible Visibility flag
52
- */
53
- setVisibleAt(instanceId, visible) {
54
- const text = this.getText(instanceId);
55
- text.visible = visible;
56
- }
57
- addText(text, instanceId) {
58
- super.addText(text);
59
- if (instanceId !== void 0) {
60
- this.mapInstanceIdToText.set(instanceId, text);
61
- }
62
- this.textArray.push(text);
63
- }
64
- dispose() {
65
- super.dispose();
66
- this.dispatchEvent({ type: "dispose" });
67
- }
68
- // TODO: Check performance
69
- _prepareForRender(material) {
70
- var _a2;
71
- const isOutline = material.isTextOutlineMaterial;
72
- material.uniforms.uTroikaIsOutline.value = isOutline;
73
- let texture = this._dataTextures[isOutline ? "outline" : "main"];
74
- const dataLength = Math.pow(2, Math.ceil(Math.log2(this._members.size * floatsPerMember)));
75
- if (!texture || dataLength !== texture.image.data.length) {
76
- if (texture) texture.dispose();
77
- const width = Math.min(dataLength / 4, 1024);
78
- texture = this._dataTextures[isOutline ? "outline" : "main"] = new DataTexture(
79
- new Float32Array(dataLength),
80
- width,
81
- dataLength / 4 / width,
82
- RGBAFormat,
83
- FloatType
84
- );
85
- }
86
- const texData = texture.image.data;
87
- this.textureNeedsUpdate = false;
88
- for (const text of this.textArray) {
89
- const index = ((_a2 = this._members.get(text)) == null ? void 0 : _a2.index) ?? -1;
90
- const textRenderInfo = text.textRenderInfo;
91
- if (index < 0 || !textRenderInfo) continue;
92
- const startIndex = index * floatsPerMember;
93
- if (!text.visible) {
94
- for (let i = 0; i < 16; i++) {
95
- this.setTexData(startIndex + i, 0, texData);
96
- }
97
- continue;
98
- }
99
- const matrix = text.matrix.elements;
100
- for (let i = 0; i < 16; i++) {
101
- this.setTexData(startIndex + i, matrix[i], texData);
102
- }
103
- text._prepareForRender(material);
104
- const {
105
- uTroikaTotalBounds,
106
- uTroikaClipRect,
107
- uTroikaPositionOffset,
108
- uTroikaEdgeOffset,
109
- uTroikaBlurRadius,
110
- uTroikaStrokeWidth,
111
- uTroikaStrokeColor,
112
- uTroikaStrokeOpacity,
113
- uTroikaFillOpacity,
114
- uTroikaCurveRadius
115
- } = material.uniforms;
116
- for (let i = 0; i < 4; i++) {
117
- this.setTexData(startIndex + 16 + i, uTroikaTotalBounds.value.getComponent(i), texData);
118
- }
119
- for (let i = 0; i < 4; i++) {
120
- this.setTexData(startIndex + 20 + i, uTroikaClipRect.value.getComponent(i), texData);
121
- }
122
- let color = isOutline ? text.outlineColor || 0 : text.color;
123
- color ?? (color = this.color);
124
- color ?? (color = this.material.color);
125
- color ?? (color = 16777215);
126
- this.setTexData(startIndex + 24, tempColor.set(color).getHex(), texData);
127
- this.setTexData(startIndex + 25, uTroikaFillOpacity.value, texData);
128
- this.setTexData(startIndex + 26, uTroikaCurveRadius.value, texData);
129
- if (isOutline) {
130
- this.setTexData(startIndex + 28, uTroikaPositionOffset.value.x, texData);
131
- this.setTexData(startIndex + 29, uTroikaPositionOffset.value.y, texData);
132
- this.setTexData(startIndex + 30, uTroikaEdgeOffset.value, texData);
133
- this.setTexData(startIndex + 31, uTroikaBlurRadius.value, texData);
134
- } else {
135
- this.setTexData(startIndex + 28, uTroikaStrokeWidth.value, texData);
136
- this.setTexData(startIndex + 29, tempColor.set(uTroikaStrokeColor.value).getHex(), texData);
137
- this.setTexData(startIndex + 30, uTroikaStrokeOpacity.value, texData);
138
- }
139
- }
140
- texture.needsUpdate = this.textureNeedsUpdate;
141
- material.setMatrixTexture(texture);
142
- }
143
- setTexData(index, value, texData) {
144
- if (value !== texData[index]) {
145
- texData[index] = value;
146
- this.textureNeedsUpdate = true;
147
- }
148
- }
149
- }
150
- class Text extends Text$1 {
151
- _prepareForRender(material) {
152
- const isOutline = material.isTextOutlineMaterial;
153
- const uniforms = material.uniforms;
154
- const textInfo = this.textRenderInfo;
155
- if (textInfo) {
156
- const { sdfTexture, blockBounds } = textInfo;
157
- const { width, height } = sdfTexture.image;
158
- uniforms.uTroikaSDFTexture.value = sdfTexture;
159
- uniforms.uTroikaSDFTextureSize.value.set(width, height);
160
- uniforms.uTroikaSDFGlyphSize.value = textInfo.sdfGlyphSize;
161
- uniforms.uTroikaSDFExponent.value = textInfo.sdfExponent;
162
- uniforms.uTroikaTotalBounds.value.fromArray(blockBounds);
163
- uniforms.uTroikaUseGlyphColors.value = !isOutline && !!textInfo.glyphColors;
164
- let distanceOffset = 0;
165
- let blurRadius = 0;
166
- let strokeWidth = 0;
167
- let fillOpacity;
168
- let strokeOpacity = 1;
169
- let strokeColor;
170
- let offsetX = 0;
171
- let offsetY = 0;
172
- if (isOutline) {
173
- const { outlineWidth, outlineOffsetX, outlineOffsetY, outlineBlur, outlineOpacity } = this;
174
- distanceOffset = this._parsePercent(outlineWidth) || 0;
175
- blurRadius = Math.max(0, this._parsePercent(outlineBlur) || 0);
176
- fillOpacity = outlineOpacity;
177
- offsetX = this._parsePercent(outlineOffsetX) || 0;
178
- offsetY = this._parsePercent(outlineOffsetY) || 0;
179
- } else {
180
- strokeWidth = Math.max(0, this._parsePercent(this.strokeWidth) || 0);
181
- if (strokeWidth) {
182
- strokeColor = this.strokeColor;
183
- uniforms.uTroikaStrokeColor.value.set(strokeColor ?? defaultStrokeColor);
184
- strokeOpacity = this.strokeOpacity;
185
- strokeOpacity ?? (strokeOpacity = 1);
186
- }
187
- fillOpacity = this.fillOpacity;
188
- }
189
- uniforms.uTroikaEdgeOffset.value = distanceOffset;
190
- uniforms.uTroikaPositionOffset.value.set(offsetX, offsetY);
191
- uniforms.uTroikaBlurRadius.value = blurRadius;
192
- uniforms.uTroikaStrokeWidth.value = strokeWidth;
193
- uniforms.uTroikaStrokeOpacity.value = strokeOpacity;
194
- uniforms.uTroikaFillOpacity.value = fillOpacity ?? 1;
195
- uniforms.uTroikaCurveRadius.value = this.curveRadius || 0;
196
- const clipRect = this.clipRect;
197
- if (clipRect && Array.isArray(clipRect) && clipRect.length === 4) {
198
- uniforms.uTroikaClipRect.value.fromArray(clipRect);
199
- } else {
200
- const pad = (this.fontSize || 0.1) * 100;
201
- uniforms.uTroikaClipRect.value.set(
202
- blockBounds[0] - pad,
203
- blockBounds[1] - pad,
204
- blockBounds[2] + pad,
205
- blockBounds[3] + pad
206
- );
207
- }
208
- this.geometry.applyClipRect(uniforms.uTroikaClipRect.value);
209
- }
210
- uniforms.uTroikaSDFDebug.value = !!this.debugSDF;
211
- material.polygonOffset = !!this.depthOffset;
212
- material.polygonOffsetFactor = material.polygonOffsetUnits = this.depthOffset || 0;
213
- const color = isOutline ? this.outlineColor || 0 : this.color;
214
- if (color == null) {
215
- delete material.color;
216
- } else {
217
- const colorObj = material.hasOwnProperty("color") ? material.color : material.color = new Color();
218
- if (color !== colorObj._input || typeof color === "object") {
219
- colorObj.set(colorObj._input = color);
220
- }
221
- }
222
- let orient = this.orientation || defaultOrient;
223
- if (orient !== material._orientation) {
224
- const rotMat = uniforms.uTroikaOrient.value;
225
- orient = orient.replace(/[^-+xyz]/g, "");
226
- const match = orient !== defaultOrient && /^([-+])([xyz])([-+])([xyz])$/.exec(orient);
227
- if (match) {
228
- const [, hSign, hAxis, vSign, vAxis] = match;
229
- tempVec3a.set(0, 0, 0)[hAxis] = hSign === "-" ? 1 : -1;
230
- tempVec3b.set(0, 0, 0)[vAxis] = vSign === "-" ? -1 : 1;
231
- tempMat4.lookAt(origin, tempVec3a.cross(tempVec3b), tempVec3b);
232
- rotMat.setFromMatrix4(tempMat4);
233
- } else {
234
- rotMat.identity();
235
- }
236
- material._orientation = orient;
237
- }
238
- }
239
- }
240
- function setDimming(root, dim) {
241
- root.userData["uDim"] = dim === void 0 ? void 0 : +dim;
242
- }
243
- function toggleInstanceDim(object, instanceId, dim) {
244
- const value = dim === void 0 ? 0 : (+dim - 0.5) * 2;
245
- if (object instanceof BatchedMesh) {
246
- object.setUniformAt(instanceId, "skipDimInstance", value);
247
- return;
248
- }
249
- const skipDimTexture = object.userData["skipDimTexture"];
250
- if (skipDimTexture) {
251
- const skipDimData = skipDimTexture.image.data;
252
- skipDimData[instanceId] = value;
253
- skipDimTexture.needsUpdate = true;
254
- }
255
- }
256
- function addDimToMaterial(material) {
257
- if (material.userData.hasDimShader) return;
258
- const onBeforeCompile = material.onBeforeCompile.bind(material);
259
- const onBeforeRender = material.onBeforeRender.bind(material);
260
- material.onBeforeCompile = (shader, renderer) => {
261
- onBeforeCompile(shader, renderer);
262
- shader.uniforms["uDim"] = { value: material.userData.uDim ?? 0 };
263
- shader.uniforms["skipDimTexture"] = { value: material.userData.skipDimTexture ?? null };
264
- shader.vertexShader = shader.vertexShader.replace("void main() {", `${dimColorVertexDefs}
265
- void main() {`).replace(
266
- "#include <fog_vertex>",
267
- /*glsl*/
268
- `
269
- #include <fog_vertex>
270
- setDimAmount();
271
- `
272
- ).concat(dimColorVertexImpl);
273
- shader.fragmentShader = /*glsl*/
274
- `
275
- ${dimColorFrag}
276
- ${shader.fragmentShader}
277
- `.replace(
278
- "#include <colorspace_fragment>",
279
- /*glsl*/
280
- `
281
- gl_FragColor = dimColor(gl_FragColor);
282
- #include <colorspace_fragment>
283
- `
284
- );
285
- material.userData.shader = shader;
286
- };
287
- material.onBeforeRender = (renderer, scene, camera, geometry, object, group) => {
288
- onBeforeRender(renderer, scene, camera, geometry, object, group);
289
- const skipDimTexture = object.userData["skipDimTexture"];
290
- let uDim = object.userData["uDim"];
291
- if (uDim === void 0) {
292
- for (const ancestor of traverseAncestorsGenerator(object)) {
293
- if (ancestor.userData["uDim"] !== void 0) {
294
- uDim = ancestor.userData["uDim"];
295
- break;
296
- }
297
- }
298
- }
299
- const shader = material.userData.shader;
300
- if (!shader) {
301
- material.userData.uDim = uDim;
302
- material.userData.skipDimTexture = object.userData["skipDimTexture"];
303
- return;
304
- }
305
- shader.uniforms["uDim"].value = uDim ?? 0;
306
- shader.uniforms["skipDimTexture"].value = skipDimTexture ?? null;
307
- };
308
- material.userData.hasDimShader = true;
309
- }
310
- function addDim(mesh) {
311
- if (mesh instanceof BatchedMesh) mesh.addPerInstanceUniforms({ vertex: { skipDimInstance: "float" } });
312
- if (mesh instanceof BatchedText) addSkipDimTexture(mesh);
313
- }
314
- function addSkipDimTexture(text) {
315
- const count = text.size;
316
- const size = Math.ceil(Math.sqrt(count));
317
- const array = new Float32Array(size * size);
318
- array.fill(0);
319
- const texture = new DataTexture(array, size, size, RedFormat, FloatType);
320
- texture.needsUpdate = true;
321
- text.userData["skipDimTexture"] = texture;
322
- text.addEventListener("dispose", () => texture.dispose());
323
- return texture;
324
- }
325
- const dimColorVertexDefs = (
326
- /*glsl*/
327
- `
328
- uniform float uDim;
329
- out float dimAmount;
330
- #ifdef TROIKA_DERIVED_MATERIAL_1
331
- uniform sampler2D skipDimTexture;
332
- #endif
333
- void setDimAmount();
334
- `
335
- );
336
- const dimColorVertexImpl = (
337
- /*glsl*/
338
- `
339
- void setDimAmount() {
340
- float instanceDim = 0.;
341
- #ifdef USE_BATCH_UNIFORMS
342
- instanceDim = batch_skipDimInstance;
343
- #endif
344
- #ifdef TROIKA_DERIVED_MATERIAL_1
345
- float indirectIndex = aTroikaTextBatchMemberIndex;
346
- int size = textureSize(skipDimTexture, 0).x;
347
- int i = int(indirectIndex);
348
- int x = i % size;
349
- int y = i / size;
350
- instanceDim = texelFetch(skipDimTexture, ivec2(x, y), 0).r;
351
- #endif
352
- dimAmount = instanceDim == 0. ? uDim : instanceDim / 2. + 0.5;
353
- }
354
- `
355
- );
356
- const dimColorFrag = (
357
- /*glsl*/
358
- `
359
- in float dimAmount;
360
-
361
- const vec3 grayWeights = vec3(0.299, 0.587, 0.114);
362
- const float darkenFactor = pow(2., 2.2); // Gamma corrected
363
-
364
- vec4 dimColor(vec4 col) {
365
- vec3 color = col.rgb / col.a;
366
- vec3 gray = vec3(dot(grayWeights, color));
367
- vec3 m = mix(color, gray / darkenFactor, dimAmount);
368
- return vec4(m * col.a, col.a);
369
- }`
370
- );
371
15
  function createLogger(namespace) {
372
16
  const fullNamespace = namespace ? `renderer:${namespace}` : "renderer";
373
17
  const info = createLog(fullNamespace);
@@ -653,7 +297,7 @@ class SquareDataTexture extends DataTexture {
653
297
  const componentsArray = ["r", "g", "b", "a"];
654
298
  const batchIdName = "batchId";
655
299
  const logger$a = createLogger("BatchedMesh");
656
- const _BatchedMesh = class _BatchedMesh extends BatchedMesh$1 {
300
+ class BatchedMesh extends BatchedMesh$1 {
657
301
  /**
658
302
  * @param instanceCount the max number of individual geometries planned to be added.
659
303
  * @param vertexCount the max number of vertices to be used by all geometries.
@@ -672,12 +316,6 @@ const _BatchedMesh = class _BatchedMesh extends BatchedMesh$1 {
672
316
  __publicField(this, "mapGeometryToInstanceId", /* @__PURE__ */ new Map());
673
317
  material.forceSinglePass = true;
674
318
  addDim(this);
675
- this.addEventListener("added", () => {
676
- if (!_BatchedMesh.useMultiDraw && this.geometry.index !== null) {
677
- this.geometry = this.geometry.toNonIndexed();
678
- this.resizeToFitGeometry(this.geometry);
679
- }
680
- });
681
319
  }
682
320
  /**
683
321
  * Appends uniform definitions to the current schema, and creates a new {@link SquareDataTexture} if needed.
@@ -698,45 +336,18 @@ const _BatchedMesh = class _BatchedMesh extends BatchedMesh$1 {
698
336
  if (!this.isMaterialPatched) this.patchMaterial(this.material);
699
337
  }
700
338
  addGeometry(geometry, reservedVertexRange, reservedIndexRange) {
701
- if (_BatchedMesh.useMultiDraw) return super.addGeometry(geometry, reservedVertexRange, reservedIndexRange);
702
- this.addBatchIdBuffer(geometry, this.instanceCount);
703
- const geometryId = super.addGeometry(geometry, reservedVertexRange, reservedIndexRange);
704
- this.geometryById.set(geometryId, geometry);
705
- return geometryId;
339
+ return super.addGeometry(geometry, reservedVertexRange, reservedIndexRange);
706
340
  }
707
341
  addInstance(geometryId) {
708
- if (_BatchedMesh.useMultiDraw) return super.addInstance(geometryId);
709
- if (this.mapGeometryToInstanceId.has(geometryId)) {
710
- const geometry = this.geometryById.get(geometryId);
711
- this.resizeToFitGeometry(geometry);
712
- geometryId = this.addGeometry(geometry);
713
- }
714
- const instanceId = super.addInstance(geometryId);
715
- this.mapGeometryToInstanceId.set(geometryId, instanceId);
716
- return instanceId;
342
+ return super.addInstance(geometryId);
717
343
  }
718
344
  onBeforeRender(renderer, scene, camera, geometry, material, group) {
719
345
  var _a2;
720
346
  (_a2 = this.uniformsTexture) == null ? void 0 : _a2.update();
721
- if (_BatchedMesh.useMultiDraw) return super.onBeforeRender(renderer, scene, camera, geometry, material, group);
722
- if (!this.indexBuffer) {
723
- const vertexCount = geometry.getAttribute("position").count;
724
- this.indexBuffer = new BufferAttribute(new Uint32Array(vertexCount), 1).setUsage(StreamDrawUsage);
725
- }
726
- super.onBeforeRender(renderer, scene, camera, geometry, material, group);
727
- this.batchCount = this.updateIndexBuffer(geometry);
728
- this._multiDrawCount = 0;
347
+ return super.onBeforeRender(renderer, scene, camera, geometry, material, group);
729
348
  }
730
349
  onAfterRender(renderer, scene, camera, geometry) {
731
- var _a2;
732
- if (_BatchedMesh.useMultiDraw) return;
733
- const batchCount = this.batchCount;
734
- const gl = renderer.getContext();
735
- if (geometry.index == null) return logger$a.debug("No index buffer", (_a2 = this.parent) == null ? void 0 : _a2.name);
736
- const type = this.getIndexType(gl, geometry.index);
737
- gl.drawElements(gl.TRIANGLES, batchCount, type, 0);
738
- renderer.info.update(batchCount, gl.TRIANGLES, 1);
739
- geometry.setIndex(null);
350
+ return;
740
351
  }
741
352
  /**
742
353
  * Retrieves the value of a uniform at the specified instance ID.
@@ -845,19 +456,7 @@ const _BatchedMesh = class _BatchedMesh extends BatchedMesh$1 {
845
456
  vertex: "",
846
457
  fragment: ""
847
458
  };
848
- const patch = (
849
- /*glsl*/
850
- `
851
- #ifdef gl_DrawID
852
- #define _gl_DrawID ${batchIdName}
853
- #else
854
- #define gl_DrawID ${batchIdName}
855
- #endif
856
- in int ${batchIdName};
857
- `
858
- );
859
459
  const main = "void main() {";
860
- if (!_BatchedMesh.useMultiDraw) shader.vertexShader = shader.vertexShader.replace(main, `${patch}${main}`);
861
460
  if (vertex) shader.vertexShader = shader.vertexShader.replace(main, vertex);
862
461
  if (fragment) shader.fragmentShader = shader.fragmentShader.replace(main, fragment);
863
462
  };
@@ -881,66 +480,421 @@ const _BatchedMesh = class _BatchedMesh extends BatchedMesh$1 {
881
480
  uniforms.push({ name, type, size });
882
481
  fetchInFragmentShader = false;
883
482
  }
884
- for (const name in fragmentSchema) {
885
- if (!vertexSchema[name]) {
886
- const type = fragmentSchema[name];
887
- const size = this.getUniformSize(type);
888
- totalSize += size;
889
- uniforms.push({ name, type, size });
483
+ for (const name in fragmentSchema) {
484
+ if (!vertexSchema[name]) {
485
+ const type = fragmentSchema[name];
486
+ const size = this.getUniformSize(type);
487
+ totalSize += size;
488
+ uniforms.push({ name, type, size });
489
+ }
490
+ }
491
+ uniforms.sort((a, b) => b.size - a.size);
492
+ const tempOffset = [];
493
+ for (const { name, size, type } of uniforms) {
494
+ const offset = this.getUniformOffset(size, tempOffset);
495
+ uniformMap.set(name, { offset, size, type });
496
+ }
497
+ const pixelsPerInstance = Math.ceil(totalSize / 4);
498
+ const channels = Math.min(totalSize, 4);
499
+ return { channels, texelsPerInstance: pixelsPerInstance, uniformMap, fetchInFragmentShader };
500
+ }
501
+ getUniformOffset(size, tempOffset) {
502
+ if (size < 4) {
503
+ for (let i = 0; i < tempOffset.length; i++) {
504
+ if (tempOffset[i] + size <= 4) {
505
+ const offset2 = i * 4 + tempOffset[i];
506
+ tempOffset[i] += size;
507
+ return offset2;
508
+ }
509
+ }
510
+ }
511
+ const offset = tempOffset.length * 4;
512
+ for (; size > 0; size -= 4) {
513
+ tempOffset.push(size);
514
+ }
515
+ return offset;
516
+ }
517
+ getUniformSize(type) {
518
+ switch (type) {
519
+ case "float":
520
+ return 1;
521
+ case "vec2":
522
+ return 2;
523
+ case "vec3":
524
+ return 3;
525
+ case "vec4":
526
+ return 4;
527
+ case "mat3":
528
+ return 9;
529
+ case "mat4":
530
+ return 16;
531
+ }
532
+ }
533
+ getIndexType(gl, index) {
534
+ const array = index.array;
535
+ if (array instanceof Uint16Array) return gl.UNSIGNED_SHORT;
536
+ if (array instanceof Uint32Array) return gl.UNSIGNED_INT;
537
+ return gl.UNSIGNED_BYTE;
538
+ }
539
+ }
540
+ /** Whether to use WebGL_multi_draw extension or less performant fallback */
541
+ __publicField(BatchedMesh, "useMultiDraw", true);
542
+ const floatsPerMember = 32;
543
+ const tempColor = new Color();
544
+ const defaultStrokeColor = 8421504;
545
+ const tempMat4 = new Matrix4();
546
+ const tempVec3a = new Vector3();
547
+ const tempVec3b = new Vector3();
548
+ const origin = new Vector3();
549
+ const defaultOrient = "+x+y";
550
+ class BatchedText extends BatchedText$1 {
551
+ // eslint-disable-next-line jsdoc/require-jsdoc
552
+ constructor() {
553
+ super();
554
+ __publicField(this, "mapInstanceIdToText", /* @__PURE__ */ new Map());
555
+ __publicField(this, "textArray", []);
556
+ __publicField(this, "textureNeedsUpdate", false);
557
+ }
558
+ /** Number of texts in the batch */
559
+ get size() {
560
+ return this._members.size;
561
+ }
562
+ /** Base material before patching */
563
+ get baseMaterial() {
564
+ return this._baseMaterial;
565
+ }
566
+ /**
567
+ * Get the {@link Text} object by instance id
568
+ * @param instanceId Instance id
569
+ * @returns Text object
570
+ */
571
+ getText(instanceId) {
572
+ return this.mapInstanceIdToText.get(instanceId);
573
+ }
574
+ /**
575
+ * Set the visibility of the {@link Text} object by instance id.
576
+ * This is for interface compatibility with {@link BatchedMesh}.
577
+ * @param instanceId Instance id
578
+ * @param visible Visibility flag
579
+ */
580
+ setVisibleAt(instanceId, visible) {
581
+ const text = this.getText(instanceId);
582
+ text.visible = visible;
583
+ }
584
+ addText(text, instanceId) {
585
+ super.addText(text);
586
+ if (instanceId !== void 0) {
587
+ this.mapInstanceIdToText.set(instanceId, text);
588
+ }
589
+ this.textArray.push(text);
590
+ }
591
+ dispose() {
592
+ super.dispose();
593
+ this.dispatchEvent({ type: "dispose" });
594
+ }
595
+ // TODO: Check performance
596
+ _prepareForRender(material) {
597
+ var _a2;
598
+ const isOutline = material.isTextOutlineMaterial;
599
+ material.uniforms.uTroikaIsOutline.value = isOutline;
600
+ let texture = this._dataTextures[isOutline ? "outline" : "main"];
601
+ const dataLength = Math.pow(2, Math.ceil(Math.log2(this._members.size * floatsPerMember)));
602
+ if (!texture || dataLength !== texture.image.data.length) {
603
+ if (texture) texture.dispose();
604
+ const width = Math.min(dataLength / 4, 1024);
605
+ texture = this._dataTextures[isOutline ? "outline" : "main"] = new DataTexture(
606
+ new Float32Array(dataLength),
607
+ width,
608
+ dataLength / 4 / width,
609
+ RGBAFormat,
610
+ FloatType
611
+ );
612
+ }
613
+ const texData = texture.image.data;
614
+ this.textureNeedsUpdate = false;
615
+ for (const text of this.textArray) {
616
+ const index = ((_a2 = this._members.get(text)) == null ? void 0 : _a2.index) ?? -1;
617
+ const textRenderInfo = text.textRenderInfo;
618
+ if (index < 0 || !textRenderInfo) continue;
619
+ const startIndex = index * floatsPerMember;
620
+ if (!text.visible) {
621
+ for (let i = 0; i < 16; i++) {
622
+ this.setTexData(startIndex + i, 0, texData);
623
+ }
624
+ continue;
625
+ }
626
+ const matrix = text.matrix.elements;
627
+ for (let i = 0; i < 16; i++) {
628
+ this.setTexData(startIndex + i, matrix[i], texData);
629
+ }
630
+ text._prepareForRender(material);
631
+ const {
632
+ uTroikaTotalBounds,
633
+ uTroikaClipRect,
634
+ uTroikaPositionOffset,
635
+ uTroikaEdgeOffset,
636
+ uTroikaBlurRadius,
637
+ uTroikaStrokeWidth,
638
+ uTroikaStrokeColor,
639
+ uTroikaStrokeOpacity,
640
+ uTroikaFillOpacity,
641
+ uTroikaCurveRadius
642
+ } = material.uniforms;
643
+ for (let i = 0; i < 4; i++) {
644
+ this.setTexData(startIndex + 16 + i, uTroikaTotalBounds.value.getComponent(i), texData);
645
+ }
646
+ for (let i = 0; i < 4; i++) {
647
+ this.setTexData(startIndex + 20 + i, uTroikaClipRect.value.getComponent(i), texData);
648
+ }
649
+ let color = isOutline ? text.outlineColor || 0 : text.color;
650
+ color ?? (color = this.color);
651
+ color ?? (color = this.material.color);
652
+ color ?? (color = 16777215);
653
+ this.setTexData(startIndex + 24, tempColor.set(color).getHex(), texData);
654
+ this.setTexData(startIndex + 25, uTroikaFillOpacity.value, texData);
655
+ this.setTexData(startIndex + 26, uTroikaCurveRadius.value, texData);
656
+ if (isOutline) {
657
+ this.setTexData(startIndex + 28, uTroikaPositionOffset.value.x, texData);
658
+ this.setTexData(startIndex + 29, uTroikaPositionOffset.value.y, texData);
659
+ this.setTexData(startIndex + 30, uTroikaEdgeOffset.value, texData);
660
+ this.setTexData(startIndex + 31, uTroikaBlurRadius.value, texData);
661
+ } else {
662
+ this.setTexData(startIndex + 28, uTroikaStrokeWidth.value, texData);
663
+ this.setTexData(startIndex + 29, tempColor.set(uTroikaStrokeColor.value).getHex(), texData);
664
+ this.setTexData(startIndex + 30, uTroikaStrokeOpacity.value, texData);
665
+ }
666
+ }
667
+ texture.needsUpdate = this.textureNeedsUpdate;
668
+ material.setMatrixTexture(texture);
669
+ }
670
+ setTexData(index, value, texData) {
671
+ if (value !== texData[index]) {
672
+ texData[index] = value;
673
+ this.textureNeedsUpdate = true;
674
+ }
675
+ }
676
+ }
677
+ class Text extends Text$1 {
678
+ _prepareForRender(material) {
679
+ const isOutline = material.isTextOutlineMaterial;
680
+ const uniforms = material.uniforms;
681
+ const textInfo = this.textRenderInfo;
682
+ if (textInfo) {
683
+ const { sdfTexture, blockBounds } = textInfo;
684
+ const { width, height } = sdfTexture.image;
685
+ uniforms.uTroikaSDFTexture.value = sdfTexture;
686
+ uniforms.uTroikaSDFTextureSize.value.set(width, height);
687
+ uniforms.uTroikaSDFGlyphSize.value = textInfo.sdfGlyphSize;
688
+ uniforms.uTroikaSDFExponent.value = textInfo.sdfExponent;
689
+ uniforms.uTroikaTotalBounds.value.fromArray(blockBounds);
690
+ uniforms.uTroikaUseGlyphColors.value = !isOutline && !!textInfo.glyphColors;
691
+ let distanceOffset = 0;
692
+ let blurRadius = 0;
693
+ let strokeWidth = 0;
694
+ let fillOpacity;
695
+ let strokeOpacity = 1;
696
+ let strokeColor;
697
+ let offsetX = 0;
698
+ let offsetY = 0;
699
+ if (isOutline) {
700
+ const { outlineWidth, outlineOffsetX, outlineOffsetY, outlineBlur, outlineOpacity } = this;
701
+ distanceOffset = this._parsePercent(outlineWidth) || 0;
702
+ blurRadius = Math.max(0, this._parsePercent(outlineBlur) || 0);
703
+ fillOpacity = outlineOpacity;
704
+ offsetX = this._parsePercent(outlineOffsetX) || 0;
705
+ offsetY = this._parsePercent(outlineOffsetY) || 0;
706
+ } else {
707
+ strokeWidth = Math.max(0, this._parsePercent(this.strokeWidth) || 0);
708
+ if (strokeWidth) {
709
+ strokeColor = this.strokeColor;
710
+ uniforms.uTroikaStrokeColor.value.set(strokeColor ?? defaultStrokeColor);
711
+ strokeOpacity = this.strokeOpacity;
712
+ strokeOpacity ?? (strokeOpacity = 1);
713
+ }
714
+ fillOpacity = this.fillOpacity;
715
+ }
716
+ uniforms.uTroikaEdgeOffset.value = distanceOffset;
717
+ uniforms.uTroikaPositionOffset.value.set(offsetX, offsetY);
718
+ uniforms.uTroikaBlurRadius.value = blurRadius;
719
+ uniforms.uTroikaStrokeWidth.value = strokeWidth;
720
+ uniforms.uTroikaStrokeOpacity.value = strokeOpacity;
721
+ uniforms.uTroikaFillOpacity.value = fillOpacity ?? 1;
722
+ uniforms.uTroikaCurveRadius.value = this.curveRadius || 0;
723
+ const clipRect = this.clipRect;
724
+ if (clipRect && Array.isArray(clipRect) && clipRect.length === 4) {
725
+ uniforms.uTroikaClipRect.value.fromArray(clipRect);
726
+ } else {
727
+ const pad = (this.fontSize || 0.1) * 100;
728
+ uniforms.uTroikaClipRect.value.set(
729
+ blockBounds[0] - pad,
730
+ blockBounds[1] - pad,
731
+ blockBounds[2] + pad,
732
+ blockBounds[3] + pad
733
+ );
734
+ }
735
+ this.geometry.applyClipRect(uniforms.uTroikaClipRect.value);
736
+ }
737
+ uniforms.uTroikaSDFDebug.value = !!this.debugSDF;
738
+ material.polygonOffset = !!this.depthOffset;
739
+ material.polygonOffsetFactor = material.polygonOffsetUnits = this.depthOffset || 0;
740
+ const color = isOutline ? this.outlineColor || 0 : this.color;
741
+ if (color == null) {
742
+ delete material.color;
743
+ } else {
744
+ const colorObj = material.hasOwnProperty("color") ? material.color : material.color = new Color();
745
+ if (color !== colorObj._input || typeof color === "object") {
746
+ colorObj.set(colorObj._input = color);
890
747
  }
891
748
  }
892
- uniforms.sort((a, b) => b.size - a.size);
893
- const tempOffset = [];
894
- for (const { name, size, type } of uniforms) {
895
- const offset = this.getUniformOffset(size, tempOffset);
896
- uniformMap.set(name, { offset, size, type });
749
+ let orient = this.orientation || defaultOrient;
750
+ if (orient !== material._orientation) {
751
+ const rotMat = uniforms.uTroikaOrient.value;
752
+ orient = orient.replace(/[^-+xyz]/g, "");
753
+ const match = orient !== defaultOrient && /^([-+])([xyz])([-+])([xyz])$/.exec(orient);
754
+ if (match) {
755
+ const [, hSign, hAxis, vSign, vAxis] = match;
756
+ tempVec3a.set(0, 0, 0)[hAxis] = hSign === "-" ? 1 : -1;
757
+ tempVec3b.set(0, 0, 0)[vAxis] = vSign === "-" ? -1 : 1;
758
+ tempMat4.lookAt(origin, tempVec3a.cross(tempVec3b), tempVec3b);
759
+ rotMat.setFromMatrix4(tempMat4);
760
+ } else {
761
+ rotMat.identity();
762
+ }
763
+ material._orientation = orient;
897
764
  }
898
- const pixelsPerInstance = Math.ceil(totalSize / 4);
899
- const channels = Math.min(totalSize, 4);
900
- return { channels, texelsPerInstance: pixelsPerInstance, uniformMap, fetchInFragmentShader };
901
765
  }
902
- getUniformOffset(size, tempOffset) {
903
- if (size < 4) {
904
- for (let i = 0; i < tempOffset.length; i++) {
905
- if (tempOffset[i] + size <= 4) {
906
- const offset2 = i * 4 + tempOffset[i];
907
- tempOffset[i] += size;
908
- return offset2;
766
+ }
767
+ function setDimming(root, dim) {
768
+ root.userData["uDim"] = dim === void 0 ? void 0 : +dim;
769
+ }
770
+ function toggleInstanceDim(object, instanceId, dim) {
771
+ const value = dim === void 0 ? 0 : (+dim - 0.5) * 2;
772
+ if (object instanceof BatchedMesh) {
773
+ object.setUniformAt(instanceId, "skipDimInstance", value);
774
+ return;
775
+ }
776
+ const skipDimTexture = object.userData["skipDimTexture"];
777
+ if (skipDimTexture) {
778
+ const skipDimData = skipDimTexture.image.data;
779
+ skipDimData[instanceId] = value;
780
+ skipDimTexture.needsUpdate = true;
781
+ }
782
+ }
783
+ function addDimToMaterial(material) {
784
+ if (material.userData.hasDimShader) return;
785
+ const onBeforeCompile = material.onBeforeCompile.bind(material);
786
+ const onBeforeRender = material.onBeforeRender.bind(material);
787
+ material.onBeforeCompile = (shader, renderer) => {
788
+ onBeforeCompile(shader, renderer);
789
+ shader.uniforms["uDim"] = { value: material.userData.uDim ?? 0 };
790
+ shader.uniforms["skipDimTexture"] = { value: material.userData.skipDimTexture ?? null };
791
+ shader.vertexShader = shader.vertexShader.replace("void main() {", `${dimColorVertexDefs}
792
+ void main() {`).replace(
793
+ "#include <fog_vertex>",
794
+ /*glsl*/
795
+ `
796
+ #include <fog_vertex>
797
+ setDimAmount();
798
+ `
799
+ ).concat(dimColorVertexImpl);
800
+ shader.fragmentShader = /*glsl*/
801
+ `
802
+ ${dimColorFrag}
803
+ ${shader.fragmentShader}
804
+ `.replace(
805
+ "#include <colorspace_fragment>",
806
+ /*glsl*/
807
+ `
808
+ gl_FragColor = dimColor(gl_FragColor);
809
+ #include <colorspace_fragment>
810
+ `
811
+ );
812
+ material.userData.shader = shader;
813
+ };
814
+ material.onBeforeRender = (renderer, scene, camera, geometry, object, group) => {
815
+ onBeforeRender(renderer, scene, camera, geometry, object, group);
816
+ const skipDimTexture = object.userData["skipDimTexture"];
817
+ let uDim = object.userData["uDim"];
818
+ if (uDim === void 0) {
819
+ for (const ancestor of traverseAncestorsGenerator(object)) {
820
+ if (ancestor.userData["uDim"] !== void 0) {
821
+ uDim = ancestor.userData["uDim"];
822
+ break;
909
823
  }
910
824
  }
911
825
  }
912
- const offset = tempOffset.length * 4;
913
- for (; size > 0; size -= 4) {
914
- tempOffset.push(size);
915
- }
916
- return offset;
917
- }
918
- getUniformSize(type) {
919
- switch (type) {
920
- case "float":
921
- return 1;
922
- case "vec2":
923
- return 2;
924
- case "vec3":
925
- return 3;
926
- case "vec4":
927
- return 4;
928
- case "mat3":
929
- return 9;
930
- case "mat4":
931
- return 16;
826
+ const shader = material.userData.shader;
827
+ if (!shader) {
828
+ material.userData.uDim = uDim;
829
+ material.userData.skipDimTexture = object.userData["skipDimTexture"];
830
+ return;
932
831
  }
832
+ shader.uniforms["uDim"].value = uDim ?? 0;
833
+ shader.uniforms["skipDimTexture"].value = skipDimTexture ?? null;
834
+ };
835
+ material.userData.hasDimShader = true;
836
+ }
837
+ function addDim(mesh) {
838
+ if (mesh instanceof BatchedMesh) mesh.addPerInstanceUniforms({ vertex: { skipDimInstance: "float" } });
839
+ if (mesh instanceof BatchedText) addSkipDimTexture(mesh);
840
+ }
841
+ function addSkipDimTexture(text) {
842
+ const count = text.size;
843
+ const size = Math.ceil(Math.sqrt(count));
844
+ const array = new Float32Array(size * size);
845
+ array.fill(0);
846
+ const texture = new DataTexture(array, size, size, RedFormat, FloatType);
847
+ texture.needsUpdate = true;
848
+ text.userData["skipDimTexture"] = texture;
849
+ text.addEventListener("dispose", () => texture.dispose());
850
+ return texture;
851
+ }
852
+ const dimColorVertexDefs = (
853
+ /*glsl*/
854
+ `
855
+ uniform float uDim;
856
+ out float dimAmount;
857
+ #ifdef TROIKA_DERIVED_MATERIAL_1
858
+ uniform sampler2D skipDimTexture;
859
+ #endif
860
+ void setDimAmount();
861
+ `
862
+ );
863
+ const dimColorVertexImpl = (
864
+ /*glsl*/
865
+ `
866
+ void setDimAmount() {
867
+ float instanceDim = 0.;
868
+ #ifdef USE_BATCH_UNIFORMS
869
+ instanceDim = batch_skipDimInstance;
870
+ #endif
871
+ #ifdef TROIKA_DERIVED_MATERIAL_1
872
+ float indirectIndex = aTroikaTextBatchMemberIndex;
873
+ int size = textureSize(skipDimTexture, 0).x;
874
+ int i = int(indirectIndex);
875
+ int x = i % size;
876
+ int y = i / size;
877
+ instanceDim = texelFetch(skipDimTexture, ivec2(x, y), 0).r;
878
+ #endif
879
+ dimAmount = instanceDim == 0. ? uDim : instanceDim / 2. + 0.5;
933
880
  }
934
- getIndexType(gl, index) {
935
- const array = index.array;
936
- if (array instanceof Uint16Array) return gl.UNSIGNED_SHORT;
937
- if (array instanceof Uint32Array) return gl.UNSIGNED_INT;
938
- return gl.UNSIGNED_BYTE;
939
- }
940
- };
941
- /** Whether to use WebGL_multi_draw extension or less performant fallback (Firefox only) */
942
- __publicField(_BatchedMesh, "useMultiDraw", true);
943
- let BatchedMesh = _BatchedMesh;
881
+ `
882
+ );
883
+ const dimColorFrag = (
884
+ /*glsl*/
885
+ `
886
+ in float dimAmount;
887
+
888
+ const vec3 grayWeights = vec3(0.299, 0.587, 0.114);
889
+ const float darkenFactor = pow(2., 2.2); // Gamma corrected
890
+
891
+ vec4 dimColor(vec4 col) {
892
+ vec3 color = col.rgb / col.a;
893
+ vec3 gray = vec3(dot(grayWeights, color));
894
+ vec3 m = mix(color, gray / darkenFactor, dimAmount);
895
+ return vec4(m * col.a, col.a);
896
+ }`
897
+ );
944
898
  const sharedParameters = {
945
899
  side: DoubleSide,
946
900
  transparent: true,
@@ -1451,14 +1405,15 @@ class ImageSystem extends RenderableSystem {
1451
1405
  logger$9.debug(`New memory usage after resizing: ${newTotal} bytes`);
1452
1406
  }
1453
1407
  updateDefImpl(imageDef, mesh, instanceIds) {
1408
+ const instanceId = instanceIds[0];
1454
1409
  const bounds = imageDef.bounds;
1455
1410
  const origin2 = imageDef.origin ?? [0.5, 0.5];
1456
1411
  this.originTranslationMatrix.makeTranslation(0.5 - origin2[0], 0.5 - origin2[1], 0);
1457
1412
  this.globalTranslationMatrix.makeTranslation(bounds.center.x, bounds.center.y, 0);
1458
- this.scaleMatrix.makeScale(bounds.size.width, bounds.size.height, 1);
1413
+ this.scaleMatrix.makeScale(bounds.size.x, bounds.size.y, 1);
1459
1414
  this.rotationMatrix.makeRotationZ(bounds.rotation);
1460
1415
  const matrix = this.originTranslationMatrix.premultiply(this.scaleMatrix).premultiply(this.rotationMatrix).premultiply(this.globalTranslationMatrix);
1461
- mesh.setMatrixAt(instanceIds[0], matrix);
1416
+ mesh.setMatrixAt(instanceId, matrix);
1462
1417
  }
1463
1418
  packImages(images) {
1464
1419
  this.packer.reset();
@@ -1472,8 +1427,8 @@ class ImageSystem extends RenderableSystem {
1472
1427
  const sourceWidth = image.source.width;
1473
1428
  const sourceHeight = image.source.height;
1474
1429
  const sourceArea = sourceWidth * sourceHeight;
1475
- const boundsWidth = image.bounds.size.width;
1476
- const boundsHeight = image.bounds.size.height;
1430
+ const boundsWidth = image.bounds.size.x;
1431
+ const boundsHeight = image.bounds.size.y;
1477
1432
  const boundsArea = boundsWidth * boundsHeight;
1478
1433
  const ratio = sourceArea / boundsArea;
1479
1434
  if (ratio > 1e3) {
@@ -1593,13 +1548,10 @@ class LineSystem extends RenderableSystem {
1593
1548
  }
1594
1549
  }
1595
1550
  function createVector2(vector2) {
1596
- if (vector2 instanceof Vector2) return vector2;
1597
1551
  if (Array.isArray(vector2)) return new Vector2(vector2[0], vector2[1]);
1598
1552
  return new Vector2(vector2.x, vector2.y);
1599
1553
  }
1600
1554
  function createVector3(vector3) {
1601
- if (vector3 instanceof Vector2) return new Vector3(vector3.x, vector3.y, 0);
1602
- if (vector3 instanceof Vector3) return vector3;
1603
1555
  if (Array.isArray(vector3)) return new Vector3(vector3[0], vector3[1], vector3[2] ?? 0);
1604
1556
  return new Vector3(vector3.x, vector3.y, "z" in vector3 ? vector3.z : 0);
1605
1557
  }
@@ -1610,28 +1562,53 @@ class Rect {
1610
1562
  * @param rotation Optional rotation of the rectangle. In radians, around center. Positive values rotate clockwise.
1611
1563
  */
1612
1564
  constructor(min, max, rotation) {
1613
- /** Top left corner of the rectangle. */
1614
- __publicField(this, "min");
1615
- /** Bottom right corner of the rectangle. */
1616
- __publicField(this, "max");
1617
1565
  /** Optional rotation of the rectangle. In radians, around center. Positive values rotate clockwise. */
1618
1566
  __publicField(this, "rotation");
1619
- __publicField(this, "_center");
1620
- __publicField(this, "_size");
1621
- this.min = createVector2(min);
1622
- this.max = createVector2(max);
1567
+ __publicField(this, "_min");
1568
+ __publicField(this, "_max");
1569
+ __publicField(this, "_center", new Vector2());
1570
+ __publicField(this, "_size", new Vector2());
1571
+ this._min = createVector2(min);
1572
+ this._max = createVector2(max);
1623
1573
  this.rotation = rotation ?? 0;
1574
+ this.updateCenterAndSize();
1624
1575
  }
1625
- /** Center of the rectangle. Read-only, calculated on demand. */
1576
+ /** Top left corner of the rectangle. */
1577
+ get min() {
1578
+ return this._min;
1579
+ }
1580
+ /** Set top left corner of the rectangle. */
1581
+ set min(min) {
1582
+ this._min.copy(min);
1583
+ this.updateCenterAndSize();
1584
+ }
1585
+ /** Bottom right corner of the rectangle. */
1586
+ get max() {
1587
+ return this._max;
1588
+ }
1589
+ /** Set bottom right corner of the rectangle. */
1590
+ set max(max) {
1591
+ this._max.copy(max);
1592
+ this.updateCenterAndSize();
1593
+ }
1594
+ /** Center of the rectangle. */
1626
1595
  get center() {
1627
- this._center ?? (this._center = this.min.clone().add(this.max).multiplyScalar(0.5));
1628
1596
  return this._center;
1629
1597
  }
1630
- /** Size of the rectangle. Read-only, calculated on demand. */
1598
+ /** Set center of the rectangle. */
1599
+ set center(center) {
1600
+ this._center.copy(center);
1601
+ this.updateMinAndMax();
1602
+ }
1603
+ /** Size of the rectangle. */
1631
1604
  get size() {
1632
- this._size ?? (this._size = this.max.clone().sub(this.min));
1633
1605
  return this._size;
1634
1606
  }
1607
+ /** Set size of the rectangle. */
1608
+ set size(size) {
1609
+ this._size.copy(size);
1610
+ this.updateMinAndMax();
1611
+ }
1635
1612
  /**
1636
1613
  * Creates a rectangle from an SVG rectangle element.
1637
1614
  * @param rect {@link SVGRectElement} or {@link SVGImageElement}
@@ -1646,18 +1623,37 @@ class Rect {
1646
1623
  return new Rect([x, y], [x + width, y + height], rotation);
1647
1624
  }
1648
1625
  /**
1649
- * Increases the size of the rectangle by the given amount. Padding is added to both sides of the rectangle.
1650
- * @param x Horizontal padding.
1651
- * @param y Vertical padding. If omitted, defaults to the same value as `x`.
1626
+ * Moves the rectangle by the given offset.
1627
+ * @param offset Offset to move the rectangle by.
1628
+ * @returns this {@link Rect} instance
1629
+ */
1630
+ translate(offset) {
1631
+ this._center.add(offset);
1632
+ this.updateMinAndMax();
1633
+ return this;
1634
+ }
1635
+ /**
1636
+ * Expands the rectangle by the given amount on all sides.
1637
+ * Use positive values to increase the size of the rectangle, negative values to decrease it.
1638
+ * @param expansion Can be a single number or a 2D vector. If a single number is provided, both horizontal and vertical sizes will be expanded by the same amount.
1652
1639
  * @returns this {@link Rect} instance
1653
1640
  */
1654
- addPadding(x, y = x) {
1655
- this.min.add({ x, y });
1656
- this.max.sub({ x, y });
1657
- this._center = void 0;
1658
- this._size = void 0;
1641
+ expand(expansion) {
1642
+ const expansionVector = typeof expansion === "number" ? { x: expansion, y: expansion } : expansion;
1643
+ this._min.sub(expansionVector);
1644
+ this._max.add(expansionVector);
1645
+ this._size.subVectors(this._max, this._min);
1659
1646
  return this;
1660
1647
  }
1648
+ updateCenterAndSize() {
1649
+ this._center.addVectors(this._min, this._max).multiplyScalar(0.5);
1650
+ this._size.subVectors(this._max, this._min);
1651
+ }
1652
+ updateMinAndMax() {
1653
+ const halfSize = tempVector2.copy(this._size).multiplyScalar(0.5);
1654
+ this._min.subVectors(this._center, halfSize);
1655
+ this._max.addVectors(this._center, halfSize);
1656
+ }
1661
1657
  }
1662
1658
  class Polygon {
1663
1659
  /**
@@ -1665,12 +1661,24 @@ class Polygon {
1665
1661
  * @param indices Array of polygon indices. Each index is a triplet of vertex indices forming a triangle.
1666
1662
  */
1667
1663
  constructor(vertices, indices) {
1668
- /** Array of polygon vertices. */
1669
- __publicField(this, "vertices");
1670
- /** Array of polygon indices. Each index is a triplet of vertex indices forming a triangle. */
1671
- __publicField(this, "indices");
1672
- this.vertices = vertices.map(createVector3);
1673
- this.indices = indices;
1664
+ __publicField(this, "_indices");
1665
+ __publicField(this, "_vertices");
1666
+ __publicField(this, "_bbox");
1667
+ this._vertices = vertices.map(createVector3);
1668
+ this._indices = indices;
1669
+ this._bbox = this.computeBoundingRect();
1670
+ }
1671
+ /** Array of polygon vertices. */
1672
+ get vertices() {
1673
+ return this._vertices;
1674
+ }
1675
+ /** Array of polygon indices. Each index is a triplet of vertex indices forming a triangle. */
1676
+ get indices() {
1677
+ return this._indices;
1678
+ }
1679
+ /** Bounding rectangle of the polygon. */
1680
+ get bounds() {
1681
+ return this._bbox;
1674
1682
  }
1675
1683
  /**
1676
1684
  * Converts a {@link Rect} to a {@link Polygon}.
@@ -1699,28 +1707,76 @@ class Polygon {
1699
1707
  let indexOffset = 0;
1700
1708
  const vertices = [];
1701
1709
  const indices = [];
1702
- for (const polygon of polygons) {
1703
- vertices.push(...polygon.vertices);
1704
- indices.push(...polygon.indices.map((index) => index.map((i) => i + indexOffset)));
1705
- indexOffset += polygon.vertices.length;
1710
+ for (const p of polygons) {
1711
+ vertices.push(...p.vertices);
1712
+ indices.push(...p.indices.map(([i1, i2, i3]) => [i1 + indexOffset, i2 + indexOffset, i3 + indexOffset]));
1713
+ indexOffset += p.vertices.length;
1706
1714
  }
1707
1715
  return new Polygon(vertices, indices);
1708
1716
  }
1709
1717
  /**
1710
- * Rotates all vertices of the polygon around the given center.
1718
+ * Translates all vertices of the polygon by the given offset.
1719
+ * @param offset Offset to translate the polygon by. Can be a 2D or 3D vector.
1720
+ * @returns this {@link Polygon} instance
1721
+ */
1722
+ translate(offset) {
1723
+ var _a2;
1724
+ const vec3 = { z: 0, ...offset };
1725
+ this._vertices.forEach((vertex) => vertex.add(vec3));
1726
+ (_a2 = this._bbox) == null ? void 0 : _a2.translate(offset);
1727
+ return this;
1728
+ }
1729
+ /**
1730
+ * Rotates all vertices of the polygon around the given center. Only 2D rotation (around Z axis) is supported.
1711
1731
  * @param rotation Rotation angle in radians. Positive values rotate clockwise.
1712
- * @param center Center of the rotation.
1732
+ * @param center Center of the rotation. If omitted, defaults to the bounding rectangle center.
1733
+ * @returns this {@link Polygon} instance
1734
+ */
1735
+ rotate(rotation, center = this.bounds.center) {
1736
+ this._vertices.forEach((vertex) => {
1737
+ tempVector2.set(vertex.x, vertex.y).rotateAround(center, rotation);
1738
+ vertex.set(tempVector2.x, tempVector2.y, vertex.z);
1739
+ });
1740
+ this._bbox = this.computeBoundingRect();
1741
+ return this;
1742
+ }
1743
+ /**
1744
+ * Scales the polygon around the given origin.
1745
+ * @param scaleFactor Can be a single number or a 2D vector. If a single number is provided, both horizontal and vertical axes will be scaled by the same factor.
1746
+ * @param origin Origin of the scaling. If omitted, defaults to the bounding rectangle center.
1713
1747
  * @returns this {@link Polygon} instance
1714
1748
  */
1715
- rotate(rotation, center) {
1716
- const tempVec2 = new Vector2();
1717
- this.vertices.forEach((vertex) => {
1718
- tempVec2.set(vertex.x, vertex.y);
1719
- tempVec2.rotateAround(center, rotation);
1720
- vertex.set(tempVec2.x, tempVec2.y, vertex.z);
1749
+ scale(scaleFactor, origin2 = this.bounds.center) {
1750
+ const scaleVector = typeof scaleFactor === "number" ? { x: scaleFactor, y: scaleFactor } : scaleFactor;
1751
+ this._vertices.forEach((vertex) => {
1752
+ tempVector2.set(vertex.x, vertex.y).sub(origin2).multiply(scaleVector).add(origin2);
1753
+ vertex.set(tempVector2.x, tempVector2.y, vertex.z);
1721
1754
  });
1755
+ this._bbox = this.computeBoundingRect();
1722
1756
  return this;
1723
1757
  }
1758
+ computeBoundingRect() {
1759
+ let minX = Infinity;
1760
+ let minY = Infinity;
1761
+ let maxX = -Infinity;
1762
+ let maxY = -Infinity;
1763
+ for (const vertex of this.vertices) {
1764
+ minX = Math.min(minX, vertex.x);
1765
+ minY = Math.min(minY, vertex.y);
1766
+ maxX = Math.max(maxX, vertex.x);
1767
+ maxY = Math.max(maxY, vertex.y);
1768
+ }
1769
+ return new Rect([minX, minY], [maxX, maxY]);
1770
+ }
1771
+ }
1772
+ const tempVector2 = new Vector2();
1773
+ function countGeometry(geometry) {
1774
+ var _a2;
1775
+ if (geometry.index == null) return { vertices: geometry.getAttribute("position").count, indices: 0 };
1776
+ return {
1777
+ vertices: geometry.getAttribute("position").count,
1778
+ indices: ((_a2 = geometry.index) == null ? void 0 : _a2.count) ?? 0
1779
+ };
1724
1780
  }
1725
1781
  const logger$7 = createLogger("mesh");
1726
1782
  extend([namesPlugin]);
@@ -1731,8 +1787,19 @@ class MeshSystem extends RenderableSystem {
1731
1787
  */
1732
1788
  constructor(materialSystem, renderer) {
1733
1789
  super("mesh", renderer, logger$7);
1734
- __publicField(this, "meshColor", new Color());
1790
+ __publicField(this, "color", new Color());
1791
+ __publicField(this, "position", new Vector3());
1792
+ __publicField(this, "rotation", new Quaternion());
1793
+ __publicField(this, "scale", new Vector3());
1794
+ __publicField(this, "matrix", new Matrix4());
1795
+ __publicField(this, "rectGeometry", new PlaneGeometry(1, 1));
1796
+ __publicField(this, "placeholderPolygonGeometry", new BufferGeometry());
1797
+ __publicField(this, "mapInstanceIdToShapeType", /* @__PURE__ */ new Map());
1735
1798
  this.materialSystem = materialSystem;
1799
+ this.rectGeometry.deleteAttribute("normal");
1800
+ this.rectGeometry.deleteAttribute("uv");
1801
+ this.placeholderPolygonGeometry.setAttribute("position", new BufferAttribute(new Float32Array(), 3));
1802
+ this.placeholderPolygonGeometry.index = new BufferAttribute(new Uint32Array(), 0);
1736
1803
  }
1737
1804
  buildLayer(layer) {
1738
1805
  const shapes = layer.children;
@@ -1769,60 +1836,78 @@ class MeshSystem extends RenderableSystem {
1769
1836
  return group;
1770
1837
  }
1771
1838
  updateDefImpl(shapeDef, mesh, instanceIds) {
1839
+ const instanceId = instanceIds[0];
1840
+ this.updateShape(shapeDef, mesh, instanceId);
1841
+ this.updateColor(shapeDef, mesh, instanceId);
1842
+ }
1843
+ updateShape(shapeDef, mesh, instanceId) {
1844
+ const geometryId = mesh.getGeometryIdAt(instanceId);
1845
+ const expectedShapeType = this.mapInstanceIdToShapeType.get(instanceId);
1846
+ const shape = shapeDef.shape;
1847
+ const isPolygon = shape instanceof Polygon;
1848
+ const isRect = shape instanceof Rect;
1849
+ if (expectedShapeType === "polygon" && !isPolygon || expectedShapeType === "rect" && !isRect) {
1850
+ logger$7.warn("Shape type changing not supported %O", shapeDef);
1851
+ return;
1852
+ }
1853
+ if (isPolygon) {
1854
+ const geometryRange = mesh.getGeometryRangeAt(geometryId);
1855
+ if (!geometryRange || shape.vertices.length != geometryRange.reservedVertexCount || shape.indices.length * 3 != geometryRange.reservedIndexCount) {
1856
+ logger$7.warn("Polygon geometry changing not supported %O", shapeDef);
1857
+ return;
1858
+ }
1859
+ mesh.setGeometryAt(geometryId, this.buildPolygonGeometry(shape));
1860
+ } else if (isRect) {
1861
+ this.position.set(shape.center.x, shape.center.y, 0);
1862
+ this.rotation.setFromAxisAngle(new Vector3(0, 0, 1), shape.rotation ?? 0);
1863
+ this.scale.set(shape.size.x, shape.size.y, 1);
1864
+ this.matrix.compose(this.position, this.rotation, this.scale);
1865
+ mesh.setMatrixAt(instanceId, this.matrix);
1866
+ }
1867
+ }
1868
+ updateColor(shapeDef, mesh, instanceId) {
1772
1869
  const color = this.normalizeColor(shapeDef.color);
1773
1870
  if (!color) {
1774
1871
  logger$7.warn(`Invalid color: ${shapeDef.color} %O`, shapeDef);
1775
1872
  return;
1776
1873
  }
1777
- for (const instanceId of instanceIds) {
1778
- mesh.setColorAt(instanceId, this.meshColor.setRGB(color.r / 255, color.g / 255, color.b / 255, SRGBColorSpace));
1779
- }
1874
+ mesh.setColorAt(instanceId, this.color.setRGB(color.r / 255, color.g / 255, color.b / 255, SRGBColorSpace));
1780
1875
  }
1781
1876
  buildBatchedMesh(shapes, opacity = 1) {
1782
- var _a2, _b;
1783
1877
  let vertexCount = 0;
1784
1878
  let indexCount = 0;
1785
- let rectGeometry = void 0;
1879
+ let rectAdded = false;
1786
1880
  const shapeDefToGeometry = /* @__PURE__ */ new Map();
1787
1881
  for (const shapeDef of shapes) {
1788
- if (shapeDef.shape instanceof Rect && !rectGeometry) {
1789
- rectGeometry = new PlaneGeometry(1, 1);
1790
- rectGeometry.deleteAttribute("normal");
1791
- rectGeometry.deleteAttribute("uv");
1792
- vertexCount += rectGeometry.getAttribute("position").count;
1793
- indexCount += ((_a2 = rectGeometry.index) == null ? void 0 : _a2.count) ?? 0;
1882
+ let vertices = 0;
1883
+ let indices = 0;
1884
+ if (shapeDef.shape instanceof Rect && !rectAdded) {
1885
+ rectAdded = true;
1886
+ ({ vertices, indices } = countGeometry(this.rectGeometry));
1794
1887
  } else if (shapeDef.shape instanceof Polygon) {
1795
1888
  const geometry = this.buildPolygonGeometry(shapeDef.shape);
1796
1889
  shapeDefToGeometry.set(shapeDef, geometry);
1797
- vertexCount += geometry.getAttribute("position").count;
1798
- indexCount += ((_b = geometry.index) == null ? void 0 : _b.count) ?? 0;
1890
+ ({ vertices, indices } = countGeometry(geometry));
1799
1891
  }
1892
+ vertexCount += vertices;
1893
+ indexCount += indices;
1800
1894
  }
1801
1895
  const material = this.materialSystem.createColorMaterial({ opacity });
1802
1896
  const batchedMesh = new BatchedMesh(shapes.length, vertexCount, indexCount, material);
1803
- const rectGeometryId = rectGeometry ? batchedMesh.addGeometry(rectGeometry) : void 0;
1897
+ const rectGeometryId = rectAdded ? batchedMesh.addGeometry(this.rectGeometry) : void 0;
1804
1898
  batchedMesh.setCustomSort((list) => this.sortInstances(batchedMesh, list));
1805
- const position = new Vector3();
1806
- const rotation = new Quaternion();
1807
- const scale = new Vector3();
1808
- const matrix = new Matrix4();
1809
- const [rects, polygons] = partition(shapes, (shapeDef) => shapeDef.shape instanceof Rect);
1810
- const sortedShapes = [...rects, ...polygons];
1811
- for (const shapeDef of sortedShapes) {
1812
- let instanceId = void 0;
1899
+ for (const shapeDef of shapes) {
1900
+ let instanceId;
1813
1901
  if (shapeDef.shape instanceof Rect && rectGeometryId !== void 0) {
1814
1902
  instanceId = batchedMesh.addInstance(rectGeometryId);
1815
- position.set(shapeDef.shape.center.x, shapeDef.shape.center.y, 0);
1816
- rotation.setFromAxisAngle(new Vector3(0, 0, 1), shapeDef.shape.rotation ?? 0);
1817
- scale.set(shapeDef.shape.size.x, shapeDef.shape.size.y, 1);
1818
- matrix.compose(position, rotation, scale);
1819
- batchedMesh.setMatrixAt(instanceId, matrix);
1820
- } else if (shapeDef.shape instanceof Polygon) {
1903
+ this.mapInstanceIdToShapeType.set(instanceId, "rect");
1904
+ } else {
1821
1905
  const geometry = shapeDefToGeometry.get(shapeDef);
1822
- const polygonGeometryId = batchedMesh.addGeometry(geometry);
1906
+ const { vertices, indices } = countGeometry(geometry);
1907
+ const polygonGeometryId = batchedMesh.addGeometry(this.placeholderPolygonGeometry, vertices, indices);
1823
1908
  instanceId = batchedMesh.addInstance(polygonGeometryId);
1909
+ this.mapInstanceIdToShapeType.set(instanceId, "polygon");
1824
1910
  }
1825
- if (instanceId === void 0) continue;
1826
1911
  this.registerDefObject(shapeDef, batchedMesh, instanceId);
1827
1912
  }
1828
1913
  return batchedMesh;
@@ -1834,8 +1919,7 @@ class MeshSystem extends RenderableSystem {
1834
1919
  return color.toRgb();
1835
1920
  }
1836
1921
  buildPolygonGeometry(polygon) {
1837
- const geometry = new BufferGeometry().setFromPoints(polygon.vertices).setIndex(polygon.indices.flat());
1838
- return geometry;
1922
+ return new BufferGeometry().setFromPoints(polygon.vertices).setIndex(polygon.indices.flat());
1839
1923
  }
1840
1924
  sortInstances(mesh, list) {
1841
1925
  const shapeDefs = this.getDefsByObject(mesh);
@@ -2005,11 +2089,10 @@ class TextSystem extends RenderableSystem {
2005
2089
  return lines;
2006
2090
  }
2007
2091
  calculateStartInBoundsPosition(textDef, lines, alignmentDirection, alignmentOffset, inBoundsPosition) {
2008
- const [w, h] = textDef.bounds.size;
2009
2092
  const padding = textDef.padding;
2010
2093
  const alignment = textDef.alignment;
2011
2094
  alignmentDirection.set(...getAlignmentDirection(alignment));
2012
- inBoundsPosition.set(w / 2, h / 2);
2095
+ inBoundsPosition.copy(textDef.bounds.size).multiplyScalar(0.5);
2013
2096
  if (alignment.vertical === "center") {
2014
2097
  const totalTextHeight = lines.filter((l) => l.height !== void 0).reduce((acc, l) => acc + l.height, 0) * this.renderer.context.getPixelRatio();
2015
2098
  alignmentOffset.set(0, -(textDef.bounds.size.y - totalTextHeight) / 2);
@@ -4785,9 +4868,9 @@ class CameraController extends CameraControls {
4785
4868
  */
4786
4869
  constructor(camera) {
4787
4870
  super(camera);
4871
+ __publicField(this, "touchCancelListener");
4788
4872
  this.dollyToCursor = true;
4789
4873
  this.draggingSmoothTime = 0;
4790
- void this.rotatePolarTo(0, false);
4791
4874
  this.mouseButtons = {
4792
4875
  left: CameraController.ACTION.NONE,
4793
4876
  middle: CameraController.ACTION.NONE,
@@ -4799,6 +4882,7 @@ class CameraController extends CameraControls {
4799
4882
  two: CameraController.ACTION.NONE,
4800
4883
  three: CameraController.ACTION.NONE
4801
4884
  };
4885
+ this.touchCancelListener = () => this.cancel();
4802
4886
  }
4803
4887
  update(delta) {
4804
4888
  const needsUpdate = super.update(delta);
@@ -4811,6 +4895,15 @@ class CameraController extends CameraControls {
4811
4895
  }
4812
4896
  return needsUpdate;
4813
4897
  }
4898
+ connect(domElement) {
4899
+ super.connect(domElement);
4900
+ domElement.addEventListener("touchcancel", this.touchCancelListener);
4901
+ }
4902
+ disconnect() {
4903
+ var _a2;
4904
+ (_a2 = this._domElement) == null ? void 0 : _a2.removeEventListener("touchcancel", this.touchCancelListener);
4905
+ super.disconnect();
4906
+ }
4814
4907
  }
4815
4908
  class CameraSystem {
4816
4909
  /**
@@ -4834,9 +4927,8 @@ class CameraSystem {
4834
4927
  const h = renderer.size[1];
4835
4928
  this.prevViewportHeightPx = h;
4836
4929
  this.camera = new PerspectiveCamera(this.defaultFov);
4837
- this.camera.up.set(0, 0, -1);
4838
4930
  this.controller = new CameraController(this.camera);
4839
- this.controller.distance = this.zoomIdentityDistance;
4931
+ void this.controller.rotatePolarTo(0, false);
4840
4932
  }
4841
4933
  /** Current camera zoom factor. */
4842
4934
  get zoomFactor() {
@@ -4858,6 +4950,12 @@ class CameraSystem {
4858
4950
  initCamera(zoomBounds) {
4859
4951
  this.zoomBounds = zoomBounds;
4860
4952
  this.updateCamera();
4953
+ this.camera.up.set(0, -1, 0);
4954
+ this.camera.position.set(0, 0, -this.zoomIdentityDistance);
4955
+ this.camera.lookAt(0, 0, 0);
4956
+ this.camera.updateMatrixWorld();
4957
+ this.camera.up.set(0, 0, -1);
4958
+ this.controller.updateCameraUp();
4861
4959
  }
4862
4960
  /** Updates the camera when the renderer size changes. */
4863
4961
  updateCamera() {
@@ -5059,7 +5157,10 @@ class SceneSystem {
5059
5157
  __publicField(this, "worldMatrix", new Matrix4());
5060
5158
  /** Inverse world matrix - world → model transform */
5061
5159
  __publicField(this, "inverseWorldMatrix", new Matrix4());
5160
+ /** Used as a scratch vector for coordinate space conversions */
5062
5161
  __publicField(this, "tempVector3", new Vector3());
5162
+ /** Used as a scratch vector for matrix calculations */
5163
+ __publicField(this, "tempVector2", new Vector2());
5063
5164
  __publicField(this, "translationMatrix", new Matrix4());
5064
5165
  __publicField(this, "scaleMatrix", new Matrix4());
5065
5166
  __publicField(this, "visibleRectOffsetMatrix", new Matrix4());
@@ -5110,14 +5211,14 @@ class SceneSystem {
5110
5211
  composeMatrices(viewbox) {
5111
5212
  const dpr = this.renderer.context.getPixelRatio();
5112
5213
  const visibleRect = this.renderer.visibleRect;
5113
- const [viewBoxWidth, viewBoxHeight] = viewbox.size;
5114
- const [visibleRectWidth, visibleRectHeight] = (visibleRect == null ? void 0 : visibleRect.size.clone().multiplyScalar(dpr)) ?? this.renderer.size;
5115
- const scaleFactor = Math.min(visibleRectWidth / viewBoxWidth, visibleRectHeight / viewBoxHeight);
5116
- const [centerX, centerY] = viewbox.center;
5214
+ const visibleRectSize = visibleRect ? this.tempVector2.copy(visibleRect.size).multiplyScalar(dpr) : this.tempVector2.set(...this.renderer.size);
5215
+ visibleRectSize.divide(viewbox.size);
5216
+ const scaleFactor = Math.min(visibleRectSize.width, visibleRectSize.height);
5217
+ const { x: centerX, y: centerY } = viewbox.center;
5117
5218
  this.translationMatrix.makeTranslation(-centerX, -centerY, 0);
5118
5219
  this.scaleMatrix.makeScale(scaleFactor, scaleFactor, 1);
5119
5220
  if (visibleRect) {
5120
- const visibleRectCenter = visibleRect.center.clone().multiplyScalar(dpr);
5221
+ const visibleRectCenter = this.tempVector2.copy(visibleRect.center).multiplyScalar(dpr);
5121
5222
  const canvasCenter = { x: this.renderer.size[0] / 2, y: this.renderer.size[1] / 2 };
5122
5223
  const offset = visibleRectCenter.sub(canvasCenter);
5123
5224
  this.visibleRectOffsetMatrix.makeTranslation(offset.x, offset.y, 0);
@@ -5187,7 +5288,7 @@ class ViewportSystem {
5187
5288
  */
5188
5289
  initViewport(sceneDef) {
5189
5290
  if (!this.renderer.isExternalMode) this.sceneSystem.initScene(sceneDef.viewbox);
5190
- this.cameraSystem.initCamera([0.1, sceneDef.viewbox.size.width > 1e5 ? 100 : 35]);
5291
+ this.cameraSystem.initCamera([0.1, sceneDef.viewbox.size.x > 1e5 ? 100 : 35]);
5191
5292
  }
5192
5293
  /** Updates the viewport when the renderer size changes. */
5193
5294
  updateViewport() {
@@ -5344,27 +5445,23 @@ class ControlsSystem {
5344
5445
  const dpr = this.renderer.context.getPixelRatio();
5345
5446
  const visibleRect = this.renderer.visibleRect;
5346
5447
  const bearingAngle = -this.controller.azimuthAngle;
5347
- const worldMin = this.viewportSystem.modelToWorld(rect.min);
5348
- const worldMax = this.viewportSystem.modelToWorld(rect.max);
5349
- const worldRect = new Rect(worldMin, worldMax);
5350
- const worldPolygon = Polygon.fromRect(worldRect).rotate(bearingAngle, worldRect.center);
5351
- const xValues = worldPolygon.vertices.map((p) => p.x);
5352
- const yValues = worldPolygon.vertices.map((p) => p.y);
5353
- const sourceRect = new Rect(
5354
- [Math.min(...xValues), Math.min(...yValues)],
5355
- [Math.max(...xValues), Math.max(...yValues)]
5448
+ const worldRect = new Rect(
5449
+ this.viewportSystem.modelToWorld(rect.min),
5450
+ this.viewportSystem.modelToWorld(rect.max)
5356
5451
  );
5452
+ const sourceRect = Polygon.fromRect(worldRect).rotate(bearingAngle).bounds;
5357
5453
  if (sourceRect.size.x <= 0 || sourceRect.size.y <= 0) {
5358
5454
  logger$1.warn("zoomTo: sourceRect size is 0");
5359
5455
  return;
5360
5456
  }
5361
- const targetRect = visibleRect ? new Rect(visibleRect.min.clone().multiplyScalar(dpr), visibleRect.max.clone().multiplyScalar(dpr)) : new Rect([0, 0], [...this.renderer.size]);
5362
- if (paddingPercent) targetRect.addPadding(targetRect.size.x * paddingPercent, targetRect.size.y * paddingPercent);
5457
+ const targetRect = visibleRect ? new Rect([visibleRect.min.x * dpr, visibleRect.min.y * dpr], [visibleRect.max.x * dpr, visibleRect.max.y * dpr]) : new Rect([0, 0], [...this.renderer.size]);
5458
+ if (paddingPercent)
5459
+ targetRect.expand({ x: -targetRect.size.x * paddingPercent, y: -targetRect.size.y * paddingPercent });
5363
5460
  const zoomByWidth = targetRect.size.x / sourceRect.size.x;
5364
5461
  const zoomByHeight = targetRect.size.y / sourceRect.size.y;
5365
5462
  const minZoom = Math.min(zoomByWidth, zoomByHeight);
5366
5463
  const zoom = maxZoom ? Math.min(minZoom, maxZoom) : minZoom;
5367
- const translate = sourceRect.center;
5464
+ const translate = new Vector2().copy(sourceRect.center);
5368
5465
  if (visibleRect) {
5369
5466
  const offset = new Vector2(...this.renderer.size).multiplyScalar(0.5).sub(targetRect.center).multiplyScalar(1 / (zoom || 1)).rotateAround({ x: 0, y: 0 }, bearingAngle);
5370
5467
  translate.add(offset);
@@ -6269,8 +6366,7 @@ class Renderer {
6269
6366
  this.controlsSystem = new ControlsSystem(this, this.viewportSystem, this.interactionsSystem);
6270
6367
  this.canvas.addEventListener("webglcontextlost", (e) => this.onContextLost(e), false);
6271
6368
  this.canvas.addEventListener("webglcontextrestored", (e) => this.onContextRestored(e), false);
6272
- this.initContext(this.renderer.getContext());
6273
- BatchedMesh.useMultiDraw = this.renderer.extensions.has("WEBGL_multi_draw");
6369
+ this.initStatsContext(this.renderer.getContext());
6274
6370
  }
6275
6371
  /**
6276
6372
  * {@link ControlsAPI} instance for controlling the viewport
@@ -6483,7 +6579,11 @@ class Renderer {
6483
6579
  const logMarker = `memoryInfo [${elapsedTime.toFixed(2)}ms since start]`;
6484
6580
  logger.debug(logMarker, memoryInfo);
6485
6581
  this.memoryInfo = memoryInfo;
6486
- this.ui.memoryInfoPanel.textContent = JSON.stringify(memoryInfo, null, 2);
6582
+ this.ui.memoryInfoPanel.textContent = JSON.stringify(
6583
+ memoryInfo,
6584
+ (_, value) => typeof value === "number" ? value.toLocaleString() : value,
6585
+ 2
6586
+ ).replaceAll('"', "");
6487
6587
  }
6488
6588
  }
6489
6589
  }
@@ -6496,7 +6596,7 @@ class Renderer {
6496
6596
  if (stats && "deleteQuery" in context) {
6497
6597
  const gpuQueries = stats.gpuQueries;
6498
6598
  for (const queryInfo of gpuQueries) {
6499
- this.renderer.getContext().deleteQuery(queryInfo.query);
6599
+ context.deleteQuery(queryInfo.query);
6500
6600
  }
6501
6601
  stats.gpuQueries = [];
6502
6602
  if (stats.gpuPanel) {
@@ -6510,11 +6610,11 @@ class Renderer {
6510
6610
  onContextRestored(event) {
6511
6611
  event.preventDefault();
6512
6612
  logger.debug("webglcontextrestored event", event);
6513
- this.initContext(this.renderer.getContext());
6613
+ this.initStatsContext(this.renderer.getContext());
6514
6614
  this.needsRedraw = true;
6515
6615
  this.start();
6516
6616
  }
6517
- initContext(context) {
6617
+ initStatsContext(context) {
6518
6618
  var _a2, _b;
6519
6619
  this.memoryInfoExtension = context.getExtension("GMAN_webgl_memory");
6520
6620
  void ((_b = (_a2 = this.ui) == null ? void 0 : _a2.stats) == null ? void 0 : _b.init(context));