genshin-engine 3.6.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/dist/index.js ADDED
@@ -0,0 +1,1258 @@
1
+ import { Box3, BufferAttribute, BufferGeometry, Color, CylinderGeometry, Data3DTexture, DataTexture, DirectionalLight, DoubleSide, HalfFloatType, InstancedBufferGeometry, LatheGeometry, LinearFilter, OrthographicCamera, Quaternion, RedFormat, Vector2, Vector3 } from "three";
2
+ import { Fn, If, attribute, cameraFar, cameraNear, cameraPosition, cameraViewMatrix, cos, exp, exp2, float, getViewPosition, hash, instanceIndex, max, mix, modelViewMatrix, modelWorldMatrix, mrt, mx_cell_noise_float, mx_fractal_noise_float, mx_noise_float, normalView, normalWorldGeometry, output, perspectiveDepthToViewZ, positionGeometry, positionLocal, positionView, positionViewDirection, positionWorld, pow, reflect, renderOutput, screenUV, sin, smoothstep, step, texture, texture3D, time, uniform, uv, varying, vec2, vec3, vec4, velocity, viewportDepthTexture, viewportSharedTexture } from "three/tsl";
3
+ import { CSMShadowNode } from "three/examples/jsm/csm/CSMShadowNode.js";
4
+ import { mergeGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js";
5
+ import { MeshBasicNodeMaterial, MeshToonNodeMaterial, NeutralToneMapping, RenderPipeline, RenderTarget, ToonOutlinePassNode, WebGPURenderer } from "three/webgpu";
6
+ import { voronoi2d } from "three/examples/jsm/tsl/math/voronoiNoise.js";
7
+ import { bilateralBlur } from "three/examples/jsm/tsl/display/BilateralBlurNode.js";
8
+ import { bloom } from "three/examples/jsm/tsl/display/BloomNode.js";
9
+ import { depthAwareBlend } from "three/examples/jsm/tsl/display/depthAwareBlend.js";
10
+ import { godrays } from "three/examples/jsm/tsl/display/GodraysNode.js";
11
+ import { lut3D } from "three/examples/jsm/tsl/display/Lut3DNode.js";
12
+ import { smaa } from "three/examples/jsm/tsl/display/SMAANode.js";
13
+ import { traa } from "three/examples/jsm/tsl/display/TRAANode.js";
14
+ //#region src/atmosphere/applySkyState.ts
15
+ const GODRAYS_REDRAW_COSINE = Math.cos(.6 * Math.PI / 180);
16
+ const godraysDirection = new Vector3();
17
+ const applySkyState = (skyState, { fogUniforms, godraysLight, hemisphere, light, lightDistance, lightUniforms, postUniforms, skyUniforms }) => {
18
+ const { horizonColor, lightColor, lightDirection } = skyState;
19
+ light.position.copy(light.target.position).addScaledVector(lightDirection, lightDistance);
20
+ light.color.copy(lightColor);
21
+ light.intensity = skyState.lightIntensity;
22
+ hemisphere.color.copy(skyState.hemisphereSkyColor);
23
+ hemisphere.groundColor.copy(skyState.hemisphereGroundColor);
24
+ hemisphere.intensity = skyState.hemisphereIntensity;
25
+ lightUniforms.lightColor.value.copy(lightColor);
26
+ lightUniforms.sunDirection.value.copy(lightDirection);
27
+ lightUniforms.rimColor.value.copy(horizonColor);
28
+ fogUniforms.color.value.copy(horizonColor);
29
+ postUniforms.godraysColor.value.copy(lightColor);
30
+ skyUniforms.cloudLitColor.value.copy(skyState.cloudLitColor);
31
+ skyUniforms.cloudShadeColor.value.copy(skyState.cloudShadeColor);
32
+ skyUniforms.horizonColor.value.copy(horizonColor);
33
+ skyUniforms.lightColor.value.copy(lightColor);
34
+ skyUniforms.moonDirection.value.copy(skyState.moonDirection);
35
+ skyUniforms.starIntensity.value = skyState.starIntensity;
36
+ skyUniforms.sunDirection.value.copy(skyState.sunDirection);
37
+ skyUniforms.zenithColor.value.copy(skyState.zenithColor);
38
+ godraysDirection.subVectors(godraysLight.position, godraysLight.target.position).normalize();
39
+ if (godraysDirection.dot(lightDirection) >= GODRAYS_REDRAW_COSINE) return;
40
+ godraysLight.position.copy(godraysLight.target.position).addScaledVector(lightDirection, lightDistance);
41
+ godraysLight.shadow.needsUpdate = true;
42
+ };
43
+ //#endregion
44
+ //#region src/clock/constants.ts
45
+ const MINUTES_PER_DAY = 1440;
46
+ const GAME_MINUTES_PER_SECOND = 1;
47
+ //#endregion
48
+ //#region src/atmosphere/computeSunDirection.ts
49
+ const computeSunDirection = (minutes, tilt, sunDirection) => {
50
+ const hourAngle = minutes / MINUTES_PER_DAY * Math.PI * 2;
51
+ const height = -Math.cos(hourAngle);
52
+ return sunDirection.set(Math.sin(hourAngle), height * Math.cos(tilt), height * Math.sin(tilt));
53
+ };
54
+ //#endregion
55
+ //#region src/atmosphere/createSkyState.ts
56
+ const createSkyState = () => ({
57
+ cloudLitColor: new Color(),
58
+ cloudShadeColor: new Color(),
59
+ hemisphereGroundColor: new Color(),
60
+ hemisphereIntensity: 0,
61
+ hemisphereSkyColor: new Color(),
62
+ horizonColor: new Color(),
63
+ lightColor: new Color(),
64
+ lightDirection: new Vector3(),
65
+ lightIntensity: 0,
66
+ moonDirection: new Vector3(),
67
+ starIntensity: 0,
68
+ sunDirection: new Vector3(),
69
+ zenithColor: new Color()
70
+ });
71
+ //#endregion
72
+ //#region src/atmosphere/createSkyUniforms.ts
73
+ const createSkyUniforms = () => ({
74
+ cloudCoverage: uniform(0),
75
+ cloudDrift: uniform(new Vector2()),
76
+ cloudLitColor: uniform(new Color()),
77
+ cloudShadeColor: uniform(new Color()),
78
+ horizonColor: uniform(new Color()),
79
+ lightColor: uniform(new Color()),
80
+ moonDirection: uniform(new Vector3(0, -1, 0)),
81
+ starIntensity: uniform(0),
82
+ sunDirection: uniform(new Vector3(0, 1, 0)),
83
+ zenithColor: uniform(new Color())
84
+ });
85
+ //#endregion
86
+ //#region src/atmosphere/createSunLight.ts
87
+ const LIGHT_MARGIN = 100;
88
+ const SHADOW_BIAS = -5e-4;
89
+ const SHADOW_NORMAL_BIAS = .05;
90
+ const createSunLight = ({ cascadeCount, maxFar, shadowMapSize }) => {
91
+ const light = new DirectionalLight();
92
+ light.castShadow = true;
93
+ light.shadow.mapSize.set(shadowMapSize, shadowMapSize);
94
+ light.shadow.bias = SHADOW_BIAS;
95
+ light.shadow.normalBias = SHADOW_NORMAL_BIAS;
96
+ light.shadow.camera.far = maxFar + 200;
97
+ const cascadedShadowNode = new CSMShadowNode(light, {
98
+ cascades: cascadeCount,
99
+ lightMargin: LIGHT_MARGIN,
100
+ maxFar,
101
+ mode: "practical"
102
+ });
103
+ cascadedShadowNode.fade = true;
104
+ light.shadow.shadowNode = cascadedShadowNode;
105
+ return {
106
+ cascadedShadowNode,
107
+ light
108
+ };
109
+ };
110
+ //#endregion
111
+ //#region src/atmosphere/sampleSkyState.ts
112
+ const lerp = (from, to, amount) => from + (to - from) * amount;
113
+ const sampleSkyState = (skyKeyframes, minutes, tilt, skyState) => {
114
+ let nextIndex = 0;
115
+ while (nextIndex < skyKeyframes.length && (skyKeyframes[nextIndex]?.minutes ?? 0) <= minutes) nextIndex++;
116
+ if (nextIndex === skyKeyframes.length) nextIndex = 0;
117
+ const previous = skyKeyframes.at(nextIndex - 1) ?? skyKeyframes[nextIndex];
118
+ const next = skyKeyframes[nextIndex];
119
+ if (!previous || !next) return skyState;
120
+ const span = (next.minutes - previous.minutes + 1440) % 1440 || 1440;
121
+ const amount = (minutes - previous.minutes + MINUTES_PER_DAY) % MINUTES_PER_DAY / span;
122
+ skyState.cloudLitColor.lerpColors(previous.cloudLitColor, next.cloudLitColor, amount);
123
+ skyState.cloudShadeColor.lerpColors(previous.cloudShadeColor, next.cloudShadeColor, amount);
124
+ skyState.hemisphereGroundColor.lerpColors(previous.hemisphereGroundColor, next.hemisphereGroundColor, amount);
125
+ skyState.hemisphereIntensity = lerp(previous.hemisphereIntensity, next.hemisphereIntensity, amount);
126
+ skyState.hemisphereSkyColor.lerpColors(previous.hemisphereSkyColor, next.hemisphereSkyColor, amount);
127
+ skyState.horizonColor.lerpColors(previous.horizonColor, next.horizonColor, amount);
128
+ skyState.lightColor.lerpColors(previous.lightColor, next.lightColor, amount);
129
+ skyState.lightIntensity = lerp(previous.lightIntensity, next.lightIntensity, amount);
130
+ skyState.starIntensity = lerp(previous.starIntensity, next.starIntensity, amount);
131
+ skyState.zenithColor.lerpColors(previous.zenithColor, next.zenithColor, amount);
132
+ computeSunDirection(minutes, tilt, skyState.sunDirection);
133
+ skyState.moonDirection.copy(skyState.sunDirection).negate();
134
+ skyState.lightDirection.copy(skyState.sunDirection.y >= 0 ? skyState.sunDirection : skyState.moonDirection);
135
+ return skyState;
136
+ };
137
+ //#endregion
138
+ //#region src/clock/advanceGameClock.ts
139
+ const advanceGameClock = (gameClock, deltaSeconds) => {
140
+ gameClock.minutes = (gameClock.minutes + deltaSeconds * gameClock.minutesPerSecond) % MINUTES_PER_DAY;
141
+ };
142
+ //#endregion
143
+ //#region src/kits/statue/createStatueGeometry.ts
144
+ const PLINTH_SIDES = 8;
145
+ const FIGURE_SEGMENTS = 24;
146
+ const FIGURE_PROFILE = [
147
+ new Vector2(0, 0),
148
+ new Vector2(1.5, 0),
149
+ new Vector2(1.25, .8),
150
+ new Vector2(.85, 2.6),
151
+ new Vector2(.7, 3.6),
152
+ new Vector2(.95, 4.3),
153
+ new Vector2(.6, 4.7),
154
+ new Vector2(.45, 5.1),
155
+ new Vector2(.5, 5.5),
156
+ new Vector2(.3, 6),
157
+ new Vector2(0, 6.2)
158
+ ];
159
+ const createStatueGeometry = () => {
160
+ const lowerStep = new CylinderGeometry(3.2, 3.4, .6, PLINTH_SIDES).translate(0, .3, 0);
161
+ const upperStep = new CylinderGeometry(2.6, 2.8, .6, PLINTH_SIDES).translate(0, .9, 0);
162
+ const pedestal = new CylinderGeometry(1.8, 2.1, 2.2, PLINTH_SIDES).translate(0, 2.3, 0);
163
+ const figure = new LatheGeometry(FIGURE_PROFILE, FIGURE_SEGMENTS).translate(0, 3.4, 0);
164
+ const parts = [
165
+ lowerStep,
166
+ upperStep,
167
+ pedestal,
168
+ figure
169
+ ].map((part) => part.toNonIndexed());
170
+ const statueGeometry = mergeGeometries(parts) ?? new BufferGeometry();
171
+ for (const part of [
172
+ lowerStep,
173
+ upperStep,
174
+ pedestal,
175
+ figure,
176
+ ...parts
177
+ ]) part.dispose();
178
+ statueGeometry.computeVertexNormals();
179
+ return statueGeometry;
180
+ };
181
+ //#endregion
182
+ //#region src/random/createSeededRandom.ts
183
+ const createSeededRandom = (seed) => {
184
+ let state = seed;
185
+ return () => {
186
+ state = state + 1831565813 >>> 0;
187
+ let mixed = Math.imul(state ^ state >>> 15, 1 | state);
188
+ mixed ^= mixed + Math.imul(mixed ^ mixed >>> 7, 61 | mixed);
189
+ return ((mixed ^ mixed >>> 14) >>> 0) / 2 ** 32;
190
+ };
191
+ };
192
+ //#endregion
193
+ //#region src/kits/tree/computeLeafCards.ts
194
+ const VERTICES_PER_CARD = 4;
195
+ const INDICES_PER_CARD = 6;
196
+ const CORNER_SIGNS = [
197
+ [-1, -1],
198
+ [1, -1],
199
+ [1, 1],
200
+ [-1, 1]
201
+ ];
202
+ const computeLeafCards = (clusterCenters, { cardSize, cardsPerCluster, clusterRadius, seed }) => {
203
+ const random = createSeededRandom(seed + 1);
204
+ const cardCount = clusterCenters.length * cardsPerCluster;
205
+ const positions = new Float32Array(cardCount * VERTICES_PER_CARD * 3);
206
+ const normals = new Float32Array(cardCount * VERTICES_PER_CARD * 3);
207
+ const uvs = new Float32Array(cardCount * VERTICES_PER_CARD * 2);
208
+ const indices = new Uint32Array(cardCount * INDICES_PER_CARD);
209
+ let card = 0;
210
+ for (const { x: centerX, y: centerY, z: centerZ } of clusterCenters) for (let cardIndex = 0; cardIndex < cardsPerCluster; cardIndex++, card++) {
211
+ let offsetX;
212
+ let offsetY;
213
+ let offsetZ;
214
+ do {
215
+ offsetX = random() * 2 - 1;
216
+ offsetY = random() * 2 - 1;
217
+ offsetZ = random() * 2 - 1;
218
+ } while (offsetX * offsetX + offsetY * offsetY + offsetZ * offsetZ > 1);
219
+ const cardX = centerX + offsetX * clusterRadius;
220
+ const cardY = centerY + offsetY * clusterRadius * .7;
221
+ const cardZ = centerZ + offsetZ * clusterRadius;
222
+ const facingAngle = random() * Math.PI * 2;
223
+ const tiltAngle = (random() - .5) * Math.PI;
224
+ const tangentX = Math.cos(facingAngle);
225
+ const tangentZ = Math.sin(facingAngle);
226
+ const bitangentX = -Math.sin(tiltAngle) * tangentZ;
227
+ const bitangentY = Math.cos(tiltAngle);
228
+ const bitangentZ = Math.sin(tiltAngle) * tangentX;
229
+ const vertexStart = card * VERTICES_PER_CARD;
230
+ for (const [cornerIndex, [tangentSign, bitangentSign]] of CORNER_SIGNS.entries()) {
231
+ const vertex = vertexStart + cornerIndex;
232
+ const x = cardX + (tangentX * tangentSign + bitangentX * bitangentSign) * cardSize;
233
+ const y = cardY + bitangentY * bitangentSign * cardSize;
234
+ const z = cardZ + (tangentZ * tangentSign + bitangentZ * bitangentSign) * cardSize;
235
+ const normalX = x - centerX;
236
+ const normalY = y - centerY;
237
+ const normalZ = z - centerZ;
238
+ const length = Math.hypot(normalX, normalY, normalZ) || 1;
239
+ positions[vertex * 3] = x;
240
+ positions[vertex * 3 + 1] = y;
241
+ positions[vertex * 3 + 2] = z;
242
+ normals[vertex * 3] = normalX / length;
243
+ normals[vertex * 3 + 1] = normalY / length;
244
+ normals[vertex * 3 + 2] = normalZ / length;
245
+ uvs[vertex * 2] = (tangentSign + 1) / 2;
246
+ uvs[vertex * 2 + 1] = (bitangentSign + 1) / 2;
247
+ }
248
+ const indexStart = card * INDICES_PER_CARD;
249
+ indices[indexStart] = vertexStart;
250
+ indices[indexStart + 1] = vertexStart + 1;
251
+ indices[indexStart + 2] = vertexStart + 2;
252
+ indices[indexStart + 3] = vertexStart;
253
+ indices[indexStart + 4] = vertexStart + 2;
254
+ indices[indexStart + 5] = vertexStart + 3;
255
+ }
256
+ return {
257
+ indices,
258
+ normals,
259
+ positions,
260
+ uvs
261
+ };
262
+ };
263
+ //#endregion
264
+ //#region src/kits/tree/computeTreeSkeleton.ts
265
+ const TRUNK_LEAN = .08;
266
+ const FORKS_PER_BRANCH = 2;
267
+ const computeTreeSkeleton = ({ branchLength, mainBranchCount, seed, trunkHeight, trunkRadius }) => {
268
+ const random = createSeededRandom(seed);
269
+ const trunkTop = new Vector3((random() - .5) * TRUNK_LEAN * trunkHeight, trunkHeight, (random() - .5) * TRUNK_LEAN * trunkHeight);
270
+ const branchSegments = [{
271
+ end: trunkTop,
272
+ endRadius: trunkRadius * .6,
273
+ start: new Vector3(),
274
+ startRadius: trunkRadius
275
+ }];
276
+ const clusterCenters = [trunkTop.clone().setY(trunkTop.y + branchLength * .6)];
277
+ for (let branchIndex = 0; branchIndex < mainBranchCount; branchIndex++) {
278
+ const angle = branchIndex / mainBranchCount * Math.PI * 2 + (random() - .5);
279
+ const start = new Vector3().lerpVectors(new Vector3(), trunkTop, .75 + random() * .25);
280
+ const direction = new Vector3(Math.cos(angle), .45 + random() * .4, Math.sin(angle)).normalize();
281
+ const end = start.clone().addScaledVector(direction, branchLength * (.75 + random() * .25));
282
+ branchSegments.push({
283
+ end,
284
+ endRadius: trunkRadius * .18,
285
+ start,
286
+ startRadius: trunkRadius * .45
287
+ });
288
+ for (let forkIndex = 0; forkIndex < FORKS_PER_BRANCH; forkIndex++) {
289
+ const forkAngle = angle + (forkIndex - .5) * .9 + (random() - .5) * .3;
290
+ const forkDirection = new Vector3(Math.cos(forkAngle), .6 + random() * .4, Math.sin(forkAngle)).normalize();
291
+ const forkEnd = end.clone().addScaledVector(forkDirection, branchLength * (.45 + random() * .2));
292
+ branchSegments.push({
293
+ end: forkEnd,
294
+ endRadius: trunkRadius * .05,
295
+ start: end,
296
+ startRadius: trunkRadius * .18
297
+ });
298
+ clusterCenters.push(forkEnd);
299
+ }
300
+ }
301
+ return {
302
+ branchSegments,
303
+ clusterCenters
304
+ };
305
+ };
306
+ //#endregion
307
+ //#region src/kits/tree/createTreeGeometry.ts
308
+ const BRANCH_RADIAL_SEGMENTS = 7;
309
+ const UP = new Vector3(0, 1, 0);
310
+ const createTreeGeometry = (treeOptions) => {
311
+ const { branchSegments, clusterCenters } = computeTreeSkeleton(treeOptions);
312
+ const direction = new Vector3();
313
+ const rotation = new Quaternion();
314
+ const segmentGeometries = branchSegments.map(({ end, endRadius, start, startRadius }) => {
315
+ direction.subVectors(end, start);
316
+ const length = direction.length();
317
+ const segmentGeometry = new CylinderGeometry(endRadius, startRadius, length, BRANCH_RADIAL_SEGMENTS, 1, true);
318
+ segmentGeometry.applyQuaternion(rotation.setFromUnitVectors(UP, direction.normalize()));
319
+ segmentGeometry.translate((start.x + end.x) / 2, (start.y + end.y) / 2, (start.z + end.z) / 2);
320
+ return segmentGeometry;
321
+ });
322
+ const branchGeometry = mergeGeometries(segmentGeometries) ?? new BufferGeometry();
323
+ for (const segmentGeometry of segmentGeometries) segmentGeometry.dispose();
324
+ const { indices, normals, positions, uvs } = computeLeafCards(clusterCenters, treeOptions);
325
+ const leafGeometry = new BufferGeometry();
326
+ leafGeometry.setAttribute("position", new BufferAttribute(positions, 3));
327
+ leafGeometry.setAttribute("normal", new BufferAttribute(normals, 3));
328
+ leafGeometry.setAttribute("uv", new BufferAttribute(uvs, 2));
329
+ leafGeometry.setIndex(new BufferAttribute(indices, 1));
330
+ leafGeometry.computeBoundingSphere();
331
+ return {
332
+ branchGeometry,
333
+ leafGeometry
334
+ };
335
+ };
336
+ //#endregion
337
+ //#region src/materials/computeRampValues.ts
338
+ const MAX_BYTE$1 = 255;
339
+ const computeRampValues = ({ resolution, softness, terminator }) => {
340
+ const lowerEdge = terminator - softness / 2;
341
+ const upperEdge = terminator + softness / 2;
342
+ return Uint8Array.from({ length: resolution }, (_value, index) => {
343
+ const coordinate = (index + .5) / resolution;
344
+ const step = Math.min(Math.max((coordinate - lowerEdge) / (upperEdge - lowerEdge), 0), 1);
345
+ return Math.round(step * step * (3 - 2 * step) * MAX_BYTE$1);
346
+ });
347
+ };
348
+ //#endregion
349
+ //#region src/materials/createRampTexture.ts
350
+ const createRampTexture = (rampOptions) => {
351
+ const rampValues = computeRampValues(rampOptions);
352
+ const rampTexture = new DataTexture(rampValues, rampOptions.resolution, 1, RedFormat);
353
+ rampTexture.magFilter = LinearFilter;
354
+ rampTexture.minFilter = LinearFilter;
355
+ rampTexture.needsUpdate = true;
356
+ return rampTexture;
357
+ };
358
+ //#endregion
359
+ //#region src/nodes/ToonNodeMaterial.ts
360
+ var ToonNodeMaterial = class extends MeshToonNodeMaterial {
361
+ emissiveNode = null;
362
+ isMeshToonNodeMaterial;
363
+ constructor(parameters, isOutlined) {
364
+ super(parameters);
365
+ this.isMeshToonNodeMaterial = isOutlined;
366
+ }
367
+ };
368
+ //#endregion
369
+ //#region src/nodes/createCausticsNode.ts
370
+ const CAUSTIC_SCALE = .35;
371
+ const CAUSTIC_SPEED = .6;
372
+ const createCausticsNode = ({ lightColor }, { causticStrength, deepDepth, level }) => {
373
+ const floorPosition = positionWorld.xz.mul(CAUSTIC_SCALE);
374
+ const drift = time.mul(CAUSTIC_SPEED);
375
+ const cells = voronoi2d(floorPosition, drift).mul(voronoi2d(floorPosition.mul(1.7), drift.mul(1.3)));
376
+ const veins = smoothstep(.35, .45, cells);
377
+ const depthBelow = level.sub(positionWorld.y);
378
+ const fade = smoothstep(0, .3, depthBelow).mul(float(1).sub(smoothstep(0, deepDepth, depthBelow)));
379
+ return lightColor.mul(veins.mul(fade).mul(causticStrength));
380
+ };
381
+ //#endregion
382
+ //#region src/nodes/createGenshinRenderer.ts
383
+ const createGenshinRenderer = (canvas) => {
384
+ const renderer = new WebGPURenderer({
385
+ antialias: false,
386
+ canvas,
387
+ powerPreference: "high-performance"
388
+ });
389
+ renderer.toneMapping = NeutralToneMapping;
390
+ return renderer;
391
+ };
392
+ //#endregion
393
+ //#region src/nodes/createRimNode.ts
394
+ const RIM_POWER = 4;
395
+ const createRimNode = ({ rimColor, rimStrength, sunDirection }) => {
396
+ const sunDirectionView = cameraViewMatrix.mul(vec4(sunDirection, 0)).xyz.normalize();
397
+ const fresnel = float(1).sub(normalView.dot(positionViewDirection).saturate()).pow(RIM_POWER);
398
+ const litMask = normalView.dot(sunDirectionView).saturate();
399
+ return rimColor.mul(fresnel.mul(litMask).mul(rimStrength));
400
+ };
401
+ //#endregion
402
+ //#region src/nodes/createToonMaterial.ts
403
+ const createToonMaterial = ({ color, isOutlined = true, isVertexColors = false, lightUniforms, rampTexture }) => {
404
+ const toonMaterial = new ToonNodeMaterial({
405
+ color,
406
+ gradientMap: rampTexture,
407
+ vertexColors: isVertexColors
408
+ }, isOutlined);
409
+ toonMaterial.emissiveNode = createRimNode(lightUniforms);
410
+ return toonMaterial;
411
+ };
412
+ //#endregion
413
+ //#region src/nodes/createWindNode.ts
414
+ const TURBULENCE_SCALE = .07;
415
+ const TURBULENCE_SPEED = .4;
416
+ const createWindNode = ({ direction, gustSpeed, gustStrength, gustWidth, strength }, groundPosition) => {
417
+ const gustPhase = groundPosition.dot(direction).sub(time.mul(gustSpeed)).div(gustWidth).mul(Math.PI * 2);
418
+ const gust = smoothstep(.2, 1, sin(gustPhase).mul(.5).add(.5)).mul(gustStrength);
419
+ const turbulence = mx_noise_float(vec3(groundPosition.mul(TURBULENCE_SCALE), time.mul(TURBULENCE_SPEED))).mul(.35);
420
+ return direction.mul(strength.add(gust).mul(turbulence.add(1)));
421
+ };
422
+ //#endregion
423
+ //#region src/nodes/createLeafMaterial.ts
424
+ const LEAF_ALPHA_TEST = .5;
425
+ const SWAY_PER_WIND = .03;
426
+ const FLUTTER = .06;
427
+ const createLeafMaterial = (toonMaterialOptions, windUniforms) => {
428
+ const leafMaterial = createToonMaterial(toonMaterialOptions);
429
+ const centered = uv().sub(.5).mul(2);
430
+ const width = float(1).sub(centered.y.abs().pow(2)).mul(.8);
431
+ leafMaterial.opacityNode = step(0, width.sub(centered.x.abs()));
432
+ leafMaterial.alphaTest = LEAF_ALPHA_TEST;
433
+ leafMaterial.side = DoubleSide;
434
+ if (windUniforms) {
435
+ const worldPosition = modelWorldMatrix.mul(vec4(positionLocal, 1)).xyz;
436
+ const lean = createWindNode(windUniforms, worldPosition.xz).mul(positionLocal.y.mul(SWAY_PER_WIND));
437
+ const phase = hash(worldPosition.x.mul(12.9).add(worldPosition.z.mul(78.2)).floor()).mul(Math.PI * 2);
438
+ const flutter = sin(time.mul(3).add(phase)).mul(FLUTTER).mul(windUniforms.strength.add(windUniforms.gustStrength));
439
+ leafMaterial.positionNode = positionLocal.add(vec3(lean.x, flutter, lean.y));
440
+ }
441
+ return leafMaterial;
442
+ };
443
+ //#endregion
444
+ //#region src/nodes/createLightUniforms.ts
445
+ const createLightUniforms = () => ({
446
+ lightColor: uniform(new Color()),
447
+ rimColor: uniform(new Color()),
448
+ rimStrength: uniform(0),
449
+ sunDirection: uniform(new Vector3(0, 1, 0))
450
+ });
451
+ //#endregion
452
+ //#region src/nodes/createSkyNode.ts
453
+ const HORIZON_BAND = .45;
454
+ const SUN_DISC_COSINE = Math.cos(.035);
455
+ const MOON_DISC_COSINE = Math.cos(.05);
456
+ const SUN_GLOW_POWER = 12;
457
+ const HORIZON_GLOW_POWER = 3;
458
+ const SUN_DISC_BRIGHTNESS = 6;
459
+ const CLOUD_SCALE = 1.6;
460
+ const CLOUD_MIN_HEIGHT = .04;
461
+ const CLOUD_EDGE = .04;
462
+ const CLOUD_SHADE_DEPTH = .18;
463
+ const STAR_SCALE = 420;
464
+ const STAR_THRESHOLD = .9975;
465
+ const createSkyNode = ({ cloudCoverage, cloudDrift, cloudLitColor, cloudShadeColor, horizonColor, lightColor, moonDirection, starIntensity, sunDirection, zenithColor }) => Fn(() => {
466
+ const direction = normalWorldGeometry.normalize();
467
+ const height = direction.y;
468
+ const gradient = mix(horizonColor, zenithColor, smoothstep(0, HORIZON_BAND, height));
469
+ const sunCosine = direction.dot(sunDirection);
470
+ const sunVisibility = smoothstep(-.1, .05, sunDirection.y);
471
+ const sunGlow = pow(max(sunCosine, 0), SUN_GLOW_POWER).mul(sunVisibility);
472
+ const horizonGlow = pow(max(sunCosine, 0), HORIZON_GLOW_POWER).mul(float(1).sub(smoothstep(0, .3, height.abs()))).mul(float(1).sub(smoothstep(0, .5, sunDirection.y))).mul(sunVisibility);
473
+ const sunDisc = smoothstep(SUN_DISC_COSINE - 5e-4, SUN_DISC_COSINE, sunCosine).mul(sunVisibility);
474
+ const moonDisc = smoothstep(MOON_DISC_COSINE - 5e-4, MOON_DISC_COSINE, direction.dot(moonDirection)).mul(smoothstep(-.05, .05, moonDirection.y));
475
+ const stars = smoothstep(STAR_THRESHOLD, 1, mx_cell_noise_float(direction.mul(STAR_SCALE))).mul(starIntensity).mul(smoothstep(0, .1, height));
476
+ const cloudPosition = direction.xz.div(max(height, CLOUD_MIN_HEIGHT)).mul(CLOUD_SCALE).add(cloudDrift);
477
+ const cloudNoise = mx_fractal_noise_float(vec3(cloudPosition, 0), 4, 2, .5).mul(.5).add(.5);
478
+ const cloudThreshold = float(1).sub(cloudCoverage);
479
+ const cloudDensity = smoothstep(cloudThreshold, cloudThreshold.add(CLOUD_EDGE), cloudNoise).mul(smoothstep(0, .12, height));
480
+ const cloudShade = smoothstep(cloudThreshold.add(CLOUD_SHADE_DEPTH), cloudThreshold.add(.22), cloudNoise);
481
+ const cloudColor = mix(cloudLitColor, cloudShadeColor, cloudShade).add(lightColor.mul(sunGlow.mul(.6)));
482
+ const sky = gradient.add(lightColor.mul(sunGlow.mul(.35).add(horizonGlow.mul(.45)))).add(vec3(stars)).add(vec3(.85, .88, 1).mul(moonDisc)).add(lightColor.mul(sunDisc.mul(SUN_DISC_BRIGHTNESS)));
483
+ return mix(sky, cloudColor, cloudDensity);
484
+ })();
485
+ //#endregion
486
+ //#region src/nodes/createWaterMaterial.ts
487
+ const RIPPLE_SCALE = .08;
488
+ const RIPPLE_SPEED = .05;
489
+ const REFRACTION_STRENGTH = .015;
490
+ const GLINT_POWER = 180;
491
+ const GLINT_BRIGHTNESS = 5;
492
+ const FOAM_SCALE = .6;
493
+ const REFLECTION_STRENGTH = .55;
494
+ const createWaterMaterial = ({ lightColor, sunDirection }, { horizonColor, zenithColor }, { deepColor, deepDepth, foamColor, foamDepth, shallowColor }) => {
495
+ const waterMaterial = new MeshBasicNodeMaterial({
496
+ side: DoubleSide,
497
+ transparent: true
498
+ });
499
+ const ripplePosition = positionWorld.xz.mul(RIPPLE_SCALE);
500
+ const drift = time.mul(RIPPLE_SPEED);
501
+ const sampleRipples = (offset) => mx_fractal_noise_float(vec3(ripplePosition.add(offset).add(vec2(drift, drift.mul(.6))), 0), 3).add(mx_fractal_noise_float(vec3(ripplePosition.add(offset).mul(1.9).sub(vec2(drift.mul(.8), drift)), 1), 2)).mul(.5);
502
+ const ripple = sampleRipples(vec2(0, 0));
503
+ const slopeX = sampleRipples(vec2(.02, 0)).sub(ripple);
504
+ const slopeZ = sampleRipples(vec2(0, .02)).sub(ripple);
505
+ const normal = vec3(slopeX.mul(-.35 / .02), 1, slopeZ.mul(-.35 / .02)).normalize();
506
+ const refractedUV = screenUV.add(normal.xz.mul(REFRACTION_STRENGTH));
507
+ const floorUV = perspectiveDepthToViewZ(viewportDepthTexture(refractedUV), cameraNear, cameraFar).greaterThan(positionView.z).select(screenUV, refractedUV);
508
+ const depth = positionView.z.sub(perspectiveDepthToViewZ(viewportDepthTexture(floorUV), cameraNear, cameraFar)).max(0);
509
+ const deepness = smoothstep(0, deepDepth, depth);
510
+ const waterColor = mix(shallowColor, deepColor, deepness);
511
+ const floorColor = viewportSharedTexture(floorUV).rgb;
512
+ const clarity = float(1).sub(smoothstep(0, deepDepth.mul(.6), depth));
513
+ const tinted = mix(waterColor, floorColor.mul(shallowColor.mul(1.4)), clarity.mul(.8));
514
+ const eyeDirection = cameraPosition.sub(positionWorld).normalize();
515
+ const fresnel = pow(float(1).sub(normal.dot(eyeDirection).abs().saturate()), 4);
516
+ const skyColor = mix(horizonColor, zenithColor, .35);
517
+ const reflected = mix(tinted, skyColor, fresnel.mul(REFLECTION_STRENGTH));
518
+ const highlight = pow(reflect(sunDirection.negate(), normal).dot(eyeDirection).max(0), GLINT_POWER);
519
+ const glints = lightColor.mul(smoothstep(.45, .5, highlight).mul(GLINT_BRIGHTNESS));
520
+ const shore = float(1).sub(smoothstep(0, foamDepth, depth));
521
+ const foamNoise = mx_fractal_noise_float(vec3(positionWorld.xz.mul(FOAM_SCALE), time.mul(.3)), 2).mul(.5).add(.5);
522
+ const foam = smoothstep(.55, .6, foamNoise.add(shore.mul(.5))).mul(shore);
523
+ waterMaterial.colorNode = mix(reflected, foamColor, foam).add(glints);
524
+ return waterMaterial;
525
+ };
526
+ //#endregion
527
+ //#region src/noise/createSimplexNoise.ts
528
+ const SKEW = (Math.sqrt(3) - 1) / 2;
529
+ const UNSKEW = (3 - Math.sqrt(3)) / 6;
530
+ const GRADIENTS_X = [
531
+ 1,
532
+ -1,
533
+ 1,
534
+ -1,
535
+ 1,
536
+ -1,
537
+ 0,
538
+ 0
539
+ ];
540
+ const GRADIENTS_Y = [
541
+ 1,
542
+ 1,
543
+ -1,
544
+ -1,
545
+ 0,
546
+ 0,
547
+ 1,
548
+ -1
549
+ ];
550
+ const NOISE_SCALE = 70;
551
+ const getCornerContribution = (gradientIndex, offsetX, offsetY) => {
552
+ const falloff = .5 - offsetX * offsetX - offsetY * offsetY;
553
+ if (falloff < 0) return 0;
554
+ const gradient = gradientIndex & 7;
555
+ return falloff ** 4 * ((GRADIENTS_X[gradient] ?? 0) * offsetX + (GRADIENTS_Y[gradient] ?? 0) * offsetY);
556
+ };
557
+ const createSimplexNoise = (seed) => {
558
+ const random = createSeededRandom(seed);
559
+ const table = Uint8Array.from({ length: 256 }, (_value, index) => index);
560
+ for (let index = table.length - 1; index > 0; index--) {
561
+ const swapIndex = Math.floor(random() * (index + 1));
562
+ [table[index], table[swapIndex]] = [table[swapIndex] ?? 0, table[index] ?? 0];
563
+ }
564
+ const permutation = Uint8Array.from({ length: 512 }, (_value, index) => table[index & 255] ?? 0);
565
+ return (x, y) => {
566
+ const skew = (x + y) * SKEW;
567
+ const cellX = Math.floor(x + skew);
568
+ const cellY = Math.floor(y + skew);
569
+ const unskew = (cellX + cellY) * UNSKEW;
570
+ const offsetX = x - cellX + unskew;
571
+ const offsetY = y - cellY + unskew;
572
+ const middleX = offsetX > offsetY ? 1 : 0;
573
+ const middleY = 1 - middleX;
574
+ const tableX = cellX & 255;
575
+ const tableY = cellY & 255;
576
+ 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));
577
+ };
578
+ };
579
+ //#endregion
580
+ //#region src/post/computeGradeLut.ts
581
+ const MAX_BYTE = 255;
582
+ const RED_LUMINANCE = .2126;
583
+ const GREEN_LUMINANCE = .7152;
584
+ const BLUE_LUMINANCE = .0722;
585
+ const CONTRAST_PIVOT = .5;
586
+ const toGradedByte = (channel, luminance, saturation, contrast, tint) => {
587
+ const graded = (luminance + (channel - luminance) * saturation - CONTRAST_PIVOT) * contrast + CONTRAST_PIVOT + tint;
588
+ return Math.round(Math.min(Math.max(graded, 0), 1) * MAX_BYTE);
589
+ };
590
+ const computeGradeLut = ({ contrast, highlightTint, saturation, shadowTint, size }) => {
591
+ const values = new Uint8Array(size * size * size * 4);
592
+ const step = 1 / (size - 1);
593
+ let offset = 0;
594
+ for (let blueIndex = 0; blueIndex < size; blueIndex++) for (let greenIndex = 0; greenIndex < size; greenIndex++) for (let redIndex = 0; redIndex < size; redIndex++) {
595
+ const red = redIndex * step;
596
+ const green = greenIndex * step;
597
+ const blue = blueIndex * step;
598
+ const luminance = red * RED_LUMINANCE + green * GREEN_LUMINANCE + blue * BLUE_LUMINANCE;
599
+ const shadowWeight = (1 - luminance) * (1 - luminance);
600
+ const highlightWeight = luminance * luminance;
601
+ const redTint = shadowTint[0] * shadowWeight + highlightTint[0] * highlightWeight;
602
+ const greenTint = shadowTint[1] * shadowWeight + highlightTint[1] * highlightWeight;
603
+ const blueTint = shadowTint[2] * shadowWeight + highlightTint[2] * highlightWeight;
604
+ values[offset] = toGradedByte(red, luminance, saturation, contrast, redTint);
605
+ values[offset + 1] = toGradedByte(green, luminance, saturation, contrast, greenTint);
606
+ values[offset + 2] = toGradedByte(blue, luminance, saturation, contrast, blueTint);
607
+ values[offset + 3] = MAX_BYTE;
608
+ offset += 4;
609
+ }
610
+ return values;
611
+ };
612
+ //#endregion
613
+ //#region src/post/createFogUniforms.ts
614
+ const createFogUniforms = () => ({
615
+ baseHeight: uniform(0),
616
+ color: uniform(new Color()),
617
+ density: uniform(0),
618
+ heightFalloff: uniform(0),
619
+ startDistance: uniform(0)
620
+ });
621
+ //#endregion
622
+ //#region src/post/createGodraysLight.ts
623
+ const createGodraysLight = (shadowMapSize, halfExtent) => {
624
+ const godraysLight = new DirectionalLight(16777215, 0);
625
+ godraysLight.castShadow = true;
626
+ godraysLight.shadow.autoUpdate = false;
627
+ godraysLight.shadow.needsUpdate = true;
628
+ godraysLight.shadow.mapSize.set(shadowMapSize, shadowMapSize);
629
+ const { camera } = godraysLight.shadow;
630
+ camera.left = -halfExtent;
631
+ camera.right = halfExtent;
632
+ camera.top = halfExtent;
633
+ camera.bottom = -halfExtent;
634
+ camera.far = halfExtent * 4;
635
+ return godraysLight;
636
+ };
637
+ //#endregion
638
+ //#region src/post/createGradeLutTexture.ts
639
+ const createGradeLutTexture = (gradeOptions) => {
640
+ const { size } = gradeOptions;
641
+ const gradeLutTexture = new Data3DTexture(computeGradeLut(gradeOptions), size, size, size);
642
+ gradeLutTexture.magFilter = LinearFilter;
643
+ gradeLutTexture.minFilter = LinearFilter;
644
+ gradeLutTexture.needsUpdate = true;
645
+ return gradeLutTexture;
646
+ };
647
+ //#endregion
648
+ //#region src/post/createHeightFogNode.ts
649
+ const MIN_RAY_CLIMB = 1e-6;
650
+ const createHeightFogNode = (colorNode, depthNode, camera, { baseHeight, color, density, heightFalloff, startDistance }) => {
651
+ const cameraWorldMatrix = uniform(camera.matrixWorld);
652
+ const cameraProjectionMatrixInverse = uniform(camera.projectionMatrixInverse);
653
+ return Fn(() => {
654
+ const depth = depthNode.sample(uv()).r;
655
+ const output = colorNode.toVar();
656
+ If(depth.lessThan(1), () => {
657
+ const viewPosition = getViewPosition(uv(), depth, cameraProjectionMatrixInverse);
658
+ const eye = cameraWorldMatrix.mul(vec4(0, 0, 0, 1)).xyz;
659
+ const ray = cameraWorldMatrix.mul(vec4(viewPosition, 1)).xyz.sub(eye);
660
+ const rayLength = ray.length();
661
+ const fogLength = max(rayLength.sub(startDistance), 0);
662
+ const slope = ray.y.div(rayLength);
663
+ const startHeight = eye.y.add(slope.mul(rayLength.min(startDistance)));
664
+ const densityAtStart = density.mul(exp(startHeight.sub(baseHeight).mul(heightFalloff).negate()));
665
+ const climb = slope.mul(heightFalloff);
666
+ const opticalDepth = densityAtStart.mul(fogLength).toVar();
667
+ If(climb.abs().greaterThan(MIN_RAY_CLIMB), () => {
668
+ opticalDepth.assign(densityAtStart.mul(float(1).sub(exp(fogLength.mul(climb).negate()))).div(climb));
669
+ });
670
+ const opacity = float(1).sub(exp(opticalDepth.negate()));
671
+ output.assign(vec4(mix(output.rgb, color, opacity), output.a));
672
+ });
673
+ return output;
674
+ })();
675
+ };
676
+ //#endregion
677
+ //#region src/renderer/AntialiasingMode.ts
678
+ let AntialiasingMode = /* @__PURE__ */ function(AntialiasingMode) {
679
+ AntialiasingMode["Smaa"] = "Smaa";
680
+ AntialiasingMode["Traa"] = "Traa";
681
+ return AntialiasingMode;
682
+ }({});
683
+ //#endregion
684
+ //#region src/post/createPostPipeline.ts
685
+ const BLOOM_STRENGTH = .35;
686
+ const BLOOM_RADIUS = .4;
687
+ const BLOOM_THRESHOLD = .85;
688
+ const MIN_VIEW_DISTANCE = .001;
689
+ const createPostPipeline = ({ camera, fogUniforms, godraysLight, gradeLutTexture, postUniforms: { godraysColor, gradeIntensity, outlineColor, outlineFadeDistance, outlineThickness }, qualityTierSettings: { antialiasingMode, godraysStepCount, isBloomEnabled }, renderer, scene }) => {
690
+ const renderPipeline = new RenderPipeline(renderer);
691
+ renderPipeline.outputColorTransform = false;
692
+ const viewDistance = modelViewMatrix.mul(vec4(positionLocal, 1)).z.negate().max(MIN_VIEW_DISTANCE);
693
+ const thicknessNode = outlineThickness.mul(outlineFadeDistance.div(viewDistance).min(1));
694
+ const scenePass = new ToonOutlinePassNode(scene, camera, outlineColor, thicknessNode, float(1));
695
+ const isTraa = antialiasingMode === "Traa";
696
+ if (isTraa) scenePass.setMRT(mrt({
697
+ output,
698
+ velocity
699
+ }));
700
+ const sceneColor = scenePass.getTextureNode("output");
701
+ const sceneDepth = scenePass.getTextureNode("depth");
702
+ const postPipeline = { renderPipeline };
703
+ let litNode = sceneColor;
704
+ if (godraysStepCount > 0) {
705
+ const godraysNode = godrays(sceneDepth, camera, godraysLight);
706
+ godraysNode.raymarchSteps.value = godraysStepCount;
707
+ const blurredGodrays = bilateralBlur(godraysNode.getTextureNode());
708
+ litNode = depthAwareBlend(sceneColor, blurredGodrays.getTextureNode(), sceneDepth, camera, { blendColor: godraysColor });
709
+ postPipeline.godraysNode = godraysNode;
710
+ }
711
+ const foggedNode = createHeightFogNode(litNode, sceneDepth, camera, fogUniforms);
712
+ let bloomedNode = foggedNode;
713
+ if (isBloomEnabled) {
714
+ const bloomNode = bloom(foggedNode, BLOOM_STRENGTH, BLOOM_RADIUS, BLOOM_THRESHOLD);
715
+ bloomedNode = foggedNode.add(bloomNode);
716
+ postPipeline.bloomNode = bloomNode;
717
+ }
718
+ const resolvedNode = isTraa ? traa(bloomedNode, sceneDepth, scenePass.getTextureNode("velocity"), camera) : bloomedNode;
719
+ const displayNode = renderOutput(resolvedNode);
720
+ const gradedNode = lut3D(displayNode, texture3D(gradeLutTexture), gradeLutTexture.image.width, gradeIntensity);
721
+ renderPipeline.outputNode = isTraa ? gradedNode : smaa(gradedNode);
722
+ return postPipeline;
723
+ };
724
+ //#endregion
725
+ //#region src/post/createPostUniforms.ts
726
+ const createPostUniforms = () => ({
727
+ godraysColor: uniform(new Color(16773590)),
728
+ gradeIntensity: uniform(1),
729
+ outlineColor: uniform(new Color(2761272)),
730
+ outlineFadeDistance: uniform(40),
731
+ outlineThickness: uniform(.003)
732
+ });
733
+ //#endregion
734
+ //#region src/renderer/QualityTier.ts
735
+ let QualityTier = /* @__PURE__ */ function(QualityTier) {
736
+ QualityTier["High"] = "High";
737
+ QualityTier["Low"] = "Low";
738
+ QualityTier["Medium"] = "Medium";
739
+ return QualityTier;
740
+ }({});
741
+ //#endregion
742
+ //#region src/renderer/QualityTierSettingsMap.ts
743
+ const QualityTierSettingsMap = {
744
+ ["High"]: {
745
+ antialiasingMode: "Traa",
746
+ cascadeCount: 4,
747
+ godraysStepCount: 60,
748
+ grassDensity: 1,
749
+ isBloomEnabled: true,
750
+ maxPixelRatio: 2,
751
+ shadowMapSize: 2048
752
+ },
753
+ ["Low"]: {
754
+ antialiasingMode: "Smaa",
755
+ cascadeCount: 2,
756
+ godraysStepCount: 0,
757
+ grassDensity: .3,
758
+ isBloomEnabled: false,
759
+ maxPixelRatio: 1,
760
+ shadowMapSize: 1024
761
+ },
762
+ ["Medium"]: {
763
+ antialiasingMode: "Smaa",
764
+ cascadeCount: 3,
765
+ godraysStepCount: 30,
766
+ grassDensity: .6,
767
+ isBloomEnabled: true,
768
+ maxPixelRatio: 1.5,
769
+ shadowMapSize: 1024
770
+ }
771
+ };
772
+ //#endregion
773
+ //#region src/terrain/constants.ts
774
+ const TILE_INDEX_OFFSET = 2 ** 19;
775
+ const TILE_INDEX_SPAN = 2 ** 20;
776
+ const TERRAIN_LAYER = 1;
777
+ //#endregion
778
+ //#region src/terrain/getTerrainTileLevel.ts
779
+ const getTerrainTileLevel = (key) => Math.floor(key / TILE_INDEX_SPAN / TILE_INDEX_SPAN);
780
+ //#endregion
781
+ //#region src/streaming/createTileStreamer.ts
782
+ const createTileStreamer = ({ disposeTile, maxCachedCount, maxPendingCount, requestTile }) => {
783
+ const cachedTileMap = /* @__PURE__ */ new Map();
784
+ const pendingWantedFrameMap = /* @__PURE__ */ new Map();
785
+ let frame = 0;
786
+ let isDisposed = false;
787
+ const evictStale = () => {
788
+ while (cachedTileMap.size > maxCachedCount) {
789
+ let staleKey;
790
+ let staleFrame = frame;
791
+ for (const [key, { lastWantedFrame }] of cachedTileMap) if (lastWantedFrame < staleFrame) {
792
+ staleKey = key;
793
+ staleFrame = lastWantedFrame;
794
+ }
795
+ if (staleKey === void 0) return;
796
+ const staleTile = cachedTileMap.get(staleKey);
797
+ cachedTileMap.delete(staleKey);
798
+ if (staleTile) disposeTile(staleTile.tile);
799
+ }
800
+ };
801
+ return {
802
+ dispose: () => {
803
+ isDisposed = true;
804
+ for (const { tile } of cachedTileMap.values()) disposeTile(tile);
805
+ cachedTileMap.clear();
806
+ pendingWantedFrameMap.clear();
807
+ },
808
+ get: (key) => cachedTileMap.get(key)?.tile,
809
+ has: (key) => cachedTileMap.has(key),
810
+ receive: (key, tile) => {
811
+ const lastWantedFrame = pendingWantedFrameMap.get(key) ?? frame;
812
+ pendingWantedFrameMap.delete(key);
813
+ if (isDisposed) {
814
+ disposeTile(tile);
815
+ return;
816
+ }
817
+ cachedTileMap.set(key, {
818
+ lastWantedFrame,
819
+ tile
820
+ });
821
+ evictStale();
822
+ },
823
+ update: ({ count, keys }, drawn) => {
824
+ frame++;
825
+ let coarsestLevel = 0;
826
+ for (let index = 0; index < count; index++) {
827
+ const key = keys[index] ?? 0;
828
+ const cachedTile = cachedTileMap.get(key);
829
+ if (cachedTile) cachedTile.lastWantedFrame = frame;
830
+ else {
831
+ if (pendingWantedFrameMap.has(key)) pendingWantedFrameMap.set(key, frame);
832
+ coarsestLevel = Math.max(coarsestLevel, getTerrainTileLevel(key));
833
+ }
834
+ }
835
+ for (let index = 0; index < drawn.count; index++) {
836
+ const cachedTile = cachedTileMap.get(drawn.keys[index] ?? 0);
837
+ if (cachedTile) cachedTile.lastWantedFrame = frame;
838
+ }
839
+ for (let level = coarsestLevel; level >= 0 && pendingWantedFrameMap.size < maxPendingCount; level--) for (let index = 0; index < count && pendingWantedFrameMap.size < maxPendingCount; index++) {
840
+ const key = keys[index] ?? 0;
841
+ if (getTerrainTileLevel(key) !== level || cachedTileMap.has(key) || pendingWantedFrameMap.has(key)) continue;
842
+ pendingWantedFrameMap.set(key, frame);
843
+ requestTile(key);
844
+ }
845
+ }
846
+ };
847
+ };
848
+ //#endregion
849
+ //#region src/terrain/computeTerrainIndices.ts
850
+ const MAX_UINT16_VERTEX_COUNT = 2 ** 16;
851
+ const computeTerrainIndices = (cellsPerSide) => {
852
+ const side = cellsPerSide + 1;
853
+ const indexCount = cellsPerSide * cellsPerSide * 6;
854
+ const indices = side * side > MAX_UINT16_VERTEX_COUNT ? new Uint32Array(indexCount) : new Uint16Array(indexCount);
855
+ let cursor = 0;
856
+ for (let row = 0; row < cellsPerSide; row++) for (let column = 0; column < cellsPerSide; column++) {
857
+ const topLeft = row * side + column;
858
+ const bottomLeft = topLeft + side;
859
+ indices[cursor++] = topLeft;
860
+ indices[cursor++] = bottomLeft;
861
+ indices[cursor++] = topLeft + 1;
862
+ indices[cursor++] = topLeft + 1;
863
+ indices[cursor++] = bottomLeft;
864
+ indices[cursor++] = bottomLeft + 1;
865
+ }
866
+ return indices;
867
+ };
868
+ //#endregion
869
+ //#region src/terrain/getTerrainTileColumn.ts
870
+ const getTerrainTileColumn = (key) => key % TILE_INDEX_SPAN - TILE_INDEX_OFFSET;
871
+ //#endregion
872
+ //#region src/terrain/getTerrainTileRow.ts
873
+ const getTerrainTileRow = (key) => Math.floor(key / TILE_INDEX_SPAN) % TILE_INDEX_SPAN - TILE_INDEX_OFFSET;
874
+ //#endregion
875
+ //#region src/terrain/computeTerrainTile.ts
876
+ const computeTerrainTile = ({ cellsPerSide, finestTileSize, getHeight, key, writeColor }) => {
877
+ const level = getTerrainTileLevel(key);
878
+ const size = finestTileSize * 2 ** level;
879
+ const step = size / cellsPerSide;
880
+ const left = getTerrainTileColumn(key) * size;
881
+ const back = getTerrainTileRow(key) * size;
882
+ const side = cellsPerSide + 1;
883
+ const paddedSide = side + 2;
884
+ const heights = new Float32Array(paddedSide * paddedSide);
885
+ for (let row = 0; row < paddedSide; row++) for (let column = 0; column < paddedSide; column++) heights[row * paddedSide + column] = getHeight(left + (column - 1) * step, back + (row - 1) * step);
886
+ const vertexCount = side * side;
887
+ const positions = new Float32Array(vertexCount * 3);
888
+ const normals = new Float32Array(vertexCount * 3);
889
+ const colors = new Float32Array(vertexCount * 3);
890
+ const coarsePositions = new Float32Array(vertexCount * 4);
891
+ for (let row = 0; row < side; row++) for (let column = 0; column < side; column++) {
892
+ const paddedIndex = (row + 1) * paddedSide + column + 1;
893
+ const height = heights[paddedIndex] ?? 0;
894
+ const normalX = (heights[paddedIndex - 1] ?? height) - (heights[paddedIndex + 1] ?? height);
895
+ const normalY = 2 * step;
896
+ const normalZ = (heights[paddedIndex - paddedSide] ?? height) - (heights[paddedIndex + paddedSide] ?? height);
897
+ const length = Math.hypot(normalX, normalY, normalZ);
898
+ const index = row * side + column;
899
+ const offset = index * 3;
900
+ const x = column * step;
901
+ const z = row * step;
902
+ positions[offset] = x;
903
+ positions[offset + 1] = height;
904
+ positions[offset + 2] = z;
905
+ normals[offset] = normalX / length;
906
+ normals[offset + 1] = normalY / length;
907
+ normals[offset + 2] = normalZ / length;
908
+ writeColor(colors, offset, height, 1 - normalY / length, left + x, back + z);
909
+ const coarseColumn = column - column % 2;
910
+ const coarseRow = row - row % 2;
911
+ const coarseOffset = index * 4;
912
+ coarsePositions[coarseOffset] = coarseColumn * step;
913
+ coarsePositions[coarseOffset + 1] = heights[(coarseRow + 1) * paddedSide + coarseColumn + 1] ?? height;
914
+ coarsePositions[coarseOffset + 2] = coarseRow * step;
915
+ coarsePositions[coarseOffset + 3] = level;
916
+ }
917
+ return {
918
+ coarsePositions,
919
+ colors,
920
+ key,
921
+ normals,
922
+ positions
923
+ };
924
+ };
925
+ //#endregion
926
+ //#region src/terrain/createTerrainMaterial.ts
927
+ const createTerrainMaterial = ({ finestRange, morphShare }, toonMaterialOptions, waterUniforms) => {
928
+ const terrainMaterial = createToonMaterial({
929
+ ...toonMaterialOptions,
930
+ isOutlined: false,
931
+ isVertexColors: true
932
+ });
933
+ const coarsePosition = attribute("coarsePosition", "vec4");
934
+ const morphEnd = exp2(coarsePosition.w).mul(finestRange);
935
+ const morphStart = morphEnd.mul(1 - morphShare);
936
+ const morphAmount = modelWorldMatrix.mul(vec4(positionLocal, 1)).xyz.distance(cameraPosition).sub(morphStart).div(morphEnd.sub(morphStart)).saturate();
937
+ terrainMaterial.positionNode = mix(positionLocal, coarsePosition.xyz, morphAmount);
938
+ if (waterUniforms) {
939
+ const { lightUniforms } = toonMaterialOptions;
940
+ terrainMaterial.emissiveNode = createRimNode(lightUniforms).add(createCausticsNode(lightUniforms, waterUniforms));
941
+ }
942
+ return terrainMaterial;
943
+ };
944
+ //#endregion
945
+ //#region src/terrain/createTerrainSelection.ts
946
+ const createTerrainSelection = (capacity) => ({
947
+ count: 0,
948
+ keys: new Float64Array(capacity)
949
+ });
950
+ //#endregion
951
+ //#region src/terrain/createTerrainTileGeometry.ts
952
+ const createTerrainTileGeometry = ({ coarsePositions, colors, normals, positions }, index) => {
953
+ const geometry = new BufferGeometry();
954
+ geometry.setAttribute("position", new BufferAttribute(positions, 3));
955
+ geometry.setAttribute("normal", new BufferAttribute(normals, 3));
956
+ geometry.setAttribute("color", new BufferAttribute(colors, 3));
957
+ geometry.setAttribute("coarsePosition", new BufferAttribute(coarsePositions, 4));
958
+ geometry.setIndex(index);
959
+ geometry.computeBoundingBox();
960
+ geometry.computeBoundingSphere();
961
+ return geometry;
962
+ };
963
+ //#endregion
964
+ //#region src/terrain/getTerrainTileKey.ts
965
+ const getTerrainTileKey = (level, column, row) => (level * TILE_INDEX_SPAN + row + TILE_INDEX_OFFSET) * TILE_INDEX_SPAN + column + TILE_INDEX_OFFSET;
966
+ //#endregion
967
+ //#region src/terrain/getParentTerrainTileKey.ts
968
+ const getParentTerrainTileKey = (key) => {
969
+ const level = getTerrainTileLevel(key);
970
+ const column = getTerrainTileColumn(key);
971
+ const row = getTerrainTileRow(key);
972
+ return getTerrainTileKey(level + 1, Math.floor(column / 2), Math.floor(row / 2));
973
+ };
974
+ //#endregion
975
+ //#region src/terrain/resolveTerrainDraws.ts
976
+ const fallbackKeys = /* @__PURE__ */ new Set();
977
+ const checkFallbackAncestor = (key, levelCount) => {
978
+ if (fallbackKeys.size === 0) return false;
979
+ let ancestorKey = key;
980
+ for (let level = getTerrainTileLevel(key) + 1; level < levelCount; level++) {
981
+ ancestorKey = getParentTerrainTileKey(ancestorKey);
982
+ if (fallbackKeys.has(ancestorKey)) return true;
983
+ }
984
+ return false;
985
+ };
986
+ const resolveTerrainDraws = ({ levelCount }, wanted, checkTileLoaded, draws) => {
987
+ fallbackKeys.clear();
988
+ for (let index = 0; index < wanted.count; index++) {
989
+ let key = wanted.keys[index] ?? 0;
990
+ if (checkTileLoaded(key)) continue;
991
+ for (let level = getTerrainTileLevel(key) + 1; level < levelCount; level++) {
992
+ key = getParentTerrainTileKey(key);
993
+ if (!checkTileLoaded(key)) continue;
994
+ fallbackKeys.add(key);
995
+ break;
996
+ }
997
+ }
998
+ draws.count = 0;
999
+ for (let index = 0; index < wanted.count && draws.count < draws.keys.length; index++) {
1000
+ const key = wanted.keys[index] ?? 0;
1001
+ if (!checkTileLoaded(key) || checkFallbackAncestor(key, levelCount)) continue;
1002
+ draws.keys[draws.count] = key;
1003
+ draws.count++;
1004
+ }
1005
+ for (const key of fallbackKeys) {
1006
+ if (draws.count === draws.keys.length || checkFallbackAncestor(key, levelCount)) continue;
1007
+ draws.keys[draws.count] = key;
1008
+ draws.count++;
1009
+ }
1010
+ return draws;
1011
+ };
1012
+ //#endregion
1013
+ //#region src/terrain/selectTerrainTiles.ts
1014
+ const tileBox = new Box3();
1015
+ const addKey = (selection, key) => {
1016
+ if (selection.count === selection.keys.length) return;
1017
+ selection.keys[selection.count] = key;
1018
+ selection.count++;
1019
+ };
1020
+ const selectTile = (terrainOptions, eye, frustum, selection, level, column, row) => {
1021
+ const { finestRange, finestTileSize, maxHeight, minHeight } = terrainOptions;
1022
+ const size = finestTileSize * 2 ** level;
1023
+ const left = column * size;
1024
+ const back = row * size;
1025
+ const distanceX = Math.max(left - eye.x, 0, eye.x - left - size);
1026
+ const distanceY = Math.max(minHeight - eye.y, 0, eye.y - maxHeight);
1027
+ const distanceZ = Math.max(back - eye.z, 0, eye.z - back - size);
1028
+ const distance = Math.hypot(distanceX, distanceY, distanceZ);
1029
+ const range = finestRange * 2 ** level;
1030
+ if (distance > range) return false;
1031
+ if (frustum) {
1032
+ tileBox.min.set(left, minHeight, back);
1033
+ tileBox.max.set(left + size, maxHeight, back + size);
1034
+ if (!frustum.intersectsBox(tileBox)) return true;
1035
+ }
1036
+ if (level === 0 || distance > range / 2) {
1037
+ addKey(selection, getTerrainTileKey(level, column, row));
1038
+ return true;
1039
+ }
1040
+ for (let childRow = row * 2; childRow < row * 2 + 2; childRow++) for (let childColumn = column * 2; childColumn < column * 2 + 2; childColumn++) if (!selectTile(terrainOptions, eye, frustum, selection, level - 1, childColumn, childRow)) addKey(selection, getTerrainTileKey(level - 1, childColumn, childRow));
1041
+ return true;
1042
+ };
1043
+ const selectTerrainTiles = (terrainOptions, eye, frustum, selection) => {
1044
+ const { finestRange, finestTileSize, levelCount } = terrainOptions;
1045
+ const level = levelCount - 1;
1046
+ const size = finestTileSize * 2 ** level;
1047
+ const range = finestRange * 2 ** level;
1048
+ selection.count = 0;
1049
+ for (let row = Math.floor((eye.z - range) / size); row <= Math.floor((eye.z + range) / size); row++) for (let column = Math.floor((eye.x - range) / size); column <= Math.floor((eye.x + range) / size); column++) selectTile(terrainOptions, eye, frustum, selection, level, column, row);
1050
+ return selection;
1051
+ };
1052
+ //#endregion
1053
+ //#region src/vegetation/computeGrassBlade.ts
1054
+ const computeGrassBlade = (segmentCount) => {
1055
+ const positions = new Float32Array((segmentCount * 2 + 1) * 3);
1056
+ for (let segment = 0; segment < segmentCount; segment++) {
1057
+ const height = segment / segmentCount;
1058
+ const halfWidth = (1 - height) / 2;
1059
+ const offset = segment * 6;
1060
+ positions[offset] = -halfWidth;
1061
+ positions[offset + 1] = height;
1062
+ positions[offset + 3] = halfWidth;
1063
+ positions[offset + 4] = height;
1064
+ }
1065
+ positions[segmentCount * 6 + 1] = 1;
1066
+ const indices = new Uint16Array((segmentCount - 1) * 6 + 3);
1067
+ let cursor = 0;
1068
+ for (let segment = 0; segment < segmentCount - 1; segment++) {
1069
+ const left = segment * 2;
1070
+ indices[cursor++] = left;
1071
+ indices[cursor++] = left + 1;
1072
+ indices[cursor++] = left + 2;
1073
+ indices[cursor++] = left + 2;
1074
+ indices[cursor++] = left + 1;
1075
+ indices[cursor++] = left + 3;
1076
+ }
1077
+ const lastLeft = (segmentCount - 1) * 2;
1078
+ indices[cursor++] = lastLeft;
1079
+ indices[cursor++] = lastLeft + 1;
1080
+ indices[cursor] = segmentCount * 2;
1081
+ return {
1082
+ indices,
1083
+ positions
1084
+ };
1085
+ };
1086
+ //#endregion
1087
+ //#region src/vegetation/createGrassGeometry.ts
1088
+ const createGrassGeometry = (segmentCount, bladeCount) => {
1089
+ const { indices, positions } = computeGrassBlade(segmentCount);
1090
+ const grassGeometry = new InstancedBufferGeometry();
1091
+ grassGeometry.setAttribute("position", new BufferAttribute(positions, 3));
1092
+ grassGeometry.setIndex(new BufferAttribute(indices, 1));
1093
+ grassGeometry.instanceCount = bladeCount;
1094
+ return grassGeometry;
1095
+ };
1096
+ //#endregion
1097
+ //#region src/vegetation/createGrassMaterial.ts
1098
+ const MIN_GREENNESS = .05;
1099
+ const BEND_PER_WIND = .9;
1100
+ const createGrassMaterial = ({ bladeHeight, bladeWidth, cameraGround, density, groundCapture, lightUniforms, rampTexture, ring, waterUniforms, windUniforms }) => {
1101
+ const grassMaterial = createToonMaterial({
1102
+ isOutlined: false,
1103
+ lightUniforms,
1104
+ rampTexture
1105
+ });
1106
+ grassMaterial.emissiveNode = null;
1107
+ grassMaterial.side = DoubleSide;
1108
+ const { cellsPerSide, fadeEnd, fadeStart, innerRadius, scale, spacing } = ring;
1109
+ const index = float(instanceIndex);
1110
+ const row = index.div(cellsPerSide).floor();
1111
+ const column = index.sub(row.mul(cellsPerSide));
1112
+ const gridOrigin = cameraGround.div(spacing).floor().mul(spacing);
1113
+ const cell = vec2(column, row).sub(cellsPerSide / 2).mul(spacing).add(gridOrigin);
1114
+ const seed = cell.x.mul(127.1).add(cell.y.mul(311.7));
1115
+ const ground = cell.add(vec2(hash(seed), hash(seed.add(1))).sub(.5).mul(spacing));
1116
+ const captureUV = vec2(ground.x.sub(groundCapture.center.x).div(groundCapture.size).add(.5), float(.5).sub(ground.y.sub(groundCapture.center.y).div(groundCapture.size)));
1117
+ const captured = texture(groundCapture.renderTarget.texture, captureUV).level(float(0));
1118
+ const isInsideCapture = step(0, captureUV.x).mul(step(captureUV.x, 1)).mul(step(0, captureUV.y)).mul(step(captureUV.y, 1));
1119
+ const greenness = captured.g.sub(captured.r.max(captured.b));
1120
+ const isGrown = isInsideCapture.mul(step(MIN_GREENNESS, greenness)).mul(step(waterUniforms.level, captured.a)).mul(step(hash(seed.add(2)), density));
1121
+ const distance = ground.distance(cameraGround);
1122
+ const fade = smoothstep(innerRadius, innerRadius + spacing * 4, distance).mul(float(1).sub(smoothstep(fadeStart, fadeEnd, distance)));
1123
+ const size = hash(seed.add(3)).mul(.5).add(.75).mul(fade).mul(isGrown).mul(scale);
1124
+ const angle = hash(seed.add(4)).mul(Math.PI * 2);
1125
+ const across = positionGeometry.x.mul(bladeWidth).mul(size);
1126
+ const up = positionGeometry.y;
1127
+ const wind = createWindNode(windUniforms, ground).mul(BEND_PER_WIND).mul(up.mul(up));
1128
+ const bladeHeightNode = up.mul(bladeHeight).mul(size).sub(wind.length().mul(bladeHeight).mul(size).mul(.3));
1129
+ grassMaterial.positionNode = vec3(ground.x.add(across.mul(cos(angle))).add(wind.x.mul(bladeHeight).mul(size)), captured.a.add(bladeHeightNode), ground.y.add(across.mul(sin(angle))).add(wind.y.mul(bladeHeight).mul(size)));
1130
+ grassMaterial.normalNode = cameraViewMatrix.mul(vec4(0, 1, 0, 0)).xyz;
1131
+ const groundColor = varying(captured.rgb);
1132
+ grassMaterial.colorNode = mix(groundColor.mul(.72), groundColor.mul(1.12).add(vec3(.03, .03, 0)), up);
1133
+ return grassMaterial;
1134
+ };
1135
+ //#endregion
1136
+ //#region src/vegetation/createGroundCapture.ts
1137
+ const createGroundCapture = (size, resolution) => {
1138
+ const renderTarget = new RenderTarget(resolution, resolution, { type: HalfFloatType });
1139
+ renderTarget.texture.magFilter = LinearFilter;
1140
+ renderTarget.texture.minFilter = LinearFilter;
1141
+ const halfSize = size / 2;
1142
+ const camera = new OrthographicCamera(-halfSize, halfSize, halfSize, -halfSize);
1143
+ camera.rotation.x = -Math.PI / 2;
1144
+ camera.layers.set(1);
1145
+ const material = new MeshBasicNodeMaterial();
1146
+ material.outputNode = vec4(attribute("color", "vec3"), positionWorld.y);
1147
+ return {
1148
+ camera,
1149
+ center: uniform(new Vector2()),
1150
+ material,
1151
+ renderTarget,
1152
+ size
1153
+ };
1154
+ };
1155
+ //#endregion
1156
+ //#region src/vegetation/renderGroundCapture.ts
1157
+ const clearColor = new Color();
1158
+ const previousClearColor = new Color();
1159
+ const renderGroundCapture = (renderer, scene, { camera, center, material, renderTarget }, { maxHeight }, worldCenter, origin) => {
1160
+ center.value.set(worldCenter.x, worldCenter.z);
1161
+ camera.position.set(worldCenter.x - origin.x, maxHeight + 1, worldCenter.z - origin.z);
1162
+ camera.far = maxHeight * 4;
1163
+ camera.updateProjectionMatrix();
1164
+ camera.updateMatrixWorld();
1165
+ const previousRenderTarget = renderer.getRenderTarget();
1166
+ const previousClearAlpha = renderer.getClearAlpha();
1167
+ renderer.getClearColor(previousClearColor);
1168
+ const previousOverrideMaterial = scene.overrideMaterial;
1169
+ const previousBackgroundNode = scene.backgroundNode;
1170
+ scene.overrideMaterial = material;
1171
+ scene.backgroundNode = null;
1172
+ renderer.setClearColor(clearColor, 0);
1173
+ renderer.setRenderTarget(renderTarget);
1174
+ renderer.clear();
1175
+ renderer.render(scene, camera);
1176
+ renderer.setRenderTarget(previousRenderTarget);
1177
+ renderer.setClearColor(previousClearColor, previousClearAlpha);
1178
+ scene.overrideMaterial = previousOverrideMaterial;
1179
+ scene.backgroundNode = previousBackgroundNode;
1180
+ };
1181
+ //#endregion
1182
+ //#region src/water/createUnderwaterFogState.ts
1183
+ const createUnderwaterFogState = () => ({
1184
+ aboveDensity: 0,
1185
+ aboveHeightFalloff: 0,
1186
+ aboveStartDistance: 0,
1187
+ isUnderwater: false
1188
+ });
1189
+ //#endregion
1190
+ //#region src/water/createWaterUniforms.ts
1191
+ const createWaterUniforms = () => ({
1192
+ causticStrength: uniform(0),
1193
+ deepColor: uniform(new Color()),
1194
+ deepDepth: uniform(1),
1195
+ foamColor: uniform(new Color(16777215)),
1196
+ foamDepth: uniform(0),
1197
+ level: uniform(0),
1198
+ shallowColor: uniform(new Color()),
1199
+ underwaterFogColor: uniform(new Color()),
1200
+ underwaterFogDensity: uniform(0)
1201
+ });
1202
+ //#endregion
1203
+ //#region src/water/updateUnderwaterFog.ts
1204
+ const updateUnderwaterFog = (eyeHeight, { level, underwaterFogColor, underwaterFogDensity }, { color, density, heightFalloff, startDistance }, underwaterFogState) => {
1205
+ const isUnderwater = eyeHeight < level.value;
1206
+ if (isUnderwater && !underwaterFogState.isUnderwater) {
1207
+ underwaterFogState.aboveDensity = density.value;
1208
+ underwaterFogState.aboveHeightFalloff = heightFalloff.value;
1209
+ underwaterFogState.aboveStartDistance = startDistance.value;
1210
+ density.value = underwaterFogDensity.value;
1211
+ heightFalloff.value = 0;
1212
+ startDistance.value = 0;
1213
+ } else if (!isUnderwater && underwaterFogState.isUnderwater) {
1214
+ density.value = underwaterFogState.aboveDensity;
1215
+ heightFalloff.value = underwaterFogState.aboveHeightFalloff;
1216
+ startDistance.value = underwaterFogState.aboveStartDistance;
1217
+ }
1218
+ underwaterFogState.isUnderwater = isUnderwater;
1219
+ if (isUnderwater) color.value.copy(underwaterFogColor.value);
1220
+ };
1221
+ //#endregion
1222
+ //#region src/wind/createWindUniforms.ts
1223
+ const createWindUniforms = () => ({
1224
+ direction: uniform(new Vector2(1, 0)),
1225
+ gustSpeed: uniform(0),
1226
+ gustStrength: uniform(0),
1227
+ gustWidth: uniform(1),
1228
+ strength: uniform(0)
1229
+ });
1230
+ //#endregion
1231
+ //#region src/world/computeOriginShift.ts
1232
+ const computeOriginShift = (cameraPosition, threshold, step, shift) => {
1233
+ if (Math.hypot(cameraPosition.x, cameraPosition.z) <= threshold) return false;
1234
+ shift.set(Math.round(cameraPosition.x / step) * step, 0, Math.round(cameraPosition.z / step) * step);
1235
+ return shift.x !== 0 || shift.z !== 0;
1236
+ };
1237
+ //#endregion
1238
+ //#region src/world/computeOutlineDistance.ts
1239
+ const computeOutlineDistance = (outline, x, z) => {
1240
+ let isInside = false;
1241
+ let nearestSquared = Infinity;
1242
+ for (let index = 0, previousIndex = outline.length - 1; index < outline.length; previousIndex = index++) {
1243
+ const start = outline[previousIndex];
1244
+ const end = outline[index];
1245
+ if (!start || !end) continue;
1246
+ if (end.z > z !== start.z > z && x < (start.x - end.x) * (z - end.z) / (start.z - end.z) + end.x) isInside = !isInside;
1247
+ const edgeX = start.x - end.x;
1248
+ const edgeZ = start.z - end.z;
1249
+ const lengthSquared = edgeX * edgeX + edgeZ * edgeZ;
1250
+ const along = lengthSquared === 0 ? 0 : Math.min(Math.max(((x - end.x) * edgeX + (z - end.z) * edgeZ) / lengthSquared, 0), 1);
1251
+ const offsetX = x - end.x - along * edgeX;
1252
+ const offsetZ = z - end.z - along * edgeZ;
1253
+ nearestSquared = Math.min(nearestSquared, offsetX * offsetX + offsetZ * offsetZ);
1254
+ }
1255
+ return isInside ? 0 : Math.sqrt(nearestSquared);
1256
+ };
1257
+ //#endregion
1258
+ export { AntialiasingMode, GAME_MINUTES_PER_SECOND, MINUTES_PER_DAY, QualityTier, QualityTierSettingsMap, TERRAIN_LAYER, TILE_INDEX_OFFSET, TILE_INDEX_SPAN, ToonNodeMaterial, advanceGameClock, applySkyState, computeGradeLut, computeGrassBlade, computeLeafCards, computeOriginShift, computeOutlineDistance, computeRampValues, computeSunDirection, computeTerrainIndices, computeTerrainTile, computeTreeSkeleton, createCausticsNode, createFogUniforms, createGenshinRenderer, createGodraysLight, createGradeLutTexture, createGrassGeometry, createGrassMaterial, createGroundCapture, createHeightFogNode, createLeafMaterial, createLightUniforms, createPostPipeline, createPostUniforms, createRampTexture, createRimNode, createSeededRandom, createSimplexNoise, createSkyNode, createSkyState, createSkyUniforms, createStatueGeometry, createSunLight, createTerrainMaterial, createTerrainSelection, createTerrainTileGeometry, createTileStreamer, createToonMaterial, createTreeGeometry, createUnderwaterFogState, createWaterMaterial, createWaterUniforms, createWindNode, createWindUniforms, getParentTerrainTileKey, getTerrainTileColumn, getTerrainTileKey, getTerrainTileLevel, getTerrainTileRow, renderGroundCapture, resolveTerrainDraws, sampleSkyState, selectTerrainTiles, updateUnderwaterFog };