Courbes de Bezier & Sprites

Informations pour bien débuter en PureBasic
Anonyme

Courbes de Bezier & Sprites

Message par Anonyme »

Voici un petit post montrant comment déformé une serie de sprites donnant l'illusion d'une courbe :

Image


Cette technique n'est ni plus ni moins que l'application de la courbe de bezier, mais qu'est ce que la courbe de bezier ?

Extrait de l'article :
http://communaute.mangue.org/e107_plugi ... ours3.html
On la doit à l'ingénieur français, Pierre Bézier, qui travaillait chez Renault dans les années 60. Il se pencha sur les problèmes liés à la conception des surfaces en 3D pour les premiers programmes de CAO (conception assistée par ordinateur). Le système UNISURF issu de ses recherches permettra à Renault de dessiner et découper la carrosserie de la plupart des automobiles de la firme.

L'enjeu était de trouver un moyen pour définir des courbes de manière précise afin que les machines puissent procéder aux découpes. Il fallait donc trouver un système de paramètres défini de manière rigoureuse et le plus simple possible. Les dessins complexes (carrosseries, coques, fuselages...) étaient jusque alors fait à main levée par les designers et aucune machine ne pouvait récupérer facilement l'information depuis un dessin. Les machines les plus avancées utilisaient des ellipses pour calculer les zones à découper. Les courbes d'interpolation connues en ce temps n'étaient pas adaptées pour ce travail car elles n'offraient pas un contrôle suffisant sur la forme des arcs et la géométrie finale des objets, il fallait aussi attendre que la technologie des machines-robots évolue en parallèle avec l'électronique. Paul de Casteljau, qui travaillait lui chez Citroen, avait défini dès 1958 des méthodes pour créer des surfaces en 3D mais son approche était moins pratique que celle de Bézier. Les travaux de Casteljau permirent à Bézier d'améliorer le concept grâce à l'ajout des "poignées de contrôle" et cette nouvelle famille de courbes remporta un vif succès dans l'industrie. Par la suite, un groupe de recherche d'Apple (ces personnes créeront plus tard Adobe) récupéra cette invention pour définir les premières polices vectorielles, les imprimantes fonctionnant un peu comme les machines de Renault.

