billiard style ball movement

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Post by rolliebollocks »

@Dodicat

Wow!

That's awesome.
h4tt3n
Posts: 698
Joined: Oct 22, 2005 21:12
Location: Denmark

Post by h4tt3n »

Really cool stuff, will need to look at your code. It reminds me of this little thing I made a few years back. It has the same features except lens mapping, and it comes with both linear and rotational friction.

Cheers,
Mike

Code: Select all

''	impulse based 2d ball-ball and ball-line collision with friction
''	made by Mike "h4tt3n"

const pi        		= 4*atn(1)
const num_balls 		= 32
const g_acc     		= 2000
const scrn_wid  		= 900
const scrn_hgt  		= 600
const rest_fps      = 60              ''  ideal framerate
const inv_rest_fps  = 1/rest_fps		  ''  inverse ideal framerate
Const dt            = 0.01    ''  timestep, delta time
Const StaticFrictionVelocity = 0.01

#include once "vec2f.bi"
randomize timer

type ball_type
  as uinteger col
  as vec2f frc, acc, vel, pos
  as Single trq, ang_acc, ang_vel, sin_ang, cos_ang, ang, mass, InverseMass, _
    dens, radius, RadiusSquared, I, InverseI, Restitution, dynamicfriction, staticfriction
end type

type wall_type
  as uinteger col
  as vec2f pos(1 to 2), dst
  as Single Restitution, dynamicfriction, staticfriction
end type

dim shared as vec2f dst, ncoll, separation_vector, vcoll, closest_point
dim shared as ball_type ptr ball
dim shared as wall_type wall(1 to 10)
dim shared as Single dst_sqd, radius, RadiusSquared, distance_, force
dim shared as integer a, b
dim shared as integer FPS, FPS_Counter
dim shared as Single FPS_Timer, t0

declare sub BallBallCollisionDetection()
declare sub BallWallCollisionDetection()
declare sub BallWallCollisionResponse(byref a as ball_type, byref w as wall_type)
declare sub IntegrateEulerCromer1stOrder()
declare sub DrawSceneToScreen()
declare sub brake()

declare function Vec_Pnt_Lin(a1 As vec2f, a2 As vec2f, p1 As vec2f) As vec2f

ball = Callocate(num_balls*sizeof(ball_type))

''  set startup condition
with wall(1)
  .pos(1).x = 10
  .pos(1).y = 10
  .pos(2).x = scrn_wid-11
  .pos(2).y = 10
end with

with wall(2)
  .pos(1).x = scrn_wid-11
  .pos(1).y = 10
  .pos(2).x = scrn_wid-11
  .pos(2).y = scrn_hgt-81
end with

with wall(3)
  .pos(1).x = scrn_wid-11
  .pos(1).y = scrn_hgt-81
  .pos(2).x = (scrn_wid\2)+80
  .pos(2).y = scrn_hgt-11
end with

with wall(4)
  .pos(1).x = (scrn_wid\2)+80
  .pos(1).y = scrn_hgt-11
  .pos(2).x = (scrn_wid\2)
  .pos(2).y = scrn_hgt-50
end with

with wall(5)
  .pos(1).x = scrn_wid-250
  .pos(1).y = 450
  .pos(2).x = .pos(1).x+60
  .pos(2).y = .pos(1).y-20
end with

with wall(6)
  .pos(1).x = (scrn_wid\2)
  .pos(1).y = scrn_hgt-50
  .pos(2).x = 10
  .pos(2).y = scrn_hgt-11
end with

with wall(7)
  .pos(1).x = 10
  .pos(1).y = scrn_hgt-11
  .pos(2).x = 10
  .pos(2).y = 10
end with

with wall(8)
  .pos(1).x = 11
  .pos(1).y = 100
  .pos(2).x = scrn_wid-150
  .pos(2).y = 150
end with

with wall(9)
  .pos(1).x = 150
  .pos(1).y = 300
  .pos(2).x = scrn_wid-11
  .pos(2).y = 250
