Gemileri bir bezier boyunca iki gezegen arasında hareket ettirmek, hızlanma için bazı denklemleri kaçırmak


48

Tamam, bunu zaten math.stackechange.com adresinde yayınladım ancak yanıt alamadım :(

Öncelikle sorunumun bir resmi, açıklama daha sonra şöyle:

alt metin

Böylece tüm puanları ve değerleri belirledim.

Gemi sol gezegen etrafında hareket başlar P1ile S=0.27 Degreeso ulaştığında, gametick başına Point Ao ulaşana kadar bezier eğrisini takip başlar Point D, o zaman doğru gezegenin etrafında hareket P2ile S=0.42 Degreesoyun kene başına. Aradaki fark S, gezegenlerin çevresinde aynı hareket hızıyla hareket etmektir.

Şimdiye kadar çok iyi, ben bu kadar çalışmaya başladım, şimdi benim sorunum.

Çok S P1ve S P2farklı olduğu zaman , gemi varış noktasına ulaştığında iki hız arasında atlar ve bu oldukça kötü görünmektedir. Bu yüzden aralarında gemiyi hızlandırmak gerekiyor Point Ave Point Dgelen S P1etmek S P2.

Kaçırdığım şey mor renkte, bunlar:

  • Keneleri hesaplamanın bir yolu, ivmeyi dikkate alarak geminin bezier boyunca hareket etmesini gerektirir.

  • Ve yine ivmeyi göz önünde bulundurarak, T'ye dayalı bezier eğrisi üzerinde bir konum bulmanın bir yolu.

ATM Ben bezellerin uzunluğunu N, noktaları arasındaki mesafeyi hesaplayarak hesaplarım . Öyleyse ihtiyacım olduğunu düşündüğüm şey, Tsınır hesaplamamın içine koymayı istediğim ivmeyi ivmelenmeye göre ölçeklemenin bir yolu .


2
Bunu anlamak için iyi bir iş. Bulgularınızı sorunuza cevap olarak göndermenizi öneririm.
bummzack

Yanıtlar:


83

Tamam, her şey çalışıyor, sonsuza dek sürdü, bu yüzden ayrıntılı çözümümü buraya göndereceğim.
Not: Tüm kod örnekleri JavaScript’tedir.

Öyleyse sorunu temel parçalara ayıralım:

  1. 0..1Bezier eğrisi arasındaki noktaların yanı sıra uzunluğunu da hesaplamanız gerekir.

  2. Artık Tgemiyi bir hızdan diğerine hızlandırmak için ölçeklemenizi ayarlamanız gerekiyor.

Bezier'i Doğru Almak

Bezier eğrisi çizmek için bazı kodlar bulmak kolaydır, ancak bunlardan biri DeCasteljau Algoritması olmakla birlikte, birkaç farklı yaklaşım vardır , ancak aynı zamanda kübik Bézier eğrileri için de denklemi kullanabilirsiniz :

// Part of a class, a, b, c, d are the four control points of the curve
x: function (t) {
    return ((1 - t) * (1 - t) * (1 - t)) * this.a.x
           + 3 * ((1 - t) * (1 - t)) * t * this.b.x
           + 3 * (1 - t) * (t * t) * this.c.x
           + (t * t * t) * this.d.x;
},

y: function (t) {
    return ((1 - t) * (1 - t) * (1 - t)) * this.a.y
           + 3 * ((1 - t) * (1 - t)) * t * this.b.y
           + 3 * (1 - t) * (t * t) * this.c.y
           + (t * t * t) * this.d.y;
}

Bununla, şimdi çağrı yaparak xve yaralarında thangi aralıklarla bir bezier eğrisi çizebileceğimize 0 to 1bakalım:

alt metin

Uh ... bu gerçekten puanların eşit bir dağılımı değil, değil mi?
Bézier eğrisinin doğası gereği, noktalar 0...1farklıdır arc lenghts, bu nedenle başlangıç ​​ve bitişe yakın bölümler, eğrinin ortasına yakın olanlardan daha uzundur.

AKA yay uzunluğu parametresi eğrisinde eşit olarak T eşlemesi

Peki ne yapmalı? Eh basit terimlerle Nerede biz eşlemek için bir işlev gerekir Tüzerine t, eğrinin böylece bizim T 0.25sonuçlanır tde en o 25%eğrisinin uzunluğu.

Bunu nasıl yaparız? Şey, biz Google ... ... ancak bu terimin bu kadar mantıklı olmadığı ve bir noktada bu PDF’ye çarpacağın ortaya çıktı . Hangi kesinlikle harika bir okuma, ancak okulda öğrendiğiniz tüm matematik şeylerini unutmuş olmanız durumunda (veya sadece bu matematiksel sembolleri sevmiyorsanız), bu oldukça işe yaramaz.

Şimdi ne var? Öyleyse git ve Google biraz daha oku (6 saat) ve sonunda konuyla ilgili harika bir makale buldun (güzel resimler dahil! ^ _ ^ "):
Http://www.planetclegg.com/projects/WarpingTextToSplines.html

Gerçek kodun yapılması

Eğer sadece PDF bulunuyor indirirken dayanamadı rağmen zaten uzun, çok uzun zaman önce senin matematik bilgisi kaybetmişler (ve atlamak için yönetilen büyük bir makale linki), şimdi düşünebilir: "Tanrı, bu alacak Yüzlerce satır kod ve CPU

Hayır, olmaz. Çünkü tüm programcıların yaptıklarını, matematik işlerine gelince yapıyoruz :
Basitçe hile yapıyoruz.

Yay uzunluğu parametresi, tembel yol

Kabul edelim, oyunumuzda sonsuz bir hassasiyete ihtiyacımız yok, değil mi? Öyleyse Nasa'da çalışıyorsanız ve insanlara Mars'ı göndermeyi planlamıyorsanız, 0.000001 pixelmükemmel bir çözüme ihtiyacınız olmayacak .

Öyleyse nasıl harita oluşturuyorsunuz Tüzerine t? Çok basit ve sadece 3 adımdan oluşuyor:

  1. NEğri üzerindeki noktaları hesaplayın tve arc-length(yani eğrinin uzunluğu) dizisini o konumda saklayın

  2. Eşleştirmek için Tüzerine tilk çarpma, Talmak için eğrinin toplam uzunluğu ile uve daha sonra daha küçük büyük değer endeksi için uzunlukları dizi aramau

  3. Kesin bir vuruş yaptıysak, bu indeks içindeki dizi değerini ikiye Nböldük, bulduğumuz nokta ile bir sonraki arasında bir bit arasına sokmazsanız, bir kez daha bölüp Ngeri dönün.

Bu kadar! Şimdi tüm kodu inceleyelim:

function Bezier(a, b, c, d) {
    this.a = a;
    this.b = b;
    this.c = c;
    this.d = d;

    this.len = 100;
    this.arcLengths = new Array(this.len + 1);
    this.arcLengths[0] = 0;

    var ox = this.x(0), oy = this.y(0), clen = 0;
    for(var i = 1; i <= this.len; i += 1) {
        var x = this.x(i * 0.05), y = this.y(i * 0.05);
        var dx = ox - x, dy = oy - y;        
        clen += Math.sqrt(dx * dx + dy * dy);
        this.arcLengths[i] = clen;
        ox = x, oy = y;
    }
    this.length = clen;    
}

Bu yeni eğrimizi başlatır ve hesaplar, arg-lenghtsayrıca uzunlukların sonunu total lengtheğrinin sonu olarak kaydeder, burada this.lenbizim anahtar faktörümüzdür N. Haritalama ne kadar yüksekse, haritalama o kadar kesin olacaktır, çünkü yukarıdaki resimdeki boyutun bir eğrisi 100 pointsyeterli olacaktır, eğer sadece uzun bir tahminde bulunmanız gerekiyorsa, bizim gibi bir şey 25zaten sadece 1 piksel kapalı olacak gibi yapacaktır. örnek, ama sonra bir o kadar da dağıtım sonuçlanacak bir az kesin eşleme olacak Teşleştirilmiş zaman t.

Bezier.prototype = {
    map: function(u) {
        var targetLength = u * this.arcLengths[this.len];
        var low = 0, high = this.len, index = 0;
        while (low < high) {
            index = low + (((high - low) / 2) | 0);
            if (this.arcLengths[index] < targetLength) {
                low = index + 1;

            } else {
                high = index;
            }
        }
        if (this.arcLengths[index] > targetLength) {
            index--;
        }

        var lengthBefore = this.arcLengths[index];
        if (lengthBefore === targetLength) {
            return index / this.len;

        } else {
            return (index + (targetLength - lengthBefore) / (this.arcLengths[index + 1] - lengthBefore)) / this.len;
        }
    },

    mx: function (u) {
        return this.x(this.map(u));
    },

    my: function (u) {
        return this.y(this.map(u));
    },

Gerçek haritalama kodu, önce binary searchdaha küçük olan en büyük uzunluğu bulmak için depolanan uzunluklarımız üzerinde basit bir işlem yaparız targetLength, sonra sadece enterpolasyon ve geri dönüş yaparız.

    x: function (t) {
        return ((1 - t) * (1 - t) * (1 - t)) * this.a.x
               + 3 * ((1 - t) * (1 - t)) * t * this.b.x
               + 3 * (1 - t) * (t * t) * this.c.x
               + (t * t * t) * this.d.x;
    },

    y: function (t) {
        return ((1 - t) * (1 - t) * (1 - t)) * this.a.y
               + 3 * ((1 - t) * (1 - t)) * t * this.b.y
               + 3 * (1 - t) * (t * t) * this.c.y
               + (t * t * t) * this.d.y;
    }
};

Yine bu t, eğri üzerinde hesaplar .

Sonuçların zamanı

alt metin

Şimdi kullanarak mxve eğri üzerinde myeşit bir şekilde dağılmış olsun T:)

Zor değil miydi? Bir kez daha, basit (mükemmel bir çözüm olmasa da) bir oyun için yeterli olacağı ortaya çıktı.

Kodun tamamını görmek istiyorsanız, mevcut bir Gist vardır:
https://gist.github.com/670236

Sonunda gemileri hızlandırmak

Şimdi geriye kalan tek şey, gemileri yolunda hızlandırmak, Tdaha sonra teğrimizi bulmak için kullandığımız pozisyonu haritalamak .

Öncelikle ihtiyacımız iki hareket denklemlerinin , yani ut + 1/2at²ve(v - u) / t

Bu gibi görünen gerçek kodda:

startSpeed = getStartingSpeedInPixels() // Note: pixels
endSpeed = getFinalSpeedInPixels() // Note: pixels
acceleration = (endSpeed - startSpeed) // since we scale to 0...1 we can leave out the division by 1 here
position = 0.5 * acceleration * t * t + startSpeed * t;

Sonra bunu 0...1yaparak aşağı doğru ölçeklendiririz :

maxPosition = 0.5 * acceleration + startSpeed;
newT = 1 / maxPosition * position;

Ve işte gidiyorsunuz, gemiler şimdi yol boyunca sorunsuz hareket ediyor.

İşe yaramazsa ...

Bunu okurken, her şey yolunda ve zahmetli çalışıyor, ama başlangıçta ivme bölümüyle ilgili bazı problemler yaşadım, sorunu gamedev sohbet odasındaki birine açıklarken düşüncelerimdeki son hatayı buldum.

Asıl sorudaki resim hakkında henüz bir şey unutmadıysanız, sorada belirtmek isterim ki, o dereces hızdır , ancak gemiler yol boyunca pikseller halinde hareket ederler ve ben de bu gerçeği unuttum. Bu durumda yapmam gereken, yer değiştirmeyi derece cinsinden piksel cinsinden yer değiştirmeye dönüştürmekti, bunun oldukça kolay olduğu ortaya çıktı:

function rotationToMovement(planetSize, rotationSpeed) {
    var r = shipAngle * Math.PI / 180;
    var rr = (shipAngle + rotationSpeed) * Math.PI / 180;
    var orbit = planetSize + shipOrbit;
    var dx = Math.cos(r) * orbit - Math.cos(rr) * orbit;
    var dy = Math.sin(r) * orbit - Math.sin(rr) * orbit;
    return Math.sqrt(dx * dx + dy * dy);
};

Demek hepsi bu! Okuduğunuz için teşekkürler;)


