Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support compressed.ply spherical harmonics #7127

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions src/core/math/float-packing.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ const oneDiv255 = 1 / 255;
const floatView = new Float32Array(1);
const int32View = new Int32Array(floatView.buffer);

const f32 = new Float32Array(1);
const magicF = new Float32Array(1);
const wasInfNanF = new Float32Array(1);

const u32 = new Uint32Array(f32.buffer);
const magicU = new Uint32Array(magicF.buffer);
const wasInfNanU = new Uint32Array(wasInfNanF.buffer);

magicU[0] = (254 - 15) << 23;
wasInfNanU[0] = (127 + 16) << 23;

/**
* Utility static class providing functionality to pack float values to various storage
* representations.
Expand Down Expand Up @@ -120,6 +131,24 @@ class FloatPacking {
FloatPacking.float2Bytes(value, array, offset, numBytes);
}

/**
* Unpacks a 16 bit half floating point value to a float.
*
* Based on https://fgiesen.wordpress.com/2012/03/28/half-to-float-done-quic/
*
* @param {number} value - The half value to pack as a uint16.
* @returns {number} The packed value.
*/
static half2Float(value) {
u32[0] = (value & 0x7fff) << 13;
f32[0] *= magicF[0];
if (f32[0] >= wasInfNanF[0]) {
u32[0] |= 255 << 23;
}
u32[0] |= (value & 0x8000) << 16;
return f32[0];
};

/**
* Packs a float into specified number of bytes, using 1 byte for exponent and the remaining
* bytes for the mantissa.
Expand Down
116 changes: 60 additions & 56 deletions src/framework/parsers/ply.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const dataTypeMap = new Map([
['uchar', Uint8Array],
['short', Int16Array],
['ushort', Uint16Array],
['half', Uint16Array],
['int', Int32Array],
['uint', Uint32Array],
['float', Float32Array],
Expand Down Expand Up @@ -205,18 +206,39 @@ const isCompressedPly = (elements) => {
'min_x', 'min_y', 'min_z',
'max_x', 'max_y', 'max_z',
'min_scale_x', 'min_scale_y', 'min_scale_z',
'max_scale_x', 'max_scale_y', 'max_scale_z'
'max_scale_x', 'max_scale_y', 'max_scale_z',
'min_r', 'min_g', 'min_b',
'max_r', 'max_g', 'max_b'
];

const vertexProperties = [
'packed_position', 'packed_rotation', 'packed_scale', 'packed_color'
];

return elements.length === 2 &&
elements[0].name === 'chunk' &&
elements[0].properties.every((p, i) => p.name === chunkProperties[i] && p.type === 'float') &&
elements[1].name === 'vertex' &&
elements[1].properties.every((p, i) => p.name === vertexProperties[i] && p.type === 'uint');
const band1Properties = ['0', '1', '2'].map(x => `coeff_${x}`);
const band2Properties = ['3', '4', '5', '6', '7'].map(x => `coeff_${x}`);
const band3Properties = ['8', '9', '10', '11', '12', '13', '14'].map(x => `coeff_${x}`);
const indicesProperties = ['0', '1', '2', '3'].map(x => `packed_sh_${x}`);

const hasBaseElements = () => {
return elements[0].name === 'chunk' &&
elements[0].properties.every((p, i) => p.name === chunkProperties[i] && p.type === 'float') &&
elements[1].name === 'vertex' &&
elements[1].properties.every((p, i) => p.name === vertexProperties[i] && p.type === 'uint');
};

const hasSHElements = () => {
return elements[2].name === 'sh_band_1' &&
elements[2].properties.every((p, i) => p.name === band1Properties[i] && p.type === 'half') &&
elements[3].name === 'sh_band_2' &&
elements[3].properties.every((p, i) => p.name === band2Properties[i] && p.type === 'half') &&
elements[4].name === 'sh_band_3' &&
elements[4].properties.every((p, i) => p.name === band3Properties[i] && p.type === 'half') &&
elements[5].name === 'vertex_sh' &&
elements[5].properties.every((p, i) => p.name === indicesProperties[i] && p.type === 'uint');
};

return (elements.length === 2 && hasBaseElements()) || (elements.length === 6 && hasBaseElements() && hasSHElements());
};

const isFloatPly = (elements) => {
Expand All @@ -230,10 +252,8 @@ const readCompressedPly = async (streamBuf, elements, littleEndian) => {
const result = new GSplatCompressedData();

const numChunks = elements[0].count;
const chunkSize = 12 * 4;

const numChunkProperties = elements[0].properties.length;
const numVertices = elements[1].count;
const vertexSize = 4 * 4;

// evaluate the storage size for the given count (this must match the
// texture size calculation in GSplatCompressed).
Expand All @@ -244,64 +264,48 @@ const readCompressedPly = async (streamBuf, elements, littleEndian) => {
};

// allocate result
result.numSplats = elements[1].count;
result.chunkData = new Float32Array(evalStorageSize(numChunks) * 12);
result.numSplats = numVertices;
result.chunkData = new Float32Array(numChunks * numChunkProperties);
result.vertexData = new Uint32Array(evalStorageSize(numVertices) * 4);

let uint32StreamData;
const uint32ChunkData = new Uint32Array(result.chunkData.buffer);
const uint32VertexData = result.vertexData;

// read chunks
let chunks = 0;
while (chunks < numChunks) {
while (streamBuf.remaining < chunkSize) {
/* eslint-disable no-await-in-loop */
await streamBuf.read();
}

