starry / backend /libs /three /math /SphericalHarmonics3.js
k-l-lambda's picture
feat: add Python ML services (CPU mode) with model download
2b7aae2
import { Vector3 } from './Vector3.js';
/**
* Primary reference:
* https://graphics.stanford.edu/papers/envmap/envmap.pdf
*
* Secondary reference:
* https://www.ppsloan.org/publications/StupidSH36.pdf
*/
// 3-band SH defined by 9 coefficients
class SphericalHarmonics3 {
constructor() {
this.coefficients = [];
for (let i = 0; i < 9; i++) {
this.coefficients.push(new Vector3());
}
}
set(coefficients) {
for (let i = 0; i < 9; i++) {
this.coefficients[i].copy(coefficients[i]);
}
return this;
}
zero() {
for (let i = 0; i < 9; i++) {
this.coefficients[i].set(0, 0, 0);
}
return this;
}
// get the radiance in the direction of the normal
// target is a Vector3
getAt(normal, target) {
// normal is assumed to be unit length
const x = normal.x,
y = normal.y,
z = normal.z;
const coeff = this.coefficients;
// band 0
target.copy(coeff[0]).multiplyScalar(0.282095);
// band 1
target.addScaledVector(coeff[1], 0.488603 * y);
target.addScaledVector(coeff[2], 0.488603 * z);
target.addScaledVector(coeff[3], 0.488603 * x);
// band 2
target.addScaledVector(coeff[4], 1.092548 * (x * y));
target.addScaledVector(coeff[5], 1.092548 * (y * z));
target.addScaledVector(coeff[6], 0.315392 * (3.0 * z * z - 1.0));
target.addScaledVector(coeff[7], 1.092548 * (x * z));
target.addScaledVector(coeff[8], 0.546274 * (x * x - y * y));
return target;
}
// get the irradiance (radiance convolved with cosine lobe) in the direction of the normal
// target is a Vector3
// https://graphics.stanford.edu/papers/envmap/envmap.pdf
getIrradianceAt(normal, target) {
// normal is assumed to be unit length
const x = normal.x,
y = normal.y,
z = normal.z;
const coeff = this.coefficients;
// band 0
target.copy(coeff[0]).multiplyScalar(0.886227); // π * 0.282095
// band 1
target.addScaledVector(coeff[1], 2.0 * 0.511664 * y); // ( 2 * π / 3 ) * 0.488603
target.addScaledVector(coeff[2], 2.0 * 0.511664 * z);
target.addScaledVector(coeff[3], 2.0 * 0.511664 * x);
// band 2
target.addScaledVector(coeff[4], 2.0 * 0.429043 * x * y); // ( π / 4 ) * 1.092548
target.addScaledVector(coeff[5], 2.0 * 0.429043 * y * z);
target.addScaledVector(coeff[6], 0.743125 * z * z - 0.247708); // ( π / 4 ) * 0.315392 * 3
target.addScaledVector(coeff[7], 2.0 * 0.429043 * x * z);
target.addScaledVector(coeff[8], 0.429043 * (x * x - y * y)); // ( π / 4 ) * 0.546274
return target;
}
add(sh) {
for (let i = 0; i < 9; i++) {
this.coefficients[i].add(sh.coefficients[i]);
}
return this;
}
addScaledSH(sh, s) {
for (let i = 0; i < 9; i++) {
this.coefficients[i].addScaledVector(sh.coefficients[i], s);
}
return this;
}
scale(s) {
for (let i = 0; i < 9; i++) {
this.coefficients[i].multiplyScalar(s);
}
return this;
}
lerp(sh, alpha) {
for (let i = 0; i < 9; i++) {
this.coefficients[i].lerp(sh.coefficients[i], alpha);
}
return this;
}
equals(sh) {
for (let i = 0; i < 9; i++) {
if (!this.coefficients[i].equals(sh.coefficients[i])) {
return false;
}
}
return true;
}
copy(sh) {
return this.set(sh.coefficients);
}
clone() {
return new this.constructor().copy(this);
}
fromArray(array, offset = 0) {
const coefficients = this.coefficients;
for (let i = 0; i < 9; i++) {
coefficients[i].fromArray(array, offset + i * 3);
}
return this;
}
toArray(array = [], offset = 0) {
const coefficients = this.coefficients;
for (let i = 0; i < 9; i++) {
coefficients[i].toArray(array, offset + i * 3);
}
return array;
}
// evaluate the basis functions
// shBasis is an Array[ 9 ]
static getBasisAt(normal, shBasis) {
// normal is assumed to be unit length
const x = normal.x,
y = normal.y,
z = normal.z;
// band 0
shBasis[0] = 0.282095;
// band 1
shBasis[1] = 0.488603 * y;
shBasis[2] = 0.488603 * z;
shBasis[3] = 0.488603 * x;
// band 2
shBasis[4] = 1.092548 * x * y;
shBasis[5] = 1.092548 * y * z;
shBasis[6] = 0.315392 * (3 * z * z - 1);
shBasis[7] = 1.092548 * x * z;
shBasis[8] = 0.546274 * (x * x - y * y);
}
}
SphericalHarmonics3.prototype.isSphericalHarmonics3 = true;
export { SphericalHarmonics3 };