UNPKG

@loaders.gl/pcd

Version:

Framework-independent loader for the PCD format

508 lines (499 loc) 17.4 kB
(function webpackUniversalModuleDefinition(root, factory) { if (typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if (typeof define === 'function' && define.amd) define([], factory); else if (typeof exports === 'object') exports['loaders'] = factory(); else root['loaders'] = factory();})(globalThis, function () { "use strict"; var __exports__ = (() => { var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default")); var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // external-global-plugin:@loaders.gl/core var require_core = __commonJS({ "external-global-plugin:@loaders.gl/core"(exports, module) { module.exports = globalThis.loaders; } }); // bundle.ts var bundle_exports = {}; __export(bundle_exports, { PCDLoader: () => PCDLoader2, PCDWorkerLoader: () => PCDLoader }); __reExport(bundle_exports, __toESM(require_core(), 1)); // ../schema/src/lib/mesh/mesh-utils.ts function getMeshBoundingBox(attributes) { let minX = Infinity; let minY = Infinity; let minZ = Infinity; let maxX = -Infinity; let maxY = -Infinity; let maxZ = -Infinity; const positions = attributes.POSITION ? attributes.POSITION.value : []; const len = positions && positions.length; for (let i = 0; i < len; i += 3) { const x = positions[i]; const y = positions[i + 1]; const z = positions[i + 2]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; minZ = z < minZ ? z : minZ; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; maxZ = z > maxZ ? z : maxZ; } return [ [minX, minY, minZ], [maxX, maxY, maxZ] ]; } // src/lib/decompress-lzf.ts function decompressLZF(inData, outLength) { const inLength = inData.length; const outData = new Uint8Array(outLength); let inPtr = 0; let outPtr = 0; let ctrl; let len; let ref; do { ctrl = inData[inPtr++]; if (ctrl < 1 << 5) { ctrl++; if (outPtr + ctrl > outLength) { throw new Error("Output buffer is not large enough"); } if (inPtr + ctrl > inLength) { throw new Error("Invalid compressed data"); } do { outData[outPtr++] = inData[inPtr++]; } while (--ctrl); } else { len = ctrl >> 5; ref = outPtr - ((ctrl & 31) << 8) - 1; if (inPtr >= inLength) { throw new Error("Invalid compressed data"); } if (len === 7) { len += inData[inPtr++]; if (inPtr >= inLength) { throw new Error("Invalid compressed data"); } } ref -= inData[inPtr++]; if (outPtr + len + 2 > outLength) { throw new Error("Output buffer is not large enough"); } if (ref < 0) { throw new Error("Invalid compressed data"); } if (ref >= outPtr) { throw new Error("Invalid compressed data"); } do { outData[outPtr++] = outData[ref++]; } while (--len + 2); } } while (inPtr < inLength); return outData; } // src/lib/get-pcd-schema.ts function getPCDSchema(PCDheader, metadata) { const offset = PCDheader.offset; const fields = []; if (offset.x !== void 0) { fields.push({ name: "POSITION", type: { type: "fixed-size-list", listSize: 3, children: [{ name: "xyz", type: "float32" }] } }); } if (offset.normal_x !== void 0) { fields.push({ name: "NORMAL", type: { type: "fixed-size-list", listSize: 3, children: [{ name: "xyz", type: "float32" }] } }); } if (offset.rgb !== void 0) { fields.push({ name: "COLOR_0", type: { type: "fixed-size-list", listSize: 3, children: [{ name: "rgb", type: "uint8" }] } }); } return { fields, metadata }; } // src/lib/parse-pcd.ts var LITTLE_ENDIAN = true; function parsePCD(data) { const textData = new TextDecoder().decode(data); const pcdHeader = parsePCDHeader(textData); let attributes = {}; switch (pcdHeader.data) { case "ascii": attributes = parsePCDASCII(pcdHeader, textData); break; case "binary": attributes = parsePCDBinary(pcdHeader, data); break; case "binary_compressed": attributes = parsePCDBinaryCompressed(pcdHeader, data); break; default: throw new Error(`PCD: ${pcdHeader.data} files are not supported`); } attributes = getMeshAttributes(attributes); const header = getMeshHeader(pcdHeader, attributes); const metadata = Object.fromEntries([ ["mode", "0"], ["boundingBox", JSON.stringify(header.boundingBox)] ]); const schema = getPCDSchema(pcdHeader, metadata); return { loader: "pcd", loaderData: pcdHeader, header, schema, mode: 0, // POINTS topology: "point-list", attributes }; } function getMeshHeader(pcdHeader, attributes) { if (typeof pcdHeader.width === "number" && typeof pcdHeader.height === "number") { const pointCount = pcdHeader.width * pcdHeader.height; return { vertexCount: pointCount, boundingBox: getMeshBoundingBox(attributes) }; } return { vertexCount: pcdHeader.vertexCount, boundingBox: pcdHeader.boundingBox }; } function getMeshAttributes(attributes) { const normalizedAttributes = { POSITION: { // Binary PCD is only 32 bit value: new Float32Array(attributes.position), size: 3 } }; if (attributes.normal && attributes.normal.length > 0) { normalizedAttributes.NORMAL = { value: new Float32Array(attributes.normal), size: 3 }; } if (attributes.color && attributes.color.length > 0) { normalizedAttributes.COLOR_0 = { value: new Uint8Array(attributes.color), size: 3 }; } if (attributes.intensity && attributes.intensity.length > 0) { normalizedAttributes.COLOR_0 = { value: new Uint8Array(attributes.color), size: 3 }; } if (attributes.label && attributes.label.length > 0) { normalizedAttributes.COLOR_0 = { value: new Uint8Array(attributes.label), size: 3 }; } return normalizedAttributes; } function parsePCDHeader(data) { const result1 = data.search(/[\r\n]DATA\s(\S*)\s/i); const result2 = /[\r\n]DATA\s(\S*)\s/i.exec(data.substr(result1 - 1)); const pcdHeader = {}; pcdHeader.data = result2 && result2[1]; if (result2 !== null) { pcdHeader.headerLen = (result2 && result2[0].length) + result1; } pcdHeader.str = data.substr(0, pcdHeader.headerLen); pcdHeader.str = pcdHeader.str.replace(/\#.*/gi, ""); pcdHeader.version = /VERSION (.*)/i.exec(pcdHeader.str); pcdHeader.fields = /FIELDS (.*)/i.exec(pcdHeader.str); pcdHeader.size = /SIZE (.*)/i.exec(pcdHeader.str); pcdHeader.type = /TYPE (.*)/i.exec(pcdHeader.str); pcdHeader.count = /COUNT (.*)/i.exec(pcdHeader.str); pcdHeader.width = /WIDTH (.*)/i.exec(pcdHeader.str); pcdHeader.height = /HEIGHT (.*)/i.exec(pcdHeader.str); pcdHeader.viewpoint = /VIEWPOINT (.*)/i.exec(pcdHeader.str); pcdHeader.points = /POINTS (.*)/i.exec(pcdHeader.str); if (pcdHeader.version !== null) { pcdHeader.version = parseFloat(pcdHeader.version[1]); } if (pcdHeader.fields !== null) { pcdHeader.fields = pcdHeader.fields[1].split(" "); } if (pcdHeader.type !== null) { pcdHeader.type = pcdHeader.type[1].split(" "); } if (pcdHeader.width !== null) { pcdHeader.width = parseInt(pcdHeader.width[1], 10); } if (pcdHeader.height !== null) { pcdHeader.height = parseInt(pcdHeader.height[1], 10); } if (pcdHeader.viewpoint !== null) { pcdHeader.viewpoint = pcdHeader.viewpoint[1]; } if (pcdHeader.points !== null) { pcdHeader.points = parseInt(pcdHeader.points[1], 10); } if (pcdHeader.points === null && typeof pcdHeader.width === "number" && typeof pcdHeader.height === "number") { pcdHeader.points = pcdHeader.width * pcdHeader.height; } if (pcdHeader.size !== null) { pcdHeader.size = pcdHeader.size[1].split(" ").map((x) => parseInt(x, 10)); } if (pcdHeader.count !== null) { pcdHeader.count = pcdHeader.count[1].split(" ").map((x) => parseInt(x, 10)); } else { pcdHeader.count = []; if (pcdHeader.fields !== null) { for (let i = 0; i < pcdHeader.fields.length; i++) { pcdHeader.count.push(1); } } } pcdHeader.offset = {}; let sizeSum = 0; if (pcdHeader.fields !== null && pcdHeader.size !== null) { for (let i = 0; i < pcdHeader.fields.length; i++) { if (pcdHeader.data === "ascii") { pcdHeader.offset[pcdHeader.fields[i]] = i; } else { pcdHeader.offset[pcdHeader.fields[i]] = sizeSum; sizeSum += pcdHeader.size[i]; } } } pcdHeader.rowSize = sizeSum; return pcdHeader; } function parsePCDASCII(pcdHeader, textData) { const position = []; const normal = []; const color = []; const intensity = []; const label = []; const offset = pcdHeader.offset; const pcdData = textData.substr(pcdHeader.headerLen); const lines = pcdData.split("\n"); for (let i = 0; i < lines.length; i++) { if (lines[i] !== "") { const line = lines[i].split(" "); if (offset.x !== void 0) { position.push(parseFloat(line[offset.x])); position.push(parseFloat(line[offset.y])); position.push(parseFloat(line[offset.z])); } if (offset.rgb !== void 0) { const floatValue = parseFloat(line[offset.rgb]); const binaryColor = new Float32Array([floatValue]); const dataview = new DataView(binaryColor.buffer, 0); color.push(dataview.getUint8(0)); color.push(dataview.getUint8(1)); color.push(dataview.getUint8(2)); } if (offset.normal_x !== void 0) { normal.push(parseFloat(line[offset.normal_x])); normal.push(parseFloat(line[offset.normal_y])); normal.push(parseFloat(line[offset.normal_z])); } if (offset.intensity !== void 0) { intensity.push(parseFloat(line[offset.intensity])); } if (offset.label !== void 0) { label.push(parseInt(line[offset.label])); } } } return { position, normal, color }; } function parsePCDBinary(pcdHeader, data) { const position = []; const normal = []; const color = []; const intensity = []; const label = []; const dataview = new DataView(data, pcdHeader.headerLen); const offset = pcdHeader.offset; for (let i = 0, row = 0; i < pcdHeader.points; i++, row += pcdHeader.rowSize) { if (offset.x !== void 0) { position.push(dataview.getFloat32(row + offset.x, LITTLE_ENDIAN)); position.push(dataview.getFloat32(row + offset.y, LITTLE_ENDIAN)); position.push(dataview.getFloat32(row + offset.z, LITTLE_ENDIAN)); } if (offset.rgb !== void 0) { color.push(dataview.getUint8(row + offset.rgb + 0)); color.push(dataview.getUint8(row + offset.rgb + 1)); color.push(dataview.getUint8(row + offset.rgb + 2)); } if (offset.normal_x !== void 0) { normal.push(dataview.getFloat32(row + offset.normal_x, LITTLE_ENDIAN)); normal.push(dataview.getFloat32(row + offset.normal_y, LITTLE_ENDIAN)); normal.push(dataview.getFloat32(row + offset.normal_z, LITTLE_ENDIAN)); } if (offset.intensity !== void 0) { intensity.push(dataview.getFloat32(row + offset.intensity, LITTLE_ENDIAN)); } if (offset.label !== void 0) { label.push(dataview.getInt32(row + offset.label, LITTLE_ENDIAN)); } } return { position, normal, color, intensity, label }; } function parsePCDBinaryCompressed(pcdHeader, data) { const position = []; const normal = []; const color = []; const intensity = []; const label = []; const sizes = new Uint32Array(data.slice(pcdHeader.headerLen, pcdHeader.headerLen + 8)); const compressedSize = sizes[0]; const decompressedSize = sizes[1]; const decompressed = decompressLZF( new Uint8Array(data, pcdHeader.headerLen + 8, compressedSize), decompressedSize ); const dataview = new DataView(decompressed.buffer); const offset = pcdHeader.offset; for (let i = 0; i < pcdHeader.points; i++) { if (offset.x !== void 0) { position.push( dataview.getFloat32(pcdHeader.points * offset.x + pcdHeader.size[0] * i, LITTLE_ENDIAN) ); position.push( dataview.getFloat32(pcdHeader.points * offset.y + pcdHeader.size[1] * i, LITTLE_ENDIAN) ); position.push( dataview.getFloat32(pcdHeader.points * offset.z + pcdHeader.size[2] * i, LITTLE_ENDIAN) ); } if (offset.rgb !== void 0) { color.push( dataview.getUint8(pcdHeader.points * offset.rgb + pcdHeader.size[3] * i + 0) / 255 ); color.push( dataview.getUint8(pcdHeader.points * offset.rgb + pcdHeader.size[3] * i + 1) / 255 ); color.push( dataview.getUint8(pcdHeader.points * offset.rgb + pcdHeader.size[3] * i + 2) / 255 ); } if (offset.normal_x !== void 0) { normal.push( dataview.getFloat32( pcdHeader.points * offset.normal_x + pcdHeader.size[4] * i, LITTLE_ENDIAN ) ); normal.push( dataview.getFloat32( pcdHeader.points * offset.normal_y + pcdHeader.size[5] * i, LITTLE_ENDIAN ) ); normal.push( dataview.getFloat32( pcdHeader.points * offset.normal_z + pcdHeader.size[6] * i, LITTLE_ENDIAN ) ); } if (offset.intensity !== void 0) { const intensityIndex = pcdHeader.fields.indexOf("intensity"); intensity.push( dataview.getFloat32( pcdHeader.points * offset.intensity + pcdHeader.size[intensityIndex] * i, LITTLE_ENDIAN ) ); } if (offset.label !== void 0) { const labelIndex = pcdHeader.fields.indexOf("label"); label.push( dataview.getInt32( pcdHeader.points * offset.label + pcdHeader.size[labelIndex] * i, LITTLE_ENDIAN ) ); } } return { position, normal, color, intensity, label }; } // src/pcd-loader.ts var VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : "latest"; var PCDLoader = { dataType: null, batchType: null, name: "PCD (Point Cloud Data)", id: "pcd", module: "pcd", version: VERSION, worker: true, extensions: ["pcd"], mimeTypes: ["text/plain"], options: { pcd: {} } }; // src/index.ts var PCDLoader2 = { ...PCDLoader, parse: async (arrayBuffer) => parsePCD(arrayBuffer), parseSync: parsePCD }; return __toCommonJS(bundle_exports); })(); /** Parse compressed PCD data in in binary_compressed form ( https://pointclouds.org/documentation/tutorials/pcd_file_format.html) * from https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/PCDLoader.js * @license MIT (http://opensource.org/licenses/MIT) * @param pcdHeader * @param data * @returns [attributes] */ return __exports__; });