@adatechnology/game-fluids 0.1.8 → 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
@@ -11,7 +11,7 @@ var liquids_default = [
11
11
  stainColor: 7309206,
12
12
  viscosity: 1,
13
13
  density: 1e3,
14
- dryTime: 600,
14
+ dryTime: 180,
15
15
  gloss: 0.9,
16
16
  drink: {
17
17
  thirst: 100,
@@ -189,9 +189,9 @@ var liquids_default = [
189
189
  stainColor: 7232298,
190
190
  viscosity: 0.6,
191
191
  density: 740,
192
- dryTime: 120,
192
+ dryTime: 60,
193
193
  gloss: 0.95,
194
- leavesMark: true,
194
+ leavesMark: false,
195
195
  flammable: true,
196
196
  drink: {
197
197
  thirst: 0,
@@ -272,10 +272,10 @@ var liquids_default = [
272
272
  id: "blood",
273
273
  color: 7997964,
274
274
  opacity: 0.92,
275
- stainColor: 4850950,
275
+ stainColor: 3874068,
276
276
  viscosity: 4,
277
277
  density: 1060,
278
- dryTime: 1200,
278
+ dryTime: 600,
279
279
  gloss: 0.75,
280
280
  leavesMark: true,
281
281
  drink: {
@@ -288,6 +288,46 @@ var liquids_default = [
288
288
  "organic"
289
289
  ]
290
290
  },
291
+ {
292
+ id: "old_blood",
293
+ color: 7085078,
294
+ opacity: 0.92,
295
+ stainColor: 2758158,
296
+ viscosity: 4,
297
+ density: 1060,
298
+ dryTime: 360,
299
+ gloss: 0.7,
300
+ leavesMark: true,
301
+ drink: {
302
+ thirst: 10,
303
+ effects: [
304
+ "disgust"
305
+ ]
306
+ },
307
+ tags: [
308
+ "organic"
309
+ ]
310
+ },
311
+ {
312
+ id: "bile",
313
+ color: 9083434,
314
+ opacity: 0.9,
315
+ stainColor: 4080160,
316
+ viscosity: 6,
317
+ density: 1020,
318
+ dryTime: 900,
319
+ gloss: 0.75,
320
+ leavesMark: true,
321
+ drink: {
322
+ thirst: 0,
323
+ effects: [
324
+ "disgust"
325
+ ]
326
+ },
327
+ tags: [
328
+ "organic"
329
+ ]
330
+ },
291
331
  {
292
332
  id: "sap",
293
333
  color: 13146940,
@@ -530,6 +570,8 @@ var locales = {
530
570
  motor_oil: "motor oil",
531
571
  cooking_oil: "cooking oil",
532
572
  blood: "blood",
573
+ old_blood: "old blood",
574
+ bile: "bile",
533
575
  sap: "sap",
534
576
  acid: "acid",
535
577
  paint: "paint",
@@ -570,6 +612,8 @@ var locales = {
570
612
  motor_oil: "\xF3leo de motor",
571
613
  cooking_oil: "\xF3leo de cozinha",
572
614
  blood: "sangue",
615
+ old_blood: "sangue antigo",
616
+ bile: "bile",
573
617
  sap: "seiva",
574
618
  acid: "\xE1cido",
575
619
  paint: "tinta",
@@ -1081,7 +1125,7 @@ function createFluids(options = {}) {
1081
1125
  }
1082
1126
  if (victim) stains.delete(victim.id);
1083
1127
  };
1084
- const deposit = (liquid, hit, liters, velocity) => {
1128
+ const deposit = (liquid, hit, liters, velocity, tint) => {
1085
1129
  const def = liquids.get(liquid);
1086
1130
  if (!def || liters <= 0) return;
1087
1131
  let s = stainAt(hit.surface, liquid, hit.point);
@@ -1089,6 +1133,7 @@ function createFluids(options = {}) {
1089
1133
  const basis2 = { normal: s.normal, t1: s.tangent, t2: cross(s.normal, s.tangent) };
1090
1134
  nudgeCenter(s, hit.point, clamp(liters / (s.liters + liters) * 0.5, 0, 0.5), basis2);
1091
1135
  addInflow(s, liters, velocity);
1136
+ if (tint) s.tint = { ...tint };
1092
1137
  return;
1093
1138
  }
1094
1139
  while (stains.size >= maxStains) evictStain();
@@ -1096,6 +1141,7 @@ function createFluids(options = {}) {
1096
1141
  const basis = hit.surface === GROUND_ID ? makeBasis(hit.normal) : surface;
1097
1142
  s = createStain(`stain_${++counters.stain}`, hit.surface, def, basis, hit.point, liters, rng);
1098
1143
  if (velocity) addInflow(s, 0, velocity);
1144
+ if (tint) s.tint = { ...tint };
1099
1145
  s.sinceInflow = 0;
1100
1146
  stains.set(s.id, s);
1101
1147
  emit({ type: "stain", stain: s.id, surface: hit.surface, liquid, point: copy3(hit.point) });
@@ -1288,7 +1334,8 @@ function createFluids(options = {}) {
1288
1334
  tangent: copy3(s.tangent),
1289
1335
  flow: s.flow ? copy3(s.flow) : void 0,
1290
1336
  mark: s.mark,
1291
- age: s.age
1337
+ age: s.age,
1338
+ tint: s.tint ? { ...s.tint } : void 0
1292
1339
  });
1293
1340
  const world = {
1294
1341
  liquids,
@@ -1473,14 +1520,14 @@ function createFluids(options = {}) {
1473
1520
  surfaces.delete(id);
1474
1521
  for (const s of [...stains.values()]) if (s.surface === id) stains.delete(s.id);
1475
1522
  },
1476
- splash(liquid, point, liters, velocity) {
1523
+ splash(liquid, point, liters, velocity, options2) {
1477
1524
  if (!liquids.has(liquid) || !(liters > 0)) return;
1478
1525
  const v = velocity ?? [0, -0.1, 0];
1479
1526
  const origin = add(point, [0, 0.02, 0]);
1480
1527
  const hit = trace(landingSurfaces(), groundAt, origin, v, gravity);
1481
1528
  if (!hit) return;
1482
1529
  emit({ type: "splash", liquid, point: copy3(hit.point), intensity: clamp(0.3 + liters * 2, 0, 1) });
1483
- deposit(liquid, hit, liters, length(v) > 0.3 ? hit.velocity : null);
1530
+ deposit(liquid, hit, liters, length(v) > 0.3 ? hit.velocity : null, options2?.tint);
1484
1531
  },
1485
1532
  clearStains(point, radius) {
1486
1533
  for (const s of [...stains.values()]) {
@@ -2168,17 +2215,996 @@ function createItemUse(world, containerId, def, getHandTransform) {
2168
2215
  };
2169
2216
  return player;
2170
2217
  }
2218
+
2219
+ // src/film/deposit.ts
2220
+ var VISIBLE_THICKNESS = 0.035;
2221
+ function depositCell(context, rowIn, columnIn, amount) {
2222
+ const { field } = context;
2223
+ const row = Math.round(rowIn);
2224
+ const column = Math.round(columnIn);
2225
+ if (row < 0 || column < 0 || row >= field.height || column >= field.width || !(amount > 0)) return;
2226
+ const index = row * field.width + column;
2227
+ field.thickness[index] += amount;
2228
+ if (amount > 0.02) field.dryness[index] *= 0.15;
2229
+ field.dampness[index] = 1;
2230
+ }
2231
+ function wetCellCount(field) {
2232
+ let cells = 0;
2233
+ for (let index = 0; index < field.thickness.length; index++) if (field.thickness[index] > VISIBLE_THICKNESS) cells++;
2234
+ return cells;
2235
+ }
2236
+ function depositBlob(context, centerRow, centerColumn, along, across, directionColumn, directionRow, amount, roughness) {
2237
+ const random = context.random;
2238
+ const phaseA = random() * 6.28;
2239
+ const phaseB = random() * 6.28;
2240
+ const phaseC = random() * 6.28;
2241
+ const phaseD = random() * 6.28;
2242
+ const reach = Math.ceil(Math.max(along, across) * (1 + roughness) + 1.5);
2243
+ for (let row = Math.floor(centerRow - reach); row <= centerRow + reach; row++) {
2244
+ for (let column = Math.floor(centerColumn - reach); column <= centerColumn + reach; column++) {
2245
+ const dx = column + 0.5 - centerColumn;
2246
+ const dy = row + 0.5 - centerRow;
2247
+ const alongDistance = dx * directionColumn + dy * directionRow;
2248
+ const acrossDistance = -dx * directionRow + dy * directionColumn;
2249
+ const angle = Math.atan2(acrossDistance / across, alongDistance / along);
2250
+ const edge = 1 + roughness * (0.45 * Math.sin(3 * angle + phaseA) + 0.3 * Math.sin(5 * angle + phaseB) + 0.18 * Math.sin(8 * angle + phaseC) + 0.12 * Math.sin(13 * angle + phaseD));
2251
+ const squared = (alongDistance / along * (alongDistance / along) + acrossDistance / across * (acrossDistance / across)) / (edge * edge);
2252
+ if (squared < 1) depositCell(context, row, column, amount * Math.sqrt(1 - squared));
2253
+ }
2254
+ }
2255
+ }
2256
+ function depositStroke(context, fromRow, fromColumn, toRow, toColumn, startWidth, endWidth, amount) {
2257
+ const { field } = context;
2258
+ const length2 = Math.hypot(toRow - fromRow, toColumn - fromColumn);
2259
+ const samples = Math.max(2, Math.ceil(length2 * 2.5));
2260
+ for (let sample = 0; sample <= samples; sample++) {
2261
+ const progress = sample / samples;
2262
+ const row = fromRow + (toRow - fromRow) * progress;
2263
+ const column = fromColumn + (toColumn - fromColumn) * progress;
2264
+ const width = startWidth + (endWidth - startWidth) * progress;
2265
+ const reach = Math.ceil(width + 1);
2266
+ for (let cellRow = Math.floor(row - reach); cellRow <= row + reach; cellRow++) {
2267
+ for (let cellColumn = Math.floor(column - reach); cellColumn <= column + reach; cellColumn++) {
2268
+ if (cellRow < 0 || cellColumn < 0 || cellRow >= field.height || cellColumn >= field.width) continue;
2269
+ const distance2 = Math.hypot(cellRow + 0.5 - row, cellColumn + 0.5 - column);
2270
+ if (distance2 >= width) continue;
2271
+ const index = cellRow * field.width + cellColumn;
2272
+ const target = amount * (1 - distance2 / width) * 0.5;
2273
+ if (field.thickness[index] < target) depositCell(context, cellRow, cellColumn, target - field.thickness[index]);
2274
+ }
2275
+ }
2276
+ }
2277
+ }
2278
+ function depositSplat(context, row, column, size, directionColumn, directionRow, sinAngleIn, amount) {
2279
+ const random = context.random;
2280
+ const spikes = context.profile().spikes;
2281
+ const sinAngle = Math.max(0.17, Math.min(1, sinAngleIn));
2282
+ const stretch = Math.min(5.5, 1 / sinAngle);
2283
+ const along = size * Math.pow(stretch, 0.75);
2284
+ const across = size / Math.pow(stretch, 0.25);
2285
+ const frontRow = row + directionRow * along * 0.25;
2286
+ const frontColumn = column + directionColumn * along * 0.25;
2287
+ depositBlob(context, frontRow, frontColumn, along, across, directionColumn, directionRow, amount, 0.1 + 0.12 * (1 - sinAngle) + (size < 1.4 ? 0 : 0.06));
2288
+ const sideColumn = -directionRow;
2289
+ const sideRow = directionColumn;
2290
+ const spikeCount = Math.round(Math.min(18, (size < 1.2 ? 1 : 4) + size * 1.6 + 5 * (1 - sinAngle)) * spikes);
2291
+ for (let spike = 0; spike < spikeCount; spike++) {
2292
+ const spread = Math.PI * (0.25 + 0.75 * sinAngle * sinAngle);
2293
+ let theta = (random() * 2 - 1) * spread;
2294
+ if (sinAngle > 0.9) theta = random() * 6.283;
2295
+ const cosine = Math.cos(theta);
2296
+ const sine = Math.sin(theta);
2297
+ const dx = cosine * directionColumn + sine * sideColumn;
2298
+ const dy = cosine * directionRow + sine * sideRow;
2299
+ const length2 = size * (0.25 + random() * 0.55) * (1 + 2.6 * (1 - sinAngle) * Math.max(0, cosine)) * (sinAngle > 0.9 ? 0.6 : 1) * (0.5 + 0.5 * spikes);
2300
+ const startRow = frontRow + (along * cosine * directionRow + across * sine * sideRow) * 0.75;
2301
+ const startColumn = frontColumn + (along * cosine * directionColumn + across * sine * sideColumn) * 0.75;
2302
+ const endRow = startRow + dy * length2;
2303
+ const endColumn = startColumn + dx * length2;
2304
+ depositStroke(context, startRow, startColumn, endRow, endColumn, Math.max(0.55, size * 0.22), 0.42, amount * 0.75);
2305
+ if (random() < 0.5) {
2306
+ const gap = 1 + random() * 2.2;
2307
+ depositBlob(context, endRow + dy * gap, endColumn + dx * gap, 0.55 + random() * 0.7, 0.5 + random() * 0.4, dx, dy, amount * 0.55, 0.08);
2308
+ }
2309
+ }
2310
+ const satelliteCount = Math.round((1 + size * 0.9) * (1.4 - sinAngle * 0.6) * (0.5 + 0.5 * spikes));
2311
+ for (let satellite = 0; satellite < satelliteCount; satellite++) {
2312
+ const cone = sinAngle > 0.9 ? Math.PI : 0.18 + 0.6 * sinAngle;
2313
+ const theta = (random() * 2 - 1) * cone;
2314
+ const cosine = Math.cos(theta);
2315
+ const sine = Math.sin(theta);
2316
+ const dx = cosine * directionColumn + sine * sideColumn;
2317
+ const dy = cosine * directionRow + sine * sideRow;
2318
+ const distance2 = along + size * (0.8 + random() * 3.2);
2319
+ const radius = 0.45 + random() * Math.min(1.2, size * 0.22);
2320
+ const satelliteRow = frontRow + dy * distance2;
2321
+ const satelliteColumn = frontColumn + dx * distance2;
2322
+ depositBlob(context, satelliteRow, satelliteColumn, radius * Math.sqrt(stretch), radius, dx, dy, amount * 0.6, 0.08);
2323
+ if (sinAngle < 0.8) depositStroke(context, satelliteRow + dy * radius * stretch * 0.7, satelliteColumn + dx * radius * stretch * 0.7, satelliteRow + dy * radius * stretch * 1.8, satelliteColumn + dx * radius * stretch * 1.8, 0.5, 0.3, amount * 0.35);
2324
+ }
2325
+ }
2326
+
2327
+ // src/film/drops.ts
2328
+ var GRAVITY2 = 9.8;
2329
+ var AIR_DRAG = 0.25;
2330
+ var MIN_SIZE_CELLS = 0.6;
2331
+ function createDropPool(layout, context) {
2332
+ const capacity = layout.rules.dropCapacity;
2333
+ let cursor = 0;
2334
+ const state = {
2335
+ capacity,
2336
+ get launched() {
2337
+ return cursor;
2338
+ },
2339
+ active: new Uint8Array(capacity),
2340
+ position: new Float64Array(capacity * 3),
2341
+ velocity: new Float64Array(capacity * 3),
2342
+ radius: new Float64Array(capacity)
2343
+ };
2344
+ const { active, position, velocity, radius } = state;
2345
+ const cellMillimetres = layout.cellSize * 1e3;
2346
+ const previous = [0, 0, 0];
2347
+ const point = [0, 0, 0];
2348
+ const speed = [0, 0, 0];
2349
+ function land(region, dropRadius) {
2350
+ const profile = context.profile();
2351
+ const impact = layout.impact(region, point, speed);
2352
+ const [alongColumn, alongRow] = impact.along;
2353
+ const travel = Math.hypot(alongColumn, alongRow) || 1e-6;
2354
+ const sinAngle = Math.abs(impact.normalSpeed) / Math.hypot(travel, impact.normalSpeed);
2355
+ const volume = 4 / 3 * Math.PI * dropRadius * dropRadius * dropRadius / (cellMillimetres * cellMillimetres) * (dropRadius > 3 ? 7 : 4) * profile.volume;
2356
+ const totalSpeed = Math.sqrt(speed[0] * speed[0] + speed[1] * speed[1] + speed[2] * speed[2]);
2357
+ const size = Math.max(MIN_SIZE_CELLS, dropRadius * 2 / cellMillimetres * (1 + 0.2 * totalSpeed));
2358
+ const amount = Math.min(profile.maxAmount, Math.max(0.25, volume / (Math.PI * size * size * 1.6 / (sinAngle < 0.3 ? 0.6 : 1))));
2359
+ if (profile.landsAsBlob) {
2360
+ depositBlob(context, impact.row, impact.column, size * 1.3, size * 1.1, alongColumn / travel, alongRow / travel, amount, 0.2);
2361
+ return;
2362
+ }
2363
+ depositSplat(context, impact.row, impact.column, size, alongColumn / travel, alongRow / travel, sinAngle, amount);
2364
+ }
2365
+ return {
2366
+ state,
2367
+ launch(origin, direction, dropRadius) {
2368
+ const slot = cursor++ % capacity;
2369
+ const offset = slot * 3;
2370
+ active[slot] = 1;
2371
+ position[offset] = origin[0];
2372
+ position[offset + 1] = origin[1];
2373
+ position[offset + 2] = origin[2];
2374
+ velocity[offset] = direction[0];
2375
+ velocity[offset + 1] = direction[1];
2376
+ velocity[offset + 2] = direction[2];
2377
+ radius[slot] = dropRadius;
2378
+ },
2379
+ update(dt) {
2380
+ for (let slot = 0; slot < capacity; slot++) {
2381
+ if (!active[slot]) continue;
2382
+ const offset = slot * 3;
2383
+ previous[0] = position[offset];
2384
+ previous[1] = position[offset + 1];
2385
+ previous[2] = position[offset + 2];
2386
+ velocity[offset + 1] -= GRAVITY2 * dt;
2387
+ const drag = 1 - dt * AIR_DRAG;
2388
+ velocity[offset] *= drag;
2389
+ velocity[offset + 1] *= drag;
2390
+ velocity[offset + 2] *= drag;
2391
+ point[0] = position[offset] + velocity[offset] * dt;
2392
+ point[1] = position[offset + 1] + velocity[offset + 1] * dt;
2393
+ point[2] = position[offset + 2] + velocity[offset + 2] * dt;
2394
+ const region = layout.collide(previous, point);
2395
+ position[offset] = point[0];
2396
+ position[offset + 1] = point[1];
2397
+ position[offset + 2] = point[2];
2398
+ if (region >= 0) {
2399
+ if (layout.contains(point)) {
2400
+ speed[0] = velocity[offset];
2401
+ speed[1] = velocity[offset + 1];
2402
+ speed[2] = velocity[offset + 2];
2403
+ land(region, radius[slot]);
2404
+ }
2405
+ active[slot] = 0;
2406
+ continue;
2407
+ }
2408
+ if (layout.escaped(point)) active[slot] = 0;
2409
+ }
2410
+ },
2411
+ clear() {
2412
+ active.fill(0);
2413
+ }
2414
+ };
2415
+ }
2416
+
2417
+ // src/film/simulate.ts
2418
+ var RAIN_SPREAD = 0.5;
2419
+ var BOUNDS_MARGIN = 8;
2420
+ function createCpuSimulator(field, layout, profileOf, options) {
2421
+ const { width, height, thickness, dryness, dampness } = field;
2422
+ const { bed, pin: pinning, vertical } = layout;
2423
+ const delta = new Float32Array(width * height);
2424
+ const bounds = { firstRow: 0, lastRow: -1, firstColumn: 0, lastColumn: -1 };
2425
+ let accumulated = 0;
2426
+ function refreshBounds() {
2427
+ let firstRow = height;
2428
+ let lastRow = -1;
2429
+ let firstColumn = width;
2430
+ let lastColumn = -1;
2431
+ for (let row = 0; row < height; row++) {
2432
+ const offset = row * width;
2433
+ let isWet = false;
2434
+ for (let column = 0; column < width; column++) {
2435
+ if (thickness[offset + column] > 0) {
2436
+ isWet = true;
2437
+ if (column < firstColumn) firstColumn = column;
2438
+ if (column > lastColumn) lastColumn = column;
2439
+ }
2440
+ }
2441
+ if (isWet) {
2442
+ if (row < firstRow) firstRow = row;
2443
+ lastRow = row;
2444
+ }
2445
+ }
2446
+ if (lastRow < 0) {
2447
+ bounds.firstRow = 0;
2448
+ bounds.lastRow = -1;
2449
+ bounds.firstColumn = 0;
2450
+ bounds.lastColumn = -1;
2451
+ return;
2452
+ }
2453
+ bounds.firstRow = Math.max(0, firstRow - BOUNDS_MARGIN);
2454
+ bounds.lastRow = Math.min(height - 1, lastRow + BOUNDS_MARGIN);
2455
+ bounds.firstColumn = Math.max(0, firstColumn - BOUNDS_MARGIN);
2456
+ bounds.lastColumn = Math.min(width - 1, lastColumn + BOUNDS_MARGIN);
2457
+ }
2458
+ const mobility = (index) => {
2459
+ const dry = dryness[index];
2460
+ return dry < 0.25 ? 1 : dry > 0.6 ? 0 : 1 - (dry - 0.25) / 0.35;
2461
+ };
2462
+ function flowStep() {
2463
+ const { firstRow, lastRow, firstColumn, lastColumn } = bounds;
2464
+ if (lastRow < 0) return;
2465
+ const profile = profileOf();
2466
+ const flow = profile.flow * (1 + RAIN_SPREAD * options.weather().rain);
2467
+ const referenceSquared = profile.referenceThickness * profile.referenceThickness;
2468
+ const slopeCap = profile.slopeCap;
2469
+ const pinLevel = profile.pin;
2470
+ const front = profile.front;
2471
+ for (let row = firstRow; row <= lastRow; row++) {
2472
+ const offset = row * width;
2473
+ for (let column = firstColumn; column <= lastColumn; column++) delta[offset + column] = 0;
2474
+ }
2475
+ for (let row = firstRow; row <= lastRow; row++) {
2476
+ const offset = row * width;
2477
+ for (let column = firstColumn; column <= lastColumn; column++) {
2478
+ const index = offset + column;
2479
+ const own = thickness[index];
2480
+ for (let edge = 0; edge < 2; edge++) {
2481
+ let neighbour;
2482
+ if (edge === 0) {
2483
+ if (column >= lastColumn) continue;
2484
+ neighbour = index + 1;
2485
+ } else {
2486
+ if (row >= lastRow) continue;
2487
+ neighbour = index + width;
2488
+ }
2489
+ const other = thickness[neighbour];
2490
+ if (own < 5e-3 && other < 5e-3) continue;
2491
+ let bedDrop = bed[index] - bed[neighbour];
2492
+ if (bedDrop > slopeCap) bedDrop = slopeCap;
2493
+ else if (bedDrop < -slopeCap) bedDrop = -slopeCap;
2494
+ let head = bedDrop + own - other;
2495
+ let source = index;
2496
+ let destination = neighbour;
2497
+ if (head < 0) {
2498
+ head = -head;
2499
+ source = neighbour;
2500
+ destination = index;
2501
+ }
2502
+ const sourceThickness = thickness[source];
2503
+ const verticalFactor = vertical[source];
2504
+ const pinThreshold = pinLevel * pinning[source] * (verticalFactor < 1 ? 0.5 : 1);
2505
+ if (sourceThickness <= pinThreshold) continue;
2506
+ if (thickness[destination] < 0.03 && sourceThickness < front * pinning[source] * verticalFactor) continue;
2507
+ const mobile = mobility(source);
2508
+ if (mobile <= 0) continue;
2509
+ let moved = flow * mobile * (sourceThickness * sourceThickness / (sourceThickness * sourceThickness + referenceSquared)) * head * (verticalFactor < 1 ? edge === 0 ? 0.22 : 1 : 4);
2510
+ const limit = (sourceThickness - pinThreshold) * 0.24;
2511
+ if (moved > limit) moved = limit;
2512
+ delta[source] -= moved;
2513
+ delta[destination] += moved;
2514
+ }
2515
+ }
2516
+ }
2517
+ for (let row = firstRow; row <= lastRow; row++) {
2518
+ const offset = row * width;
2519
+ for (let column = firstColumn; column <= lastColumn; column++) {
2520
+ const index = offset + column;
2521
+ let next = thickness[index] + delta[index];
2522
+ if (next < 1e-4) next = 0;
2523
+ else dampness[index] = 1;
2524
+ thickness[index] = next;
2525
+ }
2526
+ }
2527
+ }
2528
+ function age(dryingSeconds) {
2529
+ const { firstRow, lastRow, firstColumn, lastColumn } = bounds;
2530
+ if (lastRow < 0) return;
2531
+ const profile = profileOf();
2532
+ const rainScale = 1 - options.weather().rain;
2533
+ const seconds = dryingSeconds * rainScale;
2534
+ for (let row = firstRow; row <= lastRow; row++) {
2535
+ const offset = row * width;
2536
+ for (let column = firstColumn; column <= lastColumn; column++) {
2537
+ const index = offset + column;
2538
+ const own = thickness[index];
2539
+ if (own > 0) {
2540
+ const thin = 1 - Math.min(1, own / 1.6);
2541
+ if (profile.dryTime > 0 && dryness[index] < 1) dryness[index] = Math.min(1, dryness[index] + seconds / profile.dryTime * (0.45 + 1.7 * thin));
2542
+ if (profile.evaporation > 0) {
2543
+ const next = own - profile.evaporation * seconds * (0.5 + 1.5 * thin);
2544
+ thickness[index] = next > 4e-3 ? next : 0;
2545
+ }
2546
+ } else if (dampness[index] > 0) {
2547
+ dampness[index] = Math.max(0, dampness[index] - seconds / (profile.evaporation > 0 ? 25 : 1e9));
2548
+ }
2549
+ }
2550
+ }
2551
+ }
2552
+ return {
2553
+ bounds,
2554
+ refreshBounds,
2555
+ advance({ dt, dryingSeconds }) {
2556
+ accumulated += dt;
2557
+ let steps = 0;
2558
+ refreshBounds();
2559
+ while (accumulated >= 1 / options.stepRate && steps < options.maxSteps) {
2560
+ flowStep();
2561
+ accumulated -= 1 / options.stepRate;
2562
+ steps++;
2563
+ }
2564
+ if (steps === options.maxSteps) accumulated = 0;
2565
+ age(dryingSeconds);
2566
+ return steps;
2567
+ },
2568
+ resetClock() {
2569
+ accumulated = 0;
2570
+ }
2571
+ };
2572
+ }
2573
+
2574
+ // src/film/sources.ts
2575
+ var range = (random, from, to) => from + random() * (to - from);
2576
+ function updateDrip(drip, bead, layout, random, launch, dt) {
2577
+ if (!drip) {
2578
+ bead.visible = false;
2579
+ return;
2580
+ }
2581
+ drip.elapsed += dt;
2582
+ const swell = Math.min(1, drip.elapsed / drip.every);
2583
+ const radius = drip.radius * (0.35 + 0.65 * swell);
2584
+ const isWorkshop = layout.rules.dripStyle === "workshop";
2585
+ bead.visible = true;
2586
+ bead.swell = swell;
2587
+ bead.radius = radius;
2588
+ bead.position[0] = drip.position[0];
2589
+ bead.position[1] = drip.position[1] + (isWorkshop ? -radius * 12e-4 * (1 + swell * 0.7) : -radius * 12e-4);
2590
+ bead.position[2] = drip.position[2];
2591
+ if (drip.elapsed < drip.every) return;
2592
+ const origin = [bead.position[0], bead.position[1] + -radius * 1e-3, bead.position[2]];
2593
+ if (isWorkshop) {
2594
+ const sideX2 = range(random, -0.025, 0.025);
2595
+ const sideZ2 = range(random, -0.025, 0.025);
2596
+ launch(origin, [sideX2, -0.2, sideZ2], drip.radius * range(random, 0.85, 1.15));
2597
+ drip.elapsed = 0;
2598
+ drip.every = drip.base * range(random, 0.75, 1.25);
2599
+ return;
2600
+ }
2601
+ const sideX = (random() - 0.5) * 0.05;
2602
+ const sideZ = (random() - 0.5) * 0.05;
2603
+ launch(origin, [sideX, -0.2, sideZ], drip.radius * (0.85 + random() * 0.3));
2604
+ drip.elapsed = 0;
2605
+ drip.every = drip.base * (0.75 + random() * 0.5);
2606
+ }
2607
+ function updateSources(sources, context, dt) {
2608
+ const random = context.random;
2609
+ for (const source of sources) {
2610
+ source.elapsed += dt;
2611
+ if (source.elapsed < 0 || source.elapsed > source.duration) continue;
2612
+ const pulse = 0.6 + 0.4 * Math.max(0, Math.sin(source.elapsed * 5.2));
2613
+ const amount = source.rate * dt * pulse;
2614
+ for (let part = 0; part < 3; part++) {
2615
+ const angle = random() * 6.28;
2616
+ const distance2 = random() * source.radius;
2617
+ depositCell(context, source.row + Math.sin(angle) * distance2, source.column + Math.cos(angle) * distance2, amount / 3);
2618
+ }
2619
+ }
2620
+ }
2621
+ function updateDrags(drags, context, layout, launch, dt) {
2622
+ const random = context.random;
2623
+ for (const drag of drags) {
2624
+ if (drag.done) continue;
2625
+ drag.elapsed += dt;
2626
+ const progress = Math.min(1, drag.elapsed / drag.duration);
2627
+ const here = drag.path(progress);
2628
+ const ahead = drag.path(Math.min(1, progress + 0.01));
2629
+ let directionColumn = ahead.column - here.column;
2630
+ let directionRow = ahead.row - here.row;
2631
+ const length2 = Math.hypot(directionColumn, directionRow) || 1;
2632
+ directionColumn /= length2;
2633
+ directionRow /= length2;
2634
+ const sideColumn = -directionRow;
2635
+ const sideRow = directionColumn;
2636
+ const load = Math.pow(1 - progress, 1.3);
2637
+ for (let offset = -drag.width; offset <= drag.width; offset += 0.5) {
2638
+ const across = offset / drag.width;
2639
+ const stripe = drag.stripes[Math.round((offset + drag.width) * 2)];
2640
+ const edge = 1 - across * across;
2641
+ const amount = drag.amount * load * (0.25 + 0.75 * stripe) * edge;
2642
+ if (load < 0.45 && random() > load * 2.2) continue;
2643
+ depositCell(context, Math.round(here.row + sideRow * offset), Math.round(here.column + sideColumn * offset), amount * dt * 30);
2644
+ }
2645
+ if (drag.hand && random() < dt * 14 * load + 0.02) {
2646
+ const world = layout.cellToWorld(here.row, here.column);
2647
+ const origin = [world[0] + (random() - 0.5) * 0.06, drag.hand.height + random() * 0.3, world[2] + (random() - 0.5) * 0.06];
2648
+ const velocity = [directionColumn * drag.hand.speed, 0 * drag.hand.speed, directionRow * drag.hand.speed];
2649
+ velocity[0] += (random() - 0.5) * 0.3;
2650
+ velocity[1] += 0.2;
2651
+ velocity[2] += (random() - 0.5) * 0.3;
2652
+ launch(origin, velocity, 2 + random() * 2.2);
2653
+ }
2654
+ if (progress >= 1) drag.done = true;
2655
+ }
2656
+ }
2657
+
2658
+ // src/film/film.ts
2659
+ var DEFAULT_STEP_RATE = 180;
2660
+ var DEFAULT_MAX_STEPS = 6;
2661
+ function createFilm(options) {
2662
+ const { layout, random } = options;
2663
+ const cellCount = layout.width * layout.height;
2664
+ const field = {
2665
+ width: layout.width,
2666
+ height: layout.height,
2667
+ thickness: new Float32Array(cellCount),
2668
+ dryness: new Float32Array(cellCount),
2669
+ dampness: new Float32Array(cellCount)
2670
+ };
2671
+ let weatherState = { rain: 0 };
2672
+ let currentProfile = options.profile;
2673
+ const context = { field, random, profile: () => currentProfile };
2674
+ const drops = createDropPool(layout, context);
2675
+ const createSimulator = options.simulator ?? createCpuSimulator;
2676
+ const simulator = createSimulator(field, layout, () => currentProfile, {
2677
+ stepRate: options.stepRate ?? DEFAULT_STEP_RATE,
2678
+ maxSteps: options.maxSteps ?? DEFAULT_MAX_STEPS,
2679
+ weather: () => weatherState
2680
+ });
2681
+ const film = {
2682
+ layout,
2683
+ field,
2684
+ drops: drops.state,
2685
+ bead: { visible: false, position: [0, 0, 0], radius: 0, swell: 0 },
2686
+ simulator,
2687
+ get profile() {
2688
+ return currentProfile;
2689
+ },
2690
+ set profile(profile) {
2691
+ currentProfile = profile;
2692
+ },
2693
+ drip: void 0,
2694
+ sources: [],
2695
+ drags: [],
2696
+ launch: (position, velocity, radius) => drops.launch(position, velocity, radius),
2697
+ splat: (row, column, size, directionColumn, directionRow, sinAngle, amount) => depositSplat(context, row, column, size, directionColumn, directionRow, sinAngle, amount),
2698
+ blob: (row, column, along, across, directionColumn, directionRow, amount, roughness) => depositBlob(context, row, column, along, across, directionColumn, directionRow, amount, roughness),
2699
+ stroke: (fromRow, fromColumn, toRow, toColumn, startWidth, endWidth, amount) => depositStroke(context, fromRow, fromColumn, toRow, toColumn, startWidth, endWidth, amount),
2700
+ deposit: (row, column, amount) => depositCell(context, row, column, amount),
2701
+ update({ dt, dryingSeconds }) {
2702
+ drops.update(dt);
2703
+ updateDrip(film.drip, film.bead, layout, random, drops.launch, dt);
2704
+ updateSources(film.sources, context, dt);
2705
+ updateDrags(film.drags, context, layout, drops.launch, dt);
2706
+ simulator.advance({ dt, dryingSeconds });
2707
+ },
2708
+ setWeather(weather) {
2709
+ weatherState = { rain: Math.min(1, Math.max(0, weather.rain ?? weatherState.rain)) };
2710
+ },
2711
+ weather: () => ({ ...weatherState }),
2712
+ clear() {
2713
+ field.thickness.fill(0);
2714
+ field.dryness.fill(0);
2715
+ field.dampness.fill(0);
2716
+ drops.clear();
2717
+ film.drip = void 0;
2718
+ film.bead.visible = false;
2719
+ film.sources.length = 0;
2720
+ film.drags.length = 0;
2721
+ simulator.refreshBounds();
2722
+ },
2723
+ wetCells: () => wetCellCount(field)
2724
+ };
2725
+ return film;
2726
+ }
2727
+
2728
+ // src/film/random.ts
2729
+ function createFilmLcg(seed) {
2730
+ const lcg = (() => {
2731
+ lcg.seed = lcg.seed * 1664525 + 1013904223 >>> 0;
2732
+ return lcg.seed / 4294967296;
2733
+ });
2734
+ lcg.seed = seed >>> 0;
2735
+ return lcg;
2736
+ }
2737
+
2738
+ // src/film/noise.ts
2739
+ function hashCell(x, y) {
2740
+ let value = x * 374761393 + y * 668265263 | 0;
2741
+ value = (value ^ value >>> 13) * 1274126177 | 0;
2742
+ return ((value ^ value >>> 16) >>> 0) / 4294967296;
2743
+ }
2744
+ function valueNoise(x, y) {
2745
+ const xi = Math.floor(x);
2746
+ const yi = Math.floor(y);
2747
+ const xf = x - xi;
2748
+ const yf = y - yi;
2749
+ const u = xf * xf * (3 - 2 * xf);
2750
+ const v = yf * yf * (3 - 2 * yf);
2751
+ const a = hashCell(xi, yi);
2752
+ const b = hashCell(xi + 1, yi);
2753
+ const c = hashCell(xi, yi + 1);
2754
+ const d = hashCell(xi + 1, yi + 1);
2755
+ return a + (b - a) * u + (c - a) * v + (a - b - c + d) * u * v;
2756
+ }
2757
+
2758
+ // src/film/layout.ts
2759
+ var REGION_NAMES = ["wall", "sidewalk", "curb", "road"];
2760
+ var dot3 = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
2761
+ function impactOf(region, position, velocity, cellSize) {
2762
+ const local = [position[0] - region.origin[0], position[1] - region.origin[1], position[2] - region.origin[2]];
2763
+ return {
2764
+ row: region.firstRow + dot3(local, region.rowAxis) / cellSize,
2765
+ column: dot3(local, region.columnAxis) / cellSize,
2766
+ along: [dot3(velocity, region.columnAxis), dot3(velocity, region.rowAxis)],
2767
+ normalSpeed: -dot3(velocity, region.normal)
2768
+ };
2769
+ }
2770
+ function cellToWorldOf(regions, cellSize, row, column) {
2771
+ let region = regions[regions.length - 1];
2772
+ for (const candidate of regions) {
2773
+ if (row < candidate.firstRow + candidate.rowCount) {
2774
+ region = candidate;
2775
+ break;
2776
+ }
2777
+ }
2778
+ const across = column * cellSize;
2779
+ const down = (row - region.firstRow) * cellSize;
2780
+ return [
2781
+ region.origin[0] + region.columnAxis[0] * across + region.rowAxis[0] * down,
2782
+ region.origin[1] + region.columnAxis[1] * across + region.rowAxis[1] * down,
2783
+ region.origin[2] + region.columnAxis[2] * across + region.rowAxis[2] * down
2784
+ ];
2785
+ }
2786
+ function createFoldedLayout(params) {
2787
+ const { columns, cellSize, originX, wallRows, sidewalkRows, curbRows, roadRows, wallTopY, sidewalkY, roadY, wallZ, rules } = params;
2788
+ const curbStep = params.curbStep ?? (sidewalkY - roadY) / curbRows;
2789
+ const height = wallRows + sidewalkRows + curbRows + roadRows;
2790
+ const cellCount = columns * height;
2791
+ const bed = new Float32Array(cellCount);
2792
+ const pin = new Float32Array(cellCount);
2793
+ const vertical = new Float32Array(cellCount);
2794
+ const sidewalkEnd = wallRows + sidewalkRows;
2795
+ const curbEnd = sidewalkEnd + curbRows;
2796
+ const regionOfRow = (row) => row < wallRows ? 0 : row < sidewalkEnd ? 1 : row < curbEnd ? 2 : 3;
2797
+ for (let row = 0; row < height; row++) {
2798
+ for (let column = 0; column < columns; column++) {
2799
+ const index = row * columns + column;
2800
+ const region = regionOfRow(row);
2801
+ const x = originX + (column + 0.5) * cellSize;
2802
+ let bedHeight;
2803
+ let roughness = 0.2;
2804
+ if (region === 0) {
2805
+ bedHeight = (wallTopY - (row + 0.5) * cellSize) * 1e3;
2806
+ roughness = 0.3;
2807
+ } else if (region === 1) {
2808
+ const z = (row - wallRows + 0.5) * cellSize;
2809
+ bedHeight = sidewalkY * 1e3 - z * 6;
2810
+ const jointX = Math.abs(((x - originX) % 0.5 + 0.5) % 0.5 - 0.25);
2811
+ const jointZ = Math.abs((z % 0.5 + 0.5) % 0.5 - 0.25);
2812
+ if (jointX > 0.25 - cellSize * 0.6 || jointZ > 0.25 - cellSize * 0.6) bedHeight -= 0.9;
2813
+ roughness = 0.18;
2814
+ } else if (region === 2) {
2815
+ bedHeight = sidewalkY * 1e3 - (row - sidewalkEnd + 0.5) * curbStep * 1e3;
2816
+ } else {
2817
+ const z = (row - curbEnd + 0.5) * cellSize;
2818
+ bedHeight = roadY * 1e3 + z * 14 - (x - params.roadSlopeAnchorX) * 5;
2819
+ roughness = 0.55;
2820
+ }
2821
+ const noise = valueNoise(column * 0.35, row * 0.35) * 0.6 + valueNoise(column * 1.3, row * 1.3) * 0.4;
2822
+ bed[index] = bedHeight + (noise - 0.5) * roughness * 2;
2823
+ vertical[index] = region === 0 || region === 2 ? 0.95 : 1;
2824
+ pin[index] = 0.62 + 0.76 * (valueNoise(column * 0.21 + 40, row * 0.21) * 0.5 + hashCell(column, row) * 0.5) * (region === 3 ? 1.25 : 1);
2825
+ }
2826
+ }
2827
+ const curbZ = wallZ + sidewalkRows * cellSize;
2828
+ const roadEndZ = curbZ + roadRows * cellSize;
2829
+ const right = [1, 0, 0];
2830
+ const regions = [
2831
+ { id: REGION_NAMES[0], firstRow: 0, rowCount: wallRows, origin: [originX, wallTopY, wallZ], columnAxis: right, rowAxis: [0, -1, 0], normal: [0, 0, 1], isVertical: true },
2832
+ { id: REGION_NAMES[1], firstRow: wallRows, rowCount: sidewalkRows, origin: [originX, sidewalkY, wallZ], columnAxis: right, rowAxis: [0, 0, 1], normal: [0, 1, 0], isVertical: false },
2833
+ { id: REGION_NAMES[2], firstRow: sidewalkEnd, rowCount: curbRows, origin: [originX, sidewalkY, curbZ], columnAxis: right, rowAxis: [0, -1, 0], normal: [0, 0, 1], isVertical: true },
2834
+ { id: REGION_NAMES[3], firstRow: curbEnd, rowCount: roadRows, origin: [originX, roadY, curbZ], columnAxis: right, rowAxis: [0, 0, 1], normal: [0, 1, 0], isVertical: false }
2835
+ ];
2836
+ const maxX = originX + columns * cellSize;
2837
+ return {
2838
+ id: params.id,
2839
+ width: columns,
2840
+ height,
2841
+ cellSize,
2842
+ bed,
2843
+ pin,
2844
+ vertical,
2845
+ regions,
2846
+ rules,
2847
+ cellToWorld: (row, column) => cellToWorldOf(regions, cellSize, row, column),
2848
+ collide(previous, position) {
2849
+ if (position[2] <= wallZ && position[1] > sidewalkY) {
2850
+ position[2] = wallZ;
2851
+ return 0;
2852
+ }
2853
+ if (position[2] < curbZ && (!rules.guardBehindWall || position[2] >= wallZ) && position[1] <= sidewalkY) {
2854
+ position[1] = sidewalkY;
2855
+ return 1;
2856
+ }
2857
+ if (rules.curbCatch && position[2] >= curbZ && previous[2] < curbZ && position[1] < sidewalkY) {
2858
+ position[2] = curbZ;
2859
+ return 2;
2860
+ }
2861
+ if (position[2] >= curbZ && position[1] <= roadY) {
2862
+ position[1] = roadY;
2863
+ return 3;
2864
+ }
2865
+ return -1;
2866
+ },
2867
+ impact: (region, position, velocity) => impactOf(regions[region], position, velocity, cellSize),
2868
+ contains: (position) => position[0] > originX && position[0] < maxX && (!rules.clipRoadEnd || position[2] < roadEndZ),
2869
+ escaped(position) {
2870
+ if (position[1] < -1) return true;
2871
+ const box = rules.escapeBox;
2872
+ return box !== void 0 && (position[0] < box.minX || position[0] > box.maxX || position[2] > box.maxZ);
2873
+ }
2874
+ };
2875
+ }
2876
+ var PADRAO_CELL = 0.0125;
2877
+ var PADRAO_WALL_Z = 0;
2878
+ var PADRAO_LAYOUT = createFoldedLayout({
2879
+ id: "padrao",
2880
+ columns: 256,
2881
+ cellSize: PADRAO_CELL,
2882
+ originX: -1.6,
2883
+ wallRows: 144,
2884
+ sidewalkRows: 128,
2885
+ curbRows: 12,
2886
+ roadRows: 112,
2887
+ wallTopY: 1.95,
2888
+ sidewalkY: 0.15,
2889
+ roadY: 0,
2890
+ wallZ: PADRAO_WALL_Z,
2891
+ curbStep: PADRAO_CELL,
2892
+ roadSlopeAnchorX: 0,
2893
+ rules: {
2894
+ dropCapacity: 600,
2895
+ curbCatch: true,
2896
+ guardBehindWall: true,
2897
+ clipRoadEnd: true,
2898
+ escapeBox: { minX: -3, maxX: 3, maxZ: PADRAO_WALL_Z + 128 * PADRAO_CELL + 112 * PADRAO_CELL + 1 },
2899
+ dripStyle: "padrao"
2900
+ }
2901
+ });
2902
+ var WORKSHOP_CELL = 0.0125;
2903
+ var WORKSHOP_LAYOUT = createFoldedLayout({
2904
+ id: "workshop",
2905
+ columns: 160,
2906
+ cellSize: WORKSHOP_CELL,
2907
+ originX: -2.2,
2908
+ wallRows: 160,
2909
+ sidewalkRows: 128,
2910
+ curbRows: 9,
2911
+ roadRows: 151,
2912
+ wallTopY: 2.12,
2913
+ sidewalkY: 0.1235,
2914
+ roadY: 9e-3,
2915
+ wallZ: -4.497,
2916
+ roadSlopeAnchorX: -1.2,
2917
+ rules: { dropCapacity: 400, curbCatch: false, guardBehindWall: false, clipRoadEnd: false, dripStyle: "workshop" }
2918
+ });
2919
+
2920
+ // src/film/surface-layout.ts
2921
+ var DEFAULT_COLUMNS = 160;
2922
+ var DEFAULT_ROWS = 160;
2923
+ var DEFAULT_CELL = 0.0125;
2924
+ var DEFAULT_WALL_ROWS = 48;
2925
+ var DEFAULT_CURB_ROWS = 12;
2926
+ var RULES = { dropCapacity: 160, curbCatch: false, guardBehindWall: false, clipRoadEnd: false, dripStyle: "padrao" };
2927
+ var along3 = (origin, axis, distance2) => add(origin, scale(axis, distance2));
2928
+ var reject = (vector, axis) => sub(vector, scale(axis, dot(vector, axis)));
2929
+ function layoutFromSurfaces(options) {
2930
+ const columns = options.columns ?? DEFAULT_COLUMNS;
2931
+ const totalRows = options.rows ?? DEFAULT_ROWS;
2932
+ const cell = options.cellSize ?? DEFAULT_CELL;
2933
+ const floorBasis = makeBasis(options.floor.normal, options.floor.tangent);
2934
+ const floorNormal = floorBasis.normal;
2935
+ const projected = sub(options.center, scale(floorNormal, dot(sub(options.center, options.floor.point), floorNormal)));
2936
+ const wallNormal = options.wall ? normalize(reject(options.wall.normal, floorNormal), floorBasis.t2) : void 0;
2937
+ const awayAxis = wallNormal ?? floorBasis.t2;
2938
+ const columnAxis = wallNormal ? normalize(cross(floorNormal, wallNormal), floorBasis.t1) : floorBasis.t1;
2939
+ const wallRows = options.wall ? Math.min(totalRows - 1, options.wallRows ?? DEFAULT_WALL_ROWS) : 0;
2940
+ let fold = projected;
2941
+ if (options.wall && wallNormal) {
2942
+ const toWall = dot(sub(projected, options.wall.point), wallNormal) / (dot(awayAxis, wallNormal) || 1);
2943
+ fold = sub(projected, scale(awayAxis, toWall));
2944
+ } else {
2945
+ fold = along3(projected, awayAxis, -(totalRows * cell) / 2);
2946
+ }
2947
+ const lateral = dot(sub(projected, fold), columnAxis) - columns * cell / 2;
2948
+ const base = along3(fold, columnAxis, lateral);
2949
+ const strips = [];
2950
+ const roughness = 0.18 + 0.4 * options.floor.porosity;
2951
+ if (options.wall && wallNormal) {
2952
+ strips.push({ id: "wall", rows: wallRows, origin: along3(base, floorNormal, wallRows * cell), rowAxis: scale(floorNormal, -1), normal: wallNormal, isVertical: true, roughness: 0.3 });
2953
+ }
2954
+ const lowerRows = totalRows - wallRows;
2955
+ const dropMetres = options.curb ? Math.max(0, dot(sub(options.floor.point, options.curb.point), floorNormal)) : 0;
2956
+ const curbRows = options.curb ? Math.min(options.curbRows ?? DEFAULT_CURB_ROWS, Math.max(1, Math.ceil(dropMetres / cell)), lowerRows - 2) : 0;
2957
+ const floorRows = options.curb ? Math.max(1, Math.floor((lowerRows - curbRows) / 2)) : lowerRows;
2958
+ strips.push({ id: "floor", rows: floorRows, origin: base, rowAxis: awayAxis, normal: floorNormal, isVertical: false, roughness });
2959
+ if (options.curb) {
2960
+ const edge = along3(base, awayAxis, floorRows * cell);
2961
+ strips.push({ id: "curb", rows: curbRows, origin: edge, rowAxis: scale(floorNormal, -1), normal: awayAxis, isVertical: true, roughness: 0.2 });
2962
+ const lowerOrigin = along3(edge, floorNormal, -curbRows * cell);
2963
+ strips.push({ id: "low", rows: lowerRows - floorRows - curbRows, origin: lowerOrigin, rowAxis: awayAxis, normal: floorNormal, isVertical: false, roughness: 0.55 });
2964
+ }
2965
+ const regions = [];
2966
+ let firstRow = 0;
2967
+ for (const strip of strips) {
2968
+ regions.push({ id: strip.id, firstRow, rowCount: strip.rows, origin: strip.origin, columnAxis, rowAxis: strip.rowAxis, normal: strip.normal, isVertical: strip.isVertical });
2969
+ firstRow += strip.rows;
2970
+ }
2971
+ const height = firstRow;
2972
+ const bed = new Float32Array(columns * height);
2973
+ const pin = new Float32Array(columns * height);
2974
+ const vertical = new Float32Array(columns * height);
2975
+ regions.forEach((region, regionIndex) => {
2976
+ const strip = strips[regionIndex];
2977
+ for (let row = 0; row < region.rowCount; row++) {
2978
+ for (let column = 0; column < columns; column++) {
2979
+ const index = (region.firstRow + row) * columns + column;
2980
+ const centre = add(add(region.origin, scale(columnAxis, (column + 0.5) * cell)), scale(region.rowAxis, (row + 0.5) * cell));
2981
+ const noise = valueNoise(column * 0.35, (region.firstRow + row) * 0.35) * 0.6 + valueNoise(column * 1.3, (region.firstRow + row) * 1.3) * 0.4;
2982
+ bed[index] = centre[1] * 1e3 + (noise - 0.5) * strip.roughness * 2;
2983
+ vertical[index] = region.isVertical ? 0.95 : 1;
2984
+ pin[index] = 0.62 + 0.76 * (valueNoise(column * 0.21 + 40, (region.firstRow + row) * 0.21) * 0.5 + valueNoise(column * 0.9, row * 0.9) * 0.5);
2985
+ }
2986
+ }
2987
+ });
2988
+ const lowestY = Math.min(...regions.map((region) => region.origin[1] - (region.rowAxis[1] > 0 ? 0 : region.rowCount * cell * Math.abs(region.rowAxis[1])))) - 1;
2989
+ const width = columns * cell;
2990
+ const cellToWorld = (row, column) => {
2991
+ const region = regions.find((candidate) => row < candidate.firstRow + candidate.rowCount) ?? regions[regions.length - 1];
2992
+ return add(add(region.origin, scale(region.columnAxis, column * cell)), scale(region.rowAxis, (row - region.firstRow) * cell));
2993
+ };
2994
+ return {
2995
+ id: "surfaces",
2996
+ width: columns,
2997
+ height,
2998
+ cellSize: cell,
2999
+ bed,
3000
+ pin,
3001
+ vertical,
3002
+ regions,
3003
+ rules: RULES,
3004
+ cellToWorld,
3005
+ collide(previous, position) {
3006
+ for (let index = 0; index < regions.length; index++) {
3007
+ const region = regions[index];
3008
+ const before = dot(sub(previous, region.origin), region.normal);
3009
+ const after = dot(sub(position, region.origin), region.normal);
3010
+ if (before < 0 || after > 0) continue;
3011
+ const local = sub(position, region.origin);
3012
+ const sideways = dot(local, region.columnAxis);
3013
+ const downward = dot(local, region.rowAxis);
3014
+ if (sideways < 0 || sideways >= width || downward < 0 || downward >= region.rowCount * cell) continue;
3015
+ position[0] -= region.normal[0] * after;
3016
+ position[1] -= region.normal[1] * after;
3017
+ position[2] -= region.normal[2] * after;
3018
+ return index;
3019
+ }
3020
+ return -1;
3021
+ },
3022
+ impact(regionIndex, position, velocity) {
3023
+ const region = regions[regionIndex];
3024
+ const local = sub(position, region.origin);
3025
+ return {
3026
+ row: region.firstRow + dot(local, region.rowAxis) / cell,
3027
+ column: dot(local, region.columnAxis) / cell,
3028
+ along: [dot(velocity, region.columnAxis), dot(velocity, region.rowAxis)],
3029
+ normalSpeed: -dot(velocity, region.normal)
3030
+ };
3031
+ },
3032
+ contains: () => true,
3033
+ escaped: (position) => position[1] < lowestY
3034
+ };
3035
+ }
3036
+
3037
+ // src/data/film-profiles.json
3038
+ var film_profiles_default = [
3039
+ {
3040
+ id: "blood",
3041
+ look: "blood",
3042
+ flow: 0.05,
3043
+ pin: 0.33,
3044
+ front: 0.58,
3045
+ referenceThickness: 0.6,
3046
+ slopeCap: 1.1,
3047
+ dryTime: 600,
3048
+ evaporation: 0,
3049
+ opacityRate: 3.2,
3050
+ reflectance: 0.045,
3051
+ normalScale: 16,
3052
+ thin: 7997966,
3053
+ thick: 2753028,
3054
+ dry: 3874068,
3055
+ dryThin: 6960678,
3056
+ dropColor: 7997965,
3057
+ dropOpacity: 1,
3058
+ volume: 1,
3059
+ spikes: 1,
3060
+ landsAsBlob: false,
3061
+ maxAmount: 2.6,
3062
+ sources: {
3063
+ padrao: "F46",
3064
+ oficina: "W583"
3065
+ }
3066
+ },
3067
+ {
3068
+ id: "old_blood",
3069
+ look: "blood",
3070
+ flow: 0.032,
3071
+ pin: 0.42,
3072
+ front: 0.7,
3073
+ referenceThickness: 0.7,
3074
+ slopeCap: 1,
3075
+ dryTime: 360,
3076
+ evaporation: 0,
3077
+ opacityRate: 3.8,
3078
+ reflectance: 0.03,
3079
+ normalScale: 14,
3080
+ thin: 7085078,
3081
+ thick: 2888717,
3082
+ dry: 2758158,
3083
+ dryThin: 4860444,
3084
+ dropColor: 4854286,
3085
+ dropOpacity: 1,
3086
+ volume: 0.6,
3087
+ spikes: 1,
3088
+ landsAsBlob: false,
3089
+ maxAmount: 2.6,
3090
+ sources: {
3091
+ padrao: "F48"
3092
+ }
3093
+ },
3094
+ {
3095
+ id: "bile",
3096
+ look: "bile",
3097
+ flow: 0.02,
3098
+ pin: 0.8,
3099
+ front: 1.2,
3100
+ referenceThickness: 1.2,
3101
+ slopeCap: 0.9,
3102
+ dryTime: 900,
3103
+ evaporation: 0,
3104
+ opacityRate: 7,
3105
+ reflectance: 0.04,
3106
+ normalScale: 12,
3107
+ thin: 11121724,
3108
+ thick: 7174171,
3109
+ dry: 4080160,
3110
+ dryThin: 5659180,
3111
+ dropColor: 9083434,
3112
+ dropOpacity: 1,
3113
+ volume: 1.3,
3114
+ spikes: 0.3,
3115
+ landsAsBlob: false,
3116
+ maxAmount: 4,
3117
+ sources: {
3118
+ padrao: "F50",
3119
+ oficina: "W584 (spikes 0.3 W578, cap 4 W691)"
3120
+ }
3121
+ },
3122
+ {
3123
+ id: "motor_oil",
3124
+ look: "oil",
3125
+ flow: 0.03,
3126
+ pin: 0.12,
3127
+ front: 0.22,
3128
+ referenceThickness: 0.5,
3129
+ slopeCap: 1,
3130
+ dryTime: 0,
3131
+ evaporation: 0,
3132
+ opacityRate: 4.5,
3133
+ reflectance: 0.06,
3134
+ normalScale: 18,
3135
+ thin: 1381135,
3136
+ thick: 263173,
3137
+ dry: 263173,
3138
+ dryThin: 1381135,
3139
+ dropColor: 657932,
3140
+ dropOpacity: 1,
3141
+ volume: 1,
3142
+ spikes: 1,
3143
+ landsAsBlob: true,
3144
+ maxAmount: 2.6,
3145
+ sources: {
3146
+ padrao: "F52",
3147
+ oficina: "W585 (blob W691)"
3148
+ }
3149
+ },
3150
+ {
3151
+ id: "fuel",
3152
+ look: "fuel",
3153
+ flow: 0.2,
3154
+ pin: 0.035,
3155
+ front: 0.07,
3156
+ referenceThickness: 0.2,
3157
+ slopeCap: 1.2,
3158
+ dryTime: 0,
3159
+ evaporation: 0.02,
3160
+ opacityRate: 0.25,
3161
+ reflectance: 0.04,
3162
+ normalScale: 0,
3163
+ thin: 10129520,
3164
+ thick: 7234630,
3165
+ dry: 0,
3166
+ dryThin: 0,
3167
+ dropColor: 12103824,
3168
+ dropOpacity: 0.45,
3169
+ volume: 1.4,
3170
+ spikes: 1,
3171
+ landsAsBlob: true,
3172
+ maxAmount: 2.6,
3173
+ sources: {
3174
+ padrao: "F54",
3175
+ oficina: "W586 (blob W691)"
3176
+ }
3177
+ }
3178
+ ];
3179
+
3180
+ // src/film/profiles.ts
3181
+ var FILM_PROFILES = film_profiles_default;
3182
+ function filmProfileOf(id, overrides = {}) {
3183
+ const profile = FILM_PROFILES.find((entry) => entry.id === id);
3184
+ return profile ? { ...profile, ...overrides } : void 0;
3185
+ }
2171
3186
  export {
3187
+ FILM_PROFILES,
2172
3188
  ITEM_USES,
2173
3189
  LIQUIDS,
3190
+ PADRAO_LAYOUT,
2174
3191
  SAMPLES as STAIN_SAMPLES,
3192
+ WORKSHOP_LAYOUT,
2175
3193
  ballistic,
3194
+ createCpuSimulator,
3195
+ createFilm,
3196
+ createFilmLcg,
2176
3197
  createFluids,
3198
+ createFoldedLayout,
2177
3199
  createItemUse,
2178
3200
  describeEvent,
3201
+ filmProfileOf,
3202
+ hashCell,
3203
+ layoutFromSurfaces,
2179
3204
  locales,
2180
3205
  resolveStrings,
2181
3206
  sampleStream,
2182
- stainDirection
3207
+ stainDirection,
3208
+ valueNoise
2183
3209
  };
2184
3210
  //# sourceMappingURL=index.js.map