7
Bu sindirimi biraz zaman alacak. Ama vay, kendi sorunuza şaşırtıcı cevap.
Saldıran Hoo

7
Sadece bu cevabı yükseltmek için bir hesap yaptım
Kimse

Bazı puanlar arkadaşım var. Bir cazibe gibi çalıştı. Her ikisi de Olumsuz oylandı.
Jace

2
'i' 0.05 ile çarpılırken, 'len' 100 olarak ayarlanır. Bu, '0-1' yerine '0-5' ile eşlenir.
Evil Activity

1
@EvilActivity Evet, ben de gördüm, orijinal uzunluğu 20 olmalı, ardından 0.05 ile 0.01 arasında değişmeyi unuttum. Bu yüzden daha iyi bir dinamik 'len' (gerçek yay uzunluğuna adapte olabilir, hatta tam olarak ona eşit olabilir) ve "adım" ı 1 / 'len' ile hesaplayın. Öyle tuhaf buluyorum ki, bunca yıldır başka kimse bunu yetiştirmedi!
Bill Kotsias

4

Mesele şu ki, bir gemi bu yörüngeyi doğal olarak ele alamaz. Böylece kusursuz çalışması bile doğru bir şekilde görünmeyecek.

Gezegenler arasındaki yumuşak geçişi simüle etmek istiyorsanız, aslında onu modellemenizi öneririm. Denklemler sadece iki önemli kuvvete sahip olduğunuz için çok basittir: yerçekimi ve itme.

