archer_renderer.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. #include "archer_renderer.h"
  2. #include "../../../../game/core/component.h"
  3. #include "../../../../game/core/entity.h"
  4. #include "../../../../game/systems/nation_id.h"
  5. #include "../../../equipment/armor/cloak_renderer.h"
  6. #include "../../../equipment/equipment_registry.h"
  7. #include "../../../equipment/weapons/bow_renderer.h"
  8. #include "../../../equipment/weapons/quiver_renderer.h"
  9. #include "../../../geom/math_utils.h"
  10. #include "../../../geom/transforms.h"
  11. #include "../../../gl/backend.h"
  12. #include "../../../gl/primitives.h"
  13. #include "../../../gl/render_constants.h"
  14. #include "../../../gl/shader.h"
  15. #include "../../../humanoid/humanoid_math.h"
  16. #include "../../../humanoid/humanoid_specs.h"
  17. #include "../../../humanoid/pose_controller.h"
  18. #include "../../../humanoid/rig.h"
  19. #include "../../../humanoid/style_palette.h"
  20. #include "../../../palette.h"
  21. #include "../../../scene_renderer.h"
  22. #include "../../../submitter.h"
  23. #include "../../registry.h"
  24. #include "../../renderer_constants.h"
  25. #include "archer_style.h"
  26. #include <QMatrix4x4>
  27. #include <QString>
  28. #include <QVector3D>
  29. #include <algorithm>
  30. #include <cmath>
  31. #include <cstdint>
  32. #include <numbers>
  33. #include <optional>
  34. #include <qmatrix4x4.h>
  35. #include <qstringliteral.h>
  36. #include <qvectornd.h>
  37. #include <string>
  38. #include <string_view>
  39. #include <unordered_map>
  40. namespace Render::GL::Carthage {
  41. namespace {
  42. constexpr std::string_view k_default_style_key = "default";
  43. constexpr std::string_view k_attachment_headwrap = "carthage_headwrap";
  44. constexpr float k_kneel_depth_multiplier = 1.125F;
  45. constexpr float k_lean_amount_multiplier = 0.83F;
  46. auto style_registry() -> std::unordered_map<std::string, ArcherStyleConfig> & {
  47. static std::unordered_map<std::string, ArcherStyleConfig> styles;
  48. return styles;
  49. }
  50. void ensure_archer_styles_registered() {
  51. static const bool registered = []() {
  52. register_carthage_archer_style();
  53. return true;
  54. }();
  55. (void)registered;
  56. }
  57. constexpr float k_team_mix_weight = 0.65F;
  58. constexpr float k_style_mix_weight = 0.35F;
  59. } // namespace
  60. void register_archer_style(const std::string &nation_id,
  61. const ArcherStyleConfig &style) {
  62. style_registry()[nation_id] = style;
  63. }
  64. using Render::Geom::clamp01;
  65. using Render::Geom::clamp_f;
  66. using Render::Geom::cone_from_to;
  67. using Render::Geom::cylinder_between;
  68. using Render::Geom::sphere_at;
  69. using Render::GL::Humanoid::mix_palette_color;
  70. using Render::GL::Humanoid::saturate_color;
  71. class ArcherRenderer : public HumanoidRendererBase {
  72. public:
  73. auto get_proportion_scaling() const -> QVector3D override {
  74. return {1.03F, 1.08F, 0.98F};
  75. }
  76. void adjust_variation(const DrawContext &, uint32_t,
  77. VariationParams &variation) const override {
  78. variation.height_scale *= 1.06F;
  79. variation.bulk_scale *= 0.90F;
  80. variation.stance_width *= 0.90F;
  81. variation.arm_swing_amp *= 0.92F;
  82. }
  83. void get_variant(const DrawContext &ctx, uint32_t seed,
  84. HumanoidVariant &v) const override {
  85. QVector3D const team_tint = resolve_team_tint(ctx);
  86. v.palette = make_humanoid_palette(team_tint, seed);
  87. auto const &style = resolve_style(ctx);
  88. apply_palette_overrides(style, team_tint, v);
  89. auto nextRand = [](uint32_t &s) -> float {
  90. s = s * 1664525U + 1013904223U;
  91. return float(s & 0x7FFFFFU) / float(0x7FFFFFU);
  92. };
  93. uint32_t beard_seed = seed ^ 0xBEAD01U;
  94. v.facial_hair.style = FacialHairStyle::None;
  95. v.muscularity = 0.95F + nextRand(beard_seed) * 0.25F;
  96. v.scarring = nextRand(beard_seed) * 0.30F;
  97. v.weathering = 0.40F + nextRand(beard_seed) * 0.40F;
  98. }
  99. void customize_pose(const DrawContext &,
  100. const HumanoidAnimationContext &anim_ctx, uint32_t seed,
  101. HumanoidPose &pose) const override {
  102. using HP = HumanProportions;
  103. const AnimationInputs &anim = anim_ctx.inputs;
  104. HumanoidPoseController controller(pose, anim_ctx);
  105. float const arm_height_jitter = (hash_01(seed ^ 0xABCDU) - 0.5F) * 0.03F;
  106. float const arm_asymmetry = (hash_01(seed ^ 0xDEF0U) - 0.5F) * 0.04F;
  107. float const bow_x = 0.0F;
  108. if (anim.is_in_hold_mode || anim.is_exiting_hold) {
  109. float const t =
  110. anim.is_in_hold_mode ? 1.0F : (1.0F - anim.hold_exit_progress);
  111. controller.kneel(t * k_kneel_depth_multiplier);
  112. controller.lean(QVector3D(0.0F, 0.0F, 1.0F),
  113. t * k_lean_amount_multiplier);
  114. QVector3D const hold_hand_r(
  115. bow_x + 0.03F, controller.get_shoulder_y(false) + 0.30F, 0.55F);
  116. QVector3D const hold_hand_l(
  117. bow_x - 0.02F, controller.get_shoulder_y(true) + 0.12F, 0.55F);
  118. QVector3D const normal_hand_r(bow_x + 0.03F - arm_asymmetry,
  119. HP::SHOULDER_Y + 0.05F + arm_height_jitter,
  120. 0.55F);
  121. QVector3D const normal_hand_l(
  122. bow_x - 0.02F + arm_asymmetry * 0.5F,
  123. HP::SHOULDER_Y + 0.12F + arm_height_jitter * 0.8F, 0.50F);
  124. QVector3D const blended_hand_r =
  125. normal_hand_r * (1.0F - t) + hold_hand_r * t;
  126. QVector3D const blended_hand_l =
  127. normal_hand_l * (1.0F - t) + hold_hand_l * t;
  128. controller.place_hand_at(false, blended_hand_r);
  129. controller.place_hand_at(true, blended_hand_l);
  130. } else {
  131. QVector3D const idle_hand_r(bow_x + 0.03F - arm_asymmetry,
  132. HP::SHOULDER_Y + 0.05F + arm_height_jitter,
  133. 0.55F);
  134. QVector3D const idle_hand_l(
  135. bow_x - 0.05F + arm_asymmetry * 0.5F,
  136. HP::SHOULDER_Y + 0.14F + arm_height_jitter * 0.8F, 0.48F);
  137. controller.place_hand_at(false, idle_hand_r);
  138. controller.place_hand_at(true, idle_hand_l);
  139. }
  140. if (anim.is_attacking && !anim.is_in_hold_mode) {
  141. float const attack_phase =
  142. std::fmod(anim_ctx.attack_phase * ARCHER_INV_ATTACK_CYCLE_TIME, 1.0F);
  143. if (anim.is_melee) {
  144. controller.melee_strike(attack_phase);
  145. } else {
  146. controller.aim_bow(attack_phase);
  147. }
  148. }
  149. }
  150. void add_attachments(const DrawContext &ctx, const HumanoidVariant &v,
  151. const HumanoidPose &pose,
  152. const HumanoidAnimationContext &anim_ctx,
  153. ISubmitter &out) const override {
  154. using HP = HumanProportions;
  155. auto const &style = resolve_style(ctx);
  156. const AnimationInputs &anim = anim_ctx.inputs;
  157. QVector3D team_tint = resolve_team_tint(ctx);
  158. uint32_t seed = 0U;
  159. if (ctx.entity != nullptr) {
  160. auto *unit = ctx.entity->get_component<Engine::Core::UnitComponent>();
  161. if (unit != nullptr) {
  162. seed ^= uint32_t(unit->owner_id * 2654435761U);
  163. }
  164. seed ^= uint32_t(reinterpret_cast<uintptr_t>(ctx.entity) & 0xFFFFFFFFU);
  165. }
  166. auto tint = [&](float k) {
  167. return QVector3D(clamp01(team_tint.x() * k), clamp01(team_tint.y() * k),
  168. clamp01(team_tint.z() * k));
  169. };
  170. QVector3D const fletch = tint(0.9F);
  171. auto &registry = EquipmentRegistry::instance();
  172. if (style.show_cape) {
  173. auto cloak = registry.get(EquipmentCategory::Armor, "cloak_carthage");
  174. if (cloak) {
  175. CloakConfig cloak_config;
  176. if (style.cape_color) {
  177. cloak_config.primary_color = *style.cape_color;
  178. } else {
  179. cloak_config.primary_color = QVector3D(0.14F, 0.38F, 0.54F);
  180. }
  181. cloak_config.trim_color = v.palette.metal;
  182. auto *cloak_renderer = dynamic_cast<CloakRenderer *>(cloak.get());
  183. if (cloak_renderer) {
  184. cloak_renderer->set_config(cloak_config);
  185. }
  186. cloak->render(ctx, pose.body_frames, v.palette, anim_ctx, out);
  187. }
  188. }
  189. auto quiver = registry.get(EquipmentCategory::Weapon, "quiver");
  190. if (quiver) {
  191. QuiverRenderConfig quiver_config;
  192. quiver_config.fletching_color = fletch;
  193. quiver_config.quiver_radius = HP::HEAD_RADIUS * 0.45F;
  194. auto *quiver_renderer = dynamic_cast<QuiverRenderer *>(quiver.get());
  195. if (quiver_renderer) {
  196. quiver_renderer->set_config(quiver_config);
  197. }
  198. quiver->render(ctx, pose.body_frames, v.palette, anim_ctx, out);
  199. }
  200. auto bow = registry.get(EquipmentCategory::Weapon, "bow_carthage");
  201. if (bow) {
  202. BowRenderConfig bow_config;
  203. bow_config.string_color = QVector3D(0.30F, 0.30F, 0.32F);
  204. bow_config.metal_color =
  205. Render::Geom::clamp_vec_01(v.palette.metal * 1.15F);
  206. bow_config.fletching_color = fletch;
  207. bow_config.bow_top_y = HP::SHOULDER_Y + 0.55F;
  208. bow_config.bow_bot_y = HP::WAIST_Y - 0.25F;
  209. bow_config.bow_x = 0.0F;
  210. bow_config.arrow_visibility = ArrowVisibility::IdleAndAttackCycle;
  211. if (style.bow_string_color) {
  212. bow_config.string_color = saturate_color(*style.bow_string_color);
  213. }
  214. if (style.fletching_color) {
  215. bow_config.fletching_color = saturate_color(*style.fletching_color);
  216. }
  217. auto *bow_renderer = dynamic_cast<BowRenderer *>(bow.get());
  218. if (bow_renderer) {
  219. bow_renderer->set_config(bow_config);
  220. }
  221. bow->render(ctx, pose.body_frames, v.palette, anim_ctx, out);
  222. }
  223. }
  224. void draw_helmet(const DrawContext &ctx, const HumanoidVariant &v,
  225. const HumanoidPose &pose, ISubmitter &out) const override {
  226. using HP = HumanProportions;
  227. auto const &style = resolve_style(ctx);
  228. auto &registry = EquipmentRegistry::instance();
  229. HumanoidAnimationContext anim_ctx{};
  230. if (!style.show_helmet) {
  231. if (style.attachment_profile == std::string(k_attachment_headwrap)) {
  232. auto headwrap = registry.get(EquipmentCategory::Helmet, "headwrap");
  233. if (headwrap) {
  234. headwrap->render(ctx, pose.body_frames, v.palette, anim_ctx, out);
  235. }
  236. }
  237. return;
  238. }
  239. auto helmet = registry.get(EquipmentCategory::Helmet, "carthage_light");
  240. if (helmet) {
  241. helmet->render(ctx, pose.body_frames, v.palette, anim_ctx, out);
  242. }
  243. }
  244. void draw_armor(const DrawContext &ctx, const HumanoidVariant &v,
  245. const HumanoidPose &pose,
  246. const HumanoidAnimationContext &anim,
  247. ISubmitter &out) const override {
  248. if (resolve_style(ctx).show_armor) {
  249. auto &registry = EquipmentRegistry::instance();
  250. auto const &style = resolve_style(ctx);
  251. std::string armor_key =
  252. style.armor_id.empty() ? "armor_light_carthage" : style.armor_id;
  253. auto armor = registry.get(EquipmentCategory::Armor, armor_key);
  254. if (armor) {
  255. armor->render(ctx, pose.body_frames, v.palette, anim, out);
  256. }
  257. }
  258. }
  259. private:
  260. auto
  261. resolve_style(const DrawContext &ctx) const -> const ArcherStyleConfig & {
  262. ensure_archer_styles_registered();
  263. auto &styles = style_registry();
  264. std::string nation_id;
  265. if (ctx.entity != nullptr) {
  266. if (auto *unit =
  267. ctx.entity->get_component<Engine::Core::UnitComponent>()) {
  268. nation_id = Game::Systems::nation_id_to_string(unit->nation_id);
  269. }
  270. }
  271. if (!nation_id.empty()) {
  272. auto it = styles.find(nation_id);
  273. if (it != styles.end()) {
  274. return it->second;
  275. }
  276. }
  277. auto fallback = styles.find(std::string(k_default_style_key));
  278. if (fallback != styles.end()) {
  279. return fallback->second;
  280. }
  281. static const ArcherStyleConfig default_style{};
  282. return default_style;
  283. }
  284. public:
  285. auto resolve_shader_key(const DrawContext &ctx) const -> QString {
  286. const ArcherStyleConfig &style = resolve_style(ctx);
  287. if (!style.shader_id.empty()) {
  288. return QString::fromStdString(style.shader_id);
  289. }
  290. return QStringLiteral("archer");
  291. }
  292. private:
  293. void apply_palette_overrides(const ArcherStyleConfig &style,
  294. const QVector3D &team_tint,
  295. HumanoidVariant &variant) const {
  296. auto apply_color = [&](const std::optional<QVector3D> &override_color,
  297. QVector3D &target) {
  298. target = mix_palette_color(target, override_color, team_tint,
  299. k_team_mix_weight, k_style_mix_weight);
  300. };
  301. apply_color(style.cloth_color, variant.palette.cloth);
  302. apply_color(style.leather_color, variant.palette.leather);
  303. apply_color(style.leather_dark_color, variant.palette.leather_dark);
  304. apply_color(style.metal_color, variant.palette.metal);
  305. apply_color(style.wood_color, variant.palette.wood);
  306. }
  307. };
  308. void register_archer_renderer(Render::GL::EntityRendererRegistry &registry) {
  309. ensure_archer_styles_registered();
  310. static ArcherRenderer const renderer;
  311. registry.register_renderer(
  312. "troops/carthage/archer", [](const DrawContext &ctx, ISubmitter &out) {
  313. static ArcherRenderer const static_renderer;
  314. Shader *archer_shader = nullptr;
  315. if (ctx.backend != nullptr) {
  316. QString shader_key = static_renderer.resolve_shader_key(ctx);
  317. archer_shader = ctx.backend->shader(shader_key);
  318. if ((archer_shader == nullptr) &&
  319. shader_key == QStringLiteral("archer_carthage")) {
  320. archer_shader = ctx.backend->get_or_load_shader(
  321. shader_key,
  322. QStringLiteral(":/assets/shaders/archer_carthage.vert"),
  323. QStringLiteral(":/assets/shaders/archer_carthage.frag"));
  324. }
  325. if (archer_shader == nullptr) {
  326. archer_shader = ctx.backend->shader(QStringLiteral("archer"));
  327. }
  328. }
  329. auto *scene_renderer = dynamic_cast<Renderer *>(&out);
  330. if ((scene_renderer != nullptr) && (archer_shader != nullptr)) {
  331. scene_renderer->set_current_shader(archer_shader);
  332. }
  333. static_renderer.render(ctx, out);
  334. if (scene_renderer != nullptr) {
  335. scene_renderer->set_current_shader(nullptr);
  336. }
  337. });
  338. }
  339. } // namespace Render::GL::Carthage