Có một cách dễ dàng để xác định nếu một điểm nằm trong một tam giác? Đó là 2D, không phải 3D.
Có một cách dễ dàng để xác định nếu một điểm nằm trong một tam giác? Đó là 2D, không phải 3D.
Câu trả lời:
Nói chung, thuật toán đơn giản nhất (và khá tối ưu) là kiểm tra phía nào của nửa mặt phẳng được tạo bởi các cạnh của điểm.
Đây là một số thông tin chất lượng cao trong chủ đề này trên GameDev , bao gồm các vấn đề về hiệu suất.
Và đây là một số mã để giúp bạn bắt đầu:
float sign (fPoint p1, fPoint p2, fPoint p3)
{
return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
}
bool PointInTriangle (fPoint pt, fPoint v1, fPoint v2, fPoint v3)
{
float d1, d2, d3;
bool has_neg, has_pos;
d1 = sign(pt, v1, v2);
d2 = sign(pt, v2, v3);
d3 = sign(pt, v3, v1);
has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);
return !(has_neg && has_pos);
}
Giải hệ phương trình sau:
p = p0 + (p1 - p0) * s + (p2 - p0) * t
Vấn đề p
là bên trong tam giác nếu 0 <= s <= 1
và 0 <= t <= 1
và s + t <= 1
.
s
, t
Và 1 - s - t
được gọi là tọa độ barycentric của điểm p
.
s + t <= 1
ngụ ý s <= 1
và t <= 1
nếu s >= 0
và t >= 0
.
Tôi đồng ý với Andreas Brinck , tọa độ barycentric rất thuận tiện cho nhiệm vụ này. Lưu ý rằng không cần phải giải hệ phương trình mỗi lần: chỉ cần đánh giá giải pháp phân tích. Sử dụng ký hiệu của Andreas , giải pháp là:
s = 1/(2*Area)*(p0y*p2x - p0x*p2y + (p2y - p0y)*px + (p0x - p2x)*py);
t = 1/(2*Area)*(p0x*p1y - p0y*p1x + (p0y - p1y)*px + (p1x - p0x)*py);
nơi Area
là khu vực (đã ký) của tam giác:
Area = 0.5 *(-p1y*p2x + p0y*(-p1x + p2x) + p0x*(p1y - p2y) + p1x*p2y);
Chỉ cần đánh giá s
, t
và 1-s-t
. Điểm p
nằm trong tam giác khi và chỉ khi tất cả chúng đều dương.
EDIT: Lưu ý rằng biểu thức trên cho khu vực giả định rằng việc đánh số nút tam giác là ngược chiều kim đồng hồ. Nếu đánh số theo chiều kim đồng hồ, biểu thức này sẽ trả về một vùng âm (nhưng với cường độ chính xác). s>0 && t>0 && 1-s-t>0
Tuy nhiên, bản thân phép thử ( ) không phụ thuộc vào hướng đánh số, vì các biểu thức ở trên được nhân với 1/(2*Area)
dấu hiệu cũng thay đổi nếu hướng của nút tam giác thay đổi.
EDIT 2: Để có hiệu quả tính toán tốt hơn, hãy xem bình luận của coproc bên dưới (điều này cho thấy rằng nếu định hướng của các nút tam giác (theo chiều kim đồng hồ hoặc ngược chiều kim đồng hồ), thì việc phân chia theo 2*Area
các biểu thức cho s
và t
có thể được tránh). Xem thêm mã jsfiddle của Perro Azul trong các bình luận dưới câu trả lời của Andreas Brinck .
2*Area
, tức là bằng cách tính toán s´=2*|Area|*s
và t´=2*|Area|*t
(nếu định hướng của các điểm - theo chiều kim đồng hồ hoặc ngược chiều kim đồng hồ - không được biết, dấu hiệu của tất nhiên Area
phải được kiểm tra, nhưng nếu không thì có thể thậm chí không cần phải được tính toán), vì để kiểm tra s>0
nó đủ để kiểm tra s´>0
. Và thay vì kiểm tra 1-s-t>0
nó đủ để kiểm tra s´+t´<2*|Area|
.
p0->p1->p2
là truy cập chiều kim đồng hồ trong Descartes (mà thường là chiều kim đồng hồ trong tọa độ màn hình ), các Area
tính toán bằng phương pháp này sẽ được tích cực.
Tôi đã viết mã này trước một nỗ lực cuối cùng với Google và tìm thấy trang này, vì vậy tôi nghĩ tôi sẽ chia sẻ nó. Nó về cơ bản là một phiên bản tối ưu của câu trả lời Kisielewicz. Tôi cũng đã xem xét phương pháp Barycentric nhưng đánh giá từ bài viết trên Wikipedia tôi có một thời gian khó khăn để xem nó hiệu quả hơn như thế nào (tôi đoán có một số tương đương sâu hơn). Dù sao, thuật toán này có lợi thế là không sử dụng phép chia; một vấn đề tiềm năng là hành vi phát hiện cạnh tùy theo định hướng.
bool intpoint_inside_trigon(intPoint s, intPoint a, intPoint b, intPoint c)
{
int as_x = s.x-a.x;
int as_y = s.y-a.y;
bool s_ab = (b.x-a.x)*as_y-(b.y-a.y)*as_x > 0;
if((c.x-a.x)*as_y-(c.y-a.y)*as_x > 0 == s_ab) return false;
if((c.x-b.x)*(s.y-b.y)-(c.y-b.y)*(s.x-b.x) > 0 != s_ab) return false;
return true;
}
Nói cách khác, ý tưởng là thế này: Điểm s ở bên trái hay bên phải của cả hai đường thẳng AB và AC? Nếu đúng, nó không thể ở bên trong. Nếu sai, ít nhất là bên trong "hình nón" thỏa mãn điều kiện. Bây giờ vì chúng ta biết rằng một điểm bên trong một tam giác (tam giác) phải cùng phía với AB với BC (và cả CA), chúng ta kiểm tra xem chúng có khác nhau không. Nếu họ làm, họ không thể ở bên trong, nếu không thì phải ở trong.
Một số từ khóa trong tính toán là nửa mặt phẳng dòng và định thức (sản phẩm chéo 2x2). Có lẽ một cách sư phạm hơn có lẽ là nghĩ về nó như là một điểm bên trong nếu nó ở cùng một phía (trái hoặc phải) cho mỗi dòng AB, BC và CA. Cách trên có vẻ phù hợp hơn cho một số tối ưu hóa tuy nhiên.
Phiên bản C # của phương pháp barycentric được đăng bởi andreasdr và Perro Azul. Lưu ý rằng tính toán diện tích có thể tránh được nếu s
và t
có dấu hiệu ngược lại. Tôi xác minh hành vi đúng với một bài kiểm tra đơn vị khá kỹ lưỡng.
public static bool PointInTriangle(Point p, Point p0, Point p1, Point p2)
{
var s = p0.Y * p2.X - p0.X * p2.Y + (p2.Y - p0.Y) * p.X + (p0.X - p2.X) * p.Y;
var t = p0.X * p1.Y - p0.Y * p1.X + (p0.Y - p1.Y) * p.X + (p1.X - p0.X) * p.Y;
if ((s < 0) != (t < 0))
return false;
var A = -p1.Y * p2.X + p0.Y * (p2.X - p1.X) + p0.X * (p1.Y - p2.Y) + p1.X * p2.Y;
return A < 0 ?
(s <= 0 && s + t >= A) :
(s >= 0 && s + t <= A);
}
[ sửa ]
chấp nhận sửa đổi đề xuất bởi @Pierre; Xem ý kiến
Phiên bản Java của phương pháp barycentric:
class Triangle {
Triangle(double x1, double y1, double x2, double y2, double x3,
double y3) {
this.x3 = x3;
this.y3 = y3;
y23 = y2 - y3;
x32 = x3 - x2;
y31 = y3 - y1;
x13 = x1 - x3;
det = y23 * x13 - x32 * y31;
minD = Math.min(det, 0);
maxD = Math.max(det, 0);
}
boolean contains(double x, double y) {
double dx = x - x3;
double dy = y - y3;
double a = y23 * dx + x32 * dy;
if (a < minD || a > maxD)
return false;
double b = y31 * dx + x13 * dy;
if (b < minD || b > maxD)
return false;
double c = det - a - b;
if (c < minD || c > maxD)
return false;
return true;
}
private final double x3, y3;
private final double y23, x32, y31, x13;
private final double det, minD, maxD;
}
Đoạn mã trên sẽ hoạt động chính xác với các số nguyên, giả sử không có tràn. Nó cũng sẽ làm việc với các hình tam giác theo chiều kim đồng hồ và ngược chiều kim đồng hồ. Nó sẽ không hoạt động với các tam giác thẳng hàng (nhưng bạn có thể kiểm tra điều đó bằng cách kiểm tra det == 0).
Phiên bản barycentric là nhanh nhất nếu bạn sẽ kiểm tra các điểm khác nhau với cùng một tam giác.
Phiên bản barycentric không đối xứng trong 3 điểm tam giác, do đó, nó có thể ít nhất quán hơn phiên bản nửa mặt phẳng cạnh của Kornel Kisielewicz, do lỗi làm tròn điểm nổi.
Tín dụng: Tôi đã thực hiện mã trên từ bài viết của Wikipedia về tọa độ barycentric.
Một cách đơn giản là:
tìm các vectơ nối điểm với mỗi ba đỉnh của tam giác và tính tổng các góc giữa các vectơ đó. Nếu tổng các góc là 2 * pi thì điểm nằm trong tam giác.
Hai trang web tốt giải thích các lựa chọn thay thế là:
Bằng cách sử dụng giải pháp phân tích cho tọa độ barycentric (được chỉ ra bởi Andreas Brinck ) và:
Người ta có thể giảm thiểu số lượng các hoạt động "tốn kém":
function ptInTriangle(p, p0, p1, p2) {
var dX = p.x-p2.x;
var dY = p.y-p2.y;
var dX21 = p2.x-p1.x;
var dY12 = p1.y-p2.y;
var D = dY12*(p0.x-p2.x) + dX21*(p0.y-p2.y);
var s = dY12*dX + dX21*dY;
var t = (p2.y-p0.y)*dX + (p0.x-p2.x)*dY;
if (D<0) return s<=0 && t<=0 && s+t>=D;
return s>=0 && t>=0 && s+t<=D;
}
Mã có thể được dán trong Perro Azul jsfiddle hoặc thử nó bằng cách nhấp vào "Chạy đoạn mã" bên dưới
var ctx = $("canvas")[0].getContext("2d");
var W = 500;
var H = 500;
var point = { x: W / 2, y: H / 2 };
var triangle = randomTriangle();
$("canvas").click(function(evt) {
point.x = evt.pageX - $(this).offset().left;
point.y = evt.pageY - $(this).offset().top;
test();
});
$("canvas").dblclick(function(evt) {
triangle = randomTriangle();
test();
});
test();
function test() {
var result = ptInTriangle(point, triangle.a, triangle.b, triangle.c);
var info = "point = (" + point.x + "," + point.y + ")\n";
info += "triangle.a = (" + triangle.a.x + "," + triangle.a.y + ")\n";
info += "triangle.b = (" + triangle.b.x + "," + triangle.b.y + ")\n";
info += "triangle.c = (" + triangle.c.x + "," + triangle.c.y + ")\n";
info += "result = " + (result ? "true" : "false");
$("#result").text(info);
render();
}
function ptInTriangle(p, p0, p1, p2) {
var A = 1/2 * (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
var sign = A < 0 ? -1 : 1;
var s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y) * sign;
var t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y) * sign;
return s > 0 && t > 0 && (s + t) < 2 * A * sign;
}
function render() {
ctx.fillStyle = "#CCC";
ctx.fillRect(0, 0, 500, 500);
drawTriangle(triangle.a, triangle.b, triangle.c);
drawPoint(point);
}
function drawTriangle(p0, p1, p2) {
ctx.fillStyle = "#999";
ctx.beginPath();
ctx.moveTo(p0.x, p0.y);
ctx.lineTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.closePath();
ctx.fill();
ctx.fillStyle = "#000";
ctx.font = "12px monospace";
ctx.fillText("1", p0.x, p0.y);
ctx.fillText("2", p1.x, p1.y);
ctx.fillText("3", p2.x, p2.y);
}
function drawPoint(p) {
ctx.fillStyle = "#F00";
ctx.beginPath();
ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
ctx.fill();
}
function rand(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function randomTriangle() {
return {
a: { x: rand(0, W), y: rand(0, H) },
b: { x: rand(0, W), y: rand(0, H) },
c: { x: rand(0, W), y: rand(0, H) }
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<pre>Click: place the point.
Double click: random triangle.</pre>
<pre id="result"></pre>
<canvas width="500" height="500"></canvas>
Dẫn tới:
Điều này so sánh khá tốt với giải pháp Kornel Kisielewicz (25 hồi tưởng, 1 lưu trữ, 15 phép trừ, 6 phép nhân, 5 phép so sánh) và thậm chí có thể tốt hơn nếu cần phát hiện theo chiều kim đồng hồ / ngược chiều kim đồng hồ (mất 6 lần thu hồi, 1 phép cộng, 2 phép trừ , 2 phép nhân và 1 phép so sánh trong chính nó, sử dụng định thức giải pháp phân tích, như được chỉ ra bởi rhgb ).
Những gì tôi làm là tính toán trước ba quy tắc,
trong 3D bằng sản phẩm chéo của vectơ bên và vectơ bình thường.
trong 2D bằng cách hoán đổi các thành phần và phủ định một,
sau đó bên trong / bên ngoài cho bất kỳ một bên là khi một sản phẩm chấm của bên bình thường và vectơ từ đỉnh đến điểm, thay đổi dấu hiệu. Lặp lại cho hai bên khác (hoặc nhiều hơn).
Những lợi ích:
rất nhiều được tính toán trước rất tuyệt vời cho thử nghiệm nhiều điểm trên cùng một tam giác.
từ chối sớm của trường hợp phổ biến bên ngoài nhiều hơn bên trong điểm. (cũng nếu phân phối điểm có trọng số sang một bên, có thể kiểm tra bên đó trước.)
Đây là một triển khai Python hiệu quả :
def PointInsideTriangle2(pt,tri):
'''checks if point pt(2) is inside triangle tri(3x2). @Developer'''
a = 1/(-tri[1,1]*tri[2,0]+tri[0,1]*(-tri[1,0]+tri[2,0])+ \
tri[0,0]*(tri[1,1]-tri[2,1])+tri[1,0]*tri[2,1])
s = a*(tri[2,0]*tri[0,1]-tri[0,0]*tri[2,1]+(tri[2,1]-tri[0,1])*pt[0]+ \
(tri[0,0]-tri[2,0])*pt[1])
if s<0: return False
else: t = a*(tri[0,0]*tri[1,1]-tri[1,0]*tri[0,1]+(tri[0,1]-tri[1,1])*pt[0]+ \
(tri[1,0]-tri[0,0])*pt[1])
return ((t>0) and (1-s-t>0))
và một ví dụ đầu ra:
Nếu bạn đang tìm kiếm tốc độ, đây là một thủ tục có thể giúp bạn.
Sắp xếp các đỉnh tam giác trên tọa độ của chúng. Điều này mất ở ba so sánh tồi tệ nhất. Đặt Y0, Y1, Y2 là ba giá trị được sắp xếp. Bằng cách vẽ ba chiều ngang qua chúng, bạn phân vùng mặt phẳng thành hai nửa mặt phẳng và hai tấm. Gọi Y là tọa độ của điểm truy vấn.
if Y < Y1
if Y <= Y0 -> the point lies in the upper half plane, outside the triangle; you are done
else Y > Y0 -> the point lies in the upper slab
else
if Y >= Y2 -> the point lies in the lower half plane, outside the triangle; you are done
else Y < Y2 -> the point lies in the lower slab
Chi phí so sánh nhiều hơn. Như bạn thấy, từ chối nhanh chóng đạt được cho các điểm bên ngoài "phiến giới hạn".
Tùy chọn, bạn có thể cung cấp một bài kiểm tra trên các abscissas để từ chối nhanh ở bên trái và bên phải (X <= X0' or X >= X2'
). Điều này sẽ thực hiện kiểm tra hộp giới hạn nhanh cùng một lúc, nhưng bạn cũng cần phải sắp xếp trên các abscissas.
Cuối cùng, bạn sẽ cần tính toán dấu của điểm đã cho đối với hai cạnh của tam giác phân định phiến liên quan (trên hoặc dưới). Bài kiểm tra có dạng:
((X - Xi) * (Y - Yj) > (X - Xi) * (Y - Yj)) == ((X - Xi) * (Y - Yk) > (X - Xi) * (Y - Yk))
Các cuộc thảo luận đầy đủ về i, j, k
kết hợp (có sáu trong số chúng, dựa trên kết quả của loại) nằm ngoài phạm vi của câu trả lời này và "để lại như một bài tập cho người đọc"; để có hiệu quả, chúng nên được mã hóa cứng.
Nếu bạn nghĩ rằng giải pháp này phức tạp, hãy quan sát rằng nó chủ yếu liên quan đến các phép so sánh đơn giản (một số trong đó có thể được tính toán trước), cộng với 6 phép trừ và 4 phép nhân trong trường hợp kiểm tra hộp giới hạn không thành công. Chi phí thứ hai khó vượt qua vì trong trường hợp xấu nhất bạn không thể tránh so sánh điểm kiểm tra với hai bên (không có phương pháp nào trong các câu trả lời khác có chi phí thấp hơn, một số làm cho nó tồi tệ hơn, như 15 phép trừ và 6 phép nhân, đôi khi là chia).
CẬP NHẬT: Nhanh hơn với một biến đổi cắt
Như đã giải thích ở trên, bạn có thể nhanh chóng xác định vị trí điểm bên trong một trong bốn dải nằm ngang được phân định bởi ba tọa độ đỉnh, sử dụng hai phép so sánh.
Bạn có thể tùy ý thực hiện một hoặc hai thử nghiệm X bổ sung để kiểm tra độ không chắc chắn đối với hộp giới hạn (đường chấm chấm).
Sau đó xem xét biến đổi "cắt" được đưa ra bởi X'= X - m Y, Y' = Y
, đâu m
là độ dốc DX/DY
cho cạnh cao nhất. Biến đổi này sẽ làm cho cạnh này của tam giác thẳng đứng. Và vì bạn biết bạn đang ở phía bên nào của đường ngang ở giữa, nên đủ để kiểm tra dấu hiệu liên quan đến một phía của tam giác.
Giả sử bạn đã tính trước độ dốc m
, cũng như X'
cho các đỉnh tam giác bị cắt và các hệ số của phương trình của các cạnh như X = m Y + p
, bạn sẽ cần trong trường hợp xấu nhất
X' = X - m Y
;X >< m' Y + p'
đối với phía có liên quan của tam giác bị cắt.Nếu bạn biết tọa độ của ba đỉnh và tọa độ của điểm cụ thể, thì bạn có thể có được diện tích của tam giác hoàn chỉnh. Sau đó, tính diện tích của ba đoạn tam giác (một điểm là điểm đã cho và hai điểm còn lại là hai đỉnh của tam giác). Như vậy, bạn sẽ có được diện tích của ba đoạn tam giác. Nếu tổng của các khu vực này bằng tổng diện tích (mà bạn đã có trước đó), thì điểm phải nằm trong tam giác. Mặt khác, điểm không nằm trong tam giác. Điều này nên làm việc. Nếu có bất kỳ vấn đề, cho tôi biết. Cảm ơn bạn.
Các chức năng khác trong python , nhanh hơn phương thức của Nhà phát triển (ít nhất là đối với tôi) và được truyền cảm hứng từ giải pháp Cédric Dufour :
def ptInTriang(p_test, p0, p1, p2):
dX = p_test[0] - p0[0]
dY = p_test[1] - p0[1]
dX20 = p2[0] - p0[0]
dY20 = p2[1] - p0[1]
dX10 = p1[0] - p0[0]
dY10 = p1[1] - p0[1]
s_p = (dY20*dX) - (dX20*dY)
t_p = (dX10*dY) - (dY10*dX)
D = (dX10*dY20) - (dY10*dX20)
if D > 0:
return ( (s_p >= 0) and (t_p >= 0) and (s_p + t_p) <= D )
else:
return ( (s_p <= 0) and (t_p <= 0) and (s_p + t_p) >= D )
Bạn có thể kiểm tra nó với:
X_size = 64
Y_size = 64
ax_x = np.arange(X_size).astype(np.float32)
ax_y = np.arange(Y_size).astype(np.float32)
coords=np.meshgrid(ax_x,ax_y)
points_unif = (coords[0].reshape(X_size*Y_size,),coords[1].reshape(X_size*Y_size,))
p_test = np.array([0 , 0])
p0 = np.array([22 , 8])
p1 = np.array([12 , 55])
p2 = np.array([7 , 19])
fig = plt.figure(dpi=300)
for i in range(0,X_size*Y_size):
p_test[0] = points_unif[0][i]
p_test[1] = points_unif[1][i]
if ptInTriang(p_test, p0, p1, p2):
plt.plot(p_test[0], p_test[1], '.g')
else:
plt.plot(p_test[0], p_test[1], '.r')
Phải mất rất nhiều âm mưu, nhưng lưới đó được kiểm tra trong 0,0195319652557 giây so với 0,0844349861145 giây mã của Nhà phát triển .
Cuối cùng là mã nhận xét:
# Using barycentric coordintes, any point inside can be described as:
# X = p0.x * r + p1.x * s + p2.x * t
# Y = p0.y * r + p1.y * s + p2.y * t
# with:
# r + s + t = 1 and 0 < r,s,t < 1
# then: r = 1 - s - t
# and then:
# X = p0.x * (1 - s - t) + p1.x * s + p2.x * t
# Y = p0.y * (1 - s - t) + p1.y * s + p2.y * t
#
# X = p0.x + (p1.x-p0.x) * s + (p2.x-p0.x) * t
# Y = p0.y + (p1.y-p0.y) * s + (p2.y-p0.y) * t
#
# X - p0.x = (p1.x-p0.x) * s + (p2.x-p0.x) * t
# Y - p0.y = (p1.y-p0.y) * s + (p2.y-p0.y) * t
#
# we have to solve:
#
# [ X - p0.x ] = [(p1.x-p0.x) (p2.x-p0.x)] * [ s ]
# [ Y - p0.Y ] [(p1.y-p0.y) (p2.y-p0.y)] [ t ]
#
# ---> b = A*x ; ---> x = A^-1 * b
#
# [ s ] = A^-1 * [ X - p0.x ]
# [ t ] [ Y - p0.Y ]
#
# A^-1 = 1/D * adj(A)
#
# The adjugate of A:
#
# adj(A) = [(p2.y-p0.y) -(p2.x-p0.x)]
# [-(p1.y-p0.y) (p1.x-p0.x)]
#
# The determinant of A:
#
# D = (p1.x-p0.x)*(p2.y-p0.y) - (p1.y-p0.y)*(p2.x-p0.x)
#
# Then:
#
# s_p = { (p2.y-p0.y)*(X - p0.x) - (p2.x-p0.x)*(Y - p0.Y) }
# t_p = { (p1.x-p0.x)*(Y - p0.Y) - (p1.y-p0.y)*(X - p0.x) }
#
# s = s_p / D
# t = t_p / D
#
# Recovering r:
#
# r = 1 - (s_p + t_p)/D
#
# Since we only want to know if it is insidem not the barycentric coordinate:
#
# 0 < 1 - (s_p + t_p)/D < 1
# 0 < (s_p + t_p)/D < 1
# 0 < (s_p + t_p) < D
#
# The condition is:
# if D > 0:
# s_p > 0 and t_p > 0 and (s_p + t_p) < D
# else:
# s_p < 0 and t_p < 0 and (s_p + t_p) > D
#
# s_p = { dY20*dX - dX20*dY }
# t_p = { dX10*dY - dY10*dX }
# D = dX10*dY20 - dY10*dX20
ptInTriang([11,45],[45, 45],[45, 45] ,[44, 45])
và nó sẽ trả lại true
mặc dù đó là sai
Vì không có câu trả lời JS,
giải pháp theo chiều kim đồng hồ và ngược chiều kim đồng hồ:
function triangleContains(ax, ay, bx, by, cx, cy, x, y) {
let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)
return det * ((bx - ax) * (y - ay) - (by - ay) * (x - ax)) > 0 &&
det * ((cx - bx) * (y - by) - (cy - by) * (x - bx)) > 0 &&
det * ((ax - cx) * (y - cy) - (ay - cy) * (x - cx)) > 0
}
EDIT: đã có một lỗi đánh máy cho tính toán det ( cy - ay
thay vì cx - ax
), điều này đã được sửa.
https://jsfiddle.net/jniac/rctb3gfL/
function triangleContains(ax, ay, bx, by, cx, cy, x, y) {
let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)
return det * ((bx - ax) * (y - ay) - (by - ay) * (x - ax)) > 0 &&
det * ((cx - bx) * (y - by) - (cy - by) * (x - bx)) > 0 &&
det * ((ax - cx) * (y - cy) - (ay - cy) * (x - cx)) > 0
}
let width = 500, height = 500
// clockwise
let triangle1 = {
A : { x: 10, y: -10 },
C : { x: 20, y: 100 },
B : { x: -90, y: 10 },
color: '#f00',
}
// counter clockwise
let triangle2 = {
A : { x: 20, y: -60 },
B : { x: 90, y: 20 },
C : { x: 20, y: 60 },
color: '#00f',
}
let scale = 2
let mouse = { x: 0, y: 0 }
// DRAW >
let wrapper = document.querySelector('div.wrapper')
wrapper.onmousemove = ({ layerX:x, layerY:y }) => {
x -= width / 2
y -= height / 2
x /= scale
y /= scale
mouse.x = x
mouse.y = y
drawInteractive()
}
function drawArrow(ctx, A, B) {
let v = normalize(sub(B, A), 3)
let I = center(A, B)
let p
p = add(I, rotate(v, 90), v)
ctx.moveTo(p.x, p.y)
ctx.lineTo(I.x, I .y)
p = add(I, rotate(v, -90), v)
ctx.lineTo(p.x, p.y)
}
function drawTriangle(ctx, { A, B, C, color }) {
ctx.beginPath()
ctx.moveTo(A.x, A.y)
ctx.lineTo(B.x, B.y)
ctx.lineTo(C.x, C.y)
ctx.closePath()
ctx.fillStyle = color + '6'
ctx.strokeStyle = color
ctx.fill()
drawArrow(ctx, A, B)
drawArrow(ctx, B, C)
drawArrow(ctx, C, A)
ctx.stroke()
}
function contains({ A, B, C }, P) {
return triangleContains(A.x, A.y, B.x, B.y, C.x, C.y, P.x, P.y)
}
function resetCanvas(canvas) {
canvas.width = width
canvas.height = height
let ctx = canvas.getContext('2d')
ctx.resetTransform()
ctx.clearRect(0, 0, width, height)
ctx.setTransform(scale, 0, 0, scale, width/2, height/2)
}
function drawDots() {
let canvas = document.querySelector('canvas#dots')
let ctx = canvas.getContext('2d')
resetCanvas(canvas)
let count = 1000
for (let i = 0; i < count; i++) {
let x = width * (Math.random() - .5)
let y = width * (Math.random() - .5)
ctx.beginPath()
ctx.ellipse(x, y, 1, 1, 0, 0, 2 * Math.PI)
if (contains(triangle1, { x, y })) {
ctx.fillStyle = '#f00'
} else if (contains(triangle2, { x, y })) {
ctx.fillStyle = '#00f'
} else {
ctx.fillStyle = '#0003'
}
ctx.fill()
}
}
function drawInteractive() {
let canvas = document.querySelector('canvas#interactive')
let ctx = canvas.getContext('2d')
resetCanvas(canvas)
ctx.beginPath()
ctx.moveTo(0, -height/2)
ctx.lineTo(0, height/2)
ctx.moveTo(-width/2, 0)
ctx.lineTo(width/2, 0)
ctx.strokeStyle = '#0003'
ctx.stroke()
drawTriangle(ctx, triangle1)
drawTriangle(ctx, triangle2)
ctx.beginPath()
ctx.ellipse(mouse.x, mouse.y, 4, 4, 0, 0, 2 * Math.PI)
if (contains(triangle1, mouse)) {
ctx.fillStyle = triangle1.color + 'a'
ctx.fill()
} else if (contains(triangle2, mouse)) {
ctx.fillStyle = triangle2.color + 'a'
ctx.fill()
} else {
ctx.strokeStyle = 'black'
ctx.stroke()
}
}
drawDots()
drawInteractive()
// trigo
function add(...points) {
let x = 0, y = 0
for (let point of points) {
x += point.x
y += point.y
}
return { x, y }
}
function center(...points) {
let x = 0, y = 0
for (let point of points) {
x += point.x
y += point.y
}
x /= points.length
y /= points.length
return { x, y }
}
function sub(A, B) {
let x = A.x - B.x
let y = A.y - B.y
return { x, y }
}
function normalize({ x, y }, length = 10) {
let r = length / Math.sqrt(x * x + y * y)
x *= r
y *= r
return { x, y }
}
function rotate({ x, y }, angle = 90) {
let length = Math.sqrt(x * x + y * y)
angle *= Math.PI / 180
angle += Math.atan2(y, x)
x = length * Math.cos(angle)
y = length * Math.sin(angle)
return { x, y }
}
* {
margin: 0;
}
html {
font-family: monospace;
}
body {
padding: 32px;
}
span.red {
color: #f00;
}
span.blue {
color: #00f;
}
canvas {
position: absolute;
border: solid 1px #ddd;
}
<p><span class="red">red triangle</span> is clockwise</p>
<p><span class="blue">blue triangle</span> is couter clockwise</p>
<br>
<div class="wrapper">
<canvas id="dots"></canvas>
<canvas id="interactive"></canvas>
</div>
Tôi đang sử dụng ở đây cùng một phương pháp như được mô tả ở trên: một điểm nằm trong ABC nếu nó nằm tương ứng ở phía "cùng" của mỗi dòng AB, BC, CA.
let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)
), đây là để xác định thứ tự cuộn dây tam giác, vì vậy phương thức này sẽ hoạt động với các tam giác CW & CCW (xem jsFiddle).
let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)
thay vì let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)
vậy điều này đã được sửa, cảm ơn vì đã báo cáo
Tôi chỉ muốn sử dụng một số phép toán vectơ đơn giản để giải thích giải pháp tọa độ barycentric mà Andreas đã đưa ra, nó sẽ dễ hiểu hơn.
(1-s) | v0v2 | / | v0v2 | = tp | v0v1 | / | v0v1 |
ta được 1 - s = tp, sau đó 1 = s + tp. Nếu bất kỳ t> tp nào, trong đó 1 <s + t nằm trên đường gạch ngang kép, thì vectơ nằm ngoài tam giác, bất kỳ t <= tp nào, trong đó 1> = s + t nằm trên đường gạch ngang đơn, vectơ là bên trong tam giác.
Sau đó, nếu chúng ta cho bất kỳ s nào trong [0, 1], t tương ứng phải đáp ứng 1> = s + t, cho vectơ bên trong tam giác.
Vậy cuối cùng ta được v = s * v02 + t * v01, v nằm trong tam giác với điều kiện s, t, s + t thuộc về [0, 1]. Sau đó dịch sang điểm, chúng tôi có
p - p0 = s * (p1 - p0) + t * (p2 - p0), với s, t, s + t trong [0, 1]
tương tự như giải pháp của Andreas để giải hệ phương trình p = p0 + s * (p1 - p0) + t * (p2 - p0), với s, t, s + t thuộc về [0, 1].
Đây là một giải pháp trong python có hiệu quả, được ghi lại và chứa ba điều không mong muốn. Đó là chất lượng cấp chuyên nghiệp và sẵn sàng để được đưa vào dự án của bạn dưới dạng một mô-đun.
import unittest
###############################################################################
def point_in_triangle(point, triangle):
"""Returns True if the point is inside the triangle
and returns False if it falls outside.
- The argument *point* is a tuple with two elements
containing the X,Y coordinates respectively.
- The argument *triangle* is a tuple with three elements each
element consisting of a tuple of X,Y coordinates.
It works like this:
Walk clockwise or counterclockwise around the triangle
and project the point onto the segment we are crossing
by using the dot product.
Finally, check that the vector created is on the same side
for each of the triangle's segments.
"""
# Unpack arguments
x, y = point
ax, ay = triangle[0]
bx, by = triangle[1]
cx, cy = triangle[2]
# Segment A to B
side_1 = (x - bx) * (ay - by) - (ax - bx) * (y - by)
# Segment B to C
side_2 = (x - cx) * (by - cy) - (bx - cx) * (y - cy)
# Segment C to A
side_3 = (x - ax) * (cy - ay) - (cx - ax) * (y - ay)
# All the signs must be positive or all negative
return (side_1 < 0.0) == (side_2 < 0.0) == (side_3 < 0.0)
###############################################################################
class TestPointInTriangle(unittest.TestCase):
triangle = ((22 , 8),
(12 , 55),
(7 , 19))
def test_inside(self):
point = (15, 20)
self.assertTrue(point_in_triangle(point, self.triangle))
def test_outside(self):
point = (1, 7)
self.assertFalse(point_in_triangle(point, self.triangle))
def test_border_case(self):
"""If the point is exactly on one of the triangle's edges,
we consider it is inside."""
point = (7, 19)
self.assertTrue(point_in_triangle(point, self.triangle))
###############################################################################
if __name__ == "__main__":
suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestPointInTriangle)
unittest.TextTestRunner().run(suite)
Có một thử nghiệm đồ họa tùy chọn bổ sung cho thuật toán ở trên để xác nhận tính hợp lệ của nó:
import random
from matplotlib import pyplot
from triangle_test import point_in_triangle
###############################################################################
# The area #
size_x = 64
size_y = 64
# The triangle #
triangle = ((22 , 8),
(12 , 55),
(7 , 19))
# Number of random points #
count_points = 10000
# Prepare the figure #
figure = pyplot.figure()
axes = figure.add_subplot(111, aspect='equal')
axes.set_title("Test the 'point_in_triangle' function")
axes.set_xlim(0, size_x)
axes.set_ylim(0, size_y)
# Plot the triangle #
from matplotlib.patches import Polygon
axes.add_patch(Polygon(triangle, linewidth=1, edgecolor='k', facecolor='none'))
# Plot the points #
for i in range(count_points):
x = random.uniform(0, size_x)
y = random.uniform(0, size_y)
if point_in_triangle((x,y), triangle): pyplot.plot(x, y, '.g')
else: pyplot.plot(x, y, '.b')
# Save it #
figure.savefig("point_in_triangle.pdf")
Sản xuất đồ họa sau:
Có các điều kiện cạnh pesky trong đó một điểm chính xác trên cạnh chung của hai hình tam giác liền kề. Điểm không thể ở cả hai hoặc cả hai hình tam giác. Bạn cần một cách tùy ý nhưng nhất quán để gán điểm. Ví dụ, vẽ một đường ngang qua điểm. Nếu đường thẳng giao với phía bên kia của tam giác bên phải, điểm được xử lý như thể nó nằm trong tam giác. Nếu giao điểm ở bên trái, điểm nằm bên ngoài.
Nếu đường thẳng nằm trên điểm nằm ngang, hãy sử dụng phía trên / bên dưới.
Nếu điểm nằm trên đỉnh chung của nhiều tam giác, hãy sử dụng tam giác có tâm là điểm tạo thành góc nhỏ nhất.
Thú vị hơn: ba điểm có thể nằm trên một đường thẳng (không độ), ví dụ (0,0) - (0,10) - (0,5). Trong thuật toán tam giác, "tai" (0,10) phải được loại bỏ, "tam giác" được tạo ra là trường hợp suy biến của một đường thẳng.
Đây là khái niệm đơn giản nhất để xác định xem một điểm nằm trong hay ngoài tam giác hay trên một nhánh của tam giác.
Xác định một điểm nằm trong tam giác bằng các định thức:
Mã làm việc đơn giản nhất:
#-*- coding: utf-8 -*-
import numpy as np
tri_points = [(1,1),(2,3),(3,1)]
def pisinTri(point,tri_points):
Dx , Dy = point
A,B,C = tri_points
Ax, Ay = A
Bx, By = B
Cx, Cy = C
M1 = np.array([ [Dx - Bx, Dy - By, 0],
[Ax - Bx, Ay - By, 0],
[1 , 1 , 1]
])
M2 = np.array([ [Dx - Ax, Dy - Ay, 0],
[Cx - Ax, Cy - Ay, 0],
[1 , 1 , 1]
])
M3 = np.array([ [Dx - Cx, Dy - Cy, 0],
[Bx - Cx, By - Cy, 0],
[1 , 1 , 1]
])
M1 = np.linalg.det(M1)
M2 = np.linalg.det(M2)
M3 = np.linalg.det(M3)
print(M1,M2,M3)
if(M1 == 0 or M2 == 0 or M3 ==0):
print("Point: ",point," lies on the arms of Triangle")
elif((M1 > 0 and M2 > 0 and M3 > 0)or(M1 < 0 and M2 < 0 and M3 < 0)):
#if products is non 0 check if all of their sign is same
print("Point: ",point," lies inside the Triangle")
else:
print("Point: ",point," lies outside the Triangle")
print("Vertices of Triangle: ",tri_points)
points = [(0,0),(1,1),(2,3),(3,1),(2,2),(4,4),(1,0),(0,4)]
for c in points:
pisinTri(c,tri_points)
Cách dễ nhất và nó hoạt động với tất cả các loại hình tam giác chỉ đơn giản là xác định các góc của các điểm P điểm A, B, C. Nếu bất kỳ góc nào lớn hơn 180.0 độ thì nó ở bên ngoài, nếu 180.0 thì nó nằm trên chu vi và nếu acos lừa dối bạn và dưới 180.0 thì nó ở bên trong. Hãy tìm hiểu về http: // math-vật lý -psychology.blogspot.hu/2015/01/earlish-determination-that-point-is.html
Thành thật mà nói, nó đơn giản như câu trả lời của Simon P Steven tuy nhiên với cách tiếp cận đó, bạn không có quyền kiểm soát chặt chẽ về việc bạn có muốn bao gồm các điểm trên các cạnh của tam giác hay không.
Cách tiếp cận của tôi là một chút khác nhau nhưng rất cơ bản. Xét tam giác sau;
Để có điểm trong tam giác ta phải thỏa mãn 3 điều kiện
Trong phương pháp này, bạn có toàn quyền kiểm soát để bao gồm hoặc loại trừ điểm trên các cạnh riêng lẻ. Vì vậy, bạn có thể kiểm tra nếu một điểm nằm trong tam giác chỉ bao gồm | AC | cạnh chẳng hạn.
Vì vậy, giải pháp của tôi trong JavaScript sẽ như sau;
function isInTriangle(t,p){
function isInBorder(a,b,c,p){
var m = (a.y - b.y) / (a.x - b.x); // calculate the slope
return Math.sign(p.y - m*p.x + m*a.x - a.y) === Math.sign(c.y - m*c.x + m*a.x - a.y);
}
function findAngle(a,b,c){ // calculate the C angle from 3 points.
var ca = Math.hypot(c.x-a.x, c.y-a.y), // ca edge length
cb = Math.hypot(c.x-b.x, c.y-b.y), // cb edge length
ab = Math.hypot(a.x-b.x, a.y-b.y); // ab edge length
return Math.acos((ca*ca + cb*cb - ab*ab) / (2*ca*cb)); // return the C angle
}
var pas = t.slice(1)
.map(tp => findAngle(p,tp,t[0])), // find the angle between (p,t[0]) with (t[1],t[0]) & (t[2],t[0])
ta = findAngle(t[1],t[2],t[0]);
return pas[0] < ta && pas[1] < ta && isInBorder(t[1],t[2],t[0],p);
}
var triangle = [{x:3, y:4},{x:10, y:8},{x:6, y:10}],
point1 = {x:3, y:9},
point2 = {x:7, y:9};
console.log(isInTriangle(triangle,point1));
console.log(isInTriangle(triangle,point2));
bool isInside( float x, float y, float x1, float y1, float x2, float y2, float x3, float y3 ) {
float l1 = (x-x1)*(y3-y1) - (x3-x1)*(y-y1),
l2 = (x-x2)*(y1-y2) - (x1-x2)*(y-y2),
l3 = (x-x3)*(y2-y3) - (x2-x3)*(y-y3);
return (l1>0 && l2>0 && l3>0) || (l1<0 && l2<0 && l3<0);
}
Nó không thể hiệu quả hơn thế này! Mỗi cạnh của một tam giác có thể có vị trí và hướng độc lập, do đó ba phép tính: l1, l2 và l3 chắc chắn là cần thiết liên quan đến 2 phép nhân mỗi lần. Khi đã biết l1, l2 và l3, kết quả chỉ là một vài so sánh cơ bản và các phép toán boolean.
Mã hiệu suất cao được cho là tôi đã điều chỉnh bằng JavaScript (bài viết dưới đây):
function pointInTriangle (p, p0, p1, p2) {
return (((p1.y - p0.y) * (p.x - p0.x) - (p1.x - p0.x) * (p.y - p0.y)) | ((p2.y - p1.y) * (p.x - p1.x) - (p2.x - p1.x) * (p.y - p1.y)) | ((p0.y - p2.y) * (p.x - p2.x) - (p0.x - p2.x) * (p.y - p2.y))) >= 0;
}
pointInTriangle(p, p0, p1, p2)
- cho hình tam giác ngược chiều kim đồng hồpointInTriangle(p, p0, p1, p2)
- cho hình tam giác theo chiều kim đồng hồHãy xem trong jsFiddle (bao gồm kiểm tra hiệu năng), cũng có kiểm tra quanh co trong một chức năng riêng biệt. Hoặc nhấn "Chạy đoạn mã" bên dưới
var ctx = $("canvas")[0].getContext("2d");
var W = 500;
var H = 500;
var point = { x: W / 2, y: H / 2 };
var triangle = randomTriangle();
$("canvas").click(function(evt) {
point.x = evt.pageX - $(this).offset().left;
point.y = evt.pageY - $(this).offset().top;
test();
});
$("canvas").dblclick(function(evt) {
triangle = randomTriangle();
test();
});
document.querySelector('#performance').addEventListener('click', _testPerformance);
test();
function test() {
var result = checkClockwise(triangle.a, triangle.b, triangle.c) ? pointInTriangle(point, triangle.a, triangle.c, triangle.b) : pointInTriangle(point, triangle.a, triangle.b, triangle.c);
var info = "point = (" + point.x + "," + point.y + ")\n";
info += "triangle.a = (" + triangle.a.x + "," + triangle.a.y + ")\n";
info += "triangle.b = (" + triangle.b.x + "," + triangle.b.y + ")\n";
info += "triangle.c = (" + triangle.c.x + "," + triangle.c.y + ")\n";
info += "result = " + (result ? "true" : "false");
$("#result").text(info);
render();
}
function _testPerformance () {
var px = [], py = [], p0x = [], p0y = [], p1x = [], p1y = [], p2x = [], p2y = [], p = [], p0 = [], p1 = [], p2 = [];
for(var i = 0; i < 1000000; i++) {
p[i] = {x: Math.random() * 100, y: Math.random() * 100};
p0[i] = {x: Math.random() * 100, y: Math.random() * 100};
p1[i] = {x: Math.random() * 100, y: Math.random() * 100};
p2[i] = {x: Math.random() * 100, y: Math.random() * 100};
}
console.time('optimal: pointInTriangle');
for(var i = 0; i < 1000000; i++) {
pointInTriangle(p[i], p0[i], p1[i], p2[i]);
}
console.timeEnd('optimal: pointInTriangle');
console.time('original: ptInTriangle');
for(var i = 0; i < 1000000; i++) {
ptInTriangle(p[i], p0[i], p1[i], p2[i]);
}
console.timeEnd('original: ptInTriangle');
}
function pointInTriangle (p, p0, p1, p2) {
return (((p1.y - p0.y) * (p.x - p0.x) - (p1.x - p0.x) * (p.y - p0.y)) | ((p2.y - p1.y) * (p.x - p1.x) - (p2.x - p1.x) * (p.y - p1.y)) | ((p0.y - p2.y) * (p.x - p2.x) - (p0.x - p2.x) * (p.y - p2.y))) >= 0;
}
function ptInTriangle(p, p0, p1, p2) {
var s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y);
var t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y);
if (s <= 0 || t <= 0) return false;
var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
return (s + t) < A;
}
function render() {
ctx.fillStyle = "#CCC";
ctx.fillRect(0, 0, 500, 500);
drawTriangle(triangle.a, triangle.b, triangle.c);
drawPoint(point);
}
function checkClockwise(p0, p1, p2) {
var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
return A > 0;
}
function drawTriangle(p0, p1, p2) {
ctx.fillStyle = "#999";
ctx.beginPath();
ctx.moveTo(p0.x, p0.y);
ctx.lineTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.closePath();
ctx.fill();
ctx.fillStyle = "#000";
ctx.font = "12px monospace";
ctx.fillText("1", p0.x, p0.y);
ctx.fillText("2", p1.x, p1.y);
ctx.fillText("3", p2.x, p2.y);
}
function drawPoint(p) {
ctx.fillStyle = "#F00";
ctx.beginPath();
ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
ctx.fill();
}
function rand(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function randomTriangle() {
return {
a: { x: rand(0, W), y: rand(0, H) },
b: { x: rand(0, W), y: rand(0, H) },
c: { x: rand(0, W), y: rand(0, H) }
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id="performance">Run performance test (open console)</button>
<pre>Click: place the point.
Double click: random triangle.</pre>
<pre id="result"></pre>
<canvas width="500" height="500"></canvas>
Lấy cảm hứng từ điều này: http://www.phatcode.net/articles.php?id=459
bool point2Dtriangle(double e,double f, double a,double b,double c, double g,double h,double i, double v, double w){
/* inputs: e=point.x, f=point.y
a=triangle.Ax, b=triangle.Bx, c=triangle.Cx
g=triangle.Ay, h=triangle.By, i=triangle.Cy */
v = 1 - (f * (b - c) + h * (c - e) + i * (e - b)) / (g * (b - c) + h * (c - a) + i * (a - b));
w = (f * (a - b) + g * (b - e) + h * (e - a)) / (g * (b - c) + h * (c - a) + i * (a - b));
if (*v > -0.0 && *v < 1.0000001 && *w > -0.0 && *w < *v) return true;//is inside
else return false;//is outside
return 0;
}
Các tọa độ Cartesian gần như hoàn hảo được chuyển đổi từ barycentric được xuất trong phạm vi * v (x) và * w (y). Cả hai nhân đôi xuất khẩu nên có một * char trước trong mọi trường hợp, có khả năng: * v và * w Mã cũng có thể được sử dụng cho tam giác khác của một tứ giác. Dưới đây đã ký chỉ viết tam giác abc từ tứ giác abcd theo chiều kim đồng hồ.
A---B
|..\\.o|
|....\\.|
D---C
điểm o nằm trong tam giác ABC để kiểm tra với tam giác thứ hai gọi hàm CDA này và kết quả phải chính xác sau *v=1-*v;
và *w=1-*w;
cho tứ giác
Tôi cần điểm trong kiểm tra tam giác trong "môi trường có thể kiểm soát" khi bạn hoàn toàn chắc chắn rằng các tam giác sẽ theo chiều kim đồng hồ. Vì vậy, tôi đã lấy jsfiddle của Perro Azul và sửa đổi nó theo đề xuất của coproc cho những trường hợp như vậy; cũng loại bỏ 0,5 và 2 phép nhân dư thừa vì chúng chỉ hủy nhau.
http://jsfiddle.net/dog_funtom/H7D7g/
var ctx = $("canvas")[0].getContext("2d");
var W = 500;
var H = 500;
var point = {
x: W / 2,
y: H / 2
};
var triangle = randomTriangle();
$("canvas").click(function (evt) {
point.x = evt.pageX - $(this).offset().left;
point.y = evt.pageY - $(this).offset().top;
test();
});
$("canvas").dblclick(function (evt) {
triangle = randomTriangle();
test();
});
test();
function test() {
var result = ptInTriangle(point, triangle.a, triangle.b, triangle.c);
var info = "point = (" + point.x + "," + point.y + ")\n";
info += "triangle.a = (" + triangle.a.x + "," + triangle.a.y + ")\n";
info += "triangle.b = (" + triangle.b.x + "," + triangle.b.y + ")\n";
info += "triangle.c = (" + triangle.c.x + "," + triangle.c.y + ")\n";
info += "result = " + (result ? "true" : "false");
$("#result").text(info);
render();
}
function ptInTriangle(p, p0, p1, p2) {
var s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y);
var t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y);
if (s <= 0 || t <= 0) return false;
var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
return (s + t) < A;
}
function checkClockwise(p0, p1, p2) {
var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
return A > 0;
}
function render() {
ctx.fillStyle = "#CCC";
ctx.fillRect(0, 0, 500, 500);
drawTriangle(triangle.a, triangle.b, triangle.c);
drawPoint(point);
}
function drawTriangle(p0, p1, p2) {
ctx.fillStyle = "#999";
ctx.beginPath();
ctx.moveTo(p0.x, p0.y);
ctx.lineTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.closePath();
ctx.fill();
ctx.fillStyle = "#000";
ctx.font = "12px monospace";
ctx.fillText("1", p0.x, p0.y);
ctx.fillText("2", p1.x, p1.y);
ctx.fillText("3", p2.x, p2.y);
}
function drawPoint(p) {
ctx.fillStyle = "#F00";
ctx.beginPath();
ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
ctx.fill();
}
function rand(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function randomTriangle() {
while (true) {
var result = {
a: {
x: rand(0, W),
y: rand(0, H)
},
b: {
x: rand(0, W),
y: rand(0, H)
},
c: {
x: rand(0, W),
y: rand(0, H)
}
};
if (checkClockwise(result.a, result.b, result.c)) return result;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<pre>Click: place the point.
Double click: random triangle.</pre>
<pre id="result"></pre>
<canvas width="500" height="500"></canvas>
Đây là mã C # tương đương cho Unity:
public static bool IsPointInClockwiseTriangle(Vector2 p, Vector2 p0, Vector2 p1, Vector2 p2)
{
var s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y);
var t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y);
if (s <= 0 || t <= 0)
return false;
var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
return (s + t) < A;
}
Một trong những cách dễ nhất để kiểm tra xem diện tích hình thành bởi các đỉnh của tam giác (x1, y1), (x2, y2), (x3, y3) có dương hay không.
Diện tích có thể bằng cách tính theo công thức:
1/2 [x1 (y2, y3) + x2 (y3, y1) + x3 (y1, y2)]
hoặc mã python có thể được viết là:
def triangleornot(p1,p2,p3):
return (1/ 2) [p1[0](p2[1]–p3[1]) + p2[0] (p3[1]–p1[1]) + p3[0] (p1[0]–p2[0])]