mounted_pose_controller.cpp 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955
  1. #include "mounted_pose_controller.h"
  2. #include "../horse/rig.h"
  3. #include "humanoid_math.h"
  4. #include "humanoid_specs.h"
  5. #include "pose_controller.h"
  6. #include <QDebug>
  7. #include <QString>
  8. #include <QVector3D>
  9. #include <algorithm>
  10. #include <cmath>
  11. namespace Render::GL {
  12. namespace {
  13. auto seat_relative(const MountedAttachmentFrame &mount, float forward,
  14. float right, float up) -> QVector3D {
  15. QVector3D const base = mount.seat_position + mount.ground_offset;
  16. return base + mount.seat_forward * forward + mount.seat_right * right +
  17. mount.seat_up * up;
  18. }
  19. auto rein_anchor(const MountedAttachmentFrame &mount, bool is_left, float slack,
  20. float tension) -> QVector3D {
  21. return compute_rein_handle(mount, is_left, slack, tension) +
  22. mount.ground_offset;
  23. }
  24. } // namespace
  25. MountedPoseController::MountedPoseController(
  26. HumanoidPose &pose, const HumanoidAnimationContext &anim_ctx)
  27. : m_pose(pose), m_anim_ctx(anim_ctx) {}
  28. void MountedPoseController::mount_on_horse(
  29. const MountedAttachmentFrame &mount) {
  30. position_pelvis_on_saddle(mount);
  31. attach_feet_to_stirrups(mount);
  32. calculate_riding_knees(mount);
  33. }
  34. void MountedPoseController::dismount() {
  35. using HP = HumanProportions;
  36. m_pose.pelvis_pos = QVector3D(0.0F, HP::WAIST_Y, 0.0F);
  37. m_pose.foot_l = QVector3D(-0.14F, HP::GROUND_Y + m_pose.foot_y_offset, 0.06F);
  38. m_pose.foot_r = QVector3D(0.14F, HP::GROUND_Y + m_pose.foot_y_offset, -0.06F);
  39. }
  40. void MountedPoseController::riding_idle(const MountedAttachmentFrame &mount) {
  41. mount_on_horse(mount);
  42. QVector3D const left_hand_rest = seat_relative(mount, 0.12F, -0.14F, -0.05F);
  43. QVector3D const right_hand_rest = seat_relative(mount, 0.12F, 0.14F, -0.05F);
  44. get_hand(true) = left_hand_rest;
  45. get_hand(false) = right_hand_rest;
  46. const QVector3D left_outward = compute_outward_dir(true);
  47. const QVector3D right_outward = compute_outward_dir(false);
  48. get_elbow(true) = solve_elbow_ik(true, get_shoulder(true), left_hand_rest,
  49. left_outward, 0.45F, 0.12F, -0.05F, 1.0F);
  50. get_elbow(false) = solve_elbow_ik(false, get_shoulder(false), right_hand_rest,
  51. right_outward, 0.45F, 0.12F, -0.05F, 1.0F);
  52. update_head_hierarchy(mount, 0.0F, 0.0F, "riding_idle");
  53. }
  54. void MountedPoseController::riding_leaning(const MountedAttachmentFrame &mount,
  55. float forward_lean, float side_lean) {
  56. mount_on_horse(mount);
  57. apply_lean(mount, forward_lean, side_lean);
  58. }
  59. void MountedPoseController::riding_charging(const MountedAttachmentFrame &mount,
  60. float intensity) {
  61. intensity = std::clamp(intensity, 0.0F, 1.0F);
  62. mount_on_horse(mount);
  63. QVector3D const charge_lean = mount.seat_forward * (0.25F * intensity);
  64. m_pose.shoulder_l += charge_lean;
  65. m_pose.shoulder_r += charge_lean;
  66. m_pose.neck_base += charge_lean * 0.85F;
  67. float const crouch = 0.08F * intensity;
  68. m_pose.shoulder_l.setY(m_pose.shoulder_l.y() - crouch);
  69. m_pose.shoulder_r.setY(m_pose.shoulder_r.y() - crouch);
  70. m_pose.neck_base.setY(m_pose.neck_base.y() - crouch * 0.8F);
  71. update_head_hierarchy(mount, 0.0F, 0.0F, "riding_charging");
  72. hold_reins(mount, 0.2F, 0.2F, 0.85F, 0.85F);
  73. }
  74. void MountedPoseController::riding_reining(const MountedAttachmentFrame &mount,
  75. float left_tension,
  76. float right_tension) {
  77. left_tension = std::clamp(left_tension, 0.0F, 1.0F);
  78. right_tension = std::clamp(right_tension, 0.0F, 1.0F);
  79. mount_on_horse(mount);
  80. QVector3D left_rein_pos = rein_anchor(mount, true, 0.15F, left_tension);
  81. QVector3D right_rein_pos = rein_anchor(mount, false, 0.15F, right_tension);
  82. get_hand(true) = left_rein_pos;
  83. get_hand(false) = right_rein_pos;
  84. const QVector3D left_outward = compute_outward_dir(true);
  85. const QVector3D right_outward = compute_outward_dir(false);
  86. get_elbow(true) = solve_elbow_ik(true, get_shoulder(true), left_rein_pos,
  87. left_outward, 0.52F, 0.08F, -0.12F, 1.0F);
  88. get_elbow(false) = solve_elbow_ik(false, get_shoulder(false), right_rein_pos,
  89. right_outward, 0.52F, 0.08F, -0.12F, 1.0F);
  90. float const avg_tension = (left_tension + right_tension) * 0.5F;
  91. QVector3D const lean_back = mount.seat_forward * (-0.08F * avg_tension);
  92. m_pose.shoulder_l += lean_back;
  93. m_pose.shoulder_r += lean_back;
  94. m_pose.neck_base += lean_back * 0.9F;
  95. update_head_hierarchy(mount, 0.0F, 0.0F, "riding_reining");
  96. }
  97. void MountedPoseController::riding_melee_strike(
  98. const MountedAttachmentFrame &mount, float attack_phase) {
  99. attack_phase = std::clamp(attack_phase, 0.0F, 1.0F);
  100. mount_on_horse(mount);
  101. apply_sword_strike(mount, attack_phase, false);
  102. }
  103. void MountedPoseController::riding_spear_thrust(
  104. const MountedAttachmentFrame &mount, float attack_phase) {
  105. attack_phase = std::clamp(attack_phase, 0.0F, 1.0F);
  106. mount_on_horse(mount);
  107. apply_spear_thrust(mount, attack_phase);
  108. }
  109. void MountedPoseController::riding_bow_shot(const MountedAttachmentFrame &mount,
  110. float draw_phase) {
  111. mount_on_horse(mount);
  112. apply_bow_draw(mount, draw_phase);
  113. }
  114. void MountedPoseController::riding_shield_defense(
  115. const MountedAttachmentFrame &mount, bool raised) {
  116. mount_on_horse(mount);
  117. apply_shield_defense(mount, raised);
  118. }
  119. void MountedPoseController::hold_reins(const MountedAttachmentFrame &mount,
  120. float left_slack, float right_slack,
  121. float left_tension, float right_tension) {
  122. mount_on_horse(mount);
  123. hold_reins_impl(mount, left_slack, right_slack, left_tension, right_tension,
  124. true, true);
  125. }
  126. void MountedPoseController::hold_spear_mounted(
  127. const MountedAttachmentFrame &mount, SpearGrip grip_style) {
  128. mount_on_horse(mount);
  129. apply_spear_guard(mount, grip_style);
  130. }
  131. void MountedPoseController::hold_bow_mounted(
  132. const MountedAttachmentFrame &mount) {
  133. mount_on_horse(mount);
  134. apply_bow_draw(mount, 0.0F);
  135. }
  136. void MountedPoseController::apply_saddle_clearance(
  137. const MountedAttachmentFrame &mount, const HorseDimensions &dims,
  138. float forward_bias, float up_bias) {
  139. float const forward_pull =
  140. std::max(0.0F, forward_bias) * (dims.body_width * 0.12F) +
  141. dims.seat_forward_offset * 0.30F;
  142. float const up_lift =
  143. std::max(0.0F, up_bias) * (dims.saddle_thickness * 0.85F);
  144. QVector3D const offset = -mount.seat_forward * forward_pull +
  145. mount.seat_up * (up_lift + dims.body_height * 0.01F);
  146. m_pose.pelvis_pos += offset;
  147. translate_upper_body(offset);
  148. calculate_riding_knees(mount);
  149. }
  150. void MountedPoseController::stabilize_upper_body(
  151. const MountedAttachmentFrame &mount, const HorseDimensions &dims) {
  152. QVector3D const world_up(0.0F, 1.0F, 0.0F);
  153. QVector3D right_flat = mount.seat_right;
  154. right_flat.setY(0.0F);
  155. if (right_flat.lengthSquared() < 1e-4F) {
  156. right_flat = QVector3D(1.0F, 0.0F, 0.0F);
  157. } else {
  158. right_flat.normalize();
  159. }
  160. QVector3D const shoulder_mid = (m_pose.shoulder_l + m_pose.shoulder_r) * 0.5F;
  161. QVector3D desired_mid = shoulder_mid;
  162. desired_mid.setX(m_pose.pelvis_pos.x());
  163. desired_mid.setZ(m_pose.pelvis_pos.z());
  164. QVector3D const mid_offset = desired_mid - shoulder_mid;
  165. m_pose.shoulder_l += mid_offset;
  166. m_pose.shoulder_r += mid_offset;
  167. float const desired_half = std::clamp(dims.body_width * 0.44F, 0.10F, 0.32F);
  168. m_pose.shoulder_l = desired_mid - right_flat * desired_half;
  169. m_pose.shoulder_r = desired_mid + right_flat * desired_half;
  170. float const target_neck_height =
  171. std::max(0.04F, m_pose.neck_base.y() - desired_mid.y());
  172. m_pose.neck_base = desired_mid + world_up * target_neck_height;
  173. float const head_height =
  174. std::max(0.12F, m_pose.head_pos.y() - m_pose.neck_base.y());
  175. m_pose.head_pos = m_pose.neck_base + world_up * head_height;
  176. }
  177. void MountedPoseController::apply_pose(const MountedAttachmentFrame &mount,
  178. const MountedRiderPoseRequest &request) {
  179. mount_on_horse(mount);
  180. apply_saddle_clearance(mount, request.dims, request.clearance_forward,
  181. request.clearance_up);
  182. stabilize_upper_body(mount, request.dims);
  183. float forward = request.forward_bias;
  184. switch (request.seat_pose) {
  185. case MountedSeatPose::Forward:
  186. forward += 0.35F;
  187. break;
  188. case MountedSeatPose::Defensive:
  189. forward -= 0.20F;
  190. break;
  191. case MountedSeatPose::Neutral:
  192. default:
  193. break;
  194. }
  195. apply_lean(mount, forward, request.side_bias);
  196. apply_torso_sculpt(mount, request.torso_compression, request.torso_twist,
  197. request.shoulder_dip);
  198. float const clamped_forward = std::clamp(forward, -1.0F, 1.0F);
  199. float const clamped_side = std::clamp(request.side_bias, -1.0F, 1.0F);
  200. update_head_hierarchy(mount, clamped_forward * 0.4F, clamped_side * 0.4F,
  201. "applyPose_fixup");
  202. const bool needs_weapon_right =
  203. request.weapon_pose != MountedWeaponPose::None;
  204. const bool needs_weapon_left =
  205. request.weapon_pose == MountedWeaponPose::SpearGuard ||
  206. request.weapon_pose == MountedWeaponPose::SpearThrust ||
  207. request.weapon_pose == MountedWeaponPose::BowDraw;
  208. const bool shield_claims_left =
  209. request.shield_pose != MountedShieldPose::None ? true : false;
  210. bool apply_left_rein =
  211. request.left_hand_on_reins && !shield_claims_left && !needs_weapon_left;
  212. bool apply_right_rein = request.right_hand_on_reins && !needs_weapon_right;
  213. if (apply_left_rein || apply_right_rein) {
  214. hold_reins_impl(mount, request.rein_slack_left, request.rein_slack_right,
  215. request.rein_tension_left, request.rein_tension_right,
  216. apply_left_rein, apply_right_rein);
  217. }
  218. switch (request.shield_pose) {
  219. case MountedShieldPose::Guard:
  220. apply_shield_defense(mount, false);
  221. break;
  222. case MountedShieldPose::Raised:
  223. apply_shield_defense(mount, true);
  224. break;
  225. case MountedShieldPose::Stowed:
  226. apply_shield_stowed(mount, request.dims);
  227. break;
  228. case MountedShieldPose::None:
  229. default:
  230. break;
  231. }
  232. switch (request.weapon_pose) {
  233. case MountedWeaponPose::SwordIdle:
  234. apply_sword_idle_pose(mount, request.dims);
  235. break;
  236. case MountedWeaponPose::SwordStrike:
  237. apply_sword_strike(mount, request.action_phase,
  238. request.shield_pose != MountedShieldPose::None);
  239. break;
  240. case MountedWeaponPose::SpearGuard:
  241. apply_spear_guard(mount, SpearGrip::OVERHAND);
  242. break;
  243. case MountedWeaponPose::SpearThrust:
  244. apply_spear_thrust(mount, request.action_phase);
  245. break;
  246. case MountedWeaponPose::BowDraw:
  247. apply_bow_draw(mount, request.action_phase);
  248. break;
  249. case MountedWeaponPose::None:
  250. default:
  251. break;
  252. }
  253. }
  254. void MountedPoseController::apply_lean(const MountedAttachmentFrame &mount,
  255. float forward_lean, float side_lean) {
  256. float const clamped_forward = std::clamp(forward_lean, -1.0F, 1.0F);
  257. float const clamped_side = std::clamp(side_lean, -1.0F, 1.0F);
  258. QVector3D const lean_offset = mount.seat_forward * (clamped_forward * 0.15F) +
  259. mount.seat_right * (clamped_side * 0.10F);
  260. m_pose.shoulder_l += lean_offset;
  261. m_pose.shoulder_r += lean_offset;
  262. m_pose.neck_base += lean_offset * 0.9F;
  263. update_head_hierarchy(mount, clamped_forward * 0.4F, clamped_side * 0.4F,
  264. "apply_lean");
  265. }
  266. void MountedPoseController::apply_shield_defense(
  267. const MountedAttachmentFrame &mount, bool raised) {
  268. QVector3D shield_pos = raised ? seat_relative(mount, 0.15F, -0.18F, 0.40F)
  269. : seat_relative(mount, 0.05F, -0.16F, 0.22F);
  270. float const rein_slack = raised ? 0.15F : 0.30F;
  271. float const rein_tension = raised ? 0.45F : 0.25F;
  272. QVector3D const rein_pos =
  273. rein_anchor(mount, false, rein_slack, rein_tension);
  274. get_hand(true) = shield_pos;
  275. get_hand(false) = rein_pos;
  276. const QVector3D left_outward = compute_outward_dir(true);
  277. const QVector3D right_outward = compute_outward_dir(false);
  278. get_elbow(true) = solve_elbow_ik(true, get_shoulder(true), shield_pos,
  279. left_outward, 0.45F, 0.15F, -0.10F, 1.0F);
  280. get_elbow(false) = solve_elbow_ik(false, get_shoulder(false), rein_pos,
  281. right_outward, 0.45F, 0.12F, -0.08F, 1.0F);
  282. update_head_hierarchy(mount, 0.0F, 0.0F, "shield_defense");
  283. }
  284. void MountedPoseController::apply_shield_stowed(
  285. const MountedAttachmentFrame &mount, const HorseDimensions &dims) {
  286. QVector3D const rest =
  287. seat_relative(mount, dims.body_length * -0.05F, -dims.body_width * 0.55F,
  288. dims.saddle_thickness * 0.5F);
  289. get_hand(true) = rest;
  290. const QVector3D left_outward = compute_outward_dir(true);
  291. get_elbow(true) = solve_elbow_ik(true, get_shoulder(true), rest, left_outward,
  292. 0.42F, 0.12F, -0.05F, 1.0F);
  293. update_head_hierarchy(mount, 0.0F, 0.0F, "shield_stowed");
  294. }
  295. void MountedPoseController::apply_sword_idle_pose(
  296. const MountedAttachmentFrame &mount, const HorseDimensions &dims) {
  297. QVector3D const shoulder_r = get_shoulder(false);
  298. QVector3D const sword_anchor =
  299. shoulder_r + mount.seat_right * (dims.body_width * 0.90F) +
  300. mount.seat_forward * (dims.body_length * 0.22F) +
  301. mount.seat_up *
  302. (dims.body_height * 0.06F + dims.saddle_thickness * 0.10F);
  303. get_hand(false) = sword_anchor;
  304. const QVector3D right_outward = compute_outward_dir(false);
  305. get_elbow(false) = solve_elbow_ik(false, shoulder_r, sword_anchor,
  306. right_outward, 0.46F, 0.24F, -0.05F, 1.0F);
  307. update_head_hierarchy(mount, 0.0F, 0.0F, "sword_idle");
  308. }
  309. void MountedPoseController::apply_sword_strike(
  310. const MountedAttachmentFrame &mount, float attack_phase,
  311. bool keep_left_hand) {
  312. attack_phase = std::clamp(attack_phase, 0.0F, 1.0F);
  313. QVector3D const rest_pos = seat_relative(mount, 0.08F, 0.20F, 0.12F);
  314. QVector3D const chamber_pos = seat_relative(mount, -0.05F, 0.25F, 0.40F);
  315. QVector3D const apex_pos = seat_relative(mount, -0.02F, 0.30F, 0.48F);
  316. QVector3D const strike_pos = seat_relative(mount, 0.45F, 0.35F, 0.0F);
  317. QVector3D const followthrough_pos =
  318. seat_relative(mount, 0.55F, 0.25F, -0.10F);
  319. QVector3D hand_r_target;
  320. QVector3D hand_l_target =
  321. rein_anchor(mount, true, 0.20F, 0.25F) + mount.seat_up * -0.02F;
  322. float torso_twist = 0.0F;
  323. float side_lean = 0.0F;
  324. float forward_lean = 0.0F;
  325. float shoulder_dip = 0.0F;
  326. if (attack_phase < 0.18F) {
  327. float t = attack_phase / 0.18F;
  328. float ease_t = t * t;
  329. hand_r_target = rest_pos * (1.0F - ease_t) + chamber_pos * ease_t;
  330. torso_twist = -0.04F * ease_t;
  331. shoulder_dip = 0.03F * ease_t;
  332. update_head_hierarchy(mount, 0.0F, 0.0F, "sword_chamber");
  333. } else if (attack_phase < 0.28F) {
  334. float t = (attack_phase - 0.18F) / 0.10F;
  335. float ease_t = t * t * (3.0F - 2.0F * t);
  336. hand_r_target = chamber_pos * (1.0F - ease_t) + apex_pos * ease_t;
  337. torso_twist = -0.04F;
  338. shoulder_dip = 0.03F + 0.02F * ease_t;
  339. update_head_hierarchy(mount, 0.0F, 0.0F, "sword_apex");
  340. } else if (attack_phase < 0.48F) {
  341. float t = (attack_phase - 0.28F) / 0.20F;
  342. float power_t = t * t * t;
  343. hand_r_target = apex_pos * (1.0F - power_t) + strike_pos * power_t;
  344. torso_twist = -0.04F + 0.12F * power_t;
  345. side_lean = 0.08F * power_t;
  346. forward_lean = 0.06F * power_t;
  347. shoulder_dip = 0.05F - 0.08F * power_t;
  348. hand_l_target += mount.seat_up * (-0.03F * power_t);
  349. update_head_hierarchy(mount, 0.3F * power_t, 0.2F * power_t,
  350. "sword_strike");
  351. } else if (attack_phase < 0.65F) {
  352. float t = (attack_phase - 0.48F) / 0.17F;
  353. float ease_t = t * t * (3.0F - 2.0F * t);
  354. hand_r_target = strike_pos * (1.0F - ease_t) + followthrough_pos * ease_t;
  355. torso_twist = 0.08F - 0.02F * t;
  356. side_lean = 0.08F - 0.03F * t;
  357. forward_lean = 0.06F - 0.02F * t;
  358. shoulder_dip = -0.03F;
  359. update_head_hierarchy(mount, 0.15F, 0.1F, "sword_followthrough");
  360. } else {
  361. float t = (attack_phase - 0.65F) / 0.35F;
  362. float ease_t = 1.0F - (1.0F - t) * (1.0F - t);
  363. hand_r_target = followthrough_pos * (1.0F - ease_t) + rest_pos * ease_t;
  364. torso_twist = 0.06F * (1.0F - ease_t);
  365. side_lean = 0.05F * (1.0F - ease_t);
  366. forward_lean = 0.04F * (1.0F - ease_t);
  367. shoulder_dip = -0.03F * (1.0F - ease_t);
  368. update_head_hierarchy(mount, 0.0F, 0.0F, "sword_recover");
  369. }
  370. if (std::abs(torso_twist) > 0.001F) {
  371. QVector3D const twist_offset = mount.seat_forward * torso_twist;
  372. m_pose.shoulder_r += twist_offset;
  373. m_pose.shoulder_l -= twist_offset * 0.5F;
  374. }
  375. if (side_lean > 0.001F) {
  376. QVector3D const lean_offset = mount.seat_right * side_lean;
  377. m_pose.shoulder_l += lean_offset;
  378. m_pose.shoulder_r += lean_offset;
  379. m_pose.neck_base += lean_offset * 0.8F;
  380. }
  381. if (forward_lean > 0.001F) {
  382. QVector3D const lean_offset = mount.seat_forward * forward_lean;
  383. m_pose.shoulder_l += lean_offset;
  384. m_pose.shoulder_r += lean_offset;
  385. m_pose.neck_base += lean_offset * 0.9F;
  386. }
  387. if (std::abs(shoulder_dip) > 0.001F) {
  388. m_pose.shoulder_r += mount.seat_up * shoulder_dip;
  389. }
  390. get_hand(false) = hand_r_target;
  391. if (!keep_left_hand) {
  392. get_hand(true) = hand_l_target;
  393. }
  394. const QVector3D right_outward = compute_outward_dir(false);
  395. get_elbow(false) = solve_elbow_ik(false, get_shoulder(false), hand_r_target,
  396. right_outward, 0.42F, 0.15F, 0.0F, 1.0F);
  397. if (!keep_left_hand) {
  398. const QVector3D left_outward = compute_outward_dir(true);
  399. get_elbow(true) = solve_elbow_ik(true, get_shoulder(true), hand_l_target,
  400. left_outward, 0.45F, 0.12F, -0.08F, 1.0F);
  401. }
  402. }
  403. void MountedPoseController::apply_spear_thrust(
  404. const MountedAttachmentFrame &mount, float attack_phase) {
  405. attack_phase = std::clamp(attack_phase, 0.0F, 1.0F);
  406. QVector3D const guard_pos = seat_relative(mount, 0.12F, 0.15F, 0.15F);
  407. QVector3D const couch_pos = seat_relative(mount, 0.05F, 0.12F, 0.08F);
  408. QVector3D const thrust_pos = seat_relative(mount, 0.95F, 0.08F, 0.18F);
  409. QVector3D const extended_pos = seat_relative(mount, 1.05F, 0.05F, 0.15F);
  410. QVector3D hand_r_target;
  411. QVector3D hand_l_target;
  412. float forward_lean = 0.0F;
  413. float torso_twist = 0.0F;
  414. float shoulder_drop = 0.0F;
  415. float torso_compression = 0.0F;
  416. if (attack_phase < 0.20F) {
  417. float t = attack_phase / 0.20F;
  418. float ease_t = t * t;
  419. hand_r_target = guard_pos * (1.0F - ease_t) + couch_pos * ease_t;
  420. hand_l_target = guard_pos - mount.seat_right * 0.25F +
  421. (couch_pos - guard_pos) * ease_t * 0.6F;
  422. torso_compression = 0.03F * ease_t;
  423. forward_lean = 0.04F * ease_t;
  424. update_head_hierarchy(mount, 0.1F * ease_t, 0.0F, "spear_couch");
  425. } else if (attack_phase < 0.30F) {
  426. hand_r_target = couch_pos;
  427. hand_l_target = couch_pos - mount.seat_right * 0.22F;
  428. torso_compression = 0.03F;
  429. forward_lean = 0.04F;
  430. update_head_hierarchy(mount, 0.1F, 0.0F, "spear_tension");
  431. } else if (attack_phase < 0.50F) {
  432. float t = (attack_phase - 0.30F) / 0.20F;
  433. float power_t = t * t * t;
  434. hand_r_target = couch_pos * (1.0F - power_t) + thrust_pos * power_t;
  435. hand_l_target = (couch_pos - mount.seat_right * 0.22F) * (1.0F - power_t) +
  436. (thrust_pos - mount.seat_right * 0.28F) * power_t;
  437. forward_lean = 0.04F + 0.16F * power_t;
  438. torso_twist = 0.05F * power_t;
  439. shoulder_drop = 0.04F * power_t;
  440. torso_compression = 0.03F * (1.0F - power_t * 0.5F);
  441. update_head_hierarchy(mount, 0.5F * power_t, 0.0F, "spear_thrust");
  442. } else if (attack_phase < 0.65F) {
  443. float t = (attack_phase - 0.50F) / 0.15F;
  444. float ease_t = t * t * (3.0F - 2.0F * t);
  445. hand_r_target = thrust_pos * (1.0F - ease_t) + extended_pos * ease_t;
  446. hand_l_target = (thrust_pos - mount.seat_right * 0.28F) * (1.0F - ease_t) +
  447. (extended_pos - mount.seat_right * 0.32F) * ease_t;
  448. forward_lean = 0.20F;
  449. torso_twist = 0.05F;
  450. shoulder_drop = 0.04F;
  451. update_head_hierarchy(mount, 0.5F, 0.0F, "spear_extend");
  452. } else {
  453. float t = (attack_phase - 0.65F) / 0.35F;
  454. float ease_t = 1.0F - (1.0F - t) * (1.0F - t);
  455. hand_r_target = extended_pos * (1.0F - ease_t) + guard_pos * ease_t;
  456. hand_l_target =
  457. (extended_pos - mount.seat_right * 0.32F) * (1.0F - ease_t) +
  458. (guard_pos - mount.seat_right * 0.25F) * ease_t;
  459. forward_lean = 0.20F * (1.0F - ease_t);
  460. torso_twist = 0.05F * (1.0F - ease_t);
  461. shoulder_drop = 0.04F * (1.0F - ease_t);
  462. update_head_hierarchy(mount, 0.0F, 0.0F, "spear_recover");
  463. }
  464. if (forward_lean > 0.001F) {
  465. QVector3D const lean_offset = mount.seat_forward * forward_lean;
  466. m_pose.shoulder_l += lean_offset;
  467. m_pose.shoulder_r += lean_offset;
  468. m_pose.neck_base += lean_offset * 0.85F;
  469. }
  470. if (std::abs(torso_twist) > 0.001F) {
  471. QVector3D const twist_offset = mount.seat_forward * torso_twist;
  472. m_pose.shoulder_r += twist_offset;
  473. m_pose.shoulder_l -= twist_offset * 0.3F;
  474. }
  475. if (shoulder_drop > 0.001F) {
  476. m_pose.shoulder_r -= mount.seat_up * shoulder_drop;
  477. m_pose.shoulder_l -= mount.seat_up * (shoulder_drop * 0.3F);
  478. }
  479. if (torso_compression > 0.001F) {
  480. m_pose.shoulder_l -= mount.seat_up * torso_compression;
  481. m_pose.shoulder_r -= mount.seat_up * torso_compression;
  482. m_pose.neck_base -= mount.seat_up * (torso_compression * 0.6F);
  483. }
  484. get_hand(false) = hand_r_target;
  485. get_hand(true) = hand_l_target;
  486. const QVector3D left_outward = compute_outward_dir(true);
  487. const QVector3D right_outward = compute_outward_dir(false);
  488. get_elbow(true) = solve_elbow_ik(true, get_shoulder(true), hand_l_target,
  489. left_outward, 0.48F, 0.10F, -0.06F, 1.0F);
  490. get_elbow(false) = solve_elbow_ik(false, get_shoulder(false), hand_r_target,
  491. right_outward, 0.48F, 0.10F, -0.04F, 1.0F);
  492. }
  493. void MountedPoseController::apply_spear_guard(
  494. const MountedAttachmentFrame &mount, SpearGrip grip_style) {
  495. QVector3D hand_r_target;
  496. QVector3D hand_l_target;
  497. switch (grip_style) {
  498. case SpearGrip::OVERHAND:
  499. hand_r_target = seat_relative(mount, 0.0F, 0.12F, 0.55F);
  500. hand_l_target = rein_anchor(mount, true, 0.30F, 0.30F);
  501. break;
  502. case SpearGrip::COUCHED:
  503. hand_r_target = seat_relative(mount, -0.15F, 0.08F, 0.08F);
  504. hand_l_target = rein_anchor(mount, true, 0.35F, 0.20F);
  505. break;
  506. case SpearGrip::TWO_HANDED:
  507. hand_r_target = seat_relative(mount, 0.15F, 0.15F, 0.12F);
  508. hand_l_target = hand_r_target - mount.seat_right * 0.25F;
  509. break;
  510. }
  511. get_hand(false) = hand_r_target;
  512. get_hand(true) = hand_l_target;
  513. const QVector3D left_outward = compute_outward_dir(true);
  514. const QVector3D right_outward = compute_outward_dir(false);
  515. get_elbow(true) = solve_elbow_ik(true, get_shoulder(true), hand_l_target,
  516. left_outward, 0.45F, 0.12F, -0.08F, 1.0F);
  517. get_elbow(false) = solve_elbow_ik(false, get_shoulder(false), hand_r_target,
  518. right_outward, 0.45F, 0.12F, -0.05F, 1.0F);
  519. update_head_hierarchy(mount, 0.0F, 0.0F, "spear_guard_pose");
  520. }
  521. void MountedPoseController::apply_bow_draw(const MountedAttachmentFrame &mount,
  522. float draw_phase) {
  523. draw_phase = std::clamp(draw_phase, 0.0F, 1.0F);
  524. QVector3D const bow_hold_pos = seat_relative(mount, 0.25F, -0.08F, 0.25F);
  525. QVector3D const draw_start_pos =
  526. bow_hold_pos + mount.seat_right * 0.08F + QVector3D(0.0F, -0.05F, 0.0F);
  527. QVector3D const draw_end_pos = seat_relative(mount, 0.0F, 0.12F, 0.18F);
  528. QVector3D hand_l_target = bow_hold_pos;
  529. QVector3D hand_r_target;
  530. if (draw_phase < 0.30F) {
  531. float t = draw_phase / 0.30F;
  532. t = t * t;
  533. hand_r_target = draw_start_pos * (1.0F - t) + draw_end_pos * t;
  534. } else if (draw_phase < 0.65F) {
  535. hand_r_target = draw_end_pos;
  536. } else {
  537. float t = (draw_phase - 0.65F) / 0.35F;
  538. t = t * t * t;
  539. hand_r_target = draw_end_pos * (1.0F - t) + draw_start_pos * t;
  540. }
  541. get_hand(true) = hand_l_target;
  542. get_hand(false) = hand_r_target;
  543. const QVector3D left_outward = compute_outward_dir(true);
  544. const QVector3D right_outward = compute_outward_dir(false);
  545. get_elbow(true) = solve_elbow_ik(true, get_shoulder(true), hand_l_target,
  546. left_outward, 0.50F, 0.08F, -0.05F, 1.0F);
  547. get_elbow(false) = solve_elbow_ik(false, get_shoulder(false), hand_r_target,
  548. right_outward, 0.48F, 0.12F, -0.08F, 1.0F);
  549. update_head_hierarchy(mount, 0.0F, 0.0F, "bow_draw");
  550. }
  551. void MountedPoseController::apply_torso_sculpt(
  552. const MountedAttachmentFrame &mount, float compression, float twist,
  553. float shoulder_dip) {
  554. float const comp = std::clamp(compression, 0.0F, 1.0F);
  555. float const twist_amt = std::clamp(twist, -1.0F, 1.0F);
  556. float const dip_amt = std::clamp(shoulder_dip, -1.0F, 1.0F);
  557. if (comp < 1e-3F && std::abs(twist_amt) < 1e-3F &&
  558. std::abs(dip_amt) < 1e-3F) {
  559. return;
  560. }
  561. QVector3D const forward = mount.seat_forward;
  562. QVector3D const right = mount.seat_right;
  563. QVector3D const up = mount.seat_up;
  564. QVector3D const squeeze = -forward * (0.035F + comp * 0.08F);
  565. QVector3D const inward = squeeze * comp;
  566. m_pose.shoulder_l += inward;
  567. m_pose.shoulder_r += inward;
  568. m_pose.neck_base += inward * 0.55F;
  569. m_pose.head_pos += inward * 0.55F;
  570. QVector3D const chest_lift = up * (0.012F * comp);
  571. m_pose.neck_base += chest_lift * 0.8F;
  572. m_pose.head_pos += chest_lift * 0.8F;
  573. QVector3D const narrow = right * (0.022F * comp);
  574. m_pose.shoulder_l -= narrow;
  575. m_pose.shoulder_r += narrow;
  576. QVector3D const twist_vec = right * (0.0003F * twist_amt);
  577. m_pose.shoulder_l += twist_vec;
  578. m_pose.shoulder_r -= twist_vec;
  579. m_pose.neck_base += twist_vec * 0.25F;
  580. QVector3D const dip_vec = up * (0.03F * dip_amt);
  581. m_pose.shoulder_l += dip_vec;
  582. m_pose.shoulder_r -= dip_vec;
  583. }
  584. void MountedPoseController::hold_reins_impl(const MountedAttachmentFrame &mount,
  585. float left_slack, float right_slack,
  586. float left_tension,
  587. float right_tension, bool apply_left,
  588. bool apply_right) {
  589. left_slack = std::clamp(left_slack, 0.0F, 1.0F);
  590. right_slack = std::clamp(right_slack, 0.0F, 1.0F);
  591. left_tension = std::clamp(left_tension, 0.0F, 1.0F);
  592. right_tension = std::clamp(right_tension, 0.0F, 1.0F);
  593. if (apply_left) {
  594. QVector3D const left_rein_pos =
  595. rein_anchor(mount, true, left_slack, left_tension);
  596. get_hand(true) = left_rein_pos;
  597. const QVector3D left_outward = compute_outward_dir(true);
  598. get_elbow(true) = solve_elbow_ik(true, get_shoulder(true), left_rein_pos,
  599. left_outward, 0.45F, 0.12F, -0.08F, 1.0F);
  600. }
  601. if (apply_right) {
  602. QVector3D const right_rein_pos =
  603. rein_anchor(mount, false, right_slack, right_tension);
  604. get_hand(false) = right_rein_pos;
  605. const QVector3D right_outward = compute_outward_dir(false);
  606. get_elbow(false) =
  607. solve_elbow_ik(false, get_shoulder(false), right_rein_pos,
  608. right_outward, 0.45F, 0.12F, -0.08F, 1.0F);
  609. }
  610. }
  611. void MountedPoseController::attach_feet_to_stirrups(
  612. const MountedAttachmentFrame &mount) {
  613. m_pose.foot_l = mount.stirrup_bottom_left + mount.ground_offset;
  614. m_pose.foot_r = mount.stirrup_bottom_right + mount.ground_offset;
  615. }
  616. void MountedPoseController::position_pelvis_on_saddle(
  617. const MountedAttachmentFrame &mount) {
  618. QVector3D const seat_world = mount.seat_position + mount.ground_offset;
  619. QVector3D const delta = seat_world - m_pose.pelvis_pos;
  620. m_pose.pelvis_pos = seat_world;
  621. translate_upper_body(delta);
  622. }
  623. void MountedPoseController::translate_upper_body(const QVector3D &delta) {
  624. m_pose.shoulder_l += delta;
  625. m_pose.shoulder_r += delta;
  626. m_pose.neck_base += delta;
  627. m_pose.head_pos += delta;
  628. m_pose.elbow_l += delta;
  629. m_pose.elbow_r += delta;
  630. m_pose.hand_l += delta;
  631. m_pose.hand_r += delta;
  632. }
  633. void MountedPoseController::calculate_riding_knees(
  634. const MountedAttachmentFrame &mount) {
  635. QVector3D const hip_offset = mount.seat_up * -0.02F;
  636. QVector3D const hip_left =
  637. m_pose.pelvis_pos - mount.seat_right * 0.10F + hip_offset;
  638. QVector3D const hip_right =
  639. m_pose.pelvis_pos + mount.seat_right * 0.10F + hip_offset;
  640. float const height_scale = m_anim_ctx.variation.height_scale;
  641. m_pose.knee_l = solve_knee_ik(true, hip_left, m_pose.foot_l, height_scale);
  642. m_pose.knee_r = solve_knee_ik(false, hip_right, m_pose.foot_r, height_scale);
  643. }
  644. auto MountedPoseController::solve_elbow_ik(
  645. bool, const QVector3D &shoulder, const QVector3D &hand,
  646. const QVector3D &outward_dir, float along_frac, float lateral_offset,
  647. float y_bias, float outward_sign) const -> QVector3D {
  648. return elbow_bend_torso(shoulder, hand, outward_dir, along_frac,
  649. lateral_offset, y_bias, outward_sign);
  650. }
  651. auto MountedPoseController::solve_knee_ik(
  652. bool is_left, const QVector3D &hip, const QVector3D &foot,
  653. float height_scale) const -> QVector3D {
  654. using HP = HumanProportions;
  655. QVector3D hip_to_foot = foot - hip;
  656. float const distance = hip_to_foot.length();
  657. if (distance < 1e-5F) {
  658. return hip;
  659. }
  660. float const upper_len = HP::UPPER_LEG_LEN * height_scale;
  661. float const lower_len = HP::LOWER_LEG_LEN * height_scale;
  662. float const reach = upper_len + lower_len;
  663. float const min_reach =
  664. std::max(std::abs(upper_len - lower_len) + 1e-4F, 1e-3F);
  665. float const max_reach = std::max(reach - 1e-4F, min_reach + 1e-4F);
  666. float const clamped_dist = std::clamp(distance, min_reach, max_reach);
  667. QVector3D const dir = hip_to_foot / distance;
  668. float cos_theta = (upper_len * upper_len + clamped_dist * clamped_dist -
  669. lower_len * lower_len) /
  670. (2.0F * upper_len * clamped_dist);
  671. cos_theta = std::clamp(cos_theta, -1.0F, 1.0F);
  672. float const sin_theta =
  673. std::sqrt(std::max(0.0F, 1.0F - cos_theta * cos_theta));
  674. QVector3D bend_pref = is_left ? QVector3D(-0.70F, -0.15F, 0.30F)
  675. : QVector3D(0.70F, -0.15F, 0.30F);
  676. bend_pref.normalize();
  677. QVector3D bend_axis = bend_pref - dir * QVector3D::dotProduct(dir, bend_pref);
  678. if (bend_axis.lengthSquared() < 1e-6F) {
  679. bend_axis = QVector3D::crossProduct(dir, QVector3D(0.0F, 1.0F, 0.0F));
  680. if (bend_axis.lengthSquared() < 1e-6F) {
  681. bend_axis = QVector3D::crossProduct(dir, QVector3D(1.0F, 0.0F, 0.0F));
  682. }
  683. }
  684. bend_axis.normalize();
  685. QVector3D knee =
  686. hip + dir * (cos_theta * upper_len) + bend_axis * (sin_theta * upper_len);
  687. float const knee_floor = HP::GROUND_Y + m_pose.foot_y_offset * 0.5F;
  688. if (knee.y() < knee_floor) {
  689. knee.setY(knee_floor);
  690. }
  691. if (knee.y() > hip.y()) {
  692. knee.setY(hip.y());
  693. }
  694. return knee;
  695. }
  696. auto MountedPoseController::get_shoulder(bool is_left) const
  697. -> const QVector3D & {
  698. return is_left ? m_pose.shoulder_l : m_pose.shoulder_r;
  699. }
  700. auto MountedPoseController::get_hand(bool is_left) -> QVector3D & {
  701. return is_left ? m_pose.hand_l : m_pose.hand_r;
  702. }
  703. auto MountedPoseController::get_hand(bool is_left) const -> const QVector3D & {
  704. return is_left ? m_pose.hand_l : m_pose.hand_r;
  705. }
  706. auto MountedPoseController::get_elbow(bool is_left) -> QVector3D & {
  707. return is_left ? m_pose.elbow_l : m_pose.elbow_r;
  708. }
  709. auto MountedPoseController::compute_right_axis() const -> QVector3D {
  710. QVector3D right_axis = m_pose.shoulder_r - m_pose.shoulder_l;
  711. right_axis.setY(0.0F);
  712. if (right_axis.lengthSquared() < 1e-8F) {
  713. right_axis = QVector3D(1.0F, 0.0F, 0.0F);
  714. }
  715. right_axis.normalize();
  716. return right_axis;
  717. }
  718. auto MountedPoseController::compute_outward_dir(bool is_left) const
  719. -> QVector3D {
  720. QVector3D const right_axis = compute_right_axis();
  721. return is_left ? -right_axis : right_axis;
  722. }
  723. void MountedPoseController::apply_fixed_head_frame(
  724. const MountedAttachmentFrame &mount, std::string_view debug_label) {
  725. using HP = HumanProportions;
  726. float const h_scale = m_anim_ctx.variation.height_scale;
  727. float const neck_len = (HP::HEAD_HEIGHT * 0.5F + 0.045F) * h_scale;
  728. QVector3D up_dir = mount.seat_up;
  729. if (up_dir.lengthSquared() < 1e-6F) {
  730. up_dir = QVector3D(0.0F, 1.0F, 0.0F);
  731. } else {
  732. up_dir.normalize();
  733. }
  734. QVector3D fwd_dir = mount.seat_forward;
  735. if (fwd_dir.lengthSquared() < 1e-6F) {
  736. fwd_dir = QVector3D(0.0F, 0.0F, 1.0F);
  737. } else {
  738. fwd_dir.normalize();
  739. }
  740. QVector3D right_dir = QVector3D::crossProduct(fwd_dir, up_dir);
  741. if (right_dir.lengthSquared() < 1e-6F) {
  742. right_dir = QVector3D(1.0F, 0.0F, 0.0F);
  743. } else {
  744. right_dir.normalize();
  745. }
  746. fwd_dir = QVector3D::crossProduct(up_dir, right_dir).normalized();
  747. m_pose.head_pos = m_pose.neck_base + up_dir * neck_len;
  748. QVector3D prev_origin = m_pose.head_frame.origin;
  749. QVector3D prev_up = m_pose.head_frame.up;
  750. QVector3D prev_forward = m_pose.head_frame.forward;
  751. m_pose.head_frame.origin = m_pose.head_pos;
  752. m_pose.head_frame.up = up_dir;
  753. m_pose.head_frame.right = right_dir;
  754. m_pose.head_frame.forward = fwd_dir;
  755. if (m_pose.head_r < 0.01F) {
  756. m_pose.head_r = 0.12F;
  757. }
  758. m_pose.head_frame.radius = m_pose.head_r;
  759. m_pose.body_frames.head = m_pose.head_frame;
  760. }
  761. void MountedPoseController::update_head_hierarchy(
  762. const MountedAttachmentFrame &mount, float extra_forward_tilt,
  763. float extra_side_tilt, std::string_view debug_label) {
  764. (void)extra_forward_tilt;
  765. (void)extra_side_tilt;
  766. apply_fixed_head_frame(mount, debug_label);
  767. }
  768. void MountedPoseController::finalize_head_sync(
  769. const MountedAttachmentFrame &mount, std::string_view debug_label) {
  770. apply_fixed_head_frame(mount, debug_label);
  771. }
  772. } // namespace Render::GL