rig.cpp 60 KB

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