spearman_renderer.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. #include "spearman_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/spear_renderer.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/shader.h"
  11. #include "../../../humanoid/humanoid_math.h"
  12. #include "../../../humanoid/humanoid_specs.h"
  13. #include "../../../humanoid/pose_controller.h"
  14. #include "../../../humanoid/rig.h"
  15. #include "../../../humanoid/spear_pose_utils.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 "spearman_style.h"
  23. #include <QDebug>
  24. #include <QMatrix4x4>
  25. #include <QString>
  26. #include <QVector3D>
  27. #include <algorithm>
  28. #include <cmath>
  29. #include <cstdint>
  30. #include <optional>
  31. #include <qstringliteral.h>
  32. #include <qvectornd.h>
  33. #include <string>
  34. #include <string_view>
  35. #include <unordered_map>
  36. namespace Render::GL::Carthage {
  37. namespace {
  38. constexpr std::string_view k_spearman_default_style_key = "default";
  39. constexpr float k_spearman_team_mix_weight = 0.6F;
  40. constexpr float k_spearman_style_mix_weight = 0.4F;
  41. constexpr float k_kneel_depth_multiplier = 0.875F;
  42. constexpr float k_lean_amount_multiplier = 0.67F;
  43. struct SpearmanShaderResourcePaths {
  44. QString vertex;
  45. QString fragment;
  46. };
  47. auto lookup_spearman_shader_resources(const QString &shader_key)
  48. -> std::optional<SpearmanShaderResourcePaths> {
  49. if (shader_key == QStringLiteral("spearman_carthage")) {
  50. return SpearmanShaderResourcePaths{
  51. QStringLiteral(":/assets/shaders/spearman_carthage.vert"),
  52. QStringLiteral(":/assets/shaders/spearman_carthage.frag")};
  53. }
  54. if (shader_key == QStringLiteral("spearman_roman_republic")) {
  55. return SpearmanShaderResourcePaths{
  56. QStringLiteral(":/assets/shaders/spearman_roman_republic.vert"),
  57. QStringLiteral(":/assets/shaders/spearman_roman_republic.frag")};
  58. }
  59. return std::nullopt;
  60. }
  61. auto spearman_style_registry()
  62. -> std::unordered_map<std::string, SpearmanStyleConfig> & {
  63. static std::unordered_map<std::string, SpearmanStyleConfig> styles;
  64. return styles;
  65. }
  66. void ensure_spearman_styles_registered() {
  67. static const bool registered = []() {
  68. register_carthage_spearman_style();
  69. return true;
  70. }();
  71. (void)registered;
  72. }
  73. } // namespace
  74. void register_spearman_style(const std::string &nation_id,
  75. const SpearmanStyleConfig &style) {
  76. spearman_style_registry()[nation_id] = style;
  77. }
  78. using Render::Geom::clamp01;
  79. using Render::Geom::cone_from_to;
  80. using Render::Geom::cylinder_between;
  81. using Render::Geom::ease_in_out_cubic;
  82. using Render::Geom::lerp;
  83. using Render::Geom::smoothstep;
  84. using Render::Geom::sphere_at;
  85. using Render::GL::Humanoid::mix_palette_color;
  86. using Render::GL::Humanoid::saturate_color;
  87. struct SpearmanExtras {
  88. QVector3D spear_shaft_color;
  89. QVector3D spearhead_color;
  90. float spear_length = 1.20F;
  91. float spear_shaft_radius = 0.020F;
  92. float spearheadLength = 0.18F;
  93. };
  94. class SpearmanRenderer : public HumanoidRendererBase {
  95. public:
  96. auto get_proportion_scaling() const -> QVector3D override {
  97. return {0.72F, 1.02F, 0.74F};
  98. }
  99. void adjust_variation(const DrawContext &, uint32_t,
  100. VariationParams &variation) const override {
  101. variation.bulk_scale *= 0.90F;
  102. variation.stance_width *= 0.92F;
  103. }
  104. private:
  105. mutable std::unordered_map<uint32_t, SpearmanExtras> m_extrasCache;
  106. public:
  107. void get_variant(const DrawContext &ctx, uint32_t seed,
  108. HumanoidVariant &v) const override {
  109. QVector3D const team_tint = resolve_team_tint(ctx);
  110. v.palette = make_humanoid_palette(team_tint, seed);
  111. auto const &style = resolve_style(ctx);
  112. apply_palette_overrides(style, team_tint, v);
  113. auto nextRand = [](uint32_t &s) -> float {
  114. s = s * 1664525U + 1013904223U;
  115. return float(s & 0x7FFFFFU) / float(0x7FFFFFU);
  116. };
  117. uint32_t beard_seed = seed ^ 0xBEEFFAU;
  118. bool wants_beard = style.force_beard;
  119. if (!wants_beard) {
  120. float const beard_roll = nextRand(beard_seed);
  121. wants_beard = (beard_roll < 0.90F);
  122. }
  123. if (wants_beard) {
  124. float const style_roll = nextRand(beard_seed);
  125. if (style_roll < 0.55F) {
  126. v.facial_hair.style = FacialHairStyle::FullBeard;
  127. v.facial_hair.length = 1.0F + nextRand(beard_seed) * 0.7F;
  128. } else if (style_roll < 0.80F) {
  129. v.facial_hair.style = FacialHairStyle::LongBeard;
  130. v.facial_hair.length = 1.3F + nextRand(beard_seed) * 0.9F;
  131. } else {
  132. v.facial_hair.style = FacialHairStyle::ShortBeard;
  133. v.facial_hair.length = 0.9F + nextRand(beard_seed) * 0.5F;
  134. }
  135. float const color_roll = nextRand(beard_seed);
  136. if (color_roll < 0.60F) {
  137. v.facial_hair.color = QVector3D(0.18F + nextRand(beard_seed) * 0.10F,
  138. 0.14F + nextRand(beard_seed) * 0.08F,
  139. 0.10F + nextRand(beard_seed) * 0.06F);
  140. } else if (color_roll < 0.85F) {
  141. v.facial_hair.color = QVector3D(0.30F + nextRand(beard_seed) * 0.12F,
  142. 0.24F + nextRand(beard_seed) * 0.10F,
  143. 0.16F + nextRand(beard_seed) * 0.08F);
  144. } else {
  145. v.facial_hair.color = QVector3D(0.35F + nextRand(beard_seed) * 0.10F,
  146. 0.20F + nextRand(beard_seed) * 0.08F,
  147. 0.12F + nextRand(beard_seed) * 0.06F);
  148. }
  149. v.facial_hair.thickness = 0.95F + nextRand(beard_seed) * 0.30F;
  150. v.facial_hair.coverage = 0.80F + nextRand(beard_seed) * 0.20F;
  151. if (nextRand(beard_seed) < 0.12F) {
  152. v.facial_hair.greyness = 0.12F + nextRand(beard_seed) * 0.30F;
  153. } else {
  154. v.facial_hair.greyness = 0.0F;
  155. }
  156. } else {
  157. v.facial_hair.style = FacialHairStyle::None;
  158. }
  159. }
  160. void customize_pose(const DrawContext &,
  161. const HumanoidAnimationContext &anim_ctx, uint32_t seed,
  162. HumanoidPose &pose) const override {
  163. using HP = HumanProportions;
  164. const AnimationInputs &anim = anim_ctx.inputs;
  165. HumanoidPoseController controller(pose, anim_ctx);
  166. float const arm_height_jitter = (hash_01(seed ^ 0xABCDU) - 0.5F) * 0.03F;
  167. float const arm_asymmetry = (hash_01(seed ^ 0xDEF0U) - 0.5F) * 0.04F;
  168. if (anim.is_in_hold_mode || anim.is_exiting_hold) {
  169. float const hold_t =
  170. anim.is_in_hold_mode ? 1.0F : (1.0F - anim.hold_exit_progress);
  171. if (anim.is_exiting_hold) {
  172. controller.kneel_transition(anim.hold_exit_progress, true);
  173. } else {
  174. controller.kneel(hold_t * k_kneel_depth_multiplier);
  175. }
  176. controller.lean(QVector3D(0.0F, 0.0F, 1.0F),
  177. hold_t * k_lean_amount_multiplier);
  178. if (anim.is_attacking && anim.is_melee && anim.is_in_hold_mode) {
  179. float const attack_phase = std::fmod(
  180. anim_ctx.attack_phase * SPEARMAN_INV_ATTACK_CYCLE_TIME, 1.0F);
  181. controller.spear_thrust_from_hold(attack_phase,
  182. hold_t * k_kneel_depth_multiplier);
  183. } else {
  184. float const lowered_shoulder_y = controller.get_shoulder_y(true);
  185. float const pelvis_y = controller.get_pelvis_y();
  186. QVector3D const hand_r_pos(0.18F * (1.0F - hold_t) + 0.22F * hold_t,
  187. lowered_shoulder_y * (1.0F - hold_t) +
  188. (pelvis_y + 0.05F) * hold_t,
  189. 0.15F * (1.0F - hold_t) + 0.20F * hold_t);
  190. float const offhand_along = lerp(-0.06F, -0.02F, hold_t);
  191. float const offhand_drop = 0.10F + 0.02F * hold_t;
  192. QVector3D const hand_l_pos =
  193. compute_offhand_spear_grip(pose, anim_ctx, hand_r_pos, false,
  194. offhand_along, offhand_drop, -0.08F);
  195. controller.place_hand_at(false, hand_r_pos);
  196. controller.place_hand_at(true, hand_l_pos);
  197. }
  198. } else if (anim.is_attacking && anim.is_melee && !anim.is_in_hold_mode) {
  199. float const attack_phase = std::fmod(
  200. anim_ctx.attack_phase * SPEARMAN_INV_ATTACK_CYCLE_TIME, 1.0F);
  201. controller.spear_thrust_variant(attack_phase, anim.attack_variant);
  202. } else {
  203. QVector3D const idle_hand_r(0.28F + arm_asymmetry,
  204. HP::SHOULDER_Y - 0.02F + arm_height_jitter,
  205. 0.30F);
  206. QVector3D const idle_hand_l = compute_offhand_spear_grip(
  207. pose, anim_ctx, idle_hand_r, false, -0.04F, 0.10F, -0.08F);
  208. controller.place_hand_at(false, idle_hand_r);
  209. controller.place_hand_at(true, idle_hand_l);
  210. }
  211. }
  212. void add_attachments(const DrawContext &ctx, const HumanoidVariant &v,
  213. const HumanoidPose &pose,
  214. const HumanoidAnimationContext &anim_ctx,
  215. ISubmitter &out) const override {
  216. const AnimationInputs &anim = anim_ctx.inputs;
  217. uint32_t const seed = reinterpret_cast<uintptr_t>(ctx.entity) & 0xFFFFFFFFU;
  218. auto const &style = resolve_style(ctx);
  219. QVector3D const team_tint = resolve_team_tint(ctx);
  220. SpearmanExtras extras;
  221. auto it = m_extrasCache.find(seed);
  222. if (it != m_extrasCache.end()) {
  223. extras = it->second;
  224. } else {
  225. extras = computeSpearmanExtras(seed, v);
  226. apply_extras_overrides(style, team_tint, v, extras);
  227. m_extrasCache[seed] = extras;
  228. if (m_extrasCache.size() > MAX_EXTRAS_CACHE_SIZE) {
  229. m_extrasCache.clear();
  230. }
  231. }
  232. apply_extras_overrides(style, team_tint, v, extras);
  233. bool const is_attacking = anim.is_attacking && anim.is_melee;
  234. auto &registry = EquipmentRegistry::instance();
  235. auto spear = registry.get(EquipmentCategory::Weapon, "spear");
  236. if (spear) {
  237. SpearRenderConfig spear_config;
  238. spear_config.shaft_color = extras.spear_shaft_color;
  239. spear_config.spearhead_color = extras.spearhead_color;
  240. spear_config.spear_length = extras.spear_length;
  241. spear_config.shaft_radius = extras.spear_shaft_radius;
  242. spear_config.spearhead_length = extras.spearheadLength;
  243. auto *spear_renderer = dynamic_cast<SpearRenderer *>(spear.get());
  244. if (spear_renderer) {
  245. spear_renderer->set_config(spear_config);
  246. }
  247. spear->render(ctx, pose.body_frames, v.palette, anim_ctx, out);
  248. }
  249. }
  250. void draw_helmet(const DrawContext &ctx, const HumanoidVariant &v,
  251. const HumanoidPose &pose, ISubmitter &out) const override {
  252. auto &registry = EquipmentRegistry::instance();
  253. auto helmet = registry.get(EquipmentCategory::Helmet, "carthage_heavy");
  254. if (helmet) {
  255. HumanoidAnimationContext anim_ctx{};
  256. helmet->render(ctx, pose.body_frames, v.palette, anim_ctx, out);
  257. }
  258. }
  259. void draw_armor(const DrawContext &ctx, const HumanoidVariant &v,
  260. const HumanoidPose &pose,
  261. const HumanoidAnimationContext &anim,
  262. ISubmitter &out) const override {
  263. auto &registry = EquipmentRegistry::instance();
  264. const SpearmanStyleConfig &style = resolve_style(ctx);
  265. std::string armor_key =
  266. style.armor_id.empty() ? "armor_light_carthage" : style.armor_id;
  267. auto armor = registry.get(EquipmentCategory::Armor, armor_key);
  268. if (armor) {
  269. armor->render(ctx, pose.body_frames, v.palette, anim, out);
  270. }
  271. auto shoulder_cover =
  272. registry.get(EquipmentCategory::Armor, "carthage_shoulder_cover");
  273. if (shoulder_cover) {
  274. shoulder_cover->render(ctx, pose.body_frames, v.palette, anim, out);
  275. }
  276. }
  277. private:
  278. static auto computeSpearmanExtras(uint32_t seed, const HumanoidVariant &v)
  279. -> SpearmanExtras {
  280. SpearmanExtras e;
  281. e.spear_shaft_color = v.palette.leather * QVector3D(0.85F, 0.75F, 0.65F);
  282. e.spearhead_color = QVector3D(0.75F, 0.76F, 0.80F);
  283. e.spear_length = 1.15F + (hash_01(seed ^ 0xABCDU) - 0.5F) * 0.10F;
  284. e.spear_shaft_radius = 0.018F + (hash_01(seed ^ 0x7777U) - 0.5F) * 0.003F;
  285. e.spearheadLength = 0.16F + (hash_01(seed ^ 0xBEEFU) - 0.5F) * 0.04F;
  286. return e;
  287. }
  288. auto
  289. resolve_style(const DrawContext &ctx) const -> const SpearmanStyleConfig & {
  290. ensure_spearman_styles_registered();
  291. auto &styles = spearman_style_registry();
  292. std::string nation_id;
  293. if (ctx.entity != nullptr) {
  294. if (auto *unit =
  295. ctx.entity->get_component<Engine::Core::UnitComponent>()) {
  296. nation_id = Game::Systems::nation_id_to_string(unit->nation_id);
  297. }
  298. }
  299. if (!nation_id.empty()) {
  300. auto it = styles.find(nation_id);
  301. if (it != styles.end()) {
  302. return it->second;
  303. }
  304. }
  305. auto it_default = styles.find(std::string(k_spearman_default_style_key));
  306. if (it_default != styles.end()) {
  307. return it_default->second;
  308. }
  309. static const SpearmanStyleConfig k_empty{};
  310. return k_empty;
  311. }
  312. public:
  313. auto resolve_shader_key(const DrawContext &ctx) const -> QString {
  314. const SpearmanStyleConfig &style = resolve_style(ctx);
  315. if (!style.shader_id.empty()) {
  316. return QString::fromStdString(style.shader_id);
  317. }
  318. return QStringLiteral("spearman");
  319. }
  320. private:
  321. void apply_palette_overrides(const SpearmanStyleConfig &style,
  322. const QVector3D &team_tint,
  323. HumanoidVariant &variant) const {
  324. auto apply_color = [&](const std::optional<QVector3D> &override_color,
  325. QVector3D &target) {
  326. target = mix_palette_color(target, override_color, team_tint,
  327. k_spearman_team_mix_weight,
  328. k_spearman_style_mix_weight);
  329. };
  330. apply_color(style.cloth_color, variant.palette.cloth);
  331. apply_color(style.leather_color, variant.palette.leather);
  332. apply_color(style.leather_dark_color, variant.palette.leather_dark);
  333. apply_color(style.metal_color, variant.palette.metal);
  334. }
  335. void apply_extras_overrides(const SpearmanStyleConfig &style,
  336. const QVector3D &team_tint,
  337. [[maybe_unused]] const HumanoidVariant &variant,
  338. SpearmanExtras &extras) const {
  339. extras.spear_shaft_color = saturate_color(extras.spear_shaft_color);
  340. extras.spearhead_color = saturate_color(extras.spearhead_color);
  341. auto apply_color = [&](const std::optional<QVector3D> &override_color,
  342. QVector3D &target) {
  343. target = mix_palette_color(target, override_color, team_tint,
  344. k_spearman_team_mix_weight,
  345. k_spearman_style_mix_weight);
  346. };
  347. apply_color(style.spear_shaft_color, extras.spear_shaft_color);
  348. apply_color(style.spearhead_color, extras.spearhead_color);
  349. if (style.spear_length_scale) {
  350. extras.spear_length =
  351. std::max(0.80F, extras.spear_length * *style.spear_length_scale);
  352. }
  353. }
  354. };
  355. void register_spearman_renderer(Render::GL::EntityRendererRegistry &registry) {
  356. ensure_spearman_styles_registered();
  357. static SpearmanRenderer const renderer;
  358. registry.register_renderer(
  359. "troops/carthage/spearman", [](const DrawContext &ctx, ISubmitter &out) {
  360. static SpearmanRenderer const static_renderer;
  361. Shader *spearman_shader = nullptr;
  362. auto acquireShader = [&](const QString &shader_key) -> Shader * {
  363. if (ctx.backend == nullptr || shader_key.isEmpty()) {
  364. return nullptr;
  365. }
  366. Shader *shader = ctx.backend->shader(shader_key);
  367. if (shader != nullptr) {
  368. return shader;
  369. }
  370. if (auto resources = lookup_spearman_shader_resources(shader_key)) {
  371. shader = ctx.backend->get_or_load_shader(
  372. shader_key, resources->vertex, resources->fragment);
  373. }
  374. return shader;
  375. };
  376. if (ctx.backend != nullptr) {
  377. spearman_shader = acquireShader(QStringLiteral("spearman_carthage"));
  378. if (spearman_shader == nullptr) {
  379. static bool warned = false;
  380. if (!warned) {
  381. qWarning()
  382. << "Carthage spearman: missing spearman_carthage shader;"
  383. << "falling back to generic spearman shader.";
  384. warned = true;
  385. }
  386. spearman_shader = acquireShader(QStringLiteral("spearman"));
  387. }
  388. }
  389. auto *scene_renderer = dynamic_cast<Renderer *>(&out);
  390. if ((scene_renderer != nullptr) && (spearman_shader != nullptr)) {
  391. scene_renderer->set_current_shader(spearman_shader);
  392. }
  393. static_renderer.render(ctx, out);
  394. if (scene_renderer != nullptr) {
  395. scene_renderer->set_current_shader(nullptr);
  396. }
  397. });
  398. }
  399. } // namespace Render::GL::Carthage