BIM 中事件交互的一种架构设计

341 阅读4分钟

BIM 中事件交互的一种架构设计

所有的事件交互其实都是那几个提供的原生事件,所以对其进行抽象,封装,形成一种统一的架构思路,对于遇到的其他功能事件交互都可以按照这种架构设计进行处理。以下以测量交互为例。

封装测量操作器基类

class OpMeasure {
  app: any;

  constructor(app: any) {
    this.app = app;
  }

  onLButtonDown(event: any) {}

  onMButtonDown(event: any) {}

  onRButtonDown(event: any) {}

  onLButtonUp(event: any) {}

  onMButtonUp(event: any) {}

  onRButtonUp(event: any) {}

  onLButtonDownAndMove(event: any) {}

  onRButtonDownAndMove(event: any) {}

  onMButtonDownAndMove(event: any) {}

  onNoButtonDownAndMove(event: any) {}

  dispose() {}
}

这是最基本代码结构,在实际业务中可以增加更多的属性,函数。

实际的测量操作器

距离自动测量操作器

class OpMeasureAutoDistance extends OpMeasure {
  constructor(app: any) {
    super(app);
  }

  onLButtonUp(event: any) {
    // TODO 业务
  }

  onNoButtonDownAndMove(event: any) {
    // TODO 业务
  }

  // 创建测量结果业务,以这个为例介绍实际还有不少业务函数,这里省略。
  createNewMeasure() {}
}

在距离自动测量这个功能中,鼠标交互主要只有两个,1.鼠标左键 up 后,2.鼠标没有按下的鼠标移动,所以只需要重写这两个函数即可。

构件最小间距测量操作器

class OpMeasureMinimumDistance extends OpMeasure {
  constructor(app: any) {
    super(app);
  }

  onLButtonUp(event: any) {
    // TODO 业务
  }

  onLButtonDownAndMove(event: any) {
    // TODO 业务
  }

  onNoButtonDownAndMove(event: any) {
    // TODO 业务
  }

  // 创建测量结果业务,以这个为例介绍实际还有不少业务函数,这里省略。
  createNewMeasure() {}

  dispose() {
    // TODO 特有业务
    super.dispose();
  }
}

在构件最小间距测量这个功能中,鼠标交互主要只有三个,1.鼠标左键 up 后,2.鼠标左键按下的移动,3.鼠标没有按下的鼠标移动,所以只需要重写这三个函数即可。

测量结果封装

测量交互过程中会产生结果,为了代码易于理解和可维护,也可以面向对象的思想进行抽象封装

class MeasureMesh {
  scene: Scene;
  font: Font;
  material: LineBasicMaterial;

  constructor(scene: Scene, font: Font) {
    this.scene = scene;
    this.font = font;

    this.material = new LineBasicMaterial({ color: 0x9933ff });

    // TODO 更多的属性
  }

  setMaterialColor(color: Color) {}

  setVisible(visible: boolean) {}

  dispose() {}

  // TODO 更多的方法
}
class MeasureAutoDistanceMesh extends MeasureMesh {
  constructor(scene: Scene, font: Font) {
    super(scene, font);
  }

  createMesh(start: Vector3, end: Vector3, hasMeasureVal: boolean) {}
}
class MeasureMinimumDistanceMesh extends MeasureMesh {
  constructor(scene: Scene, font: Font) {
    super(scene, font);
  }

  createMesh(start: Vector3, end: Vector3) {}

  dispose() {}
}

操作器的使用

以上的封装是交互细节,还没有涉及到外层的事件绑定,操作器的调用。所以封装以下代码

class MeasureModeGroup {
  app: any;

  private currentOpMeasure: any;
  private measureAutoDistance: OpMeasureAutoDistance;
  private measureMinimumDistance: OpMeasureMinimumDistance;

  private onMouseDownHand: (event: any) => void;
  private onMouseUpHand: (event: any) => void;
  private onMouseMoveHand: (event: any) => void;

