Splines dans les graphismes 3D, l'option la plus automatisée

Il y a environ un mois, j'ai commencé à apprendre Python à partir du livre de Dawson et je me suis réveillé profondément en train d'écrire mon jeu sous pygame. Les savoirs traditionnels étaient tels qu'il semblait plus prometteur de faire un jeu avec des graphiques pseudo-tridimensionnels en remplissant les surfaces enregistrées des splines 3D en sprites. Je vais écrire sur ce dernier.

Donc, il y a des polygones (il est plus facile de travailler avec des quadrangles) sur lesquels nous voulons étirer les surfaces cubiques afin qu'elles s'emboîtent assez facilement - ces surfaces sont des splines.



Il est plus pratique de présenter une spline pour un polygone en fonction

g(u,v)=v cdotg1(u)+(1v) cdotg2(u)+u cdotg3(v)+(1u) cdotg4(v)(1)


Ici

g1,g2,g3,g4


- polynômes cubiques satisfaisant certaines conditions aux limites (dans la figure ci-dessous - courbes vert clair et rouge, et conditions initiales dérivées - vecteurs lilas et bleus); u et v varient de 0 à 1.



Dans cette interprétation, certains degrés sont perdus (produits de 2 et 3 degrés de paramètres), mais les coefficients polynomiaux peuvent être trouvés en ne spécifiant que 12 vecteurs de conditions initiales (4 polygones et vecteurs dérivés par rapport à u et v pour chaque point). Aux jonctions, les splines coïncident si des conditions initiales similaires sont définies pour les polygones voisins (les vecteurs dérivés doivent être colinéaires, la surface passe par les mêmes points du polygone).

Un problème - le dérivé avec une telle déclaration du problème sur toute la frontière peut ne pas coïncider - il y aura de petits artefacts aux jonctions. Vous pouvez imaginer 4 autres conditions pour corriger cela et ajouter soigneusement un autre terme à la formule

u cdotv cdot(u1) cdot(v1) cdotg5(u,v)


ce qui ne gâche pas les frontières, mais c'est un sujet pour un article séparé.

Une alternative est la surface de Bézier , mais elle propose de définir 16 paramètres de signification mathématique incompréhensible (pour moi), c'est-à-dire on suppose que l'artiste travaillera de ses propres mains. Ce n'est pas très approprié pour moi, donc un vélo avec des béquilles suit.

Les coefficients (1) sont plus facilement calculés en trouvant la matrice inverse à la matrice des valeurs et des dérivées dans les coins et en multipliant par les conditions d'entrée (trois fois, pour chaque coordonnée). La matrice peut être la suivante:

Texte masqué
[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #g (0,0)
[1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0], #g (1,0)
[1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], #g (0,1)
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], #g (1,1)
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], # dg / du (0,0)
[0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 3, 0], # dg / du (1,0)
[0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0], # dg / du (0,1)
[0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3], # dg / du (1,1)
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # dg / dv (0,0)
[0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1], # dg / dv (1,0)
[0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0], # dg / dv (0,1)
[0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 0, 1]] # dg / dv (1,1)

NumPy en fait un excellent travail.

Une question demeure - où trouver les vecteurs de dérivés. On suppose qu'ils doivent en quelque sorte être sélectionnés en fonction de la position des points voisins (pour le polygone) et pour des raisons de lissage.
Pour moi personnellement, il était encore complètement contre-intuitif de gérer la longueur du vecteur dérivé. La direction est compréhensible, mais la longueur?

En conséquence, l'algorithme suivant est né:

1. À la première étape, une classification des points a lieu. Dans le graphique (quels points des polygones et leurs connexions spécifient), les cycles de longueur 4 sont recherchés et stockés, et en outre, les voisins qui conviennent le mieux au rôle d'extension des bords du polygone sont enregistrés (il est déterminé à l'avance quels bords correspondent à un changement du paramètre u et qui correspondent à v). Voici un morceau de code qui recherche, trie et mémorise les voisins pour le 0ème point d'un cycle:

Texte masqué
"""   : cykle[5] cykle[7] cykle[4]--cykle[0] --u-- cykle[1]-cykle[6] |v |v cykle[10]-cykle[3] --u-- cykle[2]-cykle[8] cykle[11] cykle[9] """ sosed = [] for i in range(0, N): if self.connects[i][ind] == 1 and i != cykle[0] and i != cykle[1] and i != cykle[3]: sosed += [i] #    ... if(len(sosed) < 2): continue #    else: #      cykle[0] vs0c3 = MinusVecs(self.dots[sosed[0]], self.dots[cykle[3]]) vs1c1 = MinusVecs(self.dots[sosed[1]], self.dots[cykle[1]]) #        vs0c1 = MinusVecs(self.dots[sosed[0]], self.dots[cykle[1]]) vs1c3 = MinusVecs(self.dots[sosed[1]], self.dots[cykle[3]]) Rvsc = [ScalMult(vs0c3,vs0c3),ScalMult(vs1c1,vs1c1),ScalMult(vs0c1,vs0c1),ScalMult(vs1c3,vs1c3) ] n1 = Rvsc.index(max(Rvsc)) if n1 < 2: cykle += [sosed[1],sosed[0]] #  u   v else: cykle += [sosed[0],sosed[1]] 


