@adatechnology/game-fluids 0.1.7 → 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/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.global.js
CHANGED
|
@@ -23,17 +23,28 @@ var GameFluids = (() => {
|
|
|
23
23
|
// src/index.ts
|
|
24
24
|
var index_exports = {};
|
|
25
25
|
__export(index_exports, {
|
|
26
|
+
FILM_PROFILES: () => FILM_PROFILES,
|
|
26
27
|
ITEM_USES: () => ITEM_USES,
|
|
27
28
|
LIQUIDS: () => LIQUIDS,
|
|
29
|
+
PADRAO_LAYOUT: () => PADRAO_LAYOUT,
|
|
28
30
|
STAIN_SAMPLES: () => SAMPLES,
|
|
31
|
+
WORKSHOP_LAYOUT: () => WORKSHOP_LAYOUT,
|
|
29
32
|
ballistic: () => ballistic,
|
|
33
|
+
createCpuSimulator: () => createCpuSimulator,
|
|
34
|
+
createFilm: () => createFilm,
|
|
35
|
+
createFilmLcg: () => createFilmLcg,
|
|
30
36
|
createFluids: () => createFluids,
|
|
37
|
+
createFoldedLayout: () => createFoldedLayout,
|
|
31
38
|
createItemUse: () => createItemUse,
|
|
32
39
|
describeEvent: () => describeEvent,
|
|
40
|
+
filmProfileOf: () => filmProfileOf,
|
|
41
|
+
hashCell: () => hashCell,
|
|
42
|
+
layoutFromSurfaces: () => layoutFromSurfaces,
|
|
33
43
|
locales: () => locales,
|
|
34
44
|
resolveStrings: () => resolveStrings,
|
|
35
45
|
sampleStream: () => sampleStream,
|
|
36
|
-
stainDirection: () => stainDirection
|
|
46
|
+
stainDirection: () => stainDirection,
|
|
47
|
+
valueNoise: () => valueNoise
|
|
37
48
|
});
|
|
38
49
|
|
|
39
50
|
// src/data/liquids.json
|
|
@@ -45,7 +56,7 @@ var GameFluids = (() => {
|
|
|
45
56
|
stainColor: 7309206,
|
|
46
57
|
viscosity: 1,
|
|
47
58
|
density: 1e3,
|
|
48
|
-
dryTime:
|
|
59
|
+
dryTime: 180,
|
|
49
60
|
gloss: 0.9,
|
|
50
61
|
drink: {
|
|
51
62
|
thirst: 100,
|
|
@@ -223,9 +234,9 @@ var GameFluids = (() => {
|
|
|
223
234
|
stainColor: 7232298,
|
|
224
235
|
viscosity: 0.6,
|
|
225
236
|
density: 740,
|
|
226
|
-
dryTime:
|
|
237
|
+
dryTime: 60,
|
|
227
238
|
gloss: 0.95,
|
|
228
|
-
leavesMark:
|
|
239
|
+
leavesMark: false,
|
|
229
240
|
flammable: true,
|
|
230
241
|
drink: {
|
|
231
242
|
thirst: 0,
|
|
@@ -306,10 +317,10 @@ var GameFluids = (() => {
|
|
|
306
317
|
id: "blood",
|
|
307
318
|
color: 7997964,
|
|
308
319
|
opacity: 0.92,
|
|
309
|
-
stainColor:
|
|
320
|
+
stainColor: 3874068,
|
|
310
321
|
viscosity: 4,
|
|
311
322
|
density: 1060,
|
|
312
|
-
dryTime:
|
|
323
|
+
dryTime: 600,
|
|
313
324
|
gloss: 0.75,
|
|
314
325
|
leavesMark: true,
|
|
315
326
|
drink: {
|
|
@@ -322,6 +333,46 @@ var GameFluids = (() => {
|
|
|
322
333
|
"organic"
|
|
323
334
|
]
|
|
324
335
|
},
|
|
336
|
+
{
|
|
337
|
+
id: "old_blood",
|
|
338
|
+
color: 7085078,
|
|
339
|
+
opacity: 0.92,
|
|
340
|
+
stainColor: 2758158,
|
|
341
|
+
viscosity: 4,
|
|
342
|
+
density: 1060,
|
|
343
|
+
dryTime: 360,
|
|
344
|
+
gloss: 0.7,
|
|
345
|
+
leavesMark: true,
|
|
346
|
+
drink: {
|
|
347
|
+
thirst: 10,
|
|
348
|
+
effects: [
|
|
349
|
+
"disgust"
|
|
350
|
+
]
|
|
351
|
+
},
|
|
352
|
+
tags: [
|
|
353
|
+
"organic"
|
|
354
|
+
]
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
id: "bile",
|
|
358
|
+
color: 9083434,
|
|
359
|
+
opacity: 0.9,
|
|
360
|
+
stainColor: 4080160,
|
|
361
|
+
viscosity: 6,
|
|
362
|
+
density: 1020,
|
|
363
|
+
dryTime: 900,
|
|
364
|
+
gloss: 0.75,
|
|
365
|
+
leavesMark: true,
|
|
366
|
+
drink: {
|
|
367
|
+
thirst: 0,
|
|
368
|
+
effects: [
|
|
369
|
+
"disgust"
|
|
370
|
+
]
|
|
371
|
+
},
|
|
372
|
+
tags: [
|
|
373
|
+
"organic"
|
|
374
|
+
]
|
|
375
|
+
},
|
|
325
376
|
{
|
|
326
377
|
id: "sap",
|
|
327
378
|
color: 13146940,
|
|
@@ -564,6 +615,8 @@ var GameFluids = (() => {
|
|
|
564
615
|
motor_oil: "motor oil",
|
|
565
616
|
cooking_oil: "cooking oil",
|
|
566
617
|
blood: "blood",
|
|
618
|
+
old_blood: "old blood",
|
|
619
|
+
bile: "bile",
|
|
567
620
|
sap: "sap",
|
|
568
621
|
acid: "acid",
|
|
569
622
|
paint: "paint",
|
|
@@ -604,6 +657,8 @@ var GameFluids = (() => {
|
|
|
604
657
|
motor_oil: "\xF3leo de motor",
|
|
605
658
|
cooking_oil: "\xF3leo de cozinha",
|
|
606
659
|
blood: "sangue",
|
|
660
|
+
old_blood: "sangue antigo",
|
|
661
|
+
bile: "bile",
|
|
607
662
|
sap: "seiva",
|
|
608
663
|
acid: "\xE1cido",
|
|
609
664
|
paint: "tinta",
|
|
@@ -1115,7 +1170,7 @@ var GameFluids = (() => {
|
|
|
1115
1170
|
}
|
|
1116
1171
|
if (victim) stains.delete(victim.id);
|
|
1117
1172
|
};
|
|
1118
|
-
const deposit = (liquid, hit, liters, velocity) => {
|
|
1173
|
+
const deposit = (liquid, hit, liters, velocity, tint) => {
|
|
1119
1174
|
const def = liquids.get(liquid);
|
|
1120
1175
|
if (!def || liters <= 0) return;
|
|
1121
1176
|
let s = stainAt(hit.surface, liquid, hit.point);
|
|
@@ -1123,6 +1178,7 @@ var GameFluids = (() => {
|
|
|
1123
1178
|
const basis2 = { normal: s.normal, t1: s.tangent, t2: cross(s.normal, s.tangent) };
|
|
1124
1179
|
nudgeCenter(s, hit.point, clamp(liters / (s.liters + liters) * 0.5, 0, 0.5), basis2);
|
|
1125
1180
|
addInflow(s, liters, velocity);
|
|
1181
|
+
if (tint) s.tint = { ...tint };
|
|
1126
1182
|
return;
|
|
1127
1183
|
}
|
|
1128
1184
|
while (stains.size >= maxStains) evictStain();
|
|
@@ -1130,6 +1186,7 @@ var GameFluids = (() => {
|
|
|
1130
1186
|
const basis = hit.surface === GROUND_ID ? makeBasis(hit.normal) : surface;
|
|
1131
1187
|
s = createStain(`stain_${++counters.stain}`, hit.surface, def, basis, hit.point, liters, rng);
|
|
1132
1188
|
if (velocity) addInflow(s, 0, velocity);
|
|
1189
|
+
if (tint) s.tint = { ...tint };
|
|
1133
1190
|
s.sinceInflow = 0;
|
|
1134
1191
|
stains.set(s.id, s);
|
|
1135
1192
|
emit({ type: "stain", stain: s.id, surface: hit.surface, liquid, point: copy3(hit.point) });
|
|
@@ -1322,7 +1379,8 @@ var GameFluids = (() => {
|
|
|
1322
1379
|
tangent: copy3(s.tangent),
|
|
1323
1380
|
flow: s.flow ? copy3(s.flow) : void 0,
|
|
1324
1381
|
mark: s.mark,
|
|
1325
|
-
age: s.age
|
|
1382
|
+
age: s.age,
|
|
1383
|
+
tint: s.tint ? { ...s.tint } : void 0
|
|
1326
1384
|
});
|
|
1327
1385
|
const world = {
|
|
1328
1386
|
liquids,
|
|
@@ -1507,14 +1565,14 @@ var GameFluids = (() => {
|
|
|
1507
1565
|
surfaces.delete(id);
|
|
1508
1566
|
for (const s of [...stains.values()]) if (s.surface === id) stains.delete(s.id);
|
|
1509
1567
|
},
|
|
1510
|
-
splash(liquid, point, liters, velocity) {
|
|
1568
|
+
splash(liquid, point, liters, velocity, options2) {
|
|
1511
1569
|
if (!liquids.has(liquid) || !(liters > 0)) return;
|
|
1512
1570
|
const v = velocity ?? [0, -0.1, 0];
|
|
1513
1571
|
const origin = add(point, [0, 0.02, 0]);
|
|
1514
1572
|
const hit = trace(landingSurfaces(), groundAt, origin, v, gravity);
|
|
1515
1573
|
if (!hit) return;
|
|
1516
1574
|
emit({ type: "splash", liquid, point: copy3(hit.point), intensity: clamp(0.3 + liters * 2, 0, 1) });
|
|
1517
|
-
deposit(liquid, hit, liters, length(v) > 0.3 ? hit.velocity : null);
|
|
1575
|
+
deposit(liquid, hit, liters, length(v) > 0.3 ? hit.velocity : null, options2?.tint);
|
|
1518
1576
|
},
|
|
1519
1577
|
clearStains(point, radius) {
|
|
1520
1578
|
for (const s of [...stains.values()]) {
|
|
@@ -2202,6 +2260,974 @@ var GameFluids = (() => {
|
|
|
2202
2260
|
};
|
|
2203
2261
|
return player;
|
|
2204
2262
|
}
|
|
2263
|
+
|
|
2264
|
+
// src/film/deposit.ts
|
|
2265
|
+
var VISIBLE_THICKNESS = 0.035;
|
|
2266
|
+
function depositCell(context, rowIn, columnIn, amount) {
|
|
2267
|
+
const { field } = context;
|
|
2268
|
+
const row = Math.round(rowIn);
|
|
2269
|
+
const column = Math.round(columnIn);
|
|
2270
|
+
if (row < 0 || column < 0 || row >= field.height || column >= field.width || !(amount > 0)) return;
|
|
2271
|
+
const index = row * field.width + column;
|
|
2272
|
+
field.thickness[index] += amount;
|
|
2273
|
+
if (amount > 0.02) field.dryness[index] *= 0.15;
|
|
2274
|
+
field.dampness[index] = 1;
|
|
2275
|
+
}
|
|
2276
|
+
function wetCellCount(field) {
|
|
2277
|
+
let cells = 0;
|
|
2278
|
+
for (let index = 0; index < field.thickness.length; index++) if (field.thickness[index] > VISIBLE_THICKNESS) cells++;
|
|
2279
|
+
return cells;
|
|
2280
|
+
}
|
|
2281
|
+
function depositBlob(context, centerRow, centerColumn, along, across, directionColumn, directionRow, amount, roughness) {
|
|
2282
|
+
const random = context.random;
|
|
2283
|
+
const phaseA = random() * 6.28;
|
|
2284
|
+
const phaseB = random() * 6.28;
|
|
2285
|
+
const phaseC = random() * 6.28;
|
|
2286
|
+
const phaseD = random() * 6.28;
|
|
2287
|
+
const reach = Math.ceil(Math.max(along, across) * (1 + roughness) + 1.5);
|
|
2288
|
+
for (let row = Math.floor(centerRow - reach); row <= centerRow + reach; row++) {
|
|
2289
|
+
for (let column = Math.floor(centerColumn - reach); column <= centerColumn + reach; column++) {
|
|
2290
|
+
const dx = column + 0.5 - centerColumn;
|
|
2291
|
+
const dy = row + 0.5 - centerRow;
|
|
2292
|
+
const alongDistance = dx * directionColumn + dy * directionRow;
|
|
2293
|
+
const acrossDistance = -dx * directionRow + dy * directionColumn;
|
|
2294
|
+
const angle = Math.atan2(acrossDistance / across, alongDistance / along);
|
|
2295
|
+
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));
|
|
2296
|
+
const squared = (alongDistance / along * (alongDistance / along) + acrossDistance / across * (acrossDistance / across)) / (edge * edge);
|
|
2297
|
+
if (squared < 1) depositCell(context, row, column, amount * Math.sqrt(1 - squared));
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
function depositStroke(context, fromRow, fromColumn, toRow, toColumn, startWidth, endWidth, amount) {
|
|
2302
|
+
const { field } = context;
|
|
2303
|
+
const length2 = Math.hypot(toRow - fromRow, toColumn - fromColumn);
|
|
2304
|
+
const samples = Math.max(2, Math.ceil(length2 * 2.5));
|
|
2305
|
+
for (let sample = 0; sample <= samples; sample++) {
|
|
2306
|
+
const progress = sample / samples;
|
|
2307
|
+
const row = fromRow + (toRow - fromRow) * progress;
|
|
2308
|
+
const column = fromColumn + (toColumn - fromColumn) * progress;
|
|
2309
|
+
const width = startWidth + (endWidth - startWidth) * progress;
|
|
2310
|
+
const reach = Math.ceil(width + 1);
|
|
2311
|
+
for (let cellRow = Math.floor(row - reach); cellRow <= row + reach; cellRow++) {
|
|
2312
|
+
for (let cellColumn = Math.floor(column - reach); cellColumn <= column + reach; cellColumn++) {
|
|
2313
|
+
if (cellRow < 0 || cellColumn < 0 || cellRow >= field.height || cellColumn >= field.width) continue;
|
|
2314
|
+
const distance2 = Math.hypot(cellRow + 0.5 - row, cellColumn + 0.5 - column);
|
|
2315
|
+
if (distance2 >= width) continue;
|
|
2316
|
+
const index = cellRow * field.width + cellColumn;
|
|
2317
|
+
const target = amount * (1 - distance2 / width) * 0.5;
|
|
2318
|
+
if (field.thickness[index] < target) depositCell(context, cellRow, cellColumn, target - field.thickness[index]);
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
function depositSplat(context, row, column, size, directionColumn, directionRow, sinAngleIn, amount) {
|
|
2324
|
+
const random = context.random;
|
|
2325
|
+
const spikes = context.profile().spikes;
|
|
2326
|
+
const sinAngle = Math.max(0.17, Math.min(1, sinAngleIn));
|
|
2327
|
+
const stretch = Math.min(5.5, 1 / sinAngle);
|
|
2328
|
+
const along = size * Math.pow(stretch, 0.75);
|
|
2329
|
+
const across = size / Math.pow(stretch, 0.25);
|
|
2330
|
+
const frontRow = row + directionRow * along * 0.25;
|
|
2331
|
+
const frontColumn = column + directionColumn * along * 0.25;
|
|
2332
|
+
depositBlob(context, frontRow, frontColumn, along, across, directionColumn, directionRow, amount, 0.1 + 0.12 * (1 - sinAngle) + (size < 1.4 ? 0 : 0.06));
|
|
2333
|
+
const sideColumn = -directionRow;
|
|
2334
|
+
const sideRow = directionColumn;
|
|
2335
|
+
const spikeCount = Math.round(Math.min(18, (size < 1.2 ? 1 : 4) + size * 1.6 + 5 * (1 - sinAngle)) * spikes);
|
|
2336
|
+
for (let spike = 0; spike < spikeCount; spike++) {
|
|
2337
|
+
const spread = Math.PI * (0.25 + 0.75 * sinAngle * sinAngle);
|
|
2338
|
+
let theta = (random() * 2 - 1) * spread;
|
|
2339
|
+
if (sinAngle > 0.9) theta = random() * 6.283;
|
|
2340
|
+
const cosine = Math.cos(theta);
|
|
2341
|
+
const sine = Math.sin(theta);
|
|
2342
|
+
const dx = cosine * directionColumn + sine * sideColumn;
|
|
2343
|
+
const dy = cosine * directionRow + sine * sideRow;
|
|
2344
|
+
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);
|
|
2345
|
+
const startRow = frontRow + (along * cosine * directionRow + across * sine * sideRow) * 0.75;
|
|
2346
|
+
const startColumn = frontColumn + (along * cosine * directionColumn + across * sine * sideColumn) * 0.75;
|
|
2347
|
+
const endRow = startRow + dy * length2;
|
|
2348
|
+
const endColumn = startColumn + dx * length2;
|
|
2349
|
+
depositStroke(context, startRow, startColumn, endRow, endColumn, Math.max(0.55, size * 0.22), 0.42, amount * 0.75);
|
|
2350
|
+
if (random() < 0.5) {
|
|
2351
|
+
const gap = 1 + random() * 2.2;
|
|
2352
|
+
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);
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
const satelliteCount = Math.round((1 + size * 0.9) * (1.4 - sinAngle * 0.6) * (0.5 + 0.5 * spikes));
|
|
2356
|
+
for (let satellite = 0; satellite < satelliteCount; satellite++) {
|
|
2357
|
+
const cone = sinAngle > 0.9 ? Math.PI : 0.18 + 0.6 * sinAngle;
|
|
2358
|
+
const theta = (random() * 2 - 1) * cone;
|
|
2359
|
+
const cosine = Math.cos(theta);
|
|
2360
|
+
const sine = Math.sin(theta);
|
|
2361
|
+
const dx = cosine * directionColumn + sine * sideColumn;
|
|
2362
|
+
const dy = cosine * directionRow + sine * sideRow;
|
|
2363
|
+
const distance2 = along + size * (0.8 + random() * 3.2);
|
|
2364
|
+
const radius = 0.45 + random() * Math.min(1.2, size * 0.22);
|
|
2365
|
+
const satelliteRow = frontRow + dy * distance2;
|
|
2366
|
+
const satelliteColumn = frontColumn + dx * distance2;
|
|
2367
|
+
depositBlob(context, satelliteRow, satelliteColumn, radius * Math.sqrt(stretch), radius, dx, dy, amount * 0.6, 0.08);
|
|
2368
|
+
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);
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
// src/film/drops.ts
|
|
2373
|
+
var GRAVITY2 = 9.8;
|
|
2374
|
+
var AIR_DRAG = 0.25;
|
|
2375
|
+
var MIN_SIZE_CELLS = 0.6;
|
|
2376
|
+
function createDropPool(layout, context) {
|
|
2377
|
+
const capacity = layout.rules.dropCapacity;
|
|
2378
|
+
let cursor = 0;
|
|
2379
|
+
const state = {
|
|
2380
|
+
capacity,
|
|
2381
|
+
get launched() {
|
|
2382
|
+
return cursor;
|
|
2383
|
+
},
|
|
2384
|
+
active: new Uint8Array(capacity),
|
|
2385
|
+
position: new Float64Array(capacity * 3),
|
|
2386
|
+
velocity: new Float64Array(capacity * 3),
|
|
2387
|
+
radius: new Float64Array(capacity)
|
|
2388
|
+
};
|
|
2389
|
+
const { active, position, velocity, radius } = state;
|
|
2390
|
+
const cellMillimetres = layout.cellSize * 1e3;
|
|
2391
|
+
const previous = [0, 0, 0];
|
|
2392
|
+
const point = [0, 0, 0];
|
|
2393
|
+
const speed = [0, 0, 0];
|
|
2394
|
+
function land(region, dropRadius) {
|
|
2395
|
+
const profile = context.profile();
|
|
2396
|
+
const impact = layout.impact(region, point, speed);
|
|
2397
|
+
const [alongColumn, alongRow] = impact.along;
|
|
2398
|
+
const travel = Math.hypot(alongColumn, alongRow) || 1e-6;
|
|
2399
|
+
const sinAngle = Math.abs(impact.normalSpeed) / Math.hypot(travel, impact.normalSpeed);
|
|
2400
|
+
const volume = 4 / 3 * Math.PI * dropRadius * dropRadius * dropRadius / (cellMillimetres * cellMillimetres) * (dropRadius > 3 ? 7 : 4) * profile.volume;
|
|
2401
|
+
const totalSpeed = Math.sqrt(speed[0] * speed[0] + speed[1] * speed[1] + speed[2] * speed[2]);
|
|
2402
|
+
const size = Math.max(MIN_SIZE_CELLS, dropRadius * 2 / cellMillimetres * (1 + 0.2 * totalSpeed));
|
|
2403
|
+
const amount = Math.min(profile.maxAmount, Math.max(0.25, volume / (Math.PI * size * size * 1.6 / (sinAngle < 0.3 ? 0.6 : 1))));
|
|
2404
|
+
if (profile.landsAsBlob) {
|
|
2405
|
+
depositBlob(context, impact.row, impact.column, size * 1.3, size * 1.1, alongColumn / travel, alongRow / travel, amount, 0.2);
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2408
|
+
depositSplat(context, impact.row, impact.column, size, alongColumn / travel, alongRow / travel, sinAngle, amount);
|
|
2409
|
+
}
|
|
2410
|
+
return {
|
|
2411
|
+
state,
|
|
2412
|
+
launch(origin, direction, dropRadius) {
|
|
2413
|
+
const slot = cursor++ % capacity;
|
|
2414
|
+
const offset = slot * 3;
|
|
2415
|
+
active[slot] = 1;
|
|
2416
|
+
position[offset] = origin[0];
|
|
2417
|
+
position[offset + 1] = origin[1];
|
|
2418
|
+
position[offset + 2] = origin[2];
|
|
2419
|
+
velocity[offset] = direction[0];
|
|
2420
|
+
velocity[offset + 1] = direction[1];
|
|
2421
|
+
velocity[offset + 2] = direction[2];
|
|
2422
|
+
radius[slot] = dropRadius;
|
|
2423
|
+
},
|
|
2424
|
+
update(dt) {
|
|
2425
|
+
for (let slot = 0; slot < capacity; slot++) {
|
|
2426
|
+
if (!active[slot]) continue;
|
|
2427
|
+
const offset = slot * 3;
|
|
2428
|
+
previous[0] = position[offset];
|
|
2429
|
+
previous[1] = position[offset + 1];
|
|
2430
|
+
previous[2] = position[offset + 2];
|
|
2431
|
+
velocity[offset + 1] -= GRAVITY2 * dt;
|
|
2432
|
+
const drag = 1 - dt * AIR_DRAG;
|
|
2433
|
+
velocity[offset] *= drag;
|
|
2434
|
+
velocity[offset + 1] *= drag;
|
|
2435
|
+
velocity[offset + 2] *= drag;
|
|
2436
|
+
point[0] = position[offset] + velocity[offset] * dt;
|
|
2437
|
+
point[1] = position[offset + 1] + velocity[offset + 1] * dt;
|
|
2438
|
+
point[2] = position[offset + 2] + velocity[offset + 2] * dt;
|
|
2439
|
+
const region = layout.collide(previous, point);
|
|
2440
|
+
position[offset] = point[0];
|
|
2441
|
+
position[offset + 1] = point[1];
|
|
2442
|
+
position[offset + 2] = point[2];
|
|
2443
|
+
if (region >= 0) {
|
|
2444
|
+
if (layout.contains(point)) {
|
|
2445
|
+
speed[0] = velocity[offset];
|
|
2446
|
+
speed[1] = velocity[offset + 1];
|
|
2447
|
+
speed[2] = velocity[offset + 2];
|
|
2448
|
+
land(region, radius[slot]);
|
|
2449
|
+
}
|
|
2450
|
+
active[slot] = 0;
|
|
2451
|
+
continue;
|
|
2452
|
+
}
|
|
2453
|
+
if (layout.escaped(point)) active[slot] = 0;
|
|
2454
|
+
}
|
|
2455
|
+
},
|
|
2456
|
+
clear() {
|
|
2457
|
+
active.fill(0);
|
|
2458
|
+
}
|
|
2459
|
+
};
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
// src/film/simulate.ts
|
|
2463
|
+
var RAIN_SPREAD = 0.5;
|
|
2464
|
+
var BOUNDS_MARGIN = 8;
|
|
2465
|
+
function createCpuSimulator(field, layout, profileOf, options) {
|
|
2466
|
+
const { width, height, thickness, dryness, dampness } = field;
|
|
2467
|
+
const { bed, pin: pinning, vertical } = layout;
|
|
2468
|
+
const delta = new Float32Array(width * height);
|
|
2469
|
+
const bounds = { firstRow: 0, lastRow: -1, firstColumn: 0, lastColumn: -1 };
|
|
2470
|
+
let accumulated = 0;
|
|
2471
|
+
function refreshBounds() {
|
|
2472
|
+
let firstRow = height;
|
|
2473
|
+
let lastRow = -1;
|
|
2474
|
+
let firstColumn = width;
|
|
2475
|
+
let lastColumn = -1;
|
|
2476
|
+
for (let row = 0; row < height; row++) {
|
|
2477
|
+
const offset = row * width;
|
|
2478
|
+
let isWet = false;
|
|
2479
|
+
for (let column = 0; column < width; column++) {
|
|
2480
|
+
if (thickness[offset + column] > 0) {
|
|
2481
|
+
isWet = true;
|
|
2482
|
+
if (column < firstColumn) firstColumn = column;
|
|
2483
|
+
if (column > lastColumn) lastColumn = column;
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
if (isWet) {
|
|
2487
|
+
if (row < firstRow) firstRow = row;
|
|
2488
|
+
lastRow = row;
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
if (lastRow < 0) {
|
|
2492
|
+
bounds.firstRow = 0;
|
|
2493
|
+
bounds.lastRow = -1;
|
|
2494
|
+
bounds.firstColumn = 0;
|
|
2495
|
+
bounds.lastColumn = -1;
|
|
2496
|
+
return;
|
|
2497
|
+
}
|
|
2498
|
+
bounds.firstRow = Math.max(0, firstRow - BOUNDS_MARGIN);
|
|
2499
|
+
bounds.lastRow = Math.min(height - 1, lastRow + BOUNDS_MARGIN);
|
|
2500
|
+
bounds.firstColumn = Math.max(0, firstColumn - BOUNDS_MARGIN);
|
|
2501
|
+
bounds.lastColumn = Math.min(width - 1, lastColumn + BOUNDS_MARGIN);
|
|
2502
|
+
}
|
|
2503
|
+
const mobility = (index) => {
|
|
2504
|
+
const dry = dryness[index];
|
|
2505
|
+
return dry < 0.25 ? 1 : dry > 0.6 ? 0 : 1 - (dry - 0.25) / 0.35;
|
|
2506
|
+
};
|
|
2507
|
+
function flowStep() {
|
|
2508
|
+
const { firstRow, lastRow, firstColumn, lastColumn } = bounds;
|
|
2509
|
+
if (lastRow < 0) return;
|
|
2510
|
+
const profile = profileOf();
|
|
2511
|
+
const flow = profile.flow * (1 + RAIN_SPREAD * options.weather().rain);
|
|
2512
|
+
const referenceSquared = profile.referenceThickness * profile.referenceThickness;
|
|
2513
|
+
const slopeCap = profile.slopeCap;
|
|
2514
|
+
const pinLevel = profile.pin;
|
|
2515
|
+
const front = profile.front;
|
|
2516
|
+
for (let row = firstRow; row <= lastRow; row++) {
|
|
2517
|
+
const offset = row * width;
|
|
2518
|
+
for (let column = firstColumn; column <= lastColumn; column++) delta[offset + column] = 0;
|
|
2519
|
+
}
|
|
2520
|
+
for (let row = firstRow; row <= lastRow; row++) {
|
|
2521
|
+
const offset = row * width;
|
|
2522
|
+
for (let column = firstColumn; column <= lastColumn; column++) {
|
|
2523
|
+
const index = offset + column;
|
|
2524
|
+
const own = thickness[index];
|
|
2525
|
+
for (let edge = 0; edge < 2; edge++) {
|
|
2526
|
+
let neighbour;
|
|
2527
|
+
if (edge === 0) {
|
|
2528
|
+
if (column >= lastColumn) continue;
|
|
2529
|
+
neighbour = index + 1;
|
|
2530
|
+
} else {
|
|
2531
|
+
if (row >= lastRow) continue;
|
|
2532
|
+
neighbour = index + width;
|
|
2533
|
+
}
|
|
2534
|
+
const other = thickness[neighbour];
|
|
2535
|
+
if (own < 5e-3 && other < 5e-3) continue;
|
|
2536
|
+
let bedDrop = bed[index] - bed[neighbour];
|
|
2537
|
+
if (bedDrop > slopeCap) bedDrop = slopeCap;
|
|
2538
|
+
else if (bedDrop < -slopeCap) bedDrop = -slopeCap;
|
|
2539
|
+
let head = bedDrop + own - other;
|
|
2540
|
+
let source = index;
|
|
2541
|
+
let destination = neighbour;
|
|
2542
|
+
if (head < 0) {
|
|
2543
|
+
head = -head;
|
|
2544
|
+
source = neighbour;
|
|
2545
|
+
destination = index;
|
|
2546
|
+
}
|
|
2547
|
+
const sourceThickness = thickness[source];
|
|
2548
|
+
const verticalFactor = vertical[source];
|
|
2549
|
+
const pinThreshold = pinLevel * pinning[source] * (verticalFactor < 1 ? 0.5 : 1);
|
|
2550
|
+
if (sourceThickness <= pinThreshold) continue;
|
|
2551
|
+
if (thickness[destination] < 0.03 && sourceThickness < front * pinning[source] * verticalFactor) continue;
|
|
2552
|
+
const mobile = mobility(source);
|
|
2553
|
+
if (mobile <= 0) continue;
|
|
2554
|
+
let moved = flow * mobile * (sourceThickness * sourceThickness / (sourceThickness * sourceThickness + referenceSquared)) * head * (verticalFactor < 1 ? edge === 0 ? 0.22 : 1 : 4);
|
|
2555
|
+
const limit = (sourceThickness - pinThreshold) * 0.24;
|
|
2556
|
+
if (moved > limit) moved = limit;
|
|
2557
|
+
delta[source] -= moved;
|
|
2558
|
+
delta[destination] += moved;
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
for (let row = firstRow; row <= lastRow; row++) {
|
|
2563
|
+
const offset = row * width;
|
|
2564
|
+
for (let column = firstColumn; column <= lastColumn; column++) {
|
|
2565
|
+
const index = offset + column;
|
|
2566
|
+
let next = thickness[index] + delta[index];
|
|
2567
|
+
if (next < 1e-4) next = 0;
|
|
2568
|
+
else dampness[index] = 1;
|
|
2569
|
+
thickness[index] = next;
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
function age(dryingSeconds) {
|
|
2574
|
+
const { firstRow, lastRow, firstColumn, lastColumn } = bounds;
|
|
2575
|
+
if (lastRow < 0) return;
|
|
2576
|
+
const profile = profileOf();
|
|
2577
|
+
const rainScale = 1 - options.weather().rain;
|
|
2578
|
+
const seconds = dryingSeconds * rainScale;
|
|
2579
|
+
for (let row = firstRow; row <= lastRow; row++) {
|
|
2580
|
+
const offset = row * width;
|
|
2581
|
+
for (let column = firstColumn; column <= lastColumn; column++) {
|
|
2582
|
+
const index = offset + column;
|
|
2583
|
+
const own = thickness[index];
|
|
2584
|
+
if (own > 0) {
|
|
2585
|
+
const thin = 1 - Math.min(1, own / 1.6);
|
|
2586
|
+
if (profile.dryTime > 0 && dryness[index] < 1) dryness[index] = Math.min(1, dryness[index] + seconds / profile.dryTime * (0.45 + 1.7 * thin));
|
|
2587
|
+
if (profile.evaporation > 0) {
|
|
2588
|
+
const next = own - profile.evaporation * seconds * (0.5 + 1.5 * thin);
|
|
2589
|
+
thickness[index] = next > 4e-3 ? next : 0;
|
|
2590
|
+
}
|
|
2591
|
+
} else if (dampness[index] > 0) {
|
|
2592
|
+
dampness[index] = Math.max(0, dampness[index] - seconds / (profile.evaporation > 0 ? 25 : 1e9));
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
return {
|
|
2598
|
+
bounds,
|
|
2599
|
+
refreshBounds,
|
|
2600
|
+
advance({ dt, dryingSeconds }) {
|
|
2601
|
+
accumulated += dt;
|
|
2602
|
+
let steps = 0;
|
|
2603
|
+
refreshBounds();
|
|
2604
|
+
while (accumulated >= 1 / options.stepRate && steps < options.maxSteps) {
|
|
2605
|
+
flowStep();
|
|
2606
|
+
accumulated -= 1 / options.stepRate;
|
|
2607
|
+
steps++;
|
|
2608
|
+
}
|
|
2609
|
+
if (steps === options.maxSteps) accumulated = 0;
|
|
2610
|
+
age(dryingSeconds);
|
|
2611
|
+
return steps;
|
|
2612
|
+
},
|
|
2613
|
+
resetClock() {
|
|
2614
|
+
accumulated = 0;
|
|
2615
|
+
}
|
|
2616
|
+
};
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
// src/film/sources.ts
|
|
2620
|
+
var range = (random, from, to) => from + random() * (to - from);
|
|
2621
|
+
function updateDrip(drip, bead, layout, random, launch, dt) {
|
|
2622
|
+
if (!drip) {
|
|
2623
|
+
bead.visible = false;
|
|
2624
|
+
return;
|
|
2625
|
+
}
|
|
2626
|
+
drip.elapsed += dt;
|
|
2627
|
+
const swell = Math.min(1, drip.elapsed / drip.every);
|
|
2628
|
+
const radius = drip.radius * (0.35 + 0.65 * swell);
|
|
2629
|
+
const isWorkshop = layout.rules.dripStyle === "workshop";
|
|
2630
|
+
bead.visible = true;
|
|
2631
|
+
bead.swell = swell;
|
|
2632
|
+
bead.radius = radius;
|
|
2633
|
+
bead.position[0] = drip.position[0];
|
|
2634
|
+
bead.position[1] = drip.position[1] + (isWorkshop ? -radius * 12e-4 * (1 + swell * 0.7) : -radius * 12e-4);
|
|
2635
|
+
bead.position[2] = drip.position[2];
|
|
2636
|
+
if (drip.elapsed < drip.every) return;
|
|
2637
|
+
const origin = [bead.position[0], bead.position[1] + -radius * 1e-3, bead.position[2]];
|
|
2638
|
+
if (isWorkshop) {
|
|
2639
|
+
const sideX2 = range(random, -0.025, 0.025);
|
|
2640
|
+
const sideZ2 = range(random, -0.025, 0.025);
|
|
2641
|
+
launch(origin, [sideX2, -0.2, sideZ2], drip.radius * range(random, 0.85, 1.15));
|
|
2642
|
+
drip.elapsed = 0;
|
|
2643
|
+
drip.every = drip.base * range(random, 0.75, 1.25);
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
const sideX = (random() - 0.5) * 0.05;
|
|
2647
|
+
const sideZ = (random() - 0.5) * 0.05;
|
|
2648
|
+
launch(origin, [sideX, -0.2, sideZ], drip.radius * (0.85 + random() * 0.3));
|
|
2649
|
+
drip.elapsed = 0;
|
|
2650
|
+
drip.every = drip.base * (0.75 + random() * 0.5);
|
|
2651
|
+
}
|
|
2652
|
+
function updateSources(sources, context, dt) {
|
|
2653
|
+
const random = context.random;
|
|
2654
|
+
for (const source of sources) {
|
|
2655
|
+
source.elapsed += dt;
|
|
2656
|
+
if (source.elapsed < 0 || source.elapsed > source.duration) continue;
|
|
2657
|
+
const pulse = 0.6 + 0.4 * Math.max(0, Math.sin(source.elapsed * 5.2));
|
|
2658
|
+
const amount = source.rate * dt * pulse;
|
|
2659
|
+
for (let part = 0; part < 3; part++) {
|
|
2660
|
+
const angle = random() * 6.28;
|
|
2661
|
+
const distance2 = random() * source.radius;
|
|
2662
|
+
depositCell(context, source.row + Math.sin(angle) * distance2, source.column + Math.cos(angle) * distance2, amount / 3);
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
function updateDrags(drags, context, layout, launch, dt) {
|
|
2667
|
+
const random = context.random;
|
|
2668
|
+
for (const drag of drags) {
|
|
2669
|
+
if (drag.done) continue;
|
|
2670
|
+
drag.elapsed += dt;
|
|
2671
|
+
const progress = Math.min(1, drag.elapsed / drag.duration);
|
|
2672
|
+
const here = drag.path(progress);
|
|
2673
|
+
const ahead = drag.path(Math.min(1, progress + 0.01));
|
|
2674
|
+
let directionColumn = ahead.column - here.column;
|
|
2675
|
+
let directionRow = ahead.row - here.row;
|
|
2676
|
+
const length2 = Math.hypot(directionColumn, directionRow) || 1;
|
|
2677
|
+
directionColumn /= length2;
|
|
2678
|
+
directionRow /= length2;
|
|
2679
|
+
const sideColumn = -directionRow;
|
|
2680
|
+
const sideRow = directionColumn;
|
|
2681
|
+
const load = Math.pow(1 - progress, 1.3);
|
|
2682
|
+
for (let offset = -drag.width; offset <= drag.width; offset += 0.5) {
|
|
2683
|
+
const across = offset / drag.width;
|
|
2684
|
+
const stripe = drag.stripes[Math.round((offset + drag.width) * 2)];
|
|
2685
|
+
const edge = 1 - across * across;
|
|
2686
|
+
const amount = drag.amount * load * (0.25 + 0.75 * stripe) * edge;
|
|
2687
|
+
if (load < 0.45 && random() > load * 2.2) continue;
|
|
2688
|
+
depositCell(context, Math.round(here.row + sideRow * offset), Math.round(here.column + sideColumn * offset), amount * dt * 30);
|
|
2689
|
+
}
|
|
2690
|
+
if (drag.hand && random() < dt * 14 * load + 0.02) {
|
|
2691
|
+
const world = layout.cellToWorld(here.row, here.column);
|
|
2692
|
+
const origin = [world[0] + (random() - 0.5) * 0.06, drag.hand.height + random() * 0.3, world[2] + (random() - 0.5) * 0.06];
|
|
2693
|
+
const velocity = [directionColumn * drag.hand.speed, 0 * drag.hand.speed, directionRow * drag.hand.speed];
|
|
2694
|
+
velocity[0] += (random() - 0.5) * 0.3;
|
|
2695
|
+
velocity[1] += 0.2;
|
|
2696
|
+
velocity[2] += (random() - 0.5) * 0.3;
|
|
2697
|
+
launch(origin, velocity, 2 + random() * 2.2);
|
|
2698
|
+
}
|
|
2699
|
+
if (progress >= 1) drag.done = true;
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
// src/film/film.ts
|
|
2704
|
+
var DEFAULT_STEP_RATE = 180;
|
|
2705
|
+
var DEFAULT_MAX_STEPS = 6;
|
|
2706
|
+
function createFilm(options) {
|
|
2707
|
+
const { layout, random } = options;
|
|
2708
|
+
const cellCount = layout.width * layout.height;
|
|
2709
|
+
const field = {
|
|
2710
|
+
width: layout.width,
|
|
2711
|
+
height: layout.height,
|
|
2712
|
+
thickness: new Float32Array(cellCount),
|
|
2713
|
+
dryness: new Float32Array(cellCount),
|
|
2714
|
+
dampness: new Float32Array(cellCount)
|
|
2715
|
+
};
|
|
2716
|
+
let weatherState = { rain: 0 };
|
|
2717
|
+
let currentProfile = options.profile;
|
|
2718
|
+
const context = { field, random, profile: () => currentProfile };
|
|
2719
|
+
const drops = createDropPool(layout, context);
|
|
2720
|
+
const createSimulator = options.simulator ?? createCpuSimulator;
|
|
2721
|
+
const simulator = createSimulator(field, layout, () => currentProfile, {
|
|
2722
|
+
stepRate: options.stepRate ?? DEFAULT_STEP_RATE,
|
|
2723
|
+
maxSteps: options.maxSteps ?? DEFAULT_MAX_STEPS,
|
|
2724
|
+
weather: () => weatherState
|
|
2725
|
+
});
|
|
2726
|
+
const film = {
|
|
2727
|
+
layout,
|
|
2728
|
+
field,
|
|
2729
|
+
drops: drops.state,
|
|
2730
|
+
bead: { visible: false, position: [0, 0, 0], radius: 0, swell: 0 },
|
|
2731
|
+
simulator,
|
|
2732
|
+
get profile() {
|
|
2733
|
+
return currentProfile;
|
|
2734
|
+
},
|
|
2735
|
+
set profile(profile) {
|
|
2736
|
+
currentProfile = profile;
|
|
2737
|
+
},
|
|
2738
|
+
drip: void 0,
|
|
2739
|
+
sources: [],
|
|
2740
|
+
drags: [],
|
|
2741
|
+
launch: (position, velocity, radius) => drops.launch(position, velocity, radius),
|
|
2742
|
+
splat: (row, column, size, directionColumn, directionRow, sinAngle, amount) => depositSplat(context, row, column, size, directionColumn, directionRow, sinAngle, amount),
|
|
2743
|
+
blob: (row, column, along, across, directionColumn, directionRow, amount, roughness) => depositBlob(context, row, column, along, across, directionColumn, directionRow, amount, roughness),
|
|
2744
|
+
stroke: (fromRow, fromColumn, toRow, toColumn, startWidth, endWidth, amount) => depositStroke(context, fromRow, fromColumn, toRow, toColumn, startWidth, endWidth, amount),
|
|
2745
|
+
deposit: (row, column, amount) => depositCell(context, row, column, amount),
|
|
2746
|
+
update({ dt, dryingSeconds }) {
|
|
2747
|
+
drops.update(dt);
|
|
2748
|
+
updateDrip(film.drip, film.bead, layout, random, drops.launch, dt);
|
|
2749
|
+
updateSources(film.sources, context, dt);
|
|
2750
|
+
updateDrags(film.drags, context, layout, drops.launch, dt);
|
|
2751
|
+
simulator.advance({ dt, dryingSeconds });
|
|
2752
|
+
},
|
|
2753
|
+
setWeather(weather) {
|
|
2754
|
+
weatherState = { rain: Math.min(1, Math.max(0, weather.rain ?? weatherState.rain)) };
|
|
2755
|
+
},
|
|
2756
|
+
weather: () => ({ ...weatherState }),
|
|
2757
|
+
clear() {
|
|
2758
|
+
field.thickness.fill(0);
|
|
2759
|
+
field.dryness.fill(0);
|
|
2760
|
+
field.dampness.fill(0);
|
|
2761
|
+
drops.clear();
|
|
2762
|
+
film.drip = void 0;
|
|
2763
|
+
film.bead.visible = false;
|
|
2764
|
+
film.sources.length = 0;
|
|
2765
|
+
film.drags.length = 0;
|
|
2766
|
+
simulator.refreshBounds();
|
|
2767
|
+
},
|
|
2768
|
+
wetCells: () => wetCellCount(field)
|
|
2769
|
+
};
|
|
2770
|
+
return film;
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
// src/film/random.ts
|
|
2774
|
+
function createFilmLcg(seed) {
|
|
2775
|
+
const lcg = (() => {
|
|
2776
|
+
lcg.seed = lcg.seed * 1664525 + 1013904223 >>> 0;
|
|
2777
|
+
return lcg.seed / 4294967296;
|
|
2778
|
+
});
|
|
2779
|
+
lcg.seed = seed >>> 0;
|
|
2780
|
+
return lcg;
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2783
|
+
// src/film/noise.ts
|
|
2784
|
+
function hashCell(x, y) {
|
|
2785
|
+
let value = x * 374761393 + y * 668265263 | 0;
|
|
2786
|
+
value = (value ^ value >>> 13) * 1274126177 | 0;
|
|
2787
|
+
return ((value ^ value >>> 16) >>> 0) / 4294967296;
|
|
2788
|
+
}
|
|
2789
|
+
function valueNoise(x, y) {
|
|
2790
|
+
const xi = Math.floor(x);
|
|
2791
|
+
const yi = Math.floor(y);
|
|
2792
|
+
const xf = x - xi;
|
|
2793
|
+
const yf = y - yi;
|
|
2794
|
+
const u = xf * xf * (3 - 2 * xf);
|
|
2795
|
+
const v = yf * yf * (3 - 2 * yf);
|
|
2796
|
+
const a = hashCell(xi, yi);
|
|
2797
|
+
const b = hashCell(xi + 1, yi);
|
|
2798
|
+
const c = hashCell(xi, yi + 1);
|
|
2799
|
+
const d = hashCell(xi + 1, yi + 1);
|
|
2800
|
+
return a + (b - a) * u + (c - a) * v + (a - b - c + d) * u * v;
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
// src/film/layout.ts
|
|
2804
|
+
var REGION_NAMES = ["wall", "sidewalk", "curb", "road"];
|
|
2805
|
+
var dot3 = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
2806
|
+
function impactOf(region, position, velocity, cellSize) {
|
|
2807
|
+
const local = [position[0] - region.origin[0], position[1] - region.origin[1], position[2] - region.origin[2]];
|
|
2808
|
+
return {
|
|
2809
|
+
row: region.firstRow + dot3(local, region.rowAxis) / cellSize,
|
|
2810
|
+
column: dot3(local, region.columnAxis) / cellSize,
|
|
2811
|
+
along: [dot3(velocity, region.columnAxis), dot3(velocity, region.rowAxis)],
|
|
2812
|
+
normalSpeed: -dot3(velocity, region.normal)
|
|
2813
|
+
};
|
|
2814
|
+
}
|
|
2815
|
+
function cellToWorldOf(regions, cellSize, row, column) {
|
|
2816
|
+
let region = regions[regions.length - 1];
|
|
2817
|
+
for (const candidate of regions) {
|
|
2818
|
+
if (row < candidate.firstRow + candidate.rowCount) {
|
|
2819
|
+
region = candidate;
|
|
2820
|
+
break;
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
const across = column * cellSize;
|
|
2824
|
+
const down = (row - region.firstRow) * cellSize;
|
|
2825
|
+
return [
|
|
2826
|
+
region.origin[0] + region.columnAxis[0] * across + region.rowAxis[0] * down,
|
|
2827
|
+
region.origin[1] + region.columnAxis[1] * across + region.rowAxis[1] * down,
|
|
2828
|
+
region.origin[2] + region.columnAxis[2] * across + region.rowAxis[2] * down
|
|
2829
|
+
];
|
|
2830
|
+
}
|
|
2831
|
+
function createFoldedLayout(params) {
|
|
2832
|
+
const { columns, cellSize, originX, wallRows, sidewalkRows, curbRows, roadRows, wallTopY, sidewalkY, roadY, wallZ, rules } = params;
|
|
2833
|
+
const curbStep = params.curbStep ?? (sidewalkY - roadY) / curbRows;
|
|
2834
|
+
const height = wallRows + sidewalkRows + curbRows + roadRows;
|
|
2835
|
+
const cellCount = columns * height;
|
|
2836
|
+
const bed = new Float32Array(cellCount);
|
|
2837
|
+
const pin = new Float32Array(cellCount);
|
|
2838
|
+
const vertical = new Float32Array(cellCount);
|
|
2839
|
+
const sidewalkEnd = wallRows + sidewalkRows;
|
|
2840
|
+
const curbEnd = sidewalkEnd + curbRows;
|
|
2841
|
+
const regionOfRow = (row) => row < wallRows ? 0 : row < sidewalkEnd ? 1 : row < curbEnd ? 2 : 3;
|
|
2842
|
+
for (let row = 0; row < height; row++) {
|
|
2843
|
+
for (let column = 0; column < columns; column++) {
|
|
2844
|
+
const index = row * columns + column;
|
|
2845
|
+
const region = regionOfRow(row);
|
|
2846
|
+
const x = originX + (column + 0.5) * cellSize;
|
|
2847
|
+
let bedHeight;
|
|
2848
|
+
let roughness = 0.2;
|
|
2849
|
+
if (region === 0) {
|
|
2850
|
+
bedHeight = (wallTopY - (row + 0.5) * cellSize) * 1e3;
|
|
2851
|
+
roughness = 0.3;
|
|
2852
|
+
} else if (region === 1) {
|
|
2853
|
+
const z = (row - wallRows + 0.5) * cellSize;
|
|
2854
|
+
bedHeight = sidewalkY * 1e3 - z * 6;
|
|
2855
|
+
const jointX = Math.abs(((x - originX) % 0.5 + 0.5) % 0.5 - 0.25);
|
|
2856
|
+
const jointZ = Math.abs((z % 0.5 + 0.5) % 0.5 - 0.25);
|
|
2857
|
+
if (jointX > 0.25 - cellSize * 0.6 || jointZ > 0.25 - cellSize * 0.6) bedHeight -= 0.9;
|
|
2858
|
+
roughness = 0.18;
|
|
2859
|
+
} else if (region === 2) {
|
|
2860
|
+
bedHeight = sidewalkY * 1e3 - (row - sidewalkEnd + 0.5) * curbStep * 1e3;
|
|
2861
|
+
} else {
|
|
2862
|
+
const z = (row - curbEnd + 0.5) * cellSize;
|
|
2863
|
+
bedHeight = roadY * 1e3 + z * 14 - (x - params.roadSlopeAnchorX) * 5;
|
|
2864
|
+
roughness = 0.55;
|
|
2865
|
+
}
|
|
2866
|
+
const noise = valueNoise(column * 0.35, row * 0.35) * 0.6 + valueNoise(column * 1.3, row * 1.3) * 0.4;
|
|
2867
|
+
bed[index] = bedHeight + (noise - 0.5) * roughness * 2;
|
|
2868
|
+
vertical[index] = region === 0 || region === 2 ? 0.95 : 1;
|
|
2869
|
+
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);
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
const curbZ = wallZ + sidewalkRows * cellSize;
|
|
2873
|
+
const roadEndZ = curbZ + roadRows * cellSize;
|
|
2874
|
+
const right = [1, 0, 0];
|
|
2875
|
+
const regions = [
|
|
2876
|
+
{ id: REGION_NAMES[0], firstRow: 0, rowCount: wallRows, origin: [originX, wallTopY, wallZ], columnAxis: right, rowAxis: [0, -1, 0], normal: [0, 0, 1], isVertical: true },
|
|
2877
|
+
{ id: REGION_NAMES[1], firstRow: wallRows, rowCount: sidewalkRows, origin: [originX, sidewalkY, wallZ], columnAxis: right, rowAxis: [0, 0, 1], normal: [0, 1, 0], isVertical: false },
|
|
2878
|
+
{ id: REGION_NAMES[2], firstRow: sidewalkEnd, rowCount: curbRows, origin: [originX, sidewalkY, curbZ], columnAxis: right, rowAxis: [0, -1, 0], normal: [0, 0, 1], isVertical: true },
|
|
2879
|
+
{ id: REGION_NAMES[3], firstRow: curbEnd, rowCount: roadRows, origin: [originX, roadY, curbZ], columnAxis: right, rowAxis: [0, 0, 1], normal: [0, 1, 0], isVertical: false }
|
|
2880
|
+
];
|
|
2881
|
+
const maxX = originX + columns * cellSize;
|
|
2882
|
+
return {
|
|
2883
|
+
id: params.id,
|
|
2884
|
+
width: columns,
|
|
2885
|
+
height,
|
|
2886
|
+
cellSize,
|
|
2887
|
+
bed,
|
|
2888
|
+
pin,
|
|
2889
|
+
vertical,
|
|
2890
|
+
regions,
|
|
2891
|
+
rules,
|
|
2892
|
+
cellToWorld: (row, column) => cellToWorldOf(regions, cellSize, row, column),
|
|
2893
|
+
collide(previous, position) {
|
|
2894
|
+
if (position[2] <= wallZ && position[1] > sidewalkY) {
|
|
2895
|
+
position[2] = wallZ;
|
|
2896
|
+
return 0;
|
|
2897
|
+
}
|
|
2898
|
+
if (position[2] < curbZ && (!rules.guardBehindWall || position[2] >= wallZ) && position[1] <= sidewalkY) {
|
|
2899
|
+
position[1] = sidewalkY;
|
|
2900
|
+
return 1;
|
|
2901
|
+
}
|
|
2902
|
+
if (rules.curbCatch && position[2] >= curbZ && previous[2] < curbZ && position[1] < sidewalkY) {
|
|
2903
|
+
position[2] = curbZ;
|
|
2904
|
+
return 2;
|
|
2905
|
+
}
|
|
2906
|
+
if (position[2] >= curbZ && position[1] <= roadY) {
|
|
2907
|
+
position[1] = roadY;
|
|
2908
|
+
return 3;
|
|
2909
|
+
}
|
|
2910
|
+
return -1;
|
|
2911
|
+
},
|
|
2912
|
+
impact: (region, position, velocity) => impactOf(regions[region], position, velocity, cellSize),
|
|
2913
|
+
contains: (position) => position[0] > originX && position[0] < maxX && (!rules.clipRoadEnd || position[2] < roadEndZ),
|
|
2914
|
+
escaped(position) {
|
|
2915
|
+
if (position[1] < -1) return true;
|
|
2916
|
+
const box = rules.escapeBox;
|
|
2917
|
+
return box !== void 0 && (position[0] < box.minX || position[0] > box.maxX || position[2] > box.maxZ);
|
|
2918
|
+
}
|
|
2919
|
+
};
|
|
2920
|
+
}
|
|
2921
|
+
var PADRAO_CELL = 0.0125;
|
|
2922
|
+
var PADRAO_WALL_Z = 0;
|
|
2923
|
+
var PADRAO_LAYOUT = createFoldedLayout({
|
|
2924
|
+
id: "padrao",
|
|
2925
|
+
columns: 256,
|
|
2926
|
+
cellSize: PADRAO_CELL,
|
|
2927
|
+
originX: -1.6,
|
|
2928
|
+
wallRows: 144,
|
|
2929
|
+
sidewalkRows: 128,
|
|
2930
|
+
curbRows: 12,
|
|
2931
|
+
roadRows: 112,
|
|
2932
|
+
wallTopY: 1.95,
|
|
2933
|
+
sidewalkY: 0.15,
|
|
2934
|
+
roadY: 0,
|
|
2935
|
+
wallZ: PADRAO_WALL_Z,
|
|
2936
|
+
curbStep: PADRAO_CELL,
|
|
2937
|
+
roadSlopeAnchorX: 0,
|
|
2938
|
+
rules: {
|
|
2939
|
+
dropCapacity: 600,
|
|
2940
|
+
curbCatch: true,
|
|
2941
|
+
guardBehindWall: true,
|
|
2942
|
+
clipRoadEnd: true,
|
|
2943
|
+
escapeBox: { minX: -3, maxX: 3, maxZ: PADRAO_WALL_Z + 128 * PADRAO_CELL + 112 * PADRAO_CELL + 1 },
|
|
2944
|
+
dripStyle: "padrao"
|
|
2945
|
+
}
|
|
2946
|
+
});
|
|
2947
|
+
var WORKSHOP_CELL = 0.0125;
|
|
2948
|
+
var WORKSHOP_LAYOUT = createFoldedLayout({
|
|
2949
|
+
id: "workshop",
|
|
2950
|
+
columns: 160,
|
|
2951
|
+
cellSize: WORKSHOP_CELL,
|
|
2952
|
+
originX: -2.2,
|
|
2953
|
+
wallRows: 160,
|
|
2954
|
+
sidewalkRows: 128,
|
|
2955
|
+
curbRows: 9,
|
|
2956
|
+
roadRows: 151,
|
|
2957
|
+
wallTopY: 2.12,
|
|
2958
|
+
sidewalkY: 0.1235,
|
|
2959
|
+
roadY: 9e-3,
|
|
2960
|
+
wallZ: -4.497,
|
|
2961
|
+
roadSlopeAnchorX: -1.2,
|
|
2962
|
+
rules: { dropCapacity: 400, curbCatch: false, guardBehindWall: false, clipRoadEnd: false, dripStyle: "workshop" }
|
|
2963
|
+
});
|
|
2964
|
+
|
|
2965
|
+
// src/film/surface-layout.ts
|
|
2966
|
+
var DEFAULT_COLUMNS = 160;
|
|
2967
|
+
var DEFAULT_ROWS = 160;
|
|
2968
|
+
var DEFAULT_CELL = 0.0125;
|
|
2969
|
+
var DEFAULT_WALL_ROWS = 48;
|
|
2970
|
+
var DEFAULT_CURB_ROWS = 12;
|
|
2971
|
+
var RULES = { dropCapacity: 160, curbCatch: false, guardBehindWall: false, clipRoadEnd: false, dripStyle: "padrao" };
|
|
2972
|
+
var along3 = (origin, axis, distance2) => add(origin, scale(axis, distance2));
|
|
2973
|
+
var reject = (vector, axis) => sub(vector, scale(axis, dot(vector, axis)));
|
|
2974
|
+
function layoutFromSurfaces(options) {
|
|
2975
|
+
const columns = options.columns ?? DEFAULT_COLUMNS;
|
|
2976
|
+
const totalRows = options.rows ?? DEFAULT_ROWS;
|
|
2977
|
+
const cell = options.cellSize ?? DEFAULT_CELL;
|
|
2978
|
+
const floorBasis = makeBasis(options.floor.normal, options.floor.tangent);
|
|
2979
|
+
const floorNormal = floorBasis.normal;
|
|
2980
|
+
const projected = sub(options.center, scale(floorNormal, dot(sub(options.center, options.floor.point), floorNormal)));
|
|
2981
|
+
const wallNormal = options.wall ? normalize(reject(options.wall.normal, floorNormal), floorBasis.t2) : void 0;
|
|
2982
|
+
const awayAxis = wallNormal ?? floorBasis.t2;
|
|
2983
|
+
const columnAxis = wallNormal ? normalize(cross(floorNormal, wallNormal), floorBasis.t1) : floorBasis.t1;
|
|
2984
|
+
const wallRows = options.wall ? Math.min(totalRows - 1, options.wallRows ?? DEFAULT_WALL_ROWS) : 0;
|
|
2985
|
+
let fold = projected;
|
|
2986
|
+
if (options.wall && wallNormal) {
|
|
2987
|
+
const toWall = dot(sub(projected, options.wall.point), wallNormal) / (dot(awayAxis, wallNormal) || 1);
|
|
2988
|
+
fold = sub(projected, scale(awayAxis, toWall));
|
|
2989
|
+
} else {
|
|
2990
|
+
fold = along3(projected, awayAxis, -(totalRows * cell) / 2);
|
|
2991
|
+
}
|
|
2992
|
+
const lateral = dot(sub(projected, fold), columnAxis) - columns * cell / 2;
|
|
2993
|
+
const base = along3(fold, columnAxis, lateral);
|
|
2994
|
+
const strips = [];
|
|
2995
|
+
const roughness = 0.18 + 0.4 * options.floor.porosity;
|
|
2996
|
+
if (options.wall && wallNormal) {
|
|
2997
|
+
strips.push({ id: "wall", rows: wallRows, origin: along3(base, floorNormal, wallRows * cell), rowAxis: scale(floorNormal, -1), normal: wallNormal, isVertical: true, roughness: 0.3 });
|
|
2998
|
+
}
|
|
2999
|
+
const lowerRows = totalRows - wallRows;
|
|
3000
|
+
const dropMetres = options.curb ? Math.max(0, dot(sub(options.floor.point, options.curb.point), floorNormal)) : 0;
|
|
3001
|
+
const curbRows = options.curb ? Math.min(options.curbRows ?? DEFAULT_CURB_ROWS, Math.max(1, Math.ceil(dropMetres / cell)), lowerRows - 2) : 0;
|
|
3002
|
+
const floorRows = options.curb ? Math.max(1, Math.floor((lowerRows - curbRows) / 2)) : lowerRows;
|
|
3003
|
+
strips.push({ id: "floor", rows: floorRows, origin: base, rowAxis: awayAxis, normal: floorNormal, isVertical: false, roughness });
|
|
3004
|
+
if (options.curb) {
|
|
3005
|
+
const edge = along3(base, awayAxis, floorRows * cell);
|
|
3006
|
+
strips.push({ id: "curb", rows: curbRows, origin: edge, rowAxis: scale(floorNormal, -1), normal: awayAxis, isVertical: true, roughness: 0.2 });
|
|
3007
|
+
const lowerOrigin = along3(edge, floorNormal, -curbRows * cell);
|
|
3008
|
+
strips.push({ id: "low", rows: lowerRows - floorRows - curbRows, origin: lowerOrigin, rowAxis: awayAxis, normal: floorNormal, isVertical: false, roughness: 0.55 });
|
|
3009
|
+
}
|
|
3010
|
+
const regions = [];
|
|
3011
|
+
let firstRow = 0;
|
|
3012
|
+
for (const strip of strips) {
|
|
3013
|
+
regions.push({ id: strip.id, firstRow, rowCount: strip.rows, origin: strip.origin, columnAxis, rowAxis: strip.rowAxis, normal: strip.normal, isVertical: strip.isVertical });
|
|
3014
|
+
firstRow += strip.rows;
|
|
3015
|
+
}
|
|
3016
|
+
const height = firstRow;
|
|
3017
|
+
const bed = new Float32Array(columns * height);
|
|
3018
|
+
const pin = new Float32Array(columns * height);
|
|
3019
|
+
const vertical = new Float32Array(columns * height);
|
|
3020
|
+
regions.forEach((region, regionIndex) => {
|
|
3021
|
+
const strip = strips[regionIndex];
|
|
3022
|
+
for (let row = 0; row < region.rowCount; row++) {
|
|
3023
|
+
for (let column = 0; column < columns; column++) {
|
|
3024
|
+
const index = (region.firstRow + row) * columns + column;
|
|
3025
|
+
const centre = add(add(region.origin, scale(columnAxis, (column + 0.5) * cell)), scale(region.rowAxis, (row + 0.5) * cell));
|
|
3026
|
+
const noise = valueNoise(column * 0.35, (region.firstRow + row) * 0.35) * 0.6 + valueNoise(column * 1.3, (region.firstRow + row) * 1.3) * 0.4;
|
|
3027
|
+
bed[index] = centre[1] * 1e3 + (noise - 0.5) * strip.roughness * 2;
|
|
3028
|
+
vertical[index] = region.isVertical ? 0.95 : 1;
|
|
3029
|
+
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);
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
});
|
|
3033
|
+
const lowestY = Math.min(...regions.map((region) => region.origin[1] - (region.rowAxis[1] > 0 ? 0 : region.rowCount * cell * Math.abs(region.rowAxis[1])))) - 1;
|
|
3034
|
+
const width = columns * cell;
|
|
3035
|
+
const cellToWorld = (row, column) => {
|
|
3036
|
+
const region = regions.find((candidate) => row < candidate.firstRow + candidate.rowCount) ?? regions[regions.length - 1];
|
|
3037
|
+
return add(add(region.origin, scale(region.columnAxis, column * cell)), scale(region.rowAxis, (row - region.firstRow) * cell));
|
|
3038
|
+
};
|
|
3039
|
+
return {
|
|
3040
|
+
id: "surfaces",
|
|
3041
|
+
width: columns,
|
|
3042
|
+
height,
|
|
3043
|
+
cellSize: cell,
|
|
3044
|
+
bed,
|
|
3045
|
+
pin,
|
|
3046
|
+
vertical,
|
|
3047
|
+
regions,
|
|
3048
|
+
rules: RULES,
|
|
3049
|
+
cellToWorld,
|
|
3050
|
+
collide(previous, position) {
|
|
3051
|
+
for (let index = 0; index < regions.length; index++) {
|
|
3052
|
+
const region = regions[index];
|
|
3053
|
+
const before = dot(sub(previous, region.origin), region.normal);
|
|
3054
|
+
const after = dot(sub(position, region.origin), region.normal);
|
|
3055
|
+
if (before < 0 || after > 0) continue;
|
|
3056
|
+
const local = sub(position, region.origin);
|
|
3057
|
+
const sideways = dot(local, region.columnAxis);
|
|
3058
|
+
const downward = dot(local, region.rowAxis);
|
|
3059
|
+
if (sideways < 0 || sideways >= width || downward < 0 || downward >= region.rowCount * cell) continue;
|
|
3060
|
+
position[0] -= region.normal[0] * after;
|
|
3061
|
+
position[1] -= region.normal[1] * after;
|
|
3062
|
+
position[2] -= region.normal[2] * after;
|
|
3063
|
+
return index;
|
|
3064
|
+
}
|
|
3065
|
+
return -1;
|
|
3066
|
+
},
|
|
3067
|
+
impact(regionIndex, position, velocity) {
|
|
3068
|
+
const region = regions[regionIndex];
|
|
3069
|
+
const local = sub(position, region.origin);
|
|
3070
|
+
return {
|
|
3071
|
+
row: region.firstRow + dot(local, region.rowAxis) / cell,
|
|
3072
|
+
column: dot(local, region.columnAxis) / cell,
|
|
3073
|
+
along: [dot(velocity, region.columnAxis), dot(velocity, region.rowAxis)],
|
|
3074
|
+
normalSpeed: -dot(velocity, region.normal)
|
|
3075
|
+
};
|
|
3076
|
+
},
|
|
3077
|
+
contains: () => true,
|
|
3078
|
+
escaped: (position) => position[1] < lowestY
|
|
3079
|
+
};
|
|
3080
|
+
}
|
|
3081
|
+
|
|
3082
|
+
// src/data/film-profiles.json
|
|
3083
|
+
var film_profiles_default = [
|
|
3084
|
+
{
|
|
3085
|
+
id: "blood",
|
|
3086
|
+
look: "blood",
|
|
3087
|
+
flow: 0.05,
|
|
3088
|
+
pin: 0.33,
|
|
3089
|
+
front: 0.58,
|
|
3090
|
+
referenceThickness: 0.6,
|
|
3091
|
+
slopeCap: 1.1,
|
|
3092
|
+
dryTime: 600,
|
|
3093
|
+
evaporation: 0,
|
|
3094
|
+
opacityRate: 3.2,
|
|
3095
|
+
reflectance: 0.045,
|
|
3096
|
+
normalScale: 16,
|
|
3097
|
+
thin: 7997966,
|
|
3098
|
+
thick: 2753028,
|
|
3099
|
+
dry: 3874068,
|
|
3100
|
+
dryThin: 6960678,
|
|
3101
|
+
dropColor: 7997965,
|
|
3102
|
+
dropOpacity: 1,
|
|
3103
|
+
volume: 1,
|
|
3104
|
+
spikes: 1,
|
|
3105
|
+
landsAsBlob: false,
|
|
3106
|
+
maxAmount: 2.6,
|
|
3107
|
+
sources: {
|
|
3108
|
+
padrao: "F46",
|
|
3109
|
+
oficina: "W583"
|
|
3110
|
+
}
|
|
3111
|
+
},
|
|
3112
|
+
{
|
|
3113
|
+
id: "old_blood",
|
|
3114
|
+
look: "blood",
|
|
3115
|
+
flow: 0.032,
|
|
3116
|
+
pin: 0.42,
|
|
3117
|
+
front: 0.7,
|
|
3118
|
+
referenceThickness: 0.7,
|
|
3119
|
+
slopeCap: 1,
|
|
3120
|
+
dryTime: 360,
|
|
3121
|
+
evaporation: 0,
|
|
3122
|
+
opacityRate: 3.8,
|
|
3123
|
+
reflectance: 0.03,
|
|
3124
|
+
normalScale: 14,
|
|
3125
|
+
thin: 7085078,
|
|
3126
|
+
thick: 2888717,
|
|
3127
|
+
dry: 2758158,
|
|
3128
|
+
dryThin: 4860444,
|
|
3129
|
+
dropColor: 4854286,
|
|
3130
|
+
dropOpacity: 1,
|
|
3131
|
+
volume: 0.6,
|
|
3132
|
+
spikes: 1,
|
|
3133
|
+
landsAsBlob: false,
|
|
3134
|
+
maxAmount: 2.6,
|
|
3135
|
+
sources: {
|
|
3136
|
+
padrao: "F48"
|
|
3137
|
+
}
|
|
3138
|
+
},
|
|
3139
|
+
{
|
|
3140
|
+
id: "bile",
|
|
3141
|
+
look: "bile",
|
|
3142
|
+
flow: 0.02,
|
|
3143
|
+
pin: 0.8,
|
|
3144
|
+
front: 1.2,
|
|
3145
|
+
referenceThickness: 1.2,
|
|
3146
|
+
slopeCap: 0.9,
|
|
3147
|
+
dryTime: 900,
|
|
3148
|
+
evaporation: 0,
|
|
3149
|
+
opacityRate: 7,
|
|
3150
|
+
reflectance: 0.04,
|
|
3151
|
+
normalScale: 12,
|
|
3152
|
+
thin: 11121724,
|
|
3153
|
+
thick: 7174171,
|
|
3154
|
+
dry: 4080160,
|
|
3155
|
+
dryThin: 5659180,
|
|
3156
|
+
dropColor: 9083434,
|
|
3157
|
+
dropOpacity: 1,
|
|
3158
|
+
volume: 1.3,
|
|
3159
|
+
spikes: 0.3,
|
|
3160
|
+
landsAsBlob: false,
|
|
3161
|
+
maxAmount: 4,
|
|
3162
|
+
sources: {
|
|
3163
|
+
padrao: "F50",
|
|
3164
|
+
oficina: "W584 (spikes 0.3 W578, cap 4 W691)"
|
|
3165
|
+
}
|
|
3166
|
+
},
|
|
3167
|
+
{
|
|
3168
|
+
id: "motor_oil",
|
|
3169
|
+
look: "oil",
|
|
3170
|
+
flow: 0.03,
|
|
3171
|
+
pin: 0.12,
|
|
3172
|
+
front: 0.22,
|
|
3173
|
+
referenceThickness: 0.5,
|
|
3174
|
+
slopeCap: 1,
|
|
3175
|
+
dryTime: 0,
|
|
3176
|
+
evaporation: 0,
|
|
3177
|
+
opacityRate: 4.5,
|
|
3178
|
+
reflectance: 0.06,
|
|
3179
|
+
normalScale: 18,
|
|
3180
|
+
thin: 1381135,
|
|
3181
|
+
thick: 263173,
|
|
3182
|
+
dry: 263173,
|
|
3183
|
+
dryThin: 1381135,
|
|
3184
|
+
dropColor: 657932,
|
|
3185
|
+
dropOpacity: 1,
|
|
3186
|
+
volume: 1,
|
|
3187
|
+
spikes: 1,
|
|
3188
|
+
landsAsBlob: true,
|
|
3189
|
+
maxAmount: 2.6,
|
|
3190
|
+
sources: {
|
|
3191
|
+
padrao: "F52",
|
|
3192
|
+
oficina: "W585 (blob W691)"
|
|
3193
|
+
}
|
|
3194
|
+
},
|
|
3195
|
+
{
|
|
3196
|
+
id: "fuel",
|
|
3197
|
+
look: "fuel",
|
|
3198
|
+
flow: 0.2,
|
|
3199
|
+
pin: 0.035,
|
|
3200
|
+
front: 0.07,
|
|
3201
|
+
referenceThickness: 0.2,
|
|
3202
|
+
slopeCap: 1.2,
|
|
3203
|
+
dryTime: 0,
|
|
3204
|
+
evaporation: 0.02,
|
|
3205
|
+
opacityRate: 0.25,
|
|
3206
|
+
reflectance: 0.04,
|
|
3207
|
+
normalScale: 0,
|
|
3208
|
+
thin: 10129520,
|
|
3209
|
+
thick: 7234630,
|
|
3210
|
+
dry: 0,
|
|
3211
|
+
dryThin: 0,
|
|
3212
|
+
dropColor: 12103824,
|
|
3213
|
+
dropOpacity: 0.45,
|
|
3214
|
+
volume: 1.4,
|
|
3215
|
+
spikes: 1,
|
|
3216
|
+
landsAsBlob: true,
|
|
3217
|
+
maxAmount: 2.6,
|
|
3218
|
+
sources: {
|
|
3219
|
+
padrao: "F54",
|
|
3220
|
+
oficina: "W586 (blob W691)"
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
];
|
|
3224
|
+
|
|
3225
|
+
// src/film/profiles.ts
|
|
3226
|
+
var FILM_PROFILES = film_profiles_default;
|
|
3227
|
+
function filmProfileOf(id, overrides = {}) {
|
|
3228
|
+
const profile = FILM_PROFILES.find((entry) => entry.id === id);
|
|
3229
|
+
return profile ? { ...profile, ...overrides } : void 0;
|
|
3230
|
+
}
|
|
2205
3231
|
return __toCommonJS(index_exports);
|
|
2206
3232
|
})();
|
|
2207
3233
|
//# sourceMappingURL=index.global.js.map
|