humanoid_base.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  1. #include "humanoid_base.h"
  2. #include "../game/core/component.h"
  3. #include "../game/core/entity.h"
  4. #include "../game/core/world.h"
  5. #include "../game/units/troop_config.h"
  6. #include "../game/visuals/team_colors.h"
  7. #include "entity/registry.h"
  8. #include "geom/math_utils.h"
  9. #include "geom/transforms.h"
  10. #include "gl/primitives.h"
  11. #include "gl/render_constants.h"
  12. #include "humanoid_math.h"
  13. #include "humanoid_specs.h"
  14. #include "palette.h"
  15. #include "submitter.h"
  16. #include "units/spawn_type.h"
  17. #include <QMatrix4x4>
  18. #include <algorithm>
  19. #include <cmath>
  20. #include <cstdint>
  21. #include <numbers>
  22. #include <qmatrix4x4.h>
  23. #include <qvectornd.h>
  24. namespace Render::GL {
  25. using namespace Render::GL::Geometry;
  26. using Render::Geom::cylinderBetween;
  27. using Render::Geom::sphereAt;
  28. void HumanoidRendererBase::getVariant(const DrawContext &ctx, uint32_t seed,
  29. HumanoidVariant &v) const {
  30. QVector3D const team_tint = resolveTeamTint(ctx);
  31. v.palette = makeHumanoidPalette(team_tint, seed);
  32. }
  33. void HumanoidRendererBase::customizePose(const DrawContext &ctx,
  34. const AnimationInputs &anim,
  35. uint32_t seed,
  36. HumanoidPose &ioPose) const {}
  37. void HumanoidRendererBase::addAttachments(const DrawContext &ctx,
  38. const HumanoidVariant &v,
  39. const HumanoidPose &pose,
  40. const AnimationInputs &anim,
  41. ISubmitter &out) const {}
  42. auto HumanoidRendererBase::resolveTeamTint(const DrawContext &ctx)
  43. -> QVector3D {
  44. QVector3D tunic(0.8F, 0.9F, 1.0F);
  45. Engine::Core::UnitComponent *unit = nullptr;
  46. Engine::Core::RenderableComponent *rc = nullptr;
  47. if (ctx.entity != nullptr) {
  48. unit = ctx.entity->getComponent<Engine::Core::UnitComponent>();
  49. rc = ctx.entity->getComponent<Engine::Core::RenderableComponent>();
  50. }
  51. if ((unit != nullptr) && unit->owner_id > 0) {
  52. tunic = Game::Visuals::team_colorForOwner(unit->owner_id);
  53. } else if (rc != nullptr) {
  54. tunic = QVector3D(rc->color[0], rc->color[1], rc->color[2]);
  55. }
  56. return tunic;
  57. }
  58. auto HumanoidRendererBase::resolveFormation(const DrawContext &ctx)
  59. -> FormationParams {
  60. FormationParams params{};
  61. params.individuals_per_unit = 1;
  62. params.max_per_row = 1;
  63. params.spacing = 0.75F;
  64. if (ctx.entity != nullptr) {
  65. auto *unit = ctx.entity->getComponent<Engine::Core::UnitComponent>();
  66. if (unit != nullptr) {
  67. params.individuals_per_unit =
  68. Game::Units::TroopConfig::instance().getIndividualsPerUnit(
  69. unit->spawn_type);
  70. params.max_per_row =
  71. Game::Units::TroopConfig::instance().getMaxUnitsPerRow(
  72. unit->spawn_type);
  73. if (unit->spawn_type == Game::Units::SpawnType::MountedKnight) {
  74. params.spacing = 1.35F;
  75. }
  76. }
  77. }
  78. return params;
  79. }
  80. auto HumanoidRendererBase::sampleAnimState(const DrawContext &ctx)
  81. -> AnimationInputs {
  82. AnimationInputs anim{};
  83. anim.time = ctx.animationTime;
  84. anim.isMoving = false;
  85. anim.is_attacking = false;
  86. anim.isMelee = false;
  87. anim.isInHoldMode = false;
  88. anim.isExitingHold = false;
  89. anim.holdExitProgress = 0.0F;
  90. if (ctx.entity == nullptr) {
  91. return anim;
  92. }
  93. if (ctx.entity->hasComponent<Engine::Core::PendingRemovalComponent>()) {
  94. return anim;
  95. }
  96. auto *movement = ctx.entity->getComponent<Engine::Core::MovementComponent>();
  97. auto *attack = ctx.entity->getComponent<Engine::Core::AttackComponent>();
  98. auto *attack_target =
  99. ctx.entity->getComponent<Engine::Core::AttackTargetComponent>();
  100. auto *transform =
  101. ctx.entity->getComponent<Engine::Core::TransformComponent>();
  102. auto *hold_mode = ctx.entity->getComponent<Engine::Core::HoldModeComponent>();
  103. anim.isInHoldMode = ((hold_mode != nullptr) && hold_mode->active);
  104. if ((hold_mode != nullptr) && !hold_mode->active &&
  105. hold_mode->exitCooldown > 0.0F) {
  106. anim.isExitingHold = true;
  107. anim.holdExitProgress =
  108. 1.0F - (hold_mode->exitCooldown / hold_mode->standUpDuration);
  109. }
  110. anim.isMoving = ((movement != nullptr) && movement->hasTarget);
  111. if ((attack != nullptr) && (attack_target != nullptr) &&
  112. attack_target->target_id > 0 && (transform != nullptr)) {
  113. anim.isMelee = (attack->currentMode ==
  114. Engine::Core::AttackComponent::CombatMode::Melee);
  115. bool const stationary = !anim.isMoving;
  116. float const current_cooldown =
  117. anim.isMelee ? attack->meleeCooldown : attack->cooldown;
  118. bool const recently_fired =
  119. attack->timeSinceLast < std::min(current_cooldown, 0.45F);
  120. bool target_in_range = false;
  121. if (ctx.world != nullptr) {
  122. auto *target = ctx.world->getEntity(attack_target->target_id);
  123. if (target != nullptr) {
  124. auto *target_transform =
  125. target->getComponent<Engine::Core::TransformComponent>();
  126. if (target_transform != nullptr) {
  127. float const dx = target_transform->position.x - transform->position.x;
  128. float const dz = target_transform->position.z - transform->position.z;
  129. float const dist_squared = dx * dx + dz * dz;
  130. float target_radius = 0.0F;
  131. if (target->hasComponent<Engine::Core::BuildingComponent>()) {
  132. target_radius =
  133. std::max(target_transform->scale.x, target_transform->scale.z) *
  134. 0.5F;
  135. } else {
  136. target_radius =
  137. std::max(target_transform->scale.x, target_transform->scale.z) *
  138. 0.5F;
  139. }
  140. float const effective_range = attack->range + target_radius + 0.25F;
  141. target_in_range = (dist_squared <= effective_range * effective_range);
  142. }
  143. }
  144. }
  145. anim.is_attacking = stationary && (target_in_range || recently_fired);
  146. }
  147. return anim;
  148. }
  149. void HumanoidRendererBase::computeLocomotionPose(
  150. uint32_t seed, float time, bool isMoving, const VariationParams &variation,
  151. HumanoidPose &pose) {
  152. using HP = HumanProportions;
  153. float const h_scale = variation.height_scale;
  154. pose.headPos =
  155. QVector3D(0.0F, (HP::HEAD_TOP_Y + HP::CHIN_Y) * 0.5F * h_scale, 0.0F);
  156. pose.headR = HP::HEAD_RADIUS * h_scale;
  157. pose.neck_base = QVector3D(0.0F, HP::NECK_BASE_Y * h_scale, 0.0F);
  158. float const b_scale = variation.bulkScale;
  159. float const s_width = variation.stanceWidth;
  160. pose.shoulderL = QVector3D(-HP::TORSO_TOP_R * 0.98F * b_scale,
  161. HP::SHOULDER_Y * h_scale, 0.0F);
  162. pose.shoulderR = QVector3D(HP::TORSO_TOP_R * 0.98F * b_scale,
  163. HP::SHOULDER_Y * h_scale, 0.0F);
  164. pose.footYOffset = 0.02F;
  165. pose.footL = QVector3D(-HP::SHOULDER_WIDTH * 0.58F * s_width,
  166. HP::GROUND_Y + pose.footYOffset, 0.0F);
  167. pose.foot_r = QVector3D(HP::SHOULDER_WIDTH * 0.58F * s_width,
  168. HP::GROUND_Y + pose.footYOffset, 0.0F);
  169. pose.pelvisPos = QVector3D(0.0F, HP::WAIST_Y * h_scale, 0.0F);
  170. pose.knee_l = QVector3D(pose.footL.x(), HP::KNEE_Y * h_scale, pose.footL.z());
  171. pose.knee_r =
  172. QVector3D(pose.foot_r.x(), HP::KNEE_Y * h_scale, pose.foot_r.z());
  173. pose.shoulderL.setY(pose.shoulderL.y() + variation.shoulderTilt);
  174. pose.shoulderR.setY(pose.shoulderR.y() - variation.shoulderTilt);
  175. float const slouch_offset = variation.postureSlump * 0.15F;
  176. pose.shoulderL.setZ(pose.shoulderL.z() + slouch_offset);
  177. pose.shoulderR.setZ(pose.shoulderR.z() + slouch_offset);
  178. float const foot_angle_jitter = (hash_01(seed ^ 0x5678U) - 0.5F) * 0.12F;
  179. float const foot_depth_jitter = (hash_01(seed ^ 0x9ABCU) - 0.5F) * 0.08F;
  180. pose.footL.setX(pose.footL.x() + foot_angle_jitter);
  181. pose.foot_r.setX(pose.foot_r.x() - foot_angle_jitter);
  182. pose.footL.setZ(pose.footL.z() + foot_depth_jitter);
  183. pose.foot_r.setZ(pose.foot_r.z() - foot_depth_jitter);
  184. float const arm_height_jitter = (hash_01(seed ^ 0xABCDU) - 0.5F) * 0.03F;
  185. float const arm_asymmetry = (hash_01(seed ^ 0xDEF0U) - 0.5F) * 0.04F;
  186. pose.handL =
  187. QVector3D(-0.05F + arm_asymmetry,
  188. HP::SHOULDER_Y * h_scale + 0.05F + arm_height_jitter, 0.55F);
  189. pose.hand_r = QVector3D(
  190. 0.15F - arm_asymmetry * 0.5F,
  191. HP::SHOULDER_Y * h_scale + 0.15F + arm_height_jitter * 0.8F, 0.20F);
  192. if (isMoving) {
  193. float const walk_cycle_time = 0.8F / variation.walkSpeedMult;
  194. float const walk_phase = std::fmod(time * (1.0F / walk_cycle_time), 1.0F);
  195. float const left_phase = walk_phase;
  196. float const right_phase = std::fmod(walk_phase + 0.5F, 1.0F);
  197. const float ground_y = HP::GROUND_Y;
  198. const float stride_length = 0.35F * variation.armSwingAmp;
  199. auto animate_foot = [ground_y, &pose, stride_length](QVector3D &foot,
  200. float phase) {
  201. float const lift = std::sin(phase * 2.0F * std::numbers::pi_v<float>);
  202. if (lift > 0.0F) {
  203. foot.setY(ground_y + pose.footYOffset + lift * 0.12F);
  204. } else {
  205. foot.setY(ground_y + pose.footYOffset);
  206. }
  207. foot.setZ(foot.z() +
  208. std::sin((phase - 0.25F) * 2.0F * std::numbers::pi_v<float>) *
  209. stride_length);
  210. };
  211. animate_foot(pose.footL, left_phase);
  212. animate_foot(pose.foot_r, right_phase);
  213. float const hip_sway =
  214. std::sin(walk_phase * 2.0F * std::numbers::pi_v<float>) * 0.02F *
  215. variation.armSwingAmp;
  216. pose.shoulderL.setX(pose.shoulderL.x() + hip_sway);
  217. pose.shoulderR.setX(pose.shoulderR.x() + hip_sway);
  218. }
  219. QVector3D right_axis = pose.shoulderR - pose.shoulderL;
  220. right_axis.setY(0.0F);
  221. if (right_axis.lengthSquared() < 1e-8F) {
  222. right_axis = QVector3D(1, 0, 0);
  223. }
  224. right_axis.normalize();
  225. QVector3D const outward_l = -right_axis;
  226. QVector3D const outward_r = right_axis;
  227. pose.elbowL = elbowBendTorso(pose.shoulderL, pose.handL, outward_l, 0.45F,
  228. 0.15F, -0.08F, +1.0F);
  229. pose.elbowR = elbowBendTorso(pose.shoulderR, pose.hand_r, outward_r, 0.48F,
  230. 0.12F, 0.02F, +1.0F);
  231. }
  232. void HumanoidRendererBase::drawCommonBody(const DrawContext &ctx,
  233. const HumanoidVariant &v,
  234. const HumanoidPose &pose,
  235. ISubmitter &out) const {
  236. using HP = HumanProportions;
  237. QVector3D const scaling = getProportionScaling();
  238. float const width_scale = scaling.x();
  239. float const height_scale = scaling.y();
  240. float const head_scale = scaling.z();
  241. QVector3D right_axis = pose.shoulderR - pose.shoulderL;
  242. if (right_axis.lengthSquared() < 1e-8F) {
  243. right_axis = QVector3D(1, 0, 0);
  244. }
  245. right_axis.normalize();
  246. const float y_shoulder = 0.5F * (pose.shoulderL.y() + pose.shoulderR.y());
  247. const float y_neck = pose.neck_base.y();
  248. const float shoulder_half_span =
  249. 0.5F * std::abs(pose.shoulderR.x() - pose.shoulderL.x());
  250. const float torso_r =
  251. std::max(HP::TORSO_TOP_R * width_scale, shoulder_half_span * 0.95F);
  252. const float y_top_cover = std::max(y_shoulder + 0.04F, y_neck + 0.00F);
  253. QVector3D const tunic_top{0.0F, y_top_cover - 0.006F, 0.0F};
  254. QVector3D const tunic_bot{0.0F, pose.pelvisPos.y() + 0.03F, 0.0F};
  255. out.mesh(getUnitTorso(),
  256. cylinderBetween(ctx.model, tunic_top, tunic_bot, torso_r),
  257. v.palette.cloth, nullptr, 1.0F);
  258. QVector3D const chin_pos{0.0F, pose.headPos.y() - pose.headR, 0.0F};
  259. out.mesh(getUnitCylinder(),
  260. cylinderBetween(ctx.model, pose.neck_base, chin_pos,
  261. HP::NECK_RADIUS * width_scale),
  262. v.palette.skin * 0.9F, nullptr, 1.0F);
  263. out.mesh(getUnitSphere(),
  264. sphereAt(ctx.model, pose.headPos, pose.headR * head_scale),
  265. v.palette.skin, nullptr, 1.0F);
  266. QVector3D const iris(0.06F, 0.06F, 0.07F);
  267. float const eye_z = pose.headR * head_scale * 0.7F;
  268. float const eye_y = pose.headPos.y() + pose.headR * head_scale * 0.1F;
  269. float const eye_spacing = pose.headR * head_scale * 0.35F;
  270. out.mesh(getUnitSphere(),
  271. ctx.model * sphereAt(QVector3D(-eye_spacing, eye_y, eye_z),
  272. pose.headR * head_scale * 0.15F),
  273. iris, nullptr, 1.0F);
  274. out.mesh(getUnitSphere(),
  275. ctx.model * sphereAt(QVector3D(eye_spacing, eye_y, eye_z),
  276. pose.headR * head_scale * 0.15F),
  277. iris, nullptr, 1.0F);
  278. const float upper_arm_r = HP::UPPER_ARM_R * width_scale;
  279. const float fore_arm_r = HP::FORE_ARM_R * width_scale;
  280. const float joint_r = HP::HAND_RADIUS * width_scale * 1.05F;
  281. const float hand_r = HP::HAND_RADIUS * width_scale * 0.95F;
  282. out.mesh(getUnitCylinder(),
  283. cylinderBetween(ctx.model, pose.shoulderL, pose.elbowL, upper_arm_r),
  284. v.palette.cloth, nullptr, 1.0F);
  285. out.mesh(getUnitSphere(), sphereAt(ctx.model, pose.elbowL, joint_r),
  286. v.palette.cloth * 0.95F, nullptr, 1.0F);
  287. out.mesh(getUnitCylinder(),
  288. cylinderBetween(ctx.model, pose.elbowL, pose.handL, fore_arm_r),
  289. v.palette.skin * 0.95F, nullptr, 1.0F);
  290. out.mesh(getUnitSphere(), sphereAt(ctx.model, pose.handL, hand_r),
  291. v.palette.leatherDark * 0.92F, nullptr, 1.0F);
  292. out.mesh(getUnitCylinder(),
  293. cylinderBetween(ctx.model, pose.shoulderR, pose.elbowR, upper_arm_r),
  294. v.palette.cloth, nullptr, 1.0F);
  295. out.mesh(getUnitSphere(), sphereAt(ctx.model, pose.elbowR, joint_r),
  296. v.palette.cloth * 0.95F, nullptr, 1.0F);
  297. out.mesh(getUnitCylinder(),
  298. cylinderBetween(ctx.model, pose.elbowR, pose.hand_r, fore_arm_r),
  299. v.palette.skin * 0.95F, nullptr, 1.0F);
  300. out.mesh(getUnitSphere(), sphereAt(ctx.model, pose.hand_r, hand_r),
  301. v.palette.leatherDark * 0.92F, nullptr, 1.0F);
  302. const float hip_half = HP::UPPER_LEG_R * width_scale * 1.7F;
  303. const float max_stance = hip_half * 2.2F;
  304. const float upper_scale = 1.40F * 3.0F * width_scale;
  305. const float lower_scale = 1.35F * 3.0F * width_scale;
  306. const float foot_len_mul = (5.5F * 0.1F);
  307. const float foot_rad_mul = 0.70F;
  308. const float knee_forward = 0.15F;
  309. const float knee_drop = 0.02F * HP::LOWER_LEG_LEN;
  310. const QVector3D fwd(0.F, 0.F, 1.F);
  311. const float upper_r = HP::UPPER_LEG_R * upper_scale;
  312. const float lower_r = HP::LOWER_LEG_R * lower_scale;
  313. const float foot_r = lower_r * foot_rad_mul;
  314. constexpr float deg = std::numbers::pi_v<float> / 180.F;
  315. const QVector3D hip_l = pose.pelvisPos + QVector3D(-hip_half, 0.F, 0.F);
  316. const QVector3D hip_r = pose.pelvisPos + QVector3D(+hip_half, 0.F, 0.F);
  317. const float mid_x = 0.5F * (hip_l.x() + hip_r.x());
  318. auto clamp_x = [&](const QVector3D &v, float mid) {
  319. const float dx = v.x() - mid;
  320. const float mag = std::min(std::abs(dx), max_stance);
  321. return QVector3D(mid + (dx < 0 ? -mag : mag), v.y(), v.z());
  322. };
  323. const QVector3D plant_l = clamp_x(pose.footL, mid_x);
  324. const QVector3D plant_r = clamp_x(pose.foot_r, mid_x);
  325. const float foot_len = foot_len_mul * lower_r;
  326. const float heel_back_frac = 0.15F;
  327. const float ball_frac = 0.72F;
  328. const float toe_up_frac = 0.06F;
  329. constexpr float k_foot_yaw_out_degrees = 12.0F;
  330. const float ankle_fwd_frac = 0.10F;
  331. const float ankle_up_frac = 0.50F;
  332. const float toe_splay_frac = 0.06F;
  333. const QVector3D fwd_l = rotY(fwd, -k_foot_yaw_out_degrees * deg);
  334. const QVector3D fwd_r = rotY(fwd, +k_foot_yaw_out_degrees * deg);
  335. const QVector3D right_l = rightOf(fwd_l);
  336. const QVector3D right_r = rightOf(fwd_r);
  337. const float foot_cly_l = plant_l.y() + foot_r;
  338. const float foot_cly_r = plant_r.y() + foot_r;
  339. QVector3D heel_cen_l(plant_l.x(), foot_cly_l, plant_l.z());
  340. QVector3D heel_cen_r(plant_r.x(), foot_cly_r, plant_r.z());
  341. QVector3D ankle_l = heel_cen_l + fwd_l * (ankle_fwd_frac * foot_len);
  342. QVector3D ankle_r = heel_cen_r + fwd_r * (ankle_fwd_frac * foot_len);
  343. ankle_l.setY(heel_cen_l.y() + ankle_up_frac * foot_r);
  344. ankle_r.setY(heel_cen_r.y() + ankle_up_frac * foot_r);
  345. const float knee_forward_push = HP::LOWER_LEG_LEN * knee_forward;
  346. const float knee_drop_abs = knee_drop;
  347. QVector3D knee_l;
  348. QVector3D knee_r;
  349. bool const use_custom_knees = (pose.knee_l.y() < HP::KNEE_Y * 0.9F ||
  350. pose.knee_r.y() < HP::KNEE_Y * 0.9F);
  351. if (use_custom_knees) {
  352. knee_l = pose.knee_l;
  353. knee_r = pose.knee_r;
  354. } else {
  355. auto compute_knee = [&](const QVector3D &hip, const QVector3D &ankle) {
  356. QVector3D const dir = ankle - hip;
  357. QVector3D knee = hip + 0.5F * dir;
  358. knee += QVector3D(0, 0, 1) * knee_forward_push;
  359. knee.setY(knee.y() - knee_drop_abs);
  360. knee.setX((hip.x() + ankle.x()) * 0.5F);
  361. return knee;
  362. };
  363. knee_l = compute_knee(hip_l, ankle_l);
  364. knee_r = compute_knee(hip_r, ankle_r);
  365. }
  366. const float heel_back = heel_back_frac * foot_len;
  367. const float ball_len = ball_frac * foot_len;
  368. const float toe_len = (1.0F - ball_frac) * foot_len;
  369. QVector3D const ball_l = heel_cen_l + fwd_l * ball_len;
  370. QVector3D const ball_r = heel_cen_r + fwd_r * ball_len;
  371. const float toe_up_l = toe_up_frac * foot_len;
  372. const float toe_up_r = toe_up_frac * foot_len;
  373. const float toe_splay = toe_splay_frac * foot_len;
  374. QVector3D toe_l = ball_l + fwd_l * toe_len - right_l * toe_splay;
  375. QVector3D toe_r = ball_r + fwd_r * toe_len + right_r * toe_splay;
  376. toe_l.setY(ball_l.y() + toe_up_l);
  377. toe_r.setY(ball_r.y() + toe_up_r);
  378. heel_cen_l -= fwd_l * heel_back;
  379. heel_cen_r -= fwd_r * heel_back;
  380. const float heel_rad = foot_r * 1.05F;
  381. const float toe_rad = foot_r * 0.85F;
  382. out.mesh(getUnitCapsule(DefaultCapsuleSegments, 1),
  383. Render::Geom::capsuleBetween(ctx.model, hip_l, knee_l, upper_r),
  384. v.palette.leather, nullptr, 1.0F);
  385. out.mesh(getUnitCapsule(DefaultCapsuleSegments, 1),
  386. Render::Geom::capsuleBetween(ctx.model, hip_r, knee_r, upper_r),
  387. v.palette.leather, nullptr, 1.0F);
  388. out.mesh(getUnitCapsule(DefaultCapsuleSegments, 1),
  389. Render::Geom::capsuleBetween(ctx.model, knee_l, ankle_l, lower_r),
  390. v.palette.leatherDark, nullptr, 1.0F);
  391. out.mesh(getUnitCapsule(DefaultCapsuleSegments, 1),
  392. Render::Geom::capsuleBetween(ctx.model, knee_r, ankle_r, lower_r),
  393. v.palette.leatherDark, nullptr, 1.0F);
  394. out.mesh(
  395. getUnitCapsule(DefaultCapsuleSegments, 1),
  396. Render::Geom::capsuleBetween(ctx.model, heel_cen_l, ball_l, heel_rad),
  397. v.palette.leatherDark, nullptr, 1.0F);
  398. out.mesh(
  399. getUnitCapsule(DefaultCapsuleSegments, 1),
  400. Render::Geom::capsuleBetween(ctx.model, heel_cen_r, ball_r, heel_rad),
  401. v.palette.leatherDark, nullptr, 1.0F);
  402. out.mesh(getUnitCapsule(DefaultCapsuleSegments, 1),
  403. Render::Geom::capsuleBetween(ctx.model, ball_l, toe_l, toe_rad),
  404. v.palette.leatherDark, nullptr, 1.0F);
  405. out.mesh(getUnitCapsule(DefaultCapsuleSegments, 1),
  406. Render::Geom::capsuleBetween(ctx.model, ball_r, toe_r, toe_rad),
  407. v.palette.leatherDark, nullptr, 1.0F);
  408. drawHelmet(ctx, v, pose, out);
  409. draw_armorOverlay(ctx, v, pose, y_top_cover, torso_r, shoulder_half_span,
  410. upper_arm_r, right_axis, out);
  411. drawShoulderDecorations(ctx, v, pose, y_top_cover, y_neck, right_axis, out);
  412. }
  413. void HumanoidRendererBase::drawHelmet(const DrawContext &ctx,
  414. const HumanoidVariant &v,
  415. const HumanoidPose &pose,
  416. ISubmitter &out) const {
  417. using HP = HumanProportions;
  418. QVector3D const cap_c = pose.headPos + QVector3D(0, pose.headR * 0.8F, 0);
  419. out.mesh(getUnitSphere(), sphereAt(ctx.model, cap_c, pose.headR * 0.85F),
  420. v.palette.cloth * 0.9F, nullptr, 1.0F);
  421. }
  422. void HumanoidRendererBase::draw_armorOverlay(
  423. const DrawContext &ctx, const HumanoidVariant &v, const HumanoidPose &pose,
  424. float y_top_cover, float torso_r, float shoulder_half_span,
  425. float upper_arm_r, const QVector3D &right_axis, ISubmitter &out) const {}
  426. void HumanoidRendererBase::drawShoulderDecorations(
  427. const DrawContext &ctx, const HumanoidVariant &v, const HumanoidPose &pose,
  428. float y_top_cover, float y_neck, const QVector3D &right_axis,
  429. ISubmitter &out) const {}
  430. void HumanoidRendererBase::render(const DrawContext &ctx,
  431. ISubmitter &out) const {
  432. FormationParams const formation = resolveFormation(ctx);
  433. AnimationInputs const anim = sampleAnimState(ctx);
  434. Engine::Core::UnitComponent *unit_comp = nullptr;
  435. if (ctx.entity != nullptr) {
  436. unit_comp = ctx.entity->getComponent<Engine::Core::UnitComponent>();
  437. }
  438. uint32_t seed = 0U;
  439. if (unit_comp != nullptr) {
  440. seed ^= uint32_t(unit_comp->owner_id * 2654435761U);
  441. }
  442. if (ctx.entity != nullptr) {
  443. seed ^= uint32_t(reinterpret_cast<uintptr_t>(ctx.entity) & 0xFFFFFFFFU);
  444. }
  445. const int rows =
  446. (formation.individuals_per_unit + formation.max_per_row - 1) /
  447. formation.max_per_row;
  448. const int cols = formation.max_per_row;
  449. int visible_count = rows * cols;
  450. if (unit_comp != nullptr) {
  451. int const mh = std::max(1, unit_comp->max_health);
  452. float const ratio = std::clamp(unit_comp->health / float(mh), 0.0F, 1.0F);
  453. visible_count = std::max(1, (int)std::ceil(ratio * float(rows * cols)));
  454. }
  455. HumanoidVariant variant;
  456. getVariant(ctx, seed, variant);
  457. if (!m_proportionScaleCached) {
  458. m_cachedProportionScale = getProportionScaling();
  459. m_proportionScaleCached = true;
  460. }
  461. const QVector3D prop_scale = m_cachedProportionScale;
  462. const float height_scale = prop_scale.y();
  463. const bool needs_height_scaling = std::abs(height_scale - 1.0F) > 0.001F;
  464. const QMatrix4x4 k_identity_matrix;
  465. auto fast_random = [](uint32_t &state) -> float {
  466. state = state * 1664525U + 1013904223U;
  467. return float(state & 0x7FFFFFU) / float(0x7FFFFFU);
  468. };
  469. for (int idx = 0; idx < visible_count; ++idx) {
  470. int const r = idx / cols;
  471. int const c = idx % cols;
  472. float offset_x = (c - (cols - 1) * 0.5F) * formation.spacing;
  473. float offset_z = (r - (rows - 1) * 0.5F) * formation.spacing;
  474. uint32_t const inst_seed = seed ^ uint32_t(idx * 9176U);
  475. uint32_t rng_state = inst_seed;
  476. float const pos_jitter_x = (fast_random(rng_state) - 0.5F) * 0.05F;
  477. float const pos_jitter_z = (fast_random(rng_state) - 0.5F) * 0.05F;
  478. float const vertical_jitter = (fast_random(rng_state) - 0.5F) * 0.03F;
  479. float const yaw_offset = (fast_random(rng_state) - 0.5F) * 5.0F;
  480. float const phase_offset = fast_random(rng_state) * 0.25F;
  481. offset_x += pos_jitter_x;
  482. offset_z += pos_jitter_z;
  483. QMatrix4x4 inst_model;
  484. if (ctx.entity != nullptr) {
  485. if (auto *ent_t =
  486. ctx.entity->getComponent<Engine::Core::TransformComponent>()) {
  487. QMatrix4x4 m = k_identity_matrix;
  488. m.translate(ent_t->position.x, ent_t->position.y, ent_t->position.z);
  489. float const base_yaw = ent_t->rotation.y;
  490. float const applied_yaw = base_yaw + yaw_offset;
  491. m.rotate(applied_yaw, 0.0F, 1.0F, 0.0F);
  492. m.scale(ent_t->scale.x, ent_t->scale.y, ent_t->scale.z);
  493. m.translate(offset_x, vertical_jitter, offset_z);
  494. inst_model = m;
  495. } else {
  496. inst_model = ctx.model;
  497. inst_model.rotate(yaw_offset, 0.0F, 1.0F, 0.0F);
  498. inst_model.translate(offset_x, vertical_jitter, offset_z);
  499. }
  500. } else {
  501. inst_model = ctx.model;
  502. inst_model.rotate(yaw_offset, 0.0F, 1.0F, 0.0F);
  503. inst_model.translate(offset_x, vertical_jitter, offset_z);
  504. }
  505. DrawContext inst_ctx{ctx.resources, ctx.entity, ctx.world, inst_model};
  506. VariationParams const variation = VariationParams::fromSeed(inst_seed);
  507. float const combined_height_scale = height_scale * variation.height_scale;
  508. if (needs_height_scaling ||
  509. std::abs(variation.height_scale - 1.0F) > 0.001F) {
  510. QMatrix4x4 scale_matrix;
  511. scale_matrix.scale(variation.bulkScale, combined_height_scale, 1.0F);
  512. inst_ctx.model = inst_ctx.model * scale_matrix;
  513. }
  514. HumanoidPose pose;
  515. computeLocomotionPose(inst_seed, anim.time + phase_offset, anim.isMoving,
  516. variation, pose);
  517. customizePose(inst_ctx, anim, inst_seed, pose);
  518. drawCommonBody(inst_ctx, variant, pose, out);
  519. addAttachments(inst_ctx, variant, pose, anim, out);
  520. }
  521. }
  522. } // namespace Render::GL