r3f-vfx 0.0.5 → 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 +151 -1095
  2. package/package.json +3 -3
  3. package/dist/index.d.ts +0 -121
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
  }
@@ -5117,7 +5117,7 @@ ${" ".repeat(indent - 2)}}`;
5117
5117
  }
5118
5118
  });
5119
5119
 
5120
- // src/VFXParticles.jsx
5120
+ // src/VFXParticles.tsx
5121
5121
  import {
5122
5122
  forwardRef,
5123
5123
  useImperativeHandle,
@@ -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
- atan,
5153
- sqrt,
5154
- acos,
5155
- PI,
5156
- mx_noise_vec3,
5157
- screenUV,
5158
- viewportDepthTexture,
5159
- positionView,
5160
- cameraNear,
5161
- cameraFar,
5162
- clamp
5163
- } 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";
5164
5161
  import { jsx as jsx2 } from "react/jsx-runtime";
5165
- var Appearance, Blending, EmitterShape, AttractorType, Easing, Lighting, MAX_ATTRACTORS, hexToRgb, toRange, easingToType, axisToNumber, CURVE_RESOLUTION, evaluateBezierSegment, sampleCurveAtX, bakeCurveToArray, createCombinedCurveTexture, toRotation3D, VFXParticles;
5162
+ var VFXParticles;
5166
5163
  var init_VFXParticles = __esm({
5167
- "src/VFXParticles.jsx"() {
5164
+ "src/VFXParticles.tsx"() {
5168
5165
  "use strict";
5169
5166
  init_react_store();
5170
- Appearance = Object.freeze({
5171
- DEFAULT: "default",
5172
- GRADIENT: "gradient",
5173
- CIRCULAR: "circular"
5174
- });
5175
- Blending = Object.freeze({
5176
- NORMAL: THREE2.NormalBlending,
5177
- ADDITIVE: THREE2.AdditiveBlending,
5178
- MULTIPLY: THREE2.MultiplyBlending,
5179
- SUBTRACTIVE: THREE2.SubtractiveBlending
5180
- });
5181
- EmitterShape = Object.freeze({
5182
- POINT: 0,
5183
- // Single point emission
5184
- BOX: 1,
5185
- // Box/cube volume (uses startPosition ranges)
5186
- SPHERE: 2,
5187
- // Sphere surface or volume
5188
- CONE: 3,
5189
- // Cone shape (great for fire, fountains)
5190
- DISK: 4,
5191
- // Flat disk/circle
5192
- EDGE: 5
5193
- // Line between two points
5194
- });
5195
- AttractorType = Object.freeze({
5196
- POINT: 0,
5197
- // Pull toward a point (or push if negative strength)
5198
- VORTEX: 1
5199
- // Swirl around an axis
5200
- });
5201
- Easing = Object.freeze({
5202
- LINEAR: 0,
5203
- EASE_IN: 1,
5204
- EASE_OUT: 2,
5205
- EASE_IN_OUT: 3
5206
- });
5207
- Lighting = Object.freeze({
5208
- BASIC: "basic",
5209
- // No lighting, flat colors (MeshBasicNodeMaterial)
5210
- STANDARD: "standard",
5211
- // Standard PBR (MeshStandardNodeMaterial)
5212
- PHYSICAL: "physical"
5213
- // Advanced PBR with clearcoat, transmission, etc. (MeshPhysicalNodeMaterial)
5214
- });
5215
- MAX_ATTRACTORS = 4;
5216
- hexToRgb = (hex) => {
5217
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
5218
- return result ? [
5219
- parseInt(result[1], 16) / 255,
5220
- parseInt(result[2], 16) / 255,
5221
- parseInt(result[3], 16) / 255
5222
- ] : [1, 1, 1];
5223
- };
5224
- toRange = (value, defaultVal = [0, 0]) => {
5225
- if (value === void 0 || value === null) return defaultVal;
5226
- if (Array.isArray(value))
5227
- return value.length === 2 ? value : [value[0], value[0]];
5228
- return [value, value];
5229
- };
5230
- easingToType = (easing) => {
5231
- if (typeof easing === "number") return easing;
5232
- switch (easing) {
5233
- case "easeIn":
5234
- return 1;
5235
- case "easeOut":
5236
- return 2;
5237
- case "easeInOut":
5238
- return 3;
5239
- default:
5240
- return 0;
5241
- }
5242
- };
5243
- axisToNumber = (axis) => {
5244
- switch (axis) {
5245
- case "x":
5246
- case "+x":
5247
- case "X":
5248
- case "+X":
5249
- return 0;
5250
- case "y":
5251
- case "+y":
5252
- case "Y":
5253
- case "+Y":
5254
- return 1;
5255
- case "z":
5256
- case "+z":
5257
- case "Z":
5258
- case "+Z":
5259
- return 2;
5260
- case "-x":
5261
- case "-X":
5262
- return 3;
5263
- case "-y":
5264
- case "-Y":
5265
- return 4;
5266
- case "-z":
5267
- case "-Z":
5268
- return 5;
5269
- default:
5270
- return 2;
5271
- }
5272
- };
5273
- CURVE_RESOLUTION = 256;
5274
- evaluateBezierSegment = (t, p0, p1, h0Out, h1In) => {
5275
- const cp0 = p0;
5276
- const cp1 = [p0[0] + ((h0Out == null ? void 0 : h0Out[0]) || 0), p0[1] + ((h0Out == null ? void 0 : h0Out[1]) || 0)];
5277
- const cp2 = [p1[0] + ((h1In == null ? void 0 : h1In[0]) || 0), p1[1] + ((h1In == null ? void 0 : h1In[1]) || 0)];
5278
- const cp3 = p1;
5279
- const mt = 1 - t;
5280
- const mt2 = mt * mt;
5281
- const mt3 = mt2 * mt;
5282
- const t2 = t * t;
5283
- const t3 = t2 * t;
5284
- return [
5285
- mt3 * cp0[0] + 3 * mt2 * t * cp1[0] + 3 * mt * t2 * cp2[0] + t3 * cp3[0],
5286
- mt3 * cp0[1] + 3 * mt2 * t * cp1[1] + 3 * mt * t2 * cp2[1] + t3 * cp3[1]
5287
- ];
5288
- };
5289
- sampleCurveAtX = (x, points) => {
5290
- var _a, _b, _c, _d;
5291
- if (!points || points.length < 2) return x;
5292
- if (!((_a = points[0]) == null ? void 0 : _a.pos) || !((_b = points[points.length - 1]) == null ? void 0 : _b.pos)) return x;
5293
- let segmentIdx = 0;
5294
- for (let i = 0; i < points.length - 1; i++) {
5295
- 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]) {
5296
- segmentIdx = i;
5297
- break;
5298
- }
5299
- }
5300
- const p0 = points[segmentIdx];
5301
- const p1 = points[segmentIdx + 1];
5302
- if (!(p0 == null ? void 0 : p0.pos) || !(p1 == null ? void 0 : p1.pos)) return x;
5303
- let tLow = 0, tHigh = 1, t = 0.5;
5304
- for (let iter = 0; iter < 20; iter++) {
5305
- const [px] = evaluateBezierSegment(
5306
- t,
5307
- p0.pos,
5308
- p1.pos,
5309
- p0.handleOut,
5310
- p1.handleIn
5311
- );
5312
- if (Math.abs(px - x) < 1e-4) break;
5313
- if (px < x) {
5314
- tLow = t;
5315
- } else {
5316
- tHigh = t;
5317
- }
5318
- t = (tLow + tHigh) / 2;
5319
- }
5320
- const [, py] = evaluateBezierSegment(
5321
- t,
5322
- p0.pos,
5323
- p1.pos,
5324
- p0.handleOut,
5325
- p1.handleIn
5326
- );
5327
- return Math.max(-0.5, Math.min(1.5, py));
5328
- };
5329
- bakeCurveToArray = (curveData, resolution = CURVE_RESOLUTION) => {
5330
- const data = new Float32Array(resolution);
5331
- if (!(curveData == null ? void 0 : curveData.points) || !Array.isArray(curveData.points) || curveData.points.length < 2) {
5332
- for (let i = 0; i < resolution; i++) {
5333
- data[i] = 1 - i / (resolution - 1);
5334
- }
5335
- return data;
5336
- }
5337
- const firstPoint = curveData.points[0];
5338
- const lastPoint = curveData.points[curveData.points.length - 1];
5339
- if (!(firstPoint == null ? void 0 : firstPoint.pos) || !(lastPoint == null ? void 0 : lastPoint.pos) || !Array.isArray(firstPoint.pos) || !Array.isArray(lastPoint.pos)) {
5340
- for (let i = 0; i < resolution; i++) {
5341
- data[i] = 1 - i / (resolution - 1);
5342
- }
5343
- return data;
5344
- }
5345
- for (let i = 0; i < resolution; i++) {
5346
- const x = i / (resolution - 1);
5347
- data[i] = sampleCurveAtX(x, curveData.points);
5348
- }
5349
- return data;
5350
- };
5351
- createCombinedCurveTexture = (sizeCurve, opacityCurve, velocityCurve, rotationSpeedCurve) => {
5352
- const sizeData = bakeCurveToArray(sizeCurve);
5353
- const opacityData = bakeCurveToArray(opacityCurve);
5354
- const velocityData = bakeCurveToArray(velocityCurve);
5355
- const rotationSpeedData = bakeCurveToArray(rotationSpeedCurve);
5356
- const rgba = new Float32Array(CURVE_RESOLUTION * 4);
5357
- for (let i = 0; i < CURVE_RESOLUTION; i++) {
5358
- rgba[i * 4] = sizeData[i];
5359
- rgba[i * 4 + 1] = opacityData[i];
5360
- rgba[i * 4 + 2] = velocityData[i];
5361
- rgba[i * 4 + 3] = rotationSpeedData[i];
5362
- }
5363
- const tex = new THREE2.DataTexture(
5364
- rgba,
5365
- CURVE_RESOLUTION,
5366
- 1,
5367
- THREE2.RGBAFormat,
5368
- THREE2.FloatType
5369
- );
5370
- tex.minFilter = THREE2.LinearFilter;
5371
- tex.magFilter = THREE2.LinearFilter;
5372
- tex.wrapS = THREE2.ClampToEdgeWrapping;
5373
- tex.needsUpdate = true;
5374
- return tex;
5375
- };
5376
- toRotation3D = (value) => {
5377
- if (value === void 0 || value === null)
5378
- return [
5379
- [0, 0],
5380
- [0, 0],
5381
- [0, 0]
5382
- ];
5383
- if (typeof value === "number")
5384
- return [
5385
- [value, value],
5386
- [value, value],
5387
- [value, value]
5388
- ];
5389
- if (Array.isArray(value)) {
5390
- if (Array.isArray(value[0])) {
5391
- return [
5392
- toRange(value[0], [0, 0]),
5393
- toRange(value[1], [0, 0]),
5394
- toRange(value[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)
@@ -5510,7 +5271,7 @@ var init_VFXParticles = __esm({
5510
5271
  debug = false
5511
5272
  }, ref) {
5512
5273
  const { gl: renderer } = useThree();
5513
- const spriteRef = useRef2();
5274
+ const spriteRef = useRef2(null);
5514
5275
  const initialized = useRef2(false);
5515
5276
  const nextIndex = useRef2(0);
5516
5277
  const [emitting, setEmitting] = useState2(autoStart);
@@ -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]);
@@ -5618,8 +5378,9 @@ var init_VFXParticles = __esm({
5618
5378
  return [0, 0];
5619
5379
  }, [friction]);
5620
5380
  const frictionEasingType = useMemo(() => {
5381
+ var _a;
5621
5382
  if (typeof friction === "object" && friction !== null && "easing" in friction) {
5622
- return easingToType(friction.easing);
5383
+ return easingToType((_a = friction.easing) != null ? _a : "linear");
5623
5384
  }
5624
5385
  return 0;
5625
5386
  }, [friction]);
@@ -5850,14 +5611,15 @@ var init_VFXParticles = __esm({
5850
5611
  );
5851
5612
  for (let i = 0; i < MAX_ATTRACTORS; i++) {
5852
5613
  const a = attractorList[i];
5614
+ const u = uniforms;
5853
5615
  if (a) {
5854
- uniforms[`attractor${i}Pos`].value.set(...(_d = a.position) != null ? _d : [0, 0, 0]);
5855
- uniforms[`attractor${i}Strength`].value = (_e = a.strength) != null ? _e : 1;
5856
- uniforms[`attractor${i}Radius`].value = (_f = a.radius) != null ? _f : 0;
5857
- uniforms[`attractor${i}Type`].value = a.type === "vortex" ? 1 : 0;
5858
- uniforms[`attractor${i}Axis`].value.set(...(_g = a.axis) != null ? _g : [0, 1, 0]).normalize();
5616
+ u[`attractor${i}Pos`].value.set(...(_d = a.position) != null ? _d : [0, 0, 0]);
5617
+ u[`attractor${i}Strength`].value = (_e = a.strength) != null ? _e : 1;
5618
+ u[`attractor${i}Radius`].value = (_f = a.radius) != null ? _f : 0;
5619
+ u[`attractor${i}Type`].value = a.type === "vortex" ? 1 : 0;
5620
+ u[`attractor${i}Axis`].value.set(...(_g = a.axis) != null ? _g : [0, 1, 0]).normalize();
5859
5621
  } else {
5860
- uniforms[`attractor${i}Strength`].value = 0;
5622
+ u[`attractor${i}Strength`].value = 0;
5861
5623
  }
5862
5624
  }
5863
5625
  uniforms.attractToCenter.value = attractToCenter ? 1 : 0;
@@ -5919,16 +5681,7 @@ var init_VFXParticles = __esm({
5919
5681
  orientAxis,
5920
5682
  stretchBySpeed
5921
5683
  ]);
5922
- const {
5923
- positions,
5924
- velocities,
5925
- lifetimes,
5926
- fadeRates,
5927
- particleSizes,
5928
- particleRotations,
5929
- particleColorStarts,
5930
- particleColorEnds
5931
- } = useMemo(
5684
+ const storage = useMemo(
5932
5685
  () => ({
5933
5686
  positions: instancedArray(activeMaxParticles, "vec3"),
5934
5687
  velocities: instancedArray(activeMaxParticles, "vec3"),
@@ -5936,774 +5689,60 @@ var init_VFXParticles = __esm({
5936
5689
  fadeRates: instancedArray(activeMaxParticles, "float"),
5937
5690
  particleSizes: instancedArray(activeMaxParticles, "float"),
5938
5691
  particleRotations: instancedArray(activeMaxParticles, "vec3"),
5939
- // X, Y, Z rotations
5940
5692
  particleColorStarts: instancedArray(activeMaxParticles, "vec3"),
5941
5693
  particleColorEnds: instancedArray(activeMaxParticles, "vec3")
5942
5694
  }),
5943
5695
  [activeMaxParticles]
5944
5696
  );
5945
- const selectColor = (idx, c0, c1, c2, c3, c4, c5, c6, c7) => {
5946
- return idx.lessThan(1).select(
5947
- c0,
5948
- idx.lessThan(2).select(
5949
- c1,
5950
- idx.lessThan(3).select(
5951
- c2,
5952
- idx.lessThan(4).select(
5953
- c3,
5954
- idx.lessThan(5).select(
5955
- c4,
5956
- idx.lessThan(6).select(c5, idx.lessThan(7).select(c6, c7))
5957
- )
5958
- )
5959
- )
5960
- )
5961
- );
5962
- };
5963
- const computeInit = useMemo(() => {
5964
- return Fn(() => {
5965
- const position2 = positions.element(instanceIndex);
5966
- const velocity = velocities.element(instanceIndex);
5967
- const lifetime2 = lifetimes.element(instanceIndex);
5968
- const fadeRate = fadeRates.element(instanceIndex);
5969
- const particleSize = particleSizes.element(instanceIndex);
5970
- const particleRotation = particleRotations.element(instanceIndex);
5971
- const colorStart2 = particleColorStarts.element(instanceIndex);
5972
- const colorEnd2 = particleColorEnds.element(instanceIndex);
5973
- position2.assign(vec3(0, -1e3, 0));
5974
- velocity.assign(vec3(0, 0, 0));
5975
- lifetime2.assign(float(0));
5976
- fadeRate.assign(float(0));
5977
- particleSize.assign(float(0));
5978
- particleRotation.assign(vec3(0, 0, 0));
5979
- colorStart2.assign(vec3(1, 1, 1));
5980
- colorEnd2.assign(vec3(1, 1, 1));
5981
- })().compute(activeMaxParticles);
5982
- }, [
5983
- activeMaxParticles,
5984
- positions,
5985
- velocities,
5986
- lifetimes,
5987
- fadeRates,
5988
- particleSizes,
5989
- particleRotations,
5990
- particleColorStarts,
5991
- particleColorEnds
5992
- ]);
5993
- const computeSpawn = useMemo(() => {
5994
- return Fn(() => {
5995
- const idx = float(instanceIndex);
5996
- const startIdx = uniforms.spawnIndexStart;
5997
- const endIdx = uniforms.spawnIndexEnd;
5998
- const seed = uniforms.spawnSeed;
5999
- const inRange = startIdx.lessThan(endIdx).select(
6000
- idx.greaterThanEqual(startIdx).and(idx.lessThan(endIdx)),
6001
- idx.greaterThanEqual(startIdx).or(idx.lessThan(endIdx))
6002
- );
6003
- If(inRange, () => {
6004
- const position2 = positions.element(instanceIndex);
6005
- const velocity = velocities.element(instanceIndex);
6006
- const lifetime2 = lifetimes.element(instanceIndex);
6007
- const fadeRate = fadeRates.element(instanceIndex);
6008
- const particleSize = particleSizes.element(instanceIndex);
6009
- const particleRotation = particleRotations.element(instanceIndex);
6010
- const pColorStart = particleColorStarts.element(instanceIndex);
6011
- const pColorEnd = particleColorEnds.element(instanceIndex);
6012
- const particleSeed = idx.add(seed);
6013
- const randDirX = hash(particleSeed.add(333));
6014
- const randDirY = hash(particleSeed.add(444));
6015
- const randDirZ = hash(particleSeed.add(555));
6016
- const randFade = hash(particleSeed.add(666));
6017
- const randColorStart = hash(particleSeed.add(777));
6018
- const randColorEnd = hash(particleSeed.add(888));
6019
- const randSize = hash(particleSeed.add(999));
6020
- const randSpeed = hash(particleSeed.add(1111));
6021
- const randRotationX = hash(particleSeed.add(2222));
6022
- const randRotationY = hash(particleSeed.add(3333));
6023
- const randRotationZ = hash(particleSeed.add(4444));
6024
- const randPosX = hash(particleSeed.add(5555));
6025
- const randPosY = hash(particleSeed.add(6666));
6026
- const randPosZ = hash(particleSeed.add(7777));
6027
- const randRadius = hash(particleSeed.add(8880));
6028
- const randTheta = hash(particleSeed.add(9990));
6029
- const randPhi = hash(particleSeed.add(10100));
6030
- const randHeight = hash(particleSeed.add(11110));
6031
- const shapeType = uniforms.emitterShapeType;
6032
- const radiusInner = uniforms.emitterRadiusInner;
6033
- const radiusOuter = uniforms.emitterRadiusOuter;
6034
- const coneAngle = uniforms.emitterAngle;
6035
- const heightMin = uniforms.emitterHeightMin;
6036
- const heightMax = uniforms.emitterHeightMax;
6037
- const surfaceOnly = uniforms.emitterSurfaceOnly;
6038
- const emitDir = uniforms.emitterDir;
6039
- const theta = randTheta.mul(PI.mul(2));
6040
- const phi = acos(float(1).sub(randPhi.mul(2)));
6041
- const radiusT = surfaceOnly.greaterThan(0.5).select(
6042
- float(1),
6043
- randRadius.pow(float(1).div(3))
6044
- // Cube root for uniform volume
6045
- );
6046
- const radius = mix(radiusInner, radiusOuter, radiusT);
6047
- const cosAngle = emitDir.y;
6048
- const axisX = emitDir.z.negate();
6049
- const axisZ = emitDir.x;
6050
- const axisLenSq = axisX.mul(axisX).add(axisZ.mul(axisZ));
6051
- const axisLen = sqrt(axisLenSq.max(1e-4));
6052
- const kx = axisX.div(axisLen);
6053
- const kz = axisZ.div(axisLen);
6054
- const sinAngle = axisLen;
6055
- const oneMinusCos = float(1).sub(cosAngle);
6056
- const rotateToEmitDir = (localPos) => {
6057
- const crossX = kz.mul(localPos.y).negate();
6058
- const crossY = kz.mul(localPos.x).sub(kx.mul(localPos.z));
6059
- const crossZ = kx.mul(localPos.y);
6060
- const kDotV = kx.mul(localPos.x).add(kz.mul(localPos.z));
6061
- const rotatedX = localPos.x.mul(cosAngle).add(crossX.mul(sinAngle)).add(kx.mul(kDotV).mul(oneMinusCos));
6062
- const rotatedY = localPos.y.mul(cosAngle).add(crossY.mul(sinAngle));
6063
- const rotatedZ = localPos.z.mul(cosAngle).add(crossZ.mul(sinAngle)).add(kz.mul(kDotV).mul(oneMinusCos));
6064
- return cosAngle.greaterThan(0.999).select(
6065
- localPos,
6066
- cosAngle.lessThan(-0.999).select(
6067
- vec3(localPos.x, localPos.y.negate(), localPos.z),
6068
- vec3(rotatedX, rotatedY, rotatedZ)
6069
- )
6070
- );
6071
- };
6072
- const boxOffsetX = mix(
6073
- uniforms.startPosMinX,
6074
- uniforms.startPosMaxX,
6075
- randPosX
6076
- );
6077
- const boxOffsetY = mix(
6078
- uniforms.startPosMinY,
6079
- uniforms.startPosMaxY,
6080
- randPosY
6081
- );
6082
- const boxOffsetZ = mix(
6083
- uniforms.startPosMinZ,
6084
- uniforms.startPosMaxZ,
6085
- randPosZ
6086
- );
6087
- const boxPos = vec3(boxOffsetX, boxOffsetY, boxOffsetZ);
6088
- const sphereX = radius.mul(sin(phi)).mul(cos(theta));
6089
- const sphereY = radius.mul(cos(phi));
6090
- const sphereZ = radius.mul(sin(phi)).mul(sin(theta));
6091
- const spherePos = vec3(sphereX, sphereY, sphereZ);
6092
- const coneH = mix(heightMin, heightMax, randHeight);
6093
- const coneR = coneH.mul(sin(coneAngle)).mul(radiusT);
6094
- const coneLocalX = coneR.mul(cos(theta));
6095
- const coneLocalY = coneH.mul(cos(coneAngle));
6096
- const coneLocalZ = coneR.mul(sin(theta));
6097
- const conePos = rotateToEmitDir(
6098
- vec3(coneLocalX, coneLocalY, coneLocalZ)
6099
- );
6100
- const diskR = surfaceOnly.greaterThan(0.5).select(
6101
- radiusOuter,
6102
- mix(radiusInner, radiusOuter, sqrt(randRadius))
6103
- // sqrt for uniform area distribution
6104
- );
6105
- const diskLocalX = diskR.mul(cos(theta));
6106
- const diskLocalZ = diskR.mul(sin(theta));
6107
- const diskPos = rotateToEmitDir(vec3(diskLocalX, float(0), diskLocalZ));
6108
- const edgeT = randPosX;
6109
- const edgePos = vec3(
6110
- mix(uniforms.startPosMinX, uniforms.startPosMaxX, edgeT),
6111
- mix(uniforms.startPosMinY, uniforms.startPosMaxY, edgeT),
6112
- mix(uniforms.startPosMinZ, uniforms.startPosMaxZ, edgeT)
6113
- );
6114
- const pointPos = vec3(0, 0, 0);
6115
- const shapeOffset = shapeType.lessThan(0.5).select(
6116
- pointPos,
6117
- // 0: POINT
6118
- shapeType.lessThan(1.5).select(
6119
- boxPos,
6120
- // 1: BOX
6121
- shapeType.lessThan(2.5).select(
6122
- spherePos,
6123
- // 2: SPHERE
6124
- shapeType.lessThan(3.5).select(
6125
- conePos,
6126
- // 3: CONE
6127
- shapeType.lessThan(4.5).select(
6128
- diskPos,
6129
- // 4: DISK
6130
- edgePos
6131
- // 5: EDGE
6132
- )
6133
- )
6134
- )
6135
- )
6136
- );
6137
- position2.assign(uniforms.spawnPosition.add(shapeOffset));
6138
- const randomFade = mix(
6139
- uniforms.lifetimeMin,
6140
- uniforms.lifetimeMax,
6141
- randFade
6142
- );
6143
- fadeRate.assign(randomFade);
6144
- const useAttractToCenter = uniforms.attractToCenter.greaterThan(0.5);
6145
- const attractVelocity = shapeOffset.negate().mul(randomFade);
6146
- const useStartPosAsDir = uniforms.startPositionAsDirection.greaterThan(0.5);
6147
- const dirX = mix(uniforms.dirMinX, uniforms.dirMaxX, randDirX);
6148
- const dirY = mix(uniforms.dirMinY, uniforms.dirMaxY, randDirY);
6149
- const dirZ = mix(uniforms.dirMinZ, uniforms.dirMaxZ, randDirZ);
6150
- const randomDirVec = vec3(dirX, dirY, dirZ);
6151
- const randomDirLength = randomDirVec.length();
6152
- const randomDir = randomDirLength.greaterThan(1e-3).select(randomDirVec.div(randomDirLength), vec3(0, 0, 0));
6153
- const startPosLength = shapeOffset.length();
6154
- const startPosDir = startPosLength.greaterThan(1e-3).select(shapeOffset.div(startPosLength), vec3(0, 0, 0));
6155
- const dir = useStartPosAsDir.select(startPosDir, randomDir);
6156
- const randomSpeed = mix(
6157
- uniforms.speedMin,
6158
- uniforms.speedMax,
6159
- randSpeed
6160
- );
6161
- const normalVelocity = dir.mul(randomSpeed);
6162
- velocity.assign(
6163
- useAttractToCenter.select(attractVelocity, normalVelocity)
6164
- );
6165
- const randomSize = mix(uniforms.sizeMin, uniforms.sizeMax, randSize);
6166
- particleSize.assign(randomSize);
6167
- const rotX = mix(
6168
- uniforms.rotationMinX,
6169
- uniforms.rotationMaxX,
6170
- randRotationX
6171
- );
6172
- const rotY = mix(
6173
- uniforms.rotationMinY,
6174
- uniforms.rotationMaxY,
6175
- randRotationY
6176
- );
6177
- const rotZ = mix(
6178
- uniforms.rotationMinZ,
6179
- uniforms.rotationMaxZ,
6180
- randRotationZ
6181
- );
6182
- particleRotation.assign(vec3(rotX, rotY, rotZ));
6183
- const startColorIdx = floor(
6184
- randColorStart.mul(uniforms.colorStartCount)
6185
- );
6186
- const selectedStartColor = selectColor(
6187
- startColorIdx,
6188
- uniforms.colorStart0,
6189
- uniforms.colorStart1,
6190
- uniforms.colorStart2,
6191
- uniforms.colorStart3,
6192
- uniforms.colorStart4,
6193
- uniforms.colorStart5,
6194
- uniforms.colorStart6,
6195
- uniforms.colorStart7
6196
- );
6197
- pColorStart.assign(selectedStartColor);
6198
- const endColorIdx = floor(randColorEnd.mul(uniforms.colorEndCount));
6199
- const selectedEndColor = selectColor(
6200
- endColorIdx,
6201
- uniforms.colorEnd0,
6202
- uniforms.colorEnd1,
6203
- uniforms.colorEnd2,
6204
- uniforms.colorEnd3,
6205
- uniforms.colorEnd4,
6206
- uniforms.colorEnd5,
6207
- uniforms.colorEnd6,
6208
- uniforms.colorEnd7
6209
- );
6210
- pColorEnd.assign(selectedEndColor);
6211
- lifetime2.assign(float(1));
6212
- });
6213
- })().compute(activeMaxParticles);
6214
- }, [
6215
- activeMaxParticles,
6216
- positions,
6217
- velocities,
6218
- lifetimes,
6219
- fadeRates,
6220
- particleSizes,
6221
- particleRotations,
6222
- particleColorStarts,
6223
- particleColorEnds,
6224
- uniforms,
6225
- selectColor
6226
- ]);
6227
- const computeUpdate = useMemo(() => {
6228
- return Fn(() => {
6229
- const position2 = positions.element(instanceIndex);
6230
- const velocity = velocities.element(instanceIndex);
6231
- const lifetime2 = lifetimes.element(instanceIndex);
6232
- const fadeRate = fadeRates.element(instanceIndex);
6233
- const particleRotation = particleRotations.element(instanceIndex);
6234
- const particleSize = particleSizes.element(instanceIndex);
6235
- const dt = uniforms.deltaTime;
6236
- If(lifetime2.greaterThan(0), () => {
6237
- const gravityMultiplier = float(1).add(
6238
- particleSize.mul(uniforms.sizeBasedGravity)
6239
- );
6240
- velocity.addAssign(uniforms.gravity.mul(dt).mul(gravityMultiplier));
6241
- const progress = float(1).sub(lifetime2);
6242
- const velocityCurveSample = texture(
6243
- curveTexture,
6244
- vec2(progress, float(0.5))
6245
- ).z;
6246
- const speedScale = uniforms.velocityCurveEnabled.greaterThan(0.5).select(
6247
- // Use velocity curve directly as speed multiplier
6248
- velocityCurveSample,
6249
- // Legacy friction behavior
6250
- (() => {
6251
- const easingType = uniforms.frictionEasingType;
6252
- const easedProgress = easingType.lessThan(0.5).select(
6253
- progress,
6254
- easingType.lessThan(1.5).select(
6255
- progress.mul(progress),
6256
- easingType.lessThan(2.5).select(
6257
- float(1).sub(
6258
- float(1).sub(progress).mul(float(1).sub(progress))
6259
- ),
6260
- progress.lessThan(0.5).select(
6261
- float(2).mul(progress).mul(progress),
6262
- float(1).sub(
6263
- float(-2).mul(progress).add(2).pow(2).div(2)
6264
- )
6265
- )
6266
- )
6267
- )
6268
- );
6269
- const currentIntensity = mix(
6270
- uniforms.frictionIntensityStart,
6271
- uniforms.frictionIntensityEnd,
6272
- easedProgress
6273
- );
6274
- return float(1).sub(currentIntensity.mul(0.9));
6275
- })()
6276
- );
6277
- const turbIntensity = uniforms.turbulenceIntensity;
6278
- const turbFreq = uniforms.turbulenceFrequency;
6279
- const turbTime = uniforms.turbulenceTime;
6280
- If(turbIntensity.greaterThan(1e-3), () => {
6281
- const noisePos = position2.mul(turbFreq).add(vec3(turbTime, turbTime.mul(0.7), turbTime.mul(1.3)));
6282
- const eps = float(0.01);
6283
- const nPosX = mx_noise_vec3(noisePos.add(vec3(eps, 0, 0)));
6284
- const nNegX = mx_noise_vec3(noisePos.sub(vec3(eps, 0, 0)));
6285
- const nPosY = mx_noise_vec3(noisePos.add(vec3(0, eps, 0)));
6286
- const nNegY = mx_noise_vec3(noisePos.sub(vec3(0, eps, 0)));
6287
- const nPosZ = mx_noise_vec3(noisePos.add(vec3(0, 0, eps)));
6288
- const nNegZ = mx_noise_vec3(noisePos.sub(vec3(0, 0, eps)));
6289
- const dFx_dy = nPosY.x.sub(nNegY.x).div(eps.mul(2));
6290
- const dFx_dz = nPosZ.x.sub(nNegZ.x).div(eps.mul(2));
6291
- const dFy_dx = nPosX.y.sub(nNegX.y).div(eps.mul(2));
6292
- const dFy_dz = nPosZ.y.sub(nNegZ.y).div(eps.mul(2));
6293
- const dFz_dx = nPosX.z.sub(nNegX.z).div(eps.mul(2));
6294
- const dFz_dy = nPosY.z.sub(nNegY.z).div(eps.mul(2));
6295
- const curlX = dFz_dy.sub(dFy_dz);
6296
- const curlY = dFx_dz.sub(dFz_dx);
6297
- const curlZ = dFy_dx.sub(dFx_dy);
6298
- const curl = vec3(curlX, curlY, curlZ);
6299
- velocity.addAssign(curl.mul(turbIntensity).mul(uniforms.deltaTime));
6300
- });
6301
- const attractorCount = uniforms.attractorCount;
6302
- const applyAttractor = (aPos, aStrength, aRadius, aType, aAxis) => {
6303
- If(aStrength.abs().greaterThan(1e-3), () => {
6304
- const toAttractor = aPos.sub(position2);
6305
- const dist = toAttractor.length();
6306
- const safeDist = dist.max(0.01);
6307
- const direction2 = toAttractor.div(safeDist);
6308
- const falloff = aRadius.greaterThan(1e-3).select(
6309
- float(1).sub(dist.div(aRadius)).max(0),
6310
- // Linear falloff within radius
6311
- float(1).div(safeDist.mul(safeDist).add(1))
6312
- // Inverse square falloff (softened)
6313
- );
6314
- const force = aType.lessThan(0.5).select(
6315
- // Point attractor: force along direction to attractor
6316
- direction2.mul(aStrength).mul(falloff),
6317
- // Vortex: force perpendicular to both (toAttractor) and (axis)
6318
- // cross(axis, toAttractor) gives tangent direction
6319
- (() => {
6320
- const tangent = vec3(
6321
- aAxis.y.mul(toAttractor.z).sub(aAxis.z.mul(toAttractor.y)),
6322
- aAxis.z.mul(toAttractor.x).sub(aAxis.x.mul(toAttractor.z)),
6323
- aAxis.x.mul(toAttractor.y).sub(aAxis.y.mul(toAttractor.x))
6324
- );
6325
- const tangentLen = tangent.length().max(1e-3);
6326
- return tangent.div(tangentLen).mul(aStrength).mul(falloff);
6327
- })()
6328
- );
6329
- velocity.addAssign(force.mul(uniforms.deltaTime));
6330
- });
6331
- };
6332
- If(attractorCount.greaterThan(0), () => {
6333
- applyAttractor(
6334
- uniforms.attractor0Pos,
6335
- uniforms.attractor0Strength,
6336
- uniforms.attractor0Radius,
6337
- uniforms.attractor0Type,
6338
- uniforms.attractor0Axis
6339
- );
6340
- });
6341
- If(attractorCount.greaterThan(1), () => {
6342
- applyAttractor(
6343
- uniforms.attractor1Pos,
6344
- uniforms.attractor1Strength,
6345
- uniforms.attractor1Radius,
6346
- uniforms.attractor1Type,
6347
- uniforms.attractor1Axis
6348
- );
6349
- });
6350
- If(attractorCount.greaterThan(2), () => {
6351
- applyAttractor(
6352
- uniforms.attractor2Pos,
6353
- uniforms.attractor2Strength,
6354
- uniforms.attractor2Radius,
6355
- uniforms.attractor2Type,
6356
- uniforms.attractor2Axis
6357
- );
6358
- });
6359
- If(attractorCount.greaterThan(3), () => {
6360
- applyAttractor(
6361
- uniforms.attractor3Pos,
6362
- uniforms.attractor3Strength,
6363
- uniforms.attractor3Radius,
6364
- uniforms.attractor3Type,
6365
- uniforms.attractor3Axis
6366
- );
6367
- });
6368
- position2.addAssign(velocity.mul(dt).mul(speedScale));
6369
- If(uniforms.collisionEnabled.greaterThan(0.5), () => {
6370
- const planeY = uniforms.collisionPlaneY;
6371
- const bounce = uniforms.collisionBounce;
6372
- const friction2 = uniforms.collisionFriction;
6373
- const shouldDie = uniforms.collisionDie;
6374
- If(position2.y.lessThan(planeY), () => {
6375
- If(shouldDie.greaterThan(0.5), () => {
6376
- lifetime2.assign(float(0));
6377
- position2.y.assign(float(-1e3));
6378
- }).Else(() => {
6379
- position2.y.assign(planeY);
6380
- velocity.y.assign(velocity.y.abs().mul(bounce));
6381
- velocity.x.mulAssign(friction2);
6382
- velocity.z.mulAssign(friction2);
6383
- });
6384
- });
6385
- });
6386
- const idx = float(instanceIndex);
6387
- const rotSpeedX = mix(
6388
- uniforms.rotationSpeedMinX,
6389
- uniforms.rotationSpeedMaxX,
6390
- hash(idx.add(8888))
6391
- );
6392
- const rotSpeedY = mix(
6393
- uniforms.rotationSpeedMinY,
6394
- uniforms.rotationSpeedMaxY,
6395
- hash(idx.add(9999))
6396
- );
6397
- const rotSpeedZ = mix(
6398
- uniforms.rotationSpeedMinZ,
6399
- uniforms.rotationSpeedMaxZ,
6400
- hash(idx.add(10101))
6401
- );
6402
- const rotSpeedCurveSample = texture(
6403
- curveTexture,
6404
- vec2(progress, float(0.5))
6405
- ).w;
6406
- const rotSpeedMultiplier = uniforms.rotationSpeedCurveEnabled.greaterThan(0.5).select(rotSpeedCurveSample, float(1));
6407
- particleRotation.addAssign(
6408
- vec3(rotSpeedX, rotSpeedY, rotSpeedZ).mul(uniforms.deltaTime).mul(rotSpeedMultiplier)
6409
- );
6410
- lifetime2.subAssign(fadeRate.mul(uniforms.deltaTime));
6411
- If(lifetime2.lessThanEqual(0), () => {
6412
- lifetime2.assign(float(0));
6413
- position2.y.assign(float(-1e3));
6414
- });
6415
- });
6416
- })().compute(activeMaxParticles);
6417
- }, [
6418
- activeMaxParticles,
6419
- positions,
6420
- velocities,
6421
- lifetimes,
6422
- fadeRates,
6423
- particleSizes,
6424
- particleRotations,
6425
- uniforms,
6426
- curveTexture
6427
- ]);
6428
- const material = useMemo(() => {
6429
- const lifetime2 = lifetimes.element(instanceIndex);
6430
- const particleSize = particleSizes.element(instanceIndex);
6431
- const particleRotation = particleRotations.element(instanceIndex);
6432
- const pColorStart = particleColorStarts.element(instanceIndex);
6433
- const pColorEnd = particleColorEnds.element(instanceIndex);
6434
- const particlePos = positions.element(instanceIndex);
6435
- const particleVel = velocities.element(instanceIndex);
6436
- const progress = float(1).sub(lifetime2);
6437
- const currentColor = mix(pColorStart, pColorEnd, progress);
6438
- const intensifiedColor = currentColor.mul(uniforms.intensity);
6439
- const curveSample = texture(curveTexture, vec2(progress, float(0.5)));
6440
- const sizeMultiplier = uniforms.fadeSizeCurveEnabled.greaterThan(0.5).select(
6441
- curveSample.x,
6442
- // R channel - size curve value
6443
- mix(uniforms.fadeSizeStart, uniforms.fadeSizeEnd, progress)
6444
- // Linear interpolation of fadeSize
6445
- );
6446
- const opacityMultiplier = uniforms.fadeOpacityCurveEnabled.greaterThan(0.5).select(
6447
- curveSample.y,
6448
- // G channel - opacity curve value
6449
- mix(uniforms.fadeOpacityStart, uniforms.fadeOpacityEnd, progress)
6450
- // Linear interpolation of fadeOpacity
6451
- );
6452
- let sampleUV = uv();
6453
- if (flipbook && alphaMap) {
6454
- const rows = float(flipbook.rows || 1);
6455
- const columns = float(flipbook.columns || 1);
6456
- const totalFrames = rows.mul(columns);
6457
- const frameIndex = floor(
6458
- progress.mul(totalFrames).min(totalFrames.sub(1))
6459
- );
6460
- const col = mod(frameIndex, columns);
6461
- const row = floor(frameIndex.div(columns));
6462
- const scaledUV = uv().div(vec2(columns, rows));
6463
- const offsetX = col.div(columns);
6464
- const offsetY = rows.sub(1).sub(row).div(rows);
6465
- sampleUV = scaledUV.add(vec2(offsetX, offsetY));
6466
- }
6467
- let shapeMask;
6468
- if (activeGeometry) {
6469
- shapeMask = float(1);
6470
- } else if (alphaMap) {
6471
- const alphaSample = texture(alphaMap, sampleUV);
6472
- shapeMask = alphaSample.r;
6473
- } else {
6474
- const dist = uv().mul(2).sub(1).length();
6475
- switch (activeAppearance) {
6476
- case Appearance.DEFAULT:
6477
- shapeMask = float(1);
6478
- break;
6479
- case Appearance.CIRCULAR:
6480
- shapeMask = step(dist, float(1));
6481
- break;
6482
- case Appearance.GRADIENT:
6483
- default:
6484
- shapeMask = float(1).sub(dist).max(0);
6485
- break;
6486
- }
6487
- }
6488
- const baseOpacity = opacityMultiplier.mul(shapeMask).mul(lifetime2.greaterThan(1e-3).select(float(1), float(0)));
6489
- const particleData = {
6490
- progress,
6491
- // 0→1 over lifetime
6492
- lifetime: lifetime2,
6493
- // 1→0 over lifetime (inverse of progress)
6494
- position: particlePos,
6495
- // vec3 world position
6496
- velocity: particleVel,
6497
- // vec3 velocity
6498
- size: particleSize,
6499
- // float size
6500
- rotation: particleRotation,
6501
- // vec3 rotation (x, y, z)
6502
- colorStart: pColorStart,
6503
- // vec3 start color
6504
- colorEnd: pColorEnd,
6505
- // vec3 end color
6506
- color: currentColor,
6507
- // vec3 interpolated color
6508
- intensifiedColor,
6509
- // vec3 color * intensity
6510
- shapeMask,
6511
- // float shape/alpha mask
6512
- index: instanceIndex
6513
- // particle index (for randomization)
6514
- };
6515
- let finalOpacity = opacityNode ? baseOpacity.mul(
6516
- typeof opacityNode === "function" ? opacityNode(particleData) : opacityNode
6517
- ) : baseOpacity;
6518
- if (softParticles) {
6519
- const sceneDepth = viewportDepthTexture(screenUV).x;
6520
- const particleViewZ = positionView.z.negate();
6521
- const near = cameraNear;
6522
- const far = cameraFar;
6523
- const sceneViewZ = near.mul(far).mul(2).div(far.add(near).sub(sceneDepth.mul(2).sub(1).mul(far.sub(near))));
6524
- const depthDiff = sceneViewZ.sub(particleViewZ);
6525
- const softFade = clamp(depthDiff.div(uniforms.softDistance), 0, 1);
6526
- finalOpacity = finalOpacity.mul(softFade);
6527
- }
6528
- if (activeGeometry) {
6529
- let mat;
6530
- switch (activeLighting) {
6531
- case Lighting.BASIC:
6532
- mat = new THREE2.MeshBasicNodeMaterial();
6533
- break;
6534
- case Lighting.PHYSICAL:
6535
- mat = new THREE2.MeshPhysicalNodeMaterial();
6536
- break;
6537
- case Lighting.STANDARD:
6538
- default:
6539
- mat = new THREE2.MeshStandardNodeMaterial();
6540
- break;
6541
- }
6542
- const velocityCurveValue = curveSample.z;
6543
- const effectiveVelocityMultiplier = uniforms.velocityCurveEnabled.greaterThan(0.5).select(velocityCurveValue, float(1));
6544
- const effectiveSpeed = particleVel.length().mul(effectiveVelocityMultiplier);
6545
- const stretchAmount = uniforms.stretchEnabled.greaterThan(0.5).select(
6546
- float(1).add(effectiveSpeed.mul(uniforms.stretchFactor)).min(uniforms.stretchMax),
6547
- float(1)
6548
- );
6549
- const baseScale = particleSize.mul(sizeMultiplier);
6550
- const axisType = uniforms.orientAxisType;
6551
- const axisSign = axisType.lessThan(3).select(float(1), float(-1));
6552
- const axisIndex = axisType.mod(3);
6553
- const stretchedLocal = uniforms.stretchEnabled.greaterThan(0.5).select(
6554
- axisIndex.lessThan(0.5).select(
6555
- // X axis stretch
6556
- vec3(
6557
- positionLocal.x.mul(stretchAmount),
6558
- positionLocal.y,
6559
- positionLocal.z
6560
- ),
6561
- axisIndex.lessThan(1.5).select(
6562
- // Y axis stretch
6563
- vec3(
6564
- positionLocal.x,
6565
- positionLocal.y.mul(stretchAmount),
6566
- positionLocal.z
6567
- ),
6568
- // Z axis stretch
6569
- vec3(
6570
- positionLocal.x,
6571
- positionLocal.y,
6572
- positionLocal.z.mul(stretchAmount)
6573
- )
6574
- )
6575
- ),
6576
- positionLocal
6577
- );
6578
- let rotatedPos;
6579
- if (activeOrientToDirection) {
6580
- const velLen = particleVel.length().max(1e-4);
6581
- const velDir = particleVel.div(velLen).mul(axisSign);
6582
- const localAxis = axisIndex.lessThan(0.5).select(
6583
- vec3(1, 0, 0),
6584
- // X axis
6585
- axisIndex.lessThan(1.5).select(
6586
- vec3(0, 1, 0),
6587
- // Y axis
6588
- vec3(0, 0, 1)
6589
- // Z axis
6590
- )
6591
- );
6592
- const dotProduct = localAxis.dot(velDir).clamp(-1, 1);
6593
- const crossProduct = localAxis.cross(velDir);
6594
- const crossLen = crossProduct.length();
6595
- const needsRotation = crossLen.greaterThan(1e-4);
6596
- const rotAxis = needsRotation.select(
6597
- crossProduct.div(crossLen),
6598
- vec3(0, 1, 0)
6599
- // Fallback axis (won't be used if no rotation needed)
6600
- );
6601
- const cosAngle = dotProduct;
6602
- const sinAngle = crossLen;
6603
- const oneMinusCos = float(1).sub(cosAngle);
6604
- const v = stretchedLocal;
6605
- const kDotV = rotAxis.dot(v);
6606
- const kCrossV = rotAxis.cross(v);
6607
- const rotatedByAxis = needsRotation.select(
6608
- v.mul(cosAngle).add(kCrossV.mul(sinAngle)).add(rotAxis.mul(kDotV.mul(oneMinusCos))),
6609
- // If vectors are nearly aligned, check if they're opposite (dot ≈ -1)
6610
- dotProduct.lessThan(-0.99).select(
6611
- v.negate(),
6612
- // Flip 180° - just negate
6613
- v
6614
- // Already aligned, no rotation
6615
- )
6616
- );
6617
- rotatedPos = rotatedByAxis;
6618
- } else {
6619
- const rotX = particleRotation.x;
6620
- const rotY = particleRotation.y;
6621
- const rotZ = particleRotation.z;
6622
- const cX = cos(rotX);
6623
- const sX = sin(rotX);
6624
- const afterX = vec3(
6625
- stretchedLocal.x,
6626
- stretchedLocal.y.mul(cX).sub(stretchedLocal.z.mul(sX)),
6627
- stretchedLocal.y.mul(sX).add(stretchedLocal.z.mul(cX))
6628
- );
6629
- const cY = cos(rotY);
6630
- const sY = sin(rotY);
6631
- const afterY = vec3(
6632
- afterX.x.mul(cY).add(afterX.z.mul(sY)),
6633
- afterX.y,
6634
- afterX.z.mul(cY).sub(afterX.x.mul(sY))
6635
- );
6636
- const cZ = cos(rotZ);
6637
- const sZ = sin(rotZ);
6638
- rotatedPos = vec3(
6639
- afterY.x.mul(cZ).sub(afterY.y.mul(sZ)),
6640
- afterY.x.mul(sZ).add(afterY.y.mul(cZ)),
6641
- afterY.z
6642
- );
6643
- }
6644
- const scaledPos = rotatedPos.mul(baseScale);
6645
- mat.positionNode = scaledPos.add(particlePos);
6646
- const defaultColor = vec4(intensifiedColor, finalOpacity);
6647
- mat.colorNode = colorNode ? typeof colorNode === "function" ? colorNode(particleData, defaultColor) : colorNode : defaultColor;
6648
- mat.transparent = true;
6649
- mat.depthWrite = false;
6650
- mat.blending = blending;
6651
- mat.side = THREE2.DoubleSide;
6652
- if (backdropNode) {
6653
- mat.backdropNode = typeof backdropNode === "function" ? backdropNode(particleData) : backdropNode;
6654
- }
6655
- if (castShadowNode) {
6656
- mat.castShadowNode = typeof castShadowNode === "function" ? castShadowNode(particleData) : castShadowNode;
6657
- }
6658
- if (alphaTestNode) {
6659
- mat.alphaTestNode = typeof alphaTestNode === "function" ? alphaTestNode(particleData) : alphaTestNode;
6660
- }
6661
- return mat;
6662
- } else {
6663
- const mat = new THREE2.SpriteNodeMaterial();
6664
- const defaultColor = vec4(intensifiedColor, finalOpacity);
6665
- mat.colorNode = colorNode ? typeof colorNode === "function" ? colorNode(particleData, defaultColor) : colorNode : defaultColor;
6666
- mat.positionNode = positions.toAttribute();
6667
- mat.scaleNode = particleSize.mul(sizeMultiplier);
6668
- mat.rotationNode = particleRotation.y;
6669
- mat.transparent = true;
6670
- mat.depthWrite = false;
6671
- mat.blending = blending;
6672
- if (backdropNode) {
6673
- mat.backdropNode = typeof backdropNode === "function" ? backdropNode(particleData) : backdropNode;
6674
- }
6675
- if (castShadowNode) {
6676
- mat.castShadowNode = typeof castShadowNode === "function" ? castShadowNode(particleData) : castShadowNode;
6677
- }
6678
- if (alphaTestNode) {
6679
- mat.alphaTestNode = typeof alphaTestNode === "function" ? alphaTestNode(particleData) : alphaTestNode;
6680
- }
6681
- return mat;
6682
- }
6683
- }, [
6684
- positions,
6685
- velocities,
6686
- lifetimes,
6687
- particleSizes,
6688
- particleRotations,
6689
- particleColorStarts,
6690
- particleColorEnds,
6691
- uniforms,
6692
- activeAppearance,
6693
- alphaMap,
6694
- flipbook,
6695
- blending,
6696
- activeGeometry,
6697
- activeOrientToDirection,
6698
- activeLighting,
6699
- backdropNode,
6700
- opacityNode,
6701
- colorNode,
6702
- alphaTestNode,
6703
- castShadowNode,
6704
- softParticles,
6705
- curveTexture
6706
- ]);
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
+ );
6707
5746
  const renderObject = useMemo(() => {
6708
5747
  if (activeGeometry) {
6709
5748
  const mesh = new THREE2.InstancedMesh(
@@ -6729,6 +5768,7 @@ var init_VFXParticles = __esm({
6729
5768
  });
6730
5769
  }, [renderer, computeInit]);
6731
5770
  const applySpawnOverrides = useCallback2(
5771
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6732
5772
  (overrides) => {
6733
5773
  if (!overrides) return null;
6734
5774
  const saved = {};
@@ -6775,15 +5815,16 @@ var init_VFXParticles = __esm({
6775
5815
  saved.gravity = uniforms.gravity.value.clone();
6776
5816
  uniforms.gravity.value.set(...overrides.gravity);
6777
5817
  }
5818
+ const u = uniforms;
6778
5819
  if (overrides.colorStart !== void 0) {
6779
5820
  const colors = overrides.colorStart.slice(0, 8).map(hexToRgb);
6780
5821
  while (colors.length < 8)
6781
5822
  colors.push(colors[colors.length - 1] || [1, 1, 1]);
6782
5823
  setUniform("colorStartCount", overrides.colorStart.length);
6783
5824
  colors.forEach((c, i) => {
6784
- if (uniforms[`colorStart${i}`]) {
6785
- saved[`colorStart${i}`] = uniforms[`colorStart${i}`].value.clone();
6786
- uniforms[`colorStart${i}`].value.setRGB(...c);
5825
+ if (u[`colorStart${i}`]) {
5826
+ saved[`colorStart${i}`] = u[`colorStart${i}`].value.clone();
5827
+ u[`colorStart${i}`].value.setRGB(...c);
6787
5828
  }
6788
5829
  });
6789
5830
  }
@@ -6793,9 +5834,9 @@ var init_VFXParticles = __esm({
6793
5834
  colors.push(colors[colors.length - 1] || [1, 1, 1]);
6794
5835
  setUniform("colorEndCount", overrides.colorEnd.length);
6795
5836
  colors.forEach((c, i) => {
6796
- if (uniforms[`colorEnd${i}`]) {
6797
- saved[`colorEnd${i}`] = uniforms[`colorEnd${i}`].value.clone();
6798
- uniforms[`colorEnd${i}`].value.setRGB(...c);
5837
+ if (u[`colorEnd${i}`]) {
5838
+ saved[`colorEnd${i}`] = u[`colorEnd${i}`].value.clone();
5839
+ u[`colorEnd${i}`].value.setRGB(...c);
6799
5840
  }
6800
5841
  });
6801
5842
  }
@@ -6836,7 +5877,8 @@ var init_VFXParticles = __esm({
6836
5877
  );
6837
5878
  const spawn = useCallback2(
6838
5879
  (x = 0, y = 0, z = 0, count = 20, overrides = null) => {
6839
- const [px, py, pz] = positionRef.current;
5880
+ var _a;
5881
+ const [px, py, pz] = (_a = positionRef.current) != null ? _a : [0, 0, 0];
6840
5882
  spawnInternal(px + x, py + y, pz + z, count, overrides);
6841
5883
  },
6842
5884
  [spawnInternal]
@@ -6935,6 +5977,7 @@ var init_VFXParticles = __esm({
6935
5977
  const prevGeometryTypeRef = useRef2(null);
6936
5978
  const prevGeometryArgsRef = useRef2(null);
6937
5979
  const handleDebugUpdate = useCallback2(
5980
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6938
5981
  (newValues) => {
6939
5982
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C;
6940
5983
  debugValuesRef.current = __spreadValues(__spreadValues({}, debugValuesRef.current), newValues);
@@ -7074,7 +6117,7 @@ var init_VFXParticles = __esm({
7074
6117
  }
7075
6118
  }
7076
6119
  if ("emitterShape" in newValues) {
7077
- uniforms.emitterShapeType.value = (_g = newValues.emitterShape) != null ? _g : EmitterShape.BOX;
6120
+ uniforms.emitterShapeType.value = (_g = newValues.emitterShape) != null ? _g : EmitterShape2.BOX;
7078
6121
  }
7079
6122
  if ("emitterRadius" in newValues) {
7080
6123
  const emitterRadiusR = toRange(newValues.emitterRadius, [0, 1]);
@@ -7407,7 +6450,7 @@ var init_VFXParticles = __esm({
7407
6450
  // src/index.ts
7408
6451
  init_VFXParticles();
7409
6452
 
7410
- // src/VFXEmitter.jsx
6453
+ // src/VFXEmitter.tsx
7411
6454
  init_react_store();
7412
6455
  import {
7413
6456
  useRef as useRef3,
@@ -7436,7 +6479,7 @@ var VFXEmitter = forwardRef2(function VFXEmitter2({
7436
6479
  onEmit,
7437
6480
  children
7438
6481
  }, ref) {
7439
- const groupRef = useRef3();
6482
+ const groupRef = useRef3(null);
7440
6483
  const emitAccumulator = useRef3(0);
7441
6484
  const emitting = useRef3(autoStart);
7442
6485
  const hasEmittedOnce = useRef3(false);
@@ -7446,18 +6489,28 @@ var VFXEmitter = forwardRef2(function VFXEmitter2({
7446
6489
  }
7447
6490
  return useVFXStore.getState().getParticles(name);
7448
6491
  }, [name, particlesRef]);
7449
- const transformDirectionByQuat = useCallback3((dirRange, quat) => {
7450
- if (!dirRange) return null;
7451
- const minDir = _tempVec.set(dirRange[0][0], dirRange[1][0], dirRange[2][0]);
7452
- minDir.applyQuaternion(quat);
7453
- const maxDir = new Vector32(dirRange[0][1], dirRange[1][1], dirRange[2][1]);
7454
- maxDir.applyQuaternion(quat);
7455
- return [
7456
- [Math.min(minDir.x, maxDir.x), Math.max(minDir.x, maxDir.x)],
7457
- [Math.min(minDir.y, maxDir.y), Math.max(minDir.y, maxDir.y)],
7458
- [Math.min(minDir.z, maxDir.z), Math.max(minDir.z, maxDir.z)]
7459
- ];
7460
- }, []);
6492
+ const transformDirectionByQuat = useCallback3(
6493
+ (dirRange, quat) => {
6494
+ const minDir = _tempVec.set(
6495
+ dirRange[0][0],
6496
+ dirRange[1][0],
6497
+ dirRange[2][0]
6498
+ );
6499
+ minDir.applyQuaternion(quat);
6500
+ const maxDir = new Vector32(
6501
+ dirRange[0][1],
6502
+ dirRange[1][1],
6503
+ dirRange[2][1]
6504
+ );
6505
+ maxDir.applyQuaternion(quat);
6506
+ return [
6507
+ [Math.min(minDir.x, maxDir.x), Math.max(minDir.x, maxDir.x)],
6508
+ [Math.min(minDir.y, maxDir.y), Math.max(minDir.y, maxDir.y)],
6509
+ [Math.min(minDir.z, maxDir.z), Math.max(minDir.z, maxDir.z)]
6510
+ ];
6511
+ },
6512
+ []
6513
+ );
7461
6514
  const getEmitParams = useCallback3(() => {
7462
6515
  if (!groupRef.current) {
7463
6516
  return { position, direction };
@@ -7563,7 +6616,10 @@ var VFXEmitter = forwardRef2(function VFXEmitter2({
7563
6616
  }),
7564
6617
  [emit, burst, start, stop, getParticleSystem]
7565
6618
  );
7566
- return /* @__PURE__ */ jsx3("group", { ref: groupRef, position, children });
6619
+ return (
6620
+ // @ts-expect-error
6621
+ /* @__PURE__ */ jsx3("group", { ref: groupRef, position, children })
6622
+ );
7567
6623
  });
7568
6624
  function useVFXEmitter(name) {
7569
6625
  const getParticles = useVFXStore((s) => s.getParticles);
@@ -7614,15 +6670,15 @@ function useVFXEmitter(name) {
7614
6670
  init_react_store();
7615
6671
  export {
7616
6672
  Appearance,
7617
- AttractorType,
6673
+ AttractorType2 as AttractorType,
7618
6674
  Blending,
7619
- Easing,
6675
+ Easing2 as Easing,
7620
6676
  EmitterShape,
7621
6677
  Lighting,
7622
6678
  VFXEmitter,
7623
6679
  VFXParticles,
7624
6680
  bakeCurveToArray,
7625
- createCombinedCurveTexture,
6681
+ createCombinedCurveTexture2 as createCombinedCurveTexture,
7626
6682
  useVFXEmitter,
7627
6683
  useVFXStore
7628
6684
  };