@woosh/meep-engine 2.130.0 → 2.131.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/package.json +1 -1
- package/src/core/color/illuminant/D65_spd_analytical.d.ts +12 -0
- package/src/core/color/illuminant/D65_spd_analytical.d.ts.map +1 -0
- package/src/core/color/illuminant/D65_spd_analytical.js +70 -0
- package/src/core/color/illuminant/D65_spd_tabulated.d.ts +13 -0
- package/src/core/color/illuminant/D65_spd_tabulated.d.ts.map +1 -0
- package/src/core/color/illuminant/D65_spd_tabulated.js +105 -0
- package/src/core/color/sRGB/sRGB_cmf.d.ts +11 -0
- package/src/core/color/sRGB/sRGB_cmf.d.ts.map +1 -0
- package/src/core/color/sRGB/sRGB_cmf.js +22 -0
- package/src/core/color/xyz/xyz_cmf_tabulated.d.ts +22 -0
- package/src/core/color/xyz/xyz_cmf_tabulated.d.ts.map +1 -0
- package/src/core/color/xyz/xyz_cmf_tabulated.js +212 -0
- package/src/core/color/xyz/xyz_cmf_wyman.d.ts +13 -0
- package/src/core/color/xyz/xyz_cmf_wyman.d.ts.map +1 -0
- package/src/core/color/xyz/xyz_cmf_wyman.js +65 -0
- package/src/core/color/xyz/xyz_to_rgb.d.ts +3 -3
- package/src/core/color/xyz/xyz_to_rgb.d.ts.map +1 -1
- package/src/core/color/xyz/xyz_to_rgb.js +2 -2
- package/src/core/math/complex/complex_add.d.ts +8 -0
- package/src/core/math/complex/complex_add.d.ts.map +1 -0
- package/src/core/math/complex/complex_add.js +11 -0
- package/src/core/math/complex/complex_div.d.ts +8 -0
- package/src/core/math/complex/complex_div.d.ts.map +1 -0
- package/src/core/math/complex/complex_div.js +12 -0
- package/src/core/math/complex/complex_mul.d.ts +8 -0
- package/src/core/math/complex/complex_mul.d.ts.map +1 -0
- package/src/core/math/complex/complex_mul.js +10 -0
- package/src/core/math/complex/complex_sub.d.ts +8 -0
- package/src/core/math/complex/complex_sub.d.ts.map +1 -0
- package/src/core/math/complex/complex_sub.js +10 -0
- package/src/core/math/physics/mie/MIE_PARTICLES_STANDARD.d.ts +40 -0
- package/src/core/math/physics/mie/MIE_PARTICLES_STANDARD.d.ts.map +1 -0
- package/src/core/math/physics/mie/MIE_PARTICLES_STANDARD.js +124 -0
- package/src/core/math/physics/mie/MIE_PARTICLES_STANDARD_PRECOMPUTED.d.ts +168 -0
- package/src/core/math/physics/mie/MIE_PARTICLES_STANDARD_PRECOMPUTED.d.ts.map +1 -0
- package/src/core/math/physics/mie/MIE_PARTICLES_STANDARD_PRECOMPUTED.js +163 -0
- package/src/core/math/physics/mie/compute_bhmie_optical_properties.d.ts +18 -0
- package/src/core/math/physics/mie/compute_bhmie_optical_properties.d.ts.map +1 -0
- package/src/core/math/physics/mie/compute_bhmie_optical_properties.js +151 -0
- package/src/core/math/physics/mie/compute_lorenz_mie_optical_properties.d.ts +12 -0
- package/src/core/math/physics/mie/compute_lorenz_mie_optical_properties.d.ts.map +1 -0
- package/src/core/math/physics/mie/compute_lorenz_mie_optical_properties.js +363 -0
- package/src/core/math/physics/mie/compute_mie_particle_properties_rgb.d.ts +15 -0
- package/src/core/math/physics/mie/compute_mie_particle_properties_rgb.d.ts.map +1 -0
- package/src/core/math/physics/mie/compute_mie_particle_properties_rgb.js +148 -0
- package/src/core/math/physics/mie/ri_air.d.ts +7 -0
- package/src/core/math/physics/mie/ri_air.d.ts.map +1 -0
- package/src/core/math/physics/mie/ri_air.js +69 -0
- package/src/core/math/physics/mie/ri_ammonium_sulfate.d.ts +7 -0
- package/src/core/math/physics/mie/ri_ammonium_sulfate.d.ts.map +1 -0
- package/src/core/math/physics/mie/ri_ammonium_sulfate.js +140 -0
- package/src/core/math/physics/mie/ri_brine.d.ts +7 -0
- package/src/core/math/physics/mie/ri_brine.d.ts.map +1 -0
- package/src/core/math/physics/mie/ri_brine.js +145 -0
- package/src/core/math/physics/mie/ri_dust.d.ts +7 -0
- package/src/core/math/physics/mie/ri_dust.d.ts.map +1 -0
- package/src/core/math/physics/mie/ri_dust.js +153 -0
- package/src/core/math/physics/mie/ri_pollen.d.ts +7 -0
- package/src/core/math/physics/mie/ri_pollen.d.ts.map +1 -0
- package/src/core/math/physics/mie/ri_pollen.js +146 -0
- package/src/core/math/physics/mie/ri_smoke.d.ts +7 -0
- package/src/core/math/physics/mie/ri_smoke.d.ts.map +1 -0
- package/src/core/math/physics/mie/ri_smoke.js +151 -0
- package/src/core/math/physics/mie/ri_soot.d.ts +7 -0
- package/src/core/math/physics/mie/ri_soot.d.ts.map +1 -0
- package/src/core/math/physics/mie/ri_soot.js +148 -0
- package/src/core/math/physics/mie/ri_water.d.ts +7 -0
- package/src/core/math/physics/mie/ri_water.d.ts.map +1 -0
- package/src/core/math/physics/mie/ri_water.js +148 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
|
|
2
|
+
// Complex arithmetic helpers (a,b represent a + i b)
|
|
3
|
+
function cadd(a, b) { return [a[0] + b[0], a[1] + b[1]]; }
|
|
4
|
+
function csub(a, b) { return [a[0] - b[0], a[1] - b[1]]; }
|
|
5
|
+
function cmul(a, b) { return [a[0] * b[0] - a[1] * b[1], a[0] * b[1] + a[1] * b[0]]; }
|
|
6
|
+
function cdiv(a, b) { const d = b[0]*b[0] + b[1]*b[1]; return [(a[0]*b[0] + a[1]*b[1]) / d, (a[1]*b[0] - a[0]*b[1]) / d]; }
|
|
7
|
+
function creal(a) { return a[0]; }
|
|
8
|
+
function cabs2(a) { return a[0]*a[0] + a[1]*a[1]; }
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Canonical Bohren–Huffman Mie solver (reference implementation)
|
|
13
|
+
* Computes scattering/extinction cross-sections and asymmetry parameter for a
|
|
14
|
+
* homogeneous sphere embedded in a host medium.
|
|
15
|
+
*
|
|
16
|
+
* @param {number} wavelength Vacuum wavelength (meters)
|
|
17
|
+
* @param {number} radius Sphere radius (meters)
|
|
18
|
+
* @param {[number,number]} n_p Particle complex RI [n, k]
|
|
19
|
+
* @param {[number,number]} n_med Medium complex RI [n, k]
|
|
20
|
+
* @returns {{sigma_a_med:number, C_ext:number, C_sca:number, g:number}}
|
|
21
|
+
*/
|
|
22
|
+
export function compute_bhmie_optical_properties(wavelength, radius, n_p, n_med) {
|
|
23
|
+
// Geometry
|
|
24
|
+
const k0 = 2 * Math.PI / wavelength;
|
|
25
|
+
const nmed_r = n_med[0];
|
|
26
|
+
const x = k0 * nmed_r * radius; // size parameter in the (weakly absorbing) medium
|
|
27
|
+
|
|
28
|
+
if (!(x > 0)) {
|
|
29
|
+
return { sigma_a_med: 4 * Math.PI * n_med[1] / wavelength, C_ext: 0, C_sca: 0, g: 0 };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Relative refractive index m = n_p / n_med
|
|
33
|
+
const m = cdiv([n_p[0], n_p[1]], [n_med[0], n_med[1]]);
|
|
34
|
+
// mx is complex (m * x)
|
|
35
|
+
const mx = [m[0] * x, m[1] * x];
|
|
36
|
+
|
|
37
|
+
// Truncation for series
|
|
38
|
+
const nstop = Math.max(2, Math.round(x + 4.0 * Math.cbrt(x) + 2));
|
|
39
|
+
|
|
40
|
+
// Downward recurrence for D_n(mx) = ψ'_n(mx)/ψ_n(mx), complex n from nstop..1
|
|
41
|
+
const D = new Array(nstop + 1);
|
|
42
|
+
D[nstop] = [0, 0];
|
|
43
|
+
for (let n = nstop; n >= 1; --n) {
|
|
44
|
+
// D_{n-1} = n/(mx) - 1 / (D_n + n/(mx))
|
|
45
|
+
const n_over_mx = cdiv([n, 0], mx);
|
|
46
|
+
const denom = cadd(D[n], n_over_mx);
|
|
47
|
+
const invDen = cdiv([1, 0], denom);
|
|
48
|
+
D[n - 1] = csub(n_over_mx, invDen);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Upward recurrence for real ψ_n(x) and complex ξ_n(x) at real x
|
|
52
|
+
const sx = Math.sin(x);
|
|
53
|
+
const cx = Math.cos(x);
|
|
54
|
+
// ψ_0, ψ_1 (real)
|
|
55
|
+
let psi_nm1 = sx; // ψ_0
|
|
56
|
+
let psi_n = sx / x - cx; // ψ_1
|
|
57
|
+
// ξ_0, ξ_1 (complex): ξ_n = ψ_n + i χ_n, with χ_0 = -cos x, χ_1 = -cos x / x - sin x
|
|
58
|
+
let xi_nm1 = [sx, -cx]; // ξ_0
|
|
59
|
+
let xi_n = [psi_n, -(cx / x + sx)]; // ξ_1
|
|
60
|
+
|
|
61
|
+
// Sums
|
|
62
|
+
let sum_ext = 0; // Σ (2n+1) Re(a_n + b_n)
|
|
63
|
+
let sum_sca = 0; // Σ (2n+1) (|a_n|^2 + |b_n|^2)
|
|
64
|
+
|
|
65
|
+
const a = new Array(nstop + 1); // 1..nstop
|
|
66
|
+
const b = new Array(nstop + 1);
|
|
67
|
+
|
|
68
|
+
for (let n = 1; n <= nstop; ++n) {
|
|
69
|
+
// Next ψ and ξ (prepare ψ_{n+1}, ξ_{n+1}) using recurrence
|
|
70
|
+
const two_n_plus_1 = 2 * n + 1;
|
|
71
|
+
const psi_np1 = two_n_plus_1 * psi_n / x - psi_nm1; // ψ_{n+1}
|
|
72
|
+
const xi_np1 = [ (two_n_plus_1 / x) * xi_n[0] - xi_nm1[0], (two_n_plus_1 / x) * xi_n[1] - xi_nm1[1] ]; // ξ_{n+1}
|
|
73
|
+
|
|
74
|
+
// Derivatives at x (real x): ψ'_n = ψ_{n-1} - n/x ψ_n ; ξ'_n = ξ_{n-1} - n/x ξ_n
|
|
75
|
+
const n_over_x = n / x;
|
|
76
|
+
const psi_prime = psi_nm1 - n_over_x * psi_n;
|
|
77
|
+
const xi_prime = [xi_nm1[0] - n_over_x * xi_n[0], xi_nm1[1] - n_over_x * xi_n[1]];
|
|
78
|
+
|
|
79
|
+
// Log-derivative at mx
|
|
80
|
+
const Dn = D[n];
|
|
81
|
+
|
|
82
|
+
// Compute a_n and b_n using D-forms (no ψ_n(mx) factors)
|
|
83
|
+
// a_n = [m ψ'_n(x) − D(mx) ψ_n(x)] / [m ξ'_n(x) − D(mx) ξ_n(x)]
|
|
84
|
+
const m_psi_prime = [m[0] * psi_prime, m[1] * psi_prime];
|
|
85
|
+
const Dn_psi_n = [Dn[0] * psi_n, Dn[1] * psi_n];
|
|
86
|
+
const num_a = csub(m_psi_prime, Dn_psi_n);
|
|
87
|
+
const m_xi_prime = cmul(m, xi_prime);
|
|
88
|
+
const Dn_xi_n = cmul(Dn, xi_n);
|
|
89
|
+
const den_a = csub(m_xi_prime, Dn_xi_n);
|
|
90
|
+
const a_n = cdiv(num_a, den_a);
|
|
91
|
+
|
|
92
|
+
// b_n = [ψ'_n(x) − m D(mx) ψ_n(x)] / [ξ'_n(x) − m D(mx) ξ_n(x)]
|
|
93
|
+
const mDn = cmul(m, Dn);
|
|
94
|
+
const mDn_psi_n = [mDn[0] * psi_n, mDn[1] * psi_n];
|
|
95
|
+
const num_b = csub([psi_prime, 0], mDn_psi_n);
|
|
96
|
+
const mDn_xi_n = cmul(mDn, xi_n);
|
|
97
|
+
const den_b = csub(xi_prime, mDn_xi_n);
|
|
98
|
+
const b_n = cdiv(num_b, den_b);
|
|
99
|
+
|
|
100
|
+
a[n] = a_n;
|
|
101
|
+
b[n] = b_n;
|
|
102
|
+
|
|
103
|
+
const weight = 2 * n + 1;
|
|
104
|
+
sum_ext += weight * (creal(a_n) + creal(b_n));
|
|
105
|
+
sum_sca += weight * (cabs2(a_n) + cabs2(b_n));
|
|
106
|
+
|
|
107
|
+
// Shift recurrences (n→n+1)
|
|
108
|
+
psi_nm1 = psi_n;
|
|
109
|
+
psi_n = psi_np1;
|
|
110
|
+
xi_nm1 = xi_n;
|
|
111
|
+
xi_n = xi_np1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const x2 = x * x;
|
|
115
|
+
const Q_ext = (2 / x2) * sum_ext;
|
|
116
|
+
const Q_sca = (2 / x2) * sum_sca;
|
|
117
|
+
|
|
118
|
+
// Asymmetry parameter g using BH expression
|
|
119
|
+
// sum_g accumulates the two standard terms:
|
|
120
|
+
// Σ (2n+1)/(n(n+1)) Re(a_n b_n*) + Σ n(n+2)/(n+1) Re(a_n a_{n+1}* + b_n b_{n+1}*)
|
|
121
|
+
let sum_g = 0;
|
|
122
|
+
for (let n = 1; n <= nstop; ++n) {
|
|
123
|
+
const a_n = a[n];
|
|
124
|
+
const b_n = b[n];
|
|
125
|
+
const re_ab = a_n[0]*b_n[0] + a_n[1]*b_n[1]; // Re(a_n b_n*)
|
|
126
|
+
sum_g += ((2*n + 1) / (n * (n + 1))) * re_ab;
|
|
127
|
+
}
|
|
128
|
+
for (let n = 1; n <= nstop - 1; ++n) {
|
|
129
|
+
const a_n = a[n], a_np1 = a[n+1];
|
|
130
|
+
const b_n = b[n], b_np1 = b[n+1];
|
|
131
|
+
const re_aa1 = a_n[0]*a_np1[0] + a_n[1]*(-a_np1[1]); // Re(a_n a_{n+1}*)
|
|
132
|
+
const re_bb1 = b_n[0]*b_np1[0] + b_n[1]*(-b_np1[1]); // Re(b_n b_{n+1}*)
|
|
133
|
+
sum_g += (n * (n + 2)) / (n + 1) * (re_aa1 + re_bb1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Using relation g = 2 * sum_g / sum_sca to avoid re-scaling by x^2
|
|
137
|
+
let g = 0;
|
|
138
|
+
if (sum_sca > 1e-30) {
|
|
139
|
+
g = (2 * sum_g) / sum_sca;
|
|
140
|
+
if (g > 1) g = 1;
|
|
141
|
+
if (g < -1) g = -1;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const area = Math.PI * radius * radius;
|
|
145
|
+
const C_ext = Q_ext * area;
|
|
146
|
+
const C_sca = Q_sca * area;
|
|
147
|
+
|
|
148
|
+
const sigma_a_med = (4 * Math.PI * n_med[1]) / wavelength;
|
|
149
|
+
|
|
150
|
+
return { sigma_a_med, C_ext, C_sca, g };
|
|
151
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* For refractive index, the imaginary part controls the absorption coefficient.
|
|
3
|
+
* For non-absorbing media, such as pure water - this will be 0.
|
|
4
|
+
*
|
|
5
|
+
* Note: for most different types of media (and particles), the refractive index will depend on wavelength.
|
|
6
|
+
* @param {number} wavelength (in meters)
|
|
7
|
+
* @param {number} radius (in meters)
|
|
8
|
+
* @param {number[]} n_p 2d vector, complex refractive index of the particle
|
|
9
|
+
* @param {number[]} n_med 2d vector, complex refractive index of the medium (e.g. [1.0, 0.0] for air)
|
|
10
|
+
*/
|
|
11
|
+
export function compute_lorenz_mie_optical_properties(wavelength: number, radius: number, n_p: number[], n_med: number[]): any;
|
|
12
|
+
//# sourceMappingURL=compute_lorenz_mie_optical_properties.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compute_lorenz_mie_optical_properties.d.ts","sourceRoot":"","sources":["../../../../../../src/core/math/physics/mie/compute_lorenz_mie_optical_properties.js"],"names":[],"mappings":"AAsVA;;;;;;;;;GASG;AACH,kEALW,MAAM,UACN,MAAM,OACN,MAAM,EAAE,SACR,MAAM,EAAE,OAWlB"}
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { assert } from "../../../assert.js";
|
|
2
|
+
import { v2_dot } from "../../../geom/vec2/v2_dot.js";
|
|
3
|
+
import { v2_length } from "../../../geom/vec2/v2_length.js";
|
|
4
|
+
import { complex_add } from "../../complex/complex_add.js";
|
|
5
|
+
import { complex_div } from "../../complex/complex_div.js";
|
|
6
|
+
import { complex_mul } from "../../complex/complex_mul.js";
|
|
7
|
+
import { complex_sub } from "../../complex/complex_sub.js";
|
|
8
|
+
|
|
9
|
+
// Complex number operations
|
|
10
|
+
|
|
11
|
+
function vec2(x, y) {
|
|
12
|
+
return new Float64Array([x, y]);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function vec4(x, y, z, w) {
|
|
16
|
+
return new Float64Array([x, y, z, w]);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function cadd(a, b) {
|
|
20
|
+
const r = vec2(0, 0);
|
|
21
|
+
|
|
22
|
+
complex_add(r, a, b);
|
|
23
|
+
|
|
24
|
+
return r;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function csub(a, b) {
|
|
28
|
+
const r = vec2(0, 0);
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
complex_sub(r, a, b);
|
|
32
|
+
|
|
33
|
+
return r;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function cmult(a, b) {
|
|
37
|
+
const r = vec2(0, 0);
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
complex_mul(r, a, b);
|
|
41
|
+
|
|
42
|
+
return r;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function cdiv(a, b) {
|
|
46
|
+
const r = vec2(0, 0);
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
complex_div(r, a, b);
|
|
50
|
+
|
|
51
|
+
return r;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Number of terms to include in infinite sums (truncation)
|
|
55
|
+
function terms_to_sum(z) {
|
|
56
|
+
const size = v2_length(z[0], z[1]);
|
|
57
|
+
return Math.ceil(size + 4.3 * Math.cbrt(size) + 1.0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
// Downward recurrence for A
|
|
62
|
+
function A_all_n(z, M) {
|
|
63
|
+
const A = new Array(M + 2);
|
|
64
|
+
A[M + 1] = vec2(0.0, 0.0);
|
|
65
|
+
for (let n = M; n >= 0; --n) {
|
|
66
|
+
const tmp = cdiv(vec2(n + 1.0, 0.0), z);
|
|
67
|
+
A[n] = csub(tmp, cdiv(vec2(1.0, 0.0), cadd(tmp, A[n + 1])));
|
|
68
|
+
}
|
|
69
|
+
return A;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Upward recurrences for B, psi_zeta, and R
|
|
73
|
+
function psi_zeta(n, z, A, old_B, old_psi_zeta) {
|
|
74
|
+
if (n > 0) {
|
|
75
|
+
const n_z = cdiv(vec2(n, 0.0), z);
|
|
76
|
+
const tmp = cmult(csub(n_z, A[n - 1]), csub(n_z, old_B));
|
|
77
|
+
old_psi_zeta = cmult(old_psi_zeta, tmp);
|
|
78
|
+
}
|
|
79
|
+
return old_psi_zeta;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Calculates the B_n(z) coefficient for term 'n' via upward recurrence.
|
|
84
|
+
* This is a helper function for LorenzMie_ab.
|
|
85
|
+
*
|
|
86
|
+
* @param {number} n - The current recurrence term index (n >= 0).
|
|
87
|
+
* @param {vec2} z - The complex size parameter (x or y).
|
|
88
|
+
* @param {Array<vec2>} A - Pre-computed log-derivatives A_n(z).
|
|
89
|
+
* @param {vec2} old_B - The previous B coefficient (B_{n-1}).
|
|
90
|
+
* @param {vec2} old_psi_zeta - The previous psi_zeta product term.
|
|
91
|
+
* @returns {vec2} The new B_n coefficient (complex number).
|
|
92
|
+
*/
|
|
93
|
+
function B(n, z, A, old_B, old_psi_zeta) {
|
|
94
|
+
|
|
95
|
+
if (n > 0) {
|
|
96
|
+
old_B.psi_zeta = psi_zeta(n, z, A, old_B, old_psi_zeta);
|
|
97
|
+
old_B = cadd(A[n], cdiv(vec2(0.0, 1.0), old_B.psi_zeta));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return old_B;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Calculates the R_n(z) coefficient (ratio psi_n / zeta_n) for term 'n'.
|
|
105
|
+
* This is a helper function for LorenzMie_ab.
|
|
106
|
+
*
|
|
107
|
+
* @param {number} n - The current recurrence term index (n >= 0).
|
|
108
|
+
* @param {vec2} z - The complex size parameter (x).
|
|
109
|
+
* @param {Array<vec2>} A - Pre-computed log-derivatives A_n(z).
|
|
110
|
+
* @param {vec2} B_n - The current B_n(z) coefficient.
|
|
111
|
+
* @param {vec2} old_R - The previous R coefficient (R_{n-1}).
|
|
112
|
+
* @returns {vec2} The new R_n coefficient (complex number).
|
|
113
|
+
*/
|
|
114
|
+
function R(n, z, A, B_n, old_R) {
|
|
115
|
+
if (n > 0) {
|
|
116
|
+
const n_z = cdiv(vec2(n, 0.0), z);
|
|
117
|
+
const tmp = cdiv(cadd(B_n, n_z), cadd(A[n], n_z));
|
|
118
|
+
old_R = cmult(old_R, tmp);
|
|
119
|
+
}
|
|
120
|
+
return old_R;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Calculates the Lorenz-Mie scattering coefficients a_n and b_n for a single term 'n'.
|
|
125
|
+
* This implements the formulas from Figure 3 of Frisvad et al. 2007.
|
|
126
|
+
*
|
|
127
|
+
* @param {number} n - The current recurrence term index (n >= 0).
|
|
128
|
+
* @param {number} size - Unitless size parameter x_vac = 2*pi*r / lambda_vac.
|
|
129
|
+
* @param {vec2} n_p - Complex refractive index of the particle.
|
|
130
|
+
* @param {vec2} n_med - Complex refractive index of the medium.
|
|
131
|
+
* @param {Array<vec2>} A_p - Pre-computed log-derivatives A_n(y) for the particle.
|
|
132
|
+
* @param {Array<vec2>} A_med - Pre-computed log-derivatives A_n(x) for the medium.
|
|
133
|
+
* @param {vec2} old_B - The previous B_{n-1}(x) coefficient.
|
|
134
|
+
* @param {vec2} old_R - The previous R_{n-1}(x) coefficient.
|
|
135
|
+
* @param {vec2} old_psi_zeta - The previous psi_zeta product.
|
|
136
|
+
* @returns {vec4} A vec4 [Re(a_n), Im(a_n), Re(b_n), Im(b_n)] with state properties
|
|
137
|
+
* (.old_B, .old_R, .old_psi_zeta) attached for the next iteration.
|
|
138
|
+
*/
|
|
139
|
+
function LorenzMie_ab(n, size, n_p, n_med, A_p, A_med, old_B, old_R, old_psi_zeta) {
|
|
140
|
+
|
|
141
|
+
const x = vec2(size * n_med[0], size * n_med[1]);
|
|
142
|
+
const B_n = B(n, x, A_med, old_B, old_psi_zeta);
|
|
143
|
+
const R_n = R(n, x, A_med, B_n, old_R);
|
|
144
|
+
const n_med_A_p = cmult(n_med, A_p[n]);
|
|
145
|
+
const n_p_A_med = cmult(n_p, A_med[n]);
|
|
146
|
+
const n_p_A_p = cmult(n_p, A_p[n]);
|
|
147
|
+
const n_med_A_med = cmult(n_med, A_med[n]);
|
|
148
|
+
const n_p_B_n = cmult(n_p, B_n);
|
|
149
|
+
const n_med_B_n = cmult(n_med, B_n);
|
|
150
|
+
const a = cmult(R_n, cdiv(csub(n_med_A_p, n_p_A_med), csub(n_med_A_p, n_p_B_n)));
|
|
151
|
+
const b = cmult(R_n, cdiv(csub(n_p_A_p, n_med_A_med), csub(n_p_A_p, n_med_B_n)));
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
const ab = vec4(a[0], a[1], b[0], b[1]);
|
|
155
|
+
|
|
156
|
+
ab.old_psi_zeta = n > 0 ? old_B.psi_zeta : old_psi_zeta;
|
|
157
|
+
ab.old_B = B_n;
|
|
158
|
+
ab.old_R = R_n;
|
|
159
|
+
|
|
160
|
+
return ab;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The following source code implements Lorenz-Mie theory using the formulas presented in the SIGGRAPH 2007 paper
|
|
165
|
+
*
|
|
166
|
+
* Computing the Scattering Properties of Participating
|
|
167
|
+
* Media Using Lorenz-Mie Theory
|
|
168
|
+
*
|
|
169
|
+
* By authors: Jeppe Revall Frisvad
|
|
170
|
+
* Niels Joergen Christensen
|
|
171
|
+
* Henrik Wann Jensen
|
|
172
|
+
*
|
|
173
|
+
* Code written by Jeppe Revall Frisvad, 2017.
|
|
174
|
+
* Copyright (c) Jeppe Revall Frisvad 2017
|
|
175
|
+
*
|
|
176
|
+
* Permission is granted to anyone to use this code as
|
|
177
|
+
* software for any purpose, including commercial applications.
|
|
178
|
+
* However, the software is provided 'as-is' without any warranty.
|
|
179
|
+
*
|
|
180
|
+
* @param {number} radius_m - Particle radius (in meters).
|
|
181
|
+
* @param {number} wavelength_m - Wavelength of light in vacuum (in meters).
|
|
182
|
+
* @param {vec2} n_p - Complex refractive index of the particle
|
|
183
|
+
* @param {vec2} n_med - Complex refractive index of the medium (e.g., [1.0, 0.0] for air).
|
|
184
|
+
*/
|
|
185
|
+
function lorenz_mie_coefs(
|
|
186
|
+
wavelength_m,
|
|
187
|
+
radius_m,
|
|
188
|
+
n_p,
|
|
189
|
+
n_med
|
|
190
|
+
) {
|
|
191
|
+
|
|
192
|
+
assert.isNumber(wavelength_m, 'wavelength_m');
|
|
193
|
+
assert.isFinite(wavelength_m, 'wavelength_m');
|
|
194
|
+
assert.greaterThan(wavelength_m, 0, 'wavelength_m');
|
|
195
|
+
|
|
196
|
+
assert.isNumber(radius_m, 'radius_m');
|
|
197
|
+
assert.isFinite(radius_m, 'radius_m');
|
|
198
|
+
assert.greaterThan(radius_m, 0, 'radius_m');
|
|
199
|
+
|
|
200
|
+
const psize = 2.0 * Math.PI * radius_m / wavelength_m;
|
|
201
|
+
|
|
202
|
+
const x = vec2(psize * n_med[0], psize * n_med[1]);
|
|
203
|
+
const y = vec2(psize * n_p[0], psize * n_p[1]);
|
|
204
|
+
const M = terms_to_sum(x);
|
|
205
|
+
const A_med = A_all_n(x, M);
|
|
206
|
+
const A_p = A_all_n(y, M);
|
|
207
|
+
|
|
208
|
+
const e_term = Math.exp(2.0 * x[1]);
|
|
209
|
+
|
|
210
|
+
let prev_psi_zeta = vec2(0.5 * (1.0 - Math.cos(2.0 * x[0]) / e_term), -0.5 * Math.sin(2.0 * x[0]) / e_term);
|
|
211
|
+
let prev_B = vec2(0.0, 1.0);
|
|
212
|
+
let prev_R = vec2(0.5 * (1.0 - Math.cos(2.0 * x[0]) * e_term), 0.5 * Math.sin(2.0 * x[0]) * e_term);
|
|
213
|
+
|
|
214
|
+
const ab = new Float64Array(M * 4);
|
|
215
|
+
|
|
216
|
+
for (let n = 0; n < M; ++n) {
|
|
217
|
+
|
|
218
|
+
const ab_n = LorenzMie_ab(
|
|
219
|
+
n, psize, n_p, n_med, A_p, A_med,
|
|
220
|
+
prev_B, prev_R, prev_psi_zeta
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
prev_psi_zeta = ab_n.old_psi_zeta;
|
|
224
|
+
prev_B = ab_n.old_B;
|
|
225
|
+
prev_R = ab_n.old_R;
|
|
226
|
+
|
|
227
|
+
// plug the data in
|
|
228
|
+
ab[4 * n] = ab_n[0];
|
|
229
|
+
ab[4 * n + 1] = ab_n[1];
|
|
230
|
+
ab[4 * n + 2] = ab_n[2];
|
|
231
|
+
ab[4 * n + 3] = ab_n[3];
|
|
232
|
+
}
|
|
233
|
+
return ab;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Calculates the final optical properties by summing the Mie coefficients.
|
|
238
|
+
* This function implements Equations (2) and (3) from the 2007 paper
|
|
239
|
+
* "Computing the Scattering Properties of Participating Media..."
|
|
240
|
+
*
|
|
241
|
+
* @param {Float32Array} ab - The flat array of a_n, b_n coefficients from LorenzMie_coefs.
|
|
242
|
+
* @param {number} radius - Particle radius (in meters).
|
|
243
|
+
* @param {number} wavelength - Wavelength of light in vacuum (in meters).
|
|
244
|
+
* @param {vec2} n_med - Complex refractive index of the medium (e.g., [1.0, 0.0] for air).
|
|
245
|
+
* @returns {object} An object with Q_e, Q_s, albedo, C_ext, and C_sca.
|
|
246
|
+
*/
|
|
247
|
+
function ab_to_optical_properties(
|
|
248
|
+
ab,
|
|
249
|
+
wavelength,
|
|
250
|
+
radius,
|
|
251
|
+
n_med
|
|
252
|
+
) {
|
|
253
|
+
const M = ab.length / 4;
|
|
254
|
+
|
|
255
|
+
let sum_t = vec2(0, 0); // Complex sum for Ct
|
|
256
|
+
let sum_s = 0; // Real sum for Cs
|
|
257
|
+
let sum_g = 0; // Real sum for Asymmetry (g)
|
|
258
|
+
|
|
259
|
+
for (let n = 1; n < M; ++n) {
|
|
260
|
+
// The 'n' in the paper's formulas
|
|
261
|
+
const multiplier = 2 * n + 1;
|
|
262
|
+
|
|
263
|
+
// Get an and bn from your flat array
|
|
264
|
+
const an = vec2(ab[4 * n], ab[4 * n + 1]);
|
|
265
|
+
const bn = vec2(ab[4 * n + 2], ab[4 * n + 3]);
|
|
266
|
+
|
|
267
|
+
// 1. For Extinction (Ct)
|
|
268
|
+
const an_plus_bn = cadd(an, bn);
|
|
269
|
+
// You need complex math functions cadd, cdiv
|
|
270
|
+
const n_med_sq = cmult(n_med, n_med);
|
|
271
|
+
const term_t = cdiv(an_plus_bn, n_med_sq);
|
|
272
|
+
sum_t = cadd(sum_t, cmult(vec2(multiplier, 0), term_t));
|
|
273
|
+
|
|
274
|
+
// 2. For Scattering (Cs)
|
|
275
|
+
const an_mag_sq = an[0] * an[0] + an[1] * an[1];
|
|
276
|
+
const bn_mag_sq = bn[0] * bn[0] + bn[1] * bn[1];
|
|
277
|
+
sum_s += multiplier * (an_mag_sq + bn_mag_sq);
|
|
278
|
+
|
|
279
|
+
// 3. Asymmetry Accumulation (New)
|
|
280
|
+
// Term A: Interference of an and bn
|
|
281
|
+
// Formula: (2n+1) / (n(n+1)) * Re(an * bn*)
|
|
282
|
+
const factor_A = (2 * n + 1) / (n * (n + 1));
|
|
283
|
+
sum_g += factor_A * v2_dot(an[0], an[1], bn[0], bn[1]);
|
|
284
|
+
|
|
285
|
+
// Term B: Interference of n and n+1
|
|
286
|
+
// Formula: (n(n+2)) / (n+1) * Re(an * an+1* + bn * bn+1*)
|
|
287
|
+
if (n < M - 1) {
|
|
288
|
+
const n_next = n + 1;
|
|
289
|
+
const an1_r = ab[4 * n_next];
|
|
290
|
+
const an1_i = ab[4 * n_next + 1];
|
|
291
|
+
const bn1_r = ab[4 * n_next + 2];
|
|
292
|
+
const bn1_i = ab[4 * n_next + 3];
|
|
293
|
+
|
|
294
|
+
const factor_B = (n * (n + 2)) / (n + 1);
|
|
295
|
+
|
|
296
|
+
const term_an_an1 = v2_dot(an[0], an[1], an1_r, an1_i);
|
|
297
|
+
const term_bn_bn1 = v2_dot(bn[0], bn[1], bn1_r, bn1_i);
|
|
298
|
+
|
|
299
|
+
sum_g += factor_B * (term_an_an1 + term_bn_bn1);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// --- Final Calculations ---
|
|
304
|
+
|
|
305
|
+
// Asymmetry Parameter (g)
|
|
306
|
+
// g = (4/x^2 * sum_g) / (2/x^2 * sum_s) => 2 * sum_g / sum_s
|
|
307
|
+
let g = 0;
|
|
308
|
+
if (sum_s > 1e-12) {
|
|
309
|
+
g = (2 * sum_g) / sum_s;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Extinction (Ct from Eq. 22)
|
|
313
|
+
const C_t = (Math.pow(wavelength, 2) / (2 * Math.PI)) * sum_t[0]; //
|
|
314
|
+
|
|
315
|
+
// Scattering (Cs from Eq. 23)
|
|
316
|
+
const alpha = (4 * Math.PI * radius * n_med[1]) / wavelength;
|
|
317
|
+
let gamma;
|
|
318
|
+
if (Math.abs(alpha) < 1e-10) {
|
|
319
|
+
// avoid division by 0
|
|
320
|
+
gamma = 1;
|
|
321
|
+
} else {
|
|
322
|
+
gamma = (2 * (1 + (alpha - 1) * Math.exp(alpha))) / Math.pow(alpha, 2);
|
|
323
|
+
}
|
|
324
|
+
const n_med_mag_sq = n_med[0] * n_med[0] + n_med[1] * n_med[1];
|
|
325
|
+
const C_s_factor = (Math.pow(wavelength, 2) * Math.exp(-alpha)) / (2 * Math.PI * gamma * n_med_mag_sq);
|
|
326
|
+
const C_s = C_s_factor * sum_s;
|
|
327
|
+
|
|
328
|
+
// Bulk Coefficients (assuming number density N)
|
|
329
|
+
const sigma_a_med = (4 * Math.PI * n_med[1]) / wavelength;
|
|
330
|
+
|
|
331
|
+
// To get bulk coefficients sigma_t and sigma_s, use the following formulae, where N is numeric density (particles per cubic meter):
|
|
332
|
+
// sigma_t = sigma_a_med + C_t * N;
|
|
333
|
+
// sigma_s = C_s * N;
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
sigma_a_med,
|
|
337
|
+
C_ext: C_t,
|
|
338
|
+
C_sca: C_s,
|
|
339
|
+
g, // Anisotropy parameter
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* For refractive index, the imaginary part controls the absorption coefficient.
|
|
345
|
+
* For non-absorbing media, such as pure water - this will be 0.
|
|
346
|
+
*
|
|
347
|
+
* Note: for most different types of media (and particles), the refractive index will depend on wavelength.
|
|
348
|
+
* @param {number} wavelength (in meters)
|
|
349
|
+
* @param {number} radius (in meters)
|
|
350
|
+
* @param {number[]} n_p 2d vector, complex refractive index of the particle
|
|
351
|
+
* @param {number[]} n_med 2d vector, complex refractive index of the medium (e.g. [1.0, 0.0] for air)
|
|
352
|
+
*/
|
|
353
|
+
export function compute_lorenz_mie_optical_properties(
|
|
354
|
+
wavelength,
|
|
355
|
+
radius,
|
|
356
|
+
n_p,
|
|
357
|
+
n_med,
|
|
358
|
+
) {
|
|
359
|
+
const ab = lorenz_mie_coefs(wavelength, radius, n_p, n_med);
|
|
360
|
+
|
|
361
|
+
return ab_to_optical_properties(ab, wavelength, radius, n_med);
|
|
362
|
+
}
|
|
363
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compute scattering and extinction for a given particle/medium configuration
|
|
3
|
+
* @param {number} radius
|
|
4
|
+
* @param {function(result:number[], wavelength_nm:number)} n_p Particle Refraction Index. function of wavelength, Complex number.
|
|
5
|
+
* @param {function(result:number[], wavelength_nm:number)} n_med Medium Refraction Index. function of wavelength, Complex number.
|
|
6
|
+
* @param {number} [step_size_nm] In nanometers. Controls step size in spectral integration steps, smaller steps = more accurate. Use 1, or multiples of 5
|
|
7
|
+
*/
|
|
8
|
+
export function compute_mie_particle_properties_rgb(radius: number, n_p: any, n_med: any, step_size_nm?: number): {
|
|
9
|
+
scattering_coefficients: number[];
|
|
10
|
+
extinction_coefficients: number[];
|
|
11
|
+
extinction_intercept: number[];
|
|
12
|
+
albedo: number[];
|
|
13
|
+
g: number;
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=compute_mie_particle_properties_rgb.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compute_mie_particle_properties_rgb.d.ts","sourceRoot":"","sources":["../../../../../../src/core/math/physics/mie/compute_mie_particle_properties_rgb.js"],"names":[],"mappings":"AAMA;;;;;;GAMG;AACH,4DALW,MAAM,uCAGN,MAAM;;;;;;EAwIhB"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { assert } from "../../../assert.js";
|
|
2
|
+
import { D65_spd_tabulated } from "../../../color/illuminant/D65_spd_tabulated.js";
|
|
3
|
+
import { xyz_cmf_tabulated } from "../../../color/xyz/xyz_cmf_tabulated.js";
|
|
4
|
+
import { xyz_to_rgb } from "../../../color/xyz/xyz_to_rgb.js";
|
|
5
|
+
import { compute_lorenz_mie_optical_properties } from "./compute_lorenz_mie_optical_properties.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Compute scattering and extinction for a given particle/medium configuration
|
|
9
|
+
* @param {number} radius
|
|
10
|
+
* @param {function(result:number[], wavelength_nm:number)} n_p Particle Refraction Index. function of wavelength, Complex number.
|
|
11
|
+
* @param {function(result:number[], wavelength_nm:number)} n_med Medium Refraction Index. function of wavelength, Complex number.
|
|
12
|
+
* @param {number} [step_size_nm] In nanometers. Controls step size in spectral integration steps, smaller steps = more accurate. Use 1, or multiples of 5
|
|
13
|
+
*/
|
|
14
|
+
export function compute_mie_particle_properties_rgb(
|
|
15
|
+
radius,
|
|
16
|
+
n_p,
|
|
17
|
+
n_med,
|
|
18
|
+
step_size_nm = 5
|
|
19
|
+
) {
|
|
20
|
+
|
|
21
|
+
assert.isFunction(n_p, 'n_p');
|
|
22
|
+
assert.isFunction(n_med, 'n_med');
|
|
23
|
+
assert.isFinite(radius, 'radius');
|
|
24
|
+
assert.greaterThan(radius, 0, 'radius must be positive');
|
|
25
|
+
assert.isNonNegativeInteger(step_size_nm, 'step_size_nm');
|
|
26
|
+
|
|
27
|
+
const cmf = [0, 0, 0];
|
|
28
|
+
let sum_Ey = 0;
|
|
29
|
+
|
|
30
|
+
const scattering_coefficients = [0, 0, 0];
|
|
31
|
+
const extinction_coefficients = [0, 0, 0];
|
|
32
|
+
const sigma_a_med = [0, 0, 0];
|
|
33
|
+
|
|
34
|
+
const ir_particle = [];
|
|
35
|
+
const ir_medium = [];
|
|
36
|
+
|
|
37
|
+
// Variables for G integration
|
|
38
|
+
let g_weighted_accumulator = 0;
|
|
39
|
+
let g_weight_total = 0;
|
|
40
|
+
|
|
41
|
+
// integrate CMF
|
|
42
|
+
const WAVELENGTH_MIN = 380;
|
|
43
|
+
const WAVELENGTH_MAX = 780;
|
|
44
|
+
|
|
45
|
+
for (let wavelength_nm = WAVELENGTH_MIN; wavelength_nm <= WAVELENGTH_MAX; wavelength_nm += step_size_nm) {
|
|
46
|
+
|
|
47
|
+
// visible light sweep
|
|
48
|
+
// Integrate over visible spectrum in nanometers for CMF/RI
|
|
49
|
+
const wavelength_m = wavelength_nm * 1e-9;
|
|
50
|
+
|
|
51
|
+
const E = D65_spd_tabulated(wavelength_nm);
|
|
52
|
+
xyz_cmf_tabulated(cmf, wavelength_nm);
|
|
53
|
+
|
|
54
|
+
// Calculate spectral weight W(λ) = E(λ) * CMF(λ)
|
|
55
|
+
const wX = cmf[0] * E;
|
|
56
|
+
const wY = cmf[1] * E;
|
|
57
|
+
const wZ = cmf[2] * E;
|
|
58
|
+
|
|
59
|
+
// accumulate illuminant-luminance denominator (single shared normalization)
|
|
60
|
+
sum_Ey += wY;
|
|
61
|
+
|
|
62
|
+
// compute IR coefficients
|
|
63
|
+
n_p(ir_particle, wavelength_nm);
|
|
64
|
+
n_med(ir_medium, wavelength_nm);
|
|
65
|
+
|
|
66
|
+
const properties = compute_lorenz_mie_optical_properties(
|
|
67
|
+
wavelength_m,
|
|
68
|
+
radius,
|
|
69
|
+
ir_particle,
|
|
70
|
+
ir_medium,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
scattering_coefficients[0] += properties.C_sca * wX;
|
|
74
|
+
scattering_coefficients[1] += properties.C_sca * wY;
|
|
75
|
+
scattering_coefficients[2] += properties.C_sca * wZ;
|
|
76
|
+
|
|
77
|
+
extinction_coefficients[0] += properties.C_ext * wX;
|
|
78
|
+
extinction_coefficients[1] += properties.C_ext * wY;
|
|
79
|
+
extinction_coefficients[2] += properties.C_ext * wZ;
|
|
80
|
+
|
|
81
|
+
sigma_a_med[0] += properties.sigma_a_med * wX;
|
|
82
|
+
sigma_a_med[1] += properties.sigma_a_med * wY;
|
|
83
|
+
sigma_a_med[2] += properties.sigma_a_med * wZ;
|
|
84
|
+
|
|
85
|
+
// We weight 'g' by how much this wavelength contributes to
|
|
86
|
+
// the Visible Luminance (Y) of the Scattered light.
|
|
87
|
+
// If C_sca is 0, this wavelength should not affect the average G.
|
|
88
|
+
const scatter_luminance_weight = properties.C_sca * wY;
|
|
89
|
+
|
|
90
|
+
g_weighted_accumulator += properties.g * scatter_luminance_weight;
|
|
91
|
+
g_weight_total += scatter_luminance_weight;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// normalize using a single shared luminance-based denominator: k = 1 / Σ E(λ)·ȳ(λ)
|
|
95
|
+
const k = 1 / sum_Ey;
|
|
96
|
+
|
|
97
|
+
scattering_coefficients[0] *= k;
|
|
98
|
+
scattering_coefficients[1] *= k;
|
|
99
|
+
scattering_coefficients[2] *= k;
|
|
100
|
+
|
|
101
|
+
extinction_coefficients[0] *= k;
|
|
102
|
+
extinction_coefficients[1] *= k;
|
|
103
|
+
extinction_coefficients[2] *= k;
|
|
104
|
+
|
|
105
|
+
sigma_a_med[0] *= k;
|
|
106
|
+
sigma_a_med[1] *= k;
|
|
107
|
+
sigma_a_med[2] *= k;
|
|
108
|
+
|
|
109
|
+
// Resolve G
|
|
110
|
+
// If the particle is invisible (no scattering), default to isotropic (0)
|
|
111
|
+
const final_g = g_weight_total > 1e-12 ? (g_weighted_accumulator / g_weight_total) : 0;
|
|
112
|
+
|
|
113
|
+
// convert XYZ -> sRGB
|
|
114
|
+
|
|
115
|
+
xyz_to_rgb(scattering_coefficients, scattering_coefficients);
|
|
116
|
+
xyz_to_rgb(extinction_coefficients, extinction_coefficients);
|
|
117
|
+
xyz_to_rgb(sigma_a_med, sigma_a_med);
|
|
118
|
+
|
|
119
|
+
// Perform gamut clamp to remove potential negative lobes
|
|
120
|
+
scattering_coefficients[0] = Math.max(0, scattering_coefficients[0]);
|
|
121
|
+
scattering_coefficients[1] = Math.max(0, scattering_coefficients[1]);
|
|
122
|
+
scattering_coefficients[2] = Math.max(0, scattering_coefficients[2]);
|
|
123
|
+
|
|
124
|
+
extinction_coefficients[0] = Math.max(0, extinction_coefficients[0]);
|
|
125
|
+
extinction_coefficients[1] = Math.max(0, extinction_coefficients[1]);
|
|
126
|
+
extinction_coefficients[2] = Math.max(0, extinction_coefficients[2]);
|
|
127
|
+
|
|
128
|
+
sigma_a_med[0] = Math.max(0, sigma_a_med[0]);
|
|
129
|
+
sigma_a_med[1] = Math.max(0, sigma_a_med[1]);
|
|
130
|
+
sigma_a_med[2] = Math.max(0, sigma_a_med[2]);
|
|
131
|
+
|
|
132
|
+
// Derive the albedo.
|
|
133
|
+
// Prevent division by 0, and clamp gamut at the upper range to 1
|
|
134
|
+
const albedo = [
|
|
135
|
+
extinction_coefficients[0] !== 0 ? Math.min(1, scattering_coefficients[0] / extinction_coefficients[0]) : 0,
|
|
136
|
+
extinction_coefficients[1] !== 0 ? Math.min(1, scattering_coefficients[1] / extinction_coefficients[1]) : 0,
|
|
137
|
+
extinction_coefficients[2] !== 0 ? Math.min(1, scattering_coefficients[2] / extinction_coefficients[2]) : 0,
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
scattering_coefficients,
|
|
142
|
+
extinction_coefficients,
|
|
143
|
+
extinction_intercept: sigma_a_med,
|
|
144
|
+
albedo,
|
|
145
|
+
g: final_g,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
}
|