r3f-vfx 0.0.6 → 0.0.7

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.
Files changed (3) hide show
  1. package/dist/index.js +100 -1063
  2. package/package.json +3 -3
  3. package/dist/index.d.ts +0 -297
package/dist/index.js CHANGED
@@ -1141,7 +1141,7 @@ ${" ".repeat(indent - 2)}}`;
1141
1141
  }
1142
1142
  );
1143
1143
  };
1144
- useScrubber = (value, onChange, step2 = 0.01, min, max) => {
1144
+ useScrubber = (value, onChange, step = 0.01, min, max) => {
1145
1145
  const isDraggingRef = useRef(false);
1146
1146
  const hasMoved = useRef(false);
1147
1147
  const startX = useRef(0);
@@ -1166,11 +1166,11 @@ ${" ".repeat(indent - 2)}}`;
1166
1166
  setIsDragging(true);
1167
1167
  }
1168
1168
  const sensitivity = e2.shiftKey ? 0.1 : 0.5;
1169
- const change = delta * step2 * sensitivity;
1169
+ const change = delta * step * sensitivity;
1170
1170
  let newValue = startValue.current + change;
1171
1171
  if (min !== void 0) newValue = Math.max(min, newValue);
1172
1172
  if (max !== void 0) newValue = Math.min(max, newValue);
1173
- const precision = step2 < 1 ? Math.ceil(-Math.log10(step2)) : 0;
1173
+ const precision = step < 1 ? Math.ceil(-Math.log10(step)) : 0;
1174
1174
  newValue = parseFloat(newValue.toFixed(precision));
1175
1175
  onChange(newValue);
1176
1176
  };
@@ -1190,7 +1190,7 @@ ${" ".repeat(indent - 2)}}`;
1190
1190
  document.addEventListener("mousemove", handleMouseMove);
1191
1191
  document.addEventListener("mouseup", handleMouseUp);
1192
1192
  },
1193
- [value, onChange, step2, min, max]
1193
+ [value, onChange, step, min, max]
1194
1194
  );
1195
1195
  return { handleMouseDown, hasMoved, isDragging };
1196
1196
  };
@@ -1199,7 +1199,7 @@ ${" ".repeat(indent - 2)}}`;
1199
1199
  onChange,
1200
1200
  min,
1201
1201
  max,
1202
- step: step2 = 0.01,
1202
+ step = 0.01,
1203
1203
  style,
1204
1204
  placeholder
1205
1205
  }) => {
@@ -1209,7 +1209,7 @@ ${" ".repeat(indent - 2)}}`;
1209
1209
  const { handleMouseDown, hasMoved, isDragging } = useScrubber(
1210
1210
  value,
1211
1211
  onChange,
1212
- step2,
1212
+ step,
1213
1213
  min,
1214
1214
  max
1215
1215
  );
@@ -1298,8 +1298,8 @@ ${" ".repeat(indent - 2)}}`;
1298
1298
  }
1299
1299
  );
1300
1300
  };
