

ThirdPerson = {}
ThirdPerson.description = ""

function ThirdPerson.start()
	setMouseLocked(true)

	ThirdPerson.walkSpeed = 3.0
	ThirdPerson.mouseSensitivity = 0.3
	ThirdPerson.jumpSpeed = 5.0
	ThirdPerson.gravity = -12.0
	ThirdPerson.animDecelOnStop = 10.0

	ThirdPerson.position = vec3(0.0, 0.0, 0.0)
	ThirdPerson.charYaw = 0.0
	ThirdPerson.cameraYaw = 0.0
	ThirdPerson.pitch = 20.0
	ThirdPerson.velocityY = 0.0
	ThirdPerson.grounded = false

	ThirdPerson.collisionRadius = 0.3
	ThirdPerson.cameraDistance = 5.0
	ThirdPerson.cameraHeight = 2.0
	ThirdPerson.fov = degToRad(60.0)
	ThirdPerson.lastTime = getTime()
	ThirdPerson.lastMousePos = getMousePosition()

	local pageId = 1
	local levelModels = loadLevel("le1.lvl", pageId)
	ThirdPerson.levelMeshes = modelFilter(levelModels, MODEL_FILTER_MESH)
	ThirdPerson.floorModels = modelFilter(levelModels, MODEL_FILTER_DATA, "flo")
	ThirdPerson.wallModels = modelFilter(levelModels, MODEL_FILTER_DATA, "wal")

	local charModels = loadModel("char.gltf", pageId)
	ThirdPerson.charRoot = charModels[1]
	ThirdPerson.charMeshes = modelFilter(charModels, MODEL_FILTER_MESH)

	ThirdPerson.walkAnim = loadAnim("charAnim.anim", pageId)
	ThirdPerson.jumpAnim = loadAnim("jumpAnim.anim", pageId)
	ThirdPerson.walkAnimTime = 0.0
	ThirdPerson.animSpeed = 1.0
	ThirdPerson.jumpAnimTime = 0.0
	ThirdPerson.jumpAnimBlendSpeed = 5.0
	ThirdPerson.groundY = 0.0
end

function ThirdPerson.update()
	local currentTime = getTime()
	local dt = currentTime - ThirdPerson.lastTime
	ThirdPerson.lastTime = currentTime

	local mousePos = getMousePosition()
	local dx = mousePos.x - ThirdPerson.lastMousePos.x
	local dy = ThirdPerson.lastMousePos.y - mousePos.y
	ThirdPerson.lastMousePos = mousePos

	ThirdPerson.cameraYaw = ThirdPerson.cameraYaw - dx * ThirdPerson.mouseSensitivity
	ThirdPerson.pitch = clamp(ThirdPerson.pitch + dy * ThirdPerson.mouseSensitivity, -80.0, 10.0)

	-- movement relative to camera yaw
	local yawMat = mat4()
	yawMat.rotation = eulerRot(0.0, ThirdPerson.cameraYaw, 0.0)
	local forward = yawMat.forwardAxis
	local right = yawMat.rightAxis

	local moveDir = vec3(0.0, 0.0, 0.0)
	if getKeyDown(KEY_W) then
		moveDir = moveDir - vec3(forward.x, 0.0, forward.z)
	end
	if getKeyDown(KEY_S) then
		moveDir = moveDir + vec3(forward.x, 0.0, forward.z)
	end
	if getKeyDown(KEY_D) then
		moveDir = moveDir + vec3(right.x, 0.0, right.z)
	end
	if getKeyDown(KEY_A) then
		moveDir = moveDir - vec3(right.x, 0.0, right.z)
	end

	if vec3Length(moveDir) > 0.0 then
		moveDir = vec3Normalize(moveDir)
		ThirdPerson.charYaw = radToDeg(atan2(moveDir.x, moveDir.z))
	end
	
	local newPosition = ThirdPerson.position + moveDir * ThirdPerson.walkSpeed * dt

	-- gravity and jump
	if getKeyPressed(KEY_SPACE) and ThirdPerson.grounded then
		ThirdPerson.velocityY = ThirdPerson.jumpSpeed
		ThirdPerson.grounded = false
	end

	ThirdPerson.velocityY = ThirdPerson.velocityY + ThirdPerson.gravity * dt
	newPosition.y = newPosition.y + ThirdPerson.velocityY * dt

	-- wall collision
	local wallCollisionRadius = ThirdPerson.collisionRadius * 0.5
	for i = 1, #ThirdPerson.wallModels do
		local hit = sphereCast(newPosition, wallCollisionRadius, ThirdPerson.wallModels[i])
		if hit.hitDistance >= 0.0 then
			local pushDir = newPosition - hit.hitPoint
			pushDir.y = 0.0
			local pushLen = vec3Length(pushDir)
			if pushLen > 0.001 then
				pushDir = pushDir * (1.0 / pushLen)
				local penetration = wallCollisionRadius - pushLen
				newPosition = newPosition + pushDir * penetration
			end
		end
	end

	-- floor collision
	local onPlatform = false
	for i = 1, #ThirdPerson.floorModels do
		local hit = sphereCast(newPosition, ThirdPerson.collisionRadius, ThirdPerson.floorModels[i])
		if hit.hitDistance >= 0.0 then
			onPlatform = true
			if newPosition.y <= hit.hitPoint.y then
				newPosition.y = hit.hitPoint.y
				ThirdPerson.velocityY = 0.0
				ThirdPerson.grounded = true
				ThirdPerson.groundY = hit.hitPoint.y
			end
			break
		end
	end

	ThirdPerson.position = newPosition

	if ThirdPerson.position.y < -20.0 then
		ThirdPerson.position = vec3(0.0, 0.0, 0.0)
		ThirdPerson.velocityY = 0.0
	end

	-- character transform
	local charMat = mat4()
	charMat.position = ThirdPerson.position
	charMat.rotation = eulerRot(0.0, ThirdPerson.charYaw, 0.0)
	charMat.scale = vec3(0.7, 0.7, 0.7)
	setModelMatrix(ThirdPerson.charRoot, charMat)

	-- walk animation
	local moving = vec3Length(moveDir) > 0.0
	if moving then
		ThirdPerson.walkAnimTime = ThirdPerson.walkAnimTime + dt * ThirdPerson.animSpeed
		ThirdPerson.walkAnimTime = ThirdPerson.walkAnimTime % 1.0
	else
		ThirdPerson.walkAnimTime = ThirdPerson.walkAnimTime * (1.0 - dt * ThirdPerson.animDecelOnStop)
		if ThirdPerson.walkAnimTime < 0.001 then ThirdPerson.walkAnimTime = 0.0 end
	end

	for name, data in pairs(ThirdPerson.walkAnim) do
		if type(data) == "table" and name ~= "modelFilename" then
			local model = modelFilter({ThirdPerson.charRoot}, MODEL_FILTER_NAME, name)[1]
			if model ~= nil then
				animPose(model, data, ThirdPerson.walkAnimTime, 1.0)
			end
		end
	end

	-- jump animation
	local jumpHeight = ThirdPerson.position.y - ThirdPerson.groundY
	local jumpBlend = 0.0
	if jumpHeight > 0.0 then
		local maxHeight = (ThirdPerson.jumpSpeed * ThirdPerson.jumpSpeed) / (2.0 * -ThirdPerson.gravity)
		local normalized = jumpHeight / maxHeight
		if normalized > 1.0 then normalized = 1.0 end
		jumpBlend = normalized
	end

	if jumpBlend > 0.0 then
		ThirdPerson.jumpAnimTime = ThirdPerson.jumpAnimTime + dt * ThirdPerson.jumpAnimBlendSpeed
		if ThirdPerson.jumpAnimTime > 1.0 then ThirdPerson.jumpAnimTime = 1.0 end

		for name, data in pairs(ThirdPerson.jumpAnim) do
			if type(data) == "table" and name ~= "modelFilename" then
				local model = modelFilter({ThirdPerson.charRoot}, MODEL_FILTER_NAME, name)[1]
				if model ~= nil then
					animPose(model, data, ThirdPerson.jumpAnimTime, jumpBlend)
				end
			end
		end
	else
		ThirdPerson.jumpAnimTime = 0.0
	end

	-- camera: orbit behind and above character
	local cameraMat = mat4()
	cameraMat.rotation = eulerRot(ThirdPerson.pitch, 0.0, 0.0) * eulerRot(0.0, ThirdPerson.cameraYaw, 0.0)

	local cameraForward = cameraMat.forwardAxis
	local cameraPos = ThirdPerson.position + vec3(0.0, ThirdPerson.cameraHeight, 0.0) + cameraForward * ThirdPerson.cameraDistance

	cameraMat.position = cameraPos
	setCamera(cameraMat, ThirdPerson.fov)

	-- render
	local lightDir = vec3Normalize(vec3(0.5, 1.0, 0.7))
	local lightColor = vec3(255.0, 255.0, 255.0)
	local ambientColor = vec3(100.0, 100.0, 100.0)

	PolyUtil.beginLighting()
	for i = 1, #ThirdPerson.levelMeshes do
		PolyUtil.meshLight(ThirdPerson.levelMeshes[i], lightDir, lightColor, ambientColor)
		drawMesh(ThirdPerson.levelMeshes[i])
	end

	for i = 1, #ThirdPerson.charMeshes do
		PolyUtil.meshLight(ThirdPerson.charMeshes[i], lightDir, lightColor, ambientColor)
		drawMesh(ThirdPerson.charMeshes[i])
	end
	PolyUtil.endLighting()
end
