#include "horse_animation_controller.h" #include "../humanoid/rig.h" #include #include #include namespace Render::GL { namespace { constexpr float k_pi = std::numbers::pi_v; constexpr float k_idle_breathing_primary_freq = 0.4F; constexpr float k_idle_breathing_secondary_freq = 0.15F; constexpr float k_idle_breathing_primary_weight = 0.6F; constexpr float k_idle_breathing_secondary_weight = 0.4F; constexpr float k_idle_phase_speed = 0.15F; struct GaitParameters { float cycle_time; float front_leg_phase; float rear_leg_phase; float stride_swing; float stride_lift; float bob_amplitude; }; constexpr GaitParameters getGaitParams(GaitType gait) { switch (gait) { case GaitType::IDLE: return {2.5F, 0.0F, 0.0F, 0.015F, 0.008F, 0.004F}; case GaitType::WALK: return {1.1F, 0.50F, 0.0F, 0.42F, 0.18F, 0.016F}; case GaitType::TROT: return {0.55F, 0.0F, 0.50F, 0.62F, 0.32F, 0.028F}; case GaitType::CANTER: return {0.48F, 0.62F, 0.35F, 0.65F, 0.35F, 0.032F}; case GaitType::GALLOP: return {0.38F, 0.50F, 0.15F, 0.85F, 0.48F, 0.042F}; } return {2.5F, 0.0F, 0.0F, 0.015F, 0.008F, 0.004F}; } } // namespace HorseAnimationController::HorseAnimationController( HorseProfile &profile, const AnimationInputs &anim, const HumanoidAnimationContext &rider_ctx) : m_profile(profile), m_anim(anim), m_rider_ctx(rider_ctx) { m_phase = 0.0F; m_bob = 0.0F; m_rein_slack = 0.05F; m_speed = 0.0F; m_turn_angle = 0.0F; m_banking = 0.0F; m_is_rearing = false; m_rear_height = 0.0F; m_is_kicking = false; m_kick_rear_legs = false; m_kick_power = 0.0F; m_is_bucking = false; m_buck_intensity = 0.0F; m_is_jumping = false; m_jump_height = 0.0F; m_jump_distance = 0.0F; m_target_gait = GaitType::IDLE; m_gait_transition_progress = 1.0F; m_transition_start_time = 0.0F; } void HorseAnimationController::set_gait(GaitType gait) { m_current_gait = gait; m_target_gait = gait; m_gait_transition_progress = 1.0F; switch (gait) { case GaitType::IDLE: m_speed = 0.0F; break; case GaitType::WALK: m_speed = 1.5F; break; case GaitType::TROT: m_speed = 4.0F; break; case GaitType::CANTER: m_speed = 6.5F; break; case GaitType::GALLOP: m_speed = 10.0F; break; } update_gait_parameters(); } void HorseAnimationController::idle(float bob_intensity) { m_current_gait = GaitType::IDLE; m_speed = 0.0F; float const phase = std::fmod(m_anim.time * 0.25F, 1.0F); m_phase = phase; m_bob = std::sin(phase * 2.0F * k_pi) * m_profile.dims.idle_bob_amplitude * bob_intensity; update_gait_parameters(); } void HorseAnimationController::accelerate(float speed_delta) { m_speed += speed_delta; m_speed = std::max(0.0F, m_speed); GaitType new_gait; if (m_speed < 0.5F) { new_gait = GaitType::IDLE; } else if (m_speed < 3.0F) { new_gait = GaitType::WALK; } else if (m_speed < 5.5F) { new_gait = GaitType::TROT; } else if (m_speed < 8.0F) { new_gait = GaitType::CANTER; } else { new_gait = GaitType::GALLOP; } if (m_current_gait != new_gait) { m_target_gait = new_gait; m_gait_transition_progress = 0.0F; m_transition_start_time = m_anim.time; } update_gait_parameters(); } void HorseAnimationController::decelerate(float speed_delta) { accelerate(-speed_delta); } void HorseAnimationController::turn(float yaw_radians, float banking_amount) { m_turn_angle = yaw_radians; m_banking = std::clamp(banking_amount, -1.0F, 1.0F); } void HorseAnimationController::strafe_step(bool left, float distance) { float const direction = left ? -1.0F : 1.0F; m_phase = std::fmod(m_phase + direction * distance * 0.1F, 1.0F); } void HorseAnimationController::rear(float height_factor) { m_is_rearing = true; m_rear_height = std::clamp(height_factor, 0.0F, 1.0F); } void HorseAnimationController::kick(bool rear_legs, float power) { m_is_kicking = true; m_kick_rear_legs = rear_legs; m_kick_power = std::clamp(power, 0.0F, 1.0F); } void HorseAnimationController::buck(float intensity) { m_is_bucking = true; m_buck_intensity = std::clamp(intensity, 0.0F, 1.0F); } void HorseAnimationController::jump_obstacle(float height, float distance) { m_is_jumping = true; m_jump_height = std::max(0.0F, height); m_jump_distance = std::max(0.0F, distance); } auto HorseAnimationController::get_current_phase() const -> float { return m_phase; } auto HorseAnimationController::get_current_bob() const -> float { return m_bob; } auto HorseAnimationController::get_stride_cycle() const -> float { GaitParameters const params = getGaitParams(m_current_gait); return params.cycle_time; } void HorseAnimationController::update_gait_parameters() { constexpr float transition_duration = 0.3F; if (m_gait_transition_progress < 1.0F) { float const elapsed = m_anim.time - m_transition_start_time; m_gait_transition_progress = std::min(1.0F, elapsed / transition_duration); if (m_gait_transition_progress >= 1.0F) { m_current_gait = m_target_gait; } } GaitParameters const current_params = getGaitParams(m_current_gait); GaitParameters target_params = current_params; if (m_gait_transition_progress < 1.0F) { target_params = getGaitParams(m_target_gait); float const t = m_gait_transition_progress; float const ease_t = t * t * (3.0F - 2.0F * t); m_profile.gait.cycle_time = current_params.cycle_time + ease_t * (target_params.cycle_time - current_params.cycle_time); m_profile.gait.front_leg_phase = current_params.front_leg_phase + ease_t * (target_params.front_leg_phase - current_params.front_leg_phase); m_profile.gait.rear_leg_phase = current_params.rear_leg_phase + ease_t * (target_params.rear_leg_phase - current_params.rear_leg_phase); m_profile.gait.stride_swing = current_params.stride_swing + ease_t * (target_params.stride_swing - current_params.stride_swing); m_profile.gait.stride_lift = current_params.stride_lift + ease_t * (target_params.stride_lift - current_params.stride_lift); } else { m_profile.gait.cycle_time = current_params.cycle_time; m_profile.gait.front_leg_phase = current_params.front_leg_phase; m_profile.gait.rear_leg_phase = current_params.rear_leg_phase; m_profile.gait.stride_swing = current_params.stride_swing; m_profile.gait.stride_lift = current_params.stride_lift; } bool const is_moving = m_current_gait != GaitType::IDLE; if (is_moving) { if (m_rider_ctx.gait.cycle_time > 0.0001F) { m_phase = m_rider_ctx.gait.cycle_phase; } else { m_phase = std::fmod(m_anim.time / m_profile.gait.cycle_time, 1.0F); } float const rider_intensity = m_rider_ctx.locomotion_normalized_speed(); float const bob_amp = m_profile.dims.idle_bob_amplitude + rider_intensity * (m_profile.dims.move_bob_amplitude - m_profile.dims.idle_bob_amplitude); float const primary_bob = std::sin(m_phase * 2.0F * k_pi); float const secondary_bob = std::sin(m_phase * 4.0F * k_pi) * 0.25F; float const tertiary_variation = std::sin(m_anim.time * 0.7F) * 0.08F + 1.0F; float bob_pattern = primary_bob; if (m_current_gait == GaitType::TROT) { bob_pattern = (primary_bob + secondary_bob * 0.5F); } else if (m_current_gait == GaitType::CANTER) { bob_pattern = primary_bob + std::sin(m_phase * 3.0F * k_pi) * 0.15F; } else if (m_current_gait == GaitType::GALLOP) { bob_pattern = primary_bob * 1.2F + secondary_bob * 0.3F; } m_bob = bob_pattern * bob_amp * tertiary_variation; } else { float const breathing = std::sin(m_anim.time * k_idle_breathing_primary_freq) * k_idle_breathing_primary_weight + std::sin(m_anim.time * k_idle_breathing_secondary_freq) * k_idle_breathing_secondary_weight; m_phase = std::fmod(m_anim.time * k_idle_phase_speed, 1.0F); m_bob = breathing * m_profile.dims.idle_bob_amplitude * 0.8F; } float rein_tension = m_rider_ctx.locomotion_normalized_speed(); if (m_rider_ctx.gait.has_target) { rein_tension += 0.25F; } if (m_rider_ctx.is_attacking()) { rein_tension += 0.35F; } rein_tension = std::clamp(rein_tension, 0.0F, 1.0F); m_rein_slack = std::max(0.01F, m_rein_slack * (1.0F - rein_tension)); } } // namespace Render::GL