scena3d 0.1.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 +41 -11
- package/dist/index.cjs +1203 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +534 -3
- package/dist/index.d.ts +534 -3
- package/dist/index.js +1204 -41
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.cjs
CHANGED
|
@@ -21,18 +21,34 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
DEFAULT_PALETTE: () => DEFAULT_PALETTE,
|
|
24
|
+
KIT_UNIT: () => KIT_UNIT,
|
|
24
25
|
PALETTES: () => PALETTES,
|
|
25
26
|
Rng: () => Rng,
|
|
27
|
+
aboveWater: () => aboveWater,
|
|
26
28
|
applyFog: () => applyFog,
|
|
29
|
+
applyWind: () => applyWind,
|
|
30
|
+
assembleKit: () => assembleKit,
|
|
31
|
+
buildScene: () => buildScene,
|
|
27
32
|
collectObstacles: () => collectObstacles,
|
|
33
|
+
createBush: () => createBush,
|
|
28
34
|
createCrate: () => createCrate,
|
|
35
|
+
createDayCycle: () => createDayCycle,
|
|
29
36
|
createFence: () => createFence,
|
|
37
|
+
createGrassTuft: () => createGrassTuft,
|
|
38
|
+
createHouse: () => createHouse,
|
|
30
39
|
createLamp: () => createLamp,
|
|
31
40
|
createLightingRig: () => createLightingRig,
|
|
41
|
+
createPath: () => createPath,
|
|
32
42
|
createRock: () => createRock,
|
|
43
|
+
createRuin: () => createRuin,
|
|
33
44
|
createSky: () => createSky,
|
|
34
45
|
createTerrain: () => createTerrain,
|
|
46
|
+
createTower: () => createTower,
|
|
35
47
|
createTree: () => createTree,
|
|
48
|
+
createVillage: () => createVillage,
|
|
49
|
+
createWater: () => createWater,
|
|
50
|
+
createWell: () => createWell,
|
|
51
|
+
extractMarkers: () => extractMarkers,
|
|
36
52
|
fractalNoise2: () => fractalNoise2,
|
|
37
53
|
hash2: () => hash2,
|
|
38
54
|
scatter: () => scatter,
|
|
@@ -121,7 +137,12 @@ var PALETTES = {
|
|
|
121
137
|
peak: 15265007,
|
|
122
138
|
skyTop: 4026552,
|
|
123
139
|
skyBottom: 12573160,
|
|
124
|
-
fog: 12111837
|
|
140
|
+
fog: 12111837,
|
|
141
|
+
water: 4161454,
|
|
142
|
+
sand: 13220234,
|
|
143
|
+
path: 10125663,
|
|
144
|
+
wall: 14273712,
|
|
145
|
+
roof: 11032126
|
|
125
146
|
},
|
|
126
147
|
autumn: {
|
|
127
148
|
foliage: [13202735, 14257722, 11754538, 14722373],
|
|
@@ -137,7 +158,12 @@ var PALETTES = {
|
|
|
137
158
|
peak: 14933716,
|
|
138
159
|
skyTop: 9333928,
|
|
139
160
|
skyBottom: 15255976,
|
|
140
|
-
fog: 14270888
|
|
161
|
+
fog: 14270888,
|
|
162
|
+
water: 4881042,
|
|
163
|
+
sand: 13348995,
|
|
164
|
+
path: 9270356,
|
|
165
|
+
wall: 13416596,
|
|
166
|
+
roof: 9062960
|
|
141
167
|
},
|
|
142
168
|
dusk: {
|
|
143
169
|
foliage: [2055750, 2385999, 1724992, 2978904],
|
|
@@ -153,7 +179,33 @@ var PALETTES = {
|
|
|
153
179
|
peak: 12105945,
|
|
154
180
|
skyTop: 1909061,
|
|
155
181
|
skyBottom: 13199946,
|
|
156
|
-
fog: 6969978
|
|
182
|
+
fog: 6969978,
|
|
183
|
+
water: 2968168,
|
|
184
|
+
sand: 9075306,
|
|
185
|
+
path: 6969938,
|
|
186
|
+
wall: 9274009,
|
|
187
|
+
roof: 4535640
|
|
188
|
+
},
|
|
189
|
+
winter: {
|
|
190
|
+
foliage: [3038280, 3696986, 5405288, 8889750],
|
|
191
|
+
trunk: 4864563,
|
|
192
|
+
rock: [10134701, 8687256, 11581632],
|
|
193
|
+
wood: 7823433,
|
|
194
|
+
woodDark: 5522746,
|
|
195
|
+
metal: 3752013,
|
|
196
|
+
lampGlow: 16767113,
|
|
197
|
+
grassLow: 13621726,
|
|
198
|
+
grassHigh: 15002606,
|
|
199
|
+
cliff: 7764349,
|
|
200
|
+
peak: 16054266,
|
|
201
|
+
skyTop: 5929894,
|
|
202
|
+
skyBottom: 14214380,
|
|
203
|
+
fog: 13424864,
|
|
204
|
+
water: 4878470,
|
|
205
|
+
sand: 12108486,
|
|
206
|
+
path: 9143160,
|
|
207
|
+
wall: 13814203,
|
|
208
|
+
roof: 7030328
|
|
157
209
|
}
|
|
158
210
|
};
|
|
159
211
|
var DEFAULT_PALETTE = PALETTES.meadow;
|
|
@@ -368,8 +420,321 @@ function createLamp(options = {}) {
|
|
|
368
420
|
return { object: group, obstacleRadius: 0.25 };
|
|
369
421
|
}
|
|
370
422
|
|
|
371
|
-
// src/
|
|
423
|
+
// src/props/grass.ts
|
|
372
424
|
var import_three7 = require("three");
|
|
425
|
+
function createGrassTuft(options = {}) {
|
|
426
|
+
const rng = new Rng(options.seed ?? 1);
|
|
427
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
428
|
+
const blades = options.blades ?? rng.int(4, 6);
|
|
429
|
+
const group = new import_three7.Group();
|
|
430
|
+
group.name = "grass";
|
|
431
|
+
const material = new import_three7.MeshStandardMaterial({
|
|
432
|
+
color: rng.next() < 0.5 ? palette.grassHigh : rng.pick(palette.foliage),
|
|
433
|
+
flatShading: true
|
|
434
|
+
});
|
|
435
|
+
for (let i = 0; i < blades; i++) {
|
|
436
|
+
const height = rng.range(0.25, 0.5);
|
|
437
|
+
const blade = new import_three7.Mesh(new import_three7.ConeGeometry(0.035, height, 3), material);
|
|
438
|
+
blade.position.set(rng.jitter(0, 0.12), height / 2, rng.jitter(0, 0.12));
|
|
439
|
+
blade.rotation.set(rng.range(-0.25, 0.25), rng.range(0, Math.PI), rng.range(-0.25, 0.25));
|
|
440
|
+
group.add(blade);
|
|
441
|
+
}
|
|
442
|
+
return { object: group, obstacleRadius: 0 };
|
|
443
|
+
}
|
|
444
|
+
function createBush(options = {}) {
|
|
445
|
+
const rng = new Rng(options.seed ?? 1);
|
|
446
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
447
|
+
const size = options.size ?? rng.range(0.4, 0.7);
|
|
448
|
+
const group = new import_three7.Group();
|
|
449
|
+
group.name = "bush";
|
|
450
|
+
const material = new import_three7.MeshStandardMaterial({ color: rng.pick(palette.foliage), flatShading: true });
|
|
451
|
+
const blobs = rng.int(2, 3);
|
|
452
|
+
for (let i = 0; i < blobs; i++) {
|
|
453
|
+
const radius = size * rng.range(0.55, 0.85);
|
|
454
|
+
const blob = new import_three7.Mesh(new import_three7.IcosahedronGeometry(radius, 0), material);
|
|
455
|
+
blob.position.set(rng.jitter(0, size * 0.4), radius * 0.7, rng.jitter(0, size * 0.4));
|
|
456
|
+
blob.scale.y = rng.range(0.7, 0.85);
|
|
457
|
+
blob.rotation.y = rng.range(0, Math.PI);
|
|
458
|
+
group.add(blob);
|
|
459
|
+
}
|
|
460
|
+
return { object: group, obstacleRadius: size * 0.6 };
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/props/building.ts
|
|
464
|
+
var import_three8 = require("three");
|
|
465
|
+
function prismGeometry(width, height, depth) {
|
|
466
|
+
const w = width / 2;
|
|
467
|
+
const d = depth / 2;
|
|
468
|
+
const positions = new Float32Array([
|
|
469
|
+
// front gable (z = +d)
|
|
470
|
+
-w,
|
|
471
|
+
0,
|
|
472
|
+
d,
|
|
473
|
+
w,
|
|
474
|
+
0,
|
|
475
|
+
d,
|
|
476
|
+
0,
|
|
477
|
+
height,
|
|
478
|
+
d,
|
|
479
|
+
// back gable (z = -d)
|
|
480
|
+
w,
|
|
481
|
+
0,
|
|
482
|
+
-d,
|
|
483
|
+
-w,
|
|
484
|
+
0,
|
|
485
|
+
-d,
|
|
486
|
+
0,
|
|
487
|
+
height,
|
|
488
|
+
-d,
|
|
489
|
+
// left slope
|
|
490
|
+
-w,
|
|
491
|
+
0,
|
|
492
|
+
-d,
|
|
493
|
+
-w,
|
|
494
|
+
0,
|
|
495
|
+
d,
|
|
496
|
+
0,
|
|
497
|
+
height,
|
|
498
|
+
d,
|
|
499
|
+
-w,
|
|
500
|
+
0,
|
|
501
|
+
-d,
|
|
502
|
+
0,
|
|
503
|
+
height,
|
|
504
|
+
d,
|
|
505
|
+
0,
|
|
506
|
+
height,
|
|
507
|
+
-d,
|
|
508
|
+
// right slope
|
|
509
|
+
w,
|
|
510
|
+
0,
|
|
511
|
+
d,
|
|
512
|
+
w,
|
|
513
|
+
0,
|
|
514
|
+
-d,
|
|
515
|
+
0,
|
|
516
|
+
height,
|
|
517
|
+
-d,
|
|
518
|
+
w,
|
|
519
|
+
0,
|
|
520
|
+
d,
|
|
521
|
+
0,
|
|
522
|
+
height,
|
|
523
|
+
-d,
|
|
524
|
+
0,
|
|
525
|
+
height,
|
|
526
|
+
d,
|
|
527
|
+
// bottom
|
|
528
|
+
-w,
|
|
529
|
+
0,
|
|
530
|
+
-d,
|
|
531
|
+
w,
|
|
532
|
+
0,
|
|
533
|
+
-d,
|
|
534
|
+
w,
|
|
535
|
+
0,
|
|
536
|
+
d,
|
|
537
|
+
-w,
|
|
538
|
+
0,
|
|
539
|
+
-d,
|
|
540
|
+
w,
|
|
541
|
+
0,
|
|
542
|
+
d,
|
|
543
|
+
-w,
|
|
544
|
+
0,
|
|
545
|
+
d
|
|
546
|
+
]);
|
|
547
|
+
const geometry = new import_three8.BufferGeometry();
|
|
548
|
+
geometry.setAttribute("position", new import_three8.BufferAttribute(positions, 3));
|
|
549
|
+
geometry.computeVertexNormals();
|
|
550
|
+
return geometry;
|
|
551
|
+
}
|
|
552
|
+
function createHouse(options = {}) {
|
|
553
|
+
const rng = new Rng(options.seed ?? 1);
|
|
554
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
555
|
+
const width = options.width ?? rng.range(3.2, 4.2);
|
|
556
|
+
const depth = options.depth ?? width * rng.range(0.75, 0.9);
|
|
557
|
+
const wallHeight = options.wallHeight ?? rng.range(2.1, 2.4);
|
|
558
|
+
const group = new import_three8.Group();
|
|
559
|
+
group.name = "house";
|
|
560
|
+
const wall = new import_three8.MeshStandardMaterial({ color: palette.wall, flatShading: true });
|
|
561
|
+
wall.color.offsetHSL(0, 0, rng.range(-0.03, 0.03));
|
|
562
|
+
const roof = new import_three8.MeshStandardMaterial({ color: palette.roof, flatShading: true });
|
|
563
|
+
roof.color.offsetHSL(0, 0, rng.range(-0.04, 0.04));
|
|
564
|
+
const stone = new import_three8.MeshStandardMaterial({ color: palette.rock[0], flatShading: true });
|
|
565
|
+
const wood = new import_three8.MeshStandardMaterial({ color: palette.woodDark, flatShading: true });
|
|
566
|
+
const foundation = new import_three8.Mesh(new import_three8.BoxGeometry(width + 0.3, 1.6, depth + 0.3), stone);
|
|
567
|
+
foundation.position.y = -0.55;
|
|
568
|
+
group.add(foundation);
|
|
569
|
+
const body = new import_three8.Mesh(new import_three8.BoxGeometry(width, wallHeight, depth), wall);
|
|
570
|
+
body.position.y = wallHeight / 2;
|
|
571
|
+
group.add(body);
|
|
572
|
+
const ridgeHeight = width * rng.range(0.32, 0.4);
|
|
573
|
+
const roofMesh = new import_three8.Mesh(prismGeometry(width + 0.5, ridgeHeight, depth + 0.6), roof);
|
|
574
|
+
roofMesh.position.y = wallHeight - 0.02;
|
|
575
|
+
group.add(roofMesh);
|
|
576
|
+
const chimney = new import_three8.Mesh(new import_three8.BoxGeometry(0.34, ridgeHeight + 0.8, 0.34), stone);
|
|
577
|
+
chimney.position.set(rng.pick([-1, 1]) * width * 0.22, wallHeight + ridgeHeight * 0.45, depth * 0.12);
|
|
578
|
+
group.add(chimney);
|
|
579
|
+
const door = new import_three8.Mesh(new import_three8.BoxGeometry(0.85, 1.5, 0.08), wood);
|
|
580
|
+
door.position.set(rng.range(-0.4, 0.4), 0.75, depth / 2 + 0.02);
|
|
581
|
+
group.add(door);
|
|
582
|
+
const glass = new import_three8.MeshStandardMaterial({
|
|
583
|
+
color: palette.lampGlow,
|
|
584
|
+
emissive: palette.lampGlow,
|
|
585
|
+
emissiveIntensity: 1
|
|
586
|
+
});
|
|
587
|
+
const windowGeometry = new import_three8.BoxGeometry(0.6, 0.62, 0.08);
|
|
588
|
+
const front = new import_three8.Mesh(windowGeometry, glass);
|
|
589
|
+
front.position.set(door.position.x < 0 ? width * 0.28 : -width * 0.28, 1.35, depth / 2 + 0.02);
|
|
590
|
+
group.add(front);
|
|
591
|
+
for (const side of [-1, 1]) {
|
|
592
|
+
if (rng.next() < 0.35) continue;
|
|
593
|
+
const pane = new import_three8.Mesh(windowGeometry, glass);
|
|
594
|
+
pane.position.set(side * (width / 2 + 0.02), 1.35, rng.range(-0.3, 0.3) * depth);
|
|
595
|
+
pane.rotation.y = Math.PI / 2;
|
|
596
|
+
group.add(pane);
|
|
597
|
+
}
|
|
598
|
+
return { object: group, obstacleRadius: Math.hypot(width + 0.5, depth + 0.5) / 2 };
|
|
599
|
+
}
|
|
600
|
+
function createTower(options = {}) {
|
|
601
|
+
const rng = new Rng(options.seed ?? 1);
|
|
602
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
603
|
+
const height = options.height ?? rng.range(4.2, 5.2);
|
|
604
|
+
const group = new import_three8.Group();
|
|
605
|
+
group.name = "tower";
|
|
606
|
+
const wood = new import_three8.MeshStandardMaterial({ color: palette.wood, flatShading: true });
|
|
607
|
+
const woodDark = new import_three8.MeshStandardMaterial({ color: palette.woodDark, flatShading: true });
|
|
608
|
+
const roof = new import_three8.MeshStandardMaterial({ color: palette.roof, flatShading: true });
|
|
609
|
+
const spread = 1;
|
|
610
|
+
for (const x of [-1, 1]) {
|
|
611
|
+
for (const z of [-1, 1]) {
|
|
612
|
+
const leg = new import_three8.Mesh(new import_three8.CylinderGeometry(0.09, 0.12, height, 6), woodDark);
|
|
613
|
+
leg.position.set(x * spread * 0.75, height / 2 - 0.6, z * spread * 0.75);
|
|
614
|
+
leg.rotation.z = -x * 0.1;
|
|
615
|
+
leg.rotation.x = z * 0.1;
|
|
616
|
+
group.add(leg);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
for (const level of [0.3, 0.62]) {
|
|
620
|
+
const brace = new import_three8.Mesh(new import_three8.BoxGeometry(spread * 2.1, 0.09, 0.07), wood);
|
|
621
|
+
brace.position.set(0, height * level, spread * 0.82 * (1 - level * 0.35));
|
|
622
|
+
brace.rotation.z = rng.pick([-0.5, 0.5]);
|
|
623
|
+
group.add(brace);
|
|
624
|
+
const braceSide = new import_three8.Mesh(new import_three8.BoxGeometry(0.07, 0.09, spread * 2.1), wood);
|
|
625
|
+
braceSide.position.set(spread * 0.82 * (1 - level * 0.35), height * level, 0);
|
|
626
|
+
braceSide.rotation.x = rng.pick([-0.5, 0.5]);
|
|
627
|
+
group.add(braceSide);
|
|
628
|
+
}
|
|
629
|
+
const platform = new import_three8.Mesh(new import_three8.BoxGeometry(spread * 2.2, 0.14, spread * 2.2), wood);
|
|
630
|
+
platform.position.y = height - 0.5;
|
|
631
|
+
group.add(platform);
|
|
632
|
+
for (const x of [-1, 1]) {
|
|
633
|
+
for (const z of [-1, 1]) {
|
|
634
|
+
const post = new import_three8.Mesh(new import_three8.BoxGeometry(0.08, 0.9, 0.08), woodDark);
|
|
635
|
+
post.position.set(x * spread * 1, height - 0.05, z * spread * 1);
|
|
636
|
+
group.add(post);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
for (const [rx, rz, w, d] of [
|
|
640
|
+
[0, 1, spread * 2.1, 0.06],
|
|
641
|
+
[0, -1, spread * 2.1, 0.06],
|
|
642
|
+
[1, 0, 0.06, spread * 2.1],
|
|
643
|
+
[-1, 0, 0.06, spread * 2.1]
|
|
644
|
+
]) {
|
|
645
|
+
const rail = new import_three8.Mesh(new import_three8.BoxGeometry(w, 0.07, d), wood);
|
|
646
|
+
rail.position.set(rx * spread, height + 0.28, rz * spread);
|
|
647
|
+
group.add(rail);
|
|
648
|
+
}
|
|
649
|
+
const cap = new import_three8.Mesh(new import_three8.CylinderGeometry(0, spread * 1.55, 1, 4), roof);
|
|
650
|
+
cap.position.y = height + 1.15;
|
|
651
|
+
cap.rotation.y = Math.PI / 4;
|
|
652
|
+
group.add(cap);
|
|
653
|
+
return { object: group, obstacleRadius: spread * 1.35 };
|
|
654
|
+
}
|
|
655
|
+
function createWell(options = {}) {
|
|
656
|
+
const rng = new Rng(options.seed ?? 1);
|
|
657
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
658
|
+
const group = new import_three8.Group();
|
|
659
|
+
group.name = "well";
|
|
660
|
+
const stone = new import_three8.MeshStandardMaterial({ color: rng.pick(palette.rock), flatShading: true });
|
|
661
|
+
const wood = new import_three8.MeshStandardMaterial({ color: palette.woodDark, flatShading: true });
|
|
662
|
+
const roof = new import_three8.MeshStandardMaterial({ color: palette.roof, flatShading: true });
|
|
663
|
+
const ring = new import_three8.Mesh(new import_three8.CylinderGeometry(0.85, 0.95, 0.75, 10), stone);
|
|
664
|
+
ring.position.y = 0.375;
|
|
665
|
+
group.add(ring);
|
|
666
|
+
const inner = new import_three8.Mesh(
|
|
667
|
+
new import_three8.CylinderGeometry(0.62, 0.62, 0.05, 10),
|
|
668
|
+
new import_three8.MeshStandardMaterial({ color: 1450542 })
|
|
669
|
+
);
|
|
670
|
+
inner.position.y = 0.76;
|
|
671
|
+
group.add(inner);
|
|
672
|
+
for (const side of [-1, 1]) {
|
|
673
|
+
const post = new import_three8.Mesh(new import_three8.CylinderGeometry(0.06, 0.075, 1.9, 5), wood);
|
|
674
|
+
post.position.set(side * 0.72, 0.95, 0);
|
|
675
|
+
group.add(post);
|
|
676
|
+
}
|
|
677
|
+
const bar = new import_three8.Mesh(new import_three8.CylinderGeometry(0.045, 0.045, 1.5, 5), wood);
|
|
678
|
+
bar.rotation.z = Math.PI / 2;
|
|
679
|
+
bar.position.y = 1.72;
|
|
680
|
+
group.add(bar);
|
|
681
|
+
const cap = new import_three8.Mesh(prismGeometry(2, 0.55, 0.65), roof);
|
|
682
|
+
cap.position.y = 1.86;
|
|
683
|
+
cap.rotation.y = Math.PI / 2;
|
|
684
|
+
group.add(cap);
|
|
685
|
+
const rope = new import_three8.Mesh(new import_three8.CylinderGeometry(0.012, 0.012, 0.6, 4), wood);
|
|
686
|
+
rope.position.y = 1.42;
|
|
687
|
+
group.add(rope);
|
|
688
|
+
const bucket = new import_three8.Mesh(new import_three8.CylinderGeometry(0.13, 0.1, 0.2, 7), wood);
|
|
689
|
+
bucket.position.y = 1.05;
|
|
690
|
+
group.add(bucket);
|
|
691
|
+
return { object: group, obstacleRadius: 1 };
|
|
692
|
+
}
|
|
693
|
+
function createRuin(options = {}) {
|
|
694
|
+
const rng = new Rng(options.seed ?? 1);
|
|
695
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
696
|
+
const size = options.size ?? rng.range(3.5, 5);
|
|
697
|
+
const depth = size * rng.range(0.7, 0.9);
|
|
698
|
+
const group = new import_three8.Group();
|
|
699
|
+
group.name = "ruin";
|
|
700
|
+
const stone = new import_three8.MeshStandardMaterial({ color: rng.pick(palette.rock), flatShading: true });
|
|
701
|
+
stone.color.offsetHSL(0, 0, rng.range(-0.04, 0.02));
|
|
702
|
+
const thickness = 0.35;
|
|
703
|
+
const runs = [
|
|
704
|
+
[0, -depth / 2, size, true],
|
|
705
|
+
[-size / 2, 0, depth, false],
|
|
706
|
+
[size / 2, 0, depth, false],
|
|
707
|
+
[size * 0.3, depth / 2, size * 0.35, true]
|
|
708
|
+
];
|
|
709
|
+
for (const [cx, cz, length, alongX] of runs) {
|
|
710
|
+
const segments = Math.max(2, Math.round(length / 1.1));
|
|
711
|
+
const step = length / segments;
|
|
712
|
+
for (let i = 0; i < segments; i++) {
|
|
713
|
+
if (rng.next() < 0.22) continue;
|
|
714
|
+
const height = rng.range(0.5, 2.2);
|
|
715
|
+
const wall = new import_three8.Mesh(
|
|
716
|
+
new import_three8.BoxGeometry(alongX ? step * 0.96 : thickness, height, alongX ? thickness : step * 0.96),
|
|
717
|
+
stone
|
|
718
|
+
);
|
|
719
|
+
const offset = -length / 2 + step * (i + 0.5);
|
|
720
|
+
wall.position.set(alongX ? cx + offset : cx, height / 2, alongX ? cz : cz + offset);
|
|
721
|
+
wall.rotation.y = rng.range(-0.03, 0.03);
|
|
722
|
+
group.add(wall);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
const blocks = rng.int(4, 7);
|
|
726
|
+
for (let i = 0; i < blocks; i++) {
|
|
727
|
+
const s = rng.range(0.25, 0.55);
|
|
728
|
+
const block = new import_three8.Mesh(new import_three8.BoxGeometry(s, s * rng.range(0.6, 1), s * rng.range(0.7, 1.2)), stone);
|
|
729
|
+
block.position.set(rng.range(-size * 0.6, size * 0.6), s * 0.3, rng.range(-depth * 0.6, depth * 0.7));
|
|
730
|
+
block.rotation.set(rng.range(-0.3, 0.3), rng.range(0, Math.PI), rng.range(-0.3, 0.3));
|
|
731
|
+
group.add(block);
|
|
732
|
+
}
|
|
733
|
+
return { object: group, obstacleRadius: Math.hypot(size, depth) / 2 };
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// src/environment/terrain.ts
|
|
737
|
+
var import_three9 = require("three");
|
|
373
738
|
function createTerrain(options = {}) {
|
|
374
739
|
const seed = options.seed ?? 1;
|
|
375
740
|
const size = options.size ?? 80;
|
|
@@ -384,7 +749,7 @@ function createTerrain(options = {}) {
|
|
|
384
749
|
const shaped = Math.pow(n, 1 + flatness * 2);
|
|
385
750
|
return shaped * amplitude;
|
|
386
751
|
};
|
|
387
|
-
const geometry = new
|
|
752
|
+
const geometry = new import_three9.PlaneGeometry(size, size, resolution - 1, resolution - 1);
|
|
388
753
|
geometry.rotateX(-Math.PI / 2);
|
|
389
754
|
const positions = geometry.getAttribute("position");
|
|
390
755
|
for (let i = 0; i < positions.count; i++) {
|
|
@@ -393,40 +758,46 @@ function createTerrain(options = {}) {
|
|
|
393
758
|
geometry.computeVertexNormals();
|
|
394
759
|
const normals = geometry.getAttribute("normal");
|
|
395
760
|
const colors = new Float32Array(positions.count * 3);
|
|
396
|
-
const grassLow = new
|
|
397
|
-
const grassHigh = new
|
|
398
|
-
const cliff = new
|
|
399
|
-
const peak = new
|
|
400
|
-
const
|
|
761
|
+
const grassLow = new import_three9.Color(palette.grassLow);
|
|
762
|
+
const grassHigh = new import_three9.Color(palette.grassHigh);
|
|
763
|
+
const cliff = new import_three9.Color(palette.cliff);
|
|
764
|
+
const peak = new import_three9.Color(palette.peak);
|
|
765
|
+
const sand = new import_three9.Color(palette.sand);
|
|
766
|
+
const waterLevel = options.waterLevel;
|
|
767
|
+
const scratch = new import_three9.Color();
|
|
401
768
|
for (let i = 0; i < positions.count; i++) {
|
|
402
|
-
const
|
|
769
|
+
const y = positions.getY(i);
|
|
770
|
+
const h = y / amplitude;
|
|
403
771
|
const slope = 1 - normals.getY(i);
|
|
404
772
|
scratch.copy(grassLow).lerp(grassHigh, Math.min(1, h * 1.6));
|
|
405
773
|
if (h > 0.75) scratch.lerp(peak, (h - 0.75) * 4);
|
|
406
774
|
if (slope > 0.15) scratch.lerp(cliff, Math.min(1, (slope - 0.15) * 4));
|
|
775
|
+
if (waterLevel !== void 0 && y < waterLevel + 0.5) {
|
|
776
|
+
scratch.lerp(sand, Math.min(1, (waterLevel + 0.5 - y) * 1.6));
|
|
777
|
+
}
|
|
407
778
|
colors[i * 3] = scratch.r;
|
|
408
779
|
colors[i * 3 + 1] = scratch.g;
|
|
409
780
|
colors[i * 3 + 2] = scratch.b;
|
|
410
781
|
}
|
|
411
|
-
geometry.setAttribute("color", new
|
|
412
|
-
const mesh = new
|
|
782
|
+
geometry.setAttribute("color", new import_three9.BufferAttribute(colors, 3));
|
|
783
|
+
const mesh = new import_three9.Mesh(
|
|
413
784
|
geometry,
|
|
414
|
-
new
|
|
785
|
+
new import_three9.MeshStandardMaterial({ vertexColors: true, flatShading: true })
|
|
415
786
|
);
|
|
416
787
|
mesh.name = "terrain";
|
|
417
788
|
return { mesh, heightAt, size, seed };
|
|
418
789
|
}
|
|
419
790
|
|
|
420
791
|
// src/environment/sky.ts
|
|
421
|
-
var
|
|
792
|
+
var import_three10 = require("three");
|
|
422
793
|
function createSky(options = {}) {
|
|
423
794
|
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
424
|
-
const material = new
|
|
425
|
-
side:
|
|
795
|
+
const material = new import_three10.ShaderMaterial({
|
|
796
|
+
side: import_three10.BackSide,
|
|
426
797
|
depthWrite: false,
|
|
427
798
|
uniforms: {
|
|
428
|
-
topColor: { value: new
|
|
429
|
-
bottomColor: { value: new
|
|
799
|
+
topColor: { value: new import_three10.Color(options.topColor ?? palette.skyTop) },
|
|
800
|
+
bottomColor: { value: new import_three10.Color(options.bottomColor ?? palette.skyBottom) }
|
|
430
801
|
},
|
|
431
802
|
vertexShader: (
|
|
432
803
|
/* glsl */
|
|
@@ -449,7 +820,7 @@ function createSky(options = {}) {
|
|
|
449
820
|
}`
|
|
450
821
|
)
|
|
451
822
|
});
|
|
452
|
-
const mesh = new
|
|
823
|
+
const mesh = new import_three10.Mesh(new import_three10.SphereGeometry(options.radius ?? 400, 16, 12), material);
|
|
453
824
|
mesh.name = "sky";
|
|
454
825
|
return {
|
|
455
826
|
mesh,
|
|
@@ -461,7 +832,7 @@ function createSky(options = {}) {
|
|
|
461
832
|
}
|
|
462
833
|
|
|
463
834
|
// src/environment/lighting.ts
|
|
464
|
-
var
|
|
835
|
+
var import_three11 = require("three");
|
|
465
836
|
var PRESETS = {
|
|
466
837
|
day: {
|
|
467
838
|
sun: 16774368,
|
|
@@ -502,12 +873,12 @@ var PRESETS = {
|
|
|
502
873
|
};
|
|
503
874
|
function createLightingRig(preset = "day") {
|
|
504
875
|
const config = PRESETS[preset];
|
|
505
|
-
const group = new
|
|
876
|
+
const group = new import_three11.Group();
|
|
506
877
|
group.name = `lighting-${preset}`;
|
|
507
|
-
const sun = new
|
|
878
|
+
const sun = new import_three11.DirectionalLight(config.sun, config.sunIntensity);
|
|
508
879
|
sun.position.set(...config.sunPos);
|
|
509
|
-
const ambient = new
|
|
510
|
-
const hemisphere = new
|
|
880
|
+
const ambient = new import_three11.AmbientLight(config.ambient, config.ambientIntensity);
|
|
881
|
+
const hemisphere = new import_three11.HemisphereLight(config.skyTint, config.groundTint, 0.5);
|
|
511
882
|
group.add(sun, ambient, hemisphere);
|
|
512
883
|
return { group, sun, ambient, hemisphere };
|
|
513
884
|
}
|
|
@@ -522,11 +893,512 @@ function applyFog(scene, preset, palette = DEFAULT_PALETTE) {
|
|
|
522
893
|
return;
|
|
523
894
|
}
|
|
524
895
|
const { near, far } = FOG[preset];
|
|
525
|
-
scene.fog = new
|
|
896
|
+
scene.fog = new import_three11.Fog(palette.fog, near, far);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// src/environment/water.ts
|
|
900
|
+
var import_three12 = require("three");
|
|
901
|
+
function createWater(options = {}) {
|
|
902
|
+
const level = options.level ?? 0.8;
|
|
903
|
+
const size = options.size ?? 200;
|
|
904
|
+
const resolution = options.resolution ?? 40;
|
|
905
|
+
const amplitude = options.amplitude ?? 0.06;
|
|
906
|
+
const speed = options.speed ?? 1;
|
|
907
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
908
|
+
const geometry = new import_three12.PlaneGeometry(size, size, resolution, resolution);
|
|
909
|
+
geometry.rotateX(-Math.PI / 2);
|
|
910
|
+
const positions = geometry.getAttribute("position");
|
|
911
|
+
const mesh = new import_three12.Mesh(
|
|
912
|
+
geometry,
|
|
913
|
+
new import_three12.MeshStandardMaterial({
|
|
914
|
+
color: palette.water,
|
|
915
|
+
transparent: true,
|
|
916
|
+
opacity: 0.85,
|
|
917
|
+
flatShading: true,
|
|
918
|
+
metalness: 0.35,
|
|
919
|
+
roughness: 0.4
|
|
920
|
+
})
|
|
921
|
+
);
|
|
922
|
+
mesh.name = "water";
|
|
923
|
+
mesh.position.y = level;
|
|
924
|
+
let time = 0;
|
|
925
|
+
const update = (dt) => {
|
|
926
|
+
time += dt * speed;
|
|
927
|
+
for (let i = 0; i < positions.count; i++) {
|
|
928
|
+
const x = positions.getX(i);
|
|
929
|
+
const z = positions.getZ(i);
|
|
930
|
+
positions.setY(
|
|
931
|
+
i,
|
|
932
|
+
Math.sin(x * 0.35 + time) * Math.cos(z * 0.3 + time * 0.8) * amplitude
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
positions.needsUpdate = true;
|
|
936
|
+
geometry.computeVertexNormals();
|
|
937
|
+
};
|
|
938
|
+
update(0);
|
|
939
|
+
return {
|
|
940
|
+
mesh,
|
|
941
|
+
level,
|
|
942
|
+
update,
|
|
943
|
+
isUnderwater: (groundHeight) => groundHeight < level
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
function aboveWater(terrain, water, margin = 0.25) {
|
|
947
|
+
return (x, z) => terrain.heightAt(x, z) > water.level + margin;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// src/environment/dayCycle.ts
|
|
951
|
+
var import_three13 = require("three");
|
|
952
|
+
function createDayCycle(options = {}) {
|
|
953
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
954
|
+
const dayLength = options.dayLength ?? 60;
|
|
955
|
+
const frames = [
|
|
956
|
+
{ t: 0, skyTop: 725030, skyBottom: 1712960, sun: 9348824, sunIntensity: 0.03, ambientIntensity: 0.06, fog: 1317422 },
|
|
957
|
+
{ t: 0.23, skyTop: 2569052, skyBottom: 6968432, sun: 13605490, sunIntensity: 0.3, ambientIntensity: 0.12, fog: 4866648 },
|
|
958
|
+
{ t: 0.3, skyTop: 4877216, skyBottom: 15247738, sun: 16758881, sunIntensity: 1, ambientIntensity: 0.26, fog: 11569778 },
|
|
959
|
+
{ t: 0.5, skyTop: palette.skyTop, skyBottom: palette.skyBottom, sun: 16774368, sunIntensity: 1.9, ambientIntensity: 0.45, fog: palette.fog },
|
|
960
|
+
{ t: 0.7, skyTop: 4872852, skyBottom: 14718302, sun: 16752720, sunIntensity: 1, ambientIntensity: 0.26, fog: 10517096 },
|
|
961
|
+
{ t: 0.78, skyTop: 2304594, skyBottom: 9065824, sun: 14191194, sunIntensity: 0.25, ambientIntensity: 0.11, fog: 4603476 },
|
|
962
|
+
{ t: 1, skyTop: 725030, skyBottom: 1712960, sun: 9348824, sunIntensity: 0.03, ambientIntensity: 0.06, fog: 1317422 }
|
|
963
|
+
];
|
|
964
|
+
const lampLights = [];
|
|
965
|
+
const lampBulbs = [];
|
|
966
|
+
for (const entry of options.lamps ?? []) {
|
|
967
|
+
const root = entry.isObject3D ? entry : entry.object;
|
|
968
|
+
root.traverse((child) => {
|
|
969
|
+
if (child instanceof import_three13.PointLight) {
|
|
970
|
+
lampLights.push({ light: child, base: child.intensity || 6 });
|
|
971
|
+
}
|
|
972
|
+
const material = child.material;
|
|
973
|
+
if (material?.emissive && material.emissiveIntensity > 0.5) {
|
|
974
|
+
lampBulbs.push({ material, base: material.emissiveIntensity });
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
const colorA = new import_three13.Color();
|
|
979
|
+
const colorB = new import_three13.Color();
|
|
980
|
+
let timeOfDay = options.timeOfDay ?? 0.5;
|
|
981
|
+
let sunElevation = 0;
|
|
982
|
+
const sample = (t) => {
|
|
983
|
+
let a = frames[0];
|
|
984
|
+
let b = frames[frames.length - 1];
|
|
985
|
+
for (let i = 0; i < frames.length - 1; i++) {
|
|
986
|
+
if (t >= frames[i].t && t <= frames[i + 1].t) {
|
|
987
|
+
a = frames[i];
|
|
988
|
+
b = frames[i + 1];
|
|
989
|
+
break;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
const f = b.t === a.t ? 0 : (t - a.t) / (b.t - a.t);
|
|
993
|
+
const lerpHex = (ha, hb) => colorA.setHex(ha).lerp(colorB.setHex(hb), f).getHex();
|
|
994
|
+
return {
|
|
995
|
+
t,
|
|
996
|
+
skyTop: lerpHex(a.skyTop, b.skyTop),
|
|
997
|
+
skyBottom: lerpHex(a.skyBottom, b.skyBottom),
|
|
998
|
+
sun: lerpHex(a.sun, b.sun),
|
|
999
|
+
sunIntensity: a.sunIntensity + (b.sunIntensity - a.sunIntensity) * f,
|
|
1000
|
+
ambientIntensity: a.ambientIntensity + (b.ambientIntensity - a.ambientIntensity) * f,
|
|
1001
|
+
fog: lerpHex(a.fog, b.fog)
|
|
1002
|
+
};
|
|
1003
|
+
};
|
|
1004
|
+
const apply = () => {
|
|
1005
|
+
const t = timeOfDay;
|
|
1006
|
+
const frame = sample(t);
|
|
1007
|
+
const sunAngle = (t - 0.25) * Math.PI * 2;
|
|
1008
|
+
sunElevation = Math.sin(sunAngle);
|
|
1009
|
+
options.sky?.setColors(frame.skyTop, frame.skyBottom);
|
|
1010
|
+
if (options.rig) {
|
|
1011
|
+
const { sun, ambient, hemisphere } = options.rig;
|
|
1012
|
+
sun.color.setHex(frame.sun);
|
|
1013
|
+
sun.intensity = frame.sunIntensity;
|
|
1014
|
+
sun.position.set(Math.cos(sunAngle) * 40, Math.max(sunElevation, -0.2) * 45 + 6, 16);
|
|
1015
|
+
ambient.intensity = frame.ambientIntensity;
|
|
1016
|
+
hemisphere.intensity = frame.ambientIntensity * 1.4;
|
|
1017
|
+
}
|
|
1018
|
+
if (options.scene?.fog && "color" in options.scene.fog) {
|
|
1019
|
+
options.scene.fog.color.setHex(frame.fog);
|
|
1020
|
+
}
|
|
1021
|
+
const night = Math.min(1, Math.max(0, (0.06 - sunElevation) / 0.16));
|
|
1022
|
+
for (const { light, base } of lampLights) light.intensity = base * night;
|
|
1023
|
+
for (const { material, base } of lampBulbs) {
|
|
1024
|
+
material.emissiveIntensity = 0.15 * base + 0.85 * base * night;
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
apply();
|
|
1028
|
+
return {
|
|
1029
|
+
get timeOfDay() {
|
|
1030
|
+
return timeOfDay;
|
|
1031
|
+
},
|
|
1032
|
+
set timeOfDay(t) {
|
|
1033
|
+
timeOfDay = (t % 1 + 1) % 1;
|
|
1034
|
+
apply();
|
|
1035
|
+
},
|
|
1036
|
+
get sunElevation() {
|
|
1037
|
+
return sunElevation;
|
|
1038
|
+
},
|
|
1039
|
+
get isNight() {
|
|
1040
|
+
return sunElevation < 0;
|
|
1041
|
+
},
|
|
1042
|
+
update(dt) {
|
|
1043
|
+
timeOfDay = (timeOfDay + dt / dayLength) % 1;
|
|
1044
|
+
apply();
|
|
1045
|
+
},
|
|
1046
|
+
set(t) {
|
|
1047
|
+
timeOfDay = (t % 1 + 1) % 1;
|
|
1048
|
+
apply();
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
526
1051
|
}
|
|
527
1052
|
|
|
1053
|
+
// src/environment/wind.ts
|
|
1054
|
+
var import_three14 = require("three");
|
|
1055
|
+
function applyWind(target, options = {}) {
|
|
1056
|
+
const strength = options.strength ?? 0.06;
|
|
1057
|
+
const frequency = options.frequency ?? 1.2;
|
|
1058
|
+
const anchor = options.anchorHeight ?? 0.6;
|
|
1059
|
+
const time = { value: 0 };
|
|
1060
|
+
const patched = /* @__PURE__ */ new Set();
|
|
1061
|
+
target.traverse((child) => {
|
|
1062
|
+
if (!(child instanceof import_three14.Mesh)) return;
|
|
1063
|
+
const materials = Array.isArray(child.material) ? child.material : [child.material];
|
|
1064
|
+
for (const material of materials) {
|
|
1065
|
+
if (patched.has(material)) continue;
|
|
1066
|
+
patched.add(material);
|
|
1067
|
+
material.onBeforeCompile = (shader) => {
|
|
1068
|
+
shader.uniforms.uWindTime = time;
|
|
1069
|
+
shader.vertexShader = shader.vertexShader.replace(
|
|
1070
|
+
"#include <common>",
|
|
1071
|
+
`#include <common>
|
|
1072
|
+
uniform float uWindTime;`
|
|
1073
|
+
).replace(
|
|
1074
|
+
"#include <begin_vertex>",
|
|
1075
|
+
`#include <begin_vertex>
|
|
1076
|
+
{
|
|
1077
|
+
float sway = max(position.y - ${anchor.toFixed(3)}, 0.0);
|
|
1078
|
+
float phase = 0.0;
|
|
1079
|
+
#ifdef USE_INSTANCING
|
|
1080
|
+
phase = instanceMatrix[3].x * 0.7 + instanceMatrix[3].z * 1.3;
|
|
1081
|
+
#endif
|
|
1082
|
+
float w = sin(uWindTime * ${(frequency * 2).toFixed(3)} + phase + position.y * 0.8);
|
|
1083
|
+
transformed.x += w * sway * ${strength.toFixed(4)} * 4.0;
|
|
1084
|
+
transformed.z += cos(uWindTime * ${(frequency * 1.4).toFixed(3)} + phase) * sway * ${strength.toFixed(4)} * 2.0;
|
|
1085
|
+
}`
|
|
1086
|
+
);
|
|
1087
|
+
};
|
|
1088
|
+
material.needsUpdate = true;
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
return {
|
|
1092
|
+
update(dt) {
|
|
1093
|
+
time.value += dt;
|
|
1094
|
+
},
|
|
1095
|
+
materials: [...patched]
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// src/environment/path.ts
|
|
1100
|
+
var import_three15 = require("three");
|
|
1101
|
+
function createPath(points, options = {}) {
|
|
1102
|
+
const width = options.width ?? 1.8;
|
|
1103
|
+
const surface = options.surface ?? 0;
|
|
1104
|
+
const heightAt = typeof surface === "number" ? () => surface : (x, z) => surface(x, z);
|
|
1105
|
+
const loop = options.loop ?? false;
|
|
1106
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
1107
|
+
const controls = points.map((p) => new import_three15.Vector3(p.x, 0, "z" in p ? p.z : 0));
|
|
1108
|
+
const curve = new import_three15.CatmullRomCurve3(controls, loop, "centripetal");
|
|
1109
|
+
const length = curve.getLength();
|
|
1110
|
+
const samples = Math.max(8, Math.ceil(length * (options.samplesPerUnit ?? 1)));
|
|
1111
|
+
const route = [];
|
|
1112
|
+
for (let i = 0; i <= samples; i++) {
|
|
1113
|
+
if (loop && i === samples) break;
|
|
1114
|
+
const p = curve.getPoint(i / samples);
|
|
1115
|
+
route.push(new import_three15.Vector3(p.x, heightAt(p.x, p.z), p.z));
|
|
1116
|
+
}
|
|
1117
|
+
const edgeCount = loop ? route.length + 1 : route.length;
|
|
1118
|
+
const positions = new Float32Array(edgeCount * 2 * 3);
|
|
1119
|
+
const direction = new import_three15.Vector3();
|
|
1120
|
+
const perp = new import_three15.Vector3();
|
|
1121
|
+
for (let i = 0; i < edgeCount; i++) {
|
|
1122
|
+
const current = route[i % route.length];
|
|
1123
|
+
const previous = route[(i - 1 + route.length) % route.length];
|
|
1124
|
+
const next = route[(i + 1) % route.length];
|
|
1125
|
+
if (!loop && i === 0) direction.subVectors(next, current);
|
|
1126
|
+
else if (!loop && i === edgeCount - 1) direction.subVectors(current, previous);
|
|
1127
|
+
else direction.subVectors(next, previous);
|
|
1128
|
+
perp.set(-direction.z, 0, direction.x).normalize().multiplyScalar(width / 2);
|
|
1129
|
+
const left = i * 6;
|
|
1130
|
+
positions[left] = current.x - perp.x;
|
|
1131
|
+
positions[left + 1] = heightAt(current.x - perp.x, current.z - perp.z) + 0.05;
|
|
1132
|
+
positions[left + 2] = current.z - perp.z;
|
|
1133
|
+
positions[left + 3] = current.x + perp.x;
|
|
1134
|
+
positions[left + 4] = heightAt(current.x + perp.x, current.z + perp.z) + 0.05;
|
|
1135
|
+
positions[left + 5] = current.z + perp.z;
|
|
1136
|
+
}
|
|
1137
|
+
const indices = [];
|
|
1138
|
+
for (let i = 0; i < edgeCount - 1; i++) {
|
|
1139
|
+
const a = i * 2;
|
|
1140
|
+
indices.push(a, a + 1, a + 2, a + 1, a + 3, a + 2);
|
|
1141
|
+
}
|
|
1142
|
+
const geometry = new import_three15.BufferGeometry();
|
|
1143
|
+
geometry.setAttribute("position", new import_three15.BufferAttribute(positions, 3));
|
|
1144
|
+
geometry.setIndex(indices);
|
|
1145
|
+
geometry.computeVertexNormals();
|
|
1146
|
+
const mesh = new import_three15.Mesh(
|
|
1147
|
+
geometry,
|
|
1148
|
+
new import_three15.MeshStandardMaterial({
|
|
1149
|
+
color: palette.path,
|
|
1150
|
+
flatShading: true,
|
|
1151
|
+
polygonOffset: true,
|
|
1152
|
+
polygonOffsetFactor: -1
|
|
1153
|
+
})
|
|
1154
|
+
);
|
|
1155
|
+
mesh.name = "path";
|
|
1156
|
+
const keepOutMargin = options.keepOutMargin ?? 0.6;
|
|
1157
|
+
const keepOut = route.filter((_p, i) => i % 2 === 0 || i === route.length - 1).map((p) => ({ center: { x: p.x, z: p.z }, radius: width / 2 + keepOutMargin }));
|
|
1158
|
+
const half = width / 2;
|
|
1159
|
+
const contains = (x, z) => {
|
|
1160
|
+
const count = loop ? route.length : route.length - 1;
|
|
1161
|
+
for (let i = 0; i < count; i++) {
|
|
1162
|
+
const a = route[i];
|
|
1163
|
+
const b = route[(i + 1) % route.length];
|
|
1164
|
+
const abx = b.x - a.x;
|
|
1165
|
+
const abz = b.z - a.z;
|
|
1166
|
+
const lengthSq = abx * abx + abz * abz || 1e-9;
|
|
1167
|
+
const t = Math.max(0, Math.min(1, ((x - a.x) * abx + (z - a.z) * abz) / lengthSq));
|
|
1168
|
+
const dx = x - (a.x + abx * t);
|
|
1169
|
+
const dz = z - (a.z + abz * t);
|
|
1170
|
+
if (dx * dx + dz * dz <= half * half) return true;
|
|
1171
|
+
}
|
|
1172
|
+
return false;
|
|
1173
|
+
};
|
|
1174
|
+
return { mesh, route, keepOut, contains, loop };
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// src/generators/village.ts
|
|
1178
|
+
var import_three16 = require("three");
|
|
1179
|
+
function createVillage(options = {}) {
|
|
1180
|
+
const rng = new Rng(options.seed ?? 1);
|
|
1181
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
1182
|
+
const center = options.center ?? { x: 0, z: 0 };
|
|
1183
|
+
const radius = options.radius ?? 12;
|
|
1184
|
+
const houseCount = options.houses ?? 5;
|
|
1185
|
+
const surface = options.surface ?? 0;
|
|
1186
|
+
const heightAt = typeof surface === "number" ? () => surface : (x, z) => surface(x, z);
|
|
1187
|
+
const group = new import_three16.Group();
|
|
1188
|
+
group.name = "village";
|
|
1189
|
+
const props = [];
|
|
1190
|
+
const lamps = [];
|
|
1191
|
+
const place = (prop, x, z, rotY) => {
|
|
1192
|
+
prop.object.position.set(x, heightAt(x, z), z);
|
|
1193
|
+
prop.object.rotation.y = rotY;
|
|
1194
|
+
group.add(prop.object);
|
|
1195
|
+
props.push(prop);
|
|
1196
|
+
return prop;
|
|
1197
|
+
};
|
|
1198
|
+
const findSpot = (angle, distance, footprint) => {
|
|
1199
|
+
let x = center.x;
|
|
1200
|
+
let z = center.z;
|
|
1201
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
1202
|
+
const a = angle + rng.range(-0.25, 0.25) * (attempt > 0 ? 1 : 0.3);
|
|
1203
|
+
const d = distance * rng.range(attempt > 0 ? 0.8 : 0.95, 1.1);
|
|
1204
|
+
x = center.x + Math.cos(a) * d;
|
|
1205
|
+
z = center.z + Math.sin(a) * d;
|
|
1206
|
+
const y = heightAt(x, z);
|
|
1207
|
+
if (options.mask && !options.mask(x, z, y)) continue;
|
|
1208
|
+
let min = y;
|
|
1209
|
+
let max = y;
|
|
1210
|
+
for (const [dx, dz] of [[footprint, 0], [-footprint, 0], [0, footprint], [0, -footprint]]) {
|
|
1211
|
+
const h = heightAt(x + dx, z + dz);
|
|
1212
|
+
min = Math.min(min, h);
|
|
1213
|
+
max = Math.max(max, h);
|
|
1214
|
+
}
|
|
1215
|
+
if (max - min <= Math.max(0.9, footprint * 0.35)) break;
|
|
1216
|
+
}
|
|
1217
|
+
return { x, z };
|
|
1218
|
+
};
|
|
1219
|
+
const well = createWell({ seed: rng.int(1, 1e9), palette });
|
|
1220
|
+
place(well, center.x, center.z, rng.range(0, Math.PI * 2));
|
|
1221
|
+
let lightBudget = options.lampLights ?? 3;
|
|
1222
|
+
for (let i = 0; i < houseCount; i++) {
|
|
1223
|
+
const angle = i / houseCount * Math.PI * 2 + rng.range(-0.15, 0.15);
|
|
1224
|
+
const house = createHouse({ seed: rng.int(1, 1e9), palette });
|
|
1225
|
+
const spot = findSpot(angle, radius * rng.range(0.7, 0.95), house.obstacleRadius);
|
|
1226
|
+
const facing = Math.atan2(center.x - spot.x, center.z - spot.z);
|
|
1227
|
+
place(house, spot.x, spot.z, facing);
|
|
1228
|
+
lamps.push(house);
|
|
1229
|
+
if (rng.next() < 0.7) {
|
|
1230
|
+
const crateAngle = angle + rng.range(-0.5, 0.5);
|
|
1231
|
+
const crateDistance = Math.hypot(spot.x - center.x, spot.z - center.z) - house.obstacleRadius - 0.7;
|
|
1232
|
+
place(
|
|
1233
|
+
createCrate({ seed: rng.int(1, 1e9), size: rng.range(0.7, 1), palette }),
|
|
1234
|
+
center.x + Math.cos(crateAngle) * crateDistance,
|
|
1235
|
+
center.z + Math.sin(crateAngle) * crateDistance,
|
|
1236
|
+
rng.range(0, Math.PI)
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
if (i % 2 === 0) {
|
|
1240
|
+
const lampDistance = Math.hypot(spot.x - center.x, spot.z - center.z) * 0.55;
|
|
1241
|
+
const lamp = createLamp({ seed: rng.int(1, 1e9), light: lightBudget > 0, palette });
|
|
1242
|
+
lightBudget--;
|
|
1243
|
+
place(lamp, center.x + Math.cos(angle) * lampDistance, center.z + Math.sin(angle) * lampDistance, 0);
|
|
1244
|
+
lamps.push(lamp);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
if (options.tower ?? true) {
|
|
1248
|
+
const angle = rng.range(0, Math.PI * 2);
|
|
1249
|
+
const tower = createTower({ seed: rng.int(1, 1e9), palette });
|
|
1250
|
+
const spot = findSpot(angle, radius * 1.05, tower.obstacleRadius);
|
|
1251
|
+
place(tower, spot.x, spot.z, rng.range(0, Math.PI * 2));
|
|
1252
|
+
}
|
|
1253
|
+
if (options.ruin ?? true) {
|
|
1254
|
+
const angle = rng.range(0, Math.PI * 2);
|
|
1255
|
+
const ruin = createRuin({ seed: rng.int(1, 1e9), palette });
|
|
1256
|
+
const spot = findSpot(angle, radius * 1.35, ruin.obstacleRadius);
|
|
1257
|
+
place(ruin, spot.x, spot.z, rng.range(0, Math.PI * 2));
|
|
1258
|
+
}
|
|
1259
|
+
return {
|
|
1260
|
+
group,
|
|
1261
|
+
props,
|
|
1262
|
+
obstacles: collectObstacles(props),
|
|
1263
|
+
lamps,
|
|
1264
|
+
keepOut: [{ center: { ...center }, radius: radius * 1.5 }],
|
|
1265
|
+
center
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// src/kits/kit.ts
|
|
1270
|
+
var import_three17 = require("three");
|
|
1271
|
+
var KIT_UNIT = 2;
|
|
1272
|
+
function assembleKit(rows, options = {}) {
|
|
1273
|
+
const rng = new Rng(options.seed ?? 1);
|
|
1274
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
1275
|
+
const unit = options.unit ?? KIT_UNIT;
|
|
1276
|
+
const wallHeight = options.wallHeight ?? 2.6;
|
|
1277
|
+
let torchLights = options.torchLights ?? 4;
|
|
1278
|
+
const height = rows.length;
|
|
1279
|
+
const width = Math.max(...rows.map((r) => r.length), 0);
|
|
1280
|
+
const originX = (-width / 2 + 0.5) * unit;
|
|
1281
|
+
const originZ = (-height / 2 + 0.5) * unit;
|
|
1282
|
+
const worldOf = (col, row) => ({
|
|
1283
|
+
x: originX + col * unit,
|
|
1284
|
+
z: originZ + row * unit
|
|
1285
|
+
});
|
|
1286
|
+
const group = new import_three17.Group();
|
|
1287
|
+
group.name = "kit";
|
|
1288
|
+
const obstacles = [];
|
|
1289
|
+
const spawns = [];
|
|
1290
|
+
const torches = [];
|
|
1291
|
+
const floorCells = /* @__PURE__ */ new Set();
|
|
1292
|
+
const wallCells = [];
|
|
1293
|
+
const floorTiles = [];
|
|
1294
|
+
for (let row = 0; row < height; row++) {
|
|
1295
|
+
for (let col = 0; col < (rows[row] ?? "").length; col++) {
|
|
1296
|
+
const cell = rows[row][col];
|
|
1297
|
+
if (cell === " " || cell === void 0) continue;
|
|
1298
|
+
const { x, z } = worldOf(col, row);
|
|
1299
|
+
if (cell === "#") {
|
|
1300
|
+
wallCells.push({ x, z });
|
|
1301
|
+
obstacles.push({ center: new import_three17.Vector3(x, wallHeight / 2, z), radius: unit * 0.71 });
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
floorTiles.push({ x, z });
|
|
1305
|
+
floorCells.add(`${col},${row}`);
|
|
1306
|
+
if (cell === "D") {
|
|
1307
|
+
const horizontalRun = rows[row][col - 1] === "#" || rows[row][col + 1] === "#";
|
|
1308
|
+
const lintel = new import_three17.Mesh(
|
|
1309
|
+
new import_three17.BoxGeometry(
|
|
1310
|
+
horizontalRun ? unit : unit * 0.4,
|
|
1311
|
+
wallHeight * 0.25,
|
|
1312
|
+
horizontalRun ? unit * 0.4 : unit
|
|
1313
|
+
),
|
|
1314
|
+
new import_three17.MeshStandardMaterial({ color: palette.woodDark, flatShading: true })
|
|
1315
|
+
);
|
|
1316
|
+
lintel.position.set(x, wallHeight * 0.875, z);
|
|
1317
|
+
group.add(lintel);
|
|
1318
|
+
} else if (cell === "T") {
|
|
1319
|
+
group.add(createTorch(x, z, palette, rng, torchLights > 0));
|
|
1320
|
+
torchLights--;
|
|
1321
|
+
torches.push(new import_three17.Vector3(x, 0, z));
|
|
1322
|
+
obstacles.push({ center: new import_three17.Vector3(x, 0, z), radius: 0.3 });
|
|
1323
|
+
} else if (cell === "S") {
|
|
1324
|
+
spawns.push(new import_three17.Vector3(x, 0, z));
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
const stone = new import_three17.MeshStandardMaterial({ color: rng.pick(palette.rock), flatShading: true });
|
|
1329
|
+
const stoneDark = new import_three17.MeshStandardMaterial({ color: palette.cliff, flatShading: true });
|
|
1330
|
+
const matrix = new import_three17.Matrix4();
|
|
1331
|
+
if (wallCells.length > 0) {
|
|
1332
|
+
const walls = new import_three17.InstancedMesh(
|
|
1333
|
+
new import_three17.BoxGeometry(unit, wallHeight, unit),
|
|
1334
|
+
stone,
|
|
1335
|
+
wallCells.length
|
|
1336
|
+
);
|
|
1337
|
+
wallCells.forEach((cell, i) => {
|
|
1338
|
+
walls.setMatrixAt(i, matrix.makeTranslation(cell.x, wallHeight / 2, cell.z));
|
|
1339
|
+
});
|
|
1340
|
+
walls.instanceMatrix.needsUpdate = true;
|
|
1341
|
+
group.add(walls);
|
|
1342
|
+
}
|
|
1343
|
+
if (floorTiles.length > 0) {
|
|
1344
|
+
const floors = new import_three17.InstancedMesh(
|
|
1345
|
+
new import_three17.BoxGeometry(unit, 0.2, unit),
|
|
1346
|
+
stoneDark,
|
|
1347
|
+
floorTiles.length
|
|
1348
|
+
);
|
|
1349
|
+
floorTiles.forEach((cell, i) => {
|
|
1350
|
+
floors.setMatrixAt(i, matrix.makeTranslation(cell.x, -0.1, cell.z));
|
|
1351
|
+
});
|
|
1352
|
+
floors.instanceMatrix.needsUpdate = true;
|
|
1353
|
+
group.add(floors);
|
|
1354
|
+
}
|
|
1355
|
+
return {
|
|
1356
|
+
group,
|
|
1357
|
+
obstacles,
|
|
1358
|
+
spawns,
|
|
1359
|
+
torches,
|
|
1360
|
+
floorAt(x, z) {
|
|
1361
|
+
const col = Math.round((x - originX) / unit);
|
|
1362
|
+
const row = Math.round((z - originZ) / unit);
|
|
1363
|
+
return floorCells.has(`${col},${row}`);
|
|
1364
|
+
},
|
|
1365
|
+
size: { width: width * unit, depth: height * unit }
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
function createTorch(x, z, palette, rng, light) {
|
|
1369
|
+
const torch = new import_three17.Group();
|
|
1370
|
+
torch.name = "torch";
|
|
1371
|
+
const pole = new import_three17.Mesh(
|
|
1372
|
+
new import_three17.CylinderGeometry(0.04, 0.06, 1.5, 5),
|
|
1373
|
+
new import_three17.MeshStandardMaterial({ color: palette.woodDark, flatShading: true })
|
|
1374
|
+
);
|
|
1375
|
+
pole.position.y = 0.75;
|
|
1376
|
+
torch.add(pole);
|
|
1377
|
+
const flame = new import_three17.Mesh(
|
|
1378
|
+
new import_three17.SphereGeometry(0.1, 6, 5),
|
|
1379
|
+
new import_three17.MeshStandardMaterial({
|
|
1380
|
+
color: palette.lampGlow,
|
|
1381
|
+
emissive: palette.lampGlow,
|
|
1382
|
+
emissiveIntensity: 1.8
|
|
1383
|
+
})
|
|
1384
|
+
);
|
|
1385
|
+
flame.position.y = 1.58;
|
|
1386
|
+
flame.scale.y = rng.range(1.2, 1.5);
|
|
1387
|
+
torch.add(flame);
|
|
1388
|
+
if (light) {
|
|
1389
|
+
const point = new import_three17.PointLight(palette.lampGlow, 5, 9, 1.9);
|
|
1390
|
+
point.position.y = 1.6;
|
|
1391
|
+
torch.add(point);
|
|
1392
|
+
}
|
|
1393
|
+
torch.position.set(x, 0, z);
|
|
1394
|
+
return torch;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// src/scene/manifest.ts
|
|
1398
|
+
var import_three19 = require("three");
|
|
1399
|
+
|
|
528
1400
|
// src/scatter/scatter.ts
|
|
529
|
-
var
|
|
1401
|
+
var import_three18 = require("three");
|
|
530
1402
|
function scatter(options) {
|
|
531
1403
|
const seed = options.seed ?? 1;
|
|
532
1404
|
const rng = new Rng(seed);
|
|
@@ -581,7 +1453,7 @@ function scatter(options) {
|
|
|
581
1453
|
const itemIndex = pickItem();
|
|
582
1454
|
const [minScale, maxScale2] = options.items[itemIndex].scale ?? [0.8, 1.25];
|
|
583
1455
|
placements.push({
|
|
584
|
-
position: new
|
|
1456
|
+
position: new import_three18.Vector3(x, y, z),
|
|
585
1457
|
rotationY: rng.range(0, Math.PI * 2),
|
|
586
1458
|
scale: rng.range(minScale, maxScale2),
|
|
587
1459
|
itemIndex
|
|
@@ -591,18 +1463,59 @@ function scatter(options) {
|
|
|
591
1463
|
if (!bucket) occupied.set(key, bucket = []);
|
|
592
1464
|
bucket.push({ x, z });
|
|
593
1465
|
}
|
|
594
|
-
const group = new
|
|
1466
|
+
const group = new import_three18.Group();
|
|
595
1467
|
group.name = "scatter";
|
|
596
1468
|
const obstacles = [];
|
|
597
|
-
const placementMatrix = new
|
|
598
|
-
const composed = new
|
|
599
|
-
const quaternion = new
|
|
600
|
-
const up = new
|
|
601
|
-
const scaleVector = new
|
|
1469
|
+
const placementMatrix = new import_three18.Matrix4();
|
|
1470
|
+
const composed = new import_three18.Matrix4();
|
|
1471
|
+
const quaternion = new import_three18.Quaternion();
|
|
1472
|
+
const up = new import_three18.Vector3(0, 1, 0);
|
|
1473
|
+
const scaleVector = new import_three18.Vector3();
|
|
1474
|
+
const emit = (variant, list, parent) => {
|
|
1475
|
+
variant.object.updateMatrixWorld(true);
|
|
1476
|
+
variant.object.traverse((child) => {
|
|
1477
|
+
if (!(child instanceof import_three18.Mesh)) return;
|
|
1478
|
+
const instanced = new import_three18.InstancedMesh(child.geometry, child.material, list.length);
|
|
1479
|
+
instanced.instanceMatrix.setUsage(import_three18.DynamicDrawUsage);
|
|
1480
|
+
list.forEach((placement, index) => {
|
|
1481
|
+
quaternion.setFromAxisAngle(up, placement.rotationY);
|
|
1482
|
+
scaleVector.setScalar(placement.scale);
|
|
1483
|
+
placementMatrix.compose(placement.position, quaternion, scaleVector);
|
|
1484
|
+
composed.multiplyMatrices(placementMatrix, child.matrixWorld);
|
|
1485
|
+
instanced.setMatrixAt(index, composed);
|
|
1486
|
+
});
|
|
1487
|
+
instanced.instanceMatrix.needsUpdate = true;
|
|
1488
|
+
parent.add(instanced);
|
|
1489
|
+
});
|
|
1490
|
+
};
|
|
1491
|
+
const tileSize = options.lod?.tileSize ?? 16;
|
|
1492
|
+
const tileMap = /* @__PURE__ */ new Map();
|
|
1493
|
+
const tileFor = (x, z) => {
|
|
1494
|
+
const tx = Math.floor(x / tileSize);
|
|
1495
|
+
const tz = Math.floor(z / tileSize);
|
|
1496
|
+
const key = `${tx},${tz}`;
|
|
1497
|
+
let tile = tileMap.get(key);
|
|
1498
|
+
if (!tile) {
|
|
1499
|
+
tile = {
|
|
1500
|
+
center: { x: (tx + 0.5) * tileSize, z: (tz + 0.5) * tileSize },
|
|
1501
|
+
near: new import_three18.Group(),
|
|
1502
|
+
far: new import_three18.Group()
|
|
1503
|
+
};
|
|
1504
|
+
tile.far.visible = false;
|
|
1505
|
+
group.add(tile.near, tile.far);
|
|
1506
|
+
tileMap.set(key, tile);
|
|
1507
|
+
}
|
|
1508
|
+
return tile;
|
|
1509
|
+
};
|
|
602
1510
|
options.items.forEach((item, itemIndex) => {
|
|
603
1511
|
const variantCount = item.variants ?? 4;
|
|
604
1512
|
const variants = [];
|
|
605
1513
|
for (let v = 0; v < variantCount; v++) variants.push(item.create(rng.fork()));
|
|
1514
|
+
const useLod = options.lod !== void 0 && item.createFar !== void 0;
|
|
1515
|
+
const farVariants = [];
|
|
1516
|
+
if (useLod) {
|
|
1517
|
+
for (let v = 0; v < variantCount; v++) farVariants.push(item.createFar(rng.fork()));
|
|
1518
|
+
}
|
|
606
1519
|
const byVariant = variants.map(() => []);
|
|
607
1520
|
for (const placement of placements) {
|
|
608
1521
|
if (placement.itemIndex !== itemIndex) continue;
|
|
@@ -612,21 +1525,21 @@ function scatter(options) {
|
|
|
612
1525
|
variants.forEach((variant, v) => {
|
|
613
1526
|
const list = byVariant[v];
|
|
614
1527
|
if (list.length === 0) return;
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
group
|
|
629
|
-
}
|
|
1528
|
+
if (useLod) {
|
|
1529
|
+
const byTile = /* @__PURE__ */ new Map();
|
|
1530
|
+
for (const placement of list) {
|
|
1531
|
+
const tile = tileFor(placement.position.x, placement.position.z);
|
|
1532
|
+
let bucket = byTile.get(tile);
|
|
1533
|
+
if (!bucket) byTile.set(tile, bucket = []);
|
|
1534
|
+
bucket.push(placement);
|
|
1535
|
+
}
|
|
1536
|
+
for (const [tile, bucket] of byTile) {
|
|
1537
|
+
emit(variant, bucket, tile.near);
|
|
1538
|
+
emit(farVariants[v], bucket, tile.far);
|
|
1539
|
+
}
|
|
1540
|
+
} else {
|
|
1541
|
+
emit(variant, list, group);
|
|
1542
|
+
}
|
|
630
1543
|
if (variant.obstacleRadius > 0) {
|
|
631
1544
|
for (const placement of list) {
|
|
632
1545
|
obstacles.push({
|
|
@@ -637,23 +1550,263 @@ function scatter(options) {
|
|
|
637
1550
|
}
|
|
638
1551
|
});
|
|
639
1552
|
});
|
|
640
|
-
|
|
1553
|
+
const result = { group, placements, obstacles, count: placements.length };
|
|
1554
|
+
if (options.lod) {
|
|
1555
|
+
const tiles = [...tileMap.values()];
|
|
1556
|
+
const swapOut = options.lod.distance * 1.1;
|
|
1557
|
+
const swapIn = options.lod.distance * 0.9;
|
|
1558
|
+
result.tiles = tiles;
|
|
1559
|
+
result.update = (camera) => {
|
|
1560
|
+
for (const tile of tiles) {
|
|
1561
|
+
const dx = camera.position.x - tile.center.x;
|
|
1562
|
+
const dz = camera.position.z - tile.center.z;
|
|
1563
|
+
const distance = Math.hypot(dx, dz);
|
|
1564
|
+
if (tile.near.visible && distance > swapOut) {
|
|
1565
|
+
tile.near.visible = false;
|
|
1566
|
+
tile.far.visible = true;
|
|
1567
|
+
} else if (!tile.near.visible && distance < swapIn) {
|
|
1568
|
+
tile.near.visible = true;
|
|
1569
|
+
tile.far.visible = false;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
return result;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
// src/scene/manifest.ts
|
|
1578
|
+
var PROP_FACTORIES = {
|
|
1579
|
+
tree: (seed, palette) => createTree({ seed, palette }),
|
|
1580
|
+
rock: (seed, palette) => createRock({ seed, palette }),
|
|
1581
|
+
bush: (seed, palette) => createBush({ seed, palette }),
|
|
1582
|
+
grass: (seed, palette) => createGrassTuft({ seed, palette }),
|
|
1583
|
+
crate: (seed, palette) => createCrate({ seed, palette }),
|
|
1584
|
+
fence: (seed, palette) => createFence({ seed, palette }),
|
|
1585
|
+
lamp: (seed, palette) => createLamp({ seed, palette })
|
|
1586
|
+
};
|
|
1587
|
+
var FAR_FACTORIES = {
|
|
1588
|
+
tree: (seed, palette) => {
|
|
1589
|
+
const rng = new Rng(seed);
|
|
1590
|
+
const group = new import_three19.Group();
|
|
1591
|
+
const cone = new import_three19.Mesh(
|
|
1592
|
+
new import_three19.CylinderGeometry(0, rng.range(1, 1.4), rng.range(2.6, 3.8), 5),
|
|
1593
|
+
new import_three19.MeshStandardMaterial({ color: rng.pick(palette.foliage), flatShading: true })
|
|
1594
|
+
);
|
|
1595
|
+
cone.position.y = cone.geometry.parameters.height / 2 + 0.3;
|
|
1596
|
+
group.add(cone);
|
|
1597
|
+
return { object: group, obstacleRadius: 0 };
|
|
1598
|
+
},
|
|
1599
|
+
bush: (seed, palette) => {
|
|
1600
|
+
const rng = new Rng(seed);
|
|
1601
|
+
const group = new import_three19.Group();
|
|
1602
|
+
const cone = new import_three19.Mesh(
|
|
1603
|
+
new import_three19.CylinderGeometry(0.1, rng.range(0.5, 0.7), rng.range(0.5, 0.8), 5),
|
|
1604
|
+
new import_three19.MeshStandardMaterial({ color: rng.pick(palette.foliage), flatShading: true })
|
|
1605
|
+
);
|
|
1606
|
+
cone.position.y = 0.3;
|
|
1607
|
+
group.add(cone);
|
|
1608
|
+
return { object: group, obstacleRadius: 0 };
|
|
1609
|
+
},
|
|
1610
|
+
grass: () => ({ object: new import_three19.Group(), obstacleRadius: 0 })
|
|
1611
|
+
};
|
|
1612
|
+
function buildScene(manifest, scene) {
|
|
1613
|
+
const seed = manifest.seed ?? 1;
|
|
1614
|
+
const palette = manifest.palette ? PALETTES[manifest.palette] : DEFAULT_PALETTE;
|
|
1615
|
+
const group = new import_three19.Group();
|
|
1616
|
+
group.name = "scena-scene";
|
|
1617
|
+
const updates = [];
|
|
1618
|
+
let terrain;
|
|
1619
|
+
if (manifest.terrain) {
|
|
1620
|
+
terrain = createTerrain({
|
|
1621
|
+
...manifest.terrain,
|
|
1622
|
+
seed,
|
|
1623
|
+
waterLevel: manifest.water?.level,
|
|
1624
|
+
palette
|
|
1625
|
+
});
|
|
1626
|
+
group.add(terrain.mesh);
|
|
1627
|
+
}
|
|
1628
|
+
const heightAt = terrain ? terrain.heightAt : () => 0;
|
|
1629
|
+
let water;
|
|
1630
|
+
if (manifest.water) {
|
|
1631
|
+
water = createWater({
|
|
1632
|
+
level: manifest.water.level,
|
|
1633
|
+
size: manifest.water.size ?? (terrain ? terrain.size * 1.4 : 200),
|
|
1634
|
+
palette
|
|
1635
|
+
});
|
|
1636
|
+
group.add(water.mesh);
|
|
1637
|
+
updates.push((dt) => water.update(dt));
|
|
1638
|
+
}
|
|
1639
|
+
const dryLand = terrain && water ? aboveWater(terrain, water, 0.3) : () => true;
|
|
1640
|
+
let sky;
|
|
1641
|
+
if (manifest.sky ?? true) {
|
|
1642
|
+
sky = createSky({ palette });
|
|
1643
|
+
group.add(sky.mesh);
|
|
1644
|
+
}
|
|
1645
|
+
const rig = createLightingRig(manifest.lighting ?? "day");
|
|
1646
|
+
group.add(rig.group);
|
|
1647
|
+
if (scene && manifest.fog !== false) applyFog(scene, manifest.fog ?? "haze", palette);
|
|
1648
|
+
const paths = (manifest.paths ?? []).map((spec) => {
|
|
1649
|
+
const path = createPath(spec.points, {
|
|
1650
|
+
surface: heightAt,
|
|
1651
|
+
width: spec.width,
|
|
1652
|
+
loop: spec.loop,
|
|
1653
|
+
palette
|
|
1654
|
+
});
|
|
1655
|
+
group.add(path.mesh);
|
|
1656
|
+
return path;
|
|
1657
|
+
});
|
|
1658
|
+
const onAnyPath = (x, z) => paths.some((p) => p.contains(x, z));
|
|
1659
|
+
let village;
|
|
1660
|
+
if (manifest.village) {
|
|
1661
|
+
village = createVillage({
|
|
1662
|
+
...manifest.village,
|
|
1663
|
+
seed: seed + 1,
|
|
1664
|
+
surface: heightAt,
|
|
1665
|
+
mask: (x, z) => dryLand(x, z) && !onAnyPath(x, z),
|
|
1666
|
+
palette
|
|
1667
|
+
});
|
|
1668
|
+
group.add(village.group);
|
|
1669
|
+
}
|
|
1670
|
+
const wind = manifest.wind ?? true;
|
|
1671
|
+
const scatters = (manifest.scatters ?? []).map((spec, index) => {
|
|
1672
|
+
const inset = terrain ? terrain.size * 0.45 : 40;
|
|
1673
|
+
const avoidWater = spec.avoidWater ?? true;
|
|
1674
|
+
const avoidPaths = spec.avoidPaths ?? true;
|
|
1675
|
+
const result = scatter({
|
|
1676
|
+
seed: seed + 10 + index,
|
|
1677
|
+
area: spec.area ?? { min: { x: -inset, z: -inset }, max: { x: inset, z: inset } },
|
|
1678
|
+
surface: heightAt,
|
|
1679
|
+
density: spec.density,
|
|
1680
|
+
count: spec.count,
|
|
1681
|
+
minSpacing: spec.minSpacing,
|
|
1682
|
+
clumpScale: spec.clumpScale,
|
|
1683
|
+
lod: spec.lod,
|
|
1684
|
+
items: spec.items.map((item) => {
|
|
1685
|
+
const far = spec.lod ? FAR_FACTORIES[item.type] : void 0;
|
|
1686
|
+
return {
|
|
1687
|
+
create: (rng) => PROP_FACTORIES[item.type](rng.int(1, 1e9), palette),
|
|
1688
|
+
createFar: far && ((rng) => far(rng.int(1, 1e9), palette)),
|
|
1689
|
+
weight: item.weight,
|
|
1690
|
+
variants: item.variants,
|
|
1691
|
+
scale: item.scale
|
|
1692
|
+
};
|
|
1693
|
+
}),
|
|
1694
|
+
mask: (x, z, y) => (spec.maxHeight === void 0 || y < spec.maxHeight) && (!avoidWater || dryLand(x, z)) && (!avoidPaths || !onAnyPath(x, z)),
|
|
1695
|
+
keepOut: [
|
|
1696
|
+
...avoidPaths ? paths.flatMap((p) => p.keepOut) : [],
|
|
1697
|
+
...village ? village.keepOut : []
|
|
1698
|
+
]
|
|
1699
|
+
});
|
|
1700
|
+
group.add(result.group);
|
|
1701
|
+
if (wind !== false) {
|
|
1702
|
+
const sway = applyWind(result.group, {
|
|
1703
|
+
strength: typeof wind === "object" ? wind.strength ?? 0.05 : 0.05
|
|
1704
|
+
});
|
|
1705
|
+
updates.push((dt) => sway.update(dt));
|
|
1706
|
+
}
|
|
1707
|
+
return result;
|
|
1708
|
+
});
|
|
1709
|
+
let cycle;
|
|
1710
|
+
if (manifest.dayCycle) {
|
|
1711
|
+
cycle = createDayCycle({
|
|
1712
|
+
sky,
|
|
1713
|
+
rig,
|
|
1714
|
+
scene,
|
|
1715
|
+
lamps: village?.lamps,
|
|
1716
|
+
palette,
|
|
1717
|
+
dayLength: manifest.dayCycle.dayLength,
|
|
1718
|
+
timeOfDay: manifest.dayCycle.timeOfDay
|
|
1719
|
+
});
|
|
1720
|
+
updates.push((dt) => cycle.update(dt));
|
|
1721
|
+
}
|
|
1722
|
+
scene?.add(group);
|
|
1723
|
+
return {
|
|
1724
|
+
group,
|
|
1725
|
+
palette,
|
|
1726
|
+
terrain,
|
|
1727
|
+
water,
|
|
1728
|
+
sky,
|
|
1729
|
+
rig,
|
|
1730
|
+
cycle,
|
|
1731
|
+
paths,
|
|
1732
|
+
village,
|
|
1733
|
+
scatters,
|
|
1734
|
+
obstacles: [
|
|
1735
|
+
...village ? village.obstacles : [],
|
|
1736
|
+
...scatters.flatMap((s) => s.obstacles)
|
|
1737
|
+
],
|
|
1738
|
+
heightAt,
|
|
1739
|
+
update(dt) {
|
|
1740
|
+
for (const fn of updates) fn(dt);
|
|
1741
|
+
}
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
// src/scene/markers.ts
|
|
1746
|
+
var import_three20 = require("three");
|
|
1747
|
+
function extractMarkers(root) {
|
|
1748
|
+
root.updateWorldMatrix(true, true);
|
|
1749
|
+
const spawns = {};
|
|
1750
|
+
const routePoints = {};
|
|
1751
|
+
const obstacles = [];
|
|
1752
|
+
const keepOut = [];
|
|
1753
|
+
root.traverse((node) => {
|
|
1754
|
+
const name = node.name.replace(/\.\d+$/, "");
|
|
1755
|
+
const match = /^(spawn|route|obstacle|keepout)_(.+)$/.exec(name);
|
|
1756
|
+
if (!match) return;
|
|
1757
|
+
const [, kind, rest] = match;
|
|
1758
|
+
const position = node.getWorldPosition(new import_three20.Vector3());
|
|
1759
|
+
const radius = Math.max(node.scale.x, node.scale.z);
|
|
1760
|
+
if (kind === "spawn") {
|
|
1761
|
+
spawns[rest] = position;
|
|
1762
|
+
} else if (kind === "route") {
|
|
1763
|
+
const step = /^(.*)_(\d+)$/.exec(rest);
|
|
1764
|
+
const routeName = step ? step[1] : rest;
|
|
1765
|
+
const index = step ? parseInt(step[2], 10) : 0;
|
|
1766
|
+
(routePoints[routeName] ?? (routePoints[routeName] = [])).push({ index, position });
|
|
1767
|
+
} else if (kind === "obstacle") {
|
|
1768
|
+
obstacles.push({ center: position, radius });
|
|
1769
|
+
} else {
|
|
1770
|
+
keepOut.push({ center: { x: position.x, z: position.z }, radius });
|
|
1771
|
+
}
|
|
1772
|
+
});
|
|
1773
|
+
const routes = {};
|
|
1774
|
+
for (const [name, points] of Object.entries(routePoints)) {
|
|
1775
|
+
routes[name] = points.sort((a, b) => a.index - b.index).map((p) => p.position);
|
|
1776
|
+
}
|
|
1777
|
+
return { spawns, routes, obstacles, keepOut };
|
|
641
1778
|
}
|
|
642
1779
|
// Annotate the CommonJS export names for ESM import in node:
|
|
643
1780
|
0 && (module.exports = {
|
|
644
1781
|
DEFAULT_PALETTE,
|
|
1782
|
+
KIT_UNIT,
|
|
645
1783
|
PALETTES,
|
|
646
1784
|
Rng,
|
|
1785
|
+
aboveWater,
|
|
647
1786
|
applyFog,
|
|
1787
|
+
applyWind,
|
|
1788
|
+
assembleKit,
|
|
1789
|
+
buildScene,
|
|
648
1790
|
collectObstacles,
|
|
1791
|
+
createBush,
|
|
649
1792
|
createCrate,
|
|
1793
|
+
createDayCycle,
|
|
650
1794
|
createFence,
|
|
1795
|
+
createGrassTuft,
|
|
1796
|
+
createHouse,
|
|
651
1797
|
createLamp,
|
|
652
1798
|
createLightingRig,
|
|
1799
|
+
createPath,
|
|
653
1800
|
createRock,
|
|
1801
|
+
createRuin,
|
|
654
1802
|
createSky,
|
|
655
1803
|
createTerrain,
|
|
1804
|
+
createTower,
|
|
656
1805
|
createTree,
|
|
1806
|
+
createVillage,
|
|
1807
|
+
createWater,
|
|
1808
|
+
createWell,
|
|
1809
|
+
extractMarkers,
|
|
657
1810
|
fractalNoise2,
|
|
658
1811
|
hash2,
|
|
659
1812
|
scatter,
|