以下内容转载自Crape的文章《web页面上的旋转矩形碰撞》

作者:Crape

链接:https://juejin.im/post/5eede991e51d45740950c946

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

前言

本文主要是总结一下web页面中的旋转矩形的碰撞检测,碰撞算法本身并不难,只是需要注意web坐标系在计算中的影响。碰撞检测应该是在游戏等场景中很常见且基础的功能,本文记录了在JavaScript API GL遇到了这类碰撞问题的调研和实现的过程。

需求场景

用户在地图上实现MultiLabel文本标注覆盖物时,会由于两个label坐标过近,或者地图的旋转、缩放产生的变化而相互重叠。目前label的背景色均为透明且暂时还不支持配置,文字重叠之后识别度下降很多,就计划先实现label之间的避让功能。检测到两个label碰撞时,根据优先级选择隐藏其中的一个,保证文字的可读性。

确定算法

在JSAPI GL中,label并不是在三维空间中的,而是绘制在屏幕上的,只是会根据用户视角的移动实时计算出label在屏幕坐标中所处的位置,然后在每一帧中进行绘制。label实际上就是一行文字,我们可以把它用一个矩形包围起来,当做整体计算,因为每个字之间的相对位置并不会变,这样一来label的碰撞检测实际上可以转化为二维空间内的矩形碰撞。

一般的横平竖直的矩形检测碰撞很简单,只要想清楚有哪些情况即可,不在这里赘述。但是用户可以对label进行旋转和偏移操作,普通的检测方法就不适用了,如果强行把label用一个大的水平矩形包裹起来再计算,精度损失会很多,所以调研了一下旋转矩形的碰撞检测方法。

比较常见的一种方式是通过分离轴定律(SAT:Separating Axis Theorem)来计算,分离轴定义:两个凸多边形物体,如果能找到一个轴,使得两个物体在该轴上的投影互不重叠,那么这两个物体就没有发生碰撞,这条轴可以称为分离轴。

一般不会遍历所有角度的轴,而是检测垂直于多边形每条边的轴,因为在这些轴上我们可以取到极值。对于矩形来说可以进一步简化,因为一个矩形的4条轴内有2个是重复的,所以只需要检测矩形互相垂直的两条边对应的轴就可以了。

进行判断的具体方式有两种:一是把每个矩形的4个顶点投影到一个轴上,算出该矩形最长的连线距离,判断两个矩形的投影是否重叠;二是将两个矩形的半径距离投影到轴上,然后把两个矩形中心点的连线投影到通一个轴上,判断两个矩形的半径投影之和与中心点连线投影的大小。

本文采用第二种方式计算,首先搞清楚投影的概念,引入向量来进行计算:

我们可以用单位向量来表示垂直于边线的轴,这样一个向量在轴线上的投影长度可以用该向量与投影轴上的单位向量的点积来表示。如上图,A点坐标为(xa, ya),OB为线段OA在x轴上的投影,x轴的单位向量为(1, 0),OA · x轴单位向量 = (xa, ya) · (1, 0) = xa * 1 + ya * 0 = xa。

// 如果用数组[x ,y]表示一个向量,则两个向量的点积结果可以表示为
function dot(vectorA, vectorB) {return Math.abs(vectorA[0] * vectorB[0] + vectorA[1] * vectorB[1]);
}

然后就是如何表示矩形两个轴的单位向量,假设矩形以自身的中心点为原点,逆时针旋转θ,其两条相邻边的轴的单位向量如下图所示:

单位圆的半径为1,所以单位向量OA为 (cosθ, sinθ),另一条边的单位向量与OA垂直,为(-sinθ, cosθ),这两个单位向量的点积为0。但这里有一个非常重要的注意点:web页面中的坐标系与我们平时使用的坐标系不同,x轴正方向不变,y轴的正方向向下。我在最开始实现算法的过程中忽略了这个问题,导致碰撞结果不对,调试了半天才发现原因。在实际计算中,我们所使用的坐标都是web屏幕坐标系下的,轴的正方向与常用的不同,所以两个单位向量应该分别表示为 (cosθ, -sinθ), (sinθ, cosθ),如下图所示:

