Hızlandırılmış sınırlı nesnelerin yollarını nasıl hesaplayabilirim?


9

Örneğin, bir arabam olduğunu ve bir otomobilin belirli bir minimum dönüş yarıçapına sahip olduğunu ve o arabayı a noktasından b noktasına götürmek istediğimi, ancak araba b noktasına bakmadığını varsayalım. B noktasına giden bir yolu nasıl hesaplayabilirim? B noktasında yönlendirmeyi belirleyebilmek de iyi olurdu (araba yolunuza sürmek ve daha sonra garajınıza çekmek istediğinizi söyleyin - çim yolunuzu sürerek araba yolunuza ulaşırsanız çok iyi olmaz. ve yana bakacaksınız :)

Belgelere (ya da sadece bir isme) işaretçi mükemmel bir şekilde iyi olurdu - bir şey bulmakta zorlanıyorum.

Girişimlerimde, basit durumlarda çalışırlar, ancak b noktasının minimum dönüş yarıçapından daha yakın olduğu durumlarda sefil bir şekilde başarısız olurlar.

Örneğin, buna benzer bir yolu (kalın yol) nasıl belirlersiniz:

Açıklama amacıyla sadece eğri bir yol

edit: Benim gerçek sorun, bazı basit yol kısıtlamaları vardır, ama ben zaten yerinde çalışan bir A * algoritması var, ama şeyler ani başlık değişiklikleri yapmak için izin verir, bu yüzden bir araba aniden 90˚ dönüş yapmak aptalca görünüyor bir dönüş noktasına ulaştıklarında bir kuruşa.


gamedev.stackexchange.com/questions/86881/… ama 3d alanı nasıl kuracağımı anladığımdan emin değilim
xaxxon

"ideal olarak bu algoritma değişen hız ile başa çıkabilecektir" Minimum dönüş yarıçapı değiştikçe hız ile ilgili mi, yoksa herhangi bir araç için sabit mi?
DMGregory

O kısmı sileceğim. Yaptığım şey için, "gran tourismo" dan daha çok "sim city". Neden bunu sorduğunuzu anlıyorum ve bunu eklediğimde ne düşündüğümden emin değilim, çünkü bunun alakasız olduğunu anlıyorum.
xaxxon

Bezier eğrisi diyagramı , sınırlı hızlanma ile yol planlamasına ilişkin bu diğer cevabı biraz hatırlattı - bu durumda hızlanma, dönüş yarıçapından ziyade yönlü bir roket pervanesi gibi modellenmiştir, ancak yine de bazı yararlı fikirler ortaya çıkabilir.
DMGregory

Yanıtlar:


7

Bunun için henüz tam denklemler üzerinde çalışmadım, ama başımıza problemin etrafını sarmaya yardımcı olacak bazı görseller var. Bazı geometriye kaynar:

Dönüş yarıçapını gösteren daireler bulunan bir araba. ( Kenney ile araba simgeleri )

Herhangi bir başlangıç ​​noktası ve yönlendirmeden, minimum dönüş yarıçapımızla biri solda, diğeri sağda olmak üzere iki daire çizebiliriz. Bunlar, yolumuza mümkün olan en sıkı başlangıçtaki noktaları açıklar.

İstediğimiz son konum ve yön için de aynısını yapabiliriz. Bu daireler yolumuzun mümkün olan en sıkı sonunu tanımlar.

Şimdi sorun, başlangıç ​​çemberlerinden birini bitiş çemberlerinden birine birleştiren ve her birini tanjant boyunca öpen bir yol bulmada azalıyor.

