sword_renderer.cpp 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. #include "sword_renderer.h"
  2. #include "../../entity/renderer_constants.h"
  3. #include "../../geom/math_utils.h"
  4. #include "../../geom/transforms.h"
  5. #include "../../gl/primitives.h"
  6. #include "../../humanoid/humanoid_math.h"
  7. #include "../../humanoid/rig.h"
  8. #include "../../humanoid/style_palette.h"
  9. #include "../../submitter.h"
  10. #include <QMatrix4x4>
  11. #include <QVector3D>
  12. #include <cmath>
  13. namespace Render::GL {
  14. using Render::Geom::clamp01;
  15. using Render::Geom::clamp_f;
  16. using Render::Geom::cone_from_to;
  17. using Render::Geom::cylinder_between;
  18. using Render::Geom::ease_in_out_cubic;
  19. using Render::Geom::lerp;
  20. using Render::Geom::nlerp;
  21. using Render::Geom::smoothstep;
  22. SwordRenderer::SwordRenderer(SwordRenderConfig config)
  23. : m_config(std::move(config)) {}
  24. void SwordRenderer::render(const DrawContext &ctx, const BodyFrames &frames,
  25. const HumanoidPalette &palette,
  26. const HumanoidAnimationContext &anim,
  27. ISubmitter &submitter) {
  28. QVector3D const grip_pos = frames.hand_r.origin;
  29. bool const is_attacking = anim.inputs.is_attacking && anim.inputs.is_melee;
  30. float attack_phase = 0.0F;
  31. if (is_attacking) {
  32. attack_phase =
  33. std::fmod(anim.inputs.time * KNIGHT_INV_ATTACK_CYCLE_TIME, 1.0F);
  34. }
  35. constexpr float k_sword_yaw_deg = 25.0F;
  36. QMatrix4x4 yaw_m;
  37. yaw_m.rotate(k_sword_yaw_deg, 0.0F, 1.0F, 0.0F);
  38. QVector3D upish = yaw_m.map(QVector3D(0.05F, 1.0F, 0.15F));
  39. QVector3D midish = yaw_m.map(QVector3D(0.08F, 0.20F, 1.0F));
  40. QVector3D downish = yaw_m.map(QVector3D(0.10F, -1.0F, 0.25F));
  41. if (upish.lengthSquared() > 1e-6F) {
  42. upish.normalize();
  43. }
  44. if (midish.lengthSquared() > 1e-6F) {
  45. midish.normalize();
  46. }
  47. if (downish.lengthSquared() > 1e-6F) {
  48. downish.normalize();
  49. }
  50. QVector3D sword_dir = upish;
  51. if (is_attacking) {
  52. if (attack_phase < 0.18F) {
  53. float const t = ease_in_out_cubic(attack_phase / 0.18F);
  54. sword_dir = nlerp(upish, upish, t);
  55. } else if (attack_phase < 0.32F) {
  56. float const t = ease_in_out_cubic((attack_phase - 0.18F) / 0.14F);
  57. sword_dir = nlerp(upish, midish, t * 0.35F);
  58. } else if (attack_phase < 0.52F) {
  59. float t = (attack_phase - 0.32F) / 0.20F;
  60. t = t * t * t;
  61. if (t < 0.5F) {
  62. float const u = t / 0.5F;
  63. sword_dir = nlerp(upish, midish, u);
  64. } else {
  65. float const u = (t - 0.5F) / 0.5F;
  66. sword_dir = nlerp(midish, downish, u);
  67. }
  68. } else if (attack_phase < 0.72F) {
  69. float const t = ease_in_out_cubic((attack_phase - 0.52F) / 0.20F);
  70. sword_dir = nlerp(downish, midish, t);
  71. } else {
  72. float const t = smoothstep(0.72F, 1.0F, attack_phase);
  73. sword_dir = nlerp(midish, upish, t);
  74. }
  75. }
  76. QVector3D const handle_end = grip_pos - sword_dir * 0.10F;
  77. QVector3D const blade_base = grip_pos;
  78. QVector3D const blade_tip = grip_pos + sword_dir * m_config.sword_length;
  79. submitter.mesh(get_unit_cylinder(),
  80. cylinder_between(ctx.model, handle_end, blade_base,
  81. m_config.handle_radius),
  82. palette.leather, nullptr, 1.0F, m_config.material_id);
  83. QVector3D const guard_center = blade_base;
  84. float const gw = m_config.guard_half_width;
  85. QVector3D guard_right =
  86. QVector3D::crossProduct(QVector3D(0, 1, 0), sword_dir);
  87. if (guard_right.lengthSquared() < 1e-6F) {
  88. guard_right = QVector3D::crossProduct(QVector3D(1, 0, 0), sword_dir);
  89. }
  90. guard_right.normalize();
  91. QVector3D const guard_l = guard_center - guard_right * gw;
  92. QVector3D const guard_r = guard_center + guard_right * gw;
  93. submitter.mesh(get_unit_cylinder(),
  94. cylinder_between(ctx.model, guard_l, guard_r, 0.014F),
  95. m_config.metal_color, nullptr, 1.0F, m_config.material_id);
  96. QMatrix4x4 gl = ctx.model;
  97. gl.translate(guard_l);
  98. gl.scale(0.018F);
  99. submitter.mesh(get_unit_sphere(), gl, m_config.metal_color, nullptr, 1.0F,
  100. m_config.material_id);
  101. QMatrix4x4 gr = ctx.model;
  102. gr.translate(guard_r);
  103. gr.scale(0.018F);
  104. submitter.mesh(get_unit_sphere(), gr, m_config.metal_color, nullptr, 1.0F,
  105. m_config.material_id);
  106. float const l = m_config.sword_length;
  107. float const base_w = m_config.sword_width;
  108. float blade_thickness = base_w * 0.15F;
  109. float const ricasso_len = clamp_f(m_config.blade_ricasso, 0.10F, l * 0.30F);
  110. QVector3D const ricasso_end = blade_base + sword_dir * ricasso_len;
  111. float const mid_w = base_w * 0.95F;
  112. float const tip_w = base_w * 0.28F;
  113. float const tip_start_dist = lerp(ricasso_len, l, 0.70F);
  114. QVector3D const tip_start = blade_base + sword_dir * tip_start_dist;
  115. auto draw_flat_section = [&](const QVector3D &start, const QVector3D &end,
  116. float width, const QVector3D &color) {
  117. QVector3D right = QVector3D::crossProduct(sword_dir, QVector3D(0, 1, 0));
  118. if (right.lengthSquared() < 0.001F) {
  119. right = QVector3D::crossProduct(sword_dir, QVector3D(1, 0, 0));
  120. }
  121. right.normalize();
  122. float const offset = width * 0.33F;
  123. submitter.mesh(get_unit_cylinder(),
  124. cylinder_between(ctx.model, start, end, blade_thickness),
  125. color, nullptr, 1.0F, m_config.material_id);
  126. submitter.mesh(get_unit_cylinder(),
  127. cylinder_between(ctx.model, start + right * offset,
  128. end + right * offset,
  129. blade_thickness * 0.8F),
  130. color * 0.92F, nullptr, 1.0F, m_config.material_id);
  131. submitter.mesh(get_unit_cylinder(),
  132. cylinder_between(ctx.model, start - right * offset,
  133. end - right * offset,
  134. blade_thickness * 0.8F),
  135. color * 0.92F, nullptr, 1.0F, m_config.material_id);
  136. };
  137. draw_flat_section(blade_base, ricasso_end, base_w, m_config.metal_color);
  138. draw_flat_section(ricasso_end, tip_start, mid_w, m_config.metal_color);
  139. int const tip_segments = 3;
  140. for (int i = 0; i < tip_segments; ++i) {
  141. float const t0 = (float)i / tip_segments;
  142. float const t1 = (float)(i + 1) / tip_segments;
  143. QVector3D const seg_start =
  144. tip_start + sword_dir * ((blade_tip - tip_start).length() * t0);
  145. QVector3D const seg_end =
  146. tip_start + sword_dir * ((blade_tip - tip_start).length() * t1);
  147. float const w = lerp(mid_w, tip_w, t1);
  148. submitter.mesh(
  149. get_unit_cylinder(),
  150. cylinder_between(ctx.model, seg_start, seg_end, blade_thickness),
  151. m_config.metal_color * (1.0F - i * 0.03F), nullptr, 1.0F,
  152. m_config.material_id);
  153. }
  154. QVector3D const fuller_start = blade_base + sword_dir * (ricasso_len + 0.02F);
  155. QVector3D const fuller_end =
  156. blade_base + sword_dir * (tip_start_dist - 0.06F);
  157. submitter.mesh(get_unit_cylinder(),
  158. cylinder_between(ctx.model, fuller_start, fuller_end,
  159. blade_thickness * 0.6F),
  160. m_config.metal_color * 0.65F, nullptr, 1.0F,
  161. m_config.material_id);
  162. QVector3D const pommel = handle_end - sword_dir * 0.02F;
  163. QMatrix4x4 pommel_mat = ctx.model;
  164. pommel_mat.translate(pommel);
  165. pommel_mat.scale(m_config.pommel_radius);
  166. submitter.mesh(get_unit_sphere(), pommel_mat, m_config.metal_color, nullptr,
  167. 1.0F, m_config.material_id);
  168. if (is_attacking && attack_phase >= 0.32F && attack_phase < 0.56F) {
  169. float const t = (attack_phase - 0.32F) / 0.24F;
  170. float const alpha = clamp01(0.35F * (1.0F - t));
  171. QVector3D const trail_start = blade_base - sword_dir * 0.05F;
  172. QVector3D const trail_end = blade_base - sword_dir * (0.28F + 0.15F * t);
  173. submitter.mesh(
  174. get_unit_cone(),
  175. cone_from_to(ctx.model, trail_end, trail_start, base_w * 0.9F),
  176. m_config.metal_color * 0.9F, nullptr, alpha, m_config.material_id);
  177. }
  178. }
  179. } // namespace Render::GL