Déplacement de vaisseaux entre deux planètes le long d'un bezier, manque quelques équations pour l'accélération

48

OK, j'ai déjà posté ça sur math.stackechange.com mais je n'ai pas eu de réponse :(

Voici d'abord une photo de mon problème, la description suit:

texte alternatif

J'ai donc mis en place tous les points et toutes les valeurs.

Le vaisseau commence à se déplacer sur la planète gauche P1avec un S=0.27 Degreesgametick, quand il arrive, Point Ail commence à suivre la courbe de Bézier jusqu'à atteindre Point D, puis il tourne autour de la bonne planète P2avec un S=0.42 Degreestick par partie. La différence en Sest que le voyage avec la même vitesse de déplacement autour des planètes.

Jusqu'ici tout va bien, je l'ai mis en place, maintenant mon problème.

Quand S P1et S P2diffèrent à bien, le navire saute entre les deux vitesses quand il atteint sa destination, ce qui semble assez mauvais. J'ai donc besoin d'accélérer le navire entre Point Aet Point Dde S P1à S P2.

La chose qui me manque sont en violet, ce sont:

  • Une façon de calculer les tics qu'il faut au navire pour se déplacer le long de la couche de Bézier compte tenu de l'accélération.

  • Et un moyen de trouver une position sur la courbe de Bézier basée sur T, en considérant à nouveau l'accélération.

ATM Je calcule la longueur de la couche de Bézier en calculant la distance entre Nses points. Donc, ce dont je pense avoir besoin, c’est un moyen de redimensionner celui que Tj’ai besoin d’inscrire dans le calcul de Bézier en fonction de l’accélération.

Ivo Wetzel
la source
2
Bon travail pour comprendre cela. Je suggère que vous publiez vos résultats en réponse à votre question.
Bummzack

Réponses:

83

OK, tout fonctionne, cela a pris une éternité, alors je vais poster ici ma solution détaillée.
Remarque: tous les exemples de code sont en JavaScript.

Alors divisons le problème en deux parties:

  1. Vous devez calculer la longueur de, ainsi que les points entre 0..1la courbe de Bézier

  2. Vous devez maintenant ajuster l’échelle de votre Tpour accélérer le vaisseau d’une vitesse à l’autre.

Bien faire le Bézier

Il est facile de trouver du code pour dessiner une courbe de Bézier, mais il existe différentes approches. L'une d'entre elles est l' algorithme DeCasteljau , mais vous pouvez également utiliser l' équation pour les courbes de Bézier cubiques:

// 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;
}

Avec cela, on peut maintenant dessiner une courbe de Bézier en appelant xet yavec tqui va de 0 to 1, jetons un coup d'oeil:

texte alternatif

Euh ... ce n'est pas vraiment une répartition uniforme des points, n'est-ce pas?
En raison de la nature de la courbe de Bézier, les points 0...1sont différents arc lenghts, donc les segments proches du début et de la fin sont plus longs que ceux situés près du milieu de la courbe.

Cartographie de T uniformément sur la courbe AKA paramétrage longueur d'arc

Alors que faire? En termes simples, nous avons besoin d’une fonction pour mapper notre Tsur tla courbe, de sorte que nos T 0.25résultats dans let qui est en 25%de la longueur de la courbe.

