软件项目技术点(25)——提升性能之检测绘制范围

AxeSlide软件项目梳理   canvas绘图系列知识点整理

软件里的一个画面包含很多个元素,但是当缩放到某个局部位置时,需要绘制的元素个数就很少。那么怎么判断某个元素是否需要进行绘制呢?

我们在绘制整个画面时,是进行循环遍历每个元素的,如下判断是否进行绘制的代码:

 1 var elements = this.commonElements.all();//获取所有元素列表
 2 var winInfo = getWindow();//获取画布窗口的宽高值
 3 var viewMinx = 0;
 4 var viewMiny = 0;
 5 var viewMaxx = winInfo.width;
 6 var viewMaxy = winInfo.height;
 7 
 8 var windowViewPoints = new Common.List<Core.Point>();//存储窗口矩形四个顶点的坐标值
 9 windowViewPoints.add(new Core.Point(viewMinx, viewMiny));
10 windowViewPoints.add(new Core.Point(viewMaxx, viewMaxy));
11 windowViewPoints.add(new Core.Point(viewMinx, viewMaxy));
12 windowViewPoints.add(new Core.Point(viewMaxx, viewMiny));
13 //循环绘制每个元素
14 for (var key in elements) {
15     var isDraw = false;
16     var config = elements[key].config;
17     if (config.width * that.scale > 20 || config.height * that.scale > 20) {//首先判断该元素宽高不能都低于20像素,控制元素太小时不绘制
18         var points = elements[key].getAllPoints().viewTruePoints;//获取该元素矩形四个点相对于画布窗口的坐标值
19         isDraw = Common.CollisiionDetector._instance.RectToRectCollisionDec(windowViewPoints, points);//判断窗口矩形和元素矩形是否有交叉重叠,决定是否进行绘制
20         if (isDraw) {
21             elements[key].draw(gotoComplete);
22         }
23     }
24 }

矩形间交叉重叠的检测

下面主要介绍判断窗口矩形和元素矩形是否有交叉重叠RectToRectCollisionDec方法是如何实现的?

下面让大家看一个demo,主要展示如果两个矩形同时在旋转,没有重叠时绘制的颜色是蓝色,如果有重叠绘制成红色

实现原理:

  对于两个多边形,如果存在一个轴,使得两个多边形的在该轴上的投影不重叠,则多边形之间没有碰撞发生。

  在这里所有可能的轴是指垂直于多边形每个边的轴

 第一步:我们需要制定哪个轴作为参考轴

 由于矩形对边相互平行,因此平行的两个边共同拥有一条垂直于它们的轴。因此,对于每个矩形,需要用于检测的轴只有两条。我们只需要检测在另一个矩形在该轴上的投影是否和该轴重叠。为了方便,我们可以直接拿矩形相邻的两个边作为两个轴,然后把这两个轴和另一个矩形的四个边作是否有重叠的比较。

 RectToRectCollisionDec检测两个矩形是否有重叠交叉:

  //传入两个矩形的四个点,检测两个矩形是否有重叠交叉
 RectToRectCollisionDec(r1PointArray: List<Core.Point>, r2PointArray: List<Core.Point>) {
 
     var linesArr1 = this.getFourLines(r1PointArray);//矩形1的四条边
     var linesArr2 = this.getFourLines(r2PointArray);//矩形2的四条边
 
     //每个矩形相邻的两个边作为两个轴,分别和另一个矩形的四个边进行投影重叠的比较,如果有一个返回false代表存在一个轴上的投影不重叠,detectAxisCollision函数来检查一个矩形的四边在指定轴上的投影是否和轴线段本身的投影重叠
     if (this.detectAxisCollision(linesArr2[0], linesArr1) && this.detectAxisCollision(linesArr2[1], linesArr1) && this.detectAxisCollision(linesArr1[0], linesArr2) && this.detectAxisCollision(linesArr1[1], linesArr2)) {
         return true;
     }
     return false;
 
 }

 

  //获取该矩形上的四条边
  private getFourLines(rectPointsArr: List<Core.Point>) {
      var p0 = rectPointsArr.get(0);
      var p1 = rectPointsArr.get(1);
      var p2 = rectPointsArr.get(2);
      var p3 = rectPointsArr.get(3);
      var l1 = [[p0.x, p0.y], [p1.x, p1.y]];
      var l2 = [[p1.x, p1.y], [p2.x, p2.y]];
      var l3 = [[p2.x, p2.y], [p3.x, p3.y]];
      var l4 = [[p3.x, p3.y], [p0.x, p0.y]];
      return [l1, l2, l3, l4];
  }

 

第二步:检查一个矩形的四条边在指定轴上的投影和轴线段本身在向量上的投影是否有重叠,首先需要获取到一个矩形各个边在该轴上的投影和该轴在该向量上的投影