De manière plus précise, la courbe de Bézier (cubique, c'est la plus courante) est une courbe d'interpolation qui nécessite 4 points pour définir un arc. Le premier point et le dernier sont des noeuds, la courbe passe par ces points. Les deux autres points sont des points de contrôle, ils permettent de définir la forme de la courbe, celle-ci ne passe pas par ces points de contrôle (sauf pour les cas spéciaux avec des noeuds alignés). Les points de contrôle correspondent aux "poignées" des programmes de dessin vectoriel.
En gros il nous faut 4 points :

- 2 pour l'extrémité du segment
- 2 pour les points de controle

se qui nous donne :

Image


Les point de controles vont servir a donner la forme à la courbe finale, on obtient cette courbe en subdivisant les segments , plus on divise, plus la coube sera "lisse" :

Image
Image
Image

On obtient se resultat avec une fonction récursive ( qui s'appelle elle même n fois)

jusqu'a obtenir un tel resultat :

Image


je ne vais pas expliqué en détails la fonction, au moins vous aurez compris le concept d'une courbe de bezier.

Maintenant pour la déformation de sprites, c'est très simple,
Comme je connais chaque subdivision ( qui sont aussi des simples segment ) je peut facilement obtenir les point qui formeront des perpendiculaire à ces segment :

Image

Je n'ai plus qu'a stocké les coordonées des perpendiculaires puis à transformé mon sprite en fonction de ces perpendiculaires.

Voici un bout de code pour terminé mon petit article :

C'est une adaptation d'un code en C :
http://fr.wikipedia.org/wiki/Courbe_de_B%C3%A9zier_en_C

Comtois avait déjà fait la conversion en PureBasic :
http://www.purebasic.fr/french/viewtopi ... 42&start=0


Code : Tout sélectionner

InitSprite() : InitKeyboard() : InitMouse() : InitSprite3D()
OpenWindow(0,0,0,800,600,"SPRITE TORSION")
OpenWindowedScreen(WindowID(0),0,0,800,600,1,0,0)


Structure POINTF
x.f
y.f
EndStructure

Structure BEZIER
P.POINTF[4]
EndStructure

Structure Perpendiculaires
P1.POINTF
P2.POINTF
EndStructure

Global NewList Droites.Perpendiculaires()


Global SubDivide
Procedure CountSubDivide(*b.BEZIER,level)
SubDivide+1
If level<=0
Else
  CountSubDivide(*b,level-1)
  CountSubDivide(*b,level-1)
EndIf
EndProcedure


Declare DrawBezier(Global_Bezier,level)
MyBezier.BEZIER


MyBezier\P[0]\x = 100
MyBezier\P[0]\y = 500

MyBezier\P[1]\x = 100
MyBezier\P[1]\y = 100

MyBezier\P[3]\x = 700
MyBezier\P[3]\y = 500
; 
MyBezier\P[2]\x = 700
MyBezier\P[2]\y = 100



;Sprite Procedural
CreateSprite(0,256,256,#PB_Sprite_Texture)
StartDrawing(SpriteOutput(0))
For y = -128 To 128
Line(0,y+128,256,1,RGB(64,128+y,255))
Next
StopDrawing()

CreateSprite3D(0,0)






LOD=5




Repeat
ExamineKeyboard() : ClearScreen(RGB(0,0,0))
ExamineMouse()

event = WindowEvent()


         Start3D()
         Sprite3DQuality(1)

        ForEach Droites()

            If NextElement(Droites())<>0
            Nx_a.f=Droites()\P1\x
            Ny_a.f=Droites()\P1\y
            Nx_b.f=Droites()\P2\x
            Ny_b.f=Droites()\P2\y
            PreviousElement(Droites())
            EndIf     
          
            If PreviousElement(Droites())<>0
            Px_a.f=Droites()\P1\x
            Py_a.f=Droites()\P1\y
            Px_b.f=Droites()\P2\x
            Py_b.f=Droites()\P2\y
            NextElement(Droites())
            EndIf     
       
       
       
       
       Sprite3DBlendingMode(7,2)
       TransformSprite3D(0,Nx_a,Ny_a,Droites()\P1\x,Droites()\P1\y,Droites()\P2\x,Droites()\P2\y,Nx_b,Ny_b)
       DisplaySprite3D(0,0,0)
        ;LineXY(Nx_a,Ny_a,Droites()\P1\x,Droites()\P1\y,RGB(0,255,0))
        ;LineXY(Nx_b,Ny_b,Droites()\P2\x,Droites()\P2\y,RGB(0,255,0))
        ;LineXY(Droites()\P1\x,Droites()\P1\y,Droites()\P2\x,Droites()\P2\y,RGB(0,255,0))


          Next 

       ClearList(Droites())
      Stop3D()




StartDrawing(ScreenOutput())

    Mx = MouseX()
    My = MouseY()  
    
    Circle(Mx,My,4,RGB(0,0,255))
    
    If t<ElapsedMilliseconds()
    t=ElapsedMilliseconds()+500
    ;LOD=(LOD+1)%8
EndIf
    
    
   DrawBezier(MyBezier,LOD)
  
    
  
    SubDivide=0
      For i = 0 To 3
      If Mx<MyBezier\P[i]\x+4 And Mx>MyBezier\P[i]\x-4 And My<MyBezier\P[i]\y+4 And My>MyBezier\P[i]\y-4
      Circle(MyBezier\P[i]\x,MyBezier\P[i]\y,8,RGB(0,255,0))
      CTRL = i
      Else
      Circle(MyBezier\P[i]\x,MyBezier\P[i]\y,2,RGB(0,255,0))
      EndIf 
    Next 
    
    
    If CTRL <> -1
      If MouseButton(1)
        MyBezier\P[CTRL]\x = Mx 
        MyBezier\P[CTRL]\y = My
        Else
        CTRL = -1
      EndIf
      
    EndIf
StopDrawing()



FlipBuffers()
 
Until KeyboardPushed(#PB_Key_Escape) Or event = #PB_Event_CloseWindow

Procedure DrawBezierBase(*b.BEZIER,color)
  LineXY(*b\P[0]\x,*b\P[0]\y,*b\P[1]\x,*b\P[1]\y,color)
  LineXY(*b\P[1]\x,*b\P[1]\y,*b\P[2]\x,*b\P[2]\y,color/4)
  LineXY(*b\P[2]\x,*b\P[2]\y,*b\P[3]\x,*b\P[3]\y,color)
EndProcedure

Procedure Distance(x1,y1,x2,y2)
Protected Result.f
Result = Sqr(  (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)  )
ProcedureReturn Result
EndProcedure

Procedure.f GetAngle(xb,yb,Xa,Ya) 

      
   ;calcul de l'angle en radian 
    
   ar.f=ATan((Ya-yb)/(Xa-xb)) 
   
   ;conversion en degres (je suis pas un crac des radians, je préfère bosser avec les degrés) 
   ad=ar*360/2/3.1415 
 
    
   ;ajout de la partie de l'angle suivant la position des points car Atang ne renvoie qu'un angle de -90 à 90 
   If Xa<xb And Ya<yb : ad=180+ad :EndIf ; cas haut gauche 
   If Xa>xb And Ya<yb : ad=360+ad :EndIf ;cas haut droite 
   If Xa<xb And Ya>yb : ad=180+ad :EndIf ; cas bas gauche 
   If Xa>xb And Ya>yb :           :EndIf ; cas bas droite 

   ; reconversion en radian si tu en as besoins 
   ;ar=ad*2*3.1415/360 
    
  ProcedureReturn ad+ar
EndProcedure 



Procedure DrawBezierRecursive (*b.BEZIER ,level.l) 

Static OldX.f,OldY.f,i.l,Pca.l,Pcb.l,h.f

  ; Change la hauteur des perpendiculaires
  h=20+10*Cos(ElapsedMilliseconds()/2000)
         
                  
                  
         If (level <= 0) 
          LineXY((*b\P[0]\x + 0.5),(*b\P[0]\y + 0.5),(*b\P[3]\x + 0.5),(*b\P[3]\y + 0.5),RGB(Random(255),Random(255),Random(255)))
          Ang = GetAngle((*b\P[0]\x + 0.5),(*b\P[0]\y + 0.5),(*b\P[3]\x + 0.5),(*b\P[3]\y + 0.5))
          Dist.f = Distance((*b\P[0]\x + 0.5),(*b\P[0]\y + 0.5),(*b\P[3]\x + 0.5),(*b\P[3]\y + 0.5))/2
          
          M.POINTF
          UP.POINTF
          DW.POINTF
          ;Calcule des perpendiculaires
          M\x = *b\P[0]\x + Dist * Cos(Ang*#PI/180)
          M\y = *b\P[0]\y + Dist * Sin(Ang*#PI/180)
 
          UP\x = M\x + h * Cos((Ang+90)*#PI/180)
          UP\y = M\y + h * Sin((Ang+90)*#PI/180)
       
          DW\x = M\x - h * Cos((Ang+90)*#PI/180)
          DW\y = M\y - h * Sin((Ang+90)*#PI/180)
      
          AddElement(Droites())
          Droites()\P1\x=UP\x
          Droites()\P1\y=UP\y
          Droites()\P2\x=DW\x
          Droites()\P2\y=DW\y
 
         ;Dessin des Perpendiculaire
         LineXY(UP\x ,UP\y,DW\x,DW\y,255)
          
          Else 
                  left.BEZIER 
                  right.BEZIER
                 
                 left\P[0]\x = *b\P[0]\x
                 left\P[0]\y = *b\P[0]\y
                 left\P[1]\x = (*b\P[0]\x + *b\P[1]\x) / 2
                 left\P[1]\y = (*b\P[0]\y + *b\P[1]\y) / 2
                 left\P[2]\x = (*b\P[0]\x + 2**b\P[1]\x + *b\P[2]\x) / 4
                 left\P[2]\y = (*b\P[0]\y + 2**b\P[1]\y + *b\P[2]\y) / 4
                 left\P[3]\x = (*b\P[0]\x + 3**b\P[1]\x + 3**b\P[2]\x + *b\P[3]\x) / 8
                 left\P[3]\y = (*b\P[0]\y + 3**b\P[1]\y + 3**b\P[2]\y + *b\P[3]\y) / 8
                  
  ;               DrawBezierBase(left,RGB(0,255,0))
                 right\P[0]\x = left\P[3]\x;
                 right\P[0]\y = left\P[3]\y;
                 right\P[1]\x = (*b\P[1]\x + 2**b\P[2]\x + *b\P[3]\x) / 4
                 right\P[1]\y = (*b\P[1]\y + 2**b\P[2]\y + *b\P[3]\y) / 4
                 right\P[2]\x = (*b\P[2]\x + *b\P[3]\x) / 2
                 right\P[2]\y = (*b\P[2]\y + *b\P[3]\y) / 2
                 right\P[3]\x = *b\P[3]\x
                 right\P[3]\y = *b\P[3]\y
                 
                
;                 DrawBezierBase(right,RGB(0,0,255));
                 DrawBezierRecursive (left, level -1);
                 DrawBezierRecursive (right, level -1);
         EndIf
         
 
 EndProcedure
 
Procedure DrawBezier(Global_Bezier,level)
       DrawBezierBase(Global_Bezier,RGB(255,255,255));
       DrawBezierRecursive(Global_Bezier,level);
 EndProcedure
 
Voila , vous pouvez utilisé se sytème dans les jeux, pour faire par exemple des routes, des tirs lasers de malade, ou même dans une application, dessin vectoriel, etc...


@++ :wink:
comtois
Messages : 5172
Inscription : mer. 21/janv./2004 17:48
Contact :

Message par comtois »

sympa cette version avec les sprites 3D.
http://purebasic.developpez.com/
Je ne réponds à aucune question technique en PV, utilisez le forum, il est fait pour ça, et la réponse peut profiter à tous.
Anonyme

Message par Anonyme »

Merci comtois, je n'avais pas vu que tu avais convertit le code c en pb lors de mon "étude" sur les courbes :oops: j'ai perdu un peu de temps...
se genre de lien devrait être en sticky ou bien dans un post regroupant les autres post intéressant :D
Backup
Messages : 14526
Inscription : lun. 26/avr./2004 0:40

Message par Backup »

Cpl.Bator a écrit : ce genre de lien devrait être en sticky ou bien dans un post regroupant les autres post intéressant :D
voila c'est fait ! :D
en rubrique débutant , il y a un sticky sur les tutos ! :)
Anonyme

Message par Anonyme »

Frenchy Pilou
Messages : 2194
Inscription : jeu. 27/janv./2005 19:07

Message par Frenchy Pilou »

Et pour ceux que Pierre Bezier intéresse, je ne saurais trop vous recommender ce lien :) petite histoire d'une idée bizarre et autres articles
http://forums.polyloop.net/2d-3d-chez-a ... 999-a.html
Anonyme

Message par Anonyme »

Merci pour le lien , tu est vraiment une mine d'or :D
lionel_om
Messages : 1500
Inscription : jeu. 25/mars/2004 11:23
Localisation : Sophia Antipolis (Nice)
Contact :

Message par lionel_om »

Devant créer une courbe de Bezier en OpenGL, mais avec un algo dont je n'était pas suûr du résultat (trouvé sur Internet, mais qui paraissait bizarre), je me suis dit : "Ca va être plus rapide à voir en PB si ca marche ou pas". ET le résultat est que ça marche. Donc je vous fait part de ce code :

La particularité par rapport aux autres codes sur le forum, c'est qu'on peut avoir autant de points de contrôle que souhaité (juste modifier #N et la section Data).

Here the code :

Code : Tout sélectionner

Macro Type()
  Pointf
EndMacro



Structure Type()
  x.f
  y.f
EndStructure


#N = 4
#SIZE = SizeOf(Type())
#L_SIZE = #SIZE*(#N+1)

Global NewList Points.Type()()



If InitSprite() = 0 Or InitKeyboard() = 0 Or InitMouse() = 0
  MessageRequester("Error", "Can't open DirectX 7 or later", 0)
  End
EndIf



Macro DimTab(VarName)
  Dim VarName#.Type()(#N,#N)
EndMacro


Procedure CopyBezier(*src, *dest);Src.l(#N,#N), Dst.l(#N,#N))
  CopyMemory(*src, *dest, #SIZE*#N*#N)
EndProcedure

Macro Adr( tab, i, j )
  tab + i*#L_SIZE + j*#SIZE
EndMacro


Procedure GetPoint( *tab, i, j )
  ProcedureReturn Adr(*tab,i,j)
EndProcedure


Procedure SetPoint( *tab, i, j, *p.Type() )
  *dest.Point = Adr(*tab,i,j)
  *dest\x = *p\x
  *dest\y = *p\y
EndProcedure



Procedure Milieu( *tab, i1,j1, i2,j2, *p.Type() )
  *p1.Point = GetPoint(*tab, i1, j1)
  *p2.Point = GetPoint(*tab, i2, j2)
  *p\x = (*p1\x + *p2\x)/2
  *p\y = (*p1\y + *p2\y)/2
EndProcedure


Macro GetX( val )
  val & $FFFFFFFF
EndMacro

Macro GetY( val )
  val >> 32
EndMacro


Procedure Closer(*p1.Type(), *p2.Type())
  dx.f = *p1\x - *p2\x
  dy.f = *p1\y - *p2\y
  If Sqr(dx*dx + dy*dy) < 0.2
    ProcedureReturn #True
  EndIf
EndProcedure


Procedure DrawBezier( *tab )
If Not Closer( GetPoint(*tab, 0, 0), GetPoint(*tab, 0, #N) )
	For i=1 To #N
		For j=0 To #N-1
			Milieu(*tab, i-1,j, i-1,j+1, @milieu.Pointf)
			SetPoint( *tab, i, j, milieu)
		Next j
	Next i

	AddElement(Points())
	CopyMemory(GetPoint(*tab, 0, #N), Points(), #SIZE)

	DimTab(t1)
	DimTab(t2)
	For i=0 To #N
		CopyMemory(GetPoint(*tab, i, 0), t1(0,i), #SIZE)
		CopyMemory(GetPoint(*tab, #N-i, i), t2(0,i), #SIZE)
	Next i
	DrawBezier(t1())
	DrawBezier(t2())
EndIf
EndProcedure



DimTab(Bezier)
DimTab(Save)


Restore debut
For i=0 To #N
  Read Bezier(0, i)\x
  Read Bezier(0, i)\y
Next i



CopyBezier(Bezier(), Save())


Quit = #False
If OpenScreen(800, 600, 32, "Sprite")


  Repeat
    
    FlipBuffers()
    ClearScreen(RGB(0,0,0))
    

    CopyBezier(Bezier(), Save())
    ClearList(Points())
    DrawBezier(Save())
    
    
    If StartDrawing(ScreenOutput())
		ForEach(Points())
			Plot(Points()\x, Points()\y, $FF)
		Next
		
		For i=0 To #N
			Circle(Bezier(0, i)\x, Bezier(0, i)\y, 3, $FFFFFF)
		Next i
		
	    StopDrawing()
    EndIf
    
    
    ExamineKeyboard()
    ExamineMouse()
    If KeyboardPushed(#PB_Key_Escape) Or MouseButton(#PB_MouseButton_Left)
		Quit = #True
    EndIf
  Until Quit
  
Else
  MessageRequester("Error", "Can't open a 640*480 - 16 bit screen !", 0)
EndIf

End




DataSection
  debut:
  Data.f  50,50,  30,250,   160,300,     310,210,  350,70

EndDataSection
; IDE Options = PureBasic 4.10 Beta 2 (Windows - x86)
; CursorPosition = 113
; FirstLine = 85
; Folding = --
Lio :wink:
Webmestre de Basic-univers
Participez à son extension: ajouter vos programmes et partagez vos codes !
Répondre