scena3d 0.1.0 → 0.3.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 +14 -8
- package/dist/index.cjs +786 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +280 -3
- package/dist/index.d.ts +280 -3
- package/dist/index.js +774 -22
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -23,16 +23,28 @@ __export(index_exports, {
|
|
|
23
23
|
DEFAULT_PALETTE: () => DEFAULT_PALETTE,
|
|
24
24
|
PALETTES: () => PALETTES,
|
|
25
25
|
Rng: () => Rng,
|
|
26
|
+
aboveWater: () => aboveWater,
|
|
26
27
|
applyFog: () => applyFog,
|
|
28
|
+
applyWind: () => applyWind,
|
|
27
29
|
collectObstacles: () => collectObstacles,
|
|
30
|
+
createBush: () => createBush,
|
|
28
31
|
createCrate: () => createCrate,
|
|
32
|
+
createDayCycle: () => createDayCycle,
|
|
29
33
|
createFence: () => createFence,
|
|
34
|
+
createGrassTuft: () => createGrassTuft,
|
|
35
|
+
createHouse: () => createHouse,
|
|
30
36
|
createLamp: () => createLamp,
|
|
31
37
|
createLightingRig: () => createLightingRig,
|
|
38
|
+
createPath: () => createPath,
|
|
32
39
|
createRock: () => createRock,
|
|
40
|
+
createRuin: () => createRuin,
|
|
33
41
|
createSky: () => createSky,
|
|
34
42
|
createTerrain: () => createTerrain,
|
|
43
|
+
createTower: () => createTower,
|
|
35
44
|
createTree: () => createTree,
|
|
45
|
+
createVillage: () => createVillage,
|
|
46
|
+
createWater: () => createWater,
|
|
47
|
+
createWell: () => createWell,
|
|
36
48
|
fractalNoise2: () => fractalNoise2,
|
|
37
49
|
hash2: () => hash2,
|
|
38
50
|
scatter: () => scatter,
|
|
@@ -121,7 +133,12 @@ var PALETTES = {
|
|
|
121
133
|
peak: 15265007,
|
|
122
134
|
skyTop: 4026552,
|
|
123
135
|
skyBottom: 12573160,
|
|
124
|
-
fog: 12111837
|
|
136
|
+
fog: 12111837,
|
|
137
|
+
water: 4161454,
|
|
138
|
+
sand: 13220234,
|
|
139
|
+
path: 10125663,
|
|
140
|
+
wall: 14273712,
|
|
141
|
+
roof: 11032126
|
|
125
142
|
},
|
|
126
143
|
autumn: {
|
|
127
144
|
foliage: [13202735, 14257722, 11754538, 14722373],
|
|
@@ -137,7 +154,12 @@ var PALETTES = {
|
|
|
137
154
|
peak: 14933716,
|
|
138
155
|
skyTop: 9333928,
|
|
139
156
|
skyBottom: 15255976,
|
|
140
|
-
fog: 14270888
|
|
157
|
+
fog: 14270888,
|
|
158
|
+
water: 4881042,
|
|
159
|
+
sand: 13348995,
|
|
160
|
+
path: 9270356,
|
|
161
|
+
wall: 13416596,
|
|
162
|
+
roof: 9062960
|
|
141
163
|
},
|
|
142
164
|
dusk: {
|
|
143
165
|
foliage: [2055750, 2385999, 1724992, 2978904],
|
|
@@ -153,7 +175,33 @@ var PALETTES = {
|
|
|
153
175
|
peak: 12105945,
|
|
154
176
|
skyTop: 1909061,
|
|
155
177
|
skyBottom: 13199946,
|
|
156
|
-
fog: 6969978
|
|
178
|
+
fog: 6969978,
|
|
179
|
+
water: 2968168,
|
|
180
|
+
sand: 9075306,
|
|
181
|
+
path: 6969938,
|
|
182
|
+
wall: 9274009,
|
|
183
|
+
roof: 4535640
|
|
184
|
+
},
|
|
185
|
+
winter: {
|
|
186
|
+
foliage: [3038280, 3696986, 5405288, 8889750],
|
|
187
|
+
trunk: 4864563,
|
|
188
|
+
rock: [10134701, 8687256, 11581632],
|
|
189
|
+
wood: 7823433,
|
|
190
|
+
woodDark: 5522746,
|
|
191
|
+
metal: 3752013,
|
|
192
|
+
lampGlow: 16767113,
|
|
193
|
+
grassLow: 13621726,
|
|
194
|
+
grassHigh: 15002606,
|
|
195
|
+
cliff: 7764349,
|
|
196
|
+
peak: 16054266,
|
|
197
|
+
skyTop: 5929894,
|
|
198
|
+
skyBottom: 14214380,
|
|
199
|
+
fog: 13424864,
|
|
200
|
+
water: 4878470,
|
|
201
|
+
sand: 12108486,
|
|
202
|
+
path: 9143160,
|
|
203
|
+
wall: 13814203,
|
|
204
|
+
roof: 7030328
|
|
157
205
|
}
|
|
158
206
|
};
|
|
159
207
|
var DEFAULT_PALETTE = PALETTES.meadow;
|
|
@@ -368,8 +416,321 @@ function createLamp(options = {}) {
|
|
|
368
416
|
return { object: group, obstacleRadius: 0.25 };
|
|
369
417
|
}
|
|
370
418
|
|
|
371
|
-
// src/
|
|
419
|
+
// src/props/grass.ts
|
|
372
420
|
var import_three7 = require("three");
|
|
421
|
+
function createGrassTuft(options = {}) {
|
|
422
|
+
const rng = new Rng(options.seed ?? 1);
|
|
423
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
424
|
+
const blades = options.blades ?? rng.int(4, 6);
|
|
425
|
+
const group = new import_three7.Group();
|
|
426
|
+
group.name = "grass";
|
|
427
|
+
const material = new import_three7.MeshStandardMaterial({
|
|
428
|
+
color: rng.next() < 0.5 ? palette.grassHigh : rng.pick(palette.foliage),
|
|
429
|
+
flatShading: true
|
|
430
|
+
});
|
|
431
|
+
for (let i = 0; i < blades; i++) {
|
|
432
|
+
const height = rng.range(0.25, 0.5);
|
|
433
|
+
const blade = new import_three7.Mesh(new import_three7.ConeGeometry(0.035, height, 3), material);
|
|
434
|
+
blade.position.set(rng.jitter(0, 0.12), height / 2, rng.jitter(0, 0.12));
|
|
435
|
+
blade.rotation.set(rng.range(-0.25, 0.25), rng.range(0, Math.PI), rng.range(-0.25, 0.25));
|
|
436
|
+
group.add(blade);
|
|
437
|
+
}
|
|
438
|
+
return { object: group, obstacleRadius: 0 };
|
|
439
|
+
}
|
|
440
|
+
function createBush(options = {}) {
|
|
441
|
+
const rng = new Rng(options.seed ?? 1);
|
|
442
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
443
|
+
const size = options.size ?? rng.range(0.4, 0.7);
|
|
444
|
+
const group = new import_three7.Group();
|
|
445
|
+
group.name = "bush";
|
|
446
|
+
const material = new import_three7.MeshStandardMaterial({ color: rng.pick(palette.foliage), flatShading: true });
|
|
447
|
+
const blobs = rng.int(2, 3);
|
|
448
|
+
for (let i = 0; i < blobs; i++) {
|
|
449
|
+
const radius = size * rng.range(0.55, 0.85);
|
|
450
|
+
const blob = new import_three7.Mesh(new import_three7.IcosahedronGeometry(radius, 0), material);
|
|
451
|
+
blob.position.set(rng.jitter(0, size * 0.4), radius * 0.7, rng.jitter(0, size * 0.4));
|
|
452
|
+
blob.scale.y = rng.range(0.7, 0.85);
|
|
453
|
+
blob.rotation.y = rng.range(0, Math.PI);
|
|
454
|
+
group.add(blob);
|
|
455
|
+
}
|
|
456
|
+
return { object: group, obstacleRadius: size * 0.6 };
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/props/building.ts
|
|
460
|
+
var import_three8 = require("three");
|
|
461
|
+
function prismGeometry(width, height, depth) {
|
|
462
|
+
const w = width / 2;
|
|
463
|
+
const d = depth / 2;
|
|
464
|
+
const positions = new Float32Array([
|
|
465
|
+
// front gable (z = +d)
|
|
466
|
+
-w,
|
|
467
|
+
0,
|
|
468
|
+
d,
|
|
469
|
+
w,
|
|
470
|
+
0,
|
|
471
|
+
d,
|
|
472
|
+
0,
|
|
473
|
+
height,
|
|
474
|
+
d,
|
|
475
|
+
// back gable (z = -d)
|
|
476
|
+
w,
|
|
477
|
+
0,
|
|
478
|
+
-d,
|
|
479
|
+
-w,
|
|
480
|
+
0,
|
|
481
|
+
-d,
|
|
482
|
+
0,
|
|
483
|
+
height,
|
|
484
|
+
-d,
|
|
485
|
+
// left slope
|
|
486
|
+
-w,
|
|
487
|
+
0,
|
|
488
|
+
-d,
|
|
489
|
+
-w,
|
|
490
|
+
0,
|
|
491
|
+
d,
|
|
492
|
+
0,
|
|
493
|
+
height,
|
|
494
|
+
d,
|
|
495
|
+
-w,
|
|
496
|
+
0,
|
|
497
|
+
-d,
|
|
498
|
+
0,
|
|
499
|
+
height,
|
|
500
|
+
d,
|
|
501
|
+
0,
|
|
502
|
+
height,
|
|
503
|
+
-d,
|
|
504
|
+
// right slope
|
|
505
|
+
w,
|
|
506
|
+
0,
|
|
507
|
+
d,
|
|
508
|
+
w,
|
|
509
|
+
0,
|
|
510
|
+
-d,
|
|
511
|
+
0,
|
|
512
|
+
height,
|
|
513
|
+
-d,
|
|
514
|
+
w,
|
|
515
|
+
0,
|
|
516
|
+
d,
|
|
517
|
+
0,
|
|
518
|
+
height,
|
|
519
|
+
-d,
|
|
520
|
+
0,
|
|
521
|
+
height,
|
|
522
|
+
d,
|
|
523
|
+
// bottom
|
|
524
|
+
-w,
|
|
525
|
+
0,
|
|
526
|
+
-d,
|
|
527
|
+
w,
|
|
528
|
+
0,
|
|
529
|
+
-d,
|
|
530
|
+
w,
|
|
531
|
+
0,
|
|
532
|
+
d,
|
|
533
|
+
-w,
|
|
534
|
+
0,
|
|
535
|
+
-d,
|
|
536
|
+
w,
|
|
537
|
+
0,
|
|
538
|
+
d,
|
|
539
|
+
-w,
|
|
540
|
+
0,
|
|
541
|
+
d
|
|
542
|
+
]);
|
|
543
|
+
const geometry = new import_three8.BufferGeometry();
|
|
544
|
+
geometry.setAttribute("position", new import_three8.BufferAttribute(positions, 3));
|
|
545
|
+
geometry.computeVertexNormals();
|
|
546
|
+
return geometry;
|
|
547
|
+
}
|
|
548
|
+
function createHouse(options = {}) {
|
|
549
|
+
const rng = new Rng(options.seed ?? 1);
|
|
550
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
551
|
+
const width = options.width ?? rng.range(3.2, 4.2);
|
|
552
|
+
const depth = options.depth ?? width * rng.range(0.75, 0.9);
|
|
553
|
+
const wallHeight = options.wallHeight ?? rng.range(2.1, 2.4);
|
|
554
|
+
const group = new import_three8.Group();
|
|
555
|
+
group.name = "house";
|
|
556
|
+
const wall = new import_three8.MeshStandardMaterial({ color: palette.wall, flatShading: true });
|
|
557
|
+
wall.color.offsetHSL(0, 0, rng.range(-0.03, 0.03));
|
|
558
|
+
const roof = new import_three8.MeshStandardMaterial({ color: palette.roof, flatShading: true });
|
|
559
|
+
roof.color.offsetHSL(0, 0, rng.range(-0.04, 0.04));
|
|
560
|
+
const stone = new import_three8.MeshStandardMaterial({ color: palette.rock[0], flatShading: true });
|
|
561
|
+
const wood = new import_three8.MeshStandardMaterial({ color: palette.woodDark, flatShading: true });
|
|
562
|
+
const foundation = new import_three8.Mesh(new import_three8.BoxGeometry(width + 0.3, 1.6, depth + 0.3), stone);
|
|
563
|
+
foundation.position.y = -0.55;
|
|
564
|
+
group.add(foundation);
|
|
565
|
+
const body = new import_three8.Mesh(new import_three8.BoxGeometry(width, wallHeight, depth), wall);
|
|
566
|
+
body.position.y = wallHeight / 2;
|
|
567
|
+
group.add(body);
|
|
568
|
+
const ridgeHeight = width * rng.range(0.32, 0.4);
|
|
569
|
+
const roofMesh = new import_three8.Mesh(prismGeometry(width + 0.5, ridgeHeight, depth + 0.6), roof);
|
|
570
|
+
roofMesh.position.y = wallHeight - 0.02;
|
|
571
|
+
group.add(roofMesh);
|
|
572
|
+
const chimney = new import_three8.Mesh(new import_three8.BoxGeometry(0.34, ridgeHeight + 0.8, 0.34), stone);
|
|
573
|
+
chimney.position.set(rng.pick([-1, 1]) * width * 0.22, wallHeight + ridgeHeight * 0.45, depth * 0.12);
|
|
574
|
+
group.add(chimney);
|
|
575
|
+
const door = new import_three8.Mesh(new import_three8.BoxGeometry(0.85, 1.5, 0.08), wood);
|
|
576
|
+
door.position.set(rng.range(-0.4, 0.4), 0.75, depth / 2 + 0.02);
|
|
577
|
+
group.add(door);
|
|
578
|
+
const glass = new import_three8.MeshStandardMaterial({
|
|
579
|
+
color: palette.lampGlow,
|
|
580
|
+
emissive: palette.lampGlow,
|
|
581
|
+
emissiveIntensity: 1
|
|
582
|
+
});
|
|
583
|
+
const windowGeometry = new import_three8.BoxGeometry(0.6, 0.62, 0.08);
|
|
584
|
+
const front = new import_three8.Mesh(windowGeometry, glass);
|
|
585
|
+
front.position.set(door.position.x < 0 ? width * 0.28 : -width * 0.28, 1.35, depth / 2 + 0.02);
|
|
586
|
+
group.add(front);
|
|
587
|
+
for (const side of [-1, 1]) {
|
|
588
|
+
if (rng.next() < 0.35) continue;
|
|
589
|
+
const pane = new import_three8.Mesh(windowGeometry, glass);
|
|
590
|
+
pane.position.set(side * (width / 2 + 0.02), 1.35, rng.range(-0.3, 0.3) * depth);
|
|
591
|
+
pane.rotation.y = Math.PI / 2;
|
|
592
|
+
group.add(pane);
|
|
593
|
+
}
|
|
594
|
+
return { object: group, obstacleRadius: Math.hypot(width + 0.5, depth + 0.5) / 2 };
|
|
595
|
+
}
|
|
596
|
+
function createTower(options = {}) {
|
|
597
|
+
const rng = new Rng(options.seed ?? 1);
|
|
598
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
599
|
+
const height = options.height ?? rng.range(4.2, 5.2);
|
|
600
|
+
const group = new import_three8.Group();
|
|
601
|
+
group.name = "tower";
|
|
602
|
+
const wood = new import_three8.MeshStandardMaterial({ color: palette.wood, flatShading: true });
|
|
603
|
+
const woodDark = new import_three8.MeshStandardMaterial({ color: palette.woodDark, flatShading: true });
|
|
604
|
+
const roof = new import_three8.MeshStandardMaterial({ color: palette.roof, flatShading: true });
|
|
605
|
+
const spread = 1;
|
|
606
|
+
for (const x of [-1, 1]) {
|
|
607
|
+
for (const z of [-1, 1]) {
|
|
608
|
+
const leg = new import_three8.Mesh(new import_three8.CylinderGeometry(0.09, 0.12, height, 6), woodDark);
|
|
609
|
+
leg.position.set(x * spread * 0.75, height / 2 - 0.6, z * spread * 0.75);
|
|
610
|
+
leg.rotation.z = -x * 0.1;
|
|
611
|
+
leg.rotation.x = z * 0.1;
|
|
612
|
+
group.add(leg);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
for (const level of [0.3, 0.62]) {
|
|
616
|
+
const brace = new import_three8.Mesh(new import_three8.BoxGeometry(spread * 2.1, 0.09, 0.07), wood);
|
|
617
|
+
brace.position.set(0, height * level, spread * 0.82 * (1 - level * 0.35));
|
|
618
|
+
brace.rotation.z = rng.pick([-0.5, 0.5]);
|
|
619
|
+
group.add(brace);
|
|
620
|
+
const braceSide = new import_three8.Mesh(new import_three8.BoxGeometry(0.07, 0.09, spread * 2.1), wood);
|
|
621
|
+
braceSide.position.set(spread * 0.82 * (1 - level * 0.35), height * level, 0);
|
|
622
|
+
braceSide.rotation.x = rng.pick([-0.5, 0.5]);
|
|
623
|
+
group.add(braceSide);
|
|
624
|
+
}
|
|
625
|
+
const platform = new import_three8.Mesh(new import_three8.BoxGeometry(spread * 2.2, 0.14, spread * 2.2), wood);
|
|
626
|
+
platform.position.y = height - 0.5;
|
|
627
|
+
group.add(platform);
|
|
628
|
+
for (const x of [-1, 1]) {
|
|
629
|
+
for (const z of [-1, 1]) {
|
|
630
|
+
const post = new import_three8.Mesh(new import_three8.BoxGeometry(0.08, 0.9, 0.08), woodDark);
|
|
631
|
+
post.position.set(x * spread * 1, height - 0.05, z * spread * 1);
|
|
632
|
+
group.add(post);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
for (const [rx, rz, w, d] of [
|
|
636
|
+
[0, 1, spread * 2.1, 0.06],
|
|
637
|
+
[0, -1, spread * 2.1, 0.06],
|
|
638
|
+
[1, 0, 0.06, spread * 2.1],
|
|
639
|
+
[-1, 0, 0.06, spread * 2.1]
|
|
640
|
+
]) {
|
|
641
|
+
const rail = new import_three8.Mesh(new import_three8.BoxGeometry(w, 0.07, d), wood);
|
|
642
|
+
rail.position.set(rx * spread, height + 0.28, rz * spread);
|
|
643
|
+
group.add(rail);
|
|
644
|
+
}
|
|
645
|
+
const cap = new import_three8.Mesh(new import_three8.CylinderGeometry(0, spread * 1.55, 1, 4), roof);
|
|
646
|
+
cap.position.y = height + 1.15;
|
|
647
|
+
cap.rotation.y = Math.PI / 4;
|
|
648
|
+
group.add(cap);
|
|
649
|
+
return { object: group, obstacleRadius: spread * 1.35 };
|
|
650
|
+
}
|
|
651
|
+
function createWell(options = {}) {
|
|
652
|
+
const rng = new Rng(options.seed ?? 1);
|
|
653
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
654
|
+
const group = new import_three8.Group();
|
|
655
|
+
group.name = "well";
|
|
656
|
+
const stone = new import_three8.MeshStandardMaterial({ color: rng.pick(palette.rock), flatShading: true });
|
|
657
|
+
const wood = new import_three8.MeshStandardMaterial({ color: palette.woodDark, flatShading: true });
|
|
658
|
+
const roof = new import_three8.MeshStandardMaterial({ color: palette.roof, flatShading: true });
|
|
659
|
+
const ring = new import_three8.Mesh(new import_three8.CylinderGeometry(0.85, 0.95, 0.75, 10), stone);
|
|
660
|
+
ring.position.y = 0.375;
|
|
661
|
+
group.add(ring);
|
|
662
|
+
const inner = new import_three8.Mesh(
|
|
663
|
+
new import_three8.CylinderGeometry(0.62, 0.62, 0.05, 10),
|
|
664
|
+
new import_three8.MeshStandardMaterial({ color: 1450542 })
|
|
665
|
+
);
|
|
666
|
+
inner.position.y = 0.76;
|
|
667
|
+
group.add(inner);
|
|
668
|
+
for (const side of [-1, 1]) {
|
|
669
|
+
const post = new import_three8.Mesh(new import_three8.CylinderGeometry(0.06, 0.075, 1.9, 5), wood);
|
|
670
|
+
post.position.set(side * 0.72, 0.95, 0);
|
|
671
|
+
group.add(post);
|
|
672
|
+
}
|
|
673
|
+
const bar = new import_three8.Mesh(new import_three8.CylinderGeometry(0.045, 0.045, 1.5, 5), wood);
|
|
674
|
+
bar.rotation.z = Math.PI / 2;
|
|
675
|
+
bar.position.y = 1.72;
|
|
676
|
+
group.add(bar);
|
|
677
|
+
const cap = new import_three8.Mesh(prismGeometry(2, 0.55, 0.65), roof);
|
|
678
|
+
cap.position.y = 1.86;
|
|
679
|
+
cap.rotation.y = Math.PI / 2;
|
|
680
|
+
group.add(cap);
|
|
681
|
+
const rope = new import_three8.Mesh(new import_three8.CylinderGeometry(0.012, 0.012, 0.6, 4), wood);
|
|
682
|
+
rope.position.y = 1.42;
|
|
683
|
+
group.add(rope);
|
|
684
|
+
const bucket = new import_three8.Mesh(new import_three8.CylinderGeometry(0.13, 0.1, 0.2, 7), wood);
|
|
685
|
+
bucket.position.y = 1.05;
|
|
686
|
+
group.add(bucket);
|
|
687
|
+
return { object: group, obstacleRadius: 1 };
|
|
688
|
+
}
|
|
689
|
+
function createRuin(options = {}) {
|
|
690
|
+
const rng = new Rng(options.seed ?? 1);
|
|
691
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
692
|
+
const size = options.size ?? rng.range(3.5, 5);
|
|
693
|
+
const depth = size * rng.range(0.7, 0.9);
|
|
694
|
+
const group = new import_three8.Group();
|
|
695
|
+
group.name = "ruin";
|
|
696
|
+
const stone = new import_three8.MeshStandardMaterial({ color: rng.pick(palette.rock), flatShading: true });
|
|
697
|
+
stone.color.offsetHSL(0, 0, rng.range(-0.04, 0.02));
|
|
698
|
+
const thickness = 0.35;
|
|
699
|
+
const runs = [
|
|
700
|
+
[0, -depth / 2, size, true],
|
|
701
|
+
[-size / 2, 0, depth, false],
|
|
702
|
+
[size / 2, 0, depth, false],
|
|
703
|
+
[size * 0.3, depth / 2, size * 0.35, true]
|
|
704
|
+
];
|
|
705
|
+
for (const [cx, cz, length, alongX] of runs) {
|
|
706
|
+
const segments = Math.max(2, Math.round(length / 1.1));
|
|
707
|
+
const step = length / segments;
|
|
708
|
+
for (let i = 0; i < segments; i++) {
|
|
709
|
+
if (rng.next() < 0.22) continue;
|
|
710
|
+
const height = rng.range(0.5, 2.2);
|
|
711
|
+
const wall = new import_three8.Mesh(
|
|
712
|
+
new import_three8.BoxGeometry(alongX ? step * 0.96 : thickness, height, alongX ? thickness : step * 0.96),
|
|
713
|
+
stone
|
|
714
|
+
);
|
|
715
|
+
const offset = -length / 2 + step * (i + 0.5);
|
|
716
|
+
wall.position.set(alongX ? cx + offset : cx, height / 2, alongX ? cz : cz + offset);
|
|
717
|
+
wall.rotation.y = rng.range(-0.03, 0.03);
|
|
718
|
+
group.add(wall);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
const blocks = rng.int(4, 7);
|
|
722
|
+
for (let i = 0; i < blocks; i++) {
|
|
723
|
+
const s = rng.range(0.25, 0.55);
|
|
724
|
+
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);
|
|
725
|
+
block.position.set(rng.range(-size * 0.6, size * 0.6), s * 0.3, rng.range(-depth * 0.6, depth * 0.7));
|
|
726
|
+
block.rotation.set(rng.range(-0.3, 0.3), rng.range(0, Math.PI), rng.range(-0.3, 0.3));
|
|
727
|
+
group.add(block);
|
|
728
|
+
}
|
|
729
|
+
return { object: group, obstacleRadius: Math.hypot(size, depth) / 2 };
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// src/environment/terrain.ts
|
|
733
|
+
var import_three9 = require("three");
|
|
373
734
|
function createTerrain(options = {}) {
|
|
374
735
|
const seed = options.seed ?? 1;
|
|
375
736
|
const size = options.size ?? 80;
|
|
@@ -384,7 +745,7 @@ function createTerrain(options = {}) {
|
|
|
384
745
|
const shaped = Math.pow(n, 1 + flatness * 2);
|
|
385
746
|
return shaped * amplitude;
|
|
386
747
|
};
|
|
387
|
-
const geometry = new
|
|
748
|
+
const geometry = new import_three9.PlaneGeometry(size, size, resolution - 1, resolution - 1);
|
|
388
749
|
geometry.rotateX(-Math.PI / 2);
|
|
389
750
|
const positions = geometry.getAttribute("position");
|
|
390
751
|
for (let i = 0; i < positions.count; i++) {
|
|
@@ -393,40 +754,46 @@ function createTerrain(options = {}) {
|
|
|
393
754
|
geometry.computeVertexNormals();
|
|
394
755
|
const normals = geometry.getAttribute("normal");
|
|
395
756
|
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
|
|
757
|
+
const grassLow = new import_three9.Color(palette.grassLow);
|
|
758
|
+
const grassHigh = new import_three9.Color(palette.grassHigh);
|
|
759
|
+
const cliff = new import_three9.Color(palette.cliff);
|
|
760
|
+
const peak = new import_three9.Color(palette.peak);
|
|
761
|
+
const sand = new import_three9.Color(palette.sand);
|
|
762
|
+
const waterLevel = options.waterLevel;
|
|
763
|
+
const scratch = new import_three9.Color();
|
|
401
764
|
for (let i = 0; i < positions.count; i++) {
|
|
402
|
-
const
|
|
765
|
+
const y = positions.getY(i);
|
|
766
|
+
const h = y / amplitude;
|
|
403
767
|
const slope = 1 - normals.getY(i);
|
|
404
768
|
scratch.copy(grassLow).lerp(grassHigh, Math.min(1, h * 1.6));
|
|
405
769
|
if (h > 0.75) scratch.lerp(peak, (h - 0.75) * 4);
|
|
406
770
|
if (slope > 0.15) scratch.lerp(cliff, Math.min(1, (slope - 0.15) * 4));
|
|
771
|
+
if (waterLevel !== void 0 && y < waterLevel + 0.5) {
|
|
772
|
+
scratch.lerp(sand, Math.min(1, (waterLevel + 0.5 - y) * 1.6));
|
|
773
|
+
}
|
|
407
774
|
colors[i * 3] = scratch.r;
|
|
408
775
|
colors[i * 3 + 1] = scratch.g;
|
|
409
776
|
colors[i * 3 + 2] = scratch.b;
|
|
410
777
|
}
|
|
411
|
-
geometry.setAttribute("color", new
|
|
412
|
-
const mesh = new
|
|
778
|
+
geometry.setAttribute("color", new import_three9.BufferAttribute(colors, 3));
|
|
779
|
+
const mesh = new import_three9.Mesh(
|
|
413
780
|
geometry,
|
|
414
|
-
new
|
|
781
|
+
new import_three9.MeshStandardMaterial({ vertexColors: true, flatShading: true })
|
|
415
782
|
);
|
|
416
783
|
mesh.name = "terrain";
|
|
417
784
|
return { mesh, heightAt, size, seed };
|
|
418
785
|
}
|
|
419
786
|
|
|
420
787
|
// src/environment/sky.ts
|
|
421
|
-
var
|
|
788
|
+
var import_three10 = require("three");
|
|
422
789
|
function createSky(options = {}) {
|
|
423
790
|
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
424
|
-
const material = new
|
|
425
|
-
side:
|
|
791
|
+
const material = new import_three10.ShaderMaterial({
|
|
792
|
+
side: import_three10.BackSide,
|
|
426
793
|
depthWrite: false,
|
|
427
794
|
uniforms: {
|
|
428
|
-
topColor: { value: new
|
|
429
|
-
bottomColor: { value: new
|
|
795
|
+
topColor: { value: new import_three10.Color(options.topColor ?? palette.skyTop) },
|
|
796
|
+
bottomColor: { value: new import_three10.Color(options.bottomColor ?? palette.skyBottom) }
|
|
430
797
|
},
|
|
431
798
|
vertexShader: (
|
|
432
799
|
/* glsl */
|
|
@@ -449,7 +816,7 @@ function createSky(options = {}) {
|
|
|
449
816
|
}`
|
|
450
817
|
)
|
|
451
818
|
});
|
|
452
|
-
const mesh = new
|
|
819
|
+
const mesh = new import_three10.Mesh(new import_three10.SphereGeometry(options.radius ?? 400, 16, 12), material);
|
|
453
820
|
mesh.name = "sky";
|
|
454
821
|
return {
|
|
455
822
|
mesh,
|
|
@@ -461,7 +828,7 @@ function createSky(options = {}) {
|
|
|
461
828
|
}
|
|
462
829
|
|
|
463
830
|
// src/environment/lighting.ts
|
|
464
|
-
var
|
|
831
|
+
var import_three11 = require("three");
|
|
465
832
|
var PRESETS = {
|
|
466
833
|
day: {
|
|
467
834
|
sun: 16774368,
|
|
@@ -502,12 +869,12 @@ var PRESETS = {
|
|
|
502
869
|
};
|
|
503
870
|
function createLightingRig(preset = "day") {
|
|
504
871
|
const config = PRESETS[preset];
|
|
505
|
-
const group = new
|
|
872
|
+
const group = new import_three11.Group();
|
|
506
873
|
group.name = `lighting-${preset}`;
|
|
507
|
-
const sun = new
|
|
874
|
+
const sun = new import_three11.DirectionalLight(config.sun, config.sunIntensity);
|
|
508
875
|
sun.position.set(...config.sunPos);
|
|
509
|
-
const ambient = new
|
|
510
|
-
const hemisphere = new
|
|
876
|
+
const ambient = new import_three11.AmbientLight(config.ambient, config.ambientIntensity);
|
|
877
|
+
const hemisphere = new import_three11.HemisphereLight(config.skyTint, config.groundTint, 0.5);
|
|
511
878
|
group.add(sun, ambient, hemisphere);
|
|
512
879
|
return { group, sun, ambient, hemisphere };
|
|
513
880
|
}
|
|
@@ -522,11 +889,381 @@ function applyFog(scene, preset, palette = DEFAULT_PALETTE) {
|
|
|
522
889
|
return;
|
|
523
890
|
}
|
|
524
891
|
const { near, far } = FOG[preset];
|
|
525
|
-
scene.fog = new
|
|
892
|
+
scene.fog = new import_three11.Fog(palette.fog, near, far);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// src/environment/water.ts
|
|
896
|
+
var import_three12 = require("three");
|
|
897
|
+
function createWater(options = {}) {
|
|
898
|
+
const level = options.level ?? 0.8;
|
|
899
|
+
const size = options.size ?? 200;
|
|
900
|
+
const resolution = options.resolution ?? 40;
|
|
901
|
+
const amplitude = options.amplitude ?? 0.06;
|
|
902
|
+
const speed = options.speed ?? 1;
|
|
903
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
904
|
+
const geometry = new import_three12.PlaneGeometry(size, size, resolution, resolution);
|
|
905
|
+
geometry.rotateX(-Math.PI / 2);
|
|
906
|
+
const positions = geometry.getAttribute("position");
|
|
907
|
+
const mesh = new import_three12.Mesh(
|
|
908
|
+
geometry,
|
|
909
|
+
new import_three12.MeshStandardMaterial({
|
|
910
|
+
color: palette.water,
|
|
911
|
+
transparent: true,
|
|
912
|
+
opacity: 0.85,
|
|
913
|
+
flatShading: true,
|
|
914
|
+
metalness: 0.35,
|
|
915
|
+
roughness: 0.4
|
|
916
|
+
})
|
|
917
|
+
);
|
|
918
|
+
mesh.name = "water";
|
|
919
|
+
mesh.position.y = level;
|
|
920
|
+
let time = 0;
|
|
921
|
+
const update = (dt) => {
|
|
922
|
+
time += dt * speed;
|
|
923
|
+
for (let i = 0; i < positions.count; i++) {
|
|
924
|
+
const x = positions.getX(i);
|
|
925
|
+
const z = positions.getZ(i);
|
|
926
|
+
positions.setY(
|
|
927
|
+
i,
|
|
928
|
+
Math.sin(x * 0.35 + time) * Math.cos(z * 0.3 + time * 0.8) * amplitude
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
positions.needsUpdate = true;
|
|
932
|
+
geometry.computeVertexNormals();
|
|
933
|
+
};
|
|
934
|
+
update(0);
|
|
935
|
+
return {
|
|
936
|
+
mesh,
|
|
937
|
+
level,
|
|
938
|
+
update,
|
|
939
|
+
isUnderwater: (groundHeight) => groundHeight < level
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
function aboveWater(terrain, water, margin = 0.25) {
|
|
943
|
+
return (x, z) => terrain.heightAt(x, z) > water.level + margin;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// src/environment/dayCycle.ts
|
|
947
|
+
var import_three13 = require("three");
|
|
948
|
+
function createDayCycle(options = {}) {
|
|
949
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
950
|
+
const dayLength = options.dayLength ?? 60;
|
|
951
|
+
const frames = [
|
|
952
|
+
{ t: 0, skyTop: 725030, skyBottom: 1712960, sun: 9348824, sunIntensity: 0.03, ambientIntensity: 0.06, fog: 1317422 },
|
|
953
|
+
{ t: 0.23, skyTop: 2569052, skyBottom: 6968432, sun: 13605490, sunIntensity: 0.3, ambientIntensity: 0.12, fog: 4866648 },
|
|
954
|
+
{ t: 0.3, skyTop: 4877216, skyBottom: 15247738, sun: 16758881, sunIntensity: 1, ambientIntensity: 0.26, fog: 11569778 },
|
|
955
|
+
{ t: 0.5, skyTop: palette.skyTop, skyBottom: palette.skyBottom, sun: 16774368, sunIntensity: 1.9, ambientIntensity: 0.45, fog: palette.fog },
|
|
956
|
+
{ t: 0.7, skyTop: 4872852, skyBottom: 14718302, sun: 16752720, sunIntensity: 1, ambientIntensity: 0.26, fog: 10517096 },
|
|
957
|
+
{ t: 0.78, skyTop: 2304594, skyBottom: 9065824, sun: 14191194, sunIntensity: 0.25, ambientIntensity: 0.11, fog: 4603476 },
|
|
958
|
+
{ t: 1, skyTop: 725030, skyBottom: 1712960, sun: 9348824, sunIntensity: 0.03, ambientIntensity: 0.06, fog: 1317422 }
|
|
959
|
+
];
|
|
960
|
+
const lampLights = [];
|
|
961
|
+
const lampBulbs = [];
|
|
962
|
+
for (const entry of options.lamps ?? []) {
|
|
963
|
+
const root = entry.isObject3D ? entry : entry.object;
|
|
964
|
+
root.traverse((child) => {
|
|
965
|
+
if (child instanceof import_three13.PointLight) {
|
|
966
|
+
lampLights.push({ light: child, base: child.intensity || 6 });
|
|
967
|
+
}
|
|
968
|
+
const material = child.material;
|
|
969
|
+
if (material?.emissive && material.emissiveIntensity > 0.5) {
|
|
970
|
+
lampBulbs.push({ material, base: material.emissiveIntensity });
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
const colorA = new import_three13.Color();
|
|
975
|
+
const colorB = new import_three13.Color();
|
|
976
|
+
let timeOfDay = options.timeOfDay ?? 0.5;
|
|
977
|
+
let sunElevation = 0;
|
|
978
|
+
const sample = (t) => {
|
|
979
|
+
let a = frames[0];
|
|
980
|
+
let b = frames[frames.length - 1];
|
|
981
|
+
for (let i = 0; i < frames.length - 1; i++) {
|
|
982
|
+
if (t >= frames[i].t && t <= frames[i + 1].t) {
|
|
983
|
+
a = frames[i];
|
|
984
|
+
b = frames[i + 1];
|
|
985
|
+
break;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
const f = b.t === a.t ? 0 : (t - a.t) / (b.t - a.t);
|
|
989
|
+
const lerpHex = (ha, hb) => colorA.setHex(ha).lerp(colorB.setHex(hb), f).getHex();
|
|
990
|
+
return {
|
|
991
|
+
t,
|
|
992
|
+
skyTop: lerpHex(a.skyTop, b.skyTop),
|
|
993
|
+
skyBottom: lerpHex(a.skyBottom, b.skyBottom),
|
|
994
|
+
sun: lerpHex(a.sun, b.sun),
|
|
995
|
+
sunIntensity: a.sunIntensity + (b.sunIntensity - a.sunIntensity) * f,
|
|
996
|
+
ambientIntensity: a.ambientIntensity + (b.ambientIntensity - a.ambientIntensity) * f,
|
|
997
|
+
fog: lerpHex(a.fog, b.fog)
|
|
998
|
+
};
|
|
999
|
+
};
|
|
1000
|
+
const apply = () => {
|
|
1001
|
+
const t = timeOfDay;
|
|
1002
|
+
const frame = sample(t);
|
|
1003
|
+
const sunAngle = (t - 0.25) * Math.PI * 2;
|
|
1004
|
+
sunElevation = Math.sin(sunAngle);
|
|
1005
|
+
options.sky?.setColors(frame.skyTop, frame.skyBottom);
|
|
1006
|
+
if (options.rig) {
|
|
1007
|
+
const { sun, ambient, hemisphere } = options.rig;
|
|
1008
|
+
sun.color.setHex(frame.sun);
|
|
1009
|
+
sun.intensity = frame.sunIntensity;
|
|
1010
|
+
sun.position.set(Math.cos(sunAngle) * 40, Math.max(sunElevation, -0.2) * 45 + 6, 16);
|
|
1011
|
+
ambient.intensity = frame.ambientIntensity;
|
|
1012
|
+
hemisphere.intensity = frame.ambientIntensity * 1.4;
|
|
1013
|
+
}
|
|
1014
|
+
if (options.scene?.fog && "color" in options.scene.fog) {
|
|
1015
|
+
options.scene.fog.color.setHex(frame.fog);
|
|
1016
|
+
}
|
|
1017
|
+
const night = Math.min(1, Math.max(0, (0.06 - sunElevation) / 0.16));
|
|
1018
|
+
for (const { light, base } of lampLights) light.intensity = base * night;
|
|
1019
|
+
for (const { material, base } of lampBulbs) {
|
|
1020
|
+
material.emissiveIntensity = 0.15 * base + 0.85 * base * night;
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
apply();
|
|
1024
|
+
return {
|
|
1025
|
+
get timeOfDay() {
|
|
1026
|
+
return timeOfDay;
|
|
1027
|
+
},
|
|
1028
|
+
set timeOfDay(t) {
|
|
1029
|
+
timeOfDay = (t % 1 + 1) % 1;
|
|
1030
|
+
apply();
|
|
1031
|
+
},
|
|
1032
|
+
get sunElevation() {
|
|
1033
|
+
return sunElevation;
|
|
1034
|
+
},
|
|
1035
|
+
get isNight() {
|
|
1036
|
+
return sunElevation < 0;
|
|
1037
|
+
},
|
|
1038
|
+
update(dt) {
|
|
1039
|
+
timeOfDay = (timeOfDay + dt / dayLength) % 1;
|
|
1040
|
+
apply();
|
|
1041
|
+
},
|
|
1042
|
+
set(t) {
|
|
1043
|
+
timeOfDay = (t % 1 + 1) % 1;
|
|
1044
|
+
apply();
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// src/environment/wind.ts
|
|
1050
|
+
var import_three14 = require("three");
|
|
1051
|
+
function applyWind(target, options = {}) {
|
|
1052
|
+
const strength = options.strength ?? 0.06;
|
|
1053
|
+
const frequency = options.frequency ?? 1.2;
|
|
1054
|
+
const anchor = options.anchorHeight ?? 0.6;
|
|
1055
|
+
const time = { value: 0 };
|
|
1056
|
+
const patched = /* @__PURE__ */ new Set();
|
|
1057
|
+
target.traverse((child) => {
|
|
1058
|
+
if (!(child instanceof import_three14.Mesh)) return;
|
|
1059
|
+
const materials = Array.isArray(child.material) ? child.material : [child.material];
|
|
1060
|
+
for (const material of materials) {
|
|
1061
|
+
if (patched.has(material)) continue;
|
|
1062
|
+
patched.add(material);
|
|
1063
|
+
material.onBeforeCompile = (shader) => {
|
|
1064
|
+
shader.uniforms.uWindTime = time;
|
|
1065
|
+
shader.vertexShader = shader.vertexShader.replace(
|
|
1066
|
+
"#include <common>",
|
|
1067
|
+
`#include <common>
|
|
1068
|
+
uniform float uWindTime;`
|
|
1069
|
+
).replace(
|
|
1070
|
+
"#include <begin_vertex>",
|
|
1071
|
+
`#include <begin_vertex>
|
|
1072
|
+
{
|
|
1073
|
+
float sway = max(position.y - ${anchor.toFixed(3)}, 0.0);
|
|
1074
|
+
float phase = 0.0;
|
|
1075
|
+
#ifdef USE_INSTANCING
|
|
1076
|
+
phase = instanceMatrix[3].x * 0.7 + instanceMatrix[3].z * 1.3;
|
|
1077
|
+
#endif
|
|
1078
|
+
float w = sin(uWindTime * ${(frequency * 2).toFixed(3)} + phase + position.y * 0.8);
|
|
1079
|
+
transformed.x += w * sway * ${strength.toFixed(4)} * 4.0;
|
|
1080
|
+
transformed.z += cos(uWindTime * ${(frequency * 1.4).toFixed(3)} + phase) * sway * ${strength.toFixed(4)} * 2.0;
|
|
1081
|
+
}`
|
|
1082
|
+
);
|
|
1083
|
+
};
|
|
1084
|
+
material.needsUpdate = true;
|
|
1085
|
+
}
|
|
1086
|
+
});
|
|
1087
|
+
return {
|
|
1088
|
+
update(dt) {
|
|
1089
|
+
time.value += dt;
|
|
1090
|
+
},
|
|
1091
|
+
materials: [...patched]
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// src/environment/path.ts
|
|
1096
|
+
var import_three15 = require("three");
|
|
1097
|
+
function createPath(points, options = {}) {
|
|
1098
|
+
const width = options.width ?? 1.8;
|
|
1099
|
+
const surface = options.surface ?? 0;
|
|
1100
|
+
const heightAt = typeof surface === "number" ? () => surface : (x, z) => surface(x, z);
|
|
1101
|
+
const loop = options.loop ?? false;
|
|
1102
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
1103
|
+
const controls = points.map((p) => new import_three15.Vector3(p.x, 0, "z" in p ? p.z : 0));
|
|
1104
|
+
const curve = new import_three15.CatmullRomCurve3(controls, loop, "centripetal");
|
|
1105
|
+
const length = curve.getLength();
|
|
1106
|
+
const samples = Math.max(8, Math.ceil(length * (options.samplesPerUnit ?? 1)));
|
|
1107
|
+
const route = [];
|
|
1108
|
+
for (let i = 0; i <= samples; i++) {
|
|
1109
|
+
if (loop && i === samples) break;
|
|
1110
|
+
const p = curve.getPoint(i / samples);
|
|
1111
|
+
route.push(new import_three15.Vector3(p.x, heightAt(p.x, p.z), p.z));
|
|
1112
|
+
}
|
|
1113
|
+
const edgeCount = loop ? route.length + 1 : route.length;
|
|
1114
|
+
const positions = new Float32Array(edgeCount * 2 * 3);
|
|
1115
|
+
const direction = new import_three15.Vector3();
|
|
1116
|
+
const perp = new import_three15.Vector3();
|
|
1117
|
+
for (let i = 0; i < edgeCount; i++) {
|
|
1118
|
+
const current = route[i % route.length];
|
|
1119
|
+
const previous = route[(i - 1 + route.length) % route.length];
|
|
1120
|
+
const next = route[(i + 1) % route.length];
|
|
1121
|
+
if (!loop && i === 0) direction.subVectors(next, current);
|
|
1122
|
+
else if (!loop && i === edgeCount - 1) direction.subVectors(current, previous);
|
|
1123
|
+
else direction.subVectors(next, previous);
|
|
1124
|
+
perp.set(-direction.z, 0, direction.x).normalize().multiplyScalar(width / 2);
|
|
1125
|
+
const left = i * 6;
|
|
1126
|
+
positions[left] = current.x - perp.x;
|
|
1127
|
+
positions[left + 1] = heightAt(current.x - perp.x, current.z - perp.z) + 0.05;
|
|
1128
|
+
positions[left + 2] = current.z - perp.z;
|
|
1129
|
+
positions[left + 3] = current.x + perp.x;
|
|
1130
|
+
positions[left + 4] = heightAt(current.x + perp.x, current.z + perp.z) + 0.05;
|
|
1131
|
+
positions[left + 5] = current.z + perp.z;
|
|
1132
|
+
}
|
|
1133
|
+
const indices = [];
|
|
1134
|
+
for (let i = 0; i < edgeCount - 1; i++) {
|
|
1135
|
+
const a = i * 2;
|
|
1136
|
+
indices.push(a, a + 1, a + 2, a + 1, a + 3, a + 2);
|
|
1137
|
+
}
|
|
1138
|
+
const geometry = new import_three15.BufferGeometry();
|
|
1139
|
+
geometry.setAttribute("position", new import_three15.BufferAttribute(positions, 3));
|
|
1140
|
+
geometry.setIndex(indices);
|
|
1141
|
+
geometry.computeVertexNormals();
|
|
1142
|
+
const mesh = new import_three15.Mesh(
|
|
1143
|
+
geometry,
|
|
1144
|
+
new import_three15.MeshStandardMaterial({
|
|
1145
|
+
color: palette.path,
|
|
1146
|
+
flatShading: true,
|
|
1147
|
+
polygonOffset: true,
|
|
1148
|
+
polygonOffsetFactor: -1
|
|
1149
|
+
})
|
|
1150
|
+
);
|
|
1151
|
+
mesh.name = "path";
|
|
1152
|
+
const keepOutMargin = options.keepOutMargin ?? 0.6;
|
|
1153
|
+
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 }));
|
|
1154
|
+
const half = width / 2;
|
|
1155
|
+
const contains = (x, z) => {
|
|
1156
|
+
const count = loop ? route.length : route.length - 1;
|
|
1157
|
+
for (let i = 0; i < count; i++) {
|
|
1158
|
+
const a = route[i];
|
|
1159
|
+
const b = route[(i + 1) % route.length];
|
|
1160
|
+
const abx = b.x - a.x;
|
|
1161
|
+
const abz = b.z - a.z;
|
|
1162
|
+
const lengthSq = abx * abx + abz * abz || 1e-9;
|
|
1163
|
+
const t = Math.max(0, Math.min(1, ((x - a.x) * abx + (z - a.z) * abz) / lengthSq));
|
|
1164
|
+
const dx = x - (a.x + abx * t);
|
|
1165
|
+
const dz = z - (a.z + abz * t);
|
|
1166
|
+
if (dx * dx + dz * dz <= half * half) return true;
|
|
1167
|
+
}
|
|
1168
|
+
return false;
|
|
1169
|
+
};
|
|
1170
|
+
return { mesh, route, keepOut, contains, loop };
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// src/generators/village.ts
|
|
1174
|
+
var import_three16 = require("three");
|
|
1175
|
+
function createVillage(options = {}) {
|
|
1176
|
+
const rng = new Rng(options.seed ?? 1);
|
|
1177
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
1178
|
+
const center = options.center ?? { x: 0, z: 0 };
|
|
1179
|
+
const radius = options.radius ?? 12;
|
|
1180
|
+
const houseCount = options.houses ?? 5;
|
|
1181
|
+
const surface = options.surface ?? 0;
|
|
1182
|
+
const heightAt = typeof surface === "number" ? () => surface : (x, z) => surface(x, z);
|
|
1183
|
+
const group = new import_three16.Group();
|
|
1184
|
+
group.name = "village";
|
|
1185
|
+
const props = [];
|
|
1186
|
+
const lamps = [];
|
|
1187
|
+
const place = (prop, x, z, rotY) => {
|
|
1188
|
+
prop.object.position.set(x, heightAt(x, z), z);
|
|
1189
|
+
prop.object.rotation.y = rotY;
|
|
1190
|
+
group.add(prop.object);
|
|
1191
|
+
props.push(prop);
|
|
1192
|
+
return prop;
|
|
1193
|
+
};
|
|
1194
|
+
const findSpot = (angle, distance, footprint) => {
|
|
1195
|
+
let x = center.x;
|
|
1196
|
+
let z = center.z;
|
|
1197
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
1198
|
+
const a = angle + rng.range(-0.25, 0.25) * (attempt > 0 ? 1 : 0.3);
|
|
1199
|
+
const d = distance * rng.range(attempt > 0 ? 0.8 : 0.95, 1.1);
|
|
1200
|
+
x = center.x + Math.cos(a) * d;
|
|
1201
|
+
z = center.z + Math.sin(a) * d;
|
|
1202
|
+
const y = heightAt(x, z);
|
|
1203
|
+
if (options.mask && !options.mask(x, z, y)) continue;
|
|
1204
|
+
let min = y;
|
|
1205
|
+
let max = y;
|
|
1206
|
+
for (const [dx, dz] of [[footprint, 0], [-footprint, 0], [0, footprint], [0, -footprint]]) {
|
|
1207
|
+
const h = heightAt(x + dx, z + dz);
|
|
1208
|
+
min = Math.min(min, h);
|
|
1209
|
+
max = Math.max(max, h);
|
|
1210
|
+
}
|
|
1211
|
+
if (max - min <= Math.max(0.9, footprint * 0.35)) break;
|
|
1212
|
+
}
|
|
1213
|
+
return { x, z };
|
|
1214
|
+
};
|
|
1215
|
+
const well = createWell({ seed: rng.int(1, 1e9), palette });
|
|
1216
|
+
place(well, center.x, center.z, rng.range(0, Math.PI * 2));
|
|
1217
|
+
let lightBudget = options.lampLights ?? 3;
|
|
1218
|
+
for (let i = 0; i < houseCount; i++) {
|
|
1219
|
+
const angle = i / houseCount * Math.PI * 2 + rng.range(-0.15, 0.15);
|
|
1220
|
+
const house = createHouse({ seed: rng.int(1, 1e9), palette });
|
|
1221
|
+
const spot = findSpot(angle, radius * rng.range(0.7, 0.95), house.obstacleRadius);
|
|
1222
|
+
const facing = Math.atan2(center.x - spot.x, center.z - spot.z);
|
|
1223
|
+
place(house, spot.x, spot.z, facing);
|
|
1224
|
+
lamps.push(house);
|
|
1225
|
+
if (rng.next() < 0.7) {
|
|
1226
|
+
const crateAngle = angle + rng.range(-0.5, 0.5);
|
|
1227
|
+
const crateDistance = Math.hypot(spot.x - center.x, spot.z - center.z) - house.obstacleRadius - 0.7;
|
|
1228
|
+
place(
|
|
1229
|
+
createCrate({ seed: rng.int(1, 1e9), size: rng.range(0.7, 1), palette }),
|
|
1230
|
+
center.x + Math.cos(crateAngle) * crateDistance,
|
|
1231
|
+
center.z + Math.sin(crateAngle) * crateDistance,
|
|
1232
|
+
rng.range(0, Math.PI)
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
if (i % 2 === 0) {
|
|
1236
|
+
const lampDistance = Math.hypot(spot.x - center.x, spot.z - center.z) * 0.55;
|
|
1237
|
+
const lamp = createLamp({ seed: rng.int(1, 1e9), light: lightBudget > 0, palette });
|
|
1238
|
+
lightBudget--;
|
|
1239
|
+
place(lamp, center.x + Math.cos(angle) * lampDistance, center.z + Math.sin(angle) * lampDistance, 0);
|
|
1240
|
+
lamps.push(lamp);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
if (options.tower ?? true) {
|
|
1244
|
+
const angle = rng.range(0, Math.PI * 2);
|
|
1245
|
+
const tower = createTower({ seed: rng.int(1, 1e9), palette });
|
|
1246
|
+
const spot = findSpot(angle, radius * 1.05, tower.obstacleRadius);
|
|
1247
|
+
place(tower, spot.x, spot.z, rng.range(0, Math.PI * 2));
|
|
1248
|
+
}
|
|
1249
|
+
if (options.ruin ?? true) {
|
|
1250
|
+
const angle = rng.range(0, Math.PI * 2);
|
|
1251
|
+
const ruin = createRuin({ seed: rng.int(1, 1e9), palette });
|
|
1252
|
+
const spot = findSpot(angle, radius * 1.35, ruin.obstacleRadius);
|
|
1253
|
+
place(ruin, spot.x, spot.z, rng.range(0, Math.PI * 2));
|
|
1254
|
+
}
|
|
1255
|
+
return {
|
|
1256
|
+
group,
|
|
1257
|
+
props,
|
|
1258
|
+
obstacles: collectObstacles(props),
|
|
1259
|
+
lamps,
|
|
1260
|
+
keepOut: [{ center: { ...center }, radius: radius * 1.5 }],
|
|
1261
|
+
center
|
|
1262
|
+
};
|
|
526
1263
|
}
|
|
527
1264
|
|
|
528
1265
|
// src/scatter/scatter.ts
|
|
529
|
-
var
|
|
1266
|
+
var import_three17 = require("three");
|
|
530
1267
|
function scatter(options) {
|
|
531
1268
|
const seed = options.seed ?? 1;
|
|
532
1269
|
const rng = new Rng(seed);
|
|
@@ -581,7 +1318,7 @@ function scatter(options) {
|
|
|
581
1318
|
const itemIndex = pickItem();
|
|
582
1319
|
const [minScale, maxScale2] = options.items[itemIndex].scale ?? [0.8, 1.25];
|
|
583
1320
|
placements.push({
|
|
584
|
-
position: new
|
|
1321
|
+
position: new import_three17.Vector3(x, y, z),
|
|
585
1322
|
rotationY: rng.range(0, Math.PI * 2),
|
|
586
1323
|
scale: rng.range(minScale, maxScale2),
|
|
587
1324
|
itemIndex
|
|
@@ -591,14 +1328,14 @@ function scatter(options) {
|
|
|
591
1328
|
if (!bucket) occupied.set(key, bucket = []);
|
|
592
1329
|
bucket.push({ x, z });
|
|
593
1330
|
}
|
|
594
|
-
const group = new
|
|
1331
|
+
const group = new import_three17.Group();
|
|
595
1332
|
group.name = "scatter";
|
|
596
1333
|
const obstacles = [];
|
|
597
|
-
const placementMatrix = new
|
|
598
|
-
const composed = new
|
|
599
|
-
const quaternion = new
|
|
600
|
-
const up = new
|
|
601
|
-
const scaleVector = new
|
|
1334
|
+
const placementMatrix = new import_three17.Matrix4();
|
|
1335
|
+
const composed = new import_three17.Matrix4();
|
|
1336
|
+
const quaternion = new import_three17.Quaternion();
|
|
1337
|
+
const up = new import_three17.Vector3(0, 1, 0);
|
|
1338
|
+
const scaleVector = new import_three17.Vector3();
|
|
602
1339
|
options.items.forEach((item, itemIndex) => {
|
|
603
1340
|
const variantCount = item.variants ?? 4;
|
|
604
1341
|
const variants = [];
|
|
@@ -614,9 +1351,9 @@ function scatter(options) {
|
|
|
614
1351
|
if (list.length === 0) return;
|
|
615
1352
|
variant.object.updateMatrixWorld(true);
|
|
616
1353
|
variant.object.traverse((child) => {
|
|
617
|
-
if (!(child instanceof
|
|
618
|
-
const instanced = new
|
|
619
|
-
instanced.instanceMatrix.setUsage(
|
|
1354
|
+
if (!(child instanceof import_three17.Mesh)) return;
|
|
1355
|
+
const instanced = new import_three17.InstancedMesh(child.geometry, child.material, list.length);
|
|
1356
|
+
instanced.instanceMatrix.setUsage(import_three17.DynamicDrawUsage);
|
|
620
1357
|
list.forEach((placement, index) => {
|
|
621
1358
|
quaternion.setFromAxisAngle(up, placement.rotationY);
|
|
622
1359
|
scaleVector.setScalar(placement.scale);
|
|
@@ -644,16 +1381,28 @@ function scatter(options) {
|
|
|
644
1381
|
DEFAULT_PALETTE,
|
|
645
1382
|
PALETTES,
|
|
646
1383
|
Rng,
|
|
1384
|
+
aboveWater,
|
|
647
1385
|
applyFog,
|
|
1386
|
+
applyWind,
|
|
648
1387
|
collectObstacles,
|
|
1388
|
+
createBush,
|
|
649
1389
|
createCrate,
|
|
1390
|
+
createDayCycle,
|
|
650
1391
|
createFence,
|
|
1392
|
+
createGrassTuft,
|
|
1393
|
+
createHouse,
|
|
651
1394
|
createLamp,
|
|
652
1395
|
createLightingRig,
|
|
1396
|
+
createPath,
|
|
653
1397
|
createRock,
|
|
1398
|
+
createRuin,
|
|
654
1399
|
createSky,
|
|
655
1400
|
createTerrain,
|
|
1401
|
+
createTower,
|
|
656
1402
|
createTree,
|
|
1403
|
+
createVillage,
|
|
1404
|
+
createWater,
|
|
1405
|
+
createWell,
|
|
657
1406
|
fractalNoise2,
|
|
658
1407
|
hash2,
|
|
659
1408
|
scatter,
|