| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- #include "tunic_renderer.h"
- #include "../../geom/transforms.h"
- #include "../../gl/primitives.h"
- #include "../../humanoid/humanoid_math.h"
- #include "../../humanoid/humanoid_specs.h"
- #include "../../humanoid/rig.h"
- #include "../../humanoid/style_palette.h"
- #include "../../submitter.h"
- #include <QMatrix4x4>
- #include <QVector3D>
- #include <algorithm>
- #include <cmath>
- #include <numbers>
- namespace Render::GL {
- using Render::Geom::cone_from_to;
- using Render::Geom::cylinder_between;
- using Render::Geom::sphere_at;
- using Render::GL::Humanoid::saturate_color;
- TunicRenderer::TunicRenderer(const TunicConfig &config) : m_config(config) {}
- void TunicRenderer::render(const DrawContext &ctx, const BodyFrames &frames,
- const HumanoidPalette &palette,
- const HumanoidAnimationContext &anim,
- ISubmitter &submitter) {
- (void)anim;
- const AttachmentFrame &torso = frames.torso;
- const AttachmentFrame &waist = frames.waist;
- if (torso.radius <= 0.0F) {
- return;
- }
- QVector3D const steel_color =
- saturate_color(palette.metal * QVector3D(0.95F, 0.96F, 1.0F));
- QVector3D const brass_color =
- saturate_color(palette.metal * QVector3D(1.3F, 1.1F, 0.7F));
- using HP = HumanProportions;
- auto torsoY = [&](float spec_y) {
- float const delta = spec_y - HP::SHOULDER_Y;
- return torso.origin.y() + delta;
- };
- float const y_top = torsoY(HP::SHOULDER_Y + 0.02F);
- renderTorsoArmor(ctx, torso, steel_color, brass_color, submitter);
- if (m_config.include_pauldrons) {
- renderPauldrons(ctx, frames, steel_color, brass_color, submitter);
- }
- if (m_config.include_gorget) {
- renderGorget(ctx, torso, y_top, steel_color, brass_color, submitter);
- }
- if (m_config.include_belt) {
- renderBelt(ctx, waist, steel_color, brass_color, submitter);
- }
- }
- void TunicRenderer::renderTorsoArmor(const DrawContext &ctx,
- const AttachmentFrame &torso,
- const QVector3D &steel_color,
- const QVector3D &brass_color,
- ISubmitter &submitter) {
- using HP = HumanProportions;
- const QVector3D &origin = torso.origin;
- const QVector3D &right = torso.right;
- const QVector3D &up = torso.up;
- const QVector3D &forward = torso.forward;
- float const torso_r = torso.radius * m_config.torso_scale;
- float const torso_depth = (torso.depth > 0.0F)
- ? torso.depth * m_config.chest_depth_scale
- : torso.radius * m_config.chest_depth_scale;
- auto mapTorsoY = [&](float spec_y) {
- float const delta = spec_y - HP::SHOULDER_Y;
- return origin.y() + delta;
- };
- float const y_top = mapTorsoY(HP::SHOULDER_Y + 0.02F);
- float const y_mid_chest = mapTorsoY((HP::SHOULDER_Y + HP::CHEST_Y) * 0.5F);
- float const y_bottom_chest = mapTorsoY(HP::CHEST_Y);
- float const y_waist = mapTorsoY(HP::WAIST_Y + 0.06F);
- float const shoulder_width = torso_r * m_config.shoulder_width_scale;
- float const chest_width = torso_r * 1.15F;
- float const waist_width = torso_r * m_config.waist_taper;
- float const chest_depth_front = std::max(0.04F, torso_depth * 1.05F);
- float const chest_depth_back = std::max(0.03F, torso_depth * 0.75F);
- constexpr int segments = 16;
- constexpr float pi = std::numbers::pi_v<float>;
- auto createTorsoSegment = [&](float y_pos, float width_scale,
- float depth_front, float depth_back,
- const QVector3D &color) {
- for (int i = 0; i < segments; ++i) {
- float const angle1 = (static_cast<float>(i) / segments) * 2.0F * pi;
- float const angle2 = (static_cast<float>(i + 1) / segments) * 2.0F * pi;
- float const sin1 = std::sin(angle1);
- float const cos1 = std::cos(angle1);
- float const sin2 = std::sin(angle2);
- float const cos2 = std::cos(angle2);
- auto getRadiusAtAngle = [&](float angle_rad) -> float {
- float const cos_a = std::cos(angle_rad);
- float const abs_cos = std::abs(cos_a);
- float depth = (cos_a > 0.0F) ? depth_front : depth_back;
- constexpr float BASE_SHOULDER_SCALE = 1.0F;
- constexpr float SHOULDER_VARIATION_FACTOR = 0.15F;
- float const shoulder_bias =
- BASE_SHOULDER_SCALE +
- SHOULDER_VARIATION_FACTOR * std::abs(std::sin(angle_rad));
- return width_scale * shoulder_bias * (abs_cos * 0.3F + 0.7F * depth);
- };
- float const r1 = getRadiusAtAngle(angle1);
- float const r2 = getRadiusAtAngle(angle2);
- QVector3D const p1 = origin + right * (r1 * sin1) +
- forward * (r1 * cos1) + up * (y_pos - origin.y());
- QVector3D const p2 = origin + right * (r2 * sin2) +
- forward * (r2 * cos2) + up * (y_pos - origin.y());
- float const seg_r = (r1 + r2) * 0.5F * 0.08F;
- submitter.mesh(get_unit_cylinder(),
- cylinder_between(ctx.model, p1, p2, seg_r), color, nullptr,
- 1.0F);
- }
- };
- createTorsoSegment(y_top, shoulder_width, chest_depth_front, chest_depth_back,
- steel_color);
- createTorsoSegment(y_mid_chest, chest_width, chest_depth_front,
- chest_depth_back, steel_color * 0.99F);
- createTorsoSegment(y_bottom_chest, chest_width * 0.98F,
- chest_depth_front * 0.95F, chest_depth_back * 0.95F,
- steel_color * 0.98F);
- createTorsoSegment(y_waist, waist_width, chest_depth_front * 0.90F,
- chest_depth_back * 0.90F, steel_color * 0.97F);
- auto connectSegments = [&](float y1, float y2, float width1, float width2) {
- for (int i = 0; i < segments / 2; ++i) {
- float const angle = (static_cast<float>(i) / (segments / 2)) * 2.0F * pi;
- float const sin_a = std::sin(angle);
- float const cos_a = std::cos(angle);
- float const depth1 =
- (cos_a > 0.0F) ? chest_depth_front : chest_depth_back;
- float const depth2 =
- (cos_a > 0.0F) ? chest_depth_front * 0.95F : chest_depth_back * 0.95F;
- float const r1 = width1 * depth1;
- float const r2 = width2 * depth2;
- QVector3D const top = origin + right * (r1 * sin_a) +
- forward * (r1 * cos_a) + up * (y1 - origin.y());
- QVector3D const bot = origin + right * (r2 * sin_a) +
- forward * (r2 * cos_a) + up * (y2 - origin.y());
- submitter.mesh(get_unit_cylinder(),
- cylinder_between(ctx.model, top, bot, torso_r * 0.06F),
- steel_color * 0.96F, nullptr, 1.0F);
- }
- };
- connectSegments(y_top, y_mid_chest, shoulder_width, chest_width);
- connectSegments(y_mid_chest, y_bottom_chest, chest_width,
- chest_width * 0.98F);
- connectSegments(y_bottom_chest, y_waist, chest_width * 0.98F, waist_width);
- auto draw_rivet = [&](const QVector3D &pos) {
- QMatrix4x4 m = ctx.model;
- m.translate(pos);
- m.scale(0.012F);
- submitter.mesh(get_unit_sphere(), m, brass_color, nullptr, 1.0F);
- };
- constexpr float RIVET_POSITION_SCALE = 0.92F;
- for (int i = 0; i < 8; ++i) {
- float const angle = (static_cast<float>(i) / 8.0F) * 2.0F * pi;
- float const x = chest_width * std::sin(angle) * chest_depth_front *
- RIVET_POSITION_SCALE;
- float const z = chest_width * std::cos(angle) * chest_depth_front *
- RIVET_POSITION_SCALE;
- draw_rivet(origin + right * x + forward * z +
- up * (y_mid_chest + 0.08F - origin.y()));
- }
- }
- void TunicRenderer::renderPauldrons(const DrawContext &ctx,
- const BodyFrames &frames,
- const QVector3D &steel_color,
- const QVector3D &brass_color,
- ISubmitter &submitter) {
- using HP = HumanProportions;
- auto draw_pauldron = [&](const QVector3D &shoulder,
- const QVector3D &outward) {
- float const upper_arm_r = HP::UPPER_ARM_R;
- for (int i = 0; i < 4; ++i) {
- float const seg_y = shoulder.y() + 0.04F - static_cast<float>(i) * 0.045F;
- float const seg_r = upper_arm_r * (2.5F - static_cast<float>(i) * 0.12F);
- QVector3D seg_pos =
- shoulder + outward * (0.02F + static_cast<float>(i) * 0.008F);
- seg_pos.setY(seg_y);
- submitter.mesh(get_unit_sphere(), sphere_at(ctx.model, seg_pos, seg_r),
- i == 0
- ? steel_color * 1.05F
- : steel_color * (1.0F - static_cast<float>(i) * 0.03F),
- nullptr, 1.0F);
- if (i < 3) {
- QMatrix4x4 m = ctx.model;
- m.translate(seg_pos + QVector3D(0, 0.015F, 0.03F));
- m.scale(0.012F);
- submitter.mesh(get_unit_sphere(), m, brass_color, nullptr, 1.0F);
- }
- }
- };
- QVector3D const shoulder_right = frames.shoulder_r.origin;
- QVector3D const shoulder_left = frames.shoulder_l.origin;
- QVector3D const right_axis = frames.torso.right;
- draw_pauldron(shoulder_left, -right_axis);
- draw_pauldron(shoulder_right, right_axis);
- }
- void TunicRenderer::renderGorget(const DrawContext &ctx,
- const AttachmentFrame &torso, float y_top,
- const QVector3D &steel_color,
- const QVector3D &brass_color,
- ISubmitter &submitter) {
- using HP = HumanProportions;
- QVector3D const gorget_top(torso.origin.x(), y_top + 0.025F,
- torso.origin.z());
- QVector3D const gorget_bot(torso.origin.x(), y_top - 0.012F,
- torso.origin.z());
- submitter.mesh(get_unit_cylinder(),
- cylinder_between(ctx.model, gorget_bot, gorget_top,
- HP::NECK_RADIUS * 2.6F),
- steel_color * 1.08F, nullptr, 1.0F);
- QVector3D const a = gorget_top + QVector3D(0, 0.005F, 0);
- QVector3D const b = gorget_top - QVector3D(0, 0.005F, 0);
- submitter.mesh(get_unit_cylinder(),
- cylinder_between(ctx.model, a, b, HP::NECK_RADIUS * 2.62F),
- brass_color, nullptr, 1.0F);
- }
- void TunicRenderer::renderBelt(const DrawContext &ctx,
- const AttachmentFrame &waist,
- const QVector3D &steel_color,
- const QVector3D &brass_color,
- ISubmitter &submitter) {
- using HP = HumanProportions;
- float const waist_r = waist.radius * m_config.waist_taper;
- auto waistY = [&](float spec_y) {
- float const delta = spec_y - HP::WAIST_Y;
- return waist.origin.y() + delta;
- };
- float const y_center = waistY(HP::WAIST_Y + 0.02F);
- QVector3D const belt_top(waist.origin.x(), y_center + 0.02F,
- waist.origin.z());
- QVector3D const belt_bot(waist.origin.x(), y_center - 0.02F,
- waist.origin.z());
- submitter.mesh(
- get_unit_cylinder(),
- cylinder_between(ctx.model, belt_bot, belt_top, waist_r * 1.08F),
- steel_color * 0.94F, nullptr, 1.0F);
- QVector3D const trim_top = belt_top + QVector3D(0, 0.005F, 0);
- QVector3D const trim_bot = belt_bot - QVector3D(0, 0.005F, 0);
- submitter.mesh(
- get_unit_cylinder(),
- cylinder_between(ctx.model, trim_bot, trim_top, waist_r * 1.12F),
- brass_color * 0.95F, nullptr, 1.0F);
- }
- } // namespace Render::GL
|