UNPKG

@sauskylark/potree

Version:

WebGL point cloud viewer

937 lines (728 loc) 23 kB
import * as THREE from "../../libs/three.js/build/three.module.js"; import {TextSprite} from "../TextSprite.js"; import {Utils} from "../utils.js"; import {Line2} from "../../libs/three.js/lines/Line2.js"; import {LineGeometry} from "../../libs/three.js/lines/LineGeometry.js"; import {LineMaterial} from "../../libs/three.js/lines/LineMaterial.js"; function createHeightLine(){ let lineGeometry = new LineGeometry(); lineGeometry.setPositions([ 0, 0, 0, 0, 0, 0, ]); let lineMaterial = new LineMaterial({ color: 0x00ff00, dashSize: 5, gapSize: 2, linewidth: 2, resolution: new THREE.Vector2(1000, 1000), }); lineMaterial.depthTest = false; const heightEdge = new Line2(lineGeometry, lineMaterial); heightEdge.visible = false; //this.add(this.heightEdge); return heightEdge; } function createHeightLabel(){ const heightLabel = new TextSprite(''); heightLabel.setTextColor({r: 140, g: 250, b: 140, a: 1.0}); heightLabel.setBorderColor({r: 0, g: 0, b: 0, a: 1.0}); heightLabel.setBackgroundColor({r: 0, g: 0, b: 0, a: 1.0}); heightLabel.fontsize = 16; heightLabel.material.depthTest = false; heightLabel.material.opacity = 1; heightLabel.visible = false; return heightLabel; } function createAreaLabel(){ const areaLabel = new TextSprite(''); areaLabel.setTextColor({r: 140, g: 250, b: 140, a: 1.0}); areaLabel.setBorderColor({r: 0, g: 0, b: 0, a: 1.0}); areaLabel.setBackgroundColor({r: 0, g: 0, b: 0, a: 1.0}); areaLabel.fontsize = 16; areaLabel.material.depthTest = false; areaLabel.material.opacity = 1; areaLabel.visible = false; return areaLabel; } function createCircleRadiusLabel(){ const circleRadiusLabel = new TextSprite(""); circleRadiusLabel.setTextColor({r: 140, g: 250, b: 140, a: 1.0}); circleRadiusLabel.setBorderColor({r: 0, g: 0, b: 0, a: 1.0}); circleRadiusLabel.setBackgroundColor({r: 0, g: 0, b: 0, a: 1.0}); circleRadiusLabel.fontsize = 16; circleRadiusLabel.material.depthTest = false; circleRadiusLabel.material.opacity = 1; circleRadiusLabel.visible = false; return circleRadiusLabel; } function createCircleRadiusLine(){ const lineGeometry = new LineGeometry(); lineGeometry.setPositions([ 0, 0, 0, 0, 0, 0, ]); const lineMaterial = new LineMaterial({ color: 0xff0000, linewidth: 2, resolution: new THREE.Vector2(1000, 1000), gapSize: 1, dashed: true, }); lineMaterial.depthTest = false; const circleRadiusLine = new Line2(lineGeometry, lineMaterial); circleRadiusLine.visible = false; return circleRadiusLine; } function createCircleLine(){ const coordinates = []; let n = 128; for(let i = 0; i <= n; i++){ let u0 = 2 * Math.PI * (i / n); let u1 = 2 * Math.PI * (i + 1) / n; let p0 = new THREE.Vector3( Math.cos(u0), Math.sin(u0), 0 ); let p1 = new THREE.Vector3( Math.cos(u1), Math.sin(u1), 0 ); coordinates.push( ...p0.toArray(), ...p1.toArray(), ); } const geometry = new LineGeometry(); geometry.setPositions(coordinates); const material = new LineMaterial({ color: 0xff0000, dashSize: 5, gapSize: 2, linewidth: 2, resolution: new THREE.Vector2(1000, 1000), }); material.depthTest = false; const circleLine = new Line2(geometry, material); circleLine.visible = false; circleLine.computeLineDistances(); return circleLine; } function createCircleCenter(){ const sg = new THREE.SphereGeometry(1, 32, 32); const sm = new THREE.MeshNormalMaterial(); const circleCenter = new THREE.Mesh(sg, sm); circleCenter.visible = false; return circleCenter; } function createLine(){ const geometry = new LineGeometry(); geometry.setPositions([ 0, 0, 0, 0, 0, 0, ]); const material = new LineMaterial({ color: 0xff0000, linewidth: 2, resolution: new THREE.Vector2(1000, 1000), gapSize: 1, dashed: true, }); material.depthTest = false; const line = new Line2(geometry, material); return line; } function createCircle(){ const coordinates = []; let n = 128; for(let i = 0; i <= n; i++){ let u0 = 2 * Math.PI * (i / n); let u1 = 2 * Math.PI * (i + 1) / n; let p0 = new THREE.Vector3( Math.cos(u0), Math.sin(u0), 0 ); let p1 = new THREE.Vector3( Math.cos(u1), Math.sin(u1), 0 ); coordinates.push( ...p0.toArray(), ...p1.toArray(), ); } const geometry = new LineGeometry(); geometry.setPositions(coordinates); const material = new LineMaterial({ color: 0xff0000, dashSize: 5, gapSize: 2, linewidth: 2, resolution: new THREE.Vector2(1000, 1000), }); material.depthTest = false; const line = new Line2(geometry, material); line.computeLineDistances(); return line; } function createAzimuth(){ const azimuth = { label: null, center: null, target: null, north: null, centerToNorth: null, centerToTarget: null, centerToTargetground: null, targetgroundToTarget: null, circle: null, node: null, }; const sg = new THREE.SphereGeometry(1, 32, 32); const sm = new THREE.MeshNormalMaterial(); { const label = new TextSprite(""); label.setTextColor({r: 140, g: 250, b: 140, a: 1.0}); label.setBorderColor({r: 0, g: 0, b: 0, a: 1.0}); label.setBackgroundColor({r: 0, g: 0, b: 0, a: 1.0}); label.fontsize = 16; label.material.depthTest = false; label.material.opacity = 1; azimuth.label = label; } azimuth.center = new THREE.Mesh(sg, sm); azimuth.target = new THREE.Mesh(sg, sm); azimuth.north = new THREE.Mesh(sg, sm); azimuth.centerToNorth = createLine(); azimuth.centerToTarget = createLine(); azimuth.centerToTargetground = createLine(); azimuth.targetgroundToTarget = createLine(); azimuth.circle = createCircle(); azimuth.node = new THREE.Object3D(); azimuth.node.add( azimuth.centerToNorth, azimuth.centerToTarget, azimuth.centerToTargetground, azimuth.targetgroundToTarget, azimuth.circle, azimuth.label, azimuth.center, azimuth.target, azimuth.north, ); return azimuth; } export class Measure extends THREE.Object3D { constructor () { super(); this.constructor.counter = (this.constructor.counter === undefined) ? 0 : this.constructor.counter + 1; this.name = 'Measure_' + this.constructor.counter; this.points = []; this._showDistances = true; this._showCoordinates = false; this._showArea = false; this._closed = true; this._showAngles = false; this._showCircle = false; this._showHeight = false; this._showEdges = true; this._showAzimuth = false; this.maxMarkers = Number.MAX_SAFE_INTEGER; this.sphereGeometry = new THREE.SphereGeometry(0.4, 10, 10); this.color = new THREE.Color(0xff0000); this.spheres = []; this.edges = []; this.sphereLabels = []; this.edgeLabels = []; this.angleLabels = []; this.coordinateLabels = []; this.heightEdge = createHeightLine(); this.heightLabel = createHeightLabel(); this.areaLabel = createAreaLabel(); this.circleRadiusLabel = createCircleRadiusLabel(); this.circleRadiusLine = createCircleRadiusLine(); this.circleLine = createCircleLine(); this.circleCenter = createCircleCenter(); this.azimuth = createAzimuth(); this.add(this.heightEdge); this.add(this.heightLabel); this.add(this.areaLabel); this.add(this.circleRadiusLabel); this.add(this.circleRadiusLine); this.add(this.circleLine); this.add(this.circleCenter); this.add(this.azimuth.node); } createSphereMaterial () { let sphereMaterial = new THREE.MeshLambertMaterial({ //shading: THREE.SmoothShading, color: this.color, depthTest: false, depthWrite: false} ); return sphereMaterial; }; addMarker (point) { if (point.x != null) { point = {position: point}; }else if(point instanceof Array){ point = {position: new THREE.Vector3(...point)}; } this.points.push(point); // sphere let sphere = new THREE.Mesh(this.sphereGeometry, this.createSphereMaterial()); this.add(sphere); this.spheres.push(sphere); { // edges let lineGeometry = new LineGeometry(); lineGeometry.setPositions( [ 0, 0, 0, 0, 0, 0, ]); let lineMaterial = new LineMaterial({ color: 0xff0000, linewidth: 2, resolution: new THREE.Vector2(1000, 1000), }); lineMaterial.depthTest = false; let edge = new Line2(lineGeometry, lineMaterial); edge.visible = true; this.add(edge); this.edges.push(edge); } { // edge labels let edgeLabel = new TextSprite(); edgeLabel.setBorderColor({r: 0, g: 0, b: 0, a: 1.0}); edgeLabel.setBackgroundColor({r: 0, g: 0, b: 0, a: 1.0}); edgeLabel.material.depthTest = false; edgeLabel.visible = false; edgeLabel.fontsize = 16; this.edgeLabels.push(edgeLabel); this.add(edgeLabel); } { // angle labels let angleLabel = new TextSprite(); angleLabel.setBorderColor({r: 0, g: 0, b: 0, a: 1.0}); angleLabel.setBackgroundColor({r: 0, g: 0, b: 0, a: 1.0}); angleLabel.fontsize = 16; angleLabel.material.depthTest = false; angleLabel.material.opacity = 1; angleLabel.visible = false; this.angleLabels.push(angleLabel); this.add(angleLabel); } { // coordinate labels let coordinateLabel = new TextSprite(); coordinateLabel.setBorderColor({r: 0, g: 0, b: 0, a: 1.0}); coordinateLabel.setBackgroundColor({r: 0, g: 0, b: 0, a: 1.0}); coordinateLabel.fontsize = 16; coordinateLabel.material.depthTest = false; coordinateLabel.material.opacity = 1; coordinateLabel.visible = false; this.coordinateLabels.push(coordinateLabel); this.add(coordinateLabel); } { // Event Listeners let drag = (e) => { let I = Utils.getMousePointCloudIntersection( e.drag.end, e.viewer.scene.getActiveCamera(), e.viewer, e.viewer.scene.pointclouds, {pickClipped: true}); if (I) { let i = this.spheres.indexOf(e.drag.object); if (i !== -1) { let point = this.points[i]; // loop through current keys and cleanup ones that will be orphaned for (let key of Object.keys(point)) { if (!I.point[key]) { delete point[key]; } } for (let key of Object.keys(I.point).filter(e => e !== 'position')) { point[key] = I.point[key]; } this.setPosition(i, I.location); } } }; let drop = e => { let i = this.spheres.indexOf(e.drag.object); if (i !== -1) { this.dispatchEvent({ 'type': 'marker_dropped', 'measurement': this, 'index': i }); } }; let mouseover = (e) => e.object.material.emissive.setHex(0x888888); let mouseleave = (e) => e.object.material.emissive.setHex(0x000000); sphere.addEventListener('drag', drag); sphere.addEventListener('drop', drop); sphere.addEventListener('mouseover', mouseover); sphere.addEventListener('mouseleave', mouseleave); } let event = { type: 'marker_added', measurement: this, sphere: sphere }; this.dispatchEvent(event); this.setMarker(this.points.length - 1, point); }; removeMarker (index) { this.points.splice(index, 1); this.remove(this.spheres[index]); let edgeIndex = (index === 0) ? 0 : (index - 1); this.remove(this.edges[edgeIndex]); this.edges.splice(edgeIndex, 1); this.remove(this.edgeLabels[edgeIndex]); this.edgeLabels.splice(edgeIndex, 1); this.coordinateLabels.splice(index, 1); this.remove(this.angleLabels[index]); this.angleLabels.splice(index, 1); this.spheres.splice(index, 1); this.update(); this.dispatchEvent({type: 'marker_removed', measurement: this}); }; setMarker (index, point) { this.points[index] = point; let event = { type: 'marker_moved', measure: this, index: index, position: point.position.clone() }; this.dispatchEvent(event); this.update(); } setPosition (index, position) { let point = this.points[index]; point.position.copy(position); let event = { type: 'marker_moved', measure: this, index: index, position: position.clone() }; this.dispatchEvent(event); this.update(); }; getArea () { let area = 0; let j = this.points.length - 1; for (let i = 0; i < this.points.length; i++) { let p1 = this.points[i].position; let p2 = this.points[j].position; area += (p2.x + p1.x) * (p1.y - p2.y); j = i; } return Math.abs(area / 2); }; getTotalDistance () { if (this.points.length === 0) { return 0; } let distance = 0; for (let i = 1; i < this.points.length; i++) { let prev = this.points[i - 1].position; let curr = this.points[i].position; let d = prev.distanceTo(curr); distance += d; } if (this.closed && this.points.length > 1) { let first = this.points[0].position; let last = this.points[this.points.length - 1].position; let d = last.distanceTo(first); distance += d; } return distance; } getAngleBetweenLines (cornerPoint, point1, point2) { let v1 = new THREE.Vector3().subVectors(point1.position, cornerPoint.position); let v2 = new THREE.Vector3().subVectors(point2.position, cornerPoint.position); // avoid the error printed by threejs if denominator is 0 const denominator = Math.sqrt( v1.lengthSq() * v2.lengthSq() ); if(denominator === 0){ return 0; }else{ return v1.angleTo(v2); } }; getAngle (index) { if (this.points.length < 3 || index >= this.points.length) { return 0; } let previous = (index === 0) ? this.points[this.points.length - 1] : this.points[index - 1]; let point = this.points[index]; let next = this.points[(index + 1) % (this.points.length)]; return this.getAngleBetweenLines(point, previous, next); } // updateAzimuth(){ // // if(this.points.length !== 2){ // // return; // // } // // const azimuth = this.azimuth; // // const [p0, p1] = this.points; // // const r = p0.position.distanceTo(p1.position); // } update () { if (this.points.length === 0) { return; } else if (this.points.length === 1) { let point = this.points[0]; let position = point.position; this.spheres[0].position.copy(position); { // coordinate labels let coordinateLabel = this.coordinateLabels[0]; let msg = position.toArray().map(p => Utils.addCommas(p.toFixed(2))).join(" / "); coordinateLabel.setText(msg); coordinateLabel.visible = this.showCoordinates; } return; } let lastIndex = this.points.length - 1; let centroid = new THREE.Vector3(); for (let i = 0; i <= lastIndex; i++) { let point = this.points[i]; centroid.add(point.position); } centroid.divideScalar(this.points.length); for (let i = 0; i <= lastIndex; i++) { let index = i; let nextIndex = (i + 1 > lastIndex) ? 0 : i + 1; let previousIndex = (i === 0) ? lastIndex : i - 1; let point = this.points[index]; let nextPoint = this.points[nextIndex]; let previousPoint = this.points[previousIndex]; let sphere = this.spheres[index]; // spheres sphere.position.copy(point.position); sphere.material.color = this.color; { // edges let edge = this.edges[index]; edge.material.color = this.color; edge.position.copy(point.position); edge.geometry.setPositions([ 0, 0, 0, ...nextPoint.position.clone().sub(point.position).toArray(), ]); edge.geometry.verticesNeedUpdate = true; edge.geometry.computeBoundingSphere(); edge.computeLineDistances(); edge.visible = index < lastIndex || this.closed; if(!this.showEdges){ edge.visible = false; } } { // edge labels let edgeLabel = this.edgeLabels[i]; let center = new THREE.Vector3().add(point.position); center.add(nextPoint.position); center = center.multiplyScalar(0.5); let distance = point.position.distanceTo(nextPoint.position); edgeLabel.position.copy(center); let suffix = ""; if(this.lengthUnit != null && this.lengthUnitDisplay != null){ distance = distance / this.lengthUnit.unitspermeter * this.lengthUnitDisplay.unitspermeter; //convert to meters then to the display unit suffix = this.lengthUnitDisplay.code; } let txtLength = Utils.addCommas(distance.toFixed(2)); edgeLabel.setText(`${txtLength} ${suffix}`); edgeLabel.visible = this.showDistances && (index < lastIndex || this.closed) && this.points.length >= 2 && distance > 0; } { // angle labels let angleLabel = this.angleLabels[i]; let angle = this.getAngleBetweenLines(point, previousPoint, nextPoint); let dir = nextPoint.position.clone().sub(previousPoint.position); dir.multiplyScalar(0.5); dir = previousPoint.position.clone().add(dir).sub(point.position).normalize(); let dist = Math.min(point.position.distanceTo(previousPoint.position), point.position.distanceTo(nextPoint.position)); dist = dist / 9; let labelPos = point.position.clone().add(dir.multiplyScalar(dist)); angleLabel.position.copy(labelPos); let msg = Utils.addCommas((angle * (180.0 / Math.PI)).toFixed(1)) + '\u00B0'; angleLabel.setText(msg); angleLabel.visible = this.showAngles && (index < lastIndex || this.closed) && this.points.length >= 3 && angle > 0; } } { // update height stuff let heightEdge = this.heightEdge; heightEdge.visible = this.showHeight; this.heightLabel.visible = this.showHeight; if (this.showHeight) { let sorted = this.points.slice().sort((a, b) => a.position.z - b.position.z); let lowPoint = sorted[0].position.clone(); let highPoint = sorted[sorted.length - 1].position.clone(); let min = lowPoint.z; let max = highPoint.z; let height = max - min; let start = new THREE.Vector3(highPoint.x, highPoint.y, min); let end = new THREE.Vector3(highPoint.x, highPoint.y, max); heightEdge.position.copy(lowPoint); heightEdge.geometry.setPositions([ 0, 0, 0, ...start.clone().sub(lowPoint).toArray(), ...start.clone().sub(lowPoint).toArray(), ...end.clone().sub(lowPoint).toArray(), ]); heightEdge.geometry.verticesNeedUpdate = true; // heightEdge.geometry.computeLineDistances(); // heightEdge.geometry.lineDistancesNeedUpdate = true; heightEdge.geometry.computeBoundingSphere(); heightEdge.computeLineDistances(); // heightEdge.material.dashSize = height / 40; // heightEdge.material.gapSize = height / 40; let heightLabelPosition = start.clone().add(end).multiplyScalar(0.5); this.heightLabel.position.copy(heightLabelPosition); let suffix = ""; if(this.lengthUnit != null && this.lengthUnitDisplay != null){ height = height / this.lengthUnit.unitspermeter * this.lengthUnitDisplay.unitspermeter; //convert to meters then to the display unit suffix = this.lengthUnitDisplay.code; } let txtHeight = Utils.addCommas(height.toFixed(2)); let msg = `${txtHeight} ${suffix}`; this.heightLabel.setText(msg); } } { // update circle stuff const circleRadiusLabel = this.circleRadiusLabel; const circleRadiusLine = this.circleRadiusLine; const circleLine = this.circleLine; const circleCenter = this.circleCenter; const circleOkay = this.points.length === 3; circleRadiusLabel.visible = this.showCircle && circleOkay; circleRadiusLine.visible = this.showCircle && circleOkay; circleLine.visible = this.showCircle && circleOkay; circleCenter.visible = this.showCircle && circleOkay; if(this.showCircle && circleOkay){ const A = this.points[0].position; const B = this.points[1].position; const C = this.points[2].position; const AB = B.clone().sub(A); const AC = C.clone().sub(A); const N = AC.clone().cross(AB).normalize(); const center = Potree.Utils.computeCircleCenter(A, B, C); const radius = center.distanceTo(A); const scale = radius / 20; circleCenter.position.copy(center); circleCenter.scale.set(scale, scale, scale); //circleRadiusLine.geometry.vertices[0].set(0, 0, 0); //circleRadiusLine.geometry.vertices[1].copy(B.clone().sub(center)); circleRadiusLine.geometry.setPositions( [ 0, 0, 0, ...B.clone().sub(center).toArray() ] ); circleRadiusLine.geometry.verticesNeedUpdate = true; circleRadiusLine.geometry.computeBoundingSphere(); circleRadiusLine.position.copy(center); circleRadiusLine.computeLineDistances(); const target = center.clone().add(N); circleLine.position.copy(center); circleLine.scale.set(radius, radius, radius); circleLine.lookAt(target); circleRadiusLabel.visible = true; circleRadiusLabel.position.copy(center.clone().add(B).multiplyScalar(0.5)); circleRadiusLabel.setText(`${radius.toFixed(3)}`); } } { // update area label this.areaLabel.position.copy(centroid); this.areaLabel.visible = this.showArea && this.points.length >= 3; let area = this.getArea(); let suffix = ""; if(this.lengthUnit != null && this.lengthUnitDisplay != null){ area = area / Math.pow(this.lengthUnit.unitspermeter, 2) * Math.pow(this.lengthUnitDisplay.unitspermeter, 2); //convert to square meters then to the square display unit suffix = this.lengthUnitDisplay.code; } let txtArea = Utils.addCommas(area.toFixed(1)); let msg = `${txtArea} ${suffix}\u00B2`; this.areaLabel.setText(msg); } // this.updateAzimuth(); }; raycast (raycaster, intersects) { for (let i = 0; i < this.points.length; i++) { let sphere = this.spheres[i]; sphere.raycast(raycaster, intersects); } // recalculate distances because they are not necessarely correct // for scaled objects. // see https://github.com/mrdoob/three.js/issues/5827 // TODO: remove this once the bug has been fixed for (let i = 0; i < intersects.length; i++) { let I = intersects[i]; I.distance = raycaster.ray.origin.distanceTo(I.point); } intersects.sort(function (a, b) { return a.distance - b.distance; }); }; get showCoordinates () { return this._showCoordinates; } set showCoordinates (value) { this._showCoordinates = value; this.update(); } get showAngles () { return this._showAngles; } set showAngles (value) { this._showAngles = value; this.update(); } get showCircle () { return this._showCircle; } set showCircle (value) { this._showCircle = value; this.update(); } get showAzimuth(){ return this._showAzimuth; } set showAzimuth(value){ this._showAzimuth = value; this.update(); } get showEdges () { return this._showEdges; } set showEdges (value) { this._showEdges = value; this.update(); } get showHeight () { return this._showHeight; } set showHeight (value) { this._showHeight = value; this.update(); } get showArea () { return this._showArea; } set showArea (value) { this._showArea = value; this.update(); } get closed () { return this._closed; } set closed (value) { this._closed = value; this.update(); } get showDistances () { return this._showDistances; } set showDistances (value) { this._showDistances = value; this.update(); } }