2D physics engine (Verlet)

Advanced game related topics
Kelebrindae
Enthusiast
Enthusiast
Posts: 151
Joined: Tue Apr 01, 2008 3:23 pm

2D physics engine (Verlet)

Post by Kelebrindae »

Hi,

Here's a little 2D physics engine based on "Verlet Integration".
I came across the following article: http://www.gamasutra.com/resource_guide ... n_01.shtml and it seemed accessible to me despite my limited knowledge in maths. So I tried to code it in PB.
The goal is not to create an accurate simulation; it's just a little something to include in a platform game (or other) without consuming too much CPU.

The source comes in 3 parts:
- an include containing macros to manage vectors 2;
- an include containing the physics engine itself (which uses the include above);
- A small demo program that uses the physics engine.

Controls:
- F1-F9: create objects
- Mouse: keeping the left mouse button pressed, you can pick and move a point on the screen
- Del: Removes all objects (or only one if the left button is pressed)
- Right Ctrl: slow motion (keep the button pressed)
- Return: enables/disables the display (for speed tests)

Note: Due to some optimizations (for the inverse square root and pointers) I'm not sure it works in 64 bits, and even less on Mac or Linux.

There are still a few things to finish, but it's already fun as it is, so I post it... : wink:

Include#1 (save it as "vector2.pbi")

Code: Select all

; Author: Kelebrindae
; Date: December, 01, 2010
; PB version: v4.51
; ---------------------------------------------------------------------------------------------------------------
; Description:
; ---------------------------------------------------------------------------------------------------------------
; Vector2 library
; Parts of this code is based on this page: http://www.blitzmax.com/codearcs/codearcs.php?code=2737
; Thanks, Sauer!
; ---------------------------------------------------------------------------------------------------------------

;- Structure and globals
Structure vector2_struct
  x.f
  y.f
EndStructure

Global VEC2_tempLength.f
Global VEC2_tempCondition.b
Global VEC2_tempVectorC.vector2_struct,VEC2_tempVectorD.vector2_struct,VEC2_tempVectorE.vector2_struct
Global VEC2_tempProjVector.vector2_struct,VEC2_tempZeroVector.vector2_struct
Global VEC2_sqrhalf.f,*VEC2_sqrptr,VEC2_sqrInt.i ; used in fast inverse square root macro


;************************************************************************************
; Name: VEC2_INVSQR
; Purpose: Fast inverse square root approximation (faster than 1/sqr(x) )
; Credits: http://www.purebasic.fr/french/viewtopic.php?f=6&t=4113&hilit=sqr
;          http://www.purebasic.fr/french/viewtopic.php?f=6&t=9963
; Parameters:
;   - input  (float)
;   - result (lloat)
;************************************************************************************
Macro VEC2_INVSQR(x,result)
  result = x
  VEC2_sqrhalf = 0.5 * result
  *VEC2_sqrptr = @result
  VEC2_sqrInt  = PeekI(*VEC2_sqrptr)
  
  ; The magic number is different in 64 bits
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
    PokeI(*VEC2_sqrptr,$5FE6EB50C7AA19F9 - ( VEC2_sqrInt >> 1))
  CompilerElse ; all the others are 32 bits ?
    PokeI(*VEC2_sqrptr,$5F375A86 - ( VEC2_sqrInt >> 1)) ; or $5f3759df
  CompilerEndIf
  
  result=result*(1.5 - VEC2_sqrhalf * result * result)
  result=result*(1.5 - VEC2_sqrhalf * result * result) ; twice for greater precision
  result=result*(1.5 - VEC2_sqrhalf * result * result) ; thrice is even better (but you can ditch it to squeeze a few extra cycles from your code)
EndMacro


;************************************************************************************
; Name: VEC2_add
; Purpose: Adds two vector
; Parameters:
;   - vector #1 (vector2)
;   - vector #2 (vector2)
;   - result (vector2)
;************************************************************************************
Macro VEC2_add(vectorA,vectorB,vectorResult)
	vectorResult\x = vectorA\x + vectorB\x
	vectorResult\y = vectorA\y + vectorB\y
EndMacro


;************************************************************************************
; Name: VEC2_substract
; Purpose: Substracts two vector
; Parameters:
;   - vector #1 (vector2)
;   - vector #2 (vector2)
;   - result (vector2)
;************************************************************************************
Macro VEC2_substract(vectorA,vectorB,vectorResult)
	vectorResult\x = vectorA\x - vectorB\x
	vectorResult\y = vectorA\y - vectorB\y
EndMacro  


;************************************************************************************
; Name: VEC2_multiply
; Purpose: Multiply a vector by a number
; Parameters:
;   - vector  (vector2)
;   - magnitude (float)
;   - result (vector2)
;************************************************************************************
Macro VEC2_multiply(vectorA,magnitude,vectorResult)
	vectorResult\x = vectorA\x * magnitude
	vectorResult\y = vectorA\y * magnitude
EndMacro  


;************************************************************************************
; Name: VEC2_squareLength
; Purpose: (distance between A and B)²
; Parameters:
;   - coords of point A (vector2)
;   - coords of point B (vector2)
;************************************************************************************
Macro VEC2_squareLength(vectorA,vectorB)
	((vectorA\x - vectorB\x) * (vectorA\x - vectorB\x) + (vectorA\y - vectorB\y) * (vectorA\y - vectorB\y))
EndMacro


;************************************************************************************
; Name: VEC2_length
; Purpose: Distance between A and B
; Parameters:
;   - coords of point A (vector2)
;   - coords of point B (vector2)
;************************************************************************************
Macro VEC2_length(vectorA)
	Sqr( vectorA\x*vectorA\x + vectorA\y*vectorA\y )
EndMacro 


;************************************************************************************
; Name: VEC2_normalize
; Purpose: Normalizes a vector
; Parameters:
;   - vector A (vector2)
;   - result (vector2)
;************************************************************************************
Macro VEC2_normalize(vectorA,vectorResult)
  ; Standard method
  VEC2_tempLength = VEC2_length(vectorA)
  vectorResult\x = vectorA\x / VEC2_tempLength
  vectorResult\y = vectorA\y / VEC2_tempLength
  
  ; Faster
  ;VEC2_INVSQR(vectorA\x*vectorA\x + vectorA\y*vectorA\y,VEC2_tempLength)
  ;vectorResult\x = vectorA\x * VEC2_tempLength
  ;vectorResult\y = vectorA\y * VEC2_tempLength
EndMacro


;************************************************************************************
; Name: VEC2_dotproduct
; Purpose: Dot product of two 2D vector
; Parameters:
;   - vector #1
;   - vector #2
;************************************************************************************
Macro VEC2_dotProduct(vectorA,vectorB)
  (vectorA\x * vectorB\x + vectorA\y * vectorB\y)
EndMacro


;************************************************************************************
; Name: VEC2_crossproduct
; Purpose: Cross product of two 2D vector
; Parameters:
;   - vector #1
;   - vector #2
;************************************************************************************
Macro VEC2_crossProduct(vectorA,vectorB)
  (vectorA\x * vectorB\y - vectorA\y * vectorB\x)
EndMacro


;************************************************************************************
; Name: VEC2_crossVector
; Purpose: Gives the perpendicular vector
; Parameters:
;   - input (vector2)
;   - result (vector2)
;************************************************************************************
Macro VEC2_crossVector(vectorA,vectorResult)
	;returns a vector perpendicular to v
	vectorResult\x = vectorA\y
	vectorResult\y = -vectorA\x
EndMacro


;************************************************************************************
; Name: VEC2_angleBetween
; Purpose: Angle from A to B
; Parameters:
;   - coords of point A (vector2)
;   - coords of point B (vector2)
;************************************************************************************
Macro VEC2_angleBetween(vectorA,vectorB)
	ACos(VEC2_dotProduct(normalize(vectorA),normalize(vectorB)))
EndMacro 


;************************************************************************************
; Name: VEC2_rotate
; Purpose: Rotates a vector
; Parameters:
;   - vector  (vector2)
;   - angle in radians (float)
;   - result (vector2)
;************************************************************************************
Macro VEC2_rotate(vectorA,rad,vectorResult)
	; Counter clockwise rotation, in radians
	vectorResult\x=(Cos(rad)*vectorA\x)-(Sin(rad)*vectorA\y)
	vectorResult\y=(Sin(rad)*vectorA\x)+(Cos(rad)*vectorA\y)
EndMacro 


