scena3d 0.3.0 → 0.4.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 +29 -5
- package/dist/index.cjs +428 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +256 -2
- package/dist/index.d.ts +256 -2
- package/dist/index.js +436 -25
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1261,16 +1261,158 @@ function createVillage(options = {}) {
|
|
|
1261
1261
|
};
|
|
1262
1262
|
}
|
|
1263
1263
|
|
|
1264
|
-
// src/
|
|
1264
|
+
// src/kits/kit.ts
|
|
1265
1265
|
import {
|
|
1266
|
-
|
|
1266
|
+
BoxGeometry as BoxGeometry5,
|
|
1267
|
+
CylinderGeometry as CylinderGeometry5,
|
|
1267
1268
|
Group as Group10,
|
|
1268
1269
|
InstancedMesh,
|
|
1269
1270
|
Matrix4,
|
|
1270
1271
|
Mesh as Mesh13,
|
|
1271
|
-
|
|
1272
|
+
MeshStandardMaterial as MeshStandardMaterial11,
|
|
1273
|
+
PointLight as PointLight3,
|
|
1274
|
+
SphereGeometry as SphereGeometry3,
|
|
1272
1275
|
Vector3 as Vector33
|
|
1273
1276
|
} from "three";
|
|
1277
|
+
var KIT_UNIT = 2;
|
|
1278
|
+
function assembleKit(rows, options = {}) {
|
|
1279
|
+
const rng = new Rng(options.seed ?? 1);
|
|
1280
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
1281
|
+
const unit = options.unit ?? KIT_UNIT;
|
|
1282
|
+
const wallHeight = options.wallHeight ?? 2.6;
|
|
1283
|
+
let torchLights = options.torchLights ?? 4;
|
|
1284
|
+
const height = rows.length;
|
|
1285
|
+
const width = Math.max(...rows.map((r) => r.length), 0);
|
|
1286
|
+
const originX = (-width / 2 + 0.5) * unit;
|
|
1287
|
+
const originZ = (-height / 2 + 0.5) * unit;
|
|
1288
|
+
const worldOf = (col, row) => ({
|
|
1289
|
+
x: originX + col * unit,
|
|
1290
|
+
z: originZ + row * unit
|
|
1291
|
+
});
|
|
1292
|
+
const group = new Group10();
|
|
1293
|
+
group.name = "kit";
|
|
1294
|
+
const obstacles = [];
|
|
1295
|
+
const spawns = [];
|
|
1296
|
+
const torches = [];
|
|
1297
|
+
const floorCells = /* @__PURE__ */ new Set();
|
|
1298
|
+
const wallCells = [];
|
|
1299
|
+
const floorTiles = [];
|
|
1300
|
+
for (let row = 0; row < height; row++) {
|
|
1301
|
+
for (let col = 0; col < (rows[row] ?? "").length; col++) {
|
|
1302
|
+
const cell = rows[row][col];
|
|
1303
|
+
if (cell === " " || cell === void 0) continue;
|
|
1304
|
+
const { x, z } = worldOf(col, row);
|
|
1305
|
+
if (cell === "#") {
|
|
1306
|
+
wallCells.push({ x, z });
|
|
1307
|
+
obstacles.push({ center: new Vector33(x, wallHeight / 2, z), radius: unit * 0.71 });
|
|
1308
|
+
continue;
|
|
1309
|
+
}
|
|
1310
|
+
floorTiles.push({ x, z });
|
|
1311
|
+
floorCells.add(`${col},${row}`);
|
|
1312
|
+
if (cell === "D") {
|
|
1313
|
+
const horizontalRun = rows[row][col - 1] === "#" || rows[row][col + 1] === "#";
|
|
1314
|
+
const lintel = new Mesh13(
|
|
1315
|
+
new BoxGeometry5(
|
|
1316
|
+
horizontalRun ? unit : unit * 0.4,
|
|
1317
|
+
wallHeight * 0.25,
|
|
1318
|
+
horizontalRun ? unit * 0.4 : unit
|
|
1319
|
+
),
|
|
1320
|
+
new MeshStandardMaterial11({ color: palette.woodDark, flatShading: true })
|
|
1321
|
+
);
|
|
1322
|
+
lintel.position.set(x, wallHeight * 0.875, z);
|
|
1323
|
+
group.add(lintel);
|
|
1324
|
+
} else if (cell === "T") {
|
|
1325
|
+
group.add(createTorch(x, z, palette, rng, torchLights > 0));
|
|
1326
|
+
torchLights--;
|
|
1327
|
+
torches.push(new Vector33(x, 0, z));
|
|
1328
|
+
obstacles.push({ center: new Vector33(x, 0, z), radius: 0.3 });
|
|
1329
|
+
} else if (cell === "S") {
|
|
1330
|
+
spawns.push(new Vector33(x, 0, z));
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
const stone = new MeshStandardMaterial11({ color: rng.pick(palette.rock), flatShading: true });
|
|
1335
|
+
const stoneDark = new MeshStandardMaterial11({ color: palette.cliff, flatShading: true });
|
|
1336
|
+
const matrix = new Matrix4();
|
|
1337
|
+
if (wallCells.length > 0) {
|
|
1338
|
+
const walls = new InstancedMesh(
|
|
1339
|
+
new BoxGeometry5(unit, wallHeight, unit),
|
|
1340
|
+
stone,
|
|
1341
|
+
wallCells.length
|
|
1342
|
+
);
|
|
1343
|
+
wallCells.forEach((cell, i) => {
|
|
1344
|
+
walls.setMatrixAt(i, matrix.makeTranslation(cell.x, wallHeight / 2, cell.z));
|
|
1345
|
+
});
|
|
1346
|
+
walls.instanceMatrix.needsUpdate = true;
|
|
1347
|
+
group.add(walls);
|
|
1348
|
+
}
|
|
1349
|
+
if (floorTiles.length > 0) {
|
|
1350
|
+
const floors = new InstancedMesh(
|
|
1351
|
+
new BoxGeometry5(unit, 0.2, unit),
|
|
1352
|
+
stoneDark,
|
|
1353
|
+
floorTiles.length
|
|
1354
|
+
);
|
|
1355
|
+
floorTiles.forEach((cell, i) => {
|
|
1356
|
+
floors.setMatrixAt(i, matrix.makeTranslation(cell.x, -0.1, cell.z));
|
|
1357
|
+
});
|
|
1358
|
+
floors.instanceMatrix.needsUpdate = true;
|
|
1359
|
+
group.add(floors);
|
|
1360
|
+
}
|
|
1361
|
+
return {
|
|
1362
|
+
group,
|
|
1363
|
+
obstacles,
|
|
1364
|
+
spawns,
|
|
1365
|
+
torches,
|
|
1366
|
+
floorAt(x, z) {
|
|
1367
|
+
const col = Math.round((x - originX) / unit);
|
|
1368
|
+
const row = Math.round((z - originZ) / unit);
|
|
1369
|
+
return floorCells.has(`${col},${row}`);
|
|
1370
|
+
},
|
|
1371
|
+
size: { width: width * unit, depth: height * unit }
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
function createTorch(x, z, palette, rng, light) {
|
|
1375
|
+
const torch = new Group10();
|
|
1376
|
+
torch.name = "torch";
|
|
1377
|
+
const pole = new Mesh13(
|
|
1378
|
+
new CylinderGeometry5(0.04, 0.06, 1.5, 5),
|
|
1379
|
+
new MeshStandardMaterial11({ color: palette.woodDark, flatShading: true })
|
|
1380
|
+
);
|
|
1381
|
+
pole.position.y = 0.75;
|
|
1382
|
+
torch.add(pole);
|
|
1383
|
+
const flame = new Mesh13(
|
|
1384
|
+
new SphereGeometry3(0.1, 6, 5),
|
|
1385
|
+
new MeshStandardMaterial11({
|
|
1386
|
+
color: palette.lampGlow,
|
|
1387
|
+
emissive: palette.lampGlow,
|
|
1388
|
+
emissiveIntensity: 1.8
|
|
1389
|
+
})
|
|
1390
|
+
);
|
|
1391
|
+
flame.position.y = 1.58;
|
|
1392
|
+
flame.scale.y = rng.range(1.2, 1.5);
|
|
1393
|
+
torch.add(flame);
|
|
1394
|
+
if (light) {
|
|
1395
|
+
const point = new PointLight3(palette.lampGlow, 5, 9, 1.9);
|
|
1396
|
+
point.position.y = 1.6;
|
|
1397
|
+
torch.add(point);
|
|
1398
|
+
}
|
|
1399
|
+
torch.position.set(x, 0, z);
|
|
1400
|
+
return torch;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// src/scene/manifest.ts
|
|
1404
|
+
import { CylinderGeometry as CylinderGeometry6, Group as Group12, Mesh as Mesh15, MeshStandardMaterial as MeshStandardMaterial12 } from "three";
|
|
1405
|
+
|
|
1406
|
+
// src/scatter/scatter.ts
|
|
1407
|
+
import {
|
|
1408
|
+
DynamicDrawUsage,
|
|
1409
|
+
Group as Group11,
|
|
1410
|
+
InstancedMesh as InstancedMesh2,
|
|
1411
|
+
Matrix4 as Matrix42,
|
|
1412
|
+
Mesh as Mesh14,
|
|
1413
|
+
Quaternion,
|
|
1414
|
+
Vector3 as Vector34
|
|
1415
|
+
} from "three";
|
|
1274
1416
|
function scatter(options) {
|
|
1275
1417
|
const seed = options.seed ?? 1;
|
|
1276
1418
|
const rng = new Rng(seed);
|
|
@@ -1325,7 +1467,7 @@ function scatter(options) {
|
|
|
1325
1467
|
const itemIndex = pickItem();
|
|
1326
1468
|
const [minScale, maxScale2] = options.items[itemIndex].scale ?? [0.8, 1.25];
|
|
1327
1469
|
placements.push({
|
|
1328
|
-
position: new
|
|
1470
|
+
position: new Vector34(x, y, z),
|
|
1329
1471
|
rotationY: rng.range(0, Math.PI * 2),
|
|
1330
1472
|
scale: rng.range(minScale, maxScale2),
|
|
1331
1473
|
itemIndex
|
|
@@ -1335,18 +1477,59 @@ function scatter(options) {
|
|
|
1335
1477
|
if (!bucket) occupied.set(key, bucket = []);
|
|
1336
1478
|
bucket.push({ x, z });
|
|
1337
1479
|
}
|
|
1338
|
-
const group = new
|
|
1480
|
+
const group = new Group11();
|
|
1339
1481
|
group.name = "scatter";
|
|
1340
1482
|
const obstacles = [];
|
|
1341
|
-
const placementMatrix = new
|
|
1342
|
-
const composed = new
|
|
1483
|
+
const placementMatrix = new Matrix42();
|
|
1484
|
+
const composed = new Matrix42();
|
|
1343
1485
|
const quaternion = new Quaternion();
|
|
1344
|
-
const up = new
|
|
1345
|
-
const scaleVector = new
|
|
1486
|
+
const up = new Vector34(0, 1, 0);
|
|
1487
|
+
const scaleVector = new Vector34();
|
|
1488
|
+
const emit = (variant, list, parent) => {
|
|
1489
|
+
variant.object.updateMatrixWorld(true);
|
|
1490
|
+
variant.object.traverse((child) => {
|
|
1491
|
+
if (!(child instanceof Mesh14)) return;
|
|
1492
|
+
const instanced = new InstancedMesh2(child.geometry, child.material, list.length);
|
|
1493
|
+
instanced.instanceMatrix.setUsage(DynamicDrawUsage);
|
|
1494
|
+
list.forEach((placement, index) => {
|
|
1495
|
+
quaternion.setFromAxisAngle(up, placement.rotationY);
|
|
1496
|
+
scaleVector.setScalar(placement.scale);
|
|
1497
|
+
placementMatrix.compose(placement.position, quaternion, scaleVector);
|
|
1498
|
+
composed.multiplyMatrices(placementMatrix, child.matrixWorld);
|
|
1499
|
+
instanced.setMatrixAt(index, composed);
|
|
1500
|
+
});
|
|
1501
|
+
instanced.instanceMatrix.needsUpdate = true;
|
|
1502
|
+
parent.add(instanced);
|
|
1503
|
+
});
|
|
1504
|
+
};
|
|
1505
|
+
const tileSize = options.lod?.tileSize ?? 16;
|
|
1506
|
+
const tileMap = /* @__PURE__ */ new Map();
|
|
1507
|
+
const tileFor = (x, z) => {
|
|
1508
|
+
const tx = Math.floor(x / tileSize);
|
|
1509
|
+
const tz = Math.floor(z / tileSize);
|
|
1510
|
+
const key = `${tx},${tz}`;
|
|
1511
|
+
let tile = tileMap.get(key);
|
|
1512
|
+
if (!tile) {
|
|
1513
|
+
tile = {
|
|
1514
|
+
center: { x: (tx + 0.5) * tileSize, z: (tz + 0.5) * tileSize },
|
|
1515
|
+
near: new Group11(),
|
|
1516
|
+
far: new Group11()
|
|
1517
|
+
};
|
|
1518
|
+
tile.far.visible = false;
|
|
1519
|
+
group.add(tile.near, tile.far);
|
|
1520
|
+
tileMap.set(key, tile);
|
|
1521
|
+
}
|
|
1522
|
+
return tile;
|
|
1523
|
+
};
|
|
1346
1524
|
options.items.forEach((item, itemIndex) => {
|
|
1347
1525
|
const variantCount = item.variants ?? 4;
|
|
1348
1526
|
const variants = [];
|
|
1349
1527
|
for (let v = 0; v < variantCount; v++) variants.push(item.create(rng.fork()));
|
|
1528
|
+
const useLod = options.lod !== void 0 && item.createFar !== void 0;
|
|
1529
|
+
const farVariants = [];
|
|
1530
|
+
if (useLod) {
|
|
1531
|
+
for (let v = 0; v < variantCount; v++) farVariants.push(item.createFar(rng.fork()));
|
|
1532
|
+
}
|
|
1350
1533
|
const byVariant = variants.map(() => []);
|
|
1351
1534
|
for (const placement of placements) {
|
|
1352
1535
|
if (placement.itemIndex !== itemIndex) continue;
|
|
@@ -1356,21 +1539,21 @@ function scatter(options) {
|
|
|
1356
1539
|
variants.forEach((variant, v) => {
|
|
1357
1540
|
const list = byVariant[v];
|
|
1358
1541
|
if (list.length === 0) return;
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
group
|
|
1373
|
-
}
|
|
1542
|
+
if (useLod) {
|
|
1543
|
+
const byTile = /* @__PURE__ */ new Map();
|
|
1544
|
+
for (const placement of list) {
|
|
1545
|
+
const tile = tileFor(placement.position.x, placement.position.z);
|
|
1546
|
+
let bucket = byTile.get(tile);
|
|
1547
|
+
if (!bucket) byTile.set(tile, bucket = []);
|
|
1548
|
+
bucket.push(placement);
|
|
1549
|
+
}
|
|
1550
|
+
for (const [tile, bucket] of byTile) {
|
|
1551
|
+
emit(variant, bucket, tile.near);
|
|
1552
|
+
emit(farVariants[v], bucket, tile.far);
|
|
1553
|
+
}
|
|
1554
|
+
} else {
|
|
1555
|
+
emit(variant, list, group);
|
|
1556
|
+
}
|
|
1374
1557
|
if (variant.obstacleRadius > 0) {
|
|
1375
1558
|
for (const placement of list) {
|
|
1376
1559
|
obstacles.push({
|
|
@@ -1381,15 +1564,242 @@ function scatter(options) {
|
|
|
1381
1564
|
}
|
|
1382
1565
|
});
|
|
1383
1566
|
});
|
|
1384
|
-
|
|
1567
|
+
const result = { group, placements, obstacles, count: placements.length };
|
|
1568
|
+
if (options.lod) {
|
|
1569
|
+
const tiles = [...tileMap.values()];
|
|
1570
|
+
const swapOut = options.lod.distance * 1.1;
|
|
1571
|
+
const swapIn = options.lod.distance * 0.9;
|
|
1572
|
+
result.tiles = tiles;
|
|
1573
|
+
result.update = (camera) => {
|
|
1574
|
+
for (const tile of tiles) {
|
|
1575
|
+
const dx = camera.position.x - tile.center.x;
|
|
1576
|
+
const dz = camera.position.z - tile.center.z;
|
|
1577
|
+
const distance = Math.hypot(dx, dz);
|
|
1578
|
+
if (tile.near.visible && distance > swapOut) {
|
|
1579
|
+
tile.near.visible = false;
|
|
1580
|
+
tile.far.visible = true;
|
|
1581
|
+
} else if (!tile.near.visible && distance < swapIn) {
|
|
1582
|
+
tile.near.visible = true;
|
|
1583
|
+
tile.far.visible = false;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
return result;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
// src/scene/manifest.ts
|
|
1592
|
+
var PROP_FACTORIES = {
|
|
1593
|
+
tree: (seed, palette) => createTree({ seed, palette }),
|
|
1594
|
+
rock: (seed, palette) => createRock({ seed, palette }),
|
|
1595
|
+
bush: (seed, palette) => createBush({ seed, palette }),
|
|
1596
|
+
grass: (seed, palette) => createGrassTuft({ seed, palette }),
|
|
1597
|
+
crate: (seed, palette) => createCrate({ seed, palette }),
|
|
1598
|
+
fence: (seed, palette) => createFence({ seed, palette }),
|
|
1599
|
+
lamp: (seed, palette) => createLamp({ seed, palette })
|
|
1600
|
+
};
|
|
1601
|
+
var FAR_FACTORIES = {
|
|
1602
|
+
tree: (seed, palette) => {
|
|
1603
|
+
const rng = new Rng(seed);
|
|
1604
|
+
const group = new Group12();
|
|
1605
|
+
const cone = new Mesh15(
|
|
1606
|
+
new CylinderGeometry6(0, rng.range(1, 1.4), rng.range(2.6, 3.8), 5),
|
|
1607
|
+
new MeshStandardMaterial12({ color: rng.pick(palette.foliage), flatShading: true })
|
|
1608
|
+
);
|
|
1609
|
+
cone.position.y = cone.geometry.parameters.height / 2 + 0.3;
|
|
1610
|
+
group.add(cone);
|
|
1611
|
+
return { object: group, obstacleRadius: 0 };
|
|
1612
|
+
},
|
|
1613
|
+
bush: (seed, palette) => {
|
|
1614
|
+
const rng = new Rng(seed);
|
|
1615
|
+
const group = new Group12();
|
|
1616
|
+
const cone = new Mesh15(
|
|
1617
|
+
new CylinderGeometry6(0.1, rng.range(0.5, 0.7), rng.range(0.5, 0.8), 5),
|
|
1618
|
+
new MeshStandardMaterial12({ color: rng.pick(palette.foliage), flatShading: true })
|
|
1619
|
+
);
|
|
1620
|
+
cone.position.y = 0.3;
|
|
1621
|
+
group.add(cone);
|
|
1622
|
+
return { object: group, obstacleRadius: 0 };
|
|
1623
|
+
},
|
|
1624
|
+
grass: () => ({ object: new Group12(), obstacleRadius: 0 })
|
|
1625
|
+
};
|
|
1626
|
+
function buildScene(manifest, scene) {
|
|
1627
|
+
const seed = manifest.seed ?? 1;
|
|
1628
|
+
const palette = manifest.palette ? PALETTES[manifest.palette] : DEFAULT_PALETTE;
|
|
1629
|
+
const group = new Group12();
|
|
1630
|
+
group.name = "scena-scene";
|
|
1631
|
+
const updates = [];
|
|
1632
|
+
let terrain;
|
|
1633
|
+
if (manifest.terrain) {
|
|
1634
|
+
terrain = createTerrain({
|
|
1635
|
+
...manifest.terrain,
|
|
1636
|
+
seed,
|
|
1637
|
+
waterLevel: manifest.water?.level,
|
|
1638
|
+
palette
|
|
1639
|
+
});
|
|
1640
|
+
group.add(terrain.mesh);
|
|
1641
|
+
}
|
|
1642
|
+
const heightAt = terrain ? terrain.heightAt : () => 0;
|
|
1643
|
+
let water;
|
|
1644
|
+
if (manifest.water) {
|
|
1645
|
+
water = createWater({
|
|
1646
|
+
level: manifest.water.level,
|
|
1647
|
+
size: manifest.water.size ?? (terrain ? terrain.size * 1.4 : 200),
|
|
1648
|
+
palette
|
|
1649
|
+
});
|
|
1650
|
+
group.add(water.mesh);
|
|
1651
|
+
updates.push((dt) => water.update(dt));
|
|
1652
|
+
}
|
|
1653
|
+
const dryLand = terrain && water ? aboveWater(terrain, water, 0.3) : () => true;
|
|
1654
|
+
let sky;
|
|
1655
|
+
if (manifest.sky ?? true) {
|
|
1656
|
+
sky = createSky({ palette });
|
|
1657
|
+
group.add(sky.mesh);
|
|
1658
|
+
}
|
|
1659
|
+
const rig = createLightingRig(manifest.lighting ?? "day");
|
|
1660
|
+
group.add(rig.group);
|
|
1661
|
+
if (scene && manifest.fog !== false) applyFog(scene, manifest.fog ?? "haze", palette);
|
|
1662
|
+
const paths = (manifest.paths ?? []).map((spec) => {
|
|
1663
|
+
const path = createPath(spec.points, {
|
|
1664
|
+
surface: heightAt,
|
|
1665
|
+
width: spec.width,
|
|
1666
|
+
loop: spec.loop,
|
|
1667
|
+
palette
|
|
1668
|
+
});
|
|
1669
|
+
group.add(path.mesh);
|
|
1670
|
+
return path;
|
|
1671
|
+
});
|
|
1672
|
+
const onAnyPath = (x, z) => paths.some((p) => p.contains(x, z));
|
|
1673
|
+
let village;
|
|
1674
|
+
if (manifest.village) {
|
|
1675
|
+
village = createVillage({
|
|
1676
|
+
...manifest.village,
|
|
1677
|
+
seed: seed + 1,
|
|
1678
|
+
surface: heightAt,
|
|
1679
|
+
mask: (x, z) => dryLand(x, z) && !onAnyPath(x, z),
|
|
1680
|
+
palette
|
|
1681
|
+
});
|
|
1682
|
+
group.add(village.group);
|
|
1683
|
+
}
|
|
1684
|
+
const wind = manifest.wind ?? true;
|
|
1685
|
+
const scatters = (manifest.scatters ?? []).map((spec, index) => {
|
|
1686
|
+
const inset = terrain ? terrain.size * 0.45 : 40;
|
|
1687
|
+
const avoidWater = spec.avoidWater ?? true;
|
|
1688
|
+
const avoidPaths = spec.avoidPaths ?? true;
|
|
1689
|
+
const result = scatter({
|
|
1690
|
+
seed: seed + 10 + index,
|
|
1691
|
+
area: spec.area ?? { min: { x: -inset, z: -inset }, max: { x: inset, z: inset } },
|
|
1692
|
+
surface: heightAt,
|
|
1693
|
+
density: spec.density,
|
|
1694
|
+
count: spec.count,
|
|
1695
|
+
minSpacing: spec.minSpacing,
|
|
1696
|
+
clumpScale: spec.clumpScale,
|
|
1697
|
+
lod: spec.lod,
|
|
1698
|
+
items: spec.items.map((item) => {
|
|
1699
|
+
const far = spec.lod ? FAR_FACTORIES[item.type] : void 0;
|
|
1700
|
+
return {
|
|
1701
|
+
create: (rng) => PROP_FACTORIES[item.type](rng.int(1, 1e9), palette),
|
|
1702
|
+
createFar: far && ((rng) => far(rng.int(1, 1e9), palette)),
|
|
1703
|
+
weight: item.weight,
|
|
1704
|
+
variants: item.variants,
|
|
1705
|
+
scale: item.scale
|
|
1706
|
+
};
|
|
1707
|
+
}),
|
|
1708
|
+
mask: (x, z, y) => (spec.maxHeight === void 0 || y < spec.maxHeight) && (!avoidWater || dryLand(x, z)) && (!avoidPaths || !onAnyPath(x, z)),
|
|
1709
|
+
keepOut: [
|
|
1710
|
+
...avoidPaths ? paths.flatMap((p) => p.keepOut) : [],
|
|
1711
|
+
...village ? village.keepOut : []
|
|
1712
|
+
]
|
|
1713
|
+
});
|
|
1714
|
+
group.add(result.group);
|
|
1715
|
+
if (wind !== false) {
|
|
1716
|
+
const sway = applyWind(result.group, {
|
|
1717
|
+
strength: typeof wind === "object" ? wind.strength ?? 0.05 : 0.05
|
|
1718
|
+
});
|
|
1719
|
+
updates.push((dt) => sway.update(dt));
|
|
1720
|
+
}
|
|
1721
|
+
return result;
|
|
1722
|
+
});
|
|
1723
|
+
let cycle;
|
|
1724
|
+
if (manifest.dayCycle) {
|
|
1725
|
+
cycle = createDayCycle({
|
|
1726
|
+
sky,
|
|
1727
|
+
rig,
|
|
1728
|
+
scene,
|
|
1729
|
+
lamps: village?.lamps,
|
|
1730
|
+
palette,
|
|
1731
|
+
dayLength: manifest.dayCycle.dayLength,
|
|
1732
|
+
timeOfDay: manifest.dayCycle.timeOfDay
|
|
1733
|
+
});
|
|
1734
|
+
updates.push((dt) => cycle.update(dt));
|
|
1735
|
+
}
|
|
1736
|
+
scene?.add(group);
|
|
1737
|
+
return {
|
|
1738
|
+
group,
|
|
1739
|
+
palette,
|
|
1740
|
+
terrain,
|
|
1741
|
+
water,
|
|
1742
|
+
sky,
|
|
1743
|
+
rig,
|
|
1744
|
+
cycle,
|
|
1745
|
+
paths,
|
|
1746
|
+
village,
|
|
1747
|
+
scatters,
|
|
1748
|
+
obstacles: [
|
|
1749
|
+
...village ? village.obstacles : [],
|
|
1750
|
+
...scatters.flatMap((s) => s.obstacles)
|
|
1751
|
+
],
|
|
1752
|
+
heightAt,
|
|
1753
|
+
update(dt) {
|
|
1754
|
+
for (const fn of updates) fn(dt);
|
|
1755
|
+
}
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
// src/scene/markers.ts
|
|
1760
|
+
import { Vector3 as Vector35 } from "three";
|
|
1761
|
+
function extractMarkers(root) {
|
|
1762
|
+
root.updateWorldMatrix(true, true);
|
|
1763
|
+
const spawns = {};
|
|
1764
|
+
const routePoints = {};
|
|
1765
|
+
const obstacles = [];
|
|
1766
|
+
const keepOut = [];
|
|
1767
|
+
root.traverse((node) => {
|
|
1768
|
+
const name = node.name.replace(/\.\d+$/, "");
|
|
1769
|
+
const match = /^(spawn|route|obstacle|keepout)_(.+)$/.exec(name);
|
|
1770
|
+
if (!match) return;
|
|
1771
|
+
const [, kind, rest] = match;
|
|
1772
|
+
const position = node.getWorldPosition(new Vector35());
|
|
1773
|
+
const radius = Math.max(node.scale.x, node.scale.z);
|
|
1774
|
+
if (kind === "spawn") {
|
|
1775
|
+
spawns[rest] = position;
|
|
1776
|
+
} else if (kind === "route") {
|
|
1777
|
+
const step = /^(.*)_(\d+)$/.exec(rest);
|
|
1778
|
+
const routeName = step ? step[1] : rest;
|
|
1779
|
+
const index = step ? parseInt(step[2], 10) : 0;
|
|
1780
|
+
(routePoints[routeName] ?? (routePoints[routeName] = [])).push({ index, position });
|
|
1781
|
+
} else if (kind === "obstacle") {
|
|
1782
|
+
obstacles.push({ center: position, radius });
|
|
1783
|
+
} else {
|
|
1784
|
+
keepOut.push({ center: { x: position.x, z: position.z }, radius });
|
|
1785
|
+
}
|
|
1786
|
+
});
|
|
1787
|
+
const routes = {};
|
|
1788
|
+
for (const [name, points] of Object.entries(routePoints)) {
|
|
1789
|
+
routes[name] = points.sort((a, b) => a.index - b.index).map((p) => p.position);
|
|
1790
|
+
}
|
|
1791
|
+
return { spawns, routes, obstacles, keepOut };
|
|
1385
1792
|
}
|
|
1386
1793
|
export {
|
|
1387
1794
|
DEFAULT_PALETTE,
|
|
1795
|
+
KIT_UNIT,
|
|
1388
1796
|
PALETTES,
|
|
1389
1797
|
Rng,
|
|
1390
1798
|
aboveWater,
|
|
1391
1799
|
applyFog,
|
|
1392
1800
|
applyWind,
|
|
1801
|
+
assembleKit,
|
|
1802
|
+
buildScene,
|
|
1393
1803
|
collectObstacles,
|
|
1394
1804
|
createBush,
|
|
1395
1805
|
createCrate,
|
|
@@ -1409,6 +1819,7 @@ export {
|
|
|
1409
1819
|
createVillage,
|
|
1410
1820
|
createWater,
|
|
1411
1821
|
createWell,
|
|
1822
|
+
extractMarkers,
|
|
1412
1823
|
fractalNoise2,
|
|
1413
1824
|
hash2,
|
|
1414
1825
|
scatter,
|