end with

with wall(10)
  .pos(1).x = 11
  .pos(1).y = 400
  .pos(2).x = scrn_wid-250
  .pos(2).y = 450
end with

for a = 1 to ubound(wall)
  with wall(a)
    .Restitution = 0.9
    .dynamicfriction = 0.2
    .staticfriction = 0.8
  end with
next

for a = 0 to num_balls-1
  with ball[a]
    .col = rgb(96+rnd*160, 96+rnd*160, 96+rnd*160)
    .mass = 2+(rnd*18^(1/4))^4
    .InverseMass = 1/.mass
    .Restitution = 0.9
    .dens = 0.0002
    .radius = ((.mass/.dens)/((4/3)*pi))^(1/3)
    .RadiusSquared = .radius*.radius
    .I = (1/2)*.mass*.radius*.radius
    .InverseI = 1/.I
    .pos.x = .radius + rnd*(scrn_wid-2*.radius)
    .pos.y = .radius + rnd*(scrn_hgt\3-2*.radius)
    '.vel.Randomise(0)
    .ang = rnd*2*pi
    .cos_ang = cos(.ang)
    .sin_ang = sin(.ang)
    .ang_vel = (rnd-rnd)*100
    .dynamicfriction = 0.2
    .staticfriction = 0.8
  end with
next

''----------------------------------------------------------------------------''

screenres scrn_wid, scrn_hgt, 16
color 0, rgb(255, 255, 255)

do
  
  BallBallCollisionDetection()
  BallWallCollisionDetection()
  IntegrateEulerCromer1stOrder()
  DrawSceneToScreen()
  brake()
  
loop until multikey(1)

deallocate(ball)

end

''----------------------------------------------------------------------------''

sub BallWallCollisionDetection()
  
  for a as integer = lbound(wall) to ubound(wall)
    for b as integer = 0 to num_balls-1
      closest_point = Vec_Pnt_Lin(wall(a).pos(1), wall(a).pos(2), ball[b].pos)
      dst = ball[b].pos - closest_point
      dst_sqd = dst.MagnitudeSquared
      if dst_sqd < ball[b].RadiusSquared then
        BallWallCollisionResponse(ball[b], wall(a))
      end if
    next
  next
  
end sub

sub BallWallCollisionResponse(byref a as ball_type, byref w as wall_type)
  
  distance_ = dst.Magnitude
  
  ''  collision plane normal
  dim as vec2f NormalVector = dst/distance_
  
  ''  collision plane tangent
  dim as vec2f TangentVector = NormalVector.Normal
  
  ''  overlap distance_
  dim as Single intersection = a.radius-distance_
  
  a.pos += intersection*NormalVector
  
  dim as vec2f contactPointVelocity = a.vel + a.radius * TangentVector * a.ang_vel
  dim as Single contactPointVelocityParallel = dot(contactPointVelocity, TangentVector)
  dim as Single contactPointVelocityNormal = dot(contactPointVelocity, normalVector)
  
  ''  if ball is moving towards wall then calculate impulse
  if contactPointVelocityNormal < 0 then
    
    ''  normal impulse magnitude
    dim as Single impulseNormal = -(1+(w.Restitution+a.Restitution)*0.5)*contactPointVelocityNormal/a.InverseMass
    dim as Single impulseRequiredParallel = -contactPointVelocityParallel/(a.InverseMass+(a.radius*a.radius/a.i))
    
    'dim as Single ImpulseTangential
    dim as Single frictioncoefficient
    
    ''  if contact point is at rest compared to the wall, then apply static
    ''  friction, else apply dynamic / kinetic friction
    if contactpointvelocityparallel < StaticFrictionVelocity then 
      frictioncoefficient = (a.staticfriction+w.staticfriction)*0.5
      'a.col = RGB(255, 32, 32)
    else
      frictioncoefficient = (a.dynamicfriction+w.dynamicfriction)*0.5
      'a.col = rgb(32, 255, 32)
    endif
    
    dim as Single ImpulseTangential = IIf(Abs(impulseRequiredParallel) < frictioncoefficient*impulseNormal, _
    	impulseRequiredParallel, Sgn(impulseRequiredParallel)* frictioncoefficient*impulseNormal)
    
    a.vel += (ImpulseNormal*NormalVector+ImpulseTangential*TangentVector)*a.InverseMass
    a.ang_vel += ImpulseTangential*a.Radius/a.I
    
  end if
  
