@cazala/party 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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")};
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")};
6430
6498
 
6431
- let myDensity = max(${getState("density")}, 1e-6);
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")};
6432
6510
 
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;
6511
+ let myDensity = max(${getState("density")}, 1e-6);
6438
6512
 
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;
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;
6458
6518
 
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);
6466
-
6467
- // Gradient contribution
6468
- gradSum = gradSum + (dir * (effectivePressure * slope) / dN);
6469
- }
6470
- // Pressure force is negative gradient
6471
- var pressureForce = -gradSum;
6472
-
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,249 @@ 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
+ }
6509
6637
 
6510
- // Apply directly to velocity (CPU mirrors this behavior)
6511
- ${particleVar}.velocity = ${particleVar}.velocity + force;
6638
+ if (count > 0.0) {
6639
+ avgVelX = avgVelX / count;
6640
+ avgVelY = avgVelY / count;
6641
+
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
+ // NOTE: no max-acceleration clamp for PIC/FLIP (it tends to need the max anyway)
6683
+ newVelX = newVelX + gradX * ${dtVar};
6684
+ newVelY = newVelY + gradY * ${dtVar};
6685
+ }
6686
+
6687
+ ${particleVar}.velocity.x = newVelX;
6688
+ ${particleVar}.velocity.y = newVelY;
6689
+ }
6512
6690
  }`,
6513
6691
  };
6514
6692
  }
6515
6693
  cpu() {
6516
6694
  return {
6517
- states: ["density", "nearDensity"],
6518
- // State pass: precompute density and near-density per particle
6695
+ states: ["density", "nearDensity", "prevVelX", "prevVelY"],
6696
+ // State pass:
6697
+ // - SPH: precompute density and near-density per particle
6698
+ // - PICFLIP: store previous velocity per particle
6519
6699
  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;
6700
+ const method = this.readValue("method");
6701
+ if (method < 0.5) {
6702
+ // SPH
6703
+ const rad = this.readValue("influenceRadius");
6704
+ const enableNearPressure = this.readValue("enableNearPressure");
6705
+ // Predict current particle position for this frame (approximate)
6706
+ const posPredX = particle.position.x + particle.velocity.x * dt;
6707
+ const posPredY = particle.position.y + particle.velocity.y * dt;
6708
+ let density = 0.0;
6709
+ let nearDensity = 0.0;
6710
+ // Precompute radius powers for kernels
6711
+ const r2 = rad * rad;
6712
+ const r4 = r2 * r2;
6713
+ const r6 = r4 * r2;
6714
+ // Get neighbors using predicted position
6715
+ const neighbors = getNeighbors({ x: posPredX, y: posPredY }, rad);
6716
+ for (const other of neighbors) {
6717
+ if (other.id === particle.id)
6718
+ continue; // Skip self
6719
+ // Skip removed or pinned neighbors
6720
+ if (other.mass <= 0)
6721
+ continue;
6722
+ const dX = posPredX - other.position.x;
6723
+ const dY = posPredY - other.position.y;
6724
+ const dist2 = dX * dX + dY * dY;
6725
+ if (dist2 <= 0.0)
6726
+ continue;
6727
+ const dist = Math.sqrt(dist2);
6728
+ const factor = Math.max(rad - dist, 0.0);
6729
+ if (factor <= 0.0)
6730
+ continue;
6731
+ // Density kernel: (factor^2) / (pi/6 * r^4)
6732
+ const kDensity = (factor * factor) / ((Math.PI / 6.0) * r4);
6733
+ density += kDensity * 1000.0 * other.mass;
6734
+ // Near-density kernel: (factor^4) / (pi/15 * r^6)
6735
+ if (enableNearPressure !== 0.0) {
6736
+ const f2 = factor * factor;
6737
+ const kNear = (f2 * f2) / ((Math.PI / 15.0) * r6);
6738
+ nearDensity += kNear * 1000.0 * other.mass;
6739
+ }
6557
6740
  }
6741
+ // Store results in shared state for use in apply pass
6742
+ setState("density", density);
6743
+ setState("nearDensity", nearDensity);
6558
6744
  }
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;
6745
+ else {
6746
+ // PICFLIP
6747
+ setState("prevVelX", particle.velocity.x);
6748
+ setState("prevVelY", particle.velocity.y);
6614
6749
  }
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) {
6750
+ },
6751
+ // Apply pass:
6752
+ // - SPH: pressure + viscosity using precomputed densities
6753
+ // - PICFLIP: simplified local-pressure PIC/FLIP blend (+ optional viscosity)
6754
+ apply: ({ particle, getNeighbors, dt, getState }) => {
6755
+ const method = this.readValue("method");
6756
+ if (method < 0.5) {
6757
+ // SPH
6758
+ const rad = this.readValue("influenceRadius");
6759
+ const targetDensity = this.readValue("targetDensity");
6760
+ const pressureMul = this.readValue("pressureMultiplier");
6761
+ const visc = this.readValue("viscosity");
6762
+ const nearMul = this.readValue("nearPressureMultiplier");
6763
+ const nearThreshold = this.readValue("nearThreshold");
6764
+ const useNear = this.readValue("enableNearPressure");
6765
+ const maxAccel = this.readValue("maxAcceleration");
6766
+ const myDensity = Math.max(getState("density"), 1e-6);
6767
+ // Precompute radius powers for kernels
6768
+ const r2 = rad * rad;
6769
+ const r4 = r2 * r2;
6770
+ const r8 = r4 * r4;
6771
+ // Pressure gradient accumulation
6772
+ let gradSumX = 0.0;
6773
+ let gradSumY = 0.0;
6774
+ const neighbors = getNeighbors(particle.position, rad);
6622
6775
  for (const other of neighbors) {
6623
6776
  if (other.id === particle.id)
6624
6777
  continue; // Skip self
@@ -6631,34 +6784,155 @@ class Fluids extends Module {
6631
6784
  if (dist2 <= 0.0)
6632
6785
  continue;
6633
6786
  const dist = Math.sqrt(dist2);
6634
- if (dist >= rad)
6787
+ if (dist <= 0.0 || dist >= rad)
6635
6788
  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;
6789
+ const dirX = deltaX / dist;
6790
+ const dirY = deltaY / dist;
6791
+ // Derivative kernel: scale * (dist - rad), scale = (-12/pi)/r^4
6792
+ const scale = -12 / Math.PI / r4;
6793
+ const slope = (dist - rad) * scale;
6794
+ // Neighbor pressures from precomputed densities
6795
+ const dN = Math.max(getState("density", other.id), 1e-6);
6796
+ const nearN = useNear !== 0.0 ? getState("nearDensity", other.id) : 0.0;
6797
+ const densityDiff = dN - targetDensity;
6798
+ const pressure = densityDiff * pressureMul;
6799
+ const nearPressure = nearN * nearMul;
6800
+ const effectivePressure = dist < nearThreshold ? nearPressure : pressure;
6801
+ // Gradient contribution
6802
+ const gradContribX = (dirX * (effectivePressure * slope)) / dN;
6803
+ const gradContribY = (dirY * (effectivePressure * slope)) / dN;
6804
+ gradSumX += gradContribX;
6805
+ gradSumY += gradContribY;
6641
6806
  }
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;
6807
+ // Pressure force is negative gradient
6808
+ let pressureForceX = -gradSumX;
6809
+ let pressureForceY = -gradSumY;
6810
+ // Viscosity accumulation
6811
+ let viscosityForceX = 0.0;
6812
+ let viscosityForceY = 0.0;
6813
+ if (visc !== 0.0) {
6814
+ for (const other of neighbors) {
6815
+ if (other.id === particle.id)
6816
+ continue; // Skip self
6817
+ // Skip removed or pinned neighbors
6818
+ if (other.mass <= 0)
6819
+ continue;
6820
+ const deltaX = other.position.x - particle.position.x;
6821
+ const deltaY = other.position.y - particle.position.y;
6822
+ const dist2 = deltaX * deltaX + deltaY * deltaY;
6823
+ if (dist2 <= 0.0)
6824
+ continue;
6825
+ const dist = Math.sqrt(dist2);
6826
+ if (dist >= rad)
6827
+ continue;
6828
+ // Viscosity kernel: (max(0, r^2 - d^2)^3) / (pi/4 * r^8)
6829
+ const val = Math.max(0.0, r2 - dist2);
6830
+ const kVisc = (val * val * val) / ((Math.PI / 4.0) * r8);
6831
+ viscosityForceX +=
6832
+ (other.velocity.x - particle.velocity.x) * kVisc;
6833
+ viscosityForceY +=
6834
+ (other.velocity.y - particle.velocity.y) * kVisc;
6835
+ }
6836
+ viscosityForceX *= visc;
6837
+ viscosityForceY *= visc;
6838
+ }
6839
+ // Convert to acceleration-like effect: a = F / density
6840
+ let forceX = (pressureForceX / myDensity) * 1000000.0;
6841
+ let forceY = (pressureForceY / myDensity) * 1000000.0;
6842
+ if (visc !== 0.0) {
6843
+ forceX += (viscosityForceX * 1000.0) / myDensity;
6844
+ forceY += (viscosityForceY * 1000.0) / myDensity;
6845
+ }
6846
+ // Clamp force magnitude to avoid instabilities
6847
+ const f2 = forceX * forceX + forceY * forceY;
6848
+ if (f2 > maxAccel * maxAccel) {
6849
+ const fLen = Math.sqrt(f2);
6850
+ forceX = forceX * (maxAccel / fLen);
6851
+ forceY = forceY * (maxAccel / fLen);
6852
+ }
6853
+ // Apply directly to velocity (matching WebGPU behavior)
6854
+ particle.velocity.x += forceX;
6855
+ particle.velocity.y += forceY;
6651
6856
  }
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);
6857
+ else {
6858
+ // PICFLIP
6859
+ // Scale UI-friendly values to PIC/FLIP internal tuning:
6860
+ // - influenceRadius: /2
6861
+ // - targetDensity: *3
6862
+ // - pressureMultiplier: *30
6863
+ const flipRatio = this.readValue("flipRatio");
6864
+ const targetDensity = this.readValue("targetDensity") * 3.0;
6865
+ const rad = this.readValue("influenceRadius") * 0.5;
6866
+ const pressureScale = this.readValue("pressureMultiplier") * 30.0;
6867
+ // Get stored previous velocity
6868
+ const prevVelX = getState("prevVelX");
6869
+ const prevVelY = getState("prevVelY");
6870
+ let newVelX = particle.velocity.x;
6871
+ let newVelY = particle.velocity.y;
6872
+ // Local pressure approximation using neighbor density
6873
+ let density = 0.0;
6874
+ let avgVelX = 0.0;
6875
+ let avgVelY = 0.0;
6876
+ let count = 0.0;
6877
+ const neighbors = getNeighbors(particle.position, rad);
6878
+ for (const other of neighbors) {
6879
+ if (other.id === particle.id)
6880
+ continue;
6881
+ if (other.mass <= 0)
6882
+ continue;
6883
+ const dx = particle.position.x - other.position.x;
6884
+ const dy = particle.position.y - other.position.y;
6885
+ const dist2 = dx * dx + dy * dy;
6886
+ if (dist2 <= 0 || dist2 > rad * rad)
6887
+ continue;
6888
+ const dist = Math.sqrt(dist2);
6889
+ const weight = 1.0 - dist / rad;
6890
+ density += weight;
6891
+ avgVelX += other.velocity.x * weight;
6892
+ avgVelY += other.velocity.y * weight;
6893
+ count += weight;
6894
+ }
6895
+ if (count > 0) {
6896
+ avgVelX /= count;
6897
+ avgVelY /= count;
6898
+ // PIC/FLIP blend
6899
+ const picVelX = avgVelX;
6900
+ const picVelY = avgVelY;
6901
+ const flipVelX = newVelX + (avgVelX - prevVelX);
6902
+ const flipVelY = newVelY + (avgVelY - prevVelY);
6903
+ newVelX = picVelX * (1 - flipRatio) + flipVelX * flipRatio;
6904
+ newVelY = picVelY * (1 - flipRatio) + flipVelY * flipRatio;
6905
+ // Simple pressure force based on local density
6906
+ const maxPressureFactor = Math.abs(pressureScale) * 10.0;
6907
+ const rawPressureFactor = (density - targetDensity) * pressureScale;
6908
+ const pressureFactor = Math.max(-maxPressureFactor, Math.min(maxPressureFactor, rawPressureFactor));
6909
+ // Apply pressure gradient
6910
+ let gradX = 0.0;
6911
+ let gradY = 0.0;
6912
+ for (const other of neighbors) {
6913
+ if (other.id === particle.id)
6914
+ continue;
6915
+ if (other.mass <= 0)
6916
+ continue;
6917
+ const dx = particle.position.x - other.position.x;
6918
+ const dy = particle.position.y - other.position.y;
6919
+ const dist2 = dx * dx + dy * dy;
6920
+ if (dist2 <= 1.0 || dist2 > rad * rad)
6921
+ continue;
6922
+ const dist = Math.sqrt(dist2);
6923
+ const dirX = dx / dist;
6924
+ const dirY = dy / dist;
6925
+ const weight = 1.0 - dist / rad;
6926
+ gradX += dirX * weight * pressureFactor;
6927
+ gradY += dirY * weight * pressureFactor;
6928
+ }
6929
+ // NOTE: no max-acceleration clamp for PIC/FLIP (it tends to need the max anyway)
6930
+ newVelX += gradX * dt;
6931
+ newVelY += gradY * dt;
6932
+ }
6933
+ particle.velocity.x = newVelX;
6934
+ particle.velocity.y = newVelY;
6658
6935
  }
6659
- // Apply directly to velocity (matching WebGPU behavior)
6660
- particle.velocity.x += forceX;
6661
- particle.velocity.y += forceY;
6662
6936
  },
6663
6937
  };
6664
6938
  }
@@ -9708,5 +9982,5 @@ class Particles extends Module {
9708
9982
  }
9709
9983
  }
9710
9984
 
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 };
9985
+ 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
9986
  //# sourceMappingURL=index.js.map