@pnext/three-loader
Version:
Potree loader for ThreeJS, converted and adapted to Typescript.
292 lines (237 loc) • 9.26 kB
text/typescript
/**
* Adapted from Potree.js http://potree.org
* Potree License: https://github.com/potree/potree/blob/1.5/LICENSE
*/
import {
IPointAttribute,
IPointAttributes,
PointAttributeName,
POINT_ATTRIBUTES,
} from '../point-attributes';
import { Version } from '../version';
import { CustomArrayView } from './custom-array-view';
// IE11 does not have Math.sign(), this has been adapted from CoreJS es6.math.sign.js for TypeScript
const mathSign =
Math.sign ||
function (x: number): number {
// tslint:disable-next-line:triple-equals
return (x = +x) == 0 || x != x ? x : x < 0 ? -1 : 1;
};
interface DecodedAttribute {
buffer: ArrayBuffer;
attribute: IPointAttribute;
}
interface Ctx {
attributeBuffers: Record<string, DecodedAttribute>;
currentOffset: number;
data: CustomArrayView;
mean: [number, number, number];
nodeOffset: [number, number, number];
numPoints: number;
pointAttributes: IPointAttributes;
scale: number;
tightBoxMax: [number, number, number];
tightBoxMin: [number, number, number];
transferables: ArrayBuffer[];
version: Version;
}
export function handleMessage(event: MessageEvent) {
const buffer = event.data.buffer;
const pointAttributes: IPointAttributes = event.data.pointAttributes;
const ctx: Ctx = {
attributeBuffers: {},
currentOffset: 0,
data: new CustomArrayView(buffer),
mean: [0, 0, 0],
nodeOffset: event.data.offset,
numPoints: event.data.buffer.byteLength / pointAttributes.byteSize,
pointAttributes,
scale: event.data.scale,
tightBoxMax: [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
tightBoxMin: [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
transferables: [],
version: new Version(event.data.version),
};
for (const pointAttribute of ctx.pointAttributes.attributes) {
decodeAndAddAttribute(pointAttribute, ctx);
ctx.currentOffset += pointAttribute.byteSize;
}
const indices = new ArrayBuffer(ctx.numPoints * 4);
const iIndices = new Uint32Array(indices);
for (let i = 0; i < ctx.numPoints; i++) {
iIndices[i] = i;
}
if (!ctx.attributeBuffers[PointAttributeName.CLASSIFICATION]) {
addEmptyClassificationBuffer(ctx);
}
const message = {
buffer: buffer,
mean: ctx.mean,
attributeBuffers: ctx.attributeBuffers,
tightBoundingBox: { min: ctx.tightBoxMin, max: ctx.tightBoxMax },
indices,
};
postMessage(message, ctx.transferables as any);
}
function addEmptyClassificationBuffer(ctx: Ctx): void {
const buffer = new ArrayBuffer(ctx.numPoints * 4);
const classifications = new Float32Array(buffer);
for (let i = 0; i < ctx.numPoints; i++) {
classifications[i] = 0;
}
ctx.attributeBuffers[PointAttributeName.CLASSIFICATION] = {
buffer,
attribute: POINT_ATTRIBUTES.CLASSIFICATION,
};
}
function decodeAndAddAttribute(attribute: IPointAttribute, ctx: Ctx): void {
const decodedAttribute = decodePointAttribute(attribute, ctx);
if (decodedAttribute === undefined) {
return;
}
ctx.attributeBuffers[decodedAttribute.attribute.name] = decodedAttribute;
ctx.transferables.push(decodedAttribute.buffer);
}
function decodePointAttribute(attribute: IPointAttribute, ctx: Ctx): DecodedAttribute | undefined {
switch (attribute.name) {
case PointAttributeName.POSITION_CARTESIAN:
return decodePositionCartesian(attribute, ctx);
case PointAttributeName.COLOR_PACKED:
return decodeColor(attribute, ctx);
case PointAttributeName.INTENSITY:
return decodeIntensity(attribute, ctx);
case PointAttributeName.CLASSIFICATION:
return decodeClassification(attribute, ctx);
case PointAttributeName.NORMAL_SPHEREMAPPED:
return decodeNormalSphereMapped(attribute, ctx);
case PointAttributeName.NORMAL_OCT16:
return decodeNormalOct16(attribute, ctx);
case PointAttributeName.NORMAL:
return decodeNormal(attribute, ctx);
default:
return undefined;
}
}
function decodePositionCartesian(attribute: IPointAttribute, ctx: Ctx): DecodedAttribute {
const buffer = new ArrayBuffer(ctx.numPoints * 4 * 3);
const positions = new Float32Array(buffer);
for (let i = 0; i < ctx.numPoints; i++) {
let x;
let y;
let z;
if (ctx.version.newerThan('1.3')) {
x = ctx.data.getUint32(ctx.currentOffset + i * ctx.pointAttributes.byteSize + 0) * ctx.scale;
y = ctx.data.getUint32(ctx.currentOffset + i * ctx.pointAttributes.byteSize + 4) * ctx.scale;
z = ctx.data.getUint32(ctx.currentOffset + i * ctx.pointAttributes.byteSize + 8) * ctx.scale;
} else {
x = ctx.data.getFloat32(i * ctx.pointAttributes.byteSize + 0) + ctx.nodeOffset[0];
y = ctx.data.getFloat32(i * ctx.pointAttributes.byteSize + 4) + ctx.nodeOffset[1];
z = ctx.data.getFloat32(i * ctx.pointAttributes.byteSize + 8) + ctx.nodeOffset[2];
}
positions[3 * i + 0] = x;
positions[3 * i + 1] = y;
positions[3 * i + 2] = z;
ctx.mean[0] += x / ctx.numPoints;
ctx.mean[1] += y / ctx.numPoints;
ctx.mean[2] += z / ctx.numPoints;
ctx.tightBoxMin[0] = Math.min(ctx.tightBoxMin[0], x);
ctx.tightBoxMin[1] = Math.min(ctx.tightBoxMin[1], y);
ctx.tightBoxMin[2] = Math.min(ctx.tightBoxMin[2], z);
ctx.tightBoxMax[0] = Math.max(ctx.tightBoxMax[0], x);
ctx.tightBoxMax[1] = Math.max(ctx.tightBoxMax[1], y);
ctx.tightBoxMax[2] = Math.max(ctx.tightBoxMax[2], z);
}
return { buffer, attribute };
}
function decodeColor(attribute: IPointAttribute, ctx: Ctx): DecodedAttribute {
const buffer = new ArrayBuffer(ctx.numPoints * 3);
const colors = new Uint8Array(buffer);
for (let i = 0; i < ctx.numPoints; i++) {
colors[3 * i + 0] = ctx.data.getUint8(ctx.currentOffset + i * ctx.pointAttributes.byteSize + 0);
colors[3 * i + 1] = ctx.data.getUint8(ctx.currentOffset + i * ctx.pointAttributes.byteSize + 1);
colors[3 * i + 2] = ctx.data.getUint8(ctx.currentOffset + i * ctx.pointAttributes.byteSize + 2);
}
return { buffer, attribute };
}
function decodeIntensity(attribute: IPointAttribute, ctx: Ctx): DecodedAttribute {
const buffer = new ArrayBuffer(ctx.numPoints * 4);
const intensities = new Float32Array(buffer);
for (let i = 0; i < ctx.numPoints; i++) {
intensities[i] = ctx.data.getUint16(ctx.currentOffset + i * ctx.pointAttributes.byteSize);
}
return { buffer, attribute };
}
function decodeClassification(attribute: IPointAttribute, ctx: Ctx): DecodedAttribute {
const buffer = new ArrayBuffer(ctx.numPoints);
const classifications = new Uint8Array(buffer);
for (let j = 0; j < ctx.numPoints; j++) {
classifications[j] = ctx.data.getUint8(ctx.currentOffset + j * ctx.pointAttributes.byteSize);
}
return { buffer, attribute };
}
function decodeNormalSphereMapped(attribute: IPointAttribute, ctx: Ctx): DecodedAttribute {
const buffer = new ArrayBuffer(ctx.numPoints * 4 * 3);
const normals = new Float32Array(buffer);
for (let j = 0; j < ctx.numPoints; j++) {
const bx = ctx.data.getUint8(ctx.currentOffset + j * ctx.pointAttributes.byteSize + 0);
const by = ctx.data.getUint8(ctx.currentOffset + j * ctx.pointAttributes.byteSize + 1);
const ex = bx / 255;
const ey = by / 255;
let nx = ex * 2 - 1;
let ny = ey * 2 - 1;
let nz = 1;
const nw = -1;
const 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;
}
return { buffer, attribute };
}
function decodeNormalOct16(attribute: IPointAttribute, ctx: Ctx): DecodedAttribute {
const buff = new ArrayBuffer(ctx.numPoints * 4 * 3);
const normals = new Float32Array(buff);
for (let j = 0; j < ctx.numPoints; j++) {
const bx = ctx.data.getUint8(ctx.currentOffset + j * ctx.pointAttributes.byteSize + 0);
const by = ctx.data.getUint8(ctx.currentOffset + j * ctx.pointAttributes.byteSize + 1);
const u = (bx / 255) * 2 - 1;
const 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 / mathSign(v) - 1) / mathSign(u);
y = -(u / mathSign(u) - 1) / mathSign(v);
}
const 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;
}
return { buffer: buff, attribute };
}
function decodeNormal(attribute: IPointAttribute, ctx: Ctx): DecodedAttribute {
const buffer = new ArrayBuffer(ctx.numPoints * 4 * 3);
const normals = new Float32Array(buffer);
for (let j = 0; j < ctx.numPoints; j++) {
const x = ctx.data.getFloat32(ctx.currentOffset + j * ctx.pointAttributes.byteSize + 0);
const y = ctx.data.getFloat32(ctx.currentOffset + j * ctx.pointAttributes.byteSize + 4);
const z = ctx.data.getFloat32(ctx.currentOffset + j * ctx.pointAttributes.byteSize + 8);
normals[3 * j + 0] = x;
normals[3 * j + 1] = y;
normals[3 * j + 2] = z;
}
return { buffer, attribute };
}