然后就是计算矩形的半径投影,首先明确下半径投影的概念,可以理解为矩形中心点到一个顶点的向量,在轴上的投影长度。其实就是,矩形在X轴上最远处的交点,数学上意义就是2条检测轴的投影之和。

两个矩形检测的过程中,以其中一个矩形的检测轴为坐标系,投影另外一个矩形的检测轴。如上图所示,蓝色线段为左边矩形的半径投影,黄色线段为右边矩形检测轴。我们需要把右边2条检测轴投影到蓝色线段所在X轴的单位向量(即左边矩形的检测轴单位向量),得到投影比例,然后乘以检测轴长度(即矩形长、宽的一半),可计算出右边矩形的半径投影。红色线段则是两个矩形中心点的连线,同样需要计算它在蓝色线段所在X轴的投影长度,如果中心点连线的投影长度大于两个矩形的半径投影之和,那么在这条轴上两个矩形没有碰撞,否则发生碰撞。

检测最终是否碰撞,需要对四个分离轴都检测一次,在任何一个轴上没有碰撞,则两个矩形就没有碰撞。

实现

实际实现的过程中进行了简单的旋转矩形类,可根据实际业务需求调整,例如添加缩放、偏移等参数

class Rect {constructor(options) {const {center, height, width, angle} = options;this.centerPoint = [center.x, center.y];this.halfHeight = height / 2;this.halfWidth = width / 2;this.setRotation(angle);}getProjectionRadius(axis) { // 计算半径投影      const projectionAxisX = this.dot(axis, this.axisX);const projectionAxisY = this.dot(axis, this.axisY);return this.halfWidth * projectionAxisX + this.halfHeight * projectionAxisY;}dot(vectorA, vectorB) { // 向量点积return Math.abs(vectorA[0] * vectorB[0] + vectorA[1] * vectorB[1]);}setRotation(angle) { // 计算两个检测轴的单位向量const deg = (angle / 180) * Math.PI;this.axisX = [Math.cos(deg), -Math.sin(deg)];this.axisY = [Math.sin(deg), Math.cos(deg)]; return this;}isCollision(check) {const centerDistanceVertor = [this.centerPoint[0] - check.centerPoint[0],this.centerPoint[1] - check.centerPoint[1]];const axes = [ // 两个矩形一共4条检测轴this.axisX,this.axisY,check.axisX,check.axisY];for (let i = 0, len = axes.length; i < len; i++) {if (this.getProjectionRadius(axes[i]) + check.getProjectionRadius(axes[i]) <= this.dot(centerDistanceVertor, axes[i])) {return false; // 任意一条轴没碰上,就是没碰撞}}return true;}
}

使用时每个矩形实例化一个Rect类,然后调用实例上的isCollision方法,参数传入另一个矩形的实例,最后返回一个boolean类型的碰撞结果。

总结

封装的这个类比较简单,没有涉及到里面参数改变的问题,有需要的话可以再完善。实现过程中注意下web坐标系的问题就可以了。矩形应该是最简单的一种,其他凸多边形的检测会复杂一些,有兴趣的话可以自己尝试一下。

本文参考以下blog:
https://blog.csdn.net/tom_221x/article/details/38457757
https://aotu.io/notes/2017/02/16/2d-collision-detection/index.html

画图工具为 GeoGebra sketch

实际效果可以在腾讯位置服务官网的示例中尝试https://lbs.qq.com/webDemoCenter/glAPI/glMarker/labelCollision

腾讯位置服务JSAPI GL实现文本标记的碰撞避让相关推荐

  1. 腾讯地图JSAPI GL实现文本标记的碰撞避让

    以下内容转载自Crape的文章<web页面上的旋转矩形碰撞> 作者:Crape 链接:https://juejin.im/post/5eede991e51d45740950c946 来源: ...

  2. 腾讯地图JavaScript API GL实现文本标记的碰撞避让

    以下内容转载自Crape的文章<web页面上的旋转矩形碰撞> 作者:Crape 链接:https://juejin.im/post/5eede991e51d45740950c946 来源: ...

