softbox 4.3.2 → 4.4.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
@@ -13,7 +13,7 @@
13
13
  <SimpleViewer object="/model.glb" />
14
14
  ```
15
15
 
16
- Balanced studio lighting, a glossy glass floor with a contact shadow, auto-framing, compressed-asset decoders — all on by default, zero configuration, zero external CDN requests. The core is ~105 kB gzip; the path tracer and the decoders live in lazy chunks that load only when actually used. Every release is gated by 470+ unit tests plus a Playwright suite that asserts **real WebGL pixels** in CI.
16
+ Balanced studio lighting, a real-scale matte concrete floor with a baked soft contact shadow, auto-framing, compressed-asset decoders — all on by default, zero configuration. Lighting needs zero network requests (the studio environment is procedural); the DRACO/KTX2 decoder wasm is fetched only when a model actually uses that compression, from a CDN by default or [self-hosted](#loaders) for offline use. The core is ~117 kB gzip; the path tracer lives in a lazy chunk that loads only when enabled. Every release is gated by 590+ unit tests plus a Playwright suite that asserts **real WebGL pixels** in CI.
17
17
 
18
18
  [![softbox playground](https://raw.githubusercontent.com/LEMing/softbox/main/public/og-image.png)](https://leming.github.io/softbox/)
19
19
 
@@ -212,7 +212,7 @@ const options: SimpleViewerOptions = {
212
212
  animations: { autoplay: true, speed: 1 }, // GLTF clip playback
213
213
 
214
214
  helpers: {
215
- grid: { type: 'hexagonal_glass' }, // glossy glass floor; also: hexagonal_wire, square_wire, stone_tiles
215
+ grid: { type: 'hexagonal_glass' }, // hex-tile floor (matte concrete by default); also: hexagonal_wire, square_wire, stone_tiles
216
216
  gizmo: false, // optional viewport orientation gizmo
217
217
  studioEnvironment: true,
218
218
  },
@@ -223,6 +223,12 @@ const options: SimpleViewerOptions = {
223
223
  selection: { bvh: true }, // BVH-accelerated raycasts for picking/occlusion
224
224
  loadingIndicator: true, // built-in loading overlay (object form customizes it)
225
225
  loaders: {}, // DRACO/KTX2/Meshopt decoder config (self-host paths, toggles)
226
+
227
+ // Like <img loading="lazy">: defer the WebGL context and model download until
228
+ // the viewer first approaches the viewport. On pages with many viewers only
229
+ // the visible ones boot — browsers cap concurrent WebGL contexts, so an
230
+ // eager grid can silently kill the oldest ones. Once booted, always booted.
231
+ loading: 'eager', // or 'lazy'
226
232
  };
227
233
  ```
228
234
 
@@ -237,6 +243,17 @@ A built-in overlay shows while a model loads (and an error state if it fails)
237
243
  <SimpleViewer object={url} options={{ loadingIndicator: false }} /> // drive your own via events
