swordsman_renderer.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. #include "swordsman_renderer.h"
  2. #include "../../../../game/core/component.h"
  3. #include "../../../../game/systems/nation_id.h"
  4. #include "../../../equipment/equipment_registry.h"
  5. #include "../../../equipment/weapons/shield_renderer.h"
  6. #include "../../../equipment/weapons/sword_renderer.h"
  7. #include "../../../geom/math_utils.h"
  8. #include "../../../geom/transforms.h"
  9. #include "../../../gl/backend.h"
  10. #include "../../../gl/primitives.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 "swordsman_style.h"
  23. #include <numbers>
  24. #include <qmatrix4x4.h>
  25. #include <qstringliteral.h>
  26. #include <qvectornd.h>
  27. #include <unordered_map>
  28. #include <QMatrix4x4>
  29. #include <QString>
  30. #include <QVector3D>
  31. #include <algorithm>
  32. #include <cmath>
  33. #include <cstdint>
  34. #include <optional>
  35. #include <string>
  36. #include <string_view>
  37. namespace Render::GL::Carthage {
  38. namespace {
  39. constexpr std::string_view k_swordsman_default_style_key = "default";
  40. constexpr float k_swordsman_team_mix_weight = 0.6F;
  41. constexpr float k_swordsman_style_mix_weight = 0.4F;
  42. auto swordsman_style_registry()
  43. -> std::unordered_map<std::string, KnightStyleConfig> & {
  44. static std::unordered_map<std::string, KnightStyleConfig> styles;
  45. return styles;
  46. }
  47. void ensure_swordsman_styles_registered() {
  48. static const bool registered = []() {
  49. register_carthage_swordsman_style();
  50. return true;
  51. }();
  52. (void)registered;
  53. }
  54. } // namespace
  55. void register_swordsman_style(const std::string &nation_id,
  56. const KnightStyleConfig &style) {
  57. swordsman_style_registry()[nation_id] = style;
  58. }
  59. using Render::Geom::clamp01;
  60. using Render::Geom::clamp_f;
  61. using Render::Geom::cone_from_to;
  62. using Render::Geom::cylinder_between;
  63. using Render::Geom::ease_in_out_cubic;
  64. using Render::Geom::lerp;
  65. using Render::Geom::nlerp;
  66. using Render::Geom::smoothstep;
  67. using Render::Geom::sphere_at;
  68. using Render::GL::Humanoid::mix_palette_color;
  69. using Render::GL::Humanoid::saturate_color;
  70. struct KnightExtras {
  71. QVector3D metal_color;
  72. QVector3D shield_color;
  73. QVector3D shield_trim_color;
  74. float sword_length = 0.80F;
  75. float swordWidth = 0.065F;
  76. float shieldRadius = 0.18F;
  77. float shield_aspect = 1.0F;
  78. float guard_half_width = 0.12F;
  79. float handleRadius = 0.016F;
  80. float pommel_radius = 0.045F;
  81. float blade_ricasso = 0.16F;
  82. float blade_taper_bias = 0.65F;
  83. bool shieldCrossDecal = false;
  84. bool has_scabbard = true;
  85. };
  86. class KnightRenderer : public HumanoidRendererBase {
  87. public:
  88. KnightRenderer() { cache_equipment(); }
  89. static constexpr float kLimbWidthScale = 0.90F;
  90. static constexpr float kTorsoWidthScale = 0.75F;
  91. static constexpr float kHeightScale = 1.03F;
  92. static constexpr float kDepthScale = 0.46F;
  93. auto get_proportion_scaling() const -> QVector3D override {
  94. return {kLimbWidthScale, kHeightScale, kDepthScale};
  95. }
  96. auto get_torso_scale() const -> float override { return kTorsoWidthScale; }
  97. private:
  98. mutable std::unordered_map<uint32_t, KnightExtras> m_extrasCache;
  99. public:
  100. void get_variant(const DrawContext &ctx, uint32_t seed,
  101. HumanoidVariant &v) const override {
  102. QVector3D const team_tint = resolve_team_tint(ctx);
  103. v.palette = make_humanoid_palette(team_tint, seed);
  104. auto const &style = resolve_style(ctx);
  105. apply_palette_overrides(style, team_tint, v);
  106. }
  107. void customize_pose(const DrawContext &,
  108. const HumanoidAnimationContext &anim_ctx, uint32_t seed,
  109. HumanoidPose &pose) const override {
  110. using HP = HumanProportions;
  111. const AnimationInputs &anim = anim_ctx.inputs;
  112. HumanoidPoseController controller(pose, anim_ctx);
  113. float const arm_height_jitter = (hash_01(seed ^ 0xABCDU) - 0.5F) * 0.03F;
  114. float const arm_asymmetry = (hash_01(seed ^ 0xDEF0U) - 0.5F) * 0.04F;
  115. if (anim.is_attacking && anim.is_melee) {
  116. float const attack_phase =
  117. std::fmod(anim_ctx.attack_phase * KNIGHT_INV_ATTACK_CYCLE_TIME, 1.0F);
  118. controller.sword_slash_variant(attack_phase, anim.attack_variant);
  119. } else {
  120. QVector3D const idle_hand_r(0.30F + arm_asymmetry,
  121. HP::SHOULDER_Y - 0.02F + arm_height_jitter,
  122. 0.35F);
  123. QVector3D const idle_hand_l(-0.22F - 0.5F * arm_asymmetry,
  124. HP::SHOULDER_Y + 0.5F * arm_height_jitter,
  125. 0.18F);
  126. controller.place_hand_at(false, idle_hand_r);
  127. controller.place_hand_at(true, idle_hand_l);
  128. }
  129. }
  130. void add_attachments(const DrawContext &ctx, const HumanoidVariant &v,
  131. const HumanoidPose &pose,
  132. const HumanoidAnimationContext &anim_ctx,
  133. ISubmitter &out) const override {
  134. const AnimationInputs &anim = anim_ctx.inputs;
  135. uint32_t const seed = reinterpret_cast<uintptr_t>(ctx.entity) & 0xFFFFFFFFU;
  136. auto const &style = resolve_style(ctx);
  137. QVector3D const team_tint = resolve_team_tint(ctx);
  138. KnightExtras extras;
  139. auto it = m_extrasCache.find(seed);
  140. if (it != m_extrasCache.end()) {
  141. extras = it->second;
  142. } else {
  143. extras = computeKnightExtras(seed, v);
  144. apply_extras_overrides(style, team_tint, v, extras);
  145. m_extrasCache[seed] = extras;
  146. if (m_extrasCache.size() > MAX_EXTRAS_CACHE_SIZE) {
  147. m_extrasCache.clear();
  148. }
  149. }
  150. apply_extras_overrides(style, team_tint, v, extras);
  151. bool const is_attacking = anim.is_attacking && anim.is_melee;
  152. if (m_cached_sword) {
  153. SwordRenderConfig sword_config;
  154. sword_config.metal_color = extras.metal_color;
  155. sword_config.sword_length = extras.sword_length;
  156. sword_config.sword_width = extras.swordWidth;
  157. sword_config.guard_half_width = extras.guard_half_width;
  158. sword_config.handle_radius = extras.handleRadius;
  159. sword_config.pommel_radius = extras.pommel_radius;
  160. sword_config.blade_ricasso = extras.blade_ricasso;
  161. sword_config.blade_taper_bias = extras.blade_taper_bias;
  162. sword_config.has_scabbard = extras.has_scabbard;
  163. auto *sword_renderer =
  164. dynamic_cast<SwordRenderer *>(m_cached_sword.get());
  165. if (sword_renderer) {
  166. sword_renderer->set_config(sword_config);
  167. }
  168. m_cached_sword->render(ctx, pose.body_frames, v.palette, anim_ctx, out);
  169. }
  170. if (m_cached_shield) {
  171. m_cached_shield->render(ctx, pose.body_frames, v.palette, anim_ctx, out);
  172. }
  173. if (!is_attacking && extras.has_scabbard) {
  174. drawScabbard(ctx, pose, v, extras, out);
  175. }
  176. }
  177. void draw_helmet(const DrawContext &ctx, const HumanoidVariant &v,
  178. const HumanoidPose &pose, ISubmitter &out) const override {
  179. if (m_cached_helmet) {
  180. HumanoidAnimationContext anim_ctx{};
  181. m_cached_helmet->render(ctx, pose.body_frames, v.palette, anim_ctx, out);
  182. }
  183. }
  184. void draw_armor(const DrawContext &ctx, const HumanoidVariant &v,
  185. const HumanoidPose &pose,
  186. const HumanoidAnimationContext &anim,
  187. ISubmitter &out) const override {
  188. if (m_cached_armor) {
  189. m_cached_armor->render(ctx, pose.body_frames, v.palette, anim, out);
  190. }
  191. if (m_cached_shoulder_cover) {
  192. m_cached_shoulder_cover->render(ctx, pose.body_frames, v.palette, anim,
  193. out);
  194. }
  195. }
  196. private:
  197. void cache_equipment() {
  198. auto &registry = EquipmentRegistry::instance();
  199. m_cached_sword = registry.get(EquipmentCategory::Weapon, "sword_carthage");
  200. m_cached_shield =
  201. registry.get(EquipmentCategory::Weapon, "shield_carthage");
  202. m_cached_helmet = registry.get(EquipmentCategory::Helmet, "carthage_heavy");
  203. m_cached_armor =
  204. registry.get(EquipmentCategory::Armor, "armor_heavy_carthage");
  205. m_cached_shoulder_cover =
  206. registry.get(EquipmentCategory::Armor, "carthage_shoulder_cover");
  207. }
  208. mutable std::shared_ptr<IEquipmentRenderer> m_cached_sword;
  209. mutable std::shared_ptr<IEquipmentRenderer> m_cached_shield;
  210. mutable std::shared_ptr<IEquipmentRenderer> m_cached_helmet;
  211. mutable std::shared_ptr<IEquipmentRenderer> m_cached_armor;
  212. mutable std::shared_ptr<IEquipmentRenderer> m_cached_shoulder_cover;
  213. static auto computeKnightExtras(uint32_t seed,
  214. const HumanoidVariant &v) -> KnightExtras {
  215. KnightExtras e;
  216. e.metal_color = QVector3D(0.72F, 0.73F, 0.78F);
  217. float const shield_hue = hash_01(seed ^ 0x12345U);
  218. if (shield_hue < 0.45F) {
  219. e.shield_color = v.palette.cloth * 1.10F;
  220. } else if (shield_hue < 0.90F) {
  221. e.shield_color = v.palette.leather * 1.25F;
  222. } else {
  223. e.shield_color = e.metal_color * 0.95F;
  224. }
  225. e.sword_length = 0.80F + (hash_01(seed ^ 0xABCDU) - 0.5F) * 0.16F;
  226. e.swordWidth = 0.060F + (hash_01(seed ^ 0x7777U) - 0.5F) * 0.010F;
  227. e.shieldRadius = 0.16F + (hash_01(seed ^ 0xDEF0U) - 0.5F) * 0.04F;
  228. e.guard_half_width = 0.120F + (hash_01(seed ^ 0x3456U) - 0.5F) * 0.020F;
  229. e.handleRadius = 0.016F + (hash_01(seed ^ 0x88AAU) - 0.5F) * 0.003F;
  230. e.pommel_radius = 0.045F + (hash_01(seed ^ 0x19C3U) - 0.5F) * 0.006F;
  231. e.blade_ricasso =
  232. clamp_f(0.14F + (hash_01(seed ^ 0xBEEFU) - 0.5F) * 0.04F, 0.10F, 0.20F);
  233. e.blade_taper_bias =
  234. clamp01(0.6F + (hash_01(seed ^ 0xFACEU) - 0.5F) * 0.2F);
  235. e.shieldCrossDecal = (hash_01(seed ^ 0xA11CU) > 0.55F);
  236. e.has_scabbard = (hash_01(seed ^ 0x5CABU) > 0.15F);
  237. e.shield_trim_color = e.metal_color * 0.95F;
  238. e.shield_aspect = 1.0F;
  239. return e;
  240. }
  241. static void drawScabbard(const DrawContext &ctx, const HumanoidPose &,
  242. const HumanoidVariant &v, const KnightExtras &extras,
  243. ISubmitter &out) {
  244. using HP = HumanProportions;
  245. QVector3D const hip(0.10F, HP::WAIST_Y - 0.04F, -0.02F);
  246. QVector3D const tip = hip + QVector3D(-0.05F, -0.22F, -0.12F);
  247. float const sheath_r = extras.swordWidth * 0.85F;
  248. out.mesh(get_unit_cylinder(),
  249. cylinder_between(ctx.model, hip, tip, sheath_r),
  250. v.palette.leather * 0.9F, nullptr, 1.0F);
  251. out.mesh(get_unit_cone(),
  252. cone_from_to(ctx.model, tip,
  253. tip + QVector3D(-0.02F, -0.02F, -0.02F), sheath_r),
  254. extras.metal_color, nullptr, 1.0F);
  255. }
  256. auto
  257. resolve_style(const DrawContext &ctx) const -> const KnightStyleConfig & {
  258. ensure_swordsman_styles_registered();
  259. auto &styles = swordsman_style_registry();
  260. std::string nation_id;
  261. if (ctx.entity != nullptr) {
  262. if (auto *unit =
  263. ctx.entity->get_component<Engine::Core::UnitComponent>()) {
  264. nation_id = Game::Systems::nation_id_to_string(unit->nation_id);
  265. }
  266. }
  267. if (!nation_id.empty()) {
  268. auto it = styles.find(nation_id);
  269. if (it != styles.end()) {
  270. return it->second;
  271. }
  272. }
  273. auto it_default = styles.find(std::string(k_swordsman_default_style_key));
  274. if (it_default != styles.end()) {
  275. return it_default->second;
  276. }
  277. static const KnightStyleConfig k_empty{};
  278. return k_empty;
  279. }
  280. public:
  281. auto resolve_shader_key(const DrawContext &ctx) const -> QString {
  282. const KnightStyleConfig &style = resolve_style(ctx);
  283. if (!style.shader_id.empty()) {
  284. return QString::fromStdString(style.shader_id);
  285. }
  286. return QStringLiteral("swordsman");
  287. }
  288. private:
  289. void apply_palette_overrides(const KnightStyleConfig &style,
  290. const QVector3D &team_tint,
  291. HumanoidVariant &variant) const {
  292. auto apply_color = [&](const std::optional<QVector3D> &override_color,
  293. QVector3D &target) {
  294. target = mix_palette_color(target, override_color, team_tint,
  295. k_swordsman_team_mix_weight,
  296. k_swordsman_style_mix_weight);
  297. };
  298. apply_color(style.cloth_color, variant.palette.cloth);
  299. apply_color(style.leather_color, variant.palette.leather);
  300. apply_color(style.leather_dark_color, variant.palette.leather_dark);
  301. apply_color(style.metal_color, variant.palette.metal);
  302. }
  303. void apply_extras_overrides(const KnightStyleConfig &style,
  304. const QVector3D &team_tint,
  305. const HumanoidVariant &variant,
  306. KnightExtras &extras) const {
  307. extras.metal_color = saturate_color(variant.palette.metal);
  308. extras.shield_color = saturate_color(extras.shield_color);
  309. extras.shield_trim_color = saturate_color(extras.shield_trim_color);
  310. auto apply_shield_color =
  311. [&](const std::optional<QVector3D> &override_color, QVector3D &target) {
  312. target = mix_palette_color(target, override_color, team_tint,
  313. k_swordsman_team_mix_weight,
  314. k_swordsman_style_mix_weight);
  315. };
  316. apply_shield_color(style.shield_color, extras.shield_color);
  317. apply_shield_color(style.shield_trim_color, extras.shield_trim_color);
  318. if (style.shield_radius_scale) {
  319. extras.shieldRadius =
  320. std::max(0.10F, extras.shieldRadius * *style.shield_radius_scale);
  321. }
  322. if (style.shield_aspect_ratio) {
  323. extras.shield_aspect = std::max(0.40F, *style.shield_aspect_ratio);
  324. }
  325. if (style.has_scabbard) {
  326. extras.has_scabbard = *style.has_scabbard;
  327. }
  328. if (style.shield_cross_decal) {
  329. extras.shieldCrossDecal = *style.shield_cross_decal;
  330. }
  331. }
  332. };
  333. void register_knight_renderer(Render::GL::EntityRendererRegistry &registry) {
  334. ensure_swordsman_styles_registered();
  335. static KnightRenderer const renderer;
  336. registry.register_renderer(
  337. "troops/carthage/swordsman", [](const DrawContext &ctx, ISubmitter &out) {
  338. static KnightRenderer const static_renderer;
  339. Shader *swordsman_shader = nullptr;
  340. if (ctx.backend != nullptr) {
  341. QString shader_key = static_renderer.resolve_shader_key(ctx);
  342. swordsman_shader = ctx.backend->shader(shader_key);
  343. if (swordsman_shader == nullptr) {
  344. swordsman_shader = ctx.backend->shader(QStringLiteral("swordsman"));
  345. }
  346. }
  347. auto *scene_renderer = dynamic_cast<Renderer *>(&out);
  348. if ((scene_renderer != nullptr) && (swordsman_shader != nullptr)) {
  349. scene_renderer->set_current_shader(swordsman_shader);
  350. }
  351. static_renderer.render(ctx, out);
  352. if (scene_renderer != nullptr) {
  353. scene_renderer->set_current_shader(nullptr);
  354. }
  355. });
  356. }
  357. } // namespace Render::GL::Carthage