archer_renderer.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. #include "archer_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. struct ArcherExtras {
  35. QVector3D stringCol;
  36. QVector3D fletch;
  37. QVector3D metalHead;
  38. float bowRodR = 0.035f;
  39. float stringR = 0.008f;
  40. float bowDepth = 0.25f;
  41. float bowX = 0.0f;
  42. float bowTopY;
  43. float bowBotY;
  44. };
  45. class ArcherRenderer : public HumanoidRendererBase {
  46. public:
  47. QVector3D getProportionScaling() const override {
  48. return QVector3D(0.94f, 1.01f, 0.96f);
  49. }
  50. private:
  51. mutable std::unordered_map<uint32_t, ArcherExtras> m_extrasCache;
  52. public:
  53. void getVariant(const DrawContext &ctx, uint32_t seed,
  54. HumanoidVariant &v) const override {
  55. QVector3D teamTint = resolveTeamTint(ctx);
  56. v.palette = makeHumanoidPalette(teamTint, seed);
  57. }
  58. void customizePose(const DrawContext &ctx, const AnimationInputs &anim,
  59. uint32_t seed, HumanoidPose &pose) const override {
  60. using HP = HumanProportions;
  61. float armHeightJitter = (hash01(seed ^ 0xABCDu) - 0.5f) * 0.03f;
  62. float armAsymmetry = (hash01(seed ^ 0xDEF0u) - 0.5f) * 0.04f;
  63. float bowX = 0.0f;
  64. if (anim.isInHoldMode || anim.isExitingHold) {
  65. float t = anim.isInHoldMode ? 1.0f : (1.0f - anim.holdExitProgress);
  66. float kneelDepth = 0.45f * t;
  67. float pelvisY = HP::WAIST_Y - kneelDepth;
  68. pose.pelvisPos.setY(pelvisY);
  69. float stanceNarrow = 0.12f;
  70. float leftKneeY = HP::GROUND_Y + 0.08f * t;
  71. float leftKneeZ = -0.05f * t;
  72. pose.kneeL = QVector3D(-stanceNarrow, leftKneeY, leftKneeZ);
  73. pose.footL = QVector3D(-stanceNarrow - 0.03f, HP::GROUND_Y,
  74. leftKneeZ - HP::LOWER_LEG_LEN * 0.95f * t);
  75. float rightFootZ = 0.30f * t;
  76. pose.footR =
  77. QVector3D(stanceNarrow, HP::GROUND_Y + pose.footYOffset, rightFootZ);
  78. float rightKneeY = pelvisY - 0.10f;
  79. float rightKneeZ = rightFootZ - 0.05f;
  80. pose.kneeR = QVector3D(stanceNarrow, rightKneeY, rightKneeZ);
  81. float upperBodyDrop = kneelDepth;
  82. pose.shoulderL.setY(HP::SHOULDER_Y - upperBodyDrop);
  83. pose.shoulderR.setY(HP::SHOULDER_Y - upperBodyDrop);
  84. pose.neckBase.setY(HP::NECK_BASE_Y - upperBodyDrop);
  85. pose.headPos.setY((HP::HEAD_TOP_Y + HP::CHIN_Y) * 0.5f - upperBodyDrop);
  86. float forwardLean = 0.10f * t;
  87. pose.shoulderL.setZ(pose.shoulderL.z() + forwardLean);
  88. pose.shoulderR.setZ(pose.shoulderR.z() + forwardLean);
  89. pose.neckBase.setZ(pose.neckBase.z() + forwardLean * 0.8f);
  90. pose.headPos.setZ(pose.headPos.z() + forwardLean * 0.7f);
  91. QVector3D holdHandL(bowX - 0.15f, pose.shoulderL.y() + 0.30f, 0.55f);
  92. QVector3D holdHandR(bowX + 0.12f, pose.shoulderR.y() + 0.15f, 0.10f);
  93. QVector3D normalHandL(bowX - 0.05f + armAsymmetry,
  94. HP::SHOULDER_Y + 0.05f + armHeightJitter, 0.55f);
  95. QVector3D normalHandR(0.15f - armAsymmetry * 0.5f,
  96. HP::SHOULDER_Y + 0.15f + armHeightJitter * 0.8f,
  97. 0.20f);
  98. pose.handL = normalHandL * (1.0f - t) + holdHandL * t;
  99. pose.handR = normalHandR * (1.0f - t) + holdHandR * t;
  100. } else {
  101. pose.handL = QVector3D(bowX - 0.05f + armAsymmetry,
  102. HP::SHOULDER_Y + 0.05f + armHeightJitter, 0.55f);
  103. pose.handR =
  104. QVector3D(0.15f - armAsymmetry * 0.5f,
  105. HP::SHOULDER_Y + 0.15f + armHeightJitter * 0.8f, 0.20f);
  106. }
  107. if (anim.isAttacking && !anim.isInHoldMode) {
  108. float attackCycleTime = 1.2f;
  109. float attackPhase = fmod(anim.time * (1.0f / attackCycleTime), 1.0f);
  110. if (anim.isMelee) {
  111. QVector3D restPos(0.25f, HP::SHOULDER_Y, 0.10f);
  112. QVector3D raisedPos(0.30f, HP::HEAD_TOP_Y + 0.2f, -0.05f);
  113. QVector3D strikePos(0.35f, HP::WAIST_Y, 0.45f);
  114. if (attackPhase < 0.25f) {
  115. float t = attackPhase / 0.25f;
  116. t = t * t;
  117. pose.handR = restPos * (1.0f - t) + raisedPos * t;
  118. pose.handL = QVector3D(-0.15f, HP::SHOULDER_Y - 0.1f * t, 0.20f);
  119. } else if (attackPhase < 0.35f) {
  120. pose.handR = raisedPos;
  121. pose.handL = QVector3D(-0.15f, HP::SHOULDER_Y - 0.1f, 0.20f);
  122. } else if (attackPhase < 0.55f) {
  123. float t = (attackPhase - 0.35f) / 0.2f;
  124. t = t * t * t;
  125. pose.handR = raisedPos * (1.0f - t) + strikePos * t;
  126. pose.handL =
  127. QVector3D(-0.15f, HP::SHOULDER_Y - 0.1f * (1.0f - t * 0.5f),
  128. 0.20f + 0.15f * t);
  129. } else {
  130. float t = (attackPhase - 0.55f) / 0.45f;
  131. t = 1.0f - (1.0f - t) * (1.0f - t);
  132. pose.handR = strikePos * (1.0f - t) + restPos * t;
  133. pose.handL = QVector3D(-0.15f, HP::SHOULDER_Y - 0.05f * (1.0f - t),
  134. 0.35f * (1.0f - t) + 0.20f * t);
  135. }
  136. } else {
  137. QVector3D aimPos(0.18f, HP::SHOULDER_Y + 0.18f, 0.35f);
  138. QVector3D drawPos(0.22f, HP::SHOULDER_Y + 0.10f, -0.30f);
  139. QVector3D releasePos(0.18f, HP::SHOULDER_Y + 0.20f, 0.10f);
  140. if (attackPhase < 0.20f) {
  141. float t = attackPhase / 0.20f;
  142. t = t * t;
  143. pose.handR = aimPos * (1.0f - t) + drawPos * t;
  144. pose.handL = QVector3D(bowX - 0.05f, HP::SHOULDER_Y + 0.05f, 0.55f);
  145. float shoulderTwist = t * 0.08f;
  146. pose.shoulderR.setY(pose.shoulderR.y() + shoulderTwist);
  147. pose.shoulderL.setY(pose.shoulderL.y() - shoulderTwist * 0.5f);
  148. } else if (attackPhase < 0.50f) {
  149. pose.handR = drawPos;
  150. pose.handL = QVector3D(bowX - 0.05f, HP::SHOULDER_Y + 0.05f, 0.55f);
  151. float shoulderTwist = 0.08f;
  152. pose.shoulderR.setY(pose.shoulderR.y() + shoulderTwist);
  153. pose.shoulderL.setY(pose.shoulderL.y() - shoulderTwist * 0.5f);
  154. } else if (attackPhase < 0.58f) {
  155. float t = (attackPhase - 0.50f) / 0.08f;
  156. t = t * t * t;
  157. pose.handR = drawPos * (1.0f - t) + releasePos * t;
  158. pose.handL = QVector3D(bowX - 0.05f, HP::SHOULDER_Y + 0.05f, 0.55f);
  159. float shoulderTwist = 0.08f * (1.0f - t * 0.6f);
  160. pose.shoulderR.setY(pose.shoulderR.y() + shoulderTwist);
  161. pose.shoulderL.setY(pose.shoulderL.y() - shoulderTwist * 0.5f);
  162. pose.headPos.setZ(pose.headPos.z() - t * 0.04f);
  163. } else {
  164. float t = (attackPhase - 0.58f) / 0.42f;
  165. t = 1.0f - (1.0f - t) * (1.0f - t);
  166. pose.handR = releasePos * (1.0f - t) + aimPos * t;
  167. pose.handL = QVector3D(bowX - 0.05f, HP::SHOULDER_Y + 0.05f, 0.55f);
  168. float shoulderTwist = 0.08f * 0.4f * (1.0f - t);
  169. pose.shoulderR.setY(pose.shoulderR.y() + shoulderTwist);
  170. pose.shoulderL.setY(pose.shoulderL.y() - shoulderTwist * 0.5f);
  171. pose.headPos.setZ(pose.headPos.z() - 0.04f * (1.0f - t));
  172. }
  173. }
  174. }
  175. QVector3D rightAxis = pose.shoulderR - pose.shoulderL;
  176. rightAxis.setY(0.0f);
  177. if (rightAxis.lengthSquared() < 1e-8f)
  178. rightAxis = QVector3D(1, 0, 0);
  179. rightAxis.normalize();
  180. QVector3D outwardL = -rightAxis;
  181. QVector3D outwardR = rightAxis;
  182. pose.elbowL = elbowBendTorso(pose.shoulderL, pose.handL, outwardL, 0.45f,
  183. 0.15f, -0.08f, +1.0f);
  184. pose.elbowR = elbowBendTorso(pose.shoulderR, pose.handR, outwardR, 0.48f,
  185. 0.12f, 0.02f, +1.0f);
  186. }
  187. void addAttachments(const DrawContext &ctx, const HumanoidVariant &v,
  188. const HumanoidPose &pose, const AnimationInputs &anim,
  189. ISubmitter &out) const override {
  190. using HP = HumanProportions;
  191. QVector3D teamTint = resolveTeamTint(ctx);
  192. uint32_t seed = 0u;
  193. if (ctx.entity) {
  194. auto *unit = ctx.entity->getComponent<Engine::Core::UnitComponent>();
  195. if (unit)
  196. seed ^= uint32_t(unit->ownerId * 2654435761u);
  197. seed ^= uint32_t(reinterpret_cast<uintptr_t>(ctx.entity) & 0xFFFFFFFFu);
  198. }
  199. ArcherExtras extras;
  200. auto it = m_extrasCache.find(seed);
  201. if (it != m_extrasCache.end()) {
  202. extras = it->second;
  203. } else {
  204. extras.metalHead = Render::Geom::clampVec01(v.palette.metal * 1.15f);
  205. extras.stringCol = QVector3D(0.30f, 0.30f, 0.32f);
  206. auto tint = [&](float k) {
  207. return QVector3D(clamp01(teamTint.x() * k), clamp01(teamTint.y() * k),
  208. clamp01(teamTint.z() * k));
  209. };
  210. extras.fletch = tint(0.9f);
  211. extras.bowTopY = HP::SHOULDER_Y + 0.55f;
  212. extras.bowBotY = HP::WAIST_Y - 0.25f;
  213. m_extrasCache[seed] = extras;
  214. if (m_extrasCache.size() > MAX_EXTRAS_CACHE_SIZE) {
  215. m_extrasCache.clear();
  216. }
  217. }
  218. drawQuiver(ctx, v, pose, extras, seed, out);
  219. float attackPhase = 0.0f;
  220. if (anim.isAttacking && !anim.isMelee) {
  221. float attackCycleTime = 1.2f;
  222. attackPhase = fmod(anim.time * (1.0f / attackCycleTime), 1.0f);
  223. }
  224. drawBowAndArrow(ctx, pose, v, extras, anim.isAttacking && !anim.isMelee,
  225. attackPhase, out);
  226. }
  227. void drawHelmet(const DrawContext &ctx, const HumanoidVariant &v,
  228. const HumanoidPose &pose, ISubmitter &out) const override {
  229. using HP = HumanProportions;
  230. QVector3D helmetColor = v.palette.metal * QVector3D(1.08f, 0.98f, 0.78f);
  231. QVector3D helmetAccent = helmetColor * 1.12f;
  232. QVector3D helmetTop(0, pose.headPos.y() + pose.headR * 1.28f, 0);
  233. QVector3D helmetBot(0, pose.headPos.y() + pose.headR * 0.08f, 0);
  234. float helmetR = pose.headR * 1.10f;
  235. out.mesh(getUnitCylinder(),
  236. cylinderBetween(ctx.model, helmetBot, helmetTop, helmetR),
  237. helmetColor, nullptr, 1.0f);
  238. QVector3D apexPos(0, pose.headPos.y() + pose.headR * 1.48f, 0);
  239. out.mesh(getUnitCone(),
  240. coneFromTo(ctx.model, helmetTop, apexPos, helmetR * 0.97f),
  241. helmetAccent, nullptr, 1.0f);
  242. auto ring = [&](const QVector3D &center, float r, float h,
  243. const QVector3D &col) {
  244. QVector3D a = center + QVector3D(0, h * 0.5f, 0);
  245. QVector3D b = center - QVector3D(0, h * 0.5f, 0);
  246. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, a, b, r), col,
  247. nullptr, 1.0f);
  248. };
  249. QVector3D browPos(0, pose.headPos.y() + pose.headR * 0.35f, 0);
  250. ring(browPos, helmetR * 1.07f, 0.020f, helmetAccent);
  251. ring(QVector3D(0, pose.headPos.y() + pose.headR * 0.65f, 0),
  252. helmetR * 1.03f, 0.015f, helmetColor * 1.05f);
  253. ring(QVector3D(0, pose.headPos.y() + pose.headR * 0.95f, 0),
  254. helmetR * 1.01f, 0.012f, helmetColor * 1.03f);
  255. float cheekW = pose.headR * 0.48f;
  256. QVector3D cheekTop(0, pose.headPos.y() + pose.headR * 0.22f, 0);
  257. QVector3D cheekBot(0, pose.headPos.y() - pose.headR * 0.42f, 0);
  258. QVector3D cheekLTop = cheekTop + QVector3D(-cheekW, 0, pose.headR * 0.38f);
  259. QVector3D cheekLBot =
  260. cheekBot + QVector3D(-cheekW * 0.82f, 0, pose.headR * 0.28f);
  261. out.mesh(getUnitCylinder(),
  262. cylinderBetween(ctx.model, cheekLBot, cheekLTop, 0.028f),
  263. helmetColor * 0.96f, nullptr, 1.0f);
  264. QVector3D cheekRTop = cheekTop + QVector3D(cheekW, 0, pose.headR * 0.38f);
  265. QVector3D cheekRBot =
  266. cheekBot + QVector3D(cheekW * 0.82f, 0, pose.headR * 0.28f);
  267. out.mesh(getUnitCylinder(),
  268. cylinderBetween(ctx.model, cheekRBot, cheekRTop, 0.028f),
  269. helmetColor * 0.96f, nullptr, 1.0f);
  270. QVector3D neckGuardTop(0, pose.headPos.y() + pose.headR * 0.03f,
  271. -pose.headR * 0.82f);
  272. QVector3D neckGuardBot(0, pose.headPos.y() - pose.headR * 0.32f,
  273. -pose.headR * 0.88f);
  274. out.mesh(
  275. getUnitCylinder(),
  276. cylinderBetween(ctx.model, neckGuardBot, neckGuardTop, helmetR * 0.88f),
  277. helmetColor * 0.93f, nullptr, 1.0f);
  278. QVector3D crestBase = apexPos;
  279. QVector3D crestMid = crestBase + QVector3D(0, 0.09f, 0);
  280. QVector3D crestTop = crestMid + QVector3D(0, 0.12f, 0);
  281. out.mesh(getUnitCylinder(),
  282. cylinderBetween(ctx.model, crestBase, crestMid, 0.018f),
  283. helmetAccent, nullptr, 1.0f);
  284. out.mesh(getUnitCone(), coneFromTo(ctx.model, crestMid, crestTop, 0.042f),
  285. QVector3D(0.88f, 0.18f, 0.18f), nullptr, 1.0f);
  286. out.mesh(getUnitSphere(), sphereAt(ctx.model, crestTop, 0.020f),
  287. helmetAccent, nullptr, 1.0f);
  288. }
  289. void drawArmorOverlay(const DrawContext &ctx, const HumanoidVariant &v,
  290. const HumanoidPose &pose, float yTopCover, float torsoR,
  291. float shoulderHalfSpan, float upperArmR,
  292. const QVector3D &rightAxis,
  293. ISubmitter &out) const override {
  294. using HP = HumanProportions;
  295. auto ring = [&](const QVector3D &center, float r, float h,
  296. const QVector3D &col) {
  297. QVector3D a = center + QVector3D(0, h * 0.5f, 0);
  298. QVector3D b = center - QVector3D(0, h * 0.5f, 0);
  299. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, a, b, r), col,
  300. nullptr, 1.0f);
  301. };
  302. QVector3D mailColor = v.palette.metal * QVector3D(0.85f, 0.87f, 0.92f);
  303. QVector3D leatherTrim = v.palette.leatherDark * 0.90f;
  304. float waistY = pose.pelvisPos.y();
  305. QVector3D mailTop(0, yTopCover + 0.01f, 0);
  306. QVector3D mailMid(0, (yTopCover + waistY) * 0.5f, 0);
  307. QVector3D mailBot(0, waistY + 0.08f, 0);
  308. float rTop = torsoR * 1.10f;
  309. float rMid = torsoR * 1.08f;
  310. out.mesh(getUnitCylinder(),
  311. cylinderBetween(ctx.model, mailTop, mailMid, rTop), mailColor,
  312. nullptr, 1.0f);
  313. out.mesh(getUnitCylinder(),
  314. cylinderBetween(ctx.model, mailMid, mailBot, rMid),
  315. mailColor * 0.95f, nullptr, 1.0f);
  316. for (int i = 0; i < 3; ++i) {
  317. float y = mailTop.y() - (i * 0.12f);
  318. ring(QVector3D(0, y, 0), rTop * (1.01f + i * 0.005f), 0.012f,
  319. leatherTrim);
  320. }
  321. auto drawPauldron = [&](const QVector3D &shoulder,
  322. const QVector3D &outward) {
  323. for (int i = 0; i < 3; ++i) {
  324. float segY = shoulder.y() + 0.02f - i * 0.035f;
  325. float segR = upperArmR * (2.2f - i * 0.15f);
  326. QVector3D segTop(shoulder.x(), segY + 0.025f, shoulder.z());
  327. QVector3D segBot(shoulder.x(), segY - 0.010f, shoulder.z());
  328. segTop += outward * 0.02f;
  329. segBot += outward * 0.02f;
  330. out.mesh(getUnitSphere(), sphereAt(ctx.model, segTop, segR),
  331. mailColor * (1.0f - i * 0.05f), nullptr, 1.0f);
  332. }
  333. };
  334. drawPauldron(pose.shoulderL, -rightAxis);
  335. drawPauldron(pose.shoulderR, rightAxis);
  336. auto drawManica = [&](const QVector3D &shoulder, const QVector3D &elbow) {
  337. QVector3D dir = (elbow - shoulder);
  338. float len = dir.length();
  339. if (len < 1e-5f)
  340. return;
  341. dir /= len;
  342. for (int i = 0; i < 4; ++i) {
  343. float t0 = 0.08f + i * 0.18f;
  344. float t1 = t0 + 0.16f;
  345. QVector3D a = shoulder + dir * (t0 * len);
  346. QVector3D b = shoulder + dir * (t1 * len);
  347. float r = upperArmR * (1.25f - i * 0.03f);
  348. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, a, b, r),
  349. mailColor * (0.95f - i * 0.03f), nullptr, 1.0f);
  350. }
  351. };
  352. drawManica(pose.shoulderL, pose.elbowL);
  353. drawManica(pose.shoulderR, pose.elbowR);
  354. QVector3D beltTop(0, waistY + 0.06f, 0);
  355. QVector3D beltBot(0, waistY - 0.02f, 0);
  356. float beltR = torsoR * 1.12f;
  357. out.mesh(getUnitCylinder(),
  358. cylinderBetween(ctx.model, beltTop, beltBot, beltR), leatherTrim,
  359. nullptr, 1.0f);
  360. QVector3D brassColor = v.palette.metal * QVector3D(1.2f, 1.0f, 0.65f);
  361. ring(QVector3D(0, waistY + 0.02f, 0), beltR * 1.02f, 0.010f, brassColor);
  362. auto drawPteruge = [&](float angle, float yStart, float length) {
  363. float rad = torsoR * 1.15f;
  364. float x = rad * std::sin(angle);
  365. float z = rad * std::cos(angle);
  366. QVector3D top(x, yStart, z);
  367. QVector3D bot(x * 0.95f, yStart - length, z * 0.95f);
  368. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, top, bot, 0.018f),
  369. leatherTrim * 0.85f, nullptr, 1.0f);
  370. };
  371. float shoulderPterugeY = yTopCover - 0.02f;
  372. for (int i = 0; i < 8; ++i) {
  373. float angle = (i / 8.0f) * 2.0f * 3.14159265f;
  374. drawPteruge(angle, shoulderPterugeY, 0.14f);
  375. }
  376. float waistPterugeY = waistY - 0.04f;
  377. for (int i = 0; i < 10; ++i) {
  378. float angle = (i / 10.0f) * 2.0f * 3.14159265f;
  379. drawPteruge(angle, waistPterugeY, 0.18f);
  380. }
  381. QVector3D collarTop(0, yTopCover + 0.018f, 0);
  382. QVector3D collarBot(0, yTopCover - 0.008f, 0);
  383. out.mesh(getUnitCylinder(),
  384. cylinderBetween(ctx.model, collarTop, collarBot,
  385. HP::NECK_RADIUS * 1.8f),
  386. mailColor * 1.05f, nullptr, 1.0f);
  387. }
  388. void drawShoulderDecorations(const DrawContext &ctx, const HumanoidVariant &v,
  389. const HumanoidPose &pose, float yTopCover,
  390. float yNeck, const QVector3D &rightAxis,
  391. ISubmitter &out) const override {
  392. using HP = HumanProportions;
  393. QVector3D brassColor = v.palette.metal * QVector3D(1.2f, 1.0f, 0.65f);
  394. auto drawPhalera = [&](const QVector3D &pos) {
  395. QMatrix4x4 m = ctx.model;
  396. m.translate(pos);
  397. m.scale(0.025f);
  398. out.mesh(getUnitSphere(), m, brassColor, nullptr, 1.0f);
  399. };
  400. drawPhalera(pose.shoulderL + QVector3D(0, 0.05f, 0.02f));
  401. drawPhalera(pose.shoulderR + QVector3D(0, 0.05f, 0.02f));
  402. QVector3D claspPos(0, yNeck + 0.02f, 0.08f);
  403. QMatrix4x4 claspM = ctx.model;
  404. claspM.translate(claspPos);
  405. claspM.scale(0.020f);
  406. out.mesh(getUnitSphere(), claspM, brassColor * 1.1f, nullptr, 1.0f);
  407. QVector3D capeTop = claspPos + QVector3D(0, -0.02f, -0.05f);
  408. QVector3D capeBot = claspPos + QVector3D(0, -0.25f, -0.15f);
  409. QVector3D redFabric = v.palette.cloth * QVector3D(1.2f, 0.3f, 0.3f);
  410. out.mesh(getUnitCylinder(),
  411. cylinderBetween(ctx.model, capeTop, capeBot, 0.025f),
  412. redFabric * 0.85f, nullptr, 1.0f);
  413. }
  414. private:
  415. static void drawQuiver(const DrawContext &ctx, const HumanoidVariant &v,
  416. const HumanoidPose &pose, const ArcherExtras &extras,
  417. uint32_t seed, ISubmitter &out) {
  418. using HP = HumanProportions;
  419. QVector3D spineMid = (pose.shoulderL + pose.shoulderR) * 0.5f;
  420. QVector3D quiverOffset(-0.08f, 0.10f, -0.25f);
  421. QVector3D qTop = spineMid + quiverOffset;
  422. QVector3D qBase = qTop + QVector3D(-0.02f, -0.30f, 0.03f);
  423. float quiverR = HP::HEAD_RADIUS * 0.45f;
  424. out.mesh(getUnitCylinder(),
  425. cylinderBetween(ctx.model, qBase, qTop, quiverR),
  426. v.palette.leather, nullptr, 1.0f);
  427. float j = (hash01(seed) - 0.5f) * 0.04f;
  428. float k = (hash01(seed ^ 0x9E3779B9u) - 0.5f) * 0.04f;
  429. QVector3D a1 = qTop + QVector3D(0.00f + j, 0.08f, 0.00f + k);
  430. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, qTop, a1, 0.010f),
  431. v.palette.wood, nullptr, 1.0f);
  432. out.mesh(getUnitCone(),
  433. coneFromTo(ctx.model, a1, a1 + QVector3D(0, 0.05f, 0), 0.025f),
  434. extras.fletch, nullptr, 1.0f);
  435. QVector3D a2 = qTop + QVector3D(0.02f - j, 0.07f, 0.02f - k);
  436. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, qTop, a2, 0.010f),
  437. v.palette.wood, nullptr, 1.0f);
  438. out.mesh(getUnitCone(),
  439. coneFromTo(ctx.model, a2, a2 + QVector3D(0, 0.05f, 0), 0.025f),
  440. extras.fletch, nullptr, 1.0f);
  441. }
  442. static void drawBowAndArrow(const DrawContext &ctx, const HumanoidPose &pose,
  443. const HumanoidVariant &v,
  444. const ArcherExtras &extras, bool isAttacking,
  445. float attackPhase, ISubmitter &out) {
  446. const QVector3D up(0.0f, 1.0f, 0.0f);
  447. const QVector3D forward(0.0f, 0.0f, 1.0f);
  448. QVector3D grip = pose.handL;
  449. float bowPlaneZ = 0.45f;
  450. QVector3D topEnd(extras.bowX, extras.bowTopY, bowPlaneZ);
  451. QVector3D botEnd(extras.bowX, extras.bowBotY, bowPlaneZ);
  452. QVector3D nock(
  453. extras.bowX,
  454. clampf(pose.handR.y(), extras.bowBotY + 0.05f, extras.bowTopY - 0.05f),
  455. clampf(pose.handR.z(), bowPlaneZ - 0.30f, bowPlaneZ + 0.30f));
  456. const int segs = 22;
  457. auto qBezier = [](const QVector3D &a, const QVector3D &c,
  458. const QVector3D &b, float t) {
  459. float u = 1.0f - t;
  460. return u * u * a + 2.0f * u * t * c + t * t * b;
  461. };
  462. float bowMidY = (topEnd.y() + botEnd.y()) * 0.5f;
  463. float ctrlY = bowMidY + 0.45f;
  464. QVector3D ctrl(extras.bowX, ctrlY, bowPlaneZ + extras.bowDepth * 0.6f);
  465. QVector3D prev = botEnd;
  466. for (int i = 1; i <= segs; ++i) {
  467. float t = float(i) / float(segs);
  468. QVector3D cur = qBezier(botEnd, ctrl, topEnd, t);
  469. out.mesh(getUnitCylinder(),
  470. cylinderBetween(ctx.model, prev, cur, extras.bowRodR),
  471. v.palette.wood, nullptr, 1.0f);
  472. prev = cur;
  473. }
  474. out.mesh(getUnitCylinder(),
  475. cylinderBetween(ctx.model, grip - up * 0.05f, grip + up * 0.05f,
  476. extras.bowRodR * 1.45f),
  477. v.palette.wood, nullptr, 1.0f);
  478. out.mesh(getUnitCylinder(),
  479. cylinderBetween(ctx.model, topEnd, nock, extras.stringR),
  480. extras.stringCol, nullptr, 1.0f);
  481. out.mesh(getUnitCylinder(),
  482. cylinderBetween(ctx.model, nock, botEnd, extras.stringR),
  483. extras.stringCol, nullptr, 1.0f);
  484. out.mesh(getUnitCylinder(),
  485. cylinderBetween(ctx.model, pose.handR, nock, 0.0045f),
  486. extras.stringCol * 0.9f, nullptr, 1.0f);
  487. bool showArrow = !isAttacking || (isAttacking && attackPhase >= 0.0f &&
  488. attackPhase < 0.52f);
  489. if (showArrow) {
  490. QVector3D tail = nock - forward * 0.06f;
  491. QVector3D tip = tail + forward * 0.90f;
  492. out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, tail, tip, 0.018f),
  493. v.palette.wood, nullptr, 1.0f);
  494. QVector3D headBase = tip - forward * 0.10f;
  495. out.mesh(getUnitCone(), coneFromTo(ctx.model, headBase, tip, 0.05f),
  496. extras.metalHead, nullptr, 1.0f);
  497. QVector3D f1b = tail - forward * 0.02f, f1a = f1b - forward * 0.06f;
  498. QVector3D f2b = tail + forward * 0.02f, f2a = f2b + forward * 0.06f;
  499. out.mesh(getUnitCone(), coneFromTo(ctx.model, f1b, f1a, 0.04f),
  500. extras.fletch, nullptr, 1.0f);
  501. out.mesh(getUnitCone(), coneFromTo(ctx.model, f2a, f2b, 0.04f),
  502. extras.fletch, nullptr, 1.0f);
  503. }
  504. }
  505. };
  506. void registerArcherRenderer(Render::GL::EntityRendererRegistry &registry) {
  507. static ArcherRenderer renderer;
  508. registry.registerRenderer(
  509. "archer", [](const DrawContext &ctx, ISubmitter &out) {
  510. static ArcherRenderer staticRenderer;
  511. Shader *archerShader = nullptr;
  512. if (ctx.backend) {
  513. archerShader = ctx.backend->shader(QStringLiteral("archer"));
  514. }
  515. Renderer *sceneRenderer = dynamic_cast<Renderer *>(&out);
  516. if (sceneRenderer && archerShader) {
  517. sceneRenderer->setCurrentShader(archerShader);
  518. }
  519. staticRenderer.render(ctx, out);
  520. if (sceneRenderer) {
  521. sceneRenderer->setCurrentShader(nullptr);
  522. }
  523. });
  524. }
  525. } // namespace Render::GL