238
244
  ```
239
245
 
246
+ ### Loaders
247
+
248
+ DRACO, KTX2/Basis and Meshopt decoders are wired into the glTF loader by default — compressed exports from Blender, `gltfpack` or `gltf-transform` just load. Meshopt is bundled; the DRACO/KTX2 wasm decoders are fetched **on demand** (only the first time a model actually uses that compression) from a version-pinned jsDelivr URL. For a fully offline, no-CDN setup, self-host the decoder directories:
249
+
250
+ ```ts
251
+ loaders: {
252
+ dracoDecoderPath: '/decoders/draco/', // copy of three/examples/jsm/libs/draco/
253
+ ktx2TranscoderPath: '/decoders/basis/', // copy of three/examples/jsm/libs/basis/
254
+ }
255
+ ```
256
+
240
257
  ### Viewport gizmo
241
258
 
242
259
  ```ts
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("three"),T=require("./index-QkQEREfW.cjs");function $e(s){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(s){for(const t in s)if(t!=="default"){const r=Object.getOwnPropertyDescriptor(s,t);Object.defineProperty(e,t,r.get?r:{enumerable:!0,get:()=>s[t]})}}return e.default=s,Object.freeze(e)}const A=$e(n);function je(s){switch(s){case 1:return"R";case 2:return"RG";case 3:return"RGBA";case 4:return"RGBA"}throw new Error}function Ye(s){switch(s){case 1:return n.RedFormat;case 2:return n.RGFormat;case 3:return n.RGBAFormat;case 4:return n.RGBAFormat}}function de(s){switch(s){case 1:return n.RedIntegerFormat;case 2:return n.RGIntegerFormat;case 3:return n.RGBAIntegerFormat;case 4:return n.RGBAIntegerFormat}}class ke extends n.DataTexture{constructor(){super(),this.minFilter=n.NearestFilter,this.magFilter=n.NearestFilter,this.generateMipmaps=!1,this.overrideItemSize=null,this._forcedType=null}updateFrom(e){const t=this.overrideItemSize,r=e.itemSize,i=e.count;if(t!==null){if(r*i%t!==0)throw new Error("VertexAttributeTexture: overrideItemSize must divide evenly into buffer length.");e.itemSize=t,e.count=i*r/t}const o=e.itemSize,a=e.count,c=e.normalized,h=e.array.constructor,m=h.BYTES_PER_ELEMENT;let d=this._forcedType,f=o;if(d===null)switch(h){case Float32Array:d=n.FloatType;break;case Uint8Array:case Uint16Array:case Uint32Array:d=n.UnsignedIntType;break;case Int8Array:case Int16Array:case Int32Array:d=n.IntType;break}let l,y,p,v,u=je(o);switch(d){case n.FloatType:p=1,y=Ye(o),c&&m===1?(v=h,u+="8",h===Uint8Array?l=n.UnsignedByteType:(l=n.ByteType,u+="_SNORM")):(v=Float32Array,u+="32F",l=n.FloatType);break;case n.IntType:u+=m*8+"I",p=c?Math.pow(2,h.BYTES_PER_ELEMENT*8-1):1,y=de(o),m===1?(v=Int8Array,l=n.ByteType):m===2?(v=Int16Array,l=n.ShortType):(v=Int32Array,l=n.IntType);break;case n.UnsignedIntType:u+=m*8+"UI",p=c?Math.pow(2,h.BYTES_PER_ELEMENT*8-1):1,y=de(o),m===1?(v=Uint8Array,l=n.UnsignedByteType):m===2?(v=Uint16Array,l=n.UnsignedShortType):(v=Uint32Array,l=n.UnsignedIntType);break}f===3&&(y===n.RGBAFormat||y===n.RGBAIntegerFormat)&&(f=4);const g=Math.ceil(Math.sqrt(a))||1,x=f*g*g,b=new v(x),w=e.normalized;e.normalized=!1;for(let R=0;R<a;R++){const _=f*R;b[_]=e.getX(R)/p,o>=2&&(b[_+1]=e.getY(R)/p),o>=3&&(b[_+2]=e.getZ(R)/p,f===4&&(b[_+3]=1)),o>=4&&(b[_+3]=e.getW(R)/p)}e.normalized=w,this.internalFormat=u,this.format=y,this.type=l,this.image.width=g,this.image.height=g,this.image.data=b,this.needsUpdate=!0,this.dispose(),e.itemSize=r,e.count=i}}class Be extends ke{constructor(){super(),this._forcedType=n.UnsignedIntType}}class Ne extends ke{constructor(){super(),this._forcedType=n.FloatType}}class Xe{constructor(){this.index=new Be,this.position=new Ne,this.bvhBounds=new n.DataTexture,this.bvhContents=new n.DataTexture,this._cachedIndexAttr=null,this.index.overrideItemSize=3}updateFrom(e){const{geometry:t}=e;if(Ke(e,this.bvhBounds,this.bvhContents),this.position.updateFrom(t.attributes.position),e.indirect){const r=e._indirectBuffer;if(this._cachedIndexAttr===null||this._cachedIndexAttr.count!==r.length)if(t.index)this._cachedIndexAttr=t.index.clone();else{const i=T.getIndexArray(T.getVertexCount(t));this._cachedIndexAttr=new n.BufferAttribute(i,1,!1)}Qe(t,r,this._cachedIndexAttr),this.index.updateFrom(this._cachedIndexAttr)}else this.index.updateFrom(t.index)}dispose(){const{index:e,position:t,bvhBounds:r,bvhContents:i}=this;e&&e.dispose(),t&&t.dispose(),r&&r.dispose(),i&&i.dispose()}}function Qe(s,e,t){const r=t.array,i=s.index?s.index.array:null;for(let o=0,a=e.length;o<a;o++){const c=3*o,h=3*e[o];for(let m=0;m<3;m++)r[c+m]=i?i[h+m]:h+m}}function Ke(s,e,t){const r=s._roots;if(r.length!==1)throw new Error("MeshBVHUniformStruct: Multi-root BVHs not supported.");const i=r[0],o=new Uint16Array(i),a=new Uint32Array(i),c=new Float32Array(i),h=i.byteLength/T.BYTES_PER_NODE,m=2*Math.ceil(Math.sqrt(h/2)),d=new Float32Array(4*m*m),f=Math.ceil(Math.sqrt(h)),l=new Uint32Array(2*f*f);for(let y=0;y<h;y++){const p=y*T.BYTES_PER_NODE/4,v=p*2,u=T.BOUNDING_DATA_INDEX(p);for(let g=0;g<3;g++)d[8*y+0+g]=c[u+0+g],d[8*y+4+g]=c[u+3+g];if(T.IS_LEAF(v,o)){const g=T.COUNT(v,o),x=T.OFFSET(p,a),b=T.LEAFNODE_MASK_32|g;l[y*2+0]=b,l[y*2+1]=x}else{const g=a[p+6],x=T.SPLIT_AXIS(p,a);l[y*2+0]=x,l[y*2+1]=g}}e.image.data=d,e.image.width=m,e.image.height=m,e.format=n.RGBAFormat,e.type=n.FloatType,e.internalFormat="RGBA32F",e.minFilter=n.NearestFilter,e.magFilter=n.NearestFilter,e.generateMipmaps=!1,e.needsUpdate=!0,e.dispose(),t.image.data=l,t.image.width=f,t.image.height=f,t.format=n.RGIntegerFormat,t.type=n.UnsignedIntType,t.internalFormat="RG32UI",t.minFilter=n.NearestFilter,t.magFilter=n.NearestFilter,t.generateMipmaps=!1,t.needsUpdate=!0,t.dispose()}const Ze=`
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("three"),T=require("./index-BiSXFVUP.cjs");function $e(s){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(s){for(const t in s)if(t!=="default"){const r=Object.getOwnPropertyDescriptor(s,t);Object.defineProperty(e,t,r.get?r:{enumerable:!0,get:()=>s[t]})}}return e.default=s,Object.freeze(e)}const A=$e(n);function je(s){switch(s){case 1:return"R";case 2:return"RG";case 3:return"RGBA";case 4:return"RGBA"}throw new Error}function Ye(s){switch(s){case 1:return n.RedFormat;case 2:return n.RGFormat;case 3:return n.RGBAFormat;case 4:return n.RGBAFormat}}function de(s){switch(s){case 1:return n.RedIntegerFormat;case 2:return n.RGIntegerFormat;case 3:return n.RGBAIntegerFormat;case 4:return n.RGBAIntegerFormat}}class ke extends n.DataTexture{constructor(){super(),this.minFilter=n.NearestFilter,this.magFilter=n.NearestFilter,this.generateMipmaps=!1,this.overrideItemSize=null,this._forcedType=null}updateFrom(e){const t=this.overrideItemSize,r=e.itemSize,i=e.count;if(t!==null){if(r*i%t!==0)throw new Error("VertexAttributeTexture: overrideItemSize must divide evenly into buffer length.");e.itemSize=t,e.count=i*r/t}const o=e.itemSize,a=e.count,c=e.normalized,h=e.array.constructor,m=h.BYTES_PER_ELEMENT;let d=this._forcedType,f=o;if(d===null)switch(h){case Float32Array:d=n.FloatType;break;case Uint8Array:case Uint16Array:case Uint32Array:d=n.UnsignedIntType;break;case Int8Array:case Int16Array:case Int32Array:d=n.IntType;break}let l,y,p,v,u=je(o);switch(d){case n.FloatType:p=1,y=Ye(o),c&&m===1?(v=h,u+="8",h===Uint8Array?l=n.UnsignedByteType:(l=n.ByteType,u+="_SNORM")):(v=Float32Array,u+="32F",l=n.FloatType);break;case n.IntType:u+=m*8+"I",p=c?Math.pow(2,h.BYTES_PER_ELEMENT*8-1):1,y=de(o),m===1?(v=Int8Array,l=n.ByteType):m===2?(v=Int16Array,l=n.ShortType):(v=Int32Array,l=n.IntType);break;case n.UnsignedIntType:u+=m*8+"UI",p=c?Math.pow(2,h.BYTES_PER_ELEMENT*8-1):1,y=de(o),m===1?(v=Uint8Array,l=n.UnsignedByteType):m===2?(v=Uint16Array,l=n.UnsignedShortType):(v=Uint32Array,l=n.UnsignedIntType);break}f===3&&(y===n.RGBAFormat||y===n.RGBAIntegerFormat)&&(f=4);const g=Math.ceil(Math.sqrt(a))||1,x=f*g*g,b=new v(x),w=e.normalized;e.normalized=!1;for(let R=0;R<a;R++){const _=f*R;b[_]=e.getX(R)/p,o>=2&&(b[_+1]=e.getY(R)/p),o>=3&&(b[_+2]=e.getZ(R)/p,f===4&&(b[_+3]=1)),o>=4&&(b[_+3]=e.getW(R)/p)}e.normalized=w,this.internalFormat=u,this.format=y,this.type=l,this.image.width=g,this.image.height=g,this.image.data=b,this.needsUpdate=!0,this.dispose(),e.itemSize=r,e.count=i}}class Be extends ke{constructor(){super(),this._forcedType=n.UnsignedIntType}}class Ne extends ke{constructor(){super(),this._forcedType=n.FloatType}}class Xe{constructor(){this.index=new Be,this.position=new Ne,this.bvhBounds=new n.DataTexture,this.bvhContents=new n.DataTexture,this._cachedIndexAttr=null,this.index.overrideItemSize=3}updateFrom(e){const{geometry:t}=e;if(Ke(e,this.bvhBounds,this.bvhContents),this.position.updateFrom(t.attributes.position),e.indirect){const r=e._indirectBuffer;if(this._cachedIndexAttr===null||this._cachedIndexAttr.count!==r.length)if(t.index)this._cachedIndexAttr=t.index.clone();else{const i=T.getIndexArray(T.getVertexCount(t));this._cachedIndexAttr=new n.BufferAttribute(i,1,!1)}Qe(t,r,this._cachedIndexAttr),this.index.updateFrom(this._cachedIndexAttr)}else this.index.updateFrom(t.index)}dispose(){const{index:e,position:t,bvhBounds:r,bvhContents:i}=this;e&&e.dispose(),t&&t.dispose(),r&&r.dispose(),i&&i.dispose()}}function Qe(s,e,t){const r=t.array,i=s.index?s.index.array:null;for(let o=0,a=e.length;o<a;o++){const c=3*o,h=3*e[o];for(let m=0;m<3;m++)r[c+m]=i?i[h+m]:h+m}}function Ke(s,e,t){const r=s._roots;if(r.length!==1)throw new Error("MeshBVHUniformStruct: Multi-root BVHs not supported.");const i=r[0],o=new Uint16Array(i),a=new Uint32Array(i),c=new Float32Array(i),h=i.byteLength/T.BYTES_PER_NODE,m=2*Math.ceil(Math.sqrt(h/2)),d=new Float32Array(4*m*m),f=Math.ceil(Math.sqrt(h)),l=new Uint32Array(2*f*f);for(let y=0;y<h;y++){const p=y*T.BYTES_PER_NODE/4,v=p*2,u=T.BOUNDING_DATA_INDEX(p);for(let g=0;g<3;g++)d[8*y+0+g]=c[u+0+g],d[8*y+4+g]=c[u+3+g];if(T.IS_LEAF(v,o)){const g=T.COUNT(v,o),x=T.OFFSET(p,a),b=T.LEAFNODE_MASK_32|g;l[y*2+0]=b,l[y*2+1]=x}else{const g=a[p+6],x=T.SPLIT_AXIS(p,a);l[y*2+0]=x,l[y*2+1]=g}}e.image.data=d,e.image.width=m,e.image.height=m,e.format=n.RGBAFormat,e.type=n.FloatType,e.internalFormat="RGBA32F",e.minFilter=n.NearestFilter,e.magFilter=n.NearestFilter,e.generateMipmaps=!1,e.needsUpdate=!0,e.dispose(),t.image.data=l,t.image.width=f,t.image.height=f,t.format=n.RGIntegerFormat,t.type=n.UnsignedIntType,t.internalFormat="RG32UI",t.minFilter=n.NearestFilter,t.magFilter=n.NearestFilter,t.generateMipmaps=!1,t.needsUpdate=!0,t.dispose()}const Ze=`
2
2
 
