tunic_renderer.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. #include "tunic_renderer.h"
  2. #include "../../geom/transforms.h"
  3. #include "../../gl/primitives.h"
  4. #include "../../humanoid/humanoid_math.h"
  5. #include "../../humanoid/humanoid_specs.h"
  6. #include "../../humanoid/rig.h"
  7. #include "../../humanoid/style_palette.h"
  8. #include "../../submitter.h"
  9. #include <QMatrix4x4>
  10. #include <QVector3D>
  11. #include <algorithm>
  12. #include <cmath>
  13. #include <numbers>
  14. namespace Render::GL {
  15. using Render::Geom::cone_from_to;
  16. using Render::Geom::cylinder_between;
  17. using Render::Geom::sphere_at;
  18. using Render::GL::Humanoid::saturate_color;
  19. TunicRenderer::TunicRenderer(const TunicConfig &config) : m_config(config) {}
  20. void TunicRenderer::render(const DrawContext &ctx, const BodyFrames &frames,
  21. const HumanoidPalette &palette,
  22. const HumanoidAnimationContext &anim,
  23. ISubmitter &submitter) {
  24. (void)anim;
  25. const AttachmentFrame &torso = frames.torso;
  26. const AttachmentFrame &waist = frames.waist;
  27. if (torso.radius <= 0.0F) {
  28. return;
  29. }
  30. QVector3D const steel_color =
  31. saturate_color(palette.metal * QVector3D(0.95F, 0.96F, 1.0F));
  32. QVector3D const brass_color =
  33. saturate_color(palette.metal * QVector3D(1.3F, 1.1F, 0.7F));
  34. using HP = HumanProportions;
  35. auto torsoY = [&](float spec_y) {
  36. float const delta = spec_y - HP::SHOULDER_Y;
  37. return torso.origin.y() + delta;
  38. };
  39. float const y_top = torsoY(HP::SHOULDER_Y + 0.02F);
  40. renderTorsoArmor(ctx, torso, steel_color, brass_color, submitter);
  41. if (m_config.include_pauldrons) {
  42. renderPauldrons(ctx, frames, steel_color, brass_color, submitter);
  43. }
  44. if (m_config.include_gorget) {
  45. renderGorget(ctx, torso, y_top, steel_color, brass_color, submitter);
  46. }
  47. if (m_config.include_belt) {
  48. renderBelt(ctx, waist, steel_color, brass_color, submitter);
  49. }
  50. }
  51. void TunicRenderer::renderTorsoArmor(const DrawContext &ctx,
  52. const AttachmentFrame &torso,
  53. const QVector3D &steel_color,
  54. const QVector3D &brass_color,
  55. ISubmitter &submitter) {
  56. using HP = HumanProportions;
  57. const QVector3D &origin = torso.origin;
  58. const QVector3D &right = torso.right;
  59. const QVector3D &up = torso.up;
  60. const QVector3D &forward = torso.forward;
  61. float const torso_r = torso.radius * m_config.torso_scale;
  62. float const torso_depth = (torso.depth > 0.0F)
  63. ? torso.depth * m_config.chest_depth_scale
  64. : torso.radius * m_config.chest_depth_scale;
  65. auto mapTorsoY = [&](float spec_y) {
  66. float const delta = spec_y - HP::SHOULDER_Y;
  67. return origin.y() + delta;
  68. };
  69. float const y_top = mapTorsoY(HP::SHOULDER_Y + 0.02F);
  70. float const y_mid_chest = mapTorsoY((HP::SHOULDER_Y + HP::CHEST_Y) * 0.5F);
  71. float const y_bottom_chest = mapTorsoY(HP::CHEST_Y);
  72. float const y_waist = mapTorsoY(HP::WAIST_Y + 0.06F);
  73. float const shoulder_width = torso_r * m_config.shoulder_width_scale;
  74. float const chest_width = torso_r * 1.15F;
  75. float const waist_width = torso_r * m_config.waist_taper;
  76. float const chest_depth_front = std::max(0.04F, torso_depth * 1.05F);
  77. float const chest_depth_back = std::max(0.03F, torso_depth * 0.75F);
  78. constexpr int segments = 16;
  79. constexpr float pi = std::numbers::pi_v<float>;
  80. auto createTorsoSegment = [&](float y_pos, float width_scale,
  81. float depth_front, float depth_back,
  82. const QVector3D &color) {
  83. for (int i = 0; i < segments; ++i) {
  84. float const angle1 = (static_cast<float>(i) / segments) * 2.0F * pi;
  85. float const angle2 = (static_cast<float>(i + 1) / segments) * 2.0F * pi;
  86. float const sin1 = std::sin(angle1);
  87. float const cos1 = std::cos(angle1);
  88. float const sin2 = std::sin(angle2);
  89. float const cos2 = std::cos(angle2);
  90. auto getRadiusAtAngle = [&](float angle_rad) -> float {
  91. float const cos_a = std::cos(angle_rad);
  92. float const abs_cos = std::abs(cos_a);
  93. float depth = (cos_a > 0.0F) ? depth_front : depth_back;
  94. constexpr float BASE_SHOULDER_SCALE = 1.0F;
  95. constexpr float SHOULDER_VARIATION_FACTOR = 0.15F;
  96. float const shoulder_bias =
  97. BASE_SHOULDER_SCALE +
  98. SHOULDER_VARIATION_FACTOR * std::abs(std::sin(angle_rad));
  99. return width_scale * shoulder_bias * (abs_cos * 0.3F + 0.7F * depth);
  100. };
  101. float const r1 = getRadiusAtAngle(angle1);
  102. float const r2 = getRadiusAtAngle(angle2);
  103. QVector3D const p1 = origin + right * (r1 * sin1) +
  104. forward * (r1 * cos1) + up * (y_pos - origin.y());
  105. QVector3D const p2 = origin + right * (r2 * sin2) +
  106. forward * (r2 * cos2) + up * (y_pos - origin.y());
  107. float const seg_r = (r1 + r2) * 0.5F * 0.08F;
  108. submitter.mesh(get_unit_cylinder(),
  109. cylinder_between(ctx.model, p1, p2, seg_r), color, nullptr,
  110. 1.0F);
  111. }
  112. };
  113. createTorsoSegment(y_top, shoulder_width, chest_depth_front, chest_depth_back,
  114. steel_color);
  115. createTorsoSegment(y_mid_chest, chest_width, chest_depth_front,
  116. chest_depth_back, steel_color * 0.99F);
  117. createTorsoSegment(y_bottom_chest, chest_width * 0.98F,
  118. chest_depth_front * 0.95F, chest_depth_back * 0.95F,
  119. steel_color * 0.98F);
  120. createTorsoSegment(y_waist, waist_width, chest_depth_front * 0.90F,
  121. chest_depth_back * 0.90F, steel_color * 0.97F);
  122. auto connectSegments = [&](float y1, float y2, float width1, float width2) {
  123. for (int i = 0; i < segments / 2; ++i) {
  124. float const angle = (static_cast<float>(i) / (segments / 2)) * 2.0F * pi;
  125. float const sin_a = std::sin(angle);
  126. float const cos_a = std::cos(angle);
  127. float const depth1 =
  128. (cos_a > 0.0F) ? chest_depth_front : chest_depth_back;
  129. float const depth2 =
  130. (cos_a > 0.0F) ? chest_depth_front * 0.95F : chest_depth_back * 0.95F;
  131. float const r1 = width1 * depth1;
  132. float const r2 = width2 * depth2;
  133. QVector3D const top = origin + right * (r1 * sin_a) +
  134. forward * (r1 * cos_a) + up * (y1 - origin.y());
  135. QVector3D const bot = origin + right * (r2 * sin_a) +
  136. forward * (r2 * cos_a) + up * (y2 - origin.y());
  137. submitter.mesh(get_unit_cylinder(),
  138. cylinder_between(ctx.model, top, bot, torso_r * 0.06F),
  139. steel_color * 0.96F, nullptr, 1.0F);
  140. }
  141. };
  142. connectSegments(y_top, y_mid_chest, shoulder_width, chest_width);
  143. connectSegments(y_mid_chest, y_bottom_chest, chest_width,
  144. chest_width * 0.98F);
  145. connectSegments(y_bottom_chest, y_waist, chest_width * 0.98F, waist_width);
  146. auto draw_rivet = [&](const QVector3D &pos) {
  147. QMatrix4x4 m = ctx.model;
  148. m.translate(pos);
  149. m.scale(0.012F);
  150. submitter.mesh(get_unit_sphere(), m, brass_color, nullptr, 1.0F);
  151. };
  152. constexpr float RIVET_POSITION_SCALE = 0.92F;
  153. for (int i = 0; i < 8; ++i) {
  154. float const angle = (static_cast<float>(i) / 8.0F) * 2.0F * pi;
  155. float const x = chest_width * std::sin(angle) * chest_depth_front *
  156. RIVET_POSITION_SCALE;
  157. float const z = chest_width * std::cos(angle) * chest_depth_front *
  158. RIVET_POSITION_SCALE;
  159. draw_rivet(origin + right * x + forward * z +
  160. up * (y_mid_chest + 0.08F - origin.y()));
  161. }
  162. }
  163. void TunicRenderer::renderPauldrons(const DrawContext &ctx,
  164. const BodyFrames &frames,
  165. const QVector3D &steel_color,
  166. const QVector3D &brass_color,
  167. ISubmitter &submitter) {
  168. using HP = HumanProportions;
  169. auto draw_pauldron = [&](const QVector3D &shoulder,
  170. const QVector3D &outward) {
  171. float const upper_arm_r = HP::UPPER_ARM_R;
  172. for (int i = 0; i < 4; ++i) {
  173. float const seg_y = shoulder.y() + 0.04F - static_cast<float>(i) * 0.045F;
  174. float const seg_r = upper_arm_r * (2.5F - static_cast<float>(i) * 0.12F);
  175. QVector3D seg_pos =
  176. shoulder + outward * (0.02F + static_cast<float>(i) * 0.008F);
  177. seg_pos.setY(seg_y);
  178. submitter.mesh(get_unit_sphere(), sphere_at(ctx.model, seg_pos, seg_r),
  179. i == 0
  180. ? steel_color * 1.05F
  181. : steel_color * (1.0F - static_cast<float>(i) * 0.03F),
  182. nullptr, 1.0F);
  183. if (i < 3) {
  184. QMatrix4x4 m = ctx.model;
  185. m.translate(seg_pos + QVector3D(0, 0.015F, 0.03F));
  186. m.scale(0.012F);
  187. submitter.mesh(get_unit_sphere(), m, brass_color, nullptr, 1.0F);
  188. }
  189. }
  190. };
  191. QVector3D const shoulder_right = frames.shoulder_r.origin;
  192. QVector3D const shoulder_left = frames.shoulder_l.origin;
  193. QVector3D const right_axis = frames.torso.right;
  194. draw_pauldron(shoulder_left, -right_axis);
  195. draw_pauldron(shoulder_right, right_axis);
  196. }
  197. void TunicRenderer::renderGorget(const DrawContext &ctx,
  198. const AttachmentFrame &torso, float y_top,
  199. const QVector3D &steel_color,
  200. const QVector3D &brass_color,
  201. ISubmitter &submitter) {
  202. using HP = HumanProportions;
  203. QVector3D const gorget_top(torso.origin.x(), y_top + 0.025F,
  204. torso.origin.z());
  205. QVector3D const gorget_bot(torso.origin.x(), y_top - 0.012F,
  206. torso.origin.z());
  207. submitter.mesh(get_unit_cylinder(),
  208. cylinder_between(ctx.model, gorget_bot, gorget_top,
  209. HP::NECK_RADIUS * 2.6F),
  210. steel_color * 1.08F, nullptr, 1.0F);
  211. QVector3D const a = gorget_top + QVector3D(0, 0.005F, 0);
  212. QVector3D const b = gorget_top - QVector3D(0, 0.005F, 0);
  213. submitter.mesh(get_unit_cylinder(),
  214. cylinder_between(ctx.model, a, b, HP::NECK_RADIUS * 2.62F),
  215. brass_color, nullptr, 1.0F);
  216. }
  217. void TunicRenderer::renderBelt(const DrawContext &ctx,
  218. const AttachmentFrame &waist,
  219. const QVector3D &steel_color,
  220. const QVector3D &brass_color,
  221. ISubmitter &submitter) {
  222. using HP = HumanProportions;
  223. float const waist_r = waist.radius * m_config.waist_taper;
  224. auto waistY = [&](float spec_y) {
  225. float const delta = spec_y - HP::WAIST_Y;
  226. return waist.origin.y() + delta;
  227. };
  228. float const y_center = waistY(HP::WAIST_Y + 0.02F);
  229. QVector3D const belt_top(waist.origin.x(), y_center + 0.02F,
  230. waist.origin.z());
  231. QVector3D const belt_bot(waist.origin.x(), y_center - 0.02F,
  232. waist.origin.z());
  233. submitter.mesh(
  234. get_unit_cylinder(),
  235. cylinder_between(ctx.model, belt_bot, belt_top, waist_r * 1.08F),
  236. steel_color * 0.94F, nullptr, 1.0F);
  237. QVector3D const trim_top = belt_top + QVector3D(0, 0.005F, 0);
  238. QVector3D const trim_bot = belt_bot - QVector3D(0, 0.005F, 0);
  239. submitter.mesh(
  240. get_unit_cylinder(),
  241. cylinder_between(ctx.model, trim_bot, trim_top, waist_r * 1.12F),
  242. brass_color * 0.95F, nullptr, 1.0F);
  243. }
  244. } // namespace Render::GL