spearman_renderer.cpp 17 KB

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