Comment fait-on cela? Eh bien, nous Google ... mais il s’avère que le terme n’est pas acceptable , et à un moment donné, vous atteindrez ce fichier PDF . Ce qui est sûr, c'est une bonne lecture, mais dans le cas où vous avez déjà oublié tout ce que vous avez appris en mathématiques à l'école (ou que vous n'aimez pas ces symboles mathématiques), c'est plutôt inutile.

Et maintenant? Eh bien, allez un peu plus sur Google (lisez: 6 heures), et vous trouverez enfin un excellent article sur le sujet (y compris de belles images! ^ _ ^ "):
Http://www.planetclegg.com/projects/WarpingToTplines.html

Faire le code actuel

Au cas où vous ne pouviez pas résister à télécharger ce fichier PDF alors que vous aviez déjà perdu vos connaissances en mathématiques il y a très longtemps (et que vous avez réussi à ignorer la bonne lien de article), vous pourriez maintenant penser: "Mon Dieu, cela prendra des centaines de lignes de code et des tonnes de CPU "

Non, ça ne va pas. Parce que nous faisons ce que tous les programmeurs font en matière de maths:
nous trichons tout simplement.

Paramétrage de la longueur d'arc, paresseux

Voyons les choses en face, nous n'avons pas besoin d'une précision sans fin dans notre jeu, n'est-ce pas? Donc, sauf si vous travaillez à la Nasa et que vous envisagez d'envoyer des gens sur la Mars, vous n'aurez pas besoin d'un0.000001 pixel solution parfaite.

Alors , comment pouvons-nous cartographions Tsur t? C'est simple et ne comporte que 3 étapes:

  1. Calculer des Npoints sur la courbe en utilisant tet stocker le arc-length(autrement dit la longueur de la courbe) à cette position dans un tableau

  2. Pour cartographier Tsur t, d' abord multiplier Tpar la longueur totale de la courbe pour obtenir uet rechercher ensuite la gamme de longueurs pour l'indice de la plus grande valeur qui est plus petite queu

  3. Si nous avons eu un résultat exact, renvoyer la valeur du tableau à cet index divisé par N, sinon interpoler un bit entre le point trouvé et le suivant, diviser à nouveau l'élément par Net retourner.

C'est tout! Alors maintenant, regardons le code complet:

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;    
}

Ceci initialise notre nouvelle courbe et calcule la arg-lenghts, il stocke également la dernière des longueurs comme total lengthla courbe, le facteur clé ici this.lenest le nôtre N. Plus la cartographie sera haute et précise, pour une courbe de la taille de l'image ci-dessus100 points semble suffire, si vous avez simplement besoin d’une bonne estimation de la longueur, vous 25obtiendrez peut-être déjà le résultat escompté. par exemple, mais vous aurez une cartographie précise moins ce qui entraînera pas une répartition uniforme de la Tcarte établie à 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));
    },

Le code de mappage lui-même, d’abord nous faisons un simple binary search sur nos longueurs stockées pour trouver la plus grande longueur qui est plus petite targetLength, puis nous retournons ou faisons l’interpolation et le retour.

    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;
    }
};

Encore une fois, cela calcule tsur la courbe.

Temps pour les résultats

texte alternatif

En utilisant maintenant mx et myvous obtenez une distribution uniforme Tsur la courbe :)

N'était-ce pas difficile? Une fois encore, il s'avère qu'une solution simple (bien que non parfaite) suffira pour un match.

Si vous voulez voir le code complet, un Gist est disponible:
https://gist.github.com/670236

Enfin, accélérer les navires

Il ne reste donc maintenant qu’à accélérer les navires tout au long de leur route, en cartographiant la position sur Tlaquelle nous nous servirons ensuite pour trouver let courbe.

Nous avons d’abord besoin de deux des équations du mouvement , à savoirut + 1/2at² et(v - u) / t

Dans le code actuel, cela ressemblerait à ceci:

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;

Ensuite, nous réduisons cela 0...1en faisant:

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

Et voilà, les navires se déplacent maintenant sans à-coup le long du chemin.

Si ça ne marche pas ...

Quand vous lisez ceci, tout fonctionne bien et dandy, mais au début, j'avais quelques problèmes avec l'accélération. Lorsque j'ai expliqué le problème à quelqu'un dans la salle de discussion Gamedev, j'ai trouvé l'erreur finale dans ma pensée.

Au cas où vous n’auriez pas déjà oublié la photo dans la question initiale, je mentionne sici que la svitesse est en degrés , mais les navires se déplacent le long du chemin en pixels et j’avais oublié ce fait. Donc ce que je devais faire dans ce cas était de convertir le déplacement en degrés en un déplacement en pixels, cela s'avère être assez facile:

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);
};

Alors et c'est tout! Merci d'avoir lu ;)

Ivo Wetzel
la source
7
Cela va prendre un certain temps à digérer. Mais wow, réponse étonnante à votre propre question.
Attaquer contre
7
J'ai fait un compte juste pour upvote cette réponse
Personne Personne
Avoir des points mon ami. A travaillé comme un charme. Question et réponse les deux votés.
Jace
2
"i" est multiplié par 0,05, tandis que "len" est défini sur 100. "t" doit être mappé sur "0-5" au lieu de "0-1".
Activité maléfique
1
@EvilActivity Oui, j'ai vu ça aussi, sa longueur initiale devait être de 20, puis j'ai oublié de changer 0,05 en 0,01. Il est donc préférable d’avoir une "len" dynamique (adaptable à la longueur réelle de l’arc, voire même exactement la même), et de calculer le "pas" de 1 / 'len ". Je trouve ça tellement bizarre que personne d'autre n'a évoqué toutes ces années !!!
Bill Kotsias
4

Le problème est qu’un navire ne suivrait pas cette trajectoire naturellement. Donc, même si cela fonctionne parfaitement, cela ne semblera toujours pas correct.

Si vous voulez simuler la transition en douceur entre les planètes, je vous suggère de la modéliser. Les équations sont très simples car vous n’avez que deux forces importantes: la gravité et la poussée.

Il vous suffit de définir vos constantes: Masse de P1, P2, expédier

A chaque jeu (tick: time), vous faites 3 choses

  1. Calculez la gravité de p1 sur le navire et de p2 sur le navire, ajoutez les vecteurs résultants au vecteur de poussée.

  2. Calculez votre nouvelle vitesse en fonction de votre nouvelle accélération à partir de l'étape 1

  3. Déplacez le navire en fonction de votre nouvelle vitesse

Cela peut sembler beaucoup de travail, mais cela peut être fait en une douzaine de lignes de code et sera très naturel.

Si vous avez besoin d'aide avec la physique faites le moi savoir.

Aaronfarr
la source
Je pourrais envisager de tester que si vous pouvez fournir un moyen de faire cela dans une fonction qui prend t:)
Ivo Wetzel
-mais dans la programmation de jeux, vous n'utilisez pas t comme variable. Vous êtes déjà essentiellement dans une situation paramétrique, car vous calculez simplement les nouveaux dx et dy pour le navire. Voici un exemple de deux planètes en orbite (en Flash) aharrisbooks.net/flash/fg2r12/twoPlanets.html - et est ici la même chose en Python: aharrisbooks.net/pythonGame/ch09/twoPlanets.py
Deux pi
2

J'ai trouvé un excellent article expliquant une solution possible à ce problème avec un exemple de code écrit en javascript. Cela fonctionne en "poussant la valeur t" dans la bonne direction.

Au lieu de cela, nous pouvons utiliser le fait que la longueur moyenne de jambe d_avg pour toute distribution de points est presque identique à la longueur de jambe que produiraient des points espacés de manière égale (cette similarité augmente avec n augmente). Si nous calculons la différence d_err entre les longueurs de jambe réelles d et la longueur moyenne de jambe d_avg, le paramètre de temps t correspondant à chaque point peut être décalé pour réduire cette différence.

Cette question a déjà beaucoup de réponses intéressantes, mais j'ai trouvé cette solution intéressante à noter.

Julian Weimer
la source
1

