Skip to content

Instantly share code, notes, and snippets.

@lardratboy
Created December 22, 2025 22:50
Show Gist options
  • Select an option

  • Save lardratboy/4976d1cccd5c39e8ea3d434102481e7d to your computer and use it in GitHub Desktop.

Select an option

Save lardratboy/4976d1cccd5c39e8ea3d434102481e7d to your computer and use it in GitHub Desktop.
quantized integer point cloud helper
// Data type configuration constants
const DATA_TYPES = {
int8: { size: 1, min: -128, max: 127, method: 'getInt8' },
uint8: { size: 1, min: 0, max: 255, method: 'getUint8' },
int16: { size: 2, min: -32768, max: 32767, method: 'getInt16' },
uint16: { size: 2, min: 0, max: 65535, method: 'getUint16' },
int32: { size: 4, min: -2147483648, max: 2147483647, method: 'getInt32' },
uint32: { size: 4, min: 0, max: 4294967295, method: 'getUint32' }
};
// Pre-calculate normalization multipliers for each data type
const NORMALIZERS = Object.fromEntries(
Object.entries(DATA_TYPES).map(([type, config]) => [
type,
{
multiplier: 2 / (config.max - config.min),
offset: config.min
}
])
);
/**
* Process binary data into normalized 3D points with colors
* @param {ArrayBuffer} buffer - The input binary data
* @param {string} dataType - The data type to interpret the buffer as
* @param {boolean} isLittleEndian - Whether to read as little endian
* @returns {{ points: Float32Array, colors: Float32Array, numPoints: number }}
*/
function quantizeProcessDataAs(buffer, dataType, isLittleEndian) {
// Input validation
if (!buffer || !(buffer instanceof ArrayBuffer)) {
throw new Error('Invalid buffer provided - must be an ArrayBuffer');
}
const config = DATA_TYPES[dataType];
if (!config) {
throw new Error(`Unsupported data type: ${dataType}. Supported types: ${Object.keys(DATA_TYPES).join(', ')}`);
}
const typeSize = config.size;
const tupleSize = typeSize * 3;
if (buffer.byteLength < tupleSize) {
throw new Error(`Buffer too small for data type ${dataType}. Need at least ${tupleSize} bytes, got ${buffer.byteLength}`);
}
const view = new DataView(buffer);
const maxOffset = buffer.byteLength - tupleSize;
const maxTuples = Math.floor(buffer.byteLength / tupleSize);
// Pre-allocate typed arrays for better performance
const points = new Float32Array(maxTuples * 3);
const colors = new Float32Array(maxTuples * 3);
// Cache normalization values and methods
const { multiplier, offset } = NORMALIZERS[dataType];
const readMethod = view[config.method].bind(view);
const normalize = value => ((value - offset) * multiplier) - 1;
let pointIndex = 0;
let baseOffset = 0;
const qRange = 1024;
const qHalfRange = qRange / 2;
const qMaxIndex = qRange - 1;
// Bit array sized for 1024^3 possible quantized positions
const totalQuantizedPositions = qRange * qRange * qRange;
const bitArraySizeInUint32 = Math.ceil(totalQuantizedPositions / 32);
const tupleBitArray = new Uint32Array(bitArraySizeInUint32);
try {
while (baseOffset <= maxOffset) {
// Read and normalize all three coordinates
const x = normalize(readMethod(baseOffset, isLittleEndian));
const y = normalize(readMethod(baseOffset + typeSize, isLittleEndian));
const z = normalize(readMethod(baseOffset + typeSize * 2, isLittleEndian));
// Quantize coordinates: map [-1,1] to [0,1023] with bounds checking
const qx = Math.max(0, Math.min(qMaxIndex, Math.floor((x + 1) * qHalfRange)));
const qy = Math.max(0, Math.min(qMaxIndex, Math.floor((y + 1) * qHalfRange)));
const qz = Math.max(0, Math.min(qMaxIndex, Math.floor((z + 1) * qHalfRange)));
// Create unique index for this quantized position
const qIndex = (qz << 20) | (qy << 10) | qx;
// Check if we've seen this quantized position before
const elementIndex = qIndex >> 5;
const bitPosition = qIndex & 0x1F;
const mask = 1 << bitPosition;
if ((tupleBitArray[elementIndex] & mask) === 0) {
// Mark this position as seen
tupleBitArray[elementIndex] |= mask;
// Store points (original normalized coordinates, not quantized)
points[pointIndex] = x;
points[pointIndex + 1] = y;
points[pointIndex + 2] = z;
// Store colors (mapped from [-1,1] to [0,1] for Three.js)
colors[pointIndex] = (x + 1) / 2;
colors[pointIndex + 1] = (y + 1) / 2;
colors[pointIndex + 2] = (z + 1) / 2;
pointIndex += 3;
}
baseOffset += tupleSize;
}
} catch (e) {
console.error(`Error processing data at offset: ${baseOffset}`, e);
// Return what we've processed so far rather than failing completely
}
// Trim arrays to actual size used
const actualPoints = new Float32Array(points.buffer, 0, pointIndex);
const actualColors = new Float32Array(colors.buffer, 0, pointIndex);
return {
points: actualPoints,
colors: actualColors,
numPoints: pointIndex / 3
};
}
export { quantizeProcessDataAs, DATA_TYPES };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment