4 回答

TA貢獻1909條經驗 獲得超7個贊
我假設多邊形是凸的,并且圓沿著直線移動(至少在一段可能很小的時間間隔內)并且沒有遵循一些彎曲的軌跡。如果它遵循彎曲的軌跡,那么事情就會變得更加困難。在曲線軌跡的情況下,可以保留基本概念,但實際的碰撞點(圓的碰撞分辨率點)可能更難計算。不過,我正在概述一個想法,它也可以擴展到這種情況。另外,它可以作為圓和凸多邊形之間碰撞檢測的主要方法。
我沒有考慮所有可能的情況,可能包括特殊或極端的情況,但至少它給了你一個探索的方向。
在你的腦海中將圓和多邊形之間的碰撞轉換為圓心(一個點)和一個由圓的半徑加厚的多邊形版本之間的碰撞r
,即(i)多邊形的每條邊都是偏移的(翻譯) 沿垂直于它的向量向外半徑r
并指向多邊形外部,(ii) 頂點變成半徑為 的圓弧r
,以多邊形頂點為中心并連接適當的相鄰偏移邊的端點(基本上,放置半徑的圓r
在多邊形的頂點并取它們的凸包)。
現在,圓心的當前位置是C = [ C[0], C[1] ]并且它一直沿直線移動,方向矢量V = [ V[0], V[1] ]指向運動方向(或者如果您愿意,可以將其V視為圓在檢測到的那一刻的速度)碰撞)。然后,有一個由向量方程定義的軸(或者說是一條射線 - 一條有向半線)X = C - t * V,其中t >= 0(該軸指向過去的軌跡)?;旧?,這是通過中心點C并與向量對齊/平行的半線V?,F在,分辨率點,即您要將圓圈移動到的點是軸X = C - t * V與加厚多邊形邊界相交的點。
因此,您必須檢查 (1) 第一個軸相交的邊緣,然后 (2) 軸與與原始多邊形頂點有關的圓弧相交。
P = [ P[0], P[1], ..., P[N], P[0] ]假設多邊形由逆時針方向的頂點數組給出。
(1)對于原始多邊形的每條邊P[i-1]P[i],與您的碰撞相關(這些可能是在檢測到碰撞的頂點處相交的兩條相鄰邊,或者在圓移動的情況下實際上可能是所有邊非常高的速度并且您很晚才檢測到碰撞,因此實際碰撞甚至沒有發生在那里,我把這留給您,因為您更了解您的情況的細節)執行以下操作。您有作為輸入數據:
C = [ C[0], C[1] ]
V = [ V[0], V[1] ]
P[i-1] = [ P[i-1][0], P[i-1][1] ]
P[i] = [ P[i][0], P[i][1] ]
做:
Normal = [ P[i-1][1] - P[i][1], P[i][0] - P[i-1][0] ];
Normal = Normal / sqrt((P[i-1][1] - P[i][1])^2 + ( P[i][0] - P[i-1][0] )^2);
// you may have calculated these already
Q_0[0] = P[i-1][0] + r*Normal[0];
Q_0[1] = P[i-1][1] + r*Normal[1];
Q_1[0] = P[i][0]+ r*Normal[0];
Q_1[1] = P[i][1]+ r*Normal[1];
求解s, t線性方程組(相交方程):
Q_0[0] + s*(Q_1[0] - Q_0[0]) = C[0] - t*V[0];
Q_0[1] + s*(Q_1[1] - Q_0[1]) = C[1] - t*V[1];
如果0<= s <= 1和t >= 0,你就完成了,你的解決點是
R[0] = C[0] - t*V[0];
R[1] = C[1] - t*V[1];
別的
(2)對于與P[i]您的碰撞相關的每個頂點,請執行以下操作:求解t二次方程(有一個明確的公式)
norm(P[i] - C + t*V )^2 = r^2
或擴展:
(V[0]^2 + V[1]^2) * t^2 + 2 * ( (P[i][0] - C[0])*V[0] + (P[i][1] - C[1])*V[1] )*t + ( P[i][0] - C[0])^2 + (P[i][1] - C[1])^2 ) - r^2 = 0
或者,如果您更喜歡類似代碼的方式:
a = V[0]^2 + V[1]^2;
b = (P[i][0] - C[0])*V[0] + (P[i][1] - C[1])*V[1];
c = (P[i][0] - C[0])^2 + (P[i][1] - C[1])^2 - r^2;
D = b^2 - a*c;
if D < 0 there is no collision with the vertex
i.e. no intersection between the line X = C - t*V
and the circle of radius r centered at P[i]
else
D = sqrt(D);
t1 = ( - b - D) / a;
t2 = ( - b + D) / a;
where t2 >= t1
那么你的解決點是
R[0] = C[0] - t2*V[0];
R[1] = C[1] - t2*V[1];

