archer_renderer.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  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/render_constants.h"
  9. #include "../../../gl/shader.h"
  10. #include "../../../humanoid/rig.h"
  11. #include "../../../humanoid/style_palette.h"
  12. #include "../../../humanoid_math.h"
  13. #include "../../../humanoid_specs.h"
  14. #include "../../../palette.h"
  15. #include "../../../scene_renderer.h"
  16. #include "../../../submitter.h"
  17. #include "../../registry.h"
  18. #include "../../renderer_constants.h"
  19. #include "archer_style.h"
  20. #include <QMatrix4x4>
  21. #include <QString>
  22. #include <QVector3D>
  23. #include <cmath>
  24. #include <cstdint>
  25. #include <numbers>
  26. #include <optional>
  27. #include <qmatrix4x4.h>
  28. #include <qstringliteral.h>
  29. #include <qvectornd.h>
  30. #include <string>
  31. #include <string_view>
  32. #include <unordered_map>
  33. namespace Render::GL::Kingdom {
  34. namespace {
  35. constexpr std::string_view k_default_style_key = "default";
  36. constexpr std::string_view k_attachment_headwrap = "carthage_headwrap";
  37. auto style_registry() -> std::unordered_map<std::string, ArcherStyleConfig> & {
  38. static std::unordered_map<std::string, ArcherStyleConfig> styles;
  39. return styles;
  40. }
  41. void ensure_archer_styles_registered() {
  42. static const bool registered = []() {
  43. register_kingdom_archer_style();
  44. return true;
  45. }();
  46. (void)registered;
  47. }
  48. constexpr float k_team_mix_weight = 0.65F;
  49. constexpr float k_style_mix_weight = 0.35F;
  50. } // namespace
  51. void register_archer_style(const std::string &nation_id,
  52. const ArcherStyleConfig &style) {
  53. style_registry()[nation_id] = style;
  54. }
  55. using Render::Geom::clamp01;
  56. using Render::Geom::clampf;
  57. using Render::Geom::coneFromTo;
  58. using Render::Geom::cylinderBetween;
  59. using Render::Geom::sphereAt;
  60. using Render::GL::Humanoid::mix_palette_color;
  61. using Render::GL::Humanoid::saturate_color;
  62. struct ArcherExtras {
  63. QVector3D stringCol;
  64. QVector3D fletch;
  65. QVector3D metalHead;
  66. float bowRodR = 0.035F;
  67. float stringR = 0.008F;
  68. float bowDepth = 0.25F;
  69. float bowX = 0.0F;
  70. float bowTopY{};
  71. float bowBotY{};
  72. };
  73. class ArcherRenderer : public HumanoidRendererBase {
  74. public:
  75. auto getProportionScaling() const -> QVector3D override {
  76. return {0.94F, 1.01F, 0.96F};
  77. }
  78. void getVariant(const DrawContext &ctx, uint32_t seed,
  79. HumanoidVariant &v) const override {
  80. QVector3D const team_tint = resolveTeamTint(ctx);
  81. v.palette = makeHumanoidPalette(team_tint, seed);
  82. auto const &style = resolve_style(ctx);
  83. apply_palette_overrides(style, team_tint, v);
  84. }
  85. void customizePose(const DrawContext &,
  86. const HumanoidAnimationContext &anim_ctx, uint32_t seed,
  87. HumanoidPose &pose) const override {
  88. using HP = HumanProportions;
  89. const AnimationInputs &anim = anim_ctx.inputs;
  90. float const arm_height_jitter = (hash_01(seed ^ 0xABCDU) - 0.5F) * 0.03F;
  91. float const arm_asymmetry = (hash_01(seed ^ 0xDEF0U) - 0.5F) * 0.04F;
  92. float const bow_x = 0.0F;
  93. if (anim.isInHoldMode || anim.isExitingHold) {
  94. float const t = anim.isInHoldMode ? 1.0F : (1.0F - anim.holdExitProgress);
  95. float const kneel_depth = 0.45F * t;
  96. float const pelvis_y = HP::WAIST_Y - kneel_depth;
  97. pose.pelvisPos.setY(pelvis_y);
  98. float const stance_narrow = 0.12F;
  99. float const left_knee_y = HP::GROUND_Y + 0.08F * t;
  100. float const left_knee_z = -0.05F * t;
  101. pose.knee_l = QVector3D(-stance_narrow, left_knee_y, left_knee_z);
  102. pose.footL = QVector3D(-stance_narrow - 0.03F, HP::GROUND_Y,
  103. left_knee_z - HP::LOWER_LEG_LEN * 0.95F * t);
  104. float const right_foot_z = 0.30F * t;
  105. pose.foot_r = QVector3D(stance_narrow, HP::GROUND_Y + pose.footYOffset,
  106. right_foot_z);
  107. float const right_knee_y = pelvis_y - 0.10F;
  108. float const right_knee_z = right_foot_z - 0.05F;
  109. pose.knee_r = QVector3D(stance_narrow, right_knee_y, right_knee_z);
  110. float const upper_body_drop = kneel_depth;
  111. pose.shoulderL.setY(HP::SHOULDER_Y - upper_body_drop);
  112. pose.shoulderR.setY(HP::SHOULDER_Y - upper_body_drop);
  113. pose.neck_base.setY(HP::NECK_BASE_Y - upper_body_drop);
  114. pose.headPos.setY((HP::HEAD_TOP_Y + HP::CHIN_Y) * 0.5F - upper_body_drop);
  115. float const forward_lean = 0.10F * t;
  116. pose.shoulderL.setZ(pose.shoulderL.z() + forward_lean);
  117. pose.shoulderR.setZ(pose.shoulderR.z() + forward_lean);
  118. pose.neck_base.setZ(pose.neck_base.z() + forward_lean * 0.8F);
  119. pose.headPos.setZ(pose.headPos.z() + forward_lean * 0.7F);
  120. QVector3D const hold_hand_l(bow_x - 0.15F, pose.shoulderL.y() + 0.30F,
  121. 0.55F);
  122. QVector3D const hold_hand_r(bow_x + 0.12F, pose.shoulderR.y() + 0.15F,
  123. 0.10F);
  124. QVector3D const normal_hand_l(bow_x - 0.05F + arm_asymmetry,
  125. HP::SHOULDER_Y + 0.05F + arm_height_jitter,
  126. 0.55F);
  127. QVector3D const normal_hand_r(
  128. 0.15F - arm_asymmetry * 0.5F,
  129. HP::SHOULDER_Y + 0.15F + arm_height_jitter * 0.8F, 0.20F);
  130. pose.handL = normal_hand_l * (1.0F - t) + hold_hand_l * t;
  131. pose.hand_r = normal_hand_r * (1.0F - t) + hold_hand_r * t;
  132. } else {
  133. pose.handL = QVector3D(bow_x - 0.05F + arm_asymmetry,
  134. HP::SHOULDER_Y + 0.05F + arm_height_jitter, 0.55F);
  135. pose.hand_r =
  136. QVector3D(0.15F - arm_asymmetry * 0.5F,
  137. HP::SHOULDER_Y + 0.15F + arm_height_jitter * 0.8F, 0.20F);
  138. }
  139. if (anim.is_attacking && !anim.isInHoldMode) {
  140. float const attack_phase =
  141. std::fmod(anim.time * ARCHER_INV_ATTACK_CYCLE_TIME, 1.0F);
  142. if (anim.isMelee) {
  143. QVector3D const rest_pos(0.25F, HP::SHOULDER_Y, 0.10F);
  144. QVector3D const raised_pos(0.30F, HP::HEAD_TOP_Y + 0.2F, -0.05F);
  145. QVector3D const strike_pos(0.35F, HP::WAIST_Y, 0.45F);
  146. if (attack_phase < 0.25F) {
  147. float t = attack_phase / 0.25F;
  148. t = t * t;
  149. pose.hand_r = rest_pos * (1.0F - t) + raised_pos * t;
  150. pose.handL = QVector3D(-0.15F, HP::SHOULDER_Y - 0.1F * t, 0.20F);
  151. } else if (attack_phase < 0.35F) {
  152. pose.hand_r = raised_pos;
  153. pose.handL = QVector3D(-0.15F, HP::SHOULDER_Y - 0.1F, 0.20F);
  154. } else if (attack_phase < 0.55F) {
  155. float t = (attack_phase - 0.35F) / 0.2F;
  156. t = t * t * t;
  157. pose.hand_r = raised_pos * (1.0F - t) + strike_pos * t;
  158. pose.handL =
  159. QVector3D(-0.15F, HP::SHOULDER_Y - 0.1F * (1.0F - t * 0.5F),
  160. 0.20F + 0.15F * t);
  161. } else {
  162. float t = (attack_phase - 0.55F) / 0.45F;
  163. t = 1.0F - (1.0F - t) * (1.0F - t);
  164. pose.hand_r = strike_pos * (1.0F - t) + rest_pos * t;
  165. pose.handL = QVector3D(-0.15F, HP::SHOULDER_Y - 0.05F * (1.0F - t),
  166. 0.35F * (1.0F - t) + 0.20F * t);
  167. }
  168. } else {
  169. QVector3D const aim_pos(0.18F, HP::SHOULDER_Y + 0.18F, 0.35F);
  170. QVector3D const draw_pos(0.22F, HP::SHOULDER_Y + 0.10F, -0.30F);
  171. QVector3D const release_pos(0.18F, HP::SHOULDER_Y + 0.20F, 0.10F);
  172. if (attack_phase < 0.20F) {
  173. float t = attack_phase / 0.20F;
  174. t = t * t;
  175. pose.hand_r = aim_pos * (1.0F - t) + draw_pos * t;
  176. pose.handL = QVector3D(bow_x - 0.05F, HP::SHOULDER_Y + 0.05F, 0.55F);
  177. float const shoulder_twist = t * 0.08F;
  178. pose.shoulderR.setY(pose.shoulderR.y() + shoulder_twist);
  179. pose.shoulderL.setY(pose.shoulderL.y() - shoulder_twist * 0.5F);
  180. } else if (attack_phase < 0.50F) {
  181. pose.hand_r = draw_pos;
  182. pose.handL = QVector3D(bow_x - 0.05F, HP::SHOULDER_Y + 0.05F, 0.55F);
  183. float const shoulder_twist = 0.08F;
  184. pose.shoulderR.setY(pose.shoulderR.y() + shoulder_twist);
  185. pose.shoulderL.setY(pose.shoulderL.y() - shoulder_twist * 0.5F);
  186. } else if (attack_phase < 0.58F) {
  187. float t = (attack_phase - 0.50F) / 0.08F;
  188. t = t * t * t;
  189. pose.hand_r = draw_pos * (1.0F - t) + release_pos * t;
  190. pose.handL = QVector3D(bow_x - 0.05F, HP::SHOULDER_Y + 0.05F, 0.55F);
  191. float const shoulder_twist = 0.08F * (1.0F - t * 0.6F);
  192. pose.shoulderR.setY(pose.shoulderR.y() + shoulder_twist);
  193. pose.shoulderL.setY(pose.shoulderL.y() - shoulder_twist * 0.5F);
  194. pose.headPos.setZ(pose.headPos.z() - t * 0.04F);
  195. } else {
  196. float t = (attack_phase - 0.58F) / 0.42F;
  197. t = 1.0F - (1.0F - t) * (1.0F - t);
  198. pose.hand_r = release_pos * (1.0F - t) + aim_pos * t;
  199. pose.handL = QVector3D(bow_x - 0.05F, HP::SHOULDER_Y + 0.05F, 0.55F);
  200. float const shoulder_twist = 0.08F * 0.4F * (1.0F - t);
  201. pose.shoulderR.setY(pose.shoulderR.y() + shoulder_twist);
  202. pose.shoulderL.setY(pose.shoulderL.y() - shoulder_twist * 0.5F);
  203. pose.headPos.setZ(pose.headPos.z() - 0.04F * (1.0F - t));
  204. }
  205. }
  206. }
  207. QVector3D right_axis = pose.shoulderR - pose.shoulderL;
  208. right_axis.setY(0.0F);
  209. if (right_axis.lengthSquared() < 1e-8F) {
  210. right_axis = QVector3D(1, 0, 0);
  211. }
  212. right_axis.normalize();
  213. QVector3D const outward_l = -right_axis;
  214. QVector3D const outward_r = right_axis;
  215. pose.elbowL = elbowBendTorso(pose.shoulderL, pose.handL, outward_l, 0.45F,
  216. 0.15F, -0.08F, +1.0F);
  217. pose.elbowR = elbowBendTorso(pose.shoulderR, pose.hand_r, outward_r, 0.48F,
  218. 0.12F, 0.02F, +1.0F);
  219. }
  220. void addAttachments(const DrawContext &ctx, const HumanoidVariant &v,
  221. const HumanoidPose &pose,
  222. const HumanoidAnimationContext &anim_ctx,
  223. ISubmitter &out) const override {
  224. using HP = HumanProportions;
  225. auto const &style = resolve_style(ctx);
  226. const AnimationInputs &anim = anim_ctx.inputs;
  227. QVector3D team_tint = resolveTeamTint(ctx);
  228. uint32_t seed = 0U;
  229. if (ctx.entity != nullptr) {
  230. auto *unit = ctx.entity->getComponent<Engine::Core::UnitComponent>();
  231. if (unit != nullptr) {
  232. seed ^= uint32_t(unit->owner_id * 2654435761U);
  233. }
  234. seed ^= uint32_t(reinterpret_cast<uintptr_t>(ctx.entity) & 0xFFFFFFFFU);
  235. }
  236. ArcherExtras extras;
  237. auto it = m_extrasCache.find(seed);
  238. if (it != m_extrasCache.end()) {
  239. extras = it->second;
  240. } else {
  241. extras.metalHead = Render::Geom::clampVec01(v.palette.metal * 1.15F);
  242. extras.stringCol = QVector3D(0.30F, 0.30F, 0.32F);
  243. auto tint = [&](float k) {
  244. return QVector3D(clamp01(team_tint.x() * k), clamp01(team_tint.y() * k),
  245. clamp01(team_tint.z() * k));
  246. };
  247. extras.fletch = tint(0.9F);
  248. extras.bowTopY = HP::SHOULDER_Y + 0.55F;
  249. extras.bowBotY = HP::WAIST_Y - 0.25F;
  250. apply_extras_overrides(style, extras);
  251. m_extrasCache[seed] = extras;
  252. if (m_extrasCache.size() > MAX_EXTRAS_CACHE_SIZE) {
  253. m_extrasCache.clear();
  254. }
  255. }
  256. apply_extras_overrides(style, extras);
  257. drawQuiver(ctx, v, pose, extras, seed, out);
  258. float attack_phase = 0.0F;
  259. if (anim.is_attacking && !anim.isMelee) {
  260. attack_phase = std::fmod(anim.time * ARCHER_INV_ATTACK_CYCLE_TIME, 1.0F);
  261. }
  262. drawBowAndArrow(ctx, pose, v, extras, anim.is_attacking && !anim.isMelee,
  263. attack_phase, out);
  264. }
  265. void drawHelmet(const DrawContext &ctx, const HumanoidVariant &v,
  266. const HumanoidPose &pose, ISubmitter &out) const override {
  267. using HP = HumanProportions;
  268. auto const &style = resolve_style(ctx);
  269. if (!style.show_helmet) {
  270. if (style.attachment_profile == std::string(k_attachment_headwrap)) {
  271. draw_headwrap(ctx, v, pose, out);
  272. }
  273. return;
  274. }
  275. QVector3D const helmet_color =
  276. v.palette.metal * QVector3D(1.08F, 0.98F, 0.78F);
  277. QVector3D const helmet_accent = helmet_color * 1.12F;
  278. QVector3D const helmet_top(0, pose.headPos.y() + pose.headR * 1.28F, 0);
  279. QVector3D const helmet_bot(0, pose.headPos.y() + pose.headR * 0.08F, 0);
  280. float const helmet_r = pose.headR * 1.10F;
  281. out.mesh(getUnitCylinder(),
  282. cylinderBetween(ctx.model, helmet_bot, helmet_top, helmet_r),
  283. helmet_color, nullptr, 1.0F);
  284. QVector3D const apex_pos(0, pose.headPos.y() + pose.headR * 1.48F, 0);
  285. out.mesh(getUnitCone(),
  286. coneFromTo(ctx.model, helmet_top, apex_pos, helmet_r * 0.97F),
  287. helmet_accent, nullptr, 1.0F);
  288. auto ring = [&](const QVector3D &center, float r, float h,
  289. const QVector3D &col) {
  290. QVector3D const a = center + QVector3D(0, h * 0.5F, 0);
  291. QVector3D const b = center - QVector3D(0, h * 0.5F, 0);
  292. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, a, b, r), col,
  293. nullptr, 1.0F);
  294. };
  295. QVector3D const brow_pos(0, pose.headPos.y() + pose.headR * 0.35F, 0);
  296. ring(brow_pos, helmet_r * 1.07F, 0.020F, helmet_accent);
  297. ring(QVector3D(0, pose.headPos.y() + pose.headR * 0.65F, 0),
  298. helmet_r * 1.03F, 0.015F, helmet_color * 1.05F);
  299. ring(QVector3D(0, pose.headPos.y() + pose.headR * 0.95F, 0),
  300. helmet_r * 1.01F, 0.012F, helmet_color * 1.03F);
  301. float const cheek_w = pose.headR * 0.48F;
  302. QVector3D const cheek_top(0, pose.headPos.y() + pose.headR * 0.22F, 0);
  303. QVector3D const cheek_bot(0, pose.headPos.y() - pose.headR * 0.42F, 0);
  304. QVector3D const cheek_ltop =
  305. cheek_top + QVector3D(-cheek_w, 0, pose.headR * 0.38F);
  306. QVector3D const cheek_lbot =
  307. cheek_bot + QVector3D(-cheek_w * 0.82F, 0, pose.headR * 0.28F);
  308. out.mesh(getUnitCylinder(),
  309. cylinderBetween(ctx.model, cheek_lbot, cheek_ltop, 0.028F),
  310. helmet_color * 0.96F, nullptr, 1.0F);
  311. QVector3D const cheek_rtop =
  312. cheek_top + QVector3D(cheek_w, 0, pose.headR * 0.38F);
  313. QVector3D const cheek_rbot =
  314. cheek_bot + QVector3D(cheek_w * 0.82F, 0, pose.headR * 0.28F);
  315. out.mesh(getUnitCylinder(),
  316. cylinderBetween(ctx.model, cheek_rbot, cheek_rtop, 0.028F),
  317. helmet_color * 0.96F, nullptr, 1.0F);
  318. QVector3D const neck_guard_top(0, pose.headPos.y() + pose.headR * 0.03F,
  319. -pose.headR * 0.82F);
  320. QVector3D const neck_guard_bot(0, pose.headPos.y() - pose.headR * 0.32F,
  321. -pose.headR * 0.88F);
  322. out.mesh(getUnitCylinder(),
  323. cylinderBetween(ctx.model, neck_guard_bot, neck_guard_top,
  324. helmet_r * 0.88F),
  325. helmet_color * 0.93F, nullptr, 1.0F);
  326. QVector3D const crest_base = apex_pos;
  327. QVector3D const crest_mid = crest_base + QVector3D(0, 0.09F, 0);
  328. QVector3D const crest_top = crest_mid + QVector3D(0, 0.12F, 0);
  329. out.mesh(getUnitCylinder(),
  330. cylinderBetween(ctx.model, crest_base, crest_mid, 0.018F),
  331. helmet_accent, nullptr, 1.0F);
  332. out.mesh(getUnitCone(), coneFromTo(ctx.model, crest_mid, crest_top, 0.042F),
  333. QVector3D(0.88F, 0.18F, 0.18F), nullptr, 1.0F);
  334. out.mesh(getUnitSphere(), sphereAt(ctx.model, crest_top, 0.020F),
  335. helmet_accent, nullptr, 1.0F);
  336. }
  337. void draw_armorOverlay(const DrawContext &ctx, const HumanoidVariant &v,
  338. const HumanoidPose &pose, float y_top_cover,
  339. float torso_r, float, float upper_arm_r,
  340. const QVector3D &right_axis,
  341. ISubmitter &out) const override {
  342. using HP = HumanProportions;
  343. if (!resolve_style(ctx).show_armor) {
  344. return;
  345. }
  346. auto ring = [&](const QVector3D &center, float r, float h,
  347. const QVector3D &col) {
  348. QVector3D const a = center + QVector3D(0, h * 0.5F, 0);
  349. QVector3D const b = center - QVector3D(0, h * 0.5F, 0);
  350. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, a, b, r), col,
  351. nullptr, 1.0F);
  352. };
  353. QVector3D mail_color = v.palette.metal * QVector3D(0.85F, 0.87F, 0.92F);
  354. QVector3D leather_trim = v.palette.leatherDark * 0.90F;
  355. float const waist_y = pose.pelvisPos.y();
  356. QVector3D const mail_top(0, y_top_cover + 0.01F, 0);
  357. QVector3D const mail_mid(0, (y_top_cover + waist_y) * 0.5F, 0);
  358. QVector3D const mail_bot(0, waist_y + 0.08F, 0);
  359. float const r_top = torso_r * 1.10F;
  360. float const r_mid = torso_r * 1.08F;
  361. out.mesh(getUnitCylinder(),
  362. cylinderBetween(ctx.model, mail_top, mail_mid, r_top), mail_color,
  363. nullptr, 1.0F);
  364. out.mesh(getUnitCylinder(),
  365. cylinderBetween(ctx.model, mail_mid, mail_bot, r_mid),
  366. mail_color * 0.95F, nullptr, 1.0F);
  367. for (int i = 0; i < 3; ++i) {
  368. float const y = mail_top.y() - (i * 0.12F);
  369. ring(QVector3D(0, y, 0), r_top * (1.01F + i * 0.005F), 0.012F,
  370. leather_trim);
  371. }
  372. auto draw_pauldron = [&](const QVector3D &shoulder,
  373. const QVector3D &outward) {
  374. for (int i = 0; i < 3; ++i) {
  375. float const seg_y = shoulder.y() + 0.02F - i * 0.035F;
  376. float const seg_r = upper_arm_r * (2.2F - i * 0.15F);
  377. QVector3D seg_top(shoulder.x(), seg_y + 0.025F, shoulder.z());
  378. QVector3D seg_bot(shoulder.x(), seg_y - 0.010F, shoulder.z());
  379. seg_top += outward * 0.02F;
  380. seg_bot += outward * 0.02F;
  381. out.mesh(getUnitSphere(), sphereAt(ctx.model, seg_top, seg_r),
  382. mail_color * (1.0F - i * 0.05F), nullptr, 1.0F);
  383. }
  384. };
  385. draw_pauldron(pose.shoulderL, -right_axis);
  386. draw_pauldron(pose.shoulderR, right_axis);
  387. auto draw_manica = [&](const QVector3D &shoulder, const QVector3D &elbow) {
  388. QVector3D dir = (elbow - shoulder);
  389. float const len = dir.length();
  390. if (len < 1e-5F) {
  391. return;
  392. }
  393. dir /= len;
  394. for (int i = 0; i < 4; ++i) {
  395. float const t0 = 0.08F + i * 0.18F;
  396. float const t1 = t0 + 0.16F;
  397. QVector3D const a = shoulder + dir * (t0 * len);
  398. QVector3D const b = shoulder + dir * (t1 * len);
  399. float const r = upper_arm_r * (1.25F - i * 0.03F);
  400. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, a, b, r),
  401. mail_color * (0.95F - i * 0.03F), nullptr, 1.0F);
  402. }
  403. };
  404. draw_manica(pose.shoulderL, pose.elbowL);
  405. draw_manica(pose.shoulderR, pose.elbowR);
  406. QVector3D const belt_top(0, waist_y + 0.06F, 0);
  407. QVector3D const belt_bot(0, waist_y - 0.02F, 0);
  408. float const belt_r = torso_r * 1.12F;
  409. out.mesh(getUnitCylinder(),
  410. cylinderBetween(ctx.model, belt_top, belt_bot, belt_r),
  411. leather_trim, nullptr, 1.0F);
  412. QVector3D const brass_color =
  413. v.palette.metal * QVector3D(1.2F, 1.0F, 0.65F);
  414. ring(QVector3D(0, waist_y + 0.02F, 0), belt_r * 1.02F, 0.010F, brass_color);
  415. auto draw_pteruge = [&](float angle, float yStart, float length) {
  416. float const rad = torso_r * 1.15F;
  417. float const x = rad * std::sin(angle);
  418. float const z = rad * std::cos(angle);
  419. QVector3D const top(x, yStart, z);
  420. QVector3D const bot(x * 0.95F, yStart - length, z * 0.95F);
  421. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, top, bot, 0.018F),
  422. leather_trim * 0.85F, nullptr, 1.0F);
  423. };
  424. float const shoulder_pteruge_y = y_top_cover - 0.02F;
  425. constexpr int k_shoulder_pteruge_count = 8;
  426. constexpr float k_shoulder_pteruge_divisor = 8.0F;
  427. for (int i = 0; i < k_shoulder_pteruge_count; ++i) {
  428. float const angle =
  429. (i / k_shoulder_pteruge_divisor) * 2.0F * std::numbers::pi_v<float>;
  430. draw_pteruge(angle, shoulder_pteruge_y, 0.14F);
  431. }
  432. float const waist_pteruge_y = waist_y - 0.04F;
  433. constexpr int k_waist_pteruge_count = 10;
  434. constexpr float k_waist_pteruge_divisor = 10.0F;
  435. for (int i = 0; i < k_waist_pteruge_count; ++i) {
  436. float const angle =
  437. (i / k_waist_pteruge_divisor) * 2.0F * std::numbers::pi_v<float>;
  438. draw_pteruge(angle, waist_pteruge_y, 0.18F);
  439. }
  440. QVector3D const collar_top(0, y_top_cover + 0.018F, 0);
  441. QVector3D const collar_bot(0, y_top_cover - 0.008F, 0);
  442. out.mesh(getUnitCylinder(),
  443. cylinderBetween(ctx.model, collar_top, collar_bot,
  444. HP::NECK_RADIUS * 1.8F),
  445. mail_color * 1.05F, nullptr, 1.0F);
  446. }
  447. void drawShoulderDecorations(const DrawContext &ctx, const HumanoidVariant &v,
  448. const HumanoidPose &pose, float, float y_neck,
  449. const QVector3D &,
  450. ISubmitter &out) const override {
  451. using HP = HumanProportions;
  452. auto const &style = resolve_style(ctx);
  453. if (!style.show_shoulder_decor && !style.show_cape) {
  454. return;
  455. }
  456. QVector3D brass_color = v.palette.metal * QVector3D(1.2F, 1.0F, 0.65F);
  457. auto draw_phalera = [&](const QVector3D &pos) {
  458. QMatrix4x4 m = ctx.model;
  459. m.translate(pos);
  460. m.scale(0.025F);
  461. out.mesh(getUnitSphere(), m, brass_color, nullptr, 1.0F);
  462. };
  463. if (style.show_shoulder_decor) {
  464. draw_phalera(pose.shoulderL + QVector3D(0, 0.05F, 0.02F));
  465. draw_phalera(pose.shoulderR + QVector3D(0, 0.05F, 0.02F));
  466. }
  467. if (!style.show_cape) {
  468. return;
  469. }
  470. QVector3D const clasp_pos(0, y_neck + 0.02F, 0.08F);
  471. QMatrix4x4 clasp_m = ctx.model;
  472. clasp_m.translate(clasp_pos);
  473. clasp_m.scale(0.020F);
  474. out.mesh(getUnitSphere(), clasp_m, brass_color * 1.1F, nullptr, 1.0F);
  475. QVector3D const cape_top = clasp_pos + QVector3D(0, -0.02F, -0.05F);
  476. QVector3D const cape_bot = clasp_pos + QVector3D(0, -0.25F, -0.15F);
  477. QVector3D cape_fabric = v.palette.cloth * QVector3D(1.2F, 0.3F, 0.3F);
  478. if (style.cape_color) {
  479. cape_fabric = saturate_color(*style.cape_color);
  480. }
  481. out.mesh(getUnitCylinder(),
  482. cylinderBetween(ctx.model, cape_top, cape_bot, 0.025F),
  483. cape_fabric * 0.85F, nullptr, 1.0F);
  484. }
  485. private:
  486. mutable std::unordered_map<uint32_t, ArcherExtras> m_extrasCache;
  487. auto
  488. resolve_style(const DrawContext &ctx) const -> const ArcherStyleConfig & {
  489. ensure_archer_styles_registered();
  490. auto &styles = style_registry();
  491. std::string nation_id;
  492. if (ctx.entity != nullptr) {
  493. if (auto *unit =
  494. ctx.entity->getComponent<Engine::Core::UnitComponent>()) {
  495. nation_id = unit->nation_id;
  496. }
  497. }
  498. if (!nation_id.empty()) {
  499. auto it = styles.find(nation_id);
  500. if (it != styles.end()) {
  501. return it->second;
  502. }
  503. }
  504. auto fallback = styles.find(std::string(k_default_style_key));
  505. if (fallback != styles.end()) {
  506. return fallback->second;
  507. }
  508. static const ArcherStyleConfig default_style{};
  509. return default_style;
  510. }
  511. public:
  512. auto resolve_shader_key(const DrawContext &ctx) const -> QString {
  513. const ArcherStyleConfig &style = resolve_style(ctx);
  514. if (!style.shader_id.empty()) {
  515. return QString::fromStdString(style.shader_id);
  516. }
  517. return QStringLiteral("archer");
  518. }
  519. private:
  520. void apply_palette_overrides(const ArcherStyleConfig &style,
  521. const QVector3D &team_tint,
  522. HumanoidVariant &variant) const {
  523. auto apply_color = [&](const std::optional<QVector3D> &override_color,
  524. QVector3D &target) {
  525. target = mix_palette_color(target, override_color, team_tint,
  526. k_team_mix_weight, k_style_mix_weight);
  527. };
  528. apply_color(style.cloth_color, variant.palette.cloth);
  529. apply_color(style.leather_color, variant.palette.leather);
  530. apply_color(style.leather_dark_color, variant.palette.leatherDark);
  531. apply_color(style.metal_color, variant.palette.metal);
  532. apply_color(style.wood_color, variant.palette.wood);
  533. }
  534. void apply_extras_overrides(const ArcherStyleConfig &style,
  535. ArcherExtras &extras) const {
  536. if (style.fletching_color) {
  537. extras.fletch = saturate_color(*style.fletching_color);
  538. }
  539. if (style.bow_string_color) {
  540. extras.stringCol = saturate_color(*style.bow_string_color);
  541. }
  542. }
  543. void draw_headwrap(const DrawContext &ctx, const HumanoidVariant &v,
  544. const HumanoidPose &pose, ISubmitter &out) const {
  545. QVector3D const cloth_color =
  546. saturate_color(v.palette.cloth * QVector3D(0.9F, 1.05F, 1.05F));
  547. float const head_r = pose.headR;
  548. QVector3D const band_top(0, pose.headPos.y() + head_r * 0.70F, 0);
  549. QVector3D const band_bot(0, pose.headPos.y() + head_r * 0.30F, 0);
  550. out.mesh(getUnitCylinder(),
  551. cylinderBetween(ctx.model, band_bot, band_top, head_r * 1.08F),
  552. cloth_color, nullptr, 1.0F);
  553. QVector3D const knot_center(0.10F, pose.headPos.y() + head_r * 0.60F,
  554. head_r * 0.72F);
  555. QMatrix4x4 knot_m = ctx.model;
  556. knot_m.translate(knot_center);
  557. knot_m.scale(head_r * 0.32F);
  558. out.mesh(getUnitSphere(), knot_m, cloth_color * 1.05F, nullptr, 1.0F);
  559. QVector3D const tail_top = knot_center + QVector3D(-0.08F, -0.05F, -0.06F);
  560. QVector3D const tail_bot = tail_top + QVector3D(0.02F, -0.28F, -0.08F);
  561. out.mesh(getUnitCylinder(),
  562. cylinderBetween(ctx.model, tail_top, tail_bot, head_r * 0.28F),
  563. cloth_color * QVector3D(0.92F, 0.98F, 1.05F), nullptr, 1.0F);
  564. }
  565. static void drawQuiver(const DrawContext &ctx, const HumanoidVariant &v,
  566. const HumanoidPose &pose, const ArcherExtras &extras,
  567. uint32_t seed, ISubmitter &out) {
  568. using HP = HumanProportions;
  569. QVector3D const spine_mid = (pose.shoulderL + pose.shoulderR) * 0.5F;
  570. QVector3D const quiver_offset(-0.08F, 0.10F, -0.25F);
  571. QVector3D const q_top = spine_mid + quiver_offset;
  572. QVector3D const q_base = q_top + QVector3D(-0.02F, -0.30F, 0.03F);
  573. float const quiver_r = HP::HEAD_RADIUS * 0.45F;
  574. out.mesh(getUnitCylinder(),
  575. cylinderBetween(ctx.model, q_base, q_top, quiver_r),
  576. v.palette.leather, nullptr, 1.0F);
  577. float const j = (hash_01(seed) - 0.5F) * 0.04F;
  578. float const k =
  579. (hash_01(seed ^ HashXorShift::k_golden_ratio) - 0.5F) * 0.04F;
  580. QVector3D const a1 = q_top + QVector3D(0.00F + j, 0.08F, 0.00F + k);
  581. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, q_top, a1, 0.010F),
  582. v.palette.wood, nullptr, 1.0F);
  583. out.mesh(getUnitCone(),
  584. coneFromTo(ctx.model, a1, a1 + QVector3D(0, 0.05F, 0), 0.025F),
  585. extras.fletch, nullptr, 1.0F);
  586. QVector3D const a2 = q_top + QVector3D(0.02F - j, 0.07F, 0.02F - k);
  587. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, q_top, a2, 0.010F),
  588. v.palette.wood, nullptr, 1.0F);
  589. out.mesh(getUnitCone(),
  590. coneFromTo(ctx.model, a2, a2 + QVector3D(0, 0.05F, 0), 0.025F),
  591. extras.fletch, nullptr, 1.0F);
  592. }
  593. static void drawBowAndArrow(const DrawContext &ctx, const HumanoidPose &pose,
  594. const HumanoidVariant &v,
  595. const ArcherExtras &extras, bool is_attacking,
  596. float attack_phase, ISubmitter &out) {
  597. const QVector3D up(0.0F, 1.0F, 0.0F);
  598. const QVector3D forward(0.0F, 0.0F, 1.0F);
  599. QVector3D const grip = pose.handL;
  600. float const bow_plane_z = 0.45F;
  601. QVector3D const top_end(extras.bowX, extras.bowTopY, bow_plane_z);
  602. QVector3D const bot_end(extras.bowX, extras.bowBotY, bow_plane_z);
  603. QVector3D const nock(
  604. extras.bowX,
  605. clampf(pose.hand_r.y(), extras.bowBotY + 0.05F, extras.bowTopY - 0.05F),
  606. clampf(pose.hand_r.z(), bow_plane_z - 0.30F, bow_plane_z + 0.30F));
  607. constexpr int k_bowstring_segments = 22;
  608. auto q_bezier = [](const QVector3D &a, const QVector3D &c,
  609. const QVector3D &b, float t) {
  610. float const u = 1.0F - t;
  611. return u * u * a + 2.0F * u * t * c + t * t * b;
  612. };
  613. float const bow_mid_y = (top_end.y() + bot_end.y()) * 0.5F;
  614. float const ctrl_y = bow_mid_y + 0.45F;
  615. QVector3D const ctrl(extras.bowX, ctrl_y,
  616. bow_plane_z + extras.bowDepth * 0.6F);
  617. QVector3D prev = bot_end;
  618. for (int i = 1; i <= k_bowstring_segments; ++i) {
  619. float const t = float(i) / float(k_bowstring_segments);
  620. QVector3D const cur = q_bezier(bot_end, ctrl, top_end, t);
  621. out.mesh(getUnitCylinder(),
  622. cylinderBetween(ctx.model, prev, cur, extras.bowRodR),
  623. v.palette.wood, nullptr, 1.0F);
  624. prev = cur;
  625. }
  626. out.mesh(getUnitCylinder(),
  627. cylinderBetween(ctx.model, grip - up * 0.05F, grip + up * 0.05F,
  628. extras.bowRodR * 1.45F),
  629. v.palette.wood, nullptr, 1.0F);
  630. out.mesh(getUnitCylinder(),
  631. cylinderBetween(ctx.model, top_end, nock, extras.stringR),
  632. extras.stringCol, nullptr, 1.0F);
  633. out.mesh(getUnitCylinder(),
  634. cylinderBetween(ctx.model, nock, bot_end, extras.stringR),
  635. extras.stringCol, nullptr, 1.0F);
  636. out.mesh(getUnitCylinder(),
  637. cylinderBetween(ctx.model, pose.hand_r, nock, 0.0045F),
  638. extras.stringCol * 0.9F, nullptr, 1.0F);
  639. bool const show_arrow =
  640. !is_attacking ||
  641. (is_attacking && attack_phase >= 0.0F && attack_phase < 0.52F);
  642. if (show_arrow) {
  643. QVector3D const tail = nock - forward * 0.06F;
  644. QVector3D const tip = tail + forward * 0.90F;
  645. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, tail, tip, 0.018F),
  646. v.palette.wood, nullptr, 1.0F);
  647. QVector3D const head_base = tip - forward * 0.10F;
  648. out.mesh(getUnitCone(), coneFromTo(ctx.model, head_base, tip, 0.05F),
  649. extras.metalHead, nullptr, 1.0F);
  650. QVector3D const f1b = tail - forward * 0.02F;
  651. QVector3D const f1a = f1b - forward * 0.06F;
  652. QVector3D const f2b = tail + forward * 0.02F;
  653. QVector3D const f2a = f2b + forward * 0.06F;
  654. out.mesh(getUnitCone(), coneFromTo(ctx.model, f1b, f1a, 0.04F),
  655. extras.fletch, nullptr, 1.0F);
  656. out.mesh(getUnitCone(), coneFromTo(ctx.model, f2a, f2b, 0.04F),
  657. extras.fletch, nullptr, 1.0F);
  658. }
  659. }
  660. };
  661. void registerArcherRenderer(Render::GL::EntityRendererRegistry &registry) {
  662. ensure_archer_styles_registered();
  663. static ArcherRenderer const renderer;
  664. registry.registerRenderer(
  665. "troops/kingdom/archer", [](const DrawContext &ctx, ISubmitter &out) {
  666. static ArcherRenderer const static_renderer;
  667. Shader *archer_shader = nullptr;
  668. if (ctx.backend != nullptr) {
  669. QString shader_key = static_renderer.resolve_shader_key(ctx);
  670. archer_shader = ctx.backend->shader(shader_key);
  671. if (archer_shader == nullptr) {
  672. archer_shader = ctx.backend->shader(QStringLiteral("archer"));
  673. }
  674. }
  675. auto *scene_renderer = dynamic_cast<Renderer *>(&out);
  676. if ((scene_renderer != nullptr) && (archer_shader != nullptr)) {
  677. scene_renderer->setCurrentShader(archer_shader);
  678. }
  679. static_renderer.render(ctx, out);
  680. if (scene_renderer != nullptr) {
  681. scene_renderer->setCurrentShader(nullptr);
  682. }
  683. });
  684. }
  685. } // namespace Render::GL::Kingdom