3
3
  // A stack of uint32 indices can can store the indices for
4
4
  // a perfectly balanced tree with a depth up to 31. Lower stack
@@ -1,6 +1,6 @@
1
1
  import * as B from "three";
2
2
  import { UnsignedIntType as re, DataTexture as M, NearestFilter as T, IntType as Te, FloatType as R, UnsignedByteType as _e, UnsignedShortType as Mt, ByteType as He, ShortType as At, RGBAFormat as I, RGBAIntegerFormat as Me, RGIntegerFormat as ut, RedIntegerFormat as Ft, RGFormat as ht, RedFormat as me, BufferAttribute as P, BufferGeometry as $, Matrix4 as O, Matrix3 as Ct, Vector3 as C, Vector4 as J, MeshBasicMaterial as Dt, Mesh as ft, OrthographicCamera as Pt, Float32BufferAttribute as Be, ShaderMaterial as pe, NoBlending as ie, WebGLRenderTarget as ae, Vector2 as j, PerspectiveCamera as dt, DataUtils as k, HalfFloatType as D, LinearFilter as F, RepeatWrapping as z, ClampToEdgeWrapping as N, Source as Et, Quaternion as kt, DataArrayTexture as Ot, DoubleSide as zt, BackSide as Nt, FrontSide as Ht, WebGLArrayRenderTarget as Bt, Color as se, NoToneMapping as Wt, NormalBlending as mt, EquirectangularReflectionMapping as pt, Spherical as Lt, LinearMipMapLinearFilter as Ut, Clock as Vt, Scene as Gt, AdditiveBlending as qt } from "three";
3
- import { g as $t, a as jt, B as We, I as Yt, C as Xt, O as Qt, S as Kt, b as Zt, L as Jt, c as er, M as tr, T as rr, D as ar, d as ir, R as S, e as W, E as L, h as Le } from "./index-BdrQXlCy.js";
3
+ import { g as $t, a as jt, B as We, I as Yt, C as Xt, O as Qt, S as Kt, b as Zt, L as Jt, c as er, M as tr, T as rr, D as ar, d as ir, R as S, e as W, E as L, h as Le } from "./index-uh2exMyt.js";
4
4
  function sr(s) {
5
5
  switch (s) {
6
6
  case 1: