Simple Game Physics Question

Blitz3D Forums/Blitz3D Programming/Simple Game Physics Question

Ok, I know that title is a bit of an oxymoron, but I'm sure someone can help me out here...

What I want is a simple, built in physics system (in other words, no tokomak, no DLL's, no pain), similar to that used in unreal (original engine, not karma or anything). Basically, I want to be able to give an object a push from things like, say an explosion, that will fling entities into the air and basically all the good chaotic stuff we remember from Unreal/UT. I'm sort of familiar with the concept of giving an impulse with a certain amount of force, from a certain position (explosion position), and having that effect the entity. Now, I know this isn't nuclear fusion science, I've seen it in games all the way back to Marathon (which was released in, what, 1992?). Maybe even before that. If anyone can give me some advice on how to code this, and maybe some references to articles on the subject, I would be most grateful. Math was never a strong point for me...

http://www.blitzbasic.com/Community/posts.php?topic=34953

Yikes...I have a lot of reading to do tonight... Looks like this is exactly what I was looking for, thanks for the assistance!

Actually, I'm using Tokamak...
I'm a 15 (soon 16) year old programmer and I think it's the easyest lib I've ever used! I'm currently writing a ragdoll editor for my current project... Just sit down and read some of the tutorials.. it's suprisingly easy!

-just my suggestion

Wow!

sswift is a genius. Just read that entire linked thread. :) Nice stuff.

Wow, I'm in pain from reading that. I can understand some of it...but this stuff is way over my head. I think I'll be needing some help with this stuff...

Ok, I had an idea. I created a simple test program (no media, and in a single file), with a similar framework to my program. If I posted it on here, think you could "fill in the blanks" on how to make it act like a typical FPS engine? Basically, it has an ellipsoid player, a large hollow cube for a room, a staircase, and a ramp. Very bare bones, but I think this could be very useful to me, and anyone else wanting to see how it's done in a very small scale test. If anyone wants to take up the challenge, let me know and I'll post it. Just for the record, how does one create a code box in your posts? I used to know the command, but I never used it before and I don't remember it...

(codebox)

code her

(/codebox)

or for the one with no scroll bars..

(code)

code here

(/code)

Replace () with []

:o)

Ok, well here's what I put together.


; ------------------------------------------------------------------
; GameCore -- support@...
; ------------------------------------------------------------------
; The basics of a frame-limited Blitz 3D game, ready to rock
; Adapted from Mark Sibly's code
; ------------------------------------------------------------------

; ------------------------------------------------------------------
;	Game's frames-per-second setting
; ------------------------------------------------------------------

Global gameFPS = 60

; ------------------------------------------------------------------
;	Open 3D display mode
; ------------------------------------------------------------------

Graphics3D(800,600,32)
MoveMouse(20,20)
HidePointer()

; ------------------------------------------------------------------
; Camera setup
; ------------------------------------------------------------------

Global Cam = CreateCamera()
CameraViewport(Cam,0,0,GraphicsWidth(),GraphicsHeight())
PositionEntity(Cam,0,0,-50)
CameraFogMode(Cam,1)
CameraFogRange(Cam,100,1000)

Light = CreateLight()
LightRange(Light,5000)

; ------------------------------------------------------------------
; General setup
; ------------------------------------------------------------------

SeedRnd(MilliSecs())


;Since I nixed the includes to handle my player data, I'll just
;pop this in here for ease of testing.

Type Entity
Field Entity
End Type

; ------------------------------------------------------------------
; Global setup
; ------------------------------------------------------------------

;Collision data
;For the simplicity of this test, we only need two.

Const CT_MAP = 1
Const CT_ENTITY = 2

Collisions(CT_ENTITY,CT_MAP,2,3)

Global Player.Entity = CreateEntity(0,0,0)

CreateMap()

; ------------------------------------------------------------------
;	Frame limiting code setup
; ------------------------------------------------------------------

framePeriod = 1000 / gameFPS
frameTime = MilliSecs () - framePeriod

Repeat

	; --------------------------------------------------------------
	; Frame limiting
	; --------------------------------------------------------------

	Repeat
		frameElapsed = MilliSecs () - frameTime
	Until frameElapsed

	frameTicks = frameElapsed / framePeriod
	
	frameTween# = Float(frameElapsed Mod framePeriod) / Float(framePeriod)

	; --------------------------------------------------------------
	; Update game and world state
	; --------------------------------------------------------------
	
	For frameLimit = 1 To frameTicks
	
		If frameLimit = frameTicks Then CaptureWorld
		frameTime = frameTime + framePeriod
		
		UpdateEntity()
		
		UpdateWorld()
	
	Next
	
	; --------------------------------------------------------------
	; Draw 3D world
	; --------------------------------------------------------------

	RenderWorld frameTween
	
	; --------------------------------------------------------------
	; Show result
	; --------------------------------------------------------------
	
	Flip

Until KeyHit (1)
End

;These functions are usually placed in includes, but this will do.

;A simple function to create the test entity.

Function CreateEntity.Entity(X,Y,Z)

	e.Entity = New Entity
	e\Entity = CreateSphere()
	EntityAlpha(e\Entity,0.5)
	EntityColor(e\Entity,128,0,0)
	ScaleEntity(e\Entity,10,20,10)
	PositionEntity(e\Entity,X,Y,Z)
	EntityType(e\Entity,CT_ENTITY)
	EntityRadius(e\Entity,10,20)
	
	EntityParent(Cam,e\Entity) ;UGLY HACK FOR TESTING ONLY!
	
	Return(e)

End Function

;Normally this would be my UpdateEntities() function, but since
;we're just working with one, might as well strip it down.

Function UpdateEntity()

	;Basic gravity, not realistic looking at all...
	TranslateEntity(Player\Entity,0,-2,0)
	
	; Simple move commands, will need to be replaced with thrust.
	If KeyDown(200) Then
		MoveEntity(Player\Entity,0,0,5)
	ElseIf KeyDown(208) Then
		MoveEntity(Player\Entity,0,0,-2)
	EndIf
	
	;I use mouselook in my actual game, but for this test this
	;will suffice.
	If KeyDown(203) Then
		TurnEntity(Player\Entity,0,2,0)
	ElseIf KeyDown(205) Then
		TurnEntity(Player\Entity,0,-2,0)
	EndIf