  constructor(app: any) {
    this.app = app;

    this.currentOpMeasure = null;

    this.measureAutoDistance = new OpMeasureAutoDistance(app);
    this.measureMinimumDistance = new OpMeasureMinimumDistance(app);

    this.onMouseDownHand = this.onMouseDown.bind(this);
    this.onMouseUpHand = this.onMouseUp.bind(this);
    this.onMouseMoveHand = this.onMouseMove.bind(this);

    this.initEvent();
  }

  dispose() {
    this.disposeEvent();
    this.measureAutoDistance.dispose();
    this.measureMinimumDistance.dispose();
  }

  initEvent() {
    this.app.bimViewer.dom.addEventListener('mousedown', this.onMouseDownHand, false);
    this.app.bimViewer.dom.addEventListener('mouseup', this.onMouseUpHand, false);
    this.app.bimViewer.dom.addEventListener('mousemove', this.onMouseMoveHand, false);
  }

  disposeEvent() {
    this.app.bimViewer.dom.removeEventListener('mousedown', this.onMouseDownHand, false);
    this.app.bimViewer.dom.removeEventListener('mouseup', this.onMouseUpHand, false);
    this.app.bimViewer.dom.removeEventListener('mousemove', this.onMouseMoveHand, false);
  }

  onMouseDown(event: any) {
    if (!this.currentOpMeasure) return;

    if (event.button === 0) {
      this.currentOpMeasure.onLButtonDown(event);
    } else if (event.button === 1) {
      this.currentOpMeasure.onMButtonDown(event);
    } else if (event.button === 2) {
      this.currentOpMeasure.onRButtonDown(event);
    }
  }

  onMouseUp(event: any) {
    if (!this.currentOpMeasure) return;

    if (event.button === 0) {
      this.currentOpMeasure.onLButtonUp(event);
    } else if (event.button === 1) {
      this.currentOpMeasure.onMButtonUp(event);
    } else if (event.button === 2) {
      this.currentOpMeasure.onRButtonUp(event);
    }
  }

  onMouseMove(event: any) {
    if (!this.currentOpMeasure) return;

    if (event.buttons & 1) {
      this.currentOpMeasure.onLButtonDownAndMove(event);
    } else if (event.buttons & 2) {
      this.currentOpMeasure.onRButtonDownAndMove(event);
    } else if (event.buttons & 4) {
      this.currentOpMeasure.onMButtonDownAndMove(event);
    } else {
      this.currentOpMeasure.onNoButtonDownAndMove(event);
    }
  }

  // 设置不同的测量模式
  setCurrentMeasureMode(type: number) {
    this.closeCurrentMeasureMode();

    switch (type) {
      case 1:
        this.currentOpMeasure = this.measureAutoDistance;
        break;
      case 2:
        this.currentOpMeasure = this.measureMinimumDistance;
        break;
    }
  }
}

在我的业务中,进入测量模式后,分别有六种不同的测量,所以为了避免测量功能切换之间,频繁的 new 不同的测量对象,直接 new 所有的测量功能,在切换时,进行不同的指针指向即可。

使用方调用

// 使用方调用示例

// 经过两层封装,使用方调用时不必关心实现细节,只需new出这个对象
this.measureModeGroup = new MeasureModeGroup(this.props.app);

// 切换不同测量时,调用这个方法
this.measureModeGroup.setCurrentMeasureMode(1);
this.measureModeGroup.setCurrentMeasureMode(2);

// 退出
this.measureModeGroup.dispose();

总结

1.使用了面向对象的思想,前端是一个灵活多变的领域,基本啥都能做,编程思想既能用面向过程,也能用面向对象。

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;这个思想比较好理解,符合人的一般思维,但是写好函数式编程也不是简单的事。

面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

所以在前端开发过程中,可以根据业务场景选择使用面向对象还是面向过程。

可以看这篇文章理解 juejin.cn/post/684490… 函数=>构造函数=>对象=>原型与原型链=>类

2.使用了前端事件,这里以 mouse 事件为例,其实用 pointer 事件更好

可以看这篇文章理解 juejin.cn/post/698727… 事件处理的多种绑定方式