three-text 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE_THIRD_PARTY +15 -0
- package/README.md +73 -42
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/index.min.cjs +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.min.js +1 -1
- package/dist/three/react.d.ts +2 -0
- package/dist/types/core/types.d.ts +2 -33
- package/dist/types/vector/{core.d.ts → core/index.d.ts} +8 -7
- package/dist/types/vector/index.d.ts +22 -25
- package/dist/types/vector/react.d.ts +4 -3
- package/dist/types/vector/slug/SlugPacker.d.ts +2 -0
- package/dist/types/vector/slug/curveUtils.d.ts +6 -0
- package/dist/types/vector/slug/index.d.ts +8 -0
- package/dist/types/vector/slug/shaderStrings.d.ts +4 -0
- package/dist/types/vector/slug/slugGLSL.d.ts +21 -0
- package/dist/types/vector/slug/slugTSL.d.ts +13 -0
- package/dist/types/vector/slug/types.d.ts +30 -0
- package/dist/types/vector/slug/unpackVertices.d.ts +11 -0
- package/dist/types/vector/webgl/index.d.ts +7 -3
- package/dist/types/vector/webgpu/index.d.ts +4 -4
- package/dist/vector/core/index.cjs +856 -0
- package/dist/vector/core/index.d.ts +63 -0
- package/dist/vector/core/index.js +854 -0
- package/dist/vector/core.cjs +4419 -240
- package/dist/vector/core.d.ts +361 -71
- package/dist/vector/core.js +4406 -226
- package/dist/vector/index.cjs +5 -229
- package/dist/vector/index.d.ts +45 -396
- package/dist/vector/index.js +3 -223
- package/dist/vector/index2.cjs +287 -0
- package/dist/vector/index2.js +264 -0
- package/dist/vector/react.cjs +37 -8
- package/dist/vector/react.d.ts +6 -3
- package/dist/vector/react.js +18 -8
- package/dist/vector/slugTSL.cjs +252 -0
- package/dist/vector/slugTSL.js +231 -0
- package/dist/vector/webgl/index.cjs +131 -201
- package/dist/vector/webgl/index.d.ts +19 -44
- package/dist/vector/webgl/index.js +131 -201
- package/dist/vector/webgpu/index.cjs +100 -283
- package/dist/vector/webgpu/index.d.ts +16 -45
- package/dist/vector/webgpu/index.js +100 -283
- package/package.json +6 -1
- package/dist/types/vector/GlyphVectorGeometryBuilder.d.ts +0 -26
- package/dist/types/vector/LoopBlinnGeometry.d.ts +0 -68
- package/dist/types/vector/loopBlinnTSL.d.ts +0 -22
- package/dist/vector/loopBlinnTSL.cjs +0 -229
- package/dist/vector/loopBlinnTSL.js +0 -207
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('./core/index.cjs');
|
|
4
|
+
var THREE = require('three');
|
|
5
|
+
|
|
6
|
+
function _interopNamespaceDefault(e) {
|
|
7
|
+
var n = Object.create(null);
|
|
8
|
+
if (e) {
|
|
9
|
+
Object.keys(e).forEach(function (k) {
|
|
10
|
+
if (k !== 'default') {
|
|
11
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
12
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () { return e[k]; }
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
n.default = e;
|
|
20
|
+
return Object.freeze(n);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
|
|
24
|
+
|
|
25
|
+
// Restructures the tightly-packed vertex buffer from SlugPacker into
|
|
26
|
+
// separate per-attribute arrays for GPU attribute binding
|
|
27
|
+
const FLOATS_PER_VERT = 20;
|
|
28
|
+
function unpackSlugVertices(gpuData) {
|
|
29
|
+
const vertCount = gpuData.shapeCount * 4;
|
|
30
|
+
const positions = new Float32Array(vertCount * 3);
|
|
31
|
+
const texcoords = new Float32Array(vertCount * 2);
|
|
32
|
+
const bandings = new Float32Array(vertCount * 4);
|
|
33
|
+
const glyphData = new Float32Array(vertCount * 4);
|
|
34
|
+
const colors = new Float32Array(vertCount * 4);
|
|
35
|
+
const srcF = gpuData.vertices;
|
|
36
|
+
const srcU = new Uint32Array(srcF.buffer, srcF.byteOffset, srcF.length);
|
|
37
|
+
for (let i = 0; i < vertCount; i++) {
|
|
38
|
+
const s = i * FLOATS_PER_VERT;
|
|
39
|
+
positions[i * 3] = srcF[s];
|
|
40
|
+
positions[i * 3 + 1] = srcF[s + 1];
|
|
41
|
+
positions[i * 3 + 2] = 0;
|
|
42
|
+
texcoords[i * 2] = srcF[s + 4];
|
|
43
|
+
texcoords[i * 2 + 1] = srcF[s + 5];
|
|
44
|
+
bandings[i * 4] = srcF[s + 12];
|
|
45
|
+
bandings[i * 4 + 1] = srcF[s + 13];
|
|
46
|
+
bandings[i * 4 + 2] = srcF[s + 14];
|
|
47
|
+
bandings[i * 4 + 3] = srcF[s + 15];
|
|
48
|
+
// Unpack glyph location and band metadata from bit-packed fields
|
|
49
|
+
const g0 = srcU[s + 6];
|
|
50
|
+
const g1 = srcU[s + 7];
|
|
51
|
+
glyphData[i * 4] = g0 & 0xFFFF;
|
|
52
|
+
glyphData[i * 4 + 1] = (g0 >>> 16) & 0xFFFF;
|
|
53
|
+
glyphData[i * 4 + 2] = g1 & 0xFFFF;
|
|
54
|
+
glyphData[i * 4 + 3] = (g1 >>> 16) & 0xFFFF;
|
|
55
|
+
colors[i * 4] = srcF[s + 16];
|
|
56
|
+
colors[i * 4 + 1] = srcF[s + 17];
|
|
57
|
+
colors[i * 4 + 2] = srcF[s + 18];
|
|
58
|
+
colors[i * 4 + 3] = srcF[s + 19];
|
|
59
|
+
}
|
|
60
|
+
// Per-glyph center: average of quad corner positions
|
|
61
|
+
const glyphCenters = new Float32Array(vertCount * 3);
|
|
62
|
+
const glyphIndices = new Float32Array(vertCount);
|
|
63
|
+
for (let g = 0; g < gpuData.shapeCount; g++) {
|
|
64
|
+
const base = g * 4;
|
|
65
|
+
let cx = 0, cy = 0;
|
|
66
|
+
for (let v = 0; v < 4; v++) {
|
|
67
|
+
cx += positions[(base + v) * 3];
|
|
68
|
+
cy += positions[(base + v) * 3 + 1];
|
|
69
|
+
}
|
|
70
|
+
cx *= 0.25;
|
|
71
|
+
cy *= 0.25;
|
|
72
|
+
for (let v = 0; v < 4; v++) {
|
|
73
|
+
const idx = base + v;
|
|
74
|
+
glyphCenters[idx * 3] = cx;
|
|
75
|
+
glyphCenters[idx * 3 + 1] = cy;
|
|
76
|
+
glyphCenters[idx * 3 + 2] = 0;
|
|
77
|
+
glyphIndices[idx] = g;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { positions, texcoords, bandings, glyphData, colors, glyphCenters, glyphIndices };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
var fragGLSL = "#version 300 es\n// GLSL 300 es port of the Slug fragment shader\n// Eric Lengyel, MIT License, 2017\n\nprecision highp float;\nprecision highp int;\n\n#define kLogBandTextureWidth 12\n\nin vec4 v_color;\nin vec2 v_texcoord;\nflat in vec4 v_banding;\nflat in ivec4 v_glyph;\n\nuniform sampler2D curveTexture; // RGBA32F control points\nuniform highp usampler2D bandTexture; // RGBA32UI band data\n\nlayout(location = 0) out vec4 outColor;\n\nuint CalcRootCode(float y1, float y2, float y3) {\n uint i1 = floatBitsToUint(y1) >> 31u;\n uint i2 = floatBitsToUint(y2) >> 30u;\n uint i3 = floatBitsToUint(y3) >> 29u;\n\n uint shift = (i2 & 2u) | (i1 & ~2u);\n shift = (i3 & 4u) | (shift & ~4u);\n\n return (0x2E74u >> shift) & 0x0101u;\n}\n\nvec2 SolveHorizPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.y;\n float rb = 0.5 / b.y;\n\n float d = sqrt(max(b.y * b.y - a.y * p12.y, 0.0));\n float t1 = (b.y - d) * ra;\n float t2 = (b.y + d) * ra;\n\n if (abs(a.y) < 1.0 / 65536.0) t1 = t2 = p12.y * rb;\n\n return vec2((a.x * t1 - b.x * 2.0) * t1 + p12.x, (a.x * t2 - b.x * 2.0) * t2 + p12.x);\n}\n\nvec2 SolveVertPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.x;\n float rb = 0.5 / b.x;\n\n float d = sqrt(max(b.x * b.x - a.x * p12.x, 0.0));\n float t1 = (b.x - d) * ra;\n float t2 = (b.x + d) * ra;\n\n if (abs(a.x) < 1.0 / 65536.0) t1 = t2 = p12.x * rb;\n\n return vec2((a.y * t1 - b.y * 2.0) * t1 + p12.y, (a.y * t2 - b.y * 2.0) * t2 + p12.y);\n}\n\nivec2 CalcBandLoc(ivec2 glyphLoc, uint offset) {\n ivec2 bandLoc = ivec2(glyphLoc.x + int(offset), glyphLoc.y);\n bandLoc.y += bandLoc.x >> kLogBandTextureWidth;\n bandLoc.x &= (1 << kLogBandTextureWidth) - 1;\n return bandLoc;\n}\n\nfloat CalcCoverage(float xcov, float ycov, float xwgt, float ywgt, int flags) {\n float coverage = max(abs(xcov * xwgt + ycov * ywgt) / max(xwgt + ywgt, 1.0 / 65536.0), min(abs(xcov), abs(ycov)));\n\n#if defined(SLUG_EVENODD)\n if ((flags & 0x1000) == 0) {\n#endif\n coverage = clamp(coverage, 0.0, 1.0);\n#if defined(SLUG_EVENODD)\n } else {\n coverage = 1.0 - abs(1.0 - fract(coverage * 0.5) * 2.0);\n }\n#endif\n\n#if defined(SLUG_WEIGHT)\n coverage = sqrt(coverage);\n#endif\n\n return coverage;\n}\n\nfloat SlugRenderSingle(vec2 renderCoord, vec2 emsPerPixel, vec4 bandTransform, ivec4 glyphData) {\n int curveIndex;\n\n vec2 pixelsPerEm = 1.0 / emsPerPixel;\n\n ivec2 bandMax = glyphData.zw;\n bandMax.y &= 0x00FF;\n\n ivec2 bandIndex = clamp(ivec2(renderCoord * bandTransform.xy + bandTransform.zw), ivec2(0, 0), bandMax);\n ivec2 glyphLoc = glyphData.xy;\n\n float xcov = 0.0;\n float xwgt = 0.0;\n\n uvec2 hbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandIndex.y, glyphLoc.y), 0).xy;\n ivec2 hbandLoc = CalcBandLoc(glyphLoc, hbandData.y);\n\n for (curveIndex = 0; curveIndex < int(hbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(hbandLoc.x + curveIndex, hbandLoc.y), 0).xy);\n\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) break;\n\n uint code = CalcRootCode(p12.y, p12.w, p3.y);\n if (code != 0u) {\n vec2 r = SolveHorizPoly(p12, p3) * pixelsPerEm.x;\n\n if ((code & 1u) != 0u) {\n xcov += clamp(r.x + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n xcov -= clamp(r.y + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n float ycov = 0.0;\n float ywgt = 0.0;\n\n uvec2 vbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandMax.y + 1 + bandIndex.x, glyphLoc.y), 0).xy;\n ivec2 vbandLoc = CalcBandLoc(glyphLoc, vbandData.y);\n\n for (curveIndex = 0; curveIndex < int(vbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(vbandLoc.x + curveIndex, vbandLoc.y), 0).xy);\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) break;\n\n uint code = CalcRootCode(p12.x, p12.z, p3.x);\n if (code != 0u) {\n vec2 r = SolveVertPoly(p12, p3) * pixelsPerEm.y;\n\n if ((code & 1u) != 0u) {\n ycov -= clamp(r.x + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n ycov += clamp(r.y + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n return CalcCoverage(xcov, ycov, xwgt, ywgt, glyphData.w);\n}\n\nfloat SlugRender(vec2 renderCoord, vec4 bandTransform, ivec4 glyphData) {\n vec2 emsPerPixel = fwidth(renderCoord);\n\n#if defined(SLUG_ADAPTIVE_SUPERSAMPLE)\n // Per-pixel rotated RGSS-4. The base RGSS offsets are rotated by a\n // unique angle per fragment (interleaved gradient noise). This converts\n // structured aliasing shimmer into uncorrelated grain that the eye\n // naturally filters out, much closer to how hardware MSAA on many small\n // triangles behaves perceptually.\n float noise = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));\n float angle = noise * 6.2831853;\n float ca = cos(angle), sa = sin(angle);\n\n // Base RGSS offsets rotated by per-pixel angle\n vec2 o0 = vec2(ca * -0.375 - sa * 0.125, sa * -0.375 + ca * 0.125) * emsPerPixel;\n vec2 o1 = vec2(ca * 0.125 - sa * 0.375, sa * 0.125 + ca * 0.375) * emsPerPixel;\n vec2 o2 = vec2(ca * 0.375 - sa * -0.125, sa * 0.375 + ca * -0.125) * emsPerPixel;\n vec2 o3 = vec2(ca * -0.125 - sa * -0.375, sa * -0.125 + ca * -0.375) * emsPerPixel;\n\n float coverage =\n SlugRenderSingle(renderCoord + o0, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o1, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o2, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o3, emsPerPixel, bandTransform, glyphData);\n return coverage * 0.25;\n#else\n return SlugRenderSingle(renderCoord, emsPerPixel, bandTransform, glyphData);\n#endif\n}\n\nvoid main() {\n float coverage = SlugRender(v_texcoord, v_banding, v_glyph);\n outColor = v_color * coverage;\n}\n";
|
|
84
|
+
|
|
85
|
+
// Slug shader source re-exports
|
|
86
|
+
// The .glsl/.wgsl files are the single source of truth, imported as strings
|
|
87
|
+
// at build time via the glslPlugin in rollup.config.js
|
|
88
|
+
// @ts-ignore - resolved by rollup glslPlugin
|
|
89
|
+
const fragmentShaderGLSL300 = fragGLSL;
|
|
90
|
+
|
|
91
|
+
// Slug GLSL adapter for Three.js, using RawShaderMaterial with the
|
|
92
|
+
// reference GLSL shaders. Works with both WebGLRenderer and
|
|
93
|
+
// WebGPURenderer (via GLSL-to-WGSL transpilation)
|
|
94
|
+
//
|
|
95
|
+
// Compared to the TSL adapter (slugTSL.ts):
|
|
96
|
+
// - Works with any Three.js renderer (no node-material dependency)
|
|
97
|
+
// - Uses native Uint32 band texture (no float conversion)
|
|
98
|
+
// - Supports GLSL animation injection via animationDeclarations/animationBody
|
|
99
|
+
// - Same tradeoff: no vertex dilation (may cause sub-pixel edge clipping at extreme zoom)
|
|
100
|
+
//
|
|
101
|
+
// Requires peer dependency: three
|
|
102
|
+
// @ts-ignore - three is a peer dependency
|
|
103
|
+
// Three.js GLSL3 mode prepends #version 300 es, so strip it from the raw shader
|
|
104
|
+
const fragShader = fragmentShaderGLSL300.replace(/^#version\s+300\s+es\s*\n/, '');
|
|
105
|
+
function buildVertexShader(options) {
|
|
106
|
+
const decl = options?.animationDeclarations ?? '';
|
|
107
|
+
const body = options?.animationBody ?? 'vec3 outPos = position;';
|
|
108
|
+
return `precision highp float;
|
|
109
|
+
precision highp int;
|
|
110
|
+
|
|
111
|
+
in vec3 position;
|
|
112
|
+
in vec2 slugTexcoord;
|
|
113
|
+
in vec4 slugBanding;
|
|
114
|
+
in vec4 slugGlyph;
|
|
115
|
+
in vec4 slugColor;
|
|
116
|
+
in vec3 glyphCenter;
|
|
117
|
+
in float glyphIndex;
|
|
118
|
+
|
|
119
|
+
uniform mat4 modelViewMatrix;
|
|
120
|
+
uniform mat4 projectionMatrix;
|
|
121
|
+
uniform float time;
|
|
122
|
+
${decl}
|
|
123
|
+
|
|
124
|
+
out vec2 v_texcoord;
|
|
125
|
+
flat out vec4 v_banding;
|
|
126
|
+
flat out ivec4 v_glyph;
|
|
127
|
+
out vec4 v_color;
|
|
128
|
+
|
|
129
|
+
void main() {
|
|
130
|
+
v_texcoord = slugTexcoord;
|
|
131
|
+
v_banding = slugBanding;
|
|
132
|
+
v_glyph = ivec4(slugGlyph);
|
|
133
|
+
v_color = slugColor;
|
|
134
|
+
|
|
135
|
+
${body}
|
|
136
|
+
|
|
137
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(outPos, 1.0);
|
|
138
|
+
}
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
141
|
+
function createSlugGLSLMesh(gpuData, options) {
|
|
142
|
+
const attrs = unpackSlugVertices(gpuData);
|
|
143
|
+
const geo = new THREE__namespace.BufferGeometry();
|
|
144
|
+
geo.setAttribute('position', new THREE__namespace.Float32BufferAttribute(attrs.positions, 3));
|
|
145
|
+
geo.setAttribute('slugTexcoord', new THREE__namespace.Float32BufferAttribute(attrs.texcoords, 2));
|
|
146
|
+
geo.setAttribute('slugBanding', new THREE__namespace.Float32BufferAttribute(attrs.bandings, 4));
|
|
147
|
+
geo.setAttribute('slugGlyph', new THREE__namespace.Float32BufferAttribute(attrs.glyphData, 4));
|
|
148
|
+
geo.setAttribute('slugColor', new THREE__namespace.Float32BufferAttribute(attrs.colors, 4));
|
|
149
|
+
geo.setAttribute('glyphCenter', new THREE__namespace.Float32BufferAttribute(attrs.glyphCenters, 3));
|
|
150
|
+
geo.setAttribute('glyphIndex', new THREE__namespace.Float32BufferAttribute(attrs.glyphIndices, 1));
|
|
151
|
+
geo.setIndex(new THREE__namespace.BufferAttribute(gpuData.indices, 1));
|
|
152
|
+
// Curve texture: RGBA32F
|
|
153
|
+
const curveTex = new THREE__namespace.DataTexture(gpuData.curveTexture.data, gpuData.curveTexture.width, gpuData.curveTexture.height, THREE__namespace.RGBAFormat, THREE__namespace.FloatType);
|
|
154
|
+
curveTex.minFilter = THREE__namespace.NearestFilter;
|
|
155
|
+
curveTex.magFilter = THREE__namespace.NearestFilter;
|
|
156
|
+
curveTex.generateMipmaps = false;
|
|
157
|
+
curveTex.needsUpdate = true;
|
|
158
|
+
// Band texture: native RGBA32UI (no float conversion needed)
|
|
159
|
+
const bandTex = new THREE__namespace.DataTexture(gpuData.bandTexture.data, gpuData.bandTexture.width, gpuData.bandTexture.height);
|
|
160
|
+
bandTex.format = THREE__namespace.RGBAIntegerFormat;
|
|
161
|
+
bandTex.type = THREE__namespace.UnsignedIntType;
|
|
162
|
+
bandTex.internalFormat = 'RGBA32UI';
|
|
163
|
+
bandTex.minFilter = THREE__namespace.NearestFilter;
|
|
164
|
+
bandTex.magFilter = THREE__namespace.NearestFilter;
|
|
165
|
+
bandTex.generateMipmaps = false;
|
|
166
|
+
bandTex.needsUpdate = true;
|
|
167
|
+
const col = options?.color ?? { r: 1, g: 1, b: 1 };
|
|
168
|
+
const colorUniform = new THREE__namespace.Vector4(col.r, col.g, col.b, 1.0);
|
|
169
|
+
const uniforms = {
|
|
170
|
+
curveTexture: { value: curveTex },
|
|
171
|
+
bandTexture: { value: bandTex },
|
|
172
|
+
time: { value: 0 },
|
|
173
|
+
u_color: { value: colorUniform },
|
|
174
|
+
...options?.uniforms,
|
|
175
|
+
};
|
|
176
|
+
const frag = options?.adaptiveSupersampling
|
|
177
|
+
? '#define SLUG_ADAPTIVE_SUPERSAMPLE\n' + fragShader
|
|
178
|
+
: fragShader;
|
|
179
|
+
const material = new THREE__namespace.RawShaderMaterial({
|
|
180
|
+
glslVersion: THREE__namespace.GLSL3,
|
|
181
|
+
uniforms,
|
|
182
|
+
vertexShader: buildVertexShader(options),
|
|
183
|
+
fragmentShader: frag,
|
|
184
|
+
transparent: true,
|
|
185
|
+
depthWrite: false,
|
|
186
|
+
side: THREE__namespace.DoubleSide,
|
|
187
|
+
});
|
|
188
|
+
const mesh = new THREE__namespace.Mesh(geo, material);
|
|
189
|
+
return {
|
|
190
|
+
mesh,
|
|
191
|
+
uniforms,
|
|
192
|
+
setOffset(x, y, z = 0) {
|
|
193
|
+
mesh.position.set(x, y, z);
|
|
194
|
+
},
|
|
195
|
+
setColor(r, g, b) {
|
|
196
|
+
colorUniform.set(r, g, b, 1.0);
|
|
197
|
+
},
|
|
198
|
+
dispose() {
|
|
199
|
+
geo.dispose();
|
|
200
|
+
material.dispose();
|
|
201
|
+
curveTex.dispose();
|
|
202
|
+
bandTex.dispose();
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function parseColor(color) {
|
|
208
|
+
if (!color)
|
|
209
|
+
return { r: 1, g: 1, b: 1 };
|
|
210
|
+
if (Array.isArray(color))
|
|
211
|
+
return { r: color[0], g: color[1], b: color[2] };
|
|
212
|
+
if (color.default)
|
|
213
|
+
return { r: color.default[0], g: color.default[1], b: color.default[2] };
|
|
214
|
+
return { r: 1, g: 1, b: 1 };
|
|
215
|
+
}
|
|
216
|
+
async function createMesh(gpuData, color, adaptiveSupersampling) {
|
|
217
|
+
try {
|
|
218
|
+
const { createSlugTSLMesh } = await Promise.resolve().then(function () { return require('./slugTSL.cjs'); });
|
|
219
|
+
return createSlugTSLMesh(gpuData, color);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return createSlugGLSLMesh(gpuData, { color, adaptiveSupersampling });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async function wrapCoreResult(coreResult, options) {
|
|
226
|
+
const color = parseColor(options.color);
|
|
227
|
+
const slugMesh = await createMesh(coreResult.gpuData, color, options.adaptiveSupersampling);
|
|
228
|
+
const geo = slugMesh.mesh.geometry;
|
|
229
|
+
geo.computeBoundingBox();
|
|
230
|
+
const center = new THREE__namespace.Vector3();
|
|
231
|
+
geo.boundingBox.getCenter(center);
|
|
232
|
+
geo.translate(-center.x, -center.y, -center.z);
|
|
233
|
+
const gcAttr = geo.getAttribute('glyphCenter');
|
|
234
|
+
if (gcAttr) {
|
|
235
|
+
const arr = gcAttr.array;
|
|
236
|
+
for (let i = 0; i < arr.length; i += 3) {
|
|
237
|
+
arr[i] -= center.x;
|
|
238
|
+
arr[i + 1] -= center.y;
|
|
239
|
+
arr[i + 2] -= center.z;
|
|
240
|
+
}
|
|
241
|
+
gcAttr.needsUpdate = true;
|
|
242
|
+
}
|
|
243
|
+
const group = new THREE__namespace.Group();
|
|
244
|
+
group.add(slugMesh.mesh);
|
|
245
|
+
return {
|
|
246
|
+
group,
|
|
247
|
+
mesh: slugMesh.mesh,
|
|
248
|
+
gpuData: coreResult.gpuData,
|
|
249
|
+
glyphs: coreResult.glyphs,
|
|
250
|
+
planeBounds: coreResult.planeBounds,
|
|
251
|
+
query: (queryOptions) => coreResult.query(queryOptions),
|
|
252
|
+
getLoadedFont: () => coreResult.getLoadedFont(),
|
|
253
|
+
measureTextWidth: (text, letterSpacing) => coreResult.measureTextWidth(text, letterSpacing),
|
|
254
|
+
update: async (newOptions) => {
|
|
255
|
+
const mergedOptions = { ...options };
|
|
256
|
+
for (const key in newOptions) {
|
|
257
|
+
const value = newOptions[key];
|
|
258
|
+
if (value !== undefined) {
|
|
259
|
+
mergedOptions[key] = value;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const newCore = await coreResult.update(newOptions);
|
|
263
|
+
return wrapCoreResult(newCore, mergedOptions);
|
|
264
|
+
},
|
|
265
|
+
dispose: () => {
|
|
266
|
+
slugMesh.dispose();
|
|
267
|
+
coreResult.dispose();
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
class Text {
|
|
272
|
+
static { this.setHarfBuzzPath = core.Text.setHarfBuzzPath; }
|
|
273
|
+
static { this.setHarfBuzzBuffer = core.Text.setHarfBuzzBuffer; }
|
|
274
|
+
static { this.init = core.Text.init; }
|
|
275
|
+
static { this.registerPattern = core.Text.registerPattern; }
|
|
276
|
+
static { this.preloadPatterns = core.Text.preloadPatterns; }
|
|
277
|
+
static { this.setMaxFontCacheMemoryMB = core.Text.setMaxFontCacheMemoryMB; }
|
|
278
|
+
static { this.enableWoff2 = core.Text.enableWoff2; }
|
|
279
|
+
static async create(options) {
|
|
280
|
+
const coreResult = await core.Text.create(options);
|
|
281
|
+
return wrapCoreResult(coreResult, options);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
exports.Text = Text;
|
|
286
|
+
exports.createSlugGLSLMesh = createSlugGLSLMesh;
|
|
287
|
+
exports.unpackSlugVertices = unpackSlugVertices;
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { Text as Text$1 } from './core/index.js';
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
|
|
4
|
+
// Restructures the tightly-packed vertex buffer from SlugPacker into
|
|
5
|
+
// separate per-attribute arrays for GPU attribute binding
|
|
6
|
+
const FLOATS_PER_VERT = 20;
|
|
7
|
+
function unpackSlugVertices(gpuData) {
|
|
8
|
+
const vertCount = gpuData.shapeCount * 4;
|
|
9
|
+
const positions = new Float32Array(vertCount * 3);
|
|
10
|
+
const texcoords = new Float32Array(vertCount * 2);
|
|
11
|
+
const bandings = new Float32Array(vertCount * 4);
|
|
12
|
+
const glyphData = new Float32Array(vertCount * 4);
|
|
13
|
+
const colors = new Float32Array(vertCount * 4);
|
|
14
|
+
const srcF = gpuData.vertices;
|
|
15
|
+
const srcU = new Uint32Array(srcF.buffer, srcF.byteOffset, srcF.length);
|
|
16
|
+
for (let i = 0; i < vertCount; i++) {
|
|
17
|
+
const s = i * FLOATS_PER_VERT;
|
|
18
|
+
positions[i * 3] = srcF[s];
|
|
19
|
+
positions[i * 3 + 1] = srcF[s + 1];
|
|
20
|
+
positions[i * 3 + 2] = 0;
|
|
21
|
+
texcoords[i * 2] = srcF[s + 4];
|
|
22
|
+
texcoords[i * 2 + 1] = srcF[s + 5];
|
|
23
|
+
bandings[i * 4] = srcF[s + 12];
|
|
24
|
+
bandings[i * 4 + 1] = srcF[s + 13];
|
|
25
|
+
bandings[i * 4 + 2] = srcF[s + 14];
|
|
26
|
+
bandings[i * 4 + 3] = srcF[s + 15];
|
|
27
|
+
// Unpack glyph location and band metadata from bit-packed fields
|
|
28
|
+
const g0 = srcU[s + 6];
|
|
29
|
+
const g1 = srcU[s + 7];
|
|
30
|
+
glyphData[i * 4] = g0 & 0xFFFF;
|
|
31
|
+
glyphData[i * 4 + 1] = (g0 >>> 16) & 0xFFFF;
|
|
32
|
+
glyphData[i * 4 + 2] = g1 & 0xFFFF;
|
|
33
|
+
glyphData[i * 4 + 3] = (g1 >>> 16) & 0xFFFF;
|
|
34
|
+
colors[i * 4] = srcF[s + 16];
|
|
35
|
+
colors[i * 4 + 1] = srcF[s + 17];
|
|
36
|
+
colors[i * 4 + 2] = srcF[s + 18];
|
|
37
|
+
colors[i * 4 + 3] = srcF[s + 19];
|
|
38
|
+
}
|
|
39
|
+
// Per-glyph center: average of quad corner positions
|
|
40
|
+
const glyphCenters = new Float32Array(vertCount * 3);
|
|
41
|
+
const glyphIndices = new Float32Array(vertCount);
|
|
42
|
+
for (let g = 0; g < gpuData.shapeCount; g++) {
|
|
43
|
+
const base = g * 4;
|
|
44
|
+
let cx = 0, cy = 0;
|
|
45
|
+
for (let v = 0; v < 4; v++) {
|
|
46
|
+
cx += positions[(base + v) * 3];
|
|
47
|
+
cy += positions[(base + v) * 3 + 1];
|
|
48
|
+
}
|
|
49
|
+
cx *= 0.25;
|
|
50
|
+
cy *= 0.25;
|
|
51
|
+
for (let v = 0; v < 4; v++) {
|
|
52
|
+
const idx = base + v;
|
|
53
|
+
glyphCenters[idx * 3] = cx;
|
|
54
|
+
glyphCenters[idx * 3 + 1] = cy;
|
|
55
|
+
glyphCenters[idx * 3 + 2] = 0;
|
|
56
|
+
glyphIndices[idx] = g;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { positions, texcoords, bandings, glyphData, colors, glyphCenters, glyphIndices };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
var fragGLSL = "#version 300 es\n// GLSL 300 es port of the Slug fragment shader\n// Eric Lengyel, MIT License, 2017\n\nprecision highp float;\nprecision highp int;\n\n#define kLogBandTextureWidth 12\n\nin vec4 v_color;\nin vec2 v_texcoord;\nflat in vec4 v_banding;\nflat in ivec4 v_glyph;\n\nuniform sampler2D curveTexture; // RGBA32F control points\nuniform highp usampler2D bandTexture; // RGBA32UI band data\n\nlayout(location = 0) out vec4 outColor;\n\nuint CalcRootCode(float y1, float y2, float y3) {\n uint i1 = floatBitsToUint(y1) >> 31u;\n uint i2 = floatBitsToUint(y2) >> 30u;\n uint i3 = floatBitsToUint(y3) >> 29u;\n\n uint shift = (i2 & 2u) | (i1 & ~2u);\n shift = (i3 & 4u) | (shift & ~4u);\n\n return (0x2E74u >> shift) & 0x0101u;\n}\n\nvec2 SolveHorizPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.y;\n float rb = 0.5 / b.y;\n\n float d = sqrt(max(b.y * b.y - a.y * p12.y, 0.0));\n float t1 = (b.y - d) * ra;\n float t2 = (b.y + d) * ra;\n\n if (abs(a.y) < 1.0 / 65536.0) t1 = t2 = p12.y * rb;\n\n return vec2((a.x * t1 - b.x * 2.0) * t1 + p12.x, (a.x * t2 - b.x * 2.0) * t2 + p12.x);\n}\n\nvec2 SolveVertPoly(vec4 p12, vec2 p3) {\n vec2 a = p12.xy - p12.zw * 2.0 + p3;\n vec2 b = p12.xy - p12.zw;\n float ra = 1.0 / a.x;\n float rb = 0.5 / b.x;\n\n float d = sqrt(max(b.x * b.x - a.x * p12.x, 0.0));\n float t1 = (b.x - d) * ra;\n float t2 = (b.x + d) * ra;\n\n if (abs(a.x) < 1.0 / 65536.0) t1 = t2 = p12.x * rb;\n\n return vec2((a.y * t1 - b.y * 2.0) * t1 + p12.y, (a.y * t2 - b.y * 2.0) * t2 + p12.y);\n}\n\nivec2 CalcBandLoc(ivec2 glyphLoc, uint offset) {\n ivec2 bandLoc = ivec2(glyphLoc.x + int(offset), glyphLoc.y);\n bandLoc.y += bandLoc.x >> kLogBandTextureWidth;\n bandLoc.x &= (1 << kLogBandTextureWidth) - 1;\n return bandLoc;\n}\n\nfloat CalcCoverage(float xcov, float ycov, float xwgt, float ywgt, int flags) {\n float coverage = max(abs(xcov * xwgt + ycov * ywgt) / max(xwgt + ywgt, 1.0 / 65536.0), min(abs(xcov), abs(ycov)));\n\n#if defined(SLUG_EVENODD)\n if ((flags & 0x1000) == 0) {\n#endif\n coverage = clamp(coverage, 0.0, 1.0);\n#if defined(SLUG_EVENODD)\n } else {\n coverage = 1.0 - abs(1.0 - fract(coverage * 0.5) * 2.0);\n }\n#endif\n\n#if defined(SLUG_WEIGHT)\n coverage = sqrt(coverage);\n#endif\n\n return coverage;\n}\n\nfloat SlugRenderSingle(vec2 renderCoord, vec2 emsPerPixel, vec4 bandTransform, ivec4 glyphData) {\n int curveIndex;\n\n vec2 pixelsPerEm = 1.0 / emsPerPixel;\n\n ivec2 bandMax = glyphData.zw;\n bandMax.y &= 0x00FF;\n\n ivec2 bandIndex = clamp(ivec2(renderCoord * bandTransform.xy + bandTransform.zw), ivec2(0, 0), bandMax);\n ivec2 glyphLoc = glyphData.xy;\n\n float xcov = 0.0;\n float xwgt = 0.0;\n\n uvec2 hbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandIndex.y, glyphLoc.y), 0).xy;\n ivec2 hbandLoc = CalcBandLoc(glyphLoc, hbandData.y);\n\n for (curveIndex = 0; curveIndex < int(hbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(hbandLoc.x + curveIndex, hbandLoc.y), 0).xy);\n\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) break;\n\n uint code = CalcRootCode(p12.y, p12.w, p3.y);\n if (code != 0u) {\n vec2 r = SolveHorizPoly(p12, p3) * pixelsPerEm.x;\n\n if ((code & 1u) != 0u) {\n xcov += clamp(r.x + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n xcov -= clamp(r.y + 0.5, 0.0, 1.0);\n xwgt = max(xwgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n float ycov = 0.0;\n float ywgt = 0.0;\n\n uvec2 vbandData = texelFetch(bandTexture, ivec2(glyphLoc.x + bandMax.y + 1 + bandIndex.x, glyphLoc.y), 0).xy;\n ivec2 vbandLoc = CalcBandLoc(glyphLoc, vbandData.y);\n\n for (curveIndex = 0; curveIndex < int(vbandData.x); curveIndex++) {\n ivec2 curveLoc = ivec2(texelFetch(bandTexture, ivec2(vbandLoc.x + curveIndex, vbandLoc.y), 0).xy);\n vec4 p12 = texelFetch(curveTexture, curveLoc, 0) - vec4(renderCoord, renderCoord);\n vec2 p3 = texelFetch(curveTexture, ivec2(curveLoc.x + 1, curveLoc.y), 0).xy - renderCoord;\n\n if (max(max(p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) break;\n\n uint code = CalcRootCode(p12.x, p12.z, p3.x);\n if (code != 0u) {\n vec2 r = SolveVertPoly(p12, p3) * pixelsPerEm.y;\n\n if ((code & 1u) != 0u) {\n ycov -= clamp(r.x + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.x) * 2.0, 0.0, 1.0));\n }\n\n if (code > 1u) {\n ycov += clamp(r.y + 0.5, 0.0, 1.0);\n ywgt = max(ywgt, clamp(1.0 - abs(r.y) * 2.0, 0.0, 1.0));\n }\n }\n }\n\n return CalcCoverage(xcov, ycov, xwgt, ywgt, glyphData.w);\n}\n\nfloat SlugRender(vec2 renderCoord, vec4 bandTransform, ivec4 glyphData) {\n vec2 emsPerPixel = fwidth(renderCoord);\n\n#if defined(SLUG_ADAPTIVE_SUPERSAMPLE)\n // Per-pixel rotated RGSS-4. The base RGSS offsets are rotated by a\n // unique angle per fragment (interleaved gradient noise). This converts\n // structured aliasing shimmer into uncorrelated grain that the eye\n // naturally filters out, much closer to how hardware MSAA on many small\n // triangles behaves perceptually.\n float noise = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));\n float angle = noise * 6.2831853;\n float ca = cos(angle), sa = sin(angle);\n\n // Base RGSS offsets rotated by per-pixel angle\n vec2 o0 = vec2(ca * -0.375 - sa * 0.125, sa * -0.375 + ca * 0.125) * emsPerPixel;\n vec2 o1 = vec2(ca * 0.125 - sa * 0.375, sa * 0.125 + ca * 0.375) * emsPerPixel;\n vec2 o2 = vec2(ca * 0.375 - sa * -0.125, sa * 0.375 + ca * -0.125) * emsPerPixel;\n vec2 o3 = vec2(ca * -0.125 - sa * -0.375, sa * -0.125 + ca * -0.375) * emsPerPixel;\n\n float coverage =\n SlugRenderSingle(renderCoord + o0, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o1, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o2, emsPerPixel, bandTransform, glyphData) +\n SlugRenderSingle(renderCoord + o3, emsPerPixel, bandTransform, glyphData);\n return coverage * 0.25;\n#else\n return SlugRenderSingle(renderCoord, emsPerPixel, bandTransform, glyphData);\n#endif\n}\n\nvoid main() {\n float coverage = SlugRender(v_texcoord, v_banding, v_glyph);\n outColor = v_color * coverage;\n}\n";
|
|
63
|
+
|
|
64
|
+
// Slug shader source re-exports
|
|
65
|
+
// The .glsl/.wgsl files are the single source of truth, imported as strings
|
|
66
|
+
// at build time via the glslPlugin in rollup.config.js
|
|
67
|
+
// @ts-ignore - resolved by rollup glslPlugin
|
|
68
|
+
const fragmentShaderGLSL300 = fragGLSL;
|
|
69
|
+
|
|
70
|
+
// Slug GLSL adapter for Three.js, using RawShaderMaterial with the
|
|
71
|
+
// reference GLSL shaders. Works with both WebGLRenderer and
|
|
72
|
+
// WebGPURenderer (via GLSL-to-WGSL transpilation)
|
|
73
|
+
//
|
|
74
|
+
// Compared to the TSL adapter (slugTSL.ts):
|
|
75
|
+
// - Works with any Three.js renderer (no node-material dependency)
|
|
76
|
+
// - Uses native Uint32 band texture (no float conversion)
|
|
77
|
+
// - Supports GLSL animation injection via animationDeclarations/animationBody
|
|
78
|
+
// - Same tradeoff: no vertex dilation (may cause sub-pixel edge clipping at extreme zoom)
|
|
79
|
+
//
|
|
80
|
+
// Requires peer dependency: three
|
|
81
|
+
// @ts-ignore - three is a peer dependency
|
|
82
|
+
// Three.js GLSL3 mode prepends #version 300 es, so strip it from the raw shader
|
|
83
|
+
const fragShader = fragmentShaderGLSL300.replace(/^#version\s+300\s+es\s*\n/, '');
|
|
84
|
+
function buildVertexShader(options) {
|
|
85
|
+
const decl = options?.animationDeclarations ?? '';
|
|
86
|
+
const body = options?.animationBody ?? 'vec3 outPos = position;';
|
|
87
|
+
return `precision highp float;
|
|
88
|
+
precision highp int;
|
|
89
|
+
|
|
90
|
+
in vec3 position;
|
|
91
|
+
in vec2 slugTexcoord;
|
|
92
|
+
in vec4 slugBanding;
|
|
93
|
+
in vec4 slugGlyph;
|
|
94
|
+
in vec4 slugColor;
|
|
95
|
+
in vec3 glyphCenter;
|
|
96
|
+
in float glyphIndex;
|
|
97
|
+
|
|
98
|
+
uniform mat4 modelViewMatrix;
|
|
99
|
+
uniform mat4 projectionMatrix;
|
|
100
|
+
uniform float time;
|
|
101
|
+
${decl}
|
|
102
|
+
|
|
103
|
+
out vec2 v_texcoord;
|
|
104
|
+
flat out vec4 v_banding;
|
|
105
|
+
flat out ivec4 v_glyph;
|
|
106
|
+
out vec4 v_color;
|
|
107
|
+
|
|
108
|
+
void main() {
|
|
109
|
+
v_texcoord = slugTexcoord;
|
|
110
|
+
v_banding = slugBanding;
|
|
111
|
+
v_glyph = ivec4(slugGlyph);
|
|
112
|
+
v_color = slugColor;
|
|
113
|
+
|
|
114
|
+
${body}
|
|
115
|
+
|
|
116
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(outPos, 1.0);
|
|
117
|
+
}
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
function createSlugGLSLMesh(gpuData, options) {
|
|
121
|
+
const attrs = unpackSlugVertices(gpuData);
|
|
122
|
+
const geo = new THREE.BufferGeometry();
|
|
123
|
+
geo.setAttribute('position', new THREE.Float32BufferAttribute(attrs.positions, 3));
|
|
124
|
+
geo.setAttribute('slugTexcoord', new THREE.Float32BufferAttribute(attrs.texcoords, 2));
|
|
125
|
+
geo.setAttribute('slugBanding', new THREE.Float32BufferAttribute(attrs.bandings, 4));
|
|
126
|
+
geo.setAttribute('slugGlyph', new THREE.Float32BufferAttribute(attrs.glyphData, 4));
|
|
127
|
+
geo.setAttribute('slugColor', new THREE.Float32BufferAttribute(attrs.colors, 4));
|
|
128
|
+
geo.setAttribute('glyphCenter', new THREE.Float32BufferAttribute(attrs.glyphCenters, 3));
|
|
129
|
+
geo.setAttribute('glyphIndex', new THREE.Float32BufferAttribute(attrs.glyphIndices, 1));
|
|
130
|
+
geo.setIndex(new THREE.BufferAttribute(gpuData.indices, 1));
|
|
131
|
+
// Curve texture: RGBA32F
|
|
132
|
+
const curveTex = new THREE.DataTexture(gpuData.curveTexture.data, gpuData.curveTexture.width, gpuData.curveTexture.height, THREE.RGBAFormat, THREE.FloatType);
|
|
133
|
+
curveTex.minFilter = THREE.NearestFilter;
|
|
134
|
+
curveTex.magFilter = THREE.NearestFilter;
|
|
135
|
+
curveTex.generateMipmaps = false;
|
|
136
|
+
curveTex.needsUpdate = true;
|
|
137
|
+
// Band texture: native RGBA32UI (no float conversion needed)
|
|
138
|
+
const bandTex = new THREE.DataTexture(gpuData.bandTexture.data, gpuData.bandTexture.width, gpuData.bandTexture.height);
|
|
139
|
+
bandTex.format = THREE.RGBAIntegerFormat;
|
|
140
|
+
bandTex.type = THREE.UnsignedIntType;
|
|
141
|
+
bandTex.internalFormat = 'RGBA32UI';
|
|
142
|
+
bandTex.minFilter = THREE.NearestFilter;
|
|
143
|
+
bandTex.magFilter = THREE.NearestFilter;
|
|
144
|
+
bandTex.generateMipmaps = false;
|
|
145
|
+
bandTex.needsUpdate = true;
|
|
146
|
+
const col = options?.color ?? { r: 1, g: 1, b: 1 };
|
|
147
|
+
const colorUniform = new THREE.Vector4(col.r, col.g, col.b, 1.0);
|
|
148
|
+
const uniforms = {
|
|
149
|
+
curveTexture: { value: curveTex },
|
|
150
|
+
bandTexture: { value: bandTex },
|
|
151
|
+
time: { value: 0 },
|
|
152
|
+
u_color: { value: colorUniform },
|
|
153
|
+
...options?.uniforms,
|
|
154
|
+
};
|
|
155
|
+
const frag = options?.adaptiveSupersampling
|
|
156
|
+
? '#define SLUG_ADAPTIVE_SUPERSAMPLE\n' + fragShader
|
|
157
|
+
: fragShader;
|
|
158
|
+
const material = new THREE.RawShaderMaterial({
|
|
159
|
+
glslVersion: THREE.GLSL3,
|
|
160
|
+
uniforms,
|
|
161
|
+
vertexShader: buildVertexShader(options),
|
|
162
|
+
fragmentShader: frag,
|
|
163
|
+
transparent: true,
|
|
164
|
+
depthWrite: false,
|
|
165
|
+
side: THREE.DoubleSide,
|
|
166
|
+
});
|
|
167
|
+
const mesh = new THREE.Mesh(geo, material);
|
|
168
|
+
return {
|
|
169
|
+
mesh,
|
|
170
|
+
uniforms,
|
|
171
|
+
setOffset(x, y, z = 0) {
|
|
172
|
+
mesh.position.set(x, y, z);
|
|
173
|
+
},
|
|
174
|
+
setColor(r, g, b) {
|
|
175
|
+
colorUniform.set(r, g, b, 1.0);
|
|
176
|
+
},
|
|
177
|
+
dispose() {
|
|
178
|
+
geo.dispose();
|
|
179
|
+
material.dispose();
|
|
180
|
+
curveTex.dispose();
|
|
181
|
+
bandTex.dispose();
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function parseColor(color) {
|
|
187
|
+
if (!color)
|
|
188
|
+
return { r: 1, g: 1, b: 1 };
|
|
189
|
+
if (Array.isArray(color))
|
|
190
|
+
return { r: color[0], g: color[1], b: color[2] };
|
|
191
|
+
if (color.default)
|
|
192
|
+
return { r: color.default[0], g: color.default[1], b: color.default[2] };
|
|
193
|
+
return { r: 1, g: 1, b: 1 };
|
|
194
|
+
}
|
|
195
|
+
async function createMesh(gpuData, color, adaptiveSupersampling) {
|
|
196
|
+
try {
|
|
197
|
+
const { createSlugTSLMesh } = await import('./slugTSL.js');
|
|
198
|
+
return createSlugTSLMesh(gpuData, color);
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return createSlugGLSLMesh(gpuData, { color, adaptiveSupersampling });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async function wrapCoreResult(coreResult, options) {
|
|
205
|
+
const color = parseColor(options.color);
|
|
206
|
+
const slugMesh = await createMesh(coreResult.gpuData, color, options.adaptiveSupersampling);
|
|
207
|
+
const geo = slugMesh.mesh.geometry;
|
|
208
|
+
geo.computeBoundingBox();
|
|
209
|
+
const center = new THREE.Vector3();
|
|
210
|
+
geo.boundingBox.getCenter(center);
|
|
211
|
+
geo.translate(-center.x, -center.y, -center.z);
|
|
212
|
+
const gcAttr = geo.getAttribute('glyphCenter');
|
|
213
|
+
if (gcAttr) {
|
|
214
|
+
const arr = gcAttr.array;
|
|
215
|
+
for (let i = 0; i < arr.length; i += 3) {
|
|
216
|
+
arr[i] -= center.x;
|
|
217
|
+
arr[i + 1] -= center.y;
|
|
218
|
+
arr[i + 2] -= center.z;
|
|
219
|
+
}
|
|
220
|
+
gcAttr.needsUpdate = true;
|
|
221
|
+
}
|
|
222
|
+
const group = new THREE.Group();
|
|
223
|
+
group.add(slugMesh.mesh);
|
|
224
|
+
return {
|
|
225
|
+
group,
|
|
226
|
+
mesh: slugMesh.mesh,
|
|
227
|
+
gpuData: coreResult.gpuData,
|
|
228
|
+
glyphs: coreResult.glyphs,
|
|
229
|
+
planeBounds: coreResult.planeBounds,
|
|
230
|
+
query: (queryOptions) => coreResult.query(queryOptions),
|
|
231
|
+
getLoadedFont: () => coreResult.getLoadedFont(),
|
|
232
|
+
measureTextWidth: (text, letterSpacing) => coreResult.measureTextWidth(text, letterSpacing),
|
|
233
|
+
update: async (newOptions) => {
|
|
234
|
+
const mergedOptions = { ...options };
|
|
235
|
+
for (const key in newOptions) {
|
|
236
|
+
const value = newOptions[key];
|
|
237
|
+
if (value !== undefined) {
|
|
238
|
+
mergedOptions[key] = value;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const newCore = await coreResult.update(newOptions);
|
|
242
|
+
return wrapCoreResult(newCore, mergedOptions);
|
|
243
|
+
},
|
|
244
|
+
dispose: () => {
|
|
245
|
+
slugMesh.dispose();
|
|
246
|
+
coreResult.dispose();
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
class Text {
|
|
251
|
+
static { this.setHarfBuzzPath = Text$1.setHarfBuzzPath; }
|
|
252
|
+
static { this.setHarfBuzzBuffer = Text$1.setHarfBuzzBuffer; }
|
|
253
|
+
static { this.init = Text$1.init; }
|
|
254
|
+
static { this.registerPattern = Text$1.registerPattern; }
|
|
255
|
+
static { this.preloadPatterns = Text$1.preloadPatterns; }
|
|
256
|
+
static { this.setMaxFontCacheMemoryMB = Text$1.setMaxFontCacheMemoryMB; }
|
|
257
|
+
static { this.enableWoff2 = Text$1.enableWoff2; }
|
|
258
|
+
static async create(options) {
|
|
259
|
+
const coreResult = await Text$1.create(options);
|
|
260
|
+
return wrapCoreResult(coreResult, options);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export { Text as T, createSlugGLSLMesh as c, unpackSlugVertices as u };
|
package/dist/vector/react.cjs
CHANGED
|
@@ -2,8 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var react = require('react');
|
|
5
|
+
var THREE = require('three');
|
|
5
6
|
var index = require('./index.cjs');
|
|
6
7
|
|
|
8
|
+
function _interopNamespaceDefault(e) {
|
|
9
|
+
var n = Object.create(null);
|
|
10
|
+
if (e) {
|
|
11
|
+
Object.keys(e).forEach(function (k) {
|
|
12
|
+
if (k !== 'default') {
|
|
13
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
14
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function () { return e[k]; }
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
n.default = e;
|
|
22
|
+
return Object.freeze(n);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
|
|
26
|
+
|
|
7
27
|
function deepEqual(a, b) {
|
|
8
28
|
if (a === b)
|
|
9
29
|
return true;
|
|
@@ -46,7 +66,7 @@ function useDeepCompareMemo(value) {
|
|
|
46
66
|
}
|
|
47
67
|
|
|
48
68
|
const TextInner = react.forwardRef(function TextInner(props, ref) {
|
|
49
|
-
const { children, font, fillColor = '#ffffff',
|
|
69
|
+
const { children, font, fillColor = '#ffffff', position = [0, 0, 0], rotation = [0, 0, 0], scale = [1, 1, 1], positionNode, onLoad, onError, ...restOptions } = props;
|
|
50
70
|
const memoizedTextOptions = useDeepCompareMemo(restOptions);
|
|
51
71
|
const [group, setGroup] = react.useState(null);
|
|
52
72
|
const [error, setError] = react.useState(null);
|
|
@@ -61,24 +81,22 @@ const TextInner = react.forwardRef(function TextInner(props, ref) {
|
|
|
61
81
|
async function setup() {
|
|
62
82
|
try {
|
|
63
83
|
setError(null);
|
|
84
|
+
const color = new THREE__namespace.Color(fillColor);
|
|
64
85
|
const resultPromise = opRef.current.catch(() => null).then(() => {
|
|
65
86
|
if (cancelled)
|
|
66
87
|
return null;
|
|
88
|
+
const colorArr = [color.r, color.g, color.b];
|
|
67
89
|
return resultRef.current
|
|
68
90
|
? resultRef.current.update({
|
|
69
91
|
text: children,
|
|
70
92
|
font,
|
|
71
|
-
color:
|
|
72
|
-
positionNode,
|
|
73
|
-
colorNode,
|
|
93
|
+
color: colorArr,
|
|
74
94
|
...memoizedTextOptions
|
|
75
95
|
})
|
|
76
96
|
: index.Text.create({
|
|
77
97
|
text: children,
|
|
78
98
|
font,
|
|
79
|
-
color:
|
|
80
|
-
positionNode,
|
|
81
|
-
colorNode,
|
|
99
|
+
color: colorArr,
|
|
82
100
|
...memoizedTextOptions
|
|
83
101
|
});
|
|
84
102
|
});
|
|
@@ -90,6 +108,10 @@ const TextInner = react.forwardRef(function TextInner(props, ref) {
|
|
|
90
108
|
result.dispose();
|
|
91
109
|
return;
|
|
92
110
|
}
|
|
111
|
+
if (positionNode && result.mesh?.material) {
|
|
112
|
+
result.mesh.material.positionNode = positionNode;
|
|
113
|
+
result.mesh.material.needsUpdate = true;
|
|
114
|
+
}
|
|
93
115
|
const prev = resultRef.current;
|
|
94
116
|
resultRef.current = result;
|
|
95
117
|
setGroup(result.group);
|
|
@@ -112,7 +134,14 @@ const TextInner = react.forwardRef(function TextInner(props, ref) {
|
|
|
112
134
|
return () => {
|
|
113
135
|
cancelled = true;
|
|
114
136
|
};
|
|
115
|
-
}, [children, font, memoizedTextOptions, fillColor
|
|
137
|
+
}, [children, font, memoizedTextOptions, fillColor]);
|
|
138
|
+
react.useEffect(() => {
|
|
139
|
+
const result = resultRef.current;
|
|
140
|
+
if (result?.mesh?.material) {
|
|
141
|
+
result.mesh.material.positionNode = positionNode ?? null;
|
|
142
|
+
result.mesh.material.needsUpdate = true;
|
|
143
|
+
}
|
|
144
|
+
}, [positionNode]);
|
|
116
145
|
react.useEffect(() => {
|
|
117
146
|
return () => {
|
|
118
147
|
resultRef.current?.dispose();
|