genshin-engine 3.9.0 → 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 +515 -276
- package/dist/index.js +720 -183
- package/package.json +6 -5
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Box3, BoxGeometry, BufferAttribute, BufferGeometry, CanvasTexture, ClampToEdgeWrapping, Color, CylinderGeometry, Data3DTexture, DataTexture, DirectionalLight, DoubleSide, 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, exp, exp2, float, getViewPosition, hash, instanceIndex, instancedBufferAttribute, max, min, 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, saturate, screenUV, sin, smoothstep, step, texture, texture3D, time, uniform, uv, varying, vec2, vec3, vec4, velocity, viewportDepthTexture, viewportSharedTexture } from "three/tsl";
|
|
3
|
-
import {
|
|
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";
|
|
@@ -22,10 +23,7 @@ const DEFAULT_SKY_SHAPE = {
|
|
|
22
23
|
const DEFAULT_SAMPLE_COUNT = 33;
|
|
23
24
|
const DEFAULT_SKY_GRADIENT = {
|
|
24
25
|
green: Array.from({ length: DEFAULT_SAMPLE_COUNT }, () => 0),
|
|
25
|
-
red: Array.from({ length: DEFAULT_SAMPLE_COUNT }, (
|
|
26
|
-
const share = index / 32;
|
|
27
|
-
return 1 - share * share * (3 - 2 * share);
|
|
28
|
-
})
|
|
26
|
+
red: Array.from({ length: DEFAULT_SAMPLE_COUNT }, (_value, index) => 1 - MathUtils.smoothstep(index, 0, 32))
|
|
29
27
|
};
|
|
30
28
|
//#endregion
|
|
31
29
|
//#region src/post/toneMapNeutral.ts
|
|
@@ -33,44 +31,88 @@ const TOE_END = .08;
|
|
|
33
31
|
const TOE_OFFSET = .04;
|
|
34
32
|
const COMPRESSION_START = .76;
|
|
35
33
|
const DESATURATION = .15;
|
|
36
|
-
const
|
|
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
|
+
]) => {
|
|
37
40
|
const lowest = Math.min(red, green, blue);
|
|
38
41
|
const offset = lowest < TOE_END ? lowest - 6.25 * lowest * lowest : TOE_OFFSET;
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
+
}
|
|
46
52
|
const headroom = .24;
|
|
47
53
|
const newPeak = 1 - headroom * headroom / (peak + headroom - COMPRESSION_START);
|
|
48
54
|
const whiteShare = 1 - 1 / (DESATURATION * (peak - newPeak) + 1);
|
|
49
|
-
|
|
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;
|
|
50
59
|
};
|
|
51
60
|
//#endregion
|
|
52
61
|
//#region src/post/toSceneColor.ts
|
|
53
62
|
const INVERSE_STEP_COUNT = 16;
|
|
54
63
|
const SLOPE_NUDGE = 1e-4;
|
|
55
64
|
const SCENE_CHANNEL_LIMIT = 1.5;
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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;
|
|
63
99
|
for (let step = 0; step < INVERSE_STEP_COUNT; step++) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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;
|
|
68
106
|
nudged[index] = channel + SLOPE_NUDGE;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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];
|
|
72
114
|
}
|
|
73
|
-
return
|
|
115
|
+
return sceneColor.setRGB(input[0], input[1], input[2]);
|
|
74
116
|
};
|
|
75
117
|
//#endregion
|
|
76
118
|
//#region src/atmosphere/applySkyState.ts
|
|
@@ -88,30 +130,32 @@ const applySkyState = (skyState, { fogUniforms, godraysLight, hemisphere, light,
|
|
|
88
130
|
lightUniforms.lightColor.value.copy(lightColor);
|
|
89
131
|
lightUniforms.sunDirection.value.copy(lightDirection);
|
|
90
132
|
lightUniforms.rimColor.value.copy(horizonColor);
|
|
91
|
-
|
|
92
|
-
fogUniforms.scatterColor.value
|
|
133
|
+
toSceneColor(skyState.fogColor ?? horizonColor, fogUniforms.color.value);
|
|
134
|
+
toSceneColor(lightColor, fogUniforms.scatterColor.value);
|
|
93
135
|
fogUniforms.scatterDirection.value.copy(lightDirection);
|
|
94
136
|
postUniforms.godraysColor.value.copy(lightColor);
|
|
95
|
-
skyUniforms.cloudLitColor.value
|
|
96
|
-
skyUniforms.cloudShadeColor.value
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
skyUniforms.horizonColor.value
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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);
|
|
104
146
|
skyUniforms.lightColor.value.copy(lightColor);
|
|
105
147
|
skyUniforms.moonDirection.value.copy(skyState.moonDirection);
|
|
148
|
+
toSceneColor(skyState.moonGlowColor ?? BLACK, skyUniforms.moonGlowColor.value);
|
|
106
149
|
skyUniforms.starIntensity.value = skyState.starIntensity;
|
|
107
150
|
skyUniforms.sunDirection.value.copy(skyState.sunDirection);
|
|
108
|
-
skyUniforms.zenithColor.value
|
|
151
|
+
toSceneColor(skyState.zenithColor, skyUniforms.zenithColor.value);
|
|
109
152
|
const { frontBackBlend, haloHeight, horizonBand, moonSize, sunHaloSize } = skyState.shape ?? DEFAULT_SKY_SHAPE;
|
|
110
153
|
skyUniforms.frontBackBlend.value = frontBackBlend;
|
|
111
154
|
skyUniforms.haloHeight.value = haloHeight;
|
|
112
155
|
skyUniforms.horizonBand.value = horizonBand;
|
|
113
156
|
skyUniforms.moonSize.value = moonSize;
|
|
114
157
|
skyUniforms.sunHaloSize.value = sunHaloSize;
|
|
158
|
+
if (!godraysLight) return;
|
|
115
159
|
godraysDirection.subVectors(godraysLight.position, godraysLight.target.position).normalize();
|
|
116
160
|
if (godraysDirection.dot(lightDirection) >= GODRAYS_REDRAW_COSINE) return;
|
|
117
161
|
godraysLight.position.copy(godraysLight.target.position).addScaledVector(lightDirection, lightDistance);
|
|
@@ -129,30 +173,33 @@ const computeSunDirection = (minutes, tilt, sunDirection) => {
|
|
|
129
173
|
return sunDirection.set(Math.sin(hourAngle), height * Math.cos(tilt), height * Math.sin(tilt));
|
|
130
174
|
};
|
|
131
175
|
//#endregion
|
|
176
|
+
//#region src/atmosphere/getCloudAtlasColumns.ts
|
|
177
|
+
const getCloudAtlasColumns = (spriteCount) => Math.ceil(Math.sqrt(spriteCount));
|
|
178
|
+
//#endregion
|
|
132
179
|
//#region src/materials/addSmoothLoop.ts
|
|
133
180
|
const addSmoothLoop = (path, points) => {
|
|
134
181
|
if (points.length < 3) return;
|
|
135
|
-
const
|
|
182
|
+
const getMidpoint = (index) => {
|
|
136
183
|
const [startX = 0, startY = 0] = points[index % points.length] ?? [];
|
|
137
184
|
const [endX = 0, endY = 0] = points[(index + 1) % points.length] ?? [];
|
|
138
185
|
return [(startX + endX) / 2, (startY + endY) / 2];
|
|
139
186
|
};
|
|
140
|
-
path.moveTo(...
|
|
141
|
-
for (const [index, [x, y]] of points.entries()) path.quadraticCurveTo(x, y, ...
|
|
187
|
+
path.moveTo(...getMidpoint(points.length - 1));
|
|
188
|
+
for (const [index, [x, y]] of points.entries()) path.quadraticCurveTo(x, y, ...getMidpoint(index));
|
|
142
189
|
path.closePath();
|
|
143
190
|
};
|
|
144
191
|
//#endregion
|
|
145
192
|
//#region src/atmosphere/createCloudAtlasTexture.ts
|
|
146
|
-
const EDGE_BLUR_SHARE = .
|
|
147
|
-
const
|
|
193
|
+
const EDGE_BLUR_SHARE = .02;
|
|
194
|
+
const CROWN_BLUR_SHARE = .035;
|
|
148
195
|
const createCloudAtlasTexture = (sprites, cellSize) => {
|
|
149
196
|
const columns = getCloudAtlasColumns(sprites.length);
|
|
150
197
|
const canvas = new OffscreenCanvas(columns * cellSize, columns * cellSize);
|
|
151
198
|
const context = canvas.getContext("2d");
|
|
152
199
|
if (context) {
|
|
153
200
|
context.globalCompositeOperation = "lighter";
|
|
154
|
-
|
|
155
|
-
|
|
201
|
+
const fill = (loops, column, row, style, blurShare) => {
|
|
202
|
+
context.filter = `blur(${cellSize * blurShare}px)`;
|
|
156
203
|
const path = new Path2D();
|
|
157
204
|
const toCanvas = ([x, y]) => [(column + x) * cellSize, (row + 1 - y) * cellSize];
|
|
158
205
|
for (const loop of loops) addSmoothLoop(path, loop.map((point) => toCanvas(point)));
|
|
@@ -162,8 +209,8 @@ const createCloudAtlasTexture = (sprites, cellSize) => {
|
|
|
162
209
|
for (const [index, { lit, outline }] of sprites.entries()) {
|
|
163
210
|
const column = index % columns;
|
|
164
211
|
const row = Math.floor(index / columns);
|
|
165
|
-
fill(outline, column, row, "#f00");
|
|
166
|
-
fill(lit, column, row, "#0f0");
|
|
212
|
+
fill(outline, column, row, "#f00", EDGE_BLUR_SHARE);
|
|
213
|
+
fill(lit, column, row, "#0f0", CROWN_BLUR_SHARE);
|
|
167
214
|
}
|
|
168
215
|
}
|
|
169
216
|
const texture = new CanvasTexture(canvas);
|
|
@@ -175,18 +222,38 @@ const createCloudAtlasTexture = (sprites, cellSize) => {
|
|
|
175
222
|
};
|
|
176
223
|
//#endregion
|
|
177
224
|
//#region src/atmosphere/orderByViewDepth.ts
|
|
178
|
-
const orderByViewDepth = (places, { elements }) => {
|
|
179
|
-
|
|
180
|
-
|
|
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;
|
|
181
247
|
};
|
|
182
248
|
//#endregion
|
|
183
249
|
//#region src/atmosphere/createCloudBandSprite.ts
|
|
184
250
|
const BELOW_FADE_START = .1;
|
|
185
251
|
const BELOW_FADE_SCALE = 5;
|
|
186
252
|
const COVERAGE_LIGHT_SHARE = .4;
|
|
187
|
-
const EDGE_FADE_START =
|
|
188
|
-
const EDGE_FADE_END =
|
|
253
|
+
const EDGE_FADE_START = 0;
|
|
254
|
+
const EDGE_FADE_END = 1;
|
|
189
255
|
const DROPPED_COVERAGE = .01;
|
|
256
|
+
const MIN_CROWN_COVER = .05;
|
|
190
257
|
const createCloudBandSprite = (atlas, clouds, { aspect, spriteCount }, skyUniforms) => {
|
|
191
258
|
const { cloudCoverage, cloudFrontBackBlend, cloudLitBackColor, cloudLitColor, cloudShadeBackColor, cloudShadeColor, cloudSunBrighten, sunDirection } = skyUniforms;
|
|
192
259
|
const columns = getCloudAtlasColumns(spriteCount);
|
|
@@ -196,6 +263,8 @@ const createCloudBandSprite = (atlas, clouds, { aspect, spriteCount }, skyUnifor
|
|
|
196
263
|
const positions = new InstancedBufferAttribute(new Float32Array(clouds.length * 3), 3);
|
|
197
264
|
const scales = new InstancedBufferAttribute(new Float32Array(clouds.length * 2), 2);
|
|
198
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);
|
|
199
268
|
const mask = texture(atlas, uv().add(instancedBufferAttribute(cells)).div(columns));
|
|
200
269
|
const place = instancedBufferAttribute(positions);
|
|
201
270
|
const direction = place.sub(cameraPosition).normalize();
|
|
@@ -204,7 +273,8 @@ const createCloudBandSprite = (atlas, clouds, { aspect, spriteCount }, skyUnifor
|
|
|
204
273
|
const toward = pow(max(sunCosine.mul(cloudFrontBackBlend).add(float(1).sub(cloudFrontBackBlend)), 0), 3);
|
|
205
274
|
const lit = mix(cloudLitBackColor, cloudLitColor, toward);
|
|
206
275
|
const shade = mix(cloudShadeBackColor, cloudShadeColor, toward);
|
|
207
|
-
const
|
|
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))));
|
|
208
278
|
const material = new SpriteNodeMaterial({
|
|
209
279
|
depthWrite: false,
|
|
210
280
|
transparent: true
|
|
@@ -212,25 +282,36 @@ const createCloudBandSprite = (atlas, clouds, { aspect, spriteCount }, skyUnifor
|
|
|
212
282
|
material.positionNode = place;
|
|
213
283
|
material.scaleNode = instancedBufferAttribute(scales);
|
|
214
284
|
material.colorNode = cloudColor;
|
|
215
|
-
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))));
|
|
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));
|
|
216
286
|
material.alphaTest = DROPPED_COVERAGE;
|
|
217
287
|
const sprite = new Sprite(material);
|
|
218
288
|
sprite.center.set(.5, 0);
|
|
219
289
|
sprite.count = clouds.length;
|
|
220
290
|
sprite.frustumCulled = false;
|
|
221
291
|
const modelViewMatrix = new Matrix4();
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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;
|
|
226
297
|
scales.set(cloudScales[cloud] ?? [], instance * 2);
|
|
227
298
|
cells.set(cloudCells[cloud] ?? [], instance * 2);
|
|
299
|
+
ranks.setX(instance, (cloud + .5) / clouds.length);
|
|
228
300
|
}
|
|
229
|
-
positions.needsUpdate = true;
|
|
230
301
|
scales.needsUpdate = true;
|
|
231
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();
|
|
232
312
|
};
|
|
233
313
|
return {
|
|
314
|
+
cover,
|
|
234
315
|
dispose: () => {
|
|
235
316
|
material.dispose();
|
|
236
317
|
},
|
|
@@ -239,15 +320,17 @@ const createCloudBandSprite = (atlas, clouds, { aspect, spriteCount }, skyUnifor
|
|
|
239
320
|
};
|
|
240
321
|
};
|
|
241
322
|
//#endregion
|
|
323
|
+
//#region src/constants.ts
|
|
324
|
+
const MAX_BYTE = 255;
|
|
325
|
+
//#endregion
|
|
242
326
|
//#region src/atmosphere/createSkyGradientTexture.ts
|
|
243
|
-
const BYTE = 255;
|
|
244
327
|
const createSkyGradientTexture = ({ green, red }) => {
|
|
245
328
|
const width = Math.max(red.length, green.length, 1);
|
|
246
329
|
const data = new Uint8Array(width * 4);
|
|
247
330
|
for (let index = 0; index < width; index++) {
|
|
248
|
-
data[index * 4] = Math.round((red[index] ?? 0) *
|
|
249
|
-
data[index * 4 + 1] = Math.round((green[index] ?? 0) *
|
|
250
|
-
data[index * 4 + 3] =
|
|
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;
|
|
251
334
|
}
|
|
252
335
|
const texture = new DataTexture(data, width, 1, RGBAFormat, UnsignedByteType);
|
|
253
336
|
texture.magFilter = LinearFilter;
|
|
@@ -359,7 +442,6 @@ const placeCloudBand = ({ count, distanceRange: [near, far], heightRange: [low,
|
|
|
359
442
|
};
|
|
360
443
|
//#endregion
|
|
361
444
|
//#region src/atmosphere/sampleSkyState.ts
|
|
362
|
-
const lerp = (from, to, amount) => from + (to - from) * amount;
|
|
363
445
|
const sampleSkyState = (skyKeyframes, minutes, tilt, skyState) => {
|
|
364
446
|
let nextIndex = 0;
|
|
365
447
|
while (nextIndex < skyKeyframes.length && (skyKeyframes[nextIndex]?.minutes ?? 0) <= minutes) nextIndex++;
|
|
@@ -372,12 +454,12 @@ const sampleSkyState = (skyKeyframes, minutes, tilt, skyState) => {
|
|
|
372
454
|
skyState.cloudLitColor.lerpColors(previous.cloudLitColor, next.cloudLitColor, amount);
|
|
373
455
|
skyState.cloudShadeColor.lerpColors(previous.cloudShadeColor, next.cloudShadeColor, amount);
|
|
374
456
|
skyState.hemisphereGroundColor.lerpColors(previous.hemisphereGroundColor, next.hemisphereGroundColor, amount);
|
|
375
|
-
skyState.hemisphereIntensity = lerp(previous.hemisphereIntensity, next.hemisphereIntensity, amount);
|
|
457
|
+
skyState.hemisphereIntensity = MathUtils.lerp(previous.hemisphereIntensity, next.hemisphereIntensity, amount);
|
|
376
458
|
skyState.hemisphereSkyColor.lerpColors(previous.hemisphereSkyColor, next.hemisphereSkyColor, amount);
|
|
377
459
|
skyState.horizonColor.lerpColors(previous.horizonColor, next.horizonColor, amount);
|
|
378
460
|
skyState.lightColor.lerpColors(previous.lightColor, next.lightColor, amount);
|
|
379
|
-
skyState.lightIntensity = lerp(previous.lightIntensity, next.lightIntensity, amount);
|
|
380
|
-
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);
|
|
381
463
|
skyState.zenithColor.lerpColors(previous.zenithColor, next.zenithColor, amount);
|
|
382
464
|
computeSunDirection(minutes, tilt, skyState.sunDirection);
|
|
383
465
|
skyState.moonDirection.copy(skyState.sunDirection).negate();
|
|
@@ -385,19 +467,309 @@ const sampleSkyState = (skyKeyframes, minutes, tilt, skyState) => {
|
|
|
385
467
|
return skyState;
|
|
386
468
|
};
|
|
387
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
|
|
388
755
|
//#region src/clock/advanceGameClock.ts
|
|
389
756
|
const advanceGameClock = (gameClock, deltaSeconds) => {
|
|
390
757
|
gameClock.minutes = (gameClock.minutes + deltaSeconds * gameClock.minutesPerSecond) % MINUTES_PER_DAY;
|
|
391
758
|
};
|
|
392
759
|
//#endregion
|
|
393
|
-
//#region src/kits/
|
|
394
|
-
const
|
|
395
|
-
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));
|
|
760
|
+
//#region src/kits/mergeGeometryParts.ts
|
|
761
|
+
const mergeGeometryParts = (parts) => {
|
|
396
762
|
const geometry = mergeGeometries(parts) ?? new BufferGeometry();
|
|
397
763
|
for (const part of parts) part.dispose();
|
|
398
764
|
return geometry;
|
|
399
765
|
};
|
|
400
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
|
|
401
773
|
//#region src/kits/architecture/createLatheStackGeometry.ts
|
|
402
774
|
const createLatheStackGeometry = ({ isFaceted, radialSegments, sections }) => {
|
|
403
775
|
let footHeight = 0;
|
|
@@ -406,8 +778,7 @@ const createLatheStackGeometry = ({ isFaceted, radialSegments, sections }) => {
|
|
|
406
778
|
footHeight += height;
|
|
407
779
|
return isFaceted ? part.toNonIndexed() : part;
|
|
408
780
|
});
|
|
409
|
-
const stackGeometry =
|
|
410
|
-
for (const part of parts) part.dispose();
|
|
781
|
+
const stackGeometry = mergeGeometryParts(parts);
|
|
411
782
|
if (isFaceted) stackGeometry.computeVertexNormals();
|
|
412
783
|
return stackGeometry;
|
|
413
784
|
};
|
|
@@ -429,24 +800,14 @@ const FIGURE_PROFILE = [
|
|
|
429
800
|
new Vector2(0, 6.2)
|
|
430
801
|
];
|
|
431
802
|
const createStatueGeometry = () => {
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
figure
|
|
441
|
-
].map((part) => part.toNonIndexed());
|
|
442
|
-
const statueGeometry = mergeGeometries(parts) ?? new BufferGeometry();
|
|
443
|
-
for (const part of [
|
|
444
|
-
lowerStep,
|
|
445
|
-
upperStep,
|
|
446
|
-
pedestal,
|
|
447
|
-
figure,
|
|
448
|
-
...parts
|
|
449
|
-
]) 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();
|
|
450
811
|
statueGeometry.computeVertexNormals();
|
|
451
812
|
return statueGeometry;
|
|
452
813
|
};
|
|
@@ -580,8 +941,7 @@ const createTreeGeometry = (treeOptions) => {
|
|
|
580
941
|
segmentGeometry.translate((start.x + end.x) / 2, (start.y + end.y) / 2, (start.z + end.z) / 2);
|
|
581
942
|
return segmentGeometry;
|
|
582
943
|
});
|
|
583
|
-
const branchGeometry =
|
|
584
|
-
for (const segmentGeometry of segmentGeometries) segmentGeometry.dispose();
|
|
944
|
+
const branchGeometry = mergeGeometryParts(segmentGeometries);
|
|
585
945
|
const { indices, normals, positions, uvs } = computeLeafCards(clusterCenters, treeOptions);
|
|
586
946
|
const leafGeometry = new BufferGeometry();
|
|
587
947
|
leafGeometry.setAttribute("position", new BufferAttribute(positions, 3));
|
|
@@ -596,14 +956,12 @@ const createTreeGeometry = (treeOptions) => {
|
|
|
596
956
|
};
|
|
597
957
|
//#endregion
|
|
598
958
|
//#region src/materials/computeRampValues.ts
|
|
599
|
-
const MAX_BYTE$1 = 255;
|
|
600
959
|
const computeRampValues = ({ resolution, softness, terminator }) => {
|
|
601
960
|
const lowerEdge = terminator - softness / 2;
|
|
602
961
|
const upperEdge = terminator + softness / 2;
|
|
603
962
|
return Uint8Array.from({ length: resolution }, (_value, index) => {
|
|
604
963
|
const coordinate = (index + .5) / resolution;
|
|
605
|
-
|
|
606
|
-
return Math.round(step * step * (3 - 2 * step) * MAX_BYTE$1);
|
|
964
|
+
return Math.round(MathUtils.smoothstep(coordinate, lowerEdge, upperEdge) * 255);
|
|
607
965
|
});
|
|
608
966
|
};
|
|
609
967
|
//#endregion
|
|
@@ -617,7 +975,58 @@ const createRampTexture = (rampOptions) => {
|
|
|
617
975
|
return rampTexture;
|
|
618
976
|
};
|
|
619
977
|
//#endregion
|
|
620
|
-
//#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
|
|
621
1030
|
var ToonNodeMaterial = class extends MeshToonNodeMaterial {
|
|
622
1031
|
emissiveNode = null;
|
|
623
1032
|
isMeshToonNodeMaterial;
|
|
@@ -627,6 +1036,57 @@ var ToonNodeMaterial = class extends MeshToonNodeMaterial {
|
|
|
627
1036
|
}
|
|
628
1037
|
};
|
|
629
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
|
|
630
1090
|
//#region src/nodes/createCausticsNode.ts
|
|
631
1091
|
const CAUSTIC_SCALE = .35;
|
|
632
1092
|
const CAUSTIC_SPEED = .6;
|
|
@@ -640,21 +1100,6 @@ const createCausticsNode = ({ lightColor }, { causticStrength, deepDepth, level
|
|
|
640
1100
|
return lightColor.mul(veins.mul(fade).mul(causticStrength));
|
|
641
1101
|
};
|
|
642
1102
|
//#endregion
|
|
643
|
-
//#region src/renderer/constants.ts
|
|
644
|
-
const GENSHIN_TONE_MAPPING = NeutralToneMapping;
|
|
645
|
-
//#endregion
|
|
646
|
-
//#region src/nodes/createGenshinRenderer.ts
|
|
647
|
-
const createGenshinRenderer = (canvas) => {
|
|
648
|
-
const isWindows = window.navigator.userAgent.includes("Windows");
|
|
649
|
-
const renderer = new WebGPURenderer({
|
|
650
|
-
antialias: false,
|
|
651
|
-
canvas,
|
|
652
|
-
...isWindows ? {} : { powerPreference: "high-performance" }
|
|
653
|
-
});
|
|
654
|
-
renderer.toneMapping = GENSHIN_TONE_MAPPING;
|
|
655
|
-
return renderer;
|
|
656
|
-
};
|
|
657
|
-
//#endregion
|
|
658
1103
|
//#region src/nodes/createRimNode.ts
|
|
659
1104
|
const RIM_POWER = 4;
|
|
660
1105
|
const createRimNode = ({ rimColor, rimStrength, sunDirection }) => {
|
|
@@ -715,7 +1160,6 @@ const createLightUniforms = () => ({
|
|
|
715
1160
|
});
|
|
716
1161
|
//#endregion
|
|
717
1162
|
//#region src/nodes/createSkyColorNode.ts
|
|
718
|
-
const LEAST_DIVISOR$1 = 1e-4;
|
|
719
1163
|
const HALO_TOWARD_START = .3;
|
|
720
1164
|
const HALO_TOWARD_SPAN = .7;
|
|
721
1165
|
const HALO_SUN_HEIGHT_START = .2;
|
|
@@ -731,8 +1175,8 @@ const createSkyColorNode = ({ frontBackBlend, haloColor, haloHeight, horizonBack
|
|
|
731
1175
|
const toward = pow(max(sunCosine.mul(frontBackBlend).add(float(1).sub(frontBackBlend)), 0), 3);
|
|
732
1176
|
const top = mix(zenithBackColor, zenithColor, toward);
|
|
733
1177
|
const bottom = mix(horizonBackColor, horizonColor, toward);
|
|
734
|
-
const bottomShare = texture(gradient, vec2(elevation.div(max(horizonBand, LEAST_DIVISOR
|
|
735
|
-
const haloShare = texture(gradient, vec2(elevation.div(max(haloHeight, LEAST_DIVISOR
|
|
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;
|
|
736
1180
|
const sunSide = saturate(sunCosine.mul(.5).add(.5));
|
|
737
1181
|
const towardSun = smoothstep(0, 1, max(sunSide.sub(HALO_TOWARD_START).div(HALO_TOWARD_SPAN), 0));
|
|
738
1182
|
const sunUp = smoothstep(0, 1, saturate(abs(sunDirection.y).sub(HALO_SUN_HEIGHT_START).div(HALO_SUN_HEIGHT_SPAN)));
|
|
@@ -752,7 +1196,6 @@ const CLOUD_EDGE = .04;
|
|
|
752
1196
|
const CLOUD_SHADE_DEPTH = .18;
|
|
753
1197
|
const STAR_SCALE = 420;
|
|
754
1198
|
const STAR_THRESHOLD = .9975;
|
|
755
|
-
const LEAST_DIVISOR = 1e-4;
|
|
756
1199
|
const MOON_GLOW_SPREAD = .1;
|
|
757
1200
|
const MOON_GLOW_POWER = 6;
|
|
758
1201
|
const createSkyNode = (uniforms, gradient) => {
|
|
@@ -779,15 +1222,28 @@ const createSkyNode = (uniforms, gradient) => {
|
|
|
779
1222
|
})();
|
|
780
1223
|
};
|
|
781
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
|
|
782
1243
|
//#region src/nodes/createStoneMaterial.ts
|
|
783
|
-
const createStoneMaterial = ({ albedo, rimColor, rimPower, rimStrength
|
|
784
|
-
const material = new
|
|
785
|
-
|
|
786
|
-
metalness: 0,
|
|
787
|
-
roughness: 1 - smoothness,
|
|
788
|
-
specularColor: new Color().fromArray(specularColor),
|
|
789
|
-
specularIntensity: 1
|
|
790
|
-
});
|
|
1244
|
+
const createStoneMaterial = ({ albedo, rimColor, rimPower, rimStrength }, stoneLight, glowNode = vec3(0)) => {
|
|
1245
|
+
const material = new StoneNodeMaterial(stoneLight);
|
|
1246
|
+
material.colorNode = color(new Color(albedo));
|
|
791
1247
|
const fresnel = float(1).sub(normalView.dot(positionViewDirection).saturate()).pow(rimPower);
|
|
792
1248
|
material.emissiveNode = color(new Color().fromArray(rimColor)).mul(fresnel.mul(rimStrength)).add(glowNode);
|
|
793
1249
|
return material;
|
|
@@ -888,14 +1344,13 @@ const createSimplexNoise = (seed) => {
|
|
|
888
1344
|
};
|
|
889
1345
|
//#endregion
|
|
890
1346
|
//#region src/post/computeGradeLut.ts
|
|
891
|
-
const MAX_BYTE = 255;
|
|
892
1347
|
const RED_LUMINANCE = .2126;
|
|
893
1348
|
const GREEN_LUMINANCE = .7152;
|
|
894
1349
|
const BLUE_LUMINANCE = .0722;
|
|
895
1350
|
const CONTRAST_PIVOT = .5;
|
|
896
1351
|
const toGradedByte = (channel, luminance, saturation, contrast, tint) => {
|
|
897
1352
|
const graded = (luminance + (channel - luminance) * saturation - CONTRAST_PIVOT) * contrast + CONTRAST_PIVOT + tint;
|
|
898
|
-
return Math.round(Math.min(Math.max(graded, 0), 1) *
|
|
1353
|
+
return Math.round(Math.min(Math.max(graded, 0), 1) * 255);
|
|
899
1354
|
};
|
|
900
1355
|
const computeGradeLut = ({ contrast, highlightTint, saturation, shadowTint, size }) => {
|
|
901
1356
|
const values = new Uint8Array(size * size * size * 4);
|
|
@@ -914,7 +1369,7 @@ const computeGradeLut = ({ contrast, highlightTint, saturation, shadowTint, size
|
|
|
914
1369
|
values[offset] = toGradedByte(red, luminance, saturation, contrast, redTint);
|
|
915
1370
|
values[offset + 1] = toGradedByte(green, luminance, saturation, contrast, greenTint);
|
|
916
1371
|
values[offset + 2] = toGradedByte(blue, luminance, saturation, contrast, blueTint);
|
|
917
|
-
values[offset + 3] =
|
|
1372
|
+
values[offset + 3] = 255;
|
|
918
1373
|
offset += 4;
|
|
919
1374
|
}
|
|
920
1375
|
return values;
|
|
@@ -926,6 +1381,7 @@ const createFogUniforms = () => ({
|
|
|
926
1381
|
color: uniform(new Color()),
|
|
927
1382
|
density: uniform(0),
|
|
928
1383
|
heightFalloff: uniform(0),
|
|
1384
|
+
maxOpacity: uniform(1),
|
|
929
1385
|
scatterColor: uniform(new Color()),
|
|
930
1386
|
scatterDirection: uniform(new Vector3(0, 1, 0)),
|
|
931
1387
|
scatterPower: uniform(1),
|
|
@@ -961,7 +1417,7 @@ const createGradeLutTexture = (gradeOptions) => {
|
|
|
961
1417
|
//#endregion
|
|
962
1418
|
//#region src/post/createHeightFogNode.ts
|
|
963
1419
|
const MIN_RAY_CLIMB = 1e-6;
|
|
964
|
-
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) => {
|
|
965
1421
|
const cameraWorldMatrix = uniform(camera.matrixWorld);
|
|
966
1422
|
const cameraProjectionMatrixInverse = uniform(camera.projectionMatrixInverse);
|
|
967
1423
|
return Fn(() => {
|
|
@@ -981,59 +1437,94 @@ const createHeightFogNode = (colorNode, depthNode, camera, { baseHeight, color,
|
|
|
981
1437
|
If(climb.abs().greaterThan(MIN_RAY_CLIMB), () => {
|
|
982
1438
|
opticalDepth.assign(densityAtStart.mul(float(1).sub(exp(fogLength.mul(climb).negate()))).div(climb));
|
|
983
1439
|
});
|
|
984
|
-
const opacity = float(1).sub(exp(opticalDepth.negate()));
|
|
985
|
-
const scatter = pow(max(ray.normalize().dot(scatterDirection), 0), scatterPower).mul(scatterStrength);
|
|
986
|
-
|
|
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));
|
|
987
1445
|
});
|
|
988
1446
|
return output;
|
|
989
1447
|
})();
|
|
990
1448
|
};
|
|
991
1449
|
//#endregion
|
|
992
|
-
//#region src/
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
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
|
+
};
|
|
998
1460
|
//#endregion
|
|
999
1461
|
//#region src/post/createPostPipeline.ts
|
|
1000
1462
|
const BLOOM_STRENGTH = .35;
|
|
1001
1463
|
const BLOOM_RADIUS = .4;
|
|
1002
1464
|
const BLOOM_THRESHOLD = .85;
|
|
1003
1465
|
const MIN_VIEW_DISTANCE = .001;
|
|
1004
|
-
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 }) => {
|
|
1005
1467
|
const renderPipeline = new RenderPipeline(renderer);
|
|
1006
1468
|
renderPipeline.outputColorTransform = false;
|
|
1007
1469
|
const viewDistance = modelViewMatrix.mul(vec4(positionLocal, 1)).z.negate().max(MIN_VIEW_DISTANCE);
|
|
1008
1470
|
const thicknessNode = outlineThickness.mul(outlineFadeDistance.div(viewDistance).min(1));
|
|
1009
1471
|
const scenePass = new ToonOutlinePassNode(scene, camera, outlineColor, thicknessNode, float(1));
|
|
1010
1472
|
const isTraa = antialiasingMode === "Traa";
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1473
|
+
const sceneOutputs = { output };
|
|
1474
|
+
if (isTraa) sceneOutputs.velocity = velocity;
|
|
1475
|
+
if (stoneLight) sceneOutputs[STONE_MASK_OUTPUT] = float(0);
|
|
1476
|
+
scenePass.setMRT(mrt(sceneOutputs));
|
|
1015
1477
|
const sceneColor = scenePass.getTextureNode("output");
|
|
1016
1478
|
const sceneDepth = scenePass.getTextureNode("depth");
|
|
1017
|
-
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
|
+
};
|
|
1018
1487
|
let litNode = sceneColor;
|
|
1019
|
-
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) {
|
|
1020
1494
|
const godraysNode = godrays(sceneDepth, camera, godraysLight);
|
|
1021
1495
|
godraysNode.raymarchSteps.value = godraysStepCount;
|
|
1022
1496
|
const blurredGodrays = bilateralBlur(godraysNode.getTextureNode());
|
|
1023
|
-
|
|
1497
|
+
passNodes.push(godraysNode, blurredGodrays);
|
|
1498
|
+
litNode = depthAwareBlend(litNode, blurredGodrays.getTextureNode(), sceneDepth, camera, { blendColor: godraysColor });
|
|
1024
1499
|
postPipeline.godraysNode = godraysNode;
|
|
1025
1500
|
}
|
|
1026
|
-
|
|
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
|
+
});
|
|
1027
1507
|
let bloomedNode = foggedNode;
|
|
1028
|
-
if (isBloomEnabled) {
|
|
1508
|
+
if (isBloomed && isBloomEnabled) {
|
|
1029
1509
|
const bloomNode = bloom(foggedNode, BLOOM_STRENGTH, BLOOM_RADIUS, BLOOM_THRESHOLD);
|
|
1510
|
+
passNodes.push(bloomNode);
|
|
1030
1511
|
bloomedNode = foggedNode.add(bloomNode);
|
|
1031
1512
|
postPipeline.bloomNode = bloomNode;
|
|
1032
1513
|
}
|
|
1033
|
-
|
|
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
|
+
}
|
|
1034
1520
|
const displayNode = renderOutput(resolvedNode);
|
|
1035
|
-
const gradedNode = lut3D(displayNode, texture3D(gradeLutTexture), gradeLutTexture.image.width, gradeIntensity);
|
|
1036
|
-
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
|
+
}
|
|
1037
1528
|
return postPipeline;
|
|
1038
1529
|
};
|
|
1039
1530
|
//#endregion
|
|
@@ -1046,14 +1537,6 @@ const createPostUniforms = () => ({
|
|
|
1046
1537
|
outlineThickness: uniform(.003)
|
|
1047
1538
|
});
|
|
1048
1539
|
//#endregion
|
|
1049
|
-
//#region src/renderer/QualityTier.ts
|
|
1050
|
-
let QualityTier = /* @__PURE__ */ function(QualityTier) {
|
|
1051
|
-
QualityTier["High"] = "High";
|
|
1052
|
-
QualityTier["Low"] = "Low";
|
|
1053
|
-
QualityTier["Medium"] = "Medium";
|
|
1054
|
-
return QualityTier;
|
|
1055
|
-
}({});
|
|
1056
|
-
//#endregion
|
|
1057
1540
|
//#region src/renderer/QualityTierSettingsMap.ts
|
|
1058
1541
|
const QualityTierSettingsMap = {
|
|
1059
1542
|
["High"]: {
|
|
@@ -1062,6 +1545,7 @@ const QualityTierSettingsMap = {
|
|
|
1062
1545
|
godraysStepCount: 60,
|
|
1063
1546
|
grassDensity: 1,
|
|
1064
1547
|
isBloomEnabled: true,
|
|
1548
|
+
isOcclusionEnabled: true,
|
|
1065
1549
|
maxPixelRatio: 2,
|
|
1066
1550
|
shadowMapSize: 2048
|
|
1067
1551
|
},
|
|
@@ -1071,6 +1555,7 @@ const QualityTierSettingsMap = {
|
|
|
1071
1555
|
godraysStepCount: 0,
|
|
1072
1556
|
grassDensity: .3,
|
|
1073
1557
|
isBloomEnabled: false,
|
|
1558
|
+
isOcclusionEnabled: false,
|
|
1074
1559
|
maxPixelRatio: 1,
|
|
1075
1560
|
shadowMapSize: 1024
|
|
1076
1561
|
},
|
|
@@ -1080,11 +1565,37 @@ const QualityTierSettingsMap = {
|
|
|
1080
1565
|
godraysStepCount: 30,
|
|
1081
1566
|
grassDensity: .6,
|
|
1082
1567
|
isBloomEnabled: true,
|
|
1568
|
+
isOcclusionEnabled: false,
|
|
1083
1569
|
maxPixelRatio: 1.5,
|
|
1084
1570
|
shadowMapSize: 1024
|
|
1085
1571
|
}
|
|
1086
1572
|
};
|
|
1087
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
|
|
1088
1599
|
//#region src/renderer/getScreenDirection.ts
|
|
1089
1600
|
const X_AXIS = new Vector3(1, 0, 0);
|
|
1090
1601
|
const Y_AXIS = new Vector3(0, 1, 0);
|
|
@@ -1143,27 +1654,30 @@ const createTileStreamer = ({ disposeTile, maxCachedCount, maxPendingCount, requ
|
|
|
1143
1654
|
});
|
|
1144
1655
|
evictStale();
|
|
1145
1656
|
},
|
|
1146
|
-
update: (
|
|
1657
|
+
update: (wanted, drawn, surrounding) => {
|
|
1147
1658
|
frame++;
|
|
1148
|
-
|
|
1149
|
-
|
|
1659
|
+
for (const { count, keys } of [
|
|
1660
|
+
wanted,
|
|
1661
|
+
drawn,
|
|
1662
|
+
surrounding
|
|
1663
|
+
]) for (let index = 0; index < count; index++) {
|
|
1150
1664
|
const key = keys[index] ?? 0;
|
|
1151
1665
|
const cachedTile = cachedTileMap.get(key);
|
|
1152
1666
|
if (cachedTile) cachedTile.lastWantedFrame = frame;
|
|
1153
|
-
else
|
|
1154
|
-
if (pendingWantedFrameMap.has(key)) pendingWantedFrameMap.set(key, frame);
|
|
1155
|
-
coarsestLevel = Math.max(coarsestLevel, getTerrainTileLevel(key));
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
for (let index = 0; index < drawn.count; index++) {
|
|
1159
|
-
const cachedTile = cachedTileMap.get(drawn.keys[index] ?? 0);
|
|
1160
|
-
if (cachedTile) cachedTile.lastWantedFrame = frame;
|
|
1667
|
+
else if (pendingWantedFrameMap.has(key)) pendingWantedFrameMap.set(key, frame);
|
|
1161
1668
|
}
|
|
1162
|
-
for (
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
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
|
+
}
|
|
1167
1681
|
}
|
|
1168
1682
|
}
|
|
1169
1683
|
};
|
|
@@ -1246,6 +1760,25 @@ const computeTerrainTile = ({ cellsPerSide, finestTileSize, getHeight, key, writ
|
|
|
1246
1760
|
};
|
|
1247
1761
|
};
|
|
1248
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
|
|
1249
1782
|
//#region src/terrain/createTerrainMaterial.ts
|
|
1250
1783
|
const createTerrainMaterial = ({ finestRange, morphShare }, toonMaterialOptions, waterUniforms) => {
|
|
1251
1784
|
const terrainMaterial = createToonMaterial({
|
|
@@ -1412,7 +1945,7 @@ const createGrassGeometry = (segmentCount, bladeCount) => {
|
|
|
1412
1945
|
const { indices, positions } = computeGrassBlade(segmentCount);
|
|
1413
1946
|
const grassGeometry = new InstancedBufferGeometry();
|
|
1414
1947
|
grassGeometry.setAttribute("position", new BufferAttribute(positions, 3));
|
|
1415
|
-
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));
|
|
1416
1949
|
grassGeometry.setIndex(new BufferAttribute(indices, 1));
|
|
1417
1950
|
grassGeometry.instanceCount = bladeCount;
|
|
1418
1951
|
return grassGeometry;
|
|
@@ -1478,7 +2011,7 @@ const createGroundCapture = (size, resolution) => {
|
|
|
1478
2011
|
};
|
|
1479
2012
|
//#endregion
|
|
1480
2013
|
//#region src/vegetation/renderGroundCapture.ts
|
|
1481
|
-
const
|
|
2014
|
+
const CLEAR_COLOR = new Color();
|
|
1482
2015
|
const previousClearColor = new Color();
|
|
1483
2016
|
const renderGroundCapture = (renderer, scene, { camera, center, material, renderTarget }, { maxHeight }, worldCenter, origin) => {
|
|
1484
2017
|
center.value.set(worldCenter.x, worldCenter.z);
|
|
@@ -1493,7 +2026,7 @@ const renderGroundCapture = (renderer, scene, { camera, center, material, render
|
|
|
1493
2026
|
const previousBackgroundNode = scene.backgroundNode;
|
|
1494
2027
|
scene.overrideMaterial = material;
|
|
1495
2028
|
scene.backgroundNode = null;
|
|
1496
|
-
renderer.setClearColor(
|
|
2029
|
+
renderer.setClearColor(CLEAR_COLOR, 0);
|
|
1497
2030
|
renderer.setRenderTarget(renderTarget);
|
|
1498
2031
|
renderer.clear();
|
|
1499
2032
|
renderer.render(scene, camera);
|
|
@@ -1507,6 +2040,7 @@ const renderGroundCapture = (renderer, scene, { camera, center, material, render
|
|
|
1507
2040
|
const createUnderwaterFogState = () => ({
|
|
1508
2041
|
aboveDensity: 0,
|
|
1509
2042
|
aboveHeightFalloff: 0,
|
|
2043
|
+
aboveMaxOpacity: 1,
|
|
1510
2044
|
aboveStartDistance: 0,
|
|
1511
2045
|
isUnderwater: false
|
|
1512
2046
|
});
|
|
@@ -1525,18 +2059,21 @@ const createWaterUniforms = () => ({
|
|
|
1525
2059
|
});
|
|
1526
2060
|
//#endregion
|
|
1527
2061
|
//#region src/water/updateUnderwaterFog.ts
|
|
1528
|
-
const updateUnderwaterFog = (eyeHeight, { level, underwaterFogColor, underwaterFogDensity }, { color, density, heightFalloff, startDistance }, underwaterFogState) => {
|
|
2062
|
+
const updateUnderwaterFog = (eyeHeight, { level, underwaterFogColor, underwaterFogDensity }, { color, density, heightFalloff, maxOpacity, startDistance }, underwaterFogState) => {
|
|
1529
2063
|
const isUnderwater = eyeHeight < level.value;
|
|
1530
2064
|
if (isUnderwater && !underwaterFogState.isUnderwater) {
|
|
1531
2065
|
underwaterFogState.aboveDensity = density.value;
|
|
1532
2066
|
underwaterFogState.aboveHeightFalloff = heightFalloff.value;
|
|
2067
|
+
underwaterFogState.aboveMaxOpacity = maxOpacity.value;
|
|
1533
2068
|
underwaterFogState.aboveStartDistance = startDistance.value;
|
|
1534
2069
|
density.value = underwaterFogDensity.value;
|
|
1535
2070
|
heightFalloff.value = 0;
|
|
2071
|
+
maxOpacity.value = 1;
|
|
1536
2072
|
startDistance.value = 0;
|
|
1537
2073
|
} else if (!isUnderwater && underwaterFogState.isUnderwater) {
|
|
1538
2074
|
density.value = underwaterFogState.aboveDensity;
|
|
1539
2075
|
heightFalloff.value = underwaterFogState.aboveHeightFalloff;
|
|
2076
|
+
maxOpacity.value = underwaterFogState.aboveMaxOpacity;
|
|
1540
2077
|
startDistance.value = underwaterFogState.aboveStartDistance;
|
|
1541
2078
|
}
|
|
1542
2079
|
underwaterFogState.isUnderwater = isUnderwater;
|
|
@@ -1579,4 +2116,4 @@ const computeOutlineDistance = (outline, x, z) => {
|
|
|
1579
2116
|
return isInside ? 0 : Math.sqrt(nearestSquared);
|
|
1580
2117
|
};
|
|
1581
2118
|
//#endregion
|
|
1582
|
-
export { AntialiasingMode, DEFAULT_SKY_GRADIENT, DEFAULT_SKY_SHAPE, GAME_MINUTES_PER_SECOND, GENSHIN_TONE_MAPPING, MINUTES_PER_DAY, QualityTier, QualityTierSettingsMap, TERRAIN_LAYER, TILE_INDEX_OFFSET, TILE_INDEX_SPAN, ToonNodeMaterial, addSmoothLoop, advanceGameClock, applySkyState, computeGradeLut, computeGrassBlade, computeLeafCards, computeOriginShift, computeOutlineDistance, computeRampValues, computeSunDirection, computeTerrainIndices, computeTerrainTile, computeTreeSkeleton, createBoxesGeometry, createCausticsNode, createCloudAtlasTexture, createCloudBandSprite, createFogUniforms, createGenshinRenderer, createGodraysLight, createGradeLutTexture, createGrassGeometry, createGrassMaterial, createGroundCapture, createHeightFogNode, createLatheStackGeometry, createLeafMaterial, createLightUniforms, createPostPipeline, createPostUniforms, createRampTexture, createRimNode, createSeededRandom, createSimplexNoise, createSkyColorNode, createSkyGradientTexture, createSkyNode, createSkyState, createSkyUniforms, createStatueGeometry, createStoneMaterial, createSunLight, createTerrainMaterial, createTerrainSelection, createTerrainTileGeometry, createTileStreamer, createToonMaterial, createTreeGeometry, createUnderwaterFogState, createWaterMaterial, createWaterUniforms, createWindNode, createWindUniforms, getCloudAtlasColumns, getParentTerrainTileKey, getScreenDirection, getTerrainTileColumn, getTerrainTileKey, getTerrainTileLevel, getTerrainTileRow, orderByViewDepth, placeCloudBand, renderGroundCapture, resolveTerrainDraws, sampleSkyState, selectTerrainTiles, toSceneColor, toneMapNeutral, updateUnderwaterFog };
|
|
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 };
|