scena3d 0.1.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/LICENSE +21 -0
- package/README.md +95 -0
- package/dist/index.cjs +662 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +288 -0
- package/dist/index.d.ts +288 -0
- package/dist/index.js +665 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DEFAULT_PALETTE: () => DEFAULT_PALETTE,
|
|
24
|
+
PALETTES: () => PALETTES,
|
|
25
|
+
Rng: () => Rng,
|
|
26
|
+
applyFog: () => applyFog,
|
|
27
|
+
collectObstacles: () => collectObstacles,
|
|
28
|
+
createCrate: () => createCrate,
|
|
29
|
+
createFence: () => createFence,
|
|
30
|
+
createLamp: () => createLamp,
|
|
31
|
+
createLightingRig: () => createLightingRig,
|
|
32
|
+
createRock: () => createRock,
|
|
33
|
+
createSky: () => createSky,
|
|
34
|
+
createTerrain: () => createTerrain,
|
|
35
|
+
createTree: () => createTree,
|
|
36
|
+
fractalNoise2: () => fractalNoise2,
|
|
37
|
+
hash2: () => hash2,
|
|
38
|
+
scatter: () => scatter,
|
|
39
|
+
valueNoise2: () => valueNoise2
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(index_exports);
|
|
42
|
+
|
|
43
|
+
// src/core/random.ts
|
|
44
|
+
var Rng = class _Rng {
|
|
45
|
+
constructor(seed = 1) {
|
|
46
|
+
this.state = seed >>> 0 || 1;
|
|
47
|
+
}
|
|
48
|
+
/** Next float in [0, 1) (mulberry32). */
|
|
49
|
+
next() {
|
|
50
|
+
this.state = this.state + 1831565813 >>> 0;
|
|
51
|
+
let t = this.state;
|
|
52
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
53
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
54
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
55
|
+
}
|
|
56
|
+
/** Float in [min, max). */
|
|
57
|
+
range(min, max) {
|
|
58
|
+
return min + this.next() * (max - min);
|
|
59
|
+
}
|
|
60
|
+
/** Integer in [min, max] inclusive. */
|
|
61
|
+
int(min, max) {
|
|
62
|
+
return min + Math.floor(this.next() * (max - min + 1));
|
|
63
|
+
}
|
|
64
|
+
/** Random element of a non-empty array. */
|
|
65
|
+
pick(items) {
|
|
66
|
+
return items[Math.floor(this.next() * items.length)];
|
|
67
|
+
}
|
|
68
|
+
/** value ± spread (uniform). */
|
|
69
|
+
jitter(value, spread) {
|
|
70
|
+
return value + (this.next() * 2 - 1) * spread;
|
|
71
|
+
}
|
|
72
|
+
/** A new independent Rng derived from this one. */
|
|
73
|
+
fork() {
|
|
74
|
+
return new _Rng(Math.floor(this.next() * 4294967295) || 1);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
function hash2(ix, iz, seed) {
|
|
78
|
+
let h = ix * 374761393 + iz * 668265263 + seed * 2246822519 >>> 0;
|
|
79
|
+
h = Math.imul(h ^ h >>> 13, 1274126177) >>> 0;
|
|
80
|
+
return ((h ^ h >>> 16) >>> 0) / 4294967296;
|
|
81
|
+
}
|
|
82
|
+
var smooth = (t) => t * t * (3 - 2 * t);
|
|
83
|
+
function valueNoise2(x, z, seed) {
|
|
84
|
+
const ix = Math.floor(x);
|
|
85
|
+
const iz = Math.floor(z);
|
|
86
|
+
const fx = smooth(x - ix);
|
|
87
|
+
const fz = smooth(z - iz);
|
|
88
|
+
const a = hash2(ix, iz, seed);
|
|
89
|
+
const b = hash2(ix + 1, iz, seed);
|
|
90
|
+
const c = hash2(ix, iz + 1, seed);
|
|
91
|
+
const d = hash2(ix + 1, iz + 1, seed);
|
|
92
|
+
return a + (b - a) * fx + (c - a) * fz + (a - b - c + d) * fx * fz;
|
|
93
|
+
}
|
|
94
|
+
function fractalNoise2(x, z, seed, octaves = 4, lacunarity = 2, gain = 0.5) {
|
|
95
|
+
let amplitude = 1;
|
|
96
|
+
let frequency = 1;
|
|
97
|
+
let sum = 0;
|
|
98
|
+
let total = 0;
|
|
99
|
+
for (let i = 0; i < octaves; i++) {
|
|
100
|
+
sum += valueNoise2(x * frequency, z * frequency, seed + i * 101) * amplitude;
|
|
101
|
+
total += amplitude;
|
|
102
|
+
amplitude *= gain;
|
|
103
|
+
frequency *= lacunarity;
|
|
104
|
+
}
|
|
105
|
+
return sum / total;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/core/palette.ts
|
|
109
|
+
var PALETTES = {
|
|
110
|
+
meadow: {
|
|
111
|
+
foliage: [3120727, 3650154, 2789199, 4569208],
|
|
112
|
+
trunk: 7031347,
|
|
113
|
+
rock: [9080728, 7765126, 10133672],
|
|
114
|
+
wood: 9070146,
|
|
115
|
+
woodDark: 7031347,
|
|
116
|
+
metal: 4015185,
|
|
117
|
+
lampGlow: 16767113,
|
|
118
|
+
grassLow: 4169050,
|
|
119
|
+
grassHigh: 7319142,
|
|
120
|
+
cliff: 8223346,
|
|
121
|
+
peak: 15265007,
|
|
122
|
+
skyTop: 4026552,
|
|
123
|
+
skyBottom: 12573160,
|
|
124
|
+
fog: 12111837
|
|
125
|
+
},
|
|
126
|
+
autumn: {
|
|
127
|
+
foliage: [13202735, 14257722, 11754538, 14722373],
|
|
128
|
+
trunk: 6111280,
|
|
129
|
+
rock: [9274744, 7827299, 10261642],
|
|
130
|
+
wood: 8215098,
|
|
131
|
+
woodDark: 6111280,
|
|
132
|
+
metal: 4603706,
|
|
133
|
+
lampGlow: 16762225,
|
|
134
|
+
grassLow: 10324543,
|
|
135
|
+
grassHigh: 11901770,
|
|
136
|
+
cliff: 8549482,
|
|
137
|
+
peak: 14933716,
|
|
138
|
+
skyTop: 9333928,
|
|
139
|
+
skyBottom: 15255976,
|
|
140
|
+
fog: 14270888
|
|
141
|
+
},
|
|
142
|
+
dusk: {
|
|
143
|
+
foliage: [2055750, 2385999, 1724992, 2978904],
|
|
144
|
+
trunk: 4272455,
|
|
145
|
+
rock: [5658226, 4737121, 6579332],
|
|
146
|
+
wood: 6113891,
|
|
147
|
+
woodDark: 4272455,
|
|
148
|
+
metal: 2829117,
|
|
149
|
+
lampGlow: 16757596,
|
|
150
|
+
grassLow: 2976594,
|
|
151
|
+
grassHigh: 4029022,
|
|
152
|
+
cliff: 5394795,
|
|
153
|
+
peak: 12105945,
|
|
154
|
+
skyTop: 1909061,
|
|
155
|
+
skyBottom: 13199946,
|
|
156
|
+
fog: 6969978
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
var DEFAULT_PALETTE = PALETTES.meadow;
|
|
160
|
+
|
|
161
|
+
// src/core/types.ts
|
|
162
|
+
var import_three = require("three");
|
|
163
|
+
function collectObstacles(props) {
|
|
164
|
+
const obstacles = [];
|
|
165
|
+
for (const prop of props) {
|
|
166
|
+
if (prop.obstacleRadius <= 0) continue;
|
|
167
|
+
prop.object.updateWorldMatrix(true, false);
|
|
168
|
+
obstacles.push({
|
|
169
|
+
center: prop.object.getWorldPosition(new import_three.Vector3()),
|
|
170
|
+
radius: prop.obstacleRadius * maxScale(prop.object)
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return obstacles;
|
|
174
|
+
}
|
|
175
|
+
function maxScale(object) {
|
|
176
|
+
return Math.max(object.scale.x, object.scale.z);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/props/tree.ts
|
|
180
|
+
var import_three2 = require("three");
|
|
181
|
+
function createTree(options = {}) {
|
|
182
|
+
const rng = new Rng(options.seed ?? 1);
|
|
183
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
184
|
+
const style = options.style ?? (rng.next() < 0.6 ? "pine" : "oak");
|
|
185
|
+
const height = options.height ?? rng.range(3.2, 5.2);
|
|
186
|
+
const group = new import_three2.Group();
|
|
187
|
+
group.name = `tree-${style}`;
|
|
188
|
+
const trunkMaterial = new import_three2.MeshStandardMaterial({ color: palette.trunk, flatShading: true });
|
|
189
|
+
const foliageMaterial = new import_three2.MeshStandardMaterial({
|
|
190
|
+
color: rng.pick(palette.foliage),
|
|
191
|
+
flatShading: true
|
|
192
|
+
});
|
|
193
|
+
if (style === "pine") {
|
|
194
|
+
const trunkHeight = height * 0.25;
|
|
195
|
+
const trunk = new import_three2.Mesh(new import_three2.CylinderGeometry(0.09, 0.14, trunkHeight, 6), trunkMaterial);
|
|
196
|
+
trunk.position.y = trunkHeight / 2;
|
|
197
|
+
group.add(trunk);
|
|
198
|
+
const tiers = rng.int(3, 4);
|
|
199
|
+
let y = trunkHeight;
|
|
200
|
+
let radius = height * rng.range(0.24, 0.3);
|
|
201
|
+
const tierHeight = (height - trunkHeight) / tiers + 0.15;
|
|
202
|
+
for (let i = 0; i < tiers; i++) {
|
|
203
|
+
const cone = new import_three2.Mesh(new import_three2.ConeGeometry(radius, tierHeight * 1.35, 7), foliageMaterial);
|
|
204
|
+
cone.position.y = y + tierHeight * 0.55;
|
|
205
|
+
cone.rotation.y = rng.range(0, Math.PI);
|
|
206
|
+
group.add(cone);
|
|
207
|
+
y += tierHeight * 0.8;
|
|
208
|
+
radius *= 0.72;
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
const trunkHeight = height * 0.45;
|
|
212
|
+
const trunk = new import_three2.Mesh(new import_three2.CylinderGeometry(0.12, 0.2, trunkHeight, 6), trunkMaterial);
|
|
213
|
+
trunk.position.y = trunkHeight / 2;
|
|
214
|
+
trunk.rotation.z = rng.range(-0.08, 0.08);
|
|
215
|
+
group.add(trunk);
|
|
216
|
+
const blobs = rng.int(2, 4);
|
|
217
|
+
for (let i = 0; i < blobs; i++) {
|
|
218
|
+
const radius = height * rng.range(0.18, 0.28);
|
|
219
|
+
const blob = new import_three2.Mesh(new import_three2.IcosahedronGeometry(radius, 0), foliageMaterial);
|
|
220
|
+
blob.position.set(
|
|
221
|
+
rng.jitter(0, height * 0.16),
|
|
222
|
+
trunkHeight + radius * rng.range(0.5, 0.9) + i * radius * 0.35,
|
|
223
|
+
rng.jitter(0, height * 0.16)
|
|
224
|
+
);
|
|
225
|
+
blob.rotation.set(rng.range(0, Math.PI), rng.range(0, Math.PI), 0);
|
|
226
|
+
group.add(blob);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return { object: group, obstacleRadius: style === "pine" ? 0.5 : 0.6 };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/props/rock.ts
|
|
233
|
+
var import_three3 = require("three");
|
|
234
|
+
function createRock(options = {}) {
|
|
235
|
+
const rng = new Rng(options.seed ?? 1);
|
|
236
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
237
|
+
const size = options.size ?? rng.range(0.4, 1.1);
|
|
238
|
+
const geometry = new import_three3.IcosahedronGeometry(size, 0);
|
|
239
|
+
const positions = geometry.getAttribute("position");
|
|
240
|
+
const seen = /* @__PURE__ */ new Map();
|
|
241
|
+
for (let i = 0; i < positions.count; i++) {
|
|
242
|
+
const key = `${positions.getX(i).toFixed(4)},${positions.getY(i).toFixed(4)},${positions.getZ(i).toFixed(4)}`;
|
|
243
|
+
let offset = seen.get(key);
|
|
244
|
+
if (!offset) {
|
|
245
|
+
offset = [rng.jitter(0, size * 0.22), rng.jitter(0, size * 0.16), rng.jitter(0, size * 0.22)];
|
|
246
|
+
seen.set(key, offset);
|
|
247
|
+
}
|
|
248
|
+
positions.setXYZ(
|
|
249
|
+
i,
|
|
250
|
+
positions.getX(i) + offset[0],
|
|
251
|
+
Math.max(positions.getY(i) + offset[1], -size * 0.15),
|
|
252
|
+
// flatten the bottom
|
|
253
|
+
positions.getZ(i) + offset[2]
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
geometry.computeVertexNormals();
|
|
257
|
+
const rock = new import_three3.Mesh(
|
|
258
|
+
geometry,
|
|
259
|
+
new import_three3.MeshStandardMaterial({ color: rng.pick(palette.rock), flatShading: true })
|
|
260
|
+
);
|
|
261
|
+
rock.position.y = size * 0.15;
|
|
262
|
+
rock.scale.y = rng.range(0.6, 0.9);
|
|
263
|
+
const group = new import_three3.Group();
|
|
264
|
+
group.name = "rock";
|
|
265
|
+
group.add(rock);
|
|
266
|
+
return { object: group, obstacleRadius: size * 1.05 };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/props/crate.ts
|
|
270
|
+
var import_three4 = require("three");
|
|
271
|
+
function createCrate(options = {}) {
|
|
272
|
+
const rng = new Rng(options.seed ?? 1);
|
|
273
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
274
|
+
const size = options.size ?? 1;
|
|
275
|
+
const wear = options.weathering ?? 0.3;
|
|
276
|
+
const group = new import_three4.Group();
|
|
277
|
+
group.name = "crate";
|
|
278
|
+
const panelMaterial = new import_three4.MeshStandardMaterial({ color: palette.wood, flatShading: true });
|
|
279
|
+
panelMaterial.color.offsetHSL(0, 0, -rng.range(0, wear * 0.18));
|
|
280
|
+
const frameMaterial = new import_three4.MeshStandardMaterial({ color: palette.woodDark, flatShading: true });
|
|
281
|
+
const body = new import_three4.Mesh(new import_three4.BoxGeometry(size * 0.92, size * 0.92, size * 0.92), panelMaterial);
|
|
282
|
+
body.position.y = size / 2;
|
|
283
|
+
group.add(body);
|
|
284
|
+
const beam = size * 0.12;
|
|
285
|
+
const long = size * 1;
|
|
286
|
+
for (const y of [beam / 2, size - beam / 2]) {
|
|
287
|
+
for (const [rx, rz, w, d] of [
|
|
288
|
+
[0, size / 2 - beam / 2, long, beam],
|
|
289
|
+
[0, -(size / 2 - beam / 2), long, beam],
|
|
290
|
+
[size / 2 - beam / 2, 0, beam, long],
|
|
291
|
+
[-(size / 2 - beam / 2), 0, beam, long]
|
|
292
|
+
]) {
|
|
293
|
+
const rail = new import_three4.Mesh(new import_three4.BoxGeometry(w, beam, d), frameMaterial);
|
|
294
|
+
rail.position.set(rx, y, rz);
|
|
295
|
+
group.add(rail);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
for (const x of [-1, 1]) {
|
|
299
|
+
for (const z of [-1, 1]) {
|
|
300
|
+
const post = new import_three4.Mesh(new import_three4.BoxGeometry(beam, size, beam), frameMaterial);
|
|
301
|
+
post.position.set(x * (size / 2 - beam / 2), size / 2, z * (size / 2 - beam / 2));
|
|
302
|
+
group.add(post);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
group.rotation.y = rng.range(0, Math.PI / 2);
|
|
306
|
+
return { object: group, obstacleRadius: size * 0.75 };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/props/fence.ts
|
|
310
|
+
var import_three5 = require("three");
|
|
311
|
+
function createFence(options = {}) {
|
|
312
|
+
const rng = new Rng(options.seed ?? 1);
|
|
313
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
314
|
+
const length = options.length ?? 6;
|
|
315
|
+
const spacing = options.postSpacing ?? 1.5;
|
|
316
|
+
const height = options.height ?? 1.1;
|
|
317
|
+
const group = new import_three5.Group();
|
|
318
|
+
group.name = "fence";
|
|
319
|
+
const postMaterial = new import_three5.MeshStandardMaterial({ color: palette.woodDark, flatShading: true });
|
|
320
|
+
const railMaterial = new import_three5.MeshStandardMaterial({ color: palette.wood, flatShading: true });
|
|
321
|
+
const posts = Math.max(2, Math.round(length / spacing) + 1);
|
|
322
|
+
const step = length / (posts - 1);
|
|
323
|
+
for (let i = 0; i < posts; i++) {
|
|
324
|
+
const post = new import_three5.Mesh(new import_three5.CylinderGeometry(0.06, 0.075, height, 5), postMaterial);
|
|
325
|
+
post.position.set(-length / 2 + i * step, height / 2, rng.jitter(0, 0.03));
|
|
326
|
+
post.rotation.z = rng.range(-0.04, 0.04);
|
|
327
|
+
group.add(post);
|
|
328
|
+
}
|
|
329
|
+
for (const railY of [height * 0.55, height * 0.85]) {
|
|
330
|
+
const rail = new import_three5.Mesh(new import_three5.BoxGeometry(length, 0.07, 0.05), railMaterial);
|
|
331
|
+
rail.position.y = rng.jitter(railY, 0.02);
|
|
332
|
+
rail.rotation.x = rng.range(-0.02, 0.02);
|
|
333
|
+
group.add(rail);
|
|
334
|
+
}
|
|
335
|
+
return { object: group, obstacleRadius: length / 2 };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/props/lamp.ts
|
|
339
|
+
var import_three6 = require("three");
|
|
340
|
+
function createLamp(options = {}) {
|
|
341
|
+
const rng = new Rng(options.seed ?? 1);
|
|
342
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
343
|
+
const height = options.height ?? rng.range(2.6, 3.1);
|
|
344
|
+
const group = new import_three6.Group();
|
|
345
|
+
group.name = "lamp";
|
|
346
|
+
const metal = new import_three6.MeshStandardMaterial({ color: palette.metal, flatShading: true });
|
|
347
|
+
const post = new import_three6.Mesh(new import_three6.CylinderGeometry(0.05, 0.08, height, 6), metal);
|
|
348
|
+
post.position.y = height / 2;
|
|
349
|
+
group.add(post);
|
|
350
|
+
const head = new import_three6.Mesh(new import_three6.BoxGeometry(0.34, 0.26, 0.34), metal);
|
|
351
|
+
head.position.y = height + 0.1;
|
|
352
|
+
group.add(head);
|
|
353
|
+
const bulb = new import_three6.Mesh(
|
|
354
|
+
new import_three6.SphereGeometry(0.11, 8, 6),
|
|
355
|
+
new import_three6.MeshStandardMaterial({
|
|
356
|
+
color: palette.lampGlow,
|
|
357
|
+
emissive: palette.lampGlow,
|
|
358
|
+
emissiveIntensity: 1.6
|
|
359
|
+
})
|
|
360
|
+
);
|
|
361
|
+
bulb.position.y = height - 0.03;
|
|
362
|
+
group.add(bulb);
|
|
363
|
+
if (options.light) {
|
|
364
|
+
const light = new import_three6.PointLight(palette.lampGlow, options.lightIntensity ?? 6, 12, 1.8);
|
|
365
|
+
light.position.y = height - 0.05;
|
|
366
|
+
group.add(light);
|
|
367
|
+
}
|
|
368
|
+
return { object: group, obstacleRadius: 0.25 };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/environment/terrain.ts
|
|
372
|
+
var import_three7 = require("three");
|
|
373
|
+
function createTerrain(options = {}) {
|
|
374
|
+
const seed = options.seed ?? 1;
|
|
375
|
+
const size = options.size ?? 80;
|
|
376
|
+
const resolution = options.resolution ?? 96;
|
|
377
|
+
const amplitude = options.amplitude ?? 6;
|
|
378
|
+
const noiseScale = options.noiseScale ?? 28;
|
|
379
|
+
const octaves = options.octaves ?? 4;
|
|
380
|
+
const flatness = options.valleyFlatness ?? 0.55;
|
|
381
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
382
|
+
const heightAt = (x, z) => {
|
|
383
|
+
const n = fractalNoise2(x / noiseScale, z / noiseScale, seed, octaves);
|
|
384
|
+
const shaped = Math.pow(n, 1 + flatness * 2);
|
|
385
|
+
return shaped * amplitude;
|
|
386
|
+
};
|
|
387
|
+
const geometry = new import_three7.PlaneGeometry(size, size, resolution - 1, resolution - 1);
|
|
388
|
+
geometry.rotateX(-Math.PI / 2);
|
|
389
|
+
const positions = geometry.getAttribute("position");
|
|
390
|
+
for (let i = 0; i < positions.count; i++) {
|
|
391
|
+
positions.setY(i, heightAt(positions.getX(i), positions.getZ(i)));
|
|
392
|
+
}
|
|
393
|
+
geometry.computeVertexNormals();
|
|
394
|
+
const normals = geometry.getAttribute("normal");
|
|
395
|
+
const colors = new Float32Array(positions.count * 3);
|
|
396
|
+
const grassLow = new import_three7.Color(palette.grassLow);
|
|
397
|
+
const grassHigh = new import_three7.Color(palette.grassHigh);
|
|
398
|
+
const cliff = new import_three7.Color(palette.cliff);
|
|
399
|
+
const peak = new import_three7.Color(palette.peak);
|
|
400
|
+
const scratch = new import_three7.Color();
|
|
401
|
+
for (let i = 0; i < positions.count; i++) {
|
|
402
|
+
const h = positions.getY(i) / amplitude;
|
|
403
|
+
const slope = 1 - normals.getY(i);
|
|
404
|
+
scratch.copy(grassLow).lerp(grassHigh, Math.min(1, h * 1.6));
|
|
405
|
+
if (h > 0.75) scratch.lerp(peak, (h - 0.75) * 4);
|
|
406
|
+
if (slope > 0.15) scratch.lerp(cliff, Math.min(1, (slope - 0.15) * 4));
|
|
407
|
+
colors[i * 3] = scratch.r;
|
|
408
|
+
colors[i * 3 + 1] = scratch.g;
|
|
409
|
+
colors[i * 3 + 2] = scratch.b;
|
|
410
|
+
}
|
|
411
|
+
geometry.setAttribute("color", new import_three7.BufferAttribute(colors, 3));
|
|
412
|
+
const mesh = new import_three7.Mesh(
|
|
413
|
+
geometry,
|
|
414
|
+
new import_three7.MeshStandardMaterial({ vertexColors: true, flatShading: true })
|
|
415
|
+
);
|
|
416
|
+
mesh.name = "terrain";
|
|
417
|
+
return { mesh, heightAt, size, seed };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// src/environment/sky.ts
|
|
421
|
+
var import_three8 = require("three");
|
|
422
|
+
function createSky(options = {}) {
|
|
423
|
+
const palette = options.palette ?? DEFAULT_PALETTE;
|
|
424
|
+
const material = new import_three8.ShaderMaterial({
|
|
425
|
+
side: import_three8.BackSide,
|
|
426
|
+
depthWrite: false,
|
|
427
|
+
uniforms: {
|
|
428
|
+
topColor: { value: new import_three8.Color(options.topColor ?? palette.skyTop) },
|
|
429
|
+
bottomColor: { value: new import_three8.Color(options.bottomColor ?? palette.skyBottom) }
|
|
430
|
+
},
|
|
431
|
+
vertexShader: (
|
|
432
|
+
/* glsl */
|
|
433
|
+
`
|
|
434
|
+
varying vec3 vWorld;
|
|
435
|
+
void main() {
|
|
436
|
+
vWorld = (modelMatrix * vec4(position, 1.0)).xyz;
|
|
437
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
438
|
+
}`
|
|
439
|
+
),
|
|
440
|
+
fragmentShader: (
|
|
441
|
+
/* glsl */
|
|
442
|
+
`
|
|
443
|
+
uniform vec3 topColor;
|
|
444
|
+
uniform vec3 bottomColor;
|
|
445
|
+
varying vec3 vWorld;
|
|
446
|
+
void main() {
|
|
447
|
+
float t = clamp(normalize(vWorld).y * 0.5 + 0.5, 0.0, 1.0);
|
|
448
|
+
gl_FragColor = vec4(mix(bottomColor, topColor, pow(t, 0.8)), 1.0);
|
|
449
|
+
}`
|
|
450
|
+
)
|
|
451
|
+
});
|
|
452
|
+
const mesh = new import_three8.Mesh(new import_three8.SphereGeometry(options.radius ?? 400, 16, 12), material);
|
|
453
|
+
mesh.name = "sky";
|
|
454
|
+
return {
|
|
455
|
+
mesh,
|
|
456
|
+
setColors(top, bottom) {
|
|
457
|
+
material.uniforms.topColor.value.setHex(top);
|
|
458
|
+
material.uniforms.bottomColor.value.setHex(bottom);
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/environment/lighting.ts
|
|
464
|
+
var import_three9 = require("three");
|
|
465
|
+
var PRESETS = {
|
|
466
|
+
day: {
|
|
467
|
+
sun: 16774368,
|
|
468
|
+
sunIntensity: 1.6,
|
|
469
|
+
sunPos: [30, 45, 20],
|
|
470
|
+
ambient: 14674687,
|
|
471
|
+
ambientIntensity: 0.35,
|
|
472
|
+
skyTint: 12376319,
|
|
473
|
+
groundTint: 6978918
|
|
474
|
+
},
|
|
475
|
+
"golden-hour": {
|
|
476
|
+
sun: 16758881,
|
|
477
|
+
sunIntensity: 1.7,
|
|
478
|
+
sunPos: [40, 14, -12],
|
|
479
|
+
ambient: 16768192,
|
|
480
|
+
ambientIntensity: 0.3,
|
|
481
|
+
skyTint: 16764830,
|
|
482
|
+
groundTint: 8022616
|
|
483
|
+
},
|
|
484
|
+
overcast: {
|
|
485
|
+
sun: 14212840,
|
|
486
|
+
sunIntensity: 0.7,
|
|
487
|
+
sunPos: [12, 40, 8],
|
|
488
|
+
ambient: 13620960,
|
|
489
|
+
ambientIntensity: 0.65,
|
|
490
|
+
skyTint: 13160668,
|
|
491
|
+
groundTint: 7371384
|
|
492
|
+
},
|
|
493
|
+
night: {
|
|
494
|
+
sun: 9348824,
|
|
495
|
+
sunIntensity: 0.35,
|
|
496
|
+
sunPos: [-20, 30, -25],
|
|
497
|
+
ambient: 3818600,
|
|
498
|
+
ambientIntensity: 0.35,
|
|
499
|
+
skyTint: 2897248,
|
|
500
|
+
groundTint: 1975344
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
function createLightingRig(preset = "day") {
|
|
504
|
+
const config = PRESETS[preset];
|
|
505
|
+
const group = new import_three9.Group();
|
|
506
|
+
group.name = `lighting-${preset}`;
|
|
507
|
+
const sun = new import_three9.DirectionalLight(config.sun, config.sunIntensity);
|
|
508
|
+
sun.position.set(...config.sunPos);
|
|
509
|
+
const ambient = new import_three9.AmbientLight(config.ambient, config.ambientIntensity);
|
|
510
|
+
const hemisphere = new import_three9.HemisphereLight(config.skyTint, config.groundTint, 0.5);
|
|
511
|
+
group.add(sun, ambient, hemisphere);
|
|
512
|
+
return { group, sun, ambient, hemisphere };
|
|
513
|
+
}
|
|
514
|
+
var FOG = {
|
|
515
|
+
haze: { near: 45, far: 160 },
|
|
516
|
+
thick: { near: 12, far: 70 },
|
|
517
|
+
eerie: { near: 6, far: 42 }
|
|
518
|
+
};
|
|
519
|
+
function applyFog(scene, preset, palette = DEFAULT_PALETTE) {
|
|
520
|
+
if (preset === "clear") {
|
|
521
|
+
scene.fog = null;
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
const { near, far } = FOG[preset];
|
|
525
|
+
scene.fog = new import_three9.Fog(palette.fog, near, far);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// src/scatter/scatter.ts
|
|
529
|
+
var import_three10 = require("three");
|
|
530
|
+
function scatter(options) {
|
|
531
|
+
const seed = options.seed ?? 1;
|
|
532
|
+
const rng = new Rng(seed);
|
|
533
|
+
const { area } = options;
|
|
534
|
+
const width = area.max.x - area.min.x;
|
|
535
|
+
const depth = area.max.z - area.min.z;
|
|
536
|
+
const surface = options.surface ?? 0;
|
|
537
|
+
const heightAt = typeof surface === "number" ? () => surface : (x, z) => surface(x, z);
|
|
538
|
+
const minSpacing = options.minSpacing ?? 1.2;
|
|
539
|
+
const clumpScale = options.clumpScale ?? 18;
|
|
540
|
+
const attempts = options.count ?? Math.round(width * depth * (options.density ?? 0.03));
|
|
541
|
+
const keepOut = (options.keepOut ?? []).map((k) => ({
|
|
542
|
+
x: k.center.x,
|
|
543
|
+
z: "z" in k.center ? k.center.z : k.center.z,
|
|
544
|
+
radius: k.radius
|
|
545
|
+
}));
|
|
546
|
+
const weights = options.items.map((i) => i.weight ?? 1);
|
|
547
|
+
const totalWeight = weights.reduce((a, b) => a + b, 0);
|
|
548
|
+
const pickItem = () => {
|
|
549
|
+
let r = rng.next() * totalWeight;
|
|
550
|
+
for (let i = 0; i < weights.length; i++) {
|
|
551
|
+
r -= weights[i];
|
|
552
|
+
if (r <= 0) return i;
|
|
553
|
+
}
|
|
554
|
+
return weights.length - 1;
|
|
555
|
+
};
|
|
556
|
+
const cell = Math.max(minSpacing, 1e-3);
|
|
557
|
+
const occupied = /* @__PURE__ */ new Map();
|
|
558
|
+
const tooClose = (x, z) => {
|
|
559
|
+
const cx = Math.floor(x / cell);
|
|
560
|
+
const cz = Math.floor(z / cell);
|
|
561
|
+
for (let dx = -1; dx <= 1; dx++) {
|
|
562
|
+
for (let dz = -1; dz <= 1; dz++) {
|
|
563
|
+
const bucket = occupied.get(`${cx + dx},${cz + dz}`);
|
|
564
|
+
if (!bucket) continue;
|
|
565
|
+
for (const p of bucket) {
|
|
566
|
+
if ((p.x - x) ** 2 + (p.z - z) ** 2 < minSpacing * minSpacing) return true;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return false;
|
|
571
|
+
};
|
|
572
|
+
const placements = [];
|
|
573
|
+
for (let i = 0; i < attempts; i++) {
|
|
574
|
+
const x = rng.range(area.min.x, area.max.x);
|
|
575
|
+
const z = rng.range(area.min.z, area.max.z);
|
|
576
|
+
if (valueNoise2(x / clumpScale, z / clumpScale, seed + 77) < rng.range(0.15, 0.55)) continue;
|
|
577
|
+
if (keepOut.some((k) => (k.x - x) ** 2 + (k.z - z) ** 2 < k.radius * k.radius)) continue;
|
|
578
|
+
if (tooClose(x, z)) continue;
|
|
579
|
+
const y = heightAt(x, z);
|
|
580
|
+
if (options.mask && !options.mask(x, z, y)) continue;
|
|
581
|
+
const itemIndex = pickItem();
|
|
582
|
+
const [minScale, maxScale2] = options.items[itemIndex].scale ?? [0.8, 1.25];
|
|
583
|
+
placements.push({
|
|
584
|
+
position: new import_three10.Vector3(x, y, z),
|
|
585
|
+
rotationY: rng.range(0, Math.PI * 2),
|
|
586
|
+
scale: rng.range(minScale, maxScale2),
|
|
587
|
+
itemIndex
|
|
588
|
+
});
|
|
589
|
+
const key = `${Math.floor(x / cell)},${Math.floor(z / cell)}`;
|
|
590
|
+
let bucket = occupied.get(key);
|
|
591
|
+
if (!bucket) occupied.set(key, bucket = []);
|
|
592
|
+
bucket.push({ x, z });
|
|
593
|
+
}
|
|
594
|
+
const group = new import_three10.Group();
|
|
595
|
+
group.name = "scatter";
|
|
596
|
+
const obstacles = [];
|
|
597
|
+
const placementMatrix = new import_three10.Matrix4();
|
|
598
|
+
const composed = new import_three10.Matrix4();
|
|
599
|
+
const quaternion = new import_three10.Quaternion();
|
|
600
|
+
const up = new import_three10.Vector3(0, 1, 0);
|
|
601
|
+
const scaleVector = new import_three10.Vector3();
|
|
602
|
+
options.items.forEach((item, itemIndex) => {
|
|
603
|
+
const variantCount = item.variants ?? 4;
|
|
604
|
+
const variants = [];
|
|
605
|
+
for (let v = 0; v < variantCount; v++) variants.push(item.create(rng.fork()));
|
|
606
|
+
const byVariant = variants.map(() => []);
|
|
607
|
+
for (const placement of placements) {
|
|
608
|
+
if (placement.itemIndex !== itemIndex) continue;
|
|
609
|
+
const v = Math.abs(Math.floor(placement.position.x * 31 + placement.position.z * 17)) % variantCount;
|
|
610
|
+
byVariant[v].push(placement);
|
|
611
|
+
}
|
|
612
|
+
variants.forEach((variant, v) => {
|
|
613
|
+
const list = byVariant[v];
|
|
614
|
+
if (list.length === 0) return;
|
|
615
|
+
variant.object.updateMatrixWorld(true);
|
|
616
|
+
variant.object.traverse((child) => {
|
|
617
|
+
if (!(child instanceof import_three10.Mesh)) return;
|
|
618
|
+
const instanced = new import_three10.InstancedMesh(child.geometry, child.material, list.length);
|
|
619
|
+
instanced.instanceMatrix.setUsage(import_three10.DynamicDrawUsage);
|
|
620
|
+
list.forEach((placement, index) => {
|
|
621
|
+
quaternion.setFromAxisAngle(up, placement.rotationY);
|
|
622
|
+
scaleVector.setScalar(placement.scale);
|
|
623
|
+
placementMatrix.compose(placement.position, quaternion, scaleVector);
|
|
624
|
+
composed.multiplyMatrices(placementMatrix, child.matrixWorld);
|
|
625
|
+
instanced.setMatrixAt(index, composed);
|
|
626
|
+
});
|
|
627
|
+
instanced.instanceMatrix.needsUpdate = true;
|
|
628
|
+
group.add(instanced);
|
|
629
|
+
});
|
|
630
|
+
if (variant.obstacleRadius > 0) {
|
|
631
|
+
for (const placement of list) {
|
|
632
|
+
obstacles.push({
|
|
633
|
+
center: placement.position.clone(),
|
|
634
|
+
radius: variant.obstacleRadius * placement.scale
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
});
|
|
640
|
+
return { group, placements, obstacles, count: placements.length };
|
|
641
|
+
}
|
|
642
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
643
|
+
0 && (module.exports = {
|
|
644
|
+
DEFAULT_PALETTE,
|
|
645
|
+
PALETTES,
|
|
646
|
+
Rng,
|
|
647
|
+
applyFog,
|
|
648
|
+
collectObstacles,
|
|
649
|
+
createCrate,
|
|
650
|
+
createFence,
|
|
651
|
+
createLamp,
|
|
652
|
+
createLightingRig,
|
|
653
|
+
createRock,
|
|
654
|
+
createSky,
|
|
655
|
+
createTerrain,
|
|
656
|
+
createTree,
|
|
657
|
+
fractalNoise2,
|
|
658
|
+
hash2,
|
|
659
|
+
scatter,
|
|
660
|
+
valueNoise2
|
|
661
|
+
});
|
|
662
|
+
//# sourceMappingURL=index.cjs.map
|