rig.cpp 102 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858
  1. #include "rig.h"
  2. #include "../../game/core/component.h"
  3. #include "../../game/core/entity.h"
  4. #include "../../game/core/world.h"
  5. #include "../../game/map/terrain_service.h"
  6. #include "../../game/systems/nation_id.h"
  7. #include "../../game/units/spawn_type.h"
  8. #include "../../game/units/troop_config.h"
  9. #include "../../game/visuals/team_colors.h"
  10. #include "../entity/registry.h"
  11. #include "../geom/affine_matrix.h"
  12. #include "../geom/math_utils.h"
  13. #include "../geom/transforms.h"
  14. #include "../gl/backend.h"
  15. #include "../gl/camera.h"
  16. #include "../gl/humanoid/animation/animation_inputs.h"
  17. #include "../gl/humanoid/animation/gait.h"
  18. #include "../gl/humanoid/humanoid_constants.h"
  19. #include "../gl/primitives.h"
  20. #include "../gl/render_constants.h"
  21. #include "../gl/resources.h"
  22. #include "../palette.h"
  23. #include "../pose_palette_cache.h"
  24. #include "../scene_renderer.h"
  25. #include "../submitter.h"
  26. #include "../template_cache.h"
  27. #include "../visibility_budget.h"
  28. #include "formation_calculator.h"
  29. #include "humanoid_math.h"
  30. #include "pose_controller.h"
  31. #include <QMatrix4x4>
  32. #include <QVector2D>
  33. #include <QVector4D>
  34. #include <QtMath>
  35. #include <algorithm>
  36. #include <array>
  37. #include <cmath>
  38. #include <cstdint>
  39. #include <functional>
  40. #include <limits>
  41. #include <numbers>
  42. #include <unordered_map>
  43. #include <vector>
  44. namespace Render::GL {
  45. using namespace Render::GL::Geometry;
  46. using Render::Geom::capsule_between;
  47. using Render::Geom::cone_from_to;
  48. using Render::Geom::cylinder_between;
  49. using Render::Geom::sphere_at;
  50. namespace {
  51. constexpr float k_shadow_size_infantry = 0.16F;
  52. constexpr float k_shadow_size_mounted = 0.35F;
  53. constexpr float k_run_extra_foot_lift = 0.08F;
  54. constexpr float k_run_stride_enhancement = 0.15F;
  55. struct CachedPoseEntry {
  56. HumanoidPose pose;
  57. VariationParams variation;
  58. uint32_t frame_number{0};
  59. bool was_moving{false};
  60. };
  61. using PoseCacheKey = uint64_t;
  62. static std::unordered_map<PoseCacheKey, CachedPoseEntry> s_pose_cache;
  63. static uint32_t s_current_frame = 0;
  64. constexpr uint32_t k_pose_cache_max_age = 300;
  65. struct SoldierLayout {
  66. float offset_x{0.0F};
  67. float offset_z{0.0F};
  68. float vertical_jitter{0.0F};
  69. float yaw_offset{0.0F};
  70. float phase_offset{0.0F};
  71. std::uint32_t inst_seed{0};
  72. };
  73. struct UnitLayoutCacheEntry {
  74. std::vector<SoldierLayout> soldiers;
  75. FormationParams formation{};
  76. FormationCalculatorFactory::Nation nation{
  77. FormationCalculatorFactory::Nation::Roman};
  78. FormationCalculatorFactory::UnitCategory category{
  79. FormationCalculatorFactory::UnitCategory::Infantry};
  80. int rows{0};
  81. int cols{0};
  82. std::uint32_t seed{0};
  83. std::uint32_t frame_number{0};
  84. };
  85. static std::unordered_map<std::uintptr_t, UnitLayoutCacheEntry> s_layout_cache;
  86. constexpr uint32_t k_layout_cache_max_age = 600;
  87. inline auto make_pose_cache_key(uintptr_t entity_ptr,
  88. int soldier_idx) -> PoseCacheKey {
  89. return (static_cast<uint64_t>(entity_ptr) << 16) |
  90. static_cast<uint64_t>(soldier_idx & 0xFFFF);
  91. }
  92. static HumanoidRenderStats s_render_stats;
  93. constexpr float k_shadow_ground_offset = 0.02F;
  94. constexpr float k_shadow_base_alpha = 0.24F;
  95. constexpr QVector3D k_shadow_light_dir(0.4F, 1.0F, 0.25F);
  96. constexpr float k_temporal_skip_distance_reduced = 35.0F;
  97. constexpr float k_temporal_skip_distance_minimal = 45.0F;
  98. constexpr uint32_t k_temporal_skip_period_reduced = 2;
  99. constexpr uint32_t k_temporal_skip_period_minimal = 3;
  100. inline auto should_render_temporal(uint32_t frame, uint32_t seed,
  101. uint32_t period) -> bool {
  102. if (period <= 1) {
  103. return true;
  104. }
  105. return ((frame + seed) % period) == 0U;
  106. }
  107. inline auto make_yaw_scale_model(float cos_yaw, float sin_yaw, float scale_x,
  108. float scale_y, float scale_z,
  109. const QVector3D &translation) -> QMatrix4x4 {
  110. QMatrix4x4 m;
  111. float *d = m.data();
  112. d[0] = cos_yaw * scale_x;
  113. d[1] = 0.0F;
  114. d[2] = -sin_yaw * scale_x;
  115. d[3] = 0.0F;
  116. d[4] = 0.0F;
  117. d[5] = scale_y;
  118. d[6] = 0.0F;
  119. d[7] = 0.0F;
  120. d[8] = sin_yaw * scale_z;
  121. d[9] = 0.0F;
  122. d[10] = cos_yaw * scale_z;
  123. d[11] = 0.0F;
  124. d[12] = translation.x();
  125. d[13] = translation.y();
  126. d[14] = translation.z();
  127. d[15] = 1.0F;
  128. return m;
  129. }
  130. inline auto
  131. resolve_layout_seed(const Engine::Core::UnitComponent *unit_comp,
  132. const Engine::Core::Entity *entity) -> std::uint32_t {
  133. std::uint32_t seed = 0U;
  134. if (unit_comp != nullptr) {
  135. seed ^= std::uint32_t(unit_comp->owner_id * 2654435761U);
  136. }
  137. if (entity != nullptr) {
  138. seed ^= std::uint32_t(reinterpret_cast<uintptr_t>(entity) & 0xFFFFFFFFU);
  139. }
  140. return seed;
  141. }
  142. inline auto resolve_variant_key_from_seed(std::uint32_t seed) -> std::uint8_t {
  143. std::uint32_t v = seed ^ (seed >> 16);
  144. return static_cast<std::uint8_t>(v % k_template_variant_count);
  145. }
  146. inline auto
  147. resolve_attack_variant_from_seed(std::uint32_t seed) -> std::uint8_t {
  148. return static_cast<std::uint8_t>(seed % 3U);
  149. }
  150. inline auto resolve_variant_seed(const Engine::Core::UnitComponent *unit_comp,
  151. std::uint8_t variant_key) -> std::uint32_t {
  152. std::uint32_t seed = 0U;
  153. if (unit_comp != nullptr) {
  154. seed ^= static_cast<std::uint32_t>(unit_comp->spawn_type) * 2654435761U;
  155. seed ^= static_cast<std::uint32_t>(unit_comp->owner_id) * 1013904223U;
  156. }
  157. seed ^= static_cast<std::uint32_t>(variant_key) * 2246822519U;
  158. return seed;
  159. }
  160. inline auto resolve_formation_category(
  161. const Engine::Core::UnitComponent *unit_comp,
  162. const AnimationInputs &anim) -> FormationCalculatorFactory::UnitCategory {
  163. using UnitCategory = FormationCalculatorFactory::UnitCategory;
  164. if ((unit_comp != nullptr) &&
  165. unit_comp->spawn_type == Game::Units::SpawnType::Builder &&
  166. anim.is_constructing) {
  167. return UnitCategory::BuilderConstruction;
  168. }
  169. bool is_mounted = false;
  170. if (unit_comp != nullptr) {
  171. using Game::Units::SpawnType;
  172. auto const st = unit_comp->spawn_type;
  173. is_mounted =
  174. (st == SpawnType::MountedKnight || st == SpawnType::HorseArcher ||
  175. st == SpawnType::HorseSpearman);
  176. }
  177. return is_mounted ? UnitCategory::Cavalry : UnitCategory::Infantry;
  178. }
  179. auto get_or_build_unit_layout(std::uintptr_t key,
  180. const FormationParams &formation,
  181. FormationCalculatorFactory::Nation nation,
  182. FormationCalculatorFactory::UnitCategory category,
  183. std::uint32_t seed, int rows,
  184. int cols) -> UnitLayoutCacheEntry & {
  185. auto &entry = s_layout_cache[key];
  186. bool rebuild =
  187. entry.soldiers.empty() || entry.rows != rows || entry.cols != cols ||
  188. entry.seed != seed || entry.nation != nation ||
  189. entry.category != category ||
  190. std::abs(entry.formation.spacing - formation.spacing) > 1e-6F ||
  191. entry.formation.individuals_per_unit != formation.individuals_per_unit ||
  192. entry.formation.max_per_row != formation.max_per_row;
  193. if (rebuild) {
  194. entry.soldiers.clear();
  195. entry.soldiers.reserve(static_cast<std::size_t>(rows * cols));
  196. entry.rows = rows;
  197. entry.cols = cols;
  198. entry.seed = seed;
  199. entry.nation = nation;
  200. entry.category = category;
  201. entry.formation = formation;
  202. const IFormationCalculator *formation_calculator =
  203. FormationCalculatorFactory::get_calculator(nation, category);
  204. auto fast_random = [](uint32_t &state) -> float {
  205. state = state * 1664525U + 1013904223U;
  206. return float(state & 0x7FFFFFU) / float(0x7FFFFFU);
  207. };
  208. for (int idx = 0; idx < rows * cols; ++idx) {
  209. int const r = idx / cols;
  210. int const c = idx % cols;
  211. FormationOffset const formation_offset =
  212. formation_calculator->calculate_offset(idx, r, c, rows, cols,
  213. formation.spacing, seed);
  214. std::uint32_t const inst_seed = seed ^ std::uint32_t(idx * 9176U);
  215. std::uint32_t rng_state = inst_seed;
  216. SoldierLayout layout{};
  217. layout.offset_x = formation_offset.offset_x;
  218. layout.offset_z = formation_offset.offset_z;
  219. layout.vertical_jitter = (fast_random(rng_state) - 0.5F) * 0.03F;
  220. layout.yaw_offset = (fast_random(rng_state) - 0.5F) * 5.0F;
  221. layout.phase_offset = fast_random(rng_state) * 0.25F;
  222. layout.inst_seed = inst_seed;
  223. entry.soldiers.push_back(layout);
  224. }
  225. }
  226. entry.frame_number = s_current_frame;
  227. return entry;
  228. }
  229. } // namespace
  230. void advance_pose_cache_frame() {
  231. ++s_current_frame;
  232. if ((s_current_frame & 0x1FF) == 0) {
  233. auto it = s_pose_cache.begin();
  234. while (it != s_pose_cache.end()) {
  235. if (s_current_frame - it->second.frame_number >
  236. k_pose_cache_max_age * 2) {
  237. it = s_pose_cache.erase(it);
  238. } else {
  239. ++it;
  240. }
  241. }
  242. auto layout_it = s_layout_cache.begin();
  243. while (layout_it != s_layout_cache.end()) {
  244. if (s_current_frame - layout_it->second.frame_number >
  245. k_layout_cache_max_age * 2) {
  246. layout_it = s_layout_cache.erase(layout_it);
  247. } else {
  248. ++layout_it;
  249. }
  250. }
  251. }
  252. }
  253. void clear_humanoid_caches() {
  254. s_pose_cache.clear();
  255. s_layout_cache.clear();
  256. s_current_frame = 0;
  257. }
  258. auto torso_mesh_without_bottom_cap() -> Mesh * {
  259. static std::unique_ptr<Mesh> s_mesh;
  260. if (s_mesh != nullptr) {
  261. return s_mesh.get();
  262. }
  263. Mesh *base = get_unit_torso();
  264. if (base == nullptr) {
  265. return nullptr;
  266. }
  267. auto filtered = base->clone_with_filtered_indices(
  268. [](unsigned int a, unsigned int b, unsigned int c,
  269. const std::vector<Vertex> &verts) -> bool {
  270. auto sample = [&](unsigned int idx) -> QVector3D {
  271. return {verts[idx].position[0], verts[idx].position[1],
  272. verts[idx].position[2]};
  273. };
  274. QVector3D pa = sample(a);
  275. QVector3D pb = sample(b);
  276. QVector3D pc = sample(c);
  277. float min_y = std::min({pa.y(), pb.y(), pc.y()});
  278. float max_y = std::max({pa.y(), pb.y(), pc.y()});
  279. QVector3D n(
  280. verts[a].normal[0] + verts[b].normal[0] + verts[c].normal[0],
  281. verts[a].normal[1] + verts[b].normal[1] + verts[c].normal[1],
  282. verts[a].normal[2] + verts[b].normal[2] + verts[c].normal[2]);
  283. if (n.lengthSquared() > 0.0F) {
  284. n.normalize();
  285. }
  286. constexpr float k_band_height = 0.02F;
  287. constexpr float k_bottom_threshold = 0.45F;
  288. bool is_flat = (max_y - min_y) < k_band_height;
  289. bool is_at_bottom = min_y > k_bottom_threshold;
  290. bool facing_down = (n.y() > 0.35F);
  291. return is_flat && is_at_bottom && facing_down;
  292. });
  293. s_mesh =
  294. (filtered != nullptr) ? std::move(filtered) : std::unique_ptr<Mesh>(base);
  295. return s_mesh.get();
  296. }
  297. auto HumanoidRendererBase::frame_local_position(
  298. const AttachmentFrame &frame, const QVector3D &local) -> QVector3D {
  299. float const lx = local.x() * frame.radius;
  300. float const ly = local.y() * frame.radius;
  301. float const lz = local.z() * frame.radius;
  302. return frame.origin + frame.right * lx + frame.up * ly + frame.forward * lz;
  303. }
  304. auto HumanoidRendererBase::make_frame_local_transform(
  305. const QMatrix4x4 &parent, const AttachmentFrame &frame,
  306. const QVector3D &local_offset, float uniform_scale) -> QMatrix4x4 {
  307. float scale = frame.radius * uniform_scale;
  308. if (scale == 0.0F) {
  309. scale = uniform_scale;
  310. }
  311. QVector3D const origin = frame_local_position(frame, local_offset);
  312. QMatrix4x4 local;
  313. local.setColumn(0, QVector4D(frame.right * scale, 0.0F));
  314. local.setColumn(1, QVector4D(frame.up * scale, 0.0F));
  315. local.setColumn(2, QVector4D(frame.forward * scale, 0.0F));
  316. local.setColumn(3, QVector4D(origin, 1.0F));
  317. return parent * local;
  318. }
  319. auto HumanoidRendererBase::head_local_position(
  320. const HeadFrame &frame, const QVector3D &local) -> QVector3D {
  321. return frame_local_position(frame, local);
  322. }
  323. auto HumanoidRendererBase::make_head_local_transform(
  324. const QMatrix4x4 &parent, const HeadFrame &frame,
  325. const QVector3D &local_offset, float uniform_scale) -> QMatrix4x4 {
  326. return make_frame_local_transform(parent, frame, local_offset, uniform_scale);
  327. }
  328. void HumanoidRendererBase::get_variant(const DrawContext &ctx, uint32_t seed,
  329. HumanoidVariant &v) const {
  330. QVector3D const team_tint = resolve_team_tint(ctx);
  331. v.palette = make_humanoid_palette(team_tint, seed);
  332. }
  333. void HumanoidRendererBase::customize_pose(const DrawContext &,
  334. const HumanoidAnimationContext &,
  335. uint32_t, HumanoidPose &) const {}
  336. void HumanoidRendererBase::add_attachments(const DrawContext &,
  337. const HumanoidVariant &,
  338. const HumanoidPose &,
  339. const HumanoidAnimationContext &,
  340. ISubmitter &) const {}
  341. auto HumanoidRendererBase::resolve_entity_ground_offset(
  342. const DrawContext &, Engine::Core::UnitComponent *unit_comp,
  343. Engine::Core::TransformComponent *transform_comp) const -> float {
  344. (void)unit_comp;
  345. (void)transform_comp;
  346. return 0.0F;
  347. }
  348. auto HumanoidRendererBase::resolve_team_tint(const DrawContext &ctx)
  349. -> QVector3D {
  350. QVector3D tunic(0.8F, 0.9F, 1.0F);
  351. Engine::Core::UnitComponent *unit = nullptr;
  352. Engine::Core::RenderableComponent *rc = nullptr;
  353. if (ctx.entity != nullptr) {
  354. unit = ctx.entity->get_component<Engine::Core::UnitComponent>();
  355. rc = ctx.entity->get_component<Engine::Core::RenderableComponent>();
  356. }
  357. if ((unit != nullptr) && unit->owner_id > 0) {
  358. tunic = Game::Visuals::team_colorForOwner(unit->owner_id);
  359. } else if (rc != nullptr) {
  360. tunic = QVector3D(rc->color[0], rc->color[1], rc->color[2]);
  361. }
  362. return tunic;
  363. }
  364. auto HumanoidRendererBase::resolve_formation(const DrawContext &ctx)
  365. -> FormationParams {
  366. FormationParams params{};
  367. params.individuals_per_unit = 1;
  368. params.max_per_row = 1;
  369. params.spacing = 0.75F;
  370. if (ctx.entity != nullptr) {
  371. auto *unit = ctx.entity->get_component<Engine::Core::UnitComponent>();
  372. if (unit != nullptr) {
  373. params.individuals_per_unit =
  374. Game::Units::TroopConfig::instance().getIndividualsPerUnit(
  375. unit->spawn_type);
  376. params.max_per_row =
  377. Game::Units::TroopConfig::instance().getMaxUnitsPerRow(
  378. unit->spawn_type);
  379. if (unit->spawn_type == Game::Units::SpawnType::MountedKnight) {
  380. params.spacing = 1.05F;
  381. }
  382. }
  383. }
  384. return params;
  385. }
  386. void HumanoidRendererBase::compute_locomotion_pose(
  387. uint32_t seed, float time, bool is_moving, const VariationParams &variation,
  388. HumanoidPose &pose) {
  389. using HP = HumanProportions;
  390. float const h_scale = variation.height_scale;
  391. pose.head_pos = QVector3D(0.0F, HP::HEAD_CENTER_Y * h_scale, 0.0F);
  392. pose.head_r = HP::HEAD_RADIUS * h_scale;
  393. pose.neck_base = QVector3D(0.0F, HP::NECK_BASE_Y * h_scale, 0.0F);
  394. float const b_scale = variation.bulk_scale;
  395. float const s_width = variation.stance_width;
  396. float const half_shoulder_span = 0.5F * HP::SHOULDER_WIDTH * b_scale;
  397. pose.shoulder_l =
  398. QVector3D(-half_shoulder_span, HP::SHOULDER_Y * h_scale, 0.0F);
  399. pose.shoulder_r =
  400. QVector3D(half_shoulder_span, HP::SHOULDER_Y * h_scale, 0.0F);
  401. pose.pelvis_pos = QVector3D(0.0F, HP::WAIST_Y * h_scale, 0.0F);
  402. float const rest_stride = 0.06F + (variation.arm_swing_amp - 1.0F) * 0.045F;
  403. float const foot_x_span = HP::SHOULDER_WIDTH * 0.62F * s_width;
  404. pose.foot_y_offset = HP::FOOT_Y_OFFSET_DEFAULT;
  405. pose.foot_l =
  406. QVector3D(-foot_x_span, HP::GROUND_Y + pose.foot_y_offset, rest_stride);
  407. pose.foot_r =
  408. QVector3D(foot_x_span, HP::GROUND_Y + pose.foot_y_offset, -rest_stride);
  409. pose.shoulder_l.setY(pose.shoulder_l.y() + variation.shoulder_tilt);
  410. pose.shoulder_r.setY(pose.shoulder_r.y() - variation.shoulder_tilt);
  411. float const slouch_offset = variation.posture_slump * 0.15F;
  412. pose.shoulder_l.setZ(pose.shoulder_l.z() + slouch_offset);
  413. pose.shoulder_r.setZ(pose.shoulder_r.z() + slouch_offset);
  414. float const foot_inward_jitter = (hash_01(seed ^ 0x5678U) - 0.5F) * 0.02F;
  415. float const foot_forward_jitter = (hash_01(seed ^ 0x9ABCU) - 0.5F) * 0.035F;
  416. pose.foot_l.setX(pose.foot_l.x() + foot_inward_jitter);
  417. pose.foot_r.setX(pose.foot_r.x() - foot_inward_jitter);
  418. pose.foot_l.setZ(pose.foot_l.z() + foot_forward_jitter);
  419. pose.foot_r.setZ(pose.foot_r.z() - foot_forward_jitter);
  420. float const arm_height_jitter = (hash_01(seed ^ 0xABCDU) - 0.5F) * 0.03F;
  421. float const arm_asymmetry = (hash_01(seed ^ 0xDEF0U) - 0.5F) * 0.04F;
  422. pose.hand_l =
  423. QVector3D(-0.05F + arm_asymmetry,
  424. HP::SHOULDER_Y * h_scale + 0.05F + arm_height_jitter, 0.55F);
  425. pose.hand_r = QVector3D(
  426. 0.15F - arm_asymmetry * 0.5F,
  427. HP::SHOULDER_Y * h_scale + 0.15F + arm_height_jitter * 0.8F, 0.20F);
  428. if (is_moving) {
  429. float const walk_cycle_time = 0.8F / variation.walk_speed_mult;
  430. float const walk_phase = std::fmod(time * (1.0F / walk_cycle_time), 1.0F);
  431. float const left_phase = walk_phase;
  432. float const right_phase = std::fmod(walk_phase + 0.5F, 1.0F);
  433. const float ground_y = HP::GROUND_Y;
  434. const float stride_length = 0.35F * variation.arm_swing_amp;
  435. float const bob_phase = walk_phase * 2.0F;
  436. float const vertical_bob =
  437. std::sin(bob_phase * std::numbers::pi_v<float>) * 0.018F;
  438. float const hip_sway_amount = 0.002F;
  439. float const sway_raw =
  440. std::sin(walk_phase * 2.0F * std::numbers::pi_v<float>);
  441. float const hip_sway = sway_raw * hip_sway_amount;
  442. float const torso_sway_z = 0.0F;
  443. auto animate_foot = [ground_y, &pose, stride_length](QVector3D &foot,
  444. float phase) {
  445. float const lift_raw = std::sin(phase * 2.0F * std::numbers::pi_v<float>);
  446. float lift = 0.0F;
  447. if (lift_raw > 0.0F) {
  448. float const lift_phase =
  449. phase < 0.5F ? phase * 2.0F : (1.0F - phase) * 2.0F;
  450. float const ease_t =
  451. lift_phase * lift_phase * (3.0F - 2.0F * lift_phase);
  452. lift = lift_raw * ease_t;
  453. foot.setY(ground_y + pose.foot_y_offset + lift * 0.15F);
  454. } else {
  455. foot.setY(ground_y + pose.foot_y_offset);
  456. }
  457. float const stride_phase =
  458. (phase - 0.25F) * 2.0F * std::numbers::pi_v<float>;
  459. foot.setZ(foot.z() + std::sin(stride_phase) * stride_length);
  460. };
  461. animate_foot(pose.foot_l, left_phase);
  462. animate_foot(pose.foot_r, right_phase);
  463. pose.pelvis_pos.setY(pose.pelvis_pos.y() + vertical_bob);
  464. pose.shoulder_l.setY(pose.shoulder_l.y() + vertical_bob);
  465. pose.shoulder_r.setY(pose.shoulder_r.y() + vertical_bob);
  466. pose.neck_base.setY(pose.neck_base.y() + vertical_bob);
  467. pose.head_pos.setY(pose.head_pos.y() + vertical_bob);
  468. pose.pelvis_pos.setX(pose.pelvis_pos.x() + hip_sway);
  469. pose.shoulder_l.setZ(pose.shoulder_l.z() + torso_sway_z);
  470. pose.shoulder_r.setZ(pose.shoulder_r.z() + torso_sway_z);
  471. pose.neck_base.setZ(pose.neck_base.z() + torso_sway_z * 0.7F);
  472. pose.head_pos.setZ(pose.head_pos.z() + torso_sway_z * 0.5F);
  473. float const arm_swing_amp = 0.04F * variation.arm_swing_amp;
  474. float const arm_phase_offset = 0.15F;
  475. constexpr float max_arm_displacement = 0.06F;
  476. float const left_swing_raw = std::sin((left_phase + arm_phase_offset) *
  477. 2.0F * std::numbers::pi_v<float>);
  478. float const left_arm_swing =
  479. std::clamp(left_swing_raw * arm_swing_amp, -max_arm_displacement,
  480. max_arm_displacement);
  481. pose.hand_l.setZ(pose.hand_l.z() - left_arm_swing);
  482. float const right_swing_raw = std::sin((right_phase + arm_phase_offset) *
  483. 2.0F * std::numbers::pi_v<float>);
  484. float const right_arm_swing =
  485. std::clamp(right_swing_raw * arm_swing_amp, -max_arm_displacement,
  486. max_arm_displacement);
  487. pose.hand_r.setZ(pose.hand_r.z() - right_arm_swing);
  488. auto clamp_hand_reach = [&](const QVector3D &shoulder, QVector3D &hand) {
  489. float const max_reach =
  490. (HP::UPPER_ARM_LEN + HP::FORE_ARM_LEN) * h_scale * 0.98F;
  491. QVector3D diff = hand - shoulder;
  492. float const len = diff.length();
  493. if (len > max_reach && len > 1e-6F) {
  494. hand = shoulder + diff * (max_reach / len);
  495. }
  496. };
  497. clamp_hand_reach(pose.shoulder_l, pose.hand_l);
  498. clamp_hand_reach(pose.shoulder_r, pose.hand_r);
  499. }
  500. QVector3D const hip_l =
  501. pose.pelvis_pos +
  502. QVector3D(-HP::HIP_LATERAL_OFFSET, HP::HIP_VERTICAL_OFFSET, 0.0F);
  503. QVector3D const hip_r =
  504. pose.pelvis_pos +
  505. QVector3D(HP::HIP_LATERAL_OFFSET, HP::HIP_VERTICAL_OFFSET, 0.0F);
  506. auto solve_leg = [&](const QVector3D &hip, const QVector3D &foot,
  507. bool is_left) -> QVector3D {
  508. QVector3D hip_to_foot = foot - hip;
  509. float const distance = hip_to_foot.length();
  510. if (distance < HP::EPSILON_SMALL) {
  511. return hip;
  512. }
  513. float const upper_len = HP::UPPER_LEG_LEN * h_scale;
  514. float const lower_len = HP::LOWER_LEG_LEN * h_scale;
  515. float const reach = upper_len + lower_len;
  516. float const min_reach =
  517. std::max(std::abs(upper_len - lower_len) + 1e-4F, 1e-3F);
  518. float const max_reach = std::max(reach - 1e-4F, min_reach + 1e-4F);
  519. float const clamped_dist = std::clamp(distance, min_reach, max_reach);
  520. QVector3D const dir = hip_to_foot / distance;
  521. float cos_theta = (upper_len * upper_len + clamped_dist * clamped_dist -
  522. lower_len * lower_len) /
  523. (2.0F * upper_len * clamped_dist);
  524. cos_theta = std::clamp(cos_theta, -1.0F, 1.0F);
  525. float const sin_theta =
  526. std::sqrt(std::max(0.0F, 1.0F - cos_theta * cos_theta));
  527. QVector3D bend_pref = is_left ? QVector3D(-0.24F, 0.0F, 0.95F)
  528. : QVector3D(0.24F, 0.0F, 0.95F);
  529. bend_pref.normalize();
  530. QVector3D bend_axis =
  531. bend_pref - dir * QVector3D::dotProduct(dir, bend_pref);
  532. if (bend_axis.lengthSquared() < 1e-6F) {
  533. bend_axis = QVector3D::crossProduct(dir, QVector3D(0.0F, 1.0F, 0.0F));
  534. if (bend_axis.lengthSquared() < 1e-6F) {
  535. bend_axis = QVector3D::crossProduct(dir, QVector3D(1.0F, 0.0F, 0.0F));
  536. }
  537. }
  538. bend_axis.normalize();
  539. QVector3D const knee = hip + dir * (cos_theta * upper_len) +
  540. bend_axis * (sin_theta * upper_len);
  541. float const knee_floor = HP::GROUND_Y + pose.foot_y_offset * 0.5F;
  542. if (knee.y() < knee_floor) {
  543. QVector3D adjusted = knee;
  544. adjusted.setY(knee_floor);
  545. return adjusted;
  546. }
  547. return knee;
  548. };
  549. pose.knee_l = solve_leg(hip_l, pose.foot_l, true);
  550. pose.knee_r = solve_leg(hip_r, pose.foot_r, false);
  551. QVector3D right_axis = pose.shoulder_r - pose.shoulder_l;
  552. right_axis.setY(0.0F);
  553. if (right_axis.lengthSquared() < 1e-8F) {
  554. right_axis = QVector3D(1, 0, 0);
  555. }
  556. right_axis.normalize();
  557. if (right_axis.x() < 0.0F) {
  558. right_axis = -right_axis;
  559. }
  560. QVector3D const outward_l = -right_axis;
  561. QVector3D const outward_r = right_axis;
  562. pose.elbow_l = elbow_bend_torso(pose.shoulder_l, pose.hand_l, outward_l,
  563. 0.45F, 0.15F, -0.08F, +1.0F);
  564. pose.elbow_r = elbow_bend_torso(pose.shoulder_r, pose.hand_r, outward_r,
  565. 0.48F, 0.12F, 0.02F, +1.0F);
  566. }
  567. void HumanoidRendererBase::draw_common_body(const DrawContext &ctx,
  568. const HumanoidVariant &v,
  569. HumanoidPose &pose,
  570. ISubmitter &out) const {
  571. using HP = HumanProportions;
  572. QVector3D const scaling = get_proportion_scaling();
  573. float const width_scale = scaling.x();
  574. float const height_scale = scaling.y();
  575. float const torso_scale = get_torso_scale();
  576. float const head_scale = 1.0F;
  577. QVector3D right_axis = pose.shoulder_r - pose.shoulder_l;
  578. right_axis.setY(0.0F);
  579. right_axis.setZ(0.0F);
  580. if (right_axis.lengthSquared() < 1e-8F) {
  581. right_axis = QVector3D(1.0F, 0.0F, 0.0F);
  582. }
  583. right_axis.normalize();
  584. if (right_axis.x() < 0.0F) {
  585. right_axis = -right_axis;
  586. }
  587. QVector3D const up_axis(0.0F, 1.0F, 0.0F);
  588. QVector3D forward_axis = QVector3D::crossProduct(right_axis, up_axis);
  589. if (forward_axis.lengthSquared() < 1e-8F) {
  590. forward_axis = QVector3D(0.0F, 0.0F, 1.0F);
  591. }
  592. forward_axis.normalize();
  593. QVector3D const shoulder_mid = (pose.shoulder_l + pose.shoulder_r) * 0.5F;
  594. const float y_shoulder = shoulder_mid.y();
  595. const float y_neck = pose.neck_base.y();
  596. const float shoulder_half_span =
  597. 0.5F * std::abs(pose.shoulder_r.x() - pose.shoulder_l.x());
  598. const float torso_r_base =
  599. std::max(HP::TORSO_TOP_R, shoulder_half_span * 0.95F);
  600. const float torso_r = torso_r_base * torso_scale;
  601. float const depth_scale = scaling.z();
  602. const float torso_depth_factor =
  603. std::clamp(HP::TORSO_DEPTH_FACTOR_BASE + (depth_scale - 1.0F) * 0.20F,
  604. HP::TORSO_DEPTH_FACTOR_MIN, HP::TORSO_DEPTH_FACTOR_MAX);
  605. float torso_depth = torso_r * torso_depth_factor;
  606. const float y_top_cover =
  607. std::max(y_shoulder + 0.00F, y_neck + HP::TORSO_TOP_COVER_OFFSET);
  608. const float upper_arm_r = HP::UPPER_ARM_R * width_scale;
  609. const float fore_arm_r = HP::FORE_ARM_R * width_scale;
  610. const float joint_r = HP::HAND_RADIUS * width_scale * 1.05F;
  611. const float hand_r = HP::HAND_RADIUS * width_scale * 0.95F;
  612. const float leg_joint_r = HP::LOWER_LEG_R * width_scale * 0.95F;
  613. const float thigh_r = HP::UPPER_LEG_R * width_scale;
  614. const float shin_r = HP::LOWER_LEG_R * width_scale;
  615. const float foot_radius = shin_r * 1.10F;
  616. float const torso_x = (shoulder_mid.x() + pose.pelvis_pos.x()) * 0.5F;
  617. constexpr float torso_z = 0.0F;
  618. QVector3D const tunic_top{torso_x, y_top_cover - 0.006F, torso_z};
  619. QVector3D const tunic_bot{torso_x, pose.pelvis_pos.y() - 0.05F, torso_z};
  620. QMatrix4x4 torso_transform =
  621. cylinder_between(ctx.model, tunic_top, tunic_bot, 1.0F);
  622. torso_transform.scale(torso_r, 1.0F, torso_depth);
  623. Mesh *torso_mesh = torso_mesh_without_bottom_cap();
  624. if (torso_mesh != nullptr) {
  625. out.mesh(torso_mesh, torso_transform, v.palette.cloth, nullptr, 1.0F);
  626. }
  627. float const head_r = pose.head_r;
  628. QVector3D head_up;
  629. QVector3D head_right;
  630. QVector3D head_forward;
  631. if (pose.head_frame.radius > 0.001F) {
  632. head_up = pose.head_frame.up;
  633. head_right = pose.head_frame.right;
  634. head_forward = pose.head_frame.forward;
  635. } else {
  636. head_up = pose.head_pos - pose.neck_base;
  637. if (head_up.lengthSquared() < 1e-8F) {
  638. head_up = up_axis;
  639. } else {
  640. head_up.normalize();
  641. }
  642. head_right =
  643. right_axis - head_up * QVector3D::dotProduct(right_axis, head_up);
  644. if (head_right.lengthSquared() < 1e-8F) {
  645. head_right = QVector3D::crossProduct(head_up, forward_axis);
  646. if (head_right.lengthSquared() < 1e-8F) {
  647. head_right = QVector3D(1.0F, 0.0F, 0.0F);
  648. }
  649. }
  650. head_right.normalize();
  651. if (QVector3D::dotProduct(head_right, right_axis) < 0.0F) {
  652. head_right = -head_right;
  653. }
  654. head_forward = QVector3D::crossProduct(head_right, head_up);
  655. if (head_forward.lengthSquared() < 1e-8F) {
  656. head_forward = forward_axis;
  657. } else {
  658. head_forward.normalize();
  659. }
  660. if (QVector3D::dotProduct(head_forward, forward_axis) < 0.0F) {
  661. head_right = -head_right;
  662. head_forward = -head_forward;
  663. }
  664. }
  665. QVector3D const chin_pos = pose.head_pos - head_up * head_r;
  666. out.mesh(get_unit_cylinder(),
  667. cylinder_between(ctx.model, pose.neck_base, chin_pos,
  668. HP::NECK_RADIUS * width_scale),
  669. v.palette.skin * 0.9F, nullptr, 1.0F);
  670. QMatrix4x4 head_rot;
  671. head_rot.setColumn(0, QVector4D(head_right, 0.0F));
  672. head_rot.setColumn(1, QVector4D(head_up, 0.0F));
  673. head_rot.setColumn(2, QVector4D(head_forward, 0.0F));
  674. head_rot.setColumn(3, QVector4D(0.0F, 0.0F, 0.0F, 1.0F));
  675. QMatrix4x4 head_transform = ctx.model;
  676. head_transform.translate(pose.head_pos);
  677. head_transform = head_transform * head_rot;
  678. head_transform.scale(head_r);
  679. out.mesh(get_unit_sphere(), head_transform, v.palette.skin, nullptr, 1.0F);
  680. pose.head_frame.origin = pose.head_pos;
  681. pose.head_frame.right = head_right;
  682. pose.head_frame.up = head_up;
  683. pose.head_frame.forward = head_forward;
  684. pose.head_frame.radius = head_r;
  685. pose.body_frames.head = pose.head_frame;
  686. QVector3D const torso_center = QVector3D(
  687. (shoulder_mid.x() + pose.pelvis_pos.x()) * 0.5F, y_shoulder, 0.0F);
  688. pose.body_frames.torso.origin = torso_center;
  689. pose.body_frames.torso.right = right_axis;
  690. pose.body_frames.torso.up = up_axis;
  691. pose.body_frames.torso.forward = forward_axis;
  692. pose.body_frames.torso.radius = torso_r;
  693. pose.body_frames.torso.depth = torso_depth;
  694. pose.body_frames.back.origin = torso_center - forward_axis * torso_depth;
  695. pose.body_frames.back.right = right_axis;
  696. pose.body_frames.back.up = up_axis;
  697. pose.body_frames.back.forward = -forward_axis;
  698. pose.body_frames.back.radius = torso_r * 0.75F;
  699. pose.body_frames.back.depth = torso_depth * 0.90F;
  700. pose.body_frames.waist.origin = pose.pelvis_pos;
  701. pose.body_frames.waist.right = right_axis;
  702. pose.body_frames.waist.up = up_axis;
  703. pose.body_frames.waist.forward = forward_axis;
  704. pose.body_frames.waist.radius = torso_r * 0.80F;
  705. pose.body_frames.waist.depth = torso_depth * 0.72F;
  706. QVector3D shoulder_up = (pose.shoulder_l - pose.pelvis_pos).normalized();
  707. QVector3D shoulder_forward_l =
  708. QVector3D::crossProduct(-right_axis, shoulder_up);
  709. if (shoulder_forward_l.lengthSquared() < 1e-8F) {
  710. shoulder_forward_l = forward_axis;
  711. } else {
  712. shoulder_forward_l.normalize();
  713. }
  714. pose.body_frames.shoulder_l.origin = pose.shoulder_l;
  715. pose.body_frames.shoulder_l.right = -right_axis;
  716. pose.body_frames.shoulder_l.up = shoulder_up;
  717. pose.body_frames.shoulder_l.forward = shoulder_forward_l;
  718. pose.body_frames.shoulder_l.radius = upper_arm_r;
  719. QVector3D shoulder_forward_r =
  720. QVector3D::crossProduct(right_axis, shoulder_up);
  721. if (shoulder_forward_r.lengthSquared() < 1e-8F) {
  722. shoulder_forward_r = forward_axis;
  723. } else {
  724. shoulder_forward_r.normalize();
  725. }
  726. pose.body_frames.shoulder_r.origin = pose.shoulder_r;
  727. pose.body_frames.shoulder_r.right = right_axis;
  728. pose.body_frames.shoulder_r.up = shoulder_up;
  729. pose.body_frames.shoulder_r.forward = shoulder_forward_r;
  730. pose.body_frames.shoulder_r.radius = upper_arm_r;
  731. QVector3D hand_up_l = (pose.hand_l - pose.elbow_l);
  732. if (hand_up_l.lengthSquared() > 1e-8F) {
  733. hand_up_l.normalize();
  734. } else {
  735. hand_up_l = up_axis;
  736. }
  737. QVector3D hand_forward_l = QVector3D::crossProduct(-right_axis, hand_up_l);
  738. if (hand_forward_l.lengthSquared() < 1e-8F) {
  739. hand_forward_l = forward_axis;
  740. } else {
  741. hand_forward_l.normalize();
  742. }
  743. pose.body_frames.hand_l.origin = pose.hand_l;
  744. pose.body_frames.hand_l.right = -right_axis;
  745. pose.body_frames.hand_l.up = hand_up_l;
  746. pose.body_frames.hand_l.forward = hand_forward_l;
  747. pose.body_frames.hand_l.radius = hand_r;
  748. QVector3D hand_up_r = (pose.hand_r - pose.elbow_r);
  749. if (hand_up_r.lengthSquared() > 1e-8F) {
  750. hand_up_r.normalize();
  751. } else {
  752. hand_up_r = up_axis;
  753. }
  754. QVector3D hand_forward_r = QVector3D::crossProduct(right_axis, hand_up_r);
  755. if (hand_forward_r.lengthSquared() < 1e-8F) {
  756. hand_forward_r = forward_axis;
  757. } else {
  758. hand_forward_r.normalize();
  759. }
  760. pose.body_frames.hand_r.origin = pose.hand_r;
  761. pose.body_frames.hand_r.right = right_axis;
  762. pose.body_frames.hand_r.up = hand_up_r;
  763. pose.body_frames.hand_r.forward = hand_forward_r;
  764. pose.body_frames.hand_r.radius = hand_r;
  765. QVector3D foot_up_l = up_axis;
  766. QVector3D foot_forward_l = forward_axis - right_axis * 0.12F;
  767. if (foot_forward_l.lengthSquared() > 1e-8F) {
  768. foot_forward_l.normalize();
  769. } else {
  770. foot_forward_l = forward_axis;
  771. }
  772. pose.body_frames.foot_l.origin = pose.foot_l;
  773. pose.body_frames.foot_l.right = -right_axis;
  774. pose.body_frames.foot_l.up = foot_up_l;
  775. pose.body_frames.foot_l.forward = foot_forward_l;
  776. pose.body_frames.foot_l.radius = foot_radius;
  777. QVector3D foot_forward_r = forward_axis + right_axis * 0.12F;
  778. if (foot_forward_r.lengthSquared() > 1e-8F) {
  779. foot_forward_r.normalize();
  780. } else {
  781. foot_forward_r = forward_axis;
  782. }
  783. pose.body_frames.foot_r.origin = pose.foot_r;
  784. pose.body_frames.foot_r.right = right_axis;
  785. pose.body_frames.foot_r.up = foot_up_l;
  786. pose.body_frames.foot_r.forward = foot_forward_r;
  787. pose.body_frames.foot_r.radius = foot_radius;
  788. auto compute_shin_frame = [&](const QVector3D &ankle, const QVector3D &knee,
  789. float right_sign) -> AttachmentFrame {
  790. AttachmentFrame shin{};
  791. shin.origin = ankle;
  792. QVector3D shin_dir = knee - ankle;
  793. float shin_len = shin_dir.length();
  794. if (shin_len > 1e-6F) {
  795. shin.up = shin_dir / shin_len;
  796. } else {
  797. shin.up = up_axis;
  798. }
  799. QVector3D shin_forward = forward_axis;
  800. shin_forward =
  801. shin_forward - shin.up * QVector3D::dotProduct(shin_forward, shin.up);
  802. if (shin_forward.lengthSquared() > 1e-6F) {
  803. shin_forward.normalize();
  804. } else {
  805. shin_forward = forward_axis;
  806. }
  807. shin.forward = shin_forward;
  808. shin.right = QVector3D::crossProduct(shin.up, shin.forward) * right_sign;
  809. shin.radius = HP::LOWER_LEG_R;
  810. return shin;
  811. };
  812. pose.body_frames.shin_l = compute_shin_frame(pose.foot_l, pose.knee_l, -1.0F);
  813. pose.body_frames.shin_r = compute_shin_frame(pose.foot_r, pose.knee_r, 1.0F);
  814. QVector3D const iris = QVector3D(0.10F, 0.10F, 0.12F);
  815. auto eye_position = [&](float lateral) {
  816. QVector3D const local(lateral, 0.12F, 0.92F);
  817. QVector3D world = frame_local_position(pose.body_frames.head, local);
  818. world +=
  819. pose.body_frames.head.forward * (pose.body_frames.head.radius * 0.02F);
  820. return world;
  821. };
  822. QVector3D const left_eye_world = eye_position(-0.32F);
  823. QVector3D const right_eye_world = eye_position(0.32F);
  824. float const eye_radius = pose.body_frames.head.radius * 0.17F;
  825. out.mesh(get_unit_sphere(), sphere_at(ctx.model, left_eye_world, eye_radius),
  826. iris, nullptr, 1.0F);
  827. out.mesh(get_unit_sphere(), sphere_at(ctx.model, right_eye_world, eye_radius),
  828. iris, nullptr, 1.0F);
  829. out.mesh(
  830. get_unit_cylinder(),
  831. cylinder_between(ctx.model, pose.shoulder_l, pose.elbow_l, upper_arm_r),
  832. v.palette.cloth, nullptr, 1.0F);
  833. out.mesh(get_unit_sphere(), sphere_at(ctx.model, pose.elbow_l, joint_r),
  834. v.palette.cloth * 0.95F, nullptr, 1.0F);
  835. out.mesh(get_unit_cylinder(),
  836. cylinder_between(ctx.model, pose.elbow_l, pose.hand_l, fore_arm_r),
  837. v.palette.skin * 0.95F, nullptr, 1.0F);
  838. out.mesh(get_unit_sphere(), sphere_at(ctx.model, pose.hand_l, hand_r),
  839. v.palette.leather_dark * 0.92F, nullptr, 1.0F);
  840. out.mesh(
  841. get_unit_cylinder(),
  842. cylinder_between(ctx.model, pose.shoulder_r, pose.elbow_r, upper_arm_r),
  843. v.palette.cloth, nullptr, 1.0F);
  844. out.mesh(get_unit_sphere(), sphere_at(ctx.model, pose.elbow_r, joint_r),
  845. v.palette.cloth * 0.95F, nullptr, 1.0F);
  846. out.mesh(get_unit_cylinder(),
  847. cylinder_between(ctx.model, pose.elbow_r, pose.hand_r, fore_arm_r),
  848. v.palette.skin * 0.95F, nullptr, 1.0F);
  849. out.mesh(get_unit_sphere(), sphere_at(ctx.model, pose.hand_r, hand_r),
  850. v.palette.leather_dark * 0.92F, nullptr, 1.0F);
  851. QVector3D const hip_l =
  852. pose.pelvis_pos +
  853. QVector3D(-HP::HIP_LATERAL_OFFSET, HP::HIP_VERTICAL_OFFSET, 0.0F);
  854. QVector3D const hip_r =
  855. pose.pelvis_pos +
  856. QVector3D(HP::HIP_LATERAL_OFFSET, HP::HIP_VERTICAL_OFFSET, 0.0F);
  857. out.mesh(get_unit_cylinder(),
  858. cylinder_between(ctx.model, hip_l, pose.knee_l, thigh_r),
  859. v.palette.cloth * 0.92F, nullptr, 1.0F);
  860. out.mesh(get_unit_sphere(), sphere_at(ctx.model, pose.knee_l, leg_joint_r),
  861. v.palette.cloth * 0.90F, nullptr, 1.0F);
  862. out.mesh(get_unit_cylinder(),
  863. cylinder_between(ctx.model, pose.knee_l, pose.foot_l, shin_r),
  864. v.palette.leather * 0.95F, nullptr, 1.0F);
  865. out.mesh(get_unit_cylinder(),
  866. cylinder_between(ctx.model, hip_r, pose.knee_r, thigh_r),
  867. v.palette.cloth * 0.92F, nullptr, 1.0F);
  868. out.mesh(get_unit_sphere(), sphere_at(ctx.model, pose.knee_r, leg_joint_r),
  869. v.palette.cloth * 0.90F, nullptr, 1.0F);
  870. out.mesh(get_unit_cylinder(),
  871. cylinder_between(ctx.model, pose.knee_r, pose.foot_r, shin_r),
  872. v.palette.leather * 0.95F, nullptr, 1.0F);
  873. auto draw_foot = [&](const QVector3D &ankle, bool is_left) {
  874. QVector3D lateral = is_left ? -right_axis : right_axis;
  875. QVector3D foot_forward =
  876. forward_axis + lateral * (is_left ? -0.12F : 0.12F);
  877. if (foot_forward.lengthSquared() < 1e-6F) {
  878. foot_forward = forward_axis;
  879. }
  880. foot_forward.normalize();
  881. float const heel_span = foot_radius * 3.50F;
  882. float const toe_span = foot_radius * 5.50F;
  883. float const sole_y = HP::GROUND_Y;
  884. QVector3D ankle_ground = ankle;
  885. ankle_ground.setY(sole_y);
  886. QVector3D heel = ankle_ground - foot_forward * heel_span;
  887. QVector3D toe = ankle_ground + foot_forward * toe_span;
  888. heel.setY(sole_y);
  889. toe.setY(sole_y);
  890. QMatrix4x4 foot_mat = capsule_between(ctx.model, heel, toe, foot_radius);
  891. float const width_at_heel = 1.2F;
  892. float const width_at_toe = 2.5F;
  893. float const height_scale = 0.26F;
  894. float const depth_scale = 1.0F;
  895. QMatrix4x4 scale_mat;
  896. scale_mat.setToIdentity();
  897. scale_mat.scale((width_at_heel + width_at_toe) * 0.5F, height_scale,
  898. depth_scale);
  899. QMatrix4x4 shear_mat;
  900. shear_mat.setToIdentity();
  901. shear_mat(0, 2) = (width_at_toe - width_at_heel) * 0.5F;
  902. foot_mat = foot_mat * scale_mat * shear_mat;
  903. out.mesh(get_unit_capsule(), foot_mat, v.palette.leather_dark * 0.92F,
  904. nullptr, 1.0F);
  905. };
  906. draw_foot(pose.foot_l, true);
  907. draw_foot(pose.foot_r, false);
  908. draw_armor_overlay(ctx, v, pose, y_top_cover, torso_r, shoulder_half_span,
  909. upper_arm_r, right_axis, out);
  910. draw_shoulder_decorations(ctx, v, pose, y_top_cover, pose.neck_base.y(),
  911. right_axis, out);
  912. draw_helmet(ctx, v, pose, out);
  913. }
  914. void HumanoidRendererBase::draw_armor_overlay(
  915. const DrawContext &, const HumanoidVariant &, const HumanoidPose &, float,
  916. float, float, float, const QVector3D &, ISubmitter &) const {}
  917. void HumanoidRendererBase::draw_armor(const DrawContext &,
  918. const HumanoidVariant &,
  919. const HumanoidPose &,
  920. const HumanoidAnimationContext &,
  921. ISubmitter &) const {}
  922. void HumanoidRendererBase::draw_shoulder_decorations(
  923. const DrawContext &, const HumanoidVariant &, const HumanoidPose &, float,
  924. float, const QVector3D &, ISubmitter &) const {}
  925. void HumanoidRendererBase::draw_helmet(const DrawContext &,
  926. const HumanoidVariant &,
  927. const HumanoidPose &,
  928. ISubmitter &) const {}
  929. void HumanoidRendererBase::draw_facial_hair(const DrawContext &ctx,
  930. const HumanoidVariant &v,
  931. const HumanoidPose &pose,
  932. ISubmitter &out) const {
  933. const FacialHairParams &fh = v.facial_hair;
  934. if (fh.style == FacialHairStyle::None || fh.coverage < 0.01F) {
  935. return;
  936. }
  937. const auto &gfx_settings = Render::GraphicsSettings::instance();
  938. if (!gfx_settings.features().enable_facial_hair) {
  939. return;
  940. }
  941. constexpr float k_facial_hair_max_distance = 12.0F;
  942. if (ctx.camera != nullptr) {
  943. QVector3D const soldier_world_pos =
  944. ctx.model.map(QVector3D(0.0F, 0.0F, 0.0F));
  945. float const distance =
  946. (soldier_world_pos - ctx.camera->get_position()).length();
  947. if (distance > k_facial_hair_max_distance) {
  948. ++s_render_stats.facial_hair_skipped_distance;
  949. return;
  950. }
  951. }
  952. const AttachmentFrame &frame = pose.body_frames.head;
  953. float const head_r = frame.radius;
  954. if (head_r <= 0.0F) {
  955. return;
  956. }
  957. auto saturate = [](const QVector3D &c) -> QVector3D {
  958. return {std::clamp(c.x(), 0.0F, 1.0F), std::clamp(c.y(), 0.0F, 1.0F),
  959. std::clamp(c.z(), 0.0F, 1.0F)};
  960. };
  961. QVector3D hair_color = fh.color * (1.0F - fh.greyness) +
  962. QVector3D(0.75F, 0.75F, 0.75F) * fh.greyness;
  963. QVector3D hair_dark = hair_color * 0.80F;
  964. QVector3D const hair_root = hair_dark * 0.95F;
  965. QVector3D const hair_tip = hair_color * 1.08F;
  966. float const chin_y = -head_r * 0.95F;
  967. float const mouth_y = -head_r * 0.18F;
  968. float const jaw_z = head_r * 0.68F;
  969. float const chin_norm = chin_y / head_r;
  970. float const mouth_norm = mouth_y / head_r;
  971. float const jaw_forward_norm = jaw_z / head_r;
  972. uint32_t rand_state = 0x9E3779B9U;
  973. if (ctx.entity != nullptr) {
  974. uintptr_t ptr = reinterpret_cast<uintptr_t>(ctx.entity);
  975. rand_state ^= static_cast<uint32_t>(ptr & 0xFFFFFFFFU);
  976. rand_state ^= static_cast<uint32_t>((ptr >> 32) & 0xFFFFFFFFU);
  977. }
  978. rand_state ^= static_cast<uint32_t>(fh.length * 9973.0F);
  979. rand_state ^= static_cast<uint32_t>(fh.thickness * 6151.0F);
  980. rand_state ^= static_cast<uint32_t>(fh.coverage * 4099.0F);
  981. auto random01 = [&]() -> float {
  982. rand_state = rand_state * 1664525U + 1013904223U;
  983. return hash_01(rand_state);
  984. };
  985. auto jitter = [&](float amplitude) -> float {
  986. return (random01() - 0.5F) * 2.0F * amplitude;
  987. };
  988. float const beard_forward_tilt_norm = 0.32F;
  989. constexpr int k_max_facial_hair_strands = 24;
  990. int total_strands_emitted = 0;
  991. auto place_strands = [&](int rows, int segments, float jaw_span,
  992. float row_spacing_norm, float base_length_norm,
  993. float length_variation, float forward_bias_norm,
  994. float base_radius_norm) {
  995. if (rows <= 0 || segments <= 0) {
  996. return;
  997. }
  998. const float phi_half_range = std::max(0.35F, jaw_span * 0.5F);
  999. const float base_y_norm = chin_norm + 0.10F;
  1000. for (int row = 0; row < rows; ++row) {
  1001. float const row_t = (rows > 1) ? float(row) / float(rows - 1) : 0.0F;
  1002. float const target_y_norm =
  1003. std::clamp(base_y_norm + row_t * row_spacing_norm, -0.92F, 0.10F);
  1004. float const plane_radius =
  1005. std::sqrt(std::max(0.02F, 1.0F - target_y_norm * target_y_norm));
  1006. for (int seg = 0; seg < segments; ++seg) {
  1007. float const seg_t =
  1008. (segments > 1) ? float(seg) / float(segments - 1) : 0.5F;
  1009. float const base_phi = (seg_t - 0.5F) * jaw_span;
  1010. float const phi = std::clamp(base_phi + jitter(0.25F), -phi_half_range,
  1011. phi_half_range);
  1012. float const coverage_falloff =
  1013. 1.0F - std::abs(phi) / std::max(0.001F, phi_half_range);
  1014. float const keep_prob = std::clamp(
  1015. fh.coverage * (0.75F + coverage_falloff * 0.35F), 0.05F, 1.0F);
  1016. if (random01() > keep_prob) {
  1017. continue;
  1018. }
  1019. if (total_strands_emitted >= k_max_facial_hair_strands) {
  1020. return;
  1021. }
  1022. float const wrap_scale = 0.80F + (1.0F - row_t) * 0.20F;
  1023. float lateral_norm = plane_radius * std::sin(phi) * wrap_scale;
  1024. float forward_norm = plane_radius * std::cos(phi);
  1025. lateral_norm += jitter(0.06F);
  1026. forward_norm += jitter(0.08F);
  1027. float const y_norm = target_y_norm + jitter(0.05F);
  1028. QVector3D surface_dir(lateral_norm, y_norm,
  1029. forward_norm *
  1030. (0.75F + forward_bias_norm * 0.45F) +
  1031. (forward_bias_norm - 0.05F));
  1032. float const dir_len = surface_dir.length();
  1033. if (dir_len < 1e-4F) {
  1034. continue;
  1035. }
  1036. surface_dir /= dir_len;
  1037. float const shell = 1.02F + jitter(0.03F);
  1038. QVector3D const root = frame_local_position(frame, surface_dir * shell);
  1039. QVector3D local_dir(jitter(0.15F),
  1040. -(0.55F + row_t * 0.30F) + jitter(0.10F),
  1041. forward_bias_norm + beard_forward_tilt_norm +
  1042. row_t * 0.20F + jitter(0.12F));
  1043. QVector3D strand_dir =
  1044. frame.right * local_dir.x() + frame.up * local_dir.y() +
  1045. frame.forward * local_dir.z() - surface_dir * 0.25F;
  1046. if (strand_dir.lengthSquared() < 1e-6F) {
  1047. continue;
  1048. }
  1049. strand_dir.normalize();
  1050. float const strand_length = base_length_norm * fh.length *
  1051. (1.0F + length_variation * jitter(0.5F)) *
  1052. (1.0F + row_t * 0.25F);
  1053. if (strand_length < 0.05F) {
  1054. continue;
  1055. }
  1056. QVector3D const tip = root + strand_dir * (head_r * strand_length);
  1057. float const base_radius =
  1058. std::max(head_r * base_radius_norm * fh.thickness *
  1059. (0.7F + coverage_falloff * 0.35F),
  1060. head_r * 0.010F);
  1061. float const mid_radius = base_radius * 0.55F;
  1062. float const color_jitter = 0.85F + random01() * 0.30F;
  1063. QVector3D const root_color = saturate(hair_root * color_jitter);
  1064. QVector3D const tip_color = saturate(hair_tip * color_jitter);
  1065. QMatrix4x4 base_blob = sphere_at(ctx.model, root, base_radius * 0.95F);
  1066. out.mesh(get_unit_sphere(), base_blob, root_color, nullptr, 1.0F);
  1067. QVector3D const mid = root + (tip - root) * 0.40F;
  1068. out.mesh(get_unit_cylinder(),
  1069. cylinder_between(ctx.model, root, mid, base_radius),
  1070. root_color, nullptr, 1.0F);
  1071. out.mesh(get_unit_cone(), cone_from_to(ctx.model, mid, tip, mid_radius),
  1072. tip_color, nullptr, 1.0F);
  1073. ++total_strands_emitted;
  1074. }
  1075. }
  1076. };
  1077. auto place_mustache = [&](int segments, float base_length_norm,
  1078. float upward_bias_norm) {
  1079. if (segments <= 0) {
  1080. return;
  1081. }
  1082. float const mustache_y_norm = mouth_norm + upward_bias_norm - 0.04F;
  1083. float const phi_half_range = 0.55F;
  1084. for (int side = -1; side <= 1; side += 2) {
  1085. for (int seg = 0; seg < segments; ++seg) {
  1086. float const t =
  1087. (segments > 1) ? float(seg) / float(segments - 1) : 0.5F;
  1088. float const base_phi = (t - 0.5F) * (phi_half_range * 2.0F);
  1089. float const phi = std::clamp(base_phi + jitter(0.18F), -phi_half_range,
  1090. phi_half_range);
  1091. float const plane_radius = std::sqrt(
  1092. std::max(0.02F, 1.0F - mustache_y_norm * mustache_y_norm));
  1093. float lateral_norm = plane_radius * std::sin(phi);
  1094. float forward_norm = plane_radius * std::cos(phi);
  1095. lateral_norm += jitter(0.04F);
  1096. forward_norm += jitter(0.05F);
  1097. if (random01() > fh.coverage) {
  1098. continue;
  1099. }
  1100. if (total_strands_emitted >= k_max_facial_hair_strands) {
  1101. return;
  1102. }
  1103. QVector3D surface_dir(lateral_norm, mustache_y_norm + jitter(0.03F),
  1104. forward_norm * 0.85F + 0.20F);
  1105. float const dir_len = surface_dir.length();
  1106. if (dir_len < 1e-4F) {
  1107. continue;
  1108. }
  1109. surface_dir /= dir_len;
  1110. QVector3D const root =
  1111. frame_local_position(frame, surface_dir * (1.01F + jitter(0.02F)));
  1112. QVector3D const dir_local(side * (0.55F + jitter(0.12F)), jitter(0.06F),
  1113. 0.34F + jitter(0.08F));
  1114. QVector3D strand_dir =
  1115. frame.right * dir_local.x() + frame.up * dir_local.y() +
  1116. frame.forward * dir_local.z() - surface_dir * 0.20F;
  1117. if (strand_dir.lengthSquared() < 1e-6F) {
  1118. continue;
  1119. }
  1120. strand_dir.normalize();
  1121. float const strand_length =
  1122. base_length_norm * fh.length * (1.0F + jitter(0.35F));
  1123. QVector3D const tip = root + strand_dir * (head_r * strand_length);
  1124. float const base_radius =
  1125. std::max(head_r * 0.028F * fh.thickness, head_r * 0.0065F);
  1126. float const mid_radius = base_radius * 0.45F;
  1127. float const color_jitter = 0.92F + random01() * 0.18F;
  1128. QVector3D const root_color =
  1129. saturate(hair_root * (color_jitter * 0.95F));
  1130. QVector3D const tip_color = saturate(hair_tip * (color_jitter * 1.02F));
  1131. out.mesh(get_unit_sphere(),
  1132. sphere_at(ctx.model, root, base_radius * 0.7F), root_color,
  1133. nullptr, 1.0F);
  1134. QVector3D const mid = root + (tip - root) * 0.5F;
  1135. out.mesh(get_unit_cylinder(),
  1136. cylinder_between(ctx.model, root, mid, base_radius * 0.85F),
  1137. root_color, nullptr, 1.0F);
  1138. out.mesh(get_unit_cone(), cone_from_to(ctx.model, mid, tip, mid_radius),
  1139. tip_color, nullptr, 1.0F);
  1140. ++total_strands_emitted;
  1141. }
  1142. }
  1143. };
  1144. switch (fh.style) {
  1145. case FacialHairStyle::Stubble: {
  1146. place_strands(1, 11, 2.0F, 0.12F, 0.28F, 0.30F, 0.08F, 0.035F);
  1147. break;
  1148. }
  1149. case FacialHairStyle::ShortBeard: {
  1150. place_strands(3, 14, 2.6F, 0.18F, 0.58F, 0.38F, 0.12F, 0.055F);
  1151. break;
  1152. }
  1153. case FacialHairStyle::FullBeard:
  1154. case FacialHairStyle::LongBeard: {
  1155. bool const is_long = (fh.style == FacialHairStyle::LongBeard);
  1156. if (is_long) {
  1157. place_strands(5, 20, 3.0F, 0.24F, 1.00F, 0.48F, 0.18F, 0.060F);
  1158. } else {
  1159. place_strands(4, 18, 2.8F, 0.22F, 0.85F, 0.42F, 0.16F, 0.058F);
  1160. }
  1161. break;
  1162. }
  1163. case FacialHairStyle::Goatee: {
  1164. place_strands(2, 8, 1.8F, 0.16F, 0.70F, 0.34F, 0.14F, 0.055F);
  1165. break;
  1166. }
  1167. case FacialHairStyle::Mustache: {
  1168. place_mustache(5, 0.32F, 0.05F);
  1169. break;
  1170. }
  1171. case FacialHairStyle::MustacheAndBeard: {
  1172. FacialHairParams mustache_only = fh;
  1173. mustache_only.style = FacialHairStyle::Mustache;
  1174. FacialHairParams beard_only = fh;
  1175. beard_only.style = FacialHairStyle::ShortBeard;
  1176. HumanoidVariant v_mustache = v;
  1177. v_mustache.facial_hair = mustache_only;
  1178. draw_facial_hair(ctx, v_mustache, pose, out);
  1179. HumanoidVariant v_beard = v;
  1180. v_beard.facial_hair = beard_only;
  1181. draw_facial_hair(ctx, v_beard, pose, out);
  1182. break;
  1183. }
  1184. case FacialHairStyle::None:
  1185. default:
  1186. break;
  1187. }
  1188. }
  1189. void HumanoidRendererBase::draw_simplified_body(const DrawContext &ctx,
  1190. const HumanoidVariant &v,
  1191. HumanoidPose &pose,
  1192. ISubmitter &out) const {
  1193. using HP = HumanProportions;
  1194. QVector3D const scaling = get_proportion_scaling();
  1195. float const width_scale = scaling.x();
  1196. float const height_scale = scaling.y();
  1197. float const torso_scale = get_torso_scale();
  1198. QVector3D right_axis = pose.shoulder_r - pose.shoulder_l;
  1199. right_axis.setY(0.0F);
  1200. right_axis.setZ(0.0F);
  1201. if (right_axis.lengthSquared() < HP::EPSILON_VECTOR) {
  1202. right_axis = QVector3D(1.0F, 0.0F, 0.0F);
  1203. }
  1204. right_axis.normalize();
  1205. if (right_axis.x() < 0.0F) {
  1206. right_axis = -right_axis;
  1207. }
  1208. QVector3D const up_axis(0.0F, 1.0F, 0.0F);
  1209. QVector3D forward_axis = QVector3D::crossProduct(right_axis, up_axis);
  1210. if (forward_axis.lengthSquared() < HP::EPSILON_VECTOR) {
  1211. forward_axis = QVector3D(0.0F, 0.0F, 1.0F);
  1212. }
  1213. forward_axis.normalize();
  1214. QVector3D const shoulder_mid = (pose.shoulder_l + pose.shoulder_r) * 0.5F;
  1215. const float y_shoulder = shoulder_mid.y();
  1216. const float y_neck = pose.neck_base.y();
  1217. const float shoulder_half_span =
  1218. 0.5F * std::abs(pose.shoulder_r.x() - pose.shoulder_l.x());
  1219. const float torso_r_base =
  1220. std::max(HP::TORSO_TOP_R, shoulder_half_span * 0.95F);
  1221. const float torso_r = torso_r_base * torso_scale;
  1222. float const depth_scale = scaling.z();
  1223. const float torso_depth_factor =
  1224. std::clamp(HP::TORSO_DEPTH_FACTOR_BASE + (depth_scale - 1.0F) * 0.20F,
  1225. HP::TORSO_DEPTH_FACTOR_MIN, HP::TORSO_DEPTH_FACTOR_MAX);
  1226. float torso_depth = torso_r * torso_depth_factor;
  1227. const float y_top_cover =
  1228. std::max(y_shoulder + 0.00F, y_neck + HP::TORSO_TOP_COVER_OFFSET);
  1229. const float upper_arm_r = HP::UPPER_ARM_R * width_scale;
  1230. const float fore_arm_r = HP::FORE_ARM_R * width_scale;
  1231. const float thigh_r = HP::UPPER_LEG_R * width_scale;
  1232. const float shin_r = HP::LOWER_LEG_R * width_scale;
  1233. float const torso_x = (shoulder_mid.x() + pose.pelvis_pos.x()) * 0.5F;
  1234. constexpr float torso_z = 0.0F;
  1235. QVector3D const tunic_top{torso_x, y_top_cover - 0.006F, torso_z};
  1236. QVector3D const tunic_bot{torso_x, pose.pelvis_pos.y() - 0.05F, torso_z};
  1237. QMatrix4x4 torso_transform =
  1238. cylinder_between(ctx.model, tunic_top, tunic_bot, 1.0F);
  1239. torso_transform.scale(torso_r, 1.0F, torso_depth);
  1240. Mesh *torso_mesh = torso_mesh_without_bottom_cap();
  1241. if (torso_mesh != nullptr) {
  1242. out.mesh(torso_mesh, torso_transform, v.palette.cloth, nullptr, 1.0F);
  1243. }
  1244. float const head_r = pose.head_r;
  1245. QMatrix4x4 head_transform = ctx.model;
  1246. head_transform.translate(pose.head_pos);
  1247. head_transform.scale(head_r);
  1248. out.mesh(get_unit_sphere(), head_transform, v.palette.skin, nullptr, 1.0F);
  1249. out.mesh(get_unit_cylinder(),
  1250. cylinder_between(ctx.model, pose.shoulder_l, pose.hand_l,
  1251. (upper_arm_r + fore_arm_r) * 0.5F),
  1252. v.palette.cloth, nullptr, 1.0F);
  1253. out.mesh(get_unit_cylinder(),
  1254. cylinder_between(ctx.model, pose.shoulder_r, pose.hand_r,
  1255. (upper_arm_r + fore_arm_r) * 0.5F),
  1256. v.palette.cloth, nullptr, 1.0F);
  1257. QVector3D const hip_l =
  1258. pose.pelvis_pos +
  1259. QVector3D(-HP::HIP_LATERAL_OFFSET, HP::HIP_VERTICAL_OFFSET, 0.0F);
  1260. QVector3D const hip_r =
  1261. pose.pelvis_pos +
  1262. QVector3D(HP::HIP_LATERAL_OFFSET, HP::HIP_VERTICAL_OFFSET, 0.0F);
  1263. out.mesh(get_unit_cylinder(),
  1264. cylinder_between(ctx.model, hip_l, pose.foot_l,
  1265. (thigh_r + shin_r) * 0.5F),
  1266. v.palette.cloth * 0.92F, nullptr, 1.0F);
  1267. out.mesh(get_unit_cylinder(),
  1268. cylinder_between(ctx.model, hip_r, pose.foot_r,
  1269. (thigh_r + shin_r) * 0.5F),
  1270. v.palette.cloth * 0.92F, nullptr, 1.0F);
  1271. }
  1272. void HumanoidRendererBase::draw_minimal_body(const DrawContext &ctx,
  1273. const HumanoidVariant &v,
  1274. const HumanoidPose &pose,
  1275. ISubmitter &out) const {
  1276. using HP = HumanProportions;
  1277. QVector3D const top = pose.head_pos + QVector3D(0.0F, pose.head_r, 0.0F);
  1278. QVector3D const bot = (pose.foot_l + pose.foot_r) * 0.5F;
  1279. float const body_radius = HP::TORSO_TOP_R * get_torso_scale();
  1280. out.mesh(get_unit_capsule(),
  1281. capsule_between(ctx.model, top, bot, body_radius), v.palette.cloth,
  1282. nullptr, 1.0F);
  1283. }
  1284. namespace {
  1285. auto resolve_renderer_for_submitter(ISubmitter &out) -> Renderer * {
  1286. if (auto *renderer = dynamic_cast<Renderer *>(&out)) {
  1287. return renderer;
  1288. }
  1289. if (auto *batch = dynamic_cast<BatchingSubmitter *>(&out)) {
  1290. return dynamic_cast<Renderer *>(batch->fallback_submitter());
  1291. }
  1292. return nullptr;
  1293. }
  1294. } // namespace
  1295. void HumanoidRendererBase::render(const DrawContext &ctx,
  1296. ISubmitter &out) const {
  1297. AnimationInputs anim = sample_anim_state(ctx);
  1298. if (ctx.template_prewarm) {
  1299. if (ctx.renderer_id.empty() || ctx.animation_override == nullptr ||
  1300. !ctx.force_humanoid_lod || !ctx.has_variant_override) {
  1301. return;
  1302. }
  1303. auto *unit_comp =
  1304. ctx.entity ? ctx.entity->get_component<Engine::Core::UnitComponent>()
  1305. : nullptr;
  1306. std::uint32_t owner_id =
  1307. (unit_comp != nullptr) ? static_cast<std::uint32_t>(unit_comp->owner_id)
  1308. : 0U;
  1309. bool is_mounted_spawn = false;
  1310. if (unit_comp != nullptr) {
  1311. using Game::Units::SpawnType;
  1312. auto const st = unit_comp->spawn_type;
  1313. is_mounted_spawn =
  1314. (st == SpawnType::MountedKnight || st == SpawnType::HorseArcher ||
  1315. st == SpawnType::HorseSpearman);
  1316. }
  1317. std::uint8_t attack_variant = 0;
  1318. if (ctx.has_attack_variant_override) {
  1319. attack_variant = ctx.attack_variant_override;
  1320. }
  1321. AnimKey anim_key =
  1322. make_anim_key(*ctx.animation_override, 0.0F, attack_variant);
  1323. TemplateKey key;
  1324. key.renderer_id = ctx.renderer_id;
  1325. key.owner_id = owner_id;
  1326. key.lod = static_cast<std::uint8_t>(ctx.forced_humanoid_lod);
  1327. HorseLOD mount_lod = HorseLOD::Full;
  1328. key.mount_lod = 0;
  1329. if (is_mounted_spawn) {
  1330. mount_lod = ctx.force_horse_lod
  1331. ? ctx.forced_horse_lod
  1332. : static_cast<HorseLOD>(ctx.forced_humanoid_lod);
  1333. key.mount_lod = static_cast<std::uint8_t>(mount_lod);
  1334. }
  1335. key.variant = ctx.variant_override;
  1336. key.attack_variant = anim_key.attack_variant;
  1337. key.state = anim_key.state;
  1338. key.combat_phase = anim_key.combat_phase;
  1339. key.frame = anim_key.frame;
  1340. const TemplateCache::DenseDomainHandle dense_domain =
  1341. TemplateCache::instance().get_dense_domain_handle(
  1342. key.renderer_id, key.owner_id, key.lod, key.mount_lod);
  1343. const std::size_t dense_slot =
  1344. TemplateCache::dense_slot_index(key.variant, anim_key);
  1345. (void)TemplateCache::instance().get_or_build_dense(
  1346. dense_domain, dense_slot, key, [&]() -> PoseTemplate {
  1347. thread_local TemplateRecorder recorder;
  1348. recorder.reset(320);
  1349. recorder.set_current_shader(nullptr);
  1350. if (auto *outer = dynamic_cast<Renderer *>(&out)) {
  1351. recorder.set_current_shader(outer->get_current_shader());
  1352. }
  1353. Engine::Core::Entity template_entity(1);
  1354. auto *templ_unit =
  1355. template_entity.add_component<Engine::Core::UnitComponent>();
  1356. if (unit_comp != nullptr) {
  1357. *templ_unit = *unit_comp;
  1358. }
  1359. templ_unit->max_health =
  1360. (templ_unit->max_health > 0) ? templ_unit->max_health : 1;
  1361. templ_unit->health = templ_unit->max_health;
  1362. auto *templ_transform =
  1363. template_entity.add_component<Engine::Core::TransformComponent>();
  1364. templ_transform->position = {0.0F, 0.0F, 0.0F};
  1365. templ_transform->rotation = {0.0F, 0.0F, 0.0F};
  1366. templ_transform->scale = {1.0F, 1.0F, 1.0F};
  1367. if (auto *renderable =
  1368. ctx.entity
  1369. ? ctx.entity
  1370. ->get_component<Engine::Core::RenderableComponent>()
  1371. : nullptr) {
  1372. auto *templ_renderable =
  1373. template_entity
  1374. .add_component<Engine::Core::RenderableComponent>(
  1375. renderable->mesh_path, renderable->texture_path);
  1376. templ_renderable->renderer_id = renderable->renderer_id;
  1377. templ_renderable->mesh = renderable->mesh;
  1378. templ_renderable->visible = true;
  1379. templ_renderable->color = renderable->color;
  1380. }
  1381. DrawContext build_ctx = ctx;
  1382. build_ctx.entity = &template_entity;
  1383. build_ctx.world = nullptr;
  1384. build_ctx.model = QMatrix4x4();
  1385. build_ctx.camera = nullptr;
  1386. build_ctx.selected = false;
  1387. build_ctx.hovered = false;
  1388. build_ctx.allow_template_cache = false;
  1389. build_ctx.force_humanoid_lod = true;
  1390. build_ctx.forced_humanoid_lod = ctx.forced_humanoid_lod;
  1391. build_ctx.force_horse_lod = is_mounted_spawn;
  1392. if (is_mounted_spawn) {
  1393. build_ctx.forced_horse_lod = static_cast<HorseLOD>(key.mount_lod);
  1394. }
  1395. build_ctx.has_seed_override = true;
  1396. build_ctx.seed_override =
  1397. resolve_variant_seed(unit_comp, key.variant);
  1398. build_ctx.has_attack_variant_override = true;
  1399. build_ctx.attack_variant_override = key.attack_variant;
  1400. build_ctx.force_single_soldier = true;
  1401. build_ctx.skip_ground_offset = true;
  1402. AnimationInputs build_anim = make_animation_inputs(anim_key);
  1403. build_ctx.animation_override = &build_anim;
  1404. render_procedural(build_ctx, build_anim, recorder);
  1405. PoseTemplate built;
  1406. built.commands = recorder.take_commands();
  1407. return built;
  1408. });
  1409. return;
  1410. }
  1411. if (!ctx.allow_template_cache || ctx.renderer_id.empty() ||
  1412. ctx.entity == nullptr) {
  1413. render_procedural(ctx, anim, out);
  1414. return;
  1415. }
  1416. auto *unit_comp = ctx.entity->get_component<Engine::Core::UnitComponent>();
  1417. auto *transform_comp =
  1418. ctx.entity->get_component<Engine::Core::TransformComponent>();
  1419. if (unit_comp == nullptr || transform_comp == nullptr) {
  1420. render_procedural(ctx, anim, out);
  1421. return;
  1422. }
  1423. FormationParams const formation = resolve_formation(ctx);
  1424. const int rows =
  1425. (formation.individuals_per_unit + formation.max_per_row - 1) /
  1426. formation.max_per_row;
  1427. int cols = formation.max_per_row;
  1428. int effective_rows = rows;
  1429. if (ctx.force_single_soldier) {
  1430. cols = 1;
  1431. effective_rows = 1;
  1432. }
  1433. int visible_count = rows * cols;
  1434. int max_health = std::max(1, unit_comp->max_health);
  1435. float ratio = std::clamp(unit_comp->health / float(max_health), 0.0F, 1.0F);
  1436. visible_count = std::max(1, (int)std::ceil(ratio * float(rows * cols)));
  1437. if (visible_count <= 0) {
  1438. return;
  1439. }
  1440. s_render_stats.soldiers_total += visible_count;
  1441. bool is_mounted_spawn = false;
  1442. if (unit_comp != nullptr) {
  1443. using Game::Units::SpawnType;
  1444. auto const st = unit_comp->spawn_type;
  1445. is_mounted_spawn =
  1446. (st == SpawnType::MountedKnight || st == SpawnType::HorseArcher ||
  1447. st == SpawnType::HorseSpearman);
  1448. }
  1449. FormationCalculatorFactory::Nation nation =
  1450. FormationCalculatorFactory::Nation::Roman;
  1451. if (unit_comp != nullptr &&
  1452. unit_comp->nation_id == Game::Systems::NationID::Carthage) {
  1453. nation = FormationCalculatorFactory::Nation::Carthage;
  1454. }
  1455. FormationCalculatorFactory::UnitCategory category =
  1456. resolve_formation_category(unit_comp, anim);
  1457. std::uint32_t layout_seed = resolve_layout_seed(unit_comp, ctx.entity);
  1458. auto &layout_entry = get_or_build_unit_layout(
  1459. reinterpret_cast<std::uintptr_t>(ctx.entity), formation, nation, category,
  1460. layout_seed, rows, cols);
  1461. float entity_ground_offset =
  1462. resolve_entity_ground_offset(ctx, unit_comp, transform_comp);
  1463. Renderer *renderer = resolve_renderer_for_submitter(out);
  1464. Shader *last_shader = nullptr;
  1465. const auto &gfx_settings = Render::GraphicsSettings::instance();
  1466. const float base_pos_x = transform_comp->position.x;
  1467. const float base_pos_y = transform_comp->position.y;
  1468. const float base_pos_z = transform_comp->position.z;
  1469. const float scale_x = transform_comp->scale.x;
  1470. const float scale_y = transform_comp->scale.y;
  1471. const float scale_z = transform_comp->scale.z;
  1472. const float base_yaw = transform_comp->rotation.y;
  1473. const float ground_offset_scaled = entity_ground_offset * scale_y;
  1474. const bool has_camera = (ctx.camera != nullptr);
  1475. const QVector3D camera_position =
  1476. has_camera ? ctx.camera->get_position() : QVector3D();
  1477. auto &terrain_service = Game::Map::TerrainService::instance();
  1478. const bool terrain_initialized = terrain_service.is_initialized();
  1479. QVector2D shadow_dir_for_use(0.0F, 1.0F);
  1480. {
  1481. QVector2D light_dir_xz(k_shadow_light_dir.x(), k_shadow_light_dir.z());
  1482. if (light_dir_xz.lengthSquared() >= 1e-6F) {
  1483. light_dir_xz.normalize();
  1484. shadow_dir_for_use = -light_dir_xz;
  1485. if (shadow_dir_for_use.lengthSquared() >= 1e-6F) {
  1486. shadow_dir_for_use.normalize();
  1487. } else {
  1488. shadow_dir_for_use = QVector2D(0.0F, 1.0F);
  1489. }
  1490. }
  1491. }
  1492. Shader *shadow_shader = nullptr;
  1493. Mesh *shadow_quad_mesh = nullptr;
  1494. const bool can_render_shadow_mesh =
  1495. gfx_settings.shadows_enabled() && ctx.backend != nullptr &&
  1496. ctx.resources != nullptr && terrain_initialized;
  1497. if (can_render_shadow_mesh) {
  1498. shadow_shader = ctx.backend->shader(QStringLiteral("troop_shadow"));
  1499. shadow_quad_mesh = ctx.resources->quad();
  1500. }
  1501. auto fast_random = [](uint32_t &state) -> float {
  1502. state = state * 1664525U + 1013904223U;
  1503. return float(state & 0x7FFFFFU) / float(0x7FFFFFU);
  1504. };
  1505. const std::size_t soldier_count = std::min<std::size_t>(
  1506. layout_entry.soldiers.size(), static_cast<std::size_t>(visible_count));
  1507. const std::uint32_t template_owner_id =
  1508. static_cast<std::uint32_t>(unit_comp->owner_id);
  1509. std::array<std::array<TemplateCache::DenseDomainHandle, 4>, 4>
  1510. dense_domains{};
  1511. std::array<std::array<bool, 4>, 4> dense_domains_ready{};
  1512. auto dense_domain_for =
  1513. [&](HumanoidLOD lod,
  1514. HorseLOD mount_lod_val) -> TemplateCache::DenseDomainHandle {
  1515. const std::size_t lod_idx =
  1516. std::min<std::size_t>(static_cast<std::size_t>(lod), 3);
  1517. const std::size_t mount_idx =
  1518. std::min<std::size_t>(static_cast<std::size_t>(mount_lod_val), 3);
  1519. if (!dense_domains_ready[lod_idx][mount_idx]) {
  1520. const std::uint8_t lod_u8 = static_cast<std::uint8_t>(lod_idx);
  1521. const std::uint8_t mount_u8 = static_cast<std::uint8_t>(mount_idx);
  1522. dense_domains[lod_idx][mount_idx] =
  1523. TemplateCache::instance().get_dense_domain_handle(
  1524. ctx.renderer_id, template_owner_id, lod_u8, mount_u8);
  1525. dense_domains_ready[lod_idx][mount_idx] = true;
  1526. }
  1527. return dense_domains[lod_idx][mount_idx];
  1528. };
  1529. for (std::size_t idx = 0; idx < soldier_count; ++idx) {
  1530. const SoldierLayout &slot = layout_entry.soldiers[idx];
  1531. float offset_x = slot.offset_x;
  1532. float offset_z = slot.offset_z;
  1533. float applied_vertical_jitter = slot.vertical_jitter;
  1534. float applied_yaw_offset = slot.yaw_offset;
  1535. float phase_offset = slot.phase_offset;
  1536. uint32_t rng_state = slot.inst_seed;
  1537. if (anim.is_attacking && anim.is_melee) {
  1538. float const combat_jitter_x =
  1539. (fast_random(rng_state) - 0.5F) * formation.spacing * 0.4F;
  1540. float const combat_jitter_z =
  1541. (fast_random(rng_state) - 0.5F) * formation.spacing * 0.3F;
  1542. float const sway_time = anim.time + phase_offset * 2.0F;
  1543. float const sway_x = std::sin(sway_time * 1.5F) * 0.05F;
  1544. float const sway_z = std::cos(sway_time * 1.2F) * 0.04F;
  1545. offset_x += combat_jitter_x + sway_x;
  1546. offset_z += combat_jitter_z + sway_z;
  1547. float const combat_yaw = (fast_random(rng_state) - 0.5F) * 15.0F;
  1548. applied_yaw_offset += combat_yaw;
  1549. }
  1550. const float applied_yaw = base_yaw + applied_yaw_offset;
  1551. const float yaw_rad = qDegreesToRadians(applied_yaw);
  1552. const float cos_yaw = std::cos(yaw_rad);
  1553. const float sin_yaw = std::sin(yaw_rad);
  1554. const float local_x = offset_x * scale_x;
  1555. const float local_z = offset_z * scale_z;
  1556. QVector3D const soldier_world_pos(
  1557. base_pos_x + (cos_yaw * local_x) + (sin_yaw * local_z),
  1558. base_pos_y + (applied_vertical_jitter * scale_y) - ground_offset_scaled,
  1559. base_pos_z - (sin_yaw * local_x) + (cos_yaw * local_z));
  1560. constexpr float k_soldier_cull_radius = 0.6F;
  1561. if (has_camera &&
  1562. !ctx.camera->is_in_frustum(soldier_world_pos, k_soldier_cull_radius)) {
  1563. ++s_render_stats.soldiers_skipped_frustum;
  1564. continue;
  1565. }
  1566. HumanoidLOD soldier_lod = HumanoidLOD::Full;
  1567. float soldier_distance = 0.0F;
  1568. if (ctx.force_humanoid_lod) {
  1569. soldier_lod = ctx.forced_humanoid_lod;
  1570. } else if (has_camera) {
  1571. soldier_distance = (soldier_world_pos - camera_position).length();
  1572. soldier_lod = calculate_humanoid_lod(soldier_distance);
  1573. if (soldier_lod == HumanoidLOD::Billboard) {
  1574. ++s_render_stats.soldiers_skipped_lod;
  1575. continue;
  1576. }
  1577. soldier_lod =
  1578. Render::VisibilityBudgetTracker::instance().request_humanoid_lod(
  1579. soldier_lod);
  1580. }
  1581. if (soldier_distance > 0.0F) {
  1582. if (soldier_lod == HumanoidLOD::Reduced &&
  1583. soldier_distance > k_temporal_skip_distance_reduced) {
  1584. if (!should_render_temporal(s_current_frame, slot.inst_seed,
  1585. k_temporal_skip_period_reduced)) {
  1586. ++s_render_stats.soldiers_skipped_temporal;
  1587. continue;
  1588. }
  1589. }
  1590. if (soldier_lod == HumanoidLOD::Minimal &&
  1591. soldier_distance > k_temporal_skip_distance_minimal) {
  1592. if (!should_render_temporal(s_current_frame, slot.inst_seed,
  1593. k_temporal_skip_period_minimal)) {
  1594. ++s_render_stats.soldiers_skipped_temporal;
  1595. continue;
  1596. }
  1597. }
  1598. }
  1599. ++s_render_stats.soldiers_rendered;
  1600. switch (soldier_lod) {
  1601. case HumanoidLOD::Full:
  1602. ++s_render_stats.lod_full;
  1603. break;
  1604. case HumanoidLOD::Reduced:
  1605. ++s_render_stats.lod_reduced;
  1606. break;
  1607. case HumanoidLOD::Minimal:
  1608. ++s_render_stats.lod_minimal;
  1609. break;
  1610. case HumanoidLOD::Billboard:
  1611. break;
  1612. }
  1613. QMatrix4x4 inst_model = make_yaw_scale_model(
  1614. cos_yaw, sin_yaw, scale_x, scale_y, scale_z, soldier_world_pos);
  1615. std::uint8_t variant_key = resolve_variant_key_from_seed(slot.inst_seed);
  1616. std::uint8_t attack_variant =
  1617. resolve_attack_variant_from_seed(slot.inst_seed);
  1618. AnimKey anim_key = make_anim_key(anim, phase_offset, attack_variant);
  1619. HorseLOD mount_lod = HorseLOD::Full;
  1620. if (is_mounted_spawn) {
  1621. if (ctx.force_horse_lod) {
  1622. mount_lod = ctx.forced_horse_lod;
  1623. } else {
  1624. mount_lod = static_cast<HorseLOD>(soldier_lod);
  1625. }
  1626. }
  1627. TemplateKey key;
  1628. key.renderer_id = ctx.renderer_id;
  1629. key.owner_id = template_owner_id;
  1630. key.lod = static_cast<std::uint8_t>(soldier_lod);
  1631. key.mount_lod = is_mounted_spawn ? static_cast<std::uint8_t>(mount_lod) : 0;
  1632. key.variant = variant_key;
  1633. key.attack_variant = anim_key.attack_variant;
  1634. key.state = anim_key.state;
  1635. key.combat_phase = anim_key.combat_phase;
  1636. key.frame = anim_key.frame;
  1637. const TemplateCache::DenseDomainHandle dense_domain = dense_domain_for(
  1638. soldier_lod, is_mounted_spawn ? mount_lod : HorseLOD::Full);
  1639. const std::size_t dense_slot =
  1640. TemplateCache::dense_slot_index(variant_key, anim_key);
  1641. const PoseTemplate *tpl = TemplateCache::instance().get_or_build_dense(
  1642. dense_domain, dense_slot, key, [&]() -> PoseTemplate {
  1643. thread_local TemplateRecorder recorder;
  1644. recorder.reset(320);
  1645. recorder.set_current_shader(nullptr);
  1646. if (auto *outer = dynamic_cast<Renderer *>(&out)) {
  1647. recorder.set_current_shader(outer->get_current_shader());
  1648. }
  1649. Engine::Core::Entity template_entity(1);
  1650. auto *templ_unit =
  1651. template_entity.add_component<Engine::Core::UnitComponent>();
  1652. *templ_unit = *unit_comp;
  1653. templ_unit->max_health =
  1654. (templ_unit->max_health > 0) ? templ_unit->max_health : 1;
  1655. templ_unit->health = templ_unit->max_health;
  1656. auto *templ_transform =
  1657. template_entity.add_component<Engine::Core::TransformComponent>();
  1658. templ_transform->position = {0.0F, 0.0F, 0.0F};
  1659. templ_transform->rotation = {0.0F, 0.0F, 0.0F};
  1660. templ_transform->scale = {1.0F, 1.0F, 1.0F};
  1661. if (auto *renderable =
  1662. ctx.entity
  1663. ->get_component<Engine::Core::RenderableComponent>()) {
  1664. auto *templ_renderable =
  1665. template_entity
  1666. .add_component<Engine::Core::RenderableComponent>(
  1667. renderable->mesh_path, renderable->texture_path);
  1668. templ_renderable->renderer_id = renderable->renderer_id;
  1669. templ_renderable->mesh = renderable->mesh;
  1670. templ_renderable->visible = true;
  1671. templ_renderable->color = renderable->color;
  1672. }
  1673. DrawContext build_ctx = ctx;
  1674. build_ctx.entity = &template_entity;
  1675. build_ctx.world = nullptr;
  1676. build_ctx.model = QMatrix4x4();
  1677. build_ctx.camera = nullptr;
  1678. build_ctx.selected = false;
  1679. build_ctx.hovered = false;
  1680. build_ctx.allow_template_cache = false;
  1681. build_ctx.force_humanoid_lod = true;
  1682. build_ctx.forced_humanoid_lod = soldier_lod;
  1683. build_ctx.force_horse_lod = is_mounted_spawn;
  1684. if (is_mounted_spawn) {
  1685. build_ctx.forced_horse_lod = mount_lod;
  1686. }
  1687. build_ctx.has_seed_override = true;
  1688. build_ctx.seed_override =
  1689. resolve_variant_seed(unit_comp, variant_key);
  1690. build_ctx.has_attack_variant_override = true;
  1691. build_ctx.attack_variant_override = anim_key.attack_variant;
  1692. build_ctx.force_single_soldier = true;
  1693. build_ctx.skip_ground_offset = true;
  1694. AnimationInputs build_anim = make_animation_inputs(anim_key);
  1695. build_ctx.animation_override = &build_anim;
  1696. render_procedural(build_ctx, build_anim, recorder);
  1697. PoseTemplate built;
  1698. built.commands = recorder.take_commands();
  1699. return built;
  1700. });
  1701. if (tpl == nullptr || tpl->commands.empty()) {
  1702. render_procedural(ctx, anim, out);
  1703. return;
  1704. }
  1705. for (const auto &cmd : tpl->commands) {
  1706. if (renderer != nullptr && cmd.shader != last_shader) {
  1707. renderer->set_current_shader(cmd.shader);
  1708. last_shader = cmd.shader;
  1709. }
  1710. QMatrix4x4 world_model =
  1711. Render::Geom::multiply_affine(inst_model, cmd.local_model);
  1712. out.mesh(cmd.mesh, world_model, cmd.color, cmd.texture, cmd.alpha,
  1713. cmd.material_id);
  1714. }
  1715. const bool should_render_shadow =
  1716. (shadow_shader != nullptr) && (shadow_quad_mesh != nullptr) &&
  1717. (soldier_lod == HumanoidLOD::Full ||
  1718. soldier_lod == HumanoidLOD::Reduced) &&
  1719. soldier_distance < gfx_settings.shadow_max_distance();
  1720. if (should_render_shadow) {
  1721. float const shadow_size =
  1722. is_mounted_spawn ? k_shadow_size_mounted : k_shadow_size_infantry;
  1723. float depth_boost = 1.0F;
  1724. float width_boost = 1.0F;
  1725. using Game::Units::SpawnType;
  1726. switch (unit_comp->spawn_type) {
  1727. case SpawnType::Spearman:
  1728. depth_boost = 1.8F;
  1729. width_boost = 0.95F;
  1730. break;
  1731. case SpawnType::HorseSpearman:
  1732. depth_boost = 2.1F;
  1733. width_boost = 1.05F;
  1734. break;
  1735. case SpawnType::Archer:
  1736. case SpawnType::HorseArcher:
  1737. depth_boost = 1.2F;
  1738. width_boost = 0.95F;
  1739. break;
  1740. default:
  1741. break;
  1742. }
  1743. float const shadow_width =
  1744. shadow_size * (is_mounted_spawn ? 1.05F : 1.0F) * width_boost;
  1745. float const shadow_depth =
  1746. shadow_size * (is_mounted_spawn ? 1.30F : 1.10F) * depth_boost;
  1747. QVector3D const inst_pos = soldier_world_pos;
  1748. float const shadow_y =
  1749. terrain_service.get_terrain_height(inst_pos.x(), inst_pos.z());
  1750. float const shadow_offset = shadow_depth * 1.25F;
  1751. QVector2D const offset_2d = shadow_dir_for_use * shadow_offset;
  1752. float const light_yaw_deg = qRadiansToDegrees(std::atan2(
  1753. double(shadow_dir_for_use.x()), double(shadow_dir_for_use.y())));
  1754. QMatrix4x4 shadow_model;
  1755. shadow_model.translate(inst_pos.x() + offset_2d.x(),
  1756. shadow_y + k_shadow_ground_offset,
  1757. inst_pos.z() + offset_2d.y());
  1758. shadow_model.rotate(light_yaw_deg, 0.0F, 1.0F, 0.0F);
  1759. shadow_model.rotate(-90.0F, 1.0F, 0.0F, 0.0F);
  1760. shadow_model.scale(shadow_width, shadow_depth, 1.0F);
  1761. if (renderer != nullptr) {
  1762. Shader *previous_shader = renderer->get_current_shader();
  1763. renderer->set_current_shader(shadow_shader);
  1764. shadow_shader->set_uniform(QStringLiteral("u_lightDir"),
  1765. shadow_dir_for_use);
  1766. out.mesh(shadow_quad_mesh, shadow_model, QVector3D(0.0F, 0.0F, 0.0F),
  1767. nullptr, k_shadow_base_alpha, 0);
  1768. renderer->set_current_shader(previous_shader);
  1769. last_shader = previous_shader;
  1770. }
  1771. }
  1772. }
  1773. if (renderer != nullptr) {
  1774. renderer->set_current_shader(nullptr);
  1775. }
  1776. }
  1777. void HumanoidRendererBase::render_procedural(const DrawContext &ctx,
  1778. const AnimationInputs &anim,
  1779. ISubmitter &out) const {
  1780. FormationParams const formation = resolve_formation(ctx);
  1781. Engine::Core::UnitComponent *unit_comp = nullptr;
  1782. if (ctx.entity != nullptr) {
  1783. unit_comp = ctx.entity->get_component<Engine::Core::UnitComponent>();
  1784. }
  1785. Engine::Core::MovementComponent *movement_comp = nullptr;
  1786. Engine::Core::TransformComponent *transform_comp = nullptr;
  1787. if (ctx.entity != nullptr) {
  1788. movement_comp =
  1789. ctx.entity->get_component<Engine::Core::MovementComponent>();
  1790. transform_comp =
  1791. ctx.entity->get_component<Engine::Core::TransformComponent>();
  1792. }
  1793. float entity_ground_offset =
  1794. resolve_entity_ground_offset(ctx, unit_comp, transform_comp);
  1795. uint32_t seed = 0U;
  1796. if (ctx.has_seed_override) {
  1797. seed = ctx.seed_override;
  1798. } else {
  1799. if (unit_comp != nullptr) {
  1800. seed ^= uint32_t(unit_comp->owner_id * 2654435761U);
  1801. }
  1802. if (ctx.entity != nullptr) {
  1803. seed ^= uint32_t(reinterpret_cast<uintptr_t>(ctx.entity) & 0xFFFFFFFFU);
  1804. }
  1805. }
  1806. const int rows =
  1807. (formation.individuals_per_unit + formation.max_per_row - 1) /
  1808. formation.max_per_row;
  1809. int cols = formation.max_per_row;
  1810. int effective_rows = rows;
  1811. if (ctx.force_single_soldier) {
  1812. cols = 1;
  1813. effective_rows = 1;
  1814. }
  1815. bool is_mounted_spawn = false;
  1816. if (unit_comp != nullptr) {
  1817. using Game::Units::SpawnType;
  1818. auto const st = unit_comp->spawn_type;
  1819. is_mounted_spawn =
  1820. (st == SpawnType::MountedKnight || st == SpawnType::HorseArcher ||
  1821. st == SpawnType::HorseSpearman);
  1822. }
  1823. int visible_count = effective_rows * cols;
  1824. if (!ctx.force_single_soldier && unit_comp != nullptr) {
  1825. int const mh = std::max(1, unit_comp->max_health);
  1826. float const ratio = std::clamp(unit_comp->health / float(mh), 0.0F, 1.0F);
  1827. visible_count = std::max(1, (int)std::ceil(ratio * float(rows * cols)));
  1828. }
  1829. HumanoidVariant variant;
  1830. get_variant(ctx, seed, variant);
  1831. if (!m_proportion_scale_cached) {
  1832. m_cached_proportion_scale = get_proportion_scaling();
  1833. m_proportion_scale_cached = true;
  1834. }
  1835. const QVector3D prop_scale = m_cached_proportion_scale;
  1836. const float height_scale = prop_scale.y();
  1837. const bool needs_height_scaling = std::abs(height_scale - 1.0F) > 0.001F;
  1838. const QMatrix4x4 k_identity_matrix;
  1839. const IFormationCalculator *formation_calculator = nullptr;
  1840. {
  1841. using Nation = FormationCalculatorFactory::Nation;
  1842. using UnitCategory = FormationCalculatorFactory::UnitCategory;
  1843. Nation nation = Nation::Roman;
  1844. if (unit_comp != nullptr) {
  1845. if (unit_comp->nation_id == Game::Systems::NationID::Carthage) {
  1846. nation = Nation::Carthage;
  1847. }
  1848. }
  1849. UnitCategory category =
  1850. is_mounted_spawn ? UnitCategory::Cavalry : UnitCategory::Infantry;
  1851. if (unit_comp != nullptr &&
  1852. unit_comp->spawn_type == Game::Units::SpawnType::Builder &&
  1853. anim.is_constructing) {
  1854. category = UnitCategory::BuilderConstruction;
  1855. }
  1856. formation_calculator =
  1857. FormationCalculatorFactory::get_calculator(nation, category);
  1858. }
  1859. auto fast_random = [](uint32_t &state) -> float {
  1860. state = state * 1664525U + 1013904223U;
  1861. return float(state & 0x7FFFFFU) / float(0x7FFFFFU);
  1862. };
  1863. s_render_stats.soldiers_total += visible_count;
  1864. for (int idx = 0; idx < visible_count; ++idx) {
  1865. int const r = idx / cols;
  1866. int const c = idx % cols;
  1867. float offset_x = 0.0F;
  1868. float offset_z = 0.0F;
  1869. uint32_t inst_seed = seed ^ uint32_t(idx * 9176U);
  1870. uint32_t rng_state = inst_seed;
  1871. float vertical_jitter = 0.0F;
  1872. float yaw_offset = 0.0F;
  1873. float phase_offset = 0.0F;
  1874. if (!ctx.force_single_soldier) {
  1875. FormationOffset const formation_offset =
  1876. formation_calculator->calculate_offset(idx, r, c, rows, cols,
  1877. formation.spacing, seed);
  1878. offset_x = formation_offset.offset_x;
  1879. offset_z = formation_offset.offset_z;
  1880. vertical_jitter = (fast_random(rng_state) - 0.5F) * 0.03F;
  1881. yaw_offset = (fast_random(rng_state) - 0.5F) * 5.0F;
  1882. phase_offset = fast_random(rng_state) * 0.25F;
  1883. }
  1884. if (anim.is_attacking && anim.is_melee) {
  1885. float const combat_jitter_x =
  1886. (fast_random(rng_state) - 0.5F) * formation.spacing * 0.4F;
  1887. float const combat_jitter_z =
  1888. (fast_random(rng_state) - 0.5F) * formation.spacing * 0.3F;
  1889. float const sway_time = anim.time + phase_offset * 2.0F;
  1890. float const sway_x = std::sin(sway_time * 1.5F) * 0.05F;
  1891. float const sway_z = std::cos(sway_time * 1.2F) * 0.04F;
  1892. offset_x += combat_jitter_x + sway_x;
  1893. offset_z += combat_jitter_z + sway_z;
  1894. }
  1895. float applied_vertical_jitter = vertical_jitter;
  1896. float applied_yaw_offset = yaw_offset;
  1897. if (anim.is_attacking && anim.is_melee) {
  1898. float const combat_yaw = (fast_random(rng_state) - 0.5F) * 15.0F;
  1899. applied_yaw_offset += combat_yaw;
  1900. }
  1901. QMatrix4x4 inst_model;
  1902. float applied_yaw = applied_yaw_offset;
  1903. if (transform_comp != nullptr) {
  1904. applied_yaw = transform_comp->rotation.y + applied_yaw_offset;
  1905. QMatrix4x4 m = k_identity_matrix;
  1906. m.translate(transform_comp->position.x, transform_comp->position.y,
  1907. transform_comp->position.z);
  1908. m.rotate(applied_yaw, 0.0F, 1.0F, 0.0F);
  1909. m.scale(transform_comp->scale.x, transform_comp->scale.y,
  1910. transform_comp->scale.z);
  1911. m.translate(offset_x, applied_vertical_jitter, offset_z);
  1912. if (!ctx.skip_ground_offset && entity_ground_offset != 0.0F) {
  1913. m.translate(0.0F, -entity_ground_offset, 0.0F);
  1914. }
  1915. inst_model = m;
  1916. } else {
  1917. inst_model = ctx.model;
  1918. inst_model.rotate(applied_yaw, 0.0F, 1.0F, 0.0F);
  1919. inst_model.translate(offset_x, applied_vertical_jitter, offset_z);
  1920. if (!ctx.skip_ground_offset && entity_ground_offset != 0.0F) {
  1921. inst_model.translate(0.0F, -entity_ground_offset, 0.0F);
  1922. }
  1923. }
  1924. QVector3D const soldier_world_pos =
  1925. inst_model.map(QVector3D(0.0F, 0.0F, 0.0F));
  1926. constexpr float k_soldier_cull_radius = 0.6F;
  1927. if (ctx.camera != nullptr &&
  1928. !ctx.camera->is_in_frustum(soldier_world_pos, k_soldier_cull_radius)) {
  1929. ++s_render_stats.soldiers_skipped_frustum;
  1930. continue;
  1931. }
  1932. HumanoidLOD soldier_lod = HumanoidLOD::Full;
  1933. float soldier_distance = 0.0F;
  1934. if (ctx.force_humanoid_lod) {
  1935. soldier_lod = ctx.forced_humanoid_lod;
  1936. } else if (ctx.camera != nullptr) {
  1937. soldier_distance =
  1938. (soldier_world_pos - ctx.camera->get_position()).length();
  1939. soldier_lod = calculate_humanoid_lod(soldier_distance);
  1940. if (soldier_lod == HumanoidLOD::Billboard) {
  1941. ++s_render_stats.soldiers_skipped_lod;
  1942. continue;
  1943. }
  1944. soldier_lod =
  1945. Render::VisibilityBudgetTracker::instance().request_humanoid_lod(
  1946. soldier_lod);
  1947. }
  1948. if (soldier_distance > 0.0F) {
  1949. if (soldier_lod == HumanoidLOD::Reduced &&
  1950. soldier_distance > k_temporal_skip_distance_reduced) {
  1951. if (!should_render_temporal(s_current_frame, inst_seed,
  1952. k_temporal_skip_period_reduced)) {
  1953. ++s_render_stats.soldiers_skipped_temporal;
  1954. continue;
  1955. }
  1956. }
  1957. if (soldier_lod == HumanoidLOD::Minimal &&
  1958. soldier_distance > k_temporal_skip_distance_minimal) {
  1959. if (!should_render_temporal(s_current_frame, inst_seed,
  1960. k_temporal_skip_period_minimal)) {
  1961. ++s_render_stats.soldiers_skipped_temporal;
  1962. continue;
  1963. }
  1964. }
  1965. }
  1966. ++s_render_stats.soldiers_rendered;
  1967. DrawContext inst_ctx{ctx.resources, ctx.entity, ctx.world, inst_model};
  1968. inst_ctx.selected = ctx.selected;
  1969. inst_ctx.hovered = ctx.hovered;
  1970. inst_ctx.animation_time = ctx.animation_time;
  1971. inst_ctx.renderer_id = ctx.renderer_id;
  1972. inst_ctx.backend = ctx.backend;
  1973. inst_ctx.camera = ctx.camera;
  1974. VariationParams variation = VariationParams::fromSeed(inst_seed);
  1975. adjust_variation(inst_ctx, inst_seed, variation);
  1976. float const combined_height_scale = height_scale * variation.height_scale;
  1977. if (needs_height_scaling ||
  1978. std::abs(variation.height_scale - 1.0F) > 0.001F) {
  1979. QMatrix4x4 scale_matrix;
  1980. scale_matrix.scale(variation.bulk_scale, combined_height_scale, 1.0F);
  1981. inst_ctx.model = inst_ctx.model * scale_matrix;
  1982. }
  1983. HumanoidPose pose;
  1984. bool used_cached_pose = false;
  1985. PoseCacheKey cache_key =
  1986. make_pose_cache_key(reinterpret_cast<uintptr_t>(ctx.entity), idx);
  1987. auto cache_it = s_pose_cache.find(cache_key);
  1988. const bool allow_cached_pose = (!anim.is_moving) || ctx.animation_throttled;
  1989. if (allow_cached_pose && cache_it != s_pose_cache.end()) {
  1990. CachedPoseEntry &cached = cache_it->second;
  1991. if ((ctx.animation_throttled || !cached.was_moving) &&
  1992. s_current_frame - cached.frame_number < k_pose_cache_max_age) {
  1993. pose = cached.pose;
  1994. variation = cached.variation;
  1995. cached.frame_number = s_current_frame;
  1996. used_cached_pose = true;
  1997. ++s_render_stats.poses_cached;
  1998. }
  1999. }
  2000. if (!used_cached_pose) {
  2001. bool used_palette = false;
  2002. if (!anim.is_moving && !anim.is_attacking &&
  2003. PosePaletteCache::instance().is_generated() &&
  2004. (soldier_lod == HumanoidLOD::Reduced ||
  2005. soldier_lod == HumanoidLOD::Minimal)) {
  2006. PosePaletteKey palette_key;
  2007. palette_key.state = AnimState::Idle;
  2008. palette_key.frame = 0;
  2009. palette_key.is_moving = false;
  2010. const auto *palette_entry =
  2011. PosePaletteCache::instance().get(palette_key);
  2012. if (palette_entry != nullptr) {
  2013. pose = palette_entry->pose;
  2014. used_palette = true;
  2015. ++s_render_stats.poses_cached;
  2016. }
  2017. }
  2018. if (!used_palette) {
  2019. compute_locomotion_pose(inst_seed, anim.time + phase_offset,
  2020. anim.is_moving, variation, pose);
  2021. ++s_render_stats.poses_computed;
  2022. }
  2023. CachedPoseEntry &entry = s_pose_cache[cache_key];
  2024. entry.pose = pose;
  2025. entry.variation = variation;
  2026. entry.frame_number = s_current_frame;
  2027. entry.was_moving = anim.is_moving;
  2028. }
  2029. HumanoidAnimationContext anim_ctx{};
  2030. anim_ctx.inputs = anim;
  2031. anim_ctx.variation = variation;
  2032. anim_ctx.formation = formation;
  2033. anim_ctx.jitter_seed = phase_offset;
  2034. anim_ctx.instance_position =
  2035. inst_ctx.model.map(QVector3D(0.0F, 0.0F, 0.0F));
  2036. float yaw_rad = qDegreesToRadians(applied_yaw);
  2037. QVector3D forward(std::sin(yaw_rad), 0.0F, std::cos(yaw_rad));
  2038. if (forward.lengthSquared() > 1e-8F) {
  2039. forward.normalize();
  2040. } else {
  2041. forward = QVector3D(0.0F, 0.0F, 1.0F);
  2042. }
  2043. QVector3D up(0.0F, 1.0F, 0.0F);
  2044. QVector3D right = QVector3D::crossProduct(up, forward);
  2045. if (right.lengthSquared() > 1e-8F) {
  2046. right.normalize();
  2047. } else {
  2048. right = QVector3D(1.0F, 0.0F, 0.0F);
  2049. }
  2050. anim_ctx.entity_forward = forward;
  2051. anim_ctx.entity_right = right;
  2052. anim_ctx.entity_up = up;
  2053. anim_ctx.locomotion_direction = forward;
  2054. anim_ctx.yaw_degrees = applied_yaw;
  2055. anim_ctx.yaw_radians = yaw_rad;
  2056. anim_ctx.has_movement_target = false;
  2057. anim_ctx.move_speed = 0.0F;
  2058. anim_ctx.movement_target = QVector3D(0.0F, 0.0F, 0.0F);
  2059. if (movement_comp != nullptr) {
  2060. QVector3D velocity(movement_comp->vx, 0.0F, movement_comp->vz);
  2061. float speed = velocity.length();
  2062. anim_ctx.move_speed = speed;
  2063. if (speed > 1e-4F) {
  2064. anim_ctx.locomotion_direction = velocity.normalized();
  2065. }
  2066. anim_ctx.has_movement_target = movement_comp->has_target;
  2067. anim_ctx.movement_target =
  2068. QVector3D(movement_comp->target_x, 0.0F, movement_comp->target_y);
  2069. }
  2070. anim_ctx.locomotion_velocity =
  2071. anim_ctx.locomotion_direction * anim_ctx.move_speed;
  2072. anim_ctx.motion_state = classify_motion_state(anim, anim_ctx.move_speed);
  2073. anim_ctx.gait.state = anim_ctx.motion_state;
  2074. anim_ctx.gait.speed = anim_ctx.move_speed;
  2075. anim_ctx.gait.velocity = anim_ctx.locomotion_velocity;
  2076. anim_ctx.gait.has_target = anim_ctx.has_movement_target;
  2077. anim_ctx.gait.is_airborne = false;
  2078. float reference_speed = (anim_ctx.gait.state == HumanoidMotionState::Run)
  2079. ? k_reference_run_speed
  2080. : k_reference_walk_speed;
  2081. if (reference_speed > 0.0001F) {
  2082. anim_ctx.gait.normalized_speed =
  2083. std::clamp(anim_ctx.gait.speed / reference_speed, 0.0F, 1.0F);
  2084. } else {
  2085. anim_ctx.gait.normalized_speed = 0.0F;
  2086. }
  2087. if (!anim.is_moving) {
  2088. anim_ctx.gait.normalized_speed = 0.0F;
  2089. }
  2090. if (anim.is_moving) {
  2091. float stride_base = 0.8F;
  2092. if (anim_ctx.motion_state == HumanoidMotionState::Run) {
  2093. stride_base = 0.45F;
  2094. }
  2095. float const base_cycle =
  2096. stride_base / std::max(0.1F, variation.walk_speed_mult);
  2097. anim_ctx.locomotion_cycle_time = base_cycle;
  2098. anim_ctx.locomotion_phase = std::fmod(
  2099. (anim.time + phase_offset) / std::max(0.001F, base_cycle), 1.0F);
  2100. anim_ctx.gait.cycle_time = anim_ctx.locomotion_cycle_time;
  2101. anim_ctx.gait.cycle_phase = anim_ctx.locomotion_phase;
  2102. anim_ctx.gait.stride_distance =
  2103. anim_ctx.gait.speed * anim_ctx.gait.cycle_time;
  2104. } else {
  2105. anim_ctx.locomotion_cycle_time = 0.0F;
  2106. anim_ctx.locomotion_phase = 0.0F;
  2107. anim_ctx.gait.cycle_time = 0.0F;
  2108. anim_ctx.gait.cycle_phase = 0.0F;
  2109. anim_ctx.gait.stride_distance = 0.0F;
  2110. }
  2111. if (anim.is_attacking) {
  2112. float const attack_offset = phase_offset * 1.5F;
  2113. anim_ctx.attack_phase = std::fmod(anim.time + attack_offset, 1.0F);
  2114. if (ctx.has_attack_variant_override) {
  2115. anim_ctx.inputs.attack_variant = ctx.attack_variant_override;
  2116. } else {
  2117. anim_ctx.inputs.attack_variant =
  2118. static_cast<std::uint8_t>(inst_seed % 3);
  2119. }
  2120. }
  2121. customize_pose(inst_ctx, anim_ctx, inst_seed, pose);
  2122. if (!anim.is_moving && !anim.is_attacking) {
  2123. HumanoidPoseController pose_ctrl(pose, anim_ctx);
  2124. pose_ctrl.apply_micro_idle(anim.time + phase_offset, inst_seed);
  2125. if (visible_count > 0) {
  2126. auto hash_u32 = [](std::uint32_t x) -> std::uint32_t {
  2127. x ^= x >> 16;
  2128. x *= 0x7feb352dU;
  2129. x ^= x >> 15;
  2130. x *= 0x846ca68bU;
  2131. x ^= x >> 16;
  2132. return x;
  2133. };
  2134. constexpr float k_ambient_anim_duration = 6.0F;
  2135. constexpr float k_unit_cycle_base = 15.0F;
  2136. constexpr float k_unit_cycle_range = 10.0F;
  2137. float const unit_cycle_period =
  2138. k_unit_cycle_base +
  2139. static_cast<float>(seed % 1000U) / (1000.0F / k_unit_cycle_range);
  2140. float const unit_time_in_cycle =
  2141. std::fmod(anim.time, std::max(0.001F, unit_cycle_period));
  2142. std::uint32_t const unit_cycle_number = static_cast<std::uint32_t>(
  2143. anim.time / std::max(0.001F, unit_cycle_period));
  2144. int const max_slots = std::min(2, visible_count);
  2145. std::uint32_t const cycle_rng =
  2146. hash_u32(seed ^ (unit_cycle_number * 0x9e3779b9U));
  2147. int const slot_count =
  2148. (max_slots <= 0)
  2149. ? 0
  2150. : (1 + static_cast<int>(cycle_rng %
  2151. static_cast<std::uint32_t>(max_slots)));
  2152. int const idx_a =
  2153. static_cast<int>(hash_u32(cycle_rng ^ 0xA341316CU) %
  2154. static_cast<std::uint32_t>(visible_count));
  2155. int idx_b = static_cast<int>(hash_u32(cycle_rng ^ 0xC8013EA4U) %
  2156. static_cast<std::uint32_t>(visible_count));
  2157. if (visible_count > 1 && idx_b == idx_a) {
  2158. idx_b = (idx_a + 1 +
  2159. static_cast<int>(cycle_rng % static_cast<std::uint32_t>(
  2160. visible_count - 1))) %
  2161. visible_count;
  2162. }
  2163. auto pick_idle_type = [&](std::uint32_t v) -> AmbientIdleType {
  2164. std::uint32_t const roll = v % 100U;
  2165. if (roll < 18U) {
  2166. return AmbientIdleType::SitDown;
  2167. }
  2168. if (roll < 36U) {
  2169. return AmbientIdleType::ShiftWeight;
  2170. }
  2171. if (roll < 52U) {
  2172. return AmbientIdleType::ShuffleFeet;
  2173. }
  2174. if (roll < 66U) {
  2175. return AmbientIdleType::TapFoot;
  2176. }
  2177. if (roll < 78U) {
  2178. return AmbientIdleType::StepInPlace;
  2179. }
  2180. if (roll < 90U) {
  2181. return AmbientIdleType::BendKnee;
  2182. }
  2183. if (roll < 98U) {
  2184. return AmbientIdleType::RaiseWeapon;
  2185. }
  2186. return AmbientIdleType::Jump;
  2187. };
  2188. AmbientIdleType const unit_idle_type =
  2189. pick_idle_type(hash_u32(cycle_rng ^ 0x3C6EF372U));
  2190. if (slot_count > 0 && unit_time_in_cycle <= k_ambient_anim_duration) {
  2191. bool const is_active =
  2192. (idx == idx_a) || (slot_count > 1 && idx == idx_b);
  2193. if (is_active) {
  2194. float const phase = unit_time_in_cycle / k_ambient_anim_duration;
  2195. pose_ctrl.apply_ambient_idle_explicit(unit_idle_type, phase);
  2196. }
  2197. }
  2198. }
  2199. }
  2200. if (anim_ctx.motion_state == HumanoidMotionState::Run) {
  2201. float const run_lean = 0.10F;
  2202. pose.pelvis_pos.setZ(pose.pelvis_pos.z() - 0.05F);
  2203. pose.shoulder_l.setZ(pose.shoulder_l.z() + run_lean);
  2204. pose.shoulder_r.setZ(pose.shoulder_r.z() + run_lean);
  2205. pose.neck_base.setZ(pose.neck_base.z() + run_lean * 0.7F);
  2206. pose.head_pos.setZ(pose.head_pos.z() + run_lean * 0.5F);
  2207. pose.pelvis_pos.setY(pose.pelvis_pos.y() - 0.03F);
  2208. pose.shoulder_l.setY(pose.shoulder_l.y() - 0.04F);
  2209. pose.shoulder_r.setY(pose.shoulder_r.y() - 0.04F);
  2210. float const run_stride_mult = 1.5F;
  2211. float const phase = anim_ctx.locomotion_phase;
  2212. float const left_phase = phase;
  2213. float const right_phase = std::fmod(phase + 0.5F, 1.0F);
  2214. auto enhance_run_foot = [&](QVector3D &foot, float foot_phase) {
  2215. float const lift_raw =
  2216. std::sin(foot_phase * 2.0F * std::numbers::pi_v<float>);
  2217. if (lift_raw > 0.0F) {
  2218. float const extra_lift = lift_raw * k_run_extra_foot_lift;
  2219. foot.setY(foot.y() + extra_lift);
  2220. float const stride_enhance = std::sin((foot_phase - 0.25F) * 2.0F *
  2221. std::numbers::pi_v<float>) *
  2222. k_run_stride_enhancement;
  2223. foot.setZ(foot.z() + stride_enhance);
  2224. }
  2225. };
  2226. enhance_run_foot(pose.foot_l, left_phase);
  2227. enhance_run_foot(pose.foot_r, right_phase);
  2228. float const run_arm_swing = 0.06F;
  2229. constexpr float max_run_arm_displacement = 0.08F;
  2230. float const left_swing_raw =
  2231. std::sin((left_phase + 0.1F) * 2.0F * std::numbers::pi_v<float>);
  2232. float const left_arm_phase =
  2233. std::clamp(left_swing_raw * run_arm_swing, -max_run_arm_displacement,
  2234. max_run_arm_displacement);
  2235. float const right_swing_raw =
  2236. std::sin((right_phase + 0.1F) * 2.0F * std::numbers::pi_v<float>);
  2237. float const right_arm_phase =
  2238. std::clamp(right_swing_raw * run_arm_swing, -max_run_arm_displacement,
  2239. max_run_arm_displacement);
  2240. pose.hand_l.setZ(pose.hand_l.z() - left_arm_phase);
  2241. pose.hand_r.setZ(pose.hand_r.z() - right_arm_phase);
  2242. pose.hand_l.setY(pose.hand_l.y() + 0.02F);
  2243. pose.hand_r.setY(pose.hand_r.y() + 0.02F);
  2244. {
  2245. using HP = HumanProportions;
  2246. float const h_scale = variation.height_scale;
  2247. float const max_reach =
  2248. (HP::UPPER_ARM_LEN + HP::FORE_ARM_LEN) * h_scale * 0.98F;
  2249. auto clamp_hand = [&](const QVector3D &shoulder, QVector3D &hand) {
  2250. QVector3D diff = hand - shoulder;
  2251. float const len = diff.length();
  2252. if (len > max_reach && len > 1e-6F) {
  2253. hand = shoulder + diff * (max_reach / len);
  2254. }
  2255. };
  2256. clamp_hand(pose.shoulder_l, pose.hand_l);
  2257. clamp_hand(pose.shoulder_r, pose.hand_r);
  2258. QVector3D right_axis = pose.shoulder_r - pose.shoulder_l;
  2259. right_axis.setY(0.0F);
  2260. if (right_axis.lengthSquared() < 1e-8F) {
  2261. right_axis = QVector3D(1.0F, 0.0F, 0.0F);
  2262. }
  2263. right_axis.normalize();
  2264. if (right_axis.x() < 0.0F) {
  2265. right_axis = -right_axis;
  2266. }
  2267. QVector3D const outward_l = -right_axis;
  2268. QVector3D const outward_r = right_axis;
  2269. pose.elbow_l = elbow_bend_torso(pose.shoulder_l, pose.hand_l, outward_l,
  2270. 0.45F, 0.15F, -0.08F, +1.0F);
  2271. pose.elbow_r = elbow_bend_torso(pose.shoulder_r, pose.hand_r, outward_r,
  2272. 0.48F, 0.12F, 0.02F, +1.0F);
  2273. }
  2274. float const hip_rotation_raw =
  2275. std::sin(phase * 2.0F * std::numbers::pi_v<float>);
  2276. float const hip_rotation = hip_rotation_raw * 0.003F;
  2277. pose.pelvis_pos.setX(pose.pelvis_pos.x() + hip_rotation);
  2278. float const torso_sway_z = 0.0F;
  2279. pose.shoulder_l.setZ(pose.shoulder_l.z() + torso_sway_z);
  2280. pose.shoulder_r.setZ(pose.shoulder_r.z() + torso_sway_z);
  2281. pose.neck_base.setZ(pose.neck_base.z() + torso_sway_z * 0.7F);
  2282. pose.head_pos.setZ(pose.head_pos.z() + torso_sway_z * 0.5F);
  2283. if (pose.head_frame.radius > 0.001F) {
  2284. QVector3D head_up = pose.head_pos - pose.neck_base;
  2285. if (head_up.lengthSquared() < 1e-8F) {
  2286. head_up = pose.head_frame.up;
  2287. } else {
  2288. head_up.normalize();
  2289. }
  2290. QVector3D head_right =
  2291. pose.head_frame.right -
  2292. head_up * QVector3D::dotProduct(pose.head_frame.right, head_up);
  2293. if (head_right.lengthSquared() < 1e-8F) {
  2294. head_right =
  2295. QVector3D::crossProduct(head_up, anim_ctx.entity_forward);
  2296. if (head_right.lengthSquared() < 1e-8F) {
  2297. head_right = QVector3D(1.0F, 0.0F, 0.0F);
  2298. }
  2299. }
  2300. head_right.normalize();
  2301. QVector3D head_forward =
  2302. QVector3D::crossProduct(head_right, head_up).normalized();
  2303. pose.head_frame.origin = pose.head_pos;
  2304. pose.head_frame.up = head_up;
  2305. pose.head_frame.right = head_right;
  2306. pose.head_frame.forward = head_forward;
  2307. pose.body_frames.head = pose.head_frame;
  2308. }
  2309. }
  2310. const auto &gfx_settings = Render::GraphicsSettings::instance();
  2311. const bool should_render_shadow =
  2312. ctx.allow_template_cache && gfx_settings.shadows_enabled() &&
  2313. (soldier_lod == HumanoidLOD::Full ||
  2314. soldier_lod == HumanoidLOD::Reduced) &&
  2315. soldier_distance < gfx_settings.shadow_max_distance();
  2316. if (should_render_shadow && inst_ctx.backend != nullptr &&
  2317. inst_ctx.resources != nullptr) {
  2318. auto *shadow_shader =
  2319. inst_ctx.backend->shader(QStringLiteral("troop_shadow"));
  2320. auto *quad_mesh = inst_ctx.resources->quad();
  2321. if (shadow_shader != nullptr && quad_mesh != nullptr) {
  2322. float const shadow_size =
  2323. is_mounted_spawn ? k_shadow_size_mounted : k_shadow_size_infantry;
  2324. float depth_boost = 1.0F;
  2325. float width_boost = 1.0F;
  2326. if (unit_comp != nullptr) {
  2327. using Game::Units::SpawnType;
  2328. switch (unit_comp->spawn_type) {
  2329. case SpawnType::Spearman:
  2330. depth_boost = 1.8F;
  2331. width_boost = 0.95F;
  2332. break;
  2333. case SpawnType::HorseSpearman:
  2334. depth_boost = 2.1F;
  2335. width_boost = 1.05F;
  2336. break;
  2337. case SpawnType::Archer:
  2338. case SpawnType::HorseArcher:
  2339. depth_boost = 1.2F;
  2340. width_boost = 0.95F;
  2341. break;
  2342. default:
  2343. break;
  2344. }
  2345. }
  2346. float const shadow_width =
  2347. shadow_size * (is_mounted_spawn ? 1.05F : 1.0F) * width_boost;
  2348. float const shadow_depth =
  2349. shadow_size * (is_mounted_spawn ? 1.30F : 1.10F) * depth_boost;
  2350. auto &terrain_service = Game::Map::TerrainService::instance();
  2351. if (terrain_service.is_initialized()) {
  2352. QVector3D const inst_pos =
  2353. inst_ctx.model.map(QVector3D(0.0F, 0.0F, 0.0F));
  2354. float const shadow_y =
  2355. terrain_service.get_terrain_height(inst_pos.x(), inst_pos.z());
  2356. QVector3D light_dir = k_shadow_light_dir.normalized();
  2357. QVector2D light_dir_xz(light_dir.x(), light_dir.z());
  2358. if (light_dir_xz.lengthSquared() < 1e-6F) {
  2359. light_dir_xz = QVector2D(0.0F, 1.0F);
  2360. } else {
  2361. light_dir_xz.normalize();
  2362. }
  2363. QVector2D const shadow_dir = -light_dir_xz;
  2364. QVector2D dir_for_use = shadow_dir;
  2365. if (dir_for_use.lengthSquared() < 1e-6F) {
  2366. dir_for_use = QVector2D(0.0F, 1.0F);
  2367. } else {
  2368. dir_for_use.normalize();
  2369. }
  2370. float const shadow_offset = shadow_depth * 1.25F;
  2371. QVector2D const offset_2d = dir_for_use * shadow_offset;
  2372. float const light_yaw_deg = qRadiansToDegrees(
  2373. std::atan2(double(dir_for_use.x()), double(dir_for_use.y())));
  2374. QMatrix4x4 shadow_model;
  2375. shadow_model.translate(inst_pos.x() + offset_2d.x(),
  2376. shadow_y + k_shadow_ground_offset,
  2377. inst_pos.z() + offset_2d.y());
  2378. shadow_model.rotate(light_yaw_deg, 0.0F, 1.0F, 0.0F);
  2379. shadow_model.rotate(-90.0F, 1.0F, 0.0F, 0.0F);
  2380. shadow_model.scale(shadow_width, shadow_depth, 1.0F);
  2381. if (auto *renderer = dynamic_cast<Renderer *>(&out)) {
  2382. Shader *previous_shader = renderer->get_current_shader();
  2383. renderer->set_current_shader(shadow_shader);
  2384. shadow_shader->set_uniform(QStringLiteral("u_lightDir"),
  2385. dir_for_use);
  2386. out.mesh(quad_mesh, shadow_model, QVector3D(0.0F, 0.0F, 0.0F),
  2387. nullptr, k_shadow_base_alpha, 0);
  2388. renderer->set_current_shader(previous_shader);
  2389. }
  2390. }
  2391. }
  2392. }
  2393. switch (soldier_lod) {
  2394. case HumanoidLOD::Full:
  2395. ++s_render_stats.lod_full;
  2396. draw_common_body(inst_ctx, variant, pose, out);
  2397. draw_facial_hair(inst_ctx, variant, pose, out);
  2398. draw_armor(inst_ctx, variant, pose, anim_ctx, out);
  2399. add_attachments(inst_ctx, variant, pose, anim_ctx, out);
  2400. break;
  2401. case HumanoidLOD::Reduced:
  2402. ++s_render_stats.lod_reduced;
  2403. draw_simplified_body(inst_ctx, variant, pose, out);
  2404. draw_armor(inst_ctx, variant, pose, anim_ctx, out);
  2405. add_attachments(inst_ctx, variant, pose, anim_ctx, out);
  2406. break;
  2407. case HumanoidLOD::Minimal:
  2408. ++s_render_stats.lod_minimal;
  2409. draw_minimal_body(inst_ctx, variant, pose, out);
  2410. break;
  2411. case HumanoidLOD::Billboard:
  2412. break;
  2413. }
  2414. }
  2415. }
  2416. auto get_humanoid_render_stats() -> const HumanoidRenderStats & {
  2417. return s_render_stats;
  2418. }
  2419. void reset_humanoid_render_stats() { s_render_stats.reset(); }
  2420. } // namespace Render::GL