End Function

;This just makes us a map out of cubes to test out the physics.

Function CreateMap()

	MapPart = CreateCube()
	ScaleEntity(MapPart,1000,200,1000)
	PositionEntity(MapPart,0,100,0)
	FlipMesh(MapPart)
	EntityType(MapPart,CT_MAP)
	
	MapPart = CreateCube()
	ScaleEntity(MapPart,100,10,200)
	PositionEntity(MapPart,500,-100,0)
	EntityType(MapPart,CT_MAP)
	
	MapPart = CreateCube()
	ScaleEntity(MapPart,100,20,200)
	PositionEntity(MapPart,600,-100,0)
	EntityType(MapPart,CT_MAP)
	
	MapPart = CreateCube()
	ScaleEntity(MapPart,100,30,200)
	PositionEntity(MapPart,700,-100,0)
	EntityType(MapPart,CT_MAP)
	
	MapPart = CreateCube()
	ScaleEntity(MapPart,100,40,200)
	PositionEntity(MapPart,800,-100,0)
	EntityType(MapPart,CT_MAP)
	
	MapPart = CreateCube()
	ScaleEntity(MapPart,100,50,200)
	PositionEntity(MapPart,900,-100,0)
	EntityType(MapPart,CT_MAP)
	
	MapPart = CreateCube()
	ScaleEntity(MapPart,100,60,200)
	PositionEntity(MapPart,1000,-100,0)
	EntityType(MapPart,CT_MAP)
	
	MapPart = CreateCube()
	ScaleEntity(MapPart,100,10,500)
	TurnEntity(MapPart,20,0,0)
	PositionEntity(MapPart,-500,-100,0)
	EntityType(MapPart,CT_MAP)

End Function



What I'm looking to do is give the player realistic gravity, the ability to give objects like the player a push from outside forces (to knock them around with explosions and such), and the ability to have objects rebound from surfaces. If anyone would like to help out here, major thanks are in order. Free copy of the finished game and name in credits too of course.

Ok, looks like I have bouncing object physics figured out. I'll try adding some bouncing objects to my demo and see how that works.

This is what worklogs are for...

This isn't a worklog, it's a help request...

Ok, last bit of help I need...I have bouncing objects working, but I'm not quite sure about one point. Since instead of using moveentity I'm adjusting x, y, and z velocities, I have no clue how to decide exactly what direction the object is aimed at when I create it. I know the exact direction, as in rotateentity direction, that I want the object to intitially move in, how do I convert that into the needed x, y, and z velocity?

After rotating your object, transform it's velocity vector, which you want to point in the direction of your object, which is where the object's Z axis points, from object space to global space:

TFormVector 0, 0, Velocity#, Entity, 0

Ok, sorry I'm so dense on this issue...3D math is not my strongest point. I'm just going to post the code I'm using for bouncing objects (I found this somewhere else on the forums and modified it to add gravity, but even that doesn't work too well). If someone can please tell me how to make them start moving in the direction they are pointing, I'd be very grateful. Also, if someone can tell me where I'm going wrong on gravity...when balls run out of y velocity they stick to the ground like glue, even though they should have some x and z left in them.

Graphics3D 640,480,16,1

SeedRnd(MilliSecs)

Const C_BALL = 1
Const C_CUBE = 2
Const BALLS = 2
Const SPEED# = 2

Global camera,cube
Global light =CreateLight()

Type ball
	Field entity
	Field xvel#,yvel#,zvel#
End Type

Collisions C_BALL, C_BALL,1,1
Collisions C_BALL, C_CUBE,2,1

MakeStuff()

While Not KeyDown(1)
	
	If KeyHit(57) Then
		For b.ball = Each Ball
			b\xvel = Rnd(-SPEED,SPEED)
			b\yvel = Rnd(SPEED,(SPEED*5))
			b\zvel = Rnd(-SPEED,SPEED)
		Next
	EndIf
	UpdateBalls()
	UpdateWorld()
	RenderWorld()
	Flip
	
Wend

;==============================================
;==============================================
;==============================================

Function MakeStuff()
	
	;cube
	cube = CreateCube()
	ScaleMesh cube,30,30,30
	FlipMesh cube
	EntityColor cube,50,50,200
	EntityType cube,C_CUBE
	UpdateNormals cube
	
	cube2 = CreateCube()
	ScaleMesh(cube2,5,5,5)
	PositionEntity(cube2,0,-10,0)
	EntityColor(cube2,255,0,0)
	EntityType(cube2,C_CUBE)
	UpdateNormals(cube2)
	
	;camera
	camera = CreateCamera()
	PositionEntity camera,0,0,60
	PointEntity camera, cube
	
	;template for balls
	temp = CreateSphere()
	EntityType temp,C_BALL
	UpdateNormals temp
	EntityShininess temp,1
	HideEntity temp
		
	;balls
	For l = 1 To BALLS
		b.ball = New ball
		b\xvel = Rnd(-SPEED,SPEED)
		b\yvel = Rnd(-SPEED,SPEED)
		b\zvel = Rnd(-SPEED,SPEED)
		b\entity = CopyEntity( temp )
		PositionEntity b\entity,Rnd(-30,30),Rnd(-30,30), Rnd(-30,30)
		EntityColor b\entity,Rand(100,255),Rand(100,255),Rand(100,255)
		radius# = Rnd( 1,4 )
		ScaleEntity b\entity,radius,radius,radius
		EntityRadius b\entity,radius
	Next

End Function

;==============================================
;==============================================
;==============================================

Function UpdateBalls()

	For b.ball = Each ball
	
		For i = 1 To CountCollisions(b\entity)
			HandleBounce(b,i)
		Next
		
		b\yvel = b\yvel - 0.1
		
		TranslateEntity b\entity,b\xvel,b\yvel,b\zvel
		
	Next

End Function

;==============================================
;==============================================
;==============================================

