476 lines
12 KiB
476 lines
12 KiB
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(); |
|
} |
|
} |
|
|
|
} |