@loaders.gl/pcd
Version:
Framework-independent loader for the PCD format
508 lines (499 loc) • 17.4 kB
JavaScript
(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 () {
;
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__;
});