Sabitlerinizi ayarlamanız yeterlidir: P1, P2, gemi kütlesi

Her oyun kene (zaman: t) ile 3 şey yapıyorsun

  1. P1'in gemideki ağırlığını ve p2'nin gemideki ağırlığını hesaplayın, elde edilen vektörleri baskı vektörüne ekleyin.

  2. 1. adımdan itibaren yeni hızlanmanıza dayanarak yeni hızınızı hesaplayın.

  3. Gemiyi yeni hızına göre hareket ettir

Çok fazla iş gibi görünebilir ama bir düzine kod satırında yapılabilir ve çok doğal görünecektir.

Fizik konusunda yardıma ihtiyacınız olursa bana bildirin.


Bunu yapmanın bir yolunu sağlayabilirseniz, bunu test etmeyi düşünebilirim t:)
Ivo Wetzel

-Ama oyun programlamasında değişken olarak t kullanmıyorsunuz. Zaten temelde parametrik bir durumdasınız, çünkü sadece gemi için yeni dx ve dy'yi hesaplıyorsunuz. İşte iki gezegenin yörüngesine bir örnek (Flash'ta) aharrisbooks.net/flash/fg2r12/twoPlanets.html - ve işte Python'da aynı şey: aharrisbooks.net/pythonGame/ch09/twoPlanets.py
İki pi

