spearman_renderer.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. #include "spearman_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 "registry.h"
  20. #include "renderer_constants.h"
  21. #include <QMatrix4x4>
  22. #include <QString>
  23. #include <QVector3D>
  24. #include <algorithm>
  25. #include <cmath>
  26. #include <cstdint>
  27. #include <unordered_map>
  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::easeInOutCubic;
  34. using Render::Geom::lerp;
  35. using Render::Geom::smoothstep;
  36. using Render::Geom::sphereAt;
  37. struct SpearmanExtras {
  38. QVector3D spearShaftColor;
  39. QVector3D spearheadColor;
  40. float spearLength = 1.20f;
  41. float spearShaftRadius = 0.020f;
  42. float spearheadLength = 0.18f;
  43. };
  44. class SpearmanRenderer : public HumanoidRendererBase {
  45. public:
  46. QVector3D getProportionScaling() const override {
  47. return QVector3D(1.10f, 1.02f, 1.05f);
  48. }
  49. private:
  50. mutable std::unordered_map<uint32_t, SpearmanExtras> m_extrasCache;
  51. public:
  52. void getVariant(const DrawContext &ctx, uint32_t seed,
  53. HumanoidVariant &v) const override {
  54. QVector3D teamTint = resolveTeamTint(ctx);
  55. v.palette = makeHumanoidPalette(teamTint, seed);
  56. }
  57. void customizePose(const DrawContext &ctx, const AnimationInputs &anim,
  58. uint32_t seed, HumanoidPose &pose) const override {
  59. using HP = HumanProportions;
  60. float armHeightJitter = (hash01(seed ^ 0xABCDu) - 0.5f) * 0.03f;
  61. float armAsymmetry = (hash01(seed ^ 0xDEF0u) - 0.5f) * 0.04f;
  62. if (anim.isInHoldMode || anim.isExitingHold) {
  63. float t = anim.isInHoldMode ? 1.0f : (1.0f - anim.holdExitProgress);
  64. float kneelDepth = 0.35f * t;
  65. float pelvisY = HP::WAIST_Y - kneelDepth;
  66. pose.pelvisPos.setY(pelvisY);
  67. float stanceNarrow = 0.10f;
  68. float leftKneeY = HP::GROUND_Y + 0.06f * t;
  69. float leftKneeZ = -0.08f * t;
  70. pose.kneeL = QVector3D(-stanceNarrow, leftKneeY, leftKneeZ);
  71. pose.footL = QVector3D(-stanceNarrow - 0.02f, HP::GROUND_Y,
  72. leftKneeZ - HP::LOWER_LEG_LEN * 0.90f * t);
  73. float rightKneeY =
  74. HP::WAIST_Y * 0.45f * (1.0f - t) + HP::WAIST_Y * 0.30f * t;
  75. pose.kneeR = QVector3D(stanceNarrow + 0.05f, rightKneeY, 0.15f * t);
  76. pose.footR = QVector3D(stanceNarrow + 0.08f, HP::GROUND_Y, 0.25f * t);
  77. float upperBodyDrop = kneelDepth;
  78. pose.shoulderL.setY(HP::SHOULDER_Y - upperBodyDrop);
  79. pose.shoulderR.setY(HP::SHOULDER_Y - upperBodyDrop);
  80. pose.neckBase.setY(HP::NECK_BASE_Y - upperBodyDrop);
  81. float loweredChinY = HP::CHIN_Y - upperBodyDrop;
  82. pose.headPos.setY(loweredChinY + pose.headR);
  83. float forwardLean = 0.08f * t;
  84. pose.shoulderL.setZ(pose.shoulderL.z() + forwardLean);
  85. pose.shoulderR.setZ(pose.shoulderR.z() + forwardLean);
  86. pose.neckBase.setZ(pose.neckBase.z() + forwardLean * 0.8f);
  87. pose.headPos.setZ(pose.headPos.z() + forwardLean * 0.7f);
  88. float loweredShoulderY = HP::SHOULDER_Y - upperBodyDrop;
  89. pose.handR =
  90. QVector3D(0.18f * (1.0f - t) + 0.22f * t,
  91. loweredShoulderY * (1.0f - t) + (pelvisY + 0.05f) * t,
  92. 0.15f * (1.0f - t) + 0.20f * t);
  93. pose.handL = QVector3D(
  94. 0.0f, loweredShoulderY * (1.0f - t) + (loweredShoulderY - 0.10f) * t,
  95. 0.30f * (1.0f - t) + 0.55f * t);
  96. QVector3D shoulderToHandR = pose.handR - pose.shoulderR;
  97. float armLengthR = shoulderToHandR.length();
  98. QVector3D armDirR = shoulderToHandR.normalized();
  99. pose.elbowR = pose.shoulderR + armDirR * (armLengthR * 0.5f) +
  100. QVector3D(0.08f, -0.15f, -0.05f);
  101. QVector3D shoulderToHandL = pose.handL - pose.shoulderL;
  102. float armLengthL = shoulderToHandL.length();
  103. QVector3D armDirL = shoulderToHandL.normalized();
  104. pose.elbowL = pose.shoulderL + armDirL * (armLengthL * 0.5f) +
  105. QVector3D(-0.08f, -0.12f, 0.05f);
  106. } else if (anim.isAttacking && anim.isMelee && !anim.isInHoldMode) {
  107. float attackPhase =
  108. std::fmod(anim.time * SPEARMAN_INV_ATTACK_CYCLE_TIME, 1.0f);
  109. QVector3D guardPos(0.28f, HP::SHOULDER_Y + 0.05f, 0.25f);
  110. QVector3D preparePos(0.35f, HP::SHOULDER_Y + 0.08f, 0.05f);
  111. QVector3D thrustPos(0.32f, HP::SHOULDER_Y + 0.10f, 0.90f);
  112. QVector3D recoverPos(0.28f, HP::SHOULDER_Y + 0.06f, 0.40f);
  113. if (attackPhase < 0.20f) {
  114. float t = easeInOutCubic(attackPhase / 0.20f);
  115. pose.handR = guardPos * (1.0f - t) + preparePos * t;
  116. pose.handL = QVector3D(-0.10f, HP::SHOULDER_Y - 0.05f,
  117. 0.20f * (1.0f - t) + 0.08f * t);
  118. } else if (attackPhase < 0.30f) {
  119. pose.handR = preparePos;
  120. pose.handL = QVector3D(-0.10f, HP::SHOULDER_Y - 0.05f, 0.08f);
  121. } else if (attackPhase < 0.50f) {
  122. float t = (attackPhase - 0.30f) / 0.20f;
  123. t = t * t * t;
  124. pose.handR = preparePos * (1.0f - t) + thrustPos * t;
  125. pose.handL =
  126. QVector3D(-0.10f + 0.05f * t, HP::SHOULDER_Y - 0.05f + 0.03f * t,
  127. 0.08f + 0.45f * t);
  128. } else if (attackPhase < 0.70f) {
  129. float t = easeInOutCubic((attackPhase - 0.50f) / 0.20f);
  130. pose.handR = thrustPos * (1.0f - t) + recoverPos * t;
  131. pose.handL = QVector3D(-0.05f * (1.0f - t) - 0.10f * t,
  132. HP::SHOULDER_Y - 0.02f * (1.0f - t) - 0.06f * t,
  133. lerp(0.53f, 0.35f, t));
  134. } else {
  135. float t = smoothstep(0.70f, 1.0f, attackPhase);
  136. pose.handR = recoverPos * (1.0f - t) + guardPos * t;
  137. pose.handL = QVector3D(-0.10f - 0.02f * (1.0f - t),
  138. HP::SHOULDER_Y - 0.06f + 0.01f * t +
  139. armHeightJitter * (1.0f - t),
  140. lerp(0.35f, 0.25f, t));
  141. }
  142. } else {
  143. pose.handR = QVector3D(0.28f + armAsymmetry,
  144. HP::SHOULDER_Y - 0.02f + armHeightJitter, 0.30f);
  145. pose.handL =
  146. QVector3D(-0.08f - 0.5f * armAsymmetry,
  147. HP::SHOULDER_Y - 0.08f + 0.5f * armHeightJitter, 0.45f);
  148. QVector3D shoulderToHand = pose.handR - pose.shoulderR;
  149. float armLength = shoulderToHand.length();
  150. QVector3D armDir = shoulderToHand.normalized();
  151. pose.elbowR = pose.shoulderR + armDir * (armLength * 0.5f) +
  152. QVector3D(0.06f, -0.12f, -0.04f);
  153. }
  154. }
  155. void addAttachments(const DrawContext &ctx, const HumanoidVariant &v,
  156. const HumanoidPose &pose, const AnimationInputs &anim,
  157. ISubmitter &out) const override {
  158. uint32_t seed = reinterpret_cast<uintptr_t>(ctx.entity) & 0xFFFFFFFFu;
  159. SpearmanExtras extras;
  160. auto it = m_extrasCache.find(seed);
  161. if (it != m_extrasCache.end()) {
  162. extras = it->second;
  163. } else {
  164. extras = computeSpearmanExtras(seed, v);
  165. m_extrasCache[seed] = extras;
  166. if (m_extrasCache.size() > MAX_EXTRAS_CACHE_SIZE) {
  167. m_extrasCache.clear();
  168. }
  169. }
  170. bool isAttacking = anim.isAttacking && anim.isMelee;
  171. float attackPhase = 0.0f;
  172. if (isAttacking) {
  173. attackPhase = std::fmod(anim.time * SPEARMAN_INV_ATTACK_CYCLE_TIME, 1.0f);
  174. }
  175. drawSpear(ctx, pose, v, extras, anim, isAttacking, attackPhase, out);
  176. }
  177. void drawHelmet(const DrawContext &ctx, const HumanoidVariant &v,
  178. const HumanoidPose &pose, ISubmitter &out) const override {
  179. using HP = HumanProportions;
  180. const QVector3D ironColor = v.palette.metal * IRON_TINT;
  181. const float helmR = pose.headR * 1.12f;
  182. QVector3D helmBot(pose.headPos.x(), pose.headPos.y() - pose.headR * 0.15f,
  183. pose.headPos.z());
  184. QVector3D helmTop(pose.headPos.x(), pose.headPos.y() + pose.headR * 1.25f,
  185. pose.headPos.z());
  186. out.mesh(getUnitCylinder(),
  187. cylinderBetween(ctx.model, helmBot, helmTop, helmR), ironColor,
  188. nullptr, 1.0f);
  189. QVector3D capTop(pose.headPos.x(), pose.headPos.y() + pose.headR * 1.32f,
  190. pose.headPos.z());
  191. out.mesh(getUnitCylinder(),
  192. cylinderBetween(ctx.model, helmTop, capTop, helmR * 0.96f),
  193. ironColor * 1.04f, nullptr, 1.0f);
  194. auto ring = [&](const QVector3D &center, float r, float h,
  195. const QVector3D &col) {
  196. QVector3D a = center + QVector3D(0, h * 0.5f, 0);
  197. QVector3D b = center - QVector3D(0, h * 0.5f, 0);
  198. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, a, b, r), col,
  199. nullptr, 1.0f);
  200. };
  201. ring(QVector3D(pose.headPos.x(), pose.headPos.y() + pose.headR * 0.95f,
  202. pose.headPos.z()),
  203. helmR * 1.01f, 0.012f, ironColor * 1.06f);
  204. ring(QVector3D(pose.headPos.x(), pose.headPos.y() - pose.headR * 0.02f,
  205. pose.headPos.z()),
  206. helmR * 1.01f, 0.012f, ironColor * 1.06f);
  207. float visorY = pose.headPos.y() + pose.headR * 0.10f;
  208. float visorZ = pose.headPos.z() + helmR * 0.68f;
  209. for (int i = 0; i < 3; ++i) {
  210. float y = visorY + pose.headR * (0.18f - i * 0.12f);
  211. QVector3D visorL(pose.headPos.x() - helmR * 0.30f, y, visorZ);
  212. QVector3D visorR(pose.headPos.x() + helmR * 0.30f, y, visorZ);
  213. out.mesh(getUnitCylinder(),
  214. cylinderBetween(ctx.model, visorL, visorR, 0.010f), DARK_METAL,
  215. nullptr, 1.0f);
  216. }
  217. }
  218. void drawArmorOverlay(const DrawContext &ctx, const HumanoidVariant &v,
  219. const HumanoidPose &pose, float yTopCover, float torsoR,
  220. float shoulderHalfSpan, float upperArmR,
  221. const QVector3D &rightAxis,
  222. ISubmitter &out) const override {
  223. using HP = HumanProportions;
  224. const QVector3D ironColor = v.palette.metal * IRON_TINT;
  225. const QVector3D leatherColor = v.palette.leather * 0.95f;
  226. QVector3D chestTop(0, yTopCover + 0.02f, 0);
  227. QVector3D chestBot(0, HP::WAIST_Y + 0.08f, 0);
  228. float rChest = torsoR * 1.14f;
  229. out.mesh(getUnitCylinder(),
  230. cylinderBetween(ctx.model, chestTop, chestBot, rChest), ironColor,
  231. nullptr, 1.0f);
  232. auto drawPauldron = [&](const QVector3D &shoulder,
  233. const QVector3D &outward) {
  234. for (int i = 0; i < 3; ++i) {
  235. float segY = shoulder.y() + 0.03f - i * 0.040f;
  236. float segR = upperArmR * (2.2f - i * 0.10f);
  237. QVector3D segPos = shoulder + outward * (0.015f + i * 0.006f);
  238. segPos.setY(segY);
  239. out.mesh(getUnitSphere(), sphereAt(ctx.model, segPos, segR),
  240. i == 0 ? ironColor * 1.04f : ironColor * (1.0f - i * 0.02f),
  241. nullptr, 1.0f);
  242. }
  243. };
  244. drawPauldron(pose.shoulderL, -rightAxis);
  245. drawPauldron(pose.shoulderR, rightAxis);
  246. auto drawArmPlate = [&](const QVector3D &shoulder, const QVector3D &elbow) {
  247. QVector3D dir = (elbow - shoulder);
  248. float len = dir.length();
  249. if (len < 1e-5f) {
  250. return;
  251. }
  252. dir /= len;
  253. for (int i = 0; i < 2; ++i) {
  254. float t0 = 0.12f + i * 0.28f;
  255. float t1 = t0 + 0.24f;
  256. QVector3D a = shoulder + dir * (t0 * len);
  257. QVector3D b = shoulder + dir * (t1 * len);
  258. float r = upperArmR * (1.26f - i * 0.03f);
  259. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, a, b, r),
  260. ironColor * (0.96f - i * 0.02f), nullptr, 1.0f);
  261. }
  262. };
  263. drawArmPlate(pose.shoulderL, pose.elbowL);
  264. drawArmPlate(pose.shoulderR, pose.elbowR);
  265. for (int i = 0; i < 3; ++i) {
  266. float y = HP::WAIST_Y + 0.06f - i * 0.035f;
  267. float r = torsoR * (1.12f + i * 0.020f);
  268. QVector3D stripTop(0, y, 0);
  269. QVector3D stripBot(0, y - 0.030f, 0);
  270. out.mesh(getUnitCone(), coneFromTo(ctx.model, stripTop, stripBot, r),
  271. leatherColor * (0.98f - i * 0.02f), nullptr, 1.0f);
  272. }
  273. }
  274. void drawShoulderDecorations(const DrawContext &ctx, const HumanoidVariant &v,
  275. const HumanoidPose &pose, float yTopCover,
  276. float yNeck, const QVector3D &rightAxis,
  277. ISubmitter &out) const override {}
  278. private:
  279. static SpearmanExtras computeSpearmanExtras(uint32_t seed,
  280. const HumanoidVariant &v) {
  281. SpearmanExtras e;
  282. e.spearShaftColor = v.palette.leather * QVector3D(0.85f, 0.75f, 0.65f);
  283. e.spearheadColor = QVector3D(0.75f, 0.76f, 0.80f);
  284. e.spearLength = 1.15f + (hash01(seed ^ 0xABCDu) - 0.5f) * 0.10f;
  285. e.spearShaftRadius = 0.018f + (hash01(seed ^ 0x7777u) - 0.5f) * 0.003f;
  286. e.spearheadLength = 0.16f + (hash01(seed ^ 0xBEEFu) - 0.5f) * 0.04f;
  287. return e;
  288. }
  289. static void drawSpear(const DrawContext &ctx, const HumanoidPose &pose,
  290. const HumanoidVariant &v, const SpearmanExtras &extras,
  291. const AnimationInputs &anim, bool isAttacking,
  292. float attackPhase, ISubmitter &out) {
  293. QVector3D gripPos = pose.handR;
  294. QVector3D spearDir = QVector3D(0.05f, 0.55f, 0.85f);
  295. if (spearDir.lengthSquared() > 1e-6f) {
  296. spearDir.normalize();
  297. }
  298. if (anim.isInHoldMode || anim.isExitingHold) {
  299. float t = anim.isInHoldMode ? 1.0f : (1.0f - anim.holdExitProgress);
  300. QVector3D bracedDir = QVector3D(0.05f, 0.40f, 0.91f);
  301. if (bracedDir.lengthSquared() > 1e-6f) {
  302. bracedDir.normalize();
  303. }
  304. spearDir = spearDir * (1.0f - t) + bracedDir * t;
  305. if (spearDir.lengthSquared() > 1e-6f) {
  306. spearDir.normalize();
  307. }
  308. } else if (isAttacking) {
  309. if (attackPhase >= 0.30f && attackPhase < 0.50f) {
  310. float t = (attackPhase - 0.30f) / 0.20f;
  311. QVector3D attackDir = QVector3D(0.03f, -0.15f, 1.0f);
  312. if (attackDir.lengthSquared() > 1e-6f) {
  313. attackDir.normalize();
  314. }
  315. spearDir = spearDir * (1.0f - t) + attackDir * t;
  316. if (spearDir.lengthSquared() > 1e-6f) {
  317. spearDir.normalize();
  318. }
  319. }
  320. }
  321. QVector3D shaftBase = gripPos - spearDir * 0.28f;
  322. QVector3D shaftMid = gripPos + spearDir * (extras.spearLength * 0.5f);
  323. QVector3D shaftTip = gripPos + spearDir * extras.spearLength;
  324. shaftMid.setY(shaftMid.y() + 0.02f);
  325. out.mesh(getUnitCylinder(),
  326. cylinderBetween(ctx.model, shaftBase, shaftMid,
  327. extras.spearShaftRadius),
  328. extras.spearShaftColor, nullptr, 1.0f);
  329. out.mesh(getUnitCylinder(),
  330. cylinderBetween(ctx.model, shaftMid, shaftTip,
  331. extras.spearShaftRadius * 0.95f),
  332. extras.spearShaftColor * 0.98f, nullptr, 1.0f);
  333. QVector3D spearheadBase = shaftTip;
  334. QVector3D spearheadTip = shaftTip + spearDir * extras.spearheadLength;
  335. out.mesh(getUnitCone(),
  336. coneFromTo(ctx.model, spearheadBase, spearheadTip,
  337. extras.spearShaftRadius * 1.8f),
  338. extras.spearheadColor, nullptr, 1.0f);
  339. QVector3D gripEnd = gripPos + spearDir * 0.10f;
  340. out.mesh(getUnitCylinder(),
  341. cylinderBetween(ctx.model, gripPos, gripEnd,
  342. extras.spearShaftRadius * 1.5f),
  343. v.palette.leather * 0.92f, nullptr, 1.0f);
  344. }
  345. };
  346. void registerSpearmanRenderer(Render::GL::EntityRendererRegistry &registry) {
  347. static SpearmanRenderer renderer;
  348. registry.registerRenderer(
  349. "spearman", [](const DrawContext &ctx, ISubmitter &out) {
  350. static SpearmanRenderer staticRenderer;
  351. Shader *spearmanShader = nullptr;
  352. if (ctx.backend) {
  353. spearmanShader = ctx.backend->shader(QStringLiteral("spearman"));
  354. }
  355. Renderer *sceneRenderer = dynamic_cast<Renderer *>(&out);
  356. if (sceneRenderer && spearmanShader) {
  357. sceneRenderer->setCurrentShader(spearmanShader);
  358. }
  359. staticRenderer.render(ctx, out);
  360. if (sceneRenderer) {
  361. sceneRenderer->setCurrentShader(nullptr);
  362. }
  363. });
  364. }
  365. } // namespace Render::GL