; ==============================================================================
; Blitz3D GeoWall Game Engine Template
; (C) 2004 Jason Leigh, Electronic Visualization Laboratory
;
; Use this to get started on your Blitz3D GeoWall Game
; This code shows:
;	1. how to set up GeoWall stereo
;	2. how to create a game loop that will keep constant update rate on CPUs with different speeds
;	3. loads a simple animated test object
;
; Keys:
;	1 = mono
;	2 = stereo
;	3 = reverse stereo
;	+ = increase stereo separation
;	- = decrease sterep separation
;
; Created: 7/15/2004
; Version: 8/17/2004
; For comments email: spiff@evl.uic.edu  
; -----------------------------------------------------------------------------

AppTitle "Arcanius Demo"
HidePointer
;AntiAlias True
;WireFrame True

SeedRnd MilliSecs() ; Seed the randomizer with the current system time in milliseconds.

; Include geowall stereo library
Include "BlitzGeoWallStereoLib.bb"

; Init graphics (i.e. set resolution and rendering buffer)
SL_InitDisplayMode()

; Eye separation (-ve means cross-eyed stereo) 
Global g_slEyeSeparation#=0.005
Global g_reverseStereo#=1
SL_SetEyeSeparation(g_reverseStereo# * g_eyeSeparation#)

; Stereo viewing flag: 0=stereo 1=mono
Global g_viewMode=0

; Set up stereo cameras
Global cam = SL_CreateStereoCameras() : SL_SetUpCameras()
CameraRange SL_GetLeftCamera(),  0.01, 50000	; near clipping plane, far clipping plane
CameraRange SL_GetRightCamera(), 0.01, 50000	; near clipping plane, far clipping plane

; ***********************************************************************
Function HandleModeKeys()

	If KeyHit(2) Then			; Switch to mono mode by pressing 1
		g_viewMode=1
		SL_SwitchToMono()
	Else If KeyHit(3) Then		; Switch to stereo mode by pressing 2
		g_viewMode=0
		g_reverseStereo# = 1
		SL_SwitchToStereo()
		SL_SetEyeSeparation(g_reverseStereo# * g_slEyeSeparation#)
	Else If KeyHit(4) Then		; Switch to cross-eye stereo mode by pressing 3
		g_viewMode=0
		g_reverseStereo# = -1
		SL_SwitchToStereo()
		SL_SetEyeSeparation(g_reverseStereo# * g_slEyeSeparation#)
	Else If KeyDown(13) Then	; Change eye separation by pressing +
		g_slEyeSeparation#=g_slEyeSeparation#+0.001
		SL_SetEyeSeparation(g_reverseStereo# * g_slEyeSeparation#)
	Else If KeyDown(12) Then	; Change eye separation by pressing -
		g_slEyeSeparation#=g_slEyeSeparation#-0.001
		If g_slEyeSeparation# < 0 Then g_slEyeSeparation#=0
		SL_SetEyeSeparation(g_reverseStereo# * g_slEyeSeparation#)
	EndIf

End Function
; ***********************************************************************

; Load some model in front of camera for testing
;test = LoadMesh("arrow4.x", cam)
;RotateEntity test,0,100,90
;ScaleEntity test,0.02,0.02,0.02
;PositionEntity test,0,0,0.2

;---------------------------------------
; Set up lighting
;---------------------------------------
Global g_light1=CreateLight(2)	; point light source
LightRange g_light1,10000
AmbientLight 40,40,40

;---------------------------------------
; Set up Sprite Control and lens flares
;   credit: Barliesque @ http://www.blitzbasic.com/codearcs/codearcs.php?code=1107
;---------------------------------------

Global ViewX=GraphicsWidth()/2,ViewY=GraphicsHeight()	;/(2-g_viewMode)
Global ViewAspect# = Float(viewx)/Float(viewy)

Global FlareRed, FlareGreen, FlareBlue
Dim Flare(12)

Include "SpriteControl.bb"

spritecamera = SL_GetLeftCamera()
spritepivot = CreateSpritePivot(spritecamera,1.01)
SetupFlares("lens-flares.jpg")

;---------------------------------------
; Set up the game
;---------------------------------------
Include "defs.bb"
Include "init.bb"

Include "cameras.bb"
Include "player.bb"
Include "planets.bb"
Include "links.bb"
Include "sounds.bb"
Include "particles.bb"
Include "archery.bb"

;Include "frames.bb"
;Include "shownormals.bb"
;show_normals(earth\model)		; draw lighting normals

;---------------------------------------
; Load all the models, textures, etc.
;---------------------------------------
LoadGame()		; init.bb

; ==============================================================================
; Set the game update/calculation rate to 30 frames per second.
; This is NOT the same as FRAME RATE which marks how rapidly the graphics image is updating.
; In this code, the game update/calculation rate is locked down so that the game behaves
; the same for different speed of computers, but the graphics always refresh as fast as possible.
Const c_GameRate#=30
period#=1000/c_GameRate
theTime=MilliSecs()

; ==============================================================================
;
;                      *******  MAIN GAME LOOP  *******
;
; ==============================================================================
While Not KeyHit( 207 )	; End key
	; Compute how much time has elapsed thru the entire main loop
	Repeat
		elapsedTime=MilliSecs()-theTime
	Until elapsedTime<>0
	
	;how many computation 'frames' have elapsed	
	elapsedFrames=elapsedTime/period
	
	; If more than 1 frame has elapsed then we have to compute
	; what the state of the world would have been.
	; This is crucial for slower computers.
	; So this loop goes thru each frame of missed time and computes
	; what the new state of the world should be by simply
	; calling the user's UpdateGameState elapsedFrames times.
	For k=1 To elapsedFrames
		; When elapsedFrames is reached, save the state of the world so that tweening
		; can be used to get an accurate interpolation of what the state of the world
		; should be.
		If k=elapsedFrames Then CaptureWorld
		theTime=theTime+period
		
		GameProcess()		; Update the game's state (e.g. position of each entity)
		UpdateWorld			; Update all animation properties- e.g. compute collision checks
		DebugLog "compute frame " + k
	Next

	;Compute the tweening value (a number from 0 to 1)
	; 0 means the state of the world when CaptureWorld was called
	; 1 means the current interpolated state of the world
	; e.g. if elapsedTime is 1 second and desired period is 3 second
	; then 1mod3=1 hence tween=1/period = 1/3 
	; so we interpolate 1/3 distance from the captured point and the present time.
	;
	; if elapsedTime = 5 seconds and desired period is 3 seconds;
	; 1 frame of time would have passed so it would have gone thru
	; the for loop once computing the new frame state
	; Since 5mod3=2 hence tween=2/3 so we tween 2/3 the way between the captured state
	; and the current state.
	tween#=Float(elapsedTime Mod period)/Float(period)
	
	HandleModeKeys()	; 1: mono, 2: stereo, 3: cross-eye stereo, +/-: eye separation
	
	; Adjust stereo cameras and render the world
	SL_Update(tween#)
	
	;render_normals(cam, False)

	; 2-D stuff goes after RenderWorld
	GameProcess2D()
	
	;FPSCounter()

	; Swap buffers
	Flip 
Wend

End

Function DupText(xpos, ypos, message$, bool1=False, bool2=False)
	Text xpos, ypos, message, bool1, bool2
	If g_viewMode = 0 Then
		Text GraphicsWidth()/2 + xpos, ypos, message, bool1, bool2
	EndIf
End Function

Function FPSCounter()
	; Place in the main program loop to execute after 'RenderWorld' but before 'Flip'.
	FPS_framecounter = FPS_framecounter + 1
	If milli_secs >= FPS_timeout
		FPS_timeout = milli_secs + 1000
		FPS_framecount = FPS_framecounter
		FPS_framecounter = 0
	EndIf
	
	DupText 10, 60, "FPS " + FPS_framecount
	
	milli_secs = MilliSecs()
End Function

; ***********************************************************************
; DO YOUR OWN STUFF HERE!!!!!!

Global mxspd#, myspd#, wasmoving = False

Function GameProcess()
	wasmoving = mxspd <> 0 Or myspd <> 0
	mxspd# = MouseXSpeed()
	myspd# = MouseYSpeed()
	
	Select GAMESTATE
		Case GAME_INIT				; this should run only ONCE

			GAMESTATE = GAME_INTRO
			
		Case GAME_INTRO
		
			UpdateSound()
			
			If MenuUpdate() = True Then Return
			MENUSTATE = MENU_INTRO

		Case GAME_PLAY
		
			UpdateSound()
	
			If MenuUpdate() = True Then	Return
			
			If KeyDown(56) Then	PAUSED = True		; Alt key pauses game
			If PAUSED Then
				If MouseDown(1)
					PAUSED = False		; Left Click to resume play
				EndIf
				FlushKeys : FlushMouse
			EndIf
			MoveMouse GraphicsWidth()/2, GraphicsHeight()/2		; lock mouse at center of screen
	
			For pn.Planet 	= Each Planet 	: UpdatePlanet( pn ) 	: Next
			For pl.Player 	= Each Player 	: UpdatePlayer( pl ) 	: Next
			For a.Arrow 	= Each Arrow 	: UpdateArrow( a ) 		: Next
			For pt.Particle = Each Particle : UpdateParticle( pt ) 	: Next
			
			UpdateLinks()
			WormHole()
			
			MoveToEntity(starbox, cam)
			
			If MouseHit(3) Then
				GoToBeyond()
			EndIf
			
			;PointEntity GetChild(jupiter\ringpivot, 1), cam;, EntityRoll(cam,True)
			;TurnEntity GetChild(jupiter\ringpivot, 1),10,0,0
			
			UpdateFlare(sl_GetLeftCamera(), sol\orbpivot,archer\model,sol\camdist / 5000.0) 

		Case GAME_OVER
		
			UpdateSound()
		
			If MenuUpdate() = True Then Return
			
			ResetGame()
			MENUSTATE = MENU_CREDITS
	End Select
	
End Function


Global menu_hl = 1

Function MenuUpdate()
	
	HideEntity m_plot
	HideEntity m_hints
	HideEntity m_credits
	HideEntity b_help
	HideEntity b_end
	HideEntity b_ok
	HideEntity b_hl
	HideEntity intro
	
	If MENUSTATE <> MENU_NONE Then archer\timer = archer\timer + period
	
	Select MENUSTATE
		Case MENU_NONE
			If KeyHit(1) Then 
				If archer\state = AT_SPACE Then
					GAMESTATE = GAME_OVER
				Else
					MENUSTATE = MENU_MAIN
				EndIf
			EndIf
			Return False
		Case MENU_MAIN
			ShowEntity b_help
			ShowEntity b_end
			ShowEntity b_hl
			
			If archer\state = AT_SPACE Then
				MENUSTATE = MENU_CREDITS
			ElseIf mxspd = 0 And MouseHit(1) Then
				If menu_hl = 3 Then
					GoToBeyond()
					archer\timer = MilliSecs() - 120*60
					MENUSTATE = MENU_NONE
				Else
					MENUSTATE = MENU_HELP
				EndIf
			EndIf
			
			If menu_hl = 2 Then menu_hl = 1
			If menu_hl = 1 And mxspd > 0 Then menu_hl = 3
			If menu_hl = 3 And mxspd < 0 Then menu_hl = 1
			
			If KeyHit(1) Then MENUSTATE = MENU_NONE
		Case MENU_HELP
			ShowEntity m_hints
			ShowEntity b_ok
			ShowEntity b_hl

			menu_hl = 2
			
			If KeyHit(1) Then MENUSTATE = MENU_NONE
		Case MENU_PLOT
			ShowEntity m_plot
			ShowEntity b_ok
			ShowEntity b_hl

			menu_hl = 2
			
			If KeyHit(1) Then MENUSTATE = MENU_NONE
		Case MENU_CREDITS
			ShowEntity m_credits
			ShowEntity b_ok
			ShowEntity b_hl

			menu_hl = 2
			
			If KeyHit(1) Then
				GAMESTATE = GAME_INTRO
				MENUSTATE = MENU_INTRO
			ElseIf MouseHit(1) <> 0 Then
				GAMESTATE = GAME_INTRO
				MENUSTATE = MENU_INTRO
			EndIf
		Case MENU_INTRO
			ShowEntity intro
			chosen.Planet = PlanetPicker()
			If chosen <> Null Then
				GoToCore(chosen)
				MENUSTATE = MENU_PLOT
				GAMESTATE = GAME_PLAY
			EndIf

	End Select
	
	If menu_hl = 2 And MouseHit(1) Then MENUSTATE = MENU_NONE

	If menu_hl = 1 Then PositionEntity b_hl, -.5, -0.6, 1
	If menu_hl = 2 Then PositionEntity b_hl, 0,   -0.6, 1
	If menu_hl = 3 Then PositionEntity b_hl, 0.5, -0.6, 1

	FlushMouse
	Return True
End Function

Function GameProcess2D()
	;SL_OrtProj()
	
	DrawPixels()
	
	If MENUSTATE <> MENU_NONE Then Return
	
	Color 0,255,0
	For pn.Planet = Each planet
		If pn\visited Or pn\parent = Null Or SameSystem(pn, archer\loc) Then
			If pn <> archer\loc Or archer\state <> AT_CORE Then
				CameraProject SL_GetLeftCamera(),EntityX(pn\model,True),EntityY(pn\model,True),EntityZ(pn\model,True)
				If ProjectedZ() > 0 And ProjectedX() + 15 < ViewX And ProjectedX() > 15 Then
					DupText ProjectedX(), ProjectedY(), pn\name, True, True
					;					Text ProjectedX(),				    ProjectedY(),pn\name, True,True
					;If g_viewMode = 0	Text ProjectedX()+GraphicsWidth()/2,	ProjectedY(),pn\name, True,True
				EndIf
			EndIf
		EndIf
	Next
	
	; cursor stand-in
	; Oval GraphicsWidth()/2,GraphicsHeight()/2,4,4
	
	;time_remaining = 60 - ((MilliSecs() - archer\timer) / 1000)
	;If time_remaining >= 0 Then
	;	Color 255,255,255
	;	Text 20,20,"Time Remaining: " + time_remaining + " sec"
	;	If g_viewMode = 0 Text GraphicsWidth()/2 + 20,20,"Time Remaining: " + time_remaining + " sec"
	;Else
	;	Color 255,0,0
	;										  Text 20,20,"Time's Up!"
	;	If g_viewMode = 0 Text GraphicsWidth()/2 + 20,20,"Time's Up!"
	;	GoToBeyond()
	;EndIf
	
	; draw positive coordinate axes (red = X, green = Y, blue = Z)
	;Color 200,0,0 : Line3d(0,0,0,100,0,0,cam)
	;Color 0,200,0 : Line3d(0,0,0,0,100,0,cam)
	;Color 0,0,200 : Line3d(0,0,0,0,0,100,cam)
	;Color 255,255,255
	
	;Segments3d(EntityX(archer\arrow,True),EntityY(archer\arrow,True),EntityZ(archer\arrow,True)-.015,EntityX(archer\bow,True),EntityY(archer\bow,True)+0.02,EntityZ(archer\bow,True),cam,6)
	;Segments3d(EntityX(archer\arrow,True),EntityY(archer\arrow,True),EntityZ(archer\arrow,True)-.015,EntityX(archer\bow,True),EntityY(archer\bow,True)-0.02,EntityZ(archer\bow,True),cam,6)

	;SL_RestoreProj()
End Function

Function ResetGame()

	GoToBeyond()

	For p.Planet = Each Planet
		p\visited = False
		If p <> sol And p\flipped = True Then
			FlipMesh p\model : p\flipped = False
		EndIf
	Next
	
	showandhide(archer)

	DeleteParticles()
	DeleteLinks()
	DeleteArrows()

	g_orbspd# = 3600
	archer\timer = MilliSecs()	; reset the countdown
End Function

Function ShowAxes(entity)
	x = CreateCube(entity)
	y = CreateCube(entity)
	z = CreateCube(entity)
	ScaleEntity x,0.1,0.1,0.1
	ScaleEntity y,0.1,0.1,0.1
	ScaleEntity z,0.1,0.1,0.1
	PositionEntity x,0,0,0
	PositionEntity y,0,0,0
	PositionEntity z,0,0,0
	PositionEntity x,1,0,0
	PositionEntity y,0,1,0
	PositionEntity z,0,0,1
	EntityColor x,255,0,0
	EntityColor y,0,255,0
	EntityColor z,0,0,255
End Function

Function ClampValue(Original#, low#, high#)
	If Original<low  Then Return low
	If Original>high Then Return high
	Return Original
End Function


Function MoveToEntity(what, where, orientation=False)
	If what = 0 Or where = 0 Then
		DebugLog "movetoentity: what (" + what + ") where (" + where + ")"
		Return
	EndIf
	x# = EntityX(where,True)
	y# = EntityY(where,True)
	z# = EntityZ(where,True)
	PositionEntity what, x,y,z, True

	If orientation = True Then
		pitch# 	= EntityPitch(where,True)
		yaw# 	= EntityYaw(where,True)
		roll# 	= EntityRoll(where,True)
		RotateEntity what, pitch,yaw,roll, True
	EndIf
End Function

;*********************************************************************************
;*** Draw a 3d pixel (use this AFTER RenderWorld)
Function plot3d(x#,y#,z#, camera)
	camera = GetChild(camera,1)
	CameraProject camera,x#,y#,z#
	If ProjectedZ() > 0 Then Plot ProjectedX# ( ), ProjectedY# ( )
End Function

Function oval3d(x#,y#,z#, radius#, camera)
	;If CountChildren(camera) > 0 Then
	;	If Not oval3d(x,y,z,radius,GetChild(camera, 1)) Then
	;		oval3d(x,y,z,radius,GetChild(camera, 2))
	;	EndIf
	;	Return
	;EndIf
	;CameraProject g_slCameraL,x#,y#,z#
	;If ProjectedZ() > 0 And ProjectedX# () < ViewX Then
		;Oval ProjectedX# ( ), ProjectedY# ( ), radius, radius, 1
		;Oval ViewX + ProjectedX# ( ), ProjectedY# ( ), radius, radius, 1
	;Else
	;	CameraProject g_slCameraR,x#,y#,z#
	;	If ProjectedZ() > 0 Then Oval ProjectedX# ( ), ProjectedY# ( ), radius, radius, 1
	;EndIf
End Function

;*********************************************************************************
;*** Draw a single fast 3d line (use this AFTER RenderWorld)
;*********************************************************************************
Function Line3d(x0#,y0#,z0#,x1#,y1#,z1#, camera)
	camera = GetChild(camera,1)
	CameraProject (camera,x0,y0,z0)
	px0#=ProjectedX#() : py0#=ProjectedY#() : pz0#=ProjectedZ#() 
	CameraProject (camera,x1,y1,z1)
	If ProjectedZ#() > 0 And pz0 > 0 Then Line px0, py0, ProjectedX#(), ProjectedY#()
End Function

;*********************************************************************************
;*** Draw a multi-segment 3d line (use this AFTER RenderWorld)
;*********************************************************************************
Function segments3d(x0#,y0#,z0#,x1#,y1#,z1#, camera, segments=1)
	camera = GetChild(camera,1)
	stepx#=(x1-x0)/segments : stepy#=(y1-y0)/segments : stepz#=(z1-z0)/segments
	x1 = x0+stepx# : y1 = y0+stepy# : z1 = z0+stepz#

	For t = 1 To segments
		Line3d(x0,y0,z0,x1,y1,z1,camera)
		x0 = x1 : y0 = y1 : z0 = z1
		x1 = x0+stepx# : y1 = y0+stepy# : z1 = z0+stepz#
	Next
End Function