genshin-engine 4.0.0 → 4.1.1

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.
@@ -0,0 +1,520 @@
1
+ import { Color, MathUtils } from "three";
2
+ //#region src/random/createSeededRandom.ts
3
+ const createSeededRandom = (seed) => {
4
+ let state = seed;
5
+ return () => {
6
+ state = state + 1831565813 >>> 0;
7
+ let mixed = Math.imul(state ^ state >>> 15, 1 | state);
8
+ mixed ^= mixed + Math.imul(mixed ^ mixed >>> 7, 61 | mixed);
9
+ return ((mixed ^ mixed >>> 14) >>> 0) / 2 ** 32;
10
+ };
11
+ };
12
+ //#endregion
13
+ //#region src/models/terrain/GroundLayer.ts
14
+ let GroundLayer = /* @__PURE__ */ function(GroundLayer) {
15
+ GroundLayer["Earth"] = "Earth";
16
+ GroundLayer["Grass"] = "Grass";
17
+ GroundLayer["Path"] = "Path";
18
+ GroundLayer["Rock"] = "Rock";
19
+ GroundLayer["Sand"] = "Sand";
20
+ GroundLayer["Snow"] = "Snow";
21
+ return GroundLayer;
22
+ }({});
23
+ const GroundLayers = Object.values(GroundLayer);
24
+ //#endregion
25
+ //#region src/models/terrain/TerrainFeatureKind.ts
26
+ let TerrainFeatureKind = /* @__PURE__ */ function(TerrainFeatureKind) {
27
+ TerrainFeatureKind["Cliff"] = "cliff";
28
+ TerrainFeatureKind["Plateau"] = "plateau";
29
+ TerrainFeatureKind["Ridge"] = "ridge";
30
+ return TerrainFeatureKind;
31
+ }({});
32
+ //#endregion
33
+ //#region src/noise/createSimplexNoise.ts
34
+ const SKEW = (Math.sqrt(3) - 1) / 2;
35
+ const UNSKEW = (3 - Math.sqrt(3)) / 6;
36
+ const GRADIENTS_X = [
37
+ 1,
38
+ -1,
39
+ 1,
40
+ -1,
41
+ 1,
42
+ -1,
43
+ 0,
44
+ 0
45
+ ];
46
+ const GRADIENTS_Y = [
47
+ 1,
48
+ 1,
49
+ -1,
50
+ -1,
51
+ 0,
52
+ 0,
53
+ 1,
54
+ -1
55
+ ];
56
+ const NOISE_SCALE = 70;
57
+ const getCornerContribution = (gradientIndex, offsetX, offsetY) => {
58
+ const falloff = .5 - offsetX * offsetX - offsetY * offsetY;
59
+ if (falloff < 0) return 0;
60
+ const gradient = gradientIndex & 7;
61
+ return falloff ** 4 * ((GRADIENTS_X[gradient] ?? 0) * offsetX + (GRADIENTS_Y[gradient] ?? 0) * offsetY);
62
+ };
63
+ const createSimplexNoise = (seed) => {
64
+ const random = createSeededRandom(seed);
65
+ const table = Uint8Array.from({ length: 256 }, (_value, index) => index);
66
+ for (let index = table.length - 1; index > 0; index--) {
67
+ const swapIndex = Math.floor(random() * (index + 1));
68
+ [table[index], table[swapIndex]] = [table[swapIndex] ?? 0, table[index] ?? 0];
69
+ }
70
+ const permutation = Uint8Array.from({ length: 512 }, (_value, index) => table[index & 255] ?? 0);
71
+ return (x, y) => {
72
+ const skew = (x + y) * SKEW;
73
+ const cellX = Math.floor(x + skew);
74
+ const cellY = Math.floor(y + skew);
75
+ const unskew = (cellX + cellY) * UNSKEW;
76
+ const offsetX = x - cellX + unskew;
77
+ const offsetY = y - cellY + unskew;
78
+ const middleX = offsetX > offsetY ? 1 : 0;
79
+ const middleY = 1 - middleX;
80
+ const tableX = cellX & 255;
81
+ const tableY = cellY & 255;
82
+ return NOISE_SCALE * (getCornerContribution(permutation[tableX + (permutation[tableY] ?? 0)] ?? 0, offsetX, offsetY) + getCornerContribution(permutation[tableX + middleX + (permutation[tableY + middleY] ?? 0)] ?? 0, offsetX - middleX + UNSKEW, offsetY - middleY + UNSKEW) + getCornerContribution(permutation[tableX + 1 + (permutation[tableY + 1] ?? 0)] ?? 0, offsetX - 1 + 2 * UNSKEW, offsetY - 1 + 2 * UNSKEW));
83
+ };
84
+ };
85
+ //#endregion
86
+ //#region src/terrain/constants.ts
87
+ const TILE_INDEX_OFFSET = 2 ** 19;
88
+ const TILE_INDEX_SPAN = 2 ** 20;
89
+ const TERRAIN_LAYER = 1;
90
+ const TERRAIN_RING_INITIAL_TILE_COUNT = 64;
91
+ const TERRAIN_CELL_SIZE = 64;
92
+ const GAUSSIAN_REACH_WIDTHS = 3;
93
+ const TERRAIN_HEIGHT_PADDING = 2;
94
+ //#endregion
95
+ //#region src/terrain/getTerrainTileLevel.ts
96
+ const getTerrainTileLevel = (key) => Math.floor(key / TILE_INDEX_SPAN / TILE_INDEX_SPAN);
97
+ //#endregion
98
+ //#region src/terrain/measureSegment.ts
99
+ const measurement = /* @__PURE__ */ new Float64Array(2);
100
+ const measureSegment = ({ endX, endZ, startX, startZ }, x, z) => {
101
+ const directionX = endX - startX;
102
+ const directionZ = endZ - startZ;
103
+ const length = Math.hypot(directionX, directionZ);
104
+ const offsetX = x - startX;
105
+ const offsetZ = z - startZ;
106
+ const along = (offsetX * directionX + offsetZ * directionZ) / length;
107
+ measurement[0] = (directionX * offsetZ - directionZ * offsetX) / length;
108
+ measurement[1] = Math.max(0, -along, along - length);
109
+ return measurement;
110
+ };
111
+ //#endregion
112
+ //#region src/terrain/getCliffBlend.ts
113
+ const getCliffBlend = (feature, x, z) => {
114
+ const measurement = measureSegment(feature, x, z);
115
+ const across = measurement[0] ?? 0;
116
+ const beyond = measurement[1] ?? 0;
117
+ const { falloff, width } = feature;
118
+ return MathUtils.smoothstep(across, -falloff, falloff) * (1 - MathUtils.smoothstep(across, width, width + falloff)) * (1 - MathUtils.smoothstep(beyond, 0, falloff));
119
+ };
120
+ //#endregion
121
+ //#region src/terrain/getPlateauBlend.ts
122
+ const getPlateauBlend = (feature, x, z) => {
123
+ const { falloff, radius, x: centreX, z: centreZ } = feature;
124
+ return 1 - MathUtils.smoothstep(Math.hypot(x - centreX, z - centreZ), radius, radius + falloff);
125
+ };
126
+ //#endregion
127
+ //#region src/terrain/getRidgeBlend.ts
128
+ const getRidgeBlend = (feature, x, z) => {
129
+ const measurement = measureSegment(feature, x, z);
130
+ const across = measurement[0] ?? 0;
131
+ const beyond = measurement[1] ?? 0;
132
+ const { width } = feature;
133
+ return Math.exp(-(across ** 2 + beyond ** 2) / (2 * width * width));
134
+ };
135
+ //#endregion
136
+ //#region src/terrain/TerrainFeatureKindBlendMap.ts
137
+ const TerrainFeatureKindBlendMap = {
138
+ ["cliff"]: getCliffBlend,
139
+ ["plateau"]: getPlateauBlend,
140
+ ["ridge"]: getRidgeBlend
141
+ };
142
+ //#endregion
143
+ //#region src/terrain/blendGridNodes.ts
144
+ const blendGridNodes = (values, sizeX, sizeZ, column, row) => {
145
+ const left = Math.floor(column);
146
+ const near = Math.floor(row);
147
+ const right = Math.min(left + 1, sizeX - 1);
148
+ const far = Math.min(near + 1, sizeZ - 1);
149
+ const across = column - left;
150
+ const along = row - near;
151
+ return ((values[near * sizeX + left] ?? 0) * (1 - across) + (values[near * sizeX + right] ?? 0) * across) * (1 - along) + ((values[far * sizeX + left] ?? 0) * (1 - across) + (values[far * sizeX + right] ?? 0) * across) * along;
152
+ };
153
+ //#endregion
154
+ //#region src/terrain/getTerrainTileColumn.ts
155
+ const getTerrainTileColumn = (key) => key % TILE_INDEX_SPAN - TILE_INDEX_OFFSET;
156
+ //#endregion
157
+ //#region src/terrain/getTerrainTileRow.ts
158
+ const getTerrainTileRow = (key) => Math.floor(key / TILE_INDEX_SPAN) % TILE_INDEX_SPAN - TILE_INDEX_OFFSET;
159
+ //#endregion
160
+ //#region src/terrain/computeTerrainTile.ts
161
+ const writeNormal = (normals, offset, heights, paddedIndex, paddedSide, reach, step) => {
162
+ const height = heights[paddedIndex] ?? 0;
163
+ const normalX = (heights[paddedIndex - reach] ?? height) - (heights[paddedIndex + reach] ?? height);
164
+ const normalY = 2 * reach * step;
165
+ const normalZ = (heights[paddedIndex - reach * paddedSide] ?? height) - (heights[paddedIndex + reach * paddedSide] ?? height);
166
+ const length = Math.hypot(normalX, normalY, normalZ);
167
+ normals[offset] = normalX / length;
168
+ normals[offset + 1] = normalY / length;
169
+ normals[offset + 2] = normalZ / length;
170
+ return normalY / length;
171
+ };
172
+ const computeTerrainTile = ({ cellsPerSide, finestTileSize, getHeight, key, writeColor }) => {
173
+ const level = getTerrainTileLevel(key);
174
+ const size = finestTileSize * 2 ** level;
175
+ const step = size / cellsPerSide;
176
+ const left = getTerrainTileColumn(key) * size;
177
+ const back = getTerrainTileRow(key) * size;
178
+ const side = cellsPerSide + 1;
179
+ const paddedSide = side + 4;
180
+ const heights = new Float32Array(paddedSide * paddedSide);
181
+ for (let row = 0; row < paddedSide; row++) for (let column = 0; column < paddedSide; column++) heights[row * paddedSide + column] = getHeight(left + (column - 2) * step, back + (row - 2) * step);
182
+ const vertexCount = side * side;
183
+ const positions = new Float32Array(vertexCount * 3);
184
+ const normals = new Float32Array(vertexCount * 3);
185
+ const colors = new Float32Array(vertexCount * 3);
186
+ const coarsePositions = new Float32Array(vertexCount * 4);
187
+ const coarseNormals = new Float32Array(vertexCount * 3);
188
+ const coarseColors = new Float32Array(vertexCount * 3);
189
+ for (let row = 0; row < side; row++) for (let column = 0; column < side; column++) {
190
+ const paddedIndex = (row + 2) * paddedSide + column + 2;
191
+ const height = heights[paddedIndex] ?? 0;
192
+ const index = row * side + column;
193
+ const offset = index * 3;
194
+ const x = column * step;
195
+ const z = row * step;
196
+ positions[offset] = x;
197
+ positions[offset + 1] = height;
198
+ positions[offset + 2] = z;
199
+ writeColor(colors, offset, height, 1 - writeNormal(normals, offset, heights, paddedIndex, paddedSide, 1, step), left + x, back + z);
200
+ const coarseColumn = column - column % 2;
201
+ const coarseRow = row - row % 2;
202
+ const coarseOffset = index * 4;
203
+ coarsePositions[coarseOffset] = coarseColumn * step;
204
+ coarsePositions[coarseOffset + 1] = heights[(coarseRow + 2) * paddedSide + coarseColumn + 2] ?? height;
205
+ coarsePositions[coarseOffset + 2] = coarseRow * step;
206
+ coarsePositions[coarseOffset + 3] = level;
207
+ if (coarseColumn !== column || coarseRow !== row) {
208
+ const evenOffset = (coarseRow * side + coarseColumn) * 3;
209
+ for (let component = 0; component < 3; component++) {
210
+ coarseNormals[offset + component] = coarseNormals[evenOffset + component] ?? 0;
211
+ coarseColors[offset + component] = coarseColors[evenOffset + component] ?? 0;
212
+ }
213
+ continue;
214
+ }
215
+ writeColor(coarseColors, offset, height, 1 - writeNormal(coarseNormals, offset, heights, paddedIndex, paddedSide, 2, step), left + x, back + z);
216
+ }
217
+ return {
218
+ coarseColors,
219
+ coarseNormals,
220
+ coarsePositions,
221
+ colors,
222
+ key,
223
+ normals,
224
+ positions
225
+ };
226
+ };
227
+ //#endregion
228
+ //#region src/terrain/fileByCell.ts
229
+ const NO_ITEMS = [];
230
+ const toCellKey = (column, row) => `${column},${row}`;
231
+ const fileByCell = (items, getBounds) => {
232
+ const cellItemsMap = /* @__PURE__ */ new Map();
233
+ for (const item of items) {
234
+ const { maxX, maxZ, minX, minZ } = getBounds(item);
235
+ for (let column = Math.floor(minX / 64); column <= Math.floor(maxX / 64); column++) for (let row = Math.floor(minZ / 64); row <= Math.floor(maxZ / 64); row++) {
236
+ const key = toCellKey(column, row);
237
+ const cellItems = cellItemsMap.get(key);
238
+ if (cellItems) cellItems.push(item);
239
+ else cellItemsMap.set(key, [item]);
240
+ }
241
+ }
242
+ return (x, z) => cellItemsMap.get(toCellKey(Math.floor(x / 64), Math.floor(z / 64))) ?? NO_ITEMS;
243
+ };
244
+ //#endregion
245
+ //#region src/terrain/getGaussianHillBlend.ts
246
+ const getGaussianHillBlend = ({ width, x: hillX, z: hillZ }, x, z) => {
247
+ const reach = 3 * width;
248
+ const acrossX = x - hillX;
249
+ const acrossZ = z - hillZ;
250
+ if (Math.abs(acrossX) > reach || Math.abs(acrossZ) > reach) return 0;
251
+ return Math.exp(-(acrossX ** 2 + acrossZ ** 2) / (2 * width * width));
252
+ };
253
+ //#endregion
254
+ //#region src/terrain/createGaussianHillsHeight.ts
255
+ const createGaussianHillsHeight = ({ base, hills }) => {
256
+ const getHills = fileByCell(hills, ({ width, x, z }) => {
257
+ const reach = 3 * width;
258
+ return {
259
+ maxX: x + reach,
260
+ maxZ: z + reach,
261
+ minX: x - reach,
262
+ minZ: z - reach
263
+ };
264
+ });
265
+ return (x, z) => {
266
+ let total = base;
267
+ for (const hill of getHills(x, z)) total += hill.height * getGaussianHillBlend(hill, x, z);
268
+ return total;
269
+ };
270
+ };
271
+ //#endregion
272
+ //#region src/terrain/getSegmentBounds.ts
273
+ const getSegmentBounds = (startX, startZ, endX, endZ, reach) => ({
274
+ maxX: Math.max(startX, endX) + reach,
275
+ maxZ: Math.max(startZ, endZ) + reach,
276
+ minX: Math.min(startX, endX) - reach,
277
+ minZ: Math.min(startZ, endZ) - reach
278
+ });
279
+ //#endregion
280
+ //#region src/terrain/sampleGroundLayerField.ts
281
+ const sampleGroundLayerField = ({ cellSize, layers, origin, size }, x, z, weights) => {
282
+ const sizeX = size[0] ?? 1;
283
+ const sizeZ = size[1] ?? 1;
284
+ const column = Math.min(Math.max((x - (origin[0] ?? 0)) / cellSize, 0), sizeX - 1);
285
+ const row = Math.min(Math.max((z - (origin[1] ?? 0)) / cellSize, 0), sizeZ - 1);
286
+ let total = 0;
287
+ for (const layer of GroundLayers) {
288
+ const shares = layers[layer];
289
+ const share = shares === void 0 ? 0 : blendGridNodes(shares, sizeX, sizeZ, column, row);
290
+ weights[layer] = share;
291
+ total += share;
292
+ }
293
+ if (total === 0) {
294
+ weights["Grass"] = 1;
295
+ return;
296
+ }
297
+ for (const layer of GroundLayers) weights[layer] /= total;
298
+ };
299
+ //#endregion
300
+ //#region src/terrain/createGroundLayerWeights.ts
301
+ const getBandAmount = ({ end, start }, value) => end >= start ? MathUtils.smoothstep(value, start, end) : 1 - MathUtils.smoothstep(value, end, start);
302
+ const layOver = (weights, layer, amount) => {
303
+ if (amount === 0) return;
304
+ for (const groundLayer of GroundLayers) weights[groundLayer] *= 1 - amount;
305
+ weights[layer] += amount;
306
+ };
307
+ const createGroundLayerWeights = ({ earthSlope, layerField, pathFalloff, paths, rockSlope, sandHeight, snowHeight }) => {
308
+ const weights = {
309
+ ["Earth"]: 0,
310
+ ["Grass"]: 0,
311
+ ["Path"]: 0,
312
+ ["Rock"]: 0,
313
+ ["Sand"]: 0,
314
+ ["Snow"]: 0
315
+ };
316
+ const getPaths = fileByCell(paths, ({ endX, endZ, startX, startZ, width }) => getSegmentBounds(startX, startZ, endX, endZ, width / 2 + pathFalloff));
317
+ return (height, slope, x, z) => {
318
+ if (layerField) sampleGroundLayerField(layerField, x, z, weights);
319
+ else {
320
+ for (const groundLayer of GroundLayers) weights[groundLayer] = 0;
321
+ weights["Grass"] = 1;
322
+ }
323
+ if (earthSlope) layOver(weights, "Earth", getBandAmount(earthSlope, slope));
324
+ if (sandHeight) layOver(weights, "Sand", getBandAmount(sandHeight, height));
325
+ if (snowHeight) layOver(weights, "Snow", getBandAmount(snowHeight, height));
326
+ let pathAmount = 0;
327
+ for (const path of getPaths(x, z)) {
328
+ const measurement = measureSegment(path, x, z);
329
+ const halfWidth = path.width / 2;
330
+ const distance = Math.hypot(measurement[0] ?? 0, measurement[1] ?? 0);
331
+ pathAmount = Math.max(pathAmount, 1 - MathUtils.smoothstep(distance, halfWidth, halfWidth + pathFalloff));
332
+ }
333
+ layOver(weights, "Path", pathAmount);
334
+ if (rockSlope) layOver(weights, "Rock", getBandAmount(rockSlope, slope));
335
+ return weights;
336
+ };
337
+ };
338
+ //#endregion
339
+ //#region src/terrain/createGroundPaintColor.ts
340
+ const createGroundPaintColor = (groundPaint, seed) => {
341
+ const { layerColors, patchScale } = groundPaint;
342
+ const getWeights = createGroundLayerWeights(groundPaint);
343
+ const noise = createSimplexNoise(seed);
344
+ const linearLayerColors = GroundLayers.map((layer) => ({
345
+ color: new Color(layerColors[layer].color),
346
+ layer,
347
+ patchColor: new Color(layerColors[layer].patchColor)
348
+ }));
349
+ const layerColor = new Color();
350
+ return (colors, offset, height, slope, x, z) => {
351
+ const weights = getWeights(height, slope, x, z);
352
+ const patch = (noise(x / patchScale, z / patchScale) + 1) / 2;
353
+ let red = 0;
354
+ let green = 0;
355
+ let blue = 0;
356
+ for (const { color, layer, patchColor } of linearLayerColors) {
357
+ const weight = weights[layer];
358
+ if (weight === 0) continue;
359
+ layerColor.lerpColors(color, patchColor, patch);
360
+ red += layerColor.r * weight;
361
+ green += layerColor.g * weight;
362
+ blue += layerColor.b * weight;
363
+ }
364
+ colors[offset] = red;
365
+ colors[offset + 1] = green;
366
+ colors[offset + 2] = blue;
367
+ };
368
+ };
369
+ //#endregion
370
+ //#region src/terrain/sampleTerrainResidualFade.ts
371
+ const sampleTerrainResidualFade = ({ cellSize, clearings, origin, size, weights }, x, z) => {
372
+ const sizeX = size[0] ?? 1;
373
+ const sizeZ = size[1] ?? 1;
374
+ const column = (x - (origin[0] ?? 0)) / cellSize;
375
+ const row = (z - (origin[1] ?? 0)) / cellSize;
376
+ if (column < 0 || row < 0 || column > sizeX - 1 || row > sizeZ - 1) return 0;
377
+ let weight = blendGridNodes(weights, sizeX, sizeZ, column, row);
378
+ for (const { falloff, radius, x: centreX, z: centreZ } of clearings) weight *= MathUtils.smoothstep(Math.hypot(x - centreX, z - centreZ), radius, radius + falloff);
379
+ return weight;
380
+ };
381
+ //#endregion
382
+ //#region src/terrain/createResidualHeight.ts
383
+ const createResidualHeight = ({ amplitude, fade, octaves, scale, seed }) => {
384
+ const noise = createSimplexNoise(seed);
385
+ return (x, z) => {
386
+ const weight = fade ? sampleTerrainResidualFade(fade, x, z) : 1;
387
+ if (weight === 0) return 0;
388
+ let total = 0;
389
+ let octaveAmplitude = amplitude;
390
+ let frequency = 1 / scale;
391
+ for (let octave = 0; octave < octaves; octave++) {
392
+ total += octaveAmplitude * noise(x * frequency, z * frequency);
393
+ octaveAmplitude /= 2;
394
+ frequency *= 2;
395
+ }
396
+ return weight * total;
397
+ };
398
+ };
399
+ //#endregion
400
+ //#region src/terrain/getTerrainFeatureBounds.ts
401
+ const TerrainFeatureKindBoundsMap = {
402
+ ["cliff"]: (feature) => {
403
+ const { endX, endZ, falloff, startX, startZ, width } = feature;
404
+ return getSegmentBounds(startX, startZ, endX, endZ, width + falloff);
405
+ },
406
+ ["plateau"]: (feature) => {
407
+ const { falloff, radius, x, z } = feature;
408
+ const reach = radius + falloff;
409
+ return {
410
+ maxX: x + reach,
411
+ maxZ: z + reach,
412
+ minX: x - reach,
413
+ minZ: z - reach
414
+ };
415
+ },
416
+ ["ridge"]: (feature) => {
417
+ const { endX, endZ, startX, startZ, width } = feature;
418
+ return getSegmentBounds(startX, startZ, endX, endZ, 3 * width);
419
+ }
420
+ };
421
+ const getTerrainFeatureBounds = (feature) => TerrainFeatureKindBoundsMap[feature.kind](feature);
422
+ //#endregion
423
+ //#region src/terrain/createTerrainFeaturesBlend.ts
424
+ const createTerrainFeaturesBlend = (features) => {
425
+ const getFeatures = fileByCell(features, getTerrainFeatureBounds);
426
+ return (x, z) => {
427
+ let blend = 0;
428
+ for (const feature of getFeatures(x, z)) blend = Math.max(blend, TerrainFeatureKindBlendMap[feature.kind](feature, x, z));
429
+ return blend;
430
+ };
431
+ };
432
+ //#endregion
433
+ //#region src/terrain/createTerrainFeaturesHeight.ts
434
+ const createTerrainFeaturesHeight = (features) => {
435
+ const getFeatures = fileByCell(features, getTerrainFeatureBounds);
436
+ return (x, z) => {
437
+ let total = 0;
438
+ for (const feature of getFeatures(x, z)) total += feature.height * TerrainFeatureKindBlendMap[feature.kind](feature, x, z);
439
+ return total;
440
+ };
441
+ };
442
+ //#endregion
443
+ //#region src/terrain/getTerrainShapeLevel.ts
444
+ const getTerrainShapeLevel = ({ base, hills }, baseUnder) => {
445
+ if (hills.length === 0) return void 0;
446
+ let minX = Infinity;
447
+ let maxX = -Infinity;
448
+ let minZ = Infinity;
449
+ let maxZ = -Infinity;
450
+ let maxWidth = 0;
451
+ for (const { width, x, z } of hills) {
452
+ minX = Math.min(minX, x);
453
+ maxX = Math.max(maxX, x);
454
+ minZ = Math.min(minZ, z);
455
+ maxZ = Math.max(maxZ, z);
456
+ maxWidth = Math.max(maxWidth, width);
457
+ }
458
+ return {
459
+ falloff: 3 * maxWidth,
460
+ height: base - baseUnder,
461
+ kind: "plateau",
462
+ radius: Math.hypot(maxX - minX, maxZ - minZ) / 2,
463
+ x: (minX + maxX) / 2,
464
+ z: (minZ + maxZ) / 2
465
+ };
466
+ };
467
+ //#endregion
468
+ //#region src/terrain/createTerrainShapeHeight.ts
469
+ const createTerrainShapeHeight = (shape, ...overlays) => {
470
+ const shapes = [shape, ...overlays];
471
+ const features = shapes.flatMap((layer) => layer.features ?? []);
472
+ const levels = overlays.flatMap((overlay) => getTerrainShapeLevel(overlay, shape.base) ?? []);
473
+ const getHillsHeight = createGaussianHillsHeight({
474
+ base: shape.base,
475
+ hills: shapes.flatMap(({ hills }) => hills)
476
+ });
477
+ const getFeaturesHeight = createTerrainFeaturesHeight([...features, ...levels]);
478
+ const residualHeights = shapes.flatMap(({ residual }) => residual ? [createResidualHeight(residual)] : []);
479
+ if (residualHeights.length === 0) return (x, z) => getHillsHeight(x, z) + getFeaturesHeight(x, z);
480
+ const getFeaturesBlend = createTerrainFeaturesBlend(features);
481
+ return (x, z) => {
482
+ const residualShare = 1 - getFeaturesBlend(x, z);
483
+ let residualHeight = 0;
484
+ if (residualShare > 0) for (const getResidualHeight of residualHeights) residualHeight += residualShare * getResidualHeight(x, z);
485
+ return getHillsHeight(x, z) + getFeaturesHeight(x, z) + residualHeight;
486
+ };
487
+ };
488
+ //#endregion
489
+ //#region src/vegetation/computeScatterPoints.ts
490
+ const CELL_SIZE_PER_SPACING = 1 / Math.SQRT2;
491
+ const CELL_REACH = Math.ceil(Math.SQRT2);
492
+ const checkIsCrowded = (cells, cellsPerSide, points, spacing, column, row, x, z) => {
493
+ for (let neighbourRow = Math.max(0, row - CELL_REACH); neighbourRow <= Math.min(cellsPerSide - 1, row + CELL_REACH); neighbourRow++) for (let neighbourColumn = Math.max(0, column - CELL_REACH); neighbourColumn <= Math.min(cellsPerSide - 1, column + CELL_REACH); neighbourColumn++) {
494
+ const pointIndex = cells[neighbourRow * cellsPerSide + neighbourColumn] ?? -1;
495
+ if (pointIndex === -1) continue;
496
+ const distanceX = (points[pointIndex * 2] ?? 0) - x;
497
+ const distanceZ = (points[pointIndex * 2 + 1] ?? 0) - z;
498
+ if (distanceX * distanceX + distanceZ * distanceZ < spacing * spacing) return true;
499
+ }
500
+ return false;
501
+ };
502
+ const computeScatterPoints = ({ candidateCount, checkIsAccepted, seed, size, spacing }) => {
503
+ const random = createSeededRandom(seed);
504
+ const cellSize = spacing * CELL_SIZE_PER_SPACING;
505
+ const cellsPerSide = Math.ceil(size / cellSize);
506
+ const cells = new Int32Array(cellsPerSide * cellsPerSide).fill(-1);
507
+ const points = [];
508
+ for (let candidate = 0; candidate < candidateCount; candidate++) {
509
+ const x = random() * size;
510
+ const z = random() * size;
511
+ const column = Math.floor(x / cellSize);
512
+ const row = Math.floor(z / cellSize);
513
+ if (!checkIsAccepted(x, z) || checkIsCrowded(cells, cellsPerSide, points, spacing, column, row, x, z)) continue;
514
+ cells[row * cellsPerSide + column] = points.length / 2;
515
+ points.push(x, z);
516
+ }
517
+ return Float32Array.from(points);
518
+ };
519
+ //#endregion
520
+ export { TERRAIN_RING_INITIAL_TILE_COUNT as A, getCliffBlend as C, TERRAIN_CELL_SIZE as D, GAUSSIAN_REACH_WIDTHS as E, GroundLayer as F, GroundLayers as I, createSeededRandom as L, TILE_INDEX_SPAN as M, createSimplexNoise as N, TERRAIN_HEIGHT_PADDING as O, TerrainFeatureKind as P, getPlateauBlend as S, getTerrainTileLevel as T, getTerrainTileRow as _, createTerrainFeaturesBlend as a, TerrainFeatureKindBlendMap as b, sampleTerrainResidualFade as c, sampleGroundLayerField as d, getSegmentBounds as f, computeTerrainTile as g, fileByCell as h, createTerrainFeaturesHeight as i, TILE_INDEX_OFFSET as j, TERRAIN_LAYER as k, createGroundPaintColor as l, getGaussianHillBlend as m, createTerrainShapeHeight as n, getTerrainFeatureBounds as o, createGaussianHillsHeight as p, getTerrainShapeLevel as r, createResidualHeight as s, computeScatterPoints as t, createGroundLayerWeights as u, getTerrainTileColumn as v, measureSegment as w, getRidgeBlend as x, blendGridNodes as y };
@@ -0,0 +1,2 @@
1
+ import { E as GroundLayers, T as GroundLayer, a as createTerrainShapeHeight, c as computeTerrainTile, i as getTerrainTileColumn, l as createSeededRandom, n as getTerrainTileRow, o as createGroundPaintColor, r as getTerrainTileLevel, s as createGroundLayerWeights, t as computeScatterPoints } from "./terrainWorker-CLOYYKT1.js";
2
+ export { GroundLayer, GroundLayers, computeScatterPoints, computeTerrainTile, createGroundLayerWeights, createGroundPaintColor, createSeededRandom, createTerrainShapeHeight, getTerrainTileColumn, getTerrainTileLevel, getTerrainTileRow };
@@ -0,0 +1 @@
1
+ export { F as GroundLayer, I as GroundLayers, t as computeScatterPoints, g as computeTerrainTile, u as createGroundLayerWeights, l as createGroundPaintColor, L as createSeededRandom, n as createTerrainShapeHeight, v as getTerrainTileColumn, T as getTerrainTileLevel, _ as getTerrainTileRow } from "./terrainWorker-DZ04cA3m.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genshin-engine",
3
- "version": "4.0.0",
3
+ "version": "4.1.1",
4
4
  "description": "An anime-style open-world engine for the browser on three.js WebGPU — toon materials, outlines, post-processing, streamed terrain, parametric world kits and a Web Audio synthesizer, as plain TypeScript and TSL modules.",
