builder_renderer.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. #include "builder_renderer.h"
  2. #include "../../../../game/core/component.h"
  3. #include "../../../../game/core/entity.h"
  4. #include "../../../../game/systems/nation_id.h"
  5. #include "../../../equipment/equipment_registry.h"
  6. #include "../../../geom/math_utils.h"
  7. #include "../../../geom/transforms.h"
  8. #include "../../../gl/backend.h"
  9. #include "../../../gl/primitives.h"
  10. #include "../../../gl/render_constants.h"
  11. #include "../../../gl/shader.h"
  12. #include "../../../humanoid/humanoid_math.h"
  13. #include "../../../humanoid/humanoid_specs.h"
  14. #include "../../../humanoid/pose_controller.h"
  15. #include "../../../humanoid/rig.h"
  16. #include "../../../humanoid/style_palette.h"
  17. #include "../../../palette.h"
  18. #include "../../../scene_renderer.h"
  19. #include "../../../submitter.h"
  20. #include "../../registry.h"
  21. #include "../../renderer_constants.h"
  22. #include "builder_style.h"
  23. #include <QMatrix4x4>
  24. #include <QString>
  25. #include <QVector3D>
  26. #include <cmath>
  27. #include <cstdint>
  28. #include <numbers>
  29. #include <optional>
  30. #include <qmatrix4x4.h>
  31. #include <qstringliteral.h>
  32. #include <qvectornd.h>
  33. #include <string>
  34. #include <string_view>
  35. #include <unordered_map>
  36. using Render::Geom::cylinder_between;
  37. using Render::Geom::sphere_at;
  38. namespace Render::GL::Carthage {
  39. namespace {
  40. constexpr std::string_view k_default_style_key = "default";
  41. auto style_registry() -> std::unordered_map<std::string, BuilderStyleConfig> & {
  42. static std::unordered_map<std::string, BuilderStyleConfig> styles;
  43. return styles;
  44. }
  45. void ensure_builder_styles_registered() {
  46. static const bool registered = []() {
  47. register_carthage_builder_style();
  48. return true;
  49. }();
  50. (void)registered;
  51. }
  52. constexpr float k_team_mix_weight = 0.65F;
  53. constexpr float k_style_mix_weight = 0.35F;
  54. } // namespace
  55. void register_builder_style(const std::string &nation_id,
  56. const BuilderStyleConfig &style) {
  57. style_registry()[nation_id] = style;
  58. }
  59. using Render::Geom::clamp01;
  60. using Render::Geom::clamp_f;
  61. using Render::GL::Humanoid::mix_palette_color;
  62. using Render::GL::Humanoid::saturate_color;
  63. class BuilderRenderer : public HumanoidRendererBase {
  64. public:
  65. BuilderRenderer() { cache_equipment(); }
  66. friend void
  67. register_builder_renderer(Render::GL::EntityRendererRegistry &registry);
  68. auto get_proportion_scaling() const -> QVector3D override {
  69. return {0.98F, 1.01F, 0.96F};
  70. }
  71. void get_variant(const DrawContext &ctx, uint32_t seed,
  72. HumanoidVariant &v) const override {
  73. QVector3D const team_tint = resolve_team_tint(ctx);
  74. v.palette = make_humanoid_palette(team_tint, seed);
  75. auto const &style = resolve_style(ctx);
  76. apply_palette_overrides(style, team_tint, v);
  77. auto nextRand = [](uint32_t &s) -> float {
  78. s = s * 1664525U + 1013904223U;
  79. return float(s & 0x7FFFFFU) / float(0x7FFFFFU);
  80. };
  81. uint32_t beard_seed = seed ^ 0x0EA101U;
  82. if (style.force_beard || nextRand(beard_seed) < 0.75F) {
  83. float const style_roll = nextRand(beard_seed);
  84. if (style_roll < 0.5F) {
  85. v.facial_hair.style = FacialHairStyle::ShortBeard;
  86. v.facial_hair.length = 0.7F + nextRand(beard_seed) * 0.3F;
  87. } else if (style_roll < 0.8F) {
  88. v.facial_hair.style = FacialHairStyle::FullBeard;
  89. v.facial_hair.length = 0.8F + nextRand(beard_seed) * 0.4F;
  90. } else {
  91. v.facial_hair.style = FacialHairStyle::Goatee;
  92. v.facial_hair.length = 0.6F + nextRand(beard_seed) * 0.3F;
  93. }
  94. v.facial_hair.color = QVector3D(0.15F + nextRand(beard_seed) * 0.1F,
  95. 0.12F + nextRand(beard_seed) * 0.08F,
  96. 0.10F + nextRand(beard_seed) * 0.06F);
  97. v.facial_hair.thickness = 0.8F + nextRand(beard_seed) * 0.2F;
  98. }
  99. }
  100. void customize_pose(const DrawContext &,
  101. const HumanoidAnimationContext &anim_ctx, uint32_t seed,
  102. HumanoidPose &pose) const override {
  103. using HP = HumanProportions;
  104. const AnimationInputs &anim = anim_ctx.inputs;
  105. HumanoidPoseController controller(pose, anim_ctx);
  106. float const jitter = (hash_01(seed ^ 0xABCDU) - 0.5F) * 0.04F;
  107. float const asym = (hash_01(seed ^ 0xDEF0U) - 0.5F) * 0.05F;
  108. if (anim.is_constructing) {
  109. uint32_t const pose_selector = seed % 100;
  110. float const phase_offset = float(seed % 100) * 0.0628F;
  111. float const cycle_speed = 2.0F + float(seed % 50) * 0.02F;
  112. float const swing_cycle =
  113. std::fmod(anim.time * cycle_speed + phase_offset, 1.0F);
  114. if (pose_selector < 40) {
  115. apply_hammering_pose(controller, swing_cycle, asym, seed);
  116. } else if (pose_selector < 70) {
  117. apply_kneeling_work_pose(controller, swing_cycle, asym, seed);
  118. } else if (pose_selector < 90) {
  119. apply_sawing_pose(controller, swing_cycle, asym, seed);
  120. } else {
  121. apply_lifting_pose(controller, swing_cycle, asym, seed);
  122. }
  123. return;
  124. }
  125. float const forward = 0.20F + (anim.is_moving ? 0.02F : 0.0F);
  126. QVector3D const hammer_hand(-0.12F + asym, HP::WAIST_Y + 0.10F + jitter,
  127. forward + 0.04F);
  128. QVector3D const rest_hand(0.22F - asym * 0.5F,
  129. HP::WAIST_Y - 0.04F + jitter * 0.5F, 0.10F);
  130. controller.place_hand_at(true, hammer_hand);
  131. controller.place_hand_at(false, rest_hand);
  132. }
  133. void add_attachments(const DrawContext &ctx, const HumanoidVariant &v,
  134. const HumanoidPose &pose,
  135. const HumanoidAnimationContext &anim_ctx,
  136. ISubmitter &out) const override {
  137. if (m_cached_work_apron) {
  138. m_cached_work_apron->render(ctx, pose.body_frames, v.palette, anim_ctx,
  139. out);
  140. }
  141. if (m_cached_tool_belt) {
  142. m_cached_tool_belt->render(ctx, pose.body_frames, v.palette, anim_ctx,
  143. out);
  144. }
  145. if (m_cached_arm_guards) {
  146. m_cached_arm_guards->render(ctx, pose.body_frames, v.palette, anim_ctx,
  147. out);
  148. }
  149. draw_stone_hammer(ctx, v, pose, anim_ctx, out);
  150. }
  151. void draw_helmet(const DrawContext &ctx, const HumanoidVariant &v,
  152. const HumanoidPose &pose, ISubmitter &out) const override {
  153. draw_headwrap(ctx, v, pose, out);
  154. }
  155. void draw_armor(const DrawContext &ctx, const HumanoidVariant &v,
  156. const HumanoidPose &pose,
  157. const HumanoidAnimationContext &anim,
  158. ISubmitter &out) const override {
  159. uint32_t const seed = reinterpret_cast<uintptr_t>(ctx.entity) & 0xFFFFFFFFU;
  160. draw_craftsman_robes(ctx, v, pose, seed, out);
  161. }
  162. private:
  163. void cache_equipment() {
  164. auto &registry = EquipmentRegistry::instance();
  165. m_cached_work_apron =
  166. registry.get(EquipmentCategory::Armor, "work_apron_carthage");
  167. m_cached_tool_belt =
  168. registry.get(EquipmentCategory::Armor, "tool_belt_carthage");
  169. m_cached_arm_guards = registry.get(EquipmentCategory::Armor, "arm_guards");
  170. }
  171. mutable std::shared_ptr<IEquipmentRenderer> m_cached_work_apron;
  172. mutable std::shared_ptr<IEquipmentRenderer> m_cached_tool_belt;
  173. mutable std::shared_ptr<IEquipmentRenderer> m_cached_arm_guards;
  174. static constexpr uint32_t KNEEL_SEED_OFFSET = 0x5678U;
  175. void apply_hammering_pose(HumanoidPoseController &controller,
  176. float swing_cycle, float asym,
  177. uint32_t seed) const {
  178. using HP = HumanProportions;
  179. float swing_angle;
  180. float body_lean;
  181. float crouch_amount;
  182. if (swing_cycle < 0.3F) {
  183. float const t = swing_cycle / 0.3F;
  184. swing_angle = t * 0.92F;
  185. body_lean = -t * 0.09F;
  186. crouch_amount = 0.0F;
  187. } else if (swing_cycle < 0.5F) {
  188. float const t = (swing_cycle - 0.3F) / 0.2F;
  189. swing_angle = 0.92F - t * 1.45F;
  190. body_lean = -0.09F + t * 0.26F;
  191. crouch_amount = t * 0.07F;
  192. } else if (swing_cycle < 0.6F) {
  193. float const t = (swing_cycle - 0.5F) / 0.1F;
  194. swing_angle = -0.53F + t * 0.16F;
  195. body_lean = 0.17F - t * 0.05F;
  196. crouch_amount = 0.07F - t * 0.02F;
  197. } else {
  198. float const t = (swing_cycle - 0.6F) / 0.4F;
  199. swing_angle = -0.37F + t * 0.37F;
  200. body_lean = 0.12F * (1.0F - t);
  201. crouch_amount = 0.05F * (1.0F - t);
  202. }
  203. float const torso_y_offset = -crouch_amount;
  204. float const hammer_y = HP::SHOULDER_Y + 0.10F + swing_angle * 0.20F;
  205. float const hammer_forward =
  206. 0.18F + std::abs(swing_angle) * 0.15F + body_lean * 0.5F;
  207. float const hammer_down =
  208. swing_cycle > 0.4F && swing_cycle < 0.65F ? 0.08F : 0.0F;
  209. QVector3D const hammer_hand(
  210. -0.06F + asym, hammer_y - hammer_down + torso_y_offset, hammer_forward);
  211. float const brace_y =
  212. HP::WAIST_Y + 0.12F + torso_y_offset - crouch_amount * 0.5F;
  213. float const brace_forward = 0.15F + body_lean * 0.3F;
  214. QVector3D const brace_hand(0.14F - asym * 0.5F, brace_y, brace_forward);
  215. controller.place_hand_at(true, hammer_hand);
  216. controller.place_hand_at(false, brace_hand);
  217. }
  218. void apply_kneeling_work_pose(HumanoidPoseController &controller, float cycle,
  219. float asym, uint32_t seed) const {
  220. using HP = HumanProportions;
  221. float const kneel_depth =
  222. 0.50F + (hash_01(seed ^ KNEEL_SEED_OFFSET) * 0.12F);
  223. controller.kneel(kneel_depth);
  224. float const work_cycle = std::sin(cycle * std::numbers::pi_v<float> * 2.0F);
  225. float const tool_y = HP::WAIST_Y * 0.32F + work_cycle * 0.07F;
  226. float const tool_x_offset = 0.06F + work_cycle * 0.05F;
  227. QVector3D const tool_hand(-tool_x_offset + asym, tool_y, 0.24F);
  228. float const brace_x = 0.20F - asym * 0.5F;
  229. QVector3D const brace_hand(brace_x, HP::WAIST_Y * 0.28F, 0.22F);
  230. controller.place_hand_at(true, tool_hand);
  231. controller.place_hand_at(false, brace_hand);
  232. }
  233. void apply_sawing_pose(HumanoidPoseController &controller, float cycle,
  234. float asym, uint32_t seed) const {
  235. using HP = HumanProportions;
  236. controller.lean(QVector3D(0.0F, 0.0F, 1.0F), 0.14F);
  237. float const saw_offset =
  238. std::sin(cycle * std::numbers::pi_v<float> * 4.0F) * 0.14F;
  239. float const saw_y = HP::WAIST_Y + 0.18F;
  240. float const saw_z = 0.22F + saw_offset;
  241. QVector3D const left_hand(-0.10F + asym, saw_y, saw_z);
  242. QVector3D const right_hand(0.10F - asym, saw_y + 0.03F, saw_z);
  243. controller.place_hand_at(true, left_hand);
  244. controller.place_hand_at(false, right_hand);
  245. }
  246. void apply_lifting_pose(HumanoidPoseController &controller, float cycle,
  247. float asym, uint32_t seed) const {
  248. using HP = HumanProportions;
  249. float lift_height;
  250. float crouch;
  251. if (cycle < 0.3F) {
  252. float const t = cycle / 0.3F;
  253. lift_height = HP::WAIST_Y * (1.0F - t * 0.5F);
  254. crouch = t * 0.22F;
  255. } else if (cycle < 0.6F) {
  256. float const t = (cycle - 0.3F) / 0.3F;
  257. lift_height =
  258. HP::WAIST_Y * 0.5F + t * (HP::SHOULDER_Y - HP::WAIST_Y * 0.5F);
  259. crouch = 0.22F * (1.0F - t);
  260. } else if (cycle < 0.8F) {
  261. lift_height = HP::SHOULDER_Y;
  262. crouch = 0.0F;
  263. } else {
  264. float const t = (cycle - 0.8F) / 0.2F;
  265. lift_height = HP::SHOULDER_Y * (1.0F - t * 0.35F);
  266. crouch = 0.0F;
  267. }
  268. QVector3D const left_hand(-0.14F + asym, lift_height, 0.18F);
  269. QVector3D const right_hand(0.14F - asym, lift_height, 0.18F);
  270. controller.place_hand_at(true, left_hand);
  271. controller.place_hand_at(false, right_hand);
  272. if (crouch > 0.0F) {
  273. controller.kneel(crouch);
  274. }
  275. }
  276. void draw_stone_hammer(const DrawContext &ctx, const HumanoidVariant &v,
  277. const HumanoidPose &pose,
  278. const HumanoidAnimationContext &anim_ctx,
  279. ISubmitter &out) const {
  280. QVector3D const wood = v.palette.wood;
  281. QVector3D const stone_color(0.52F, 0.50F, 0.46F);
  282. QVector3D const stone_dark(0.42F, 0.40F, 0.36F);
  283. QVector3D const hand = pose.hand_l;
  284. QVector3D const up(0.0F, 1.0F, 0.0F);
  285. QVector3D const forward(0.0F, 0.0F, 1.0F);
  286. QVector3D const right(1.0F, 0.0F, 0.0F);
  287. const AnimationInputs &anim = anim_ctx.inputs;
  288. QVector3D handle_axis;
  289. QVector3D head_axis;
  290. if (anim.is_constructing) {
  291. handle_axis = forward;
  292. head_axis = up;
  293. } else {
  294. handle_axis = up;
  295. head_axis = right;
  296. }
  297. float const h_len = 0.30F;
  298. QVector3D const handle_offset = anim.is_constructing
  299. ? (forward * 0.11F + up * 0.02F)
  300. : (up * 0.11F + forward * 0.02F);
  301. QVector3D const h_top = hand + handle_offset;
  302. QVector3D const h_bot = h_top - handle_axis * h_len;
  303. out.mesh(get_unit_cylinder(),
  304. cylinder_between(ctx.model, h_bot, h_top, 0.015F), wood, nullptr,
  305. 1.0F);
  306. float const head_len = 0.09F;
  307. float const head_r = 0.028F;
  308. QVector3D const head_center = h_top + handle_axis * 0.03F;
  309. out.mesh(
  310. get_unit_cylinder(),
  311. cylinder_between(ctx.model, head_center - head_axis * (head_len * 0.5F),
  312. head_center + head_axis * (head_len * 0.5F), head_r),
  313. stone_color, nullptr, 1.0F);
  314. out.mesh(get_unit_sphere(),
  315. sphere_at(ctx.model, head_center + head_axis * (head_len * 0.5F),
  316. head_r * 1.1F),
  317. stone_dark, nullptr, 1.0F);
  318. out.mesh(get_unit_sphere(),
  319. sphere_at(ctx.model, head_center - head_axis * (head_len * 0.5F),
  320. head_r * 0.85F),
  321. stone_color * 0.92F, nullptr, 1.0F);
  322. }
  323. void draw_headwrap(const DrawContext &ctx, const HumanoidVariant &v,
  324. const HumanoidPose &pose, ISubmitter &out) const {
  325. const BodyFrames &frames = pose.body_frames;
  326. QVector3D const wrap_color(0.88F, 0.82F, 0.72F);
  327. QVector3D const head_top = frames.head.origin + frames.head.up * 0.05F;
  328. QVector3D const head_back = frames.head.origin -
  329. frames.head.forward * 0.03F +
  330. frames.head.up * 0.02F;
  331. out.mesh(get_unit_sphere(), sphere_at(ctx.model, head_top, 0.052F),
  332. wrap_color, nullptr, 1.0F);
  333. out.mesh(get_unit_sphere(), sphere_at(ctx.model, head_back, 0.048F),
  334. wrap_color * 0.95F, nullptr, 1.0F);
  335. }
  336. void draw_craftsman_robes(const DrawContext &ctx, const HumanoidVariant &v,
  337. const HumanoidPose &pose, uint32_t seed,
  338. ISubmitter &out) const {
  339. using HP = HumanProportions;
  340. const BodyFrames &frames = pose.body_frames;
  341. const AttachmentFrame &torso = frames.torso;
  342. const AttachmentFrame &waist = frames.waist;
  343. if (torso.radius <= 0.0F) {
  344. return;
  345. }
  346. float const var = hash_01(seed ^ 0xCDEU);
  347. QVector3D robe_color;
  348. if (var < 0.35F) {
  349. robe_color = QVector3D(0.85F, 0.78F, 0.68F);
  350. } else if (var < 0.65F) {
  351. robe_color = QVector3D(0.72F, 0.65F, 0.55F);
  352. } else {
  353. robe_color = QVector3D(0.62F, 0.58F, 0.52F);
  354. }
  355. QVector3D const robe_dark = robe_color * 0.88F;
  356. const QVector3D &origin = torso.origin;
  357. const QVector3D &right = torso.right;
  358. const QVector3D &up = torso.up;
  359. const QVector3D &forward = torso.forward;
  360. float const tr = torso.radius * 1.06F;
  361. float const td =
  362. (torso.depth > 0.0F) ? torso.depth * 0.90F : torso.radius * 0.78F;
  363. float const y_sh = origin.y() + 0.035F;
  364. float const y_w = waist.origin.y();
  365. float const y_hem = y_w - 0.22F;
  366. constexpr int segs = 12;
  367. constexpr float pi = std::numbers::pi_v<float>;
  368. auto ring = [&](float y, float w, float d, const QVector3D &c, float th) {
  369. for (int i = 0; i < segs; ++i) {
  370. float a1 = (float(i) / segs) * 2.0F * pi;
  371. float a2 = (float(i + 1) / segs) * 2.0F * pi;
  372. QVector3D p1 = origin + right * (w * std::sin(a1)) +
  373. forward * (d * std::cos(a1)) + up * (y - origin.y());
  374. QVector3D p2 = origin + right * (w * std::sin(a2)) +
  375. forward * (d * std::cos(a2)) + up * (y - origin.y());
  376. out.mesh(get_unit_cylinder(), cylinder_between(ctx.model, p1, p2, th),
  377. c, nullptr, 1.0F);
  378. }
  379. };
  380. ring(y_sh + 0.045F, tr * 0.65F, td * 0.58F, robe_dark, 0.020F);
  381. ring(y_sh + 0.03F, tr * 1.15F, td * 1.08F, robe_color, 0.035F);
  382. ring(y_sh, tr * 1.10F, td * 1.04F, robe_color, 0.032F);
  383. for (int i = 0; i < 5; ++i) {
  384. float t = float(i) / 4.0F;
  385. float y = y_sh - 0.02F - t * (y_sh - y_w - 0.02F);
  386. QVector3D c = robe_color * (1.0F - t * 0.05F);
  387. ring(y, tr * (1.06F - t * 0.12F), td * (1.00F - t * 0.10F), c,
  388. 0.026F - t * 0.003F);
  389. }
  390. for (int i = 0; i < 6; ++i) {
  391. float t = float(i) / 5.0F;
  392. float y = y_w - 0.02F - t * (y_w - y_hem);
  393. float flare = 1.0F + t * 0.28F;
  394. QVector3D c = robe_color * (1.0F - t * 0.06F);
  395. ring(y, tr * 0.85F * flare, td * 0.80F * flare, c, 0.020F + t * 0.008F);
  396. }
  397. auto sleeve = [&](const QVector3D &sh, const QVector3D &out_dir) {
  398. QVector3D const back = -forward;
  399. QVector3D anchor = sh + up * 0.055F + back * 0.012F;
  400. for (int i = 0; i < 4; ++i) {
  401. float t = float(i) / 4.0F;
  402. QVector3D pos = anchor + out_dir * (0.012F + t * 0.022F) +
  403. forward * (-0.012F + t * 0.05F) - up * (t * 0.035F);
  404. float r = HP::UPPER_ARM_R * (1.48F - t * 0.08F);
  405. out.mesh(get_unit_sphere(), sphere_at(ctx.model, pos, r),
  406. robe_color * (1.0F - t * 0.03F), nullptr, 1.0F);
  407. }
  408. };
  409. sleeve(frames.shoulder_l.origin, -right);
  410. sleeve(frames.shoulder_r.origin, right);
  411. draw_extended_forearm(ctx, v, pose, out);
  412. }
  413. void draw_extended_forearm(const DrawContext &ctx, const HumanoidVariant &v,
  414. const HumanoidPose &pose, ISubmitter &out) const {
  415. QVector3D const skin_color = v.palette.skin;
  416. QVector3D const elbow_r = pose.elbow_r;
  417. QVector3D const hand_r = pose.hand_r;
  418. for (int i = 0; i < 4; ++i) {
  419. float t = 0.25F + float(i) * 0.20F;
  420. QVector3D pos = elbow_r * (1.0F - t) + hand_r * t;
  421. float r = 0.022F - float(i) * 0.002F;
  422. out.mesh(get_unit_sphere(), sphere_at(ctx.model, pos, r), skin_color,
  423. nullptr, 1.0F);
  424. }
  425. }
  426. private:
  427. auto
  428. resolve_style(const DrawContext &ctx) const -> const BuilderStyleConfig & {
  429. ensure_builder_styles_registered();
  430. auto &styles = style_registry();
  431. std::string nation_id;
  432. if (ctx.entity != nullptr) {
  433. if (auto *unit =
  434. ctx.entity->get_component<Engine::Core::UnitComponent>()) {
  435. nation_id = Game::Systems::nation_id_to_string(unit->nation_id);
  436. }
  437. }
  438. if (!nation_id.empty()) {
  439. auto it = styles.find(nation_id);
  440. if (it != styles.end()) {
  441. return it->second;
  442. }
  443. }
  444. auto f = styles.find(std::string(k_default_style_key));
  445. if (f != styles.end()) {
  446. return f->second;
  447. }
  448. static const BuilderStyleConfig def{};
  449. return def;
  450. }
  451. auto resolve_shader_key(const DrawContext &ctx) const -> QString {
  452. const BuilderStyleConfig &s = resolve_style(ctx);
  453. if (!s.shader_id.empty()) {
  454. return QString::fromStdString(s.shader_id);
  455. }
  456. return QStringLiteral("builder");
  457. }
  458. void apply_palette_overrides(const BuilderStyleConfig &style,
  459. const QVector3D &team_tint,
  460. HumanoidVariant &v) const {
  461. auto apply = [&](const std::optional<QVector3D> &c, QVector3D &t, float tw,
  462. float sw) {
  463. t = mix_palette_color(t, c, team_tint, tw, sw);
  464. };
  465. apply(style.skin_color, v.palette.skin, 0.0F, 1.0F);
  466. apply(style.cloth_color, v.palette.cloth, 0.0F, 1.0F);
  467. apply(style.leather_color, v.palette.leather, k_team_mix_weight,
  468. k_style_mix_weight);
  469. apply(style.leather_dark_color, v.palette.leather_dark, k_team_mix_weight,
  470. k_style_mix_weight);
  471. apply(style.metal_color, v.palette.metal, k_team_mix_weight,
  472. k_style_mix_weight);
  473. apply(style.wood_color, v.palette.wood, k_team_mix_weight,
  474. k_style_mix_weight);
  475. }
  476. };
  477. void register_builder_renderer(Render::GL::EntityRendererRegistry &registry) {
  478. ensure_builder_styles_registered();
  479. static BuilderRenderer const renderer;
  480. registry.register_renderer(
  481. "troops/carthage/builder", [](const DrawContext &ctx, ISubmitter &out) {
  482. static BuilderRenderer const r;
  483. Shader *shader = nullptr;
  484. if (ctx.backend != nullptr) {
  485. QString key = r.resolve_shader_key(ctx);
  486. shader = ctx.backend->shader(key);
  487. if (!shader) {
  488. shader = ctx.backend->shader(QStringLiteral("builder"));
  489. }
  490. }
  491. auto *sr = dynamic_cast<Renderer *>(&out);
  492. if (sr && shader) {
  493. sr->set_current_shader(shader);
  494. }
  495. r.render(ctx, out);
  496. if (sr) {
  497. sr->set_current_shader(nullptr);
  498. }
  499. });
  500. }
  501. } // namespace Render::GL::Carthage