'----------------------------------------------------------------
'   TITLE: Sponza
'   AUTHOR: n00b
'   DATE: Nov 21, 2025
'
'   DESCRIPTION: 3D Platformer Demo
'
'----------------------------------------------------------------

Include "serenity.bas"

Include "util.bas" 'This has the RotatePoint2D and GetHeading2D functions

'This variable will be set to the main 3D canvas once its created
scene_canvas = -1

hero = -1
level = -1
level_collision = -1

'Used to store the bounding box for character
'NOTE: min_bby is used to determine the length of the ray cast from the character to the ground to check if player is on the ground
Dim min_bbx, min_bby, min_bbz
Dim max_bbx, max_bby, max_bbz

'Forces applied when the player moves
player_linear_force = 3000
player_angular_force = 1000
player_jump_force = 5000
player_jump_linear_force = 0
player_jump_ready = False
player_ground_test = FALSE

'These variables will store the animations once set below
PLAYER_IDLE_ANIMATION = 0
PLAYER_RUN_ANIMATION = 0
PLAYER_JUMP_ANIMATION = 0

'These are the buttons for player control mode
PLAYER_KEY_RUN = K_UP
PLAYER_KEY_LEFT = K_LEFT
PLAYER_KEY_RIGHT = K_RIGHT
PLAYER_KEY_JUMP = K_SPACE

'I am setting up some matrices that I use to get the direction the
'player is facing. GetActorPosition() is not reliable since it is
'prone to gimbal lock so the prefered method is to calculate the
'direction from the actor's transform matrix using a forward vector
'
'NOTE: Matrices are not automatically cleaned up once out of scope
'      so if you are not creating a matrix in global scope then you
'      must call DeleteMatrix() to clean them up
player_m = DimMatrix(4, 4)
forward_m = DimMatrix(4, 4)
result_m = DimMatrix(4, 4)

'This matrix is just setting a forward vector so it won't change
SetIdentityMatrix(forward_m, 4)
SetMatrixTranslation(forward_m, 0, 0, -1)

'A simple 3rd person camera to follow the player
Function Player_Camera(player_actor)
	Dim cam_x, cam_y, cam_z
	Canvas(scene_canvas)
	GetCameraPosition(cam_x, cam_y, cam_z)
	
	Dim px, py, pz
	GetActorPosition(player_actor, px, py, pz)
	
	Dim rx, ry, rz
	GetActorRotation(player_actor, rx, ry, rz)
	
	tx = 1 - (cam_x - px)
	ty = 30 - (cam_y - py)
	tz = -75 -(cam_z - pz)
	
	Dim rv As Vector2D
		
	GetActorTransform(player_actor, player_m)
	MultiplyMatrix(player_m, forward_m, result_m)
	Dim mx, my, mz
	GetMatrixTranslation(result_m, mx, my, mz)
	cam_rot_y = GetHeading2D(px, pz, mx, mz)
	rv = RotatePoint2D(px+1, pz-75, px, pz, 90-cam_rot_y)
	SetCameraPosition(rv.x, py+30, rv.y)
	SetCameraRotation(0, 90-cam_rot_y, 0)
End Function