Merci pour votre excellente page décrivant comment vous avez résolu ce problème. J'ai fait quelque chose d'un peu différent de ce que vous avez fait dans un détail, car j'étais extrêmement contraint par la mémoire: je ne construis pas de tableau, je ne dois pas chercher le bon 'segment' avec une recherche binaire. C’est parce que je sais toujours que je me déplace d’un bout à l’autre de ma courbe de Bézier: c’est pourquoi je me souviens simplement du segment "actuel", et si je vois que je vais sortir des limites de ce segment pour calculer mon prochain position, je calcule le segment suivant (ou précédent) (en fonction du sens de déplacement). Cela fonctionne assez bien pour mon application. Le seul petit problème que je devais résoudre était que, dans certaines courbes, la taille des segments était si petite que ma parcelle de terrain suivante était - à de rares occasions - plus d'un segment en avance sur le segment actuel, donc au lieu d'aller simplement à la '

Je ne sais pas si cela a du sens, mais cela m'a certainement aidé.


la source
0

Ce type de modélisation est étrange et peut produire d'étranges résultats illogiques. Surtout si la vitesse des planètes de départ est vraiment lente.

Modéliser les navires avec une puissance de poussée.

Lorsque les navires sont sur leur dernière orbite sur la planète de départ, accélérez à pleine vitesse.

Lorsque le vaisseau se trouve dans une certaine distance, utilisez l'inversion de poussée pour le ralentir jusqu'à la vitesse d'orbite de la planète cible.

Edit: Effectuez toute la simulation en même temps lorsqu'un nœud est sur le point de quitter l'orbite. soit envoyer toutes les données, soit envoyer seulement quelques vecteurs de mouvement à intervalles, et interpoler entre eux.

Attaquer
la source
Le problème, c'est que tout est basé sur les ticks, il n'y a pas de position intermédiaire. C'est un jeu multijoueur en réseau et l'envoi de toutes les positions de plus de 600 navires dans un jeu complet va tuer tous les réseaux. Il n'y a que des événements qui transmettent un tickOffset, le reste est calculé en fonction du tick du monde actuel et du décalage.
Ivo Wetzel
J'ai édité ma réponse.
AttaquerHobo
0

Si je comprends bien, votre problème est surcontraint.

Je crois que vous voulez que le vaisseau spatial se déplace le long d'un chemin spécifié entre orbites dans un certain temps t et que vous souhaitiez également qu'il passe de la vitesse s1 à la vitesse s2 dans le même temps t . Malheureusement, vous ne pouvez pas (en général) trouver une accélération qui réponde simultanément à ces deux contraintes.

Vous allez devoir assouplir un peu votre problème pour le rendre résoluble.

Gareth Rees
la source
2
Alors comment se détendre? Ce que je pouvais imaginer, c’est de modifier le T que j’ai branché sur le chemin de Bézier. J'aurais besoin de l'adapter d'une manière ou d'une autre pour croître plus lentement à 0,5 puis à une vitesse supérieure à 1. Ainsi, le navire décélère de sa vitesse initiale à une vitesse fixe au milieu de la courbe, puis accélère à nouveau de cette vitesse à la vitesse finale. de la courbe?
Ivo Wetzel
1
Je pense que cela semblera plus réaliste si le vaisseau spatial accélère de sa vitesse initiale à quelque part autour du point médian du transfert puis décélère vers la nouvelle orbite.
Gareth Rees
Pourtant, je suis coincé sur la façon de brancher l'accélération dans l'ensemble, je dois modifier le T en quelque sorte: /
Ivo Wetzel
0

Je suis tombé sur cette réponse parce que je cherche à répartir les points uniformément le long d'un chemin svg utilisant une courbe de Bézier.

Bien que MDN dise qu'il est obsolète, vous pouvez utiliser le path.getPointAtLengthpour obtenir le résultat correct. https://developer.mozilla.org/en-US/docs/Web/API/SVGPathElement/getPointAtLength

Cela fonctionne actuellement dans Chrome / Safari / Firefox, et devrait fonctionner dans IE / Edge également, mais je n'ai pas vérifié ces 2.

