UNPKG

potree

Version:

WebGL point cloud viewer - WORK IN PROGRESS

1,940 lines (1,532 loc) 519 kB
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