rig.cpp 51 KB

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