end sub

sub BallBallCollisionDetection()
  
  dim as integer a, b
  
  for a = 0 to num_balls-2
    for b = a+1 to num_balls-1
      dst = ball[a].pos-ball[b].pos
      dst_sqd = MagnitudeSquared(dst)
      radius = ball[a].radius+ball[b].radius
      RadiusSquared = radius*radius
      
      If dst_sqd < RadiusSquared Then
	      	
				Dim As vec2f distance_Vector = ball[a].pos-ball[b].pos
				Dim As double distance_Squared = MagnitudeSquared(distance_Vector)
				Dim As double distance_ = sqr(distance_Squared)
				dim as vec2f NormalVector = distance_Vector/distance_
				dim as vec2f TangentVector = normal(NormalVector)
				
				dim as double intersection = radius-distance_
				dim as vec2f separation_vector = (normalvector*intersection)/(ball[a].InverseMass+ball[b].InverseMass)
				
				ball[a].pos += separation_vector*ball[a].InverseMass
				ball[b].pos -= separation_vector*ball[b].InverseMass
				
				Dim As vec2f contactpoint_A = ball[a].pos-ball[a].radius*NormalVector
				Dim As vec2f contactpoint_B = ball[b].pos+ball[b].radius*NormalVector
				dim as vec2f contactPointVelocity_A = ball[a].vel+ball[a].ang_vel*normal(ball[a].pos-contactpoint_A)
				dim as vec2f contactPointVelocity_B = ball[b].vel+ball[b].ang_vel*normal(ball[b].pos-contactpoint_B)
				dim as vec2f contactPointVelocity = contactPointVelocity_a - contactPointVelocity_b
				dim as double contactPointVelocityNormal = dot(contactPointVelocity, normalVector)
				
				if contactPointVelocityNormal < 0 Then
					
					Dim As double Restitution = (ball[a].Restitution+ball[b].Restitution)*0.5
					dim as double impulseNormal = -(1+Restitution)*contactPointVelocityNormal/(ball[a].InverseMass+ball[b].InverseMass)
					
					Dim As double contactpointvelocityTangent = dot(contactpointvelocity, tangentvector)
					Dim As double FrictionCoefficient = IIf(Abs(ContactPointVelocityTangent) < StaticFrictionVelocity, (Ball[a].StaticFriction+Ball[b].StaticFriction)*0.5, (Ball[a].DynamicFriction+Ball[b].DynamicFriction)*0.5)
					Dim as double MaxImpulseTangent = -ContactPointVelocityTangent/(Ball[a].InverseMass+Ball[b].InverseMass+Ball[a].RadiusSquared*Ball[a].InverseI+Ball[b].RadiusSquared*Ball[b].InverseI)
					Dim As double ImpulseTangent = IIf(Abs(MaxImpulseTangent) < FrictionCoefficient*ImpulseNormal, MaxImpulseTangent, Sgn(MaxImpulseTangent)*FrictionCoefficient*ImpulseNormal)
					Dim As vec2f Impulse = ImpulseNormal*Normalvector+ImpulseTangent*TangentVector
					
					ball[a].vel += Impulse*ball[a].InverseMass
					ball[a].ang_vel += dotnormal(Impulse, ball[a].pos-contactpoint_a)*ball[a].InverseI
					
					ball[b].vel -= Impulse*ball[b].InverseMass
					ball[b].ang_vel -= dotnormal(Impulse, ball[b].pos-contactpoint_b)*ball[b].InverseI
					
				End If
			
      end if
    next
  next
  
