mounted_knight_renderer.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  1. #include "mounted_knight_renderer.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 "../geom/math_utils.h"
  8. #include "../geom/transforms.h"
  9. #include "../gl/backend.h"
  10. #include "../gl/mesh.h"
  11. #include "../gl/primitives.h"
  12. #include "../gl/shader.h"
  13. #include "../humanoid_base.h"
  14. #include "../humanoid_math.h"
  15. #include "../humanoid_specs.h"
  16. #include "../palette.h"
  17. #include "../scene_renderer.h"
  18. #include "../submitter.h"
  19. #include "horse_renderer.h"
  20. #include "registry.h"
  21. #include <unordered_map>
  22. #include <QMatrix4x4>
  23. #include <QString>
  24. #include <QVector3D>
  25. #include <algorithm>
  26. #include <cmath>
  27. #include <cstdint>
  28. namespace Render::GL {
  29. using Render::Geom::clamp01;
  30. using Render::Geom::clampf;
  31. using Render::Geom::coneFromTo;
  32. using Render::Geom::cylinderBetween;
  33. using Render::Geom::sphereAt;
  34. static constexpr std::size_t MAX_EXTRAS_CACHE_SIZE = 10000;
  35. static inline float easeInOutCubic(float t) {
  36. t = clamp01(t);
  37. return t < 0.5f ? 4.0f * t * t * t
  38. : 1.0f - std::pow(-2.0f * t + 2.0f, 3.0f) / 2.0f;
  39. }
  40. static inline float smoothstep(float a, float b, float x) {
  41. x = clamp01((x - a) / (b - a));
  42. return x * x * (3.0f - 2.0f * x);
  43. }
  44. static inline float lerp(float a, float b, float t) {
  45. return a * (1.0f - t) + b * t;
  46. }
  47. static inline QVector3D nlerp(const QVector3D &a, const QVector3D &b, float t) {
  48. QVector3D v = a * (1.0f - t) + b * t;
  49. if (v.lengthSquared() > 1e-6f)
  50. v.normalize();
  51. return v;
  52. }
  53. struct MountedKnightExtras {
  54. QVector3D metalColor;
  55. HorseProfile horseProfile;
  56. float swordLength = 0.85f;
  57. float swordWidth = 0.045f;
  58. bool hasSword = true;
  59. bool hasCavalryShield = false;
  60. };
  61. class MountedKnightRenderer : public HumanoidRendererBase {
  62. public:
  63. QVector3D getProportionScaling() const override {
  64. return QVector3D(1.40f, 1.05f, 1.10f);
  65. }
  66. private:
  67. mutable std::unordered_map<uint32_t, MountedKnightExtras> m_extrasCache;
  68. HorseRenderer m_horseRenderer;
  69. public:
  70. void getVariant(const DrawContext &ctx, uint32_t seed,
  71. HumanoidVariant &v) const override {
  72. QVector3D teamTint = resolveTeamTint(ctx);
  73. v.palette = makeHumanoidPalette(teamTint, seed);
  74. }
  75. void customizePose(const DrawContext &ctx, const AnimationInputs &anim,
  76. uint32_t seed, HumanoidPose &pose) const override {
  77. using HP = HumanProportions;
  78. const float armHeightJitter = (hash01(seed ^ 0xABCDu) - 0.5f) * 0.03f;
  79. const float armAsymmetry = (hash01(seed ^ 0xDEF0u) - 0.5f) * 0.04f;
  80. uint32_t horseSeed = seed;
  81. if (ctx.entity) {
  82. horseSeed = static_cast<uint32_t>(
  83. reinterpret_cast<uintptr_t>(ctx.entity) & 0xFFFFFFFFu);
  84. }
  85. const HorseDimensions dims = makeHorseDimensions(horseSeed);
  86. const float saddleHeight = dims.saddleHeight;
  87. const float offsetY = saddleHeight - pose.pelvisPos.y();
  88. pose.pelvisPos.setY(pose.pelvisPos.y() + offsetY);
  89. pose.headPos.setY(pose.headPos.y() + offsetY);
  90. pose.neckBase.setY(pose.neckBase.y() + offsetY);
  91. pose.shoulderL.setY(pose.shoulderL.y() + offsetY);
  92. pose.shoulderR.setY(pose.shoulderR.y() + offsetY);
  93. const float leanForward = dims.seatForwardOffset * 0.08f;
  94. pose.shoulderL.setZ(pose.shoulderL.z() + leanForward);
  95. pose.shoulderR.setZ(pose.shoulderR.z() + leanForward);
  96. const float stirrupForward = dims.seatForwardOffset - 0.035f;
  97. const float stirrupHeight = saddleHeight - dims.stirrupDrop;
  98. pose.footYOffset = 0.0f;
  99. pose.footL = QVector3D(-dims.stirrupOut, stirrupHeight, stirrupForward);
  100. pose.footR = QVector3D(dims.stirrupOut, stirrupHeight, stirrupForward);
  101. const float kneeY = stirrupHeight + (saddleHeight - stirrupHeight) * 0.62f;
  102. const float kneeZ = stirrupForward * 0.60f + 0.06f;
  103. pose.kneeL = QVector3D(-dims.stirrupOut * 0.92f, kneeY, kneeZ);
  104. pose.kneeR = QVector3D(dims.stirrupOut * 0.92f, kneeY, kneeZ);
  105. const float reinForward = dims.seatForwardOffset + 0.22f;
  106. const float shoulderHeight = pose.shoulderL.y();
  107. const float reinSpread = HP::SHOULDER_WIDTH * 0.36f;
  108. QVector3D restHandR(reinSpread, shoulderHeight - 0.05f + armHeightJitter,
  109. reinForward);
  110. QVector3D restHandL(-reinSpread * 0.85f,
  111. shoulderHeight - 0.08f - armHeightJitter * 0.4f,
  112. reinForward - 0.05f);
  113. restHandR.setX(restHandR.x() + armAsymmetry * 0.45f);
  114. restHandL.setX(restHandL.x() - armAsymmetry * 0.55f);
  115. pose.elbowL = QVector3D(pose.shoulderL.x() * 0.4f + restHandL.x() * 0.6f,
  116. (pose.shoulderL.y() + restHandL.y()) * 0.5f - 0.08f,
  117. (pose.shoulderL.z() + restHandL.z()) * 0.5f);
  118. pose.elbowR = QVector3D(pose.shoulderR.x() * 0.4f + restHandR.x() * 0.6f,
  119. (pose.shoulderR.y() + restHandR.y()) * 0.5f - 0.08f,
  120. (pose.shoulderR.z() + restHandR.z()) * 0.5f);
  121. if (anim.isAttacking && anim.isMelee) {
  122. const float attackCycleTime = 0.70f;
  123. float attackPhase = std::fmod(anim.time / attackCycleTime, 1.0f);
  124. QVector3D restPos = restHandR;
  125. QVector3D windupPos = QVector3D(
  126. restHandR.x() + 0.32f, shoulderHeight + 0.15f, reinForward - 0.35f);
  127. QVector3D raisedPos = QVector3D(
  128. reinSpread + 0.38f, shoulderHeight + 0.28f, reinForward - 0.25f);
  129. QVector3D slashPos = QVector3D(
  130. -reinSpread * 0.65f, shoulderHeight - 0.08f, reinForward + 0.85f);
  131. QVector3D followThrough = QVector3D(
  132. -reinSpread * 0.85f, shoulderHeight - 0.15f, reinForward + 0.60f);
  133. QVector3D recoverPos = QVector3D(
  134. reinSpread * 0.45f, shoulderHeight - 0.05f, reinForward + 0.25f);
  135. if (attackPhase < 0.18f) {
  136. float t = easeInOutCubic(attackPhase / 0.18f);
  137. pose.handR = restPos * (1.0f - t) + windupPos * t;
  138. } else if (attackPhase < 0.30f) {
  139. float t = easeInOutCubic((attackPhase - 0.18f) / 0.12f);
  140. pose.handR = windupPos * (1.0f - t) + raisedPos * t;
  141. } else if (attackPhase < 0.48f) {
  142. float t = (attackPhase - 0.30f) / 0.18f;
  143. t = t * t * t;
  144. pose.handR = raisedPos * (1.0f - t) + slashPos * t;
  145. } else if (attackPhase < 0.62f) {
  146. float t = easeInOutCubic((attackPhase - 0.48f) / 0.14f);
  147. pose.handR = slashPos * (1.0f - t) + followThrough * t;
  148. } else if (attackPhase < 0.80f) {
  149. float t = easeInOutCubic((attackPhase - 0.62f) / 0.18f);
  150. pose.handR = followThrough * (1.0f - t) + recoverPos * t;
  151. } else {
  152. float t = smoothstep(0.80f, 1.0f, attackPhase);
  153. pose.handR = recoverPos * (1.0f - t) + restPos * t;
  154. }
  155. float reinTension = clamp01((attackPhase - 0.10f) * 2.2f);
  156. pose.handL = restHandL +
  157. QVector3D(0.0f, -0.015f * reinTension, 0.10f * reinTension);
  158. pose.elbowR =
  159. QVector3D(pose.shoulderR.x() * 0.3f + pose.handR.x() * 0.7f,
  160. (pose.shoulderR.y() + pose.handR.y()) * 0.5f - 0.12f,
  161. (pose.shoulderR.z() + pose.handR.z()) * 0.5f);
  162. pose.elbowL =
  163. QVector3D(pose.shoulderL.x() * 0.4f + pose.handL.x() * 0.6f,
  164. (pose.shoulderL.y() + pose.handL.y()) * 0.5f - 0.08f,
  165. (pose.shoulderL.z() + pose.handL.z()) * 0.5f);
  166. } else {
  167. pose.handR = restHandR;
  168. pose.handL = restHandL;
  169. }
  170. }
  171. void addAttachments(const DrawContext &ctx, const HumanoidVariant &v,
  172. const HumanoidPose &pose, const AnimationInputs &anim,
  173. ISubmitter &out) const override {
  174. uint32_t horseSeed = 0u;
  175. if (ctx.entity) {
  176. horseSeed = static_cast<uint32_t>(
  177. reinterpret_cast<uintptr_t>(ctx.entity) & 0xFFFFFFFFu);
  178. }
  179. MountedKnightExtras extras;
  180. auto it = m_extrasCache.find(horseSeed);
  181. if (it != m_extrasCache.end()) {
  182. extras = it->second;
  183. } else {
  184. extras = computeMountedKnightExtras(horseSeed, v);
  185. m_extrasCache[horseSeed] = extras;
  186. if (m_extrasCache.size() > MAX_EXTRAS_CACHE_SIZE) {
  187. m_extrasCache.clear();
  188. }
  189. }
  190. m_horseRenderer.render(ctx, anim, extras.horseProfile, out);
  191. bool isAttacking = anim.isAttacking && anim.isMelee;
  192. float attackPhase = 0.0f;
  193. if (isAttacking) {
  194. float attackCycleTime = 0.7f;
  195. attackPhase = std::fmod(anim.time * (1.0f / attackCycleTime), 1.0f);
  196. }
  197. if (extras.hasSword) {
  198. drawSword(ctx, pose, v, extras, isAttacking, attackPhase, out);
  199. }
  200. if (extras.hasCavalryShield) {
  201. drawCavalryShield(ctx, pose, v, extras, out);
  202. }
  203. }
  204. void drawHelmet(const DrawContext &ctx, const HumanoidVariant &v,
  205. const HumanoidPose &pose, ISubmitter &out) const override {
  206. using HP = HumanProportions;
  207. auto ring = [&](const QVector3D &center, float r, float h,
  208. const QVector3D &col) {
  209. QVector3D a = center + QVector3D(0, h * 0.5f, 0);
  210. QVector3D b = center - QVector3D(0, h * 0.5f, 0);
  211. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, a, b, r), col,
  212. nullptr, 1.0f);
  213. };
  214. QVector3D steelColor = v.palette.metal * QVector3D(0.95f, 0.96f, 1.0f);
  215. float helmR = pose.headR * 1.15f;
  216. QVector3D helmBot(0, pose.headPos.y() - pose.headR * 0.20f, 0);
  217. QVector3D helmTop(0, pose.headPos.y() + pose.headR * 1.40f, 0);
  218. out.mesh(getUnitCylinder(),
  219. cylinderBetween(ctx.model, helmBot, helmTop, helmR), steelColor,
  220. nullptr, 1.0f);
  221. QVector3D capTop(0, pose.headPos.y() + pose.headR * 1.48f, 0);
  222. out.mesh(getUnitCylinder(),
  223. cylinderBetween(ctx.model, helmTop, capTop, helmR * 0.98f),
  224. steelColor * 1.05f, nullptr, 1.0f);
  225. ring(QVector3D(0, pose.headPos.y() + pose.headR * 1.25f, 0), helmR * 1.02f,
  226. 0.015f, steelColor * 1.08f);
  227. ring(QVector3D(0, pose.headPos.y() + pose.headR * 0.50f, 0), helmR * 1.02f,
  228. 0.015f, steelColor * 1.08f);
  229. ring(QVector3D(0, pose.headPos.y() - pose.headR * 0.05f, 0), helmR * 1.02f,
  230. 0.015f, steelColor * 1.08f);
  231. float visorY = pose.headPos.y() + pose.headR * 0.15f;
  232. float visorZ = helmR * 0.72f;
  233. QVector3D visorHL(-helmR * 0.35f, visorY, visorZ);
  234. QVector3D visorHR(helmR * 0.35f, visorY, visorZ);
  235. out.mesh(getUnitCylinder(),
  236. cylinderBetween(ctx.model, visorHL, visorHR, 0.012f),
  237. QVector3D(0.1f, 0.1f, 0.1f), nullptr, 1.0f);
  238. QVector3D visorVT(0, visorY + helmR * 0.25f, visorZ);
  239. QVector3D visorVB(0, visorY - helmR * 0.25f, visorZ);
  240. out.mesh(getUnitCylinder(),
  241. cylinderBetween(ctx.model, visorVB, visorVT, 0.012f),
  242. QVector3D(0.1f, 0.1f, 0.1f), nullptr, 1.0f);
  243. auto drawBreathingHole = [&](float x, float y) {
  244. QVector3D pos(x, pose.headPos.y() + y, helmR * 0.70f);
  245. QMatrix4x4 m = ctx.model;
  246. m.translate(pos);
  247. m.scale(0.010f);
  248. out.mesh(getUnitSphere(), m, QVector3D(0.1f, 0.1f, 0.1f), nullptr, 1.0f);
  249. };
  250. for (int i = 0; i < 4; ++i) {
  251. drawBreathingHole(helmR * 0.50f, pose.headR * (0.05f - i * 0.10f));
  252. }
  253. for (int i = 0; i < 4; ++i) {
  254. drawBreathingHole(-helmR * 0.50f, pose.headR * (0.05f - i * 0.10f));
  255. }
  256. QVector3D plumeBase(0, pose.headPos.y() + pose.headR * 1.50f, 0);
  257. QVector3D brassColor = v.palette.metal * QVector3D(1.3f, 1.1f, 0.7f);
  258. QMatrix4x4 plume = ctx.model;
  259. plume.translate(plumeBase);
  260. plume.scale(0.030f, 0.015f, 0.030f);
  261. out.mesh(getUnitSphere(), plume, brassColor * 1.2f, nullptr, 1.0f);
  262. for (int i = 0; i < 5; ++i) {
  263. float offset = i * 0.025f;
  264. QVector3D featherStart =
  265. plumeBase + QVector3D(0, 0.005f, -0.020f + offset * 0.5f);
  266. QVector3D featherEnd = featherStart + QVector3D(0, 0.15f - i * 0.015f,
  267. -0.08f + offset * 0.3f);
  268. out.mesh(getUnitCylinder(),
  269. cylinderBetween(ctx.model, featherStart, featherEnd, 0.008f),
  270. v.palette.cloth * (1.1f - i * 0.05f), nullptr, 1.0f);
  271. }
  272. }
  273. void drawArmorOverlay(const DrawContext &ctx, const HumanoidVariant &v,
  274. const HumanoidPose &pose, float yTopCover, float torsoR,
  275. float shoulderHalfSpan, float upperArmR,
  276. const QVector3D &rightAxis,
  277. ISubmitter &out) const override {
  278. using HP = HumanProportions;
  279. auto ring = [&](const QVector3D &center, float r, float h,
  280. const QVector3D &col) {
  281. QVector3D a = center + QVector3D(0, h * 0.5f, 0);
  282. QVector3D b = center - QVector3D(0, h * 0.5f, 0);
  283. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, a, b, r), col,
  284. nullptr, 1.0f);
  285. };
  286. QVector3D steelColor = v.palette.metal * QVector3D(0.95f, 0.96f, 1.0f);
  287. QVector3D darkSteel = steelColor * 0.85f;
  288. QVector3D brassColor = v.palette.metal * QVector3D(1.3f, 1.1f, 0.7f);
  289. QVector3D bpTop(0, yTopCover + 0.02f, 0);
  290. QVector3D bpMid(0, (yTopCover + pose.pelvisPos.y()) * 0.5f + 0.04f, 0);
  291. QVector3D bpBot(0, pose.pelvisPos.y() + 0.06f, 0);
  292. float rChest = torsoR * 1.18f;
  293. float rWaist = torsoR * 1.14f;
  294. out.mesh(getUnitCylinder(),
  295. cylinderBetween(ctx.model, bpTop, bpMid, rChest), steelColor,
  296. nullptr, 1.0f);
  297. QVector3D bpMidLow(0, (bpMid.y() + bpBot.y()) * 0.5f, 0);
  298. out.mesh(getUnitCylinder(),
  299. cylinderBetween(ctx.model, bpMid, bpMidLow, rChest * 0.98f),
  300. steelColor * 0.99f, nullptr, 1.0f);
  301. out.mesh(getUnitCone(), coneFromTo(ctx.model, bpBot, bpMidLow, rWaist),
  302. steelColor * 0.98f, nullptr, 1.0f);
  303. auto drawRivet = [&](const QVector3D &pos) {
  304. QMatrix4x4 m = ctx.model;
  305. m.translate(pos);
  306. m.scale(0.012f);
  307. out.mesh(getUnitSphere(), m, brassColor, nullptr, 1.0f);
  308. };
  309. for (int i = 0; i < 8; ++i) {
  310. float angle = (i / 8.0f) * 2.0f * 3.14159265f;
  311. float x = rChest * std::sin(angle) * 0.95f;
  312. float z = rChest * std::cos(angle) * 0.95f;
  313. drawRivet(QVector3D(x, bpMid.y() + 0.08f, z));
  314. }
  315. auto drawPauldron = [&](const QVector3D &shoulder,
  316. const QVector3D &outward) {
  317. for (int i = 0; i < 4; ++i) {
  318. float segY = shoulder.y() + 0.04f - i * 0.045f;
  319. float segR = upperArmR * (2.5f - i * 0.12f);
  320. QVector3D segPos = shoulder + outward * (0.02f + i * 0.008f);
  321. segPos.setY(segY);
  322. out.mesh(getUnitSphere(), sphereAt(ctx.model, segPos, segR),
  323. i == 0 ? steelColor * 1.05f : steelColor * (1.0f - i * 0.03f),
  324. nullptr, 1.0f);
  325. if (i < 3) {
  326. drawRivet(segPos + QVector3D(0, 0.015f, 0.03f));
  327. }
  328. }
  329. };
  330. drawPauldron(pose.shoulderL, -rightAxis);
  331. drawPauldron(pose.shoulderR, rightAxis);
  332. auto drawArmPlate = [&](const QVector3D &shoulder, const QVector3D &elbow) {
  333. QVector3D dir = (elbow - shoulder);
  334. float len = dir.length();
  335. if (len < 1e-5f)
  336. return;
  337. dir /= len;
  338. for (int i = 0; i < 3; ++i) {
  339. float t0 = 0.10f + i * 0.25f;
  340. float t1 = t0 + 0.22f;
  341. QVector3D a = shoulder + dir * (t0 * len);
  342. QVector3D b = shoulder + dir * (t1 * len);
  343. float r = upperArmR * (1.32f - i * 0.04f);
  344. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, a, b, r),
  345. steelColor * (0.98f - i * 0.02f), nullptr, 1.0f);
  346. if (i < 2) {
  347. drawRivet(b);
  348. }
  349. }
  350. };
  351. drawArmPlate(pose.shoulderL, pose.elbowL);
  352. drawArmPlate(pose.shoulderR, pose.elbowR);
  353. QVector3D gorgetTop(0, yTopCover + 0.025f, 0);
  354. QVector3D gorgetBot(0, yTopCover - 0.012f, 0);
  355. out.mesh(getUnitCylinder(),
  356. cylinderBetween(ctx.model, gorgetBot, gorgetTop,
  357. HP::NECK_RADIUS * 2.6f),
  358. steelColor * 1.08f, nullptr, 1.0f);
  359. ring(gorgetTop, HP::NECK_RADIUS * 2.62f, 0.010f, brassColor);
  360. }
  361. void drawShoulderDecorations(const DrawContext &ctx, const HumanoidVariant &v,
  362. const HumanoidPose &pose, float yTopCover,
  363. float yNeck, const QVector3D &rightAxis,
  364. ISubmitter &out) const override {
  365. using HP = HumanProportions;
  366. QVector3D brassColor = v.palette.metal * QVector3D(1.3f, 1.1f, 0.7f);
  367. QVector3D chainmailColor = v.palette.metal * QVector3D(0.85f, 0.88f, 0.92f);
  368. QVector3D mantlingColor = v.palette.cloth;
  369. for (int i = 0; i < 5; ++i) {
  370. float y = yNeck - i * 0.022f;
  371. float r = HP::NECK_RADIUS * (1.85f + i * 0.08f);
  372. QVector3D ringPos(0, y, 0);
  373. QVector3D a = ringPos + QVector3D(0, 0.010f, 0);
  374. QVector3D b = ringPos - QVector3D(0, 0.010f, 0);
  375. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, a, b, r),
  376. chainmailColor * (1.0f - i * 0.04f), nullptr, 1.0f);
  377. }
  378. auto drawStud = [&](const QVector3D &pos) {
  379. QMatrix4x4 m = ctx.model;
  380. m.translate(pos);
  381. m.scale(0.008f);
  382. out.mesh(getUnitSphere(), m, brassColor * 1.3f, nullptr, 1.0f);
  383. };
  384. QVector3D beltCenter(0, HP::WAIST_Y + 0.03f, HP::TORSO_BOT_R * 1.15f);
  385. QMatrix4x4 buckle = ctx.model;
  386. buckle.translate(beltCenter);
  387. buckle.scale(0.035f, 0.025f, 0.012f);
  388. out.mesh(getUnitSphere(), buckle, brassColor * 1.25f, nullptr, 1.0f);
  389. QVector3D buckleH1 = beltCenter + QVector3D(-0.025f, 0, 0.005f);
  390. QVector3D buckleH2 = beltCenter + QVector3D(0.025f, 0, 0.005f);
  391. out.mesh(getUnitCylinder(),
  392. cylinderBetween(ctx.model, buckleH1, buckleH2, 0.006f),
  393. brassColor * 1.4f, nullptr, 1.0f);
  394. QVector3D buckleV1 = beltCenter + QVector3D(0, -0.018f, 0.005f);
  395. QVector3D buckleV2 = beltCenter + QVector3D(0, 0.018f, 0.005f);
  396. out.mesh(getUnitCylinder(),
  397. cylinderBetween(ctx.model, buckleV1, buckleV2, 0.006f),
  398. brassColor * 1.4f, nullptr, 1.0f);
  399. }
  400. private:
  401. static MountedKnightExtras
  402. computeMountedKnightExtras(uint32_t seed, const HumanoidVariant &v) {
  403. MountedKnightExtras e;
  404. e.metalColor = QVector3D(0.72f, 0.73f, 0.78f);
  405. e.horseProfile = makeHorseProfile(seed, v.palette.leather, v.palette.cloth);
  406. e.swordLength = 0.82f + (hash01(seed ^ 0xABCDu) - 0.5f) * 0.12f;
  407. e.swordWidth = 0.042f + (hash01(seed ^ 0x7777u) - 0.5f) * 0.008f;
  408. e.hasSword = (hash01(seed ^ 0xFACEu) > 0.15f);
  409. e.hasCavalryShield = (hash01(seed ^ 0xCAFEu) > 0.60f);
  410. return e;
  411. }
  412. static void drawSword(const DrawContext &ctx, const HumanoidPose &pose,
  413. const HumanoidVariant &v,
  414. const MountedKnightExtras &extras, bool isAttacking,
  415. float attackPhase, ISubmitter &out) {
  416. const QVector3D gripPos = pose.handR;
  417. QVector3D swordDir(0.0f, 0.15f, 1.0f);
  418. swordDir.normalize();
  419. QVector3D worldUp(0.0f, 1.0f, 0.0f);
  420. QVector3D rightAxis = QVector3D::crossProduct(worldUp, swordDir);
  421. if (rightAxis.lengthSquared() < 1e-6f)
  422. rightAxis = QVector3D(1.0f, 0.0f, 0.0f);
  423. rightAxis.normalize();
  424. QVector3D upAxis = QVector3D::crossProduct(swordDir, rightAxis);
  425. upAxis.normalize();
  426. const QVector3D steel = extras.metalColor;
  427. const QVector3D steelHi = steel * 1.18f;
  428. const QVector3D steelLo = steel * 0.92f;
  429. const QVector3D leather = v.palette.leather;
  430. const QVector3D pommelCol =
  431. v.palette.metal * QVector3D(1.25f, 1.10f, 0.75f);
  432. const float pommelOffset = 0.10f;
  433. const float gripLen = 0.16f;
  434. const float gripRad = 0.017f;
  435. const float guardHalf = 0.11f;
  436. const float guardRad = 0.012f;
  437. const float guardCurve = 0.03f;
  438. const QVector3D pommelPos = gripPos - swordDir * pommelOffset;
  439. out.mesh(getUnitSphere(), sphereAt(ctx.model, pommelPos, 0.028f), pommelCol,
  440. nullptr, 1.0f);
  441. {
  442. QVector3D neckA = pommelPos + swordDir * 0.010f;
  443. QVector3D neckB = gripPos - swordDir * 0.005f;
  444. out.mesh(getUnitCylinder(),
  445. cylinderBetween(ctx.model, neckA, neckB, 0.0125f), steelLo,
  446. nullptr, 1.0f);
  447. QVector3D peen = pommelPos - swordDir * 0.012f;
  448. out.mesh(getUnitCone(), coneFromTo(ctx.model, peen, pommelPos, 0.010f),
  449. steel, nullptr, 1.0f);
  450. }
  451. const QVector3D gripA = gripPos - swordDir * 0.005f;
  452. const QVector3D gripB = gripPos + swordDir * (gripLen - 0.005f);
  453. const int wrapRings = 5;
  454. for (int i = 0; i < wrapRings; ++i) {
  455. float t0 = (float)i / wrapRings;
  456. float t1 = (float)(i + 1) / wrapRings;
  457. QVector3D a = gripA + swordDir * (gripLen * t0);
  458. QVector3D b = gripA + swordDir * (gripLen * t1);
  459. float rMid = gripRad * (0.96f + 0.08f * std::sin((t0 + t1) * 3.14159f));
  460. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, a, b, rMid),
  461. leather * 0.98f, nullptr, 1.0f);
  462. }
  463. const QVector3D guardCenter = gripB + swordDir * 0.010f;
  464. {
  465. const int segs = 4;
  466. QVector3D prev =
  467. guardCenter - rightAxis * guardHalf + (-upAxis * guardCurve);
  468. for (int s = 1; s <= segs; ++s) {
  469. float u = -1.0f + 2.0f * (float)s / segs;
  470. QVector3D p = guardCenter + rightAxis * (guardHalf * u) +
  471. (-upAxis * guardCurve * (1.0f - u * u));
  472. out.mesh(getUnitCylinder(),
  473. cylinderBetween(ctx.model, prev, p, guardRad), steelHi,
  474. nullptr, 1.0f);
  475. prev = p;
  476. }
  477. QVector3D Lend =
  478. guardCenter - rightAxis * guardHalf + (-upAxis * guardCurve);
  479. QVector3D Rend =
  480. guardCenter + rightAxis * guardHalf + (-upAxis * guardCurve);
  481. out.mesh(getUnitCone(),
  482. coneFromTo(ctx.model, Lend - rightAxis * 0.030f, Lend,
  483. guardRad * 1.12f),
  484. steelHi, nullptr, 1.0f);
  485. out.mesh(getUnitCone(),
  486. coneFromTo(ctx.model, Rend + rightAxis * 0.030f, Rend,
  487. guardRad * 1.12f),
  488. steelHi, nullptr, 1.0f);
  489. out.mesh(getUnitSphere(),
  490. sphereAt(ctx.model, guardCenter, guardRad * 0.9f), steel,
  491. nullptr, 1.0f);
  492. }
  493. const float bladeLen = std::max(0.0f, extras.swordLength - 0.14f);
  494. const QVector3D bladeRoot = guardCenter + swordDir * 0.020f;
  495. const QVector3D bladeTip = bladeRoot + swordDir * bladeLen;
  496. const QVector3D ricassoEnd = bladeRoot + swordDir * (bladeLen * 0.08f);
  497. out.mesh(getUnitCylinder(),
  498. cylinderBetween(ctx.model, bladeRoot, ricassoEnd,
  499. extras.swordWidth * 0.32f),
  500. steelHi, nullptr, 1.0f);
  501. const QVector3D fullerA = bladeRoot + swordDir * (bladeLen * 0.10f);
  502. const QVector3D fullerB = bladeRoot + swordDir * (bladeLen * 0.80f);
  503. out.mesh(
  504. getUnitCylinder(),
  505. cylinderBetween(ctx.model, fullerA, fullerB, extras.swordWidth * 0.10f),
  506. steelLo, nullptr, 1.0f);
  507. const float baseR = extras.swordWidth * 0.26f;
  508. const float midR = extras.swordWidth * 0.16f;
  509. const float preTipR = extras.swordWidth * 0.09f;
  510. QVector3D s0 = ricassoEnd;
  511. QVector3D s1 = bladeRoot + swordDir * (bladeLen * 0.55f);
  512. QVector3D s2 = bladeRoot + swordDir * (bladeLen * 0.88f);
  513. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, s0, s1, baseR),
  514. steelHi, nullptr, 1.0f);
  515. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, s1, s2, midR),
  516. steelHi, nullptr, 1.0f);
  517. {
  518. float edgeR = extras.swordWidth * 0.03f;
  519. QVector3D eA = bladeRoot + swordDir * (bladeLen * 0.10f);
  520. QVector3D eB = bladeTip - swordDir * (bladeLen * 0.06f);
  521. QVector3D leftEdgeA = eA + rightAxis * (baseR * 0.95f);
  522. QVector3D leftEdgeB = eB + rightAxis * (preTipR * 0.95f);
  523. QVector3D rightEdgeA = eA - rightAxis * (baseR * 0.95f);
  524. QVector3D rightEdgeB = eB - rightAxis * (preTipR * 0.95f);
  525. out.mesh(getUnitCylinder(),
  526. cylinderBetween(ctx.model, leftEdgeA, leftEdgeB, edgeR),
  527. steel * 1.08f, nullptr, 1.0f);
  528. out.mesh(getUnitCylinder(),
  529. cylinderBetween(ctx.model, rightEdgeA, rightEdgeB, edgeR),
  530. steel * 1.08f, nullptr, 1.0f);
  531. }
  532. out.mesh(
  533. getUnitCylinder(),
  534. cylinderBetween(ctx.model, s2, bladeTip - swordDir * 0.020f, preTipR),
  535. steelHi, nullptr, 1.0f);
  536. out.mesh(getUnitCone(),
  537. coneFromTo(ctx.model, bladeTip, bladeTip - swordDir * 0.060f,
  538. preTipR * 0.95f),
  539. steelHi * 1.04f, nullptr, 1.0f);
  540. {
  541. QVector3D shoulderL0 = bladeRoot + rightAxis * (baseR * 1.05f);
  542. QVector3D shoulderL1 = shoulderL0 - rightAxis * (baseR * 0.45f);
  543. QVector3D shoulderR0 = bladeRoot - rightAxis * (baseR * 1.05f);
  544. QVector3D shoulderR1 = shoulderR0 + rightAxis * (baseR * 0.45f);
  545. out.mesh(getUnitCone(),
  546. coneFromTo(ctx.model, shoulderL1, shoulderL0, baseR * 0.22f),
  547. steel, nullptr, 1.0f);
  548. out.mesh(getUnitCone(),
  549. coneFromTo(ctx.model, shoulderR1, shoulderR0, baseR * 0.22f),
  550. steel, nullptr, 1.0f);
  551. }
  552. if (isAttacking && attackPhase >= 0.28f && attackPhase < 0.58f) {
  553. float t = (attackPhase - 0.28f) / 0.30f;
  554. float alpha = clamp01(0.40f * (1.0f - t * t));
  555. QVector3D sweep = (-rightAxis * 0.18f - swordDir * 0.10f) * t;
  556. QVector3D trailTip = bladeTip + sweep;
  557. QVector3D trailRoot = bladeRoot + sweep * 0.6f;
  558. out.mesh(getUnitCone(),
  559. coneFromTo(ctx.model, trailRoot, trailTip, baseR * 1.10f),
  560. steel * 0.90f, nullptr, alpha);
  561. out.mesh(getUnitCone(),
  562. coneFromTo(ctx.model, trailRoot + upAxis * 0.01f, trailTip,
  563. baseR * 0.75f),
  564. steel * 0.80f, nullptr, alpha * 0.7f);
  565. }
  566. }
  567. static void drawCavalryShield(const DrawContext &ctx,
  568. const HumanoidPose &pose,
  569. const HumanoidVariant &v,
  570. const MountedKnightExtras &extras,
  571. ISubmitter &out) {
  572. const float scaleFactor = 2.0f;
  573. const float R = 0.15f * scaleFactor;
  574. const float yawDeg = -70.0f;
  575. QMatrix4x4 rot;
  576. rot.rotate(yawDeg, 0.0f, 1.0f, 0.0f);
  577. const QVector3D n = rot.map(QVector3D(0.0f, 0.0f, 1.0f));
  578. const QVector3D axisX = rot.map(QVector3D(1.0f, 0.0f, 0.0f));
  579. const QVector3D axisY = rot.map(QVector3D(0.0f, 1.0f, 0.0f));
  580. QVector3D shieldCenter =
  581. pose.handL + axisX * (-R * 0.30f) + axisY * (-0.05f) + n * (0.05f);
  582. const float plateHalf = 0.0012f;
  583. const float plateFull = plateHalf * 2.0f;
  584. {
  585. QMatrix4x4 m = ctx.model;
  586. m.translate(shieldCenter + n * plateHalf);
  587. m.rotate(yawDeg, 0.0f, 1.0f, 0.0f);
  588. m.scale(R, R, plateFull);
  589. out.mesh(getUnitCylinder(), m, v.palette.cloth * 1.15f, nullptr, 1.0f);
  590. }
  591. {
  592. QMatrix4x4 m = ctx.model;
  593. m.translate(shieldCenter - n * plateHalf);
  594. m.rotate(yawDeg, 0.0f, 1.0f, 0.0f);
  595. m.scale(R * 0.985f, R * 0.985f, plateFull);
  596. out.mesh(getUnitCylinder(), m, v.palette.leather * 0.8f, nullptr, 1.0f);
  597. }
  598. {
  599. QMatrix4x4 m = ctx.model;
  600. m.translate(shieldCenter + n * (0.015f * scaleFactor));
  601. m.scale(0.035f * scaleFactor);
  602. out.mesh(getUnitSphere(), m, extras.metalColor, nullptr, 1.0f);
  603. }
  604. {
  605. QVector3D gripA = shieldCenter - axisX * 0.025f - n * 0.025f;
  606. QVector3D gripB = shieldCenter + axisX * 0.025f - n * 0.025f;
  607. out.mesh(getUnitCylinder(),
  608. cylinderBetween(ctx.model, gripA, gripB, 0.008f),
  609. v.palette.leather, nullptr, 1.0f);
  610. }
  611. }
  612. };
  613. void registerMountedKnightRenderer(
  614. Render::GL::EntityRendererRegistry &registry) {
  615. static MountedKnightRenderer renderer;
  616. registry.registerRenderer(
  617. "mounted_knight", [](const DrawContext &ctx, ISubmitter &out) {
  618. static MountedKnightRenderer staticRenderer;
  619. Shader *mountedKnightShader = nullptr;
  620. if (ctx.backend) {
  621. mountedKnightShader =
  622. ctx.backend->shader(QStringLiteral("mounted_knight"));
  623. }
  624. Renderer *sceneRenderer = dynamic_cast<Renderer *>(&out);
  625. if (sceneRenderer && mountedKnightShader) {
  626. sceneRenderer->setCurrentShader(mountedKnightShader);
  627. }
  628. staticRenderer.render(ctx, out);
  629. if (sceneRenderer) {
  630. sceneRenderer->setCurrentShader(nullptr);
  631. }
  632. });
  633. }
  634. } // namespace Render::GL