@bloopjs/toodle 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mod.js CHANGED
@@ -1,3 +1,14 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, {
5
+ get: all[name],
6
+ enumerable: true,
7
+ configurable: true,
8
+ set: (newValue) => all[name] = () => newValue
9
+ });
10
+ };
11
+
1
12
  // src/limits.ts
2
13
  var DEFAULT_LIMITS = {
3
14
  instanceCount: 1024 * 2,
@@ -3434,6 +3445,18 @@ var {
3434
3445
  vec4: vec4n
3435
3446
  } = wgpuMatrixAPI(ZeroArray, Array, Array, Array, Array, Array);
3436
3447
 
3448
+ // src/backends/mod.ts
3449
+ var exports_mod = {};
3450
+ __export(exports_mod, {
3451
+ isWebGPUAvailable: () => isWebGPUAvailable,
3452
+ isWebGL2Available: () => isWebGL2Available,
3453
+ detectBackend: () => detectBackend,
3454
+ defaultGLSLFragmentShader: () => defaultFragmentShader,
3455
+ WebGPUBackend: () => WebGPUBackend,
3456
+ WebGLBackend: () => WebGLBackend,
3457
+ PostProcessDefaults: () => PostProcessDefaults
3458
+ });
3459
+
3437
3460
  // src/backends/detection.ts
3438
3461
  async function detectBackend() {
3439
3462
  if (typeof navigator !== "undefined" && "gpu" in navigator) {
@@ -3446,13 +3469,21 @@ async function detectBackend() {
3446
3469
  }
3447
3470
  return "webgl2";
3448
3471
  }
3449
- // src/utils/assert.ts
3450
- function assert(condition, message) {
3451
- if (!condition) {
3452
- throw new Error(message);
3472
+ function isWebGPUAvailable() {
3473
+ return typeof navigator !== "undefined" && "gpu" in navigator;
3474
+ }
3475
+ function isWebGL2Available() {
3476
+ if (typeof document === "undefined") {
3477
+ return false;
3478
+ }
3479
+ try {
3480
+ const canvas = document.createElement("canvas");
3481
+ const gl = canvas.getContext("webgl2");
3482
+ return gl !== null;
3483
+ } catch {
3484
+ return false;
3453
3485
  }
3454
3486
  }
3455
-
3456
3487
  // src/backends/webgl2/glsl/quad.glsl.ts
3457
3488
  var vertexShader = `#version 300 es
3458
3489
  precision highp float;
@@ -3557,6 +3588,13 @@ void main() {
3557
3588
  }
3558
3589
  }
3559
3590
  `;
3591
+ var defaultFragmentShader = fragmentShader;
3592
+ // src/utils/assert.ts
3593
+ function assert(condition, message) {
3594
+ if (!condition) {
3595
+ throw new Error(message);
3596
+ }
3597
+ }
3560
3598
 
3561
3599
  // src/backends/webgl2/WebGLQuadShader.ts
3562
3600
  var INSTANCE_FLOATS = 28;
@@ -3716,7 +3754,7 @@ class WebGLQuadShader {
3716
3754
  }
3717
3755
 
3718
3756
  // src/backends/webgl2/WebGLBackend.ts
3719
- class WebGLBackend2 {
3757
+ class WebGLBackend {
3720
3758
  type = "webgl2";
3721
3759
  limits;
3722
3760
  atlasSize;
@@ -3746,7 +3784,7 @@ class WebGLBackend2 {
3746
3784
  ...DEFAULT_LIMITS,
3747
3785
  ...options.limits
3748
3786
  };
3749
- const backend = new WebGLBackend2(gl, canvas, limits);
3787
+ const backend = new WebGLBackend(gl, canvas, limits);
3750
3788
  backend.createTextureAtlas("default", {
3751
3789
  format: options.format ?? "rgba8unorm",
3752
3790
  layers: limits.textureArrayLayers,
@@ -3835,6 +3873,58 @@ class WebGLBackend2 {
3835
3873
  return this.getTextureAtlas("default")?.format ?? "rgba8unorm";
3836
3874
  }
3837
3875
  }
3876
+ // src/backends/webgpu/postprocess/mod.ts
3877
+ var PostProcessDefaults = {
3878
+ sampler(device) {
3879
+ return device.createSampler({
3880
+ label: "toodle post process sampler",
3881
+ magFilter: "linear",
3882
+ minFilter: "linear",
3883
+ mipmapFilter: "linear",
3884
+ addressModeU: "clamp-to-edge",
3885
+ addressModeV: "clamp-to-edge"
3886
+ });
3887
+ },
3888
+ vertexBufferLayout(_device) {
3889
+ return {
3890
+ arrayStride: 4 * 4,
3891
+ attributes: [{ shaderLocation: 0, offset: 0, format: "float32x2" }]
3892
+ };
3893
+ },
3894
+ vertexShader(device) {
3895
+ return device.createShaderModule({
3896
+ label: "toodle post process vertex shader",
3897
+ code: `
3898
+ struct VertexOut {
3899
+ @builtin(position) position: vec4<f32>,
3900
+ @location(0) uv: vec2<f32>,
3901
+ };
3902
+
3903
+ const enginePosLookup = array(vec2f(-1, 1), vec2f(-1, -1), vec2f(1, 1), vec2f(1, -1));
3904
+ const engineUvLookup = array(vec2f(0, 0), vec2f(0, 1), vec2f(1, 0), vec2f(1, 1));
3905
+
3906
+ @vertex
3907
+ fn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VertexOut {
3908
+ var out: VertexOut;
3909
+ out.position = vec4(enginePosLookup[vertexIndex], 0.0, 1.0);
3910
+ out.uv = engineUvLookup[vertexIndex];
3911
+ return out;
3912
+ }
3913
+ `
3914
+ });
3915
+ },
3916
+ pipelineDescriptor(device) {
3917
+ return {
3918
+ label: "toodle post process pipeline descriptor",
3919
+ layout: "auto",
3920
+ primitive: { topology: "triangle-strip" },
3921
+ vertex: {
3922
+ buffers: [PostProcessDefaults.vertexBufferLayout(device)],
3923
+ module: PostProcessDefaults.vertexShader(device)
3924
+ }
3925
+ };
3926
+ }
3927
+ };
3838
3928
  // node_modules/webgpu-utils/dist/1.x/webgpu-utils.module.js
3839
3929
  var roundUpToMultipleOf = (v, multiple) => ((v + multiple - 1) / multiple | 0) * multiple;
3840
3930
  function keysOf(obj) {
@@ -16941,7 +17031,7 @@ function convertBlendMode(mode) {
16941
17031
  }
16942
17032
 
16943
17033
  // src/backends/webgpu/WebGPUBackend.ts
16944
- class WebGPUBackend2 {
17034
+ class WebGPUBackend {
16945
17035
  type = "webgpu";
16946
17036
  limits;
16947
17037
  atlasSize;
@@ -16986,7 +17076,7 @@ class WebGPUBackend2 {
16986
17076
  ...DEFAULT_LIMITS,
16987
17077
  ...options.limits
16988
17078
  };
16989
- const backend = new WebGPUBackend2(device, context, presentationFormat, limits, canvas);
17079
+ const backend = new WebGPUBackend(device, context, presentationFormat, limits, canvas);
16990
17080
  backend.createTextureAtlas("default", {
16991
17081
  format: options.format ?? "rgba8unorm",
16992
17082
  layers: limits.textureArrayLayers,
@@ -17131,1037 +17221,755 @@ class WebGPUBackend2 {
17131
17221
  return this.#renderPass;
17132
17222
  }
17133
17223
  }
17134
- // src/math/matrix.ts
17135
- function createProjectionMatrix(resolution, dst) {
17136
- const { width, height } = resolution;
17137
- return mat3.scaling([2 / width, 2 / height], dst);
17138
- }
17139
- function createViewMatrix(camera, target) {
17140
- const matrix = mat3.identity(target);
17141
- mat3.scale(matrix, [camera.zoom, camera.zoom], matrix);
17142
- mat3.rotate(matrix, camera.rotationRadians, matrix);
17143
- mat3.translate(matrix, [-camera.x, -camera.y], matrix);
17144
- return matrix;
17145
- }
17146
- function createModelMatrix(transform, base) {
17147
- mat3.translate(base, [transform.position.x, transform.position.y], base);
17148
- mat3.rotate(base, transform.rotation, base);
17149
- mat3.scale(base, [transform.scale.x, transform.scale.y], base);
17150
- return base;
17151
- }
17152
- function convertScreenToWorld(screenCoordinates, camera, projectionMatrix, resolution) {
17153
- const inverseViewProjectionMatrix = mat3.mul(mat3.inverse(camera.matrix), mat3.inverse(projectionMatrix));
17154
- const normalizedDeviceCoordinates = {
17155
- x: 2 * screenCoordinates.x / resolution.width - 1,
17156
- y: 1 - 2 * screenCoordinates.y / resolution.height
17157
- };
17158
- return transformPoint(normalizedDeviceCoordinates, inverseViewProjectionMatrix);
17159
- }
17160
- function convertWorldToScreen(worldCoordinates, camera, projectionMatrix, resolution) {
17161
- const viewProjectionMatrix = mat3.mul(projectionMatrix, camera.matrix);
17162
- const ndcPoint = transformPoint(worldCoordinates, viewProjectionMatrix);
17163
- return {
17164
- x: (ndcPoint.x + 1) * resolution.width / 2,
17165
- y: (1 - ndcPoint.y) * resolution.height / 2
17166
- };
17167
- }
17168
- function transformPoint(point, matrix) {
17169
- const result = vec2.transformMat3([point.x, point.y], matrix);
17170
- return {
17171
- x: result[0],
17172
- y: result[1]
17173
- };
17174
- }
17175
-
17176
- // src/scene/Batcher.ts
17177
- class Batcher {
17178
- nodes = [];
17179
- layers = [];
17180
- pipelines = [];
17181
- enqueue(node) {
17182
- if (node.renderComponent && node.isActive) {
17183
- this.nodes.push(node);
17184
- const z3 = node.layer;
17185
- const layer = this.#findOrCreateLayer(z3);
17186
- const pipeline = this.#findOrCreatePipeline(layer, node.renderComponent.shader);
17187
- pipeline.nodes.push(node);
17188
- }
17189
- for (const kid of node.kids) {
17190
- this.enqueue(kid);
17191
- }
17192
- }
17193
- flush() {
17194
- this.nodes = [];
17195
- this.layers = [];
17196
- this.pipelines = [];
17224
+ // src/backends/webgl2/WebGLFontPipeline.ts
17225
+ class WebGLFontPipeline {
17226
+ font;
17227
+ fontTexture;
17228
+ charDataTexture;
17229
+ textBufferTexture;
17230
+ maxCharCount;
17231
+ lineHeight;
17232
+ #gl;
17233
+ constructor(gl, font, fontTexture, charDataTexture, textBufferTexture, maxCharCount) {
17234
+ this.#gl = gl;
17235
+ this.font = font;
17236
+ this.fontTexture = fontTexture;
17237
+ this.charDataTexture = charDataTexture;
17238
+ this.textBufferTexture = textBufferTexture;
17239
+ this.maxCharCount = maxCharCount;
17240
+ this.lineHeight = font.lineHeight;
17197
17241
  }
17198
- #findOrCreateLayer(z3) {
17199
- let layer = this.layers.find((l3) => l3.z === z3);
17200
- if (!layer) {
17201
- layer = { z: z3, pipelines: [] };
17202
- this.layers.push(layer);
17203
- this.layers.sort((a3, b3) => a3.z - b3.z);
17242
+ static create(gl, font, maxCharCount) {
17243
+ const fontTexture = gl.createTexture();
17244
+ assert(fontTexture, "Failed to create font texture");
17245
+ gl.bindTexture(gl.TEXTURE_2D, fontTexture);
17246
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
17247
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
17248
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
17249
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
17250
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, font.imageBitmap);
17251
+ const charDataTexture = gl.createTexture();
17252
+ assert(charDataTexture, "Failed to create char data texture");
17253
+ gl.bindTexture(gl.TEXTURE_2D, charDataTexture);
17254
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
17255
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
17256
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
17257
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
17258
+ const charCount = font.charCount;
17259
+ const charTextureWidth = charCount * 2;
17260
+ const charTextureData = new Float32Array(charTextureWidth * 4);
17261
+ for (let i3 = 0;i3 < charCount; i3++) {
17262
+ const srcOffset = i3 * 8;
17263
+ const dstOffset0 = i3 * 2 * 4;
17264
+ const dstOffset1 = (i3 * 2 + 1) * 4;
17265
+ charTextureData[dstOffset0] = font.charBuffer[srcOffset];
17266
+ charTextureData[dstOffset0 + 1] = font.charBuffer[srcOffset + 1];
17267
+ charTextureData[dstOffset0 + 2] = font.charBuffer[srcOffset + 2];
17268
+ charTextureData[dstOffset0 + 3] = font.charBuffer[srcOffset + 3];
17269
+ charTextureData[dstOffset1] = font.charBuffer[srcOffset + 4];
17270
+ charTextureData[dstOffset1 + 1] = font.charBuffer[srcOffset + 5];
17271
+ charTextureData[dstOffset1 + 2] = font.charBuffer[srcOffset + 6];
17272
+ charTextureData[dstOffset1 + 3] = font.charBuffer[srcOffset + 7];
17204
17273
  }
17205
- return layer;
17274
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, charTextureWidth, 1, 0, gl.RGBA, gl.FLOAT, charTextureData);
17275
+ const textBufferTexture = gl.createTexture();
17276
+ assert(textBufferTexture, "Failed to create text buffer texture");
17277
+ gl.bindTexture(gl.TEXTURE_2D, textBufferTexture);
17278
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
17279
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
17280
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
17281
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
17282
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, maxCharCount, 1, 0, gl.RGBA, gl.FLOAT, null);
17283
+ gl.bindTexture(gl.TEXTURE_2D, null);
17284
+ return new WebGLFontPipeline(gl, font, fontTexture, charDataTexture, textBufferTexture, maxCharCount);
17206
17285
  }
17207
- #findOrCreatePipeline(layer, shader) {
17208
- let pipeline = layer.pipelines.find((p3) => p3.shader === shader);
17209
- if (!pipeline) {
17210
- pipeline = { shader, nodes: [] };
17211
- layer.pipelines.push(pipeline);
17212
- this.pipelines.push(pipeline);
17213
- }
17214
- return pipeline;
17286
+ updateTextBuffer(data, glyphCount) {
17287
+ const gl = this.#gl;
17288
+ gl.bindTexture(gl.TEXTURE_2D, this.textBufferTexture);
17289
+ gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, glyphCount, 1, gl.RGBA, gl.FLOAT, data);
17290
+ }
17291
+ destroy() {
17292
+ const gl = this.#gl;
17293
+ gl.deleteTexture(this.fontTexture);
17294
+ gl.deleteTexture(this.charDataTexture);
17295
+ gl.deleteTexture(this.textBufferTexture);
17215
17296
  }
17216
17297
  }
17217
17298
 
17218
- // src/math/angle.ts
17219
- function deg2rad(degrees) {
17220
- return degrees * (Math.PI / 180);
17221
- }
17222
- function rad2deg(radians) {
17223
- return radians * (180 / Math.PI);
17224
- }
17225
-
17226
- // src/scene/Camera.ts
17227
- class Camera {
17228
- #position = { x: 0, y: 0 };
17229
- #zoom = 1;
17230
- #rotation = 0;
17231
- #isDirty = true;
17232
- #matrix = mat3.create();
17233
- get zoom() {
17234
- return this.#zoom;
17235
- }
17236
- set zoom(value) {
17237
- this.#zoom = value;
17238
- this.setDirty();
17239
- }
17240
- get rotation() {
17241
- return rad2deg(this.#rotation);
17242
- }
17243
- set rotation(value) {
17244
- this.#rotation = deg2rad(value);
17245
- this.setDirty();
17246
- }
17247
- get rotationRadians() {
17248
- return this.#rotation;
17249
- }
17250
- set rotationRadians(value) {
17251
- this.#rotation = value;
17252
- this.setDirty();
17253
- }
17254
- get x() {
17255
- return this.#position.x;
17256
- }
17257
- get y() {
17258
- return this.#position.y;
17259
- }
17260
- set x(value) {
17261
- this.#position.x = value;
17262
- this.setDirty();
17263
- }
17264
- set y(value) {
17265
- this.#position.y = value;
17266
- this.setDirty();
17267
- }
17268
- get matrix() {
17269
- if (this.#isDirty) {
17270
- this.#isDirty = false;
17271
- this.#matrix = createViewMatrix(this, this.#matrix);
17272
- }
17273
- return this.#matrix;
17274
- }
17275
- setDirty() {
17276
- this.#isDirty = true;
17299
+ // src/utils/error.ts
17300
+ var warnings = new Map;
17301
+ function warnOnce(key, msg) {
17302
+ if (warnings.has(key)) {
17303
+ return;
17277
17304
  }
17305
+ warnings.set(key, true);
17306
+ console.warn(msg ?? key);
17278
17307
  }
17279
17308
 
17280
- // src/scene/SceneNode.ts
17281
- class SceneNode {
17282
- static nextId = 1;
17309
+ // src/text/MsdfFont.ts
17310
+ class MsdfFont {
17283
17311
  id;
17284
- label;
17285
- #isActive = true;
17286
- #layer = null;
17287
- #parent = null;
17288
- #key = null;
17289
- #kids = [];
17290
- #transform;
17291
- #matrix = mat3.identity();
17292
- #renderComponent = null;
17293
- #size = null;
17294
- #positionProxy;
17295
- #scaleProxy;
17296
- #cache = null;
17297
- constructor(opts) {
17298
- this.id = opts?.id ?? SceneNode.nextId++;
17299
- if (opts?.rotation && opts?.rotationRadians) {
17300
- throw new Error(`Cannot set both rotation and rotationRadians for node ${opts?.label ?? this.id}`);
17301
- }
17302
- this.#transform = {
17303
- position: opts?.position ?? { x: 0, y: 0 },
17304
- scale: { x: 1, y: 1 },
17305
- size: opts?.size ?? { width: 1, height: 1 },
17306
- rotation: opts?.rotationRadians ?? 0
17307
- };
17308
- if (opts?.scale)
17309
- this.scale = opts.scale;
17310
- if (opts?.rotation)
17311
- this.rotation = opts.rotation;
17312
- this.#matrix = mat3.identity();
17313
- this.#renderComponent = opts?.render ?? null;
17314
- this.#layer = opts?.layer ?? null;
17315
- this.#isActive = opts?.isActive ?? true;
17316
- this.label = opts?.label ?? undefined;
17317
- this.#size = opts?.size ?? null;
17318
- this.#key = opts?.key ?? null;
17319
- for (const kid of opts?.kids ?? []) {
17320
- this.add(kid);
17321
- }
17322
- const self = this;
17323
- this.#positionProxy = {
17324
- get x() {
17325
- return self.#transform.position.x;
17326
- },
17327
- set x(value) {
17328
- self.#transform.position.x = value;
17329
- self.setDirty();
17330
- },
17331
- get y() {
17332
- return self.#transform.position.y;
17333
- },
17334
- set y(value) {
17335
- self.#transform.position.y = value;
17336
- self.setDirty();
17337
- }
17338
- };
17339
- this.#scaleProxy = {
17340
- get x() {
17341
- return self.#transform.scale.x;
17342
- },
17343
- set x(value) {
17344
- self.#transform.scale.x = value;
17345
- self.setDirty();
17346
- },
17347
- get y() {
17348
- return self.#transform.scale.y;
17349
- },
17350
- set y(value) {
17351
- self.#transform.scale.y = value;
17352
- self.setDirty();
17312
+ json;
17313
+ imageBitmap;
17314
+ name;
17315
+ charset;
17316
+ charCount;
17317
+ lineHeight;
17318
+ charBuffer;
17319
+ #kernings;
17320
+ #chars;
17321
+ #fallbackCharCode;
17322
+ constructor(id, json, imageBitmap) {
17323
+ this.id = id;
17324
+ this.json = json;
17325
+ this.imageBitmap = imageBitmap;
17326
+ const charArray = Object.values(json.chars);
17327
+ this.charCount = charArray.length;
17328
+ this.lineHeight = json.common.lineHeight;
17329
+ this.charset = json.info.charset;
17330
+ this.name = json.info.face;
17331
+ this.#kernings = new Map;
17332
+ if (json.kernings) {
17333
+ for (const kearning of json.kernings) {
17334
+ let charKerning = this.#kernings.get(kearning.first);
17335
+ if (!charKerning) {
17336
+ charKerning = new Map;
17337
+ this.#kernings.set(kearning.first, charKerning);
17338
+ }
17339
+ charKerning.set(kearning.second, kearning.amount);
17353
17340
  }
17354
- };
17355
- }
17356
- add(kid, index) {
17357
- kid.#parent = this;
17358
- if (index === undefined) {
17359
- this.#kids.push(kid);
17360
- } else {
17361
- this.#kids.splice(index, 0, kid);
17362
17341
  }
17363
- kid.setDirty();
17364
- return kid;
17365
- }
17366
- get kids() {
17367
- return this.#kids;
17368
- }
17369
- get children() {
17370
- return this.#kids;
17371
- }
17372
- get transform() {
17373
- return this.#transform;
17374
- }
17375
- get key() {
17376
- return this.#key ?? "";
17377
- }
17378
- get parent() {
17379
- return this.#parent;
17380
- }
17381
- set position(value) {
17382
- this.#transform.position = value;
17383
- this.setDirty();
17384
- }
17385
- get position() {
17386
- return this.#positionProxy;
17387
- }
17388
- set x(value) {
17389
- this.#transform.position.x = value;
17390
- this.setDirty();
17391
- }
17392
- get x() {
17393
- return this.#transform.position.x;
17394
- }
17395
- set y(value) {
17396
- this.#transform.position.y = value;
17397
- this.setDirty();
17398
- }
17399
- get y() {
17400
- return this.#transform.position.y;
17401
- }
17402
- set rotation(value) {
17403
- this.#transform.rotation = deg2rad(value);
17404
- this.setDirty();
17405
- }
17406
- get rotation() {
17407
- return rad2deg(this.#transform.rotation);
17408
- }
17409
- get rotationRadians() {
17410
- return this.#transform.rotation;
17411
- }
17412
- set rotationRadians(value) {
17413
- this.#transform.rotation = value;
17414
- this.setDirty();
17415
- }
17416
- get scale() {
17417
- return this.#scaleProxy;
17418
- }
17419
- set scale(value) {
17420
- if (typeof value === "number") {
17421
- this.#transform.scale = { x: value, y: value };
17422
- } else {
17423
- this.#transform.scale = value;
17342
+ this.#chars = new Map;
17343
+ const charCount = Object.values(json.chars).length;
17344
+ this.charBuffer = new Float32Array(charCount * 8);
17345
+ let offset = 0;
17346
+ const u3 = 1 / json.common.scaleW;
17347
+ const v3 = 1 / json.common.scaleH;
17348
+ for (const [i3, char] of json.chars.entries()) {
17349
+ this.#chars.set(char.id, char);
17350
+ this.#chars.get(char.id).charIndex = i3;
17351
+ this.charBuffer[offset] = char.x * u3;
17352
+ this.charBuffer[offset + 1] = char.y * v3;
17353
+ this.charBuffer[offset + 2] = char.width * u3;
17354
+ this.charBuffer[offset + 3] = char.height * v3;
17355
+ this.charBuffer[offset + 4] = char.width;
17356
+ this.charBuffer[offset + 5] = char.height;
17357
+ this.charBuffer[offset + 6] = char.xoffset;
17358
+ this.charBuffer[offset + 7] = -char.yoffset;
17359
+ offset += 8;
17424
17360
  }
17425
- this.setDirty();
17426
- }
17427
- set size(value) {
17428
- this.#size = value;
17429
- this.setDirty();
17430
17361
  }
17431
- get size() {
17432
- return this.#size;
17433
- }
17434
- get aspectRatio() {
17435
- if (!this.#size) {
17436
- console.warn("Attempted to get aspect ratio of a node with no ideal size");
17437
- return 1;
17362
+ getChar(charCode) {
17363
+ const char = this.#chars.get(charCode);
17364
+ if (!char) {
17365
+ const fallbackCharacter = this.#chars.get(this.#fallbackCharCode ?? this.#chars.keys().toArray()[0]);
17366
+ warnOnce(`unknown_char_${this.name}`, `Couldn't find character ${charCode} in characters for font ${this.name} -- defaulting to first available character "${fallbackCharacter.char}"`);
17367
+ return fallbackCharacter;
17438
17368
  }
17439
- return this.#size.width / this.#size.height;
17369
+ return char;
17440
17370
  }
17441
- get isActive() {
17442
- if (!this.#cache?.isActive) {
17443
- this.#cache ??= {};
17444
- let parent = this;
17445
- let isActive = this.#isActive;
17446
- while (isActive && parent.#parent) {
17447
- parent = parent.#parent;
17448
- isActive = isActive && parent.#isActive;
17371
+ getXAdvance(charCode, nextCharCode = -1) {
17372
+ const char = this.getChar(charCode);
17373
+ if (nextCharCode >= 0) {
17374
+ const kerning = this.#kernings.get(charCode);
17375
+ if (kerning) {
17376
+ return char.xadvance + (kerning.get(nextCharCode) ?? 0);
17449
17377
  }
17450
- this.#cache.isActive = isActive;
17451
17378
  }
17452
- return this.#cache.isActive;
17453
- }
17454
- set isActive(value) {
17455
- this.#isActive = value;
17456
- this.setDirty();
17379
+ return char.xadvance;
17457
17380
  }
17458
- get layer() {
17459
- if (this.#layer != null) {
17460
- return this.#layer;
17381
+ static async create(id, fontJsonUrl) {
17382
+ const response = await fetch(fontJsonUrl);
17383
+ const json = await response.json();
17384
+ const i3 = fontJsonUrl.href.lastIndexOf("/");
17385
+ const baseUrl = i3 !== -1 ? fontJsonUrl.href.substring(0, i3 + 1) : undefined;
17386
+ if (json.pages.length < 1) {
17387
+ throw new Error(`Can't create an msdf font without a reference to the page url in the json`);
17461
17388
  }
17462
- if (!this.#cache?.layer) {
17463
- this.#cache ??= {};
17464
- let parent = this;
17465
- while (parent.#parent) {
17466
- parent = parent.#parent;
17467
- if (parent.hasExplicitLayer) {
17468
- this.#cache.layer = parent.#layer;
17469
- return this.#cache.layer;
17470
- }
17471
- }
17472
- this.#cache.layer = 0;
17389
+ if (json.pages.length > 1) {
17390
+ throw new Error(`Can't create an msdf font with more than one page`);
17473
17391
  }
17474
- return this.#cache.layer;
17475
- }
17476
- get hasExplicitLayer() {
17477
- return this.#layer != null;
17478
- }
17479
- set layer(value) {
17480
- this.#layer = value;
17481
- this.setDirty();
17392
+ const textureUrl = baseUrl + json.pages[0];
17393
+ const textureResponse = await fetch(textureUrl);
17394
+ const bitmap = await createImageBitmap(await textureResponse.blob());
17395
+ return new MsdfFont(id, json, bitmap);
17482
17396
  }
17483
- get renderComponent() {
17484
- return this.#renderComponent;
17485
- }
17486
- get matrix() {
17487
- if (!this.#cache?.matrix) {
17488
- this.#cache ??= {};
17489
- if (this.#parent) {
17490
- mat3.clone(this.#parent.matrix, this.#matrix);
17491
- } else {
17492
- mat3.identity(this.#matrix);
17493
- }
17494
- this.#cache.matrix = createModelMatrix(this.transform, this.#matrix);
17397
+ set fallbackCharacter(character) {
17398
+ const charCode = character.charCodeAt(0);
17399
+ if (this.#chars.has(charCode)) {
17400
+ this.#fallbackCharCode = charCode;
17401
+ } else {
17402
+ const fallbackCode = this.#chars.keys().toArray()[0];
17403
+ console.warn(`${character} character does not exist in font ${this.name} defaulting to "${this.#chars.get(fallbackCode)?.char}".`);
17404
+ this.#fallbackCharCode = fallbackCode;
17495
17405
  }
17496
- return this.#cache.matrix;
17497
17406
  }
17498
- get bounds() {
17499
- if (!this.#cache?.bounds) {
17500
- this.#cache ??= {};
17501
- const height = this.size?.height ?? 0;
17502
- const width = this.size?.width ?? 0;
17503
- const corners = [
17504
- vec2.transformMat3([-width / 2, height / 2], this.matrix),
17505
- vec2.transformMat3([width / 2, height / 2], this.matrix),
17506
- vec2.transformMat3([-width / 2, -height / 2], this.matrix),
17507
- vec2.transformMat3([width / 2, -height / 2], this.matrix)
17508
- ];
17509
- const center = vec2.transformMat3([0, 0], this.matrix);
17510
- const xValues = corners.map((c3) => c3[0]);
17511
- const yValues = corners.map((c3) => c3[1]);
17512
- this.#cache.bounds = {
17513
- x: center[0],
17514
- y: center[1],
17515
- left: Math.min(xValues[0], xValues[1], xValues[2], xValues[3]),
17516
- right: Math.max(xValues[0], xValues[1], xValues[2], xValues[3]),
17517
- top: Math.max(yValues[0], yValues[1], yValues[2], yValues[3]),
17518
- bottom: Math.min(yValues[0], yValues[1], yValues[2], yValues[3])
17519
- };
17520
- }
17521
- return this.#cache.bounds;
17407
+ }
17408
+
17409
+ // src/text/shaping.ts
17410
+ var TAB_SPACES = 4;
17411
+ function shapeText(font, text, blockSize, fontSize, formatting, textArray, initialFloatOffset = 0, debug = false) {
17412
+ let offset = initialFloatOffset;
17413
+ const measurements = measureText(font, text, formatting.wordWrap);
17414
+ const alignment = formatting.align || "left";
17415
+ const em2px = fontSize / font.lineHeight;
17416
+ const hackHasExplicitBlock = blockSize.width !== measurements.width;
17417
+ let debugData = null;
17418
+ if (debug) {
17419
+ debugData = [];
17522
17420
  }
17523
- setBounds(bounds) {
17524
- if (bounds.left !== undefined)
17525
- this.left = bounds.left;
17526
- if (bounds.right !== undefined)
17527
- this.right = bounds.right;
17528
- if (bounds.top !== undefined)
17529
- this.top = bounds.top;
17530
- if (bounds.bottom !== undefined)
17531
- this.bottom = bounds.bottom;
17532
- if (bounds.x !== undefined)
17533
- this.centerX = bounds.x;
17534
- if (bounds.y !== undefined)
17535
- this.centerY = bounds.y;
17536
- return this;
17421
+ for (const word of measurements.words) {
17422
+ for (const glyph of word.glyphs) {
17423
+ let lineOffset = 0;
17424
+ if (alignment === "center") {
17425
+ lineOffset = measurements.width * -0.5 - (measurements.width - measurements.lineWidths[glyph.line]) * -0.5;
17426
+ } else if (alignment === "right") {
17427
+ const blockSizeEm = blockSize.width / em2px;
17428
+ const delta = measurements.width - measurements.lineWidths[glyph.line];
17429
+ lineOffset = (hackHasExplicitBlock ? blockSizeEm / 2 : measurements.width / 2) - measurements.width + delta;
17430
+ } else if (alignment === "left") {
17431
+ const blockSizeEm = blockSize.width / em2px;
17432
+ lineOffset = hackHasExplicitBlock ? -blockSizeEm / 2 : -measurements.width / 2;
17433
+ }
17434
+ if (debug && debugData) {
17435
+ debugData.push({
17436
+ line: glyph.line,
17437
+ word: word.glyphs.map((g3) => g3.char.char).join(""),
17438
+ glyph: glyph.char.char,
17439
+ startX: word.startX,
17440
+ glyphX: glyph.offset[0],
17441
+ advance: glyph.char.xadvance,
17442
+ lineOffset,
17443
+ startY: word.startY,
17444
+ glyphY: glyph.offset[1]
17445
+ });
17446
+ }
17447
+ textArray[offset] = word.startX + glyph.offset[0] + lineOffset;
17448
+ textArray[offset + 1] = word.startY + glyph.offset[1];
17449
+ textArray[offset + 2] = glyph.char.charIndex;
17450
+ offset += 4;
17451
+ }
17537
17452
  }
17538
- set left(value) {
17539
- this.#adjustWorldPosition([value - this.bounds.left, 0]);
17453
+ if (debug && debugData) {
17454
+ console.table(debugData);
17540
17455
  }
17541
- set bottom(value) {
17542
- this.#adjustWorldPosition([0, value - this.bounds.bottom]);
17456
+ }
17457
+ function measureText(font, text, wordWrap) {
17458
+ let maxWidth = 0;
17459
+ const lineWidths = [];
17460
+ let textOffsetX = 0;
17461
+ let textOffsetY = 0;
17462
+ let line = 0;
17463
+ let printedCharCount = 0;
17464
+ let nextCharCode = text.charCodeAt(0);
17465
+ let word = { glyphs: [], width: 0, startX: 0, startY: 0 };
17466
+ const words = [];
17467
+ for (let i3 = 0;i3 < text.length; i3++) {
17468
+ const isLastLetter = i3 === text.length - 1;
17469
+ const charCode = nextCharCode;
17470
+ nextCharCode = i3 < text.length - 1 ? text.charCodeAt(i3 + 1) : -1;
17471
+ switch (charCode) {
17472
+ case 9 /* HorizontalTab */:
17473
+ insertSpaces(TAB_SPACES);
17474
+ break;
17475
+ case 10 /* Newline */:
17476
+ flushLine();
17477
+ flushWord();
17478
+ break;
17479
+ case 13 /* CarriageReturn */:
17480
+ break;
17481
+ case 32 /* Space */:
17482
+ insertSpaces(1);
17483
+ break;
17484
+ default: {
17485
+ const advance = font.getXAdvance(charCode, nextCharCode);
17486
+ if (wordWrap && wordWrap.breakOn === "character" && textOffsetX + advance > wordWrap.emWidth) {
17487
+ if (word.startX === 0) {
17488
+ flushWord();
17489
+ } else {
17490
+ lineWidths.push(textOffsetX - word.width);
17491
+ line++;
17492
+ maxWidth = Math.max(maxWidth, textOffsetX);
17493
+ textOffsetX = word.width;
17494
+ textOffsetY -= font.lineHeight;
17495
+ word.startX = 0;
17496
+ word.startY = textOffsetY;
17497
+ word.glyphs.forEach((g3) => {
17498
+ g3.line = line;
17499
+ });
17500
+ }
17501
+ }
17502
+ word.glyphs.push({
17503
+ char: font.getChar(charCode),
17504
+ offset: [word.width, 0],
17505
+ line
17506
+ });
17507
+ if (isLastLetter) {
17508
+ flushWord();
17509
+ }
17510
+ word.width += advance;
17511
+ textOffsetX += advance;
17512
+ }
17513
+ }
17543
17514
  }
17544
- set top(value) {
17545
- this.#adjustWorldPosition([0, value - this.bounds.top]);
17515
+ lineWidths.push(textOffsetX);
17516
+ maxWidth = Math.max(maxWidth, textOffsetX);
17517
+ const lineCount = lineWidths.length;
17518
+ return {
17519
+ width: maxWidth,
17520
+ height: lineCount * font.lineHeight,
17521
+ lineWidths,
17522
+ lineCount,
17523
+ printedCharCount,
17524
+ words
17525
+ };
17526
+ function flushWord() {
17527
+ printedCharCount += word.glyphs.length;
17528
+ words.push(word);
17529
+ word = {
17530
+ glyphs: [],
17531
+ width: 0,
17532
+ startX: textOffsetX,
17533
+ startY: textOffsetY
17534
+ };
17546
17535
  }
17547
- set right(value) {
17548
- this.#adjustWorldPosition([value - this.bounds.right, 0]);
17536
+ function flushLine() {
17537
+ lineWidths.push(textOffsetX);
17538
+ line++;
17539
+ maxWidth = Math.max(maxWidth, textOffsetX);
17540
+ textOffsetX = 0;
17541
+ textOffsetY -= font.lineHeight;
17549
17542
  }
17550
- set centerX(value) {
17551
- this.#adjustWorldPosition([value - this.bounds.x, 0]);
17543
+ function insertSpaces(spaces) {
17544
+ if (spaces < 1)
17545
+ spaces = 1;
17546
+ textOffsetX += font.getXAdvance(32 /* Space */) * spaces;
17547
+ if (wordWrap?.breakOn === "word" && textOffsetX >= wordWrap.emWidth) {
17548
+ flushLine();
17549
+ }
17550
+ flushWord();
17552
17551
  }
17553
- set centerY(value) {
17554
- this.#adjustWorldPosition([0, value - this.bounds.y]);
17552
+ }
17553
+ function findLargestFontSize(font, text, size, formatting) {
17554
+ if (!formatting.fontSize) {
17555
+ throw new Error("fontSize is required for shrinkToFit");
17555
17556
  }
17556
- delete() {
17557
- this.#parent?.remove(this);
17558
- for (const child of this.#kids) {
17559
- child.delete();
17560
- }
17561
- this.#kids = [];
17562
- this.#isActive = false;
17563
- this.#layer = null;
17564
- this.#renderComponent = null;
17557
+ if (!formatting.shrinkToFit) {
17558
+ throw new Error("shrinkToFit is required for findLargestFontSize");
17565
17559
  }
17566
- remove(kid) {
17567
- const childIndex = this.#kids.findIndex((child) => child.id === kid.id);
17568
- if (childIndex <= -1) {
17569
- throw new Error(`${kid.label ?? kid.id} is not a child of ${this.label ?? this.id}`);
17560
+ const minSize = formatting.shrinkToFit.minFontSize;
17561
+ const maxSize = formatting.shrinkToFit.maxFontSize ?? formatting.fontSize;
17562
+ const maxLines = formatting.shrinkToFit.maxLines ?? Number.POSITIVE_INFINITY;
17563
+ const threshold = 0.5;
17564
+ let low = minSize;
17565
+ let high = maxSize;
17566
+ while (high - low > threshold) {
17567
+ const testSize = (low + high) / 2;
17568
+ const testMeasure = measureText(font, text, formatting.wordWrap);
17569
+ const padding = formatting.shrinkToFit.padding ?? 0;
17570
+ const scaledWidth = testMeasure.width * (testSize / font.lineHeight);
17571
+ const scaledHeight = testMeasure.height * (testSize / font.lineHeight);
17572
+ const fitsWidth = scaledWidth <= size.width - size.width * padding;
17573
+ const fitsHeight = scaledHeight <= size.height - size.height * padding;
17574
+ const fitsLines = testMeasure.lineCount <= maxLines;
17575
+ if (fitsWidth && fitsHeight && fitsLines) {
17576
+ low = testSize;
17577
+ } else {
17578
+ high = testSize;
17570
17579
  }
17571
- this.#kids.splice(childIndex, 1);
17572
- kid.#parent = null;
17573
- kid.setDirty();
17574
- }
17575
- #adjustWorldPosition(delta) {
17576
- const inverseMatrix = mat3.inverse(this.#parent?.matrix ?? mat3.identity());
17577
- inverseMatrix[8] = inverseMatrix[9] = 0;
17578
- const localDelta = vec2.transformMat3(delta, inverseMatrix);
17579
- this.#transform.position.x += localDelta[0];
17580
- this.#transform.position.y += localDelta[1];
17581
- this.setDirty();
17582
- }
17583
- setDirty() {
17584
- this.#cache = null;
17585
- this.#kids.forEach((kid) => kid.setDirty());
17586
- }
17587
- static parse(json) {
17588
- const obj = JSON.parse(json, reviver);
17589
- return new SceneNode(obj);
17590
- }
17591
- toJSON() {
17592
- return {
17593
- id: this.id,
17594
- label: this.label,
17595
- transform: this.#transform,
17596
- layer: this.#layer,
17597
- isActive: this.#isActive,
17598
- kids: this.#kids,
17599
- render: this.#renderComponent
17600
- };
17601
17580
  }
17581
+ return low;
17602
17582
  }
17603
- function reviver(key, value) {
17604
- if (key === "kids") {
17605
- return value.map((kid) => new SceneNode(kid));
17606
- }
17607
- if (Array.isArray(value) && value.every((v3) => typeof v3 === "number")) {
17608
- if (value.length === 2) {
17609
- return value;
17610
- }
17611
- if (value.length === 3) {
17612
- return value;
17613
- }
17614
- if (value.length === 4) {
17615
- return value;
17616
- }
17617
- }
17618
- return value;
17583
+
17584
+ // src/math/angle.ts
17585
+ function deg2rad(degrees) {
17586
+ return degrees * (Math.PI / 180);
17587
+ }
17588
+ function rad2deg(radians) {
17589
+ return radians * (180 / Math.PI);
17619
17590
  }
17620
17591
 
17621
- // src/scene/QuadNode.ts
17622
- var PRIMITIVE_TEXTURE = "__primitive__";
17623
- var RESERVED_PRIMITIVE_INDEX_START = 1000;
17624
- var CIRCLE_INDEX = 1001;
17625
- var DEFAULT_REGION = {
17626
- x: 0,
17627
- y: 0,
17628
- width: 0,
17629
- height: 0
17630
- };
17592
+ // src/math/matrix.ts
17593
+ function createProjectionMatrix(resolution, dst) {
17594
+ const { width, height } = resolution;
17595
+ return mat3.scaling([2 / width, 2 / height], dst);
17596
+ }
17597
+ function createViewMatrix(camera, target) {
17598
+ const matrix = mat3.identity(target);
17599
+ mat3.scale(matrix, [camera.zoom, camera.zoom], matrix);
17600
+ mat3.rotate(matrix, camera.rotationRadians, matrix);
17601
+ mat3.translate(matrix, [-camera.x, -camera.y], matrix);
17602
+ return matrix;
17603
+ }
17604
+ function createModelMatrix(transform, base) {
17605
+ mat3.translate(base, [transform.position.x, transform.position.y], base);
17606
+ mat3.rotate(base, transform.rotation, base);
17607
+ mat3.scale(base, [transform.scale.x, transform.scale.y], base);
17608
+ return base;
17609
+ }
17610
+ function convertScreenToWorld(screenCoordinates, camera, projectionMatrix, resolution) {
17611
+ const inverseViewProjectionMatrix = mat3.mul(mat3.inverse(camera.matrix), mat3.inverse(projectionMatrix));
17612
+ const normalizedDeviceCoordinates = {
17613
+ x: 2 * screenCoordinates.x / resolution.width - 1,
17614
+ y: 1 - 2 * screenCoordinates.y / resolution.height
17615
+ };
17616
+ return transformPoint(normalizedDeviceCoordinates, inverseViewProjectionMatrix);
17617
+ }
17618
+ function convertWorldToScreen(worldCoordinates, camera, projectionMatrix, resolution) {
17619
+ const viewProjectionMatrix = mat3.mul(projectionMatrix, camera.matrix);
17620
+ const ndcPoint = transformPoint(worldCoordinates, viewProjectionMatrix);
17621
+ return {
17622
+ x: (ndcPoint.x + 1) * resolution.width / 2,
17623
+ y: (1 - ndcPoint.y) * resolution.height / 2
17624
+ };
17625
+ }
17626
+ function transformPoint(point, matrix) {
17627
+ const result = vec2.transformMat3([point.x, point.y], matrix);
17628
+ return {
17629
+ x: result[0],
17630
+ y: result[1]
17631
+ };
17632
+ }
17631
17633
 
17632
- class QuadNode extends SceneNode {
17633
- assetManager;
17634
- #color;
17635
- #atlasCoords;
17636
- #region;
17637
- #matrixPool;
17638
- #flip;
17639
- #cropOffset;
17640
- #cropRatio;
17641
- #atlasSize;
17642
- #textureId;
17643
- #writeInstance;
17644
- constructor(options, matrixPool) {
17645
- assert(options.shader, "QuadNode requires a shader to be explicitly provided");
17646
- assert(options.size, "QuadNode requires a size to be explicitly provided");
17647
- assert(options.atlasCoords, "QuadNode requires atlas coords to be explicitly provided");
17648
- options.render ??= {
17649
- shader: options.shader,
17650
- writeInstance: writeQuadInstance
17634
+ // src/scene/SceneNode.ts
17635
+ class SceneNode {
17636
+ static nextId = 1;
17637
+ id;
17638
+ label;
17639
+ #isActive = true;
17640
+ #layer = null;
17641
+ #parent = null;
17642
+ #key = null;
17643
+ #kids = [];
17644
+ #transform;
17645
+ #matrix = mat3.identity();
17646
+ #renderComponent = null;
17647
+ #size = null;
17648
+ #positionProxy;
17649
+ #scaleProxy;
17650
+ #cache = null;
17651
+ constructor(opts) {
17652
+ this.id = opts?.id ?? SceneNode.nextId++;
17653
+ if (opts?.rotation && opts?.rotationRadians) {
17654
+ throw new Error(`Cannot set both rotation and rotationRadians for node ${opts?.label ?? this.id}`);
17655
+ }
17656
+ this.#transform = {
17657
+ position: opts?.position ?? { x: 0, y: 0 },
17658
+ scale: { x: 1, y: 1 },
17659
+ size: opts?.size ?? { width: 1, height: 1 },
17660
+ rotation: opts?.rotationRadians ?? 0
17651
17661
  };
17652
- super(options);
17653
- assert(options.assetManager, "QuadNode requires an asset manager");
17654
- this.assetManager = options.assetManager;
17655
- if (options.atlasCoords && options.atlasCoords.atlasIndex >= RESERVED_PRIMITIVE_INDEX_START) {
17656
- this.#textureId = PRIMITIVE_TEXTURE;
17657
- this.#region = DEFAULT_REGION;
17658
- this.#atlasSize = DEFAULT_REGION;
17659
- } else {
17660
- assert(options.textureId, "QuadNode requires texture id to be explicitly provided");
17661
- this.#textureId = options.textureId;
17662
- assert(options.region, "QuadNode requires a region to be explicitly provided");
17663
- this.#region = options.region;
17664
- assert(options.atlasSize, "QuadNode requires atlas size to be explicitly provided");
17665
- this.#atlasSize = options.atlasSize;
17662
+ if (opts?.scale)
17663
+ this.scale = opts.scale;
17664
+ if (opts?.rotation)
17665
+ this.rotation = opts.rotation;
17666
+ this.#matrix = mat3.identity();
17667
+ this.#renderComponent = opts?.render ?? null;
17668
+ this.#layer = opts?.layer ?? null;
17669
+ this.#isActive = opts?.isActive ?? true;
17670
+ this.label = opts?.label ?? undefined;
17671
+ this.#size = opts?.size ?? null;
17672
+ this.#key = opts?.key ?? null;
17673
+ for (const kid of opts?.kids ?? []) {
17674
+ this.add(kid);
17666
17675
  }
17667
- this.#atlasCoords = options.atlasCoords;
17668
- this.#color = options.color ?? { r: 1, g: 1, b: 1, a: 1 };
17669
- this.#matrixPool = matrixPool;
17670
- this.#flip = { x: options.flipX ? -1 : 1, y: options.flipY ? -1 : 1 };
17671
- this.#cropOffset = options.cropOffset ?? { x: 0, y: 0 };
17672
- this.#cropRatio = !this.#atlasCoords.uvScaleCropped ? { width: 1, height: 1 } : {
17673
- width: this.#atlasCoords.uvScaleCropped.width / this.#atlasCoords.uvScale.width,
17674
- height: this.#atlasCoords.uvScaleCropped.height / this.#atlasCoords.uvScale.height
17676
+ const self = this;
17677
+ this.#positionProxy = {
17678
+ get x() {
17679
+ return self.#transform.position.x;
17680
+ },
17681
+ set x(value) {
17682
+ self.#transform.position.x = value;
17683
+ self.setDirty();
17684
+ },
17685
+ get y() {
17686
+ return self.#transform.position.y;
17687
+ },
17688
+ set y(value) {
17689
+ self.#transform.position.y = value;
17690
+ self.setDirty();
17691
+ }
17692
+ };
17693
+ this.#scaleProxy = {
17694
+ get x() {
17695
+ return self.#transform.scale.x;
17696
+ },
17697
+ set x(value) {
17698
+ self.#transform.scale.x = value;
17699
+ self.setDirty();
17700
+ },
17701
+ get y() {
17702
+ return self.#transform.scale.y;
17703
+ },
17704
+ set y(value) {
17705
+ self.#transform.scale.y = value;
17706
+ self.setDirty();
17707
+ }
17675
17708
  };
17676
- this.#writeInstance = options.writeInstance;
17677
- }
17678
- get color() {
17679
- return this.#color;
17680
- }
17681
- set color(value) {
17682
- this.#color = value;
17683
17709
  }
17684
- get size() {
17685
- const size = super.size;
17686
- if (!size) {
17687
- throw new Error("QuadNode requires a size");
17710
+ add(kid, index) {
17711
+ kid.#parent = this;
17712
+ if (index === undefined) {
17713
+ this.#kids.push(kid);
17714
+ } else {
17715
+ this.#kids.splice(index, 0, kid);
17688
17716
  }
17689
- return size;
17717
+ kid.setDirty();
17718
+ return kid;
17690
17719
  }
17691
- set size(val) {
17692
- super.size = val;
17720
+ get kids() {
17721
+ return this.#kids;
17693
17722
  }
17694
- get matrixWithSize() {
17695
- const matrix = mat3.clone(this.matrix, this.#matrixPool.get());
17696
- mat3.scale(matrix, [this.size.width * this.#flip.x, this.size.height * this.#flip.y], matrix);
17697
- return matrix;
17723
+ get children() {
17724
+ return this.#kids;
17698
17725
  }
17699
- get atlasCoords() {
17700
- return this.#atlasCoords;
17726
+ get transform() {
17727
+ return this.#transform;
17701
17728
  }
17702
- get region() {
17703
- return this.#region;
17729
+ get key() {
17730
+ return this.#key ?? "";
17704
17731
  }
17705
- get writeInstance() {
17706
- return this.#writeInstance;
17732
+ get parent() {
17733
+ return this.#parent;
17707
17734
  }
17708
- get flipX() {
17709
- return this.#flip.x === -1;
17735
+ set position(value) {
17736
+ this.#transform.position = value;
17737
+ this.setDirty();
17710
17738
  }
17711
- set flipX(value) {
17712
- this.#flip.x = value ? -1 : 1;
17739
+ get position() {
17740
+ return this.#positionProxy;
17741
+ }
17742
+ set x(value) {
17743
+ this.#transform.position.x = value;
17713
17744
  this.setDirty();
17714
17745
  }
17715
- get flipY() {
17716
- return this.#flip.y === -1;
17746
+ get x() {
17747
+ return this.#transform.position.x;
17717
17748
  }
17718
- set flipY(value) {
17719
- this.#flip.y = value ? -1 : 1;
17749
+ set y(value) {
17750
+ this.#transform.position.y = value;
17720
17751
  this.setDirty();
17721
17752
  }
17722
- get cropOffset() {
17723
- return this.#cropOffset;
17753
+ get y() {
17754
+ return this.#transform.position.y;
17724
17755
  }
17725
- set cropOffset(value) {
17726
- this.#cropOffset = value;
17756
+ set rotation(value) {
17757
+ this.#transform.rotation = deg2rad(value);
17758
+ this.setDirty();
17727
17759
  }
17728
- get textureId() {
17729
- return this.#textureId;
17760
+ get rotation() {
17761
+ return rad2deg(this.#transform.rotation);
17730
17762
  }
17731
- get isPrimitive() {
17732
- return this.#textureId === PRIMITIVE_TEXTURE;
17763
+ get rotationRadians() {
17764
+ return this.#transform.rotation;
17733
17765
  }
17734
- get isCircle() {
17735
- return this.#atlasCoords.atlasIndex === CIRCLE_INDEX;
17766
+ set rotationRadians(value) {
17767
+ this.#transform.rotation = value;
17768
+ this.setDirty();
17736
17769
  }
17737
- extra = {
17738
- setAtlasCoords: (value) => {
17739
- this.#atlasCoords = value;
17740
- },
17741
- cropRatio: () => {
17742
- return this.#cropRatio;
17743
- },
17744
- atlasSize: () => {
17745
- return this.#atlasSize;
17770
+ get scale() {
17771
+ return this.#scaleProxy;
17772
+ }
17773
+ set scale(value) {
17774
+ if (typeof value === "number") {
17775
+ this.#transform.scale = { x: value, y: value };
17776
+ } else {
17777
+ this.#transform.scale = value;
17746
17778
  }
17747
- };
17748
- }
17749
- function writeQuadInstance(node, array, offset) {
17750
- if (!(node instanceof QuadNode)) {
17751
- throw new Error("QuadNode.writeInstance can only be called on QuadNodes");
17779
+ this.setDirty();
17752
17780
  }
17753
- array.set(node.matrixWithSize, offset);
17754
- array.set([node.color.r, node.color.g, node.color.b, node.color.a], offset + 12);
17755
- const region = node.region;
17756
- if (node.textureId === PRIMITIVE_TEXTURE) {
17757
- array.set([
17758
- node.atlasCoords.uvOffset.x,
17759
- node.atlasCoords.uvOffset.y,
17760
- node.atlasCoords.uvScale.width,
17761
- node.atlasCoords.uvScale.height
17762
- ], offset + 16);
17763
- } else {
17764
- const atlasSize = node.extra.atlasSize();
17765
- array.set([
17766
- node.atlasCoords.uvOffset.x + region.x / atlasSize.width,
17767
- node.atlasCoords.uvOffset.y + region.y / atlasSize.height,
17768
- region.width / atlasSize.width,
17769
- region.height / atlasSize.height
17770
- ], offset + 16);
17781
+ set size(value) {
17782
+ this.#size = value;
17783
+ this.setDirty();
17771
17784
  }
17772
- array.set([
17773
- node.cropOffset.x / 2 / (node.atlasCoords.originalSize.width || 1),
17774
- node.cropOffset.y / 2 / (node.atlasCoords.originalSize.height || 1),
17775
- node.extra.cropRatio().width,
17776
- node.extra.cropRatio().height
17777
- ], offset + 20);
17778
- new DataView(array.buffer).setUint32(array.byteOffset + (offset + 24) * Float32Array.BYTES_PER_ELEMENT, node.atlasCoords.atlasIndex, true);
17779
- node.writeInstance?.(array, offset + 28);
17780
- return 1;
17781
- }
17782
-
17783
- // src/scene/JumboQuadNode.ts
17784
- var MAT3_SIZE = 12;
17785
- var VEC4F_SIZE = 4;
17786
-
17787
- class JumboQuadNode extends QuadNode {
17788
- #tiles;
17789
- #matrixPool;
17790
- constructor(options, matrixPool) {
17791
- assert(options.shader, "JumboQuadNode requires a shader to be explicitly provided");
17792
- assert(options.tiles && options.tiles.length > 0, "JumboQuadNode requires at least one tile to be provided");
17793
- options.render ??= {
17794
- shader: options.shader,
17795
- writeInstance: writeJumboQuadInstance
17796
- };
17797
- super({
17798
- ...options,
17799
- atlasCoords: options.tiles[0].atlasCoords
17800
- }, matrixPool);
17801
- this.#matrixPool = matrixPool;
17802
- this.#tiles = [];
17803
- for (const tile of options.tiles) {
17804
- assert(tile.atlasCoords, "JumboQuadNode requires atlas coords to be provided");
17805
- assert(tile.size, "JumboQuadNode requires a size to be provided");
17806
- this.#tiles.push({
17807
- textureId: tile.textureId,
17808
- offset: tile.offset,
17809
- size: tile.size,
17810
- atlasCoords: tile.atlasCoords
17811
- });
17785
+ get size() {
17786
+ return this.#size;
17787
+ }
17788
+ get aspectRatio() {
17789
+ if (!this.#size) {
17790
+ console.warn("Attempted to get aspect ratio of a node with no ideal size");
17791
+ return 1;
17812
17792
  }
17793
+ return this.#size.width / this.#size.height;
17813
17794
  }
17814
- get atlasCoords() {
17815
- throw new Error("JumboQuadNode does not have a single atlas coords");
17795
+ get isActive() {
17796
+ if (!this.#cache?.isActive) {
17797
+ this.#cache ??= {};
17798
+ let parent = this;
17799
+ let isActive = this.#isActive;
17800
+ while (isActive && parent.#parent) {
17801
+ parent = parent.#parent;
17802
+ isActive = isActive && parent.#isActive;
17803
+ }
17804
+ this.#cache.isActive = isActive;
17805
+ }
17806
+ return this.#cache.isActive;
17816
17807
  }
17817
- get tiles() {
17818
- return this.#tiles;
17808
+ set isActive(value) {
17809
+ this.#isActive = value;
17810
+ this.setDirty();
17819
17811
  }
17820
- getTileMatrix(tile) {
17821
- const matrix = mat3.clone(this.matrix, this.#matrixPool.get());
17822
- const originalSize = {
17823
- width: Math.max(...this.#tiles.map((t3) => t3.offset.x + t3.size.width)),
17824
- height: Math.max(...this.#tiles.map((t3) => t3.offset.y + t3.size.height))
17825
- };
17826
- const proportionalSize = {
17827
- width: this.size.width / originalSize.width,
17828
- height: this.size.height / originalSize.height
17829
- };
17830
- const centerOffset = {
17831
- x: tile.offset.x + tile.size.width / 2 - originalSize.width / 2,
17832
- y: -(tile.offset.y + tile.size.height / 2 - originalSize.height / 2)
17833
- };
17834
- mat3.translate(matrix, [
17835
- centerOffset.x * proportionalSize.width,
17836
- centerOffset.y * proportionalSize.height
17837
- ], matrix);
17838
- mat3.scale(matrix, [
17839
- tile.size.width * proportionalSize.width * (this.flipX ? -1 : 1),
17840
- tile.size.height * proportionalSize.height * (this.flipY ? -1 : 1)
17841
- ], matrix);
17842
- return matrix;
17812
+ get layer() {
17813
+ if (this.#layer != null) {
17814
+ return this.#layer;
17815
+ }
17816
+ if (!this.#cache?.layer) {
17817
+ this.#cache ??= {};
17818
+ let parent = this;
17819
+ while (parent.#parent) {
17820
+ parent = parent.#parent;
17821
+ if (parent.hasExplicitLayer) {
17822
+ this.#cache.layer = parent.#layer;
17823
+ return this.#cache.layer;
17824
+ }
17825
+ }
17826
+ this.#cache.layer = 0;
17827
+ }
17828
+ return this.#cache.layer;
17843
17829
  }
17844
- }
17845
- function writeJumboQuadInstance(node, array, offset) {
17846
- if (!(node instanceof JumboQuadNode)) {
17847
- throw new Error("JumboQuadNode.writeJumboQuadInstance can only be called on JumboQuadNodes");
17830
+ get hasExplicitLayer() {
17831
+ return this.#layer != null;
17848
17832
  }
17849
- let tileOffset = 0;
17850
- for (const tile of node.tiles) {
17851
- const coord = tile.atlasCoords;
17852
- const matrix = node.getTileMatrix(tile);
17853
- array.set(matrix, offset + tileOffset);
17854
- tileOffset += MAT3_SIZE;
17855
- array.set([node.color.r, node.color.g, node.color.b, node.color.a], offset + tileOffset);
17856
- tileOffset += VEC4F_SIZE;
17857
- array.set([
17858
- coord.uvOffset.x,
17859
- coord.uvOffset.y,
17860
- coord.uvScale.width,
17861
- coord.uvScale.height
17862
- ], offset + tileOffset);
17863
- tileOffset += VEC4F_SIZE;
17864
- const cropRatio = !coord.uvScaleCropped ? { width: 1, height: 1 } : {
17865
- width: coord.uvScaleCropped.width / coord.uvScale.width,
17866
- height: coord.uvScaleCropped.height / coord.uvScale.height
17867
- };
17868
- array.set([
17869
- tile.atlasCoords.cropOffset.x / 2 / (tile.atlasCoords.originalSize.width || 1),
17870
- tile.atlasCoords.cropOffset.y / 2 / (tile.atlasCoords.originalSize.height || 1),
17871
- cropRatio.width,
17872
- cropRatio.height
17873
- ], offset + tileOffset);
17874
- tileOffset += VEC4F_SIZE;
17875
- new DataView(array.buffer).setUint32(array.byteOffset + (offset + tileOffset) * Float32Array.BYTES_PER_ELEMENT, coord.atlasIndex, true);
17876
- tileOffset += VEC4F_SIZE;
17833
+ set layer(value) {
17834
+ this.#layer = value;
17835
+ this.setDirty();
17877
17836
  }
17878
- node.writeInstance?.(array, offset + tileOffset);
17879
- return node.tiles.length;
17880
- }
17881
-
17882
- // src/utils/error.ts
17883
- var warnings = new Map;
17884
- function warnOnce(key, msg) {
17885
- if (warnings.has(key)) {
17886
- return;
17837
+ get renderComponent() {
17838
+ return this.#renderComponent;
17887
17839
  }
17888
- warnings.set(key, true);
17889
- console.warn(msg ?? key);
17890
- }
17891
-
17892
- // src/text/MsdfFont.ts
17893
- class MsdfFont {
17894
- id;
17895
- json;
17896
- imageBitmap;
17897
- name;
17898
- charset;
17899
- charCount;
17900
- lineHeight;
17901
- charBuffer;
17902
- #kernings;
17903
- #chars;
17904
- #fallbackCharCode;
17905
- constructor(id, json, imageBitmap) {
17906
- this.id = id;
17907
- this.json = json;
17908
- this.imageBitmap = imageBitmap;
17909
- const charArray = Object.values(json.chars);
17910
- this.charCount = charArray.length;
17911
- this.lineHeight = json.common.lineHeight;
17912
- this.charset = json.info.charset;
17913
- this.name = json.info.face;
17914
- this.#kernings = new Map;
17915
- if (json.kernings) {
17916
- for (const kearning of json.kernings) {
17917
- let charKerning = this.#kernings.get(kearning.first);
17918
- if (!charKerning) {
17919
- charKerning = new Map;
17920
- this.#kernings.set(kearning.first, charKerning);
17921
- }
17922
- charKerning.set(kearning.second, kearning.amount);
17840
+ get matrix() {
17841
+ if (!this.#cache?.matrix) {
17842
+ this.#cache ??= {};
17843
+ if (this.#parent) {
17844
+ mat3.clone(this.#parent.matrix, this.#matrix);
17845
+ } else {
17846
+ mat3.identity(this.#matrix);
17923
17847
  }
17848
+ this.#cache.matrix = createModelMatrix(this.transform, this.#matrix);
17924
17849
  }
17925
- this.#chars = new Map;
17926
- const charCount = Object.values(json.chars).length;
17927
- this.charBuffer = new Float32Array(charCount * 8);
17928
- let offset = 0;
17929
- const u3 = 1 / json.common.scaleW;
17930
- const v3 = 1 / json.common.scaleH;
17931
- for (const [i3, char] of json.chars.entries()) {
17932
- this.#chars.set(char.id, char);
17933
- this.#chars.get(char.id).charIndex = i3;
17934
- this.charBuffer[offset] = char.x * u3;
17935
- this.charBuffer[offset + 1] = char.y * v3;
17936
- this.charBuffer[offset + 2] = char.width * u3;
17937
- this.charBuffer[offset + 3] = char.height * v3;
17938
- this.charBuffer[offset + 4] = char.width;
17939
- this.charBuffer[offset + 5] = char.height;
17940
- this.charBuffer[offset + 6] = char.xoffset;
17941
- this.charBuffer[offset + 7] = -char.yoffset;
17942
- offset += 8;
17943
- }
17850
+ return this.#cache.matrix;
17944
17851
  }
17945
- getChar(charCode) {
17946
- const char = this.#chars.get(charCode);
17947
- if (!char) {
17948
- const fallbackCharacter = this.#chars.get(this.#fallbackCharCode ?? this.#chars.keys().toArray()[0]);
17949
- warnOnce(`unknown_char_${this.name}`, `Couldn't find character ${charCode} in characters for font ${this.name} -- defaulting to first available character "${fallbackCharacter.char}"`);
17950
- return fallbackCharacter;
17852
+ get bounds() {
17853
+ if (!this.#cache?.bounds) {
17854
+ this.#cache ??= {};
17855
+ const height = this.size?.height ?? 0;
17856
+ const width = this.size?.width ?? 0;
17857
+ const corners = [
17858
+ vec2.transformMat3([-width / 2, height / 2], this.matrix),
17859
+ vec2.transformMat3([width / 2, height / 2], this.matrix),
17860
+ vec2.transformMat3([-width / 2, -height / 2], this.matrix),
17861
+ vec2.transformMat3([width / 2, -height / 2], this.matrix)
17862
+ ];
17863
+ const center = vec2.transformMat3([0, 0], this.matrix);
17864
+ const xValues = corners.map((c3) => c3[0]);
17865
+ const yValues = corners.map((c3) => c3[1]);
17866
+ this.#cache.bounds = {
17867
+ x: center[0],
17868
+ y: center[1],
17869
+ left: Math.min(xValues[0], xValues[1], xValues[2], xValues[3]),
17870
+ right: Math.max(xValues[0], xValues[1], xValues[2], xValues[3]),
17871
+ top: Math.max(yValues[0], yValues[1], yValues[2], yValues[3]),
17872
+ bottom: Math.min(yValues[0], yValues[1], yValues[2], yValues[3])
17873
+ };
17951
17874
  }
17952
- return char;
17875
+ return this.#cache.bounds;
17953
17876
  }
17954
- getXAdvance(charCode, nextCharCode = -1) {
17955
- const char = this.getChar(charCode);
17956
- if (nextCharCode >= 0) {
17957
- const kerning = this.#kernings.get(charCode);
17958
- if (kerning) {
17959
- return char.xadvance + (kerning.get(nextCharCode) ?? 0);
17960
- }
17961
- }
17962
- return char.xadvance;
17877
+ setBounds(bounds) {
17878
+ if (bounds.left !== undefined)
17879
+ this.left = bounds.left;
17880
+ if (bounds.right !== undefined)
17881
+ this.right = bounds.right;
17882
+ if (bounds.top !== undefined)
17883
+ this.top = bounds.top;
17884
+ if (bounds.bottom !== undefined)
17885
+ this.bottom = bounds.bottom;
17886
+ if (bounds.x !== undefined)
17887
+ this.centerX = bounds.x;
17888
+ if (bounds.y !== undefined)
17889
+ this.centerY = bounds.y;
17890
+ return this;
17963
17891
  }
17964
- static async create(id, fontJsonUrl) {
17965
- const response = await fetch(fontJsonUrl);
17966
- const json = await response.json();
17967
- const i3 = fontJsonUrl.href.lastIndexOf("/");
17968
- const baseUrl = i3 !== -1 ? fontJsonUrl.href.substring(0, i3 + 1) : undefined;
17969
- if (json.pages.length < 1) {
17970
- throw new Error(`Can't create an msdf font without a reference to the page url in the json`);
17971
- }
17972
- if (json.pages.length > 1) {
17973
- throw new Error(`Can't create an msdf font with more than one page`);
17892
+ set left(value) {
17893
+ this.#adjustWorldPosition([value - this.bounds.left, 0]);
17894
+ }
17895
+ set bottom(value) {
17896
+ this.#adjustWorldPosition([0, value - this.bounds.bottom]);
17897
+ }
17898
+ set top(value) {
17899
+ this.#adjustWorldPosition([0, value - this.bounds.top]);
17900
+ }
17901
+ set right(value) {
17902
+ this.#adjustWorldPosition([value - this.bounds.right, 0]);
17903
+ }
17904
+ set centerX(value) {
17905
+ this.#adjustWorldPosition([value - this.bounds.x, 0]);
17906
+ }
17907
+ set centerY(value) {
17908
+ this.#adjustWorldPosition([0, value - this.bounds.y]);
17909
+ }
17910
+ delete() {
17911
+ this.#parent?.remove(this);
17912
+ for (const child of this.#kids) {
17913
+ child.delete();
17974
17914
  }
17975
- const textureUrl = baseUrl + json.pages[0];
17976
- const textureResponse = await fetch(textureUrl);
17977
- const bitmap = await createImageBitmap(await textureResponse.blob());
17978
- return new MsdfFont(id, json, bitmap);
17915
+ this.#kids = [];
17916
+ this.#isActive = false;
17917
+ this.#layer = null;
17918
+ this.#renderComponent = null;
17979
17919
  }
17980
- set fallbackCharacter(character) {
17981
- const charCode = character.charCodeAt(0);
17982
- if (this.#chars.has(charCode)) {
17983
- this.#fallbackCharCode = charCode;
17984
- } else {
17985
- const fallbackCode = this.#chars.keys().toArray()[0];
17986
- console.warn(`${character} character does not exist in font ${this.name} defaulting to "${this.#chars.get(fallbackCode)?.char}".`);
17987
- this.#fallbackCharCode = fallbackCode;
17920
+ remove(kid) {
17921
+ const childIndex = this.#kids.findIndex((child) => child.id === kid.id);
17922
+ if (childIndex <= -1) {
17923
+ throw new Error(`${kid.label ?? kid.id} is not a child of ${this.label ?? this.id}`);
17988
17924
  }
17925
+ this.#kids.splice(childIndex, 1);
17926
+ kid.#parent = null;
17927
+ kid.setDirty();
17928
+ }
17929
+ #adjustWorldPosition(delta) {
17930
+ const inverseMatrix = mat3.inverse(this.#parent?.matrix ?? mat3.identity());
17931
+ inverseMatrix[8] = inverseMatrix[9] = 0;
17932
+ const localDelta = vec2.transformMat3(delta, inverseMatrix);
17933
+ this.#transform.position.x += localDelta[0];
17934
+ this.#transform.position.y += localDelta[1];
17935
+ this.setDirty();
17936
+ }
17937
+ setDirty() {
17938
+ this.#cache = null;
17939
+ this.#kids.forEach((kid) => kid.setDirty());
17940
+ }
17941
+ static parse(json) {
17942
+ const obj = JSON.parse(json, reviver);
17943
+ return new SceneNode(obj);
17944
+ }
17945
+ toJSON() {
17946
+ return {
17947
+ id: this.id,
17948
+ label: this.label,
17949
+ transform: this.#transform,
17950
+ layer: this.#layer,
17951
+ isActive: this.#isActive,
17952
+ kids: this.#kids,
17953
+ render: this.#renderComponent
17954
+ };
17989
17955
  }
17990
17956
  }
17991
-
17992
- // src/text/shaping.ts
17993
- var TAB_SPACES = 4;
17994
- function shapeText(font, text, blockSize, fontSize, formatting, textArray, initialFloatOffset = 0, debug = false) {
17995
- let offset = initialFloatOffset;
17996
- const measurements = measureText(font, text, formatting.wordWrap);
17997
- const alignment = formatting.align || "left";
17998
- const em2px = fontSize / font.lineHeight;
17999
- const hackHasExplicitBlock = blockSize.width !== measurements.width;
18000
- let debugData = null;
18001
- if (debug) {
18002
- debugData = [];
17957
+ function reviver(key, value) {
17958
+ if (key === "kids") {
17959
+ return value.map((kid) => new SceneNode(kid));
18003
17960
  }
18004
- for (const word of measurements.words) {
18005
- for (const glyph of word.glyphs) {
18006
- let lineOffset = 0;
18007
- if (alignment === "center") {
18008
- lineOffset = measurements.width * -0.5 - (measurements.width - measurements.lineWidths[glyph.line]) * -0.5;
18009
- } else if (alignment === "right") {
18010
- const blockSizeEm = blockSize.width / em2px;
18011
- const delta = measurements.width - measurements.lineWidths[glyph.line];
18012
- lineOffset = (hackHasExplicitBlock ? blockSizeEm / 2 : measurements.width / 2) - measurements.width + delta;
18013
- } else if (alignment === "left") {
18014
- const blockSizeEm = blockSize.width / em2px;
18015
- lineOffset = hackHasExplicitBlock ? -blockSizeEm / 2 : -measurements.width / 2;
18016
- }
18017
- if (debug && debugData) {
18018
- debugData.push({
18019
- line: glyph.line,
18020
- word: word.glyphs.map((g3) => g3.char.char).join(""),
18021
- glyph: glyph.char.char,
18022
- startX: word.startX,
18023
- glyphX: glyph.offset[0],
18024
- advance: glyph.char.xadvance,
18025
- lineOffset,
18026
- startY: word.startY,
18027
- glyphY: glyph.offset[1]
18028
- });
18029
- }
18030
- textArray[offset] = word.startX + glyph.offset[0] + lineOffset;
18031
- textArray[offset + 1] = word.startY + glyph.offset[1];
18032
- textArray[offset + 2] = glyph.char.charIndex;
18033
- offset += 4;
17961
+ if (Array.isArray(value) && value.every((v3) => typeof v3 === "number")) {
17962
+ if (value.length === 2) {
17963
+ return value;
17964
+ }
17965
+ if (value.length === 3) {
17966
+ return value;
17967
+ }
17968
+ if (value.length === 4) {
17969
+ return value;
18034
17970
  }
18035
17971
  }
18036
- if (debug && debugData) {
18037
- console.table(debugData);
18038
- }
18039
- }
18040
- function measureText(font, text, wordWrap) {
18041
- let maxWidth = 0;
18042
- const lineWidths = [];
18043
- let textOffsetX = 0;
18044
- let textOffsetY = 0;
18045
- let line = 0;
18046
- let printedCharCount = 0;
18047
- let nextCharCode = text.charCodeAt(0);
18048
- let word = { glyphs: [], width: 0, startX: 0, startY: 0 };
18049
- const words = [];
18050
- for (let i3 = 0;i3 < text.length; i3++) {
18051
- const isLastLetter = i3 === text.length - 1;
18052
- const charCode = nextCharCode;
18053
- nextCharCode = i3 < text.length - 1 ? text.charCodeAt(i3 + 1) : -1;
18054
- switch (charCode) {
18055
- case 9 /* HorizontalTab */:
18056
- insertSpaces(TAB_SPACES);
18057
- break;
18058
- case 10 /* Newline */:
18059
- flushLine();
18060
- flushWord();
18061
- break;
18062
- case 13 /* CarriageReturn */:
18063
- break;
18064
- case 32 /* Space */:
18065
- insertSpaces(1);
18066
- break;
18067
- default: {
18068
- const advance = font.getXAdvance(charCode, nextCharCode);
18069
- if (wordWrap && wordWrap.breakOn === "character" && textOffsetX + advance > wordWrap.emWidth) {
18070
- if (word.startX === 0) {
18071
- flushWord();
18072
- } else {
18073
- lineWidths.push(textOffsetX - word.width);
18074
- line++;
18075
- maxWidth = Math.max(maxWidth, textOffsetX);
18076
- textOffsetX = word.width;
18077
- textOffsetY -= font.lineHeight;
18078
- word.startX = 0;
18079
- word.startY = textOffsetY;
18080
- word.glyphs.forEach((g3) => {
18081
- g3.line = line;
18082
- });
18083
- }
18084
- }
18085
- word.glyphs.push({
18086
- char: font.getChar(charCode),
18087
- offset: [word.width, 0],
18088
- line
18089
- });
18090
- if (isLastLetter) {
18091
- flushWord();
18092
- }
18093
- word.width += advance;
18094
- textOffsetX += advance;
18095
- }
18096
- }
18097
- }
18098
- lineWidths.push(textOffsetX);
18099
- maxWidth = Math.max(maxWidth, textOffsetX);
18100
- const lineCount = lineWidths.length;
18101
- return {
18102
- width: maxWidth,
18103
- height: lineCount * font.lineHeight,
18104
- lineWidths,
18105
- lineCount,
18106
- printedCharCount,
18107
- words
18108
- };
18109
- function flushWord() {
18110
- printedCharCount += word.glyphs.length;
18111
- words.push(word);
18112
- word = {
18113
- glyphs: [],
18114
- width: 0,
18115
- startX: textOffsetX,
18116
- startY: textOffsetY
18117
- };
18118
- }
18119
- function flushLine() {
18120
- lineWidths.push(textOffsetX);
18121
- line++;
18122
- maxWidth = Math.max(maxWidth, textOffsetX);
18123
- textOffsetX = 0;
18124
- textOffsetY -= font.lineHeight;
18125
- }
18126
- function insertSpaces(spaces) {
18127
- if (spaces < 1)
18128
- spaces = 1;
18129
- textOffsetX += font.getXAdvance(32 /* Space */) * spaces;
18130
- if (wordWrap?.breakOn === "word" && textOffsetX >= wordWrap.emWidth) {
18131
- flushLine();
18132
- }
18133
- flushWord();
18134
- }
18135
- }
18136
- function findLargestFontSize(font, text, size, formatting) {
18137
- if (!formatting.fontSize) {
18138
- throw new Error("fontSize is required for shrinkToFit");
18139
- }
18140
- if (!formatting.shrinkToFit) {
18141
- throw new Error("shrinkToFit is required for findLargestFontSize");
18142
- }
18143
- const minSize = formatting.shrinkToFit.minFontSize;
18144
- const maxSize = formatting.shrinkToFit.maxFontSize ?? formatting.fontSize;
18145
- const maxLines = formatting.shrinkToFit.maxLines ?? Number.POSITIVE_INFINITY;
18146
- const threshold = 0.5;
18147
- let low = minSize;
18148
- let high = maxSize;
18149
- while (high - low > threshold) {
18150
- const testSize = (low + high) / 2;
18151
- const testMeasure = measureText(font, text, formatting.wordWrap);
18152
- const padding = formatting.shrinkToFit.padding ?? 0;
18153
- const scaledWidth = testMeasure.width * (testSize / font.lineHeight);
18154
- const scaledHeight = testMeasure.height * (testSize / font.lineHeight);
18155
- const fitsWidth = scaledWidth <= size.width - size.width * padding;
18156
- const fitsHeight = scaledHeight <= size.height - size.height * padding;
18157
- const fitsLines = testMeasure.lineCount <= maxLines;
18158
- if (fitsWidth && fitsHeight && fitsLines) {
18159
- low = testSize;
18160
- } else {
18161
- high = testSize;
18162
- }
18163
- }
18164
- return low;
17972
+ return value;
18165
17973
  }
18166
17974
 
18167
17975
  // src/scene/TextNode.ts
@@ -18222,81 +18030,6 @@ class TextNode extends SceneNode {
18222
18030
  }
18223
18031
  }
18224
18032
 
18225
- // src/backends/webgl2/WebGLFontPipeline.ts
18226
- class WebGLFontPipeline2 {
18227
- font;
18228
- fontTexture;
18229
- charDataTexture;
18230
- textBufferTexture;
18231
- maxCharCount;
18232
- lineHeight;
18233
- #gl;
18234
- constructor(gl, font, fontTexture, charDataTexture, textBufferTexture, maxCharCount) {
18235
- this.#gl = gl;
18236
- this.font = font;
18237
- this.fontTexture = fontTexture;
18238
- this.charDataTexture = charDataTexture;
18239
- this.textBufferTexture = textBufferTexture;
18240
- this.maxCharCount = maxCharCount;
18241
- this.lineHeight = font.lineHeight;
18242
- }
18243
- static create(gl, font, maxCharCount) {
18244
- const fontTexture = gl.createTexture();
18245
- assert(fontTexture, "Failed to create font texture");
18246
- gl.bindTexture(gl.TEXTURE_2D, fontTexture);
18247
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
18248
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
18249
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
18250
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
18251
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, font.imageBitmap);
18252
- const charDataTexture = gl.createTexture();
18253
- assert(charDataTexture, "Failed to create char data texture");
18254
- gl.bindTexture(gl.TEXTURE_2D, charDataTexture);
18255
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
18256
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
18257
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
18258
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
18259
- const charCount = font.charCount;
18260
- const charTextureWidth = charCount * 2;
18261
- const charTextureData = new Float32Array(charTextureWidth * 4);
18262
- for (let i3 = 0;i3 < charCount; i3++) {
18263
- const srcOffset = i3 * 8;
18264
- const dstOffset0 = i3 * 2 * 4;
18265
- const dstOffset1 = (i3 * 2 + 1) * 4;
18266
- charTextureData[dstOffset0] = font.charBuffer[srcOffset];
18267
- charTextureData[dstOffset0 + 1] = font.charBuffer[srcOffset + 1];
18268
- charTextureData[dstOffset0 + 2] = font.charBuffer[srcOffset + 2];
18269
- charTextureData[dstOffset0 + 3] = font.charBuffer[srcOffset + 3];
18270
- charTextureData[dstOffset1] = font.charBuffer[srcOffset + 4];
18271
- charTextureData[dstOffset1 + 1] = font.charBuffer[srcOffset + 5];
18272
- charTextureData[dstOffset1 + 2] = font.charBuffer[srcOffset + 6];
18273
- charTextureData[dstOffset1 + 3] = font.charBuffer[srcOffset + 7];
18274
- }
18275
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, charTextureWidth, 1, 0, gl.RGBA, gl.FLOAT, charTextureData);
18276
- const textBufferTexture = gl.createTexture();
18277
- assert(textBufferTexture, "Failed to create text buffer texture");
18278
- gl.bindTexture(gl.TEXTURE_2D, textBufferTexture);
18279
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
18280
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
18281
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
18282
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
18283
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, maxCharCount, 1, 0, gl.RGBA, gl.FLOAT, null);
18284
- gl.bindTexture(gl.TEXTURE_2D, null);
18285
- return new WebGLFontPipeline2(gl, font, fontTexture, charDataTexture, textBufferTexture, maxCharCount);
18286
- }
18287
- updateTextBuffer(data, glyphCount) {
18288
- const gl = this.#gl;
18289
- gl.bindTexture(gl.TEXTURE_2D, this.textBufferTexture);
18290
- gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, glyphCount, 1, gl.RGBA, gl.FLOAT, data);
18291
- }
18292
- destroy() {
18293
- const gl = this.#gl;
18294
- gl.deleteTexture(this.fontTexture);
18295
- gl.deleteTexture(this.charDataTexture);
18296
- gl.deleteTexture(this.textBufferTexture);
18297
- }
18298
- }
18299
-
18300
18033
  // src/backends/webgl2/glsl/text.glsl.ts
18301
18034
  var vertexShader2 = `#version 300 es
18302
18035
  precision highp float;
@@ -18421,7 +18154,7 @@ void main() {
18421
18154
  `;
18422
18155
 
18423
18156
  // src/backends/webgl2/WebGLTextShader.ts
18424
- class WebGLTextShader2 {
18157
+ class WebGLTextShader {
18425
18158
  label = "text";
18426
18159
  code = fragmentShader2;
18427
18160
  font;
@@ -18743,7 +18476,7 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
18743
18476
  `;
18744
18477
 
18745
18478
  // src/backends/webgpu/FontPipeline.ts
18746
- class FontPipeline2 {
18479
+ class FontPipeline {
18747
18480
  pipeline;
18748
18481
  font;
18749
18482
  fontBindGroup;
@@ -18806,7 +18539,7 @@ class FontPipeline2 {
18806
18539
  }
18807
18540
  ]
18808
18541
  });
18809
- return new FontPipeline2(pipeline, font, fontBindGroup, maxCharCount);
18542
+ return new FontPipeline(pipeline, font, fontBindGroup, maxCharCount);
18810
18543
  }
18811
18544
  }
18812
18545
  function pipelinePromise(device, colorFormat, label) {
@@ -18852,70 +18585,550 @@ function pipelinePromise(device, colorFormat, label) {
18852
18585
  }
18853
18586
  });
18854
18587
  }
18855
- if (typeof GPUShaderStage === "undefined") {
18856
- globalThis.GPUShaderStage = {
18857
- VERTEX: 1,
18858
- FRAGMENT: 2,
18859
- COMPUTE: 4
18860
- };
18588
+ if (typeof GPUShaderStage === "undefined") {
18589
+ globalThis.GPUShaderStage = {
18590
+ VERTEX: 1,
18591
+ FRAGMENT: 2,
18592
+ COMPUTE: 4
18593
+ };
18594
+ }
18595
+ var fontBindGroupLayout = {
18596
+ label: "MSDF font group layout",
18597
+ entries: [
18598
+ {
18599
+ binding: 0,
18600
+ visibility: GPUShaderStage.FRAGMENT,
18601
+ texture: {}
18602
+ },
18603
+ {
18604
+ binding: 1,
18605
+ visibility: GPUShaderStage.FRAGMENT,
18606
+ sampler: {}
18607
+ },
18608
+ {
18609
+ binding: 2,
18610
+ visibility: GPUShaderStage.VERTEX,
18611
+ buffer: { type: "read-only-storage" }
18612
+ },
18613
+ {
18614
+ binding: 3,
18615
+ visibility: GPUShaderStage.VERTEX,
18616
+ buffer: {}
18617
+ }
18618
+ ]
18619
+ };
18620
+ var engineUniformBindGroupLayout = {
18621
+ label: "Uniform bind group",
18622
+ entries: [
18623
+ {
18624
+ binding: 0,
18625
+ visibility: GPUShaderStage.VERTEX,
18626
+ buffer: {}
18627
+ }
18628
+ ]
18629
+ };
18630
+ var sampler = {
18631
+ label: "MSDF text sampler",
18632
+ minFilter: "linear",
18633
+ magFilter: "linear",
18634
+ mipmapFilter: "linear",
18635
+ maxAnisotropy: 16
18636
+ };
18637
+ var textUniformBindGroupLayout = {
18638
+ label: "MSDF text block uniform",
18639
+ entries: [
18640
+ {
18641
+ binding: 0,
18642
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
18643
+ buffer: { type: "read-only-storage" }
18644
+ },
18645
+ {
18646
+ binding: 1,
18647
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
18648
+ buffer: { type: "read-only-storage" }
18649
+ }
18650
+ ]
18651
+ };
18652
+ // src/backends/webgpu/WebGPUTextShader.ts
18653
+ var deets = new _t2(text_wgsl_default);
18654
+ var struct = deets.structs.find((s3) => s3.name === "TextBlockDescriptor");
18655
+ if (!struct) {
18656
+ throw new Error("FormattedText struct not found");
18657
+ }
18658
+ var textDescriptorInstanceSize = struct.size;
18659
+
18660
+ class WebGPUTextShader {
18661
+ label = "text";
18662
+ code = text_wgsl_default;
18663
+ #backend;
18664
+ #pipeline;
18665
+ #bindGroups = [];
18666
+ #font;
18667
+ #maxCharCount;
18668
+ #engineUniformsBuffer;
18669
+ #descriptorBuffer;
18670
+ #textBlockBuffer;
18671
+ #cpuDescriptorBuffer;
18672
+ #cpuTextBlockBuffer;
18673
+ #instanceIndex = 0;
18674
+ #textBlockOffset = 0;
18675
+ constructor(backend, pipeline, font, _colorFormat, instanceCount) {
18676
+ this.#backend = backend;
18677
+ const device = backend.device;
18678
+ this.#font = font;
18679
+ this.#pipeline = pipeline.pipeline;
18680
+ this.#maxCharCount = pipeline.maxCharCount;
18681
+ this.#descriptorBuffer = device.createBuffer({
18682
+ label: "msdf text descriptor buffer",
18683
+ size: textDescriptorInstanceSize * instanceCount,
18684
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
18685
+ });
18686
+ this.#cpuDescriptorBuffer = new Float32Array(instanceCount * textDescriptorInstanceSize / Float32Array.BYTES_PER_ELEMENT);
18687
+ this.#cpuTextBlockBuffer = new Float32Array(instanceCount * this.maxCharCount * 4);
18688
+ this.#engineUniformsBuffer = device.createBuffer({
18689
+ label: "msdf view projection matrix",
18690
+ size: Float32Array.BYTES_PER_ELEMENT * 12,
18691
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
18692
+ });
18693
+ this.#textBlockBuffer = device.createBuffer({
18694
+ label: "msdf text buffer",
18695
+ size: instanceCount * this.maxCharCount * 4 * Float32Array.BYTES_PER_ELEMENT,
18696
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
18697
+ });
18698
+ this.#bindGroups.push(pipeline.fontBindGroup);
18699
+ this.#bindGroups.push(device.createBindGroup({
18700
+ label: "msdf text bind group",
18701
+ layout: pipeline.pipeline.getBindGroupLayout(1),
18702
+ entries: [
18703
+ {
18704
+ binding: 0,
18705
+ resource: { buffer: this.#descriptorBuffer }
18706
+ },
18707
+ {
18708
+ binding: 1,
18709
+ resource: { buffer: this.#textBlockBuffer }
18710
+ }
18711
+ ]
18712
+ }));
18713
+ const engineUniformsBindGroup = device.createBindGroup({
18714
+ label: "msdf text uniforms bind group",
18715
+ layout: pipeline.pipeline.getBindGroupLayout(2),
18716
+ entries: [
18717
+ {
18718
+ binding: 0,
18719
+ resource: { buffer: this.#engineUniformsBuffer }
18720
+ }
18721
+ ]
18722
+ });
18723
+ this.#bindGroups.push(engineUniformsBindGroup);
18724
+ }
18725
+ startFrame(uniform) {
18726
+ const device = this.#backend.device;
18727
+ device.queue.writeBuffer(this.#engineUniformsBuffer, 0, uniform.viewProjectionMatrix);
18728
+ this.#instanceIndex = 0;
18729
+ this.#textBlockOffset = 0;
18730
+ }
18731
+ processBatch(nodes) {
18732
+ if (nodes.length === 0)
18733
+ return 0;
18734
+ const renderPass = this.#backend.renderPass;
18735
+ renderPass.setPipeline(this.#pipeline);
18736
+ for (let i3 = 0;i3 < this.#bindGroups.length; i3++) {
18737
+ renderPass.setBindGroup(i3, this.#bindGroups[i3]);
18738
+ }
18739
+ for (const node of nodes) {
18740
+ if (!(node instanceof TextNode)) {
18741
+ console.error(node);
18742
+ throw new Error(`Tried to use WebGPUTextShader on something that isn't a TextNode: ${node}`);
18743
+ }
18744
+ const text = node.text;
18745
+ const formatting = node.formatting;
18746
+ const measurements = measureText(this.#font, text, formatting.wordWrap);
18747
+ const textBlockSize = 4 * text.length;
18748
+ const textDescriptorOffset = this.#instanceIndex * textDescriptorInstanceSize / Float32Array.BYTES_PER_ELEMENT;
18749
+ this.#cpuDescriptorBuffer.set(node.matrix, textDescriptorOffset);
18750
+ this.#cpuDescriptorBuffer.set([node.tint.r, node.tint.g, node.tint.b, node.tint.a], textDescriptorOffset + 12);
18751
+ const size = node.size ?? measurements;
18752
+ const fontSize = formatting.shrinkToFit ? findLargestFontSize(this.#font, text, size, formatting) : formatting.fontSize;
18753
+ const actualFontSize = fontSize || DEFAULT_FONT_SIZE;
18754
+ this.#cpuDescriptorBuffer[textDescriptorOffset + 16] = actualFontSize;
18755
+ this.#cpuDescriptorBuffer[textDescriptorOffset + 17] = formatting.align === "center" ? 0 : measurements.width;
18756
+ this.#cpuDescriptorBuffer[textDescriptorOffset + 18] = measurements.height;
18757
+ this.#cpuDescriptorBuffer[textDescriptorOffset + 19] = this.#textBlockOffset / 4;
18758
+ shapeText(this.#font, text, size, actualFontSize, formatting, this.#cpuTextBlockBuffer, this.#textBlockOffset);
18759
+ this.#backend.device.queue.writeBuffer(this.#descriptorBuffer, textDescriptorOffset * Float32Array.BYTES_PER_ELEMENT, this.#cpuDescriptorBuffer, textDescriptorOffset, textDescriptorInstanceSize / Float32Array.BYTES_PER_ELEMENT);
18760
+ this.#backend.device.queue.writeBuffer(this.#textBlockBuffer, this.#textBlockOffset * Float32Array.BYTES_PER_ELEMENT, this.#cpuTextBlockBuffer, this.#textBlockOffset, textBlockSize);
18761
+ this.#textBlockOffset += textBlockSize;
18762
+ renderPass.draw(4, measurements.printedCharCount, 4 * this.#instanceIndex, 0);
18763
+ this.#instanceIndex++;
18764
+ }
18765
+ return nodes.length;
18766
+ }
18767
+ endFrame() {}
18768
+ get font() {
18769
+ return this.#font;
18770
+ }
18771
+ get maxCharCount() {
18772
+ return this.#maxCharCount;
18773
+ }
18774
+ }
18775
+
18776
+ // src/scene/Batcher.ts
18777
+ class Batcher {
18778
+ nodes = [];
18779
+ layers = [];
18780
+ pipelines = [];
18781
+ enqueue(node) {
18782
+ if (node.renderComponent && node.isActive) {
18783
+ this.nodes.push(node);
18784
+ const z3 = node.layer;
18785
+ const layer = this.#findOrCreateLayer(z3);
18786
+ const pipeline = this.#findOrCreatePipeline(layer, node.renderComponent.shader);
18787
+ pipeline.nodes.push(node);
18788
+ }
18789
+ for (const kid of node.kids) {
18790
+ this.enqueue(kid);
18791
+ }
18792
+ }
18793
+ flush() {
18794
+ this.nodes = [];
18795
+ this.layers = [];
18796
+ this.pipelines = [];
18797
+ }
18798
+ #findOrCreateLayer(z3) {
18799
+ let layer = this.layers.find((l3) => l3.z === z3);
18800
+ if (!layer) {
18801
+ layer = { z: z3, pipelines: [] };
18802
+ this.layers.push(layer);
18803
+ this.layers.sort((a3, b3) => a3.z - b3.z);
18804
+ }
18805
+ return layer;
18806
+ }
18807
+ #findOrCreatePipeline(layer, shader) {
18808
+ let pipeline = layer.pipelines.find((p3) => p3.shader === shader);
18809
+ if (!pipeline) {
18810
+ pipeline = { shader, nodes: [] };
18811
+ layer.pipelines.push(pipeline);
18812
+ this.pipelines.push(pipeline);
18813
+ }
18814
+ return pipeline;
18815
+ }
18816
+ }
18817
+
18818
+ // src/scene/Camera.ts
18819
+ class Camera {
18820
+ #position = { x: 0, y: 0 };
18821
+ #zoom = 1;
18822
+ #rotation = 0;
18823
+ #isDirty = true;
18824
+ #matrix = mat3.create();
18825
+ get zoom() {
18826
+ return this.#zoom;
18827
+ }
18828
+ set zoom(value) {
18829
+ this.#zoom = value;
18830
+ this.setDirty();
18831
+ }
18832
+ get rotation() {
18833
+ return rad2deg(this.#rotation);
18834
+ }
18835
+ set rotation(value) {
18836
+ this.#rotation = deg2rad(value);
18837
+ this.setDirty();
18838
+ }
18839
+ get rotationRadians() {
18840
+ return this.#rotation;
18841
+ }
18842
+ set rotationRadians(value) {
18843
+ this.#rotation = value;
18844
+ this.setDirty();
18845
+ }
18846
+ get x() {
18847
+ return this.#position.x;
18848
+ }
18849
+ get y() {
18850
+ return this.#position.y;
18851
+ }
18852
+ set x(value) {
18853
+ this.#position.x = value;
18854
+ this.setDirty();
18855
+ }
18856
+ set y(value) {
18857
+ this.#position.y = value;
18858
+ this.setDirty();
18859
+ }
18860
+ get matrix() {
18861
+ if (this.#isDirty) {
18862
+ this.#isDirty = false;
18863
+ this.#matrix = createViewMatrix(this, this.#matrix);
18864
+ }
18865
+ return this.#matrix;
18866
+ }
18867
+ setDirty() {
18868
+ this.#isDirty = true;
18869
+ }
18870
+ }
18871
+
18872
+ // src/scene/QuadNode.ts
18873
+ var PRIMITIVE_TEXTURE = "__primitive__";
18874
+ var RESERVED_PRIMITIVE_INDEX_START = 1000;
18875
+ var CIRCLE_INDEX = 1001;
18876
+ var DEFAULT_REGION = {
18877
+ x: 0,
18878
+ y: 0,
18879
+ width: 0,
18880
+ height: 0
18881
+ };
18882
+
18883
+ class QuadNode extends SceneNode {
18884
+ assetManager;
18885
+ #color;
18886
+ #atlasCoords;
18887
+ #region;
18888
+ #matrixPool;
18889
+ #flip;
18890
+ #cropOffset;
18891
+ #cropRatio;
18892
+ #atlasSize;
18893
+ #textureId;
18894
+ #writeInstance;
18895
+ constructor(options, matrixPool) {
18896
+ assert(options.shader, "QuadNode requires a shader to be explicitly provided");
18897
+ assert(options.size, "QuadNode requires a size to be explicitly provided");
18898
+ assert(options.atlasCoords, "QuadNode requires atlas coords to be explicitly provided");
18899
+ options.render ??= {
18900
+ shader: options.shader,
18901
+ writeInstance: writeQuadInstance
18902
+ };
18903
+ super(options);
18904
+ assert(options.assetManager, "QuadNode requires an asset manager");
18905
+ this.assetManager = options.assetManager;
18906
+ if (options.atlasCoords && options.atlasCoords.atlasIndex >= RESERVED_PRIMITIVE_INDEX_START) {
18907
+ this.#textureId = PRIMITIVE_TEXTURE;
18908
+ this.#region = DEFAULT_REGION;
18909
+ this.#atlasSize = DEFAULT_REGION;
18910
+ } else {
18911
+ assert(options.textureId, "QuadNode requires texture id to be explicitly provided");
18912
+ this.#textureId = options.textureId;
18913
+ assert(options.region, "QuadNode requires a region to be explicitly provided");
18914
+ this.#region = options.region;
18915
+ assert(options.atlasSize, "QuadNode requires atlas size to be explicitly provided");
18916
+ this.#atlasSize = options.atlasSize;
18917
+ }
18918
+ this.#atlasCoords = options.atlasCoords;
18919
+ this.#color = options.color ?? { r: 1, g: 1, b: 1, a: 1 };
18920
+ this.#matrixPool = matrixPool;
18921
+ this.#flip = { x: options.flipX ? -1 : 1, y: options.flipY ? -1 : 1 };
18922
+ this.#cropOffset = options.cropOffset ?? { x: 0, y: 0 };
18923
+ this.#cropRatio = !this.#atlasCoords.uvScaleCropped ? { width: 1, height: 1 } : {
18924
+ width: this.#atlasCoords.uvScaleCropped.width / this.#atlasCoords.uvScale.width,
18925
+ height: this.#atlasCoords.uvScaleCropped.height / this.#atlasCoords.uvScale.height
18926
+ };
18927
+ this.#writeInstance = options.writeInstance;
18928
+ }
18929
+ get color() {
18930
+ return this.#color;
18931
+ }
18932
+ set color(value) {
18933
+ this.#color = value;
18934
+ }
18935
+ get size() {
18936
+ const size = super.size;
18937
+ if (!size) {
18938
+ throw new Error("QuadNode requires a size");
18939
+ }
18940
+ return size;
18941
+ }
18942
+ set size(val) {
18943
+ super.size = val;
18944
+ }
18945
+ get matrixWithSize() {
18946
+ const matrix = mat3.clone(this.matrix, this.#matrixPool.get());
18947
+ mat3.scale(matrix, [this.size.width * this.#flip.x, this.size.height * this.#flip.y], matrix);
18948
+ return matrix;
18949
+ }
18950
+ get atlasCoords() {
18951
+ return this.#atlasCoords;
18952
+ }
18953
+ get region() {
18954
+ return this.#region;
18955
+ }
18956
+ get writeInstance() {
18957
+ return this.#writeInstance;
18958
+ }
18959
+ get flipX() {
18960
+ return this.#flip.x === -1;
18961
+ }
18962
+ set flipX(value) {
18963
+ this.#flip.x = value ? -1 : 1;
18964
+ this.setDirty();
18965
+ }
18966
+ get flipY() {
18967
+ return this.#flip.y === -1;
18968
+ }
18969
+ set flipY(value) {
18970
+ this.#flip.y = value ? -1 : 1;
18971
+ this.setDirty();
18972
+ }
18973
+ get cropOffset() {
18974
+ return this.#cropOffset;
18975
+ }
18976
+ set cropOffset(value) {
18977
+ this.#cropOffset = value;
18978
+ }
18979
+ get textureId() {
18980
+ return this.#textureId;
18981
+ }
18982
+ get isPrimitive() {
18983
+ return this.#textureId === PRIMITIVE_TEXTURE;
18984
+ }
18985
+ get isCircle() {
18986
+ return this.#atlasCoords.atlasIndex === CIRCLE_INDEX;
18987
+ }
18988
+ extra = {
18989
+ setAtlasCoords: (value) => {
18990
+ this.#atlasCoords = value;
18991
+ },
18992
+ cropRatio: () => {
18993
+ return this.#cropRatio;
18994
+ },
18995
+ atlasSize: () => {
18996
+ return this.#atlasSize;
18997
+ }
18998
+ };
18999
+ }
19000
+ function writeQuadInstance(node, array, offset) {
19001
+ if (!(node instanceof QuadNode)) {
19002
+ throw new Error("QuadNode.writeInstance can only be called on QuadNodes");
19003
+ }
19004
+ array.set(node.matrixWithSize, offset);
19005
+ array.set([node.color.r, node.color.g, node.color.b, node.color.a], offset + 12);
19006
+ const region = node.region;
19007
+ if (node.textureId === PRIMITIVE_TEXTURE) {
19008
+ array.set([
19009
+ node.atlasCoords.uvOffset.x,
19010
+ node.atlasCoords.uvOffset.y,
19011
+ node.atlasCoords.uvScale.width,
19012
+ node.atlasCoords.uvScale.height
19013
+ ], offset + 16);
19014
+ } else {
19015
+ const atlasSize = node.extra.atlasSize();
19016
+ array.set([
19017
+ node.atlasCoords.uvOffset.x + region.x / atlasSize.width,
19018
+ node.atlasCoords.uvOffset.y + region.y / atlasSize.height,
19019
+ region.width / atlasSize.width,
19020
+ region.height / atlasSize.height
19021
+ ], offset + 16);
19022
+ }
19023
+ array.set([
19024
+ node.cropOffset.x / 2 / (node.atlasCoords.originalSize.width || 1),
19025
+ node.cropOffset.y / 2 / (node.atlasCoords.originalSize.height || 1),
19026
+ node.extra.cropRatio().width,
19027
+ node.extra.cropRatio().height
19028
+ ], offset + 20);
19029
+ new DataView(array.buffer).setUint32(array.byteOffset + (offset + 24) * Float32Array.BYTES_PER_ELEMENT, node.atlasCoords.atlasIndex, true);
19030
+ node.writeInstance?.(array, offset + 28);
19031
+ return 1;
19032
+ }
19033
+
19034
+ // src/scene/JumboQuadNode.ts
19035
+ var MAT3_SIZE = 12;
19036
+ var VEC4F_SIZE = 4;
19037
+
19038
+ class JumboQuadNode extends QuadNode {
19039
+ #tiles;
19040
+ #matrixPool;
19041
+ constructor(options, matrixPool) {
19042
+ assert(options.shader, "JumboQuadNode requires a shader to be explicitly provided");
19043
+ assert(options.tiles && options.tiles.length > 0, "JumboQuadNode requires at least one tile to be provided");
19044
+ options.render ??= {
19045
+ shader: options.shader,
19046
+ writeInstance: writeJumboQuadInstance
19047
+ };
19048
+ super({
19049
+ ...options,
19050
+ atlasCoords: options.tiles[0].atlasCoords
19051
+ }, matrixPool);
19052
+ this.#matrixPool = matrixPool;
19053
+ this.#tiles = [];
19054
+ for (const tile of options.tiles) {
19055
+ assert(tile.atlasCoords, "JumboQuadNode requires atlas coords to be provided");
19056
+ assert(tile.size, "JumboQuadNode requires a size to be provided");
19057
+ this.#tiles.push({
19058
+ textureId: tile.textureId,
19059
+ offset: tile.offset,
19060
+ size: tile.size,
19061
+ atlasCoords: tile.atlasCoords
19062
+ });
19063
+ }
19064
+ }
19065
+ get atlasCoords() {
19066
+ throw new Error("JumboQuadNode does not have a single atlas coords");
19067
+ }
19068
+ get tiles() {
19069
+ return this.#tiles;
19070
+ }
19071
+ getTileMatrix(tile) {
19072
+ const matrix = mat3.clone(this.matrix, this.#matrixPool.get());
19073
+ const originalSize = {
19074
+ width: Math.max(...this.#tiles.map((t3) => t3.offset.x + t3.size.width)),
19075
+ height: Math.max(...this.#tiles.map((t3) => t3.offset.y + t3.size.height))
19076
+ };
19077
+ const proportionalSize = {
19078
+ width: this.size.width / originalSize.width,
19079
+ height: this.size.height / originalSize.height
19080
+ };
19081
+ const centerOffset = {
19082
+ x: tile.offset.x + tile.size.width / 2 - originalSize.width / 2,
19083
+ y: -(tile.offset.y + tile.size.height / 2 - originalSize.height / 2)
19084
+ };
19085
+ mat3.translate(matrix, [
19086
+ centerOffset.x * proportionalSize.width,
19087
+ centerOffset.y * proportionalSize.height
19088
+ ], matrix);
19089
+ mat3.scale(matrix, [
19090
+ tile.size.width * proportionalSize.width * (this.flipX ? -1 : 1),
19091
+ tile.size.height * proportionalSize.height * (this.flipY ? -1 : 1)
19092
+ ], matrix);
19093
+ return matrix;
19094
+ }
19095
+ }
19096
+ function writeJumboQuadInstance(node, array, offset) {
19097
+ if (!(node instanceof JumboQuadNode)) {
19098
+ throw new Error("JumboQuadNode.writeJumboQuadInstance can only be called on JumboQuadNodes");
19099
+ }
19100
+ let tileOffset = 0;
19101
+ for (const tile of node.tiles) {
19102
+ const coord = tile.atlasCoords;
19103
+ const matrix = node.getTileMatrix(tile);
19104
+ array.set(matrix, offset + tileOffset);
19105
+ tileOffset += MAT3_SIZE;
19106
+ array.set([node.color.r, node.color.g, node.color.b, node.color.a], offset + tileOffset);
19107
+ tileOffset += VEC4F_SIZE;
19108
+ array.set([
19109
+ coord.uvOffset.x,
19110
+ coord.uvOffset.y,
19111
+ coord.uvScale.width,
19112
+ coord.uvScale.height
19113
+ ], offset + tileOffset);
19114
+ tileOffset += VEC4F_SIZE;
19115
+ const cropRatio = !coord.uvScaleCropped ? { width: 1, height: 1 } : {
19116
+ width: coord.uvScaleCropped.width / coord.uvScale.width,
19117
+ height: coord.uvScaleCropped.height / coord.uvScale.height
19118
+ };
19119
+ array.set([
19120
+ tile.atlasCoords.cropOffset.x / 2 / (tile.atlasCoords.originalSize.width || 1),
19121
+ tile.atlasCoords.cropOffset.y / 2 / (tile.atlasCoords.originalSize.height || 1),
19122
+ cropRatio.width,
19123
+ cropRatio.height
19124
+ ], offset + tileOffset);
19125
+ tileOffset += VEC4F_SIZE;
19126
+ new DataView(array.buffer).setUint32(array.byteOffset + (offset + tileOffset) * Float32Array.BYTES_PER_ELEMENT, coord.atlasIndex, true);
19127
+ tileOffset += VEC4F_SIZE;
19128
+ }
19129
+ node.writeInstance?.(array, offset + tileOffset);
19130
+ return node.tiles.length;
18861
19131
  }
18862
- var fontBindGroupLayout = {
18863
- label: "MSDF font group layout",
18864
- entries: [
18865
- {
18866
- binding: 0,
18867
- visibility: GPUShaderStage.FRAGMENT,
18868
- texture: {}
18869
- },
18870
- {
18871
- binding: 1,
18872
- visibility: GPUShaderStage.FRAGMENT,
18873
- sampler: {}
18874
- },
18875
- {
18876
- binding: 2,
18877
- visibility: GPUShaderStage.VERTEX,
18878
- buffer: { type: "read-only-storage" }
18879
- },
18880
- {
18881
- binding: 3,
18882
- visibility: GPUShaderStage.VERTEX,
18883
- buffer: {}
18884
- }
18885
- ]
18886
- };
18887
- var engineUniformBindGroupLayout = {
18888
- label: "Uniform bind group",
18889
- entries: [
18890
- {
18891
- binding: 0,
18892
- visibility: GPUShaderStage.VERTEX,
18893
- buffer: {}
18894
- }
18895
- ]
18896
- };
18897
- var sampler = {
18898
- label: "MSDF text sampler",
18899
- minFilter: "linear",
18900
- magFilter: "linear",
18901
- mipmapFilter: "linear",
18902
- maxAnisotropy: 16
18903
- };
18904
- var textUniformBindGroupLayout = {
18905
- label: "MSDF text block uniform",
18906
- entries: [
18907
- {
18908
- binding: 0,
18909
- visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
18910
- buffer: { type: "read-only-storage" }
18911
- },
18912
- {
18913
- binding: 1,
18914
- visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
18915
- buffer: { type: "read-only-storage" }
18916
- }
18917
- ]
18918
- };
18919
19132
 
18920
19133
  // src/backends/webgpu/wgsl/pixel-scraping.wgsl.ts
18921
19134
  var pixel_scraping_wgsl_default = `
@@ -19290,130 +19503,6 @@ function createPipelines(device, label) {
19290
19503
  };
19291
19504
  }
19292
19505
 
19293
- // src/backends/webgpu/WebGPUTextShader.ts
19294
- var deets = new _t2(text_wgsl_default);
19295
- var struct = deets.structs.find((s3) => s3.name === "TextBlockDescriptor");
19296
- if (!struct) {
19297
- throw new Error("FormattedText struct not found");
19298
- }
19299
- var textDescriptorInstanceSize = struct.size;
19300
-
19301
- class WebGPUTextShader2 {
19302
- label = "text";
19303
- code = text_wgsl_default;
19304
- #backend;
19305
- #pipeline;
19306
- #bindGroups = [];
19307
- #font;
19308
- #maxCharCount;
19309
- #engineUniformsBuffer;
19310
- #descriptorBuffer;
19311
- #textBlockBuffer;
19312
- #cpuDescriptorBuffer;
19313
- #cpuTextBlockBuffer;
19314
- #instanceIndex = 0;
19315
- #textBlockOffset = 0;
19316
- constructor(backend, pipeline, font, _colorFormat, instanceCount) {
19317
- this.#backend = backend;
19318
- const device = backend.device;
19319
- this.#font = font;
19320
- this.#pipeline = pipeline.pipeline;
19321
- this.#maxCharCount = pipeline.maxCharCount;
19322
- this.#descriptorBuffer = device.createBuffer({
19323
- label: "msdf text descriptor buffer",
19324
- size: textDescriptorInstanceSize * instanceCount,
19325
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
19326
- });
19327
- this.#cpuDescriptorBuffer = new Float32Array(instanceCount * textDescriptorInstanceSize / Float32Array.BYTES_PER_ELEMENT);
19328
- this.#cpuTextBlockBuffer = new Float32Array(instanceCount * this.maxCharCount * 4);
19329
- this.#engineUniformsBuffer = device.createBuffer({
19330
- label: "msdf view projection matrix",
19331
- size: Float32Array.BYTES_PER_ELEMENT * 12,
19332
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
19333
- });
19334
- this.#textBlockBuffer = device.createBuffer({
19335
- label: "msdf text buffer",
19336
- size: instanceCount * this.maxCharCount * 4 * Float32Array.BYTES_PER_ELEMENT,
19337
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
19338
- });
19339
- this.#bindGroups.push(pipeline.fontBindGroup);
19340
- this.#bindGroups.push(device.createBindGroup({
19341
- label: "msdf text bind group",
19342
- layout: pipeline.pipeline.getBindGroupLayout(1),
19343
- entries: [
19344
- {
19345
- binding: 0,
19346
- resource: { buffer: this.#descriptorBuffer }
19347
- },
19348
- {
19349
- binding: 1,
19350
- resource: { buffer: this.#textBlockBuffer }
19351
- }
19352
- ]
19353
- }));
19354
- const engineUniformsBindGroup = device.createBindGroup({
19355
- label: "msdf text uniforms bind group",
19356
- layout: pipeline.pipeline.getBindGroupLayout(2),
19357
- entries: [
19358
- {
19359
- binding: 0,
19360
- resource: { buffer: this.#engineUniformsBuffer }
19361
- }
19362
- ]
19363
- });
19364
- this.#bindGroups.push(engineUniformsBindGroup);
19365
- }
19366
- startFrame(uniform) {
19367
- const device = this.#backend.device;
19368
- device.queue.writeBuffer(this.#engineUniformsBuffer, 0, uniform.viewProjectionMatrix);
19369
- this.#instanceIndex = 0;
19370
- this.#textBlockOffset = 0;
19371
- }
19372
- processBatch(nodes) {
19373
- if (nodes.length === 0)
19374
- return 0;
19375
- const renderPass = this.#backend.renderPass;
19376
- renderPass.setPipeline(this.#pipeline);
19377
- for (let i3 = 0;i3 < this.#bindGroups.length; i3++) {
19378
- renderPass.setBindGroup(i3, this.#bindGroups[i3]);
19379
- }
19380
- for (const node of nodes) {
19381
- if (!(node instanceof TextNode)) {
19382
- console.error(node);
19383
- throw new Error(`Tried to use WebGPUTextShader on something that isn't a TextNode: ${node}`);
19384
- }
19385
- const text = node.text;
19386
- const formatting = node.formatting;
19387
- const measurements = measureText(this.#font, text, formatting.wordWrap);
19388
- const textBlockSize = 4 * text.length;
19389
- const textDescriptorOffset = this.#instanceIndex * textDescriptorInstanceSize / Float32Array.BYTES_PER_ELEMENT;
19390
- this.#cpuDescriptorBuffer.set(node.matrix, textDescriptorOffset);
19391
- this.#cpuDescriptorBuffer.set([node.tint.r, node.tint.g, node.tint.b, node.tint.a], textDescriptorOffset + 12);
19392
- const size = node.size ?? measurements;
19393
- const fontSize = formatting.shrinkToFit ? findLargestFontSize(this.#font, text, size, formatting) : formatting.fontSize;
19394
- const actualFontSize = fontSize || DEFAULT_FONT_SIZE;
19395
- this.#cpuDescriptorBuffer[textDescriptorOffset + 16] = actualFontSize;
19396
- this.#cpuDescriptorBuffer[textDescriptorOffset + 17] = formatting.align === "center" ? 0 : measurements.width;
19397
- this.#cpuDescriptorBuffer[textDescriptorOffset + 18] = measurements.height;
19398
- this.#cpuDescriptorBuffer[textDescriptorOffset + 19] = this.#textBlockOffset / 4;
19399
- shapeText(this.#font, text, size, actualFontSize, formatting, this.#cpuTextBlockBuffer, this.#textBlockOffset);
19400
- this.#backend.device.queue.writeBuffer(this.#descriptorBuffer, textDescriptorOffset * Float32Array.BYTES_PER_ELEMENT, this.#cpuDescriptorBuffer, textDescriptorOffset, textDescriptorInstanceSize / Float32Array.BYTES_PER_ELEMENT);
19401
- this.#backend.device.queue.writeBuffer(this.#textBlockBuffer, this.#textBlockOffset * Float32Array.BYTES_PER_ELEMENT, this.#cpuTextBlockBuffer, this.#textBlockOffset, textBlockSize);
19402
- this.#textBlockOffset += textBlockSize;
19403
- renderPass.draw(4, measurements.printedCharCount, 4 * this.#instanceIndex, 0);
19404
- this.#instanceIndex++;
19405
- }
19406
- return nodes.length;
19407
- }
19408
- endFrame() {}
19409
- get font() {
19410
- return this.#font;
19411
- }
19412
- get maxCharCount() {
19413
- return this.#maxCharCount;
19414
- }
19415
- }
19416
-
19417
19506
  // src/textures/Bundles.ts
19418
19507
  class Bundles {
19419
19508
  #bundles = new Map;
@@ -20088,13 +20177,13 @@ class AssetManager {
20088
20177
  const webgpuBackend = this.#backend;
20089
20178
  const device = webgpuBackend.device;
20090
20179
  const presentationFormat = webgpuBackend.presentationFormat;
20091
- const fontPipeline = await FontPipeline2.create(device, font, presentationFormat, limits.maxTextLength);
20092
- const textShader = new WebGPUTextShader2(webgpuBackend, fontPipeline, font, presentationFormat, limits.instanceCount);
20180
+ const fontPipeline = await FontPipeline.create(device, font, presentationFormat, limits.maxTextLength);
20181
+ const textShader = new WebGPUTextShader(webgpuBackend, fontPipeline, font, presentationFormat, limits.instanceCount);
20093
20182
  this.#fonts.set(id, textShader);
20094
20183
  } else {
20095
20184
  const webglBackend = this.#backend;
20096
- const fontPipeline = WebGLFontPipeline2.create(webglBackend.gl, font, limits.maxTextLength);
20097
- const textShader = new WebGLTextShader2(webglBackend, fontPipeline);
20185
+ const fontPipeline = WebGLFontPipeline.create(webglBackend.gl, font, limits.maxTextLength);
20186
+ const textShader = new WebGLTextShader(webglBackend, fontPipeline);
20098
20187
  this.#fonts.set(id, textShader);
20099
20188
  }
20100
20189
  return id;
@@ -20237,6 +20326,13 @@ class AssetManager {
20237
20326
  }
20238
20327
  }
20239
20328
 
20329
+ // src/utils/mod.ts
20330
+ var exports_mod2 = {};
20331
+ __export(exports_mod2, {
20332
+ assert: () => assert,
20333
+ Pool: () => Pool
20334
+ });
20335
+
20240
20336
  // src/utils/pool.ts
20241
20337
  class Pool {
20242
20338
  #items = [];
@@ -20258,7 +20354,6 @@ class Pool {
20258
20354
  this.#index = 0;
20259
20355
  }
20260
20356
  }
20261
-
20262
20357
  // src/Toodle.ts
20263
20358
  class Toodle {
20264
20359
  assets;
@@ -20595,11 +20690,11 @@ class Toodle {
20595
20690
  }
20596
20691
  let backend;
20597
20692
  if (backendType === "webgpu") {
20598
- backend = await WebGPUBackend2.create(canvas, {
20693
+ backend = await WebGPUBackend.create(canvas, {
20599
20694
  limits: options?.limits
20600
20695
  });
20601
20696
  } else {
20602
- backend = await WebGLBackend2.create(canvas, {
20697
+ backend = await WebGLBackend.create(canvas, {
20603
20698
  limits: options?.limits
20604
20699
  });
20605
20700
  }
@@ -20617,20 +20712,204 @@ class Toodle {
20617
20712
  return this.#backend;
20618
20713
  }
20619
20714
  }
20715
+ // src/colors/mod.ts
20716
+ var exports_mod3 = {};
20717
+ __export(exports_mod3, {
20718
+ web: () => web
20719
+ });
20720
+ var web = Object.freeze({
20721
+ aliceBlue: { r: 0.941176, g: 0.972549, b: 1, a: 1 },
20722
+ antiqueWhite: { r: 0.980392, g: 0.921569, b: 0.843137, a: 1 },
20723
+ aqua: { r: 0, g: 1, b: 1, a: 1 },
20724
+ aquamarine: { r: 0.498039, g: 1, b: 0.831373, a: 1 },
20725
+ azure: { r: 0.941176, g: 1, b: 1, a: 1 },
20726
+ beige: { r: 0.960784, g: 0.960784, b: 0.862745, a: 1 },
20727
+ bisque: { r: 1, g: 0.894118, b: 0.768627, a: 1 },
20728
+ black: { r: 0, g: 0, b: 0, a: 1 },
20729
+ blanchedAlmond: { r: 1, g: 0.921569, b: 0.803922, a: 1 },
20730
+ blue: { r: 0, g: 0, b: 1, a: 1 },
20731
+ blueViolet: { r: 0.541176, g: 0.168627, b: 0.886275, a: 1 },
20732
+ brown: { r: 0.647059, g: 0.164706, b: 0.164706, a: 1 },
20733
+ burlywood: { r: 0.870588, g: 0.721569, b: 0.529412, a: 1 },
20734
+ cadetBlue: { r: 0.372549, g: 0.619608, b: 0.627451, a: 1 },
20735
+ chartreuse: { r: 0.498039, g: 1, b: 0, a: 1 },
20736
+ chocolate: { r: 0.823529, g: 0.411765, b: 0.117647, a: 1 },
20737
+ coral: { r: 1, g: 0.498039, b: 0.313726, a: 1 },
20738
+ cornflowerBlue: { r: 0.392157, g: 0.584314, b: 0.929412, a: 1 },
20739
+ cornsilk: { r: 1, g: 0.972549, b: 0.862745, a: 1 },
20740
+ crimson: { r: 0.862745, g: 0.0784314, b: 0.235294, a: 1 },
20741
+ cyan: { r: 0, g: 1, b: 1, a: 1 },
20742
+ darkBlue: { r: 0, g: 0, b: 0.545098, a: 1 },
20743
+ darkCyan: { r: 0, g: 0.545098, b: 0.545098, a: 1 },
20744
+ darkGoldenrod: { r: 0.721569, g: 0.52549, b: 0.0431373, a: 1 },
20745
+ darkGray: { r: 0.662745, g: 0.662745, b: 0.662745, a: 1 },
20746
+ darkGreen: { r: 0, g: 0.392157, b: 0, a: 1 },
20747
+ darkKhaki: { r: 0.741176, g: 0.717647, b: 0.419608, a: 1 },
20748
+ darkMagenta: { r: 0.545098, g: 0, b: 0.545098, a: 1 },
20749
+ darkOliveGreen: { r: 0.333333, g: 0.419608, b: 0.184314, a: 1 },
20750
+ darkOrange: { r: 1, g: 0.54902, b: 0, a: 1 },
20751
+ darkOrchid: { r: 0.6, g: 0.196078, b: 0.8, a: 1 },
20752
+ darkRed: { r: 0.545098, g: 0, b: 0, a: 1 },
20753
+ darkSalmon: { r: 0.913725, g: 0.588235, b: 0.478431, a: 1 },
20754
+ darkSeaGreen: { r: 0.560784, g: 0.737255, b: 0.560784, a: 1 },
20755
+ darkSlateBlue: { r: 0.282353, g: 0.239216, b: 0.545098, a: 1 },
20756
+ darkSlateGray: { r: 0.184314, g: 0.309804, b: 0.309804, a: 1 },
20757
+ darkTurquoise: { r: 0, g: 0.807843, b: 0.819608, a: 1 },
20758
+ darkViolet: { r: 0.580392, g: 0, b: 0.827451, a: 1 },
20759
+ deepPink: { r: 1, g: 0.0784314, b: 0.576471, a: 1 },
20760
+ deepSkyBlue: { r: 0, g: 0.74902, b: 1, a: 1 },
20761
+ dimGray: { r: 0.411765, g: 0.411765, b: 0.411765, a: 1 },
20762
+ dodgerBlue: { r: 0.117647, g: 0.564706, b: 1, a: 1 },
20763
+ firebrick: { r: 0.698039, g: 0.133333, b: 0.133333, a: 1 },
20764
+ floralWhite: { r: 1, g: 0.980392, b: 0.941176, a: 1 },
20765
+ forestGreen: { r: 0.133333, g: 0.545098, b: 0.133333, a: 1 },
20766
+ fuchsia: { r: 1, g: 0, b: 1, a: 1 },
20767
+ gainsboro: { r: 0.862745, g: 0.862745, b: 0.862745, a: 1 },
20768
+ ghostWhite: { r: 0.972549, g: 0.972549, b: 1, a: 1 },
20769
+ gold: { r: 1, g: 0.843137, b: 0, a: 1 },
20770
+ goldenrod: { r: 0.854902, g: 0.647059, b: 0.12549, a: 1 },
20771
+ gray: { r: 0.745098, g: 0.745098, b: 0.745098, a: 1 },
20772
+ green: { r: 0, g: 1, b: 0, a: 1 },
20773
+ greenYellow: { r: 0.678431, g: 1, b: 0.184314, a: 1 },
20774
+ honeydew: { r: 0.941176, g: 1, b: 0.941176, a: 1 },
20775
+ hotPink: { r: 1, g: 0.411765, b: 0.705882, a: 1 },
20776
+ indigo: { r: 0.294118, g: 0, b: 0.509804, a: 1 },
20777
+ ivory: { r: 1, g: 1, b: 0.941176, a: 1 },
20778
+ khaki: { r: 0.941176, g: 0.901961, b: 0.54902, a: 1 },
20779
+ lavender: { r: 0.901961, g: 0.901961, b: 0.980392, a: 1 },
20780
+ lavenderBlush: { r: 1, g: 0.941176, b: 0.960784, a: 1 },
20781
+ lawnGreen: { r: 0.486275, g: 0.988235, b: 0, a: 1 },
20782
+ lemonChiffon: { r: 1, g: 0.980392, b: 0.803922, a: 1 },
20783
+ lightBlue: { r: 0.678431, g: 0.847059, b: 0.901961, a: 1 },
20784
+ lightCoral: { r: 0.941176, g: 0.501961, b: 0.501961, a: 1 },
20785
+ lightCyan: { r: 0.878431, g: 1, b: 1, a: 1 },
20786
+ lightGoldenrod: { r: 0.980392, g: 0.980392, b: 0.823529, a: 1 },
20787
+ lightGray: { r: 0.827451, g: 0.827451, b: 0.827451, a: 1 },
20788
+ lightGreen: { r: 0.564706, g: 0.933333, b: 0.564706, a: 1 },
20789
+ lightPink: { r: 1, g: 0.713726, b: 0.756863, a: 1 },
20790
+ lightSalmon: { r: 1, g: 0.627451, b: 0.478431, a: 1 },
20791
+ lightSeaGreen: { r: 0.12549, g: 0.698039, b: 0.666667, a: 1 },
20792
+ lightSkyBlue: { r: 0.529412, g: 0.807843, b: 0.980392, a: 1 },
20793
+ lightSlateGray: { r: 0.466667, g: 0.533333, b: 0.6, a: 1 },
20794
+ lightSteelBlue: { r: 0.690196, g: 0.768627, b: 0.870588, a: 1 },
20795
+ lightYellow: { r: 1, g: 1, b: 0.878431, a: 1 },
20796
+ lime: { r: 0, g: 1, b: 0, a: 1 },
20797
+ limeGreen: { r: 0.196078, g: 0.803922, b: 0.196078, a: 1 },
20798
+ linen: { r: 0.980392, g: 0.941176, b: 0.901961, a: 1 },
20799
+ magenta: { r: 1, g: 0, b: 1, a: 1 },
20800
+ maroon: { r: 0.690196, g: 0.188235, b: 0.376471, a: 1 },
20801
+ mediumAquamarine: { r: 0.4, g: 0.803922, b: 0.666667, a: 1 },
20802
+ mediumBlue: { r: 0, g: 0, b: 0.803922, a: 1 },
20803
+ mediumOrchid: { r: 0.729412, g: 0.333333, b: 0.827451, a: 1 },
20804
+ mediumPurple: { r: 0.576471, g: 0.439216, b: 0.858824, a: 1 },
20805
+ mediumSeaGreen: { r: 0.235294, g: 0.701961, b: 0.443137, a: 1 },
20806
+ mediumSlateBlue: { r: 0.482353, g: 0.407843, b: 0.933333, a: 1 },
20807
+ mediumSpringGreen: { r: 0, g: 0.980392, b: 0.603922, a: 1 },
20808
+ mediumTurquoise: { r: 0.282353, g: 0.819608, b: 0.8, a: 1 },
20809
+ mediumVioletRed: { r: 0.780392, g: 0.0823529, b: 0.521569, a: 1 },
20810
+ midnightBlue: { r: 0.0980392, g: 0.0980392, b: 0.439216, a: 1 },
20811
+ mintCream: { r: 0.960784, g: 1, b: 0.980392, a: 1 },
20812
+ mistyRose: { r: 1, g: 0.894118, b: 0.882353, a: 1 },
20813
+ moccasin: { r: 1, g: 0.894118, b: 0.709804, a: 1 },
20814
+ navyBlue: { r: 0, g: 0, b: 0.501961, a: 1 },
20815
+ oldLace: { r: 0.992157, g: 0.960784, b: 0.901961, a: 1 },
20816
+ olive: { r: 0.501961, g: 0.501961, b: 0, a: 1 },
20817
+ oliveDrab: { r: 0.419608, g: 0.556863, b: 0.137255, a: 1 },
20818
+ orange: { r: 1, g: 0.647059, b: 0, a: 1 },
20819
+ orangeRed: { r: 1, g: 0.270588, b: 0, a: 1 },
20820
+ orchid: { r: 0.854902, g: 0.439216, b: 0.839216, a: 1 },
20821
+ paleGoldenrod: { r: 0.933333, g: 0.909804, b: 0.666667, a: 1 },
20822
+ paleGreen: { r: 0.596078, g: 0.984314, b: 0.596078, a: 1 },
20823
+ paleTurquoise: { r: 0.686275, g: 0.933333, b: 0.933333, a: 1 },
20824
+ paleVioletRed: { r: 0.858824, g: 0.439216, b: 0.576471, a: 1 },
20825
+ papayaWhip: { r: 1, g: 0.937255, b: 0.835294, a: 1 },
20826
+ peachPuff: { r: 1, g: 0.854902, b: 0.72549, a: 1 },
20827
+ peru: { r: 0.803922, g: 0.521569, b: 0.247059, a: 1 },
20828
+ pink: { r: 1, g: 0.752941, b: 0.796078, a: 1 },
20829
+ plum: { r: 0.866667, g: 0.627451, b: 0.866667, a: 1 },
20830
+ powderBlue: { r: 0.690196, g: 0.878431, b: 0.901961, a: 1 },
20831
+ purple: { r: 0.627451, g: 0.12549, b: 0.941176, a: 1 },
20832
+ rebeccaPurple: { r: 0.4, g: 0.2, b: 0.6, a: 1 },
20833
+ red: { r: 1, g: 0, b: 0, a: 1 },
20834
+ rosyBrown: { r: 0.737255, g: 0.560784, b: 0.560784, a: 1 },
20835
+ royalBlue: { r: 0.254902, g: 0.411765, b: 0.882353, a: 1 },
20836
+ saddleBrown: { r: 0.545098, g: 0.270588, b: 0.0745098, a: 1 },
20837
+ salmon: { r: 0.980392, g: 0.501961, b: 0.447059, a: 1 },
20838
+ sandyBrown: { r: 0.956863, g: 0.643137, b: 0.376471, a: 1 },
20839
+ seaGreen: { r: 0.180392, g: 0.545098, b: 0.341176, a: 1 },
20840
+ seashell: { r: 1, g: 0.960784, b: 0.933333, a: 1 },
20841
+ sienna: { r: 0.627451, g: 0.321569, b: 0.176471, a: 1 },
20842
+ silver: { r: 0.752941, g: 0.752941, b: 0.752941, a: 1 },
20843
+ skyBlue: { r: 0.529412, g: 0.807843, b: 0.921569, a: 1 },
20844
+ slateBlue: { r: 0.415686, g: 0.352941, b: 0.803922, a: 1 },
20845
+ slateGray: { r: 0.439216, g: 0.501961, b: 0.564706, a: 1 },
20846
+ snow: { r: 1, g: 0.980392, b: 0.980392, a: 1 },
20847
+ springGreen: { r: 0, g: 1, b: 0.498039, a: 1 },
20848
+ steelBlue: { r: 0.27451, g: 0.509804, b: 0.705882, a: 1 },
20849
+ tan: { r: 0.823529, g: 0.705882, b: 0.54902, a: 1 },
20850
+ teal: { r: 0, g: 0.501961, b: 0.501961, a: 1 },
20851
+ thistle: { r: 0.847059, g: 0.74902, b: 0.847059, a: 1 },
20852
+ tomato: { r: 1, g: 0.388235, b: 0.278431, a: 1 },
20853
+ transparent: { r: 1, g: 1, b: 1, a: 0 },
20854
+ turquoise: { r: 0.25098, g: 0.878431, b: 0.815686, a: 1 },
20855
+ violet: { r: 0.933333, g: 0.509804, b: 0.933333, a: 1 },
20856
+ webGray: { r: 0.501961, g: 0.501961, b: 0.501961, a: 1 },
20857
+ webGreen: { r: 0, g: 0.501961, b: 0, a: 1 },
20858
+ webMaroon: { r: 0.501961, g: 0, b: 0, a: 1 },
20859
+ webPurple: { r: 0.501961, g: 0, b: 0.501961, a: 1 },
20860
+ wheat: { r: 0.960784, g: 0.870588, b: 0.701961, a: 1 },
20861
+ white: { r: 1, g: 1, b: 1, a: 1 },
20862
+ whiteSmoke: { r: 0.960784, g: 0.960784, b: 0.960784, a: 1 },
20863
+ yellow: { r: 1, g: 1, b: 0, a: 1 },
20864
+ yellowGreen: { r: 0.603922, g: 0.803922, b: 0.196078, a: 1 }
20865
+ });
20866
+ // src/math/mod.ts
20867
+ var exports_mod4 = {};
20868
+ __export(exports_mod4, {
20869
+ transformPoint: () => transformPoint,
20870
+ rad2deg: () => rad2deg,
20871
+ deg2rad: () => deg2rad,
20872
+ createViewMatrix: () => createViewMatrix,
20873
+ createProjectionMatrix: () => createProjectionMatrix,
20874
+ createModelMatrix: () => createModelMatrix,
20875
+ convertWorldToScreen: () => convertWorldToScreen,
20876
+ convertScreenToWorld: () => convertScreenToWorld
20877
+ });
20878
+ // src/scene/mod.ts
20879
+ var exports_mod5 = {};
20880
+ __export(exports_mod5, {
20881
+ TextNode: () => TextNode,
20882
+ SceneNode: () => SceneNode,
20883
+ QuadNode: () => QuadNode,
20884
+ DEFAULT_FONT_SIZE: () => DEFAULT_FONT_SIZE,
20885
+ Camera: () => Camera
20886
+ });
20887
+ // src/screen/mod.ts
20888
+ var exports_mod6 = {};
20889
+ // src/text/mod.ts
20890
+ var exports_mod7 = {};
20891
+ __export(exports_mod7, {
20892
+ TextShader: () => WebGPUTextShader
20893
+ });
20894
+ // src/textures/mod.ts
20895
+ var exports_mod8 = {};
20896
+ __export(exports_mod8, {
20897
+ Bundles: () => Bundles
20898
+ });
20620
20899
  export {
20621
- Utils,
20900
+ exports_mod2 as Utils,
20622
20901
  Toodle,
20623
- Textures,
20624
- Text,
20625
- Screen,
20626
- Scene,
20627
- GfxMath,
20902
+ exports_mod8 as Textures,
20903
+ exports_mod7 as Text,
20904
+ exports_mod6 as Screen,
20905
+ exports_mod5 as Scene,
20906
+ exports_mod4 as GfxMath,
20628
20907
  DEFAULT_LIMITS,
20629
- Colors,
20630
- Bundles2 as Bundles,
20631
- Backends,
20632
- AssetManager2 as AssetManager
20908
+ exports_mod3 as Colors,
20909
+ Bundles,
20910
+ exports_mod as Backends,
20911
+ AssetManager
20633
20912
  };
20634
20913
 
20635
- //# debugId=F5D1111C12CFF04864756E2164756E21
20914
+ //# debugId=18E70D7AC0F5250F64756E2164756E21
20636
20915
  //# sourceMappingURL=mod.js.map