arrow_vfx_renderer.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  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/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::cone_from_to;
  21. using Render::Geom::cylinder_between;
  22. using Render::Geom::sphere_at;
  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.hand_r = QVector3D(P.bowX + 0.03F, HP::SHOULDER_Y + 0.08F, 0.55F);
  80. P.hand_l = QVector3D(-0.02F, HP::SHOULDER_Y + 0.12F, 0.50F);
  81. QVector3D shoulder_to_hand_l = P.hand_l - P.shoulder_l;
  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.elbow_l = P.shoulder_l + dirL * (distL * 0.45F) + perpL * elbowOffsetL +
  87. QVector3D(0, -0.08F, 0);
  88. QVector3D shoulder_to_hand_r = P.hand_r - P.shoulder_r;
  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.elbow_r = P.shoulder_r + 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(get_unit_cylinder(),
  121. cylinder_between(p.model, torso_top, torsoBot, torso_radius),
  122. C.tunic, 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(get_unit_cone(),
  126. cone_from_to(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(get_unit_cylinder(),
  134. cylinder_between(p.model, P.neck_base, chin_pos, HP::NECK_RADIUS),
  135. C.skin * 0.9F, nullptr, 1.0F);
  136. out.mesh(get_unit_sphere(), sphere_at(p.model, P.head_pos, P.head_r), C.skin,
  137. nullptr, 1.0F);
  138. QVector3D iris(0.06F, 0.06F, 0.07F);
  139. float eyeZ = P.head_r * 0.7F;
  140. float eyeY = P.head_pos.y() + P.head_r * 0.1F;
  141. float eye_spacing = P.head_r * 0.35F;
  142. out.mesh(get_unit_sphere(),
  143. p.model *
  144. sphere_at(QVector3D(-eye_spacing, eyeY, eyeZ), P.head_r * 0.15F),
  145. iris, nullptr, 1.0F);
  146. out.mesh(get_unit_sphere(),
  147. p.model *
  148. sphere_at(QVector3D(eye_spacing, eyeY, eyeZ), P.head_r * 0.15F),
  149. iris, nullptr, 1.0F);
  150. QVector3D domeC = P.head_pos + QVector3D(0.0F, P.head_r * 0.25F, 0.0F);
  151. float domeR = P.head_r * 1.05F;
  152. out.mesh(get_unit_sphere(), sphere_at(p.model, domeC, domeR), C.metal,
  153. nullptr, 1.0F);
  154. QVector3D visorBase(0.0F, P.head_pos.y() + P.head_r * 0.10F,
  155. P.head_r * 0.80F);
  156. QVector3D visorTip = visorBase + QVector3D(0.0F, -0.015F, 0.06F);
  157. out.mesh(get_unit_cone(),
  158. cone_from_to(p.model, visorBase, visorTip, P.head_r * 0.38F),
  159. C.metal * 0.92F, nullptr, 1.0F);
  160. QVector3D cheekL0(-P.head_r * 0.85F, P.head_pos.y() + P.head_r * 0.05F,
  161. 0.02F);
  162. QVector3D cheekL1(-P.head_r * 0.85F, P.head_pos.y() - P.head_r * 0.20F,
  163. 0.04F);
  164. QVector3D cheekR0(P.head_r * 0.85F, P.head_pos.y() + P.head_r * 0.05F,
  165. -0.02F);
  166. QVector3D cheekR1(P.head_r * 0.85F, P.head_pos.y() - P.head_r * 0.20F,
  167. -0.04F);
  168. out.mesh(get_unit_cone(),
  169. cone_from_to(p.model, cheekL0, cheekL1, P.head_r * 0.24F),
  170. C.metal * 0.95F, nullptr, 1.0F);
  171. out.mesh(get_unit_cone(),
  172. cone_from_to(p.model, cheekR0, cheekR1, P.head_r * 0.24F),
  173. C.metal * 0.95F, nullptr, 1.0F);
  174. }
  175. static inline void draw_arms(const DrawContext &p, ISubmitter &out,
  176. const ArcherPose &P, const ArcherColors &C) {
  177. using HP = HumanProportions;
  178. const float upper_arm_r = HP::UPPER_ARM_R;
  179. const float fore_arm_r = HP::FORE_ARM_R;
  180. const float joint_r = upper_arm_r * 1.2F;
  181. out.mesh(get_unit_cylinder(),
  182. cylinder_between(p.model, P.shoulder_l, P.elbow_l, upper_arm_r),
  183. C.tunic, nullptr, 1.0F);
  184. out.mesh(get_unit_sphere(), sphere_at(p.model, P.elbow_l, joint_r),
  185. C.tunic * 0.95F, nullptr, 1.0F);
  186. out.mesh(get_unit_cylinder(),
  187. cylinder_between(p.model, P.elbow_l, P.hand_l, fore_arm_r),
  188. C.skin * 0.95F, nullptr, 1.0F);
  189. out.mesh(get_unit_cylinder(),
  190. cylinder_between(p.model, P.shoulder_r, P.elbow_r, upper_arm_r),
  191. C.tunic, nullptr, 1.0F);
  192. out.mesh(get_unit_sphere(), sphere_at(p.model, P.elbow_r, joint_r),
  193. C.tunic * 0.95F, nullptr, 1.0F);
  194. out.mesh(get_unit_cylinder(),
  195. cylinder_between(p.model, P.elbow_r, P.hand_r, fore_arm_r),
  196. C.skin * 0.95F, nullptr, 1.0F);
  197. }
  198. static inline void draw_legs(const DrawContext &p, ISubmitter &out,
  199. const ArcherPose &P, const ArcherColors &C) {
  200. using HP = HumanProportions;
  201. const float thighR = HP::UPPER_LEG_R;
  202. const float shinR = HP::LOWER_LEG_R;
  203. const float kneeJointR = thighR * 1.15F;
  204. auto makeKnee = [&](const QVector3D &hip, const QVector3D &foot,
  205. float outwardSign) {
  206. const float t = 0.38F;
  207. QVector3D knee = hip * (1.0F - t) + foot * t;
  208. knee.setY(HP::KNEE_Y + 0.03F);
  209. knee.setZ(knee.z() + 0.05F);
  210. knee.setX(knee.x() + outwardSign * 0.06F);
  211. return knee;
  212. };
  213. QVector3D knee_l = makeKnee(P.hipL, P.foot_l, -1.0F);
  214. QVector3D knee_r = makeKnee(P.hipR, P.foot_r, 1.0F);
  215. out.mesh(get_unit_cone(), cone_from_to(p.model, P.hipL, knee_l, thighR),
  216. C.leather, nullptr, 1.0F);
  217. out.mesh(get_unit_cone(), cone_from_to(p.model, P.hipR, knee_r, thighR),
  218. C.leather, nullptr, 1.0F);
  219. out.mesh(get_unit_sphere(), sphere_at(p.model, knee_l, kneeJointR),
  220. C.leather * 0.95F, nullptr, 1.0F);
  221. out.mesh(get_unit_sphere(), sphere_at(p.model, knee_r, kneeJointR),
  222. C.leather * 0.95F, nullptr, 1.0F);
  223. out.mesh(get_unit_cone(), cone_from_to(p.model, knee_l, P.foot_l, shinR),
  224. C.leatherDark, nullptr, 1.0F);
  225. out.mesh(get_unit_cone(), cone_from_to(p.model, knee_r, P.foot_r, shinR),
  226. C.leatherDark, nullptr, 1.0F);
  227. QVector3D down(0.0F, -0.02F, 0.0F);
  228. out.mesh(get_unit_cylinder(),
  229. cylinder_between(p.model, P.foot_l, P.foot_l + down, shinR * 1.1F),
  230. C.leatherDark, nullptr, 1.0F);
  231. out.mesh(get_unit_cylinder(),
  232. cylinder_between(p.model, P.foot_r, P.foot_r + down, shinR * 1.1F),
  233. C.leatherDark, nullptr, 1.0F);
  234. }
  235. static inline void drawQuiver(const DrawContext &p, ISubmitter &out,
  236. const ArcherColors &C, const ArcherPose &P,
  237. uint32_t seed) {
  238. using HP = HumanProportions;
  239. QVector3D qTop(-0.08F, HP::SHOULDER_Y + 0.10F, -0.25F);
  240. QVector3D q_base(-0.10F, HP::CHEST_Y, -0.22F);
  241. float quiver_r = HP::HEAD_RADIUS * 0.45F;
  242. out.mesh(get_unit_cylinder(),
  243. cylinder_between(p.model, q_base, qTop, quiver_r), C.leather,
  244. nullptr, 1.0F);
  245. float j = (hash_01(seed) - 0.5F) * 0.04F;
  246. float k = (hash_01(seed ^ HashXorShift::k_golden_ratio) - 0.5F) * 0.04F;
  247. QVector3D a1 = qTop + QVector3D(0.00F + j, 0.08F, 0.00F + k);
  248. out.mesh(get_unit_cylinder(), cylinder_between(p.model, qTop, a1, 0.010F),
  249. C.wood, nullptr, 1.0F);
  250. out.mesh(get_unit_cone(),
  251. cone_from_to(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(get_unit_cylinder(), cylinder_between(p.model, qTop, a2, 0.010F),
  255. C.wood, nullptr, 1.0F);
  256. out.mesh(get_unit_cone(),
  257. cone_from_to(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.hand_r;
  265. QVector3D top_end(P.bowX, P.bowTopY, grip.z());
  266. QVector3D bot_end(P.bowX, P.bowBotY, grip.z());
  267. QVector3D nock(P.bowX,
  268. clampf(P.hand_l.y(), P.bowBotY + 0.05F, P.bowTopY - 0.05F),
  269. clampf(P.hand_l.z(), grip.z() - 0.30F, grip.z() + 0.30F));
  270. constexpr int k_bow_curve_segments = 22;
  271. auto q_bezier = [](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 = bot_end;
  278. for (int i = 1; i <= k_bow_curve_segments; ++i) {
  279. float t = float(i) / float(k_bow_curve_segments);
  280. QVector3D cur = q_bezier(bot_end, ctrl, top_end, t);
  281. out.mesh(get_unit_cylinder(),
  282. cylinder_between(p.model, prev, cur, P.bowRodR), C.wood, nullptr,
  283. 1.0F);
  284. prev = cur;
  285. }
  286. out.mesh(get_unit_cylinder(),
  287. cylinder_between(p.model, grip - up * 0.05F, grip + up * 0.05F,
  288. P.bowRodR * 1.45F),
  289. C.wood, nullptr, 1.0F);
  290. out.mesh(get_unit_cylinder(),
  291. cylinder_between(p.model, top_end, nock, P.stringR), C.stringCol,
  292. nullptr, 1.0F);
  293. out.mesh(get_unit_cylinder(),
  294. cylinder_between(p.model, nock, bot_end, P.stringR), C.stringCol,
  295. nullptr, 1.0F);
  296. out.mesh(get_unit_cylinder(),
  297. cylinder_between(p.model, P.hand_l, nock, 0.0045F),
  298. C.stringCol * 0.9F, nullptr, 1.0F);
  299. QVector3D tail = nock - forward * 0.06F;
  300. QVector3D tip = tail + forward * 0.90F;
  301. out.mesh(get_unit_cylinder(), cylinder_between(p.model, tail, tip, 0.035F),
  302. C.wood, nullptr, 1.0F);
  303. QVector3D head_base = tip - forward * 0.10F;
  304. out.mesh(get_unit_cone(), cone_from_to(p.model, head_base, tip, 0.05F),
  305. C.metalHead, nullptr, 1.0F);
  306. QVector3D f1b = tail - forward * 0.02F, f1a = f1b - forward * 0.06F;
  307. QVector3D f2b = tail + forward * 0.02F, f2a = f2b + forward * 0.06F;
  308. out.mesh(get_unit_cone(), cone_from_to(p.model, f1b, f1a, 0.04F), C.fletch,
  309. nullptr, 1.0F);
  310. out.mesh(get_unit_cone(), cone_from_to(p.model, f2a, f2b, 0.04F), C.fletch,
  311. nullptr, 1.0F);
  312. }
  313. void register_archer_renderer(Render::GL::EntityRendererRegistry &registry) {
  314. registry.register_renderer(
  315. "archer", [](const DrawContext &p, ISubmitter &out) {
  316. QVector3D tunic(0.8F, 0.9F, 1.0F);
  317. Engine::Core::UnitComponent *unit = nullptr;
  318. Engine::Core::RenderableComponent *rc = nullptr;
  319. if (p.entity) {
  320. unit = p.entity->get_component<Engine::Core::UnitComponent>();
  321. rc = p.entity->get_component<Engine::Core::RenderableComponent>();
  322. }
  323. if (unit && unit->owner_id > 0) {
  324. tunic = Game::Visuals::team_colorForOwner(unit->owner_id);
  325. } else if (rc) {
  326. tunic = QVector3D(rc->color[0], rc->color[1], rc->color[2]);
  327. }
  328. uint32_t seed = 0u;
  329. if (unit)
  330. seed ^= uint32_t(unit->owner_id * 2654435761u);
  331. if (p.entity)
  332. seed ^= uint32_t(reinterpret_cast<uintptr_t>(p.entity) & 0xFFFFFFFFu);
  333. ArcherPose pose = makePose(seed);
  334. ArcherColors colors = makeColors(tunic);
  335. drawQuiver(p, out, colors, pose, seed);
  336. draw_legs(p, out, pose, colors);
  337. drawTorso(p, out, colors, pose);
  338. draw_arms(p, out, pose, colors);
  339. drawHeadAndNeck(p, out, pose, colors);
  340. drawBowAndArrow(p, out, pose, colors);
  341. });
  342. }
  343. } // namespace Render::GL