@adatechnology/game-fluids 0.1.8 → 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/README.md +39 -5
- package/dist/index.d.ts +349 -3
- package/dist/index.global.js +1036 -10
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1036 -10
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
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:
|
|
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:
|
|
192
|
+
dryTime: 60,
|
|
193
193
|
gloss: 0.95,
|
|
194
|
-
leavesMark:
|
|
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:
|
|
275
|
+
stainColor: 3874068,
|
|
276
276
|
viscosity: 4,
|
|
277
277
|
density: 1060,
|
|
278
|
-
dryTime:
|
|
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
|