我想要一种算法来计算4个2D点的凸包。我已经研究了广义问题的算法,但我想知道是否有一个简单的4点解决方案。
答案 0 :(得分:18)
取三个点,确定它们的三角形是顺时针还是逆时针::
triangle_ABC= (A.y-B.y)*C.x + (B.x-A.x)*C.y + (A.x*B.y-B.x*A.y)
对于右手坐标系,如果ABC为逆时针,则该值为正,顺时针为负,如果为共线则为零。但是,以下对于左手坐标系也同样适用,因为方向是相对的。
计算包含第四个点的三个三角形的可比较值:
triangle_ABD= (A.y-B.y)*D.x + (B.x-A.x)*D.y + (A.x*B.y-B.x*A.y)
triangle_BCD= (B.y-C.y)*D.x + (C.x-B.x)*D.y + (B.x*C.y-C.x*B.y)
triangle_CAD= (C.y-A.y)*D.x + (A.x-C.x)*D.y + (C.x*A.y-A.x*C.y)
如果{ABD,BCD,CAD}的所有三个符号都与ABC相同,那么D在ABC中,而船体是三角形ABC。
如果{ABD,BCD,CAD}中的两个具有与ABC相同的符号,并且一个具有相反的符号,那么所有四个点都是极值的,并且船体是四边形ABCD。
如果{ABD,BCD,CAD}中的一个具有与ABC相同的符号,并且两个具有相反的符号,则凸包是具有相同符号的三角形;剩下的一点就在里面。
如果任何三角形值为零,则三个点共线,中间点不是极值。如果所有四个点都是共线的,则所有四个值都应为零,并且船体将是一条线或一条点。在这些情况下要注意数值稳健性问题!
对于ABC为阳性的情况:
ABC ABD BCD CAD hull
------------------------
+ + + + ABC
+ + + - ABCD
+ + - + ABDC
+ + - - ABD
+ - + + ADBC
+ - + - BCD
+ - - + CAD
+ - - - [should not happen]
答案 1 :(得分:3)
或者只使用Jarvis march。
答案 2 :(得分:1)
这是一个特定于4点的特别算法:
如果有4个点可以正确排序,则需要进行一些计算,以避免获得蝴蝶结形状。嗯....看起来有足够的特殊情况来证明使用广义算法。但是,您可以将其调整为比通用算法更快地运行。
答案 3 :(得分:0)
我根据礼品包装算法的原始版本做了a proof of concept fiddle。
在一般情况下效率不高,但只有4分。
function Point (x, y)
{
this.x = x;
this.y = y;
}
Point.prototype.equals = function (p)
{
return this.x == p.x && this.y == p.y;
};
Point.prototype.distance = function (p)
{
return Math.sqrt (Math.pow (this.x-p.x, 2)
+ Math.pow (this.y-p.y, 2));
};
function convex_hull (points)
{
function left_oriented (p1, p2, candidate)
{
var det = (p2.x - p1.x) * (candidate.y - p1.y)
- (candidate.x - p1.x) * (p2.y - p1.y);
if (det > 0) return true; // left-oriented
if (det < 0) return false; // right oriented
// select the farthest point in case of colinearity
return p1.distance (candidate) > p1.distance (p2);
}
var N = points.length;
var hull = [];
// get leftmost point
var min = 0;
for (var i = 1; i != N; i++)
{
if (points[i].y < points[min].y) min = i;
}
hull_point = points[min];
// walk the hull
do
{
hull.push(hull_point);
var end_point = points[0];
for (var i = 1; i != N; i++)
{
if ( hull_point.equals (end_point)
|| left_oriented (hull_point,
end_point,
points[i]))
{
end_point = points[i];
}
}
hull_point = end_point;
}
/*
* must compare coordinates values (and not simply objects)
* for the case of 4 co-incident points
*/
while (!end_point.equals (hull[0]));
return hull;
}
很有趣:)
答案 4 :(得分:0)
我使用查找表编写了一个快速实现来自暴风雨的答案。由于我的应用程序不需要它,因此所有四个点都是共线的情况是不处理。如果点是共线的,则算法将第一个指针点[0]设置为空。如果point [3]是空指针,则船体包含3个点,否则船体有4个点。对于坐标系,船体是逆时针顺序,其中y轴指向顶部,x轴指向右侧。
const char hull4_table[] = {
1,2,3,0,1,2,3,0,1,2,4,3,1,2,3,0,1,2,3,0,1,2,4,0,1,2,3,4,1,2,4,0,1,2,4,0,
1,2,3,0,1,2,3,0,1,4,3,0,1,2,3,0,0,0,0,0,0,0,0,0,2,3,4,0,0,0,0,0,0,0,0,0,
1,4,2,3,1,4,3,0,1,4,3,0,2,3,4,0,0,0,0,0,0,0,0,0,2,3,4,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,2,4,3,0,0,0,0,0,0,0,0,0,1,2,4,0,1,3,4,0,1,2,4,0,1,2,4,0,
0,0,0,0,0,0,0,0,1,4,3,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,4,0,0,0,0,0,0,0,0,0,
1,4,2,0,1,4,2,0,1,4,3,0,1,4,2,0,0,0,0,0,0,0,0,0,2,3,4,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,2,4,3,0,0,0,0,0,0,0,0,0,2,4,3,0,1,3,4,0,1,3,4,0,1,3,2,4,
0,0,0,0,0,0,0,0,2,4,3,0,0,0,0,0,0,0,0,0,1,3,2,0,1,3,4,0,1,3,2,0,1,3,2,0,
1,4,2,0,1,4,2,0,1,4,3,2,1,4,2,0,1,3,2,0,1,3,2,0,1,3,4,2,1,3,2,0,1,3,2,0
};
struct Vec2i {
int x, y;
};
typedef long long int64;
inline int sign(int64 x) {
return (x > 0) - (x < 0);
}
inline int64 orientation(const Vec2i& a, const Vec2i& b, const Vec2i& c) {
return (int64)(b.x - a.x) * (c.y - b.y) - (b.y - a.y) * (c.x - b.x);
}
void convex_hull4(const Vec2i** points) {
const Vec2i* p[5] = {(Vec2i*)0, points[0], points[1], points[2], points[3]};
char abc = (char)1 - sign(orientation(*points[0], *points[1], *points[2]));
char abd = (char)1 - sign(orientation(*points[0], *points[1], *points[3]));
char cad = (char)1 - sign(orientation(*points[2], *points[0], *points[3]));
char bcd = (char)1 - sign(orientation(*points[1], *points[2], *points[3]));
const char* t = hull4_table + (int)4 * (bcd + 3*cad + 9*abd + 27*abc);
points[0] = p[t[0]];
points[1] = p[t[1]];
points[2] = p[t[2]];
points[3] = p[t[3]];
}
答案 5 :(得分:0)
基于@comingstorm回答我创建了Swift解决方案:
func convexHull4(a: Pt, b: Pt, c: Pt, d: Pt) -> [LineSegment]? {
let abc = (a.y-b.y)*c.x + (b.x-a.x)*c.y + (a.x*b.y-b.x*a.y)
let abd = (a.y-b.y)*d.x + (b.x-a.x)*d.y + (a.x*b.y-b.x*a.y)
let bcd = (b.y-c.y)*d.x + (c.x-b.x)*d.y + (b.x*c.y-c.x*b.y)
let cad = (c.y-a.y)*d.x + (a.x-c.x)*d.y + (c.x*a.y-a.x*c.y)
if (abc > 0 && abd > 0 && bcd > 0 && cad > 0) ||
(abc < 0 && abd < 0 && bcd < 0 && cad < 0) {
//abc
return [
LineSegment(p1: a, p2: b),
LineSegment(p1: b, p2: c),
LineSegment(p1: c, p2: a)
]
} else if (abc > 0 && abd > 0 && bcd > 0 && cad < 0) ||
(abc < 0 && abd < 0 && bcd < 0 && cad > 0) {
//abcd
return [
LineSegment(p1: a, p2: b),
LineSegment(p1: b, p2: c),
LineSegment(p1: c, p2: d),
LineSegment(p1: d, p2: a)
]
} else if (abc > 0 && abd > 0 && bcd < 0 && cad > 0) ||
(abc < 0 && abd < 0 && bcd > 0 && cad < 0) {
//abdc
return [
LineSegment(p1: a, p2: b),
LineSegment(p1: b, p2: d),
LineSegment(p1: d, p2: c),
LineSegment(p1: c, p2: a)
]
} else if (abc > 0 && abd < 0 && bcd > 0 && cad > 0) ||
(abc < 0 && abd > 0 && bcd < 0 && cad < 0) {
//acbd
return [
LineSegment(p1: a, p2: c),
LineSegment(p1: c, p2: b),
LineSegment(p1: b, p2: d),
LineSegment(p1: d, p2: a)
]
} else if (abc > 0 && abd > 0 && bcd < 0 && cad < 0) ||
(abc < 0 && abd < 0 && bcd > 0 && cad > 0) {
//abd
return [
LineSegment(p1: a, p2: b),
LineSegment(p1: b, p2: d),
LineSegment(p1: d, p2: a)
]
} else if (abc > 0 && abd < 0 && bcd > 0 && cad < 0) ||
(abc < 0 && abd > 0 && bcd < 0 && cad > 0) {
//bcd
return [
LineSegment(p1: b, p2: c),
LineSegment(p1: c, p2: d),
LineSegment(p1: d, p2: b)
]
} else if (abc > 0 && abd < 0 && bcd < 0 && cad > 0) ||
(abc < 0 && abd > 0 && bcd > 0 && cad < 0) {
//cad
return [
LineSegment(p1: c, p2: a),
LineSegment(p1: a, p2: d),
LineSegment(p1: d, p2: c)
]
}
return nil
}
答案 6 :(得分:0)
基于comestorm的解决方案,我创建了一个C#解决方案来处理退化情况(例如,4个点形成线或点)。
https://gist.github.com/miyu/6e32e993d93d932c419f1f46020e23f0
public static IntVector2[] ConvexHull3(IntVector2 a, IntVector2 b, IntVector2 c) {
var abc = Clockness(a, b, c);
if (abc == Clk.Neither) {
var (s, t) = FindCollinearBounds(a, b, c);
return s == t ? new[] { s } : new[] { s, t };
}
if (abc == Clk.Clockwise) {
return new[] { c, b, a };
}
return new[] { a, b, c };
}
public static (IntVector2, IntVector2) FindCollinearBounds(IntVector2 a, IntVector2 b, IntVector2 c) {
var ab = a.To(b).SquaredNorm2();
var ac = a.To(c).SquaredNorm2();
var bc = b.To(c).SquaredNorm2();
if (ab > ac) {
return ab > bc ? (a, b) : (b, c);
} else {
return ac > bc ? (a, c) : (b, c);
}
}
// See https://stackoverflow.com/questions/2122305/convex-hull-of-4-points
public static IntVector2[] ConvexHull4(IntVector2 a, IntVector2 b, IntVector2 c, IntVector2 d) {
var abc = Clockness(a, b, c);
if (abc == Clk.Neither) {
var (s, t) = FindCollinearBounds(a, b, c);
return ConvexHull3(s, t, d);
}
// make abc ccw
if (abc == Clk.Clockwise) (a, c) = (c, a);
var abd = Clockness(a, b, d);
var bcd = Clockness(b, c, d);
var cad = Clockness(c, a, d);
if (abd == Clk.Neither) {
var (s, t) = FindCollinearBounds(a, b, d);
return ConvexHull3(s, t, c);
}
if (bcd == Clk.Neither) {
var (s, t) = FindCollinearBounds(b, c, d);
return ConvexHull3(s, t, a);
}
if (cad == Clk.Neither) {
var (s, t) = FindCollinearBounds(c, a, d);
return ConvexHull3(s, t, b);
}
if (abd == Clk.CounterClockwise) {
if (bcd == Clk.CounterClockwise && cad == Clk.CounterClockwise) return new[] { a, b, c };
if (bcd == Clk.CounterClockwise && cad == Clk.Clockwise) return new[] { a, b, c, d };
if (bcd == Clk.Clockwise && cad == Clk.CounterClockwise) return new[] { a, b, d, c };
if (bcd == Clk.Clockwise && cad == Clk.Clockwise) return new[] { a, b, d };
throw new InvalidStateException();
} else {
if (bcd == Clk.CounterClockwise && cad == Clk.CounterClockwise) return new[] { a, d, b, c };
if (bcd == Clk.CounterClockwise && cad == Clk.Clockwise) return new[] { d, b, c };
if (bcd == Clk.Clockwise && cad == Clk.CounterClockwise) return new[] { a, d, c };
// 4th state impossible
throw new InvalidStateException();
}
}
您需要为矢量类型实现此样板:
// relative to screen coordinates, so top left origin, x+ right, y+ down.
// clockwise goes from origin to x+ to x+/y+ to y+ to origin, like clockwise if
// you were to stare at a clock on your screen
//
// That is, if you draw an angle between 3 points on your screen, the clockness of that
// direction is the clockness this would return.
public enum Clockness {
Clockwise = -1,
Neither = 0,
CounterClockwise = 1
}
public static Clockness Clockness(IntVector2 a, IntVector2 b, IntVector2 c) => Clockness(b - a, b - c);
public static Clockness Clockness(IntVector2 ba, IntVector2 bc) => Clockness(ba.X, ba.Y, bc.X, bc.Y);
public static Clockness Clockness(cInt ax, cInt ay, cInt bx, cInt by, cInt cx, cInt cy) => Clockness(bx - ax, by - ay, bx - cx, by - cy);
public static Clockness Clockness(cInt bax, cInt bay, cInt bcx, cInt bcy) => (Clockness)Math.Sign(Cross(bax, bay, bcx, bcy));
答案 7 :(得分:0)
这里是对问题的完整分析和有效的 红宝石代码(可最大程度地减少比较次数)
# positions for d:
#
# abc > 0 abc < 0
# (+-+- doesn't exist) (-+-+ doesn't exist)
#
#
# | / ---+ \ --++ | -+++
# | / bdc \ acbd | acd
# | +-++ / \ |
# | abd / ---------A--------B---------
# | / \ --+- |
# | / \ acb |
# | / \ |
# | / \ |
# |/ ---- \ | -++-
# C adcb \ | acdb
# /| \ |
# / | \|
# ++++ / | C
# abcd / | |\
# / | +--+ | \
# / | abdc | \
# / ++-+ | | \
# / abc | | \
# ---------A--------B--------- | \
# +++- / | | \
# bcd / ++-- | +--- | -+-- \
# / adbc | adc | adb \
#
# or as table
#
# ++++ abcd -+++ acd
# +++- bcd -++- acdb
# ++-+ abc -+-+ XXXX
# ++-- adbc -+-- adb
# +-++ abd --++ acbd
# +-+- XXXX --+- acb
# +--+ abdc ---+ bdc
# +--- adc ---- adcb
#
# if there are some collinear points, the hull will be nil (for the moment)
#
def point_point_point_orientation(p, q, r)
(q.x - p.x) * (r.y - q.y) - (q.y - p.y) * (r.x - q.x)
end
def convex_hull_4_points(a, b, c, d)
abc = point_point_point_orientation(a, b, c)
if abc.zero?
# todo
return nil
end
bcd = point_point_point_orientation(b, c, d)
if bcd.zero?
# todo
return nil
end
cda = point_point_point_orientation(c, d, a)
if cda.zero?
# todo
return nil
end
dab = point_point_point_orientation(d, a, b)
if dab.zero?
# todo
return nil
end
if abc.positive?
if bcd.positive?
if cda.positive?
if dab.positive?
[a, b, c, d] # ++++
else
[b, c, d] # +++-
end
else
if dab.positive?
[a, b, c] # ++-+
else
[a, d, b, c] # ++--
end
end
else
if cda.positive?
if dab.positive?
[a, b, d] # +-++
else
raise # +-+-
end
else
if dab.positive?
[a, b, d, c] # +--+
else
[a, d, c] # +---
end
end
end
else
if bcd.positive?
if cda.positive?
if dab.positive?
[a, c, d] # -+++
else
[a, c, d, b] # -++-
end
else
if dab.positive?
raise # -+-+
else
[a, d, b] # -+--
end
end
else
if cda.positive?
if dab.positive?
[a, c, b, d] # --++
else
[a, c, b] # --+-
end
else
if dab.positive?
[b, d, c] # ---+
else
[a, d, c, b] # ----
end
end
end
end
end