De plus, lors de la construction de la spline, la dérivée le long du bord (par exemple, par rapport au paramètre u) au point du polygone est sélectionnée en fonction de l'emplacement de deux points du bord et d'un point adjacent (que ce soit les points vec1, vec2 et vec3; le point auquel la dérivée est recherchée est le deuxième).



Au début, j'ai essayé d'utiliser le vecteur normalisé vec3 - vec1 (comme j'ai appliqué le théorème de Lagrange) pour ce rôle, mais des problèmes sont survenus précisément avec la longueur du vecteur dérivé - en faire une constante était une mauvaise idée.

Digression lyrique:

Pour une petite illustration de ce qu'est le vecteur dérivé dans la version paramétrique, nous nous tournons vers une analogie bidimensionnelle simple - voici un morceau d'arc de cercle:



g(u)=(Rsin(u pi/2),Rcos(u pi/2))


g(u)=(R pi/2 cdotcos(u pi/2),R pi/2 cdotsin(u pi/2))


C'est-à-dire module dérivé = R * pi / 2 et, d'une manière générale, dépend de la taille du morceau de l'arc, que nous définissons par le paramètre.

Que faire maintenant? Leo Nikolaevich Tolstoy nous a légué que tout est rond = bon, donc, si nous réglons la dérivée à un point comme si nous voulions y construire un arc, nous obtiendrons une belle courbe lisse.

La fin de la digression.

La deuxième étape de la recherche dérivée:

2. A travers nos trois points vec1, vec2, vec3 nous construisons un plan, dans ce plan nous recherchons un cercle qui passe par les trois points. La dérivée souhaitée sera dirigée le long de la tangente au cercle au point vec2, et le module du vecteur de la dérivée doit être égal au produit du rayon du cercle et de l'angle du secteur, qui forment deux points de la face du polygone (semblable à notre simple analogie plate de la digression lyrique).

g=R cdot phi


Tout semble simple, voici le code de fonction par exemple (NumPy est à nouveau utilisé):

pas un code, mais ...
 def create_dd(vec1, vec2, tuda, vec3): """     1-2       3   == 1""" #0.         -         :( vecmult = VecMult(MinusVecs(vec2,vec1),MinusVecs(vec3,vec1)) #  vec2-vec1 x vec3 - vec1 if vecmult[0]*vecmult[0] + vecmult[1]*vecmult[1] + vecmult[2]*vecmult[2] < 0.000001: #  0 if tuda == 1: return NormVec(MinusVecs(vec3,vec2)) else: return NormVec(MinusVecs(vec1,vec2)) #1.   ,     #           2   M = np.zeros((3,3),float) C = np.zeros((3,1),float) M[0][0] = 2*vec2[0] - 2*vec1[0] M[0][1] = 2*vec2[1] - 2*vec1[1] M[0][2] = 2*vec2[2] - 2*vec1[2] C[0] = -vec1[0]*vec1[0] - vec1[1]*vec1[1] - vec1[2]*vec1[2] + vec2[0]*vec2[0] + vec2[1]*vec2[1] + vec2[2]*vec2[2] M[1][0] = 2*vec3[0] - 2*vec2[0] M[1][1] = 2*vec3[1] - 2*vec2[1] M[1][2] = 2*vec3[2] - 2*vec2[2] C[1] = -vec2[0]*vec2[0] - vec2[1]*vec2[1] - vec2[2]*vec2[2] + vec3[0]*vec3[0] + vec3[1]*vec3[1] + vec3[2]*vec3[2] #   ,   4     M[2][0] = vecmult[0] M[2][1] = vecmult[1] M[2][2] = vecmult[2] C[2] = vecmult[0]*vec1[0] + vecmult[1]*vec1[1] + vecmult[2]*vec1[2] # M *    = C Mobr = np.linalg.inv(M) Rvec = np.dot(Mobr,C) res = NormVec(VecMult(MinusVecs(Rvec,vec2), vecmult)) #    ka #R = math.sqrt((Rvec[0]-vec1[0])**2 + (Rvec[1]-vec1[1])**2 + (Rvec[2]-vec1[2])**2) #   = 3.14*2*asin(l/(2*R))/180 * R,      l  ,    l = 1.2*math.sqrt((vec1[0]-vec2[0])**2 + (vec1[1]-vec2[1])**2 + (vec1[2]-vec2[2])**2) if ScalMult(res, MinusVecs(vec2, vec1)) > 0: #     return MultVxC(res, tuda*l) else: return MultVxC(res, -tuda*l) 


Eh bien, en général, tout cela fonctionne d'une manière ou d'une autre. Pour démonstration, j'ai pris un cube 5x5x5 et changé la position des points avec un randomiseur:

GIF, 8Mb

Source: https://habr.com/ru/post/fr473790/


All Articles