chainmail_armor.cpp 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. #include "chainmail_armor.h"
  2. #include "../../geom/transforms.h"
  3. #include "../../gl/backend.h"
  4. #include "../../gl/primitives.h"
  5. #include "../../humanoid/humanoid_math.h"
  6. #include "../../humanoid/rig.h"
  7. #include "../../submitter.h"
  8. #include <QMatrix4x4>
  9. #include <QVector3D>
  10. #include <algorithm>
  11. #include <cmath>
  12. #include <numbers>
  13. namespace Render::GL {
  14. using Render::Geom::cylinder_between;
  15. auto ChainmailArmorRenderer::calculate_ring_color(float x, float y,
  16. float z) const -> QVector3D {
  17. float rust_noise =
  18. std::sin(x * 127.3F) * std::cos(y * 97.1F) * std::sin(z * 83.7F);
  19. rust_noise = (rust_noise + 1.0F) * 0.5F;
  20. float gravity_rust = std::clamp(1.0F - y * 0.8F, 0.0F, 1.0F);
  21. float total_rust =
  22. (rust_noise * 0.6F + gravity_rust * 0.4F) * m_config.rust_amount;
  23. return m_config.metal_color * (1.0F - total_rust) +
  24. m_config.rust_tint * total_rust;
  25. }
  26. void ChainmailArmorRenderer::render(const DrawContext &ctx,
  27. const BodyFrames &frames,
  28. const HumanoidPalette &palette,
  29. const HumanoidAnimationContext &anim,
  30. ISubmitter &submitter) {
  31. (void)anim;
  32. (void)palette;
  33. renderTorsoMail(ctx, frames, submitter);
  34. if (m_config.has_shoulder_guards) {
  35. renderShoulderGuards(ctx, frames, submitter);
  36. }
  37. if (m_config.has_arm_coverage) {
  38. renderArmMail(ctx, frames, submitter);
  39. }
  40. }
  41. void ChainmailArmorRenderer::renderTorsoMail(const DrawContext &ctx,
  42. const BodyFrames &frames,
  43. ISubmitter &submitter) {
  44. const AttachmentFrame &torso = frames.torso;
  45. const AttachmentFrame &waist = frames.waist;
  46. if (torso.radius <= 0.0F) {
  47. return;
  48. }
  49. float const torso_r = torso.radius;
  50. float const torso_depth =
  51. (torso.depth > 0.0F) ? torso.depth : torso.radius * 0.75F;
  52. QVector3D top = torso.origin + torso.up * (torso_r * 0.20F);
  53. QVector3D bottom = waist.origin - waist.up * (torso_r * 0.35F);
  54. QMatrix4x4 mail_transform = ctx.model;
  55. mail_transform.translate((top + bottom) * 0.5F);
  56. QVector3D up_dir = torso.up.normalized();
  57. QVector3D right_dir = torso.right.normalized();
  58. QVector3D fwd_dir = torso.forward.normalized();
  59. QMatrix4x4 orientation;
  60. orientation(0, 0) = right_dir.x();
  61. orientation(0, 1) = up_dir.x();
  62. orientation(0, 2) = fwd_dir.x();
  63. orientation(1, 0) = right_dir.y();
  64. orientation(1, 1) = up_dir.y();
  65. orientation(1, 2) = fwd_dir.y();
  66. orientation(2, 0) = right_dir.z();
  67. orientation(2, 1) = up_dir.z();
  68. orientation(2, 2) = fwd_dir.z();
  69. mail_transform = mail_transform * orientation;
  70. float height = (top - bottom).length();
  71. mail_transform.scale(torso_r * 1.12F, height * 0.5F,
  72. std::max(0.08F, torso_depth * 1.10F));
  73. QVector3D steel_color = QVector3D(0.65F, 0.67F, 0.70F);
  74. Mesh *torso_mesh = torso_mesh_without_bottom_cap();
  75. submitter.mesh(torso_mesh != nullptr ? torso_mesh : get_unit_torso(),
  76. mail_transform, steel_color, nullptr, 1.0F);
  77. }
  78. void ChainmailArmorRenderer::renderShoulderGuards(const DrawContext &ctx,
  79. const BodyFrames &frames,
  80. ISubmitter &submitter) {
  81. const AttachmentFrame &shoulder_l = frames.shoulder_l;
  82. const AttachmentFrame &shoulder_r = frames.shoulder_r;
  83. const AttachmentFrame &torso = frames.torso;
  84. QVector3D left_base = shoulder_l.origin;
  85. QVector3D left_tip = left_base + torso.up * 0.08F + torso.right * (-0.05F);
  86. float const shoulder_radius = 0.08F;
  87. QVector3D left_color =
  88. calculate_ring_color(left_base.x(), left_base.y(), left_base.z());
  89. submitter.mesh(
  90. get_unit_cylinder(),
  91. cylinder_between(ctx.model, left_base, left_tip, shoulder_radius),
  92. left_color, nullptr, 0.8F);
  93. QVector3D right_base = shoulder_r.origin;
  94. QVector3D right_tip = right_base + torso.up * 0.08F + torso.right * 0.05F;
  95. QVector3D right_color =
  96. calculate_ring_color(right_base.x(), right_base.y(), right_base.z());
  97. submitter.mesh(
  98. get_unit_cylinder(),
  99. cylinder_between(ctx.model, right_base, right_tip, shoulder_radius),
  100. right_color, nullptr, 0.8F);
  101. if (m_config.detail_level >= 1) {
  102. for (int layer = 0; layer < 3; ++layer) {
  103. float layer_offset = static_cast<float>(layer) * 0.025F;
  104. QVector3D left_layer = left_base + torso.up * (-layer_offset);
  105. QVector3D right_layer = right_base + torso.up * (-layer_offset);
  106. QMatrix4x4 left_m = ctx.model;
  107. left_m.translate(left_layer);
  108. left_m.scale(shoulder_radius * 1.3F);
  109. submitter.mesh(get_unit_sphere(), left_m,
  110. left_color * (1.0F - layer_offset), nullptr, 0.75F);
  111. QMatrix4x4 right_m = ctx.model;
  112. right_m.translate(right_layer);
  113. right_m.scale(shoulder_radius * 1.3F);
  114. submitter.mesh(get_unit_sphere(), right_m,
  115. right_color * (1.0F - layer_offset), nullptr, 0.75F);
  116. }
  117. }
  118. }
  119. void ChainmailArmorRenderer::renderArmMail(const DrawContext &ctx,
  120. const BodyFrames &frames,
  121. ISubmitter &submitter) {
  122. const AttachmentFrame &shoulder_l = frames.shoulder_l;
  123. const AttachmentFrame &shoulder_r = frames.shoulder_r;
  124. const AttachmentFrame &hand_l = frames.hand_l;
  125. const AttachmentFrame &hand_r = frames.hand_r;
  126. QVector3D left_shoulder = shoulder_l.origin;
  127. QVector3D left_elbow = (left_shoulder + hand_l.origin) * 0.5F;
  128. int const arm_segments = m_config.detail_level >= 2 ? 6 : 3;
  129. for (int i = 0; i < arm_segments; ++i) {
  130. float t0 = static_cast<float>(i) / static_cast<float>(arm_segments);
  131. float t1 = static_cast<float>(i + 1) / static_cast<float>(arm_segments);
  132. QVector3D pos0 = left_shoulder * (1.0F - t0) + left_elbow * t0;
  133. QVector3D pos1 = left_shoulder * (1.0F - t1) + left_elbow * t1;
  134. float radius = 0.05F * (1.0F - t0 * 0.2F);
  135. QVector3D color = calculate_ring_color(pos0.x(), pos0.y(), pos0.z());
  136. submitter.mesh(get_unit_cylinder(),
  137. cylinder_between(ctx.model, pos0, pos1, radius), color,
  138. nullptr, 0.75F);
  139. }
  140. QVector3D right_shoulder = shoulder_r.origin;
  141. QVector3D right_elbow = (right_shoulder + hand_r.origin) * 0.5F;
  142. for (int i = 0; i < arm_segments; ++i) {
  143. float t0 = static_cast<float>(i) / static_cast<float>(arm_segments);
  144. float t1 = static_cast<float>(i + 1) / static_cast<float>(arm_segments);
  145. QVector3D pos0 = right_shoulder * (1.0F - t0) + right_elbow * t0;
  146. QVector3D pos1 = right_shoulder * (1.0F - t1) + right_elbow * t1;
  147. float radius = 0.05F * (1.0F - t0 * 0.2F);
  148. QVector3D color = calculate_ring_color(pos0.x(), pos0.y(), pos0.z());
  149. submitter.mesh(get_unit_cylinder(),
  150. cylinder_between(ctx.model, pos0, pos1, radius), color,
  151. nullptr, 0.75F);
  152. }
  153. }
  154. void ChainmailArmorRenderer::renderRingDetails(
  155. const DrawContext &ctx, const QVector3D &center, float radius, float height,
  156. const QVector3D &up, const QVector3D &right, ISubmitter &submitter) {
  157. int const rings_around = 24;
  158. int const rings_vertical = 4;
  159. for (int row = 0; row < rings_vertical; ++row) {
  160. float y =
  161. (static_cast<float>(row) / static_cast<float>(rings_vertical)) * height;
  162. float row_offset = (row % 2) * 0.5F;
  163. for (int col = 0; col < rings_around; ++col) {
  164. float angle = ((static_cast<float>(col) + row_offset) /
  165. static_cast<float>(rings_around)) *
  166. 2.0F * std::numbers::pi_v<float>;
  167. float x = std::cos(angle) * radius;
  168. float z = std::sin(angle) * radius;
  169. QVector3D ring_pos = center + up * y + right * x +
  170. QVector3D::crossProduct(up, right).normalized() * z;
  171. QMatrix4x4 ring_m = ctx.model;
  172. ring_m.translate(ring_pos);
  173. ring_m.scale(m_config.ring_size);
  174. QVector3D color =
  175. calculate_ring_color(ring_pos.x(), ring_pos.y(), ring_pos.z());
  176. submitter.mesh(get_unit_sphere(), ring_m, color, nullptr, 0.85F);
  177. }
  178. }
  179. }
  180. } // namespace Render::GL