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