import { EventState, Mesh, MeshBuilder, PickingInfo, PointerEventTypes, PointerInfo, Scene, Vector3 } from "@babylonjs/core"; import { Button, Container, Control, Ellipse, MultiLine, Rectangle, TextBlock } from "@babylonjs/gui"; import { UIManager } from "../controller/ui-manager"; import { BabylonUIStyleTool } from "./babylon-ui-style-tool"; /** * 测量工具 */ export class MeasureTool { static instance: MeasureTool; /** * 当前编辑的测量 */ currentMeasureInfo: MeasureInfo; /** * 当前所处的测量状态 */ currentMeasureType: MeasureType = MeasureType.None; scene: Scene; constructor(scene: Scene) { this.scene = scene; MeasureTool.instance = this; scene.onPointerObservable.add( MeasureTool.instance.onPointerObservable ); } /** * 新建测量 * @param start * @param type */ createMeasureInfo(start: Vector3) { let type: MeasureType = this.currentMeasureType; if (type != MeasureType.None) { this.currentMeasureInfo = new MeasureInfo(start, type); } } /** * 中断测量 */ breakMeasure() { this.currentMeasureInfo = null; } /** * 改变测量类型 * @param type MeasureType.None,表示结束测量 */ changeMeasureType(type: MeasureType) { this.currentMeasureType = type; switch (type) { case MeasureType.None: this.breakMeasure(); break; } } //鼠标交互监听 onPointerObservable(eventData: PointerInfo, eventState: EventState) { let instance = MeasureTool.instance; if (instance.currentMeasureType == MeasureType.None) { return; //非测量状态 } switch (eventData.type) { case PointerEventTypes.POINTERPICK: if (eventData.event.button == 0 && eventData.pickInfo.hit) { if (!instance.isPickTooFar(eventData.pickInfo)) { if (instance.currentMeasureInfo == null) { instance.createMeasureInfo(eventData.pickInfo.pickedPoint); } else { instance.addMeasurePoint(eventData.pickInfo); } } } else if (eventData.event.button == 2) //右键,中断 { instance.breakMeasure(); } break; } } /** * 添加测量点 */ addMeasurePoint(pickInfo: PickingInfo) { console.log("测量", pickInfo); if (this.currentMeasureInfo != null) { this.currentMeasureInfo.addPoint(pickInfo.pickedPoint); } } /** * 点击太远了(点在天空盒上) * @param point */ isPickTooFar(point: PickingInfo) { if (point.pickedMesh != null && point.pickedMesh.name == "skyBox") { return true; } else { return false; } } } /** * 测量状态 */ export enum MeasureType { /** * 未测量 */ None, /** * 直线距离 */ Distance, /** * 高度 */ Height, /** * 面积 */ Area, } /** * 测量信息 */ export class MeasureInfo { /** * 所有点 */ points: MeasurePoint[]; type: MeasureType; /** * ui根节点 */ uiRoot: Container; /** * 圆形起点 */ ell_start: Ellipse; /** * 释放按钮 */ btn_dispose: Button; /** * 是信息 */ txt_info: TextBlock; /** * 信息背景 */ txtbg: Rectangle; mulLine: MultiLine; constructor(start: Vector3, type: MeasureType) { this.points = []; this.type = type; this.mulLine = new MultiLine("mulLine" + type); this.mulLine.lineWidth = 1.5; this.mulLine.color = BabylonUIStyleTool.c_color_3d_blue; this.mulLine.shadowColor = BabylonUIStyleTool.c_color_3d_blue; this.mulLine.shadowBlur = 5; UIManager.Instance.uiRoot.addControl(this.mulLine); this.addPoint(start); this.updateUI(); } /** * 新增节点 */ addPoint(pos: Vector3) { if (this.points.length > 0) { for (let i = 0; i < this.points.length; i++) { this.points[i].changeEnd(false); } if (this.type == MeasureType.Height) { pos.x = this.points[0].pos.x; pos.z = this.points[0].pos.z; } } let point = new MeasurePoint(pos, true, this); this.points.push(point); if (this.points.length > 3 && this.type == MeasureType.Area) { let lastPoint = this.mulLine.getAt(this.points.length - 1); this.mulLine.remove(lastPoint); this.mulLine.add(point.mesh); this.mulLine.add(this.points[0].mesh); } else { this.mulLine.add(point.mesh); } if (this.points.length > 1 && this.type == MeasureType.Height) { //高度只能放两个点 MeasureTool.instance.breakMeasure(); } if (this.points.length == 3 && this.type == MeasureType.Area) { this.mulLine.add(this.points[0].mesh); } //更新显示 this.updateUI(); } /** * 更新UI */ updateUI() { if (this.uiRoot == null) { this.ell_start = new Ellipse("MeasureStart") UIManager.Instance.uiRoot.addControl(this.ell_start); BabylonUIStyleTool.setStyle_size(this.ell_start, "10px", "10px"); this.ell_start.thickness = 2; this.ell_start.color = BabylonUIStyleTool.c_color_3d_blue; this.ell_start.background = BabylonUIStyleTool.c_color_3d_blueBg; this.ell_start.linkWithMesh(this.points[0].mesh); this.uiRoot = new Container("Measure"); this.uiRoot.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT; UIManager.Instance.uiRoot.addControl(this.uiRoot); BabylonUIStyleTool.setStyle_size(this.uiRoot, "100px", "40px"); this.btn_dispose = Button.CreateSimpleButton("dispose", "x"); this.uiRoot.addControl(this.btn_dispose); this.btn_dispose.leftInPixels = 10; BabylonUIStyleTool.setStyle_size(this.btn_dispose, "15px", "15px"); this.btn_dispose.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT; this.btn_dispose.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP; let instance = this; this.btn_dispose.onPointerClickObservable.add(() => { instance.dispose(); }); this.btn_dispose.background = BabylonUIStyleTool.c_color_3d_blueBg; this.btn_dispose.color = BabylonUIStyleTool.c_color_3d_blue; this.txtbg = new Rectangle("txtBG"); this.uiRoot.addControl(this.txtbg); BabylonUIStyleTool.setStyle_size(this.txtbg, "70px", "25px"); this.txtbg.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT; this.txtbg.verticalAlignment = Control.VERTICAL_ALIGNMENT_BOTTOM; this.txtbg.background = BabylonUIStyleTool.c_color_3d_blueBg; this.txtbg.color = BabylonUIStyleTool.c_color_3d_blue; this.txt_info = new TextBlock("txt"); this.uiRoot.addControl(this.txt_info); BabylonUIStyleTool.setStyle_size(this.txt_info, "70px", "25px"); this.txt_info.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT; this.txt_info.verticalAlignment = Control.VERTICAL_ALIGNMENT_BOTTOM; this.txt_info.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT; this.txt_info.color = BabylonUIStyleTool.c_color_3d_blue; this.txt_info.resizeToFit = true; let ell_end = new Ellipse("end") this.uiRoot.addControl(ell_end); BabylonUIStyleTool.setStyle_size(ell_end, "10px", "10px"); ell_end.thickness = 2; ell_end.color = BabylonUIStyleTool.c_color_3d_blue; ell_end.background = BabylonUIStyleTool.c_color_3d_blueBg; ell_end.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT; ell_end.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP; } let info: string = ""; switch (this.type) { case MeasureType.Distance: info = " 长度:" + this.getPointsDistance(this.points).toFixed(1) + "米"; break; case MeasureType.Height: info = " 高度:" + this.getPointsHeight(this.points).toFixed(1) + "米"; break; case MeasureType.Area: info = " 面积:" + this.getPointsArea(this.points).toFixed(1) + "平方米"; break; } this.txt_info.text = info; let instance = this; setTimeout(() => { instance.txtbg.widthInPixels = instance.txt_info.widthInPixels + 3; instance.uiRoot.widthInPixels = instance.txt_info.widthInPixels + 3; let lastPoint = instance.points[instance.points.length - 1]; instance.uiRoot.linkWithMesh(lastPoint.mesh); instance.uiRoot.linkOffsetYInPixels = 20 - 5; instance.uiRoot.linkOffsetXInPixels = instance.uiRoot.widthInPixels * 0.5 - 5; }, (30)); } /** * 获取多个点的长度 * @param points */ getPointsDistance(points: MeasurePoint[]) { let result = 0; if (points != null && points.length > 1) { for (let i = 1; i < points.length; i++) { result += Vector3.Distance(points[i - 1].pos, points[i].pos); } } return result; } /** * 获取高度 * @param points */ getPointsHeight(points: MeasurePoint[]) { let result = 0; if (points != null && points.length > 1) { result = points[points.length - 1].pos.y - points[0].pos.y; result = Math.abs(result); } return result; } /** * 获取面积(在地面的投影面积, xz平面内) * @param points */ getPointsArea(points: MeasurePoint[]) { let result = 0; if (points != null && points.length > 2) { for (let i = 0; i < points.length - 1; i++) { result += (points[i].pos.x * points[i + 1].pos.z - points[i + 1].pos.x * points[i].pos.z); } let num = points.length - 1; result = 0.5 * (result + points[num].pos.x * points[0].pos.z - points[0].pos.x * points[num].pos.z); result = Math.abs(result); } return result; } /** * 释放 */ dispose() { this.ell_start.dispose(); this.uiRoot.dispose(); this.uiRoot = null; this.btn_dispose = null; this.txt_info = null; this.mulLine.dispose(); this.mulLine = null; for (let i = 0; i < this.points.length; i++) { this.points[i].dispose(); } this.points = []; MeasureTool.instance.breakMeasure(); } } /** * 测量点 */ export class MeasurePoint { /** * 终点 */ isEnd: boolean; pos: Vector3; /** * 所属测量信息 */ belongTo: MeasureInfo; /** * 模型网格 */ mesh: Mesh; constructor(pos: Vector3, isEnd: boolean, belongTo: MeasureInfo) { this.pos = pos; this.isEnd = isEnd; this.belongTo = belongTo; this.mesh = MeshBuilder.CreateSphere(belongTo.type.toString(), { segments: 1, diameter: 0.01 }, MeasureTool.instance.scene); this.mesh.position = pos; } /** * 更新终点的显示 * @param isEnd */ changeEnd(isEnd: boolean) { this.isEnd = isEnd; //更新终点显示 } dispose() { if (this.mesh != null) { this.mesh.dispose(); } } }