Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 181 additions & 50 deletions examples/jsm/loaders/IESLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Loader,
UnsignedByteType,
LinearFilter,
RepeatWrapping,
HalfFloatType,
DataUtils
} from 'three';
Expand Down Expand Up @@ -50,81 +51,90 @@ class IESLoader extends Loader {

_getIESValues( iesLamp, type ) {

const width = 360;
const height = 180;
const size = width * height;
function findIndex( angles, value ) {

const data = new Array( size );
for ( let i = 0; i < angles.length - 2; ++ i ) {

function interpolateCandelaValues( phi, theta ) {
if ( value < angles[ i + 1 ] ) {

let phiIndex = 0, thetaIndex = 0;
let startTheta = 0, endTheta = 0, startPhi = 0, endPhi = 0;

for ( let i = 0; i < iesLamp.numHorAngles - 1; ++ i ) { // numHorAngles = horAngles.length-1 because of extra padding, so this wont cause an out of bounds error

if ( theta < iesLamp.horAngles[ i + 1 ] || i == iesLamp.numHorAngles - 2 ) {

thetaIndex = i;
startTheta = iesLamp.horAngles[ i ];
endTheta = iesLamp.horAngles[ i + 1 ];

break;
return i;

}

}

for ( let i = 0; i < iesLamp.numVerAngles - 1; ++ i ) {
return angles.length - 2;

if ( phi < iesLamp.verAngles[ i + 1 ] || i == iesLamp.numVerAngles - 2 ) {
}

phiIndex = i;
startPhi = iesLamp.verAngles[ i ];
endPhi = iesLamp.verAngles[ i + 1 ];
function interpolateCandelaValues( azimuth, inclination ) {

break;
const azimuthIndex = findIndex( iesLamp.horAngles, azimuth );
const deltaAzimuth = iesLamp.horAngles[ azimuthIndex + 1 ] - iesLamp.horAngles[ azimuthIndex ];
const tAzimuth = ( azimuth - iesLamp.horAngles[ azimuthIndex ] ) / deltaAzimuth;

}
const inclinationIndex = findIndex( iesLamp.verAngles, inclination );
const deltaInclination = iesLamp.verAngles[ inclinationIndex + 1 ] - iesLamp.verAngles[ inclinationIndex ];
const tInclination = ( inclination - iesLamp.verAngles[ inclinationIndex ] ) / deltaInclination;

}
const v1 = MathUtils.lerp( iesLamp.candelaValues[ azimuthIndex ][ inclinationIndex ], iesLamp.candelaValues[ azimuthIndex ][ inclinationIndex + 1 ], tInclination );
const v2 = MathUtils.lerp( iesLamp.candelaValues[ azimuthIndex + 1 ][ inclinationIndex ], iesLamp.candelaValues[ azimuthIndex + 1 ][ inclinationIndex + 1 ], tInclination );
const v = MathUtils.lerp( v1, v2, tAzimuth );
return v;

}

const deltaTheta = endTheta - startTheta;
const deltaPhi = endPhi - startPhi;
const startAzimuth = iesLamp.horAngles[ 0 ], endAzimuth = iesLamp.horAngles[ iesLamp.numHorAngles - 1 ];
const startInclination = iesLamp.verAngles[ 0 ], endInclination = iesLamp.verAngles[ iesLamp.numVerAngles - 1 ];

if ( deltaPhi === 0 ) // Outside range
return 0;
// compute the best resolution for the IES texture based on the minium sampling angle
const nh = iesLamp.horAngles.length;
const nv = iesLamp.verAngles.length;
let dAzimuth = 360;
for ( let i = 0; i < nh - 1; ++ i ) {

const t1 = deltaTheta === 0 ? 0 : ( theta - startTheta ) / deltaTheta;
const t2 = ( phi - startPhi ) / deltaPhi;
dAzimuth = Math.min( dAzimuth, iesLamp.horAngles[ i + 1 ] - iesLamp.horAngles[ i ] );

const nextThetaIndex = deltaTheta === 0 ? thetaIndex : thetaIndex + 1;
}

const v1 = MathUtils.lerp( iesLamp.candelaValues[ thetaIndex ][ phiIndex ], iesLamp.candelaValues[ nextThetaIndex ][ phiIndex ], t1 );
const v2 = MathUtils.lerp( iesLamp.candelaValues[ thetaIndex ][ phiIndex + 1 ], iesLamp.candelaValues[ nextThetaIndex ][ phiIndex + 1 ], t1 );
const v = MathUtils.lerp( v1, v2, t2 );
dAzimuth = Math.max( dAzimuth, 0.5 );
let dInclination = 180;
for ( let i = 0; i < nv - 1; ++ i ) {

return v;
dInclination = Math.min( dInclination, iesLamp.verAngles[ i + 1 ] - iesLamp.verAngles[ i ] );

}

const startTheta = iesLamp.horAngles[ 0 ], endTheta = iesLamp.horAngles[ iesLamp.numHorAngles - 1 ];
dInclination = Math.max( dInclination, 0.5 );

for ( let i = 0; i < size; ++ i ) {
const rangeAzimuth = iesLamp.horAngles[ nh - 1 ] - iesLamp.horAngles[ 0 ];
const nAzimuth = Math.round( rangeAzimuth / dAzimuth ) + 1;
const rangeInclination = iesLamp.verAngles[ nv - 1 ] - iesLamp.verAngles[ 0 ];
const nInclination = Math.round( rangeInclination / dInclination ) + 1;

let theta = i % width;
const phi = Math.floor( i / width );
const data = new Array( nAzimuth * nInclination );

if ( endTheta - startTheta !== 0 && ( theta < startTheta || theta >= endTheta ) ) { // Handle symmetry for hor angles
for ( let iAzimuth = 0; iAzimuth < nAzimuth; ++ iAzimuth ) {

theta %= endTheta * 2;
const azimuth = iAzimuth * 360 / ( nAzimuth - 1 );
if ( azimuth < startAzimuth || azimuth > endAzimuth ) {

if ( theta > endTheta )
theta = endTheta * 2 - theta;
continue;

}

data[ phi + theta * height ] = interpolateCandelaValues( phi, theta );
for ( let iInclination = 0; iInclination < nInclination; ++ iInclination ) {

const inclination = iInclination * 180 / ( nInclination - 1 );
if ( inclination < startInclination || inclination > endInclination ) {

continue;

}

data[ iAzimuth + iInclination * nAzimuth ] = interpolateCandelaValues( azimuth, inclination );

}

}

Expand All @@ -135,7 +145,7 @@ class IESLoader extends Loader {
else if ( type === FloatType ) result = Float32Array.from( data );
else console.error( 'IESLoader: Unsupported type:', type );

return result;
return { data: result, width: nAzimuth, height: nInclination };

}

Expand Down Expand Up @@ -176,11 +186,12 @@ class IESLoader extends Loader {
const type = this.type;

const iesLamp = new IESLamp( text );
const data = this._getIESValues( iesLamp, type );
const result = this._getIESValues( iesLamp, type );

const texture = new DataTexture( data, 180, 1, RedFormat, type );
const texture = new DataTexture( result.data, result.width, result.height, RedFormat, type );
texture.minFilter = LinearFilter;
texture.magFilter = LinearFilter;
texture.wrapS = RepeatWrapping;
texture.needsUpdate = true;

return texture;
Expand Down Expand Up @@ -286,6 +297,122 @@ function IESLamp( text ) {

}

function _unrollTypeA() {

if ( _self.horAngles.at( 0 ) == 0 ) {

const candelaValues = [];
const horAngles = [];
for ( let i = _self.numHorAngles - 1; i > 0; -- i ) {

candelaValues.push( _self.candelaValues[ i ].slice() );
horAngles.push( - _self.horAngles[ i ] );

}

_self.candelaValues = candelaValues.concat( _self.candelaValues );
_self.horAngles = horAngles.concat( _self.horAngles );

}

for ( let iv = 0; iv < _self.verAngles.length; ++ iv ) {

_self.verAngles[ iv ] += 90;

}

}

function _unrollTypeB() {

console.log( 'Type B : this type is not supported correctly, sorry.' );
if ( _self.horAngles.at( 0 ) == 0 ) {

const candelaValues = [];
const horAngles = [];
for ( let i = _self.numHorAngles - 1; i > 0; -- i ) {

candelaValues.push( _self.candelaValues[ i ].slice() );
horAngles.push( - _self.horAngles[ i ] );

}

_self.candelaValues = candelaValues.concat( _self.candelaValues );
_self.horAngles = horAngles.concat( _self.horAngles );

}

for ( let iv = 0; iv < _self.verAngles.length; ++ iv ) {

_self.verAngles[ iv ] += 90;

}

}

function _unrollTypeC() {

if ( _self.horAngles.at( - 1 ) == 0 ) {

_self.candelaValues.push( _self.candelaValues.at( - 1 ).slice() );
_self.horAngles.push( 360 );
_self.numHorAngles = 2;

}

if ( _self.horAngles.at( - 1 ) == 90 ) {

for ( let i = _self.numHorAngles - 2; i >= 0; -- i ) {

_self.candelaValues.push( _self.candelaValues[ i ].slice() );
_self.horAngles.push( 180 - _self.horAngles[ i ] );

}

_self.numHorAngles = 2 * _self.numHorAngles - 1;

}

if ( _self.horAngles.at( - 1 ) == 180 ) {

for ( let i = _self.numHorAngles - 2; i >= 0; -- i ) {

_self.candelaValues.push( _self.candelaValues[ i ].slice() );
_self.horAngles.push( 360 - _self.horAngles[ i ] );

}

_self.numHorAngles = 2 * _self.numHorAngles - 1;

}

if ( _self.horAngles.at( - 1 ) != 360 ) {

//do nothing
console.log( 'Type C : There is an issue in the horizontal angles.' );

}

}

function unroll() {

if ( _self.gonioType == 1 ) {

_unrollTypeC();

} else if ( _self.gonioType == 3 ) {

_unrollTypeA();

} else if ( _self.gonioType == 2 ) {

_unrollTypeB();

}

}

while ( true ) {

line = textArray[ lineNumber ++ ];
Expand Down Expand Up @@ -327,6 +454,8 @@ function IESLamp( text ) {
readArray( _self.numVerAngles, _self.verAngles );
readArray( _self.numHorAngles, _self.horAngles );



// Parse Candela values
for ( let i = 0; i < _self.numHorAngles; ++ i ) {

Expand All @@ -335,12 +464,12 @@ function IESLamp( text ) {
}

// Calculate actual candela values, and normalize.
const factor = _self.multiplier * _self.ballFactor * _self.blpFactor;
for ( let i = 0; i < _self.numHorAngles; ++ i ) {

for ( let j = 0; j < _self.numVerAngles; ++ j ) {

_self.candelaValues[ i ][ j ] *= _self.candelaValues[ i ][ j ] * _self.multiplier
* _self.ballFactor * _self.blpFactor;
_self.candelaValues[ i ][ j ] *= factor;

}

Expand Down Expand Up @@ -373,6 +502,8 @@ function IESLamp( text ) {

}

unroll();

}


Expand Down
10 changes: 6 additions & 4 deletions src/nodes/lighting/IESSpotLightNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,25 @@ class IESSpotLightNode extends SpotLightNode {
*
* @param {NodeBuilder} builder - The node builder.
* @param {Node<float>} angleCosine - The angle to compute the spot attenuation for.
* @param {Node<float>} azimuthAngle - The azimuthal angle
* @return {Node<float>} The spot attenuation.
*/
getSpotAttenuation( builder, angleCosine ) {
getSpotAttenuation( builder, angleCosine, azimuthAngle ) {

const iesMap = this.light.iesMap;

let spotAttenuation = null;

if ( iesMap && iesMap.isTexture === true ) {

const angle = angleCosine.acos().mul( 1.0 / Math.PI );
const inclinationNormalized = angleCosine.acos().mul( 1.0 / Math.PI );
const azimuthNormalized = azimuthAngle.mul( 1.0 / ( 2 * Math.PI ) );

spotAttenuation = texture( iesMap, vec2( angle, 0 ), 0 ).r;
spotAttenuation = texture( iesMap, vec2( azimuthNormalized, inclinationNormalized ), 0 ).r;

} else {

spotAttenuation = super.getSpotAttenuation( angleCosine );
spotAttenuation = super.getSpotAttenuation( angleCosine, azimuthAngle );

}

Expand Down
18 changes: 14 additions & 4 deletions src/nodes/lighting/SpotLightNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import { uniform } from '../core/UniformNode.js';
import { smoothstep } from '../math/MathNode.js';
import { renderGroup } from '../core/UniformGroupNode.js';
import { lightTargetDirection, lightProjectionUV } from '../accessors/Lights.js';
import { lightTargetDirection, lightProjectionUV, lightPosition, lightTargetPosition } from '../accessors/Lights.js';
import { texture } from '../accessors/TextureNode.js';
import { positionWorld } from '../accessors/Position.js';

/**
* Module for representing spot lights as nodes.
Expand Down Expand Up @@ -89,9 +90,10 @@
*
* @param {NodeBuilder} builder - The node builder.
* @param {Node<float>} angleCosine - The angle to compute the spot attenuation for.
* @param {Node<float>} azimuthAngle - The azimuthal angle
* @return {Node<float>} The spot attenuation.
*/
getSpotAttenuation( builder, angleCosine ) {
getSpotAttenuation( builder, angleCosine, azimuthAngle ) {

Check warning on line 96 in src/nodes/lighting/SpotLightNode.js

View workflow job for this annotation

GitHub Actions / Lint, Unit, Unit addons, Circular dependencies & Examples testing

'azimuthAngle' is defined but never used

const { coneCosNode, penumbraCosNode } = this;

Expand Down Expand Up @@ -120,12 +122,20 @@

const { colorNode, cutoffDistanceNode, decayExponentNode, light } = this;

const lightVector = this.getLightVector( builder );

const vecLight = lightPosition( light ).sub( builder.context.positionWorld || positionWorld ).normalize();

const vecTargetToLight = lightPosition( light ).sub( lightTargetPosition( light ) ).normalize();
const vecTarget = lightTargetPosition( light ).normalize();
const vecRight = vecTarget.cross( vecTargetToLight ).normalize();
const vecUp = vecTargetToLight.cross( vecRight ).normalize();
const rotationAngle = vecLight.dot( vecRight ).atan( vecLight.dot( vecUp ) ).add( Math.PI * 2 );

const lightVector = this.getLightVector( builder );
const lightDirection = lightVector.normalize();
const angleCos = lightDirection.dot( lightTargetDirection( light ) );

const spotAttenuation = this.getSpotAttenuation( builder, angleCos );
const spotAttenuation = this.getSpotAttenuation( builder, angleCos, rotationAngle );

const lightDistance = lightVector.length();

Expand Down
Loading