

ThirdPersonExample = {}
ThirdPersonExample.description = ""

function ThirdPersonExample.start()
	setMouseLocked(true)

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

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

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

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

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

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

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

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

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

	-- movement relative to camera yaw
	local yawMat = mat4()
	yawMat.rotation = eulerRot(0.0, ThirdPersonExample.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)
		ThirdPersonExample.charYaw = radToDeg(atan2(moveDir.x, moveDir.z))
	end
	
	local newPosition = ThirdPersonExample.position + moveDir * ThirdPersonExample.walkSpeed * dt

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

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

	-- wall collision
	local wallCollisionRadius = ThirdPersonExample.collisionRadius * 0.5
	for i = 1, #ThirdPersonExample.wallModels do
		local hit = sphereCast(newPosition, wallCollisionRadius, ThirdPersonExample.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, #ThirdPersonExample.floorModels do
		local hit = sphereCast(newPosition, ThirdPersonExample.collisionRadius, ThirdPersonExample.floorModels[i])
		if hit.hitDistance >= 0.0 then
			onPlatform = true
			if newPosition.y <= hit.hitPoint.y then
				newPosition.y = hit.hitPoint.y
				ThirdPersonExample.velocityY = 0.0
				ThirdPersonExample.grounded = true
				ThirdPersonExample.groundY = hit.hitPoint.y
			end
			break
		end
	end

	ThirdPersonExample.position = newPosition

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

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

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

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

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

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

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

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

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

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

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

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

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