1301
- NumberInput = ({ label, value, onChange, min, max, step: step2 = 0.01 }) => {
1302
- const { handleMouseDown } = useScrubber(value, onChange, step2, min, max);
1301
+ NumberInput = ({ label, value, onChange, min, max, step = 0.01 }) => {
1302
+ const { handleMouseDown } = useScrubber(value, onChange, step, min, max);
1303
1303
  return /* @__PURE__ */ jsxs("div", { style: styles.row, children: [
1304
1304
  /* @__PURE__ */ jsx(
1305
1305
  "label",
@@ -1317,7 +1317,7 @@ ${" ".repeat(indent - 2)}}`;
1317
1317
  onChange,
1318
1318
  min,
1319
1319
  max,
1320
- step: step2,
1320
+ step,
1321
1321
  style: styles.input
1322
1322
  }
1323
1323
  )
@@ -1329,7 +1329,7 @@ ${" ".repeat(indent - 2)}}`;
1329
1329
  onChange,
1330
1330
  min = -100,
1331
1331
  max = 100,
1332
- step: step2 = 0.01
1332
+ step = 0.01
1333
1333
  }) => {
1334
1334
  const [minVal, maxVal] = parseRange(value, [0, 0]);
1335
1335
  const [linked, setLinked] = useState(false);
@@ -1356,7 +1356,7 @@ ${" ".repeat(indent - 2)}}`;
1356
1356
  const { handleMouseDown } = useScrubber(
1357
1357
  minVal,
1358
1358
  handleMinChange,
1359
- step2,
1359
+ step,
1360
1360
  min,
1361
1361
  max
1362
1362
  );
@@ -1389,7 +1389,7 @@ ${" ".repeat(indent - 2)}}`;
1389
1389
  onChange: handleMinChange,
1390
1390
  min,
1391
1391
  max,
1392
- step: step2,
1392
+ step,
1393
1393
  style: styles.rangeInput,
1394
1394
  placeholder: "min"
1395
1395
  }
@@ -1427,7 +1427,7 @@ ${" ".repeat(indent - 2)}}`;
1427
1427
  onChange: handleMaxChange,
1428
1428
  min,
1429
1429
  max,
1430
- step: step2,
1430
+ step,
1431
1431
  style: styles.rangeInput,
1432
1432
  placeholder: "max"
1433
1433
  }
@@ -5129,280 +5129,41 @@ import {
5129
5129
  } from "react";
5130
5130
  import { useFrame, useThree } from "@react-three/fiber";
5131
5131
  import * as THREE2 from "three/webgpu";
5132
+ import { uniform, instancedArray } from "three/tsl";
5132
5133
  import {
5133
- Fn,
5134
- If,
5135
- uniform,
5136
- float,
5137
- uv,
5138
- vec2,
5139
- vec3,
5140
- vec4,
5141
- hash,
5142
- mix,
5143
- floor,
5144
- step,
5145
- mod,
5146
- texture,
5147
- instancedArray,
5148
- instanceIndex,
5149
- positionLocal,
5150
- cos,
5151
- sin,
5152
- sqrt,
5153
- acos,
5154
- PI,
5155
- mx_noise_vec3,
5156
- screenUV,
5157
- viewportDepthTexture,
5158
- positionView,
5159
- cameraNear,
5160
- cameraFar,
5161
- clamp
5162
- } from "three/tsl";
5134
+ Appearance as Appearance2,
5135
+ Blending as Blending2,
5136
+ EmitterShape as EmitterShape2,
5137
+ Lighting as Lighting2,
5138
+ MAX_ATTRACTORS,
5139
+ hexToRgb,
5140
+ toRange,
5141
+ easingToType,
5142
+ axisToNumber,
5143
+ toRotation3D,
5144
+ lifetimeToFadeRate,
5145
+ createCombinedCurveTexture,
5146
+ createInitCompute,
5147
+ createSpawnCompute,
5148
+ createUpdateCompute,
5149
+ createParticleMaterial
5150
+ } from "core-vfx";
5151
+ import {
5152
+ Appearance,
5153
+ Blending,
5154
+ EmitterShape,
5155
+ AttractorType as AttractorType2,
5156
+ Easing as Easing2,
5157
+ Lighting,
5158
+ bakeCurveToArray,
5159
+ createCombinedCurveTexture as createCombinedCurveTexture2
5160
+ } from "core-vfx";
5163
5161
  import { jsx as jsx2 } from "react/jsx-runtime";
5164
- var Appearance, Blending, EmitterShape, AttractorType, Easing, Lighting, MAX_ATTRACTORS, hexToRgb, toRange, easingToType, axisToNumber, CURVE_RESOLUTION, evaluateBezierSegment, sampleCurveAtX, bakeCurveToArray, createCombinedCurveTexture, toRotation3D, VFXParticles;
5162
+ var VFXParticles;
5165
5163
  var init_VFXParticles = __esm({
5166
5164
  "src/VFXParticles.tsx"() {
5167
5165
  "use strict";
5168
5166
  init_react_store();
5169
- Appearance = Object.freeze({
5170
- DEFAULT: "default",
5171
- GRADIENT: "gradient",
5172
- CIRCULAR: "circular"
5173
- });
5174
- Blending = Object.freeze({
5175
- NORMAL: THREE2.NormalBlending,
5176
- ADDITIVE: THREE2.AdditiveBlending,
5177
- MULTIPLY: THREE2.MultiplyBlending,
5178
- SUBTRACTIVE: THREE2.SubtractiveBlending
5179
- });
5180
- EmitterShape = Object.freeze({
5181
- POINT: 0,
5182
- // Single point emission
5183
- BOX: 1,
5184
- // Box/cube volume (uses startPosition ranges)
5185
- SPHERE: 2,
5186
- // Sphere surface or volume
5187
- CONE: 3,
5188
- // Cone shape (great for fire, fountains)
5189
- DISK: 4,
5190
- // Flat disk/circle
5191
- EDGE: 5
5192
- // Line between two points
5193
- });
5194
- AttractorType = Object.freeze({
5195
- POINT: 0,
5196
- // Pull toward a point (or push if negative strength)
5197
- VORTEX: 1
5198
- // Swirl around an axis
5199
- });
5200
- Easing = Object.freeze({
5201
- LINEAR: 0,
5202
- EASE_IN: 1,
5203
- EASE_OUT: 2,
5204
- EASE_IN_OUT: 3
5205
- });
5206
- Lighting = Object.freeze({
5207
- BASIC: "basic",
5208
- // No lighting, flat colors (MeshBasicNodeMaterial)
5209
- STANDARD: "standard",
5210
- // Standard PBR (MeshStandardNodeMaterial)
5211
- PHYSICAL: "physical"
5212
- // Advanced PBR with clearcoat, transmission, etc. (MeshPhysicalNodeMaterial)
5213
- });
5214
- MAX_ATTRACTORS = 4;
5215
- hexToRgb = (hex) => {
5216
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
5217
- return result ? [
5218
- parseInt(result[1], 16) / 255,
5219
- parseInt(result[2], 16) / 255,
5220
- parseInt(result[3], 16) / 255
5221
- ] : [1, 1, 1];
5222
- };
5223
- toRange = (value, defaultVal = [0, 0]) => {
5224
- if (value === void 0 || value === null) return defaultVal;
5225
- if (Array.isArray(value))
5226
- return value.length === 2 ? value : [value[0], value[0]];
5227
- return [value, value];
5228
- };
5229
- easingToType = (easing) => {
5230
- if (typeof easing === "number") return easing;
5231
- switch (easing) {
5232
- case "easeIn":
5233
- return 1;
5234
- case "easeOut":
5235
- return 2;
5236
- case "easeInOut":
5237
- return 3;
5238
- default:
5239
- return 0;
5240
- }
5241
- };
5242
- axisToNumber = (axis) => {
5243
- switch (axis) {
5244
- case "x":
5245
- case "+x":
5246
- case "X":
5247
- case "+X":
5248
- return 0;
5249
- case "y":
5250
- case "+y":
5251
- case "Y":
5252
- case "+Y":
5253
- return 1;
5254
- case "z":
5255
- case "+z":
5256
- case "Z":
5257
- case "+Z":
5258
- return 2;
5259
- case "-x":
5260
- case "-X":
5261
- return 3;
5262
- case "-y":
5263
- case "-Y":
5264
- return 4;
5265
- case "-z":
5266
- case "-Z":
5267
- return 5;
5268
- default:
5269
- return 2;
5270
- }
5271
- };
5272
- CURVE_RESOLUTION = 256;
5273
- evaluateBezierSegment = (t, p0, p1, h0Out, h1In) => {
5274
- const cp0 = p0;
5275
- const cp1 = [p0[0] + ((h0Out == null ? void 0 : h0Out[0]) || 0), p0[1] + ((h0Out == null ? void 0 : h0Out[1]) || 0)];
5276
- const cp2 = [p1[0] + ((h1In == null ? void 0 : h1In[0]) || 0), p1[1] + ((h1In == null ? void 0 : h1In[1]) || 0)];
5277
- const cp3 = p1;
5278
- const mt = 1 - t;
5279
- const mt2 = mt * mt;
5280
- const mt3 = mt2 * mt;
5281
- const t2 = t * t;
5282
- const t3 = t2 * t;
5283
- return [
5284
- mt3 * cp0[0] + 3 * mt2 * t * cp1[0] + 3 * mt * t2 * cp2[0] + t3 * cp3[0],
5285
- mt3 * cp0[1] + 3 * mt2 * t * cp1[1] + 3 * mt * t2 * cp2[1] + t3 * cp3[1]
5286
- ];
5287
- };
5288
- sampleCurveAtX = (x, points) => {
5289
- var _a, _b, _c, _d;
5290
- if (!points || points.length < 2) return x;
5291
- if (!((_a = points[0]) == null ? void 0 : _a.pos) || !((_b = points[points.length - 1]) == null ? void 0 : _b.pos)) return x;
5292
- let segmentIdx = 0;
5293
- for (let i = 0; i < points.length - 1; i++) {
5294
- if (((_c = points[i]) == null ? void 0 : _c.pos) && ((_d = points[i + 1]) == null ? void 0 : _d.pos) && x >= points[i].pos[0] && x <= points[i + 1].pos[0]) {
5295
- segmentIdx = i;
5296
- break;
5297
- }
5298
- }
5299
- const p0 = points[segmentIdx];
5300
- const p1 = points[segmentIdx + 1];
5301
- if (!(p0 == null ? void 0 : p0.pos) || !(p1 == null ? void 0 : p1.pos)) return x;
5302
- let tLow = 0, tHigh = 1, t = 0.5;
5303
- for (let iter = 0; iter < 20; iter++) {
5304
- const [px] = evaluateBezierSegment(
5305
- t,
5306
- p0.pos,
5307
- p1.pos,
5308
- p0.handleOut,
5309
- p1.handleIn
5310
- );
5311
- if (Math.abs(px - x) < 1e-4) break;
5312
- if (px < x) {
5313
- tLow = t;
5314
- } else {
5315
- tHigh = t;
5316
- }
5317
- t = (tLow + tHigh) / 2;
5318
- }
5319
- const [, py] = evaluateBezierSegment(
5320
- t,
5321
- p0.pos,
5322
- p1.pos,
5323
- p0.handleOut,
5324
- p1.handleIn
5325
- );
5326
- return Math.max(-0.5, Math.min(1.5, py));
5327
- };
5328
- bakeCurveToArray = (curveData, resolution = CURVE_RESOLUTION) => {
5329
- const data = new Float32Array(resolution);
5330
- if (!(curveData == null ? void 0 : curveData.points) || !Array.isArray(curveData.points) || curveData.points.length < 2) {
5331
- for (let i = 0; i < resolution; i++) {
5332
- data[i] = 1 - i / (resolution - 1);
5333
- }
5334
- return data;
5335
- }
5336
- const firstPoint = curveData.points[0];
5337
- const lastPoint = curveData.points[curveData.points.length - 1];
5338
- if (!(firstPoint == null ? void 0 : firstPoint.pos) || !(lastPoint == null ? void 0 : lastPoint.pos) || !Array.isArray(firstPoint.pos) || !Array.isArray(lastPoint.pos)) {
5339
- for (let i = 0; i < resolution; i++) {
5340
- data[i] = 1 - i / (resolution - 1);
5341
- }
5342
- return data;
5343
- }
5344
- for (let i = 0; i < resolution; i++) {
5345
- const x = i / (resolution - 1);
5346
- data[i] = sampleCurveAtX(x, curveData.points);
5347
- }
5348
- return data;
5349
- };
5350
- createCombinedCurveTexture = (sizeCurve, opacityCurve, velocityCurve, rotationSpeedCurve) => {
5351
- const sizeData = bakeCurveToArray(sizeCurve);
5352
- const opacityData = bakeCurveToArray(opacityCurve);
5353
- const velocityData = bakeCurveToArray(velocityCurve);
5354
- const rotationSpeedData = bakeCurveToArray(rotationSpeedCurve);
5355
- const rgba = new Float32Array(CURVE_RESOLUTION * 4);
5356
- for (let i = 0; i < CURVE_RESOLUTION; i++) {
5357
- rgba[i * 4] = sizeData[i];
5358
- rgba[i * 4 + 1] = opacityData[i];
5359
- rgba[i * 4 + 2] = velocityData[i];
5360
- rgba[i * 4 + 3] = rotationSpeedData[i];
5361
- }
5362
- const tex = new THREE2.DataTexture(
5363
- rgba,
5364
- CURVE_RESOLUTION,
5365
- 1,
5366
- THREE2.RGBAFormat,
5367
- THREE2.FloatType
5368
- );
5369
- tex.minFilter = THREE2.LinearFilter;
5370
- tex.magFilter = THREE2.LinearFilter;
5371
- tex.wrapS = THREE2.ClampToEdgeWrapping;
5372
- tex.needsUpdate = true;
5373
- return tex;
5374
- };
5375
- toRotation3D = (value) => {
5376
- if (value === void 0 || value === null)
5377
- return [
5378
- [0, 0],
5379
- [0, 0],
5380
- [0, 0]
5381
- ];
5382
- if (typeof value === "number")
5383
- return [
5384
- [value, value],
5385
- [value, value],
5386
- [value, value]
5387
- ];
5388
- if (Array.isArray(value)) {
5389
- if (Array.isArray(value[0])) {
5390
- const nested = value;
5391
- return [
5392
- toRange(nested[0], [0, 0]),
5393
- toRange(nested[1], [0, 0]),
5394
- toRange(nested[2], [0, 0])
5395
- ];
5396
- }
5397
- const range = toRange(value, [0, 0]);
5398
- return [range, range, range];
5399
- }
5400
- return [
5401
- [0, 0],
5402
- [0, 0],
5403
- [0, 0]
5404
- ];
5405
- };
5406
5167
  VFXParticles = forwardRef(function VFXParticles2({
5407
5168
  name,
5408
5169
  // Optional name for registering with useVFXStore (enables VFXEmitter linking)
@@ -5437,7 +5198,7 @@ var init_VFXParticles = __esm({
5437
5198
  friction = { intensity: 0, easing: "linear" },
5438
5199
  // { intensity: [start, end] or single value, easing: string }
5439
5200
  // intensity: 1 = max friction (almost stopped), 0 = no friction (normal), negative = boost/acceleration
5440
- appearance = Appearance.GRADIENT,
5201
+ appearance = Appearance2.GRADIENT,
5441
5202
  alphaMap = null,
5442
5203
  flipbook = null,
5443
5204
  // { rows: 4, columns: 8 }
@@ -5455,11 +5216,11 @@ var init_VFXParticles = __esm({
5455
5216
  // Which local axis aligns with velocity: "x", "y", "z", "-x", "-y", "-z"
5456
5217
  stretchBySpeed = null,
5457
5218
  // { factor: 2, maxStretch: 5 } - stretch particles in velocity direction based on effective speed
5458
- lighting = Lighting.STANDARD,
5219
+ lighting = Lighting2.STANDARD,
5459
5220
  // 'basic' | 'standard' | 'physical' - material type for geometry mode
5460
5221
  shadow = false,
5461
5222
  // Enable both castShadow and receiveShadow on geometry instances
5462
- blending = Blending.NORMAL,
5223
+ blending = Blending2.NORMAL,
5463
5224
  intensity = 1,
5464
5225
  position = [0, 0, 0],
5465
5226
  autoStart = true,
@@ -5476,7 +5237,7 @@ var init_VFXParticles = __esm({
5476
5237
  // TSL node or function for shadow map output (what shadow the particle casts)
5477
5238
  emitCount = 1,
5478
5239
  // Emitter shape props
5479
- emitterShape = EmitterShape.BOX,
5240
+ emitterShape = EmitterShape2.BOX,
5480
5241
  // Emission shape type
5481
5242
  emitterRadius = [0, 1],
5482
5243
  // [inner, outer] radius for sphere/cone/disk (inner=0 for solid)
@@ -5559,7 +5320,6 @@ var init_VFXParticles = __esm({
5559
5320
  velocityCurve,
5560
5321
  rotationSpeedCurve
5561
5322
  ]);
5562
- const lifetimeToFadeRate = (seconds) => 1 / seconds;
5563
5323
  const sizeRange = useMemo(() => toRange(size, [0.1, 0.3]), [size]);
5564
5324
  const speedRange = useMemo(() => toRange(speed, [0.1, 0.1]), [speed]);
5565
5325
  const fadeSizeRange = useMemo(() => toRange(fadeSize, [1, 0]), [fadeSize]);
@@ -5921,16 +5681,7 @@ var init_VFXParticles = __esm({
5921
5681
  orientAxis,
5922
5682
  stretchBySpeed
5923
5683
  ]);
5924
- const {
5925
- positions,
5926
- velocities,
5927
- lifetimes,
5928
- fadeRates,
5929
- particleSizes,
5930
- particleRotations,
5931
- particleColorStarts,
5932
- particleColorEnds
5933
- } = useMemo(
5684
+ const storage = useMemo(
5934
5685
  () => ({
5935
5686
  positions: instancedArray(activeMaxParticles, "vec3"),
5936
5687
  velocities: instancedArray(activeMaxParticles, "vec3"),
@@ -5938,774 +5689,60 @@ var init_VFXParticles = __esm({
5938
5689
  fadeRates: instancedArray(activeMaxParticles, "float"),
5939
5690
  particleSizes: instancedArray(activeMaxParticles, "float"),
5940
5691
  particleRotations: instancedArray(activeMaxParticles, "vec3"),
5941
- // X, Y, Z rotations
5942
5692
  particleColorStarts: instancedArray(activeMaxParticles, "vec3"),
5943
5693
  particleColorEnds: instancedArray(activeMaxParticles, "vec3")
5944
5694
  }),
5945
5695
  [activeMaxParticles]
5946
5696
  );
5947
- const selectColor = (idx, c0, c1, c2, c3, c4, c5, c6, c7) => {
5948
- return idx.lessThan(1).select(
5949
- c0,
5950
- idx.lessThan(2).select(
5951
- c1,
5952
- idx.lessThan(3).select(
5953
- c2,
5954
- idx.lessThan(4).select(
5955
- c3,
5956
- idx.lessThan(5).select(
5957
- c4,
5958
- idx.lessThan(6).select(c5, idx.lessThan(7).select(c6, c7))
5959
- )
5960
- )
5961
- )
5962
- )
5963
- );
5964
- };
5965
- const computeInit = useMemo(() => {
5966
- return Fn(() => {
5967
- const position2 = positions.element(instanceIndex);
5968
- const velocity = velocities.element(instanceIndex);
5969
- const lifetime2 = lifetimes.element(instanceIndex);
5970
- const fadeRate = fadeRates.element(instanceIndex);
5971
- const particleSize = particleSizes.element(instanceIndex);
5972
- const particleRotation = particleRotations.element(instanceIndex);
5973
- const colorStart2 = particleColorStarts.element(instanceIndex);
5974
- const colorEnd2 = particleColorEnds.element(instanceIndex);
5975
- position2.assign(vec3(0, -1e3, 0));
5976
- velocity.assign(vec3(0, 0, 0));
5977
- lifetime2.assign(float(0));
5978
- fadeRate.assign(float(0));
5979
- particleSize.assign(float(0));
5980
- particleRotation.assign(vec3(0, 0, 0));
5981
- colorStart2.assign(vec3(1, 1, 1));
5982
- colorEnd2.assign(vec3(1, 1, 1));
5983
- })().compute(activeMaxParticles);
5984
- }, [
5985
- activeMaxParticles,
5986
- positions,
5987
- velocities,
5988
- lifetimes,
5989
- fadeRates,
5990
- particleSizes,
5991
- particleRotations,
5992
- particleColorStarts,
5993
- particleColorEnds
5994
- ]);
5995
- const computeSpawn = useMemo(() => {
5996
- return Fn(() => {
5997
- const idx = float(instanceIndex);
5998
- const startIdx = uniforms.spawnIndexStart;
5999
- const endIdx = uniforms.spawnIndexEnd;
6000
- const seed = uniforms.spawnSeed;
6001
- const inRange = startIdx.lessThan(endIdx).select(
6002
- idx.greaterThanEqual(startIdx).and(idx.lessThan(endIdx)),
6003
- idx.greaterThanEqual(startIdx).or(idx.lessThan(endIdx))
6004
- );
6005
- If(inRange, () => {
6006
- const position2 = positions.element(instanceIndex);
6007
- const velocity = velocities.element(instanceIndex);
6008
- const lifetime2 = lifetimes.element(instanceIndex);
6009
- const fadeRate = fadeRates.element(instanceIndex);
6010
- const particleSize = particleSizes.element(instanceIndex);
6011
- const particleRotation = particleRotations.element(instanceIndex);
6012
- const pColorStart = particleColorStarts.element(instanceIndex);
6013
- const pColorEnd = particleColorEnds.element(instanceIndex);
6014
- const particleSeed = idx.add(seed);
6015
- const randDirX = hash(particleSeed.add(333));
6016
- const randDirY = hash(particleSeed.add(444));
6017
- const randDirZ = hash(particleSeed.add(555));
6018
- const randFade = hash(particleSeed.add(666));
6019
- const randColorStart = hash(particleSeed.add(777));
6020
- const randColorEnd = hash(particleSeed.add(888));
6021
- const randSize = hash(particleSeed.add(999));
6022
- const randSpeed = hash(particleSeed.add(1111));
6023
- const randRotationX = hash(particleSeed.add(2222));
6024
- const randRotationY = hash(particleSeed.add(3333));
6025
- const randRotationZ = hash(particleSeed.add(4444));
6026
- const randPosX = hash(particleSeed.add(5555));
6027
- const randPosY = hash(particleSeed.add(6666));
6028
- const randPosZ = hash(particleSeed.add(7777));
6029
- const randRadius = hash(particleSeed.add(8880));
6030
- const randTheta = hash(particleSeed.add(9990));
6031
- const randPhi = hash(particleSeed.add(10100));
6032
- const randHeight = hash(particleSeed.add(11110));
6033
- const shapeType = uniforms.emitterShapeType;
6034
- const radiusInner = uniforms.emitterRadiusInner;
6035
- const radiusOuter = uniforms.emitterRadiusOuter;
6036
- const coneAngle = uniforms.emitterAngle;
6037
- const heightMin = uniforms.emitterHeightMin;
6038
- const heightMax = uniforms.emitterHeightMax;
6039
- const surfaceOnly = uniforms.emitterSurfaceOnly;
6040
- const emitDir = uniforms.emitterDir;
6041
- const theta = randTheta.mul(PI.mul(2));
6042
- const phi = acos(float(1).sub(randPhi.mul(2)));
6043
- const radiusT = surfaceOnly.greaterThan(0.5).select(
6044
- float(1),
6045
- randRadius.pow(float(1).div(3))
6046
- // Cube root for uniform volume
6047
- );
6048
- const radius = mix(radiusInner, radiusOuter, radiusT);
6049
- const cosAngle = emitDir.y;
6050
- const axisX = emitDir.z.negate();
6051
- const axisZ = emitDir.x;
6052
- const axisLenSq = axisX.mul(axisX).add(axisZ.mul(axisZ));
6053
- const axisLen = sqrt(axisLenSq.max(1e-4));
6054
- const kx = axisX.div(axisLen);
6055
- const kz = axisZ.div(axisLen);
6056
- const sinAngle = axisLen;
6057
- const oneMinusCos = float(1).sub(cosAngle);
6058
- const rotateToEmitDir = (localPos) => {
6059
- const crossX = kz.mul(localPos.y).negate();
6060
- const crossY = kz.mul(localPos.x).sub(kx.mul(localPos.z));
6061
- const crossZ = kx.mul(localPos.y);
6062
- const kDotV = kx.mul(localPos.x).add(kz.mul(localPos.z));
6063
- const rotatedX = localPos.x.mul(cosAngle).add(crossX.mul(sinAngle)).add(kx.mul(kDotV).mul(oneMinusCos));
6064
- const rotatedY = localPos.y.mul(cosAngle).add(crossY.mul(sinAngle));
6065
- const rotatedZ = localPos.z.mul(cosAngle).add(crossZ.mul(sinAngle)).add(kz.mul(kDotV).mul(oneMinusCos));
6066
- return cosAngle.greaterThan(0.999).select(
6067
- localPos,
6068
- cosAngle.lessThan(-0.999).select(
6069
- vec3(localPos.x, localPos.y.negate(), localPos.z),
6070
- vec3(rotatedX, rotatedY, rotatedZ)
6071
- )
6072
- );
6073
- };
6074
- const boxOffsetX = mix(
6075
- uniforms.startPosMinX,
6076
- uniforms.startPosMaxX,
6077
- randPosX
6078
- );
6079
- const boxOffsetY = mix(
6080
- uniforms.startPosMinY,
6081
- uniforms.startPosMaxY,
6082
- randPosY
6083
- );
6084
- const boxOffsetZ = mix(
6085
- uniforms.startPosMinZ,
6086
- uniforms.startPosMaxZ,
6087
- randPosZ
6088
- );
6089
- const boxPos = vec3(boxOffsetX, boxOffsetY, boxOffsetZ);
6090
- const sphereX = radius.mul(sin(phi)).mul(cos(theta));
6091
- const sphereY = radius.mul(cos(phi));
6092
- const sphereZ = radius.mul(sin(phi)).mul(sin(theta));
6093
- const spherePos = vec3(sphereX, sphereY, sphereZ);
6094
- const coneH = mix(heightMin, heightMax, randHeight);
6095
- const coneR = coneH.mul(sin(coneAngle)).mul(radiusT);
6096
- const coneLocalX = coneR.mul(cos(theta));
6097
- const coneLocalY = coneH.mul(cos(coneAngle));
6098
- const coneLocalZ = coneR.mul(sin(theta));
6099
- const conePos = rotateToEmitDir(
6100
- vec3(coneLocalX, coneLocalY, coneLocalZ)
6101
- );
6102
- const diskR = surfaceOnly.greaterThan(0.5).select(
6103
- radiusOuter,
6104
- mix(radiusInner, radiusOuter, sqrt(randRadius))
6105
- // sqrt for uniform area distribution
6106
- );
6107
- const diskLocalX = diskR.mul(cos(theta));
6108
- const diskLocalZ = diskR.mul(sin(theta));
6109
- const diskPos = rotateToEmitDir(vec3(diskLocalX, float(0), diskLocalZ));
6110
- const edgeT = randPosX;
6111
- const edgePos = vec3(
6112
- mix(uniforms.startPosMinX, uniforms.startPosMaxX, edgeT),
6113
- mix(uniforms.startPosMinY, uniforms.startPosMaxY, edgeT),
6114
- mix(uniforms.startPosMinZ, uniforms.startPosMaxZ, edgeT)
6115
- );
6116
- const pointPos = vec3(0, 0, 0);
6117
- const shapeOffset = shapeType.lessThan(0.5).select(
6118
- pointPos,
6119
- // 0: POINT
6120
- shapeType.lessThan(1.5).select(
6121
- boxPos,
6122
- // 1: BOX
6123
- shapeType.lessThan(2.5).select(
6124
- spherePos,
6125
- // 2: SPHERE
6126
- shapeType.lessThan(3.5).select(
6127
- conePos,
6128
- // 3: CONE
6129
- shapeType.lessThan(4.5).select(
6130
- diskPos,
6131
- // 4: DISK
6132
- edgePos
6133
- // 5: EDGE
6134
- )
6135
- )
6136
- )
6137
- )
6138
- );
6139
- position2.assign(uniforms.spawnPosition.add(shapeOffset));
6140
- const randomFade = mix(
6141
- uniforms.lifetimeMin,
6142
- uniforms.lifetimeMax,
6143
- randFade
6144
- );
6145
- fadeRate.assign(randomFade);
6146
- const useAttractToCenter = uniforms.attractToCenter.greaterThan(0.5);
6147
- const attractVelocity = shapeOffset.negate().mul(randomFade);
6148
- const useStartPosAsDir = uniforms.startPositionAsDirection.greaterThan(0.5);
6149
- const dirX = mix(uniforms.dirMinX, uniforms.dirMaxX, randDirX);
6150
- const dirY = mix(uniforms.dirMinY, uniforms.dirMaxY, randDirY);
6151
- const dirZ = mix(uniforms.dirMinZ, uniforms.dirMaxZ, randDirZ);
6152
- const randomDirVec = vec3(dirX, dirY, dirZ);
6153
- const randomDirLength = randomDirVec.length();
6154
- const randomDir = randomDirLength.greaterThan(1e-3).select(randomDirVec.div(randomDirLength), vec3(0, 0, 0));
6155
- const startPosLength = shapeOffset.length();
6156
- const startPosDir = startPosLength.greaterThan(1e-3).select(shapeOffset.div(startPosLength), vec3(0, 0, 0));
6157
- const dir = useStartPosAsDir.select(startPosDir, randomDir);
6158
- const randomSpeed = mix(
6159
- uniforms.speedMin,
6160
- uniforms.speedMax,
6161
- randSpeed
6162
- );
6163
- const normalVelocity = dir.mul(randomSpeed);
6164
- velocity.assign(
6165
- useAttractToCenter.select(attractVelocity, normalVelocity)
6166
- );
6167
- const randomSize = mix(uniforms.sizeMin, uniforms.sizeMax, randSize);
6168
- particleSize.assign(randomSize);
6169
- const rotX = mix(
6170
- uniforms.rotationMinX,
6171
- uniforms.rotationMaxX,
6172
- randRotationX
6173
- );
6174
- const rotY = mix(
6175
- uniforms.rotationMinY,
6176
- uniforms.rotationMaxY,
6177
- randRotationY
6178
- );
6179
- const rotZ = mix(
6180
- uniforms.rotationMinZ,
6181
- uniforms.rotationMaxZ,
6182
- randRotationZ
6183
- );
6184
- particleRotation.assign(vec3(rotX, rotY, rotZ));
6185
- const startColorIdx = floor(
6186
- randColorStart.mul(uniforms.colorStartCount)
6187
- );
6188
- const selectedStartColor = selectColor(
6189
- startColorIdx,
6190
- uniforms.colorStart0,
6191
- uniforms.colorStart1,
6192
- uniforms.colorStart2,
6193
- uniforms.colorStart3,
6194
- uniforms.colorStart4,
6195
- uniforms.colorStart5,
6196
- uniforms.colorStart6,
6197
- uniforms.colorStart7
6198
- );
6199
- pColorStart.assign(selectedStartColor);
6200
- const endColorIdx = floor(randColorEnd.mul(uniforms.colorEndCount));
6201
- const selectedEndColor = selectColor(
6202
- endColorIdx,
6203
- uniforms.colorEnd0,
6204
- uniforms.colorEnd1,
6205
- uniforms.colorEnd2,
6206
- uniforms.colorEnd3,
6207
- uniforms.colorEnd4,
6208
- uniforms.colorEnd5,
6209
- uniforms.colorEnd6,
6210
- uniforms.colorEnd7
6211
- );
6212
- pColorEnd.assign(selectedEndColor);
6213
- lifetime2.assign(float(1));
6214
- });
6215
- })().compute(activeMaxParticles);
6216
- }, [
6217
- activeMaxParticles,
6218
- positions,
6219
- velocities,
6220
- lifetimes,
6221
- fadeRates,
6222
- particleSizes,
6223
- particleRotations,
6224
- particleColorStarts,
6225
- particleColorEnds,
6226
- uniforms,
6227
- selectColor
6228
- ]);
6229
- const computeUpdate = useMemo(() => {
6230
- return Fn(() => {
6231
- const position2 = positions.element(instanceIndex);
6232
- const velocity = velocities.element(instanceIndex);
6233
- const lifetime2 = lifetimes.element(instanceIndex);
6234
- const fadeRate = fadeRates.element(instanceIndex);
6235
- const particleRotation = particleRotations.element(instanceIndex);
6236
- const particleSize = particleSizes.element(instanceIndex);
6237
- const dt = uniforms.deltaTime;
6238
- If(lifetime2.greaterThan(0), () => {
6239
- const gravityMultiplier = float(1).add(
6240
- particleSize.mul(uniforms.sizeBasedGravity)
6241
- );
6242
- velocity.addAssign(uniforms.gravity.mul(dt).mul(gravityMultiplier));
6243
- const progress = float(1).sub(lifetime2);
6244
- const velocityCurveSample = texture(
6245
- curveTexture,
6246
- vec2(progress, float(0.5))
6247
- ).z;
6248
- const speedScale = uniforms.velocityCurveEnabled.greaterThan(0.5).select(
6249
- // Use velocity curve directly as speed multiplier
6250
- velocityCurveSample,
6251
- // Legacy friction behavior
6252
- (() => {
6253
- const easingType = uniforms.frictionEasingType;
6254
- const easedProgress = easingType.lessThan(0.5).select(
6255
- progress,
6256
- easingType.lessThan(1.5).select(
6257
- progress.mul(progress),
6258
- easingType.lessThan(2.5).select(
6259
- float(1).sub(
6260
- float(1).sub(progress).mul(float(1).sub(progress))
6261
- ),
6262
- progress.lessThan(0.5).select(
6263
- float(2).mul(progress).mul(progress),
6264
- float(1).sub(
6265
- float(-2).mul(progress).add(2).pow(2).div(2)
6266
- )
6267
- )
6268
- )
6269
- )
6270
- );
6271
- const currentIntensity = mix(
6272
- uniforms.frictionIntensityStart,
6273
- uniforms.frictionIntensityEnd,
6274
- easedProgress
6275
- );
6276
- return float(1).sub(currentIntensity.mul(0.9));
6277
- })()
6278
- );
6279
- const turbIntensity = uniforms.turbulenceIntensity;
6280
- const turbFreq = uniforms.turbulenceFrequency;
6281
- const turbTime = uniforms.turbulenceTime;
6282
- If(turbIntensity.greaterThan(1e-3), () => {
6283
- const noisePos = position2.mul(turbFreq).add(vec3(turbTime, turbTime.mul(0.7), turbTime.mul(1.3)));
6284
- const eps = float(0.01);
6285
- const nPosX = mx_noise_vec3(noisePos.add(vec3(eps, 0, 0)));
6286
- const nNegX = mx_noise_vec3(noisePos.sub(vec3(eps, 0, 0)));
6287
- const nPosY = mx_noise_vec3(noisePos.add(vec3(0, eps, 0)));
6288
- const nNegY = mx_noise_vec3(noisePos.sub(vec3(0, eps, 0)));
6289
- const nPosZ = mx_noise_vec3(noisePos.add(vec3(0, 0, eps)));
6290
- const nNegZ = mx_noise_vec3(noisePos.sub(vec3(0, 0, eps)));
6291
- const dFx_dy = nPosY.x.sub(nNegY.x).div(eps.mul(2));
6292
- const dFx_dz = nPosZ.x.sub(nNegZ.x).div(eps.mul(2));
6293
- const dFy_dx = nPosX.y.sub(nNegX.y).div(eps.mul(2));
6294
- const dFy_dz = nPosZ.y.sub(nNegZ.y).div(eps.mul(2));
6295
- const dFz_dx = nPosX.z.sub(nNegX.z).div(eps.mul(2));
6296
- const dFz_dy = nPosY.z.sub(nNegY.z).div(eps.mul(2));
6297
- const curlX = dFz_dy.sub(dFy_dz);
6298
- const curlY = dFx_dz.sub(dFz_dx);
6299
- const curlZ = dFy_dx.sub(dFx_dy);
6300
- const curl = vec3(curlX, curlY, curlZ);
6301
- velocity.addAssign(curl.mul(turbIntensity).mul(uniforms.deltaTime));
6302
- });
6303
- const attractorCount = uniforms.attractorCount;
6304
- const applyAttractor = (aPos, aStrength, aRadius, aType, aAxis) => {
6305
- If(aStrength.abs().greaterThan(1e-3), () => {
6306
- const toAttractor = aPos.sub(position2);
6307
- const dist = toAttractor.length();
6308
- const safeDist = dist.max(0.01);
6309
- const direction2 = toAttractor.div(safeDist);
6310
- const falloff = aRadius.greaterThan(1e-3).select(
6311
- float(1).sub(dist.div(aRadius)).max(0),
6312
- // Linear falloff within radius
6313
- float(1).div(safeDist.mul(safeDist).add(1))
6314
- // Inverse square falloff (softened)
6315
- );
6316
- const force = aType.lessThan(0.5).select(
6317
- // Point attractor: force along direction to attractor
6318
- direction2.mul(aStrength).mul(falloff),
6319
- // Vortex: force perpendicular to both (toAttractor) and (axis)
6320
- // cross(axis, toAttractor) gives tangent direction
6321
- (() => {
6322
- const tangent = vec3(
6323
- aAxis.y.mul(toAttractor.z).sub(aAxis.z.mul(toAttractor.y)),
6324
- aAxis.z.mul(toAttractor.x).sub(aAxis.x.mul(toAttractor.z)),
6325
- aAxis.x.mul(toAttractor.y).sub(aAxis.y.mul(toAttractor.x))
6326
- );
6327
- const tangentLen = tangent.length().max(1e-3);
6328
- return tangent.div(tangentLen).mul(aStrength).mul(falloff);
6329
- })()
6330
- );
6331
- velocity.addAssign(force.mul(uniforms.deltaTime));
6332
- });
6333
- };
6334
- If(attractorCount.greaterThan(0), () => {
6335
- applyAttractor(
6336
- uniforms.attractor0Pos,
6337
- uniforms.attractor0Strength,
6338
- uniforms.attractor0Radius,
6339
- uniforms.attractor0Type,
6340
- uniforms.attractor0Axis
6341
- );
6342
- });
6343
- If(attractorCount.greaterThan(1), () => {
6344
- applyAttractor(
6345
- uniforms.attractor1Pos,
6346
- uniforms.attractor1Strength,
6347
- uniforms.attractor1Radius,
6348
- uniforms.attractor1Type,
6349
- uniforms.attractor1Axis
6350
- );
6351
- });
6352
- If(attractorCount.greaterThan(2), () => {
6353
- applyAttractor(
6354
- uniforms.attractor2Pos,
6355
- uniforms.attractor2Strength,
6356
- uniforms.attractor2Radius,
6357
- uniforms.attractor2Type,
6358
- uniforms.attractor2Axis
6359
- );
6360
- });
6361
- If(attractorCount.greaterThan(3), () => {
6362
- applyAttractor(
6363
- uniforms.attractor3Pos,
6364
- uniforms.attractor3Strength,
6365
- uniforms.attractor3Radius,
6366
- uniforms.attractor3Type,
6367
- uniforms.attractor3Axis
6368
- );
6369
- });
6370
- position2.addAssign(velocity.mul(dt).mul(speedScale));
6371
- If(uniforms.collisionEnabled.greaterThan(0.5), () => {
6372
- const planeY = uniforms.collisionPlaneY;
6373
- const bounce = uniforms.collisionBounce;
6374
- const friction2 = uniforms.collisionFriction;
6375
- const shouldDie = uniforms.collisionDie;
6376
- If(position2.y.lessThan(planeY), () => {
6377
- If(shouldDie.greaterThan(0.5), () => {
6378
- lifetime2.assign(float(0));
6379
- position2.y.assign(float(-1e3));
6380
- }).Else(() => {
6381
- position2.y.assign(planeY);
6382
- velocity.y.assign(velocity.y.abs().mul(bounce));
6383
- velocity.x.mulAssign(friction2);
6384
- velocity.z.mulAssign(friction2);
6385
- });
6386
- });
6387
- });
6388
- const idx = float(instanceIndex);
6389
- const rotSpeedX = mix(
6390
- uniforms.rotationSpeedMinX,
6391
- uniforms.rotationSpeedMaxX,
6392
- hash(idx.add(8888))
6393
- );
6394
- const rotSpeedY = mix(
6395
- uniforms.rotationSpeedMinY,
6396
- uniforms.rotationSpeedMaxY,
6397
- hash(idx.add(9999))
6398
- );
6399
- const rotSpeedZ = mix(
6400
- uniforms.rotationSpeedMinZ,
6401
- uniforms.rotationSpeedMaxZ,
6402
- hash(idx.add(10101))
6403
- );
6404
- const rotSpeedCurveSample = texture(
6405
- curveTexture,
6406
- vec2(progress, float(0.5))
6407
- ).w;
6408
- const rotSpeedMultiplier = uniforms.rotationSpeedCurveEnabled.greaterThan(0.5).select(rotSpeedCurveSample, float(1));
6409
- particleRotation.addAssign(
6410
- vec3(rotSpeedX, rotSpeedY, rotSpeedZ).mul(uniforms.deltaTime).mul(rotSpeedMultiplier)
6411
- );
6412
- lifetime2.subAssign(fadeRate.mul(uniforms.deltaTime));
6413
- If(lifetime2.lessThanEqual(0), () => {
6414
- lifetime2.assign(float(0));
6415
- position2.y.assign(float(-1e3));
6416
- });
6417
- });
6418
- })().compute(activeMaxParticles);
6419
- }, [
6420
- activeMaxParticles,
6421
- positions,
6422
- velocities,
6423
- lifetimes,
6424
- fadeRates,
6425
- particleSizes,
6426
- particleRotations,
6427
- uniforms,
6428
- curveTexture
6429
- ]);
6430
- const material = useMemo(() => {
6431
- const lifetime2 = lifetimes.element(instanceIndex);
6432
- const particleSize = particleSizes.element(instanceIndex);
6433
- const particleRotation = particleRotations.element(instanceIndex);
6434
- const pColorStart = particleColorStarts.element(instanceIndex);
6435
- const pColorEnd = particleColorEnds.element(instanceIndex);
6436
- const particlePos = positions.element(instanceIndex);
6437
- const particleVel = velocities.element(instanceIndex);
6438
- const progress = float(1).sub(lifetime2);
6439
- const currentColor = mix(pColorStart, pColorEnd, progress);
6440
- const intensifiedColor = currentColor.mul(uniforms.intensity);
6441
- const curveSample = texture(curveTexture, vec2(progress, float(0.5)));
6442
- const sizeMultiplier = uniforms.fadeSizeCurveEnabled.greaterThan(0.5).select(
6443
- curveSample.x,
6444
- // R channel - size curve value
6445
- mix(uniforms.fadeSizeStart, uniforms.fadeSizeEnd, progress)
6446
- // Linear interpolation of fadeSize
6447
- );
6448
- const opacityMultiplier = uniforms.fadeOpacityCurveEnabled.greaterThan(0.5).select(
6449
- curveSample.y,
6450
- // G channel - opacity curve value
6451
- mix(uniforms.fadeOpacityStart, uniforms.fadeOpacityEnd, progress)
6452
- // Linear interpolation of fadeOpacity
6453
- );
6454
- let sampleUV = uv();
6455
- if (flipbook && alphaMap) {
6456
- const rows = float(flipbook.rows || 1);
6457
- const columns = float(flipbook.columns || 1);
6458
- const totalFrames = rows.mul(columns);
6459
- const frameIndex = floor(
6460
- progress.mul(totalFrames).min(totalFrames.sub(1))
6461
- );
6462
- const col = mod(frameIndex, columns);
6463
- const row = floor(frameIndex.div(columns));
6464
- const scaledUV = uv().div(vec2(columns, rows));
6465
- const offsetX = col.div(columns);
6466
- const offsetY = rows.sub(1).sub(row).div(rows);
6467
- sampleUV = scaledUV.add(vec2(offsetX, offsetY));
6468
- }
6469
- let shapeMask;
6470
- if (activeGeometry) {
6471
- shapeMask = float(1);
6472
- } else if (alphaMap) {
6473
- const alphaSample = texture(alphaMap, sampleUV);
6474
- shapeMask = alphaSample.r;
6475
- } else {
6476
- const dist = uv().mul(2).sub(1).length();
6477
- switch (activeAppearance) {
6478
- case Appearance.DEFAULT:
6479
- shapeMask = float(1);
6480
- break;
6481
- case Appearance.CIRCULAR:
6482
- shapeMask = step(dist, float(1));
6483
- break;
6484
- case Appearance.GRADIENT:
6485
- default:
6486
- shapeMask = float(1).sub(dist).max(0);
6487
- break;
6488
- }
6489
- }
6490
- const baseOpacity = opacityMultiplier.mul(shapeMask).mul(lifetime2.greaterThan(1e-3).select(float(1), float(0)));
6491
- const particleData = {
6492
- progress,
6493
- // 0→1 over lifetime
6494
- lifetime: lifetime2,
6495
- // 1→0 over lifetime (inverse of progress)
6496
- position: particlePos,
6497
- // vec3 world position
6498
- velocity: particleVel,
6499
- // vec3 velocity
6500
- size: particleSize,
6501
- // float size
6502
- rotation: particleRotation,
6503
- // vec3 rotation (x, y, z)
6504
- colorStart: pColorStart,
6505
- // vec3 start color
6506
- colorEnd: pColorEnd,
6507
- // vec3 end color
6508
- color: currentColor,
6509
- // vec3 interpolated color
6510
- intensifiedColor,
6511
- // vec3 color * intensity
6512
- shapeMask,
6513
- // float shape/alpha mask
6514
- index: instanceIndex
6515
- // particle index (for randomization)
6516
- };
6517
- let finalOpacity = opacityNode ? baseOpacity.mul(
6518
- typeof opacityNode === "function" ? opacityNode(particleData) : opacityNode
6519
- ) : baseOpacity;
6520
- if (softParticles) {
6521
- const sceneDepth = viewportDepthTexture(screenUV).x;
6522
- const particleViewZ = positionView.z.negate();
6523
- const near = cameraNear;
6524
- const far = cameraFar;
6525
- const sceneViewZ = near.mul(far).mul(2).div(far.add(near).sub(sceneDepth.mul(2).sub(1).mul(far.sub(near))));
6526
- const depthDiff = sceneViewZ.sub(particleViewZ);
6527
- const softFade = clamp(depthDiff.div(uniforms.softDistance), 0, 1);
6528
- finalOpacity = finalOpacity.mul(softFade);
6529
- }
6530
- if (activeGeometry) {
6531
- let mat;
6532
- switch (activeLighting) {
6533
- case Lighting.BASIC:
6534
- mat = new THREE2.MeshBasicNodeMaterial();
6535
- break;
6536
- case Lighting.PHYSICAL:
6537
- mat = new THREE2.MeshPhysicalNodeMaterial();
6538
- break;
6539
- case Lighting.STANDARD:
6540
- default:
6541
- mat = new THREE2.MeshStandardNodeMaterial();
6542
- break;
6543
- }
6544
- const velocityCurveValue = curveSample.z;
6545
- const effectiveVelocityMultiplier = uniforms.velocityCurveEnabled.greaterThan(0.5).select(velocityCurveValue, float(1));
6546
- const effectiveSpeed = particleVel.length().mul(effectiveVelocityMultiplier);
6547
- const stretchAmount = uniforms.stretchEnabled.greaterThan(0.5).select(
6548
- float(1).add(effectiveSpeed.mul(uniforms.stretchFactor)).min(uniforms.stretchMax),
6549
- float(1)
6550
- );
6551
- const baseScale = particleSize.mul(sizeMultiplier);
6552
- const axisType = uniforms.orientAxisType;
6553
- const axisSign = axisType.lessThan(3).select(float(1), float(-1));
6554
- const axisIndex = axisType.mod(3);
6555
- const stretchedLocal = uniforms.stretchEnabled.greaterThan(0.5).select(
6556
- axisIndex.lessThan(0.5).select(
6557
- // X axis stretch
6558
- vec3(
6559
- positionLocal.x.mul(stretchAmount),
6560
- positionLocal.y,
6561
- positionLocal.z
6562
- ),
6563
- axisIndex.lessThan(1.5).select(
6564
- // Y axis stretch
6565
- vec3(
6566
- positionLocal.x,
6567
- positionLocal.y.mul(stretchAmount),
6568
- positionLocal.z
6569
- ),
6570
- // Z axis stretch
6571
- vec3(
6572
- positionLocal.x,
6573
- positionLocal.y,
6574
- positionLocal.z.mul(stretchAmount)
6575
- )
6576
- )
6577
- ),
6578
- positionLocal
6579
- );
6580
- let rotatedPos;
6581
- if (activeOrientToDirection) {
6582
- const velLen = particleVel.length().max(1e-4);
6583
- const velDir = particleVel.div(velLen).mul(axisSign);
6584
- const localAxis = axisIndex.lessThan(0.5).select(
6585
- vec3(1, 0, 0),
6586
- // X axis
6587
- axisIndex.lessThan(1.5).select(
6588
- vec3(0, 1, 0),
6589
- // Y axis
6590
- vec3(0, 0, 1)
6591
- // Z axis
6592
- )
6593
- );
6594
- const dotProduct = localAxis.dot(velDir).clamp(-1, 1);
6595
- const crossProduct = localAxis.cross(velDir);
6596
- const crossLen = crossProduct.length();
6597
- const needsRotation = crossLen.greaterThan(1e-4);
6598
- const rotAxis = needsRotation.select(
6599
- crossProduct.div(crossLen),
6600
- vec3(0, 1, 0)
6601
- // Fallback axis (won't be used if no rotation needed)
6602
- );
6603
- const cosAngle = dotProduct;
6604
- const sinAngle = crossLen;
6605
- const oneMinusCos = float(1).sub(cosAngle);
6606
- const v = stretchedLocal;
6607
- const kDotV = rotAxis.dot(v);
6608
- const kCrossV = rotAxis.cross(v);
6609
- const rotatedByAxis = needsRotation.select(
6610
- v.mul(cosAngle).add(kCrossV.mul(sinAngle)).add(rotAxis.mul(kDotV.mul(oneMinusCos))),
6611
- // If vectors are nearly aligned, check if they're opposite (dot ≈ -1)
6612
- dotProduct.lessThan(-0.99).select(
6613
- v.negate(),
6614
- // Flip 180° - just negate
6615
- v
6616
- // Already aligned, no rotation
6617
- )
6618
- );
6619
- rotatedPos = rotatedByAxis;
6620
- } else {
6621
- const rotX = particleRotation.x;
6622
- const rotY = particleRotation.y;
6623
- const rotZ = particleRotation.z;
6624
- const cX = cos(rotX);
6625
- const sX = sin(rotX);
6626
- const afterX = vec3(
6627
- stretchedLocal.x,
6628
- stretchedLocal.y.mul(cX).sub(stretchedLocal.z.mul(sX)),
6629
- stretchedLocal.y.mul(sX).add(stretchedLocal.z.mul(cX))
6630
- );
6631
- const cY = cos(rotY);
6632
- const sY = sin(rotY);
6633
- const afterY = vec3(
6634
- afterX.x.mul(cY).add(afterX.z.mul(sY)),
6635
- afterX.y,
6636
- afterX.z.mul(cY).sub(afterX.x.mul(sY))
6637
- );
6638
- const cZ = cos(rotZ);
6639
- const sZ = sin(rotZ);
6640
- rotatedPos = vec3(
6641
- afterY.x.mul(cZ).sub(afterY.y.mul(sZ)),
6642
- afterY.x.mul(sZ).add(afterY.y.mul(cZ)),
6643
- afterY.z
6644
- );
6645
- }
6646
- const scaledPos = rotatedPos.mul(baseScale);
6647
- mat.positionNode = scaledPos.add(particlePos);
6648
- const defaultColor = vec4(intensifiedColor, finalOpacity);
6649
- mat.colorNode = colorNode ? typeof colorNode === "function" ? colorNode(particleData, defaultColor) : colorNode : defaultColor;
6650
- mat.transparent = true;
6651
- mat.depthWrite = false;
6652
- mat.blending = blending;
6653
- mat.side = THREE2.DoubleSide;
6654
- if (backdropNode) {
6655
- mat.backdropNode = typeof backdropNode === "function" ? backdropNode(particleData) : backdropNode;
6656
- }
6657
- if (castShadowNode) {
6658
- mat.castShadowNode = typeof castShadowNode === "function" ? castShadowNode(particleData) : castShadowNode;
6659
- }
6660
- if (alphaTestNode) {
6661
- mat.alphaTestNode = typeof alphaTestNode === "function" ? alphaTestNode(particleData) : alphaTestNode;
6662
- }
6663
- return mat;
6664
- } else {
6665
- const mat = new THREE2.SpriteNodeMaterial();
6666
- const defaultColor = vec4(intensifiedColor, finalOpacity);
6667
- mat.colorNode = colorNode ? typeof colorNode === "function" ? colorNode(particleData, defaultColor) : colorNode : defaultColor;
6668
- mat.positionNode = positions.toAttribute();
6669
- mat.scaleNode = particleSize.mul(sizeMultiplier);
6670
- mat.rotationNode = particleRotation.y;
6671
- mat.transparent = true;
6672
- mat.depthWrite = false;
6673
- mat.blending = blending;
6674
- if (backdropNode) {
6675
- mat.backdropNode = typeof backdropNode === "function" ? backdropNode(particleData) : backdropNode;
6676
- }
6677
- if (castShadowNode) {
6678
- mat.castShadowNode = typeof castShadowNode === "function" ? castShadowNode(particleData) : castShadowNode;
6679
- }
6680
- if (alphaTestNode) {
6681
- mat.alphaTestNode = typeof alphaTestNode === "function" ? alphaTestNode(particleData) : alphaTestNode;
6682
- }
6683
- return mat;
6684
- }
6685
- }, [
6686
- positions,
6687
- velocities,
6688
- lifetimes,
6689
- particleSizes,
6690
- particleRotations,
6691
- particleColorStarts,
6692
- particleColorEnds,
6693
- uniforms,
6694
- activeAppearance,
6695
- alphaMap,
6696
- flipbook,
6697
- blending,
6698
- activeGeometry,
6699
- activeOrientToDirection,
6700
- activeLighting,
6701
- backdropNode,
6702
- opacityNode,
6703
- colorNode,
6704
- alphaTestNode,
6705
- castShadowNode,
6706
- softParticles,
6707
- curveTexture
6708
- ]);
5697
+ const computeInit = useMemo(
5698
+ () => createInitCompute(storage, activeMaxParticles),
5699
+ [storage, activeMaxParticles]
5700
+ );
5701
+ const computeSpawn = useMemo(
5702
+ () => createSpawnCompute(storage, uniforms, activeMaxParticles),
5703
+ [storage, uniforms, activeMaxParticles]
5704
+ );
5705
+ const computeUpdate = useMemo(
5706
+ () => createUpdateCompute(storage, uniforms, curveTexture, activeMaxParticles),
5707
+ [storage, uniforms, curveTexture, activeMaxParticles]
5708
+ );
5709
+ const material = useMemo(
5710
+ () => createParticleMaterial(storage, uniforms, curveTexture, {
5711
+ alphaMap,
5712
+ flipbook,
5713
+ appearance: activeAppearance,
5714
+ lighting: activeLighting,
5715
+ softParticles,
5716
+ geometry: activeGeometry,
5717
+ orientToDirection: activeOrientToDirection,
5718
+ shadow: activeShadow,
5719
+ blending,
5720
+ opacityNode,
5721
+ colorNode,
5722
+ backdropNode,
5723
+ alphaTestNode,
5724
+ castShadowNode
5725
+ }),
5726
+ [
5727
+ storage,
5728
+ uniforms,
5729
+ curveTexture,
5730
+ activeAppearance,
5731
+ alphaMap,
5732
+ flipbook,
5733
+ blending,
5734
+ activeGeometry,
5735
+ activeOrientToDirection,
5736
+ activeLighting,
5737
+ backdropNode,
5738
+ opacityNode,
5739
+ colorNode,
5740
+ alphaTestNode,
5741
+ castShadowNode,
5742
+ softParticles,
5743
+ activeShadow
5744
+ ]
5745
+ );
6709
5746
  const renderObject = useMemo(() => {
6710
5747
  if (activeGeometry) {
6711
5748
  const mesh = new THREE2.InstancedMesh(
@@ -7080,7 +6117,7 @@ var init_VFXParticles = __esm({
7080
6117
  }
7081
6118
  }
7082
6119
  if ("emitterShape" in newValues) {
7083
- uniforms.emitterShapeType.value = (_g = newValues.emitterShape) != null ? _g : EmitterShape.BOX;
6120
+ uniforms.emitterShapeType.value = (_g = newValues.emitterShape) != null ? _g : EmitterShape2.BOX;
7084
6121
  }
7085
6122
  if ("emitterRadius" in newValues) {
7086
6123
  const emitterRadiusR = toRange(newValues.emitterRadius, [0, 1]);
@@ -7633,15 +6670,15 @@ function useVFXEmitter(name) {
7633
6670
  init_react_store();
7634
6671
  export {
7635
6672
  Appearance,
7636
- AttractorType,
6673
+ AttractorType2 as AttractorType,
7637
6674
  Blending,
7638
- Easing,
6675
+ Easing2 as Easing,
7639
6676
  EmitterShape,
7640
6677
  Lighting,
7641
6678
  VFXEmitter,
7642
6679
  VFXParticles,
7643
6680
  bakeCurveToArray,
7644
- createCombinedCurveTexture,
6681
+ createCombinedCurveTexture2 as createCombinedCurveTexture,
7645
6682
  useVFXEmitter,
7646
6683
  useVFXStore
7647
6684
  };