/** * Some types of possible point attribute data formats * * @class */ const PointAttributeTypes = { DATA_TYPE_DOUBLE: {ordinal: 0, name: "double", size: 8}, DATA_TYPE_FLOAT: {ordinal: 1, name: "float", size: 4}, DATA_TYPE_INT8: {ordinal: 2, name: "int8", size: 1}, DATA_TYPE_UINT8: {ordinal: 3, name: "uint8", size: 1}, DATA_TYPE_INT16: {ordinal: 4, name: "int16", size: 2}, DATA_TYPE_UINT16: {ordinal: 5, name: "uint16", size: 2}, DATA_TYPE_INT32: {ordinal: 6, name: "int32", size: 4}, DATA_TYPE_UINT32: {ordinal: 7, name: "uint32", size: 4}, DATA_TYPE_INT64: {ordinal: 8, name: "int64", size: 8}, DATA_TYPE_UINT64: {ordinal: 9, name: "uint64", size: 8} }; let i = 0; for (let obj in PointAttributeTypes) { PointAttributeTypes[i] = PointAttributeTypes[obj]; i++; } class PointAttribute{ constructor(name, type, numElements){ this.name = name; this.type = type; this.numElements = numElements; this.byteSize = this.numElements * this.type.size; this.description = ""; this.range = [Infinity, -Infinity]; } }//add const replacements = { "COLOR_PACKED": "rgba", "RGBA": "rgba", "INTENSITY": "intensity", "CLASSIFICATION": "classification", "GPS_TIME": "gps-time", }; const replaceOldNames = (old) => { if(replacements[old]){ return replacements[old]; }else { return old; } }; PointAttribute.POSITION_CARTESIAN = new PointAttribute( "POSITION_CARTESIAN", PointAttributeTypes.DATA_TYPE_FLOAT, 3); PointAttribute.RGBA_PACKED = new PointAttribute( replaceOldNames("COLOR_PACKED"), PointAttributeTypes.DATA_TYPE_INT8, 4); PointAttribute.COLOR_PACKED = PointAttribute.RGBA_PACKED; PointAttribute.RGB_PACKED = new PointAttribute( "COLOR_PACKED", PointAttributeTypes.DATA_TYPE_INT8, 3); PointAttribute.NORMAL_FLOATS = new PointAttribute( "NORMAL_FLOATS", PointAttributeTypes.DATA_TYPE_FLOAT, 3); PointAttribute.INTENSITY = new PointAttribute( replaceOldNames("INTENSITY"), PointAttributeTypes.DATA_TYPE_UINT16, 1); PointAttribute.CLASSIFICATION = new PointAttribute( replaceOldNames("CLASSIFICATION"), PointAttributeTypes.DATA_TYPE_UINT8, 1); PointAttribute.NORMAL_SPHEREMAPPED = new PointAttribute( "NORMAL_SPHEREMAPPED", PointAttributeTypes.DATA_TYPE_UINT8, 2); PointAttribute.NORMAL_OCT16 = new PointAttribute( "NORMAL_OCT16", PointAttributeTypes.DATA_TYPE_UINT8, 2); PointAttribute.NORMAL = new PointAttribute( "NORMAL", PointAttributeTypes.DATA_TYPE_FLOAT, 3); PointAttribute.RETURN_NUMBER = new PointAttribute( "RETURN_NUMBER", PointAttributeTypes.DATA_TYPE_UINT8, 1); PointAttribute.NUMBER_OF_RETURNS = new PointAttribute( "NUMBER_OF_RETURNS", PointAttributeTypes.DATA_TYPE_UINT8, 1); PointAttribute.SOURCE_ID = new PointAttribute( "SOURCE_ID", PointAttributeTypes.DATA_TYPE_UINT16, 1); PointAttribute.INDICES = new PointAttribute( "INDICES", PointAttributeTypes.DATA_TYPE_UINT32, 1); PointAttribute.SPACING = new PointAttribute( "SPACING", PointAttributeTypes.DATA_TYPE_FLOAT, 1); PointAttribute.GPS_TIME = new PointAttribute( replaceOldNames("GPS_TIME"), PointAttributeTypes.DATA_TYPE_DOUBLE, 1); const typedArrayMapping = { "int8": Int8Array, "int16": Int16Array, "int32": Int32Array, "int64": Float64Array, "uint8": Uint8Array, "uint16": Uint16Array, "uint32": Uint32Array, "uint64": Float64Array, "float": Float32Array, "double": Float64Array, }; Potree = {}; onmessage = function (event) { let {buffer, pointAttributes, scale, name, min, max, size, offset, numPoints} = event.data; let tStart = performance.now(); let view = new DataView(buffer); let attributeBuffers = {}; let attributeOffset = 0; let bytesPerPoint = 0; for (let pointAttribute of pointAttributes.attributes) { bytesPerPoint += pointAttribute.byteSize; } let gridSize = 32; let grid = new Uint32Array(gridSize ** 3); let toIndex = (x, y, z) => { // let dx = gridSize * (x - min.x) / size.x; // let dy = gridSize * (y - min.y) / size.y; // let dz = gridSize * (z - min.z) / size.z; // min is already subtracted let dx = gridSize * x / size.x; let dy = gridSize * y / size.y; let dz = gridSize * z / size.z; let ix = Math.min(parseInt(dx), gridSize - 1); let iy = Math.min(parseInt(dy), gridSize - 1); let iz = Math.min(parseInt(dz), gridSize - 1); let index = ix + iy * gridSize + iz * gridSize * gridSize; return index; }; let numOccupiedCells = 0; for (let pointAttribute of pointAttributes.attributes) { if(["POSITION_CARTESIAN", "position"].includes(pointAttribute.name)){ let buff = new ArrayBuffer(numPoints * 4 * 3); let positions = new Float32Array(buff); for (let j = 0; j < numPoints; j++) { let pointOffset = j * bytesPerPoint; let x = (view.getInt32(pointOffset + attributeOffset + 0, true) * scale[0]) + offset[0] - min.x; let y = (view.getInt32(pointOffset + attributeOffset + 4, true) * scale[1]) + offset[1] - min.y; let z = (view.getInt32(pointOffset + attributeOffset + 8, true) * scale[2]) + offset[2] - min.z; let index = toIndex(x, y, z); let count = grid[index]++; if(count === 0){ numOccupiedCells++; } positions[3 * j + 0] = x; positions[3 * j + 1] = y; positions[3 * j + 2] = z; } attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute }; }else if(["RGBA", "rgba"].includes(pointAttribute.name)){ let buff = new ArrayBuffer(numPoints * 4); let colors = new Uint8Array(buff); for (let j = 0; j < numPoints; j++) { let pointOffset = j * bytesPerPoint; let r = view.getUint16(pointOffset + attributeOffset + 0, true); let g = view.getUint16(pointOffset + attributeOffset + 2, true); let b = view.getUint16(pointOffset + attributeOffset + 4, true); colors[4 * j + 0] = r > 255 ? r / 256 : r; colors[4 * j + 1] = g > 255 ? g / 256 : g; colors[4 * j + 2] = b > 255 ? b / 256 : b; } attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute }; }else { let buff = new ArrayBuffer(numPoints * 4); let f32 = new Float32Array(buff); let TypedArray = typedArrayMapping[pointAttribute.type.name]; preciseBuffer = new TypedArray(numPoints); let [offset, scale] = [0, 1]; const getterMap = { "int8": view.getInt8, "int16": view.getInt16, "int32": view.getInt32, // "int64": view.getInt64, "uint8": view.getUint8, "uint16": view.getUint16, "uint32": view.getUint32, // "uint64": view.getUint64, "float": view.getFloat32, "double": view.getFloat64, }; const getter = getterMap[pointAttribute.type.name].bind(view); // compute offset and scale to pack larger types into 32 bit floats if(pointAttribute.type.size > 4){ let [amin, amax] = pointAttribute.range; offset = amin; scale = 1 / (amax - amin); } for(let j = 0; j < numPoints; j++){ let pointOffset = j * bytesPerPoint; let value = getter(pointOffset + attributeOffset, true); f32[j] = (value - offset) * scale; preciseBuffer[j] = value; } attributeBuffers[pointAttribute.name] = { buffer: buff, preciseBuffer: preciseBuffer, attribute: pointAttribute, offset: offset, scale: scale, }; } attributeOffset += pointAttribute.byteSize; } let occupancy = parseInt(numPoints / numOccupiedCells); // console.log(`${name}: #points: ${numPoints}: #occupiedCells: ${numOccupiedCells}, occupancy: ${occupancy} points/cell`); { // add indices let buff = new ArrayBuffer(numPoints * 4); let indices = new Uint32Array(buff); for (let i = 0; i < numPoints; i++) { indices[i] = i; } attributeBuffers["INDICES"] = { buffer: buff, attribute: PointAttribute.INDICES }; } { // handle attribute vectors let vectors = pointAttributes.vectors; for(let vector of vectors){ let {name, attributes} = vector; let numVectorElements = attributes.length; let buffer = new ArrayBuffer(numVectorElements * numPoints * 4); let f32 = new Float32Array(buffer); let iElement = 0; for(let sourceName of attributes){ let sourceBuffer = attributeBuffers[sourceName]; let {offset, scale} = sourceBuffer; let view = new DataView(sourceBuffer.buffer); const getter = view.getFloat32.bind(view); for(let j = 0; j < numPoints; j++){ let value = getter(j * 4, true); f32[j * numVectorElements + iElement] = (value / scale) + offset; } iElement++; } let vecAttribute = new PointAttribute(name, PointAttributeTypes.DATA_TYPE_FLOAT, 3); attributeBuffers[name] = { buffer: buffer, attribute: vecAttribute, }; } } // let duration = performance.now() - tStart; // let pointsPerMs = numPoints / duration; // console.log(`duration: ${duration.toFixed(1)}ms, #points: ${numPoints}, points/ms: ${pointsPerMs.toFixed(1)}`); let message = { buffer: buffer, attributeBuffers: attributeBuffers, density: occupancy, }; let transferables = []; for (let property in message.attributeBuffers) { transferables.push(message.attributeBuffers[property].buffer); } transferables.push(buffer); postMessage(message, transferables); };