three-environment-shadows 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -1,23 +1,23 @@
1
1
  # three-environment-shadows
2
2
 
3
+ Use environment as a source of light and _shadows_ in your Three.js scene.
4
+ Extracts light sources from environment map automatically - no need for any more of the manually set PointLights and SunLights 🤲
5
+
3
6
  ```js
4
7
  import * as Three from 'three';
5
8
  import { UltraHDRLoader } from 'three/addons/loaders/UltraHDRLoader.js';
6
- import { EnvironmentLightExtractor, EnvironmentShadowLights } from 'three-environment-shadows';
9
+ import { EnvironmentShadowLights } from 'three-environment-shadows';
7
10
 
8
11
  renderer.shadowMap.enabled = true;
9
12
 
10
13
  const hdri = await new UltraHDRLoader().loadAsync('./environment.jpg');
11
14
  hdri.mapping = Three.EquirectangularReflectionMapping;
12
15
 
13
- const result = await EnvironmentLightExtractor.extract(hdri);
14
-
15
16
  scene.background = hdri;
16
- scene.environment = result.texture;
17
+ scene.environment = hdri;
17
18
 
18
- const lights = new EnvironmentShadowLights(result.lights);
19
- lights.bounds.set(new Three.Vector3(0, 1, 0), 6);
20
- scene.add(lights);
19
+ const shadows = new EnvironmentShadowLights(scene);
20
+ shadows.bounds.set(new Three.Vector3(0, 1, 0), 6);
21
21
 
22
22
  mesh.castShadow = true;
23
23
  mesh.receiveShadow = true;
package/index.d.ts CHANGED
@@ -4,6 +4,8 @@ import {
4
4
  DirectionalLight,
5
5
  Group,
6
6
  Object3D,
7
+ Object3DEventMap,
8
+ Scene,
7
9
  Sphere,
8
10
  Texture,
9
11
  Vector3,
@@ -74,9 +76,19 @@ export class EnvironmentLightExtractor {
74
76
  }
75
77
 
76
78
  export interface EnvironmentShadowLightsOptions {
77
- /** The maximum number of shadow casting lights per source. Default `4`. */
79
+ /** The maximum number of lights. Default `2`. */
80
+ maxLights?: number;
81
+ /** Texels brighter than this factor times the mean radiance are part of a light. Default `8`. */
82
+ threshold?: number;
83
+ /** The minimum fraction of the total energy a light must emit. Default `0.05`. */
84
+ minEnergy?: number;
85
+ /** The minimum elevation (in radians) of a light, in the space of the environment map. Default `-Math.PI / 2`. */
86
+ minElevation?: number;
87
+ /** The width of the grid used to group bright texels into regions. Default `512`. */
88
+ resolution?: number;
89
+ /** The maximum number of shadow casting lights per light source. Default `4`. */
78
90
  samples?: number;
79
- /** The maximum number of shadow maps of all sources. Default `4`. */
91
+ /** The maximum number of shadow maps of all light sources. Default `4`. */
80
92
  maxShadowMaps?: number;
81
93
  /** The shadow map size of each light. Default `1024`. */
82
94
  mapSize?: number;
@@ -84,29 +96,45 @@ export interface EnvironmentShadowLightsOptions {
84
96
  relativeOccluderDistance?: number;
85
97
  }
86
98
 
99
+ export interface EnvironmentShadowLightsEventMap extends Object3DEventMap {
100
+ /** The light sources changed, see `EnvironmentShadowLights.result`. */
101
+ change: {};
102
+ }
103
+
87
104
  /**
88
- * Casts shadows from the light sources of an environment map.
105
+ * Casts shadows from the light sources of `scene.environment`.
89
106
  */
90
- export class EnvironmentShadowLights extends Group {
91
- constructor(sources?: ExtractedLight[], options?: EnvironmentShadowLightsOptions);
107
+ export class EnvironmentShadowLights extends Group<EnvironmentShadowLightsEventMap> {
108
+ /** Constructs the group and adds it to the scene. */
109
+ constructor(scene?: Scene | null, options?: EnvironmentShadowLightsOptions);
92
110
 
93
111
  readonly isEnvironmentShadowLights: true;
94
112
 
113
+ maxLights: number;
114
+ threshold: number;
115
+ minEnergy: number;
116
+ minElevation: number;
117
+ resolution: number;
95
118
  samples: number;
96
119
  maxShadowMaps: number;
97
120
  mapSize: number;
98
121
  relativeOccluderDistance: number;
99
122
 
123
+ /** Set to `true` to find the light sources again, e.g. after a change of the options. */
124
+ needsUpdate: boolean;
125
+
100
126
  /** The area that receives shadows, in world space. */
101
127
  bounds: Sphere;
102
128
 
129
+ /** The light sources of the environment map, or `null` before they are found. */
130
+ readonly result: Omit<ExtractionResult, 'texture'> | null;
131
+
103
132
  /** The target of all lights, at the center of the bounds. */
104
133
  readonly target: Object3D;
105
134
 
106
135
  /** The lights. The shadow casting lights come first. */
107
136
  readonly lights: DirectionalLight[];
108
137
 
109
- setLights(sources: ExtractedLight[]): this;
110
-
138
+ /** Puts the original environment map back, frees the GPU-related resources and removes the group. */
111
139
  dispose(): void;
112
140
  }
package/index.js CHANGED
@@ -2,6 +2,6 @@ import { Color, RGBAFormat, RGBFormat, FloatType, HalfFloatType, UnsignedByteTyp
2
2
 
3
3
  const _color=new Color,_rgb={r:0,g:0,b:0};class EnvironmentLightExtractor{static async extract(t,e={}){const{maxLights:n=2,threshold:o=8,minEnergy:r=.05,minElevation:a=-Math.PI/2,samples:i=4,resolution:l=512,removeLights:c=!0}=e,s=readSource(t),h=await runAnalysis({data:s.data,width:s.width,height:s.height,channels:s.channels,encoding:s.encoding,decode:s.decode,flipY:t.flipY,maxLights:n,thresholdFactor:o,minEnergy:r,minElevation:a,maxParts:i,resolution:l});h.clipped&&console.warn("THREE.EnvironmentLightExtractor: Light texels are clipped in the 8-bit texture. Light intensities are too low.");const f=t=>({direction:(new Vector3).fromArray(t.direction),color:(new Color).fromArray(t.color),intensity:t.intensity,coneAngle:t.coneAngle}),p=h.lights.map(t=>{const e=t.partitions.map(t=>t.map(f));return {...f(t),energyFraction:t.energyFraction,partitions:e}});let g=null;return c&&(g=s.createTexture(h.data),removeSelectedLights(s,g.image.data,h)),{lights:p,directionality:h.directionality,dominance:h.dominance,threshold:h.threshold,texture:g}}}function runAnalysis(t){if("undefined"==typeof Worker)return Promise.resolve(analyze(t));const e=`const analyze = ${analyze.toString()};\n\n self.onmessage = (event) => {\n const output = analyze(event.data);\n self.postMessage(output, [output.data.buffer, output.cellSelected.buffer]);\n };`,n=URL.createObjectURL(new Blob([e],{type:"application/javascript"}));return new Promise((e,o)=>{const r=new Worker(n),a=()=>{r.terminate(),URL.revokeObjectURL(n);};r.onmessage=t=>{a(),e(t.data);},r.onerror=t=>{a(),o(new Error("THREE.EnvironmentLightExtractor: "+t.message));},r.postMessage(t,[t.data.buffer]);})}function analyze(t){const{data:e,width:n,height:o,channels:r,flipY:a,maxLights:i,thresholdFactor:l,minEnergy:c,minElevation:s,maxParts:h,resolution:f}=t;let p=t.decode;if("half"===t.encoding){p=new Float32Array(65536);for(let t=0;t<65536;t++){const e=t>>10&31,n=1023&t;let o;o=0===e?n*Math.pow(2,-24):31===e?0===n?1/0:NaN:(1+n/1024)*Math.pow(2,e-15),p[t]=32768&t?-o:o;}}const g="byte"===t.encoding,d=[0,0,0];function u(t,o){const a=(o*n+t)*r;return null===p?(d[0]=e[a],d[1]=e[a+1],d[2]=e[a+2]):(d[0]=p[e[a]],d[1]=p[e[a+1]],d[2]=p[e[a+2]]),.2126*d[0]+.7152*d[1]+.0722*d[2]}const m=new Float64Array(n),y=new Float64Array(n);for(let t=0;t<n;t++){const e=2*((t+.5)/n-.5)*Math.PI;m[t]=Math.sin(e),y[t]=Math.cos(e);}const M=new Float64Array(o),w=new Float64Array(o),x=new Float64Array(o);for(let t=0;t<o;t++){const e=a?1-t/o:t/o,r=a?1-(t+1)/o:(t+1)/o,i=((e+r)/2-.5)*Math.PI;M[t]=Math.sin(i),w[t]=Math.cos(i),x[t]=2*Math.PI/n*Math.abs(Math.sin((r-.5)*Math.PI)-Math.sin((e-.5)*Math.PI));}const E=Math.max(1,Math.min(n,f)),F=Math.max(1,Math.round(E*o/n)),b=E*F,T=new Int32Array(n),S=new Int32Array(o);for(let t=0;t<n;t++)T[t]=Math.floor(t*E/n);for(let t=0;t<o;t++)S[t]=Math.floor(t*F/o);const R={data:e,directionality:0,dominance:0,threshold:0,gridWidth:E,gridHeight:F,cellSelected:new Uint8Array(b),lights:[],clipped:!1},A=new Float32Array(b);let L=0,v=0,B=0,H=0;for(let t=0;t<o;t++){const e=x[t],o=S[t]*E;for(let r=0;r<n;r++){const n=u(r,t);if(!(n>0&&n<1/0))continue;const a=n*e;L+=a,v+=a*w[t]*y[r],B+=a*M[t],H+=a*w[t]*m[r];const i=o+T[r];n>A[i]&&(A[i]=n);}}if(0===L)return R;R.directionality=Math.hypot(v,B,H)/L;const U=l*L/(4*Math.PI);R.threshold=U;const G=new Int32Array(b).fill(-1),C=[],I=[],_=Math.floor(E/2);for(let t=0;t<b;t++){if(G[t]>=0||A[t]<=U)continue;const e=[];for(G[t]=C.length,I.push(t);I.length>0;){const t=I.pop(),n=t%E,o=(t-n)/E;e.push(t);for(let t=-1;t<=1;t++)for(let e=-1;e<=1;e++){if(0===e&&0===t)continue;let r=n+e,a=o+t;(a<0||a>=F)&&(a=a<0?0:F-1,r+=_),r=(r%E+E)%E;const i=a*E+r;G[i]>=0||A[i]<=U||(G[i]=C.length,I.push(i));}}C.push(e);}const D=new Int32Array(b).fill(-1);let P=0;for(const t of C)for(const e of t)D[e]=P++;const z=new Float64Array(7*P),k=new Uint8Array(P);for(let t=0;t<o;t++){const o=x[t],a=S[t]*E;for(let i=0;i<n;i++){const l=D[a+T[i]];if(l<0)continue;const c=u(i,t);if(!(c>U&&c<1/0))continue;const s=1-U/c,h=c*s*o,f=7*l;if(z[f]+=d[0]*s*o,z[f+1]+=d[1]*s*o,z[f+2]+=d[2]*s*o,z[f+3]+=h,z[f+4]+=h*w[t]*y[i],z[f+5]+=h*M[t],z[f+6]+=h*w[t]*m[i],g){const o=(t*n+i)*r;255!==e[o]&&255!==e[o+1]&&255!==e[o+2]||(k[l]=1);}}}function j(t){const e={r:0,g:0,b:0,energy:0,x:0,y:0,z:0};for(const n of t){const t=7*D[n];e.r+=z[t],e.g+=z[t+1],e.b+=z[t+2],e.energy+=z[t+3],e.x+=z[t+4],e.y+=z[t+5],e.z+=z[t+6];}return e}function O(t,e){const{r:n,g:o,b:r,energy:a,x:i,y:l,z:c}=j(t),s=Math.hypot(i,l,c),h=Math.max(n,o,r),f=s/a;return {direction:s>0?[i/s,l/s,c/s]:e,color:[n/h,o/h,r/h],intensity:h,coneAngle:Math.acos(Math.min(1,Math.max(-1,2*f-1)))}}function Y(t){const{x:e,y:n,z:o}=j(t),r=Math.hypot(e,n,o),a=[e/r,n/r,o/r],i=Math.abs(a[1])<.99?[0,1,0]:[1,0,0],l=[i[1]*a[2]-i[2]*a[1],i[2]*a[0]-i[0]*a[2],i[0]*a[1]-i[1]*a[0]],c=Math.hypot(l[0],l[1],l[2]);l[0]/=c,l[1]/=c,l[2]/=c;const s=[a[1]*l[2]-a[2]*l[1],a[2]*l[0]-a[0]*l[2],a[0]*l[1]-a[1]*l[0]],h=[];let f=0,p=0,g=0;for(const e of t){const t=7*D[e],n=z[t+3],o=Math.hypot(z[t+4],z[t+5],z[t+6]),r=z[t+4]/o,a=z[t+5]/o,i=z[t+6]/o,c=r*l[0]+a*l[1]+i*l[2],d=r*s[0]+a*s[1]+i*s[2];h.push({cell:e,e:n,px:c,py:d}),f+=n,p+=n*c,g+=n*d;}p/=f,g/=f;let d=0,u=0,m=0;for(const t of h){const e=t.px-p,n=t.py-g;d+=t.e*e*e,u+=t.e*n*n,m+=t.e*e*n;}const y=.5*Math.atan2(2*m,d-u),M=Math.cos(y),w=Math.sin(y);for(const t of h)t.projection=t.px*M+t.py*w;h.sort((t,e)=>t.projection-e.projection);let x=0,E=0;for(;x<h.length-1&&E+h[x].e<=f/2;)E+=h[x++].e;return x=Math.min(h.length-1,Math.max(1,x)),[h.slice(0,x).map(t=>t.cell),h.slice(x).map(t=>t.cell)]}function N(t,e){const n=[[t]];for(;n.length<h;){const t=n[n.length-1];let e=-1,o=0;for(let n=0;n<t.length;n++){if(t[n].length<2)continue;const{energy:r,x:a,y:i,z:l}=j(t[n]),c=r-Math.hypot(a,i,l);c>o&&(e=n,o=c);}if(e<0)break;const r=t.slice();r.splice(e,1,...Y(t[e])),n.push(r);}return n.map(t=>t.map(t=>O(t,e)))}const W=C.map(t=>j(t).energy),q=C.map((t,e)=>e).sort((t,e)=>W[e]-W[t]),V=Math.sin(s);for(const t of q){if(R.lights.length>=i)break;const e=W[t]/L;if(e<c)break;const n=C[t],o=O(n,[0,1,0]);if(!(o.direction[1]<V)){o.energyFraction=e,o.partitions=N(n,o.direction),R.lights.push(o),R.dominance+=e;for(const t of n)R.cellSelected[t]=1,1===k[D[t]]&&(R.clipped=!0);}}return R}function removeSelectedLights(t,e,n){const{width:o,height:r}=t,{gridWidth:a,gridHeight:i,cellSelected:l,threshold:c}=n,s=t.reader(e),h=t.writer(e);for(let t=0;t<i;t++){const e=Math.ceil(t*r/i),n=Math.ceil((t+1)*r/i);for(let r=0;r<a;r++){if(0===l[t*a+r])continue;const i=Math.ceil(r*o/a),f=Math.ceil((r+1)*o/a);for(let t=e;t<n;t++)for(let e=i;e<f;e++){s(e,t,_rgb);const n=.2126*_rgb.r+.7152*_rgb.g+.0722*_rgb.b;if(!(n>c&&n<1/0))continue;const o=c/n;h(e,t,_rgb.r*o,_rgb.g*o,_rgb.b*o);}}}}function readSource(t){const e=t.image;if(t.isCubeTexture||t.isCompressedTexture||t.isRenderTargetTexture||t.isData3DTexture||t.isDataArrayTexture)throw new Error("THREE.EnvironmentLightExtractor: Only equirectangular data or image textures are supported.");if(null==e)throw new Error("THREE.EnvironmentLightExtractor: The texture has no image data.");let n,o,r,a,i,l;if(void 0!==e.data){if(o=e.width,r=e.height,n=e.data.slice(),i=t.type,l=t.format,l===RGBAFormat)a=4;else {if(l!==RGBFormat)throw new Error("THREE.EnvironmentLightExtractor: Only RGB and RGBA textures are supported.");a=3;}if(i!==FloatType&&i!==HalfFloatType&&i!==UnsignedByteType)throw new Error("THREE.EnvironmentLightExtractor: Only float, half float and unsigned byte textures are supported.")}else o=e.width,r=e.height,n=readImage(e,o,r),a=4,i=UnsignedByteType,l=RGBAFormat;let c=t.colorSpace;c!==SRGBColorSpace&&c!==LinearSRGBColorSpace&&c!==NoColorSpace&&(console.warn("THREE.EnvironmentLightExtractor: Unsupported color space. Texels are read as linear."),c=LinearSRGBColorSpace);const s=c===SRGBColorSpace;let h=null;if(i===UnsignedByteType){h=new Float32Array(256);for(let t=0;t<256;t++)h[t]=s?_color.setRGB(t/255,0,0,SRGBColorSpace).r:t/255;}return {data:n,width:o,height:r,channels:a,encoding:i===FloatType?"float":i===HalfFloatType?"half":"byte",decode:h,reader:function(t){return i===FloatType?(e,n,r)=>{const i=(n*o+e)*a;r.r=t[i],r.g=t[i+1],r.b=t[i+2];}:i===HalfFloatType?(e,n,r)=>{const i=(n*o+e)*a;r.r=DataUtils.fromHalfFloat(t[i]),r.g=DataUtils.fromHalfFloat(t[i+1]),r.b=DataUtils.fromHalfFloat(t[i+2]);}:(e,n,r)=>{const i=(n*o+e)*a;r.r=h[t[i]],r.g=h[t[i+1]],r.b=h[t[i+2]];}},writer:function(t){return i===FloatType?(e,n,r,i,l)=>{const c=(n*o+e)*a;t[c]=r,t[c+1]=i,t[c+2]=l;}:i===HalfFloatType?(e,n,r,i,l)=>{const c=(n*o+e)*a;t[c]=DataUtils.toHalfFloat(r),t[c+1]=DataUtils.toHalfFloat(i),t[c+2]=DataUtils.toHalfFloat(l);}:(e,n,r,i,l)=>{const c=(n*o+e)*a;s&&(_color.setRGB(r,i,l).getRGB(_rgb,SRGBColorSpace),r=_rgb.r,i=_rgb.g,l=_rgb.b),t[c]=Math.round(255*Math.min(1,r)),t[c+1]=Math.round(255*Math.min(1,i)),t[c+2]=Math.round(255*Math.min(1,l));}},createTexture:function(e){const n=new DataTexture(e,o,r,l,i);return n.mapping=t.mapping,n.wrapS=t.wrapS,n.wrapT=t.wrapT,n.magFilter=t.magFilter,n.minFilter=t.minFilter,n.anisotropy=t.anisotropy,n.generateMipmaps=t.generateMipmaps,n.unpackAlignment=t.unpackAlignment,n.colorSpace=t.colorSpace,n.flipY=t.flipY,n.needsUpdate=!0,n}}}function readImage(t,e,n){const o=Math.min(e,4096),r=Math.min(n,4096);let a;"undefined"!=typeof document?(a=document.createElement("canvas"),a.width=o,a.height=r):a=new OffscreenCanvas(o,r);const i=a.getContext("2d",{willReadFrequently:!0}),l=new Uint8Array(e*n*4);for(let a=0;a<n;a+=r)for(let c=0;c<e;c+=o){const s=Math.min(o,e-c),h=Math.min(r,n-a);i.clearRect(0,0,s,h),i.drawImage(t,c,a,s,h,0,0,s,h);const f=i.getImageData(0,0,s,h).data;for(let t=0;t<h;t++)l.set(f.subarray(t*s*4,(t+1)*s*4),4*((a+t)*e+c));}return l}
4
4
 
5
- const _direction=new Vector3,_position=new Vector3;class EnvironmentShadowLights extends Group{constructor(t=[],i={}){super(),this.isEnvironmentShadowLights=!0,this.type="EnvironmentShadowLights",this.samples=void 0!==i.samples?i.samples:4,this.maxShadowMaps=void 0!==i.maxShadowMaps?i.maxShadowMaps:4,this.mapSize=void 0!==i.mapSize?i.mapSize:1024,this.relativeOccluderDistance=void 0!==i.relativeOccluderDistance?i.relativeOccluderDistance:.1,this.bounds=new Sphere(new Vector3,10),this.target=new Object3D,this.target.matrixAutoUpdate=!1,this.target.matrixWorldAutoUpdate=!1,this.add(this.target),this.lights=[],this._sources=[],this._parts=[],this.setLights(t);}setLights(t){this._removeLights(),this._sources=t.slice();const i=t=>Math.tan(t)*this.relativeOccluderDistance*this.mapSize/2,s=t.map(()=>0);let e=this.maxShadowMaps;for(let i=0;i<t.length&&e>0;i++)s[i]=1,e--;for(let a=0;a<t.length&&e>0;a++){const o=t[a];if(0===s[a]||i(o.coneAngle)<=1)continue;const r=Math.min(Math.min(this.samples,o.partitions.length)-1,e);s[a]+=r,e-=r;}for(let e=0;e<t.length;e++){const a=s[e]>0,o=t[e].partitions[Math.max(1,s[e])-1];for(const t of o){const s=new DirectionalLight(t.color,t.intensity);s.matrixAutoUpdate=!1,s.matrixWorldAutoUpdate=!1,s.target=this.target,a&&(s.castShadow=!0,s.shadow.mapSize.setScalar(this.mapSize),s.shadow.radius=Math.min(8,Math.max(1,i(t.coneAngle)))),this.lights.push(s),this._parts.push(t),this.add(s);}}return this}updateMatrixWorld(t){super.updateMatrixWorld(t);let i=this;for(;null!==i.parent;)i=i.parent;const s=i.isScene?i.environmentRotation:null,e=i.isScene?i.environmentIntensity:1,{center:a,radius:o}=this.bounds;this.target.position.copy(a),this.target.matrix.makeTranslation(a),this.target.matrixWorld.copy(this.target.matrix);const r=2*o/this.mapSize;for(let t=0;t<this.lights.length;t++){const i=this.lights[t],n=this._parts[t];if(_direction.copy(n.direction),null!==s&&_direction.applyEuler(s),_position.copy(_direction).multiplyScalar(2*o).add(a),i.position.copy(_position),i.matrix.makeTranslation(_position),i.matrixWorld.copy(i.matrix),i.intensity=n.intensity*e,i.castShadow){const t=i.shadow.camera;t.left=-o,t.right=o,t.top=o,t.bottom=-o,t.near=o,t.far=3*o,t.updateProjectionMatrix(),i.shadow.normalBias=r;}}}copy(t,i=!0){if(super.copy(t,!1),this.samples=t.samples,this.maxShadowMaps=t.maxShadowMaps,this.mapSize=t.mapSize,this.relativeOccluderDistance=t.relativeOccluderDistance,this.bounds.copy(t.bounds),this.setLights(t._sources),i)for(const i of t.children)i===t.target||t.lights.includes(i)||this.add(i.clone());return this}dispose(){this._removeLights();}_removeLights(){for(const t of this.lights)this.remove(t),t.dispose();this.lights.length=0,this._parts.length=0;}}
5
+ const _direction=new Vector3,_position=new Vector3,getRoot=t=>{let i=t;for(;null!==i.parent;)i=i.parent;return i},isLoaded=t=>{const i=t.image;return null!=i&&(!!Array.isArray(i)||i.width>0&&i.height>0&&(void 0===i.data||i.data.length>0))};class EnvironmentShadowLights extends Group{constructor(t=null,i={}){super(),this.isEnvironmentShadowLights=!0,this.type="EnvironmentShadowLights",this.maxLights=i.maxLights??2,this.threshold=i.threshold??8,this.minEnergy=i.minEnergy??.05,this.minElevation=i.minElevation??-Math.PI/2,this.resolution=i.resolution??512,this.samples=i.samples??4,this.maxShadowMaps=i.maxShadowMaps??4,this.mapSize=i.mapSize??1024,this.relativeOccluderDistance=i.relativeOccluderDistance??.1,this.needsUpdate=!1,this.bounds=new Sphere(new Vector3,10),this.result=null,this.target=new Object3D,this.target.matrixAutoUpdate=!1,this.target.matrixWorldAutoUpdate=!1,this.add(this.target),this.lights=[],this._parts=[],this._source=null,this._sourceVersion=-1,this._residual=null,this._scene=null,this._request=null,this.addEventListener("removed",()=>this._restoreEnvironment()),null!==t&&t.add(this);}updateMatrixWorld(t){super.updateMatrixWorld(t);const i=getRoot(this);i.isScene&&this._syncEnvironment(i);const s=i.isScene?i.environmentRotation:null,e=i.isScene?i.environmentIntensity:1,{center:n,radius:r}=this.bounds;this.target.position.copy(n),this.target.matrix.makeTranslation(n),this.target.matrixWorld.copy(this.target.matrix);const o=2*r/this.mapSize;for(let t=0;t<this.lights.length;t++){const i=this.lights[t],h=this._parts[t];if(_direction.copy(h.direction),null!==s&&_direction.applyEuler(s),_position.copy(_direction).multiplyScalar(2*r).add(n),i.position.copy(_position),i.matrix.makeTranslation(_position),i.matrixWorld.copy(i.matrix),i.intensity=h.intensity*e,i.castShadow){const t=i.shadow.camera;t.left=-r,t.right=r,t.top=r,t.bottom=-r,t.near=r,t.far=3*r,t.updateProjectionMatrix(),i.shadow.normalBias=o;}}}copy(t,i=!0){if(super.copy(t,!1),this.maxLights=t.maxLights,this.threshold=t.threshold,this.minEnergy=t.minEnergy,this.minElevation=t.minElevation,this.resolution=t.resolution,this.samples=t.samples,this.maxShadowMaps=t.maxShadowMaps,this.mapSize=t.mapSize,this.relativeOccluderDistance=t.relativeOccluderDistance,this.bounds.copy(t.bounds),i)for(const i of t.children)i===t.target||t.lights.includes(i)||this.add(i.clone());return this}dispose(){this._restoreEnvironment(),this._clear(),this.removeFromParent();}_syncEnvironment(t){let i=t.environment;if(null!==i&&i===this._residual){if(!this.needsUpdate&&this._source.version===this._sourceVersion)return;i=this._source;}else if(null!==i&&i===this._source&&null!==this._residual&&!this.needsUpdate&&i.version===this._sourceVersion)return t.environment=this._residual,void(this._scene=t);const s=this._request;i===this._source||null!==s&&i===s.texture||this._clear(),null!==i&&isLoaded(i)&&(null===s||s.texture!==i||s.version!==i.version||this.needsUpdate)&&this._extract(t,i);}_extract(t,i){const s={texture:i,version:i.version};this.needsUpdate=!1,this._request=s,EnvironmentLightExtractor.extract(i,{maxLights:this.maxLights,threshold:this.threshold,minEnergy:this.minEnergy,minElevation:this.minElevation,samples:this.samples,resolution:this.resolution}).then(i=>{this._request===s?(this._request=null,this._apply(t,s,i)):i.texture.dispose();},t=>{this._request===s&&console.warn(t.message);});}_apply(t,i,s){const e=this._residual,n=t.environment;this._source=i.texture,this._sourceVersion=i.version,this._residual=s.texture,this.result={lights:s.lights,directionality:s.directionality,dominance:s.dominance,threshold:s.threshold},this._setLights(s.lights),getRoot(this)!==t||n!==this._source&&n!==e||(t.environment=this._residual,this._scene=t),null!==e&&e.dispose(),this.dispatchEvent({type:"change"});}_restoreEnvironment(){const t=this._scene;null!==t&&t.environment===this._residual&&(t.environment=this._source),this._scene=null;}_clear(){this._request=null,null!==this._residual&&this._residual.dispose(),this._source=null,this._sourceVersion=-1,this._residual=null,this._scene=null,null===this.result&&0===this.lights.length||(this.result=null,this._setLights([]),this.dispatchEvent({type:"change"}));}_setLights(t){for(const t of this.lights)this.remove(t),t.dispose();this.lights.length=0,this._parts.length=0;const i=t=>Math.tan(t)*this.relativeOccluderDistance*this.mapSize/2,s=t.map(()=>0);let e=this.maxShadowMaps;for(let i=0;i<t.length&&e>0;i++)s[i]=1,e--;for(let n=0;n<t.length&&e>0;n++){const r=t[n];if(0===s[n]||i(r.coneAngle)<=1)continue;const o=Math.min(Math.min(this.samples,r.partitions.length)-1,e);s[n]+=o,e-=o;}for(let e=0;e<t.length;e++){const n=s[e]>0,r=t[e].partitions[Math.max(1,s[e])-1];for(const t of r){const s=new DirectionalLight(t.color,t.intensity);s.matrixAutoUpdate=!1,s.matrixWorldAutoUpdate=!1,s.target=this.target,n&&(s.castShadow=!0,s.shadow.mapSize.setScalar(this.mapSize),s.shadow.radius=Math.min(8,Math.max(1,i(t.coneAngle)))),this.lights.push(s),this._parts.push(t),this.add(s);}}}}
6
6
 
7
7
  export { EnvironmentLightExtractor, EnvironmentShadowLights };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "three-environment-shadows",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Use HDR / EXR / UltraHDR environment maps as source of light and shadows in Three.js.",
5
5
  "type": "module",
6
6
  "main": "./index.js",