;************************************************************************************
; Name: VEC2_reflection
; Purpose: Computes a reflection vector, vectorA being the direction and vectorB being 
;          the surface.on which to reflect ("Through" means "through wall" or "bounce 
;          off wall")
; Parameters:
;   - vector #1 (vector2)
;   - vector #2 (vector2)
;   - result (vector2)
;************************************************************************************
Macro VEC2_Reflection(vectorA,vectorB,through,vectorResult)
	If through=#False
	  VEC2_tempVectorC\x = vectorB\x
	  VEC2_tempVectorC\y = vectorB\y
	Else
		VEC2_tempVectorC = VEC2_crossVector(vectorB)
	EndIf 
	VEC2_tempVectorD = VEC2_normalize(vectorA)
	VEC2_tempVectorC = VEC2_normalize(VEC2_tempVectorC)
	VEC2_tempVectorE = VEC2_multiply(VEC2_tempVectorC,VEC2_dotProduct(VEC2_tempVectorD,VEC2_tempVectorC))
	vectorResult = VEC2_substract(VEC2_multiply(VEC2_tempVectorE,2),VEC2_tempVectorD)
	vectorResult = VEC2_normalize(vectorResult)
EndMacro 


;************************************************************************************
; Name: VEC2_collCircleToCircle
; Purpose: Indicates if two circles collide
; Parameters:
;   - center of circle #1 (vector2)
;   - center of circle #2 (vector2)
;   - radius of circle #1 (float)
;   - radius of circle #2 (float)
;   - result (boolean)
;************************************************************************************
Macro VEC2_collCircleToCircle(vectorA,vectorB,radiusA,radiusB,result)
	;simple circle to circle collision using vectors; both circles has the same radius
	If VEC2_squareLength(vectorA,vectorB) < (radius) * (radius)
		result = #True
	Else
		result = #False
	EndIf
EndMacro 


;************************************************************************************
; Name: VEC2_collCircleToVector
; Purpose: Indicates if a circle (with center vectorA and radius) and a vector (with
;          origin vectorB and direction/magnitude.vectorC) are colliding, 
; Parameters:
;   - center of circle (vector2)
;   - radius of circle (float)
;   - vector origin (vector2)
;   - vector direction/magnitude (vector2)
;   - result (boolean)
;************************************************************************************
Macro VEC2_collCircleToVector(vectorA,radius,vectorB,vectorC,result)
  VEC2_tempVectorD = VEC2_substract(vectorA,vectorB)
  VEC2_tempVectorE = normalize(vectorC)
  VEC2_tempProjVector = VEC2_multiply(VEC2_tempVectorE,VEC2_dotProduct(VEC2_tempVectorE,VEC2_tempVectorD))
	VEC2_collCircleToCircle(VEC2_tempVectorD,VEC2_tempProjVector,radius,radius,VEC2_tempCondition)
	VEC2_tempLength = VEC2_length(vectorC) + radius
	VEC2_tempLength = (VEC2_tempLength * VEC2_tempLength)
	If VEC2_tempCondition = #True And VEC2_squareLength(VEC2_tempProjVector,VEC2_tempZeroVector) <= tempVEC2_Length + radius And VEC2_squareLength(VEC2_tempProjVector,vectorC) <= VEC2_tempLength + radius
	  result = #True
	Else
	  result = #False
	EndIf
EndMacro

Last edited by Kelebrindae on Mon Dec 22, 2014 9:39 am, edited 1 time in total.
Kelebrindae
Enthusiast
Enthusiast
Posts: 151
Joined: Tue Apr 01, 2008 3:23 pm

Re: 2D physics engine (Verlet)

Post by Kelebrindae »

Include #2 (save it as "verlet2D.pbi")

Code: Select all

; Author: Kelebrindae
; Date: December, 15, 2010
; PB version: v4.51
; ---------------------------------------------------------------------------------------------------------------
; Description:
; ---------------------------------------------------------------------------------------------------------------
; A simple 2D physics engine, based on Verlet integration. For a physics noob like me, Verlet looks like magic!
;
; The physics code is based on the following two articles:
;   - http://www.gamasutra.com/resource_guide/20030121/jacobson_01.shtml
;   - http://www.gamedev.net/reference/articles/article2714.asp
; ---------------------------------------------------------------------------------------------------------------
; Known bugs and limitations:
; ---------------------------------------------------------------------------------------------------------------
; - Considering my knowledge of physics and my fluency with vectors, most of the maths in the collision response 
;   are probably wrong. Sorry!
; - TO DO: Procs to weld / separate two points
; - TO DO: Ball simulation (by adding a radius to a single point)
; - TO DO: Adding a "stop" state to bodies under special conditions (near-to-zero speed, resting on an object...)
;          to increase stability.
; ---------------------------------------------------------------------------------------------------------------

;- Constants, structures, globals
; This constant is used to read the lists of points/constraints in memory 
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
  #SIZEOFPTR = 8
CompilerElse
  #SIZEOFPTR = 4
CompilerEndIf


#SUBSAMPLING = 5              ; Greater sub-sampling means slower but more precise simulation (and objects that don't pass through each others)
#CONSTRAINT_ITERATIONS = 4    ; More iterations means that bodies are more rigid
#GRAVITY = 0.005              ; Should be set according to #SUBSAMPLING (gravity is applied in each sub-sampling iteration)
#FRICTION = 0.45              ; Friction for a point colliding against an edge
#FRICTION_EDGEONPOINT_FACTOR = 5  ; Friction for a edge colliding ( = grinding) against a single point is 5 times lesser by default

; 2D vector macro library
IncludeFile "vector2.pbi"

; Pointmass, a.k.a. vertices of a rigid body
Structure pointmass_struct
  x.f     ; Coords x,y
  y.f 
  oldX.f  ; Previous coords x,y
  oldY.f  ; in Verlet integration, velocity, momentum, etc.. are implicit: we just need current and previous coords.
  mass.f    ; mass of the point
  invmass.f ; inverse mass. Set it to zero for static points
  collision.b
EndStructure
Global NewList pointmass.pointmass_struct()

; Constraints, a.k.a. edges of a rigid body
Structure constraint_struct
 *p1.pointmass_struct ; pointer to first point
 *p2.pointmass_struct ; pointer to second point
 enable.b             ; will this constraint be checked for collision ?
 length.f             ; length of the constraint
 length2.f            ; this is equals to length*length (optimization)
 *ptrParent.rigidBody_struct  ; pointer to the parent body
EndStructure
Global NewList constraint.constraint_struct()

; Rigid body
Structure rigidBody_struct
  numint.i  ; Unique ID
  
  nbPoint.i ; Nb points in this body
  nbEdge.i  ; Nb constraints in this body
  *ptrPointList      ; pointer to a list of pointers to pointmasses (complicated, but faster than a list() )
  *ptrConstraintList ; pointer to a list of pointers to constraints (complicated, but faster than a list() )
  
  ; Point to the parent compound
  *ptrParent.compound_struct
  
  ; These are re-calculated at each frame (for collisions)
  center.vector2_struct
  minX.f
  maxX.f
  minY.f
  maxY.f
  
  ; friction
  friction.f
  frictionEdgeOnPoint.f
  
  timerRest.i
  atRest.b
EndStructure
Global NewList body.rigidBody_struct()

; Compounds are multi-parts objects, like ropes or bridges. Objects which are part of the same compound don't collide with each other
Structure compound_struct
  name.s
EndStructure
Global NewList compound.compound_struct()

; Used to store collisions datas
Structure collisionInfo_struct
  depth.f
  normal.vector2_struct
  collisionVector.vector2_struct
  
  *ptrEdge.constraint_struct
  *ptrPoint.pointmass_struct
  
  collisionCase.b ; store the collision configuration: no static point, one static point, etc.
EndStructure
Global VERLET_collisionInfo.collisionInfo_struct

; Global vars used in Macros
Global VERLET_i.i,VERLET_j.i,VERLET_numBody.i,VERLET_nbEdges.i
Global VERLET_dx.f,VERLET_dy.f, VERLET_deltalength.f,VERLET_diff.f
Global *VERLET_ptrPoint.pointmass_struct,*VERLET_ptrEdge.constraint_struct,*VERLET_ptrGen,*VERLET_ptrGen2
Global VERLET_axis.vector2_struct,VERLET_tempVector.vector2_struct
Global VERLET_minA.f,VERLET_minB.f,VERLET_maxA.f,VERLET_maxB.f,VERLET_distance.f,VERLET_minDistance.f
Global VERLET_dotP.f
Global VERLET_ratio1.f,VERLET_ratio3.f
Global *VERLET_ptrCollPointBody.rigidBody_struct,*VERLET_ptrCollEdgeBody.rigidBody_struct
Global VERLET_T.f,VERLET_lambda.f,VERLET_edgeMass.f,VERLET_invCollisionMass.f
Global *VERLET_prevBodyPtr.rigidBody_struct,*VERLET_pointList
Global VERLET_startIndex.i, VERLET_isCollision.b, VERLET_isCollisionWhole.b
Global sumMvt.f,mvt.b


;- Collision and movements

;************************************************************************************
; Name: INTERVAl_DISTANCE
; Purpose: Indicates the minimum distance between the points of two overlapping segments
; Parameters:
;   - segment A min (float)
;   - segment A max (float)
;   - segment B min (float)
;   - segment B max (float)
;   - distance (output float)
;************************************************************************************
Macro INTERVAL_DISTANCE(minA,maxA,minB,maxB,distance)
  If minA < minB
    distance = maxA - minB
  Else
    distance = maxB - minA
  EndIf
EndMacro

;************************************************************************************
; Name: PROJECT_TO_AXIS
; Purpose: Projects the points of a body onto an line
; Parameters:
;   - pointer to a rigid body (*ptr)
;   - axis (vector2)
;   - min (output float)
;   - max (output float)
;************************************************************************************
Macro PROJECT_TO_AXIS (ptrBody,axis,minValue,maxValue)
  *VERLET_ptrGen = ptrBody\ptrPointList
  *VERLET_ptrPoint = PeekI(*VERLET_ptrGen)
  VERLET_dotP = VEC2_dotProduct(axis,*VERLET_ptrPoint)
  minValue = VERLET_dotP
  maxValue = VERLET_dotP
  For VERLET_j = 1 To ptrBody\nbPoint
    *VERLET_ptrGen + #SIZEOFPTR
    *VERLET_ptrPoint = PeekI(*VERLET_ptrGen)
    
    ; Project the rest of the vertices onto the axis and extend the interval to the left/right if necessary
    VERLET_dotP = VEC2_dotProduct(axis,*VERLET_ptrPoint)
    If VERLET_dotP < minValue
      minValue = VERLET_dotP
    EndIf
    If VERLET_dotP > maxValue
      maxValue = VERLET_dotP
    EndIf
  Next VERLET_j
EndMacro    

;************************************************************************************
; Name: COLLISION_RESPONSE
; Purpose: Computes the collision response between a point and a segment. None, one or
;          two points may be static (which leads to different response managements)
; Parameters:
;   None => the needed info is stored in the "VERLET_collisionInfo" structure
;************************************************************************************
Macro COLLISION_RESPONSE(ptrBodyPoint,ptrBodyEdge)
    ; Collision response = VERLET_collisionInfo\normal * VERLET_collisionInfo\depth
  VEC2_multiply(VERLET_collisionInfo\normal,VERLET_collisionInfo\depth,VERLET_collisionInfo\collisionVector)
  
  ; Edge collision response: we need to know where the point hit the edge
  If( Abs( VERLET_collisionInfo\ptrEdge\p1\x - VERLET_collisionInfo\ptrEdge\p2\x ) > Abs( VERLET_collisionInfo\ptrEdge\p1\y - VERLET_collisionInfo\ptrEdge\p2\y ) )
    VERLET_T = ( VERLET_collisionInfo\ptrPoint\x - VERLET_collisionInfo\collisionVector\x - VERLET_collisionInfo\ptrEdge\p1\x )/(  VERLET_collisionInfo\ptrEdge\p2\x - VERLET_collisionInfo\ptrEdge\p1\x)
  Else
    VERLET_T = ( VERLET_collisionInfo\ptrPoint\y - VERLET_collisionInfo\collisionVector\y - VERLET_collisionInfo\ptrEdge\p1\y )/(  VERLET_collisionInfo\ptrEdge\p2\y - VERLET_collisionInfo\ptrEdge\p1\y)
  EndIf
  VERLET_lambda = 1.0 / ( VERLET_T*VERLET_T + ( 1 - VERLET_T )*( 1 - VERLET_T ) )
  
; Friction of collision with a dynamic body is a little more tricky. You need to calculate relative velocity of colliding points. 
; Let's assume your mass point I collided with and edge (defined by mass points J and K) of the other object. 
;  1 - The relative velocity is relVel = I.pos - I.last_pos - (J.pos + K.pos - J.last_pos - K.last_pos)/2. 
;  2 - you compute tangent from the collision normal and project the relative velocity onto it: relTanVel = tangent*dot(relVel, tangent)
;  3 - you add/subtract (depending on how you computed tangent) some fraction of relTanVel to/from last_pos of I,J,K. 
;      What fraction it will be is determined by I,J,K masses and "hit coefficient" (because I is somewhere between J and K...you get the hit coefficient by projecting I onto the edge JK).

  ; From here, we need the absolute value of the collision normal
  If VERLET_collisionInfo\normal\x < 0
    VERLET_collisionInfo\normal\x = -VERLET_collisionInfo\normal\x
  EndIf
  If VERLET_collisionInfo\normal\y < 0
    VERLET_collisionInfo\normal\y = -VERLET_collisionInfo\normal\y
  EndIf  
  
  ; What's configuration are we in (is there some staic points involved?)
  If VERLET_collisionInfo\ptrPoint\invMass = 0
    VERLET_collisionInfo\collisionCase = %001
  Else
    VERLET_collisionInfo\collisionCase = %000
  EndIf
  If VERLET_collisionInfo\ptrEdge\p1\invmass = 0
    VERLET_collisionInfo\collisionCase + %010
  EndIf
  If VERLET_collisionInfo\ptrEdge\p2\invmass = 0
    VERLET_collisionInfo\collisionCase + %100
  EndIf
  
  ; CASE 1 : no static point
  Select VERLET_collisionInfo\collisionCase
    Case %000 ; no Static point
      ; Mass of the constraint at the intersection point
      VERLET_edgeMass = VERLET_T * VERLET_collisionInfo\ptrEdge\p2\Mass + ( 1 - VERLET_T ) * VERLET_collisionInfo\ptrEdge\p1\Mass
      VERLET_invCollisionMass = 1/( VERLET_edgeMass + VERLET_collisionInfo\ptrPoint\Mass )
      
      ; Response repartition, according to each point's mass
      VERLET_ratio1 = VERLET_collisionInfo\ptrPoint\Mass * VERLET_invCollisionMass
      VERLET_ratio3 = VERLET_edgeMass * VERLET_invCollisionMass;
      
      ; Constraint collision response
      VERLET_collisionInfo\ptrEdge\p1\x + VERLET_collisionInfo\collisionVector\x * ( 1 - VERLET_T ) * VERLET_ratio1 * VERLET_lambda
      VERLET_collisionInfo\ptrEdge\p1\y + VERLET_collisionInfo\collisionVector\y * ( 1 - VERLET_T ) * VERLET_ratio1 * VERLET_lambda
      
      VERLET_collisionInfo\ptrEdge\p2\x + VERLET_collisionInfo\collisionVector\x * VERLET_T  * VERLET_ratio1 * VERLET_lambda
      VERLET_collisionInfo\ptrEdge\p2\y + VERLET_collisionInfo\collisionVector\y * VERLET_T  * VERLET_ratio1 * VERLET_lambda
      
      ; Constraint friction
      VERLET_collisionInfo\ptrEdge\p1\oldX + (VERLET_collisionInfo\ptrEdge\p1\x - VERLET_collisionInfo\ptrEdge\p1\oldX) * VERLET_collisionInfo\normal\y * (ptrBodyPoint\frictionEdgeOnPoint * ptrBodyEdge\frictionEdgeOnPoint)
      VERLET_collisionInfo\ptrEdge\p1\oldY + (VERLET_collisionInfo\ptrEdge\p1\y - VERLET_collisionInfo\ptrEdge\p1\oldY) * VERLET_collisionInfo\normal\x * (ptrBodyPoint\frictionEdgeOnPoint * ptrBodyEdge\frictionEdgeOnPoint)
      VERLET_collisionInfo\ptrEdge\p2\oldX + (VERLET_collisionInfo\ptrEdge\p2\x - VERLET_collisionInfo\ptrEdge\p2\oldX) * VERLET_collisionInfo\normal\y * (ptrBodyPoint\frictionEdgeOnPoint * ptrBodyEdge\frictionEdgeOnPoint)
      VERLET_collisionInfo\ptrEdge\p2\oldY + (VERLET_collisionInfo\ptrEdge\p2\y - VERLET_collisionInfo\ptrEdge\p2\oldY) * VERLET_collisionInfo\normal\x * (ptrBodyPoint\frictionEdgeOnPoint * ptrBodyEdge\frictionEdgeOnPoint)
      
      ; Vertex collision response
      VERLET_collisionInfo\ptrPoint\x - VERLET_collisionInfo\collisionVector\x * VERLET_ratio3
      VERLET_collisionInfo\ptrPoint\y - VERLET_collisionInfo\collisionVector\y * VERLET_ratio3
      
      ; Vertex friction
      VERLET_collisionInfo\ptrPoint\oldX + (VERLET_collisionInfo\ptrPoint\x - VERLET_collisionInfo\ptrPoint\oldX) * VERLET_collisionInfo\normal\y * (ptrBodyPoint\friction * ptrBodyEdge\friction)
      
    Case %110 ; point vs. static constraint
      ; Vertex collision response
      VERLET_collisionInfo\ptrPoint\x - VERLET_collisionInfo\collisionVector\x
      VERLET_collisionInfo\ptrPoint\y - VERLET_collisionInfo\collisionVector\y
      
      ; Vertex friction
      VERLET_collisionInfo\ptrPoint\oldX + (VERLET_collisionInfo\ptrPoint\x - VERLET_collisionInfo\ptrPoint\oldX) * VERLET_collisionInfo\normal\y * (ptrBodyPoint\friction * ptrBodyEdge\friction)
      VERLET_collisionInfo\ptrPoint\oldY + (VERLET_collisionInfo\ptrPoint\y - VERLET_collisionInfo\ptrPoint\oldY) * VERLET_collisionInfo\normal\x * (ptrBodyPoint\friction * ptrBodyEdge\friction)
      
    Case %001 ; constraint vs. static point
      VERLET_ratio1 = 1 / (VERLET_T * VERLET_collisionInfo\ptrEdge\p2\Mass + ( 1 - VERLET_T ) * VERLET_collisionInfo\ptrEdge\p1\Mass)
      
      ; Constraint collision response
      VERLET_collisionInfo\ptrEdge\p1\x + VERLET_collisionInfo\collisionVector\x * ( 1 - VERLET_T ) * VERLET_lambda * VERLET_ratio1
      VERLET_collisionInfo\ptrEdge\p1\y + VERLET_collisionInfo\collisionVector\y * ( 1 - VERLET_T ) * VERLET_lambda * VERLET_ratio1
      
      VERLET_collisionInfo\ptrEdge\p2\x + VERLET_collisionInfo\collisionVector\x * VERLET_T * VERLET_lambda * VERLET_ratio1
      VERLET_collisionInfo\ptrEdge\p2\y + VERLET_collisionInfo\collisionVector\y * VERLET_T * VERLET_lambda * VERLET_ratio1
      
      ; Constraint friction
      VERLET_collisionInfo\ptrEdge\p1\oldX + (VERLET_collisionInfo\ptrEdge\p1\x - VERLET_collisionInfo\ptrEdge\p1\oldX) * VERLET_collisionInfo\normal\y * (ptrBodyPoint\frictionEdgeOnPoint * ptrBodyEdge\frictionEdgeOnPoint)
      VERLET_collisionInfo\ptrEdge\p1\oldY + (VERLET_collisionInfo\ptrEdge\p1\y - VERLET_collisionInfo\ptrEdge\p1\oldY) * VERLET_collisionInfo\normal\x * (ptrBodyPoint\frictionEdgeOnPoint * ptrBodyEdge\frictionEdgeOnPoint)
      VERLET_collisionInfo\ptrEdge\p2\oldX + (VERLET_collisionInfo\ptrEdge\p2\x - VERLET_collisionInfo\ptrEdge\p2\oldX) * VERLET_collisionInfo\normal\y * (ptrBodyPoint\frictionEdgeOnPoint * ptrBodyEdge\frictionEdgeOnPoint)
      VERLET_collisionInfo\ptrEdge\p2\oldY + (VERLET_collisionInfo\ptrEdge\p2\y - VERLET_collisionInfo\ptrEdge\p2\oldY) * VERLET_collisionInfo\normal\x * (ptrBodyPoint\frictionEdgeOnPoint * ptrBodyEdge\frictionEdgeOnPoint)
      
    Case %010 ; point vs. semi-static constraint (first point is static)
      VERLET_edgeMass = ( 1 - VERLET_T ) * VERLET_collisionInfo\ptrEdge\p2\Mass 
      VERLET_invCollisionMass = 1/( VERLET_edgeMass + VERLET_collisionInfo\ptrPoint\Mass )
      
      VERLET_ratio1 = VERLET_collisionInfo\ptrPoint\Mass * VERLET_invCollisionMass
      VERLET_ratio3 = VERLET_edgeMass * VERLET_invCollisionMass
      
      ; Constraint collision response
      VERLET_collisionInfo\ptrEdge\p2\x + VERLET_collisionInfo\collisionVector\x * ( 1 - VERLET_T ) * VERLET_lambda * VERLET_ratio1
      VERLET_collisionInfo\ptrEdge\p2\y + VERLET_collisionInfo\collisionVector\y * ( 1 - VERLET_T ) * VERLET_lambda * VERLET_ratio1
      
      ; Constraint friction
      VERLET_collisionInfo\ptrEdge\p2\oldX + (VERLET_collisionInfo\ptrEdge\p2\x - VERLET_collisionInfo\ptrEdge\p2\oldX) * VERLET_collisionInfo\normal\y * (ptrBodyPoint\frictionEdgeOnPoint * ptrBodyEdge\frictionEdgeOnPoint)
      VERLET_collisionInfo\ptrEdge\p2\oldY + (VERLET_collisionInfo\ptrEdge\p2\y - VERLET_collisionInfo\ptrEdge\p2\oldY) * VERLET_collisionInfo\normal\x * (ptrBodyPoint\frictionEdgeOnPoint * ptrBodyEdge\frictionEdgeOnPoint)
    
      ; Vertex collision response
      VERLET_collisionInfo\ptrPoint\x - VERLET_collisionInfo\collisionVector\x * VERLET_ratio3
      VERLET_collisionInfo\ptrPoint\y - VERLET_collisionInfo\collisionVector\y * VERLET_ratio3
      
      ; Vertex friction
      VERLET_collisionInfo\ptrPoint\oldX + (VERLET_collisionInfo\ptrPoint\x - VERLET_collisionInfo\ptrPoint\oldX) * VERLET_collisionInfo\normal\y * (ptrBodyPoint\friction * ptrBodyEdge\friction)
      VERLET_collisionInfo\ptrPoint\oldY + (VERLET_collisionInfo\ptrPoint\y - VERLET_collisionInfo\ptrPoint\oldY) * VERLET_collisionInfo\normal\x * (ptrBodyPoint\friction * ptrBodyEdge\friction)
      
    Case %100 ; point vs. semi-static constraint (second point is static)
      VERLET_edgeMass = VERLET_T * VERLET_collisionInfo\ptrEdge\p1\Mass
      VERLET_invCollisionMass = 1/( VERLET_edgeMass + VERLET_collisionInfo\ptrPoint\Mass )
      
      VERLET_ratio1 = VERLET_collisionInfo\ptrPoint\Mass * VERLET_invCollisionMass
      VERLET_ratio3 = VERLET_edgeMass * VERLET_invCollisionMass
      
      ; Constraint collision response
      VERLET_collisionInfo\ptrEdge\p1\x + VERLET_collisionInfo\collisionVector\x * VERLET_T * VERLET_lambda * VERLET_ratio1
      VERLET_collisionInfo\ptrEdge\p1\y + VERLET_collisionInfo\collisionVector\y * VERLET_T * VERLET_lambda * VERLET_ratio1
      
      ; Constraint friction
      VERLET_collisionInfo\ptrEdge\p1\oldX + (VERLET_collisionInfo\ptrEdge\p1\x - VERLET_collisionInfo\ptrEdge\p1\oldX) * VERLET_collisionInfo\normal\y * (ptrBodyPoint\frictionEdgeOnPoint * ptrBodyEdge\frictionEdgeOnPoint)
      VERLET_collisionInfo\ptrEdge\p1\oldY + (VERLET_collisionInfo\ptrEdge\p1\y - VERLET_collisionInfo\ptrEdge\p1\oldY) * VERLET_collisionInfo\normal\x * (ptrBodyPoint\frictionEdgeOnPoint * ptrBodyEdge\frictionEdgeOnPoint)
    
      ; Vertex collision response
      VERLET_collisionInfo\ptrPoint\x - VERLET_collisionInfo\collisionVector\x * VERLET_ratio3
      VERLET_collisionInfo\ptrPoint\y - VERLET_collisionInfo\collisionVector\y * VERLET_ratio3
      
      ; Vertex friction
      VERLET_collisionInfo\ptrPoint\oldX + (VERLET_collisionInfo\ptrPoint\x - VERLET_collisionInfo\ptrPoint\oldX) * VERLET_collisionInfo\normal\y * (ptrBodyPoint\friction * ptrBodyEdge\friction)
      VERLET_collisionInfo\ptrPoint\oldY + (VERLET_collisionInfo\ptrPoint\y - VERLET_collisionInfo\ptrPoint\oldY) * VERLET_collisionInfo\normal\x * (ptrBodyPoint\friction * ptrBodyEdge\friction)
      
    Case %011 ; static point vs. semi-static constraint (first point is Static)
      ; Constraint collision response
      VERLET_ratio1 = 1 / (VERLET_T * VERLET_collisionInfo\ptrEdge\p2\Mass )
      VERLET_collisionInfo\ptrEdge\p2\x + VERLET_collisionInfo\collisionVector\x * VERLET_T * VERLET_lambda * VERLET_ratio1
      VERLET_collisionInfo\ptrEdge\p2\y + VERLET_collisionInfo\collisionVector\y * VERLET_T * VERLET_lambda * VERLET_ratio1
      
      ; Constraint friction
      VERLET_collisionInfo\ptrEdge\p2\oldX + (VERLET_collisionInfo\ptrEdge\p2\x - VERLET_collisionInfo\ptrEdge\p2\oldX) * VERLET_collisionInfo\normal\y * (ptrBodyPoint\frictionEdgeOnPoint * ptrBodyEdge\frictionEdgeOnPoint)
      VERLET_collisionInfo\ptrEdge\p2\oldY + (VERLET_collisionInfo\ptrEdge\p2\y - VERLET_collisionInfo\ptrEdge\p2\oldY) * VERLET_collisionInfo\normal\x * (ptrBodyPoint\frictionEdgeOnPoint * ptrBodyEdge\frictionEdgeOnPoint)
      
    Case %101 ; static point vs. semi-static constraint (second point is Static)
      ; Constraint collision response
      VERLET_ratio1 = 1 / (( 1 - VERLET_T ) * VERLET_collisionInfo\ptrEdge\p1\Mass)
      VERLET_collisionInfo\ptrEdge\p1\x + VERLET_collisionInfo\collisionVector\x * ( 1 - VERLET_T ) * VERLET_lambda * VERLET_ratio1
      VERLET_collisionInfo\ptrEdge\p1\y + VERLET_collisionInfo\collisionVector\y * ( 1 - VERLET_T ) * VERLET_lambda * VERLET_ratio1
      
      ; Constraint friction
      VERLET_collisionInfo\ptrEdge\p1\oldX + (VERLET_collisionInfo\ptrEdge\p1\x - VERLET_collisionInfo\ptrEdge\p1\oldX) * VERLET_collisionInfo\normal\y * (ptrBodyPoint\frictionEdgeOnPoint * ptrBodyEdge\frictionEdgeOnPoint)
      VERLET_collisionInfo\ptrEdge\p1\oldY + (VERLET_collisionInfo\ptrEdge\p1\y - VERLET_collisionInfo\ptrEdge\p1\oldY) * VERLET_collisionInfo\normal\x * (ptrBodyPoint\frictionEdgeOnPoint * ptrBodyEdge\frictionEdgeOnPoint)
      
  EndSelect
  
EndMacro

;************************************************************************************
; Name: DETECT_COLLISION
; Purpose: Determines if two rigid bodies are colliding, using SAT algorithm
;           http://www.gamedev.net/reference/articles/article2714.asp
; Parameters:
;   - pointer to first body
;   - pointer to second body
;   - collision or not (output boolean)
; Note: This macro looks for a collision between a point and an edge (= a constraint).
;       The structure "collisionInfo" stores the pointer to these, as well as the
;       collsion depth, the collision normal, etc..
;************************************************************************************
Macro DETECT_COLLISION(ptrBody1,ptrBody2,collisionResult)
  
  ; We check all edges of the two bodies
  VERLET_nbEdges = ptrBody1\nbEdge + ptrBody2\nbEdge + 1
  *VERLET_ptrGen2 = ptrBody1\ptrConstraintList
  VERLET_minDistance = 10000000
  collisionResult = #True
  For VERLET_i = 0 To VERLET_nbEdges
    *VERLET_ptrEdge = PeekI(*VERLET_ptrGen2)
    ; When all of body1's edges have been checked, switch to body2
    If VERLET_i = ptrBody1\nbEdge
      *VERLET_ptrGen2 = ptrBody2\ptrConstraintList
    Else
      *VERLET_ptrGen2 + #SIZEOFPTR
    EndIf
    
    If *VERLET_ptrEdge\enable = #False
      Continue
    EndIf
    
    ; Calculate the axis perpendicular (cross vector) to this edge and normalize it
    VERLET_axis\x = *VERLET_ptrEdge\p1\y - *VERLET_ptrEdge\p2\y
    VERLET_axis\y = *VERLET_ptrEdge\p2\x - *VERLET_ptrEdge\p1\x
    VEC2_Normalize(VERLET_axis,VERLET_axis)
    
    ; Project each point of the 2 bodies on this vector; Get min/max for each body
    PROJECT_TO_AXIS(ptrBody1,VERLET_axis,VERLET_minA,VERLET_maxA)
    PROJECT_TO_AXIS(ptrBody2,VERLET_axis,VERLET_minB,VERLET_maxB)
    
    ; Calculate the distance between the two intervals
    INTERVAL_DISTANCE( VERLET_minA, VERLET_maxA, VERLET_minB, VERLET_maxB, VERLET_distance)
       
    ; If the intervals don't overlap, there is no collision
    If VERLET_distance < 0.0       
      collisionResult = #False
      Break
    EndIf     
    
    ; Note: if body contains parallel lines, the "minDistance" edge might not be the good one...
    ; => if distance = minDistance, we should check if the edge is closer to the second body
    If VERLET_distance < VERLET_minDistance
      VERLET_minDistance = VERLET_distance
      VERLET_collisionInfo\normal\x = VERLET_axis\x
      VERLET_collisionInfo\normal\y = VERLET_axis\y
      VERLET_collisionInfo\ptrEdge = *VERLET_ptrEdge
    EndIf
  
  Next VERLET_i
  
  ; If the two bodies collide, what do we do ?
  If collisionResult = #True
    VERLET_collisionInfo\depth = VERLET_minDistance
    
    ; To make the folowing code simplier, we make sure that we have:
    ;   - a body #1 containing the colliding point
    ;   - a body #2 containing the colliding edge
    If VERLET_collisionInfo\ptrEdge\ptrParent = ptrBody2
      *VERLET_ptrCollPointBody = ptrBody1
      *VERLET_ptrCollEdgeBody = ptrBody2
    Else
      *VERLET_ptrCollPointBody = ptrBody2
      *VERLET_ptrCollEdgeBody = ptrBody1
    EndIf
    
    ; The collision normal must point at body #1
    VEC2_substract(*VERLET_ptrCollEdgeBody\center,*VERLET_ptrCollPointBody\center,VERLET_tempVector)
    ; if not, revert the collision normal
    If VEC2_dotproduct(VERLET_collisionInfo\normal,VERLET_tempVector) < 0
      VERLET_collisionInfo\normal\x = -VERLET_collisionInfo\normal\x
      VERLET_collisionInfo\normal\y = -VERLET_collisionInfo\normal\y
    EndIf
  
    ; The colliding vertex is the one closer to the center of body #2
    ; So we measure the distance of the vertex from the line using the line equation
    *VERLET_ptrGen = *VERLET_ptrCollPointBody\ptrPointList
    *VERLET_ptrPoint = PeekI(*VERLET_ptrGen)
    VEC2_substract(*VERLET_ptrCollEdgeBody\center,*VERLET_ptrPoint,VERLET_tempVector)
    VERLET_distance = VEC2_dotProduct(VERLET_collisionInfo\normal,VERLET_tempVector)
    VERLET_minDistance = VERLET_Distance
    VERLET_collisionInfo\ptrPoint = *VERLET_ptrPoint    
    For VERLET_i = 1 To *VERLET_ptrCollPointBody\nbPoint
      *VERLET_ptrGen + #SIZEOFPTR
      *VERLET_ptrPoint = PeekI(*VERLET_ptrGen)
      
      ; Measure the distance of the vertex from the line using the line equation
      VEC2_substract(*VERLET_ptrCollEdgeBody\center,*VERLET_ptrPoint,VERLET_tempVector)
      VERLET_distance = VEC2_dotProduct(VERLET_collisionInfo\normal,VERLET_tempVector)
      
      ; If the measured distance is smaller than the min distance, we've got our collision vertex
      If VERLET_distance < VERLET_minDistance
        VERLET_minDistance = VERLET_Distance
        VERLET_collisionInfo\ptrPoint = *VERLET_ptrPoint
      EndIf
    Next VERLET_i
    
    VERLET_collisionInfo\ptrPoint\collision = #True
    VERLET_collisionInfo\ptrEdge\p1\collision = #True
    VERLET_collisionInfo\ptrEdge\p2\collision = #True
  
    ; Compute the collision response
    COLLISION_RESPONSE(*VERLET_ptrCollPointBody,*VERLET_ptrCollEdgeBody)

  EndIf ; if collisionResult = #true...
  
  
EndMacro

;************************************************************************************
; Name: UPDATE_POINTMASSES
; Purpose: Update position of each pointmass and add gravity (except for the static points)
; Parameters:
;   None
;************************************************************************************
Macro UPDATE_POINTMASSES()
  
  ForEach pointmass()
    If pointmass()\invmass <> 0
      
      ;gets the velocity  
		  VERLET_dx = pointmass()\x - pointmass()\oldX	
		  VERLET_dy = pointmass()\y - pointmass()\oldY + #GRAVITY 
		  
	    ;updates oldX and oldY
  		pointmass()\oldX = pointmass()\x
  		pointmass()\oldY = pointmass()\y
  		
  		;adds velocity to get new x and new y
  		pointmass()\x + VERLET_dx 	
  		pointmass()\y + VERLET_dy 
  		  		
  	EndIf
  Next pointmass()
  
EndMacro

;************************************************************************************
; Name: UPDATE_CONSTRAINTS
; Purpose: Once the points moved, the constraints do their best to retrieve their
;          initial length
; Parameters:
;   None
;************************************************************************************
Macro UPDATE_CONSTRAINTS()

  For VERLET_i = 1 To #CONSTRAINT_ITERATIONS	 ;this is necessary with many constraints to solve them correctly
    ForEach constraint()
	    
	    If constraint()\p1\invmass > 0 Or constraint()\p2\invmass > 0
  	    VERLET_dx = constraint()\p2\x - constraint()\p1\x
  	    VERLET_dy = constraint()\p2\y - constraint()\p1\y
  	    
  	    ; Correction to apply
   			;VERLET_deltalength = Sqr(VERLET_dx*VERLET_dx + VERLET_dy*VERLET_dy)	;distance formula
   			;VERLET_diff = (VERLET_deltalength - constraint()\length) / (VERLET_deltalength*(constraint()\p1\invmass+constraint()\p2\invmass)) 			
  
        ; Same thing, but without SQR (faster?)
        ;length2 = constraint()\length*constraint()\length ; optimisation: length2 is stored
        VERLET_diff = ( (constraint()\length2 / ((VERLET_dx*VERLET_dx + VERLET_dy*VERLET_dy) + constraint()\length2)) - 0.5) / ((constraint()\p1\invmass + constraint()\p2\invmass) * -0.5)
        
    	  constraint()\p1\x + (VERLET_diff * VERLET_dx * constraint()\p1\invmass)
    	  constraint()\p1\y + (VERLET_diff * VERLET_dy * constraint()\p1\invmass)
    	  constraint()\p2\x - (VERLET_diff * VERLET_dx * constraint()\p2\invmass)
    	  constraint()\p2\y - (VERLET_diff * VERLET_dy * constraint()\p2\invmass)
    	EndIf
    	
    Next constraint()
	Next VERLET_i	
	
EndMacro

;************************************************************************************
; Name: MANAGE_COLLISIONS
; Purpose: Check collision for each body against all the others (include collision
;          response => bounce)
; Parameters:
;   None
;************************************************************************************
Macro MANAGE_COLLISIONS()
  
    ; Compute center and "collision box" for each rigid body
    ForEach body()     
      *VERLET_pointList = body()\ptrPointList
      *VERLET_ptrPoint = PeekI(*VERLET_pointList)
      *VERLET_ptrPoint\collision = #False
      body()\center\x = *VERLET_ptrPoint\x
      body()\center\y = *VERLET_ptrPoint\y
      body()\minX = *VERLET_ptrPoint\x
      body()\maxX = *VERLET_ptrPoint\x
      body()\minY = *VERLET_ptrPoint\y
      body()\maxY = *VERLET_ptrPoint\y
      
      mvt = #False
      sumMvt = (*VERLET_ptrPoint\x - *VERLET_ptrPoint\oldX) * (*VERLET_ptrPoint\x - *VERLET_ptrPoint\oldX) + (*VERLET_ptrPoint\y - *VERLET_ptrPoint\oldY) * (*VERLET_ptrPoint\y - *VERLET_ptrPoint\oldY)
      If sumMvt > 0.001
        mvt=#True
      EndIf
      
      For VERLET_i = 1 To body()\nbPoint
        *VERLET_pointList + #SIZEOFPTR
        *VERLET_ptrPoint = PeekI(*VERLET_pointList)
        *VERLET_ptrPoint\collision = #False
        body()\center\x + *VERLET_ptrPoint\x
        body()\center\y + *VERLET_ptrPoint\y
        If *VERLET_ptrPoint\x < body()\minX
          body()\minX = *VERLET_ptrPoint\x
        EndIf
        If *VERLET_ptrPoint\x > body()\maxX
          body()\maxX = *VERLET_ptrPoint\x
        EndIf
        If *VERLET_ptrPoint\y < body()\minY
          body()\minY = *VERLET_ptrPoint\y
        EndIf
        If *VERLET_ptrPoint\y > body()\maxY
          body()\maxY = *VERLET_ptrPoint\y
        EndIf
        
        sumMvt = (*VERLET_ptrPoint\x - *VERLET_ptrPoint\oldX) * (*VERLET_ptrPoint\x - *VERLET_ptrPoint\oldX) + (*VERLET_ptrPoint\y - *VERLET_ptrPoint\oldY) * (*VERLET_ptrPoint\y - *VERLET_ptrPoint\oldY)      
        If sumMvt > 0.001
          mvt=#True
        EndIf

      Next VERLET_i

      ; At this point, VERLET_i = body()\nbPoint + 1
      body()\center\x / VERLET_i
      body()\center\y / VERLET_i
      
      ;sumMvt / VERLET_i
      If mvt = #False ;sumMvt < 0.0005
        body()\timerRest + 1
        If body()\timerRest > 200
          body()\atRest = #True
          
          *VERLET_pointList = body()\ptrPointList
          *VERLET_ptrPoint = PeekI(*VERLET_pointList)
          *VERLET_ptrPoint\oldX = *VERLET_ptrPoint\x
          *VERLET_ptrPoint\oldY = *VERLET_ptrPoint\y + #GRAVITY
          For VERLET_i = 1 To body()\nbPoint
              *VERLET_pointList + #SIZEOFPTR
              *VERLET_ptrPoint = PeekI(*VERLET_pointList)
              *VERLET_ptrPoint\oldX = *VERLET_ptrPoint\x
              *VERLET_ptrPoint\oldY = *VERLET_ptrPoint\y + #GRAVITY
          Next VERLET_i
              
          
        EndIf
      Else
        body()\timerRest = 0
        body()\atRest = #False
      EndIf
      
    Next body()
    
    ; Collisions: Check each body against all the others
    ForEach body()
      *VERLET_prevBodyPtr = @body()
      VERLET_startIndex = ListIndex(body())
      VERLET_isCollisionWhole = #False
      ForEach body()
        If ListIndex(body()) > VERLET_startIndex And (*VERLET_prevBodyPtr\ptrParent = 0 Or body()\ptrParent = 0 Or *VERLET_prevBodyPtr\ptrParent <> body()\ptrParent)
          If ( *VERLET_prevBodyPtr\MinX <= body()\MaxX ) And ( *VERLET_prevBodyPtr\MinY <= body()\MaxY ) And ( *VERLET_prevBodyPtr\MaxX >= body()\MinX ) And ( *VERLET_prevBodyPtr\MaxY >= body()\MinY )
            DETECT_COLLISION(*VERLET_prevBodyPtr,body(),VERLET_isCollision)
          EndIf
        EndIf
      Next body()
      ChangeCurrentElement(body(),*VERLET_prevBodyPtr)

    Next body()
    
EndMacro    


;- Points / constraints creation

;************************************************************************************
; Name: createPointmass
; Purpose: Creates a pointmass
; Parameters:
;   - coords x,y (float)
;   - mass (optional float)
;   - initial velocities x,y (optional float)
; Returns : pointer to newly created pointmass
;************************************************************************************
Procedure createPointmass(x.f,y.f,mass.f = 1,vx.f = 0 ,vy.f = 0)	; x and y are coords for the verlet. vx and vy are velocity values for the verlet
  
  AddElement(pointmass())
  pointmass()\x = x
  pointmass()\y = y
  pointmass()\oldX = x - vx	;gives the particle a starting velocity
  pointmass()\oldY = y - vy
  If mass > 0
    pointmass()\mass = mass
    pointmass()\invmass = 1 / mass
  Else
    pointmass()\mass = 999999
    pointmass()\invmass = 0
  EndIf
  
  ProcedureReturn @pointmass()
EndProcedure


;************************************************************************************
; Name: createConstraint
; Purpose: Creates a constraint
; Parameters:
;   - pointer to first point (ptr)
;   - pointer to second point (ptr)
;   - is this constraint can be collided (optional boolean)
; Returns : pointer to newly created constraint
;************************************************************************************
Procedure createConstraint(*p1.pointmass_struct,*p2.pointmass_struct,enable.b = #True)
  
  AddElement(constraint())
	constraint()\p1 = *p1
	constraint()\p2 = *p2
	constraint()\enable = enable
	constraint()\length = Sqr( Pow((constraint()\p1\x - constraint()\p2\x),2) + Pow((constraint()\p1\y - constraint()\p2\y),2) )
	constraint()\length2 = constraint()\length * constraint()\length ; square length. Stored for optimization
	
	ProcedureReturn @constraint()

EndProcedure


;************************************************************************************
; Name: createBody
; Purpose: Creates a rigid body
; Parameters:
;   None (points and constraints will be added later)
; Returns : pointer to newly created body
;************************************************************************************
Procedure createBody(friction.f = #FRICTION)
  VERLET_numBody + 1
  
  AddElement(body())
  body()\numint = VERLET_numBody
  body()\nbPoint = -1
  body()\nbEdge = -1
  
  body()\friction = friction
  body()\frictionEdgeOnPoint = body()\friction / #FRICTION_EDGEONPOINT_FACTOR
  
  ProcedureReturn @body()  
EndProcedure

;************************************************************************************
; Name: addBodyPointmass
; Purpose: Creates a pointmass and adds it to an already created body
; Parameters:
;   - pointer to the body (ptr)
;   - coords x,y (float)
;   - mass (optional float)
;   - initial velocities x,y (optional float)
; Returns : pointer to newly created pointmass
;************************************************************************************
Procedure addBodyPointmass(*ptrBody.rigidBody_struct ,x.f,y.f,mass.f = 1,vx.f = 0 ,vy.f = 0)
  Protected *ptr.pointmass_struct
  
  ; Make room for the new point in the body's list
  *ptrBody\nbPoint + 1
  If *ptrBody\nbPoint = 0
    *ptrBody\ptrPointList = AllocateMemory((*ptrBody\nbPoint + 1) * #SIZEOFPTR )
  Else
    *ptrBody\ptrPointList = ReAllocateMemory(*ptrBody\ptrPointList,(*ptrBody\nbPoint + 1) * #SIZEOFPTR )
  EndIf
  
  ; Create the point and store its pointer in the list
  *ptr = createPointmass(x,y,mass,vx,vy)
  PokeI(*ptrBody\ptrPointList + *ptrBody\nbPoint * #SIZEOFPTR , *ptr )
  
  ProcedureReturn *ptr
EndProcedure


;************************************************************************************
; Name: addBodyConstraint
; Purpose: Creates a constraint and adds it to an already created body
; Parameters:
;   - pointer to the body (ptr)
;   - pointer to first point (ptr)
;   - pointer to second point (ptr)
;   - is this constraint can be collided (optional boolean)
; Returns : pointer to newly created constraint
;************************************************************************************
Procedure addBodyConstraint(*ptrBody.rigidBody_struct,*p1.pointmass_struct,*p2.pointmass_struct,enable.b = #True)
  Protected *ptr.constraint_struct
  
  ; Make room for the new constraint in the body's list
  *ptrBody\nbEdge + 1
  If *ptrBody\nbEdge = 0
    *ptrBody\ptrConstraintList = AllocateMemory((*ptrBody\nbEdge + 1) * #SIZEOFPTR )
  Else
    *ptrBody\ptrConstraintList = ReAllocateMemory(*ptrBody\ptrConstraintList,(*ptrBody\nbEdge + 1) * #SIZEOFPTR )
  EndIf
      
  ; Create the constraint and store its pointer in the list
  *ptr = createConstraint(*p1,*p2,enable)
  PokeI(*ptrBody\ptrConstraintList + *ptrBody\nbEdge * #SIZEOFPTR, *ptr)
  *ptr\ptrParent = *ptrBody
   
  ProcedureReturn *ptr
EndProcedure

Last edited by Kelebrindae on Mon Dec 22, 2014 9:40 am, edited 1 time in total.
Kelebrindae
Enthusiast
Enthusiast
Posts: 151
Joined: Tue Apr 01, 2008 3:23 pm

Re: 2D physics engine (Verlet)

Post by Kelebrindae »

Last part of the code:

(Here's a zip of the three parts of the source: http://keleb.free.fr/codecorner/downloa ... verlet.zip )

Code: Select all

; Author: Kelebrindae
; Date: December, 15, 2010
; PB version: v4.51
; ---------------------------------------------------------------------------------------------------------------
; Description:
; ---------------------------------------------------------------------------------------------------------------
; Demo for my Verlet 2D physics engine.
;
; F1 to F9 : Create pre-defined sets of objects
; Left mouse button: Pick an object
; Del: Delete the picked object (or all objects if none is picked)
; Return: Enable/disable drawing (for benchmarking)
; Right-Ctrl : Slow motion
; ---------------------------------------------------------------------------------------------------------------
; Known bugs and limitations:
; ---------------------------------------------------------------------------------------------------------------
; - The drawing  method used in this demo is quite slow. To test the real speed of the engine, use the 
;   "#PB_Screen_NoSynchronization" flag in OpenWindowedScreen and press "Return" (disables drawing)
; ---------------------------------------------------------------------------------------------------------------

; Window size
#SCREENWIDTH = 800
#SCREENHEIGHT = 500

; Verlet integration library
IncludeFile "verlet2D.pbi"

; General purpose variables
Global Dim *p.pointmass_struct(20)
Global *body.rigidBody_struct
Global drawmode.b = #True, mousemode.b = #False
Global *mousePoint.pointmass_struct, *ptrPoint.pointmass_struct
Global timer.i,j.i,minDistance.f,distance.f,numObj.i,numFps.i,numFpsShown.i

;- --- Procedures ---
EnableExplicit

;********************************************************
;- Drawing procedures
;********************************************************

; Draw all the points
Procedure drawpointmasses()
  Protected w.i,h.i
  
  ForEach pointmass()
    ; not static => white circle
    If pointmass()\invmass > 0
      Circle(pointmass()\x,pointmass()\y,2,$FFFFFF)
      
      ; also, we draw the speed as yellow lines (but most of the time, it's invisible)
      w = pointmass()\oldX - pointmass()\x
      h = pointmass()\oldY - pointmass()\y
      If w = 0
        w = 1
      EndIf
      If h = 0
        h = 1
      EndIf
      Line(pointmass()\x,pointmass()\y,w,h,$00FFFF)
    Else ; static => blue cross
      Line(pointmass()\x - 3,pointmass()\y - 3,7,7,$FF0000)
      Line(pointmass()\x - 3,pointmass()\y + 3,7,-7,$FF0000)
    EndIf
  Next pointmass()

EndProcedure

; Draw all the constraints
Procedure drawconstraints()
  Protected w.i,h.i,color.i
  
  ForEach constraint()
    w = constraint()\p2\x - constraint()\p1\x
    h = constraint()\p2\y - constraint()\p1\y
    
    If constraint()\ptrParent\atRest = #True
      color = $0077FF
    Else
      color = $00FF00
    EndIf
    
    If w = 0
      w = 1
    EndIf
    If h = 0
      h = 1
    EndIf
    
    ; Draw constraints in green, or grey is they are disabled
    If constraint()\enable = #True
      Line(constraint()\p1\x,constraint()\p1\y,w,h,color)
    Else
      Line(constraint()\p1\x,constraint()\p1\y,w,h,$777777)
    EndIf
  Next constraint()

EndProcedure



;********************************************************
;- Pre-defined objects
;********************************************************

; Create a single line. Useful to figure the ground, the walls, etc..
; (It's made of two points and TWO constraints, because my collision algo doesn't seem to like single constraint bodies...)
Procedure createLine(x1.i,y1.i,x2.i,y2.i,mass.f = 1,friction.f = #FRICTION)
  Protected *body.rigidBody_struct
  *body = createBody(friction)
  
  *p(1) = addBodyPointmass(*body,x1,y1,mass)
  *p(2) = addBodyPointmass(*body,x2,y2,mass)
  
  addBodyConstraint(*body,*p(1),*p(2))
  addBodyConstraint(*body,*p(2),*p(1))
  
  ProcedureReturn *body
EndProcedure

; Create a triangle
Procedure createTriangle(x.i,y.i,x2.i,y2.i,x3.i,y3.i,mass.f = 1,friction.f = #FRICTION,hspeed.f = 0,vspeed.f = 0,rotation.f = 0)
  Protected *body.rigidBody_struct
  *body = createBody(friction)
  
  *p(1) = addBodyPointmass(*body,x,y,mass,hspeed+rotation,vspeed)
  *p(2) = addBodyPointmass(*body,x2,y2,mass,hspeed,vspeed)
  *p(3) = addBodyPointmass(*body,x3,y3,mass,hspeed,vspeed+rotation)

  addBodyConstraint(*body,*p(1),*p(2))
  addBodyConstraint(*body,*p(1),*p(3))
  addBodyConstraint(*body,*p(2),*p(3))
    
  ProcedureReturn *body  
EndProcedure


; Create a box
Procedure createBox(x.i,y.i,width.i,height.i,mass.f = 1,friction.f = #FRICTION,hspeed.f = 0,vspeed.f = 0,rotation.f = 0)
  Protected *body.rigidBody_struct
  *body = createBody(friction)
  
  *p(1) = addBodyPointmass(*body,x,y,mass,hspeed+rotation,vspeed)
  *p(2) = addBodyPointmass(*body,x,y+height,mass,hspeed,vspeed)
  *p(3) = addBodyPointmass(*body,x+width,y+height,mass,hspeed-rotation,vspeed)
  *p(4) = addBodyPointmass(*body,x+width,y,mass,hspeed+rotation,vspeed)
  
  addBodyConstraint(*body,*p(1),*p(2))
  addBodyConstraint(*body,*p(2),*p(3))
  addBodyConstraint(*body,*p(3),*p(4))
  addBodyConstraint(*body,*p(1),*p(4))
  addBodyConstraint(*body,*p(1),*p(3),#False)
  addBodyConstraint(*body,*p(2),*p(4),#False)
  
  ProcedureReturn *body  
EndProcedure


; Create a chain of lines which can be used as a rope, an elastic, a bridge, etc..
Procedure createRope(x1.i,y1.i,x2.i,y2.i,nbSegments.i,friction.f = #FRICTION)
  Protected i.i
  Protected x.f = x1,y.f = y1
  Protected xd.f = (x2 - x1) / nbSegments,yd.f = (y2 - y1) / nbSegments
  Protected *body.rigidBody_struct
  
  AddElement(compound())
  
  For i=1 To nbSegments
    *body = createBody(friction)
    If i=1
      *p(1) = addBodyPointmass(*body,x,y,0) ; mass = 0 => static point
    Else
      *p(1) = *p(2)
      ; Re-use previous point
      *body\nbPoint + 1
      *body\ptrPointList = ReAllocateMemory(*body\ptrPointList,(*body\nbPoint + 1) * #SIZEOFPTR )
      PokeI(*body\ptrPointList + *body\nbPoint * #SIZEOFPTR , *p(1) )
    EndIf
        
    ; add next point
    x+xd
    y+yd
    *p(2) = addBodyPointmass(*body,x,y)
    
    ; create link between the two points
    addBodyConstraint(*body,*p(1),*p(2))
    addBodyConstraint(*body,*p(2),*p(1))

    *body\ptrParent = @compound()
  Next i
  
  ProcedureReturn *body
EndProcedure


; Create a swing (for all your circus needs ;) )
Procedure createSwing(x.i,y.i,width.i,height.i,center.i = 0,mass.f = 1,friction.f = #FRICTION)
  Protected *body.rigidBody_struct
  
  If center = 0
    center = x + width/2
  EndIf
  
  *body = createBody(friction)
  
  *p(1) = addBodyPointmass(*body,x,y,mass)
  *p(2) = addBodyPointmass(*body,center,y,0)
  *p(3) = addBodyPointmass(*body,x + width,y,mass)
  *p(4) = addBodyPointmass(*body,center,y + height,mass)

  
  addBodyConstraint(*body,*p(1),*p(2))
  addBodyConstraint(*body,*p(2),*p(3))
  addBodyConstraint(*body,*p(2),*p(4),#False)
  addBodyConstraint(*body,*p(1),*p(4))
  addBodyConstraint(*body,*p(3),*p(4))
  
  ProcedureReturn *body  
EndProcedure


; Create a ball
Procedure createBall(x.i,y.i,radius.f,mass.f = 1,friction.f = #FRICTION)
  Protected i.i
  Protected *body.rigidBody_struct
  
  *body = createBody(friction)
  *p(1) = addBodyPointmass(*body,x,y,mass)
  For i = 0 To 330 Step 30
    If i = 0
      *p(2) = addBodyPointmass(*body,x+Cos(Radian(i))*radius,y+Sin(Radian(i))*radius,mass)
      *p(5) = *p(2)
    Else
      *p(3) = *p(2)
      *p(2) = addBodyPointmass(*body,x+Cos(Radian(i))*radius,y+Sin(Radian(i))*radius,mass)
      addBodyConstraint(*body,*p(2),*p(3))
      addBodyConstraint(*body,*p(1),*p(3),#False)
    EndIf
  Next i
  addBodyConstraint(*body,*p(2),*p(5))
  addBodyConstraint(*body,*p(1),*p(2),#False)
  
  ProcedureReturn *body  
EndProcedure


; Create a primitive ragdoll
Procedure createRagdoll(x.i,y.i,width.i = 50,height.i = 100,legSupport.b = #False)
  Protected *ptrCompound.compound_struct,*ptrTorso.rigidbody_struct
  Protected unitX.f = width / 5,unitY.i = height / 10
  
  ; Note to self: All this is a bit complicated; Need a way to attach a body to an other more easily...
  
  *ptrCompound = AddElement(compound())
  
  ; Torso
  *ptrTorso = createBody()
  *ptrTorso\ptrParent = *ptrCompound 
  *p(1) = addBodyPointmass(*ptrTorso,x + unitX,y)
  *p(2) = addBodyPointmass(*ptrTorso,x + 4*unitX,y)
  *p(3) = addBodyPointmass(*ptrTorso,x + 1.5*unitX,y + 5*unitY)
  *p(4) = addBodyPointmass(*ptrTorso,x + 3.5*unitX,y + 5*unitY)      

  addBodyConstraint(*ptrTorso,*p(1),*p(2))
  addBodyConstraint(*ptrTorso,*p(1),*p(3))
  addBodyConstraint(*ptrTorso,*p(2),*p(4))
  addBodyConstraint(*ptrTorso,*p(3),*p(4))
  addBodyConstraint(*ptrTorso,*p(1),*p(4),#False)
  addBodyConstraint(*ptrTorso,*p(2),*p(3),#False)
  
  ; Right forearm
  *body = createBody()
  *body\ptrParent = *ptrCompound 
  *p(5) = addBodyPointmass(*body,x,y + 3*unitY,0.5)
  *p(6) = addBodyPointmass(*body,x,y + 6*unitY,0.5)
  addBodyConstraint(*body,*p(5),*p(6))
  addBodyConstraint(*body,*p(6),*p(5))
  
  ; Right arm (we re-use already existing points from the foream and the torso)
  *body = createBody()
  *body\ptrParent = *ptrCompound
  
  *body\nbPoint + 1
  *body\ptrPointList = ReAllocateMemory(*body\ptrPointList,(*body\nbPoint + 1) * #SIZEOFPTR )
  PokeI(*body\ptrPointList + *body\nbPoint * #SIZEOFPTR , *p(1) )
  *body\nbPoint + 1
  *body\ptrPointList = ReAllocateMemory(*body\ptrPointList,(*body\nbPoint + 1) * #SIZEOFPTR )
  PokeI(*body\ptrPointList + *body\nbPoint * #SIZEOFPTR , *p(5) )
  
  addBodyConstraint(*body,*p(5),*p(1))
  addBodyConstraint(*body,*p(1),*p(5))
  
  ; Left forearm
  *body = createBody()
  *body\ptrParent = *ptrCompound 
  *p(7) = addBodyPointmass(*body,x + 5*unitX,y + 3*unitY,0.5)
  *p(8) = addBodyPointmass(*body,x + 5*unitX,y + 6*unitY,0.5)
  addBodyConstraint(*body,*p(7),*p(8))
  addBodyConstraint(*body,*p(8),*p(7))
  
  ; Left arm (we re-use already existing points from the foream and the torso)
  *body = createBody()
  *body\ptrParent = *ptrCompound 
  
  *body\nbPoint + 1
  *body\ptrPointList = ReAllocateMemory(*body\ptrPointList,(*body\nbPoint + 1) * #SIZEOFPTR )
  PokeI(*body\ptrPointList + *body\nbPoint * #SIZEOFPTR , *p(2) )
  *body\nbPoint + 1
  *body\ptrPointList = ReAllocateMemory(*body\ptrPointList,(*body\nbPoint + 1) * #SIZEOFPTR )
  PokeI(*body\ptrPointList + *body\nbPoint * #SIZEOFPTR , *p(7) )
  
  addBodyConstraint(*body,*p(7),*p(2))
  addBodyConstraint(*body,*p(2),*p(7))
  
  ; Right leg
  *body = createBody()
  *body\ptrParent = *ptrCompound 
  *p(9) = addBodyPointmass(*body,x + unitX,y + 8*unitY)
  *p(10) = addBodyPointmass(*body,x + unitX,y + 11*unitY)
  addBodyConstraint(*body,*p(9),*p(10))
  addBodyConstraint(*body,*p(10),*p(9))
  
  ; Right thigh (we re-use already existing points from the leg and the lower torso)
  *body = createBody()
  *body\ptrParent = *ptrCompound 
  
  *body\nbPoint + 1
  *body\ptrPointList = ReAllocateMemory(*body\ptrPointList,(*body\nbPoint + 1) * #SIZEOFPTR )
  PokeI(*body\ptrPointList + *body\nbPoint * #SIZEOFPTR , *p(3) )
  *body\nbPoint + 1
  *body\ptrPointList = ReAllocateMemory(*body\ptrPointList,(*body\nbPoint + 1) * #SIZEOFPTR )
  PokeI(*body\ptrPointList + *body\nbPoint * #SIZEOFPTR , *p(9) )
  
  addBodyConstraint(*body,*p(9),*p(3))
  addBodyConstraint(*body,*p(3),*p(9))
  
 ; Left leg
  *body = createBody()
  *body\ptrParent = *ptrCompound 
  *p(11) = addBodyPointmass(*body,x + 4*unitX,y + 8*unitY)
  *p(12) = addBodyPointmass(*body,x + 4*unitX,y + 11*unitY)
  addBodyConstraint(*body,*p(11),*p(12))
  addBodyConstraint(*body,*p(12),*p(11))
  
  ; Left thigh (we re-use already existing points from the leg and the lower torso)
  *body = createBody()
  *body\ptrParent = *ptrCompound 
  
  *body\nbPoint + 1
  *body\ptrPointList = ReAllocateMemory(*body\ptrPointList,(*body\nbPoint + 1) * #SIZEOFPTR )
  PokeI(*body\ptrPointList + *body\nbPoint * #SIZEOFPTR , *p(4) )
  *body\nbPoint + 1
  *body\ptrPointList = ReAllocateMemory(*body\ptrPointList,(*body\nbPoint + 1) * #SIZEOFPTR )
  PokeI(*body\ptrPointList + *body\nbPoint * #SIZEOFPTR , *p(11) )
  
  addBodyConstraint(*body,*p(11),*p(4))
  addBodyConstraint(*body,*p(4),*p(11))
  
  ; Support constraints (without them, the doll can't stand up)
  If legSupport = #True
    createConstraint(*p(9),*p(11),#False)
    createConstraint(*p(10),*p(12),#False)
    createConstraint(*p(9),*p(4),#False)
    createConstraint(*p(10),*p(11),#False)
  EndIf
  
  ProcedureReturn *ptrTorso
EndProcedure
  

; Create some pre-defined objects, for demo's sake)
Procedure createObjects(num.i)
  Protected x.i,i.i,j.i
  
  Select num
    Case 0  ; Reset
      ClearList(compound())
      ClearList(body())
      ClearList(constraint())
      ClearList(pointmass())
      
      ; Ground and walls
      createBox(-50,#SCREENHEIGHT,#SCREENWIDTH+100,50,0)
      createBox(-51,-5000,50,#SCREENHEIGHT + 5000,0)
      createBox(#SCREENWIDTH,-5000,50,#SCREENHEIGHT + 5000,0)

      
    Case 1  ; Random boxes
      For i = 1 To 8          
        createbox(i * 75, 0 ,50,50,1,#FRICTION,(300 - Random(600)) / 1000.0,(300 - Random(600)) / 1000.0,(300 - Random(600)) / 1000.0)
      Next i
      createBox(200,150,350,70,5)
      
    Case 2 ; Pile o' boxes
      x = Random(700)+20
      For j=1 To 7
        *body = createBox(x,#SCREENHEIGHT - (j * 50),50,50 )
        *body\atRest = #True
        *body\timerRest = 200
      Next j
      
    Case 3  ; a ball, a line, a triangle
      createLine(250,300 + Random(100),550,300 + Random(100),0)
      createTriangle(350,50,390,50,390,110)
      createBall(560,50,25,0.2)
      
    Case 4  ; a catapult
      x = Random(400) + 100
      createSwing(x,450,200,45)
      createBox(x - 16,450,15,50,0)
      createBox(x,410,25,25)
      createBox(x + 150,0,50,50,10)
      
    Case 5  ; Rope
      x = Random(600)+50
      createRope(x,50,x+150,150,8)
      
    Case 6  ; Bridge (just a rope with both ends static)
      *body = createRope(450,250,680,250,10)
      *p(1) = PeekI(*body\ptrPointList + (*body\nbPoint * #SIZEOFPTR))
      *p(1)\invmass = 0
      
    Case 7  ; Ragdoll
      createRagdoll(500,200)
      
      
    Case 8 ; big boxes atop static points
      createLine(250,400,250,490,0)
      createBox(149,350,200,50,1)
      
      createLine(550,400,550,490,0)
      createBox(451,350,200,50,1)
      
    Case 9 ; speed test
      For i = 10 To #SCREENWIDTH-60 Step 60
        For j=1 To 10
          createBox(i,#SCREENHEIGHT - (j * 50),50,50 )
        Next j
      Next i
      
  EndSelect   
EndProcedure


; Deletes the current body and all its points and constraints
Macro DELETEBODY()
  ; Delete body's constraints
  *ptr = body()\ptrConstraintList
  For i = 0 To body()\nbEdge
    ; But first, check if it hasn't been already deleted
    If FindString(listDeleted," "+Str(PeekI(*ptr))+" ",1) = 0
      ChangeCurrentElement(constraint(),PeekI(*ptr))
      DeleteElement(constraint())
      
      ; Store the reference of the deleted constraint
      listDeleted + " "+Str(PeekI(*ptr))+" "
    EndIf          
    *ptr + #SIZEOFPTR      
  Next i
  ; Delete body's points
  *ptr = body()\ptrPointList
  For i = 0 To body()\nbPoint
    ; But first, check if it hasn't been already deleted
    If FindString(listDeleted," "+Str(PeekI(*ptr))+" ",1) = 0
      ChangeCurrentElement(pointmass(),PeekI(*ptr))
      DeleteElement(pointmass())
      
      ; Store the reference of the deleted point
      listDeleted + " "+Str(PeekI(*ptr))+" "
    EndIf          
    *ptr + #SIZEOFPTR      
  Next i
  
  ; Store the reference of the deleted body
  listDeleted +" "+Str(@body())+" "
  
  ; Delete the body
  DeleteElement(body())
EndMacro  


; Deletes all things tied to the point in input => constraints, bodies, compound
Procedure deleteObjectFromPoint(*ptrPoint.pointmass_struct)
  Protected *ptrConst.constraint_struct
  Protected *ptr,i.i
  Protected listDeleted.s
  
  ForEach constraint()
    ; if the constraint contains the point, delete it and its parents (body, compound...)
    If constraint()\p1 = *ptrPoint Or constraint()\p2 = *ptrPoint
      *ptrConst = @constraint()
      If constraint()\ptrParent <> 0 And FindString(listDeleted," "+Str(@body())+" ",1) = 0

        ; Find the parent body
        ChangeCurrentElement(body(),constraint()\ptrParent)        
        
        ; If the body is part of a compound, delete the compound
        If body()\ptrParent <> 0 
          If FindString(listDeleted," "+Str(body()\ptrParent)+" ",1) = 0
            ChangeCurrentElement(compound(),body()\ptrParent)      
            
            ForEach body()
              If body()\ptrParent = @compound()
                DELETEBODY()
              EndIf
            Next body()
            
            ; Store the reference of the deleted compound
            listDeleted + " "+Str(@compound())+" "
            DeleteElement(compound())
          EndIf ; if FindString(listDeleted ...
        Else
          DELETEBODY()
        EndIf ; else (not part of a compound)
        
      EndIf ; if constraint()\ptrParent <> 0...
    EndIf ; if constraint()\p1 = *ptrPoint or...
    
  Next constraint()
  
EndProcedure

;DisableExplicit

;********************************************************
;- --- Main program ---
;********************************************************

;- initialization
InitSprite()
InitSprite()
InitKeyboard()
InitMouse()

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

; Initialize environnement (3 objects: a ground, two walls)
createObjects(0)

;- --- Main Loop ---
timer = ElapsedMilliseconds()
Repeat
  While WindowEvent() : Wend
  
  ; Sub-sampling => executes the simulation in small steps; slower, but more precise
  For j = 1 To #SUBSAMPLING
    ; Moves the points
    UPDATE_POINTMASSES()
   
    If *mousePoint > 0
      *mousePoint\x = MouseX()
      *mousePoint\y = MouseY()
      *mousePoint\oldX = *mousePoint\x
      *mousePoint\oldY = *mousePoint\y
    EndIf
   
    ; Solves the constraints
    UPDATE_CONSTRAINTS()
    
    ; Solves the collisions
    MANAGE_COLLISIONS()
  Next j 
  
  ;- Keyboard
  ExamineKeyboard()
  If KeyboardPushed(#PB_Key_RightControl)
    Delay(100)
  EndIf
  If KeyboardReleased(#PB_Key_Return)
    drawmode = 1-drawmode
  EndIf
  If KeyboardReleased(#PB_Key_F1)
    createObjects(1)
  EndIf
  If KeyboardReleased(#PB_Key_F2)
    createObjects(2)
  EndIf
  If KeyboardReleased(#PB_Key_F3)
    createObjects(3)
  EndIf
  If KeyboardReleased(#PB_Key_F4)
    createObjects(4)
  EndIf
  If KeyboardReleased(#PB_Key_F5)
    createObjects(5)
  EndIf
  If KeyboardReleased(#PB_Key_F6)
    createObjects(6)
  EndIf
  If KeyboardReleased(#PB_Key_F7)
    createObjects(7)
  EndIf
  If KeyboardReleased(#PB_Key_F8)
    createObjects(8)
  EndIf
  If KeyboardReleased(#PB_Key_F9)
    createObjects(9)
  EndIf
  
  ;- Mouse
  ExamineMouse()
  If mousemode = #False
    If MouseButton(#PB_MouseButton_Left) And ListSize(pointmass()) > 0
      minDistance = 999999
      ForEach pointmass()
        distance = (MouseX() - pointmass()\x)*(MouseX() - pointmass()\x) + (MouseY() - pointmass()\y)*(MouseY() - pointmass()\y)
        If distance < minDistance
          *mousePoint = @pointmass()
          minDistance = distance
        EndIf
      Next pointmass()
      If mindistance <= 300
        mousemode = #True
      Else
        *mousePoint = 0
      EndIf
    EndIf
    
    ; Del => delete all objects
    If KeyboardReleased(#PB_Key_Delete)
      createObjects(0)
      numobj=0
    EndIf
    
  Else
    If MouseButton(#PB_MouseButton_Left) = 0
      mousemode = #False
      *mousePoint = 0
    EndIf
    
    ; Del => delete only the picked object
    If KeyboardReleased(#PB_Key_Delete) And ListSize(pointmass()) > 0
      deleteObjectFromPoint(*mousePoint)
      mousemode = #False
      *mousePoint = 0
    EndIf
  EndIf
  
  
  ;- Drawing
  ClearScreen($000001)
  StartDrawing(ScreenOutput())
  If drawmode = #True
    Circle(MouseX(),MouseY(),4,$0000FF)
    drawconstraints()
    drawpointmasses()
    numobj=0
    ForEach body()
      numobj+1
      Box(body()\center\x,body()\center\y,2,2,$FF00FF)
    Next body()
  EndIf
  
  numfps+1
  If ElapsedMilliseconds()-timer >= 1000
    numfpsShown = numfps
    numfps=0
    timer = ElapsedMilliseconds()
  EndIf
  DrawText(0,0,Str(numobj) + " obj. / " + Str(numfpsShown) + "FPS")

  StopDrawing()
  FlipBuffers()
    
Until KeyboardPushed(#PB_Key_Escape)

End
Last edited by Kelebrindae on Mon Dec 22, 2014 9:51 am, edited 2 times in total.
User avatar
Rings
Moderator
Moderator
Posts: 1435
Joined: Sat Apr 26, 2003 1:11 am

Re: 2D physics engine (Verlet)

Post by Rings »

Code: Select all

; Author: Kelebrindae
; Date: December, 15, 2010
; PB version: v4.51
; ---------------------------------------------------------------------------------------------------------------
; Description:
; ---------------------------------------------------------------------------------------------------------------
; Demo for my Verlet 2D physics engine.
;
; F1 to F9 : Create pre-defined sets of objects
; Left mouse button: Pick an object
; Del: Delete the picked object (or all objects if none is picked)
; Return: Enable/disable drawing (for benchmarking)
; Right-Ctrl : Slow motion
; ---------------------------------------------------------------------------------------------------------------
; Known bugs and limitations:
; ---------------------------------------------------------------------------------------------------------------
; - The drawing  method used in this demo is quite slow. To test the real speed of the engine, use the 
;   "#PB_Screen_NoSynchronization" flag in OpenWindowedScreen and press "Return" (disables drawing)
; ---------------------------------------------------------------------------------------------------------------

; Window size
#SCREENWIDTH = 800
#SCREENHEIGHT = 500

; Verlet integration library
IncludeFile "verlet2D.pbi"

; General purpose variables
Global Dim *p.pointmass_struct(20)
Global *body.rigidBody_struct
Global drawmode.b = #True, mousemode.b = #False
Global *mousePoint.pointmass_struct, *ptrPoint.pointmass_struct
Global timer.i,j.i,minDistance.f,distance.f,numObj.i,numFps.i,numFpsShown.i

;- --- Procedures ---
EnableExplicit

;********************************************************
;- Drawing procedures
;********************************************************

; Draw all the points
Procedure drawpointmasses()
  Protected w.i,h.i
  
  ForEach pointmass()
    ; not static => white circle
    If pointmass()\invmass > 0
      Circle(pointmass()\x,pointmass()\y,2,$FFFFFF)
      
      ; also, we draw the speed as yellow lines (but most of the time, it's invisible)
      w = pointmass()\oldX - pointmass()\x
      h = pointmass()\oldY - pointmass()\y
      If w = 0
        w = 1
      EndIf
      If h = 0
        h = 1
      EndIf
      Line(pointmass()\x,pointmass()\y,w,h,$00FFFF)
    Else ; static => blue cross
      Line(pointmass()\x - 3,pointmass()\y - 3,7,7,$FF0000)
      Line(pointmass()\x - 3,pointmass()\y + 3,7,-7,$FF0000)
    EndIf
  Next pointmass()

EndProcedure

; Draw all the constraints
Procedure drawconstraints()
  Protected w.i,h.i
  
  ForEach constraint()
    w = constraint()\p2\x - constraint()\p1\x
    h = constraint()\p2\y - constraint()\p1\y
    
    If w = 0
      w = 1
    EndIf
    If h = 0
      h = 1
    EndIf
    
    ; Draw constraints in green, or grey is they are disabled
    If constraint()\enable = #True
      Line(constraint()\p1\x,constraint()\p1\y,w,h,$00FF00)
    Else
      Line(constraint()\p1\x,constraint()\p1\y,w,h,$777777)
    EndIf
  Next constraint()

EndProcedure



;********************************************************
;- Pre-defined objects
;********************************************************

; Create a single line. Useful to figure the ground, the walls, etc..
; (It's made of two points and TWO constraints, because my collision algo doesn't seem to like single constraint bodies...)
Procedure createLine(x1.i,y1.i,x2.i,y2.i,mass.f = 1)
  Protected *body.rigidBody_struct
  *body = createBody()
  
  *p(1) = addBodyPointmass(*body,x1,y1,mass)
  *p(2) = addBodyPointmass(*body,x2,y2,mass)
  
  addBodyConstraint(*body,*p(1),*p(2))
  addBodyConstraint(*body,*p(2),*p(1))
  
  ProcedureReturn *body
EndProcedure

; Create a triangle
Procedure createTriangle(x.i,y.i,width.i,height.i,mass.f = 1,hspeed.f = 0,vspeed.f = 0,rotation.f = 0)
  Protected *body.rigidBody_struct
  *body = createBody()
  
  *p(1) = addBodyPointmass(*body,x,y,mass,hspeed+rotation,vspeed)
  *p(2) = addBodyPointmass(*body,x,y+height,mass,hspeed,vspeed)
  *p(3) = addBodyPointmass(*body,x+width,y,mass,hspeed,vspeed+rotation)

  addBodyConstraint(*body,*p(1),*p(2))
  addBodyConstraint(*body,*p(1),*p(3))
  addBodyConstraint(*body,*p(2),*p(3))
  
  ProcedureReturn *body  
EndProcedure


; Create a box
Procedure createBox(x.i,y.i,width.i,height.i,mass.f = 1,hspeed.f = 0,vspeed.f = 0,rotation.f = 0)
  Protected *body.rigidBody_struct
  *body = createBody()
  
  *p(1) = addBodyPointmass(*body,x,y,mass,hspeed+rotation,vspeed)
  *p(2) = addBodyPointmass(*body,x,y+height,mass,hspeed,vspeed)
  *p(3) = addBodyPointmass(*body,x+width,y+height,mass,hspeed-rotation,vspeed)
  *p(4) = addBodyPointmass(*body,x+width,y,mass,hspeed+rotation,vspeed)
  
  addBodyConstraint(*body,*p(1),*p(2))
  addBodyConstraint(*body,*p(2),*p(3))
  addBodyConstraint(*body,*p(3),*p(4))
  addBodyConstraint(*body,*p(1),*p(4))
  addBodyConstraint(*body,*p(1),*p(3),#False)
  addBodyConstraint(*body,*p(2),*p(4),#False)
  
  ProcedureReturn *body  
EndProcedure


; Create a chain of lines which can be used as a rope, an elastic, a bridge, etc..
Procedure createRope(x1.i,y1.i,x2.i,y2.i,nbSegments.i)
  Protected i.i
  Protected x.f = x1,y.f = y1
  Protected xd.f = (x2 - x1) / nbSegments,yd.f = (y2 - y1) / nbSegments
  Protected *body.rigidBody_struct
  
  AddElement(compound())
  
  For i=1 To nbSegments
    *body = createBody()
    If i=1
      *p(1) = addBodyPointmass(*body,x,y,0) ; mass = 0 => static point
    Else
      *p(1) = *p(2)
      ; Re-use previous point
      *body\nbPoint + 1
      *body\ptrPointList = ReAllocateMemory(*body\ptrPointList,(*body\nbPoint + 1) * #SIZEOFPTR )
      PokeI(*body\ptrPointList + *body\nbPoint * #SIZEOFPTR , *p(1) )
    EndIf
        
    ; add next point
    x+xd
    y+yd
    *p(2) = addBodyPointmass(*body,x,y)
    
    ; create link between the two points
    addBodyConstraint(*body,*p(1),*p(2))
    addBodyConstraint(*body,*p(2),*p(1))

    *body\ptrParent = @compound()
  Next i
  
  ProcedureReturn *body
EndProcedure


; Create a swing (for all your circus needs ;) )
Procedure createSwing(x.i,y.i,width.i,height.i,center.i = 0,mass.f = 1)
  Protected *body.rigidBody_struct
  
  If center = 0
    center = x + width/2
  EndIf
  
  *body = createBody()
  
  *p(1) = addBodyPointmass(*body,x,y,mass)
  *p(2) = addBodyPointmass(*body,center,y,0)
  *p(3) = addBodyPointmass(*body,x + width,y,mass)
  *p(4) = addBodyPointmass(*body,center,y + height,mass)

  
  addBodyConstraint(*body,*p(1),*p(2))
  addBodyConstraint(*body,*p(2),*p(3))
  addBodyConstraint(*body,*p(2),*p(4),#False)
  addBodyConstraint(*body,*p(1),*p(4))
  addBodyConstraint(*body,*p(3),*p(4))
  
  ProcedureReturn *body  
EndProcedure


; Create a ball
Procedure createBall(x.i,y.i,radius.f,mass.f = 1)
  Protected i.i
  Protected *body.rigidBody_struct
  
  *body = createBody()
  *p(1) = addBodyPointmass(*body,x,y,mass*3)
  For i = 0 To 330 Step 30
    If i = 0
      *p(2) = addBodyPointmass(*body,x+Cos(Radian(i))*radius,y+Sin(Radian(i))*radius,mass)
      *p(5) = *p(2)
    Else
      *p(3) = *p(2)
      *p(2) = addBodyPointmass(*body,x+Cos(Radian(i))*radius,y+Sin(Radian(i))*radius,mass)
      addBodyConstraint(*body,*p(2),*p(3))
      addBodyConstraint(*body,*p(1),*p(3),#False)
    EndIf
  Next i
  addBodyConstraint(*body,*p(2),*p(5))
  addBodyConstraint(*body,*p(1),*p(2),#False)
  
  ProcedureReturn *body  
EndProcedure


; Create a primitive ragdoll
Procedure createRagdoll(x.i,y.i,width.i = 50,height.i = 100,legSupport.b = #False)
  Protected *ptrCompound.compound_struct,*ptrTorso.rigidbody_struct
  Protected unitX.f = width / 5,unitY.i = height / 10
  
  ; Note to self: All this is a bit complicated; Need a way to attach a body to an other more easily...
  
  *ptrCompound = AddElement(compound())
  
  ; Torso
  *ptrTorso = createBody()
  *ptrTorso\ptrParent = *ptrCompound 
  *p(1) = addBodyPointmass(*ptrTorso,x + unitX,y)
  *p(2) = addBodyPointmass(*ptrTorso,x + 4*unitX,y)
  *p(3) = addBodyPointmass(*ptrTorso,x + 1.5*unitX,y + 5*unitY)
  *p(4) = addBodyPointmass(*ptrTorso,x + 3.5*unitX,y + 5*unitY)      

  addBodyConstraint(*ptrTorso,*p(1),*p(2))
  addBodyConstraint(*ptrTorso,*p(1),*p(3))
  addBodyConstraint(*ptrTorso,*p(2),*p(4))
  addBodyConstraint(*ptrTorso,*p(3),*p(4))
  addBodyConstraint(*ptrTorso,*p(1),*p(4),#False)
  addBodyConstraint(*ptrTorso,*p(2),*p(3),#False)
  
  ; Right forearm
  *body = createBody()
  *body\ptrParent = *ptrCompound 
  *p(5) = addBodyPointmass(*body,x,y + 3*unitY,0.5)
  *p(6) = addBodyPointmass(*body,x,y + 6*unitY,0.5)
  addBodyConstraint(*body,*p(5),*p(6))
  addBodyConstraint(*body,*p(6),*p(5))
  
  ; Right arm (we re-use already existing points from the foream and the torso)
  *body = createBody()
  *body\ptrParent = *ptrCompound
  
  *body\nbPoint + 1
  *body\ptrPointList = ReAllocateMemory(*body\ptrPointList,(*body\nbPoint + 1) * #SIZEOFPTR )
  PokeI(*body\ptrPointList + *body\nbPoint * #SIZEOFPTR , *p(1) )
  *body\nbPoint + 1
  *body\ptrPointList = ReAllocateMemory(*body\ptrPointList,(*body\nbPoint + 1) * #SIZEOFPTR )
  PokeI(*body\ptrPointList + *body\nbPoint * #SIZEOFPTR , *p(5) )
  
  addBodyConstraint(*body,*p(5),*p(1))
  addBodyConstraint(*body,*p(1),*p(5))
  
  ; Left forearm
  *body = createBody()
  *body\ptrParent = *ptrCompound 
  *p(7) = addBodyPointmass(*body,x + 5*unitX,y + 3*unitY,0.5)
  *p(8) = addBodyPointmass(*body,x + 5*unitX,y + 6*unitY,0.5)
  addBodyConstraint(*body,*p(7),*p(8))
  addBodyConstraint(*body,*p(8),*p(7))
  
  ; Left arm (we re-use already existing points from the foream and the torso)
  *body = createBody()
  *body\ptrParent = *ptrCompound 
  
  *body\nbPoint + 1
  *body\ptrPointList = ReAllocateMemory(*body\ptrPointList,(*body\nbPoint + 1) * #SIZEOFPTR )
  PokeI(*body\ptrPointList + *body\nbPoint * #SIZEOFPTR , *p(2) )
  *body\nbPoint + 1
  *body\ptrPointList = ReAllocateMemory(*body\ptrPointList,(*body\nbPoint + 1) * #SIZEOFPTR )
  PokeI(*body\ptrPointList + *body\nbPoint * #SIZEOFPTR , *p(7) )
  
  addBodyConstraint(*body,*p(7),*p(2))
  addBodyConstraint(*body,*p(2),*p(7))
  
  ; Right leg
  *body = createBody()
  *body\ptrParent = *ptrCompound 
  *p(9) = addBodyPointmass(*body,x + unitX,y + 8*unitY)
  *p(10) = addBodyPointmass(*body,x + unitX,y + 11*unitY)
  addBodyConstraint(*body,*p(9),*p(10))
  addBodyConstraint(*body,*p(10),*p(9))
  
  ; Right thigh (we re-use already existing points from the leg and the lower torso)
  *body = createBody()
  *body\ptrParent = *ptrCompound 
  
  *body\nbPoint + 1
  *body\ptrPointList = ReAllocateMemory(*body\ptrPointList,(*body\nbPoint + 1) * #SIZEOFPTR )
  PokeI(*body\ptrPointList + *body\nbPoint * #SIZEOFPTR , *p(3) )
  *body\nbPoint + 1
  *body\ptrPointList = ReAllocateMemory(*body\ptrPointList,(*body\nbPoint + 1) * #SIZEOFPTR )
  PokeI(*body\ptrPointList + *body\nbPoint * #SIZEOFPTR , *p(9) )
  
  addBodyConstraint(*body,*p(9),*p(3))
  addBodyConstraint(*body,*p(3),*p(9))
  
 ; Left leg
  *body = createBody()
  *body\ptrParent = *ptrCompound 
  *p(11) = addBodyPointmass(*body,x + 4*unitX,y + 8*unitY)
  *p(12) = addBodyPointmass(*body,x + 4*unitX,y + 11*unitY)
  addBodyConstraint(*body,*p(11),*p(12))
  addBodyConstraint(*body,*p(12),*p(11))
  
  ; Left thigh (we re-use already existing points from the leg and the lower torso)
  *body = createBody()
  *body\ptrParent = *ptrCompound 
  
  *body\nbPoint + 1
  *body\ptrPointList = ReAllocateMemory(*body\ptrPointList,(*body\nbPoint + 1) * #SIZEOFPTR )
  PokeI(*body\ptrPointList + *body\nbPoint * #SIZEOFPTR , *p(4) )
  *body\nbPoint + 1
  *body\ptrPointList = ReAllocateMemory(*body\ptrPointList,(*body\nbPoint + 1) * #SIZEOFPTR )
  PokeI(*body\ptrPointList + *body\nbPoint * #SIZEOFPTR , *p(11) )
  
  addBodyConstraint(*body,*p(11),*p(4))
  addBodyConstraint(*body,*p(4),*p(11))
  
  ; Support constraints (without them, the doll can't stand up)
  If legSupport = #True
    createConstraint(*p(9),*p(11),#False)
    createConstraint(*p(10),*p(12),#False)
    createConstraint(*p(9),*p(4),#False)
    createConstraint(*p(10),*p(11),#False)
  EndIf
  
  ProcedureReturn *ptrTorso
EndProcedure
  

; Create some pre-defined objects, for demo's sake)
Procedure createObjects(num.i)
  Protected x.i,i.i,j.i
  
  Select num
    Case 0  ; Reset
      ClearList(compound())
      ClearList(body())
      ClearList(constraint())
      ClearList(pointmass())
      
      ; Ground and walls
      createBox(-50,#SCREENHEIGHT,#SCREENWIDTH+100,50,0)
      createBox(-51,-5000,50,#SCREENHEIGHT + 5000,0)
      createBox(#SCREENWIDTH,-5000,50,#SCREENHEIGHT + 5000,0)

      
    Case 1  ; Random boxes
      For i = 1 To 8          
        createbox(i * 75, 0 ,50,50,1,(300 - Random(600)) / 1000.0,(300 - Random(600)) / 1000.0,(300 - Random(600)) / 1000.0)
      Next i
      createBox(200,150,350,70,5)
      
    Case 2 ; Pile o' boxes
      x = Random(700)+20
      For j=1 To 7
        createBox(x,#SCREENHEIGHT - (j * 50),50,50 )
      Next j
      
    Case 3  ; a ball, a line, a triangle
      createLine(250,300 + Random(100),550,300 + Random(100),0)
      createTriangle(350,50,40,60)
      createBall(560,50,25,0.2)
      
    Case 4  ; a catapult
      x = Random(400) + 100
      createSwing(x,450,200,45)
      createBox(x - 16,450,15,50,0)
      createBox(x,410,25,25)
      createBox(x + 150,0,50,50,10)
      
    Case 5  ; Rope
      x = Random(600)+50
      createRope(x,50,x+150,150,8)
      
    Case 6  ; Bridge (just a rope with both ends static)
      *body = createRope(450,250,680,250,10)
      *p(1) = PeekI(*body\ptrPointList + (*body\nbPoint * #SIZEOFPTR))
      *p(1)\invmass = 0
      
    Case 7  ; Ragdoll
      createRagdoll(500,200)
      
      
    Case 8 ; big boxes atop static points
      createLine(250,400,250,490,0)
      createBox(149,350,200,50,1)
      
      createLine(550,400,550,490,0)
      createBox(451,350,200,50,1)
      
    Case 9 ; speed test
      For i = 10 To #SCREENWIDTH-60 Step 60
        For j=1 To 10
          createBox(i,#SCREENHEIGHT - (j * 50),50,50 )
        Next j
      Next i
      
  EndSelect   
EndProcedure


; Deletes the current body and all its points and constraints
Macro DELETEBODY()
  ; Delete body's constraints
  *ptr = body()\ptrConstraintList
  For i = 0 To body()\nbEdge
    ; But first, check if it hasn't been already deleted
    If FindString(listDeleted," "+Str(PeekI(*ptr))+" ",1) = 0
      ChangeCurrentElement(constraint(),PeekI(*ptr))
      DeleteElement(constraint())
      
      ; Store the reference of the deleted constraint
      listDeleted + " "+Str(PeekI(*ptr))+" "
    EndIf          
    *ptr + #SIZEOFPTR      
  Next i
  ; Delete body's points
  *ptr = body()\ptrPointList
  For i = 0 To body()\nbPoint
    ; But first, check if it hasn't been already deleted
    If FindString(listDeleted," "+Str(PeekI(*ptr))+" ",1) = 0
      ChangeCurrentElement(pointmass(),PeekI(*ptr))
      DeleteElement(pointmass())
      
      ; Store the reference of the deleted point
      listDeleted + " "+Str(PeekI(*ptr))+" "
    EndIf          
    *ptr + #SIZEOFPTR      
  Next i
  
  ; Store the reference of the deleted body
  listDeleted +" "+Str(@body())+" "
  
  ; Delete the body
  DeleteElement(body())
EndMacro  


; Deletes all things tied to the point in input => constraints, bodies, compound
Procedure deleteObjectFromPoint(*ptrPoint.pointmass_struct)
  Protected *ptrConst.constraint_struct
  Protected *ptr,i.i
  Protected listDeleted.s
  
  ForEach constraint()
    ; if the constraint contains the point, delete it and its parents (body, compound...)
    If constraint()\p1 = *ptrPoint Or constraint()\p2 = *ptrPoint
      *ptrConst = @constraint()
      If constraint()\ptrParent <> 0 And FindString(listDeleted," "+Str(@body())+" ",1) = 0

        ; Find the parent body
        ChangeCurrentElement(body(),constraint()\ptrParent)        
        
        ; If the body is part of a compound, delete the compound
        If body()\ptrParent <> 0 
          If FindString(listDeleted," "+Str(body()\ptrParent)+" ",1) = 0
            ChangeCurrentElement(compound(),body()\ptrParent)      
            
            ForEach body()
              If body()\ptrParent = @compound()
                DELETEBODY()
              EndIf
            Next body()
            
            ; Store the reference of the deleted compound
            listDeleted + " "+Str(@compound())+" "
            DeleteElement(compound())
          EndIf ; if FindString(listDeleted ...
        Else
          DELETEBODY()
        EndIf ; else (not part of a compound)
        
      EndIf ; if constraint()\ptrParent <> 0...
    EndIf ; if constraint()\p1 = *ptrPoint or...
    
  Next constraint()
  
EndProcedure

;DisableExplicit

;********************************************************
;- --- Main program ---
;********************************************************

;- initialization
InitSprite()
InitSprite3D()
InitKeyboard()
InitMouse()

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

; Initialize environnement (3 objects: a ground, two walls)
createObjects(0)

;- --- Main Loop ---
timer = ElapsedMilliseconds()
Repeat
  While WindowEvent() : Wend
  
  ; Sub-sampling => executes the simulation in small steps; slower, but more precise
  For j = 1 To #SUBSAMPLING
    ; Moves the points
    UPDATE_POINTMASSES()
   
    If *mousePoint > 0
      *mousePoint\x = MouseX()
      *mousePoint\y = MouseY()
      *mousePoint\oldX = *mousePoint\x
      *mousePoint\oldY = *mousePoint\y
    EndIf
   
    ; Solves the constraints
    UPDATE_CONSTRAINTS()
    
    ; Solves the collisions
    MANAGE_COLLISIONS()
  Next j 
  
  ;- Keyboard
  ExamineKeyboard()
  If KeyboardPushed(#PB_Key_RightControl)
    Delay(100)
  EndIf
  If KeyboardReleased(#PB_Key_Return)
    drawmode = 1-drawmode
  EndIf
  If KeyboardReleased(#PB_Key_F1)
    createObjects(1)
  EndIf
  If KeyboardReleased(#PB_Key_F2)
    createObjects(2)
  EndIf
  If KeyboardReleased(#PB_Key_F3)
    createObjects(3)
  EndIf
  If KeyboardReleased(#PB_Key_F4)
    createObjects(4)
  EndIf
  If KeyboardReleased(#PB_Key_F5)
    createObjects(5)
  EndIf
  If KeyboardReleased(#PB_Key_F6)
    createObjects(6)
  EndIf
  If KeyboardReleased(#PB_Key_F7)
    createObjects(7)
  EndIf
  If KeyboardReleased(#PB_Key_F8)
    createObjects(8)
  EndIf
  If KeyboardReleased(#PB_Key_F9)
    createObjects(9)
  EndIf
  
  ;- Mouse
  ExamineMouse()
  If mousemode = #False
    If MouseButton(#PB_MouseButton_Left) And ListSize(pointmass()) > 0
      minDistance = 999999
      ForEach pointmass()
        distance = (MouseX() - pointmass()\x)*(MouseX() - pointmass()\x) + (MouseY() - pointmass()\y)*(MouseY() - pointmass()\y)
        If distance < minDistance
          *mousePoint = @pointmass()
          minDistance = distance
        EndIf
      Next pointmass()
      If mindistance <= 300
        mousemode = #True
      Else
        *mousePoint = 0
      EndIf
    EndIf
    
    ; Del => delete all objects
    If KeyboardReleased(#PB_Key_Delete)
      createObjects(0)
      numobj=0
    EndIf
    
  Else
    If MouseButton(#PB_MouseButton_Left) = 0
      mousemode = #False
      *mousePoint = 0
    EndIf
    
    ; Del => delete only the picked object
    If KeyboardReleased(#PB_Key_Delete) And ListSize(pointmass()) > 0
      deleteObjectFromPoint(*mousePoint)
      mousemode = #False
      *mousePoint = 0
    EndIf
  EndIf
  
  
  ;- Drawing
  ClearScreen($000001)
  StartDrawing(ScreenOutput())
  If drawmode = #True
    Circle(MouseX(),MouseY(),4,$0000FF)
    drawconstraints()
    drawpointmasses()
    numobj=0
    ForEach body()
      numobj+1
      Box(body()\center\x,body()\center\y,2,2,$FF00FF)
    Next body()
  EndIf
  
  numfps+1
  If ElapsedMilliseconds()-timer >= 1000
    numfpsShown = numfps
    numfps=0
    timer = ElapsedMilliseconds()
  EndIf
  DrawText(0,0,Str(numobj) + " obj. / " + Str(numfpsShown) + "FPS")

  StopDrawing()
  FlipBuffers()
    
Until KeyboardPushed(#PB_Key_Escape)

End
SPAMINATOR NR.1
User avatar
Rings
Moderator
Moderator
Posts: 1435
Joined: Sat Apr 26, 2003 1:11 am

Re: 2D physics engine (Verlet)

Post by Rings »

Great Stuff !!!
SPAMINATOR NR.1
User avatar
Didelphodon
PureBasic Expert
PureBasic Expert
Posts: 450
Joined: Sat Dec 18, 2004 11:56 am
Location: Vienna - Austria
Contact:

Re: 2D physics engine (Verlet)

Post by Didelphodon »

Cool!
Go, tell it on the mountains.
Kelebrindae
Enthusiast
Enthusiast
Posts: 151
Joined: Tue Apr 01, 2008 3:23 pm

Re: 2D physics engine (Verlet)

Post by Kelebrindae »

Thanks!
(and thank you, Rings, for posting the last part of the source)
User avatar
idle
Always Here
Always Here
Posts: 5836
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: 2D physics engine (Verlet)

Post by idle »

Thanks that's awesome.
Windows 11, Manjaro, Raspberry Pi OS
Image
User avatar
Rook Zimbabwe
Addict
Addict
Posts: 4322
Joined: Tue Jan 02, 2007 8:16 pm
Location: Cypress TX
Contact:

Re: 2D physics engine (Verlet)

Post by Rook Zimbabwe »

Very nice... I am going to try to recode my PLINKO game with this! :mrgreen:
Binarily speaking... it takes 10 to Tango!!!

Image
http://www.bluemesapc.com/
User avatar
kenmo
Addict
Addict
Posts: 2033
Joined: Tue Dec 23, 2003 3:54 am

Re: 2D physics engine (Verlet)

Post by kenmo »

Very nice work... I will study your collision detection later in particular!
Kelebrindae
Enthusiast
Enthusiast
Posts: 151
Joined: Tue Apr 01, 2008 3:23 pm

Re: 2D physics engine (Verlet)

Post by Kelebrindae »

Thanks a lot for your comments, guys!

Merry christmas and happy new year to you all !
ImageImageImage
dige
Addict
Addict
Posts: 1391
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Re: 2D physics engine (Verlet)

Post by dige »

Amazing! :shock: Looks very good - thank you Kelebrindae!
"Daddy, I'll run faster, then it is not so far..."
User avatar
idle
Always Here
Always Here
Posts: 5836
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: 2D physics engine (Verlet)

Post by idle »

Added a rudimentary spatial map
Dropped a couple of loops by combining the calculations
Added a center of object select mode.

I would have thought using a map would have made a huge speed improvement but it doesn't seem to be the case?
I could be missing something or perhaps it's due to the world boundaries being added to the map every cycle.

http://www.idlearts.com/verlet.zip
Windows 11, Manjaro, Raspberry Pi OS
Image
User avatar
Rook Zimbabwe
Addict
Addict
Posts: 4322
Joined: Tue Jan 02, 2007 8:16 pm
Location: Cypress TX
Contact:

Re: 2D physics engine (Verlet)

Post by Rook Zimbabwe »

idle wrote: perhaps it's due to the world boundaries being added to the map every cycle.

http://www.idlearts.com/verlet.zip
I am thinking that is it... OK dusting off the brain for a rudimentary plinko example
Binarily speaking... it takes 10 to Tango!!!

Image
http://www.bluemesapc.com/
User avatar
idle
Always Here
Always Here
Posts: 5836
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: 2D physics engine (Verlet)

Post by idle »

I think the changes I made to reduce the loops has slowed it heaps perhaps due to paging faulting memory.
Changed the Spatial map to handle static items like boundaries so they aren't added every cycle.
Behavior is looking a bit better though.

http://www.idlearts.com/verlet.zip
Windows 11, Manjaro, Raspberry Pi OS
Image
Post Reply