Function HandleBounce(b.ball,i)
	
	; Get the normal of the surface collided with. 
	Nx# = CollisionNX(b\entity, i) 
	Ny# = CollisionNY(b\entity, i) 
	Nz# = CollisionNZ(b\entity, i) 
			
	; Compute the dot product of the ball's motion vector and the normal of the surface collided with. 
	VdotN# = b\xvel*Nx + b\yvel*Ny + b\zvel*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 motion vector. 
	b\xvel = b\xvel + NFx
	b\yvel = (b\yvel + NFy) * 0.8
	b\zvel = b\zvel + NFz
	
End Function


And if there's a simpler/more efficient way than what I'm doing, please let me know.

I don't have time to correct your code but here is code for my own game with a ball that can bounce properly off surfaces, and is afftected by gravity and air friction. I haven't incldued the include files, but the physics code is all there, so you can just copy that and modify it to suit your needs.


Include "..\functions\scancodes.bb"
Include "..\shadow system\Swift Shadow System - 053.bb"
Include "..\particle system\Swift_Particle_System-036.bb"
Include "..\functions\mesh_optimizer.bb"
Include "..\functions\media.bb"
Include "..\functions\level-tiled.bb"
Include "..\functions\calculate_normals.bb"
Include "..\functions\removetagfromstring.bb"
Include "..\functions\cel_shade.bb"
Include "..\functions\fstr-float2string.bb"
Include "..\functions\addmeshtosurface.bb"
Include "..\functions\savescreenshot.bb"
Include "..\functions\createquad.bb"
Include "tag_system-002.bb"


Global info1$="Boing!"
Global info2$=""
Global info3$=""
Global info4$="Press TAB in game for instructions."
Include "start.bb"


; -------------------------------------------------------------------------------------------------------------------
.Constants

	Const SHAREWARE = False

	Const FPS=72

	Const GRAVITY# 							= 9.8*2
	Const AIR_FRICTION_CONSTANT# 			= 0.1  ; 0.2
	Const GROUND_FRICTION_CONSTANT# 		= 0.5  ;3.0

	; This is the number of times to run the physics each frame.
	; Increasing this value will make the physics more stable... ie, when you're in a corner you won't bounce all
	; over the place... you'll just sit against it properly.
	Const PHYSICS_DIVIDER = 4
	
	Const X = 0
	Const Y = 1
	Const Z = 2
	Const XYZ = 3
	
	Const R = 0
	Const G = 1
	Const B = 2
	Const RGB = 3

	Const TEX_COLOR		= 1
	Const TEX_ALPHA		= 2
	Const TEX_MASK		= 4
	Const TEX_MIPMAP	= 8	
	Const TEX_UCLAMP	= 16
	Const TEX_VCLAMP	= 32
	Const TEX_UVCLAMP	= TEX_UCLAMP + TEX_VCLAMP
										
	Const LEVEL_WIDTH	= 64
	Const LEVEL_HEIGHT	= 64


	Const COLLIDE_GameGrid	= 1
	Const COLLIDE_Level 	= 2
	Const COLLIDE_Ball		= 3


	Const BALL_START_HEIGHT# = 0.25


	; Font stuff:

		; Maximum number of fonts the game can utilize.
		Const MAX_FONTS = 4
		
		; Spacing distance between the characters, assuming character width of 1.0
		Const FONT3D_KERNING# = 0.25

				
; -------------------------------------------------------------------------------------------------------------------
.Types

	;Type Actor
	
	;	Field Mesh
	;	Field Velocity#[XYZ]	
	;	Field Collision_Radius#			
	;	Field Health

	;End Type


	Type Player

		Field Name$
		Field Avatar

		Field Input_Up
		Field Input_Down
		Field Input_Left
		Field Input_Right
		Field Input_Fire1
		Field Input_Fire2

		Field Score
 
	End Type					
			
				
; -------------------------------------------------------------------------------------------------------------------
.Variables

	Show_Debug_Data	 	= 1									; Defines whether to show the FPS and other information such as the current number of polygons in the world.  0 = no data, 1 = fps only, 2 = all data

	; These are the default keyboard definitions.
	KEY_Up						= KEY_UPARROW
	KEY_Down					= KEY_DOWNARROW
	KEY_Left					= KEY_LEFTARROW
	KEY_Right					= KEY_RIGHTARROW
	KEY_Fire1					= KEY_LEFTCTRL
	KEY_ToggleDebugData			= KEY_TAB
	KEY_ExitGame				= KEY_ESC
	KEY_Screenshot				= KEY_F8	
	KEY_NextBlock				= KEY_PGDN
	KEY_PreviousBlock			= KEY_PGUP
	KEY_EditSavelevel			= KEY_S
	KEY_EditLoadlevel			= KEY_L
	KEY_EditResetLevel			= KEY_R
	KEY_ResetBall				= KEY_ENTER
	KEY_ToggleShadows			= KEY_F5
	KEY_RecalculateLevelNormals = KEY_F4
	KEY_ToggleMotionBlur		= KEY_SPACE
	
	Dim Cam(TOTAL_CAMERAS)									; This is a list of entity handles for all the cameras in the world.
	Camera_Active = 0	 									; This is the currently active camera's entity handle.
	Camera_Target = 0										; This is the object which the currently active camera should point at.


	; These are all the valid characters which are in the game's fonts.  Space MUST be last, because it does not actually have a mesh file associated with it.
	Global CHARACTER_SET$ = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?.()[]/\:,'+-@" + Chr$(34) + Chr$(32)

	Global Fonts_Loaded = 0									; Dunno why you'd ever need to know this in the game, but here it is.
	Dim Font_Mesh(MAX_FONTS, 128)							; This is the mesh for each ascii character in the fonts.  Only the lower 128 ascii characters are supported.
	Dim Font_Mesh_Width#(MAX_FONTS, 128)					; This is one half the width of each character in the font.
	
	Player_Thrust# = 16.0		
	
	Player_Friction_Stick_Time = 100						; This is the amount of time (in milliseconds) which the player will be considered to be sticking to the level after it initally collides with it.  This is to avoid issues where tiny bounces of the non-deforming ball constantly bring it into and out of contact with the ground every other frame or two.  A real ball would stay in contact with the ground when rolling because it deforms when touching the ground.
															; A more realistic physics system would define an amount which the ball deforms when it hits, and thus how much energy it stores and returns when it rebounds, as well as how long it stays in contact with the ground when doing so.  A harder hit or greater gravity would mean more stick.
	
	Edit_Mode = False										; Edit mode is off by default.

	; Stuff for generating the shadows:
	;Global ShadowTris = 0

	
					
