healer_renderer.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. #include "healer_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 "healer_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, HealerStyleConfig> & {
  42. static std::unordered_map<std::string, HealerStyleConfig> styles;
  43. return styles;
  44. }
  45. void ensure_healer_styles_registered() {
  46. static const bool registered = []() {
  47. register_carthage_healer_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_healer_style(const std::string &nation_id,
  56. const HealerStyleConfig &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 HealerRenderer : public HumanoidRendererBase {
  64. public:
  65. auto get_proportion_scaling() const -> QVector3D override {
  66. return {0.88F, 0.99F, 0.90F};
  67. }
  68. void get_variant(const DrawContext &ctx, uint32_t seed,
  69. HumanoidVariant &v) const override {
  70. QVector3D const team_tint = resolve_team_tint(ctx);
  71. v.palette = make_humanoid_palette(team_tint, seed);
  72. auto const &style = resolve_style(ctx);
  73. apply_palette_overrides(style, team_tint, v);
  74. auto nextRand = [](uint32_t &s) -> float {
  75. s = s * 1664525U + 1013904223U;
  76. return float(s & 0x7FFFFFU) / float(0x7FFFFFU);
  77. };
  78. uint32_t beard_seed = seed ^ 0x0EA101U;
  79. bool wants_beard = style.force_beard;
  80. if (!wants_beard) {
  81. float const beard_roll = nextRand(beard_seed);
  82. wants_beard = (beard_roll < 0.85F);
  83. }
  84. if (wants_beard) {
  85. float const style_roll = nextRand(beard_seed);
  86. if (style_roll < 0.45F) {
  87. v.facial_hair.style = FacialHairStyle::ShortBeard;
  88. v.facial_hair.length = 0.8F + nextRand(beard_seed) * 0.4F;
  89. } else if (style_roll < 0.75F) {
  90. v.facial_hair.style = FacialHairStyle::FullBeard;
  91. v.facial_hair.length = 0.9F + nextRand(beard_seed) * 0.5F;
  92. } else if (style_roll < 0.90F) {
  93. v.facial_hair.style = FacialHairStyle::Goatee;
  94. v.facial_hair.length = 0.7F + nextRand(beard_seed) * 0.4F;
  95. } else {
  96. v.facial_hair.style = FacialHairStyle::MustacheAndBeard;
  97. v.facial_hair.length = 1.0F + nextRand(beard_seed) * 0.4F;
  98. }
  99. float const color_roll = nextRand(beard_seed);
  100. if (color_roll < 0.55F) {
  101. v.facial_hair.color = QVector3D(0.12F + nextRand(beard_seed) * 0.08F,
  102. 0.10F + nextRand(beard_seed) * 0.06F,
  103. 0.08F + nextRand(beard_seed) * 0.05F);
  104. } else if (color_roll < 0.80F) {
  105. v.facial_hair.color = QVector3D(0.22F + nextRand(beard_seed) * 0.10F,
  106. 0.17F + nextRand(beard_seed) * 0.08F,
  107. 0.12F + nextRand(beard_seed) * 0.06F);
  108. } else {
  109. v.facial_hair.color = QVector3D(0.35F + nextRand(beard_seed) * 0.15F,
  110. 0.32F + nextRand(beard_seed) * 0.12F,
  111. 0.30F + nextRand(beard_seed) * 0.10F);
  112. v.facial_hair.greyness = 0.3F + nextRand(beard_seed) * 0.4F;
  113. }
  114. v.facial_hair.thickness = 0.85F + nextRand(beard_seed) * 0.25F;
  115. v.facial_hair.coverage = 0.80F + nextRand(beard_seed) * 0.20F;
  116. }
  117. }
  118. void customize_pose(const DrawContext &,
  119. const HumanoidAnimationContext &anim_ctx, uint32_t seed,
  120. HumanoidPose &pose) const override {
  121. using HP = HumanProportions;
  122. const AnimationInputs &anim = anim_ctx.inputs;
  123. HumanoidPoseController controller(pose, anim_ctx);
  124. float const arm_height_jitter = (hash_01(seed ^ 0xABCDU) - 0.5F) * 0.03F;
  125. float const arm_asymmetry = (hash_01(seed ^ 0xDEF0U) - 0.5F) * 0.04F;
  126. if (anim.is_healing) {
  127. float const healing_time = anim.time * 2.2F;
  128. float const sway_phase = std::sin(healing_time);
  129. float const sway_phase_offset = std::sin(healing_time + 0.7F);
  130. float const high_arm_height =
  131. HP::SHOULDER_Y + 0.18F + 0.04F * sway_phase + arm_height_jitter;
  132. float const extended_arm_height = HP::SHOULDER_Y + 0.05F +
  133. 0.02F * sway_phase_offset +
  134. arm_height_jitter;
  135. float const circle_x = 0.03F * std::sin(healing_time * 0.8F);
  136. float const circle_z = 0.02F * std::cos(healing_time * 0.8F);
  137. QVector3D const heal_hand_l(-0.08F + arm_asymmetry + circle_x,
  138. high_arm_height, 0.28F + circle_z);
  139. QVector3D const heal_hand_r(0.15F - arm_asymmetry * 0.5F,
  140. extended_arm_height,
  141. 0.40F + 0.03F * sway_phase_offset);
  142. controller.place_hand_at(true, heal_hand_l);
  143. controller.place_hand_at(false, heal_hand_r);
  144. float target_dir_x = 0.0F;
  145. float target_dir_z = 1.0F;
  146. float const target_dist =
  147. std::sqrt(anim.healing_target_dx * anim.healing_target_dx +
  148. anim.healing_target_dz * anim.healing_target_dz);
  149. if (target_dist > 0.01F) {
  150. target_dir_x = anim.healing_target_dx / target_dist;
  151. target_dir_z = anim.healing_target_dz / target_dist;
  152. }
  153. QVector3D const look_dir(target_dir_x, 0.0F, target_dir_z);
  154. QVector3D const head_focus =
  155. pose.head_pos +
  156. QVector3D(look_dir.x() * 0.18F, 0.0F, look_dir.z() * 0.45F);
  157. controller.look_at(head_focus);
  158. } else {
  159. QVector3D const idle_hand_l(-0.10F + arm_asymmetry,
  160. HP::SHOULDER_Y + 0.10F + arm_height_jitter,
  161. 0.45F);
  162. QVector3D const idle_hand_r(
  163. 0.10F - arm_asymmetry * 0.5F,
  164. HP::SHOULDER_Y + 0.10F + arm_height_jitter * 0.8F, 0.45F);
  165. controller.place_hand_at(true, idle_hand_l);
  166. controller.place_hand_at(false, idle_hand_r);
  167. }
  168. }
  169. void add_attachments(const DrawContext &ctx, const HumanoidVariant &v,
  170. const HumanoidPose &pose,
  171. const HumanoidAnimationContext &anim_ctx,
  172. ISubmitter &out) const override {}
  173. void draw_helmet(const DrawContext &ctx, const HumanoidVariant &v,
  174. const HumanoidPose &pose, ISubmitter &out) const override {
  175. if (!resolve_style(ctx).show_helmet) {
  176. return;
  177. }
  178. auto &registry = EquipmentRegistry::instance();
  179. auto helmet = registry.get(EquipmentCategory::Helmet, "carthage_light");
  180. if (helmet) {
  181. HumanoidAnimationContext anim_ctx{};
  182. helmet->render(ctx, pose.body_frames, v.palette, anim_ctx, out);
  183. }
  184. }
  185. void draw_armor(const DrawContext &ctx, const HumanoidVariant &v,
  186. const HumanoidPose &pose,
  187. const HumanoidAnimationContext &anim,
  188. ISubmitter &out) const override {
  189. if (resolve_style(ctx).show_armor) {
  190. auto &registry = EquipmentRegistry::instance();
  191. auto armor =
  192. registry.get(EquipmentCategory::Armor, "carthage_light_armor");
  193. if (armor) {
  194. armor->render(ctx, pose.body_frames, v.palette, anim, out);
  195. return;
  196. }
  197. }
  198. draw_healer_robes(ctx, v, pose, out);
  199. }
  200. void draw_healer_robes(const DrawContext &ctx, const HumanoidVariant &v,
  201. const HumanoidPose &pose, ISubmitter &out) const {
  202. using HP = HumanProportions;
  203. const BodyFrames &frames = pose.body_frames;
  204. const AttachmentFrame &torso = frames.torso;
  205. const AttachmentFrame &waist = frames.waist;
  206. if (torso.radius <= 0.0F) {
  207. return;
  208. }
  209. QVector3D const team_tint = resolve_team_tint(ctx);
  210. QVector3D const robe_cream(0.46F, 0.46F, 0.48F);
  211. QVector3D const robe_light(0.42F, 0.42F, 0.44F);
  212. QVector3D const robe_tan(0.38F, 0.38F, 0.40F);
  213. QVector3D const purple_tyrian(0.05F, 0.05F, 0.05F);
  214. QVector3D const purple_dark(0.05F, 0.05F, 0.05F);
  215. QVector3D const bronze_color(0.78F, 0.58F, 0.32F);
  216. const QVector3D &origin = torso.origin;
  217. const QVector3D &right = torso.right;
  218. const QVector3D &up = torso.up;
  219. const QVector3D &forward = torso.forward;
  220. constexpr int k_mat_tunic = 1;
  221. constexpr int k_mat_purple_trim = 2;
  222. constexpr int k_mat_tools = 4;
  223. float const torso_r = torso.radius * 1.02F;
  224. float const torso_depth =
  225. (torso.depth > 0.0F) ? torso.depth * 0.88F : torso.radius * 0.82F;
  226. float const y_shoulder = origin.y() + 0.040F;
  227. float const y_waist = waist.origin.y();
  228. constexpr int segments = 12;
  229. constexpr float pi = std::numbers::pi_v<float>;
  230. auto drawRobeRing = [&](float y_pos, float width, float depth,
  231. const QVector3D &color, float thickness,
  232. int materialId) {
  233. for (int i = 0; i < segments; ++i) {
  234. float const angle1 = (static_cast<float>(i) / segments) * 2.0F * pi;
  235. float const angle2 = (static_cast<float>(i + 1) / segments) * 2.0F * pi;
  236. float const sin1 = std::sin(angle1);
  237. float const cos1 = std::cos(angle1);
  238. float const sin2 = std::sin(angle2);
  239. float const cos2 = std::cos(angle2);
  240. float const r1 =
  241. (std::abs(cos1) * depth + (1.0F - std::abs(cos1)) * width);
  242. float const r2 =
  243. (std::abs(cos2) * depth + (1.0F - std::abs(cos2)) * width);
  244. QVector3D const p1 = origin + right * (r1 * sin1) +
  245. forward * (r1 * cos1) + up * (y_pos - origin.y());
  246. QVector3D const p2 = origin + right * (r2 * sin2) +
  247. forward * (r2 * cos2) + up * (y_pos - origin.y());
  248. out.mesh(get_unit_cylinder(),
  249. cylinder_between(ctx.model, p1, p2, thickness), color, nullptr,
  250. 1.0F, materialId);
  251. }
  252. };
  253. drawRobeRing(y_shoulder - 0.00F, torso_r * 1.22F, torso_depth * 1.12F,
  254. robe_cream, 0.036F, k_mat_tunic);
  255. drawRobeRing(y_shoulder - 0.05F, torso_r * 1.30F, torso_depth * 1.18F,
  256. robe_cream, 0.038F, k_mat_tunic);
  257. drawRobeRing(y_shoulder - 0.09F, torso_r * 1.12F, torso_depth * 1.00F,
  258. robe_cream, 0.032F, k_mat_tunic);
  259. float const torso_fill_top = y_shoulder - 0.12F;
  260. float const torso_fill_bot = y_waist + 0.04F;
  261. constexpr int torso_fill_layers = 8;
  262. for (int i = 0; i < torso_fill_layers; ++i) {
  263. float const t =
  264. static_cast<float>(i) / static_cast<float>(torso_fill_layers - 1);
  265. float const y = torso_fill_top + (torso_fill_bot - torso_fill_top) * t;
  266. float const width = torso_r * (1.08F - t * 0.22F);
  267. float const depth = torso_depth * (1.00F - t * 0.18F);
  268. float const thickness = 0.030F - t * 0.010F;
  269. QVector3D const c =
  270. (t < 0.35F) ? robe_cream : robe_light * (1.0F - (t - 0.35F) * 0.3F);
  271. drawRobeRing(y, width, depth, c, thickness, k_mat_tunic);
  272. }
  273. float const skirt_flare = 1.40F;
  274. constexpr int skirt_layers = 9;
  275. for (int layer = 0; layer < skirt_layers; ++layer) {
  276. float const t =
  277. static_cast<float>(layer) / static_cast<float>(skirt_layers - 1);
  278. float const y = y_waist - t * 0.32F;
  279. float const flare = 1.0F + t * (skirt_flare - 1.0F);
  280. QVector3D const skirt_color = robe_cream * (1.0F - t * 0.08F);
  281. drawRobeRing(y, torso_r * 0.90F * flare, torso_depth * 0.84F * flare,
  282. skirt_color, 0.022F + t * 0.012F, k_mat_tunic);
  283. }
  284. float const sash_y = y_waist + 0.01F;
  285. QVector3D const sash_top = origin + up * (sash_y + 0.028F - origin.y());
  286. QVector3D const sash_bot = origin + up * (sash_y - 0.028F - origin.y());
  287. out.mesh(get_unit_cylinder(),
  288. cylinder_between(ctx.model, sash_bot, sash_top, torso_r * 0.99F),
  289. purple_tyrian, nullptr, 1.0F, k_mat_purple_trim);
  290. out.mesh(get_unit_cylinder(),
  291. cylinder_between(ctx.model, sash_top, sash_top - up * 0.006F,
  292. torso_r * 1.02F),
  293. team_tint, nullptr, 1.0F, k_mat_tools);
  294. out.mesh(get_unit_cylinder(),
  295. cylinder_between(ctx.model, sash_bot + up * 0.006F, sash_bot,
  296. torso_r * 1.02F),
  297. team_tint, nullptr, 1.0F, k_mat_tools);
  298. QVector3D const sash_hang_start =
  299. origin + right * (torso_r * 0.3F) + up * (sash_y - origin.y());
  300. QVector3D const sash_hang_end =
  301. sash_hang_start - up * 0.12F + forward * 0.02F;
  302. out.mesh(
  303. get_unit_cylinder(),
  304. cylinder_between(ctx.model, sash_hang_start, sash_hang_end, 0.018F),
  305. purple_dark, nullptr, 1.0F, k_mat_purple_trim);
  306. out.mesh(get_unit_sphere(),
  307. sphere_at(ctx.model, sash_hang_end - up * 0.01F, 0.015F),
  308. bronze_color, nullptr, 1.0F, k_mat_tools);
  309. float const neck_y = y_shoulder + 0.04F;
  310. QVector3D const neck_center = origin + up * (neck_y - origin.y());
  311. out.mesh(get_unit_cylinder(),
  312. cylinder_between(ctx.model, neck_center - up * 0.012F,
  313. neck_center + up * 0.012F,
  314. HP::NECK_RADIUS * 1.7F),
  315. robe_tan, nullptr, 1.0F, k_mat_tunic);
  316. out.mesh(get_unit_cylinder(),
  317. cylinder_between(ctx.model, neck_center + up * 0.010F,
  318. neck_center + up * 0.018F,
  319. HP::NECK_RADIUS * 2.0F),
  320. purple_tyrian * 0.9F, nullptr, 1.0F, k_mat_purple_trim);
  321. auto drawFlowingSleeve = [&](const QVector3D &shoulder_pos,
  322. const QVector3D &outward) {
  323. QVector3D const backward = -forward;
  324. QVector3D const anchor = shoulder_pos + up * 0.070F + backward * 0.020F;
  325. for (int i = 0; i < 5; ++i) {
  326. float const t = static_cast<float>(i) / 5.0F;
  327. QVector3D const sleeve_pos = anchor + outward * (0.014F + t * 0.030F) +
  328. forward * (-0.020F + t * 0.065F) -
  329. up * (t * 0.05F);
  330. float const sleeve_r = HP::UPPER_ARM_R * (1.55F - t * 0.08F);
  331. QVector3D const sleeve_color = robe_cream * (1.0F - t * 0.04F);
  332. out.mesh(get_unit_sphere(), sphere_at(ctx.model, sleeve_pos, sleeve_r),
  333. sleeve_color, nullptr, 1.0F, k_mat_tunic);
  334. }
  335. QVector3D const cuff_pos =
  336. anchor + outward * 0.055F + forward * 0.040F - up * 0.05F;
  337. out.mesh(get_unit_sphere(),
  338. sphere_at(ctx.model, cuff_pos, HP::UPPER_ARM_R * 1.15F),
  339. purple_tyrian * 0.85F, nullptr, 1.0F, k_mat_purple_trim);
  340. };
  341. drawFlowingSleeve(frames.shoulder_l.origin, -right);
  342. drawFlowingSleeve(frames.shoulder_r.origin, right);
  343. QVector3D const pendant_pos = origin + forward * (torso_depth * 0.6F) +
  344. up * (y_shoulder - 0.06F - origin.y());
  345. out.mesh(get_unit_sphere(), sphere_at(ctx.model, pendant_pos, 0.022F),
  346. bronze_color, nullptr, 1.0F, k_mat_tools);
  347. out.mesh(get_unit_cylinder(),
  348. cylinder_between(ctx.model,
  349. neck_center + forward * (torso_depth * 0.3F),
  350. pendant_pos + up * 0.01F, 0.006F),
  351. bronze_color * 0.85F, nullptr, 1.0F, k_mat_tools);
  352. }
  353. private:
  354. auto
  355. resolve_style(const DrawContext &ctx) const -> const HealerStyleConfig & {
  356. ensure_healer_styles_registered();
  357. auto &styles = style_registry();
  358. std::string nation_id;
  359. if (ctx.entity != nullptr) {
  360. if (auto *unit =
  361. ctx.entity->get_component<Engine::Core::UnitComponent>()) {
  362. nation_id = Game::Systems::nation_id_to_string(unit->nation_id);
  363. }
  364. }
  365. if (!nation_id.empty()) {
  366. auto it = styles.find(nation_id);
  367. if (it != styles.end()) {
  368. return it->second;
  369. }
  370. }
  371. auto fallback = styles.find(std::string(k_default_style_key));
  372. if (fallback != styles.end()) {
  373. return fallback->second;
  374. }
  375. static const HealerStyleConfig default_style{};
  376. return default_style;
  377. }
  378. public:
  379. auto resolve_shader_key(const DrawContext &ctx) const -> QString {
  380. const HealerStyleConfig &style = resolve_style(ctx);
  381. if (!style.shader_id.empty()) {
  382. return QString::fromStdString(style.shader_id);
  383. }
  384. return QStringLiteral("healer");
  385. }
  386. private:
  387. void apply_palette_overrides(const HealerStyleConfig &style,
  388. const QVector3D &team_tint,
  389. HumanoidVariant &variant) const {
  390. auto apply_color = [&](const std::optional<QVector3D> &override_color,
  391. QVector3D &target, float team_weight,
  392. float style_weight) {
  393. target = mix_palette_color(target, override_color, team_tint, team_weight,
  394. style_weight);
  395. };
  396. constexpr float k_skin_team_mix_weight = 0.0F;
  397. constexpr float k_skin_style_mix_weight = 1.0F;
  398. constexpr float k_cloth_team_mix_weight = 0.0F;
  399. constexpr float k_cloth_style_mix_weight = 1.0F;
  400. apply_color(style.skin_color, variant.palette.skin, k_skin_team_mix_weight,
  401. k_skin_style_mix_weight);
  402. apply_color(style.cloth_color, variant.palette.cloth,
  403. k_cloth_team_mix_weight, k_cloth_style_mix_weight);
  404. apply_color(style.leather_color, variant.palette.leather, k_team_mix_weight,
  405. k_style_mix_weight);
  406. apply_color(style.leather_dark_color, variant.palette.leather_dark,
  407. k_team_mix_weight, k_style_mix_weight);
  408. apply_color(style.metal_color, variant.palette.metal, k_team_mix_weight,
  409. k_style_mix_weight);
  410. apply_color(style.wood_color, variant.palette.wood, k_team_mix_weight,
  411. k_style_mix_weight);
  412. }
  413. };
  414. void register_healer_renderer(Render::GL::EntityRendererRegistry &registry) {
  415. ensure_healer_styles_registered();
  416. static HealerRenderer const renderer;
  417. registry.register_renderer(
  418. "troops/carthage/healer", [](const DrawContext &ctx, ISubmitter &out) {
  419. static HealerRenderer const static_renderer;
  420. Shader *healer_shader = nullptr;
  421. if (ctx.backend != nullptr) {
  422. QString shader_key = static_renderer.resolve_shader_key(ctx);
  423. healer_shader = ctx.backend->shader(shader_key);
  424. if (healer_shader == nullptr) {
  425. healer_shader = ctx.backend->shader(QStringLiteral("healer"));
  426. }
  427. }
  428. auto *scene_renderer = dynamic_cast<Renderer *>(&out);
  429. if ((scene_renderer != nullptr) && (healer_shader != nullptr)) {
  430. scene_renderer->set_current_shader(healer_shader);
  431. }
  432. static_renderer.render(ctx, out);
  433. if (scene_renderer != nullptr) {
  434. scene_renderer->set_current_shader(nullptr);
  435. }
  436. });
  437. }
  438. } // namespace Render::GL::Carthage