【发布时间】:2020-09-23 17:12:50
【问题描述】:
有什么简单的解决方法吗?或者有人有实现的例子吗?
谢谢,乔纳斯
【问题讨论】:
-
为什么不直接calculate the circle 并检查是否包含?
有什么简单的解决方法吗?或者有人有实现的例子吗?
谢谢,乔纳斯
【问题讨论】:
我们打电话
确定 d 是否在 C 中的一种快速方法是计算此行列式:
| ax-dx, ay-dy, (ax-dx)² + (ay-dy)² |
det = | bx-dx, by-dy, (bx-dx)² + (by-dy)² |
| cx-dx, cy-dy, (cx-dx)² + (cy-dy)² |
如果a、b、c按逆时针顺序排列,则:
这里有一个 javascript 函数可以做到这一点:
function inCircle (ax, ay, bx, by, cx, cy, dx, dy) {
let ax_ = ax-dx;
let ay_ = ay-dy;
let bx_ = bx-dx;
let by_ = by-dy;
let cx_ = cx-dx;
let cy_ = cy-dy;
return (
(ax_*ax_ + ay_*ay_) * (bx_*cy_-cx_*by_) -
(bx_*bx_ + by_*by_) * (ax_*cy_-cx_*ay_) +
(cx_*cx_ + cy_*cy_) * (ax_*by_-bx_*ay_)
) > 0;
}
您可能还需要检查您的积分是否按逆时针顺序排列:
function ccw (ax, ay, bx, by, cx, cy) {
return (bx - ax)*(cy - ay)-(cx - ax)*(by - ay) > 0;
}
我没有将 ccw 检查放在 inCircle 函数中,因为您不应该每次都检查它。
此过程不进行任何除法或平方根运算。
您可以在此处查看正在运行的代码:https://titouant.github.io/testTriangle/
【讨论】:
(如果您对不明显/“疯狂”的解决方案感兴趣。)
Delaunay 三角剖分的一个等价性质如下:如果你在三角剖分中建立任何三角形的外接圆,保证不包含三角剖分的任何其他顶点。
Delaunay 三角剖分的另一个等效特性是:它使最小三角角最大化(即在同一组点上的所有三角剖分中使其最大化)。
这为您的测试建议了一个算法:
ABC 建立在原来的 3 点之上。P 位于三角形内,则它肯定在圆内P属于“角”区域之一(见下图中阴影区域),则肯定在圆外P 位于区域 1)考虑四边形 ABCP 的两个三角剖分:原始一个包含原始三角形(红色对角线),替代 一个带有“翻转”对角线(蓝色对角线)
α = min(∠1,∠4,∠5,∠8) 与 β = min(∠2,∠3,∠6,∠7)。α > β),则 P 位于圆外。如果 alternate 三角剖分是 Delaunay 三角剖分 (α < β),则 P 位于圆内。完成。
(一段时间后重温这个答案。)
这个解决方案可能并不像乍一看那样“疯狂”。请注意,为了比较第 5 步和第 6 步的角度,无需计算实际角度值。知道它们的余弦就足够了(即不需要涉及三角函数)。
代码的 C++ 版本
#include <cmath>
#include <array>
#include <algorithm>
struct pnt_t
{
int x, y;
pnt_t ccw90() const
{ return { -y, x }; }
double length() const
{ return std::hypot(x, y); }
pnt_t &operator -=(const pnt_t &rhs)
{
x -= rhs.x;
y -= rhs.y;
return *this;
}
friend pnt_t operator -(const pnt_t &lhs, const pnt_t &rhs)
{ return pnt_t(lhs) -= rhs; }
friend int operator *(const pnt_t &lhs, const pnt_t &rhs)
{ return lhs.x * rhs.x + lhs.y * rhs.y; }
};
int side(const pnt_t &a, const pnt_t &b, const pnt_t &p)
{
int cp = (b - a).ccw90() * (p - a);
return (cp > 0) - (cp < 0);
}
void make_ccw(std::array<pnt_t, 3> &t)
{
if (side(t[0], t[1], t[2]) < 0)
std::swap(t[0], t[1]);
}
double ncos(pnt_t a, const pnt_t &o, pnt_t b)
{
a -= o;
b -= o;
return -(a * b) / (a.length() * b.length());
}
bool inside_circle(std::array<pnt_t, 3> t, const pnt_t &p)
{
make_ccw(t);
std::array<int, 3> s =
{ side(t[0], t[1], p), side(t[1], t[2], p), side(t[2], t[0], p) };
unsigned outside = std::count(std::begin(s), std::end(s), -1);
if (outside != 1)
return outside == 0;
while (s[0] >= 0)
{
std::rotate(std::begin(t), std::begin(t) + 1, std::end(t));
std::rotate(std::begin(s), std::begin(s) + 1, std::end(s));
}
double
min_org = std::min({
ncos(t[0], t[1], t[2]), ncos(t[2], t[0], t[1]),
ncos(t[1], t[0], p), ncos(p, t[1], t[0]) }),
min_alt = std::min({
ncos(t[1], t[2], p), ncos(p, t[2], t[0]),
ncos(t[0], p, t[2]), ncos(t[2], p, t[1]) });
return min_org <= min_alt;
}
以及一些任意选择的三角形和大量随机点的测试
当然,整个事情可以很容易地重新表述,甚至不用提到德劳内三角剖分。从第 4 步开始,此解决方案基于 cyclic quadrilateral 的对角属性,其和必须为 180°。
【讨论】:
ncos 函数,它表示“负余弦”,即简单地取反实际余弦。它在整个[0, Pi) 范围内表现一致:角度越大 - ncos 越大。 (虽然在这个问题中,如果我没有遗漏任何东西,我们将只处理锐角。)。
在this Math SE post of mine 中,我包含了一个方程,它通过计算一个 4×4 行列式来检查四个点是否共圆。通过将该等式转化为不等式,您可以检查其内在性。
如果您想知道不等式必须朝哪个方向发展,请考虑一个非常远的点的情况。在这种情况下,x²+y² 项将支配所有其他项。因此,您可以简单地假设对于所讨论的点,该术语为一,而其他三个为零。然后选择你的不等式的符号,使这个值不满足它,因为这个点肯定在外面,但你想表征里面。
如果数值精度是个问题,this page by Prof. Shewchuk 描述了如何为使用常规双精度浮点数表示的点获得一致的谓词。
【讨论】:
给定 3 个点 (x1,y1),(x2,y2),(x3,y3) 并且您要检查的点在上述 3 个点 (x,y) 定义的圆圈内,您可以执行类似的操作
/**
*
* @param x coordinate of point want to check if inside
* @param y coordinate of point want to check if inside
* @param cx center x
* @param cy center y
* @param r radius of circle
* @return whether (x,y) is inside circle
*/
static boolean g(double x,double y,double cx,double cy,double r){
return Math.sqrt((x-cx)*(x-cx)+(y-cy)*(y-cy))<r;
}
// check if (x,y) is inside circle defined by (x1,y1),(x2,y2),(x3,y3)
static boolean isInside(double x,double y,double x1,double y1,double x2,double y2,double x3,double y3){
double m1 = (x1-x2)/(y2-y1);
double m2 = (x1-x3)/(y3-y1);
double b1 = ((y1+y2)/2) - m1*(x1+x2)/2;
double b2 = ((y1+y3)/2) - m2*(x1+x3)/2;
double xx = (b2-b1)/(m1-m2);
double yy = m1*xx + b1;
return g(x,y,xx,yy,Math.sqrt((xx-x1)*(xx-x1)+(yy-y1)*(yy-y1)));
}
public static void main(String[] args) {
// if (0,1) is inside the circle defined by (0,0),(0,2),(1,1)
System.out.println(isInside(0,1,0,0,0,2,1,1));
}
从3点得到圆心表达式的方法是找到2条线段的2perpendicular bisectors的交点,上面我选择了(x1,y1)-(x2,y2)和(x1,y1)-(x3,y3)。由于您知道每个垂直平分线上的一个点,即(x1+x2)/2 和(x1+x3)/2,并且由于您还分别从上述两条线段知道每个垂直平分线的斜率,即(x1-x2)/(y2-y1) 和(x1-x3)/(y3-y1),因此您可以求解对于它们相交的 (x,y)。
【讨论】: