@newkrok/three-particles 2.12.0 → 2.14.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/dist/index.js CHANGED
@@ -174,6 +174,7 @@ var RendererType = /* @__PURE__ */ ((RendererType2) => {
174
174
  RendererType2["POINTS"] = "POINTS";
175
175
  RendererType2["INSTANCED"] = "INSTANCED";
176
176
  RendererType2["TRAIL"] = "TRAIL";
177
+ RendererType2["MESH"] = "MESH";
177
178
  return RendererType2;
178
179
  })(RendererType || {});
179
180
  var ForceFieldFalloff = /* @__PURE__ */ ((ForceFieldFalloff2) => {
@@ -310,6 +311,24 @@ var calculateRandomPositionAndVelocityOnRectangle = (position, quaternion, veloc
310
311
  velocity.set(0, 0, speed);
311
312
  velocity.applyQuaternion(quaternion);
312
313
  };
314
+ var createDefaultMeshTexture = () => {
315
+ try {
316
+ const canvas = document.createElement("canvas");
317
+ canvas.width = 1;
318
+ canvas.height = 1;
319
+ const context = canvas.getContext("2d");
320
+ if (context) {
321
+ context.fillStyle = "white";
322
+ context.fillRect(0, 0, 1, 1);
323
+ const texture = new THREE4.CanvasTexture(canvas);
324
+ texture.needsUpdate = true;
325
+ return texture;
326
+ }
327
+ return null;
328
+ } catch {
329
+ return null;
330
+ }
331
+ };
313
332
  var createDefaultParticleTexture = () => {
314
333
  try {
315
334
  const canvas = document.createElement("canvas");
@@ -494,6 +513,16 @@ var applyModifiers = ({
494
513
  positionArr[positionIndex + 2] += noiseOnPosition * noisePower * positionAmount;
495
514
  attributes.position.needsUpdate = true;
496
515
  }
516
+ if (attributes.quat) {
517
+ const rotZ = attributes.rotation.array[particleIndex];
518
+ const halfZ = rotZ * 0.5;
519
+ const qi = particleIndex * 4;
520
+ attributes.quat.array[qi] = 0;
521
+ attributes.quat.array[qi + 1] = 0;
522
+ attributes.quat.array[qi + 2] = Math.sin(halfZ);
523
+ attributes.quat.array[qi + 3] = Math.cos(halfZ);
524
+ attributes.quat.needsUpdate = true;
525
+ }
497
526
  };
498
527
 
499
528
  // src/js/effects/three-particles/shaders/instanced-particle-fragment-shader.glsl.ts
@@ -506,6 +535,10 @@ var InstancedParticleFragmentShader = `
506
535
  uniform bool discardBackgroundColor;
507
536
  uniform vec3 backgroundColor;
508
537
  uniform float backgroundColorTolerance;
538
+ uniform bool softParticlesEnabled;
539
+ uniform float softParticlesIntensity;
540
+ uniform sampler2D sceneDepthTexture;
541
+ uniform vec2 cameraNearFar;
509
542
 
510
543
  varying vec2 vUv;
511
544
  varying vec4 vColor;
@@ -513,10 +546,16 @@ var InstancedParticleFragmentShader = `
513
546
  varying float vStartLifetime;
514
547
  varying float vStartFrame;
515
548
  varying float vRotation;
549
+ varying float vViewZ;
516
550
 
517
551
  #include <common>
518
552
  #include <logdepthbuf_pars_fragment>
519
553
 
554
+ float linearizeDepth(float depthSample, float near, float far) {
555
+ float z_ndc = 2.0 * depthSample - 1.0;
556
+ return 2.0 * near * far / (far + near - z_ndc * (far - near));
557
+ }
558
+
520
559
  void main()
521
560
  {
522
561
  gl_FragColor = vColor;
@@ -559,6 +598,16 @@ var InstancedParticleFragmentShader = `
559
598
 
560
599
  if (discardBackgroundColor && abs(length(rotatedTexture.rgb - backgroundColor.rgb)) < backgroundColorTolerance) discard;
561
600
 
601
+ if (softParticlesEnabled) {
602
+ vec2 screenUV = gl_FragCoord.xy / vec2(textureSize(sceneDepthTexture, 0));
603
+ float sceneDepthSample = texture2D(sceneDepthTexture, screenUV).r;
604
+ float sceneDepthLinear = linearizeDepth(sceneDepthSample, cameraNearFar.x, cameraNearFar.y);
605
+ float depthDiff = sceneDepthLinear - vViewZ;
606
+ float softFade = smoothstep(0.0, softParticlesIntensity, depthDiff);
607
+ gl_FragColor.a *= softFade;
608
+ if (gl_FragColor.a < 0.001) discard;
609
+ }
610
+
562
611
  #include <logdepthbuf_fragment>
563
612
  }
564
613
  `;
@@ -585,6 +634,7 @@ var InstancedParticleVertexShader = `
585
634
  varying float vStartLifetime;
586
635
  varying float vStartFrame;
587
636
  varying float vRotation;
637
+ varying float vViewZ;
588
638
 
589
639
  #include <common>
590
640
  #include <logdepthbuf_pars_vertex>
@@ -619,6 +669,7 @@ var InstancedParticleVertexShader = `
619
669
  // identical to the Points renderer).
620
670
  mvPosition.xy += position.xy * perspectiveSize;
621
671
 
672
+ vViewZ = -mvPosition.z;
622
673
  gl_Position = projectionMatrix * mvPosition;
623
674
 
624
675
  // Pass UV for texture sampling (quad ranges from -0.5..0.5, map to 0..1).
@@ -630,6 +681,151 @@ var InstancedParticleVertexShader = `
630
681
  `;
631
682
  var instanced_particle_vertex_shader_glsl_default = InstancedParticleVertexShader;
632
683
 
684
+ // src/js/effects/three-particles/shaders/mesh-particle-fragment-shader.glsl.ts
685
+ var MeshParticleFragmentShader = `
686
+ uniform sampler2D map;
687
+ uniform float elapsed;
688
+ uniform float fps;
689
+ uniform bool useFPSForFrameIndex;
690
+ uniform vec2 tiles;
691
+ uniform bool discardBackgroundColor;
692
+ uniform vec3 backgroundColor;
693
+ uniform float backgroundColorTolerance;
694
+ uniform bool softParticlesEnabled;
695
+ uniform float softParticlesIntensity;
696
+ uniform sampler2D sceneDepthTexture;
697
+ uniform vec2 cameraNearFar;
698
+
699
+ varying vec4 vColor;
700
+ varying float vLifetime;
701
+ varying float vStartLifetime;
702
+ varying float vStartFrame;
703
+ varying float vRotation;
704
+ varying vec3 vNormal;
705
+ varying vec2 vUv;
706
+ varying float vViewZ;
707
+
708
+ #include <common>
709
+ #include <logdepthbuf_pars_fragment>
710
+
711
+ float linearizeDepth(float depthSample, float near, float far) {
712
+ float z_ndc = 2.0 * depthSample - 1.0;
713
+ return 2.0 * near * far / (far + near - z_ndc * (far - near));
714
+ }
715
+
716
+ void main()
717
+ {
718
+ gl_FragColor = vColor;
719
+
720
+ // Use mesh UVs directly for texture sampling
721
+ vec2 uvPoint = vUv;
722
+
723
+ // Apply texture sheet animation if tiles > 1x1
724
+ if (tiles.x > 1.0 || tiles.y > 1.0) {
725
+ float frameIndex = round(vStartFrame) + (
726
+ useFPSForFrameIndex == true
727
+ ? fps == 0.0
728
+ ? 0.0
729
+ : max((vLifetime / 1000.0) * fps, 0.0)
730
+ : max(min(floor(min(vLifetime / vStartLifetime, 1.0) * (tiles.x * tiles.y)), tiles.x * tiles.y - 1.0), 0.0)
731
+ );
732
+
733
+ float spriteXIndex = floor(mod(frameIndex, tiles.x));
734
+ float spriteYIndex = floor(mod(frameIndex / tiles.x, tiles.y));
735
+
736
+ uvPoint = vec2(
737
+ vUv.x / tiles.x + spriteXIndex / tiles.x,
738
+ vUv.y / tiles.y + spriteYIndex / tiles.y
739
+ );
740
+ }
741
+
742
+ vec4 texColor = texture2D(map, uvPoint);
743
+ gl_FragColor = gl_FragColor * texColor;
744
+
745
+ if (discardBackgroundColor && abs(length(texColor.rgb - backgroundColor.rgb)) < backgroundColorTolerance) discard;
746
+
747
+ // Simple directional lighting from camera direction
748
+ float lightIntensity = 0.5 + 0.5 * max(dot(vNormal, vec3(0.0, 0.0, 1.0)), 0.0);
749
+ gl_FragColor.rgb *= lightIntensity;
750
+
751
+ if (softParticlesEnabled) {
752
+ vec2 screenUV = gl_FragCoord.xy / vec2(textureSize(sceneDepthTexture, 0));
753
+ float sceneDepthSample = texture2D(sceneDepthTexture, screenUV).r;
754
+ float sceneDepthLinear = linearizeDepth(sceneDepthSample, cameraNearFar.x, cameraNearFar.y);
755
+ float depthDiff = sceneDepthLinear - vViewZ;
756
+ float softFade = smoothstep(0.0, softParticlesIntensity, depthDiff);
757
+ gl_FragColor.a *= softFade;
758
+ if (gl_FragColor.a < 0.001) discard;
759
+ }
760
+
761
+ #include <logdepthbuf_fragment>
762
+ }
763
+ `;
764
+ var mesh_particle_fragment_shader_glsl_default = MeshParticleFragmentShader;
765
+
766
+ // src/js/effects/three-particles/shaders/mesh-particle-vertex-shader.glsl.ts
767
+ var MeshParticleVertexShader = `
768
+ attribute float instanceSize;
769
+ attribute float instanceColorR;
770
+ attribute float instanceColorG;
771
+ attribute float instanceColorB;
772
+ attribute float instanceColorA;
773
+ attribute float instanceLifetime;
774
+ attribute float instanceStartLifetime;
775
+ attribute float instanceRotation;
776
+ attribute float instanceStartFrame;
777
+ attribute vec3 instanceOffset;
778
+ attribute vec4 instanceQuat;
779
+
780
+ varying vec4 vColor;
781
+ varying float vLifetime;
782
+ varying float vStartLifetime;
783
+ varying float vStartFrame;
784
+ varying float vRotation;
785
+ varying vec3 vNormal;
786
+ varying vec2 vUv;
787
+ varying float vViewZ;
788
+
789
+ #include <common>
790
+ #include <logdepthbuf_pars_vertex>
791
+
792
+ vec3 applyQuaternion(vec3 v, vec4 q) {
793
+ vec3 t = 2.0 * cross(q.xyz, v);
794
+ return v + q.w * t + cross(q.xyz, t);
795
+ }
796
+
797
+ void main()
798
+ {
799
+ vColor = vec4(instanceColorR, instanceColorG, instanceColorB, instanceColorA);
800
+ vLifetime = instanceLifetime;
801
+ vStartLifetime = instanceStartLifetime;
802
+ vStartFrame = instanceStartFrame;
803
+ vRotation = instanceRotation;
804
+
805
+ // Apply quaternion rotation to the mesh vertex position
806
+ vec3 rotatedPosition = applyQuaternion(position, instanceQuat);
807
+
808
+ // Scale mesh by particle size
809
+ vec3 scaledPosition = rotatedPosition * instanceSize;
810
+
811
+ // Apply instance offset (particle world position)
812
+ vec3 worldPos = scaledPosition + instanceOffset;
813
+
814
+ vec4 mvPosition = modelViewMatrix * vec4(worldPos, 1.0);
815
+ vViewZ = -mvPosition.z;
816
+ gl_Position = projectionMatrix * mvPosition;
817
+
818
+ // Transform normal by quaternion for lighting
819
+ vNormal = normalize((modelViewMatrix * vec4(applyQuaternion(normal, instanceQuat), 0.0)).xyz);
820
+
821
+ // Pass through UVs from the mesh geometry
822
+ vUv = uv;
823
+
824
+ #include <logdepthbuf_vertex>
825
+ }
826
+ `;
827
+ var mesh_particle_vertex_shader_glsl_default = MeshParticleVertexShader;
828
+
633
829
  // src/js/effects/three-particles/shaders/particle-system-fragment-shader.glsl.ts
634
830
  var ParticleSystemFragmentShader = `
635
831
  uniform sampler2D map;
@@ -640,16 +836,26 @@ var ParticleSystemFragmentShader = `
640
836
  uniform bool discardBackgroundColor;
641
837
  uniform vec3 backgroundColor;
642
838
  uniform float backgroundColorTolerance;
839
+ uniform bool softParticlesEnabled;
840
+ uniform float softParticlesIntensity;
841
+ uniform sampler2D sceneDepthTexture;
842
+ uniform vec2 cameraNearFar;
643
843
 
644
844
  varying vec4 vColor;
645
845
  varying float vLifetime;
646
846
  varying float vStartLifetime;
647
847
  varying float vRotation;
648
848
  varying float vStartFrame;
849
+ varying float vViewZ;
649
850
 
650
851
  #include <common>
651
852
  #include <logdepthbuf_pars_fragment>
652
853
 
854
+ float linearizeDepth(float depthSample, float near, float far) {
855
+ float z_ndc = 2.0 * depthSample - 1.0;
856
+ return 2.0 * near * far / (far + near - z_ndc * (far - near));
857
+ }
858
+
653
859
  void main()
654
860
  {
655
861
  gl_FragColor = vColor;
@@ -697,7 +903,17 @@ var ParticleSystemFragmentShader = `
697
903
  gl_FragColor = gl_FragColor * rotatedTexture;
698
904
 
699
905
  if (discardBackgroundColor && abs(length(rotatedTexture.rgb - backgroundColor.rgb)) < backgroundColorTolerance) discard;
700
-
906
+
907
+ if (softParticlesEnabled) {
908
+ vec2 screenUV = gl_FragCoord.xy / vec2(textureSize(sceneDepthTexture, 0));
909
+ float sceneDepthSample = texture2D(sceneDepthTexture, screenUV).r;
910
+ float sceneDepthLinear = linearizeDepth(sceneDepthSample, cameraNearFar.x, cameraNearFar.y);
911
+ float depthDiff = sceneDepthLinear - vViewZ;
912
+ float softFade = smoothstep(0.0, softParticlesIntensity, depthDiff);
913
+ gl_FragColor.a *= softFade;
914
+ if (gl_FragColor.a < 0.001) discard;
915
+ }
916
+
701
917
  #include <logdepthbuf_fragment>
702
918
  }
703
919
  `;
@@ -721,6 +937,7 @@ var ParticleSystemVertexShader = `
721
937
  varying float vStartLifetime;
722
938
  varying float vRotation;
723
939
  varying float vStartFrame;
940
+ varying float vViewZ;
724
941
 
725
942
  #include <common>
726
943
  #include <logdepthbuf_pars_vertex>
@@ -735,6 +952,7 @@ var ParticleSystemVertexShader = `
735
952
 
736
953
  vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
737
954
  gl_PointSize = size * (100.0 / length(mvPosition.xyz));
955
+ vViewZ = -mvPosition.z;
738
956
  gl_Position = projectionMatrix * mvPosition;
739
957
 
740
958
  #include <logdepthbuf_vertex>
@@ -749,14 +967,24 @@ var TrailFragmentShader = `
749
967
  uniform bool discardBackgroundColor;
750
968
  uniform vec3 backgroundColor;
751
969
  uniform float backgroundColorTolerance;
970
+ uniform bool softParticlesEnabled;
971
+ uniform float softParticlesIntensity;
972
+ uniform sampler2D sceneDepthTexture;
973
+ uniform vec2 cameraNearFar;
752
974
 
753
975
  varying float vAlpha;
754
976
  varying vec4 vColor;
755
977
  varying vec2 vUv;
978
+ varying float vViewZ;
756
979
 
757
980
  #include <common>
758
981
  #include <logdepthbuf_pars_fragment>
759
982
 
983
+ float linearizeDepth(float depthSample, float near, float far) {
984
+ float z_ndc = 2.0 * depthSample - 1.0;
985
+ return 2.0 * near * far / (far + near - z_ndc * (far - near));
986
+ }
987
+
760
988
  void main()
761
989
  {
762
990
  // Soft edge: always fade near ribbon edges
@@ -777,6 +1005,16 @@ var TrailFragmentShader = `
777
1005
 
778
1006
  if (gl_FragColor.a < 0.001) discard;
779
1007
 
1008
+ if (softParticlesEnabled) {
1009
+ vec2 screenUV = gl_FragCoord.xy / vec2(textureSize(sceneDepthTexture, 0));
1010
+ float sceneDepthSample = texture2D(sceneDepthTexture, screenUV).r;
1011
+ float sceneDepthLinear = linearizeDepth(sceneDepthSample, cameraNearFar.x, cameraNearFar.y);
1012
+ float depthDiff = sceneDepthLinear - vViewZ;
1013
+ float softFade = smoothstep(0.0, softParticlesIntensity, depthDiff);
1014
+ gl_FragColor.a *= softFade;
1015
+ if (gl_FragColor.a < 0.001) discard;
1016
+ }
1017
+
780
1018
  if (discardBackgroundColor && abs(length(gl_FragColor.rgb - backgroundColor.rgb)) < backgroundColorTolerance) discard;
781
1019
 
782
1020
  #include <logdepthbuf_fragment>
@@ -796,6 +1034,7 @@ var TrailVertexShader = `
796
1034
  varying float vAlpha;
797
1035
  varying vec4 vColor;
798
1036
  varying vec2 vUv;
1037
+ varying float vViewZ;
799
1038
 
800
1039
  #include <common>
801
1040
  #include <logdepthbuf_pars_vertex>
@@ -822,28 +1061,27 @@ var TrailVertexShader = `
822
1061
  float perpLen = length(perp);
823
1062
 
824
1063
  // When tangent is nearly parallel to view direction, the cross product
825
- // collapses and the ribbon becomes edge-on (invisible). Use a secondary
826
- // perpendicular from cross(tangent, up) and blend it in to guarantee
827
- // a minimum visible width.
828
- vec3 upPerp = cross(tangent, vec3(0.0, 1.0, 0.0));
829
- float upPerpLen = length(upPerp);
830
- if (upPerpLen < 0.0001) {
831
- upPerp = cross(tangent, vec3(1.0, 0.0, 0.0));
832
- upPerpLen = length(upPerp);
833
- }
834
- upPerp = upPerp / max(upPerpLen, 0.0001);
1064
+ // collapses and the ribbon becomes edge-on (invisible). Build a stable
1065
+ // fallback perpendicular from the camera's right axis \u2014 this keeps the
1066
+ // ribbon in screen-space and prevents it from flipping into an arbitrary
1067
+ // plane when viewed edge-on.
1068
+ vec3 camRight = vec3(viewMatrix[0][0], viewMatrix[1][0], viewMatrix[2][0]);
1069
+ vec3 fallbackPerp = normalize(camRight - tangent * dot(camRight, tangent));
835
1070
 
836
1071
  if (perpLen < 0.0001) {
837
- perp = upPerp;
1072
+ perp = fallbackPerp;
838
1073
  } else {
839
1074
  perp = perp / perpLen;
840
- // Blend in the secondary perp when billboard perp gets weak
841
- float blendFactor = smoothstep(0.0, 0.5, perpLen);
842
- perp = normalize(mix(upPerp, perp, blendFactor));
1075
+ // Smoothly blend toward the fallback when the billboard perp weakens.
1076
+ // The wide range (0..0.7) ensures a gradual transition so the ribbon
1077
+ // does not snap abruptly when rotating toward edge-on.
1078
+ float blendFactor = smoothstep(0.0, 0.7, perpLen);
1079
+ perp = normalize(mix(fallbackPerp, perp, blendFactor));
843
1080
  }
844
1081
 
845
1082
  vec3 offsetPos = position + perp * trailOffset * trailHalfWidth;
846
1083
  vec4 mvPosition = modelViewMatrix * vec4(offsetPos, 1.0);
1084
+ vViewZ = -mvPosition.z;
847
1085
  gl_Position = projectionMatrix * mvPosition;
848
1086
 
849
1087
  #include <logdepthbuf_vertex>
@@ -1016,7 +1254,11 @@ var DEFAULT_PARTICLE_SYSTEM_CONFIG = {
1016
1254
  backgroundColor: { r: 1, g: 1, b: 1 },
1017
1255
  transparent: true,
1018
1256
  depthTest: true,
1019
- depthWrite: false
1257
+ depthWrite: false,
1258
+ softParticles: {
1259
+ enabled: false,
1260
+ intensity: 1
1261
+ }
1020
1262
  },
1021
1263
  velocityOverLifetime: {
1022
1264
  isActive: false,
@@ -1239,7 +1481,7 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
1239
1481
  config,
1240
1482
  { applyToFirstObject: false, skippedProperties: [] }
1241
1483
  );
1242
- let particleMap = normalizedConfig.map || createDefaultParticleTexture();
1484
+ let particleMap = normalizedConfig.map || (normalizedConfig.renderer.rendererType === "MESH" /* MESH */ ? createDefaultMeshTexture() : createDefaultParticleTexture());
1243
1485
  const {
1244
1486
  transform,
1245
1487
  duration,
@@ -1424,7 +1666,9 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
1424
1666
  }));
1425
1667
  }
1426
1668
  const useTrail = renderer.rendererType === "TRAIL" /* TRAIL */;
1427
- const useInstancing = !useTrail && renderer.rendererType === "INSTANCED" /* INSTANCED */;
1669
+ const useMesh = renderer.rendererType === "MESH" /* MESH */;
1670
+ const useInstancing = !useTrail && !useMesh && renderer.rendererType === "INSTANCED" /* INSTANCED */;
1671
+ const useInstancedAttributes = useInstancing || useMesh;
1428
1672
  const defaultTrailCurve = {
1429
1673
  type: "BEZIER" /* BEZIER */,
1430
1674
  scale: 1,
@@ -1444,7 +1688,13 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
1444
1688
  renderer.trail?.opacityOverTrail,
1445
1689
  defaultTrailCurve
1446
1690
  ),
1447
- colorOverTrail: renderer.trail?.colorOverTrail
1691
+ colorOverTrail: renderer.trail?.colorOverTrail,
1692
+ minVertexDistance: renderer.trail?.minVertexDistance ?? 0,
1693
+ maxTime: renderer.trail?.maxTime ?? 0,
1694
+ smoothing: renderer.trail?.smoothing ?? false,
1695
+ smoothingSubdivisions: renderer.trail?.smoothingSubdivisions ?? 3,
1696
+ twistPrevention: renderer.trail?.twistPrevention ?? false,
1697
+ ribbonId: renderer.trail?.ribbonId
1448
1698
  } : void 0;
1449
1699
  if (useTrail && trailConfig) {
1450
1700
  const trailLength = trailConfig.length;
@@ -1454,9 +1704,21 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
1454
1704
  );
1455
1705
  generalData.positionHistoryIndex = new Uint16Array(maxParticles);
1456
1706
  generalData.positionHistoryCount = new Uint16Array(maxParticles);
1707
+ if (trailConfig.minVertexDistance > 0) {
1708
+ generalData.trailLastSampledPosition = new Float32Array(maxParticles * 3);
1709
+ }
1710
+ if (trailConfig.maxTime > 0) {
1711
+ generalData.trailSampleTimes = new Float64Array(
1712
+ maxParticles * trailLength
1713
+ );
1714
+ }
1715
+ if (trailConfig.twistPrevention) {
1716
+ generalData.trailPrevNormal = new Float32Array(maxParticles * 3);
1717
+ }
1457
1718
  }
1458
- const attr = (name) => useInstancing ? `instance${name.charAt(0).toUpperCase()}${name.slice(1)}` : name;
1459
- const posAttr = useInstancing ? "instanceOffset" : "position";
1719
+ const attr = (name) => useInstancedAttributes ? `instance${name.charAt(0).toUpperCase()}${name.slice(1)}` : name;
1720
+ const posAttr = useInstancedAttributes ? "instanceOffset" : "position";
1721
+ const softParticlesEnabled = !!(renderer.softParticles?.enabled && renderer.softParticles?.depthTexture);
1460
1722
  const sharedUniforms = {
1461
1723
  elapsed: { value: 0 },
1462
1724
  map: { value: particleMap },
@@ -1468,19 +1730,56 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
1468
1730
  backgroundColor: { value: renderer.backgroundColor },
1469
1731
  discardBackgroundColor: { value: renderer.discardBackgroundColor },
1470
1732
  backgroundColorTolerance: { value: renderer.backgroundColorTolerance },
1471
- ...useInstancing ? { viewportHeight: { value: 1 } } : {}
1733
+ ...useInstancing ? { viewportHeight: { value: 1 } } : {},
1734
+ softParticlesEnabled: { value: softParticlesEnabled },
1735
+ softParticlesIntensity: {
1736
+ value: Math.max(renderer.softParticles?.intensity ?? 1, 1e-3)
1737
+ },
1738
+ sceneDepthTexture: {
1739
+ value: renderer.softParticles?.depthTexture ?? null
1740
+ },
1741
+ cameraNearFar: { value: new THREE4.Vector2(0.1, 1e3) }
1742
+ };
1743
+ const getVertexShader = () => {
1744
+ if (useMesh) return mesh_particle_vertex_shader_glsl_default;
1745
+ if (useInstancing) return instanced_particle_vertex_shader_glsl_default;
1746
+ return particle_system_vertex_shader_glsl_default;
1747
+ };
1748
+ const getFragmentShader = () => {
1749
+ if (useMesh) return mesh_particle_fragment_shader_glsl_default;
1750
+ if (useInstancing) return instanced_particle_fragment_shader_glsl_default;
1751
+ return particle_system_fragment_shader_glsl_default;
1472
1752
  };
1473
1753
  const material = new THREE4.ShaderMaterial({
1474
1754
  uniforms: sharedUniforms,
1475
- vertexShader: useInstancing ? instanced_particle_vertex_shader_glsl_default : particle_system_vertex_shader_glsl_default,
1476
- fragmentShader: useInstancing ? instanced_particle_fragment_shader_glsl_default : particle_system_fragment_shader_glsl_default,
1755
+ vertexShader: getVertexShader(),
1756
+ fragmentShader: getFragmentShader(),
1477
1757
  transparent: renderer.transparent,
1478
1758
  blending: renderer.blending,
1479
1759
  depthTest: renderer.depthTest,
1480
1760
  depthWrite: renderer.depthWrite
1481
1761
  });
1482
1762
  let geometry;
1483
- if (useInstancing) {
1763
+ if (useMesh) {
1764
+ const meshConfig = renderer.mesh;
1765
+ if (!meshConfig?.geometry) {
1766
+ throw new Error(
1767
+ "RendererType.MESH requires a mesh configuration with a geometry. Set renderer.mesh.geometry to a THREE.BufferGeometry instance."
1768
+ );
1769
+ }
1770
+ const instancedGeometry = new THREE4.InstancedBufferGeometry();
1771
+ const sourceGeom = meshConfig.geometry;
1772
+ const srcPos = sourceGeom.getAttribute("position");
1773
+ if (srcPos) instancedGeometry.setAttribute("position", srcPos);
1774
+ const srcNormal = sourceGeom.getAttribute("normal");
1775
+ if (srcNormal) instancedGeometry.setAttribute("normal", srcNormal);
1776
+ const srcUv = sourceGeom.getAttribute("uv");
1777
+ if (srcUv) instancedGeometry.setAttribute("uv", srcUv);
1778
+ const srcIndex = sourceGeom.getIndex();
1779
+ if (srcIndex) instancedGeometry.setIndex(srcIndex);
1780
+ instancedGeometry.instanceCount = maxParticles;
1781
+ geometry = instancedGeometry;
1782
+ } else if (useInstancing) {
1484
1783
  const instancedGeometry = new THREE4.InstancedBufferGeometry();
1485
1784
  const quadPositions = new Float32Array([
1486
1785
  -0.5,
@@ -1521,28 +1820,28 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
1521
1820
  positionArray[i * 3 + 1] = startPositions[i].y;
1522
1821
  positionArray[i * 3 + 2] = startPositions[i].z;
1523
1822
  }
1524
- const positionAttribute = useInstancing ? new THREE4.InstancedBufferAttribute(positionArray, 3) : new THREE4.BufferAttribute(positionArray, 3);
1823
+ const positionAttribute = useInstancedAttributes ? new THREE4.InstancedBufferAttribute(positionArray, 3) : new THREE4.BufferAttribute(positionArray, 3);
1525
1824
  geometry.setAttribute(posAttr, positionAttribute);
1526
1825
  createFloat32Attributes({
1527
1826
  geometry,
1528
1827
  propertyName: attr("isActive"),
1529
1828
  maxParticles,
1530
1829
  factory: 0,
1531
- instanced: useInstancing
1830
+ instanced: useInstancedAttributes
1532
1831
  });
1533
1832
  createFloat32Attributes({
1534
1833
  geometry,
1535
1834
  propertyName: attr("lifetime"),
1536
1835
  maxParticles,
1537
1836
  factory: 0,
1538
- instanced: useInstancing
1837
+ instanced: useInstancedAttributes
1539
1838
  });
1540
1839
  createFloat32Attributes({
1541
1840
  geometry,
1542
1841
  propertyName: attr("startLifetime"),
1543
1842
  maxParticles,
1544
1843
  factory: () => calculateValue(generalData.particleSystemId, startLifetime, 0) * 1e3,
1545
- instanced: useInstancing
1844
+ instanced: useInstancedAttributes
1546
1845
  });
1547
1846
  createFloat32Attributes({
1548
1847
  geometry,
@@ -1553,21 +1852,21 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
1553
1852
  textureSheetAnimation.startFrame,
1554
1853
  0
1555
1854
  ) : 0,
1556
- instanced: useInstancing
1855
+ instanced: useInstancedAttributes
1557
1856
  });
1558
1857
  createFloat32Attributes({
1559
1858
  geometry,
1560
1859
  propertyName: attr("size"),
1561
1860
  maxParticles,
1562
1861
  factory: (_, index) => generalData.startValues.startSize[index],
1563
- instanced: useInstancing
1862
+ instanced: useInstancedAttributes
1564
1863
  });
1565
1864
  createFloat32Attributes({
1566
1865
  geometry,
1567
1866
  propertyName: attr("rotation"),
1568
1867
  maxParticles,
1569
1868
  factory: 0,
1570
- instanced: useInstancing
1869
+ instanced: useInstancedAttributes
1571
1870
  });
1572
1871
  const colorRandomRatio = Math.random();
1573
1872
  createFloat32Attributes({
@@ -1575,29 +1874,39 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
1575
1874
  propertyName: attr("colorR"),
1576
1875
  maxParticles,
1577
1876
  factory: () => startColor.min.r + colorRandomRatio * (startColor.max.r - startColor.min.r),
1578
- instanced: useInstancing
1877
+ instanced: useInstancedAttributes
1579
1878
  });
1580
1879
  createFloat32Attributes({
1581
1880
  geometry,
1582
1881
  propertyName: attr("colorG"),
1583
1882
  maxParticles,
1584
1883
  factory: () => startColor.min.g + colorRandomRatio * (startColor.max.g - startColor.min.g),
1585
- instanced: useInstancing
1884
+ instanced: useInstancedAttributes
1586
1885
  });
1587
1886
  createFloat32Attributes({
1588
1887
  geometry,
1589
1888
  propertyName: attr("colorB"),
1590
1889
  maxParticles,
1591
1890
  factory: () => startColor.min.b + colorRandomRatio * (startColor.max.b - startColor.min.b),
1592
- instanced: useInstancing
1891
+ instanced: useInstancedAttributes
1593
1892
  });
1594
1893
  createFloat32Attributes({
1595
1894
  geometry,
1596
1895
  propertyName: attr("colorA"),
1597
1896
  maxParticles,
1598
1897
  factory: 0,
1599
- instanced: useInstancing
1898
+ instanced: useInstancedAttributes
1600
1899
  });
1900
+ if (useMesh) {
1901
+ const quatArray = new Float32Array(maxParticles * 4);
1902
+ for (let i = 0; i < maxParticles; i++) {
1903
+ quatArray[i * 4 + 3] = 1;
1904
+ }
1905
+ geometry.setAttribute(
1906
+ attr("quat"),
1907
+ new THREE4.InstancedBufferAttribute(quatArray, 4)
1908
+ );
1909
+ }
1601
1910
  const a = geometry.attributes;
1602
1911
  const aIsActive = a[attr("isActive")];
1603
1912
  const aColorR = a[attr("colorR")];
@@ -1610,6 +1919,7 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
1610
1919
  const aRotation = a[attr("rotation")];
1611
1920
  const aLifetime = a[attr("lifetime")];
1612
1921
  const aPosition = a[posAttr];
1922
+ const aQuat = useMesh ? a[attr("quat")] : void 0;
1613
1923
  const deactivateParticle = (particleIndex) => {
1614
1924
  aIsActive.array[particleIndex] = 0;
1615
1925
  aColorA.array[particleIndex] = 0;
@@ -1626,6 +1936,18 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
1626
1936
  if (generalData.positionHistoryCount) {
1627
1937
  generalData.positionHistoryCount[particleIndex] = 0;
1628
1938
  generalData.positionHistoryIndex[particleIndex] = 0;
1939
+ if (generalData.trailLastSampledPosition) {
1940
+ const lsIdx = particleIndex * 3;
1941
+ generalData.trailLastSampledPosition[lsIdx] = 0;
1942
+ generalData.trailLastSampledPosition[lsIdx + 1] = 0;
1943
+ generalData.trailLastSampledPosition[lsIdx + 2] = 0;
1944
+ }
1945
+ if (generalData.trailPrevNormal) {
1946
+ const nIdx = particleIndex * 3;
1947
+ generalData.trailPrevNormal[nIdx] = 0;
1948
+ generalData.trailPrevNormal[nIdx + 1] = 0;
1949
+ generalData.trailPrevNormal[nIdx + 2] = 0;
1950
+ }
1629
1951
  }
1630
1952
  if (generalData.noise.offsets)
1631
1953
  generalData.noise.offsets[particleIndex] = Math.random() * 100;
@@ -1671,6 +1993,16 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
1671
1993
  generalData.normalizedLifetimePercentage
1672
1994
  );
1673
1995
  aRotation.needsUpdate = true;
1996
+ if (aQuat) {
1997
+ const rotZ = aRotation.array[particleIndex];
1998
+ const halfZ = rotZ * 0.5;
1999
+ const qi = particleIndex * 4;
2000
+ aQuat.array[qi] = 0;
2001
+ aQuat.array[qi + 1] = 0;
2002
+ aQuat.array[qi + 2] = Math.sin(halfZ);
2003
+ aQuat.array[qi + 3] = Math.cos(halfZ);
2004
+ aQuat.needsUpdate = true;
2005
+ }
1674
2006
  if (normalizedConfig.rotationOverLifetime.isActive)
1675
2007
  generalData.lifetimeValues.rotationOverLifetime[particleIndex] = THREE4.MathUtils.randFloat(
1676
2008
  normalizedConfig.rotationOverLifetime.min,
@@ -1796,7 +2128,7 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
1796
2128
  },
1797
2129
  renderer: {
1798
2130
  ...subConfig.config.renderer ?? {},
1799
- rendererType: renderer.rendererType
2131
+ ...subConfig.config.renderer?.rendererType ? {} : renderer.rendererType === "MESH" /* MESH */ || renderer.rendererType === "TRAIL" /* TRAIL */ ? {} : { rendererType: renderer.rendererType }
1800
2132
  },
1801
2133
  ...inheritVelocity > 0 ? {
1802
2134
  startSpeed: (typeof subConfig.config.startSpeed === "number" ? subConfig.config.startSpeed : typeof subConfig.config.startSpeed === "object" && subConfig.config.startSpeed !== null && "min" in subConfig.config.startSpeed ? subConfig.config.startSpeed.min ?? 0 : 0) + velocity.length() * inheritVelocity
@@ -1881,7 +2213,15 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
1881
2213
  useMap: { value: !!particleMap },
1882
2214
  discardBackgroundColor: { value: renderer.discardBackgroundColor },
1883
2215
  backgroundColor: { value: renderer.backgroundColor },
1884
- backgroundColorTolerance: { value: renderer.backgroundColorTolerance }
2216
+ backgroundColorTolerance: { value: renderer.backgroundColorTolerance },
2217
+ softParticlesEnabled: { value: softParticlesEnabled },
2218
+ softParticlesIntensity: {
2219
+ value: Math.max(renderer.softParticles?.intensity ?? 1, 1e-3)
2220
+ },
2221
+ sceneDepthTexture: {
2222
+ value: renderer.softParticles?.depthTexture ?? null
2223
+ },
2224
+ cameraNearFar: { value: new THREE4.Vector2(0.1, 1e3) }
1885
2225
  },
1886
2226
  vertexShader: trail_vertex_shader_glsl_default,
1887
2227
  fragmentShader: trail_fragment_shader_glsl_default,
@@ -1896,6 +2236,14 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
1896
2236
  const trailCameraPos = new THREE4.Vector3();
1897
2237
  trailMesh.onBeforeRender = (_renderer, _scene, camera) => {
1898
2238
  camera.getWorldPosition(trailCameraPos);
2239
+ if (softParticlesEnabled && camera.isPerspectiveCamera) {
2240
+ const perspCam = camera;
2241
+ const trailUniforms = trailMaterial.uniforms;
2242
+ trailUniforms.cameraNearFar.value.set(
2243
+ perspCam.near,
2244
+ perspCam.far
2245
+ );
2246
+ }
1899
2247
  };
1900
2248
  generalData.trailCameraPosition = trailCameraPos;
1901
2249
  trailWidthCurveFn = getCurveFunctionFromConfig(
@@ -1923,11 +2271,20 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
1923
2271
  };
1924
2272
  }
1925
2273
  }
1926
- let particleSystem = useInstancing ? new THREE4.Mesh(geometry, material) : new THREE4.Points(geometry, material);
1927
- if (useInstancing) {
1928
- particleSystem.onBeforeRender = (glRenderer) => {
1929
- const size = glRenderer.getSize(new THREE4.Vector2());
1930
- sharedUniforms.viewportHeight.value = size.y * glRenderer.getPixelRatio();
2274
+ let particleSystem = useInstancing || useMesh ? new THREE4.Mesh(geometry, material) : new THREE4.Points(geometry, material);
2275
+ if (useInstancing || softParticlesEnabled) {
2276
+ particleSystem.onBeforeRender = (glRenderer, _scene, camera) => {
2277
+ if (useInstancing) {
2278
+ const size = glRenderer.getSize(new THREE4.Vector2());
2279
+ sharedUniforms.viewportHeight.value = size.y * glRenderer.getPixelRatio();
2280
+ }
2281
+ if (softParticlesEnabled && camera.isPerspectiveCamera) {
2282
+ const perspCam = camera;
2283
+ sharedUniforms.cameraNearFar.value.set(
2284
+ perspCam.near,
2285
+ perspCam.far
2286
+ );
2287
+ }
1931
2288
  };
1932
2289
  }
1933
2290
  if (useTrail && trailMesh) {
@@ -1950,7 +2307,8 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
1950
2307
  colorR: aColorR,
1951
2308
  colorG: aColorG,
1952
2309
  colorB: aColorB,
1953
- colorA: aColorA
2310
+ colorA: aColorA,
2311
+ ...useMesh ? { quat: aQuat } : {}
1954
2312
  };
1955
2313
  const calculatedCreationTime = now + calculateValue(generalData.particleSystemId, startDelay) * 1e3;
1956
2314
  let wrapper;
@@ -2022,7 +2380,13 @@ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow
2022
2380
  trailColorOverTrailFns,
2023
2381
  trailConfig: {
2024
2382
  length: trailConfig.length,
2025
- width: trailConfig.width
2383
+ width: trailConfig.width,
2384
+ minVertexDistance: trailConfig.minVertexDistance,
2385
+ maxTime: trailConfig.maxTime,
2386
+ smoothing: trailConfig.smoothing,
2387
+ smoothingSubdivisions: trailConfig.smoothingSubdivisions,
2388
+ twistPrevention: trailConfig.twistPrevention,
2389
+ ribbonId: trailConfig.ribbonId
2026
2390
  }
2027
2391
  } : {}
2028
2392
  };
@@ -2284,10 +2648,84 @@ var updateParticleSystemInstance = (props, { now, delta, elapsed }) => {
2284
2648
  particleSystem
2285
2649
  });
