text: 
  import {Measure} from "./Measure.js";
  import {Utils} from "../utils.js";
  import {CameraMode} from "../defines.js";
  import { EventDispatcher } from "../EventDispatcher.js";
  
  function updateAzimuth(viewer, measure){
  
  	const azimuth = measure.azimuth;
  
  	const isOkay = measure.points.length === 2;
  
  	azimuth.node.visible = isOkay && measure.showAzimuth;
  
  	if(!azimuth.node.visible){
  		return;
  	}
  
  	const camera = viewer.scene.getActiveCamera();
  	const renderAreaSize = viewer.renderer.getSize(new THREE.Vector2());
  	const width = renderAreaSize.width;
  	const height = renderAreaSize.height;
  
  	const [p0, p1] = measure.points;
  	const r = p0.position.distanceTo(p1.position);
  	const northVec = Utils.getNorthVec(p0.position, r, viewer.getProjection());
  	const northPos = p0.position.clone().add(northVec);
  
  	azimuth.center.position.copy(p0.position);
  	azimuth.center.scale.set(2, 2, 2);
  
  	azimuth.center.visible = false;
  	// azimuth.target.visible = false;
  
  
  	{ // north
  		azimuth.north.position.copy(northPos);
  		azimuth.north.scale.set(2, 2, 2);
  
  		let distance = azimuth.north.position.distanceTo(camera.position);
  		let pr = Utils.projectedRadius(1, camera, distance, width, height);
  
  		let scale = (5 / pr);
  		azimuth.north.scale.set(scale, scale, scale);
  	}
  
  	{ // target
  		azimuth.target.position.copy(p1.position);
  		azimuth.target.position.z = azimuth.north.position.z;
  
  		let distance = azimuth.target.position.distanceTo(camera.position);
  		let pr = Utils.projectedRadius(1, camera, distance, width, height);
  
  		let scale = (5 / pr);
  		azimuth.target.scale.set(scale, scale, scale);
  	}
  
  
  	azimuth.circle.position.copy(p0.position);
  	azimuth.circle.scale.set(r, r, r);
  	azimuth.circle.material.resolution.set(width, height);
  
  	// to target
  	azimuth.centerToTarget.geometry.setPositions([
  		0, 0, 0,
  		...p1.position.clone().sub(p0.position).toArray(),
  	]);
  	azimuth.centerToTarget.position.copy(p0.position);
  	azimuth.centerToTarget.geometry.verticesNeedUpdate = true;
  	azimuth.centerToTarget.geometry.computeBoundingSphere();
  	azimuth.centerToTarget.computeLineDistances();
  	azimuth.centerToTarget.material.resolution.set(width, height);
  
  	// to target ground
  	azimuth.centerToTargetground.geometry.setPositions([
  		0, 0, 0,
  		p1.position.x - p0.position.x,
  		p1.position.y - p0.position.y,
  		0,
  	]);
  	azimuth.centerToTargetground.position.copy(p0.position);
  	azimuth.centerToTargetground.geometry.verticesNeedUpdate = true;
  	azimuth.centerToTargetground.geometry.computeBoundingSphere();
  	azimuth.centerToTargetground.computeLineDistances();
  	azimuth.centerToTargetground.material.resolution.set(width, height);
  
  	// to north
  	azimuth.centerToNorth.geometry.setPositions([
  		0, 0, 0,
  		northPos.x - p0.position.x,
  		northPos.y - p0.position.y,
  		0,
  	]);
  	azimuth.centerToNorth.position.copy(p0.position);
  	azimuth.centerToNorth.geometry.verticesNeedUpdate = true;
  	azimuth.centerToNorth.geometry.computeBoundingSphere();
  	azimuth.centerToNorth.computeLineDistances();
  	azimuth.centerToNorth.material.resolution.set(width, height);
  
  	// label
  	const radians = Utils.computeAzimuth(p0.position, p1.position, viewer.getProjection());
  	let degrees = THREE.Math.radToDeg(radians);
  	if(degrees < 0){
  		degrees = 360 + degrees;
  	}
  	const txtDegrees = `${degrees.toFixed(2)}°`;
  	const labelDir = northPos.clone().add(p1.position).multiplyScalar(0.5).sub(p0.position);
  	if(labelDir.length() > 0){
  		labelDir.z = 0;
  		labelDir.normalize();
  		const labelVec = labelDir.clone().multiplyScalar(r);
  		const labelPos = p0.position.clone().add(labelVec);
  		azimuth.label.position.copy(labelPos);
  	}
  	azimuth.label.setText(txtDegrees);
  	let distance = azimuth.label.position.distanceTo(camera.position);
  	let pr = Utils.projectedRadius(1, camera, distance, width, height);
  	let scale = (70 / pr);
  	azimuth.label.scale.set(scale, scale, scale);
  }
  
  export class MeasuringTool extends EventDispatcher{
  	constructor (viewer) {
  		super();
  
  		this.viewer = viewer;
  		this.renderer = viewer.renderer;
  
  		this.addEventListener('start_inserting_measurement', e => {
  			this.viewer.dispatchEvent({
  				type: 'cancel_insertions'
  			});
  		});
  
  		this.showLabels = true;
  		this.scene = new THREE.Scene();
  		this.scene.name = 'scene_measurement';
  		this.light = new THREE.PointLight(0xffffff, 1.0);
  		this.scene.add(this.light);
  
  		this.viewer.inputHandler.registerInteractiveScene(this.scene);
  
  		this.onRemove = (e) => { this.scene.remove(e.measurement);};
  		this.onAdd = e => {this.scene.add(e.measurement);};
  
  		for(let measurement of viewer.scene.measurements){
  			this.onAdd({measurement: measurement});
  		}
  
  		viewer.addEventListener("update", this.update.bind(this));
  		viewer.addEventListener("render.pass.perspective_overlay", this.render.bind(this));
  		viewer.addEventListener("scene_changed", this.onSceneChange.bind(this));
  
  		viewer.scene.addEventListener('measurement_added', this.onAdd);
  		viewer.scene.addEventListener('measurement_removed', this.onRemove);
  	}
  
  	onSceneChange(e){
  		if(e.oldScene){
  			e.oldScene.removeEventListener('measurement_added', this.onAdd);
  			e.oldScene.removeEventListener('measurement_removed', this.onRemove);
  		}
  
  		e.scene.addEventListener('measurement_added', this.onAdd);
  		e.scene.addEventListener('measurement_removed', this.onRemove);
  	}
  
  	startInsertion (args = {}) {
  		let domElement = this.viewer.renderer.domElement;
  
  		let measure = new Measure(args.type);
  
  		this.dispatchEvent({
  			type: 'start_inserting_measurement',
  			measure: measure
  		});
  
  		const pick = (defaul, alternative) => {
  			if(defaul != null){
  				return defaul;
  			}else{
  				return alternative;
  			}
  		};
  
  		measure.showDistances = (args.showDistances === null) ? true : args.showDistances;
  
  		measure.showArea = pick(args.showArea, false);
  		measure.showAngles = pick(args.showAngles, false);
  		measure.showCoordinates = pick(args.showCoordinates, false);
  		measure.showHeight = pick(args.showHeight, false);
  		measure.showCircle = pick(args.showCircle, false);
  		measure.showAzimuth = pick(args.showAzimuth, false);
  		measure.showEdges = pick(args.showEdges, true);
  		measure.closed = pick(args.closed, false);
  		measure.maxMarkers = pick(args.maxMarkers, Infinity);
  
  		this.scene.add(measure);
  
  		let cancel = {
  			removeLastMarker: measure.maxMarkers > 3,
  			callback: null
  		};
  
  		let insertionCallback = (e) => {
  			if (e.button === THREE.MOUSE.LEFT) {
  				measure.addMarker(measure.points[measure.points.length - 1].position.clone());
  
  				if (measure.points.length >= measure.maxMarkers) {
  					cancel.callback();
  				}
  
  				this.viewer.inputHandler.startDragging(
  					measure.spheres[measure.spheres.length - 1]);
  			} else if (e.button === THREE.MOUSE.RIGHT) {
  				cancel.callback();
  			}
  		};
  
  		cancel.callback = e => {
  			if (cancel.removeLastMarker) {
  				measure.removeMarker(measure.points.length - 1);
  			}
  			domElement.removeEventListener('mouseup', insertionCallback, true);
  			this.viewer.removeEventListener('cancel_insertions', cancel.callback);
  		};
  
  		if (measure.maxMarkers > 1) {
  			this.viewer.addEventListener('cancel_insertions', cancel.callback);
  			domElement.addEventListener('mouseup', insertionCallback, true);
  		}
  
  		measure.addMarker(new THREE.Vector3(0, 0, 0));
  		this.viewer.inputHandler.startDragging(
  			measure.spheres[measure.spheres.length - 1]);
  
  		this.viewer.scene.addMeasurement(measure);
  
  		return measure;
  	}
  
  	update(){
  		let camera = this.viewer.scene.getActiveCamera();
  		let domElement = this.renderer.domElement;
  		let measurements = this.viewer.scene.measurements;
  
  		const renderAreaSize = this.renderer.getSize(new THREE.Vector2());
  		let clientWidth = renderAreaSize.width;
  		let clientHeight = renderAreaSize.height;
  
  		this.light.position.copy(camera.position);
  
  		// make size independant of distance
  		for (let measure of measurements) {
  			measure.lengthUnit = this.viewer.lengthUnit;
  			measure.lengthUnitDisplay = this.viewer.lengthUnitDisplay;
  			measure.update();
  
  			updateAzimuth(viewer, measure);
  
  			// spheres
  			for(let sphere of measure.spheres){
  				let distance = camera.position.distanceTo(sphere.getWorldPosition(new THREE.Vector3()));
  				let pr = Utils.projectedRadius(1, camera, distance, clientWidth, clientHeight);
  				let scale = (15 / pr);
  				sphere.scale.set(scale, scale, scale);
  			}
  
  			// labels
  			let labels = measure.edgeLabels.concat(measure.angleLabels);
  			for(let label of labels){
  				let distance = camera.position.distanceTo(label.getWorldPosition(new THREE.Vector3()));
  				let pr = Utils.projectedRadius(1, camera, distance, clientWidth, clientHeight);
  				let scale = (70 / pr);
  
  				if(Potree.debug.scale){
  					scale = (Potree.debug.scale / pr);
  				}
  
  				label.scale.set(scale, scale, scale);
  			}
  
  			// coordinate labels
  			for (let j = 0; j < measure.coordinateLabels.length; j++) {
  				let label = measure.coordinateLabels[j];
  				let sphere = measure.spheres[j];
  
  				let distance = camera.position.distanceTo(sphere.getWorldPosition(new THREE.Vector3()));
  
  				let screenPos = sphere.getWorldPosition(new THREE.Vector3()).clone().project(camera);
  				screenPos.x = Math.round((screenPos.x + 1) * clientWidth / 2);
  				screenPos.y = Math.round((-screenPos.y + 1) * clientHeight / 2);
  				screenPos.z = 0;
  				screenPos.y -= 30;
  
  				let labelPos = new THREE.Vector3(
  					(screenPos.x / clientWidth) * 2 - 1,
  					-(screenPos.y / clientHeight) * 2 + 1,
  					0.5 );
  				labelPos.unproject(camera);
  				if(this.viewer.scene.cameraMode == CameraMode.PERSPECTIVE) {
  					let direction = labelPos.sub(camera.position).normalize();
  					labelPos = new THREE.Vector3().addVectors(
  						camera.position, direction.multiplyScalar(distance));
  
  				}
  				label.position.copy(labelPos);
  				let pr = Utils.projectedRadius(1, camera, distance, clientWidth, clientHeight);
  				let scale = (70 / pr);
  				label.scale.set(scale, scale, scale);
  			}
  
  			// height label
  			if (measure.showHeight) {
  				let label = measure.heightLabel;
  
  				{
  					let distance = label.position.distanceTo(camera.position);
  					let pr = Utils.projectedRadius(1, camera, distance, clientWidth, clientHeight);
  					let scale = (70 / pr);
  					label.scale.set(scale, scale, scale);
  				}
  
  				{ // height edge
  					let edge = measure.heightEdge;
  
  					let sorted = measure.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 start = new THREE.Vector3(highPoint.x, highPoint.y, min);
  					let end = new THREE.Vector3(highPoint.x, highPoint.y, max);
  
  					let lowScreen = lowPoint.clone().project(camera);
  					let startScreen = start.clone().project(camera);
  					let endScreen = end.clone().project(camera);
  
  					let toPixelCoordinates = v => {
  						let r = v.clone().addScalar(1).divideScalar(2);
  						r.x = r.x * clientWidth;
  						r.y = r.y * clientHeight;
  						r.z = 0;
  
  						return r;
  					};
  
  					let lowEL = toPixelCoordinates(lowScreen);
  					let startEL = toPixelCoordinates(startScreen);
  					let endEL = toPixelCoordinates(endScreen);
  
  					let lToS = lowEL.distanceTo(startEL);
  					let sToE = startEL.distanceTo(endEL);
  
  					edge.geometry.lineDistances = [0, lToS, lToS, lToS + sToE];
  					edge.geometry.lineDistancesNeedUpdate = true;
  
  					edge.material.dashSize = 10;
  					edge.material.gapSize = 10;
  				}
  			}
  
  			{ // area label
  				let label = measure.areaLabel;
  				let distance = label.position.distanceTo(camera.position);
  				let pr = Utils.projectedRadius(1, camera, distance, clientWidth, clientHeight);
  
  				let scale = (70 / pr);
  				label.scale.set(scale, scale, scale);
  			}
  
  			{ // radius label
  				let label = measure.circleRadiusLabel;
  				let distance = label.position.distanceTo(camera.position);
  				let pr = Utils.projectedRadius(1, camera, distance, clientWidth, clientHeight);
  
  				let scale = (70 / pr);
  				label.scale.set(scale, scale, scale);
  			}
  
  			{ // edges
  				const materials = [
  					measure.circleRadiusLine.material,
  					...measure.edges.map( (e) => e.material),
  					measure.heightEdge.material,
  					measure.circleLine.material,
  				];
  
  				for(const material of materials){
  					material.resolution.set(clientWidth, clientHeight);
  				}
  			}
  
  			if(!this.showLabels){
  
  				const labels = [
  					...measure.sphereLabels,
  					...measure.edgeLabels,
  					...measure.angleLabels,
  					...measure.coordinateLabels,
  					measure.heightLabel,
  					measure.areaLabel,
  					measure.circleRadiusLabel,
  				];
  
  				for(const label of labels){
  					label.visible = false;
  				}
  			}
  		}
  	}
  
  	render(){
  		this.viewer.renderer.render(this.scene, this.viewer.scene.getActiveCamera());
  	}
  };
  