@expofp/renderer 2.1.2 → 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 +656 -567
  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.
@@ -671,13 +315,7 @@ const _BatchedMesh = class _BatchedMesh extends BatchedMesh$1 {
671
315
  __publicField(this, "geometryById", /* @__PURE__ */ new Map());
672
316
  __publicField(this, "mapGeometryToInstanceId", /* @__PURE__ */ new Map());
673
317
  material.forceSinglePass = true;
674
- 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
- });
318
+ addDim(this);
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();
1575
+ }
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;
1624
1588
  }
1625
- /** Center of the rectangle. Read-only, calculated on demand. */
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);
@@ -4788,7 +4871,6 @@ class CameraController extends CameraControls {
4788
4871
  __publicField(this, "touchCancelListener");
4789
4872
  this.dollyToCursor = true;
4790
4873
  this.draggingSmoothTime = 0;
4791
- void this.rotatePolarTo(0, false);
4792
4874
  this.mouseButtons = {
4793
4875
  left: CameraController.ACTION.NONE,
4794
4876
  middle: CameraController.ACTION.NONE,
@@ -4845,9 +4927,8 @@ class CameraSystem {
4845
4927
  const h = renderer.size[1];
4846
4928
  this.prevViewportHeightPx = h;
4847
4929
  this.camera = new PerspectiveCamera(this.defaultFov);
4848
- this.camera.up.set(0, 0, -1);
4849
4930
  this.controller = new CameraController(this.camera);
4850
- this.controller.distance = this.zoomIdentityDistance;
4931
+ void this.controller.rotatePolarTo(0, false);
4851
4932
  }
4852
4933
  /** Current camera zoom factor. */
4853
4934
  get zoomFactor() {
@@ -4869,6 +4950,12 @@ class CameraSystem {
4869
4950
  initCamera(zoomBounds) {
4870
4951
  this.zoomBounds = zoomBounds;
4871
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();
4872
4959
  }
4873
4960
  /** Updates the camera when the renderer size changes. */
4874
4961
  updateCamera() {
@@ -5070,7 +5157,10 @@ class SceneSystem {
5070
5157
  __publicField(this, "worldMatrix", new Matrix4());
5071
5158
  /** Inverse world matrix - world → model transform */
5072
5159
  __publicField(this, "inverseWorldMatrix", new Matrix4());
5160
+ /** Used as a scratch vector for coordinate space conversions */
5073
5161
  __publicField(this, "tempVector3", new Vector3());
5162
+ /** Used as a scratch vector for matrix calculations */
5163
+ __publicField(this, "tempVector2", new Vector2());
5074
5164
  __publicField(this, "translationMatrix", new Matrix4());
5075
5165
  __publicField(this, "scaleMatrix", new Matrix4());
5076
5166
  __publicField(this, "visibleRectOffsetMatrix", new Matrix4());
@@ -5121,14 +5211,14 @@ class SceneSystem {
5121
5211
  composeMatrices(viewbox) {
5122
5212
  const dpr = this.renderer.context.getPixelRatio();
5123
5213
  const visibleRect = this.renderer.visibleRect;
5124
- const [viewBoxWidth, viewBoxHeight] = viewbox.size;
5125
- const [visibleRectWidth, visibleRectHeight] = (visibleRect == null ? void 0 : visibleRect.size.clone().multiplyScalar(dpr)) ?? this.renderer.size;
5126
- const scaleFactor = Math.min(visibleRectWidth / viewBoxWidth, visibleRectHeight / viewBoxHeight);
5127
- 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;
5128
5218
  this.translationMatrix.makeTranslation(-centerX, -centerY, 0);
5129
5219
  this.scaleMatrix.makeScale(scaleFactor, scaleFactor, 1);
5130
5220
  if (visibleRect) {
5131
- const visibleRectCenter = visibleRect.center.clone().multiplyScalar(dpr);
5221
+ const visibleRectCenter = this.tempVector2.copy(visibleRect.center).multiplyScalar(dpr);
5132
5222
  const canvasCenter = { x: this.renderer.size[0] / 2, y: this.renderer.size[1] / 2 };
5133
5223
  const offset = visibleRectCenter.sub(canvasCenter);
5134
5224
  this.visibleRectOffsetMatrix.makeTranslation(offset.x, offset.y, 0);
@@ -5198,7 +5288,7 @@ class ViewportSystem {
5198
5288
  */
5199
5289
  initViewport(sceneDef) {
5200
5290
  if (!this.renderer.isExternalMode) this.sceneSystem.initScene(sceneDef.viewbox);
5201
- 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]);
5202
5292
  }
5203
5293
  /** Updates the viewport when the renderer size changes. */
5204
5294
  updateViewport() {
@@ -5355,27 +5445,23 @@ class ControlsSystem {
5355
5445
  const dpr = this.renderer.context.getPixelRatio();
5356
5446
  const visibleRect = this.renderer.visibleRect;
5357
5447
  const bearingAngle = -this.controller.azimuthAngle;
5358
- const worldMin = this.viewportSystem.modelToWorld(rect.min);
5359
- const worldMax = this.viewportSystem.modelToWorld(rect.max);
5360
- const worldRect = new Rect(worldMin, worldMax);
5361
- const worldPolygon = Polygon.fromRect(worldRect).rotate(bearingAngle, worldRect.center);
5362
- const xValues = worldPolygon.vertices.map((p) => p.x);
5363
- const yValues = worldPolygon.vertices.map((p) => p.y);
5364
- const sourceRect = new Rect(
5365
- [Math.min(...xValues), Math.min(...yValues)],
5366
- [Math.max(...xValues), Math.max(...yValues)]
5448
+ const worldRect = new Rect(
5449
+ this.viewportSystem.modelToWorld(rect.min),
5450
+ this.viewportSystem.modelToWorld(rect.max)
5367
5451
  );
5452
+ const sourceRect = Polygon.fromRect(worldRect).rotate(bearingAngle).bounds;
5368
5453
  if (sourceRect.size.x <= 0 || sourceRect.size.y <= 0) {
5369
5454
  logger$1.warn("zoomTo: sourceRect size is 0");
5370
5455
  return;
5371
5456
  }
5372
- const targetRect = visibleRect ? new Rect(visibleRect.min.clone().multiplyScalar(dpr), visibleRect.max.clone().multiplyScalar(dpr)) : new Rect([0, 0], [...this.renderer.size]);
5373
- 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 });
5374
5460
  const zoomByWidth = targetRect.size.x / sourceRect.size.x;
5375
5461
  const zoomByHeight = targetRect.size.y / sourceRect.size.y;
5376
5462
  const minZoom = Math.min(zoomByWidth, zoomByHeight);
5377
5463
  const zoom = maxZoom ? Math.min(minZoom, maxZoom) : minZoom;
5378
- const translate = sourceRect.center;
5464
+ const translate = new Vector2().copy(sourceRect.center);
5379
5465
  if (visibleRect) {
5380
5466
  const offset = new Vector2(...this.renderer.size).multiplyScalar(0.5).sub(targetRect.center).multiplyScalar(1 / (zoom || 1)).rotateAround({ x: 0, y: 0 }, bearingAngle);
5381
5467
  translate.add(offset);
@@ -6280,8 +6366,7 @@ class Renderer {
6280
6366
  this.controlsSystem = new ControlsSystem(this, this.viewportSystem, this.interactionsSystem);
6281
6367
  this.canvas.addEventListener("webglcontextlost", (e) => this.onContextLost(e), false);
6282
6368
  this.canvas.addEventListener("webglcontextrestored", (e) => this.onContextRestored(e), false);
6283
- this.initContext(this.renderer.getContext());
6284
- BatchedMesh.useMultiDraw = this.renderer.extensions.has("WEBGL_multi_draw");
6369
+ this.initStatsContext(this.renderer.getContext());
6285
6370
  }
6286
6371
  /**
6287
6372
  * {@link ControlsAPI} instance for controlling the viewport
@@ -6494,7 +6579,11 @@ class Renderer {
6494
6579
  const logMarker = `memoryInfo [${elapsedTime.toFixed(2)}ms since start]`;
6495
6580
  logger.debug(logMarker, memoryInfo);
6496
6581
  this.memoryInfo = memoryInfo;
6497
- 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('"', "");
6498
6587
  }
6499
6588
  }
6500
6589
  }
@@ -6507,7 +6596,7 @@ class Renderer {
6507
6596
  if (stats && "deleteQuery" in context) {
6508
6597
  const gpuQueries = stats.gpuQueries;
6509
6598
  for (const queryInfo of gpuQueries) {
6510
- this.renderer.getContext().deleteQuery(queryInfo.query);
6599
+ context.deleteQuery(queryInfo.query);
6511
6600
  }
6512
6601
  stats.gpuQueries = [];
6513
6602
  if (stats.gpuPanel) {
@@ -6521,11 +6610,11 @@ class Renderer {
6521
6610
  onContextRestored(event) {
6522
6611
  event.preventDefault();
6523
6612
  logger.debug("webglcontextrestored event", event);
6524
- this.initContext(this.renderer.getContext());
6613
+ this.initStatsContext(this.renderer.getContext());
6525
6614
  this.needsRedraw = true;
6526
6615
  this.start();
6527
6616
  }
6528
- initContext(context) {
6617
+ initStatsContext(context) {
6529
6618
  var _a2, _b;
6530
6619
  this.memoryInfoExtension = context.getExtension("GMAN_webgl_memory");
6531
6620
  void ((_b = (_a2 = this.ui) == null ? void 0 : _a2.stats) == null ? void 0 : _b.init(context));