判断点是否在形状之内的算法很有用。我们可以用它实现鼠标与图形的交互功能,或是直接使用着色器绘制图形。
为了解释方便,我规定点$P$就是我们需要判断的那个点。
圆和椭圆
如何判断点圆关系是小学二年级的内容,这里不讲了。
根据椭圆的定义($|PF_1|+|PF_2|=2a$),我们可以算出椭圆的焦点$F_1$、$F_2$,进而求出$|PF_1|+|PF_2|$,若小于$2a$,那么可以得出$P$点在椭圆内部。
此外,既然椭圆是圆“压扁”变成的,那么我们就可以把椭圆重新变成圆。
设椭圆的长轴(与$x$轴平行)长为$a$,短轴(与$y$轴平行)长为$b$,$P$点的坐标为$(x,y)$。
把$x$轴的单位长度变为原来的$\dfrac{b}{a}$倍,那么椭圆的长轴和短轴都为$b$,成为了圆,点的坐标则变为$(\dfrac{b}{a}x,y)$。如此,我们就可以用我们熟悉的方法来计算了。
扇形
扇形是圆的一部分,我用以下特征来描述一个扇形:圆心、半径、起始角度$\alpha$和圆心角$\beta$。
首先计算圆心与点$P$的距离,如果大于半径返回假。
接着使用atan2
计算圆心与点$P$的连线与$x$轴的夹角(在$[0, 2\pi)$内),若在$(\alpha,\alpha+\beta)$的范围内返回真。
矩形
这个超级简单,算法如下:
|
|
线段和胶囊体
这里的线段指的是有宽度的线段,它虽然可以作为一个旋转过的矩形来处理,但是会用到一堆三角函数,很烦!这里介绍的是一种只涉及到加减乘除和乘方的算法。
为了描述简便,线段只加粗而不伸长。
◎ 一张简单的示意图
首先,我们需要确定点$P$是否在两条红虚线的内侧,即线段向着其法向量的方向平移扫过的区域。
向量点乘可以做到这一点,若以下表达式为真,则点$P$符合要求:
$$\overrightarrow{AB}\cdot\overrightarrow{AP}\times\overrightarrow{BA}\cdot\overrightarrow{BP}\ge0$$
接下来就很简单了,使用三角形等面积法,算出$S_{\triangle PAB}$中$AB$边对应的高再和线段的宽度比较即可。
这里点$A(x_1,y_1)$、点$B(x_2,y_2)$和点$P(x_3,y_3)$的坐标都是已知的,在学到行列式的时候一定讲到一个公式可以求$\triangle PAB$的面积:
$$ S=\frac{1}{2} \begin{Vmatrix} x_1 & y_1 & 1 \\ x_2 & y_2 & 1 \\ x_3 & y_3 & 1 \\ \end{Vmatrix} $$
这很显然比任何已知的方法都要快。
胶囊体与线段类似,若点$P$在红虚线的外侧(需要确定为哪一侧),则计算它与端点的距离。
多边形
据我所知,判断点是否在多边形里面的算法有两个。
首先是只适用于凸多边形的分离轴定理(SAT),它通常用来判断两个凸多边形是否相交,如果其中的一个多边形变成了单独的一个点,此算法依然是正确的。
另一种算法,简而言之:从$P$点向任意方向画一条射线,若该射线与多边形的边的相交次数为奇数次,该点位于多边形内部;否则该点在多边形外。您可以从该网站获取这个算法的Python代码。
三角形
将三角形从多边形里面拎出来是有原因的:我想到了一个更简单的方法(但不一定快)。
现在回忆一下奔驰定理:点$P$为$\triangle ABC$内任意一点,那么以下等式成立:
$$S_{\triangle PBC}\overrightarrow{AP}+S_{\triangle PAC}\overrightarrow{BP}+S_{\triangle PAB}\overrightarrow{CP}=\overrightarrow{0}$$
据此,我们可以很快地编写算法判断点是否在三角形内。
一些小事
在实现上述功能的时候,代码应该尽量简洁。例如,为了画出倾斜$45^{\circ}$的图形,形状一般会有一个rotation
属性。但是我们在做上述的检测时,将旋转施加到形状上就麻烦了,我们应该让那个点绕着形状的中心反向旋转,而保持形状不变。这样的话运算更快,代码也会简洁。