|
import * as THREE from "three"; |
|
import { OrbitControls } from "three/addons/controls/OrbitControls.js"; |
|
|
|
var renderer = new THREE.WebGLRenderer({ antialias: true }); |
|
renderer.setClearColor(0xffffff, 0); |
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
document.body.appendChild(renderer.domElement); |
|
|
|
|
|
const scene = new THREE.Scene(); |
|
|
|
|
|
const camera = new THREE.PerspectiveCamera( |
|
45, |
|
window.innerWidth / window.innerHeight, |
|
0.1, |
|
200.0 |
|
); |
|
camera.position.set(0, 0, 3.0); |
|
scene.add(camera); |
|
|
|
|
|
const controls = new OrbitControls(camera, renderer.domElement); |
|
|
|
controls.enableDamping = true; |
|
controls.dampingFactor = 0.5; |
|
|
|
let sortReady = false; |
|
function sortSplats(matrices, view) { |
|
const vertexCount = matrices.length / 16; |
|
|
|
let maxDepth = -Infinity; |
|
let minDepth = Infinity; |
|
let depthList = new Float32Array(vertexCount); |
|
let sizeList = new Int32Array(depthList.buffer); |
|
for (let i = 0; i < vertexCount; i++) { |
|
let depth = |
|
view[0] * matrices[i * 16 + 12] - |
|
view[1] * matrices[i * 16 + 13] - |
|
view[2] * matrices[i * 16 + 14]; |
|
depthList[i] = depth; |
|
if (depth > maxDepth) maxDepth = depth; |
|
if (depth < minDepth) minDepth = depth; |
|
} |
|
|
|
|
|
let depthInv = (256 * 256 - 1) / (maxDepth - minDepth); |
|
let counts0 = new Uint32Array(256 * 256); |
|
for (let i = 0; i < vertexCount; i++) { |
|
sizeList[i] = ((depthList[i] - minDepth) * depthInv) | 0; |
|
counts0[sizeList[i]]++; |
|
} |
|
let starts0 = new Uint32Array(256 * 256); |
|
for (let i = 1; i < 256 * 256; i++) |
|
starts0[i] = starts0[i - 1] + counts0[i - 1]; |
|
let depthIndex = new Uint32Array(vertexCount); |
|
for (let i = 0; i < vertexCount; i++) depthIndex[starts0[sizeList[i]]++] = i; |
|
|
|
let sortedMatrices = new Float32Array(vertexCount * 16); |
|
for (let j = 0; j < vertexCount; j++) { |
|
let i = depthIndex[j]; |
|
for (let k = 0; k < 16; k++) { |
|
sortedMatrices[j * 16 + k] = matrices[i * 16 + k]; |
|
} |
|
} |
|
|
|
return sortedMatrices; |
|
} |
|
|
|
function createWorker(self) { |
|
let sortFunction; |
|
let matrices; |
|
self.onmessage = (e) => { |
|
if (e.data.sortFunction) { |
|
eval(e.data.sortFunction); |
|
sortFunction = sortSplats; |
|
} |
|
if (e.data.matrices) { |
|
matrices = new Float32Array(e.data.matrices); |
|
} |
|
if (e.data.view) { |
|
const view = new Float32Array(e.data.view); |
|
const sortedMatrices = sortFunction(matrices, view); |
|
self.postMessage({ sortedMatrices }, [sortedMatrices.buffer]); |
|
} |
|
}; |
|
} |
|
|
|
const size = new THREE.Vector2(); |
|
renderer.getSize(size); |
|
const focal = size.y / 2.0 / Math.tan(((camera.fov / 2.0) * Math.PI) / 180.0); |
|
|
|
const geometry = new THREE.PlaneGeometry(4, 4); |
|
const material = new THREE.ShaderMaterial({ |
|
uniforms: { |
|
viewport: { value: new Float32Array([size.x, size.y]) }, |
|
focal: { value: focal }, |
|
}, |
|
vertexShader: `varying vec4 vColor; |
|
varying vec2 vPosition; |
|
uniform vec2 viewport; |
|
uniform float focal; |
|
|
|
void main () { |
|
vec4 center = vec4(instanceMatrix[3][0], instanceMatrix[3][1], instanceMatrix[3][2], 1); |
|
// Adjust View Pose |
|
mat4 adjViewMatrix = inverse(viewMatrix); |
|
adjViewMatrix[0][1] *= -1.0; |
|
adjViewMatrix[1][0] *= -1.0; |
|
adjViewMatrix[1][2] *= -1.0; |
|
adjViewMatrix[2][1] *= -1.0; |
|
adjViewMatrix[3][1] *= -1.0; |
|
adjViewMatrix = inverse(adjViewMatrix); |
|
mat4 modelView = adjViewMatrix * modelMatrix; |
|
|
|
vec4 camspace = modelView * center; |
|
vec4 pos2d = projectionMatrix * mat4(1,0,0,0,0,-1,0,0,0,0,1,0,0,0,0,1) * camspace; |
|
|
|
float bounds = 1.2 * pos2d.w; |
|
if (pos2d.z < -pos2d.w || pos2d.x < -bounds || pos2d.x > bounds |
|
|| pos2d.y < -bounds || pos2d.y > bounds) { |
|
gl_Position = vec4(0.0, 0.0, 2.0, 1.0); |
|
return; |
|
} |
|
|
|
mat3 J = mat3( |
|
focal / camspace.z, 0., -(focal * camspace.x) / (camspace.z * camspace.z), |
|
0., -focal / camspace.z, (focal * camspace.y) / (camspace.z * camspace.z), |
|
0., 0., 0. |
|
); |
|
|
|
mat3 W = transpose(mat3(modelView)); |
|
mat3 T = W * J; |
|
mat3 cov = transpose(T) * mat3(instanceMatrix) * T; |
|
|
|
vec2 vCenter = vec2(pos2d) / pos2d.w; |
|
|
|
float diagonal1 = cov[0][0] + 0.3; |
|
float offDiagonal = cov[0][1]; |
|
float diagonal2 = cov[1][1] + 0.3; |
|
|
|
float mid = 0.5 * (diagonal1 + diagonal2); |
|
float radius = length(vec2((diagonal1 - diagonal2) / 2.0, offDiagonal)); |
|
float lambda1 = mid + radius; |
|
float lambda2 = max(mid - radius, 0.1); |
|
vec2 diagonalVector = normalize(vec2(offDiagonal, lambda1 - diagonal1)); |
|
vec2 v1 = min(sqrt(2.0 * lambda1), 1024.0) * diagonalVector; |
|
vec2 v2 = min(sqrt(2.0 * lambda2), 1024.0) * vec2(diagonalVector.y, -diagonalVector.x); |
|
|
|
vColor = vec4(instanceMatrix[0][3], instanceMatrix[1][3], instanceMatrix[2][3], instanceMatrix[3][3]); |
|
vPosition = position.xy; |
|
|
|
gl_Position = vec4( |
|
vCenter |
|
+ position.x * v2 / viewport * 2.0 |
|
+ position.y * v1 / viewport * 2.0, 0.0, 1.0); |
|
}`, |
|
fragmentShader: `varying vec4 vColor; |
|
varying vec2 vPosition; |
|
|
|
void main () { |
|
float A = -dot(vPosition, vPosition); |
|
if (A < -4.0) discard; |
|
float B = exp(A) * vColor.a; |
|
gl_FragColor = vec4(B * vColor.rgb, B); |
|
}`, |
|
}); |
|
|
|
material.blending = THREE.CustomBlending; |
|
material.blendEquation = THREE.AddEquation; |
|
material.blendSrc = THREE.OneMinusDstAlphaFactor; |
|
material.blendDst = THREE.OneFactor; |
|
material.blendSrcAlpha = THREE.OneMinusDstAlphaFactor; |
|
material.blendDstAlpha = THREE.OneFactor; |
|
material.depthTest = false; |
|
material.needsUpdate = true; |
|
|
|
window.addEventListener("resize", () => { |
|
renderer.getSize(size); |
|
const focal = |
|
size.y / |
|
2.0 / |
|
Math.tan(((camera.components.camera.data.fov / 2.0) * Math.PI) / 180.0); |
|
material.uniforms.viewport.value[0] = size.x; |
|
material.uniforms.viewport.value[1] = size.y; |
|
material.uniforms.focal.value = focal; |
|
}); |
|
const urlParams = new URLSearchParams(window.location.search); |
|
const name = urlParams.get('url'); |
|
|
|
console.log(name); |
|
|
|
fetch(name) |
|
.then((data) => data.blob()) |
|
.then((res) => res.arrayBuffer()) |
|
.then((buffer) => { |
|
const rowLength = 3 * 4 + 3 * 4 + 4 + 4; |
|
const vertexCount = Math.floor(buffer.byteLength / rowLength); |
|
const f_buffer = new Float32Array(buffer); |
|
const u_buffer = new Uint8Array(buffer); |
|
|
|
const matrices = new Float32Array(vertexCount * 16); |
|
for (let i = 0; i < vertexCount; i++) { |
|
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 |
|
); |
|
const center = new THREE.Vector3( |
|
f_buffer[8 * i + 0], |
|
f_buffer[8 * i + 1], |
|
-f_buffer[8 * i + 2] |
|
); |
|
const scale = new THREE.Vector3( |
|
f_buffer[8 * i + 3 + 0], |
|
f_buffer[8 * i + 3 + 1], |
|
f_buffer[8 * i + 3 + 2] |
|
); |
|
|
|
const mtx = new THREE.Matrix4(); |
|
mtx.makeRotationFromQuaternion(quat); |
|
mtx.transpose(); |
|
mtx.scale(scale); |
|
const mtx_t = mtx.clone(); |
|
mtx.transpose(); |
|
mtx.premultiply(mtx_t); |
|
mtx.setPosition(center); |
|
|
|
|
|
mtx.elements[3] = u_buffer[32 * i + 24 + 0] / 255; |
|
mtx.elements[7] = u_buffer[32 * i + 24 + 1] / 255; |
|
mtx.elements[11] = u_buffer[32 * i + 24 + 2] / 255; |
|
mtx.elements[15] = u_buffer[32 * i + 24 + 3] / 255; |
|
|
|
for (let j = 0; j < 16; j++) { |
|
matrices[i * 16 + j] = mtx.elements[j]; |
|
} |
|
} |
|
|
|
const view = new Float32Array([ |
|
camera.matrixWorld.elements[2], |
|
camera.matrixWorld.elements[6], |
|
camera.matrixWorld.elements[10], |
|
]); |
|
|
|
const iMesh = new THREE.InstancedMesh(geometry, material, vertexCount); |
|
iMesh.frustumCulled = false; |
|
iMesh.instanceMatrix.array = sortSplats(matrices, view); |
|
iMesh.instanceMatrix.needsUpdate = true; |
|
scene.add(iMesh); |
|
|
|
const worker = new Worker( |
|
URL.createObjectURL( |
|
new Blob(["(", createWorker.toString(), ")(self)"], { |
|
type: "application/javascript", |
|
}) |
|
) |
|
); |
|
|
|
worker.postMessage( |
|
{ |
|
sortFunction: sortSplats.toString(), |
|
matrices: matrices.buffer, |
|
}, |
|
[matrices.buffer] |
|
); |
|
|
|
worker.onmessage = (e) => { |
|
iMesh.instanceMatrix.array = new Float32Array(e.data.sortedMatrices); |
|
iMesh.instanceMatrix.needsUpdate = true; |
|
sortReady = true; |
|
}; |
|
sortReady = true; |
|
|
|
function animate() { |
|
requestAnimationFrame(animate); |
|
if (sortReady) { |
|
sortReady = false; |
|
const view = new Float32Array([ |
|
camera.matrixWorld.elements[2], |
|
camera.matrixWorld.elements[6], |
|
camera.matrixWorld.elements[10], |
|
]); |
|
worker.postMessage({ view }, [view.buffer]); |
|
} |
|
controls.update(); |
|
renderer.render(scene, camera); |
|
} |
|
|
|
animate(); |
|
}) |
|
.catch((error) => { |
|
console.error(error); |
|
}); |
|
|