archer_renderer.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. #include "archer_renderer.h"
  2. #include "../../game/core/component.h"
  3. #include "../../game/core/entity.h"
  4. #include "../geom/math_utils.h"
  5. #include "../geom/transforms.h"
  6. #include "../gl/backend.h"
  7. #include "../gl/primitives.h"
  8. #include "../gl/shader.h"
  9. #include "../humanoid_base.h"
  10. #include "../humanoid_math.h"
  11. #include "../humanoid_specs.h"
  12. #include "../palette.h"
  13. #include "../scene_renderer.h"
  14. #include "../submitter.h"
  15. #include "gl/render_constants.h"
  16. #include "registry.h"
  17. #include "renderer_constants.h"
  18. #include <QMatrix4x4>
  19. #include <QString>
  20. #include <QVector3D>
  21. #include <cmath>
  22. #include <cstdint>
  23. #include <numbers>
  24. #include <qmatrix4x4.h>
  25. #include <qstringliteral.h>
  26. #include <qvectornd.h>
  27. #include <unordered_map>
  28. namespace Render::GL {
  29. using Render::Geom::clamp01;
  30. using Render::Geom::clampf;
  31. using Render::Geom::coneFromTo;
  32. using Render::Geom::cylinderBetween;
  33. using Render::Geom::sphereAt;
  34. struct ArcherExtras {
  35. QVector3D stringCol;
  36. QVector3D fletch;
  37. QVector3D metalHead;
  38. float bowRodR = 0.035F;
  39. float stringR = 0.008F;
  40. float bowDepth = 0.25F;
  41. float bowX = 0.0F;
  42. float bowTopY{};
  43. float bowBotY{};
  44. };
  45. class ArcherRenderer : public HumanoidRendererBase {
  46. public:
  47. auto getProportionScaling() const -> QVector3D override {
  48. return {0.94F, 1.01F, 0.96F};
  49. }
  50. private:
  51. mutable std::unordered_map<uint32_t, ArcherExtras> m_extrasCache;
  52. public:
  53. void getVariant(const DrawContext &ctx, uint32_t seed,
  54. HumanoidVariant &v) const override {
  55. QVector3D const team_tint = resolveTeamTint(ctx);
  56. v.palette = makeHumanoidPalette(team_tint, seed);
  57. }
  58. void customizePose(const DrawContext &ctx, const AnimationInputs &anim,
  59. uint32_t seed, HumanoidPose &pose) const override {
  60. using HP = HumanProportions;
  61. float const arm_height_jitter = (hash_01(seed ^ 0xABCDU) - 0.5F) * 0.03F;
  62. float const arm_asymmetry = (hash_01(seed ^ 0xDEF0U) - 0.5F) * 0.04F;
  63. float const bowX = 0.0F;
  64. if (anim.isInHoldMode || anim.isExitingHold) {
  65. float const t = anim.isInHoldMode ? 1.0F : (1.0F - anim.holdExitProgress);
  66. float const kneel_depth = 0.45F * t;
  67. float const pelvis_y = HP::WAIST_Y - kneel_depth;
  68. pose.pelvisPos.setY(pelvis_y);
  69. float const stance_narrow = 0.12F;
  70. float const left_knee_y = HP::GROUND_Y + 0.08F * t;
  71. float const left_knee_z = -0.05F * t;
  72. pose.knee_l = QVector3D(-stance_narrow, left_knee_y, left_knee_z);
  73. pose.footL = QVector3D(-stance_narrow - 0.03F, HP::GROUND_Y,
  74. left_knee_z - HP::LOWER_LEG_LEN * 0.95F * t);
  75. float const right_foot_z = 0.30F * t;
  76. pose.foot_r = QVector3D(stance_narrow, HP::GROUND_Y + pose.footYOffset,
  77. right_foot_z);
  78. float const right_knee_y = pelvis_y - 0.10F;
  79. float const right_knee_z = right_foot_z - 0.05F;
  80. pose.knee_r = QVector3D(stance_narrow, right_knee_y, right_knee_z);
  81. float const upper_body_drop = kneel_depth;
  82. pose.shoulderL.setY(HP::SHOULDER_Y - upper_body_drop);
  83. pose.shoulderR.setY(HP::SHOULDER_Y - upper_body_drop);
  84. pose.neck_base.setY(HP::NECK_BASE_Y - upper_body_drop);
  85. pose.headPos.setY((HP::HEAD_TOP_Y + HP::CHIN_Y) * 0.5F - upper_body_drop);
  86. float const forward_lean = 0.10F * t;
  87. pose.shoulderL.setZ(pose.shoulderL.z() + forward_lean);
  88. pose.shoulderR.setZ(pose.shoulderR.z() + forward_lean);
  89. pose.neck_base.setZ(pose.neck_base.z() + forward_lean * 0.8F);
  90. pose.headPos.setZ(pose.headPos.z() + forward_lean * 0.7F);
  91. QVector3D const hold_hand_l(bowX - 0.15F, pose.shoulderL.y() + 0.30F,
  92. 0.55F);
  93. QVector3D const hold_hand_r(bowX + 0.12F, pose.shoulderR.y() + 0.15F,
  94. 0.10F);
  95. QVector3D const normal_hand_l(bowX - 0.05F + arm_asymmetry,
  96. HP::SHOULDER_Y + 0.05F + arm_height_jitter,
  97. 0.55F);
  98. QVector3D const normal_hand_r(
  99. 0.15F - arm_asymmetry * 0.5F,
  100. HP::SHOULDER_Y + 0.15F + arm_height_jitter * 0.8F, 0.20F);
  101. pose.handL = normal_hand_l * (1.0F - t) + hold_hand_l * t;
  102. pose.hand_r = normal_hand_r * (1.0F - t) + hold_hand_r * t;
  103. } else {
  104. pose.handL = QVector3D(bowX - 0.05F + arm_asymmetry,
  105. HP::SHOULDER_Y + 0.05F + arm_height_jitter, 0.55F);
  106. pose.hand_r =
  107. QVector3D(0.15F - arm_asymmetry * 0.5F,
  108. HP::SHOULDER_Y + 0.15F + arm_height_jitter * 0.8F, 0.20F);
  109. }
  110. if (anim.is_attacking && !anim.isInHoldMode) {
  111. float const attack_phase =
  112. std::fmod(anim.time * ARCHER_INV_ATTACK_CYCLE_TIME, 1.0F);
  113. if (anim.isMelee) {
  114. QVector3D const rest_pos(0.25F, HP::SHOULDER_Y, 0.10F);
  115. QVector3D const raised_pos(0.30F, HP::HEAD_TOP_Y + 0.2F, -0.05F);
  116. QVector3D const strike_pos(0.35F, HP::WAIST_Y, 0.45F);
  117. if (attack_phase < 0.25F) {
  118. float t = attack_phase / 0.25F;
  119. t = t * t;
  120. pose.hand_r = rest_pos * (1.0F - t) + raised_pos * t;
  121. pose.handL = QVector3D(-0.15F, HP::SHOULDER_Y - 0.1F * t, 0.20F);
  122. } else if (attack_phase < 0.35F) {
  123. pose.hand_r = raised_pos;
  124. pose.handL = QVector3D(-0.15F, HP::SHOULDER_Y - 0.1F, 0.20F);
  125. } else if (attack_phase < 0.55F) {
  126. float t = (attack_phase - 0.35F) / 0.2F;
  127. t = t * t * t;
  128. pose.hand_r = raised_pos * (1.0F - t) + strike_pos * t;
  129. pose.handL =
  130. QVector3D(-0.15F, HP::SHOULDER_Y - 0.1F * (1.0F - t * 0.5F),
  131. 0.20F + 0.15F * t);
  132. } else {
  133. float t = (attack_phase - 0.55F) / 0.45F;
  134. t = 1.0F - (1.0F - t) * (1.0F - t);
  135. pose.hand_r = strike_pos * (1.0F - t) + rest_pos * t;
  136. pose.handL = QVector3D(-0.15F, HP::SHOULDER_Y - 0.05F * (1.0F - t),
  137. 0.35F * (1.0F - t) + 0.20F * t);
  138. }
  139. } else {
  140. QVector3D const aim_pos(0.18F, HP::SHOULDER_Y + 0.18F, 0.35F);
  141. QVector3D const draw_pos(0.22F, HP::SHOULDER_Y + 0.10F, -0.30F);
  142. QVector3D const release_pos(0.18F, HP::SHOULDER_Y + 0.20F, 0.10F);
  143. if (attack_phase < 0.20F) {
  144. float t = attack_phase / 0.20F;
  145. t = t * t;
  146. pose.hand_r = aim_pos * (1.0F - t) + draw_pos * t;
  147. pose.handL = QVector3D(bowX - 0.05F, HP::SHOULDER_Y + 0.05F, 0.55F);
  148. float const shoulder_twist = t * 0.08F;
  149. pose.shoulderR.setY(pose.shoulderR.y() + shoulder_twist);
  150. pose.shoulderL.setY(pose.shoulderL.y() - shoulder_twist * 0.5F);
  151. } else if (attack_phase < 0.50F) {
  152. pose.hand_r = draw_pos;
  153. pose.handL = QVector3D(bowX - 0.05F, HP::SHOULDER_Y + 0.05F, 0.55F);
  154. float const shoulder_twist = 0.08F;
  155. pose.shoulderR.setY(pose.shoulderR.y() + shoulder_twist);
  156. pose.shoulderL.setY(pose.shoulderL.y() - shoulder_twist * 0.5F);
  157. } else if (attack_phase < 0.58F) {
  158. float t = (attack_phase - 0.50F) / 0.08F;
  159. t = t * t * t;
  160. pose.hand_r = draw_pos * (1.0F - t) + release_pos * t;
  161. pose.handL = QVector3D(bowX - 0.05F, HP::SHOULDER_Y + 0.05F, 0.55F);
  162. float const shoulder_twist = 0.08F * (1.0F - t * 0.6F);
  163. pose.shoulderR.setY(pose.shoulderR.y() + shoulder_twist);
  164. pose.shoulderL.setY(pose.shoulderL.y() - shoulder_twist * 0.5F);
  165. pose.headPos.setZ(pose.headPos.z() - t * 0.04F);
  166. } else {
  167. float t = (attack_phase - 0.58F) / 0.42F;
  168. t = 1.0F - (1.0F - t) * (1.0F - t);
  169. pose.hand_r = release_pos * (1.0F - t) + aim_pos * t;
  170. pose.handL = QVector3D(bowX - 0.05F, HP::SHOULDER_Y + 0.05F, 0.55F);
  171. float const shoulder_twist = 0.08F * 0.4F * (1.0F - t);
  172. pose.shoulderR.setY(pose.shoulderR.y() + shoulder_twist);
  173. pose.shoulderL.setY(pose.shoulderL.y() - shoulder_twist * 0.5F);
  174. pose.headPos.setZ(pose.headPos.z() - 0.04F * (1.0F - t));
  175. }
  176. }
  177. }
  178. QVector3D right_axis = pose.shoulderR - pose.shoulderL;
  179. right_axis.setY(0.0F);
  180. if (right_axis.lengthSquared() < 1e-8F) {
  181. right_axis = QVector3D(1, 0, 0);
  182. }
  183. right_axis.normalize();
  184. QVector3D const outward_l = -right_axis;
  185. QVector3D const outward_r = right_axis;
  186. pose.elbowL = elbowBendTorso(pose.shoulderL, pose.handL, outward_l, 0.45F,
  187. 0.15F, -0.08F, +1.0F);
  188. pose.elbowR = elbowBendTorso(pose.shoulderR, pose.hand_r, outward_r, 0.48F,
  189. 0.12F, 0.02F, +1.0F);
  190. }
  191. void addAttachments(const DrawContext &ctx, const HumanoidVariant &v,
  192. const HumanoidPose &pose, const AnimationInputs &anim,
  193. ISubmitter &out) const override {
  194. using HP = HumanProportions;
  195. QVector3D team_tint = resolveTeamTint(ctx);
  196. uint32_t seed = 0U;
  197. if (ctx.entity != nullptr) {
  198. auto *unit = ctx.entity->getComponent<Engine::Core::UnitComponent>();
  199. if (unit != nullptr) {
  200. seed ^= uint32_t(unit->owner_id * 2654435761U);
  201. }
  202. seed ^= uint32_t(reinterpret_cast<uintptr_t>(ctx.entity) & 0xFFFFFFFFU);
  203. }
  204. ArcherExtras extras;
  205. auto it = m_extrasCache.find(seed);
  206. if (it != m_extrasCache.end()) {
  207. extras = it->second;
  208. } else {
  209. extras.metalHead = Render::Geom::clampVec01(v.palette.metal * 1.15F);
  210. extras.stringCol = QVector3D(0.30F, 0.30F, 0.32F);
  211. auto tint = [&](float k) {
  212. return QVector3D(clamp01(team_tint.x() * k), clamp01(team_tint.y() * k),
  213. clamp01(team_tint.z() * k));
  214. };
  215. extras.fletch = tint(0.9F);
  216. extras.bowTopY = HP::SHOULDER_Y + 0.55F;
  217. extras.bowBotY = HP::WAIST_Y - 0.25F;
  218. m_extrasCache[seed] = extras;
  219. if (m_extrasCache.size() > MAX_EXTRAS_CACHE_SIZE) {
  220. m_extrasCache.clear();
  221. }
  222. }
  223. drawQuiver(ctx, v, pose, extras, seed, out);
  224. float attack_phase = 0.0F;
  225. if (anim.is_attacking && !anim.isMelee) {
  226. attack_phase = std::fmod(anim.time * ARCHER_INV_ATTACK_CYCLE_TIME, 1.0F);
  227. }
  228. drawBowAndArrow(ctx, pose, v, extras, anim.is_attacking && !anim.isMelee,
  229. attack_phase, out);
  230. }
  231. void drawHelmet(const DrawContext &ctx, const HumanoidVariant &v,
  232. const HumanoidPose &pose, ISubmitter &out) const override {
  233. using HP = HumanProportions;
  234. QVector3D const helmet_color =
  235. v.palette.metal * QVector3D(1.08F, 0.98F, 0.78F);
  236. QVector3D const helmet_accent = helmet_color * 1.12F;
  237. QVector3D const helmet_top(0, pose.headPos.y() + pose.headR * 1.28F, 0);
  238. QVector3D const helmet_bot(0, pose.headPos.y() + pose.headR * 0.08F, 0);
  239. float const helmet_r = pose.headR * 1.10F;
  240. out.mesh(getUnitCylinder(),
  241. cylinderBetween(ctx.model, helmet_bot, helmet_top, helmet_r),
  242. helmet_color, nullptr, 1.0F);
  243. QVector3D const apex_pos(0, pose.headPos.y() + pose.headR * 1.48F, 0);
  244. out.mesh(getUnitCone(),
  245. coneFromTo(ctx.model, helmet_top, apex_pos, helmet_r * 0.97F),
  246. helmet_accent, nullptr, 1.0F);
  247. auto ring = [&](const QVector3D &center, float r, float h,
  248. const QVector3D &col) {
  249. QVector3D const a = center + QVector3D(0, h * 0.5F, 0);
  250. QVector3D const b = center - QVector3D(0, h * 0.5F, 0);
  251. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, a, b, r), col,
  252. nullptr, 1.0F);
  253. };
  254. QVector3D const brow_pos(0, pose.headPos.y() + pose.headR * 0.35F, 0);
  255. ring(brow_pos, helmet_r * 1.07F, 0.020F, helmet_accent);
  256. ring(QVector3D(0, pose.headPos.y() + pose.headR * 0.65F, 0),
  257. helmet_r * 1.03F, 0.015F, helmet_color * 1.05F);
  258. ring(QVector3D(0, pose.headPos.y() + pose.headR * 0.95F, 0),
  259. helmet_r * 1.01F, 0.012F, helmet_color * 1.03F);
  260. float const cheek_w = pose.headR * 0.48F;
  261. QVector3D const cheek_top(0, pose.headPos.y() + pose.headR * 0.22F, 0);
  262. QVector3D const cheek_bot(0, pose.headPos.y() - pose.headR * 0.42F, 0);
  263. QVector3D const cheek_ltop =
  264. cheek_top + QVector3D(-cheek_w, 0, pose.headR * 0.38F);
  265. QVector3D const cheek_lbot =
  266. cheek_bot + QVector3D(-cheek_w * 0.82F, 0, pose.headR * 0.28F);
  267. out.mesh(getUnitCylinder(),
  268. cylinderBetween(ctx.model, cheek_lbot, cheek_ltop, 0.028F),
  269. helmet_color * 0.96F, nullptr, 1.0F);
  270. QVector3D const cheek_rtop =
  271. cheek_top + QVector3D(cheek_w, 0, pose.headR * 0.38F);
  272. QVector3D const cheek_rbot =
  273. cheek_bot + QVector3D(cheek_w * 0.82F, 0, pose.headR * 0.28F);
  274. out.mesh(getUnitCylinder(),
  275. cylinderBetween(ctx.model, cheek_rbot, cheek_rtop, 0.028F),
  276. helmet_color * 0.96F, nullptr, 1.0F);
  277. QVector3D const neck_guard_top(0, pose.headPos.y() + pose.headR * 0.03F,
  278. -pose.headR * 0.82F);
  279. QVector3D const neck_guard_bot(0, pose.headPos.y() - pose.headR * 0.32F,
  280. -pose.headR * 0.88F);
  281. out.mesh(getUnitCylinder(),
  282. cylinderBetween(ctx.model, neck_guard_bot, neck_guard_top,
  283. helmet_r * 0.88F),
  284. helmet_color * 0.93F, nullptr, 1.0F);
  285. QVector3D const crest_base = apex_pos;
  286. QVector3D const crest_mid = crest_base + QVector3D(0, 0.09F, 0);
  287. QVector3D const crest_top = crest_mid + QVector3D(0, 0.12F, 0);
  288. out.mesh(getUnitCylinder(),
  289. cylinderBetween(ctx.model, crest_base, crest_mid, 0.018F),
  290. helmet_accent, nullptr, 1.0F);
  291. out.mesh(getUnitCone(), coneFromTo(ctx.model, crest_mid, crest_top, 0.042F),
  292. QVector3D(0.88F, 0.18F, 0.18F), nullptr, 1.0F);
  293. out.mesh(getUnitSphere(), sphereAt(ctx.model, crest_top, 0.020F),
  294. helmet_accent, nullptr, 1.0F);
  295. }
  296. void draw_armorOverlay(const DrawContext &ctx, const HumanoidVariant &v,
  297. const HumanoidPose &pose, float y_top_cover,
  298. float torso_r, float shoulder_half_span,
  299. float upper_arm_r, const QVector3D &right_axis,
  300. ISubmitter &out) const override {
  301. using HP = HumanProportions;
  302. auto ring = [&](const QVector3D &center, float r, float h,
  303. const QVector3D &col) {
  304. QVector3D const a = center + QVector3D(0, h * 0.5F, 0);
  305. QVector3D const b = center - QVector3D(0, h * 0.5F, 0);
  306. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, a, b, r), col,
  307. nullptr, 1.0F);
  308. };
  309. QVector3D mail_color = v.palette.metal * QVector3D(0.85F, 0.87F, 0.92F);
  310. QVector3D leather_trim = v.palette.leatherDark * 0.90F;
  311. float const waist_y = pose.pelvisPos.y();
  312. QVector3D const mail_top(0, y_top_cover + 0.01F, 0);
  313. QVector3D const mail_mid(0, (y_top_cover + waist_y) * 0.5F, 0);
  314. QVector3D const mail_bot(0, waist_y + 0.08F, 0);
  315. float const rTop = torso_r * 1.10F;
  316. float const rMid = torso_r * 1.08F;
  317. out.mesh(getUnitCylinder(),
  318. cylinderBetween(ctx.model, mail_top, mail_mid, rTop), mail_color,
  319. nullptr, 1.0F);
  320. out.mesh(getUnitCylinder(),
  321. cylinderBetween(ctx.model, mail_mid, mail_bot, rMid),
  322. mail_color * 0.95F, nullptr, 1.0F);
  323. for (int i = 0; i < 3; ++i) {
  324. float const y = mail_top.y() - (i * 0.12F);
  325. ring(QVector3D(0, y, 0), rTop * (1.01F + i * 0.005F), 0.012F,
  326. leather_trim);
  327. }
  328. auto draw_pauldron = [&](const QVector3D &shoulder,
  329. const QVector3D &outward) {
  330. for (int i = 0; i < 3; ++i) {
  331. float const segY = shoulder.y() + 0.02F - i * 0.035F;
  332. float const segR = upper_arm_r * (2.2F - i * 0.15F);
  333. QVector3D seg_top(shoulder.x(), segY + 0.025F, shoulder.z());
  334. QVector3D seg_bot(shoulder.x(), segY - 0.010F, shoulder.z());
  335. seg_top += outward * 0.02F;
  336. seg_bot += outward * 0.02F;
  337. out.mesh(getUnitSphere(), sphereAt(ctx.model, seg_top, segR),
  338. mail_color * (1.0F - i * 0.05F), nullptr, 1.0F);
  339. }
  340. };
  341. draw_pauldron(pose.shoulderL, -right_axis);
  342. draw_pauldron(pose.shoulderR, right_axis);
  343. auto draw_manica = [&](const QVector3D &shoulder, const QVector3D &elbow) {
  344. QVector3D dir = (elbow - shoulder);
  345. float const len = dir.length();
  346. if (len < 1e-5F) {
  347. return;
  348. }
  349. dir /= len;
  350. for (int i = 0; i < 4; ++i) {
  351. float const t0 = 0.08F + i * 0.18F;
  352. float const t1 = t0 + 0.16F;
  353. QVector3D const a = shoulder + dir * (t0 * len);
  354. QVector3D const b = shoulder + dir * (t1 * len);
  355. float const r = upper_arm_r * (1.25F - i * 0.03F);
  356. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, a, b, r),
  357. mail_color * (0.95F - i * 0.03F), nullptr, 1.0F);
  358. }
  359. };
  360. draw_manica(pose.shoulderL, pose.elbowL);
  361. draw_manica(pose.shoulderR, pose.elbowR);
  362. QVector3D const belt_top(0, waist_y + 0.06F, 0);
  363. QVector3D const belt_bot(0, waist_y - 0.02F, 0);
  364. float const belt_r = torso_r * 1.12F;
  365. out.mesh(getUnitCylinder(),
  366. cylinderBetween(ctx.model, belt_top, belt_bot, belt_r),
  367. leather_trim, nullptr, 1.0F);
  368. QVector3D const brass_color =
  369. v.palette.metal * QVector3D(1.2F, 1.0F, 0.65F);
  370. ring(QVector3D(0, waist_y + 0.02F, 0), belt_r * 1.02F, 0.010F, brass_color);
  371. auto draw_pteruge = [&](float angle, float yStart, float length) {
  372. float const rad = torso_r * 1.15F;
  373. float const x = rad * std::sin(angle);
  374. float const z = rad * std::cos(angle);
  375. QVector3D const top(x, yStart, z);
  376. QVector3D const bot(x * 0.95F, yStart - length, z * 0.95F);
  377. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, top, bot, 0.018F),
  378. leather_trim * 0.85F, nullptr, 1.0F);
  379. };
  380. float const shoulder_pteruge_y = y_top_cover - 0.02F;
  381. for (int i = 0; i < 8; ++i) {
  382. float const angle = (i / 8.0F) * 2.0F * std::numbers::pi_v<float>;
  383. draw_pteruge(angle, shoulder_pteruge_y, 0.14F);
  384. }
  385. float const waist_pteruge_y = waist_y - 0.04F;
  386. for (int i = 0; i < 10; ++i) {
  387. float const angle = (i / 10.0F) * 2.0F * std::numbers::pi_v<float>;
  388. draw_pteruge(angle, waist_pteruge_y, 0.18F);
  389. }
  390. QVector3D const collar_top(0, y_top_cover + 0.018F, 0);
  391. QVector3D const collar_bot(0, y_top_cover - 0.008F, 0);
  392. out.mesh(getUnitCylinder(),
  393. cylinderBetween(ctx.model, collar_top, collar_bot,
  394. HP::NECK_RADIUS * 1.8F),
  395. mail_color * 1.05F, nullptr, 1.0F);
  396. }
  397. void drawShoulderDecorations(const DrawContext &ctx, const HumanoidVariant &v,
  398. const HumanoidPose &pose, float y_top_cover,
  399. float y_neck, const QVector3D &right_axis,
  400. ISubmitter &out) const override {
  401. using HP = HumanProportions;
  402. QVector3D brass_color = v.palette.metal * QVector3D(1.2F, 1.0F, 0.65F);
  403. auto draw_phalera = [&](const QVector3D &pos) {
  404. QMatrix4x4 m = ctx.model;
  405. m.translate(pos);
  406. m.scale(0.025F);
  407. out.mesh(getUnitSphere(), m, brass_color, nullptr, 1.0F);
  408. };
  409. draw_phalera(pose.shoulderL + QVector3D(0, 0.05F, 0.02F));
  410. draw_phalera(pose.shoulderR + QVector3D(0, 0.05F, 0.02F));
  411. QVector3D const clasp_pos(0, y_neck + 0.02F, 0.08F);
  412. QMatrix4x4 clasp_m = ctx.model;
  413. clasp_m.translate(clasp_pos);
  414. clasp_m.scale(0.020F);
  415. out.mesh(getUnitSphere(), clasp_m, brass_color * 1.1F, nullptr, 1.0F);
  416. QVector3D const cape_top = clasp_pos + QVector3D(0, -0.02F, -0.05F);
  417. QVector3D const cape_bot = clasp_pos + QVector3D(0, -0.25F, -0.15F);
  418. QVector3D const red_fabric = v.palette.cloth * QVector3D(1.2F, 0.3F, 0.3F);
  419. out.mesh(getUnitCylinder(),
  420. cylinderBetween(ctx.model, cape_top, cape_bot, 0.025F),
  421. red_fabric * 0.85F, nullptr, 1.0F);
  422. }
  423. private:
  424. static void drawQuiver(const DrawContext &ctx, const HumanoidVariant &v,
  425. const HumanoidPose &pose, const ArcherExtras &extras,
  426. uint32_t seed, ISubmitter &out) {
  427. using HP = HumanProportions;
  428. QVector3D const spine_mid = (pose.shoulderL + pose.shoulderR) * 0.5F;
  429. QVector3D const quiver_offset(-0.08F, 0.10F, -0.25F);
  430. QVector3D const qTop = spine_mid + quiver_offset;
  431. QVector3D const q_base = qTop + QVector3D(-0.02F, -0.30F, 0.03F);
  432. float const quiver_r = HP::HEAD_RADIUS * 0.45F;
  433. out.mesh(getUnitCylinder(),
  434. cylinderBetween(ctx.model, q_base, qTop, quiver_r),
  435. v.palette.leather, nullptr, 1.0F);
  436. float const j = (hash_01(seed) - 0.5F) * 0.04F;
  437. float const k =
  438. (hash_01(seed ^ HashXorShift::k_golden_ratio) - 0.5F) * 0.04F;
  439. QVector3D const a1 = qTop + QVector3D(0.00F + j, 0.08F, 0.00F + k);
  440. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, qTop, a1, 0.010F),
  441. v.palette.wood, nullptr, 1.0F);
  442. out.mesh(getUnitCone(),
  443. coneFromTo(ctx.model, a1, a1 + QVector3D(0, 0.05F, 0), 0.025F),
  444. extras.fletch, nullptr, 1.0F);
  445. QVector3D const a2 = qTop + QVector3D(0.02F - j, 0.07F, 0.02F - k);
  446. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, qTop, a2, 0.010F),
  447. v.palette.wood, nullptr, 1.0F);
  448. out.mesh(getUnitCone(),
  449. coneFromTo(ctx.model, a2, a2 + QVector3D(0, 0.05F, 0), 0.025F),
  450. extras.fletch, nullptr, 1.0F);
  451. }
  452. static void drawBowAndArrow(const DrawContext &ctx, const HumanoidPose &pose,
  453. const HumanoidVariant &v,
  454. const ArcherExtras &extras, bool is_attacking,
  455. float attack_phase, ISubmitter &out) {
  456. const QVector3D up(0.0F, 1.0F, 0.0F);
  457. const QVector3D forward(0.0F, 0.0F, 1.0F);
  458. QVector3D const grip = pose.handL;
  459. float const bow_plane_z = 0.45F;
  460. QVector3D const top_end(extras.bowX, extras.bowTopY, bow_plane_z);
  461. QVector3D const bot_end(extras.bowX, extras.bowBotY, bow_plane_z);
  462. QVector3D const nock(
  463. extras.bowX,
  464. clampf(pose.hand_r.y(), extras.bowBotY + 0.05F, extras.bowTopY - 0.05F),
  465. clampf(pose.hand_r.z(), bow_plane_z - 0.30F, bow_plane_z + 0.30F));
  466. const int segs = 22;
  467. auto q_bezier = [](const QVector3D &a, const QVector3D &c,
  468. const QVector3D &b, float t) {
  469. float const u = 1.0F - t;
  470. return u * u * a + 2.0F * u * t * c + t * t * b;
  471. };
  472. float const bow_mid_y = (top_end.y() + bot_end.y()) * 0.5F;
  473. float const ctrl_y = bow_mid_y + 0.45F;
  474. QVector3D const ctrl(extras.bowX, ctrl_y,
  475. bow_plane_z + extras.bowDepth * 0.6F);
  476. QVector3D prev = bot_end;
  477. for (int i = 1; i <= segs; ++i) {
  478. float const t = float(i) / float(segs);
  479. QVector3D const cur = q_bezier(bot_end, ctrl, top_end, t);
  480. out.mesh(getUnitCylinder(),
  481. cylinderBetween(ctx.model, prev, cur, extras.bowRodR),
  482. v.palette.wood, nullptr, 1.0F);
  483. prev = cur;
  484. }
  485. out.mesh(getUnitCylinder(),
  486. cylinderBetween(ctx.model, grip - up * 0.05F, grip + up * 0.05F,
  487. extras.bowRodR * 1.45F),
  488. v.palette.wood, nullptr, 1.0F);
  489. out.mesh(getUnitCylinder(),
  490. cylinderBetween(ctx.model, top_end, nock, extras.stringR),
  491. extras.stringCol, nullptr, 1.0F);
  492. out.mesh(getUnitCylinder(),
  493. cylinderBetween(ctx.model, nock, bot_end, extras.stringR),
  494. extras.stringCol, nullptr, 1.0F);
  495. out.mesh(getUnitCylinder(),
  496. cylinderBetween(ctx.model, pose.hand_r, nock, 0.0045F),
  497. extras.stringCol * 0.9F, nullptr, 1.0F);
  498. bool const show_arrow =
  499. !is_attacking ||
  500. (is_attacking && attack_phase >= 0.0F && attack_phase < 0.52F);
  501. if (show_arrow) {
  502. QVector3D const tail = nock - forward * 0.06F;
  503. QVector3D const tip = tail + forward * 0.90F;
  504. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, tail, tip, 0.018F),
  505. v.palette.wood, nullptr, 1.0F);
  506. QVector3D const head_base = tip - forward * 0.10F;
  507. out.mesh(getUnitCone(), coneFromTo(ctx.model, head_base, tip, 0.05F),
  508. extras.metalHead, nullptr, 1.0F);
  509. QVector3D const f1b = tail - forward * 0.02F;
  510. QVector3D const f1a = f1b - forward * 0.06F;
  511. QVector3D const f2b = tail + forward * 0.02F;
  512. QVector3D const f2a = f2b + forward * 0.06F;
  513. out.mesh(getUnitCone(), coneFromTo(ctx.model, f1b, f1a, 0.04F),
  514. extras.fletch, nullptr, 1.0F);
  515. out.mesh(getUnitCone(), coneFromTo(ctx.model, f2a, f2b, 0.04F),
  516. extras.fletch, nullptr, 1.0F);
  517. }
  518. }
  519. };
  520. void registerArcherRenderer(Render::GL::EntityRendererRegistry &registry) {
  521. static ArcherRenderer const renderer;
  522. registry.registerRenderer(
  523. "archer", [](const DrawContext &ctx, ISubmitter &out) {
  524. static ArcherRenderer const static_renderer;
  525. Shader *archer_shader = nullptr;
  526. if (ctx.backend != nullptr) {
  527. archer_shader = ctx.backend->shader(QStringLiteral("archer"));
  528. }
  529. auto *scene_renderer = dynamic_cast<Renderer *>(&out);
  530. if ((scene_renderer != nullptr) && (archer_shader != nullptr)) {
  531. scene_renderer->setCurrentShader(archer_shader);
  532. }
  533. static_renderer.render(ctx, out);
  534. if (scene_renderer != nullptr) {
  535. scene_renderer->setCurrentShader(nullptr);
  536. }
  537. });
  538. }
  539. } // namespace Render::GL