; -------------------------------------------------------------------------------------------------------------------
.Main

	Gosub Initialize_Game
	
	Frame_Period = Ceil(1000.0/Float(FPS))
	Time_Now = MilliSecs()
	
	Repeat

		; Update the game's timer.

			Time_Old = Time_Now							; Save the time at which the last frame began.
	
			; Repeat until the time since the last frame is greater than the minimum time allowed between frames.
			; This is how we limit the framerate to 60fps or less to keep the game smooth.
			Repeat

				Time_Now   = MilliSecs()
				Time_Delta = Time_Now - Time_Old

			Until Time_Delta >= Frame_Period
			
			; Calculate the time the last frame took, in fractions of a second, and divide by
			; the number of times we run the physics each frame.
			Time_Delta_Sec# = (Float(Time_Delta) / 1000.0) / Float(PHYSICS_DIVIDER)
	
		Gosub Gather_Input								; Gather input from all sources, keyboard, joystick, net, etc.
		
		; Do physics several times per frame to make them smoother.
		For Loop_Physics = 1 To PHYSICS_DIVIDER
			Gosub Animate_World							; Move entities, perform AI, etc.
			UpdateWorld									; Check for collisions.
		Next
			
		SPS_Update(Time_Delta)		  					; Update the particles.
		Update_Shadows(Camera_Active)					; Update the shadows.
			
		RenderWorld										; Render 3D scene.
		Gosub Render_2D_Overlay							; Render 2D graphics over finished 3D scene.
		Flip											; Swap the front and back buffers.

		
	Until KeyHit(1)

End


; -------------------------------------------------------------------------------------------------------------------
; This subroutime initializes the game.
; -------------------------------------------------------------------------------------------------------------------
.Initialize_Game

	; Turn off hardware multitexturing because it causes problems on some systems.
	HWMultiTex False 
	WBuffer True

	; Show the pointer when the mouse is over the game window.  Only works in windowed mode.
	ShowPointer

	; Set ambient light level.
	Shadow_Ambient_Light_R = 35*3
	Shadow_Ambient_Light_G = 35*3
	Shadow_Ambient_Light_B = 40*3
	AmbientLight Shadow_Ambient_Light_R, Shadow_Ambient_Light_G, Shadow_Ambient_Light_B

	LIGHT_Sun_Down = CreateLight(1)
	LightColor LIGHT_Sun_Down, 250, 250, 220
	RotateEntity LIGHT_Sun_Down, 90, 0, 0
	
	LIGHT_Sun_Left = CreateLight(1)
	LightColor LIGHT_Sun_Left, 50, 50, 44
	RotateEntity LIGHT_Sun_Left, 0, 90, 0

	LIGHT_Shadow = CreateLight(1)
	LightColor LIGHT_Shadow, -23, -23, -23
	RotateEntity LIGHT_Shadow, -90, 0, 0

	; Specify the lights which cast shadows, and set their range.
	FakeLight = CreatePivot()
	PositionEntity FakeLight, 0, 10000, 0, True
	Cast_Light(FakeLight, 200000)


	; Create the primary local player.
		Local_Player.Player = New Player

	Gosub Initialize_Cameras
	Gosub Load_Media
	Gosub Initialize_Particle_System
	
	
	; Set up collision between the level and the ball:
		EntityRadius ballpos, BallRadius#
		EntityType ballpos, COLLIDE_Ball
		Collisions COLLIDE_Ball, COLLIDE_Level, 2, 2 


	; Load and construct the level:
		Load_Level(255, False)
		
			
	Receive_Shadow(Level_Avatar)

	;Cel_Shade(Level_Avatar)
			
Return


