Marble / Ball rolling on mesh physics

Blitz3D Forums/Blitz3D Programming/Marble / Ball rolling on mesh physics

Rob Pearmain(Posted 2002) [#1]
It is simple.

I want a sphere to site on a mesh landscape and roll down the hills and up the other side etc realistically.

I am pulling my hair out, are there any examples anywhere?


BadJim(Posted 2002) [#2]
This may or may not work:

1) set full sliding collision.
2) implement inertia. Each frame you check the position against the position you recorded the previous frame. The difference is then used to shift the ball with TranslateEntity
3) implement gravity by pulling the ball down a set amount each frame. The inertia system will make it accelerate downwards.


Rob Pearmain(Posted 2002) [#3]
So, how would the intertia system work on step 2?

for example would I get entityx(),entityy(), and entityz() of the last position and subtract it from current values and add a fraction of this to the values for x,y, and z for tranlate entity?

Sorry, do you have an example of how you would implement interia.

In point 3, this would be applied to the 'y' axis, right?

i.e. translateentity ball,0,-gravity,0?

Could I implement step 2 and 3 in one translate entity?

Any help would be most appreciated


Rob(Posted 2002) [#4]
Use my simple Ball rolling code in code archives to allow you to get an idea how to roll it without affecting it.

From here, you'll be needing it's collision normals. The Driver demo will help here. Check it out on the CD. Once you've obtained the normals under your sphere (these will likely be averaged), you can use this "push" data to create yourself some fake gravity. Let me explain...

The steeper the angle of the normal, the greater the velocity of the ball if the ball is going down. If the ball is going up, the greater the angle, the greater the damping.

failing that, look up the other Rob. He's done that sphereracers game that has just these properties...


sswift(Posted 2002) [#5]
Sigh. :-)

Try searching the forums people! This is like the third time this week I've posted this. :-)


	; Check to see if the entity collided with the level last frame.
	Entity_Hit = EntityCollided(ballpos, COLLIDE_Level)

				
	; Calculate motion friction:


		; Calculate the entity's current velocity.
		Velocity# = Sqr(Vx#^2 + Vy#^2 + Vz#^2)


		; If the entity is not traveling fast enough to be motion blurred:
		If Velocity# < 6.0

			
			; If motion blur is on, turn it off:
			If Motion_Blur = True

				SPS_DELETE_CHILD_EMITTERS(ballpos)
				Motion_Blur = False

			EndIf


		EndIf
		

		; If the entity is moving, adjust it's velocity:
		If Velocity# > 0 


			; Calculate the direction vector.
			; The direction vector has a length of 1.
			Direction_X# = Vx# / Velocity#
			Direction_Y# = Vy# / Velocity#
			Direction_Z# = Vz# / Velocity#


			; Compute air friction.
			; Air friction is dependent on the speed of the entity, and will prevent it from accelerting forever.
			Air_Friction_Force# = AIR_FRICTION_CONSTANT# * Velocity#^2.0	
			Velocity# = Velocity# - (Air_Friction_Force# * Time_Delta_Sec#)

	
			; If the entity collided with the level, apply ground friction.
			If Entity_Hit > 0 

				; Compute ground friction.  Ground friction is not dependent on the speed of the entity.
				Velocity# = Velocity# - (GROUND_FRICTION_CONSTANT# * Time_Delta_Sec#)

			EndIf


			; Make sure the entity's velocity doesn't go below 0.
			; It is impossible to have a negative velocity in physics and "bad things" happen if you try to.
			If (Velocity# < 0) Then Velocity# = 0			


			; Convert the entity's velocity and direction back into a motion vector.
			Vx# = Direction_X# * Velocity#
			Vy# = Direction_Y# * Velocity#
			Vz# = Direction_Z# * Velocity#


			; If the entity collided with the level, make it bounce.
			If Entity_Hit > 0 

				; Calculate bounce:

	    			; Get the normal of the surface which the entity collided with.    
					Nx# = CollisionNX(ballpos, 1)
					Ny# = CollisionNY(ballpos, 1)
					Nz# = CollisionNZ(ballpos, 1)
		
				; Compute the dot product of the entity's motion vector and the normal of the surface collided with.
					VdotN# = Vx#*Nx# + Vy#*Ny# + Vz#*Nz#
							
				; Calculate the normal force.
					NFx# = -2.0 * Nx# * VdotN#
					NFy# = -2.0 * Ny# * VdotN#
					NFz# = -2.0 * Nz# * VdotN#

				; Add the normal force to the direction vector.
					Vx# = Vx# + NFx#
					Vy# = Vy# + NFy#
					Vz# = Vz# + NFz#
	
				; Do not allow the entity to move vertically.
					If Vy# > 0 Then Vy# = 0
	
			EndIf


		EndIf

	
	; Apply directional thrust:

		; If the entity collided with the level, apply directional thrust.
		;If Entity_Hit > 0 
		
			; Take thrust in object space, and translates it to an XYZ vector in world space.
			;TFormVector 0, 0, Thrust#, ballpos, 0

			; Add any thrust being applied this frame.
			; There's a very good reason why this is done AFTER the friction is calculated.
			; It involves inequalities in force cause by variable framerates.
			Vx# = Vx# + (Thrust_X# * Time_Delta_Sec#)
			Vz# = Vz# + (Thrust_Z# * Time_Delta_Sec#)

		;EndIf


	; Apply gravity:
		Vy# = Vy# - (GRAVITY# * Time_Delta_Sec#)


	; Move and rotate the entity:

		; We rotate the entity by the actual distance moved and not by the velocity because if we rotate according
		; to the velocity then the entity will roll when it's up against a wall and not moving.

		OldX# = NewX#
		OldZ# = NewZ#

		TranslateEntity ballpos, Vx#*Time_Delta_Sec#, Vy#*Time_Delta_Sec#, Vz#*Time_Delta_Sec#, True

		NewX# = EntityX#(ballpos, True)
		NewZ# = EntityZ#(ballpos, True)
		
		Mx# = (NewX# - OldX#)
		Mz# = (NewZ# - OldZ#)		

		; Rotate the entity the right amount for it's radius and the distance it has moved along the X and Z axis.
		; This is kinda a hack and only designed for rolling on planes but you won't notice the diffrence.
		XAngleAdjust# = (Mx# / BallRadius#) * (180.0/Pi) 
		ZAngleAdjust# = (Mz# / BallRadius#) * (180.0/Pi)
	    TurnEntity ball,ZAngleAdjust#,0,-XAngleAdjust#,True


Return




sswift(Posted 2002) [#6]
Btw, there's a line in there that says:

; Do not allow the entity to move vertically.
If Vy# > 0 Then Vy# = 0


You want to comment that out if you want the ball to be able to roll up hills.

Comenting it out will allow the ball to bounce when it hits a surface though. To counteract that what you might want to do is set the Y componenet of the normal force listed just above that line to 0.

The normal force is the force with which the ground pushes back when you push against it. In real situations you'd probably multiply it by some dampening factor... say 0.5, before adding it to the object's velocity.


sswift(Posted 2002) [#7]
You'll need this too:

Const GRAVITY# = 9.8
Const AIR_FRICTION_CONSTANT# = 0.1
Const GROUND_FRICTION_CONSTANT# = 3.0


Oh, and btw, the whole system assumes that one unit = one meter.

So if your world is in a diffrent scale than that you're gonna have to multiply the gravity by however many units are in a meter in your game, and when adding velocity to your entity you'll have to specify that in meters per second, but then multiply that by your world scale too. And you'll have to multuiply the ground friction constant too. And maybe the air friction constant though I'm not sure about that.

Ground friction is the force which slows an object down quickly... air friction is a force which is expoential and keeps the object from acelerating forever.

You can remove those, but you'll have to then check your entity's velocity to see if it's over a certain value.


Rob(Posted 2002) [#8]
Give that man a candy cigar! :)


bradford6(Posted 2002) [#9]
HERE IS A DEMO.

[CODE]
; thanks to SSWIFT for the nuts and bolts of this code

AppTitle "Ball Movement Demo ","goodbye"
; check to see if a graphics mode exist then
; if so set it, if not display error
If GfxMode3DExists (1024,768,16)
Graphics3D 1024,768
HidePointer
Else
RuntimeError "UPGRADE YOUR VIDEO CARD!"
EndIf



;-------------------------------------------------------------------------------------\
; GLOBALS
;-------------------------------------------------------------------------------------

; world vars
Global cam,lite,ball,world,ball_type,world_type

; Physics vars (thanks goes to sswift)
Const GRAVITY# = .0098
Const AIR_FRICTION_CONSTANT# = 0.01
Const GROUND_FRICTION_CONSTANT# = .0030
Global bullet_life=600

Global VX#,VY#,VZ#,NX#,NY#,NZ#,oldx#,newx#,oldz#,newz#,MX#,mz#
Global Velocity#,VdotN#,nfx#,nfy#,nfz#,Thrust_X#,Thrust_z#
Global Direction_X#,Direction_y#,Direction_z#
Global xAngleAdjust# ,ZAngleAdjust#,BallRadius#
Global Entity_Hit
Global jumping,player,vector_piv,balltex,BOX

Global midw=GraphicsWidth()/2,midh=GraphicsHeight()/2
Global speed#,lateral_speed#,cam_mx#,cam_my#,pyvel#
Global shot_timer
Type ball
Field entity,life,brush
Field vx#,vy#,vz#
Field oldx#,oldz#,newx#,newz#
End Type

SeedRnd MilliSecs
initialize_world()
For x= 1 To 32
b.ball= New ball
b\entity =CopyEntity(ball)
EntityType b\entity,ball_type
EntityRadius b\entity,.5

b\brush=CreateBrush()
BrushTexture b\brush,balltex
BrushColor b\brush,Rnd(0,255),Rnd(0,255),Rnd(0,255)
PaintEntity b\entity,b\brush
b\life=2
EntityAlpha b\entity,0

Next

; -------------------------------------------------------------------------------








Repeat ; * * * * beginning of loop

user_input()
update_ball()
fps_camera()

UpdateWorld
RenderWorld ; render the 3d scene

draw_crosshairs()
Flip ; flip the buffer


Until KeyDown(1)=1 ; * * * * end of loop
RuntimeError "goodbye"
ClearWorld
End
;=====================================================================================
; FUNCTIONS
;=====================================================================================
Function create_checker_tex(red1,green1,blue1,red2,green2,blue2,scale_u#,scale_v#)
texture_handle=CreateTexture(32,32)
SetBuffer TextureBuffer(texture_handle)
Color red1,green1,blue1
Rect 0,0,32,32
Color red2,green2,blue2
Rect 0,0,16,16,1
Rect 16,16,15,15,1
ScaleTexture texture_handle,scale_u#,scale_v#
SetBuffer BackBuffer()
Return texture_handle

End Function
;=====================================================================================

Function update_ball()
shot_timer=shot_timer-1
If shot_timer<1 Then shot_timer=0

For b.ball=Each ball

b\life=b\life-1
If b\life=1
HideEntity b\entity
PositionEntity b\entity,EntityX(cam),EntityY(cam),EntityZ(cam)

b\vx=0
b\vy=0
b\vz=0
b\life=0
EndIf
Entity_Hit = EntityCollided(b\entity, world_type)
For x=1 To CountCollisions(B\ENTITY)
If CollisionEntity(b\entity,x)=box
EntityColor box,Rnd(0,100),Rnd(0,100),Rnd(0,100)
EndIf
Next

Velocity# = Sqr(b\Vx#^2 + b\Vy#^2 + b\Vz#^2)
If Velocity# > 0 ; Calculate the direction vector. The direction vector has a length of 1.
If b\life>1 ; only if there is life left
Direction_X# = b\Vx# / Velocity#
Direction_Y# = b\Vy# / Velocity#
Direction_Z# = b\Vz# / Velocity#
; Compute air friction. ; Air friction is dependent on the speed of the entity, and will prevent it from accelerting forever.
Air_Friction_Force# = AIR_FRICTION_CONSTANT# * Velocity#^2.0
Velocity# = Velocity# - (Air_Friction_Force# )
; If the entity collided with the level, apply ground friction.
If Entity_Hit > 0 ; Compute ground friction. Ground friction is not dependent on the speed of the entity.
Velocity# = Velocity# - (GROUND_FRICTION_CONSTANT#)
EndIf
; Make sure the entity's velocity doesn't go below 0.
; It is impossible to have a negative velocity in physics and "bad things" happen if you try to.
If (Velocity# < 0) Then Velocity# = Velocity#+.001
; Convert the entity's velocity and direction back into a motion vector.
b\Vx# = Direction_X# * Velocity#
b\Vy# = Direction_Y# * Velocity#
b\Vz# = Direction_Z# * Velocity#
; If the entity collided with the level, make it bounce.
If Entity_Hit > 0
; Calculate bounce:
; Get the normal of the surface which the entity collided with.
Nx# = CollisionNX(b\entity, 1)
Ny# = CollisionNY(b\entity, 1)
Nz# = CollisionNZ(b\entity, 1)
; Compute the dot product of the entity's motion vector and the normal of the surface collided with.
VdotN# = b\Vx#*Nx# + b\Vy#*Ny# + b\Vz#*Nz#
; Calculate the normal force.
NFx# = -2.0 * Nx# * VdotN#
NFy# = -2.0 * Ny# * VdotN#
NFz# = -2.0 * Nz# * VdotN#
; Add the normal force to the direction vector.
b\Vx# = b\Vx# + NFx#
b\Vy# = b\Vy# + NFy#
b\Vz# = b\Vz# + NFz#
; Do not allow the entity to move vertically.
;If Vy# > 0 Then Vy# = 0

EndIf
EndIf
EndIf
; Apply directional thrust:
; If the entity collided with the level, apply directional thrust.
;If Entity_Hit > 0
; Take thrust in object space, and translates it to an XYZ vector in world space.
;TFormVector 0, 0, Thrust#, ballpos, 0
; Add any thrust being applied this frame.
; There's a very good reason why this is done AFTER the friction is calculated.
; It involves inequalities in force cause by variable framerates.
b\Vx# = b\Vx# + (Thrust_X# )
b\Vz# = b\Vz# + (Thrust_Z# )
;EndIf
; Apply gravity:
b\Vy# = b\Vy# - (GRAVITY# )
; Move and rotate the entity:
; We rotate the entity by the actual distance moved and not by the velocity because if we rotate according
; to the velocity then the entity will roll when it's up against a wall and not moving.
b\OldX# = b\NewX#
b\OldZ# = b\NewZ#
TranslateEntity b\entity, b\Vx#, b\Vy#, b\Vz#, True
b\NewX# = EntityX#(b\entity, True)
b\NewZ# = EntityZ#(b\entity, True)
Mx# = (b\NewX# - b\OldX#)
Mz# = (b\NewZ# - b\OldZ#)
; Rotate the entity the right amount for it's radius and the distance it has moved along the X and Z axis.
; This is kinda a hack and only designed for rolling on planes but you won't notice the diffrence.
XAngleAdjust# = (Mx# / BallRadius#) * (180.0/Pi)
ZAngleAdjust# = (Mz# / BallRadius#) * (180.0/Pi)
TurnEntity b\entity,ZAngleAdjust#,0,-XAngleAdjust#,True
Next

End Function
;=====================================================================================
Function user_input()

If KeyDown(17)=1 Or KeyDown(200) Then vz#=vz#+ .01
If KeyDown(30)=1 Or KeyDown(203) Then vx# = vx# - .01
If KeyDown(31)=1 Or KeyDown(208) Then vz# = vz# -.01
If KeyDown(32)=1 Or KeyDown(205) Then vx# = vx# + .01
If KeyDown(57)=1 Then VY#=VY#+.1

End Function
;=====================================================================================

Function initialize_world()
cam=CreateCamera() ; create a world camera
player=CreatePivot()
EntityRadius player,2
MoveEntity player,0,10,-10
vector_piv=CreatePivot()
;MoveEntity cam,0,10,-25 ; move the camera "back" 5 units

;MoveEntity lite,-10,10,-10
ball=CreateSphere(12) ; create a cube and call it blob
HideEntity ball
PositionEntity ball,-1,10,0 ; place the blob at world coordinate 0,0,3

AmbientLight(50,50,50)
lite=CreateLight(3) ; create a light for our world
PositionEntity lite,-20,20,-20
PointEntity lite,ball


balltex=create_checker_tex(0,150,0,0,0,100,.25,.25)




;EntityColor ball,255,0,0 ; color our blob, red, green , blue
world=CreateCube()
;RotateMesh world,0,0,0
ws=CreateSphere(12)
EntityTexture ws,create_checker_tex(0,50,0,100,75,0,.125,.125)

BOX=CreateCube()
EntityAlpha BOX,.8
RotateMesh BOX,0,45,0
PositionEntity box,10,5,10
ScaleEntity box,3,6,3
ScaleEntity ws,7,3,7
;MoveEntity ws,0,-5,0
ScaleEntity world,25,15,25
MoveEntity world,0,15,0
;AddMesh ws,world

EntityTexture world,create_checker_tex(200,200,0,0,0,0,.5,.5)

FlipMesh world

; SETUP COLLISIONS
ball_type=1 ; collision types
world_type=2
BallRadius#=.5
EntityType world,world_type
EntityType ws,world_type
EntityType box,world_type
EntityColor box,255,0,0
EntityType player,ball_type

Collisions ball_type,world_type,2,2 ;sphere-to-poly collisions between the ball and the world and set response to "STOP"


End Function

;=====================================================================================
Function fps_camera()



;If KeyDown(57) And jumping=0 ; SPACE to JUMP
; jumping=1
; pyvel#=.2
;EndIf

If KeyDown(17)=1 Or KeyDown(200) Then speed#=speed#+.005 +boost#
If KeyDown(30)=1 Or KeyDown(203) Then lateral_speed# = lateral_speed# - .004 +boost#
If KeyDown(31)=1 Or KeyDown(208) Then speed# = speed# -(.005 +boost#)
If KeyDown(32)=1 Or KeyDown(205) Then lateral_speed# = lateral_speed# + .004 +boost#

; FRICTION FOR SMOOTH MOVEMENT

lateral_speed#=lateral_speed#*.97
speed#=speed#*.95

PositionEntity cam,EntityX(player),EntityY(player)+1,EntityZ(player)

; CAMERA MOVEMENTS
cam_MY#=curvevalue#(MouseYSpeed(),cam_MY#,4 )
cam_MX#=curvevalue#(MouseXSpeed(),cam_MX#,4 )
TurnEntity cam,cam_MY#,0,0 ; turn camera up and down
TurnEntity player,0,-cam_mx,0 ; turn pivot left --right
RotateEntity cam,EntityPitch(cam),EntityYaw(player),0
MoveMouse midw,midh; Bring mouse to middle of screen for mouselook to work



If on_platform=0 Then pyvel#=pyvel#-gravity#
;pyvel#=pyvel#*.99

;move the player --- the camera in this case!
MoveEntity player,lateral_speed#,pyvel#,speed#

TranslateEntity player,pxvel#,0,pzvel#


pxvel#=pxvel#*.9
pzvel#=pzvel#*.9

If MouseDown(1)= 1 Then fire_cannon()

FlushKeys
FlushMouse

End Function

;=====================================================================================
Function curvevalue#(newvalue#,oldvalue#,increments# )
If increments>1 Then oldvalue#=oldvalue#-(oldvalue#-newvalue#)/increments
If increments<=1 Then oldvalue=newvalue
Return oldvalue#
End Function

;=====================================================================================
Function fire_cannon()



If shot_timer=0
For b.ball = Each ball
If b\life<1
ShowEntity b\entity
EntityAlpha b\entity,1

PositionEntity b\entity,EntityX(cam),EntityY(cam)+.4,EntityZ(cam)

PositionEntity vector_piv,EntityX(cam),EntityY(cam)+.4,EntityZ(cam)
RotateEntity vector_piv,EntityPitch(cam),EntityYaw(cam),EntityRoll(cam)
MoveEntity vector_piv,0,0,5

vectx#=EntityX(vector_piv)-EntityX(cam)
vecty#=EntityY(vector_piv)-EntityY(cam)
vectz#=EntityZ(vector_piv)-EntityZ(cam)



TFormVector vectx#,vecty#,vectz#,vector_piv,cam

b\vx=TFormedX() /8
b\vz=TFormedZ() /8

b\vy=TFormedY() /4

b\life=bullet_life
shot_timer=20
Exit
EndIf

Next



EndIf





End Function
;=====================================================================================
Function update_cannon()

shot_timer=shot_timer-1
If shot_timer<1 Then shot_timer=0

For b.ball = Each ball


Next
End Function

;=====================================================================================
Function draw_crosshairs()

Color 0,235,0
Rect midw-6,midh-6,12,12,0
Line midw-6,midh,midw+6,midh
End Function


;=====================================================================================
;=====================================================================================
;=====================================================================================
;=====================================================================================
;=====================================================================================
;=====================================================================================
;=====================================================================================
;=====================================================================================
;=====================================================================================
[/CODE]


Rob Pearmain(Posted 2002) [#10]
Swift, Bill
Your both my heroes, thank you so SO much


wmaass(Posted 2003) [#11]
sswift, or whoever else can help, in bradfords example the balls fire off in whatever direction the camera is facing. I need that. I've gone thru the demo thad bradford put up but I'm still not getting it. Can anyone sort it out in plain english?? Basically I need to my golf ball in my project to go the direction the camera is facing. Thanks in advance.


sswift(Posted 2003) [#12]
TFormVector 0, 0, Magnitude#, Camera, 0

Transforms a velcoity of Magnitude# from camera space pointing along it's z axis into world space giving you a vector pointing in the direction the camera is pointing.

Magnitude# is the force you want to apply. So if you want to impart an instant additional velocity of 10 meters per second, pass 10 as the magnitude.


wmaass(Posted 2003) [#13]
Works like a charm! Thanks!


rsbrowndog(Posted 2003) [#14]
I'm really hoping that sswift or bradford6 look in here and can help me!

I'm using a slightly modified version of the code posted above by bradford6 in my game, to fire cannonballs out of cannons. However, when I rotate the cannon to fire in any direction OTHER than straight down the Z axis I get an inconsistent (lower) velocity, meaning shots go short.

Here is a snip of my code, can anyone tell me what I am doing wrong?

    PositionEntity vector_piv,EntityX(ball[pnum],1),EntityY(ball[pnum],1),EntityZ(ball[pnum],1),1
    RotateEntity vector_piv,EntityPitch(ball[pnum],1)-(cannon_loft[pnum]/4),EntityYaw(ball[pnum],1),EntityRoll(ball[pnum],1),1

    hitpower#=cannon_powr[pnum]*shot_power[pnum]
    MoveEntity vector_piv,0,0,hitpower#

    vectx#=EntityX(vector_piv)-EntityX(ball[pnum])
    vecty#=EntityY(vector_piv)-EntityY(ball[pnum])
    vectz#=EntityZ(vector_piv)-EntityZ(ball[pnum])

    TFormVector vectx#,vecty#,vectz#,vector_piv,ball[pnum]
     
    ball_vx[pnum]=TFormedX() /8
    ball_vz[pnum]=TFormedZ() /8
    ball_vy[pnum]=TFormedY() /4


Please?

Cheers,

Ryan


sswift(Posted 2003) [#15]
Dividing Y by a different amount?


rsbrowndog(Posted 2003) [#16]
sswift,

Thanks for responding!

I've changed that (it was as per the code that Bill posted above) and that has sorted out the initial velocity issue, but I am still getting different distances dependent on which direction the ball is fired in, but I guess that must now be down to something in the update_ball() part of the function.

I will have a further poke around and see what I can find...

Cheers,

Ryan


scooter43(Posted 2003) [#17]
Does everything (position, rotation, x,y,z) have to be in global coordinate system?

That could definatly be a problem...

hope that helps

scooter43


rsbrowndog(Posted 2003) [#18]
scooter43,

Thanks for the suggestion...

TBH... I don't know. It wasn't working when I first adapted the code and I think that is because I used ScaleEntity on my balls. Putting in the global flags fixed that problem and got it working.

Anyway, I've solved it now with a slightly different approach, when I've got time I'll post it here for others to use!

Cheers,

Ryan


Tracer(Posted 2003) [#19]
Ok, another question for sswift :)

Say i want to give the ball a certain "power" when it's 'fired' ... ie: take a cannon ball and if you use so and so much gun powder, it would fly this far, use less and it will fly less far.. I can't seem to find this in your code.

Tracer


Bolo_Loco(Posted 2003) [#20]
Hi !

( Function fire_cannon()....

....MoveEntity vector_piv,0,0,5< Change this Value
)

Bolo


Tracer(Posted 2003) [#21]
Kewl, never even saw that... thanks.

Tracer


rsbrowndog(Posted 2003) [#22]
Argh!

Turns out I am not as smart as I thought I was and my solution created more problems than it fixed. So I am still having problems, if anyone else has any suggestions!?

Cheers,

Ryan


rsbrowndog(Posted 2003) [#23]
I finally found the problem!

It was this line:

RotateEntity vector_piv,EntityPitch(ball[pnum],1)-(cannon_loft[pnum]/4),EntityYaw(ball[pnum],1),EntityRoll(ball[pnum],1),1


Which needed to be changed to this:

RotateEntity ball[pnum],EntityPitch(ball[pnum],1)-(cannon_loft[pnum]/4),EntityYaw(ball[pnum],1),EntityRoll(ball[pnum],1),1
RotateEntity vector_piv,EntityPitch(ball[pnum],1)-(cannon_loft[pnum]/4),EntityYaw(ball[pnum],1),EntityRoll(ball[pnum],1),1


Cheers,

Ryan