2286
2650
  if (props.trailMesh) {
2287
- updateTrailGeometry(props);
2651
+ updateTrailGeometry(props, now);
2288
2652
  }
2289
2653
  };
2290
- var updateTrailGeometry = (props) => {
2654
+ var catmullRom = (out, outIdx, p0x, p0y, p0z, p1x, p1y, p1z, p2x, p2y, p2z, p3x, p3y, p3z, t) => {
2655
+ const t2 = t * t;
2656
+ const t3 = t2 * t;
2657
+ out[outIdx] = 0.5 * (2 * p1x + (-p0x + p2x) * t + (2 * p0x - 5 * p1x + 4 * p2x - p3x) * t2 + (-p0x + 3 * p1x - 3 * p2x + p3x) * t3);
2658
+ out[outIdx + 1] = 0.5 * (2 * p1y + (-p0y + p2y) * t + (2 * p0y - 5 * p1y + 4 * p2y - p3y) * t2 + (-p0y + 3 * p1y - 3 * p2y + p3y) * t3);
2659
+ out[outIdx + 2] = 0.5 * (2 * p1z + (-p0z + p2z) * t + (2 * p0z - 5 * p1z + 4 * p2z - p3z) * t2 + (-p0z + 3 * p1z - 3 * p2z + p3z) * t3);
2660
+ };
2661
+ var clearTrailVertex = (vIdx, cIdx, aIdx, uvIdx, trailPosArr, trailNextArr, trailHalfWidthArr, trailUVArr, trailAlphaArr, trailColorArr, fallbackX, fallbackY, fallbackZ) => {
2662
+ trailPosArr[vIdx] = fallbackX;
2663
+ trailPosArr[vIdx + 1] = fallbackY;
2664
+ trailPosArr[vIdx + 2] = fallbackZ;
2665
+ trailPosArr[vIdx + 3] = fallbackX;
2666
+ trailPosArr[vIdx + 4] = fallbackY;
2667
+ trailPosArr[vIdx + 5] = fallbackZ;
2668
+ trailNextArr[vIdx] = fallbackX;
2669
+ trailNextArr[vIdx + 1] = fallbackY;
2670
+ trailNextArr[vIdx + 2] = fallbackZ;
2671
+ trailNextArr[vIdx + 3] = fallbackX;
2672
+ trailNextArr[vIdx + 4] = fallbackY;
2673
+ trailNextArr[vIdx + 5] = fallbackZ;
2674
+ trailHalfWidthArr[aIdx] = 0;
2675
+ trailHalfWidthArr[aIdx + 1] = 0;
2676
+ trailUVArr[uvIdx] = 0;
2677
+ trailUVArr[uvIdx + 1] = 0;
2678
+ trailUVArr[uvIdx + 2] = 0;
2679
+ trailUVArr[uvIdx + 3] = 0;
2680
+ trailAlphaArr[aIdx] = 0;
2681
+ trailAlphaArr[aIdx + 1] = 0;
2682
+ trailColorArr[cIdx] = 0;
2683
+ trailColorArr[cIdx + 1] = 0;
2684
+ trailColorArr[cIdx + 2] = 0;
2685
+ trailColorArr[cIdx + 3] = 0;
2686
+ trailColorArr[cIdx + 4] = 0;
2687
+ trailColorArr[cIdx + 5] = 0;
2688
+ trailColorArr[cIdx + 6] = 0;
2689
+ trailColorArr[cIdx + 7] = 0;
2690
+ };
2691
+ var writeTrailVertex = (vIdx, cIdx, aIdx, uvIdx, hx, hy, hz, nx, ny, nz, halfWidth, t, alpha, fr, fg, fb, ca, trailPosArr, trailNextArr, trailHalfWidthArr, trailUVArr, trailAlphaArr, trailColorArr) => {
2692
+ trailPosArr[vIdx] = hx;
2693
+ trailPosArr[vIdx + 1] = hy;
2694
+ trailPosArr[vIdx + 2] = hz;
2695
+ trailPosArr[vIdx + 3] = hx;
2696
+ trailPosArr[vIdx + 4] = hy;
2697
+ trailPosArr[vIdx + 5] = hz;
2698
+ trailNextArr[vIdx] = nx;
2699
+ trailNextArr[vIdx + 1] = ny;
2700
+ trailNextArr[vIdx + 2] = nz;
2701
+ trailNextArr[vIdx + 3] = nx;
2702
+ trailNextArr[vIdx + 4] = ny;
2703
+ trailNextArr[vIdx + 5] = nz;
2704
+ trailHalfWidthArr[aIdx] = halfWidth;
2705
+ trailHalfWidthArr[aIdx + 1] = halfWidth;
2706
+ trailUVArr[uvIdx] = 0;
2707
+ trailUVArr[uvIdx + 1] = t;
2708
+ trailUVArr[uvIdx + 2] = 1;
2709
+ trailUVArr[uvIdx + 3] = t;
2710
+ trailAlphaArr[aIdx] = alpha;
2711
+ trailAlphaArr[aIdx + 1] = alpha;
2712
+ trailColorArr[cIdx] = fr;
2713
+ trailColorArr[cIdx + 1] = fg;
2714
+ trailColorArr[cIdx + 2] = fb;
2715
+ trailColorArr[cIdx + 3] = ca;
2716
+ trailColorArr[cIdx + 4] = fr;
2717
+ trailColorArr[cIdx + 5] = fg;
2718
+ trailColorArr[cIdx + 6] = fb;
2719
+ trailColorArr[cIdx + 7] = ca;
2720
+ };
2721
+ var _rawPoints = null;
2722
+ var _rawPointsSize = 0;
2723
+ var _smoothedPoints = null;
2724
+ var _smoothedPointsSize = 0;
2725
+ var _ribbonIndices = null;
2726
+ var _ribbonIndicesSize = 0;
2727
+ var _ribbonCount = 0;
2728
+ var updateTrailGeometry = (props, now) => {
2291
2729
  const {
2292
2730
  generalData,
2293
2731
  trailPositionAttr,
@@ -2305,13 +2743,23 @@ var updateTrailGeometry = (props) => {
2305
2743
  const positionHistory = generalData.positionHistory;
2306
2744
  const historyIndex = generalData.positionHistoryIndex;
2307
2745
  const historyCount = generalData.positionHistoryCount;
2746
+ const sampleTimes = generalData.trailSampleTimes;
2747
+ const lastSampledPos = generalData.trailLastSampledPosition;
2748
+ const prevNormal = generalData.trailPrevNormal;
2749
+ const minVertexDist = trailConfig.minVertexDistance;
2750
+ const minVertexDistSq = minVertexDist * minVertexDist;
2751
+ const maxTime = trailConfig.maxTime;
2752
+ const maxTimeMs = maxTime * 1e3;
2753
+ const useSmoothing = trailConfig.smoothing;
2754
+ const subdivisions = trailConfig.smoothingSubdivisions;
2755
+ const useTwistPrevention = trailConfig.twistPrevention;
2756
+ const ribbonId = trailConfig.ribbonId;
2308
2757
  const isActiveArr = ma.isActive.array;
2309
2758
  const positionArr = ma.position.array;
2310
2759
  const colorRArr = ma.colorR.array;
2311
2760
  const colorGArr = ma.colorG.array;
2312
2761
  const colorBArr = ma.colorB.array;
2313
2762
  const colorAArr = ma.colorA.array;
2314
- ma.size.array;
2315
2763
  const trailPosArr = trailPositionAttr.array;
2316
2764
  const trailAlphaArr = trailAlphaAttr.array;
2317
2765
  const trailColorArr = trailColorAttr.array;
@@ -2322,115 +2770,329 @@ var updateTrailGeometry = (props) => {
2322
2770
  const verticesPerParticle = trailLength * 2;
2323
2771
  const creationTimesLength = generalData.creationTimes.length;
2324
2772
  let hasUpdates = false;
2773
+ const useRibbon = ribbonId !== void 0;
2774
+ let ribbonLeader = -1;
2775
+ if (useRibbon) {
2776
+ if (!_ribbonIndices || _ribbonIndicesSize < creationTimesLength) {
2777
+ _ribbonIndices = new Uint16Array(creationTimesLength);
2778
+ _ribbonIndicesSize = creationTimesLength;
2779
+ }
2780
+ _ribbonCount = 0;
2781
+ for (let i = 0; i < creationTimesLength; i++) {
2782
+ if (isActiveArr[i]) _ribbonIndices[_ribbonCount++] = i;
2783
+ }
2784
+ for (let i = 1; i < _ribbonCount; i++) {
2785
+ const key = _ribbonIndices[i];
2786
+ const keyTime = generalData.creationTimes[key];
2787
+ let j = i - 1;
2788
+ while (j >= 0 && generalData.creationTimes[_ribbonIndices[j]] > keyTime) {
2789
+ _ribbonIndices[j + 1] = _ribbonIndices[j];
2790
+ j--;
2791
+ }
2792
+ _ribbonIndices[j + 1] = key;
2793
+ }
2794
+ if (_ribbonCount > 0) ribbonLeader = _ribbonIndices[0];
2795
+ }
2325
2796
  for (let index = 0; index < creationTimesLength; index++) {
2326
2797
  const vertBase = index * verticesPerParticle;
2327
2798
  if (isActiveArr[index]) {
2799
+ if (useRibbon && _ribbonCount >= 2 && index !== ribbonLeader) {
2800
+ const posIdx2 = index * 3;
2801
+ const px2 = positionArr[posIdx2];
2802
+ const py2 = positionArr[posIdx2 + 1];
2803
+ const pz2 = positionArr[posIdx2 + 2];
2804
+ const histBase = (index * trailLength + historyIndex[index]) * 3;
2805
+ positionHistory[histBase] = px2;
2806
+ positionHistory[histBase + 1] = py2;
2807
+ positionHistory[histBase + 2] = pz2;
2808
+ if (sampleTimes) {
2809
+ sampleTimes[index * trailLength + historyIndex[index]] = now;
2810
+ }
2811
+ historyIndex[index] = (historyIndex[index] + 1) % trailLength;
2812
+ if (historyCount[index] < trailLength) historyCount[index]++;
2813
+ continue;
2814
+ }
2328
2815
  hasUpdates = true;
2329
2816
  const posIdx = index * 3;
2330
2817
  const px = positionArr[posIdx];
2331
2818
  const py = positionArr[posIdx + 1];
2332
2819
  const pz = positionArr[posIdx + 2];
2333
- const histBase = (index * trailLength + historyIndex[index]) * 3;
2334
- positionHistory[histBase] = px;
2335
- positionHistory[histBase + 1] = py;
2336
- positionHistory[histBase + 2] = pz;
2337
- historyIndex[index] = (historyIndex[index] + 1) % trailLength;
2338
- if (historyCount[index] < trailLength) historyCount[index]++;
2339
- const count = historyCount[index];
2820
+ let shouldSample = true;
2821
+ if (minVertexDist > 0 && lastSampledPos && historyCount[index] > 0) {
2822
+ const lsIdx = index * 3;
2823
+ const dx = px - lastSampledPos[lsIdx];
2824
+ const dy = py - lastSampledPos[lsIdx + 1];
2825
+ const dz = pz - lastSampledPos[lsIdx + 2];
2826
+ if (dx * dx + dy * dy + dz * dz < minVertexDistSq) {
2827
+ shouldSample = false;
2828
+ }
2829
+ }
2830
+ if (shouldSample) {
2831
+ const histBase = (index * trailLength + historyIndex[index]) * 3;
2832
+ positionHistory[histBase] = px;
2833
+ positionHistory[histBase + 1] = py;
2834
+ positionHistory[histBase + 2] = pz;
2835
+ if (sampleTimes) {
2836
+ sampleTimes[index * trailLength + historyIndex[index]] = now;
2837
+ }
2838
+ historyIndex[index] = (historyIndex[index] + 1) % trailLength;
2839
+ if (historyCount[index] < trailLength) historyCount[index]++;
2840
+ if (lastSampledPos) {
2841
+ const lsIdx = index * 3;
2842
+ lastSampledPos[lsIdx] = px;
2843
+ lastSampledPos[lsIdx + 1] = py;
2844
+ lastSampledPos[lsIdx + 2] = pz;
2845
+ }
2846
+ }
2847
+ let rawCount = historyCount[index];
2848
+ let effectiveCount = rawCount;
2849
+ if (maxTime > 0 && sampleTimes && rawCount > 0) {
2850
+ const sampleBase = index * trailLength;
2851
+ effectiveCount = 0;
2852
+ for (let s = 0; s < rawCount; s++) {
2853
+ const sampleSlot = (historyIndex[index] - 1 - s + trailLength * 2) % trailLength;
2854
+ const age = now - sampleTimes[sampleBase + sampleSlot];
2855
+ if (age <= maxTimeMs) {
2856
+ effectiveCount++;
2857
+ } else {
2858
+ break;
2859
+ }
2860
+ }
2861
+ }
2862
+ const count = effectiveCount;
2340
2863
  const ribbonWidth = trailConfig.width;
2341
2864
  const cr = colorRArr[index];
2342
2865
  const cg = colorGArr[index];
2343
2866
  const cb = colorBArr[index];
2344
2867
  const ca = colorAArr[index];
2345
2868
  const ringOff = index * trailLength * 3;
2869
+ const rawPtsSize = count * 3;
2870
+ if (!_rawPoints || _rawPointsSize < rawPtsSize) {
2871
+ _rawPoints = new Float32Array(rawPtsSize);
2872
+ _rawPointsSize = rawPtsSize;
2873
+ }
2874
+ const rawPts = _rawPoints;
2875
+ for (let s = 0; s < count; s++) {
2876
+ const histSlot = (historyIndex[index] - 1 - s + trailLength * 2) % trailLength * 3 + ringOff;
2877
+ rawPts[s * 3] = positionHistory[histSlot];
2878
+ rawPts[s * 3 + 1] = positionHistory[histSlot + 1];
2879
+ rawPts[s * 3 + 2] = positionHistory[histSlot + 2];
2880
+ }
2881
+ let finalPts;
2882
+ let finalCount;
2883
+ if (useSmoothing && count >= 3) {
2884
+ const segmentCount = count - 1;
2885
+ finalCount = segmentCount * subdivisions + 1;
2886
+ const neededSize = finalCount * 3;
2887
+ if (!_smoothedPoints || _smoothedPointsSize < neededSize) {
2888
+ _smoothedPoints = new Float32Array(neededSize);
2889
+ _smoothedPointsSize = neededSize;
2890
+ }
2891
+ finalPts = _smoothedPoints;
2892
+ for (let seg = 0; seg < segmentCount; seg++) {
2893
+ const i0 = Math.max(0, seg - 1);
2894
+ const i1 = seg;
2895
+ const i2 = Math.min(count - 1, seg + 1);
2896
+ const i3 = Math.min(count - 1, seg + 2);
2897
+ const p0x = rawPts[i0 * 3], p0y = rawPts[i0 * 3 + 1], p0z = rawPts[i0 * 3 + 2];
2898
+ const p1x = rawPts[i1 * 3], p1y = rawPts[i1 * 3 + 1], p1z = rawPts[i1 * 3 + 2];
2899
+ const p2x = rawPts[i2 * 3], p2y = rawPts[i2 * 3 + 1], p2z = rawPts[i2 * 3 + 2];
2900
+ const p3x = rawPts[i3 * 3], p3y = rawPts[i3 * 3 + 1], p3z = rawPts[i3 * 3 + 2];
2901
+ for (let sub = 0; sub < subdivisions; sub++) {
2902
+ const t = sub / subdivisions;
2903
+ const outIdx = (seg * subdivisions + sub) * 3;
2904
+ catmullRom(
2905
+ finalPts,
2906
+ outIdx,
2907
+ p0x,
2908
+ p0y,
2909
+ p0z,
2910
+ p1x,
2911
+ p1y,
2912
+ p1z,
2913
+ p2x,
2914
+ p2y,
2915
+ p2z,
2916
+ p3x,
2917
+ p3y,
2918
+ p3z,
2919
+ t
2920
+ );
2921
+ }
2922
+ }
2923
+ const lastOutIdx = (finalCount - 1) * 3;
2924
+ finalPts[lastOutIdx] = rawPts[(count - 1) * 3];
2925
+ finalPts[lastOutIdx + 1] = rawPts[(count - 1) * 3 + 1];
2926
+ finalPts[lastOutIdx + 2] = rawPts[(count - 1) * 3 + 2];
2927
+ } else {
2928
+ finalPts = rawPts;
2929
+ finalCount = count;
2930
+ }
2931
+ if (finalCount > trailLength) finalCount = trailLength;
2932
+ if (useSmoothing && finalCount >= 2) {
2933
+ const MIN_SEG_DIST_SQ = 1e-4 * 1e-4;
2934
+ for (let d = 1; d < finalCount; d++) {
2935
+ const pi = (d - 1) * 3;
2936
+ const ci = d * 3;
2937
+ const dx = finalPts[ci] - finalPts[pi];
2938
+ const dy = finalPts[ci + 1] - finalPts[pi + 1];
2939
+ const dz = finalPts[ci + 2] - finalPts[pi + 2];
2940
+ if (dx * dx + dy * dy + dz * dz < MIN_SEG_DIST_SQ) {
2941
+ finalPts[ci] = finalPts[pi];
2942
+ finalPts[ci + 1] = finalPts[pi + 1];
2943
+ finalPts[ci + 2] = finalPts[pi + 2];
2944
+ }
2945
+ }
2946
+ }
2346
2947
  for (let s = 0; s < trailLength; s++) {
2347
2948
  const vIdx = (vertBase + s * 2) * 3;
2348
2949
  const cIdx = (vertBase + s * 2) * 4;
2349
2950
  const aIdx = vertBase + s * 2;
2350
- if (s >= count) {
2351
- trailPosArr[vIdx] = px;
2352
- trailPosArr[vIdx + 1] = py;
2353
- trailPosArr[vIdx + 2] = pz;
2354
- trailPosArr[vIdx + 3] = px;
2355
- trailPosArr[vIdx + 4] = py;
2356
- trailPosArr[vIdx + 5] = pz;
2357
- trailNextArr[vIdx] = px;
2358
- trailNextArr[vIdx + 1] = py;
2359
- trailNextArr[vIdx + 2] = pz;
2360
- trailNextArr[vIdx + 3] = px;
2361
- trailNextArr[vIdx + 4] = py;
2362
- trailNextArr[vIdx + 5] = pz;
2363
- trailHalfWidthArr[aIdx] = 0;
2364
- trailHalfWidthArr[aIdx + 1] = 0;
2365
- const uvIdx2 = (vertBase + s * 2) * 2;
2366
- trailUVArr[uvIdx2] = 0;
2367
- trailUVArr[uvIdx2 + 1] = 0;
2368
- trailUVArr[uvIdx2 + 2] = 0;
2369
- trailUVArr[uvIdx2 + 3] = 0;
2370
- trailAlphaArr[aIdx] = 0;
2371
- trailAlphaArr[aIdx + 1] = 0;
2372
- trailColorArr[cIdx] = 0;
2373
- trailColorArr[cIdx + 1] = 0;
2374
- trailColorArr[cIdx + 2] = 0;
2375
- trailColorArr[cIdx + 3] = 0;
2376
- trailColorArr[cIdx + 4] = 0;
2377
- trailColorArr[cIdx + 5] = 0;
2378
- trailColorArr[cIdx + 6] = 0;
2379
- trailColorArr[cIdx + 7] = 0;
2951
+ const uvIdxBase = (vertBase + s * 2) * 2;
2952
+ if (s >= finalCount) {
2953
+ clearTrailVertex(
2954
+ vIdx,
2955
+ cIdx,
2956
+ aIdx,
2957
+ uvIdxBase,
2958
+ trailPosArr,
2959
+ trailNextArr,
2960
+ trailHalfWidthArr,
2961
+ trailUVArr,
2962
+ trailAlphaArr,
2963
+ trailColorArr,
2964
+ px,
2965
+ py,
2966
+ pz
2967
+ );
2380
2968
  continue;
2381
2969
  }
2382
- const histIdx = (historyIndex[index] - 1 - s + trailLength * 2) % trailLength * 3 + ringOff;
2383
- const hx = positionHistory[histIdx];
2384
- const hy = positionHistory[histIdx + 1];
2385
- const hz = positionHistory[histIdx + 2];
2970
+ const hx = finalPts[s * 3];
2971
+ const hy = finalPts[s * 3 + 1];
2972
+ const hz = finalPts[s * 3 + 2];
2386
2973
  let nx, ny, nz;
2387
- if (s < count - 1) {
2388
- const ni = (historyIndex[index] - 2 - s + trailLength * 2) % trailLength * 3 + ringOff;
2389
- nx = positionHistory[ni];
2390
- ny = positionHistory[ni + 1];
2391
- nz = positionHistory[ni + 2];
2974
+ if (s > 0 && s < finalCount - 1) {
2975
+ const px2 = finalPts[(s - 1) * 3];
2976
+ const py2 = finalPts[(s - 1) * 3 + 1];
2977
+ const pz2 = finalPts[(s - 1) * 3 + 2];
2978
+ const nx2 = finalPts[(s + 1) * 3];
2979
+ const ny2 = finalPts[(s + 1) * 3 + 1];
2980
+ const nz2 = finalPts[(s + 1) * 3 + 2];
2981
+ const atx = nx2 - px2;
2982
+ const aty = ny2 - py2;
2983
+ const atz = nz2 - pz2;
2984
+ const atLen = Math.sqrt(atx * atx + aty * aty + atz * atz);
2985
+ if (atLen > 1e-4) {
2986
+ nx = hx + atx / atLen;
2987
+ ny = hy + aty / atLen;
2988
+ nz = hz + atz / atLen;
2989
+ } else {
2990
+ nx = finalPts[(s + 1) * 3];
2991
+ ny = finalPts[(s + 1) * 3 + 1];
2992
+ nz = finalPts[(s + 1) * 3 + 2];
2993
+ }
2994
+ } else if (s < finalCount - 1) {
2995
+ nx = finalPts[(s + 1) * 3];
2996
+ ny = finalPts[(s + 1) * 3 + 1];
2997
+ nz = finalPts[(s + 1) * 3 + 2];
2392
2998
  } else {
2393
2999
  nx = hx;
2394
3000
  ny = hy + 1e-3;
2395
3001
  nz = hz;
2396
3002
  }
2397
- const t = count > 1 ? s / (count - 1) : 0;
3003
+ const t = finalCount > 1 ? s / (finalCount - 1) : 0;
3004
+ let timeFade = 1;
3005
+ if (maxTime > 0 && sampleTimes && effectiveCount > 0) {
3006
+ const rawS = useSmoothing ? Math.min(
3007
+ Math.floor(s / Math.max(finalCount - 1, 1) * (rawCount - 1)),
3008
+ rawCount - 1
3009
+ ) : Math.min(s, rawCount - 1);
3010
+ const sampleSlot = (historyIndex[index] - 1 - rawS + trailLength * 2) % trailLength;
3011
+ const sampleBase = index * trailLength;
3012
+ const age = now - sampleTimes[sampleBase + sampleSlot];
3013
+ timeFade = 1 - Math.min(age / maxTimeMs, 1);
3014
+ }
2398
3015
  const widthScale = trailWidthCurveFn(t);
2399
3016
  const opacityScale = trailOpacityCurveFn(t);
2400
3017
  const halfWidth = ribbonWidth * widthScale * 0.5;
2401
- trailPosArr[vIdx] = hx;
2402
- trailPosArr[vIdx + 1] = hy;
2403
- trailPosArr[vIdx + 2] = hz;
2404
- trailPosArr[vIdx + 3] = hx;
2405
- trailPosArr[vIdx + 4] = hy;
2406
- trailPosArr[vIdx + 5] = hz;
2407
- trailNextArr[vIdx] = nx;
2408
- trailNextArr[vIdx + 1] = ny;
2409
- trailNextArr[vIdx + 2] = nz;
2410
- trailNextArr[vIdx + 3] = nx;
2411
- trailNextArr[vIdx + 4] = ny;
2412
- trailNextArr[vIdx + 5] = nz;
2413
- trailHalfWidthArr[aIdx] = halfWidth;
2414
- trailHalfWidthArr[aIdx + 1] = halfWidth;
2415
- const uvIdx = (vertBase + s * 2) * 2;
2416
- trailUVArr[uvIdx] = 0;
2417
- trailUVArr[uvIdx + 1] = t;
2418
- trailUVArr[uvIdx + 2] = 1;
2419
- trailUVArr[uvIdx + 3] = t;
2420
- const alpha = ca * opacityScale;
2421
- trailAlphaArr[aIdx] = alpha;
2422
- trailAlphaArr[aIdx + 1] = alpha;
3018
+ const alpha = ca * opacityScale * timeFade;
2423
3019
  const fr = trailColorOverTrailFns ? cr * trailColorOverTrailFns.r(t) : cr;
2424
3020
  const fg = trailColorOverTrailFns ? cg * trailColorOverTrailFns.g(t) : cg;
2425
3021
  const fb = trailColorOverTrailFns ? cb * trailColorOverTrailFns.b(t) : cb;
2426
- trailColorArr[cIdx] = fr;
2427
- trailColorArr[cIdx + 1] = fg;
2428
- trailColorArr[cIdx + 2] = fb;
2429
- trailColorArr[cIdx + 3] = ca;
2430
- trailColorArr[cIdx + 4] = fr;
2431
- trailColorArr[cIdx + 5] = fg;
2432
- trailColorArr[cIdx + 6] = fb;
2433
- trailColorArr[cIdx + 7] = ca;
3022
+ writeTrailVertex(
3023
+ vIdx,
3024
+ cIdx,
3025
+ aIdx,
3026
+ uvIdxBase,
3027
+ hx,
3028
+ hy,
3029
+ hz,
3030
+ nx,
3031
+ ny,
3032
+ nz,
3033
+ halfWidth,
3034
+ t,
3035
+ alpha,
3036
+ fr,
3037
+ fg,
3038
+ fb,
3039
+ ca,
3040
+ trailPosArr,
3041
+ trailNextArr,
3042
+ trailHalfWidthArr,
3043
+ trailUVArr,
3044
+ trailAlphaArr,
3045
+ trailColorArr
3046
+ );
3047
+ }
3048
+ if (useTwistPrevention && prevNormal && finalCount >= 2) {
3049
+ const nIdx = index * 3;
3050
+ const tx = finalPts[3] - finalPts[0];
3051
+ const ty = finalPts[4] - finalPts[1];
3052
+ const tz = finalPts[5] - finalPts[2];
3053
+ const tLen = Math.sqrt(tx * tx + ty * ty + tz * tz);
3054
+ if (tLen > 1e-4) {
3055
+ const ntx = tx / tLen;
3056
+ const nty = ty / tLen;
3057
+ const ntz = tz / tLen;
3058
+ let upx = 0, upy = 1, upz = 0;
3059
+ const dot = ntx * upx + nty * upy + ntz * upz;
3060
+ if (Math.abs(dot) > 0.999) {
3061
+ upx = 1;
3062
+ upy = 0;
3063
+ upz = 0;
3064
+ }
3065
+ let cnx = nty * upz - ntz * upy;
3066
+ let cny = ntz * upx - ntx * upz;
3067
+ let cnz = ntx * upy - nty * upx;
3068
+ const cnLen = Math.sqrt(cnx * cnx + cny * cny + cnz * cnz);
3069
+ if (cnLen > 1e-4) {
3070
+ cnx /= cnLen;
3071
+ cny /= cnLen;
3072
+ cnz /= cnLen;
3073
+ }
3074
+ const prevNx = prevNormal[nIdx];
3075
+ const prevNy = prevNormal[nIdx + 1];
3076
+ const prevNz = prevNormal[nIdx + 2];
3077
+ const hasPrev = prevNx !== 0 || prevNy !== 0 || prevNz !== 0;
3078
+ if (hasPrev) {
3079
+ const normalDot = cnx * prevNx + cny * prevNy + cnz * prevNz;
3080
+ if (normalDot < 0) {
3081
+ for (let s = 0; s < Math.min(finalCount, trailLength); s++) {
3082
+ const aIdx = vertBase + s * 2;
3083
+ const hw = trailHalfWidthArr[aIdx];
3084
+ trailHalfWidthArr[aIdx] = -hw;
3085
+ trailHalfWidthArr[aIdx + 1] = -hw;
3086
+ }
3087
+ cnx = -cnx;
3088
+ cny = -cny;
3089
+ cnz = -cnz;
3090
+ }
3091
+ }
3092
+ prevNormal[nIdx] = cnx;
3093
+ prevNormal[nIdx + 1] = cny;
3094
+ prevNormal[nIdx + 2] = cnz;
3095
+ }
2434
3096
  }
2435
3097
  } else if (historyCount[index] > 0) {
2436
3098
  hasUpdates = true;
@@ -2440,30 +3102,209 @@ var updateTrailGeometry = (props) => {
2440
3102
  const vIdx = (vertBase + s * 2) * 3;
2441
3103
  const cIdx = (vertBase + s * 2) * 4;
2442
3104
  const aIdx = vertBase + s * 2;
2443
- trailPosArr[vIdx] = 0;
2444
- trailPosArr[vIdx + 1] = 0;
2445
- trailPosArr[vIdx + 2] = 0;
2446
- trailPosArr[vIdx + 3] = 0;
2447
- trailPosArr[vIdx + 4] = 0;
2448
- trailPosArr[vIdx + 5] = 0;
2449
- trailNextArr[vIdx] = 0;
2450
- trailNextArr[vIdx + 1] = 0;
2451
- trailNextArr[vIdx + 2] = 0;
2452
- trailNextArr[vIdx + 3] = 0;
2453
- trailNextArr[vIdx + 4] = 0;
2454
- trailNextArr[vIdx + 5] = 0;
2455
- trailHalfWidthArr[aIdx] = 0;
2456
- trailHalfWidthArr[aIdx + 1] = 0;
2457
- trailAlphaArr[aIdx] = 0;
2458
- trailAlphaArr[aIdx + 1] = 0;
2459
- trailColorArr[cIdx] = 0;
2460
- trailColorArr[cIdx + 1] = 0;
2461
- trailColorArr[cIdx + 2] = 0;
2462
- trailColorArr[cIdx + 3] = 0;
2463
- trailColorArr[cIdx + 4] = 0;
2464
- trailColorArr[cIdx + 5] = 0;
2465
- trailColorArr[cIdx + 6] = 0;
2466
- trailColorArr[cIdx + 7] = 0;
3105
+ const uvIdxBase = (vertBase + s * 2) * 2;
3106
+ clearTrailVertex(
3107
+ vIdx,
3108
+ cIdx,
3109
+ aIdx,
3110
+ uvIdxBase,
3111
+ trailPosArr,
3112
+ trailNextArr,
3113
+ trailHalfWidthArr,
3114
+ trailUVArr,
3115
+ trailAlphaArr,
3116
+ trailColorArr,
3117
+ 0,
3118
+ 0,
3119
+ 0
3120
+ );
3121
+ }
3122
+ }
3123
+ }
3124
+ if (useRibbon && _ribbonCount >= 2 && _ribbonIndices) {
3125
+ hasUpdates = true;
3126
+ const leader = _ribbonIndices[0];
3127
+ const leaderVertBase = leader * verticesPerParticle;
3128
+ const controlCount = _ribbonCount;
3129
+ const filledCount = Math.min(
3130
+ trailLength,
3131
+ Math.max(controlCount * 4, controlCount)
3132
+ );
3133
+ const chainSize = filledCount * 3;
3134
+ if (!_rawPoints || _rawPointsSize < chainSize) {
3135
+ _rawPoints = new Float32Array(chainSize);
3136
+ _rawPointsSize = chainSize;
3137
+ }
3138
+ if (controlCount === 2) {
3139
+ const p0Idx = _ribbonIndices[0] * 3;
3140
+ const p1Idx = _ribbonIndices[1] * 3;
3141
+ for (let i = 0; i < filledCount; i++) {
3142
+ const t = i / (filledCount - 1);
3143
+ _rawPoints[i * 3] = positionArr[p0Idx] + t * (positionArr[p1Idx] - positionArr[p0Idx]);
3144
+ _rawPoints[i * 3 + 1] = positionArr[p0Idx + 1] + t * (positionArr[p1Idx + 1] - positionArr[p0Idx + 1]);
3145
+ _rawPoints[i * 3 + 2] = positionArr[p0Idx + 2] + t * (positionArr[p1Idx + 2] - positionArr[p0Idx + 2]);
3146
+ }
3147
+ } else {
3148
+ const segments = controlCount - 1;
3149
+ const ptsPerSeg = Math.max(1, Math.floor((filledCount - 1) / segments));
3150
+ let wi = 0;
3151
+ for (let seg = 0; seg < segments && wi < filledCount; seg++) {
3152
+ const i0 = Math.max(0, seg - 1);
3153
+ const i1 = seg;
3154
+ const i2 = Math.min(controlCount - 1, seg + 1);
3155
+ const i3 = Math.min(controlCount - 1, seg + 2);
3156
+ const p0i = _ribbonIndices[i0] * 3;
3157
+ const p1i = _ribbonIndices[i1] * 3;
3158
+ const p2i = _ribbonIndices[i2] * 3;
3159
+ const p3i = _ribbonIndices[i3] * 3;
3160
+ const subCount = seg === segments - 1 ? filledCount - wi : ptsPerSeg;
3161
+ for (let sub = 0; sub < subCount && wi < filledCount; sub++) {
3162
+ const t = sub / subCount;
3163
+ catmullRom(
3164
+ _rawPoints,
3165
+ wi * 3,
3166
+ positionArr[p0i],
3167
+ positionArr[p0i + 1],
3168
+ positionArr[p0i + 2],
3169
+ positionArr[p1i],
3170
+ positionArr[p1i + 1],
3171
+ positionArr[p1i + 2],
3172
+ positionArr[p2i],
3173
+ positionArr[p2i + 1],
3174
+ positionArr[p2i + 2],
3175
+ positionArr[p3i],
3176
+ positionArr[p3i + 1],
3177
+ positionArr[p3i + 2],
3178
+ t
3179
+ );
3180
+ wi++;
3181
+ }
3182
+ }
3183
+ if (wi > 0) {
3184
+ const lastPIdx = _ribbonIndices[controlCount - 1] * 3;
3185
+ _rawPoints[(wi - 1) * 3] = positionArr[lastPIdx];
3186
+ _rawPoints[(wi - 1) * 3 + 1] = positionArr[lastPIdx + 1];
3187
+ _rawPoints[(wi - 1) * 3 + 2] = positionArr[lastPIdx + 2];
3188
+ }
3189
+ }
3190
+ const leaderCr = colorRArr[leader];
3191
+ const leaderCg = colorGArr[leader];
3192
+ const leaderCb = colorBArr[leader];
3193
+ const leaderCa = colorAArr[leader];
3194
+ for (let s = 0; s < trailLength; s++) {
3195
+ const vIdx = (leaderVertBase + s * 2) * 3;
3196
+ const cIdx = (leaderVertBase + s * 2) * 4;
3197
+ const aIdx = leaderVertBase + s * 2;
3198
+ const uvIdxBase = (leaderVertBase + s * 2) * 2;
3199
+ if (s >= filledCount) {
3200
+ clearTrailVertex(
3201
+ vIdx,
3202
+ cIdx,
3203
+ aIdx,
3204
+ uvIdxBase,
3205
+ trailPosArr,
3206
+ trailNextArr,
3207
+ trailHalfWidthArr,
3208
+ trailUVArr,
3209
+ trailAlphaArr,
3210
+ trailColorArr,
3211
+ 0,
3212
+ 0,
3213
+ 0
3214
+ );
3215
+ continue;
3216
+ }
3217
+ const ptIdx = s * 3;
3218
+ const ptx = _rawPoints[ptIdx];
3219
+ const pty = _rawPoints[ptIdx + 1];
3220
+ const ptz = _rawPoints[ptIdx + 2];
3221
+ let nx, ny, nz;
3222
+ if (s > 0 && s < filledCount - 1) {
3223
+ const px2 = _rawPoints[(s - 1) * 3];
3224
+ const py2 = _rawPoints[(s - 1) * 3 + 1];
3225
+ const pz2 = _rawPoints[(s - 1) * 3 + 2];
3226
+ const nx2 = _rawPoints[(s + 1) * 3];
3227
+ const ny2 = _rawPoints[(s + 1) * 3 + 1];
3228
+ const nz2 = _rawPoints[(s + 1) * 3 + 2];
3229
+ const atx = nx2 - px2;
3230
+ const aty = ny2 - py2;
3231
+ const atz = nz2 - pz2;
3232
+ const atLen = Math.sqrt(atx * atx + aty * aty + atz * atz);
3233
+ if (atLen > 1e-4) {
3234
+ nx = ptx + atx / atLen;
3235
+ ny = pty + aty / atLen;
3236
+ nz = ptz + atz / atLen;
3237
+ } else {
3238
+ nx = _rawPoints[(s + 1) * 3];
3239
+ ny = _rawPoints[(s + 1) * 3 + 1];
3240
+ nz = _rawPoints[(s + 1) * 3 + 2];
3241
+ }
3242
+ } else if (s < filledCount - 1) {
3243
+ nx = _rawPoints[(s + 1) * 3];
3244
+ ny = _rawPoints[(s + 1) * 3 + 1];
3245
+ nz = _rawPoints[(s + 1) * 3 + 2];
3246
+ } else {
3247
+ nx = ptx;
3248
+ ny = pty + 1e-3;
3249
+ nz = ptz;
3250
+ }
3251
+ const t = filledCount > 1 ? s / (filledCount - 1) : 0;
3252
+ const widthScale = trailWidthCurveFn(t);
3253
+ const opacityScale = trailOpacityCurveFn(t);
3254
+ const halfWidth = trailConfig.width * widthScale * 0.5;
3255
+ const alpha = leaderCa * opacityScale;
3256
+ const fr = trailColorOverTrailFns ? leaderCr * trailColorOverTrailFns.r(t) : leaderCr;
3257
+ const fg = trailColorOverTrailFns ? leaderCg * trailColorOverTrailFns.g(t) : leaderCg;
3258
+ const fb = trailColorOverTrailFns ? leaderCb * trailColorOverTrailFns.b(t) : leaderCb;
3259
+ writeTrailVertex(
3260
+ vIdx,
3261
+ cIdx,
3262
+ aIdx,
3263
+ uvIdxBase,
3264
+ ptx,
3265
+ pty,
3266
+ ptz,
3267
+ nx,
3268
+ ny,
3269
+ nz,
3270
+ halfWidth,
3271
+ t,
3272
+ alpha,
3273
+ fr,
3274
+ fg,
3275
+ fb,
3276
+ leaderCa,
3277
+ trailPosArr,
3278
+ trailNextArr,
3279
+ trailHalfWidthArr,
3280
+ trailUVArr,
3281
+ trailAlphaArr,
3282
+ trailColorArr
3283
+ );
3284
+ }
3285
+ for (let ri = 1; ri < _ribbonCount; ri++) {
3286
+ const pIdx = _ribbonIndices[ri];
3287
+ const pVertBase = pIdx * verticesPerParticle;
3288
+ for (let s = 0; s < trailLength; s++) {
3289
+ const vIdx = (pVertBase + s * 2) * 3;
3290
+ const cIdx = (pVertBase + s * 2) * 4;
3291
+ const aIdx = pVertBase + s * 2;
3292
+ const uvIdxBase = (pVertBase + s * 2) * 2;
3293
+ clearTrailVertex(
3294
+ vIdx,
3295
+ cIdx,
3296
+ aIdx,
3297
+ uvIdxBase,
3298
+ trailPosArr,
3299
+ trailNextArr,
3300
+ trailHalfWidthArr,
3301
+ trailUVArr,
3302
+ trailAlphaArr,
3303
+ trailColorArr,
3304
+ 0,
3305
+ 0,
3306
+ 0
3307
+ );
2467
3308
  }
2468
3309
  }
2469
3310
  }
@@ -2491,6 +3332,6 @@ var updateParticleSystems = (cycleData) => {
2491
3332
  );
2492
3333
  };
2493
3334
 
2494
- export { CurveFunctionId, EmitFrom, ForceFieldFalloff, ForceFieldType, LifeTimeCurve, RendererType, Shape, SimulationSpace, SubEmitterTrigger, TimeMode, applyModifiers, blendingMap, calculateRandomPositionAndVelocityOnBox, calculateRandomPositionAndVelocityOnCircle, calculateRandomPositionAndVelocityOnCone, calculateRandomPositionAndVelocityOnRectangle, calculateRandomPositionAndVelocityOnSphere, calculateValue, createBezierCurveFunction, createDefaultParticleTexture, createParticleSystem, curveFunctionIdMap, getBezierCacheSize, getCurveFunction, getCurveFunctionFromConfig, getDefaultParticleSystemConfig, isLifeTimeCurve, removeBezierCurveFunction, updateParticleSystems };
3335
+ export { CurveFunctionId, EmitFrom, ForceFieldFalloff, ForceFieldType, LifeTimeCurve, RendererType, Shape, SimulationSpace, SubEmitterTrigger, TimeMode, applyModifiers, blendingMap, calculateRandomPositionAndVelocityOnBox, calculateRandomPositionAndVelocityOnCircle, calculateRandomPositionAndVelocityOnCone, calculateRandomPositionAndVelocityOnRectangle, calculateRandomPositionAndVelocityOnSphere, calculateValue, createBezierCurveFunction, createDefaultMeshTexture, createDefaultParticleTexture, createParticleSystem, curveFunctionIdMap, getBezierCacheSize, getCurveFunction, getCurveFunctionFromConfig, getDefaultParticleSystemConfig, isLifeTimeCurve, removeBezierCurveFunction, updateParticleSystems };
2495
3336
  //# sourceMappingURL=index.js.map
2496
3337
  //# sourceMappingURL=index.js.map