; -------------------------------------------------------------------------------------------------------------------
; This subroutine loads and/or creates all objects and textures required to play the game.
; -------------------------------------------------------------------------------------------------------------------
.Load_Media
	
	Load_Textures()
	Load_Brushes()
	Load_Meshes()
	Load_Blocks()

	;Font3D_Load(1)

	; Create a plane for the level editor to use to determine which coordinates the mouse is pointing at.
	; Make the plane invisible by adjusting the alpha rather than hiding it so that collision still works on it.
	MESH_GameGrid = CreateQuad(0, 0, LEVEL_HEIGHT, 0, 0, LEVEL_WIDTH, 0, LEVEL_HEIGHT, 1, 0, LEVEL_WIDTH, 0, 0, 1, 1, 0, 0, 0, 0, 1)	
	
	EntityType MESH_GameGrid, COLLIDE_GameGrid
	EntityPickMode MESH_GameGrid, 2
	EntityAlpha MESH_GameGrid, 0
	PositionEntity MESH_GameGrid, 0, 0.5, 0

	
	TEX_Edit_Cube = LoadTexture("gfx\tex-009.bmp")

	MESH_Edit_Cube = CreateCube()
	ScaleEntity MESH_Edit_Cube, 0.6, 0.6, 0.6
	EntityTexture MESH_Edit_Cube, TEX_Edit_Cube
	EntityAlpha MESH_Edit_Cube, 0.75
	EntityBlend MESH_Edit_Cube, 3
	;EntityFX MESH_Edit_Cube, 16

	; Create the editing grid and cursor cube:

		MESH_Edit_Cube2 = CreateCube()
		ScaleEntity MESH_Edit_Cube2, 0.56, 0.56, 0.56
		EntityColor MESH_Edit_Cube2, 0, 0, 0
		EntityAlpha MESH_Edit_Cube2, 0.25
		EntityBlend MESH_Edit_Cube2, 1
		EntityParent MESH_Edit_Cube2, MESH_Edit_Cube
		EntityFX MESH_Edit_Cube2, 16

		MESH_Grid = CreateQuad(0, 0, LEVEL_HEIGHT, 0, 0, LEVEL_WIDTH, 0, LEVEL_HEIGHT, LEVEL_WIDTH, 0, LEVEL_WIDTH, 0, 0, LEVEL_WIDTH, LEVEL_HEIGHT, 0, 0, 0, 0, LEVEL_HEIGHT)
		PositionEntity MESH_Grid, 0, -0.025, 0	
		TEX_Edit_Grid = TEX_Edit_Cube
		EntityTexture MESH_Grid, TEX_Edit_Grid
		EntityFX MESH_Grid, 1+8
		EntityAlpha MESH_Grid, 0.5
		EntityBlend MESH_Grid, 3		
	
		; If edit mode is off by default, hide the editing entities initially.
		If Edit_Mode = False
			HideEntity MESH_Edit_Cube
			HideEntity MESH_Grid			
		EndIf

	
	; Set up the ball:

		BallRadius# = 0.25
	
		ballpos=CreatePivot()
		ball=CreateSphere(16,ballpos)
		ScaleMesh ball,BallRadius#,BallRadius#,BallRadius#

		Local_Player\Avatar = ballpos
		Camera_Target = Local_Player\Avatar	
		TARGET_CameraPivot = CreatePivot()
		EntityParent TARGET_CameraPivot, Local_Player\Avatar
		TranslateEntity TARGET_CameraPivot, 0, 0, -4

		PositionEntity ballpos, 0.5, BALL_START_HEIGHT#, 0.5

		TEX_Ball = LoadTexture("gfx\sphere.bmp")
		;TEX_Ball = LoadTexture("tex-035.bmp")
		;TEX_Ball = LoadTexture("cell-001.tga", 1+2+8+64)
		EntityTexture ball,TEX_Ball


		; Set the ball's initial location for the code which rotates the ball only when it's actually moving.
		NewX# = EntityX#(ballpos, True)
		NewY# = EntityY#(ballpos, True)
		NewZ# = EntityZ#(ballpos, True)


	; Set up the ball's shadow:	

		TEX_Ball_Shadow = LoadTexture("gfx\tex-020.bmp", TEX_UVCLAMP)
		TextureBlend TEX_Ball_Shadow, 3

		; Create a mesh for the shadow.  We delete it each frame before creating a new one.  This prevents a crash.
		;MESH_Shadow = CreateMesh()
	
		;Cast_Shadow(ball)
		Cast_Textured_Shadow(ball, TEX_Ball_Shadow)

		;Cel_Shade(ball)		

	; Set up the backdrop:
	
;		MESH_Backdrop = CreateSprite2(Camera_Active)

		; Center the sprite in the screen.
;		Sprite2D MESH_Backdrop,320,240,1

		; Scale the sprite to fill the screen.
;		ScaleSprite2 MESH_Backdrop, 640, 480 

		; Draw background behind everything else.
;		EntityOrder MESH_Backdrop,1


	; Set up the sky:

;		MESH_Sky = MESH_Backdrop
;		TEX_Sky = LoadTexture("flatsky03.bmp")
;		EntityTexture MESH_Sky, TEX_Sky
;		EntityFX MESH_Sky, 1+8


Return