5
5
  "keywords": [
6
6
  "anime",
@@ -36,6 +36,7 @@
36
36
  },
37
37
  "exports": {
38
38
  ".": "./dist/index.js",
39
+ "./terrainWorker": "./dist/terrainWorker.js",
39
40
  "./package.json": "./package.json"
40
41
  },
41
42
  "publishConfig": {
@@ -51,19 +52,22 @@
51
52
  "test": "vitest",
52
53
  "typecheck": "tsc"
53
54
  },
55
+ "dependencies": {
56
+ "three-mesh-bvh": "^0.9.16"
57
+ },
54
58
  "devDependencies": {
55
59
  "@arethetypeswrong/core": "^0.18.5",
56
- "@esposter/configuration": "4.0.0",
57
- "@esposter/shared-node": "4.0.0",
58
- "@types/node": "^26.6.4",
60
+ "@esposter/configuration": "4.1.1",
61
+ "@esposter/shared-node": "4.1.1",
62
+ "@types/node": "^26.6.5",
59
63
  "@types/three": "^0.186.0",
60
64
  "publint": "^0.3.25",
61
- "tsdown": "^0.23.0",
62
- "vite": "^8.3.3",
63
- "vitest": "^5.0.3"
65
+ "tsdown": "^0.23.1",
66
+ "vite": "^8.3.4",
67
+ "vitest": "^5.0.4"
64
68
  },
65
69
  "peerDependencies": {
66
70
  "three": "^0.186.1"
67
71
  },
68
- "gitHead": "7667bc8c9a2b4e9dcacd6b845ee8cc9951250ae7"
72
+ "gitHead": "47b7795ab557d7731aa60588d0b9d1cdff243328"
69
73
  }