2

Javascript'te yazılmış bir kod örneği ile bu soruna olası bir çözümü açıklayan mükemmel bir makale buldum . "T" değerini doğru yöne dürtmek suretiyle çalışır.

Bunun yerine, herhangi bir nokta dağılımı için ortalama bacak uzunluğunun d_avg'ın, eşit aralıklarla yerleştirilmiş noktaların üreteceği bacak uzunluklarıyla neredeyse aynı olduğu gerçeğini kullanabiliriz (bu benzerlik n arttıkça artar). Gerçek bacak uzunlukları d ile ortalama bacak uzunluğu d_avg arasındaki d_err farkını hesaplarsak, bu farkı azaltmak için her noktaya karşılık gelen zaman parametresi t döndürülebilir.

Bu sorunun çok güzel cevapları var; ama bu çözümü fark etmeye değer buldum.


1

Bu sorunu nasıl çözdüğünüzü anlatan mükemmel sayfanız için teşekkür ederiz. Ben derinlemesine kısıtlı olduğum için senden biraz farklı bir şey yaptım: bir dizi yapmam ya da ikili arama ile doğru 'segmenti' aramak zorunda kalıyorum. Bunun sebebi, her zaman Bezier eğrimin bir ucundan diğerine geçeceğimi bildiğim için: Bu nedenle, sadece 'mevcut' segmenti hatırlıyorum ve eğer görürsem, bir sonraki adımımı hesaplamak için o segmentin sınırlarından çıkacağımı görürsem pozisyonu, sonraki (veya önceki) segmenti (seyahat yönüne göre) hesaplarım. Bu benim başvurum için oldukça iyi çalışıyor. Çalışmam gereken tek aksaklık, bazı eğrilerde, segmentlerin boyutunun o kadar küçük olmasıydı ki, işaret edeceğim bir sonraki planım - nadir zamanlarda - mevcut olanın önünde birden fazla segment oldu, yani sadece gitmek yerine 'ye

