spearman_renderer.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. #include "spearman_renderer.h"
  2. #include "../../../../game/core/component.h"
  3. #include "../../../geom/math_utils.h"
  4. #include "../../../geom/transforms.h"
  5. #include "../../../gl/backend.h"
  6. #include "../../../gl/primitives.h"
  7. #include "../../../gl/shader.h"
  8. #include "../../../humanoid/rig.h"
  9. #include "../../../humanoid/style_palette.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 "../../registry.h"
  16. #include "../../renderer_constants.h"
  17. #include "spearman_style.h"
  18. #include <QMatrix4x4>
  19. #include <QString>
  20. #include <QVector3D>
  21. #include <algorithm>
  22. #include <cmath>
  23. #include <cstdint>
  24. #include <optional>
  25. #include <qstringliteral.h>
  26. #include <qvectornd.h>
  27. #include <string>
  28. #include <string_view>
  29. #include <unordered_map>
  30. namespace Render::GL::Carthage {
  31. namespace {
  32. constexpr std::string_view k_spearman_default_style_key = "default";
  33. constexpr float k_spearman_team_mix_weight = 0.6F;
  34. constexpr float k_spearman_style_mix_weight = 0.4F;
  35. auto spearman_style_registry()
  36. -> std::unordered_map<std::string, SpearmanStyleConfig> & {
  37. static std::unordered_map<std::string, SpearmanStyleConfig> styles;
  38. return styles;
  39. }
  40. void ensure_spearman_styles_registered() {
  41. static const bool registered = []() {
  42. register_carthage_spearman_style();
  43. return true;
  44. }();
  45. (void)registered;
  46. }
  47. } // namespace
  48. void register_spearman_style(const std::string &nation_id,
  49. const SpearmanStyleConfig &style) {
  50. spearman_style_registry()[nation_id] = style;
  51. }
  52. using Render::Geom::clamp01;
  53. using Render::Geom::coneFromTo;
  54. using Render::Geom::cylinderBetween;
  55. using Render::Geom::easeInOutCubic;
  56. using Render::Geom::lerp;
  57. using Render::Geom::smoothstep;
  58. using Render::Geom::sphereAt;
  59. using Render::GL::Humanoid::mix_palette_color;
  60. using Render::GL::Humanoid::saturate_color;
  61. struct SpearmanExtras {
  62. QVector3D spearShaftColor;
  63. QVector3D spearheadColor;
  64. float spearLength = 1.20F;
  65. float spearShaftRadius = 0.020F;
  66. float spearheadLength = 0.18F;
  67. };
  68. class SpearmanRenderer : public HumanoidRendererBase {
  69. public:
  70. auto getProportionScaling() const -> QVector3D override {
  71. return {1.10F, 1.02F, 1.05F};
  72. }
  73. private:
  74. mutable std::unordered_map<uint32_t, SpearmanExtras> m_extrasCache;
  75. public:
  76. void getVariant(const DrawContext &ctx, uint32_t seed,
  77. HumanoidVariant &v) const override {
  78. QVector3D const team_tint = resolveTeamTint(ctx);
  79. v.palette = makeHumanoidPalette(team_tint, seed);
  80. auto const &style = resolve_style(ctx);
  81. apply_palette_overrides(style, team_tint, v);
  82. }
  83. void customizePose(const DrawContext &,
  84. const HumanoidAnimationContext &anim_ctx, uint32_t seed,
  85. HumanoidPose &pose) const override {
  86. using HP = HumanProportions;
  87. const AnimationInputs &anim = anim_ctx.inputs;
  88. float const arm_height_jitter = (hash_01(seed ^ 0xABCDU) - 0.5F) * 0.03F;
  89. float const arm_asymmetry = (hash_01(seed ^ 0xDEF0U) - 0.5F) * 0.04F;
  90. if (anim.isInHoldMode || anim.isExitingHold) {
  91. float const t = anim.isInHoldMode ? 1.0F : (1.0F - anim.holdExitProgress);
  92. float const kneel_depth = 0.35F * t;
  93. float const pelvis_y = HP::WAIST_Y - kneel_depth;
  94. pose.pelvisPos.setY(pelvis_y);
  95. float const stance_narrow = 0.10F;
  96. float const left_knee_y = HP::GROUND_Y + 0.06F * t;
  97. float const left_knee_z = -0.08F * t;
  98. pose.knee_l = QVector3D(-stance_narrow, left_knee_y, left_knee_z);
  99. pose.footL = QVector3D(-stance_narrow - 0.02F, HP::GROUND_Y,
  100. left_knee_z - HP::LOWER_LEG_LEN * 0.90F * t);
  101. float const right_knee_y =
  102. HP::WAIST_Y * 0.45F * (1.0F - t) + HP::WAIST_Y * 0.30F * t;
  103. pose.knee_r = QVector3D(stance_narrow + 0.05F, right_knee_y, 0.15F * t);
  104. pose.foot_r = QVector3D(stance_narrow + 0.08F, HP::GROUND_Y, 0.25F * t);
  105. float const upper_body_drop = kneel_depth;
  106. pose.shoulderL.setY(HP::SHOULDER_Y - upper_body_drop);
  107. pose.shoulderR.setY(HP::SHOULDER_Y - upper_body_drop);
  108. pose.neck_base.setY(HP::NECK_BASE_Y - upper_body_drop);
  109. float const lowered_chin_y = HP::CHIN_Y - upper_body_drop;
  110. pose.headPos.setY(lowered_chin_y + pose.headR);
  111. float const forward_lean = 0.08F * t;
  112. pose.shoulderL.setZ(pose.shoulderL.z() + forward_lean);
  113. pose.shoulderR.setZ(pose.shoulderR.z() + forward_lean);
  114. pose.neck_base.setZ(pose.neck_base.z() + forward_lean * 0.8F);
  115. pose.headPos.setZ(pose.headPos.z() + forward_lean * 0.7F);
  116. float const lowered_shoulder_y = HP::SHOULDER_Y - upper_body_drop;
  117. pose.hand_r =
  118. QVector3D(0.18F * (1.0F - t) + 0.22F * t,
  119. lowered_shoulder_y * (1.0F - t) + (pelvis_y + 0.05F) * t,
  120. 0.15F * (1.0F - t) + 0.20F * t);
  121. pose.handL = QVector3D(0.0F,
  122. lowered_shoulder_y * (1.0F - t) +
  123. (lowered_shoulder_y - 0.10F) * t,
  124. 0.30F * (1.0F - t) + 0.55F * t);
  125. QVector3D const shoulder_to_hand_r = pose.hand_r - pose.shoulderR;
  126. float const arm_length_r = shoulder_to_hand_r.length();
  127. QVector3D const arm_dir_r = shoulder_to_hand_r.normalized();
  128. pose.elbowR = pose.shoulderR + arm_dir_r * (arm_length_r * 0.5F) +
  129. QVector3D(0.08F, -0.15F, -0.05F);
  130. QVector3D const shoulder_to_hand_l = pose.handL - pose.shoulderL;
  131. float const arm_length_l = shoulder_to_hand_l.length();
  132. QVector3D const arm_dir_l = shoulder_to_hand_l.normalized();
  133. pose.elbowL = pose.shoulderL + arm_dir_l * (arm_length_l * 0.5F) +
  134. QVector3D(-0.08F, -0.12F, 0.05F);
  135. } else if (anim.is_attacking && anim.isMelee && !anim.isInHoldMode) {
  136. float const attack_phase =
  137. std::fmod(anim.time * SPEARMAN_INV_ATTACK_CYCLE_TIME, 1.0F);
  138. QVector3D const guard_pos(0.28F, HP::SHOULDER_Y + 0.05F, 0.25F);
  139. QVector3D const prepare_pos(0.35F, HP::SHOULDER_Y + 0.08F, 0.05F);
  140. QVector3D const thrust_pos(0.32F, HP::SHOULDER_Y + 0.10F, 0.90F);
  141. QVector3D const recover_pos(0.28F, HP::SHOULDER_Y + 0.06F, 0.40F);
  142. if (attack_phase < 0.20F) {
  143. float const t = easeInOutCubic(attack_phase / 0.20F);
  144. pose.hand_r = guard_pos * (1.0F - t) + prepare_pos * t;
  145. pose.handL = QVector3D(-0.10F, HP::SHOULDER_Y - 0.05F,
  146. 0.20F * (1.0F - t) + 0.08F * t);
  147. } else if (attack_phase < 0.30F) {
  148. pose.hand_r = prepare_pos;
  149. pose.handL = QVector3D(-0.10F, HP::SHOULDER_Y - 0.05F, 0.08F);
  150. } else if (attack_phase < 0.50F) {
  151. float t = (attack_phase - 0.30F) / 0.20F;
  152. t = t * t * t;
  153. pose.hand_r = prepare_pos * (1.0F - t) + thrust_pos * t;
  154. pose.handL =
  155. QVector3D(-0.10F + 0.05F * t, HP::SHOULDER_Y - 0.05F + 0.03F * t,
  156. 0.08F + 0.45F * t);
  157. } else if (attack_phase < 0.70F) {
  158. float const t = easeInOutCubic((attack_phase - 0.50F) / 0.20F);
  159. pose.hand_r = thrust_pos * (1.0F - t) + recover_pos * t;
  160. pose.handL = QVector3D(-0.05F * (1.0F - t) - 0.10F * t,
  161. HP::SHOULDER_Y - 0.02F * (1.0F - t) - 0.06F * t,
  162. lerp(0.53F, 0.35F, t));
  163. } else {
  164. float const t = smoothstep(0.70F, 1.0F, attack_phase);
  165. pose.hand_r = recover_pos * (1.0F - t) + guard_pos * t;
  166. pose.handL = QVector3D(-0.10F - 0.02F * (1.0F - t),
  167. HP::SHOULDER_Y - 0.06F + 0.01F * t +
  168. arm_height_jitter * (1.0F - t),
  169. lerp(0.35F, 0.25F, t));
  170. }
  171. } else {
  172. pose.hand_r =
  173. QVector3D(0.28F + arm_asymmetry,
  174. HP::SHOULDER_Y - 0.02F + arm_height_jitter, 0.30F);
  175. pose.handL =
  176. QVector3D(-0.08F - 0.5F * arm_asymmetry,
  177. HP::SHOULDER_Y - 0.08F + 0.5F * arm_height_jitter, 0.45F);
  178. QVector3D const shoulder_to_hand = pose.hand_r - pose.shoulderR;
  179. float const arm_length = shoulder_to_hand.length();
  180. QVector3D const arm_dir = shoulder_to_hand.normalized();
  181. pose.elbowR = pose.shoulderR + arm_dir * (arm_length * 0.5F) +
  182. QVector3D(0.06F, -0.12F, -0.04F);
  183. }
  184. }
  185. void addAttachments(const DrawContext &ctx, const HumanoidVariant &v,
  186. const HumanoidPose &pose,
  187. const HumanoidAnimationContext &anim_ctx,
  188. ISubmitter &out) const override {
  189. const AnimationInputs &anim = anim_ctx.inputs;
  190. uint32_t const seed = reinterpret_cast<uintptr_t>(ctx.entity) & 0xFFFFFFFFU;
  191. auto const &style = resolve_style(ctx);
  192. QVector3D const team_tint = resolveTeamTint(ctx);
  193. SpearmanExtras extras;
  194. auto it = m_extrasCache.find(seed);
  195. if (it != m_extrasCache.end()) {
  196. extras = it->second;
  197. } else {
  198. extras = computeSpearmanExtras(seed, v);
  199. apply_extras_overrides(style, team_tint, v, extras);
  200. m_extrasCache[seed] = extras;
  201. if (m_extrasCache.size() > MAX_EXTRAS_CACHE_SIZE) {
  202. m_extrasCache.clear();
  203. }
  204. }
  205. apply_extras_overrides(style, team_tint, v, extras);
  206. bool const is_attacking = anim.is_attacking && anim.isMelee;
  207. float attack_phase = 0.0F;
  208. if (is_attacking) {
  209. attack_phase =
  210. std::fmod(anim.time * SPEARMAN_INV_ATTACK_CYCLE_TIME, 1.0F);
  211. }
  212. drawSpear(ctx, pose, v, extras, anim, is_attacking, attack_phase, out);
  213. }
  214. void drawHelmet(const DrawContext &ctx, const HumanoidVariant &v,
  215. const HumanoidPose &pose, ISubmitter &out) const override {
  216. using HP = HumanProportions;
  217. const QVector3D iron_color = v.palette.metal * IRON_TINT;
  218. const float helm_r = pose.headR * 1.12F;
  219. QVector3D const helm_bot(pose.headPos.x(),
  220. pose.headPos.y() - pose.headR * 0.15F,
  221. pose.headPos.z());
  222. QVector3D const helm_top(pose.headPos.x(),
  223. pose.headPos.y() + pose.headR * 1.25F,
  224. pose.headPos.z());
  225. out.mesh(getUnitCylinder(),
  226. cylinderBetween(ctx.model, helm_bot, helm_top, helm_r), iron_color,
  227. nullptr, 1.0F);
  228. QVector3D const cap_top(pose.headPos.x(),
  229. pose.headPos.y() + pose.headR * 1.32F,
  230. pose.headPos.z());
  231. out.mesh(getUnitCylinder(),
  232. cylinderBetween(ctx.model, helm_top, cap_top, helm_r * 0.96F),
  233. iron_color * 1.04F, nullptr, 1.0F);
  234. auto ring = [&](const QVector3D &center, float r, float h,
  235. const QVector3D &col) {
  236. QVector3D const a = center + QVector3D(0, h * 0.5F, 0);
  237. QVector3D const b = center - QVector3D(0, h * 0.5F, 0);
  238. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, a, b, r), col,
  239. nullptr, 1.0F);
  240. };
  241. ring(QVector3D(pose.headPos.x(), pose.headPos.y() + pose.headR * 0.95F,
  242. pose.headPos.z()),
  243. helm_r * 1.01F, 0.012F, iron_color * 1.06F);
  244. ring(QVector3D(pose.headPos.x(), pose.headPos.y() - pose.headR * 0.02F,
  245. pose.headPos.z()),
  246. helm_r * 1.01F, 0.012F, iron_color * 1.06F);
  247. float const visor_y = pose.headPos.y() + pose.headR * 0.10F;
  248. float const visor_z = pose.headPos.z() + helm_r * 0.68F;
  249. for (int i = 0; i < 3; ++i) {
  250. float const y = visor_y + pose.headR * (0.18F - i * 0.12F);
  251. QVector3D const visor_l(pose.headPos.x() - helm_r * 0.30F, y, visor_z);
  252. QVector3D const visor_r(pose.headPos.x() + helm_r * 0.30F, y, visor_z);
  253. out.mesh(getUnitCylinder(),
  254. cylinderBetween(ctx.model, visor_l, visor_r, 0.010F), DARK_METAL,
  255. nullptr, 1.0F);
  256. }
  257. }
  258. void draw_armorOverlay(const DrawContext &ctx, const HumanoidVariant &v,
  259. const HumanoidPose &pose, float y_top_cover,
  260. float torso_r, float, float upper_arm_r,
  261. const QVector3D &right_axis,
  262. ISubmitter &out) const override {
  263. using HP = HumanProportions;
  264. const QVector3D iron_color = v.palette.metal * IRON_TINT;
  265. const QVector3D leather_color = v.palette.leather * 0.95F;
  266. QVector3D const chest_top(0, y_top_cover + 0.02F, 0);
  267. QVector3D const chest_bot(0, HP::WAIST_Y + 0.08F, 0);
  268. float const r_chest = torso_r * 1.14F;
  269. out.mesh(getUnitCylinder(),
  270. cylinderBetween(ctx.model, chest_top, chest_bot, r_chest),
  271. iron_color, nullptr, 1.0F);
  272. auto draw_pauldron = [&](const QVector3D &shoulder,
  273. const QVector3D &outward) {
  274. for (int i = 0; i < 3; ++i) {
  275. float const seg_y = shoulder.y() + 0.03F - i * 0.040F;
  276. float const seg_r = upper_arm_r * (2.2F - i * 0.10F);
  277. QVector3D seg_pos = shoulder + outward * (0.015F + i * 0.006F);
  278. seg_pos.setY(seg_y);
  279. out.mesh(getUnitSphere(), sphereAt(ctx.model, seg_pos, seg_r),
  280. i == 0 ? iron_color * 1.04F : iron_color * (1.0F - i * 0.02F),
  281. nullptr, 1.0F);
  282. }
  283. };
  284. draw_pauldron(pose.shoulderL, -right_axis);
  285. draw_pauldron(pose.shoulderR, right_axis);
  286. auto draw_arm_plate = [&](const QVector3D &shoulder,
  287. const QVector3D &elbow) {
  288. QVector3D dir = (elbow - shoulder);
  289. float const len = dir.length();
  290. if (len < 1e-5F) {
  291. return;
  292. }
  293. dir /= len;
  294. for (int i = 0; i < 2; ++i) {
  295. float const t0 = 0.12F + i * 0.28F;
  296. float const t1 = t0 + 0.24F;
  297. QVector3D const a = shoulder + dir * (t0 * len);
  298. QVector3D const b = shoulder + dir * (t1 * len);
  299. float const r = upper_arm_r * (1.26F - i * 0.03F);
  300. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, a, b, r),
  301. iron_color * (0.96F - i * 0.02F), nullptr, 1.0F);
  302. }
  303. };
  304. draw_arm_plate(pose.shoulderL, pose.elbowL);
  305. draw_arm_plate(pose.shoulderR, pose.elbowR);
  306. for (int i = 0; i < 3; ++i) {
  307. float const y = HP::WAIST_Y + 0.06F - i * 0.035F;
  308. float const r = torso_r * (1.12F + i * 0.020F);
  309. QVector3D const strip_top(0, y, 0);
  310. QVector3D const strip_bot(0, y - 0.030F, 0);
  311. out.mesh(getUnitCone(), coneFromTo(ctx.model, strip_top, strip_bot, r),
  312. leather_color * (0.98F - i * 0.02F), nullptr, 1.0F);
  313. }
  314. }
  315. void drawShoulderDecorations(const DrawContext &ctx, const HumanoidVariant &v,
  316. const HumanoidPose &pose, float y_top_cover,
  317. float y_neck, const QVector3D &right_axis,
  318. ISubmitter &out) const override {}
  319. private:
  320. static auto computeSpearmanExtras(uint32_t seed, const HumanoidVariant &v)
  321. -> SpearmanExtras {
  322. SpearmanExtras e;
  323. e.spearShaftColor = v.palette.leather * QVector3D(0.85F, 0.75F, 0.65F);
  324. e.spearheadColor = QVector3D(0.75F, 0.76F, 0.80F);
  325. e.spearLength = 1.15F + (hash_01(seed ^ 0xABCDU) - 0.5F) * 0.10F;
  326. e.spearShaftRadius = 0.018F + (hash_01(seed ^ 0x7777U) - 0.5F) * 0.003F;
  327. e.spearheadLength = 0.16F + (hash_01(seed ^ 0xBEEFU) - 0.5F) * 0.04F;
  328. return e;
  329. }
  330. static void drawSpear(const DrawContext &ctx, const HumanoidPose &pose,
  331. const HumanoidVariant &v, const SpearmanExtras &extras,
  332. const AnimationInputs &anim, bool is_attacking,
  333. float attack_phase, ISubmitter &out) {
  334. QVector3D const grip_pos = pose.hand_r;
  335. QVector3D spear_dir = QVector3D(0.05F, 0.55F, 0.85F);
  336. if (spear_dir.lengthSquared() > 1e-6F) {
  337. spear_dir.normalize();
  338. }
  339. if (anim.isInHoldMode || anim.isExitingHold) {
  340. float const t = anim.isInHoldMode ? 1.0F : (1.0F - anim.holdExitProgress);
  341. QVector3D braced_dir = QVector3D(0.05F, 0.40F, 0.91F);
  342. if (braced_dir.lengthSquared() > 1e-6F) {
  343. braced_dir.normalize();
  344. }
  345. spear_dir = spear_dir * (1.0F - t) + braced_dir * t;
  346. if (spear_dir.lengthSquared() > 1e-6F) {
  347. spear_dir.normalize();
  348. }
  349. } else if (is_attacking) {
  350. if (attack_phase >= 0.30F && attack_phase < 0.50F) {
  351. float const t = (attack_phase - 0.30F) / 0.20F;
  352. QVector3D attack_dir = QVector3D(0.03F, -0.15F, 1.0F);
  353. if (attack_dir.lengthSquared() > 1e-6F) {
  354. attack_dir.normalize();
  355. }
  356. spear_dir = spear_dir * (1.0F - t) + attack_dir * t;
  357. if (spear_dir.lengthSquared() > 1e-6F) {
  358. spear_dir.normalize();
  359. }
  360. }
  361. }
  362. QVector3D const shaft_base = grip_pos - spear_dir * 0.28F;
  363. QVector3D shaft_mid = grip_pos + spear_dir * (extras.spearLength * 0.5F);
  364. QVector3D const shaft_tip = grip_pos + spear_dir * extras.spearLength;
  365. shaft_mid.setY(shaft_mid.y() + 0.02F);
  366. out.mesh(getUnitCylinder(),
  367. cylinderBetween(ctx.model, shaft_base, shaft_mid,
  368. extras.spearShaftRadius),
  369. extras.spearShaftColor, nullptr, 1.0F);
  370. out.mesh(getUnitCylinder(),
  371. cylinderBetween(ctx.model, shaft_mid, shaft_tip,
  372. extras.spearShaftRadius * 0.95F),
  373. extras.spearShaftColor * 0.98F, nullptr, 1.0F);
  374. QVector3D const spearhead_base = shaft_tip;
  375. QVector3D const spearhead_tip =
  376. shaft_tip + spear_dir * extras.spearheadLength;
  377. out.mesh(getUnitCone(),
  378. coneFromTo(ctx.model, spearhead_base, spearhead_tip,
  379. extras.spearShaftRadius * 1.8F),
  380. extras.spearheadColor, nullptr, 1.0F);
  381. QVector3D const grip_end = grip_pos + spear_dir * 0.10F;
  382. out.mesh(getUnitCylinder(),
  383. cylinderBetween(ctx.model, grip_pos, grip_end,
  384. extras.spearShaftRadius * 1.5F),
  385. v.palette.leather * 0.92F, nullptr, 1.0F);
  386. }
  387. auto
  388. resolve_style(const DrawContext &ctx) const -> const SpearmanStyleConfig & {
  389. ensure_spearman_styles_registered();
  390. auto &styles = spearman_style_registry();
  391. std::string nation_id;
  392. if (ctx.entity != nullptr) {
  393. if (auto *unit =
  394. ctx.entity->getComponent<Engine::Core::UnitComponent>()) {
  395. nation_id = unit->nation_id;
  396. }
  397. }
  398. if (!nation_id.empty()) {
  399. auto it = styles.find(nation_id);
  400. if (it != styles.end()) {
  401. return it->second;
  402. }
  403. }
  404. auto it_default = styles.find(std::string(k_spearman_default_style_key));
  405. if (it_default != styles.end()) {
  406. return it_default->second;
  407. }
  408. static const SpearmanStyleConfig k_empty{};
  409. return k_empty;
  410. }
  411. public:
  412. auto resolve_shader_key(const DrawContext &ctx) const -> QString {
  413. const SpearmanStyleConfig &style = resolve_style(ctx);
  414. if (!style.shader_id.empty()) {
  415. return QString::fromStdString(style.shader_id);
  416. }
  417. return QStringLiteral("spearman");
  418. }
  419. private:
  420. void apply_palette_overrides(const SpearmanStyleConfig &style,
  421. const QVector3D &team_tint,
  422. HumanoidVariant &variant) const {
  423. auto apply_color = [&](const std::optional<QVector3D> &override_color,
  424. QVector3D &target) {
  425. target = mix_palette_color(target, override_color, team_tint,
  426. k_spearman_team_mix_weight,
  427. k_spearman_style_mix_weight);
  428. };
  429. apply_color(style.cloth_color, variant.palette.cloth);
  430. apply_color(style.leather_color, variant.palette.leather);
  431. apply_color(style.leather_dark_color, variant.palette.leatherDark);
  432. apply_color(style.metal_color, variant.palette.metal);
  433. }
  434. void apply_extras_overrides(const SpearmanStyleConfig &style,
  435. const QVector3D &team_tint,
  436. [[maybe_unused]] const HumanoidVariant &variant,
  437. SpearmanExtras &extras) const {
  438. extras.spearShaftColor = saturate_color(extras.spearShaftColor);
  439. extras.spearheadColor = saturate_color(extras.spearheadColor);
  440. auto apply_color = [&](const std::optional<QVector3D> &override_color,
  441. QVector3D &target) {
  442. target = mix_palette_color(target, override_color, team_tint,
  443. k_spearman_team_mix_weight,
  444. k_spearman_style_mix_weight);
  445. };
  446. apply_color(style.spear_shaft_color, extras.spearShaftColor);
  447. apply_color(style.spearhead_color, extras.spearheadColor);
  448. if (style.spear_length_scale) {
  449. extras.spearLength =
  450. std::max(0.80F, extras.spearLength * *style.spear_length_scale);
  451. }
  452. }
  453. };
  454. void registerSpearmanRenderer(Render::GL::EntityRendererRegistry &registry) {
  455. ensure_spearman_styles_registered();
  456. static SpearmanRenderer const renderer;
  457. registry.registerRenderer(
  458. "troops/carthage/spearman", [](const DrawContext &ctx, ISubmitter &out) {
  459. static SpearmanRenderer const static_renderer;
  460. Shader *spearman_shader = nullptr;
  461. if (ctx.backend != nullptr) {
  462. QString shader_key = static_renderer.resolve_shader_key(ctx);
  463. spearman_shader = ctx.backend->shader(shader_key);
  464. if (spearman_shader == nullptr) {
  465. spearman_shader = ctx.backend->shader(QStringLiteral("spearman"));
  466. }
  467. }
  468. auto *scene_renderer = dynamic_cast<Renderer *>(&out);
  469. if ((scene_renderer != nullptr) && (spearman_shader != nullptr)) {
  470. scene_renderer->setCurrentShader(spearman_shader);
  471. }
  472. static_renderer.render(ctx, out);
  473. if (scene_renderer != nullptr) {
  474. scene_renderer->setCurrentShader(nullptr);
  475. }
  476. });
  477. }
  478. } // namespace Render::GL::Carthage