potree
Version:
WebGL point cloud viewer - WORK IN PROGRESS
1,940 lines (1,532 loc) • 519 kB
JavaScript
function Potree () {
}
Potree.version = {
major: 1,
minor: 5,
suffix: 'RC'
};
console.log('Potree ' + Potree.version.major + '.' + Potree.version.minor + Potree.version.suffix);
Potree.pointBudget = 1 * 1000 * 1000;
Potree.framenumber = 0;
// contains WebWorkers with base64 encoded code
// Potree.workers = {};
Potree.Shaders = {};
Potree.webgl = {
shaders: {},
vaos: {},
vbos: {}
};
Potree.scriptPath = null;
if (document.currentScript.src) {
Potree.scriptPath = new URL(document.currentScript.src + '/..').href;
if (Potree.scriptPath.slice(-1) === '/') {
Potree.scriptPath = Potree.scriptPath.slice(0, -1);
}
} else {
console.error('Potree was unable to find its script path using document.currentScript. Is Potree included with a script tag? Does your browser support this function?');
}
Potree.resourcePath = Potree.scriptPath + '/resources';
Potree.timerQueries = {};
Potree.timerQueriesEnabled = false;
Potree.startQuery = function (name, gl) {
if (!Potree.timerQueriesEnabled) {
return null;
}
if (Potree.timerQueries[name] === undefined) {
Potree.timerQueries[name] = [];
}
let ext = gl.getExtension('EXT_disjoint_timer_query');
let query = ext.createQueryEXT();
ext.beginQueryEXT(ext.TIME_ELAPSED_EXT, query);
Potree.timerQueries[name].push(query);
return query;
};
Potree.endQuery = function (query, gl) {
if (!Potree.timerQueriesEnabled) {
return;
}
let ext = gl.getExtension('EXT_disjoint_timer_query');
ext.endQueryEXT(ext.TIME_ELAPSED_EXT);
};
Potree.resolveQueries = function (gl) {
if (!Potree.timerQueriesEnabled) {
return;
}
let ext = gl.getExtension('EXT_disjoint_timer_query');
for (let name in Potree.timerQueries) {
let queries = Potree.timerQueries[name];
if (queries.length > 0) {
let query = queries[0];
let available = ext.getQueryObjectEXT(query, ext.QUERY_RESULT_AVAILABLE_EXT);
let disjoint = viewer.renderer.getContext().getParameter(ext.GPU_DISJOINT_EXT);
if (available && !disjoint) {
// See how much time the rendering of the object took in nanoseconds.
let timeElapsed = ext.getQueryObjectEXT(query, ext.QUERY_RESULT_EXT);
let miliseconds = timeElapsed / (1000 * 1000);
console.log(name + ': ' + miliseconds + 'ms');
queries.shift();
}
}
if (queries.length === 0) {
delete Potree.timerQueries[name];
}
}
};
Potree.MOUSE = {
LEFT: 0b0001,
RIGHT: 0b0010,
MIDDLE: 0b0100
};
Potree.Points = class Points {
constructor () {
this.boundingBox = new THREE.Box3();
this.numPoints = 0;
this.data = {};
}
add (points) {
let currentSize = this.numPoints;
let additionalSize = points.numPoints;
let newSize = currentSize + additionalSize;
let thisAttributes = Object.keys(this.data);
let otherAttributes = Object.keys(points.data);
let attributes = new Set([...thisAttributes, ...otherAttributes]);
for (let attribute of attributes) {
if (thisAttributes.includes(attribute) && otherAttributes.includes(attribute)) {
// attribute in both, merge
let Type = this.data[attribute].constructor;
let merged = new Type(this.data[attribute].length + points.data[attribute].length);
merged.set(this.data[attribute], 0);
merged.set(points.data[attribute], this.data[attribute].length);
this.data[attribute] = merged;
} else if (thisAttributes.includes(attribute) && !otherAttributes.includes(attribute)) {
// attribute only in this; take over this and expand to new size
let elementsPerPoint = this.data[attribute].length / this.numPoints;
let Type = this.data[attribute].constructor;
let expanded = new Type(elementsPerPoint * newSize);
expanded.set(this.data[attribute], 0);
this.data[attribute] = expanded;
} else if (!thisAttributes.includes(attribute) && otherAttributes.includes(attribute)) {
// attribute only in points to be added; take over new points and expand to new size
let elementsPerPoint = points.data[attribute].length / points.numPoints;
let Type = points.data[attribute].constructor;
let expanded = new Type(elementsPerPoint * newSize);
expanded.set(points.data[attribute], elementsPerPoint * currentSize);
this.data[attribute] = expanded;
}
}
this.numPoints = newSize;
this.boundingBox.union(points.boundingBox);
}
};
/* eslint-disable standard/no-callback-literal */
Potree.loadPointCloud = function (path, name, callback) {
let loaded = function (pointcloud) {
pointcloud.name = name;
callback({type: 'pointcloud_loaded', pointcloud: pointcloud});
};
// load pointcloud
if (!path) {
// TODO: callback? comment? Hello? Bueller? Anyone?
} else if (path.indexOf('greyhound://') === 0) {
// We check if the path string starts with 'greyhound:', if so we assume it's a greyhound server URL.
Potree.GreyhoundLoader.load(path, function (geometry) {
if (!geometry) {
callback({type: 'loading_failed'});
} else {
let pointcloud = new Potree.PointCloudOctree(geometry);
loaded(pointcloud);
}
});
} else if (path.indexOf('cloud.js') > 0) {
Potree.POCLoader.load(path, function (geometry) {
if (!geometry) {
callback({type: 'loading_failed'});
} else {
let pointcloud = new Potree.PointCloudOctree(geometry);
loaded(pointcloud);
}
});
} else if (path.indexOf('.vpc') > 0) {
Potree.PointCloudArena4DGeometry.load(path, function (geometry) {
if (!geometry) {
callback({type: 'loading_failed'});
} else {
let pointcloud = new Potree.PointCloudArena4D(geometry);
loaded(pointcloud);
}
});
} else {
callback({'type': 'loading_failed'});
}
};
/* eslint-enable standard/no-callback-literal */
Potree.updatePointClouds = function (pointclouds, camera, renderer) {
if (!Potree.lru) {
Potree.lru = new LRU();
}
for (let i = 0; i < pointclouds.length; i++) {
let pointcloud = pointclouds[i];
for (let j = 0; j < pointcloud.profileRequests.length; j++) {
pointcloud.profileRequests[j].update();
}
}
let result = Potree.updateVisibility(pointclouds, camera, renderer);
for (let i = 0; i < pointclouds.length; i++) {
let pointcloud = pointclouds[i];
pointcloud.updateMaterial(pointcloud.material, pointcloud.visibleNodes, camera, renderer);
pointcloud.updateVisibleBounds();
}
Potree.getLRU().freeMemory();
return result;
};
Potree.getLRU = function () {
if (!Potree.lru) {
Potree.lru = new LRU();
}
return Potree.lru;
};
function updateVisibilityStructures (pointclouds, camera, renderer) {
let frustums = [];
let camObjPositions = [];
let priorityQueue = new BinaryHeap(function (x) { return 1 / x.weight; });
for (let i = 0; i < pointclouds.length; i++) {
let pointcloud = pointclouds[i];
if (!pointcloud.initialized()) {
continue;
}
pointcloud.numVisibleNodes = 0;
pointcloud.numVisiblePoints = 0;
pointcloud.deepestVisibleLevel = 0;
pointcloud.visibleNodes = [];
pointcloud.visibleGeometry = [];
// frustum in object space
camera.updateMatrixWorld();
let frustum = new THREE.Frustum();
let viewI = camera.matrixWorldInverse;
let world = pointcloud.matrixWorld;
let proj = camera.projectionMatrix;
let fm = new THREE.Matrix4().multiply(proj).multiply(viewI).multiply(world);
frustum.setFromMatrix(fm);
frustums.push(frustum);
// camera position in object space
let view = camera.matrixWorld;
let worldI = new THREE.Matrix4().getInverse(world);
let camMatrixObject = new THREE.Matrix4().multiply(worldI).multiply(view);
let camObjPos = new THREE.Vector3().setFromMatrixPosition(camMatrixObject);
camObjPositions.push(camObjPos);
if (pointcloud.visible && pointcloud.root !== null) {
priorityQueue.push({pointcloud: i, node: pointcloud.root, weight: Number.MAX_VALUE});
}
// hide all previously visible nodes
// if(pointcloud.root instanceof Potree.PointCloudOctreeNode){
// pointcloud.hideDescendants(pointcloud.root.sceneNode);
// }
if (pointcloud.root.isTreeNode()) {
pointcloud.hideDescendants(pointcloud.root.sceneNode);
}
for (let j = 0; j < pointcloud.boundingBoxNodes.length; j++) {
pointcloud.boundingBoxNodes[j].visible = false;
}
}
return {
'frustums': frustums,
'camObjPositions': camObjPositions,
'priorityQueue': priorityQueue
};
}
Potree.getDEMWorkerInstance = function () {
if (!Potree.DEMWorkerInstance) {
let workerPath = Potree.scriptPath + '/workers/DEMWorker.js';
Potree.DEMWorkerInstance = Potree.workerPool.getWorker(workerPath);
}
return Potree.DEMWorkerInstance;
};
/*
function createDEMMesh (dem) {
let box = dem.boundingBox;
let steps = 256;
let triangles = [];
for (let i = 0; i < steps; i++) {
for (let j = 0; j < steps; j++) {
let u0 = i / steps;
let u1 = (i + 1) / steps;
let v0 = j / steps;
let v1 = (j + 1) / steps;
// let x0 = box.min.x + u0 * box.getSize().x;
// let x1 = box.min.x + u1 * box.getSize().x;
// let y0 = box.min.y + v0 * box.getSize().y;
// let y1 = box.min.y + v1 * box.getSize().y;
//
// let h00 = dem.height(new THREE.Vector3(x0, y0, 0));
// let h10 = dem.height(new THREE.Vector3(x1, y0, 0));
// let h01 = dem.height(new THREE.Vector3(x0, y1, 0));
// let h11 = dem.height(new THREE.Vector3(x1, y1, 0));
let x0 = u0 * box.getSize().x;
let x1 = u1 * box.getSize().x;
let y0 = v0 * box.getSize().y;
let y1 = v1 * box.getSize().y;
// let h00 = demNode.data[(i+0) + tileSize * (j+0)];
// let h10 = demNode.data[(i+1) + tileSize * (j+0)];
// let h01 = demNode.data[(i+0) + tileSize * (j+1)];
// let h11 = demNode.data[(i+1) + tileSize * (j+1)];
let h00 = dem.height(new THREE.Vector3(box.min.x + x0, box.min.y + y0));
let h10 = dem.height(new THREE.Vector3(box.min.x + x1, box.min.y + y0));
let h01 = dem.height(new THREE.Vector3(box.min.x + x0, box.min.y + y1));
let h11 = dem.height(new THREE.Vector3(box.min.x + x1, box.min.y + y1));
if (![h00, h10, h01, h11].every(n => isFinite(n))) {
continue;
}
triangles.push(x0, y0, h00);
triangles.push(x0, y1, h01);
triangles.push(x1, y0, h10);
triangles.push(x0, y1, h01);
triangles.push(x1, y1, h11);
triangles.push(x1, y0, h10);
}
}
let geometry = new THREE.BufferGeometry();
let positions = new Float32Array(triangles);
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.computeBoundingSphere();
geometry.computeVertexNormals();
let material = new THREE.MeshNormalMaterial({side: THREE.DoubleSide});
let mesh = new THREE.Mesh(geometry, material);
mesh.position.copy(box.min);
// mesh.position.copy(pointcloud.position);
viewer.scene.scene.add(mesh);
}
*/
/*
function createDEMMeshNode (dem, demNode) {
let box = demNode.box;
let tileSize = dem.tileSize * 1;
let triangles = [];
for (let i = 0; i < tileSize; i++) {
// for(let j = 0; j < 1; j++){
for (let j = 0; j < tileSize; j++) {
let u0 = i / tileSize;
let u1 = (i + 1) / tileSize;
let v0 = j / tileSize;
let v1 = (j + 1) / tileSize;
let x0 = u0 * box.getSize().x;
let x1 = u1 * box.getSize().x;
let y0 = v0 * box.getSize().y;
let y1 = v1 * box.getSize().y;
// let h00 = demNode.data[(i+0) + tileSize * (j+0)];
// let h10 = demNode.data[(i+1) + tileSize * (j+0)];
// let h01 = demNode.data[(i+0) + tileSize * (j+1)];
// let h11 = demNode.data[(i+1) + tileSize * (j+1)];
let h00 = demNode.height(new THREE.Vector3(box.min.x + x0, box.min.y + y0));
let h10 = demNode.height(new THREE.Vector3(box.min.x + x1, box.min.y + y0));
let h01 = demNode.height(new THREE.Vector3(box.min.x + x0, box.min.y + y1));
let h11 = demNode.height(new THREE.Vector3(box.min.x + x1, box.min.y + y1));
if (![h00, h10, h01, h11].every(n => isFinite(n))) {
continue;
}
triangles.push(x0, y0, h00);
triangles.push(x0, y1, h01);
triangles.push(x1, y0, h10);
triangles.push(x0, y1, h01);
triangles.push(x1, y1, h11);
triangles.push(x1, y0, h10);
}
}
let geometry = new THREE.BufferGeometry();
let positions = new Float32Array(triangles);
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.computeBoundingSphere();
geometry.computeVertexNormals();
let material = new THREE.MeshNormalMaterial({side: THREE.DoubleSide});
let mesh = new THREE.Mesh(geometry, material);
mesh.position.copy(box.min);
// mesh.position.copy(pointcloud.position);
viewer.scene.scene.add(mesh);
{ // DEBUG code
// let data = demNode.data;
let steps = 64;
let data = new Float32Array(steps * steps);
let imgData = new Uint8Array(data.length * 4);
let box = demNode.box;
let boxSize = box.getSize();
for (let i = 0; i < steps; i++) {
for (let j = 0; j < steps; j++) {
let [u, v] = [i / (steps - 1), j / (steps - 1)];
let pos = new THREE.Vector3(
u * boxSize.x + box.min.x,
v * boxSize.y + box.min.y,
0
);
let height = demNode.height(pos);
let index = i + steps * j;
data[index] = height;
// let index = i + steps * j;
// imgData[4*index + 0] = 255 * (height - min) / (max - min);
// imgData[4*index + 1] = 100;
// imgData[4*index + 2] = 0;
// imgData[4*index + 3] = 255;
}
}
let [min, max] = [Infinity, -Infinity];
for (let height of data) {
if (!isFinite(height)) {
continue;
}
min = Math.min(min, height);
max = Math.max(max, height);
}
for (let i = 0; i < data.length; i++) {
imgData[4 * i + 0] = 255 * (data[i] - min) / (max - min);
imgData[4 * i + 1] = 100;
imgData[4 * i + 2] = 0;
imgData[4 * i + 3] = 255;
}
let img = Potree.utils.pixelsArrayToImage(imgData, steps, steps);
let screenshot = img.src;
if (!this.debugDIV) {
this.debugDIV = $(`
<div id="pickDebug"
style="position: absolute;
right: 400px; width: 300px;
bottom: 44px; width: 300px;
z-index: 1000;
"></div>`);
$(document.body).append(this.debugDIV);
}
this.debugDIV.empty();
this.debugDIV.append($(`<img src="${screenshot}"
style="transform: scaleY(-1); width: 256px; height: 256px;"/>`));
}
}
*/
Potree.updateVisibility = function (pointclouds, camera, renderer) {
// let numVisibleNodes = 0;
let numVisiblePoints = 0;
let visibleNodes = [];
let visibleGeometry = [];
let unloadedGeometry = [];
let lowestSpacing = Infinity;
// calculate object space frustum and cam pos and setup priority queue
let s = updateVisibilityStructures(pointclouds, camera, renderer);
let frustums = s.frustums;
let camObjPositions = s.camObjPositions;
let priorityQueue = s.priorityQueue;
let loadedToGPUThisFrame = 0;
while (priorityQueue.size() > 0) {
let element = priorityQueue.pop();
let node = element.node;
let parent = element.parent;
let pointcloud = pointclouds[element.pointcloud];
// { // restrict to certain nodes for debugging
// let allowedNodes = ["r", "r0", "r4"];
// if(!allowedNodes.includes(node.name)){
// continue;
// }
// }
let box = node.getBoundingBox();
let frustum = frustums[element.pointcloud];
let camObjPos = camObjPositions[element.pointcloud];
let insideFrustum = frustum.intersectsBox(box);
let maxLevel = pointcloud.maxLevel || Infinity;
let level = node.getLevel();
let visible = insideFrustum;
visible = visible && !(numVisiblePoints + node.getNumPoints() > Potree.pointBudget);
visible = visible && level < maxLevel;
if (pointcloud.material.numClipBoxes > 0 && visible && pointcloud.material.clipMode === Potree.ClipMode.CLIP_OUTSIDE) {
let box2 = box.clone();
pointcloud.updateMatrixWorld(true);
box2.applyMatrix4(pointcloud.matrixWorld);
let intersectsClipBoxes = false;
for (let clipBox of pointcloud.material.clipBoxes) {
let clipMatrixWorld = clipBox.matrix;
let clipBoxWorld = new THREE.Box3(
new THREE.Vector3(-0.5, -0.5, -0.5),
new THREE.Vector3(0.5, 0.5, 0.5)
).applyMatrix4(clipMatrixWorld);
if (box2.intersectsBox(clipBoxWorld)) {
intersectsClipBoxes = true;
break;
}
}
visible = visible && intersectsClipBoxes;
}
// visible = ["r", "r0", "r06", "r060"].includes(node.name);
// visible = ["r"].includes(node.name);
if (node.spacing) {
lowestSpacing = Math.min(lowestSpacing, node.spacing);
} else if (node.geometryNode && node.geometryNode.spacing) {
lowestSpacing = Math.min(lowestSpacing, node.geometryNode.spacing);
}
if (numVisiblePoints + node.getNumPoints() > Potree.pointBudget) {
break;
}
if (!visible) {
continue;
}
// TODO: not used, same as the declaration?
// numVisibleNodes++;
numVisiblePoints += node.getNumPoints();
pointcloud.numVisibleNodes++;
pointcloud.numVisiblePoints += node.getNumPoints();
if (node.isGeometryNode() && (!parent || parent.isTreeNode())) {
if (node.isLoaded() && loadedToGPUThisFrame < 2) {
node = pointcloud.toTreeNode(node, parent);
loadedToGPUThisFrame++;
} else {
unloadedGeometry.push(node);
visibleGeometry.push(node);
}
}
if (node.isTreeNode()) {
Potree.getLRU().touch(node.geometryNode);
node.sceneNode.visible = true;
node.sceneNode.material = pointcloud.material;
visibleNodes.push(node);
pointcloud.visibleNodes.push(node);
node.sceneNode.updateMatrix();
node.sceneNode.matrixWorld.multiplyMatrices(pointcloud.matrixWorld, node.sceneNode.matrix);
if (pointcloud.showBoundingBox && !node.boundingBoxNode && node.getBoundingBox) {
let boxHelper = new Potree.Box3Helper(node.getBoundingBox());
// let boxHelper = new THREE.BoxHelper(node.sceneNode);
pointcloud.add(boxHelper);
pointcloud.boundingBoxNodes.push(boxHelper);
node.boundingBoxNode = boxHelper;
node.boundingBoxNode.matrixWorld.copy(pointcloud.matrixWorld);
} else if (pointcloud.showBoundingBox) {
node.boundingBoxNode.visible = true;
node.boundingBoxNode.matrixWorld.copy(pointcloud.matrixWorld);
} else if (!pointcloud.showBoundingBox && node.boundingBoxNode) {
node.boundingBoxNode.visible = false;
}
}
// add child nodes to priorityQueue
let children = node.getChildren();
for (let i = 0; i < children.length; i++) {
let child = children[i];
let sphere = child.getBoundingSphere();
let distance = sphere.center.distanceTo(camObjPos);
let radius = sphere.radius;
let fov = (camera.fov * Math.PI) / 180;
let slope = Math.tan(fov / 2);
let projFactor = (0.5 * renderer.domElement.clientHeight) / (slope * distance);
let screenPixelRadius = radius * projFactor;
if (screenPixelRadius < pointcloud.minimumNodePixelSize) {
continue;
}
let weight = screenPixelRadius;
if (distance - radius < 0) {
weight = Number.MAX_VALUE;
}
priorityQueue.push({pointcloud: element.pointcloud, node: child, parent: node, weight: weight});
}
}// end priority queue loop
{ // update DEM
let maxDEMLevel = 4;
let candidates = pointclouds
.filter(p => (p.generateDEM && p.dem instanceof Potree.DEM));
for (let pointcloud of candidates) {
let updatingNodes = pointcloud.visibleNodes.filter(n => n.getLevel() <= maxDEMLevel);
pointcloud.dem.update(updatingNodes);
}
}
for (let i = 0; i < Math.min(5, unloadedGeometry.length); i++) {
unloadedGeometry[i].load();
}
// for(let node of visibleNodes){
// let allowedNodes = ["r", "r0", "r4"];
// node.sceneNode.visible = allowedNodes.includes(node.geometryNode.name);
//
// if(node.boundingBoxNode){
// node.boundingBoxNode.visible = node.boundingBoxNode.visible && node.sceneNode.visible;
// }
// }
// Potree.updateDEMs(renderer, visibleNodes);
return {
visibleNodes: visibleNodes,
numVisiblePoints: numVisiblePoints,
lowestSpacing: lowestSpacing
};
};
/*
//
// WAY TOO SLOW WITH SYNCHRONOUS READ PIXEL
//
Potree.DEMRenderer = class{
constructor(renderer){
this.renderer = renderer;
this.tileWidth = 64;
this.tileHeight = 64;
//this.target = new THREE.WebGLRenderTarget( 64, 64, {
// minFilter: THREE.NearestFilter,
// magFilter: THREE.NearestFilter,
// format: THREE.RGBAFormat,
// type: THREE.FloatType
//} );
//this.target.depthTexture = new THREE.DepthTexture();
//this.target.depthTexture.type = THREE.UnsignedIntType;
this.targetElevation = new THREE.WebGLRenderTarget( this.tileWidth, this.tileHeight, {
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBAFormat,
//type: THREE.FloatType
});
this.targetMedian = new THREE.WebGLRenderTarget( this.tileWidth, this.tileHeight, {
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBAFormat,
//type: THREE.FloatType
});
this.vsElevation = `
precision mediump float;
precision mediump int;
attribute vec3 position;
uniform mat4 modelMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
varying float vElevation;
void main(){
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_Position = projectionMatrix * mvPosition;
gl_PointSize = 1.0;
vElevation = position.z;
}
`;
this.fsElevation = `
precision mediump float;
precision mediump int;
varying float vElevation;
void main(){
gl_FragColor = vec4(vElevation, 0.0, 0.0, 1.0);
}
`;
this.vsMedian = `
precision mediump float;
precision mediump int;
attribute vec3 position;
attribute vec2 uv;
uniform mat4 modelMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
varying vec2 vUV;
void main() {
vUV = uv;
vec4 mvPosition = modelViewMatrix * vec4(position,1.0);
gl_Position = projectionMatrix * mvPosition;
}
`;
this.fsMedian = `
precision mediump float;
precision mediump int;
uniform float uWidth;
uniform float uHeight;
uniform sampler2D uTexture;
varying vec2 vUV;
void main(){
vec2 uv = gl_FragCoord.xy / vec2(uWidth, uHeight);
vec4 color = texture2D(uTexture, uv);
gl_FragColor = color;
if(color.a == 0.0){
vec4 sum;
float minVal = 1.0 / 0.0;
float sumA = 0.0;
for(int i = -1; i <= 1; i++){
for(int j = -1; j <= 1; j++){
vec2 n = gl_FragCoord.xy + vec2(i, j);
vec2 uv = n / vec2(uWidth, uHeight);
vec4 c = texture2D(uTexture, uv);
if(c.a == 1.0){
minVal = min(c.r, minVal);
}
sumA += c.a;
}
}
if(sumA > 0.0){
gl_FragColor = vec4(minVal, 0.0, 0.0, 1.0);
}else{
discard;
}
}else{
//gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
gl_FragColor = vec4(color.rgb, 1.0);
}
}
`;
this.elevationMaterial = new THREE.RawShaderMaterial( {
vertexShader: this.vsElevation,
fragmentShader: this.fsElevation,
} );
this.medianFilterMaterial = new THREE.RawShaderMaterial( {
uniforms: {
uWidth: {value: 1.0},
uHeight: {value: 1.0},
uTexture: {type: "t", value: this.targetElevation.texture}
},
vertexShader: this.vsMedian,
fragmentShader: this.fsMedian,
});
this.camera = new THREE.OrthographicCamera(0, 1, 1, 0, 0, 1);
}
render(pointcloud, node){
if(!node.geometryNode){
return;
}
Potree.timerQueriesEnabled = true;
let start = new Date().getTime();
let queryAll = Potree.startQuery("All", this.renderer.getContext());
this.renderer.setClearColor(0x0000FF, 0);
this.renderer.clearTarget( this.target, true, true, true );
this.renderer.clearTarget(this.targetElevation, true, true, false );
this.renderer.clearTarget(this.targetMedian, true, true, false );
let box = node.geometryNode.boundingBox;
this.camera.up.set(0, 0, 1);
//this.camera.rotation.x = Math.PI / 2;
this.camera.left = box.min.x;
this.camera.right = box.max.x;
this.camera.top = box.max.y;
this.camera.bottom = box.min.y;
this.camera.near = -1000;
this.camera.far = 1000;
this.camera.updateProjectionMatrix();
let scene = new THREE.Scene();
//let material = new THREE.PointsMaterial({color: 0x00ff00, size: 0.0001});
let material = this.elevationMaterial;
let points = new THREE.Points(node.geometryNode.geometry, material);
scene.add(points);
this.renderer.render(points, this.camera, this.targetElevation);
this.medianFilterMaterial.uniforms.uWidth.value = this.targetMedian.width;
this.medianFilterMaterial.uniforms.uHeight.value = this.targetMedian.height;
this.medianFilterMaterial.uniforms.uTexture.value = this.targetElevation.texture;
Potree.utils.screenPass.render(this.renderer, this.medianFilterMaterial, this.targetMedian);
Potree.endQuery(queryAll, this.renderer.getContext());
Potree.resolveQueries(this.renderer.getContext());
Potree.timerQueriesEnabled = false;
setTimeout( () => {
let start = new Date().getTime();
let pixelCount = this.tileWidth * this.tileHeight;
let buffer = new Uint8Array(4 * pixelCount);
this.renderer.readRenderTargetPixels(this.targetMedian,
0, 0, this.tileWidth, this.tileHeight,
buffer);
let end = new Date().getTime();
let duration = end - start;
console.log(`read duration: ${duration}ms`);
}, 3000);
let end = new Date().getTime();
let duration = end - start;
console.log(`duration: ${duration}ms`);
//{ // open window with image
//
// let pixelCount = this.tileWidth * this.tileHeight;
// let buffer = new Float32Array(4 * pixelCount);
// this.renderer.readRenderTargetPixels(this.targetMedian,
// 0, 0, this.tileWidth, this.tileHeight,
// buffer);
//
// let uiBuffer = new Uint8Array(4 * pixelCount);
// for(let i = 0; i < pixelCount; i++){
// uiBuffer[i] = buffer[i] / 1.0;
// }
//
// let img = Potree.utils.pixelsArrayToImage(uiBuffer, this.tileWidth, this.tileHeight);
// let screenshot = img.src;
//
// if(!this.debugDIV){
// this.debugDIV = $(`
// <div id="pickDebug"
// style="position: absolute;
// right: 400px; width: 300px;
// bottom: 44px; width: 300px;
// z-index: 1000;
// "></div>`);
// $(document.body).append(this.debugDIV);
// }
//
// this.debugDIV.empty();
// this.debugDIV.append($(`<img src="${screenshot}"
// style="transform: scaleY(-1);"/>`));
// //$(this.debugWindow.document).append($(`<img src="${screenshot}"/>`));
// //this.debugWindow.document.write('<img src="'+screenshot+'"/>');
//}
}
};
*/
module.exports = Potree;
//
// index is in order xyzxyzxyz
//
Potree.DEMNode = class DEMNode {
constructor (name, box, tileSize) {
this.name = name;
this.box = box;
this.tileSize = tileSize;
this.level = this.name.length - 1;
this.data = new Float32Array(tileSize * tileSize);
this.data.fill(-Infinity);
this.children = [];
this.mipMap = [this.data];
this.mipMapNeedsUpdate = true;
}
createMipMap () {
this.mipMap = [this.data];
let sourceSize = this.tileSize;
let mipSize = parseInt(sourceSize / 2);
let mipSource = this.data;
while (mipSize > 1) {
let mipData = new Float32Array(mipSize * mipSize);
for (let i = 0; i < mipSize; i++) {
for (let j = 0; j < mipSize; j++) {
let h00 = mipSource[2 * i + 0 + 2 * j * sourceSize];
let h01 = mipSource[2 * i + 0 + 2 * j * sourceSize + sourceSize];
let h10 = mipSource[2 * i + 1 + 2 * j * sourceSize];
let h11 = mipSource[2 * i + 1 + 2 * j * sourceSize + sourceSize];
let [height, weight] = [0, 0];
if (isFinite(h00)) { height += h00; weight += 1; };
if (isFinite(h01)) { height += h01; weight += 1; };
if (isFinite(h10)) { height += h10; weight += 1; };
if (isFinite(h11)) { height += h11; weight += 1; };
height = height / weight;
// let hs = [h00, h01, h10, h11].filter(h => isFinite(h));
// let height = hs.reduce( (a, v, i) => a + v, 0) / hs.length;
mipData[i + j * mipSize] = height;
}
}
this.mipMap.push(mipData);
mipSource = mipData;
sourceSize = mipSize;
mipSize = parseInt(mipSize / 2);
}
this.mipMapNeedsUpdate = false;
}
uv (position) {
let boxSize = this.box.getSize();
let u = (position.x - this.box.min.x) / boxSize.x;
let v = (position.y - this.box.min.y) / boxSize.y;
return [u, v];
}
heightAtMipMapLevel (position, mipMapLevel) {
let uv = this.uv(position);
let tileSize = parseInt(this.tileSize / parseInt(2 ** mipMapLevel));
let data = this.mipMap[mipMapLevel];
let i = Math.min(uv[0] * tileSize, tileSize - 1);
let j = Math.min(uv[1] * tileSize, tileSize - 1);
let a = i % 1;
let b = j % 1;
let [i0, i1] = [Math.floor(i), Math.ceil(i)];
let [j0, j1] = [Math.floor(j), Math.ceil(j)];
let h00 = data[i0 + tileSize * j0];
let h01 = data[i0 + tileSize * j1];
let h10 = data[i1 + tileSize * j0];
let h11 = data[i1 + tileSize * j1];
let wh00 = isFinite(h00) ? (1 - a) * (1 - b) : 0;
let wh01 = isFinite(h01) ? (1 - a) * b : 0;
let wh10 = isFinite(h10) ? a * (1 - b) : 0;
let wh11 = isFinite(h11) ? a * b : 0;
let wsum = wh00 + wh01 + wh10 + wh11;
wh00 = wh00 / wsum;
wh01 = wh01 / wsum;
wh10 = wh10 / wsum;
wh11 = wh11 / wsum;
if (wsum === 0) {
return null;
}
let h = 0;
if (isFinite(h00)) h += h00 * wh00;
if (isFinite(h01)) h += h01 * wh01;
if (isFinite(h10)) h += h10 * wh10;
if (isFinite(h11)) h += h11 * wh11;
return h;
}
height (position) {
let h = null;
for (let i = 0; i < this.mipMap.length; i++) {
h = this.heightAtMipMapLevel(position, i);
if (h !== null) {
return h;
}
}
return h;
}
traverse (handler, level = 0) {
handler(this, level);
for (let child of this.children.filter(c => c !== undefined)) {
child.traverse(handler, level + 1);
}
}
};
Potree.DEM = class DEM {
constructor (pointcloud) {
this.pointcloud = pointcloud;
this.matrix = null;
this.boundingBox = null;
this.tileSize = 64;
this.root = null;
this.version = 0;
}
// expands the tree to all nodes that intersect <box> at <level>
// returns the intersecting nodes at <level>
expandAndFindByBox (box, level) {
if (level === 0) {
return [this.root];
}
let result = [];
let stack = [this.root];
while (stack.length > 0) {
let node = stack.pop();
let nodeBoxSize = node.box.getSize();
// check which children intersect by transforming min/max to quadrants
let min = {
x: (box.min.x - node.box.min.x) / nodeBoxSize.x,
y: (box.min.y - node.box.min.y) / nodeBoxSize.y};
let max = {
x: (box.max.x - node.box.max.x) / nodeBoxSize.x,
y: (box.max.y - node.box.max.y) / nodeBoxSize.y};
min.x = min.x < 0.5 ? 0 : 1;
min.y = min.y < 0.5 ? 0 : 1;
max.x = max.x < 0.5 ? 0 : 1;
max.y = max.y < 0.5 ? 0 : 1;
let childIndices;
if (min.x === 0 && min.y === 0 && max.x === 1 && max.y === 1) {
childIndices = [0, 1, 2, 3];
} else if (min.x === max.x && min.y === max.y) {
childIndices = [(min.x << 1) | min.y];
} else {
childIndices = [(min.x << 1) | min.y, (max.x << 1) | max.y];
}
for (let index of childIndices) {
if (node.children[index] === undefined) {
let childBox = node.box.clone();
if ((index & 2) > 0) {
childBox.min.x += nodeBoxSize.x / 2.0;
} else {
childBox.max.x -= nodeBoxSize.x / 2.0;
}
if ((index & 1) > 0) {
childBox.min.y += nodeBoxSize.y / 2.0;
} else {
childBox.max.y -= nodeBoxSize.y / 2.0;
}
let child = new Potree.DEMNode(node.name + index, childBox, this.tileSize);
node.children[index] = child;
}
let child = node.children[index];
if (child.level < level) {
stack.push(child);
} else {
result.push(child);
}
}
}
return result;
}
childIndex (uv) {
let [x, y] = uv.map(n => n < 0.5 ? 0 : 1);
let index = (x << 1) | y;
return index;
}
height (position) {
// return this.root.height(position);
if (!this.root) {
return 0;
}
let height = null;
let list = [this.root];
while (true) {
let node = list[list.length - 1];
let currentHeight = node.height(position);
if (currentHeight !== null) {
height = currentHeight;
}
let uv = node.uv(position);
let childIndex = this.childIndex(uv);
if (node.children[childIndex]) {
list.push(node.children[childIndex]);
} else {
break;
}
}
return height + this.pointcloud.position.z;
}
update (visibleNodes) {
if (Potree.getDEMWorkerInstance().working) {
return;
}
// check if point cloud transformation changed
if (this.matrix === null || !this.matrix.equals(this.pointcloud.matrixWorld)) {
this.matrix = this.pointcloud.matrixWorld.clone();
this.boundingBox = this.pointcloud.boundingBox.clone().applyMatrix4(this.matrix);
this.root = new Potree.DEMNode('r', this.boundingBox, this.tileSize);
this.version++;
}
// find node to update
let node = null;
for (let vn of visibleNodes) {
if (vn.demVersion === undefined || vn.demVersion < this.version) {
node = vn;
break;
}
}
if (node === null) {
return;
}
// update node
let projectedBox = node.getBoundingBox().clone().applyMatrix4(this.matrix);
let projectedBoxSize = projectedBox.getSize();
let targetNodes = this.expandAndFindByBox(projectedBox, node.getLevel());
node.demVersion = this.version;
Potree.getDEMWorkerInstance().onmessage = (e) => {
let data = new Float32Array(e.data.dem.data);
for (let demNode of targetNodes) {
let boxSize = demNode.box.getSize();
for (let i = 0; i < this.tileSize; i++) {
for (let j = 0; j < this.tileSize; j++) {
let u = (i / (this.tileSize - 1));
let v = (j / (this.tileSize - 1));
let x = demNode.box.min.x + u * boxSize.x;
let y = demNode.box.min.y + v * boxSize.y;
let ix = this.tileSize * (x - projectedBox.min.x) / projectedBoxSize.x;
let iy = this.tileSize * (y - projectedBox.min.y) / projectedBoxSize.y;
if (ix < 0 || ix > this.tileSize) {
continue;
}
if (iy < 0 || iy > this.tileSize) {
continue;
}
ix = Math.min(Math.floor(ix), this.tileSize - 1);
iy = Math.min(Math.floor(iy), this.tileSize - 1);
demNode.data[i + this.tileSize * j] = data[ix + this.tileSize * iy];
}
}
demNode.createMipMap();
demNode.mipMapNeedsUpdate = true;
Potree.getDEMWorkerInstance().working = false;
}
// TODO only works somewhat if there is no rotation to the point cloud
// let target = targetNodes[0];
// target.data = new Float32Array(data);
//
//
/// /node.dem = e.data.dem;
//
// Potree.getDEMWorkerInstance().working = false;
//
// { // create scene objects for debugging
// //for(let demNode of targetNodes){
// var bb = new Potree.Box3Helper(box);
// viewer.scene.scene.add(bb);
//
// createDEMMesh(this, target);
// //}
//
// }
};
let position = node.geometryNode.geometry.attributes.position.array;
let message = {
boundingBox: {
min: node.getBoundingBox().min.toArray(),
max: node.getBoundingBox().max.toArray()
},
position: new Float32Array(position).buffer
};
let transferables = [message.position];
Potree.getDEMWorkerInstance().working = true;
Potree.getDEMWorkerInstance().postMessage(message, transferables);
}
};
Potree.PointCloudTreeNode = class {
getChildren () {
throw new Error('override function');
}
getBoundingBox () {
throw new Error('override function');
}
isLoaded () {
throw new Error('override function');
}
isGeometryNode () {
throw new Error('override function');
}
isTreeNode () {
throw new Error('override function');
}
getLevel () {
throw new Error('override function');
}
getBoundingSphere () {
throw new Error('override function');
}
};
Potree.PointCloudTree = class PointCloudTree extends THREE.Object3D {
constructor () {
super();
this.dem = new Potree.DEM(this);
}
initialized () {
return this.root !== null;
}
};
Potree.WorkerPool = class WorkerPool {
constructor () {
this.workers = {};
}
getWorker (url) {
if (!this.workers[url]) {
this.workers[url] = [];
}
if (this.workers[url].length === 0) {
let worker = new Worker(url);
this.workers[url].push(worker);
}
let worker = this.workers[url].pop();
return worker;
}
returnWorker (url, worker) {
this.workers[url].push(worker);
}
};
Potree.workerPool = new Potree.WorkerPool();
Potree.Shaders["pointcloud.vs"] = `
precision mediump float;
precision mediump int;
#define max_clip_boxes 30
attribute vec3 position;
attribute vec3 color;
attribute vec3 normal;
attribute float intensity;
attribute float classification;
attribute float returnNumber;
attribute float numberOfReturns;
attribute float pointSourceID;
attribute vec4 indices;
//attribute float indices;
uniform mat4 modelMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat3 normalMatrix;
uniform float pcIndex;
//uniform mat4 toModel;
uniform float screenWidth;
uniform float screenHeight;
uniform float fov;
uniform float spacing;
uniform float near;
uniform float far;
#if defined use_clip_box
uniform mat4 clipBoxes[max_clip_boxes];
#endif
uniform float heightMin;
uniform float heightMax;
uniform float size; // pixel size factor
uniform float minSize; // minimum pixel size
uniform float maxSize; // maximum pixel size
uniform float octreeSize;
uniform vec3 bbSize;
uniform vec3 uColor;
uniform float opacity;
uniform float clipBoxCount;
uniform float level;
uniform float vnStart;
uniform vec2 intensityRange;
uniform float intensityGamma;
uniform float intensityContrast;
uniform float intensityBrightness;
uniform float rgbGamma;
uniform float rgbContrast;
uniform float rgbBrightness;
uniform float transition;
uniform float wRGB;
uniform float wIntensity;
uniform float wElevation;
uniform float wClassification;
uniform float wReturnNumber;
uniform float wSourceID;
uniform sampler2D visibleNodes;
uniform sampler2D gradient;
uniform sampler2D classificationLUT;
uniform sampler2D depthMap;
varying float vOpacity;
varying vec3 vColor;
varying float vLinearDepth;
varying float vLogDepth;
varying vec3 vViewPosition;
varying float vRadius;
varying vec3 vWorldPosition;
varying vec3 vNormal;
// ---------------------
// OCTREE
// ---------------------
#if (defined(adaptive_point_size) || defined(color_type_lod)) && defined(tree_type_octree)
/**
* number of 1-bits up to inclusive index position
* number is treated as if it were an integer in the range 0-255
*
*/
float numberOfOnes(float number, float index){
float tmp = mod(number, pow(2.0, index + 1.0));
float numOnes = 0.0;
for(float i = 0.0; i < 8.0; i++){
if(mod(tmp, 2.0) != 0.0){
numOnes++;
}
tmp = floor(tmp / 2.0);
}
return numOnes;
}
/**
* checks whether the bit at index is 1
* number is treated as if it were an integer in the range 0-255
*
*/
bool isBitSet(float number, float index){
return mod(floor(number / pow(2.0, index)), 2.0) != 0.0;
}
/**
* find the LOD at the point position
*/
float getLOD(){
vec3 offset = vec3(0.0, 0.0, 0.0);
float iOffset = vnStart;
float depth = level;
for(float i = 0.0; i <= 30.0; i++){
float nodeSizeAtLevel = octreeSize / pow(2.0, i + level + 0.0);
vec3 index3d = (position-offset) / nodeSizeAtLevel;
index3d = floor(index3d + 0.5);
float index = 4.0 * index3d.x + 2.0 * index3d.y + index3d.z;
vec4 value = texture2D(visibleNodes, vec2(iOffset / 2048.0, 0.0));
float mask = value.r * 255.0;
if(isBitSet(mask, index)){
// there are more visible child nodes at this position
iOffset = iOffset + value.g * 255.0 * 256.0 + value.b * 255.0 + numberOfOnes(mask, index - 1.0);
depth++;
}else{
// no more visible child nodes at this position
return depth;
}
offset = offset + (vec3(1.0, 1.0, 1.0) * nodeSizeAtLevel * 0.5) * index3d;
}
return depth;
}
float getPointSizeAttenuation(){
return pow(1.9, getLOD());
}
#endif
// ---------------------
// KD-TREE
// ---------------------
#if (defined(adaptive_point_size) || defined(color_type_lod)) && defined(tree_type_kdtree)
float getLOD(){
vec3 offset = vec3(0.0, 0.0, 0.0);
float iOffset = 0.0;
float depth = 0.0;
vec3 size = bbSize;
vec3 pos = position;
for(float i = 0.0; i <= 1000.0; i++){
vec4 value = texture2D(visibleNodes, vec2(iOffset / 2048.0, 0.0));
int children = int(value.r * 255.0);
float next = value.g * 255.0;
int split = int(value.b * 255.0);
if(next == 0.0){
return depth;
}
vec3 splitv = vec3(0.0, 0.0, 0.0);
if(split == 1){
splitv.x = 1.0;
}else if(split == 2){
splitv.y = 1.0;
}else if(split == 4){
splitv.z = 1.0;
}
iOffset = iOffset + next;
float factor = length(pos * splitv / size);
if(factor < 0.5){
// left
if(children == 0 || children == 2){
return depth;
}
}else{
// right
pos = pos - size * splitv * 0.5;
if(children == 0 || children == 1){
return depth;
}
if(children == 3){
iOffset = iOffset + 1.0;
}
}
size = size * ((1.0 - (splitv + 1.0) / 2.0) + 0.5);
depth++;
}
return depth;
}
float getPointSizeAttenuation(){
return 0.5 * pow(1.3, getLOD());
}
#endif
// formula adapted from: http://www.dfstudios.co.uk/articles/programming/image-programming-algorithms/image-processing-algorithms-part-5-contrast-adjustment/
float getContrastFactor(float contrast){
return (1.0158730158730156 * (contrast + 1.0)) / (1.0158730158730156 - contrast);
}
vec3 getRGB(){
vec3 rgb = color;
rgb = pow(rgb, vec3(rgbGamma));
rgb = rgb + rgbBrightness;
rgb = (rgb - 0.5) * getContrastFactor(rgbContrast) + 0.5;
rgb = clamp(rgb, 0.0, 1.0);
//rgb = indices.rgb;
//rgb.b = pcIndex / 255.0;
return rgb;
}
float getIntensity(){
float w = (intensity - intensityRange.x) / (intensityRange.y - intensityRange.x);
w = pow(w, intensityGamma);
w = w + intensityBrightness;
w = (w - 0.5) * getContrastFactor(intensityContrast) + 0.5;
w = clamp(w, 0.0, 1.0);
return w;
}
vec3 getElevation(){
vec4 world = modelMatrix * vec4( position, 1.0 );
float w = (world.z - heightMin) / (heightMax-heightMin);
vec3 cElevation = texture2D(gradient, vec2(w,1.0-w)).rgb;
return cElevation;
}
vec4 getClassification(){
vec2 uv = vec2(classification / 255.0, 0.5);
vec4 classColor = texture2D(classificationLUT, uv);
return classColor;
}
vec3 getReturnNumber(){
if(numberOfReturns == 1.0){
return vec3(1.0, 1.0, 0.0);
}else{
if(returnNumber == 1.0){
return vec3(1.0, 0.0, 0.0);
}else if(returnNumber == numberOfReturns){
return vec3(0.0, 0.0, 1.0);
}else{
return vec3(0.0, 1.0, 0.0);
}
}
}
vec3 getSourceID(){
float w = mod(pointSourceID, 10.0) / 10.0;
return texture2D(gradient, vec2(w,1.0 - w)).rgb;
}
vec3 getCompositeColor(){
vec3 c;
float w;
c += wRGB * getRGB();
w += wRGB;
c += wIntensity * getIntensity() * vec3(1.0, 1.0, 1.0);
w += wIntensity;
c += wElevation * getElevation();
w += wElevation;
c += wReturnNumber * getReturnNumber();
w += wReturnNumber;
c += wSourceID * getSourceID();
w += wSourceID;
vec4 cl = wClassification * getClassification();
c += cl.a * cl.rgb;
w += wClassification * cl.a;
c = c / w;
if(w == 0.0){
//c = color;
gl_Position = vec4(100.0, 100.0, 100.0, 0.0);
}
return c;
}
void main() {
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
vViewPosition = mvPosition.xyz;
gl_Position = projectionMatrix * mvPosition;
vOpacity = opacity;
vLinearDepth = gl_Position.w;
vLogDepth = log2(gl_Position.w);
vNormal = normalize(normalMatrix * normal);
// ---------------------
// POINT COLOR
// ---------------------
vec4 cl = getClassification();
#ifdef color_type_rgb
vColor = getRGB();
#elif defined color_type_height
vColor = getElevation();
#elif defined color_type_rgb_height
vec3 cHeight = getElevation();
vColor = (1.0 - transition) * getRGB() + transition * cHeight;
#elif defined color_type_depth
float linearDepth = -mvPosition.z ;
float expDepth = (gl_Position.z / gl_Position.w) * 0.5 + 0.5;
vColor = vec3(linearDepth, expDepth, 0.0);
#elif defined color_type_intensity
float w = getIntensity();
vColor = vec3(w, w, w);
#elif defined color_type_intensity_gradient
float w = getIntensity();
vColor = texture2D(gradient, vec2(w,1.0-w)).rgb;
#elif defined color_type_color
vColor = uColor;
#elif defined color_type_lod
float depth = getLOD();
float w = depth / 5.0;
vColor = texture2D(gradient, vec2(w,1.0-w)).rgb;
#elif defined color_type_point_index
//vColor = indices.rgb * 255.0;
vColor = indices.rgb;
//vColor.r = mod(indices, 256.0) / 255.0;
//vColor.g = mod(indices / 256.0, 256.0) / 255.0;
//vColor.b = 0.0;
#elif defined color_type_classification
vColor = cl.rgb;
#elif defined color_type_return_number
vColor = getReturnNumber();
#elif defined color_type_source
vColor = getSourceID();
#elif defined color_type_normal
vColor = (modelMatrix * vec4(normal, 0.0)).xyz;
#elif defined color_type_phong
vColor = color;
#elif defined color_type_composite
vColor = getCompositeColor();
#endif
#if !defined color_type_composite
if(cl.a == 0.0){
gl_Position = vec4(100.0, 100.0, 100.0, 0.0);
return;
}
#endif
// ---------------------
// POINT SIZE
// ---------------------
float pointSize = 1.0;
float slope = tan(fov / 2.0);
float projFactor = -0.5 * screenHeight / (slope * vViewPosition.z);
float r = spacing * 1.5;
vRadius = r;
#if defined fixed_point_size
pointSize = size;
#elif defined attenuated_point_size
pointSize = size * projFactor;
#elif defined adaptive_point_size
float worldSpaceSize = size * r / getPointSizeAttenuation();
pointSize = worldSpaceSize * projFactor;
#endif
pointSize = max(minSize, pointSize);
pointSize = min(maxSize, pointSize);
vRadius = pointSize / projFactor;
gl_PointSize = pointSize;
//gl_Position = vec4(1000.0, 1000.0, 1000.0, 1.0);
// ---------------------
// CLIPPING
// ---------------------
#if defined use_clip_box
bool insideAny = false;
for(int i = 0; i < max_clip_boxes; i++){
if(i == int(clipBoxCount)){
break;
}
vec4 clipPosition = clipBoxes[i] * modelMatrix * vec4( position, 1.0 );
bool inside = -0.5 <= clipPosition.x && clipPosition.x <= 0.5;
inside = inside && -0.5 <= clipPosition.y && clipPosition.y <= 0.5;
inside = inside && -0.5 <= clipPosition.z && clipPosition.z <= 0.5;
insideAny = insideAny || inside;
}
if(!insideAny){
#if defined clip_outside
gl_Position = vec4(1000.0, 1000.0, 1000.0, 1.0);
#elif defined clip_highlight_inside && !defined(color_type_depth)
float c = (vColor.r + vColor.g + vColor.b) / 6.0;
#endif
}else{
#if defined clip_highlight_inside
vColor.r += 0.5;
//vec3 hsv = rgb2hsv(vColor);
//hsv.x = hsv.x - 0.3;
//hsv.z = hsv.z + 0.1;
//vColor = hsv2rgb(hsv);
#endif
}
#endif
//vColor = indices.rgb * 255.0;
}
`
Potree.Shaders["pointcloud.fs"] = `
precision mediump float;
precision mediump int;
#if defined paraboloid_point_shape
#extension GL_EXT_frag_depth : enable
#endif
uniform mat4 viewMatrix;
uniform vec3 cameraPosition;
uniform mat4 projectionMatrix;
uniform float opacity;
uniform float blendHardness;
uniform float blendDepthSupplement;
uniform float fov;
uniform float spacing;
uniform float near;
uniform float far;
uniform float pcIndex;
uniform float screenWidth;
uniform float screenHeight;
uniform sampler2D depthMap;
varying vec3 vColor;
varying float vOpacity;
varying float vLinearDepth;
varying float vLogDepth;
varying vec3 vViewPosition;
varying float vRadius;
varying vec3 vNormal;
float specularStrength = 1.0;
void main() {
vec3 color = vColor;
float depth = gl_FragCoord.z;
#if defined(circle_point_shape) || defined(paraboloid_point_shape) || defined (weighted_splats)
float u = 2.0 * gl_PointCoord.x - 1.0;
float v = 2.0 * gl_PointCoord.y - 1.0;
#endif
#if defined(circle_point_shape) || defined (weighted_splats)
float cc = u*u + v*v;
if(cc > 1.0){
discard;
}
#endif
#if defined weighted_splats
vec2 uv = gl_FragCoord.xy / vec2(screenWidth, screenHeight);
float sDepth = texture2D(depthMap, uv).r;
if(vLinearDepth > sDepth + vRadius + blendDepthSupplement){
discard;
}
#endif
#if defined color_type_point_index
gl_FragColor = vec4(color, pcIndex / 255.0);
#else
gl_FragColor = vec4(color, vOpacity);
#endif
vec3 normal = normalize( vNormal );
normal.z = abs(normal.z);
vec3 viewPosition = normalize( vViewPosition );
#if defined(color_type_phong)
// code taken from three.js phong light fragment shader
#if MAX_POINT_LIGHTS > 0
vec3 pointDiffuse = vec3( 0.0 );
vec3 pointSpecular = vec3( 0.0 );
for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {
vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );
vec3 lVector = lPosition.xyz + vViewPosition.xyz;
float lDistance = 1.0;
if ( pointLightDistance[ i ] > 0.0 )
lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );
lVector = normalize( lVector );
// diffuse
float dotProduct = dot( normal, lVector );
#ifdef WRAP_AROUND
float pointDiffuseWeightFull = max( dotProduct, 0.0 );
float pointDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );
vec3 pointDiffuseWeight = mix( vec3( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );
#else
float pointDiffuseWeight = max( dotProduct, 0.0 );
#endif
pointDiffuse += diffuse * pointLightColor[ i ] * pointDiffuseWeight * lDistance;
// specular
vec3 pointHalfVector = normalize( lVector + viewPosition );
float pointDotNormalHalf = max( d