亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

SAT Polygon Circle Collision - 解決速度方向的交點并確定碰撞的一側

SAT Polygon Circle Collision - 解決速度方向的交點并確定碰撞的一側

侃侃無極 2022-10-08 15:10:47
概括這個問題是用 JavaScript 寫的,但是用任何語言、偽代碼或數學來回答都會很棒!我一直在嘗試實現分離軸定理來完成以下任務:檢測凸多邊形和圓之間的交點。找出可應用于圓的平移以解決相交問題,使圓幾乎不接觸多邊形,但不再在內部。確定碰撞的軸(問題末尾的詳細信息)。我已成功完成第一個要點,您可以在問題末尾看到我的 javascript 代碼。我在其他部分有困難。解決交集網上有很多關于如何解決圓最小/最短重疊方向上的交點的例子。您可以在最后的代碼中看到我已經計算過了。但是,這不適合我的需要。我必須解決與圓軌跡相反方向的碰撞(假設我已經有了圓的軌跡,并希望將它作為單位向量或角度傳遞給我的函數,以適合者為準)。您可以在下圖中看到最短分辨率和預期分辨率之間的差異:如何計算最小平移向量以解決test_CIRCLE_POLY函數內部的交點,但這將應用于特定方向,與圓的軌跡相反?我的想法/嘗試:我的第一個想法是在必須在 SAT 算法中測試的軸上添加一個附加軸,該軸將垂直于圓的軌跡。然后我會在投影到這個軸上時根據重疊來解決。這會有點工作,但在大多數情況下會解決得很遠。它不會導致最低翻譯。所以這不會令人滿意。我的第二個想法是繼續使用最短重疊的幅度,但將方向更改為與圓的軌跡相反。這看起來很有希望,但可能有很多我沒有考慮到的極端情況。也許這是一個不錯的起點。確定碰撞側/軸我想出了一種方法來確定圓與多邊形的哪些邊相撞。對于多邊形的每個測試軸,我會簡單地檢查重疊。如果有重疊,那一側就會發生碰撞。這個解決方案將不再被接受,因為我只想根據圓的軌跡找出一側。我的預期解決方案會告訴我,在下面的示例圖像中,軸 A 是碰撞軸,而不是軸 B。這是因為一旦解決了交點,軸 A 就是對應于多邊形邊的軸只是勉強接觸到圓圈。我的想法/嘗試:目前我假設碰撞軸垂直于 MTV(最小平移向量)。這目前是不正確的,但是一旦我在問題的前半部分更新了交叉點解析過程,它應該是正確的軸。所以這部分應該首先解決?;蛘?,我考慮從圓的先前位置及其當前位置+半徑創建一條線,并檢查哪些邊與這條線相交。但是,仍然存在歧義,因為有時會有不止一側與線相交。
查看完整描述

4 回答

?
jeck貓

TA貢獻1909條經驗 獲得超7個贊

我假設多邊形是凸的,并且圓沿著直線移動(至少在一段可能很小的時間間隔內)并且沒有遵循一些彎曲的軌跡。如果它遵循彎曲的軌跡,那么事情就會變得更加困難。在曲線軌跡的情況下,可以保留基本概念,但實際的碰撞點(圓的碰撞分辨率點)可能更難計算。不過,我正在概述一個想法,它也可以擴展到這種情況。另外,它可以作為圓和凸多邊形之間碰撞檢測的主要方法。

我沒有考慮所有可能的情況,可能包括特殊或極端的情況,但至少它給了你一個探索的方向。

在你的腦海中將圓和多邊形之間的碰撞轉換為圓心(一個點)和一個由圓的半徑加厚的多邊形版本之間的碰撞r,即(i)多邊形的每條邊都是偏移的(翻譯) 沿垂直于它的向量向外半徑r并指向多邊形外部,(ii) 頂點變成半徑為 的圓弧r,以多邊形頂點為中心并連接適當的相鄰偏移邊的端點(基本上,放置半徑的圓r在多邊形的頂點并取它們的凸包)。

http://img1.sycdn.imooc.com//634122ac0001a66406580726.jpg

現在,圓心的當前位置是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];


查看完整回答
反對 回復 2022-10-08
?
慕慕森

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;

我沒有測試過這段代碼,所以我希望那里沒有大錯誤。它也沒有優化,并且這種方法存在一些問題(交點可能最終在多邊形內)所以它仍然需要改進,但我認為你明白了。另一個(很大,取決于你在做什么)問題是它是一個近似值而不是一個完美的答案。


查看完整回答
反對 回復 2022-10-08
?
弒天下

TA貢獻1818條經驗 獲得超8個贊

圓多邊形截距

如果球在移動并且你可以確保球總是在多邊形之外開始,那么解決方案就相當簡單了。

我們將球及其運動稱為球線。它從球的當前位置開始,到球將在下一幀的位置結束。

要解決此問題,您需要找到距球線起點最近的截距。

攔截有兩種。

  • 線段(球線)與線段(多邊形邊)

  • 帶圓的線段(球線)(每個(僅凸)多邊形角處的圓)

示例代碼有一個Lines2對象,其中包含兩個相關的攔截函數。截距以Vec2包含兩個單位距離的形式返回。截距函數用于線(無限長)而不是線段。如果沒有攔截,則返回未定義。

對于線截距Line2.unitInterceptsLine(line, result = new Vec2()),單位值 (in result) 是從起點開始沿每條線的單位距離。負值落后于開始。

考慮到球半徑,每個多邊形邊沿其法線偏移球半徑。多邊形邊緣具有一致的方向很重要。在示例中,法線位于直線的右側,多邊形點位于順時針方向。

對于線段/圓的截距Line2.unitInterceptsCircle(center, radius, result = new Vec2()),單位值 (in result) 是沿直線與圓相交的單位距離。result.x將始終包含最近的截距(假設您從圓圈外開始)。如果有一個攔截,那么即使它們在同一點,也總是有兩個。

例子

該示例包含所有需要的內容

感興趣的對象是ballpoly

  • 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 

Vec2Line2

為了使其更容易,矢量庫將有所幫助。對于示例,我編寫了一個快速Vec2Line2對象(注意僅示例中使用的函數已經過測試,注意該對象是為性能而設計的,沒有經驗的編碼人員應避免使用這些對象并選擇更標準的矢量和線庫)


查看完整回答
反對 回復 2022-10-08
?
智慧大石

TA貢獻1946條經驗 獲得超3個贊

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



查看完整回答
反對 回復 2022-10-08
  • 4 回答
  • 0 關注
  • 257 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號