Skip to content

Commit

Permalink
core(PointCloud): add support for other data type in potree pointclouds
Browse files Browse the repository at this point in the history
  • Loading branch information
autra committed Jun 11, 2018
1 parent 2aa09d3 commit 23da8d3
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 35 deletions.
118 changes: 92 additions & 26 deletions src/Parser/PotreeBinParser.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,118 @@
import * as THREE from 'three';

// See the different constants holding ordinal, name, numElements, byteSize in PointAttributes.cpp in PotreeConverter
// elementByteSize is byteSize / numElements
const POINT_ATTTRIBUTES = {
POSITION_CARTESIAN: {
numElements: 3,
arrayType: Float32Array,
attributeName: 'position',
},
COLOR_PACKED: {
numElements: 4,
arrayType: Uint8Array,
attributeName: 'color',
normalized: true,
},
INTENSITY: {
numElements: 1,
numByte: 2,
// using Float32Array because Float16Array doesn't exist
arrayType: Float32Array,
attributeName: 'intensity',
normalized: true,
},
CLASSIFICATION: {
numElements: 1,
arrayType: Uint8Array,
attributeName: 'classification',
},
// Note: at the time of writing, PotreeConverter will only generate normals in Oct16 format
// see PotreeConverter.cpp:121
// we keep all the historical value to still supports old conversion
NORMAL_SPHEREMAPPED: {
numElements: 2,
arrayType: Uint8Array,
attributeName: 'sphereMappedNormal',
},
// see https://web.archive.org/web/20150303053317/http://lgdv.cs.fau.de/get/1602
NORMAL_OCT16: {
numElements: 2,
arrayType: Uint8Array,
attributeName: 'oct16Normal',
},
NORMAL: {
numElements: 3,
arrayType: Float32Array,
attributeName: 'normal',
},
};