end sub

sub IntegrateEulerCromer1stOrder()
  
  dim as integer a
  
  for a = 0 to num_balls-1
    with ball[a]
      .acc = .frc/.mass
      .acc.y += g_acc
      .frc = vec2f(0, 0)
      .vel += .acc*dt
      .pos += .vel*dt
      .ang_acc = .trq/.I
      .trq = 0
      .ang_vel += .ang_acc*dt
      .ang += .ang_vel*dt
      .cos_ang = cos(.ang)
      .sin_ang = sin(.ang)
    end with
  next
  
end sub

sub DrawSceneToScreen()
  
  dim as integer a
  
  screenlock
    cls
    locate  3, (scrn_wid\8)-4: print using "###"; fps
    for a = 0 to num_balls-1
      with ball[a]
        circle (.pos.x, .pos.y), .radius, 0,,,1, f
        circle (.pos.x, .pos.y), .radius-2, .col,,,1, f
        Line(.pos.x, .pos.y)-(.pos.x+.cos_ang*.radius, .pos.y+.sin_ang*.radius), 0
      end with
    next
    for a = 1 to ubound(wall)
      with wall(a)
        Line(.pos(1).x, .pos(1).y)-(.pos(2).x, .pos(2).y), .col
      end with
    next
    
  screenunlock
  
end sub

sub brake()
  
  if timer < fps_timer then
    fps_counter += 1
  else
    fps = fps_counter
    fps_counter = 0
    fps_timer = timer+1
  end if
  
  do: sleep 1, 1: loop while timer-t0 < inv_rest_fps
  t0 = timer
  
end sub

Function Vec_Pnt_Lin(a1 As vec2f, a2 As vec2f, p1 As vec2f) As vec2f
  Dim As vec2f ab = a2-a1
  Dim As vec2f ap = p1-a1
  Dim As double t = dot(ap, ab)/magnitudesquared(ab)
  If t < 0 Then t = 0
  If t > 1 Then t = 1
  Return a1+ab*t
End function

And heres the "vec2f.bi" file for good measure:

Code: Select all

''*******************************************************************************
''		
''		Freebasic 2d floating point vector library
''		version 0.4b, may 2009, Michael "h4tt3n" Nissen, jernmager@yahoo.dk
''		
''		function syntax:
''		
''   	(return type) (function name) (argument type (, ...))
''		
''	 	function list:
''		
''   	vector absolute          	(vector)          - absolute value
''   	vector normal            	(vector)          - normal vector
''   	vector normalised        	(vector)          - normalised vector
''		vector normalisednormal		(vector)					-	normalised normal vector
''   	scalar magnitude         	(vector)          - magnitude
''   	scalar magnitudesquared		(vector)          - magnitude squared
''   	scalar distance          	(vector, vector)  - vector distance
''   	scalar distancesquared   	(vector, vector)  - vector distance squared
''   	scalar dot               	(vector, vector)  - dot product
''   	scalar dotnormal         	(vector, vector)  - normal dot product
''   	vector project           	(vector, vector)	-	vector projection
''		vector component					(vector, vector)	-	vector component
''		vector randomise					(scalar)					-	randomise in range +/- value
''		
''	 	all functions are defined both as members and non-members, and thus can 
''		be called in two different ways:
''
''		vector_a.function(vector_b),	function(vector_a, vector_b)
''		vector_a.function(parameter), function(vector_a, parameter)
''		
''*******************************************************************************

type float as double

