ポイントが三角形の内側にあるかどうかを判断する簡単な方法はありますか?3Dではなく2Dです。
ポイントが三角形の内側にあるかどうかを判断する簡単な方法はありますか?3Dではなく2Dです。
回答:
一般に、最も単純な(そして非常に最適な)アルゴリズムは、エッジによって作成された半平面のどちら側にポイントがあるかをチェックします。
パフォーマンスの問題を含む、GameDevに関するこのトピックの高品質の情報をいくつか示します。
そして、あなたが始めるためのいくつかのコードがあります:
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);
}
次の方程式系を解きます。
p = p0 + (p1 - p0) * s + (p2 - p0) * t
とのp
場合0 <= s <= 1
、ポイントは三角形の内側に0 <= t <= 1
ありs + t <= 1
ます。
s
、t
およびポイントの重心座標1 - s - t
と呼ばれます。p
s + t <= 1
意味しs <= 1
、t <= 1
if s >= 0
とt >= 0
。
Andreas Brinckに同意します。重心座標はこのタスクに非常に便利です。毎回方程式系を解く必要はないことに注意してください。分析解を評価するだけです。Andreasの表記を使用すると、解決策は次のとおりです。
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);
ここArea
で、三角形の(符号付き)領域は次のとおりです。
Area = 0.5 *(-p1y*p2x + p0y*(-p1x + p2x) + p0x*(p1y - p2y) + p1x*p2y);
評価s
しt
てください1-s-t
。p
すべて正の場合に限り、ポイントは三角形の内側にあります。
編集:エリアの上記の式は、三角形のノードの番号付けが反時計回りであることを前提としています。番号が時計回りの場合、この式は負の面積を返します(ただし、大きさは正しくなります)。s>0 && t>0 && 1-s-t>0
ただし、テスト自体()は番号付けの方向には依存しません1/(2*Area)
。これは、三角形のノードの向きが変わると、上記の式に乗算される符号も変わるためです。
EDIT 2:より良い計算効率のために、参照coprocを三角形ノード(時計回りまたは反時計回り)の方向をすることにより、予め分割知られている場合、その点になり以下のコメント(2*Area
の式にs
とt
することができます避けてください)。Andreas Brinckの回答の下にあるコメントのPerro Azulのjsfiddle-code も参照してください。
2*Area
。つまり、計算しs´=2*|Area|*s
、t´=2*|Area|*t
(ポイントの向き(時計回りまたは反時計回り)がわからないArea
場合は、もちろんの符号を確認する必要がありますが、それ以外の場合は、計算する必要があります)、チェックs>0
するにはをチェックするだけで十分s´>0
です。そして、チェック1-s-t>0
する代わりに、チェックするだけで十分s´+t´<2*|Area|
です。
p0->p1->p2
である反時計方向に直交(通常は時計回りにスクリーン座標)、Area
この方法で算出は正となります。
私はこのコードをグーグルで最後に試してこのページを見つける前に書いたので、共有したいと思った。これは基本的にはKisielewicz回答の最適化バージョンです。私はバリセントリック法も調べましたが、ウィキペディアの記事から判断して、それがいかに効率的であるかを理解するのに苦労しています(同等性がより深いと思います)。とにかく、このアルゴリズムには除算を使用しないという利点があります。潜在的な問題は、向きに応じたエッジ検出の動作です。
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;
}
つまり、アイデアは次のとおりです。点sは線ABとACの両方の左側または右側にありますか?trueの場合、内部にすることはできません。falseの場合、少なくとも条件を満たす「コーン」内にあります。これで、三角形(三角形)内のポイントはABのBC(およびCA)と同じ側にある必要があることがわかったので、それらが異なるかどうかを確認します。もしそうなら、sはおそらく内部に存在できません。そうでなければ、sは内部になければなりません。
計算のいくつかのキーワードは、線の半平面と行列式(2x2の外積)です。おそらく、より教育的な方法は、AB、BC、CAの各線と同じ側(左または右)にある場合に限り、内部にある点と考えることでしょう。上記の方法は、いくつかの最適化に適しているようです。
andreasdrとPerro Azulによって投稿された重心法のC#バージョン。s
とt
反対の符号がある場合、面積計算は回避できることに注意してください。私はかなり徹底した単体テストで正しい動作を確認しました。
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);
}
[ 編集 ]
@Pierreによる提案された変更を受け入れました。コメントを見る
重心メソッドのJavaバージョン:
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;
}
上記のコードは、オーバーフローがないと仮定して、整数で正確に動作します。また、時計回りと反時計回りの三角形でも機能します。共線三角形では機能しません(ただし、det == 0をテストすることで確認できます)。
同じ三角形で異なるポイントをテストする場合は、重心バージョンが最も高速です。
重心バージョンは3つの三角形の点で対称ではないため、浮動小数点の丸めエラーのため、Kornel Kisielewiczのエッジ半平面バージョンよりも一貫性が低くなる可能性があります。
クレジット:重心座標に関するWikipediaの記事から上記のコードを作成しました。
簡単な方法は次のとおりです。
ポイントを三角形の3つの頂点のそれぞれに接続するベクトルを見つけ、それらのベクトル間の角度を合計します。角度の合計が2 * piの場合、ポイントは三角形の内側にあります。
代替案を説明する2つの優れたサイトは次のとおりです。
(Andreas Brinckによって指摘された)重心座標の分析ソリューションを使用して、
「コストのかかる」操作の数を最小限に抑えることができます。
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;
}
コードはPerro Azul jsfiddleに貼り付けるか、下の[コードスニペットを実行]をクリックして試してください。
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>
につながる:
これは、Kornel Kisielewiczソリューション(呼び出し 25回、記憶1回、減算15回、乗算5回、比較5回)と非常によく似ており、時計回り/反時計回りの検出が必要な場合はさらに優れている可能性があります(呼び出し 6回、加算1回、減算2回の減算が必要です)。 、rghbで指摘されているように、解析解の行列式を使用した2つの乗算と1つの比較自体。
以下は効率的なPython実装です。
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))
そして出力例:
あなたがスピードを求めているなら、これはあなたを助けるかもしれない手順です。
縦座標の三角形の頂点を並べ替えます。これには、最低3つの比較が必要です。Y0、Y1、Y2を3つのソート値とします。それらに3つの水平線を引くことにより、平面を2つの半平面と2つのスラブに分割します。Yをクエリポイントの縦座標にします。
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
さらに2つの比較が必要です。ご覧のように、「境界スラブ」の外側のポイントでは、すばやい拒否が実現されます。
オプションで、横軸にテストを指定して、左側と右側をすばやく拒否できます(X <= X0' or X >= X2'
)。これにより、クイックバウンディングボックステストが同時に実装されますが、横座標でも並べ替える必要があります。
最終的には、関連するスラブ(上または下)を区切る三角形の2つの辺に関して、指定された点の符号を計算する必要があります。テストの形式は次のとおりです。
((X - Xi) * (Y - Yj) > (X - Xi) * (Y - Yj)) == ((X - Xi) * (Y - Yk) > (X - Xi) * (Y - Yk))
i, j, k
組み合わせの完全な説明(並べ替えの結果に基づいて6つあります)はこの回答の範囲外であり、「読者への課題として残しておきます」。効率を上げるために、ハードコーディングする必要があります。
このソリューションが複雑であると思われる場合は、主に単純な比較(一部は事前に計算できます)に加えて、バウンディングボックステストが失敗した場合の6つの減算と4つの乗算が関係することに注意してください。後者のコストは、テストポイントを両側と比較することを避けることができない最悪の場合のように、打ち勝つことが困難です(他の回答の方法ではコストが低く、15の減算と6の乗算、場合によっては除算など、悪化する場合もあります)。
更新:せん断変換により高速化
上記で説明したように、2つの比較を使用して、3つの頂点縦座標で区切られた4つの水平バンドの1つの内側のポイントをすばやく見つけることができます。
オプションで、1つまたは2つの追加のXテストを実行して、境界ボックス(点線)の内部をチェックできます。
次に、によって与えられる「せん断」変換を考えます。X'= X - m Y, Y' = Y
ここm
で、DX/DY
は最も高いエッジの勾配です。この変換により、三角形のこちら側が垂直になります。そして、あなたはあなたが真ん中の水平線のどちら側にあるかを知っているので、三角形の片側に関してサインをテストすることで十分です。
勾配と、せん断された三角形の頂点と辺の方程式の係数をm
として事前計算したと仮定すると、最悪の場合X'
X = m Y + p
X' = X - m Y
;X >< m' Y + p'
せん断された三角形の関連する辺に対する1つの符号テスト。Pythonの他の関数、開発者の方法よりも高速で(少なくとも私にとっては)、CédricDufourソリューションに触発された:
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 )
あなたはそれをテストすることができます:
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')
プロットには多くの時間がかかりますが、そのグリッドは、開発者のコードの 0.0844349861145秒に対して0.0195319652557秒でテストされます。
最後にコードのコメント:
# 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])
とtrue
、それは偽ですが返されます
JSの回答がないため、
時計回りと反時計回りのソリューション:
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
}
編集:det計算のタイプミスがあった(のcy - ay
代わりにcx - ax
)、これは修正されました。
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>
ここでは、上記と同じ方法を使用しています。ポイントがそれぞれの線AB、BC、CAの「同じ」側にある場合、点はABCの内側にあります。
let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)
)を使用する場合、これは三角形の巻き順を決定するためです。そのため、この方法はCWおよびCCWの三角形で機能します(jsFiddleを参照)。
let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)
代わりに let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)
、これは修正されました。報告をありがとう
私はいくつかの単純なベクトル数学を使用して、アンドレアスが与えた重心座標の解を説明したいだけですが、それは理解しやすくなります。
(1-s)| v0v2 | / | v0v2 | = tp | v0v1 | / | v0v1 |
1-s = tp、1 = s + tpとなります。1 <s + tであるt> tpが二重破線にある場合、ベクトルは三角形の外側にあり、任意のt <= tp、1> = s + tが単一破線にある場合、ベクトルは三角形の内側。
次に、[0、1]でsを指定した場合、対応するtは、三角形の内側のベクトルについて、1> = s + tを満たしている必要があります。
したがって、最終的にv = s * v02 + t * v01が得られます。vは三角形であり、s、t、s + tは[0、1]に属します。次に、ポイントに変換します。
p-p0 = s *(p1-p0)+ t *(p2-p0)、[0、1]のs、t、s + t
これは、方程式システムp = p0 + s *(p1-p0)+ t *(p2-p0)を解くためのAndreasの解法と同じで、s、t、s + tは[0、1]に属します。
以下は、効率的で文書化された3つのユニットテストを含むpythonのソリューションです。それはプロ級の品質であり、そのままモジュールの形でプロジェクトにドロップする準備ができています。
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)
上記のアルゴリズムには、その有効性を確認するための追加のオプションのグラフィカルテストがあります。
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")
次の図を作成します。
隣接する2つの三角形の共通のエッジ上に点がある正確なエッジ条件があります。点は両方に存在することはできず、どちらの三角形にも存在することはできません。ポイントを割り当てるには、任意だが一貫した方法が必要です。たとえば、ポイントを通る水平線を描画します。線が三角形の反対側の右側と交差する場合、ポイントは三角形の内部にあるものとして扱われます。交差点が左側にある場合、ポイントは外側です。
ポイントのある線が水平の場合は、上/下を使用します。
ポイントが複数の三角形の共通の頂点上にある場合は、ポイントが最小の角度を形成する中心を持つ三角形を使用します。
もっと楽しく:3つのポイントは直線(0度)にできます(例:(0,0)-(0,10)-(0,5))。三角測量アルゴリズムでは、「耳」(0,10)を削除する必要があります。生成される「三角形」は、直線の縮退した場合です。
これは、ポイントが三角形の内部または外部にあるか、三角形の腕にあるかを決定する最も簡単な概念です。
点の決定は、行列式によって三角形の内側にあります。
最も単純な作業コード:
#-*- 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)
最も簡単な方法で、すべてのタイプの三角形で機能するのは、単純にPポイントA、B、Cポイントの角度を決定することです。角度のいずれかが180.0度より大きい場合、それは外側にあり、180.0の場合、それは円周上にあり、acosがあなたを浮気し、180.0未満の場合、内側にあります。http:// math-physicsを理解するために見てください-psychology.blogspot.hu/2015/01/earlish-determination-that-point-is.html
正直なところ、Simon P Stevenの答えと同じくらい簡単です。ですが、そのアプローチでは、三角形のエッジ上のポイントを含めるかどうかを確実に制御できません。
私のアプローチは少し異なりますが、非常に基本的です。次の三角形について考えてみましょう。
三角形のポイントを取得するには、3つの条件を満たす必要があります
この方法では、エッジ上のポイントを個別に含めるか除外するかを完全に制御できます。したがって、| AC |のみを含む三角形内に点があるかどうかを確認できます。たとえばエッジ。
したがって、JavaScriptでの私のソリューションは次のようになります。
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);
}
これ以上効率的ではありません!三角形の各辺は独立した位置と方向を持つことができるため、3つの計算:l1、l2、l3はそれぞれ2つの乗算を必要とします。l1、l2、l3がわかると、結果はいくつかの基本的な比較とブール演算だけになります。
私がJavaScriptで採用したおそらく高性能なコード(以下の記事):
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)
-反時計回りの三角形の場合pointInTriangle(p, p0, p1, p2)
-時計回りの三角形の場合jsFiddle(パフォーマンステストが含まれています)を見てください。別の関数で巻線チェックも行われています。または、下の「コードスニペットを実行」を押してください
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>
これに触発された: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;
}
重心から変換されたほぼ完全なデカルト座標は、* v(x)および* w(y)のdouble内でエクスポートされます。いずれの場合も、両方のエクスポートdoubleの前に*文字が必要です。おそらく、* vおよび* wコードは、四角形の他の三角形にも使用できます。これにより、署名された時計回りのabcdクワッドから三角形abcのみが書き込まれました。
A---B
|..\\.o|
|....\\.|
D---C
o点はABC三角形の内側にあり、2番目の三角形でこの関数をCDA方向に呼び出してテストします。四角形の後*v=1-*v;
と*w=1-*w;
四角形の結果は正しいはずです。
三角形が時計回りであることが確実である場合は、「制御可能な環境」で三角形のチェックインポイントが必要でした。そこで、Perro Azulのjsfiddleを取得し、coprocの提案に従って修正しました、そのような場合ので。また、0.5と2の冗長な乗算も削除されました。
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>
Unityの同等のC#コードは次のとおりです。
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;
}