TkAstral3D/packages/sdk/lib/core/tools/Measure.ts

814 lines
28 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as THREE from "three";
import {CSS2DObject} from "three/examples/jsm/renderers/CSS2DRenderer.js";
import {useDispatchSignal} from "@/hooks";
import Viewer from "@/core/viewer/Viewer";
export interface MeasureEventMap{
/**
* 完成绘制触发
*/
complete: {object: THREE.Group};
}
export enum MeasureMode {
Distance = "Distance",
Area = "Area",
Angle = "Angle"
}
let pdFn, pmFn, puFn, kdFn;
/**
* Measure class
*/
class Measure extends THREE.EventDispatcher<MeasureEventMap>{
static LINE_MATERIAL = new THREE.LineBasicMaterial({
color: 0xE63C17,
linewidth: 2,
opacity: 0.9,
transparent: true,
side: THREE.DoubleSide,
depthWrite: false,
depthTest: false
});
static MESH_MATERIAL = new THREE.MeshBasicMaterial({
color: 0x87cefa,
transparent: true,
opacity: 0.7,
side: THREE.DoubleSide,
depthWrite: false,
depthTest: false
});
// 标记点图片
static MARKER_TEXTURE = new URL(import.meta.env.BASE_URL + 'static/images/logo.png', import.meta.url).href;
static MAX_DISTANCE = 500; //当相交物体的距离太远时,忽略它
static OBJ_NAME = "object_for_measure";
static LABEL_NAME = "label_for_measure";
public isCompleted = true; // 测量操作是否完成
public isClose = true; // 测量操作是否已关闭(全销毁)
public mode: MeasureMode;
protected scene: THREE.Scene;
protected spriteMaterial?: THREE.SpriteMaterial;
protected raycaster?: THREE.Raycaster;
protected mouseMoved = false;
protected polyline?: THREE.Line; // 用户在测量时绘制的线的当前实例
protected faces?: THREE.Mesh; // 用于测量面积的当前实例
protected curve?: THREE.Line; // 用弧线表示角度
protected tempPointMarker?: THREE.Sprite; // 用于存储临时点
protected tempLine?: THREE.Line; // 用于存储临时线条,用于在鼠标移动时绘制线条/区域/角度
protected tempLabel?: CSS2DObject; // 用于在鼠标移动时存储临时标签,只有测量距离时才有
protected pointArray: THREE.Vector3[] = []; // 存储点
protected lastClickTime?: number; //保存上次点击时间,以便检测双击事件
protected viewer:Viewer;
// 所有测绘内容组
public measureGroup: THREE.Group;
// 当前测绘内容组
protected group: THREE.Group;
constructor(viewer:Viewer, mode: MeasureMode = MeasureMode.Distance) {
super();
this.mode = mode;
this.scene = viewer.scene;
this.viewer = viewer;
// 初始化group
this.measureGroup = new THREE.Group();
this.measureGroup.name = `measure_group`;
this.measureGroup.ignore = true;
this.group = new THREE.Group();
this.scene.add(this.measureGroup);
this.viewer.modules.dragControl.setMeasureInstance(this);
}
get domElement(): HTMLCanvasElement{
return this.viewer.renderer.domElement;
}
get canvas(): HTMLCanvasElement {
return this.domElement as HTMLCanvasElement;
}
addEvent() {
pdFn = this.mousedown.bind(this);
this.canvas.addEventListener("pointerdown", pdFn);
pmFn = this.mousemove.bind(this);
this.canvas.addEventListener("pointermove", pmFn);
puFn = this.mouseup.bind(this);
this.canvas.addEventListener("pointerup", puFn);
kdFn = this.keydown.bind(this);
window.addEventListener("keydown", kdFn);
}
removeEvent() {
this.canvas.removeEventListener("pointerdown", pdFn);
pdFn = undefined;
this.canvas.removeEventListener("pointermove", pmFn);
pmFn = undefined;
this.canvas.removeEventListener("pointerup", puFn);
puFn = undefined;
window.removeEventListener("keydown", kdFn);
kdFn = undefined;
}
// 开始测量
open() {
this.addEvent();
if (this.isClose) {
this.raycaster = new THREE.Raycaster();
}
// 重置
this.group = new THREE.Group();
this.group.name = `${Measure.OBJ_NAME}_group`;
this.group.userData = {
mode: this.mode
}
this.measureGroup.add(this.group);
// 当次绘制点
this.pointArray = [];
// 测量距离、面积和角度需要折线
this.polyline = this.createLine();
this.group.add(this.polyline as THREE.Object3D);
if (this.mode === MeasureMode.Area) {
this.faces = this.createFaces();
this.group.add(this.faces as THREE.Object3D);
}
this.isCompleted = false;
this.isClose = false;
this.domElement.style.cursor = "crosshair";
// 禁用拖拽控制器
this.viewer.modules.dragControl.dragControls.enabled = false;
}
// 重绘
redraw(point: THREE.Sprite) {
// 当次绘制点
this.pointArray = [];
(point.parent as THREE.Group).children.forEach(child => {
switch (child.userData.type) {
case "measure-marker":
// 当前点正在操作,不加入
if(child.uuid !== point.uuid) {
this.pointArray[child.userData.pointIndex] = child.userData.point;
}else{
this.tempPointMarker = child as THREE.Sprite;
}
break;
case "line":
this.polyline = child as THREE.Line;
this.tempLine = this.createLine();
this.scene.add(this.tempLine as THREE.Object3D);
break;
case "faces":
this.faces = child as THREE.Mesh;
break;
case "curve":
this.curve = child as THREE.Line;
break;
}
})
// 重写move事件
pmFn = this.redrawMousemove.bind(this);
this.canvas.addEventListener("pointermove", pmFn);
this.group = point.parent as THREE.Group;
this.isCompleted = false;
this.mode = this.group.userData.mode;
}
/**
* 结束测量,清空所有结果
*/
clear() {
this.removeEvent();
this.clearTemp();
this.measureGroup.children.forEach(g => {
for (let i = g.children.length - 1; i >= 0; i--) {
const c = g.children[i];
if(c.userData.type == "measure-marker"){
// 从拖拽控制器移除
this.viewer.modules.dragControl.setDragObjects([c], "remove");
}
g.remove(c)
}
})
this.measureGroup.remove(...this.measureGroup.children);
this.polyline = undefined;
this.faces = undefined;
this.curve = undefined;
this.pointArray = [];
this.raycaster = undefined;
this.domElement.style.cursor = "";
this.isClose = true;
useDispatchSignal("sceneGraphChanged")
}
/**
* 新版本threejs中BufferGeometry.setFromPoints方法不在支持添加点位置需要手动设置顶点属性
*/
setFromPoints(geo: THREE.BufferGeometry, points: THREE.Vector3[]){
const position:number[] = [];
for ( let i = 0, l = points.length; i < l; i++) {
const point = points[ i ];
position.push(point.x, point.y, point.z);
}
geo.setAttribute('position', new THREE.Float32BufferAttribute(position, 3));
}
/**
* 初始化点标记材料
*/
initPointMarkerMaterial() {
const markerTexture = new THREE.TextureLoader().load(Measure.MARKER_TEXTURE);
this.spriteMaterial = new THREE.SpriteMaterial({
map: markerTexture,
depthTest: false, // 深度测试
depthWrite: false, // 深度写入
sizeAttenuation: false,
transparent: true,
opacity: 0.9
});
}
// 创建点标记
createPointMarker(position?: THREE.Vector3): THREE.Sprite {
if (!this.spriteMaterial) {
this.initPointMarkerMaterial();
}
const p = position;
const scale = 0.012;
const obj = new THREE.Sprite(this.spriteMaterial);
obj.scale.set(scale, scale, scale);
if (p) {
obj.position.set(p.x, p.y, p.z);
}
obj.name = Measure.OBJ_NAME;
obj.userData = {
mode: this.mode,
type: "measure-marker",
}
return obj;
}
/**
* Creates THREE.Line
*/
private createLine(): THREE.Line {
const geom = new THREE.BufferGeometry();
const obj = new THREE.Line(geom, Measure.LINE_MATERIAL);
obj.frustumCulled = false;
obj.name = Measure.OBJ_NAME;
obj.userData = {
type: "line",
}
return obj;
}
/**
* Creates THREE.Mesh
*/
private createFaces() {
const geom = new THREE.BufferGeometry();
const obj = new THREE.Mesh(geom, Measure.MESH_MATERIAL);
obj.frustumCulled = false;
obj.name = Measure.OBJ_NAME;
obj.userData = {
// 将点存储到userData中
vertices: [],
type: "faces",
}
return obj;
}
// 清除临时信息
clearTemp() {
this.tempPointMarker && this.scene.remove(this.tempPointMarker);
this.tempLine && this.scene.remove(this.tempLine as THREE.Object3D);
this.tempLabel && this.scene.remove(this.tempLabel);
this.tempPointMarker = undefined;
this.tempLine = undefined;
this.tempLabel = undefined;
}
// 完成绘制,不清空结果
complete() {
if (this.isCompleted) return;
useDispatchSignal("sceneGraphChanged")
let clearPoints = false;
let clearPolyline = false;
// 为了测量面积,我们需要制作一个接近的表面,然后添加面积标签
const count = this.pointArray.length;
if (this.mode === MeasureMode.Area && this.polyline) {
if (count > 2) {
const p0 = this.pointArray[0];
this.setFromPoints(this.polyline.geometry, [...this.pointArray, p0]);
// 计算面积
const area = this.calculateArea(this.pointArray);
const label = `${this.numberToString(area)} ${this.getUnitString()}`;
const p = this.getBarycenter(this.pointArray);
const labelObj = this.createLabel(label);
labelObj.position.set(p.x, p.y, p.z);
labelObj.element.innerHTML = label;
this.group.add(labelObj);
} else {
clearPoints = true;
clearPolyline = true;
}
}
if (this.mode === MeasureMode.Distance) {
if (count < 2) {
clearPoints = true;
}
}
if (this.mode === MeasureMode.Angle && this.polyline) {
if (count >= 3) {
const p0 = this.pointArray[0];
const p1 = this.pointArray[1];
const p2 = this.pointArray[2];
const dir0 = new THREE.Vector3(p0.x - p1.x, p0.y - p1.y, p0.z - p1.z).normalize();
const dir1 = this.getAngleBisector(p0, p1, p2);
const dir2 = new THREE.Vector3(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z).normalize();
const angle = this.calculateAngle(p0, p1, p2);
const label = `${this.numberToString(angle)} ${this.getUnitString()}`;
const distance = Math.min(p0.distanceTo(p1), p2.distanceTo(p1));
let d = distance * 0.3; // distance from label to p1
let p = p1.clone().add(new THREE.Vector3(dir1.x * d, dir1.y * d, dir1.z * d)); // label's position
const labelObj = this.createLabel(label);
labelObj.position.set(p.x, p.y, p.z);
labelObj.element.innerHTML = label;
this.group.add(labelObj);
d = distance * 0.2; // 弧到p1的距离
p = p1.clone().add(new THREE.Vector3(dir1.x * d, dir1.y * d, dir1.z * d)); // 圆弧中间位置
const arcP0 = p1.clone().add(new THREE.Vector3(dir0.x * d, dir0.y * d, dir0.z * d));
const arcP2 = p1.clone().add(new THREE.Vector3(dir2.x * d, dir2.y * d, dir2.z * d));
this.curve = this.createCurve(arcP0, p, arcP2);
// 添加弧
this.group.add(this.curve as THREE.Object3D);
} else {
clearPoints = true;
clearPolyline = true;
}
}
// 无效的情况,清除此次的无用的对象
if (clearPoints) {
// 从this.measureGroup移除
this.measureGroup.remove(this.group);
}
if (clearPolyline && this.polyline) {
this.group.remove(this.polyline as THREE.Object3D);
this.polyline = undefined;
}
this.isCompleted = true;
this.domElement.style.cursor = "";
this.clearTemp();
useDispatchSignal("sceneGraphChanged");
this.removeEvent();
// 启用拖拽控制器
this.viewer.modules.dragControl.dragControls.enabled = true;
this.dispatchEvent({type:"complete",object:this.group})
}
// 清除当前group label
clearCurrentLabel() {
for (let i = this.group.children.length - 1; i >=0 ; i--) {
const c = this.group.children[i];
if(c.userData.type === "label"){
this.group.remove(c);
}
}
}
// 获取按下对应三维位置
getClosestIntersection(e: MouseEvent){
const _point = new THREE.Vector2();
_point.x = e.offsetX / this.viewer.renderer.domElement.offsetWidth;
_point.y = e.offsetY / this.viewer.renderer.domElement.offsetHeight;
const intersects = this.viewer.getIntersects(_point);
if (intersects && intersects.length > 0) {
if (intersects.length > 0 && intersects[0].distance < Measure.MAX_DISTANCE) {
return intersects[0].point;
}
}
return null;
}
// 重绘监听鼠标移动
redrawMousemove(e: MouseEvent) {
let point = this.getClosestIntersection(e);
if (!point && this.tempPointMarker) {
this.tempPointMarker.position.set(this.tempPointMarker.userData.point.x, this.tempPointMarker.userData.point.y, this.tempPointMarker.userData.point.z);
return;
}
if (!point || !this.tempPointMarker) return;
// 在鼠标移动时绘制临时点
this.tempPointMarker.position.set(point.x, point.y, point.z);
this.tempPointMarker.userData.point = point;
// 当前点的索引
const cIndex = this.tempPointMarker.userData.pointIndex;
// 移动时绘制临时线
if (this.pointArray.length > 0) {
const line = this.tempLine || this.createLine();
const geom = line.geometry;
let startPoint = this.pointArray[cIndex + 1];
let lastPoint = this.pointArray[cIndex - 1];
// 如果是面积测量,且当前点是最后一个点或者第一个点
// 则需要重置其中一个点,才能有两条线拖动效果
if(this.mode === MeasureMode.Area){
if(!lastPoint){
lastPoint = this.pointArray[this.pointArray.length - 1];
}else if(!startPoint){
startPoint = this.pointArray[0];
}
}
if (startPoint && lastPoint) {
this.setFromPoints(geom, [lastPoint, point, startPoint]);
} else {
this.setFromPoints(geom,[startPoint || lastPoint, point]);
}
}
}
// 重绘完成
redrawComplete() {
if(!this.tempPointMarker) return;
const point = this.tempPointMarker.userData.point;
this.pointArray[this.tempPointMarker.userData.pointIndex] = point;
const count = this.pointArray.length;
if (this.polyline) {
this.setFromPoints(this.polyline.geometry,this.pointArray);
// 如果是距离测量则清除group中已有的label再重新创建
if (this.mode === MeasureMode.Distance && count > 1) {
this.clearCurrentLabel();
// 绘制label
for (let i = 0; i < count - 1; i++) {
const p0 = this.pointArray[i];
const p1 = this.pointArray[i + 1];
if(!p0 || !p1) continue;
const dist = p0.distanceTo(p1);
const label = `${this.numberToString(dist)} ${this.getUnitString()}`;
const position = new THREE.Vector3((p0.x + p1.x) / 2, (p0.y + p1.y) / 2, (p0.z + p1.z) / 2);
const labelObj = this.createLabel(label);
labelObj.position.set(position.x, position.y, position.z);
labelObj.element.innerHTML = label;
this.group.add(labelObj);
}
}
}
// 面积测量
if (this.mode === MeasureMode.Area && this.faces) {
const geom = this.faces.geometry as THREE.BufferGeometry;
const vertices = this.faces.userData.vertices;
// vertices.push(point);
vertices[this.tempPointMarker.userData.pointIndex] = point;
this.setFromPoints(geom,vertices);
const len = vertices.length;
if (len > 2) {
const indexArray:number[] = [];
for (let i = 1; i < len - 1; ++i) {
indexArray.push(0, i, i + 1);
}
geom.setIndex(indexArray);
geom.computeVertexNormals();
}
// 移除原来的label,新的label会在complete中创建
this.clearCurrentLabel();
}
// 角度测量
if (this.mode === MeasureMode.Angle && this.curve) {
// 清除弧跟原来的label 会在complete中创建
this.group.remove(this.curve as THREE.Object3D);
this.clearCurrentLabel();
}
this.complete();
}
mousedown = () => {
this.mouseMoved = false;
};
// 鼠标移动,创建对应的临时点与线
mousemove = (e: MouseEvent) => {
if(this.isCompleted) return;
this.mouseMoved = true;
const point = this.getClosestIntersection(e);
if (!point) {
return;
}
// 在鼠标移动时绘制临时点
if (this.tempPointMarker) {
this.tempPointMarker.position.set(point.x, point.y, point.z);
} else {
this.tempPointMarker = this.createPointMarker(point);
this.scene.add(this.tempPointMarker);
}
// 移动时绘制临时线
if (this.pointArray.length > 0) {
const p0 = this.pointArray[this.pointArray.length - 1]; // 获取最后一个点
const line = this.tempLine || this.createLine();
const geom = line.geometry;
const startPoint = this.pointArray[0];
const lastPoint = this.pointArray[this.pointArray.length - 1];
if (this.mode === MeasureMode.Area) {
this.setFromPoints(geom,[lastPoint, point, startPoint]);
} else {
this.setFromPoints(geom,[lastPoint, point]);
}
if (this.mode === MeasureMode.Distance) {
const dist = p0.distanceTo(point);
const label = `${this.numberToString(dist)} ${this.getUnitString()}`;
const position = new THREE.Vector3((point.x + p0.x) / 2, (point.y + p0.y) / 2, (point.z + p0.z) / 2);
this.addOrUpdateTempLabel(label, position);
}
// tempLine 只需添加到场景一次
if (!this.tempLine) {
this.scene.add(line as THREE.Object3D);
this.tempLine = line;
}
}
useDispatchSignal("sceneGraphChanged")
};
mouseup = (e: MouseEvent) => {
// 如果mouseMoved是true那么它可能在移动而不是点击
if (!this.mouseMoved) {
// 右键点击表示完成绘图操作
if (e.button === 2) {
this.complete();
} else if (e.button === 0) { // 左键点击表示添加点
this.onMouseClicked(e);
}
}
};
onMouseClicked = (e: MouseEvent) => {
if (!this.raycaster || !this.viewer.camera || !this.scene || this.isCompleted) {
return;
}
const point =this.getClosestIntersection(e);
if (!point) {
return;
}
// 双击触发两次点击事件,我们需要避免这里的第二次点击
const now = Date.now();
if (this.lastClickTime && (now - this.lastClickTime < 100)) return;
this.lastClickTime = now;
this.pointArray.push(point);
const count = this.pointArray.length;
const marker = this.createPointMarker(point);
marker.userData.point = point;
marker.userData.pointIndex = count - 1;
this.group.add(marker);
// 把点加入拖拽控制器
this.viewer.modules.dragControl.setDragObjects([marker], "push");
if (this.polyline) {
this.setFromPoints(this.polyline.geometry, this.pointArray);
if (this.tempLabel && count > 1) {
const p0 = this.pointArray[count - 2];
this.tempLabel.position.set((p0.x + point.x) / 2, (p0.y + point.y) / 2, (p0.z + point.z) / 2);
this.group.add(this.tempLabel);
// 创建距离测量线时,此处的 临时label 将作为正式的使用不在this.clearTemp()中清除故置为undefined
this.tempLabel = undefined;
}
}
if (this.mode === MeasureMode.Area && this.faces) {
const geom = this.faces.geometry as THREE.BufferGeometry;
const vertices = this.faces.userData.vertices;
vertices.push(point);
this.setFromPoints(geom,vertices);
const len = vertices.length;
if (len > 2) {
const indexArray:number[] = [];
for (let i = 1; i < len - 1; ++i) {
indexArray.push(0, i, i + 1);
}
geom.setIndex(indexArray);
geom.computeVertexNormals();
this.clearCurrentLabel();
const p0 = this.pointArray[0];
this.setFromPoints(geom, [...this.pointArray, p0]);
// 计算面积
const area = this.calculateArea(this.pointArray);
const label = `${this.numberToString(area)} ${this.getUnitString()}`;
const p = this.getBarycenter(this.pointArray);
const labelObj = this.createLabel(label);
labelObj.position.set(p.x, p.y, p.z);
labelObj.element.innerHTML = label;
this.group.add(labelObj);
}
}
// 创建角度测量时,三个点完成
if (this.mode === MeasureMode.Angle && this.pointArray.length % 3 === 0) {
this.complete();
}
useDispatchSignal("sceneGraphChanged");
};
keydown = (e: KeyboardEvent) => {
if (e.key === "Enter") {
this.complete();
}
};
/**
* 添加或更新临时标签和位置
*/
addOrUpdateTempLabel(label: string, position: THREE.Vector3) {
if (!this.tempLabel) {
this.tempLabel = this.createLabel(label);
this.scene.add(this.tempLabel);
}
this.tempLabel.position.set(position.x, position.y, position.z);
this.tempLabel.element.innerHTML = label;
}
/**
* 创建标签
*/
createLabel(text: string): CSS2DObject {
const div = document.createElement("div");
div.className = 'css2dObjectLabel';
div.innerHTML = text;
div.style.padding = "5px 8px";
div.style.color = "#fff";
div.style.fontSize = "14px";
div.style.position = "absolute";
div.style.backgroundColor = "rgba(25, 25, 25, 0.3)";
div.style.borderRadius = "12px";
div.style.top = "0px";
div.style.left = "0px";
// div.style.pointerEvents = 'none' //避免HTML元素影响场景的鼠标事件
const obj = new CSS2DObject(div);
obj.name = Measure.LABEL_NAME;
obj.userData = {
type: "label"
}
return obj;
}
/**
* 创建圆弧曲线以表示角度
*/
createCurve(p0: THREE.Vector3, p1: THREE.Vector3, p2: THREE.Vector3) {
const curve = new THREE.QuadraticBezierCurve3(p0, p1, p2);
const points = curve.getPoints(4);
const geometry = new THREE.BufferGeometry();
this.setFromPoints(geometry,points)
const obj = new THREE.Line(geometry, Measure.LINE_MATERIAL);
obj.name = Measure.OBJ_NAME;
obj.userData = {
type: "curve"
}
return obj;
}
/**
* 计算区域
* TODO: 对于凹多边形,数值不对,需要修正
* @param points
*/
calculateArea(points: THREE.Vector3[]) {
let area = 0;
for (let i = 0, j = 1, k = 2; k < points.length; j++, k++) {
const a = points[i].distanceTo(points[j]);
const b = points[j].distanceTo(points[k]);
const c = points[k].distanceTo(points[i]);
const p = (a + b + c) / 2;
area += Math.sqrt(p * (p - a) * (p - b) * (p - c));
}
return area;
}
/**
* 以度表示两条直线的夹角
*/
calculateAngle(startPoint: THREE.Vector3, middlePoint: THREE.Vector3, endPoint: THREE.Vector3) {
const p0 = startPoint;
const p1 = middlePoint;
const p2 = endPoint;
const dir0 = new THREE.Vector3(p0.x - p1.x, p0.y - p1.y, p0.z - p1.z);
const dir1 = new THREE.Vector3(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z);
const angle = dir0.angleTo(dir1);
return angle * 180 / Math.PI; // convert to degree
}
/**
* 获取两条线的角平分线
*/
getAngleBisector(startPoint: THREE.Vector3, middlePoint: THREE.Vector3, endPoint: THREE.Vector3): THREE.Vector3 {
const p0 = startPoint;
const p1 = middlePoint;
const p2 = endPoint;
const dir0 = new THREE.Vector3(p0.x - p1.x, p0.y - p1.y, p0.z - p1.z).normalize();
const dir2 = new THREE.Vector3(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z).normalize();
return new THREE.Vector3(dir0.x + dir2.x, dir0.y + dir2.y, dir0.z + dir2.z).normalize(); // the middle direction between dir0 and dir2
}
/**
* 得到点的重心
*/
getBarycenter(points: THREE.Vector3[]): THREE.Vector3 {
const l = points.length;
let x = 0;
let y = 0;
let z = 0;
points.forEach(p => {
x += p.x;
y += p.y;
z += p.z
});
return new THREE.Vector3(x / l, y / l, z / l);
}
/**
* 获取距离、面积或角度的单位字符串
*/
getUnitString() {
if (this.mode === MeasureMode.Distance) return "m";
if (this.mode === MeasureMode.Area) return "m²";
if (this.mode === MeasureMode.Angle) return "°";
return "";
}
/**
* 将数字转换为具有适当分数数字的字符串
*/
numberToString(num: number) {
if (num < 0.0001) {
return num.toString();
}
let fractionDigits = 2;
if (num < 0.01) {
fractionDigits = 4;
} else if (num < 0.1) {
fractionDigits = 3;
}
return num.toFixed(fractionDigits);
}
dispose(): void {
this.clear();
}
}
export {Measure};