Bu oldukça mantıklı mı bilmiyorum, ama bu kesinlikle bana yardımcı oldu.


0

Bu tür bir modelleme tuhaf ve garip mantıksız sonuçlar doğurabilir. Özellikle de başlangıç ​​gezegenlerinin hızı gerçekten yavaşsa.

Gemileri bir itme gücüyle modelleyin.

Gemiler başlangıç ​​gezegenindeki son yörüngelerindeyken, tam itiş ile hızlanın.

Gemi belli bir mesafeye girdiğinde, gemiyi hedef gezegenin yörünge hızına düşürmek için ters itme kullanın.

Düzenleme: Bir düğüm yörüngeden ayrılmak üzereyken simülasyonun tamamını bir kerede yapın. ya tüm verileri gönderir ya da aralıklarla sadece birkaç hareket vektörü gönderir ve aralarında enterpolasyon yapar.


Sorun şu ki, tüm bunlar kene tabanlı, ara konum yok. Bu bir ağ oluşturma çok oyunculu oyun ve tam bir oyunda 600'den fazla geminin tüm konumlarını göndermek tüm ağları öldürür. Sadece bir tickOffset ileten olaylar var, gerisi mevcut dünya kene ve ofsetine göre hesaplanıyor.
Ivo Wetzel

Yanıtımı değiştirdim.
Saldırı

0

Doğru anladıysam, problemin aşırı sınırlı.

Biraz zaman içinde yörüngeleri arasında belirli bir yol boyunca seyahat için uzay istediğine inanıyorum t ve ayrıca bu hız dan hızlandırmak isteyen s1 hız için s2 aynı zaman içinde t . Ne yazık ki, (genel olarak) bu kısıtlamaları her ikisini de aynı anda sağlayan bir ivme bulamıyorsunuz.

Sorununuzu çözülebilir hale getirmek için biraz rahatlamanız gerekecek.


2
O zaman nasıl rahatlarsın? Hayal edebildiğim şey, bezier yolundaki şeylere taktığım T'yi değiştirmek. Bir şekilde ilk önce 0,5'e yavaş ve sonra 1'e yavaş büyümesi için ölçeklendirmem gerekecek. Böylece gemi orijinal hızından eğrinin ortasındaki sabit bir hıza yavaşlar ve sonra tekrar bu hızdan sonunda hıza doğru hızlanır. Eğrinin
Ivo Wetzel,

1
Bence uzay gemisi orjinal hızından transferin orta noktasında bir yere doğru hızlanırsa ve ardından yeni yörüngeye yavaşlarsa daha gerçekçi gözükeceğini düşünüyorum.
Gareth Rees

Yine de, ivmeyi her şeye nasıl sokacağım konusunda sıkıştım, T'yi bir şekilde değiştirmem gerekiyor: /
Ivo Wetzel

0

Bu cevaba rastladım, çünkü bir bezier eğrisi kullanan bir svg yolu boyunca noktaları eşit olarak dağıtmayı düşünüyorum.

MDN'nin onaylanmadığını söylemesine rağmen path.getPointAtLength, doğru sonucu almak için kullanabilirsiniz . https://developer.mozilla.org/en-US/docs/Web/API/SVGPathElement/getPointAtLength

Şu anda Chrome / Safari / Firefox'ta çalışıyor ve IE / Edge'de de çalışması gerekiyor, ancak bu 2’yi doğrulamamıştım.


-1

Kabul edilen çözümle ilgili sorun

Bezier olarak üstel bir fonksiyon olarak, eğrinin farklı alanlarında farklı ilerleme oranları bekliyoruz.

