text: 
  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 = true;
  
  	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 = true;
  
  	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: true,
  			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 = true;
  
  			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();
  	}
  
  }
  