445 lines
13 KiB
JavaScript
445 lines
13 KiB
JavaScript
class Version{
|
|
|
|
constructor(version){
|
|
this.version = version;
|
|
let vmLength = (version.indexOf('.') === -1) ? version.length : version.indexOf('.');
|
|
this.versionMajor = parseInt(version.substr(0, vmLength));
|
|
this.versionMinor = parseInt(version.substr(vmLength + 1));
|
|
if (this.versionMinor.length === 0) {
|
|
this.versionMinor = 0;
|
|
}
|
|
}
|
|
|
|
newerThan(version){
|
|
let v = new Version(version);
|
|
|
|
if (this.versionMajor > v.versionMajor) {
|
|
return true;
|
|
} else if (this.versionMajor === v.versionMajor && this.versionMinor > v.versionMinor) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
equalOrHigher(version){
|
|
let v = new Version(version);
|
|
|
|
if (this.versionMajor > v.versionMajor) {
|
|
return true;
|
|
} else if (this.versionMajor === v.versionMajor && this.versionMinor >= v.versionMinor) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
upTo(version){
|
|
return !this.newerThan(version);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* 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];
|
|
}
|
|
|
|
}
|
|
PointAttribute.POSITION_CARTESIAN = new PointAttribute(
|
|
"POSITION_CARTESIAN", PointAttributeTypes.DATA_TYPE_FLOAT, 3);
|
|
|
|
PointAttribute.RGBA_PACKED = new PointAttribute(
|
|
"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(
|
|
"INTENSITY", PointAttributeTypes.DATA_TYPE_UINT16, 1);
|
|
|
|
PointAttribute.CLASSIFICATION = new PointAttribute(
|
|
"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(
|
|
"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) {
|
|
|
|
performance.mark("binary-decoder-start");
|
|
|
|
let buffer = event.data.buffer;
|
|
let pointAttributes = event.data.pointAttributes;
|
|
let numPoints = buffer.byteLength / pointAttributes.byteSize;
|
|
let view = new DataView(buffer);
|
|
let version = new Version(event.data.version);
|
|
let nodeOffset = event.data.offset;
|
|
let scale = event.data.scale;
|
|
let spacing = event.data.spacing;
|
|
let hasChildren = event.data.hasChildren;
|
|
let name = event.data.name;
|
|
|
|
let tightBoxMin = [ Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY ];
|
|
let tightBoxMax = [ Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY ];
|
|
let mean = [0, 0, 0];
|
|
|
|
|
|
let attributeBuffers = {};
|
|
let inOffset = 0;
|
|
for (let pointAttribute of pointAttributes.attributes) {
|
|
|
|
if (pointAttribute.name === "POSITION_CARTESIAN") {
|
|
let buff = new ArrayBuffer(numPoints * 4 * 3);
|
|
let positions = new Float32Array(buff);
|
|
|
|
for (let j = 0; j < numPoints; j++) {
|
|
let x, y, z;
|
|
|
|
if (version.newerThan('1.3')) {
|
|
x = (view.getUint32(inOffset + j * pointAttributes.byteSize + 0, true) * scale);
|
|
y = (view.getUint32(inOffset + j * pointAttributes.byteSize + 4, true) * scale);
|
|
z = (view.getUint32(inOffset + j * pointAttributes.byteSize + 8, true) * scale);
|
|
} else {
|
|
x = view.getFloat32(j * pointAttributes.byteSize + 0, true) + nodeOffset[0];
|
|
y = view.getFloat32(j * pointAttributes.byteSize + 4, true) + nodeOffset[1];
|
|
z = view.getFloat32(j * pointAttributes.byteSize + 8, true) + nodeOffset[2];
|
|
}
|
|
|
|
positions[3 * j + 0] = x;
|
|
positions[3 * j + 1] = y;
|
|
positions[3 * j + 2] = z;
|
|
|
|
mean[0] += x / numPoints;
|
|
mean[1] += y / numPoints;
|
|
mean[2] += z / numPoints;
|
|
|
|
tightBoxMin[0] = Math.min(tightBoxMin[0], x);
|
|
tightBoxMin[1] = Math.min(tightBoxMin[1], y);
|
|
tightBoxMin[2] = Math.min(tightBoxMin[2], z);
|
|
|
|
tightBoxMax[0] = Math.max(tightBoxMax[0], x);
|
|
tightBoxMax[1] = Math.max(tightBoxMax[1], y);
|
|
tightBoxMax[2] = Math.max(tightBoxMax[2], z);
|
|
}
|
|
|
|
attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute };
|
|
} else if (pointAttribute.name === "rgba") {
|
|
let buff = new ArrayBuffer(numPoints * 4);
|
|
let colors = new Uint8Array(buff);
|
|
|
|
for (let j = 0; j < numPoints; j++) {
|
|
colors[4 * j + 0] = view.getUint8(inOffset + j * pointAttributes.byteSize + 0);
|
|
colors[4 * j + 1] = view.getUint8(inOffset + j * pointAttributes.byteSize + 1);
|
|
colors[4 * j + 2] = view.getUint8(inOffset + j * pointAttributes.byteSize + 2);
|
|
}
|
|
|
|
attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute };
|
|
} else if (pointAttribute.name === "NORMAL_SPHEREMAPPED") {
|
|
let buff = new ArrayBuffer(numPoints * 4 * 3);
|
|
let normals = new Float32Array(buff);
|
|
|
|
for (let j = 0; j < numPoints; j++) {
|
|
let bx = view.getUint8(inOffset + j * pointAttributes.byteSize + 0);
|
|
let by = view.getUint8(inOffset + j * pointAttributes.byteSize + 1);
|
|
|
|
let ex = bx / 255;
|
|
let ey = by / 255;
|
|
|
|
let nx = ex * 2 - 1;
|
|
let ny = ey * 2 - 1;
|
|
let nz = 1;
|
|
let nw = -1;
|
|
|
|
let l = (nx * (-nx)) + (ny * (-ny)) + (nz * (-nw));
|
|
nz = l;
|
|
nx = nx * Math.sqrt(l);
|
|
ny = ny * Math.sqrt(l);
|
|
|
|
nx = nx * 2;
|
|
ny = ny * 2;
|
|
nz = nz * 2 - 1;
|
|
|
|
normals[3 * j + 0] = nx;
|
|
normals[3 * j + 1] = ny;
|
|
normals[3 * j + 2] = nz;
|
|
}
|
|
|
|
attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute };
|
|
} else if (pointAttribute.name === "NORMAL_OCT16") {
|
|
let buff = new ArrayBuffer(numPoints * 4 * 3);
|
|
let normals = new Float32Array(buff);
|
|
|
|
for (let j = 0; j < numPoints; j++) {
|
|
let bx = view.getUint8(inOffset + j * pointAttributes.byteSize + 0);
|
|
let by = view.getUint8(inOffset + j * pointAttributes.byteSize + 1);
|
|
|
|
let u = (bx / 255) * 2 - 1;
|
|
let v = (by / 255) * 2 - 1;
|
|
|
|
let z = 1 - Math.abs(u) - Math.abs(v);
|
|
|
|
let x = 0;
|
|
let y = 0;
|
|
if (z >= 0) {
|
|
x = u;
|
|
y = v;
|
|
} else {
|
|
x = -(v / Math.sign(v) - 1) / Math.sign(u);
|
|
y = -(u / Math.sign(u) - 1) / Math.sign(v);
|
|
}
|
|
|
|
let length = Math.sqrt(x * x + y * y + z * z);
|
|
x = x / length;
|
|
y = y / length;
|
|
z = z / length;
|
|
|
|
normals[3 * j + 0] = x;
|
|
normals[3 * j + 1] = y;
|
|
normals[3 * j + 2] = z;
|
|
}
|
|
|
|
attributeBuffers[pointAttribute.name] = { buffer: buff, attribute: pointAttribute };
|
|
} else if (pointAttribute.name === "NORMAL") {
|
|
let buff = new ArrayBuffer(numPoints * 4 * 3);
|
|
let normals = new Float32Array(buff);
|
|
|
|
for (let j = 0; j < numPoints; j++) {
|
|
let x = view.getFloat32(inOffset + j * pointAttributes.byteSize + 0, true);
|
|
let y = view.getFloat32(inOffset + j * pointAttributes.byteSize + 4, true);
|
|
let z = view.getFloat32(inOffset + j * pointAttributes.byteSize + 8, true);
|
|
|
|
normals[3 * j + 0] = x;
|
|
normals[3 * j + 1] = y;
|
|
normals[3 * j + 2] = z;
|
|
}
|
|
|
|
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 [min, max] = [Infinity, -Infinity];
|
|
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){
|
|
for(let j = 0; j < numPoints; j++){
|
|
let value = getter(inOffset + j * pointAttributes.byteSize, true);
|
|
|
|
if(!Number.isNaN(value)){
|
|
min = Math.min(min, value);
|
|
max = Math.max(max, value);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if(pointAttribute.initialRange != null){
|
|
offset = pointAttribute.initialRange[0];
|
|
scale = 1 / (pointAttribute.initialRange[1] - pointAttribute.initialRange[0]);
|
|
}else {
|
|
offset = min;
|
|
scale = 1 / (max - min);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
for(let j = 0; j < numPoints; j++){
|
|
let value = getter(inOffset + j * pointAttributes.byteSize, true);
|
|
|
|
if(!Number.isNaN(value)){
|
|
min = Math.min(min, value);
|
|
max = Math.max(max, value);
|
|
}
|
|
|
|
f32[j] = (value - offset) * scale;
|
|
preciseBuffer[j] = value;
|
|
}
|
|
|
|
pointAttribute.range = [min, max];
|
|
|
|
attributeBuffers[pointAttribute.name] = {
|
|
buffer: buff,
|
|
preciseBuffer: preciseBuffer,
|
|
attribute: pointAttribute,
|
|
offset: offset,
|
|
scale: scale,
|
|
};
|
|
}
|
|
|
|
inOffset += pointAttribute.byteSize;
|
|
}
|
|
|
|
{ // 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,
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
performance.mark("binary-decoder-end");
|
|
|
|
// { // print timings
|
|
// //performance.measure("spacing", "spacing-start", "spacing-end");
|
|
// performance.measure("binary-decoder", "binary-decoder-start", "binary-decoder-end");
|
|
// let measure = performance.getEntriesByType("measure")[0];
|
|
// let dpp = 1000 * measure.duration / numPoints;
|
|
// let pps = parseInt(numPoints / (measure.duration / 1000));
|
|
// let debugMessage = `${measure.duration.toFixed(3)} ms, ${numPoints} points, ${pps.toLocaleString()} points/sec`;
|
|
// console.log(debugMessage);
|
|
// }
|
|
|
|
performance.clearMarks();
|
|
performance.clearMeasures();
|
|
|
|
let message = {
|
|
buffer: buffer,
|
|
mean: mean,
|
|
attributeBuffers: attributeBuffers,
|
|
tightBoundingBox: { min: tightBoxMin, max: tightBoxMax },
|
|
};
|
|
|
|
let transferables = [];
|
|
for (let property in message.attributeBuffers) {
|
|
transferables.push(message.attributeBuffers[property].buffer);
|
|
}
|
|
transferables.push(buffer);
|
|
|
|
postMessage(message, transferables);
|
|
};
|