''  2d float vector structure
type vec2f
	
  ''  variables
  as float x, y
	
  ''  constructor declarations
	declare constructor ()
  declare constructor (byval X as float, byval Y as float)
	
  ''  compound arithmetic member operator declarations
  declare operator += (byref rhs as vec2f)
  declare operator -= (byref rhs as vec2f)
  declare operator *= (byref rhs as vec2f)
  declare operator *= (byref rhs as float)
  declare operator /= (byref rhs as float)
  declare operator let (byref rhs as vec2f)
	
	''  member function declarations
  declare function absolute() as vec2f
  declare function normal() as vec2f 
  declare function normalised() as vec2f
  declare function normalisednormal() as vec2f
  declare function magnitude() as float
  declare function magnitudesquared() as float
  declare function distance(byref rhs as vec2f) as float
  declare function distancesquared(byref rhs as vec2f) as float
  declare function dot(byref rhs as vec2f) as float
  declare function dotnormal(byref rhs as vec2f) as float
  declare function project(byref rhs as vec2f) as vec2f
  declare function component(byref rhs as vec2f) as vec2f
  declare function randomise(byval rhs as float) as vec2f
  
end type

''  unary arithmetic non-member operator declarations
declare operator - (byref rhs as vec2f) as  vec2f

''  binary arithmetic non-member operator declarations
declare operator + (byval lhs as vec2f, byref rhs as vec2f) as vec2f
declare operator - (byval lhs as vec2f, byref rhs as vec2f) as vec2f
declare operator * (byval lhs as vec2f, byref rhs as vec2f) as vec2f
declare operator * (byval lhs as float, byref rhs as vec2f) as vec2f
declare operator * (byval lhs as vec2f, byval rhs as float) as vec2f
declare operator / (byval lhs as vec2f, byval rhs as float) as vec2f

''  non-member function declarations
declare function absolute (byref lhs as vec2f) as vec2f
declare function normal (byref lhs as vec2f) as vec2f
declare function normalised (byref lhs as vec2f) as vec2f
declare function normalisednormal(byref lhs as vec2f) as vec2f
declare function magnitude (byref lhs as vec2f) as float
declare function magnitudesquared (byref lhs as vec2f) as float
declare function distance (byval lhs as vec2f, byref rhs as vec2f) as float
declare function distancesquared (byval lhs as vec2f, byref rhs as vec2f) as float
declare function dot (byval lhs as vec2f, byref rhs as vec2f) as float
declare function dotnormal (byval lhs as vec2f, byref rhs as vec2f) as float
declare function project (byval lhs as vec2f, byref rhs as vec2f) as vec2f
declare function component(byval lhs as vec2f, byref rhs as vec2f) as vec2f
declare function trigonometry(byref lhs as float) as vec2f

''  constructors
constructor vec2f(): this.x = 0.0: this.y = 0.0: end constructor
constructor vec2f(byval x as float, byval y as float): this.x = x: this.y = y: end constructor

''  compound arithmetic member operators
operator vec2f.+= (byref rhs as  vec2f): x += rhs.x: y += rhs.y: end operator
operator vec2f.-= (byref rhs as  vec2f): x -= rhs.x: y -= rhs.y: end operator
operator vec2f.*= (byref rhs as  vec2f): x *= rhs.x: y *= rhs.y: end operator
operator vec2f.*= (byref rhs as  float): x *= rhs: y *= rhs: end operator
operator vec2f./= (byref rhs as  float): x /= rhs: y /= rhs: end operator
operator vec2f.let (byref rhs as  vec2f): x = rhs.x: y = rhs.y: end operator

''  member functions
function vec2f.absolute() as vec2f: return vec2f(abs(x), abs(y)): end function
function vec2f.normal() as vec2f: return vec2f(y, -x): end function 
function vec2f.normalised() as vec2f: return this/magnitude(): end function
function vec2f.normalisednormal() as vec2f: return this.normal()/magnitude(): end function
function vec2f.magnitude() as float: return sqr(magnitudesquared()): end function
function vec2f.magnitudesquared() as float: return this.dot(this): end function
function vec2f.distance(byref rhs as vec2f) as float: return sqr(distancesquared(rhs)): end function
function vec2f.distancesquared(byref rhs as vec2f) as float: return (x-rhs.x)*(x-rhs.x)+(y-rhs.y)*(y-rhs.y): end function
function vec2f.dot(byref rhs as vec2f) as float: return (x*rhs.x+y*rhs.y): end function
function vec2f.dotnormal(byref rhs as vec2f) as float: return this.dot(rhs.normal()): end function
function vec2f.project(byref rhs as vec2f) as vec2f: return (dot(rhs)/magnitudesquared())*rhs: end function
function vec2f.component(byref rhs as vec2f) as vec2f: return (dot(rhs)/rhs.magnitudesquared)*rhs: end function
function vec2f.randomise(byval rhs as float) as vec2f: return vec2f((rnd-rnd)*rhs, (rnd-rnd)*rhs): end function

