@driftengine/core 4.3.0 → 4.4.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/CHANGELOG.json +261 -0
- package/README.md +1 -1
- package/dist/cinematic/cameraPath.d.ts +53 -0
- package/dist/cinematic/cameraPath.js +118 -0
- package/dist/environment/daylight.d.ts +77 -0
- package/dist/environment/daylight.js +77 -0
- package/dist/geometry/meshConcat.d.ts +9 -0
- package/dist/geometry/meshConcat.js +101 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +14 -1
- package/dist/render/backend/createRenderer.d.ts +13 -1
- package/dist/render/backend/createRenderer.js +6 -5
- package/dist/render/backend/webgl2/renderer.d.ts +147 -1
- package/dist/render/backend/webgl2/renderer.js +402 -23
- package/dist/render/backend/webgpu/buffers.js +46 -8
- package/dist/render/backend/webgpu/depthPass.d.ts +81 -3
- package/dist/render/backend/webgpu/depthPass.js +75 -14
- package/dist/render/backend/webgpu/exposurePass.d.ts +39 -0
- package/dist/render/backend/webgpu/exposurePass.js +190 -0
- package/dist/render/backend/webgpu/fieldCompose.d.ts +11 -0
- package/dist/render/backend/webgpu/fieldCompose.js +13 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +3 -1
- package/dist/render/backend/webgpu/flatPass.js +12 -5
- package/dist/render/backend/webgpu/particlePass.d.ts +8 -0
- package/dist/render/backend/webgpu/particlePass.js +1 -0
- package/dist/render/backend/webgpu/postPass.d.ts +12 -2
- package/dist/render/backend/webgpu/probeBake.d.ts +59 -3
- package/dist/render/backend/webgpu/probeBake.js +97 -7
- package/dist/render/backend/webgpu/probeTargets.d.ts +65 -0
- package/dist/render/backend/webgpu/probeTargets.js +180 -0
- package/dist/render/backend/webgpu/renderer.d.ts +174 -11
- package/dist/render/backend/webgpu/renderer.js +675 -249
- package/dist/render/backend/webgpu/scatterPass.js +7 -7
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
- package/dist/render/backend/webgpu/skinPaletteRing.js +9 -0
- package/dist/render/backend/webgpu/skyPass.js +5 -1
- package/dist/render/backend/webgpu/uniformRing.d.ts +31 -1
- package/dist/render/backend/webgpu/uniformRing.js +53 -3
- package/dist/render/bloomChain.d.ts +6 -0
- package/dist/render/bloomChain.js +8 -0
- package/dist/render/clusteredLights.d.ts +19 -11
- package/dist/render/clusteredLights.js +96 -45
- package/dist/render/cutoutCaster.d.ts +23 -0
- package/dist/render/cutoutCaster.js +20 -0
- package/dist/render/driftLight/bake.d.ts +34 -0
- package/dist/render/driftLight/bake.js +95 -0
- package/dist/render/driftLight/falloff.d.ts +16 -0
- package/dist/render/driftLight/falloff.js +23 -0
- package/dist/render/driftLight/layout.d.ts +59 -0
- package/dist/render/driftLight/layout.js +138 -0
- package/dist/render/driftLight/lightField.d.ts +108 -0
- package/dist/render/driftLight/lightField.js +179 -0
- package/dist/render/driftLight/uniforms.d.ts +28 -0
- package/dist/render/driftLight/uniforms.js +41 -0
- package/dist/render/driftLight/visibility.d.ts +18 -0
- package/dist/render/driftLight/visibility.js +47 -0
- package/dist/render/emptyTexture.d.ts +6 -0
- package/dist/render/emptyTexture.js +19 -0
- package/dist/render/exposurePass.d.ts +48 -0
- package/dist/render/exposurePass.js +172 -0
- package/dist/render/flameLight.d.ts +71 -0
- package/dist/render/flameLight.js +63 -0
- package/dist/render/gi/bounceLights.d.ts +34 -0
- package/dist/render/gi/bounceLights.js +66 -0
- package/dist/render/gi/fieldReach.d.ts +12 -0
- package/dist/render/gi/fieldReach.js +62 -0
- package/dist/render/gi/globalField.d.ts +8 -0
- package/dist/render/gi/globalField.js +31 -0
- package/dist/render/gi/probeTrace.d.ts +3 -1
- package/dist/render/gi/probeTrace.js +3 -1
- package/dist/render/gpuCapability.js +19 -4
- package/dist/render/lightBudget.d.ts +11 -0
- package/dist/render/lightBudget.js +11 -0
- package/dist/render/pointLightSelection.d.ts +16 -0
- package/dist/render/pointLightSelection.js +178 -83
- package/dist/render/probeGrid.d.ts +11 -1
- package/dist/render/probeGrid.js +5 -2
- package/dist/render/probeSweeps.d.ts +45 -0
- package/dist/render/probeSweeps.js +77 -0
- package/dist/render/reflectionProbe.d.ts +43 -1
- package/dist/render/reflectionProbe.js +17 -3
- package/dist/render/renderQuality.d.ts +3 -1
- package/dist/render/rendererHarness.d.ts +9 -0
- package/dist/render/rendererHarness.js +18 -3
- package/dist/render/sceneTarget.d.ts +20 -0
- package/dist/render/sceneTarget.js +62 -1
- package/dist/render/shaders/agx.d.ts +31 -0
- package/dist/render/shaders/agx.js +81 -0
- package/dist/render/shaders/ambientOcclusion.d.ts +24 -5
- package/dist/render/shaders/ambientOcclusion.js +120 -63
- package/dist/render/shaders/cutoutCoverage.d.ts +31 -0
- package/dist/render/shaders/cutoutCoverage.js +40 -0
- package/dist/render/shaders/depth.d.ts +25 -0
- package/dist/render/shaders/depth.js +69 -3
- package/dist/render/shaders/exposure.d.ts +52 -0
- package/dist/render/shaders/exposure.js +124 -0
- package/dist/render/shaders/filmLook.d.ts +47 -0
- package/dist/render/shaders/filmLook.js +98 -0
- package/dist/render/shaders/flat/directionalShadow.js +35 -4
- package/dist/render/shaders/flat/driftLight.d.ts +29 -0
- package/dist/render/shaders/flat/driftLight.js +75 -0
- package/dist/render/shaders/flat/index.js +5 -0
- package/dist/render/shaders/flat/lobes.js +13 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +68 -9
- package/dist/render/shaders/flat/preamble.js +23 -5
- package/dist/render/shaders/flat/probeGrid.js +46 -3
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +1 -1
- package/dist/render/shaders/generated/depth.wgsl.d.ts +100 -0
- package/dist/render/shaders/generated/depth.wgsl.js +100 -0
- package/dist/render/shaders/generated/exposure.wgsl.d.ts +42 -0
- package/dist/render/shaders/generated/exposure.wgsl.js +48 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1800 -1344
- package/dist/render/shaders/generated/flat.wgsl.js +1816 -1360
- package/dist/render/shaders/generated/localExposure.wgsl.d.ts +18 -0
- package/dist/render/shaders/generated/localExposure.wgsl.js +24 -0
- package/dist/render/shaders/generated/rush.wgsl.d.ts +40 -10
- package/dist/render/shaders/generated/rush.wgsl.js +40 -10
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.d.ts +16 -0
- package/dist/render/shaders/gi/driftLight.wgsl.js +49 -0
- package/dist/render/shaders/gi/probeBake.wgsl.d.ts +29 -4
- package/dist/render/shaders/gi/probeBake.wgsl.js +190 -20
- package/dist/render/shaders/globalMedium.js +4 -0
- package/dist/render/shaders/localExposure.d.ts +35 -0
- package/dist/render/shaders/localExposure.js +138 -0
- package/dist/render/shaders/prefilter.d.ts +1 -1
- package/dist/render/shaders/prefilter.js +6 -0
- package/dist/render/shaders/rush.js +33 -5
- package/dist/render/shaders/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +15 -3
- package/dist/render/shaders/temporalResolve.js +39 -1
- package/dist/render/shadowCasters.d.ts +5 -3
- package/dist/render/shadowMap.js +13 -0
- package/dist/render/surfaceTexture.d.ts +5 -0
- package/package.json +6 -6
- package/scripts/gpuCompute.mjs +24 -3
- package/scripts/png.mjs +23 -12
- package/src/cinematic/cameraPath.ts +178 -0
- package/src/environment/daylight.ts +152 -0
- package/src/geometry/meshConcat.ts +100 -0
- package/src/index.ts +32 -0
- package/src/render/backend/createRenderer.ts +22 -6
- package/src/render/backend/webgl2/renderer.ts +439 -20
- package/src/render/backend/webgpu/buffers.ts +51 -8
- package/src/render/backend/webgpu/depthPass.ts +78 -10
- package/src/render/backend/webgpu/exposurePass.ts +234 -0
- package/src/render/backend/webgpu/fieldCompose.ts +15 -0
- package/src/render/backend/webgpu/flatPass.ts +10 -2
- package/src/render/backend/webgpu/particlePass.ts +9 -0
- package/src/render/backend/webgpu/probeBake.ts +143 -9
- package/src/render/backend/webgpu/probeTargets.ts +211 -0
- package/src/render/backend/webgpu/renderer.ts +801 -270
- package/src/render/backend/webgpu/scatterPass.ts +7 -7
- package/src/render/backend/webgpu/skinPaletteRing.ts +10 -0
- package/src/render/backend/webgpu/skyPass.ts +5 -1
- package/src/render/backend/webgpu/uniformRing.ts +49 -3
- package/src/render/bloomChain.ts +9 -0
- package/src/render/clusteredLights.ts +108 -63
- package/src/render/cutoutCaster.ts +44 -0
- package/src/render/driftLight/bake.ts +125 -0
- package/src/render/driftLight/falloff.ts +26 -0
- package/src/render/driftLight/layout.ts +177 -0
- package/src/render/driftLight/lightField.ts +227 -0
- package/src/render/driftLight/uniforms.ts +61 -0
- package/src/render/driftLight/visibility.ts +58 -0
- package/src/render/emptyTexture.ts +41 -0
- package/src/render/exposurePass.ts +186 -0
- package/src/render/flameLight.ts +135 -0
- package/src/render/gi/bounceLights.ts +68 -0
- package/src/render/gi/fieldReach.ts +74 -0
- package/src/render/gi/globalField.ts +39 -0
- package/src/render/gi/probeTrace.ts +3 -1
- package/src/render/gpuCapability.ts +18 -5
- package/src/render/lightBudget.ts +12 -0
- package/src/render/pointLightSelection.ts +191 -79
- package/src/render/probeGrid.ts +13 -2
- package/src/render/probeSweeps.ts +80 -0
- package/src/render/reflectionProbe.ts +54 -2
- package/src/render/renderQuality.ts +3 -1
- package/src/render/rendererHarness.ts +25 -3
- package/src/render/sceneTarget.ts +75 -1
- package/src/render/shaders/ambientOcclusion.ts +125 -63
- package/src/render/shaders/cutoutCoverage.ts +43 -0
- package/src/render/shaders/depth.ts +80 -3
- package/src/render/shaders/exposure.ts +128 -0
- package/src/render/shaders/filmLook.ts +111 -0
- package/src/render/shaders/flat/directionalShadow.ts +35 -4
- package/src/render/shaders/flat/driftLight.ts +75 -0
- package/src/render/shaders/flat/index.ts +5 -0
- package/src/render/shaders/flat/lobes.ts +13 -0
- package/src/render/shaders/flat/main.ts +68 -9
- package/src/render/shaders/flat/preamble.ts +23 -5
- package/src/render/shaders/flat/probeGrid.ts +46 -3
- package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +1 -1
- package/src/render/shaders/generated/depth.wgsl.ts +103 -0
- package/src/render/shaders/generated/exposure.wgsl.ts +51 -0
- package/src/render/shaders/generated/flat.wgsl.ts +1816 -1360
- package/src/render/shaders/generated/localExposure.wgsl.ts +26 -0
- package/src/render/shaders/generated/rush.wgsl.ts +40 -10
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
- package/src/render/shaders/gi/driftLight.wgsl.ts +49 -0
- package/src/render/shaders/gi/probeBake.wgsl.ts +191 -20
- package/src/render/shaders/globalMedium.ts +4 -0
- package/src/render/shaders/localExposure.ts +154 -0
- package/src/render/shaders/prefilter.ts +6 -0
- package/src/render/shaders/rush.ts +33 -5
- package/src/render/shaders/tangentFrame.ts +15 -3
- package/src/render/shaders/temporalResolve.ts +39 -1
- package/src/render/shadowCasters.ts +5 -3
- package/src/render/shadowMap.ts +14 -0
- package/src/render/surfaceTexture.ts +5 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A day's lighting as a palette keyed by one number, and an exposure that follows it in time.
|
|
3
|
+
*
|
|
4
|
+
* **Keyed by any scalar a caller chooses.** A courtyard keys it by the sun's elevation in degrees,
|
|
5
|
+
* because what the light does there is a function of how high the sun stands; a scene that lerps
|
|
6
|
+
* between a night and a day by a day factor keys it at 0 and 1 and gets exactly the blend it had.
|
|
7
|
+
* The palette is data — colours, a density, a strength, an exposure — so which hours exist and what
|
|
8
|
+
* they look like stay the scene's, and the mechanism is the engine's.
|
|
9
|
+
*
|
|
10
|
+
* **Linear between keys, and in stops for exposure.** Colours and densities blend linearly, which
|
|
11
|
+
* is what every hand-written day cycle in this repository did; exposure blends in log2, because two
|
|
12
|
+
* keys a stop apart should pass through half a stop and not through the arithmetic mean, which sits
|
|
13
|
+
* nearer the brighter key. What that gives up is smoothness at a key: the blend has a corner there.
|
|
14
|
+
* What would make it wrong is a palette so sparse that the corner shows as a change of pace in the
|
|
15
|
+
* sky, and the answer then is more keys rather than a curve.
|
|
16
|
+
*
|
|
17
|
+
* Nothing here allocates after construction: `resolveDaylight` writes into a caller-owned state.
|
|
18
|
+
*/
|
|
19
|
+
import type { Vec3 } from '../math/color.ts';
|
|
20
|
+
|
|
21
|
+
/** The lighting at one point of the palette's scalar. Every colour is linear and may exceed one. */
|
|
22
|
+
export interface DaylightKey {
|
|
23
|
+
/** Where on the palette's scalar this key sits: a sun elevation, a day factor, an hour. */
|
|
24
|
+
readonly at: number;
|
|
25
|
+
/** The sun's radiance at the surface it lights, which is the directional light while it is up. */
|
|
26
|
+
readonly sunColor: Readonly<Vec3>;
|
|
27
|
+
/** The moon's, for whichever part of the palette the moon lights. */
|
|
28
|
+
readonly moonColor: Readonly<Vec3>;
|
|
29
|
+
readonly skyTop: Readonly<Vec3>;
|
|
30
|
+
readonly skyHorizon: Readonly<Vec3>;
|
|
31
|
+
readonly skyDeep: Readonly<Vec3>;
|
|
32
|
+
/** The hemisphere's sky half. */
|
|
33
|
+
readonly ambient: Readonly<Vec3>;
|
|
34
|
+
/** The hemisphere's ground half: what the floor throws back up. */
|
|
35
|
+
readonly ambientGround: Readonly<Vec3>;
|
|
36
|
+
readonly fogColor: Readonly<Vec3>;
|
|
37
|
+
readonly fogDensity: number;
|
|
38
|
+
/** How dark a directional shadow is, 0 to 1. */
|
|
39
|
+
readonly shadowStrength: number;
|
|
40
|
+
/** The emissive master gain: how strongly lamps and flames read against this light. */
|
|
41
|
+
readonly emissiveGain: number;
|
|
42
|
+
/** The exposure a viewer's eye settles on here. Positive; blended in stops. */
|
|
43
|
+
readonly exposure: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Keys in ascending order of `at`, validated once. Build with `createDaylightPalette`. */
|
|
47
|
+
export interface DaylightPalette {
|
|
48
|
+
readonly keys: readonly DaylightKey[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** One moment of the palette, written in place by `resolveDaylight`. */
|
|
52
|
+
export interface DaylightState {
|
|
53
|
+
sunColor: Vec3;
|
|
54
|
+
moonColor: Vec3;
|
|
55
|
+
skyTop: Vec3;
|
|
56
|
+
skyHorizon: Vec3;
|
|
57
|
+
skyDeep: Vec3;
|
|
58
|
+
ambient: Vec3;
|
|
59
|
+
ambientGround: Vec3;
|
|
60
|
+
fogColor: Vec3;
|
|
61
|
+
fogDensity: number;
|
|
62
|
+
shadowStrength: number;
|
|
63
|
+
emissiveGain: number;
|
|
64
|
+
exposure: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Sort and check the keys. Throws at construction, so a bad palette never reaches a frame. */
|
|
68
|
+
export function createDaylightPalette(keys: readonly DaylightKey[]): DaylightPalette {
|
|
69
|
+
if (keys.length === 0) throw new Error('a daylight palette needs at least one key');
|
|
70
|
+
const sorted = [...keys].sort((a, b) => a.at - b.at);
|
|
71
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
72
|
+
const current = sorted[i] as DaylightKey;
|
|
73
|
+
if (!(current.exposure > 0)) {
|
|
74
|
+
throw new Error(`the key at ${current.at} has exposure ${current.exposure}; it must be > 0`);
|
|
75
|
+
}
|
|
76
|
+
if (i > 0 && (sorted[i - 1] as DaylightKey).at === current.at) {
|
|
77
|
+
throw new Error(`a daylight palette has two keys at ${current.at}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { keys: sorted };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function createDaylightState(): DaylightState {
|
|
84
|
+
return {
|
|
85
|
+
sunColor: [0, 0, 0],
|
|
86
|
+
moonColor: [0, 0, 0],
|
|
87
|
+
skyTop: [0, 0, 0],
|
|
88
|
+
skyHorizon: [0, 0, 0],
|
|
89
|
+
skyDeep: [0, 0, 0],
|
|
90
|
+
ambient: [0, 0, 0],
|
|
91
|
+
ambientGround: [0, 0, 0],
|
|
92
|
+
fogColor: [0, 0, 0],
|
|
93
|
+
fogDensity: 0,
|
|
94
|
+
shadowStrength: 0,
|
|
95
|
+
emissiveGain: 0,
|
|
96
|
+
exposure: 1,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function blendInto(out: Vec3, a: Readonly<Vec3>, b: Readonly<Vec3>, t: number): void {
|
|
101
|
+
out[0] = a[0] + (b[0] - a[0]) * t;
|
|
102
|
+
out[1] = a[1] + (b[1] - a[1]) * t;
|
|
103
|
+
out[2] = a[2] + (b[2] - a[2]) * t;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The palette at `at`, into `out`. Past either end the nearest key holds. */
|
|
107
|
+
export function resolveDaylight(
|
|
108
|
+
at: number,
|
|
109
|
+
palette: DaylightPalette,
|
|
110
|
+
out: DaylightState,
|
|
111
|
+
): DaylightState {
|
|
112
|
+
const keys = palette.keys;
|
|
113
|
+
let upper = 0;
|
|
114
|
+
while (upper < keys.length && (keys[upper] as DaylightKey).at < at) upper++;
|
|
115
|
+
const b = keys[Math.min(upper, keys.length - 1)] as DaylightKey;
|
|
116
|
+
const a = keys[Math.max(upper - 1, 0)] as DaylightKey;
|
|
117
|
+
const t = a === b ? 0 : (at - a.at) / (b.at - a.at);
|
|
118
|
+
blendInto(out.sunColor, a.sunColor, b.sunColor, t);
|
|
119
|
+
blendInto(out.moonColor, a.moonColor, b.moonColor, t);
|
|
120
|
+
blendInto(out.skyTop, a.skyTop, b.skyTop, t);
|
|
121
|
+
blendInto(out.skyHorizon, a.skyHorizon, b.skyHorizon, t);
|
|
122
|
+
blendInto(out.skyDeep, a.skyDeep, b.skyDeep, t);
|
|
123
|
+
blendInto(out.ambient, a.ambient, b.ambient, t);
|
|
124
|
+
blendInto(out.ambientGround, a.ambientGround, b.ambientGround, t);
|
|
125
|
+
blendInto(out.fogColor, a.fogColor, b.fogColor, t);
|
|
126
|
+
out.fogDensity = a.fogDensity + (b.fogDensity - a.fogDensity) * t;
|
|
127
|
+
out.shadowStrength = a.shadowStrength + (b.shadowStrength - a.shadowStrength) * t;
|
|
128
|
+
out.emissiveGain = a.emissiveGain + (b.emissiveGain - a.emissiveGain) * t;
|
|
129
|
+
out.exposure = t === 0 ? a.exposure : a.exposure * (b.exposure / a.exposure) ** t;
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* `current` moved toward `target` over `dtSec`, closing half the distance in stops every
|
|
135
|
+
* `halfLifeSec`.
|
|
136
|
+
*
|
|
137
|
+
* **In time, not per frame**, so a 144 Hz display adapts exactly as fast as a 60 Hz one: the
|
|
138
|
+
* remaining distance after any number of steps is `2^(−elapsed / halfLife)` of what it was, however
|
|
139
|
+
* the elapsed time was cut up. A half-life of zero is a cut to the target. Both exposures must be
|
|
140
|
+
* positive, which is what an exposure is.
|
|
141
|
+
*/
|
|
142
|
+
export function easeExposure(
|
|
143
|
+
current: number,
|
|
144
|
+
target: number,
|
|
145
|
+
dtSec: number,
|
|
146
|
+
halfLifeSec: number,
|
|
147
|
+
): number {
|
|
148
|
+
if (halfLifeSec <= 0) return target;
|
|
149
|
+
if (dtSec <= 0) return current;
|
|
150
|
+
const closed = 1 - 2 ** (-dtSec / halfLifeSec);
|
|
151
|
+
return current * (target / current) ** closed;
|
|
152
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meshes moved and joined as typed arrays: the loader's fast path, with no builder in between.
|
|
3
|
+
*
|
|
4
|
+
* **`MeshBuilder` is for building geometry, and a loader was using it to copy geometry.** It
|
|
5
|
+
* pushes every vertex one number at a time into growable arrays, and it carries the attributes a
|
|
6
|
+
* procedural mesh has. Streaming a bought courtyard spent seven seconds of its main thread there,
|
|
7
|
+
* copying each part twice, once to upload it and once into its merge group. Worse, the builder has
|
|
8
|
+
* no tangent channel, so every normal-mapped part lost the frame its file carried and the shader
|
|
9
|
+
* fell back to derivatives.
|
|
10
|
+
*
|
|
11
|
+
* So `placeMesh` moves a mesh by a fit, or hands it back untouched when the fit is the identity,
|
|
12
|
+
* and `concatMeshes` joins a group once, into arrays sized in advance. An attribute one member
|
|
13
|
+
* lacks is filled with the value a mesh without it draws with (`ABSENT_ATTRIBUTE`). One no member
|
|
14
|
+
* carries is left out, as absent rather than invented.
|
|
15
|
+
*/
|
|
16
|
+
import { ABSENT_ATTRIBUTE } from '../render/vertexDefaults.ts';
|
|
17
|
+
import type { MeshData } from '../render/mesh.ts';
|
|
18
|
+
|
|
19
|
+
/** The optional per-vertex attributes, with how many floats a vertex each carries. */
|
|
20
|
+
const OPTIONAL = [
|
|
21
|
+
['specular', 1],
|
|
22
|
+
['tangents', 4],
|
|
23
|
+
['uvs', 2],
|
|
24
|
+
['emissiveColor', 3],
|
|
25
|
+
['roughness', 1],
|
|
26
|
+
['grain', 1],
|
|
27
|
+
['relief', 1],
|
|
28
|
+
['channel', 4],
|
|
29
|
+
['joints', 4],
|
|
30
|
+
['weights', 4],
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* `mesh` scaled uniformly and then moved by `x, y, z`. The same object when the fit is the
|
|
35
|
+
* identity; otherwise a copy of the positions and every other attribute shared, since a uniform
|
|
36
|
+
* scale turns no normal. Morph deltas are the caller's to scale.
|
|
37
|
+
*/
|
|
38
|
+
export function placeMesh(mesh: MeshData, x: number, y: number, z: number, scale = 1): MeshData {
|
|
39
|
+
if (x === 0 && y === 0 && z === 0 && scale === 1) return mesh;
|
|
40
|
+
const from = mesh.positions;
|
|
41
|
+
const positions = new Float32Array(from.length);
|
|
42
|
+
for (let i = 0; i + 2 < from.length; i += 3) {
|
|
43
|
+
positions[i] = (from[i] as number) * scale + x;
|
|
44
|
+
positions[i + 1] = (from[i + 1] as number) * scale + y;
|
|
45
|
+
positions[i + 2] = (from[i + 2] as number) * scale + z;
|
|
46
|
+
}
|
|
47
|
+
return { ...mesh, positions };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** `meshes` as one, in order, each member's indices moved past the vertices before it. */
|
|
51
|
+
export function concatMeshes(meshes: readonly MeshData[]): MeshData {
|
|
52
|
+
const only = meshes[0];
|
|
53
|
+
if (meshes.length === 1 && only !== undefined) return only;
|
|
54
|
+
let vertices = 0;
|
|
55
|
+
let indexCount = 0;
|
|
56
|
+
for (const mesh of meshes) {
|
|
57
|
+
vertices += mesh.positions.length / 3;
|
|
58
|
+
indexCount += mesh.indices.length;
|
|
59
|
+
}
|
|
60
|
+
const out: MeshData = {
|
|
61
|
+
positions: new Float32Array(vertices * 3),
|
|
62
|
+
normals: new Float32Array(vertices * 3),
|
|
63
|
+
colors: new Float32Array(vertices * 3),
|
|
64
|
+
emissive: new Float32Array(vertices),
|
|
65
|
+
indices: new Uint32Array(indexCount),
|
|
66
|
+
};
|
|
67
|
+
const optional = out as unknown as Record<string, Float32Array | undefined>;
|
|
68
|
+
for (const [key, width] of OPTIONAL) {
|
|
69
|
+
if (meshes.some((mesh) => mesh[key] !== undefined)) {
|
|
70
|
+
optional[key] = new Float32Array(vertices * width);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
let vertex = 0;
|
|
74
|
+
let index = 0;
|
|
75
|
+
for (const mesh of meshes) {
|
|
76
|
+
const count = mesh.positions.length / 3;
|
|
77
|
+
out.positions.set(mesh.positions, vertex * 3);
|
|
78
|
+
out.normals.set(mesh.normals, vertex * 3);
|
|
79
|
+
out.colors.set(mesh.colors, vertex * 3);
|
|
80
|
+
out.emissive.set(mesh.emissive, vertex);
|
|
81
|
+
for (const [key, width] of OPTIONAL) {
|
|
82
|
+
const target = optional[key];
|
|
83
|
+
if (target === undefined) continue;
|
|
84
|
+
const source = mesh[key];
|
|
85
|
+
if (source !== undefined) {
|
|
86
|
+
target.set(source, vertex * width);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const fill = ABSENT_ATTRIBUTE[key] ?? [];
|
|
90
|
+
for (let v = 0; v < count; v++) {
|
|
91
|
+
for (let c = 0; c < width; c++) target[(vertex + v) * width + c] = fill[c] ?? 0;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const from = mesh.indices;
|
|
95
|
+
for (let i = 0; i < from.length; i++) out.indices[index + i] = (from[i] as number) + vertex;
|
|
96
|
+
vertex += count;
|
|
97
|
+
index += from.length;
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -50,6 +50,17 @@ export {
|
|
|
50
50
|
} from './core/timeCurve.ts';
|
|
51
51
|
export type { TimeCurve, TimeCurveKind, TimeCurveOptions } from './core/timeCurve.ts';
|
|
52
52
|
export { CinematicPlayer } from './cinematic/player.ts';
|
|
53
|
+
export {
|
|
54
|
+
createCameraPath,
|
|
55
|
+
createCameraPathSample,
|
|
56
|
+
sampleCameraPath,
|
|
57
|
+
} from './cinematic/cameraPath.ts';
|
|
58
|
+
export type {
|
|
59
|
+
CameraPath,
|
|
60
|
+
CameraPathKey,
|
|
61
|
+
CameraPathOptions,
|
|
62
|
+
CameraPathSample,
|
|
63
|
+
} from './cinematic/cameraPath.ts';
|
|
53
64
|
export { defineCinematic, validateCinematic } from './cinematic/script.ts';
|
|
54
65
|
export type {
|
|
55
66
|
CameraKeyframe,
|
|
@@ -65,6 +76,13 @@ export {
|
|
|
65
76
|
moonIllumination,
|
|
66
77
|
} from './environment/celestialClock.ts';
|
|
67
78
|
export type { CelestialSite, CelestialState } from './environment/celestialClock.ts';
|
|
79
|
+
export {
|
|
80
|
+
createDaylightPalette,
|
|
81
|
+
createDaylightState,
|
|
82
|
+
easeExposure,
|
|
83
|
+
resolveDaylight,
|
|
84
|
+
} from './environment/daylight.ts';
|
|
85
|
+
export type { DaylightKey, DaylightPalette, DaylightState } from './environment/daylight.ts';
|
|
68
86
|
|
|
69
87
|
export {
|
|
70
88
|
TAU,
|
|
@@ -219,6 +237,7 @@ export { createRenderer } from './render/backend/createRenderer.ts';
|
|
|
219
237
|
export type {
|
|
220
238
|
CreatedRenderer,
|
|
221
239
|
CreateRendererOptions,
|
|
240
|
+
QualityForBackend,
|
|
222
241
|
RenderPipeline,
|
|
223
242
|
} from './render/backend/createRenderer.ts';
|
|
224
243
|
/*
|
|
@@ -361,6 +380,8 @@ export type {
|
|
|
361
380
|
GlobalFieldCascade,
|
|
362
381
|
GlobalFieldInstance,
|
|
363
382
|
} from './render/gi/globalField.ts';
|
|
383
|
+
export { FIELD_FADE_SEC, LightField } from './render/driftLight/lightField.ts';
|
|
384
|
+
export type { LightFieldOptions, LightFieldSource } from './render/driftLight/lightField.ts';
|
|
364
385
|
export {
|
|
365
386
|
GI_SCREEN_MARCH,
|
|
366
387
|
SCREEN_TRACE_BIAS_M,
|
|
@@ -554,6 +575,8 @@ export type {
|
|
|
554
575
|
WaterReflectionFilterTaps,
|
|
555
576
|
} from './render/renderQuality.ts';
|
|
556
577
|
export { MAX_POINT_LIGHTS, SURFACE_TEXTURE_UNIT } from './render/lightBudget.ts';
|
|
578
|
+
/* The width a light buffer can usefully be with `clusteredLights` on; `createPointLightBuffer`. */
|
|
579
|
+
export { MAX_CLUSTERED_LIGHTS } from './render/clusteredLights.ts';
|
|
557
580
|
export { SurfaceTexture } from './render/surfaceTexture.ts';
|
|
558
581
|
export type { SurfaceMaterial, SurfaceTextureOptions } from './render/surfaceTexture.ts';
|
|
559
582
|
export { DEFAULT_GOVERNOR_LIMITS, ResolutionGovernor } from './render/resolutionGovernor.ts';
|
|
@@ -573,6 +596,8 @@ export {
|
|
|
573
596
|
selectPointLights,
|
|
574
597
|
} from './render/pointLightSelection.ts';
|
|
575
598
|
export type { PointLightBuffer, PointLightSource } from './render/pointLightSelection.ts';
|
|
599
|
+
export { createFlame, flameFrequencyHz, updateFlame } from './render/flameLight.ts';
|
|
600
|
+
export type { Flame, FlameOptions } from './render/flameLight.ts';
|
|
576
601
|
export { computeLightMatrix } from './render/lightMatrix.ts';
|
|
577
602
|
export { PlumeRenderer } from './render/plumeRenderer.ts';
|
|
578
603
|
export { createEnvironment } from './render/backend/webgl2/renderer.ts';
|
|
@@ -622,6 +647,7 @@ export { SMOKE_FRAG } from './render/shaders/smoke.ts';
|
|
|
622
647
|
export { WaterRenderer } from './render/waterRenderer.ts';
|
|
623
648
|
export type { WaterBody, WaterBounds, WaterSettings } from './render/waterRenderer.ts';
|
|
624
649
|
export { MeshBuilder } from './geometry/meshBuilder.ts';
|
|
650
|
+
export { concatMeshes, placeMesh } from './geometry/meshConcat.ts';
|
|
625
651
|
/* A tangent frame, which every map in the material track reads. */
|
|
626
652
|
export { generateTangents } from './geometry/tangents.ts';
|
|
627
653
|
export type { MeshBuildOptions } from './geometry/meshBuilder.ts';
|
|
@@ -941,6 +967,12 @@ export {
|
|
|
941
967
|
* and draw with that one number.
|
|
942
968
|
*/
|
|
943
969
|
deviceSnappedCellSize,
|
|
970
|
+
/*
|
|
971
|
+
* How many lit cells one text object draws before the rest of its string is cut. Exported so a
|
|
972
|
+
* caller laying out a runtime string, a readout that grows, can break it before the engine does:
|
|
973
|
+
* the cut is silent by design, since a clipped message beats a frame that does not render.
|
|
974
|
+
*/
|
|
975
|
+
MAX_CELLS as MAX_TEXT_CELLS,
|
|
944
976
|
textHeightPx,
|
|
945
977
|
textWidthPx,
|
|
946
978
|
} from './render/textLayout.ts';
|
|
@@ -49,6 +49,20 @@ export function pipelineRefusal(
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/** Everything about selection a caller may want to decide instead of inherit. */
|
|
52
|
+
/**
|
|
53
|
+
* The quality to build with, or a function of the backend `createRenderer` chose.
|
|
54
|
+
*
|
|
55
|
+
* **A function is how an option only one backend has is asked of that backend alone.** Traced
|
|
56
|
+
* indirect light and reconstruction are WebGPU's, and WebGL2 refuses either in words when handed
|
|
57
|
+
* one: right for a consumer who asked for it there, and noise for one who wanted it wherever it
|
|
58
|
+
* runs, since every device that fell back printed a refusal. Called once for the backend that
|
|
59
|
+
* draws, and once more for WebGPU's timing request where WebGPU is tried. What would make it wrong
|
|
60
|
+
* is a consumer needing to know *why* the backend fell back before choosing, which `reason` on the
|
|
61
|
+
* result says after the fact and nothing says before.
|
|
62
|
+
*/
|
|
63
|
+
export type QualityForBackend =
|
|
64
|
+
RenderQualityOptions | ((backend: RenderBackend) => RenderQualityOptions);
|
|
65
|
+
|
|
52
66
|
export interface CreateRendererOptions {
|
|
53
67
|
/**
|
|
54
68
|
* Whether to try WebGPU at all. **Defaults to true**: WebGPU is the backend this engine is
|
|
@@ -234,9 +248,11 @@ function presentOnFirstFrame(renderer: RendererApi, splash: MountedSplash): void
|
|
|
234
248
|
*/
|
|
235
249
|
export async function createRenderer(
|
|
236
250
|
canvas: HTMLCanvasElement,
|
|
237
|
-
quality:
|
|
251
|
+
quality: QualityForBackend = {},
|
|
238
252
|
options: CreateRendererOptions = {},
|
|
239
253
|
): Promise<CreatedRenderer> {
|
|
254
|
+
const qualityFor = (backend: RenderBackend): RenderQualityOptions =>
|
|
255
|
+
typeof quality === 'function' ? quality(backend) : quality;
|
|
240
256
|
const search = options.search ?? globalThis.location?.search ?? '';
|
|
241
257
|
|
|
242
258
|
/*
|
|
@@ -317,7 +333,7 @@ export async function createRenderer(
|
|
|
317
333
|
const choice = await selectBackend(
|
|
318
334
|
search,
|
|
319
335
|
options.preferWebGpu ?? true,
|
|
320
|
-
|
|
336
|
+
(options.preferWebGpu ?? true) ? (qualityFor('webgpu').gpuTiming ?? false) : false,
|
|
321
337
|
options.probeShaders ?? [],
|
|
322
338
|
budgetMs,
|
|
323
339
|
);
|
|
@@ -364,7 +380,7 @@ export async function createRenderer(
|
|
|
364
380
|
if (settled === TIMED_OUT) {
|
|
365
381
|
device.destroy();
|
|
366
382
|
return built({
|
|
367
|
-
renderer: new Renderer(canvas,
|
|
383
|
+
renderer: new Renderer(canvas, qualityFor('webgl2')),
|
|
368
384
|
backend: 'webgl2',
|
|
369
385
|
reason: `WebGPU stalled while ${stage}, fell back after ${budgetMs} ms`,
|
|
370
386
|
});
|
|
@@ -385,7 +401,7 @@ export async function createRenderer(
|
|
|
385
401
|
*/
|
|
386
402
|
renderer: new WebGPURenderer(
|
|
387
403
|
surface,
|
|
388
|
-
resolveRenderQuality(
|
|
404
|
+
resolveRenderQuality(qualityFor('webgpu')),
|
|
389
405
|
/* The adapter's own words, so the capability clamp has a part number to
|
|
390
406
|
recognise rather than the literal string `WebGPU`. */
|
|
391
407
|
choice.rendererName ?? undefined,
|
|
@@ -397,7 +413,7 @@ export async function createRenderer(
|
|
|
397
413
|
choice.device.destroy();
|
|
398
414
|
const detail = error instanceof Error ? error.message : String(error);
|
|
399
415
|
return built({
|
|
400
|
-
renderer: new Renderer(canvas,
|
|
416
|
+
renderer: new Renderer(canvas, qualityFor('webgl2')),
|
|
401
417
|
backend: 'webgl2',
|
|
402
418
|
reason: `WebGPU surface failed, fell back: ${detail}`,
|
|
403
419
|
});
|
|
@@ -425,7 +441,7 @@ export async function createRenderer(
|
|
|
425
441
|
console.warn(`[driftengine] WebGPU was asked for and is not being used: ${choice.reason}`);
|
|
426
442
|
}
|
|
427
443
|
return built({
|
|
428
|
-
renderer: new Renderer(canvas,
|
|
444
|
+
renderer: new Renderer(canvas, qualityFor('webgl2')),
|
|
429
445
|
backend: 'webgl2',
|
|
430
446
|
reason: choice.reason,
|
|
431
447
|
});
|