@cazala/party 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3594,7 +3594,12 @@ class WebGPUEngine extends AbstractEngine {
3594
3594
  this.registry.initialize(this.resources);
3595
3595
  const program = this.registry.getProgram();
3596
3596
  if (program.extraBindings.simState) {
3597
- this.resources.createSimStateBuffer(this.bufferMaxParticles, 4);
3597
+ // SIM_STATE is a per-particle float array used by integration and force-module state.
3598
+ // Its stride is program-dependent (base 4 + per-module state slots), so we must
3599
+ // allocate using the generated program's stride. Allocating a fixed 4-float stride
3600
+ // causes out-of-bounds reads/writes for particles beyond a threshold (e.g. ~40k),
3601
+ // resulting in "exploding" velocities/positions.
3602
+ this.resources.createSimStateBuffer(this.bufferMaxParticles, program.simStateStride);
3598
3603
  }
3599
3604
  // Build compute pipelines
3600
3605
  this.sim.initialize(this.resources, program);
@@ -6269,14 +6274,30 @@ class Collisions extends Module {
6269
6274
  * - apply(): compute pressure gradient and viscosity forces; clamp max accel
6270
6275
  * Stores per-particle state in shared SIM_STATE via the Program-provided helpers.
6271
6276
  */
6277
+ var FluidsMethod;
6278
+ (function (FluidsMethod) {
6279
+ FluidsMethod["Sph"] = "sph";
6280
+ FluidsMethod["Picflip"] = "picflip";
6281
+ })(FluidsMethod || (FluidsMethod = {}));
6272
6282
  const DEFAULT_FLUIDS_INFLUENCE_RADIUS = 100;
6273
- const DEFAULT_FLUIDS_TARGET_DENSITY = 1;
6283
+ const DEFAULT_FLUIDS_TARGET_DENSITY = 2;
6274
6284
  const DEFAULT_FLUIDS_PRESSURE_MULTIPLIER = 30;
6275
6285
  const DEFAULT_FLUIDS_VISCOSITY = 1;
6276
6286
  const DEFAULT_FLUIDS_NEAR_PRESSURE_MULTIPLIER = 50;
6277
6287
  const DEFAULT_FLUIDS_NEAR_THRESHOLD = 20;
6278
6288
  const DEFAULT_FLUIDS_ENABLE_NEAR_PRESSURE = true;
6279
6289
  const DEFAULT_FLUIDS_MAX_ACCELERATION = 75;
6290
+ // Default values for PIC/FLIP parameters (exposed via Fluids when method=picflip)
6291
+ const DEFAULT_PICFLIP_FLIP_RATIO = 0.9;
6292
+ const DEFAULT_PICFLIP_TARGET_DENSITY = 2.0;
6293
+ const DEFAULT_PICFLIP_INFLUENCE_RADIUS = 50.0;
6294
+ const DEFAULT_PICFLIP_PRESSURE_MULTIPLIER = 500.0;
6295
+ const DEFAULT_PICFLIP_INTERNAL_MAX_ACCELERATION = 20000.0;
6296
+ // Backwards-compatible aliases for previous standalone Picflip module constants.
6297
+ // (The recommended names are the *_TARGET_DENSITY / *_INFLUENCE_RADIUS / *_PRESSURE_MULTIPLIER ones.)
6298
+ const DEFAULT_PICFLIP_DENSITY = DEFAULT_PICFLIP_TARGET_DENSITY;
6299
+ const DEFAULT_PICFLIP_RADIUS = DEFAULT_PICFLIP_INFLUENCE_RADIUS;
6300
+ const DEFAULT_PICFLIP_PRESSURE = DEFAULT_PICFLIP_PRESSURE_MULTIPLIER;
6280
6301
  class Fluids extends Module {
6281
6302
  constructor(opts) {
6282
6303
  super();
@@ -6297,6 +6318,7 @@ class Fluids extends Module {
6297
6318
  configurable: true,
6298
6319
  writable: true,
6299
6320
  value: {
6321
+ method: DataType.NUMBER,
6300
6322
  influenceRadius: DataType.NUMBER,
6301
6323
  targetDensity: DataType.NUMBER,
6302
6324
  pressureMultiplier: DataType.NUMBER,
@@ -6305,22 +6327,56 @@ class Fluids extends Module {
6305
6327
  nearThreshold: DataType.NUMBER,
6306
6328
  enableNearPressure: DataType.NUMBER,
6307
6329
  maxAcceleration: DataType.NUMBER,
6330
+ flipRatio: DataType.NUMBER,
6308
6331
  }
6309
6332
  });
6310
- this.write({
6311
- influenceRadius: opts?.influenceRadius ?? DEFAULT_FLUIDS_INFLUENCE_RADIUS,
6312
- targetDensity: opts?.targetDensity ?? DEFAULT_FLUIDS_TARGET_DENSITY,
6313
- pressureMultiplier: opts?.pressureMultiplier ?? DEFAULT_FLUIDS_PRESSURE_MULTIPLIER,
6314
- viscosity: opts?.viscosity ?? DEFAULT_FLUIDS_VISCOSITY,
6315
- nearPressureMultiplier: opts?.nearPressureMultiplier ?? DEFAULT_FLUIDS_NEAR_PRESSURE_MULTIPLIER,
6316
- nearThreshold: opts?.nearThreshold ?? DEFAULT_FLUIDS_NEAR_THRESHOLD,
6317
- enableNearPressure: opts?.enableNearPressure ?? DEFAULT_FLUIDS_ENABLE_NEAR_PRESSURE ? 1 : 0,
6318
- maxAcceleration: opts?.maxAcceleration ?? DEFAULT_FLUIDS_MAX_ACCELERATION,
6319
- });
6333
+ const method = opts?.method ?? FluidsMethod.Sph;
6334
+ const isPicflip = method === FluidsMethod.Picflip;
6335
+ if (isPicflip) {
6336
+ const o = opts;
6337
+ this.write({
6338
+ method: 1,
6339
+ influenceRadius: o?.influenceRadius ?? DEFAULT_PICFLIP_INFLUENCE_RADIUS,
6340
+ targetDensity: o?.targetDensity ?? DEFAULT_PICFLIP_TARGET_DENSITY,
6341
+ pressureMultiplier: o?.pressureMultiplier ?? DEFAULT_PICFLIP_PRESSURE_MULTIPLIER,
6342
+ // SPH-only; keep initialized but unused for PICFLIP
6343
+ viscosity: 0,
6344
+ // Keep SPH-only uniforms initialized (unused when method=picflip)
6345
+ nearPressureMultiplier: DEFAULT_FLUIDS_NEAR_PRESSURE_MULTIPLIER,
6346
+ nearThreshold: DEFAULT_FLUIDS_NEAR_THRESHOLD,
6347
+ enableNearPressure: 1 ,
6348
+ maxAcceleration: DEFAULT_FLUIDS_MAX_ACCELERATION,
6349
+ flipRatio: o?.flipRatio ?? DEFAULT_PICFLIP_FLIP_RATIO,
6350
+ });
6351
+ }
6352
+ else {
6353
+ const o = opts;
6354
+ this.write({
6355
+ method: 0,
6356
+ influenceRadius: o?.influenceRadius ?? DEFAULT_FLUIDS_INFLUENCE_RADIUS,
6357
+ targetDensity: o?.targetDensity ?? DEFAULT_FLUIDS_TARGET_DENSITY,
6358
+ pressureMultiplier: o?.pressureMultiplier ?? DEFAULT_FLUIDS_PRESSURE_MULTIPLIER,
6359
+ viscosity: o?.viscosity ?? DEFAULT_FLUIDS_VISCOSITY,
6360
+ nearPressureMultiplier: o?.nearPressureMultiplier ?? DEFAULT_FLUIDS_NEAR_PRESSURE_MULTIPLIER,
6361
+ nearThreshold: o?.nearThreshold ?? DEFAULT_FLUIDS_NEAR_THRESHOLD,
6362
+ enableNearPressure: o?.enableNearPressure ?? DEFAULT_FLUIDS_ENABLE_NEAR_PRESSURE ? 1 : 0,
6363
+ maxAcceleration: o?.maxAcceleration ?? DEFAULT_FLUIDS_MAX_ACCELERATION,
6364
+ // Keep PICFLIP-only uniform initialized (unused when method=sph)
6365
+ flipRatio: DEFAULT_PICFLIP_FLIP_RATIO,
6366
+ });
6367
+ }
6320
6368
  if (opts?.enabled !== undefined) {
6321
6369
  this.setEnabled(!!opts.enabled);
6322
6370
  }
6323
6371
  }
6372
+ setMethod(method) {
6373
+ this.write({ method: method === FluidsMethod.Picflip ? 1 : 0 });
6374
+ }
6375
+ getMethod() {
6376
+ return this.readValue("method") >= 0.5
6377
+ ? FluidsMethod.Picflip
6378
+ : FluidsMethod.Sph;
6379
+ }
6324
6380
  setInfluenceRadius(v) {
6325
6381
  this.write({ influenceRadius: v });
6326
6382
  }
@@ -6333,6 +6389,9 @@ class Fluids extends Module {
6333
6389
  setViscosity(v) {
6334
6390
  this.write({ viscosity: v });
6335
6391
  }
6392
+ setFlipRatio(v) {
6393
+ this.write({ flipRatio: v });
6394
+ }
6336
6395
  setNearPressureMultiplier(v) {
6337
6396
  this.write({ nearPressureMultiplier: v });
6338
6397
  }
@@ -6357,6 +6416,9 @@ class Fluids extends Module {
6357
6416
  getViscosity() {
6358
6417
  return this.readValue("viscosity");
6359
6418
  }
6419
+ getFlipRatio() {
6420
+ return this.readValue("flipRatio");
6421
+ }
6360
6422
  getNearPressureMultiplier() {
6361
6423
  return this.readValue("nearPressureMultiplier");
6362
6424
  }
@@ -6371,111 +6433,94 @@ class Fluids extends Module {
6371
6433
  }
6372
6434
  webgpu() {
6373
6435
  return {
6374
- states: ["density", "nearDensity"],
6375
- // State pass: precompute density and near-density per particle
6436
+ states: ["density", "nearDensity", "prevVelX", "prevVelY"],
6437
+ // State pass:
6438
+ // - SPH: precompute density and near-density per particle
6439
+ // - PICFLIP: store previous velocity per particle (for FLIP delta)
6376
6440
  state: ({ particleVar, dtVar, getUniform, setState }) => `{
6377
- // Predict current particle position for this frame (approximate)
6378
- let rad = ${getUniform("influenceRadius")};
6379
- let posPred = ${particleVar}.position + ${particleVar}.velocity * (${dtVar});
6380
- var density: f32 = 0.0;
6381
- var nearDensity: f32 = 0.0;
6441
+ let method = ${getUniform("method")};
6382
6442
 
6383
- // Precompute radius powers for kernels
6384
- let r2 = rad * rad;
6385
- let r4 = r2 * r2;
6386
- let r6 = r4 * r2;
6387
- let r8 = r4 * r4;
6443
+ if (method < 0.5) {
6444
+ // SPH: Predict current particle position for this frame (approximate)
6445
+ let rad = ${getUniform("influenceRadius")};
6446
+ let posPred = ${particleVar}.position + ${particleVar}.velocity * (${dtVar});
6447
+ var density: f32 = 0.0;
6448
+ var nearDensity: f32 = 0.0;
6388
6449
 
6389
- // Iterate neighbors using the spatial grid
6390
- var it = neighbor_iter_init(posPred, rad);
6391
- loop {
6392
- let j = neighbor_iter_next(&it, index);
6393
- if (j == NEIGHBOR_NONE) { break; }
6394
- let other = particles[j];
6395
- // Skip removed or pinned neighbors
6396
- if (other.mass <= 0.0) { continue; }
6450
+ // Precompute radius powers for kernels
6451
+ let r2 = rad * rad;
6452
+ let r4 = r2 * r2;
6453
+ let r6 = r4 * r2;
6454
+ let r8 = r4 * r4;
6397
6455
 
6398
- let d = posPred - other.position;
6399
- let dist2 = dot(d, d);
6400
- if (dist2 <= 0.0) { continue; }
6401
- let dist = sqrt(dist2);
6402
- let factor = max(rad - dist, 0.0);
6403
- if (factor <= 0.0) { continue; }
6456
+ // Iterate neighbors using the spatial grid
6457
+ var it = neighbor_iter_init(posPred, rad);
6458
+ loop {
6459
+ let j = neighbor_iter_next(&it, index);
6460
+ if (j == NEIGHBOR_NONE) { break; }
6461
+ let other = particles[j];
6462
+ // Skip removed or pinned neighbors
6463
+ if (other.mass <= 0.0) { continue; }
6404
6464
 
6405
- // Density kernel (CPU: (factor^2) / (pi/6 * r^4))
6406
- let kDensity = (factor * factor) / ((3.14159265 / 6.0) * r4);
6407
- density = density + kDensity * 1000.0 * other.mass;
6465
+ let d = posPred - other.position;
6466
+ let dist2 = dot(d, d);
6467
+ if (dist2 <= 0.0) { continue; }
6468
+ let dist = sqrt(dist2);
6469
+ let factor = max(rad - dist, 0.0);
6470
+ if (factor <= 0.0) { continue; }
6408
6471
 
6409
- // Near-density kernel (CPU: (factor^4) / (pi/15 * r^6))
6410
- if (${getUniform("enableNearPressure")} != 0.0) {
6411
- let f2 = factor * factor;
6412
- let kNear = (f2 * f2) / ((3.14159265 / 15.0) * r6);
6413
- nearDensity = nearDensity + kNear * 1000.0 * other.mass;
6472
+ // Density kernel (CPU: (factor^2) / (pi/6 * r^4))
6473
+ let kDensity = (factor * factor) / ((3.14159265 / 6.0) * r4);
6474
+ density = density + kDensity * 1000.0 * other.mass;
6475
+
6476
+ // Near-density kernel (CPU: (factor^4) / (pi/15 * r^6))
6477
+ if (${getUniform("enableNearPressure")} != 0.0) {
6478
+ let f2 = factor * factor;
6479
+ let kNear = (f2 * f2) / ((3.14159265 / 15.0) * r6);
6480
+ nearDensity = nearDensity + kNear * 1000.0 * other.mass;
6481
+ }
6414
6482
  }
6415
- }
6416
6483
 
6417
- // Store results in shared SIM_STATE for use in apply pass
6418
- ${setState("density", "density")};
6419
- ${setState("nearDensity", "nearDensity")};
6484
+ // Store results in shared SIM_STATE for use in apply pass
6485
+ ${setState("density", "density")};
6486
+ ${setState("nearDensity", "nearDensity")};
6487
+ } else {
6488
+ // PICFLIP: Store current velocity before blend/pressure
6489
+ ${setState("prevVelX", `${particleVar}.velocity.x`)};
6490
+ ${setState("prevVelY", `${particleVar}.velocity.y`)};
6491
+ }
6420
6492
  }`,
6421
- // Apply pass: compute pressure and viscosity forces using precomputed densities
6422
- apply: ({ particleVar, getUniform, getState }) => `{
6423
- let rad = ${getUniform("influenceRadius")};
6424
- let targetDensity = ${getUniform("targetDensity")};
6425
- let pressureMul = ${getUniform("pressureMultiplier")};
6426
- let visc = ${getUniform("viscosity")};
6427
- let nearMul = ${getUniform("nearPressureMultiplier")};
6428
- let nearThreshold = ${getUniform("nearThreshold")};
6429
- let useNear = ${getUniform("enableNearPressure")};
6430
-
6431
- let myDensity = max(${getState("density")}, 1e-6);
6432
-
6433
- // Precompute radius powers for kernels
6434
- let r2 = rad * rad;
6435
- let r4 = r2 * r2;
6436
- let r6 = r4 * r2;
6437
- let r8 = r4 * r4;
6493
+ // Apply pass:
6494
+ // - SPH: pressure + viscosity using precomputed densities
6495
+ // - PICFLIP: simplified local-pressure PIC/FLIP blend (+ optional viscosity)
6496
+ apply: ({ particleVar, dtVar, getUniform, getState }) => `{
6497
+ let method = ${getUniform("method")};
6438
6498
 
6439
- // Pressure gradient accumulation
6440
- var gradSum: vec2<f32> = vec2<f32>(0.0, 0.0);
6441
- var it1 = neighbor_iter_init(${particleVar}.position, rad);
6442
- loop {
6443
- let j = neighbor_iter_next(&it1, index);
6444
- if (j == NEIGHBOR_NONE) { break; }
6445
- let other = particles[j];
6446
- // Skip removed or pinned neighbors
6447
- if (other.mass <= 0.0) { continue; }
6448
- let delta = other.position - ${particleVar}.position;
6449
- let dist2 = dot(delta, delta);
6450
- if (dist2 <= 0.0) { continue; }
6451
- let dist = sqrt(dist2);
6452
- if (dist <= 0.0 || dist >= rad) { continue; }
6453
- let dir = delta / dist;
6454
-
6455
- // Derivative kernel (CPU: scale * (dist - rad), scale = (-12/pi)/r^4)
6456
- let scale = (-12.0 / 3.14159265) / r4;
6457
- let slope = (dist - rad) * scale;
6499
+ if (method < 0.5) {
6500
+ // ==========================
6501
+ // SPH
6502
+ // ==========================
6503
+ let rad = ${getUniform("influenceRadius")};
6504
+ let targetDensity = ${getUniform("targetDensity")};
6505
+ let pressureMul = ${getUniform("pressureMultiplier")};
6506
+ let visc = ${getUniform("viscosity")};
6507
+ let nearMul = ${getUniform("nearPressureMultiplier")};
6508
+ let nearThreshold = ${getUniform("nearThreshold")};
6509
+ let useNear = ${getUniform("enableNearPressure")};
6458
6510
 
6459
- // Neighbor pressures from precomputed densities
6460
- let dN = max(${getState("density", "j")}, 1e-6);
6461
- let nearN = select(0.0, ${getState("nearDensity", "j")}, useNear != 0.0);
6462
- let densityDiff = dN - targetDensity;
6463
- let pressure = densityDiff * pressureMul;
6464
- let nearPressure = nearN * nearMul;
6465
- let effectivePressure = select(pressure, nearPressure, dist < nearThreshold);
6511
+ let myDensity = max(${getState("density")}, 1e-6);
6466
6512
 
6467
- // Gradient contribution
6468
- gradSum = gradSum + (dir * (effectivePressure * slope) / dN);
6469
- }
6470
- // Pressure force is negative gradient
6471
- var pressureForce = -gradSum;
6513
+ // Precompute radius powers for kernels
6514
+ let r2 = rad * rad;
6515
+ let r4 = r2 * r2;
6516
+ let r6 = r4 * r2;
6517
+ let r8 = r4 * r4;
6472
6518
 
6473
- // Viscosity accumulation
6474
- var viscosityForce: vec2<f32> = vec2<f32>(0.0, 0.0);
6475
- if (visc != 0.0) {
6476
- var it2 = neighbor_iter_init(${particleVar}.position, rad);
6519
+ // Pressure gradient accumulation
6520
+ var gradSum: vec2<f32> = vec2<f32>(0.0, 0.0);
6521
+ var it1 = neighbor_iter_init(${particleVar}.position, rad);
6477
6522
  loop {
6478
- let j = neighbor_iter_next(&it2, index);
6523
+ let j = neighbor_iter_next(&it1, index);
6479
6524
  if (j == NEIGHBOR_NONE) { break; }
6480
6525
  let other = particles[j];
6481
6526
  // Skip removed or pinned neighbors
@@ -6484,141 +6529,257 @@ class Fluids extends Module {
6484
6529
  let dist2 = dot(delta, delta);
6485
6530
  if (dist2 <= 0.0) { continue; }
6486
6531
  let dist = sqrt(dist2);
6487
- if (dist >= rad) { continue; }
6488
- // Viscosity kernel (CPU: (max(0, r^2 - d^2)^3) / (pi/4 * r^8))
6489
- let val = max(0.0, r2 - dist2);
6490
- let kVisc = (val * val * val) / ((3.14159265 / 4.0) * r8);
6491
- viscosityForce = viscosityForce + (other.velocity - ${particleVar}.velocity) * kVisc;
6532
+ if (dist <= 0.0 || dist >= rad) { continue; }
6533
+ let dir = delta / dist;
6534
+
6535
+ // Derivative kernel (CPU: scale * (dist - rad), scale = (-12/pi)/r^4)
6536
+ let scale = (-12.0 / 3.14159265) / r4;
6537
+ let slope = (dist - rad) * scale;
6538
+
6539
+ // Neighbor pressures from precomputed densities
6540
+ let dN = max(${getState("density", "j")}, 1e-6);
6541
+ let nearN = select(0.0, ${getState("nearDensity", "j")}, useNear != 0.0);
6542
+ let densityDiff = dN - targetDensity;
6543
+ let pressure = densityDiff * pressureMul;
6544
+ let nearPressure = nearN * nearMul;
6545
+ let effectivePressure = select(pressure, nearPressure, dist < nearThreshold);
6546
+
6547
+ // Gradient contribution
6548
+ gradSum = gradSum + (dir * (effectivePressure * slope) / dN);
6492
6549
  }
6493
- viscosityForce = viscosityForce * visc;
6494
- }
6550
+ // Pressure force is negative gradient
6551
+ var pressureForce = -gradSum;
6495
6552
 
6496
- // Convert to acceleration-like effect: a = F / density
6497
- var force = (pressureForce / myDensity) * 1000000.0;
6498
- if (visc != 0.0) {
6499
- force = force + (viscosityForce * 1000.0) / myDensity;
6500
- }
6553
+ // Viscosity accumulation
6554
+ var viscosityForce: vec2<f32> = vec2<f32>(0.0, 0.0);
6555
+ if (visc != 0.0) {
6556
+ var it2 = neighbor_iter_init(${particleVar}.position, rad);
6557
+ loop {
6558
+ let j = neighbor_iter_next(&it2, index);
6559
+ if (j == NEIGHBOR_NONE) { break; }
6560
+ let other = particles[j];
6561
+ // Skip removed or pinned neighbors
6562
+ if (other.mass <= 0.0) { continue; }
6563
+ let delta = other.position - ${particleVar}.position;
6564
+ let dist2 = dot(delta, delta);
6565
+ if (dist2 <= 0.0) { continue; }
6566
+ let dist = sqrt(dist2);
6567
+ if (dist >= rad) { continue; }
6568
+ // Viscosity kernel (CPU: (max(0, r^2 - d^2)^3) / (pi/4 * r^8))
6569
+ let val = max(0.0, r2 - dist2);
6570
+ let kVisc = (val * val * val) / ((3.14159265 / 4.0) * r8);
6571
+ viscosityForce = viscosityForce + (other.velocity - ${particleVar}.velocity) * kVisc;
6572
+ }
6573
+ viscosityForce = viscosityForce * visc;
6574
+ }
6501
6575
 
6502
- // Clamp force magnitude to avoid instabilities (tunable)
6503
- let maxLen = ${getUniform("maxAcceleration")};
6504
- let f2 = dot(force, force);
6505
- if (f2 > maxLen * maxLen) {
6506
- let fLen = sqrt(f2);
6507
- force = force * (maxLen / fLen);
6508
- }
6576
+ // Convert to acceleration-like effect: a = F / density
6577
+ var force = (pressureForce / myDensity) * 1000000.0;
6578
+ if (visc != 0.0) {
6579
+ force = force + (viscosityForce * 1000.0) / myDensity;
6580
+ }
6581
+
6582
+ // Clamp force magnitude to avoid instabilities (tunable)
6583
+ let maxLen = ${getUniform("maxAcceleration")};
6584
+ let f2 = dot(force, force);
6585
+ if (f2 > maxLen * maxLen) {
6586
+ let fLen = sqrt(f2);
6587
+ force = force * (maxLen / fLen);
6588
+ }
6589
+
6590
+ // Apply directly to velocity (CPU mirrors this behavior)
6591
+ ${particleVar}.velocity = ${particleVar}.velocity + force;
6592
+ } else {
6593
+ // ==========================
6594
+ // PICFLIP (simplified local-pressure approximation)
6595
+ // ==========================
6596
+ // Scale UI-friendly values to PIC/FLIP internal tuning:
6597
+ // - influenceRadius: /2
6598
+ // - targetDensity: *3
6599
+ // - pressureMultiplier: *30
6600
+ let flipRatio = ${getUniform("flipRatio")};
6601
+ let rad = ${getUniform("influenceRadius")} * 0.5;
6602
+ let targetDensity = ${getUniform("targetDensity")} * 3.0;
6603
+ let pressureScale = ${getUniform("pressureMultiplier")} * 30.0;
6604
+
6605
+ // Get stored previous velocity
6606
+ let prevVelX = ${getState("prevVelX")};
6607
+ let prevVelY = ${getState("prevVelY")};
6608
+
6609
+ var newVelX = ${particleVar}.velocity.x;
6610
+ var newVelY = ${particleVar}.velocity.y;
6611
+
6612
+ // Local density + average neighbor velocity (PIC grid approximation)
6613
+ var density: f32 = 0.0;
6614
+ var avgVelX: f32 = 0.0;
6615
+ var avgVelY: f32 = 0.0;
6616
+ var count: f32 = 0.0;
6617
+
6618
+ var it = neighbor_iter_init(${particleVar}.position, rad);
6619
+ loop {
6620
+ let j = neighbor_iter_next(&it, index);
6621
+ if (j == NEIGHBOR_NONE) { break; }
6622
+ let other = particles[j];
6623
+ if (other.mass <= 0.0) { continue; }
6624
+
6625
+ let d = ${particleVar}.position - other.position;
6626
+ let dist2 = dot(d, d);
6627
+ if (dist2 <= 0.0 || dist2 > rad * rad) { continue; }
6628
+
6629
+ let dist = sqrt(dist2);
6630
+ let weight = 1.0 - dist / rad;
6631
+
6632
+ density = density + weight;
6633
+ avgVelX = avgVelX + other.velocity.x * weight;
6634
+ avgVelY = avgVelY + other.velocity.y * weight;
6635
+ count = count + weight;
6636
+ }
6637
+
6638
+ if (count > 0.0) {
6639
+ avgVelX = avgVelX / count;
6640
+ avgVelY = avgVelY / count;
6509
6641
 
6510
- // Apply directly to velocity (CPU mirrors this behavior)
6511
- ${particleVar}.velocity = ${particleVar}.velocity + force;
6642
+ // PIC/FLIP blend
6643
+ let picVelX = avgVelX;
6644
+ let picVelY = avgVelY;
6645
+ let flipVelX = newVelX + (avgVelX - prevVelX);
6646
+ let flipVelY = newVelY + (avgVelY - prevVelY);
6647
+
6648
+ newVelX = mix(picVelX, flipVelX, flipRatio);
6649
+ newVelY = mix(picVelY, flipVelY, flipRatio);
6650
+
6651
+ // Simple pressure force based on local density
6652
+ let maxPressureFactor = abs(pressureScale) * 10.0;
6653
+ let pressureFactor = clamp(
6654
+ (density - targetDensity) * pressureScale,
6655
+ -maxPressureFactor,
6656
+ maxPressureFactor
6657
+ );
6658
+
6659
+ // Apply pressure gradient (push away from high density regions)
6660
+ var gradX: f32 = 0.0;
6661
+ var gradY: f32 = 0.0;
6662
+
6663
+ var it2 = neighbor_iter_init(${particleVar}.position, rad);
6664
+ loop {
6665
+ let j = neighbor_iter_next(&it2, index);
6666
+ if (j == NEIGHBOR_NONE) { break; }
6667
+ let other = particles[j];
6668
+ if (other.mass <= 0.0) { continue; }
6669
+
6670
+ let d = ${particleVar}.position - other.position;
6671
+ let dist2 = dot(d, d);
6672
+ if (dist2 <= 1.0 || dist2 > rad * rad) { continue; }
6673
+
6674
+ let dist = sqrt(dist2);
6675
+ let dir = d / dist;
6676
+ let weight = 1.0 - dist / rad;
6677
+
6678
+ gradX = gradX + dir.x * weight * pressureFactor;
6679
+ gradY = gradY + dir.y * weight * pressureFactor;
6680
+ }
6681
+
6682
+ // Clamp acceleration magnitude (units: velocity / second) to avoid dt-dependent instability
6683
+ // (notably visible at lower FPS / larger dt near boundaries).
6684
+ let maxAccel = 30000.0;
6685
+ var grad = vec2<f32>(gradX, gradY);
6686
+ let gl = length(grad);
6687
+ if (gl > maxAccel) {
6688
+ grad = grad * (maxAccel / max(gl, 1e-6));
6689
+ }
6690
+
6691
+ newVelX = newVelX + grad.x * ${dtVar};
6692
+ newVelY = newVelY + grad.y * ${dtVar};
6693
+ }
6694
+
6695
+ ${particleVar}.velocity.x = newVelX;
6696
+ ${particleVar}.velocity.y = newVelY;
6697
+ }
6512
6698
  }`,
6513
6699
  };
6514
6700
  }
6515
6701
  cpu() {
6516
6702
  return {
6517
- states: ["density", "nearDensity"],
6518
- // State pass: precompute density and near-density per particle
6703
+ states: ["density", "nearDensity", "prevVelX", "prevVelY"],
6704
+ // State pass:
6705
+ // - SPH: precompute density and near-density per particle
6706
+ // - PICFLIP: store previous velocity per particle
6519
6707
  state: ({ particle, getNeighbors, dt, setState }) => {
6520
- // Get fluid parameters
6521
- const rad = this.readValue("influenceRadius");
6522
- const enableNearPressure = this.readValue("enableNearPressure");
6523
- // Predict current particle position for this frame (approximate)
6524
- const posPredX = particle.position.x + particle.velocity.x * dt;
6525
- const posPredY = particle.position.y + particle.velocity.y * dt;
6526
- let density = 0.0;
6527
- let nearDensity = 0.0;
6528
- // Precompute radius powers for kernels
6529
- const r2 = rad * rad;
6530
- const r4 = r2 * r2;
6531
- const r6 = r4 * r2;
6532
- // Get neighbors using predicted position
6533
- const neighbors = getNeighbors({ x: posPredX, y: posPredY }, rad);
6534
- for (const other of neighbors) {
6535
- if (other.id === particle.id)
6536
- continue; // Skip self
6537
- // Skip removed or pinned neighbors
6538
- if (other.mass <= 0)
6539
- continue;
6540
- const dX = posPredX - other.position.x;
6541
- const dY = posPredY - other.position.y;
6542
- const dist2 = dX * dX + dY * dY;
6543
- if (dist2 <= 0.0)
6544
- continue;
6545
- const dist = Math.sqrt(dist2);
6546
- const factor = Math.max(rad - dist, 0.0);
6547
- if (factor <= 0.0)
6548
- continue;
6549
- // Density kernel: (factor^2) / (pi/6 * r^4)
6550
- const kDensity = (factor * factor) / ((Math.PI / 6.0) * r4);
6551
- density += kDensity * 1000.0 * other.mass;
6552
- // Near-density kernel: (factor^4) / (pi/15 * r^6)
6553
- if (enableNearPressure !== 0.0) {
6554
- const f2 = factor * factor;
6555
- const kNear = (f2 * f2) / ((Math.PI / 15.0) * r6);
6556
- nearDensity += kNear * 1000.0 * other.mass;
6708
+ const method = this.readValue("method");
6709
+ if (method < 0.5) {
6710
+ // SPH
6711
+ const rad = this.readValue("influenceRadius");
6712
+ const enableNearPressure = this.readValue("enableNearPressure");
6713
+ // Predict current particle position for this frame (approximate)
6714
+ const posPredX = particle.position.x + particle.velocity.x * dt;
6715
+ const posPredY = particle.position.y + particle.velocity.y * dt;
6716
+ let density = 0.0;
6717
+ let nearDensity = 0.0;
6718
+ // Precompute radius powers for kernels
6719
+ const r2 = rad * rad;
6720
+ const r4 = r2 * r2;
6721
+ const r6 = r4 * r2;
6722
+ // Get neighbors using predicted position
6723
+ const neighbors = getNeighbors({ x: posPredX, y: posPredY }, rad);
6724
+ for (const other of neighbors) {
6725
+ if (other.id === particle.id)
6726
+ continue; // Skip self
6727
+ // Skip removed or pinned neighbors
6728
+ if (other.mass <= 0)
6729
+ continue;
6730
+ const dX = posPredX - other.position.x;
6731
+ const dY = posPredY - other.position.y;
6732
+ const dist2 = dX * dX + dY * dY;
6733
+ if (dist2 <= 0.0)
6734
+ continue;
6735
+ const dist = Math.sqrt(dist2);
6736
+ const factor = Math.max(rad - dist, 0.0);
6737
+ if (factor <= 0.0)
6738
+ continue;
6739
+ // Density kernel: (factor^2) / (pi/6 * r^4)
6740
+ const kDensity = (factor * factor) / ((Math.PI / 6.0) * r4);
6741
+ density += kDensity * 1000.0 * other.mass;
6742
+ // Near-density kernel: (factor^4) / (pi/15 * r^6)
6743
+ if (enableNearPressure !== 0.0) {
6744
+ const f2 = factor * factor;
6745
+ const kNear = (f2 * f2) / ((Math.PI / 15.0) * r6);
6746
+ nearDensity += kNear * 1000.0 * other.mass;
6747
+ }
6557
6748
  }
6749
+ // Store results in shared state for use in apply pass
6750
+ setState("density", density);
6751
+ setState("nearDensity", nearDensity);
6558
6752
  }
6559
- // Store results in shared state for use in apply pass
6560
- setState("density", density);
6561
- setState("nearDensity", nearDensity);
6562
- },
6563
- // Apply pass: compute pressure and viscosity forces using precomputed densities
6564
- apply: ({ particle, getNeighbors, getState }) => {
6565
- // Get fluid parameters
6566
- const rad = this.readValue("influenceRadius");
6567
- const targetDensity = this.readValue("targetDensity");
6568
- const pressureMul = this.readValue("pressureMultiplier");
6569
- const visc = this.readValue("viscosity");
6570
- const nearMul = this.readValue("nearPressureMultiplier");
6571
- const nearThreshold = this.readValue("nearThreshold");
6572
- const useNear = this.readValue("enableNearPressure");
6573
- const maxAccel = this.readValue("maxAcceleration");
6574
- const myDensity = Math.max(getState("density"), 1e-6);
6575
- // Precompute radius powers for kernels
6576
- const r2 = rad * rad;
6577
- const r4 = r2 * r2;
6578
- const r8 = r4 * r4;
6579
- // Pressure gradient accumulation
6580
- let gradSumX = 0.0;
6581
- let gradSumY = 0.0;
6582
- const neighbors = getNeighbors(particle.position, rad);
6583
- for (const other of neighbors) {
6584
- if (other.id === particle.id)
6585
- continue; // Skip self
6586
- // Skip removed or pinned neighbors
6587
- if (other.mass <= 0)
6588
- continue;
6589
- const deltaX = other.position.x - particle.position.x;
6590
- const deltaY = other.position.y - particle.position.y;
6591
- const dist2 = deltaX * deltaX + deltaY * deltaY;
6592
- if (dist2 <= 0.0)
6593
- continue;
6594
- const dist = Math.sqrt(dist2);
6595
- if (dist <= 0.0 || dist >= rad)
6596
- continue;
6597
- const dirX = deltaX / dist;
6598
- const dirY = deltaY / dist;
6599
- // Derivative kernel: scale * (dist - rad), scale = (-12/pi)/r^4
6600
- const scale = -12 / Math.PI / r4;
6601
- const slope = (dist - rad) * scale;
6602
- // Neighbor pressures from precomputed densities
6603
- const dN = Math.max(getState("density", other.id), 1e-6);
6604
- const nearN = useNear !== 0.0 ? getState("nearDensity", other.id) : 0.0;
6605
- const densityDiff = dN - targetDensity;
6606
- const pressure = densityDiff * pressureMul;
6607
- const nearPressure = nearN * nearMul;
6608
- const effectivePressure = dist < nearThreshold ? nearPressure : pressure;
6609
- // Gradient contribution
6610
- const gradContribX = (dirX * (effectivePressure * slope)) / dN;
6611
- const gradContribY = (dirY * (effectivePressure * slope)) / dN;
6612
- gradSumX += gradContribX;
6613
- gradSumY += gradContribY;
6753
+ else {
6754
+ // PICFLIP
6755
+ setState("prevVelX", particle.velocity.x);
6756
+ setState("prevVelY", particle.velocity.y);
6614
6757
  }
6615
- // Pressure force is negative gradient
6616
- let pressureForceX = -gradSumX;
6617
- let pressureForceY = -gradSumY;
6618
- // Viscosity accumulation
6619
- let viscosityForceX = 0.0;
6620
- let viscosityForceY = 0.0;
6621
- if (visc !== 0.0) {
6758
+ },
6759
+ // Apply pass:
6760
+ // - SPH: pressure + viscosity using precomputed densities
6761
+ // - PICFLIP: simplified local-pressure PIC/FLIP blend (+ optional viscosity)
6762
+ apply: ({ particle, getNeighbors, dt, getState }) => {
6763
+ const method = this.readValue("method");
6764
+ if (method < 0.5) {
6765
+ // SPH
6766
+ const rad = this.readValue("influenceRadius");
6767
+ const targetDensity = this.readValue("targetDensity");
6768
+ const pressureMul = this.readValue("pressureMultiplier");
6769
+ const visc = this.readValue("viscosity");
6770
+ const nearMul = this.readValue("nearPressureMultiplier");
6771
+ const nearThreshold = this.readValue("nearThreshold");
6772
+ const useNear = this.readValue("enableNearPressure");
6773
+ const maxAccel = this.readValue("maxAcceleration");
6774
+ const myDensity = Math.max(getState("density"), 1e-6);
6775
+ // Precompute radius powers for kernels
6776
+ const r2 = rad * rad;
6777
+ const r4 = r2 * r2;
6778
+ const r8 = r4 * r4;
6779
+ // Pressure gradient accumulation
6780
+ let gradSumX = 0.0;
6781
+ let gradSumY = 0.0;
6782
+ const neighbors = getNeighbors(particle.position, rad);
6622
6783
  for (const other of neighbors) {
6623
6784
  if (other.id === particle.id)
6624
6785
  continue; // Skip self
@@ -6631,34 +6792,163 @@ class Fluids extends Module {
6631
6792
  if (dist2 <= 0.0)
6632
6793
  continue;
6633
6794
  const dist = Math.sqrt(dist2);
6634
- if (dist >= rad)
6795
+ if (dist <= 0.0 || dist >= rad)
6635
6796
  continue;
6636
- // Viscosity kernel: (max(0, r^2 - d^2)^3) / (pi/4 * r^8)
6637
- const val = Math.max(0.0, r2 - dist2);
6638
- const kVisc = (val * val * val) / ((Math.PI / 4.0) * r8);
6639
- viscosityForceX += (other.velocity.x - particle.velocity.x) * kVisc;
6640
- viscosityForceY += (other.velocity.y - particle.velocity.y) * kVisc;
6797
+ const dirX = deltaX / dist;
6798
+ const dirY = deltaY / dist;
6799
+ // Derivative kernel: scale * (dist - rad), scale = (-12/pi)/r^4
6800
+ const scale = -12 / Math.PI / r4;
6801
+ const slope = (dist - rad) * scale;
6802
+ // Neighbor pressures from precomputed densities
6803
+ const dN = Math.max(getState("density", other.id), 1e-6);
6804
+ const nearN = useNear !== 0.0 ? getState("nearDensity", other.id) : 0.0;
6805
+ const densityDiff = dN - targetDensity;
6806
+ const pressure = densityDiff * pressureMul;
6807
+ const nearPressure = nearN * nearMul;
6808
+ const effectivePressure = dist < nearThreshold ? nearPressure : pressure;
6809
+ // Gradient contribution
6810
+ const gradContribX = (dirX * (effectivePressure * slope)) / dN;
6811
+ const gradContribY = (dirY * (effectivePressure * slope)) / dN;
6812
+ gradSumX += gradContribX;
6813
+ gradSumY += gradContribY;
6641
6814
  }
6642
- viscosityForceX *= visc;
6643
- viscosityForceY *= visc;
6644
- }
6645
- // Convert to acceleration-like effect: a = F / density
6646
- let forceX = (pressureForceX / myDensity) * 1000000.0;
6647
- let forceY = (pressureForceY / myDensity) * 1000000.0;
6648
- if (visc !== 0.0) {
6649
- forceX += (viscosityForceX * 1000.0) / myDensity;
6650
- forceY += (viscosityForceY * 1000.0) / myDensity;
6815
+ // Pressure force is negative gradient
6816
+ let pressureForceX = -gradSumX;
6817
+ let pressureForceY = -gradSumY;
6818
+ // Viscosity accumulation
6819
+ let viscosityForceX = 0.0;
6820
+ let viscosityForceY = 0.0;
6821
+ if (visc !== 0.0) {
6822
+ for (const other of neighbors) {
6823
+ if (other.id === particle.id)
6824
+ continue; // Skip self
6825
+ // Skip removed or pinned neighbors
6826
+ if (other.mass <= 0)
6827
+ continue;
6828
+ const deltaX = other.position.x - particle.position.x;
6829
+ const deltaY = other.position.y - particle.position.y;
6830
+ const dist2 = deltaX * deltaX + deltaY * deltaY;
6831
+ if (dist2 <= 0.0)
6832
+ continue;
6833
+ const dist = Math.sqrt(dist2);
6834
+ if (dist >= rad)
6835
+ continue;
6836
+ // Viscosity kernel: (max(0, r^2 - d^2)^3) / (pi/4 * r^8)
6837
+ const val = Math.max(0.0, r2 - dist2);
6838
+ const kVisc = (val * val * val) / ((Math.PI / 4.0) * r8);
6839
+ viscosityForceX +=
6840
+ (other.velocity.x - particle.velocity.x) * kVisc;
6841
+ viscosityForceY +=
6842
+ (other.velocity.y - particle.velocity.y) * kVisc;
6843
+ }
6844
+ viscosityForceX *= visc;
6845
+ viscosityForceY *= visc;
6846
+ }
6847
+ // Convert to acceleration-like effect: a = F / density
6848
+ let forceX = (pressureForceX / myDensity) * 1000000.0;
6849
+ let forceY = (pressureForceY / myDensity) * 1000000.0;
6850
+ if (visc !== 0.0) {
6851
+ forceX += (viscosityForceX * 1000.0) / myDensity;
6852
+ forceY += (viscosityForceY * 1000.0) / myDensity;
6853
+ }
6854
+ // Clamp force magnitude to avoid instabilities
6855
+ const f2 = forceX * forceX + forceY * forceY;
6856
+ if (f2 > maxAccel * maxAccel) {
6857
+ const fLen = Math.sqrt(f2);
6858
+ forceX = forceX * (maxAccel / fLen);
6859
+ forceY = forceY * (maxAccel / fLen);
6860
+ }
6861
+ // Apply directly to velocity (matching WebGPU behavior)
6862
+ particle.velocity.x += forceX;
6863
+ particle.velocity.y += forceY;
6651
6864
  }
6652
- // Clamp force magnitude to avoid instabilities
6653
- const f2 = forceX * forceX + forceY * forceY;
6654
- if (f2 > maxAccel * maxAccel) {
6655
- const fLen = Math.sqrt(f2);
6656
- forceX = forceX * (maxAccel / fLen);
6657
- forceY = forceY * (maxAccel / fLen);
6865
+ else {
6866
+ // PICFLIP
6867
+ // Scale UI-friendly values to PIC/FLIP internal tuning:
6868
+ // - influenceRadius: /2
6869
+ // - targetDensity: *3
6870
+ // - pressureMultiplier: *30
6871
+ const flipRatio = this.readValue("flipRatio");
6872
+ const targetDensity = this.readValue("targetDensity") * 3.0;
6873
+ const rad = this.readValue("influenceRadius") * 0.5;
6874
+ const pressureScale = this.readValue("pressureMultiplier") * 30.0;
6875
+ // Get stored previous velocity
6876
+ const prevVelX = getState("prevVelX");
6877
+ const prevVelY = getState("prevVelY");
6878
+ let newVelX = particle.velocity.x;
6879
+ let newVelY = particle.velocity.y;
6880
+ // Local pressure approximation using neighbor density
6881
+ let density = 0.0;
6882
+ let avgVelX = 0.0;
6883
+ let avgVelY = 0.0;
6884
+ let count = 0.0;
6885
+ const neighbors = getNeighbors(particle.position, rad);
6886
+ for (const other of neighbors) {
6887
+ if (other.id === particle.id)
6888
+ continue;
6889
+ if (other.mass <= 0)
6890
+ continue;
6891
+ const dx = particle.position.x - other.position.x;
6892
+ const dy = particle.position.y - other.position.y;
6893
+ const dist2 = dx * dx + dy * dy;
6894
+ if (dist2 <= 0 || dist2 > rad * rad)
6895
+ continue;
6896
+ const dist = Math.sqrt(dist2);
6897
+ const weight = 1.0 - dist / rad;
6898
+ density += weight;
6899
+ avgVelX += other.velocity.x * weight;
6900
+ avgVelY += other.velocity.y * weight;
6901
+ count += weight;
6902
+ }
6903
+ if (count > 0) {
6904
+ avgVelX /= count;
6905
+ avgVelY /= count;
6906
+ // PIC/FLIP blend
6907
+ const picVelX = avgVelX;
6908
+ const picVelY = avgVelY;
6909
+ const flipVelX = newVelX + (avgVelX - prevVelX);
6910
+ const flipVelY = newVelY + (avgVelY - prevVelY);
6911
+ newVelX = picVelX * (1 - flipRatio) + flipVelX * flipRatio;
6912
+ newVelY = picVelY * (1 - flipRatio) + flipVelY * flipRatio;
6913
+ // Simple pressure force based on local density
6914
+ const maxPressureFactor = Math.abs(pressureScale) * 10.0;
6915
+ const rawPressureFactor = (density - targetDensity) * pressureScale;
6916
+ const pressureFactor = Math.max(-maxPressureFactor, Math.min(maxPressureFactor, rawPressureFactor));
6917
+ // Apply pressure gradient
6918
+ let gradX = 0.0;
6919
+ let gradY = 0.0;
6920
+ for (const other of neighbors) {
6921
+ if (other.id === particle.id)
6922
+ continue;
6923
+ if (other.mass <= 0)
6924
+ continue;
6925
+ const dx = particle.position.x - other.position.x;
6926
+ const dy = particle.position.y - other.position.y;
6927
+ const dist2 = dx * dx + dy * dy;
6928
+ if (dist2 <= 1.0 || dist2 > rad * rad)
6929
+ continue;
6930
+ const dist = Math.sqrt(dist2);
6931
+ const dirX = dx / dist;
6932
+ const dirY = dy / dist;
6933
+ const weight = 1.0 - dist / rad;
6934
+ gradX += dirX * weight * pressureFactor;
6935
+ gradY += dirY * weight * pressureFactor;
6936
+ }
6937
+ // Clamp acceleration magnitude (units: velocity / second) to avoid dt-dependent instability
6938
+ // (notably visible at lower FPS / larger dt near boundaries).
6939
+ const maxAccel = 20000.0;
6940
+ const gradLen = Math.hypot(gradX, gradY);
6941
+ if (gradLen > maxAccel && gradLen > 1e-6) {
6942
+ const s = maxAccel / gradLen;
6943
+ gradX *= s;
6944
+ gradY *= s;
6945
+ }
6946
+ newVelX += gradX * dt;
6947
+ newVelY += gradY * dt;
6948
+ }
6949
+ particle.velocity.x = newVelX;
6950
+ particle.velocity.y = newVelY;
6658
6951
  }
6659
- // Apply directly to velocity (matching WebGPU behavior)
6660
- particle.velocity.x += forceX;
6661
- particle.velocity.y += forceY;
6662
6952
  },
6663
6953
  };
6664
6954
  }
@@ -9708,5 +9998,5 @@ class Particles extends Module {
9708
9998
  }
9709
9999
  }
9710
10000
 
9711
- export { AbstractEngine, Behavior, Boundary, CanvasComposition, Collisions, DEFAULT_BEHAVIOR_ALIGNMENT, DEFAULT_BEHAVIOR_AVOID, DEFAULT_BEHAVIOR_CHASE, DEFAULT_BEHAVIOR_COHESION, DEFAULT_BEHAVIOR_REPULSION, DEFAULT_BEHAVIOR_SEPARATION, DEFAULT_BEHAVIOR_VIEW_ANGLE, DEFAULT_BEHAVIOR_VIEW_RADIUS, DEFAULT_BEHAVIOR_WANDER, DEFAULT_BOUNDARY_FRICTION, DEFAULT_BOUNDARY_MODE, DEFAULT_BOUNDARY_REPEL_DISTANCE, DEFAULT_BOUNDARY_REPEL_STRENGTH, DEFAULT_BOUNDARY_RESTITUTION, DEFAULT_COLLISIONS_RESTITUTION, DEFAULT_ENVIRONMENT_DAMPING, DEFAULT_ENVIRONMENT_FRICTION, DEFAULT_ENVIRONMENT_GRAVITY_ANGLE, DEFAULT_ENVIRONMENT_GRAVITY_DIRECTION, DEFAULT_ENVIRONMENT_GRAVITY_STRENGTH, DEFAULT_ENVIRONMENT_INERTIA, DEFAULT_FLUIDS_ENABLE_NEAR_PRESSURE, DEFAULT_FLUIDS_INFLUENCE_RADIUS, DEFAULT_FLUIDS_MAX_ACCELERATION, DEFAULT_FLUIDS_NEAR_PRESSURE_MULTIPLIER, DEFAULT_FLUIDS_NEAR_THRESHOLD, DEFAULT_FLUIDS_PRESSURE_MULTIPLIER, DEFAULT_FLUIDS_TARGET_DENSITY, DEFAULT_FLUIDS_VISCOSITY, DEFAULT_GRAB_GRABBED_INDEX, DEFAULT_GRAB_POSITION_X, DEFAULT_GRAB_POSITION_Y, DEFAULT_INTERACTION_MODE, DEFAULT_INTERACTION_RADIUS, DEFAULT_INTERACTION_STRENGTH, DEFAULT_JOINTS_ENABLE_JOINT_COLLISIONS, DEFAULT_JOINTS_ENABLE_PARTICLE_COLLISIONS, DEFAULT_JOINTS_FRICTION, DEFAULT_JOINTS_MOMENTUM, DEFAULT_JOINTS_RESTITUTION, DEFAULT_JOINTS_SEPARATION, DEFAULT_JOINTS_STEPS, DEFAULT_SENSORS_COLOR_SIMILARITY_THRESHOLD, DEFAULT_SENSORS_FLEE_ANGLE, DEFAULT_SENSORS_FLEE_BEHAVIOR, DEFAULT_SENSORS_FOLLOW_BEHAVIOR, DEFAULT_SENSORS_SENSOR_ANGLE, DEFAULT_SENSORS_SENSOR_DISTANCE, DEFAULT_SENSORS_SENSOR_RADIUS, DEFAULT_SENSORS_SENSOR_STRENGTH, DEFAULT_SENSORS_SENSOR_THRESHOLD, DEFAULT_TRAILS_TRAIL_DECAY, DEFAULT_TRAILS_TRAIL_DIFFUSE, DataType, Engine, Environment, Fluids, Grab, Interaction, Joints, Lines, Module, ModuleRole, OscillatorManager, Particles, ParticlesColorType, RenderPassKind, Sensors, Spawner, Trails, Vector, degToRad, radToDeg };
10001
+ export { AbstractEngine, Behavior, Boundary, CanvasComposition, Collisions, DEFAULT_BEHAVIOR_ALIGNMENT, DEFAULT_BEHAVIOR_AVOID, DEFAULT_BEHAVIOR_CHASE, DEFAULT_BEHAVIOR_COHESION, DEFAULT_BEHAVIOR_REPULSION, DEFAULT_BEHAVIOR_SEPARATION, DEFAULT_BEHAVIOR_VIEW_ANGLE, DEFAULT_BEHAVIOR_VIEW_RADIUS, DEFAULT_BEHAVIOR_WANDER, DEFAULT_BOUNDARY_FRICTION, DEFAULT_BOUNDARY_MODE, DEFAULT_BOUNDARY_REPEL_DISTANCE, DEFAULT_BOUNDARY_REPEL_STRENGTH, DEFAULT_BOUNDARY_RESTITUTION, DEFAULT_COLLISIONS_RESTITUTION, DEFAULT_ENVIRONMENT_DAMPING, DEFAULT_ENVIRONMENT_FRICTION, DEFAULT_ENVIRONMENT_GRAVITY_ANGLE, DEFAULT_ENVIRONMENT_GRAVITY_DIRECTION, DEFAULT_ENVIRONMENT_GRAVITY_STRENGTH, DEFAULT_ENVIRONMENT_INERTIA, DEFAULT_FLUIDS_ENABLE_NEAR_PRESSURE, DEFAULT_FLUIDS_INFLUENCE_RADIUS, DEFAULT_FLUIDS_MAX_ACCELERATION, DEFAULT_FLUIDS_NEAR_PRESSURE_MULTIPLIER, DEFAULT_FLUIDS_NEAR_THRESHOLD, DEFAULT_FLUIDS_PRESSURE_MULTIPLIER, DEFAULT_FLUIDS_TARGET_DENSITY, DEFAULT_FLUIDS_VISCOSITY, DEFAULT_GRAB_GRABBED_INDEX, DEFAULT_GRAB_POSITION_X, DEFAULT_GRAB_POSITION_Y, DEFAULT_INTERACTION_MODE, DEFAULT_INTERACTION_RADIUS, DEFAULT_INTERACTION_STRENGTH, DEFAULT_JOINTS_ENABLE_JOINT_COLLISIONS, DEFAULT_JOINTS_ENABLE_PARTICLE_COLLISIONS, DEFAULT_JOINTS_FRICTION, DEFAULT_JOINTS_MOMENTUM, DEFAULT_JOINTS_RESTITUTION, DEFAULT_JOINTS_SEPARATION, DEFAULT_JOINTS_STEPS, DEFAULT_PICFLIP_DENSITY, DEFAULT_PICFLIP_FLIP_RATIO, DEFAULT_PICFLIP_INFLUENCE_RADIUS, DEFAULT_PICFLIP_INTERNAL_MAX_ACCELERATION, DEFAULT_PICFLIP_PRESSURE, DEFAULT_PICFLIP_PRESSURE_MULTIPLIER, DEFAULT_PICFLIP_RADIUS, DEFAULT_PICFLIP_TARGET_DENSITY, DEFAULT_SENSORS_COLOR_SIMILARITY_THRESHOLD, DEFAULT_SENSORS_FLEE_ANGLE, DEFAULT_SENSORS_FLEE_BEHAVIOR, DEFAULT_SENSORS_FOLLOW_BEHAVIOR, DEFAULT_SENSORS_SENSOR_ANGLE, DEFAULT_SENSORS_SENSOR_DISTANCE, DEFAULT_SENSORS_SENSOR_RADIUS, DEFAULT_SENSORS_SENSOR_STRENGTH, DEFAULT_SENSORS_SENSOR_THRESHOLD, DEFAULT_TRAILS_TRAIL_DECAY, DEFAULT_TRAILS_TRAIL_DIFFUSE, DataType, Engine, Environment, Fluids, FluidsMethod, Grab, Interaction, Joints, Lines, Module, ModuleRole, OscillatorManager, Particles, ParticlesColorType, RenderPassKind, Sensors, Spawner, Trails, Vector, degToRad, radToDeg };
9712
10002
  //# sourceMappingURL=index.js.map