第六章 图形交互操作:拾取
在图形系统中,拾取是指从屏幕上选择一个图形对象的过程。这个过程通常是通过鼠标或触摸屏等输入设备来实现的。当用户将鼠标移动到图形对象上时,图形系统会检测到鼠标的位置,然后根据鼠标位置计算该位置上的图形对象,从而实现了拾取操作,这个过程也称之为‘碰撞检测’。
由于 Canvas 不会保存绘制图形的信息,一旦绘制完成用户在浏览器中得到的是一张图片,用户在图片上点击时时不能直接获取到对应的图形对象,所以在绘图时需要缓存已经绘制的图形对象,碰撞检测有以下几种方案:
- 内置API法:通过Canvas渲染上下文对象内置的 API,实现拾取图形
- 几何法:通过几何运算,判断鼠标点击位置附近的对象,实现拾取图形
- 取色法:通过获取点击位置的颜色值,实现图形拾取
1 内置API法
Canvas 渲染上下文对象提供了 isPointInPath()
可以判断一个坐标点是否在路径内, 提供了 isPointInStroke()
判断一个坐标点是否在描边的边上。
我们在 图形系统开发实战课程-基础篇 中曾经讲述了Canvas路径的用法,路径可完成各种常见基本几何图形,和贝塞尔曲线的绘制,下图是 anyGraph内置的一些点的类型。
上面这些点类型均是通过路径绘制出来的,从这张图可以看出通过路径我们不仅仅可以绘制常见的几何图形,如三角形、矩形、圆形、多边形,还可以绘制诸如黑桃、红桃、梅花、花朵等复杂的带有曲线的图形。
如今 Canvas 渲染上下文对象更是提供了 isPointInPath()
可以判断一个坐标点是否在路径内, 提供了 isPointInStroke()
判断一个坐标点是否在描边的边上。利用这两个API我们可以实现几乎所有类型的图形对象拾取。
下面这个示例是在上一篇文章 图形交互:图形交互操作:平移和缩放 中的示例基础上做出了一些改变,在 redraw()
方法中增加了 point
参数,该参数为鼠标当前的位置。当鼠标移动到某个方块内的时候,即 isPointInPath()
返回true
,该方块将会显示为红颜色;当鼠标移动到某个方块边缘的时候,即 isPointInStroke()
返回true
,该方块将会显示为黄颜色。 运行效果如下图所示:
这个示例中采用了 Canvas 渲染上下文对象提供的 isPointInPath()
和 isPointInStroke()
方法判断点是否在路径中或边框之上。其源代码如下:
1 | // 绘图 |
使用该方法需要注意:
- 我们在 第四章 图形基本形状 讲述的图形对象类型包括:点、线、多边形、图像、文本、圆等,其中文本类型和图像类型无法通过该方法判断是否与点碰撞。
- 如果点存在大小时,该方法仅能判断其中心是否在路径内,而无法判断点的边缘是否在路境内。
- 在使用
isPointInPath(x, y)
和isPointInStroke(x, y)
方法判断点是否在路径中或边框之上的时候,这个点(x,y)坐标是指画布中的像素坐标。如果 画布在绘制路径之前进行了变形操作,参数依旧需传入画布变形前的像素坐标值。
2 几何法
几何法是指根据点的位置,采用几何算法判断该位置是否存在图形对象。我们在 第四章 图形基本形状 讲述的图形对象类型包括:点、线、矩形、多边形、图像、文本、圆等等。使用几何法进行判断时,需要分别对这些类型进行判断。
2.1 点与点
判断规则:
判断点与点是否相交,最简单的判断方法是两个点的坐标是否相等,如果相等则判断这两个点相交,而实际绘图的点是有大小的,而且由于浮点误差方面的原因,不能简单判断点与点是否相等,而因改为计算点与点之间的距离,如果这个距离小于容差值,则判断这两个点相交。
实现代码:
1 | /** |
运行效果:
2.2 点与圆
判断规则:
判断点与圆是否相交的规则和判断点与点是否相交的规则类似,通过计算点与圆心之间的距离,如果距离小于等于圆的半径,则判断点在圆内,否则判断点不在圆内。
实现代码:
1 | /** |
运行效果:
2.3 点与线
判断规则:
判断点与线是否相交的规则: 分别计算点与线的两个端点之间的距离,如果距离之和等于线的长度,则可判定点与线相交。
实现代码:
1 | /** |
运行效果:
折线通常包含了多个线段,点与折线是否相交的判断规则是以 点与线段是否相交为基础,逐一判断点与每段线的关系。点只要与其中任何一段线相交,即可判段点与折线相交。
2.4 点与矩形
判断规则:
判断点与矩形是否相交的规则:当点的X坐标大于等于矩形的起点X坐标,小于等于矩形的起点X坐标加上矩形宽度,且点的Y坐标大于等于矩形的起点Y坐标,小于等于矩形的起点Y坐标加上矩形高度时,判断点在矩形内,否则判断点不在矩形内。
实现代码:
1 | /** |
运行效果:
2.5 点与文本
判断规则:
文本绘制的结果是一个矩形区域,因此其判断点与文本是否相交的方法与矩形一样。需要注意的是在绘制文本的时候 水平对齐方式 和 垂直对齐方式 对文本的绘制位置有很大影响。下面的代码可计算绘制文本时的矩形位置和大小:
1 | /** |
运行效果:
2.6 点与图像
判断规则:
图像绘制的结果是也一个矩形区域,因此其判断点与文本是否相交的方法与矩形一样,且其Bounding Box的计算较为简单,这里就不展开叙述了。
2.7 点与多边形
判断点与多边形是否相交的规则是:判断这个点和多边形每条边的位置关系。在一个多条边围成的区域,点在一条边的右侧,这个点可能在多边形内部,也可能在外部。但是如果判断完点和每一条边的左右关系,如果在右边的边是奇数个,那么点就在内部,如果是偶数,那么点就在外部。通过这个规则,就可以判断点是否包含在多边形内。
来源:https://blog.csdn.net/tom_221x/article/details/51861129
那么,如何判断一个点和一条边的位置关系? 这里需要用到一个向量叉积公式。比如,点(x, y),与线 (x1, y1) (x2, y2) 的位置关系。我们先求出两个向量 (x - x1, y - y1) 和 (x2 - x1, y2 - y1)。对这两个向量做叉积的结果是 (x - x1) * (y2 - y1) - (y - y1) * (x2 - x1), 如果结果是0,那么点在线上。如果结果大于0,点在线的右边。如果结果小于0,点在线的左边。 利用这个公式,我们就能判断点是否在多边形的内部还是外部。
实现代码:
1 | /** |
运行效果:
点与多边形是否相交的几何判断方法很重要,2.4节中讲述了点与矩形的判断方法,那种方法在矩形旋转后,就无法判断了。而对于旋转的矩形可将计算矩形旋转后各个角的坐标值,然后将其转换为多边形,采用点与多边形是否相交的办法进行判断。
3. 取色法
取色法的核心思想是在绘制图像的同时,在另一个Canvas中绘制一份与当前图形中各个对象坐标位置和大小均相同,且使用独一无二颜色绘制的图形(缓存图形),同时还需保存一份图形颜色与图形对象的对照表。利用 Canvas
渲染上下文对象提供的像素操作API,在进行拾取操作时,从缓存图形中根据点的位置获取相应的颜色信息,并从颜色对照表中取出对应的图形对象。
下图展示了取色法的核心思想,图形的左侧是要绘制的图形,图形的右侧是缓存的图形,这份缓存图形中中的对象位置和大小和原图完全一样,而颜色却是随机产生的独一无二的颜色。使用 Canvas
渲染上下文对象的 ctx.getImageData(x, y, 1, 1)
方法,可取的指定位置那个像素的颜色,最后通过颜色与图形对象对照表即可取得拾取的对象。
ctx.getImageData()
返回的数据中包含了 data
属性,该属性属于Uint8ClampedArray类型,存储了像素数据,每个像素包含了4个byte的值,分别是该像素对应的红,绿,蓝和透明值(r,g,b,a)。下图显示了imageData中data的数组结构,每个像素均占了data数组中的4个元素,第一个像素存储在data数组的0至3个元素,第二个像素存储在data数组的4至7个元素。
我们这里仅拾取了1个像素值,因此该数组仅包含4个元素。拾取指定位置颜色的代码如下所示:
1 | /** |
对于常见的几何图形,使用这个思路直接在缓存的图形中绘制即可。对于文本和图像,则需要将其转换为矩形,通过矩形的方式绘制在缓存的图形中。下图演示了使用 取色法 从一张包含了各类图形对象的图形中拾取对象的效果。
4. 方案比较
4.1 内置API法
优点:
- 开发简单;
- 识别率高;
缺点:
- 拾取效率低,拾取的时候定义路径导致拾取效率低;
- 无法拾取非路径绘制的图形,例如文本和图像;
- 无法实现根据矩形或多边形进行拾取;
4.2 几何法
优点:
- 扩展性强;
- 识别率高;
缺点:
- 开发复杂,需针对各种类型的图形分别实现其算法;
- 拾取效率一般;
4.3 取色法
优点:
- 开发简单;
- 拾取效率高;
缺点:
- 渲染开销加倍,每次图形渲染时间为内置API法或几何法的两倍;
- 当图形重叠时,仅能识别到最上层的图形对象;
- 无法实现根据矩形或多边形进行拾取;
5. anyGraph 的实现
anyGraph 实现了 几何法 和 取色法 两种图形对象的拾取方案。
5.1 取色法
Graph 对象在初始化时可通过 hitGetColor
选项,指定是否启用‘取色法拾取方案’。 该参数的缺省值为 false
。
下面这段代码启用了‘取色法拾取方案’。
1 | // graph对象 |
下面这段代码演示了取色法的碰撞检测:
1 | // 取色法进行碰撞检测 |
5.2 几何法
anyGraph 图形基本形状类 Geometry对象提供了 contain(point)
方法,各子类均已实现了该方法,通过该方法可判断图形对象与点的位置关系。
下面这段代码演示了几何法的碰撞检测:
1 | // 逐一与数据层中的对象进行碰撞检测 |
“图形系统实战开发-进阶篇 第六章 图形交互操作: 拾取” 的内容讲解到这里就结束了,如果觉得对你有帮助有收获,可以关注我们的官方账号,持续关注更多精彩内容。
本文为“图形开发学院”(graphanywhere.com)网站原创文章,遵循CC BY-NC-ND 4.0版权协议,商业转载请联系作者获得授权,非商业转载请附上原文出处链接及本声明。
0评论