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.js
CHANGED
|
@@ -79,7 +79,12 @@ var PALETTES = {
|
|
|
79
79
|
peak: 15265007,
|
|
80
80
|
skyTop: 4026552,
|
|
81
81
|
skyBottom: 12573160,
|
|
82
|
-
fog: 12111837
|
|
82
|
+
fog: 12111837,
|
|
83
|
+
water: 4161454,
|
|
84
|
+
sand: 13220234,
|
|
85
|
+
path: 10125663,
|
|
86
|
+
wall: 14273712,
|
|
87
|
+
roof: 11032126
|
|
83
88
|
},
|
|
84
89
|
autumn: {
|
|
85
90
|
foliage: [13202735, 14257722, 11754538, 14722373],
|
|
@@ -95,7 +100,12 @@ var PALETTES = {
|
|
|
95
100
|
peak: 14933716,
|
|
96
101
|
skyTop: 9333928,
|
|
97
102
|
skyBottom: 15255976,
|
|
98
|
-
fog: 14270888
|
|
103
|
+
fog: 14270888,
|
|
104
|
+
water: 4881042,
|
|
105
|
+
sand: 13348995,
|
|
106
|
+
path: 9270356,
|
|
107
|
+
wall: 13416596,
|
|
108
|
+
roof: 9062960
|
|
99
109
|
},
|
|
100
110
|
dusk: {
|
|
101
111
|
foliage: [2055750, 2385999, 1724992, 2978904],
|
|
@@ -111,7 +121,33 @@ var PALETTES = {
|
|
|
111
121
|
peak: 12105945,
|
|
112
122
|
skyTop: 1909061,
|
|
113
123
|
skyBottom: 13199946,
|
|
114
|
-
fog: 6969978
|
|
124
|
+
fog: 6969978,
|
|
125
|
+
water: 2968168,
|
|
126
|
+
sand: 9075306,
|
|
127
|
+
path: 6969938,
|
|
128
|
+
wall: 9274009,
|
|
129
|
+
roof: 4535640
|
|
130
|
+
},
|
|
131
|
+
winter: {
|
|
132
|
+
foliage: [3038280, 3696986, 5405288, 8889750],
|
|
133
|
+
trunk: 4864563,
|
|
134
|
+
rock: [10134701, 8687256, 11581632],
|
|
135
|
+
wood: 7823433,
|
|
136
|
+
woodDark: 5522746,
|
|
137
|
+
metal: 3752013,
|
|
138
|
+
lampGlow: 16767113,
|
|
139
|
+
grassLow: 13621726,
|
|
140
|
+
grassHigh: 15002606,
|
|
141
|
+
cliff: 7764349,
|
|
142
|
+
peak: 16054266,
|
|
143
|
+
skyTop: 5929894,
|
|
144
|
+
skyBottom: 14214380,
|
|
145
|
+
fog: 13424864,
|
|
146
|
+
water: 4878470,
|
|
147
|
+
sand: 12108486,
|
|
148
|
+
path: 9143160,
|
|
149
|
+
wall: 13814203,
|
|
150
|
+
roof: 7030328
|
|
115
151
|
}
|
|
116
152
|
};
|
|
117
153
|
var DEFAULT_PALETTE = PALETTES.meadow;
|
|
@@ -346,12 +382,333 @@ function createLamp(options = {}) {
|
|
|
346
382
|
return { object: group, obstacleRadius: 0.25 };
|
|
347
383
|
}
|
|
348
384
|
|
|
349
|
-
// src/
|
|
385
|
+
// src/props/grass.ts
|
|
386
|
+
import { ConeGeometry as ConeGeometry2, Group as Group6, IcosahedronGeometry as IcosahedronGeometry3, Mesh as Mesh6, MeshStandardMaterial as MeshStandardMaterial6 } from "three";
|
|
387
|
+
function createGrassTuft(options = {}) {
|
|
388
|
+
const rng = new Rng(options.seed ?? 1);
|
|
389
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
390
|
+
const blades = options.blades ?? rng.int(4, 6);
|
|
391
|
+
const group = new Group6();
|
|
392
|
+
group.name = "grass";
|
|
393
|
+
const material = new MeshStandardMaterial6({
|
|
394
|
+
color: rng.next() < 0.5 ? palette.grassHigh : rng.pick(palette.foliage),
|
|
395
|
+
flatShading: true
|
|
396
|
+
});
|
|
397
|
+
for (let i = 0; i < blades; i++) {
|
|
398
|
+
const height = rng.range(0.25, 0.5);
|
|
399
|
+
const blade = new Mesh6(new ConeGeometry2(0.035, height, 3), material);
|
|
400
|
+
blade.position.set(rng.jitter(0, 0.12), height / 2, rng.jitter(0, 0.12));
|
|
401
|
+
blade.rotation.set(rng.range(-0.25, 0.25), rng.range(0, Math.PI), rng.range(-0.25, 0.25));
|
|
402
|
+
group.add(blade);
|
|
403
|
+
}
|
|
404
|
+
return { object: group, obstacleRadius: 0 };
|
|
405
|
+
}
|
|
406
|
+
function createBush(options = {}) {
|
|
407
|
+
const rng = new Rng(options.seed ?? 1);
|
|
408
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
409
|
+
const size = options.size ?? rng.range(0.4, 0.7);
|
|
410
|
+
const group = new Group6();
|
|
411
|
+
group.name = "bush";
|
|
412
|
+
const material = new MeshStandardMaterial6({ color: rng.pick(palette.foliage), flatShading: true });
|
|
413
|
+
const blobs = rng.int(2, 3);
|
|
414
|
+
for (let i = 0; i < blobs; i++) {
|
|
415
|
+
const radius = size * rng.range(0.55, 0.85);
|
|
416
|
+
const blob = new Mesh6(new IcosahedronGeometry3(radius, 0), material);
|
|
417
|
+
blob.position.set(rng.jitter(0, size * 0.4), radius * 0.7, rng.jitter(0, size * 0.4));
|
|
418
|
+
blob.scale.y = rng.range(0.7, 0.85);
|
|
419
|
+
blob.rotation.y = rng.range(0, Math.PI);
|
|
420
|
+
group.add(blob);
|
|
421
|
+
}
|
|
422
|
+
return { object: group, obstacleRadius: size * 0.6 };
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// src/props/building.ts
|
|
350
426
|
import {
|
|
427
|
+
BoxGeometry as BoxGeometry4,
|
|
351
428
|
BufferAttribute as BufferAttribute2,
|
|
429
|
+
BufferGeometry,
|
|
430
|
+
CylinderGeometry as CylinderGeometry4,
|
|
431
|
+
Group as Group7,
|
|
432
|
+
Mesh as Mesh7,
|
|
433
|
+
MeshStandardMaterial as MeshStandardMaterial7
|
|
434
|
+
} from "three";
|
|
435
|
+
function prismGeometry(width, height, depth) {
|
|
436
|
+
const w = width / 2;
|
|
437
|
+
const d = depth / 2;
|
|
438
|
+
const positions = new Float32Array([
|
|
439
|
+
// front gable (z = +d)
|
|
440
|
+
-w,
|
|
441
|
+
0,
|
|
442
|
+
d,
|
|
443
|
+
w,
|
|
444
|
+
0,
|
|
445
|
+
d,
|
|
446
|
+
0,
|
|
447
|
+
height,
|
|
448
|
+
d,
|
|
449
|
+
// back gable (z = -d)
|
|
450
|
+
w,
|
|
451
|
+
0,
|
|
452
|
+
-d,
|
|
453
|
+
-w,
|
|
454
|
+
0,
|
|
455
|
+
-d,
|
|
456
|
+
0,
|
|
457
|
+
height,
|
|
458
|
+
-d,
|
|
459
|
+
// left slope
|
|
460
|
+
-w,
|
|
461
|
+
0,
|
|
462
|
+
-d,
|
|
463
|
+
-w,
|
|
464
|
+
0,
|
|
465
|
+
d,
|
|
466
|
+
0,
|
|
467
|
+
height,
|
|
468
|
+
d,
|
|
469
|
+
-w,
|
|
470
|
+
0,
|
|
471
|
+
-d,
|
|
472
|
+
0,
|
|
473
|
+
height,
|
|
474
|
+
d,
|
|
475
|
+
0,
|
|
476
|
+
height,
|
|
477
|
+
-d,
|
|
478
|
+
// right slope
|
|
479
|
+
w,
|
|
480
|
+
0,
|
|
481
|
+
d,
|
|
482
|
+
w,
|
|
483
|
+
0,
|
|
484
|
+
-d,
|
|
485
|
+
0,
|
|
486
|
+
height,
|
|
487
|
+
-d,
|
|
488
|
+
w,
|
|
489
|
+
0,
|
|
490
|
+
d,
|
|
491
|
+
0,
|
|
492
|
+
height,
|
|
493
|
+
-d,
|
|
494
|
+
0,
|
|
495
|
+
height,
|
|
496
|
+
d,
|
|
497
|
+
// bottom
|
|
498
|
+
-w,
|
|
499
|
+
0,
|
|
500
|
+
-d,
|
|
501
|
+
w,
|
|
502
|
+
0,
|
|
503
|
+
-d,
|
|
504
|
+
w,
|
|
505
|
+
0,
|
|
506
|
+
d,
|
|
507
|
+
-w,
|
|
508
|
+
0,
|
|
509
|
+
-d,
|
|
510
|
+
w,
|
|
511
|
+
0,
|
|
512
|
+
d,
|
|
513
|
+
-w,
|
|
514
|
+
0,
|
|
515
|
+
d
|
|
516
|
+
]);
|
|
517
|
+
const geometry = new BufferGeometry();
|
|
518
|
+
geometry.setAttribute("position", new BufferAttribute2(positions, 3));
|
|
519
|
+
geometry.computeVertexNormals();
|
|
520
|
+
return geometry;
|
|
521
|
+
}
|
|
522
|
+
function createHouse(options = {}) {
|
|
523
|
+
const rng = new Rng(options.seed ?? 1);
|
|
524
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
525
|
+
const width = options.width ?? rng.range(3.2, 4.2);
|
|
526
|
+
const depth = options.depth ?? width * rng.range(0.75, 0.9);
|
|
527
|
+
const wallHeight = options.wallHeight ?? rng.range(2.1, 2.4);
|
|
528
|
+
const group = new Group7();
|
|
529
|
+
group.name = "house";
|
|
530
|
+
const wall = new MeshStandardMaterial7({ color: palette.wall, flatShading: true });
|
|
531
|
+
wall.color.offsetHSL(0, 0, rng.range(-0.03, 0.03));
|
|
532
|
+
const roof = new MeshStandardMaterial7({ color: palette.roof, flatShading: true });
|
|
533
|
+
roof.color.offsetHSL(0, 0, rng.range(-0.04, 0.04));
|
|
534
|
+
const stone = new MeshStandardMaterial7({ color: palette.rock[0], flatShading: true });
|
|
535
|
+
const wood = new MeshStandardMaterial7({ color: palette.woodDark, flatShading: true });
|
|
536
|
+
const foundation = new Mesh7(new BoxGeometry4(width + 0.3, 1.6, depth + 0.3), stone);
|
|
537
|
+
foundation.position.y = -0.55;
|
|
538
|
+
group.add(foundation);
|
|
539
|
+
const body = new Mesh7(new BoxGeometry4(width, wallHeight, depth), wall);
|
|
540
|
+
body.position.y = wallHeight / 2;
|
|
541
|
+
group.add(body);
|
|
542
|
+
const ridgeHeight = width * rng.range(0.32, 0.4);
|
|
543
|
+
const roofMesh = new Mesh7(prismGeometry(width + 0.5, ridgeHeight, depth + 0.6), roof);
|
|
544
|
+
roofMesh.position.y = wallHeight - 0.02;
|
|
545
|
+
group.add(roofMesh);
|
|
546
|
+
const chimney = new Mesh7(new BoxGeometry4(0.34, ridgeHeight + 0.8, 0.34), stone);
|
|
547
|
+
chimney.position.set(rng.pick([-1, 1]) * width * 0.22, wallHeight + ridgeHeight * 0.45, depth * 0.12);
|
|
548
|
+
group.add(chimney);
|
|
549
|
+
const door = new Mesh7(new BoxGeometry4(0.85, 1.5, 0.08), wood);
|
|
550
|
+
door.position.set(rng.range(-0.4, 0.4), 0.75, depth / 2 + 0.02);
|
|
551
|
+
group.add(door);
|
|
552
|
+
const glass = new MeshStandardMaterial7({
|
|
553
|
+
color: palette.lampGlow,
|
|
554
|
+
emissive: palette.lampGlow,
|
|
555
|
+
emissiveIntensity: 1
|
|
556
|
+
});
|
|
557
|
+
const windowGeometry = new BoxGeometry4(0.6, 0.62, 0.08);
|
|
558
|
+
const front = new Mesh7(windowGeometry, glass);
|
|
559
|
+
front.position.set(door.position.x < 0 ? width * 0.28 : -width * 0.28, 1.35, depth / 2 + 0.02);
|
|
560
|
+
group.add(front);
|
|
561
|
+
for (const side of [-1, 1]) {
|
|
562
|
+
if (rng.next() < 0.35) continue;
|
|
563
|
+
const pane = new Mesh7(windowGeometry, glass);
|
|
564
|
+
pane.position.set(side * (width / 2 + 0.02), 1.35, rng.range(-0.3, 0.3) * depth);
|
|
565
|
+
pane.rotation.y = Math.PI / 2;
|
|
566
|
+
group.add(pane);
|
|
567
|
+
}
|
|
568
|
+
return { object: group, obstacleRadius: Math.hypot(width + 0.5, depth + 0.5) / 2 };
|
|
569
|
+
}
|
|
570
|
+
function createTower(options = {}) {
|
|
571
|
+
const rng = new Rng(options.seed ?? 1);
|
|
572
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
573
|
+
const height = options.height ?? rng.range(4.2, 5.2);
|
|
574
|
+
const group = new Group7();
|
|
575
|
+
group.name = "tower";
|
|
576
|
+
const wood = new MeshStandardMaterial7({ color: palette.wood, flatShading: true });
|
|
577
|
+
const woodDark = new MeshStandardMaterial7({ color: palette.woodDark, flatShading: true });
|
|
578
|
+
const roof = new MeshStandardMaterial7({ color: palette.roof, flatShading: true });
|
|
579
|
+
const spread = 1;
|
|
580
|
+
for (const x of [-1, 1]) {
|
|
581
|
+
for (const z of [-1, 1]) {
|
|
582
|
+
const leg = new Mesh7(new CylinderGeometry4(0.09, 0.12, height, 6), woodDark);
|
|
583
|
+
leg.position.set(x * spread * 0.75, height / 2 - 0.6, z * spread * 0.75);
|
|
584
|
+
leg.rotation.z = -x * 0.1;
|
|
585
|
+
leg.rotation.x = z * 0.1;
|
|
586
|
+
group.add(leg);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
for (const level of [0.3, 0.62]) {
|
|
590
|
+
const brace = new Mesh7(new BoxGeometry4(spread * 2.1, 0.09, 0.07), wood);
|
|
591
|
+
brace.position.set(0, height * level, spread * 0.82 * (1 - level * 0.35));
|
|
592
|
+
brace.rotation.z = rng.pick([-0.5, 0.5]);
|
|
593
|
+
group.add(brace);
|
|
594
|
+
const braceSide = new Mesh7(new BoxGeometry4(0.07, 0.09, spread * 2.1), wood);
|
|
595
|
+
braceSide.position.set(spread * 0.82 * (1 - level * 0.35), height * level, 0);
|
|
596
|
+
braceSide.rotation.x = rng.pick([-0.5, 0.5]);
|
|
597
|
+
group.add(braceSide);
|
|
598
|
+
}
|
|
599
|
+
const platform = new Mesh7(new BoxGeometry4(spread * 2.2, 0.14, spread * 2.2), wood);
|
|
600
|
+
platform.position.y = height - 0.5;
|
|
601
|
+
group.add(platform);
|
|
602
|
+
for (const x of [-1, 1]) {
|
|
603
|
+
for (const z of [-1, 1]) {
|
|
604
|
+
const post = new Mesh7(new BoxGeometry4(0.08, 0.9, 0.08), woodDark);
|
|
605
|
+
post.position.set(x * spread * 1, height - 0.05, z * spread * 1);
|
|
606
|
+
group.add(post);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
for (const [rx, rz, w, d] of [
|
|
610
|
+
[0, 1, spread * 2.1, 0.06],
|
|
611
|
+
[0, -1, spread * 2.1, 0.06],
|
|
612
|
+
[1, 0, 0.06, spread * 2.1],
|
|
613
|
+
[-1, 0, 0.06, spread * 2.1]
|
|
614
|
+
]) {
|
|
615
|
+
const rail = new Mesh7(new BoxGeometry4(w, 0.07, d), wood);
|
|
616
|
+
rail.position.set(rx * spread, height + 0.28, rz * spread);
|
|
617
|
+
group.add(rail);
|
|
618
|
+
}
|
|
619
|
+
const cap = new Mesh7(new CylinderGeometry4(0, spread * 1.55, 1, 4), roof);
|
|
620
|
+
cap.position.y = height + 1.15;
|
|
621
|
+
cap.rotation.y = Math.PI / 4;
|
|
622
|
+
group.add(cap);
|
|
623
|
+
return { object: group, obstacleRadius: spread * 1.35 };
|
|
624
|
+
}
|
|
625
|
+
function createWell(options = {}) {
|
|
626
|
+
const rng = new Rng(options.seed ?? 1);
|
|
627
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
628
|
+
const group = new Group7();
|
|
629
|
+
group.name = "well";
|
|
630
|
+
const stone = new MeshStandardMaterial7({ color: rng.pick(palette.rock), flatShading: true });
|
|
631
|
+
const wood = new MeshStandardMaterial7({ color: palette.woodDark, flatShading: true });
|
|
632
|
+
const roof = new MeshStandardMaterial7({ color: palette.roof, flatShading: true });
|
|
633
|
+
const ring = new Mesh7(new CylinderGeometry4(0.85, 0.95, 0.75, 10), stone);
|
|
634
|
+
ring.position.y = 0.375;
|
|
635
|
+
group.add(ring);
|
|
636
|
+
const inner = new Mesh7(
|
|
637
|
+
new CylinderGeometry4(0.62, 0.62, 0.05, 10),
|
|
638
|
+
new MeshStandardMaterial7({ color: 1450542 })
|
|
639
|
+
);
|
|
640
|
+
inner.position.y = 0.76;
|
|
641
|
+
group.add(inner);
|
|
642
|
+
for (const side of [-1, 1]) {
|
|
643
|
+
const post = new Mesh7(new CylinderGeometry4(0.06, 0.075, 1.9, 5), wood);
|
|
644
|
+
post.position.set(side * 0.72, 0.95, 0);
|
|
645
|
+
group.add(post);
|
|
646
|
+
}
|
|
647
|
+
const bar = new Mesh7(new CylinderGeometry4(0.045, 0.045, 1.5, 5), wood);
|
|
648
|
+
bar.rotation.z = Math.PI / 2;
|
|
649
|
+
bar.position.y = 1.72;
|
|
650
|
+
group.add(bar);
|
|
651
|
+
const cap = new Mesh7(prismGeometry(2, 0.55, 0.65), roof);
|
|
652
|
+
cap.position.y = 1.86;
|
|
653
|
+
cap.rotation.y = Math.PI / 2;
|
|
654
|
+
group.add(cap);
|
|
655
|
+
const rope = new Mesh7(new CylinderGeometry4(0.012, 0.012, 0.6, 4), wood);
|
|
656
|
+
rope.position.y = 1.42;
|
|
657
|
+
group.add(rope);
|
|
658
|
+
const bucket = new Mesh7(new CylinderGeometry4(0.13, 0.1, 0.2, 7), wood);
|
|
659
|
+
bucket.position.y = 1.05;
|
|
660
|
+
group.add(bucket);
|
|
661
|
+
return { object: group, obstacleRadius: 1 };
|
|
662
|
+
}
|
|
663
|
+
function createRuin(options = {}) {
|
|
664
|
+
const rng = new Rng(options.seed ?? 1);
|
|
665
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
666
|
+
const size = options.size ?? rng.range(3.5, 5);
|
|
667
|
+
const depth = size * rng.range(0.7, 0.9);
|
|
668
|
+
const group = new Group7();
|
|
669
|
+
group.name = "ruin";
|
|
670
|
+
const stone = new MeshStandardMaterial7({ color: rng.pick(palette.rock), flatShading: true });
|
|
671
|
+
stone.color.offsetHSL(0, 0, rng.range(-0.04, 0.02));
|
|
672
|
+
const thickness = 0.35;
|
|
673
|
+
const runs = [
|
|
674
|
+
[0, -depth / 2, size, true],
|
|
675
|
+
[-size / 2, 0, depth, false],
|
|
676
|
+
[size / 2, 0, depth, false],
|
|
677
|
+
[size * 0.3, depth / 2, size * 0.35, true]
|
|
678
|
+
];
|
|
679
|
+
for (const [cx, cz, length, alongX] of runs) {
|
|
680
|
+
const segments = Math.max(2, Math.round(length / 1.1));
|
|
681
|
+
const step = length / segments;
|
|
682
|
+
for (let i = 0; i < segments; i++) {
|
|
683
|
+
if (rng.next() < 0.22) continue;
|
|
684
|
+
const height = rng.range(0.5, 2.2);
|
|
685
|
+
const wall = new Mesh7(
|
|
686
|
+
new BoxGeometry4(alongX ? step * 0.96 : thickness, height, alongX ? thickness : step * 0.96),
|
|
687
|
+
stone
|
|
688
|
+
);
|
|
689
|
+
const offset = -length / 2 + step * (i + 0.5);
|
|
690
|
+
wall.position.set(alongX ? cx + offset : cx, height / 2, alongX ? cz : cz + offset);
|
|
691
|
+
wall.rotation.y = rng.range(-0.03, 0.03);
|
|
692
|
+
group.add(wall);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
const blocks = rng.int(4, 7);
|
|
696
|
+
for (let i = 0; i < blocks; i++) {
|
|
697
|
+
const s = rng.range(0.25, 0.55);
|
|
698
|
+
const block = new Mesh7(new BoxGeometry4(s, s * rng.range(0.6, 1), s * rng.range(0.7, 1.2)), stone);
|
|
699
|
+
block.position.set(rng.range(-size * 0.6, size * 0.6), s * 0.3, rng.range(-depth * 0.6, depth * 0.7));
|
|
700
|
+
block.rotation.set(rng.range(-0.3, 0.3), rng.range(0, Math.PI), rng.range(-0.3, 0.3));
|
|
701
|
+
group.add(block);
|
|
702
|
+
}
|
|
703
|
+
return { object: group, obstacleRadius: Math.hypot(size, depth) / 2 };
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// src/environment/terrain.ts
|
|
707
|
+
import {
|
|
708
|
+
BufferAttribute as BufferAttribute3,
|
|
352
709
|
Color,
|
|
353
|
-
Mesh as
|
|
354
|
-
MeshStandardMaterial as
|
|
710
|
+
Mesh as Mesh8,
|
|
711
|
+
MeshStandardMaterial as MeshStandardMaterial8,
|
|
355
712
|
PlaneGeometry
|
|
356
713
|
} from "three";
|
|
357
714
|
function createTerrain(options = {}) {
|
|
@@ -381,21 +738,27 @@ function createTerrain(options = {}) {
|
|
|
381
738
|
const grassHigh = new Color(palette.grassHigh);
|
|
382
739
|
const cliff = new Color(palette.cliff);
|
|
383
740
|
const peak = new Color(palette.peak);
|
|
741
|
+
const sand = new Color(palette.sand);
|
|
742
|
+
const waterLevel = options.waterLevel;
|
|
384
743
|
const scratch = new Color();
|
|
385
744
|
for (let i = 0; i < positions.count; i++) {
|
|
386
|
-
const
|
|
745
|
+
const y = positions.getY(i);
|
|
746
|
+
const h = y / amplitude;
|
|
387
747
|
const slope = 1 - normals.getY(i);
|
|
388
748
|
scratch.copy(grassLow).lerp(grassHigh, Math.min(1, h * 1.6));
|
|
389
749
|
if (h > 0.75) scratch.lerp(peak, (h - 0.75) * 4);
|
|
390
750
|
if (slope > 0.15) scratch.lerp(cliff, Math.min(1, (slope - 0.15) * 4));
|
|
751
|
+
if (waterLevel !== void 0 && y < waterLevel + 0.5) {
|
|
752
|
+
scratch.lerp(sand, Math.min(1, (waterLevel + 0.5 - y) * 1.6));
|
|
753
|
+
}
|
|
391
754
|
colors[i * 3] = scratch.r;
|
|
392
755
|
colors[i * 3 + 1] = scratch.g;
|
|
393
756
|
colors[i * 3 + 2] = scratch.b;
|
|
394
757
|
}
|
|
395
|
-
geometry.setAttribute("color", new
|
|
396
|
-
const mesh = new
|
|
758
|
+
geometry.setAttribute("color", new BufferAttribute3(colors, 3));
|
|
759
|
+
const mesh = new Mesh8(
|
|
397
760
|
geometry,
|
|
398
|
-
new
|
|
761
|
+
new MeshStandardMaterial8({ vertexColors: true, flatShading: true })
|
|
399
762
|
);
|
|
400
763
|
mesh.name = "terrain";
|
|
401
764
|
return { mesh, heightAt, size, seed };
|
|
@@ -405,7 +768,7 @@ function createTerrain(options = {}) {
|
|
|
405
768
|
import {
|
|
406
769
|
BackSide,
|
|
407
770
|
Color as Color2,
|
|
408
|
-
Mesh as
|
|
771
|
+
Mesh as Mesh9,
|
|
409
772
|
ShaderMaterial,
|
|
410
773
|
SphereGeometry as SphereGeometry2
|
|
411
774
|
} from "three";
|
|
@@ -439,7 +802,7 @@ function createSky(options = {}) {
|
|
|
439
802
|
}`
|
|
440
803
|
)
|
|
441
804
|
});
|
|
442
|
-
const mesh = new
|
|
805
|
+
const mesh = new Mesh9(new SphereGeometry2(options.radius ?? 400, 16, 12), material);
|
|
443
806
|
mesh.name = "sky";
|
|
444
807
|
return {
|
|
445
808
|
mesh,
|
|
@@ -455,7 +818,7 @@ import {
|
|
|
455
818
|
AmbientLight,
|
|
456
819
|
DirectionalLight,
|
|
457
820
|
Fog,
|
|
458
|
-
Group as
|
|
821
|
+
Group as Group8,
|
|
459
822
|
HemisphereLight
|
|
460
823
|
} from "three";
|
|
461
824
|
var PRESETS = {
|
|
@@ -498,7 +861,7 @@ var PRESETS = {
|
|
|
498
861
|
};
|
|
499
862
|
function createLightingRig(preset = "day") {
|
|
500
863
|
const config = PRESETS[preset];
|
|
501
|
-
const group = new
|
|
864
|
+
const group = new Group8();
|
|
502
865
|
group.name = `lighting-${preset}`;
|
|
503
866
|
const sun = new DirectionalLight(config.sun, config.sunIntensity);
|
|
504
867
|
sun.position.set(...config.sunPos);
|
|
@@ -521,15 +884,392 @@ function applyFog(scene, preset, palette = DEFAULT_PALETTE) {
|
|
|
521
884
|
scene.fog = new Fog(palette.fog, near, far);
|
|
522
885
|
}
|
|
523
886
|
|
|
887
|
+
// src/environment/water.ts
|
|
888
|
+
import { Mesh as Mesh10, MeshStandardMaterial as MeshStandardMaterial9, PlaneGeometry as PlaneGeometry2 } from "three";
|
|
889
|
+
function createWater(options = {}) {
|
|
890
|
+
const level = options.level ?? 0.8;
|
|
891
|
+
const size = options.size ?? 200;
|
|
892
|
+
const resolution = options.resolution ?? 40;
|
|
893
|
+
const amplitude = options.amplitude ?? 0.06;
|
|
894
|
+
const speed = options.speed ?? 1;
|
|
895
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
896
|
+
const geometry = new PlaneGeometry2(size, size, resolution, resolution);
|
|
897
|
+
geometry.rotateX(-Math.PI / 2);
|
|
898
|
+
const positions = geometry.getAttribute("position");
|
|
899
|
+
const mesh = new Mesh10(
|
|
900
|
+
geometry,
|
|
901
|
+
new MeshStandardMaterial9({
|
|
902
|
+
color: palette.water,
|
|
903
|
+
transparent: true,
|
|
904
|
+
opacity: 0.85,
|
|
905
|
+
flatShading: true,
|
|
906
|
+
metalness: 0.35,
|
|
907
|
+
roughness: 0.4
|
|
908
|
+
})
|
|
909
|
+
);
|
|
910
|
+
mesh.name = "water";
|
|
911
|
+
mesh.position.y = level;
|
|
912
|
+
let time = 0;
|
|
913
|
+
const update = (dt) => {
|
|
914
|
+
time += dt * speed;
|
|
915
|
+
for (let i = 0; i < positions.count; i++) {
|
|
916
|
+
const x = positions.getX(i);
|
|
917
|
+
const z = positions.getZ(i);
|
|
918
|
+
positions.setY(
|
|
919
|
+
i,
|
|
920
|
+
Math.sin(x * 0.35 + time) * Math.cos(z * 0.3 + time * 0.8) * amplitude
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
positions.needsUpdate = true;
|
|
924
|
+
geometry.computeVertexNormals();
|
|
925
|
+
};
|
|
926
|
+
update(0);
|
|
927
|
+
return {
|
|
928
|
+
mesh,
|
|
929
|
+
level,
|
|
930
|
+
update,
|
|
931
|
+
isUnderwater: (groundHeight) => groundHeight < level
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
function aboveWater(terrain, water, margin = 0.25) {
|
|
935
|
+
return (x, z) => terrain.heightAt(x, z) > water.level + margin;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// src/environment/dayCycle.ts
|
|
939
|
+
import { Color as Color3, PointLight as PointLight2 } from "three";
|
|
940
|
+
function createDayCycle(options = {}) {
|
|
941
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
942
|
+
const dayLength = options.dayLength ?? 60;
|
|
943
|
+
const frames = [
|
|
944
|
+
{ t: 0, skyTop: 725030, skyBottom: 1712960, sun: 9348824, sunIntensity: 0.03, ambientIntensity: 0.06, fog: 1317422 },
|
|
945
|
+
{ t: 0.23, skyTop: 2569052, skyBottom: 6968432, sun: 13605490, sunIntensity: 0.3, ambientIntensity: 0.12, fog: 4866648 },
|
|
946
|
+
{ t: 0.3, skyTop: 4877216, skyBottom: 15247738, sun: 16758881, sunIntensity: 1, ambientIntensity: 0.26, fog: 11569778 },
|
|
947
|
+
{ t: 0.5, skyTop: palette.skyTop, skyBottom: palette.skyBottom, sun: 16774368, sunIntensity: 1.9, ambientIntensity: 0.45, fog: palette.fog },
|
|
948
|
+
{ t: 0.7, skyTop: 4872852, skyBottom: 14718302, sun: 16752720, sunIntensity: 1, ambientIntensity: 0.26, fog: 10517096 },
|
|
949
|
+
{ t: 0.78, skyTop: 2304594, skyBottom: 9065824, sun: 14191194, sunIntensity: 0.25, ambientIntensity: 0.11, fog: 4603476 },
|
|
950
|
+
{ t: 1, skyTop: 725030, skyBottom: 1712960, sun: 9348824, sunIntensity: 0.03, ambientIntensity: 0.06, fog: 1317422 }
|
|
951
|
+
];
|
|
952
|
+
const lampLights = [];
|
|
953
|
+
const lampBulbs = [];
|
|
954
|
+
for (const entry of options.lamps ?? []) {
|
|
955
|
+
const root = entry.isObject3D ? entry : entry.object;
|
|
956
|
+
root.traverse((child) => {
|
|
957
|
+
if (child instanceof PointLight2) {
|
|
958
|
+
lampLights.push({ light: child, base: child.intensity || 6 });
|
|
959
|
+
}
|
|
960
|
+
const material = child.material;
|
|
961
|
+
if (material?.emissive && material.emissiveIntensity > 0.5) {
|
|
962
|
+
lampBulbs.push({ material, base: material.emissiveIntensity });
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
const colorA = new Color3();
|
|
967
|
+
const colorB = new Color3();
|
|
968
|
+
let timeOfDay = options.timeOfDay ?? 0.5;
|
|
969
|
+
let sunElevation = 0;
|
|
970
|
+
const sample = (t) => {
|
|
971
|
+
let a = frames[0];
|
|
972
|
+
let b = frames[frames.length - 1];
|
|
973
|
+
for (let i = 0; i < frames.length - 1; i++) {
|
|
974
|
+
if (t >= frames[i].t && t <= frames[i + 1].t) {
|
|
975
|
+
a = frames[i];
|
|
976
|
+
b = frames[i + 1];
|
|
977
|
+
break;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
const f = b.t === a.t ? 0 : (t - a.t) / (b.t - a.t);
|
|
981
|
+
const lerpHex = (ha, hb) => colorA.setHex(ha).lerp(colorB.setHex(hb), f).getHex();
|
|
982
|
+
return {
|
|
983
|
+
t,
|
|
984
|
+
skyTop: lerpHex(a.skyTop, b.skyTop),
|
|
985
|
+
skyBottom: lerpHex(a.skyBottom, b.skyBottom),
|
|
986
|
+
sun: lerpHex(a.sun, b.sun),
|
|
987
|
+
sunIntensity: a.sunIntensity + (b.sunIntensity - a.sunIntensity) * f,
|
|
988
|
+
ambientIntensity: a.ambientIntensity + (b.ambientIntensity - a.ambientIntensity) * f,
|
|
989
|
+
fog: lerpHex(a.fog, b.fog)
|
|
990
|
+
};
|
|
991
|
+
};
|
|
992
|
+
const apply = () => {
|
|
993
|
+
const t = timeOfDay;
|
|
994
|
+
const frame = sample(t);
|
|
995
|
+
const sunAngle = (t - 0.25) * Math.PI * 2;
|
|
996
|
+
sunElevation = Math.sin(sunAngle);
|
|
997
|
+
options.sky?.setColors(frame.skyTop, frame.skyBottom);
|
|
998
|
+
if (options.rig) {
|
|
999
|
+
const { sun, ambient, hemisphere } = options.rig;
|
|
1000
|
+
sun.color.setHex(frame.sun);
|
|
1001
|
+
sun.intensity = frame.sunIntensity;
|
|
1002
|
+
sun.position.set(Math.cos(sunAngle) * 40, Math.max(sunElevation, -0.2) * 45 + 6, 16);
|
|
1003
|
+
ambient.intensity = frame.ambientIntensity;
|
|
1004
|
+
hemisphere.intensity = frame.ambientIntensity * 1.4;
|
|
1005
|
+
}
|
|
1006
|
+
if (options.scene?.fog && "color" in options.scene.fog) {
|
|
1007
|
+
options.scene.fog.color.setHex(frame.fog);
|
|
1008
|
+
}
|
|
1009
|
+
const night = Math.min(1, Math.max(0, (0.06 - sunElevation) / 0.16));
|
|
1010
|
+
for (const { light, base } of lampLights) light.intensity = base * night;
|
|
1011
|
+
for (const { material, base } of lampBulbs) {
|
|
1012
|
+
material.emissiveIntensity = 0.15 * base + 0.85 * base * night;
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
apply();
|
|
1016
|
+
return {
|
|
1017
|
+
get timeOfDay() {
|
|
1018
|
+
return timeOfDay;
|
|
1019
|
+
},
|
|
1020
|
+
set timeOfDay(t) {
|
|
1021
|
+
timeOfDay = (t % 1 + 1) % 1;
|
|
1022
|
+
apply();
|
|
1023
|
+
},
|
|
1024
|
+
get sunElevation() {
|
|
1025
|
+
return sunElevation;
|
|
1026
|
+
},
|
|
1027
|
+
get isNight() {
|
|
1028
|
+
return sunElevation < 0;
|
|
1029
|
+
},
|
|
1030
|
+
update(dt) {
|
|
1031
|
+
timeOfDay = (timeOfDay + dt / dayLength) % 1;
|
|
1032
|
+
apply();
|
|
1033
|
+
},
|
|
1034
|
+
set(t) {
|
|
1035
|
+
timeOfDay = (t % 1 + 1) % 1;
|
|
1036
|
+
apply();
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// src/environment/wind.ts
|
|
1042
|
+
import { Mesh as Mesh11 } from "three";
|
|
1043
|
+
function applyWind(target, options = {}) {
|
|
1044
|
+
const strength = options.strength ?? 0.06;
|
|
1045
|
+
const frequency = options.frequency ?? 1.2;
|
|
1046
|
+
const anchor = options.anchorHeight ?? 0.6;
|
|
1047
|
+
const time = { value: 0 };
|
|
1048
|
+
const patched = /* @__PURE__ */ new Set();
|
|
1049
|
+
target.traverse((child) => {
|
|
1050
|
+
if (!(child instanceof Mesh11)) return;
|
|
1051
|
+
const materials = Array.isArray(child.material) ? child.material : [child.material];
|
|
1052
|
+
for (const material of materials) {
|
|
1053
|
+
if (patched.has(material)) continue;
|
|
1054
|
+
patched.add(material);
|
|
1055
|
+
material.onBeforeCompile = (shader) => {
|
|
1056
|
+
shader.uniforms.uWindTime = time;
|
|
1057
|
+
shader.vertexShader = shader.vertexShader.replace(
|
|
1058
|
+
"#include <common>",
|
|
1059
|
+
`#include <common>
|
|
1060
|
+
uniform float uWindTime;`
|
|
1061
|
+
).replace(
|
|
1062
|
+
"#include <begin_vertex>",
|
|
1063
|
+
`#include <begin_vertex>
|
|
1064
|
+
{
|
|
1065
|
+
float sway = max(position.y - ${anchor.toFixed(3)}, 0.0);
|
|
1066
|
+
float phase = 0.0;
|
|
1067
|
+
#ifdef USE_INSTANCING
|
|
1068
|
+
phase = instanceMatrix[3].x * 0.7 + instanceMatrix[3].z * 1.3;
|
|
1069
|
+
#endif
|
|
1070
|
+
float w = sin(uWindTime * ${(frequency * 2).toFixed(3)} + phase + position.y * 0.8);
|
|
1071
|
+
transformed.x += w * sway * ${strength.toFixed(4)} * 4.0;
|
|
1072
|
+
transformed.z += cos(uWindTime * ${(frequency * 1.4).toFixed(3)} + phase) * sway * ${strength.toFixed(4)} * 2.0;
|
|
1073
|
+
}`
|
|
1074
|
+
);
|
|
1075
|
+
};
|
|
1076
|
+
material.needsUpdate = true;
|
|
1077
|
+
}
|
|
1078
|
+
});
|
|
1079
|
+
return {
|
|
1080
|
+
update(dt) {
|
|
1081
|
+
time.value += dt;
|
|
1082
|
+
},
|
|
1083
|
+
materials: [...patched]
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// src/environment/path.ts
|
|
1088
|
+
import {
|
|
1089
|
+
BufferAttribute as BufferAttribute5,
|
|
1090
|
+
BufferGeometry as BufferGeometry2,
|
|
1091
|
+
CatmullRomCurve3,
|
|
1092
|
+
Mesh as Mesh12,
|
|
1093
|
+
MeshStandardMaterial as MeshStandardMaterial10,
|
|
1094
|
+
Vector3 as Vector32
|
|
1095
|
+
} from "three";
|
|
1096
|
+
function createPath(points, options = {}) {
|
|
1097
|
+
const width = options.width ?? 1.8;
|
|
1098
|
+
const surface = options.surface ?? 0;
|
|
1099
|
+
const heightAt = typeof surface === "number" ? () => surface : (x, z) => surface(x, z);
|
|
1100
|
+
const loop = options.loop ?? false;
|
|
1101
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
1102
|
+
const controls = points.map((p) => new Vector32(p.x, 0, "z" in p ? p.z : 0));
|
|
1103
|
+
const curve = new CatmullRomCurve3(controls, loop, "centripetal");
|
|
1104
|
+
const length = curve.getLength();
|
|
1105
|
+
const samples = Math.max(8, Math.ceil(length * (options.samplesPerUnit ?? 1)));
|
|
1106
|
+
const route = [];
|
|
1107
|
+
for (let i = 0; i <= samples; i++) {
|
|
1108
|
+
if (loop && i === samples) break;
|
|
1109
|
+
const p = curve.getPoint(i / samples);
|
|
1110
|
+
route.push(new Vector32(p.x, heightAt(p.x, p.z), p.z));
|
|
1111
|
+
}
|
|
1112
|
+
const edgeCount = loop ? route.length + 1 : route.length;
|
|
1113
|
+
const positions = new Float32Array(edgeCount * 2 * 3);
|
|
1114
|
+
const direction = new Vector32();
|
|
1115
|
+
const perp = new Vector32();
|
|
1116
|
+
for (let i = 0; i < edgeCount; i++) {
|
|
1117
|
+
const current = route[i % route.length];
|
|
1118
|
+
const previous = route[(i - 1 + route.length) % route.length];
|
|
1119
|
+
const next = route[(i + 1) % route.length];
|
|
1120
|
+
if (!loop && i === 0) direction.subVectors(next, current);
|
|
1121
|
+
else if (!loop && i === edgeCount - 1) direction.subVectors(current, previous);
|
|
1122
|
+
else direction.subVectors(next, previous);
|
|
1123
|
+
perp.set(-direction.z, 0, direction.x).normalize().multiplyScalar(width / 2);
|
|
1124
|
+
const left = i * 6;
|
|
1125
|
+
positions[left] = current.x - perp.x;
|
|
1126
|
+
positions[left + 1] = heightAt(current.x - perp.x, current.z - perp.z) + 0.05;
|
|
1127
|
+
positions[left + 2] = current.z - perp.z;
|
|
1128
|
+
positions[left + 3] = current.x + perp.x;
|
|
1129
|
+
positions[left + 4] = heightAt(current.x + perp.x, current.z + perp.z) + 0.05;
|
|
1130
|
+
positions[left + 5] = current.z + perp.z;
|
|
1131
|
+
}
|
|
1132
|
+
const indices = [];
|
|
1133
|
+
for (let i = 0; i < edgeCount - 1; i++) {
|
|
1134
|
+
const a = i * 2;
|
|
1135
|
+
indices.push(a, a + 1, a + 2, a + 1, a + 3, a + 2);
|
|
1136
|
+
}
|
|
1137
|
+
const geometry = new BufferGeometry2();
|
|
1138
|
+
geometry.setAttribute("position", new BufferAttribute5(positions, 3));
|
|
1139
|
+
geometry.setIndex(indices);
|
|
1140
|
+
geometry.computeVertexNormals();
|
|
1141
|
+
const mesh = new Mesh12(
|
|
1142
|
+
geometry,
|
|
1143
|
+
new MeshStandardMaterial10({
|
|
1144
|
+
color: palette.path,
|
|
1145
|
+
flatShading: true,
|
|
1146
|
+
polygonOffset: true,
|
|
1147
|
+
polygonOffsetFactor: -1
|
|
1148
|
+
})
|
|
1149
|
+
);
|
|
1150
|
+
mesh.name = "path";
|
|
1151
|
+
const keepOutMargin = options.keepOutMargin ?? 0.6;
|
|
1152
|
+
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 }));
|
|
1153
|
+
const half = width / 2;
|
|
1154
|
+
const contains = (x, z) => {
|
|
1155
|
+
const count = loop ? route.length : route.length - 1;
|
|
1156
|
+
for (let i = 0; i < count; i++) {
|
|
1157
|
+
const a = route[i];
|
|
1158
|
+
const b = route[(i + 1) % route.length];
|
|
1159
|
+
const abx = b.x - a.x;
|
|
1160
|
+
const abz = b.z - a.z;
|
|
1161
|
+
const lengthSq = abx * abx + abz * abz || 1e-9;
|
|
1162
|
+
const t = Math.max(0, Math.min(1, ((x - a.x) * abx + (z - a.z) * abz) / lengthSq));
|
|
1163
|
+
const dx = x - (a.x + abx * t);
|
|
1164
|
+
const dz = z - (a.z + abz * t);
|
|
1165
|
+
if (dx * dx + dz * dz <= half * half) return true;
|
|
1166
|
+
}
|
|
1167
|
+
return false;
|
|
1168
|
+
};
|
|
1169
|
+
return { mesh, route, keepOut, contains, loop };
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// src/generators/village.ts
|
|
1173
|
+
import { Group as Group9 } from "three";
|
|
1174
|
+
function createVillage(options = {}) {
|
|
1175
|
+
const rng = new Rng(options.seed ?? 1);
|
|
1176
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
1177
|
+
const center = options.center ?? { x: 0, z: 0 };
|
|
1178
|
+
const radius = options.radius ?? 12;
|
|
1179
|
+
const houseCount = options.houses ?? 5;
|
|
1180
|
+
const surface = options.surface ?? 0;
|
|
1181
|
+
const heightAt = typeof surface === "number" ? () => surface : (x, z) => surface(x, z);
|
|
1182
|
+
const group = new Group9();
|
|
1183
|
+
group.name = "village";
|
|
1184
|
+
const props = [];
|
|
1185
|
+
const lamps = [];
|
|
1186
|
+
const place = (prop, x, z, rotY) => {
|
|
1187
|
+
prop.object.position.set(x, heightAt(x, z), z);
|
|
1188
|
+
prop.object.rotation.y = rotY;
|
|
1189
|
+
group.add(prop.object);
|
|
1190
|
+
props.push(prop);
|
|
1191
|
+
return prop;
|
|
1192
|
+
};
|
|
1193
|
+
const findSpot = (angle, distance, footprint) => {
|
|
1194
|
+
let x = center.x;
|
|
1195
|
+
let z = center.z;
|
|
1196
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
1197
|
+
const a = angle + rng.range(-0.25, 0.25) * (attempt > 0 ? 1 : 0.3);
|
|
1198
|
+
const d = distance * rng.range(attempt > 0 ? 0.8 : 0.95, 1.1);
|
|
1199
|
+
x = center.x + Math.cos(a) * d;
|
|
1200
|
+
z = center.z + Math.sin(a) * d;
|
|
1201
|
+
const y = heightAt(x, z);
|
|
1202
|
+
if (options.mask && !options.mask(x, z, y)) continue;
|
|
1203
|
+
let min = y;
|
|
1204
|
+
let max = y;
|
|
1205
|
+
for (const [dx, dz] of [[footprint, 0], [-footprint, 0], [0, footprint], [0, -footprint]]) {
|
|
1206
|
+
const h = heightAt(x + dx, z + dz);
|
|
1207
|
+
min = Math.min(min, h);
|
|
1208
|
+
max = Math.max(max, h);
|
|
1209
|
+
}
|
|
1210
|
+
if (max - min <= Math.max(0.9, footprint * 0.35)) break;
|
|
1211
|
+
}
|
|
1212
|
+
return { x, z };
|
|
1213
|
+
};
|
|
1214
|
+
const well = createWell({ seed: rng.int(1, 1e9), palette });
|
|
1215
|
+
place(well, center.x, center.z, rng.range(0, Math.PI * 2));
|
|
1216
|
+
let lightBudget = options.lampLights ?? 3;
|
|
1217
|
+
for (let i = 0; i < houseCount; i++) {
|
|
1218
|
+
const angle = i / houseCount * Math.PI * 2 + rng.range(-0.15, 0.15);
|
|
1219
|
+
const house = createHouse({ seed: rng.int(1, 1e9), palette });
|
|
1220
|
+
const spot = findSpot(angle, radius * rng.range(0.7, 0.95), house.obstacleRadius);
|
|
1221
|
+
const facing = Math.atan2(center.x - spot.x, center.z - spot.z);
|
|
1222
|
+
place(house, spot.x, spot.z, facing);
|
|
1223
|
+
lamps.push(house);
|
|
1224
|
+
if (rng.next() < 0.7) {
|
|
1225
|
+
const crateAngle = angle + rng.range(-0.5, 0.5);
|
|
1226
|
+
const crateDistance = Math.hypot(spot.x - center.x, spot.z - center.z) - house.obstacleRadius - 0.7;
|
|
1227
|
+
place(
|
|
1228
|
+
createCrate({ seed: rng.int(1, 1e9), size: rng.range(0.7, 1), palette }),
|
|
1229
|
+
center.x + Math.cos(crateAngle) * crateDistance,
|
|
1230
|
+
center.z + Math.sin(crateAngle) * crateDistance,
|
|
1231
|
+
rng.range(0, Math.PI)
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
if (i % 2 === 0) {
|
|
1235
|
+
const lampDistance = Math.hypot(spot.x - center.x, spot.z - center.z) * 0.55;
|
|
1236
|
+
const lamp = createLamp({ seed: rng.int(1, 1e9), light: lightBudget > 0, palette });
|
|
1237
|
+
lightBudget--;
|
|
1238
|
+
place(lamp, center.x + Math.cos(angle) * lampDistance, center.z + Math.sin(angle) * lampDistance, 0);
|
|
1239
|
+
lamps.push(lamp);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
if (options.tower ?? true) {
|
|
1243
|
+
const angle = rng.range(0, Math.PI * 2);
|
|
1244
|
+
const tower = createTower({ seed: rng.int(1, 1e9), palette });
|
|
1245
|
+
const spot = findSpot(angle, radius * 1.05, tower.obstacleRadius);
|
|
1246
|
+
place(tower, spot.x, spot.z, rng.range(0, Math.PI * 2));
|
|
1247
|
+
}
|
|
1248
|
+
if (options.ruin ?? true) {
|
|
1249
|
+
const angle = rng.range(0, Math.PI * 2);
|
|
1250
|
+
const ruin = createRuin({ seed: rng.int(1, 1e9), palette });
|
|
1251
|
+
const spot = findSpot(angle, radius * 1.35, ruin.obstacleRadius);
|
|
1252
|
+
place(ruin, spot.x, spot.z, rng.range(0, Math.PI * 2));
|
|
1253
|
+
}
|
|
1254
|
+
return {
|
|
1255
|
+
group,
|
|
1256
|
+
props,
|
|
1257
|
+
obstacles: collectObstacles(props),
|
|
1258
|
+
lamps,
|
|
1259
|
+
keepOut: [{ center: { ...center }, radius: radius * 1.5 }],
|
|
1260
|
+
center
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
|
|
524
1264
|
// src/scatter/scatter.ts
|
|
525
1265
|
import {
|
|
526
1266
|
DynamicDrawUsage,
|
|
527
|
-
Group as
|
|
1267
|
+
Group as Group10,
|
|
528
1268
|
InstancedMesh,
|
|
529
1269
|
Matrix4,
|
|
530
|
-
Mesh as
|
|
1270
|
+
Mesh as Mesh13,
|
|
531
1271
|
Quaternion,
|
|
532
|
-
Vector3 as
|
|
1272
|
+
Vector3 as Vector33
|
|
533
1273
|
} from "three";
|
|
534
1274
|
function scatter(options) {
|
|
535
1275
|
const seed = options.seed ?? 1;
|
|
@@ -585,7 +1325,7 @@ function scatter(options) {
|
|
|
585
1325
|
const itemIndex = pickItem();
|
|
586
1326
|
const [minScale, maxScale2] = options.items[itemIndex].scale ?? [0.8, 1.25];
|
|
587
1327
|
placements.push({
|
|
588
|
-
position: new
|
|
1328
|
+
position: new Vector33(x, y, z),
|
|
589
1329
|
rotationY: rng.range(0, Math.PI * 2),
|
|
590
1330
|
scale: rng.range(minScale, maxScale2),
|
|
591
1331
|
itemIndex
|
|
@@ -595,14 +1335,14 @@ function scatter(options) {
|
|
|
595
1335
|
if (!bucket) occupied.set(key, bucket = []);
|
|
596
1336
|
bucket.push({ x, z });
|
|
597
1337
|
}
|
|
598
|
-
const group = new
|
|
1338
|
+
const group = new Group10();
|
|
599
1339
|
group.name = "scatter";
|
|
600
1340
|
const obstacles = [];
|
|
601
1341
|
const placementMatrix = new Matrix4();
|
|
602
1342
|
const composed = new Matrix4();
|
|
603
1343
|
const quaternion = new Quaternion();
|
|
604
|
-
const up = new
|
|
605
|
-
const scaleVector = new
|
|
1344
|
+
const up = new Vector33(0, 1, 0);
|
|
1345
|
+
const scaleVector = new Vector33();
|
|
606
1346
|
options.items.forEach((item, itemIndex) => {
|
|
607
1347
|
const variantCount = item.variants ?? 4;
|
|
608
1348
|
const variants = [];
|
|
@@ -618,7 +1358,7 @@ function scatter(options) {
|
|
|
618
1358
|
if (list.length === 0) return;
|
|
619
1359
|
variant.object.updateMatrixWorld(true);
|
|
620
1360
|
variant.object.traverse((child) => {
|
|
621
|
-
if (!(child instanceof
|
|
1361
|
+
if (!(child instanceof Mesh13)) return;
|
|
622
1362
|
const instanced = new InstancedMesh(child.geometry, child.material, list.length);
|
|
623
1363
|
instanced.instanceMatrix.setUsage(DynamicDrawUsage);
|
|
624
1364
|
list.forEach((placement, index) => {
|
|
@@ -647,16 +1387,28 @@ export {
|
|
|
647
1387
|
DEFAULT_PALETTE,
|
|
648
1388
|
PALETTES,
|
|
649
1389
|
Rng,
|
|
1390
|
+
aboveWater,
|
|
650
1391
|
applyFog,
|
|
1392
|
+
applyWind,
|
|
651
1393
|
collectObstacles,
|
|
1394
|
+
createBush,
|
|
652
1395
|
createCrate,
|
|
1396
|
+
createDayCycle,
|
|
653
1397
|
createFence,
|
|
1398
|
+
createGrassTuft,
|
|
1399
|
+
createHouse,
|
|
654
1400
|
createLamp,
|
|
655
1401
|
createLightingRig,
|
|
1402
|
+
createPath,
|
|
656
1403
|
createRock,
|
|
1404
|
+
createRuin,
|
|
657
1405
|
createSky,
|
|
658
1406
|
createTerrain,
|
|
1407
|
+
createTower,
|
|
659
1408
|
createTree,
|
|
1409
|
+
createVillage,
|
|
1410
|
+
createWater,
|
|
1411
|
+
createWell,
|
|
660
1412
|
fractalNoise2,
|
|
661
1413
|
hash2,
|
|
662
1414
|
scatter,
|