ludic/packages/ludic.character/camera.ludic

71 lines
3.9 KiB
Text

# camera.ludic - the orbit: a pivot at the chest easing after the body, the camera on its orbit
# behind and a share of the distance to the right (so the body sits left of the crosshair), lifted
# out of the ground and aimed back at the pivot. Or the eyes, with the gait's bob on them.
function chr_eyes_allowed(character_st: CharacterState) -> bool { return not CharacterBody.riding() and not character_st.chr_sit and CharacterBody.eyes_ok() }
function chr_shoulder(character_st: CharacterState) -> float { return Math.clamp(character_st.chr_cam_dist * 0.30, 0.6, 1.9) }
export function char_camera(character_st: mut CharacterState, dt: float) -> void {
if character_st.chr_fps and chr_eyes_allowed(character_st) {
chr_camera_eyes(character_st, dt)
return
}
char_camera_third(character_st, dt)
}
# over the shoulder, whatever the player plays in (a figure looked at from inside its head is not)
export function char_camera_third(character_st: mut CharacterState, dt: float) -> void {
character_st.chr_eyes_now = false
let k = Math.min(1.0, 10.0 * dt)
var ty = character_st.chr_y + 1.45
if character_st.chr_swim { ty = character_st.chr_y + 0.9 }
character_st.chr_piv_x = Math.lerp(character_st.chr_piv_x, character_st.chr_x, k)
character_st.chr_piv_y = Math.lerp(character_st.chr_piv_y, ty, k)
character_st.chr_piv_z = Math.lerp(character_st.chr_piv_z, character_st.chr_z, k)
let cyw = Math.cos(character_st.chr_cam_yaw)
let syw = Math.sin(character_st.chr_cam_yaw)
let cpt = Math.cos(character_st.chr_cam_pitch)
let fx = -(syw * cpt)
let fy = Math.sin(character_st.chr_cam_pitch)
let fz = -(cyw * cpt)
let ax = character_st.chr_piv_x + cyw * chr_shoulder(character_st)
let az = character_st.chr_piv_z - syw * chr_shoulder(character_st)
let cx = ax - fx * character_st.chr_cam_dist
var cyy = character_st.chr_piv_y - fy * character_st.chr_cam_dist
let cz = az - fz * character_st.chr_cam_dist
cyy = Math.max(cyy, CharacterGround.bed(cx, cz) + 0.55)
# at the top of the water the camera is held clear of it; under it, it follows the dive
if character_st.chr_swim and not character_st.chr_dive and cyy < character_st.chr_water + 0.35 { cyy = character_st.chr_water + 0.35 }
let mid_g = CharacterGround.bed((cx + ax) * 0.5, (cz + az) * 0.5) + 0.4
let mid_y = (cyy + character_st.chr_piv_y) * 0.5
if mid_y < mid_g { cyy = cyy + (mid_g - mid_y) * 2.0 }
character_st.chr_eye_x = cx
character_st.chr_eye_y = cyy
character_st.chr_eye_z = cz
let ddx = ax - cx
let ddz = az - cz
# straight over or under the pivot the offset has no heading of its own: keep the look's
if ddx * ddx + ddz * ddz > 0.0025 { character_st.chr_eye_yaw = Math.atan2(-ddx, -ddz) } else { character_st.chr_eye_yaw = character_st.chr_cam_yaw }
character_st.chr_eye_pitch = Math.atan2(character_st.chr_piv_y - cyy, Math.sqrt(ddx * ddx + ddz * ddz))
}
# the eyes: the head's height, the walk's bob, and the look the player is already holding
function chr_camera_eyes(character_st: mut CharacterState, dt: float) -> void {
character_st.chr_eyes_now = true
var eye = 1.62
if character_st.chr_swim { eye = 0.42 }
if character_st.chr_dive { eye = 0.30 }
let moving = Math.clamp(character_st.chr_speed / 5.0, 0.0, 1.0)
let want = 0.035 * moving * (1.0 - Math.cos(character_st.chr_phase * 2.0))
character_st.chr_fps_bob = Math.lerp(character_st.chr_fps_bob, want, Math.min(1.0, 12.0 * dt))
# never inside the ground on a steep step
let ey = Math.max(character_st.chr_y + eye + character_st.chr_fps_bob, CharacterGround.bed(character_st.chr_x, character_st.chr_z) + 0.30)
character_st.chr_piv_x = character_st.chr_x
character_st.chr_piv_y = ey
character_st.chr_piv_z = character_st.chr_z
character_st.chr_eye_x = character_st.chr_x
character_st.chr_eye_y = ey
character_st.chr_eye_z = character_st.chr_z
character_st.chr_eye_yaw = character_st.chr_cam_yaw
character_st.chr_eye_pitch = character_st.chr_cam_pitch
}