TA貢獻1856條經驗 獲得超17個贊
這可能不是您想要的,但這里有一種方法(如果您不是在尋找完美的精度):
您可以嘗試近似位置而不是計算它。
您設置代碼的方式有一個很大的優勢:在碰撞之前您擁有圓的最后位置。多虧了這一點,您可以通過軌跡“迭代”并嘗試找到最接近交點位置的位置。我假設你已經有一個函數可以告訴你一個圓是否與多邊形相交。代碼(C++):
// What we need :
Vector startPos; // Last position of the circle before the collision
Vector currentPos; // Current, unwanted position
Vector dir; // Direction (a unit vector) of the circle's velocity
float distance = compute_distance(startPos, currentPos); // The distance from startPos to currentPos.
Polygon polygon; // The polygon
Circle circle; // The circle.
unsigned int iterations_count = 10; // The number of iterations that will be done. The higher this number, the more precise the resolution.
// The algorithm :
float currentDistance = distance / 2.f; // We start at the half of the distance.
Circle temp_copy; // A copy of the real circle to "play" with.
for (int i = 0; i < iterations_count; ++i) {
temp_copy.pos = startPos + currentDistance * dir;
if (checkForCollision(temp_copy, polygon)) {
currentDistance -= currentDistance / 2.f; // We go towards startPos by the half of the current distance.
}
else {
currentDistance += currentDistance / 2.f; // We go towards currentPos by the half of the current distance.
}
}
// currentDistance now contains the distance between startPos and the intersection point
// And this is where you should place your circle :
Vector intersectionPoint = startPos + currentDistance * dir;
我沒有測試過這段代碼,所以我希望那里沒有大錯誤。它也沒有優化,并且這種方法存在一些問題(交點可能最終在多邊形內)所以它仍然需要改進,但我認為你明白了。另一個(很大,取決于你在做什么)問題是它是一個近似值而不是一個完美的答案。

