实现模型周边感应功能

实现模型周边感应功能

需求目标

需求目标

需要在模型移动过程中感知到模型一个“十”字形的范围内的所有模型,如有相交则标注相隔距离,如果没有感知到阻挡模型,则显示到墙体的距离。

方案思路

一开始,当我拿到这个需求的时候,我的第一想法就是在模型的周围做四条射线,如果和其他模型有相交点,则以相交点的位置  做连线。

下一秒就产生了一个问题,如图。

在这样的情况下,标注线应该标注最近的那一个模型,但是却标注到了另外一个模型。

但有一点是母庸质疑的,那就是他需要有四条标注线,要分开来一条一条处理。

接着,拿其中一条边来说,是否应该要找到最近的一个模型?

这两种情况都证明了上面这种思路的问题所在,要考虑方向和模型大小。

那么是否能够在保证当前模型四条边绘制顺序的情况下,寻找右侧的所有模型的 boundingBox 的 4 个点的集合,然后在取集合中里这条边最近的点?

首先解释一下,什么是 boundingBox,当在模型计算的时候,经常会遇到一些多边形,或者模型旋转,为了简化处理,我们将多边形放在一个最小正矩形内,而这个最小正矩形就是 boundingBox。如下图:

如果按照上面  的思路,在不考虑性能的情况下,能否满足要求呢,思考了一下反例又来了。如图:

在上图中,B 在 A 的右侧最近,应该被有标注。但按照之前的算法,B 的 4 个点并不在 A 这一边的感应区域内,所以不会做处理。

所以,按照点的算法来说是不能处理的。如果要用单条线去比较,那么必须用线与线的比较方式来计算。

如果使用线段来算,大概思路有一下内容:

- 找到当前选中模型的boundingBox2D的四条边
- 遍历四条边,查找每一条边的最近模型包含
    - 找到所有模型(可过滤为当前房间的所有模型)
    - 遍历所有模型boundingBox2D
        - 遍历boundingBox2D的四条线段
            - 是与比较线段平行?
            - 是在比较线段的感应范围内?
                - 横向线段比较x范围
                - 纵向线段比较y范围
                - .........

当思路  理到这里的时候,我就已经无法继续下去了,这样的算法显然不是我想要的 ,并且上面的算法,并没有包含房间  墙体的内容,即使这样实现了,也肯定存在其他遗漏掉的内容。

既然线段实现过程还是有很大困难,那么回到  看原型图。发现,按照需求,是需要感知模型  周围四个方向的矩形区域。那么是否应该使用 Polygon 多边形去处理更好呢。

使用多边形处理,首先要解决的内容是:如何判断两个多边形相交?

 显然这样的算法是普遍的,这里不做具体展开,所以在封装了Polygon2D.insterectPolygon(polygon:Polygon2D):Polygon2D

这样的方法后,再理思路,下方附有过程图:

- 找到选中模型的boundingBox2D的四个射线感应多边形(扩大多边形范围)(图1)
- 获取所有需要查找的范围集合,包含模型和墙体(后面统一称为compares)(图2)
- 遍历四个方向的感应多边形
    - 遍历查找范围集合,从compares中查找到最近的一点
        - 过滤未相交的compare(图3)
        - 将所有过滤后的polygon集合展开为所有多边形点的集合=>points
        - 找出points与选中模型最近的点(图4)
    - 找到的最近一点与当前方向一边的垂足
    - 连接垂足与最近点成线段(图5)
    - 平移线段到正确点的位置(图6)
- 绘制找到的四条线段
- 后续对线段长度标注、根据线段方向与设置大小,改变模型位置...

过程图:

图 1

图1

图 2

图2

图 3

图3

图 4

图4

图 5

图5

图 6

图6

最终效果

代码实现

  /**
   * 根据模型获取 模型四个方向的最近射线
   * @param srcModel
   * @return {Line2D[]}
   * Created by yee.wang on 2018/9/7
   */
  public static getNearLines(srcModel: ModelDataBase): Line2D[] {
    const room = ToolRoom.getRoomByPoint(
      srcModel.position,
      Scene3D.getInstance().homePlan.roomLayer.getDatas() as Room[],
    );

    if (!room) {
      return [];
    }

    // 当前模型的4个边界点
    const edgePoints: Vector2D[] = (() => {
      const modelBoundingPoints = ToolModel.getModelPoints(srcModel);

      return [
        // top:
        minBy(modelBoundingPoints, vec => vec.y).clone(),
        // right:
        maxBy(modelBoundingPoints, vec => vec.x).clone(),
        // bottom:
        maxBy(modelBoundingPoints, vec => vec.y).clone(),
        // left:
        minBy(modelBoundingPoints, vec => vec.x).clone(),
      ];
    })();

    // 根据当前模型获取到的感应区域
    const inductionPolygons = (() => {
      const boundingBox = new BoundingBox2D();
      edgePoints.forEach(point => boundingBox.expandByPoint(point));

      return boundingBox.polygon.getEdges().map(line => {
        line = line.translateLeft(1);
        line.setLength(line.length - 1); // 缩小范围,防止边界重叠
        const rayLine = line.translateLeft(2000);

        return new Polygon2D([line.start, line.end, rayLine.end, rayLine.start]);
      });
    })();

    const position2D = new Vector2D(srcModel.position.x, srcModel.position.z);

    // 当前房间的除了当前模型的所有模型
    const models = room.getModelsInRoom() as ModelDataBase[];
    const cubeBoxes = room
      .getCubeBoxesInRoom()
      .map(cubeBox => ToolCubeBox.makeVirtualModel(cubeBox)) as ModelDataBase[];
    const walls = room.walls as Wall[];

    const srcPolygon = srcModel.getPolygon();

    const nearLines: Line2D[] = [];
    inductionPolygons.forEach((polygon, index) => {
      const modelLine = new Line2D(polygon.vertices[0], polygon.vertices[1]);
      const rayLine = modelLine.setLength(2000);

      const intersectResults = [...models, ...walls, ...cubeBoxes].filter(compare => {
        let pos2D: Vector2D = null;
        if (compare.position instanceof Vector3D) {
          pos2D = new Vector2D(compare.position.x, compare.position.z);
        } else {
          pos2D = compare.position.clone();
        }

        // 过滤在线段右侧的物体
        if (rayLine.isRight(pos2D)) {
          return false;
        }

        const comparePolygon = compare.getPolygon();
        // 过滤与自身相交的模型
        if (!!srcPolygon.intersectPolygon(comparePolygon.boundingBox.polygon)) {
          return false;
        }

        return !!polygon.intersectPolygon(comparePolygon);
      });

      const allPoints = intersectResults.reduce((prev, next) => prev.concat(next.getPolygon().vertices), []);

      const nearestPoint = minBy(allPoints, point => position2D.distanceSquared(point));

      if (nearestPoint) {
        const footPoint = nearestPoint.footPoint(rayLine);

        const resultLine = new Line2D(footPoint, nearestPoint);
        const originPoint = edgePoints[index];

        resultLine.translateBy(originPoint.subtract(resultLine.start) as Vector2D);

        nearLines.push(resultLine);
      }
    });

    return nearLines;
  }
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×