@loaders.gl/ply
Version:
Framework-independent loader for the PLY format
643 lines (634 loc) • 19.4 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// dist/index.js
var dist_exports = {};
__export(dist_exports, {
PLYLoader: () => PLYLoader2,
PLYWorkerLoader: () => PLYLoader
});
module.exports = __toCommonJS(dist_exports);
// dist/ply-loader.js
var VERSION = true ? "4.3.2" : "latest";
var PLYLoader = {
dataType: null,
batchType: null,
name: "PLY",
id: "ply",
module: "ply",
// shapes: ['mesh', 'gltf', 'columnar-table'],
version: VERSION,
worker: true,
extensions: ["ply"],
mimeTypes: ["text/plain", "application/octet-stream"],
text: true,
binary: true,
tests: ["ply"],
options: {
ply: {}
}
};
// dist/lib/normalize-ply.js
var import_schema2 = require("@loaders.gl/schema");
// dist/lib/get-ply-schema.js
var import_schema = require("@loaders.gl/schema");
function getPLYSchema(plyHeader, attributes) {
const metadata = makeMetadataFromPlyHeader(plyHeader);
const schema = (0, import_schema.deduceMeshSchema)(attributes, metadata);
return schema;
}
function makeMetadataFromPlyHeader(plyHeader) {
const metadata = {};
metadata.ply_comments = JSON.stringify(plyHeader.comments);
metadata.ply_elements = JSON.stringify(plyHeader.elements);
if (plyHeader.format !== void 0) {
metadata.ply_format = plyHeader.format;
}
if (plyHeader.version !== void 0) {
metadata.ply_version = plyHeader.version;
}
if (plyHeader.headerLength !== void 0) {
metadata.ply_headerLength = plyHeader.headerLength.toString(10);
}
return metadata;
}
// dist/lib/normalize-ply.js
function normalizePLY(plyHeader, plyAttributes, options) {
const attributes = getMeshAttributes(plyAttributes);
const boundingBox = (0, import_schema2.getMeshBoundingBox)(attributes);
const vertexCount = plyAttributes.indices.length || plyAttributes.vertices.length / 3;
const isTriangles = plyAttributes.indices && plyAttributes.indices.length > 0;
const mode = isTriangles ? 4 : 0;
const topology = isTriangles ? "triangle-list" : "point-list";
const schema = getPLYSchema(plyHeader, attributes);
const plyMesh = {
loader: "ply",
loaderData: plyHeader,
header: {
vertexCount,
boundingBox
},
schema,
attributes,
indices: { value: new Uint32Array(0), size: 0 },
mode,
topology
};
if (plyAttributes.indices.length > 0) {
plyMesh.indices = { value: new Uint32Array(plyAttributes.indices), size: 1 };
}
return plyMesh;
}
function getMeshAttributes(attributes) {
const accessors = {};
for (const attributeName of Object.keys(attributes)) {
switch (attributeName) {
case "vertices":
if (attributes.vertices.length > 0) {
accessors.POSITION = { value: new Float32Array(attributes.vertices), size: 3 };
}
break;
case "normals":
if (attributes.normals.length > 0) {
accessors.NORMAL = { value: new Float32Array(attributes.normals), size: 3 };
}
break;
case "uvs":
if (attributes.uvs.length > 0) {
accessors.TEXCOORD_0 = { value: new Float32Array(attributes.uvs), size: 2 };
}
break;
case "colors":
if (attributes.colors.length > 0) {
accessors.COLOR_0 = { value: new Uint8Array(attributes.colors), size: 3, normalized: true };
}
break;
case "indices":
break;
default:
if (attributes[attributeName].length > 0) {
accessors[attributeName] = { value: new Float32Array(attributes[attributeName]), size: 1 };
}
break;
}
}
return accessors;
}
// dist/lib/parse-ply.js
function parsePLY(data, options = {}) {
let header;
let attributes;
if (data instanceof ArrayBuffer) {
const text = new TextDecoder().decode(data);
header = parseHeader(text, options);
attributes = header.format === "ascii" ? parseASCII(text, header) : parseBinary(data, header);
} else {
header = parseHeader(data, options);
attributes = parseASCII(data, header);
}
return normalizePLY(header, attributes);
}
function parseHeader(data, options) {
const PLY_HEADER_PATTERN = /ply([\s\S]*)end_header\s/;
let headerText = "";
let headerLength = 0;
const result = PLY_HEADER_PATTERN.exec(data);
if (result !== null) {
headerText = result[1];
headerLength = result[0].length;
}
const lines = headerText.split("\n");
const header = parseHeaderLines(lines, headerLength, options);
return header;
}
function parseHeaderLines(lines, headerLength, options) {
const header = {
comments: [],
elements: [],
headerLength
};
let lineType;
let lineValues;
let currentElement2 = null;
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
line = line.trim();
if (line === "") {
continue;
}
lineValues = line.split(/\s+/);
lineType = lineValues.shift();
line = lineValues.join(" ");
switch (lineType) {
case "format":
header.format = lineValues[0];
header.version = lineValues[1];
break;
case "comment":
header.comments.push(line);
break;
case "element":
if (currentElement2) {
header.elements.push(currentElement2);
}
currentElement2 = {
name: lineValues[0],
count: parseInt(lineValues[1], 10),
properties: []
};
break;
case "property":
if (currentElement2) {
const property = makePLYElementProperty(lineValues);
if ((options == null ? void 0 : options.propertyNameMapping) && property.name in (options == null ? void 0 : options.propertyNameMapping)) {
property.name = options == null ? void 0 : options.propertyNameMapping[property.name];
}
currentElement2.properties.push(property);
}
break;
default:
console.log("unhandled", lineType, lineValues);
}
}
if (currentElement2) {
header.elements.push(currentElement2);
}
return header;
}
function getPLYAttributes(header) {
const attributes = {
indices: [],
vertices: [],
normals: [],
uvs: [],
colors: []
};
for (const element of header.elements) {
if (element.name === "vertex") {
for (const property of element.properties) {
switch (property.name) {
case "x":
case "y":
case "z":
case "nx":
case "ny":
case "nz":
case "s":
case "t":
case "red":
case "green":
case "blue":
break;
default:
attributes[property.name] = [];
break;
}
}
}
}
return attributes;
}
function makePLYElementProperty(propertyValues) {
const type = propertyValues[0];
switch (type) {
case "list":
return {
type,
name: propertyValues[3],
countType: propertyValues[1],
itemType: propertyValues[2]
};
default:
return {
type,
name: propertyValues[1]
};
}
}
function parseASCIINumber(n, type) {
switch (type) {
case "char":
case "uchar":
case "short":
case "ushort":
case "int":
case "uint":
case "int8":
case "uint8":
case "int16":
case "uint16":
case "int32":
case "uint32":
return parseInt(n, 10);
case "float":
case "double":
case "float32":
case "float64":
return parseFloat(n);
default:
throw new Error(type);
}
}
function parsePLYElement(properties, line) {
const values = line.split(/\s+/);
const element = {};
for (let i = 0; i < properties.length; i++) {
if (properties[i].type === "list") {
const list = [];
const n = parseASCIINumber(values.shift(), properties[i].countType);
for (let j = 0; j < n; j++) {
list.push(parseASCIINumber(values.shift(), properties[i].itemType));
}
element[properties[i].name] = list;
} else {
element[properties[i].name] = parseASCIINumber(values.shift(), properties[i].type);
}
}
return element;
}
function parseASCII(data, header) {
const attributes = getPLYAttributes(header);
let result;
const patternBody = /end_header\s([\s\S]*)$/;
let body = "";
if ((result = patternBody.exec(data)) !== null) {
body = result[1];
}
const lines = body.split("\n");
let currentElement2 = 0;
let currentElementCount = 0;
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
line = line.trim();
if (line !== "") {
if (currentElementCount >= header.elements[currentElement2].count) {
currentElement2++;
currentElementCount = 0;
}
const element = parsePLYElement(header.elements[currentElement2].properties, line);
handleElement(attributes, header.elements[currentElement2].name, element);
currentElementCount++;
}
}
return attributes;
}
function handleElement(buffer, elementName, element = {}) {
if (elementName === "vertex") {
for (const propertyName of Object.keys(element)) {
switch (propertyName) {
case "x":
buffer.vertices.push(element.x, element.y, element.z);
break;
case "y":
case "z":
break;
case "nx":
if ("nx" in element && "ny" in element && "nz" in element) {
buffer.normals.push(element.nx, element.ny, element.nz);
}
break;
case "ny":
case "nz":
break;
case "s":
if ("s" in element && "t" in element) {
buffer.uvs.push(element.s, element.t);
}
break;
case "t":
break;
case "red":
if ("red" in element && "green" in element && "blue" in element) {
buffer.colors.push(element.red, element.green, element.blue);
}
break;
case "green":
case "blue":
break;
default:
buffer[propertyName].push(element[propertyName]);
}
}
} else if (elementName === "face") {
const vertexIndices = element.vertex_indices || element.vertex_index;
if (vertexIndices.length === 3) {
buffer.indices.push(vertexIndices[0], vertexIndices[1], vertexIndices[2]);
} else if (vertexIndices.length === 4) {
buffer.indices.push(vertexIndices[0], vertexIndices[1], vertexIndices[3]);
buffer.indices.push(vertexIndices[1], vertexIndices[2], vertexIndices[3]);
}
}
}
function binaryRead(dataview, at, type, littleEndian) {
switch (type) {
case "int8":
case "char":
return [dataview.getInt8(at), 1];
case "uint8":
case "uchar":
return [dataview.getUint8(at), 1];
case "int16":
case "short":
return [dataview.getInt16(at, littleEndian), 2];
case "uint16":
case "ushort":
return [dataview.getUint16(at, littleEndian), 2];
case "int32":
case "int":
return [dataview.getInt32(at, littleEndian), 4];
case "uint32":
case "uint":
return [dataview.getUint32(at, littleEndian), 4];
case "float32":
case "float":
return [dataview.getFloat32(at, littleEndian), 4];
case "float64":
case "double":
return [dataview.getFloat64(at, littleEndian), 8];
default:
throw new Error(type);
}
}
function binaryReadElement(dataview, at, properties, littleEndian) {
const element = {};
let result;
let read = 0;
for (let i = 0; i < properties.length; i++) {
if (properties[i].type === "list") {
const list = [];
result = binaryRead(dataview, at + read, properties[i].countType, littleEndian);
const n = result[0];
read += result[1];
for (let j = 0; j < n; j++) {
result = binaryRead(dataview, at + read, properties[i].itemType, littleEndian);
list.push(result[0]);
read += result[1];
}
element[properties[i].name] = list;
} else {
result = binaryRead(dataview, at + read, properties[i].type, littleEndian);
element[properties[i].name] = result[0];
read += result[1];
}
}
return [element, read];
}
function parseBinary(data, header) {
const attributes = getPLYAttributes(header);
const littleEndian = header.format === "binary_little_endian";
const body = new DataView(data, header.headerLength);
let result;
let loc = 0;
for (let currentElement2 = 0; currentElement2 < header.elements.length; currentElement2++) {
const count = header.elements[currentElement2].count;
for (let currentElementCount = 0; currentElementCount < count; currentElementCount++) {
result = binaryReadElement(body, loc, header.elements[currentElement2].properties, littleEndian);
loc += result[1];
const element = result[0];
handleElement(attributes, header.elements[currentElement2].name, element);
}
}
return attributes;
}
// dist/lib/parse-ply-in-batches.js
var import_loader_utils = require("@loaders.gl/loader-utils");
var currentElement;
async function* parsePLYInBatches(iterator, options) {
const lineIterator = (0, import_loader_utils.makeLineIterator)((0, import_loader_utils.makeTextDecoderIterator)(iterator));
const header = await parsePLYHeader(lineIterator, options);
let attributes;
switch (header.format) {
case "ascii":
attributes = await parseASCII2(lineIterator, header);
break;
default:
throw new Error("Binary PLY can not yet be parsed in streaming mode");
}
yield normalizePLY(header, attributes, options);
}
async function parsePLYHeader(lineIterator, options) {
const header = {
comments: [],
elements: []
// headerLength
};
await (0, import_loader_utils.forEach)(lineIterator, (line) => {
line = line.trim();
if (line === "end_header") {
return true;
}
if (line === "") {
return false;
}
const lineValues = line.split(/\s+/);
const lineType = lineValues.shift();
line = lineValues.join(" ");
switch (lineType) {
case "ply":
break;
case "format":
header.format = lineValues[0];
header.version = lineValues[1];
break;
case "comment":
header.comments.push(line);
break;
case "element":
if (currentElement) {
header.elements.push(currentElement);
}
currentElement = {
name: lineValues[0],
count: parseInt(lineValues[1], 10),
properties: []
};
break;
case "property":
const property = makePLYElementProperty2(lineValues, options.propertyNameMapping);
currentElement.properties.push(property);
break;
default:
console.log("unhandled", lineType, lineValues);
}
return false;
});
if (currentElement) {
header.elements.push(currentElement);
}
return header;
}
function makePLYElementProperty2(propertyValues, propertyNameMapping) {
const type = propertyValues[0];
switch (type) {
case "list":
return {
type,
name: propertyValues[3],
countType: propertyValues[1],
itemType: propertyValues[2]
};
default:
return {
type,
name: propertyValues[1]
};
}
}
async function parseASCII2(lineIterator, header) {
const attributes = {
indices: [],
vertices: [],
normals: [],
uvs: [],
colors: []
};
let currentElement2 = 0;
let currentElementCount = 0;
for await (let line of lineIterator) {
line = line.trim();
if (line !== "") {
if (currentElementCount >= header.elements[currentElement2].count) {
currentElement2++;
currentElementCount = 0;
}
const element = parsePLYElement2(header.elements[currentElement2].properties, line);
handleElement2(attributes, header.elements[currentElement2].name, element);
currentElementCount++;
}
}
return attributes;
}
function parseASCIINumber2(n, type) {
switch (type) {
case "char":
case "uchar":
case "short":
case "ushort":
case "int":
case "uint":
case "int8":
case "uint8":
case "int16":
case "uint16":
case "int32":
case "uint32":
return parseInt(n, 10);
case "float":
case "double":
case "float32":
case "float64":
return parseFloat(n);
default:
throw new Error(type);
}
}
function parsePLYElement2(properties, line) {
const values = line.split(/\s+/);
const element = {};
for (let i = 0; i < properties.length; i++) {
if (properties[i].type === "list") {
const list = [];
const n = parseASCIINumber2(values.shift(), properties[i].countType);
for (let j = 0; j < n; j++) {
list.push(parseASCIINumber2(values.shift(), properties[i].itemType));
}
element[properties[i].name] = list;
} else {
element[properties[i].name] = parseASCIINumber2(values.shift(), properties[i].type);
}
}
return element;
}
function handleElement2(buffer, elementName, element = {}) {
switch (elementName) {
case "vertex":
buffer.vertices.push(element.x, element.y, element.z);
if ("nx" in element && "ny" in element && "nz" in element) {
buffer.normals.push(element.nx, element.ny, element.nz);
}
if ("s" in element && "t" in element) {
buffer.uvs.push(element.s, element.t);
}
if ("red" in element && "green" in element && "blue" in element) {
buffer.colors.push(element.red / 255, element.green / 255, element.blue / 255);
}
break;
case "face":
const vertexIndices = element.vertex_indices || element.vertex_index;
if (vertexIndices.length === 3) {
buffer.indices.push(vertexIndices[0], vertexIndices[1], vertexIndices[2]);
} else if (vertexIndices.length === 4) {
buffer.indices.push(vertexIndices[0], vertexIndices[1], vertexIndices[3]);
buffer.indices.push(vertexIndices[1], vertexIndices[2], vertexIndices[3]);
}
break;
default:
break;
}
}
// dist/index.js
var PLYLoader2 = {
...PLYLoader,
// Note: parsePLY supports both text and binary
parse: async (arrayBuffer, options) => parsePLY(arrayBuffer, options == null ? void 0 : options.ply),
// TODO - this may not detect text correctly?
parseTextSync: (arrayBuffer, options) => parsePLY(arrayBuffer, options == null ? void 0 : options.ply),
parseSync: (arrayBuffer, options) => parsePLY(arrayBuffer, options == null ? void 0 : options.ply),
parseInBatches: (arrayBuffer, options) => parsePLYInBatches(arrayBuffer, options == null ? void 0 : options.ply)
};
//# sourceMappingURL=index.cjs.map