; -------------------------------------------------------------------------------------------------------------------
; This subroutine gathers local and remote user input.
; -------------------------------------------------------------------------------------------------------------------
.Gather_Input

	; Check for non-gameplay related local input which we can deal with immediately.

		; Take a screenshot.	
			If KeyHit(KEY_Screenshot)
				SaveScreenshot()
			EndIf	


		; Toggle through various styles of displaying debug data
			If KeyHit(KEY_ToggleDebugData) 
				
				Show_Debug_Data = Show_Debug_Data + 1
				If (Show_Debug_Data > 3) Then Show_Debug_Data = 0

				If (Show_Debug_Data = 2)
				
					ShowEntity MESH_Grid
					;EntityAlpha MESH_Grid, 1
					
					ShowEntity MESH_Edit_Cube
					;EntityAlpha MESH_Edit_Cube, 1
					
					Edit_Mode = True
			
				Else
				
					;EntityAlpha MESH_Grid, 0
					HideEntity MESH_Grid
					
					;EntityAlpha MESH_Edit_Cube, 0
					HideEntity MESH_Edit_Cube

					Edit_Mode = False
			
				EndIf
					
			EndIf


		;Toggle the polygons which are shadowed beign visible or not.
			If KeyHit(KEY_ToggleShadows)
				Shadow_Mode = (Shadow_Mode+1) Mod 3
			EndIf	

		
		; Toggle motion blur on the ball.
			If KeyHit(KEY_ToggleMotionBlur)

				; If motion blur has not already been applied,
				If Motion_Blur = False

					; Activate motion blur.
					SPS_CREATE_EMITTER(BallEmitter.EmitterBrush, ballpos)
					Motion_Blur = True
				
				EndIf
				
				; Increase the ball's velocity.
				Velocity# = Sqr(Vx#^2 + Vy#^2 + Vz#^2)
						
				If Velocity# > 0 

					; Calculate the direction vector.  The direction vector is a normal and has a length of 1.
					Direction_X# = Vx# / Velocity#
					Direction_Y# = Vy# / Velocity#
					Direction_Z# = Vz# / Velocity#
						
					; Set the ball's velocity to a specific amount.
					Velocity# = 20
						
					Vx# = Direction_X# * Velocity#
					Vy# = Direction_Y# * Velocity#
					Vz# = Direction_Z# * Velocity#

				EndIf
					
			EndIf
			

		; If the game is currently in edit mode...
		If Edit_Mode


			; Check to see which tile the mouse is pointing at.
			Temp = CameraPick(Camera_Active, MouseX(), MouseY())

			Picked_X# = PickedX#()
			Picked_Y# = PickedY#()
			Picked_Z# = PickedZ#()

			Grid_Picked_X = Floor(Picked_X#)
			Grid_Picked_Y = Floor(Picked_Y#)
			Grid_Picked_Z = Floor(Picked_Z#)
		
			PositionEntity MESH_Edit_Cube, Grid_Picked_X+0.5, Grid_Picked_Y+0.5, Grid_Picked_Z+0.5


			; Switch the current block for drawing.
				If KeyHit(KEY_NextBlock)

					Current_Drawing_Block = Current_Drawing_Block+1
					If Current_Drawing_Block >= Blocks_Loaded Then Current_Drawing_Block = 0

				EndIf	

				If KeyHit(KEY_PreviousBlock)
	
					Current_Drawing_Block = Current_Drawing_Block-1
					If Current_Drawing_Block < 0 Then Current_Drawing_Block = Blocks_Loaded-1

				EndIf	


			; Draw with the current drawing block and update the level if the left mouse button is pressed.
				If MouseDown(1)
					
					If (Grid_Picked_X >= 0) And (Grid_Picked_X < LEVEL_WIDTH) And (Grid_Picked_Y >= 0) And (Grid_Picked_Y < LEVEL_HEIGHT)
					
						; If the block below the current location is diffrent than the block we want to draw with...
						If Level_Block(Grid_Picked_X, Grid_Picked_Z) <> Current_Drawing_Block

							; Replace the block with the new block and reconstruct the level.
								Level_Block(Grid_Picked_X, Grid_Picked_Z) = Current_Drawing_Block 
								ResetEntity Level_Avatar
								FreeEntity Level_Avatar
								Construct_Level()

							; The level has changed so reset the save check.
								Level_Save_Sucess = False

						EndIf	

					EndIf

				EndIf


			; Grab the block below the cursor if the right mouse button is pressed.
				If MouseDown(2)
					
					If (Grid_Picked_X >= 0) And (Grid_Picked_X < LEVEL_WIDTH) And (Grid_Picked_Y >= 0) And (Grid_Picked_Y < LEVEL_HEIGHT)
						Current_Drawing_Block = Level_Block(Grid_Picked_X, Grid_Picked_Z)
					EndIf
					
				EndIf
		

			; Save the level.
				If KeyHit(KEY_EditSavelevel)
					Level_Save_Sucess = Save_Level()
				EndIf	
		
			; Load the last edited level.
				If KeyHit(KEY_EditLoadlevel)

					Load_Sucessful = Load_Level(255)
					ResetEntity Level_Avatar
					FreeEntity Level_Avatar
					Level_Avatar = Construct_Level()

					; The level has changed so reset the save check.
					Level_Save_Sucess = False	

				EndIf	

			; Reset the level.
				If KeyHit(KEY_EditResetLevel)
			
					ResetEntity Level_Avatar
					FreeEntity Level_Avatar
				
					For LoopZ = 0 To LEVEL_WIDTH-1
						For LoopX = 0 To LEVEL_HEIGHT-1
							Level_Block(LoopX, LoopZ) = 0
						Next
					Next	
				
					Level_Avatar = Construct_Level()
	
					; The level has changed so reset the save check.
					Level_Save_Sucess = False	
			
				EndIf

		EndIf


	; Reset the ball back to it's original location.				
		If (KeyHit(KEY_ResetBall) = True) Or (EntityY#(ballpos) < -5)
	
			; Rset the ball's collisions so it will move to w		
			ResetEntity ballpos
			; PositionEntity ballpos, 0.5, BallRadius#+0.01, 0.5	
			PositionEntity ballpos, 0.5, BALL_START_HEIGHT#, 0.5
			EntityRadius ballpos, BallRadius#
			EntityType ballpos, COLLIDE_Ball
			Discard = EntityCollided(ballpos, COLLIDE_Level)
				
			Vx# = 0
			Vy# = 0
			Vz# = 0
			Vr# = 0
			
			; Delete all particles and emitters in the world.
			SPS_OFF()
							
		EndIf	
		

		Thrust_Z# = 0	
		If KeyDown(208)=True Then Thrust_Z# = -Player_Thrust# 
		If KeyDown(200)=True Then Thrust_Z# =  Player_Thrust#
			
		Thrust_X# = 0
		If KeyDown(203)=True Then Thrust_X# = -Player_Thrust#
		If KeyDown(205)=True Then Thrust_X# =  Player_Thrust#



	; Check for local gameplay input which we will deal with later.
	
;		Local_Player\Input_Up		= KeyDown(KEY_Up)
;		Local_Player\Input_Down 	= KeyDown(KEY_Down)
;		Local_Player\Input_Left		= KeyDown(KEY_Left)
;		Local_Player\Input_Right	= KeyDown(KEY_Right)
;		Local_Player\Input_Fire1	= KeyDown(KEY_Fire1)

;	If MouseDown(1)
;		BallRadius# = BallRadius# + 0.5
;		PositionEntity ballpos,EntityX(ballpos),BallRadius#,EntityZ(ballpos)
;		ScaleEntity ball,BallRadius#,BallRadius#,BallRadius#
;	EndIf		
		
;	If MouseDown(2)
;		BallRadius# = BallRadius# - 0.5
;		If (BallRadius# < 1.0) Then BallRadius# = 1.0	
;		PositionEntity ballpos,EntityX(ballpos),BallRadius#,EntityZ(ballpos)
;		ScaleEntity ball,BallRadius#,BallRadius#,BallRadius#
;	EndIf		



Return


; -------------------------------------------------------------------------------------------------------------------
; This subroutine parses local and remote user input and modifys certain variables accordingly.
; -------------------------------------------------------------------------------------------------------------------
.Parse_Input

	


Return


; -------------------------------------------------------------------------------------------------------------------
; This subroutine animates everything in the world according to the rules of the game and the given input.
; -------------------------------------------------------------------------------------------------------------------
.Animate_World

	Gosub Animate_Sphere
	Gosub Animate_Cameras

	; Lock sky to camera.
	;PositionEntity MESH_Sky, EntityX#(Camera_Active), EntityY#(Camera_Active), EntityZ#(Camera_Active)

Return



; -------------------------------------------------------------------------------------------------------------------
; This subroutine animates the sphere.
; -------------------------------------------------------------------------------------------------------------------
.Animate_Sphere


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

	; If the entity collided with the level, then store the time of the last collision.	
	If Entity_Hit Then Last_Collide_Time = Time_Now 
					
	; 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 is colliding with the level this frame, or still sticking to the level from it's last
			; collision, then apply ground friction.
			If (Time_Now - Last_Collide_Time) <= Player_Friction_Stick_Time 

				; 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)
		
				; Multiply the entity's motion vector by the surface normal.
				; This gives us the part of the vector which points in the same direction as the surface the object collided with.

					Elasticity# = 0.5 

					Vx# = Vx# - Vx#*Abs(Nx#)*(1.0 - Elasticity#)
					Vy# = Vy# - Vy#*Abs(Ny#)*(1.0 - Elasticity#)
					Vz# = Vz# - Vz#*Abs(Nz#)*(1.0 - Elasticity#)
			
				; 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



; -------------------------------------------------------------------------------------------------------------------
; This subroutine animates the cameras.
; -------------------------------------------------------------------------------------------------------------------
.Animate_Cameras
		
	; Point the active camera at the currently specified target.

	;Px# = EntityX#(TARGET_CameraPivot, True)
	;Py# = EntityY#(TARGET_CameraPivot, True)
	;Pz# = EntityZ#(TARGET_CameraPivot, True)
		
	;PositionEntity Camera_Active, Px#, 2, Pz#
	
	PositionEntity Camera_Active, EntityX#(Camera_Target, True), 2, EntityZ#(Camera_Target, True) - 4

	;PointEntity Camera_Active, Camera_Target
	;RotateEntity Camera_Active, 0, EntityYaw#(Camera_Active), 0, True

Return



; -------------------------------------------------------------------------------------------------------------------
; This subroutine draws any 2D status information over the 3D scene after the 3D scene has been rendered.
; -------------------------------------------------------------------------------------------------------------------
.Render_2D_Overlay

	Color 255, 255, 255

	Select Show_Debug_Data
	
		Case 1 

			If (Time_Delta > 0)

				Frames_Per_Second = Int(Floor(1000.0/Float(Time_Delta)))
				
				Text 0, 16*0, " FPS = " + Str$(Frames_Per_Second)
				
				;If (FPS_Text > 0) Then FreeEntity FPS_Text
				;FPS_Text = Font3D_Text(Str$(Frames_Per_Second), 1, 64, 1)
				;RotateEntity FPS_Text, -90, 0, 0
				;EntityParent FPS_Text, Camera_Active, 1
				;Sprite2D(FPS_Text, 320, 60, 1)
								
			EndIf
			
			
		Case 2		

			Text 0, 16*1, " Polygons = " + Str$(TrisRendered())
			Text 0, 16*2, " Camera Mode = " + Str$(CameraMode)
			
			Text 0, 16*3, " Grid_X: " + Str$(Grid_Picked_X)
			Text 0, 16*4, " Grid_Z: " + Str$(Grid_Picked_Z)			
			Text 0, 16*5, " Pick Block: " + Str$(Level_Block(Grid_Picked_X, Grid_Picked_Z))
			Text 0, 16*6, " Draw Block: " + Str$(Current_Drawing_Block)
			
			Text 0, 16*8,  " F8         = Screenshot"
			Text 0, 16*9,  " S          = Save level"
			Text 0, 16*10, " L          = Load level"
			Text 0, 16*11, " R          = Reset Level"
			Text 0, 16*12, " PGUP/PGDN  = Change block"
			Text 0, 16*13, " LEFTMOUSE  = Paste block"
			Text 0, 16*14, " RIGHTMOUSE = Copy block"

			If Level_Save_Sucess = True Then Text 0, 16*16, " Level Saved!"
			
			;Text 0, 16*4, "    Camera_Active Location = " + RSet$(FStr$(EntityX#(Camera_Active)), 8) + "," + RSet$(FStr$(EntityY#(Camera_Active)), 8) + "," + RSet$(FStr$(EntityZ#(Camera_Active)), 8)
			;Text 0, 16*5, "    Local_Player Location = " + RSet$(FStr$(EntityX#(Local_Player\Avatar)), 8) + "," + RSet$(FStr$(EntityY#(Local_Player\Avatar)), 8) + "," + RSet$(FStr$(EntityZ#(Local_Player\Avatar)), 8)
			;Text 0, 16*7, "    Local_Player Speed = " + RSet$(FStr$(Local_Player\Speed#), 8) + " m/s"


		Case 3
		
			Text 0, 16*1,  " Vx = " + Str$(Vx#)
			Text 0, 16*2,  " Vy = " + Str$(Vy#)
			Text 0, 16*3,  " Vz = " + Str$(Vz#)
			Text 0, 16*5,  " Velocity            = " + Str$(Velocity#)
			Text 0, 16*6,  " Rotational_Velocity = " + Str$(Rotational_Velocity#)

			Text 0, 16*8,  " Textures_Loaded = " + Str$(Textures_Loaded)
			Text 0, 16*9,  " Meshes_Loaded = " + Str$(Meshes_Loaded)
			Text 0, 16*10, " Brushes_Loaded = " + Str$(Brushes_Loaded)
			Text 0, 16*11, " Blocks_Loaded = " + Str$(Blocks_Loaded)

			Text 0, 16*13, " Shadow polygons = " + Str$(Shadow_Poly_Count)
	
							
	End Select
			
Return


; -------------------------------------------------------------------------------------------------------------------
; This subroutine sets up the cameras.
; -------------------------------------------------------------------------------------------------------------------
.Initialize_Cameras
	
	Cam(0) = CreateCamera()
		
	CameraRange Cam(0), 1.0, 1024.0
	CameraZoom Cam(0), 1.0

	CameraFogMode Cam(0), 0

	;CameraFogRange Cam(0), 0, 127.0
	;CameraFogColor Cam(0), 127, 255, 255	
	;CameraClsMode Cam(0), False, True
	;Purple haze
	;CameraFogColor Cam(0), 198, 200, 255
	;Pink haze
	;CameraFogColor Cam(0), 255, 217, 214 

	Camera_Active = Cam(0)
	Camera_Target = Local_Player\Avatar

Return


; -------------------------------------------------------------------------------------------------------------------
; This subroutine frees up memory and shuts down stuff before exiting.
; -------------------------------------------------------------------------------------------------------------------
.Quit_Game

	; Fade to black, shut down.
	Exit_Game = True				

Return


; -------------------------------------------------------------------------------------------------------------------
; This subroutine sets up the various kinds of particles we want to use and initializes certain variables in the 
; particle system.
; -------------------------------------------------------------------------------------------------------------------
.Initialize_Particle_System

	; Load and/or create the meshes used to represent the particles.

		; Rememeber that any changes to these meshes at any time will affect ALL particles in the world which use 
		; them as their template, even particles which have already been created!  

		; This creates a small 1x1 plane mesh which we can use as a template and then hides it.  
		ParticleMesh = CreateQuad(0.5, 0.5, 0, 0, 0, -0.5, 0.5, 0, 1, 0, -0.5, -0.5, 0, 1, 1, 0.5, -0.5, 0, 0, 1)
		HideEntity ParticleMesh

	
	; Load the textures required for the particles:

		;FireTexture	= LoadAnimTexture("fire.bmp",	TEX_Color + TEX_Mipmap + TEX_UVClamp, 64, 64, 0, 16)
		;SmokeTexture	= LoadAnimTexture("smoke.bmp",	TEX_Color + TEX_Mipmap + TEX_UVClamp, 64, 64, 0, 16)


	; Create the particle brushes.  

		; Particle brushes define how each type of particle behaves - how long it lives, if it changes size, etc.

		BallParticle.ParticleBrush = New ParticleBrush
		
			BallParticle\Speed_Min# 			= 0
			BallParticle\Speed_Max#				= 0
			BallParticle\Heading_Min#			= 0
			BallParticle\Heading_Max#			= 0 
			BallParticle\Inclination_Min#		= 0
			BallParticle\Inclination_Max#		= 0
			BallParticle\Wind_Min#				= 0
			BallParticle\Wind_Max#				= 0
			BallParticle\Gravity_Min#			= 0
			BallParticle\Gravity_Max#			= 0
			BallParticle\Lifespan_Min			= 200
			BallParticle\Lifespan_Max			= 200
			BallParticle\XRot_Speed_Min#		= 0
			BallParticle\YRot_Speed_Min#		= 0
			BallParticle\ZRot_Speed_Min#		= 0
			BallParticle\XRot_Speed_Max#		= 0		
			BallParticle\YRot_Speed_Max#		= 0
			BallParticle\ZRot_Speed_Max#		= 0
			BallParticle\BrushMesh				= ball
			BallParticle\TextureHandle			= TEX_Ball
			BallParticle\TextureFrames			= 1
			BallParticle\Alpha_Start#			= 0.5
			BallParticle\Alpha_End#				= 0.0
			BallParticle\Scale_Start#			= 0.98
			BallParticle\Scale_End#				= 0.98
			BallParticle\FaceDirection			= 0
			BallParticle\Blend					= 3	
			BallParticle\Shininess				= 0
			BallParticle\FX						= 0 
			BallParticle\AutoFade_Near#			= 1000
			BallParticle\AutoFade_Far#			= 1000
			BallParticle\OnExpireSpawnEmitter	= Null
	

	; Create the emitter brushes.
	
		; Emitter brushes define the properties of the emitters, including their lifespan and the kind and number of
		; particles they emit.

		; Create a smoke particle emitter which emits from a single point.
		BallEmitter.EmitterBrush = New EmitterBrush

			BallEmitter\Shape 				= 1
			BallEmitter\OuterRadius#		= 0
			BallEmitter\InnerRadius#		= 0
			BallEmitter\EmissionRate		= 35
			BallEmitter\EmissionOdds		= 100
			BallEmitter\Lifespan			= -1
			BallEmitter\ParticleType		= BallParticle
			BallEmitter\RotateWithParent	= 1

		
	; Set the particle system's world scale and gravity to match the game.

		SPS_Units_Per_Meter# = 1.0
		SPS_Gravity# = GRAVITY#

	; Tell the particle system which camera to point billboarded particles at.
	;
	; You can change this anytime you want if you want to change cameras.  Just remmember the particle's won't point
	; at the new camera until SPS_Update() is called.

		SPS_CurrentCameraHandle = Cam(0)

Return


; -------------------------------------------------------------------------------------------------------------------
; fov is the same as your camerazoom.
; -------------------------------------------------------------------------------------------------------------------
Function Sprite2D(sprite,x#,y#,fov#)
	PositionEntity sprite,2*(x-320),-2*(y-240),fov#*640
End Function


; -------------------------------------------------------------------------------------------------------------------
; scale sprite in screen pixels relative to a 640x480 res when used with Sprite2D
; -------------------------------------------------------------------------------------------------------------------
Function ScaleSprite2(sprite,x,y)
    ScaleEntity sprite,x,y,1
End Function


; -------------------------------------------------------------------------------------------------------------------
; please pass camera to this function or 0 for a billboard type with mesh.
; -------------------------------------------------------------------------------------------------------------------
Function CreateSprite2(parent)
    If parent<>0
        m=CreateMesh(parent)
    Else
        m=CreateMesh()
    EndIf
    s=CreateSurface(m)
    AddVertex s,-1,+1,-1,0,0:AddVertex s,+1,+1,-1,1,0
    AddVertex s,+1,-1,-1,1,1:AddVertex s,-1,-1,-1,0,1
    AddTriangle s,0,1,2:AddTriangle s,0,2,3
    ScaleEntity m,100,100,1
    Return m
End Function


Thanks for all the help guys, and I'll take a look at that code sswift, and see if I can figure it out enough to implement it...sorry I have such a hard time figuring out this 3D math. I haven't really needed it until now thanks to the many useful features in Blitz. I can actually fake the effect I'm after, to some extent. But, I hope I can make this work...it's so much better to use the real thing...