useloadpcd
Version:
A .pcd file loader for React with React hooks and Three.js.
272 lines (219 loc) • 7.73 kB
text/typescript
import * as THREE from 'three';
class PCDLoader extends THREE.Loader {
public manager?: THREE.LoadingManager;
public littleEndian: boolean;
public path: string;
constructor(manager?: THREE.LoadingManager) {
super();
this.manager =
manager !== undefined ? manager : THREE.DefaultLoadingManager;
this.littleEndian = true;
this.path = '';
}
public load(
url: string,
onLoad: (data: any) => void,
onProgress: (request: ProgressEvent) => void,
onError: (event: ErrorEvent) => void
) {
const loader = new THREE.FileLoader(this.manager);
loader.setPath(this.path);
loader.setResponseType('arraybuffer');
loader.load(
url,
data => {
try {
onLoad(this.parse(data, url));
} catch (e) {
if (onError) {
onError(e);
} else {
throw e;
}
}
},
onProgress,
onError
);
}
public setPath(value: string) {
this.path = value;
return this;
}
public parse(data: any, url: string) {
const parseHeader = (headerData: string) => {
const header: any = {};
const result1 = headerData.search(/[\r\n]DATA\s(\S*)\s/i);
const result2: any = /[\r\n]DATA\s(\S*)\s/i.exec(
headerData.substr(result1 - 1)
);
header.data = result2[1];
header.headerLen = result2[0].length + result1;
header.str = headerData.substr(0, header.headerLen);
// remove comments
header.str = header.str.replace(/\#.*/gi, '');
// parse
header.version = /VERSION (.*)/i.exec(header.str);
header.fields = /FIELDS (.*)/i.exec(header.str);
header.size = /SIZE (.*)/i.exec(header.str);
header.type = /TYPE (.*)/i.exec(header.str);
header.count = /COUNT (.*)/i.exec(header.str);
header.width = /WIDTH (.*)/i.exec(header.str);
header.height = /HEIGHT (.*)/i.exec(header.str);
header.viewpoint = /VIEWPOINT (.*)/i.exec(header.str);
header.points = /POINTS (.*)/i.exec(header.str);
// evaluate
if (header.version !== null) {
header.version = parseFloat(header.version[1]);
}
if (header.fields !== null) header.fields = header.fields[1].split(' ');
if (header.type !== null) header.type = header.type[1].split(' ');
if (header.width !== null) header.width = parseInt(header.width[1], 10);
if (header.height !== null) {
header.height = parseInt(header.height[1], 10);
}
if (header.viewpoint !== null) header.viewpoint = header.viewpoint[1];
if (header.points !== null) {
header.points = parseInt(header.points[1], 10);
}
if (header.points === null) header.points = header.width * header.height;
if (header.size !== null) {
header.size = header.size[1].split(' ').map((x: string) => {
return parseInt(x, 10);
});
}
if (header.count !== null) {
header.count = header.count[1].split(' ').map((x: string) => {
return parseInt(x, 10);
});
} else {
header.count = [];
for (let iter = 0, l = header.fields.length; iter < l; iter += 1) {
header.count.push(1);
}
}
header.offset = {};
let sizeSum = 0;
for (let iter = 0, l = header.fields.length; iter < l; iter += 1) {
if (header.data === 'ascii') {
header.offset[header.fields[iter]] = iter;
} else {
header.offset[header.fields[iter]] = sizeSum;
sizeSum += header.size[iter];
}
}
// for binary only
header.rowSize = sizeSum;
return header;
};
const textData = THREE.LoaderUtils.decodeText(data);
const pcdHeader = parseHeader(textData);
const position = [];
const normal = [];
const color = [];
if (pcdHeader.data === 'ascii') {
const offset = pcdHeader.offset;
const pcdData = textData.substr(pcdHeader.headerLen);
const lines = pcdData.split('\n');
for (let iter = 0, l = lines.length; iter < l; iter += 1) {
if (lines[iter] === '') continue;
const line = lines[iter].split(' ');
if (offset.x !== undefined) {
position.push(parseFloat(line[offset.x]));
position.push(parseFloat(line[offset.y]));
position.push(parseFloat(line[offset.z]));
}
if (offset.rgb !== undefined) {
const rgb = parseFloat(line[offset.rgb]);
const r = (rgb >> 16) & 0x0000ff;
const g = (rgb >> 8) & 0x0000ff;
const b = (rgb >> 0) & 0x0000ff;
color.push(r / 255, g / 255, b / 255);
}
if (offset.normal_x !== undefined) {
normal.push(parseFloat(line[offset.normal_x]));
normal.push(parseFloat(line[offset.normal_y]));
normal.push(parseFloat(line[offset.normal_z]));
}
}
}
if (pcdHeader.data === 'binary_compressed') {
console.error(
'THREE.PCDLoader: binary_compressed files are not supported'
);
return;
}
if (pcdHeader.data === 'binary') {
const dataview = new DataView(data, pcdHeader.headerLen);
const offset = pcdHeader.offset;
for (
let iter = 0, row = 0;
iter < pcdHeader.points;
iter += 1, row += pcdHeader.rowSize
) {
if (offset.x !== undefined) {
position.push(dataview.getFloat32(row + offset.x, this.littleEndian));
position.push(dataview.getFloat32(row + offset.y, this.littleEndian));
position.push(dataview.getFloat32(row + offset.z, this.littleEndian));
}
if (offset.rgb !== undefined) {
color.push(dataview.getUint8(row + offset.rgb + 2) / 255.0);
color.push(dataview.getUint8(row + offset.rgb + 1) / 255.0);
color.push(dataview.getUint8(row + offset.rgb + 0) / 255.0);
}
if (offset.normal_x !== undefined) {
normal.push(
dataview.getFloat32(row + offset.normal_x, this.littleEndian)
);
normal.push(
dataview.getFloat32(row + offset.normal_y, this.littleEndian)
);
normal.push(
dataview.getFloat32(row + offset.normal_z, this.littleEndian)
);
}
}
}
const geometry = new THREE.BufferGeometry();
if (position.length > 0) {
geometry.addAttribute(
'position',
new THREE.Float32BufferAttribute(position, 3)
);
}
if (normal.length > 0) {
geometry.addAttribute(
'normal',
new THREE.Float32BufferAttribute(normal, 3)
);
}
if (color.length > 0) {
geometry.addAttribute(
'color',
new THREE.Float32BufferAttribute(color, 3)
);
}
geometry.computeBoundingSphere();
// build material
const material = new THREE.PointsMaterial({ size: 0.005 });
if (color.length > 0) {
material.vertexColors = THREE.VertexColors;
} else {
material.color.setHex(Math.random() * 0xffffff);
}
// build mesh
const mesh = new THREE.Points(geometry, material);
let name: string | RegExpExecArray = url
.split('')
.reverse()
.join('');
name = /([^\/]*)/.exec(name) as RegExpExecArray;
name = name[1]
.split('')
.reverse()
.join('');
mesh.name = name;
return mesh;
}
}
export default PCDLoader;