UNPKG

@pnext/three-loader

Version:

Potree loader for ThreeJS, converted and adapted to Typescript.

602 lines (481 loc) 17.9 kB
import { PointAttribute, PointAttributeTypes } from './point-attributes.ts'; const typedArrayMapping = { int8: Int8Array, int16: Int16Array, int32: Int32Array, int64: Float64Array, uint8: Uint8Array, uint16: Uint16Array, uint32: Uint32Array, uint64: Float64Array, float: Float32Array, double: Float64Array, }; onmessage = function (event) { const { buffer, pointAttributes, scale, name, min, max, size, offset, numPoints, harmonicsEnabled, } = event.data; const view = new DataView(buffer); const attributeBuffers = {}; const bytesPerPointPosition = 12; const bytesPerPointColor = 4 * 3; const bytesPerPointScale = 4 * 3; const bytesPerPointRotation = 4 * 4; const bytesPerPointOpacity = 4; const bytesPerPointHarmonics = 4 * 3; const gridSize = 32; const grid = new Uint32Array(gridSize ** 3); const toIndex = (x, y, z) => { // min is already subtracted const dx = (gridSize * x) / size.x; const dy = (gridSize * y) / size.y; const dz = (gridSize * z) / size.z; const ix = Math.min(parseInt(dx), gridSize - 1); const iy = Math.min(parseInt(dy), gridSize - 1); const iz = Math.min(parseInt(dz), gridSize - 1); const index = ix + iy * gridSize + iz * gridSize * gridSize; return index; }; const clamp = function (val, min, max) { return Math.max(Math.min(val, max), min); }; let numOccupiedCells = 0; const tightBoxMin = [ Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, ]; const tightBoxMax = [ Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, ]; const bufferScales = new ArrayBuffer(numPoints * 4 * 3); const scales = new Float32Array(bufferScales); const bufferRotations = new ArrayBuffer(numPoints * 4 * 4); const rotations = new Float32Array(bufferRotations); const buff = new ArrayBuffer(numPoints * 4 * 4); const positions = new Float32Array(buff); const bufferColors = new ArrayBuffer(numPoints * 4 * 4); const colors = new Float32Array(bufferColors); const rawPositionsBuffer = new ArrayBuffer(numPoints * 4 * 4); const rawPositions = new Float32Array(rawPositionsBuffer); const harmonicsBuffer = new ArrayBuffer(numPoints * 4 * 45); let harmonics = new Float32Array(harmonicsBuffer); let harmonicsScale = 0; const harmonicsBandsName = [ 'sh_band_1_triplet_0', 'sh_band_1_triplet_1', 'sh_band_1_triplet_2', 'sh_band_2_triplet_0', 'sh_band_2_triplet_1', 'sh_band_2_triplet_2', 'sh_band_2_triplet_3', 'sh_band_2_triplet_4', 'sh_band_3_triplet_0', 'sh_band_3_triplet_1', 'sh_band_3_triplet_2', 'sh_band_3_triplet_3', 'sh_band_3_triplet_4', 'sh_band_3_triplet_5', 'sh_band_3_triplet_6', ]; // positions, colors, opacities, scales, rotations for (const pointAttribute of pointAttributes.attributes) { if (['POSITION_CARTESIAN', 'position'].includes(pointAttribute.name)) { for (let j = 0; j < numPoints; j++) { const pointOffset = j * bytesPerPointPosition; const _x = view.getFloat32(pointOffset + 0, true); const _y = view.getFloat32(pointOffset + 4, true); const _z = view.getFloat32(pointOffset + 8, true); const x = _x + offset[0] - min.x; const y = _y + offset[1] - min.y; const z = _z + offset[2] - min.z; tightBoxMin[0] = Math.min(tightBoxMin[0], x); tightBoxMin[1] = Math.min(tightBoxMin[1], y); tightBoxMin[2] = Math.min(tightBoxMin[2], z); tightBoxMax[0] = Math.max(tightBoxMax[0], x); tightBoxMax[1] = Math.max(tightBoxMax[1], y); tightBoxMax[2] = Math.max(tightBoxMax[2], z); const index = toIndex(x, y, z); const count = grid[index]++; if (count === 0) { numOccupiedCells++; } positions[4 * j + 0] = x; positions[4 * j + 1] = y; positions[4 * j + 2] = z; rawPositions[4 * j + 0] = _x; rawPositions[4 * j + 1] = _y; rawPositions[4 * j + 2] = _z; } attributeBuffers['raw_position'] = { buffer: rawPositionsBuffer, attribute: 'raw_position' }; attributeBuffers['position'] = { buffer: buff, attribute: 'position' }; } else if (['sh_band_0'].includes(pointAttribute.name)) { const SH_C0 = 0.28209479177387814; for (let j = 0; j < numPoints; j++) { const c0 = 4 * j + 0; const c1 = 4 * j + 1; const c2 = 4 * j + 2; const c3 = 4 * j + 3; const colorOffset = j * bytesPerPointColor + numPoints * bytesPerPointPosition; const opacityOffset = j * bytesPerPointOpacity + numPoints * (bytesPerPointPosition + bytesPerPointColor); // rgb const r = view.getFloat32(colorOffset + 0, true); const g = view.getFloat32(colorOffset + 4, true); const b = view.getFloat32(colorOffset + 8, true); colors[c0] = (0.5 + SH_C0 * r) * 255; colors[c1] = (0.5 + SH_C0 * g) * 255; colors[c2] = (0.5 + SH_C0 * b) * 255; colors[c0] = clamp(Math.floor(colors[c0]), 0, 255); colors[c1] = clamp(Math.floor(colors[c1]), 0, 255); colors[c2] = clamp(Math.floor(colors[c2]), 0, 255); // opacity let a = view.getFloat32(opacityOffset, true); a = (1 / (1 + Math.exp(-a))) * 255; colors[c3] = clamp(Math.floor(a), 0, 255); } } else if (['scale'].includes(pointAttribute.name)) { let maxScale = 0; let index = 0; for (let j = 0; j < numPoints; j++) { const scaleOffset = j * bytesPerPointScale + numPoints * (bytesPerPointPosition + bytesPerPointColor + bytesPerPointOpacity); const sx = view.getFloat32(scaleOffset + 0, true); const sy = view.getFloat32(scaleOffset + 4, true); const sz = view.getFloat32(scaleOffset + 8, true); scales[3 * j + 0] = Math.exp(sx); scales[3 * j + 1] = Math.exp(sy); scales[3 * j + 2] = Math.exp(sz); let s = Math.max(Math.exp(sx), Math.exp(sy)); if (s > maxScale) { maxScale = s; index = j; } } attributeBuffers['scale'] = { buffer: bufferScales, attribute: 'scale' }; } else if (['rotation'].includes(pointAttribute.name)) { const tempRotation = { x: 0, y: 0, z: 0, w: 0 }; for (let j = 0; j < numPoints; j++) { const rotationOffset = j * bytesPerPointRotation + numPoints * (bytesPerPointPosition + bytesPerPointColor + bytesPerPointOpacity + bytesPerPointScale); const rx = view.getFloat32(rotationOffset + 0, true); const ry = view.getFloat32(rotationOffset + 4, true); const rz = view.getFloat32(rotationOffset + 8, true); const rw = view.getFloat32(rotationOffset + 12, true); tempRotation.x = rx; tempRotation.y = ry; tempRotation.z = rz; tempRotation.w = rw; let l = Math.sqrt(rx * rx + ry * ry + rz * rz + rw * rw); if (l == 0) { tempRotation.x = 0; tempRotation.y = 0; tempRotation.z = 0; tempRotation.w = 1; } else { tempRotation.x = rx / l; tempRotation.y = ry / l; tempRotation.z = rz / l; tempRotation.w = rw / l; } rotations[4 * j + 0] = tempRotation.x; rotations[4 * j + 1] = tempRotation.y; rotations[4 * j + 2] = tempRotation.z; rotations[4 * j + 3] = tempRotation.w; } attributeBuffers['orientation'] = { buffer: bufferRotations, attribute: 'orientation' }; } //For the spherical harmonics else if (pointAttribute.name.indexOf('triplet') > -1 && harmonicsEnabled) { for (let j = 0; j < numPoints; j++) { let harmonicIndex = harmonicsBandsName.indexOf(pointAttribute.name); const harmonicsOffset = j * bytesPerPointHarmonics + numPoints * (bytesPerPointPosition + bytesPerPointColor + bytesPerPointOpacity + bytesPerPointScale + bytesPerPointRotation + harmonicIndex * bytesPerPointHarmonics); const r = view.getFloat32(harmonicsOffset + 0, true); const g = view.getFloat32(harmonicsOffset + 4, true); const b = view.getFloat32(harmonicsOffset + 8, true); harmonics[45 * j + harmonicIndex * 3 + 0] = r; harmonics[45 * j + harmonicIndex * 3 + 1] = g; harmonics[45 * j + harmonicIndex * 3 + 2] = b; harmonicsScale = Math.max(Math.abs(r), harmonicsScale); harmonicsScale = Math.max(Math.abs(g), harmonicsScale); harmonicsScale = Math.max(Math.abs(b), harmonicsScale); } } } //calculate the convariance from scales and rotations. { function multiplyMatricex(ae, be) { const te = new Array(9); const a11 = ae[0], a12 = ae[3], a13 = ae[6]; const a21 = ae[1], a22 = ae[4], a23 = ae[7]; const a31 = ae[2], a32 = ae[5], a33 = ae[8]; const b11 = be[0], b12 = be[3], b13 = be[6]; const b21 = be[1], b22 = be[4], b23 = be[7]; const b31 = be[2], b32 = be[5], b33 = be[8]; te[0] = a11 * b11 + a12 * b21 + a13 * b31; te[3] = a11 * b12 + a12 * b22 + a13 * b32; te[6] = a11 * b13 + a12 * b23 + a13 * b33; te[1] = a21 * b11 + a22 * b21 + a23 * b31; te[4] = a21 * b12 + a22 * b22 + a23 * b32; te[7] = a21 * b13 + a22 * b23 + a23 * b33; te[2] = a31 * b11 + a32 * b21 + a33 * b31; te[5] = a31 * b12 + a32 * b22 + a33 * b32; te[8] = a31 * b13 + a32 * b23 + a33 * b33; return te; } function generateCovarianceMatrix(quaternion, scale) { const covarianceMatrix = new Array(16); const x = quaternion.x, y = quaternion.y, z = quaternion.z, w = quaternion.w; const x2 = x + x, y2 = y + y, z2 = z + z; const xx = x * x2, xy = x * y2, xz = x * z2; const yy = y * y2, yz = y * z2, zz = z * z2; const wx = w * x2, wy = w * y2, wz = w * z2; const sx = scale.x, sy = scale.y, sz = scale.z; covarianceMatrix[0] = (1 - (yy + zz)) * sx; covarianceMatrix[1] = (xy + wz) * sx; covarianceMatrix[2] = (xz - wy) * sx; covarianceMatrix[3] = (xy - wz) * sy; covarianceMatrix[4] = (1 - (xx + zz)) * sy; covarianceMatrix[5] = (yz + wx) * sy; covarianceMatrix[6] = (xz + wy) * sz; covarianceMatrix[7] = (yz - wx) * sz; covarianceMatrix[8] = (1 - (xx + yy)) * sz; const transposeCovariance = covarianceMatrix.map((el) => el); let tmp; const m = transposeCovariance; tmp = m[1]; m[1] = m[3]; m[3] = tmp; tmp = m[2]; m[2] = m[6]; m[6] = tmp; tmp = m[5]; m[5] = m[7]; m[7] = tmp; return multiplyMatricex(covarianceMatrix, transposeCovariance); } function computeCovariance(scale, rotation, outOffset) { let transformedCovariance = generateCovarianceMatrix(rotation, scale); covariances0[4 * outOffset + 0] = transformedCovariance[0]; covariances0[4 * outOffset + 1] = transformedCovariance[3]; covariances0[4 * outOffset + 2] = transformedCovariance[6]; covariances0[4 * outOffset + 3] = transformedCovariance[4]; covariances1[2 * outOffset + 0] = transformedCovariance[7]; covariances1[2 * outOffset + 1] = transformedCovariance[8]; } const covariances0Buffer = new ArrayBuffer(numPoints * 4 * 4); const covariances0 = new Float32Array(covariances0Buffer); const covariances1Buffer = new ArrayBuffer(numPoints * 4 * 2); const covariances1 = new Float32Array(covariances1Buffer); for (let j = 0; j < numPoints; j++) { const quat = { x: 0, y: 0, z: 0, w: 0 }; const scale = { x: 0, y: 0, z: 0 }; quat.w = rotations[4 * j + 0]; quat.x = rotations[4 * j + 1]; quat.y = rotations[4 * j + 2]; quat.z = rotations[4 * j + 3]; scale.x = scales[3 * j + 0]; scale.y = scales[3 * j + 1]; scale.z = scales[3 * j + 2]; computeCovariance(scale, quat, j); } attributeBuffers['COVARIANCE0'] = { buffer: covariances0Buffer, attribute: PointAttribute.COVARIANCE0, }; attributeBuffers['COVARIANCE1'] = { buffer: covariances1Buffer, attribute: PointAttribute.COVARIANCE1, }; } //Compress the position and the color in a single attribute { const rgbaArrayToInteger = function (arr) { return arr[0] + (arr[1] << 8) + (arr[2] << 16) + (arr[3] << 24); }; let colX = Math.floor(255 * Math.random()); let colY = Math.floor(255 * Math.random()); let colZ = Math.floor(255 * Math.random()); let m0 = 0; let m1 = 1 - m0; const uintEncodedFloat = (function () { const floatView = new Float32Array(1); const int32View = new Int32Array(floatView.buffer); return function (f) { floatView[0] = f; return int32View[0]; }; })(); const posColorBuffer = new ArrayBuffer(numPoints * 4 * 4); const posColor = new Int32Array(posColorBuffer); for (let j = 0; j < numPoints; j++) { const color = { x: 0, y: 0, z: 0, w: 0 }; const pos = { x: 0, y: 0, z: 0 }; color.x = colors[4 * j + 0]; color.y = colors[4 * j + 1]; color.z = colors[4 * j + 2]; color.w = colors[4 * j + 3]; pos.x = positions[4 * j + 0]; pos.y = positions[4 * j + 1]; pos.z = positions[4 * j + 2]; let encodedColor = rgbaArrayToInteger([ colX * m0 + color.x * m1, colY * m0 + color.y * m1, colZ * m0 + color.z * m1, color.w, ]); pos.x = uintEncodedFloat(pos.x); pos.y = uintEncodedFloat(pos.y); pos.z = uintEncodedFloat(pos.z); posColor[4 * j + 0] = encodedColor; posColor[4 * j + 1] = pos.x; posColor[4 * j + 2] = pos.y; posColor[4 * j + 3] = pos.z; } attributeBuffers['POS_COLOR'] = { buffer: posColorBuffer, attribute: PointAttribute.POS_COLOR }; } //Setup the harmonics normalised and compressed { const compressedHarmonicsBuffer1 = new ArrayBuffer(numPoints * 4 * 3); const compressedHarmonics1 = new Uint32Array(compressedHarmonicsBuffer1); const compressedHarmonicsBuffer2 = new ArrayBuffer(numPoints * 4 * 5); const compressedHarmonics2 = new Uint32Array(compressedHarmonicsBuffer2); const compressedHarmonicsBuffer3 = new ArrayBuffer(numPoints * 4 * 7); const compressedHarmonics3 = new Uint32Array(compressedHarmonicsBuffer3); harmonics = harmonics.map((value, index) => { value = value / harmonicsScale; value = Math.min(Math.max(value, -1), 1); value = 0.5 * value + 0.5; let scaler = index % 3 == 1 ? 1023 : 2047; value = Math.min(Math.max(Math.floor(value * scaler), 0), scaler); return value; }); for (let j = 0; j < numPoints; j++) { for (let i = 0; i < 15; i++) { let r = harmonics[45 * j + 3 * i + 0]; let g = harmonics[45 * j + 3 * i + 1]; let b = harmonics[45 * j + 3 * i + 2]; if (i < 3) { compressedHarmonics1[3 * j + i - 0] = (r << 21) | (g << 11) | b; } if (i >= 3 && i < 8) { compressedHarmonics2[5 * j + i - 3] = (r << 21) | (g << 11) | b; } if (i >= 8) { compressedHarmonics3[7 * j + i - 8] = (r << 21) | (g << 11) | b; } } } attributeBuffers['HARMONICS1'] = { buffer: compressedHarmonicsBuffer1, attribute: 'HARMONICS1', }; attributeBuffers['HARMONICS2'] = { buffer: compressedHarmonicsBuffer2, attribute: 'HARMONICS2', }; attributeBuffers['HARMONICS3'] = { buffer: compressedHarmonicsBuffer3, attribute: 'HARMONICS3', }; } const occupancy = parseInt(numPoints / numOccupiedCells); { // add indices const buff = new ArrayBuffer(numPoints * 4); const indices = new Uint32Array(buff); for (let i = 0; i < numPoints; i++) { indices[i] = i; } attributeBuffers['INDICES'] = { buffer: buff, attribute: PointAttribute.INDICES }; } { // handle attribute vectors const vectors = pointAttributes.vectors; for (let vector of vectors) { const { name, attributes } = vector; const numVectorElements = attributes.length; const buffer = new ArrayBuffer(numVectorElements * numPoints * 4); const f32 = new Float32Array(buffer); const iElement = 0; for (let sourceName of attributes) { const sourceBuffer = attributeBuffers[sourceName]; const { offset, scale } = sourceBuffer; const view = new DataView(sourceBuffer.buffer); const getter = view.getFloat32.bind(view); for (let j = 0; j < numPoints; j++) { const value = getter(j * 4, true); f32[j * numVectorElements + iElement] = value / scale + offset; } iElement++; } const vecAttribute = new PointAttribute(name, PointAttributeTypes.DATA_TYPE_FLOAT, 3); attributeBuffers[name] = { buffer: buffer, attribute: vecAttribute, }; } } const message = { buffer: buffer, attributeBuffers: attributeBuffers, density: occupancy, tightBoundingBox: { min: tightBoxMin, max: tightBoxMax }, }; const transferables = []; for (let property in message.attributeBuffers) { transferables.push(message.attributeBuffers[property].buffer); } transferables.push(buffer); postMessage(message, transferables); };