arrow_vfx_renderer.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. #include "../../game/core/component.h"
  2. #include "../../game/core/entity.h"
  3. #include "../../game/units/troop_config.h"
  4. #include "../../game/visuals/team_colors.h"
  5. #include "../geom/math_utils.h"
  6. #include "../geom/selection_ring.h"
  7. #include "../geom/transforms.h"
  8. #include "../gl/mesh.h"
  9. #include "../gl/primitives.h"
  10. #include "../gl/texture.h"
  11. #include "archer_renderer.h"
  12. #include "registry.h"
  13. #include <QMatrix4x4>
  14. #include <QVector3D>
  15. #include <cstdint>
  16. namespace Render::GL {
  17. using Render::Geom::clamp01;
  18. using Render::Geom::clampf;
  19. using Render::Geom::clampVec01;
  20. using Render::Geom::coneFromTo;
  21. using Render::Geom::cylinderBetween;
  22. using Render::Geom::sphereAt;
  23. struct HumanProportions {
  24. static constexpr float TOTAL_HEIGHT = 2.00f;
  25. static constexpr float HEAD_HEIGHT = 0.25f;
  26. static constexpr float GROUND_Y = -1.00f;
  27. static constexpr float HEAD_TOP_Y = GROUND_Y + TOTAL_HEIGHT;
  28. static constexpr float CHIN_Y = HEAD_TOP_Y - HEAD_HEIGHT;
  29. static constexpr float NECK_BASE_Y = CHIN_Y - 0.10f;
  30. static constexpr float SHOULDER_Y = NECK_BASE_Y - 0.15f;
  31. static constexpr float CHEST_Y = SHOULDER_Y - 0.35f;
  32. static constexpr float WAIST_Y = CHEST_Y - 0.30f;
  33. static constexpr float HIP_Y = WAIST_Y - 0.15f;
  34. static constexpr float KNEE_Y = HIP_Y - 0.35f;
  35. static constexpr float SHOULDER_WIDTH = HEAD_HEIGHT * 1.6f;
  36. static constexpr float HEAD_RADIUS = HEAD_HEIGHT * 0.40f;
  37. static constexpr float NECK_RADIUS = HEAD_RADIUS * 0.35f;
  38. static constexpr float TORSO_TOP_R = HEAD_RADIUS * 1.0f;
  39. static constexpr float TORSO_BOT_R = HEAD_RADIUS * 0.9f;
  40. static constexpr float UPPER_ARM_R = HEAD_RADIUS * 0.30f;
  41. static constexpr float FORE_ARM_R = HEAD_RADIUS * 0.25f;
  42. static constexpr float HAND_RADIUS = HEAD_RADIUS * 0.22f;
  43. static constexpr float UPPER_LEG_R = HEAD_RADIUS * 0.38f;
  44. static constexpr float LOWER_LEG_R = HEAD_RADIUS * 0.32f;
  45. static constexpr float UPPER_ARM_LEN = 0.28f;
  46. static constexpr float FORE_ARM_LEN = 0.30f;
  47. static constexpr float UPPER_LEG_LEN = 0.35f;
  48. static constexpr float LOWER_LEG_LEN = 0.35f;
  49. };
  50. struct ArcherColors {
  51. QVector3D tunic, skin, leather, leatherDark, wood, metal, metalHead,
  52. stringCol, fletch;
  53. };
  54. struct ArcherPose {
  55. using P = HumanProportions;
  56. QVector3D headPos{0.0f, (P::HEAD_TOP_Y + P::CHIN_Y) * 0.5f, 0.0f};
  57. float headR = P::HEAD_RADIUS;
  58. QVector3D neckBase{0.0f, P::NECK_BASE_Y, 0.0f};
  59. QVector3D shoulderL{-P::SHOULDER_WIDTH * 0.5f, P::SHOULDER_Y, 0.1f};
  60. QVector3D shoulderR{P::SHOULDER_WIDTH * 0.5f, P::SHOULDER_Y, 0.1f};
  61. QVector3D elbowL, elbowR;
  62. QVector3D handL, handR;
  63. float hipSpacing = P::SHOULDER_WIDTH * 0.55f;
  64. QVector3D hipL{-hipSpacing, P::HIP_Y, 0.03f};
  65. QVector3D hipR{hipSpacing, P::HIP_Y, -0.03f};
  66. QVector3D footL{-hipSpacing * 1.6f, P::GROUND_Y, 0.10f};
  67. QVector3D footR{hipSpacing * 1.6f, P::GROUND_Y, -0.10f};
  68. float bowX = 0.0f;
  69. float bowTopY = P::SHOULDER_Y + 0.55f;
  70. float bowBotY = P::HIP_Y - 0.25f;
  71. float bowRodR = 0.035f;
  72. float stringR = 0.008f;
  73. float bowDepth = 0.25f;
  74. };
  75. static inline ArcherPose makePose(uint32_t seed) {
  76. (void)seed;
  77. ArcherPose P;
  78. using HP = HumanProportions;
  79. P.handL = QVector3D(P.bowX - 0.05f, HP::SHOULDER_Y + 0.05f, 0.55f);
  80. P.handR = QVector3D(0.15f, HP::SHOULDER_Y + 0.15f, 0.20f);
  81. QVector3D shoulderToHandL = P.handL - P.shoulderL;
  82. float distL = shoulderToHandL.length();
  83. QVector3D dirL = shoulderToHandL.normalized();
  84. QVector3D perpL(-dirL.z(), 0.0f, dirL.x());
  85. float elbowOffsetL = 0.15f;
  86. P.elbowL = P.shoulderL + dirL * (distL * 0.45f) + perpL * elbowOffsetL +
  87. QVector3D(0, -0.08f, 0);
  88. QVector3D shoulderToHandR = P.handR - P.shoulderR;
  89. float distR = shoulderToHandR.length();
  90. QVector3D dirR = shoulderToHandR.normalized();
  91. QVector3D perpR(-dirR.z(), 0.0f, dirR.x());
  92. float elbowOffsetR = 0.12f;
  93. P.elbowR = P.shoulderR + dirR * (distR * 0.48f) + perpR * elbowOffsetR +
  94. QVector3D(0, 0.02f, 0);
  95. return P;
  96. }
  97. static inline ArcherColors makeColors(const QVector3D &teamTint) {
  98. ArcherColors C;
  99. auto tint = [&](float k) {
  100. return QVector3D(clamp01(teamTint.x() * k), clamp01(teamTint.y() * k),
  101. clamp01(teamTint.z() * k));
  102. };
  103. C.tunic = teamTint;
  104. C.skin = QVector3D(0.96f, 0.80f, 0.69f);
  105. C.leather = QVector3D(0.35f, 0.22f, 0.12f);
  106. C.leatherDark = C.leather * 0.9f;
  107. C.wood = QVector3D(0.16f, 0.10f, 0.05f);
  108. C.metal = QVector3D(0.65f, 0.66f, 0.70f);
  109. C.metalHead = clampVec01(C.metal * 1.1f);
  110. C.stringCol = QVector3D(0.30f, 0.30f, 0.32f);
  111. C.fletch = tint(0.9f);
  112. return C;
  113. }
  114. static inline void drawTorso(const DrawContext &p, ISubmitter &out,
  115. const ArcherColors &C, const ArcherPose &P) {
  116. using HP = HumanProportions;
  117. QVector3D torsoTop{0.0f, HP::NECK_BASE_Y - 0.05f, 0.0f};
  118. QVector3D torsoBot{0.0f, HP::WAIST_Y, 0.0f};
  119. float torsoRadius = HP::TORSO_TOP_R;
  120. out.mesh(getUnitCylinder(),
  121. cylinderBetween(p.model, torsoTop, torsoBot, torsoRadius), C.tunic,
  122. nullptr, 1.0f);
  123. QVector3D waist{0.0f, HP::WAIST_Y, 0.0f};
  124. QVector3D hipCenter = (P.hipL + P.hipR) * 0.5f;
  125. out.mesh(getUnitCone(),
  126. coneFromTo(p.model, waist, hipCenter, HP::TORSO_BOT_R),
  127. C.tunic * 0.9f, nullptr, 1.0f);
  128. }
  129. static inline void drawHeadAndNeck(const DrawContext &p, ISubmitter &out,
  130. const ArcherPose &P, const ArcherColors &C) {
  131. using HP = HumanProportions;
  132. QVector3D chinPos{0.0f, HP::CHIN_Y, 0.0f};
  133. out.mesh(getUnitCylinder(),
  134. cylinderBetween(p.model, P.neckBase, chinPos, HP::NECK_RADIUS),
  135. C.skin * 0.9f, nullptr, 1.0f);
  136. out.mesh(getUnitSphere(), sphereAt(p.model, P.headPos, P.headR), C.skin,
  137. nullptr, 1.0f);
  138. QVector3D iris(0.06f, 0.06f, 0.07f);
  139. float eyeZ = P.headR * 0.7f;
  140. float eyeY = P.headPos.y() + P.headR * 0.1f;
  141. float eyeSpacing = P.headR * 0.35f;
  142. out.mesh(getUnitSphere(),
  143. p.model *
  144. sphereAt(QVector3D(-eyeSpacing, eyeY, eyeZ), P.headR * 0.15f),
  145. iris, nullptr, 1.0f);
  146. out.mesh(getUnitSphere(),
  147. p.model *
  148. sphereAt(QVector3D(eyeSpacing, eyeY, eyeZ), P.headR * 0.15f),
  149. iris, nullptr, 1.0f);
  150. QVector3D domeC = P.headPos + QVector3D(0.0f, P.headR * 0.25f, 0.0f);
  151. float domeR = P.headR * 1.05f;
  152. out.mesh(getUnitSphere(), sphereAt(p.model, domeC, domeR), C.metal, nullptr,
  153. 1.0f);
  154. QVector3D visorBase(0.0f, P.headPos.y() + P.headR * 0.10f, P.headR * 0.80f);
  155. QVector3D visorTip = visorBase + QVector3D(0.0f, -0.015f, 0.06f);
  156. out.mesh(getUnitCone(),
  157. coneFromTo(p.model, visorBase, visorTip, P.headR * 0.38f),
  158. C.metal * 0.92f, nullptr, 1.0f);
  159. QVector3D cheekL0(-P.headR * 0.85f, P.headPos.y() + P.headR * 0.05f, 0.02f);
  160. QVector3D cheekL1(-P.headR * 0.85f, P.headPos.y() - P.headR * 0.20f, 0.04f);
  161. QVector3D cheekR0(P.headR * 0.85f, P.headPos.y() + P.headR * 0.05f, -0.02f);
  162. QVector3D cheekR1(P.headR * 0.85f, P.headPos.y() - P.headR * 0.20f, -0.04f);
  163. out.mesh(getUnitCone(),
  164. coneFromTo(p.model, cheekL0, cheekL1, P.headR * 0.24f),
  165. C.metal * 0.95f, nullptr, 1.0f);
  166. out.mesh(getUnitCone(),
  167. coneFromTo(p.model, cheekR0, cheekR1, P.headR * 0.24f),
  168. C.metal * 0.95f, nullptr, 1.0f);
  169. }
  170. static inline void drawArms(const DrawContext &p, ISubmitter &out,
  171. const ArcherPose &P, const ArcherColors &C) {
  172. using HP = HumanProportions;
  173. const float upperArmR = HP::UPPER_ARM_R;
  174. const float foreArmR = HP::FORE_ARM_R;
  175. const float jointR = upperArmR * 1.2f;
  176. out.mesh(getUnitCylinder(),
  177. cylinderBetween(p.model, P.shoulderL, P.elbowL, upperArmR), C.tunic,
  178. nullptr, 1.0f);
  179. out.mesh(getUnitSphere(), sphereAt(p.model, P.elbowL, jointR),
  180. C.tunic * 0.95f, nullptr, 1.0f);
  181. out.mesh(getUnitCylinder(),
  182. cylinderBetween(p.model, P.elbowL, P.handL, foreArmR),
  183. C.skin * 0.95f, nullptr, 1.0f);
  184. out.mesh(getUnitCylinder(),
  185. cylinderBetween(p.model, P.shoulderR, P.elbowR, upperArmR), C.tunic,
  186. nullptr, 1.0f);
  187. out.mesh(getUnitSphere(), sphereAt(p.model, P.elbowR, jointR),
  188. C.tunic * 0.95f, nullptr, 1.0f);
  189. out.mesh(getUnitCylinder(),
  190. cylinderBetween(p.model, P.elbowR, P.handR, foreArmR),
  191. C.skin * 0.95f, nullptr, 1.0f);
  192. }
  193. static inline void drawLegs(const DrawContext &p, ISubmitter &out,
  194. const ArcherPose &P, const ArcherColors &C) {
  195. using HP = HumanProportions;
  196. const float thighR = HP::UPPER_LEG_R;
  197. const float shinR = HP::LOWER_LEG_R;
  198. const float kneeJointR = thighR * 1.15f;
  199. auto makeKnee = [&](const QVector3D &hip, const QVector3D &foot,
  200. float outwardSign) {
  201. const float t = 0.38f;
  202. QVector3D knee = hip * (1.0f - t) + foot * t;
  203. knee.setY(HP::KNEE_Y + 0.03f);
  204. knee.setZ(knee.z() + 0.05f);
  205. knee.setX(knee.x() + outwardSign * 0.06f);
  206. return knee;
  207. };
  208. QVector3D kneeL = makeKnee(P.hipL, P.footL, -1.0f);
  209. QVector3D kneeR = makeKnee(P.hipR, P.footR, 1.0f);
  210. out.mesh(getUnitCone(), coneFromTo(p.model, P.hipL, kneeL, thighR), C.leather,
  211. nullptr, 1.0f);
  212. out.mesh(getUnitCone(), coneFromTo(p.model, P.hipR, kneeR, thighR), C.leather,
  213. nullptr, 1.0f);
  214. out.mesh(getUnitSphere(), sphereAt(p.model, kneeL, kneeJointR),
  215. C.leather * 0.95f, nullptr, 1.0f);
  216. out.mesh(getUnitSphere(), sphereAt(p.model, kneeR, kneeJointR),
  217. C.leather * 0.95f, nullptr, 1.0f);
  218. out.mesh(getUnitCone(), coneFromTo(p.model, kneeL, P.footL, shinR),
  219. C.leatherDark, nullptr, 1.0f);
  220. out.mesh(getUnitCone(), coneFromTo(p.model, kneeR, P.footR, shinR),
  221. C.leatherDark, nullptr, 1.0f);
  222. QVector3D down(0.0f, -0.02f, 0.0f);
  223. out.mesh(getUnitCylinder(),
  224. cylinderBetween(p.model, P.footL, P.footL + down, shinR * 1.1f),
  225. C.leatherDark, nullptr, 1.0f);
  226. out.mesh(getUnitCylinder(),
  227. cylinderBetween(p.model, P.footR, P.footR + down, shinR * 1.1f),
  228. C.leatherDark, nullptr, 1.0f);
  229. }
  230. static inline void drawQuiver(const DrawContext &p, ISubmitter &out,
  231. const ArcherColors &C, const ArcherPose &P,
  232. uint32_t seed) {
  233. using HP = HumanProportions;
  234. auto hash01 = [](uint32_t x) {
  235. x ^= x << 13;
  236. x ^= x >> 17;
  237. x ^= x << 5;
  238. return (x & 0x00FFFFFF) / float(0x01000000);
  239. };
  240. QVector3D qTop(-0.08f, HP::SHOULDER_Y + 0.10f, -0.25f);
  241. QVector3D qBase(-0.10f, HP::CHEST_Y, -0.22f);
  242. float quiverR = HP::HEAD_RADIUS * 0.45f;
  243. out.mesh(getUnitCylinder(), cylinderBetween(p.model, qBase, qTop, quiverR),
  244. C.leather, nullptr, 1.0f);
  245. float j = (hash01(seed) - 0.5f) * 0.04f;
  246. float k = (hash01(seed ^ 0x9E3779B9u) - 0.5f) * 0.04f;
  247. QVector3D a1 = qTop + QVector3D(0.00f + j, 0.08f, 0.00f + k);
  248. out.mesh(getUnitCylinder(), cylinderBetween(p.model, qTop, a1, 0.010f),
  249. C.wood, nullptr, 1.0f);
  250. out.mesh(getUnitCone(),
  251. coneFromTo(p.model, a1, a1 + QVector3D(0, 0.05f, 0), 0.025f),
  252. C.fletch, nullptr, 1.0f);
  253. QVector3D a2 = qTop + QVector3D(0.02f - j, 0.07f, 0.02f - k);
  254. out.mesh(getUnitCylinder(), cylinderBetween(p.model, qTop, a2, 0.010f),
  255. C.wood, nullptr, 1.0f);
  256. out.mesh(getUnitCone(),
  257. coneFromTo(p.model, a2, a2 + QVector3D(0, 0.05f, 0), 0.025f),
  258. C.fletch, nullptr, 1.0f);
  259. }
  260. static inline void drawBowAndArrow(const DrawContext &p, ISubmitter &out,
  261. const ArcherPose &P, const ArcherColors &C) {
  262. const QVector3D up(0.0f, 1.0f, 0.0f);
  263. const QVector3D forward(0.0f, 0.0f, 1.0f);
  264. QVector3D grip = P.handL;
  265. QVector3D topEnd(P.bowX, P.bowTopY, grip.z());
  266. QVector3D botEnd(P.bowX, P.bowBotY, grip.z());
  267. QVector3D nock(P.bowX,
  268. clampf(P.handR.y(), P.bowBotY + 0.05f, P.bowTopY - 0.05f),
  269. clampf(P.handR.z(), grip.z() - 0.30f, grip.z() + 0.30f));
  270. const int segs = 22;
  271. auto qBezier = [](const QVector3D &a, const QVector3D &c, const QVector3D &b,
  272. float t) {
  273. float u = 1.0f - t;
  274. return u * u * a + 2.0f * u * t * c + t * t * b;
  275. };
  276. QVector3D ctrl = nock + forward * P.bowDepth;
  277. QVector3D prev = botEnd;
  278. for (int i = 1; i <= segs; ++i) {
  279. float t = float(i) / float(segs);
  280. QVector3D cur = qBezier(botEnd, ctrl, topEnd, t);
  281. out.mesh(getUnitCylinder(), cylinderBetween(p.model, prev, cur, P.bowRodR),
  282. C.wood, nullptr, 1.0f);
  283. prev = cur;
  284. }
  285. out.mesh(getUnitCylinder(),
  286. cylinderBetween(p.model, grip - up * 0.05f, grip + up * 0.05f,
  287. P.bowRodR * 1.45f),
  288. C.wood, nullptr, 1.0f);
  289. out.mesh(getUnitCylinder(), cylinderBetween(p.model, topEnd, nock, P.stringR),
  290. C.stringCol, nullptr, 1.0f);
  291. out.mesh(getUnitCylinder(), cylinderBetween(p.model, nock, botEnd, P.stringR),
  292. C.stringCol, nullptr, 1.0f);
  293. out.mesh(getUnitCylinder(), cylinderBetween(p.model, P.handR, nock, 0.0045f),
  294. C.stringCol * 0.9f, nullptr, 1.0f);
  295. QVector3D tail = nock - forward * 0.06f;
  296. QVector3D tip = tail + forward * 0.90f;
  297. out.mesh(getUnitCylinder(), cylinderBetween(p.model, tail, tip, 0.035f),
  298. C.wood, nullptr, 1.0f);
  299. QVector3D headBase = tip - forward * 0.10f;
  300. out.mesh(getUnitCone(), coneFromTo(p.model, headBase, tip, 0.05f),
  301. C.metalHead, nullptr, 1.0f);
  302. QVector3D f1b = tail - forward * 0.02f, f1a = f1b - forward * 0.06f;
  303. QVector3D f2b = tail + forward * 0.02f, f2a = f2b + forward * 0.06f;
  304. out.mesh(getUnitCone(), coneFromTo(p.model, f1b, f1a, 0.04f), C.fletch,
  305. nullptr, 1.0f);
  306. out.mesh(getUnitCone(), coneFromTo(p.model, f2a, f2b, 0.04f), C.fletch,
  307. nullptr, 1.0f);
  308. }
  309. static inline void drawSelectionFX(const DrawContext &p, ISubmitter &out) {
  310. if (p.selected || p.hovered) {
  311. float ringSize = 0.5f;
  312. if (p.entity) {
  313. auto *unit = p.entity->getComponent<Engine::Core::UnitComponent>();
  314. if (unit && !unit->unitType.empty()) {
  315. ringSize = Game::Units::TroopConfig::instance().getSelectionRingSize(
  316. unit->unitType);
  317. }
  318. }
  319. QMatrix4x4 ringM;
  320. QVector3D pos = p.model.column(3).toVector3D();
  321. ringM.translate(pos.x(), 0.15f, pos.z());
  322. ringM.scale(ringSize, 1.0f, ringSize);
  323. if (p.selected)
  324. out.selectionRing(ringM, 0.6f, 0.25f, QVector3D(0.2f, 0.8f, 0.2f));
  325. else
  326. out.selectionRing(ringM, 0.35f, 0.15f, QVector3D(0.90f, 0.90f, 0.25f));
  327. }
  328. }
  329. void registerArcherRenderer(Render::GL::EntityRendererRegistry &registry) {
  330. registry.registerRenderer(
  331. "archer", [](const DrawContext &p, ISubmitter &out) {
  332. QVector3D tunic(0.8f, 0.9f, 1.0f);
  333. Engine::Core::UnitComponent *unit = nullptr;
  334. Engine::Core::RenderableComponent *rc = nullptr;
  335. if (p.entity) {
  336. unit = p.entity->getComponent<Engine::Core::UnitComponent>();
  337. rc = p.entity->getComponent<Engine::Core::RenderableComponent>();
  338. }
  339. if (unit && unit->ownerId > 0) {
  340. tunic = Game::Visuals::teamColorForOwner(unit->ownerId);
  341. } else if (rc) {
  342. tunic = QVector3D(rc->color[0], rc->color[1], rc->color[2]);
  343. }
  344. uint32_t seed = 0u;
  345. if (unit)
  346. seed ^= uint32_t(unit->ownerId * 2654435761u);
  347. if (p.entity)
  348. seed ^= uint32_t(reinterpret_cast<uintptr_t>(p.entity) & 0xFFFFFFFFu);
  349. ArcherPose pose = makePose(seed);
  350. ArcherColors colors = makeColors(tunic);
  351. drawQuiver(p, out, colors, pose, seed);
  352. drawLegs(p, out, pose, colors);
  353. drawTorso(p, out, colors, pose);
  354. drawArms(p, out, pose, colors);
  355. drawHeadAndNeck(p, out, pose, colors);
  356. drawBowAndArrow(p, out, pose, colors);
  357. drawSelectionFX(p, out);
  358. });
  359. }
  360. } // namespace Render::GL