TA貢獻1818條經驗 獲得超8個贊
圓多邊形截距
如果球在移動并且你可以確保球總是在多邊形之外開始,那么解決方案就相當簡單了。
我們將球及其運動稱為球線。它從球的當前位置開始,到球將在下一幀的位置結束。
要解決此問題,您需要找到距球線起點最近的截距。
攔截有兩種。
線段(球線)與線段(多邊形邊)
帶圓的線段(球線)(每個(僅凸)多邊形角處的圓)
示例代碼有一個Lines2
對象,其中包含兩個相關的攔截函數。截距以Vec2
包含兩個單位距離的形式返回。截距函數用于線(無限長)而不是線段。如果沒有攔截,則返回未定義。
對于線截距Line2.unitInterceptsLine(line, result = new Vec2())
,單位值 (in result
) 是從起點開始沿每條線的單位距離。負值落后于開始。
考慮到球半徑,每個多邊形邊沿其法線偏移球半徑。多邊形邊緣具有一致的方向很重要。在示例中,法線位于直線的右側,多邊形點位于順時針方向。
對于線段/圓的截距Line2.unitInterceptsCircle(center, radius, result = new Vec2())
,單位值 (in result
) 是沿直線與圓相交的單位距離。result.x
將始終包含最近的截距(假設您從圓圈外開始)。如果有一個攔截,那么即使它們在同一點,也總是有兩個。
例子
該示例包含所有需要的內容
感興趣的對象是ball
和poly
ball
定義球及其運動。也有代碼來繪制它的例子poly
保存多邊形的點。根據球半徑將點轉換為偏移線。它被優化為僅在球半徑發生變化時才計算線條。
函數poly.movingBallIntercept
是完成所有工作的函數。它需要一個球對象和一個可選的結果向量。
Vec2
如果它接觸多邊形,它將返回作為球的位置。
它通過找到到偏移線和點(作為圓)的最小單位距離來做到這一點,并使用該單位距離來定位結果。
請注意,如果球在多邊形內,則與角的截距是相反的。該函數Line2.unitInterceptsCircle
確實提供了線進入和退出圓圈的 2 個單位距離。但是,您需要知道您是在室內還是室外才能知道使用哪一個。該示例假設您在多邊形之外。
指示
移動鼠標來改變球的路徑。
單擊以設置球的起始位置。
Math.EPSILON = 1e-6;
Math.isSmall = val => Math.abs(val) < Math.EPSILON;
Math.isUnit = u => !(u < 0 || u > 1);
Math.TAU = Math.PI * 2;
/* export {Vec2, Line2} */ // this should be a module
var temp;
function Vec2(x = 0, y = (temp = x, x === 0 ? (x = 0 , 0) : (x = x.x, temp.y))) {
this.x = x;
this.y = y;
}
Vec2.prototype = {
init(x, y = (temp = x, x = x.x, temp.y)) { this.x = x; this.y = y; return this }, // assumes x is a Vec2 if y is undefined
copy() { return new Vec2(this) },
equal(v) { return (this.x - v.x) === 0 && (this.y - v.y) === 0 },
isUnits() { return Math.isUnit(this.x) && Math.isUnit(this.y) },
add(v, res = this) { res.x = this.x + v.x; res.y = this.y + v.y; return res },
sub(v, res = this) { res.x = this.x - v.x; res.y = this.y - v.y; return res },
scale(val, res = this) { res.x = this.x * val; res.y = this.y * val; return res },
invScale(val, res = this) { res.x = this.x / val; res.y = this.y / val; return res },
dot(v) { return this.x * v.x + this.y * v.y },
uDot(v, div) { return (this.x * v.x + this.y * v.y) / div },
cross(v) { return this.x * v.y - this.y * v.x },
uCross(v, div) { return (this.x * v.y - this.y * v.x) / div },
get length() { return this.lengthSqr ** 0.5 },
set length(l) { this.scale(l / this.length) },
get lengthSqr() { return this.x * this.x + this.y * this.y },
rot90CW(res = this) {
const y = this.x;
res.x = -this.y;
res.y = y;
return res;
},
};
const wV1 = new Vec2(), wV2 = new Vec2(), wV3 = new Vec2(); // pre allocated work vectors used by Line2 functions
function Line2(p1 = new Vec2(), p2 = (temp = p1, p1 = p1.p1 ? p1.p1 : p1, temp.p2 ? temp.p2 : new Vec2())) {
this.p1 = p1;
this.p2 = p2;
}
Line2.prototype = {
init(p1, p2 = (temp = p1, p1 = p1.p1, temp.p2)) { this.p1.init(p1); this.p2.init(p2) },
copy() { return new Line2(this) },
asVec(res = new Vec2()) { return this.p2.sub(this.p1, res) },
unitDistOn(u, res = new Vec2()) { return this.p2.sub(this.p1, res).scale(u).add(this.p1) },
translate(vec, res = this) {
this.p1.add(vec, res.p1);
this.p2.add(vec, res.p2);
return res;
},
translateNormal(amount, res = this) {
this.asVec(wV1).rot90CW().length = -amount;
this.translate(wV1, res);
return res;
},
unitInterceptsLine(line, res = new Vec2()) { // segments
this.asVec(wV1);
line.asVec(wV2);
const c = wV1.cross(wV2);
if (Math.isSmall(c)) { return }
wV3.init(this.p1).sub(line.p1);
res.init(wV1.uCross(wV3, c), wV2.uCross(wV3, c));
return res;
},
unitInterceptsCircle(point, radius, res = new Vec2()) {
this.asVec(wV1);
var b = -2 * this.p1.sub(point, wV2).dot(wV1);
const c = 2 * wV1.lengthSqr;
const d = (b * b - 2 * c * (wV2.lengthSqr - radius * radius)) ** 0.5
if (isNaN(d)) { return }
return res.init((b - d) / c, (b + d) / c);
},
};
/* END of file */ // Vec2 and Line2 module
/* import {vec2, Line2} from "whateverfilename.jsm" */ // Should import vec2 and line2
const POLY_SCALE = 0.5;
const ball = {
pos: new Vec2(-150,0),
delta: new Vec2(10, 10),
radius: 20,
drawPath(ctx) {
ctx.beginPath();
ctx.arc(this.pos.x, this.pos.y, this.radius, 0, Math.TAU);
ctx.stroke();
},
}
const poly = {
bRadius: 0,
lines: [],
set ballRadius(radius) {
const len = this.points.length
this.bRadius = ball.radius;
i = 0;
while (i < len) {
let line = this.lines[i];
if (line) { line.init(this.points[i], this.points[(i + 1) % len]) }
else { line = new Line2(new Vec2(this.points[i]), new Vec2(this.points[(i + 1) % len])) }
this.lines[i++] = line.translateNormal(radius);
}
this.lines.length = i;
},
points: [
new Vec2(-200, -150).scale(POLY_SCALE),
new Vec2(200, -100).scale(POLY_SCALE),
new Vec2(100, 0).scale(POLY_SCALE),
new Vec2(200, 100).scale(POLY_SCALE),
new Vec2(-200, 75).scale(POLY_SCALE),
new Vec2(-150, -50).scale(POLY_SCALE),
],
drawBallLines(ctx) {
if (this.lines.length) {
const r = this.bRadius;
ctx.beginPath();
for (const l of this.lines) {
ctx.moveTo(l.p1.x, l.p1.y);
ctx.lineTo(l.p2.x, l.p2.y);
}
for (const p of this.points) {
ctx.moveTo(p.x + r, p.y);
ctx.arc(p.x, p.y, r, 0, Math.TAU);
}
ctx.stroke()
}
},
drawPath(ctx) {
ctx.beginPath();
for (const p of this.points) { ctx.lineTo(p.x, p.y) }
ctx.closePath();
ctx.stroke();
},
movingBallIntercept(ball, res = new Vec2()) {
if (this.bRadius !== ball.radius) { this.ballRadius = ball.radius }
var i = 0, nearest = Infinity, nearestGeom, units = new Vec2();
const ballT = new Line2(ball.pos, ball.pos.add(ball.delta, new Vec2()));
for (const p of this.points) {
const res = ballT.unitInterceptsCircle(p, ball.radius, units);
if (res && units.x < nearest && Math.isUnit(units.x)) { // assumes ball started outside poly so only need first point
nearest = units.x;
nearestGeom = ballT;
}
}
for (const line of this.lines) {
const res = line.unitInterceptsLine(ballT, units);
if (res && units.x < nearest && units.isUnits()) { // first unit.x is for unit dist on line
nearest = units.x;
nearestGeom = ballT;
}
}
if (nearestGeom) { return ballT.unitDistOn(nearest, res) }
return;
},
}
const ctx = canvas.getContext("2d");
var w = canvas.width, cw = w / 2;
var h = canvas.height, ch = h / 2
requestAnimationFrame(mainLoop);
// line and point for displaying mouse interaction. point holds the result if any
const line = new Line2(ball.pos, ball.pos.add(ball.delta, new Vec2())), point = new Vec2();
function mainLoop() {
ctx.setTransform(1,0,0,1,0,0); // reset transform
if(w !== innerWidth || h !== innerHeight){
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
}else{
ctx.clearRect(0,0,w,h);
}
ctx.setTransform(1,0,0,1,cw,ch); // center to canvas
if (mouse.button) { ball.pos.init(mouse.x - cw, mouse.y - ch) }
line.p2.init(mouse.x - cw, mouse.y - ch);
line.p2.sub(line.p1, ball.delta);
ctx.lineWidth = 1;
ctx.strokeStyle = "#000"
poly.drawPath(ctx)
ctx.strokeStyle = "#F804"
poly.drawBallLines(ctx);
ctx.strokeStyle = "#F00"
ctx.beginPath();
ctx.arc(ball.pos.x, ball.pos.y, ball.radius, 0, Math.TAU);
ctx.moveTo(line.p1.x, line.p1.y);
ctx.lineTo(line.p2.x, line.p2.y);
ctx.stroke();
ctx.strokeStyle = "#00f"
ctx.lineWidth = 2;
ctx.beginPath();
if (poly.movingBallIntercept(ball, point)) {
ctx.arc(point.x, point.y, ball.radius, 0, Math.TAU);
} else {
ctx.arc(line.p2.x, line.p2.y, ball.radius, 0, Math.TAU);
}
ctx.stroke();
requestAnimationFrame(mainLoop);
}
const mouse = {x:0, y:0, button: false};
function mouseEvents(e) {
const bounds = canvas.getBoundingClientRect();
mouse.x = e.pageX - bounds.left - scrollX;
mouse.y = e.pageY - bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["mousedown","mouseup","mousemove"].forEach(name => document.addEventListener(name,mouseEvents));
#canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>
Click to position ball. Move mouse to test trajectory
Vec2
和Line2
為了使其更容易,矢量庫將有所幫助。對于示例,我編寫了一個快速Vec2
和Line2
對象(注意僅示例中使用的函數已經過測試,注意該對象是為性能而設計的,沒有經驗的編碼人員應避免使用這些對象并選擇更標準的矢量和線庫)

TA貢獻1946條經驗 獲得超3個贊
我不確定我是否正確理解了場景,但是一個有效的直接用例是,首先只使用圓形的正方形邊界框,計算該正方形與多邊形的交點非常快,快得多,而不是使用圓圈。一旦您檢測到該正方形和多邊形的交集,您需要考慮或編寫最適合您的場景的精度。如果你需要比這個狀態更好的精度,你可以從這里繼續:從你的方形交叉點的 90° 角,你畫一條 45° 的線,直到它接觸你的圓,此時,它在哪里觸摸,你畫了一個新的正方形,但是這一次,正方形嵌入到圓中,現在讓它運行,直到這個新的正方形與多邊形相交,一旦相交,你就有了一個保證的圓相交。根據您需要的精度,您可以像這樣簡單地玩耍。我不確定你的下一個問題是什么?如果它只能是圓形軌跡的倒數,那么它必須是那個倒數,我真的不確定我在這里錯過了什么。
添加回答
舉報