很多bim轻量化平台应该都有测量这个功能,什么距离啊,角度测量呀,里面比较复杂的还是面积测量这块,其他就比较简单了;
首先面积测量你得考虑是否共面,还得兼容凹凸多边形
先看看效果吧
逻辑很简单:1.只有模型才可以点击,空白的地方不能测量的(其实就是碰撞检测)2.鼠标抬起是确定一个点,鼠标移动是你绘制线 3.三个点确定一个平面 4.把任意多变形拆分成三角形来计算面积
实现逻辑都在这里,画线是用three的MeshLine画的,其实主要看面积测量逻辑就ok了,语言是ts写的,具体细节交流私下
@Injectable()
export class MeasureAreaService {
private sprite = null;
private line_measure = null;
private currentPoint = null;
private currentCount = 0;
private firstPoint = null;
private origin = null;
private downTime = 0;
private upTime = 0;
private onmousemoveSubscription = null;
private onmousedownSubscription = null;
private onmouseupSubscription = null;
private mousewheelSubscription = null;
private needHighLightSubscription = null;
private pointList = new Array < THREE.Vector3 > ();
private keydownEvent = null;
private aParameter = 0;
private bParameter = 0;
private cParameter = 0;
private dParameter = 0;
private measureItems: any[] = [];
private dashedLine;
public close() {
if (this.onmousemoveSubscription) {
this.onmousemoveSubscription.unsubscribe();
}
if (this.onmousedownSubscription) {
this.onmousedownSubscription.unsubscribe();
}
if (this.onmouseupSubscription) {
this.onmouseupSubscription.unsubscribe();
}
if (this.mousewheelSubscription) {
this.mousewheelSubscription.unsubscribe();
}
if (this.needHighLightSubscription) {
this.needHighLightSubscription.unsubscribe();
}
if (this.sprite) {
this.viewerService.highlightScene.remove(this.sprite);
}
if (this.keydownEvent) {
(window as any).removeEventListener('keydown', this.keydownEvent, false);
}
//this.state = OptState.Idle;
this.clear();
//this.rendererService.needHighLightRender.emit(true);
this.measureItemsManagerService.close();
}
public open() {
this.keydownEvent = (event) => {
if (event.keyCode === 27) {
if (this.currentCount >= 3) {
if (this.currentPoint.x == this.firstPoint.x &&
this.currentPoint.y == this.firstPoint.y &&
this.currentPoint.z == this.firstPoint.z) {
//防止闪烁
if (this.measureItemsManagerService.getCurrentMeasureItem()) {
this.measureItemsManagerService.deactiveCurrentMeasureLine();
}
//实例化线条
this.measureItemsManagerService.getCurrentMeasureItem().active = true;
this.measureItemsManagerService.createMeasureItem().active = true;
} else {
this.viewerService.highlightScene.remove(this.measureItemsManagerService.getCurrentMeasureItem().highlightLine.getHighlightLine());
let highlightLine = new HighLightLine(this.measureItemsManagerService.getHighlightMaterial());
let measureItem = this.measureItemsManagerService.createMeasureItem();
measureItem.setHighlightLine(highlightLine);
measureItem.active = true;
this.viewerService.highlightScene.add(measureItem.highlightLine.getHighlightLine());
this.measureItemsManagerService.getCurrentMeasureItem().highlightLine.setFirstVertice(this.firstPoint);
this.measureItemsManagerService.getCurrentMeasureItem().highlightLine.setSecondVertice(this.origin);
this.measureItems[2].highlightLabel.setPosition(this.geometricCenter());
this.measureItems[2].highlightLabel.setLabelText((this.areaPolygon()).toFixed(1) + '㎡');
this.measureItems[2].highlightLabel.updatePosition(this.viewerService.activeCamera);
this.measureItemsManagerService.deactiveCurrentMeasureLine();
this.measureItemsManagerService.getCurrentMeasureItem().active = true;
this.measureItemsManagerService.createMeasureItem().active = true;
}
this.rendererService.needHighLightRender.emit(true);
}
event.stopPropagation();
event.preventDefault();
this.currentCount = 0;
this.origin = null;
this.firstPoint = null;
this.pointList = [];
this.measureItems = [];
this.measureItemsManagerService.removeItem(this.measureItemsManagerService.getCurrentMeasureItem());
}
};
(window as any).addEventListener('keydown', this.keydownEvent, false);
this.needHighLightSubscription = this.rendererService.needHighLightRender.subscribe(() => {
if (this.sprite) {
let scale = this.sprite.position.distanceTo(this.viewerService.activeCamera.position); // virtual_d;
this.sprite.scale.set(scale / 80, scale / 80, scale / 80);
}
});
let spriteMap = new THREE.TextureLoader().load(BimConfig.FILE_PATH_PROD + '/assets/bim/measure/cross1.png');
let spriteMaterial = new THREE.SpriteMaterial({
map: spriteMap,
color: 0xffffff
});
this.sprite = new THREE.Sprite(spriteMaterial);
this.sprite.visible = false;
this.viewerService.highlightScene.add(this.sprite);
let MAX_POINTS = 4;
// geometry
let line_geometry = new THREE.BufferGeometry();
let line_material = new THREE.LineBasicMaterial({
color: 0x0000ff
});
line_material.depthTest = false;
// attributes
let positions = new Float32Array(MAX_POINTS * 3); // 3 vertices per point
line_geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
// draw range
let drawCount = 4; // draw the first 2 points, only
line_geometry.setDrawRange(0, drawCount);
this.line_measure = new THREE.Line(line_geometry, line_material);
this.line_measure.frustumCulled = false;
// this.viewerService.highlightScene.add(this.line_measure);
this.onmousemoveSubscription = this.pickerService.onmousemoveemitter.subscribe((event: MouseEvent) => {
try {
let point = this.raycasterService.getMousePosition(event);
this.raycasterService.getFace3Intersects(point).then((intersection: THREE.Intersection) => {
if (intersection.face) {
this.currentPoint = this.snapperService.testVertices(intersection);
this.updateCross();
this.rendererService.needHighLightRender.emit(true);
let isShow = this.isInPlane(this.currentPoint);
if (isShow) {
if (this.currentCount > 0) {
this.measureItemsManagerService.getCurrentMeasureItem().highlightLine.setSecondVertice(this.currentPoint);
}
if(this.currentCount>3){
}
}
}
// console.log(intersections[0].face);
});
} catch (e) {
//console.log(e);
}
});
this.mousewheelSubscription = this.pickerService.mousewheelemitter.subscribe((event) => {
try {
let point = this.raycasterService.getMousePosition(event);
this.raycasterService.getFace3Intersects(point).then((intersection: THREE.Intersection) => {
if (intersection.face) {
this.currentPoint = intersection.point;
this.updateCross();
this.rendererService.needHighLightRender.emit(true);
} else {
// console.log(intersection);
}
});
} catch (e) {
console.log(e);
}
});
this.onmousedownSubscription = this.pickerService.onmousedownemitter.subscribe(() => {
let date = new Date();
this.downTime = date.getTime();
});
this.onmouseupSubscription = this.pickerService.onmouseupemitter.subscribe((event: MouseEvent) => {
let date = new Date();
this.upTime = date.getTime();
let deltaTime = this.upTime - this.downTime;
if (deltaTime < 250) {
let isShow = this.isInPlane(this.currentPoint);
if (event.button === 0 && isShow) {
if (this.measureItemsManagerService.getCurrentMeasureItem()) {
this.measureItemsManagerService.hideCurrentMeasureItemAuxiliary();
this.measureItemsManagerService.deactiveCurrentMeasureLine();
}
if (this.origin === null) {
this.origin = this.currentPoint.clone();
}
this.firstPoint = this.currentPoint.clone();
this.currentCount++;
this.pointList.push(this.currentPoint.clone());
let highlightLine = new HighLightLine(this.measureItemsManagerService.getHighlightMaterial());
let highlightLabel = new HighLightLabel();
highlightLabel.setPosition(this.firstPoint);
// if (this.measureItemsManagerService.getCurrentMeasureItem()) {
// this.measureItemsManagerService.getCurrentMeasureItem().active = false;
// }
//let measureItem2 = this.measureItemsManagerService.createMeasureItem();
let measureItem = this.measureItemsManagerService.createMeasureItem();
measureItem.setHighlightLine(highlightLine);
if (this.currentCount === 3) {
this.getPlane();
measureItem.setHighlightLabel(highlightLabel);
this.createDashedLine();
this.viewerService.highlightScene.add(this.dashedLine);
}
measureItem.active = true;
this.viewerService.highlightScene.add(measureItem.highlightLine.getHighlightLine());
this.measureItems.push(measureItem);
if (this.currentCount >= 3) {
this.measureItemsManagerService.getCurrentMeasureItem().highlightLine.setFirstVertice(this.firstPoint);
this.measureItemsManagerService.getCurrentMeasureItem().highlightLine.setSecondVertice(this.origin);
//this.measureItemsManagerService.getCurrentMeasureItem().setHighlightLabel(highlightLabel);
this.measureItems[2].highlightLabel.setPosition(this.geometricCenter());
this.measureItems[2].highlightLabel.setLabelText((this.areaPolygon()).toFixed(1) + '㎡');
this.measureItems[2].highlightLabel.updatePosition(this.viewerService.activeCamera);
} else {
measureItem.highlightLine.setFirstVertice(this.firstPoint);
}
if(this.currentCount>3){
this.viewerService.highlightScene.remove(this.dashedLine);
this.createDashedLine();
this.viewerService.highlightScene.add(this.dashedLine);
}
this.rendererService.needHighLightRender.emit(true);
}
}
});
}
//面的方程
private getPlane() {
this.aParameter = ((this.pointList[1].y - this.pointList[0].y) * (this.pointList[2].z - this.pointList[0].z) -
(this.pointList[1].z - this.pointList[0].z) * (this.pointList[2].y - this.pointList[0].y));
this.bParameter = ((this.pointList[1].z - this.pointList[0].z) * (this.pointList[2].x - this.pointList[0].x) -
(this.pointList[1].x - this.pointList[0].x) * (this.pointList[2].z - this.pointList[0].z));
this.cParameter = ((this.pointList[1].x - this.pointList[0].x) * (this.pointList[2].y - this.pointList[0].y) -
(this.pointList[1].y - this.pointList[0].y) * (this.pointList[2].x - this.pointList[0].x));
this.dParameter = (0 - (this.aParameter * this.pointList[0].x + this.bParameter * this.pointList[0].y + this.cParameter * this.pointList[0].z));
}
//判断是否在面内
private isInPlane(point: THREE.Vector3): boolean {
if (this.currentCount < 3) {
return true;
} else {
let a = this.aParameter * point.x + this.bParameter * point.y + this.cParameter * point.z + this.dParameter;
if (a == 0) {
return true;
} else {
return false;
}
}
}
//计算任意多边形面积
private areaPolygon(): number {
let area = 0;
for (let i = 0, j = 1, k = 2; k < this.currentCount; j++, k++) {
let a = this.pointList[i].distanceTo(this.pointList[j]);
let b = this.pointList[j].distanceTo(this.pointList[k]);
let c = this.pointList[k].distanceTo(this.pointList[i]);
let p = (a + b + c) / 2;
area += Math.sqrt(p * (p - a) * (p - b) * (p - b));
}
return area;
}
//计算三维任意多边形面积(慎用)
private area3DPolygon(): number {
let area = 0;
let an, ax, ay, az; //法向及其坐标的abs值
let coord;
let tempa, tempb, tempc;
let normal = new Vector3(this.aParameter, this.bParameter, this.cParameter);
let unitNormal = new Vector3(normal.x / Math.sqrt(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z),
normal.y / Math.sqrt(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z),
normal.z / Math.sqrt(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z)); //面的单位法向量
if (this.currentCount > 3) {
//为投影选择要忽略的最大abs坐标
ax = (unitNormal.x > 0 ? unitNormal.x : -unitNormal.x);
ay = (unitNormal.y > 0 ? unitNormal.y : -unitNormal.y);
az = (unitNormal.z > 0 ? unitNormal.z : -unitNormal.z);
coord = 3;
if (ax > ay) {
if (ax > az)
coord = 1;
} else if (ay > az) {
coord = 2;
}
//计算二维投影面积
for (tempa = 1, tempb = 2, tempc = 0; tempb < this.currentCount; tempa++, tempb++, tempc++) {
switch (coord) {
case 1:
area += (this.pointList[tempa].y * (this.pointList[tempb].z - this.pointList[tempc].z));
continue;
case 2:
area += (this.pointList[tempa].x * (this.pointList[tempb].z - this.pointList[tempc].z));
continue;
case 3:
area += (this.pointList[tempa].x * (this.pointList[tempb].y - this.pointList[tempc].y));
continue;
}
}
// console.log(area);
an = Math.sqrt(ax * ax + ay * ay + az * az);
switch (coord) {
case 1:
area *= (an / (2 * ax));
break;
case 2:
area *= (an / (2 * ay));
break;
case 3:
area *= (an / (2 * az));
}
return area;
} else {
let slideA = this.pointList[0].distanceTo(this.pointList[1]);
let slideB = this.pointList[1].distanceTo(this.pointList[2]);
let slideC = this.pointList[2].distanceTo(this.pointList[0]);
let p = (slideA + slideB + slideC) / 2;
area = (p * (p - slideA) * (p - slideB) * (p - slideC));
return area;
}
}
//几何体中心点
private geometricCenter(): THREE.Vector3 {
let x = 0,
y = 0,
z = 0;
for (let i = 0; i < this.pointList.length; i++) {
x += this.pointList[i].x;
y += this.pointList[i].y;
z += this.pointList[i].z;
}
x = x / this.currentCount;
y = y / this.currentCount;
z = z / this.currentCount;
return new Vector3(x, y, z);
}
//绘制虚线
private createDashedLine(){
let geometry = new THREE.Geometry();
geometry.vertices.push(this.origin, this.firstPoint);
let material = new THREE.LineDashedMaterial({
color:0xff0000,
dashSize:3,
gapSize:0.5,
linewidth:3,
scale: 3
});
this.dashedLine = new THREE.LineSegments(geometry,material);
(this.dashedLine as any).computeLineDistances();
}
private updateCross() {
if (!this.sprite.visible) {
this.sprite.visible = true;
}
this.sprite.position.copy(this.currentPoint);
this.sprite.updateMatrixWorld(true);
}
public clear(){
this.currentCount = 0;
this.origin = null;
this.firstPoint = null;
this.pointList = [];
this.measureItems = [];
this.viewerService.highlightScene.remove(this.dashedLine);
}
}