@bloopjs/toodle 0.1.6 → 0.1.7
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 +1471 -1747
- package/dist/mod.js.map +16 -18
- package/dist/textures/Bundles.d.ts.map +1 -1
- package/dist/textures/util.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/textures/Bundles.ts +9 -4
- package/src/textures/util.ts +8 -4
package/dist/mod.js
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
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
|
-
|
|
12
1
|
// src/limits.ts
|
|
13
2
|
var DEFAULT_LIMITS = {
|
|
14
3
|
instanceCount: 1024 * 2,
|
|
@@ -3445,18 +3434,6 @@ var {
|
|
|
3445
3434
|
vec4: vec4n
|
|
3446
3435
|
} = wgpuMatrixAPI(ZeroArray, Array, Array, Array, Array, Array);
|
|
3447
3436
|
|
|
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
|
-
|
|
3460
3437
|
// src/backends/detection.ts
|
|
3461
3438
|
async function detectBackend() {
|
|
3462
3439
|
if (typeof navigator !== "undefined" && "gpu" in navigator) {
|
|
@@ -3469,21 +3446,13 @@ async function detectBackend() {
|
|
|
3469
3446
|
}
|
|
3470
3447
|
return "webgl2";
|
|
3471
3448
|
}
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
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;
|
|
3449
|
+
// src/utils/assert.ts
|
|
3450
|
+
function assert(condition, message) {
|
|
3451
|
+
if (!condition) {
|
|
3452
|
+
throw new Error(message);
|
|
3485
3453
|
}
|
|
3486
3454
|
}
|
|
3455
|
+
|
|
3487
3456
|
// src/backends/webgl2/glsl/quad.glsl.ts
|
|
3488
3457
|
var vertexShader = `#version 300 es
|
|
3489
3458
|
precision highp float;
|
|
@@ -3588,13 +3557,6 @@ void main() {
|
|
|
3588
3557
|
}
|
|
3589
3558
|
}
|
|
3590
3559
|
`;
|
|
3591
|
-
var defaultFragmentShader = fragmentShader;
|
|
3592
|
-
// src/utils/assert.ts
|
|
3593
|
-
function assert(condition, message) {
|
|
3594
|
-
if (!condition) {
|
|
3595
|
-
throw new Error(message);
|
|
3596
|
-
}
|
|
3597
|
-
}
|
|
3598
3560
|
|
|
3599
3561
|
// src/backends/webgl2/WebGLQuadShader.ts
|
|
3600
3562
|
var INSTANCE_FLOATS = 28;
|
|
@@ -3754,7 +3716,7 @@ class WebGLQuadShader {
|
|
|
3754
3716
|
}
|
|
3755
3717
|
|
|
3756
3718
|
// src/backends/webgl2/WebGLBackend.ts
|
|
3757
|
-
class
|
|
3719
|
+
class WebGLBackend2 {
|
|
3758
3720
|
type = "webgl2";
|
|
3759
3721
|
limits;
|
|
3760
3722
|
atlasSize;
|
|
@@ -3784,7 +3746,7 @@ class WebGLBackend {
|
|
|
3784
3746
|
...DEFAULT_LIMITS,
|
|
3785
3747
|
...options.limits
|
|
3786
3748
|
};
|
|
3787
|
-
const backend = new
|
|
3749
|
+
const backend = new WebGLBackend2(gl, canvas, limits);
|
|
3788
3750
|
backend.createTextureAtlas("default", {
|
|
3789
3751
|
format: options.format ?? "rgba8unorm",
|
|
3790
3752
|
layers: limits.textureArrayLayers,
|
|
@@ -3873,58 +3835,6 @@ class WebGLBackend {
|
|
|
3873
3835
|
return this.getTextureAtlas("default")?.format ?? "rgba8unorm";
|
|
3874
3836
|
}
|
|
3875
3837
|
}
|
|
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
|
-
};
|
|
3928
3838
|
// node_modules/webgpu-utils/dist/1.x/webgpu-utils.module.js
|
|
3929
3839
|
var roundUpToMultipleOf = (v, multiple) => ((v + multiple - 1) / multiple | 0) * multiple;
|
|
3930
3840
|
function keysOf(obj) {
|
|
@@ -17031,7 +16941,7 @@ function convertBlendMode(mode) {
|
|
|
17031
16941
|
}
|
|
17032
16942
|
|
|
17033
16943
|
// src/backends/webgpu/WebGPUBackend.ts
|
|
17034
|
-
class
|
|
16944
|
+
class WebGPUBackend2 {
|
|
17035
16945
|
type = "webgpu";
|
|
17036
16946
|
limits;
|
|
17037
16947
|
atlasSize;
|
|
@@ -17076,7 +16986,7 @@ class WebGPUBackend {
|
|
|
17076
16986
|
...DEFAULT_LIMITS,
|
|
17077
16987
|
...options.limits
|
|
17078
16988
|
};
|
|
17079
|
-
const backend = new
|
|
16989
|
+
const backend = new WebGPUBackend2(device, context, presentationFormat, limits, canvas);
|
|
17080
16990
|
backend.createTextureAtlas("default", {
|
|
17081
16991
|
format: options.format ?? "rgba8unorm",
|
|
17082
16992
|
layers: limits.textureArrayLayers,
|
|
@@ -17221,414 +17131,150 @@ class WebGPUBackend {
|
|
|
17221
17131
|
return this.#renderPass;
|
|
17222
17132
|
}
|
|
17223
17133
|
}
|
|
17224
|
-
// src/
|
|
17225
|
-
|
|
17226
|
-
|
|
17227
|
-
|
|
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;
|
|
17241
|
-
}
|
|
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];
|
|
17273
|
-
}
|
|
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);
|
|
17285
|
-
}
|
|
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);
|
|
17296
|
-
}
|
|
17134
|
+
// src/math/matrix.ts
|
|
17135
|
+
function createProjectionMatrix(resolution, dst) {
|
|
17136
|
+
const { width, height } = resolution;
|
|
17137
|
+
return mat3.scaling([2 / width, 2 / height], dst);
|
|
17297
17138
|
}
|
|
17298
|
-
|
|
17299
|
-
|
|
17300
|
-
|
|
17301
|
-
|
|
17302
|
-
|
|
17303
|
-
|
|
17304
|
-
|
|
17305
|
-
|
|
17306
|
-
|
|
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
|
+
};
|
|
17307
17174
|
}
|
|
17308
17175
|
|
|
17309
|
-
// src/
|
|
17310
|
-
class
|
|
17311
|
-
|
|
17312
|
-
|
|
17313
|
-
|
|
17314
|
-
|
|
17315
|
-
|
|
17316
|
-
|
|
17317
|
-
|
|
17318
|
-
|
|
17319
|
-
|
|
17320
|
-
|
|
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);
|
|
17340
|
-
}
|
|
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);
|
|
17341
17188
|
}
|
|
17342
|
-
|
|
17343
|
-
|
|
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;
|
|
17189
|
+
for (const kid of node.kids) {
|
|
17190
|
+
this.enqueue(kid);
|
|
17360
17191
|
}
|
|
17361
17192
|
}
|
|
17362
|
-
|
|
17363
|
-
|
|
17364
|
-
|
|
17365
|
-
|
|
17366
|
-
|
|
17367
|
-
|
|
17193
|
+
flush() {
|
|
17194
|
+
this.nodes = [];
|
|
17195
|
+
this.layers = [];
|
|
17196
|
+
this.pipelines = [];
|
|
17197
|
+
}
|
|
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);
|
|
17368
17204
|
}
|
|
17369
|
-
return
|
|
17205
|
+
return layer;
|
|
17370
17206
|
}
|
|
17371
|
-
|
|
17372
|
-
|
|
17373
|
-
if (
|
|
17374
|
-
|
|
17375
|
-
|
|
17376
|
-
|
|
17377
|
-
}
|
|
17378
|
-
}
|
|
17379
|
-
return char.xadvance;
|
|
17380
|
-
}
|
|
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`);
|
|
17388
|
-
}
|
|
17389
|
-
if (json.pages.length > 1) {
|
|
17390
|
-
throw new Error(`Can't create an msdf font with more than one page`);
|
|
17391
|
-
}
|
|
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);
|
|
17396
|
-
}
|
|
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;
|
|
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);
|
|
17405
17213
|
}
|
|
17214
|
+
return pipeline;
|
|
17406
17215
|
}
|
|
17407
17216
|
}
|
|
17408
17217
|
|
|
17409
|
-
// src/
|
|
17410
|
-
|
|
17411
|
-
|
|
17412
|
-
|
|
17413
|
-
|
|
17414
|
-
|
|
17415
|
-
|
|
17416
|
-
|
|
17417
|
-
|
|
17418
|
-
|
|
17419
|
-
|
|
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;
|
|
17420
17235
|
}
|
|
17421
|
-
|
|
17422
|
-
|
|
17423
|
-
|
|
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
|
-
}
|
|
17236
|
+
set zoom(value) {
|
|
17237
|
+
this.#zoom = value;
|
|
17238
|
+
this.setDirty();
|
|
17452
17239
|
}
|
|
17453
|
-
|
|
17454
|
-
|
|
17240
|
+
get rotation() {
|
|
17241
|
+
return rad2deg(this.#rotation);
|
|
17455
17242
|
}
|
|
17456
|
-
|
|
17457
|
-
|
|
17458
|
-
|
|
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
|
-
}
|
|
17243
|
+
set rotation(value) {
|
|
17244
|
+
this.#rotation = deg2rad(value);
|
|
17245
|
+
this.setDirty();
|
|
17514
17246
|
}
|
|
17515
|
-
|
|
17516
|
-
|
|
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
|
-
};
|
|
17247
|
+
get rotationRadians() {
|
|
17248
|
+
return this.#rotation;
|
|
17535
17249
|
}
|
|
17536
|
-
|
|
17537
|
-
|
|
17538
|
-
|
|
17539
|
-
maxWidth = Math.max(maxWidth, textOffsetX);
|
|
17540
|
-
textOffsetX = 0;
|
|
17541
|
-
textOffsetY -= font.lineHeight;
|
|
17250
|
+
set rotationRadians(value) {
|
|
17251
|
+
this.#rotation = value;
|
|
17252
|
+
this.setDirty();
|
|
17542
17253
|
}
|
|
17543
|
-
|
|
17544
|
-
|
|
17545
|
-
spaces = 1;
|
|
17546
|
-
textOffsetX += font.getXAdvance(32 /* Space */) * spaces;
|
|
17547
|
-
if (wordWrap?.breakOn === "word" && textOffsetX >= wordWrap.emWidth) {
|
|
17548
|
-
flushLine();
|
|
17549
|
-
}
|
|
17550
|
-
flushWord();
|
|
17254
|
+
get x() {
|
|
17255
|
+
return this.#position.x;
|
|
17551
17256
|
}
|
|
17552
|
-
|
|
17553
|
-
|
|
17554
|
-
if (!formatting.fontSize) {
|
|
17555
|
-
throw new Error("fontSize is required for shrinkToFit");
|
|
17257
|
+
get y() {
|
|
17258
|
+
return this.#position.y;
|
|
17556
17259
|
}
|
|
17557
|
-
|
|
17558
|
-
|
|
17260
|
+
set x(value) {
|
|
17261
|
+
this.#position.x = value;
|
|
17262
|
+
this.setDirty();
|
|
17559
17263
|
}
|
|
17560
|
-
|
|
17561
|
-
|
|
17562
|
-
|
|
17563
|
-
|
|
17564
|
-
|
|
17565
|
-
|
|
17566
|
-
|
|
17567
|
-
|
|
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;
|
|
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);
|
|
17579
17272
|
}
|
|
17273
|
+
return this.#matrix;
|
|
17274
|
+
}
|
|
17275
|
+
setDirty() {
|
|
17276
|
+
this.#isDirty = true;
|
|
17580
17277
|
}
|
|
17581
|
-
return low;
|
|
17582
|
-
}
|
|
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);
|
|
17590
|
-
}
|
|
17591
|
-
|
|
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
17278
|
}
|
|
17633
17279
|
|
|
17634
17280
|
// src/scene/SceneNode.ts
|
|
@@ -17972,1163 +17618,1304 @@ function reviver(key, value) {
|
|
|
17972
17618
|
return value;
|
|
17973
17619
|
}
|
|
17974
17620
|
|
|
17975
|
-
// src/scene/
|
|
17976
|
-
var
|
|
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
|
+
};
|
|
17977
17631
|
|
|
17978
|
-
class
|
|
17979
|
-
|
|
17980
|
-
#
|
|
17981
|
-
#
|
|
17982
|
-
|
|
17983
|
-
|
|
17984
|
-
|
|
17985
|
-
|
|
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
|
|
17651
|
+
};
|
|
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;
|
|
17986
17666
|
}
|
|
17987
|
-
|
|
17988
|
-
|
|
17989
|
-
|
|
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
|
|
17675
|
+
};
|
|
17676
|
+
this.#writeInstance = options.writeInstance;
|
|
17677
|
+
}
|
|
17678
|
+
get color() {
|
|
17679
|
+
return this.#color;
|
|
17680
|
+
}
|
|
17681
|
+
set color(value) {
|
|
17682
|
+
this.#color = value;
|
|
17683
|
+
}
|
|
17684
|
+
get size() {
|
|
17685
|
+
const size = super.size;
|
|
17686
|
+
if (!size) {
|
|
17687
|
+
throw new Error("QuadNode requires a size");
|
|
17990
17688
|
}
|
|
17991
|
-
|
|
17992
|
-
...opts,
|
|
17993
|
-
render: {
|
|
17994
|
-
shader,
|
|
17995
|
-
writeInstance: (_node, _array, _offset) => {
|
|
17996
|
-
throw new Error("not implemented - needs access to text uniform buffer, dimensions and a model matrix");
|
|
17997
|
-
}
|
|
17998
|
-
}
|
|
17999
|
-
});
|
|
18000
|
-
this.#font = shader.font;
|
|
18001
|
-
this.#text = text;
|
|
18002
|
-
this.#formatting = opts;
|
|
17689
|
+
return size;
|
|
18003
17690
|
}
|
|
18004
|
-
|
|
18005
|
-
|
|
17691
|
+
set size(val) {
|
|
17692
|
+
super.size = val;
|
|
18006
17693
|
}
|
|
18007
|
-
get
|
|
18008
|
-
|
|
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;
|
|
18009
17698
|
}
|
|
18010
|
-
get
|
|
18011
|
-
return this.#
|
|
17699
|
+
get atlasCoords() {
|
|
17700
|
+
return this.#atlasCoords;
|
|
18012
17701
|
}
|
|
18013
|
-
|
|
18014
|
-
|
|
18015
|
-
throw new Error("text cannot be empty");
|
|
18016
|
-
}
|
|
18017
|
-
this.#text = text;
|
|
18018
|
-
this.setDirty();
|
|
17702
|
+
get region() {
|
|
17703
|
+
return this.#region;
|
|
18019
17704
|
}
|
|
18020
|
-
get
|
|
18021
|
-
return this.#
|
|
17705
|
+
get writeInstance() {
|
|
17706
|
+
return this.#writeInstance;
|
|
18022
17707
|
}
|
|
18023
|
-
|
|
18024
|
-
this.#
|
|
17708
|
+
get flipX() {
|
|
17709
|
+
return this.#flip.x === -1;
|
|
17710
|
+
}
|
|
17711
|
+
set flipX(value) {
|
|
17712
|
+
this.#flip.x = value ? -1 : 1;
|
|
18025
17713
|
this.setDirty();
|
|
18026
17714
|
}
|
|
18027
|
-
|
|
18028
|
-
this.#
|
|
17715
|
+
get flipY() {
|
|
17716
|
+
return this.#flip.y === -1;
|
|
17717
|
+
}
|
|
17718
|
+
set flipY(value) {
|
|
17719
|
+
this.#flip.y = value ? -1 : 1;
|
|
18029
17720
|
this.setDirty();
|
|
18030
17721
|
}
|
|
17722
|
+
get cropOffset() {
|
|
17723
|
+
return this.#cropOffset;
|
|
17724
|
+
}
|
|
17725
|
+
set cropOffset(value) {
|
|
17726
|
+
this.#cropOffset = value;
|
|
17727
|
+
}
|
|
17728
|
+
get textureId() {
|
|
17729
|
+
return this.#textureId;
|
|
17730
|
+
}
|
|
17731
|
+
get isPrimitive() {
|
|
17732
|
+
return this.#textureId === PRIMITIVE_TEXTURE;
|
|
17733
|
+
}
|
|
17734
|
+
get isCircle() {
|
|
17735
|
+
return this.#atlasCoords.atlasIndex === CIRCLE_INDEX;
|
|
17736
|
+
}
|
|
17737
|
+
extra = {
|
|
17738
|
+
setAtlasCoords: (value) => {
|
|
17739
|
+
this.#atlasCoords = value;
|
|
17740
|
+
},
|
|
17741
|
+
cropRatio: () => {
|
|
17742
|
+
return this.#cropRatio;
|
|
17743
|
+
},
|
|
17744
|
+
atlasSize: () => {
|
|
17745
|
+
return this.#atlasSize;
|
|
17746
|
+
}
|
|
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");
|
|
17752
|
+
}
|
|
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);
|
|
17771
|
+
}
|
|
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;
|
|
18031
17781
|
}
|
|
18032
17782
|
|
|
18033
|
-
// src/
|
|
18034
|
-
var
|
|
18035
|
-
|
|
18036
|
-
|
|
18037
|
-
// Engine uniforms
|
|
18038
|
-
uniform mat3 u_viewProjection;
|
|
18039
|
-
|
|
18040
|
-
// Per-text-block uniforms
|
|
18041
|
-
uniform mat3 u_textTransform;
|
|
18042
|
-
uniform vec4 u_textColor;
|
|
18043
|
-
uniform float u_fontSize;
|
|
18044
|
-
uniform float u_blockWidth;
|
|
18045
|
-
uniform float u_blockHeight;
|
|
18046
|
-
uniform float u_lineHeight;
|
|
18047
|
-
|
|
18048
|
-
// Character data texture (RGBA32F, 2 texels per character)
|
|
18049
|
-
// Texel 0: texOffset.xy, texExtent.xy
|
|
18050
|
-
// Texel 1: size.xy, offset.xy
|
|
18051
|
-
uniform sampler2D u_charData;
|
|
18052
|
-
|
|
18053
|
-
// Text buffer texture (RGBA32F, 1 texel per glyph)
|
|
18054
|
-
// Each texel: xy = glyph position, z = char index
|
|
18055
|
-
uniform sampler2D u_textBuffer;
|
|
18056
|
-
|
|
18057
|
-
// Outputs to fragment shader
|
|
18058
|
-
out vec2 v_texcoord;
|
|
18059
|
-
|
|
18060
|
-
// Quad vertex positions for a character (matches WGSL)
|
|
18061
|
-
const vec2 pos[4] = vec2[4](
|
|
18062
|
-
vec2(0.0, -1.0),
|
|
18063
|
-
vec2(1.0, -1.0),
|
|
18064
|
-
vec2(0.0, 0.0),
|
|
18065
|
-
vec2(1.0, 0.0)
|
|
18066
|
-
);
|
|
17783
|
+
// src/scene/JumboQuadNode.ts
|
|
17784
|
+
var MAT3_SIZE = 12;
|
|
17785
|
+
var VEC4F_SIZE = 4;
|
|
18067
17786
|
|
|
18068
|
-
|
|
18069
|
-
|
|
18070
|
-
|
|
18071
|
-
|
|
18072
|
-
|
|
18073
|
-
|
|
18074
|
-
|
|
18075
|
-
|
|
18076
|
-
|
|
18077
|
-
|
|
18078
|
-
|
|
18079
|
-
|
|
18080
|
-
|
|
18081
|
-
|
|
18082
|
-
|
|
18083
|
-
|
|
18084
|
-
|
|
18085
|
-
|
|
18086
|
-
|
|
18087
|
-
|
|
18088
|
-
|
|
18089
|
-
|
|
18090
|
-
|
|
18091
|
-
|
|
18092
|
-
|
|
18093
|
-
|
|
18094
|
-
|
|
18095
|
-
|
|
18096
|
-
|
|
18097
|
-
|
|
18098
|
-
|
|
18099
|
-
|
|
18100
|
-
|
|
18101
|
-
|
|
18102
|
-
|
|
18103
|
-
|
|
18104
|
-
|
|
18105
|
-
|
|
18106
|
-
|
|
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
|
+
});
|
|
17812
|
+
}
|
|
17813
|
+
}
|
|
17814
|
+
get atlasCoords() {
|
|
17815
|
+
throw new Error("JumboQuadNode does not have a single atlas coords");
|
|
17816
|
+
}
|
|
17817
|
+
get tiles() {
|
|
17818
|
+
return this.#tiles;
|
|
17819
|
+
}
|
|
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;
|
|
17843
|
+
}
|
|
18107
17844
|
}
|
|
18108
|
-
|
|
18109
|
-
|
|
18110
|
-
|
|
18111
|
-
|
|
18112
|
-
|
|
18113
|
-
|
|
18114
|
-
|
|
18115
|
-
|
|
18116
|
-
|
|
18117
|
-
|
|
18118
|
-
|
|
18119
|
-
|
|
18120
|
-
|
|
18121
|
-
|
|
18122
|
-
|
|
18123
|
-
|
|
18124
|
-
|
|
18125
|
-
|
|
18126
|
-
|
|
18127
|
-
|
|
18128
|
-
|
|
17845
|
+
function writeJumboQuadInstance(node, array, offset) {
|
|
17846
|
+
if (!(node instanceof JumboQuadNode)) {
|
|
17847
|
+
throw new Error("JumboQuadNode.writeJumboQuadInstance can only be called on JumboQuadNodes");
|
|
17848
|
+
}
|
|
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;
|
|
17877
|
+
}
|
|
17878
|
+
node.writeInstance?.(array, offset + tileOffset);
|
|
17879
|
+
return node.tiles.length;
|
|
18129
17880
|
}
|
|
18130
17881
|
|
|
18131
|
-
|
|
18132
|
-
|
|
18133
|
-
|
|
18134
|
-
|
|
18135
|
-
|
|
18136
|
-
// Anti-aliasing technique by Paul Houx
|
|
18137
|
-
// https://github.com/Chlumsky/msdfgen/issues/22#issuecomment-234958005
|
|
18138
|
-
float dx = texSize.x * length(vec2(dFdx(v_texcoord.x), dFdy(v_texcoord.x)));
|
|
18139
|
-
float dy = texSize.y * length(vec2(dFdx(v_texcoord.y), dFdy(v_texcoord.y)));
|
|
18140
|
-
|
|
18141
|
-
float toPixels = pxRange * inversesqrt(dx * dx + dy * dy);
|
|
18142
|
-
float sigDist = sampleMsdf(v_texcoord) - 0.5;
|
|
18143
|
-
float pxDist = sigDist * toPixels;
|
|
18144
|
-
|
|
18145
|
-
float edgeWidth = 0.5;
|
|
18146
|
-
float alpha = smoothstep(-edgeWidth, edgeWidth, pxDist);
|
|
18147
|
-
|
|
18148
|
-
if (alpha < 0.001) {
|
|
18149
|
-
discard;
|
|
17882
|
+
// src/utils/error.ts
|
|
17883
|
+
var warnings = new Map;
|
|
17884
|
+
function warnOnce(key, msg) {
|
|
17885
|
+
if (warnings.has(key)) {
|
|
17886
|
+
return;
|
|
18150
17887
|
}
|
|
17888
|
+
warnings.set(key, true);
|
|
17889
|
+
console.warn(msg ?? key);
|
|
17890
|
+
}
|
|
18151
17891
|
|
|
18152
|
-
|
|
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);
|
|
17923
|
+
}
|
|
17924
|
+
}
|
|
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
|
+
}
|
|
17944
|
+
}
|
|
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;
|
|
17951
|
+
}
|
|
17952
|
+
return char;
|
|
17953
|
+
}
|
|
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;
|
|
17963
|
+
}
|
|
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`);
|
|
17974
|
+
}
|
|
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);
|
|
17979
|
+
}
|
|
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;
|
|
17988
|
+
}
|
|
17989
|
+
}
|
|
18153
17990
|
}
|
|
18154
|
-
`;
|
|
18155
17991
|
|
|
18156
|
-
// src/
|
|
18157
|
-
|
|
18158
|
-
|
|
18159
|
-
|
|
18160
|
-
font;
|
|
18161
|
-
|
|
18162
|
-
|
|
18163
|
-
|
|
18164
|
-
|
|
18165
|
-
|
|
18166
|
-
|
|
18167
|
-
|
|
18168
|
-
|
|
18169
|
-
|
|
18170
|
-
|
|
18171
|
-
|
|
18172
|
-
|
|
18173
|
-
|
|
18174
|
-
|
|
18175
|
-
|
|
18176
|
-
|
|
18177
|
-
|
|
18178
|
-
|
|
18179
|
-
|
|
18180
|
-
|
|
18181
|
-
|
|
18182
|
-
|
|
18183
|
-
|
|
18184
|
-
|
|
18185
|
-
|
|
18186
|
-
|
|
18187
|
-
|
|
18188
|
-
|
|
18189
|
-
|
|
18190
|
-
|
|
18191
|
-
|
|
18192
|
-
|
|
18193
|
-
|
|
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 = [];
|
|
18003
|
+
}
|
|
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;
|
|
18194
18034
|
}
|
|
18195
|
-
this.#program = program;
|
|
18196
|
-
this.#uViewProjection = gl.getUniformLocation(program, "u_viewProjection");
|
|
18197
|
-
this.#uTextTransform = gl.getUniformLocation(program, "u_textTransform");
|
|
18198
|
-
this.#uTextColor = gl.getUniformLocation(program, "u_textColor");
|
|
18199
|
-
this.#uFontSize = gl.getUniformLocation(program, "u_fontSize");
|
|
18200
|
-
this.#uBlockWidth = gl.getUniformLocation(program, "u_blockWidth");
|
|
18201
|
-
this.#uBlockHeight = gl.getUniformLocation(program, "u_blockHeight");
|
|
18202
|
-
this.#uLineHeight = gl.getUniformLocation(program, "u_lineHeight");
|
|
18203
|
-
this.#uCharData = gl.getUniformLocation(program, "u_charData");
|
|
18204
|
-
this.#uTextBuffer = gl.getUniformLocation(program, "u_textBuffer");
|
|
18205
|
-
this.#uFontTexture = gl.getUniformLocation(program, "u_fontTexture");
|
|
18206
|
-
const vao = gl.createVertexArray();
|
|
18207
|
-
assert(vao, "Failed to create WebGL VAO");
|
|
18208
|
-
this.#vao = vao;
|
|
18209
|
-
this.#cpuTextBuffer = new Float32Array(this.maxCharCount * 4);
|
|
18210
|
-
gl.deleteShader(vs);
|
|
18211
|
-
gl.deleteShader(fs);
|
|
18212
18035
|
}
|
|
18213
|
-
|
|
18214
|
-
|
|
18036
|
+
if (debug && debugData) {
|
|
18037
|
+
console.table(debugData);
|
|
18215
18038
|
}
|
|
18216
|
-
|
|
18217
|
-
|
|
18218
|
-
|
|
18219
|
-
|
|
18220
|
-
|
|
18221
|
-
|
|
18222
|
-
|
|
18223
|
-
|
|
18224
|
-
|
|
18225
|
-
|
|
18226
|
-
|
|
18227
|
-
|
|
18228
|
-
|
|
18229
|
-
|
|
18230
|
-
|
|
18231
|
-
|
|
18232
|
-
|
|
18233
|
-
|
|
18234
|
-
|
|
18235
|
-
|
|
18236
|
-
|
|
18237
|
-
|
|
18238
|
-
|
|
18239
|
-
|
|
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
|
+
}
|
|
18240
18096
|
}
|
|
18241
|
-
|
|
18242
|
-
|
|
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();
|
|
18243
18132
|
}
|
|
18244
|
-
|
|
18245
|
-
|
|
18246
|
-
|
|
18247
|
-
|
|
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;
|
|
18248
18162
|
}
|
|
18249
|
-
|
|
18250
|
-
|
|
18251
|
-
|
|
18252
|
-
|
|
18163
|
+
}
|
|
18164
|
+
return low;
|
|
18165
|
+
}
|
|
18166
|
+
|
|
18167
|
+
// src/scene/TextNode.ts
|
|
18168
|
+
var DEFAULT_FONT_SIZE = 14;
|
|
18169
|
+
|
|
18170
|
+
class TextNode extends SceneNode {
|
|
18171
|
+
#text;
|
|
18172
|
+
#formatting;
|
|
18173
|
+
#font;
|
|
18174
|
+
constructor(shader, text, opts = {}) {
|
|
18175
|
+
const { width, height } = measureText(shader.font, text, opts.wordWrap);
|
|
18176
|
+
if (text.length > shader.maxCharCount) {
|
|
18177
|
+
throw new Error(`Text: ${text} exceeds ${shader.maxCharCount} characters. Try using fewer characters or increase the limit in Toodle.attach.`);
|
|
18253
18178
|
}
|
|
18254
|
-
|
|
18255
|
-
|
|
18256
|
-
|
|
18257
|
-
gl.uniform1i(this.#uTextBuffer, 2);
|
|
18179
|
+
const em2px = shader.font.lineHeight / (opts.fontSize ?? DEFAULT_FONT_SIZE);
|
|
18180
|
+
if (!opts.shrinkToFit && !opts.size) {
|
|
18181
|
+
opts.size = { width: width / em2px, height: height / em2px };
|
|
18258
18182
|
}
|
|
18259
|
-
|
|
18260
|
-
|
|
18261
|
-
|
|
18262
|
-
|
|
18263
|
-
|
|
18264
|
-
|
|
18265
|
-
|
|
18266
|
-
const measurements = measureText(this.font, text, formatting.wordWrap);
|
|
18267
|
-
const size = node.size ?? measurements;
|
|
18268
|
-
const fontSize = formatting.shrinkToFit ? findLargestFontSize(this.font, text, size, formatting) : formatting.fontSize;
|
|
18269
|
-
const actualFontSize = fontSize || DEFAULT_FONT_SIZE;
|
|
18270
|
-
shapeText(this.font, text, size, actualFontSize, formatting, this.#cpuTextBuffer, 0);
|
|
18271
|
-
this.#pipeline.updateTextBuffer(this.#cpuTextBuffer, measurements.printedCharCount);
|
|
18272
|
-
if (this.#uTextTransform) {
|
|
18273
|
-
const m3 = node.matrix;
|
|
18274
|
-
const mat3x3 = new Float32Array([
|
|
18275
|
-
m3[0],
|
|
18276
|
-
m3[1],
|
|
18277
|
-
m3[2],
|
|
18278
|
-
m3[4],
|
|
18279
|
-
m3[5],
|
|
18280
|
-
m3[6],
|
|
18281
|
-
m3[8],
|
|
18282
|
-
m3[9],
|
|
18283
|
-
m3[10]
|
|
18284
|
-
]);
|
|
18285
|
-
gl.uniformMatrix3fv(this.#uTextTransform, false, mat3x3);
|
|
18286
|
-
}
|
|
18287
|
-
if (this.#uTextColor) {
|
|
18288
|
-
const tint = node.tint;
|
|
18289
|
-
gl.uniform4f(this.#uTextColor, tint.r, tint.g, tint.b, tint.a);
|
|
18290
|
-
}
|
|
18291
|
-
if (this.#uFontSize) {
|
|
18292
|
-
gl.uniform1f(this.#uFontSize, actualFontSize);
|
|
18293
|
-
}
|
|
18294
|
-
if (this.#uBlockWidth) {
|
|
18295
|
-
gl.uniform1f(this.#uBlockWidth, formatting.align === "center" ? 0 : measurements.width);
|
|
18296
|
-
}
|
|
18297
|
-
if (this.#uBlockHeight) {
|
|
18298
|
-
gl.uniform1f(this.#uBlockHeight, measurements.height);
|
|
18183
|
+
super({
|
|
18184
|
+
...opts,
|
|
18185
|
+
render: {
|
|
18186
|
+
shader,
|
|
18187
|
+
writeInstance: (_node, _array, _offset) => {
|
|
18188
|
+
throw new Error("not implemented - needs access to text uniform buffer, dimensions and a model matrix");
|
|
18189
|
+
}
|
|
18299
18190
|
}
|
|
18300
|
-
|
|
18191
|
+
});
|
|
18192
|
+
this.#font = shader.font;
|
|
18193
|
+
this.#text = text;
|
|
18194
|
+
this.#formatting = opts;
|
|
18195
|
+
}
|
|
18196
|
+
get text() {
|
|
18197
|
+
return this.#text;
|
|
18198
|
+
}
|
|
18199
|
+
get formatting() {
|
|
18200
|
+
return this.#formatting;
|
|
18201
|
+
}
|
|
18202
|
+
get font() {
|
|
18203
|
+
return this.#font;
|
|
18204
|
+
}
|
|
18205
|
+
set text(text) {
|
|
18206
|
+
if (!text) {
|
|
18207
|
+
throw new Error("text cannot be empty");
|
|
18301
18208
|
}
|
|
18302
|
-
|
|
18303
|
-
|
|
18209
|
+
this.#text = text;
|
|
18210
|
+
this.setDirty();
|
|
18304
18211
|
}
|
|
18305
|
-
|
|
18306
|
-
|
|
18307
|
-
|
|
18308
|
-
|
|
18309
|
-
|
|
18310
|
-
|
|
18311
|
-
|
|
18312
|
-
|
|
18313
|
-
|
|
18314
|
-
|
|
18315
|
-
|
|
18212
|
+
get tint() {
|
|
18213
|
+
return this.#formatting.color || { r: 1, g: 1, b: 1, a: 1 };
|
|
18214
|
+
}
|
|
18215
|
+
set tint(tint) {
|
|
18216
|
+
this.#formatting.color = tint;
|
|
18217
|
+
this.setDirty();
|
|
18218
|
+
}
|
|
18219
|
+
set formatting(formatting) {
|
|
18220
|
+
this.#formatting = formatting;
|
|
18221
|
+
this.setDirty();
|
|
18222
|
+
}
|
|
18223
|
+
}
|
|
18224
|
+
|
|
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];
|
|
18316
18274
|
}
|
|
18317
|
-
|
|
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);
|
|
18318
18291
|
}
|
|
18319
18292
|
destroy() {
|
|
18320
|
-
const gl = this.#
|
|
18321
|
-
gl.
|
|
18322
|
-
gl.
|
|
18323
|
-
this
|
|
18293
|
+
const gl = this.#gl;
|
|
18294
|
+
gl.deleteTexture(this.fontTexture);
|
|
18295
|
+
gl.deleteTexture(this.charDataTexture);
|
|
18296
|
+
gl.deleteTexture(this.textBufferTexture);
|
|
18324
18297
|
}
|
|
18325
18298
|
}
|
|
18326
18299
|
|
|
18327
|
-
// src/backends/
|
|
18328
|
-
var
|
|
18329
|
-
|
|
18300
|
+
// src/backends/webgl2/glsl/text.glsl.ts
|
|
18301
|
+
var vertexShader2 = `#version 300 es
|
|
18302
|
+
precision highp float;
|
|
18330
18303
|
|
|
18331
|
-
//
|
|
18332
|
-
|
|
18333
|
-
vec2f(0, -1),
|
|
18334
|
-
vec2f(1, -1),
|
|
18335
|
-
vec2f(0, 0),
|
|
18336
|
-
vec2f(1, 0),
|
|
18337
|
-
);
|
|
18304
|
+
// Engine uniforms
|
|
18305
|
+
uniform mat3 u_viewProjection;
|
|
18338
18306
|
|
|
18339
|
-
//
|
|
18340
|
-
|
|
18341
|
-
|
|
18342
|
-
|
|
18343
|
-
|
|
18344
|
-
|
|
18345
|
-
|
|
18307
|
+
// Per-text-block uniforms
|
|
18308
|
+
uniform mat3 u_textTransform;
|
|
18309
|
+
uniform vec4 u_textColor;
|
|
18310
|
+
uniform float u_fontSize;
|
|
18311
|
+
uniform float u_blockWidth;
|
|
18312
|
+
uniform float u_blockHeight;
|
|
18313
|
+
uniform float u_lineHeight;
|
|
18346
18314
|
|
|
18347
|
-
//
|
|
18348
|
-
|
|
18349
|
-
|
|
18350
|
-
|
|
18351
|
-
};
|
|
18315
|
+
// Character data texture (RGBA32F, 2 texels per character)
|
|
18316
|
+
// Texel 0: texOffset.xy, texExtent.xy
|
|
18317
|
+
// Texel 1: size.xy, offset.xy
|
|
18318
|
+
uniform sampler2D u_charData;
|
|
18352
18319
|
|
|
18353
|
-
//
|
|
18354
|
-
|
|
18355
|
-
|
|
18356
|
-
@location(0) texcoord: vec2f,
|
|
18357
|
-
@location(1) debugColor: vec4f,
|
|
18358
|
-
@location(2) @interpolate(flat) instanceIndex: u32,
|
|
18359
|
-
};
|
|
18320
|
+
// Text buffer texture (RGBA32F, 1 texel per glyph)
|
|
18321
|
+
// Each texel: xy = glyph position, z = char index
|
|
18322
|
+
uniform sampler2D u_textBuffer;
|
|
18360
18323
|
|
|
18361
|
-
//
|
|
18362
|
-
|
|
18363
|
-
texOffset: vec2f, // Offset to top-left in MSDF texture (pixels)
|
|
18364
|
-
texExtent: vec2f, // Size in texture (pixels)
|
|
18365
|
-
size: vec2f, // Glyph size in ems
|
|
18366
|
-
offset: vec2f, // Position offset in ems
|
|
18367
|
-
};
|
|
18324
|
+
// Outputs to fragment shader
|
|
18325
|
+
out vec2 v_texcoord;
|
|
18368
18326
|
|
|
18369
|
-
//
|
|
18370
|
-
|
|
18371
|
-
|
|
18372
|
-
|
|
18373
|
-
|
|
18374
|
-
|
|
18375
|
-
|
|
18376
|
-
bufferPosition: f32 // Index and length in textBuffer
|
|
18377
|
-
};
|
|
18327
|
+
// Quad vertex positions for a character (matches WGSL)
|
|
18328
|
+
const vec2 pos[4] = vec2[4](
|
|
18329
|
+
vec2(0.0, -1.0),
|
|
18330
|
+
vec2(1.0, -1.0),
|
|
18331
|
+
vec2(0.0, 0.0),
|
|
18332
|
+
vec2(1.0, 0.0)
|
|
18333
|
+
);
|
|
18378
18334
|
|
|
18379
|
-
|
|
18380
|
-
|
|
18381
|
-
|
|
18382
|
-
|
|
18383
|
-
|
|
18335
|
+
void main() {
|
|
18336
|
+
// gl_VertexID gives us 0-3 for the quad vertices
|
|
18337
|
+
// gl_InstanceID gives us which glyph we're rendering
|
|
18338
|
+
int vertexIndex = gl_VertexID;
|
|
18339
|
+
int glyphIndex = gl_InstanceID;
|
|
18384
18340
|
|
|
18385
|
-
//
|
|
18386
|
-
|
|
18387
|
-
|
|
18341
|
+
// Fetch glyph data from text buffer texture
|
|
18342
|
+
vec4 glyphData = texelFetch(u_textBuffer, ivec2(glyphIndex, 0), 0);
|
|
18343
|
+
vec2 glyphPos = glyphData.xy;
|
|
18344
|
+
int charIndex = int(glyphData.z);
|
|
18388
18345
|
|
|
18389
|
-
//
|
|
18390
|
-
|
|
18346
|
+
// Fetch character metrics (2 texels per char)
|
|
18347
|
+
// Texel 0: texOffset.x, texOffset.y, texExtent.x, texExtent.y
|
|
18348
|
+
// Texel 1: size.x, size.y, offset.x, offset.y
|
|
18349
|
+
vec4 charData0 = texelFetch(u_charData, ivec2(charIndex * 2, 0), 0);
|
|
18350
|
+
vec4 charData1 = texelFetch(u_charData, ivec2(charIndex * 2 + 1, 0), 0);
|
|
18391
18351
|
|
|
18392
|
-
|
|
18393
|
-
|
|
18394
|
-
|
|
18395
|
-
|
|
18396
|
-
// overloading the vertex index to store the instance of the text metadata.
|
|
18397
|
-
//
|
|
18398
|
-
// I.e...
|
|
18399
|
-
// Vertex 0-4 = Instance 0, Vertex 0-4
|
|
18400
|
-
// Vertex 4-8 = Instance 1, Vertex 0-4
|
|
18401
|
-
// Vertex 8-12 = Instance 2, Vertex 0-4
|
|
18402
|
-
let vertexIndex = input.vertex % 4;
|
|
18403
|
-
let textIndex = input.vertex / 4;
|
|
18352
|
+
vec2 texOffset = charData0.xy;
|
|
18353
|
+
vec2 texExtent = charData0.zw;
|
|
18354
|
+
vec2 charSize = charData1.xy;
|
|
18355
|
+
vec2 charOffset = charData1.zw;
|
|
18404
18356
|
|
|
18405
|
-
|
|
18406
|
-
|
|
18407
|
-
let char = chars[u32(textElement.z)];
|
|
18357
|
+
// Center text vertically; origin is mid-height
|
|
18358
|
+
vec2 offset = vec2(0.0, -u_blockHeight / 2.0);
|
|
18408
18359
|
|
|
18409
|
-
|
|
18410
|
-
|
|
18411
|
-
|
|
18360
|
+
// Glyph position in ems (quad pos * size + per-char offset)
|
|
18361
|
+
vec2 emPos = pos[vertexIndex] * charSize + charOffset + glyphPos - offset;
|
|
18362
|
+
vec2 charPos = emPos * (u_fontSize / u_lineHeight);
|
|
18412
18363
|
|
|
18413
|
-
//
|
|
18414
|
-
|
|
18364
|
+
// Transform position through model and view-projection matrices
|
|
18365
|
+
vec3 worldPos = u_textTransform * vec3(charPos, 1.0);
|
|
18366
|
+
vec3 clipPos = u_viewProjection * worldPos;
|
|
18367
|
+
|
|
18368
|
+
gl_Position = vec4(clipPos.xy, 0.0, 1.0);
|
|
18369
|
+
|
|
18370
|
+
// Calculate texture coordinates
|
|
18371
|
+
v_texcoord = pos[vertexIndex] * vec2(1.0, -1.0);
|
|
18372
|
+
v_texcoord *= texExtent;
|
|
18373
|
+
v_texcoord += texOffset;
|
|
18374
|
+
}
|
|
18375
|
+
`;
|
|
18376
|
+
var fragmentShader2 = `#version 300 es
|
|
18377
|
+
precision highp float;
|
|
18415
18378
|
|
|
18416
|
-
|
|
18417
|
-
|
|
18418
|
-
let charPos = emPos * (text.fontSize / lineHeight);
|
|
18379
|
+
// Font texture (MSDF atlas)
|
|
18380
|
+
uniform sampler2D u_fontTexture;
|
|
18419
18381
|
|
|
18420
|
-
|
|
18421
|
-
|
|
18382
|
+
// Text color
|
|
18383
|
+
uniform vec4 u_textColor;
|
|
18422
18384
|
|
|
18423
|
-
|
|
18424
|
-
|
|
18425
|
-
output.texcoord *= char.texExtent;
|
|
18426
|
-
output.texcoord += char.texOffset;
|
|
18427
|
-
output.debugColor = debugColors[vertexIndex];
|
|
18428
|
-
output.instanceIndex = textIndex;
|
|
18429
|
-
return output;
|
|
18385
|
+
// Input from vertex shader
|
|
18386
|
+
in vec2 v_texcoord;
|
|
18430
18387
|
|
|
18431
|
-
|
|
18432
|
-
|
|
18433
|
-
}
|
|
18388
|
+
// Output color
|
|
18389
|
+
out vec4 fragColor;
|
|
18434
18390
|
|
|
18435
18391
|
// Signed distance function sampling for MSDF font rendering
|
|
18436
|
-
|
|
18437
|
-
|
|
18392
|
+
// Median of three: max(min(r,g), min(max(r,g), b))
|
|
18393
|
+
float sampleMsdf(vec2 texcoord) {
|
|
18394
|
+
vec4 c = texture(u_fontTexture, texcoord);
|
|
18438
18395
|
return max(min(c.r, c.g), min(max(c.r, c.g), c.b));
|
|
18439
18396
|
}
|
|
18440
18397
|
|
|
18441
|
-
|
|
18442
|
-
//
|
|
18443
|
-
|
|
18444
|
-
|
|
18445
|
-
@fragment
|
|
18446
|
-
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
|
|
18447
|
-
let text = texts[input.instanceIndex];
|
|
18448
|
-
|
|
18449
|
-
// pxRange (AKA distanceRange) comes from the msdfgen tool.
|
|
18450
|
-
let pxRange = 4.0;
|
|
18451
|
-
let texSize = vec2f(textureDimensions(fontTexture, 0));
|
|
18398
|
+
void main() {
|
|
18399
|
+
// pxRange (AKA distanceRange) comes from the msdfgen tool
|
|
18400
|
+
float pxRange = 4.0;
|
|
18401
|
+
vec2 texSize = vec2(textureSize(u_fontTexture, 0));
|
|
18452
18402
|
|
|
18453
|
-
|
|
18454
|
-
|
|
18403
|
+
// Anti-aliasing technique by Paul Houx
|
|
18404
|
+
// https://github.com/Chlumsky/msdfgen/issues/22#issuecomment-234958005
|
|
18405
|
+
float dx = texSize.x * length(vec2(dFdx(v_texcoord.x), dFdy(v_texcoord.x)));
|
|
18406
|
+
float dy = texSize.y * length(vec2(dFdx(v_texcoord.y), dFdy(v_texcoord.y)));
|
|
18455
18407
|
|
|
18456
|
-
|
|
18457
|
-
|
|
18458
|
-
|
|
18408
|
+
float toPixels = pxRange * inversesqrt(dx * dx + dy * dy);
|
|
18409
|
+
float sigDist = sampleMsdf(v_texcoord) - 0.5;
|
|
18410
|
+
float pxDist = sigDist * toPixels;
|
|
18459
18411
|
|
|
18460
|
-
|
|
18461
|
-
|
|
18412
|
+
float edgeWidth = 0.5;
|
|
18413
|
+
float alpha = smoothstep(-edgeWidth, edgeWidth, pxDist);
|
|
18462
18414
|
|
|
18463
18415
|
if (alpha < 0.001) {
|
|
18464
18416
|
discard;
|
|
18465
18417
|
}
|
|
18466
18418
|
|
|
18467
|
-
|
|
18468
|
-
return msdfColor;
|
|
18469
|
-
|
|
18470
|
-
// Debug options:
|
|
18471
|
-
// return text.color;
|
|
18472
|
-
// return input.debugColor;
|
|
18473
|
-
// return vec4f(1, 0, 1, 1); // hardcoded magenta
|
|
18474
|
-
// return textureSample(fontTexture, fontSampler, input.texcoord);
|
|
18419
|
+
fragColor = vec4(u_textColor.rgb, u_textColor.a * alpha);
|
|
18475
18420
|
}
|
|
18476
18421
|
`;
|
|
18477
18422
|
|
|
18478
|
-
// src/backends/
|
|
18479
|
-
class
|
|
18480
|
-
pipeline;
|
|
18481
|
-
font;
|
|
18482
|
-
fontBindGroup;
|
|
18483
|
-
maxCharCount;
|
|
18484
|
-
constructor(pipeline, font, fontBindGroup, maxCharCount) {
|
|
18485
|
-
this.pipeline = pipeline;
|
|
18486
|
-
this.font = font;
|
|
18487
|
-
this.fontBindGroup = fontBindGroup;
|
|
18488
|
-
this.maxCharCount = maxCharCount;
|
|
18489
|
-
}
|
|
18490
|
-
static async create(device, font, colorFormat, maxCharCount) {
|
|
18491
|
-
const pipeline = await pipelinePromise(device, colorFormat, font.name);
|
|
18492
|
-
const texture = device.createTexture({
|
|
18493
|
-
label: `MSDF font ${font.name}`,
|
|
18494
|
-
size: [font.imageBitmap.width, font.imageBitmap.height, 1],
|
|
18495
|
-
format: "rgba8unorm",
|
|
18496
|
-
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
18497
|
-
});
|
|
18498
|
-
device.queue.copyExternalImageToTexture({ source: font.imageBitmap }, { texture }, [font.imageBitmap.width, font.imageBitmap.height]);
|
|
18499
|
-
const charsGpuBuffer = device.createBuffer({
|
|
18500
|
-
label: `MSDF font ${font.name} character layout buffer`,
|
|
18501
|
-
size: font.charCount * Float32Array.BYTES_PER_ELEMENT * 8,
|
|
18502
|
-
usage: GPUBufferUsage.STORAGE,
|
|
18503
|
-
mappedAtCreation: true
|
|
18504
|
-
});
|
|
18505
|
-
const charsArray = new Float32Array(charsGpuBuffer.getMappedRange());
|
|
18506
|
-
charsArray.set(font.charBuffer, 0);
|
|
18507
|
-
charsGpuBuffer.unmap();
|
|
18508
|
-
const fontDataBuffer = device.createBuffer({
|
|
18509
|
-
label: `MSDF font ${font.name} metadata buffer`,
|
|
18510
|
-
size: Float32Array.BYTES_PER_ELEMENT * 4,
|
|
18511
|
-
usage: GPUBufferUsage.UNIFORM,
|
|
18512
|
-
mappedAtCreation: true
|
|
18513
|
-
});
|
|
18514
|
-
const fontDataArray = new Float32Array(fontDataBuffer.getMappedRange());
|
|
18515
|
-
fontDataArray[0] = font.lineHeight;
|
|
18516
|
-
fontDataBuffer.unmap();
|
|
18517
|
-
const fontBindGroup = device.createBindGroup({
|
|
18518
|
-
layout: pipeline.getBindGroupLayout(0),
|
|
18519
|
-
entries: [
|
|
18520
|
-
{
|
|
18521
|
-
binding: 0,
|
|
18522
|
-
resource: texture.createView()
|
|
18523
|
-
},
|
|
18524
|
-
{
|
|
18525
|
-
binding: 1,
|
|
18526
|
-
resource: device.createSampler(sampler)
|
|
18527
|
-
},
|
|
18528
|
-
{
|
|
18529
|
-
binding: 2,
|
|
18530
|
-
resource: {
|
|
18531
|
-
buffer: charsGpuBuffer
|
|
18532
|
-
}
|
|
18533
|
-
},
|
|
18534
|
-
{
|
|
18535
|
-
binding: 3,
|
|
18536
|
-
resource: {
|
|
18537
|
-
buffer: fontDataBuffer
|
|
18538
|
-
}
|
|
18539
|
-
}
|
|
18540
|
-
]
|
|
18541
|
-
});
|
|
18542
|
-
return new FontPipeline(pipeline, font, fontBindGroup, maxCharCount);
|
|
18543
|
-
}
|
|
18544
|
-
}
|
|
18545
|
-
function pipelinePromise(device, colorFormat, label) {
|
|
18546
|
-
const shader = device.createShaderModule({
|
|
18547
|
-
label: `${label} shader`,
|
|
18548
|
-
code: text_wgsl_default
|
|
18549
|
-
});
|
|
18550
|
-
return device.createRenderPipelineAsync({
|
|
18551
|
-
label: `${label} pipeline`,
|
|
18552
|
-
layout: device.createPipelineLayout({
|
|
18553
|
-
bindGroupLayouts: [
|
|
18554
|
-
device.createBindGroupLayout(fontBindGroupLayout),
|
|
18555
|
-
device.createBindGroupLayout(textUniformBindGroupLayout),
|
|
18556
|
-
device.createBindGroupLayout(engineUniformBindGroupLayout)
|
|
18557
|
-
]
|
|
18558
|
-
}),
|
|
18559
|
-
vertex: {
|
|
18560
|
-
module: shader,
|
|
18561
|
-
entryPoint: "vertexMain"
|
|
18562
|
-
},
|
|
18563
|
-
fragment: {
|
|
18564
|
-
module: shader,
|
|
18565
|
-
entryPoint: "fragmentMain",
|
|
18566
|
-
targets: [
|
|
18567
|
-
{
|
|
18568
|
-
format: colorFormat,
|
|
18569
|
-
blend: {
|
|
18570
|
-
color: {
|
|
18571
|
-
srcFactor: "src-alpha",
|
|
18572
|
-
dstFactor: "one-minus-src-alpha"
|
|
18573
|
-
},
|
|
18574
|
-
alpha: {
|
|
18575
|
-
srcFactor: "one",
|
|
18576
|
-
dstFactor: "one"
|
|
18577
|
-
}
|
|
18578
|
-
}
|
|
18579
|
-
}
|
|
18580
|
-
]
|
|
18581
|
-
},
|
|
18582
|
-
primitive: {
|
|
18583
|
-
topology: "triangle-strip",
|
|
18584
|
-
stripIndexFormat: "uint32"
|
|
18585
|
-
}
|
|
18586
|
-
});
|
|
18587
|
-
}
|
|
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 {
|
|
18423
|
+
// src/backends/webgl2/WebGLTextShader.ts
|
|
18424
|
+
class WebGLTextShader2 {
|
|
18661
18425
|
label = "text";
|
|
18662
|
-
code =
|
|
18426
|
+
code = fragmentShader2;
|
|
18427
|
+
font;
|
|
18428
|
+
maxCharCount;
|
|
18663
18429
|
#backend;
|
|
18664
18430
|
#pipeline;
|
|
18665
|
-
#
|
|
18666
|
-
#
|
|
18667
|
-
#
|
|
18668
|
-
#
|
|
18669
|
-
#
|
|
18670
|
-
#
|
|
18671
|
-
#
|
|
18672
|
-
#
|
|
18673
|
-
#
|
|
18674
|
-
#
|
|
18675
|
-
|
|
18431
|
+
#program;
|
|
18432
|
+
#vao;
|
|
18433
|
+
#cpuTextBuffer;
|
|
18434
|
+
#cachedUniform = null;
|
|
18435
|
+
#uViewProjection = null;
|
|
18436
|
+
#uTextTransform = null;
|
|
18437
|
+
#uTextColor = null;
|
|
18438
|
+
#uFontSize = null;
|
|
18439
|
+
#uBlockWidth = null;
|
|
18440
|
+
#uBlockHeight = null;
|
|
18441
|
+
#uLineHeight = null;
|
|
18442
|
+
#uCharData = null;
|
|
18443
|
+
#uTextBuffer = null;
|
|
18444
|
+
#uFontTexture = null;
|
|
18445
|
+
constructor(backend, pipeline) {
|
|
18676
18446
|
this.#backend = backend;
|
|
18677
|
-
|
|
18678
|
-
this
|
|
18679
|
-
this
|
|
18680
|
-
|
|
18681
|
-
this.#
|
|
18682
|
-
|
|
18683
|
-
|
|
18684
|
-
|
|
18685
|
-
|
|
18686
|
-
|
|
18687
|
-
|
|
18688
|
-
|
|
18689
|
-
|
|
18690
|
-
|
|
18691
|
-
|
|
18692
|
-
|
|
18693
|
-
this.#
|
|
18694
|
-
|
|
18695
|
-
|
|
18696
|
-
|
|
18697
|
-
|
|
18698
|
-
this.#
|
|
18699
|
-
this.#
|
|
18700
|
-
|
|
18701
|
-
|
|
18702
|
-
|
|
18703
|
-
|
|
18704
|
-
|
|
18705
|
-
|
|
18706
|
-
|
|
18707
|
-
|
|
18708
|
-
|
|
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);
|
|
18447
|
+
this.#pipeline = pipeline;
|
|
18448
|
+
this.font = pipeline.font;
|
|
18449
|
+
this.maxCharCount = pipeline.maxCharCount;
|
|
18450
|
+
const gl = backend.gl;
|
|
18451
|
+
const vs = this.#compileShader(gl, gl.VERTEX_SHADER, vertexShader2);
|
|
18452
|
+
const fs = this.#compileShader(gl, gl.FRAGMENT_SHADER, fragmentShader2);
|
|
18453
|
+
const program = gl.createProgram();
|
|
18454
|
+
assert(program, "Failed to create WebGL program");
|
|
18455
|
+
gl.attachShader(program, vs);
|
|
18456
|
+
gl.attachShader(program, fs);
|
|
18457
|
+
gl.linkProgram(program);
|
|
18458
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
18459
|
+
const info = gl.getProgramInfoLog(program);
|
|
18460
|
+
throw new Error(`Failed to link text shader program: ${info}`);
|
|
18461
|
+
}
|
|
18462
|
+
this.#program = program;
|
|
18463
|
+
this.#uViewProjection = gl.getUniformLocation(program, "u_viewProjection");
|
|
18464
|
+
this.#uTextTransform = gl.getUniformLocation(program, "u_textTransform");
|
|
18465
|
+
this.#uTextColor = gl.getUniformLocation(program, "u_textColor");
|
|
18466
|
+
this.#uFontSize = gl.getUniformLocation(program, "u_fontSize");
|
|
18467
|
+
this.#uBlockWidth = gl.getUniformLocation(program, "u_blockWidth");
|
|
18468
|
+
this.#uBlockHeight = gl.getUniformLocation(program, "u_blockHeight");
|
|
18469
|
+
this.#uLineHeight = gl.getUniformLocation(program, "u_lineHeight");
|
|
18470
|
+
this.#uCharData = gl.getUniformLocation(program, "u_charData");
|
|
18471
|
+
this.#uTextBuffer = gl.getUniformLocation(program, "u_textBuffer");
|
|
18472
|
+
this.#uFontTexture = gl.getUniformLocation(program, "u_fontTexture");
|
|
18473
|
+
const vao = gl.createVertexArray();
|
|
18474
|
+
assert(vao, "Failed to create WebGL VAO");
|
|
18475
|
+
this.#vao = vao;
|
|
18476
|
+
this.#cpuTextBuffer = new Float32Array(this.maxCharCount * 4);
|
|
18477
|
+
gl.deleteShader(vs);
|
|
18478
|
+
gl.deleteShader(fs);
|
|
18724
18479
|
}
|
|
18725
18480
|
startFrame(uniform) {
|
|
18726
|
-
|
|
18727
|
-
device.queue.writeBuffer(this.#engineUniformsBuffer, 0, uniform.viewProjectionMatrix);
|
|
18728
|
-
this.#instanceIndex = 0;
|
|
18729
|
-
this.#textBlockOffset = 0;
|
|
18481
|
+
this.#cachedUniform = uniform;
|
|
18730
18482
|
}
|
|
18731
18483
|
processBatch(nodes) {
|
|
18732
18484
|
if (nodes.length === 0)
|
|
18733
18485
|
return 0;
|
|
18734
|
-
const
|
|
18735
|
-
|
|
18736
|
-
|
|
18737
|
-
|
|
18486
|
+
const gl = this.#backend.gl;
|
|
18487
|
+
const uniform = this.#cachedUniform;
|
|
18488
|
+
if (!uniform) {
|
|
18489
|
+
throw new Error("Tried to process batch but engine uniform is not set");
|
|
18490
|
+
}
|
|
18491
|
+
gl.useProgram(this.#program);
|
|
18492
|
+
gl.bindVertexArray(this.#vao);
|
|
18493
|
+
if (this.#uViewProjection) {
|
|
18494
|
+
const m3 = uniform.viewProjectionMatrix;
|
|
18495
|
+
const mat3x3 = new Float32Array([
|
|
18496
|
+
m3[0],
|
|
18497
|
+
m3[1],
|
|
18498
|
+
m3[2],
|
|
18499
|
+
m3[4],
|
|
18500
|
+
m3[5],
|
|
18501
|
+
m3[6],
|
|
18502
|
+
m3[8],
|
|
18503
|
+
m3[9],
|
|
18504
|
+
m3[10]
|
|
18505
|
+
]);
|
|
18506
|
+
gl.uniformMatrix3fv(this.#uViewProjection, false, mat3x3);
|
|
18507
|
+
}
|
|
18508
|
+
if (this.#uLineHeight) {
|
|
18509
|
+
gl.uniform1f(this.#uLineHeight, this.#pipeline.lineHeight);
|
|
18510
|
+
}
|
|
18511
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
18512
|
+
gl.bindTexture(gl.TEXTURE_2D, this.#pipeline.fontTexture);
|
|
18513
|
+
if (this.#uFontTexture) {
|
|
18514
|
+
gl.uniform1i(this.#uFontTexture, 0);
|
|
18515
|
+
}
|
|
18516
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
18517
|
+
gl.bindTexture(gl.TEXTURE_2D, this.#pipeline.charDataTexture);
|
|
18518
|
+
if (this.#uCharData) {
|
|
18519
|
+
gl.uniform1i(this.#uCharData, 1);
|
|
18520
|
+
}
|
|
18521
|
+
gl.activeTexture(gl.TEXTURE2);
|
|
18522
|
+
gl.bindTexture(gl.TEXTURE_2D, this.#pipeline.textBufferTexture);
|
|
18523
|
+
if (this.#uTextBuffer) {
|
|
18524
|
+
gl.uniform1i(this.#uTextBuffer, 2);
|
|
18738
18525
|
}
|
|
18739
18526
|
for (const node of nodes) {
|
|
18740
18527
|
if (!(node instanceof TextNode)) {
|
|
18741
18528
|
console.error(node);
|
|
18742
|
-
throw new Error(`Tried to use
|
|
18529
|
+
throw new Error(`Tried to use WebGLTextShader on something that isn't a TextNode: ${node}`);
|
|
18743
18530
|
}
|
|
18744
18531
|
const text = node.text;
|
|
18745
18532
|
const formatting = node.formatting;
|
|
18746
|
-
const measurements = measureText(this
|
|
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);
|
|
18533
|
+
const measurements = measureText(this.font, text, formatting.wordWrap);
|
|
18751
18534
|
const size = node.size ?? measurements;
|
|
18752
|
-
const fontSize = formatting.shrinkToFit ? findLargestFontSize(this
|
|
18535
|
+
const fontSize = formatting.shrinkToFit ? findLargestFontSize(this.font, text, size, formatting) : formatting.fontSize;
|
|
18753
18536
|
const actualFontSize = fontSize || DEFAULT_FONT_SIZE;
|
|
18754
|
-
this
|
|
18755
|
-
this.#
|
|
18756
|
-
this.#
|
|
18757
|
-
|
|
18758
|
-
|
|
18759
|
-
|
|
18760
|
-
|
|
18761
|
-
|
|
18762
|
-
|
|
18763
|
-
|
|
18764
|
-
|
|
18765
|
-
|
|
18766
|
-
|
|
18767
|
-
|
|
18768
|
-
|
|
18769
|
-
|
|
18770
|
-
|
|
18771
|
-
|
|
18772
|
-
|
|
18773
|
-
|
|
18774
|
-
}
|
|
18775
|
-
|
|
18776
|
-
|
|
18777
|
-
|
|
18778
|
-
|
|
18779
|
-
|
|
18780
|
-
|
|
18781
|
-
|
|
18782
|
-
|
|
18783
|
-
|
|
18784
|
-
|
|
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);
|
|
18537
|
+
shapeText(this.font, text, size, actualFontSize, formatting, this.#cpuTextBuffer, 0);
|
|
18538
|
+
this.#pipeline.updateTextBuffer(this.#cpuTextBuffer, measurements.printedCharCount);
|
|
18539
|
+
if (this.#uTextTransform) {
|
|
18540
|
+
const m3 = node.matrix;
|
|
18541
|
+
const mat3x3 = new Float32Array([
|
|
18542
|
+
m3[0],
|
|
18543
|
+
m3[1],
|
|
18544
|
+
m3[2],
|
|
18545
|
+
m3[4],
|
|
18546
|
+
m3[5],
|
|
18547
|
+
m3[6],
|
|
18548
|
+
m3[8],
|
|
18549
|
+
m3[9],
|
|
18550
|
+
m3[10]
|
|
18551
|
+
]);
|
|
18552
|
+
gl.uniformMatrix3fv(this.#uTextTransform, false, mat3x3);
|
|
18553
|
+
}
|
|
18554
|
+
if (this.#uTextColor) {
|
|
18555
|
+
const tint = node.tint;
|
|
18556
|
+
gl.uniform4f(this.#uTextColor, tint.r, tint.g, tint.b, tint.a);
|
|
18557
|
+
}
|
|
18558
|
+
if (this.#uFontSize) {
|
|
18559
|
+
gl.uniform1f(this.#uFontSize, actualFontSize);
|
|
18560
|
+
}
|
|
18561
|
+
if (this.#uBlockWidth) {
|
|
18562
|
+
gl.uniform1f(this.#uBlockWidth, formatting.align === "center" ? 0 : measurements.width);
|
|
18563
|
+
}
|
|
18564
|
+
if (this.#uBlockHeight) {
|
|
18565
|
+
gl.uniform1f(this.#uBlockHeight, measurements.height);
|
|
18566
|
+
}
|
|
18567
|
+
gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, measurements.printedCharCount);
|
|
18813
18568
|
}
|
|
18814
|
-
|
|
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();
|
|
18569
|
+
gl.bindVertexArray(null);
|
|
18570
|
+
return nodes.length;
|
|
18859
18571
|
}
|
|
18860
|
-
|
|
18861
|
-
|
|
18862
|
-
|
|
18863
|
-
|
|
18572
|
+
endFrame() {}
|
|
18573
|
+
#compileShader(gl, type, source) {
|
|
18574
|
+
const shader = gl.createShader(type);
|
|
18575
|
+
assert(shader, "Failed to create WebGL shader");
|
|
18576
|
+
gl.shaderSource(shader, source);
|
|
18577
|
+
gl.compileShader(shader);
|
|
18578
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
18579
|
+
const info = gl.getShaderInfoLog(shader);
|
|
18580
|
+
const typeStr = type === gl.VERTEX_SHADER ? "vertex" : "fragment";
|
|
18581
|
+
gl.deleteShader(shader);
|
|
18582
|
+
throw new Error(`Failed to compile ${typeStr} shader: ${info}`);
|
|
18864
18583
|
}
|
|
18865
|
-
return
|
|
18584
|
+
return shader;
|
|
18866
18585
|
}
|
|
18867
|
-
|
|
18868
|
-
|
|
18586
|
+
destroy() {
|
|
18587
|
+
const gl = this.#backend.gl;
|
|
18588
|
+
gl.deleteProgram(this.#program);
|
|
18589
|
+
gl.deleteVertexArray(this.#vao);
|
|
18590
|
+
this.#pipeline.destroy();
|
|
18869
18591
|
}
|
|
18870
18592
|
}
|
|
18871
18593
|
|
|
18872
|
-
// src/
|
|
18873
|
-
var
|
|
18874
|
-
|
|
18875
|
-
|
|
18876
|
-
|
|
18877
|
-
|
|
18878
|
-
|
|
18879
|
-
|
|
18880
|
-
|
|
18594
|
+
// src/backends/webgpu/wgsl/text.wgsl.ts
|
|
18595
|
+
var text_wgsl_default = `
|
|
18596
|
+
// Adapted from: https://webgpu.github.io/webgpu-samples/?sample=textRenderingMsdf
|
|
18597
|
+
|
|
18598
|
+
// Quad vertex positions for a character
|
|
18599
|
+
const pos = array(
|
|
18600
|
+
vec2f(0, -1),
|
|
18601
|
+
vec2f(1, -1),
|
|
18602
|
+
vec2f(0, 0),
|
|
18603
|
+
vec2f(1, 0),
|
|
18604
|
+
);
|
|
18605
|
+
|
|
18606
|
+
// Debug colors for visualization
|
|
18607
|
+
const debugColors = array(
|
|
18608
|
+
vec4f(1, 0, 0, 1),
|
|
18609
|
+
vec4f(0, 1, 0, 1),
|
|
18610
|
+
vec4f(0, 0, 1, 1),
|
|
18611
|
+
vec4f(1, 1, 1, 1),
|
|
18612
|
+
);
|
|
18613
|
+
|
|
18614
|
+
// Vertex input from GPU
|
|
18615
|
+
struct VertexInput {
|
|
18616
|
+
@builtin(vertex_index) vertex: u32,
|
|
18617
|
+
@builtin(instance_index) instance: u32,
|
|
18881
18618
|
};
|
|
18882
18619
|
|
|
18883
|
-
|
|
18884
|
-
|
|
18885
|
-
|
|
18886
|
-
|
|
18887
|
-
|
|
18888
|
-
|
|
18889
|
-
|
|
18890
|
-
|
|
18891
|
-
|
|
18892
|
-
|
|
18893
|
-
|
|
18894
|
-
|
|
18895
|
-
|
|
18896
|
-
|
|
18897
|
-
|
|
18898
|
-
|
|
18899
|
-
|
|
18900
|
-
|
|
18901
|
-
|
|
18902
|
-
|
|
18903
|
-
|
|
18904
|
-
|
|
18905
|
-
|
|
18906
|
-
|
|
18907
|
-
|
|
18908
|
-
|
|
18909
|
-
|
|
18910
|
-
|
|
18911
|
-
|
|
18912
|
-
|
|
18913
|
-
|
|
18914
|
-
|
|
18915
|
-
|
|
18916
|
-
|
|
18917
|
-
|
|
18918
|
-
|
|
18919
|
-
|
|
18920
|
-
|
|
18921
|
-
|
|
18922
|
-
|
|
18923
|
-
|
|
18924
|
-
|
|
18925
|
-
|
|
18926
|
-
|
|
18927
|
-
|
|
18928
|
-
|
|
18929
|
-
|
|
18930
|
-
|
|
18931
|
-
|
|
18932
|
-
|
|
18933
|
-
|
|
18934
|
-
|
|
18935
|
-
|
|
18936
|
-
|
|
18937
|
-
|
|
18938
|
-
|
|
18939
|
-
|
|
18940
|
-
|
|
18941
|
-
|
|
18942
|
-
|
|
18943
|
-
|
|
18944
|
-
|
|
18945
|
-
|
|
18946
|
-
|
|
18947
|
-
|
|
18948
|
-
|
|
18949
|
-
|
|
18950
|
-
|
|
18951
|
-
|
|
18952
|
-
|
|
18953
|
-
|
|
18954
|
-
|
|
18955
|
-
|
|
18956
|
-
|
|
18957
|
-
|
|
18958
|
-
|
|
18959
|
-
|
|
18960
|
-
|
|
18961
|
-
|
|
18962
|
-
|
|
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
|
-
};
|
|
18620
|
+
// Output from vertex shader to fragment shader
|
|
18621
|
+
struct VertexOutput {
|
|
18622
|
+
@builtin(position) position: vec4f,
|
|
18623
|
+
@location(0) texcoord: vec2f,
|
|
18624
|
+
@location(1) debugColor: vec4f,
|
|
18625
|
+
@location(2) @interpolate(flat) instanceIndex: u32,
|
|
18626
|
+
};
|
|
18627
|
+
|
|
18628
|
+
// Metadata for a single character glyph
|
|
18629
|
+
struct Char {
|
|
18630
|
+
texOffset: vec2f, // Offset to top-left in MSDF texture (pixels)
|
|
18631
|
+
texExtent: vec2f, // Size in texture (pixels)
|
|
18632
|
+
size: vec2f, // Glyph size in ems
|
|
18633
|
+
offset: vec2f, // Position offset in ems
|
|
18634
|
+
};
|
|
18635
|
+
|
|
18636
|
+
// Metadata for a text block
|
|
18637
|
+
struct TextBlockDescriptor {
|
|
18638
|
+
transform: mat3x3f, // Text transform matrix (model matrix)
|
|
18639
|
+
color: vec4f, // Text color
|
|
18640
|
+
fontSize: f32, // Font size
|
|
18641
|
+
blockWidth: f32, // Total width of text block
|
|
18642
|
+
blockHeight: f32, // Total height of text block
|
|
18643
|
+
bufferPosition: f32 // Index and length in textBuffer
|
|
18644
|
+
};
|
|
18645
|
+
|
|
18646
|
+
// Font bindings
|
|
18647
|
+
@group(0) @binding(0) var fontTexture: texture_2d<f32>;
|
|
18648
|
+
@group(0) @binding(1) var fontSampler: sampler;
|
|
18649
|
+
@group(0) @binding(2) var<storage> chars: array<Char>;
|
|
18650
|
+
@group(0) @binding(3) var<uniform> fontData: vec4f; // Contains line height (x)
|
|
18651
|
+
|
|
18652
|
+
// Text bindings
|
|
18653
|
+
@group(1) @binding(0) var<storage> texts: array<TextBlockDescriptor>;
|
|
18654
|
+
@group(1) @binding(1) var<storage> textBuffer: array<vec4f>; // Each vec4: xy = glyph pos, z = char index
|
|
18655
|
+
|
|
18656
|
+
// Global uniforms
|
|
18657
|
+
@group(2) @binding(0) var<uniform> viewProjectionMatrix: mat3x3f;
|
|
18658
|
+
|
|
18659
|
+
// Vertex shader
|
|
18660
|
+
@vertex
|
|
18661
|
+
fn vertexMain(input: VertexInput) -> VertexOutput {
|
|
18662
|
+
// Because the instance index is used for character indexing, we are
|
|
18663
|
+
// overloading the vertex index to store the instance of the text metadata.
|
|
18664
|
+
//
|
|
18665
|
+
// I.e...
|
|
18666
|
+
// Vertex 0-4 = Instance 0, Vertex 0-4
|
|
18667
|
+
// Vertex 4-8 = Instance 1, Vertex 0-4
|
|
18668
|
+
// Vertex 8-12 = Instance 2, Vertex 0-4
|
|
18669
|
+
let vertexIndex = input.vertex % 4;
|
|
18670
|
+
let textIndex = input.vertex / 4;
|
|
18671
|
+
|
|
18672
|
+
let text = texts[textIndex];
|
|
18673
|
+
let textElement = textBuffer[u32(text.bufferPosition) + input.instance];
|
|
18674
|
+
let char = chars[u32(textElement.z)];
|
|
18675
|
+
|
|
18676
|
+
let lineHeight = fontData.x;
|
|
18677
|
+
let textWidth = text.blockWidth;
|
|
18678
|
+
let textHeight = text.blockHeight;
|
|
18679
|
+
|
|
18680
|
+
// Center text vertically; origin is mid-height
|
|
18681
|
+
let offset = vec2f(0, -textHeight / 2);
|
|
18682
|
+
|
|
18683
|
+
// Glyph position in ems (quad pos * size + per-char offset)
|
|
18684
|
+
let emPos = pos[vertexIndex] * char.size + char.offset + textElement.xy - offset;
|
|
18685
|
+
let charPos = emPos * (text.fontSize / lineHeight);
|
|
18686
|
+
|
|
18687
|
+
var output: VertexOutput;
|
|
18688
|
+
let transformedPosition = viewProjectionMatrix * text.transform * vec3f(charPos, 1);
|
|
18689
|
+
|
|
18690
|
+
output.position = vec4f(transformedPosition, 1);
|
|
18691
|
+
output.texcoord = pos[vertexIndex] * vec2f(1, -1);
|
|
18692
|
+
output.texcoord *= char.texExtent;
|
|
18693
|
+
output.texcoord += char.texOffset;
|
|
18694
|
+
output.debugColor = debugColors[vertexIndex];
|
|
18695
|
+
output.instanceIndex = textIndex;
|
|
18696
|
+
return output;
|
|
18697
|
+
|
|
18698
|
+
// To debug - hardcode quad in bottom right quarter of the screen:
|
|
18699
|
+
// output.position = vec4f(pos[input.vertex], 0, 1);
|
|
18999
18700
|
}
|
|
19000
|
-
|
|
19001
|
-
|
|
19002
|
-
|
|
19003
|
-
|
|
19004
|
-
|
|
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;
|
|
18701
|
+
|
|
18702
|
+
// Signed distance function sampling for MSDF font rendering
|
|
18703
|
+
fn sampleMsdf(texcoord: vec2f) -> f32 {
|
|
18704
|
+
let c = textureSample(fontTexture, fontSampler, texcoord);
|
|
18705
|
+
return max(min(c.r, c.g), min(max(c.r, c.g), c.b));
|
|
19032
18706
|
}
|
|
19033
18707
|
|
|
19034
|
-
//
|
|
19035
|
-
|
|
19036
|
-
|
|
18708
|
+
// Fragment shader
|
|
18709
|
+
// Anti-aliasing technique by Paul Houx
|
|
18710
|
+
// more details here:
|
|
18711
|
+
// https://github.com/Chlumsky/msdfgen/issues/22#issuecomment-234958005
|
|
18712
|
+
@fragment
|
|
18713
|
+
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
|
|
18714
|
+
let text = texts[input.instanceIndex];
|
|
19037
18715
|
|
|
19038
|
-
|
|
19039
|
-
|
|
19040
|
-
|
|
19041
|
-
|
|
19042
|
-
|
|
19043
|
-
|
|
19044
|
-
|
|
19045
|
-
|
|
19046
|
-
|
|
19047
|
-
|
|
19048
|
-
|
|
19049
|
-
|
|
19050
|
-
|
|
19051
|
-
|
|
19052
|
-
|
|
19053
|
-
|
|
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;
|
|
18716
|
+
// pxRange (AKA distanceRange) comes from the msdfgen tool.
|
|
18717
|
+
let pxRange = 4.0;
|
|
18718
|
+
let texSize = vec2f(textureDimensions(fontTexture, 0));
|
|
18719
|
+
|
|
18720
|
+
let dx = texSize.x * length(vec2f(dpdxFine(input.texcoord.x), dpdyFine(input.texcoord.x)));
|
|
18721
|
+
let dy = texSize.y * length(vec2f(dpdxFine(input.texcoord.y), dpdyFine(input.texcoord.y)));
|
|
18722
|
+
|
|
18723
|
+
let toPixels = pxRange * inverseSqrt(dx * dx + dy * dy);
|
|
18724
|
+
let sigDist = sampleMsdf(input.texcoord) - 0.5;
|
|
18725
|
+
let pxDist = sigDist * toPixels;
|
|
18726
|
+
|
|
18727
|
+
let edgeWidth = 0.5;
|
|
18728
|
+
let alpha = smoothstep(-edgeWidth, edgeWidth, pxDist);
|
|
18729
|
+
|
|
18730
|
+
if (alpha < 0.001) {
|
|
18731
|
+
discard;
|
|
19094
18732
|
}
|
|
18733
|
+
|
|
18734
|
+
let msdfColor = vec4f(text.color.rgb, text.color.a * alpha);
|
|
18735
|
+
return msdfColor;
|
|
18736
|
+
|
|
18737
|
+
// Debug options:
|
|
18738
|
+
// return text.color;
|
|
18739
|
+
// return input.debugColor;
|
|
18740
|
+
// return vec4f(1, 0, 1, 1); // hardcoded magenta
|
|
18741
|
+
// return textureSample(fontTexture, fontSampler, input.texcoord);
|
|
19095
18742
|
}
|
|
19096
|
-
|
|
19097
|
-
|
|
19098
|
-
|
|
18743
|
+
`;
|
|
18744
|
+
|
|
18745
|
+
// src/backends/webgpu/FontPipeline.ts
|
|
18746
|
+
class FontPipeline2 {
|
|
18747
|
+
pipeline;
|
|
18748
|
+
font;
|
|
18749
|
+
fontBindGroup;
|
|
18750
|
+
maxCharCount;
|
|
18751
|
+
constructor(pipeline, font, fontBindGroup, maxCharCount) {
|
|
18752
|
+
this.pipeline = pipeline;
|
|
18753
|
+
this.font = font;
|
|
18754
|
+
this.fontBindGroup = fontBindGroup;
|
|
18755
|
+
this.maxCharCount = maxCharCount;
|
|
19099
18756
|
}
|
|
19100
|
-
|
|
19101
|
-
|
|
19102
|
-
const
|
|
19103
|
-
|
|
19104
|
-
|
|
19105
|
-
|
|
19106
|
-
|
|
19107
|
-
|
|
19108
|
-
|
|
19109
|
-
|
|
19110
|
-
|
|
19111
|
-
|
|
19112
|
-
|
|
19113
|
-
|
|
19114
|
-
|
|
19115
|
-
const
|
|
19116
|
-
|
|
19117
|
-
|
|
19118
|
-
|
|
19119
|
-
|
|
19120
|
-
|
|
19121
|
-
|
|
19122
|
-
|
|
19123
|
-
|
|
19124
|
-
|
|
19125
|
-
|
|
19126
|
-
|
|
19127
|
-
|
|
18757
|
+
static async create(device, font, colorFormat, maxCharCount) {
|
|
18758
|
+
const pipeline = await pipelinePromise(device, colorFormat, font.name);
|
|
18759
|
+
const texture = device.createTexture({
|
|
18760
|
+
label: `MSDF font ${font.name}`,
|
|
18761
|
+
size: [font.imageBitmap.width, font.imageBitmap.height, 1],
|
|
18762
|
+
format: "rgba8unorm",
|
|
18763
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
18764
|
+
});
|
|
18765
|
+
device.queue.copyExternalImageToTexture({ source: font.imageBitmap }, { texture }, [font.imageBitmap.width, font.imageBitmap.height]);
|
|
18766
|
+
const charsGpuBuffer = device.createBuffer({
|
|
18767
|
+
label: `MSDF font ${font.name} character layout buffer`,
|
|
18768
|
+
size: font.charCount * Float32Array.BYTES_PER_ELEMENT * 8,
|
|
18769
|
+
usage: GPUBufferUsage.STORAGE,
|
|
18770
|
+
mappedAtCreation: true
|
|
18771
|
+
});
|
|
18772
|
+
const charsArray = new Float32Array(charsGpuBuffer.getMappedRange());
|
|
18773
|
+
charsArray.set(font.charBuffer, 0);
|
|
18774
|
+
charsGpuBuffer.unmap();
|
|
18775
|
+
const fontDataBuffer = device.createBuffer({
|
|
18776
|
+
label: `MSDF font ${font.name} metadata buffer`,
|
|
18777
|
+
size: Float32Array.BYTES_PER_ELEMENT * 4,
|
|
18778
|
+
usage: GPUBufferUsage.UNIFORM,
|
|
18779
|
+
mappedAtCreation: true
|
|
18780
|
+
});
|
|
18781
|
+
const fontDataArray = new Float32Array(fontDataBuffer.getMappedRange());
|
|
18782
|
+
fontDataArray[0] = font.lineHeight;
|
|
18783
|
+
fontDataBuffer.unmap();
|
|
18784
|
+
const fontBindGroup = device.createBindGroup({
|
|
18785
|
+
layout: pipeline.getBindGroupLayout(0),
|
|
18786
|
+
entries: [
|
|
18787
|
+
{
|
|
18788
|
+
binding: 0,
|
|
18789
|
+
resource: texture.createView()
|
|
18790
|
+
},
|
|
18791
|
+
{
|
|
18792
|
+
binding: 1,
|
|
18793
|
+
resource: device.createSampler(sampler)
|
|
18794
|
+
},
|
|
18795
|
+
{
|
|
18796
|
+
binding: 2,
|
|
18797
|
+
resource: {
|
|
18798
|
+
buffer: charsGpuBuffer
|
|
18799
|
+
}
|
|
18800
|
+
},
|
|
18801
|
+
{
|
|
18802
|
+
binding: 3,
|
|
18803
|
+
resource: {
|
|
18804
|
+
buffer: fontDataBuffer
|
|
18805
|
+
}
|
|
18806
|
+
}
|
|
18807
|
+
]
|
|
18808
|
+
});
|
|
18809
|
+
return new FontPipeline2(pipeline, font, fontBindGroup, maxCharCount);
|
|
19128
18810
|
}
|
|
19129
|
-
node.writeInstance?.(array, offset + tileOffset);
|
|
19130
|
-
return node.tiles.length;
|
|
19131
18811
|
}
|
|
18812
|
+
function pipelinePromise(device, colorFormat, label) {
|
|
18813
|
+
const shader = device.createShaderModule({
|
|
18814
|
+
label: `${label} shader`,
|
|
18815
|
+
code: text_wgsl_default
|
|
18816
|
+
});
|
|
18817
|
+
return device.createRenderPipelineAsync({
|
|
18818
|
+
label: `${label} pipeline`,
|
|
18819
|
+
layout: device.createPipelineLayout({
|
|
18820
|
+
bindGroupLayouts: [
|
|
18821
|
+
device.createBindGroupLayout(fontBindGroupLayout),
|
|
18822
|
+
device.createBindGroupLayout(textUniformBindGroupLayout),
|
|
18823
|
+
device.createBindGroupLayout(engineUniformBindGroupLayout)
|
|
18824
|
+
]
|
|
18825
|
+
}),
|
|
18826
|
+
vertex: {
|
|
18827
|
+
module: shader,
|
|
18828
|
+
entryPoint: "vertexMain"
|
|
18829
|
+
},
|
|
18830
|
+
fragment: {
|
|
18831
|
+
module: shader,
|
|
18832
|
+
entryPoint: "fragmentMain",
|
|
18833
|
+
targets: [
|
|
18834
|
+
{
|
|
18835
|
+
format: colorFormat,
|
|
18836
|
+
blend: {
|
|
18837
|
+
color: {
|
|
18838
|
+
srcFactor: "src-alpha",
|
|
18839
|
+
dstFactor: "one-minus-src-alpha"
|
|
18840
|
+
},
|
|
18841
|
+
alpha: {
|
|
18842
|
+
srcFactor: "one",
|
|
18843
|
+
dstFactor: "one"
|
|
18844
|
+
}
|
|
18845
|
+
}
|
|
18846
|
+
}
|
|
18847
|
+
]
|
|
18848
|
+
},
|
|
18849
|
+
primitive: {
|
|
18850
|
+
topology: "triangle-strip",
|
|
18851
|
+
stripIndexFormat: "uint32"
|
|
18852
|
+
}
|
|
18853
|
+
});
|
|
18854
|
+
}
|
|
18855
|
+
if (typeof GPUShaderStage === "undefined") {
|
|
18856
|
+
globalThis.GPUShaderStage = {
|
|
18857
|
+
VERTEX: 1,
|
|
18858
|
+
FRAGMENT: 2,
|
|
18859
|
+
COMPUTE: 4
|
|
18860
|
+
};
|
|
18861
|
+
}
|
|
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
|
+
};
|
|
19132
18919
|
|
|
19133
18920
|
// src/backends/webgpu/wgsl/pixel-scraping.wgsl.ts
|
|
19134
18921
|
var pixel_scraping_wgsl_default = `
|
|
@@ -19503,6 +19290,130 @@ function createPipelines(device, label) {
|
|
|
19503
19290
|
};
|
|
19504
19291
|
}
|
|
19505
19292
|
|
|
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
|
+
|
|
19506
19417
|
// src/textures/Bundles.ts
|
|
19507
19418
|
class Bundles {
|
|
19508
19419
|
#bundles = new Map;
|
|
@@ -19720,6 +19631,8 @@ class Bundles {
|
|
|
19720
19631
|
const rightCrop = frame.sourceSize.w - frame.spriteSourceSize.x - frame.spriteSourceSize.w;
|
|
19721
19632
|
const topCrop = frame.spriteSourceSize.y;
|
|
19722
19633
|
const bottomCrop = frame.sourceSize.h - frame.spriteSourceSize.y - frame.spriteSourceSize.h;
|
|
19634
|
+
const halfTexelX = 0.5 / atlasWidth;
|
|
19635
|
+
const halfTexelY = 0.5 / atlasHeight;
|
|
19723
19636
|
return {
|
|
19724
19637
|
cropOffset: {
|
|
19725
19638
|
x: leftCrop - rightCrop,
|
|
@@ -19730,16 +19643,16 @@ class Bundles {
|
|
|
19730
19643
|
height: frame.sourceSize.h
|
|
19731
19644
|
},
|
|
19732
19645
|
uvOffset: {
|
|
19733
|
-
x: frame.frame.x / atlasWidth,
|
|
19734
|
-
y: frame.frame.y / atlasHeight
|
|
19646
|
+
x: frame.frame.x / atlasWidth + halfTexelX,
|
|
19647
|
+
y: frame.frame.y / atlasHeight + halfTexelY
|
|
19735
19648
|
},
|
|
19736
19649
|
uvScale: {
|
|
19737
19650
|
width: frame.sourceSize.w / atlasWidth,
|
|
19738
19651
|
height: frame.sourceSize.h / atlasHeight
|
|
19739
19652
|
},
|
|
19740
19653
|
uvScaleCropped: {
|
|
19741
|
-
width: frame.frame.w / atlasWidth,
|
|
19742
|
-
height: frame.frame.h / atlasHeight
|
|
19654
|
+
width: (frame.frame.w - 1) / atlasWidth,
|
|
19655
|
+
height: (frame.frame.h - 1) / atlasHeight
|
|
19743
19656
|
}
|
|
19744
19657
|
};
|
|
19745
19658
|
}
|
|
@@ -19849,18 +19762,19 @@ async function packBitmapsToAtlas(images, textureSize, device) {
|
|
|
19849
19762
|
height: space.height - texture.height
|
|
19850
19763
|
});
|
|
19851
19764
|
}
|
|
19765
|
+
const halfTexel = 0.5 / textureSize;
|
|
19852
19766
|
atlasRegionMap.set(id, {
|
|
19853
19767
|
uvOffset: {
|
|
19854
|
-
x: space.x / textureSize,
|
|
19855
|
-
y: space.y / textureSize
|
|
19768
|
+
x: space.x / textureSize + halfTexel,
|
|
19769
|
+
y: space.y / textureSize + halfTexel
|
|
19856
19770
|
},
|
|
19857
19771
|
uvScale: {
|
|
19858
19772
|
width: originalSize.width / textureSize,
|
|
19859
19773
|
height: originalSize.height / textureSize
|
|
19860
19774
|
},
|
|
19861
19775
|
uvScaleCropped: {
|
|
19862
|
-
width: texture.width / textureSize,
|
|
19863
|
-
height: texture.height / textureSize
|
|
19776
|
+
width: (texture.width - 1) / textureSize,
|
|
19777
|
+
height: (texture.height - 1) / textureSize
|
|
19864
19778
|
},
|
|
19865
19779
|
cropOffset: offset,
|
|
19866
19780
|
originalSize
|
|
@@ -20174,13 +20088,13 @@ class AssetManager {
|
|
|
20174
20088
|
const webgpuBackend = this.#backend;
|
|
20175
20089
|
const device = webgpuBackend.device;
|
|
20176
20090
|
const presentationFormat = webgpuBackend.presentationFormat;
|
|
20177
|
-
const fontPipeline = await
|
|
20178
|
-
const textShader = new
|
|
20091
|
+
const fontPipeline = await FontPipeline2.create(device, font, presentationFormat, limits.maxTextLength);
|
|
20092
|
+
const textShader = new WebGPUTextShader2(webgpuBackend, fontPipeline, font, presentationFormat, limits.instanceCount);
|
|
20179
20093
|
this.#fonts.set(id, textShader);
|
|
20180
20094
|
} else {
|
|
20181
20095
|
const webglBackend = this.#backend;
|
|
20182
|
-
const fontPipeline =
|
|
20183
|
-
const textShader = new
|
|
20096
|
+
const fontPipeline = WebGLFontPipeline2.create(webglBackend.gl, font, limits.maxTextLength);
|
|
20097
|
+
const textShader = new WebGLTextShader2(webglBackend, fontPipeline);
|
|
20184
20098
|
this.#fonts.set(id, textShader);
|
|
20185
20099
|
}
|
|
20186
20100
|
return id;
|
|
@@ -20323,13 +20237,6 @@ class AssetManager {
|
|
|
20323
20237
|
}
|
|
20324
20238
|
}
|
|
20325
20239
|
|
|
20326
|
-
// src/utils/mod.ts
|
|
20327
|
-
var exports_mod2 = {};
|
|
20328
|
-
__export(exports_mod2, {
|
|
20329
|
-
assert: () => assert,
|
|
20330
|
-
Pool: () => Pool
|
|
20331
|
-
});
|
|
20332
|
-
|
|
20333
20240
|
// src/utils/pool.ts
|
|
20334
20241
|
class Pool {
|
|
20335
20242
|
#items = [];
|
|
@@ -20351,6 +20258,7 @@ class Pool {
|
|
|
20351
20258
|
this.#index = 0;
|
|
20352
20259
|
}
|
|
20353
20260
|
}
|
|
20261
|
+
|
|
20354
20262
|
// src/Toodle.ts
|
|
20355
20263
|
class Toodle {
|
|
20356
20264
|
assets;
|
|
@@ -20687,11 +20595,11 @@ class Toodle {
|
|
|
20687
20595
|
}
|
|
20688
20596
|
let backend;
|
|
20689
20597
|
if (backendType === "webgpu") {
|
|
20690
|
-
backend = await
|
|
20598
|
+
backend = await WebGPUBackend2.create(canvas, {
|
|
20691
20599
|
limits: options?.limits
|
|
20692
20600
|
});
|
|
20693
20601
|
} else {
|
|
20694
|
-
backend = await
|
|
20602
|
+
backend = await WebGLBackend2.create(canvas, {
|
|
20695
20603
|
limits: options?.limits
|
|
20696
20604
|
});
|
|
20697
20605
|
}
|
|
@@ -20709,204 +20617,20 @@ class Toodle {
|
|
|
20709
20617
|
return this.#backend;
|
|
20710
20618
|
}
|
|
20711
20619
|
}
|
|
20712
|
-
// src/colors/mod.ts
|
|
20713
|
-
var exports_mod3 = {};
|
|
20714
|
-
__export(exports_mod3, {
|
|
20715
|
-
web: () => web
|
|
20716
|
-
});
|
|
20717
|
-
var web = Object.freeze({
|
|
20718
|
-
aliceBlue: { r: 0.941176, g: 0.972549, b: 1, a: 1 },
|
|
20719
|
-
antiqueWhite: { r: 0.980392, g: 0.921569, b: 0.843137, a: 1 },
|
|
20720
|
-
aqua: { r: 0, g: 1, b: 1, a: 1 },
|
|
20721
|
-
aquamarine: { r: 0.498039, g: 1, b: 0.831373, a: 1 },
|
|
20722
|
-
azure: { r: 0.941176, g: 1, b: 1, a: 1 },
|
|
20723
|
-
beige: { r: 0.960784, g: 0.960784, b: 0.862745, a: 1 },
|
|
20724
|
-
bisque: { r: 1, g: 0.894118, b: 0.768627, a: 1 },
|
|
20725
|
-
black: { r: 0, g: 0, b: 0, a: 1 },
|
|
20726
|
-
blanchedAlmond: { r: 1, g: 0.921569, b: 0.803922, a: 1 },
|
|
20727
|
-
blue: { r: 0, g: 0, b: 1, a: 1 },
|
|
20728
|
-
blueViolet: { r: 0.541176, g: 0.168627, b: 0.886275, a: 1 },
|
|
20729
|
-
brown: { r: 0.647059, g: 0.164706, b: 0.164706, a: 1 },
|
|
20730
|
-
burlywood: { r: 0.870588, g: 0.721569, b: 0.529412, a: 1 },
|
|
20731
|
-
cadetBlue: { r: 0.372549, g: 0.619608, b: 0.627451, a: 1 },
|
|
20732
|
-
chartreuse: { r: 0.498039, g: 1, b: 0, a: 1 },
|
|
20733
|
-
chocolate: { r: 0.823529, g: 0.411765, b: 0.117647, a: 1 },
|
|
20734
|
-
coral: { r: 1, g: 0.498039, b: 0.313726, a: 1 },
|
|
20735
|
-
cornflowerBlue: { r: 0.392157, g: 0.584314, b: 0.929412, a: 1 },
|
|
20736
|
-
cornsilk: { r: 1, g: 0.972549, b: 0.862745, a: 1 },
|
|
20737
|
-
crimson: { r: 0.862745, g: 0.0784314, b: 0.235294, a: 1 },
|
|
20738
|
-
cyan: { r: 0, g: 1, b: 1, a: 1 },
|
|
20739
|
-
darkBlue: { r: 0, g: 0, b: 0.545098, a: 1 },
|
|
20740
|
-
darkCyan: { r: 0, g: 0.545098, b: 0.545098, a: 1 },
|
|
20741
|
-
darkGoldenrod: { r: 0.721569, g: 0.52549, b: 0.0431373, a: 1 },
|
|
20742
|
-
darkGray: { r: 0.662745, g: 0.662745, b: 0.662745, a: 1 },
|
|
20743
|
-
darkGreen: { r: 0, g: 0.392157, b: 0, a: 1 },
|
|
20744
|
-
darkKhaki: { r: 0.741176, g: 0.717647, b: 0.419608, a: 1 },
|
|
20745
|
-
darkMagenta: { r: 0.545098, g: 0, b: 0.545098, a: 1 },
|
|
20746
|
-
darkOliveGreen: { r: 0.333333, g: 0.419608, b: 0.184314, a: 1 },
|
|
20747
|
-
darkOrange: { r: 1, g: 0.54902, b: 0, a: 1 },
|
|
20748
|
-
darkOrchid: { r: 0.6, g: 0.196078, b: 0.8, a: 1 },
|
|
20749
|
-
darkRed: { r: 0.545098, g: 0, b: 0, a: 1 },
|
|
20750
|
-
darkSalmon: { r: 0.913725, g: 0.588235, b: 0.478431, a: 1 },
|
|
20751
|
-
darkSeaGreen: { r: 0.560784, g: 0.737255, b: 0.560784, a: 1 },
|
|
20752
|
-
darkSlateBlue: { r: 0.282353, g: 0.239216, b: 0.545098, a: 1 },
|
|
20753
|
-
darkSlateGray: { r: 0.184314, g: 0.309804, b: 0.309804, a: 1 },
|
|
20754
|
-
darkTurquoise: { r: 0, g: 0.807843, b: 0.819608, a: 1 },
|
|
20755
|
-
darkViolet: { r: 0.580392, g: 0, b: 0.827451, a: 1 },
|
|
20756
|
-
deepPink: { r: 1, g: 0.0784314, b: 0.576471, a: 1 },
|
|
20757
|
-
deepSkyBlue: { r: 0, g: 0.74902, b: 1, a: 1 },
|
|
20758
|
-
dimGray: { r: 0.411765, g: 0.411765, b: 0.411765, a: 1 },
|
|
20759
|
-
dodgerBlue: { r: 0.117647, g: 0.564706, b: 1, a: 1 },
|
|
20760
|
-
firebrick: { r: 0.698039, g: 0.133333, b: 0.133333, a: 1 },
|
|
20761
|
-
floralWhite: { r: 1, g: 0.980392, b: 0.941176, a: 1 },
|
|
20762
|
-
forestGreen: { r: 0.133333, g: 0.545098, b: 0.133333, a: 1 },
|
|
20763
|
-
fuchsia: { r: 1, g: 0, b: 1, a: 1 },
|
|
20764
|
-
gainsboro: { r: 0.862745, g: 0.862745, b: 0.862745, a: 1 },
|
|
20765
|
-
ghostWhite: { r: 0.972549, g: 0.972549, b: 1, a: 1 },
|
|
20766
|
-
gold: { r: 1, g: 0.843137, b: 0, a: 1 },
|
|
20767
|
-
goldenrod: { r: 0.854902, g: 0.647059, b: 0.12549, a: 1 },
|
|
20768
|
-
gray: { r: 0.745098, g: 0.745098, b: 0.745098, a: 1 },
|
|
20769
|
-
green: { r: 0, g: 1, b: 0, a: 1 },
|
|
20770
|
-
greenYellow: { r: 0.678431, g: 1, b: 0.184314, a: 1 },
|
|
20771
|
-
honeydew: { r: 0.941176, g: 1, b: 0.941176, a: 1 },
|
|
20772
|
-
hotPink: { r: 1, g: 0.411765, b: 0.705882, a: 1 },
|
|
20773
|
-
indigo: { r: 0.294118, g: 0, b: 0.509804, a: 1 },
|
|
20774
|
-
ivory: { r: 1, g: 1, b: 0.941176, a: 1 },
|
|
20775
|
-
khaki: { r: 0.941176, g: 0.901961, b: 0.54902, a: 1 },
|
|
20776
|
-
lavender: { r: 0.901961, g: 0.901961, b: 0.980392, a: 1 },
|
|
20777
|
-
lavenderBlush: { r: 1, g: 0.941176, b: 0.960784, a: 1 },
|
|
20778
|
-
lawnGreen: { r: 0.486275, g: 0.988235, b: 0, a: 1 },
|
|
20779
|
-
lemonChiffon: { r: 1, g: 0.980392, b: 0.803922, a: 1 },
|
|
20780
|
-
lightBlue: { r: 0.678431, g: 0.847059, b: 0.901961, a: 1 },
|
|
20781
|
-
lightCoral: { r: 0.941176, g: 0.501961, b: 0.501961, a: 1 },
|
|
20782
|
-
lightCyan: { r: 0.878431, g: 1, b: 1, a: 1 },
|
|
20783
|
-
lightGoldenrod: { r: 0.980392, g: 0.980392, b: 0.823529, a: 1 },
|
|
20784
|
-
lightGray: { r: 0.827451, g: 0.827451, b: 0.827451, a: 1 },
|
|
20785
|
-
lightGreen: { r: 0.564706, g: 0.933333, b: 0.564706, a: 1 },
|
|
20786
|
-
lightPink: { r: 1, g: 0.713726, b: 0.756863, a: 1 },
|
|
20787
|
-
lightSalmon: { r: 1, g: 0.627451, b: 0.478431, a: 1 },
|
|
20788
|
-
lightSeaGreen: { r: 0.12549, g: 0.698039, b: 0.666667, a: 1 },
|
|
20789
|
-
lightSkyBlue: { r: 0.529412, g: 0.807843, b: 0.980392, a: 1 },
|
|
20790
|
-
lightSlateGray: { r: 0.466667, g: 0.533333, b: 0.6, a: 1 },
|
|
20791
|
-
lightSteelBlue: { r: 0.690196, g: 0.768627, b: 0.870588, a: 1 },
|
|
20792
|
-
lightYellow: { r: 1, g: 1, b: 0.878431, a: 1 },
|
|
20793
|
-
lime: { r: 0, g: 1, b: 0, a: 1 },
|
|
20794
|
-
limeGreen: { r: 0.196078, g: 0.803922, b: 0.196078, a: 1 },
|
|
20795
|
-
linen: { r: 0.980392, g: 0.941176, b: 0.901961, a: 1 },
|
|
20796
|
-
magenta: { r: 1, g: 0, b: 1, a: 1 },
|
|
20797
|
-
maroon: { r: 0.690196, g: 0.188235, b: 0.376471, a: 1 },
|
|
20798
|
-
mediumAquamarine: { r: 0.4, g: 0.803922, b: 0.666667, a: 1 },
|
|
20799
|
-
mediumBlue: { r: 0, g: 0, b: 0.803922, a: 1 },
|
|
20800
|
-
mediumOrchid: { r: 0.729412, g: 0.333333, b: 0.827451, a: 1 },
|
|
20801
|
-
mediumPurple: { r: 0.576471, g: 0.439216, b: 0.858824, a: 1 },
|
|
20802
|
-
mediumSeaGreen: { r: 0.235294, g: 0.701961, b: 0.443137, a: 1 },
|
|
20803
|
-
mediumSlateBlue: { r: 0.482353, g: 0.407843, b: 0.933333, a: 1 },
|
|
20804
|
-
mediumSpringGreen: { r: 0, g: 0.980392, b: 0.603922, a: 1 },
|
|
20805
|
-
mediumTurquoise: { r: 0.282353, g: 0.819608, b: 0.8, a: 1 },
|
|
20806
|
-
mediumVioletRed: { r: 0.780392, g: 0.0823529, b: 0.521569, a: 1 },
|
|
20807
|
-
midnightBlue: { r: 0.0980392, g: 0.0980392, b: 0.439216, a: 1 },
|
|
20808
|
-
mintCream: { r: 0.960784, g: 1, b: 0.980392, a: 1 },
|
|
20809
|
-
mistyRose: { r: 1, g: 0.894118, b: 0.882353, a: 1 },
|
|
20810
|
-
moccasin: { r: 1, g: 0.894118, b: 0.709804, a: 1 },
|
|
20811
|
-
navyBlue: { r: 0, g: 0, b: 0.501961, a: 1 },
|
|
20812
|
-
oldLace: { r: 0.992157, g: 0.960784, b: 0.901961, a: 1 },
|
|
20813
|
-
olive: { r: 0.501961, g: 0.501961, b: 0, a: 1 },
|
|
20814
|
-
oliveDrab: { r: 0.419608, g: 0.556863, b: 0.137255, a: 1 },
|
|
20815
|
-
orange: { r: 1, g: 0.647059, b: 0, a: 1 },
|
|
20816
|
-
orangeRed: { r: 1, g: 0.270588, b: 0, a: 1 },
|
|
20817
|
-
orchid: { r: 0.854902, g: 0.439216, b: 0.839216, a: 1 },
|
|
20818
|
-
paleGoldenrod: { r: 0.933333, g: 0.909804, b: 0.666667, a: 1 },
|
|
20819
|
-
paleGreen: { r: 0.596078, g: 0.984314, b: 0.596078, a: 1 },
|
|
20820
|
-
paleTurquoise: { r: 0.686275, g: 0.933333, b: 0.933333, a: 1 },
|
|
20821
|
-
paleVioletRed: { r: 0.858824, g: 0.439216, b: 0.576471, a: 1 },
|
|
20822
|
-
papayaWhip: { r: 1, g: 0.937255, b: 0.835294, a: 1 },
|
|
20823
|
-
peachPuff: { r: 1, g: 0.854902, b: 0.72549, a: 1 },
|
|
20824
|
-
peru: { r: 0.803922, g: 0.521569, b: 0.247059, a: 1 },
|
|
20825
|
-
pink: { r: 1, g: 0.752941, b: 0.796078, a: 1 },
|
|
20826
|
-
plum: { r: 0.866667, g: 0.627451, b: 0.866667, a: 1 },
|
|
20827
|
-
powderBlue: { r: 0.690196, g: 0.878431, b: 0.901961, a: 1 },
|
|
20828
|
-
purple: { r: 0.627451, g: 0.12549, b: 0.941176, a: 1 },
|
|
20829
|
-
rebeccaPurple: { r: 0.4, g: 0.2, b: 0.6, a: 1 },
|
|
20830
|
-
red: { r: 1, g: 0, b: 0, a: 1 },
|
|
20831
|
-
rosyBrown: { r: 0.737255, g: 0.560784, b: 0.560784, a: 1 },
|
|
20832
|
-
royalBlue: { r: 0.254902, g: 0.411765, b: 0.882353, a: 1 },
|
|
20833
|
-
saddleBrown: { r: 0.545098, g: 0.270588, b: 0.0745098, a: 1 },
|
|
20834
|
-
salmon: { r: 0.980392, g: 0.501961, b: 0.447059, a: 1 },
|
|
20835
|
-
sandyBrown: { r: 0.956863, g: 0.643137, b: 0.376471, a: 1 },
|
|
20836
|
-
seaGreen: { r: 0.180392, g: 0.545098, b: 0.341176, a: 1 },
|
|
20837
|
-
seashell: { r: 1, g: 0.960784, b: 0.933333, a: 1 },
|
|
20838
|
-
sienna: { r: 0.627451, g: 0.321569, b: 0.176471, a: 1 },
|
|
20839
|
-
silver: { r: 0.752941, g: 0.752941, b: 0.752941, a: 1 },
|
|
20840
|
-
skyBlue: { r: 0.529412, g: 0.807843, b: 0.921569, a: 1 },
|
|
20841
|
-
slateBlue: { r: 0.415686, g: 0.352941, b: 0.803922, a: 1 },
|
|
20842
|
-
slateGray: { r: 0.439216, g: 0.501961, b: 0.564706, a: 1 },
|
|
20843
|
-
snow: { r: 1, g: 0.980392, b: 0.980392, a: 1 },
|
|
20844
|
-
springGreen: { r: 0, g: 1, b: 0.498039, a: 1 },
|
|
20845
|
-
steelBlue: { r: 0.27451, g: 0.509804, b: 0.705882, a: 1 },
|
|
20846
|
-
tan: { r: 0.823529, g: 0.705882, b: 0.54902, a: 1 },
|
|
20847
|
-
teal: { r: 0, g: 0.501961, b: 0.501961, a: 1 },
|
|
20848
|
-
thistle: { r: 0.847059, g: 0.74902, b: 0.847059, a: 1 },
|
|
20849
|
-
tomato: { r: 1, g: 0.388235, b: 0.278431, a: 1 },
|
|
20850
|
-
transparent: { r: 1, g: 1, b: 1, a: 0 },
|
|
20851
|
-
turquoise: { r: 0.25098, g: 0.878431, b: 0.815686, a: 1 },
|
|
20852
|
-
violet: { r: 0.933333, g: 0.509804, b: 0.933333, a: 1 },
|
|
20853
|
-
webGray: { r: 0.501961, g: 0.501961, b: 0.501961, a: 1 },
|
|
20854
|
-
webGreen: { r: 0, g: 0.501961, b: 0, a: 1 },
|
|
20855
|
-
webMaroon: { r: 0.501961, g: 0, b: 0, a: 1 },
|
|
20856
|
-
webPurple: { r: 0.501961, g: 0, b: 0.501961, a: 1 },
|
|
20857
|
-
wheat: { r: 0.960784, g: 0.870588, b: 0.701961, a: 1 },
|
|
20858
|
-
white: { r: 1, g: 1, b: 1, a: 1 },
|
|
20859
|
-
whiteSmoke: { r: 0.960784, g: 0.960784, b: 0.960784, a: 1 },
|
|
20860
|
-
yellow: { r: 1, g: 1, b: 0, a: 1 },
|
|
20861
|
-
yellowGreen: { r: 0.603922, g: 0.803922, b: 0.196078, a: 1 }
|
|
20862
|
-
});
|
|
20863
|
-
// src/math/mod.ts
|
|
20864
|
-
var exports_mod4 = {};
|
|
20865
|
-
__export(exports_mod4, {
|
|
20866
|
-
transformPoint: () => transformPoint,
|
|
20867
|
-
rad2deg: () => rad2deg,
|
|
20868
|
-
deg2rad: () => deg2rad,
|
|
20869
|
-
createViewMatrix: () => createViewMatrix,
|
|
20870
|
-
createProjectionMatrix: () => createProjectionMatrix,
|
|
20871
|
-
createModelMatrix: () => createModelMatrix,
|
|
20872
|
-
convertWorldToScreen: () => convertWorldToScreen,
|
|
20873
|
-
convertScreenToWorld: () => convertScreenToWorld
|
|
20874
|
-
});
|
|
20875
|
-
// src/scene/mod.ts
|
|
20876
|
-
var exports_mod5 = {};
|
|
20877
|
-
__export(exports_mod5, {
|
|
20878
|
-
TextNode: () => TextNode,
|
|
20879
|
-
SceneNode: () => SceneNode,
|
|
20880
|
-
QuadNode: () => QuadNode,
|
|
20881
|
-
DEFAULT_FONT_SIZE: () => DEFAULT_FONT_SIZE,
|
|
20882
|
-
Camera: () => Camera
|
|
20883
|
-
});
|
|
20884
|
-
// src/screen/mod.ts
|
|
20885
|
-
var exports_mod6 = {};
|
|
20886
|
-
// src/text/mod.ts
|
|
20887
|
-
var exports_mod7 = {};
|
|
20888
|
-
__export(exports_mod7, {
|
|
20889
|
-
TextShader: () => WebGPUTextShader
|
|
20890
|
-
});
|
|
20891
|
-
// src/textures/mod.ts
|
|
20892
|
-
var exports_mod8 = {};
|
|
20893
|
-
__export(exports_mod8, {
|
|
20894
|
-
Bundles: () => Bundles
|
|
20895
|
-
});
|
|
20896
20620
|
export {
|
|
20897
|
-
|
|
20621
|
+
Utils,
|
|
20898
20622
|
Toodle,
|
|
20899
|
-
|
|
20900
|
-
|
|
20901
|
-
|
|
20902
|
-
|
|
20903
|
-
|
|
20623
|
+
Textures,
|
|
20624
|
+
Text,
|
|
20625
|
+
Screen,
|
|
20626
|
+
Scene,
|
|
20627
|
+
GfxMath,
|
|
20904
20628
|
DEFAULT_LIMITS,
|
|
20905
|
-
|
|
20906
|
-
Bundles,
|
|
20907
|
-
|
|
20908
|
-
AssetManager
|
|
20629
|
+
Colors,
|
|
20630
|
+
Bundles2 as Bundles,
|
|
20631
|
+
Backends,
|
|
20632
|
+
AssetManager2 as AssetManager
|
|
20909
20633
|
};
|
|
20910
20634
|
|
|
20911
|
-
//# debugId=
|
|
20635
|
+
//# debugId=F5D1111C12CFF04864756E2164756E21
|
|
20912
20636
|
//# sourceMappingURL=mod.js.map
|