Ivo'nun çözümü , bu ilk üstel numuneler arasında doğrusal olarak enterpolasyon yaptığı için, bu deltaların en büyük olduğu (tipik olarak kübik) eğrinin uçlarına / ortasına doğru yanlışlıklar büyük ölçüde önyargılı olacaktır; Bu nedenle, örnekleme oranı önerdiği gibi büyük ölçüde arttırılmadığı sürece , hatalar belirgindir ve bazı yakınlaştırma seviyelerinde verilenler için her zaman belirgin olacaktır ; Zumun sınırsız olabileceği vektör tabanlı grafikler için iyi değil.NN

Kılavuzlu örnekleme yoluyla önyargı

Alternatif bir çözüm doğrusal eşleştirmek için distanceiçin tBezier işlevi üreten doğal önyargı mücadele sonrası.

Bunu varsaymak, ideal olarak istediğimiz şeydir:

curve length = 10

t      distance
0.2    2
0.4    4
0.6    6
0.8    8
1.0    10

fakat Bezier pozisyonu fonksiyonundan elde ettiğimiz şey şudur:

t      distance
0.2    0.12
0.4    1.22
0.6    2.45
0.8    5.81
1.0    10.00

NAlınan örneklere bakarak, mesafe deltalarının nerelerde en büyük olduğunu görebiliriz ve iki bitişik mesafenin ortasında yeniden örnekleme ("bölme") 'i N1 ile artırabiliriz . Örneğin, bölmeye t=0.9(en büyük deltanın ortasındaki) almak:

0.8    5.81
0.9    7.39
1.0    10.00

Bu işlemi, tüm kümedeki iki mesafe arasındaki maksimum delta bazılarının altında minDistanceDeltave daha spesifik olarak, epsilonharitalandırmak istediğiniz belirli mesafelerden uzakta olandan daha az olana kadar bir sonraki en büyük mesafe aralığı için tekrarlıyoruz t; daha sonra istenen tadımlarımızı ilgili distances'ye doğrusal olarak eşleyebiliriz . Bu, ucuz bir şekilde erişebileceğiniz ve çalışma zamanında önyargısız değerler arasında alabileceğiniz bir karma tablo / harita oluşturur.

Set büyüdükçe N, bunu tekrarlamanın maliyeti artar, bu yüzden ideal olarak bunu ön işlem olarak yapın. Her zaman Nartarsa, intervalsdeğiştirdikleri eski, tek aralığı kaldırırken iki yeni sonuç aralığını da koleksiyona ekleyin . Bu, ikiye bölünecek bir sonraki en büyük aralığı bulmaya çalıştığınız yapıdır. intervalsMesafeye göre sıralı tutmak , sadece bir sonraki iş öğesini uçtan aşağıya çekebileceğiniz ve bölebileceğiniz gibi şeyleri kolaylaştırır.

İdeal olarak istediğimiz gibi bir şeyle sonuçlanır:

epsilon: 0.01

t            distance
0.200417     2.00417
0.3998132    3.9998132
0.600703     6.00703
0.800001     8.00001
0.9995309    9.995309

Biz her adımda tahmin alıyor yana, tam kesin mesafeler almazsınız 2, 4istediğimiz vb, ancak eşleme böylece tekrar yoluyla yineleme bu istenen mesafe değerlerine yakın yeterince olsun tadil doğrulukla adımlar nedeniyle önyargı ortadan kaldırarak, yakın eşit örneklemeye.

Daha sonra, örneğin t=0.5, Ivo'nun cevabında olduğu gibi, yani yukarıdaki en yakın iki değer ( 3.9998132ve 6.00703) arasında araya girerek alabilirsiniz .

Sonuç

Çoğu durumda, Ivo'nun çözümü iyi çalışacaktır, ancak her ne pahasına olursa olsun önyargıdan kaçınılması gereken durumlar distanceiçin, s'nizin mümkün olduğunca eşit dağıldığından ve daha sonra doğrusal olarak eşlendiğinden emin olun t.

Bölünmenin, her seferinde ortadan bölmek yerine, stokastik olarak yapılabileceğine dikkat edin; örneğin, ilk örnek aralığı t=0.827değil de bölebiliriz t=0.9.

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.