@react-three/drei 9.89.2 → 9.90.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.
- package/README.md +33 -5
- package/core/Splat.cjs.js +1 -0
- package/core/Splat.d.ts +58 -0
- package/core/Splat.js +505 -0
- package/core/index.cjs.js +1 -1
- package/core/index.d.ts +1 -0
- package/core/index.js +1 -0
- package/core/useVideoTexture.cjs.js +1 -1
- package/core/useVideoTexture.js +6 -1
- package/index.cjs.js +1 -1
- package/index.js +1 -0
- package/materials/SpotLightMaterial.cjs.js +1 -1
- package/materials/SpotLightMaterial.js +69 -61
- package/native/index.cjs.js +1 -1
- package/native/index.js +1 -0
- package/package.json +1 -1
- package/react-three-drei-0.0.0-semantic-release.tgz +0 -0
- package/web/index.cjs.js +1 -1
- package/web/index.js +1 -0
package/README.md
CHANGED
|
@@ -77,7 +77,7 @@ The `native` route of the library **does not** export `Html` or `Loader`. The de
|
|
|
77
77
|
<li><a href="#outlines">Outlines</a></li>
|
|
78
78
|
<li><a href="#trail">Trail</a></li>
|
|
79
79
|
<li><a href="#sampler">Sampler</a></li>
|
|
80
|
-
<li><a href="#computedattribute">
|
|
80
|
+
<li><a href="#computedattribute">ComputedAttribute</a></li>
|
|
81
81
|
<li><a href="#clone">Clone</a></li>
|
|
82
82
|
<li><a href="#useanimations">useAnimations</a></li>
|
|
83
83
|
<li><a href="#marchingcubes">MarchingCubes</a></li>
|
|
@@ -85,6 +85,7 @@ The `native` route of the library **does not** export `Html` or `Loader`. The de
|
|
|
85
85
|
<li><a href="#svg">Svg</a></li>
|
|
86
86
|
<li><a href="#gltf">Gltf</a></li>
|
|
87
87
|
<li><a href="#asciirenderer">AsciiRenderer</a></li>
|
|
88
|
+
<li><a href="#splat">Splat</a></li>
|
|
88
89
|
</ul>
|
|
89
90
|
<li><a href="#shaders">Shaders</a></li>
|
|
90
91
|
<ul>
|
|
@@ -1770,8 +1771,8 @@ The decal box has to intersect the surface, otherwise it will not be visible. if
|
|
|
1770
1771
|
rotation={[0, 0, 0]} // Rotation of the decal (can be a vector or a degree in radians)
|
|
1771
1772
|
scale={1} // Scale of the decal
|
|
1772
1773
|
>
|
|
1773
|
-
<meshBasicMaterial
|
|
1774
|
-
map={texture}
|
|
1774
|
+
<meshBasicMaterial
|
|
1775
|
+
map={texture}
|
|
1775
1776
|
polygonOffset
|
|
1776
1777
|
polygonOffsetFactor={-1} // The material should take precedence over the original
|
|
1777
1778
|
/>
|
|
@@ -1849,6 +1850,33 @@ type AsciiRendererProps = {
|
|
|
1849
1850
|
<AsciiRenderer />
|
|
1850
1851
|
```
|
|
1851
1852
|
|
|
1853
|
+
#### Splat
|
|
1854
|
+
|
|
1855
|
+
<p>
|
|
1856
|
+
<a href="https://codesandbox.io/s/qp4jmf"><img width="20%" src="https://codesandbox.io/api/v1/sandboxes/qp4jmf/screenshot.png" alt="Demo"/></a>
|
|
1857
|
+
</p>
|
|
1858
|
+
|
|
1859
|
+
A declarative abstraction around [antimatter15/splat](https://github.com/antimatter15/splat). It supports re-use, multiple splats with correct depth sorting, splats can move and behave as a regular object3d's, supports alphahash & alphatest, and stream-loading.
|
|
1860
|
+
|
|
1861
|
+
```tsx
|
|
1862
|
+
type SplatProps = {
|
|
1863
|
+
/** Url towards a *.splat file, no support for *.ply */
|
|
1864
|
+
src: string
|
|
1865
|
+
/** Whether to use tone mapping, default: false */
|
|
1866
|
+
toneMapped?: boolean
|
|
1867
|
+
/** Alpha test value, , default: 0 */
|
|
1868
|
+
alphaTest?: number
|
|
1869
|
+
/** Whether to use alpha hashing, default: false */
|
|
1870
|
+
alphaHash?: boolean
|
|
1871
|
+
/** Chunk size for lazy loading, prevents chokings the worker, default: 25000 (25kb) */
|
|
1872
|
+
chunkSize?: number
|
|
1873
|
+
} & JSX.IntrinsicElements['mesh']
|
|
1874
|
+
```
|
|
1875
|
+
|
|
1876
|
+
```jsx
|
|
1877
|
+
<Splat src="https://huggingface.co/cakewalk/splat-data/resolve/main/nike.splat" />
|
|
1878
|
+
```
|
|
1879
|
+
|
|
1852
1880
|
# Shaders
|
|
1853
1881
|
|
|
1854
1882
|
#### MeshReflectorMaterial
|
|
@@ -2371,13 +2399,13 @@ Enable shadows using the `castShadow` and `recieveShadow` prop.
|
|
|
2371
2399
|
|
|
2372
2400
|
> Note: Html 'blending' mode only correctly occludes rectangular HTML elements by default. Use the `geometry` prop to swap the backing geometry to a custom one if your Html has a different shape.
|
|
2373
2401
|
|
|
2374
|
-
If transform mode is enabled, the dimensions of the rendered html will depend on the position relative to the camera, the camera fov and the distanceFactor. For example, an Html component placed at (0,0,0) and with a distanceFactor of 10, rendered inside a scene with a perspective camera positioned at (0,0,2.45) and a FOV of 75, will have the same dimensions as a "plain" html element like in [this example](https://codesandbox.io/s/drei-html-magic-number-6mzt6m).
|
|
2402
|
+
If transform mode is enabled, the dimensions of the rendered html will depend on the position relative to the camera, the camera fov and the distanceFactor. For example, an Html component placed at (0,0,0) and with a distanceFactor of 10, rendered inside a scene with a perspective camera positioned at (0,0,2.45) and a FOV of 75, will have the same dimensions as a "plain" html element like in [this example](https://codesandbox.io/s/drei-html-magic-number-6mzt6m).
|
|
2375
2403
|
|
|
2376
2404
|
A caveat of transform mode is that on some devices and browsers, the rendered html may appear blurry, as discussed in [#859](https://github.com/pmndrs/drei/issues/859). The issue can be at least mitigated by scaling down the Html parent and scaling up the html children:
|
|
2377
2405
|
|
|
2378
2406
|
```jsx
|
|
2379
2407
|
<Html transform scale={0.5}>
|
|
2380
|
-
<div style={{ transform:
|
|
2408
|
+
<div style={{ transform: 'scale(2)' }}>Some text</div>
|
|
2381
2409
|
</Html>
|
|
2382
2410
|
```
|
|
2383
2411
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("@babel/runtime/helpers/extends"),t=require("three"),n=require("react"),r=require("@react-three/fiber"),o=require("./shaderMaterial.cjs.js");function a(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}function l(e){if(e&&e.__esModule)return e;var t=Object.create(null);return e&&Object.keys(e).forEach((function(n){if("default"!==n){var r=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(t,n,r.get?r:{enumerable:!0,get:function(){return e[n]}})}})),t.default=e,Object.freeze(t)}var c=a(e),s=l(t),i=l(n);const u=o.shaderMaterial({alphaTest:0,viewport:new s.Vector2(1980,1080),focal:1e3,centerAndScaleTexture:null,covAndColorTexture:null},"\n precision highp sampler2D;\n precision highp usampler2D;\n out vec4 vColor;\n out vec3 vPosition;\n uniform vec2 resolution;\n uniform vec2 viewport;\n uniform float focal;\n attribute uint splatIndex;\n uniform sampler2D centerAndScaleTexture;\n uniform usampler2D covAndColorTexture; \n\n vec2 unpackInt16(in uint value) {\n int v = int(value);\n int v0 = v >> 16;\n int v1 = (v & 0xFFFF);\n if((v & 0x8000) != 0)\n v1 |= 0xFFFF0000;\n return vec2(float(v1), float(v0));\n }\n\n void main () {\n ivec2 texSize = textureSize(centerAndScaleTexture, 0);\n ivec2 texPos = ivec2(splatIndex%uint(texSize.x), splatIndex/uint(texSize.x));\n vec4 centerAndScaleData = texelFetch(centerAndScaleTexture, texPos, 0);\n vec4 center = vec4(centerAndScaleData.xyz, 1);\n vec4 camspace = modelViewMatrix * center;\n vec4 pos2d = projectionMatrix * camspace;\n\n float bounds = 1.2 * pos2d.w;\n if (pos2d.z < -pos2d.w || pos2d.x < -bounds || pos2d.x > bounds\n || pos2d.y < -bounds || pos2d.y > bounds) {\n gl_Position = vec4(0.0, 0.0, 2.0, 1.0);\n return;\n }\n\n uvec4 covAndColorData = texelFetch(covAndColorTexture, texPos, 0);\n vec2 cov3D_M11_M12 = unpackInt16(covAndColorData.x) * centerAndScaleData.w;\n vec2 cov3D_M13_M22 = unpackInt16(covAndColorData.y) * centerAndScaleData.w;\n vec2 cov3D_M23_M33 = unpackInt16(covAndColorData.z) * centerAndScaleData.w;\n mat3 Vrk = mat3(\n cov3D_M11_M12.x, cov3D_M11_M12.y, cov3D_M13_M22.x,\n cov3D_M11_M12.y, cov3D_M13_M22.y, cov3D_M23_M33.x,\n cov3D_M13_M22.x, cov3D_M23_M33.x, cov3D_M23_M33.y\n );\n\n mat3 J = mat3(\n focal / camspace.z, 0., -(focal * camspace.x) / (camspace.z * camspace.z),\n 0., focal / camspace.z, -(focal * camspace.y) / (camspace.z * camspace.z),\n 0., 0., 0.\n );\n\n mat3 W = transpose(mat3(modelViewMatrix));\n mat3 T = W * J;\n mat3 cov = transpose(T) * Vrk * T;\n vec2 vCenter = vec2(pos2d) / pos2d.w;\n float diagonal1 = cov[0][0] + 0.3;\n float offDiagonal = cov[0][1];\n float diagonal2 = cov[1][1] + 0.3;\n float mid = 0.5 * (diagonal1 + diagonal2);\n float radius = length(vec2((diagonal1 - diagonal2) / 2.0, offDiagonal));\n float lambda1 = mid + radius;\n float lambda2 = max(mid - radius, 0.1);\n vec2 diagonalVector = normalize(vec2(offDiagonal, lambda1 - diagonal1));\n vec2 v1 = min(sqrt(2.0 * lambda1), 1024.0) * diagonalVector;\n vec2 v2 = min(sqrt(2.0 * lambda2), 1024.0) * vec2(diagonalVector.y, -diagonalVector.x);\n uint colorUint = covAndColorData.w;\n vColor = vec4(\n float(colorUint & uint(0xFF)) / 255.0,\n float((colorUint >> uint(8)) & uint(0xFF)) / 255.0,\n float((colorUint >> uint(16)) & uint(0xFF)) / 255.0,\n float(colorUint >> uint(24)) / 255.0\n );\n vPosition = position;\n\n gl_Position = vec4(\n vCenter \n + position.x * v2 / viewport * 2.0 \n + position.y * v1 / viewport * 2.0, pos2d.z / pos2d.w, 1.0);\n }\n ",`\n #include <alphatest_pars_fragment>\n #include <alphahash_pars_fragment>\n in vec4 vColor;\n in vec3 vPosition;\n void main () {\n float A = -dot(vPosition.xy, vPosition.xy);\n if (A < -4.0) discard;\n float B = exp(A) * vColor.a;\n vec4 diffuseColor = vec4(vColor.rgb, B);\n #include <alphatest_fragment>\n #include <alphahash_fragment>\n gl_FragColor = diffuseColor;\n #include <tonemapping_fragment>\n #include <${parseInt(s.REVISION.replace(/\D+/g,""))>=154?"colorspace_fragment":"encodings_fragment"}>\n }\n `);function d(e){let t=null,n=0;e.onmessage=r=>{if("push"==r.data.method){0===n&&(t=new Float32Array(r.data.length));const e=new Float32Array(r.data.matrices);t.set(e,n),n+=e.length}else if("sort"==r.data.method&&null!==t){const n=function(e,n=!1){const r=t.length/16;let o=-1/0,a=1/0;const l=new Float32Array(r),c=new Int32Array(l.buffer),s=new Int32Array(r);let i=0;for(let c=0;c<r;c++){const r=e[0]*t[16*c+12]+e[1]*t[16*c+13]+e[2]*t[16*c+14]+e[3];(n||r<0&&t[16*c+15]>-1e-4*r)&&(l[i]=r,s[i]=c,i++,r>o&&(o=r),r<a&&(a=r))}const u=65535/(o-a),d=new Uint32Array(65536);for(let e=0;e<i;e++)c[e]=(l[e]-a)*u|0,d[c[e]]++;const f=new Uint32Array(65536);for(let e=1;e<65536;e++)f[e]=f[e-1]+d[e-1];const x=new Uint32Array(i);for(let e=0;e<i;e++)x[f[c[e]]++]=s[e];return x}(new Float32Array(r.data.view),r.data.hashed);e.postMessage({indices:n,key:r.data.key},[n.buffer])}}}class f extends s.Loader{constructor(...e){super(...e),this.gl=null,this.chunkSize=25e3}load(e,t,n,r){const o={gl:this.gl,url:this.manager.resolveURL(e),worker:new Worker(URL.createObjectURL(new Blob(["(",d.toString(),")(self)"],{type:"application/javascript"}))),manager:this.manager,update:(e,t,n)=>function(e,t,n,r){if(e.updateMatrixWorld(),t.gl.getCurrentViewport(n.viewport),n.material.viewport.x=n.viewport.z,n.material.viewport.y=n.viewport.w,n.material.focal=n.viewport.w/2*Math.abs(e.projectionMatrix.elements[5]),n.ready){if(r&&n.sorted)return;n.ready=!1;const e=new Float32Array([n.modelViewMatrix.elements[2],-n.modelViewMatrix.elements[6],n.modelViewMatrix.elements[10],n.modelViewMatrix.elements[14]]);t.worker.postMessage({method:"sort",src:t.url,key:n.uuid,view:e.buffer,hashed:r},[e.buffer]),r&&t.loaded&&(n.sorted=!0)}}(t,o,e,n),connect:e=>function(e,t){e.loading||async function(e){e.loading=!0;let t=0,n=0;const r=[];let o=0;const a=0!==e.totalDownloadBytes;for(;;)try{const{value:l,done:c}=await e.stream.read();if(c)break;if(t+=l.length,null!=e.totalDownloadBytes){const n=t/e.totalDownloadBytes*100;if(e.onProgress&&n-o>1){const r=new ProgressEvent("progress",{lengthComputable:a,loaded:t,total:e.totalDownloadBytes});e.onProgress(r),o=n}}r.push(l);const s=t-n;if(null!=e.totalDownloadBytes&&s>e.rowLength*e.chunkSize){let t=Math.floor(s/e.rowLength);const o=new Uint8Array(s);let l=0;for(const e of r)o.set(e,l),l+=e.length;if(r.length=0,s>t*e.rowLength){const n=new Uint8Array(s-t*e.rowLength);n.set(o.subarray(s-n.length,s),0),r.push(n)}const c=new Uint8Array(t*e.rowLength);c.set(o.subarray(0,c.byteLength),0);const i=x(e,c.buffer,t);if(e.worker.postMessage({method:"push",src:e.url,length:16*e.numVertices,matrices:i.buffer},[i.buffer]),n+=t*e.rowLength,e.onProgress){const t=new ProgressEvent("progress",{lengthComputable:a,loaded:e.totalDownloadBytes,total:e.totalDownloadBytes});e.onProgress(t)}}}catch(e){console.error(e);break}if(t-n>0){let t=new Uint8Array(r.reduce(((e,t)=>e+t.length),0)),n=0;for(const e of r)t.set(e,n),n+=e.length;let o=Math.floor(t.byteLength/e.rowLength);const a=x(e,t.buffer,o);e.worker.postMessage({method:"push",src:e.url,length:16*o,matrices:a.buffer},[a.buffer])}e.loaded=!0,e.manager.itemEnd(e.url)}(e);t.ready=!1,t.pm=new s.Matrix4,t.vm1=new s.Matrix4,t.vm2=new s.Matrix4,t.viewport=new s.Vector4;let n=new Uint32Array(e.bufferTextureWidth*e.bufferTextureHeight);const r=new s.InstancedBufferAttribute(n,1,!1);r.setUsage(s.DynamicDrawUsage);const o=t.geometry=new s.InstancedBufferGeometry,a=new Float32Array(18),l=new s.BufferAttribute(a,3);function c(e){if(t&&e.data.key===t.uuid){let n=new Uint32Array(e.data.indices);o.attributes.splatIndex.set(n),o.attributes.splatIndex.needsUpdate=!0,o.instanceCount=n.length,t.ready=!0}}async function i(){for(;;){const t=e.gl.properties.get(e.centerAndScaleTexture),n=e.gl.properties.get(e.covAndColorTexture);if(null!=t&&t.__webglTexture&&null!=n&&n.__webglTexture&&e.loadedVertexCount>0)break;await new Promise((e=>setTimeout(e,10)))}t.ready=!0}return o.setAttribute("position",l),l.setXYZ(2,-2,2,0),l.setXYZ(1,2,2,0),l.setXYZ(0,-2,-2,0),l.setXYZ(5,-2,-2,0),l.setXYZ(4,2,2,0),l.setXYZ(3,2,-2,0),l.needsUpdate=!0,o.setAttribute("splatIndex",r),o.instanceCount=1,e.worker.addEventListener("message",c),i(),()=>e.worker.removeEventListener("message",c)}(o,e),loading:!1,loaded:!1,loadedVertexCount:0,chunkSize:this.chunkSize,totalDownloadBytes:0,numVertices:0,rowLength:32,maxVertexes:0,bufferTextureWidth:0,bufferTextureHeight:0,stream:null,centerAndScaleData:null,covAndColorData:null,covAndColorTexture:null,centerAndScaleTexture:null,onProgress:n};(async function(e){e.manager.itemStart(e.url);const t=await fetch(e.url);if(null===t.body)throw"Failed to fetch file";let n=t.headers.get("Content-Length");const r=n?parseInt(n):void 0;if(null==r)throw"Failed to get content length";e.stream=t.body.getReader(),e.totalDownloadBytes=r,e.numVertices=Math.floor(e.totalDownloadBytes/e.rowLength);const o=e.gl.getContext();let a=o.getParameter(o.MAX_TEXTURE_SIZE);e.maxVertexes=a*a,e.numVertices>e.maxVertexes&&(e.numVertices=e.maxVertexes);return e.bufferTextureWidth=a,e.bufferTextureHeight=Math.floor((e.numVertices-1)/a)+1,e.centerAndScaleData=new Float32Array(e.bufferTextureWidth*e.bufferTextureHeight*4),e.covAndColorData=new Uint32Array(e.bufferTextureWidth*e.bufferTextureHeight*4),e.centerAndScaleTexture=new s.DataTexture(e.centerAndScaleData,e.bufferTextureWidth,e.bufferTextureHeight,s.RGBAFormat,s.FloatType),e.centerAndScaleTexture.needsUpdate=!0,e.covAndColorTexture=new s.DataTexture(e.covAndColorData,e.bufferTextureWidth,e.bufferTextureHeight,s.RGBAIntegerFormat,s.UnsignedIntType),e.covAndColorTexture.internalFormat="RGBA32UI",e.covAndColorTexture.needsUpdate=!0,e})(o).then(t).catch((e=>{null==r||r(e),o.manager.itemError(o.url)}))}}function x(e,t,n){const r=e.gl.getContext();if(e.loadedVertexCount+n>e.maxVertexes&&(n=e.maxVertexes-e.loadedVertexCount),n<=0)throw"Failed to parse file";const o=new Uint8Array(t),a=new Float32Array(t),l=new Float32Array(16*n),c=new Uint8Array(e.covAndColorData.buffer),i=new Int16Array(e.covAndColorData.buffer);for(let t=0;t<n;t++){const n=new s.Quaternion(-(o[32*t+28+1]-128)/128,(o[32*t+28+2]-128)/128,(o[32*t+28+3]-128)/128,-(o[32*t+28+0]-128)/128);n.invert();const r=new s.Vector3(a[8*t+0],a[8*t+1],-a[8*t+2]),u=new s.Vector3(a[8*t+3+0],a[8*t+3+1],a[8*t+3+2]),d=new s.Matrix4;d.makeRotationFromQuaternion(n),d.transpose(),d.scale(u);const f=d.clone();d.transpose(),d.premultiply(f),d.setPosition(r);const x=[0,1,2,5,6,10];let p=0;for(let e=0;e<x.length;e++)Math.abs(d.elements[x[e]])>p&&(p=Math.abs(d.elements[x[e]]));let g=4*e.loadedVertexCount+4*t;e.centerAndScaleData[g+0]=r.x,e.centerAndScaleData[g+1]=-r.y,e.centerAndScaleData[g+2]=r.z,e.centerAndScaleData[g+3]=p/32767,g=8*e.loadedVertexCount+4*t*2;for(let e=0;e<x.length;e++)i[g+e]=32767*d.elements[x[e]]/p;g=16*e.loadedVertexCount+4*(4*t+3);const m=new s.Color(o[32*t+24+0]/255,o[32*t+24+1]/255,o[32*t+24+2]/255);m.convertSRGBToLinear(),c[g+0]=255*m.r,c[g+1]=255*m.g,c[g+2]=255*m.b,c[g+3]=o[32*t+24+3],d.elements[15]=Math.max(u.x,u.y,u.z)*o[32*t+24+3]/255;for(let e=0;e<16;e++)l[16*t+e]=d.elements[e]}for(;n>0;){let t=0,o=0;const a=e.loadedVertexCount%e.bufferTextureWidth,l=Math.floor(e.loadedVertexCount/e.bufferTextureWidth);e.loadedVertexCount%e.bufferTextureWidth!=0?(t=Math.min(e.bufferTextureWidth,a+n)-a,o=1):Math.floor(n/e.bufferTextureWidth)>0?(t=e.bufferTextureWidth,o=Math.floor(n/e.bufferTextureWidth)):(t=n%e.bufferTextureWidth,o=1);const c=e.gl.properties.get(e.centerAndScaleTexture);r.bindTexture(r.TEXTURE_2D,c.__webglTexture),r.texSubImage2D(r.TEXTURE_2D,0,a,l,t,o,r.RGBA,r.FLOAT,e.centerAndScaleData,4*e.loadedVertexCount);const s=e.gl.properties.get(e.covAndColorTexture);r.bindTexture(r.TEXTURE_2D,s.__webglTexture),r.texSubImage2D(r.TEXTURE_2D,0,a,l,t,o,r.RGBA_INTEGER,r.UNSIGNED_INT,e.covAndColorData,4*e.loadedVertexCount),e.gl.resetState(),e.loadedVertexCount+=t*o,n-=t*o}return l}exports.Splat=function({src:e,toneMapped:t=!1,alphaTest:n=0,alphaHash:o=!1,chunkSize:a=25e3,...l}){r.extend({SplatMaterial:u});const d=i.useRef(null),x=r.useThree((e=>e.gl)),p=r.useThree((e=>e.camera)),g=r.useLoader(f,e,(e=>{e.gl=x,e.chunkSize=a}));return i.useLayoutEffect((()=>g.connect(d.current)),[e]),r.useFrame((()=>g.update(d.current,p,o))),i.createElement("mesh",c.default({ref:d,frustumCulled:!1},l),i.createElement("splatMaterial",{key:`${e}/${n}/${o}${u.key}`,transparent:!o,depthTest:!0,alphaTest:o?0:n,centerAndScaleTexture:g.centerAndScaleTexture,covAndColorTexture:g.covAndColorTexture,depthWrite:!!o||n>0,blending:o?s.NormalBlending:s.CustomBlending,blendSrcAlpha:s.OneFactor,alphaHash:!!o,toneMapped:t}))};
|
package/core/Splat.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
export type SplatMaterialType = {
|
|
4
|
+
alphaTest?: number;
|
|
5
|
+
alphaHash?: boolean;
|
|
6
|
+
centerAndScaleTexture?: THREE.DataTexture;
|
|
7
|
+
covAndColorTexture?: THREE.DataTexture;
|
|
8
|
+
viewport?: THREE.Vector2;
|
|
9
|
+
focal?: number;
|
|
10
|
+
};
|
|
11
|
+
export type TargetMesh = THREE.Mesh<THREE.InstancedBufferGeometry, THREE.ShaderMaterial & SplatMaterialType> & {
|
|
12
|
+
ready: boolean;
|
|
13
|
+
sorted: boolean;
|
|
14
|
+
pm: THREE.Matrix4;
|
|
15
|
+
vm1: THREE.Matrix4;
|
|
16
|
+
vm2: THREE.Matrix4;
|
|
17
|
+
viewport: THREE.Vector4;
|
|
18
|
+
};
|
|
19
|
+
export type SharedState = {
|
|
20
|
+
url: string;
|
|
21
|
+
gl: THREE.WebGLRenderer;
|
|
22
|
+
worker: Worker;
|
|
23
|
+
manager: THREE.LoadingManager;
|
|
24
|
+
stream: ReadableStreamDefaultReader<Uint8Array>;
|
|
25
|
+
loading: boolean;
|
|
26
|
+
loaded: boolean;
|
|
27
|
+
loadedVertexCount: number;
|
|
28
|
+
rowLength: number;
|
|
29
|
+
maxVertexes: number;
|
|
30
|
+
chunkSize: number;
|
|
31
|
+
totalDownloadBytes: number;
|
|
32
|
+
numVertices: number;
|
|
33
|
+
bufferTextureWidth: number;
|
|
34
|
+
bufferTextureHeight: number;
|
|
35
|
+
centerAndScaleData: Float32Array;
|
|
36
|
+
covAndColorData: Uint32Array;
|
|
37
|
+
covAndColorTexture: THREE.DataTexture;
|
|
38
|
+
centerAndScaleTexture: THREE.DataTexture;
|
|
39
|
+
connect(target: TargetMesh): () => void;
|
|
40
|
+
update(target: TargetMesh, camera: THREE.Camera, hashed: boolean): void;
|
|
41
|
+
onProgress?: (event: ProgressEvent) => void;
|
|
42
|
+
};
|
|
43
|
+
declare global {
|
|
44
|
+
namespace JSX {
|
|
45
|
+
interface IntrinsicElements {
|
|
46
|
+
splatMaterial: SplatMaterialType & JSX.IntrinsicElements['shaderMaterial'];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
type SplatProps = {
|
|
51
|
+
src: string;
|
|
52
|
+
toneMapped?: boolean;
|
|
53
|
+
alphaTest?: number;
|
|
54
|
+
alphaHash?: boolean;
|
|
55
|
+
chunkSize?: number;
|
|
56
|
+
} & JSX.IntrinsicElements['mesh'];
|
|
57
|
+
export declare function Splat({ src, toneMapped, alphaTest, alphaHash, chunkSize, ...props }: SplatProps): React.JSX.Element;
|
|
58
|
+
export {};
|
package/core/Splat.js
ADDED
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
import _extends from '@babel/runtime/helpers/esm/extends';
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { extend, useThree, useLoader, useFrame } from '@react-three/fiber';
|
|
5
|
+
import { shaderMaterial } from './shaderMaterial.js';
|
|
6
|
+
|
|
7
|
+
const SplatMaterial = /* @__PURE__ */shaderMaterial({
|
|
8
|
+
alphaTest: 0,
|
|
9
|
+
viewport: /* @__PURE__ */new THREE.Vector2(1980, 1080),
|
|
10
|
+
focal: 1000.0,
|
|
11
|
+
centerAndScaleTexture: null,
|
|
12
|
+
covAndColorTexture: null
|
|
13
|
+
}, /*glsl*/`
|
|
14
|
+
precision highp sampler2D;
|
|
15
|
+
precision highp usampler2D;
|
|
16
|
+
out vec4 vColor;
|
|
17
|
+
out vec3 vPosition;
|
|
18
|
+
uniform vec2 resolution;
|
|
19
|
+
uniform vec2 viewport;
|
|
20
|
+
uniform float focal;
|
|
21
|
+
attribute uint splatIndex;
|
|
22
|
+
uniform sampler2D centerAndScaleTexture;
|
|
23
|
+
uniform usampler2D covAndColorTexture;
|
|
24
|
+
|
|
25
|
+
vec2 unpackInt16(in uint value) {
|
|
26
|
+
int v = int(value);
|
|
27
|
+
int v0 = v >> 16;
|
|
28
|
+
int v1 = (v & 0xFFFF);
|
|
29
|
+
if((v & 0x8000) != 0)
|
|
30
|
+
v1 |= 0xFFFF0000;
|
|
31
|
+
return vec2(float(v1), float(v0));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
void main () {
|
|
35
|
+
ivec2 texSize = textureSize(centerAndScaleTexture, 0);
|
|
36
|
+
ivec2 texPos = ivec2(splatIndex%uint(texSize.x), splatIndex/uint(texSize.x));
|
|
37
|
+
vec4 centerAndScaleData = texelFetch(centerAndScaleTexture, texPos, 0);
|
|
38
|
+
vec4 center = vec4(centerAndScaleData.xyz, 1);
|
|
39
|
+
vec4 camspace = modelViewMatrix * center;
|
|
40
|
+
vec4 pos2d = projectionMatrix * camspace;
|
|
41
|
+
|
|
42
|
+
float bounds = 1.2 * pos2d.w;
|
|
43
|
+
if (pos2d.z < -pos2d.w || pos2d.x < -bounds || pos2d.x > bounds
|
|
44
|
+
|| pos2d.y < -bounds || pos2d.y > bounds) {
|
|
45
|
+
gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
uvec4 covAndColorData = texelFetch(covAndColorTexture, texPos, 0);
|
|
50
|
+
vec2 cov3D_M11_M12 = unpackInt16(covAndColorData.x) * centerAndScaleData.w;
|
|
51
|
+
vec2 cov3D_M13_M22 = unpackInt16(covAndColorData.y) * centerAndScaleData.w;
|
|
52
|
+
vec2 cov3D_M23_M33 = unpackInt16(covAndColorData.z) * centerAndScaleData.w;
|
|
53
|
+
mat3 Vrk = mat3(
|
|
54
|
+
cov3D_M11_M12.x, cov3D_M11_M12.y, cov3D_M13_M22.x,
|
|
55
|
+
cov3D_M11_M12.y, cov3D_M13_M22.y, cov3D_M23_M33.x,
|
|
56
|
+
cov3D_M13_M22.x, cov3D_M23_M33.x, cov3D_M23_M33.y
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
mat3 J = mat3(
|
|
60
|
+
focal / camspace.z, 0., -(focal * camspace.x) / (camspace.z * camspace.z),
|
|
61
|
+
0., focal / camspace.z, -(focal * camspace.y) / (camspace.z * camspace.z),
|
|
62
|
+
0., 0., 0.
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
mat3 W = transpose(mat3(modelViewMatrix));
|
|
66
|
+
mat3 T = W * J;
|
|
67
|
+
mat3 cov = transpose(T) * Vrk * T;
|
|
68
|
+
vec2 vCenter = vec2(pos2d) / pos2d.w;
|
|
69
|
+
float diagonal1 = cov[0][0] + 0.3;
|
|
70
|
+
float offDiagonal = cov[0][1];
|
|
71
|
+
float diagonal2 = cov[1][1] + 0.3;
|
|
72
|
+
float mid = 0.5 * (diagonal1 + diagonal2);
|
|
73
|
+
float radius = length(vec2((diagonal1 - diagonal2) / 2.0, offDiagonal));
|
|
74
|
+
float lambda1 = mid + radius;
|
|
75
|
+
float lambda2 = max(mid - radius, 0.1);
|
|
76
|
+
vec2 diagonalVector = normalize(vec2(offDiagonal, lambda1 - diagonal1));
|
|
77
|
+
vec2 v1 = min(sqrt(2.0 * lambda1), 1024.0) * diagonalVector;
|
|
78
|
+
vec2 v2 = min(sqrt(2.0 * lambda2), 1024.0) * vec2(diagonalVector.y, -diagonalVector.x);
|
|
79
|
+
uint colorUint = covAndColorData.w;
|
|
80
|
+
vColor = vec4(
|
|
81
|
+
float(colorUint & uint(0xFF)) / 255.0,
|
|
82
|
+
float((colorUint >> uint(8)) & uint(0xFF)) / 255.0,
|
|
83
|
+
float((colorUint >> uint(16)) & uint(0xFF)) / 255.0,
|
|
84
|
+
float(colorUint >> uint(24)) / 255.0
|
|
85
|
+
);
|
|
86
|
+
vPosition = position;
|
|
87
|
+
|
|
88
|
+
gl_Position = vec4(
|
|
89
|
+
vCenter
|
|
90
|
+
+ position.x * v2 / viewport * 2.0
|
|
91
|
+
+ position.y * v1 / viewport * 2.0, pos2d.z / pos2d.w, 1.0);
|
|
92
|
+
}
|
|
93
|
+
`, /*glsl*/`
|
|
94
|
+
#include <alphatest_pars_fragment>
|
|
95
|
+
#include <alphahash_pars_fragment>
|
|
96
|
+
in vec4 vColor;
|
|
97
|
+
in vec3 vPosition;
|
|
98
|
+
void main () {
|
|
99
|
+
float A = -dot(vPosition.xy, vPosition.xy);
|
|
100
|
+
if (A < -4.0) discard;
|
|
101
|
+
float B = exp(A) * vColor.a;
|
|
102
|
+
vec4 diffuseColor = vec4(vColor.rgb, B);
|
|
103
|
+
#include <alphatest_fragment>
|
|
104
|
+
#include <alphahash_fragment>
|
|
105
|
+
gl_FragColor = diffuseColor;
|
|
106
|
+
#include <tonemapping_fragment>
|
|
107
|
+
#include <${parseInt(THREE.REVISION.replace(/\D+/g, '')) >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
|
|
108
|
+
}
|
|
109
|
+
`);
|
|
110
|
+
function createWorker(self) {
|
|
111
|
+
let matrices = null;
|
|
112
|
+
let offset = 0;
|
|
113
|
+
function sortSplats(view, hashed = false) {
|
|
114
|
+
const vertexCount = matrices.length / 16;
|
|
115
|
+
const threshold = -0.0001;
|
|
116
|
+
let maxDepth = -Infinity;
|
|
117
|
+
let minDepth = Infinity;
|
|
118
|
+
const depthList = new Float32Array(vertexCount);
|
|
119
|
+
const sizeList = new Int32Array(depthList.buffer);
|
|
120
|
+
const validIndexList = new Int32Array(vertexCount);
|
|
121
|
+
let validCount = 0;
|
|
122
|
+
for (let i = 0; i < vertexCount; i++) {
|
|
123
|
+
// Sign of depth is reversed
|
|
124
|
+
const depth = view[0] * matrices[i * 16 + 12] + view[1] * matrices[i * 16 + 13] + view[2] * matrices[i * 16 + 14] + view[3];
|
|
125
|
+
// Skip behind of camera and small, transparent splat
|
|
126
|
+
if (hashed || depth < 0 && matrices[i * 16 + 15] > threshold * depth) {
|
|
127
|
+
depthList[validCount] = depth;
|
|
128
|
+
validIndexList[validCount] = i;
|
|
129
|
+
validCount++;
|
|
130
|
+
if (depth > maxDepth) maxDepth = depth;
|
|
131
|
+
if (depth < minDepth) minDepth = depth;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// This is a 16 bit single-pass counting sort
|
|
136
|
+
const depthInv = (256 * 256 - 1) / (maxDepth - minDepth);
|
|
137
|
+
const counts0 = new Uint32Array(256 * 256);
|
|
138
|
+
for (let i = 0; i < validCount; i++) {
|
|
139
|
+
sizeList[i] = (depthList[i] - minDepth) * depthInv | 0;
|
|
140
|
+
counts0[sizeList[i]]++;
|
|
141
|
+
}
|
|
142
|
+
const starts0 = new Uint32Array(256 * 256);
|
|
143
|
+
for (let i = 1; i < 256 * 256; i++) starts0[i] = starts0[i - 1] + counts0[i - 1];
|
|
144
|
+
const depthIndex = new Uint32Array(validCount);
|
|
145
|
+
for (let i = 0; i < validCount; i++) depthIndex[starts0[sizeList[i]]++] = validIndexList[i];
|
|
146
|
+
return depthIndex;
|
|
147
|
+
}
|
|
148
|
+
self.onmessage = e => {
|
|
149
|
+
if (e.data.method == 'push') {
|
|
150
|
+
if (offset === 0) matrices = new Float32Array(e.data.length);
|
|
151
|
+
const new_matrices = new Float32Array(e.data.matrices);
|
|
152
|
+
matrices.set(new_matrices, offset);
|
|
153
|
+
offset += new_matrices.length;
|
|
154
|
+
} else if (e.data.method == 'sort') {
|
|
155
|
+
if (matrices !== null) {
|
|
156
|
+
const indices = sortSplats(new Float32Array(e.data.view), e.data.hashed);
|
|
157
|
+
// @ts-ignore
|
|
158
|
+
self.postMessage({
|
|
159
|
+
indices,
|
|
160
|
+
key: e.data.key
|
|
161
|
+
}, [indices.buffer]);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
class SplatLoader extends THREE.Loader {
|
|
167
|
+
constructor(...args) {
|
|
168
|
+
super(...args);
|
|
169
|
+
// WebGLRenderer, needs to be filled out!
|
|
170
|
+
this.gl = null;
|
|
171
|
+
// Default chunk size for lazy loading
|
|
172
|
+
this.chunkSize = 25000;
|
|
173
|
+
}
|
|
174
|
+
load(url, onLoad, onProgress, onError) {
|
|
175
|
+
const shared = {
|
|
176
|
+
gl: this.gl,
|
|
177
|
+
url: this.manager.resolveURL(url),
|
|
178
|
+
worker: new Worker(URL.createObjectURL(new Blob(['(', createWorker.toString(), ')(self)'], {
|
|
179
|
+
type: 'application/javascript'
|
|
180
|
+
}))),
|
|
181
|
+
manager: this.manager,
|
|
182
|
+
update: (target, camera, hashed) => update(camera, shared, target, hashed),
|
|
183
|
+
connect: target => connect(shared, target),
|
|
184
|
+
loading: false,
|
|
185
|
+
loaded: false,
|
|
186
|
+
loadedVertexCount: 0,
|
|
187
|
+
chunkSize: this.chunkSize,
|
|
188
|
+
totalDownloadBytes: 0,
|
|
189
|
+
numVertices: 0,
|
|
190
|
+
rowLength: 3 * 4 + 3 * 4 + 4 + 4,
|
|
191
|
+
maxVertexes: 0,
|
|
192
|
+
bufferTextureWidth: 0,
|
|
193
|
+
bufferTextureHeight: 0,
|
|
194
|
+
stream: null,
|
|
195
|
+
centerAndScaleData: null,
|
|
196
|
+
covAndColorData: null,
|
|
197
|
+
covAndColorTexture: null,
|
|
198
|
+
centerAndScaleTexture: null,
|
|
199
|
+
onProgress
|
|
200
|
+
};
|
|
201
|
+
load(shared).then(onLoad).catch(e => {
|
|
202
|
+
onError == null || onError(e);
|
|
203
|
+
shared.manager.itemError(shared.url);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async function load(shared) {
|
|
208
|
+
shared.manager.itemStart(shared.url);
|
|
209
|
+
const data = await fetch(shared.url);
|
|
210
|
+
if (data.body === null) throw 'Failed to fetch file';
|
|
211
|
+
let _totalDownloadBytes = data.headers.get('Content-Length');
|
|
212
|
+
const totalDownloadBytes = _totalDownloadBytes ? parseInt(_totalDownloadBytes) : undefined;
|
|
213
|
+
if (totalDownloadBytes == undefined) throw 'Failed to get content length';
|
|
214
|
+
shared.stream = data.body.getReader();
|
|
215
|
+
shared.totalDownloadBytes = totalDownloadBytes;
|
|
216
|
+
shared.numVertices = Math.floor(shared.totalDownloadBytes / shared.rowLength);
|
|
217
|
+
const context = shared.gl.getContext();
|
|
218
|
+
let maxTextureSize = context.getParameter(context.MAX_TEXTURE_SIZE);
|
|
219
|
+
shared.maxVertexes = maxTextureSize * maxTextureSize;
|
|
220
|
+
if (shared.numVertices > shared.maxVertexes) shared.numVertices = shared.maxVertexes;
|
|
221
|
+
shared.bufferTextureWidth = maxTextureSize;
|
|
222
|
+
shared.bufferTextureHeight = Math.floor((shared.numVertices - 1) / maxTextureSize) + 1;
|
|
223
|
+
shared.centerAndScaleData = new Float32Array(shared.bufferTextureWidth * shared.bufferTextureHeight * 4);
|
|
224
|
+
shared.covAndColorData = new Uint32Array(shared.bufferTextureWidth * shared.bufferTextureHeight * 4);
|
|
225
|
+
shared.centerAndScaleTexture = new THREE.DataTexture(shared.centerAndScaleData, shared.bufferTextureWidth, shared.bufferTextureHeight, THREE.RGBAFormat, THREE.FloatType);
|
|
226
|
+
shared.centerAndScaleTexture.needsUpdate = true;
|
|
227
|
+
shared.covAndColorTexture = new THREE.DataTexture(shared.covAndColorData, shared.bufferTextureWidth, shared.bufferTextureHeight, THREE.RGBAIntegerFormat, THREE.UnsignedIntType);
|
|
228
|
+
shared.covAndColorTexture.internalFormat = 'RGBA32UI';
|
|
229
|
+
shared.covAndColorTexture.needsUpdate = true;
|
|
230
|
+
return shared;
|
|
231
|
+
}
|
|
232
|
+
async function lazyLoad(shared) {
|
|
233
|
+
shared.loading = true;
|
|
234
|
+
let bytesDownloaded = 0;
|
|
235
|
+
let bytesProcessed = 0;
|
|
236
|
+
const chunks = [];
|
|
237
|
+
let lastReportedProgress = 0;
|
|
238
|
+
const lengthComputable = shared.totalDownloadBytes !== 0;
|
|
239
|
+
while (true) {
|
|
240
|
+
try {
|
|
241
|
+
const {
|
|
242
|
+
value,
|
|
243
|
+
done
|
|
244
|
+
} = await shared.stream.read();
|
|
245
|
+
if (done) break;
|
|
246
|
+
bytesDownloaded += value.length;
|
|
247
|
+
if (shared.totalDownloadBytes != undefined) {
|
|
248
|
+
const percent = bytesDownloaded / shared.totalDownloadBytes * 100;
|
|
249
|
+
if (shared.onProgress && percent - lastReportedProgress > 1) {
|
|
250
|
+
const event = new ProgressEvent('progress', {
|
|
251
|
+
lengthComputable,
|
|
252
|
+
loaded: bytesDownloaded,
|
|
253
|
+
total: shared.totalDownloadBytes
|
|
254
|
+
});
|
|
255
|
+
shared.onProgress(event);
|
|
256
|
+
lastReportedProgress = percent;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
chunks.push(value);
|
|
260
|
+
const bytesRemains = bytesDownloaded - bytesProcessed;
|
|
261
|
+
if (shared.totalDownloadBytes != undefined && bytesRemains > shared.rowLength * shared.chunkSize) {
|
|
262
|
+
let vertexCount = Math.floor(bytesRemains / shared.rowLength);
|
|
263
|
+
const concatenatedChunksbuffer = new Uint8Array(bytesRemains);
|
|
264
|
+
let offset = 0;
|
|
265
|
+
for (const chunk of chunks) {
|
|
266
|
+
concatenatedChunksbuffer.set(chunk, offset);
|
|
267
|
+
offset += chunk.length;
|
|
268
|
+
}
|
|
269
|
+
chunks.length = 0;
|
|
270
|
+
if (bytesRemains > vertexCount * shared.rowLength) {
|
|
271
|
+
const extra_data = new Uint8Array(bytesRemains - vertexCount * shared.rowLength);
|
|
272
|
+
extra_data.set(concatenatedChunksbuffer.subarray(bytesRemains - extra_data.length, bytesRemains), 0);
|
|
273
|
+
chunks.push(extra_data);
|
|
274
|
+
}
|
|
275
|
+
const buffer = new Uint8Array(vertexCount * shared.rowLength);
|
|
276
|
+
buffer.set(concatenatedChunksbuffer.subarray(0, buffer.byteLength), 0);
|
|
277
|
+
const matrices = pushDataBuffer(shared, buffer.buffer, vertexCount);
|
|
278
|
+
shared.worker.postMessage({
|
|
279
|
+
method: 'push',
|
|
280
|
+
src: shared.url,
|
|
281
|
+
length: shared.numVertices * 16,
|
|
282
|
+
matrices: matrices.buffer
|
|
283
|
+
}, [matrices.buffer]);
|
|
284
|
+
bytesProcessed += vertexCount * shared.rowLength;
|
|
285
|
+
if (shared.onProgress) {
|
|
286
|
+
const event = new ProgressEvent('progress', {
|
|
287
|
+
lengthComputable,
|
|
288
|
+
loaded: shared.totalDownloadBytes,
|
|
289
|
+
total: shared.totalDownloadBytes
|
|
290
|
+
});
|
|
291
|
+
shared.onProgress(event);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
} catch (error) {
|
|
295
|
+
console.error(error);
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (bytesDownloaded - bytesProcessed > 0) {
|
|
300
|
+
// Concatenate the chunks into a single Uint8Array
|
|
301
|
+
let concatenatedChunks = new Uint8Array(chunks.reduce((acc, chunk) => acc + chunk.length, 0));
|
|
302
|
+
let offset = 0;
|
|
303
|
+
for (const chunk of chunks) {
|
|
304
|
+
concatenatedChunks.set(chunk, offset);
|
|
305
|
+
offset += chunk.length;
|
|
306
|
+
}
|
|
307
|
+
let numVertices = Math.floor(concatenatedChunks.byteLength / shared.rowLength);
|
|
308
|
+
const matrices = pushDataBuffer(shared, concatenatedChunks.buffer, numVertices);
|
|
309
|
+
shared.worker.postMessage({
|
|
310
|
+
method: 'push',
|
|
311
|
+
src: shared.url,
|
|
312
|
+
length: numVertices * 16,
|
|
313
|
+
matrices: matrices.buffer
|
|
314
|
+
}, [matrices.buffer]);
|
|
315
|
+
}
|
|
316
|
+
shared.loaded = true;
|
|
317
|
+
shared.manager.itemEnd(shared.url);
|
|
318
|
+
}
|
|
319
|
+
function update(camera, shared, target, hashed) {
|
|
320
|
+
camera.updateMatrixWorld();
|
|
321
|
+
shared.gl.getCurrentViewport(target.viewport);
|
|
322
|
+
// @ts-ignore
|
|
323
|
+
target.material.viewport.x = target.viewport.z;
|
|
324
|
+
// @ts-ignore
|
|
325
|
+
target.material.viewport.y = target.viewport.w;
|
|
326
|
+
target.material.focal = target.viewport.w / 2.0 * Math.abs(camera.projectionMatrix.elements[5]);
|
|
327
|
+
if (target.ready) {
|
|
328
|
+
if (hashed && target.sorted) return;
|
|
329
|
+
target.ready = false;
|
|
330
|
+
const view = new Float32Array([target.modelViewMatrix.elements[2], -target.modelViewMatrix.elements[6], target.modelViewMatrix.elements[10], target.modelViewMatrix.elements[14]]);
|
|
331
|
+
shared.worker.postMessage({
|
|
332
|
+
method: 'sort',
|
|
333
|
+
src: shared.url,
|
|
334
|
+
key: target.uuid,
|
|
335
|
+
view: view.buffer,
|
|
336
|
+
hashed
|
|
337
|
+
}, [view.buffer]);
|
|
338
|
+
if (hashed && shared.loaded) target.sorted = true;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function connect(shared, target) {
|
|
342
|
+
if (!shared.loading) lazyLoad(shared);
|
|
343
|
+
target.ready = false;
|
|
344
|
+
target.pm = new THREE.Matrix4();
|
|
345
|
+
target.vm1 = new THREE.Matrix4();
|
|
346
|
+
target.vm2 = new THREE.Matrix4();
|
|
347
|
+
target.viewport = new THREE.Vector4();
|
|
348
|
+
let splatIndexArray = new Uint32Array(shared.bufferTextureWidth * shared.bufferTextureHeight);
|
|
349
|
+
const splatIndexes = new THREE.InstancedBufferAttribute(splatIndexArray, 1, false);
|
|
350
|
+
splatIndexes.setUsage(THREE.DynamicDrawUsage);
|
|
351
|
+
const geometry = target.geometry = new THREE.InstancedBufferGeometry();
|
|
352
|
+
const positionsArray = new Float32Array(6 * 3);
|
|
353
|
+
const positions = new THREE.BufferAttribute(positionsArray, 3);
|
|
354
|
+
geometry.setAttribute('position', positions);
|
|
355
|
+
positions.setXYZ(2, -2.0, 2.0, 0.0);
|
|
356
|
+
positions.setXYZ(1, 2.0, 2.0, 0.0);
|
|
357
|
+
positions.setXYZ(0, -2.0, -2.0, 0.0);
|
|
358
|
+
positions.setXYZ(5, -2.0, -2.0, 0.0);
|
|
359
|
+
positions.setXYZ(4, 2.0, 2.0, 0.0);
|
|
360
|
+
positions.setXYZ(3, 2.0, -2.0, 0.0);
|
|
361
|
+
positions.needsUpdate = true;
|
|
362
|
+
geometry.setAttribute('splatIndex', splatIndexes);
|
|
363
|
+
geometry.instanceCount = 1;
|
|
364
|
+
function listener(e) {
|
|
365
|
+
if (target && e.data.key === target.uuid) {
|
|
366
|
+
let indexes = new Uint32Array(e.data.indices);
|
|
367
|
+
// @ts-ignore
|
|
368
|
+
geometry.attributes.splatIndex.set(indexes);
|
|
369
|
+
geometry.attributes.splatIndex.needsUpdate = true;
|
|
370
|
+
geometry.instanceCount = indexes.length;
|
|
371
|
+
target.ready = true;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
shared.worker.addEventListener('message', listener);
|
|
375
|
+
async function wait() {
|
|
376
|
+
while (true) {
|
|
377
|
+
const centerAndScaleTextureProperties = shared.gl.properties.get(shared.centerAndScaleTexture);
|
|
378
|
+
const covAndColorTextureProperties = shared.gl.properties.get(shared.covAndColorTexture);
|
|
379
|
+
if (centerAndScaleTextureProperties != null && centerAndScaleTextureProperties.__webglTexture && covAndColorTextureProperties != null && covAndColorTextureProperties.__webglTexture && shared.loadedVertexCount > 0) break;
|
|
380
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
381
|
+
}
|
|
382
|
+
target.ready = true;
|
|
383
|
+
}
|
|
384
|
+
wait();
|
|
385
|
+
return () => shared.worker.removeEventListener('message', listener);
|
|
386
|
+
}
|
|
387
|
+
function pushDataBuffer(shared, buffer, vertexCount) {
|
|
388
|
+
const context = shared.gl.getContext();
|
|
389
|
+
if (shared.loadedVertexCount + vertexCount > shared.maxVertexes) vertexCount = shared.maxVertexes - shared.loadedVertexCount;
|
|
390
|
+
if (vertexCount <= 0) throw 'Failed to parse file';
|
|
391
|
+
const u_buffer = new Uint8Array(buffer);
|
|
392
|
+
const f_buffer = new Float32Array(buffer);
|
|
393
|
+
const matrices = new Float32Array(vertexCount * 16);
|
|
394
|
+
const covAndColorData_uint8 = new Uint8Array(shared.covAndColorData.buffer);
|
|
395
|
+
const covAndColorData_int16 = new Int16Array(shared.covAndColorData.buffer);
|
|
396
|
+
for (let i = 0; i < vertexCount; i++) {
|
|
397
|
+
const quat = new THREE.Quaternion(-(u_buffer[32 * i + 28 + 1] - 128) / 128.0, (u_buffer[32 * i + 28 + 2] - 128) / 128.0, (u_buffer[32 * i + 28 + 3] - 128) / 128.0, -(u_buffer[32 * i + 28 + 0] - 128) / 128.0);
|
|
398
|
+
quat.invert();
|
|
399
|
+
const center = new THREE.Vector3(f_buffer[8 * i + 0], f_buffer[8 * i + 1], -f_buffer[8 * i + 2]);
|
|
400
|
+
const scale = new THREE.Vector3(f_buffer[8 * i + 3 + 0], f_buffer[8 * i + 3 + 1], f_buffer[8 * i + 3 + 2]);
|
|
401
|
+
const mtx = new THREE.Matrix4();
|
|
402
|
+
mtx.makeRotationFromQuaternion(quat);
|
|
403
|
+
mtx.transpose();
|
|
404
|
+
mtx.scale(scale);
|
|
405
|
+
const mtx_t = mtx.clone();
|
|
406
|
+
mtx.transpose();
|
|
407
|
+
mtx.premultiply(mtx_t);
|
|
408
|
+
mtx.setPosition(center);
|
|
409
|
+
const cov_indexes = [0, 1, 2, 5, 6, 10];
|
|
410
|
+
let max_value = 0.0;
|
|
411
|
+
for (let j = 0; j < cov_indexes.length; j++) if (Math.abs(mtx.elements[cov_indexes[j]]) > max_value) max_value = Math.abs(mtx.elements[cov_indexes[j]]);
|
|
412
|
+
let destOffset = shared.loadedVertexCount * 4 + i * 4;
|
|
413
|
+
shared.centerAndScaleData[destOffset + 0] = center.x;
|
|
414
|
+
shared.centerAndScaleData[destOffset + 1] = -center.y;
|
|
415
|
+
shared.centerAndScaleData[destOffset + 2] = center.z;
|
|
416
|
+
shared.centerAndScaleData[destOffset + 3] = max_value / 32767.0;
|
|
417
|
+
destOffset = shared.loadedVertexCount * 8 + i * 4 * 2;
|
|
418
|
+
for (let j = 0; j < cov_indexes.length; j++) covAndColorData_int16[destOffset + j] = mtx.elements[cov_indexes[j]] * 32767.0 / max_value;
|
|
419
|
+
|
|
420
|
+
// RGBA
|
|
421
|
+
destOffset = shared.loadedVertexCount * 16 + (i * 4 + 3) * 4;
|
|
422
|
+
const col = new THREE.Color(u_buffer[32 * i + 24 + 0] / 255, u_buffer[32 * i + 24 + 1] / 255, u_buffer[32 * i + 24 + 2] / 255);
|
|
423
|
+
col.convertSRGBToLinear();
|
|
424
|
+
covAndColorData_uint8[destOffset + 0] = col.r * 255;
|
|
425
|
+
covAndColorData_uint8[destOffset + 1] = col.g * 255;
|
|
426
|
+
covAndColorData_uint8[destOffset + 2] = col.b * 255;
|
|
427
|
+
covAndColorData_uint8[destOffset + 3] = u_buffer[32 * i + 24 + 3];
|
|
428
|
+
|
|
429
|
+
// Store scale and transparent to remove splat in sorting process
|
|
430
|
+
mtx.elements[15] = Math.max(scale.x, scale.y, scale.z) * u_buffer[32 * i + 24 + 3] / 255.0;
|
|
431
|
+
for (let j = 0; j < 16; j++) matrices[i * 16 + j] = mtx.elements[j];
|
|
432
|
+
}
|
|
433
|
+
while (vertexCount > 0) {
|
|
434
|
+
let width = 0;
|
|
435
|
+
let height = 0;
|
|
436
|
+
const xoffset = shared.loadedVertexCount % shared.bufferTextureWidth;
|
|
437
|
+
const yoffset = Math.floor(shared.loadedVertexCount / shared.bufferTextureWidth);
|
|
438
|
+
if (shared.loadedVertexCount % shared.bufferTextureWidth != 0) {
|
|
439
|
+
width = Math.min(shared.bufferTextureWidth, xoffset + vertexCount) - xoffset;
|
|
440
|
+
height = 1;
|
|
441
|
+
} else if (Math.floor(vertexCount / shared.bufferTextureWidth) > 0) {
|
|
442
|
+
width = shared.bufferTextureWidth;
|
|
443
|
+
height = Math.floor(vertexCount / shared.bufferTextureWidth);
|
|
444
|
+
} else {
|
|
445
|
+
width = vertexCount % shared.bufferTextureWidth;
|
|
446
|
+
height = 1;
|
|
447
|
+
}
|
|
448
|
+
const centerAndScaleTextureProperties = shared.gl.properties.get(shared.centerAndScaleTexture);
|
|
449
|
+
context.bindTexture(context.TEXTURE_2D, centerAndScaleTextureProperties.__webglTexture);
|
|
450
|
+
context.texSubImage2D(context.TEXTURE_2D, 0, xoffset, yoffset, width, height, context.RGBA, context.FLOAT, shared.centerAndScaleData, shared.loadedVertexCount * 4);
|
|
451
|
+
const covAndColorTextureProperties = shared.gl.properties.get(shared.covAndColorTexture);
|
|
452
|
+
context.bindTexture(context.TEXTURE_2D, covAndColorTextureProperties.__webglTexture);
|
|
453
|
+
context.texSubImage2D(context.TEXTURE_2D, 0, xoffset, yoffset, width, height,
|
|
454
|
+
// @ts-ignore
|
|
455
|
+
context.RGBA_INTEGER, context.UNSIGNED_INT, shared.covAndColorData, shared.loadedVertexCount * 4);
|
|
456
|
+
shared.gl.resetState();
|
|
457
|
+
shared.loadedVertexCount += width * height;
|
|
458
|
+
vertexCount -= width * height;
|
|
459
|
+
}
|
|
460
|
+
return matrices;
|
|
461
|
+
}
|
|
462
|
+
function Splat({
|
|
463
|
+
src,
|
|
464
|
+
toneMapped = false,
|
|
465
|
+
alphaTest = 0,
|
|
466
|
+
alphaHash = false,
|
|
467
|
+
chunkSize = 25000,
|
|
468
|
+
...props
|
|
469
|
+
}) {
|
|
470
|
+
extend({
|
|
471
|
+
SplatMaterial
|
|
472
|
+
});
|
|
473
|
+
const ref = React.useRef(null);
|
|
474
|
+
const gl = useThree(state => state.gl);
|
|
475
|
+
const camera = useThree(state => state.camera);
|
|
476
|
+
|
|
477
|
+
// Shared state, globally memoized, the same url re-uses the same daza
|
|
478
|
+
const shared = useLoader(SplatLoader, src, loader => {
|
|
479
|
+
loader.gl = gl;
|
|
480
|
+
loader.chunkSize = chunkSize;
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// Listen to worker results, apply them to the target mesh
|
|
484
|
+
React.useLayoutEffect(() => shared.connect(ref.current), [src]);
|
|
485
|
+
// Update the worker
|
|
486
|
+
useFrame(() => shared.update(ref.current, camera, alphaHash));
|
|
487
|
+
return /*#__PURE__*/React.createElement("mesh", _extends({
|
|
488
|
+
ref: ref,
|
|
489
|
+
frustumCulled: false
|
|
490
|
+
}, props), /*#__PURE__*/React.createElement("splatMaterial", {
|
|
491
|
+
key: `${src}/${alphaTest}/${alphaHash}${SplatMaterial.key}`,
|
|
492
|
+
transparent: !alphaHash,
|
|
493
|
+
depthTest: true,
|
|
494
|
+
alphaTest: alphaHash ? 0 : alphaTest,
|
|
495
|
+
centerAndScaleTexture: shared.centerAndScaleTexture,
|
|
496
|
+
covAndColorTexture: shared.covAndColorTexture,
|
|
497
|
+
depthWrite: alphaHash ? true : alphaTest > 0,
|
|
498
|
+
blending: alphaHash ? THREE.NormalBlending : THREE.CustomBlending,
|
|
499
|
+
blendSrcAlpha: THREE.OneFactor,
|
|
500
|
+
alphaHash: !!alphaHash,
|
|
501
|
+
toneMapped: toneMapped
|
|
502
|
+
}));
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
export { Splat };
|