Created
December 22, 2025 22:50
-
-
Save lardratboy/4976d1cccd5c39e8ea3d434102481e7d to your computer and use it in GitHub Desktop.
quantized integer point cloud helper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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