rig.cpp 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654
  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/math_utils.h"
  12. #include "../geom/transforms.h"
  13. #include "../gl/backend.h"
  14. #include "../gl/camera.h"
  15. #include "../gl/humanoid/animation/animation_inputs.h"
  16. #include "../gl/humanoid/animation/gait.h"
  17. #include "../gl/humanoid/humanoid_constants.h"
  18. #include "../gl/primitives.h"
  19. #include "../gl/render_constants.h"
  20. #include "../gl/resources.h"
  21. #include "../palette.h"
  22. #include "../scene_renderer.h"
  23. #include "../submitter.h"
  24. #include "formation_calculator.h"
  25. #include "humanoid_math.h"
  26. #include <QMatrix4x4>
  27. #include <QVector2D>
  28. #include <QVector4D>
  29. #include <QtMath>
  30. #include <algorithm>
  31. #include <cmath>
  32. #include <cstdint>
  33. #include <functional>
  34. #include <limits>
  35. #include <numbers>
  36. #include <unordered_map>
  37. #include <vector>
  38. namespace Render::GL {
  39. using namespace Render::GL::Geometry;
  40. using Render::Geom::capsuleBetween;
  41. using Render::Geom::coneFromTo;
  42. using Render::Geom::cylinderBetween;
  43. using Render::Geom::sphereAt;
  44. namespace {
  45. constexpr float k_shadow_size_infantry = 0.16F;
  46. constexpr float k_shadow_size_mounted = 0.35F;
  47. struct CachedPoseEntry {
  48. HumanoidPose pose;
  49. VariationParams variation;
  50. uint32_t frameNumber{0};
  51. bool wasMoving{false};
  52. };
  53. using PoseCacheKey = uint64_t;
  54. static std::unordered_map<PoseCacheKey, CachedPoseEntry> s_poseCache;
  55. static uint32_t s_currentFrame = 0;
  56. constexpr uint32_t kPoseCacheMaxAge = 300;
  57. inline auto makePoseCacheKey(uintptr_t entityPtr,
  58. int soldierIdx) -> PoseCacheKey {
  59. return (static_cast<uint64_t>(entityPtr) << 16) |
  60. static_cast<uint64_t>(soldierIdx & 0xFFFF);
  61. }
  62. static HumanoidRenderStats s_renderStats;
  63. constexpr float k_shadow_ground_offset = 0.02F;
  64. constexpr float k_shadow_base_alpha = 0.24F;
  65. constexpr QVector3D k_shadow_light_dir(0.4F, 1.0F, 0.25F);
  66. } // namespace
  67. void advancePoseCacheFrame() {
  68. ++s_currentFrame;
  69. if ((s_currentFrame & 0x1FF) == 0) {
  70. auto it = s_poseCache.begin();
  71. while (it != s_poseCache.end()) {
  72. if (s_currentFrame - it->second.frameNumber > kPoseCacheMaxAge * 2) {
  73. it = s_poseCache.erase(it);
  74. } else {
  75. ++it;
  76. }
  77. }
  78. }
  79. }
  80. auto torso_mesh_without_bottom_cap() -> Mesh * {
  81. static std::unique_ptr<Mesh> s_mesh;
  82. if (s_mesh != nullptr) {
  83. return s_mesh.get();
  84. }
  85. Mesh *base = getUnitTorso();
  86. if (base == nullptr) {
  87. return nullptr;
  88. }
  89. auto filtered = base->cloneWithFilteredIndices(
  90. [](unsigned int a, unsigned int b, unsigned int c,
  91. const std::vector<Vertex> &verts) -> bool {
  92. float min_y = std::numeric_limits<float>::max();
  93. float max_y = -std::numeric_limits<float>::max();
  94. auto sample = [&](unsigned int idx) -> QVector3D {
  95. return {verts[idx].position[0], verts[idx].position[1],
  96. verts[idx].position[2]};
  97. };
  98. QVector3D pa = sample(a);
  99. QVector3D pb = sample(b);
  100. QVector3D pc = sample(c);
  101. min_y = std::min({pa.y(), pb.y(), pc.y()});
  102. max_y = std::max({pa.y(), pb.y(), pc.y()});
  103. QVector3D n(
  104. verts[a].normal[0] + verts[b].normal[0] + verts[c].normal[0],
  105. verts[a].normal[1] + verts[b].normal[1] + verts[c].normal[1],
  106. verts[a].normal[2] + verts[b].normal[2] + verts[c].normal[2]);
  107. if (n.lengthSquared() > 0.0F) {
  108. n.normalize();
  109. }
  110. constexpr float k_band_height = 0.02F;
  111. bool is_bottom_band = (max_y - min_y) < k_band_height &&
  112. (min_y < (pa.y() + pb.y() + pc.y()) / 3.0F);
  113. bool facing_down = (n.y() < -0.35F);
  114. return is_bottom_band && facing_down;
  115. });
  116. s_mesh =
  117. (filtered != nullptr) ? std::move(filtered) : std::unique_ptr<Mesh>(base);
  118. return s_mesh.get();
  119. }
  120. auto HumanoidRendererBase::frameLocalPosition(
  121. const AttachmentFrame &frame, const QVector3D &local) -> QVector3D {
  122. float const lx = local.x() * frame.radius;
  123. float const ly = local.y() * frame.radius;
  124. float const lz = local.z() * frame.radius;
  125. return frame.origin + frame.right * lx + frame.up * ly + frame.forward * lz;
  126. }
  127. auto HumanoidRendererBase::makeFrameLocalTransform(
  128. const QMatrix4x4 &parent, const AttachmentFrame &frame,
  129. const QVector3D &local_offset, float uniform_scale) -> QMatrix4x4 {
  130. float scale = frame.radius * uniform_scale;
  131. if (scale == 0.0F) {
  132. scale = uniform_scale;
  133. }
  134. QVector3D const origin = frameLocalPosition(frame, local_offset);
  135. QMatrix4x4 local;
  136. local.setColumn(0, QVector4D(frame.right * scale, 0.0F));
  137. local.setColumn(1, QVector4D(frame.up * scale, 0.0F));
  138. local.setColumn(2, QVector4D(frame.forward * scale, 0.0F));
  139. local.setColumn(3, QVector4D(origin, 1.0F));
  140. return parent * local;
  141. }
  142. auto HumanoidRendererBase::headLocalPosition(
  143. const HeadFrame &frame, const QVector3D &local) -> QVector3D {
  144. return frameLocalPosition(frame, local);
  145. }
  146. auto HumanoidRendererBase::makeHeadLocalTransform(
  147. const QMatrix4x4 &parent, const HeadFrame &frame,
  148. const QVector3D &local_offset, float uniform_scale) -> QMatrix4x4 {
  149. return makeFrameLocalTransform(parent, frame, local_offset, uniform_scale);
  150. }
  151. void HumanoidRendererBase::get_variant(const DrawContext &ctx, uint32_t seed,
  152. HumanoidVariant &v) const {
  153. QVector3D const team_tint = resolveTeamTint(ctx);
  154. v.palette = makeHumanoidPalette(team_tint, seed);
  155. }
  156. void HumanoidRendererBase::customize_pose(const DrawContext &,
  157. const HumanoidAnimationContext &,
  158. uint32_t, HumanoidPose &) const {}
  159. void HumanoidRendererBase::addAttachments(const DrawContext &,
  160. const HumanoidVariant &,
  161. const HumanoidPose &,
  162. const HumanoidAnimationContext &,
  163. ISubmitter &) const {}
  164. auto HumanoidRendererBase::resolve_entity_ground_offset(
  165. const DrawContext &, Engine::Core::UnitComponent *unit_comp,
  166. Engine::Core::TransformComponent *transform_comp) const -> float {
  167. (void)unit_comp;
  168. (void)transform_comp;
  169. return 0.0F;
  170. }
  171. auto HumanoidRendererBase::resolveTeamTint(const DrawContext &ctx)
  172. -> QVector3D {
  173. QVector3D tunic(0.8F, 0.9F, 1.0F);
  174. Engine::Core::UnitComponent *unit = nullptr;
  175. Engine::Core::RenderableComponent *rc = nullptr;
  176. if (ctx.entity != nullptr) {
  177. unit = ctx.entity->getComponent<Engine::Core::UnitComponent>();
  178. rc = ctx.entity->getComponent<Engine::Core::RenderableComponent>();
  179. }
  180. if ((unit != nullptr) && unit->owner_id > 0) {
  181. tunic = Game::Visuals::team_colorForOwner(unit->owner_id);
  182. } else if (rc != nullptr) {
  183. tunic = QVector3D(rc->color[0], rc->color[1], rc->color[2]);
  184. }
  185. return tunic;
  186. }
  187. auto HumanoidRendererBase::resolveFormation(const DrawContext &ctx)
  188. -> FormationParams {
  189. FormationParams params{};
  190. params.individuals_per_unit = 1;
  191. params.max_per_row = 1;
  192. params.spacing = 0.75F;
  193. if (ctx.entity != nullptr) {
  194. auto *unit = ctx.entity->getComponent<Engine::Core::UnitComponent>();
  195. if (unit != nullptr) {
  196. params.individuals_per_unit =
  197. Game::Units::TroopConfig::instance().getIndividualsPerUnit(
  198. unit->spawn_type);
  199. params.max_per_row =
  200. Game::Units::TroopConfig::instance().getMaxUnitsPerRow(
  201. unit->spawn_type);
  202. if (unit->spawn_type == Game::Units::SpawnType::MountedKnight) {
  203. params.spacing = 1.05F;
  204. }
  205. }
  206. }
  207. return params;
  208. }
  209. void HumanoidRendererBase::computeLocomotionPose(
  210. uint32_t seed, float time, bool isMoving, const VariationParams &variation,
  211. HumanoidPose &pose) {
  212. using HP = HumanProportions;
  213. float const h_scale = variation.height_scale;
  214. pose.head_pos = QVector3D(0.0F, HP::HEAD_CENTER_Y * h_scale, 0.0F);
  215. pose.head_r = HP::HEAD_RADIUS * h_scale;
  216. pose.neck_base = QVector3D(0.0F, HP::NECK_BASE_Y * h_scale, 0.0F);
  217. float const b_scale = variation.bulk_scale;
  218. float const s_width = variation.stance_width;
  219. float const half_shoulder_span = 0.5F * HP::SHOULDER_WIDTH * b_scale;
  220. pose.shoulder_l =
  221. QVector3D(-half_shoulder_span, HP::SHOULDER_Y * h_scale, 0.0F);
  222. pose.shoulder_r =
  223. QVector3D(half_shoulder_span, HP::SHOULDER_Y * h_scale, 0.0F);
  224. pose.pelvis_pos = QVector3D(0.0F, HP::WAIST_Y * h_scale, 0.0F);
  225. float const rest_stride = 0.06F + (variation.arm_swing_amp - 1.0F) * 0.045F;
  226. float const foot_x_span = HP::SHOULDER_WIDTH * 0.62F * s_width;
  227. pose.foot_y_offset = 0.022F;
  228. pose.foot_l =
  229. QVector3D(-foot_x_span, HP::GROUND_Y + pose.foot_y_offset, rest_stride);
  230. pose.foot_r =
  231. QVector3D(foot_x_span, HP::GROUND_Y + pose.foot_y_offset, -rest_stride);
  232. pose.shoulder_l.setY(pose.shoulder_l.y() + variation.shoulder_tilt);
  233. pose.shoulder_r.setY(pose.shoulder_r.y() - variation.shoulder_tilt);
  234. float const slouch_offset = variation.posture_slump * 0.15F;
  235. pose.shoulder_l.setZ(pose.shoulder_l.z() + slouch_offset);
  236. pose.shoulder_r.setZ(pose.shoulder_r.z() + slouch_offset);
  237. float const foot_inward_jitter = (hash_01(seed ^ 0x5678U) - 0.5F) * 0.02F;
  238. float const foot_forward_jitter = (hash_01(seed ^ 0x9ABCU) - 0.5F) * 0.035F;
  239. pose.foot_l.setX(pose.foot_l.x() + foot_inward_jitter);
  240. pose.foot_r.setX(pose.foot_r.x() - foot_inward_jitter);
  241. pose.foot_l.setZ(pose.foot_l.z() + foot_forward_jitter);
  242. pose.foot_r.setZ(pose.foot_r.z() - foot_forward_jitter);
  243. float const arm_height_jitter = (hash_01(seed ^ 0xABCDU) - 0.5F) * 0.03F;
  244. float const arm_asymmetry = (hash_01(seed ^ 0xDEF0U) - 0.5F) * 0.04F;
  245. pose.hand_l =
  246. QVector3D(-0.05F + arm_asymmetry,
  247. HP::SHOULDER_Y * h_scale + 0.05F + arm_height_jitter, 0.55F);
  248. pose.hand_r = QVector3D(
  249. 0.15F - arm_asymmetry * 0.5F,
  250. HP::SHOULDER_Y * h_scale + 0.15F + arm_height_jitter * 0.8F, 0.20F);
  251. if (isMoving) {
  252. float const walk_cycle_time = 0.8F / variation.walk_speed_mult;
  253. float const walk_phase = std::fmod(time * (1.0F / walk_cycle_time), 1.0F);
  254. float const left_phase = walk_phase;
  255. float const right_phase = std::fmod(walk_phase + 0.5F, 1.0F);
  256. const float ground_y = HP::GROUND_Y;
  257. const float stride_length = 0.35F * variation.arm_swing_amp;
  258. auto animate_foot = [ground_y, &pose, stride_length](QVector3D &foot,
  259. float phase) {
  260. float const lift = std::sin(phase * 2.0F * std::numbers::pi_v<float>);
  261. if (lift > 0.0F) {
  262. foot.setY(ground_y + pose.foot_y_offset + lift * 0.12F);
  263. } else {
  264. foot.setY(ground_y + pose.foot_y_offset);
  265. }
  266. foot.setZ(foot.z() +
  267. std::sin((phase - 0.25F) * 2.0F * std::numbers::pi_v<float>) *
  268. stride_length);
  269. };
  270. animate_foot(pose.foot_l, left_phase);
  271. animate_foot(pose.foot_r, right_phase);
  272. }
  273. QVector3D const hip_l = pose.pelvis_pos + QVector3D(-0.10F, -0.02F, 0.0F);
  274. QVector3D const hip_r = pose.pelvis_pos + QVector3D(0.10F, -0.02F, 0.0F);
  275. auto solve_leg = [&](const QVector3D &hip, const QVector3D &foot,
  276. bool is_left) -> QVector3D {
  277. QVector3D hip_to_foot = foot - hip;
  278. float const distance = hip_to_foot.length();
  279. if (distance < 1e-5F) {
  280. return hip;
  281. }
  282. float const upper_len = HP::UPPER_LEG_LEN * h_scale;
  283. float const lower_len = HP::LOWER_LEG_LEN * h_scale;
  284. float const reach = upper_len + lower_len;
  285. float const min_reach =
  286. std::max(std::abs(upper_len - lower_len) + 1e-4F, 1e-3F);
  287. float const max_reach = std::max(reach - 1e-4F, min_reach + 1e-4F);
  288. float const clamped_dist = std::clamp(distance, min_reach, max_reach);
  289. QVector3D const dir = hip_to_foot / distance;
  290. float cos_theta = (upper_len * upper_len + clamped_dist * clamped_dist -
  291. lower_len * lower_len) /
  292. (2.0F * upper_len * clamped_dist);
  293. cos_theta = std::clamp(cos_theta, -1.0F, 1.0F);
  294. float const sin_theta =
  295. std::sqrt(std::max(0.0F, 1.0F - cos_theta * cos_theta));
  296. QVector3D bend_pref = is_left ? QVector3D(-0.24F, 0.0F, 0.95F)
  297. : QVector3D(0.24F, 0.0F, 0.95F);
  298. bend_pref.normalize();
  299. QVector3D bend_axis =
  300. bend_pref - dir * QVector3D::dotProduct(dir, bend_pref);
  301. if (bend_axis.lengthSquared() < 1e-6F) {
  302. bend_axis = QVector3D::crossProduct(dir, QVector3D(0.0F, 1.0F, 0.0F));
  303. if (bend_axis.lengthSquared() < 1e-6F) {
  304. bend_axis = QVector3D::crossProduct(dir, QVector3D(1.0F, 0.0F, 0.0F));
  305. }
  306. }
  307. bend_axis.normalize();
  308. QVector3D const knee = hip + dir * (cos_theta * upper_len) +
  309. bend_axis * (sin_theta * upper_len);
  310. float const knee_floor = HP::GROUND_Y + pose.foot_y_offset * 0.5F;
  311. if (knee.y() < knee_floor) {
  312. QVector3D adjusted = knee;
  313. adjusted.setY(knee_floor);
  314. return adjusted;
  315. }
  316. return knee;
  317. };
  318. pose.knee_l = solve_leg(hip_l, pose.foot_l, true);
  319. pose.knee_r = solve_leg(hip_r, pose.foot_r, false);
  320. QVector3D right_axis = pose.shoulder_r - pose.shoulder_l;
  321. right_axis.setY(0.0F);
  322. if (right_axis.lengthSquared() < 1e-8F) {
  323. right_axis = QVector3D(1, 0, 0);
  324. }
  325. right_axis.normalize();
  326. QVector3D const outward_l = -right_axis;
  327. QVector3D const outward_r = right_axis;
  328. pose.elbow_l = elbowBendTorso(pose.shoulder_l, pose.hand_l, outward_l, 0.45F,
  329. 0.15F, -0.08F, +1.0F);
  330. pose.elbow_r = elbowBendTorso(pose.shoulder_r, pose.hand_r, outward_r, 0.48F,
  331. 0.12F, 0.02F, +1.0F);
  332. }
  333. void HumanoidRendererBase::drawCommonBody(const DrawContext &ctx,
  334. const HumanoidVariant &v,
  335. HumanoidPose &pose,
  336. ISubmitter &out) const {
  337. using HP = HumanProportions;
  338. QVector3D const scaling = get_proportion_scaling();
  339. float const width_scale = scaling.x();
  340. float const height_scale = scaling.y();
  341. float const torso_scale = get_torso_scale();
  342. float const head_scale = 1.0F;
  343. QVector3D right_axis = pose.shoulder_r - pose.shoulder_l;
  344. if (right_axis.lengthSquared() < 1e-8F) {
  345. right_axis = QVector3D(1, 0, 0);
  346. }
  347. right_axis.normalize();
  348. QVector3D const up_axis(0.0F, 1.0F, 0.0F);
  349. QVector3D forward_axis = QVector3D::crossProduct(right_axis, up_axis);
  350. if (forward_axis.lengthSquared() < 1e-8F) {
  351. forward_axis = QVector3D(0.0F, 0.0F, 1.0F);
  352. }
  353. forward_axis.normalize();
  354. QVector3D const shoulder_mid = (pose.shoulder_l + pose.shoulder_r) * 0.5F;
  355. const float y_shoulder = shoulder_mid.y();
  356. const float y_neck = pose.neck_base.y();
  357. const float shoulder_half_span =
  358. 0.5F * std::abs(pose.shoulder_r.x() - pose.shoulder_l.x());
  359. const float torso_r_base =
  360. std::max(HP::TORSO_TOP_R, shoulder_half_span * 0.95F);
  361. const float torso_r = torso_r_base * torso_scale;
  362. float const depth_scale = scaling.z();
  363. const float torso_depth_factor =
  364. std::clamp(0.55F + (depth_scale - 1.0F) * 0.20F, 0.40F, 0.85F);
  365. float torso_depth = torso_r * torso_depth_factor;
  366. const float y_top_cover = std::max(y_shoulder + 0.00F, y_neck - 0.03F);
  367. const float upper_arm_r = HP::UPPER_ARM_R * width_scale;
  368. const float fore_arm_r = HP::FORE_ARM_R * width_scale;
  369. const float joint_r = HP::HAND_RADIUS * width_scale * 1.05F;
  370. const float hand_r = HP::HAND_RADIUS * width_scale * 0.95F;
  371. const float leg_joint_r = HP::LOWER_LEG_R * width_scale * 0.95F;
  372. const float thigh_r = HP::UPPER_LEG_R * width_scale;
  373. const float shin_r = HP::LOWER_LEG_R * width_scale;
  374. const float foot_radius = shin_r * 1.10F;
  375. QVector3D const tunic_top{shoulder_mid.x(), y_top_cover - 0.006F,
  376. shoulder_mid.z()};
  377. QVector3D const tunic_bot{pose.pelvis_pos.x(), pose.pelvis_pos.y() - 0.05F,
  378. pose.pelvis_pos.z()};
  379. QMatrix4x4 torso_transform =
  380. cylinderBetween(ctx.model, tunic_top, tunic_bot, 1.0F);
  381. torso_transform.scale(torso_r, 1.0F, torso_depth);
  382. Mesh *torso_mesh = torso_mesh_without_bottom_cap();
  383. if (torso_mesh != nullptr) {
  384. out.mesh(torso_mesh, torso_transform, v.palette.cloth, nullptr, 1.0F);
  385. }
  386. float const head_r = pose.head_r;
  387. QVector3D head_up;
  388. QVector3D head_right;
  389. QVector3D head_forward;
  390. if (pose.head_frame.radius > 0.001F) {
  391. head_up = pose.head_frame.up;
  392. head_right = pose.head_frame.right;
  393. head_forward = pose.head_frame.forward;
  394. } else {
  395. head_up = pose.head_pos - pose.neck_base;
  396. if (head_up.lengthSquared() < 1e-8F) {
  397. head_up = up_axis;
  398. } else {
  399. head_up.normalize();
  400. }
  401. head_right =
  402. right_axis - head_up * QVector3D::dotProduct(right_axis, head_up);
  403. if (head_right.lengthSquared() < 1e-8F) {
  404. head_right = QVector3D::crossProduct(head_up, forward_axis);
  405. if (head_right.lengthSquared() < 1e-8F) {
  406. head_right = QVector3D(1.0F, 0.0F, 0.0F);
  407. }
  408. }
  409. head_right.normalize();
  410. if (QVector3D::dotProduct(head_right, right_axis) < 0.0F) {
  411. head_right = -head_right;
  412. }
  413. head_forward = QVector3D::crossProduct(head_right, head_up);
  414. if (head_forward.lengthSquared() < 1e-8F) {
  415. head_forward = forward_axis;
  416. } else {
  417. head_forward.normalize();
  418. }
  419. if (QVector3D::dotProduct(head_forward, forward_axis) < 0.0F) {
  420. head_right = -head_right;
  421. head_forward = -head_forward;
  422. }
  423. }
  424. QVector3D const chin_pos = pose.head_pos - head_up * head_r;
  425. out.mesh(getUnitCylinder(),
  426. cylinderBetween(ctx.model, pose.neck_base, chin_pos,
  427. HP::NECK_RADIUS * width_scale),
  428. v.palette.skin * 0.9F, nullptr, 1.0F);
  429. QMatrix4x4 head_rot;
  430. head_rot.setColumn(0, QVector4D(head_right, 0.0F));
  431. head_rot.setColumn(1, QVector4D(head_up, 0.0F));
  432. head_rot.setColumn(2, QVector4D(head_forward, 0.0F));
  433. head_rot.setColumn(3, QVector4D(0.0F, 0.0F, 0.0F, 1.0F));
  434. QMatrix4x4 head_transform = ctx.model;
  435. head_transform.translate(pose.head_pos);
  436. head_transform = head_transform * head_rot;
  437. head_transform.scale(head_r);
  438. out.mesh(getUnitSphere(), head_transform, v.palette.skin, nullptr, 1.0F);
  439. pose.head_frame.origin = pose.head_pos;
  440. pose.head_frame.right = head_right;
  441. pose.head_frame.up = head_up;
  442. pose.head_frame.forward = head_forward;
  443. pose.head_frame.radius = head_r;
  444. pose.body_frames.head = pose.head_frame;
  445. QVector3D const torso_center =
  446. QVector3D((shoulder_mid.x() + pose.pelvis_pos.x()) * 0.5F, y_shoulder,
  447. (shoulder_mid.z() + pose.pelvis_pos.z()) * 0.5F);
  448. pose.body_frames.torso.origin = torso_center;
  449. pose.body_frames.torso.right = right_axis;
  450. pose.body_frames.torso.up = up_axis;
  451. pose.body_frames.torso.forward = forward_axis;
  452. pose.body_frames.torso.radius = torso_r;
  453. pose.body_frames.torso.depth = torso_depth;
  454. pose.body_frames.back.origin = torso_center - forward_axis * torso_depth;
  455. pose.body_frames.back.right = right_axis;
  456. pose.body_frames.back.up = up_axis;
  457. pose.body_frames.back.forward = -forward_axis;
  458. pose.body_frames.back.radius = torso_r * 0.75F;
  459. pose.body_frames.back.depth = torso_depth * 0.90F;
  460. pose.body_frames.waist.origin = pose.pelvis_pos;
  461. pose.body_frames.waist.right = right_axis;
  462. pose.body_frames.waist.up = up_axis;
  463. pose.body_frames.waist.forward = forward_axis;
  464. pose.body_frames.waist.radius = torso_r * 0.80F;
  465. pose.body_frames.waist.depth = torso_depth * 0.72F;
  466. QVector3D shoulder_up = (pose.shoulder_l - pose.pelvis_pos).normalized();
  467. QVector3D shoulder_forward_l =
  468. QVector3D::crossProduct(-right_axis, shoulder_up);
  469. if (shoulder_forward_l.lengthSquared() < 1e-8F) {
  470. shoulder_forward_l = forward_axis;
  471. } else {
  472. shoulder_forward_l.normalize();
  473. }
  474. pose.body_frames.shoulder_l.origin = pose.shoulder_l;
  475. pose.body_frames.shoulder_l.right = -right_axis;
  476. pose.body_frames.shoulder_l.up = shoulder_up;
  477. pose.body_frames.shoulder_l.forward = shoulder_forward_l;
  478. pose.body_frames.shoulder_l.radius = upper_arm_r;
  479. QVector3D shoulder_forward_r =
  480. QVector3D::crossProduct(right_axis, shoulder_up);
  481. if (shoulder_forward_r.lengthSquared() < 1e-8F) {
  482. shoulder_forward_r = forward_axis;
  483. } else {
  484. shoulder_forward_r.normalize();
  485. }
  486. pose.body_frames.shoulder_r.origin = pose.shoulder_r;
  487. pose.body_frames.shoulder_r.right = right_axis;
  488. pose.body_frames.shoulder_r.up = shoulder_up;
  489. pose.body_frames.shoulder_r.forward = shoulder_forward_r;
  490. pose.body_frames.shoulder_r.radius = upper_arm_r;
  491. QVector3D hand_up_l = (pose.hand_l - pose.elbow_l);
  492. if (hand_up_l.lengthSquared() > 1e-8F) {
  493. hand_up_l.normalize();
  494. } else {
  495. hand_up_l = up_axis;
  496. }
  497. QVector3D hand_forward_l = QVector3D::crossProduct(-right_axis, hand_up_l);
  498. if (hand_forward_l.lengthSquared() < 1e-8F) {
  499. hand_forward_l = forward_axis;
  500. } else {
  501. hand_forward_l.normalize();
  502. }
  503. pose.body_frames.hand_l.origin = pose.hand_l;
  504. pose.body_frames.hand_l.right = -right_axis;
  505. pose.body_frames.hand_l.up = hand_up_l;
  506. pose.body_frames.hand_l.forward = hand_forward_l;
  507. pose.body_frames.hand_l.radius = hand_r;
  508. QVector3D hand_up_r = (pose.hand_r - pose.elbow_r);
  509. if (hand_up_r.lengthSquared() > 1e-8F) {
  510. hand_up_r.normalize();
  511. } else {
  512. hand_up_r = up_axis;
  513. }
  514. QVector3D hand_forward_r = QVector3D::crossProduct(right_axis, hand_up_r);
  515. if (hand_forward_r.lengthSquared() < 1e-8F) {
  516. hand_forward_r = forward_axis;
  517. } else {
  518. hand_forward_r.normalize();
  519. }
  520. pose.body_frames.hand_r.origin = pose.hand_r;
  521. pose.body_frames.hand_r.right = right_axis;
  522. pose.body_frames.hand_r.up = hand_up_r;
  523. pose.body_frames.hand_r.forward = hand_forward_r;
  524. pose.body_frames.hand_r.radius = hand_r;
  525. QVector3D foot_up_l = up_axis;
  526. QVector3D foot_forward_l = forward_axis - right_axis * 0.12F;
  527. if (foot_forward_l.lengthSquared() > 1e-8F) {
  528. foot_forward_l.normalize();
  529. } else {
  530. foot_forward_l = forward_axis;
  531. }
  532. pose.body_frames.foot_l.origin = pose.foot_l;
  533. pose.body_frames.foot_l.right = -right_axis;
  534. pose.body_frames.foot_l.up = foot_up_l;
  535. pose.body_frames.foot_l.forward = foot_forward_l;
  536. pose.body_frames.foot_l.radius = foot_radius;
  537. QVector3D foot_forward_r = forward_axis + right_axis * 0.12F;
  538. if (foot_forward_r.lengthSquared() > 1e-8F) {
  539. foot_forward_r.normalize();
  540. } else {
  541. foot_forward_r = forward_axis;
  542. }
  543. pose.body_frames.foot_r.origin = pose.foot_r;
  544. pose.body_frames.foot_r.right = right_axis;
  545. pose.body_frames.foot_r.up = foot_up_l;
  546. pose.body_frames.foot_r.forward = foot_forward_r;
  547. pose.body_frames.foot_r.radius = foot_radius;
  548. QVector3D const iris = QVector3D(0.10F, 0.10F, 0.12F);
  549. auto eyePosition = [&](float lateral) {
  550. QVector3D const local(lateral, 0.12F, 0.92F);
  551. QVector3D world = frameLocalPosition(pose.body_frames.head, local);
  552. world +=
  553. pose.body_frames.head.forward * (pose.body_frames.head.radius * 0.02F);
  554. return world;
  555. };
  556. QVector3D const left_eye_world = eyePosition(-0.32F);
  557. QVector3D const right_eye_world = eyePosition(0.32F);
  558. float const eye_radius = pose.body_frames.head.radius * 0.17F;
  559. out.mesh(getUnitSphere(), sphereAt(ctx.model, left_eye_world, eye_radius),
  560. iris, nullptr, 1.0F);
  561. out.mesh(getUnitSphere(), sphereAt(ctx.model, right_eye_world, eye_radius),
  562. iris, nullptr, 1.0F);
  563. out.mesh(
  564. getUnitCylinder(),
  565. cylinderBetween(ctx.model, pose.shoulder_l, pose.elbow_l, upper_arm_r),
  566. v.palette.cloth, nullptr, 1.0F);
  567. out.mesh(getUnitSphere(), sphereAt(ctx.model, pose.elbow_l, joint_r),
  568. v.palette.cloth * 0.95F, nullptr, 1.0F);
  569. out.mesh(getUnitCylinder(),
  570. cylinderBetween(ctx.model, pose.elbow_l, pose.hand_l, fore_arm_r),
  571. v.palette.skin * 0.95F, nullptr, 1.0F);
  572. out.mesh(getUnitSphere(), sphereAt(ctx.model, pose.hand_l, hand_r),
  573. v.palette.leatherDark * 0.92F, nullptr, 1.0F);
  574. out.mesh(
  575. getUnitCylinder(),
  576. cylinderBetween(ctx.model, pose.shoulder_r, pose.elbow_r, upper_arm_r),
  577. v.palette.cloth, nullptr, 1.0F);
  578. out.mesh(getUnitSphere(), sphereAt(ctx.model, pose.elbow_r, joint_r),
  579. v.palette.cloth * 0.95F, nullptr, 1.0F);
  580. out.mesh(getUnitCylinder(),
  581. cylinderBetween(ctx.model, pose.elbow_r, pose.hand_r, fore_arm_r),
  582. v.palette.skin * 0.95F, nullptr, 1.0F);
  583. out.mesh(getUnitSphere(), sphereAt(ctx.model, pose.hand_r, hand_r),
  584. v.palette.leatherDark * 0.92F, nullptr, 1.0F);
  585. QVector3D const hip_l = pose.pelvis_pos + QVector3D(-0.10F, -0.02F, 0.0F);
  586. QVector3D const hip_r = pose.pelvis_pos + QVector3D(0.10F, -0.02F, 0.0F);
  587. out.mesh(getUnitCylinder(),
  588. cylinderBetween(ctx.model, hip_l, pose.knee_l, thigh_r),
  589. v.palette.cloth * 0.92F, nullptr, 1.0F);
  590. out.mesh(getUnitSphere(), sphereAt(ctx.model, pose.knee_l, leg_joint_r),
  591. v.palette.cloth * 0.90F, nullptr, 1.0F);
  592. out.mesh(getUnitCylinder(),
  593. cylinderBetween(ctx.model, pose.knee_l, pose.foot_l, shin_r),
  594. v.palette.leather * 0.95F, nullptr, 1.0F);
  595. out.mesh(getUnitCylinder(),
  596. cylinderBetween(ctx.model, hip_r, pose.knee_r, thigh_r),
  597. v.palette.cloth * 0.92F, nullptr, 1.0F);
  598. out.mesh(getUnitSphere(), sphereAt(ctx.model, pose.knee_r, leg_joint_r),
  599. v.palette.cloth * 0.90F, nullptr, 1.0F);
  600. out.mesh(getUnitCylinder(),
  601. cylinderBetween(ctx.model, pose.knee_r, pose.foot_r, shin_r),
  602. v.palette.leather * 0.95F, nullptr, 1.0F);
  603. auto draw_foot = [&](const QVector3D &ankle, bool is_left) {
  604. QVector3D lateral = is_left ? -right_axis : right_axis;
  605. QVector3D foot_forward =
  606. forward_axis + lateral * (is_left ? -0.12F : 0.12F);
  607. if (foot_forward.lengthSquared() < 1e-6F) {
  608. foot_forward = forward_axis;
  609. }
  610. foot_forward.normalize();
  611. float const heel_span = foot_radius * 3.50F;
  612. float const toe_span = foot_radius * 5.50F;
  613. float const sole_y = HP::GROUND_Y;
  614. QVector3D ankle_ground = ankle;
  615. ankle_ground.setY(sole_y);
  616. QVector3D heel = ankle_ground - foot_forward * heel_span;
  617. QVector3D toe = ankle_ground + foot_forward * toe_span;
  618. heel.setY(sole_y);
  619. toe.setY(sole_y);
  620. QMatrix4x4 foot_mat = capsuleBetween(ctx.model, heel, toe, foot_radius);
  621. float const width_at_heel = 1.2F;
  622. float const width_at_toe = 2.5F;
  623. float const height_scale = 0.26F;
  624. float const depth_scale = 1.0F;
  625. QMatrix4x4 scale_mat;
  626. scale_mat.setToIdentity();
  627. scale_mat.scale((width_at_heel + width_at_toe) * 0.5F, height_scale,
  628. depth_scale);
  629. QMatrix4x4 shear_mat;
  630. shear_mat.setToIdentity();
  631. shear_mat(0, 2) = (width_at_toe - width_at_heel) * 0.5F;
  632. foot_mat = foot_mat * scale_mat * shear_mat;
  633. out.mesh(getUnitCapsule(), foot_mat, v.palette.leatherDark * 0.92F, nullptr,
  634. 1.0F);
  635. };
  636. draw_foot(pose.foot_l, true);
  637. draw_foot(pose.foot_r, false);
  638. draw_armorOverlay(ctx, v, pose, y_top_cover, torso_r, shoulder_half_span,
  639. upper_arm_r, right_axis, out);
  640. drawShoulderDecorations(ctx, v, pose, y_top_cover, pose.neck_base.y(),
  641. right_axis, out);
  642. draw_helmet(ctx, v, pose, out);
  643. }
  644. void HumanoidRendererBase::draw_armorOverlay(const DrawContext &,
  645. const HumanoidVariant &,
  646. const HumanoidPose &, float, float,
  647. float, float, const QVector3D &,
  648. ISubmitter &) const {}
  649. void HumanoidRendererBase::draw_armor(const DrawContext &,
  650. const HumanoidVariant &,
  651. const HumanoidPose &,
  652. const HumanoidAnimationContext &,
  653. ISubmitter &) const {}
  654. void HumanoidRendererBase::drawShoulderDecorations(const DrawContext &,
  655. const HumanoidVariant &,
  656. const HumanoidPose &, float,
  657. float, const QVector3D &,
  658. ISubmitter &) const {}
  659. void HumanoidRendererBase::draw_helmet(const DrawContext &,
  660. const HumanoidVariant &,
  661. const HumanoidPose &,
  662. ISubmitter &) const {}
  663. void HumanoidRendererBase::drawFacialHair(const DrawContext &ctx,
  664. const HumanoidVariant &v,
  665. const HumanoidPose &pose,
  666. ISubmitter &out) const {
  667. const FacialHairParams &fh = v.facial_hair;
  668. if (fh.style == FacialHairStyle::None || fh.coverage < 0.01F) {
  669. return;
  670. }
  671. const AttachmentFrame &frame = pose.body_frames.head;
  672. float const head_r = frame.radius;
  673. if (head_r <= 0.0F) {
  674. return;
  675. }
  676. auto saturate = [](const QVector3D &c) -> QVector3D {
  677. return {std::clamp(c.x(), 0.0F, 1.0F), std::clamp(c.y(), 0.0F, 1.0F),
  678. std::clamp(c.z(), 0.0F, 1.0F)};
  679. };
  680. QVector3D hair_color = fh.color * (1.0F - fh.greyness) +
  681. QVector3D(0.75F, 0.75F, 0.75F) * fh.greyness;
  682. QVector3D hair_dark = hair_color * 0.80F;
  683. QVector3D const hair_root = hair_dark * 0.95F;
  684. QVector3D const hair_tip = hair_color * 1.08F;
  685. float const chin_y = -head_r * 0.95F;
  686. float const mouth_y = -head_r * 0.18F;
  687. float const jaw_z = head_r * 0.68F;
  688. float const chin_norm = chin_y / head_r;
  689. float const mouth_norm = mouth_y / head_r;
  690. float const jaw_forward_norm = jaw_z / head_r;
  691. uint32_t rand_state = 0x9E3779B9U;
  692. if (ctx.entity != nullptr) {
  693. uintptr_t ptr = reinterpret_cast<uintptr_t>(ctx.entity);
  694. rand_state ^= static_cast<uint32_t>(ptr & 0xFFFFFFFFU);
  695. rand_state ^= static_cast<uint32_t>((ptr >> 32) & 0xFFFFFFFFU);
  696. }
  697. rand_state ^= static_cast<uint32_t>(fh.length * 9973.0F);
  698. rand_state ^= static_cast<uint32_t>(fh.thickness * 6151.0F);
  699. rand_state ^= static_cast<uint32_t>(fh.coverage * 4099.0F);
  700. auto random01 = [&]() -> float {
  701. rand_state = rand_state * 1664525U + 1013904223U;
  702. return hash_01(rand_state);
  703. };
  704. auto jitter = [&](float amplitude) -> float {
  705. return (random01() - 0.5F) * 2.0F * amplitude;
  706. };
  707. float const beard_forward_tilt_norm = 0.32F;
  708. auto place_strands = [&](int rows, int segments, float jaw_span,
  709. float row_spacing_norm, float base_length_norm,
  710. float length_variation, float forward_bias_norm,
  711. float base_radius_norm) {
  712. if (rows <= 0 || segments <= 0) {
  713. return;
  714. }
  715. const float phi_half_range = std::max(0.35F, jaw_span * 0.5F);
  716. const float base_y_norm = chin_norm + 0.10F;
  717. for (int row = 0; row < rows; ++row) {
  718. float const row_t = (rows > 1) ? float(row) / float(rows - 1) : 0.0F;
  719. float const target_y_norm =
  720. std::clamp(base_y_norm + row_t * row_spacing_norm, -0.92F, 0.10F);
  721. float const plane_radius =
  722. std::sqrt(std::max(0.02F, 1.0F - target_y_norm * target_y_norm));
  723. for (int seg = 0; seg < segments; ++seg) {
  724. float const seg_t =
  725. (segments > 1) ? float(seg) / float(segments - 1) : 0.5F;
  726. float const base_phi = (seg_t - 0.5F) * jaw_span;
  727. float const phi = std::clamp(base_phi + jitter(0.25F), -phi_half_range,
  728. phi_half_range);
  729. float const coverage_falloff =
  730. 1.0F - std::abs(phi) / std::max(0.001F, phi_half_range);
  731. float const keep_prob = std::clamp(
  732. fh.coverage * (0.75F + coverage_falloff * 0.35F), 0.05F, 1.0F);
  733. if (random01() > keep_prob) {
  734. continue;
  735. }
  736. float const wrap_scale = 0.80F + (1.0F - row_t) * 0.20F;
  737. float lateral_norm = plane_radius * std::sin(phi) * wrap_scale;
  738. float forward_norm = plane_radius * std::cos(phi);
  739. lateral_norm += jitter(0.06F);
  740. forward_norm += jitter(0.08F);
  741. float const y_norm = target_y_norm + jitter(0.05F);
  742. QVector3D surface_dir(lateral_norm, y_norm,
  743. forward_norm *
  744. (0.75F + forward_bias_norm * 0.45F) +
  745. (forward_bias_norm - 0.05F));
  746. float const dir_len = surface_dir.length();
  747. if (dir_len < 1e-4F) {
  748. continue;
  749. }
  750. surface_dir /= dir_len;
  751. float const shell = 1.02F + jitter(0.03F);
  752. QVector3D const root = frameLocalPosition(frame, surface_dir * shell);
  753. QVector3D local_dir(jitter(0.15F),
  754. -(0.55F + row_t * 0.30F) + jitter(0.10F),
  755. forward_bias_norm + beard_forward_tilt_norm +
  756. row_t * 0.20F + jitter(0.12F));
  757. QVector3D strand_dir =
  758. frame.right * local_dir.x() + frame.up * local_dir.y() +
  759. frame.forward * local_dir.z() - surface_dir * 0.25F;
  760. if (strand_dir.lengthSquared() < 1e-6F) {
  761. continue;
  762. }
  763. strand_dir.normalize();
  764. float const strand_length = base_length_norm * fh.length *
  765. (1.0F + length_variation * jitter(0.5F)) *
  766. (1.0F + row_t * 0.25F);
  767. if (strand_length < 0.05F) {
  768. continue;
  769. }
  770. QVector3D const tip = root + strand_dir * (head_r * strand_length);
  771. float const base_radius =
  772. std::max(head_r * base_radius_norm * fh.thickness *
  773. (0.7F + coverage_falloff * 0.35F),
  774. head_r * 0.010F);
  775. float const mid_radius = base_radius * 0.55F;
  776. float const color_jitter = 0.85F + random01() * 0.30F;
  777. QVector3D const root_color = saturate(hair_root * color_jitter);
  778. QVector3D const tip_color = saturate(hair_tip * color_jitter);
  779. QMatrix4x4 base_blob = sphereAt(ctx.model, root, base_radius * 0.95F);
  780. out.mesh(getUnitSphere(), base_blob, root_color, nullptr, 1.0F);
  781. QVector3D const mid = root + (tip - root) * 0.40F;
  782. out.mesh(getUnitCylinder(),
  783. cylinderBetween(ctx.model, root, mid, base_radius), root_color,
  784. nullptr, 1.0F);
  785. out.mesh(getUnitCone(), coneFromTo(ctx.model, mid, tip, mid_radius),
  786. tip_color, nullptr, 1.0F);
  787. }
  788. }
  789. };
  790. auto place_mustache = [&](int segments, float base_length_norm,
  791. float upward_bias_norm) {
  792. if (segments <= 0) {
  793. return;
  794. }
  795. float const mustache_y_norm = mouth_norm + upward_bias_norm - 0.04F;
  796. float const phi_half_range = 0.55F;
  797. for (int side = -1; side <= 1; side += 2) {
  798. for (int seg = 0; seg < segments; ++seg) {
  799. float const t =
  800. (segments > 1) ? float(seg) / float(segments - 1) : 0.5F;
  801. float const base_phi = (t - 0.5F) * (phi_half_range * 2.0F);
  802. float const phi = std::clamp(base_phi + jitter(0.18F), -phi_half_range,
  803. phi_half_range);
  804. float const plane_radius = std::sqrt(
  805. std::max(0.02F, 1.0F - mustache_y_norm * mustache_y_norm));
  806. float lateral_norm = plane_radius * std::sin(phi);
  807. float forward_norm = plane_radius * std::cos(phi);
  808. lateral_norm += jitter(0.04F);
  809. forward_norm += jitter(0.05F);
  810. if (random01() > fh.coverage) {
  811. continue;
  812. }
  813. QVector3D surface_dir(lateral_norm, mustache_y_norm + jitter(0.03F),
  814. forward_norm * 0.85F + 0.20F);
  815. float const dir_len = surface_dir.length();
  816. if (dir_len < 1e-4F) {
  817. continue;
  818. }
  819. surface_dir /= dir_len;
  820. QVector3D const root =
  821. frameLocalPosition(frame, surface_dir * (1.01F + jitter(0.02F)));
  822. QVector3D const dir_local(side * (0.55F + jitter(0.12F)), jitter(0.06F),
  823. 0.34F + jitter(0.08F));
  824. QVector3D strand_dir =
  825. frame.right * dir_local.x() + frame.up * dir_local.y() +
  826. frame.forward * dir_local.z() - surface_dir * 0.20F;
  827. if (strand_dir.lengthSquared() < 1e-6F) {
  828. continue;
  829. }
  830. strand_dir.normalize();
  831. float const strand_length =
  832. base_length_norm * fh.length * (1.0F + jitter(0.35F));
  833. QVector3D const tip = root + strand_dir * (head_r * strand_length);
  834. float const base_radius =
  835. std::max(head_r * 0.028F * fh.thickness, head_r * 0.0065F);
  836. float const mid_radius = base_radius * 0.45F;
  837. float const color_jitter = 0.92F + random01() * 0.18F;
  838. QVector3D const root_color =
  839. saturate(hair_root * (color_jitter * 0.95F));
  840. QVector3D const tip_color = saturate(hair_tip * (color_jitter * 1.02F));
  841. out.mesh(getUnitSphere(), sphereAt(ctx.model, root, base_radius * 0.7F),
  842. root_color, nullptr, 1.0F);
  843. QVector3D const mid = root + (tip - root) * 0.5F;
  844. out.mesh(getUnitCylinder(),
  845. cylinderBetween(ctx.model, root, mid, base_radius * 0.85F),
  846. root_color, nullptr, 1.0F);
  847. out.mesh(getUnitCone(), coneFromTo(ctx.model, mid, tip, mid_radius),
  848. tip_color, nullptr, 1.0F);
  849. }
  850. }
  851. };
  852. switch (fh.style) {
  853. case FacialHairStyle::Stubble: {
  854. place_strands(1, 11, 2.0F, 0.12F, 0.28F, 0.30F, 0.08F, 0.035F);
  855. break;
  856. }
  857. case FacialHairStyle::ShortBeard: {
  858. place_strands(3, 14, 2.6F, 0.18F, 0.58F, 0.38F, 0.12F, 0.055F);
  859. break;
  860. }
  861. case FacialHairStyle::FullBeard:
  862. case FacialHairStyle::LongBeard: {
  863. bool const is_long = (fh.style == FacialHairStyle::LongBeard);
  864. if (is_long) {
  865. place_strands(5, 20, 3.0F, 0.24F, 1.00F, 0.48F, 0.18F, 0.060F);
  866. } else {
  867. place_strands(4, 18, 2.8F, 0.22F, 0.85F, 0.42F, 0.16F, 0.058F);
  868. }
  869. break;
  870. }
  871. case FacialHairStyle::Goatee: {
  872. place_strands(2, 8, 1.8F, 0.16F, 0.70F, 0.34F, 0.14F, 0.055F);
  873. break;
  874. }
  875. case FacialHairStyle::Mustache: {
  876. place_mustache(5, 0.32F, 0.05F);
  877. break;
  878. }
  879. case FacialHairStyle::MustacheAndBeard: {
  880. FacialHairParams mustache_only = fh;
  881. mustache_only.style = FacialHairStyle::Mustache;
  882. FacialHairParams beard_only = fh;
  883. beard_only.style = FacialHairStyle::ShortBeard;
  884. HumanoidVariant v_mustache = v;
  885. v_mustache.facial_hair = mustache_only;
  886. drawFacialHair(ctx, v_mustache, pose, out);
  887. HumanoidVariant v_beard = v;
  888. v_beard.facial_hair = beard_only;
  889. drawFacialHair(ctx, v_beard, pose, out);
  890. break;
  891. }
  892. case FacialHairStyle::None:
  893. default:
  894. break;
  895. }
  896. }
  897. void HumanoidRendererBase::drawSimplifiedBody(const DrawContext &ctx,
  898. const HumanoidVariant &v,
  899. HumanoidPose &pose,
  900. ISubmitter &out) const {
  901. using HP = HumanProportions;
  902. QVector3D const scaling = get_proportion_scaling();
  903. float const width_scale = scaling.x();
  904. float const height_scale = scaling.y();
  905. float const torso_scale = get_torso_scale();
  906. QVector3D right_axis = pose.shoulder_r - pose.shoulder_l;
  907. if (right_axis.lengthSquared() < 1e-8F) {
  908. right_axis = QVector3D(1, 0, 0);
  909. }
  910. right_axis.normalize();
  911. QVector3D const up_axis(0.0F, 1.0F, 0.0F);
  912. QVector3D forward_axis = QVector3D::crossProduct(right_axis, up_axis);
  913. if (forward_axis.lengthSquared() < 1e-8F) {
  914. forward_axis = QVector3D(0.0F, 0.0F, 1.0F);
  915. }
  916. forward_axis.normalize();
  917. QVector3D const shoulder_mid = (pose.shoulder_l + pose.shoulder_r) * 0.5F;
  918. const float y_shoulder = shoulder_mid.y();
  919. const float y_neck = pose.neck_base.y();
  920. const float shoulder_half_span =
  921. 0.5F * std::abs(pose.shoulder_r.x() - pose.shoulder_l.x());
  922. const float torso_r_base =
  923. std::max(HP::TORSO_TOP_R, shoulder_half_span * 0.95F);
  924. const float torso_r = torso_r_base * torso_scale;
  925. float const depth_scale = scaling.z();
  926. const float torso_depth_factor =
  927. std::clamp(0.55F + (depth_scale - 1.0F) * 0.20F, 0.40F, 0.85F);
  928. float torso_depth = torso_r * torso_depth_factor;
  929. const float y_top_cover = std::max(y_shoulder + 0.00F, y_neck - 0.03F);
  930. const float upper_arm_r = HP::UPPER_ARM_R * width_scale;
  931. const float fore_arm_r = HP::FORE_ARM_R * width_scale;
  932. const float thigh_r = HP::UPPER_LEG_R * width_scale;
  933. const float shin_r = HP::LOWER_LEG_R * width_scale;
  934. QVector3D const tunic_top{shoulder_mid.x(), y_top_cover - 0.006F,
  935. shoulder_mid.z()};
  936. QVector3D const tunic_bot{pose.pelvis_pos.x(), pose.pelvis_pos.y() - 0.05F,
  937. pose.pelvis_pos.z()};
  938. QMatrix4x4 torso_transform =
  939. cylinderBetween(ctx.model, tunic_top, tunic_bot, 1.0F);
  940. torso_transform.scale(torso_r, 1.0F, torso_depth);
  941. Mesh *torso_mesh = torso_mesh_without_bottom_cap();
  942. if (torso_mesh != nullptr) {
  943. out.mesh(torso_mesh, torso_transform, v.palette.cloth, nullptr, 1.0F);
  944. }
  945. float const head_r = pose.head_r;
  946. QMatrix4x4 head_transform = ctx.model;
  947. head_transform.translate(pose.head_pos);
  948. head_transform.scale(head_r);
  949. out.mesh(getUnitSphere(), head_transform, v.palette.skin, nullptr, 1.0F);
  950. out.mesh(getUnitCylinder(),
  951. cylinderBetween(ctx.model, pose.shoulder_l, pose.hand_l,
  952. (upper_arm_r + fore_arm_r) * 0.5F),
  953. v.palette.cloth, nullptr, 1.0F);
  954. out.mesh(getUnitCylinder(),
  955. cylinderBetween(ctx.model, pose.shoulder_r, pose.hand_r,
  956. (upper_arm_r + fore_arm_r) * 0.5F),
  957. v.palette.cloth, nullptr, 1.0F);
  958. QVector3D const hip_l = pose.pelvis_pos + QVector3D(-0.10F, -0.02F, 0.0F);
  959. QVector3D const hip_r = pose.pelvis_pos + QVector3D(0.10F, -0.02F, 0.0F);
  960. out.mesh(
  961. getUnitCylinder(),
  962. cylinderBetween(ctx.model, hip_l, pose.foot_l, (thigh_r + shin_r) * 0.5F),
  963. v.palette.cloth * 0.92F, nullptr, 1.0F);
  964. out.mesh(
  965. getUnitCylinder(),
  966. cylinderBetween(ctx.model, hip_r, pose.foot_r, (thigh_r + shin_r) * 0.5F),
  967. v.palette.cloth * 0.92F, nullptr, 1.0F);
  968. }
  969. void HumanoidRendererBase::drawMinimalBody(const DrawContext &ctx,
  970. const HumanoidVariant &v,
  971. const HumanoidPose &pose,
  972. ISubmitter &out) const {
  973. using HP = HumanProportions;
  974. QVector3D const top = pose.head_pos + QVector3D(0.0F, pose.head_r, 0.0F);
  975. QVector3D const bot = (pose.foot_l + pose.foot_r) * 0.5F;
  976. float const body_radius = HP::TORSO_TOP_R * get_torso_scale();
  977. out.mesh(getUnitCapsule(), capsuleBetween(ctx.model, top, bot, body_radius),
  978. v.palette.cloth, nullptr, 1.0F);
  979. }
  980. void HumanoidRendererBase::render(const DrawContext &ctx,
  981. ISubmitter &out) const {
  982. FormationParams const formation = resolveFormation(ctx);
  983. AnimationInputs const anim = sampleAnimState(ctx);
  984. Engine::Core::UnitComponent *unit_comp = nullptr;
  985. if (ctx.entity != nullptr) {
  986. unit_comp = ctx.entity->getComponent<Engine::Core::UnitComponent>();
  987. }
  988. Engine::Core::MovementComponent *movement_comp = nullptr;
  989. Engine::Core::TransformComponent *transform_comp = nullptr;
  990. if (ctx.entity != nullptr) {
  991. movement_comp = ctx.entity->getComponent<Engine::Core::MovementComponent>();
  992. transform_comp =
  993. ctx.entity->getComponent<Engine::Core::TransformComponent>();
  994. }
  995. float entity_ground_offset =
  996. resolve_entity_ground_offset(ctx, unit_comp, transform_comp);
  997. uint32_t seed = 0U;
  998. if (unit_comp != nullptr) {
  999. seed ^= uint32_t(unit_comp->owner_id * 2654435761U);
  1000. }
  1001. if (ctx.entity != nullptr) {
  1002. seed ^= uint32_t(reinterpret_cast<uintptr_t>(ctx.entity) & 0xFFFFFFFFU);
  1003. }
  1004. const int rows =
  1005. (formation.individuals_per_unit + formation.max_per_row - 1) /
  1006. formation.max_per_row;
  1007. const int cols = formation.max_per_row;
  1008. bool is_mounted_spawn = false;
  1009. if (unit_comp != nullptr) {
  1010. using Game::Units::SpawnType;
  1011. auto const st = unit_comp->spawn_type;
  1012. is_mounted_spawn =
  1013. (st == SpawnType::MountedKnight || st == SpawnType::HorseArcher ||
  1014. st == SpawnType::HorseSpearman);
  1015. }
  1016. int visible_count = rows * cols;
  1017. if (unit_comp != nullptr) {
  1018. int const mh = std::max(1, unit_comp->max_health);
  1019. float const ratio = std::clamp(unit_comp->health / float(mh), 0.0F, 1.0F);
  1020. visible_count = std::max(1, (int)std::ceil(ratio * float(rows * cols)));
  1021. }
  1022. HumanoidVariant variant;
  1023. get_variant(ctx, seed, variant);
  1024. if (!m_proportionScaleCached) {
  1025. m_cachedProportionScale = get_proportion_scaling();
  1026. m_proportionScaleCached = true;
  1027. }
  1028. const QVector3D prop_scale = m_cachedProportionScale;
  1029. const float height_scale = prop_scale.y();
  1030. const bool needs_height_scaling = std::abs(height_scale - 1.0F) > 0.001F;
  1031. const QMatrix4x4 k_identity_matrix;
  1032. const IFormationCalculator *formation_calculator = nullptr;
  1033. {
  1034. using Nation = FormationCalculatorFactory::Nation;
  1035. using UnitCategory = FormationCalculatorFactory::UnitCategory;
  1036. Nation nation = Nation::Roman;
  1037. if (unit_comp != nullptr) {
  1038. if (unit_comp->nation_id == Game::Systems::NationID::Carthage) {
  1039. nation = Nation::Carthage;
  1040. }
  1041. }
  1042. UnitCategory category =
  1043. is_mounted_spawn ? UnitCategory::Cavalry : UnitCategory::Infantry;
  1044. formation_calculator =
  1045. FormationCalculatorFactory::getCalculator(nation, category);
  1046. }
  1047. auto fast_random = [](uint32_t &state) -> float {
  1048. state = state * 1664525U + 1013904223U;
  1049. return float(state & 0x7FFFFFU) / float(0x7FFFFFU);
  1050. };
  1051. s_renderStats.soldiersTotal += visible_count;
  1052. for (int idx = 0; idx < visible_count; ++idx) {
  1053. int const r = idx / cols;
  1054. int const c = idx % cols;
  1055. FormationOffset const formation_offset =
  1056. formation_calculator->calculateOffset(idx, r, c, rows, cols,
  1057. formation.spacing, seed);
  1058. float offset_x = formation_offset.offset_x;
  1059. float offset_z = formation_offset.offset_z;
  1060. uint32_t const inst_seed = seed ^ uint32_t(idx * 9176U);
  1061. uint32_t rng_state = inst_seed;
  1062. float const vertical_jitter = (fast_random(rng_state) - 0.5F) * 0.03F;
  1063. float const yaw_offset = (fast_random(rng_state) - 0.5F) * 5.0F;
  1064. float const phase_offset = fast_random(rng_state) * 0.25F;
  1065. float applied_vertical_jitter = vertical_jitter;
  1066. float applied_yaw_offset = yaw_offset;
  1067. QMatrix4x4 inst_model;
  1068. float applied_yaw = yaw_offset;
  1069. if (transform_comp != nullptr) {
  1070. applied_yaw = transform_comp->rotation.y + applied_yaw_offset;
  1071. QMatrix4x4 m = k_identity_matrix;
  1072. m.translate(transform_comp->position.x, transform_comp->position.y,
  1073. transform_comp->position.z);
  1074. m.rotate(applied_yaw, 0.0F, 1.0F, 0.0F);
  1075. m.scale(transform_comp->scale.x, transform_comp->scale.y,
  1076. transform_comp->scale.z);
  1077. m.translate(offset_x, applied_vertical_jitter, offset_z);
  1078. if (entity_ground_offset != 0.0F) {
  1079. m.translate(0.0F, -entity_ground_offset, 0.0F);
  1080. }
  1081. inst_model = m;
  1082. } else {
  1083. inst_model = ctx.model;
  1084. inst_model.rotate(applied_yaw, 0.0F, 1.0F, 0.0F);
  1085. inst_model.translate(offset_x, applied_vertical_jitter, offset_z);
  1086. if (entity_ground_offset != 0.0F) {
  1087. inst_model.translate(0.0F, -entity_ground_offset, 0.0F);
  1088. }
  1089. }
  1090. QVector3D const soldier_world_pos =
  1091. inst_model.map(QVector3D(0.0F, 0.0F, 0.0F));
  1092. constexpr float kSoldierCullRadius = 0.6F;
  1093. if (ctx.camera != nullptr &&
  1094. !ctx.camera->isInFrustum(soldier_world_pos, kSoldierCullRadius)) {
  1095. ++s_renderStats.soldiersSkippedFrustum;
  1096. continue;
  1097. }
  1098. HumanoidLOD soldier_lod = HumanoidLOD::Full;
  1099. float soldier_distance = 0.0F;
  1100. if (ctx.camera != nullptr) {
  1101. soldier_distance =
  1102. (soldier_world_pos - ctx.camera->getPosition()).length();
  1103. soldier_lod = calculateHumanoidLOD(soldier_distance);
  1104. if (soldier_lod == HumanoidLOD::Billboard) {
  1105. ++s_renderStats.soldiersSkippedLOD;
  1106. continue;
  1107. }
  1108. }
  1109. ++s_renderStats.soldiersRendered;
  1110. DrawContext inst_ctx{ctx.resources, ctx.entity, ctx.world, inst_model};
  1111. inst_ctx.selected = ctx.selected;
  1112. inst_ctx.hovered = ctx.hovered;
  1113. inst_ctx.animationTime = ctx.animationTime;
  1114. inst_ctx.rendererId = ctx.rendererId;
  1115. inst_ctx.backend = ctx.backend;
  1116. inst_ctx.camera = ctx.camera;
  1117. VariationParams variation = VariationParams::fromSeed(inst_seed);
  1118. adjust_variation(inst_ctx, inst_seed, variation);
  1119. float const combined_height_scale = height_scale * variation.height_scale;
  1120. if (needs_height_scaling ||
  1121. std::abs(variation.height_scale - 1.0F) > 0.001F) {
  1122. QMatrix4x4 scale_matrix;
  1123. scale_matrix.scale(variation.bulk_scale, combined_height_scale, 1.0F);
  1124. inst_ctx.model = inst_ctx.model * scale_matrix;
  1125. }
  1126. HumanoidPose pose;
  1127. bool usedCachedPose = false;
  1128. PoseCacheKey cacheKey =
  1129. makePoseCacheKey(reinterpret_cast<uintptr_t>(ctx.entity), idx);
  1130. auto cacheIt = s_poseCache.find(cacheKey);
  1131. if (!anim.is_moving && cacheIt != s_poseCache.end()) {
  1132. const CachedPoseEntry &cached = cacheIt->second;
  1133. if (!cached.wasMoving &&
  1134. s_currentFrame - cached.frameNumber < kPoseCacheMaxAge) {
  1135. pose = cached.pose;
  1136. usedCachedPose = true;
  1137. ++s_renderStats.posesCached;
  1138. }
  1139. }
  1140. if (!usedCachedPose) {
  1141. computeLocomotionPose(inst_seed, anim.time + phase_offset, anim.is_moving,
  1142. variation, pose);
  1143. ++s_renderStats.posesComputed;
  1144. CachedPoseEntry &entry = s_poseCache[cacheKey];
  1145. entry.pose = pose;
  1146. entry.variation = variation;
  1147. entry.frameNumber = s_currentFrame;
  1148. entry.wasMoving = anim.is_moving;
  1149. }
  1150. HumanoidAnimationContext anim_ctx{};
  1151. anim_ctx.inputs = anim;
  1152. anim_ctx.variation = variation;
  1153. anim_ctx.formation = formation;
  1154. anim_ctx.jitter_seed = phase_offset;
  1155. anim_ctx.instance_position =
  1156. inst_ctx.model.map(QVector3D(0.0F, 0.0F, 0.0F));
  1157. float yaw_rad = qDegreesToRadians(applied_yaw);
  1158. QVector3D forward(std::sin(yaw_rad), 0.0F, std::cos(yaw_rad));
  1159. if (forward.lengthSquared() > 1e-8F) {
  1160. forward.normalize();
  1161. } else {
  1162. forward = QVector3D(0.0F, 0.0F, 1.0F);
  1163. }
  1164. QVector3D up(0.0F, 1.0F, 0.0F);
  1165. QVector3D right = QVector3D::crossProduct(up, forward);
  1166. if (right.lengthSquared() > 1e-8F) {
  1167. right.normalize();
  1168. } else {
  1169. right = QVector3D(1.0F, 0.0F, 0.0F);
  1170. }
  1171. anim_ctx.entity_forward = forward;
  1172. anim_ctx.entity_right = right;
  1173. anim_ctx.entity_up = up;
  1174. anim_ctx.locomotion_direction = forward;
  1175. anim_ctx.yaw_degrees = applied_yaw;
  1176. anim_ctx.yaw_radians = yaw_rad;
  1177. anim_ctx.has_movement_target = false;
  1178. anim_ctx.move_speed = 0.0F;
  1179. anim_ctx.movement_target = QVector3D(0.0F, 0.0F, 0.0F);
  1180. if (movement_comp != nullptr) {
  1181. QVector3D velocity(movement_comp->vx, 0.0F, movement_comp->vz);
  1182. float speed = velocity.length();
  1183. anim_ctx.move_speed = speed;
  1184. if (speed > 1e-4F) {
  1185. anim_ctx.locomotion_direction = velocity.normalized();
  1186. }
  1187. anim_ctx.has_movement_target = movement_comp->hasTarget;
  1188. anim_ctx.movement_target =
  1189. QVector3D(movement_comp->target_x, 0.0F, movement_comp->target_y);
  1190. }
  1191. anim_ctx.locomotion_velocity =
  1192. anim_ctx.locomotion_direction * anim_ctx.move_speed;
  1193. anim_ctx.motion_state = classifyMotionState(anim, anim_ctx.move_speed);
  1194. anim_ctx.gait.state = anim_ctx.motion_state;
  1195. anim_ctx.gait.speed = anim_ctx.move_speed;
  1196. anim_ctx.gait.velocity = anim_ctx.locomotion_velocity;
  1197. anim_ctx.gait.has_target = anim_ctx.has_movement_target;
  1198. anim_ctx.gait.is_airborne = false;
  1199. float reference_speed = (anim_ctx.gait.state == HumanoidMotionState::Run)
  1200. ? k_reference_run_speed
  1201. : k_reference_walk_speed;
  1202. if (reference_speed > 0.0001F) {
  1203. anim_ctx.gait.normalized_speed =
  1204. std::clamp(anim_ctx.gait.speed / reference_speed, 0.0F, 1.0F);
  1205. } else {
  1206. anim_ctx.gait.normalized_speed = 0.0F;
  1207. }
  1208. if (!anim.is_moving) {
  1209. anim_ctx.gait.normalized_speed = 0.0F;
  1210. }
  1211. if (anim.is_moving) {
  1212. float stride_base = 0.8F;
  1213. if (anim_ctx.motion_state == HumanoidMotionState::Run) {
  1214. stride_base = 0.55F;
  1215. }
  1216. float const base_cycle =
  1217. stride_base / std::max(0.1F, variation.walk_speed_mult);
  1218. anim_ctx.locomotion_cycle_time = base_cycle;
  1219. anim_ctx.locomotion_phase = std::fmod(
  1220. (anim.time + phase_offset) / std::max(0.001F, base_cycle), 1.0F);
  1221. anim_ctx.gait.cycle_time = anim_ctx.locomotion_cycle_time;
  1222. anim_ctx.gait.cycle_phase = anim_ctx.locomotion_phase;
  1223. anim_ctx.gait.stride_distance =
  1224. anim_ctx.gait.speed * anim_ctx.gait.cycle_time;
  1225. } else {
  1226. anim_ctx.locomotion_cycle_time = 0.0F;
  1227. anim_ctx.locomotion_phase = 0.0F;
  1228. anim_ctx.gait.cycle_time = 0.0F;
  1229. anim_ctx.gait.cycle_phase = 0.0F;
  1230. anim_ctx.gait.stride_distance = 0.0F;
  1231. }
  1232. if (anim.is_attacking) {
  1233. anim_ctx.attack_phase = std::fmod(anim.time, 1.0F);
  1234. }
  1235. customize_pose(inst_ctx, anim_ctx, inst_seed, pose);
  1236. if (anim_ctx.motion_state == HumanoidMotionState::Run) {
  1237. pose.pelvis_pos.setZ(pose.pelvis_pos.z() - 0.05F);
  1238. pose.shoulder_l.setZ(pose.shoulder_l.z() + 0.10F);
  1239. pose.shoulder_r.setZ(pose.shoulder_r.z() + 0.10F);
  1240. pose.neck_base.setZ(pose.neck_base.z() + 0.06F);
  1241. pose.head_pos.setZ(pose.head_pos.z() + 0.04F);
  1242. pose.shoulder_l.setY(pose.shoulder_l.y() - 0.02F);
  1243. pose.shoulder_r.setY(pose.shoulder_r.y() - 0.02F);
  1244. if (pose.head_frame.radius > 0.001F) {
  1245. QVector3D head_up = pose.head_pos - pose.neck_base;
  1246. if (head_up.lengthSquared() < 1e-8F) {
  1247. head_up = pose.head_frame.up;
  1248. } else {
  1249. head_up.normalize();
  1250. }
  1251. QVector3D head_right =
  1252. pose.head_frame.right -
  1253. head_up * QVector3D::dotProduct(pose.head_frame.right, head_up);
  1254. if (head_right.lengthSquared() < 1e-8F) {
  1255. head_right =
  1256. QVector3D::crossProduct(head_up, anim_ctx.entity_forward);
  1257. if (head_right.lengthSquared() < 1e-8F) {
  1258. head_right = QVector3D(1.0F, 0.0F, 0.0F);
  1259. }
  1260. }
  1261. head_right.normalize();
  1262. QVector3D head_forward =
  1263. QVector3D::crossProduct(head_right, head_up).normalized();
  1264. pose.head_frame.origin = pose.head_pos;
  1265. pose.head_frame.up = head_up;
  1266. pose.head_frame.right = head_right;
  1267. pose.head_frame.forward = head_forward;
  1268. pose.body_frames.head = pose.head_frame;
  1269. }
  1270. }
  1271. const auto &gfxSettings = Render::GraphicsSettings::instance();
  1272. const bool shouldRenderShadow =
  1273. gfxSettings.shadowsEnabled() &&
  1274. (soldier_lod == HumanoidLOD::Full ||
  1275. soldier_lod == HumanoidLOD::Reduced) &&
  1276. soldier_distance < gfxSettings.shadowMaxDistance();
  1277. if (shouldRenderShadow && inst_ctx.backend != nullptr &&
  1278. inst_ctx.resources != nullptr) {
  1279. auto *shadowShader =
  1280. inst_ctx.backend->shader(QStringLiteral("troop_shadow"));
  1281. auto *quadMesh = inst_ctx.resources->quad();
  1282. if (shadowShader != nullptr && quadMesh != nullptr) {
  1283. float const shadowSize =
  1284. is_mounted_spawn ? k_shadow_size_mounted : k_shadow_size_infantry;
  1285. float depth_boost = 1.0F;
  1286. float width_boost = 1.0F;
  1287. if (unit_comp != nullptr) {
  1288. using Game::Units::SpawnType;
  1289. switch (unit_comp->spawn_type) {
  1290. case SpawnType::Spearman:
  1291. depth_boost = 1.8F;
  1292. width_boost = 0.95F;
  1293. break;
  1294. case SpawnType::HorseSpearman:
  1295. depth_boost = 2.1F;
  1296. width_boost = 1.05F;
  1297. break;
  1298. case SpawnType::Archer:
  1299. case SpawnType::HorseArcher:
  1300. depth_boost = 1.2F;
  1301. width_boost = 0.95F;
  1302. break;
  1303. default:
  1304. break;
  1305. }
  1306. }
  1307. float const shadowWidth =
  1308. shadowSize * (is_mounted_spawn ? 1.05F : 1.0F) * width_boost;
  1309. float const shadowDepth =
  1310. shadowSize * (is_mounted_spawn ? 1.30F : 1.10F) * depth_boost;
  1311. auto &terrain_service = Game::Map::TerrainService::instance();
  1312. if (terrain_service.isInitialized()) {
  1313. QVector3D const instPos =
  1314. inst_ctx.model.map(QVector3D(0.0F, 0.0F, 0.0F));
  1315. float const shadowY =
  1316. terrain_service.getTerrainHeight(instPos.x(), instPos.z());
  1317. QVector3D light_dir = k_shadow_light_dir.normalized();
  1318. QVector2D light_dir_xz(light_dir.x(), light_dir.z());
  1319. if (light_dir_xz.lengthSquared() < 1e-6F) {
  1320. light_dir_xz = QVector2D(0.0F, 1.0F);
  1321. } else {
  1322. light_dir_xz.normalize();
  1323. }
  1324. QVector2D const shadow_dir = -light_dir_xz;
  1325. QVector2D dir_for_use = shadow_dir;
  1326. if (dir_for_use.lengthSquared() < 1e-6F) {
  1327. dir_for_use = QVector2D(0.0F, 1.0F);
  1328. } else {
  1329. dir_for_use.normalize();
  1330. }
  1331. float const shadowOffset = shadowDepth * 1.25F;
  1332. QVector2D const offset2d = dir_for_use * shadowOffset;
  1333. float const lightYawDeg = qRadiansToDegrees(
  1334. std::atan2(double(dir_for_use.x()), double(dir_for_use.y())));
  1335. QMatrix4x4 shadowModel;
  1336. shadowModel.translate(instPos.x() + offset2d.x(),
  1337. shadowY + k_shadow_ground_offset,
  1338. instPos.z() + offset2d.y());
  1339. shadowModel.rotate(lightYawDeg, 0.0F, 1.0F, 0.0F);
  1340. shadowModel.rotate(-90.0F, 1.0F, 0.0F, 0.0F);
  1341. shadowModel.scale(shadowWidth, shadowDepth, 1.0F);
  1342. if (auto *renderer = dynamic_cast<Renderer *>(&out)) {
  1343. Shader *previous_shader = renderer->getCurrentShader();
  1344. renderer->setCurrentShader(shadowShader);
  1345. shadowShader->setUniform(QStringLiteral("u_lightDir"), dir_for_use);
  1346. out.mesh(quadMesh, shadowModel, QVector3D(0.0F, 0.0F, 0.0F),
  1347. nullptr, k_shadow_base_alpha, 0);
  1348. renderer->setCurrentShader(previous_shader);
  1349. }
  1350. }
  1351. }
  1352. }
  1353. switch (soldier_lod) {
  1354. case HumanoidLOD::Full:
  1355. ++s_renderStats.lodFull;
  1356. drawCommonBody(inst_ctx, variant, pose, out);
  1357. drawFacialHair(inst_ctx, variant, pose, out);
  1358. draw_armor(inst_ctx, variant, pose, anim_ctx, out);
  1359. addAttachments(inst_ctx, variant, pose, anim_ctx, out);
  1360. break;
  1361. case HumanoidLOD::Reduced:
  1362. ++s_renderStats.lodReduced;
  1363. drawSimplifiedBody(inst_ctx, variant, pose, out);
  1364. draw_armor(inst_ctx, variant, pose, anim_ctx, out);
  1365. addAttachments(inst_ctx, variant, pose, anim_ctx, out);
  1366. break;
  1367. case HumanoidLOD::Minimal:
  1368. ++s_renderStats.lodMinimal;
  1369. drawMinimalBody(inst_ctx, variant, pose, out);
  1370. break;
  1371. case HumanoidLOD::Billboard:
  1372. break;
  1373. }
  1374. }
  1375. }
  1376. auto getHumanoidRenderStats() -> const HumanoidRenderStats & {
  1377. return s_renderStats;
  1378. }
  1379. void resetHumanoidRenderStats() { s_renderStats.reset(); }
  1380. } // namespace Render::GL