arrow_vfx_renderer.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  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 "../humanoid_math.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 neck_base{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, hand_r;
  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 foot_r{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.hand_r = QVector3D(0.15F, HP::SHOULDER_Y + 0.15F, 0.20F);
  81. QVector3D shoulder_to_hand_l = P.handL - P.shoulderL;
  82. float distL = shoulder_to_hand_l.length();
  83. QVector3D dirL = shoulder_to_hand_l.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 shoulder_to_hand_r = P.hand_r - P.shoulderR;
  89. float distR = shoulder_to_hand_r.length();
  90. QVector3D dirR = shoulder_to_hand_r.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 &team_tint) {
  98. ArcherColors C;
  99. auto tint = [&](float k) {
  100. return QVector3D(clamp01(team_tint.x() * k), clamp01(team_tint.y() * k),
  101. clamp01(team_tint.z() * k));
  102. };
  103. C.tunic = team_tint;
  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 torso_top{0.0F, HP::NECK_BASE_Y - 0.05F, 0.0F};
  118. QVector3D torsoBot{0.0F, HP::WAIST_Y, 0.0F};
  119. float torso_radius = HP::TORSO_TOP_R;
  120. out.mesh(getUnitCylinder(),
  121. cylinderBetween(p.model, torso_top, torsoBot, torso_radius), 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 chin_pos{0.0F, HP::CHIN_Y, 0.0F};
  133. out.mesh(getUnitCylinder(),
  134. cylinderBetween(p.model, P.neck_base, chin_pos, 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 eye_spacing = P.headR * 0.35F;
  142. out.mesh(getUnitSphere(),
  143. p.model *
  144. sphereAt(QVector3D(-eye_spacing, eyeY, eyeZ), P.headR * 0.15F),
  145. iris, nullptr, 1.0F);
  146. out.mesh(getUnitSphere(),
  147. p.model *
  148. sphereAt(QVector3D(eye_spacing, 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 draw_arms(const DrawContext &p, ISubmitter &out,
  171. const ArcherPose &P, const ArcherColors &C) {
  172. using HP = HumanProportions;
  173. const float upper_arm_r = HP::UPPER_ARM_R;
  174. const float fore_arm_r = HP::FORE_ARM_R;
  175. const float joint_r = upper_arm_r * 1.2F;
  176. out.mesh(getUnitCylinder(),
  177. cylinderBetween(p.model, P.shoulderL, P.elbowL, upper_arm_r),
  178. C.tunic, nullptr, 1.0F);
  179. out.mesh(getUnitSphere(), sphereAt(p.model, P.elbowL, joint_r),
  180. C.tunic * 0.95F, nullptr, 1.0F);
  181. out.mesh(getUnitCylinder(),
  182. cylinderBetween(p.model, P.elbowL, P.handL, fore_arm_r),
  183. C.skin * 0.95F, nullptr, 1.0F);
  184. out.mesh(getUnitCylinder(),
  185. cylinderBetween(p.model, P.shoulderR, P.elbowR, upper_arm_r),
  186. C.tunic, nullptr, 1.0F);
  187. out.mesh(getUnitSphere(), sphereAt(p.model, P.elbowR, joint_r),
  188. C.tunic * 0.95F, nullptr, 1.0F);
  189. out.mesh(getUnitCylinder(),
  190. cylinderBetween(p.model, P.elbowR, P.hand_r, fore_arm_r),
  191. C.skin * 0.95F, nullptr, 1.0F);
  192. }
  193. static inline void draw_legs(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 knee_l = makeKnee(P.hipL, P.footL, -1.0F);
  209. QVector3D knee_r = makeKnee(P.hipR, P.foot_r, 1.0F);
  210. out.mesh(getUnitCone(), coneFromTo(p.model, P.hipL, knee_l, thighR),
  211. C.leather, nullptr, 1.0F);
  212. out.mesh(getUnitCone(), coneFromTo(p.model, P.hipR, knee_r, thighR),
  213. C.leather, nullptr, 1.0F);
  214. out.mesh(getUnitSphere(), sphereAt(p.model, knee_l, kneeJointR),
  215. C.leather * 0.95F, nullptr, 1.0F);
  216. out.mesh(getUnitSphere(), sphereAt(p.model, knee_r, kneeJointR),
  217. C.leather * 0.95F, nullptr, 1.0F);
  218. out.mesh(getUnitCone(), coneFromTo(p.model, knee_l, P.footL, shinR),
  219. C.leatherDark, nullptr, 1.0F);
  220. out.mesh(getUnitCone(), coneFromTo(p.model, knee_r, P.foot_r, 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.foot_r, P.foot_r + 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. QVector3D qTop(-0.08F, HP::SHOULDER_Y + 0.10F, -0.25F);
  235. QVector3D q_base(-0.10F, HP::CHEST_Y, -0.22F);
  236. float quiver_r = HP::HEAD_RADIUS * 0.45F;
  237. out.mesh(getUnitCylinder(), cylinderBetween(p.model, q_base, qTop, quiver_r),
  238. C.leather, nullptr, 1.0F);
  239. float j = (hash_01(seed) - 0.5F) * 0.04F;
  240. float k = (hash_01(seed ^ HashXorShift::k_golden_ratio) - 0.5F) * 0.04F;
  241. QVector3D a1 = qTop + QVector3D(0.00F + j, 0.08F, 0.00F + k);
  242. out.mesh(getUnitCylinder(), cylinderBetween(p.model, qTop, a1, 0.010F),
  243. C.wood, nullptr, 1.0F);
  244. out.mesh(getUnitCone(),
  245. coneFromTo(p.model, a1, a1 + QVector3D(0, 0.05F, 0), 0.025F),
  246. C.fletch, nullptr, 1.0F);
  247. QVector3D a2 = qTop + QVector3D(0.02F - j, 0.07F, 0.02F - k);
  248. out.mesh(getUnitCylinder(), cylinderBetween(p.model, qTop, a2, 0.010F),
  249. C.wood, nullptr, 1.0F);
  250. out.mesh(getUnitCone(),
  251. coneFromTo(p.model, a2, a2 + QVector3D(0, 0.05F, 0), 0.025F),
  252. C.fletch, nullptr, 1.0F);
  253. }
  254. static inline void drawBowAndArrow(const DrawContext &p, ISubmitter &out,
  255. const ArcherPose &P, const ArcherColors &C) {
  256. const QVector3D up(0.0F, 1.0F, 0.0F);
  257. const QVector3D forward(0.0F, 0.0F, 1.0F);
  258. QVector3D grip = P.handL;
  259. QVector3D top_end(P.bowX, P.bowTopY, grip.z());
  260. QVector3D bot_end(P.bowX, P.bowBotY, grip.z());
  261. QVector3D nock(P.bowX,
  262. clampf(P.hand_r.y(), P.bowBotY + 0.05F, P.bowTopY - 0.05F),
  263. clampf(P.hand_r.z(), grip.z() - 0.30F, grip.z() + 0.30F));
  264. constexpr int k_bow_curve_segments = 22;
  265. auto q_bezier = [](const QVector3D &a, const QVector3D &c, const QVector3D &b,
  266. float t) {
  267. float u = 1.0F - t;
  268. return u * u * a + 2.0F * u * t * c + t * t * b;
  269. };
  270. QVector3D ctrl = nock + forward * P.bowDepth;
  271. QVector3D prev = bot_end;
  272. for (int i = 1; i <= k_bow_curve_segments; ++i) {
  273. float t = float(i) / float(k_bow_curve_segments);
  274. QVector3D cur = q_bezier(bot_end, ctrl, top_end, t);
  275. out.mesh(getUnitCylinder(), cylinderBetween(p.model, prev, cur, P.bowRodR),
  276. C.wood, nullptr, 1.0F);
  277. prev = cur;
  278. }
  279. out.mesh(getUnitCylinder(),
  280. cylinderBetween(p.model, grip - up * 0.05F, grip + up * 0.05F,
  281. P.bowRodR * 1.45F),
  282. C.wood, nullptr, 1.0F);
  283. out.mesh(getUnitCylinder(),
  284. cylinderBetween(p.model, top_end, nock, P.stringR), C.stringCol,
  285. nullptr, 1.0F);
  286. out.mesh(getUnitCylinder(),
  287. cylinderBetween(p.model, nock, bot_end, P.stringR), C.stringCol,
  288. nullptr, 1.0F);
  289. out.mesh(getUnitCylinder(), cylinderBetween(p.model, P.hand_r, nock, 0.0045F),
  290. C.stringCol * 0.9F, nullptr, 1.0F);
  291. QVector3D tail = nock - forward * 0.06F;
  292. QVector3D tip = tail + forward * 0.90F;
  293. out.mesh(getUnitCylinder(), cylinderBetween(p.model, tail, tip, 0.035F),
  294. C.wood, nullptr, 1.0F);
  295. QVector3D head_base = tip - forward * 0.10F;
  296. out.mesh(getUnitCone(), coneFromTo(p.model, head_base, tip, 0.05F),
  297. C.metalHead, nullptr, 1.0F);
  298. QVector3D f1b = tail - forward * 0.02F, f1a = f1b - forward * 0.06F;
  299. QVector3D f2b = tail + forward * 0.02F, f2a = f2b + forward * 0.06F;
  300. out.mesh(getUnitCone(), coneFromTo(p.model, f1b, f1a, 0.04F), C.fletch,
  301. nullptr, 1.0F);
  302. out.mesh(getUnitCone(), coneFromTo(p.model, f2a, f2b, 0.04F), C.fletch,
  303. nullptr, 1.0F);
  304. }
  305. void registerArcherRenderer(Render::GL::EntityRendererRegistry &registry) {
  306. registry.registerRenderer(
  307. "archer", [](const DrawContext &p, ISubmitter &out) {
  308. QVector3D tunic(0.8F, 0.9F, 1.0F);
  309. Engine::Core::UnitComponent *unit = nullptr;
  310. Engine::Core::RenderableComponent *rc = nullptr;
  311. if (p.entity) {
  312. unit = p.entity->getComponent<Engine::Core::UnitComponent>();
  313. rc = p.entity->getComponent<Engine::Core::RenderableComponent>();
  314. }
  315. if (unit && unit->owner_id > 0) {
  316. tunic = Game::Visuals::team_colorForOwner(unit->owner_id);
  317. } else if (rc) {
  318. tunic = QVector3D(rc->color[0], rc->color[1], rc->color[2]);
  319. }
  320. uint32_t seed = 0u;
  321. if (unit)
  322. seed ^= uint32_t(unit->owner_id * 2654435761u);
  323. if (p.entity)
  324. seed ^= uint32_t(reinterpret_cast<uintptr_t>(p.entity) & 0xFFFFFFFFu);
  325. ArcherPose pose = makePose(seed);
  326. ArcherColors colors = makeColors(tunic);
  327. drawQuiver(p, out, colors, pose, seed);
  328. draw_legs(p, out, pose, colors);
  329. drawTorso(p, out, colors, pose);
  330. draw_arms(p, out, pose, colors);
  331. drawHeadAndNeck(p, out, pose, colors);
  332. drawBowAndArrow(p, out, pose, colors);
  333. });
  334. }
  335. } // namespace Render::GL