UNPKG

@mkkellogg/gaussian-splats-3d

Version:
1,213 lines (1,014 loc) 586 kB
import * as THREE from 'three'; import { Ray as Ray$1, Plane, MathUtils, EventDispatcher, Vector3, MOUSE, TOUCH, Quaternion, Spherical, Vector2 } from 'three'; /** * AbortablePromise: A quick & dirty wrapper for JavaScript's Promise class that allows the underlying * asynchronous operation to be cancelled. It is only meant for simple situations where no complex promise * chaining or merging occurs. It needs a significant amount of work to truly replicate the full * functionality of JavaScript's Promise class. Look at Util.fetchWithProgress() for example usage. * * This class was primarily added to allow splat scene downloads to be cancelled. It has not been tested * very thoroughly and the implementation is kinda janky. If you can at all help it, please avoid using it :) */ class AbortablePromise { static idGen = 0; constructor(promiseFunc, abortHandler) { let resolver; let rejecter; this.promise = new Promise((resolve, reject) => { resolver = resolve; rejecter = reject; }); const promiseResolve = resolver.bind(this); const promiseReject = rejecter.bind(this); const resolve = (...args) => { promiseResolve(...args); }; const reject = (error) => { promiseReject(error); }; promiseFunc(resolve.bind(this), reject.bind(this)); this.abortHandler = abortHandler; this.id = AbortablePromise.idGen++; } then(onResolve) { return new AbortablePromise((resolve, reject) => { this.promise = this.promise .then((...args) => { const onResolveResult = onResolve(...args); if (onResolveResult instanceof Promise || onResolveResult instanceof AbortablePromise) { onResolveResult.then((...args2) => { resolve(...args2); }); } else { resolve(onResolveResult); } }) .catch((error) => { reject(error); }); }, this.abortHandler); } catch(onFail) { return new AbortablePromise((resolve) => { this.promise = this.promise.then((...args) => { resolve(...args); }) .catch(onFail); }, this.abortHandler); } abort(reason) { if (this.abortHandler) this.abortHandler(reason); } } class AbortedPromiseError extends Error { constructor(msg) { super(msg); } } const floatToHalf = function() { const floatView = new Float32Array(1); const int32View = new Int32Array(floatView.buffer); return function(val) { floatView[0] = val; const x = int32View[0]; let bits = (x >> 16) & 0x8000; let m = (x >> 12) & 0x07ff; const e = (x >> 23) & 0xff; if (e < 103) return bits; if (e > 142) { bits |= 0x7c00; bits |= ((e == 255) ? 0 : 1) && (x & 0x007fffff); return bits; } if (e < 113) { m |= 0x0800; bits |= (m >> (114 - e)) + ((m >> (113 - e)) & 1); return bits; } bits |= (( e - 112) << 10) | (m >> 1); bits += m & 1; return bits; }; }(); const uintEncodedFloat = function() { const floatView = new Float32Array(1); const int32View = new Int32Array(floatView.buffer); return function(f) { floatView[0] = f; return int32View[0]; }; }(); const rgbaToInteger = function(r, g, b, a) { return r + (g << 8) + (b << 16) + (a << 24); }; const rgbaArrayToInteger = function(arr, offset) { return arr[offset] + (arr[offset + 1] << 8) + (arr[offset + 2] << 16) + (arr[offset + 3] << 24); }; const fetchWithProgress = function(path, onProgress, saveChunks = true, headers) { const abortController = new AbortController(); const signal = abortController.signal; let aborted = false; const abortHandler = (reason) => { abortController.abort(reason); aborted = true; }; return new AbortablePromise((resolve, reject) => { const fetchOptions = { signal }; if (headers) fetchOptions.headers = headers; fetch(path, fetchOptions) .then(async (data) => { // Handle error conditions where data is still returned if (!data.ok) { const errorText = await data.text(); reject(new Error(`Fetch failed: ${data.status} ${data.statusText} ${errorText}`)); return; } const reader = data.body.getReader(); let bytesDownloaded = 0; let _fileSize = data.headers.get('Content-Length'); let fileSize = _fileSize ? parseInt(_fileSize) : undefined; const chunks = []; while (!aborted) { try { const { value: chunk, done } = await reader.read(); if (done) { if (onProgress) { onProgress(100, '100%', chunk, fileSize); } if (saveChunks) { const buffer = new Blob(chunks).arrayBuffer(); resolve(buffer); } else { resolve(); } break; } bytesDownloaded += chunk.length; let percent; let percentLabel; if (fileSize !== undefined) { percent = bytesDownloaded / fileSize * 100; percentLabel = `${percent.toFixed(2)}%`; } if (saveChunks) { chunks.push(chunk); } if (onProgress) { onProgress(percent, percentLabel, chunk, fileSize); } } catch (error) { reject(error); return; } } }) .catch((error) => { reject(new AbortedPromiseError(error)); }); }, abortHandler); }; const clamp = function(val, min, max) { return Math.max(Math.min(val, max), min); }; const getCurrentTime = function() { return performance.now() / 1000; }; const disposeAllMeshes = (object3D) => { if (object3D.geometry) { object3D.geometry.dispose(); object3D.geometry = null; } if (object3D.material) { object3D.material.dispose(); object3D.material = null; } if (object3D.children) { for (let child of object3D.children) { disposeAllMeshes(child); } } }; const delayedExecute = (func, fast) => { return new Promise((resolve) => { window.setTimeout(() => { resolve(func()); }, fast ? 1 : 50); }); }; const getSphericalHarmonicsComponentCountForDegree = (sphericalHarmonicsDegree = 0) => { switch (sphericalHarmonicsDegree) { case 1: return 9; case 2: return 24; } return 0; }; const nativePromiseWithExtractedComponents = () => { let resolver; let rejecter; const promise = new Promise((resolve, reject) => { resolver = resolve; rejecter = reject; }); return { 'promise': promise, 'resolve': resolver, 'reject': rejecter }; }; const abortablePromiseWithExtractedComponents = (abortHandler) => { let resolver; let rejecter; if (!abortHandler) { abortHandler = () => {}; } const promise = new AbortablePromise((resolve, reject) => { resolver = resolve; rejecter = reject; }, abortHandler); return { 'promise': promise, 'resolve': resolver, 'reject': rejecter }; }; class Semver { constructor(major, minor, patch) { this.major = major; this.minor = minor; this.patch = patch; } toString() { return `${this.major}_${this.minor}_${this.patch}`; } } function isIOS() { const ua = navigator.userAgent; return ua.indexOf('iPhone') > 0 || ua.indexOf('iPad') > 0; } function getIOSSemever() { if (isIOS()) { const extract = navigator.userAgent.match(/OS (\d+)_(\d+)_?(\d+)?/); return new Semver( parseInt(extract[1] || 0, 10), parseInt(extract[2] || 0, 10), parseInt(extract[3] || 0, 10) ); } else { return null; // or [0,0,0] } } const BASE_COMPONENT_COUNT = 14; class UncompressedSplatArray { static OFFSET = { X: 0, Y: 1, Z: 2, SCALE0: 3, SCALE1: 4, SCALE2: 5, ROTATION0: 6, ROTATION1: 7, ROTATION2: 8, ROTATION3: 9, FDC0: 10, FDC1: 11, FDC2: 12, OPACITY: 13, FRC0: 14, FRC1: 15, FRC2: 16, FRC3: 17, FRC4: 18, FRC5: 19, FRC6: 20, FRC7: 21, FRC8: 22, FRC9: 23, FRC10: 24, FRC11: 25, FRC12: 26, FRC13: 27, FRC14: 28, FRC15: 29, FRC16: 30, FRC17: 31, FRC18: 32, FRC19: 33, FRC20: 34, FRC21: 35, FRC22: 36, FRC23: 37 }; constructor(sphericalHarmonicsDegree = 0) { this.sphericalHarmonicsDegree = sphericalHarmonicsDegree; this.sphericalHarmonicsCount = getSphericalHarmonicsComponentCountForDegree(this.sphericalHarmonicsDegree); this.componentCount = this.sphericalHarmonicsCount + BASE_COMPONENT_COUNT; this.defaultSphericalHarmonics = new Array(this.sphericalHarmonicsCount).fill(0); this.splats = []; this.splatCount = 0; } static createSplat(sphericalHarmonicsDegree = 0) { const baseSplat = [0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]; let shEntries = getSphericalHarmonicsComponentCountForDegree(sphericalHarmonicsDegree); for (let i = 0; i < shEntries; i++) baseSplat.push(0); return baseSplat; } addSplat(splat) { this.splats.push(splat); this.splatCount++; } getSplat(index) { return this.splats[index]; } addDefaultSplat() { const newSplat = UncompressedSplatArray.createSplat(this.sphericalHarmonicsDegree); this.addSplat(newSplat); return newSplat; } addSplatFromComonents(x, y, z, scale0, scale1, scale2, rot0, rot1, rot2, rot3, r, g, b, opacity, ...rest) { const newSplat = [x, y, z, scale0, scale1, scale2, rot0, rot1, rot2, rot3, r, g, b, opacity, ...this.defaultSphericalHarmonics]; for (let i = 0; i < rest.length && i < this.sphericalHarmonicsCount; i++) { newSplat[i] = rest[i]; } this.addSplat(newSplat); return newSplat; } addSplatFromArray(src, srcIndex) { const srcSplat = src.splats[srcIndex]; const newSplat = UncompressedSplatArray.createSplat(this.sphericalHarmonicsDegree); for (let i = 0; i < this.componentCount && i < srcSplat.length; i++) { newSplat[i] = srcSplat[i]; } this.addSplat(newSplat); } } class Constants { static DefaultSplatSortDistanceMapPrecision = 16; static MemoryPageSize = 65536; static BytesPerFloat = 4; static BytesPerInt = 4; static MaxScenes = 32; static ProgressiveLoadSectionSize = 262144; static ProgressiveLoadSectionDelayDuration = 15; static SphericalHarmonics8BitCompressionRange = 3; } const DefaultSphericalHarmonics8BitCompressionRange = Constants.SphericalHarmonics8BitCompressionRange; const DefaultSphericalHarmonics8BitCompressionHalfRange = DefaultSphericalHarmonics8BitCompressionRange / 2.0; const toHalfFloat = THREE.DataUtils.toHalfFloat.bind(THREE.DataUtils); const fromHalfFloat$1 = THREE.DataUtils.fromHalfFloat.bind(THREE.DataUtils); const toUncompressedFloat = (f, compressionLevel, isSH = false, range8BitMin, range8BitMax) => { if (compressionLevel === 0) { return f; } else if (compressionLevel === 1 || compressionLevel === 2 && !isSH) { return THREE.DataUtils.fromHalfFloat(f); } else if (compressionLevel === 2) { return fromUint8(f, range8BitMin, range8BitMax); } }; const toUint8 = (v, rangeMin, rangeMax) => { v = clamp(v, rangeMin, rangeMax); const range = (rangeMax - rangeMin); return clamp(Math.floor((v - rangeMin) / range * 255), 0, 255); }; const fromUint8 = (v, rangeMin, rangeMax) => { const range = (rangeMax - rangeMin); return (v / 255 * range + rangeMin); }; const fromHalfFloatToUint8 = (v, rangeMin, rangeMax) => { return toUint8(fromHalfFloat$1(v, rangeMin, rangeMax)); }; const fromUint8ToHalfFloat = (v, rangeMin, rangeMax) => { return toHalfFloat(fromUint8(v, rangeMin, rangeMax)); }; const dataViewFloatForCompressionLevel = (dataView, floatIndex, compressionLevel, isSH = false) => { if (compressionLevel === 0) { return dataView.getFloat32(floatIndex * 4, true); } else if (compressionLevel === 1 || compressionLevel === 2 && !isSH) { return dataView.getUint16(floatIndex * 2, true); } else { return dataView.getUint8(floatIndex, true); } }; const convertBetweenCompressionLevels = function() { const noop = (v) => v; return function(val, fromLevel, toLevel, isSH = false) { if (fromLevel === toLevel) return val; let outputConversionFunc = noop; if (fromLevel === 2 && isSH) { if (toLevel === 1) outputConversionFunc = fromUint8ToHalfFloat; else if (toLevel == 0) { outputConversionFunc = fromUint8; } } else if (fromLevel === 2 || fromLevel === 1) { if (toLevel === 0) outputConversionFunc = fromHalfFloat$1; else if (toLevel == 2) { if (!isSH) outputConversionFunc = noop; else outputConversionFunc = fromHalfFloatToUint8; } } else if (fromLevel === 0) { if (toLevel === 1) outputConversionFunc = toHalfFloat; else if (toLevel == 2) { if (!isSH) outputConversionFunc = toHalfFloat; else outputConversionFunc = toUint8; } } return outputConversionFunc(val); }; }(); const copyBetweenBuffers = (srcBuffer, srcOffset, destBuffer, destOffset, byteCount = 0) => { const src = new Uint8Array(srcBuffer, srcOffset); const dest = new Uint8Array(destBuffer, destOffset); for (let i = 0; i < byteCount; i++) { dest[i] = src[i]; } }; /** * SplatBuffer: Container for splat data from a single scene/file and capable of (mediocre) compression. */ class SplatBuffer { static CurrentMajorVersion = 0; static CurrentMinorVersion = 1; static CenterComponentCount = 3; static ScaleComponentCount = 3; static RotationComponentCount = 4; static ColorComponentCount = 4; static CovarianceComponentCount = 6; static SplatScaleOffsetFloat = 3; static SplatRotationOffsetFloat = 6; static CompressionLevels = { 0: { BytesPerCenter: 12, BytesPerScale: 12, BytesPerRotation: 16, BytesPerColor: 4, ScaleOffsetBytes: 12, RotationffsetBytes: 24, ColorOffsetBytes: 40, SphericalHarmonicsOffsetBytes: 44, ScaleRange: 1, BytesPerSphericalHarmonicsComponent: 4, SphericalHarmonicsOffsetFloat: 11, SphericalHarmonicsDegrees: { 0: { BytesPerSplat: 44 }, 1: { BytesPerSplat: 80 }, 2: { BytesPerSplat: 140 } }, }, 1: { BytesPerCenter: 6, BytesPerScale: 6, BytesPerRotation: 8, BytesPerColor: 4, ScaleOffsetBytes: 6, RotationffsetBytes: 12, ColorOffsetBytes: 20, SphericalHarmonicsOffsetBytes: 24, ScaleRange: 32767, BytesPerSphericalHarmonicsComponent: 2, SphericalHarmonicsOffsetFloat: 12, SphericalHarmonicsDegrees: { 0: { BytesPerSplat: 24 }, 1: { BytesPerSplat: 42 }, 2: { BytesPerSplat: 72 } }, }, 2: { BytesPerCenter: 6, BytesPerScale: 6, BytesPerRotation: 8, BytesPerColor: 4, ScaleOffsetBytes: 6, RotationffsetBytes: 12, ColorOffsetBytes: 20, SphericalHarmonicsOffsetBytes: 24, ScaleRange: 32767, BytesPerSphericalHarmonicsComponent: 1, SphericalHarmonicsOffsetFloat: 12, SphericalHarmonicsDegrees: { 0: { BytesPerSplat: 24 }, 1: { BytesPerSplat: 33 }, 2: { BytesPerSplat: 48 } }, } }; static CovarianceSizeFloats = 6; static HeaderSizeBytes = 4096; static SectionHeaderSizeBytes = 1024; static BucketStorageSizeBytes = 12; static BucketStorageSizeFloats = 3; static BucketBlockSize = 5.0; static BucketSize = 256; constructor(bufferData, secLoadedCountsToMax = true) { this.constructFromBuffer(bufferData, secLoadedCountsToMax); } getSplatCount() { return this.splatCount; } getMaxSplatCount() { return this.maxSplatCount; } getMinSphericalHarmonicsDegree() { let minSphericalHarmonicsDegree = 0; for (let i = 0; i < this.sections.length; i++) { const section = this.sections[i]; if (i === 0 || section.sphericalHarmonicsDegree < minSphericalHarmonicsDegree) { minSphericalHarmonicsDegree = section.sphericalHarmonicsDegree; } } return minSphericalHarmonicsDegree; } getBucketIndex(section, localSplatIndex) { let bucketIndex; const maxSplatIndexInFullBuckets = section.fullBucketCount * section.bucketSize; if (localSplatIndex < maxSplatIndexInFullBuckets) { bucketIndex = Math.floor(localSplatIndex / section.bucketSize); } else { let bucketSplatIndex = maxSplatIndexInFullBuckets; bucketIndex = section.fullBucketCount; let partiallyFullBucketIndex = 0; while (bucketSplatIndex < section.splatCount) { let currentPartiallyFilledBucketSize = section.partiallyFilledBucketLengths[partiallyFullBucketIndex]; if (localSplatIndex >= bucketSplatIndex && localSplatIndex < bucketSplatIndex + currentPartiallyFilledBucketSize) { break; } bucketSplatIndex += currentPartiallyFilledBucketSize; bucketIndex++; partiallyFullBucketIndex++; } } return bucketIndex; } getSplatCenter(globalSplatIndex, outCenter, transform) { const sectionIndex = this.globalSplatIndexToSectionMap[globalSplatIndex]; const section = this.sections[sectionIndex]; const localSplatIndex = globalSplatIndex - section.splatCountOffset; const srcSplatCentersBase = section.bytesPerSplat * localSplatIndex; const dataView = new DataView(this.bufferData, section.dataBase + srcSplatCentersBase); const x = dataViewFloatForCompressionLevel(dataView, 0, this.compressionLevel); const y = dataViewFloatForCompressionLevel(dataView, 1, this.compressionLevel); const z = dataViewFloatForCompressionLevel(dataView, 2, this.compressionLevel); if (this.compressionLevel >= 1) { const bucketIndex = this.getBucketIndex(section, localSplatIndex); const bucketBase = bucketIndex * SplatBuffer.BucketStorageSizeFloats; const sf = section.compressionScaleFactor; const sr = section.compressionScaleRange; outCenter.x = (x - sr) * sf + section.bucketArray[bucketBase]; outCenter.y = (y - sr) * sf + section.bucketArray[bucketBase + 1]; outCenter.z = (z - sr) * sf + section.bucketArray[bucketBase + 2]; } else { outCenter.x = x; outCenter.y = y; outCenter.z = z; } if (transform) outCenter.applyMatrix4(transform); } getSplatScaleAndRotation = function() { const scaleMatrix = new THREE.Matrix4(); const rotationMatrix = new THREE.Matrix4(); const tempMatrix = new THREE.Matrix4(); const tempPosition = new THREE.Vector3(); const scale = new THREE.Vector3(); const rotation = new THREE.Quaternion(); return function(index, outScale, outRotation, transform, scaleOverride) { const sectionIndex = this.globalSplatIndexToSectionMap[index]; const section = this.sections[sectionIndex]; const localSplatIndex = index - section.splatCountOffset; const srcSplatScalesBase = section.bytesPerSplat * localSplatIndex + SplatBuffer.CompressionLevels[this.compressionLevel].ScaleOffsetBytes; const dataView = new DataView(this.bufferData, section.dataBase + srcSplatScalesBase); scale.set(toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 0, this.compressionLevel), this.compressionLevel), toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 1, this.compressionLevel), this.compressionLevel), toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 2, this.compressionLevel), this.compressionLevel)); if (scaleOverride) { if (scaleOverride.x !== undefined) scale.x = scaleOverride.x; if (scaleOverride.y !== undefined) scale.y = scaleOverride.y; if (scaleOverride.z !== undefined) scale.z = scaleOverride.z; } rotation.set(toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 4, this.compressionLevel), this.compressionLevel), toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 5, this.compressionLevel), this.compressionLevel), toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 6, this.compressionLevel), this.compressionLevel), toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 3, this.compressionLevel), this.compressionLevel)); if (transform) { scaleMatrix.makeScale(scale.x, scale.y, scale.z); rotationMatrix.makeRotationFromQuaternion(rotation); tempMatrix.copy(scaleMatrix).multiply(rotationMatrix).multiply(transform); tempMatrix.decompose(tempPosition, outRotation, outScale); } else { outScale.copy(scale); outRotation.copy(rotation); } }; }(); getSplatColor(globalSplatIndex, outColor) { const sectionIndex = this.globalSplatIndexToSectionMap[globalSplatIndex]; const section = this.sections[sectionIndex]; const localSplatIndex = globalSplatIndex - section.splatCountOffset; const srcSplatColorsBase = section.bytesPerSplat * localSplatIndex + SplatBuffer.CompressionLevels[this.compressionLevel].ColorOffsetBytes; const splatColorsArray = new Uint8Array(this.bufferData, section.dataBase + srcSplatColorsBase, 4); outColor.set(splatColorsArray[0], splatColorsArray[1], splatColorsArray[2], splatColorsArray[3]); } fillSplatCenterArray(outCenterArray, transform, srcFrom, srcTo, destFrom) { const splatCount = this.splatCount; srcFrom = srcFrom || 0; srcTo = srcTo || splatCount - 1; if (destFrom === undefined) destFrom = srcFrom; const center = new THREE.Vector3(); for (let i = srcFrom; i <= srcTo; i++) { const sectionIndex = this.globalSplatIndexToSectionMap[i]; const section = this.sections[sectionIndex]; const localSplatIndex = i - section.splatCountOffset; const centerDestBase = (i - srcFrom + destFrom) * SplatBuffer.CenterComponentCount; const srcSplatCentersBase = section.bytesPerSplat * localSplatIndex; const dataView = new DataView(this.bufferData, section.dataBase + srcSplatCentersBase); const x = dataViewFloatForCompressionLevel(dataView, 0, this.compressionLevel); const y = dataViewFloatForCompressionLevel(dataView, 1, this.compressionLevel); const z = dataViewFloatForCompressionLevel(dataView, 2, this.compressionLevel); if (this.compressionLevel >= 1) { const bucketIndex = this.getBucketIndex(section, localSplatIndex); const bucketBase = bucketIndex * SplatBuffer.BucketStorageSizeFloats; const sf = section.compressionScaleFactor; const sr = section.compressionScaleRange; center.x = (x - sr) * sf + section.bucketArray[bucketBase]; center.y = (y - sr) * sf + section.bucketArray[bucketBase + 1]; center.z = (z - sr) * sf + section.bucketArray[bucketBase + 2]; } else { center.x = x; center.y = y; center.z = z; } if (transform) { center.applyMatrix4(transform); } outCenterArray[centerDestBase] = center.x; outCenterArray[centerDestBase + 1] = center.y; outCenterArray[centerDestBase + 2] = center.z; } } fillSplatScaleRotationArray = function() { const scaleMatrix = new THREE.Matrix4(); const rotationMatrix = new THREE.Matrix4(); const tempMatrix = new THREE.Matrix4(); const scale = new THREE.Vector3(); const rotation = new THREE.Quaternion(); const tempPosition = new THREE.Vector3(); const ensurePositiveW = (quaternion) => { const flip = quaternion.w < 0 ? -1 : 1; quaternion.x *= flip; quaternion.y *= flip; quaternion.z *= flip; quaternion.w *= flip; }; return function(outScaleArray, outRotationArray, transform, srcFrom, srcTo, destFrom, desiredOutputCompressionLevel, scaleOverride) { const splatCount = this.splatCount; srcFrom = srcFrom || 0; srcTo = srcTo || splatCount - 1; if (destFrom === undefined) destFrom = srcFrom; const outputConversion = (value, srcCompressionLevel) => { if (srcCompressionLevel === undefined) srcCompressionLevel = this.compressionLevel; return convertBetweenCompressionLevels(value, srcCompressionLevel, desiredOutputCompressionLevel); }; for (let i = srcFrom; i <= srcTo; i++) { const sectionIndex = this.globalSplatIndexToSectionMap[i]; const section = this.sections[sectionIndex]; const localSplatIndex = i - section.splatCountOffset; const srcSplatScalesBase = section.bytesPerSplat * localSplatIndex + SplatBuffer.CompressionLevels[this.compressionLevel].ScaleOffsetBytes; const scaleDestBase = (i - srcFrom + destFrom) * SplatBuffer.ScaleComponentCount; const rotationDestBase = (i - srcFrom + destFrom) * SplatBuffer.RotationComponentCount; const dataView = new DataView(this.bufferData, section.dataBase + srcSplatScalesBase); const srcScaleX = (scaleOverride && scaleOverride.x !== undefined) ? scaleOverride.x : dataViewFloatForCompressionLevel(dataView, 0, this.compressionLevel); const srcScaleY = (scaleOverride && scaleOverride.y !== undefined) ? scaleOverride.y : dataViewFloatForCompressionLevel(dataView, 1, this.compressionLevel); const srcScaleZ = (scaleOverride && scaleOverride.z !== undefined) ? scaleOverride.z : dataViewFloatForCompressionLevel(dataView, 2, this.compressionLevel); const srcRotationW = dataViewFloatForCompressionLevel(dataView, 3, this.compressionLevel); const srcRotationX = dataViewFloatForCompressionLevel(dataView, 4, this.compressionLevel); const srcRotationY = dataViewFloatForCompressionLevel(dataView, 5, this.compressionLevel); const srcRotationZ = dataViewFloatForCompressionLevel(dataView, 6, this.compressionLevel); scale.set(toUncompressedFloat(srcScaleX, this.compressionLevel), toUncompressedFloat(srcScaleY, this.compressionLevel), toUncompressedFloat(srcScaleZ, this.compressionLevel)); rotation.set(toUncompressedFloat(srcRotationX, this.compressionLevel), toUncompressedFloat(srcRotationY, this.compressionLevel), toUncompressedFloat(srcRotationZ, this.compressionLevel), toUncompressedFloat(srcRotationW, this.compressionLevel)).normalize(); if (transform) { tempPosition.set(0, 0, 0); scaleMatrix.makeScale(scale.x, scale.y, scale.z); rotationMatrix.makeRotationFromQuaternion(rotation); tempMatrix.identity().premultiply(scaleMatrix).premultiply(rotationMatrix); tempMatrix.premultiply(transform); tempMatrix.decompose(tempPosition, rotation, scale); rotation.normalize(); } ensurePositiveW(rotation); if (outScaleArray) { outScaleArray[scaleDestBase] = outputConversion(scale.x, 0); outScaleArray[scaleDestBase + 1] = outputConversion(scale.y, 0); outScaleArray[scaleDestBase + 2] = outputConversion(scale.z, 0); } if (outRotationArray) { outRotationArray[rotationDestBase] = outputConversion(rotation.x, 0); outRotationArray[rotationDestBase + 1] = outputConversion(rotation.y, 0); outRotationArray[rotationDestBase + 2] = outputConversion(rotation.z, 0); outRotationArray[rotationDestBase + 3] = outputConversion(rotation.w, 0); } } }; }(); static computeCovariance = function() { const tempMatrix4 = new THREE.Matrix4(); const scaleMatrix = new THREE.Matrix3(); const rotationMatrix = new THREE.Matrix3(); const covarianceMatrix = new THREE.Matrix3(); const transformedCovariance = new THREE.Matrix3(); const transform3x3 = new THREE.Matrix3(); const transform3x3Transpose = new THREE.Matrix3(); return function(scale, rotation, transform, outCovariance, outOffset = 0, desiredOutputCompressionLevel) { tempMatrix4.makeScale(scale.x, scale.y, scale.z); scaleMatrix.setFromMatrix4(tempMatrix4); tempMatrix4.makeRotationFromQuaternion(rotation); rotationMatrix.setFromMatrix4(tempMatrix4); covarianceMatrix.copy(rotationMatrix).multiply(scaleMatrix); transformedCovariance.copy(covarianceMatrix).transpose().premultiply(covarianceMatrix); if (transform) { transform3x3.setFromMatrix4(transform); transform3x3Transpose.copy(transform3x3).transpose(); transformedCovariance.multiply(transform3x3Transpose); transformedCovariance.premultiply(transform3x3); } if (desiredOutputCompressionLevel >= 1) { outCovariance[outOffset] = toHalfFloat(transformedCovariance.elements[0]); outCovariance[outOffset + 1] = toHalfFloat(transformedCovariance.elements[3]); outCovariance[outOffset + 2] = toHalfFloat(transformedCovariance.elements[6]); outCovariance[outOffset + 3] = toHalfFloat(transformedCovariance.elements[4]); outCovariance[outOffset + 4] = toHalfFloat(transformedCovariance.elements[7]); outCovariance[outOffset + 5] = toHalfFloat(transformedCovariance.elements[8]); } else { outCovariance[outOffset] = transformedCovariance.elements[0]; outCovariance[outOffset + 1] = transformedCovariance.elements[3]; outCovariance[outOffset + 2] = transformedCovariance.elements[6]; outCovariance[outOffset + 3] = transformedCovariance.elements[4]; outCovariance[outOffset + 4] = transformedCovariance.elements[7]; outCovariance[outOffset + 5] = transformedCovariance.elements[8]; } }; }(); fillSplatCovarianceArray(covarianceArray, transform, srcFrom, srcTo, destFrom, desiredOutputCompressionLevel) { const splatCount = this.splatCount; const scale = new THREE.Vector3(); const rotation = new THREE.Quaternion(); srcFrom = srcFrom || 0; srcTo = srcTo || splatCount - 1; if (destFrom === undefined) destFrom = srcFrom; for (let i = srcFrom; i <= srcTo; i++) { const sectionIndex = this.globalSplatIndexToSectionMap[i]; const section = this.sections[sectionIndex]; const localSplatIndex = i - section.splatCountOffset; const covarianceDestBase = (i - srcFrom + destFrom) * SplatBuffer.CovarianceComponentCount; const srcSplatScalesBase = section.bytesPerSplat * localSplatIndex + SplatBuffer.CompressionLevels[this.compressionLevel].ScaleOffsetBytes; const dataView = new DataView(this.bufferData, section.dataBase + srcSplatScalesBase); scale.set(toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 0, this.compressionLevel), this.compressionLevel), toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 1, this.compressionLevel), this.compressionLevel), toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 2, this.compressionLevel), this.compressionLevel)); rotation.set(toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 4, this.compressionLevel), this.compressionLevel), toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 5, this.compressionLevel), this.compressionLevel), toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 6, this.compressionLevel), this.compressionLevel), toUncompressedFloat(dataViewFloatForCompressionLevel(dataView, 3, this.compressionLevel), this.compressionLevel)); SplatBuffer.computeCovariance(scale, rotation, transform, covarianceArray, covarianceDestBase, desiredOutputCompressionLevel); } } fillSplatColorArray(outColorArray, minimumAlpha, srcFrom, srcTo, destFrom) { const splatCount = this.splatCount; srcFrom = srcFrom || 0; srcTo = srcTo || splatCount - 1; if (destFrom === undefined) destFrom = srcFrom; for (let i = srcFrom; i <= srcTo; i++) { const sectionIndex = this.globalSplatIndexToSectionMap[i]; const section = this.sections[sectionIndex]; const localSplatIndex = i - section.splatCountOffset; const colorDestBase = (i - srcFrom + destFrom) * SplatBuffer.ColorComponentCount; const srcSplatColorsBase = section.bytesPerSplat * localSplatIndex + SplatBuffer.CompressionLevels[this.compressionLevel].ColorOffsetBytes; const dataView = new Uint8Array(this.bufferData, section.dataBase + srcSplatColorsBase); let alpha = dataView[3]; alpha = (alpha >= minimumAlpha) ? alpha : 0; outColorArray[colorDestBase] = dataView[0]; outColorArray[colorDestBase + 1] = dataView[1]; outColorArray[colorDestBase + 2] = dataView[2]; outColorArray[colorDestBase + 3] = alpha; } } fillSphericalHarmonicsArray = function() { const sphericalHarmonicVectors = []; for (let i = 0; i < 15; i++) { sphericalHarmonicVectors[i] = new THREE.Vector3(); } const tempMatrix3 = new THREE.Matrix3(); const tempMatrix4 = new THREE.Matrix4(); const tempTranslation = new THREE.Vector3(); const tempScale = new THREE.Vector3(); const tempRotation = new THREE.Quaternion(); const sh11 = []; const sh12 = []; const sh13 = []; const sh21 = []; const sh22 = []; const sh23 = []; const sh24 = []; const sh25 = []; const shIn1 = []; const shIn2 = []; const shIn3 = []; const shIn4 = []; const shIn5 = []; const shOut1 = []; const shOut2 = []; const shOut3 = []; const shOut4 = []; const shOut5 = []; const noop = (v) => v; const set3 = (array, val1, val2, val3) => { array[0] = val1; array[1] = val2; array[2] = val3; }; const set3FromArray = (array, srcDestView, stride, srcBase, compressionLevel) => { array[0] = dataViewFloatForCompressionLevel(srcDestView, srcBase, compressionLevel, true); array[1] = dataViewFloatForCompressionLevel(srcDestView, srcBase + stride, compressionLevel, true); array[2] = dataViewFloatForCompressionLevel(srcDestView, srcBase + stride + stride, compressionLevel, true); }; const copy3 = (srcArray, destArray) => { destArray[0] = srcArray[0]; destArray[1] = srcArray[1]; destArray[2] = srcArray[2]; }; const setOutput3 = (srcArray, destArray, destBase, conversionFunc) => { destArray[destBase] = conversionFunc(srcArray[0]); destArray[destBase + 1] = conversionFunc(srcArray[1]); destArray[destBase + 2] = conversionFunc(srcArray[2]); }; const toUncompressedFloatArray3 = (src, dest, compressionLevel, range8BitMin, range8BitMax) => { dest[0] = toUncompressedFloat(src[0], compressionLevel, true, range8BitMin, range8BitMax); dest[1] = toUncompressedFloat(src[1], compressionLevel, true, range8BitMin, range8BitMax); dest[2] = toUncompressedFloat(src[2], compressionLevel, true, range8BitMin, range8BitMax); return dest; }; return function(outSphericalHarmonicsArray, outSphericalHarmonicsDegree, transform, srcFrom, srcTo, destFrom, desiredOutputCompressionLevel) { const splatCount = this.splatCount; srcFrom = srcFrom || 0; srcTo = srcTo || splatCount - 1; if (destFrom === undefined) destFrom = srcFrom; if (transform && outSphericalHarmonicsDegree >= 1) { tempMatrix4.copy(transform); tempMatrix4.decompose(tempTranslation, tempRotation, tempScale); tempRotation.normalize(); tempMatrix4.makeRotationFromQuaternion(tempRotation); tempMatrix3.setFromMatrix4(tempMatrix4); set3(sh11, tempMatrix3.elements[4], -tempMatrix3.elements[7], tempMatrix3.elements[1]); set3(sh12, -tempMatrix3.elements[5], tempMatrix3.elements[8], -tempMatrix3.elements[2]); set3(sh13, tempMatrix3.elements[3], -tempMatrix3.elements[6], tempMatrix3.elements[0]); } const localFromHalfFloatToUint8 = (v) => { return fromHalfFloatToUint8(v, this.minSphericalHarmonicsCoeff, this.maxSphericalHarmonicsCoeff); }; const localToUint8 = (v) => { return toUint8(v, this.minSphericalHarmonicsCoeff, this.maxSphericalHarmonicsCoeff); }; for (let i = srcFrom; i <= srcTo; i++) { const sectionIndex = this.globalSplatIndexToSectionMap[i]; const section = this.sections[sectionIndex]; outSphericalHarmonicsDegree = Math.min(outSphericalHarmonicsDegree, section.sphericalHarmonicsDegree); const outSphericalHarmonicsComponentsCount = getSphericalHarmonicsComponentCountForDegree(outSphericalHarmonicsDegree); const localSplatIndex = i - section.splatCountOffset; const srcSplatSHBase = section.bytesPerSplat * localSplatIndex + SplatBuffer.CompressionLevels[this.compressionLevel].SphericalHarmonicsOffsetBytes; const dataView = new DataView(this.bufferData, section.dataBase + srcSplatSHBase); const shDestBase = (i - srcFrom + destFrom) * outSphericalHarmonicsComponentsCount; let compressionLevelForOutputConversion = transform ? 0 : this.compressionLevel; let outputConversionFunc = noop; if (compressionLevelForOutputConversion !== desiredOutputCompressionLevel) { if (compressionLevelForOutputConversion === 1) { if (desiredOutputCompressionLevel === 0) outputConversionFunc = fromHalfFloat$1; else if (desiredOutputCompressionLevel == 2) outputConversionFunc = localFromHalfFloatToUint8; } else if (compressionLevelForOutputConversion === 0) { if (desiredOutputCompressionLevel === 1) outputConversionFunc = toHalfFloat; else if (desiredOutputCompressionLevel == 2) outputConversionFunc = localToUint8; } } const minShCoeff = this.minSphericalHarmonicsCoeff; const maxShCoeff = this.maxSphericalHarmonicsCoeff; if (outSphericalHarmonicsDegree >= 1) { set3FromArray(shIn1, dataView, 3, 0, this.compressionLevel); set3FromArray(shIn2, dataView, 3, 1, this.compressionLevel); set3FromArray(shIn3, dataView, 3, 2, this.compressionLevel); if (transform) { toUncompressedFloatArray3(shIn1, shIn1, this.compressionLevel, minShCoeff, maxShCoeff); toUncompressedFloatArray3(shIn2, shIn2, this.compressionLevel, minShCoeff, maxShCoeff); toUncompressedFloatArray3(shIn3, shIn3, this.compressionLevel, minShCoeff, maxShCoeff); SplatBuffer.rotateSphericalHarmonics3(shIn1, shIn2, shIn3, sh11, sh12, sh13, shOut1, shOut2, shOut3); } else { copy3(shIn1, shOut1); copy3(shIn2, shOut2); copy3(shIn3, shOut3); } setOutput3(shOut1, outSphericalHarmonicsArray, shDestBase, outputConversionFunc); setOutput3(shOut2, outSphericalHarmonicsArray, shDestBase + 3, outputConversionFunc); setOutput3(shOut3, outSphericalHarmonicsArray, shDestBase + 6, outputConversionFunc); if (outSphericalHarmonicsDegree >= 2) { set3FromArray(shIn1, dataView, 5, 9, this.compressionLevel); set3FromArray(shIn2, dataView, 5, 10, this.compressionLevel); set3FromArray(shIn3, dataView, 5, 11, this.compressionLevel); set3FromArray(shIn4, dataView, 5, 12, this.compressionLevel); set3FromArray(shIn5, dataView, 5, 13, this.compressionLevel); if (transform) { toUncompressedFloatArray3(shIn1, shIn1, this.compressionLevel, minShCoeff, maxShCoeff); toUncompressedFloatArray3(shIn2, shIn2, this.compressionLevel, minShCoeff, maxShCoeff); toUncompressedFloatArray3(shIn3, shIn3, this.compressionLevel, minShCoeff, maxShCoeff); toUncompressedFloatArray3(shIn4, shIn4, this.compressionLevel, minShCoeff, maxShCoeff); toUncompressedFloatArray3(shIn5, shIn5, this.compressionLevel, minShCoeff, maxShCoeff); SplatBuffer.rotateSphericalHarmonics5(shIn1, shIn2, shIn3, shIn4, shIn5, sh11, sh12, sh13, sh21, sh22, sh23, sh24, sh25, shOut1, shOut2, shOut3, shOut4, shOut5); } else { copy3(shIn1, shOut1); copy3(shIn2, shOut2); copy3(shIn3, shOut3); copy3(shIn4, shOut4); copy3(shIn5, shOut5); } setOutput3(shOut1, outSphericalHarmonicsArray, shDestBase + 9, outputConversionFunc); setOutput3(shOut2, outSphericalHarmonicsArray, shDestBase + 12, outputConversionFunc); setOutput3(shOut3, outSphericalHarmonicsArray, shDestBase + 15, outputConversionFunc); setOutput3(shOut4, outSphericalHarmonicsArray, shDestBase + 18, outputConversionFunc); setOutput3(shOut5, outSphericalHarmonicsArray, shDestBase + 21, outputConversionFunc); } } } }; }(); static dot3 = (v1, v2, v3, transformRow, outArray) => { outArray[0] = outArray[1] = outArray[2] = 0; const t0 = transformRow[0]; const t1 = transformRow[1]; const t2 = transformRow[2]; SplatBuffer.addInto3(v1[0] * t0, v1[1] * t0, v1[2] * t0, outArray); SplatBuffer.addInto3(v2[0] * t1, v2[1] * t1, v2[2] * t1, outArray); SplatBuffer.addInto3(v3[0] * t2, v3[1] * t2, v3[2] * t2, outArray); }; static addInto3 = (val1, val2, val3, destArray) => { destArray[0] = destArray[0] + val1; destArray[1] = destArray[1] + val2; destArray[2] = destArray[2] + val3; }; static dot5 = (v1, v2, v3, v4, v5, transformRow, outArray) => { outArray[0] = outArray[1] = outArray[2] = 0; const t0 = transformRow[0]; const t1 = transformRow[1]; const t2 = transformRow[2]; const t3 = transformRow[3]; const t4 = transformRow[4]; SplatBuffer.addInto3(v1[0] * t0, v1[1] * t0, v1[2] * t0, outArray); SplatBuffer.addInto3(v2[0] * t1, v2[1] * t1, v2[2] * t1, outArray); SplatBuffer.addInto3(v3[0] * t2, v3[1] * t2, v3[2] * t2, outArray); SplatBuffer.addInto3(v4[0] * t3, v4[1] * t3, v4[2] * t3, outArray); SplatBuffer.addInto3(v5[0] * t4, v5[1] * t4, v5[2] * t4, outArray); }; static rotateSphericalHarmonics3 = (in1, in2, in3, tsh11, tsh12, tsh13, out1, out2, out3) => { SplatBuffer.dot3(in1, in2, in3, tsh11, out1); SplatBuffer.dot3(in1, in2, in3, tsh12, out2); SplatBuffer.dot3(in1, in2, in3, tsh13, out3); }; static rotateSphericalHarmonics5 = (in1, in2, in3, in4, in5, tsh11, tsh12, tsh13, tsh21, tsh22, tsh23, tsh24, tsh25, out1, out2, out3, out4, out5) => { const kSqrt0104 = Math.sqrt(1.0 / 4.0); const kSqrt0304 = Math.sqrt(3.0 / 4.0); const kSqrt0103 = Math.sqrt(1.0 / 3.0); const kSqrt0403 = Math.sqrt(4.0 / 3.0); const kSqrt0112 = Math.sqrt(1.0 / 12.0); tsh21[0] = kSqrt0104 * ((tsh13[2] * tsh11[0] + tsh13[0] * tsh11[2]) + (tsh11[2] * tsh13[0] + tsh11[0] * tsh13[2])); tsh21[1] = (tsh13[1] * tsh11[0] + tsh11[1] * tsh13[0]); tsh21[2] = kSqrt0304 * (tsh13[1] * tsh11[1] + tsh11[1] * tsh13[1]); tsh21[3] = (tsh13[1] * tsh11[2] + tsh11[1] * tsh13[2]); tsh21[4] = kSqrt0104 * ((tsh13[2] * tsh11[2] - tsh13[0] * tsh11[0]) + (tsh11[2] * tsh13[2] - tsh11[0] * tsh13[0])); SplatBuffer.dot5(in1, in2, in3, in4, in5, tsh21, out1); tsh22[0] = kSqrt0104 * ((tsh12[2] * tsh11[0] + tsh12[0] * tsh11[2]) + (tsh11[2] * tsh12[0] + tsh11[0] * tsh12[2])); tsh22[1] = tsh12[1] * tsh11[0] + tsh11[1] * tsh12[0]; tsh22[2] = kSqrt0304 * (tsh12[1] * tsh11[1] + tsh11[1] * tsh12[1]); tsh22[3] = tsh12[1] * tsh11[2] + tsh11[1] * tsh12[2]; tsh22[4] = kSqrt0104 * ((tsh12[2] * tsh11[2] - tsh12[0] * tsh11[0]) + (tsh11[2] * tsh12[2] - tsh11[0] * tsh12[0])); SplatBuffer.dot5(in1, in2, in3, in4, in5, tsh22, out2); tsh23[0] = kSqrt0103 * (tsh12[2] * tsh12[0] + tsh12[0] * tsh12[2]) + -kSqrt0112 * ((tsh13[2] * tsh13[0] + tsh13[0] * tsh13[2]) + (tsh11[2] * tsh11[0] + tsh11[0] * tsh11[2])); tsh23[1] = kSqrt0403 * tsh12[1] * tsh12[0] + -kSqrt0103 * (tsh13[1] * tsh13[0] + tsh11[1] * tsh11[0]); tsh23[2] = tsh12[1] * tsh12[1] + -kSqrt0104 * (tsh13[1] * tsh13[1] + tsh11[1] * tsh11[1]); tsh23[3] = kSqrt0403 * tsh12[1] * tsh12[2] + -kSqrt0103 * (tsh13[1] * tsh13[2] + tsh11[1] * tsh11[2]); tsh23[4] = kSqrt0103 * (tsh12[2] * tsh12[2] - tsh12[0] * tsh12[0]) + -