genshin-engine 3.8.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.d.ts +556 -247
- package/dist/index.js +894 -195
- package/package.json +8 -7
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Box3, BufferAttribute, BufferGeometry, CanvasTexture, Color, CylinderGeometry, Data3DTexture, DataTexture, DirectionalLight, DoubleSide,
|
|
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 { MeshBasicNodeMaterial, MeshToonNodeMaterial, RenderPipeline, RenderTarget, SpriteNodeMaterial, ToonOutlinePassNode, WebGPURenderer } from "three/webgpu";
|
|
1
|
+
import { Box3, BoxGeometry, BufferAttribute, BufferGeometry, CanvasTexture, ClampToEdgeWrapping, Color, CylinderGeometry, Data3DTexture, DataTexture, DirectionalLight, DoubleSide, FloatType, HalfFloatType, InstancedBufferAttribute, InstancedBufferGeometry, LatheGeometry, LinearFilter, MathUtils, Matrix4, NeutralToneMapping, NoColorSpace, OrthographicCamera, Quaternion, RGBAFormat, RedFormat, Sprite, UnsignedByteType, Vector2, Vector3 } from "three";
|
|
2
|
+
import { Fn, If, abs, asin, attribute, cameraFar, cameraNear, cameraPosition, cameraViewMatrix, clamp, color, cos, diffuseColor, exp, exp2, float, getNormalFromDepth, getViewPosition, hash, instanceIndex, instancedBufferAttribute, luminance, max, min, mix, modelViewMatrix, modelWorldMatrix, mrt, mx_cell_noise_float, mx_fractal_noise_float, mx_noise_float, normalView, normalWorld, normalWorldGeometry, output, perspectiveDepthToViewZ, positionGeometry, positionLocal, positionView, positionViewDirection, positionWorld, pow, reflect, renderOutput, sample, saturate, screenUV, sin, smoothstep, step, texture, texture3D, time, uniform, uniformArray, uv, varying, vec2, vec3, vec4, velocity, viewportDepthTexture, viewportSharedTexture } from "three/tsl";
|
|
3
|
+
import { LightingModel, MeshBasicNodeMaterial, MeshToonNodeMaterial, NodeMaterial, RenderPipeline, RenderTarget, SpriteNodeMaterial, ToonOutlinePassNode, WebGPURenderer } from "three/webgpu";
|
|
4
4
|
import { CSMShadowNode } from "three/examples/jsm/csm/CSMShadowNode.js";
|
|
5
5
|
import { mergeGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js";
|
|
6
6
|
import { voronoi2d } from "three/examples/jsm/tsl/math/voronoiNoise.js";
|
|
7
|
+
import GTAONode from "three/examples/jsm/tsl/display/GTAONode.js";
|
|
7
8
|
import { bilateralBlur } from "three/examples/jsm/tsl/display/BilateralBlurNode.js";
|
|
8
9
|
import { bloom } from "three/examples/jsm/tsl/display/BloomNode.js";
|
|
9
10
|
import { depthAwareBlend } from "three/examples/jsm/tsl/display/depthAwareBlend.js";
|
|
@@ -11,52 +12,113 @@ import { godrays } from "three/examples/jsm/tsl/display/GodraysNode.js";
|
|
|
11
12
|
import { lut3D } from "three/examples/jsm/tsl/display/Lut3DNode.js";
|
|
12
13
|
import { smaa } from "three/examples/jsm/tsl/display/SMAANode.js";
|
|
13
14
|
import { traa } from "three/examples/jsm/tsl/display/TRAANode.js";
|
|
14
|
-
//#region src/
|
|
15
|
+
//#region src/atmosphere/constants.ts
|
|
16
|
+
const DEFAULT_SKY_SHAPE = {
|
|
17
|
+
frontBackBlend: 1,
|
|
18
|
+
haloHeight: 1,
|
|
19
|
+
horizonBand: .45,
|
|
20
|
+
moonSize: 1,
|
|
21
|
+
sunHaloSize: 1
|
|
22
|
+
};
|
|
23
|
+
const DEFAULT_SAMPLE_COUNT = 33;
|
|
24
|
+
const DEFAULT_SKY_GRADIENT = {
|
|
25
|
+
green: Array.from({ length: DEFAULT_SAMPLE_COUNT }, () => 0),
|
|
26
|
+
red: Array.from({ length: DEFAULT_SAMPLE_COUNT }, (_value, index) => 1 - MathUtils.smoothstep(index, 0, 32))
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/post/toneMapNeutral.ts
|
|
15
30
|
const TOE_END = .08;
|
|
16
31
|
const TOE_OFFSET = .04;
|
|
17
32
|
const COMPRESSION_START = .76;
|
|
18
33
|
const DESATURATION = .15;
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
34
|
+
const compressChannel = (channel, peak, newPeak, whiteShare) => channel * newPeak / peak + (newPeak - channel * newPeak / peak) * whiteShare;
|
|
35
|
+
const toneMapNeutral = ([red, green, blue], toneMapped = [
|
|
36
|
+
0,
|
|
37
|
+
0,
|
|
38
|
+
0
|
|
39
|
+
]) => {
|
|
23
40
|
const lowest = Math.min(red, green, blue);
|
|
24
41
|
const offset = lowest < TOE_END ? lowest - 6.25 * lowest * lowest : TOE_OFFSET;
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
const offsetRed = red - offset;
|
|
43
|
+
const offsetGreen = green - offset;
|
|
44
|
+
const offsetBlue = blue - offset;
|
|
45
|
+
const peak = Math.max(offsetRed, offsetGreen, offsetBlue);
|
|
46
|
+
if (peak < COMPRESSION_START) {
|
|
47
|
+
toneMapped[0] = offsetRed;
|
|
48
|
+
toneMapped[1] = offsetGreen;
|
|
49
|
+
toneMapped[2] = offsetBlue;
|
|
50
|
+
return toneMapped;
|
|
51
|
+
}
|
|
32
52
|
const headroom = .24;
|
|
33
53
|
const newPeak = 1 - headroom * headroom / (peak + headroom - COMPRESSION_START);
|
|
34
54
|
const whiteShare = 1 - 1 / (DESATURATION * (peak - newPeak) + 1);
|
|
35
|
-
|
|
55
|
+
toneMapped[0] = compressChannel(offsetRed, peak, newPeak, whiteShare);
|
|
56
|
+
toneMapped[1] = compressChannel(offsetGreen, peak, newPeak, whiteShare);
|
|
57
|
+
toneMapped[2] = compressChannel(offsetBlue, peak, newPeak, whiteShare);
|
|
58
|
+
return toneMapped;
|
|
36
59
|
};
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/post/toSceneColor.ts
|
|
62
|
+
const INVERSE_STEP_COUNT = 16;
|
|
63
|
+
const SLOPE_NUDGE = 1e-4;
|
|
64
|
+
const SCENE_CHANNEL_LIMIT = 1.5;
|
|
65
|
+
const target = [
|
|
66
|
+
0,
|
|
67
|
+
0,
|
|
68
|
+
0
|
|
69
|
+
];
|
|
70
|
+
const input = [
|
|
71
|
+
0,
|
|
72
|
+
0,
|
|
73
|
+
0
|
|
74
|
+
];
|
|
75
|
+
const output$1 = [
|
|
76
|
+
0,
|
|
77
|
+
0,
|
|
78
|
+
0
|
|
79
|
+
];
|
|
80
|
+
const nudged = [
|
|
81
|
+
0,
|
|
82
|
+
0,
|
|
83
|
+
0
|
|
84
|
+
];
|
|
85
|
+
const nudgedOutput = [
|
|
86
|
+
0,
|
|
87
|
+
0,
|
|
88
|
+
0
|
|
89
|
+
];
|
|
90
|
+
const stepped = [
|
|
91
|
+
0,
|
|
92
|
+
0,
|
|
93
|
+
0
|
|
94
|
+
];
|
|
95
|
+
const toSceneColor = (displayColor, sceneColor = new Color()) => {
|
|
96
|
+
target[0] = input[0] = displayColor.r;
|
|
97
|
+
target[1] = input[1] = displayColor.g;
|
|
98
|
+
target[2] = input[2] = displayColor.b;
|
|
44
99
|
for (let step = 0; step < INVERSE_STEP_COUNT; step++) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
100
|
+
toneMapNeutral(input, output$1);
|
|
101
|
+
for (let index = 0; index < 3; index++) {
|
|
102
|
+
nudged[0] = input[0];
|
|
103
|
+
nudged[1] = input[1];
|
|
104
|
+
nudged[2] = input[2];
|
|
105
|
+
const channel = input[index] ?? 0;
|
|
49
106
|
nudged[index] = channel + SLOPE_NUDGE;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
107
|
+
toneMapNeutral(nudged, nudgedOutput);
|
|
108
|
+
const slope = ((nudgedOutput[index] ?? 0) - (output$1[index] ?? 0)) / SLOPE_NUDGE;
|
|
109
|
+
stepped[index] = Math.min(Math.max(channel + ((target[index] ?? 0) - (output$1[index] ?? 0)) / slope, 0), SCENE_CHANNEL_LIMIT);
|
|
110
|
+
}
|
|
111
|
+
input[0] = stepped[0];
|
|
112
|
+
input[1] = stepped[1];
|
|
113
|
+
input[2] = stepped[2];
|
|
53
114
|
}
|
|
54
|
-
return
|
|
115
|
+
return sceneColor.setRGB(input[0], input[1], input[2]);
|
|
55
116
|
};
|
|
56
117
|
//#endregion
|
|
57
118
|
//#region src/atmosphere/applySkyState.ts
|
|
58
119
|
const GODRAYS_REDRAW_COSINE = Math.cos(.6 * Math.PI / 180);
|
|
59
120
|
const godraysDirection = new Vector3();
|
|
121
|
+
const BLACK = new Color(0, 0, 0);
|
|
60
122
|
const applySkyState = (skyState, { fogUniforms, godraysLight, hemisphere, light, lightDistance, lightUniforms, postUniforms, skyUniforms }) => {
|
|
61
123
|
const { horizonColor, lightColor, lightDirection } = skyState;
|
|
62
124
|
light.position.copy(light.target.position).addScaledVector(lightDirection, lightDistance);
|
|
@@ -68,18 +130,32 @@ const applySkyState = (skyState, { fogUniforms, godraysLight, hemisphere, light,
|
|
|
68
130
|
lightUniforms.lightColor.value.copy(lightColor);
|
|
69
131
|
lightUniforms.sunDirection.value.copy(lightDirection);
|
|
70
132
|
lightUniforms.rimColor.value.copy(horizonColor);
|
|
71
|
-
|
|
72
|
-
fogUniforms.scatterColor.value
|
|
133
|
+
toSceneColor(skyState.fogColor ?? horizonColor, fogUniforms.color.value);
|
|
134
|
+
toSceneColor(lightColor, fogUniforms.scatterColor.value);
|
|
73
135
|
fogUniforms.scatterDirection.value.copy(lightDirection);
|
|
74
136
|
postUniforms.godraysColor.value.copy(lightColor);
|
|
75
|
-
skyUniforms.cloudLitColor.value
|
|
76
|
-
skyUniforms.cloudShadeColor.value
|
|
77
|
-
skyUniforms.
|
|
137
|
+
toSceneColor(skyState.cloudLitColor, skyUniforms.cloudLitColor.value);
|
|
138
|
+
toSceneColor(skyState.cloudShadeColor, skyUniforms.cloudShadeColor.value);
|
|
139
|
+
toSceneColor(skyState.cloudLitBackColor ?? skyState.cloudLitColor, skyUniforms.cloudLitBackColor.value);
|
|
140
|
+
toSceneColor(skyState.cloudShadeBackColor ?? skyState.cloudShadeColor, skyUniforms.cloudShadeBackColor.value);
|
|
141
|
+
toSceneColor(horizonColor, skyUniforms.horizonColor.value);
|
|
142
|
+
toSceneColor(skyState.horizonBackColor ?? horizonColor, skyUniforms.horizonBackColor.value);
|
|
143
|
+
toSceneColor(skyState.zenithBackColor ?? skyState.zenithColor, skyUniforms.zenithBackColor.value);
|
|
144
|
+
toSceneColor(skyState.haloColor ?? BLACK, skyUniforms.haloColor.value);
|
|
145
|
+
toSceneColor(skyState.sunHaloColor ?? BLACK, skyUniforms.sunHaloColor.value);
|
|
78
146
|
skyUniforms.lightColor.value.copy(lightColor);
|
|
79
147
|
skyUniforms.moonDirection.value.copy(skyState.moonDirection);
|
|
148
|
+
toSceneColor(skyState.moonGlowColor ?? BLACK, skyUniforms.moonGlowColor.value);
|
|
80
149
|
skyUniforms.starIntensity.value = skyState.starIntensity;
|
|
81
150
|
skyUniforms.sunDirection.value.copy(skyState.sunDirection);
|
|
82
|
-
skyUniforms.zenithColor.value
|
|
151
|
+
toSceneColor(skyState.zenithColor, skyUniforms.zenithColor.value);
|
|
152
|
+
const { frontBackBlend, haloHeight, horizonBand, moonSize, sunHaloSize } = skyState.shape ?? DEFAULT_SKY_SHAPE;
|
|
153
|
+
skyUniforms.frontBackBlend.value = frontBackBlend;
|
|
154
|
+
skyUniforms.haloHeight.value = haloHeight;
|
|
155
|
+
skyUniforms.horizonBand.value = horizonBand;
|
|
156
|
+
skyUniforms.moonSize.value = moonSize;
|
|
157
|
+
skyUniforms.sunHaloSize.value = sunHaloSize;
|
|
158
|
+
if (!godraysLight) return;
|
|
83
159
|
godraysDirection.subVectors(godraysLight.position, godraysLight.target.position).normalize();
|
|
84
160
|
if (godraysDirection.dot(lightDirection) >= GODRAYS_REDRAW_COSINE) return;
|
|
85
161
|
godraysLight.position.copy(godraysLight.target.position).addScaledVector(lightDirection, lightDistance);
|
|
@@ -97,31 +173,44 @@ const computeSunDirection = (minutes, tilt, sunDirection) => {
|
|
|
97
173
|
return sunDirection.set(Math.sin(hourAngle), height * Math.cos(tilt), height * Math.sin(tilt));
|
|
98
174
|
};
|
|
99
175
|
//#endregion
|
|
100
|
-
//#region src/atmosphere/
|
|
176
|
+
//#region src/atmosphere/getCloudAtlasColumns.ts
|
|
101
177
|
const getCloudAtlasColumns = (spriteCount) => Math.ceil(Math.sqrt(spriteCount));
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region src/materials/addSmoothLoop.ts
|
|
180
|
+
const addSmoothLoop = (path, points) => {
|
|
181
|
+
if (points.length < 3) return;
|
|
182
|
+
const getMidpoint = (index) => {
|
|
183
|
+
const [startX = 0, startY = 0] = points[index % points.length] ?? [];
|
|
184
|
+
const [endX = 0, endY = 0] = points[(index + 1) % points.length] ?? [];
|
|
185
|
+
return [(startX + endX) / 2, (startY + endY) / 2];
|
|
186
|
+
};
|
|
187
|
+
path.moveTo(...getMidpoint(points.length - 1));
|
|
188
|
+
for (const [index, [x, y]] of points.entries()) path.quadraticCurveTo(x, y, ...getMidpoint(index));
|
|
189
|
+
path.closePath();
|
|
190
|
+
};
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/atmosphere/createCloudAtlasTexture.ts
|
|
193
|
+
const EDGE_BLUR_SHARE = .02;
|
|
194
|
+
const CROWN_BLUR_SHARE = .035;
|
|
102
195
|
const createCloudAtlasTexture = (sprites, cellSize) => {
|
|
103
196
|
const columns = getCloudAtlasColumns(sprites.length);
|
|
104
197
|
const canvas = new OffscreenCanvas(columns * cellSize, columns * cellSize);
|
|
105
198
|
const context = canvas.getContext("2d");
|
|
106
199
|
if (context) {
|
|
107
200
|
context.globalCompositeOperation = "lighter";
|
|
108
|
-
const fill = (loops, column, row, style) => {
|
|
201
|
+
const fill = (loops, column, row, style, blurShare) => {
|
|
202
|
+
context.filter = `blur(${cellSize * blurShare}px)`;
|
|
109
203
|
const path = new Path2D();
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const toCanvas = ([x, y]) => [(column + x) * cellSize, (row + 1 - y) * cellSize];
|
|
113
|
-
path.moveTo(...toCanvas(first));
|
|
114
|
-
for (const point of rest) path.lineTo(...toCanvas(point));
|
|
115
|
-
path.closePath();
|
|
116
|
-
}
|
|
204
|
+
const toCanvas = ([x, y]) => [(column + x) * cellSize, (row + 1 - y) * cellSize];
|
|
205
|
+
for (const loop of loops) addSmoothLoop(path, loop.map((point) => toCanvas(point)));
|
|
117
206
|
context.fillStyle = style;
|
|
118
207
|
context.fill(path, "evenodd");
|
|
119
208
|
};
|
|
120
209
|
for (const [index, { lit, outline }] of sprites.entries()) {
|
|
121
210
|
const column = index % columns;
|
|
122
211
|
const row = Math.floor(index / columns);
|
|
123
|
-
fill(outline, column, row, "#f00");
|
|
124
|
-
fill(lit, column, row, "#0f0");
|
|
212
|
+
fill(outline, column, row, "#f00", EDGE_BLUR_SHARE);
|
|
213
|
+
fill(lit, column, row, "#0f0", CROWN_BLUR_SHARE);
|
|
125
214
|
}
|
|
126
215
|
}
|
|
127
216
|
const texture = new CanvasTexture(canvas);
|
|
@@ -132,21 +221,124 @@ const createCloudAtlasTexture = (sprites, cellSize) => {
|
|
|
132
221
|
return texture;
|
|
133
222
|
};
|
|
134
223
|
//#endregion
|
|
135
|
-
//#region src/atmosphere/
|
|
136
|
-
const
|
|
224
|
+
//#region src/atmosphere/orderByViewDepth.ts
|
|
225
|
+
const orderByViewDepth = (places, { elements }, depths, order) => {
|
|
226
|
+
for (let index = 0; index < places.length; index++) {
|
|
227
|
+
const place = places[index];
|
|
228
|
+
depths[index] = elements[2] * (place?.[0] ?? 0) + elements[6] * (place?.[1] ?? 0) + elements[10] * (place?.[2] ?? 0) + elements[14];
|
|
229
|
+
}
|
|
230
|
+
let isReordered = false;
|
|
231
|
+
for (let index = 1; index < order.length; index++) {
|
|
232
|
+
const place = order[index] ?? 0;
|
|
233
|
+
const depth = depths[place] ?? 0;
|
|
234
|
+
let slot = index;
|
|
235
|
+
while (slot > 0) {
|
|
236
|
+
const nearer = order[slot - 1] ?? 0;
|
|
237
|
+
const nearerDepth = depths[nearer] ?? 0;
|
|
238
|
+
if (nearerDepth < depth || nearerDepth === depth && nearer < place) break;
|
|
239
|
+
order[slot] = nearer;
|
|
240
|
+
slot--;
|
|
241
|
+
}
|
|
242
|
+
if (slot === index) continue;
|
|
243
|
+
order[slot] = place;
|
|
244
|
+
isReordered = true;
|
|
245
|
+
}
|
|
246
|
+
return isReordered;
|
|
247
|
+
};
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/atmosphere/createCloudBandSprite.ts
|
|
250
|
+
const BELOW_FADE_START = .1;
|
|
251
|
+
const BELOW_FADE_SCALE = 5;
|
|
252
|
+
const COVERAGE_LIGHT_SHARE = .4;
|
|
253
|
+
const EDGE_FADE_START = 0;
|
|
254
|
+
const EDGE_FADE_END = 1;
|
|
255
|
+
const DROPPED_COVERAGE = .01;
|
|
256
|
+
const MIN_CROWN_COVER = .05;
|
|
257
|
+
const createCloudBandSprite = (atlas, clouds, { aspect, spriteCount }, skyUniforms) => {
|
|
258
|
+
const { cloudCoverage, cloudFrontBackBlend, cloudLitBackColor, cloudLitColor, cloudShadeBackColor, cloudShadeColor, cloudSunBrighten, sunDirection } = skyUniforms;
|
|
137
259
|
const columns = getCloudAtlasColumns(spriteCount);
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
const
|
|
260
|
+
const places = clouds.map(({ position }) => [...position]);
|
|
261
|
+
const cloudScales = clouds.map(({ width }) => [width, width / aspect]);
|
|
262
|
+
const cloudCells = clouds.map(({ spriteIndex }) => [spriteIndex % columns, columns - 1 - Math.floor(spriteIndex / columns)]);
|
|
263
|
+
const positions = new InstancedBufferAttribute(new Float32Array(clouds.length * 3), 3);
|
|
264
|
+
const scales = new InstancedBufferAttribute(new Float32Array(clouds.length * 2), 2);
|
|
265
|
+
const cells = new InstancedBufferAttribute(new Float32Array(clouds.length * 2), 2);
|
|
266
|
+
const ranks = new InstancedBufferAttribute(new Float32Array(clouds.length), 1);
|
|
267
|
+
const cover = uniform(1);
|
|
268
|
+
const mask = texture(atlas, uv().add(instancedBufferAttribute(cells)).div(columns));
|
|
269
|
+
const place = instancedBufferAttribute(positions);
|
|
270
|
+
const direction = place.sub(cameraPosition).normalize();
|
|
271
|
+
const elevation = asin(clamp(direction.y, -1, 1)).mul(2 / Math.PI);
|
|
272
|
+
const sunCosine = direction.dot(sunDirection);
|
|
273
|
+
const toward = pow(max(sunCosine.mul(cloudFrontBackBlend).add(float(1).sub(cloudFrontBackBlend)), 0), 3);
|
|
274
|
+
const lit = mix(cloudLitBackColor, cloudLitColor, toward);
|
|
275
|
+
const shade = mix(cloudShadeBackColor, cloudShadeColor, toward);
|
|
276
|
+
const crown = saturate(mask.g.div(max(mask.r, MIN_CROWN_COVER)));
|
|
277
|
+
const cloudColor = mix(shade, lit, crown).add(lit.mul(cloudCoverage.mul(COVERAGE_LIGHT_SHARE))).mul(float(1).add(cloudSunBrighten.mul(sunCosine.mul(.5).add(.5))));
|
|
142
278
|
const material = new SpriteNodeMaterial({
|
|
143
279
|
depthWrite: false,
|
|
144
280
|
transparent: true
|
|
145
281
|
});
|
|
146
|
-
material.
|
|
147
|
-
material.
|
|
148
|
-
material.
|
|
149
|
-
|
|
282
|
+
material.positionNode = place;
|
|
283
|
+
material.scaleNode = instancedBufferAttribute(scales);
|
|
284
|
+
material.colorNode = cloudColor;
|
|
285
|
+
material.opacityNode = smoothstep(EDGE_FADE_START, EDGE_FADE_END, mask.r).mul(smoothstep(0, 1, saturate(elevation.add(BELOW_FADE_START).mul(BELOW_FADE_SCALE)))).mul(step(instancedBufferAttribute(ranks), cover));
|
|
286
|
+
material.alphaTest = DROPPED_COVERAGE;
|
|
287
|
+
const sprite = new Sprite(material);
|
|
288
|
+
sprite.center.set(.5, 0);
|
|
289
|
+
sprite.count = clouds.length;
|
|
290
|
+
sprite.frustumCulled = false;
|
|
291
|
+
const modelViewMatrix = new Matrix4();
|
|
292
|
+
const depths = new Float64Array(clouds.length);
|
|
293
|
+
const order = Uint32Array.from(clouds, (_cloud, index) => index);
|
|
294
|
+
const layOut = () => {
|
|
295
|
+
for (let instance = 0; instance < order.length; instance++) {
|
|
296
|
+
const cloud = order[instance] ?? 0;
|
|
297
|
+
scales.set(cloudScales[cloud] ?? [], instance * 2);
|
|
298
|
+
cells.set(cloudCells[cloud] ?? [], instance * 2);
|
|
299
|
+
ranks.setX(instance, (cloud + .5) / clouds.length);
|
|
300
|
+
}
|
|
301
|
+
scales.needsUpdate = true;
|
|
302
|
+
cells.needsUpdate = true;
|
|
303
|
+
ranks.needsUpdate = true;
|
|
304
|
+
};
|
|
305
|
+
layOut();
|
|
306
|
+
sprite.onBeforeRender = (_renderer, _scene, camera) => {
|
|
307
|
+
modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, sprite.matrixWorld);
|
|
308
|
+
const isReordered = orderByViewDepth(places, modelViewMatrix, depths, order);
|
|
309
|
+
for (let instance = 0; instance < order.length; instance++) positions.set(places[order[instance] ?? 0] ?? [], instance * 3);
|
|
310
|
+
positions.needsUpdate = true;
|
|
311
|
+
if (isReordered) layOut();
|
|
312
|
+
};
|
|
313
|
+
return {
|
|
314
|
+
cover,
|
|
315
|
+
dispose: () => {
|
|
316
|
+
material.dispose();
|
|
317
|
+
},
|
|
318
|
+
places,
|
|
319
|
+
sprite
|
|
320
|
+
};
|
|
321
|
+
};
|
|
322
|
+
//#endregion
|
|
323
|
+
//#region src/constants.ts
|
|
324
|
+
const MAX_BYTE = 255;
|
|
325
|
+
//#endregion
|
|
326
|
+
//#region src/atmosphere/createSkyGradientTexture.ts
|
|
327
|
+
const createSkyGradientTexture = ({ green, red }) => {
|
|
328
|
+
const width = Math.max(red.length, green.length, 1);
|
|
329
|
+
const data = new Uint8Array(width * 4);
|
|
330
|
+
for (let index = 0; index < width; index++) {
|
|
331
|
+
data[index * 4] = Math.round((red[index] ?? 0) * 255);
|
|
332
|
+
data[index * 4 + 1] = Math.round((green[index] ?? 0) * 255);
|
|
333
|
+
data[index * 4 + 3] = 255;
|
|
334
|
+
}
|
|
335
|
+
const texture = new DataTexture(data, width, 1, RGBAFormat, UnsignedByteType);
|
|
336
|
+
texture.magFilter = LinearFilter;
|
|
337
|
+
texture.minFilter = LinearFilter;
|
|
338
|
+
texture.wrapS = ClampToEdgeWrapping;
|
|
339
|
+
texture.wrapT = ClampToEdgeWrapping;
|
|
340
|
+
texture.needsUpdate = true;
|
|
341
|
+
return texture;
|
|
150
342
|
};
|
|
151
343
|
//#endregion
|
|
152
344
|
//#region src/atmosphere/createSkyState.ts
|
|
@@ -170,14 +362,27 @@ const createSkyState = () => ({
|
|
|
170
362
|
const createSkyUniforms = () => ({
|
|
171
363
|
cloudCoverage: uniform(0),
|
|
172
364
|
cloudDrift: uniform(new Vector2()),
|
|
365
|
+
cloudFrontBackBlend: uniform(1),
|
|
366
|
+
cloudLitBackColor: uniform(new Color()),
|
|
173
367
|
cloudLitColor: uniform(new Color()),
|
|
368
|
+
cloudShadeBackColor: uniform(new Color()),
|
|
174
369
|
cloudShadeColor: uniform(new Color()),
|
|
175
|
-
|
|
370
|
+
cloudSunBrighten: uniform(0),
|
|
371
|
+
frontBackBlend: uniform(DEFAULT_SKY_SHAPE.frontBackBlend),
|
|
372
|
+
haloColor: uniform(new Color(0, 0, 0)),
|
|
373
|
+
haloHeight: uniform(DEFAULT_SKY_SHAPE.haloHeight),
|
|
374
|
+
horizonBackColor: uniform(new Color()),
|
|
375
|
+
horizonBand: uniform(DEFAULT_SKY_SHAPE.horizonBand),
|
|
176
376
|
horizonColor: uniform(new Color()),
|
|
177
377
|
lightColor: uniform(new Color()),
|
|
178
378
|
moonDirection: uniform(new Vector3(0, -1, 0)),
|
|
379
|
+
moonGlowColor: uniform(new Color(0, 0, 0)),
|
|
380
|
+
moonSize: uniform(DEFAULT_SKY_SHAPE.moonSize),
|
|
179
381
|
starIntensity: uniform(0),
|
|
180
382
|
sunDirection: uniform(new Vector3(0, 1, 0)),
|
|
383
|
+
sunHaloColor: uniform(new Color(0, 0, 0)),
|
|
384
|
+
sunHaloSize: uniform(DEFAULT_SKY_SHAPE.sunHaloSize),
|
|
385
|
+
zenithBackColor: uniform(new Color()),
|
|
181
386
|
zenithColor: uniform(new Color())
|
|
182
387
|
});
|
|
183
388
|
//#endregion
|
|
@@ -237,7 +442,6 @@ const placeCloudBand = ({ count, distanceRange: [near, far], heightRange: [low,
|
|
|
237
442
|
};
|
|
238
443
|
//#endregion
|
|
239
444
|
//#region src/atmosphere/sampleSkyState.ts
|
|
240
|
-
const lerp = (from, to, amount) => from + (to - from) * amount;
|
|
241
445
|
const sampleSkyState = (skyKeyframes, minutes, tilt, skyState) => {
|
|
242
446
|
let nextIndex = 0;
|
|
243
447
|
while (nextIndex < skyKeyframes.length && (skyKeyframes[nextIndex]?.minutes ?? 0) <= minutes) nextIndex++;
|
|
@@ -250,12 +454,12 @@ const sampleSkyState = (skyKeyframes, minutes, tilt, skyState) => {
|
|
|
250
454
|
skyState.cloudLitColor.lerpColors(previous.cloudLitColor, next.cloudLitColor, amount);
|
|
251
455
|
skyState.cloudShadeColor.lerpColors(previous.cloudShadeColor, next.cloudShadeColor, amount);
|
|
252
456
|
skyState.hemisphereGroundColor.lerpColors(previous.hemisphereGroundColor, next.hemisphereGroundColor, amount);
|
|
253
|
-
skyState.hemisphereIntensity = lerp(previous.hemisphereIntensity, next.hemisphereIntensity, amount);
|
|
457
|
+
skyState.hemisphereIntensity = MathUtils.lerp(previous.hemisphereIntensity, next.hemisphereIntensity, amount);
|
|
254
458
|
skyState.hemisphereSkyColor.lerpColors(previous.hemisphereSkyColor, next.hemisphereSkyColor, amount);
|
|
255
459
|
skyState.horizonColor.lerpColors(previous.horizonColor, next.horizonColor, amount);
|
|
256
460
|
skyState.lightColor.lerpColors(previous.lightColor, next.lightColor, amount);
|
|
257
|
-
skyState.lightIntensity = lerp(previous.lightIntensity, next.lightIntensity, amount);
|
|
258
|
-
skyState.starIntensity = lerp(previous.starIntensity, next.starIntensity, amount);
|
|
461
|
+
skyState.lightIntensity = MathUtils.lerp(previous.lightIntensity, next.lightIntensity, amount);
|
|
462
|
+
skyState.starIntensity = MathUtils.lerp(previous.starIntensity, next.starIntensity, amount);
|
|
259
463
|
skyState.zenithColor.lerpColors(previous.zenithColor, next.zenithColor, amount);
|
|
260
464
|
computeSunDirection(minutes, tilt, skyState.sunDirection);
|
|
261
465
|
skyState.moonDirection.copy(skyState.sunDirection).negate();
|
|
@@ -263,11 +467,309 @@ const sampleSkyState = (skyKeyframes, minutes, tilt, skyState) => {
|
|
|
263
467
|
return skyState;
|
|
264
468
|
};
|
|
265
469
|
//#endregion
|
|
470
|
+
//#region src/audio/collectMusicNotes.ts
|
|
471
|
+
const collectMusicNotes = ({ segment, start }, from, to) => segment.voices.flatMap((voice) => voice.notes.flatMap((note) => {
|
|
472
|
+
const time = start + note.start;
|
|
473
|
+
return time >= from && time < to ? [{
|
|
474
|
+
note,
|
|
475
|
+
time,
|
|
476
|
+
voice
|
|
477
|
+
}] : [];
|
|
478
|
+
}));
|
|
479
|
+
//#endregion
|
|
480
|
+
//#region src/audio/collectMusicSegments.ts
|
|
481
|
+
const collectMusicSegments = ({ isLooping, order, segments }, from, to) => {
|
|
482
|
+
const passDuration = order.reduce((sum, index) => sum + (segments[index]?.duration ?? 0), 0);
|
|
483
|
+
if (passDuration <= 0) return [];
|
|
484
|
+
const scheduledSegments = [];
|
|
485
|
+
const lastPass = isLooping ? Math.ceil(to / passDuration) : 1;
|
|
486
|
+
for (let pass = Math.max(Math.floor(from / passDuration), 0); pass < lastPass; pass++) {
|
|
487
|
+
let start = pass * passDuration;
|
|
488
|
+
for (const index of order) {
|
|
489
|
+
const segment = segments[index];
|
|
490
|
+
if (!segment) continue;
|
|
491
|
+
else if (start >= to) break;
|
|
492
|
+
else if (start + segment.duration > from) scheduledSegments.push({
|
|
493
|
+
segment,
|
|
494
|
+
start
|
|
495
|
+
});
|
|
496
|
+
start += segment.duration;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return scheduledSegments;
|
|
500
|
+
};
|
|
501
|
+
//#endregion
|
|
502
|
+
//#region src/audio/constants.ts
|
|
503
|
+
const MUSIC_LOOKAHEAD_SECONDS = 2;
|
|
504
|
+
const MUSIC_SCHEDULE_INTERVAL_MS = 500;
|
|
505
|
+
const RELEASE_TIME_CONSTANTS = 7;
|
|
506
|
+
const MUSIC_NOISE_LENGTH = 2 ** 16;
|
|
507
|
+
const MUSIC_NOISE_BAND_CENTRES = [
|
|
508
|
+
63,
|
|
509
|
+
125,
|
|
510
|
+
250,
|
|
511
|
+
500,
|
|
512
|
+
1e3,
|
|
513
|
+
2e3,
|
|
514
|
+
4e3,
|
|
515
|
+
8e3
|
|
516
|
+
];
|
|
517
|
+
const MUSIC_EXPRESSION_WINDOW_SECONDS = 8;
|
|
518
|
+
const A4_PITCH = 69;
|
|
519
|
+
const A4_FREQUENCY = 440;
|
|
520
|
+
const MIDI_VELOCITY_MAX = 127;
|
|
521
|
+
//#endregion
|
|
522
|
+
//#region src/audio/transformFourier.ts
|
|
523
|
+
const transformFourier = (real, imaginary) => {
|
|
524
|
+
const { length } = real;
|
|
525
|
+
for (let index = 1, reversed = 0; index < length; index++) {
|
|
526
|
+
let bit = length >> 1;
|
|
527
|
+
for (; reversed & bit; bit >>= 1) reversed ^= bit;
|
|
528
|
+
reversed ^= bit;
|
|
529
|
+
if (index < reversed) {
|
|
530
|
+
[real[index], real[reversed]] = [real[reversed] ?? 0, real[index] ?? 0];
|
|
531
|
+
[imaginary[index], imaginary[reversed]] = [imaginary[reversed] ?? 0, imaginary[index] ?? 0];
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
for (let size = 2; size <= length; size <<= 1) {
|
|
535
|
+
const half = size >> 1;
|
|
536
|
+
const angle = -2 * Math.PI / size;
|
|
537
|
+
for (let offset = 0; offset < half; offset++) {
|
|
538
|
+
const twiddleReal = Math.cos(angle * offset);
|
|
539
|
+
const twiddleImaginary = Math.sin(angle * offset);
|
|
540
|
+
for (let even = offset; even < length; even += size) {
|
|
541
|
+
const odd = even + half;
|
|
542
|
+
const oddReal = (real[odd] ?? 0) * twiddleReal - (imaginary[odd] ?? 0) * twiddleImaginary;
|
|
543
|
+
const oddImaginary = (real[odd] ?? 0) * twiddleImaginary + (imaginary[odd] ?? 0) * twiddleReal;
|
|
544
|
+
real[odd] = (real[even] ?? 0) - oddReal;
|
|
545
|
+
imaginary[odd] = (imaginary[even] ?? 0) - oddImaginary;
|
|
546
|
+
real[even] = (real[even] ?? 0) + oddReal;
|
|
547
|
+
imaginary[even] = (imaginary[even] ?? 0) + oddImaginary;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
//#endregion
|
|
553
|
+
//#region src/audio/computeNoiseSamples.ts
|
|
554
|
+
const computeNoiseSamples = (noiseBands, sampleRate) => {
|
|
555
|
+
const real = new Float64Array(MUSIC_NOISE_LENGTH);
|
|
556
|
+
const imaginary = new Float64Array(MUSIC_NOISE_LENGTH);
|
|
557
|
+
for (let band = 0; band < noiseBands.length; band++) {
|
|
558
|
+
const level = noiseBands[band] ?? 0;
|
|
559
|
+
const centre = MUSIC_NOISE_BAND_CENTRES[band] ?? sampleRate;
|
|
560
|
+
const lowBin = Math.max(Math.ceil(centre / Math.SQRT2 * MUSIC_NOISE_LENGTH / sampleRate), 1);
|
|
561
|
+
const highBin = Math.min(Math.floor(centre * Math.SQRT2 * MUSIC_NOISE_LENGTH / sampleRate), MUSIC_NOISE_LENGTH / 2 - 1);
|
|
562
|
+
if (level <= 0 || lowBin > highBin) continue;
|
|
563
|
+
const amplitude = level / Math.sqrt(2 * (highBin - lowBin + 1));
|
|
564
|
+
for (let bin = lowBin; bin <= highBin; bin++) {
|
|
565
|
+
const hash = Math.sin(bin * 12.9898 + band * 78.233) * 43758.5453;
|
|
566
|
+
const phase = 2 * Math.PI * (hash - Math.floor(hash));
|
|
567
|
+
const binReal = amplitude * Math.cos(phase);
|
|
568
|
+
const binImaginary = amplitude * Math.sin(phase);
|
|
569
|
+
real[bin] = (real[bin] ?? 0) + binReal;
|
|
570
|
+
real[MUSIC_NOISE_LENGTH - bin] = (real[MUSIC_NOISE_LENGTH - bin] ?? 0) + binReal;
|
|
571
|
+
imaginary[bin] = (imaginary[bin] ?? 0) - binImaginary;
|
|
572
|
+
imaginary[MUSIC_NOISE_LENGTH - bin] = (imaginary[MUSIC_NOISE_LENGTH - bin] ?? 0) + binImaginary;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
transformFourier(real, imaginary);
|
|
576
|
+
return Float32Array.from(real);
|
|
577
|
+
};
|
|
578
|
+
//#endregion
|
|
579
|
+
//#region src/audio/computeSoundEffectSamples.ts
|
|
580
|
+
const computeSoundEffectSamples = ({ frameSeconds, levels }, sampleRate) => {
|
|
581
|
+
const samples = new Float32Array(Math.ceil(levels.length * frameSeconds * sampleRate));
|
|
582
|
+
for (const band of MUSIC_NOISE_BAND_CENTRES.keys()) {
|
|
583
|
+
if (levels.every((frameLevels) => (frameLevels[band] ?? 0) <= 0)) continue;
|
|
584
|
+
const noise = computeNoiseSamples(MUSIC_NOISE_BAND_CENTRES.map((_centre, index) => index === band ? 1 : 0), sampleRate);
|
|
585
|
+
for (let sample = 0; sample < samples.length; sample++) {
|
|
586
|
+
const position = sample / sampleRate / frameSeconds;
|
|
587
|
+
const frame = Math.floor(position);
|
|
588
|
+
const share = position - frame;
|
|
589
|
+
const level = (levels[frame]?.[band] ?? 0) * (1 - share) + (levels[Math.min(frame + 1, levels.length - 1)]?.[band] ?? 0) * share;
|
|
590
|
+
samples[sample] = (samples[sample] ?? 0) + level * (noise[sample % MUSIC_NOISE_LENGTH] ?? 0);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return samples;
|
|
594
|
+
};
|
|
595
|
+
//#endregion
|
|
596
|
+
//#region src/audio/createInstrumentWave.ts
|
|
597
|
+
const createInstrumentWave = (context, harmonics) => context.createPeriodicWave(new Float32Array(harmonics.length + 1), Float32Array.of(0, ...harmonics), { disableNormalization: true });
|
|
598
|
+
//#endregion
|
|
599
|
+
//#region src/audio/createNoiseBuffer.ts
|
|
600
|
+
const createNoiseBuffer = (context, noiseBands) => {
|
|
601
|
+
if (noiseBands.every((level) => level <= 0)) return void 0;
|
|
602
|
+
const { sampleRate } = context;
|
|
603
|
+
const buffer = new AudioBuffer({
|
|
604
|
+
length: MUSIC_NOISE_LENGTH,
|
|
605
|
+
sampleRate
|
|
606
|
+
});
|
|
607
|
+
buffer.getChannelData(0).set(computeNoiseSamples(noiseBands, sampleRate));
|
|
608
|
+
return buffer;
|
|
609
|
+
};
|
|
610
|
+
//#endregion
|
|
611
|
+
//#region src/audio/scheduleMusicExpression.ts
|
|
612
|
+
const toAmplitude = (gain) => 10 ** (gain / 20);
|
|
613
|
+
const scheduleMusicExpression = (gain, expression, start) => {
|
|
614
|
+
const [first = 0, ...rest] = expression;
|
|
615
|
+
gain.setValueAtTime(toAmplitude(first), start);
|
|
616
|
+
if (rest.length === 0) return;
|
|
617
|
+
gain.setValueAtTime(toAmplitude(first), start + 4);
|
|
618
|
+
for (const [index, windowGain] of rest.entries()) gain.exponentialRampToValueAtTime(toAmplitude(windowGain), start + (index + 1.5) * 8);
|
|
619
|
+
};
|
|
620
|
+
//#endregion
|
|
621
|
+
//#region src/audio/scheduleMusicNote.ts
|
|
622
|
+
const scheduleMusicNote = (context, destination, wave, noiseBuffer, { attack, decay, level, release, sustain, tuning }, { duration, pitch, velocity }, time) => {
|
|
623
|
+
const peak = level * velocity;
|
|
624
|
+
const end = time + duration;
|
|
625
|
+
const oscillator = new OscillatorNode(context, {
|
|
626
|
+
frequency: 440 * 2 ** ((pitch + tuning - 69) / 12),
|
|
627
|
+
periodicWave: wave
|
|
628
|
+
});
|
|
629
|
+
const gain = new GainNode(context, { gain: 0 });
|
|
630
|
+
gain.gain.setValueAtTime(0, time);
|
|
631
|
+
if (duration <= attack) gain.gain.linearRampToValueAtTime(peak * duration / attack, end);
|
|
632
|
+
else {
|
|
633
|
+
gain.gain.linearRampToValueAtTime(peak, time + attack);
|
|
634
|
+
gain.gain.setTargetAtTime(peak * sustain, time + attack, decay);
|
|
635
|
+
gain.gain.setValueAtTime(peak * (sustain + (1 - sustain) * Math.exp(-(duration - attack) / decay)), end);
|
|
636
|
+
}
|
|
637
|
+
gain.gain.setTargetAtTime(0, end, release);
|
|
638
|
+
oscillator.connect(gain).connect(destination);
|
|
639
|
+
oscillator.addEventListener("ended", () => {
|
|
640
|
+
gain.disconnect();
|
|
641
|
+
});
|
|
642
|
+
oscillator.start(time);
|
|
643
|
+
oscillator.stop(end + release * 7);
|
|
644
|
+
if (!noiseBuffer) return;
|
|
645
|
+
const noiseSource = new AudioBufferSourceNode(context, {
|
|
646
|
+
buffer: noiseBuffer,
|
|
647
|
+
loop: true
|
|
648
|
+
});
|
|
649
|
+
noiseSource.connect(gain);
|
|
650
|
+
noiseSource.start(time, time % noiseBuffer.duration);
|
|
651
|
+
noiseSource.stop(end + release * 7);
|
|
652
|
+
};
|
|
653
|
+
//#endregion
|
|
654
|
+
//#region src/audio/createMusicPlayer.ts
|
|
655
|
+
const createMusicPlayer = (context, music, destination = context.destination) => {
|
|
656
|
+
const output = new GainNode(context);
|
|
657
|
+
const instrumentSoundMap = /* @__PURE__ */ new Map();
|
|
658
|
+
let origin = 0;
|
|
659
|
+
let scheduledUntil = 0;
|
|
660
|
+
let startSegmentOutputMap = /* @__PURE__ */ new Map();
|
|
661
|
+
let timer;
|
|
662
|
+
const schedule = () => {
|
|
663
|
+
const until = context.currentTime - origin + 2;
|
|
664
|
+
const nextStartSegmentOutputMap = /* @__PURE__ */ new Map();
|
|
665
|
+
for (const scheduledSegment of collectMusicSegments(music, scheduledUntil, until)) {
|
|
666
|
+
let segmentOutput = startSegmentOutputMap.get(scheduledSegment.start);
|
|
667
|
+
if (!segmentOutput) {
|
|
668
|
+
segmentOutput = new GainNode(context);
|
|
669
|
+
scheduleMusicExpression(segmentOutput.gain, scheduledSegment.segment.expression, origin + scheduledSegment.start);
|
|
670
|
+
segmentOutput.connect(output);
|
|
671
|
+
}
|
|
672
|
+
nextStartSegmentOutputMap.set(scheduledSegment.start, segmentOutput);
|
|
673
|
+
for (const { note, time, voice } of collectMusicNotes(scheduledSegment, scheduledUntil, until)) {
|
|
674
|
+
let sound = instrumentSoundMap.get(voice.instrument);
|
|
675
|
+
if (!sound) {
|
|
676
|
+
sound = {
|
|
677
|
+
noiseBuffer: createNoiseBuffer(context, voice.instrument.noiseBands),
|
|
678
|
+
wave: createInstrumentWave(context, voice.instrument.harmonics)
|
|
679
|
+
};
|
|
680
|
+
instrumentSoundMap.set(voice.instrument, sound);
|
|
681
|
+
}
|
|
682
|
+
scheduleMusicNote(context, segmentOutput, sound.wave, sound.noiseBuffer, voice.instrument, note, origin + time);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
startSegmentOutputMap = nextStartSegmentOutputMap;
|
|
686
|
+
scheduledUntil = until;
|
|
687
|
+
};
|
|
688
|
+
return {
|
|
689
|
+
start: () => {
|
|
690
|
+
output.connect(destination);
|
|
691
|
+
origin = context.currentTime;
|
|
692
|
+
startSegmentOutputMap = /* @__PURE__ */ new Map();
|
|
693
|
+
scheduledUntil = 0;
|
|
694
|
+
schedule();
|
|
695
|
+
timer = setInterval(() => {
|
|
696
|
+
schedule();
|
|
697
|
+
}, 500);
|
|
698
|
+
},
|
|
699
|
+
stop: () => {
|
|
700
|
+
clearInterval(timer);
|
|
701
|
+
output.disconnect();
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
};
|
|
705
|
+
//#endregion
|
|
706
|
+
//#region src/audio/createSoundEffectBuffer.ts
|
|
707
|
+
const createSoundEffectBuffer = (context, soundEffect) => {
|
|
708
|
+
const { sampleRate } = context;
|
|
709
|
+
const samples = computeSoundEffectSamples(soundEffect, sampleRate);
|
|
710
|
+
const buffer = new AudioBuffer({
|
|
711
|
+
length: Math.max(samples.length, 1),
|
|
712
|
+
sampleRate
|
|
713
|
+
});
|
|
714
|
+
buffer.getChannelData(0).set(samples);
|
|
715
|
+
return buffer;
|
|
716
|
+
};
|
|
717
|
+
//#endregion
|
|
718
|
+
//#region src/audio/renderMusicSegment.ts
|
|
719
|
+
const renderMusicSegment = (music, segmentIndex, sampleRate) => {
|
|
720
|
+
const segment = music.segments[segmentIndex];
|
|
721
|
+
const context = new OfflineAudioContext({
|
|
722
|
+
length: Math.max(Math.ceil((segment?.duration ?? 0) * sampleRate), 1),
|
|
723
|
+
numberOfChannels: 1,
|
|
724
|
+
sampleRate
|
|
725
|
+
});
|
|
726
|
+
const output = new GainNode(context);
|
|
727
|
+
scheduleMusicExpression(output.gain, segment?.expression ?? [], 0);
|
|
728
|
+
output.connect(context.destination);
|
|
729
|
+
for (const { instrument, notes } of segment?.voices ?? []) {
|
|
730
|
+
const wave = createInstrumentWave(context, instrument.harmonics);
|
|
731
|
+
const noiseBuffer = createNoiseBuffer(context, instrument.noiseBands);
|
|
732
|
+
for (const note of notes) scheduleMusicNote(context, output, wave, noiseBuffer, instrument, note, note.start);
|
|
733
|
+
}
|
|
734
|
+
return context.startRendering();
|
|
735
|
+
};
|
|
736
|
+
//#endregion
|
|
737
|
+
//#region src/audio/selectMusicSample.ts
|
|
738
|
+
const getOutsideDistance = (value, low, high) => value < low ? low - value : Math.max(value - high, 0);
|
|
739
|
+
const selectMusicSample = (samples, pitch, velocity) => {
|
|
740
|
+
const midiVelocity = Math.min(Math.max(Math.round(velocity * 127), 1), 127);
|
|
741
|
+
let selected;
|
|
742
|
+
let selectedKeyDistance = Infinity;
|
|
743
|
+
let selectedVelocityDistance = Infinity;
|
|
744
|
+
for (const sample of samples) {
|
|
745
|
+
const keyDistance = getOutsideDistance(pitch, sample.lowKey, sample.highKey);
|
|
746
|
+
const velocityDistance = getOutsideDistance(midiVelocity, sample.lowVelocity, sample.highVelocity);
|
|
747
|
+
if (keyDistance > selectedKeyDistance || keyDistance === selectedKeyDistance && velocityDistance >= selectedVelocityDistance) continue;
|
|
748
|
+
selected = sample;
|
|
749
|
+
selectedKeyDistance = keyDistance;
|
|
750
|
+
selectedVelocityDistance = velocityDistance;
|
|
751
|
+
}
|
|
752
|
+
return selected;
|
|
753
|
+
};
|
|
754
|
+
//#endregion
|
|
266
755
|
//#region src/clock/advanceGameClock.ts
|
|
267
756
|
const advanceGameClock = (gameClock, deltaSeconds) => {
|
|
268
757
|
gameClock.minutes = (gameClock.minutes + deltaSeconds * gameClock.minutesPerSecond) % MINUTES_PER_DAY;
|
|
269
758
|
};
|
|
270
759
|
//#endregion
|
|
760
|
+
//#region src/kits/mergeGeometryParts.ts
|
|
761
|
+
const mergeGeometryParts = (parts) => {
|
|
762
|
+
const geometry = mergeGeometries(parts) ?? new BufferGeometry();
|
|
763
|
+
for (const part of parts) part.dispose();
|
|
764
|
+
return geometry;
|
|
765
|
+
};
|
|
766
|
+
//#endregion
|
|
767
|
+
//#region src/kits/architecture/createBoxesGeometry.ts
|
|
768
|
+
const createBoxesGeometry = (boxes) => {
|
|
769
|
+
const parts = boxes.map(([minX = 0, minY = 0, minZ = 0, maxX = 0, maxY = 0, maxZ = 0]) => new BoxGeometry(maxX - minX, maxY - minY, maxZ - minZ).translate((minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2));
|
|
770
|
+
return mergeGeometryParts(parts);
|
|
771
|
+
};
|
|
772
|
+
//#endregion
|
|
271
773
|
//#region src/kits/architecture/createLatheStackGeometry.ts
|
|
272
774
|
const createLatheStackGeometry = ({ isFaceted, radialSegments, sections }) => {
|
|
273
775
|
let footHeight = 0;
|
|
@@ -276,26 +778,11 @@ const createLatheStackGeometry = ({ isFaceted, radialSegments, sections }) => {
|
|
|
276
778
|
footHeight += height;
|
|
277
779
|
return isFaceted ? part.toNonIndexed() : part;
|
|
278
780
|
});
|
|
279
|
-
const stackGeometry =
|
|
280
|
-
for (const part of parts) part.dispose();
|
|
781
|
+
const stackGeometry = mergeGeometryParts(parts);
|
|
281
782
|
if (isFaceted) stackGeometry.computeVertexNormals();
|
|
282
783
|
return stackGeometry;
|
|
283
784
|
};
|
|
284
785
|
//#endregion
|
|
285
|
-
//#region src/kits/architecture/createSilhouetteGeometry.ts
|
|
286
|
-
const createSilhouetteGeometry = ({ contours, depth: [front, back] }) => {
|
|
287
|
-
const shapePath = new ShapePath();
|
|
288
|
-
for (const [first, ...rest] of contours) {
|
|
289
|
-
if (!first) continue;
|
|
290
|
-
shapePath.moveTo(...first);
|
|
291
|
-
for (const point of rest) shapePath.lineTo(...point);
|
|
292
|
-
}
|
|
293
|
-
return new ExtrudeGeometry(shapePath.toShapes(), {
|
|
294
|
-
bevelEnabled: false,
|
|
295
|
-
depth: back - front
|
|
296
|
-
}).translate(0, 0, front);
|
|
297
|
-
};
|
|
298
|
-
//#endregion
|
|
299
786
|
//#region src/kits/statue/createStatueGeometry.ts
|
|
300
787
|
const PLINTH_SIDES = 8;
|
|
301
788
|
const FIGURE_SEGMENTS = 24;
|
|
@@ -313,24 +800,14 @@ const FIGURE_PROFILE = [
|
|
|
313
800
|
new Vector2(0, 6.2)
|
|
314
801
|
];
|
|
315
802
|
const createStatueGeometry = () => {
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
figure
|
|
325
|
-
].map((part) => part.toNonIndexed());
|
|
326
|
-
const statueGeometry = mergeGeometries(parts) ?? new BufferGeometry();
|
|
327
|
-
for (const part of [
|
|
328
|
-
lowerStep,
|
|
329
|
-
upperStep,
|
|
330
|
-
pedestal,
|
|
331
|
-
figure,
|
|
332
|
-
...parts
|
|
333
|
-
]) part.dispose();
|
|
803
|
+
const indexedParts = [
|
|
804
|
+
new CylinderGeometry(3.2, 3.4, .6, PLINTH_SIDES).translate(0, .3, 0),
|
|
805
|
+
new CylinderGeometry(2.6, 2.8, .6, PLINTH_SIDES).translate(0, .9, 0),
|
|
806
|
+
new CylinderGeometry(1.8, 2.1, 2.2, PLINTH_SIDES).translate(0, 2.3, 0),
|
|
807
|
+
new LatheGeometry(FIGURE_PROFILE, FIGURE_SEGMENTS).translate(0, 3.4, 0)
|
|
808
|
+
];
|
|
809
|
+
const statueGeometry = mergeGeometryParts(indexedParts.map((part) => part.toNonIndexed()));
|
|
810
|
+
for (const part of indexedParts) part.dispose();
|
|
334
811
|
statueGeometry.computeVertexNormals();
|
|
335
812
|
return statueGeometry;
|
|
336
813
|
};
|
|
@@ -464,8 +941,7 @@ const createTreeGeometry = (treeOptions) => {
|
|
|
464
941
|
segmentGeometry.translate((start.x + end.x) / 2, (start.y + end.y) / 2, (start.z + end.z) / 2);
|
|
465
942
|
return segmentGeometry;
|
|
466
943
|
});
|
|
467
|
-
const branchGeometry =
|
|
468
|
-
for (const segmentGeometry of segmentGeometries) segmentGeometry.dispose();
|
|
944
|
+
const branchGeometry = mergeGeometryParts(segmentGeometries);
|
|
469
945
|
const { indices, normals, positions, uvs } = computeLeafCards(clusterCenters, treeOptions);
|
|
470
946
|
const leafGeometry = new BufferGeometry();
|
|
471
947
|
leafGeometry.setAttribute("position", new BufferAttribute(positions, 3));
|
|
@@ -480,14 +956,12 @@ const createTreeGeometry = (treeOptions) => {
|
|
|
480
956
|
};
|
|
481
957
|
//#endregion
|
|
482
958
|
//#region src/materials/computeRampValues.ts
|
|
483
|
-
const MAX_BYTE$1 = 255;
|
|
484
959
|
const computeRampValues = ({ resolution, softness, terminator }) => {
|
|
485
960
|
const lowerEdge = terminator - softness / 2;
|
|
486
961
|
const upperEdge = terminator + softness / 2;
|
|
487
962
|
return Uint8Array.from({ length: resolution }, (_value, index) => {
|
|
488
963
|
const coordinate = (index + .5) / resolution;
|
|
489
|
-
|
|
490
|
-
return Math.round(step * step * (3 - 2 * step) * MAX_BYTE$1);
|
|
964
|
+
return Math.round(MathUtils.smoothstep(coordinate, lowerEdge, upperEdge) * 255);
|
|
491
965
|
});
|
|
492
966
|
};
|
|
493
967
|
//#endregion
|
|
@@ -501,7 +975,58 @@ const createRampTexture = (rampOptions) => {
|
|
|
501
975
|
return rampTexture;
|
|
502
976
|
};
|
|
503
977
|
//#endregion
|
|
504
|
-
//#region src/nodes/
|
|
978
|
+
//#region src/nodes/constants.ts
|
|
979
|
+
const LEAST_DIVISOR = 1e-4;
|
|
980
|
+
const STONE_SHADOW_EXPONENT = .2;
|
|
981
|
+
const STONE_RAMP_KNOT_COUNT = 16;
|
|
982
|
+
const STONE_HARMONIC_COUNT = 9;
|
|
983
|
+
const STONE_MASK_OUTPUT = "stoneMask";
|
|
984
|
+
const STONE_HEIGHT_FALLOFF = .05;
|
|
985
|
+
//#endregion
|
|
986
|
+
//#region src/nodes/createSunVisibilityNode.ts
|
|
987
|
+
const createSunVisibilityNode = (lightColor, sunRadiance) => luminance(lightColor).div(luminance(sunRadiance).max(LEAST_DIVISOR)).saturate();
|
|
988
|
+
//#endregion
|
|
989
|
+
//#region src/models/nodes/StoneLightingModel.ts
|
|
990
|
+
var StoneLightingModel = class extends LightingModel {
|
|
991
|
+
stoneLight;
|
|
992
|
+
constructor(stoneLight) {
|
|
993
|
+
super();
|
|
994
|
+
this.stoneLight = stoneLight;
|
|
995
|
+
}
|
|
996
|
+
direct({ lightColor, lightDirection, reflectedLight }) {
|
|
997
|
+
const { ramp, sunRadiance } = this.stoneLight;
|
|
998
|
+
const visibility = createSunVisibilityNode(lightColor, sunRadiance);
|
|
999
|
+
const rampU = normalView.dot(lightDirection).mul(visibility.pow(STONE_SHADOW_EXPONENT)).mul(.5).add(.5).mul(15).add(.5).div(16);
|
|
1000
|
+
reflectedLight.directDiffuse.addAssign(texture(ramp, vec2(rampU, .5)).rgb.mul(diffuseColor.rgb));
|
|
1001
|
+
}
|
|
1002
|
+
indirect(builder) {
|
|
1003
|
+
const { harmonics, heightFade } = this.stoneLight;
|
|
1004
|
+
const { x, y, z } = normalWorld;
|
|
1005
|
+
const ambient = harmonics.element(0).add(harmonics.element(1).mul(x)).add(harmonics.element(2).mul(y)).add(harmonics.element(3).mul(z)).add(harmonics.element(4).mul(x.mul(y))).add(harmonics.element(5).mul(y.mul(z))).add(harmonics.element(6).mul(x.mul(z))).add(harmonics.element(7).mul(x.mul(x).sub(z.mul(z)))).add(harmonics.element(8).mul(y.mul(y).mul(3).sub(1))).add(heightFade.mul(positionWorld.y.mul(-.05).exp()));
|
|
1006
|
+
const { reflectedLight } = builder.context;
|
|
1007
|
+
reflectedLight.indirectDiffuse.addAssign(ambient.mul(diffuseColor.rgb));
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
//#endregion
|
|
1011
|
+
//#region src/models/nodes/StoneNodeMaterial.ts
|
|
1012
|
+
var StoneNodeMaterial = class extends NodeMaterial {
|
|
1013
|
+
emissiveNode = null;
|
|
1014
|
+
stoneLight;
|
|
1015
|
+
constructor(stoneLight) {
|
|
1016
|
+
super();
|
|
1017
|
+
this.lights = true;
|
|
1018
|
+
this.stoneLight = stoneLight;
|
|
1019
|
+
this.mrtNode = mrt({
|
|
1020
|
+
output,
|
|
1021
|
+
[STONE_MASK_OUTPUT]: float(1)
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
setupLightingModel() {
|
|
1025
|
+
return new StoneLightingModel(this.stoneLight);
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
//#endregion
|
|
1029
|
+
//#region src/models/nodes/ToonNodeMaterial.ts
|
|
505
1030
|
var ToonNodeMaterial = class extends MeshToonNodeMaterial {
|
|
506
1031
|
emissiveNode = null;
|
|
507
1032
|
isMeshToonNodeMaterial;
|
|
@@ -511,6 +1036,57 @@ var ToonNodeMaterial = class extends MeshToonNodeMaterial {
|
|
|
511
1036
|
}
|
|
512
1037
|
};
|
|
513
1038
|
//#endregion
|
|
1039
|
+
//#region src/models/renderer/AntialiasingMode.ts
|
|
1040
|
+
let AntialiasingMode = /* @__PURE__ */ function(AntialiasingMode) {
|
|
1041
|
+
AntialiasingMode["Smaa"] = "Smaa";
|
|
1042
|
+
AntialiasingMode["Traa"] = "Traa";
|
|
1043
|
+
return AntialiasingMode;
|
|
1044
|
+
}({});
|
|
1045
|
+
//#endregion
|
|
1046
|
+
//#region src/models/renderer/QualityTier.ts
|
|
1047
|
+
let QualityTier = /* @__PURE__ */ function(QualityTier) {
|
|
1048
|
+
QualityTier["High"] = "High";
|
|
1049
|
+
QualityTier["Low"] = "Low";
|
|
1050
|
+
QualityTier["Medium"] = "Medium";
|
|
1051
|
+
return QualityTier;
|
|
1052
|
+
}({});
|
|
1053
|
+
//#endregion
|
|
1054
|
+
//#region src/nodes/applyStoneLight.ts
|
|
1055
|
+
const applyStoneLight = ({ harmonics, hazeColor, hazeScatterColor, heightFade, ramp }, { harmonics: harmonicsUniform, hazeColor: hazeColorUniform, hazeScatterColor: hazeScatterColorUniform, heightFade: heightFadeUniform, ramp: rampTexture, sunRadiance }, light) => {
|
|
1056
|
+
const { data } = rampTexture.image;
|
|
1057
|
+
if (data) for (const [knot, [red = 0, green = 0, blue = 0]] of ramp.entries()) {
|
|
1058
|
+
data[knot * 4] = red;
|
|
1059
|
+
data[knot * 4 + 1] = green;
|
|
1060
|
+
data[knot * 4 + 2] = blue;
|
|
1061
|
+
data[knot * 4 + 3] = 1;
|
|
1062
|
+
}
|
|
1063
|
+
rampTexture.needsUpdate = true;
|
|
1064
|
+
for (const [term, [red = 0, green = 0, blue = 0]] of harmonics.entries()) {
|
|
1065
|
+
const value = harmonicsUniform.array[term];
|
|
1066
|
+
if (value instanceof Vector3) value.set(red, green, blue);
|
|
1067
|
+
}
|
|
1068
|
+
heightFadeUniform.value.fromArray(heightFade);
|
|
1069
|
+
hazeColorUniform.value.fromArray(hazeColor);
|
|
1070
|
+
hazeScatterColorUniform.value.fromArray(hazeScatterColor);
|
|
1071
|
+
sunRadiance.value.copy(light.color).multiplyScalar(light.intensity);
|
|
1072
|
+
};
|
|
1073
|
+
//#endregion
|
|
1074
|
+
//#region src/nodes/computeStoneHarmonics.ts
|
|
1075
|
+
const computeStoneHarmonics = ([x = 0, y = 0, z = 0], harmonics) => {
|
|
1076
|
+
harmonics[0] = 1;
|
|
1077
|
+
harmonics[1] = x;
|
|
1078
|
+
harmonics[2] = y;
|
|
1079
|
+
harmonics[3] = z;
|
|
1080
|
+
harmonics[4] = x * y;
|
|
1081
|
+
harmonics[5] = y * z;
|
|
1082
|
+
harmonics[6] = x * z;
|
|
1083
|
+
harmonics[7] = x * x - z * z;
|
|
1084
|
+
harmonics[8] = 3 * y * y - 1;
|
|
1085
|
+
};
|
|
1086
|
+
//#endregion
|
|
1087
|
+
//#region src/nodes/computeStoneRampCoordinate.ts
|
|
1088
|
+
const computeStoneRampCoordinate = (facing, visibility) => .5 + .5 * facing * Math.max(visibility, 0) ** STONE_SHADOW_EXPONENT;
|
|
1089
|
+
//#endregion
|
|
514
1090
|
//#region src/nodes/createCausticsNode.ts
|
|
515
1091
|
const CAUSTIC_SCALE = .35;
|
|
516
1092
|
const CAUSTIC_SPEED = .6;
|
|
@@ -524,21 +1100,6 @@ const createCausticsNode = ({ lightColor }, { causticStrength, deepDepth, level
|
|
|
524
1100
|
return lightColor.mul(veins.mul(fade).mul(causticStrength));
|
|
525
1101
|
};
|
|
526
1102
|
//#endregion
|
|
527
|
-
//#region src/renderer/constants.ts
|
|
528
|
-
const GENSHIN_TONE_MAPPING = NeutralToneMapping;
|
|
529
|
-
//#endregion
|
|
530
|
-
//#region src/nodes/createGenshinRenderer.ts
|
|
531
|
-
const createGenshinRenderer = (canvas) => {
|
|
532
|
-
const isWindows = window.navigator.userAgent.includes("Windows");
|
|
533
|
-
const renderer = new WebGPURenderer({
|
|
534
|
-
antialias: false,
|
|
535
|
-
canvas,
|
|
536
|
-
...isWindows ? {} : { powerPreference: "high-performance" }
|
|
537
|
-
});
|
|
538
|
-
renderer.toneMapping = GENSHIN_TONE_MAPPING;
|
|
539
|
-
return renderer;
|
|
540
|
-
};
|
|
541
|
-
//#endregion
|
|
542
1103
|
//#region src/nodes/createRimNode.ts
|
|
543
1104
|
const RIM_POWER = 4;
|
|
544
1105
|
const createRimNode = ({ rimColor, rimStrength, sunDirection }) => {
|
|
@@ -598,11 +1159,36 @@ const createLightUniforms = () => ({
|
|
|
598
1159
|
sunDirection: uniform(new Vector3(0, 1, 0))
|
|
599
1160
|
});
|
|
600
1161
|
//#endregion
|
|
1162
|
+
//#region src/nodes/createSkyColorNode.ts
|
|
1163
|
+
const HALO_TOWARD_START = .3;
|
|
1164
|
+
const HALO_TOWARD_SPAN = .7;
|
|
1165
|
+
const HALO_SUN_HEIGHT_START = .2;
|
|
1166
|
+
const HALO_SUN_HEIGHT_SPAN = .3;
|
|
1167
|
+
const SUN_HALO_MIDDLE_SPREAD = .1;
|
|
1168
|
+
const SUN_HALO_MIDDLE_WEIGHT = .12;
|
|
1169
|
+
const SUN_HALO_OUTER_SPREAD = .01;
|
|
1170
|
+
const SUN_HALO_OUTER_WEIGHT = .03;
|
|
1171
|
+
const createSkyColorNode = ({ frontBackBlend, haloColor, haloHeight, horizonBackColor, horizonBand, horizonColor, sunDirection, sunHaloColor, sunHaloSize, zenithBackColor, zenithColor }, gradient, direction) => {
|
|
1172
|
+
const height = direction.y;
|
|
1173
|
+
const elevation = abs(asin(clamp(height, -1, 1)).mul(2 / Math.PI));
|
|
1174
|
+
const sunCosine = direction.dot(sunDirection);
|
|
1175
|
+
const toward = pow(max(sunCosine.mul(frontBackBlend).add(float(1).sub(frontBackBlend)), 0), 3);
|
|
1176
|
+
const top = mix(zenithBackColor, zenithColor, toward);
|
|
1177
|
+
const bottom = mix(horizonBackColor, horizonColor, toward);
|
|
1178
|
+
const bottomShare = texture(gradient, vec2(elevation.div(max(horizonBand, LEAST_DIVISOR)), .5)).r;
|
|
1179
|
+
const haloShare = texture(gradient, vec2(elevation.div(max(haloHeight, LEAST_DIVISOR)), .5)).g;
|
|
1180
|
+
const sunSide = saturate(sunCosine.mul(.5).add(.5));
|
|
1181
|
+
const towardSun = smoothstep(0, 1, max(sunSide.sub(HALO_TOWARD_START).div(HALO_TOWARD_SPAN), 0));
|
|
1182
|
+
const sunUp = smoothstep(0, 1, saturate(abs(sunDirection.y).sub(HALO_SUN_HEIGHT_START).div(HALO_SUN_HEIGHT_SPAN)));
|
|
1183
|
+
const halo = haloColor.mul(haloShare).mul(sunUp.mul(float(1).sub(towardSun)).add(towardSun));
|
|
1184
|
+
const spread = sunHaloSize.mul(abs(height));
|
|
1185
|
+
const sunHalo = min(pow(sunSide, spread), 1).add(min(pow(sunSide, spread.mul(SUN_HALO_MIDDLE_SPREAD)), 1).mul(SUN_HALO_MIDDLE_WEIGHT)).add(min(pow(sunSide, spread.mul(SUN_HALO_OUTER_SPREAD)), 1).mul(SUN_HALO_OUTER_WEIGHT)).mul(smoothstep(0, 1, max(sunSide.sub(.5).mul(2), 0)));
|
|
1186
|
+
return mix(top, bottom, bottomShare).add(halo).add(sunHaloColor.mul(sunHalo));
|
|
1187
|
+
};
|
|
1188
|
+
//#endregion
|
|
601
1189
|
//#region src/nodes/createSkyNode.ts
|
|
602
1190
|
const SUN_DISC_COSINE = Math.cos(.035);
|
|
603
1191
|
const MOON_DISC_COSINE = Math.cos(.05);
|
|
604
|
-
const SUN_GLOW_POWER = 12;
|
|
605
|
-
const HORIZON_GLOW_POWER = 3;
|
|
606
1192
|
const SUN_DISC_BRIGHTNESS = 6;
|
|
607
1193
|
const CLOUD_SCALE = 1.6;
|
|
608
1194
|
const CLOUD_MIN_HEIGHT = .04;
|
|
@@ -610,26 +1196,58 @@ const CLOUD_EDGE = .04;
|
|
|
610
1196
|
const CLOUD_SHADE_DEPTH = .18;
|
|
611
1197
|
const STAR_SCALE = 420;
|
|
612
1198
|
const STAR_THRESHOLD = .9975;
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
1199
|
+
const MOON_GLOW_SPREAD = .1;
|
|
1200
|
+
const MOON_GLOW_POWER = 6;
|
|
1201
|
+
const createSkyNode = (uniforms, gradient) => {
|
|
1202
|
+
const { cloudCoverage, cloudDrift, cloudLitColor, cloudShadeColor, lightColor, moonDirection, moonGlowColor, moonSize, starIntensity, sunDirection } = uniforms;
|
|
1203
|
+
return Fn(() => {
|
|
1204
|
+
const direction = normalWorldGeometry.normalize();
|
|
1205
|
+
const height = direction.y;
|
|
1206
|
+
const sunCosine = direction.dot(sunDirection);
|
|
1207
|
+
const gradientColor = createSkyColorNode(uniforms, gradient, direction);
|
|
1208
|
+
const moonCosine = saturate(direction.dot(moonDirection));
|
|
1209
|
+
const moonGlow = pow(max(moonCosine.sub(1).div(max(moonSize.mul(MOON_GLOW_SPREAD), LEAST_DIVISOR)).add(1), 0), MOON_GLOW_POWER);
|
|
1210
|
+
const sunVisibility = smoothstep(-.1, .05, sunDirection.y);
|
|
1211
|
+
const sunDisc = smoothstep(SUN_DISC_COSINE - 5e-4, SUN_DISC_COSINE, sunCosine).mul(sunVisibility);
|
|
1212
|
+
const moonDisc = smoothstep(MOON_DISC_COSINE - 5e-4, MOON_DISC_COSINE, direction.dot(moonDirection)).mul(smoothstep(-.05, .05, moonDirection.y));
|
|
1213
|
+
const stars = smoothstep(STAR_THRESHOLD, 1, mx_cell_noise_float(direction.mul(STAR_SCALE))).mul(starIntensity).mul(smoothstep(0, .1, height));
|
|
1214
|
+
const cloudPosition = direction.xz.div(max(height, CLOUD_MIN_HEIGHT)).mul(CLOUD_SCALE).add(cloudDrift);
|
|
1215
|
+
const cloudNoise = mx_fractal_noise_float(vec3(cloudPosition, 0), 4, 2, .5).mul(.5).add(.5);
|
|
1216
|
+
const cloudThreshold = float(1).sub(cloudCoverage);
|
|
1217
|
+
const cloudDensity = smoothstep(cloudThreshold, cloudThreshold.add(CLOUD_EDGE), cloudNoise).mul(smoothstep(0, .12, height));
|
|
1218
|
+
const cloudShade = smoothstep(cloudThreshold.add(CLOUD_SHADE_DEPTH), cloudThreshold.add(.22), cloudNoise);
|
|
1219
|
+
const cloudColor = mix(cloudLitColor, cloudShadeColor, cloudShade);
|
|
1220
|
+
const sky = gradientColor.add(moonGlowColor.mul(moonGlow)).add(vec3(stars)).add(vec3(.85, .88, 1).mul(moonDisc)).add(lightColor.mul(sunDisc.mul(SUN_DISC_BRIGHTNESS)));
|
|
1221
|
+
return mix(sky, cloudColor, cloudDensity);
|
|
1222
|
+
})();
|
|
1223
|
+
};
|
|
1224
|
+
//#endregion
|
|
1225
|
+
//#region src/nodes/createStoneLightUniforms.ts
|
|
1226
|
+
const createStoneLightUniforms = () => {
|
|
1227
|
+
const ramp = new DataTexture(/* @__PURE__ */ new Float32Array(64), 16, 1, RGBAFormat, FloatType);
|
|
1228
|
+
ramp.magFilter = LinearFilter;
|
|
1229
|
+
ramp.minFilter = LinearFilter;
|
|
1230
|
+
ramp.wrapS = ClampToEdgeWrapping;
|
|
1231
|
+
ramp.wrapT = ClampToEdgeWrapping;
|
|
1232
|
+
ramp.needsUpdate = true;
|
|
1233
|
+
return {
|
|
1234
|
+
harmonics: uniformArray(Array.from({ length: 9 }, () => new Vector3()), "vec3"),
|
|
1235
|
+
hazeColor: uniform(new Color()),
|
|
1236
|
+
hazeScatterColor: uniform(new Color()),
|
|
1237
|
+
heightFade: uniform(new Color()),
|
|
1238
|
+
ramp,
|
|
1239
|
+
sunRadiance: uniform(new Color())
|
|
1240
|
+
};
|
|
1241
|
+
};
|
|
1242
|
+
//#endregion
|
|
1243
|
+
//#region src/nodes/createStoneMaterial.ts
|
|
1244
|
+
const createStoneMaterial = ({ albedo, rimColor, rimPower, rimStrength }, stoneLight, glowNode = vec3(0)) => {
|
|
1245
|
+
const material = new StoneNodeMaterial(stoneLight);
|
|
1246
|
+
material.colorNode = color(new Color(albedo));
|
|
1247
|
+
const fresnel = float(1).sub(normalView.dot(positionViewDirection).saturate()).pow(rimPower);
|
|
1248
|
+
material.emissiveNode = color(new Color().fromArray(rimColor)).mul(fresnel.mul(rimStrength)).add(glowNode);
|
|
1249
|
+
return material;
|
|
1250
|
+
};
|
|
633
1251
|
//#endregion
|
|
634
1252
|
//#region src/nodes/createWaterMaterial.ts
|
|
635
1253
|
const RIPPLE_SCALE = .08;
|
|
@@ -726,14 +1344,13 @@ const createSimplexNoise = (seed) => {
|
|
|
726
1344
|
};
|
|
727
1345
|
//#endregion
|
|
728
1346
|
//#region src/post/computeGradeLut.ts
|
|
729
|
-
const MAX_BYTE = 255;
|
|
730
1347
|
const RED_LUMINANCE = .2126;
|
|
731
1348
|
const GREEN_LUMINANCE = .7152;
|
|
732
1349
|
const BLUE_LUMINANCE = .0722;
|
|
733
1350
|
const CONTRAST_PIVOT = .5;
|
|
734
1351
|
const toGradedByte = (channel, luminance, saturation, contrast, tint) => {
|
|
735
1352
|
const graded = (luminance + (channel - luminance) * saturation - CONTRAST_PIVOT) * contrast + CONTRAST_PIVOT + tint;
|
|
736
|
-
return Math.round(Math.min(Math.max(graded, 0), 1) *
|
|
1353
|
+
return Math.round(Math.min(Math.max(graded, 0), 1) * 255);
|
|
737
1354
|
};
|
|
738
1355
|
const computeGradeLut = ({ contrast, highlightTint, saturation, shadowTint, size }) => {
|
|
739
1356
|
const values = new Uint8Array(size * size * size * 4);
|
|
@@ -752,7 +1369,7 @@ const computeGradeLut = ({ contrast, highlightTint, saturation, shadowTint, size
|
|
|
752
1369
|
values[offset] = toGradedByte(red, luminance, saturation, contrast, redTint);
|
|
753
1370
|
values[offset + 1] = toGradedByte(green, luminance, saturation, contrast, greenTint);
|
|
754
1371
|
values[offset + 2] = toGradedByte(blue, luminance, saturation, contrast, blueTint);
|
|
755
|
-
values[offset + 3] =
|
|
1372
|
+
values[offset + 3] = 255;
|
|
756
1373
|
offset += 4;
|
|
757
1374
|
}
|
|
758
1375
|
return values;
|
|
@@ -764,6 +1381,7 @@ const createFogUniforms = () => ({
|
|
|
764
1381
|
color: uniform(new Color()),
|
|
765
1382
|
density: uniform(0),
|
|
766
1383
|
heightFalloff: uniform(0),
|
|
1384
|
+
maxOpacity: uniform(1),
|
|
767
1385
|
scatterColor: uniform(new Color()),
|
|
768
1386
|
scatterDirection: uniform(new Vector3(0, 1, 0)),
|
|
769
1387
|
scatterPower: uniform(1),
|
|
@@ -799,7 +1417,7 @@ const createGradeLutTexture = (gradeOptions) => {
|
|
|
799
1417
|
//#endregion
|
|
800
1418
|
//#region src/post/createHeightFogNode.ts
|
|
801
1419
|
const MIN_RAY_CLIMB = 1e-6;
|
|
802
|
-
const createHeightFogNode = (colorNode, depthNode, camera, { baseHeight, color, density, heightFalloff, scatterColor, scatterDirection, scatterPower, scatterStrength, startDistance }) => {
|
|
1420
|
+
const createHeightFogNode = (colorNode, depthNode, camera, { baseHeight, color, density, heightFalloff, maxOpacity, scatterColor, scatterDirection, scatterPower, scatterStrength, startDistance }, stoneHaze) => {
|
|
803
1421
|
const cameraWorldMatrix = uniform(camera.matrixWorld);
|
|
804
1422
|
const cameraProjectionMatrixInverse = uniform(camera.projectionMatrixInverse);
|
|
805
1423
|
return Fn(() => {
|
|
@@ -819,59 +1437,94 @@ const createHeightFogNode = (colorNode, depthNode, camera, { baseHeight, color,
|
|
|
819
1437
|
If(climb.abs().greaterThan(MIN_RAY_CLIMB), () => {
|
|
820
1438
|
opticalDepth.assign(densityAtStart.mul(float(1).sub(exp(fogLength.mul(climb).negate()))).div(climb));
|
|
821
1439
|
});
|
|
822
|
-
const opacity = float(1).sub(exp(opticalDepth.negate()));
|
|
823
|
-
const scatter = pow(max(ray.normalize().dot(scatterDirection), 0), scatterPower).mul(scatterStrength);
|
|
824
|
-
|
|
1440
|
+
const opacity = float(1).sub(exp(opticalDepth.negate())).mul(maxOpacity);
|
|
1441
|
+
const scatter = pow(max(ray.normalize().dot(scatterDirection), 0), scatterPower).mul(scatterStrength).min(1);
|
|
1442
|
+
const hazeColor = mix(color, scatterColor, scatter);
|
|
1443
|
+
const shownColor = stoneHaze ? mix(hazeColor, mix(stoneHaze.hazeColor, stoneHaze.hazeScatterColor, scatter), stoneHaze.maskNode.sample(uv()).r) : hazeColor;
|
|
1444
|
+
output.assign(vec4(mix(output.rgb, shownColor, opacity), output.a));
|
|
825
1445
|
});
|
|
826
1446
|
return output;
|
|
827
1447
|
})();
|
|
828
1448
|
};
|
|
829
1449
|
//#endregion
|
|
830
|
-
//#region src/
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1450
|
+
//#region src/post/createOcclusionNode.ts
|
|
1451
|
+
const OCCLUSION_THICKNESS_SHARE = 4;
|
|
1452
|
+
const createOcclusionNode = (depthNode, camera, radius) => {
|
|
1453
|
+
const cameraProjectionMatrixInverse = uniform(camera.projectionMatrixInverse);
|
|
1454
|
+
const normalNode = sample((uv) => getNormalFromDepth(uv, depthNode, cameraProjectionMatrixInverse));
|
|
1455
|
+
const occlusionNode = new GTAONode(depthNode, normalNode, camera);
|
|
1456
|
+
occlusionNode.radius.value = radius;
|
|
1457
|
+
occlusionNode.thickness.value = radius * OCCLUSION_THICKNESS_SHARE;
|
|
1458
|
+
return occlusionNode;
|
|
1459
|
+
};
|
|
836
1460
|
//#endregion
|
|
837
1461
|
//#region src/post/createPostPipeline.ts
|
|
838
1462
|
const BLOOM_STRENGTH = .35;
|
|
839
1463
|
const BLOOM_RADIUS = .4;
|
|
840
1464
|
const BLOOM_THRESHOLD = .85;
|
|
841
1465
|
const MIN_VIEW_DISTANCE = .001;
|
|
842
|
-
const createPostPipeline = ({ camera, fogUniforms, godraysLight, gradeLutTexture, postUniforms: { godraysColor, gradeIntensity, outlineColor, outlineFadeDistance, outlineThickness }, qualityTierSettings: { antialiasingMode, godraysStepCount, isBloomEnabled }, renderer, scene }) => {
|
|
1466
|
+
const createPostPipeline = ({ camera, fogUniforms, godraysLight, gradeLutTexture, isBloomed = true, occlusionRadius, postUniforms: { godraysColor, gradeIntensity, outlineColor, outlineFadeDistance, outlineThickness }, qualityTierSettings: { antialiasingMode, godraysStepCount, isBloomEnabled, isOcclusionEnabled }, renderer, scene, stoneLight }) => {
|
|
843
1467
|
const renderPipeline = new RenderPipeline(renderer);
|
|
844
1468
|
renderPipeline.outputColorTransform = false;
|
|
845
1469
|
const viewDistance = modelViewMatrix.mul(vec4(positionLocal, 1)).z.negate().max(MIN_VIEW_DISTANCE);
|
|
846
1470
|
const thicknessNode = outlineThickness.mul(outlineFadeDistance.div(viewDistance).min(1));
|
|
847
1471
|
const scenePass = new ToonOutlinePassNode(scene, camera, outlineColor, thicknessNode, float(1));
|
|
848
1472
|
const isTraa = antialiasingMode === "Traa";
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
1473
|
+
const sceneOutputs = { output };
|
|
1474
|
+
if (isTraa) sceneOutputs.velocity = velocity;
|
|
1475
|
+
if (stoneLight) sceneOutputs[STONE_MASK_OUTPUT] = float(0);
|
|
1476
|
+
scenePass.setMRT(mrt(sceneOutputs));
|
|
853
1477
|
const sceneColor = scenePass.getTextureNode("output");
|
|
854
1478
|
const sceneDepth = scenePass.getTextureNode("depth");
|
|
855
|
-
const
|
|
1479
|
+
const passNodes = [scenePass];
|
|
1480
|
+
const postPipeline = {
|
|
1481
|
+
dispose: () => {
|
|
1482
|
+
renderPipeline.dispose();
|
|
1483
|
+
for (const passNode of passNodes) passNode.dispose();
|
|
1484
|
+
},
|
|
1485
|
+
renderPipeline
|
|
1486
|
+
};
|
|
856
1487
|
let litNode = sceneColor;
|
|
857
|
-
if (
|
|
1488
|
+
if (occlusionRadius && isOcclusionEnabled) {
|
|
1489
|
+
const occlusionNode = createOcclusionNode(sceneDepth, camera, occlusionRadius);
|
|
1490
|
+
passNodes.push(occlusionNode);
|
|
1491
|
+
litNode = vec4(sceneColor.rgb.mul(occlusionNode.getTextureNode().r), sceneColor.a);
|
|
1492
|
+
}
|
|
1493
|
+
if (godraysLight && godraysStepCount > 0) {
|
|
858
1494
|
const godraysNode = godrays(sceneDepth, camera, godraysLight);
|
|
859
1495
|
godraysNode.raymarchSteps.value = godraysStepCount;
|
|
860
1496
|
const blurredGodrays = bilateralBlur(godraysNode.getTextureNode());
|
|
861
|
-
|
|
1497
|
+
passNodes.push(godraysNode, blurredGodrays);
|
|
1498
|
+
litNode = depthAwareBlend(litNode, blurredGodrays.getTextureNode(), sceneDepth, camera, { blendColor: godraysColor });
|
|
862
1499
|
postPipeline.godraysNode = godraysNode;
|
|
863
1500
|
}
|
|
864
|
-
|
|
1501
|
+
if (stoneLight) scenePass.getTexture(STONE_MASK_OUTPUT).type = UnsignedByteType;
|
|
1502
|
+
const foggedNode = createHeightFogNode(litNode, sceneDepth, camera, fogUniforms, stoneLight && {
|
|
1503
|
+
hazeColor: stoneLight.hazeColor,
|
|
1504
|
+
hazeScatterColor: stoneLight.hazeScatterColor,
|
|
1505
|
+
maskNode: scenePass.getTextureNode("stoneMask")
|
|
1506
|
+
});
|
|
865
1507
|
let bloomedNode = foggedNode;
|
|
866
|
-
if (isBloomEnabled) {
|
|
1508
|
+
if (isBloomed && isBloomEnabled) {
|
|
867
1509
|
const bloomNode = bloom(foggedNode, BLOOM_STRENGTH, BLOOM_RADIUS, BLOOM_THRESHOLD);
|
|
1510
|
+
passNodes.push(bloomNode);
|
|
868
1511
|
bloomedNode = foggedNode.add(bloomNode);
|
|
869
1512
|
postPipeline.bloomNode = bloomNode;
|
|
870
1513
|
}
|
|
871
|
-
|
|
1514
|
+
let resolvedNode = bloomedNode;
|
|
1515
|
+
if (isTraa) {
|
|
1516
|
+
const traaNode = traa(bloomedNode, sceneDepth, scenePass.getTextureNode("velocity"), camera);
|
|
1517
|
+
passNodes.push(traaNode);
|
|
1518
|
+
resolvedNode = traaNode;
|
|
1519
|
+
}
|
|
872
1520
|
const displayNode = renderOutput(resolvedNode);
|
|
873
|
-
const gradedNode = lut3D(displayNode, texture3D(gradeLutTexture), gradeLutTexture.image.width, gradeIntensity);
|
|
874
|
-
renderPipeline.outputNode =
|
|
1521
|
+
const gradedNode = gradeLutTexture ? lut3D(displayNode, texture3D(gradeLutTexture), gradeLutTexture.image.width, gradeIntensity) : displayNode;
|
|
1522
|
+
if (isTraa) renderPipeline.outputNode = gradedNode;
|
|
1523
|
+
else {
|
|
1524
|
+
const smaaNode = smaa(gradedNode);
|
|
1525
|
+
passNodes.push(smaaNode);
|
|
1526
|
+
renderPipeline.outputNode = smaaNode;
|
|
1527
|
+
}
|
|
875
1528
|
return postPipeline;
|
|
876
1529
|
};
|
|
877
1530
|
//#endregion
|
|
@@ -884,14 +1537,6 @@ const createPostUniforms = () => ({
|
|
|
884
1537
|
outlineThickness: uniform(.003)
|
|
885
1538
|
});
|
|
886
1539
|
//#endregion
|
|
887
|
-
//#region src/renderer/QualityTier.ts
|
|
888
|
-
let QualityTier = /* @__PURE__ */ function(QualityTier) {
|
|
889
|
-
QualityTier["High"] = "High";
|
|
890
|
-
QualityTier["Low"] = "Low";
|
|
891
|
-
QualityTier["Medium"] = "Medium";
|
|
892
|
-
return QualityTier;
|
|
893
|
-
}({});
|
|
894
|
-
//#endregion
|
|
895
1540
|
//#region src/renderer/QualityTierSettingsMap.ts
|
|
896
1541
|
const QualityTierSettingsMap = {
|
|
897
1542
|
["High"]: {
|
|
@@ -900,6 +1545,7 @@ const QualityTierSettingsMap = {
|
|
|
900
1545
|
godraysStepCount: 60,
|
|
901
1546
|
grassDensity: 1,
|
|
902
1547
|
isBloomEnabled: true,
|
|
1548
|
+
isOcclusionEnabled: true,
|
|
903
1549
|
maxPixelRatio: 2,
|
|
904
1550
|
shadowMapSize: 2048
|
|
905
1551
|
},
|
|
@@ -909,6 +1555,7 @@ const QualityTierSettingsMap = {
|
|
|
909
1555
|
godraysStepCount: 0,
|
|
910
1556
|
grassDensity: .3,
|
|
911
1557
|
isBloomEnabled: false,
|
|
1558
|
+
isOcclusionEnabled: false,
|
|
912
1559
|
maxPixelRatio: 1,
|
|
913
1560
|
shadowMapSize: 1024
|
|
914
1561
|
},
|
|
@@ -918,11 +1565,37 @@ const QualityTierSettingsMap = {
|
|
|
918
1565
|
godraysStepCount: 30,
|
|
919
1566
|
grassDensity: .6,
|
|
920
1567
|
isBloomEnabled: true,
|
|
1568
|
+
isOcclusionEnabled: false,
|
|
921
1569
|
maxPixelRatio: 1.5,
|
|
922
1570
|
shadowMapSize: 1024
|
|
923
1571
|
}
|
|
924
1572
|
};
|
|
925
1573
|
//#endregion
|
|
1574
|
+
//#region src/renderer/attachInspector.ts
|
|
1575
|
+
const attachInspector = async (renderer) => {
|
|
1576
|
+
const { Inspector } = await import("three/examples/jsm/inspector/Inspector.js");
|
|
1577
|
+
if (renderer.inspector instanceof Inspector) return renderer.inspector;
|
|
1578
|
+
const inspector = new Inspector();
|
|
1579
|
+
renderer.inspector = inspector;
|
|
1580
|
+
return inspector;
|
|
1581
|
+
};
|
|
1582
|
+
//#endregion
|
|
1583
|
+
//#region src/renderer/constants.ts
|
|
1584
|
+
const GENSHIN_TONE_MAPPING = NeutralToneMapping;
|
|
1585
|
+
//#endregion
|
|
1586
|
+
//#region src/renderer/createGenshinRenderer.ts
|
|
1587
|
+
const createGenshinRenderer = (canvas) => {
|
|
1588
|
+
const isWindows = window.navigator.userAgent.includes("Windows");
|
|
1589
|
+
const renderer = new WebGPURenderer({
|
|
1590
|
+
antialias: false,
|
|
1591
|
+
canvas,
|
|
1592
|
+
...isWindows ? {} : { powerPreference: "high-performance" }
|
|
1593
|
+
});
|
|
1594
|
+
renderer.toneMapping = GENSHIN_TONE_MAPPING;
|
|
1595
|
+
if (import.meta.env?.DEV && !window.navigator.webdriver) attachInspector(renderer);
|
|
1596
|
+
return renderer;
|
|
1597
|
+
};
|
|
1598
|
+
//#endregion
|
|
926
1599
|
//#region src/renderer/getScreenDirection.ts
|
|
927
1600
|
const X_AXIS = new Vector3(1, 0, 0);
|
|
928
1601
|
const Y_AXIS = new Vector3(0, 1, 0);
|
|
@@ -981,27 +1654,30 @@ const createTileStreamer = ({ disposeTile, maxCachedCount, maxPendingCount, requ
|
|
|
981
1654
|
});
|
|
982
1655
|
evictStale();
|
|
983
1656
|
},
|
|
984
|
-
update: (
|
|
1657
|
+
update: (wanted, drawn, surrounding) => {
|
|
985
1658
|
frame++;
|
|
986
|
-
|
|
987
|
-
|
|
1659
|
+
for (const { count, keys } of [
|
|
1660
|
+
wanted,
|
|
1661
|
+
drawn,
|
|
1662
|
+
surrounding
|
|
1663
|
+
]) for (let index = 0; index < count; index++) {
|
|
988
1664
|
const key = keys[index] ?? 0;
|
|
989
1665
|
const cachedTile = cachedTileMap.get(key);
|
|
990
1666
|
if (cachedTile) cachedTile.lastWantedFrame = frame;
|
|
991
|
-
else
|
|
992
|
-
if (pendingWantedFrameMap.has(key)) pendingWantedFrameMap.set(key, frame);
|
|
993
|
-
coarsestLevel = Math.max(coarsestLevel, getTerrainTileLevel(key));
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
for (let index = 0; index < drawn.count; index++) {
|
|
997
|
-
const cachedTile = cachedTileMap.get(drawn.keys[index] ?? 0);
|
|
998
|
-
if (cachedTile) cachedTile.lastWantedFrame = frame;
|
|
1667
|
+
else if (pendingWantedFrameMap.has(key)) pendingWantedFrameMap.set(key, frame);
|
|
999
1668
|
}
|
|
1000
|
-
for (
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1669
|
+
for (const { count, keys } of [wanted, surrounding]) {
|
|
1670
|
+
let coarsestLevel = 0;
|
|
1671
|
+
for (let index = 0; index < count; index++) {
|
|
1672
|
+
const key = keys[index] ?? 0;
|
|
1673
|
+
if (!cachedTileMap.has(key)) coarsestLevel = Math.max(coarsestLevel, getTerrainTileLevel(key));
|
|
1674
|
+
}
|
|
1675
|
+
for (let level = coarsestLevel; level >= 0 && pendingWantedFrameMap.size < maxPendingCount; level--) for (let index = 0; index < count && pendingWantedFrameMap.size < maxPendingCount; index++) {
|
|
1676
|
+
const key = keys[index] ?? 0;
|
|
1677
|
+
if (getTerrainTileLevel(key) !== level || cachedTileMap.has(key) || pendingWantedFrameMap.has(key)) continue;
|
|
1678
|
+
pendingWantedFrameMap.set(key, frame);
|
|
1679
|
+
requestTile(key);
|
|
1680
|
+
}
|
|
1005
1681
|
}
|
|
1006
1682
|
}
|
|
1007
1683
|
};
|
|
@@ -1084,6 +1760,25 @@ const computeTerrainTile = ({ cellsPerSide, finestTileSize, getHeight, key, writ
|
|
|
1084
1760
|
};
|
|
1085
1761
|
};
|
|
1086
1762
|
//#endregion
|
|
1763
|
+
//#region src/terrain/createGaussianHillsHeight.ts
|
|
1764
|
+
const REACH_WIDTHS = 3;
|
|
1765
|
+
const CELL_SIZE = 64;
|
|
1766
|
+
const toCellKey = (column, row) => `${column},${row}`;
|
|
1767
|
+
const createGaussianHillsHeight = ({ base, hills }) => {
|
|
1768
|
+
const filed = hills.flatMap((hill) => {
|
|
1769
|
+
const reach = REACH_WIDTHS * hill.width;
|
|
1770
|
+
const cells = [];
|
|
1771
|
+
for (let column = Math.floor((hill.x - reach) / CELL_SIZE); column <= Math.floor((hill.x + reach) / CELL_SIZE); column++) for (let row = Math.floor((hill.z - reach) / CELL_SIZE); row <= Math.floor((hill.z + reach) / CELL_SIZE); row++) cells.push([toCellKey(column, row), hill]);
|
|
1772
|
+
return cells;
|
|
1773
|
+
});
|
|
1774
|
+
const cellHillsMap = new Map(Array.from(Map.groupBy(filed, ([key]) => key), ([key, entries]) => [key, entries.map(([, hill]) => hill)]));
|
|
1775
|
+
return (x, z) => {
|
|
1776
|
+
let total = base;
|
|
1777
|
+
for (const { height, width, x: hillX, z: hillZ } of cellHillsMap.get(toCellKey(Math.floor(x / CELL_SIZE), Math.floor(z / CELL_SIZE))) ?? []) total += height * Math.exp(-((x - hillX) ** 2 + (z - hillZ) ** 2) / (2 * width * width));
|
|
1778
|
+
return total;
|
|
1779
|
+
};
|
|
1780
|
+
};
|
|
1781
|
+
//#endregion
|
|
1087
1782
|
//#region src/terrain/createTerrainMaterial.ts
|
|
1088
1783
|
const createTerrainMaterial = ({ finestRange, morphShare }, toonMaterialOptions, waterUniforms) => {
|
|
1089
1784
|
const terrainMaterial = createToonMaterial({
|
|
@@ -1250,7 +1945,7 @@ const createGrassGeometry = (segmentCount, bladeCount) => {
|
|
|
1250
1945
|
const { indices, positions } = computeGrassBlade(segmentCount);
|
|
1251
1946
|
const grassGeometry = new InstancedBufferGeometry();
|
|
1252
1947
|
grassGeometry.setAttribute("position", new BufferAttribute(positions, 3));
|
|
1253
|
-
grassGeometry.setAttribute("normal", new BufferAttribute(Float32Array.from({ length: positions.length }, (
|
|
1948
|
+
grassGeometry.setAttribute("normal", new BufferAttribute(Float32Array.from({ length: positions.length }, (_value, index) => index % 3 === 1 ? 1 : 0), 3));
|
|
1254
1949
|
grassGeometry.setIndex(new BufferAttribute(indices, 1));
|
|
1255
1950
|
grassGeometry.instanceCount = bladeCount;
|
|
1256
1951
|
return grassGeometry;
|
|
@@ -1316,7 +2011,7 @@ const createGroundCapture = (size, resolution) => {
|
|
|
1316
2011
|
};
|
|
1317
2012
|
//#endregion
|
|
1318
2013
|
//#region src/vegetation/renderGroundCapture.ts
|
|
1319
|
-
const
|
|
2014
|
+
const CLEAR_COLOR = new Color();
|
|
1320
2015
|
const previousClearColor = new Color();
|
|
1321
2016
|
const renderGroundCapture = (renderer, scene, { camera, center, material, renderTarget }, { maxHeight }, worldCenter, origin) => {
|
|
1322
2017
|
center.value.set(worldCenter.x, worldCenter.z);
|
|
@@ -1331,7 +2026,7 @@ const renderGroundCapture = (renderer, scene, { camera, center, material, render
|
|
|
1331
2026
|
const previousBackgroundNode = scene.backgroundNode;
|
|
1332
2027
|
scene.overrideMaterial = material;
|
|
1333
2028
|
scene.backgroundNode = null;
|
|
1334
|
-
renderer.setClearColor(
|
|
2029
|
+
renderer.setClearColor(CLEAR_COLOR, 0);
|
|
1335
2030
|
renderer.setRenderTarget(renderTarget);
|
|
1336
2031
|
renderer.clear();
|
|
1337
2032
|
renderer.render(scene, camera);
|
|
@@ -1345,6 +2040,7 @@ const renderGroundCapture = (renderer, scene, { camera, center, material, render
|
|
|
1345
2040
|
const createUnderwaterFogState = () => ({
|
|
1346
2041
|
aboveDensity: 0,
|
|
1347
2042
|
aboveHeightFalloff: 0,
|
|
2043
|
+
aboveMaxOpacity: 1,
|
|
1348
2044
|
aboveStartDistance: 0,
|
|
1349
2045
|
isUnderwater: false
|
|
1350
2046
|
});
|
|
@@ -1363,18 +2059,21 @@ const createWaterUniforms = () => ({
|
|
|
1363
2059
|
});
|
|
1364
2060
|
//#endregion
|
|
1365
2061
|
//#region src/water/updateUnderwaterFog.ts
|
|
1366
|
-
const updateUnderwaterFog = (eyeHeight, { level, underwaterFogColor, underwaterFogDensity }, { color, density, heightFalloff, startDistance }, underwaterFogState) => {
|
|
2062
|
+
const updateUnderwaterFog = (eyeHeight, { level, underwaterFogColor, underwaterFogDensity }, { color, density, heightFalloff, maxOpacity, startDistance }, underwaterFogState) => {
|
|
1367
2063
|
const isUnderwater = eyeHeight < level.value;
|
|
1368
2064
|
if (isUnderwater && !underwaterFogState.isUnderwater) {
|
|
1369
2065
|
underwaterFogState.aboveDensity = density.value;
|
|
1370
2066
|
underwaterFogState.aboveHeightFalloff = heightFalloff.value;
|
|
2067
|
+
underwaterFogState.aboveMaxOpacity = maxOpacity.value;
|
|
1371
2068
|
underwaterFogState.aboveStartDistance = startDistance.value;
|
|
1372
2069
|
density.value = underwaterFogDensity.value;
|
|
1373
2070
|
heightFalloff.value = 0;
|
|
2071
|
+
maxOpacity.value = 1;
|
|
1374
2072
|
startDistance.value = 0;
|
|
1375
2073
|
} else if (!isUnderwater && underwaterFogState.isUnderwater) {
|
|
1376
2074
|
density.value = underwaterFogState.aboveDensity;
|
|
1377
2075
|
heightFalloff.value = underwaterFogState.aboveHeightFalloff;
|
|
2076
|
+
maxOpacity.value = underwaterFogState.aboveMaxOpacity;
|
|
1378
2077
|
startDistance.value = underwaterFogState.aboveStartDistance;
|
|
1379
2078
|
}
|
|
1380
2079
|
underwaterFogState.isUnderwater = isUnderwater;
|
|
@@ -1417,4 +2116,4 @@ const computeOutlineDistance = (outline, x, z) => {
|
|
|
1417
2116
|
return isInside ? 0 : Math.sqrt(nearestSquared);
|
|
1418
2117
|
};
|
|
1419
2118
|
//#endregion
|
|
1420
|
-
export { AntialiasingMode, GAME_MINUTES_PER_SECOND, GENSHIN_TONE_MAPPING, 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, createCloudAtlasTexture,
|
|
2119
|
+
export { A4_FREQUENCY, A4_PITCH, AntialiasingMode, DEFAULT_SKY_GRADIENT, DEFAULT_SKY_SHAPE, GAME_MINUTES_PER_SECOND, GENSHIN_TONE_MAPPING, LEAST_DIVISOR, MAX_BYTE, MIDI_VELOCITY_MAX, MINUTES_PER_DAY, MUSIC_EXPRESSION_WINDOW_SECONDS, MUSIC_LOOKAHEAD_SECONDS, MUSIC_NOISE_BAND_CENTRES, MUSIC_NOISE_LENGTH, MUSIC_SCHEDULE_INTERVAL_MS, QualityTier, QualityTierSettingsMap, RELEASE_TIME_CONSTANTS, STONE_HARMONIC_COUNT, STONE_HEIGHT_FALLOFF, STONE_MASK_OUTPUT, STONE_RAMP_KNOT_COUNT, STONE_SHADOW_EXPONENT, StoneLightingModel, StoneNodeMaterial, TERRAIN_LAYER, TILE_INDEX_OFFSET, TILE_INDEX_SPAN, ToonNodeMaterial, addSmoothLoop, advanceGameClock, applySkyState, applyStoneLight, attachInspector, collectMusicNotes, collectMusicSegments, computeGradeLut, computeGrassBlade, computeLeafCards, computeNoiseSamples, computeOriginShift, computeOutlineDistance, computeRampValues, computeSoundEffectSamples, computeStoneHarmonics, computeStoneRampCoordinate, computeSunDirection, computeTerrainIndices, computeTerrainTile, computeTreeSkeleton, createBoxesGeometry, createCausticsNode, createCloudAtlasTexture, createCloudBandSprite, createFogUniforms, createGaussianHillsHeight, createGenshinRenderer, createGodraysLight, createGradeLutTexture, createGrassGeometry, createGrassMaterial, createGroundCapture, createHeightFogNode, createInstrumentWave, createLatheStackGeometry, createLeafMaterial, createLightUniforms, createMusicPlayer, createNoiseBuffer, createOcclusionNode, createPostPipeline, createPostUniforms, createRampTexture, createRimNode, createSeededRandom, createSimplexNoise, createSkyColorNode, createSkyGradientTexture, createSkyNode, createSkyState, createSkyUniforms, createSoundEffectBuffer, createStatueGeometry, createStoneLightUniforms, createStoneMaterial, createSunLight, createSunVisibilityNode, createTerrainMaterial, createTerrainSelection, createTerrainTileGeometry, createTileStreamer, createToonMaterial, createTreeGeometry, createUnderwaterFogState, createWaterMaterial, createWaterUniforms, createWindNode, createWindUniforms, getCloudAtlasColumns, getParentTerrainTileKey, getScreenDirection, getTerrainTileColumn, getTerrainTileKey, getTerrainTileLevel, getTerrainTileRow, mergeGeometryParts, orderByViewDepth, placeCloudBand, renderGroundCapture, renderMusicSegment, resolveTerrainDraws, sampleSkyState, scheduleMusicExpression, scheduleMusicNote, selectMusicSample, selectTerrainTiles, toSceneColor, toneMapNeutral, transformFourier, updateUnderwaterFog };
|