mounted_pose_controller.cpp 34 KB

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