''  unary arithmetic non-member operators
operator - (byref rhs as vec2f) as vec2f: return vec2f(-rhs.x, -rhs.y): end operator

''  binary arithmetic non-member operators
operator + (byval lhs as vec2f, byref rhs as vec2f) as vec2f: return vec2f(lhs.x+rhs.x, lhs.y+rhs.y): end operator
operator - (byval lhs as vec2f, byref rhs as vec2f) as vec2f: return vec2f(lhs.x-rhs.x, lhs.y-rhs.y): end operator
operator * (byval lhs as vec2f, byref rhs as vec2f) as vec2f: return vec2f(lhs.x*rhs.x, lhs.y*rhs.y): end operator
operator * (byval lhs as float, byref rhs as vec2f) as vec2f: return vec2f(lhs*rhs.x, lhs*rhs.y): end operator
operator * (byval lhs as vec2f, byval rhs as float) as vec2f: return vec2f(lhs.x*rhs, lhs.y*rhs): end operator
operator / (byval lhs as vec2f, byval rhs as float) as vec2f: return vec2f(lhs.x/rhs, lhs.y/rhs): end operator

''  non-member functions
function absolute (byref lhs as vec2f) as vec2f: return lhs.absolute(): end function
function normal (byref lhs as vec2f) as vec2f: return lhs.normal(): end function
function normalised (byref lhs as vec2f) as vec2f: return lhs.normalised(): end function
function normalisednormal(byref lhs as vec2f) as vec2f: return lhs.normalisednormal(): end function
function magnitude (byref lhs as vec2f) as float: return lhs.magnitude(): end function
function magnitudesquared (byref lhs as vec2f) as float: return lhs.magnitudesquared(): end function
function distance (byval lhs as vec2f, byref rhs as vec2f) as float: return lhs.distance(rhs): end function
function distancesquared (byval lhs as vec2f, byref rhs as vec2f) as float: return lhs.distancesquared(rhs): end function
function dot (byval lhs as vec2f, byref rhs as vec2f) as float: return lhs.dot(rhs): end function
function dotnormal (byval lhs as vec2f, byref rhs as vec2f) as float: return lhs.dotnormal(rhs): end function
function project (byval lhs as vec2f, byref rhs as vec2f) as vec2f: return lhs.project(rhs): end function
function component(byval lhs as vec2f, byref rhs as vec2f) as vec2f: return lhs.component(rhs): end function
function trigonometry(byref lhs as float) as vec2f: return vec2f(cos(lhs), sin(lhs)): end function
qbworker
Posts: 73
Joined: Jan 14, 2011 2:34

Post by qbworker »

My lib does bouncing off any line as well as inter-ball bouncing as well.
dodicat
Posts: 8227
Joined: Jan 10, 2006 20:30
Location: Scotland

Post by dodicat »

qbworker wrote:My lib does bouncing off any line as well as inter-ball bouncing as well.
Hi qbworker
I'm just fiddling around a bit with this stuff.
I've tried a bounce off a polynomial base, I'm just about to post it in the squares topic.
Hi h4tt3n
Thanks for the code, it is sophisticated and runs well.

Rollie~ wanted a kind of sphere mapping idea, so The thing is posted in the squares topic.

@ qbworker and h4tt3n
These collisions tend to run on tenderhooks if certain constraints are not applied.
I've applied no restraints, so an odd jam up may occur.
My problem is how many frames to run before re-enabling collisions.
I'll just post my thingy in squares now.
Post Reply