export default {
/** @module PotreeBinParser */
/** Parse .bin PotreeConverter format and convert to a THREE.BufferGeometry
* @function parse
* @param {ArrayBuffer} buffer - the bin buffer.
* @param {Object} pointAttributes - the point attributes information contained in layer.metadata coming from cloud.js
* @return {Promise} - a promise that resolves with a THREE.BufferGeometry.
*
*/
parse: function parse(buffer) {
parse: function parse(buffer, pointAttributes) {
if (!buffer) {
throw new Error('No array buffer provided.');
}

const view = new DataView(buffer);
// Format: X1,Y1,Z1,R1,G1,B1,A1,[...],XN,YN,ZN,RN,GN,BN,AN
const numPoints = Math.floor(buffer.byteLength / 16);

const positions = new Float32Array(3 * numPoints);
const colors = new Uint8Array(4 * numPoints);
let pointByteSize = 0;
for (const potreeName of pointAttributes) {
const attr = POINT_ATTTRIBUTES[potreeName];
pointByteSize += attr.numElements * (attr.numByte || attr.arrayType.BYTES_PER_ELEMENT);
}
const numPoints = Math.floor(buffer.byteLength / pointByteSize);

const box = new THREE.Box3();
box.min.set(Infinity, Infinity, Infinity);
box.max.set(-Infinity, -Infinity, -Infinity);
const tmp = new THREE.Vector3();
const attrs = [];
// get the variable attributes
for (const potreeName of pointAttributes) {
const attr = POINT_ATTTRIBUTES[potreeName];
const numByte = attr.numByte || attr.arrayType.BYTES_PER_ELEMENT;
attrs.push({
potreeName,
numElements: attr.numElements,
attributeName: attr.attributeName,
normalized: attr.normalized,
array: new attr.arrayType(attr.numElements * numPoints),
numByte,
// Potree stores everything as int, and uses scale + offset
fnName: `getUint${numByte * 8}`,
});
}

let offset = 0;
for (let i = 0; i < numPoints; i++) {
positions[3 * i] = view.getUint32(offset + 0, true);
positions[3 * i + 1] = view.getUint32(offset + 4, true);
positions[3 * i + 2] = view.getUint32(offset + 8, true);
for (let pntIdx = 0; pntIdx < numPoints; pntIdx++) {
for (const attr of attrs) {
for (let elemIdx = 0; elemIdx < attr.numElements; elemIdx++) {
// chrome is known to perform badly when we call a method without respecting its arity
// also, not using ternary because I measured a 25% perf hit on firefox doing so...
let value;
if (attr.numByte === 1) {
value = view[attr.fnName](offset);
} else {
value = view[attr.fnName](offset, true);
}
attr.array[pntIdx * attr.numElements + elemIdx] = value;

tmp.fromArray(positions, 3 * i);
box.min.min(tmp);
box.max.max(tmp);

colors[4 * i] = view.getUint8(offset + 12);
colors[4 * i + 1] = view.getUint8(offset + 13);
colors[4 * i + 2] = view.getUint8(offset + 14);
colors[4 * i + 3] = 255;

offset += 16;
offset += attr.numByte;
}
}
}

const geometry = new THREE.BufferGeometry();
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.addAttribute('color', new THREE.BufferAttribute(colors, 4, true));
geometry.boundingBox = box;
for (const attr of attrs) {
geometry.addAttribute(attr.attributeName, new THREE.BufferAttribute(attr.array, attr.numElements, attr.normalized));
}

geometry.computeBoundingBox();

return Promise.resolve(geometry);
},
Expand Down
1 change: 1 addition & 0 deletions src/Process/PointCloudProcessing.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export default {
layer.material.opacity = layer.opacity;
layer.material.transparent = layer.opacity < 1;
layer.material.size = layer.pointSize;
layer.material.mode = layer.mode;
}

// lookup lowest common ancestor of changeSources
Expand Down
12 changes: 10 additions & 2 deletions src/Provider/PointCloudProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Fetcher from './Fetcher';
import PointCloudProcessing from '../Process/PointCloudProcessing';
import PotreeBinParser from '../Parser/PotreeBinParser';
import PotreeCinParser from '../Parser/PotreeCinParser';
import PointsMaterial from '../Renderer/PointsMaterial';
import PointsMaterial, { MODE } from '../Renderer/PointsMaterial';
import Picking from '../Core/Picking';

// Create an A(xis)A(ligned)B(ounding)B(ox) for the child `childIndex` of one aabb.
Expand Down Expand Up @@ -168,6 +168,7 @@ export default {
layer.type = 'geometry';
layer.material = layer.material || {};
layer.material = layer.material.isMaterial ? layer.material : new PointsMaterial(layer.material);
layer.mode = MODE.COLOR;

// default update methods
layer.preUpdate = PointCloudProcessing.preUpdate;
Expand All @@ -193,6 +194,13 @@ export default {
bbox = new THREE.Box3(
new THREE.Vector3(cloud.boundingBox.lx, cloud.boundingBox.ly, cloud.boundingBox.lz),
new THREE.Vector3(cloud.boundingBox.ux, cloud.boundingBox.uy, cloud.boundingBox.uz));

// do we have normal information
const normal = Array.isArray(layer.metadata.pointAttributes) &&
layer.metadata.pointAttributes.find(elem => elem.startsWith('NORMAL'));
if (normal) {
layer.material.defines[normal] = 1;
}
} else {
// Lopocs
layer.metadata.scale = 1;
Expand Down Expand Up @@ -245,7 +253,7 @@ export default {
// when we request .hrc files)
const url = `${node.baseurl}/r${node.name}.${layer.extension}?isleaf=${command.isLeaf ? 1 : 0}`;

return Fetcher.arrayBuffer(url, layer.fetchOptions).then(layer.parse).then((geometry) => {
return Fetcher.arrayBuffer(url, layer.fetchOptions).then(buffer => layer.parse(buffer, layer.metadata.pointAttributes)).then((geometry) => {
const points = new THREE.Points(geometry, layer.material.clone());
addPickingAttribute(points);
points.frustumCulled = false;
Expand Down
36 changes: 31 additions & 5 deletions src/Renderer/PointsMaterial.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import PointsVS from './Shader/PointsVS.glsl';
import PointsFS from './Shader/PointsFS.glsl';
import Capabilities from '../Core/System/Capabilities';

export const MODE = {
COLOR: 0,
PICKING: 1,
INTENSITY: 2,
CLASSIFICATION: 3,
NORMAL: 4,
};

class PointsMaterial extends RawShaderMaterial {
constructor(options = {}) {
super(options);
Expand All @@ -12,17 +20,24 @@ class PointsMaterial extends RawShaderMaterial {
this.size = options.size || 0;
this.scale = options.scale || 0.05 * 0.5 / Math.tan(1.0 / 2.0); // autosizing scale
this.overlayColor = options.overlayColor || new Vector4(0, 0, 0, 0);
this.mode = options.mode || MODE.COLOR;
this.oldMode = null;

for (const key in MODE) {
if (Object.prototype.hasOwnProperty.call(MODE, key)) {
this.defines[`MODE_${key}`] = MODE[key];
}
}

this.uniforms.size = new Uniform(this.size);
this.uniforms.mode = new Uniform(this.mode);
this.uniforms.pickingMode = new Uniform(false);
this.uniforms.opacity = new Uniform(this.opacity);
this.uniforms.overlayColor = new Uniform(this.overlayColor);

if (Capabilities.isLogDepthBufferSupported()) {
this.defines = {
USE_LOGDEPTHBUF: 1,
USE_LOGDEPTHBUF_EXT: 1,
};
this.defines.USE_LOGDEPTHBUF = 1;
this.defines.USE_LOGDEPTHBUF_EXT = 1;
}

if (__DEBUG__) {
Expand All @@ -33,13 +48,22 @@ class PointsMaterial extends RawShaderMaterial {

enablePicking(pickingMode) {
// we don't want pixels to blend over already drawn pixels
this.uniforms.pickingMode.value = pickingMode;
this.blending = pickingMode ? NoBlending : NormalBlending;
if (pickingMode) {
if (this.uniforms.mode.value !== MODE.PICKING) {
this.oldMode = this.uniforms.mode.value;
this.uniforms.mode.value = MODE.PICKING;
}
} else {
this.uniforms.mode.value = this.oldMode || this.uniforms.mode.value;
this.oldMode = null;
}
}

updateUniforms() {
// if size is null, switch to autosizing using the canvas height
this.uniforms.size.value = (this.size > 0) ? this.size : -this.scale * window.innerHeight;
this.uniforms.mode.value = this.mode || MODE.COLOR;
this.uniforms.opacity.value = this.opacity;
this.uniforms.overlayColor.value = this.overlayColor;
}
Expand All @@ -49,9 +73,11 @@ class PointsMaterial extends RawShaderMaterial {
this.opacity = source.opacity;
this.transparent = source.transparent;
this.size = source.size;
this.mode = source.mode;
this.scale = source.scale;
this.overlayColor.copy(source.overlayColor);
this.updateUniforms();
Object.assign(this.defines, source.defines);
return this;
}
}
Expand Down
57 changes: 55 additions & 2 deletions src/Renderer/Shader/PointsVS.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,71 @@ uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
uniform float size;

uniform bool pickingMode;
uniform int mode;
uniform float opacity;
uniform vec4 overlayColor;
attribute vec3 color;
attribute vec4 unique_id;
attribute float intensity;

#if defined(NORMAL_OCT16)
attribute vec2 oct16Normal;
#elif defined(NORMAL_SPHEREMAPPED)
attribute vec2 sphereMappedNormal;
#else
attribute vec3 normal;
#endif

varying vec4 vColor;

// see https://web.archive.org/web/20150303053317/http://lgdv.cs.fau.de/get/1602
// and implementation in PotreeConverter (BINPointReader.cpp) and potree (BinaryDecoderWorker.js)
#if defined(NORMAL_OCT16)
vec3 decodeOct16Normal(vec2 encodedNormal) {
vec2 nNorm = 2. * (encodedNormal / 255.) - 1.;
vec3 n;
n.z = 1. - abs(nNorm.x) - abs(nNorm.y);
if (n.z >= 0.) {
n.x = nNorm.x;
n.y = nNorm.y;
} else {
n.x = sign(nNorm.x) - sign(nNorm.x) * sign(nNorm.y) * nNorm.y;
n.y = sign(nNorm.y) - sign(nNorm.y) * sign(nNorm.x) * nNorm.x;
}
return normalize(n);
}
#elif defined(NORMAL_SPHEREMAPPED)
// see http://aras-p.info/texts/CompactNormalStorage.html method #4
// or see potree's implementation in BINPointReader.cpp
vec3 decodeSphereMappedNormal(vec2 encodedNormal) {
vec2 fenc = 2. * encodedNormal / 255. - 1.;
float f = dot(fenc,fenc);
float g = 2. * sqrt(1. - f);
vec3 n;
n.xy = fenc * g;
n.z = 1. - 2. * f;
return n;
}
#endif

void main() {
if (pickingMode) {
if (mode == MODE_PICKING) {
vColor = unique_id;
} else if (mode == MODE_INTENSITY) {
vColor = vec4(intensity, intensity, intensity, opacity);
} else if (mode == MODE_NORMAL) {
#if defined(NORMAL_OCT16)
vColor = vec4(abs(decodeOct16Normal(oct16Normal)), opacity);
#elif defined(NORMAL_SPHEREMAPPED)
vColor = vec4(abs(decodeSphereMappedNormal(sphereMappedNormal)), opacity);
#elif defined(NORMAL)
vColor = vec4(abs(normal), opacity);
#else
// default to color
vColor = vec4(mix(color, overlayColor.rgb, overlayColor.a), opacity);
#endif
} else {
// default to color mode
vColor = vec4(mix(color, overlayColor.rgb, overlayColor.a), opacity);
}

Expand Down
Loading

0 comments on commit 23da8d3

Please sign in to comment.