@neutrinoparticles/js-v1.1-pixi8 1.0.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.
@@ -0,0 +1,1214 @@
1
+ (function(x,p){typeof exports=="object"&&typeof module<"u"?p(exports,require("pixi.js"),require("@neutrinoparticles/js-v1.1")):typeof define=="function"&&define.amd?define(["exports","pixi.js","@neutrinoparticles/js-v1.1"],p):(x=typeof globalThis<"u"?globalThis:x||self,p(x.PIXINeutrino={},x.PIXI,x.Neutrino))})(this,function(exports,pixi_js,Neutrino){"use strict";var L=Object.defineProperty;var V=(x,p,l)=>p in x?L(x,p,{enumerable:!0,configurable:!0,writable:!0,value:l}):x[p]=l;var i=(x,p,l)=>V(x,typeof p!="symbol"?p+"":p,l);function _interopNamespaceDefault(l){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(l){for(const t in l)if(t!=="default"){const r=Object.getOwnPropertyDescriptor(l,t);Object.defineProperty(e,t,r.get?r:{enumerable:!0,get:()=>l[t]})}}return e.default=l,Object.freeze(e)}const Neutrino__namespace=_interopNamespaceDefault(Neutrino),vertexShaderSource=`#version 300 es
2
+ precision highp float;
3
+
4
+ uniform sampler2D uDataTexture;
5
+ uniform int uDataTextureWidth;
6
+ uniform vec3 uCameraRight;
7
+ uniform vec3 uCameraUp;
8
+ uniform vec3 uCameraDir;
9
+ uniform mat4 uViewProjMatrix;
10
+ uniform mat4 uModelMatrix;
11
+ uniform float uViewportAspect; // viewport width / height — strip-only
12
+ uniform vec4 uTexRemaps[8];
13
+
14
+ const vec2 ORIGIN_MUL[4] = vec2[4](
15
+ vec2(-1.0, 0.0),
16
+ vec2(-1.0, -1.0),
17
+ vec2(0.0, -1.0),
18
+ vec2(0.0, 0.0)
19
+ );
20
+
21
+ const vec2 INV_ORIGIN_MUL[4] = vec2[4](
22
+ vec2(0.0, 1.0),
23
+ vec2(0.0, 0.0),
24
+ vec2(1.0, 0.0),
25
+ vec2(1.0, 1.0)
26
+ );
27
+
28
+ const vec2 TEX_COORD[4] = vec2[4](
29
+ vec2(0.0, 0.0),
30
+ vec2(0.0, 1.0),
31
+ vec2(1.0, 1.0),
32
+ vec2(1.0, 0.0)
33
+ );
34
+
35
+ out vec2 vTexCoord;
36
+ out vec4 vColor;
37
+ flat out int vBatchTextureIndex;
38
+ // Atlas slot rect (origin.xy, extent.zw) used by the strip path to wrap
39
+ // U inside its own slot at the fragment stage. Quad path keeps the old
40
+ // vertex-side remap so quad sampling stays bit-perfect with prior IBCT
41
+ // snapshots. The flag distinguishes the two paths in the fragment.
42
+ flat out vec4 vAtlasRemap;
43
+ flat out int vStripPath;
44
+
45
+ vec4 fetchTexel(int globalTexelIndex) {
46
+ int x = globalTexelIndex % uDataTextureWidth;
47
+ int y = globalTexelIndex / uDataTextureWidth;
48
+ return texelFetch(uDataTexture, ivec2(x, y), 0);
49
+ }
50
+
51
+ // Strip geometry helpers (port of GPU33 makeScreenSpaceStrip).
52
+ // 2D screen-space variant used by the Faced strip frame; world Z reaches
53
+ // gl_Position only through uViewProjMatrix (ortho or v1.0-parity perspective).
54
+ vec2 stripOrtho(vec2 v) {
55
+ return vec2(-v.y, v.x);
56
+ }
57
+
58
+ void main() {
59
+ int vertexIndex = gl_VertexID % 4;
60
+ int particleIndex = gl_VertexID / 4;
61
+
62
+ int baseTexel = particleIndex * 4;
63
+
64
+ vec4 t0 = fetchTexel(baseTexel + 0);
65
+ vec4 t1 = fetchTexel(baseTexel + 1);
66
+ vec4 t2 = fetchTexel(baseTexel + 2);
67
+ vec4 t3 = fetchTexel(baseTexel + 3);
68
+
69
+ // Texel 0: flags (uint32 bits in float), posX, posY, posZ
70
+ uint flags = floatBitsToUint(t0.r);
71
+ vec3 position = t0.gba;
72
+ uint rotationType = (flags >> 8u) & 3u;
73
+ uint ctorType = (flags >> 10u) & 3u;
74
+ int batchTexIdx = int(flags & 0xFFu);
75
+ vBatchTextureIndex = batchTexIdx;
76
+
77
+ // ─── Strip path ───────────────────────────────────────────────────────
78
+ // ctorType: 0 = Quad, 1 = StripCur, 2 = StripNex.
79
+ // Strip meta records come in pairs: meta_cur describes the "p1" end and
80
+ // meta_nex describes the "p2" end of one segment. Sibling meta is at the
81
+ // adjacent texel base (cur + 4 = nex; nex - 4 = cur). p0 (= prev pair's
82
+ // p1) and p3 (= next pair's p2) are reached via neighborIdxP0 / P3 stored
83
+ // as uint32 in t3.g / t3.b. Boundary particles use self-reference: when
84
+ // p0==p1 the shader sets dir01=dir12 and the knee degenerates.
85
+ //
86
+ // rotationType handling in the strip path:
87
+ // 0 (Faced): screen-space ortho to dir12 — equivalent to camera-faced
88
+ // billboard normal in a 2D camera setup.
89
+ // 1 (Free): the quaternion stored in texel 2 nominally encodes a 3D
90
+ // orientation. The PIXI7 renderer has no true 3D camera
91
+ // (uViewProjMatrix is ortho or the v1.0-parity perspective),
92
+ // so the quat is read but its 3D y-axis is projected to screen
93
+ // and used as the section normal. When the projection
94
+ // degenerates (looking along the y-axis) we fall back to
95
+ // the screen-space ortho. Real 3D free-rotation requires a
96
+ // future 3D pipeline; this is documented as a deviation in
97
+ // docs/tdd/TDD_80-js-v1.1-constructor-strip.md.
98
+ if (ctorType >= 1u) {
99
+ bool isCur = (ctorType == 1u);
100
+ int siblingTexel = isCur ? (baseTexel + 4) : (baseTexel - 4);
101
+ vec4 ts0 = fetchTexel(siblingTexel + 0);
102
+ vec4 ts1 = fetchTexel(siblingTexel + 1);
103
+ vec4 ts3 = fetchTexel(siblingTexel + 3);
104
+
105
+ // p1 always = position of the meta_cur end; p2 = meta_nex end.
106
+ vec3 p1 = isCur ? t0.gba : ts0.gba;
107
+ vec3 p2 = isCur ? ts0.gba : t0.gba;
108
+
109
+ float halfSize1 = isCur ? t1.r : ts1.r;
110
+ float halfSize2 = isCur ? ts1.r : t1.r;
111
+ float texU1 = isCur ? t1.g : ts1.g;
112
+ float texU2 = isCur ? ts1.g : t1.g;
113
+
114
+ // Color: meta_cur stores color of p1, meta_nex stores color of p2.
115
+ uint packedColorCur = floatBitsToUint(isCur ? t3.r : ts3.r);
116
+ uint packedColorNex = floatBitsToUint(isCur ? ts3.r : t3.r);
117
+ vec4 color1 = vec4(
118
+ float(packedColorCur & 0xFFu) / 255.0,
119
+ float((packedColorCur >> 8u) & 0xFFu) / 255.0,
120
+ float((packedColorCur >> 16u) & 0xFFu) / 255.0,
121
+ float((packedColorCur >> 24u) & 0xFFu) / 255.0);
122
+ vec4 color2 = vec4(
123
+ float(packedColorNex & 0xFFu) / 255.0,
124
+ float((packedColorNex >> 8u) & 0xFFu) / 255.0,
125
+ float((packedColorNex >> 16u) & 0xFFu) / 255.0,
126
+ float((packedColorNex >> 24u) & 0xFFu) / 255.0);
127
+
128
+ // neighborIdxP0 / neighborIdxP3 are stored on every meta of a pair
129
+ // (both meta_cur and meta_nex hold the same pair of indices), so we
130
+ // can read them from either self or sibling.
131
+ //
132
+ // Runtime convention (\`_constructStripParticles\` in system.js):
133
+ // neighborIdxP0 = global texel index of the position of W
134
+ // (= lnkdPrev(X)); boundary sentinel = curTexIdx
135
+ // (the pair's meta_cur texel index).
136
+ // neighborIdxP3 = global texel index of the position of Z
137
+ // (= lnkdNext(Y)); boundary sentinel = curTexIdx.
138
+ // Both are direct position-texel indices (NOT pair-cur indices that
139
+ // would need a +1 offset for the nex slot). Boundary detection uses
140
+ // selfPairCurTexel for both.
141
+ uint neighborIdxP0 = floatBitsToUint(isCur ? t3.g : ts3.g);
142
+ uint neighborIdxP3 = floatBitsToUint(isCur ? ts3.b : t3.b);
143
+ uint selfPairCurTexel = uint(isCur ? baseTexel : siblingTexel) / 4u;
144
+
145
+ vec3 p0 = (neighborIdxP0 == selfPairCurTexel)
146
+ ? p1
147
+ : fetchTexel(int(neighborIdxP0) * 4 + 0).gba;
148
+ vec3 p3 = (neighborIdxP3 == selfPairCurTexel)
149
+ ? p2
150
+ : fetchTexel(int(neighborIdxP3) * 4 + 0).gba;
151
+
152
+ // Segment frame. Two completely separate frames depending on
153
+ // rotationType — MIRROR of preview3 DataTextureRenderer3 (the editor's
154
+ // own JS1.1 renderer) so all data-texture renderers stay identical:
155
+ // Faced (rotationType 0/2): screen-space NDC frame around dir12.
156
+ // Free (rotationType 1): FULL world-space 3D frame (make3DStrip);
157
+ // the ribbon keeps its 3D orientation from the quat and Z collapses
158
+ // naturally only at projection (front-planar is a camera artifact,
159
+ // not a shader simplification). Mixing world-space section into a
160
+ // screen-space miter distorts sharp bends and makes geometry track
161
+ // the camera even with a static quat — Free stays camera-independent.
162
+ mat4 mvpMatrix = uViewProjMatrix * uModelMatrix;
163
+ bool isFreeRotation = (rotationType == 1u);
164
+
165
+ // Anchor projections — both branches use them for gl_Position.
166
+ vec4 sp1 = mvpMatrix * vec4(p1, 1.0);
167
+ vec4 sp2 = mvpMatrix * vec4(p2, 1.0);
168
+
169
+ // Near-plane guard (Faced only; the world-space Free frame is unaffected
170
+ // by the sign of w). Forces a discard outside the [-w,w] cube at the end.
171
+ bool nearPlaneClipped = (sp1.w < 1e-3) || (sp2.w < 1e-3);
172
+
173
+ // Aspect correction so the 2D Faced ortho gives visually-perpendicular
174
+ // dirs on non-square viewports (inverted before gl_Position).
175
+ vec2 aspectMul = vec2(1.0, 1.0 / max(uViewportAspect, 1e-6));
176
+
177
+ vec4 ts2 = fetchTexel(siblingTexel + 2);
178
+ vec4 q1 = isCur ? t2 : ts2;
179
+ vec4 q2 = isCur ? ts2 : t2;
180
+
181
+ // Frame outputs: Faced fills the 2D (k/b/IK), Free fills the 3D
182
+ // (worldK/B/IK); the vertex ladder picks the right one per gl_Position.
183
+ vec3 worldK1, worldB1, worldIK1, worldK2, worldB2, worldIK2;
184
+ vec2 k1, b1, IK1, k2, b2, IK2;
185
+ float Bmul1, Bmul2, kb1, kb2;
186
+ bool noKnee1, noKnee2;
187
+ float pathLen12; // len12 in the active frame's units (NDC for Faced, world for Free)
188
+
189
+ if (isFreeRotation) {
190
+ // World-space frame (mirror of GPU33 make3DStrip). Section normal
191
+ // n = quat z-axis (local Z ⟂ ribbon plane); ribbon lies in local XY
192
+ // (travel along local X, width along local Y). B = cross(dir12, n).
193
+ float xz1 = 2.0 * q1.x * q1.z;
194
+ float wy1 = 2.0 * q1.w * q1.y;
195
+ float yz1 = 2.0 * q1.y * q1.z;
196
+ float wx1 = 2.0 * q1.w * q1.x;
197
+ vec3 n1 = vec3(xz1 + wy1, yz1 - wx1, 1.0 - 2.0 * (q1.x * q1.x + q1.y * q1.y));
198
+ float xz2 = 2.0 * q2.x * q2.z;
199
+ float wy2 = 2.0 * q2.w * q2.y;
200
+ float yz2 = 2.0 * q2.y * q2.z;
201
+ float wx2 = 2.0 * q2.w * q2.x;
202
+ vec3 n2 = vec3(xz2 + wy2, yz2 - wx2, 1.0 - 2.0 * (q2.x * q2.x + q2.y * q2.y));
203
+
204
+ vec3 wp01 = p1 - p0;
205
+ vec3 wp12 = p2 - p1;
206
+ vec3 wp23 = p3 - p2;
207
+ float wlen01 = length(wp01);
208
+ float wlen12 = length(wp12);
209
+ float wlen23 = length(wp23);
210
+ vec3 wdir12 = (wlen12 > 1e-6) ? (wp12 / wlen12) : vec3(1.0, 0.0, 0.0);
211
+ vec3 wdir01 = (wlen01 > 1e-6) ? (wp01 / wlen01) : wdir12;
212
+ vec3 wdir23 = (wlen23 > 1e-6) ? (wp23 / wlen23) : wdir12;
213
+ noKnee1 = dot(wdir01, wdir12) > 0.999;
214
+ noKnee2 = dot(wdir12, wdir23) > 0.999;
215
+ pathLen12 = wlen12;
216
+
217
+ if (!noKnee1) {
218
+ vec3 B1raw = normalize(cross(wdir12, n1));
219
+ vec3 c1 = normalize(cross(wdir01, wdir12));
220
+ float Kmul1 = sign(dot(c1, n1));
221
+ vec3 K1 = Kmul1 * normalize(cross(wdir01 + wdir12, n1));
222
+ Bmul1 = sign(dot(B1raw, K1));
223
+ vec3 B1 = B1raw * Bmul1;
224
+ float ik1 = halfSize1 / max(dot(B1, K1), 1e-6);
225
+ if (dot(-wdir01, ik1 * -K1) > wlen01) ik1 = wlen01;
226
+ if (dot( wdir12, ik1 * -K1) > wlen12) ik1 = wlen12;
227
+ worldK1 = p1 + K1 * halfSize1;
228
+ worldB1 = p1 + B1 * halfSize1;
229
+ worldIK1 = p1 - K1 * ik1;
230
+ kb1 = distance(worldK1, worldB1);
231
+ } else {
232
+ Bmul1 = 1.0;
233
+ vec3 B1 = normalize(cross(wdir01 + wdir12, n1));
234
+ worldK1 = p1 + B1 * halfSize1;
235
+ worldB1 = worldK1;
236
+ worldIK1 = p1 - B1 * halfSize1;
237
+ kb1 = 0.0;
238
+ }
239
+
240
+ if (!noKnee2) {
241
+ vec3 B2raw = normalize(cross(wdir12, n2));
242
+ vec3 c2 = normalize(cross(wdir12, wdir23));
243
+ float Kmul2 = sign(dot(c2, n2));
244
+ vec3 K2 = Kmul2 * normalize(cross(wdir12 + wdir23, n2));
245
+ Bmul2 = sign(dot(B2raw, K2));
246
+ vec3 B2 = B2raw * Bmul2;
247
+ float ik2 = halfSize2 / max(dot(B2, K2), 1e-6);
248
+ if (dot(-wdir12, ik2 * -K2) > wlen12) ik2 = wlen12;
249
+ if (dot( wdir23, ik2 * -K2) > wlen23) ik2 = wlen23;
250
+ worldK2 = p2 + K2 * halfSize2;
251
+ worldB2 = p2 + B2 * halfSize2;
252
+ worldIK2 = p2 - K2 * ik2;
253
+ kb2 = distance(worldK2, worldB2);
254
+ } else {
255
+ Bmul2 = 1.0;
256
+ vec3 B2 = normalize(cross(wdir12 + wdir23, n2));
257
+ worldK2 = p2 + B2 * halfSize2;
258
+ worldB2 = worldK2;
259
+ worldIK2 = p2 - B2 * halfSize2;
260
+ kb2 = 0.0;
261
+ }
262
+ } else {
263
+ // Faced: screen-space NDC frame around dir12.
264
+ vec4 sp0 = mvpMatrix * vec4(p0, 1.0);
265
+ vec4 sp3 = mvpMatrix * vec4(p3, 1.0);
266
+ vec2 ndc1 = (sp1.xy / sp1.w) * aspectMul;
267
+ vec2 ndc2 = (sp2.xy / sp2.w) * aspectMul;
268
+ // Neighbor anchors behind the near plane produce garbage NDC after
269
+ // the perspective divide (possible only with Projection3D, where w
270
+ // varies); collapse them onto their segment anchor so the existing
271
+ // zero-length degeneracy path (dir01 = dir12 / dir23 = dir12)
272
+ // handles the knee instead of a poisoned miter. spU1/spU2 need no
273
+ // guard: uCameraUp has zero Z, so their w equals sp1.w / sp2.w.
274
+ vec2 ndc0 = (sp0.w < 1e-3) ? ndc1 : (sp0.xy / sp0.w) * aspectMul;
275
+ vec2 ndc3 = (sp3.w < 1e-3) ? ndc2 : (sp3.xy / sp3.w) * aspectMul;
276
+
277
+ vec2 d01 = ndc1 - ndc0;
278
+ vec2 d12 = ndc2 - ndc1;
279
+ vec2 d23 = ndc3 - ndc2;
280
+ float len01 = length(d01);
281
+ float len12 = length(d12);
282
+ float len23 = length(d23);
283
+ vec2 dir12 = (len12 > 1e-6) ? (d12 / len12) : vec2(1.0, 0.0);
284
+ vec2 dir01 = (len01 > 1e-6) ? (d01 / len01) : dir12;
285
+ vec2 dir23 = (len23 > 1e-6) ? (d23 / len23) : dir12;
286
+ noKnee1 = dot(dir01, dir12) > 0.999;
287
+ noKnee2 = dot(dir12, dir23) > 0.999;
288
+ pathLen12 = len12;
289
+
290
+ // Section magnitude: project camera_up*halfSize per anchor into NDC.
291
+ vec4 spU1 = mvpMatrix * vec4(p1 + uCameraUp * halfSize1, 1.0);
292
+ vec4 spU2 = mvpMatrix * vec4(p2 + uCameraUp * halfSize2, 1.0);
293
+ float ndcHalfSize1 = length((spU1.xy / spU1.w) * aspectMul - ndc1);
294
+ float ndcHalfSize2 = length((spU2.xy / spU2.w) * aspectMul - ndc2);
295
+
296
+ vec2 B = stripOrtho(dir12);
297
+ if (!noKnee1) {
298
+ float Kmul1 = -sign(dot(stripOrtho(dir01), dir12));
299
+ vec2 K1 = Kmul1 * normalize(stripOrtho(dir01 + dir12));
300
+ Bmul1 = sign(dot(B, K1));
301
+ vec2 B1 = B * Bmul1;
302
+ float ik1 = ndcHalfSize1 / max(dot(B1, K1), 1e-6);
303
+ if (dot(-dir01, ik1 * -K1) > len01) ik1 = len01;
304
+ if (dot(dir12, ik1 * -K1) > len12) ik1 = len12;
305
+ k1 = ndc1 + K1 * ndcHalfSize1;
306
+ b1 = ndc1 + B1 * ndcHalfSize1;
307
+ IK1 = ndc1 - K1 * ik1;
308
+ kb1 = distance(k1, b1);
309
+ } else {
310
+ vec2 B1 = normalize(stripOrtho(dir01 + dir12));
311
+ Bmul1 = 1.0;
312
+ k1 = ndc1 + B1 * ndcHalfSize1;
313
+ b1 = k1;
314
+ IK1 = ndc1 - B1 * ndcHalfSize1;
315
+ kb1 = 0.0;
316
+ }
317
+ if (!noKnee2) {
318
+ float Kmul2 = -sign(dot(stripOrtho(dir12), dir23));
319
+ vec2 K2 = Kmul2 * normalize(stripOrtho(dir12 + dir23));
320
+ Bmul2 = sign(dot(B, K2));
321
+ vec2 B2 = B * Bmul2;
322
+ float ik2 = ndcHalfSize2 / max(dot(B2, K2), 1e-6);
323
+ if (dot(-dir12, ik2 * -K2) > len12) ik2 = len12;
324
+ if (dot(dir23, ik2 * -K2) > len23) ik2 = len23;
325
+ k2 = ndc2 + K2 * ndcHalfSize2;
326
+ b2 = ndc2 + B2 * ndcHalfSize2;
327
+ IK2 = ndc2 - K2 * ik2;
328
+ kb2 = distance(k2, b2);
329
+ } else {
330
+ vec2 B2 = normalize(stripOrtho(dir12 + dir23));
331
+ Bmul2 = 1.0;
332
+ k2 = ndc2 + B2 * ndcHalfSize2;
333
+ b2 = k2;
334
+ IK2 = ndc2 - B2 * ndcHalfSize2;
335
+ kb2 = 0.0;
336
+ }
337
+ }
338
+
339
+ // V coords (mirror of preview3 / GPU33 make*Strip):
340
+ // V at outer knee (k) = (Bmul + 1) * 0.5
341
+ // V at inner knee (IK) = 1 - V_outer ; bend (b) = outer
342
+ float Vtop1 = (Bmul1 + 1.0) * 0.5;
343
+ float Vbot1 = 1.0 - Vtop1;
344
+ float Vtop2 = (Bmul2 + 1.0) * 0.5;
345
+ float Vbot2 = 1.0 - Vtop2;
346
+
347
+ // Vertex layout per meta (4 vertices × IBO [0,1,2, 0,2,3] = 2 tris):
348
+ //
349
+ // Bmul1*Bmul2 ≥ 0 (knees same side):
350
+ // meta_cur: v0=b1, v1=k1, v2=IK1, v3=b2
351
+ // T1=(b1,k1,IK1) — knee at p1 ✓
352
+ // T2=(b1,IK1,b2) — top half segment ✓
353
+ // meta_nex: v0=b2, v1=IK1, v2=IK2, v3=k2
354
+ // T1=(b2,IK1,IK2) — bottom half segment ✓
355
+ // T2=(b2,IK2,k2) — knee at p2 ✓
356
+ //
357
+ // Bmul1*Bmul2 < 0 (knees opposite sides):
358
+ // meta_cur: v0=b1, v1=k1, v2=IK1, v3=IK2
359
+ // T1=(b1,k1,IK1) ✓
360
+ // T2=(b1,IK1,IK2) — diagonal cross ✓
361
+ // meta_nex: v0=IK2, v1=IK1, v2=b2, v3=k2
362
+ // T1=(IK2,IK1,b2) — diagonal cross ✓
363
+ // T2=(IK2,b2,k2) — knee at p2 ✓
364
+ //
365
+ // The shader picks v0..v3 based on (isCur, vertexIndex, sign(Bmul1*Bmul2)).
366
+ bool sameSide = (Bmul1 * Bmul2 >= 0.0);
367
+ // Distance for U interpolation along the bend point. Clamped to a
368
+ // small epsilon to avoid NaN on fully degenerate segments (p1≈p2 or
369
+ // 1-particle "strip" reached through self-references).
370
+ float dPath = max(sameSide ? (kb1 + pathLen12 + kb2) : (kb1 + pathLen12), 1e-6);
371
+ float dPathNex = max(sameSide ? dPath : (kb2 + pathLen12), 1e-6);
372
+
373
+ // Per-vertex colour follows the same kb / dPath ratio as texU.
374
+ // Knee corners (k1/IK1, k2/IK2) are anchored to their own particle;
375
+ // bend points (b1/b2) blend between color1 and color2 by the same
376
+ // fraction the U coord uses. Opposite-side bend uses the shorter
377
+ // denominator (kb*+len12) and IK on the far end is the other colour.
378
+ float bUcur = kb1 / dPath;
379
+ float bUnex = sameSide ? (kb2 / dPath) : (kb2 / dPathNex);
380
+ vec4 colorB1 = mix(color1, color2, bUcur);
381
+ vec4 colorB2 = mix(color2, color1, bUnex);
382
+
383
+ // vertPos holds the picked vertex in the 2D Faced frame (NDC);
384
+ // vertPos3D holds it in world space for Free. A single ladder over the
385
+ // IBO layout picks both, then gl_Position chooses by isFreeRotation.
386
+ vec2 vertPos;
387
+ vec3 vertPos3D = vec3(0.0);
388
+ vec2 vertUV;
389
+ vec4 vertColor;
390
+ if (isCur) {
391
+ // meta_cur vertices
392
+ if (vertexIndex == 0) {
393
+ vertPos = b1; vertPos3D = worldB1;
394
+ vertUV = vec2(mix(texU1, texU2, kb1 / dPath), Vtop1);
395
+ vertColor = colorB1;
396
+ } else if (vertexIndex == 1) {
397
+ vertPos = k1; vertPos3D = worldK1;
398
+ vertUV = vec2(texU1, Vtop1);
399
+ vertColor = color1;
400
+ } else if (vertexIndex == 2) {
401
+ vertPos = IK1; vertPos3D = worldIK1;
402
+ vertUV = vec2(texU1, Vbot1);
403
+ vertColor = color1;
404
+ } else {
405
+ // v3 = b2 (sameSide) or IK2 (different side)
406
+ if (sameSide) {
407
+ vertPos = b2; vertPos3D = worldB2;
408
+ vertUV = vec2(mix(texU2, texU1, kb2 / dPath), Vtop2);
409
+ vertColor = colorB2;
410
+ } else {
411
+ vertPos = IK2; vertPos3D = worldIK2;
412
+ vertUV = vec2(texU2, Vbot2);
413
+ vertColor = color2;
414
+ }
415
+ }
416
+ // noKnee fallback: collapse to k1/IK1 to produce degenerate segments.
417
+ if (noKnee1) {
418
+ if (vertexIndex == 0) { vertPos = k1; vertPos3D = worldK1; vertUV = vec2(texU1, Vtop1); vertColor = color1; }
419
+ }
420
+ } else {
421
+ // meta_nex vertices
422
+ if (sameSide) {
423
+ if (vertexIndex == 0) { vertPos = b2; vertPos3D = worldB2; vertUV = vec2(mix(texU2, texU1, kb2 / dPath), Vtop2); vertColor = colorB2; }
424
+ else if (vertexIndex == 1) { vertPos = IK1; vertPos3D = worldIK1; vertUV = vec2(texU1, Vbot1); vertColor = color1; }
425
+ else if (vertexIndex == 2) { vertPos = IK2; vertPos3D = worldIK2; vertUV = vec2(texU2, Vbot2); vertColor = color2; }
426
+ else { vertPos = k2; vertPos3D = worldK2; vertUV = vec2(texU2, Vtop2); vertColor = color2; }
427
+ } else {
428
+ if (vertexIndex == 0) { vertPos = IK2; vertPos3D = worldIK2; vertUV = vec2(texU2, Vbot2); vertColor = color2; }
429
+ else if (vertexIndex == 1) { vertPos = IK1; vertPos3D = worldIK1; vertUV = vec2(texU1, Vbot1); vertColor = color1; }
430
+ else if (vertexIndex == 2) { vertPos = b2; vertPos3D = worldB2; vertUV = vec2(mix(texU2, texU1, kb2 / dPathNex), Vtop2); vertColor = colorB2; }
431
+ else { vertPos = k2; vertPos3D = worldK2; vertUV = vec2(texU2, Vtop2); vertColor = color2; }
432
+ }
433
+ if (noKnee2) {
434
+ if (vertexIndex == 3) { vertPos = IK2; vertPos3D = worldIK2; vertUV = vec2(texU2, Vbot2); vertColor = color2; }
435
+ }
436
+ }
437
+
438
+ // Free projects the world-space vertex through MVP directly; Faced
439
+ // reconstructs clip space from aspect-corrected NDC (mirror of preview3).
440
+ if (isFreeRotation) {
441
+ gl_Position = mvpMatrix * vec4(vertPos3D, 1.0);
442
+ } else if (nearPlaneClipped) {
443
+ // At least one anchor is at / behind the near plane → the NDC math
444
+ // is garbage; emit a point outside the [-w,w] cube to discard.
445
+ gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
446
+ } else {
447
+ // Inverse aspect, × anchor w; reuse anchor z/w for a consistent
448
+ // perspective divide within a pair-half.
449
+ vec4 anchorProj = isCur ? sp1 : sp2;
450
+ vec2 vertPosClip = vertPos / aspectMul;
451
+ gl_Position = vec4(vertPosClip * anchorProj.w, anchorProj.zw);
452
+ }
453
+ vColor = vertColor;
454
+
455
+ // Strip emits *raw* (U, V) — fragment wraps U via fract() before
456
+ // scaling into the atlas slot. Otherwise a Distance/PerSegment U
457
+ // > 1 would step into the neighbouring atlas tile.
458
+ vTexCoord = vertUV;
459
+ vAtlasRemap = uTexRemaps[batchTexIdx];
460
+ vStripPath = 1;
461
+ return;
462
+ }
463
+ // ─── End strip path ───────────────────────────────────────────────────
464
+
465
+ // Texel 1: originX, originY, sizeX, sizeY
466
+ vec2 origin = t1.rg;
467
+ vec2 size = t1.ba;
468
+
469
+ // Texel 3: packed colorRGBA (uint32 bits in float), packed gridConfig (uint32 bits), gridIndex
470
+ uint packedColor = floatBitsToUint(t3.r);
471
+ vColor = vec4(
472
+ float(packedColor & 0xFFu) / 255.0,
473
+ float((packedColor >> 8u) & 0xFFu) / 255.0,
474
+ float((packedColor >> 16u) & 0xFFu) / 255.0,
475
+ float((packedColor >> 24u) & 0xFFu) / 255.0
476
+ );
477
+
478
+ uint gridConfig = floatBitsToUint(t3.g);
479
+ float gridWidth = max(float(gridConfig & 0xFFFFu), 1.0);
480
+ float gridHeight = max(float((gridConfig >> 16u) & 0xFFFFu), 1.0);
481
+ // gridIndex must be truncated to an integer to pick a single cell —
482
+ // otherwise a fractional index (animated frames) blends UVs from two
483
+ // adjacent cells horizontally (gx = mod(frac, W) leaks the fractional
484
+ // part into the column offset). Matches the original int(*data.inIndex_) cast.
485
+ float gridIndex = floor(t3.b);
486
+
487
+ // Texel 2: rotation
488
+ vec3 xaxis, yaxis;
489
+
490
+ if (rotationType == 0u) {
491
+ float angle = t2.r;
492
+ float rad = angle * 3.14159265 / 180.0;
493
+ float s = -sin(rad);
494
+ float c = cos(rad);
495
+ xaxis = uCameraRight * c + uCameraUp * s;
496
+ yaxis = -uCameraRight * s + uCameraUp * c;
497
+ } else if (rotationType == 1u) {
498
+ vec4 q = t2;
499
+ float z2 = 2.0 * q.z * q.z;
500
+ float xy = 2.0 * q.x * q.y;
501
+ float wz = 2.0 * q.w * q.z;
502
+ xaxis = vec3(
503
+ 1.0 - 2.0 * q.y * q.y - z2,
504
+ xy + wz,
505
+ 2.0 * q.x * q.z - 2.0 * q.w * q.y);
506
+ yaxis = vec3(
507
+ xy - wz,
508
+ 1.0 - 2.0 * q.x * q.x - z2,
509
+ 2.0 * q.y * q.z + 2.0 * q.w * q.x);
510
+ } else {
511
+ xaxis = cross(uCameraUp, uCameraDir);
512
+ yaxis = uCameraUp;
513
+ }
514
+
515
+ xaxis *= size.x;
516
+ yaxis *= size.y;
517
+
518
+ vec2 vOrigin = ORIGIN_MUL[vertexIndex] * origin
519
+ + INV_ORIGIN_MUL[vertexIndex] * (vec2(1.0) - origin);
520
+ vec3 posDisp = vOrigin.x * xaxis + vOrigin.y * yaxis;
521
+
522
+ mat4 mvpMatrix = uViewProjMatrix * uModelMatrix;
523
+ vec3 worldPos = position + posDisp;
524
+ gl_Position = mvpMatrix * vec4(worldPos, 1.0);
525
+
526
+ // v1.0-parity near-plane culling for the perspective projection: the
527
+ // whole quad is discarded when ANY of its 4 corners is behind the near
528
+ // plane (w < 0.01 == pos.z > 0.99 * z_cam in PerspectiveProjection
529
+ // terms). w is linear in position, so the corner minimum comes from the
530
+ // w-row and the corner offsets (x in {-ox, 1-ox}, y in {-oy, 1-oy}).
531
+ // Without a projection every w is 1.0 and this is a no-op.
532
+ vec4 wRow = vec4(mvpMatrix[0].w, mvpMatrix[1].w, mvpMatrix[2].w, mvpMatrix[3].w);
533
+ float wx = dot(wRow.xyz, xaxis);
534
+ float wy = dot(wRow.xyz, yaxis);
535
+ float minW = dot(wRow.xyz, position) + wRow.w
536
+ - origin.x * wx + min(wx, 0.0)
537
+ - origin.y * wy + min(wy, 0.0);
538
+ if (minW < 0.01) {
539
+ gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
540
+ }
541
+
542
+ // Texture coordinates with grid + atlas remap
543
+ vec2 baseTexCoord = TEX_COORD[vertexIndex];
544
+
545
+ float gx = mod(gridIndex, gridWidth);
546
+ float gy = floor(gridIndex / gridWidth);
547
+ float cellW = 1.0 / gridWidth;
548
+ float cellH = 1.0 / gridHeight;
549
+
550
+ vec2 gridTexCoord = vec2(
551
+ (gx + baseTexCoord.x) * cellW,
552
+ 1.0 - (gy + 1.0 - baseTexCoord.y) * cellH
553
+ );
554
+
555
+ // Quad keeps the old vertex-side atlas remap so existing IBCT
556
+ // snapshots (interpolated vTexCoord = final atlas UV) stay bit-
557
+ // perfect identical. Strip uses the fragment-side wrap path
558
+ // (vStripPath = 1) for atlas-aware tiling of accumulated U.
559
+ vec4 remap = uTexRemaps[batchTexIdx];
560
+ vTexCoord = remap.xy + gridTexCoord * remap.zw;
561
+ vAtlasRemap = remap; // unused by fragment when vStripPath == 0
562
+ vStripPath = 0;
563
+ }
564
+ `,fragmentShaderSource=`#version 300 es
565
+ precision highp float;
566
+
567
+ uniform sampler2D uTextures[8];
568
+ // Host scene-graph container/world alpha. The particle already carries its own
569
+ // per-particle alpha in vColor; this is the global multiplier (e.g. a faded
570
+ // parent). Mirrors JS1.0, which scaled vertex.color[3] by worldAlpha.
571
+ uniform float uWorldAlpha;
572
+
573
+ in vec2 vTexCoord;
574
+ in vec4 vColor;
575
+ flat in int vBatchTextureIndex;
576
+ flat in vec4 vAtlasRemap;
577
+ flat in int vStripPath;
578
+
579
+ out vec4 fragColor;
580
+
581
+ void main() {
582
+ // Quad path (vStripPath == 0): vTexCoord is already the final atlas
583
+ // UV multiplied through in the vertex stage — kept bit-perfect with
584
+ // the original quad shader so IBCT snapshots remain identical.
585
+ //
586
+ // Strip path (vStripPath == 1): vTexCoord is a raw [0;..]-domain UV
587
+ // because the strip's accumulated U can exceed 1 (Distance mode
588
+ // accumulates dist / repeat, PerSegment counts up by 1 per segment).
589
+ // We wrap U via mod-style fract per-pixel before scaling into the
590
+ // emitter's atlas slot, so it tiles inside the slot instead of
591
+ // bleeding into a neighbouring atlas tile. fract(1.0) == 0.0, so a
592
+ // plain fract() would collapse U == 1.0 to U == 0.0 — we leave
593
+ // exact-[0;1] U values alone and only wrap outside that range.
594
+ // Atlas-space gradients of the RAW (un-wrapped) strip U. Hoisted above the
595
+ // strip/quad branch so the derivatives are taken in uniform control flow.
596
+ // For the affine atlas remap, d(vec2(u,y)*remap.zw+remap.xy) == d(u,y)*remap.zw,
597
+ // so differentiate the incoming varying (branch-invariant) and scale.
598
+ vec2 tcDx = dFdx(vTexCoord);
599
+ vec2 tcDy = dFdy(vTexCoord);
600
+ vec2 gradX = tcDx * vAtlasRemap.zw;
601
+ vec2 gradY = tcDy * vAtlasRemap.zw;
602
+
603
+ vec2 atlasUV;
604
+ // useGrad: strip atlas slot → sample with the un-wrapped-U gradients so the
605
+ // frac(u) discontinuity at a tile junction no longer poisons the mip/aniso
606
+ // LOD (the "chewed" column). Non-atlas strips (identity remap) and quads
607
+ // keep the implicit-LOD texture() sample so their baselines stay identical.
608
+ bool useGrad = false;
609
+ if (vStripPath == 0) {
610
+ atlasUV = vTexCoord;
611
+ } else {
612
+ float u = vTexCoord.x;
613
+ float wrappedU = u - floor(u);
614
+ if (u >= 0.0 && u <= 1.0) wrappedU = u;
615
+ atlasUV = vec2(wrappedU, vTexCoord.y) * vAtlasRemap.zw + vAtlasRemap.xy;
616
+ bool isAtlas = vAtlasRemap != vec4(0.0, 0.0, 1.0, 1.0);
617
+ useGrad = isAtlas;
618
+ }
619
+
620
+ vec4 texColor;
621
+ // WebGL2 requires static indexing for sampler arrays.
622
+ if (vBatchTextureIndex == 0) texColor = useGrad ? textureGrad(uTextures[0], atlasUV, gradX, gradY) : texture(uTextures[0], atlasUV);
623
+ else if (vBatchTextureIndex == 1) texColor = useGrad ? textureGrad(uTextures[1], atlasUV, gradX, gradY) : texture(uTextures[1], atlasUV);
624
+ else if (vBatchTextureIndex == 2) texColor = useGrad ? textureGrad(uTextures[2], atlasUV, gradX, gradY) : texture(uTextures[2], atlasUV);
625
+ else if (vBatchTextureIndex == 3) texColor = useGrad ? textureGrad(uTextures[3], atlasUV, gradX, gradY) : texture(uTextures[3], atlasUV);
626
+ else if (vBatchTextureIndex == 4) texColor = useGrad ? textureGrad(uTextures[4], atlasUV, gradX, gradY) : texture(uTextures[4], atlasUV);
627
+ else if (vBatchTextureIndex == 5) texColor = useGrad ? textureGrad(uTextures[5], atlasUV, gradX, gradY) : texture(uTextures[5], atlasUV);
628
+ else if (vBatchTextureIndex == 6) texColor = useGrad ? textureGrad(uTextures[6], atlasUV, gradX, gradY) : texture(uTextures[6], atlasUV);
629
+ else texColor = useGrad ? textureGrad(uTextures[7], atlasUV, gradX, gradY) : texture(uTextures[7], atlasUV);
630
+
631
+ fragColor = texColor * vColor;
632
+ // Apply host world alpha. texColor is premultiplied and the blend funcs use
633
+ // a src factor of ONE (NORMAL) / ONE,ONE (ADD), so the whole RGBA must be
634
+ // scaled — fading RGB, not just the alpha channel — to dim correctly in both
635
+ // alpha-blended and additive modes.
636
+ fragColor *= uWorldAlpha;
637
+ }
638
+ `,vertexShaderSourceGL1=`// WebGL1 (GLSL ES 1.00) port of particle.vert — variant C: the RGBA32F data
639
+ // texture is sampled with texture2DLod + computed UV, the u32-bit-packed
640
+ // fields come from the RGBA8 meta texture (ES 1.00 has no floatBitsToUint),
641
+ // and vertex identity comes from the static aId attribute (no gl_VertexID).
642
+ // Design: docs/tdd/TDD_js-v1.1-webgl1.md §4.5.
643
+ precision highp float;
644
+
645
+ attribute float aId; // particleIndex * 4 + corner
646
+
647
+ uniform sampler2D uDataTexture; // RGBA/FLOAT, 4 texels per particle
648
+ uniform sampler2D uMetaTexture; // RGBA8, same dimensions (meta texel i ~ data texel i)
649
+ uniform vec2 uDataTexSize;
650
+ uniform vec3 uCameraRight;
651
+ uniform vec3 uCameraUp;
652
+ uniform vec3 uCameraDir;
653
+ uniform mat4 uViewProjMatrix;
654
+ uniform mat4 uModelMatrix;
655
+ uniform float uViewportAspect; // viewport width / height — strip-only
656
+ uniform vec4 uTexRemaps[8];
657
+
658
+ varying vec2 vTexCoord;
659
+ varying vec4 vColor;
660
+ // Constant across the 4 vertices of a particle, so plain (smooth) varyings
661
+ // interpolate to the same value — ES 1.00 has no \`flat\`. The fragment stage
662
+ // re-quantizes vBatchTextureIndex via floor(x + 0.5).
663
+ varying float vBatchTextureIndex;
664
+ varying vec4 vAtlasRemap;
665
+ varying float vStripPath;
666
+
667
+ // idx -> row/column with the x-range fix-up: floor(idx/W) is exact on an
668
+ // IEEE-correct driver for idx < 2^24 (TDD §4.7), the fix-up defends against
669
+ // the division imprecision GLSL ES 1.00 permits.
670
+ vec2 texelUv(float idx) {
671
+ float y = floor(idx / uDataTexSize.x);
672
+ float x = idx - y * uDataTexSize.x;
673
+ if (x < 0.0) { y -= 1.0; x += uDataTexSize.x; }
674
+ if (x >= uDataTexSize.x) { y += 1.0; x -= uDataTexSize.x; }
675
+ return (vec2(x, y) + 0.5) / uDataTexSize;
676
+ }
677
+
678
+ vec4 fetchData(float idx) { return texture2DLod(uDataTexture, texelUv(idx), 0.0); }
679
+ vec4 fetchMeta(float idx) { return texture2DLod(uMetaTexture, texelUv(idx), 0.0); }
680
+
681
+ // Exact byte recovery from an RGBA8 UNORM sample (b/255 is spec-exact).
682
+ float byteOf(float v) { return floor(v * 255.0 + 0.5); }
683
+
684
+ // Strip geometry helper (port of GPU33 makeScreenSpaceStrip).
685
+ vec2 stripOrtho(vec2 v) {
686
+ return vec2(-v.y, v.x);
687
+ }
688
+
689
+ // Corner tables (ES 1.00 has no const-array initializers — if-ladder like
690
+ // the CS1.2 GL cornerTables()).
691
+ vec2 originMul(float corner) {
692
+ if (corner < 0.5) return vec2(-1.0, 0.0);
693
+ if (corner < 1.5) return vec2(-1.0, -1.0);
694
+ if (corner < 2.5) return vec2(0.0, -1.0);
695
+ return vec2(0.0, 0.0);
696
+ }
697
+ vec2 invOriginMul(float corner) {
698
+ if (corner < 0.5) return vec2(0.0, 1.0);
699
+ if (corner < 1.5) return vec2(0.0, 0.0);
700
+ if (corner < 2.5) return vec2(1.0, 0.0);
701
+ return vec2(1.0, 1.0);
702
+ }
703
+ vec2 texCoordOf(float corner) {
704
+ if (corner < 0.5) return vec2(0.0, 0.0);
705
+ if (corner < 1.5) return vec2(0.0, 1.0);
706
+ if (corner < 2.5) return vec2(1.0, 1.0);
707
+ return vec2(1.0, 0.0);
708
+ }
709
+
710
+ void main() {
711
+ // Canonical aId decomposition (exact for aId < 2^24, TDD §4.5).
712
+ float corner = mod(aId, 4.0);
713
+ float particleIndex = (aId - corner) * 0.25;
714
+ float baseTexel = particleIndex * 4.0;
715
+
716
+ vec4 t0 = fetchData(baseTexel);
717
+ vec4 t1 = fetchData(baseTexel + 1.0);
718
+ vec4 t2 = fetchData(baseTexel + 2.0);
719
+ vec4 t3 = fetchData(baseTexel + 3.0);
720
+
721
+ // Meta M0: flags bytes — [batchTexIdx, rotationType|ctorType bits, -, -].
722
+ vec4 m0 = fetchMeta(baseTexel);
723
+ float batchTexIdx = byteOf(m0.r);
724
+ float flagsByte1 = byteOf(m0.g);
725
+ float rotationType = mod(flagsByte1, 4.0); // bits 1:0
726
+ float ctorType = mod(floor(flagsByte1 * 0.25), 4.0); // bits 3:2
727
+ vBatchTextureIndex = batchTexIdx;
728
+
729
+ vec3 position = t0.gba;
730
+
731
+ // ─── Strip path ───────────────────────────────────────────────────────
732
+ // Port of the ES 3.00 strip branch (particle.vert). Same algorithm and
733
+ // record conventions; deltas are purely mechanical: texel fetches via
734
+ // texture2DLod, packed colors read directly from meta M1 (the sample IS
735
+ // the color), neighbor RECORD indices decoded from 3 meta bytes
736
+ // (M2 @ pair-cur = neighborIdxP0, M3 @ pair-nex = neighborIdxP3), and
737
+ // the corner/int ladders in float math.
738
+ if (ctorType >= 0.5) {
739
+ bool isCur = (ctorType < 1.5);
740
+ float siblingTexel = isCur ? (baseTexel + 4.0) : (baseTexel - 4.0);
741
+ vec4 ts0 = fetchData(siblingTexel);
742
+ vec4 ts1 = fetchData(siblingTexel + 1.0);
743
+
744
+ // Pair bases: cur = the "p1" end, nex = the "p2" end.
745
+ float curBase = isCur ? baseTexel : siblingTexel;
746
+ float nexBase = isCur ? siblingTexel : baseTexel;
747
+
748
+ // p1 always = position of the meta_cur end; p2 = meta_nex end.
749
+ vec3 p1 = isCur ? t0.gba : ts0.gba;
750
+ vec3 p2 = isCur ? ts0.gba : t0.gba;
751
+
752
+ float halfSize1 = isCur ? t1.r : ts1.r;
753
+ float halfSize2 = isCur ? ts1.r : t1.r;
754
+ float texU1 = isCur ? t1.g : ts1.g;
755
+ float texU2 = isCur ? ts1.g : t1.g;
756
+
757
+ // Colors: the meta M1 sample is the normalized RGBA directly.
758
+ vec4 color1 = fetchMeta(curBase + 1.0);
759
+ vec4 color2 = fetchMeta(nexBase + 1.0);
760
+
761
+ // Neighbor RECORD indices (system.js convention: neighborIdxP0 on the
762
+ // pair-cur meta, neighborIdxP3 on the pair-nex meta; boundary
763
+ // sentinel = the pair's cur record index).
764
+ vec4 mNp0 = fetchMeta(curBase + 2.0);
765
+ vec4 mNp3 = fetchMeta(nexBase + 3.0);
766
+ float np0 = byteOf(mNp0.r) + byteOf(mNp0.g) * 256.0 + byteOf(mNp0.b) * 65536.0;
767
+ float np3 = byteOf(mNp3.r) + byteOf(mNp3.g) * 256.0 + byteOf(mNp3.b) * 65536.0;
768
+ float selfPairCurRecord = curBase * 0.25;
769
+
770
+ vec3 p0 = (abs(np0 - selfPairCurRecord) < 0.5)
771
+ ? p1
772
+ : fetchData(np0 * 4.0).gba;
773
+ vec3 p3 = (abs(np3 - selfPairCurRecord) < 0.5)
774
+ ? p2
775
+ : fetchData(np3 * 4.0).gba;
776
+
777
+ // Segment frame: Faced (rotationType 0/2) = screen-space NDC frame;
778
+ // Free (rotationType 1) = full world-space 3D frame. Mirror of the
779
+ // ES 3.00 shader / preview3 DataTextureRenderer3.
780
+ mat4 mvpMatrix = uViewProjMatrix * uModelMatrix;
781
+ bool isFreeRotation = (rotationType > 0.5 && rotationType < 1.5);
782
+
783
+ // Anchor projections — both branches use them for gl_Position.
784
+ vec4 sp1 = mvpMatrix * vec4(p1, 1.0);
785
+ vec4 sp2 = mvpMatrix * vec4(p2, 1.0);
786
+
787
+ // Near-plane guard (Faced only; the world-space Free frame is
788
+ // unaffected by the sign of w).
789
+ bool nearPlaneClipped = (sp1.w < 1e-3) || (sp2.w < 1e-3);
790
+
791
+ // Aspect correction for the 2D Faced ortho on non-square viewports.
792
+ vec2 aspectMul = vec2(1.0, 1.0 / max(uViewportAspect, 1e-6));
793
+
794
+ vec4 ts2 = fetchData(siblingTexel + 2.0);
795
+ vec4 q1 = isCur ? t2 : ts2;
796
+ vec4 q2 = isCur ? ts2 : t2;
797
+
798
+ // Frame outputs: Faced fills the 2D (k/b/IK), Free the 3D
799
+ // (worldK/B/IK); the vertex ladder picks by isFreeRotation.
800
+ vec3 worldK1, worldB1, worldIK1, worldK2, worldB2, worldIK2;
801
+ vec2 k1, b1, IK1, k2, b2, IK2;
802
+ float Bmul1, Bmul2, kb1, kb2;
803
+ bool noKnee1, noKnee2;
804
+ float pathLen12; // len12 in the active frame's units
805
+
806
+ // Initialize both frames (ES 1.00 warns on possibly-uninitialized
807
+ // locals across the branch below on some compilers).
808
+ worldK1 = vec3(0.0); worldB1 = vec3(0.0); worldIK1 = vec3(0.0);
809
+ worldK2 = vec3(0.0); worldB2 = vec3(0.0); worldIK2 = vec3(0.0);
810
+ k1 = vec2(0.0); b1 = vec2(0.0); IK1 = vec2(0.0);
811
+ k2 = vec2(0.0); b2 = vec2(0.0); IK2 = vec2(0.0);
812
+
813
+ if (isFreeRotation) {
814
+ // World-space frame (mirror of GPU33 make3DStrip). Section normal
815
+ // n = quat z-axis; B = cross(dir12, n).
816
+ float xz1 = 2.0 * q1.x * q1.z;
817
+ float wy1 = 2.0 * q1.w * q1.y;
818
+ float yz1 = 2.0 * q1.y * q1.z;
819
+ float wx1 = 2.0 * q1.w * q1.x;
820
+ vec3 n1 = vec3(xz1 + wy1, yz1 - wx1, 1.0 - 2.0 * (q1.x * q1.x + q1.y * q1.y));
821
+ float xz2 = 2.0 * q2.x * q2.z;
822
+ float wy2 = 2.0 * q2.w * q2.y;
823
+ float yz2 = 2.0 * q2.y * q2.z;
824
+ float wx2 = 2.0 * q2.w * q2.x;
825
+ vec3 n2 = vec3(xz2 + wy2, yz2 - wx2, 1.0 - 2.0 * (q2.x * q2.x + q2.y * q2.y));
826
+
827
+ vec3 wp01 = p1 - p0;
828
+ vec3 wp12 = p2 - p1;
829
+ vec3 wp23 = p3 - p2;
830
+ float wlen01 = length(wp01);
831
+ float wlen12 = length(wp12);
832
+ float wlen23 = length(wp23);
833
+ vec3 wdir12 = (wlen12 > 1e-6) ? (wp12 / wlen12) : vec3(1.0, 0.0, 0.0);
834
+ vec3 wdir01 = (wlen01 > 1e-6) ? (wp01 / wlen01) : wdir12;
835
+ vec3 wdir23 = (wlen23 > 1e-6) ? (wp23 / wlen23) : wdir12;
836
+ noKnee1 = dot(wdir01, wdir12) > 0.999;
837
+ noKnee2 = dot(wdir12, wdir23) > 0.999;
838
+ pathLen12 = wlen12;
839
+
840
+ if (!noKnee1) {
841
+ vec3 B1raw = normalize(cross(wdir12, n1));
842
+ vec3 c1 = normalize(cross(wdir01, wdir12));
843
+ float Kmul1 = sign(dot(c1, n1));
844
+ vec3 K1 = Kmul1 * normalize(cross(wdir01 + wdir12, n1));
845
+ Bmul1 = sign(dot(B1raw, K1));
846
+ vec3 B1 = B1raw * Bmul1;
847
+ float ik1 = halfSize1 / max(dot(B1, K1), 1e-6);
848
+ if (dot(-wdir01, ik1 * -K1) > wlen01) ik1 = wlen01;
849
+ if (dot( wdir12, ik1 * -K1) > wlen12) ik1 = wlen12;
850
+ worldK1 = p1 + K1 * halfSize1;
851
+ worldB1 = p1 + B1 * halfSize1;
852
+ worldIK1 = p1 - K1 * ik1;
853
+ kb1 = distance(worldK1, worldB1);
854
+ } else {
855
+ Bmul1 = 1.0;
856
+ vec3 B1 = normalize(cross(wdir01 + wdir12, n1));
857
+ worldK1 = p1 + B1 * halfSize1;
858
+ worldB1 = worldK1;
859
+ worldIK1 = p1 - B1 * halfSize1;
860
+ kb1 = 0.0;
861
+ }
862
+
863
+ if (!noKnee2) {
864
+ vec3 B2raw = normalize(cross(wdir12, n2));
865
+ vec3 c2 = normalize(cross(wdir12, wdir23));
866
+ float Kmul2 = sign(dot(c2, n2));
867
+ vec3 K2 = Kmul2 * normalize(cross(wdir12 + wdir23, n2));
868
+ Bmul2 = sign(dot(B2raw, K2));
869
+ vec3 B2 = B2raw * Bmul2;
870
+ float ik2 = halfSize2 / max(dot(B2, K2), 1e-6);
871
+ if (dot(-wdir12, ik2 * -K2) > wlen12) ik2 = wlen12;
872
+ if (dot( wdir23, ik2 * -K2) > wlen23) ik2 = wlen23;
873
+ worldK2 = p2 + K2 * halfSize2;
874
+ worldB2 = p2 + B2 * halfSize2;
875
+ worldIK2 = p2 - K2 * ik2;
876
+ kb2 = distance(worldK2, worldB2);
877
+ } else {
878
+ Bmul2 = 1.0;
879
+ vec3 B2 = normalize(cross(wdir12 + wdir23, n2));
880
+ worldK2 = p2 + B2 * halfSize2;
881
+ worldB2 = worldK2;
882
+ worldIK2 = p2 - B2 * halfSize2;
883
+ kb2 = 0.0;
884
+ }
885
+ } else {
886
+ // Faced: screen-space NDC frame around dir12.
887
+ vec4 sp0 = mvpMatrix * vec4(p0, 1.0);
888
+ vec4 sp3 = mvpMatrix * vec4(p3, 1.0);
889
+ vec2 ndc1 = (sp1.xy / sp1.w) * aspectMul;
890
+ vec2 ndc2 = (sp2.xy / sp2.w) * aspectMul;
891
+ // Neighbor anchors behind the near plane produce garbage NDC after
892
+ // the perspective divide (possible only with Projection3D, where w
893
+ // varies); collapse them onto their segment anchor so the existing
894
+ // zero-length degeneracy path (dir01 = dir12 / dir23 = dir12)
895
+ // handles the knee instead of a poisoned miter. spU1/spU2 need no
896
+ // guard: uCameraUp has zero Z, so their w equals sp1.w / sp2.w.
897
+ vec2 ndc0 = (sp0.w < 1e-3) ? ndc1 : (sp0.xy / sp0.w) * aspectMul;
898
+ vec2 ndc3 = (sp3.w < 1e-3) ? ndc2 : (sp3.xy / sp3.w) * aspectMul;
899
+
900
+ vec2 d01 = ndc1 - ndc0;
901
+ vec2 d12 = ndc2 - ndc1;
902
+ vec2 d23 = ndc3 - ndc2;
903
+ float len01 = length(d01);
904
+ float len12 = length(d12);
905
+ float len23 = length(d23);
906
+ vec2 dir12 = (len12 > 1e-6) ? (d12 / len12) : vec2(1.0, 0.0);
907
+ vec2 dir01 = (len01 > 1e-6) ? (d01 / len01) : dir12;
908
+ vec2 dir23 = (len23 > 1e-6) ? (d23 / len23) : dir12;
909
+ noKnee1 = dot(dir01, dir12) > 0.999;
910
+ noKnee2 = dot(dir12, dir23) > 0.999;
911
+ pathLen12 = len12;
912
+
913
+ // Section magnitude: project camera_up*halfSize per anchor into NDC.
914
+ vec4 spU1 = mvpMatrix * vec4(p1 + uCameraUp * halfSize1, 1.0);
915
+ vec4 spU2 = mvpMatrix * vec4(p2 + uCameraUp * halfSize2, 1.0);
916
+ float ndcHalfSize1 = length((spU1.xy / spU1.w) * aspectMul - ndc1);
917
+ float ndcHalfSize2 = length((spU2.xy / spU2.w) * aspectMul - ndc2);
918
+
919
+ vec2 B = stripOrtho(dir12);
920
+ if (!noKnee1) {
921
+ float Kmul1 = -sign(dot(stripOrtho(dir01), dir12));
922
+ vec2 K1 = Kmul1 * normalize(stripOrtho(dir01 + dir12));
923
+ Bmul1 = sign(dot(B, K1));
924
+ vec2 B1 = B * Bmul1;
925
+ float ik1 = ndcHalfSize1 / max(dot(B1, K1), 1e-6);
926
+ if (dot(-dir01, ik1 * -K1) > len01) ik1 = len01;
927
+ if (dot(dir12, ik1 * -K1) > len12) ik1 = len12;
928
+ k1 = ndc1 + K1 * ndcHalfSize1;
929
+ b1 = ndc1 + B1 * ndcHalfSize1;
930
+ IK1 = ndc1 - K1 * ik1;
931
+ kb1 = distance(k1, b1);
932
+ } else {
933
+ vec2 B1 = normalize(stripOrtho(dir01 + dir12));
934
+ Bmul1 = 1.0;
935
+ k1 = ndc1 + B1 * ndcHalfSize1;
936
+ b1 = k1;
937
+ IK1 = ndc1 - B1 * ndcHalfSize1;
938
+ kb1 = 0.0;
939
+ }
940
+ if (!noKnee2) {
941
+ float Kmul2 = -sign(dot(stripOrtho(dir12), dir23));
942
+ vec2 K2 = Kmul2 * normalize(stripOrtho(dir12 + dir23));
943
+ Bmul2 = sign(dot(B, K2));
944
+ vec2 B2 = B * Bmul2;
945
+ float ik2 = ndcHalfSize2 / max(dot(B2, K2), 1e-6);
946
+ if (dot(-dir12, ik2 * -K2) > len12) ik2 = len12;
947
+ if (dot(dir23, ik2 * -K2) > len23) ik2 = len23;
948
+ k2 = ndc2 + K2 * ndcHalfSize2;
949
+ b2 = ndc2 + B2 * ndcHalfSize2;
950
+ IK2 = ndc2 - K2 * ik2;
951
+ kb2 = distance(k2, b2);
952
+ } else {
953
+ vec2 B2 = normalize(stripOrtho(dir12 + dir23));
954
+ Bmul2 = 1.0;
955
+ k2 = ndc2 + B2 * ndcHalfSize2;
956
+ b2 = k2;
957
+ IK2 = ndc2 - B2 * ndcHalfSize2;
958
+ kb2 = 0.0;
959
+ }
960
+ }
961
+
962
+ // V coords: V at outer knee (k) = (Bmul + 1) * 0.5; inner = 1 - outer.
963
+ float Vtop1 = (Bmul1 + 1.0) * 0.5;
964
+ float Vbot1 = 1.0 - Vtop1;
965
+ float Vtop2 = (Bmul2 + 1.0) * 0.5;
966
+ float Vbot2 = 1.0 - Vtop2;
967
+
968
+ // Vertex layout per meta over the IBO [0,1,2, 0,2,3] — same ladder as
969
+ // the ES 3.00 shader (see its comment block for the geometry).
970
+ bool sameSide = (Bmul1 * Bmul2 >= 0.0);
971
+ float dPath = max(sameSide ? (kb1 + pathLen12 + kb2) : (kb1 + pathLen12), 1e-6);
972
+ float dPathNex = max(sameSide ? dPath : (kb2 + pathLen12), 1e-6);
973
+
974
+ float bUcur = kb1 / dPath;
975
+ float bUnex = sameSide ? (kb2 / dPath) : (kb2 / dPathNex);
976
+ vec4 colorB1 = mix(color1, color2, bUcur);
977
+ vec4 colorB2 = mix(color2, color1, bUnex);
978
+
979
+ vec2 vertPos;
980
+ vec3 vertPos3D = vec3(0.0);
981
+ vec2 vertUV;
982
+ vec4 vertColor;
983
+ if (isCur) {
984
+ // meta_cur vertices
985
+ if (corner < 0.5) {
986
+ vertPos = b1; vertPos3D = worldB1;
987
+ vertUV = vec2(mix(texU1, texU2, kb1 / dPath), Vtop1);
988
+ vertColor = colorB1;
989
+ } else if (corner < 1.5) {
990
+ vertPos = k1; vertPos3D = worldK1;
991
+ vertUV = vec2(texU1, Vtop1);
992
+ vertColor = color1;
993
+ } else if (corner < 2.5) {
994
+ vertPos = IK1; vertPos3D = worldIK1;
995
+ vertUV = vec2(texU1, Vbot1);
996
+ vertColor = color1;
997
+ } else {
998
+ // v3 = b2 (sameSide) or IK2 (different side)
999
+ if (sameSide) {
1000
+ vertPos = b2; vertPos3D = worldB2;
1001
+ vertUV = vec2(mix(texU2, texU1, kb2 / dPath), Vtop2);
1002
+ vertColor = colorB2;
1003
+ } else {
1004
+ vertPos = IK2; vertPos3D = worldIK2;
1005
+ vertUV = vec2(texU2, Vbot2);
1006
+ vertColor = color2;
1007
+ }
1008
+ }
1009
+ // noKnee fallback: collapse to k1/IK1 to produce degenerate segments.
1010
+ if (noKnee1) {
1011
+ if (corner < 0.5) { vertPos = k1; vertPos3D = worldK1; vertUV = vec2(texU1, Vtop1); vertColor = color1; }
1012
+ }
1013
+ } else {
1014
+ // meta_nex vertices
1015
+ if (sameSide) {
1016
+ if (corner < 0.5) { vertPos = b2; vertPos3D = worldB2; vertUV = vec2(mix(texU2, texU1, kb2 / dPath), Vtop2); vertColor = colorB2; }
1017
+ else if (corner < 1.5) { vertPos = IK1; vertPos3D = worldIK1; vertUV = vec2(texU1, Vbot1); vertColor = color1; }
1018
+ else if (corner < 2.5) { vertPos = IK2; vertPos3D = worldIK2; vertUV = vec2(texU2, Vbot2); vertColor = color2; }
1019
+ else { vertPos = k2; vertPos3D = worldK2; vertUV = vec2(texU2, Vtop2); vertColor = color2; }
1020
+ } else {
1021
+ if (corner < 0.5) { vertPos = IK2; vertPos3D = worldIK2; vertUV = vec2(texU2, Vbot2); vertColor = color2; }
1022
+ else if (corner < 1.5) { vertPos = IK1; vertPos3D = worldIK1; vertUV = vec2(texU1, Vbot1); vertColor = color1; }
1023
+ else if (corner < 2.5) { vertPos = b2; vertPos3D = worldB2; vertUV = vec2(mix(texU2, texU1, kb2 / dPathNex), Vtop2); vertColor = colorB2; }
1024
+ else { vertPos = k2; vertPos3D = worldK2; vertUV = vec2(texU2, Vtop2); vertColor = color2; }
1025
+ }
1026
+ if (noKnee2) {
1027
+ if (corner > 2.5) { vertPos = IK2; vertPos3D = worldIK2; vertUV = vec2(texU2, Vbot2); vertColor = color2; }
1028
+ }
1029
+ }
1030
+
1031
+ // Free projects the world-space vertex through MVP directly; Faced
1032
+ // reconstructs clip space from aspect-corrected NDC.
1033
+ if (isFreeRotation) {
1034
+ gl_Position = mvpMatrix * vec4(vertPos3D, 1.0);
1035
+ } else if (nearPlaneClipped) {
1036
+ gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
1037
+ } else {
1038
+ vec4 anchorProj = isCur ? sp1 : sp2;
1039
+ vec2 vertPosClip = vertPos / aspectMul;
1040
+ gl_Position = vec4(vertPosClip * anchorProj.w, anchorProj.zw);
1041
+ }
1042
+ vColor = vertColor;
1043
+
1044
+ // Strip emits raw (U, V) — fragment wraps U into the atlas slot.
1045
+ vTexCoord = vertUV;
1046
+ vAtlasRemap = uTexRemaps[int(batchTexIdx)];
1047
+ vStripPath = 1.0;
1048
+ return;
1049
+ }
1050
+ // ─── End strip path ─────────────────────────────────────────────────────
1051
+
1052
+ // Texel 1: originX, originY, sizeX, sizeY
1053
+ vec2 origin = t1.rg;
1054
+ vec2 size = t1.ba;
1055
+
1056
+ // Meta M1: the packedColor u32 LE bytes ARE the normalized RGBA sample.
1057
+ vColor = fetchMeta(baseTexel + 1.0);
1058
+
1059
+ // Meta M2: gridConfig — [gridW lo, gridW hi, gridH lo, gridH hi].
1060
+ vec4 m2 = fetchMeta(baseTexel + 2.0);
1061
+ float gridWidth = max(byteOf(m2.r) + byteOf(m2.g) * 256.0, 1.0);
1062
+ float gridHeight = max(byteOf(m2.b) + byteOf(m2.a) * 256.0, 1.0);
1063
+ // gridIndex is a plain float — read from the float texture (mirror of the
1064
+ // ES 3.00 shader: truncate so animated frames pick a single cell).
1065
+ float gridIndex = floor(t3.b);
1066
+
1067
+ // Texel 2: rotation
1068
+ vec3 xaxis, yaxis;
1069
+
1070
+ if (rotationType < 0.5) {
1071
+ float angle = t2.r;
1072
+ float rad = angle * 3.14159265 / 180.0;
1073
+ float s = -sin(rad);
1074
+ float c = cos(rad);
1075
+ xaxis = uCameraRight * c + uCameraUp * s;
1076
+ yaxis = -uCameraRight * s + uCameraUp * c;
1077
+ } else if (rotationType < 1.5) {
1078
+ vec4 q = t2;
1079
+ float z2 = 2.0 * q.z * q.z;
1080
+ float xy = 2.0 * q.x * q.y;
1081
+ float wz = 2.0 * q.w * q.z;
1082
+ xaxis = vec3(
1083
+ 1.0 - 2.0 * q.y * q.y - z2,
1084
+ xy + wz,
1085
+ 2.0 * q.x * q.z - 2.0 * q.w * q.y);
1086
+ yaxis = vec3(
1087
+ xy - wz,
1088
+ 1.0 - 2.0 * q.x * q.x - z2,
1089
+ 2.0 * q.y * q.z + 2.0 * q.w * q.x);
1090
+ } else {
1091
+ xaxis = cross(uCameraUp, uCameraDir);
1092
+ yaxis = uCameraUp;
1093
+ }
1094
+
1095
+ xaxis *= size.x;
1096
+ yaxis *= size.y;
1097
+
1098
+ vec2 vOrigin = originMul(corner) * origin
1099
+ + invOriginMul(corner) * (vec2(1.0) - origin);
1100
+ vec3 posDisp = vOrigin.x * xaxis + vOrigin.y * yaxis;
1101
+
1102
+ mat4 mvpMatrix = uViewProjMatrix * uModelMatrix;
1103
+ vec3 worldPos = position + posDisp;
1104
+ gl_Position = mvpMatrix * vec4(worldPos, 1.0);
1105
+
1106
+ // v1.0-parity near-plane culling for the perspective projection: the
1107
+ // whole quad is discarded when ANY of its 4 corners is behind the near
1108
+ // plane (w < 0.01 == pos.z > 0.99 * z_cam in PerspectiveProjection
1109
+ // terms). w is linear in position, so the corner minimum comes from the
1110
+ // w-row and the corner offsets (x in {-ox, 1-ox}, y in {-oy, 1-oy}).
1111
+ // Without a projection every w is 1.0 and this is a no-op.
1112
+ vec4 wRow = vec4(mvpMatrix[0].w, mvpMatrix[1].w, mvpMatrix[2].w, mvpMatrix[3].w);
1113
+ float wx = dot(wRow.xyz, xaxis);
1114
+ float wy = dot(wRow.xyz, yaxis);
1115
+ float minW = dot(wRow.xyz, position) + wRow.w
1116
+ - origin.x * wx + min(wx, 0.0)
1117
+ - origin.y * wy + min(wy, 0.0);
1118
+ if (minW < 0.01) {
1119
+ gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
1120
+ }
1121
+
1122
+ // Texture coordinates with grid + atlas remap
1123
+ vec2 baseTexCoord = texCoordOf(corner);
1124
+
1125
+ float gx = mod(gridIndex, gridWidth);
1126
+ float gy = floor(gridIndex / gridWidth);
1127
+ float cellW = 1.0 / gridWidth;
1128
+ float cellH = 1.0 / gridHeight;
1129
+
1130
+ vec2 gridTexCoord = vec2(
1131
+ (gx + baseTexCoord.x) * cellW,
1132
+ 1.0 - (gy + 1.0 - baseTexCoord.y) * cellH
1133
+ );
1134
+
1135
+ // Dynamic indexing of a uniform array is legal in ES 1.00 VERTEX shaders
1136
+ // (only fragment shaders are constant-index restricted).
1137
+ vec4 remap = uTexRemaps[int(batchTexIdx)];
1138
+ vTexCoord = remap.xy + gridTexCoord * remap.zw;
1139
+ vAtlasRemap = remap; // unused by fragment when vStripPath == 0
1140
+ vStripPath = 0.0;
1141
+ }
1142
+ `,fragmentShaderSourceGL1=`// WebGL1 (GLSL ES 1.00) port of particle.frag. Fragment highp is a REQUIRED
1143
+ // capability of the GL1 path (probed at Context creation) — the strip U-wrap
1144
+ // does floor/fract on accumulated texU which mediump would corrupt; the
1145
+ // #else branch only keeps the shader compilable on probe-rejected devices.
1146
+ #ifdef GL_FRAGMENT_PRECISION_HIGH
1147
+ precision highp float;
1148
+ #else
1149
+ precision mediump float;
1150
+ #endif
1151
+
1152
+ uniform sampler2D uTextures[8];
1153
+ // Host scene-graph container/world alpha (same semantics as the ES 3.00
1154
+ // shader: scales the whole premultiplied RGBA).
1155
+ uniform float uWorldAlpha;
1156
+
1157
+ varying vec2 vTexCoord;
1158
+ varying vec4 vColor;
1159
+ varying float vBatchTextureIndex; // constant across the quad; re-quantized below
1160
+ varying vec4 vAtlasRemap;
1161
+ varying float vStripPath;
1162
+
1163
+ void main() {
1164
+ // Quad path (vStripPath == 0): vTexCoord is the final atlas UV from the
1165
+ // vertex stage. Strip path (vStripPath == 1): raw U wrapped per-pixel
1166
+ // into the atlas slot; exact-[0;1] U is left alone (fract(1.0) == 0.0
1167
+ // would collapse the strip end) — mirror of the ES 3.00 shader.
1168
+ vec2 atlasUV;
1169
+ if (vStripPath < 0.5) {
1170
+ atlasUV = vTexCoord;
1171
+ } else {
1172
+ float u = vTexCoord.x;
1173
+ float wrappedU = u - floor(u);
1174
+ if (u >= 0.0 && u <= 1.0) wrappedU = u;
1175
+ atlasUV = vec2(wrappedU, vTexCoord.y) * vAtlasRemap.zw + vAtlasRemap.xy;
1176
+ }
1177
+
1178
+ // ES 1.00 fragment shaders require constant sampler indexing — the same
1179
+ // if-ladder as WebGL2, driven by the re-quantized float varying.
1180
+ float texIdx = floor(vBatchTextureIndex + 0.5);
1181
+ vec4 texColor;
1182
+ if (texIdx < 0.5) texColor = texture2D(uTextures[0], atlasUV);
1183
+ else if (texIdx < 1.5) texColor = texture2D(uTextures[1], atlasUV);
1184
+ else if (texIdx < 2.5) texColor = texture2D(uTextures[2], atlasUV);
1185
+ else if (texIdx < 3.5) texColor = texture2D(uTextures[3], atlasUV);
1186
+ else if (texIdx < 4.5) texColor = texture2D(uTextures[4], atlasUV);
1187
+ else if (texIdx < 5.5) texColor = texture2D(uTextures[5], atlasUV);
1188
+ else if (texIdx < 6.5) texColor = texture2D(uTextures[6], atlasUV);
1189
+ else texColor = texture2D(uTextures[7], atlasUV);
1190
+
1191
+ // texColor is premultiplied and blend src factors are ONE-based, so the
1192
+ // world alpha scales the whole RGBA (mirror of the ES 3.00 shader).
1193
+ gl_FragColor = texColor * vColor * uWorldAlpha;
1194
+ }
1195
+ `,x=class x{constructor(e,t){i(this,"neutrino");i(this,"renderer");i(this,"gl");i(this,"options");i(this,"loadData");i(this,"_shaderProgram",null);i(this,"_indexBuffer",null);i(this,"_dummyVB",null);i(this,"_indexBufferCapacity",0);i(this,"_vao",null);i(this,"_isWebGL1",!1);i(this,"_maxBatchTextures",x.MAX_BATCH_TEXTURES);i(this,"_hasElementIndexUint",!1);i(this,"_vaoExt",null);i(this,"_aIdBuffer",null);i(this,"_aIdLocation",-1);i(this,"_uDataTexture",null);i(this,"_uDataTextureWidth",null);i(this,"_uMetaTexture",null);i(this,"_uDataTexSize",null);i(this,"_uCameraRight",null);i(this,"_uCameraUp",null);i(this,"_uCameraDir",null);i(this,"_uViewProjMatrix",null);i(this,"_uModelMatrix",null);i(this,"_uViewportAspect",null);i(this,"_uWorldAlpha",null);i(this,"_uTextures",[]);i(this,"_uTexRemaps",[]);i(this,"_noiseInitialized");var n;if(this.renderer=e,this.gl=e.gl,!this.gl)throw new Error("NeutrinoParticles (js-v1.1): a WebGL renderer is required.");this.options=Object.assign({texturesBasePath:"",trimmedExtensionsLookupFirst:!0},t),this.neutrino=new Neutrino__namespace.Context,this.loadData={__neutrinoContext__:this},this._noiseInitialized=!1;const r=((n=e.context)==null?void 0:n.webGLVersion)??(typeof this.gl.createVertexArray=="function"?2:1);this._isWebGL1=r===1,this._isWebGL1&&this._probeWebGL1Capabilities(),this._initShader(),this._isWebGL1&&this._smokeTestVertexTextureFetch()}initializeNoise(e,t,r){if(this._noiseInitialized){t&&t();return}this.neutrino.initializeNoise(e,()=>{this._noiseInitialized=!0,t&&t()},r)}generateNoise(){if(this._noiseInitialized)return;const e=new this.neutrino.NoiseGenerator;for(;!e.step(););this._noiseInitialized=!0}get shaderProgram(){return this._shaderProgram}get isWebGL1(){return this._isWebGL1}get maxBatchTextures(){return this._maxBatchTextures}get uDataTexture(){return this._uDataTexture}get uDataTextureWidth(){return this._uDataTextureWidth}get uMetaTexture(){return this._uMetaTexture}get uDataTexSize(){return this._uDataTexSize}get uCameraRight(){return this._uCameraRight}get uCameraUp(){return this._uCameraUp}get uCameraDir(){return this._uCameraDir}get uViewProjMatrix(){return this._uViewProjMatrix}get uModelMatrix(){return this._uModelMatrix}get uViewportAspect(){return this._uViewportAspect}get uWorldAlpha(){return this._uWorldAlpha}get uTextures(){return this._uTextures}get uTexRemaps(){return this._uTexRemaps}ensureIndexBuffer(e){if(e<=this._indexBufferCapacity)return;const t=this.gl;this._indexBuffer&&t.deleteBuffer(this._indexBuffer),this._dummyVB&&(t.deleteBuffer(this._dummyVB),this._dummyVB=null),this._vao&&t.deleteVertexArray(this._vao),this._aIdBuffer&&(t.deleteBuffer(this._aIdBuffer),this._aIdBuffer=null);const r=e>16383;if(r&&this._isWebGL1&&!this._hasElementIndexUint)throw new Error(`NeutrinoParticles (js-v1.1): the effect needs 32-bit indices (${e} particles) but this WebGL1 context lacks OES_element_index_uint.`);const n=r?new Uint32Array(e*6):new Uint16Array(e*6);for(let o=0;o<e;o++){const s=o*4,a=o*6;n[a+0]=s+0,n[a+1]=s+1,n[a+2]=s+2,n[a+3]=s+0,n[a+4]=s+2,n[a+5]=s+3}if(this._indexBuffer=t.createBuffer(),t.bindBuffer(t.ELEMENT_ARRAY_BUFFER,this._indexBuffer),t.bufferData(t.ELEMENT_ARRAY_BUFFER,n,t.STATIC_DRAW),this._isWebGL1){const o=new Float32Array(e*4);for(let s=0;s<o.length;s++)o[s]=s;this._aIdBuffer=t.createBuffer(),t.bindBuffer(t.ARRAY_BUFFER,this._aIdBuffer),t.bufferData(t.ARRAY_BUFFER,o,t.STATIC_DRAW),t.bindBuffer(t.ARRAY_BUFFER,null),t.bindBuffer(t.ELEMENT_ARRAY_BUFFER,null)}else this._dummyVB=t.createBuffer(),t.bindBuffer(t.ARRAY_BUFFER,this._dummyVB),t.bufferData(t.ARRAY_BUFFER,e*4,t.STATIC_DRAW),this._vao=t.createVertexArray(),t.bindVertexArray(this._vao),t.bindBuffer(t.ELEMENT_ARRAY_BUFFER,this._indexBuffer),t.bindBuffer(t.ARRAY_BUFFER,this._dummyVB),t.enableVertexAttribArray(0),t.vertexAttribPointer(0,1,t.UNSIGNED_BYTE,!1,0,0),t.bindVertexArray(null);this._indexBufferCapacity=e}get vao(){return this._vao}get indexBuffer(){return this._indexBuffer}get aIdBuffer(){return this._aIdBuffer}get aIdLocation(){return this._aIdLocation}get indexType(){return this._indexBufferCapacity>16383?this.gl.UNSIGNED_INT:this.gl.UNSIGNED_SHORT}unbindVao(){this._isWebGL1?this._vaoExt&&this._vaoExt.bindVertexArrayOES(null):this.gl.bindVertexArray(null)}destroy(){const e=this.gl;this._shaderProgram&&e.deleteProgram(this._shaderProgram),this._indexBuffer&&e.deleteBuffer(this._indexBuffer),this._dummyVB&&e.deleteBuffer(this._dummyVB),this._vao&&e.deleteVertexArray(this._vao),this._aIdBuffer&&e.deleteBuffer(this._aIdBuffer)}_probeWebGL1Capabilities(){const e=this.gl;if(!e.getExtension("OES_texture_float"))throw new Error("NeutrinoParticles (js-v1.1): WebGL1 context lacks OES_texture_float; WebGL2 or WebGL1 with vertex float textures is required.");const t=e.getParameter(e.MAX_VERTEX_TEXTURE_IMAGE_UNITS);if(t<2)throw new Error("NeutrinoParticles (js-v1.1): WebGL1 context has "+t+" vertex texture units (2 required); WebGL2 or WebGL1 with vertex float textures is required.");const r=e.getShaderPrecisionFormat(e.FRAGMENT_SHADER,e.HIGH_FLOAT);if(!r||r.precision===0)throw new Error("NeutrinoParticles (js-v1.1): WebGL1 context lacks highp float in fragment shaders; WebGL2 or WebGL1 with fragment highp is required.");this._hasElementIndexUint=!!e.getExtension("OES_element_index_uint"),this._vaoExt=e.getExtension("OES_vertex_array_object");const n=e.getParameter(e.MAX_COMBINED_TEXTURE_IMAGE_UNITS),o=e.getParameter(e.MAX_TEXTURE_IMAGE_UNITS);this._maxBatchTextures=Math.max(1,Math.min(x.MAX_BATCH_TEXTURES,n-2,o))}_smokeTestVertexTextureFetch(){const e=this.gl,t=`precision highp float;
1196
+ attribute float aId;
1197
+ uniform sampler2D uF;
1198
+ uniform sampler2D uB;
1199
+ varying vec4 vC;
1200
+ void main() {
1201
+ vec4 f = texture2DLod(uF, vec2(0.5, 0.5), 0.0);
1202
+ vec4 b = texture2DLod(uB, vec2(0.5, 0.5), 0.0);
1203
+ vC = vec4(f.r * b.r, f.g * b.g, f.b * b.b, 1.0);
1204
+ gl_Position = vec4(aId, 0.0, 0.0, 1.0);
1205
+ gl_PointSize = 1.0;
1206
+ }
1207
+ `,r=`precision mediump float;
1208
+ varying vec4 vC;
1209
+ void main() { gl_FragColor = vC; }
1210
+ `,n=e.getParameter(e.VIEWPORT),o=e.getParameter(e.CURRENT_PROGRAM),s=e.getParameter(e.ARRAY_BUFFER_BINDING),a=e.getParameter(e.FRAMEBUFFER_BINDING),c=e.getParameter(e.ACTIVE_TEXTURE);e.activeTexture(e.TEXTURE0);const I=e.getParameter(e.TEXTURE_BINDING_2D);e.activeTexture(e.TEXTURE1);const S=e.getParameter(e.TEXTURE_BINDING_2D),C=e.isEnabled(e.BLEND),B=e.isEnabled(e.DEPTH_TEST),D=e.isEnabled(e.SCISSOR_TEST);let T=null,P=null,E=null,_=null,g=null,w=null,f=-1,A=!1,y=null,U=4,d=0,v=0,b=0,R=!1,u=!1;const h=new Uint8Array(4);try{T=e.createProgram(),e.attachShader(T,this._compileShader(e.VERTEX_SHADER,t)),e.attachShader(T,this._compileShader(e.FRAGMENT_SHADER,r)),e.linkProgram(T);const m=(M,K)=>{const k=e.createTexture();return e.bindTexture(e.TEXTURE_2D,k),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,1,1,0,e.RGBA,M,K),k};e.activeTexture(e.TEXTURE0),P=m(e.FLOAT,new Float32Array([1,.5,.25,1])),E=m(e.UNSIGNED_BYTE,new Uint8Array([51,102,153,255])),_=e.createTexture(),e.bindTexture(e.TEXTURE_2D,_),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,1,1,0,e.RGBA,e.UNSIGNED_BYTE,null),g=e.createFramebuffer(),e.bindFramebuffer(e.FRAMEBUFFER,g),e.framebufferTexture2D(e.FRAMEBUFFER,e.COLOR_ATTACHMENT0,e.TEXTURE_2D,_,0),w=e.createBuffer(),e.bindBuffer(e.ARRAY_BUFFER,w),e.bufferData(e.ARRAY_BUFFER,new Float32Array([0]),e.STATIC_DRAW),u=e.getProgramParameter(T,e.LINK_STATUS),u&&(e.useProgram(T),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,P),e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,E),e.uniform1i(e.getUniformLocation(T,"uF"),0),e.uniform1i(e.getUniformLocation(T,"uB"),1),f=e.getAttribLocation(T,"aId"),A=e.getVertexAttrib(f,e.VERTEX_ATTRIB_ARRAY_ENABLED),y=e.getVertexAttrib(f,e.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING),U=e.getVertexAttrib(f,e.VERTEX_ATTRIB_ARRAY_SIZE),d=e.getVertexAttrib(f,e.VERTEX_ATTRIB_ARRAY_TYPE),R=e.getVertexAttrib(f,e.VERTEX_ATTRIB_ARRAY_NORMALIZED),v=e.getVertexAttrib(f,e.VERTEX_ATTRIB_ARRAY_STRIDE),b=e.getVertexAttribOffset(f,e.VERTEX_ATTRIB_ARRAY_POINTER),e.enableVertexAttribArray(f),e.vertexAttribPointer(f,1,e.FLOAT,!1,0,0),e.viewport(0,0,1,1),e.disable(e.BLEND),e.disable(e.DEPTH_TEST),e.disable(e.SCISSOR_TEST),e.drawArrays(e.POINTS,0,1),e.readPixels(0,0,1,1,e.RGBA,e.UNSIGNED_BYTE,h),u=Math.abs(h[0]-51)<=2&&Math.abs(h[1]-51)<=2&&Math.abs(h[2]-38)<=2)}finally{f>=0&&(y&&(e.bindBuffer(e.ARRAY_BUFFER,y),e.vertexAttribPointer(f,U,d,R,v,b)),A?e.enableVertexAttribArray(f):e.disableVertexAttribArray(f)),g&&e.deleteFramebuffer(g),_&&e.deleteTexture(_),P&&e.deleteTexture(P),E&&e.deleteTexture(E),w&&e.deleteBuffer(w),T&&e.deleteProgram(T),e.bindFramebuffer(e.FRAMEBUFFER,a),e.bindBuffer(e.ARRAY_BUFFER,s),e.useProgram(o),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,I),e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,S),e.activeTexture(c),C&&e.enable(e.BLEND),B&&e.enable(e.DEPTH_TEST),D&&e.enable(e.SCISSOR_TEST),e.viewport(n[0],n[1],n[2],n[3])}if(!u)throw new Error(`NeutrinoParticles (js-v1.1): WebGL1 vertex texture fetch smoke test failed (got ${h[0]},${h[1]},${h[2]}); WebGL2 or WebGL1 with working vertex float textures is required.`)}_initShader(){const e=this.gl,t=this._isWebGL1?vertexShaderSourceGL1:vertexShaderSource,r=this._isWebGL1?fragmentShaderSourceGL1:fragmentShaderSource,n=this._compileShader(e.VERTEX_SHADER,t),o=this._compileShader(e.FRAGMENT_SHADER,r);if(this._shaderProgram=e.createProgram(),e.attachShader(this._shaderProgram,n),e.attachShader(this._shaderProgram,o),e.linkProgram(this._shaderProgram),!e.getProgramParameter(this._shaderProgram,e.LINK_STATUS))throw new Error("Shader link error: "+e.getProgramInfoLog(this._shaderProgram));e.deleteShader(n),e.deleteShader(o),this._uDataTexture=e.getUniformLocation(this._shaderProgram,"uDataTexture"),this._uCameraRight=e.getUniformLocation(this._shaderProgram,"uCameraRight"),this._uCameraUp=e.getUniformLocation(this._shaderProgram,"uCameraUp"),this._uCameraDir=e.getUniformLocation(this._shaderProgram,"uCameraDir"),this._uViewProjMatrix=e.getUniformLocation(this._shaderProgram,"uViewProjMatrix"),this._uModelMatrix=e.getUniformLocation(this._shaderProgram,"uModelMatrix"),this._uViewportAspect=e.getUniformLocation(this._shaderProgram,"uViewportAspect"),this._uWorldAlpha=e.getUniformLocation(this._shaderProgram,"uWorldAlpha"),this._isWebGL1?(this._uMetaTexture=e.getUniformLocation(this._shaderProgram,"uMetaTexture"),this._uDataTexSize=e.getUniformLocation(this._shaderProgram,"uDataTexSize"),this._aIdLocation=e.getAttribLocation(this._shaderProgram,"aId")):this._uDataTextureWidth=e.getUniformLocation(this._shaderProgram,"uDataTextureWidth"),this._uTextures=[],this._uTexRemaps=[];for(let s=0;s<x.MAX_BATCH_TEXTURES;s++)this._uTextures.push(e.getUniformLocation(this._shaderProgram,`uTextures[${s}]`)),this._uTexRemaps.push(e.getUniformLocation(this._shaderProgram,`uTexRemaps[${s}]`))}_compileShader(e,t){const r=this.gl,n=r.createShader(e);if(r.shaderSource(n,t),r.compileShader(n),!r.getShaderParameter(n,r.COMPILE_STATUS)){const o=r.getShaderInfoLog(n);throw r.deleteShader(n),new Error("Shader compile error: "+o)}return n}};i(x,"MAX_BATCH_TEXTURES",8);let Context=x;class ApplicationPlugin{static get extension(){return{name:"neutrino",type:pixi_js.ExtensionType.Application}}static init(e){this.neutrino=new Context(this.renderer,e.neutrino)}static destroy(){this.neutrino&&(this.neutrino.destroy(),this.neutrino=void 0)}}class EffectModel extends pixi_js.EventEmitter{constructor(context,neutrinoModel,texturesLoader){super();i(this,"ctx");i(this,"effectModel");i(this,"texturesRemap",[]);i(this,"textures",[]);i(this,"_numTexturesToLoadLeft");if(this.ctx=context,typeof neutrinoModel=="string"){const evalScript=`(function(ctx) {
1211
+ `+neutrinoModel+`
1212
+ return new NeutrinoEffect(ctx);
1213
+ })(context.neutrino);`;this.effectModel=eval(evalScript)}else this.effectModel=neutrinoModel;const numTextures=this.effectModel.textures.length;this._numTexturesToLoadLeft=numTextures;for(let e=0;e<numTextures;++e){const t=this.effectModel.textures[e];let r=null;if(this.ctx.options.trimmedExtensionsLookupFirst){const n=t.replace(/\.[^/.]+$/,"");pixi_js.Cache.has(n)&&(r=pixi_js.Cache.get(n))}!r&&pixi_js.Cache.has(t)&&(r=pixi_js.Cache.get(t)),r?this._onTextureLoaded(e,r,!0):texturesLoader.load(this.ctx.options.texturesBasePath+t).then(((n,o)=>s=>{n._onTextureLoaded(o,s,!1)})(this,e))}}ready(){return this._numTexturesToLoadLeft===0}_onTextureLoaded(e,t,r){this.textures[e]=t,this._numTexturesToLoadLeft--,this._numTexturesToLoadLeft===0&&(this._initTexturesRemapIfNeeded(),r?setTimeout(()=>{this.emit("ready",this)},0):this.emit("ready",this))}_initTexturesRemapIfNeeded(){let e=!1;for(let t=0;t<this.textures.length;++t){const r=this.textures[t];if(r.frame.x!=0||r.frame.y!=0||r.frame.width!=r.source.pixelWidth||r.frame.height!=r.source.pixelHeight){e=!0;break}}if(e)for(let t=0;t<this.textures.length;++t){const r=this.textures[t];this.texturesRemap[t]={x:r.frame.x/r.source.pixelWidth,y:1-(r.frame.y+r.frame.height)/r.source.pixelHeight,width:r.frame.width/r.source.pixelWidth,height:r.frame.height/r.source.pixelHeight}}}}class AbstractTexturesLoader{constructor(){}}class AssetsTexturesLoader extends AbstractTexturesLoader{constructor(t){super();i(this,"loader");this.loader=t}async load(t){return await this.loader.load(t)}}const p=class p extends AssetsTexturesLoader{constructor(){super(pixi_js.Assets.loader)}};i(p,"instance",new p);let DefaultAssetsTexturesLoader=p;const effectModelLoader={extension:{type:pixi_js.ExtensionType.LoadParser,priority:pixi_js.LoaderParserPriority.Normal},test(l,e,t){return e.data&&e.data.__neutrinoContext__&&e.data.__neutrinoContext__ instanceof Context},async load(l,e,t){return await(await pixi_js.DOMAdapter.get().fetch(l)).text()},async testParse(l,e){return e.data&&e.data.__neutrinoContext__&&e.data.__neutrinoContext__ instanceof Context},async parse(l,e,t){return new Promise(r=>{const n=e.data.__neutrinoContext__,o=new EffectModel(n,l,new AssetsTexturesLoader(t));o.ready()?r(o):o.once("ready",()=>{r(o)})})},unload(l){}},DATA_TEXTURE_UNIT=0,META_TEXTURE_UNIT=1,PARTICLE_TEXTURE_START_UNIT=1,PARTICLE_TEXTURE_START_UNIT_GL1=2,FLOATS_PER_PARTICLE=16,BYTES_PER_PARTICLE=FLOATS_PER_PARTICLE*4,MAX_BATCHES=64;class DrawBatch{constructor(){i(this,"blendMode",0);i(this,"startParticle",0);i(this,"numParticles",0);i(this,"numTextures",0);i(this,"glTextures",new Array(Context.MAX_BATCH_TEXTURES).fill(null));i(this,"remaps",new Array(Context.MAX_BATCH_TEXTURES).fill(null))}reset(){this.blendMode=0,this.startParticle=0,this.numParticles=0,this.numTextures=0}}class NeutrinoRenderer{constructor(){i(this,"_orthoMatrix",new Float32Array(16));i(this,"_perspMatrix",new Float32Array(16));i(this,"_vpMatrix",new Float32Array(16));i(this,"_modelMatrix",new Float32Array(16));i(this,"_projFrame",new pixi_js.Rectangle);i(this,"_texSlotMap",new Map);i(this,"_batchPool");i(this,"_batchCount",0);this._batchPool=[];for(let e=0;e<MAX_BATCHES;e++)this._batchPool.push(new DrawBatch)}renderEffect(e,t,r,n,o,s){if(s===0)return;const a=t.gl,c=t.isWebGL1,I=e.texture,S=t.maxBatchTextures,C=r.effect.model.renderStyles,B=r.effect.model.materials,D=r.effectModel.textures,T=r.effectModel.texturesRemap,P=r.effect.particleDataView,E=c?n.meta:null;E&&E.build(r.effect.particleDataUint32,s),this._batchCount=0;const _=this._texSlotMap;for(;this._batchPool.length<o.length;)this._batchPool.push(new DrawBatch);let g=0;for(;g<o.length;){const d=this._batchPool[this._batchCount];d.reset(),d.blendMode=this._resolveBlend(o[g].blendMode,B),d.startParticle=o[g].startParticleIndex,_.clear();let v=g;for(;v<o.length&&this._resolveBlend(o[v].blendMode,B)===d.blendMode;){const b=C[o[v].renderStyleIndex].textureIndices[0];if(!_.has(b)){if(_.size>=S)break;const h=_.size;_.set(b,h);const m=D[b];d.glTextures[h]=m?I.getGlSource(m.source).texture:null,d.remaps[h]=T[b]||null,d.numTextures=h+1}const R=_.get(b),u=o[v];if(E)E.patchSlot(u.startParticleIndex,u.numParticles,R);else for(let h=0;h<u.numParticles;h++){const m=(u.startParticleIndex+h)*BYTES_PER_PARTICLE,M=P.getUint32(m,!0);P.setUint32(m,M&4294967040|R,!0)}d.numParticles+=u.numParticles,v++}this._batchCount++,g=v}if(e.shader.resetState(),e.geometry.resetState(),e.state.resetState(),a.disable(a.DEPTH_TEST),a.disable(a.STENCIL_TEST),a.disable(a.SCISSOR_TEST),a.disable(a.CULL_FACE),a.depthMask(!1),a.colorMask(!0,!0,!0,!0),a.useProgram(t.shaderProgram),c){const d=n;d.upload(s,DATA_TEXTURE_UNIT,META_TEXTURE_UNIT),a.uniform1i(t.uDataTexture,DATA_TEXTURE_UNIT),a.uniform1i(t.uMetaTexture,META_TEXTURE_UNIT),a.uniform2f(t.uDataTexSize,d.textureWidth,d.textureHeight)}else n.uploadAndBind(DATA_TEXTURE_UNIT),a.uniform1i(t.uDataTexture,DATA_TEXTURE_UNIT),a.uniform1i(t.uDataTextureWidth,n.textureWidth);a.uniform3f(t.uCameraRight,1,0,0),a.uniform3f(t.uCameraUp,0,-1,0),a.uniform3f(t.uCameraDir,0,0,-1);const w=e.renderTarget.viewport,f=e.renderTarget.renderTarget.resolution;if(this._fillProjMatrix(this._orthoMatrix,e),r.projection){const d=this._projFrame;d.x=0,d.y=0,d.width=w.width/f,d.height=w.height/f,r.projection.setScreenFrame(d),r.projection.writeMatrix(this._perspMatrix),this._mulMatrix(this._vpMatrix,this._orthoMatrix,this._perspMatrix),a.uniformMatrix4fv(t.uViewProjMatrix,!1,this._vpMatrix)}else a.uniformMatrix4fv(t.uViewProjMatrix,!1,this._orthoMatrix);const A=w.height>0?w.width/w.height:1;t.uViewportAspect&&a.uniform1f(t.uViewportAspect,A),t.uWorldAlpha&&a.uniform1f(t.uWorldAlpha,r.worldAlpha),this._fillModelMatrix(this._modelMatrix,r),a.uniformMatrix4fv(t.uModelMatrix,!1,this._modelMatrix),c?(t.unbindVao(),a.bindBuffer(a.ARRAY_BUFFER,t.aIdBuffer),a.enableVertexAttribArray(t.aIdLocation),a.vertexAttribPointer(t.aIdLocation,1,a.FLOAT,!1,0,0),a.bindBuffer(a.ELEMENT_ARRAY_BUFFER,t.indexBuffer)):a.bindVertexArray(t.vao);const y=c?PARTICLE_TEXTURE_START_UNIT_GL1:PARTICLE_TEXTURE_START_UNIT;a.enable(a.BLEND);for(let d=0;d<this._batchCount;d++){const v=this._batchPool[d];this._applyBlendMode(a,v.blendMode);for(let u=0;u<v.numTextures;u++){const h=y+u;a.activeTexture(a.TEXTURE0+h),a.bindTexture(a.TEXTURE_2D,v.glTextures[u]),a.uniform1i(t.uTextures[u],h);const m=v.remaps[u];m?a.uniform4f(t.uTexRemaps[u],m.x,m.y,m.width,m.height):a.uniform4f(t.uTexRemaps[u],0,0,1,1)}const b=v.startParticle*6,R=v.numParticles*6;a.drawElements(a.TRIANGLES,R,t.indexType,b*(t.indexType===a.UNSIGNED_INT?4:2))}c?(a.disableVertexAttribArray(t.aIdLocation),a.bindBuffer(a.ARRAY_BUFFER,null),a.bindBuffer(a.ELEMENT_ARRAY_BUFFER,null)):a.bindVertexArray(null),a.useProgram(null);const U=I._boundTextures;if(U)for(let d=0;d<y+Context.MAX_BATCH_TEXTURES;d++)U[d]=null;e.shader.resetState(),e.geometry.resetState(),e.state.resetState()}_resolveBlend(e,t){return t&&e>=0&&e<t.length?t[e]:e}_applyBlendMode(e,t){switch(t){default:case 0:e.blendFuncSeparate(e.ONE,e.ONE_MINUS_SRC_ALPHA,e.ONE,e.ONE_MINUS_SRC_ALPHA);break;case 1:e.blendFunc(e.ONE,e.ONE);break;case 2:e.blendFunc(e.DST_COLOR,e.ONE_MINUS_SRC_ALPHA);break}}_fillProjMatrix(e,t){const r=t.renderTarget.projectionMatrix;e[0]=r.a,e[1]=r.b,e[2]=0,e[3]=0,e[4]=r.c,e[5]=r.d,e[6]=0,e[7]=0,e[8]=0,e[9]=0,e[10]=0,e[11]=0,e[12]=r.tx,e[13]=r.ty,e[14]=0,e[15]=1}_mulMatrix(e,t,r){for(let n=0;n<16;n+=4){const o=r[n],s=r[n+1],a=r[n+2],c=r[n+3];e[n]=t[0]*o+t[4]*s+t[8]*a+t[12]*c,e[n+1]=t[1]*o+t[5]*s+t[9]*a+t[13]*c,e[n+2]=t[2]*o+t[6]*s+t[10]*a+t[14]*c,e[n+3]=t[3]*o+t[7]*s+t[11]*a+t[15]*c}}_fillModelMatrix(e,t){const r=t.worldRenderMatrix;e[0]=r.a,e[1]=r.b,e[4]=r.c,e[5]=r.d,e[12]=r.tx,e[13]=r.ty,e[2]=0,e[3]=0,e[6]=0,e[7]=0,e[8]=0,e[9]=0,e[10]=1,e[11]=0,e[14]=0,e[15]=1}}class EffectPipe{constructor(e){i(this,"renderer");i(this,"_neutrinoRenderer",new NeutrinoRenderer);this.renderer=e}validateRenderable(e){return!0}addRenderable(e,t){t.add(e)}updateRenderable(e){}destroyRenderable(e){}execute(e){if(!e.ready()||e._isInvisible())return;const t=e.ctx,r=e._dtm();if(!t||!r)return;e._prepareRender(),e.effect.construct([0,0,-1]);const n=e.effect.renderInstructions,o=n._totalParticles;o!==0&&(this._neutrinoRenderer.renderEffect(this.renderer,t,e,r,n,o),r.advance())}destroy(){}}i(EffectPipe,"extension",{type:[pixi_js.ExtensionType.WebGLPipes],name:"neutrino"});const RING_BUFFER_SIZE$1=3;class DataTextureManager{constructor(e,t,r,n){i(this,"textureWidth");i(this,"textureHeight");i(this,"_gl");i(this,"_floatView");i(this,"_textures",[]);i(this,"_currentIndex",0);const o=e.getParameter(e.MAX_TEXTURE_SIZE);if(r>o||n>o)throw new Error(`NeutrinoParticles: data texture ${r}x${n} exceeds GL MAX_TEXTURE_SIZE (${o}). Reduce effect particle capacity.`);this._gl=e,this.textureWidth=r,this.textureHeight=n,this._floatView=new Float32Array(t.buffer,t.byteOffset,t.length);for(let s=0;s<RING_BUFFER_SIZE$1;s++){const a=e.createTexture();e.bindTexture(e.TEXTURE_2D,a),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texImage2D(e.TEXTURE_2D,0,e.RGBA32F,r,n,0,e.RGBA,e.FLOAT,null),this._textures.push(a)}e.bindTexture(e.TEXTURE_2D,null)}get currentTexture(){return this._textures[this._currentIndex]}uploadAndBind(e){const t=this._gl;t.activeTexture(t.TEXTURE0+e),t.bindTexture(t.TEXTURE_2D,this._textures[this._currentIndex]),t.pixelStorei(t.UNPACK_ALIGNMENT,4),t.pixelStorei(t.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL,!1),t.texSubImage2D(t.TEXTURE_2D,0,0,0,this.textureWidth,this.textureHeight,t.RGBA,t.FLOAT,this._floatView)}advance(){this._currentIndex=(this._currentIndex+1)%RING_BUFFER_SIZE$1}destroy(){const e=this._gl;for(const t of this._textures)t&&e.deleteTexture(t);this._textures=[]}}class MetaTextureManager{constructor(e,t){i(this,"bytes");i(this,"_u32");this.bytes=new Uint8Array(e*t*4),this._u32=new Uint32Array(this.bytes.buffer)}build(e,t){const r=this._u32;for(let n=0;n<t;n++){const o=n*16,s=n*4;r[s]=e[o],r[s+1]=e[o+12],r[s+2]=e[o+13],r[s+3]=e[o+14]}}patchSlot(e,t,r){const n=this.bytes;let o=e*16;for(let s=0;s<t;s++)n[o]=r,o+=16}}const RING_BUFFER_SIZE=3;class DataTextureManagerGL1{constructor(e,t,r,n){i(this,"textureWidth");i(this,"textureHeight");i(this,"meta");i(this,"_gl");i(this,"_floatView");i(this,"_dataTextures",[]);i(this,"_metaTextures",[]);i(this,"_currentIndex",0);const o=e.getParameter(e.MAX_TEXTURE_SIZE);if(r>o||n>o)throw new Error(`NeutrinoParticles: data texture ${r}x${n} exceeds GL MAX_TEXTURE_SIZE (${o}). Reduce effect particle capacity.`);if(r*n>16777216)throw new Error(`NeutrinoParticles: data texture ${r}x${n} exceeds the WebGL1 render path texel cap (2^24). Reduce effect particle capacity.`);this._gl=e,this.textureWidth=r,this.textureHeight=n,this._floatView=new Float32Array(t.buffer,t.byteOffset,t.length),this.meta=new MetaTextureManager(r,n);const s=e.getParameter(e.ACTIVE_TEXTURE);e.activeTexture(e.TEXTURE0);const a=e.getParameter(e.TEXTURE_BINDING_2D);for(let c=0;c<RING_BUFFER_SIZE;c++)this._dataTextures.push(this._createTexture(e.FLOAT)),this._metaTextures.push(this._createTexture(e.UNSIGNED_BYTE));e.bindTexture(e.TEXTURE_2D,a),e.activeTexture(s)}get currentDataTexture(){return this._dataTextures[this._currentIndex]}get currentMetaTexture(){return this._metaTextures[this._currentIndex]}upload(e,t,r){const n=this._gl,o=this.textureWidth,s=Math.min(this.textureHeight,Math.ceil(e*4/o));if(s===0)return;const a=s*o*4;n.pixelStorei(n.UNPACK_FLIP_Y_WEBGL,!1),n.pixelStorei(n.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),n.pixelStorei(n.UNPACK_ALIGNMENT,4),n.activeTexture(n.TEXTURE0+t),n.bindTexture(n.TEXTURE_2D,this.currentDataTexture),n.texSubImage2D(n.TEXTURE_2D,0,0,0,o,s,n.RGBA,n.FLOAT,this._floatView.subarray(0,a)),n.activeTexture(n.TEXTURE0+r),n.bindTexture(n.TEXTURE_2D,this.currentMetaTexture),n.texSubImage2D(n.TEXTURE_2D,0,0,0,o,s,n.RGBA,n.UNSIGNED_BYTE,this.meta.bytes.subarray(0,a))}advance(){this._currentIndex=(this._currentIndex+1)%RING_BUFFER_SIZE}destroy(){const e=this._gl;for(const t of this._dataTextures)e.deleteTexture(t);for(const t of this._metaTextures)e.deleteTexture(t);this._dataTextures=[],this._metaTextures=[]}_createTexture(e){const t=this._gl,r=t.createTexture();return t.bindTexture(t.TEXTURE_2D,r),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,t.NEAREST),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,t.NEAREST),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE),t.texImage2D(t.TEXTURE_2D,0,t.RGBA,this.textureWidth,this.textureHeight,0,t.RGBA,e,null),r}}var Pause=(l=>(l[l.NO=0]="NO",l[l.BEFORE_UPDATE_OR_RENDER=1]="BEFORE_UPDATE_OR_RENDER",l[l.YES=2]="YES",l))(Pause||{});const emptyBounds=new pixi_js.Bounds(0,0,0,0);class Effect extends pixi_js.ViewContainer{constructor(t,r){super({});i(this,"renderPipeId","neutrino");i(this,"batched",!1);i(this,"ctx");i(this,"effectModel");i(this,"effect");i(this,"baseParent");i(this,"projection");i(this,"positionZ");i(this,"scaleZ");i(this,"worldRenderMatrix",{a:1,b:0,c:0,d:1,tx:0,ty:0});i(this,"_worldPosition",new pixi_js.Point(0,0));i(this,"_worldRotationDegree",0);i(this,"_worldScale",new pixi_js.Point(1,1));i(this,"_dataTextureManager");i(this,"_unpauseOnUpdateRender");i(this,"_startupOptions");i(this,"_inited",!1);i(this,"_ready",!1);const n=Object.assign({position:[0,0,0],rotation:0,scale:[1,1,1],pause:1,generatorsPaused:!1,baseParent:void 0,projection:void 0,autoInit:!0},r);this.ctx=t.ctx,this.effectModel=t,this.baseParent=n.baseParent,this.projection=n.projection,this.position.set(n.position[0],n.position[1]),this.positionZ=n.position[2],this.rotation=n.rotation,this.scale.x=n.scale[0],this.scale.y=n.scale[1],this.scaleZ=n.scale[2],this._startupOptions={paused:n.pause!==0,generatorsPaused:n.generatorsPaused},this._unpauseOnUpdateRender=n.pause===1,n.autoInit&&this.init(),this._updateWorldTransform()}get bounds(){return emptyBounds}updateBounds(){}init(){this._inited||(this._inited=!0,this.effectModel.ready()?this._onEffectReady():this.effectModel.once("ready",this._onEffectReady,this))}ready(){return this._ready}update(t){this.ready()&&(this._checkUnpauseOnUpdateRender(),this._updateWorldTransform(),this.effect&&(this.effect.update(t,this._scaledPosition(),this.ctx.neutrino.axisangle2quat_([0,0,1],this._worldRotationDegree)),this.onViewUpdate()))}restart(t,r){t&&(this.position.x=t[0],this.position.y=t[1],this.positionZ=t[2]),r&&(this.rotation=r),this._updateWorldTransform(),this.effect.restart(this._scaledPosition(),r?this.ctx.neutrino.axisangle2quat_([0,0,1],this._worldRotationDegree):void 0),this.onViewUpdate()}resetPosition(t,r){t&&(this.position.x=t[0],this.position.y=t[1],this.positionZ=t[2]),r&&(this.rotation=r),this._updateWorldTransform(),this.effect.resetPosition(this._scaledPosition(),r?this.ctx.neutrino.axisangle2quat_([0,0,1],this._worldRotationDegree):void 0),this.onViewUpdate()}pause(){this.ready()?this.effect.pauseAllEmitters():this._startupOptions.paused=!0}unpause(){this.ready()?this.effect.unpauseAllEmitters():this._startupOptions.paused=!1}pauseGenerators(){this.ready()?this.effect.pauseGeneratorsInAllEmitters():this._startupOptions.generatorsPaused=!0}unpauseGenerators(){this.ready()?this.effect.unpauseGeneratorsInAllEmitters():this._startupOptions.generatorsPaused=!1}setPropertyInAllEmitters(t,r){this.effect.setPropertyInAllEmitters(t,r),this.onViewUpdate()}getNumParticles(){return this.effect?this.effect.getNumParticles():0}_dtm(){return this._dataTextureManager}_prepareRender(){this._checkUnpauseOnUpdateRender(),this._updateWorldTransform()}get worldAlpha(){var t;return this.groupAlpha*(((t=this.parentRenderGroup)==null?void 0:t.worldAlpha)??1)}_isInvisible(){return this.worldAlpha<=0}destroy(t){this.effect||this.effectModel.removeListener("ready",this._onEffectReady,this),this._dataTextureManager&&(this._dataTextureManager.destroy(),this._dataTextureManager=void 0),super.destroy(t)}_onEffectReady(){this._updateWorldTransform(),this.effect=this.effectModel.effectModel.createInstance(this._scaledPosition(),this.ctx.neutrino.axisangle2quat_([0,0,1],this._worldRotationDegree),this._startupOptions),this._dataTextureManager=this.ctx.isWebGL1?new DataTextureManagerGL1(this.ctx.gl,this.effect.particleDataUint32,this.effect.dataTextureWidth,this.effect.dataTextureHeight):new DataTextureManager(this.ctx.gl,this.effect.particleDataUint32,this.effect.dataTextureWidth,this.effect.dataTextureHeight),this.ctx.ensureIndexBuffer(this.effectModel.effectModel.totalParticles),this._ready=!0,this.emit("ready",this)}_scaledPosition(){return[this._worldScale.x!==0?this._worldPosition.x/this._worldScale.x:0,this._worldScale.y!==0?this._worldPosition.y/this._worldScale.y:0,this.scaleZ!==0?this.positionZ/this.scaleZ:0]}_getRenderGroupContainer(t){return t.renderGroup||!t.parent?t:this._getRenderGroupContainer(t.parent)}_updateWorldTransform(){const t=new pixi_js.Point(0,0),r=new pixi_js.Point(1,0),n=new pixi_js.Point(0,1),o=this.baseParent||this._getRenderGroupContainer(this);this._worldPosition=o.toLocal(t,this);const s=o.toLocal(r,this),a=o.toLocal(n,this);if(s.x-=this._worldPosition.x,s.y-=this._worldPosition.y,a.x-=this._worldPosition.x,a.y-=this._worldPosition.y,this._worldScale=new pixi_js.Point(Math.sqrt(s.x*s.x+s.y*s.y),Math.sqrt(a.x*a.x+a.y*a.y)),this._worldRotationDegree=this._calcWorldRotation(this)/Math.PI*180%360,this.baseParent){const c=this.baseParent.worldTransform;this.worldRenderMatrix={a:c.a,b:c.b,c:c.c,d:c.d,tx:c.tx,ty:c.ty}}else this.worldRenderMatrix={a:this._worldScale.x,b:0,c:0,d:this._worldScale.y,tx:0,ty:0}}_calcWorldRotation(t){return t.parent&&t.parent!=this.baseParent?t.rotation+this._calcWorldRotation(t.parent):t.rotation}_checkUnpauseOnUpdateRender(){this._unpauseOnUpdateRender&&(this.resetPosition(),this.unpause(),this._unpauseOnUpdateRender=!1)}}class PerspectiveProjection{constructor(e){i(this,"_angleTan",0);i(this,"_screenWidth",0);i(this,"_screenPosX",0);i(this,"_screenPosY",0);i(this,"_z",0);i(this,"_near",0);this.horizontalAngle=e}set horizontalAngle(e){this._angleTan=Math.tan(e*.5/180*Math.PI)}setScreenFrame(e){this._screenWidth=e.width,this._screenPosX=e.x+e.width*.5,this._screenPosY=e.y+e.height*.5,this._z=this._screenWidth*.5/this._angleTan,this._near=this._z*.99}transformPosition(e,t){if(t[2]>this._near)return!1;const r=this._getScale(t);return e[0]=(t[0]-this._screenPosX)*r+this._screenPosX,e[1]=(t[1]-this._screenPosY)*r+this._screenPosY,!0}transformSize(e,t,r){const n=this._getScale(t);e[0]=r[0]*n,e[1]=r[1]*n}_getScale(e){return this._z/(this._z-e[2])}writeMatrix(e){const t=this._z,r=Number.isFinite(t)&&t>0?1/t:0;e.fill(0),e[0]=1,e[5]=1,e[8]=-this._screenPosX*r,e[9]=-this._screenPosY*r,e[11]=-r,e[15]=1}}function registerPlugins(){pixi_js.extensions.add(ApplicationPlugin),pixi_js.extensions.add(effectModelLoader),pixi_js.extensions.add(EffectPipe)}function unregisterPlugins(){pixi_js.extensions.remove(ApplicationPlugin),pixi_js.extensions.remove(effectModelLoader),pixi_js.extensions.remove(EffectPipe)}registerPlugins(),exports.AbstractTexturesLoader=AbstractTexturesLoader,exports.ApplicationPlugin=ApplicationPlugin,exports.AssetsTexturesLoader=AssetsTexturesLoader,exports.Context=Context,exports.DataTextureManager=DataTextureManager,exports.DefaultAssetsTexturesLoader=DefaultAssetsTexturesLoader,exports.Effect=Effect,exports.EffectModel=EffectModel,exports.EffectPipe=EffectPipe,exports.NeutrinoRenderer=NeutrinoRenderer,exports.Pause=Pause,exports.PerspectiveProjection=PerspectiveProjection,exports.effectModelLoader=effectModelLoader,exports.registerPlugins=registerPlugins,exports.unregisterPlugins=unregisterPlugins,Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"})});
1214
+ //# sourceMappingURL=neutrinoparticles.js-v1.1-pixi8.umd.js.map