@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.
- package/CHANGELOG.md +9 -0
- package/README.md +231 -0
- package/dist/AplicationPlugin.d.ts +9 -0
- package/dist/Context.d.ts +92 -0
- package/dist/DataTextureManager.d.ts +32 -0
- package/dist/Effect.d.ts +104 -0
- package/dist/EffectModel.d.ts +20 -0
- package/dist/EffectModelLoader.d.ts +13 -0
- package/dist/EffectPipe.d.ts +25 -0
- package/dist/NeutrinoRenderer.d.ts +31 -0
- package/dist/PerspectiveProjection.d.ts +70 -0
- package/dist/TexturesLoader.d.ts +17 -0
- package/dist/gl1/DataTextureManagerGL1.d.ts +23 -0
- package/dist/gl1/MetaTextureManager.d.ts +9 -0
- package/dist/index.d.ts +22 -0
- package/dist/neutrinoparticles.js-v1.1-pixi8.cjs.js +1214 -0
- package/dist/neutrinoparticles.js-v1.1-pixi8.cjs.js.map +1 -0
- package/dist/neutrinoparticles.js-v1.1-pixi8.d.ts +482 -0
- package/dist/neutrinoparticles.js-v1.1-pixi8.es.js +2211 -0
- package/dist/neutrinoparticles.js-v1.1-pixi8.es.js.map +1 -0
- package/dist/neutrinoparticles.js-v1.1-pixi8.umd.js +1214 -0
- package/dist/neutrinoparticles.js-v1.1-pixi8.umd.js.map +1 -0
- package/dist/types.d.ts +59 -0
- package/package.json +56 -0
|
@@ -0,0 +1,2211 @@
|
|
|
1
|
+
var V = Object.defineProperty;
|
|
2
|
+
var L = (l, e, t) => e in l ? V(l, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : l[e] = t;
|
|
3
|
+
var i = (l, e, t) => L(l, typeof e != "symbol" ? e + "" : e, t);
|
|
4
|
+
import { ExtensionType, EventEmitter, Cache, Assets, LoaderParserPriority, DOMAdapter, Rectangle, Bounds, ViewContainer, Point, extensions } from "pixi.js";
|
|
5
|
+
import * as Neutrino from "@neutrinoparticles/js-v1.1";
|
|
6
|
+
const vertexShaderSource = `#version 300 es
|
|
7
|
+
precision highp float;
|
|
8
|
+
|
|
9
|
+
uniform sampler2D uDataTexture;
|
|
10
|
+
uniform int uDataTextureWidth;
|
|
11
|
+
uniform vec3 uCameraRight;
|
|
12
|
+
uniform vec3 uCameraUp;
|
|
13
|
+
uniform vec3 uCameraDir;
|
|
14
|
+
uniform mat4 uViewProjMatrix;
|
|
15
|
+
uniform mat4 uModelMatrix;
|
|
16
|
+
uniform float uViewportAspect; // viewport width / height — strip-only
|
|
17
|
+
uniform vec4 uTexRemaps[8];
|
|
18
|
+
|
|
19
|
+
const vec2 ORIGIN_MUL[4] = vec2[4](
|
|
20
|
+
vec2(-1.0, 0.0),
|
|
21
|
+
vec2(-1.0, -1.0),
|
|
22
|
+
vec2(0.0, -1.0),
|
|
23
|
+
vec2(0.0, 0.0)
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const vec2 INV_ORIGIN_MUL[4] = vec2[4](
|
|
27
|
+
vec2(0.0, 1.0),
|
|
28
|
+
vec2(0.0, 0.0),
|
|
29
|
+
vec2(1.0, 0.0),
|
|
30
|
+
vec2(1.0, 1.0)
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const vec2 TEX_COORD[4] = vec2[4](
|
|
34
|
+
vec2(0.0, 0.0),
|
|
35
|
+
vec2(0.0, 1.0),
|
|
36
|
+
vec2(1.0, 1.0),
|
|
37
|
+
vec2(1.0, 0.0)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
out vec2 vTexCoord;
|
|
41
|
+
out vec4 vColor;
|
|
42
|
+
flat out int vBatchTextureIndex;
|
|
43
|
+
// Atlas slot rect (origin.xy, extent.zw) used by the strip path to wrap
|
|
44
|
+
// U inside its own slot at the fragment stage. Quad path keeps the old
|
|
45
|
+
// vertex-side remap so quad sampling stays bit-perfect with prior IBCT
|
|
46
|
+
// snapshots. The flag distinguishes the two paths in the fragment.
|
|
47
|
+
flat out vec4 vAtlasRemap;
|
|
48
|
+
flat out int vStripPath;
|
|
49
|
+
|
|
50
|
+
vec4 fetchTexel(int globalTexelIndex) {
|
|
51
|
+
int x = globalTexelIndex % uDataTextureWidth;
|
|
52
|
+
int y = globalTexelIndex / uDataTextureWidth;
|
|
53
|
+
return texelFetch(uDataTexture, ivec2(x, y), 0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Strip geometry helpers (port of GPU33 makeScreenSpaceStrip).
|
|
57
|
+
// 2D screen-space variant used by the Faced strip frame; world Z reaches
|
|
58
|
+
// gl_Position only through uViewProjMatrix (ortho or v1.0-parity perspective).
|
|
59
|
+
vec2 stripOrtho(vec2 v) {
|
|
60
|
+
return vec2(-v.y, v.x);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
void main() {
|
|
64
|
+
int vertexIndex = gl_VertexID % 4;
|
|
65
|
+
int particleIndex = gl_VertexID / 4;
|
|
66
|
+
|
|
67
|
+
int baseTexel = particleIndex * 4;
|
|
68
|
+
|
|
69
|
+
vec4 t0 = fetchTexel(baseTexel + 0);
|
|
70
|
+
vec4 t1 = fetchTexel(baseTexel + 1);
|
|
71
|
+
vec4 t2 = fetchTexel(baseTexel + 2);
|
|
72
|
+
vec4 t3 = fetchTexel(baseTexel + 3);
|
|
73
|
+
|
|
74
|
+
// Texel 0: flags (uint32 bits in float), posX, posY, posZ
|
|
75
|
+
uint flags = floatBitsToUint(t0.r);
|
|
76
|
+
vec3 position = t0.gba;
|
|
77
|
+
uint rotationType = (flags >> 8u) & 3u;
|
|
78
|
+
uint ctorType = (flags >> 10u) & 3u;
|
|
79
|
+
int batchTexIdx = int(flags & 0xFFu);
|
|
80
|
+
vBatchTextureIndex = batchTexIdx;
|
|
81
|
+
|
|
82
|
+
// ─── Strip path ───────────────────────────────────────────────────────
|
|
83
|
+
// ctorType: 0 = Quad, 1 = StripCur, 2 = StripNex.
|
|
84
|
+
// Strip meta records come in pairs: meta_cur describes the "p1" end and
|
|
85
|
+
// meta_nex describes the "p2" end of one segment. Sibling meta is at the
|
|
86
|
+
// adjacent texel base (cur + 4 = nex; nex - 4 = cur). p0 (= prev pair's
|
|
87
|
+
// p1) and p3 (= next pair's p2) are reached via neighborIdxP0 / P3 stored
|
|
88
|
+
// as uint32 in t3.g / t3.b. Boundary particles use self-reference: when
|
|
89
|
+
// p0==p1 the shader sets dir01=dir12 and the knee degenerates.
|
|
90
|
+
//
|
|
91
|
+
// rotationType handling in the strip path:
|
|
92
|
+
// 0 (Faced): screen-space ortho to dir12 — equivalent to camera-faced
|
|
93
|
+
// billboard normal in a 2D camera setup.
|
|
94
|
+
// 1 (Free): the quaternion stored in texel 2 nominally encodes a 3D
|
|
95
|
+
// orientation. The PIXI7 renderer has no true 3D camera
|
|
96
|
+
// (uViewProjMatrix is ortho or the v1.0-parity perspective),
|
|
97
|
+
// so the quat is read but its 3D y-axis is projected to screen
|
|
98
|
+
// and used as the section normal. When the projection
|
|
99
|
+
// degenerates (looking along the y-axis) we fall back to
|
|
100
|
+
// the screen-space ortho. Real 3D free-rotation requires a
|
|
101
|
+
// future 3D pipeline; this is documented as a deviation in
|
|
102
|
+
// docs/tdd/TDD_80-js-v1.1-constructor-strip.md.
|
|
103
|
+
if (ctorType >= 1u) {
|
|
104
|
+
bool isCur = (ctorType == 1u);
|
|
105
|
+
int siblingTexel = isCur ? (baseTexel + 4) : (baseTexel - 4);
|
|
106
|
+
vec4 ts0 = fetchTexel(siblingTexel + 0);
|
|
107
|
+
vec4 ts1 = fetchTexel(siblingTexel + 1);
|
|
108
|
+
vec4 ts3 = fetchTexel(siblingTexel + 3);
|
|
109
|
+
|
|
110
|
+
// p1 always = position of the meta_cur end; p2 = meta_nex end.
|
|
111
|
+
vec3 p1 = isCur ? t0.gba : ts0.gba;
|
|
112
|
+
vec3 p2 = isCur ? ts0.gba : t0.gba;
|
|
113
|
+
|
|
114
|
+
float halfSize1 = isCur ? t1.r : ts1.r;
|
|
115
|
+
float halfSize2 = isCur ? ts1.r : t1.r;
|
|
116
|
+
float texU1 = isCur ? t1.g : ts1.g;
|
|
117
|
+
float texU2 = isCur ? ts1.g : t1.g;
|
|
118
|
+
|
|
119
|
+
// Color: meta_cur stores color of p1, meta_nex stores color of p2.
|
|
120
|
+
uint packedColorCur = floatBitsToUint(isCur ? t3.r : ts3.r);
|
|
121
|
+
uint packedColorNex = floatBitsToUint(isCur ? ts3.r : t3.r);
|
|
122
|
+
vec4 color1 = vec4(
|
|
123
|
+
float(packedColorCur & 0xFFu) / 255.0,
|
|
124
|
+
float((packedColorCur >> 8u) & 0xFFu) / 255.0,
|
|
125
|
+
float((packedColorCur >> 16u) & 0xFFu) / 255.0,
|
|
126
|
+
float((packedColorCur >> 24u) & 0xFFu) / 255.0);
|
|
127
|
+
vec4 color2 = vec4(
|
|
128
|
+
float(packedColorNex & 0xFFu) / 255.0,
|
|
129
|
+
float((packedColorNex >> 8u) & 0xFFu) / 255.0,
|
|
130
|
+
float((packedColorNex >> 16u) & 0xFFu) / 255.0,
|
|
131
|
+
float((packedColorNex >> 24u) & 0xFFu) / 255.0);
|
|
132
|
+
|
|
133
|
+
// neighborIdxP0 / neighborIdxP3 are stored on every meta of a pair
|
|
134
|
+
// (both meta_cur and meta_nex hold the same pair of indices), so we
|
|
135
|
+
// can read them from either self or sibling.
|
|
136
|
+
//
|
|
137
|
+
// Runtime convention (\`_constructStripParticles\` in system.js):
|
|
138
|
+
// neighborIdxP0 = global texel index of the position of W
|
|
139
|
+
// (= lnkdPrev(X)); boundary sentinel = curTexIdx
|
|
140
|
+
// (the pair's meta_cur texel index).
|
|
141
|
+
// neighborIdxP3 = global texel index of the position of Z
|
|
142
|
+
// (= lnkdNext(Y)); boundary sentinel = curTexIdx.
|
|
143
|
+
// Both are direct position-texel indices (NOT pair-cur indices that
|
|
144
|
+
// would need a +1 offset for the nex slot). Boundary detection uses
|
|
145
|
+
// selfPairCurTexel for both.
|
|
146
|
+
uint neighborIdxP0 = floatBitsToUint(isCur ? t3.g : ts3.g);
|
|
147
|
+
uint neighborIdxP3 = floatBitsToUint(isCur ? ts3.b : t3.b);
|
|
148
|
+
uint selfPairCurTexel = uint(isCur ? baseTexel : siblingTexel) / 4u;
|
|
149
|
+
|
|
150
|
+
vec3 p0 = (neighborIdxP0 == selfPairCurTexel)
|
|
151
|
+
? p1
|
|
152
|
+
: fetchTexel(int(neighborIdxP0) * 4 + 0).gba;
|
|
153
|
+
vec3 p3 = (neighborIdxP3 == selfPairCurTexel)
|
|
154
|
+
? p2
|
|
155
|
+
: fetchTexel(int(neighborIdxP3) * 4 + 0).gba;
|
|
156
|
+
|
|
157
|
+
// Segment frame. Two completely separate frames depending on
|
|
158
|
+
// rotationType — MIRROR of preview3 DataTextureRenderer3 (the editor's
|
|
159
|
+
// own JS1.1 renderer) so all data-texture renderers stay identical:
|
|
160
|
+
// Faced (rotationType 0/2): screen-space NDC frame around dir12.
|
|
161
|
+
// Free (rotationType 1): FULL world-space 3D frame (make3DStrip);
|
|
162
|
+
// the ribbon keeps its 3D orientation from the quat and Z collapses
|
|
163
|
+
// naturally only at projection (front-planar is a camera artifact,
|
|
164
|
+
// not a shader simplification). Mixing world-space section into a
|
|
165
|
+
// screen-space miter distorts sharp bends and makes geometry track
|
|
166
|
+
// the camera even with a static quat — Free stays camera-independent.
|
|
167
|
+
mat4 mvpMatrix = uViewProjMatrix * uModelMatrix;
|
|
168
|
+
bool isFreeRotation = (rotationType == 1u);
|
|
169
|
+
|
|
170
|
+
// Anchor projections — both branches use them for gl_Position.
|
|
171
|
+
vec4 sp1 = mvpMatrix * vec4(p1, 1.0);
|
|
172
|
+
vec4 sp2 = mvpMatrix * vec4(p2, 1.0);
|
|
173
|
+
|
|
174
|
+
// Near-plane guard (Faced only; the world-space Free frame is unaffected
|
|
175
|
+
// by the sign of w). Forces a discard outside the [-w,w] cube at the end.
|
|
176
|
+
bool nearPlaneClipped = (sp1.w < 1e-3) || (sp2.w < 1e-3);
|
|
177
|
+
|
|
178
|
+
// Aspect correction so the 2D Faced ortho gives visually-perpendicular
|
|
179
|
+
// dirs on non-square viewports (inverted before gl_Position).
|
|
180
|
+
vec2 aspectMul = vec2(1.0, 1.0 / max(uViewportAspect, 1e-6));
|
|
181
|
+
|
|
182
|
+
vec4 ts2 = fetchTexel(siblingTexel + 2);
|
|
183
|
+
vec4 q1 = isCur ? t2 : ts2;
|
|
184
|
+
vec4 q2 = isCur ? ts2 : t2;
|
|
185
|
+
|
|
186
|
+
// Frame outputs: Faced fills the 2D (k/b/IK), Free fills the 3D
|
|
187
|
+
// (worldK/B/IK); the vertex ladder picks the right one per gl_Position.
|
|
188
|
+
vec3 worldK1, worldB1, worldIK1, worldK2, worldB2, worldIK2;
|
|
189
|
+
vec2 k1, b1, IK1, k2, b2, IK2;
|
|
190
|
+
float Bmul1, Bmul2, kb1, kb2;
|
|
191
|
+
bool noKnee1, noKnee2;
|
|
192
|
+
float pathLen12; // len12 in the active frame's units (NDC for Faced, world for Free)
|
|
193
|
+
|
|
194
|
+
if (isFreeRotation) {
|
|
195
|
+
// World-space frame (mirror of GPU33 make3DStrip). Section normal
|
|
196
|
+
// n = quat z-axis (local Z ⟂ ribbon plane); ribbon lies in local XY
|
|
197
|
+
// (travel along local X, width along local Y). B = cross(dir12, n).
|
|
198
|
+
float xz1 = 2.0 * q1.x * q1.z;
|
|
199
|
+
float wy1 = 2.0 * q1.w * q1.y;
|
|
200
|
+
float yz1 = 2.0 * q1.y * q1.z;
|
|
201
|
+
float wx1 = 2.0 * q1.w * q1.x;
|
|
202
|
+
vec3 n1 = vec3(xz1 + wy1, yz1 - wx1, 1.0 - 2.0 * (q1.x * q1.x + q1.y * q1.y));
|
|
203
|
+
float xz2 = 2.0 * q2.x * q2.z;
|
|
204
|
+
float wy2 = 2.0 * q2.w * q2.y;
|
|
205
|
+
float yz2 = 2.0 * q2.y * q2.z;
|
|
206
|
+
float wx2 = 2.0 * q2.w * q2.x;
|
|
207
|
+
vec3 n2 = vec3(xz2 + wy2, yz2 - wx2, 1.0 - 2.0 * (q2.x * q2.x + q2.y * q2.y));
|
|
208
|
+
|
|
209
|
+
vec3 wp01 = p1 - p0;
|
|
210
|
+
vec3 wp12 = p2 - p1;
|
|
211
|
+
vec3 wp23 = p3 - p2;
|
|
212
|
+
float wlen01 = length(wp01);
|
|
213
|
+
float wlen12 = length(wp12);
|
|
214
|
+
float wlen23 = length(wp23);
|
|
215
|
+
vec3 wdir12 = (wlen12 > 1e-6) ? (wp12 / wlen12) : vec3(1.0, 0.0, 0.0);
|
|
216
|
+
vec3 wdir01 = (wlen01 > 1e-6) ? (wp01 / wlen01) : wdir12;
|
|
217
|
+
vec3 wdir23 = (wlen23 > 1e-6) ? (wp23 / wlen23) : wdir12;
|
|
218
|
+
noKnee1 = dot(wdir01, wdir12) > 0.999;
|
|
219
|
+
noKnee2 = dot(wdir12, wdir23) > 0.999;
|
|
220
|
+
pathLen12 = wlen12;
|
|
221
|
+
|
|
222
|
+
if (!noKnee1) {
|
|
223
|
+
vec3 B1raw = normalize(cross(wdir12, n1));
|
|
224
|
+
vec3 c1 = normalize(cross(wdir01, wdir12));
|
|
225
|
+
float Kmul1 = sign(dot(c1, n1));
|
|
226
|
+
vec3 K1 = Kmul1 * normalize(cross(wdir01 + wdir12, n1));
|
|
227
|
+
Bmul1 = sign(dot(B1raw, K1));
|
|
228
|
+
vec3 B1 = B1raw * Bmul1;
|
|
229
|
+
float ik1 = halfSize1 / max(dot(B1, K1), 1e-6);
|
|
230
|
+
if (dot(-wdir01, ik1 * -K1) > wlen01) ik1 = wlen01;
|
|
231
|
+
if (dot( wdir12, ik1 * -K1) > wlen12) ik1 = wlen12;
|
|
232
|
+
worldK1 = p1 + K1 * halfSize1;
|
|
233
|
+
worldB1 = p1 + B1 * halfSize1;
|
|
234
|
+
worldIK1 = p1 - K1 * ik1;
|
|
235
|
+
kb1 = distance(worldK1, worldB1);
|
|
236
|
+
} else {
|
|
237
|
+
Bmul1 = 1.0;
|
|
238
|
+
vec3 B1 = normalize(cross(wdir01 + wdir12, n1));
|
|
239
|
+
worldK1 = p1 + B1 * halfSize1;
|
|
240
|
+
worldB1 = worldK1;
|
|
241
|
+
worldIK1 = p1 - B1 * halfSize1;
|
|
242
|
+
kb1 = 0.0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!noKnee2) {
|
|
246
|
+
vec3 B2raw = normalize(cross(wdir12, n2));
|
|
247
|
+
vec3 c2 = normalize(cross(wdir12, wdir23));
|
|
248
|
+
float Kmul2 = sign(dot(c2, n2));
|
|
249
|
+
vec3 K2 = Kmul2 * normalize(cross(wdir12 + wdir23, n2));
|
|
250
|
+
Bmul2 = sign(dot(B2raw, K2));
|
|
251
|
+
vec3 B2 = B2raw * Bmul2;
|
|
252
|
+
float ik2 = halfSize2 / max(dot(B2, K2), 1e-6);
|
|
253
|
+
if (dot(-wdir12, ik2 * -K2) > wlen12) ik2 = wlen12;
|
|
254
|
+
if (dot( wdir23, ik2 * -K2) > wlen23) ik2 = wlen23;
|
|
255
|
+
worldK2 = p2 + K2 * halfSize2;
|
|
256
|
+
worldB2 = p2 + B2 * halfSize2;
|
|
257
|
+
worldIK2 = p2 - K2 * ik2;
|
|
258
|
+
kb2 = distance(worldK2, worldB2);
|
|
259
|
+
} else {
|
|
260
|
+
Bmul2 = 1.0;
|
|
261
|
+
vec3 B2 = normalize(cross(wdir12 + wdir23, n2));
|
|
262
|
+
worldK2 = p2 + B2 * halfSize2;
|
|
263
|
+
worldB2 = worldK2;
|
|
264
|
+
worldIK2 = p2 - B2 * halfSize2;
|
|
265
|
+
kb2 = 0.0;
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
// Faced: screen-space NDC frame around dir12.
|
|
269
|
+
vec4 sp0 = mvpMatrix * vec4(p0, 1.0);
|
|
270
|
+
vec4 sp3 = mvpMatrix * vec4(p3, 1.0);
|
|
271
|
+
vec2 ndc1 = (sp1.xy / sp1.w) * aspectMul;
|
|
272
|
+
vec2 ndc2 = (sp2.xy / sp2.w) * aspectMul;
|
|
273
|
+
// Neighbor anchors behind the near plane produce garbage NDC after
|
|
274
|
+
// the perspective divide (possible only with Projection3D, where w
|
|
275
|
+
// varies); collapse them onto their segment anchor so the existing
|
|
276
|
+
// zero-length degeneracy path (dir01 = dir12 / dir23 = dir12)
|
|
277
|
+
// handles the knee instead of a poisoned miter. spU1/spU2 need no
|
|
278
|
+
// guard: uCameraUp has zero Z, so their w equals sp1.w / sp2.w.
|
|
279
|
+
vec2 ndc0 = (sp0.w < 1e-3) ? ndc1 : (sp0.xy / sp0.w) * aspectMul;
|
|
280
|
+
vec2 ndc3 = (sp3.w < 1e-3) ? ndc2 : (sp3.xy / sp3.w) * aspectMul;
|
|
281
|
+
|
|
282
|
+
vec2 d01 = ndc1 - ndc0;
|
|
283
|
+
vec2 d12 = ndc2 - ndc1;
|
|
284
|
+
vec2 d23 = ndc3 - ndc2;
|
|
285
|
+
float len01 = length(d01);
|
|
286
|
+
float len12 = length(d12);
|
|
287
|
+
float len23 = length(d23);
|
|
288
|
+
vec2 dir12 = (len12 > 1e-6) ? (d12 / len12) : vec2(1.0, 0.0);
|
|
289
|
+
vec2 dir01 = (len01 > 1e-6) ? (d01 / len01) : dir12;
|
|
290
|
+
vec2 dir23 = (len23 > 1e-6) ? (d23 / len23) : dir12;
|
|
291
|
+
noKnee1 = dot(dir01, dir12) > 0.999;
|
|
292
|
+
noKnee2 = dot(dir12, dir23) > 0.999;
|
|
293
|
+
pathLen12 = len12;
|
|
294
|
+
|
|
295
|
+
// Section magnitude: project camera_up*halfSize per anchor into NDC.
|
|
296
|
+
vec4 spU1 = mvpMatrix * vec4(p1 + uCameraUp * halfSize1, 1.0);
|
|
297
|
+
vec4 spU2 = mvpMatrix * vec4(p2 + uCameraUp * halfSize2, 1.0);
|
|
298
|
+
float ndcHalfSize1 = length((spU1.xy / spU1.w) * aspectMul - ndc1);
|
|
299
|
+
float ndcHalfSize2 = length((spU2.xy / spU2.w) * aspectMul - ndc2);
|
|
300
|
+
|
|
301
|
+
vec2 B = stripOrtho(dir12);
|
|
302
|
+
if (!noKnee1) {
|
|
303
|
+
float Kmul1 = -sign(dot(stripOrtho(dir01), dir12));
|
|
304
|
+
vec2 K1 = Kmul1 * normalize(stripOrtho(dir01 + dir12));
|
|
305
|
+
Bmul1 = sign(dot(B, K1));
|
|
306
|
+
vec2 B1 = B * Bmul1;
|
|
307
|
+
float ik1 = ndcHalfSize1 / max(dot(B1, K1), 1e-6);
|
|
308
|
+
if (dot(-dir01, ik1 * -K1) > len01) ik1 = len01;
|
|
309
|
+
if (dot(dir12, ik1 * -K1) > len12) ik1 = len12;
|
|
310
|
+
k1 = ndc1 + K1 * ndcHalfSize1;
|
|
311
|
+
b1 = ndc1 + B1 * ndcHalfSize1;
|
|
312
|
+
IK1 = ndc1 - K1 * ik1;
|
|
313
|
+
kb1 = distance(k1, b1);
|
|
314
|
+
} else {
|
|
315
|
+
vec2 B1 = normalize(stripOrtho(dir01 + dir12));
|
|
316
|
+
Bmul1 = 1.0;
|
|
317
|
+
k1 = ndc1 + B1 * ndcHalfSize1;
|
|
318
|
+
b1 = k1;
|
|
319
|
+
IK1 = ndc1 - B1 * ndcHalfSize1;
|
|
320
|
+
kb1 = 0.0;
|
|
321
|
+
}
|
|
322
|
+
if (!noKnee2) {
|
|
323
|
+
float Kmul2 = -sign(dot(stripOrtho(dir12), dir23));
|
|
324
|
+
vec2 K2 = Kmul2 * normalize(stripOrtho(dir12 + dir23));
|
|
325
|
+
Bmul2 = sign(dot(B, K2));
|
|
326
|
+
vec2 B2 = B * Bmul2;
|
|
327
|
+
float ik2 = ndcHalfSize2 / max(dot(B2, K2), 1e-6);
|
|
328
|
+
if (dot(-dir12, ik2 * -K2) > len12) ik2 = len12;
|
|
329
|
+
if (dot(dir23, ik2 * -K2) > len23) ik2 = len23;
|
|
330
|
+
k2 = ndc2 + K2 * ndcHalfSize2;
|
|
331
|
+
b2 = ndc2 + B2 * ndcHalfSize2;
|
|
332
|
+
IK2 = ndc2 - K2 * ik2;
|
|
333
|
+
kb2 = distance(k2, b2);
|
|
334
|
+
} else {
|
|
335
|
+
vec2 B2 = normalize(stripOrtho(dir12 + dir23));
|
|
336
|
+
Bmul2 = 1.0;
|
|
337
|
+
k2 = ndc2 + B2 * ndcHalfSize2;
|
|
338
|
+
b2 = k2;
|
|
339
|
+
IK2 = ndc2 - B2 * ndcHalfSize2;
|
|
340
|
+
kb2 = 0.0;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// V coords (mirror of preview3 / GPU33 make*Strip):
|
|
345
|
+
// V at outer knee (k) = (Bmul + 1) * 0.5
|
|
346
|
+
// V at inner knee (IK) = 1 - V_outer ; bend (b) = outer
|
|
347
|
+
float Vtop1 = (Bmul1 + 1.0) * 0.5;
|
|
348
|
+
float Vbot1 = 1.0 - Vtop1;
|
|
349
|
+
float Vtop2 = (Bmul2 + 1.0) * 0.5;
|
|
350
|
+
float Vbot2 = 1.0 - Vtop2;
|
|
351
|
+
|
|
352
|
+
// Vertex layout per meta (4 vertices × IBO [0,1,2, 0,2,3] = 2 tris):
|
|
353
|
+
//
|
|
354
|
+
// Bmul1*Bmul2 ≥ 0 (knees same side):
|
|
355
|
+
// meta_cur: v0=b1, v1=k1, v2=IK1, v3=b2
|
|
356
|
+
// T1=(b1,k1,IK1) — knee at p1 ✓
|
|
357
|
+
// T2=(b1,IK1,b2) — top half segment ✓
|
|
358
|
+
// meta_nex: v0=b2, v1=IK1, v2=IK2, v3=k2
|
|
359
|
+
// T1=(b2,IK1,IK2) — bottom half segment ✓
|
|
360
|
+
// T2=(b2,IK2,k2) — knee at p2 ✓
|
|
361
|
+
//
|
|
362
|
+
// Bmul1*Bmul2 < 0 (knees opposite sides):
|
|
363
|
+
// meta_cur: v0=b1, v1=k1, v2=IK1, v3=IK2
|
|
364
|
+
// T1=(b1,k1,IK1) ✓
|
|
365
|
+
// T2=(b1,IK1,IK2) — diagonal cross ✓
|
|
366
|
+
// meta_nex: v0=IK2, v1=IK1, v2=b2, v3=k2
|
|
367
|
+
// T1=(IK2,IK1,b2) — diagonal cross ✓
|
|
368
|
+
// T2=(IK2,b2,k2) — knee at p2 ✓
|
|
369
|
+
//
|
|
370
|
+
// The shader picks v0..v3 based on (isCur, vertexIndex, sign(Bmul1*Bmul2)).
|
|
371
|
+
bool sameSide = (Bmul1 * Bmul2 >= 0.0);
|
|
372
|
+
// Distance for U interpolation along the bend point. Clamped to a
|
|
373
|
+
// small epsilon to avoid NaN on fully degenerate segments (p1≈p2 or
|
|
374
|
+
// 1-particle "strip" reached through self-references).
|
|
375
|
+
float dPath = max(sameSide ? (kb1 + pathLen12 + kb2) : (kb1 + pathLen12), 1e-6);
|
|
376
|
+
float dPathNex = max(sameSide ? dPath : (kb2 + pathLen12), 1e-6);
|
|
377
|
+
|
|
378
|
+
// Per-vertex colour follows the same kb / dPath ratio as texU.
|
|
379
|
+
// Knee corners (k1/IK1, k2/IK2) are anchored to their own particle;
|
|
380
|
+
// bend points (b1/b2) blend between color1 and color2 by the same
|
|
381
|
+
// fraction the U coord uses. Opposite-side bend uses the shorter
|
|
382
|
+
// denominator (kb*+len12) and IK on the far end is the other colour.
|
|
383
|
+
float bUcur = kb1 / dPath;
|
|
384
|
+
float bUnex = sameSide ? (kb2 / dPath) : (kb2 / dPathNex);
|
|
385
|
+
vec4 colorB1 = mix(color1, color2, bUcur);
|
|
386
|
+
vec4 colorB2 = mix(color2, color1, bUnex);
|
|
387
|
+
|
|
388
|
+
// vertPos holds the picked vertex in the 2D Faced frame (NDC);
|
|
389
|
+
// vertPos3D holds it in world space for Free. A single ladder over the
|
|
390
|
+
// IBO layout picks both, then gl_Position chooses by isFreeRotation.
|
|
391
|
+
vec2 vertPos;
|
|
392
|
+
vec3 vertPos3D = vec3(0.0);
|
|
393
|
+
vec2 vertUV;
|
|
394
|
+
vec4 vertColor;
|
|
395
|
+
if (isCur) {
|
|
396
|
+
// meta_cur vertices
|
|
397
|
+
if (vertexIndex == 0) {
|
|
398
|
+
vertPos = b1; vertPos3D = worldB1;
|
|
399
|
+
vertUV = vec2(mix(texU1, texU2, kb1 / dPath), Vtop1);
|
|
400
|
+
vertColor = colorB1;
|
|
401
|
+
} else if (vertexIndex == 1) {
|
|
402
|
+
vertPos = k1; vertPos3D = worldK1;
|
|
403
|
+
vertUV = vec2(texU1, Vtop1);
|
|
404
|
+
vertColor = color1;
|
|
405
|
+
} else if (vertexIndex == 2) {
|
|
406
|
+
vertPos = IK1; vertPos3D = worldIK1;
|
|
407
|
+
vertUV = vec2(texU1, Vbot1);
|
|
408
|
+
vertColor = color1;
|
|
409
|
+
} else {
|
|
410
|
+
// v3 = b2 (sameSide) or IK2 (different side)
|
|
411
|
+
if (sameSide) {
|
|
412
|
+
vertPos = b2; vertPos3D = worldB2;
|
|
413
|
+
vertUV = vec2(mix(texU2, texU1, kb2 / dPath), Vtop2);
|
|
414
|
+
vertColor = colorB2;
|
|
415
|
+
} else {
|
|
416
|
+
vertPos = IK2; vertPos3D = worldIK2;
|
|
417
|
+
vertUV = vec2(texU2, Vbot2);
|
|
418
|
+
vertColor = color2;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
// noKnee fallback: collapse to k1/IK1 to produce degenerate segments.
|
|
422
|
+
if (noKnee1) {
|
|
423
|
+
if (vertexIndex == 0) { vertPos = k1; vertPos3D = worldK1; vertUV = vec2(texU1, Vtop1); vertColor = color1; }
|
|
424
|
+
}
|
|
425
|
+
} else {
|
|
426
|
+
// meta_nex vertices
|
|
427
|
+
if (sameSide) {
|
|
428
|
+
if (vertexIndex == 0) { vertPos = b2; vertPos3D = worldB2; vertUV = vec2(mix(texU2, texU1, kb2 / dPath), Vtop2); vertColor = colorB2; }
|
|
429
|
+
else if (vertexIndex == 1) { vertPos = IK1; vertPos3D = worldIK1; vertUV = vec2(texU1, Vbot1); vertColor = color1; }
|
|
430
|
+
else if (vertexIndex == 2) { vertPos = IK2; vertPos3D = worldIK2; vertUV = vec2(texU2, Vbot2); vertColor = color2; }
|
|
431
|
+
else { vertPos = k2; vertPos3D = worldK2; vertUV = vec2(texU2, Vtop2); vertColor = color2; }
|
|
432
|
+
} else {
|
|
433
|
+
if (vertexIndex == 0) { vertPos = IK2; vertPos3D = worldIK2; vertUV = vec2(texU2, Vbot2); vertColor = color2; }
|
|
434
|
+
else if (vertexIndex == 1) { vertPos = IK1; vertPos3D = worldIK1; vertUV = vec2(texU1, Vbot1); vertColor = color1; }
|
|
435
|
+
else if (vertexIndex == 2) { vertPos = b2; vertPos3D = worldB2; vertUV = vec2(mix(texU2, texU1, kb2 / dPathNex), Vtop2); vertColor = colorB2; }
|
|
436
|
+
else { vertPos = k2; vertPos3D = worldK2; vertUV = vec2(texU2, Vtop2); vertColor = color2; }
|
|
437
|
+
}
|
|
438
|
+
if (noKnee2) {
|
|
439
|
+
if (vertexIndex == 3) { vertPos = IK2; vertPos3D = worldIK2; vertUV = vec2(texU2, Vbot2); vertColor = color2; }
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Free projects the world-space vertex through MVP directly; Faced
|
|
444
|
+
// reconstructs clip space from aspect-corrected NDC (mirror of preview3).
|
|
445
|
+
if (isFreeRotation) {
|
|
446
|
+
gl_Position = mvpMatrix * vec4(vertPos3D, 1.0);
|
|
447
|
+
} else if (nearPlaneClipped) {
|
|
448
|
+
// At least one anchor is at / behind the near plane → the NDC math
|
|
449
|
+
// is garbage; emit a point outside the [-w,w] cube to discard.
|
|
450
|
+
gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
|
|
451
|
+
} else {
|
|
452
|
+
// Inverse aspect, × anchor w; reuse anchor z/w for a consistent
|
|
453
|
+
// perspective divide within a pair-half.
|
|
454
|
+
vec4 anchorProj = isCur ? sp1 : sp2;
|
|
455
|
+
vec2 vertPosClip = vertPos / aspectMul;
|
|
456
|
+
gl_Position = vec4(vertPosClip * anchorProj.w, anchorProj.zw);
|
|
457
|
+
}
|
|
458
|
+
vColor = vertColor;
|
|
459
|
+
|
|
460
|
+
// Strip emits *raw* (U, V) — fragment wraps U via fract() before
|
|
461
|
+
// scaling into the atlas slot. Otherwise a Distance/PerSegment U
|
|
462
|
+
// > 1 would step into the neighbouring atlas tile.
|
|
463
|
+
vTexCoord = vertUV;
|
|
464
|
+
vAtlasRemap = uTexRemaps[batchTexIdx];
|
|
465
|
+
vStripPath = 1;
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
// ─── End strip path ───────────────────────────────────────────────────
|
|
469
|
+
|
|
470
|
+
// Texel 1: originX, originY, sizeX, sizeY
|
|
471
|
+
vec2 origin = t1.rg;
|
|
472
|
+
vec2 size = t1.ba;
|
|
473
|
+
|
|
474
|
+
// Texel 3: packed colorRGBA (uint32 bits in float), packed gridConfig (uint32 bits), gridIndex
|
|
475
|
+
uint packedColor = floatBitsToUint(t3.r);
|
|
476
|
+
vColor = vec4(
|
|
477
|
+
float(packedColor & 0xFFu) / 255.0,
|
|
478
|
+
float((packedColor >> 8u) & 0xFFu) / 255.0,
|
|
479
|
+
float((packedColor >> 16u) & 0xFFu) / 255.0,
|
|
480
|
+
float((packedColor >> 24u) & 0xFFu) / 255.0
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
uint gridConfig = floatBitsToUint(t3.g);
|
|
484
|
+
float gridWidth = max(float(gridConfig & 0xFFFFu), 1.0);
|
|
485
|
+
float gridHeight = max(float((gridConfig >> 16u) & 0xFFFFu), 1.0);
|
|
486
|
+
// gridIndex must be truncated to an integer to pick a single cell —
|
|
487
|
+
// otherwise a fractional index (animated frames) blends UVs from two
|
|
488
|
+
// adjacent cells horizontally (gx = mod(frac, W) leaks the fractional
|
|
489
|
+
// part into the column offset). Matches the original int(*data.inIndex_) cast.
|
|
490
|
+
float gridIndex = floor(t3.b);
|
|
491
|
+
|
|
492
|
+
// Texel 2: rotation
|
|
493
|
+
vec3 xaxis, yaxis;
|
|
494
|
+
|
|
495
|
+
if (rotationType == 0u) {
|
|
496
|
+
float angle = t2.r;
|
|
497
|
+
float rad = angle * 3.14159265 / 180.0;
|
|
498
|
+
float s = -sin(rad);
|
|
499
|
+
float c = cos(rad);
|
|
500
|
+
xaxis = uCameraRight * c + uCameraUp * s;
|
|
501
|
+
yaxis = -uCameraRight * s + uCameraUp * c;
|
|
502
|
+
} else if (rotationType == 1u) {
|
|
503
|
+
vec4 q = t2;
|
|
504
|
+
float z2 = 2.0 * q.z * q.z;
|
|
505
|
+
float xy = 2.0 * q.x * q.y;
|
|
506
|
+
float wz = 2.0 * q.w * q.z;
|
|
507
|
+
xaxis = vec3(
|
|
508
|
+
1.0 - 2.0 * q.y * q.y - z2,
|
|
509
|
+
xy + wz,
|
|
510
|
+
2.0 * q.x * q.z - 2.0 * q.w * q.y);
|
|
511
|
+
yaxis = vec3(
|
|
512
|
+
xy - wz,
|
|
513
|
+
1.0 - 2.0 * q.x * q.x - z2,
|
|
514
|
+
2.0 * q.y * q.z + 2.0 * q.w * q.x);
|
|
515
|
+
} else {
|
|
516
|
+
xaxis = cross(uCameraUp, uCameraDir);
|
|
517
|
+
yaxis = uCameraUp;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
xaxis *= size.x;
|
|
521
|
+
yaxis *= size.y;
|
|
522
|
+
|
|
523
|
+
vec2 vOrigin = ORIGIN_MUL[vertexIndex] * origin
|
|
524
|
+
+ INV_ORIGIN_MUL[vertexIndex] * (vec2(1.0) - origin);
|
|
525
|
+
vec3 posDisp = vOrigin.x * xaxis + vOrigin.y * yaxis;
|
|
526
|
+
|
|
527
|
+
mat4 mvpMatrix = uViewProjMatrix * uModelMatrix;
|
|
528
|
+
vec3 worldPos = position + posDisp;
|
|
529
|
+
gl_Position = mvpMatrix * vec4(worldPos, 1.0);
|
|
530
|
+
|
|
531
|
+
// v1.0-parity near-plane culling for the perspective projection: the
|
|
532
|
+
// whole quad is discarded when ANY of its 4 corners is behind the near
|
|
533
|
+
// plane (w < 0.01 == pos.z > 0.99 * z_cam in PerspectiveProjection
|
|
534
|
+
// terms). w is linear in position, so the corner minimum comes from the
|
|
535
|
+
// w-row and the corner offsets (x in {-ox, 1-ox}, y in {-oy, 1-oy}).
|
|
536
|
+
// Without a projection every w is 1.0 and this is a no-op.
|
|
537
|
+
vec4 wRow = vec4(mvpMatrix[0].w, mvpMatrix[1].w, mvpMatrix[2].w, mvpMatrix[3].w);
|
|
538
|
+
float wx = dot(wRow.xyz, xaxis);
|
|
539
|
+
float wy = dot(wRow.xyz, yaxis);
|
|
540
|
+
float minW = dot(wRow.xyz, position) + wRow.w
|
|
541
|
+
- origin.x * wx + min(wx, 0.0)
|
|
542
|
+
- origin.y * wy + min(wy, 0.0);
|
|
543
|
+
if (minW < 0.01) {
|
|
544
|
+
gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Texture coordinates with grid + atlas remap
|
|
548
|
+
vec2 baseTexCoord = TEX_COORD[vertexIndex];
|
|
549
|
+
|
|
550
|
+
float gx = mod(gridIndex, gridWidth);
|
|
551
|
+
float gy = floor(gridIndex / gridWidth);
|
|
552
|
+
float cellW = 1.0 / gridWidth;
|
|
553
|
+
float cellH = 1.0 / gridHeight;
|
|
554
|
+
|
|
555
|
+
vec2 gridTexCoord = vec2(
|
|
556
|
+
(gx + baseTexCoord.x) * cellW,
|
|
557
|
+
1.0 - (gy + 1.0 - baseTexCoord.y) * cellH
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
// Quad keeps the old vertex-side atlas remap so existing IBCT
|
|
561
|
+
// snapshots (interpolated vTexCoord = final atlas UV) stay bit-
|
|
562
|
+
// perfect identical. Strip uses the fragment-side wrap path
|
|
563
|
+
// (vStripPath = 1) for atlas-aware tiling of accumulated U.
|
|
564
|
+
vec4 remap = uTexRemaps[batchTexIdx];
|
|
565
|
+
vTexCoord = remap.xy + gridTexCoord * remap.zw;
|
|
566
|
+
vAtlasRemap = remap; // unused by fragment when vStripPath == 0
|
|
567
|
+
vStripPath = 0;
|
|
568
|
+
}
|
|
569
|
+
`, fragmentShaderSource = `#version 300 es
|
|
570
|
+
precision highp float;
|
|
571
|
+
|
|
572
|
+
uniform sampler2D uTextures[8];
|
|
573
|
+
// Host scene-graph container/world alpha. The particle already carries its own
|
|
574
|
+
// per-particle alpha in vColor; this is the global multiplier (e.g. a faded
|
|
575
|
+
// parent). Mirrors JS1.0, which scaled vertex.color[3] by worldAlpha.
|
|
576
|
+
uniform float uWorldAlpha;
|
|
577
|
+
|
|
578
|
+
in vec2 vTexCoord;
|
|
579
|
+
in vec4 vColor;
|
|
580
|
+
flat in int vBatchTextureIndex;
|
|
581
|
+
flat in vec4 vAtlasRemap;
|
|
582
|
+
flat in int vStripPath;
|
|
583
|
+
|
|
584
|
+
out vec4 fragColor;
|
|
585
|
+
|
|
586
|
+
void main() {
|
|
587
|
+
// Quad path (vStripPath == 0): vTexCoord is already the final atlas
|
|
588
|
+
// UV multiplied through in the vertex stage — kept bit-perfect with
|
|
589
|
+
// the original quad shader so IBCT snapshots remain identical.
|
|
590
|
+
//
|
|
591
|
+
// Strip path (vStripPath == 1): vTexCoord is a raw [0;..]-domain UV
|
|
592
|
+
// because the strip's accumulated U can exceed 1 (Distance mode
|
|
593
|
+
// accumulates dist / repeat, PerSegment counts up by 1 per segment).
|
|
594
|
+
// We wrap U via mod-style fract per-pixel before scaling into the
|
|
595
|
+
// emitter's atlas slot, so it tiles inside the slot instead of
|
|
596
|
+
// bleeding into a neighbouring atlas tile. fract(1.0) == 0.0, so a
|
|
597
|
+
// plain fract() would collapse U == 1.0 to U == 0.0 — we leave
|
|
598
|
+
// exact-[0;1] U values alone and only wrap outside that range.
|
|
599
|
+
// Atlas-space gradients of the RAW (un-wrapped) strip U. Hoisted above the
|
|
600
|
+
// strip/quad branch so the derivatives are taken in uniform control flow.
|
|
601
|
+
// For the affine atlas remap, d(vec2(u,y)*remap.zw+remap.xy) == d(u,y)*remap.zw,
|
|
602
|
+
// so differentiate the incoming varying (branch-invariant) and scale.
|
|
603
|
+
vec2 tcDx = dFdx(vTexCoord);
|
|
604
|
+
vec2 tcDy = dFdy(vTexCoord);
|
|
605
|
+
vec2 gradX = tcDx * vAtlasRemap.zw;
|
|
606
|
+
vec2 gradY = tcDy * vAtlasRemap.zw;
|
|
607
|
+
|
|
608
|
+
vec2 atlasUV;
|
|
609
|
+
// useGrad: strip atlas slot → sample with the un-wrapped-U gradients so the
|
|
610
|
+
// frac(u) discontinuity at a tile junction no longer poisons the mip/aniso
|
|
611
|
+
// LOD (the "chewed" column). Non-atlas strips (identity remap) and quads
|
|
612
|
+
// keep the implicit-LOD texture() sample so their baselines stay identical.
|
|
613
|
+
bool useGrad = false;
|
|
614
|
+
if (vStripPath == 0) {
|
|
615
|
+
atlasUV = vTexCoord;
|
|
616
|
+
} else {
|
|
617
|
+
float u = vTexCoord.x;
|
|
618
|
+
float wrappedU = u - floor(u);
|
|
619
|
+
if (u >= 0.0 && u <= 1.0) wrappedU = u;
|
|
620
|
+
atlasUV = vec2(wrappedU, vTexCoord.y) * vAtlasRemap.zw + vAtlasRemap.xy;
|
|
621
|
+
bool isAtlas = vAtlasRemap != vec4(0.0, 0.0, 1.0, 1.0);
|
|
622
|
+
useGrad = isAtlas;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
vec4 texColor;
|
|
626
|
+
// WebGL2 requires static indexing for sampler arrays.
|
|
627
|
+
if (vBatchTextureIndex == 0) texColor = useGrad ? textureGrad(uTextures[0], atlasUV, gradX, gradY) : texture(uTextures[0], atlasUV);
|
|
628
|
+
else if (vBatchTextureIndex == 1) texColor = useGrad ? textureGrad(uTextures[1], atlasUV, gradX, gradY) : texture(uTextures[1], atlasUV);
|
|
629
|
+
else if (vBatchTextureIndex == 2) texColor = useGrad ? textureGrad(uTextures[2], atlasUV, gradX, gradY) : texture(uTextures[2], atlasUV);
|
|
630
|
+
else if (vBatchTextureIndex == 3) texColor = useGrad ? textureGrad(uTextures[3], atlasUV, gradX, gradY) : texture(uTextures[3], atlasUV);
|
|
631
|
+
else if (vBatchTextureIndex == 4) texColor = useGrad ? textureGrad(uTextures[4], atlasUV, gradX, gradY) : texture(uTextures[4], atlasUV);
|
|
632
|
+
else if (vBatchTextureIndex == 5) texColor = useGrad ? textureGrad(uTextures[5], atlasUV, gradX, gradY) : texture(uTextures[5], atlasUV);
|
|
633
|
+
else if (vBatchTextureIndex == 6) texColor = useGrad ? textureGrad(uTextures[6], atlasUV, gradX, gradY) : texture(uTextures[6], atlasUV);
|
|
634
|
+
else texColor = useGrad ? textureGrad(uTextures[7], atlasUV, gradX, gradY) : texture(uTextures[7], atlasUV);
|
|
635
|
+
|
|
636
|
+
fragColor = texColor * vColor;
|
|
637
|
+
// Apply host world alpha. texColor is premultiplied and the blend funcs use
|
|
638
|
+
// a src factor of ONE (NORMAL) / ONE,ONE (ADD), so the whole RGBA must be
|
|
639
|
+
// scaled — fading RGB, not just the alpha channel — to dim correctly in both
|
|
640
|
+
// alpha-blended and additive modes.
|
|
641
|
+
fragColor *= uWorldAlpha;
|
|
642
|
+
}
|
|
643
|
+
`, vertexShaderSourceGL1 = `// WebGL1 (GLSL ES 1.00) port of particle.vert — variant C: the RGBA32F data
|
|
644
|
+
// texture is sampled with texture2DLod + computed UV, the u32-bit-packed
|
|
645
|
+
// fields come from the RGBA8 meta texture (ES 1.00 has no floatBitsToUint),
|
|
646
|
+
// and vertex identity comes from the static aId attribute (no gl_VertexID).
|
|
647
|
+
// Design: docs/tdd/TDD_js-v1.1-webgl1.md §4.5.
|
|
648
|
+
precision highp float;
|
|
649
|
+
|
|
650
|
+
attribute float aId; // particleIndex * 4 + corner
|
|
651
|
+
|
|
652
|
+
uniform sampler2D uDataTexture; // RGBA/FLOAT, 4 texels per particle
|
|
653
|
+
uniform sampler2D uMetaTexture; // RGBA8, same dimensions (meta texel i ~ data texel i)
|
|
654
|
+
uniform vec2 uDataTexSize;
|
|
655
|
+
uniform vec3 uCameraRight;
|
|
656
|
+
uniform vec3 uCameraUp;
|
|
657
|
+
uniform vec3 uCameraDir;
|
|
658
|
+
uniform mat4 uViewProjMatrix;
|
|
659
|
+
uniform mat4 uModelMatrix;
|
|
660
|
+
uniform float uViewportAspect; // viewport width / height — strip-only
|
|
661
|
+
uniform vec4 uTexRemaps[8];
|
|
662
|
+
|
|
663
|
+
varying vec2 vTexCoord;
|
|
664
|
+
varying vec4 vColor;
|
|
665
|
+
// Constant across the 4 vertices of a particle, so plain (smooth) varyings
|
|
666
|
+
// interpolate to the same value — ES 1.00 has no \`flat\`. The fragment stage
|
|
667
|
+
// re-quantizes vBatchTextureIndex via floor(x + 0.5).
|
|
668
|
+
varying float vBatchTextureIndex;
|
|
669
|
+
varying vec4 vAtlasRemap;
|
|
670
|
+
varying float vStripPath;
|
|
671
|
+
|
|
672
|
+
// idx -> row/column with the x-range fix-up: floor(idx/W) is exact on an
|
|
673
|
+
// IEEE-correct driver for idx < 2^24 (TDD §4.7), the fix-up defends against
|
|
674
|
+
// the division imprecision GLSL ES 1.00 permits.
|
|
675
|
+
vec2 texelUv(float idx) {
|
|
676
|
+
float y = floor(idx / uDataTexSize.x);
|
|
677
|
+
float x = idx - y * uDataTexSize.x;
|
|
678
|
+
if (x < 0.0) { y -= 1.0; x += uDataTexSize.x; }
|
|
679
|
+
if (x >= uDataTexSize.x) { y += 1.0; x -= uDataTexSize.x; }
|
|
680
|
+
return (vec2(x, y) + 0.5) / uDataTexSize;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
vec4 fetchData(float idx) { return texture2DLod(uDataTexture, texelUv(idx), 0.0); }
|
|
684
|
+
vec4 fetchMeta(float idx) { return texture2DLod(uMetaTexture, texelUv(idx), 0.0); }
|
|
685
|
+
|
|
686
|
+
// Exact byte recovery from an RGBA8 UNORM sample (b/255 is spec-exact).
|
|
687
|
+
float byteOf(float v) { return floor(v * 255.0 + 0.5); }
|
|
688
|
+
|
|
689
|
+
// Strip geometry helper (port of GPU33 makeScreenSpaceStrip).
|
|
690
|
+
vec2 stripOrtho(vec2 v) {
|
|
691
|
+
return vec2(-v.y, v.x);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Corner tables (ES 1.00 has no const-array initializers — if-ladder like
|
|
695
|
+
// the CS1.2 GL cornerTables()).
|
|
696
|
+
vec2 originMul(float corner) {
|
|
697
|
+
if (corner < 0.5) return vec2(-1.0, 0.0);
|
|
698
|
+
if (corner < 1.5) return vec2(-1.0, -1.0);
|
|
699
|
+
if (corner < 2.5) return vec2(0.0, -1.0);
|
|
700
|
+
return vec2(0.0, 0.0);
|
|
701
|
+
}
|
|
702
|
+
vec2 invOriginMul(float corner) {
|
|
703
|
+
if (corner < 0.5) return vec2(0.0, 1.0);
|
|
704
|
+
if (corner < 1.5) return vec2(0.0, 0.0);
|
|
705
|
+
if (corner < 2.5) return vec2(1.0, 0.0);
|
|
706
|
+
return vec2(1.0, 1.0);
|
|
707
|
+
}
|
|
708
|
+
vec2 texCoordOf(float corner) {
|
|
709
|
+
if (corner < 0.5) return vec2(0.0, 0.0);
|
|
710
|
+
if (corner < 1.5) return vec2(0.0, 1.0);
|
|
711
|
+
if (corner < 2.5) return vec2(1.0, 1.0);
|
|
712
|
+
return vec2(1.0, 0.0);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
void main() {
|
|
716
|
+
// Canonical aId decomposition (exact for aId < 2^24, TDD §4.5).
|
|
717
|
+
float corner = mod(aId, 4.0);
|
|
718
|
+
float particleIndex = (aId - corner) * 0.25;
|
|
719
|
+
float baseTexel = particleIndex * 4.0;
|
|
720
|
+
|
|
721
|
+
vec4 t0 = fetchData(baseTexel);
|
|
722
|
+
vec4 t1 = fetchData(baseTexel + 1.0);
|
|
723
|
+
vec4 t2 = fetchData(baseTexel + 2.0);
|
|
724
|
+
vec4 t3 = fetchData(baseTexel + 3.0);
|
|
725
|
+
|
|
726
|
+
// Meta M0: flags bytes — [batchTexIdx, rotationType|ctorType bits, -, -].
|
|
727
|
+
vec4 m0 = fetchMeta(baseTexel);
|
|
728
|
+
float batchTexIdx = byteOf(m0.r);
|
|
729
|
+
float flagsByte1 = byteOf(m0.g);
|
|
730
|
+
float rotationType = mod(flagsByte1, 4.0); // bits 1:0
|
|
731
|
+
float ctorType = mod(floor(flagsByte1 * 0.25), 4.0); // bits 3:2
|
|
732
|
+
vBatchTextureIndex = batchTexIdx;
|
|
733
|
+
|
|
734
|
+
vec3 position = t0.gba;
|
|
735
|
+
|
|
736
|
+
// ─── Strip path ───────────────────────────────────────────────────────
|
|
737
|
+
// Port of the ES 3.00 strip branch (particle.vert). Same algorithm and
|
|
738
|
+
// record conventions; deltas are purely mechanical: texel fetches via
|
|
739
|
+
// texture2DLod, packed colors read directly from meta M1 (the sample IS
|
|
740
|
+
// the color), neighbor RECORD indices decoded from 3 meta bytes
|
|
741
|
+
// (M2 @ pair-cur = neighborIdxP0, M3 @ pair-nex = neighborIdxP3), and
|
|
742
|
+
// the corner/int ladders in float math.
|
|
743
|
+
if (ctorType >= 0.5) {
|
|
744
|
+
bool isCur = (ctorType < 1.5);
|
|
745
|
+
float siblingTexel = isCur ? (baseTexel + 4.0) : (baseTexel - 4.0);
|
|
746
|
+
vec4 ts0 = fetchData(siblingTexel);
|
|
747
|
+
vec4 ts1 = fetchData(siblingTexel + 1.0);
|
|
748
|
+
|
|
749
|
+
// Pair bases: cur = the "p1" end, nex = the "p2" end.
|
|
750
|
+
float curBase = isCur ? baseTexel : siblingTexel;
|
|
751
|
+
float nexBase = isCur ? siblingTexel : baseTexel;
|
|
752
|
+
|
|
753
|
+
// p1 always = position of the meta_cur end; p2 = meta_nex end.
|
|
754
|
+
vec3 p1 = isCur ? t0.gba : ts0.gba;
|
|
755
|
+
vec3 p2 = isCur ? ts0.gba : t0.gba;
|
|
756
|
+
|
|
757
|
+
float halfSize1 = isCur ? t1.r : ts1.r;
|
|
758
|
+
float halfSize2 = isCur ? ts1.r : t1.r;
|
|
759
|
+
float texU1 = isCur ? t1.g : ts1.g;
|
|
760
|
+
float texU2 = isCur ? ts1.g : t1.g;
|
|
761
|
+
|
|
762
|
+
// Colors: the meta M1 sample is the normalized RGBA directly.
|
|
763
|
+
vec4 color1 = fetchMeta(curBase + 1.0);
|
|
764
|
+
vec4 color2 = fetchMeta(nexBase + 1.0);
|
|
765
|
+
|
|
766
|
+
// Neighbor RECORD indices (system.js convention: neighborIdxP0 on the
|
|
767
|
+
// pair-cur meta, neighborIdxP3 on the pair-nex meta; boundary
|
|
768
|
+
// sentinel = the pair's cur record index).
|
|
769
|
+
vec4 mNp0 = fetchMeta(curBase + 2.0);
|
|
770
|
+
vec4 mNp3 = fetchMeta(nexBase + 3.0);
|
|
771
|
+
float np0 = byteOf(mNp0.r) + byteOf(mNp0.g) * 256.0 + byteOf(mNp0.b) * 65536.0;
|
|
772
|
+
float np3 = byteOf(mNp3.r) + byteOf(mNp3.g) * 256.0 + byteOf(mNp3.b) * 65536.0;
|
|
773
|
+
float selfPairCurRecord = curBase * 0.25;
|
|
774
|
+
|
|
775
|
+
vec3 p0 = (abs(np0 - selfPairCurRecord) < 0.5)
|
|
776
|
+
? p1
|
|
777
|
+
: fetchData(np0 * 4.0).gba;
|
|
778
|
+
vec3 p3 = (abs(np3 - selfPairCurRecord) < 0.5)
|
|
779
|
+
? p2
|
|
780
|
+
: fetchData(np3 * 4.0).gba;
|
|
781
|
+
|
|
782
|
+
// Segment frame: Faced (rotationType 0/2) = screen-space NDC frame;
|
|
783
|
+
// Free (rotationType 1) = full world-space 3D frame. Mirror of the
|
|
784
|
+
// ES 3.00 shader / preview3 DataTextureRenderer3.
|
|
785
|
+
mat4 mvpMatrix = uViewProjMatrix * uModelMatrix;
|
|
786
|
+
bool isFreeRotation = (rotationType > 0.5 && rotationType < 1.5);
|
|
787
|
+
|
|
788
|
+
// Anchor projections — both branches use them for gl_Position.
|
|
789
|
+
vec4 sp1 = mvpMatrix * vec4(p1, 1.0);
|
|
790
|
+
vec4 sp2 = mvpMatrix * vec4(p2, 1.0);
|
|
791
|
+
|
|
792
|
+
// Near-plane guard (Faced only; the world-space Free frame is
|
|
793
|
+
// unaffected by the sign of w).
|
|
794
|
+
bool nearPlaneClipped = (sp1.w < 1e-3) || (sp2.w < 1e-3);
|
|
795
|
+
|
|
796
|
+
// Aspect correction for the 2D Faced ortho on non-square viewports.
|
|
797
|
+
vec2 aspectMul = vec2(1.0, 1.0 / max(uViewportAspect, 1e-6));
|
|
798
|
+
|
|
799
|
+
vec4 ts2 = fetchData(siblingTexel + 2.0);
|
|
800
|
+
vec4 q1 = isCur ? t2 : ts2;
|
|
801
|
+
vec4 q2 = isCur ? ts2 : t2;
|
|
802
|
+
|
|
803
|
+
// Frame outputs: Faced fills the 2D (k/b/IK), Free the 3D
|
|
804
|
+
// (worldK/B/IK); the vertex ladder picks by isFreeRotation.
|
|
805
|
+
vec3 worldK1, worldB1, worldIK1, worldK2, worldB2, worldIK2;
|
|
806
|
+
vec2 k1, b1, IK1, k2, b2, IK2;
|
|
807
|
+
float Bmul1, Bmul2, kb1, kb2;
|
|
808
|
+
bool noKnee1, noKnee2;
|
|
809
|
+
float pathLen12; // len12 in the active frame's units
|
|
810
|
+
|
|
811
|
+
// Initialize both frames (ES 1.00 warns on possibly-uninitialized
|
|
812
|
+
// locals across the branch below on some compilers).
|
|
813
|
+
worldK1 = vec3(0.0); worldB1 = vec3(0.0); worldIK1 = vec3(0.0);
|
|
814
|
+
worldK2 = vec3(0.0); worldB2 = vec3(0.0); worldIK2 = vec3(0.0);
|
|
815
|
+
k1 = vec2(0.0); b1 = vec2(0.0); IK1 = vec2(0.0);
|
|
816
|
+
k2 = vec2(0.0); b2 = vec2(0.0); IK2 = vec2(0.0);
|
|
817
|
+
|
|
818
|
+
if (isFreeRotation) {
|
|
819
|
+
// World-space frame (mirror of GPU33 make3DStrip). Section normal
|
|
820
|
+
// n = quat z-axis; B = cross(dir12, n).
|
|
821
|
+
float xz1 = 2.0 * q1.x * q1.z;
|
|
822
|
+
float wy1 = 2.0 * q1.w * q1.y;
|
|
823
|
+
float yz1 = 2.0 * q1.y * q1.z;
|
|
824
|
+
float wx1 = 2.0 * q1.w * q1.x;
|
|
825
|
+
vec3 n1 = vec3(xz1 + wy1, yz1 - wx1, 1.0 - 2.0 * (q1.x * q1.x + q1.y * q1.y));
|
|
826
|
+
float xz2 = 2.0 * q2.x * q2.z;
|
|
827
|
+
float wy2 = 2.0 * q2.w * q2.y;
|
|
828
|
+
float yz2 = 2.0 * q2.y * q2.z;
|
|
829
|
+
float wx2 = 2.0 * q2.w * q2.x;
|
|
830
|
+
vec3 n2 = vec3(xz2 + wy2, yz2 - wx2, 1.0 - 2.0 * (q2.x * q2.x + q2.y * q2.y));
|
|
831
|
+
|
|
832
|
+
vec3 wp01 = p1 - p0;
|
|
833
|
+
vec3 wp12 = p2 - p1;
|
|
834
|
+
vec3 wp23 = p3 - p2;
|
|
835
|
+
float wlen01 = length(wp01);
|
|
836
|
+
float wlen12 = length(wp12);
|
|
837
|
+
float wlen23 = length(wp23);
|
|
838
|
+
vec3 wdir12 = (wlen12 > 1e-6) ? (wp12 / wlen12) : vec3(1.0, 0.0, 0.0);
|
|
839
|
+
vec3 wdir01 = (wlen01 > 1e-6) ? (wp01 / wlen01) : wdir12;
|
|
840
|
+
vec3 wdir23 = (wlen23 > 1e-6) ? (wp23 / wlen23) : wdir12;
|
|
841
|
+
noKnee1 = dot(wdir01, wdir12) > 0.999;
|
|
842
|
+
noKnee2 = dot(wdir12, wdir23) > 0.999;
|
|
843
|
+
pathLen12 = wlen12;
|
|
844
|
+
|
|
845
|
+
if (!noKnee1) {
|
|
846
|
+
vec3 B1raw = normalize(cross(wdir12, n1));
|
|
847
|
+
vec3 c1 = normalize(cross(wdir01, wdir12));
|
|
848
|
+
float Kmul1 = sign(dot(c1, n1));
|
|
849
|
+
vec3 K1 = Kmul1 * normalize(cross(wdir01 + wdir12, n1));
|
|
850
|
+
Bmul1 = sign(dot(B1raw, K1));
|
|
851
|
+
vec3 B1 = B1raw * Bmul1;
|
|
852
|
+
float ik1 = halfSize1 / max(dot(B1, K1), 1e-6);
|
|
853
|
+
if (dot(-wdir01, ik1 * -K1) > wlen01) ik1 = wlen01;
|
|
854
|
+
if (dot( wdir12, ik1 * -K1) > wlen12) ik1 = wlen12;
|
|
855
|
+
worldK1 = p1 + K1 * halfSize1;
|
|
856
|
+
worldB1 = p1 + B1 * halfSize1;
|
|
857
|
+
worldIK1 = p1 - K1 * ik1;
|
|
858
|
+
kb1 = distance(worldK1, worldB1);
|
|
859
|
+
} else {
|
|
860
|
+
Bmul1 = 1.0;
|
|
861
|
+
vec3 B1 = normalize(cross(wdir01 + wdir12, n1));
|
|
862
|
+
worldK1 = p1 + B1 * halfSize1;
|
|
863
|
+
worldB1 = worldK1;
|
|
864
|
+
worldIK1 = p1 - B1 * halfSize1;
|
|
865
|
+
kb1 = 0.0;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
if (!noKnee2) {
|
|
869
|
+
vec3 B2raw = normalize(cross(wdir12, n2));
|
|
870
|
+
vec3 c2 = normalize(cross(wdir12, wdir23));
|
|
871
|
+
float Kmul2 = sign(dot(c2, n2));
|
|
872
|
+
vec3 K2 = Kmul2 * normalize(cross(wdir12 + wdir23, n2));
|
|
873
|
+
Bmul2 = sign(dot(B2raw, K2));
|
|
874
|
+
vec3 B2 = B2raw * Bmul2;
|
|
875
|
+
float ik2 = halfSize2 / max(dot(B2, K2), 1e-6);
|
|
876
|
+
if (dot(-wdir12, ik2 * -K2) > wlen12) ik2 = wlen12;
|
|
877
|
+
if (dot( wdir23, ik2 * -K2) > wlen23) ik2 = wlen23;
|
|
878
|
+
worldK2 = p2 + K2 * halfSize2;
|
|
879
|
+
worldB2 = p2 + B2 * halfSize2;
|
|
880
|
+
worldIK2 = p2 - K2 * ik2;
|
|
881
|
+
kb2 = distance(worldK2, worldB2);
|
|
882
|
+
} else {
|
|
883
|
+
Bmul2 = 1.0;
|
|
884
|
+
vec3 B2 = normalize(cross(wdir12 + wdir23, n2));
|
|
885
|
+
worldK2 = p2 + B2 * halfSize2;
|
|
886
|
+
worldB2 = worldK2;
|
|
887
|
+
worldIK2 = p2 - B2 * halfSize2;
|
|
888
|
+
kb2 = 0.0;
|
|
889
|
+
}
|
|
890
|
+
} else {
|
|
891
|
+
// Faced: screen-space NDC frame around dir12.
|
|
892
|
+
vec4 sp0 = mvpMatrix * vec4(p0, 1.0);
|
|
893
|
+
vec4 sp3 = mvpMatrix * vec4(p3, 1.0);
|
|
894
|
+
vec2 ndc1 = (sp1.xy / sp1.w) * aspectMul;
|
|
895
|
+
vec2 ndc2 = (sp2.xy / sp2.w) * aspectMul;
|
|
896
|
+
// Neighbor anchors behind the near plane produce garbage NDC after
|
|
897
|
+
// the perspective divide (possible only with Projection3D, where w
|
|
898
|
+
// varies); collapse them onto their segment anchor so the existing
|
|
899
|
+
// zero-length degeneracy path (dir01 = dir12 / dir23 = dir12)
|
|
900
|
+
// handles the knee instead of a poisoned miter. spU1/spU2 need no
|
|
901
|
+
// guard: uCameraUp has zero Z, so their w equals sp1.w / sp2.w.
|
|
902
|
+
vec2 ndc0 = (sp0.w < 1e-3) ? ndc1 : (sp0.xy / sp0.w) * aspectMul;
|
|
903
|
+
vec2 ndc3 = (sp3.w < 1e-3) ? ndc2 : (sp3.xy / sp3.w) * aspectMul;
|
|
904
|
+
|
|
905
|
+
vec2 d01 = ndc1 - ndc0;
|
|
906
|
+
vec2 d12 = ndc2 - ndc1;
|
|
907
|
+
vec2 d23 = ndc3 - ndc2;
|
|
908
|
+
float len01 = length(d01);
|
|
909
|
+
float len12 = length(d12);
|
|
910
|
+
float len23 = length(d23);
|
|
911
|
+
vec2 dir12 = (len12 > 1e-6) ? (d12 / len12) : vec2(1.0, 0.0);
|
|
912
|
+
vec2 dir01 = (len01 > 1e-6) ? (d01 / len01) : dir12;
|
|
913
|
+
vec2 dir23 = (len23 > 1e-6) ? (d23 / len23) : dir12;
|
|
914
|
+
noKnee1 = dot(dir01, dir12) > 0.999;
|
|
915
|
+
noKnee2 = dot(dir12, dir23) > 0.999;
|
|
916
|
+
pathLen12 = len12;
|
|
917
|
+
|
|
918
|
+
// Section magnitude: project camera_up*halfSize per anchor into NDC.
|
|
919
|
+
vec4 spU1 = mvpMatrix * vec4(p1 + uCameraUp * halfSize1, 1.0);
|
|
920
|
+
vec4 spU2 = mvpMatrix * vec4(p2 + uCameraUp * halfSize2, 1.0);
|
|
921
|
+
float ndcHalfSize1 = length((spU1.xy / spU1.w) * aspectMul - ndc1);
|
|
922
|
+
float ndcHalfSize2 = length((spU2.xy / spU2.w) * aspectMul - ndc2);
|
|
923
|
+
|
|
924
|
+
vec2 B = stripOrtho(dir12);
|
|
925
|
+
if (!noKnee1) {
|
|
926
|
+
float Kmul1 = -sign(dot(stripOrtho(dir01), dir12));
|
|
927
|
+
vec2 K1 = Kmul1 * normalize(stripOrtho(dir01 + dir12));
|
|
928
|
+
Bmul1 = sign(dot(B, K1));
|
|
929
|
+
vec2 B1 = B * Bmul1;
|
|
930
|
+
float ik1 = ndcHalfSize1 / max(dot(B1, K1), 1e-6);
|
|
931
|
+
if (dot(-dir01, ik1 * -K1) > len01) ik1 = len01;
|
|
932
|
+
if (dot(dir12, ik1 * -K1) > len12) ik1 = len12;
|
|
933
|
+
k1 = ndc1 + K1 * ndcHalfSize1;
|
|
934
|
+
b1 = ndc1 + B1 * ndcHalfSize1;
|
|
935
|
+
IK1 = ndc1 - K1 * ik1;
|
|
936
|
+
kb1 = distance(k1, b1);
|
|
937
|
+
} else {
|
|
938
|
+
vec2 B1 = normalize(stripOrtho(dir01 + dir12));
|
|
939
|
+
Bmul1 = 1.0;
|
|
940
|
+
k1 = ndc1 + B1 * ndcHalfSize1;
|
|
941
|
+
b1 = k1;
|
|
942
|
+
IK1 = ndc1 - B1 * ndcHalfSize1;
|
|
943
|
+
kb1 = 0.0;
|
|
944
|
+
}
|
|
945
|
+
if (!noKnee2) {
|
|
946
|
+
float Kmul2 = -sign(dot(stripOrtho(dir12), dir23));
|
|
947
|
+
vec2 K2 = Kmul2 * normalize(stripOrtho(dir12 + dir23));
|
|
948
|
+
Bmul2 = sign(dot(B, K2));
|
|
949
|
+
vec2 B2 = B * Bmul2;
|
|
950
|
+
float ik2 = ndcHalfSize2 / max(dot(B2, K2), 1e-6);
|
|
951
|
+
if (dot(-dir12, ik2 * -K2) > len12) ik2 = len12;
|
|
952
|
+
if (dot(dir23, ik2 * -K2) > len23) ik2 = len23;
|
|
953
|
+
k2 = ndc2 + K2 * ndcHalfSize2;
|
|
954
|
+
b2 = ndc2 + B2 * ndcHalfSize2;
|
|
955
|
+
IK2 = ndc2 - K2 * ik2;
|
|
956
|
+
kb2 = distance(k2, b2);
|
|
957
|
+
} else {
|
|
958
|
+
vec2 B2 = normalize(stripOrtho(dir12 + dir23));
|
|
959
|
+
Bmul2 = 1.0;
|
|
960
|
+
k2 = ndc2 + B2 * ndcHalfSize2;
|
|
961
|
+
b2 = k2;
|
|
962
|
+
IK2 = ndc2 - B2 * ndcHalfSize2;
|
|
963
|
+
kb2 = 0.0;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// V coords: V at outer knee (k) = (Bmul + 1) * 0.5; inner = 1 - outer.
|
|
968
|
+
float Vtop1 = (Bmul1 + 1.0) * 0.5;
|
|
969
|
+
float Vbot1 = 1.0 - Vtop1;
|
|
970
|
+
float Vtop2 = (Bmul2 + 1.0) * 0.5;
|
|
971
|
+
float Vbot2 = 1.0 - Vtop2;
|
|
972
|
+
|
|
973
|
+
// Vertex layout per meta over the IBO [0,1,2, 0,2,3] — same ladder as
|
|
974
|
+
// the ES 3.00 shader (see its comment block for the geometry).
|
|
975
|
+
bool sameSide = (Bmul1 * Bmul2 >= 0.0);
|
|
976
|
+
float dPath = max(sameSide ? (kb1 + pathLen12 + kb2) : (kb1 + pathLen12), 1e-6);
|
|
977
|
+
float dPathNex = max(sameSide ? dPath : (kb2 + pathLen12), 1e-6);
|
|
978
|
+
|
|
979
|
+
float bUcur = kb1 / dPath;
|
|
980
|
+
float bUnex = sameSide ? (kb2 / dPath) : (kb2 / dPathNex);
|
|
981
|
+
vec4 colorB1 = mix(color1, color2, bUcur);
|
|
982
|
+
vec4 colorB2 = mix(color2, color1, bUnex);
|
|
983
|
+
|
|
984
|
+
vec2 vertPos;
|
|
985
|
+
vec3 vertPos3D = vec3(0.0);
|
|
986
|
+
vec2 vertUV;
|
|
987
|
+
vec4 vertColor;
|
|
988
|
+
if (isCur) {
|
|
989
|
+
// meta_cur vertices
|
|
990
|
+
if (corner < 0.5) {
|
|
991
|
+
vertPos = b1; vertPos3D = worldB1;
|
|
992
|
+
vertUV = vec2(mix(texU1, texU2, kb1 / dPath), Vtop1);
|
|
993
|
+
vertColor = colorB1;
|
|
994
|
+
} else if (corner < 1.5) {
|
|
995
|
+
vertPos = k1; vertPos3D = worldK1;
|
|
996
|
+
vertUV = vec2(texU1, Vtop1);
|
|
997
|
+
vertColor = color1;
|
|
998
|
+
} else if (corner < 2.5) {
|
|
999
|
+
vertPos = IK1; vertPos3D = worldIK1;
|
|
1000
|
+
vertUV = vec2(texU1, Vbot1);
|
|
1001
|
+
vertColor = color1;
|
|
1002
|
+
} else {
|
|
1003
|
+
// v3 = b2 (sameSide) or IK2 (different side)
|
|
1004
|
+
if (sameSide) {
|
|
1005
|
+
vertPos = b2; vertPos3D = worldB2;
|
|
1006
|
+
vertUV = vec2(mix(texU2, texU1, kb2 / dPath), Vtop2);
|
|
1007
|
+
vertColor = colorB2;
|
|
1008
|
+
} else {
|
|
1009
|
+
vertPos = IK2; vertPos3D = worldIK2;
|
|
1010
|
+
vertUV = vec2(texU2, Vbot2);
|
|
1011
|
+
vertColor = color2;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
// noKnee fallback: collapse to k1/IK1 to produce degenerate segments.
|
|
1015
|
+
if (noKnee1) {
|
|
1016
|
+
if (corner < 0.5) { vertPos = k1; vertPos3D = worldK1; vertUV = vec2(texU1, Vtop1); vertColor = color1; }
|
|
1017
|
+
}
|
|
1018
|
+
} else {
|
|
1019
|
+
// meta_nex vertices
|
|
1020
|
+
if (sameSide) {
|
|
1021
|
+
if (corner < 0.5) { vertPos = b2; vertPos3D = worldB2; vertUV = vec2(mix(texU2, texU1, kb2 / dPath), Vtop2); vertColor = colorB2; }
|
|
1022
|
+
else if (corner < 1.5) { vertPos = IK1; vertPos3D = worldIK1; vertUV = vec2(texU1, Vbot1); vertColor = color1; }
|
|
1023
|
+
else if (corner < 2.5) { vertPos = IK2; vertPos3D = worldIK2; vertUV = vec2(texU2, Vbot2); vertColor = color2; }
|
|
1024
|
+
else { vertPos = k2; vertPos3D = worldK2; vertUV = vec2(texU2, Vtop2); vertColor = color2; }
|
|
1025
|
+
} else {
|
|
1026
|
+
if (corner < 0.5) { vertPos = IK2; vertPos3D = worldIK2; vertUV = vec2(texU2, Vbot2); vertColor = color2; }
|
|
1027
|
+
else if (corner < 1.5) { vertPos = IK1; vertPos3D = worldIK1; vertUV = vec2(texU1, Vbot1); vertColor = color1; }
|
|
1028
|
+
else if (corner < 2.5) { vertPos = b2; vertPos3D = worldB2; vertUV = vec2(mix(texU2, texU1, kb2 / dPathNex), Vtop2); vertColor = colorB2; }
|
|
1029
|
+
else { vertPos = k2; vertPos3D = worldK2; vertUV = vec2(texU2, Vtop2); vertColor = color2; }
|
|
1030
|
+
}
|
|
1031
|
+
if (noKnee2) {
|
|
1032
|
+
if (corner > 2.5) { vertPos = IK2; vertPos3D = worldIK2; vertUV = vec2(texU2, Vbot2); vertColor = color2; }
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// Free projects the world-space vertex through MVP directly; Faced
|
|
1037
|
+
// reconstructs clip space from aspect-corrected NDC.
|
|
1038
|
+
if (isFreeRotation) {
|
|
1039
|
+
gl_Position = mvpMatrix * vec4(vertPos3D, 1.0);
|
|
1040
|
+
} else if (nearPlaneClipped) {
|
|
1041
|
+
gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
|
|
1042
|
+
} else {
|
|
1043
|
+
vec4 anchorProj = isCur ? sp1 : sp2;
|
|
1044
|
+
vec2 vertPosClip = vertPos / aspectMul;
|
|
1045
|
+
gl_Position = vec4(vertPosClip * anchorProj.w, anchorProj.zw);
|
|
1046
|
+
}
|
|
1047
|
+
vColor = vertColor;
|
|
1048
|
+
|
|
1049
|
+
// Strip emits raw (U, V) — fragment wraps U into the atlas slot.
|
|
1050
|
+
vTexCoord = vertUV;
|
|
1051
|
+
vAtlasRemap = uTexRemaps[int(batchTexIdx)];
|
|
1052
|
+
vStripPath = 1.0;
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
// ─── End strip path ─────────────────────────────────────────────────────
|
|
1056
|
+
|
|
1057
|
+
// Texel 1: originX, originY, sizeX, sizeY
|
|
1058
|
+
vec2 origin = t1.rg;
|
|
1059
|
+
vec2 size = t1.ba;
|
|
1060
|
+
|
|
1061
|
+
// Meta M1: the packedColor u32 LE bytes ARE the normalized RGBA sample.
|
|
1062
|
+
vColor = fetchMeta(baseTexel + 1.0);
|
|
1063
|
+
|
|
1064
|
+
// Meta M2: gridConfig — [gridW lo, gridW hi, gridH lo, gridH hi].
|
|
1065
|
+
vec4 m2 = fetchMeta(baseTexel + 2.0);
|
|
1066
|
+
float gridWidth = max(byteOf(m2.r) + byteOf(m2.g) * 256.0, 1.0);
|
|
1067
|
+
float gridHeight = max(byteOf(m2.b) + byteOf(m2.a) * 256.0, 1.0);
|
|
1068
|
+
// gridIndex is a plain float — read from the float texture (mirror of the
|
|
1069
|
+
// ES 3.00 shader: truncate so animated frames pick a single cell).
|
|
1070
|
+
float gridIndex = floor(t3.b);
|
|
1071
|
+
|
|
1072
|
+
// Texel 2: rotation
|
|
1073
|
+
vec3 xaxis, yaxis;
|
|
1074
|
+
|
|
1075
|
+
if (rotationType < 0.5) {
|
|
1076
|
+
float angle = t2.r;
|
|
1077
|
+
float rad = angle * 3.14159265 / 180.0;
|
|
1078
|
+
float s = -sin(rad);
|
|
1079
|
+
float c = cos(rad);
|
|
1080
|
+
xaxis = uCameraRight * c + uCameraUp * s;
|
|
1081
|
+
yaxis = -uCameraRight * s + uCameraUp * c;
|
|
1082
|
+
} else if (rotationType < 1.5) {
|
|
1083
|
+
vec4 q = t2;
|
|
1084
|
+
float z2 = 2.0 * q.z * q.z;
|
|
1085
|
+
float xy = 2.0 * q.x * q.y;
|
|
1086
|
+
float wz = 2.0 * q.w * q.z;
|
|
1087
|
+
xaxis = vec3(
|
|
1088
|
+
1.0 - 2.0 * q.y * q.y - z2,
|
|
1089
|
+
xy + wz,
|
|
1090
|
+
2.0 * q.x * q.z - 2.0 * q.w * q.y);
|
|
1091
|
+
yaxis = vec3(
|
|
1092
|
+
xy - wz,
|
|
1093
|
+
1.0 - 2.0 * q.x * q.x - z2,
|
|
1094
|
+
2.0 * q.y * q.z + 2.0 * q.w * q.x);
|
|
1095
|
+
} else {
|
|
1096
|
+
xaxis = cross(uCameraUp, uCameraDir);
|
|
1097
|
+
yaxis = uCameraUp;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
xaxis *= size.x;
|
|
1101
|
+
yaxis *= size.y;
|
|
1102
|
+
|
|
1103
|
+
vec2 vOrigin = originMul(corner) * origin
|
|
1104
|
+
+ invOriginMul(corner) * (vec2(1.0) - origin);
|
|
1105
|
+
vec3 posDisp = vOrigin.x * xaxis + vOrigin.y * yaxis;
|
|
1106
|
+
|
|
1107
|
+
mat4 mvpMatrix = uViewProjMatrix * uModelMatrix;
|
|
1108
|
+
vec3 worldPos = position + posDisp;
|
|
1109
|
+
gl_Position = mvpMatrix * vec4(worldPos, 1.0);
|
|
1110
|
+
|
|
1111
|
+
// v1.0-parity near-plane culling for the perspective projection: the
|
|
1112
|
+
// whole quad is discarded when ANY of its 4 corners is behind the near
|
|
1113
|
+
// plane (w < 0.01 == pos.z > 0.99 * z_cam in PerspectiveProjection
|
|
1114
|
+
// terms). w is linear in position, so the corner minimum comes from the
|
|
1115
|
+
// w-row and the corner offsets (x in {-ox, 1-ox}, y in {-oy, 1-oy}).
|
|
1116
|
+
// Without a projection every w is 1.0 and this is a no-op.
|
|
1117
|
+
vec4 wRow = vec4(mvpMatrix[0].w, mvpMatrix[1].w, mvpMatrix[2].w, mvpMatrix[3].w);
|
|
1118
|
+
float wx = dot(wRow.xyz, xaxis);
|
|
1119
|
+
float wy = dot(wRow.xyz, yaxis);
|
|
1120
|
+
float minW = dot(wRow.xyz, position) + wRow.w
|
|
1121
|
+
- origin.x * wx + min(wx, 0.0)
|
|
1122
|
+
- origin.y * wy + min(wy, 0.0);
|
|
1123
|
+
if (minW < 0.01) {
|
|
1124
|
+
gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// Texture coordinates with grid + atlas remap
|
|
1128
|
+
vec2 baseTexCoord = texCoordOf(corner);
|
|
1129
|
+
|
|
1130
|
+
float gx = mod(gridIndex, gridWidth);
|
|
1131
|
+
float gy = floor(gridIndex / gridWidth);
|
|
1132
|
+
float cellW = 1.0 / gridWidth;
|
|
1133
|
+
float cellH = 1.0 / gridHeight;
|
|
1134
|
+
|
|
1135
|
+
vec2 gridTexCoord = vec2(
|
|
1136
|
+
(gx + baseTexCoord.x) * cellW,
|
|
1137
|
+
1.0 - (gy + 1.0 - baseTexCoord.y) * cellH
|
|
1138
|
+
);
|
|
1139
|
+
|
|
1140
|
+
// Dynamic indexing of a uniform array is legal in ES 1.00 VERTEX shaders
|
|
1141
|
+
// (only fragment shaders are constant-index restricted).
|
|
1142
|
+
vec4 remap = uTexRemaps[int(batchTexIdx)];
|
|
1143
|
+
vTexCoord = remap.xy + gridTexCoord * remap.zw;
|
|
1144
|
+
vAtlasRemap = remap; // unused by fragment when vStripPath == 0
|
|
1145
|
+
vStripPath = 0.0;
|
|
1146
|
+
}
|
|
1147
|
+
`, fragmentShaderSourceGL1 = `// WebGL1 (GLSL ES 1.00) port of particle.frag. Fragment highp is a REQUIRED
|
|
1148
|
+
// capability of the GL1 path (probed at Context creation) — the strip U-wrap
|
|
1149
|
+
// does floor/fract on accumulated texU which mediump would corrupt; the
|
|
1150
|
+
// #else branch only keeps the shader compilable on probe-rejected devices.
|
|
1151
|
+
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
|
1152
|
+
precision highp float;
|
|
1153
|
+
#else
|
|
1154
|
+
precision mediump float;
|
|
1155
|
+
#endif
|
|
1156
|
+
|
|
1157
|
+
uniform sampler2D uTextures[8];
|
|
1158
|
+
// Host scene-graph container/world alpha (same semantics as the ES 3.00
|
|
1159
|
+
// shader: scales the whole premultiplied RGBA).
|
|
1160
|
+
uniform float uWorldAlpha;
|
|
1161
|
+
|
|
1162
|
+
varying vec2 vTexCoord;
|
|
1163
|
+
varying vec4 vColor;
|
|
1164
|
+
varying float vBatchTextureIndex; // constant across the quad; re-quantized below
|
|
1165
|
+
varying vec4 vAtlasRemap;
|
|
1166
|
+
varying float vStripPath;
|
|
1167
|
+
|
|
1168
|
+
void main() {
|
|
1169
|
+
// Quad path (vStripPath == 0): vTexCoord is the final atlas UV from the
|
|
1170
|
+
// vertex stage. Strip path (vStripPath == 1): raw U wrapped per-pixel
|
|
1171
|
+
// into the atlas slot; exact-[0;1] U is left alone (fract(1.0) == 0.0
|
|
1172
|
+
// would collapse the strip end) — mirror of the ES 3.00 shader.
|
|
1173
|
+
vec2 atlasUV;
|
|
1174
|
+
if (vStripPath < 0.5) {
|
|
1175
|
+
atlasUV = vTexCoord;
|
|
1176
|
+
} else {
|
|
1177
|
+
float u = vTexCoord.x;
|
|
1178
|
+
float wrappedU = u - floor(u);
|
|
1179
|
+
if (u >= 0.0 && u <= 1.0) wrappedU = u;
|
|
1180
|
+
atlasUV = vec2(wrappedU, vTexCoord.y) * vAtlasRemap.zw + vAtlasRemap.xy;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// ES 1.00 fragment shaders require constant sampler indexing — the same
|
|
1184
|
+
// if-ladder as WebGL2, driven by the re-quantized float varying.
|
|
1185
|
+
float texIdx = floor(vBatchTextureIndex + 0.5);
|
|
1186
|
+
vec4 texColor;
|
|
1187
|
+
if (texIdx < 0.5) texColor = texture2D(uTextures[0], atlasUV);
|
|
1188
|
+
else if (texIdx < 1.5) texColor = texture2D(uTextures[1], atlasUV);
|
|
1189
|
+
else if (texIdx < 2.5) texColor = texture2D(uTextures[2], atlasUV);
|
|
1190
|
+
else if (texIdx < 3.5) texColor = texture2D(uTextures[3], atlasUV);
|
|
1191
|
+
else if (texIdx < 4.5) texColor = texture2D(uTextures[4], atlasUV);
|
|
1192
|
+
else if (texIdx < 5.5) texColor = texture2D(uTextures[5], atlasUV);
|
|
1193
|
+
else if (texIdx < 6.5) texColor = texture2D(uTextures[6], atlasUV);
|
|
1194
|
+
else texColor = texture2D(uTextures[7], atlasUV);
|
|
1195
|
+
|
|
1196
|
+
// texColor is premultiplied and blend src factors are ONE-based, so the
|
|
1197
|
+
// world alpha scales the whole RGBA (mirror of the ES 3.00 shader).
|
|
1198
|
+
gl_FragColor = texColor * vColor * uWorldAlpha;
|
|
1199
|
+
}
|
|
1200
|
+
`, R = class R {
|
|
1201
|
+
constructor(e, t) {
|
|
1202
|
+
i(this, "neutrino");
|
|
1203
|
+
i(this, "renderer");
|
|
1204
|
+
// On a WebGL1 context this actually holds a WebGLRenderingContext; the
|
|
1205
|
+
// GL2-only members are never touched on the GL1 path.
|
|
1206
|
+
i(this, "gl");
|
|
1207
|
+
i(this, "options");
|
|
1208
|
+
i(this, "loadData");
|
|
1209
|
+
i(this, "_shaderProgram", null);
|
|
1210
|
+
i(this, "_indexBuffer", null);
|
|
1211
|
+
i(this, "_dummyVB", null);
|
|
1212
|
+
i(this, "_indexBufferCapacity", 0);
|
|
1213
|
+
i(this, "_vao", null);
|
|
1214
|
+
// WebGL1 render path state (TDD_js-v1.1-webgl1.md)
|
|
1215
|
+
i(this, "_isWebGL1", !1);
|
|
1216
|
+
i(this, "_maxBatchTextures", R.MAX_BATCH_TEXTURES);
|
|
1217
|
+
i(this, "_hasElementIndexUint", !1);
|
|
1218
|
+
i(this, "_vaoExt", null);
|
|
1219
|
+
i(this, "_aIdBuffer", null);
|
|
1220
|
+
i(this, "_aIdLocation", -1);
|
|
1221
|
+
// Uniform locations
|
|
1222
|
+
i(this, "_uDataTexture", null);
|
|
1223
|
+
i(this, "_uDataTextureWidth", null);
|
|
1224
|
+
i(this, "_uMetaTexture", null);
|
|
1225
|
+
i(this, "_uDataTexSize", null);
|
|
1226
|
+
i(this, "_uCameraRight", null);
|
|
1227
|
+
i(this, "_uCameraUp", null);
|
|
1228
|
+
i(this, "_uCameraDir", null);
|
|
1229
|
+
i(this, "_uViewProjMatrix", null);
|
|
1230
|
+
i(this, "_uModelMatrix", null);
|
|
1231
|
+
i(this, "_uViewportAspect", null);
|
|
1232
|
+
i(this, "_uWorldAlpha", null);
|
|
1233
|
+
i(this, "_uTextures", []);
|
|
1234
|
+
i(this, "_uTexRemaps", []);
|
|
1235
|
+
i(this, "_noiseInitialized");
|
|
1236
|
+
var n;
|
|
1237
|
+
if (this.renderer = e, this.gl = e.gl, !this.gl)
|
|
1238
|
+
throw new Error("NeutrinoParticles (js-v1.1): a WebGL renderer is required.");
|
|
1239
|
+
this.options = Object.assign({
|
|
1240
|
+
texturesBasePath: "",
|
|
1241
|
+
trimmedExtensionsLookupFirst: !0
|
|
1242
|
+
}, t), this.neutrino = new Neutrino.Context(), this.loadData = { __neutrinoContext__: this }, this._noiseInitialized = !1;
|
|
1243
|
+
const r = ((n = e.context) == null ? void 0 : n.webGLVersion) ?? (typeof this.gl.createVertexArray == "function" ? 2 : 1);
|
|
1244
|
+
this._isWebGL1 = r === 1, this._isWebGL1 && this._probeWebGL1Capabilities(), this._initShader(), this._isWebGL1 && this._smokeTestVertexTextureFetch();
|
|
1245
|
+
}
|
|
1246
|
+
initializeNoise(e, t, r) {
|
|
1247
|
+
if (this._noiseInitialized) {
|
|
1248
|
+
t && t();
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
this.neutrino.initializeNoise(
|
|
1252
|
+
e,
|
|
1253
|
+
() => {
|
|
1254
|
+
this._noiseInitialized = !0, t && t();
|
|
1255
|
+
},
|
|
1256
|
+
r
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
generateNoise() {
|
|
1260
|
+
if (this._noiseInitialized) return;
|
|
1261
|
+
const e = new this.neutrino.NoiseGenerator();
|
|
1262
|
+
for (; !e.step(); )
|
|
1263
|
+
;
|
|
1264
|
+
this._noiseInitialized = !0;
|
|
1265
|
+
}
|
|
1266
|
+
get shaderProgram() {
|
|
1267
|
+
return this._shaderProgram;
|
|
1268
|
+
}
|
|
1269
|
+
get isWebGL1() {
|
|
1270
|
+
return this._isWebGL1;
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Textures usable in one batch. 8 on WebGL2; on WebGL1 reduced when the
|
|
1274
|
+
* combined texture-unit budget is tight (the vertex stage takes 2 units).
|
|
1275
|
+
* A lower cap only produces more batches, never wrong output.
|
|
1276
|
+
*/
|
|
1277
|
+
get maxBatchTextures() {
|
|
1278
|
+
return this._maxBatchTextures;
|
|
1279
|
+
}
|
|
1280
|
+
get uDataTexture() {
|
|
1281
|
+
return this._uDataTexture;
|
|
1282
|
+
}
|
|
1283
|
+
get uDataTextureWidth() {
|
|
1284
|
+
return this._uDataTextureWidth;
|
|
1285
|
+
}
|
|
1286
|
+
get uMetaTexture() {
|
|
1287
|
+
return this._uMetaTexture;
|
|
1288
|
+
}
|
|
1289
|
+
get uDataTexSize() {
|
|
1290
|
+
return this._uDataTexSize;
|
|
1291
|
+
}
|
|
1292
|
+
get uCameraRight() {
|
|
1293
|
+
return this._uCameraRight;
|
|
1294
|
+
}
|
|
1295
|
+
get uCameraUp() {
|
|
1296
|
+
return this._uCameraUp;
|
|
1297
|
+
}
|
|
1298
|
+
get uCameraDir() {
|
|
1299
|
+
return this._uCameraDir;
|
|
1300
|
+
}
|
|
1301
|
+
get uViewProjMatrix() {
|
|
1302
|
+
return this._uViewProjMatrix;
|
|
1303
|
+
}
|
|
1304
|
+
get uModelMatrix() {
|
|
1305
|
+
return this._uModelMatrix;
|
|
1306
|
+
}
|
|
1307
|
+
get uViewportAspect() {
|
|
1308
|
+
return this._uViewportAspect;
|
|
1309
|
+
}
|
|
1310
|
+
get uWorldAlpha() {
|
|
1311
|
+
return this._uWorldAlpha;
|
|
1312
|
+
}
|
|
1313
|
+
get uTextures() {
|
|
1314
|
+
return this._uTextures;
|
|
1315
|
+
}
|
|
1316
|
+
get uTexRemaps() {
|
|
1317
|
+
return this._uTexRemaps;
|
|
1318
|
+
}
|
|
1319
|
+
ensureIndexBuffer(e) {
|
|
1320
|
+
if (e <= this._indexBufferCapacity) return;
|
|
1321
|
+
const t = this.gl;
|
|
1322
|
+
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);
|
|
1323
|
+
const r = e > 16383;
|
|
1324
|
+
if (r && this._isWebGL1 && !this._hasElementIndexUint)
|
|
1325
|
+
throw new Error(
|
|
1326
|
+
`NeutrinoParticles (js-v1.1): the effect needs 32-bit indices (${e} particles) but this WebGL1 context lacks OES_element_index_uint.`
|
|
1327
|
+
);
|
|
1328
|
+
const n = r ? new Uint32Array(e * 6) : new Uint16Array(e * 6);
|
|
1329
|
+
for (let o = 0; o < e; o++) {
|
|
1330
|
+
const s = o * 4, a = o * 6;
|
|
1331
|
+
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;
|
|
1332
|
+
}
|
|
1333
|
+
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) {
|
|
1334
|
+
const o = new Float32Array(e * 4);
|
|
1335
|
+
for (let s = 0; s < o.length; s++) o[s] = s;
|
|
1336
|
+
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);
|
|
1337
|
+
} else
|
|
1338
|
+
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);
|
|
1339
|
+
this._indexBufferCapacity = e;
|
|
1340
|
+
}
|
|
1341
|
+
get vao() {
|
|
1342
|
+
return this._vao;
|
|
1343
|
+
}
|
|
1344
|
+
get indexBuffer() {
|
|
1345
|
+
return this._indexBuffer;
|
|
1346
|
+
}
|
|
1347
|
+
get aIdBuffer() {
|
|
1348
|
+
return this._aIdBuffer;
|
|
1349
|
+
}
|
|
1350
|
+
get aIdLocation() {
|
|
1351
|
+
return this._aIdLocation;
|
|
1352
|
+
}
|
|
1353
|
+
get indexType() {
|
|
1354
|
+
return this._indexBufferCapacity > 16383 ? this.gl.UNSIGNED_INT : this.gl.UNSIGNED_SHORT;
|
|
1355
|
+
}
|
|
1356
|
+
/** Unbind any VAO (incl. OES on WebGL1) before raw attribute setup. */
|
|
1357
|
+
unbindVao() {
|
|
1358
|
+
this._isWebGL1 ? this._vaoExt && this._vaoExt.bindVertexArrayOES(null) : this.gl.bindVertexArray(null);
|
|
1359
|
+
}
|
|
1360
|
+
destroy() {
|
|
1361
|
+
const e = this.gl;
|
|
1362
|
+
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);
|
|
1363
|
+
}
|
|
1364
|
+
// ── WebGL1 capability probe (TDD §4.2) ─────────────────────────────────
|
|
1365
|
+
_probeWebGL1Capabilities() {
|
|
1366
|
+
const e = this.gl;
|
|
1367
|
+
if (!e.getExtension("OES_texture_float"))
|
|
1368
|
+
throw new Error(
|
|
1369
|
+
"NeutrinoParticles (js-v1.1): WebGL1 context lacks OES_texture_float; WebGL2 or WebGL1 with vertex float textures is required."
|
|
1370
|
+
);
|
|
1371
|
+
const t = e.getParameter(e.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
|
|
1372
|
+
if (t < 2)
|
|
1373
|
+
throw new Error(
|
|
1374
|
+
"NeutrinoParticles (js-v1.1): WebGL1 context has " + t + " vertex texture units (2 required); WebGL2 or WebGL1 with vertex float textures is required."
|
|
1375
|
+
);
|
|
1376
|
+
const r = e.getShaderPrecisionFormat(e.FRAGMENT_SHADER, e.HIGH_FLOAT);
|
|
1377
|
+
if (!r || r.precision === 0)
|
|
1378
|
+
throw new Error(
|
|
1379
|
+
"NeutrinoParticles (js-v1.1): WebGL1 context lacks highp float in fragment shaders; WebGL2 or WebGL1 with fragment highp is required."
|
|
1380
|
+
);
|
|
1381
|
+
this._hasElementIndexUint = !!e.getExtension("OES_element_index_uint"), this._vaoExt = e.getExtension("OES_vertex_array_object");
|
|
1382
|
+
const n = e.getParameter(e.MAX_COMBINED_TEXTURE_IMAGE_UNITS), o = e.getParameter(e.MAX_TEXTURE_IMAGE_UNITS);
|
|
1383
|
+
this._maxBatchTextures = Math.max(
|
|
1384
|
+
1,
|
|
1385
|
+
Math.min(R.MAX_BATCH_TEXTURES, n - 2, o)
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1389
|
+
* Compile/link alone does not prove vertex texture fetch works on a
|
|
1390
|
+
* driver — some fail only at draw time. One-time 1×1 smoke draw: a point
|
|
1391
|
+
* whose color is produced by sampling a float and a byte texture in the
|
|
1392
|
+
* vertex shader, rendered into a 1×1 RGBA8 FBO and read back.
|
|
1393
|
+
*/
|
|
1394
|
+
_smokeTestVertexTextureFetch() {
|
|
1395
|
+
const e = this.gl, t = `precision highp float;
|
|
1396
|
+
attribute float aId;
|
|
1397
|
+
uniform sampler2D uF;
|
|
1398
|
+
uniform sampler2D uB;
|
|
1399
|
+
varying vec4 vC;
|
|
1400
|
+
void main() {
|
|
1401
|
+
vec4 f = texture2DLod(uF, vec2(0.5, 0.5), 0.0);
|
|
1402
|
+
vec4 b = texture2DLod(uB, vec2(0.5, 0.5), 0.0);
|
|
1403
|
+
vC = vec4(f.r * b.r, f.g * b.g, f.b * b.b, 1.0);
|
|
1404
|
+
gl_Position = vec4(aId, 0.0, 0.0, 1.0);
|
|
1405
|
+
gl_PointSize = 1.0;
|
|
1406
|
+
}
|
|
1407
|
+
`, r = `precision mediump float;
|
|
1408
|
+
varying vec4 vC;
|
|
1409
|
+
void main() { gl_FragColor = vC; }
|
|
1410
|
+
`, 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);
|
|
1411
|
+
e.activeTexture(e.TEXTURE0);
|
|
1412
|
+
const U = e.getParameter(e.TEXTURE_BINDING_2D);
|
|
1413
|
+
e.activeTexture(e.TEXTURE1);
|
|
1414
|
+
const S = e.getParameter(e.TEXTURE_BINDING_2D), C = e.isEnabled(e.BLEND), I = e.isEnabled(e.DEPTH_TEST), D = e.isEnabled(e.SCISSOR_TEST);
|
|
1415
|
+
let v = null, b = null, w = null, m = null, T = null, _ = null, x = -1, B = !1, P = null, y = 4, d = 0, f = 0, g = 0, E = !1, u = !1;
|
|
1416
|
+
const h = new Uint8Array(4);
|
|
1417
|
+
try {
|
|
1418
|
+
v = e.createProgram(), e.attachShader(v, this._compileShader(e.VERTEX_SHADER, t)), e.attachShader(v, this._compileShader(e.FRAGMENT_SHADER, r)), e.linkProgram(v);
|
|
1419
|
+
const p = (M, k) => {
|
|
1420
|
+
const K = e.createTexture();
|
|
1421
|
+
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;
|
|
1422
|
+
};
|
|
1423
|
+
e.activeTexture(e.TEXTURE0), b = p(e.FLOAT, new Float32Array([1, 0.5, 0.25, 1])), w = p(e.UNSIGNED_BYTE, new Uint8Array([51, 102, 153, 255])), m = e.createTexture(), e.bindTexture(e.TEXTURE_2D, m), e.texImage2D(e.TEXTURE_2D, 0, e.RGBA, 1, 1, 0, e.RGBA, e.UNSIGNED_BYTE, null), T = e.createFramebuffer(), e.bindFramebuffer(e.FRAMEBUFFER, T), e.framebufferTexture2D(e.FRAMEBUFFER, e.COLOR_ATTACHMENT0, e.TEXTURE_2D, m, 0), _ = e.createBuffer(), e.bindBuffer(e.ARRAY_BUFFER, _), e.bufferData(e.ARRAY_BUFFER, new Float32Array([0]), e.STATIC_DRAW), u = e.getProgramParameter(v, e.LINK_STATUS), u && (e.useProgram(v), e.activeTexture(e.TEXTURE0), e.bindTexture(e.TEXTURE_2D, b), e.activeTexture(e.TEXTURE1), e.bindTexture(e.TEXTURE_2D, w), e.uniform1i(e.getUniformLocation(v, "uF"), 0), e.uniform1i(e.getUniformLocation(v, "uB"), 1), x = e.getAttribLocation(v, "aId"), B = e.getVertexAttrib(x, e.VERTEX_ATTRIB_ARRAY_ENABLED), P = e.getVertexAttrib(x, e.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING), y = e.getVertexAttrib(x, e.VERTEX_ATTRIB_ARRAY_SIZE), d = e.getVertexAttrib(x, e.VERTEX_ATTRIB_ARRAY_TYPE), E = e.getVertexAttrib(x, e.VERTEX_ATTRIB_ARRAY_NORMALIZED), f = e.getVertexAttrib(x, e.VERTEX_ATTRIB_ARRAY_STRIDE), g = e.getVertexAttribOffset(x, e.VERTEX_ATTRIB_ARRAY_POINTER), e.enableVertexAttribArray(x), e.vertexAttribPointer(x, 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);
|
|
1424
|
+
} finally {
|
|
1425
|
+
x >= 0 && (P && (e.bindBuffer(e.ARRAY_BUFFER, P), e.vertexAttribPointer(
|
|
1426
|
+
x,
|
|
1427
|
+
y,
|
|
1428
|
+
d,
|
|
1429
|
+
E,
|
|
1430
|
+
f,
|
|
1431
|
+
g
|
|
1432
|
+
)), B ? e.enableVertexAttribArray(x) : e.disableVertexAttribArray(x)), T && e.deleteFramebuffer(T), m && e.deleteTexture(m), b && e.deleteTexture(b), w && e.deleteTexture(w), _ && e.deleteBuffer(_), v && e.deleteProgram(v), e.bindFramebuffer(e.FRAMEBUFFER, a), e.bindBuffer(e.ARRAY_BUFFER, s), e.useProgram(o), e.activeTexture(e.TEXTURE0), e.bindTexture(e.TEXTURE_2D, U), e.activeTexture(e.TEXTURE1), e.bindTexture(e.TEXTURE_2D, S), e.activeTexture(c), C && e.enable(e.BLEND), I && e.enable(e.DEPTH_TEST), D && e.enable(e.SCISSOR_TEST), e.viewport(n[0], n[1], n[2], n[3]);
|
|
1433
|
+
}
|
|
1434
|
+
if (!u)
|
|
1435
|
+
throw new Error(
|
|
1436
|
+
`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.`
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
_initShader() {
|
|
1440
|
+
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);
|
|
1441
|
+
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))
|
|
1442
|
+
throw new Error("Shader link error: " + e.getProgramInfoLog(this._shaderProgram));
|
|
1443
|
+
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 = [];
|
|
1444
|
+
for (let s = 0; s < R.MAX_BATCH_TEXTURES; s++)
|
|
1445
|
+
this._uTextures.push(e.getUniformLocation(this._shaderProgram, `uTextures[${s}]`)), this._uTexRemaps.push(e.getUniformLocation(this._shaderProgram, `uTexRemaps[${s}]`));
|
|
1446
|
+
}
|
|
1447
|
+
_compileShader(e, t) {
|
|
1448
|
+
const r = this.gl, n = r.createShader(e);
|
|
1449
|
+
if (r.shaderSource(n, t), r.compileShader(n), !r.getShaderParameter(n, r.COMPILE_STATUS)) {
|
|
1450
|
+
const o = r.getShaderInfoLog(n);
|
|
1451
|
+
throw r.deleteShader(n), new Error("Shader compile error: " + o);
|
|
1452
|
+
}
|
|
1453
|
+
return n;
|
|
1454
|
+
}
|
|
1455
|
+
};
|
|
1456
|
+
i(R, "MAX_BATCH_TEXTURES", 8);
|
|
1457
|
+
let Context = R;
|
|
1458
|
+
class ApplicationPlugin {
|
|
1459
|
+
static get extension() {
|
|
1460
|
+
return {
|
|
1461
|
+
name: "neutrino",
|
|
1462
|
+
type: ExtensionType.Application
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
static init(e) {
|
|
1466
|
+
this.neutrino = new Context(this.renderer, e.neutrino);
|
|
1467
|
+
}
|
|
1468
|
+
static destroy() {
|
|
1469
|
+
this.neutrino && (this.neutrino.destroy(), this.neutrino = void 0);
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
class EffectModel extends EventEmitter {
|
|
1473
|
+
constructor(context, neutrinoModel, texturesLoader) {
|
|
1474
|
+
super();
|
|
1475
|
+
i(this, "ctx");
|
|
1476
|
+
i(this, "effectModel");
|
|
1477
|
+
i(this, "texturesRemap", []);
|
|
1478
|
+
i(this, "textures", []);
|
|
1479
|
+
i(this, "_numTexturesToLoadLeft");
|
|
1480
|
+
if (this.ctx = context, typeof neutrinoModel == "string") {
|
|
1481
|
+
const evalScript = `(function(ctx) {
|
|
1482
|
+
` + neutrinoModel + `
|
|
1483
|
+
return new NeutrinoEffect(ctx);
|
|
1484
|
+
})(context.neutrino);`;
|
|
1485
|
+
this.effectModel = eval(evalScript);
|
|
1486
|
+
} else
|
|
1487
|
+
this.effectModel = neutrinoModel;
|
|
1488
|
+
const numTextures = this.effectModel.textures.length;
|
|
1489
|
+
this._numTexturesToLoadLeft = numTextures;
|
|
1490
|
+
for (let e = 0; e < numTextures; ++e) {
|
|
1491
|
+
const t = this.effectModel.textures[e];
|
|
1492
|
+
let r = null;
|
|
1493
|
+
if (this.ctx.options.trimmedExtensionsLookupFirst) {
|
|
1494
|
+
const n = t.replace(/\.[^/.]+$/, "");
|
|
1495
|
+
Cache.has(n) && (r = Cache.get(n));
|
|
1496
|
+
}
|
|
1497
|
+
!r && Cache.has(t) && (r = Cache.get(t)), r ? this._onTextureLoaded(e, r, !0) : texturesLoader.load(this.ctx.options.texturesBasePath + t).then(
|
|
1498
|
+
/* @__PURE__ */ ((n, o) => (s) => {
|
|
1499
|
+
n._onTextureLoaded(o, s, !1);
|
|
1500
|
+
})(this, e)
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
ready() {
|
|
1505
|
+
return this._numTexturesToLoadLeft === 0;
|
|
1506
|
+
}
|
|
1507
|
+
_onTextureLoaded(e, t, r) {
|
|
1508
|
+
this.textures[e] = t, this._numTexturesToLoadLeft--, this._numTexturesToLoadLeft === 0 && (this._initTexturesRemapIfNeeded(), r ? setTimeout(() => {
|
|
1509
|
+
this.emit("ready", this);
|
|
1510
|
+
}, 0) : this.emit("ready", this));
|
|
1511
|
+
}
|
|
1512
|
+
_initTexturesRemapIfNeeded() {
|
|
1513
|
+
let e = !1;
|
|
1514
|
+
for (let t = 0; t < this.textures.length; ++t) {
|
|
1515
|
+
const r = this.textures[t];
|
|
1516
|
+
if (r.frame.x != 0 || r.frame.y != 0 || r.frame.width != r.source.pixelWidth || r.frame.height != r.source.pixelHeight) {
|
|
1517
|
+
e = !0;
|
|
1518
|
+
break;
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
if (e)
|
|
1522
|
+
for (let t = 0; t < this.textures.length; ++t) {
|
|
1523
|
+
const r = this.textures[t];
|
|
1524
|
+
this.texturesRemap[t] = {
|
|
1525
|
+
x: r.frame.x / r.source.pixelWidth,
|
|
1526
|
+
y: 1 - (r.frame.y + r.frame.height) / r.source.pixelHeight,
|
|
1527
|
+
width: r.frame.width / r.source.pixelWidth,
|
|
1528
|
+
height: r.frame.height / r.source.pixelHeight
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
class AbstractTexturesLoader {
|
|
1534
|
+
constructor() {
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
class AssetsTexturesLoader extends AbstractTexturesLoader {
|
|
1538
|
+
constructor(t) {
|
|
1539
|
+
super();
|
|
1540
|
+
i(this, "loader");
|
|
1541
|
+
this.loader = t;
|
|
1542
|
+
}
|
|
1543
|
+
async load(t) {
|
|
1544
|
+
return await this.loader.load(t);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
const A = class A extends AssetsTexturesLoader {
|
|
1548
|
+
constructor() {
|
|
1549
|
+
super(Assets.loader);
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
i(A, "instance", new A());
|
|
1553
|
+
let DefaultAssetsTexturesLoader = A;
|
|
1554
|
+
const effectModelLoader = {
|
|
1555
|
+
extension: {
|
|
1556
|
+
type: ExtensionType.LoadParser,
|
|
1557
|
+
priority: LoaderParserPriority.Normal
|
|
1558
|
+
},
|
|
1559
|
+
test(l, e, t) {
|
|
1560
|
+
return e.data && e.data.__neutrinoContext__ && e.data.__neutrinoContext__ instanceof Context;
|
|
1561
|
+
},
|
|
1562
|
+
async load(l, e, t) {
|
|
1563
|
+
return await (await DOMAdapter.get().fetch(l)).text();
|
|
1564
|
+
},
|
|
1565
|
+
async testParse(l, e) {
|
|
1566
|
+
return e.data && e.data.__neutrinoContext__ && e.data.__neutrinoContext__ instanceof Context;
|
|
1567
|
+
},
|
|
1568
|
+
async parse(l, e, t) {
|
|
1569
|
+
return new Promise((r) => {
|
|
1570
|
+
const n = e.data.__neutrinoContext__, o = new EffectModel(n, l, new AssetsTexturesLoader(t));
|
|
1571
|
+
o.ready() ? r(o) : o.once("ready", () => {
|
|
1572
|
+
r(o);
|
|
1573
|
+
});
|
|
1574
|
+
});
|
|
1575
|
+
},
|
|
1576
|
+
unload(l) {
|
|
1577
|
+
}
|
|
1578
|
+
}, 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;
|
|
1579
|
+
class DrawBatch {
|
|
1580
|
+
constructor() {
|
|
1581
|
+
i(this, "blendMode", 0);
|
|
1582
|
+
i(this, "startParticle", 0);
|
|
1583
|
+
i(this, "numParticles", 0);
|
|
1584
|
+
i(this, "numTextures", 0);
|
|
1585
|
+
i(this, "glTextures", new Array(Context.MAX_BATCH_TEXTURES).fill(null));
|
|
1586
|
+
i(this, "remaps", new Array(Context.MAX_BATCH_TEXTURES).fill(null));
|
|
1587
|
+
}
|
|
1588
|
+
reset() {
|
|
1589
|
+
this.blendMode = 0, this.startParticle = 0, this.numParticles = 0, this.numTextures = 0;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
class NeutrinoRenderer {
|
|
1593
|
+
constructor() {
|
|
1594
|
+
i(this, "_orthoMatrix", new Float32Array(16));
|
|
1595
|
+
i(this, "_perspMatrix", new Float32Array(16));
|
|
1596
|
+
i(this, "_vpMatrix", new Float32Array(16));
|
|
1597
|
+
i(this, "_modelMatrix", new Float32Array(16));
|
|
1598
|
+
i(this, "_projFrame", new Rectangle());
|
|
1599
|
+
i(this, "_texSlotMap", /* @__PURE__ */ new Map());
|
|
1600
|
+
i(this, "_batchPool");
|
|
1601
|
+
i(this, "_batchCount", 0);
|
|
1602
|
+
this._batchPool = [];
|
|
1603
|
+
for (let e = 0; e < MAX_BATCHES; e++)
|
|
1604
|
+
this._batchPool.push(new DrawBatch());
|
|
1605
|
+
}
|
|
1606
|
+
renderEffect(e, t, r, n, o, s) {
|
|
1607
|
+
if (s === 0) return;
|
|
1608
|
+
const a = t.gl, c = t.isWebGL1, U = e.texture, S = t.maxBatchTextures, C = r.effect.model.renderStyles, I = r.effect.model.materials, D = r.effectModel.textures, v = r.effectModel.texturesRemap, b = r.effect.particleDataView, w = c ? n.meta : null;
|
|
1609
|
+
w && w.build(r.effect.particleDataUint32, s), this._batchCount = 0;
|
|
1610
|
+
const m = this._texSlotMap;
|
|
1611
|
+
for (; this._batchPool.length < o.length; )
|
|
1612
|
+
this._batchPool.push(new DrawBatch());
|
|
1613
|
+
let T = 0;
|
|
1614
|
+
for (; T < o.length; ) {
|
|
1615
|
+
const d = this._batchPool[this._batchCount];
|
|
1616
|
+
d.reset(), d.blendMode = this._resolveBlend(o[T].blendMode, I), d.startParticle = o[T].startParticleIndex, m.clear();
|
|
1617
|
+
let f = T;
|
|
1618
|
+
for (; f < o.length && this._resolveBlend(o[f].blendMode, I) === d.blendMode; ) {
|
|
1619
|
+
const g = C[o[f].renderStyleIndex].textureIndices[0];
|
|
1620
|
+
if (!m.has(g)) {
|
|
1621
|
+
if (m.size >= S) break;
|
|
1622
|
+
const h = m.size;
|
|
1623
|
+
m.set(g, h);
|
|
1624
|
+
const p = D[g];
|
|
1625
|
+
d.glTextures[h] = p ? U.getGlSource(p.source).texture : null, d.remaps[h] = v[g] || null, d.numTextures = h + 1;
|
|
1626
|
+
}
|
|
1627
|
+
const E = m.get(g), u = o[f];
|
|
1628
|
+
if (w)
|
|
1629
|
+
w.patchSlot(u.startParticleIndex, u.numParticles, E);
|
|
1630
|
+
else
|
|
1631
|
+
for (let h = 0; h < u.numParticles; h++) {
|
|
1632
|
+
const p = (u.startParticleIndex + h) * BYTES_PER_PARTICLE, M = b.getUint32(p, !0);
|
|
1633
|
+
b.setUint32(p, M & 4294967040 | E, !0);
|
|
1634
|
+
}
|
|
1635
|
+
d.numParticles += u.numParticles, f++;
|
|
1636
|
+
}
|
|
1637
|
+
this._batchCount++, T = f;
|
|
1638
|
+
}
|
|
1639
|
+
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) {
|
|
1640
|
+
const d = n;
|
|
1641
|
+
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);
|
|
1642
|
+
} else
|
|
1643
|
+
n.uploadAndBind(DATA_TEXTURE_UNIT), a.uniform1i(t.uDataTexture, DATA_TEXTURE_UNIT), a.uniform1i(t.uDataTextureWidth, n.textureWidth);
|
|
1644
|
+
a.uniform3f(t.uCameraRight, 1, 0, 0), a.uniform3f(t.uCameraUp, 0, -1, 0), a.uniform3f(t.uCameraDir, 0, 0, -1);
|
|
1645
|
+
const _ = e.renderTarget.viewport, x = e.renderTarget.renderTarget.resolution;
|
|
1646
|
+
if (this._fillProjMatrix(this._orthoMatrix, e), r.projection) {
|
|
1647
|
+
const d = this._projFrame;
|
|
1648
|
+
d.x = 0, d.y = 0, d.width = _.width / x, d.height = _.height / x, r.projection.setScreenFrame(d), r.projection.writeMatrix(this._perspMatrix), this._mulMatrix(this._vpMatrix, this._orthoMatrix, this._perspMatrix), a.uniformMatrix4fv(t.uViewProjMatrix, !1, this._vpMatrix);
|
|
1649
|
+
} else
|
|
1650
|
+
a.uniformMatrix4fv(t.uViewProjMatrix, !1, this._orthoMatrix);
|
|
1651
|
+
const B = _.height > 0 ? _.width / _.height : 1;
|
|
1652
|
+
t.uViewportAspect && a.uniform1f(t.uViewportAspect, B), 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);
|
|
1653
|
+
const P = c ? PARTICLE_TEXTURE_START_UNIT_GL1 : PARTICLE_TEXTURE_START_UNIT;
|
|
1654
|
+
a.enable(a.BLEND);
|
|
1655
|
+
for (let d = 0; d < this._batchCount; d++) {
|
|
1656
|
+
const f = this._batchPool[d];
|
|
1657
|
+
this._applyBlendMode(a, f.blendMode);
|
|
1658
|
+
for (let u = 0; u < f.numTextures; u++) {
|
|
1659
|
+
const h = P + u;
|
|
1660
|
+
a.activeTexture(a.TEXTURE0 + h), a.bindTexture(a.TEXTURE_2D, f.glTextures[u]), a.uniform1i(t.uTextures[u], h);
|
|
1661
|
+
const p = f.remaps[u];
|
|
1662
|
+
p ? a.uniform4f(t.uTexRemaps[u], p.x, p.y, p.width, p.height) : a.uniform4f(t.uTexRemaps[u], 0, 0, 1, 1);
|
|
1663
|
+
}
|
|
1664
|
+
const g = f.startParticle * 6, E = f.numParticles * 6;
|
|
1665
|
+
a.drawElements(
|
|
1666
|
+
a.TRIANGLES,
|
|
1667
|
+
E,
|
|
1668
|
+
t.indexType,
|
|
1669
|
+
g * (t.indexType === a.UNSIGNED_INT ? 4 : 2)
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
c ? (a.disableVertexAttribArray(t.aIdLocation), a.bindBuffer(a.ARRAY_BUFFER, null), a.bindBuffer(a.ELEMENT_ARRAY_BUFFER, null)) : a.bindVertexArray(null), a.useProgram(null);
|
|
1673
|
+
const y = U._boundTextures;
|
|
1674
|
+
if (y)
|
|
1675
|
+
for (let d = 0; d < P + Context.MAX_BATCH_TEXTURES; d++)
|
|
1676
|
+
y[d] = null;
|
|
1677
|
+
e.shader.resetState(), e.geometry.resetState(), e.state.resetState();
|
|
1678
|
+
}
|
|
1679
|
+
// Resolve a renderStyle's compacted material INDEX to the real blend enum (0=Normal/
|
|
1680
|
+
// 1=Add/2=Multiply) via materials[]. The runtime sets instruction.blendMode =
|
|
1681
|
+
// renderStyles[rsi].materialIndex (system.js _renderStyleBlendModes), an INDEX into
|
|
1682
|
+
// materials[], NOT the blend itself — without this deref a single-material effect
|
|
1683
|
+
// (materialIndex 0) would always render as materials[0]'s slot 0 (Normal). Falls back to
|
|
1684
|
+
// the raw value if materials is missing/out of range (mirror of cocos _resolveBlendMode).
|
|
1685
|
+
_resolveBlend(e, t) {
|
|
1686
|
+
return t && e >= 0 && e < t.length ? t[e] : e;
|
|
1687
|
+
}
|
|
1688
|
+
_applyBlendMode(e, t) {
|
|
1689
|
+
switch (t) {
|
|
1690
|
+
default:
|
|
1691
|
+
case 0:
|
|
1692
|
+
e.blendFuncSeparate(e.ONE, e.ONE_MINUS_SRC_ALPHA, e.ONE, e.ONE_MINUS_SRC_ALPHA);
|
|
1693
|
+
break;
|
|
1694
|
+
case 1:
|
|
1695
|
+
e.blendFunc(e.ONE, e.ONE);
|
|
1696
|
+
break;
|
|
1697
|
+
case 2:
|
|
1698
|
+
e.blendFunc(e.DST_COLOR, e.ONE_MINUS_SRC_ALPHA);
|
|
1699
|
+
break;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
_fillProjMatrix(e, t) {
|
|
1703
|
+
const r = t.renderTarget.projectionMatrix;
|
|
1704
|
+
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;
|
|
1705
|
+
}
|
|
1706
|
+
// Column-major 4x4 multiply: out = a * b. Preallocated outputs only.
|
|
1707
|
+
_mulMatrix(e, t, r) {
|
|
1708
|
+
for (let n = 0; n < 16; n += 4) {
|
|
1709
|
+
const o = r[n], s = r[n + 1], a = r[n + 2], c = r[n + 3];
|
|
1710
|
+
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;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
_fillModelMatrix(e, t) {
|
|
1714
|
+
const r = t.worldRenderMatrix;
|
|
1715
|
+
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;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
class EffectPipe {
|
|
1719
|
+
constructor(e) {
|
|
1720
|
+
i(this, "renderer");
|
|
1721
|
+
i(this, "_neutrinoRenderer", new NeutrinoRenderer());
|
|
1722
|
+
this.renderer = e;
|
|
1723
|
+
}
|
|
1724
|
+
validateRenderable(e) {
|
|
1725
|
+
return !0;
|
|
1726
|
+
}
|
|
1727
|
+
addRenderable(e, t) {
|
|
1728
|
+
t.add(e);
|
|
1729
|
+
}
|
|
1730
|
+
updateRenderable(e) {
|
|
1731
|
+
}
|
|
1732
|
+
destroyRenderable(e) {
|
|
1733
|
+
}
|
|
1734
|
+
execute(e) {
|
|
1735
|
+
if (!e.ready() || e._isInvisible()) return;
|
|
1736
|
+
const t = e.ctx, r = e._dtm();
|
|
1737
|
+
if (!t || !r) return;
|
|
1738
|
+
e._prepareRender(), e.effect.construct([0, 0, -1]);
|
|
1739
|
+
const n = e.effect.renderInstructions, o = n._totalParticles;
|
|
1740
|
+
o !== 0 && (this._neutrinoRenderer.renderEffect(
|
|
1741
|
+
this.renderer,
|
|
1742
|
+
t,
|
|
1743
|
+
e,
|
|
1744
|
+
r,
|
|
1745
|
+
n,
|
|
1746
|
+
o
|
|
1747
|
+
), r.advance());
|
|
1748
|
+
}
|
|
1749
|
+
destroy() {
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
i(EffectPipe, "extension", {
|
|
1753
|
+
type: [ExtensionType.WebGLPipes],
|
|
1754
|
+
name: "neutrino"
|
|
1755
|
+
});
|
|
1756
|
+
const RING_BUFFER_SIZE$1 = 3;
|
|
1757
|
+
class DataTextureManager {
|
|
1758
|
+
/**
|
|
1759
|
+
* @param gl - WebGL2 context (from the host renderer)
|
|
1760
|
+
* @param data - Uint32Array from the runtime. A Float32Array view over the
|
|
1761
|
+
* same buffer is uploaded as RGBA32F — bit patterns (incl. NaN/Inf) pass
|
|
1762
|
+
* through unchanged.
|
|
1763
|
+
* @param textureWidth - from runtime (system.dataTextureWidth)
|
|
1764
|
+
* @param textureHeight - from runtime (system.dataTextureHeight)
|
|
1765
|
+
*/
|
|
1766
|
+
constructor(e, t, r, n) {
|
|
1767
|
+
i(this, "textureWidth");
|
|
1768
|
+
i(this, "textureHeight");
|
|
1769
|
+
i(this, "_gl");
|
|
1770
|
+
i(this, "_floatView");
|
|
1771
|
+
i(this, "_textures", []);
|
|
1772
|
+
i(this, "_currentIndex", 0);
|
|
1773
|
+
const o = e.getParameter(e.MAX_TEXTURE_SIZE);
|
|
1774
|
+
if (r > o || n > o)
|
|
1775
|
+
throw new Error(
|
|
1776
|
+
`NeutrinoParticles: data texture ${r}x${n} exceeds GL MAX_TEXTURE_SIZE (${o}). Reduce effect particle capacity.`
|
|
1777
|
+
);
|
|
1778
|
+
this._gl = e, this.textureWidth = r, this.textureHeight = n, this._floatView = new Float32Array(t.buffer, t.byteOffset, t.length);
|
|
1779
|
+
for (let s = 0; s < RING_BUFFER_SIZE$1; s++) {
|
|
1780
|
+
const a = e.createTexture();
|
|
1781
|
+
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(
|
|
1782
|
+
e.TEXTURE_2D,
|
|
1783
|
+
0,
|
|
1784
|
+
e.RGBA32F,
|
|
1785
|
+
r,
|
|
1786
|
+
n,
|
|
1787
|
+
0,
|
|
1788
|
+
e.RGBA,
|
|
1789
|
+
e.FLOAT,
|
|
1790
|
+
null
|
|
1791
|
+
), this._textures.push(a);
|
|
1792
|
+
}
|
|
1793
|
+
e.bindTexture(e.TEXTURE_2D, null);
|
|
1794
|
+
}
|
|
1795
|
+
get currentTexture() {
|
|
1796
|
+
return this._textures[this._currentIndex];
|
|
1797
|
+
}
|
|
1798
|
+
/** Bind the current texture to a unit and upload the latest particle data. */
|
|
1799
|
+
uploadAndBind(e) {
|
|
1800
|
+
const t = this._gl;
|
|
1801
|
+
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(
|
|
1802
|
+
t.TEXTURE_2D,
|
|
1803
|
+
0,
|
|
1804
|
+
0,
|
|
1805
|
+
0,
|
|
1806
|
+
this.textureWidth,
|
|
1807
|
+
this.textureHeight,
|
|
1808
|
+
t.RGBA,
|
|
1809
|
+
t.FLOAT,
|
|
1810
|
+
this._floatView
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
1813
|
+
advance() {
|
|
1814
|
+
this._currentIndex = (this._currentIndex + 1) % RING_BUFFER_SIZE$1;
|
|
1815
|
+
}
|
|
1816
|
+
destroy() {
|
|
1817
|
+
const e = this._gl;
|
|
1818
|
+
for (const t of this._textures)
|
|
1819
|
+
t && e.deleteTexture(t);
|
|
1820
|
+
this._textures = [];
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
class MetaTextureManager {
|
|
1824
|
+
constructor(e, t) {
|
|
1825
|
+
i(this, "bytes");
|
|
1826
|
+
i(this, "_u32");
|
|
1827
|
+
this.bytes = new Uint8Array(e * t * 4), this._u32 = new Uint32Array(this.bytes.buffer);
|
|
1828
|
+
}
|
|
1829
|
+
/** Repack the packed-u32 record fields for the first `numParticles` records. */
|
|
1830
|
+
build(e, t) {
|
|
1831
|
+
const r = this._u32;
|
|
1832
|
+
for (let n = 0; n < t; n++) {
|
|
1833
|
+
const o = n * 16, s = n * 4;
|
|
1834
|
+
r[s] = e[o], r[s + 1] = e[o + 12], r[s + 2] = e[o + 13], r[s + 3] = e[o + 14];
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
/** Write the batch texture slot into byte0 of M0 (flags) per particle. */
|
|
1838
|
+
patchSlot(e, t, r) {
|
|
1839
|
+
const n = this.bytes;
|
|
1840
|
+
let o = e * 16;
|
|
1841
|
+
for (let s = 0; s < t; s++)
|
|
1842
|
+
n[o] = r, o += 16;
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
const RING_BUFFER_SIZE = 3;
|
|
1846
|
+
class DataTextureManagerGL1 {
|
|
1847
|
+
constructor(e, t, r, n) {
|
|
1848
|
+
i(this, "textureWidth");
|
|
1849
|
+
i(this, "textureHeight");
|
|
1850
|
+
i(this, "meta");
|
|
1851
|
+
i(this, "_gl");
|
|
1852
|
+
i(this, "_floatView");
|
|
1853
|
+
i(this, "_dataTextures", []);
|
|
1854
|
+
i(this, "_metaTextures", []);
|
|
1855
|
+
i(this, "_currentIndex", 0);
|
|
1856
|
+
const o = e.getParameter(e.MAX_TEXTURE_SIZE);
|
|
1857
|
+
if (r > o || n > o)
|
|
1858
|
+
throw new Error(
|
|
1859
|
+
`NeutrinoParticles: data texture ${r}x${n} exceeds GL MAX_TEXTURE_SIZE (${o}). Reduce effect particle capacity.`
|
|
1860
|
+
);
|
|
1861
|
+
if (r * n > 16777216)
|
|
1862
|
+
throw new Error(
|
|
1863
|
+
`NeutrinoParticles: data texture ${r}x${n} exceeds the WebGL1 render path texel cap (2^24). Reduce effect particle capacity.`
|
|
1864
|
+
);
|
|
1865
|
+
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);
|
|
1866
|
+
const s = e.getParameter(e.ACTIVE_TEXTURE);
|
|
1867
|
+
e.activeTexture(e.TEXTURE0);
|
|
1868
|
+
const a = e.getParameter(e.TEXTURE_BINDING_2D);
|
|
1869
|
+
for (let c = 0; c < RING_BUFFER_SIZE; c++)
|
|
1870
|
+
this._dataTextures.push(this._createTexture(e.FLOAT)), this._metaTextures.push(this._createTexture(e.UNSIGNED_BYTE));
|
|
1871
|
+
e.bindTexture(e.TEXTURE_2D, a), e.activeTexture(s);
|
|
1872
|
+
}
|
|
1873
|
+
get currentDataTexture() {
|
|
1874
|
+
return this._dataTextures[this._currentIndex];
|
|
1875
|
+
}
|
|
1876
|
+
get currentMetaTexture() {
|
|
1877
|
+
return this._metaTextures[this._currentIndex];
|
|
1878
|
+
}
|
|
1879
|
+
/**
|
|
1880
|
+
* Upload the rows covering `numParticles` into the current ring textures,
|
|
1881
|
+
* leaving them bound to `dataUnit` / `metaUnit`. The caller invalidates
|
|
1882
|
+
* PIXI's bound-texture bookkeeping for those units.
|
|
1883
|
+
*/
|
|
1884
|
+
upload(e, t, r) {
|
|
1885
|
+
const n = this._gl, o = this.textureWidth, s = Math.min(this.textureHeight, Math.ceil(e * 4 / o));
|
|
1886
|
+
if (s === 0) return;
|
|
1887
|
+
const a = s * o * 4;
|
|
1888
|
+
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(
|
|
1889
|
+
n.TEXTURE_2D,
|
|
1890
|
+
0,
|
|
1891
|
+
0,
|
|
1892
|
+
0,
|
|
1893
|
+
o,
|
|
1894
|
+
s,
|
|
1895
|
+
n.RGBA,
|
|
1896
|
+
n.FLOAT,
|
|
1897
|
+
this._floatView.subarray(0, a)
|
|
1898
|
+
), n.activeTexture(n.TEXTURE0 + r), n.bindTexture(n.TEXTURE_2D, this.currentMetaTexture), n.texSubImage2D(
|
|
1899
|
+
n.TEXTURE_2D,
|
|
1900
|
+
0,
|
|
1901
|
+
0,
|
|
1902
|
+
0,
|
|
1903
|
+
o,
|
|
1904
|
+
s,
|
|
1905
|
+
n.RGBA,
|
|
1906
|
+
n.UNSIGNED_BYTE,
|
|
1907
|
+
this.meta.bytes.subarray(0, a)
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1910
|
+
advance() {
|
|
1911
|
+
this._currentIndex = (this._currentIndex + 1) % RING_BUFFER_SIZE;
|
|
1912
|
+
}
|
|
1913
|
+
destroy() {
|
|
1914
|
+
const e = this._gl;
|
|
1915
|
+
for (const t of this._dataTextures) e.deleteTexture(t);
|
|
1916
|
+
for (const t of this._metaTextures) e.deleteTexture(t);
|
|
1917
|
+
this._dataTextures = [], this._metaTextures = [];
|
|
1918
|
+
}
|
|
1919
|
+
_createTexture(e) {
|
|
1920
|
+
const t = this._gl, r = t.createTexture();
|
|
1921
|
+
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(
|
|
1922
|
+
t.TEXTURE_2D,
|
|
1923
|
+
0,
|
|
1924
|
+
t.RGBA,
|
|
1925
|
+
this.textureWidth,
|
|
1926
|
+
this.textureHeight,
|
|
1927
|
+
0,
|
|
1928
|
+
t.RGBA,
|
|
1929
|
+
e,
|
|
1930
|
+
null
|
|
1931
|
+
), r;
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
var Pause = /* @__PURE__ */ ((l) => (l[l.NO = 0] = "NO", l[l.BEFORE_UPDATE_OR_RENDER = 1] = "BEFORE_UPDATE_OR_RENDER", l[l.YES = 2] = "YES", l))(Pause || {});
|
|
1935
|
+
const emptyBounds = new Bounds(0, 0, 0, 0);
|
|
1936
|
+
class Effect extends ViewContainer {
|
|
1937
|
+
constructor(t, r) {
|
|
1938
|
+
super({});
|
|
1939
|
+
i(this, "renderPipeId", "neutrino");
|
|
1940
|
+
i(this, "batched", !1);
|
|
1941
|
+
i(this, "ctx");
|
|
1942
|
+
i(this, "effectModel");
|
|
1943
|
+
i(this, "effect");
|
|
1944
|
+
i(this, "baseParent");
|
|
1945
|
+
i(this, "projection");
|
|
1946
|
+
i(this, "positionZ");
|
|
1947
|
+
i(this, "scaleZ");
|
|
1948
|
+
/** World transform actually fed to the renderer's model matrix (2x3). */
|
|
1949
|
+
i(this, "worldRenderMatrix", { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 });
|
|
1950
|
+
i(this, "_worldPosition", new Point(0, 0));
|
|
1951
|
+
i(this, "_worldRotationDegree", 0);
|
|
1952
|
+
i(this, "_worldScale", new Point(1, 1));
|
|
1953
|
+
i(this, "_dataTextureManager");
|
|
1954
|
+
i(this, "_unpauseOnUpdateRender");
|
|
1955
|
+
i(this, "_startupOptions");
|
|
1956
|
+
i(this, "_inited", !1);
|
|
1957
|
+
i(this, "_ready", !1);
|
|
1958
|
+
const n = Object.assign({
|
|
1959
|
+
position: [0, 0, 0],
|
|
1960
|
+
rotation: 0,
|
|
1961
|
+
scale: [1, 1, 1],
|
|
1962
|
+
pause: 1,
|
|
1963
|
+
generatorsPaused: !1,
|
|
1964
|
+
baseParent: void 0,
|
|
1965
|
+
projection: void 0,
|
|
1966
|
+
autoInit: !0
|
|
1967
|
+
}, r);
|
|
1968
|
+
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 = {
|
|
1969
|
+
paused: n.pause !== 0,
|
|
1970
|
+
generatorsPaused: n.generatorsPaused
|
|
1971
|
+
}, this._unpauseOnUpdateRender = n.pause === 1, n.autoInit && this.init(), this._updateWorldTransform();
|
|
1972
|
+
}
|
|
1973
|
+
get bounds() {
|
|
1974
|
+
return emptyBounds;
|
|
1975
|
+
}
|
|
1976
|
+
updateBounds() {
|
|
1977
|
+
}
|
|
1978
|
+
init() {
|
|
1979
|
+
this._inited || (this._inited = !0, this.effectModel.ready() ? this._onEffectReady() : this.effectModel.once("ready", this._onEffectReady, this));
|
|
1980
|
+
}
|
|
1981
|
+
ready() {
|
|
1982
|
+
return this._ready;
|
|
1983
|
+
}
|
|
1984
|
+
update(t) {
|
|
1985
|
+
this.ready() && (this._checkUnpauseOnUpdateRender(), this._updateWorldTransform(), this.effect && (this.effect.update(
|
|
1986
|
+
t,
|
|
1987
|
+
this._scaledPosition(),
|
|
1988
|
+
this.ctx.neutrino.axisangle2quat_([0, 0, 1], this._worldRotationDegree)
|
|
1989
|
+
), this.onViewUpdate()));
|
|
1990
|
+
}
|
|
1991
|
+
restart(t, r) {
|
|
1992
|
+
t && (this.position.x = t[0], this.position.y = t[1], this.positionZ = t[2]), r && (this.rotation = r), this._updateWorldTransform(), this.effect.restart(
|
|
1993
|
+
this._scaledPosition(),
|
|
1994
|
+
r ? this.ctx.neutrino.axisangle2quat_([0, 0, 1], this._worldRotationDegree) : void 0
|
|
1995
|
+
), this.onViewUpdate();
|
|
1996
|
+
}
|
|
1997
|
+
resetPosition(t, r) {
|
|
1998
|
+
t && (this.position.x = t[0], this.position.y = t[1], this.positionZ = t[2]), r && (this.rotation = r), this._updateWorldTransform(), this.effect.resetPosition(
|
|
1999
|
+
this._scaledPosition(),
|
|
2000
|
+
r ? this.ctx.neutrino.axisangle2quat_([0, 0, 1], this._worldRotationDegree) : void 0
|
|
2001
|
+
), this.onViewUpdate();
|
|
2002
|
+
}
|
|
2003
|
+
pause() {
|
|
2004
|
+
this.ready() ? this.effect.pauseAllEmitters() : this._startupOptions.paused = !0;
|
|
2005
|
+
}
|
|
2006
|
+
unpause() {
|
|
2007
|
+
this.ready() ? this.effect.unpauseAllEmitters() : this._startupOptions.paused = !1;
|
|
2008
|
+
}
|
|
2009
|
+
pauseGenerators() {
|
|
2010
|
+
this.ready() ? this.effect.pauseGeneratorsInAllEmitters() : this._startupOptions.generatorsPaused = !0;
|
|
2011
|
+
}
|
|
2012
|
+
unpauseGenerators() {
|
|
2013
|
+
this.ready() ? this.effect.unpauseGeneratorsInAllEmitters() : this._startupOptions.generatorsPaused = !1;
|
|
2014
|
+
}
|
|
2015
|
+
setPropertyInAllEmitters(t, r) {
|
|
2016
|
+
this.effect.setPropertyInAllEmitters(t, r), this.onViewUpdate();
|
|
2017
|
+
}
|
|
2018
|
+
getNumParticles() {
|
|
2019
|
+
return this.effect ? this.effect.getNumParticles() : 0;
|
|
2020
|
+
}
|
|
2021
|
+
/** @internal — called by EffectPipe during the render pass. */
|
|
2022
|
+
_dtm() {
|
|
2023
|
+
return this._dataTextureManager;
|
|
2024
|
+
}
|
|
2025
|
+
/**
|
|
2026
|
+
* @internal — called by EffectPipe before constructing the frame.
|
|
2027
|
+
* Auto-unpauses an effect whose first interaction is a render rather than an
|
|
2028
|
+
* update (Pause.BEFORE_UPDATE_OR_RENDER), then refreshes the world transform
|
|
2029
|
+
* from the live scene graph so a parent transform change since the last
|
|
2030
|
+
* update() is reflected this frame (mirrors pixi7, which reads
|
|
2031
|
+
* baseParent.worldTransform live in its render plugin).
|
|
2032
|
+
*/
|
|
2033
|
+
_prepareRender() {
|
|
2034
|
+
this._checkUnpauseOnUpdateRender(), this._updateWorldTransform();
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* @internal — resolved world alpha fed to the renderer (uWorldAlpha).
|
|
2038
|
+
* groupAlpha only accumulates alpha WITHIN the effect's render group; the
|
|
2039
|
+
* cross-group factor lives on parentRenderGroup.worldAlpha, so multiply both
|
|
2040
|
+
* to honour transparency set on a render-group ancestor.
|
|
2041
|
+
*/
|
|
2042
|
+
get worldAlpha() {
|
|
2043
|
+
var t;
|
|
2044
|
+
return this.groupAlpha * (((t = this.parentRenderGroup) == null ? void 0 : t.worldAlpha) ?? 1);
|
|
2045
|
+
}
|
|
2046
|
+
/**
|
|
2047
|
+
* @internal — called by EffectPipe. True when the effect is fully
|
|
2048
|
+
* transparent, in which case the draw is skipped entirely (mirrors the phaser
|
|
2049
|
+
* adapter and PIXI's scene-graph cull of a zero-alpha container). Partial
|
|
2050
|
+
* alpha is applied in the shader via uWorldAlpha (see {@link worldAlpha}).
|
|
2051
|
+
*/
|
|
2052
|
+
_isInvisible() {
|
|
2053
|
+
return this.worldAlpha <= 0;
|
|
2054
|
+
}
|
|
2055
|
+
destroy(t) {
|
|
2056
|
+
this.effect || this.effectModel.removeListener("ready", this._onEffectReady, this), this._dataTextureManager && (this._dataTextureManager.destroy(), this._dataTextureManager = void 0), super.destroy(t);
|
|
2057
|
+
}
|
|
2058
|
+
_onEffectReady() {
|
|
2059
|
+
this._updateWorldTransform(), this.effect = this.effectModel.effectModel.createInstance(
|
|
2060
|
+
this._scaledPosition(),
|
|
2061
|
+
this.ctx.neutrino.axisangle2quat_([0, 0, 1], this._worldRotationDegree),
|
|
2062
|
+
this._startupOptions
|
|
2063
|
+
), this._dataTextureManager = this.ctx.isWebGL1 ? new DataTextureManagerGL1(
|
|
2064
|
+
this.ctx.gl,
|
|
2065
|
+
this.effect.particleDataUint32,
|
|
2066
|
+
this.effect.dataTextureWidth,
|
|
2067
|
+
this.effect.dataTextureHeight
|
|
2068
|
+
) : new DataTextureManager(
|
|
2069
|
+
this.ctx.gl,
|
|
2070
|
+
this.effect.particleDataUint32,
|
|
2071
|
+
this.effect.dataTextureWidth,
|
|
2072
|
+
this.effect.dataTextureHeight
|
|
2073
|
+
), this.ctx.ensureIndexBuffer(this.effectModel.effectModel.totalParticles), this._ready = !0, this.emit("ready", this);
|
|
2074
|
+
}
|
|
2075
|
+
_scaledPosition() {
|
|
2076
|
+
return [
|
|
2077
|
+
this._worldScale.x !== 0 ? this._worldPosition.x / this._worldScale.x : 0,
|
|
2078
|
+
this._worldScale.y !== 0 ? this._worldPosition.y / this._worldScale.y : 0,
|
|
2079
|
+
this.scaleZ !== 0 ? this.positionZ / this.scaleZ : 0
|
|
2080
|
+
];
|
|
2081
|
+
}
|
|
2082
|
+
_getRenderGroupContainer(t) {
|
|
2083
|
+
return t.renderGroup || !t.parent ? t : this._getRenderGroupContainer(t.parent);
|
|
2084
|
+
}
|
|
2085
|
+
_updateWorldTransform() {
|
|
2086
|
+
const t = new Point(0, 0), r = new Point(1, 0), n = new Point(0, 1), o = this.baseParent || this._getRenderGroupContainer(this);
|
|
2087
|
+
this._worldPosition = o.toLocal(t, this);
|
|
2088
|
+
const s = o.toLocal(r, this), a = o.toLocal(n, this);
|
|
2089
|
+
if (s.x -= this._worldPosition.x, s.y -= this._worldPosition.y, a.x -= this._worldPosition.x, a.y -= this._worldPosition.y, this._worldScale = new Point(
|
|
2090
|
+
Math.sqrt(s.x * s.x + s.y * s.y),
|
|
2091
|
+
Math.sqrt(a.x * a.x + a.y * a.y)
|
|
2092
|
+
), this._worldRotationDegree = this._calcWorldRotation(this) / Math.PI * 180 % 360, this.baseParent) {
|
|
2093
|
+
const c = this.baseParent.worldTransform;
|
|
2094
|
+
this.worldRenderMatrix = { a: c.a, b: c.b, c: c.c, d: c.d, tx: c.tx, ty: c.ty };
|
|
2095
|
+
} else
|
|
2096
|
+
this.worldRenderMatrix = {
|
|
2097
|
+
a: this._worldScale.x,
|
|
2098
|
+
b: 0,
|
|
2099
|
+
c: 0,
|
|
2100
|
+
d: this._worldScale.y,
|
|
2101
|
+
tx: 0,
|
|
2102
|
+
ty: 0
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
_calcWorldRotation(t) {
|
|
2106
|
+
return t.parent && t.parent != this.baseParent ? t.rotation + this._calcWorldRotation(t.parent) : t.rotation;
|
|
2107
|
+
}
|
|
2108
|
+
_checkUnpauseOnUpdateRender() {
|
|
2109
|
+
this._unpauseOnUpdateRender && (this.resetPosition(), this.unpause(), this._unpauseOnUpdateRender = !1);
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
class PerspectiveProjection {
|
|
2113
|
+
constructor(e) {
|
|
2114
|
+
i(this, "_angleTan", 0);
|
|
2115
|
+
i(this, "_screenWidth", 0);
|
|
2116
|
+
i(this, "_screenPosX", 0);
|
|
2117
|
+
i(this, "_screenPosY", 0);
|
|
2118
|
+
i(this, "_z", 0);
|
|
2119
|
+
i(this, "_near", 0);
|
|
2120
|
+
this.horizontalAngle = e;
|
|
2121
|
+
}
|
|
2122
|
+
set horizontalAngle(e) {
|
|
2123
|
+
this._angleTan = Math.tan(e * 0.5 / 180 * Math.PI);
|
|
2124
|
+
}
|
|
2125
|
+
/**
|
|
2126
|
+
* Changes horizontal angle of the projection.
|
|
2127
|
+
*
|
|
2128
|
+
* @param {number} value Angle in degrees.
|
|
2129
|
+
*/
|
|
2130
|
+
/**
|
|
2131
|
+
* Sets rendering frame for the projection.
|
|
2132
|
+
*
|
|
2133
|
+
* This method shouldn't be called manually when the class is used with effects. It is called
|
|
2134
|
+
* on every render call for each effect automatically.
|
|
2135
|
+
* @param {PIXI.Rectangle} frame Rendering frame.
|
|
2136
|
+
*/
|
|
2137
|
+
setScreenFrame(e) {
|
|
2138
|
+
this._screenWidth = e.width, this._screenPosX = e.x + e.width * 0.5, this._screenPosY = e.y + e.height * 0.5, this._z = this._screenWidth * 0.5 / this._angleTan, this._near = this._z * 0.99;
|
|
2139
|
+
}
|
|
2140
|
+
/**
|
|
2141
|
+
* Transforms 3D point accordingly to the projection.
|
|
2142
|
+
*
|
|
2143
|
+
* Basically, point's position X and Y components are simply scaled dependently on Z position. The method
|
|
2144
|
+
* is used in WebGL and Canvas rendering.
|
|
2145
|
+
*
|
|
2146
|
+
* @param {Array} out [x, y] Transformed position.
|
|
2147
|
+
* @param {Array} pos [x, y, z] Untransformed input vertex position.
|
|
2148
|
+
* @returns false, if a particle is on the back side of the camera and should be discarded. Otherwise - true.
|
|
2149
|
+
*/
|
|
2150
|
+
transformPosition(e, t) {
|
|
2151
|
+
if (t[2] > this._near)
|
|
2152
|
+
return !1;
|
|
2153
|
+
const r = this._getScale(t);
|
|
2154
|
+
return e[0] = (t[0] - this._screenPosX) * r + this._screenPosX, e[1] = (t[1] - this._screenPosY) * r + this._screenPosY, !0;
|
|
2155
|
+
}
|
|
2156
|
+
/**
|
|
2157
|
+
* Transforms 2D size of a particle accordingly to the projection.
|
|
2158
|
+
*
|
|
2159
|
+
* Basically, size is simply scaled dependently on Z position of a particle. The method is used
|
|
2160
|
+
* only in Canvas rendering.
|
|
2161
|
+
*
|
|
2162
|
+
* @param {Array} outSize [width, height] Transformed size.
|
|
2163
|
+
* @param {Array} pos [x, y, z] Untransformed particle position.
|
|
2164
|
+
* @param {Array} size [width, height] Untransformed size.
|
|
2165
|
+
*/
|
|
2166
|
+
transformSize(e, t, r) {
|
|
2167
|
+
const n = this._getScale(t);
|
|
2168
|
+
e[0] = r[0] * n, e[1] = r[1] * n;
|
|
2169
|
+
}
|
|
2170
|
+
_getScale(e) {
|
|
2171
|
+
return this._z / (this._z - e[2]);
|
|
2172
|
+
}
|
|
2173
|
+
/**
|
|
2174
|
+
* Writes the projection as a 4x4 column-major matrix operating in screen
|
|
2175
|
+
* space, for the GPU render path. Homogeneous equivalent of
|
|
2176
|
+
* transformPosition(): clip.xy = p.xy - center * (p.z / z),
|
|
2177
|
+
* clip.w = 1 - p.z / z; the z row is zeroed (no depth on this path).
|
|
2178
|
+
* setScreenFrame() must have been called before.
|
|
2179
|
+
*
|
|
2180
|
+
* @param {Float32Array} out 16-element column-major output matrix.
|
|
2181
|
+
*/
|
|
2182
|
+
writeMatrix(e) {
|
|
2183
|
+
const t = this._z, r = Number.isFinite(t) && t > 0 ? 1 / t : 0;
|
|
2184
|
+
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;
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
function registerPlugins() {
|
|
2188
|
+
extensions.add(ApplicationPlugin), extensions.add(effectModelLoader), extensions.add(EffectPipe);
|
|
2189
|
+
}
|
|
2190
|
+
function unregisterPlugins() {
|
|
2191
|
+
extensions.remove(ApplicationPlugin), extensions.remove(effectModelLoader), extensions.remove(EffectPipe);
|
|
2192
|
+
}
|
|
2193
|
+
registerPlugins();
|
|
2194
|
+
export {
|
|
2195
|
+
AbstractTexturesLoader,
|
|
2196
|
+
ApplicationPlugin,
|
|
2197
|
+
AssetsTexturesLoader,
|
|
2198
|
+
Context,
|
|
2199
|
+
DataTextureManager,
|
|
2200
|
+
DefaultAssetsTexturesLoader,
|
|
2201
|
+
Effect,
|
|
2202
|
+
EffectModel,
|
|
2203
|
+
EffectPipe,
|
|
2204
|
+
NeutrinoRenderer,
|
|
2205
|
+
Pause,
|
|
2206
|
+
PerspectiveProjection,
|
|
2207
|
+
effectModelLoader,
|
|
2208
|
+
registerPlugins,
|
|
2209
|
+
unregisterPlugins
|
|
2210
|
+
};
|
|
2211
|
+
//# sourceMappingURL=neutrinoparticles.js-v1.1-pixi8.es.js.map
|