@woosh/meep-engine 2.165.2 → 2.165.4
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/package.json +1 -1
- package/src/core/geom/3d/sphere/harmonics/sh3_convolve_with_zh.d.ts +21 -0
- package/src/core/geom/3d/sphere/harmonics/sh3_convolve_with_zh.d.ts.map +1 -0
- package/src/core/geom/3d/sphere/harmonics/sh3_convolve_with_zh.js +53 -0
- package/src/core/geom/3d/sphere/harmonics/sh3_ggx_zh.d.ts +18 -0
- package/src/core/geom/3d/sphere/harmonics/sh3_ggx_zh.d.ts.map +1 -0
- package/src/core/geom/3d/sphere/harmonics/sh3_ggx_zh.js +30 -0
- package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts +2 -0
- package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail2d/Trail2D.js +5 -2
- package/src/engine/graphics/ecs/trail2d/Trail2DSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail2d/Trail2DSystem.js +3 -2
- package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts +2 -0
- package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail3d/Trail3D.js +5 -2
- package/src/engine/graphics/ecs/trail3d/Trail3DSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail3d/Trail3DSystem.js +3 -2
- package/src/engine/graphics/sh3/lpg/LightProbeGridBaker.d.ts +43 -4
- package/src/engine/graphics/sh3/lpg/LightProbeGridBaker.d.ts.map +1 -1
- package/src/engine/graphics/sh3/lpg/LightProbeGridBaker.js +72 -5
- package/src/engine/graphics/sh3/lpg/bake_probe_sh3_resampled.d.ts +37 -0
- package/src/engine/graphics/sh3/lpg/bake_probe_sh3_resampled.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/bake_probe_sh3_resampled.js +298 -0
- package/src/engine/graphics/sh3/lpg/light_probe_grid_dilate.d.ts +18 -0
- package/src/engine/graphics/sh3/lpg/light_probe_grid_dilate.d.ts.map +1 -0
- package/src/engine/graphics/sh3/lpg/light_probe_grid_dilate.js +142 -0
- package/src/engine/graphics/sh3/path_tracer/PathTracer.d.ts +6 -0
- package/src/engine/graphics/sh3/path_tracer/PathTracer.d.ts.map +1 -1
- package/src/engine/graphics/sh3/path_tracer/PathTracer.js +1 -1
- package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.d.ts +5 -3
- package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.d.ts.map +1 -1
- package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.js +6 -4
- package/src/engine/graphics/trail/x/simulator/RibbonXFixedPhysicsSimulator.d.ts +2 -1
- package/src/engine/graphics/trail/x/simulator/RibbonXFixedPhysicsSimulator.d.ts.map +1 -1
- package/src/engine/graphics/trail/x/simulator/RibbonXFixedPhysicsSimulator.js +3 -2
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"description": "Pure JavaScript game engine. Fully featured and production ready.",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"author": "Alexander Goldring",
|
|
9
|
-
"version": "2.165.
|
|
9
|
+
"version": "2.165.4",
|
|
10
10
|
"main": "build/meep.module.js",
|
|
11
11
|
"module": "build/meep.module.js",
|
|
12
12
|
"exports": {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convolve 3-band spherical harmonic coefficients with a zonal harmonic kernel.
|
|
3
|
+
*
|
|
4
|
+
* Convolution with a circularly-symmetric (zonal) kernel is a per-band scale: band 0 is scaled by
|
|
5
|
+
* `zh0`, band 1 by `zh1` and band 2 by `zh2`. The kernel coefficients are expected to already include
|
|
6
|
+
* the `sqrt(4*PI / (2l + 1))` convolution normalization (as e.g. {@link sh3_ggx_zh} does).
|
|
7
|
+
*
|
|
8
|
+
* Layout is coefficient-major with `dimensions` interleaved channels per coefficient, matching
|
|
9
|
+
* {@link sh3_vector_accumulate}. Safe to use in-place (`out === input` with equal offsets).
|
|
10
|
+
*
|
|
11
|
+
* @param {number[]|Float32Array|Float64Array} out
|
|
12
|
+
* @param {number} out_offset
|
|
13
|
+
* @param {number[]|Float32Array|Float64Array} input
|
|
14
|
+
* @param {number} input_offset
|
|
15
|
+
* @param {number} zh0 band 0 scale
|
|
16
|
+
* @param {number} zh1 band 1 scale
|
|
17
|
+
* @param {number} zh2 band 2 scale
|
|
18
|
+
* @param {number} dimensions number of interleaved channels per coefficient, e.g. 3 for RGB
|
|
19
|
+
*/
|
|
20
|
+
export function sh3_convolve_with_zh(out: number[] | Float32Array | Float64Array, out_offset: number, input: number[] | Float32Array | Float64Array, input_offset: number, zh0: number, zh1: number, zh2: number, dimensions: number): void;
|
|
21
|
+
//# sourceMappingURL=sh3_convolve_with_zh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sh3_convolve_with_zh.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/geom/3d/sphere/harmonics/sh3_convolve_with_zh.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,0CATW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,cAClC,MAAM,SACN,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,gBAClC,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,cACN,MAAM,QAiChB"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { assert } from "../../../../assert.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convolve 3-band spherical harmonic coefficients with a zonal harmonic kernel.
|
|
5
|
+
*
|
|
6
|
+
* Convolution with a circularly-symmetric (zonal) kernel is a per-band scale: band 0 is scaled by
|
|
7
|
+
* `zh0`, band 1 by `zh1` and band 2 by `zh2`. The kernel coefficients are expected to already include
|
|
8
|
+
* the `sqrt(4*PI / (2l + 1))` convolution normalization (as e.g. {@link sh3_ggx_zh} does).
|
|
9
|
+
*
|
|
10
|
+
* Layout is coefficient-major with `dimensions` interleaved channels per coefficient, matching
|
|
11
|
+
* {@link sh3_vector_accumulate}. Safe to use in-place (`out === input` with equal offsets).
|
|
12
|
+
*
|
|
13
|
+
* @param {number[]|Float32Array|Float64Array} out
|
|
14
|
+
* @param {number} out_offset
|
|
15
|
+
* @param {number[]|Float32Array|Float64Array} input
|
|
16
|
+
* @param {number} input_offset
|
|
17
|
+
* @param {number} zh0 band 0 scale
|
|
18
|
+
* @param {number} zh1 band 1 scale
|
|
19
|
+
* @param {number} zh2 band 2 scale
|
|
20
|
+
* @param {number} dimensions number of interleaved channels per coefficient, e.g. 3 for RGB
|
|
21
|
+
*/
|
|
22
|
+
export function sh3_convolve_with_zh(
|
|
23
|
+
out, out_offset,
|
|
24
|
+
input, input_offset,
|
|
25
|
+
zh0, zh1, zh2,
|
|
26
|
+
dimensions
|
|
27
|
+
) {
|
|
28
|
+
assert.isArrayLike(out, 'out');
|
|
29
|
+
assert.isNonNegativeInteger(out_offset, 'out_offset');
|
|
30
|
+
assert.isArrayLike(input, 'input');
|
|
31
|
+
assert.isNonNegativeInteger(input_offset, 'input_offset');
|
|
32
|
+
assert.isNumber(zh0, 'zh0');
|
|
33
|
+
assert.isNumber(zh1, 'zh1');
|
|
34
|
+
assert.isNumber(zh2, 'zh2');
|
|
35
|
+
assert.isNonNegativeInteger(dimensions, 'dimensions');
|
|
36
|
+
|
|
37
|
+
const band_1_end = 4 * dimensions;
|
|
38
|
+
const band_2_end = 9 * dimensions;
|
|
39
|
+
|
|
40
|
+
let i = 0;
|
|
41
|
+
|
|
42
|
+
for (; i < dimensions; i++) {
|
|
43
|
+
out[out_offset + i] = input[input_offset + i] * zh0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (; i < band_1_end; i++) {
|
|
47
|
+
out[out_offset + i] = input[input_offset + i] * zh1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (; i < band_2_end; i++) {
|
|
51
|
+
out[out_offset + i] = input[input_offset + i] * zh2;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zonal harmonic coefficients approximating a GGX lobe of the given roughness.
|
|
3
|
+
*
|
|
4
|
+
* Convolving a radiance SH with these coefficients (see {@link sh3_convolve_with_zh}) low-passes it with
|
|
5
|
+
* a GGX-shaped cone: band 0 is preserved while bands 1 and 2 are attenuated progressively harder as
|
|
6
|
+
* `alpha` grows. Attenuating the directional bands tapers the SH reconstruction towards its mean,
|
|
7
|
+
* which suppresses ringing (negative lobes / light bleeding) at the cost of directional sharpness.
|
|
8
|
+
*
|
|
9
|
+
* The rational fit is from MJP's SH library.
|
|
10
|
+
*
|
|
11
|
+
* @see https://github.com/TheRealMJP/SHforHLSL/blob/e426058959123063e13d61a62df6217259b72cec/SH.hlsli#L394
|
|
12
|
+
*
|
|
13
|
+
* @param {number[]|Float32Array|Float64Array} out zonal coefficients [l0, l1, l2] are written here
|
|
14
|
+
* @param {number} out_offset
|
|
15
|
+
* @param {number} alpha GGX roughness (perceptual roughness squared) in [0, 1]; the fit is only valid in this range
|
|
16
|
+
*/
|
|
17
|
+
export function sh3_ggx_zh(out: number[] | Float32Array | Float64Array, out_offset: number, alpha: number): void;
|
|
18
|
+
//# sourceMappingURL=sh3_ggx_zh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sh3_ggx_zh.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/geom/3d/sphere/harmonics/sh3_ggx_zh.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AACH,gCAJW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,cAClC,MAAM,SACN,MAAM,QAahB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { assert } from "../../../../assert.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Zonal harmonic coefficients approximating a GGX lobe of the given roughness.
|
|
5
|
+
*
|
|
6
|
+
* Convolving a radiance SH with these coefficients (see {@link sh3_convolve_with_zh}) low-passes it with
|
|
7
|
+
* a GGX-shaped cone: band 0 is preserved while bands 1 and 2 are attenuated progressively harder as
|
|
8
|
+
* `alpha` grows. Attenuating the directional bands tapers the SH reconstruction towards its mean,
|
|
9
|
+
* which suppresses ringing (negative lobes / light bleeding) at the cost of directional sharpness.
|
|
10
|
+
*
|
|
11
|
+
* The rational fit is from MJP's SH library.
|
|
12
|
+
*
|
|
13
|
+
* @see https://github.com/TheRealMJP/SHforHLSL/blob/e426058959123063e13d61a62df6217259b72cec/SH.hlsli#L394
|
|
14
|
+
*
|
|
15
|
+
* @param {number[]|Float32Array|Float64Array} out zonal coefficients [l0, l1, l2] are written here
|
|
16
|
+
* @param {number} out_offset
|
|
17
|
+
* @param {number} alpha GGX roughness (perceptual roughness squared) in [0, 1]; the fit is only valid in this range
|
|
18
|
+
*/
|
|
19
|
+
export function sh3_ggx_zh(out, out_offset, alpha) {
|
|
20
|
+
assert.isArrayLike(out, 'out');
|
|
21
|
+
assert.isNonNegativeInteger(out_offset, 'out_offset');
|
|
22
|
+
assert.isNumber(alpha, 'alpha');
|
|
23
|
+
assert.notNaN(alpha, 'alpha');
|
|
24
|
+
assert.greaterThanOrEqual(alpha, 0, 'alpha');
|
|
25
|
+
assert.lessThanOrEqual(alpha, 1, 'alpha');
|
|
26
|
+
|
|
27
|
+
out[out_offset] = 1;
|
|
28
|
+
out[out_offset + 1] = 1.66711256633276 / (1.65715038133932 + alpha);
|
|
29
|
+
out[out_offset + 2] = 1.56127990596116 / (0.96989757593282 + alpha) - 0.599972342361123;
|
|
30
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Trail2D.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/ecs/trail2d/Trail2D.js"],"names":[],"mappings":";AAwBA;
|
|
1
|
+
{"version":3,"file":"Trail2D.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/ecs/trail2d/Trail2D.js"],"names":[],"mappings":";AAwBA;IAkMI,oCAMC;IAtMD;;;OAGG;IACH,QAFU,MAAM,CAES;IAEzB;;;OAGG;IACH,OAFU,MAAM,CAEM;IAEtB;;;OAGG;IACH,WAFU,cAAc,GAAC,MAAM,CAEC;IAEhC;;;;OAIG;IACH,eAFU,MAAM,CAEuB;IAEvC;;;OAGG;IACH,MAFU,MAAM,CAEP;IAET;;;OAGG;IACH,eAFU,MAAM,CAEE;IAElB;;;OAGG;IACH,qBAFU,MAAM,CAEQ;IAExB;;;OAGG;IACH,yBAFU,MAAM,CAEY;IAE5B;;;;;OAKG;IACH,gBAFU,KAAK,CAEe;IAE9B;;;;OAIG;IACH,iBAFU,OAAO,CAEM;IAEvB;;;OAGG;IACH,QAFU,OAAO,GAAC,IAAI,CAER;IAEd;;;OAGG;IACH,UAFU,mBAAmB,CAEQ;IAErC;;;OAGG;IACH,cAFU,SAAS,CAEG;IAEtB;;;OAGG;IACH,cAAsB;IAEtB,gBAQC;IAMD,4BAEC;IAND,yBAEC;IAMD;;;;OAIG;IACH,cAHW,MAAM,GAAC,YAAY,GACjB,IAAI,CAIhB;IAED;;;;OAIG;IACH,gBAHW,MAAM,GAAC,YAAY,GACjB,IAAI,CAIhB;IAED;;;;OAIG;IACH,gBAHW,MAAM,GAAC,YAAY,SACnB,OAAO,QAQjB;IAED;;;;OAIG;IACH,cAHW,MAAM,GAAC,YAAY,GACjB,OAAO,CAInB;IAED;;;OAGG;IACH,qBAFW,MAAM,QAehB;IADG,kHAAgB;IAGpB;;OAEG;IACH,cAsBC;IAUD;;;;;;;;;;;;;aAkBC;IAGD;;;;;;;;;;;;;;;;;MAUC;IAED;;;OAGG;IACH,cAFW,OAAO,WAWjB;IAED;;;;;;;OAOG;IACH,cANW,MAAM,KACN,MAAM,KACN,MAAM,YACN,MAAM,GACJ,OAAO,CAmEnB;CAEJ;;;;;;+BAtV8B,+BAA+B;sBALxC,iCAAiC;oBACnC,kCAAkC;wBAM9B,0BAA0B;oCACd,sCAAsC;0BAThD,yCAAyC;6BAWtC,mBAAmB;kCADd,wBAAwB"}
|
|
@@ -74,6 +74,8 @@ class Trail2D {
|
|
|
74
74
|
distanceSinceLastUpdate = 0;
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
|
+
* RGB tints the ribbon; alpha is the trail's base opacity, which the
|
|
78
|
+
* age-driven fade scales down to zero.
|
|
77
79
|
* @readonly
|
|
78
80
|
* @type {Color}
|
|
79
81
|
*/
|
|
@@ -336,9 +338,10 @@ class Trail2D {
|
|
|
336
338
|
this.setFlag(Trail2DFlags.BoundsNeedUpdate);
|
|
337
339
|
}
|
|
338
340
|
|
|
339
|
-
// set head to fresh value
|
|
341
|
+
// set head to fresh value; alpha follows the same fade law the simulator
|
|
342
|
+
// applies every tick, so the point doesn't pop when it stops being the head
|
|
340
343
|
ribbon.setPointAttribute_Scalar(head_index, RIBBON_ATTRIBUTE_ADDRESS_AGE, head_age);
|
|
341
|
-
ribbon.setPointAlpha(head_index, clamp01(head_age / maxAge));
|
|
344
|
+
ribbon.setPointAlpha(head_index, (1 - clamp01(head_age / maxAge)) * this.color.w);
|
|
342
345
|
ribbon.setPointThickness(head_index, this.width);
|
|
343
346
|
|
|
344
347
|
return head_position_changed;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Trail2DSystem.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/ecs/trail2d/Trail2DSystem.js"],"names":[],"mappings":";AA0BA;IAkCI;;;OAGG;IACH,4BA4BC;IAjED,oDAAoC;IAEpC,+DAEE;IAEF;;;OAGG;IACH,WAFU,4BAA4B,CAES;IAE/C;;;;OAIG;IACH,wBAAiC;IAGjC;;;;OAIG;IACH,oBAAgB;IAEhB;;;OAGG;IACH,YAAgB;IAcZ;;;;OAIG;IACH,iBAAsB;IAEtB;;;OAGG;IACH,UAFU,cAAc,CAEO;IAE/B;;;OAGG;IACH,aAFU,cAAY,IAAI,CAEH;IAK3B,2CAmBC;IAED,4CAIC;IAED;;;;;OAKG;IACH,
|
|
1
|
+
{"version":3,"file":"Trail2DSystem.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/ecs/trail2d/Trail2DSystem.js"],"names":[],"mappings":";AA0BA;IAkCI;;;OAGG;IACH,4BA4BC;IAjED,oDAAoC;IAEpC,+DAEE;IAEF;;;OAGG;IACH,WAFU,4BAA4B,CAES;IAE/C;;;;OAIG;IACH,wBAAiC;IAGjC;;;;OAIG;IACH,oBAAgB;IAEhB;;;OAGG;IACH,YAAgB;IAcZ;;;;OAIG;IACH,iBAAsB;IAEtB;;;OAGG;IACH,UAFU,cAAc,CAEO;IAE/B;;;OAGG;IACH,aAFU,cAAY,IAAI,CAEH;IAK3B,2CAmBC;IAED,4CAIC;IAED;;;;;OAKG;IACH,sBAsDC;IAED;;;;;OAKG;IACH,YAHW,OAAO,aADP,SAAS,YAET,MAAM,QAahB;IAED;;;;;OAKG;IACH,kBAJW,OAAO,aACP,SAAS,UACT,MAAM,QAShB;IAED;;;;OAIG;IACH,yBAHW,OAAO,aACP,SAAS,QAoCnB;IAED,6BAWC;CACJ;uBAtQsB,wBAAwB;0BACrB,qCAAqC;oBAY3C,cAAc;4CAdU,uDAAuD;6CAYtD,yDAAyD;+BARvE,yBAAyB"}
|
|
@@ -167,7 +167,8 @@ class Trail2DSystem extends System {
|
|
|
167
167
|
ribbon.setPointPosition(i, position_x, position_y, position_z);
|
|
168
168
|
ribbon.setPointThickness(i, trail.width);
|
|
169
169
|
ribbon.setPointAttribute_Scalar(i, RIBBON_ATTRIBUTE_ADDRESS_UV_OFFSET, f);
|
|
170
|
-
|
|
170
|
+
// seeded age = maxAge means "already expired", so alpha follows the fade law: 0
|
|
171
|
+
ribbon.setPointAlpha(i, 0);
|
|
171
172
|
ribbon.setPointAttribute_Scalar(i, RIBBON_ATTRIBUTE_ADDRESS_AGE, trail.maxAge);
|
|
172
173
|
|
|
173
174
|
}
|
|
@@ -238,7 +239,7 @@ class Trail2DSystem extends System {
|
|
|
238
239
|
|
|
239
240
|
const ageOffset = max2(0, trail.maxAge - trail.time);
|
|
240
241
|
|
|
241
|
-
this.simulator.update(ribbon, trail.maxAge, timeDelta);
|
|
242
|
+
this.simulator.update(ribbon, trail.maxAge, timeDelta, trail.color.w);
|
|
242
243
|
|
|
243
244
|
if (trail.getFlag(Trail2DFlags.Spawning)) {
|
|
244
245
|
trail.updateHead(newPosition.x, newPosition.y, newPosition.z, ageOffset);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Trail3D.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/ecs/trail3d/Trail3D.js"],"names":[],"mappings":";AAgCA;;;;;GAKG;AACH;
|
|
1
|
+
{"version":3,"file":"Trail3D.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/ecs/trail3d/Trail3D.js"],"names":[],"mappings":";AAgCA;;;;;GAKG;AACH;IA8MI,oCAMC;IAlND;;;OAGG;IACH,QAFU,MAAM,CAES;IAEzB;;;OAGG;IACH,OAFU,MAAM,CAEM;IAEtB;;;;OAIG;IACH,gBAFU,MAAM,CAEyB;IAEzC;;;OAGG;IACH,WAFU,cAAc,GAAC,MAAM,CAEC;IAEhC;;;;OAIG;IACH,eAFU,MAAM,CAEuB;IAEvC;;;OAGG;IACH,MAFU,MAAM,CAEP;IAET;;OAEG;IACH,eAFU,MAAM,CAEE;IAElB;;;OAGG;IACH,qBAFU,MAAM,CAEQ;IAExB;;;OAGG;IACH,yBAFU,MAAM,CAEY;IAE5B;;;;;OAKG;IACH,gBAFU,KAAK,CAEe;IAE9B;;;;OAIG;IACH,iBAFU,OAAO,CAEM;IAEvB;;;OAGG;IACH,MAFU,KAAK,GAAC,IAAI,CAER;IAEZ;;;OAGG;IACH,MAFU,OAAK,IAAI,CAEP;IAEZ;;;OAGG;IACH,UAFU,iBAAiB,CAEQ;IAEnC;;;OAGG;IACH,cAFU,SAAS,CAEG;IAEtB;;;OAGG;IACH,cAAsB;IAEtB,gBAQC;IAMD,4BAEC;IAND,yBAEC;IAUD,6BAEC;IAND,0BAEC;IAMD;;;OAGG;IACH,cAHW,MAAM,GAAC,YAAY,GACjB,IAAI,CAIhB;IAED;;;OAGG;IACH,gBAHW,MAAM,GAAC,YAAY,GACjB,IAAI,CAIhB;IAED;;;OAGG;IACH,gBAHW,MAAM,GAAC,YAAY,SACnB,OAAO,QAQjB;IAED;;;OAGG;IACH,cAHW,MAAM,GAAC,YAAY,GACjB,OAAO,CAInB;IAED;;OAEG;IACH,qBAFW,MAAM,QAehB;IAED;;OAEG;IACH,cAmBC;IAUD;;;;;;;;;;;;;;;aAsBC;IAED;;;;;;;;;;;;;;;;;;;MAYC;IAED;;OAEG;IACH,cAFW,OAAO,WAajB;IAkDD;;;;;;OAMG;IACH,cANW,MAAM,KACN,MAAM,KACN,MAAM,YACN,MAAM,GACJ,OAAO,CAiEnB;;CAEJ;;;;;+BAja8B,+BAA+B;sBAPxC,iCAAiC;oBACnC,kCAAkC;sBAShC,2BAA2B;kCACf,uCAAuC;0BAZ/C,yCAAyC;6BAatC,mBAAmB"}
|
|
@@ -94,6 +94,8 @@ class Trail3D {
|
|
|
94
94
|
distanceSinceLastUpdate = 0;
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
|
+
* RGB tints the tube; alpha is the trail's base opacity, which the
|
|
98
|
+
* age-driven fade scales down to zero.
|
|
97
99
|
* @readonly
|
|
98
100
|
* @type {Color}
|
|
99
101
|
*/
|
|
@@ -413,9 +415,10 @@ class Trail3D {
|
|
|
413
415
|
this.setFlag(Trail3DFlags.BoundsNeedUpdate);
|
|
414
416
|
}
|
|
415
417
|
|
|
416
|
-
// set head to fresh value
|
|
418
|
+
// set head to fresh value; alpha follows the same fade law the simulator
|
|
419
|
+
// applies every tick, so the knot doesn't pop when it stops being the head
|
|
417
420
|
tube.setKnotAttribute_Scalar(head_index, TUBE_ATTRIBUTE_ADDRESS_AGE, head_age);
|
|
418
|
-
tube.setKnotAlpha(head_index, clamp01(head_age / maxAge));
|
|
421
|
+
tube.setKnotAlpha(head_index, (1 - clamp01(head_age / maxAge)) * this.color.w);
|
|
419
422
|
tube.setKnotThickness(head_index, this.width);
|
|
420
423
|
|
|
421
424
|
return head_position_changed;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Trail3DSystem.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/ecs/trail3d/Trail3DSystem.js"],"names":[],"mappings":";AAyBA;IAiCI;;;OAGG;IACH,4BAkBC;IAtDD,oDAAoC;IAEpC,+DAEE;IAEF;;OAEG;IACH,WAFU,0BAA0B,CAES;IAiB7C;;;OAGG;IACH,YAAgB;IAeZ;;OAEG;IACH,UAFU,cAAc,CAEO;IAE/B;;OAEG;IACH,aAFU,cAAY,IAAI,CAEH;IAG3B,2CAmBC;IAED,4CAIC;
|
|
1
|
+
{"version":3,"file":"Trail3DSystem.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/ecs/trail3d/Trail3DSystem.js"],"names":[],"mappings":";AAyBA;IAiCI;;;OAGG;IACH,4BAkBC;IAtDD,oDAAoC;IAEpC,+DAEE;IAEF;;OAEG;IACH,WAFU,0BAA0B,CAES;IAiB7C;;;OAGG;IACH,YAAgB;IAeZ;;OAEG;IACH,UAFU,cAAc,CAEO;IAE/B;;OAEG;IACH,aAFU,cAAY,IAAI,CAEH;IAG3B,2CAmBC;IAED,4CAIC;IA2DD;;;;OAIG;IACH,YAJW,OAAO,aACP,SAAS,YACT,MAAM,QAYhB;IAED;;;;OAIG;IACH,kBAJW,OAAO,aACP,SAAS,UACT,MAAM,QAQhB;IAED;;;OAGG;IACH,yBAHW,OAAO,aACP,SAAS,QAkCnB;IAED,6BAWC;;CACJ;uBA9OsB,wBAAwB;0BACrB,qCAAqC;oBAW3C,cAAc;4CAbU,uDAAuD;2CAUxD,0DAA0D;+BANtE,yBAAyB"}
|
|
@@ -151,7 +151,8 @@ class Trail3DSystem extends System {
|
|
|
151
151
|
tube.setKnotPosition(i, position_x, position_y, position_z);
|
|
152
152
|
tube.setKnotThickness(i, trail.width);
|
|
153
153
|
tube.setKnotAttribute_Scalar(i, TUBE_ATTRIBUTE_ADDRESS_UV_V, f);
|
|
154
|
-
|
|
154
|
+
// seeded age = maxAge means "already expired", so alpha follows the fade law: 0
|
|
155
|
+
tube.setKnotAlpha(i, 0);
|
|
155
156
|
tube.setKnotAttribute_Scalar(i, TUBE_ATTRIBUTE_ADDRESS_AGE, trail.maxAge);
|
|
156
157
|
}
|
|
157
158
|
|
|
@@ -215,7 +216,7 @@ class Trail3DSystem extends System {
|
|
|
215
216
|
|
|
216
217
|
const ageOffset = max2(0, trail.maxAge - trail.time);
|
|
217
218
|
|
|
218
|
-
this.simulator.update(tube, trail.maxAge, timeDelta);
|
|
219
|
+
this.simulator.update(tube, trail.maxAge, timeDelta, trail.color.w);
|
|
219
220
|
|
|
220
221
|
if (trail.getFlag(Trail3DFlags.Spawning)) {
|
|
221
222
|
trail.updateHead(newPosition.x, newPosition.y, newPosition.z, ageOffset);
|
|
@@ -1,9 +1,45 @@
|
|
|
1
1
|
export class LightProbeGridBaker {
|
|
2
|
+
/**
|
|
3
|
+
* Directions sampled one hit deep per probe. Direct light and sky converge with this count,
|
|
4
|
+
* every candidate also feeds the back-face visibility statistic.
|
|
5
|
+
*
|
|
6
|
+
* The defaults match the directional noise of the previous 4096-full-path bake at roughly half
|
|
7
|
+
* the cost (candidates are single-segment); raise both counts together for higher quality.
|
|
8
|
+
* @type {number}
|
|
9
|
+
*/
|
|
10
|
+
candidate_count: number;
|
|
11
|
+
/**
|
|
12
|
+
* Full multi-bounce path traces per probe, re-sampled from the candidates by first-bounce
|
|
13
|
+
* importance. Only the indirect part of the estimate rides on these, so far fewer are needed
|
|
14
|
+
* than candidates.
|
|
15
|
+
* @type {number}
|
|
16
|
+
*/
|
|
17
|
+
indirect_sample_count: number;
|
|
18
|
+
/**
|
|
19
|
+
* Total path depth including the first hit
|
|
20
|
+
* @type {number}
|
|
21
|
+
*/
|
|
22
|
+
max_bounce_count: number;
|
|
23
|
+
/**
|
|
24
|
+
* GGX roughness of the prefilter cone applied to the baked radiance SH, in [0, 1].
|
|
25
|
+
* Attenuates band 1 before the 2-band truncation, suppressing directional overshoot that would
|
|
26
|
+
* otherwise reconstruct as negative irradiance (light/shadow bleeding) on the far side of a
|
|
27
|
+
* strong light. Higher values trade directionality for stability.
|
|
28
|
+
* @type {number}
|
|
29
|
+
*/
|
|
30
|
+
filter_ggx_alpha: number;
|
|
31
|
+
/**
|
|
32
|
+
* A probe whose fraction of candidate rays hitting geometry back-faces exceeds this threshold is
|
|
33
|
+
* considered to be inside geometry. Such probes are filled from their valid neighbors after the
|
|
34
|
+
* bake instead of contributing interior darkness to trilinear filtering.
|
|
35
|
+
* @type {number}
|
|
36
|
+
*/
|
|
37
|
+
interior_backface_threshold: number;
|
|
2
38
|
/**
|
|
3
39
|
* Renderer used to bake probes. Exposed so callers (and tests) can configure the underlying scene.
|
|
4
|
-
* @return {
|
|
40
|
+
* @return {PathTracerProbeRenderer}
|
|
5
41
|
*/
|
|
6
|
-
get renderer():
|
|
42
|
+
get renderer(): PathTracerProbeRenderer;
|
|
7
43
|
/**
|
|
8
44
|
* Bake light probes
|
|
9
45
|
* @returns {TaskGroup}
|
|
@@ -13,12 +49,15 @@ export class LightProbeGridBaker {
|
|
|
13
49
|
bake(grid: LightProbeGrid, ecd: EntityComponentDataset): TaskGroup;
|
|
14
50
|
/**
|
|
15
51
|
*
|
|
16
|
-
* @param {
|
|
52
|
+
* @param {PathTracerProbeRenderer} renderer
|
|
17
53
|
* @param {LightProbeGrid} grid
|
|
18
54
|
* @param {number} probe_index
|
|
55
|
+
* @returns {boolean} true if the probe is valid, false if it appears to be inside geometry and
|
|
56
|
+
* should be filled from its neighbors
|
|
19
57
|
*/
|
|
20
|
-
bake_probe(renderer:
|
|
58
|
+
bake_probe(renderer: PathTracerProbeRenderer, grid: LightProbeGrid, probe_index: number): boolean;
|
|
21
59
|
#private;
|
|
22
60
|
}
|
|
61
|
+
import { PathTracerProbeRenderer } from "../lpv/PathTracerProbeRenderer.js";
|
|
23
62
|
import TaskGroup from "../../../../core/process/task/TaskGroup.js";
|
|
24
63
|
//# sourceMappingURL=LightProbeGridBaker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LightProbeGridBaker.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/sh3/lpg/LightProbeGridBaker.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LightProbeGridBaker.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/sh3/lpg/LightProbeGridBaker.js"],"names":[],"mappings":"AAgBA;IAII;;;;;;;OAOG;IACH,iBAFU,MAAM,CAEO;IAEvB;;;;;OAKG;IACH,uBAFU,MAAM,CAEY;IAE5B;;;OAGG;IACH,kBAFU,MAAM,CAEK;IAErB;;;;;;OAMG;IACH,kBAFU,MAAM,CAEO;IAEvB;;;;;OAKG;IACH,6BAFU,MAAM,CAEmB;IAEnC;;;OAGG;IACH,wCAEC;IAED;;;;;OAKG;IACH,yDAJa,SAAS,CAoDrB;IAED;;;;;;;OAOG;IACH,qBANW,uBAAuB,qCAEvB,MAAM,GACJ,OAAO,CAuBnB;;CACJ;wCAlJuC,mCAAmC;sBAJrD,4CAA4C"}
|
|
@@ -1,21 +1,67 @@
|
|
|
1
1
|
import { assert } from "../../../../core/assert.js";
|
|
2
2
|
import { array_copy } from "../../../../core/collection/array/array_copy.js";
|
|
3
|
+
import { sh3_convolve_with_zh } from "../../../../core/geom/3d/sphere/harmonics/sh3_convolve_with_zh.js";
|
|
4
|
+
import { sh3_ggx_zh } from "../../../../core/geom/3d/sphere/harmonics/sh3_ggx_zh.js";
|
|
3
5
|
import TaskGroup from "../../../../core/process/task/TaskGroup.js";
|
|
4
6
|
import { actionTask } from "../../../../core/process/task/util/actionTask.js";
|
|
5
7
|
import { countTask } from "../../../../core/process/task/util/countTask.js";
|
|
6
8
|
import { promiseTask } from "../../../../core/process/task/util/promiseTask.js";
|
|
7
9
|
import { PathTracerProbeRenderer } from "../lpv/PathTracerProbeRenderer.js";
|
|
10
|
+
import { bake_probe_sh3_resampled } from "./bake_probe_sh3_resampled.js";
|
|
11
|
+
import { light_probe_grid_dilate } from "./light_probe_grid_dilate.js";
|
|
8
12
|
|
|
9
13
|
const __position = new Float32Array(3);
|
|
10
14
|
const __sh3_rgb = new Float64Array(27);
|
|
15
|
+
const __zh = new Float64Array(3);
|
|
11
16
|
|
|
12
17
|
export class LightProbeGridBaker {
|
|
13
18
|
|
|
14
19
|
#ren = new PathTracerProbeRenderer();
|
|
15
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Directions sampled one hit deep per probe. Direct light and sky converge with this count,
|
|
23
|
+
* every candidate also feeds the back-face visibility statistic.
|
|
24
|
+
*
|
|
25
|
+
* The defaults match the directional noise of the previous 4096-full-path bake at roughly half
|
|
26
|
+
* the cost (candidates are single-segment); raise both counts together for higher quality.
|
|
27
|
+
* @type {number}
|
|
28
|
+
*/
|
|
29
|
+
candidate_count = 2048;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Full multi-bounce path traces per probe, re-sampled from the candidates by first-bounce
|
|
33
|
+
* importance. Only the indirect part of the estimate rides on these, so far fewer are needed
|
|
34
|
+
* than candidates.
|
|
35
|
+
* @type {number}
|
|
36
|
+
*/
|
|
37
|
+
indirect_sample_count = 512;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Total path depth including the first hit
|
|
41
|
+
* @type {number}
|
|
42
|
+
*/
|
|
43
|
+
max_bounce_count = 5;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* GGX roughness of the prefilter cone applied to the baked radiance SH, in [0, 1].
|
|
47
|
+
* Attenuates band 1 before the 2-band truncation, suppressing directional overshoot that would
|
|
48
|
+
* otherwise reconstruct as negative irradiance (light/shadow bleeding) on the far side of a
|
|
49
|
+
* strong light. Higher values trade directionality for stability.
|
|
50
|
+
* @type {number}
|
|
51
|
+
*/
|
|
52
|
+
filter_ggx_alpha = 0.5;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* A probe whose fraction of candidate rays hitting geometry back-faces exceeds this threshold is
|
|
56
|
+
* considered to be inside geometry. Such probes are filled from their valid neighbors after the
|
|
57
|
+
* bake instead of contributing interior darkness to trilinear filtering.
|
|
58
|
+
* @type {number}
|
|
59
|
+
*/
|
|
60
|
+
interior_backface_threshold = 0.25;
|
|
61
|
+
|
|
16
62
|
/**
|
|
17
63
|
* Renderer used to bake probes. Exposed so callers (and tests) can configure the underlying scene.
|
|
18
|
-
* @return {
|
|
64
|
+
* @return {PathTracerProbeRenderer}
|
|
19
65
|
*/
|
|
20
66
|
get renderer() {
|
|
21
67
|
return this.#ren;
|
|
@@ -40,8 +86,10 @@ export class LightProbeGridBaker {
|
|
|
40
86
|
|
|
41
87
|
const probe_count = grid.count;
|
|
42
88
|
|
|
89
|
+
const validity = new Uint8Array(probe_count);
|
|
90
|
+
|
|
43
91
|
const tBakeIrradiance = countTask(0, probe_count, (probe_index) => {
|
|
44
|
-
this.bake_probe(renderer, grid, probe_index);
|
|
92
|
+
validity[probe_index] = this.bake_probe(renderer, grid, probe_index) ? 1 : 0;
|
|
45
93
|
|
|
46
94
|
});
|
|
47
95
|
tBakeIrradiance.estimatedDuration = probe_count;
|
|
@@ -57,6 +105,13 @@ export class LightProbeGridBaker {
|
|
|
57
105
|
const tFinish = actionTask(() => {
|
|
58
106
|
renderer.bake_end();
|
|
59
107
|
|
|
108
|
+
// probes inside geometry take on the average of their valid neighbors, so trilinear
|
|
109
|
+
// sampling near walls does not blend in interior darkness
|
|
110
|
+
light_probe_grid_dilate(
|
|
111
|
+
grid.harmonics, validity,
|
|
112
|
+
grid.resolution_x, grid.resolution_y, grid.resolution_z
|
|
113
|
+
);
|
|
114
|
+
|
|
60
115
|
grid.incrementVersion();
|
|
61
116
|
});
|
|
62
117
|
|
|
@@ -70,19 +125,31 @@ export class LightProbeGridBaker {
|
|
|
70
125
|
|
|
71
126
|
/**
|
|
72
127
|
*
|
|
73
|
-
* @param {
|
|
128
|
+
* @param {PathTracerProbeRenderer} renderer
|
|
74
129
|
* @param {LightProbeGrid} grid
|
|
75
130
|
* @param {number} probe_index
|
|
131
|
+
* @returns {boolean} true if the probe is valid, false if it appears to be inside geometry and
|
|
132
|
+
* should be filled from its neighbors
|
|
76
133
|
*/
|
|
77
134
|
bake_probe(renderer, grid, probe_index) {
|
|
78
135
|
assert.isNonNegativeInteger(probe_index, 'probe_index');
|
|
79
136
|
|
|
80
137
|
grid.probe_position(__position, 0, probe_index);
|
|
81
138
|
|
|
82
|
-
|
|
83
|
-
|
|
139
|
+
const backface_fraction = bake_probe_sh3_resampled(
|
|
140
|
+
__sh3_rgb, 0,
|
|
141
|
+
renderer.scene, renderer.tracer, renderer.random,
|
|
142
|
+
__position, 0,
|
|
143
|
+
this.candidate_count, this.indirect_sample_count, this.max_bounce_count
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// prefilter the radiance with a GGX cone before truncation, taming band-1 overshoot
|
|
147
|
+
sh3_ggx_zh(__zh, 0, this.filter_ggx_alpha);
|
|
148
|
+
sh3_convolve_with_zh(__sh3_rgb, 0, __sh3_rgb, 0, __zh[0], __zh[1], __zh[2], 3);
|
|
84
149
|
|
|
85
150
|
// keep only bands 0 and 1: the coefficient-major layout puts them in the first 12 values
|
|
86
151
|
array_copy(__sh3_rgb, 0, grid.harmonics, probe_index * 12, 12);
|
|
152
|
+
|
|
153
|
+
return backface_fraction <= this.interior_backface_threshold;
|
|
87
154
|
}
|
|
88
155
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bake a single SH3 radiance probe with importance re-sampling and back-face visibility rejection.
|
|
3
|
+
*
|
|
4
|
+
* Two-phase estimator, split by path length:
|
|
5
|
+
*
|
|
6
|
+
* 1. Candidate phase: `candidate_count` uniform sphere directions are traced one hit deep. Misses
|
|
7
|
+
* take the background radiance; front-face hits take `emissive + albedo * direct_light`, both of
|
|
8
|
+
* which are cheap and low-noise, and every such sample is projected onto the SH. Directions whose
|
|
9
|
+
* first hit is a back face see the inside of geometry — they are rejected and the estimate is
|
|
10
|
+
* renormalized over the remaining directions, so interior darkness does not bleed into the probe.
|
|
11
|
+
*
|
|
12
|
+
* 2. Indirect phase: `indirect_sample_count` continuations are picked from the hit candidates with
|
|
13
|
+
* probability proportional to their first-bounce luminance (resampled importance sampling — the
|
|
14
|
+
* selection a weighted reservoir would produce, drawn from the materialized candidate table), and
|
|
15
|
+
* only these few directions pay for a full multi-bounce path trace. The 1/p_hat re-sampling weight
|
|
16
|
+
* keeps the combined estimator unbiased.
|
|
17
|
+
*
|
|
18
|
+
* The two phases sum to the same integral as tracing every direction to full depth, but direct light
|
|
19
|
+
* converges with the full candidate count while the expensive multi-bounce work is spent where the
|
|
20
|
+
* first bounce found energy.
|
|
21
|
+
*
|
|
22
|
+
* @param {number[]|Float32Array|Float64Array} out 27 values: SH3 radiance, coefficient-major RGB triplets
|
|
23
|
+
* @param {number} out_offset
|
|
24
|
+
* @param {PathTracedScene} scene
|
|
25
|
+
* @param {PathTracer} tracer
|
|
26
|
+
* @param {function():number} random
|
|
27
|
+
* @param {number[]|Float32Array} position probe position
|
|
28
|
+
* @param {number} position_offset
|
|
29
|
+
* @param {number} candidate_count directions sampled one hit deep, at least 1
|
|
30
|
+
* @param {number} indirect_sample_count full path-traced continuations re-sampled from the candidates
|
|
31
|
+
* @param {number} max_bounce_count total path depth including the first hit, at least 1
|
|
32
|
+
* @returns {number} fraction of candidate directions whose first hit was a back face, in [0, 1]
|
|
33
|
+
*/
|
|
34
|
+
export function bake_probe_sh3_resampled(out: number[] | Float32Array | Float64Array, out_offset: number, scene: PathTracedScene, tracer: PathTracer, random: () => number, position: number[] | Float32Array, position_offset: number, candidate_count: number, indirect_sample_count: number, max_bounce_count: number): number;
|
|
35
|
+
import { PathTracedScene } from "../path_tracer/PathTracedScene.js";
|
|
36
|
+
import { PathTracer } from "../path_tracer/PathTracer.js";
|
|
37
|
+
//# sourceMappingURL=bake_probe_sh3_resampled.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bake_probe_sh3_resampled.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/sh3/lpg/bake_probe_sh3_resampled.js"],"names":[],"mappings":"AAyEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,8CAZW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,cAClC,MAAM,SACN,eAAe,UACf,UAAU,gBACC,MAAM,YACjB,MAAM,EAAE,GAAC,YAAY,mBACrB,MAAM,mBACN,MAAM,yBACN,MAAM,oBACN,MAAM,GACJ,MAAM,CAiMlB;gCAlS+B,mCAAmC;2BACjB,8BAA8B"}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { assert } from "../../../../core/assert.js";
|
|
2
|
+
import { rgb_to_luminance } from "../../../../core/color/rgb_to_luminance.js";
|
|
3
|
+
import { Ray3 } from "../../../../core/geom/3d/ray/Ray3.js";
|
|
4
|
+
import { sh3_basis_at } from "../../../../core/geom/3d/sphere/harmonics/sh3_basis_at.js";
|
|
5
|
+
import { sh3_vector_accumulate } from "../../../../core/geom/3d/sphere/harmonics/sh3_vector_accumulate.js";
|
|
6
|
+
import { random_point_on_sphere } from "../../../../core/geom/random/random_point_on_sphere.js";
|
|
7
|
+
import { v3_dot } from "../../../../core/geom/vec3/v3_dot.js";
|
|
8
|
+
import { PathTracedScene } from "../path_tracer/PathTracedScene.js";
|
|
9
|
+
import { PathTracer, SELF_OCCLUSION_OFFSET } from "../path_tracer/PathTracer.js";
|
|
10
|
+
import { getBiasedNormalSample } from "../path_tracer/sampling/getBiasedNormalSample.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Per-candidate record: direction(3), offset surface position(3), shading normal(3), albedo(3)
|
|
14
|
+
* @type {number}
|
|
15
|
+
*/
|
|
16
|
+
const CANDIDATE_STRIDE = 12;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fraction of the mean candidate luminance added to every re-sampling weight. Keeps surfaces that
|
|
20
|
+
* receive no direct light selectable — they may still return indirect radiance — so the estimator
|
|
21
|
+
* stays unbiased.
|
|
22
|
+
* @type {number}
|
|
23
|
+
*/
|
|
24
|
+
const RESAMPLING_WEIGHT_FLOOR = 0.1;
|
|
25
|
+
|
|
26
|
+
const __ray = new Ray3();
|
|
27
|
+
const __hit = [];
|
|
28
|
+
const __surface = new Float32Array(9);
|
|
29
|
+
const __direct = new Float32Array(3);
|
|
30
|
+
const __radiance = new Float32Array(3);
|
|
31
|
+
const __basis = new Float64Array(9);
|
|
32
|
+
const __sh_direct = new Float64Array(27);
|
|
33
|
+
const __sh_indirect = new Float64Array(27);
|
|
34
|
+
|
|
35
|
+
let __candidates = new Float32Array(0);
|
|
36
|
+
let __p_hat = new Float64Array(0);
|
|
37
|
+
let __cumulative_p_hat = new Float64Array(0);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
* @param {number} candidate_count
|
|
42
|
+
*/
|
|
43
|
+
function ensure_pool_capacity(candidate_count) {
|
|
44
|
+
if (__p_hat.length < candidate_count) {
|
|
45
|
+
__candidates = new Float32Array(candidate_count * CANDIDATE_STRIDE);
|
|
46
|
+
__p_hat = new Float64Array(candidate_count);
|
|
47
|
+
__cumulative_p_hat = new Float64Array(candidate_count);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Index of the candidate owning the cumulative-weight interval that contains `value`
|
|
53
|
+
* @param {number} value in [0, cumulative[count - 1])
|
|
54
|
+
* @param {number} count
|
|
55
|
+
* @returns {number}
|
|
56
|
+
*/
|
|
57
|
+
function pick_candidate(value, count) {
|
|
58
|
+
let low = 0;
|
|
59
|
+
let high = count - 1;
|
|
60
|
+
|
|
61
|
+
while (low < high) {
|
|
62
|
+
const mid = (low + high) >> 1;
|
|
63
|
+
|
|
64
|
+
if (__cumulative_p_hat[mid] > value) {
|
|
65
|
+
high = mid;
|
|
66
|
+
} else {
|
|
67
|
+
low = mid + 1;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return low;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Bake a single SH3 radiance probe with importance re-sampling and back-face visibility rejection.
|
|
76
|
+
*
|
|
77
|
+
* Two-phase estimator, split by path length:
|
|
78
|
+
*
|
|
79
|
+
* 1. Candidate phase: `candidate_count` uniform sphere directions are traced one hit deep. Misses
|
|
80
|
+
* take the background radiance; front-face hits take `emissive + albedo * direct_light`, both of
|
|
81
|
+
* which are cheap and low-noise, and every such sample is projected onto the SH. Directions whose
|
|
82
|
+
* first hit is a back face see the inside of geometry — they are rejected and the estimate is
|
|
83
|
+
* renormalized over the remaining directions, so interior darkness does not bleed into the probe.
|
|
84
|
+
*
|
|
85
|
+
* 2. Indirect phase: `indirect_sample_count` continuations are picked from the hit candidates with
|
|
86
|
+
* probability proportional to their first-bounce luminance (resampled importance sampling — the
|
|
87
|
+
* selection a weighted reservoir would produce, drawn from the materialized candidate table), and
|
|
88
|
+
* only these few directions pay for a full multi-bounce path trace. The 1/p_hat re-sampling weight
|
|
89
|
+
* keeps the combined estimator unbiased.
|
|
90
|
+
*
|
|
91
|
+
* The two phases sum to the same integral as tracing every direction to full depth, but direct light
|
|
92
|
+
* converges with the full candidate count while the expensive multi-bounce work is spent where the
|
|
93
|
+
* first bounce found energy.
|
|
94
|
+
*
|
|
95
|
+
* @param {number[]|Float32Array|Float64Array} out 27 values: SH3 radiance, coefficient-major RGB triplets
|
|
96
|
+
* @param {number} out_offset
|
|
97
|
+
* @param {PathTracedScene} scene
|
|
98
|
+
* @param {PathTracer} tracer
|
|
99
|
+
* @param {function():number} random
|
|
100
|
+
* @param {number[]|Float32Array} position probe position
|
|
101
|
+
* @param {number} position_offset
|
|
102
|
+
* @param {number} candidate_count directions sampled one hit deep, at least 1
|
|
103
|
+
* @param {number} indirect_sample_count full path-traced continuations re-sampled from the candidates
|
|
104
|
+
* @param {number} max_bounce_count total path depth including the first hit, at least 1
|
|
105
|
+
* @returns {number} fraction of candidate directions whose first hit was a back face, in [0, 1]
|
|
106
|
+
*/
|
|
107
|
+
export function bake_probe_sh3_resampled(
|
|
108
|
+
out, out_offset,
|
|
109
|
+
scene, tracer, random,
|
|
110
|
+
position, position_offset,
|
|
111
|
+
candidate_count, indirect_sample_count, max_bounce_count
|
|
112
|
+
) {
|
|
113
|
+
assert.isArrayLike(out, 'out');
|
|
114
|
+
assert.isNonNegativeInteger(out_offset, 'out_offset');
|
|
115
|
+
assert.isInstanceOf(scene, PathTracedScene, 'scene');
|
|
116
|
+
assert.isInstanceOf(tracer, PathTracer, 'tracer');
|
|
117
|
+
assert.isFunction(random, 'random');
|
|
118
|
+
assert.isArrayLike(position, 'position');
|
|
119
|
+
assert.isNonNegativeInteger(position_offset, 'position_offset');
|
|
120
|
+
assert.isNonNegativeInteger(candidate_count, 'candidate_count');
|
|
121
|
+
assert.greaterThan(candidate_count, 0, 'candidate_count');
|
|
122
|
+
assert.isNonNegativeInteger(indirect_sample_count, 'indirect_sample_count');
|
|
123
|
+
assert.isNonNegativeInteger(max_bounce_count, 'max_bounce_count');
|
|
124
|
+
assert.greaterThan(max_bounce_count, 0, 'max_bounce_count');
|
|
125
|
+
|
|
126
|
+
ensure_pool_capacity(candidate_count);
|
|
127
|
+
|
|
128
|
+
__sh_direct.fill(0);
|
|
129
|
+
__sh_indirect.fill(0);
|
|
130
|
+
|
|
131
|
+
const position_x = position[position_offset];
|
|
132
|
+
const position_y = position[position_offset + 1];
|
|
133
|
+
const position_z = position[position_offset + 2];
|
|
134
|
+
|
|
135
|
+
let hit_count = 0;
|
|
136
|
+
let valid_count = 0;
|
|
137
|
+
let backface_count = 0;
|
|
138
|
+
let luminance_sum = 0;
|
|
139
|
+
|
|
140
|
+
for (let i = 0; i < candidate_count; i++) {
|
|
141
|
+
|
|
142
|
+
__ray[0] = position_x;
|
|
143
|
+
__ray[1] = position_y;
|
|
144
|
+
__ray[2] = position_z;
|
|
145
|
+
|
|
146
|
+
random_point_on_sphere(random, __ray, 3);
|
|
147
|
+
|
|
148
|
+
__ray.tMax = Infinity;
|
|
149
|
+
|
|
150
|
+
const direction_x = __ray[3];
|
|
151
|
+
const direction_y = __ray[4];
|
|
152
|
+
const direction_z = __ray[5];
|
|
153
|
+
|
|
154
|
+
const hit_distance = scene.trace(__hit, __ray);
|
|
155
|
+
|
|
156
|
+
if (hit_distance < 0) {
|
|
157
|
+
// sky
|
|
158
|
+
scene.sample_background(__radiance, 0, __ray, 3);
|
|
159
|
+
} else if (v3_dot(
|
|
160
|
+
__hit[3], __hit[4], __hit[5],
|
|
161
|
+
direction_x, direction_y, direction_z
|
|
162
|
+
) > 0) {
|
|
163
|
+
// the first hit is a back face: this direction sees the inside of geometry
|
|
164
|
+
backface_count++;
|
|
165
|
+
continue;
|
|
166
|
+
} else {
|
|
167
|
+
scene.sample_material(__surface, __hit, __ray);
|
|
168
|
+
|
|
169
|
+
// stage the surface state in the ray: origin at the hit, direction holding the shading
|
|
170
|
+
// normal, then step off the surface along the normal — mirrors PathTracer exactly, so
|
|
171
|
+
// shadow rays and continuations start from the same offset point the tracer would use
|
|
172
|
+
__ray[0] = __hit[0];
|
|
173
|
+
__ray[1] = __hit[1];
|
|
174
|
+
__ray[2] = __hit[2];
|
|
175
|
+
|
|
176
|
+
__ray[3] = __surface[3];
|
|
177
|
+
__ray[4] = __surface[4];
|
|
178
|
+
__ray[5] = __surface[5];
|
|
179
|
+
|
|
180
|
+
const origin_magnitude = Math.max(Math.abs(__ray[0]), Math.abs(__ray[1]), Math.abs(__ray[2]));
|
|
181
|
+
__ray.shiftForward(SELF_OCCLUSION_OFFSET * Math.max(1, origin_magnitude));
|
|
182
|
+
|
|
183
|
+
scene.sample_lights(__direct, 0, __ray);
|
|
184
|
+
|
|
185
|
+
// first-bounce radiance: emission plus albedo-modulated direct light
|
|
186
|
+
__radiance[0] = __surface[6] + __surface[0] * __direct[0];
|
|
187
|
+
__radiance[1] = __surface[7] + __surface[1] * __direct[1];
|
|
188
|
+
__radiance[2] = __surface[8] + __surface[2] * __direct[2];
|
|
189
|
+
|
|
190
|
+
// record the candidate for indirect re-sampling
|
|
191
|
+
const candidate_address = hit_count * CANDIDATE_STRIDE;
|
|
192
|
+
|
|
193
|
+
__candidates[candidate_address] = direction_x;
|
|
194
|
+
__candidates[candidate_address + 1] = direction_y;
|
|
195
|
+
__candidates[candidate_address + 2] = direction_z;
|
|
196
|
+
|
|
197
|
+
__candidates[candidate_address + 3] = __ray[0];
|
|
198
|
+
__candidates[candidate_address + 4] = __ray[1];
|
|
199
|
+
__candidates[candidate_address + 5] = __ray[2];
|
|
200
|
+
|
|
201
|
+
__candidates[candidate_address + 6] = __surface[3];
|
|
202
|
+
__candidates[candidate_address + 7] = __surface[4];
|
|
203
|
+
__candidates[candidate_address + 8] = __surface[5];
|
|
204
|
+
|
|
205
|
+
__candidates[candidate_address + 9] = __surface[0];
|
|
206
|
+
__candidates[candidate_address + 10] = __surface[1];
|
|
207
|
+
__candidates[candidate_address + 11] = __surface[2];
|
|
208
|
+
|
|
209
|
+
const luminance = rgb_to_luminance(__radiance[0], __radiance[1], __radiance[2]);
|
|
210
|
+
|
|
211
|
+
__p_hat[hit_count] = luminance;
|
|
212
|
+
luminance_sum += luminance;
|
|
213
|
+
|
|
214
|
+
hit_count++;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
valid_count++;
|
|
218
|
+
|
|
219
|
+
sh3_basis_at(direction_x, direction_y, direction_z, __basis);
|
|
220
|
+
sh3_vector_accumulate(__sh_direct, 0, __basis, 0, __radiance, 0, 3);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const backface_fraction = backface_count / candidate_count;
|
|
224
|
+
|
|
225
|
+
if (valid_count === 0) {
|
|
226
|
+
// probe is fully embedded in geometry, there is no usable radiance
|
|
227
|
+
for (let i = 0; i < 27; i++) {
|
|
228
|
+
out[out_offset + i] = 0;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return backface_fraction;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let total_p_hat = 0;
|
|
235
|
+
|
|
236
|
+
if (max_bounce_count > 1 && indirect_sample_count > 0 && hit_count > 0) {
|
|
237
|
+
|
|
238
|
+
// floor the weights so indirectly-lit surfaces stay selectable; when nothing received any
|
|
239
|
+
// direct light the selection degenerates to uniform (selection is scale-invariant in p_hat)
|
|
240
|
+
const mean_luminance = luminance_sum / hit_count;
|
|
241
|
+
const weight_floor = mean_luminance > 0 ? mean_luminance * RESAMPLING_WEIGHT_FLOOR : 1;
|
|
242
|
+
|
|
243
|
+
for (let i = 0; i < hit_count; i++) {
|
|
244
|
+
const p_hat = __p_hat[i] + weight_floor;
|
|
245
|
+
|
|
246
|
+
__p_hat[i] = p_hat;
|
|
247
|
+
total_p_hat += p_hat;
|
|
248
|
+
|
|
249
|
+
__cumulative_p_hat[i] = total_p_hat;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
for (let k = 0; k < indirect_sample_count; k++) {
|
|
253
|
+
const pick = pick_candidate(random() * total_p_hat, hit_count);
|
|
254
|
+
|
|
255
|
+
const candidate_address = pick * CANDIDATE_STRIDE;
|
|
256
|
+
|
|
257
|
+
__ray[0] = __candidates[candidate_address + 3];
|
|
258
|
+
__ray[1] = __candidates[candidate_address + 4];
|
|
259
|
+
__ray[2] = __candidates[candidate_address + 5];
|
|
260
|
+
|
|
261
|
+
// cosine-weighted continuation about the shading normal, matching the tracer's own
|
|
262
|
+
// bounce sampling; the albedo/pi BRDF and the cosine/pi pdf cancel to a bare albedo scale
|
|
263
|
+
getBiasedNormalSample(__ray, 3, __candidates, candidate_address + 6, 1, random);
|
|
264
|
+
|
|
265
|
+
__ray.tMax = Infinity;
|
|
266
|
+
|
|
267
|
+
tracer.path_trace(__radiance, __ray, 1, max_bounce_count - 1, random, scene);
|
|
268
|
+
|
|
269
|
+
const resampling_weight = 1 / __p_hat[pick];
|
|
270
|
+
|
|
271
|
+
__radiance[0] *= __candidates[candidate_address + 9] * resampling_weight;
|
|
272
|
+
__radiance[1] *= __candidates[candidate_address + 10] * resampling_weight;
|
|
273
|
+
__radiance[2] *= __candidates[candidate_address + 11] * resampling_weight;
|
|
274
|
+
|
|
275
|
+
sh3_basis_at(
|
|
276
|
+
__candidates[candidate_address],
|
|
277
|
+
__candidates[candidate_address + 1],
|
|
278
|
+
__candidates[candidate_address + 2],
|
|
279
|
+
__basis
|
|
280
|
+
);
|
|
281
|
+
sh3_vector_accumulate(__sh_indirect, 0, __basis, 0, __radiance, 0, 3);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// back-face directions are excluded from the domain: normalizing by the valid count spreads the
|
|
286
|
+
// radiance of the visible directions over the full sphere, so partially embedded probes represent
|
|
287
|
+
// the open side instead of averaging in interior darkness
|
|
288
|
+
const direct_scale = (4 * Math.PI) / valid_count;
|
|
289
|
+
const indirect_scale = indirect_sample_count > 0
|
|
290
|
+
? direct_scale * (total_p_hat / indirect_sample_count)
|
|
291
|
+
: 0;
|
|
292
|
+
|
|
293
|
+
for (let i = 0; i < 27; i++) {
|
|
294
|
+
out[out_offset + i] = __sh_direct[i] * direct_scale + __sh_indirect[i] * indirect_scale;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return backface_fraction;
|
|
298
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fill invalid probes with the average of their valid 6-connected neighbors, expanding in waves until
|
|
3
|
+
* every reachable probe is filled.
|
|
4
|
+
*
|
|
5
|
+
* Probes marked invalid (typically because they sit inside geometry) would otherwise contribute their
|
|
6
|
+
* meaningless data to hardware trilinear filtering and bleed darkness across walls. Each pass reads
|
|
7
|
+
* only probes that were valid before the pass started, so the result is independent of traversal
|
|
8
|
+
* order. Probes with no valid probe anywhere in their connected region are left untouched.
|
|
9
|
+
*
|
|
10
|
+
* @param {Float32Array} harmonics probe data, {@link PROBE_VALUE_COUNT} values per probe; filled probes are written in place
|
|
11
|
+
* @param {Uint8Array} validity one flag per probe, non-zero = valid; filled probes are marked valid in place
|
|
12
|
+
* @param {number} resolution_x
|
|
13
|
+
* @param {number} resolution_y
|
|
14
|
+
* @param {number} resolution_z
|
|
15
|
+
* @returns {number} number of probes that remained invalid
|
|
16
|
+
*/
|
|
17
|
+
export function light_probe_grid_dilate(harmonics: Float32Array, validity: Uint8Array, resolution_x: number, resolution_y: number, resolution_z: number): number;
|
|
18
|
+
//# sourceMappingURL=light_probe_grid_dilate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"light_probe_grid_dilate.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/sh3/lpg/light_probe_grid_dilate.js"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;;;GAeG;AACH,mDAPW,YAAY,YACZ,UAAU,gBACV,MAAM,gBACN,MAAM,gBACN,MAAM,GACJ,MAAM,CAsGlB"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { assert } from "../../../../core/assert.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Values per probe in {@link LightProbeGrid#harmonics}
|
|
5
|
+
* @type {number}
|
|
6
|
+
*/
|
|
7
|
+
const PROBE_VALUE_COUNT = 12;
|
|
8
|
+
|
|
9
|
+
const __accumulator = new Float64Array(PROBE_VALUE_COUNT);
|
|
10
|
+
|
|
11
|
+
let __source_validity = new Uint8Array(0);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Fill invalid probes with the average of their valid 6-connected neighbors, expanding in waves until
|
|
15
|
+
* every reachable probe is filled.
|
|
16
|
+
*
|
|
17
|
+
* Probes marked invalid (typically because they sit inside geometry) would otherwise contribute their
|
|
18
|
+
* meaningless data to hardware trilinear filtering and bleed darkness across walls. Each pass reads
|
|
19
|
+
* only probes that were valid before the pass started, so the result is independent of traversal
|
|
20
|
+
* order. Probes with no valid probe anywhere in their connected region are left untouched.
|
|
21
|
+
*
|
|
22
|
+
* @param {Float32Array} harmonics probe data, {@link PROBE_VALUE_COUNT} values per probe; filled probes are written in place
|
|
23
|
+
* @param {Uint8Array} validity one flag per probe, non-zero = valid; filled probes are marked valid in place
|
|
24
|
+
* @param {number} resolution_x
|
|
25
|
+
* @param {number} resolution_y
|
|
26
|
+
* @param {number} resolution_z
|
|
27
|
+
* @returns {number} number of probes that remained invalid
|
|
28
|
+
*/
|
|
29
|
+
export function light_probe_grid_dilate(
|
|
30
|
+
harmonics,
|
|
31
|
+
validity,
|
|
32
|
+
resolution_x, resolution_y, resolution_z
|
|
33
|
+
) {
|
|
34
|
+
assert.isNonNegativeInteger(resolution_x, 'resolution_x');
|
|
35
|
+
assert.isNonNegativeInteger(resolution_y, 'resolution_y');
|
|
36
|
+
assert.isNonNegativeInteger(resolution_z, 'resolution_z');
|
|
37
|
+
|
|
38
|
+
const probe_count = resolution_x * resolution_y * resolution_z;
|
|
39
|
+
|
|
40
|
+
assert.greaterThanOrEqual(validity.length, probe_count, 'validity.length');
|
|
41
|
+
assert.greaterThanOrEqual(harmonics.length, probe_count * PROBE_VALUE_COUNT, 'harmonics.length');
|
|
42
|
+
|
|
43
|
+
if (__source_validity.length < probe_count) {
|
|
44
|
+
__source_validity = new Uint8Array(probe_count);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let invalid_count = 0;
|
|
48
|
+
|
|
49
|
+
for (let i = 0; i < probe_count; i++) {
|
|
50
|
+
if (validity[i] === 0) {
|
|
51
|
+
invalid_count++;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const slice_size = resolution_x * resolution_y;
|
|
56
|
+
|
|
57
|
+
let filled_this_pass = 1;
|
|
58
|
+
|
|
59
|
+
while (invalid_count > 0 && filled_this_pass > 0) {
|
|
60
|
+
filled_this_pass = 0;
|
|
61
|
+
|
|
62
|
+
// snapshot so each pass only reads probes that were valid before it started
|
|
63
|
+
__source_validity.set(validity.subarray(0, probe_count));
|
|
64
|
+
|
|
65
|
+
for (let z = 0; z < resolution_z; z++) {
|
|
66
|
+
for (let y = 0; y < resolution_y; y++) {
|
|
67
|
+
for (let x = 0; x < resolution_x; x++) {
|
|
68
|
+
|
|
69
|
+
const index = x + resolution_x * y + slice_size * z;
|
|
70
|
+
|
|
71
|
+
if (__source_validity[index] !== 0) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
__accumulator.fill(0);
|
|
76
|
+
let neighbor_count = 0;
|
|
77
|
+
|
|
78
|
+
if (x > 0 && __source_validity[index - 1] !== 0) {
|
|
79
|
+
accumulate_probe(harmonics, index - 1);
|
|
80
|
+
neighbor_count++;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (x < resolution_x - 1 && __source_validity[index + 1] !== 0) {
|
|
84
|
+
accumulate_probe(harmonics, index + 1);
|
|
85
|
+
neighbor_count++;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (y > 0 && __source_validity[index - resolution_x] !== 0) {
|
|
89
|
+
accumulate_probe(harmonics, index - resolution_x);
|
|
90
|
+
neighbor_count++;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (y < resolution_y - 1 && __source_validity[index + resolution_x] !== 0) {
|
|
94
|
+
accumulate_probe(harmonics, index + resolution_x);
|
|
95
|
+
neighbor_count++;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (z > 0 && __source_validity[index - slice_size] !== 0) {
|
|
99
|
+
accumulate_probe(harmonics, index - slice_size);
|
|
100
|
+
neighbor_count++;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (z < resolution_z - 1 && __source_validity[index + slice_size] !== 0) {
|
|
104
|
+
accumulate_probe(harmonics, index + slice_size);
|
|
105
|
+
neighbor_count++;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (neighbor_count === 0) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const probe_address = index * PROBE_VALUE_COUNT;
|
|
113
|
+
const inverse_count = 1 / neighbor_count;
|
|
114
|
+
|
|
115
|
+
for (let i = 0; i < PROBE_VALUE_COUNT; i++) {
|
|
116
|
+
harmonics[probe_address + i] = __accumulator[i] * inverse_count;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
validity[index] = 1;
|
|
120
|
+
filled_this_pass++;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
invalid_count -= filled_this_pass;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return invalid_count;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
*
|
|
133
|
+
* @param {Float32Array} harmonics
|
|
134
|
+
* @param {number} probe_index
|
|
135
|
+
*/
|
|
136
|
+
function accumulate_probe(harmonics, probe_index) {
|
|
137
|
+
const address = probe_index * PROBE_VALUE_COUNT;
|
|
138
|
+
|
|
139
|
+
for (let i = 0; i < PROBE_VALUE_COUNT; i++) {
|
|
140
|
+
__accumulator[i] += harmonics[address + i];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PathTracer.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/sh3/path_tracer/PathTracer.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PathTracer.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/sh3/path_tracer/PathTracer.js"],"names":[],"mappings":"AAsCA;;;;GAIG;AACH,oCAFU,MAAM,CAE6B;AAI7C;IAGI;;;;;;;;OAQG;IACH,gBAPW,MAAM,EAAE,GAAC,YAAY,OACrB,MAAM,EAAE,GAAC,YAAY,GAAC,IAAI,cAC1B,MAAM,cACN,MAAM,kDAuFhB;CACJ;qBA7IoB,sCAAsC"}
|
|
@@ -41,7 +41,7 @@ const RUSSIAN_ROULETTE_SURVIVAL_RECIPROCAL = 1 / (1 - RUSSIAN_ROULETTE_PROBABILI
|
|
|
41
41
|
* at large world scales
|
|
42
42
|
* @type {number}
|
|
43
43
|
*/
|
|
44
|
-
const SELF_OCCLUSION_OFFSET = 0.00001;
|
|
44
|
+
export const SELF_OCCLUSION_OFFSET = 0.00001;
|
|
45
45
|
|
|
46
46
|
const color_mask = new Float32Array(3);
|
|
47
47
|
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Fixed-function simulation for {@link TubeX} tubes — the 3D analogue of
|
|
3
|
-
* {@link RibbonXFixedPhysicsSimulator}. Ages every knot, fades its alpha
|
|
4
|
-
* as it approaches `max_age`, and flows the along-tube UV
|
|
3
|
+
* {@link RibbonXFixedPhysicsSimulator}. Ages every knot, fades its alpha from
|
|
4
|
+
* `alpha` to zero as it approaches `max_age`, and flows the along-tube UV
|
|
5
|
+
* coordinate with age.
|
|
5
6
|
*/
|
|
6
7
|
export class TubeXFixedPhysicsSimulator {
|
|
7
8
|
/**
|
|
8
9
|
* @param {TubeX} tube
|
|
9
10
|
* @param {number} max_age
|
|
10
11
|
* @param {number} timeDelta
|
|
12
|
+
* @param {number} [alpha] base alpha of an age-0 knot; the age fade scales it
|
|
11
13
|
*/
|
|
12
|
-
update(tube: TubeX, max_age: number, timeDelta: number): void;
|
|
14
|
+
update(tube: TubeX, max_age: number, timeDelta: number, alpha?: number): void;
|
|
13
15
|
}
|
|
14
16
|
//# sourceMappingURL=TubeXFixedPhysicsSimulator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TubeXFixedPhysicsSimulator.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.js"],"names":[],"mappings":"AAGA
|
|
1
|
+
{"version":3,"file":"TubeXFixedPhysicsSimulator.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH;IAEI;;;;;OAKG;IACH,6BAJW,MAAM,aACN,MAAM,UACN,MAAM,QAiBhB;CACJ"}
|
|
@@ -3,8 +3,9 @@ import { TUBE_ATTRIBUTE_ADDRESS_AGE, TUBE_ATTRIBUTE_ADDRESS_UV_V } from "../tube
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Fixed-function simulation for {@link TubeX} tubes — the 3D analogue of
|
|
6
|
-
* {@link RibbonXFixedPhysicsSimulator}. Ages every knot, fades its alpha
|
|
7
|
-
* as it approaches `max_age`, and flows the along-tube UV
|
|
6
|
+
* {@link RibbonXFixedPhysicsSimulator}. Ages every knot, fades its alpha from
|
|
7
|
+
* `alpha` to zero as it approaches `max_age`, and flows the along-tube UV
|
|
8
|
+
* coordinate with age.
|
|
8
9
|
*/
|
|
9
10
|
export class TubeXFixedPhysicsSimulator {
|
|
10
11
|
|
|
@@ -12,8 +13,9 @@ export class TubeXFixedPhysicsSimulator {
|
|
|
12
13
|
* @param {TubeX} tube
|
|
13
14
|
* @param {number} max_age
|
|
14
15
|
* @param {number} timeDelta
|
|
16
|
+
* @param {number} [alpha] base alpha of an age-0 knot; the age fade scales it
|
|
15
17
|
*/
|
|
16
|
-
update(tube, max_age, timeDelta) {
|
|
18
|
+
update(tube, max_age, timeDelta, alpha = 1) {
|
|
17
19
|
|
|
18
20
|
const n = tube.getCount();
|
|
19
21
|
|
|
@@ -23,7 +25,7 @@ export class TubeXFixedPhysicsSimulator {
|
|
|
23
25
|
const relative_age = clamp01(age / max_age);
|
|
24
26
|
|
|
25
27
|
// fade out with age
|
|
26
|
-
tube.setKnotAlpha(i, 1.0 - relative_age);
|
|
28
|
+
tube.setKnotAlpha(i, (1.0 - relative_age) * alpha);
|
|
27
29
|
|
|
28
30
|
// flow the along-tube UV with age
|
|
29
31
|
tube.setKnotAttribute_Scalar(i, TUBE_ATTRIBUTE_ADDRESS_UV_V, relative_age);
|
|
@@ -7,7 +7,8 @@ export class RibbonXFixedPhysicsSimulator {
|
|
|
7
7
|
* @param {RibbonX} ribbon
|
|
8
8
|
* @param {number} max_age
|
|
9
9
|
* @param {number} timeDelta
|
|
10
|
+
* @param {number} [alpha] base alpha of an age-0 point; the age fade scales it
|
|
10
11
|
*/
|
|
11
|
-
update(ribbon: RibbonX, max_age: number, timeDelta: number): void;
|
|
12
|
+
update(ribbon: RibbonX, max_age: number, timeDelta: number, alpha?: number): void;
|
|
12
13
|
}
|
|
13
14
|
//# sourceMappingURL=RibbonXFixedPhysicsSimulator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RibbonXFixedPhysicsSimulator.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/graphics/trail/x/simulator/RibbonXFixedPhysicsSimulator.js"],"names":[],"mappings":"AAIA;;GAEG;AACH;IAEI
|
|
1
|
+
{"version":3,"file":"RibbonXFixedPhysicsSimulator.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/graphics/trail/x/simulator/RibbonXFixedPhysicsSimulator.js"],"names":[],"mappings":"AAIA;;GAEG;AACH;IAEI;;;;;;OAMG;IACH,iCAJW,MAAM,aACN,MAAM,UACN,MAAM,QAkBhB;CACJ"}
|
|
@@ -12,8 +12,9 @@ export class RibbonXFixedPhysicsSimulator {
|
|
|
12
12
|
* @param {RibbonX} ribbon
|
|
13
13
|
* @param {number} max_age
|
|
14
14
|
* @param {number} timeDelta
|
|
15
|
+
* @param {number} [alpha] base alpha of an age-0 point; the age fade scales it
|
|
15
16
|
*/
|
|
16
|
-
update(ribbon, max_age, timeDelta) {
|
|
17
|
+
update(ribbon, max_age, timeDelta, alpha = 1) {
|
|
17
18
|
|
|
18
19
|
const n = ribbon.getCount();
|
|
19
20
|
for (let i = 0; i < n; i++) {
|
|
@@ -23,7 +24,7 @@ export class RibbonXFixedPhysicsSimulator {
|
|
|
23
24
|
const relative_age = clamp01(age / max_age);
|
|
24
25
|
|
|
25
26
|
// apply alpha over-time
|
|
26
|
-
ribbon.setPointAlpha(i, 1.0 - relative_age);
|
|
27
|
+
ribbon.setPointAlpha(i, (1.0 - relative_age) * alpha);
|
|
27
28
|
|
|
28
29
|
ribbon.setPointAttribute_Scalar(i, RIBBON_ATTRIBUTE_ADDRESS_UV_OFFSET, relative_age);
|
|
29
30
|
}
|