  3. 腾讯位置服务JavaScript API GL实现文本标记的碰撞避让

    以下内容转载自Crape的文章<web页面上的旋转矩形碰撞> 作者:Crape 链接:https://juejin.im/post/5eede991e51d45740950c946 来源: ...

  4. 腾讯地图api_腾讯位置服务JavaScript API GL正式版发布 免费向开发者开放

    近日,腾讯位置服务推出JavaScript API GL正式版,并免费向开发者开放.JavaScript API GL是新一代基于WebGL实现的高性能三维渲染引擎而封装的一套3D版本地图API,是腾 ...

  5. 发布地图服务属性表_Web3D地图来了!腾讯位置服务正式版发布!

    公众号关注 "菜鸟要飞" 设为 "星标",和10万程序员一起成长! 原文转载自公众号[开发者技术前线] 腾讯位置服务在半年前推出JavaScript API G ...

  6. 【小5聊】腾讯位置服务之小程序简单使用以及显示附近WC步行路线

    [一起来体验腾讯位置服务功能吧] 结合自己小程序业务,集成腾讯位置服务地图功能还是不错的,推荐各位同行提前体验和使用 点击我快速报名体验吧 [前言] 第一次使用腾讯位置服务也算是挺早的,当时是在web ...

  7. 腾讯位置服务---->(小程序简单使用+显示附近WC步行路线)

    体验腾讯位置服务功能,请请点击下方链接 https://lbs.qq.com?lbs_invite=9QNIFL9 1.申请Key: 地址:点击此连接登录注册申请Key 2.设置域名: 小程序管理后台 ...

  8. 腾讯位置服务定位组件实现周边公用厕所远近排序分布图

    前言 地图应用非常广泛,目前地图服务,都提供地图操作.标注.地点搜索.出行规划.地址解析.街景等接口,功能非常丰富.在实际开发过程中,各有优劣.本次基于需求,使用腾讯位置服务作为一个公用厕所位置标注的 ...

  9. vue接入腾讯位置服务之标注定位实战

    [vue接入腾讯位置服务之点击事件],请参考: https://blog.csdn.net/weixin_45653125/article/details/108466146 1.[标注] 添加标注 ...

最新文章

  1. Laravel 传递数据到视图
  2. Django 自定义表名
  3. 参加动画电影《魔比斯环》首映
  4. 我好像明白了如何画序列图了
  5. Java关键字—instanceof
  6. Python开发入门与实战14-基于Extjs的界面
  7. serialVersionUID干嘛用的?
  8. Vue2.0史上最全入坑教程(中)—— 脚手架代码详解
  9. 使用zTree和json构建简单树节点
  10. Visio导出矢量图转eps格式
  11. 计算机主板常见故障检修,主板常见故障检查与维修
  12. 在html中如何做SEO优化
  13. 如何调教ChatGpt 让它听话
  14. 【统计学】统计学基础
  15. js replace使用方法
  16. 基本数据结构——线性结构(有序表)
  17. Java实现验证码生成
  18. 二年级课程表(3月14日-3月18日)
  19. 跨平台移动应用开发引擎CrossApp推出正式版V0.3.0
  20. KVM 介绍 虚拟化简史

热门文章

  1. Pr:更改剪辑的速度和持续时间
  2. linux下ping百度报错未知的名称或服务原因分析及yum安装[Errno 14] curl#6 - “Could not resolve host: mirrors.neusoft.edu.cn;
  3. 客制化键盘编程_【故事汇】客制化键盘讲解
  4. Java-蓝桥算法圆的面积,序列求和
  5. Learning with Recoverable Forgetting阅读心得
  6. python打地鼠游戏教程_Python 0基础开发游戏:打地鼠(详细教程)VS code版本
  7. CentOS7和CentOS8 Asterisk 20.0.0 简单图形化界面9--对接鼎兴FXO网关落地
  8. GC 日志解读与分析:千淘万漉虽辛苦,吹尽狂沙始到金
  9. JS逆向案例 | CryptoJS模块破解加密字符串
  10. 以quot;小刀会“的成败论当今创业成败