要获得线段在轴上的投影,我们需要分解为计算线段两个顶点在轴上的投影。如何计算点在轴上的投影?

 1 private getTYPoing(p, axis) {//获取点在轴上的投影点
 2     //顶点在轴上的投影
 3     var x = ((p[0] * axis[0] + p[1] * axis[1]) / (axis[0] * axis[0] + axis[1] * axis[1])) * axis[0];
 4     var y = ((p[0] * axis[0] + p[1] * axis[1]) / (axis[0] * axis[0] + axis[1] * axis[1])) * axis[1];
 5     return [x, y];
 6 }
 7 private getLineTYToAxis(line, axis) {//线到轴的投影
 8 
 9     var a = [axis[1][0] - axis[0][0], axis[1][1] - axis[0][1]];//轴向量axis的计算
10     var p0 = line[0];//线的一个顶点0
11     var p1 = line[1];//线的一个顶点1
12     var pt0 = this.getTYPoing(p0, a);
13     var pt1 = this.getTYPoing(p1, a);
14     return [pt0, pt1];
15 }
16   

第三步:计算一条边在指定轴上的投影和轴线段本身在向量上的投影是否有重叠

如何检测线段的重叠?由于这里两个线段是投影在同一个轴向量上,因此他们肯定平行,所以判别方法也比较简单了。方法这里提供一个:线段端点的x轴坐标分别和另一线段的两个端点的x轴坐标相减,得出的两个结果相乘,如果存在结果小于0,则证明线段重叠(当两个线段垂直的时候,使用端点的y轴坐标作判断)

 1 private isLineOverlap(l1, l2) {//判断线段是否重叠
 2 
 3     var l1p1 = l1[0], l1p2 = l1[1], l2p1 = l2[0], l2p2 = l2[1];
 4     if (l1p1[0] != l2p1[0]) {//非垂直X轴的两线段
 5         if ((l1p1[0] - l2p1[0]) * (l1p1[0] - l2p2[0]) < 0 || (l1p2[0] - l2p1[0]) * (l1p2[0] - l2p2[0]) < 0 || (l2p1[0] - l1p1[0]) * (l2p1[0] - l1p2[0]) < 0 || (l2p2[0] - l1p1[0]) * (l2p2[0] - l1p2[0]) < 0) {
 6             return true;
 7         }
 8     }
 9     else {//垂直X轴
10         if ((l1p1[1] - l2p1[1]) * (l1p1[1] - l2p2[1]) < 0 || (l1p2[1] - l2p1[1]) * (l1p2[1] - l2p2[1]) < 0 || (l2p1[1] - l1p1[1]) * (l2p1[1] - l1p2[1]) < 0 || (l2p2[1] - l1p1[1]) * (l2p2[1] - l1p2[1]) < 0) {
11             return true;
12         }
13     }
14 
15     return false;
16 }

第四步:检查一个矩形的四条边在指定轴上的投影和轴线段本身在向量上的投影是否都不重叠,则没有碰撞,否则产生碰撞。就是第一段代码中函数RectToRectCollisionDec中调用的四次函数detectAxisCollision

 

    private detectAxisCollision(axis:any, lineArr:any) {//矩形的轴和另一个矩形要比较的四个边

        for (var i = 0, len = lineArr.length; i < len; i++) {
            var tyLine = this.getLineTYToAxis(lineArr[i], axis);//获取线段在轴上的投影线段 [[a,b],[a1,b1]]
            var tyAxis = this.getLineTYToAxis(axis, axis);

            if (this.isLineOverlap(tyLine, tyAxis)) {
                return true;
            }
        }
        return false;
    }

 

线段交叉的检测

软件有也需要判断线段与矩形是否交叉,线段之间是否交叉?

下面先介绍一个函数

CCW(p1, p2, p3): boolean {
     return (p3.y - p1.y) * (p2.x - p1.x) > (p2.y - p1.y) * (p3.x - p1.x);
}

CCW利用叉积的性质,这是图形学的标准算法。 

两个向量的叉积如果是正的,说明是第一个向量转到第二个向量是逆时针方向;反之亦然 
。他的CCW函数就是干这个的。 
用这个可以判断一个点是在线段的左侧还是右侧。 
然后只要判断是否线段A的两个端点在线段B的两侧并且线段B的两个端点也在线段A的两侧就可以了。

线段与线段是否交叉的方法(线段A的两个端点在线段B的两侧并且线段B的两个端点也在线段A的两侧):

LineToLineCollisionDec(line1Start: Core.Point, line1End: Core.Point, line2Start: Core.Point, line2End: Core.Point) {
    return (this.CCW(line1Start, line2Start, line2End) != this.CCW(line1End, line2Start, line2End)) && (this.CCW(line1Start, line1End, line2Start) != this.CCW(line1Start, line1End, line2End));
}

 

参考资料:碰撞检测作分析:方向包围盒(OBB)碰撞检测  

posted @ 2017-02-11 17:42  方帅  阅读(647)  评论(0编辑  收藏  举报