threejs+angular 实现面积测量

1 篇文章 0 订阅
0 篇文章 0 订阅

很多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);
  }
}

 

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值