@neutrinoparticles/js-v1.1-phaser 1.0.1 → 1.1.1

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.
@@ -1,6 +1,6 @@
1
- var F = Object.defineProperty;
2
- var N = (s, e, t) => e in s ? F(s, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : s[e] = t;
3
- var n = (s, e, t) => N(s, typeof e != "symbol" ? e + "" : e, t);
1
+ var K = Object.defineProperty;
2
+ var z = (s, e, t) => e in s ? K(s, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : s[e] = t;
3
+ var n = (s, e, t) => z(s, typeof e != "symbol" ? e + "" : e, t);
4
4
  import Phaser from "phaser";
5
5
  import * as Neutrino from "@neutrinoparticles/js-v1.1";
6
6
  const vertexShaderSource = `#version 300 es
@@ -54,7 +54,8 @@ vec4 fetchTexel(int globalTexelIndex) {
54
54
  }
55
55
 
56
56
  // Strip geometry helpers (port of GPU33 makeScreenSpaceStrip).
57
- // 2D screen-space variant PIXI7 renderer is 2D, world Z is dropped at gl_Position.
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).
58
59
  vec2 stripOrtho(vec2 v) {
59
60
  return vec2(-v.y, v.x);
60
61
  }
@@ -91,9 +92,9 @@ void main() {
91
92
  // 0 (Faced): screen-space ortho to dir12 — equivalent to camera-faced
92
93
  // billboard normal in a 2D camera setup.
93
94
  // 1 (Free): the quaternion stored in texel 2 nominally encodes a 3D
94
- // orientation. The current PIXI7 renderer is fundamentally
95
- // 2D (worldPos.z is dropped at gl_Position for quads), so
96
- // the quat is read but its 3D y-axis is projected to screen
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
97
98
  // and used as the section normal. When the projection
98
99
  // degenerates (looking along the y-axis) we fall back to
99
100
  // the screen-space ortho. Real 3D free-rotation requires a
@@ -128,6 +129,10 @@ void main() {
128
129
  float((packedColorNex >> 8u) & 0xFFu) / 255.0,
129
130
  float((packedColorNex >> 16u) & 0xFFu) / 255.0,
130
131
  float((packedColorNex >> 24u) & 0xFFu) / 255.0);
132
+ // HDR color scale S (RGBS, bloom TDD #175 §13.12): offset+60 (t3.a) per-particle
133
+ // float scale; multiply BEFORE the cross-fade. S=1 for LDR (no-op).
134
+ color1.rgb *= (isCur ? t3.a : ts3.a);
135
+ color2.rgb *= (isCur ? ts3.a : t3.a);
131
136
 
132
137
  // neighborIdxP0 / neighborIdxP3 are stored on every meta of a pair
133
138
  // (both meta_cur and meta_nex hold the same pair of indices), so we
@@ -175,7 +180,7 @@ void main() {
175
180
  bool nearPlaneClipped = (sp1.w < 1e-3) || (sp2.w < 1e-3);
176
181
 
177
182
  // #236: set when the segment itself is degenerate (much shorter than
178
- // the ribbon half-width) - the pair is discarded like nearPlaneClipped.
183
+ // the ribbon half-width) the pair is discarded like nearPlaneClipped.
179
184
  bool stripDegenerate = false;
180
185
 
181
186
  // Aspect correction so the 2D Faced ortho gives visually-perpendicular
@@ -261,7 +266,7 @@ void main() {
261
266
  vec3 K2 = Kmul2 * normalize(cross(wdir12 + wdir23, n2));
262
267
  Bmul2 = sign(dot(B2raw, K2));
263
268
  vec3 B2 = B2raw * Bmul2;
264
- // #236: see ik1 - inner vertex at the ribbon half-width.
269
+ // #236: see ik1 inner vertex at the ribbon half-width.
265
270
  float ik2 = halfSize2;
266
271
  worldK2 = p2 + K2 * halfSize2;
267
272
  worldB2 = p2 + B2 * halfSize2;
@@ -279,10 +284,16 @@ void main() {
279
284
  // Faced: screen-space NDC frame around dir12.
280
285
  vec4 sp0 = mvpMatrix * vec4(p0, 1.0);
281
286
  vec4 sp3 = mvpMatrix * vec4(p3, 1.0);
282
- vec2 ndc0 = (sp0.xy / sp0.w) * aspectMul;
283
287
  vec2 ndc1 = (sp1.xy / sp1.w) * aspectMul;
284
288
  vec2 ndc2 = (sp2.xy / sp2.w) * aspectMul;
285
- vec2 ndc3 = (sp3.xy / sp3.w) * aspectMul;
289
+ // Neighbor anchors behind the near plane produce garbage NDC after
290
+ // the perspective divide (possible only with Projection3D, where w
291
+ // varies); collapse them onto their segment anchor so the existing
292
+ // zero-length degeneracy path (dir01 = dir12 / dir23 = dir12)
293
+ // handles the knee instead of a poisoned miter. spU1/spU2 need no
294
+ // guard: uCameraUp has zero Z, so their w equals sp1.w / sp2.w.
295
+ vec2 ndc0 = (sp0.w < 1e-3) ? ndc1 : (sp0.xy / sp0.w) * aspectMul;
296
+ vec2 ndc3 = (sp3.w < 1e-3) ? ndc2 : (sp3.xy / sp3.w) * aspectMul;
286
297
 
287
298
  vec2 d01 = ndc1 - ndc0;
288
299
  vec2 d12 = ndc2 - ndc1;
@@ -333,7 +344,7 @@ void main() {
333
344
  vec2 K2 = Kmul2 * normalize(stripOrtho(dir12 + dir23));
334
345
  Bmul2 = sign(dot(B, K2));
335
346
  vec2 B2 = B * Bmul2;
336
- // #236: see ik1 - inner vertex at the ribbon half-width.
347
+ // #236: see ik1 inner vertex at the ribbon half-width.
337
348
  float ik2 = ndcHalfSize2;
338
349
  k2 = ndc2 + K2 * ndcHalfSize2;
339
350
  b2 = ndc2 + B2 * ndcHalfSize2;
@@ -349,7 +360,7 @@ void main() {
349
360
  }
350
361
  }
351
362
 
352
- // #236: a noKnee (straight-cap) end carries no bend side of its own -
363
+ // #236: a noKnee (straight-cap) end carries no bend side of its own
353
364
  // its raw side label is always the +B side. If the segment's other
354
365
  // end is a real knee on the -B side, the mismatched labels would send
355
366
  // the ladder into the opposite-sides (twist) branch and flip the
@@ -376,6 +387,7 @@ void main() {
376
387
  Bmul2 = Bmul1;
377
388
  }
378
389
 
390
+
379
391
  // V coords (mirror of preview3 / GPU33 make*Strip):
380
392
  // V at outer knee (k) = (Bmul + 1) * 0.5
381
393
  // V at inner knee (IK) = 1 - V_outer ; bend (b) = outer
@@ -478,7 +490,7 @@ void main() {
478
490
  // Free projects the world-space vertex through MVP directly; Faced
479
491
  // reconstructs clip space from aspect-corrected NDC (mirror of preview3).
480
492
  if (stripDegenerate) {
481
- // #236: sub-visual segment - discard the pair entirely.
493
+ // #236: sub-visual segment discard the pair entirely.
482
494
  gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
483
495
  } else if (isFreeRotation) {
484
496
  gl_Position = mvpMatrix * vec4(vertPos3D, 1.0);
@@ -517,6 +529,10 @@ void main() {
517
529
  float((packedColor >> 16u) & 0xFFu) / 255.0,
518
530
  float((packedColor >> 24u) & 0xFFu) / 255.0
519
531
  );
532
+ // HDR color scale S (RGBS, bloom TDD #175 §13.12): offset+60 (t3.a) is a
533
+ // per-particle float scale. RGB was normalized by S at pack time; restore HDR
534
+ // here. S=1 for LDR colors (no-op). Alpha is untouched.
535
+ vColor.rgb *= t3.a;
520
536
 
521
537
  uint gridConfig = floatBitsToUint(t3.g);
522
538
  float gridWidth = max(float(gridConfig & 0xFFFFu), 1.0);
@@ -564,13 +580,37 @@ void main() {
564
580
 
565
581
  mat4 mvpMatrix = uViewProjMatrix * uModelMatrix;
566
582
  vec3 worldPos = position + posDisp;
567
- gl_Position = mvpMatrix * vec4(worldPos.xy, 0.0, 1.0);
583
+ gl_Position = mvpMatrix * vec4(worldPos, 1.0);
584
+
585
+ // v1.0-parity near-plane culling for the perspective projection: the
586
+ // whole quad is discarded when ANY of its 4 corners is behind the near
587
+ // plane (w < 0.01 == pos.z > 0.99 * z_cam in PerspectiveProjection
588
+ // terms). w is linear in position, so the corner minimum comes from the
589
+ // w-row and the corner offsets (x in {-ox, 1-ox}, y in {-oy, 1-oy}).
590
+ // Without a projection every w is 1.0 and this is a no-op.
591
+ vec4 wRow = vec4(mvpMatrix[0].w, mvpMatrix[1].w, mvpMatrix[2].w, mvpMatrix[3].w);
592
+ float wx = dot(wRow.xyz, xaxis);
593
+ float wy = dot(wRow.xyz, yaxis);
594
+ float minW = dot(wRow.xyz, position) + wRow.w
595
+ - origin.x * wx + min(wx, 0.0)
596
+ - origin.y * wy + min(wy, 0.0);
597
+ if (minW < 0.01) {
598
+ gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
599
+ }
568
600
 
569
601
  // Texture coordinates with grid + atlas remap
570
602
  vec2 baseTexCoord = TEX_COORD[vertexIndex];
571
603
 
572
- float gx = mod(gridIndex, gridWidth);
604
+ // #283: gx must come from the SAME quotient as gy. GLSL mod(x,y) is
605
+ // x - y*floor(x/y), but the driver may evaluate its internal quotient
606
+ // differently from the floor() below (reciprocal-multiply / FMA). At exact
607
+ // multiples of gridWidth the two disagreed — mod(15,15) gave 15 while
608
+ // floor(15/15) gave 0 — addressing column 15 of a 15-column grid, whose U
609
+ // range starts at 1.0 (off the atlas), so the first cell of every row
610
+ // sampled the clamped transparent edge and vanished. The clamp keeps a
611
+ // rounding-induced gx == gridWidth inside the last column.
573
612
  float gy = floor(gridIndex / gridWidth);
613
+ float gx = clamp(gridIndex - gy * gridWidth, 0.0, gridWidth - 1.0);
574
614
  float cellW = 1.0 / gridWidth;
575
615
  float cellH = 1.0 / gridHeight;
576
616
 
@@ -656,6 +696,13 @@ void main() {
656
696
  else texColor = useGrad ? textureGrad(uTextures[7], atlasUV, gradX, gradY) : texture(uTextures[7], atlasUV);
657
697
 
658
698
  fragColor = texColor * vColor;
699
+ // Premultiply the per-particle alpha into RGB. texColor is premultiplied but
700
+ // vColor.a is not, and every blend func expects a premultiplied source:
701
+ // NORMAL (ONE, 1-src.a) would only reduce how much background shows through,
702
+ // ADD (ONE, ONE) would ignore alpha entirely, and MULTIPLY
703
+ // (DST_COLOR, 1-src.a) would brighten the destination as alpha drops instead
704
+ // of leaving it untouched.
705
+ fragColor.rgb *= vColor.a;
659
706
  // Apply host world alpha. texColor is premultiplied and the blend funcs use
660
707
  // a src factor of ONE (NORMAL) / ONE,ONE (ADD), so the whole RGBA must be
661
708
  // scaled — fading RGB, not just the alpha channel — to dim correctly in both
@@ -784,6 +831,11 @@ void main() {
784
831
  // Colors: the meta M1 sample is the normalized RGBA directly.
785
832
  vec4 color1 = fetchMeta(curBase + 1.0);
786
833
  vec4 color2 = fetchMeta(nexBase + 1.0);
834
+ // HDR color scale S (RGBS, bloom TDD #175 §13.12): a plain float at offset+60,
835
+ // read straight from the float DATA texture (texel 3, .a) — like gridIndex — not
836
+ // the meta texture. Multiply BEFORE the cross-fade. S=1 → no-op.
837
+ color1.rgb *= fetchData(curBase + 3.0).a;
838
+ color2.rgb *= fetchData(nexBase + 3.0).a;
787
839
 
788
840
  // Neighbor RECORD indices (system.js convention: neighborIdxP0 on the
789
841
  // pair-cur meta, neighborIdxP3 on the pair-nex meta; boundary
@@ -919,10 +971,16 @@ void main() {
919
971
  // Faced: screen-space NDC frame around dir12.
920
972
  vec4 sp0 = mvpMatrix * vec4(p0, 1.0);
921
973
  vec4 sp3 = mvpMatrix * vec4(p3, 1.0);
922
- vec2 ndc0 = (sp0.xy / sp0.w) * aspectMul;
923
974
  vec2 ndc1 = (sp1.xy / sp1.w) * aspectMul;
924
975
  vec2 ndc2 = (sp2.xy / sp2.w) * aspectMul;
925
- vec2 ndc3 = (sp3.xy / sp3.w) * aspectMul;
976
+ // Neighbor anchors behind the near plane produce garbage NDC after
977
+ // the perspective divide (possible only with Projection3D, where w
978
+ // varies); collapse them onto their segment anchor so the existing
979
+ // zero-length degeneracy path (dir01 = dir12 / dir23 = dir12)
980
+ // handles the knee instead of a poisoned miter. spU1/spU2 need no
981
+ // guard: uCameraUp has zero Z, so their w equals sp1.w / sp2.w.
982
+ vec2 ndc0 = (sp0.w < 1e-3) ? ndc1 : (sp0.xy / sp0.w) * aspectMul;
983
+ vec2 ndc3 = (sp3.w < 1e-3) ? ndc2 : (sp3.xy / sp3.w) * aspectMul;
926
984
 
927
985
  vec2 d01 = ndc1 - ndc0;
928
986
  vec2 d12 = ndc2 - ndc1;
@@ -1114,6 +1172,9 @@ void main() {
1114
1172
 
1115
1173
  // Meta M1: the packedColor u32 LE bytes ARE the normalized RGBA sample.
1116
1174
  vColor = fetchMeta(baseTexel + 1.0);
1175
+ // HDR color scale S (RGBS, bloom TDD #175 §13.12): a plain float at offset+60,
1176
+ // read directly from the float DATA texture (t3.a) like gridIndex. S=1 → no-op.
1177
+ vColor.rgb *= t3.a;
1117
1178
 
1118
1179
  // Meta M2: gridConfig — [gridW lo, gridW hi, gridH lo, gridH hi].
1119
1180
  vec4 m2 = fetchMeta(baseTexel + 2.0);
@@ -1160,13 +1221,33 @@ void main() {
1160
1221
 
1161
1222
  mat4 mvpMatrix = uViewProjMatrix * uModelMatrix;
1162
1223
  vec3 worldPos = position + posDisp;
1163
- gl_Position = mvpMatrix * vec4(worldPos.xy, 0.0, 1.0);
1224
+ gl_Position = mvpMatrix * vec4(worldPos, 1.0);
1225
+
1226
+ // v1.0-parity near-plane culling for the perspective projection: the
1227
+ // whole quad is discarded when ANY of its 4 corners is behind the near
1228
+ // plane (w < 0.01 == pos.z > 0.99 * z_cam in PerspectiveProjection
1229
+ // terms). w is linear in position, so the corner minimum comes from the
1230
+ // w-row and the corner offsets (x in {-ox, 1-ox}, y in {-oy, 1-oy}).
1231
+ // Without a projection every w is 1.0 and this is a no-op.
1232
+ vec4 wRow = vec4(mvpMatrix[0].w, mvpMatrix[1].w, mvpMatrix[2].w, mvpMatrix[3].w);
1233
+ float wx = dot(wRow.xyz, xaxis);
1234
+ float wy = dot(wRow.xyz, yaxis);
1235
+ float minW = dot(wRow.xyz, position) + wRow.w
1236
+ - origin.x * wx + min(wx, 0.0)
1237
+ - origin.y * wy + min(wy, 0.0);
1238
+ if (minW < 0.01) {
1239
+ gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
1240
+ }
1164
1241
 
1165
1242
  // Texture coordinates with grid + atlas remap
1166
1243
  vec2 baseTexCoord = texCoordOf(corner);
1167
1244
 
1168
- float gx = mod(gridIndex, gridWidth);
1245
+ // #283: gx must come from the SAME quotient as gy — see particle.vert.
1246
+ // mod(x,y) and a separate floor(x/y) can disagree at exact multiples of
1247
+ // gridWidth (mod(15,15) -> 15, floor(15/15) -> 0), which addressed a
1248
+ // column past the grid and blanked the first cell of every row.
1169
1249
  float gy = floor(gridIndex / gridWidth);
1250
+ float gx = clamp(gridIndex - gy * gridWidth, 0.0, gridWidth - 1.0);
1170
1251
  float cellW = 1.0 / gridWidth;
1171
1252
  float cellH = 1.0 / gridHeight;
1172
1253
 
@@ -1234,6 +1315,11 @@ void main() {
1234
1315
  // texColor is premultiplied and blend src factors are ONE-based, so the
1235
1316
  // world alpha scales the whole RGBA (mirror of the ES 3.00 shader).
1236
1317
  gl_FragColor = texColor * vColor * uWorldAlpha;
1318
+ // Premultiply the per-particle alpha into RGB: vColor.a is not premultiplied,
1319
+ // so without this ADD ignores alpha entirely, NORMAL only reduces background
1320
+ // show-through, and MULTIPLY brightens the destination as alpha drops.
1321
+ // Mirror of the ES 3.00 shader.
1322
+ gl_FragColor.rgb *= vColor.a;
1237
1323
  }
1238
1324
  `, A = class A {
1239
1325
  constructor(e, t) {
@@ -1353,8 +1439,8 @@ void main() {
1353
1439
  );
1354
1440
  const d = l ? new Uint32Array(e * 6) : new Uint16Array(e * 6);
1355
1441
  for (let i = 0; i < e; i++) {
1356
- const u = i * 4, g = i * 6;
1357
- d[g + 0] = u + 0, d[g + 1] = u + 1, d[g + 2] = u + 2, d[g + 3] = u + 0, d[g + 4] = u + 2, d[g + 5] = u + 3;
1442
+ const u = i * 4, _ = i * 6;
1443
+ d[_ + 0] = u + 0, d[_ + 1] = u + 1, d[_ + 2] = u + 2, d[_ + 3] = u + 0, d[_ + 4] = u + 2, d[_ + 5] = u + 3;
1358
1444
  }
1359
1445
  if (this._indexBuffer = t.createBuffer(), t.bindBuffer(t.ELEMENT_ARRAY_BUFFER, this._indexBuffer), t.bufferData(t.ELEMENT_ARRAY_BUFFER, d, t.STATIC_DRAW), this._isWebGL1) {
1360
1446
  const i = new Float32Array(e * 4);
@@ -1437,29 +1523,29 @@ void main() { gl_FragColor = vC; }
1437
1523
  e.activeTexture(e.TEXTURE0);
1438
1524
  const u = e.getParameter(e.TEXTURE_BINDING_2D);
1439
1525
  e.activeTexture(e.TEXTURE1);
1440
- const g = e.getParameter(e.TEXTURE_BINDING_2D), I = e.isEnabled(e.BLEND), E = e.isEnabled(e.DEPTH_TEST), U = e.isEnabled(e.SCISSOR_TEST);
1441
- let x = null, b = null, B = null, p = null, v = null, T = null, c = -1, w = !1, f = null, h = 4, y = 0, _ = 0, P = 0, D = !1, S = !1;
1442
- const m = new Uint8Array(4);
1526
+ const _ = e.getParameter(e.TEXTURE_BINDING_2D), y = e.isEnabled(e.BLEND), E = e.isEnabled(e.DEPTH_TEST), I = e.isEnabled(e.SCISSOR_TEST);
1527
+ let m = null, w = null, U = null, f = null, v = null, g = null, c = -1, b = !1, x = null, h = 4, B = 0, p = 0, R = 0, D = !1, S = !1;
1528
+ const T = new Uint8Array(4);
1443
1529
  try {
1444
- x = e.createProgram(), e.attachShader(x, this._compileShader(e.VERTEX_SHADER, t)), e.attachShader(x, this._compileShader(e.FRAGMENT_SHADER, a)), e.linkProgram(x);
1445
- const C = (R, K) => {
1530
+ m = e.createProgram(), e.attachShader(m, this._compileShader(e.VERTEX_SHADER, t)), e.attachShader(m, this._compileShader(e.FRAGMENT_SHADER, a)), e.linkProgram(m);
1531
+ const C = (P, F) => {
1446
1532
  const M = e.createTexture();
1447
- return e.bindTexture(e.TEXTURE_2D, M), 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, R, K), M;
1533
+ return e.bindTexture(e.TEXTURE_2D, M), 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, P, F), M;
1448
1534
  };
1449
- e.activeTexture(e.TEXTURE0), b = C(e.FLOAT, new Float32Array([1, 0.5, 0.25, 1])), B = C(e.UNSIGNED_BYTE, new Uint8Array([51, 102, 153, 255])), p = e.createTexture(), e.bindTexture(e.TEXTURE_2D, p), e.texImage2D(e.TEXTURE_2D, 0, e.RGBA, 1, 1, 0, e.RGBA, e.UNSIGNED_BYTE, null), v = e.createFramebuffer(), e.bindFramebuffer(e.FRAMEBUFFER, v), e.framebufferTexture2D(e.FRAMEBUFFER, e.COLOR_ATTACHMENT0, e.TEXTURE_2D, p, 0), T = e.createBuffer(), e.bindBuffer(e.ARRAY_BUFFER, T), e.bufferData(e.ARRAY_BUFFER, new Float32Array([0]), e.STATIC_DRAW), S = e.getProgramParameter(x, e.LINK_STATUS), S && (e.useProgram(x), e.activeTexture(e.TEXTURE0), e.bindTexture(e.TEXTURE_2D, b), e.activeTexture(e.TEXTURE1), e.bindTexture(e.TEXTURE_2D, B), e.uniform1i(e.getUniformLocation(x, "uF"), 0), e.uniform1i(e.getUniformLocation(x, "uB"), 1), c = e.getAttribLocation(x, "aId"), w = e.getVertexAttrib(c, e.VERTEX_ATTRIB_ARRAY_ENABLED), f = e.getVertexAttrib(c, e.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING), h = e.getVertexAttrib(c, e.VERTEX_ATTRIB_ARRAY_SIZE), y = e.getVertexAttrib(c, e.VERTEX_ATTRIB_ARRAY_TYPE), D = e.getVertexAttrib(c, e.VERTEX_ATTRIB_ARRAY_NORMALIZED), _ = e.getVertexAttrib(c, e.VERTEX_ATTRIB_ARRAY_STRIDE), P = e.getVertexAttribOffset(c, e.VERTEX_ATTRIB_ARRAY_POINTER), e.enableVertexAttribArray(c), e.vertexAttribPointer(c, 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, m), S = Math.abs(m[0] - 51) <= 2 && Math.abs(m[1] - 51) <= 2 && Math.abs(m[2] - 38) <= 2);
1535
+ e.activeTexture(e.TEXTURE0), w = C(e.FLOAT, new Float32Array([1, 0.5, 0.25, 1])), U = C(e.UNSIGNED_BYTE, new Uint8Array([51, 102, 153, 255])), f = e.createTexture(), e.bindTexture(e.TEXTURE_2D, f), e.texImage2D(e.TEXTURE_2D, 0, e.RGBA, 1, 1, 0, e.RGBA, e.UNSIGNED_BYTE, null), v = e.createFramebuffer(), e.bindFramebuffer(e.FRAMEBUFFER, v), e.framebufferTexture2D(e.FRAMEBUFFER, e.COLOR_ATTACHMENT0, e.TEXTURE_2D, f, 0), g = e.createBuffer(), e.bindBuffer(e.ARRAY_BUFFER, g), e.bufferData(e.ARRAY_BUFFER, new Float32Array([0]), e.STATIC_DRAW), S = e.getProgramParameter(m, e.LINK_STATUS), S && (e.useProgram(m), e.activeTexture(e.TEXTURE0), e.bindTexture(e.TEXTURE_2D, w), e.activeTexture(e.TEXTURE1), e.bindTexture(e.TEXTURE_2D, U), e.uniform1i(e.getUniformLocation(m, "uF"), 0), e.uniform1i(e.getUniformLocation(m, "uB"), 1), c = e.getAttribLocation(m, "aId"), b = e.getVertexAttrib(c, e.VERTEX_ATTRIB_ARRAY_ENABLED), x = e.getVertexAttrib(c, e.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING), h = e.getVertexAttrib(c, e.VERTEX_ATTRIB_ARRAY_SIZE), B = e.getVertexAttrib(c, e.VERTEX_ATTRIB_ARRAY_TYPE), D = e.getVertexAttrib(c, e.VERTEX_ATTRIB_ARRAY_NORMALIZED), p = e.getVertexAttrib(c, e.VERTEX_ATTRIB_ARRAY_STRIDE), R = e.getVertexAttribOffset(c, e.VERTEX_ATTRIB_ARRAY_POINTER), e.enableVertexAttribArray(c), e.vertexAttribPointer(c, 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, T), S = Math.abs(T[0] - 51) <= 2 && Math.abs(T[1] - 51) <= 2 && Math.abs(T[2] - 38) <= 2);
1450
1536
  } finally {
1451
- c >= 0 && (f && (e.bindBuffer(e.ARRAY_BUFFER, f), e.vertexAttribPointer(
1537
+ c >= 0 && (x && (e.bindBuffer(e.ARRAY_BUFFER, x), e.vertexAttribPointer(
1452
1538
  c,
1453
1539
  h,
1454
- y,
1540
+ B,
1455
1541
  D,
1456
- _,
1457
- P
1458
- )), w ? e.enableVertexAttribArray(c) : e.disableVertexAttribArray(c)), v && e.deleteFramebuffer(v), p && e.deleteTexture(p), b && e.deleteTexture(b), B && e.deleteTexture(B), T && e.deleteBuffer(T), x && e.deleteProgram(x), e.bindFramebuffer(e.FRAMEBUFFER, d), e.bindBuffer(e.ARRAY_BUFFER, l), e.useProgram(o), e.activeTexture(e.TEXTURE0), e.bindTexture(e.TEXTURE_2D, u), e.activeTexture(e.TEXTURE1), e.bindTexture(e.TEXTURE_2D, g), e.activeTexture(i), I && e.enable(e.BLEND), E && e.enable(e.DEPTH_TEST), U && e.enable(e.SCISSOR_TEST), e.viewport(r[0], r[1], r[2], r[3]);
1542
+ p,
1543
+ R
1544
+ )), b ? e.enableVertexAttribArray(c) : e.disableVertexAttribArray(c)), v && e.deleteFramebuffer(v), f && e.deleteTexture(f), w && e.deleteTexture(w), U && e.deleteTexture(U), g && e.deleteBuffer(g), m && e.deleteProgram(m), e.bindFramebuffer(e.FRAMEBUFFER, d), e.bindBuffer(e.ARRAY_BUFFER, l), e.useProgram(o), e.activeTexture(e.TEXTURE0), e.bindTexture(e.TEXTURE_2D, u), e.activeTexture(e.TEXTURE1), e.bindTexture(e.TEXTURE_2D, _), e.activeTexture(i), y && e.enable(e.BLEND), E && e.enable(e.DEPTH_TEST), I && e.enable(e.SCISSOR_TEST), e.viewport(r[0], r[1], r[2], r[3]);
1459
1545
  }
1460
1546
  if (!S)
1461
1547
  throw new Error(
1462
- `NeutrinoParticles (js-v1.1): WebGL1 vertex texture fetch smoke test failed (got ${m[0]},${m[1]},${m[2]}); WebGL2 or WebGL1 with working vertex float textures is required.`
1548
+ `NeutrinoParticles (js-v1.1): WebGL1 vertex texture fetch smoke test failed (got ${T[0]},${T[1]},${T[2]}); WebGL2 or WebGL1 with working vertex float textures is required.`
1463
1549
  );
1464
1550
  }
1465
1551
  _initShader() {
@@ -1812,6 +1898,9 @@ class DrawBatch {
1812
1898
  class NeutrinoRenderer {
1813
1899
  constructor() {
1814
1900
  n(this, "_orthoMatrix", new Float32Array(16));
1901
+ n(this, "_perspMatrix", new Float32Array(16));
1902
+ n(this, "_vpMatrix", new Float32Array(16));
1903
+ n(this, "_projFrame", { x: 0, y: 0, width: 0, height: 0 });
1815
1904
  n(this, "_modelMatrix", new Float32Array(16));
1816
1905
  n(this, "_texSlotMap", /* @__PURE__ */ new Map());
1817
1906
  n(this, "_batchPool");
@@ -1822,63 +1911,67 @@ class NeutrinoRenderer {
1822
1911
  }
1823
1912
  render(e, t, a, r, o, l, d) {
1824
1913
  if (l === 0) return;
1825
- const i = e.gl, u = e.isWebGL1, g = e.maxBatchTextures, I = d.renderStyles, E = d.materials, U = u ? t.meta : null;
1826
- U && U.build(r, l), this._batchCount = 0;
1827
- const x = this._texSlotMap;
1914
+ const i = e.gl, u = e.isWebGL1, _ = e.maxBatchTextures, y = d.renderStyles, E = d.materials, I = u ? t.meta : null;
1915
+ I && I.build(r, l), this._batchCount = 0;
1916
+ const m = this._texSlotMap;
1828
1917
  for (; this._batchPool.length < o.length; )
1829
1918
  this._batchPool.push(new DrawBatch());
1830
- let b = 0;
1831
- for (; b < o.length; ) {
1832
- const p = this._batchPool[this._batchCount];
1833
- p.reset(), p.blendMode = this._resolveBlend(o[b].blendMode, E), p.startParticle = o[b].startParticleIndex, x.clear();
1834
- let v = b;
1835
- for (; v < o.length && this._resolveBlend(o[v].blendMode, E) === p.blendMode; ) {
1836
- const T = I[o[v].renderStyleIndex].textureIndices[0];
1837
- if (!x.has(T)) {
1838
- if (x.size >= g) break;
1839
- const f = x.size;
1840
- x.set(T, f), p.glTextures[f] = d.glTextures[T] || null, p.remaps[f] = d.remaps[T] || null, p.numTextures = f + 1;
1919
+ let w = 0;
1920
+ for (; w < o.length; ) {
1921
+ const f = this._batchPool[this._batchCount];
1922
+ f.reset(), f.blendMode = this._resolveBlend(o[w].blendMode, E), f.startParticle = o[w].startParticleIndex, m.clear();
1923
+ let v = w;
1924
+ for (; v < o.length && this._resolveBlend(o[v].blendMode, E) === f.blendMode; ) {
1925
+ const g = y[o[v].renderStyleIndex].textureIndices[0];
1926
+ if (!m.has(g)) {
1927
+ if (m.size >= _) break;
1928
+ const x = m.size;
1929
+ m.set(g, x), f.glTextures[x] = d.glTextures[g] || null, f.remaps[x] = d.remaps[g] || null, f.numTextures = x + 1;
1841
1930
  }
1842
- const c = x.get(T), w = o[v];
1843
- if (U)
1844
- U.patchSlot(w.startParticleIndex, w.numParticles, c);
1845
- else
1846
- for (let f = 0; f < w.numParticles; f++) {
1847
- const h = (w.startParticleIndex + f) * BYTES_PER_PARTICLE, y = a.getUint32(h, !0);
1848
- a.setUint32(h, y & 4294967040 | c, !0);
1931
+ const c = m.get(g), b = o[v];
1932
+ if (I)
1933
+ I.patchSlot(b.startParticleIndex, b.numParticles, c);
1934
+ else if (c !== 0)
1935
+ for (let x = 0; x < b.numParticles; x++) {
1936
+ const h = (b.startParticleIndex + x) * BYTES_PER_PARTICLE, B = a.getUint32(h, !0);
1937
+ a.setUint32(h, B & 4294967040 | c, !0);
1849
1938
  }
1850
- p.numParticles += w.numParticles, v++;
1939
+ f.numParticles += b.numParticles, v++;
1851
1940
  }
1852
- this._batchCount++, b = v;
1941
+ this._batchCount++, w = v;
1853
1942
  }
1854
- const B = i.isEnabled(i.SCISSOR_TEST);
1943
+ const U = i.isEnabled(i.SCISSOR_TEST);
1855
1944
  try {
1856
1945
  if (i.disable(i.DEPTH_TEST), i.disable(i.STENCIL_TEST), i.disable(i.SCISSOR_TEST), i.disable(i.CULL_FACE), i.depthMask(!1), i.colorMask(!0, !0, !0, !0), i.useProgram(e.shaderProgram), u) {
1857
- const _ = t;
1858
- _.upload(l, DATA_TEXTURE_UNIT, META_TEXTURE_UNIT), i.uniform1i(e.uDataTexture, DATA_TEXTURE_UNIT), i.uniform1i(e.uMetaTexture, META_TEXTURE_UNIT), i.uniform2f(e.uDataTexSize, _.textureWidth, _.textureHeight);
1946
+ const p = t;
1947
+ p.upload(l, DATA_TEXTURE_UNIT, META_TEXTURE_UNIT), i.uniform1i(e.uDataTexture, DATA_TEXTURE_UNIT), i.uniform1i(e.uMetaTexture, META_TEXTURE_UNIT), i.uniform2f(e.uDataTexSize, p.textureWidth, p.textureHeight);
1859
1948
  } else {
1860
- const _ = t;
1861
- _.uploadAndBind(DATA_TEXTURE_UNIT), i.uniform1i(e.uDataTexture, DATA_TEXTURE_UNIT), i.uniform1i(e.uDataTextureWidth, _.textureWidth);
1949
+ const p = t;
1950
+ p.uploadAndBind(DATA_TEXTURE_UNIT), i.uniform1i(e.uDataTexture, DATA_TEXTURE_UNIT), i.uniform1i(e.uDataTextureWidth, p.textureWidth);
1862
1951
  }
1863
1952
  i.uniform3f(e.uCameraRight, 1, 0, 0), i.uniform3f(e.uCameraUp, 0, -1, 0), i.uniform3f(e.uCameraDir, 0, 0, -1);
1864
- const p = d.viewportWidth, v = d.viewportHeight, T = d.zoom || 1, c = this._orthoMatrix;
1865
- c.fill(0), c[0] = 2 / p * T, c[5] = -2 / v * T, c[10] = 1, c[15] = 1, c[12] = -1 - 2 / p * T * d.scrollX, c[13] = 1 + 2 / v * T * d.scrollY, i.uniformMatrix4fv(e.uViewProjMatrix, !1, c);
1866
- const w = v > 0 ? p / v : 1;
1867
- e.uViewportAspect && i.uniform1f(e.uViewportAspect, w), e.uWorldAlpha && i.uniform1f(e.uWorldAlpha, d.worldAlpha);
1868
- const f = d.model, h = this._modelMatrix;
1869
- h[0] = f.a, h[1] = f.b, h[2] = 0, h[3] = 0, h[4] = f.c, h[5] = f.d, h[6] = 0, h[7] = 0, h[8] = 0, h[9] = 0, h[10] = 1, h[11] = 0, h[12] = f.tx, h[13] = f.ty, h[14] = 0, h[15] = 1, i.uniformMatrix4fv(e.uModelMatrix, !1, h), u ? (e.unbindVao(), i.bindBuffer(i.ARRAY_BUFFER, e.aIdBuffer), i.enableVertexAttribArray(e.aIdLocation), i.vertexAttribPointer(e.aIdLocation, 1, i.FLOAT, !1, 0, 0), i.bindBuffer(i.ELEMENT_ARRAY_BUFFER, e.indexBuffer)) : i.bindVertexArray(e.vao);
1870
- const y = u ? PARTICLE_TEXTURE_START_UNIT_GL1 : PARTICLE_TEXTURE_START_UNIT;
1953
+ const f = d.viewportWidth, v = d.viewportHeight, g = d.zoom || 1, c = this._orthoMatrix;
1954
+ if (c.fill(0), c[0] = 2 / f * g, c[5] = -2 / v * g, c[10] = 0, c[15] = 1, c[12] = -1 - 2 / f * g * d.scrollX, c[13] = 1 + 2 / v * g * d.scrollY, d.projection) {
1955
+ const p = this._projFrame;
1956
+ p.x = 0, p.y = 0, p.width = f, p.height = v, d.projection.setScreenFrame(p), d.projection.writeMatrix(this._perspMatrix), this._mulMatrix(this._vpMatrix, c, this._perspMatrix), i.uniformMatrix4fv(e.uViewProjMatrix, !1, this._vpMatrix);
1957
+ } else
1958
+ i.uniformMatrix4fv(e.uViewProjMatrix, !1, c);
1959
+ const b = v > 0 ? f / v : 1;
1960
+ e.uViewportAspect && i.uniform1f(e.uViewportAspect, b), e.uWorldAlpha && i.uniform1f(e.uWorldAlpha, d.worldAlpha);
1961
+ const x = d.model, h = this._modelMatrix;
1962
+ h[0] = x.a, h[1] = x.b, h[2] = 0, h[3] = 0, h[4] = x.c, h[5] = x.d, h[6] = 0, h[7] = 0, h[8] = 0, h[9] = 0, h[10] = 1, h[11] = 0, h[12] = x.tx, h[13] = x.ty, h[14] = 0, h[15] = 1, i.uniformMatrix4fv(e.uModelMatrix, !1, h), u ? (e.unbindVao(), i.bindBuffer(i.ARRAY_BUFFER, e.aIdBuffer), i.enableVertexAttribArray(e.aIdLocation), i.vertexAttribPointer(e.aIdLocation, 1, i.FLOAT, !1, 0, 0), i.bindBuffer(i.ELEMENT_ARRAY_BUFFER, e.indexBuffer)) : i.bindVertexArray(e.vao);
1963
+ const B = u ? PARTICLE_TEXTURE_START_UNIT_GL1 : PARTICLE_TEXTURE_START_UNIT;
1871
1964
  i.enable(i.BLEND);
1872
- for (let _ = 0; _ < this._batchCount; _++) {
1873
- const P = this._batchPool[_];
1874
- this._applyBlendMode(i, P.blendMode);
1875
- for (let m = 0; m < P.numTextures; m++) {
1876
- const C = y + m;
1877
- i.activeTexture(i.TEXTURE0 + C), i.bindTexture(i.TEXTURE_2D, P.glTextures[m]), i.uniform1i(e.uTextures[m], C);
1878
- const R = P.remaps[m];
1879
- R ? i.uniform4f(e.uTexRemaps[m], R.x, R.y, R.width, R.height) : i.uniform4f(e.uTexRemaps[m], 0, 0, 1, 1);
1965
+ for (let p = 0; p < this._batchCount; p++) {
1966
+ const R = this._batchPool[p];
1967
+ this._applyBlendMode(i, R.blendMode);
1968
+ for (let T = 0; T < R.numTextures; T++) {
1969
+ const C = B + T;
1970
+ i.activeTexture(i.TEXTURE0 + C), i.bindTexture(i.TEXTURE_2D, R.glTextures[T]), i.uniform1i(e.uTextures[T], C);
1971
+ const P = R.remaps[T];
1972
+ P ? i.uniform4f(e.uTexRemaps[T], P.x, P.y, P.width, P.height) : i.uniform4f(e.uTexRemaps[T], 0, 0, 1, 1);
1880
1973
  }
1881
- const D = P.startParticle * 6, S = P.numParticles * 6;
1974
+ const D = R.startParticle * 6, S = R.numParticles * 6;
1882
1975
  i.drawElements(
1883
1976
  i.TRIANGLES,
1884
1977
  S,
@@ -1887,7 +1980,7 @@ class NeutrinoRenderer {
1887
1980
  );
1888
1981
  }
1889
1982
  } finally {
1890
- u ? (i.disableVertexAttribArray(e.aIdLocation), i.bindBuffer(i.ARRAY_BUFFER, null), i.bindBuffer(i.ELEMENT_ARRAY_BUFFER, null)) : i.bindVertexArray(null), i.useProgram(null), B && i.enable(i.SCISSOR_TEST);
1983
+ u ? (i.disableVertexAttribArray(e.aIdLocation), i.bindBuffer(i.ARRAY_BUFFER, null), i.bindBuffer(i.ELEMENT_ARRAY_BUFFER, null)) : i.bindVertexArray(null), i.useProgram(null), U && i.enable(i.SCISSOR_TEST);
1891
1984
  }
1892
1985
  }
1893
1986
  // Resolve a renderStyle's compacted material INDEX to the real blend enum (0=Normal/
@@ -1896,6 +1989,13 @@ class NeutrinoRenderer {
1896
1989
  // materials[], NOT the blend itself — without this deref a single-material effect
1897
1990
  // (materialIndex 0) would always render as materials[0]'s slot 0 (Normal). Falls back to
1898
1991
  // the raw value if materials is missing/out of range (mirror of cocos _resolveBlendMode).
1992
+ // Column-major 4x4 multiply: out = a * b. Preallocated outputs only.
1993
+ _mulMatrix(e, t, a) {
1994
+ for (let r = 0; r < 16; r += 4) {
1995
+ const o = a[r], l = a[r + 1], d = a[r + 2], i = a[r + 3];
1996
+ e[r] = t[0] * o + t[4] * l + t[8] * d + t[12] * i, e[r + 1] = t[1] * o + t[5] * l + t[9] * d + t[13] * i, e[r + 2] = t[2] * o + t[6] * l + t[10] * d + t[14] * i, e[r + 3] = t[3] * o + t[7] * l + t[11] * d + t[15] * i;
1997
+ }
1998
+ }
1899
1999
  _resolveBlend(e, t) {
1900
2000
  return t && e >= 0 && e < t.length ? t[e] : e;
1901
2001
  }
@@ -1941,6 +2041,8 @@ class Effect extends Phaser.GameObjects.GameObject {
1941
2041
  n(this, "alpha", 1);
1942
2042
  n(this, "depth", 0);
1943
2043
  n(this, "blendMode", Phaser.BlendModes.NORMAL);
2044
+ /** Optional v1.0-parity perspective projection (Projection3D). */
2045
+ n(this, "projection");
1944
2046
  n(this, "_dataTextureManager", null);
1945
2047
  n(this, "_worldPosition");
1946
2048
  n(this, "_worldScaledPosition");
@@ -1950,7 +2052,7 @@ class Effect extends Phaser.GameObjects.GameObject {
1950
2052
  n(this, "_tempMatrix2");
1951
2053
  n(this, "_unpauseOnUpdateRender");
1952
2054
  n(this, "_glTexturesScratch", []);
1953
- this.gamePlugin = a.plugins.get("neutrino"), this.effectModel = t, this.x = r.position[0], this.y = r.position[1], this.z = r.position[2], this.angle = r.angle, this.scaleX = r.scale, this.scaleY = r.scale, this._worldPosition = new Phaser.Math.Vector2(), this._worldScaledPosition = [0, 0, 0], this._worldScale = 1, this._tempMatrix1 = new Phaser.GameObjects.Components.TransformMatrix(), this._tempMatrix2 = new Phaser.GameObjects.Components.TransformMatrix(), this._unpauseOnUpdateRender = r.pause === Pause.BEFORE_UPDATE_OR_RENDER, this._updateWorldTransform();
2055
+ this.gamePlugin = a.plugins.get("neutrino"), this.effectModel = t, this.x = r.position[0], this.y = r.position[1], this.z = r.position[2], this.angle = r.angle, this.scaleX = r.scale, this.scaleY = r.scale, this.projection = r.projection, this._worldPosition = new Phaser.Math.Vector2(), this._worldScaledPosition = [0, 0, 0], this._worldScale = 1, this._tempMatrix1 = new Phaser.GameObjects.Components.TransformMatrix(), this._tempMatrix2 = new Phaser.GameObjects.Components.TransformMatrix(), this._unpauseOnUpdateRender = r.pause === Pause.BEFORE_UPDATE_OR_RENDER, this._updateWorldTransform();
1954
2056
  const o = {
1955
2057
  paused: r.pause !== Pause.NO,
1956
2058
  generatorsPaused: r.generatorsPaused
@@ -1985,11 +2087,11 @@ class Effect extends Phaser.GameObjects.GameObject {
1985
2087
  const d = this.effect.renderInstructions, i = d._totalParticles;
1986
2088
  if (i === 0) return;
1987
2089
  t.flush();
1988
- const u = this.effectModel.effectModel.textures.length, g = this._glTexturesScratch;
1989
- g.length = u;
2090
+ const u = this.effectModel.effectModel.textures.length, _ = this._glTexturesScratch;
2091
+ _.length = u;
1990
2092
  for (let E = 0; E < u; ++E)
1991
- g[E] = this.effectModel.glTexture(E);
1992
- const I = this._worldScale;
2093
+ _[E] = this.effectModel.glTexture(E);
2094
+ const y = this._worldScale;
1993
2095
  try {
1994
2096
  sharedRenderer.render(
1995
2097
  o,
@@ -1999,14 +2101,15 @@ class Effect extends Phaser.GameObjects.GameObject {
1999
2101
  d,
2000
2102
  i,
2001
2103
  {
2002
- model: { a: I, b: 0, c: 0, d: I, tx: 0, ty: 0 },
2104
+ model: { a: y, b: 0, c: 0, d: y, tx: 0, ty: 0 },
2003
2105
  viewportWidth: t.width,
2004
2106
  viewportHeight: t.height,
2005
2107
  scrollX: r.scrollX * this.scrollFactorX,
2006
2108
  scrollY: r.scrollY * this.scrollFactorY,
2007
2109
  zoom: r.zoom,
2008
2110
  worldAlpha: this.alpha,
2009
- glTextures: g,
2111
+ projection: this.projection,
2112
+ glTextures: _,
2010
2113
  remaps: this.effectModel.texturesRemap,
2011
2114
  renderStyles: this.effect.model.renderStyles,
2012
2115
  materials: this.effect.model.materials
@@ -2043,6 +2146,19 @@ class Effect extends Phaser.GameObjects.GameObject {
2043
2146
  setPropertyInAllEmitters(t, a) {
2044
2147
  this.ready && this.effect.setPropertyInAllEmitters(t, a);
2045
2148
  }
2149
+ setPropertyInEmitter(t, a, r) {
2150
+ this.ready && this.effect.setPropertyInEmitter(t, a, r);
2151
+ }
2152
+ getEmitterPropertyValue(t, a) {
2153
+ if (this.ready)
2154
+ return a === void 0 ? this.effect.getEmitterPropertyValue(t) : this.effect.getEmitterPropertyValue(t, a);
2155
+ }
2156
+ hasEmitterProperty(t, a) {
2157
+ return this.ready ? a === void 0 ? this.effect.hasEmitterProperty(t) : this.effect.hasEmitterProperty(t, a) : !1;
2158
+ }
2159
+ getEmitterProperties() {
2160
+ return this.ready ? this.effect.getEmitterProperties() : [];
2161
+ }
2046
2162
  getNumParticles() {
2047
2163
  return this.ready ? this.effect.getNumParticles() : 0;
2048
2164
  }
@@ -2146,6 +2262,19 @@ class PerspectiveProjection {
2146
2262
  _getScale(e) {
2147
2263
  return this._z / (this._z - e[2]);
2148
2264
  }
2265
+ /**
2266
+ * Writes the projection as a 4x4 column-major matrix operating in screen
2267
+ * space, for the GPU render path. Homogeneous equivalent of
2268
+ * transformPosition(): clip.xy = p.xy - center * (p.z / z),
2269
+ * clip.w = 1 - p.z / z; the z row is zeroed (no depth on this path).
2270
+ * setScreenFrame() must have been called before.
2271
+ *
2272
+ * @param {Float32Array} out 16-element column-major output matrix.
2273
+ */
2274
+ writeMatrix(e) {
2275
+ const t = this._z, a = Number.isFinite(t) && t > 0 ? 1 / t : 0;
2276
+ e.fill(0), e[0] = 1, e[5] = 1, e[8] = -this._screenPosX * a, e[9] = -this._screenPosY * a, e[11] = -a, e[15] = 1;
2277
+ }
2149
2278
  }
2150
2279
  let _installed = !1;
2151
2280
  function installPlugin() {