Physique d'une balle

Vous débutez et vous avez besoin d'aide ? N'hésitez pas à poser vos questions
kelebrindae
Messages : 579
Inscription : ven. 11/mai/2007 15:21

Physique d'une balle

Message par kelebrindae »

Bonjour,

J'ai ce petit code pour simuler très grossièrement le comportement d'une balle qui rebondit sur un décor aléatoire; Le but n'est pas d'être hyper-exact, mais plutôt d'avoir un truc rapide que l'on pourrait insérer dans un jeu.
Seulement, je bute sur un problème: quand la balle a cessé de rebondir, qu'elle est posée sur le sol, elle n'arrête pas de "tressauter" verticalement (essayez le code, vous verrez de quoi je veux parler).

Malgré tous mes efforts, je n'arrive pas à résoudre le problème sans en faire apparaître d'autres (la balle se met à passer à travers les murs, ou à rester collée aux obstacles, etc..) :( .

Quelqu'un aurait une solution ?

Code : Tout sélectionner

; Author: Kelebrindae
; Date: october, 7, 2010
; PB version: v4.51
; OS: Windows

; ---------------------------------------------------------------------------------------------------------------
; Description:
; ---------------------------------------------------------------------------------------------------------------
; Very simple simulation of a bouncing ball physics
; 
; Controls:
; - [Space] : reset ball position
; - Esc : quit
; ---------------------------------------------------------------------------------------------------------------

#BLANKCOLOR = $000001

Structure ball_struct
  x.f
  y.f
  radius.i
  elasticity.f
  
  vx.f  ; horizontal velocity
  vy.f  ; vertical velocity
EndStructure
Global NewList ball.ball_struct()

Global *ptrSilhouette.b
Global angle.i, nbStep.i
Global collisionX.f,collisionY.f,normalX.f,normalY.f,VdotN.f,normalForceX.f,normalForceY.f, absVx.f, absVy.f,vxStep.f,vyStep.f


;- --- Procedures ---
EnableExplicit
Procedure storeSilhouette(numImage.i)
  Protected *ptrSilhouette,*ptr
  Protected i.i,j.i,width.i,height.i
  
  width = ImageWidth(numImage)
  height = ImageHeight(numImage)
  *ptrSilhouette = AllocateMemory(width * height)
  
  If *ptrSilhouette <> 0
    *ptr = *ptrSilhouette
  	StartDrawing(ImageOutput(numImage))
    For j = 0 To height-1
      For i = 0 To width-1
        If Point(i,j) <> #BLANKCOLOR
          PokeB(*ptr,255)
        Else
          PokeB(*ptr,0)
        EndIf
        *ptr+1
      Next i
    Next j
    StopDrawing()
  EndIf
  
  ProcedureReturn *ptrSilhouette
EndProcedure

; Look up in the "silhouette" memory array if a given pixel is blank or not
Procedure.b checkCollision(xtest.i, ytest.i, levelWidth.i, levelHeight.i)
	
   If xtest < 1 Or ytest < 1 Or xtest > levelWidth-1 Or ytest > levelHeight-1
     ProcedureReturn 255
   EndIf
  
  ProcedureReturn PeekB(*ptrSilhouette + (ytest*levelWidth) + xtest)
	
EndProcedure
DisableExplicit


;- --- Main program ---
InitSprite()
InitSprite3D()
InitKeyboard()

;- Window
OpenWindow(0, 0, 0, 800, 600, "Bounce", #PB_Window_ScreenCentered|#PB_Window_SystemMenu)
OpenWindowedScreen(WindowID(0), 0, 0, 800,600, 0, 0, 0,#PB_Screen_SmartSynchronization)
	
;- Create a sprite for the ball
CreateImage(0,25,25)
StartDrawing(ImageOutput(0))
  DrawingMode(#PB_2DDrawing_Gradient)
  FrontColor($BBBB00)
  BackColor($FFFF00)
  CircularGradient(7,8,12)
  Circle(12,12,12)
StopDrawing()
SaveImage(0,"ball.bmp")
FreeImage(0)
LoadSprite(0, "ball.bmp", #PB_Sprite_Texture)
DeleteFile("ball.bmp")
CreateSprite3D(0, 0)

;- Create a random level
CreateImage(0,800, 600)
StartDrawing(ImageOutput(0))  
  Box(0,0,800,600,#BLANKCOLOR)
  DrawingMode(#PB_2DDrawing_Gradient)
  FrontColor($616161)
  BackColor($EAEAEA)
  For i = 0 To 30
    x=Random(800):y=Random(600):w=Random(25)+10:h=Random(25)+10
    LinearGradient(x,y,x+h/2,y+h)
    Box(x,y,w, h)
  Next i
  FrontColor($77777A)
  BackColor($CCCCCD)
  For i = 0 To 30
    x=Random(800):y=Random(600):w=Random(25)+10
    CircularGradient(x-(w/2),y-(w/2),w)
    Circle(x,y,w) 
  Next i  
    
  DrawingMode(#PB_2DDrawing_Default)
  Circle(400,10,50,#BLANKCOLOR)
  DrawingMode(#PB_2DDrawing_Outlined)
  Box(0,0,800,600,$FFFFFF)
StopDrawing()
SaveImage(0,"decor.bmp")
LoadSprite(999, "decor.bmp", #PB_Sprite_Texture)
DeleteFile("decor.bmp")
CreateSprite3D(999,999)

; Store level's "silhouette" in memory for faster collision check
; (it uses a lot of memory, though)
*ptrSilhouette = storeSilhouette(0)
FreeImage(0)

; Store sinus and cosinus (faster) 
Global Dim cosTable.f(360)
Global Dim sinTable.f(360)
For i=0 To 359
  cosTable(i) = Cos( Radian(i) )
  sinTable(i) = Sin( Radian(i) )
Next i

;- Define ball's properties
AddElement(ball())
ball()\x = 400
ball()\y = 20
ball()\radius = 12
ball()\elasticity = 0.75
ball()\vx = (20 - Random(40))/10
ball()\vy = 0


;- --- Main Loop ---
Repeat
  While WindowEvent() : Wend
  
  ; If X-velocity or Y-velocity exceeds 1, break movement into N steps where "partial" velocities are smaller then 1 (= normalize)
  If ball()\Vx > 1 Or ball()\Vx < -1 Or ball()\Vy > 1 Or ball()\Vy < -1
    absVx = Abs(ball()\Vx)
    absVy = Abs(ball()\Vy)
    If absVx > absVy
      nbStep = Int(absVx) + 1
    Else
      nbStep = Int(absVy) + 1
    EndIf
    vxStep = ball()\Vx / nbStep
    vyStep = ball()\Vy / nbStep
  Else
    nbStep = 1
    vxStep = ball()\Vx
    vyStep = ball()\Vy
  EndIf
  
  ; For each step, check for collision and move the ball
  For i = 1 To nbStep
    ;Check 12 collision points around the ball
  	For angle = 0 To 330 Step 30
  	  ; Get coords for check point at angle [angle]
  		collisionX = cosTable(angle)
  		collisionY = sinTable(angle)
  		
  		; Check collision
  		If checkCollision( ball()\x + collisionX * ball()\radius, ball()\y + collisionY * ball()\radius ,800,600)
  			; Get normal to collision
  			normalX = -collisionX
  			normalY = -collisionY
  			 			
  			; Move ball a bit away from collision
  			ball()\x + normalX 
  			ball()\y + normalY
  			
  			; Project velocity onto collision normal vector
  			VdotN = vxStep * normalX + vyStep * normalY
  			
  			; Calculate normal force
  		  normalForceX = -2.0 * normalX * VdotN
  		  normalForceY = -2.0 * normalY * VdotN
  				
  			; Add normal force to velocity vector
  			vxStep + normalForceX * ball()\elasticity
  			vyStep + normalForceY * ball()\elasticity

  		EndIf
  	Next angle
  	
  	; Move the ball	
  	ball()\x + vxStep
  	ball()\y + vyStep
  Next i
  
  ; Total velocities =  partial velocities * number of steps
  ball()\vx = vxStep * nbStep
  ball()\vy = vyStep * nbStep
    
  ; Gravity
  If checkCollision( ball()\x, ball()\y + ball()\radius,800,600) = 0
    ball()\vy + 0.16
  EndIf
  
  ; Display screen
	Start3D()
  	; Background	
  	DisplaySprite3D(999,0,0,255)		
    ; Ball
  	DisplaySprite3D(0,ball()\x-ball()\radius,ball()\y-ball()\radius)
  Stop3D()	
  	
	FlipBuffers()
	
	; Space => reset sim
	ExamineKeyboard()
	If KeyboardReleased(#PB_Key_Space)
	  ball()\x = 400
    ball()\y = 20
    ball()\radius = 12
    ball()\elasticity = 0.75
    ball()\vx = (20 - Random(40))/10
    ball()\vy = 0
	EndIf

Until KeyboardPushed(#PB_Key_Escape)

FreeMemory(*ptrSilhouette)
End
Les idées sont le souvenir de choses qui ne se sont pas encore produites.
Avatar de l’utilisateur
flaith
Messages : 1487
Inscription : jeu. 07/avr./2005 1:06
Localisation : Rennes
Contact :

Re: Physique d'une balle

Message par flaith »

8O whaaa pas mal, le début d'un Patchinko ?
G-Rom
Messages : 3641
Inscription : dim. 10/janv./2010 5:29

Re: Physique d'une balle

Message par G-Rom »

Tu observes la vitesse de la balle , si elle est en dessous d'un certain seuil ( que tu choisi ) par ex < 1 pixel
tu ne corrige pas la position de la balle.

@+
kelebrindae
Messages : 579
Inscription : ven. 11/mai/2007 15:21

Re: Physique d'une balle

Message par kelebrindae »

@Flaith: Non, j'avais plutôt en têteun truc à la "Worms", mais rien de précis en fait.
C'est vrai qu'il ne manque pas grand-chose pour un Patchinko (les collisions entre les balles), mais j'ai toujours eu un peu de mal à comprendre l'intérêt de ce jeu, vu qu'on ne contrôle rien ou presque... :|

@G-Rom: Merci, l'idée de seuil m'a mis sur la piste.

Voilà un code qui gère 90% à peu près correctement. La balle reste encore bloquée de temps en temps, mais ça présente déjà mieux. :)
Par contre, je crois qu'il faudrait gérer de façon distincte le cas où la balle roule sur le sol, pour prendre en compte la pente sur laquelle elle roule, tout ça. 'Faut que j'y réfléchisse encore un peu...

Code : Tout sélectionner

; Author: Kelebrindae
; Date: october, 7, 2010
; PB version: v4.51
; OS: Windows

; ---------------------------------------------------------------------------------------------------------------
; Description:
; ---------------------------------------------------------------------------------------------------------------
; Very simple simulation of a bouncing ball physics
;
; Controls:
; - [Space] : reset ball position
; - Esc : quit
; ---------------------------------------------------------------------------------------------------------------

#SCREENWIDTH = 800
#SCREENHEIGHT = 600
#BLANKCOLOR = $000001

; These values applies to a 60Hz frame rate
#GRAVITY = 0.16
#STICKYNESS = 0.4
#STOPTHRESHOLD = 0.05

Structure ball_struct
  x.f
  y.f
  radius.i
  elasticity.f
  
  onTheGround.b
  
  vx.f  ; horizontal velocity
  vy.f  ; vertical velocity
EndStructure
Global NewList ball.ball_struct()

Global *ptrSilhouette.b
Global angle.i, nbStep.i
Global collisionX.f,collisionY.f,normalX.f,normalY.f,VdotN.f,normalForceX.f,normalForceY.f
Global absVx.f, absVy.f,vxStep.f,vyStep.f

; Store sinus and cosinus (faster)
Global Dim cosTable.f(360)
Global Dim sinTable.f(360)
For i=0 To 359
  cosTable(i) = Cos( Radian(i) )
  sinTable(i) = Sin( Radian(i) )
Next i
   
EnableExplicit

;- --- Procedures ---

; Store the level's "silhouette" in memory to check collisions
Procedure storeSilhouette(numImage.i)
  Protected *ptrSilhouette,*ptr
  Protected i.i,j.i,width.i,height.i
 
  width = ImageWidth(numImage)
  height = ImageHeight(numImage)
  *ptrSilhouette = AllocateMemory(width * height)
 
  If *ptrSilhouette <> 0
    *ptr = *ptrSilhouette
     StartDrawing(ImageOutput(numImage))
    For j = 0 To height-1
      For i = 0 To width-1
        If Point(i,j) <> #BLANKCOLOR
          PokeB(*ptr,255)
        Else
          PokeB(*ptr,0)
        EndIf
        *ptr+1
      Next i
    Next j
    StopDrawing()
  EndIf
 
  ProcedureReturn *ptrSilhouette
EndProcedure

; Look up in the "silhouette" memory array if a given pixel is blank or not
Procedure.b checkCollision(xtest.i, ytest.i, levelWidth.i, levelHeight.i)
   
  If xtest < 1 Or ytest < 1 Or xtest > levelWidth-1 Or ytest > levelHeight-1
    ProcedureReturn 255
  EndIf
 
  ProcedureReturn PeekB(*ptrSilhouette + (ytest*levelWidth) + xtest)
   
EndProcedure

; Draw a random background And create a 3D sprite from it
Procedure drawLevel()
  Protected i.i,x.i,y.i,w.i,h.i
  
  CreateImage(0,#SCREENWIDTH, #SCREENHEIGHT)
  StartDrawing(ImageOutput(0)) 
    Box(0,0,#SCREENWIDTH,#SCREENHEIGHT,#BLANKCOLOR)
    DrawingMode(#PB_2DDrawing_Gradient)
    FrontColor($616161)
    BackColor($EAEAEA)
    For i = 0 To 30
      x=Random(#SCREENWIDTH):y=Random(#SCREENHEIGHT):w=Random(25)+10:h=Random(25)+10
      LinearGradient(x,y,x+h/2,y+h)
      Box(x,y,w,h)
    Next i
    FrontColor($77777A)
    BackColor($CCCCCD)
    For i = 0 To 30
      x=Random(#SCREENWIDTH):y=Random(#SCREENHEIGHT):w=Random(25)+10
      CircularGradient(x-(w/2),y-(w/2),w)
      Circle(x,y,w)
    Next i 
     
    DrawingMode(#PB_2DDrawing_Default)
    Circle(#SCREENWIDTH/2,10,50,#BLANKCOLOR)
    DrawingMode(#PB_2DDrawing_Outlined)
    Box(0,0,#SCREENWIDTH,#SCREENHEIGHT,$FFFFFF)
  StopDrawing()
    
  SaveImage(0,"decor.bmp")
  LoadSprite(999, "decor.bmp", #PB_Sprite_Texture)
  DeleteFile("decor.bmp")
  CreateSprite3D(999,999)
  
  ; Store level's "silhouette" in memory for faster collision check
  ; (it uses a lot of memory, though)
  *ptrSilhouette = storeSilhouette(0)
  FreeImage(0)
  
EndProcedure


;- --- Main program ---
InitSprite()
InitSprite3D()
InitKeyboard()

;- Window
OpenWindow(0, 0, 0, #SCREENWIDTH, #SCREENHEIGHT, "Bounce", #PB_Window_ScreenCentered|#PB_Window_SystemMenu)
OpenWindowedScreen(WindowID(0), 0, 0, #SCREENWIDTH,#SCREENHEIGHT, 0, 0, 0,#PB_Screen_SmartSynchronization)

;- Create a sprite for the ball
CreateImage(0,25,25)
StartDrawing(ImageOutput(0))
  DrawingMode(#PB_2DDrawing_Gradient)
  FrontColor($BBBB00)
  BackColor($FFFF00)
  CircularGradient(7,8,12)
  Circle(12,12,12)
StopDrawing()
SaveImage(0,"ball.bmp")
FreeImage(0)
LoadSprite(0, "ball.bmp", #PB_Sprite_Texture)
DeleteFile("ball.bmp")
CreateSprite3D(0, 0)

;- Create a random level
drawLevel()

;- Define ball's properties
AddElement(ball())
ball()\x = #SCREENWIDTH/2
ball()\y = 20
ball()\radius = 12
ball()\elasticity = 0.75
ball()\vx = (20 - Random(40))/10
ball()\vy = 0


;- --- Main Loop ---
Repeat
  While WindowEvent() : Wend
  
  ; If the ball is moving or falling, check for collision
  If ball()\vx <> 0 Or ball()\vy <> 0 Or ball()\onTheGround = #False
    
    ; If X-velocity or Y-velocity exceeds 1, break movement into N steps where "partial" velocities are smaller then 1 (= normalize)
    If ball()\Vx > 1 Or ball()\Vx < -1 Or ball()\Vy > 1 Or ball()\Vy < -1
      absVx = Abs(ball()\Vx)
      absVy = Abs(ball()\Vy)
      If absVx > absVy
        nbStep = Int(absVx) + 1
      Else
        nbStep = Int(absVy) + 1
      EndIf
      vxStep = ball()\Vx / nbStep
      vyStep = ball()\Vy / nbStep
    Else
      nbStep = 1
      vxStep = ball()\Vx
      vyStep = ball()\Vy
    EndIf
   
    ; For each step, check for collision and move the ball
    For i = 1 To nbStep
  
      ;Check 12 collision points around the ball
      For angle = 0 To 330 Step 30
        ; Get coords for check point at angle [angle]
        collisionX = cosTable(angle)
        collisionY = sinTable(angle)
          
        ; Check collision
        If checkCollision( ball()\x + collisionX * ball()\radius, ball()\y + collisionY * ball()\radius ,#SCREENWIDTH,#SCREENHEIGHT)
          ; Get normal to collision
          normalX = -collisionX
          normalY = -collisionY
                  
          ; Move ball a bit away from collision
          ball()\y + normalY
          ball()\x + normalX
          
          ; Project velocity onto collision normal vector
          VdotN = vxStep * normalX + vyStep * normalY
          
          ; Calculate normal force
          normalForceX = -2.0 * normalX * VdotN
          normalForceY = -2.0 * normalY * VdotN
            
          ; Add normal force to velocity vector
          vxStep + normalForceX * ball()\elasticity
          vyStep + normalForceY * ball()\elasticity
        EndIf
        
      Next angle
       
      ; Move the ball   
      ball()\x + vxStep
      ball()\y + vyStep
    Next i
   
    ; Total velocities =  partial velocities * number of steps
    ball()\vx = vxStep * nbStep
    ball()\vy = vyStep * nbStep
     
    ; If the ball is in the air
    If checkCollision( ball()\x, ball()\y + ball()\radius + 1,#SCREENWIDTH,#SCREENHEIGHT) = 0
      ; Apply gravity
      ball()\vy + #GRAVITY
      ball()\onTheGround = #False
    Else
      ; If the ball is on the ground and its falling speed is under the "stickyness" threshold, consider it's rolling 
      If ball()\vy < #STICKYNESS And ball()\vy > 0        
        Debug "on the ground"
        ball()\onTheGround = #True
        ball()\vy = 0
      EndIf
      
      ; If the ball is rolling on the ground, apply friction
      If ball()\onTheGround = #True
        ball()\vx * 0.99
        
        ; if the ball rolling speed is sufficiently small, stop it.
        If ball()\vx < #STOPTHRESHOLD And ball()\vx > -#STOPTHRESHOLD 
          Debug "stop"
          ball()\vx = 0
        EndIf
        
      EndIf ; if ball()\onTheGround = #True...
    EndIf ; else... (ball is touching the ground)
    
  EndIf ; if the ball is moving or falling...
 
  ; Display screen
  Start3D()
    ; Background   
    DisplaySprite3D(999,0,0,255)      
    ; Ball
    DisplaySprite3D(0,ball()\x-ball()\radius,ball()\y-ball()\radius)
  Stop3D()   
     
  FlipBuffers()
   
  ; Space => reset sim
  ExamineKeyboard()
  If KeyboardReleased(#PB_Key_Space)
    ball()\x = #SCREENWIDTH/2
    ball()\y = 20
    ball()\vx = (20 - Random(40))/10
    ball()\vy = 0
    ball()\onTheGround = #False
  EndIf

Until KeyboardPushed(#PB_Key_Escape)

FreeMemory(*ptrSilhouette)
End
Les idées sont le souvenir de choses qui ne se sont pas encore produites.
Avatar de l’utilisateur
Cool Dji
Messages : 1126
Inscription : ven. 05/sept./2008 11:42
Localisation : Besançon
Contact :

Re: Physique d'une balle

Message par Cool Dji »

Bravo,

J'aime bien cette petite recette de cuisine :D

Pour la retombée sur le sol, effectivement, ça marche beaucoup mieux que la version précédente mais je ne pense pas qu'il faille faire un test spécifique pour le sol car la balle pourrait tomber sur une surface plane qui ressemble au sol avec deux butées de chaque coté l'empêchant d'aller plus bas.
Je n'ai pas la solution, mais une méthode générique valable pour toutes les surfaces est (à mon humble avis) nécessaire.
Only PureBasic makes it possible
Avatar de l’utilisateur
flaith
Messages : 1487
Inscription : jeu. 07/avr./2005 1:06
Localisation : Rennes
Contact :

Re: Physique d'une balle

Message par flaith »

Effectivement ca marche mieux, sauf si la balle se retrouve entre deux éléments, elle a la 'bloblote' :)
Backup
Messages : 14526
Inscription : lun. 26/avr./2004 0:40

Re: Physique d'une balle

Message par Backup »

alors , toujours la meme rangaine !
pensez a MOA avec mon NC10 j'ai une résolution de 1024X600

c'est pas dur d'ajouter un petit

Code : Tout sélectionner

ExamineDesktops()
Global  SCREENWIDTH=DesktopWidth(0)
Global  SCREENHEIGHT=DesktopHeight(0)



Code : Tout sélectionner

; Author: Kelebrindae
; Date: october, 7, 2010
; PB version: v4.51
; OS: Windows

; ---------------------------------------------------------------------------------------------------------------
; Description:
; ---------------------------------------------------------------------------------------------------------------
; Very simple simulation of a bouncing ball physics
;
; Controls:
; - [Space] : reset ball position
; - Esc : quit
; ---------------------------------------------------------------------------------------------------------------

;#SCREENWIDTH = 800
;#SCREENHEIGHT = 600




#BLANKCOLOR = $000001

; These values applies to a 60Hz frame rate
#GRAVITY = 0.16
#STICKYNESS = 0.4
#STOPTHRESHOLD = 0.05

Structure ball_struct
   x.f
   Y.f
   radius.i
   elasticity.f
   
   onTheGround.b
   
   vx.f  ; horizontal velocity
   vy.f  ; vertical velocity
EndStructure
Global NewList ball.ball_struct()

Global *ptrSilhouette.b
Global angle.i, nbStep.i
Global collisionX.f,collisionY.f,normalX.f,normalY.f,VdotN.f,normalForceX.f,normalForceY.f
Global absVx.f, absVy.f,vxStep.f,vyStep.f
ExamineDesktops()
Global  SCREENWIDTH=DesktopWidth(0)
Global  SCREENHEIGHT=DesktopHeight(0)

; Store sinus and cosinus (faster)
Global Dim cosTable.f(360)
Global Dim sinTable.f(360)
For i=0 To 359
   cosTable(i) = Cos( Radian(i) )
   sinTable(i) = Sin( Radian(i) )
Next i
   
EnableExplicit

;- --- Procedures ---

; Store the level's "silhouette" in memory to check collisions
Procedure storeSilhouette(numImage.i)
   Protected *ptrSilhouette,*ptr
   Protected i.i,j.i,width.i,height.i
   
   width = ImageWidth(numImage)
   height = ImageHeight(numImage)
   *ptrSilhouette = AllocateMemory(width * height)
   
   If *ptrSilhouette <> 0
      *ptr = *ptrSilhouette
      StartDrawing(ImageOutput(numImage))
         For j = 0 To height-1
            For i = 0 To width-1
               If Point(i,j) <> #BLANKCOLOR
                  PokeB(*ptr,255)
               Else
                  PokeB(*ptr,0)
               EndIf
               *ptr+1
            Next i
         Next j
      StopDrawing()
   EndIf
   
   ProcedureReturn *ptrSilhouette
EndProcedure

; Look up in the "silhouette" memory array if a given pixel is blank or not
Procedure.b checkCollision(xtest.i, ytest.i, levelWidth.i, levelHeight.i)
   
   If xtest < 1 Or ytest < 1 Or xtest > levelWidth-1 Or ytest > levelHeight-1
      ProcedureReturn 255
   EndIf
   
   ProcedureReturn PeekB(*ptrSilhouette + (ytest*levelWidth) + xtest)
   
EndProcedure

; Draw a random background And create a 3D sprite from it
Procedure drawLevel()
   Protected i.i,x.i,Y.i,w.i,h.i
   
   CreateImage(0,SCREENWIDTH, SCREENHEIGHT)
   StartDrawing(ImageOutput(0))
      Box(0,0,SCREENWIDTH,SCREENHEIGHT,#BLANKCOLOR)
      DrawingMode(#PB_2DDrawing_Gradient)
      FrontColor($616161)
      BackColor($EAEAEA)
      For i = 0 To 30
         x=Random(SCREENWIDTH):Y=Random(SCREENHEIGHT):w=Random(25)+10:h=Random(25)+10
         LinearGradient(x,Y,x+h/2,Y+h)
         Box(x,Y,w,h)
      Next i
      FrontColor($77777A)
      BackColor($CCCCCD)
      For i = 0 To 30
         x=Random(SCREENWIDTH):Y=Random(SCREENHEIGHT):w=Random(25)+10
         CircularGradient(x-(w/2),Y-(w/2),w)
         Circle(x,Y,w)
      Next i
      
      DrawingMode(#PB_2DDrawing_Default)
      Circle(SCREENWIDTH/2,10,50,#BLANKCOLOR)
      DrawingMode(#PB_2DDrawing_Outlined)
      Box(0,0,SCREENWIDTH,SCREENHEIGHT,$FFFFFF)
   StopDrawing()
   
   SaveImage(0,"decor.bmp")
   LoadSprite(999, "decor.bmp", #PB_Sprite_Texture)
   DeleteFile("decor.bmp")
   CreateSprite3D(999,999)
   
   ; Store level's "silhouette" in memory for faster collision check
   ; (it uses a lot of memory, though)
   *ptrSilhouette = storeSilhouette(0)
   FreeImage(0)
   
EndProcedure


;- --- Main program ---
InitSprite()
InitSprite3D()
InitKeyboard()

;- Window
OpenWindow(0, 0, 0, SCREENWIDTH, SCREENHEIGHT, "Bounce", #PB_Window_ScreenCentered|#PB_Window_SystemMenu)
OpenWindowedScreen(WindowID(0), 0, 0, SCREENWIDTH,SCREENHEIGHT, 0, 0, 0,#PB_Screen_SmartSynchronization)

;- Create a sprite for the ball
CreateImage(0,25,25)
StartDrawing(ImageOutput(0))
   DrawingMode(#PB_2DDrawing_Gradient)
   FrontColor($BBBB00)
   BackColor($FFFF00)
   CircularGradient(7,8,12)
   Circle(12,12,12)
StopDrawing()
SaveImage(0,"ball.bmp")
FreeImage(0)
LoadSprite(0, "ball.bmp", #PB_Sprite_Texture)
DeleteFile("ball.bmp")
CreateSprite3D(0, 0)

;- Create a random level
drawLevel()

;- Define ball's properties
AddElement(ball())
ball()\x = Random(SCREENWIDTH)
ball()\Y = 20
ball()\radius = 12
ball()\elasticity = 0.75
ball()\vx = (20 - Random(40))/10
ball()\vy = 0


;- --- Main Loop ---
Repeat
   While WindowEvent() : Wend
   
   ; If the ball is moving or falling, check for collision
   If ball()\vx <> 0 Or ball()\vy <> 0 Or ball()\onTheGround = #False
      
      ; If X-velocity or Y-velocity exceeds 1, break movement into N steps where "partial" velocities are smaller then 1 (= normalize)
      If ball()\vx > 1 Or ball()\vx < -1 Or ball()\vy > 1 Or ball()\vy < -1
         absVx = Abs(ball()\vx)
         absVy = Abs(ball()\vy)
         If absVx > absVy
            nbStep = Int(absVx) + 1
         Else
            nbStep = Int(absVy) + 1
         EndIf
         vxStep = ball()\vx / nbStep
         vyStep = ball()\vy / nbStep
      Else
         nbStep = 1
         vxStep = ball()\vx
         vyStep = ball()\vy
      EndIf
      
      ; For each step, check for collision and move the ball
      For i = 1 To nbStep
         
         ;Check 12 collision points around the ball
         For angle = 0 To 330 Step 30
            ; Get coords for check point at angle [angle]
            collisionX = cosTable(angle)
            collisionY = sinTable(angle)
            
            ; Check collision
            If checkCollision( ball()\x + collisionX * ball()\radius, ball()\Y + collisionY * ball()\radius ,SCREENWIDTH,SCREENHEIGHT)
               ; Get normal to collision
               normalX = -collisionX
               normalY = -collisionY
               
               ; Move ball a bit away from collision
               ball()\Y + normalY
               ball()\x + normalX
               
               ; Project velocity onto collision normal vector
               VdotN = vxStep * normalX + vyStep * normalY
               
               ; Calculate normal force
               normalForceX = -2.0 * normalX * VdotN
               normalForceY = -2.0 * normalY * VdotN
               
               ; Add normal force to velocity vector
               vxStep + normalForceX * ball()\elasticity
               vyStep + normalForceY * ball()\elasticity
            EndIf
            
         Next angle
         
         ; Move the ball   
         ball()\x + vxStep
         ball()\Y + vyStep
      Next i
      
      ; Total velocities =  partial velocities * number of steps
      ball()\vx = vxStep * nbStep
      ball()\vy = vyStep * nbStep
      
      ; If the ball is in the air
      If checkCollision( ball()\x, ball()\Y + ball()\radius + 1,SCREENWIDTH,SCREENHEIGHT) = 0
         ; Apply gravity
         ball()\vy + #GRAVITY
         ball()\onTheGround = #False
      Else
         ; If the ball is on the ground and its falling speed is under the "stickyness" threshold, consider it's rolling
         If ball()\vy < #STICKYNESS And ball()\vy > 0       
            Debug "on the ground"
            ball()\onTheGround = #True
            ball()\vy = 0
         EndIf
         
         ; If the ball is rolling on the ground, apply friction
         If ball()\onTheGround = #True
            ball()\vx * 0.99
            
            ; if the ball rolling speed is sufficiently small, stop it.
            If ball()\vx < #STOPTHRESHOLD And ball()\vx > -#STOPTHRESHOLD
               Debug "stop"
               ball()\vx = 0
            EndIf
            
         EndIf ; if ball()\onTheGround = #True...
      EndIf ; else... (ball is touching the ground)
      
   EndIf ; if the ball is moving or falling...
   
   ; Display screen
   Start3D()
      ; Background   
      DisplaySprite3D(999,0,0,255)     
      ; Ball
      DisplaySprite3D(0,ball()\x-ball()\radius,ball()\Y-ball()\radius)
   Stop3D()   
   
   FlipBuffers()
   
   ; Space => reset sim
   ExamineKeyboard()
   If KeyboardReleased(#PB_Key_Space)
      ball()\x = Random(SCREENWIDTH)
      ball()\Y = 20
      ball()\vx = (20 - Random(40))/10
      ball()\vy = 0
      ball()\onTheGround = #False
   EndIf
   
Until KeyboardPushed(#PB_Key_Escape)

FreeMemory(*ptrSilhouette)
End



kelebrindae
Messages : 579
Inscription : ven. 11/mai/2007 15:21

Re: Physique d'une balle

Message par kelebrindae »

@Dobro:
Euh, mon code fonctionne dans une fenêtre de 800x600; J'aurais tendance à penser qu'il peut fonctionner sur un bureau de 1024x600, non ?
Par ailleurs, si j'ai mis la résolution au tout début du code dans des variables prépro bien visibles, c'est bien pour que les gens puissent la modifier facilement en fonction de leurs goûts/besoin.

Et tu oses dire que je ne pense pas à "TOA" ?
(Pfff! Faites des efforts, tiens...)
Les idées sont le souvenir de choses qui ne se sont pas encore produites.
Backup
Messages : 14526
Inscription : lun. 26/avr./2004 0:40

Re: Physique d'une balle

Message par Backup »

kelebrindae a écrit :@Dobro:
Euh, mon code fonctionne dans une fenêtre de 800x600; J'aurais tendance à penser qu'il peut fonctionner sur un bureau de 1024x600, non ?
oui tu pourrai toujour le penser.... mais voila

ton code d'origine donne
Image

alors que ma modification avec l'emploi de desktop donne :
Image
²
tu vois pas la difference ?

ben dans le code employant desktop() la barre des tache est recouverte !!
alors que dans le tiens elle recouvre le prg , ce qui fait qu'on perd le bas de l'ecran !! ;)
Par ailleurs, si j'ai mis la résolution au tout début du code dans des variables prépro bien visibles, c'est bien pour que les gens puissent la modifier facilement en fonction de leurs goûts/besoin.
ben l'emploi de desktop évite d'avoir a modifier le code "a priori"
et ça va marcher automatiquement chez tout le monde ... :roll:

elles ne sont pas là que pour faire jolie,ces fonctions ;) :)

sinon l'autre soluce consiste a forcer la résolution

Code : Tout sélectionner

; Author: Kelebrindae
; Date: october, 7, 2010
; PB version: v4.51
; OS: Windows

; ---------------------------------------------------------------------------------------------------------------
; Description:
; ---------------------------------------------------------------------------------------------------------------
; Very simple simulation of a bouncing ball physics
;
; Controls:
; - [Space] : reset ball position
; - Esc : quit
; ---------------------------------------------------------------------------------------------------------------

;#SCREENWIDTH = 800
;#SCREENHEIGHT = 600
Declare SetResolution(RezX, RezY, NbCoul, Frequence, Memoriser)
; ************* force la résolution *******************
SetResolution(800, 600, 32, 60, Memoriser)
; *************************************************


#BLANKCOLOR = $000001

; These values applies to a 60Hz frame rate
#GRAVITY = 0.16
#STICKYNESS = 0.4
#STOPTHRESHOLD = 0.05

Structure ball_struct
   x.f
   Y.f
   radius.i
   elasticity.f
   
   onTheGround.b
   
   vx.f  ; horizontal velocity
   vy.f  ; vertical velocity
EndStructure
Global NewList ball.ball_struct()

Global *ptrSilhouette.b
Global angle.i, nbStep.i
Global collisionX.f,collisionY.f,normalX.f,normalY.f,VdotN.f,normalForceX.f,normalForceY.f
Global absVx.f, absVy.f,vxStep.f,vyStep.f
ExamineDesktops()
Global  SCREENWIDTH=DesktopWidth(0)
Global  SCREENHEIGHT=DesktopHeight(0)

; Store sinus and cosinus (faster)
Global Dim cosTable.f(360)
Global Dim sinTable.f(360)
For i=0 To 359
   cosTable(i) = Cos( Radian(i) )
   sinTable(i) = Sin( Radian(i) )
Next i
   
;EnableExplicit

;- --- Procedures ---

; Store the level's "silhouette" in memory to check collisions
Procedure storeSilhouette(numImage.i)
   Protected *ptrSilhouette,*ptr
   Protected i.i,j.i,width.i,height.i
   
   width = ImageWidth(numImage)
   height = ImageHeight(numImage)
   *ptrSilhouette = AllocateMemory(width * height)
   
   If *ptrSilhouette <> 0
      *ptr = *ptrSilhouette
      StartDrawing(ImageOutput(numImage))
         For j = 0 To height-1
            For i = 0 To width-1
               If Point(i,j) <> #BLANKCOLOR
                  PokeB(*ptr,255)
               Else
                  PokeB(*ptr,0)
               EndIf
               *ptr+1
            Next i
         Next j
      StopDrawing()
   EndIf
   
   ProcedureReturn *ptrSilhouette
EndProcedure

; Look up in the "silhouette" memory array if a given pixel is blank or not
Procedure.b checkCollision(xtest.i, ytest.i, levelWidth.i, levelHeight.i)
   
   If xtest < 1 Or ytest < 1 Or xtest > levelWidth-1 Or ytest > levelHeight-1
      ProcedureReturn 255
   EndIf
   
   ProcedureReturn PeekB(*ptrSilhouette + (ytest*levelWidth) + xtest)
   
EndProcedure

; Draw a random background And create a 3D sprite from it
Procedure drawLevel()
   Protected i.i,x.i,Y.i,w.i,h.i
   
   CreateImage(0,SCREENWIDTH, SCREENHEIGHT)
   StartDrawing(ImageOutput(0))
      Box(0,0,SCREENWIDTH,SCREENHEIGHT,#BLANKCOLOR)
      DrawingMode(#PB_2DDrawing_Gradient)
      FrontColor($616161)
      BackColor($EAEAEA)
      For i = 0 To 30
         x=Random(SCREENWIDTH):Y=Random(SCREENHEIGHT):w=Random(25)+10:h=Random(25)+10
         LinearGradient(x,Y,x+h/2,Y+h)
         Box(x,Y,w,h)
      Next i
      FrontColor($77777A)
      BackColor($CCCCCD)
      For i = 0 To 30
         x=Random(SCREENWIDTH):Y=Random(SCREENHEIGHT):w=Random(25)+10
         CircularGradient(x-(w/2),Y-(w/2),w)
         Circle(x,Y,w)
      Next i
      
      DrawingMode(#PB_2DDrawing_Default)
      Circle(SCREENWIDTH/2,10,50,#BLANKCOLOR)
      DrawingMode(#PB_2DDrawing_Outlined)
      Box(0,0,SCREENWIDTH,SCREENHEIGHT,$FFFFFF)
   StopDrawing()
   
   SaveImage(0,"decor.bmp")
   LoadSprite(999, "decor.bmp", #PB_Sprite_Texture)
   DeleteFile("decor.bmp")
   CreateSprite3D(999,999)
   
   ; Store level's "silhouette" in memory for faster collision check
   ; (it uses a lot of memory, though)
   *ptrSilhouette = storeSilhouette(0)
   FreeImage(0)
   
EndProcedure


;- --- Main program ---
InitSprite()
InitSprite3D()
InitKeyboard()

;- Window
OpenWindow(0, 0, 0, SCREENWIDTH, SCREENHEIGHT, "Bounce", #PB_Window_ScreenCentered|#PB_Window_SystemMenu)
OpenWindowedScreen(WindowID(0), 0, 0, SCREENWIDTH,SCREENHEIGHT, 0, 0, 0,#PB_Screen_SmartSynchronization)

;- Create a sprite for the ball
CreateImage(0,25,25)
StartDrawing(ImageOutput(0))
   DrawingMode(#PB_2DDrawing_Gradient)
   FrontColor($BBBB00)
   BackColor($FFFF00)
   CircularGradient(7,8,12)
   Circle(12,12,12)
StopDrawing()
SaveImage(0,"ball.bmp")
FreeImage(0)
LoadSprite(0, "ball.bmp", #PB_Sprite_Texture)
DeleteFile("ball.bmp")
CreateSprite3D(0, 0)

;- Create a random level
drawLevel()

;- Define ball's properties
AddElement(ball())
ball()\x = Random(SCREENWIDTH)
ball()\Y = 20
ball()\radius = 12
ball()\elasticity = 0.75
ball()\vx = (20 - Random(40))/10
ball()\vy = 0


;- --- Main Loop ---
Repeat
   While WindowEvent() : Wend
   
   ; If the ball is moving or falling, check for collision
   If ball()\vx <> 0 Or ball()\vy <> 0 Or ball()\onTheGround = #False
      
      ; If X-velocity or Y-velocity exceeds 1, break movement into N steps where "partial" velocities are smaller then 1 (= normalize)
      If ball()\vx > 1 Or ball()\vx < -1 Or ball()\vy > 1 Or ball()\vy < -1
         absVx = Abs(ball()\vx)
         absVy = Abs(ball()\vy)
         If absVx > absVy
            nbStep = Int(absVx) + 1
         Else
            nbStep = Int(absVy) + 1
         EndIf
         vxStep = ball()\vx / nbStep
         vyStep = ball()\vy / nbStep
      Else
         nbStep = 1
         vxStep = ball()\vx
         vyStep = ball()\vy
      EndIf
      
      ; For each step, check for collision and move the ball
      For i = 1 To nbStep
         
         ;Check 12 collision points around the ball
         For angle = 0 To 330 Step 30
            ; Get coords for check point at angle [angle]
            collisionX = cosTable(angle)
            collisionY = sinTable(angle)
            
            ; Check collision
            If checkCollision( ball()\x + collisionX * ball()\radius, ball()\Y + collisionY * ball()\radius ,SCREENWIDTH,SCREENHEIGHT)
               ; Get normal to collision
               normalX = -collisionX
               normalY = -collisionY
               
               ; Move ball a bit away from collision
               ball()\Y + normalY
               ball()\x + normalX
               
               ; Project velocity onto collision normal vector
               VdotN = vxStep * normalX + vyStep * normalY
               
               ; Calculate normal force
               normalForceX = -2.0 * normalX * VdotN
               normalForceY = -2.0 * normalY * VdotN
               
               ; Add normal force to velocity vector
               vxStep + normalForceX * ball()\elasticity
               vyStep + normalForceY * ball()\elasticity
            EndIf
            
         Next angle
         
         ; Move the ball   
         ball()\x + vxStep
         ball()\Y + vyStep
      Next i
      
      ; Total velocities =  partial velocities * number of steps
      ball()\vx = vxStep * nbStep
      ball()\vy = vyStep * nbStep
      
      ; If the ball is in the air
      If checkCollision( ball()\x, ball()\Y + ball()\radius + 1,SCREENWIDTH,SCREENHEIGHT) = 0
         ; Apply gravity
         ball()\vy + #GRAVITY
         ball()\onTheGround = #False
      Else
         ; If the ball is on the ground and its falling speed is under the "stickyness" threshold, consider it's rolling
         If ball()\vy < #STICKYNESS And ball()\vy > 0       
            Debug "on the ground"
            ball()\onTheGround = #True
            ball()\vy = 0
         EndIf
         
         ; If the ball is rolling on the ground, apply friction
         If ball()\onTheGround = #True
            ball()\vx * 0.99
            
            ; if the ball rolling speed is sufficiently small, stop it.
            If ball()\vx < #STOPTHRESHOLD And ball()\vx > -#STOPTHRESHOLD
               Debug "stop"
               ball()\vx = 0
            EndIf
            
         EndIf ; if ball()\onTheGround = #True...
      EndIf ; else... (ball is touching the ground)
      
   EndIf ; if the ball is moving or falling...
   
   ; Display screen
   Start3D()
      ; Background   
      DisplaySprite3D(999,0,0,255)     
      ; Ball
      DisplaySprite3D(0,ball()\x-ball()\radius,ball()\Y-ball()\radius)
   Stop3D()   
   
   FlipBuffers()
   
   ; Space => reset sim
   ExamineKeyboard()
   If KeyboardReleased(#PB_Key_Space)
      ball()\x = Random(SCREENWIDTH)
      ball()\Y = 20
      ball()\vx = (20 - Random(40))/10
      ball()\vy = 0
      ball()\onTheGround = #False
   EndIf
   
Until KeyboardPushed(#PB_Key_Escape)

FreeMemory(*ptrSilhouette)
End

; Activer une resolution !


#CDS_UPDATEREGISTRY  = $1
#CDS_TEST = $2
#CDS_FULLSCREEN = $4
#CDS_GLOBAL = $8
#CDS_SET_PRIMARY = $10
#CDS_RESET = $40000000
#CDS_SETRECT = $20000000
#CDS_NORESET = $10000000

#DISP_CHANGE_SUCCESSFUL = 0
#DISP_CHANGE_RESTART = 1
#DISP_CHANGE_FAILED = -1
#DISP_CHANGE_BADMODE = -2
#DISP_CHANGE_NOTUPDATED = -3
#DISP_CHANGE_BADFLAGS = -4
#DISP_CHANGE_BADPARAM = -5

#DM_BITSPERPEL = $40000
#DM_PELSWIDTH = $80000
#DM_PELSHEIGHT = $100000
#DM_DISPLAYFREQUENCY = $400000

Procedure SetResolution(RezX, RezY, NbCoul, Frequence, Memoriser) ; si SetResolution=2 la résolution est identique à l'actuelle, si 1 erreur lors de la recherche des infos, sinon regarder #DISP_CHANG_...
   
   dm.DEVMODE
   If EnumDisplaySettings_ (0, -1, @dm)
      If RezX = dm\dmPelsWidth And RezY = dm\dmPelsHeight And NbCoul = dm\dmBitsPerPel And Frequence=dm\dmDisplayFrequency : ProcedureReturn 2 : EndIf
      dmEcran.DEVMODE
      dmEcran\dmSize = SizeOf(dmEcran)
      dmEcran\dmFields = #DM_PELSWIDTH|#DM_PELSHEIGHT|#DM_BITSPERPEL|#DM_DISPLAYFREQUENCY
      dmEcran\dmPelsWidth = RezX
      dmEcran\dmPelsHeight = RezY
      dmEcran\dmBitsPerPel = NbCoul
      dmEcran\dmDisplayFrequency = Frequence
      If Memoriser=1 : Memoriser= #CDS_UPDATEREGISTRY Or #CDS_NORESET : Else : Memoriser= #CDS_FULLSCREEN : EndIf
      dmx = ChangeDisplaySettings_ (@dmEcran,Memoriser)
      ProcedureReturn dmx
   Else
      ProcedureReturn 1
   EndIf
EndProcedure


Avatar de l’utilisateur
flaith
Messages : 1487
Inscription : jeu. 07/avr./2005 1:06
Localisation : Rennes
Contact :

Re: Physique d'une balle

Message par flaith »

Un petit soucis, lorsque la balle se trouve sous cette forme : Image il n'arrête pas de bouger de la droite vers la gauche,
mais en plus il indique qu'il est sur le sol (on the ground) et puis il s'arrête comme ca : Image
C'est pour faire avancer le schmilblick :wink:
kelebrindae
Messages : 579
Inscription : ven. 11/mai/2007 15:21

Re: Physique d'une balle

Message par kelebrindae »

@Flaith:
Oui, ça fait partie des cas non-gérés (comme celui où la balle est bloquée entre deux objets légèrement espacés et bloblotte sans s'arrêter).
"on the ground" indique que le pixel juste à la verticale sous la balle est "plein" et que la vitesse de la balle n'est pas assez grande pour qu'elle rebondisse. Et comme les collisions sont gérées aux pixels plutôt qu'aux vecteurs, la balle peut s'arrêter en plein dans une pente si les "escaliers" sont assez plats.

Euh... Je n'ai pas l'impression d'être très clair, là... :|

En gros, vu de près, n'importe quelle courbe ou pente ressemble à ça pour le programme:

Code : Tout sélectionner

XXXXXXXX
        XXXXXXXX
                XXXXXXXX
                        XXXXXXXX
=> avec le peu de points de collision gérés (12 seulement), la balle peut très bien penser qu'un de ces paliers est plat alors que visuellement, pour nous, ça penche.

Mais comme je l'ai dit, l'algo est très sommaire: mon intention n'est pas d'obtenir une simulation, mais juste un p'tit truc qu'on pourrait insérer dans un jeu de plateforme ou un environnement simple du même genre.
(Ceci dit, si quelqu'un a une solution je suis preneur ! :) )
Les idées sont le souvenir de choses qui ne se sont pas encore produites.
Avatar de l’utilisateur
flaith
Messages : 1487
Inscription : jeu. 07/avr./2005 1:06
Localisation : Rennes
Contact :

Re: Physique d'une balle

Message par flaith »

Si, je te rassures, tu as été parfaitement clair :)
Ce qui m'intéresse dans ton code c'est ta gestion de la gravité (j'avais commencé un jeu en Blitzbasic il y a qq années en faisant un remake de Ghost & goblins, ca va probablement m'aider à le reprendre sous PB avec tes fonctions) :wink:
kelebrindae
Messages : 579
Inscription : ven. 11/mai/2007 15:21

Re: Physique d'une balle

Message par kelebrindae »

En parlant de Blitz Basic, j'ai trouvé un code d'un dénommé Jim Brown (ici: http://www.blitzbasic.com/Community/pos ... opic=55823) permettant de gérer les collisions entre les balles.
Aussitôt vu, aussitôt adapté (pompé?), voici maintenant plein de baballes qui rebondissent. Le code pourrait être optimisé (en remplaçant les procédures par des macros, par exemple) et il y a toujours le problème des balles qui restent bloqués ou bloblottent, mais c'est déjà marrant comme ça... :)

NB: réduisez la valeur de la variable #BALLRADIUS pour augmenter le nombre de balles; plus les balles sont petites, plus il y en a à l'écran. Et si ça rame, n'oubliez pas de désactiver le débugger. :wink:

Code : Tout sélectionner

; Author: Kelebrindae
; Date: october, 7, 2010
; PB version: v4.51
; OS: Windows

; ---------------------------------------------------------------------------------------------------------------
; Description:
; ---------------------------------------------------------------------------------------------------------------
; Very simple simulation of bouncing balls physics
;
; Controls:
; - [Space] : reset balls position
; - Esc : quit
; ---------------------------------------------------------------------------------------------------------------

#SCREENWIDTH = 800
#SCREENHEIGHT = 500
#BLANKCOLOR = $000001

; These values apply to a 60Hz frame rate
#GRAVITY = 0.16
#STICKYNESS = 0.4
#STOPTHRESHOLD = 0.05

; Smaller => more balls on screen
#BALLRADIUS = 12

Structure ball_struct
  x.f
  y.f
  radius.i
  elasticity.f
  mass.f
  
  onTheGround.b
  
  vx.f  ; horizontal velocity
  vy.f  ; vertical velocity
EndStructure
Global NewList ball.ball_struct()

Global *ptrSilhouette.b
Global angle.i, nbStep.i,i.i,j.i
Global collisionX.f,collisionY.f,normalX.f,normalY.f,VdotN.f,normalForceX.f,normalForceY.f
Global absVx.f, absVy.f,vxStep.f,vyStep.f
Global *ptrBall.ball_struct

; Store sinus and cosinus (faster)
Global Dim cosTable.f(360)
Global Dim sinTable.f(360)
For i=0 To 359
  cosTable(i) = Cos( Radian(i) )
  sinTable(i) = Sin( Radian(i) )
Next i
   
EnableExplicit

;- --- Procedures ---

; Store the level's "silhouette" in memory to check collisions
Procedure storeSilhouette(numImage.i)
  Protected *ptrSilhouette,*ptr
  Protected i.i,j.i,width.i,height.i
 
  width = ImageWidth(numImage)
  height = ImageHeight(numImage)
  *ptrSilhouette = AllocateMemory(width * height)
 
  If *ptrSilhouette <> 0
    *ptr = *ptrSilhouette
     StartDrawing(ImageOutput(numImage))
    For j = 0 To height-1
      For i = 0 To width-1
        If Point(i,j) <> #BLANKCOLOR
          PokeB(*ptr,255)
        Else
          PokeB(*ptr,0)
        EndIf
        *ptr+1
      Next i
    Next j
    StopDrawing()
  EndIf
 
  ProcedureReturn *ptrSilhouette
EndProcedure

; Look up in the "silhouette" memory array if a given pixel is blank or not
Procedure.b checkCollision(xtest.i, ytest.i, levelWidth.i, levelHeight.i)
   
  If xtest < 1 Or ytest < 1 Or xtest > levelWidth-1 Or ytest > levelHeight-1
    ProcedureReturn 255
  EndIf
 
  ProcedureReturn PeekB(*ptrSilhouette + (ytest*levelWidth) + xtest)
   
EndProcedure

; Draw a random background And create a 3D sprite from it
Procedure drawLevel()
  Protected i.i,x.i,y.i,w.i,h.i
  
  CreateImage(0,#SCREENWIDTH, #SCREENHEIGHT)
  StartDrawing(ImageOutput(0)) 
    Box(0,0,#SCREENWIDTH,#SCREENHEIGHT,#BLANKCOLOR)
    DrawingMode(#PB_2DDrawing_Gradient)
    FrontColor($616161)
    BackColor($EAEAEA)
    For i = 0 To 20
      x=Random(#SCREENWIDTH):y=50+Random(#SCREENHEIGHT-50):w=Random(25)+10:h=Random(25)+10
      LinearGradient(x,y,x+h/2,y+h)
      Box(x,y,w,h)
    Next i
    FrontColor($77777A)
    BackColor($CCCCCD)
    For i = 0 To 20
      x=Random(#SCREENWIDTH):y=50+Random(#SCREENHEIGHT-50):w=Random(25)+10
      CircularGradient(x-(w/2),y-(w/2),w)
      Circle(x,y,w)
    Next i 
    
    DrawingMode(#PB_2DDrawing_Default)
    Box(0,0,#SCREENWIDTH,50,#BLANKCOLOR)
    DrawingMode(#PB_2DDrawing_Outlined)
    Box(0,0,#SCREENWIDTH,#SCREENHEIGHT,$FFFFFF)
  StopDrawing()
    
  SaveImage(0,"decor.bmp")
  LoadSprite(999, "decor.bmp", #PB_Sprite_Texture)
  DeleteFile("decor.bmp")
  CreateSprite3D(999,999)
  
  ; Store level's "silhouette" in memory for faster collision check
  ; (it uses a lot of memory, though)
  *ptrSilhouette = storeSilhouette(0)
  FreeImage(0)
  
EndProcedure

; Check collision between two balls. If they collide, set their new speed and direction
; (conversion of a Blitz Basic code from Jim Brown, here: http://www.blitzbasic.com/Community/posts.php?topic=55823 )
Procedure collideBalls(*ptrBall1.ball_struct,*ptrBall2.ball_struct)
  Protected collisionDistance.f,actualDistance.f
  Protected realcollNormalAngle.f,collNormalAngle.i
  Protected moveDist1.f,moveDist2.f
  Protected nX.f,nY.f,a1.f,a2.f,optimisedP.f
  
  ; Check distance between the balls (don't use SQR to make it faster)
	collisionDistance = (*ptrBall1\radius + *ptrBall2\radius)*(*ptrBall1\radius + *ptrBall2\radius)
	actualDistance = ( (*ptrBall2\x - *ptrBall1\x)*(*ptrBall2\x - *ptrBall1\x) + (*ptrBall2\y - *ptrBall1\y)*(*ptrBall2\y - *ptrBall1\y) )
	
	; If the balls collide
	If actualDistance < collisionDistance
	  ; well, now we need real distances, so => SQR
	  collisionDistance = *ptrBall1\radius + *ptrBall2\radius
	  actualDistance = Sqr(actualDistance) 
	  
    ; Compute angle between the to ball	  
    realcollNormalAngle = Degree(ATan2(*ptrBall2\x - *ptrBall1\x,*ptrBall2\y - *ptrBall1\y ))
    
    ; Convert this angle to an integer in the 0 - 359 range (to use in the Cos/Sin tables)
		If realcollNormalAngle < 0
		  collNormalAngle = realcollNormalAngle + 360
		Else
		  collNormalAngle = realcollNormalAngle 
		EndIf  
	  
		; Move the balls so they don't intersect, according to the collision angle and their mass
		moveDist1=(collisionDistance-actualDistance) * (*ptrBall2\mass / (*ptrBall1\mass + *ptrBall2\mass))
		moveDist2=(collisionDistance-actualDistance) * (*ptrBall1\mass / (*ptrBall1\mass + *ptrBall2\mass))
		*ptrBall1\x - moveDist1 * cosTable(collNormalAngle)
		*ptrBall1\y - moveDist1 * sinTable(collNormalAngle)
		*ptrBall2\x + moveDist2 * cosTable(collNormalAngle)
		*ptrBall2\y + moveDist2 * sinTable(collNormalAngle)
				
		; COLLISION RESPONSE
		; n = vector connecting the centers of the balls.
		; nX and nY are the components of the normalised vector n
		nX = cosTable(collNormalAngle)
		nY = sinTable(collNormalAngle)
		
		; Find the components of each movement vectors along n, by using dot product
		a1 = *ptrBall1\vx * nX + *ptrBall1\vy * nY
		a2 = *ptrBall2\vx * nX + *ptrBall2\vy * nY
		
		; optimisedP = 2(a1 - a2)
		;             ----------
		;              m1 + m2
		optimisedP = (2.0 * (a1-a2)) / (*ptrBall1\mass + *ptrBall2\mass)
		
		; Resultant vector for first ball
		*ptrBall1\vx - (optimisedP * *ptrBall2\mass * nX)
		*ptrBall1\vy - (optimisedP * *ptrBall2\mass * nY)
		
		; Resultant vector for second ball
		*ptrBall2\vx + (optimisedP * *ptrBall1\mass * nX)
		*ptrBall2\vy + (optimisedP * *ptrBall1\mass * nY)
	EndIf
	
EndProcedure


;- --- Main program ---
InitSprite()
InitSprite3D()
InitKeyboard()

;- Window
OpenWindow(0, 0, 0, #SCREENWIDTH, #SCREENHEIGHT, "Bounce", #PB_Window_ScreenCentered|#PB_Window_SystemMenu)
OpenWindowedScreen(WindowID(0), 0, 0, #SCREENWIDTH,#SCREENHEIGHT, 0, 0, 0,#PB_Screen_WaitSynchronization)

;- Create a sprite for the ball
CreateImage(0,51,51)
StartDrawing(ImageOutput(0))
  DrawingMode(#PB_2DDrawing_Gradient)
  FrontColor($BBBB00)
  BackColor($FFFF00)
  CircularGradient(14,14,25)
  Circle(25,25,25)
StopDrawing()
ResizeImage(0,#BALLRADIUS*2,#BALLRADIUS*2)  
SaveImage(0,"ball.bmp")
FreeImage(0)
LoadSprite(0, "ball.bmp", #PB_Sprite_Texture)
DeleteFile("ball.bmp")
CreateSprite3D(0, 0)

;- Create a random level
drawLevel()

;- Create balls
i=10+#BALLRADIUS:j=#BALLRADIUS
Repeat
  AddElement(ball())
  ball()\x = i
  ball()\y = j
  ball()\radius = #BALLRADIUS
  ball()\mass = 10
  ball()\elasticity = 0.75
  ball()\vx = (20 - Random(40))/10
  ball()\vy = 0
  i + #BALLRADIUS*2 + 4
  If i > #SCREENWIDTH - 10 - #BALLRADIUS
    i=10+#BALLRADIUS
    j + #BALLRADIUS*2 + 4
  EndIf
Until j > 50-#BALLRADIUS

;- --- Main Loop ---
Repeat
  While WindowEvent() : Wend
  
  ForEach ball()
    ; If the ball is moving or falling, check for collision
    If ball()\vx <> 0 Or ball()\vy <> 0 Or ball()\onTheGround = #False
      
      ;- Collision with the background
      ; If X-velocity or Y-velocity exceeds 1, break movement into N steps where "partial" velocities are smaller then 1 (= normalize)
      If ball()\Vx > 1 Or ball()\Vx < -1 Or ball()\Vy > 1 Or ball()\Vy < -1
        absVx = Abs(ball()\Vx)
        absVy = Abs(ball()\Vy)
        If absVx > absVy
          nbStep = Int(absVx) + 1
        Else
          nbStep = Int(absVy) + 1
        EndIf
        vxStep = ball()\Vx / nbStep
        vyStep = ball()\Vy / nbStep
      Else
        nbStep = 1
        vxStep = ball()\Vx
        vyStep = ball()\Vy
      EndIf
     
      ; For each step, check for collision and move the ball
      For i = 1 To nbStep
    
        ;Check 12 collision points around the ball
        For angle = 0 To 330 Step 30
          ; Get coords for check point at angle [angle]
          collisionX = cosTable(angle)
          collisionY = sinTable(angle)
            
          ; Check collision
          If checkCollision( ball()\x + collisionX * ball()\radius, ball()\y + collisionY * ball()\radius ,#SCREENWIDTH,#SCREENHEIGHT)
            ; Get normal to collision
            normalX = -collisionX
            normalY = -collisionY
                    
            ; Move ball a bit away from collision
            ball()\y + normalY
            ball()\x + normalX
            
            ; Project velocity onto collision normal vector
            VdotN = vxStep * normalX + vyStep * normalY
            
            ; Calculate normal force
            normalForceX = -2.0 * normalX * VdotN
            normalForceY = -2.0 * normalY * VdotN
              
            ; Add normal force to velocity vector
            vxStep + normalForceX * ball()\elasticity
            vyStep + normalForceY * ball()\elasticity
          EndIf
          
        Next angle
         
        ; Move the ball   
        ball()\x + vxStep
        ball()\y + vyStep
      Next i
     
      ; Total velocities =  partial velocities * number of steps
      ball()\vx = vxStep * nbStep
      ball()\vy = vyStep * nbStep
       
      ; If the ball is in the air
      If checkCollision( ball()\x, ball()\y + ball()\radius + 1,#SCREENWIDTH,#SCREENHEIGHT) = 0
        ; Apply gravity
        ball()\vy + #GRAVITY
        ball()\onTheGround = #False
      Else
        ; If the ball is on the ground and its falling speed is under the "stickyness" threshold, consider it's rolling 
        If ball()\vy < #STICKYNESS And ball()\vy > 0        
          ball()\onTheGround = #True
          ball()\vy = 0
        EndIf
        
        ; If the ball is rolling on the ground, apply friction
        If ball()\onTheGround = #True
          ball()\vx * 0.99
          
          ; if the ball rolling speed is sufficiently small, stop it.
          If ball()\vx < #STOPTHRESHOLD And ball()\vx > -#STOPTHRESHOLD 
            ball()\vx = 0
          EndIf
          
        EndIf ; if ball()\onTheGround = #True...
      EndIf ; else... (ball is touching the ground)
      
      ;- Collision with all the other balls
      *ptrBall = @ball()
      ForEach ball()
        If @ball() <> *ptrBall
          collideBalls(*ptrBall,@ball())
        EndIf
      Next ball()
      ChangeCurrentElement(ball(),*ptrBall)
      
    EndIf ; if the ball is moving or falling...
  Next ball()  
    
  ;- Display screen
  Start3D()
    ; Background   
    DisplaySprite3D(999,0,0,255)      
    ; Ball
    ForEach ball()
      DisplaySprite3D(0,ball()\x-ball()\radius,ball()\y-ball()\radius)
    Next ball()
  Stop3D()   
     
  FlipBuffers()
   
  ; Space => reset sim
  ExamineKeyboard()
  If KeyboardReleased(#PB_Key_Space)
    i=10+#BALLRADIUS:j=#BALLRADIUS
    ForEach ball()
      ball()\x = i
      ball()\y = j
      ball()\vx = (20 - Random(40))/10
      ball()\vy = 0
      ball()\onTheGround = #False
      i + #BALLRADIUS*2 + 4
      If i > #SCREENWIDTH - 10 - #BALLRADIUS
        i=10+#BALLRADIUS
        j + #BALLRADIUS*2 + 4
      EndIf
    Next ball()
  EndIf

Until KeyboardPushed(#PB_Key_Escape)

FreeMemory(*ptrSilhouette)
End
Les idées sont le souvenir de choses qui ne se sont pas encore produites.
Avatar de l’utilisateur
Cool Dji
Messages : 1126
Inscription : ven. 05/sept./2008 11:42
Localisation : Besançon
Contact :

Re: Physique d'une balle

Message par Cool Dji »

Génial !

Dommage pour la bloblotte :mrgreen:
Only PureBasic makes it possible
Répondre