#include "pose_controller.h" #include "humanoid_math.h" #include "spear_pose_utils.h" #include #include #include #include namespace Render::GL { HumanoidPoseController::HumanoidPoseController( HumanoidPose &pose, const HumanoidAnimationContext &anim_ctx) : m_pose(pose), m_anim_ctx(anim_ctx) {} void HumanoidPoseController::standIdle() {} void HumanoidPoseController::kneel(float depth) { using HP = HumanProportions; depth = std::clamp(depth, 0.0F, 1.0F); if (depth < 1e-6F) { return; } float const kneel_offset = depth * 0.40F; float const pelvis_y = HP::WAIST_Y - kneel_offset; m_pose.pelvis_pos.setY(pelvis_y); float const stance_narrow = 0.11F; float const left_knee_y = HP::GROUND_Y + 0.07F * depth; float const left_knee_z = -0.06F * depth; m_pose.knee_l = QVector3D(-stance_narrow, left_knee_y, left_knee_z); m_pose.foot_l = QVector3D(-stance_narrow - 0.025F, HP::GROUND_Y, left_knee_z - HP::LOWER_LEG_LEN * 0.93F * depth); float const right_knee_y = pelvis_y - 0.12F; float const right_foot_z = 0.28F * depth; m_pose.knee_r = QVector3D(stance_narrow, right_knee_y, right_foot_z - 0.05F); m_pose.foot_r = QVector3D(stance_narrow, HP::GROUND_Y + m_pose.foot_y_offset, right_foot_z); float const upper_body_drop = kneel_offset; m_pose.shoulder_l.setY(m_pose.shoulder_l.y() - upper_body_drop); m_pose.shoulder_r.setY(m_pose.shoulder_r.y() - upper_body_drop); m_pose.neck_base.setY(m_pose.neck_base.y() - upper_body_drop); m_pose.head_pos.setY(m_pose.head_pos.y() - upper_body_drop); } void HumanoidPoseController::lean(const QVector3D &direction, float amount) { amount = std::clamp(amount, 0.0F, 1.0F); QVector3D dir = direction; if (dir.lengthSquared() > 1e-6F) { dir.normalize(); } else { dir = QVector3D(0.0F, 0.0F, 1.0F); } float const lean_magnitude = 0.12F * amount; QVector3D const lean_offset = dir * lean_magnitude; m_pose.shoulder_l += lean_offset; m_pose.shoulder_r += lean_offset; m_pose.neck_base += lean_offset * 0.85F; m_pose.head_pos += lean_offset * 0.75F; } void HumanoidPoseController::placeHandAt(bool is_left, const QVector3D &target_position) { get_hand(is_left) = target_position; const QVector3D &shoulder = get_shoulder(is_left); const QVector3D outward_dir = compute_outward_dir(is_left); float const along_frac = is_left ? 0.45F : 0.48F; float const lateral_offset = is_left ? 0.15F : 0.12F; float const y_bias = is_left ? -0.08F : 0.02F; float const outward_sign = 1.0F; get_elbow(is_left) = solve_elbow_ik(is_left, shoulder, target_position, outward_dir, along_frac, lateral_offset, y_bias, outward_sign); } auto HumanoidPoseController::solve_elbow_ik( bool, const QVector3D &shoulder, const QVector3D &hand, const QVector3D &outward_dir, float along_frac, float lateral_offset, float y_bias, float outward_sign) const -> QVector3D { return elbow_bend_torso(shoulder, hand, outward_dir, along_frac, lateral_offset, y_bias, outward_sign); } auto HumanoidPoseController::solve_knee_ik( bool is_left, const QVector3D &hip, const QVector3D &foot, float height_scale) const -> QVector3D { using HP = HumanProportions; QVector3D hip_to_foot = foot - hip; float const distance = hip_to_foot.length(); if (distance < 1e-5F) { return hip; } float const upper_len = HP::UPPER_LEG_LEN * height_scale; float const lower_len = HP::LOWER_LEG_LEN * height_scale; float const reach = upper_len + lower_len; float const min_reach = std::max(std::abs(upper_len - lower_len) + 1e-4F, 1e-3F); float const max_reach = std::max(reach - 1e-4F, min_reach + 1e-4F); float const clamped_dist = std::clamp(distance, min_reach, max_reach); QVector3D const dir = hip_to_foot / distance; float cos_theta = (upper_len * upper_len + clamped_dist * clamped_dist - lower_len * lower_len) / (2.0F * upper_len * clamped_dist); cos_theta = std::clamp(cos_theta, -1.0F, 1.0F); float const sin_theta = std::sqrt(std::max(0.0F, 1.0F - cos_theta * cos_theta)); QVector3D bend_pref = is_left ? QVector3D(-0.24F, 0.0F, 0.95F) : QVector3D(0.24F, 0.0F, 0.95F); bend_pref.normalize(); QVector3D bend_axis = bend_pref - dir * QVector3D::dotProduct(dir, bend_pref); if (bend_axis.lengthSquared() < 1e-6F) { bend_axis = QVector3D::crossProduct(dir, QVector3D(0.0F, 1.0F, 0.0F)); if (bend_axis.lengthSquared() < 1e-6F) { bend_axis = QVector3D::crossProduct(dir, QVector3D(1.0F, 0.0F, 0.0F)); } } bend_axis.normalize(); QVector3D knee = hip + dir * (cos_theta * upper_len) + bend_axis * (sin_theta * upper_len); float const knee_floor = HP::GROUND_Y + m_pose.foot_y_offset * 0.5F; if (knee.y() < knee_floor) { knee.setY(knee_floor); } if (knee.y() > hip.y()) { knee.setY(hip.y()); } return knee; } auto HumanoidPoseController::get_shoulder(bool is_left) const -> const QVector3D & { return is_left ? m_pose.shoulder_l : m_pose.shoulder_r; } auto HumanoidPoseController::get_hand(bool is_left) -> QVector3D & { return is_left ? m_pose.hand_l : m_pose.hand_r; } auto HumanoidPoseController::get_hand(bool is_left) const -> const QVector3D & { return is_left ? m_pose.hand_l : m_pose.hand_r; } auto HumanoidPoseController::get_elbow(bool is_left) -> QVector3D & { return is_left ? m_pose.elbow_l : m_pose.elbow_r; } auto HumanoidPoseController::compute_right_axis() const -> QVector3D { QVector3D right_axis = m_pose.shoulder_r - m_pose.shoulder_l; right_axis.setY(0.0F); if (right_axis.lengthSquared() < 1e-8F) { right_axis = QVector3D(1.0F, 0.0F, 0.0F); } right_axis.normalize(); return right_axis; } auto HumanoidPoseController::compute_outward_dir(bool is_left) const -> QVector3D { QVector3D const right_axis = compute_right_axis(); return is_left ? -right_axis : right_axis; } auto HumanoidPoseController::get_shoulder_y(bool is_left) const -> float { return is_left ? m_pose.shoulder_l.y() : m_pose.shoulder_r.y(); } auto HumanoidPoseController::get_pelvis_y() const -> float { return m_pose.pelvis_pos.y(); } void HumanoidPoseController::aim_bow(float draw_phase) { using HP = HumanProportions; draw_phase = std::clamp(draw_phase, 0.0F, 1.0F); QVector3D const aim_pos(-0.02F, HP::SHOULDER_Y + 0.18F, 0.42F); QVector3D const draw_pos(-0.05F, HP::SHOULDER_Y + 0.12F, 0.22F); QVector3D const release_pos(-0.02F, HP::SHOULDER_Y + 0.20F, 0.34F); QVector3D hand_l_target; float shoulder_twist = 0.0F; float head_recoil = 0.0F; if (draw_phase < 0.20F) { float t = draw_phase / 0.20F; t = t * t; hand_l_target = aim_pos * (1.0F - t) + draw_pos * t; shoulder_twist = t * 0.08F; } else if (draw_phase < 0.50F) { hand_l_target = draw_pos; shoulder_twist = 0.08F; } else if (draw_phase < 0.58F) { float t = (draw_phase - 0.50F) / 0.08F; t = t * t * t; hand_l_target = draw_pos * (1.0F - t) + release_pos * t; shoulder_twist = 0.08F * (1.0F - t * 0.6F); head_recoil = t * 0.04F; } else { float t = (draw_phase - 0.58F) / 0.42F; t = 1.0F - (1.0F - t) * (1.0F - t); hand_l_target = release_pos * (1.0F - t) + aim_pos * t; shoulder_twist = 0.08F * 0.4F * (1.0F - t); head_recoil = 0.04F * (1.0F - t); } QVector3D const hand_r_target(0.03F, HP::SHOULDER_Y + 0.08F, 0.55F); placeHandAt(false, hand_r_target); placeHandAt(true, hand_l_target); if (shoulder_twist > 0.01F) { m_pose.shoulder_l.setY(m_pose.shoulder_l.y() + shoulder_twist); m_pose.shoulder_r.setY(m_pose.shoulder_r.y() - shoulder_twist * 0.5F); } if (head_recoil > 0.01F) { m_pose.head_pos.setZ(m_pose.head_pos.z() - head_recoil); } } void HumanoidPoseController::meleeStrike(float strike_phase) { using HP = HumanProportions; strike_phase = std::clamp(strike_phase, 0.0F, 1.0F); // Define key positions for a natural horizontal slash toward the target QVector3D const rest_pos(0.22F, HP::SHOULDER_Y + 0.02F, 0.18F); QVector3D const chamber_pos(0.30F, HP::SHOULDER_Y + 0.08F, 0.05F); QVector3D const strike_pos(0.28F, HP::SHOULDER_Y - 0.05F, 0.65F); QVector3D const followthrough_pos(0.10F, HP::SHOULDER_Y - 0.12F, 0.55F); QVector3D hand_r_target; QVector3D hand_l_target; // Body dynamics float torso_twist = 0.0F; float forward_lean = 0.0F; float shoulder_dip = 0.0F; float step_forward = 0.0F; if (strike_phase < 0.20F) { // Phase 1: Chamber - pull weapon back, twist torso away float t = strike_phase / 0.20F; float ease_t = t * t; hand_r_target = rest_pos * (1.0F - ease_t) + chamber_pos * ease_t; hand_l_target = QVector3D(-0.18F, HP::SHOULDER_Y + 0.02F, 0.22F - 0.08F * t); // Twist torso back to coil for the strike torso_twist = -0.04F * ease_t; shoulder_dip = -0.02F * ease_t; } else if (strike_phase < 0.28F) { // Phase 2: Brief anticipation hold hand_r_target = chamber_pos; hand_l_target = QVector3D(-0.18F, HP::SHOULDER_Y + 0.02F, 0.14F); torso_twist = -0.04F; shoulder_dip = -0.02F; } else if (strike_phase < 0.48F) { // Phase 3: Explosive strike - uncoil torso, step forward float t = (strike_phase - 0.28F) / 0.20F; float power_t = t * t * (3.0F - 2.0F * t); // smoothstep for power hand_r_target = chamber_pos * (1.0F - power_t) + strike_pos * power_t; hand_l_target = QVector3D(-0.18F + 0.06F * power_t, HP::SHOULDER_Y + 0.02F - 0.08F * power_t, 0.14F + 0.20F * power_t); // Uncoil torso forward and rotate into strike torso_twist = -0.04F + 0.10F * power_t; forward_lean = 0.08F * power_t; shoulder_dip = -0.02F + 0.05F * power_t; step_forward = 0.06F * power_t; } else if (strike_phase < 0.65F) { // Phase 4: Follow-through - weapon continues past target float t = (strike_phase - 0.48F) / 0.17F; float ease_t = t * t; hand_r_target = strike_pos * (1.0F - ease_t) + followthrough_pos * ease_t; hand_l_target = QVector3D(-0.12F, HP::SHOULDER_Y - 0.06F, 0.34F); torso_twist = 0.06F - 0.02F * t; forward_lean = 0.08F - 0.03F * t; shoulder_dip = 0.03F; step_forward = 0.06F; } else { // Phase 5: Recovery - return to guard float t = (strike_phase - 0.65F) / 0.35F; float ease_t = 1.0F - (1.0F - t) * (1.0F - t); hand_r_target = followthrough_pos * (1.0F - ease_t) + rest_pos * ease_t; hand_l_target = QVector3D(-0.12F + (-0.18F + 0.12F) * ease_t, HP::SHOULDER_Y - 0.06F * (1.0F - ease_t) + 0.02F * ease_t, 0.34F * (1.0F - ease_t) + 0.22F * ease_t); torso_twist = 0.04F * (1.0F - ease_t); forward_lean = 0.05F * (1.0F - ease_t); shoulder_dip = 0.03F * (1.0F - ease_t); step_forward = 0.06F * (1.0F - ease_t); } // Apply body dynamics if (std::abs(torso_twist) > 0.001F) { m_pose.shoulder_r.setZ(m_pose.shoulder_r.z() + torso_twist); m_pose.shoulder_l.setZ(m_pose.shoulder_l.z() - torso_twist * 0.5F); } if (forward_lean > 0.001F) { m_pose.shoulder_l.setZ(m_pose.shoulder_l.z() + forward_lean); m_pose.shoulder_r.setZ(m_pose.shoulder_r.z() + forward_lean); m_pose.neck_base.setZ(m_pose.neck_base.z() + forward_lean * 0.8F); m_pose.head_pos.setZ(m_pose.head_pos.z() + forward_lean * 0.6F); } if (std::abs(shoulder_dip) > 0.001F) { m_pose.shoulder_r.setY(m_pose.shoulder_r.y() + shoulder_dip); } if (step_forward > 0.001F) { m_pose.foot_r.setZ(m_pose.foot_r.z() + step_forward); m_pose.knee_r.setZ(m_pose.knee_r.z() + step_forward * 0.5F); } placeHandAt(false, hand_r_target); placeHandAt(true, hand_l_target); } void HumanoidPoseController::graspTwoHanded(const QVector3D &grip_center, float hand_separation) { hand_separation = std::clamp(hand_separation, 0.1F, 0.8F); QVector3D const right_axis = compute_right_axis(); QVector3D const right_hand_pos = grip_center + right_axis * (hand_separation * 0.5F); QVector3D const left_hand_pos = grip_center - right_axis * (hand_separation * 0.5F); placeHandAt(false, right_hand_pos); placeHandAt(true, left_hand_pos); } void HumanoidPoseController::spearThrust(float attack_phase) { using HP = HumanProportions; attack_phase = std::clamp(attack_phase, 0.0F, 1.0F); QVector3D const guard_pos(0.28F, HP::SHOULDER_Y + 0.05F, 0.25F); QVector3D const prepare_pos(0.35F, HP::SHOULDER_Y + 0.08F, 0.05F); QVector3D const thrust_pos(0.32F, HP::SHOULDER_Y + 0.10F, 0.90F); QVector3D const recover_pos(0.28F, HP::SHOULDER_Y + 0.06F, 0.40F); QVector3D hand_r_target; QVector3D hand_l_target; auto easeInOutCubic = [](float t) { return t < 0.5F ? 4.0F * t * t * t : 1.0F - std::pow(-2.0F * t + 2.0F, 3.0F) / 2.0F; }; auto smoothstep = [](float edge0, float edge1, float x) { float t = std::clamp((x - edge0) / (edge1 - edge0), 0.0F, 1.0F); return t * t * (3.0F - 2.0F * t); }; auto lerp = [](float a, float b, float t) { return a * (1.0F - t) + b * t; }; if (attack_phase < 0.20F) { float const t = easeInOutCubic(attack_phase / 0.20F); hand_r_target = guard_pos * (1.0F - t) + prepare_pos * t; hand_l_target = QVector3D(-0.10F, HP::SHOULDER_Y - 0.05F, 0.20F * (1.0F - t) + 0.08F * t); } else if (attack_phase < 0.30F) { hand_r_target = prepare_pos; hand_l_target = QVector3D(-0.10F, HP::SHOULDER_Y - 0.05F, 0.08F); } else if (attack_phase < 0.50F) { float t = (attack_phase - 0.30F) / 0.20F; t = t * t * t; hand_r_target = prepare_pos * (1.0F - t) + thrust_pos * t; hand_l_target = QVector3D(-0.10F + 0.05F * t, HP::SHOULDER_Y - 0.05F + 0.03F * t, 0.08F + 0.45F * t); } else if (attack_phase < 0.70F) { float const t = easeInOutCubic((attack_phase - 0.50F) / 0.20F); hand_r_target = thrust_pos * (1.0F - t) + recover_pos * t; hand_l_target = QVector3D(-0.05F * (1.0F - t) - 0.10F * t, HP::SHOULDER_Y - 0.02F * (1.0F - t) - 0.06F * t, lerp(0.53F, 0.35F, t)); } else { float const t = smoothstep(0.70F, 1.0F, attack_phase); hand_r_target = recover_pos * (1.0F - t) + guard_pos * t; hand_l_target = QVector3D(-0.10F - 0.02F * (1.0F - t), HP::SHOULDER_Y - 0.06F + 0.01F * t, lerp(0.35F, 0.25F, t)); } float const thrust_extent = std::clamp((attack_phase - 0.20F) / 0.60F, 0.0F, 1.0F); float const along_offset = -0.06F + 0.02F * thrust_extent; float const y_drop = 0.10F + 0.02F * thrust_extent; hand_l_target = computeOffhandSpearGrip(m_pose, m_anim_ctx, hand_r_target, false, along_offset, y_drop, -0.08F); placeHandAt(false, hand_r_target); placeHandAt(true, hand_l_target); } void HumanoidPoseController::sword_slash(float attack_phase) { using HP = HumanProportions; attack_phase = std::clamp(attack_phase, 0.0F, 1.0F); // Key positions for a diagonal downward slash QVector3D const rest_pos(0.20F, HP::SHOULDER_Y + 0.05F, 0.15F); QVector3D const chamber_pos(0.28F, HP::SHOULDER_Y + 0.20F, 0.02F); QVector3D const apex_pos(0.30F, HP::SHOULDER_Y + 0.25F, 0.08F); QVector3D const strike_pos(0.18F, HP::SHOULDER_Y - 0.15F, 0.62F); QVector3D const followthrough_pos(0.05F, HP::WAIST_Y + 0.10F, 0.50F); QVector3D const recover_pos(0.22F, HP::SHOULDER_Y + 0.02F, 0.22F); QVector3D hand_r_target; QVector3D hand_l_target; // Body dynamics float torso_twist = 0.0F; float forward_lean = 0.0F; float shoulder_rotation = 0.0F; float weight_shift = 0.0F; auto smoothstep = [](float t) { return t * t * (3.0F - 2.0F * t); }; auto ease_out = [](float t) { return 1.0F - (1.0F - t) * (1.0F - t); }; auto ease_in = [](float t) { return t * t; }; if (attack_phase < 0.15F) { // Phase 1: Prepare - raise weapon to chamber position float t = attack_phase / 0.15F; float ease_t = ease_in(t); hand_r_target = rest_pos * (1.0F - ease_t) + chamber_pos * ease_t; hand_l_target = QVector3D(-0.20F, HP::SHOULDER_Y - 0.02F, 0.15F + 0.02F * t); // Rotate shoulders back, coiling for strike torso_twist = -0.05F * ease_t; shoulder_rotation = 0.03F * ease_t; } else if (attack_phase < 0.28F) { // Phase 2: Apex - weapon at highest point, brief tension float t = (attack_phase - 0.15F) / 0.13F; float ease_t = smoothstep(t); hand_r_target = chamber_pos * (1.0F - ease_t) + apex_pos * ease_t; hand_l_target = QVector3D(-0.20F, HP::SHOULDER_Y - 0.04F, 0.17F); torso_twist = -0.05F; shoulder_rotation = 0.03F + 0.02F * ease_t; weight_shift = -0.02F * ease_t; // Weight shifts back before strike } else if (attack_phase < 0.48F) { // Phase 3: Strike - explosive diagonal slash downward float t = (attack_phase - 0.28F) / 0.20F; float power_t = t * t * t; // Fast acceleration hand_r_target = apex_pos * (1.0F - power_t) + strike_pos * power_t; hand_l_target = QVector3D(-0.20F + 0.08F * power_t, HP::SHOULDER_Y - 0.04F - 0.06F * power_t, 0.17F + 0.22F * power_t); // Explosive uncoil - torso rotates into strike, body leans forward torso_twist = -0.05F + 0.14F * power_t; forward_lean = 0.10F * power_t; shoulder_rotation = 0.05F - 0.08F * power_t; weight_shift = -0.02F + 0.08F * power_t; } else if (attack_phase < 0.62F) { // Phase 4: Follow-through - weapon continues past target float t = (attack_phase - 0.48F) / 0.14F; float ease_t = smoothstep(t); hand_r_target = strike_pos * (1.0F - ease_t) + followthrough_pos * ease_t; hand_l_target = QVector3D(-0.12F, HP::SHOULDER_Y - 0.10F, 0.39F); torso_twist = 0.09F - 0.03F * t; forward_lean = 0.10F - 0.02F * t; weight_shift = 0.06F; } else { // Phase 5: Recovery - return to guard float t = (attack_phase - 0.62F) / 0.38F; float ease_t = ease_out(t); hand_r_target = followthrough_pos * (1.0F - ease_t) + recover_pos * 0.5F * ease_t + rest_pos * 0.5F * ease_t; hand_l_target = QVector3D(-0.12F - 0.08F * ease_t, HP::SHOULDER_Y - 0.10F * (1.0F - ease_t), 0.39F * (1.0F - ease_t) + 0.15F * ease_t); torso_twist = 0.06F * (1.0F - ease_t); forward_lean = 0.08F * (1.0F - ease_t); weight_shift = 0.06F * (1.0F - ease_t); } // Apply body dynamics if (std::abs(torso_twist) > 0.001F) { m_pose.shoulder_r.setZ(m_pose.shoulder_r.z() + torso_twist); m_pose.shoulder_l.setZ(m_pose.shoulder_l.z() - torso_twist * 0.6F); } if (std::abs(shoulder_rotation) > 0.001F) { m_pose.shoulder_r.setY(m_pose.shoulder_r.y() - shoulder_rotation); m_pose.shoulder_l.setY(m_pose.shoulder_l.y() + shoulder_rotation * 0.4F); } if (forward_lean > 0.001F) { m_pose.shoulder_l.setZ(m_pose.shoulder_l.z() + forward_lean); m_pose.shoulder_r.setZ(m_pose.shoulder_r.z() + forward_lean); m_pose.neck_base.setZ(m_pose.neck_base.z() + forward_lean * 0.7F); m_pose.head_pos.setZ(m_pose.head_pos.z() + forward_lean * 0.5F); m_pose.pelvis_pos.setZ(m_pose.pelvis_pos.z() + forward_lean * 0.3F); } if (std::abs(weight_shift) > 0.001F) { m_pose.foot_r.setZ(m_pose.foot_r.z() + weight_shift); m_pose.knee_r.setZ(m_pose.knee_r.z() + weight_shift * 0.6F); } placeHandAt(false, hand_r_target); placeHandAt(true, hand_l_target); } void HumanoidPoseController::mount_on_horse(float saddle_height) { float const offset_y = saddle_height - m_pose.pelvis_pos.y(); m_pose.pelvis_pos.setY(saddle_height); } void HumanoidPoseController::hold_sword_and_shield() { using HP = HumanProportions; QVector3D const sword_hand_pos(0.30F, HP::SHOULDER_Y - 0.02F, 0.35F); QVector3D const shield_hand_pos(-0.22F, HP::SHOULDER_Y, 0.18F); placeHandAt(false, sword_hand_pos); placeHandAt(true, shield_hand_pos); } void HumanoidPoseController::look_at(const QVector3D &target) { QVector3D const head_to_target = target - m_pose.head_pos; if (head_to_target.lengthSquared() < 1e-6F) { return; } QVector3D const direction = head_to_target.normalized(); float const max_head_turn = 0.03F; QVector3D const head_offset = direction * max_head_turn; m_pose.head_pos += QVector3D(head_offset.x(), 0.0F, head_offset.z()); float const neck_follow = 0.5F; m_pose.neck_base += QVector3D(head_offset.x() * neck_follow, 0.0F, head_offset.z() * neck_follow); } void HumanoidPoseController::hit_flinch(float intensity) { intensity = std::clamp(intensity, 0.0F, 1.0F); if (intensity < 0.01F) { return; } float const flinch_back = intensity * 0.06F; float const flinch_down = intensity * 0.04F; float const shoulder_drop = intensity * 0.03F; m_pose.head_pos.setZ(m_pose.head_pos.z() - flinch_back); m_pose.head_pos.setY(m_pose.head_pos.y() - flinch_down * 0.5F); m_pose.neck_base.setZ(m_pose.neck_base.z() - flinch_back * 0.8F); m_pose.shoulder_l.setY(m_pose.shoulder_l.y() - shoulder_drop); m_pose.shoulder_r.setY(m_pose.shoulder_r.y() - shoulder_drop); m_pose.shoulder_l.setZ(m_pose.shoulder_l.z() - flinch_back * 0.6F); m_pose.shoulder_r.setZ(m_pose.shoulder_r.z() - flinch_back * 0.6F); m_pose.pelvis_pos.setY(m_pose.pelvis_pos.y() - flinch_down * 0.3F); } void HumanoidPoseController::sword_slash_variant(float attack_phase, std::uint8_t variant) { using HP = HumanProportions; attack_phase = std::clamp(attack_phase, 0.0F, 1.0F); // Base positions - will be modified by variant QVector3D rest_pos(0.20F, HP::SHOULDER_Y + 0.05F, 0.15F); QVector3D chamber_pos(0.28F, HP::SHOULDER_Y + 0.20F, 0.02F); QVector3D apex_pos(0.30F, HP::SHOULDER_Y + 0.25F, 0.08F); QVector3D strike_pos(0.18F, HP::SHOULDER_Y - 0.15F, 0.62F); QVector3D followthrough_pos(0.05F, HP::WAIST_Y + 0.10F, 0.50F); // Variant-specific attack patterns float strike_direction = 1.0F; // 1.0 = right-to-left, -1.0 = left-to-right switch (variant % 3) { case 1: // Left-to-right diagonal slash chamber_pos = QVector3D(-0.10F, HP::SHOULDER_Y + 0.22F, 0.04F); apex_pos = QVector3D(-0.08F, HP::SHOULDER_Y + 0.28F, 0.10F); strike_pos = QVector3D(0.32F, HP::SHOULDER_Y - 0.12F, 0.58F); followthrough_pos = QVector3D(0.40F, HP::WAIST_Y + 0.08F, 0.48F); strike_direction = -1.0F; break; case 2: // Horizontal slash from right chamber_pos = QVector3D(0.35F, HP::SHOULDER_Y + 0.10F, 0.0F); apex_pos = QVector3D(0.38F, HP::SHOULDER_Y + 0.08F, 0.06F); strike_pos = QVector3D(0.05F, HP::SHOULDER_Y - 0.05F, 0.65F); followthrough_pos = QVector3D(-0.10F, HP::SHOULDER_Y - 0.10F, 0.55F); break; default: break; } QVector3D hand_r_target; QVector3D hand_l_target; // Body dynamics float torso_twist = 0.0F; float forward_lean = 0.0F; float shoulder_rotation = 0.0F; float weight_shift = 0.0F; auto smoothstep = [](float t) { return t * t * (3.0F - 2.0F * t); }; auto ease_out = [](float t) { return 1.0F - (1.0F - t) * (1.0F - t); }; if (attack_phase < 0.15F) { float t = attack_phase / 0.15F; float ease_t = t * t; hand_r_target = rest_pos * (1.0F - ease_t) + chamber_pos * ease_t; hand_l_target = QVector3D(-0.20F, HP::SHOULDER_Y - 0.02F, 0.15F); torso_twist = -0.05F * strike_direction * ease_t; shoulder_rotation = 0.03F * ease_t; } else if (attack_phase < 0.28F) { float t = (attack_phase - 0.15F) / 0.13F; float ease_t = smoothstep(t); hand_r_target = chamber_pos * (1.0F - ease_t) + apex_pos * ease_t; hand_l_target = QVector3D(-0.20F, HP::SHOULDER_Y - 0.04F, 0.17F); torso_twist = -0.05F * strike_direction; shoulder_rotation = 0.03F + 0.02F * ease_t; weight_shift = -0.02F * ease_t; } else if (attack_phase < 0.48F) { float t = (attack_phase - 0.28F) / 0.20F; float power_t = t * t * t; hand_r_target = apex_pos * (1.0F - power_t) + strike_pos * power_t; hand_l_target = QVector3D(-0.20F + 0.08F * power_t, HP::SHOULDER_Y - 0.04F - 0.06F * power_t, 0.17F + 0.22F * power_t); torso_twist = -0.05F * strike_direction + 0.14F * strike_direction * power_t; forward_lean = 0.10F * power_t; shoulder_rotation = 0.05F - 0.08F * power_t; weight_shift = -0.02F + 0.08F * power_t; } else if (attack_phase < 0.62F) { float t = (attack_phase - 0.48F) / 0.14F; float ease_t = smoothstep(t); hand_r_target = strike_pos * (1.0F - ease_t) + followthrough_pos * ease_t; hand_l_target = QVector3D(-0.12F, HP::SHOULDER_Y - 0.10F, 0.39F); torso_twist = 0.09F * strike_direction - 0.03F * strike_direction * t; forward_lean = 0.10F - 0.02F * t; weight_shift = 0.06F; } else { float t = (attack_phase - 0.62F) / 0.38F; float ease_t = ease_out(t); hand_r_target = followthrough_pos * (1.0F - ease_t) + rest_pos * ease_t; hand_l_target = QVector3D(-0.12F - 0.08F * ease_t, HP::SHOULDER_Y - 0.10F * (1.0F - ease_t), 0.39F * (1.0F - ease_t) + 0.15F * ease_t); torso_twist = 0.06F * strike_direction * (1.0F - ease_t); forward_lean = 0.08F * (1.0F - ease_t); weight_shift = 0.06F * (1.0F - ease_t); } // Apply body dynamics if (std::abs(torso_twist) > 0.001F) { m_pose.shoulder_r.setZ(m_pose.shoulder_r.z() + torso_twist); m_pose.shoulder_l.setZ(m_pose.shoulder_l.z() - torso_twist * 0.6F); } if (std::abs(shoulder_rotation) > 0.001F) { m_pose.shoulder_r.setY(m_pose.shoulder_r.y() - shoulder_rotation); m_pose.shoulder_l.setY(m_pose.shoulder_l.y() + shoulder_rotation * 0.4F); } if (forward_lean > 0.001F) { m_pose.shoulder_l.setZ(m_pose.shoulder_l.z() + forward_lean); m_pose.shoulder_r.setZ(m_pose.shoulder_r.z() + forward_lean); m_pose.neck_base.setZ(m_pose.neck_base.z() + forward_lean * 0.7F); m_pose.head_pos.setZ(m_pose.head_pos.z() + forward_lean * 0.5F); } if (std::abs(weight_shift) > 0.001F) { m_pose.foot_r.setZ(m_pose.foot_r.z() + weight_shift); m_pose.knee_r.setZ(m_pose.knee_r.z() + weight_shift * 0.6F); } placeHandAt(false, hand_r_target); placeHandAt(true, hand_l_target); } void HumanoidPoseController::spear_thrust_variant(float attack_phase, std::uint8_t variant) { using HP = HumanProportions; attack_phase = std::clamp(attack_phase, 0.0F, 1.0F); QVector3D guard_pos(0.28F, HP::SHOULDER_Y + 0.05F, 0.25F); QVector3D prepare_pos(0.35F, HP::SHOULDER_Y + 0.08F, 0.05F); QVector3D thrust_pos(0.32F, HP::SHOULDER_Y + 0.10F, 0.90F); QVector3D recover_pos(0.28F, HP::SHOULDER_Y + 0.06F, 0.40F); switch (variant % 3) { case 1: prepare_pos = QVector3D(0.30F, HP::SHOULDER_Y + 0.15F, 0.0F); thrust_pos = QVector3D(0.28F, HP::WAIST_Y + 0.20F, 0.95F); break; case 2: prepare_pos = QVector3D(0.40F, HP::SHOULDER_Y, 0.10F); thrust_pos = QVector3D(0.35F, HP::SHOULDER_Y + 0.05F, 0.85F); break; default: break; } QVector3D hand_r_target; QVector3D hand_l_target; auto easeInOutCubic = [](float t) { return t < 0.5F ? 4.0F * t * t * t : 1.0F - std::pow(-2.0F * t + 2.0F, 3.0F) / 2.0F; }; auto smoothstep = [](float edge0, float edge1, float x) { float t = std::clamp((x - edge0) / (edge1 - edge0), 0.0F, 1.0F); return t * t * (3.0F - 2.0F * t); }; auto lerp = [](float a, float b, float t) { return a * (1.0F - t) + b * t; }; if (attack_phase < 0.20F) { float const t = easeInOutCubic(attack_phase / 0.20F); hand_r_target = guard_pos * (1.0F - t) + prepare_pos * t; hand_l_target = QVector3D(-0.10F, HP::SHOULDER_Y - 0.05F, 0.20F * (1.0F - t) + 0.08F * t); } else if (attack_phase < 0.30F) { hand_r_target = prepare_pos; hand_l_target = QVector3D(-0.10F, HP::SHOULDER_Y - 0.05F, 0.08F); } else if (attack_phase < 0.50F) { float t = (attack_phase - 0.30F) / 0.20F; t = t * t * t; hand_r_target = prepare_pos * (1.0F - t) + thrust_pos * t; hand_l_target = QVector3D(-0.10F + 0.05F * t, HP::SHOULDER_Y - 0.05F + 0.03F * t, 0.08F + 0.45F * t); } else if (attack_phase < 0.70F) { float const t = easeInOutCubic((attack_phase - 0.50F) / 0.20F); hand_r_target = thrust_pos * (1.0F - t) + recover_pos * t; hand_l_target = QVector3D(-0.05F * (1.0F - t) - 0.10F * t, HP::SHOULDER_Y - 0.02F * (1.0F - t) - 0.06F * t, lerp(0.53F, 0.35F, t)); } else { float const t = smoothstep(0.70F, 1.0F, attack_phase); hand_r_target = recover_pos * (1.0F - t) + guard_pos * t; hand_l_target = QVector3D(-0.10F - 0.02F * (1.0F - t), HP::SHOULDER_Y - 0.06F + 0.01F * t, lerp(0.35F, 0.25F, t)); } placeHandAt(false, hand_r_target); placeHandAt(true, hand_l_target); } void HumanoidPoseController::tilt_torso(float side_tilt, float forward_tilt) { QVector3D const right = m_anim_ctx.heading_right(); QVector3D const forward = m_anim_ctx.heading_forward(); QVector3D const offset = right * side_tilt + forward * forward_tilt; m_pose.shoulder_l += offset; m_pose.shoulder_r += offset; m_pose.neck_base += offset * 1.2F; m_pose.head_pos += offset * 1.5F; m_pose.body_frames.torso.origin += offset; m_pose.body_frames.head.origin += offset * 1.5F; } } // namespace Render::GL