Jon Harris
la source
-1

Le problème avec la solution acceptée

Comme Bézier est une fonction exponentielle , nous nous attendons à des taux d’avancement différents dans différentes zones de la courbe.

Étant donné que la solution d'Ivo interpole linéairement entre ces échantillons exponentiels initiaux , les imprécisions seront fortement biaisées vers les extrémités / milieu de la courbe (généralement cubique) où ces deltas sont les plus grands; ainsi, à moins que la fréquence d'échantillonnage Naugmente considérablement, comme il le suggère, les erreurs sont évidentes et, à un certain niveau de zoom, le sera toujours pour une donnée donnée N, c'est-à-dire que le biais est intrinsèque à cet algorithme. Pas bon pour les graphiques vectoriels, par exemple, où le zoom peut être illimité.

Contrer les biais par échantillonnage guidé

Une autre solution est de remappage linéaire distancepour taprès contrer la tendance naturelle que la fonction de Bézier produit.

En supposant que c'est ce que nous souhaitons idéalement:

curve length = 10

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

mais c’est ce que nous obtenons de la fonction de position de Bézier:

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

En examinant les Néchantillons prélevés, nous pouvons voir où les deltas de distance sont les plus grands et rééchantillonner ("scinder") à mi-chemin entre les deux distances adjacentes, en augmentant Nde 1. Par exemple, en se scindant à t=0.9(ce qui est à mi-chemin dans le plus grand delta), nous pourrions obtenir:

0.8    5.81
0.9    7.39
1.0    10.00

Nous répétons ce processus pour le prochain intervalle de distance le plus grand jusqu'à ce que le delta maximal entre deux distances quelconques de l'ensemble soit inférieur à certaines minDistanceDelta, et plus précisément à une epsilondistance inférieure à des distances spécifiques que nous voulons mapper aux étapes suivantes t; nous pouvons ensuite mapper linéairement nos tétapes souhaitées aux distances correspondants . Cela produit une hashtable / map à laquelle vous pouvez accéder à moindre coût et dont les valeurs peuvent être lerpées, à l'exécution, sans biais.

Le Ncoût de répétition de cette opération augmente à mesure que l'ensemble augmente, il est donc préférable de le faire en tant que prétraitement. Chaque fois que vous Naugmentez, ajoutez les deux nouveaux intervalles résultants à une intervalscollection tout en supprimant l'ancien intervalle unique qu'ils ont remplacé. C'est la structure sur laquelle vous travaillez pour trouver le prochain intervalle le plus grand à scinder en deux. Garder le intervalstri en fonction de la distance facilite les choses, car vous pouvez simplement supprimer le prochain élément de travail, le fractionner, etc.

Nous nous retrouvons avec quelque chose comme ce que nous voulions idéalement:

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

Étant donné que nous prenons des suppositions à chaque étape, nous n'obtiendrons pas les distances exactes 2, 4etc., mais nous les souhaitions, mais elles sont rapprochées des valeurs de distance souhaitées de manière à ce que vous puissiez les cartographier.t étapes avec une précision , éliminant ainsi les biais dus. échantillonnage quasi-équidistant.

Vous pouvez ensuite récupérer, par exemple t=0.5, comme le fait Ivo dans sa réponse, c'est-à-dire en interpolant entre les deux valeurs les plus proches ci-dessus ( 3.9998132et 6.00703).

Conclusion

Dans la plupart des cas, la solution d’Ivo fonctionnera bien, mais dans les cas où il faut absolument éviter les biais, assurez-vous que vos distances sont le plus uniformément dispersés possible, puis mappés linéairement t.

Notez que la division peut être effectuée de manière stochastique plutôt que fractionnée au milieu à chaque fois. Par exemple, nous pourrions avoir divisé le premier exemple d'intervalle à t=0.827plutôt qu'à t=0.9.

Ingénieur
la source