// ensure the uint32 view is still valid
if (uint32StreamData?.buffer !== streamBuf.data.buffer) {
uint32StreamData = new Uint32Array(streamBuf.data.buffer, 0, Math.floor(streamBuf.data.buffer.byteLength / 4));
}
// read length bytes of data into buffer
const read = async (buffer, length) => {
const target = new Uint8Array(buffer);
let cursor = 0;

// read the next chunk of data
const toRead = Math.min(numChunks - chunks, Math.floor(streamBuf.remaining / chunkSize));
while (cursor < length) {
while (streamBuf.remaining === 0) {
/* eslint-disable no-await-in-loop */
await streamBuf.read();
}

const dstOffset = chunks * 12;
const srcOffset = streamBuf.head / 4;
for (let i = 0; i < toRead * 12; ++i) {
uint32ChunkData[dstOffset + i] = uint32StreamData[srcOffset + i];
const toCopy = Math.min(length - cursor, streamBuf.remaining);
const src = streamBuf.data;
for (let i = 0; i < toCopy; ++i) {
target[cursor++] = src[streamBuf.head++];
}
}
};

streamBuf.head += toRead * chunkSize;
chunks += toRead;
}
// read chunk data
await read(result.chunkData.buffer, numChunks * numChunkProperties * 4);

// read vertices
let vertices = 0;
while (vertices < numVertices) {
while (streamBuf.remaining < vertexSize) {
/* eslint-disable no-await-in-loop */
await streamBuf.read();
}
// read packed vertices
await read(result.vertexData.buffer, numVertices * 4 * 4);

// ensure the uint32 view is still valid
if (uint32StreamData?.buffer !== streamBuf.data.buffer) {
uint32StreamData = new Uint32Array(streamBuf.data.buffer, 0, Math.floor(streamBuf.data.buffer.byteLength / 4));
}
// read sh data
if (elements.length === 6) {
result.band1Data = new Uint16Array(elements[2].count * 3);
await read(result.band1Data.buffer, result.band1Data.byteLength);

// read the next chunk of data
const toRead = Math.min(numVertices - vertices, Math.floor(streamBuf.remaining / vertexSize));
result.band2Data = new Uint16Array(elements[3].count * 5);
await read(result.band2Data.buffer, result.band2Data.byteLength);

const dstOffset = vertices * 4;
const srcOffset = streamBuf.head / 4;
for (let i = 0; i < toRead * 4; ++i) {
uint32VertexData[dstOffset + i] = uint32StreamData[srcOffset + i];
}
result.band3Data = new Uint16Array(elements[4].count * 7);
await read(result.band3Data.buffer, result.band3Data.byteLength);

streamBuf.head += toRead * vertexSize;
vertices += toRead;
result.packedSHData = new Uint32Array(evalStorageSize(numVertices) * 4);
await read(result.packedSHData.buffer, numVertices * 4 * 4);
}

return result;
Expand Down
2 changes: 1 addition & 1 deletion src/platform/graphics/texture.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ class Texture {
* - {@link FUNC_NOTEQUAL}
*
* Defaults to {@link FUNC_LESS}.
* @param {Uint8Array[]|HTMLCanvasElement[]|HTMLImageElement[]|HTMLVideoElement[]|Uint8Array[][]} [options.levels]
* @param {Uint8Array[]|Uint16Array[]|Uint32Array[]|Float32Array[]|HTMLCanvasElement[]|HTMLImageElement[]|HTMLVideoElement[]|Uint8Array[][]} [options.levels]
slimbuck marked this conversation as resolved.
Show resolved Hide resolved
* - Array of Uint8Array or other supported browser interface; or a two-dimensional array
* of Uint8Array if options.arrayLength is defined and greater than zero.
* @param {boolean} [options.storage] - Defines if texture can be used as a storage texture by
Expand Down
Loading