Spaces:
Running
Running
| const SH_COEFF_DEG0 = Math.sqrt(1 / (4 * Math.PI)) | |
| const INV_SH_COEFF_DEG0 = 1 / SH_COEFF_DEG0 | |
| const COLOR_SPACE_SRGB_INDEX = 1 | |
| export interface GaussianBuffers { | |
| count: number | |
| meanVectors: Float32Array | |
| singularValues: Float32Array | |
| quaternions: Float32Array | |
| colors: Float32Array | |
| opacities: Float32Array | |
| } | |
| export interface SharpPlyBuildInput extends GaussianBuffers { | |
| imageWidth: number | |
| imageHeight: number | |
| focalPx: number | |
| } | |
| function clamp01(value: number): number { | |
| if (value <= 0) return 0 | |
| if (value >= 1) return 1 | |
| return value | |
| } | |
| function linearToSrgb(value: number): number { | |
| const x = clamp01(value) | |
| if (x <= 0.0031308) { | |
| return 12.92 * x | |
| } | |
| return 1.055 * x ** (1 / 2.4) - 0.055 | |
| } | |
| function rgbToSh0(rgb: number): number { | |
| return (rgb - 0.5) * INV_SH_COEFF_DEG0 | |
| } | |
| function safeLog(value: number, epsilon = 1e-8): number { | |
| return Math.log(Math.max(value, epsilon)) | |
| } | |
| function safeLogit(value: number, epsilon = 1e-6): number { | |
| const x = Math.min(1 - epsilon, Math.max(epsilon, value)) | |
| return Math.log(x / (1 - x)) | |
| } | |
| function quantile(values: Float32Array, q: number): number { | |
| if (values.length === 0) { | |
| return 0 | |
| } | |
| const sorted = Array.from(values) | |
| sorted.sort((a, b) => a - b) | |
| const index = Math.min(sorted.length - 1, Math.max(0, Math.round((sorted.length - 1) * q))) | |
| return sorted[index] ?? 0 | |
| } | |
| function buildHeader(vertexCount: number): string { | |
| return [ | |
| 'ply', | |
| 'format binary_little_endian 1.0', | |
| `element vertex ${vertexCount}`, | |
| 'property float x', | |
| 'property float y', | |
| 'property float z', | |
| 'property float f_dc_0', | |
| 'property float f_dc_1', | |
| 'property float f_dc_2', | |
| 'property float opacity', | |
| 'property float scale_0', | |
| 'property float scale_1', | |
| 'property float scale_2', | |
| 'property float rot_0', | |
| 'property float rot_1', | |
| 'property float rot_2', | |
| 'property float rot_3', | |
| 'element extrinsic 16', | |
| 'property float extrinsic', | |
| 'element intrinsic 9', | |
| 'property float intrinsic', | |
| 'element image_size 2', | |
| 'property uint image_size', | |
| 'element frame 2', | |
| 'property int frame', | |
| 'element disparity 2', | |
| 'property float disparity', | |
| 'element color_space 1', | |
| 'property uchar color_space', | |
| 'element version 3', | |
| 'property uchar version', | |
| 'end_header', | |
| '', | |
| ].join('\n') | |
| } | |
| export function buildSharpPlyBinary(input: SharpPlyBuildInput): Uint8Array { | |
| const { | |
| count, | |
| meanVectors, | |
| singularValues, | |
| quaternions, | |
| colors, | |
| opacities, | |
| focalPx, | |
| imageWidth, | |
| imageHeight, | |
| } = input | |
| if (meanVectors.length !== count * 3) { | |
| throw new Error('meanVectors length does not match count * 3') | |
| } | |
| if (singularValues.length !== count * 3) { | |
| throw new Error('singularValues length does not match count * 3') | |
| } | |
| if (quaternions.length !== count * 4) { | |
| throw new Error('quaternions length does not match count * 4') | |
| } | |
| if (colors.length !== count * 3) { | |
| throw new Error('colors length does not match count * 3') | |
| } | |
| if (opacities.length !== count) { | |
| throw new Error('opacities length does not match count') | |
| } | |
| const header = buildHeader(count) | |
| const encoder = new TextEncoder() | |
| const headerBytes = encoder.encode(header) | |
| const vertexStrideBytes = 14 * 4 | |
| const vertexBytesLength = count * vertexStrideBytes | |
| const extrinsicBytesLength = 16 * 4 | |
| const intrinsicBytesLength = 9 * 4 | |
| const imageSizeBytesLength = 2 * 4 | |
| const frameBytesLength = 2 * 4 | |
| const disparityBytesLength = 2 * 4 | |
| const colorSpaceBytesLength = 1 | |
| const versionBytesLength = 3 | |
| const totalBytes = | |
| headerBytes.byteLength + | |
| vertexBytesLength + | |
| extrinsicBytesLength + | |
| intrinsicBytesLength + | |
| imageSizeBytesLength + | |
| frameBytesLength + | |
| disparityBytesLength + | |
| colorSpaceBytesLength + | |
| versionBytesLength | |
| const output = new Uint8Array(totalBytes) | |
| output.set(headerBytes, 0) | |
| const view = new DataView(output.buffer) | |
| let offset = headerBytes.byteLength | |
| const disparities = new Float32Array(count) | |
| for (let i = 0; i < count; i += 1) { | |
| const v3 = i * 3 | |
| const v4 = i * 4 | |
| const x = meanVectors[v3] | |
| const y = meanVectors[v3 + 1] | |
| const z = meanVectors[v3 + 2] | |
| disparities[i] = 1 / Math.max(z, 1e-6) | |
| const sr = linearToSrgb(colors[v3]) | |
| const sg = linearToSrgb(colors[v3 + 1]) | |
| const sb = linearToSrgb(colors[v3 + 2]) | |
| const values = [ | |
| x, | |
| y, | |
| z, | |
| rgbToSh0(sr), | |
| rgbToSh0(sg), | |
| rgbToSh0(sb), | |
| safeLogit(opacities[i]), | |
| safeLog(singularValues[v3]), | |
| safeLog(singularValues[v3 + 1]), | |
| safeLog(singularValues[v3 + 2]), | |
| quaternions[v4], | |
| quaternions[v4 + 1], | |
| quaternions[v4 + 2], | |
| quaternions[v4 + 3], | |
| ] | |
| for (const value of values) { | |
| view.setFloat32(offset, value, true) | |
| offset += 4 | |
| } | |
| } | |
| const extrinsic = new Float32Array([ | |
| 1, 0, 0, 0, | |
| 0, 1, 0, 0, | |
| 0, 0, 1, 0, | |
| 0, 0, 0, 1, | |
| ]) | |
| for (const value of extrinsic) { | |
| view.setFloat32(offset, value, true) | |
| offset += 4 | |
| } | |
| const intrinsic = new Float32Array([ | |
| focalPx, | |
| 0, | |
| imageWidth * 0.5, | |
| 0, | |
| focalPx, | |
| imageHeight * 0.5, | |
| 0, | |
| 0, | |
| 1, | |
| ]) | |
| for (const value of intrinsic) { | |
| view.setFloat32(offset, value, true) | |
| offset += 4 | |
| } | |
| view.setUint32(offset, imageWidth, true) | |
| offset += 4 | |
| view.setUint32(offset, imageHeight, true) | |
| offset += 4 | |
| view.setInt32(offset, 1, true) | |
| offset += 4 | |
| view.setInt32(offset, count, true) | |
| offset += 4 | |
| view.setFloat32(offset, quantile(disparities, 0.1), true) | |
| offset += 4 | |
| view.setFloat32(offset, quantile(disparities, 0.9), true) | |
| offset += 4 | |
| view.setUint8(offset, COLOR_SPACE_SRGB_INDEX) | |
| offset += 1 | |
| view.setUint8(offset, 1) | |
| offset += 1 | |
| view.setUint8(offset, 5) | |
| offset += 1 | |
| view.setUint8(offset, 0) | |
| offset += 1 | |
| if (offset !== output.byteLength) { | |
| throw new Error(`PLY writer size mismatch (${offset} != ${output.byteLength})`) | |
| } | |
| return output | |
| } | |