(Bu, soruda bahsedilmeyen aradaki engeller etrafında yol bulmamız gerekmediğini varsayıyor. Stormwind'in cevabı, bu tür problemler için navigasyon grafik bilgilerini nasıl kullanabileceğimize geliyor. geçmek için aşağıdaki yöntemi planın her bir bölümüne uygulayabiliriz.)

Basitlik için düz çizgiler kullanırsak, şöyle bir şey alırız:

Bir araba alabilir çeşitli yollar gösteren diyagram.

Bu bize sınırlayıcı davayı verir. Bu yöntemle bir yol bulduktan sonra, iki dairenin öpüştüğü noktaya kadar daha az doğrudan ama daha düzgün bir yol elde etmek için başlangıç ​​veya bitiş dairelerinden birini veya her ikisini yapay olarak şişirebilirsiniz.

Bu yolları hesaplamak

Vakaları bir dönüş yönü için çalışalım - yolumuza sağa dönerek başladığımızı varsayalım.

Sağa dönüş dairemizin merkezi:

startRightCenter = carStart.position + carStart.right * minRadius

Yolumuzun düz bölümünün açısını diyelim (pozitif x ekseninden ölçülür) pathAngle

Biz bir vektör çizerseniz rightCenterbiz dönüş çapı (ki biz pathAngle dönük olmalıdır işaret) terk noktaya, vektör daha sonra bu ...

startOffset = minRadius * (-cos(pathAngle), sin(pathAngle))

Bu, daireyi terk ettiğimiz noktanın ...

departure = startRightCenter + startOffset

Bir dönüş çemberine tekrar girdiğimiz nokta, sola mı yoksa sağa mı dönmeyi hedeflediğimize bağlıdır:

// To end with a right turn:
reentry = endRightCenter + startOffset

// To end with a left turn: (crossover)
reentry = endLeftCenter - startOffset

Biz işimizi doğru yaptık Şimdi, eğer, çizgi birleştirme departureiçin reentrydik olması gerektiğini startOffset:

dot(reentry - departure,  startOffset) = 0

Ve bu denklemi çözmek bize bunun doğru olduğu açı (ları) verecektir. (Burada çoğul kullanıyorum çünkü teknik olarak böyle iki açı var, ancak bunlardan biri tersine sürmeyi içeriyor, bu genellikle istediğimiz şey değil)

Örnek olarak sağa dönüşü sağa dönüş davasına koyalım:

dot(endRightCenter + startOffset - startRightCenter - startOffset, startOffset) = 0
dot(endRightCenter - startRightCenter, startOffset) = 0
pathAngle = atan2(endRightCenter - startRightCenter)

Geçiş davası daha karmaşıktır - henüz tüm matematiği henüz çalışmadığım. Kalan ayrıntıları incelerken sizin için yararlı olması durumunda, cevabı şimdilik olmadan göndereceğim.

Düzenleme: Minimum dönüş yarıçapı içindeki hedef

Görünüşe göre, bu yöntem, varış noktası minimum dönüş mesafemizden daha yakın olsa bile genellikle kutudan çıkar çıkmaz çalışır. Yeniden giriş çevrelerinden birinin en azından bir kısmı dönüş yarıçapının dışına çıkıyor ve biraz tuzlu kraker benzeri bir şey elde etmemize izin vermediğimiz sürece uygun bir yol bulmamıza izin veriyor ...

Yakın bir hedefe yol planlama yaparken seçenekleri gösterme.

Eğer yolu bu şekilde beğenmezsek (ya da mümkün değilse - her vakayı ayrıntılı olarak kontrol etmedim - belki imkansız olanlar var), uygun olana kadar her zaman doğrudan ileri veya geri gidebiliriz Yukarıda gösterildiği gibi bir başlangıç ​​ve bitiş çemberi arasındaki öpüşme kontağı.


Bunu düşünmenin güzel ve basit bir yolu ve dairelerdeki teğetlerle çalışmak oldukça kolay. Cevabınızı şimdiye kadar gözden kaçırdım, ancak her yaklaşımda bir sorun, hedefin başlangıç ​​noktasının dönüş çemberlerinde olması.
xaxxon

Bununla başa çıkmayı bildiğim en basit politika, dönüş çevrelerinizden birinde olana kadar tersine dönüp sonra ona dönmektir. Hedef yönlendirmeyle, başlangıç ​​ve bitiş dönüş çemberleri bir yere öpene kadar ters çevirirsiniz. Bu durumu görselleştirmek için bir diyagram ekleyeceğim.
DMGregory

2
Bir ay (ve birkaç dikkat dağıtıcı) sonra bu işe başladım. 4 teğet hesaplıyorum - "dış" ve "iç" (veya "geçiş") teğetler. Öyleyse start.left_circle öğesini goal.left_circle'a, start.left_circle goal.right_circle 'a "crossing" (ve sonra diğer ikisi daireleri değiştirerek). İşte bir "dış" yol: youtube.com/watch?v=99e5Wm8OKb0 ve işte bir "geçiş" yolu: youtube.com/watch?v=iEMt8mBheZU
xaxxon

1

Bu, navigasyon için veri modelinizin geri kalanına bağlıdır. Yani. elinizde hangi veriler, ne kolayca veri ekleyebilir ve bunları nasıl tüketebilirsiniz.

Benzer bir senaryoyu sudaki bir trafik sisteminden almak ve

  • bir oyun döngüsündesin
  • bir düğüm yolu sisteminiz var
  • araçlarınız kendi güçlerini ve direksiyonlarını kullanarak kendilerini "içeriden" kontrol eden özerk bir nesne gibi davranıyor
  • arabalarınız raylardaki gibi hareket etmiyor

aşağıdaki gibi bir şey olabilir (resimlerin çocuksu görünümü için beni affet)

resim açıklamasını buraya girin

(Kırmızı kareler düğümlerdir, kırmızı çizgiler düğüm ara bağlantılarıdır. 1-9 arası düğümler veren bir yol bulma çözücüsü kullandığınızı; resimde 4-9 düğümler görüldüğünü ve yeşil çizgiyle gösterilen düğümlerden geçmek istediğinizi varsayalım , düğüm # 9'daki garaja; ancak yeşil çizgiye tam olarak gitmek istemiyorsunuz, bunun yerine doğal olarak sağ şeritte kalıyor ve pürüzsüz manevralar yapıyorsunuz).

Her düğüm , çeşitli amaçlar için örneğin bir yarıçapı veya birden çok olanı tutan meta verilere sahip olacaktır . Bunlardan biri , arabalara yönelik rehberlik sağlayan mavi daire .

En herhangi bir durum , taşıt ihtiyaçları aşağıdaki iki düğüm noktaları farkında (bir sonraki) P ve P (aşağıdaki + 1), ve pozisyonları. Doğal olarak, otomobilin de bir konumu var. Bir araba P'nin mavi meta veri çemberinin sağ tarafındaki tanjantını hedefler (sonraki). Arabalar ters yöne gidiyor, bu yüzden çarpışmayacaklar. Teğeti hedeflemek, otomobilin daireye herhangi bir yönden yaklaşabileceği ve her zaman doğru kalabileceği anlamına gelir. Bu, pek çok açıdan geliştirilebilecek kaba bir temel ilkedir.

Bir mesafeyi belirlemek için P (sonraki + 1) gereklidir - araç P'ye (sonraki) ulaştığında veya meta verilerinin bir yarıçapına girdiğinde, P (sonraki + 1) mesafesine bağlı olarak direksiyon açısını ayarlayabilir . Yani. eğer yakınsa, fazla dön, eğer çok uzaklarda, az dön. Görünüşe göre başka kurallar ve kenar koşulları da olmalı, örneğin araba ile P (sonraki) ve P'nin (sonraki + 1) sağ taraf teğetlerine dayanan bir yardım hattı arasındaki mesafenin hesaplanması ve bunun düzeltilmesi - kesikli (resim üstü) ve noktalı (resim altı) çizgi üzerinde kalma isteği.

Her durumda, araba bir düğümü geçerken , onu unutur ve sonraki iki taneye bakmaya başlar .

Sorunuza. Görünüşe göre, düğüm 7'ye ulaştığınızda (yukarıdaki resimde, aşağıdaki resimde düğüm 2 olarak görülüyor), yeterince dönemiyor .

resim açıklamasını buraya girin

Olası bir çözüm için bazı yardım hatlarını inşa ve bir hedefe her zaman korumak ve sonra o kendi fizik ayarlarından araba hareket (belirli bir oranda hızlandırmak zorunda ters yavaş, dikkate de frenleri düğüm meta speedlimits almak Verilen veya hesaplanmış G, vb.). Söylendiği gibi, araba bu senaryoda özerk, kendini tanımlayan, kendini taşıyan bir nesnedir.

Yeşil yardım hatlarına 1,2,3 sahip olun . Araba eflatun daireye ulaştığında, sağa dönmeye başlar. Bu noktada, başarılı olamayacağını zaten hesaplayabilirsiniz (maksimum dönüş oranını biliyorsunuz ve eğriyi hesaplayabiliyorsunuz ve her iki yardım hattını da 2 ve 3'ü geçeceğini görebilirsiniz). Direksiyonu tam sağa çevirin ve ilerlemesine izin verin (fizik artışlarıyla) ve yardım hattı 3'e ulaştığında yavaşlayın (kullanım sınırlarına yaklaştığında, f (yardım hattına göre) vb.). O olduğu zaman en yardım hattı 3, gitmek ters , moda direksiyonu çevirin tam tersi . Yardım hattına 4 ulaşıncaya kadar ters çevirelim(düğüm 1 ve 2 arasındaki bağlantı çizgisi - "satır algoritmasının kenarındaki nokta" için google). Yavaşlayın, ulaştığında tekrar ileri sürüş moduna geçin , tekerleği çevirin. Yol açık olana kadar tekrarlayın - görünüşe göre bu sefer 1 ekstra manevra ile yeterliydi.

Bu genel fikir: Oyun döngüsü sırasında veya oyun görevi que sistemini kontrol ederken:

  • Mevcut kenar sınırlarına ve hedefine göre araç konumunu, hızını, açısını vb. Kontrol edin ,
  • Eğer henüz ulaşamamış , ne yaptığını devam (izin fizik taşımak, araba bir rpm ve bir dişli vardır). Örneğin, 0,1 sn. İçinde olmak için que sisteminize yeni bir kontrol ekleyin.
  • Eğer ulaştı , calqculate yeni koşullar, veri set ve başlamak . Que sisteminde gerçekleşmesi için yeni bir kontrol ekleyin, örneğin 0,1 sn.
  • Döngü döngüsünü tamamlayın - devam edin, tekrarlayın.

Düğümlere ve araçlara yeterli veri verilerek hareket ve devam olacaktır.

Düzenleme: Ve ekleme: Bu doğal olarak ince ayar gerektirir. Simülasyonunuz farklı yardım hatları, meta veriler, çevreler, herhangi bir şey gerektirebilir. Bu, olası bir çözüm hakkında fikir verecektir.


Cevabınızı okumak biraz zaman alacak. Ben zaten kurmak ve çalışma genel pathfinding var, ama nesneleri herhangi bir noktada sonsuz hızlanma yapmak için izin verir.
xaxxon

Rastgele, aslında tarif ettiğinize oldukça yakın bir şeyim var. Mor "hareketli" çizgi işlemsel olarak iki düz çizgiden tamamen oluşturulur: youtube.com/watch?v=EyhBhrkmRiY ancak "sıkı" durumlarda çalışmaz ve eğri gerçek yol bulma için kullanılmaz.
xaxxon

0

DMGregory'nin önerdiğini yaptım ve iyi çalışıyor. İki teğet stilini hesaplamak için kullanılabilecek bazı ilgili kodlar (bağımsız olmasa da). Eminim bu kod verimli değildir ve muhtemelen her durumda doğru değildir, ancak şu ana kadar benim için çalışıyor:

bool Circle::outer_tangent_to(const Circle & c2, LineSegment & shared_tangent) const {
    if (this->direction != c2.direction) {
        return false;
    }
    if (this->radius != c2.radius) {
        // how to add it: http://mathworld.wolfram.com/Circle-CircleTangents.html
        // just subtract smaller circle radius from larger circle radius and find path to center
        //  then add back in the rest of the larger circle radius
        throw ApbException("circles with different length radius not supported");
    }

    auto vector_to_c2 = c2.center - this->center;
    glm::vec2 normal_to_c2;
    if (this->direction == Circle::CW) {
        normal_to_c2 = glm::normalize(glm::vec2(-vector_to_c2.y, vector_to_c2.x));
    } else {
        normal_to_c2 = glm::normalize(glm::vec2(vector_to_c2.y, -vector_to_c2.x));
    }

    shared_tangent = LineSegment(this->center + (normal_to_c2 * this->radius),
                                 c2.center + (normal_to_c2 * this->radius));
    return true;
}


bool Circle::inner_tangent_to(const Circle & c2, LineSegment & tangent) const {

    if (this->radius != c2.radius) {
        // http://mathworld.wolfram.com/Circle-CircleTangents.html
        // adding this is non-trivial
        throw ApbException("inner_tangents doesn't support circles with different radiuses");
    }

    if (this->direction == c2.direction) {
        // inner tangents require opposing direction circles
        return false;
    }

    auto vector_to_c2 = c2.center - this->center;
    auto distance_between_circles = glm::length(vector_to_c2);

    if ( distance_between_circles < 2 * this->radius) {
//      throw ApbException("Circles are too close and don't have inner tangents");
        return false;
    } else {
        auto normalized_to_c2 = glm::normalize(vector_to_c2);
        auto distance_to_midpoint = glm::length(vector_to_c2) / 2;
        auto midpoint = this->center + (vector_to_c2 / 2.0f);

        // if hypotenuse is oo then cos_angle = 0 and angle = 90˚
        // if hypotenuse is radius then cos_angle = r/r = 1 and angle = 0
        auto cos_angle = radius / distance_to_midpoint;
        auto angle = acosf(cos_angle);

        // now find the angle between the circles
        auto midpoint_angle = glm::orientedAngle(glm::vec2(1, 0), normalized_to_c2);

        glm::vec2 p1;
        if (this->direction == Circle::CW) {
            p1 = this->center + (glm::vec2{cos(midpoint_angle + angle), sin(midpoint_angle + angle)} * this->radius);
        } else {
            p1 = this->center + (glm::vec2{cos(midpoint_angle - angle), sin(midpoint_angle - angle)} * this->radius);
        }

        auto tangent_to_midpoint = midpoint - p1;
        auto p2 = p1 + (2.0f * tangent_to_midpoint);
        tangent = {p1, p2};

        return true;
    }
};

İşte yukarıdaki kodun iki filmi iş başında:

İşte "dış" bir yol: http://youtube.com/watch?v=99e5Wm8OKb0 ve işte bir "geçiş" yolu: http://youtube.com/watch?v=iEMt8mBheZU

Bu kod yardımcı olur, ancak burada gösterilmeyen bazı bölümleri hakkında sorularınız varsa, sadece bir yorum gönderin ve bir veya iki gün içinde görmeliyim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.