@navaramap/three-csm 0.0.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.
@@ -0,0 +1,306 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("three")):typeof define==`function`&&define.amd?define([`exports`,`three`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.NavaraThreeCSM={},e.three))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var n=new t.Color(16777215),r=class extends t.Object3D{mainLight;cascadedLights;direction=new t.Vector3(-1,-1,-1);constructor(e,n){super();let r=new t.DirectionalLight(e,n);r.castShadow=!0,this.mainLight=r,this.cascadedLights=[r],this.add(r),this.add(r.target)}setCount(e){e=Math.max(1,e);let t=this.cascadedLights.length;if(e===t)return this;if(e>t){let r=this.mainLight;for(let i=t;i<e;++i){let e=r.clone();e.color.copy(n),this.add(e),this.add(e.target),this.cascadedLights[i]=e}}else for(let n=e;n<t;++n){let e=this.cascadedLights[n];e.dispose(),this.remove(e.target),this.remove(e)}return this.cascadedLights.length=e,this}dispose(){for(let e=0;e<this.cascadedLights.length;++e)this.cascadedLights[e].dispose()}copy(e,t){super.copy(e,!1),this.cascadedLights.length=0,this.clear();for(let t of e.cascadedLights){let e=t.clone();this.add(e),this.add(e.target),this.cascadedLights.push(e)}return this}},i=`Invariant failed`;function a(e,t){if(!e)throw Error(i)}var o=class e{near=[new t.Vector3,new t.Vector3,new t.Vector3,new t.Vector3];far=[new t.Vector3,new t.Vector3,new t.Vector3,new t.Vector3];constructor(e,t){e!=null&&t!=null&&this.setFromCamera(e,t)}clone(){return new e().copy(this)}copy(e){for(let t=0;t<4;++t)this.near[t].copy(e.near[t]),this.far[t].copy(e.far[t]);return this}setFromCamera(e,t){let n=e.isOrthographicCamera===!0,r=e.projectionMatrixInverse;this.near[0].set(1,1,-1),this.near[1].set(1,-1,-1),this.near[2].set(-1,-1,-1),this.near[3].set(-1,1,-1);for(let e=0;e<4;++e)this.near[e].applyMatrix4(r);this.far[0].set(1,1,1),this.far[1].set(1,-1,1),this.far[2].set(-1,-1,1),this.far[3].set(-1,1,1);for(let e=0;e<4;++e){let i=this.far[e];i.applyMatrix4(r);let a=Math.abs(i.z);n?i.z*=Math.min(t/a,1):i.multiplyScalar(Math.min(t/a,1))}return this}split(t,n=[]){for(let r=0;r<t.length;++r){let i=n[r]??=new e;if(r===0)for(let e=0;e<4;++e)i.near[e].copy(this.near[e]);else for(let e=0;e<4;++e)i.near[e].lerpVectors(this.near[e],this.far[e],t[r-1]);if(r===t.length-1)for(let e=0;e<4;++e)i.far[e].copy(this.far[e]);else for(let e=0;e<4;++e)i.far[e].lerpVectors(this.near[e],this.far[e],t[r])}return n.length=t.length,n}applyMatrix4(e){for(let t=0;t<4;++t)this.near[t].applyMatrix4(e),this.far[t].applyMatrix4(e);return this}},s=`// Based on the following work:
2
+ // https://github.com/StrandedKitty/three-csm/tree/master/src
3
+ // https://github.com/mrdoob/three.js/tree/r169/examples/jsm/csm
4
+
5
+ void cascadedFadedLights() {
6
+ directionalLight = directionalLights[0];
7
+ getDirectionalLightInfo(directionalLight, directLight);
8
+
9
+ float clipDepth = vViewPosition.z / (csmFar - csmNear);
10
+ bool lastCascade;
11
+ vec2 cascade;
12
+ float cascadeCenter;
13
+ float closestEdge;
14
+ float margin;
15
+ float shadow = 1.0;
16
+ float nextShadow = 1.0;
17
+
18
+ #pragma unroll_loop_start
19
+ for (int i = 0; i < NUM_DIR_LIGHTS; i++) {
20
+ #if UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS
21
+ #if UNROLLED_LOOP_INDEX < CSM_CASCADE_COUNT
22
+
23
+ lastCascade = UNROLLED_LOOP_INDEX == CSM_CASCADE_COUNT - 1;
24
+ cascade = csmCascades[i];
25
+ cascadeCenter = (cascade.x + cascade.y) * 0.5;
26
+ closestEdge = clipDepth < cascadeCenter ? cascade.x : cascade.y;
27
+ margin = 0.25 * pow(closestEdge, 2.0);
28
+ cascade += margin * vec2(-0.5, 0.5);
29
+
30
+ if (clipDepth >= cascade.x && (clipDepth < cascade.y || lastCascade)) {
31
+ directionalLightShadow = directionalLightShadows[i];
32
+ float prevShadow = nextShadow;
33
+ nextShadow =
34
+ directLight.visible && receiveShadow
35
+ ? getShadow(
36
+ directionalShadowMap[i],
37
+ directionalLightShadow.shadowMapSize,
38
+ directionalLightShadow.shadowIntensity,
39
+ directionalLightShadow.shadowBias,
40
+ directionalLightShadow.shadowRadius,
41
+ vDirectionalShadowCoord[i]
42
+ )
43
+ : 1.0;
44
+
45
+ shadow = mix(
46
+ prevShadow,
47
+ nextShadow,
48
+ saturate(min(clipDepth - cascade.x, cascade.y - clipDepth) / margin)
49
+ );
50
+ }
51
+ #endif // UNROLLED_LOOP_INDEX < CSM_CASCADE_COUNT
52
+ #endif // UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS
53
+ }
54
+ #pragma unroll_loop_end
55
+
56
+ // No loop here: all cascaded lights are in fact one light only.
57
+ directLight.color *= shadow;
58
+ RE_Direct(
59
+ directLight,
60
+ geometryPosition,
61
+ geometryNormal,
62
+ geometryViewDir,
63
+ geometryClearcoatNormal,
64
+ material,
65
+ reflectedLight
66
+ );
67
+
68
+ // The rest of directional lights.
69
+ #pragma unroll_loop_start
70
+ for (int i = 0; i < NUM_DIR_LIGHTS; i++) {
71
+ #if UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS
72
+ #if UNROLLED_LOOP_INDEX >= CSM_CASCADE_COUNT
73
+ directionalLight = directionalLights[i];
74
+ getDirectionalLightInfo(directionalLight, directLight);
75
+
76
+ directionalLightShadow = directionalLightShadows[i];
77
+ directLight.color *=
78
+ directLight.visible && receiveShadow
79
+ ? getShadow(
80
+ directionalShadowMap[i],
81
+ directionalLightShadow.shadowMapSize,
82
+ directionalLightShadow.shadowIntensity,
83
+ directionalLightShadow.shadowBias,
84
+ directionalLightShadow.shadowRadius,
85
+ vDirectionalShadowCoord[i]
86
+ )
87
+ : 1.0;
88
+
89
+ RE_Direct(
90
+ directLight,
91
+ geometryPosition,
92
+ geometryNormal,
93
+ geometryViewDir,
94
+ geometryClearcoatNormal,
95
+ material,
96
+ reflectedLight
97
+ );
98
+ #endif // UNROLLED_LOOP_INDEX >= CSM_CASCADE_COUNT
99
+ #endif // UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS
100
+ }
101
+ #pragma unroll_loop_end
102
+ }
103
+ `,c=`// Based on the following work:
104
+ // https://github.com/StrandedKitty/three-csm/tree/master/src
105
+ // https://github.com/mrdoob/three.js/tree/r169/examples/jsm/csm
106
+
107
+ void cascadedLights() {
108
+ directionalLight = directionalLights[0];
109
+ getDirectionalLightInfo(directionalLight, directLight);
110
+
111
+ float clipDepth = vViewPosition.z / (csmFar - csmNear);
112
+ vec2 cascade;
113
+
114
+ #pragma unroll_loop_start
115
+ for (int i = 0; i < NUM_DIR_LIGHTS; i++) {
116
+ #if UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS
117
+ #if UNROLLED_LOOP_INDEX < CSM_CASCADE_COUNT
118
+ cascade = csmCascades[i];
119
+ directionalLightShadow = directionalLightShadows[i];
120
+ if (clipDepth >= cascade.x && clipDepth < cascade.y) {
121
+ directLight.color *=
122
+ directLight.visible && receiveShadow
123
+ ? getShadow(
124
+ directionalShadowMap[i],
125
+ directionalLightShadow.shadowMapSize,
126
+ directionalLightShadow.shadowIntensity,
127
+ directionalLightShadow.shadowBias,
128
+ directionalLightShadow.shadowRadius,
129
+ vDirectionalShadowCoord[i]
130
+ )
131
+ : 1.0;
132
+ }
133
+ #endif // UNROLLED_LOOP_INDEX < CSM_CASCADE_COUNT
134
+ #endif // UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS
135
+ }
136
+ #pragma unroll_loop_end
137
+
138
+ // No loop here: all cascaded lights are in fact one light only.
139
+ RE_Direct(
140
+ directLight,
141
+ geometryPosition,
142
+ geometryNormal,
143
+ geometryViewDir,
144
+ geometryClearcoatNormal,
145
+ material,
146
+ reflectedLight
147
+ );
148
+
149
+ // The rest of directional lights.
150
+ #pragma unroll_loop_start
151
+ for (int i = 0; i < NUM_DIR_LIGHTS; i++) {
152
+ #if UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS
153
+ #if UNROLLED_LOOP_INDEX >= CSM_CASCADE_COUNT
154
+ directionalLight = directionalLights[i];
155
+ getDirectionalLightInfo(directionalLight, directLight);
156
+
157
+ directionalLightShadow = directionalLightShadows[i];
158
+ directLight.color *=
159
+ directLight.visible && receiveShadow
160
+ ? getShadow(
161
+ directionalShadowMap[i],
162
+ directionalLightShadow.shadowMapSize,
163
+ directionalLightShadow.shadowIntensity,
164
+ directionalLightShadow.shadowBias,
165
+ directionalLightShadow.shadowRadius,
166
+ vDirectionalShadowCoord[i]
167
+ )
168
+ : 1.0;
169
+
170
+ RE_Direct(
171
+ directLight,
172
+ geometryPosition,
173
+ geometryNormal,
174
+ geometryViewDir,
175
+ geometryClearcoatNormal,
176
+ material,
177
+ reflectedLight
178
+ );
179
+ #endif // UNROLLED_LOOP_INDEX >= CSM_CASCADE_COUNT
180
+ #endif // UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS
181
+ }
182
+ #pragma unroll_loop_end
183
+ }
184
+ `,l=`// Based on the following work:
185
+ // https://github.com/StrandedKitty/three-csm/tree/master/src
186
+ // https://github.com/mrdoob/three.js/tree/r169/examples/jsm/csm
187
+
188
+ void cascadedLightsWithoutShadows() {
189
+ // No loop here: all cascaded lights are in fact one light only.
190
+ getDirectionalLightInfo(directionalLights[0], directLight);
191
+ RE_Direct(
192
+ directLight,
193
+ geometryPosition,
194
+ geometryNormal,
195
+ geometryViewDir,
196
+ geometryClearcoatNormal,
197
+ material,
198
+ reflectedLight
199
+ );
200
+
201
+ // The rest of directional lights.
202
+ #pragma unroll_loop_start
203
+ for (int i = CSM_CASCADE_COUNT; i < NUM_DIR_LIGHTS; i++) {
204
+ directionalLight = directionalLights[i];
205
+ getDirectionalLightInfo(directionalLight, directLight);
206
+
207
+ RE_Direct(
208
+ directLight,
209
+ geometryPosition,
210
+ geometryNormal,
211
+ geometryViewDir,
212
+ geometryClearcoatNormal,
213
+ material,
214
+ reflectedLight
215
+ );
216
+ }
217
+ #pragma unroll_loop_end
218
+ }
219
+ `,u=`// Default shader.
220
+ // See: https://github.com/mrdoob/three.js/blob/r169/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js#L136
221
+
222
+ void defaultLights() {
223
+ #pragma unroll_loop_start
224
+ for (int i = 0; i < NUM_DIR_LIGHTS; i++) {
225
+ directionalLight = directionalLights[i];
226
+ getDirectionalLightInfo(directionalLight, directLight);
227
+
228
+ #if defined(USE_SHADOWMAP) && UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS
229
+ directionalLightShadow = directionalLightShadows[i];
230
+ directLight.color *=
231
+ directLight.visible && receiveShadow
232
+ ? getShadow(
233
+ directionalShadowMap[i],
234
+ directionalLightShadow.shadowMapSize,
235
+ directionalLightShadow.shadowIntensity,
236
+ directionalLightShadow.shadowBias,
237
+ directionalLightShadow.shadowRadius,
238
+ vDirectionalShadowCoord[i]
239
+ )
240
+ : 1.0;
241
+ #endif
242
+
243
+ RE_Direct(
244
+ directLight,
245
+ geometryPosition,
246
+ geometryNormal,
247
+ geometryViewDir,
248
+ geometryClearcoatNormal,
249
+ material,
250
+ reflectedLight
251
+ );
252
+ }
253
+ #pragma unroll_loop_end
254
+ }
255
+ `,d=`void nonShadowCastingLights() {
256
+ #pragma unroll_loop_start
257
+ for (int i = NUM_DIR_LIGHT_SHADOWS; i < NUM_DIR_LIGHTS; i++) {
258
+ directionalLight = directionalLights[i];
259
+ getDirectionalLightInfo(directionalLight, directLight);
260
+
261
+ RE_Direct(
262
+ directLight,
263
+ geometryPosition,
264
+ geometryNormal,
265
+ geometryViewDir,
266
+ geometryClearcoatNormal,
267
+ material,
268
+ reflectedLight
269
+ );
270
+ }
271
+ #pragma unroll_loop_end
272
+ }
273
+ `,f=`
274
+ #include <lights_pars_begin>
275
+
276
+ #ifdef CSM
277
+ uniform vec2 csmCascades[CSM_CASCADE_COUNT];
278
+ uniform float csmNear;
279
+ uniform float csmFar;
280
+ #endif // CSM
281
+ `;function p(e){return e.replace(/^\s*void\s+\w+\s*\(\s*\)\s*/m,``)}var m=`
282
+ #if NUM_DIR_LIGHTS > 0 && defined(RE_Direct)
283
+ DirectionalLight directionalLight;
284
+ #if defined(USE_SHADOWMAP) && NUM_DIR_LIGHT_SHADOWS > 0
285
+ DirectionalLightShadow directionalLightShadow;
286
+ #endif
287
+
288
+ #ifdef CSM
289
+ #ifdef USE_SHADOWMAP
290
+ #ifdef CSM_FADE
291
+ ${p(s)}
292
+ #else
293
+ ${p(c)}
294
+ #endif // CSM_FADE
295
+ #elif NUM_DIR_LIGHT_SHADOWS > 0
296
+ ${p(l)}
297
+ #endif // USE_SHADOWMAP
298
+
299
+ #if NUM_DIR_LIGHTS > NUM_DIR_LIGHT_SHADOWS
300
+ ${p(d)}
301
+ #endif
302
+ #else
303
+ ${p(u)}
304
+ #endif // CSM
305
+ #endif // NUM_DIR_LIGHTS > 0 && defined(RE_Direct)
306
+ `;function h({source:e,startLine:t,endLine:n,replacement:r}){let i=`\n${t}\n`,a=e.indexOf(i);if(a<0)return e;let o=`\n${n}\n`,s=e.indexOf(o,a+i.length);return s<0?e:e.slice(0,a+1)+r+e.slice(s+o.length-1)}function g(e){return e.replace(`#include <lights_pars_begin>`,f).replace(`#include <lights_fragment_begin>`,h({source:t.ShaderChunk.lights_fragment_begin,startLine:`#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )`,endLine:`#endif`,replacement:m}))}var _=class{map=new Map;setup(e,t){if(this.map.has(e))return e;e.userData.defines??={},e.userData.defines.CSM=1,e.userData.defines.CSM_CASCADE_COUNT=t.cascadeCount,t.fade&&(e.userData.defines.CSM_FADE=1);let n={};this.map.set(e,n);let r=e=>{n.parameters=e;let r=t.cascades,i=t.mainCamera.near,a=Math.min(t.mainCamera.far,t.far),o=e.uniforms;o.csmCascades={value:r},o.csmNear={value:i},o.csmFar={value:a},e.fragmentShader=g(e.fragmentShader)},i=e.onBeforeCompile.bind(e),a=e.customProgramCacheKey.bind(e);Object.prototype.hasOwnProperty.call(e,`hasOwnProperty`)&&(n.originalHandler=i),e.customProgramCacheKey=()=>i.toString()+a(),e.onBeforeCompile=(...t)=>{let[n]=t;n.defines??={},Object.assign(n.defines,e.userData.defines),i.apply(e,t),r(...t)};let o=()=>{this.map.delete(e),e.removeEventListener(`dispose`,o)};return e.addEventListener(`dispose`,o),e}update(e){let t=e.cascadeCount,n=e.fade,r=e.cascades,i=e.mainCamera.near,a=Math.min(e.mainCamera.far,e.far);for(let[e,o]of this.map.entries()){let s=e.userData.defines;if(s!=null){let r=!1;s.CSM_FADE!=null!==n&&(n?s.CSM_FADE=1:delete s.CSM_FADE,r=!0),s.CSM_CASCADE_COUNT!==t&&(s.CSM_CASCADE_COUNT=t,r=!0),r&&(e.needsUpdate=!0)}let c=o.parameters?.uniforms;c!=null&&(c.csmCascades.value=r,c.csmNear.value=i,c.csmFar.value=a)}}rollback(e){let t=this.map.get(e);return t==null?e:this.rollbackToState(e,t)}rollbackToState(e,{parameters:t,originalHandler:n}){return t!=null&&(delete t.uniforms.csmCascades,delete t.uniforms.csmNear,delete t.uniforms.csmFar),n==null?delete e.onBeforeCompile:e.onBeforeCompile=n,e.userData.defines!=null&&(delete e.userData.defines.CSM,delete e.userData.defines.CSM_CASCADE_COUNT,delete e.userData.defines.CSM_FADE),e.needsUpdate=!0,this.map.delete(e),e}dispose(){for(let[e,t]of this.map)this.rollbackToState(e,t);this.map.clear()}};Math.PI/180,180/Math.PI;function v(e,t,n){return(1-n)*e+n*t}var y={uniform:(e,t,n,r,i=[])=>{for(let r=0;r<e;++r)i[r]=(t+(n-t)*(r+1)/e)/n;return i.length=e,i},logarithmic:(e,t,n,r,i=[])=>{for(let r=0;r<e;++r)i[r]=t*(n/t)**((r+1)/e)/n;return i.length=e,i},practical:(e,t,n,r=.5,i=[])=>{for(let a=0;a<e;++a)i[a]=v((t+(n-t)*(a+1)/e)/n,t*(n/t)**((a+1)/e)/n,r);return i.length=e,i}},b=y;function x(e,t,n,r,i,a=[]){return y[e](t,n,r,i,a)}var S=new t.Vector3,C=new t.Vector3,w=new t.Matrix4,T=new t.Matrix4,E=new o,D=new t.Box3,O={cascadeCount:4,mapSize:2048,far:5e3,mode:`practical`,lambda:.5,margin:200,fade:!0,disableLastCascadeCutoff:!1},k=class{mainCamera;needsUpdateFrusta=!0;directionalLights=new r;materialStates=new _;mainFrustum=new o;cascadedFrusta=[];splits=[];cascades=[];_far;_mode;_lambda;_margin;_fade;_disableLastCascadeCutoff;constructor(e,t){this.mainCamera=e;let{cascadeCount:n,mapSize:r,far:i,mode:a,lambda:o,margin:s,fade:c,disableLastCascadeCutoff:l}={...O,...t};this._far=i,this._mode=a,this._lambda=o,this._margin=s,this._fade=c,this._disableLastCascadeCutoff=l,this.cascadeCount=n,this.mapSize=r}dispose(){this.materialStates.dispose(),this.directionalLights.dispose()}setupMaterial(e){return this.materialStates.setup(e,this)}rollbackMaterial(e){return this.materialStates.rollback(e)}setupMaterials(e){for(let t=0;t<e.length;++t)this.materialStates.setup(e[t],this);return()=>{for(let t=0;t<e.length;++t)this.materialStates.rollback(e[t])}}updateCascades(){let e=this.cascadeCount,n=this.splits;x(this.mode,e,this.mainCamera.near,Math.min(this.mainCamera.far,this.far),this.lambda,n),this.mainFrustum.setFromCamera(this.mainCamera,this.far),this.mainFrustum.split(n,this.cascadedFrusta);let r=this.cascades;for(let i=0;i<e;++i)(r[i]??(r[i]=new t.Vector2)).set(n[i-1]??0,n[i]??0);this.disableLastCascadeCutoff&&(r[e-1].y=1/0),r.length=e}getFrustumRadius(e){let t=e.near,n=e.far,r=Math.max(n[0].distanceTo(n[2]),n[0].distanceTo(t[2]));if(this.fade){let e=this.mainCamera,t=e.near,i=Math.min(e.far,this.far),a=n[0].z/(i-t);r+=.25*a**2*(i-t)}return r*.5}updateShadowBounds(){let e=this.cascadedFrusta,t=this.directionalLights.cascadedLights;a(e.length===t.length);for(let n=0;n<e.length;++n){let e=this.getFrustumRadius(this.cascadedFrusta[n]),r=t[n].shadow.camera;r.left=-e,r.right=e,r.top=e,r.bottom=-e,r.near=0,r.far=e*2+this.margin,r.updateProjectionMatrix()}}updateFrusta(){this.updateCascades(),this.updateShadowBounds(),this.materialStates.update(this)}update(){this.needsUpdateFrusta&&(this.needsUpdateFrusta=!1,this.updateFrusta());let e=this.directionalLights,n=S.copy(e.direction).normalize(),r=w.lookAt(new t.Vector3,n,t.Object3D.DEFAULT_UP),i=T.multiplyMatrices(T.copy(r).invert(),this.mainCamera.matrixWorld),a=this.margin,o=this.mapSize,s=this.cascadedFrusta,c=e.cascadedLights;for(let e=0;e<s.length;++e){let{near:t,far:l}=E.copy(s[e]).applyMatrix4(i),u=D.makeEmpty();for(let e=0;e<4;e++)u.expandByPoint(t[e]),u.expandByPoint(l[e]);let d=u.getCenter(C);d.z=u.max.z+a;let f=c[e],{left:p,right:m,top:h,bottom:g}=f.shadow.camera,_=(m-p)/o,v=(h-g)/o;d.x=Math.round(d.x/_)*_,d.y=Math.round(d.y/v)*v,d.applyMatrix4(r),f.position.copy(d),f.target.position.copy(d),f.target.position.add(n)}}get cascadeCount(){return this.directionalLights.cascadedLights.length}set cascadeCount(e){e!==this.cascadeCount&&(this.directionalLights.setCount(e),this.needsUpdateFrusta=!0)}get mapSize(){return this.directionalLights.mainLight.shadow.mapSize.width}set mapSize(e){if(e!==this.mapSize){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n){let r=t[n].shadow;r.mapSize.width=e,r.mapSize.height=e,r.map!=null&&(r.map.dispose(),r.map=null)}}}get far(){return this._far}set far(e){e!==this._far&&(this._far=e,this.needsUpdateFrusta=!0)}get mode(){return this._mode}set mode(e){e!==this._mode&&(this._mode=e,this.needsUpdateFrusta=!0)}get lambda(){return this._lambda}set lambda(e){e!==this._lambda&&(this._lambda=e,this.needsUpdateFrusta=!0)}get margin(){return this._margin}set margin(e){e!==this._margin&&(this._margin=e,this.needsUpdateFrusta=!0)}get fade(){return this._fade}set fade(e){e!==this._fade&&(this._fade=e,this.needsUpdateFrusta=!0)}get disableLastCascadeCutoff(){return this._disableLastCascadeCutoff}set disableLastCascadeCutoff(e){e!==this._disableLastCascadeCutoff&&(this._disableLastCascadeCutoff=e,this.needsUpdateFrusta=!0)}get intensity(){return this.directionalLights.mainLight.intensity}set intensity(e){if(e!==this.intensity){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].intensity=e}}get shadowIntensity(){return this.directionalLights.mainLight.shadow.intensity}set shadowIntensity(e){if(e!==this.shadowIntensity){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.intensity=e}}get color(){return this.directionalLights.mainLight.color}set color(e){e!==this.color&&this.directionalLights.mainLight.color.copy(e)}get bias(){return this.directionalLights.mainLight.shadow.bias}set bias(e){if(e!==this.bias){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.bias=e}}get normalBias(){return this.directionalLights.mainLight.shadow.normalBias}set normalBias(e){if(e!==this.normalBias){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.normalBias=e}}get radius(){return this.directionalLights.mainLight.shadow.radius}set radius(e){if(e!==this.radius){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.radius=e}}get blurSamples(){return this.directionalLights.mainLight.shadow.blurSamples}set blurSamples(e){if(e!==this.blurSamples){let t=this.directionalLights.cascadedLights;for(let n=0;n<t.length;++n)t[n].shadow.blurSamples=e}}};e.CSMHelper=class extends t.Group{csm;displayFrustum=!0;displayPlanes=!0;displayShadowBounds=!0;frustumLines;cascadeLines=[];cascadePlanes=[];shadowLines=[];constructor(e){super(),this.csm=e;let n=new Uint16Array([0,1,1,2,2,3,3,0,4,5,5,6,6,7,7,4,0,4,1,5,2,6,3,7]),r=new Float32Array(24),i=new t.BufferGeometry;i.setIndex(new t.BufferAttribute(n,1)),i.setAttribute(`position`,new t.BufferAttribute(r,3,!1));let a=new t.LineSegments(i,new t.LineBasicMaterial);this.add(a),this.frustumLines=a}updateVisibility(){let e=this.displayFrustum,t=this.displayPlanes,n=this.displayShadowBounds,r=this.frustumLines,i=this.cascadeLines,a=this.cascadePlanes,o=this.shadowLines;for(let r=0,s=i.length;r<s;r++){let s=i[r],c=a[r],l=o[r];s.visible=e,c.visible=e&&t,l.visible=n}r.visible=e}update(){let e=this.csm,n=e.mainCamera,r=e.cascadeCount,i=e.mainFrustum,a=e.cascadedFrusta,o=e.directionalLights.cascadedLights,s=this.frustumLines.geometry.getAttribute(`position`),c=this.cascadeLines,l=this.cascadePlanes,u=this.shadowLines;for(this.position.copy(n.position),this.quaternion.copy(n.quaternion),this.scale.copy(n.scale),this.updateMatrixWorld(!0);c.length>r;){let e=c.pop(),t=l.pop(),n=u.pop();e&&this.remove(e),t&&this.remove(t),n&&this.remove(n)}for(;c.length<r;){let e=new t.Box3Helper(new t.Box3,new t.Color(16777215)),n=new t.MeshBasicMaterial({transparent:!0,opacity:.1,depthWrite:!1,side:t.DoubleSide}),r=new t.Mesh(new t.PlaneGeometry,n),i=new t.Group,a=new t.Box3Helper(new t.Box3,new t.Color(16776960));i.add(a),this.add(e),this.add(r),this.add(i),c.push(e),l.push(r),u.push(i)}for(let e=0;e<r;e++){let t=a[e],n=o[e].shadow.camera,r=t.far,i=c[e],s=l[e],d=u[e],f=d.children[0];i.box.min.copy(r[2]),i.box.max.copy(r[0]),i.box.max.z+=1e-4,s.position.addVectors(r[0],r[2]),s.position.multiplyScalar(.5),s.scale.subVectors(r[0],r[2]),s.scale.z=1e-4,this.remove(d),d.position.copy(n.position),d.quaternion.copy(n.quaternion),d.scale.copy(n.scale),d.updateMatrixWorld(!0),this.attach(d),f.box.min.set(n.bottom,n.left,-n.far),f.box.max.set(n.top,n.right,-n.near)}let d=i.near,f=i.far;s.setXYZ(0,f[0].x,f[0].y,f[0].z),s.setXYZ(1,f[3].x,f[3].y,f[3].z),s.setXYZ(2,f[2].x,f[2].y,f[2].z),s.setXYZ(3,f[1].x,f[1].y,f[1].z),s.setXYZ(4,d[0].x,d[0].y,d[0].z),s.setXYZ(5,d[3].x,d[3].y,d[3].z),s.setXYZ(6,d[2].x,d[2].y,d[2].z),s.setXYZ(7,d[1].x,d[1].y,d[1].z),s.needsUpdate=!0}},e.CascadedDirectionalLights=r,e.CascadedShadowMaps=k,e.cascadedShadowMapsOptionsDefaults=O,e.frustumSplitFunctions=b,e.overrideFragmentShaderForCSM=g,e.splitFrustum=x});
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@navaramap/three-csm",
3
+ "type": "module",
4
+ "version": "0.0.0",
5
+ "main": "dist/index.umd.cjs",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "src",
10
+ "dist"
11
+ ],
12
+ "license": "MIT OR Apache-2.0",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/reearth/navara.git",
16
+ "directory": "web/navara_three_csm"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "peerDependencies": {
22
+ "three": ">=0.183.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/three": "0.185.1",
26
+ "three": "0.185.1"
27
+ },
28
+ "scripts": {
29
+ "build": "tsc && vite build --watch=false",
30
+ "build:dev": "vite build --mode development",
31
+ "build:dev:watch": "pnpm build:dev --watch",
32
+ "test": "vitest --passWithNoTests --watch=false",
33
+ "lint": "eslint . --fix",
34
+ "lint:check": "eslint .",
35
+ "format": "prettier --write --ignore-path ../../.gitignore --ignore-path ../../.prettierignore .",
36
+ "format:check": "prettier --check --ignore-path ../../.gitignore --ignore-path ../../.prettierignore ."
37
+ }
38
+ }
@@ -0,0 +1,199 @@
1
+ // TODO
2
+
3
+ // Based on the following work:
4
+ // https://github.com/StrandedKitty/three-csm/tree/master/src
5
+ // https://github.com/mrdoob/three.js/tree/r169/examples/jsm/csm
6
+
7
+ import {
8
+ Box3,
9
+ Box3Helper,
10
+ BufferAttribute,
11
+ BufferGeometry,
12
+ Color,
13
+ DoubleSide,
14
+ Group,
15
+ LineBasicMaterial,
16
+ LineSegments,
17
+ Mesh,
18
+ MeshBasicMaterial,
19
+ PlaneGeometry,
20
+ } from "three";
21
+
22
+ import { type CascadedShadowMaps } from "./CascadedShadowMaps";
23
+
24
+ export class CSMHelper extends Group {
25
+ private readonly csm: CascadedShadowMaps;
26
+ public displayFrustum = true;
27
+ public displayPlanes = true;
28
+ public displayShadowBounds = true;
29
+ private frustumLines: LineSegments<BufferGeometry, LineBasicMaterial>;
30
+ private cascadeLines: Box3Helper[] = [];
31
+ private cascadePlanes: Mesh[] = [];
32
+ private shadowLines: Group[] = [];
33
+
34
+ public constructor(csm: CascadedShadowMaps) {
35
+ super();
36
+ this.csm = csm;
37
+
38
+ const indices = new Uint16Array([
39
+ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7,
40
+ ]);
41
+ const positions = new Float32Array(24);
42
+ const frustumGeometry = new BufferGeometry();
43
+ frustumGeometry.setIndex(new BufferAttribute(indices, 1));
44
+ frustumGeometry.setAttribute(
45
+ "position",
46
+ new BufferAttribute(positions, 3, false),
47
+ );
48
+ const frustumLines = new LineSegments(
49
+ frustumGeometry,
50
+ new LineBasicMaterial(),
51
+ );
52
+ this.add(frustumLines);
53
+
54
+ this.frustumLines = frustumLines;
55
+ }
56
+
57
+ public updateVisibility() {
58
+ const displayFrustum = this.displayFrustum;
59
+ const displayPlanes = this.displayPlanes;
60
+ const displayShadowBounds = this.displayShadowBounds;
61
+
62
+ const frustumLines = this.frustumLines;
63
+ const cascadeLines = this.cascadeLines;
64
+ const cascadePlanes = this.cascadePlanes;
65
+ const shadowLines = this.shadowLines;
66
+ for (let i = 0, l = cascadeLines.length; i < l; i++) {
67
+ const cascadeLine = cascadeLines[i];
68
+ const cascadePlane = cascadePlanes[i];
69
+ const shadowLineGroup = shadowLines[i];
70
+
71
+ cascadeLine.visible = displayFrustum;
72
+ cascadePlane.visible = displayFrustum && displayPlanes;
73
+ shadowLineGroup.visible = displayShadowBounds;
74
+ }
75
+
76
+ frustumLines.visible = displayFrustum;
77
+ }
78
+
79
+ public update() {
80
+ const csm = this.csm;
81
+ const camera = csm.mainCamera;
82
+ const cascadeCount = csm.cascadeCount;
83
+ const mainFrustum = csm.mainFrustum;
84
+ const frusta = csm.cascadedFrusta;
85
+ const lights = csm.directionalLights.cascadedLights;
86
+
87
+ const frustumLines = this.frustumLines;
88
+ const frustumLinePositions = frustumLines.geometry.getAttribute("position");
89
+ const cascadeLines = this.cascadeLines;
90
+ const cascadePlanes = this.cascadePlanes;
91
+ const shadowLines = this.shadowLines;
92
+
93
+ this.position.copy(camera.position);
94
+ this.quaternion.copy(camera.quaternion);
95
+ this.scale.copy(camera.scale);
96
+ this.updateMatrixWorld(true);
97
+
98
+ while (cascadeLines.length > cascadeCount) {
99
+ const cascadeLine = cascadeLines.pop();
100
+ const cascadePlane = cascadePlanes.pop();
101
+ const shadowLine = shadowLines.pop();
102
+ if (cascadeLine) {
103
+ this.remove(cascadeLine);
104
+ }
105
+ if (cascadePlane) {
106
+ this.remove(cascadePlane);
107
+ }
108
+ if (shadowLine) {
109
+ this.remove(shadowLine);
110
+ }
111
+ }
112
+
113
+ while (cascadeLines.length < cascadeCount) {
114
+ const cascadeLine = new Box3Helper(new Box3(), new Color(0xffffff));
115
+ const planeMat = new MeshBasicMaterial({
116
+ transparent: true,
117
+ opacity: 0.1,
118
+ depthWrite: false,
119
+ side: DoubleSide,
120
+ });
121
+ const cascadePlane = new Mesh(new PlaneGeometry(), planeMat);
122
+ const shadowLineGroup = new Group();
123
+ const shadowLine = new Box3Helper(new Box3(), new Color(0xffff00));
124
+ shadowLineGroup.add(shadowLine);
125
+
126
+ this.add(cascadeLine);
127
+ this.add(cascadePlane);
128
+ this.add(shadowLineGroup);
129
+
130
+ cascadeLines.push(cascadeLine);
131
+ cascadePlanes.push(cascadePlane);
132
+ shadowLines.push(shadowLineGroup);
133
+ }
134
+
135
+ for (let i = 0; i < cascadeCount; i++) {
136
+ const frustum = frusta[i];
137
+ const light = lights[i];
138
+ const shadowCam = light.shadow.camera;
139
+ const farVerts = frustum.far;
140
+
141
+ const cascadeLine = cascadeLines[i];
142
+ const cascadePlane = cascadePlanes[i];
143
+ const shadowLineGroup = shadowLines[i];
144
+ const shadowLine = shadowLineGroup.children[0] as Box3Helper;
145
+
146
+ cascadeLine.box.min.copy(farVerts[2]);
147
+ cascadeLine.box.max.copy(farVerts[0]);
148
+ cascadeLine.box.max.z += 1e-4;
149
+
150
+ cascadePlane.position.addVectors(farVerts[0], farVerts[2]);
151
+ cascadePlane.position.multiplyScalar(0.5);
152
+ cascadePlane.scale.subVectors(farVerts[0], farVerts[2]);
153
+ cascadePlane.scale.z = 1e-4;
154
+
155
+ this.remove(shadowLineGroup);
156
+ shadowLineGroup.position.copy(shadowCam.position);
157
+ shadowLineGroup.quaternion.copy(shadowCam.quaternion);
158
+ shadowLineGroup.scale.copy(shadowCam.scale);
159
+ shadowLineGroup.updateMatrixWorld(true);
160
+ this.attach(shadowLineGroup);
161
+
162
+ shadowLine.box.min.set(shadowCam.bottom, shadowCam.left, -shadowCam.far);
163
+ shadowLine.box.max.set(shadowCam.top, shadowCam.right, -shadowCam.near);
164
+ }
165
+
166
+ const nearVerts = mainFrustum.near;
167
+ const farVerts = mainFrustum.far;
168
+ frustumLinePositions.setXYZ(0, farVerts[0].x, farVerts[0].y, farVerts[0].z);
169
+ frustumLinePositions.setXYZ(1, farVerts[3].x, farVerts[3].y, farVerts[3].z);
170
+ frustumLinePositions.setXYZ(2, farVerts[2].x, farVerts[2].y, farVerts[2].z);
171
+ frustumLinePositions.setXYZ(3, farVerts[1].x, farVerts[1].y, farVerts[1].z);
172
+
173
+ frustumLinePositions.setXYZ(
174
+ 4,
175
+ nearVerts[0].x,
176
+ nearVerts[0].y,
177
+ nearVerts[0].z,
178
+ );
179
+ frustumLinePositions.setXYZ(
180
+ 5,
181
+ nearVerts[3].x,
182
+ nearVerts[3].y,
183
+ nearVerts[3].z,
184
+ );
185
+ frustumLinePositions.setXYZ(
186
+ 6,
187
+ nearVerts[2].x,
188
+ nearVerts[2].y,
189
+ nearVerts[2].z,
190
+ );
191
+ frustumLinePositions.setXYZ(
192
+ 7,
193
+ nearVerts[1].x,
194
+ nearVerts[1].y,
195
+ nearVerts[1].z,
196
+ );
197
+ frustumLinePositions.needsUpdate = true;
198
+ }
199
+ }
@@ -0,0 +1,80 @@
1
+ import {
2
+ Color,
3
+ DirectionalLight,
4
+ Object3D,
5
+ Vector3,
6
+ type ColorRepresentation,
7
+ } from "three";
8
+
9
+ const DEFAULT_LIGHT_COLOR = new Color(0xffffff);
10
+
11
+ export class CascadedDirectionalLights extends Object3D {
12
+ readonly mainLight: DirectionalLight;
13
+ readonly cascadedLights: DirectionalLight[];
14
+
15
+ // For convenience and stability of the values, because CascadedShadowMaps
16
+ // modifies the positions of lights and their targets.
17
+ // i.e. position.copy(direction).multiplyScalar(-1).add(target.position)
18
+ direction = new Vector3(-1, -1, -1);
19
+
20
+ constructor(color?: ColorRepresentation, intensity?: number) {
21
+ super();
22
+ const light = new DirectionalLight(color, intensity);
23
+ light.castShadow = true;
24
+ this.mainLight = light;
25
+ this.cascadedLights = [light];
26
+ this.add(light);
27
+ this.add(light.target);
28
+ }
29
+
30
+ setCount(value: number): this {
31
+ value = Math.max(1, value);
32
+ const count = this.cascadedLights.length;
33
+ if (value === count) {
34
+ return this;
35
+ }
36
+ if (value > count) {
37
+ const mainLight = this.mainLight;
38
+ for (let i = count; i < value; ++i) {
39
+ const light = mainLight.clone(); // Clones the shadow parameters as well.
40
+ // Avoid accumulating that lighting color.
41
+ light.color.copy(DEFAULT_LIGHT_COLOR);
42
+ this.add(light);
43
+ this.add(light.target);
44
+ this.cascadedLights[i] = light;
45
+ }
46
+ } else {
47
+ for (let i = value; i < count; ++i) {
48
+ const light = this.cascadedLights[i];
49
+ light.dispose();
50
+ this.remove(light.target);
51
+ this.remove(light);
52
+ }
53
+ }
54
+ this.cascadedLights.length = value;
55
+ return this;
56
+ }
57
+
58
+ dispose(): void {
59
+ // eslint-disable-next-line @typescript-eslint/prefer-for-of
60
+ for (let i = 0; i < this.cascadedLights.length; ++i) {
61
+ this.cascadedLights[i].dispose();
62
+ }
63
+ }
64
+
65
+ copy(source: this, _recursive?: boolean): this {
66
+ super.copy(source, false);
67
+
68
+ this.cascadedLights.length = 0;
69
+ this.clear();
70
+
71
+ for (const l of source.cascadedLights) {
72
+ const cloned = l.clone();
73
+ this.add(cloned);
74
+ this.add(cloned.target);
75
+ this.cascadedLights.push(cloned);
76
+ }
77
+
78
+ return this;
79
+ }
80
+ }