'This function handles the players controls
Sub Player_Control(player_actor)
	If Key(K_R) Then
		SetActorPosition(hero, 80, 85, 570)
		mode_switch = TRUE
		While Key(K_R)
			Update()
		Wend
		Return
	End If
	
	'Store the current player animation
	player_animation = GetActorCurrentAnimation(player_actor)
	
	'We need to set the linear and angular factors and velocities to 0 to prevent
	'the forces from being continually applied when the player is idle
	Dim lx, ly, lz
	GetActorLinearFactor(player_actor, lx, ly, lz)
	'SetActorLinearFactor(player_actor, 0, ly, 0)
	Dim ax, ay, az
	GetActorAngularFactor(player_actor, ax, ay, az)
	SetActorAngularFactor(player_actor, 0, 0, 0)
	Dim lfx, lfy, lfz
	GetActorLinearVelocityWorld(player_actor, lfx, lfy, lfz)
	SetActorLinearVelocityWorld(player_actor, 0, lfy, 0)
	Dim afx, afy, afz
	GetActorAngularVelocityWorld(player_actor, afx, afy, afz)
	SetActorAngularVelocityWorld(player_actor, 0, 0, 0)
	ClearActorForces(player_actor)
	action_run = FALSE
	action_jump = FALSE
	
	'If the run key is pressed and the player is on the ground
	'then the current animation is set to the run animation
	If Key(PLAYER_KEY_RUN) And player_ground_test Then
		player_animation = PLAYER_RUN_ANIMATION
		action_run = TRUE
	End If
	
	'If the player can jump then set the current animation
	'to the jump animation
	If Key(PLAYER_KEY_JUMP) And player_jump_ready Then
		player_animation = PLAYER_JUMP_ANIMATION
		action_jump = TRUE
			If Key(PLAYER_KEY_RUN) Then
				player_jump_linear_force = -1500
			End If
	End If
	
	'If the player is on the ground and not running then
	'the current animation is set to the idle animation
	If player_ground_test AND (NOT action_run) Then
		player_animation = PLAYER_IDLE_ANIMATION
		SetActorLinearVelocityWorld(player_actor, 0, lfy, 0)
	End If
	
	'If the player is running then apply an impulse to the
	'player
	If action_run Then
		SetActorLinearFactor(player_actor, 1, ly, 1)
		Dim tx, ty, tz
		GetActorLinearVelocityLocal(player_actor, tx, ty, tz)
		If abs(tz) < 20 Then
			ApplyActorCentralImpulseLocal(player_actor, 0, 0, -player_linear_force)
		End If
	End If
	
	'If the player jumps then apply an upward impulse
	If action_jump Then
		SetActorLinearFactor(player_actor, 1, ly, 1)
		ApplyActorCentralImpulseLocal(player_actor, 0, player_jump_force, 0)
		player_jump_ready = FALSE
	End If
	
	'If the player turns then set a torque
	If Key(PLAYER_KEY_LEFT) Then
		SetActorAngularFactor(player_actor, 0, 1, 0)
		ApplyActorTorqueImpulseWorld(player_actor, 0, -player_angular_force, 0)
	ElseIf Key(PLAYER_KEY_RIGHT) Then
		SetActorAngularFactor(player_actor, 0, 1, 0)
		ApplyActorTorqueImpulseWorld(player_actor, 0, player_angular_force, 0)
	End If
	
	Dim x, y, z
	GetActorPosition(player_actor, x, y, z)

	'Cast a downward ray to check if the player is on the ground
	n = CastRay3D(x, y, z, x, y - (min_bby/4), z)
	
	'If n returns 0 hits, then the player is in the air so we set the animation
	'to the jump animation
	If n = 0 Then
		player_animation = PLAYER_JUMP_ANIMATION
		player_ground_test = FALSE
		player_jump_ready = FALSE
		ApplyActorCentralImpulseLocal(player_actor, 0, 0, player_jump_linear_force)
	End If
	
	'If n returns a hit or player is colliding with level, then set the ground test flag
	If n Or GetActorCollision(player_actor, level_collision) Then
		player_ground_test = TRUE
		If Not Key(PLAYER_KEY_JUMP) Then
			player_jump_ready = TRUE
			player_jump_linear_force = 0
		End If
	End If
	
	'Sets the current animation
	If player_animation <> GetActorCurrentAnimation(player_actor) Then
		SetActorAnimation(player_actor, player_animation, -1)
	End If
End Sub



title$ = "Serenity3D Template"
w = 640
h = 480
fullscreen = FALSE
vsync = FALSE


'Open a graphics window
OpenWindowEx("Serenity3D Template", WINDOWPOS_CENTERED, WINDOWPOS_CENTERED, w, h, WindowMode(1, 0, 0, 0, 0), 0, true, vsync)
SetFPS(60)

'Open a 3D Canvas
scene_canvas = OpenCanvas3D(0, 0, 640, 480, 0)

'Set the 3D Canvas as the active Canvas (Camera Actions will be done on active canvas)
Canvas(scene_canvas)

'Move and Rotate the Camera
SetCameraPosition(-459, 319, 155)
SetCameraRotation(9,-83,0)

'Load a Stage
Serenity_LoadStage("stage1")

'Get the Serenity internal index to the actor
hero_index = Serenity_GetActorIndex("guy")

'Use the Serenity index to get the RCBasic Actor ID
hero = Serenity_GetActorID(hero_index)

'Get the bounding box for the actor
GetActorAABB(hero, min_bbx, min_bby, min_bbz, max_bbx, max_bby, max_bbz)

'Adjust the actor shape and gravity parameters
'NOTE: SetActorShapeEx has an extra parameter to set the radius of capsule shapes
SetActorShapeEx(hero, ACTOR_SHAPE_CAPSULE, 40, (max_bbx - min_bbx)/2 - 5)
SetActorGravity(hero, 0, -150, 0)

mesh_index = Serenity_GetActorMeshIndex(hero_index)
PLAYER_IDLE_ANIMATION = Serenity_GetMeshAnimationIDByName(mesh_index, "IDLE")
PLAYER_RUN_ANIMATION = Serenity_GetMeshAnimationIDByName(mesh_index, "RUN")
PLAYER_JUMP_ANIMATION = Serenity_GetMeshAnimationIDByName(mesh_index, "JUMP")

level_collision = Serenity_GetActorID(Serenity_GetActorIndex("level"))

'Set the starting animation to idle_animation and set loops to -1 to loop infinitely


'Main Loop
While Not Key(K_ESCAPE)
	Player_Control(hero)
	Player_Camera(hero)
	Update()
Wend

