|
@@ -8,10 +8,10 @@
|
|
|
#include "../../../gl/primitives.h"
|
|
#include "../../../gl/primitives.h"
|
|
|
#include "../../../gl/render_constants.h"
|
|
#include "../../../gl/render_constants.h"
|
|
|
#include "../../../gl/shader.h"
|
|
#include "../../../gl/shader.h"
|
|
|
-#include "../../../humanoid/rig.h"
|
|
|
|
|
-#include "../../../humanoid/style_palette.h"
|
|
|
|
|
#include "../../../humanoid/humanoid_math.h"
|
|
#include "../../../humanoid/humanoid_math.h"
|
|
|
#include "../../../humanoid/humanoid_specs.h"
|
|
#include "../../../humanoid/humanoid_specs.h"
|
|
|
|
|
+#include "../../../humanoid/rig.h"
|
|
|
|
|
+#include "../../../humanoid/style_palette.h"
|
|
|
#include "../../../palette.h"
|
|
#include "../../../palette.h"
|
|
|
#include "../../../scene_renderer.h"
|
|
#include "../../../scene_renderer.h"
|
|
|
#include "../../../submitter.h"
|
|
#include "../../../submitter.h"
|
|
@@ -57,7 +57,7 @@ void ensure_archer_styles_registered() {
|
|
|
constexpr float k_team_mix_weight = 0.65F;
|
|
constexpr float k_team_mix_weight = 0.65F;
|
|
|
constexpr float k_style_mix_weight = 0.35F;
|
|
constexpr float k_style_mix_weight = 0.35F;
|
|
|
|
|
|
|
|
-} // namespace
|
|
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
void register_archer_style(const std::string &nation_id,
|
|
void register_archer_style(const std::string &nation_id,
|
|
|
const ArcherStyleConfig &style) {
|
|
const ArcherStyleConfig &style) {
|
|
@@ -87,8 +87,9 @@ struct ArcherExtras {
|
|
|
class ArcherRenderer : public HumanoidRendererBase {
|
|
class ArcherRenderer : public HumanoidRendererBase {
|
|
|
public:
|
|
public:
|
|
|
auto getProportionScaling() const -> QVector3D override {
|
|
auto getProportionScaling() const -> QVector3D override {
|
|
|
-
|
|
|
|
|
- return {0.94F, 1.01F, 0.96F};
|
|
|
|
|
|
|
+ // X = width (shoulders/chest WIDE), Y = height, Z = depth (front-to-back NARROW)
|
|
|
|
|
+ // Human torso is much wider than it is deep!
|
|
|
|
|
+ return {1.15F, 1.02F, 0.75F}; // Wide shoulders, narrow chest depth
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void getVariant(const DrawContext &ctx, uint32_t seed,
|
|
void getVariant(const DrawContext &ctx, uint32_t seed,
|
|
@@ -97,6 +98,73 @@ public:
|
|
|
v.palette = makeHumanoidPalette(team_tint, seed);
|
|
v.palette = makeHumanoidPalette(team_tint, seed);
|
|
|
auto const &style = resolve_style(ctx);
|
|
auto const &style = resolve_style(ctx);
|
|
|
apply_palette_overrides(style, team_tint, v);
|
|
apply_palette_overrides(style, team_tint, v);
|
|
|
|
|
+
|
|
|
|
|
+ auto nextRand = [](uint32_t &s) -> float {
|
|
|
|
|
+ s = s * 1664525U + 1013904223U;
|
|
|
|
|
+ return float(s & 0x7FFFFFU) / float(0x7FFFFFU);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ uint32_t beard_seed = seed ^ 0xBEAD01U;
|
|
|
|
|
+
|
|
|
|
|
+ float const beard_chance = nextRand(beard_seed);
|
|
|
|
|
+ bool const wants_beard = style.force_beard || (beard_chance < 0.85F);
|
|
|
|
|
+
|
|
|
|
|
+ if (wants_beard) {
|
|
|
|
|
+
|
|
|
|
|
+ float const style_roll = nextRand(beard_seed);
|
|
|
|
|
+
|
|
|
|
|
+ if (style_roll < 0.50F) {
|
|
|
|
|
+
|
|
|
|
|
+ v.facialHair.style = FacialHairStyle::FullBeard;
|
|
|
|
|
+ v.facialHair.length = 0.9F + nextRand(beard_seed) * 0.6F;
|
|
|
|
|
+ } else if (style_roll < 0.75F) {
|
|
|
|
|
+
|
|
|
|
|
+ v.facialHair.style = FacialHairStyle::LongBeard;
|
|
|
|
|
+ v.facialHair.length = 1.2F + nextRand(beard_seed) * 0.8F;
|
|
|
|
|
+ } else if (style_roll < 0.90F) {
|
|
|
|
|
+
|
|
|
|
|
+ v.facialHair.style = FacialHairStyle::ShortBeard;
|
|
|
|
|
+ v.facialHair.length = 0.8F + nextRand(beard_seed) * 0.4F;
|
|
|
|
|
+ } else {
|
|
|
|
|
+
|
|
|
|
|
+ v.facialHair.style = FacialHairStyle::Goatee;
|
|
|
|
|
+ v.facialHair.length = 0.9F + nextRand(beard_seed) * 0.5F;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ float const color_roll = nextRand(beard_seed);
|
|
|
|
|
+ if (color_roll < 0.60F) {
|
|
|
|
|
+ // Dark brown/black (most common)
|
|
|
|
|
+ v.facialHair.color = QVector3D(0.18F + nextRand(beard_seed) * 0.10F,
|
|
|
|
|
+ 0.14F + nextRand(beard_seed) * 0.08F,
|
|
|
|
|
+ 0.10F + nextRand(beard_seed) * 0.06F);
|
|
|
|
|
+ } else if (color_roll < 0.85F) {
|
|
|
|
|
+ // Medium brown
|
|
|
|
|
+ v.facialHair.color = QVector3D(0.30F + nextRand(beard_seed) * 0.12F,
|
|
|
|
|
+ 0.24F + nextRand(beard_seed) * 0.10F,
|
|
|
|
|
+ 0.16F + nextRand(beard_seed) * 0.08F);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Reddish-brown (Libyan/Berber influence)
|
|
|
|
|
+ v.facialHair.color = QVector3D(0.35F + nextRand(beard_seed) * 0.10F,
|
|
|
|
|
+ 0.20F + nextRand(beard_seed) * 0.08F,
|
|
|
|
|
+ 0.12F + nextRand(beard_seed) * 0.06F);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ v.facialHair.thickness = 0.85F + nextRand(beard_seed) * 0.35F;
|
|
|
|
|
+ v.facialHair.coverage = 0.75F + nextRand(beard_seed) * 0.25F;
|
|
|
|
|
+
|
|
|
|
|
+ if (nextRand(beard_seed) < 0.10F) {
|
|
|
|
|
+ v.facialHair.greyness = 0.15F + nextRand(beard_seed) * 0.35F;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ v.facialHair.greyness = 0.0F;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+
|
|
|
|
|
+ v.facialHair.style = FacialHairStyle::None;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ v.muscularity = 0.95F + nextRand(beard_seed) * 0.25F;
|
|
|
|
|
+ v.scarring = nextRand(beard_seed) * 0.30F;
|
|
|
|
|
+ v.weathering = 0.40F + nextRand(beard_seed) * 0.40F;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void customizePose(const DrawContext &,
|
|
void customizePose(const DrawContext &,
|
|
@@ -330,86 +398,100 @@ public:
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- auto draw_montefortino = [&](const QVector3D &base_color) {
|
|
|
|
|
- QVector3D bronze =
|
|
|
|
|
- saturate_color(base_color * QVector3D(1.14F, 1.00F, 0.76F));
|
|
|
|
|
- QVector3D tinned_highlight =
|
|
|
|
|
- saturate_color(base_color * QVector3D(1.38F, 1.36F, 1.44F));
|
|
|
|
|
- QVector3D patina =
|
|
|
|
|
- saturate_color(base_color * QVector3D(0.92F, 1.05F, 1.04F));
|
|
|
|
|
- QVector3D leather_band =
|
|
|
|
|
- saturate_color(v.palette.leather * QVector3D(1.12F, 0.98F, 0.84F));
|
|
|
|
|
-
|
|
|
|
|
- float const head_r = pose.headR;
|
|
|
|
|
- QVector3D const bowl_center(0.0F, pose.headPos.y() + head_r * 0.72F,
|
|
|
|
|
- 0.0F);
|
|
|
|
|
-
|
|
|
|
|
- QMatrix4x4 bowl = ctx.model;
|
|
|
|
|
- bowl.translate(bowl_center);
|
|
|
|
|
- bowl.scale(head_r * 1.08F, head_r * 1.24F, head_r * 1.02F);
|
|
|
|
|
- bowl.rotate(-5.0F, 1.0F, 0.0F, 0.0F);
|
|
|
|
|
- out.mesh(getUnitSphere(), bowl, bronze, nullptr, 1.0F);
|
|
|
|
|
-
|
|
|
|
|
- QMatrix4x4 ridge = ctx.model;
|
|
|
|
|
- ridge.translate(QVector3D(0.0F, pose.headPos.y() + head_r * 1.00F, 0.0F));
|
|
|
|
|
- ridge.scale(head_r * 0.20F, head_r * 0.48F, head_r * 0.20F);
|
|
|
|
|
- out.mesh(getUnitCone(), ridge, patina, nullptr, 1.0F);
|
|
|
|
|
-
|
|
|
|
|
- QMatrix4x4 knob = ctx.model;
|
|
|
|
|
- knob.translate(QVector3D(0.0F, pose.headPos.y() + head_r * 1.38F, 0.0F));
|
|
|
|
|
- knob.scale(head_r * 0.22F, head_r * 0.32F, head_r * 0.22F);
|
|
|
|
|
- out.mesh(getUnitSphere(), knob, tinned_highlight, nullptr, 1.0F);
|
|
|
|
|
-
|
|
|
|
|
- QVector3D const brow_top(0.0F, pose.headPos.y() + head_r * 0.58F, 0.0F);
|
|
|
|
|
- QVector3D const brow_bottom(0.0F, pose.headPos.y() + head_r * 0.46F,
|
|
|
|
|
- 0.0F);
|
|
|
|
|
- QMatrix4x4 brow =
|
|
|
|
|
- cylinderBetween(ctx.model, brow_bottom, brow_top, head_r * 1.20F);
|
|
|
|
|
- brow.scale(1.04F, 1.0F, 0.86F);
|
|
|
|
|
- out.mesh(getUnitCylinder(), brow, leather_band, nullptr, 1.0F);
|
|
|
|
|
-
|
|
|
|
|
- QVector3D const rim_upper(0.0F, pose.headPos.y() + head_r * 0.44F, 0.0F);
|
|
|
|
|
- QVector3D const rim_lower(0.0F, pose.headPos.y() + head_r * 0.34F, 0.0F);
|
|
|
|
|
- QMatrix4x4 rim =
|
|
|
|
|
- cylinderBetween(ctx.model, rim_lower, rim_upper, head_r * 1.30F);
|
|
|
|
|
- rim.scale(1.06F, 1.0F, 0.90F);
|
|
|
|
|
- out.mesh(getUnitCylinder(), rim, bronze * QVector3D(0.94F, 0.92F, 0.88F),
|
|
|
|
|
- nullptr, 1.0F);
|
|
|
|
|
-
|
|
|
|
|
- QVector3D const neck_base(0.0F, pose.headPos.y() + head_r * 0.32F,
|
|
|
|
|
- -head_r * 0.68F);
|
|
|
|
|
- QVector3D const neck_drop(0.0F, pose.headPos.y() - head_r * 0.48F,
|
|
|
|
|
- -head_r * 0.96F);
|
|
|
|
|
- QMatrix4x4 neck =
|
|
|
|
|
- coneFromTo(ctx.model, neck_drop, neck_base, head_r * 1.34F);
|
|
|
|
|
- neck.scale(1.0F, 1.0F, 0.94F);
|
|
|
|
|
- out.mesh(getUnitCone(), neck, bronze * 0.96F, nullptr, 1.0F);
|
|
|
|
|
-
|
|
|
|
|
- auto cheek_plate = [&](float sign) {
|
|
|
|
|
- QVector3D const hinge(sign * head_r * 0.80F,
|
|
|
|
|
- pose.headPos.y() + head_r * 0.38F,
|
|
|
|
|
- head_r * 0.38F);
|
|
|
|
|
- QVector3D const lobe =
|
|
|
|
|
- hinge + QVector3D(sign * head_r * 0.20F, -head_r * 0.82F, 0.02F);
|
|
|
|
|
- QMatrix4x4 cheek = coneFromTo(ctx.model, lobe, hinge, head_r * 0.46F);
|
|
|
|
|
- cheek.scale(0.60F, 1.0F, 0.42F);
|
|
|
|
|
- out.mesh(getUnitCone(), cheek, patina, nullptr, 1.0F);
|
|
|
|
|
- };
|
|
|
|
|
- cheek_plate(+1.0F);
|
|
|
|
|
- cheek_plate(-1.0F);
|
|
|
|
|
-
|
|
|
|
|
- QVector3D const crest_front(0.0F, pose.headPos.y() + head_r * 0.96F,
|
|
|
|
|
- head_r * 0.82F);
|
|
|
|
|
- QVector3D const crest_back(0.0F, pose.headPos.y() + head_r * 0.96F,
|
|
|
|
|
- -head_r * 0.90F);
|
|
|
|
|
- QMatrix4x4 crest =
|
|
|
|
|
- cylinderBetween(ctx.model, crest_back, crest_front, head_r * 0.14F);
|
|
|
|
|
- crest.scale(0.54F, 1.0F, 1.0F);
|
|
|
|
|
- out.mesh(getUnitCylinder(), crest,
|
|
|
|
|
- tinned_highlight * QVector3D(0.94F, 0.96F, 1.02F), nullptr,
|
|
|
|
|
- 1.0F);
|
|
|
|
|
|
|
+ auto draw_montefortino = [&](const QVector3D &base_metal) {
|
|
|
|
|
+ const HeadFrame &head = pose.headFrame;
|
|
|
|
|
+ float const head_r = head.radius;
|
|
|
|
|
+ if (head_r <= 0.0F) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ auto headPoint = [&](const QVector3D &norm) -> QVector3D {
|
|
|
|
|
+ return headLocalPosition(head, norm);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ auto headTransform = [&](const QVector3D &norm, float scale) -> QMatrix4x4 {
|
|
|
|
|
+ return makeHeadLocalTransform(ctx.model, head, norm, scale);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ QVector3D bronze =
|
|
|
|
|
+ saturate_color(base_metal * QVector3D(1.22F, 1.04F, 0.70F));
|
|
|
|
|
+ QVector3D patina =
|
|
|
|
|
+ saturate_color(bronze * QVector3D(0.88F, 0.96F, 0.92F));
|
|
|
|
|
+ QVector3D tinned_highlight =
|
|
|
|
|
+ saturate_color(bronze * QVector3D(1.12F, 1.08F, 1.04F));
|
|
|
|
|
+ QVector3D leather_band =
|
|
|
|
|
+ saturate_color(v.palette.leatherDark * QVector3D(1.10F, 0.96F, 0.80F));
|
|
|
|
|
+
|
|
|
|
|
+ auto draw_leather_cap = [&]() {
|
|
|
|
|
+ QVector3D leather_brown = saturate_color(v.palette.leatherDark *
|
|
|
|
|
+ QVector3D(1.15F, 0.95F, 0.78F));
|
|
|
|
|
+ QVector3D leather_dark =
|
|
|
|
|
+ saturate_color(leather_brown * QVector3D(0.85F, 0.88F, 0.92F));
|
|
|
|
|
+ QVector3D bronze_stud =
|
|
|
|
|
+ saturate_color(v.palette.metal * QVector3D(1.20F, 1.02F, 0.70F));
|
|
|
|
|
+
|
|
|
|
|
+ // Cap sits on crown in head-local coordinates (0,0,0) = head center
|
|
|
|
|
+ QMatrix4x4 cap_transform = headTransform(QVector3D(0.0F, 0.70F, 0.0F), 1.0F);
|
|
|
|
|
+ cap_transform.scale(0.92F, 0.55F, 0.88F); // Non-uniform scaling for cap shape
|
|
|
|
|
+ out.mesh(getUnitSphere(), cap_transform, leather_brown, nullptr, 1.0F);
|
|
|
|
|
+
|
|
|
|
|
+ // Band at forehead level in head-local coordinates
|
|
|
|
|
+ QVector3D const band_top = headPoint(QVector3D(0.0F, 0.20F, 0.0F));
|
|
|
|
|
+ QVector3D const band_bot = headPoint(QVector3D(0.0F, 0.15F, 0.0F));
|
|
|
|
|
+
|
|
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
|
|
+ cylinderBetween(ctx.model, band_bot, band_top, head_r * 1.02F),
|
|
|
|
|
+ leather_dark, nullptr, 1.0F);
|
|
|
|
|
+
|
|
|
|
|
+ auto draw_stud = [&](float angle) {
|
|
|
|
|
+ QVector3D const stud_pos =
|
|
|
|
|
+ headPoint(QVector3D(std::sin(angle) * 1.03F, 0.175F,
|
|
|
|
|
+ std::cos(angle) * 1.03F));
|
|
|
|
|
+ out.mesh(getUnitSphere(),
|
|
|
|
|
+ sphereAt(ctx.model, stud_pos, head_r * 0.012F),
|
|
|
|
|
+ bronze_stud, nullptr, 1.0F);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ for (int i = 0; i < 4; ++i) {
|
|
|
|
|
+ float const angle = (i / 4.0F) * 2.0F * std::numbers::pi_v<float>;
|
|
|
|
|
+ draw_stud(angle);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ draw_leather_cap();
|
|
|
|
|
+
|
|
|
|
|
+ // Top knob
|
|
|
|
|
+ QMatrix4x4 top_knob = headTransform(QVector3D(0.0F, 0.88F, 0.0F), 0.18F);
|
|
|
|
|
+ out.mesh(getUnitSphere(), top_knob, tinned_highlight, nullptr, 1.0F);
|
|
|
|
|
+
|
|
|
|
|
+ // Brow reinforcement
|
|
|
|
|
+ QVector3D const brow_top = headPoint(QVector3D(0.0F, 0.55F, 0.0F));
|
|
|
|
|
+ QVector3D const brow_bot = headPoint(QVector3D(0.0F, 0.42F, 0.0F));
|
|
|
|
|
+ QMatrix4x4 brow =
|
|
|
|
|
+ cylinderBetween(ctx.model, brow_bot, brow_top, head_r * 1.20F);
|
|
|
|
|
+ brow.scale(1.04F, 1.0F, 0.86F);
|
|
|
|
|
+ out.mesh(getUnitCylinder(), brow, leather_band, nullptr, 1.0F);
|
|
|
|
|
+
|
|
|
|
|
+ // Rim
|
|
|
|
|
+ QVector3D const rim_upper = headPoint(QVector3D(0.0F, 0.40F, 0.0F));
|
|
|
|
|
+ QVector3D const rim_lower = headPoint(QVector3D(0.0F, 0.30F, 0.0F));
|
|
|
|
|
+ QMatrix4x4 rim =
|
|
|
|
|
+ cylinderBetween(ctx.model, rim_lower, rim_upper, head_r * 1.30F);
|
|
|
|
|
+ rim.scale(1.06F, 1.0F, 0.90F);
|
|
|
|
|
+ out.mesh(getUnitCylinder(), rim, bronze * QVector3D(0.94F, 0.92F, 0.88F),
|
|
|
|
|
+ nullptr, 1.0F);
|
|
|
|
|
+
|
|
|
|
|
+ // Crest
|
|
|
|
|
+ QVector3D const crest_front = headPoint(QVector3D(0.0F, 0.92F, 0.82F));
|
|
|
|
|
+ QVector3D const crest_back = headPoint(QVector3D(0.0F, 0.92F, -0.90F));
|
|
|
|
|
+ QMatrix4x4 crest =
|
|
|
|
|
+ cylinderBetween(ctx.model, crest_back, crest_front, head_r * 0.14F);
|
|
|
|
|
+ crest.scale(0.54F, 1.0F, 1.0F);
|
|
|
|
|
+ out.mesh(getUnitCylinder(), crest,
|
|
|
|
|
+ tinned_highlight * QVector3D(0.94F, 0.96F, 1.02F), nullptr, 1.0F);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
draw_montefortino(v.palette.metal);
|
|
draw_montefortino(v.palette.metal);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -424,126 +506,60 @@ public:
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Carthaginian archers wore simple tunics, not heavy armor
|
|
|
|
|
+ // Light linen or wool tunic in natural/earthy colors
|
|
|
|
|
+
|
|
|
float const waist_y = pose.pelvisPos.y();
|
|
float const waist_y = pose.pelvisPos.y();
|
|
|
- float const cuirass_top = y_top_cover + 0.02F;
|
|
|
|
|
- float const cuirass_bottom = waist_y - 0.10F;
|
|
|
|
|
-
|
|
|
|
|
- QVector3D linen =
|
|
|
|
|
- saturate_color(v.palette.cloth * QVector3D(1.12F, 1.04F, 0.88F));
|
|
|
|
|
- QVector3D bronze =
|
|
|
|
|
- saturate_color(v.palette.metal * QVector3D(1.18F, 1.02F, 0.74F));
|
|
|
|
|
- QVector3D bronze_shadow =
|
|
|
|
|
- saturate_color(bronze * QVector3D(0.90F, 0.94F, 0.98F));
|
|
|
|
|
- QVector3D tinned =
|
|
|
|
|
- saturate_color(v.palette.metal * QVector3D(1.36F, 1.36F, 1.42F));
|
|
|
|
|
- QVector3D leather =
|
|
|
|
|
- saturate_color(v.palette.leatherDark * QVector3D(1.06F, 0.98F, 0.84F));
|
|
|
|
|
-
|
|
|
|
|
- QVector3D const tunic_top(0.0F, cuirass_top + 0.04F, 0.0F);
|
|
|
|
|
- QVector3D const tunic_bot(0.0F, waist_y + 0.05F, 0.0F);
|
|
|
|
|
- QMatrix4x4 tunic =
|
|
|
|
|
- cylinderBetween(ctx.model, tunic_bot, tunic_top, torso_r * 0.94F);
|
|
|
|
|
- tunic.scale(1.02F, 1.0F, 0.90F);
|
|
|
|
|
- out.mesh(getUnitCylinder(), tunic, linen, nullptr, 1.0F);
|
|
|
|
|
-
|
|
|
|
|
- constexpr int k_scale_rows = 6;
|
|
|
|
|
- constexpr float k_tile_height = 0.085F;
|
|
|
|
|
- constexpr float k_tile_width = 0.11F;
|
|
|
|
|
- constexpr float k_tile_thickness = 0.020F;
|
|
|
|
|
- constexpr float k_row_overlap = 0.032F;
|
|
|
|
|
- constexpr float k_radial_push = 0.010F;
|
|
|
|
|
-
|
|
|
|
|
- auto draw_scale_tile = [&](const QVector3D ¢er, float yaw,
|
|
|
|
|
- const QVector3D &color, float height,
|
|
|
|
|
- float width_scale) {
|
|
|
|
|
- QVector3D const top = center + QVector3D(0.0F, height * 0.5F, 0.0F);
|
|
|
|
|
- QVector3D const bot = center - QVector3D(0.0F, height * 0.5F, 0.0F);
|
|
|
|
|
- float const radius = (k_tile_width * width_scale) * 0.5F;
|
|
|
|
|
- QMatrix4x4 plate = cylinderBetween(ctx.model, bot, top, radius);
|
|
|
|
|
- float const yaw_deg = yaw * (180.0F / std::numbers::pi_v<float>);
|
|
|
|
|
- plate.rotate(yaw_deg, 0.0F, 1.0F, 0.0F);
|
|
|
|
|
- plate.scale(0.92F, 1.0F,
|
|
|
|
|
- (k_tile_thickness / (k_tile_width * width_scale)));
|
|
|
|
|
- out.mesh(getUnitCylinder(18), plate, color, nullptr, 1.0F);
|
|
|
|
|
-
|
|
|
|
|
- QVector3D const lip_top = bot + QVector3D(0.0F, height * 0.22F, 0.0F);
|
|
|
|
|
- QMatrix4x4 lip = cylinderBetween(ctx.model, bot, lip_top, radius * 0.92F);
|
|
|
|
|
- lip.rotate(yaw_deg, 0.0F, 1.0F, 0.0F);
|
|
|
|
|
- lip.scale(0.88F, 1.0F,
|
|
|
|
|
- (k_tile_thickness / (k_tile_width * width_scale)) * 0.72F);
|
|
|
|
|
- out.mesh(getUnitCylinder(16), lip, color * QVector3D(0.90F, 0.92F, 0.96F),
|
|
|
|
|
- nullptr, 1.0F);
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- auto emit_scale_band = [&](float center_angle, float span, int columns,
|
|
|
|
|
- float radius_scale) {
|
|
|
|
|
- float const start = center_angle - span * 0.5F;
|
|
|
|
|
- float const step = columns > 1 ? span / float(columns - 1) : 0.0F;
|
|
|
|
|
-
|
|
|
|
|
- for (int row = 0; row < k_scale_rows; ++row) {
|
|
|
|
|
- float const row_y = cuirass_top - row * (k_tile_height - k_row_overlap);
|
|
|
|
|
- float const layer_radius =
|
|
|
|
|
- torso_r * radius_scale + row * (k_radial_push * radius_scale);
|
|
|
|
|
- for (int col = 0; col < columns; ++col) {
|
|
|
|
|
- float const angle = start + step * col;
|
|
|
|
|
- QVector3D const radial(std::sin(angle), 0.0F, std::cos(angle));
|
|
|
|
|
- QVector3D center(radial.x() * layer_radius, row_y,
|
|
|
|
|
- radial.z() * layer_radius);
|
|
|
|
|
- center += radial * (row * 0.006F);
|
|
|
|
|
- float const yaw = std::atan2(radial.x(), radial.z());
|
|
|
|
|
- bool const tinned_tile = ((row + col) % 4) == 0;
|
|
|
|
|
- QVector3D color =
|
|
|
|
|
- tinned_tile ? tinned : (row % 2 == 0 ? bronze : bronze_shadow);
|
|
|
|
|
- float const width_scale =
|
|
|
|
|
- 1.0F - 0.04F * std::abs((columns - 1) * 0.5F - col);
|
|
|
|
|
- draw_scale_tile(center, yaw, color, k_tile_height,
|
|
|
|
|
- std::clamp(width_scale, 0.78F, 1.05F));
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- emit_scale_band(0.0F, 1.30F, 7, 1.08F);
|
|
|
|
|
- emit_scale_band(std::numbers::pi_v<float>, 1.20F, 7, 1.04F);
|
|
|
|
|
- emit_scale_band(std::numbers::pi_v<float> * 0.5F, 0.82F, 5, 1.02F);
|
|
|
|
|
- emit_scale_band(-std::numbers::pi_v<float> * 0.5F, 0.82F, 5, 1.02F);
|
|
|
|
|
-
|
|
|
|
|
- QVector3D const collar_top(0.0F, cuirass_top + 0.020F, 0.0F);
|
|
|
|
|
- QVector3D const collar_bot(0.0F, cuirass_top - 0.010F, 0.0F);
|
|
|
|
|
- QMatrix4x4 collar = cylinderBetween(ctx.model, collar_bot, collar_top,
|
|
|
|
|
- HP::NECK_RADIUS * 1.90F);
|
|
|
|
|
- collar.scale(1.04F, 1.0F, 0.90F);
|
|
|
|
|
- out.mesh(getUnitCylinder(), collar, leather, nullptr, 1.0F);
|
|
|
|
|
-
|
|
|
|
|
- QVector3D const waist_top(0.0F, waist_y + 0.05F, 0.0F);
|
|
|
|
|
- QVector3D const waist_bot(0.0F, waist_y - 0.02F, 0.0F);
|
|
|
|
|
- QMatrix4x4 waist =
|
|
|
|
|
- cylinderBetween(ctx.model, waist_bot, waist_top, torso_r * 1.16F);
|
|
|
|
|
- waist.scale(1.06F, 1.0F, 0.90F);
|
|
|
|
|
- out.mesh(getUnitCylinder(), waist, leather, nullptr, 1.0F);
|
|
|
|
|
-
|
|
|
|
|
- auto draw_pteruge = [&](float angle, float length) {
|
|
|
|
|
- QVector3D const radial(std::sin(angle), 0.0F, std::cos(angle));
|
|
|
|
|
- QVector3D const base =
|
|
|
|
|
- QVector3D(radial.x() * torso_r * 1.18F, waist_y - 0.03F,
|
|
|
|
|
- radial.z() * torso_r * 1.18F);
|
|
|
|
|
- QVector3D const tip =
|
|
|
|
|
- base + QVector3D(radial.x() * 0.02F, -length, radial.z() * 0.02F);
|
|
|
|
|
- QMatrix4x4 strap =
|
|
|
|
|
- cylinderBetween(ctx.model, tip, base, k_tile_width * 0.18F);
|
|
|
|
|
- float const yaw = std::atan2(radial.x(), radial.z());
|
|
|
|
|
- strap.rotate(yaw * (180.0F / std::numbers::pi_v<float>), 0.0F, 1.0F,
|
|
|
|
|
- 0.0F);
|
|
|
|
|
- strap.scale(0.65F, 1.0F, 0.40F);
|
|
|
|
|
- out.mesh(getUnitCylinder(12), strap,
|
|
|
|
|
- leather * QVector3D(0.90F, 0.92F, 0.96F), nullptr, 1.0F);
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- constexpr int k_pteruge_count = 12;
|
|
|
|
|
- for (int i = 0; i < k_pteruge_count; ++i) {
|
|
|
|
|
- float const angle = (static_cast<float>(i) / k_pteruge_count) * 2.0F *
|
|
|
|
|
- std::numbers::pi_v<float>;
|
|
|
|
|
- draw_pteruge(angle, 0.18F);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ float const tunic_top = y_top_cover;
|
|
|
|
|
+ float const tunic_bottom = waist_y - 0.08F; // Short tunic ending just below waist
|
|
|
|
|
+
|
|
|
|
|
+ QVector3D tunic_color =
|
|
|
|
|
+ saturate_color(v.palette.cloth * QVector3D(1.05F, 1.02F, 0.92F));
|
|
|
|
|
+ QVector3D tunic_trim =
|
|
|
|
|
+ saturate_color(tunic_color * QVector3D(0.75F, 0.70F, 0.65F));
|
|
|
|
|
+ QVector3D leather_belt =
|
|
|
|
|
+ saturate_color(v.palette.leatherDark * QVector3D(1.08F, 0.94F, 0.78F));
|
|
|
|
|
+
|
|
|
|
|
+ // Main tunic body - simple cylinder
|
|
|
|
|
+ QVector3D const tunic_top_pos(0.0F, tunic_top, 0.0F);
|
|
|
|
|
+ QVector3D const tunic_bot_pos(0.0F, tunic_bottom, 0.0F);
|
|
|
|
|
+
|
|
|
|
|
+ QMatrix4x4 tunic_main =
|
|
|
|
|
+ cylinderBetween(ctx.model, tunic_bot_pos, tunic_top_pos, torso_r * 1.06F);
|
|
|
|
|
+ tunic_main.scale(1.0F, 1.0F, 0.98F);
|
|
|
|
|
+ out.mesh(getUnitCylinder(), tunic_main, tunic_color, nullptr, 1.0F);
|
|
|
|
|
+
|
|
|
|
|
+ // Simple trim at neck
|
|
|
|
|
+ QVector3D const neck_trim_top(0.0F, tunic_top + 0.005F, 0.0F);
|
|
|
|
|
+ QVector3D const neck_trim_bot(0.0F, tunic_top - 0.015F, 0.0F);
|
|
|
|
|
+ QMatrix4x4 neck_trim =
|
|
|
|
|
+ cylinderBetween(ctx.model, neck_trim_bot, neck_trim_top, HP::NECK_RADIUS * 1.85F);
|
|
|
|
|
+ neck_trim.scale(1.03F, 1.0F, 0.92F);
|
|
|
|
|
+ out.mesh(getUnitCylinder(), neck_trim, tunic_trim, nullptr, 1.0F);
|
|
|
|
|
+
|
|
|
|
|
+ // Simple trim at bottom hem
|
|
|
|
|
+ QVector3D const hem_top(0.0F, tunic_bottom + 0.020F, 0.0F);
|
|
|
|
|
+ QVector3D const hem_bot(0.0F, tunic_bottom - 0.010F, 0.0F);
|
|
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
|
|
+ cylinderBetween(ctx.model, hem_bot, hem_top, torso_r * 1.05F),
|
|
|
|
|
+ tunic_trim, nullptr, 1.0F);
|
|
|
|
|
+
|
|
|
|
|
+ // Leather belt at waist
|
|
|
|
|
+ QVector3D const belt_top(0.0F, waist_y + 0.03F, 0.0F);
|
|
|
|
|
+ QVector3D const belt_bot(0.0F, waist_y - 0.03F, 0.0F);
|
|
|
|
|
+ QMatrix4x4 belt =
|
|
|
|
|
+ cylinderBetween(ctx.model, belt_bot, belt_top, torso_r * 1.08F);
|
|
|
|
|
+ belt.scale(1.04F, 1.0F, 0.94F);
|
|
|
|
|
+ out.mesh(getUnitCylinder(), belt, leather_belt, nullptr, 1.0F);
|
|
|
|
|
+
|
|
|
|
|
+ // Simple belt buckle
|
|
|
|
|
+ QVector3D const buckle_pos(0.0F, waist_y, torso_r * 1.10F);
|
|
|
|
|
+ QMatrix4x4 buckle = ctx.model;
|
|
|
|
|
+ buckle.translate(buckle_pos);
|
|
|
|
|
+ buckle.scale(0.025F, 0.035F, 0.012F);
|
|
|
|
|
+ QVector3D bronze_buckle =
|
|
|
|
|
+ saturate_color(v.palette.metal * QVector3D(1.15F, 1.00F, 0.68F));
|
|
|
|
|
+ out.mesh(getUnitSphere(), buckle, bronze_buckle, nullptr, 1.0F);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void drawShoulderDecorations(const DrawContext &ctx, const HumanoidVariant &v,
|
|
void drawShoulderDecorations(const DrawContext &ctx, const HumanoidVariant &v,
|
|
@@ -594,207 +610,214 @@ public:
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
private:
|
|
|
- mutable std::unordered_map<uint32_t, ArcherExtras> m_extrasCache;
|
|
|
|
|
|
|
+mutable std::unordered_map<uint32_t, ArcherExtras> m_extrasCache;
|
|
|
|
|
|
|
|
- auto
|
|
|
|
|
- resolve_style(const DrawContext &ctx) const -> const ArcherStyleConfig & {
|
|
|
|
|
- ensure_archer_styles_registered();
|
|
|
|
|
- auto &styles = style_registry();
|
|
|
|
|
- std::string nation_id;
|
|
|
|
|
- if (ctx.entity != nullptr) {
|
|
|
|
|
- if (auto *unit =
|
|
|
|
|
- ctx.entity->getComponent<Engine::Core::UnitComponent>()) {
|
|
|
|
|
- nation_id = Game::Systems::nationIDToString(unit->nation_id);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- if (!nation_id.empty()) {
|
|
|
|
|
- auto it = styles.find(nation_id);
|
|
|
|
|
- if (it != styles.end()) {
|
|
|
|
|
- return it->second;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+auto resolve_style(const DrawContext &ctx) const -> const ArcherStyleConfig & {
|
|
|
|
|
+ ensure_archer_styles_registered();
|
|
|
|
|
+ auto &styles = style_registry();
|
|
|
|
|
+ std::string nation_id;
|
|
|
|
|
+ if (ctx.entity != nullptr) {
|
|
|
|
|
+ if (auto *unit = ctx.entity->getComponent<Engine::Core::UnitComponent>()) {
|
|
|
|
|
+ nation_id = Game::Systems::nationIDToString(unit->nation_id);
|
|
|
}
|
|
}
|
|
|
- auto fallback = styles.find(std::string(k_default_style_key));
|
|
|
|
|
- if (fallback != styles.end()) {
|
|
|
|
|
- return fallback->second;
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!nation_id.empty()) {
|
|
|
|
|
+ auto it = styles.find(nation_id);
|
|
|
|
|
+ if (it != styles.end()) {
|
|
|
|
|
+ return it->second;
|
|
|
}
|
|
}
|
|
|
- static const ArcherStyleConfig default_style{};
|
|
|
|
|
- return default_style;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
+ auto fallback = styles.find(std::string(k_default_style_key));
|
|
|
|
|
+ if (fallback != styles.end()) {
|
|
|
|
|
+ return fallback->second;
|
|
|
|
|
+ }
|
|
|
|
|
+ static const ArcherStyleConfig default_style{};
|
|
|
|
|
+ return default_style;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
public:
|
|
public:
|
|
|
- auto resolve_shader_key(const DrawContext &ctx) const -> QString {
|
|
|
|
|
- const ArcherStyleConfig &style = resolve_style(ctx);
|
|
|
|
|
- if (!style.shader_id.empty()) {
|
|
|
|
|
- return QString::fromStdString(style.shader_id);
|
|
|
|
|
- }
|
|
|
|
|
- return QStringLiteral("archer");
|
|
|
|
|
|
|
+auto resolve_shader_key(const DrawContext &ctx) const -> QString {
|
|
|
|
|
+ const ArcherStyleConfig &style = resolve_style(ctx);
|
|
|
|
|
+ if (!style.shader_id.empty()) {
|
|
|
|
|
+ return QString::fromStdString(style.shader_id);
|
|
|
}
|
|
}
|
|
|
|
|
+ return QStringLiteral("archer");
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
private:
|
|
private:
|
|
|
- void apply_palette_overrides(const ArcherStyleConfig &style,
|
|
|
|
|
- const QVector3D &team_tint,
|
|
|
|
|
- HumanoidVariant &variant) const {
|
|
|
|
|
- auto apply_color = [&](const std::optional<QVector3D> &override_color,
|
|
|
|
|
- QVector3D &target) {
|
|
|
|
|
- target = mix_palette_color(target, override_color, team_tint,
|
|
|
|
|
- k_team_mix_weight, k_style_mix_weight);
|
|
|
|
|
- };
|
|
|
|
|
|
|
+void apply_palette_overrides(const ArcherStyleConfig &style,
|
|
|
|
|
+ const QVector3D &team_tint,
|
|
|
|
|
+ HumanoidVariant &variant) const {
|
|
|
|
|
+ auto apply_color = [&](const std::optional<QVector3D> &override_color,
|
|
|
|
|
+ QVector3D &target) {
|
|
|
|
|
+ target = mix_palette_color(target, override_color, team_tint,
|
|
|
|
|
+ k_team_mix_weight, k_style_mix_weight);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ apply_color(style.cloth_color, variant.palette.cloth);
|
|
|
|
|
+ apply_color(style.leather_color, variant.palette.leather);
|
|
|
|
|
+ apply_color(style.leather_dark_color, variant.palette.leatherDark);
|
|
|
|
|
+ apply_color(style.metal_color, variant.palette.metal);
|
|
|
|
|
+ apply_color(style.wood_color, variant.palette.wood);
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- apply_color(style.cloth_color, variant.palette.cloth);
|
|
|
|
|
- apply_color(style.leather_color, variant.palette.leather);
|
|
|
|
|
- apply_color(style.leather_dark_color, variant.palette.leatherDark);
|
|
|
|
|
- apply_color(style.metal_color, variant.palette.metal);
|
|
|
|
|
- apply_color(style.wood_color, variant.palette.wood);
|
|
|
|
|
|
|
+void apply_extras_overrides(const ArcherStyleConfig &style,
|
|
|
|
|
+ ArcherExtras &extras) const {
|
|
|
|
|
+ if (style.fletching_color) {
|
|
|
|
|
+ extras.fletch = saturate_color(*style.fletching_color);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- void apply_extras_overrides(const ArcherStyleConfig &style,
|
|
|
|
|
- ArcherExtras &extras) const {
|
|
|
|
|
- if (style.fletching_color) {
|
|
|
|
|
- extras.fletch = saturate_color(*style.fletching_color);
|
|
|
|
|
- }
|
|
|
|
|
- if (style.bow_string_color) {
|
|
|
|
|
- extras.stringCol = saturate_color(*style.bow_string_color);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (style.bow_string_color) {
|
|
|
|
|
+ extras.stringCol = saturate_color(*style.bow_string_color);
|
|
|
}
|
|
}
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- void draw_headwrap(const DrawContext &ctx, const HumanoidVariant &v,
|
|
|
|
|
- const HumanoidPose &pose, ISubmitter &out) const {
|
|
|
|
|
- QVector3D const cloth_color =
|
|
|
|
|
- saturate_color(v.palette.cloth * QVector3D(0.9F, 1.05F, 1.05F));
|
|
|
|
|
- float const head_r = pose.headR;
|
|
|
|
|
-
|
|
|
|
|
- QVector3D const band_top(0, pose.headPos.y() + head_r * 0.70F, 0);
|
|
|
|
|
- QVector3D const band_bot(0, pose.headPos.y() + head_r * 0.30F, 0);
|
|
|
|
|
- out.mesh(getUnitCylinder(),
|
|
|
|
|
- cylinderBetween(ctx.model, band_bot, band_top, head_r * 1.08F),
|
|
|
|
|
- cloth_color, nullptr, 1.0F);
|
|
|
|
|
-
|
|
|
|
|
- QVector3D const knot_center(0.10F, pose.headPos.y() + head_r * 0.60F,
|
|
|
|
|
- head_r * 0.72F);
|
|
|
|
|
- QMatrix4x4 knot_m = ctx.model;
|
|
|
|
|
- knot_m.translate(knot_center);
|
|
|
|
|
- knot_m.scale(head_r * 0.32F);
|
|
|
|
|
- out.mesh(getUnitSphere(), knot_m, cloth_color * 1.05F, nullptr, 1.0F);
|
|
|
|
|
-
|
|
|
|
|
- QVector3D const tail_top = knot_center + QVector3D(-0.08F, -0.05F, -0.06F);
|
|
|
|
|
- QVector3D const tail_bot = tail_top + QVector3D(0.02F, -0.28F, -0.08F);
|
|
|
|
|
- out.mesh(getUnitCylinder(),
|
|
|
|
|
- cylinderBetween(ctx.model, tail_top, tail_bot, head_r * 0.28F),
|
|
|
|
|
- cloth_color * QVector3D(0.92F, 0.98F, 1.05F), nullptr, 1.0F);
|
|
|
|
|
|
|
+void draw_headwrap(const DrawContext &ctx, const HumanoidVariant &v,
|
|
|
|
|
+ const HumanoidPose &pose, ISubmitter &out) const {
|
|
|
|
|
+ QVector3D const cloth_color =
|
|
|
|
|
+ saturate_color(v.palette.cloth * QVector3D(0.9F, 1.05F, 1.05F));
|
|
|
|
|
+ const HeadFrame &head = pose.headFrame;
|
|
|
|
|
+ float const head_r = head.radius;
|
|
|
|
|
+ if (head_r <= 0.0F) {
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- static void drawQuiver(const DrawContext &ctx, const HumanoidVariant &v,
|
|
|
|
|
- const HumanoidPose &pose, const ArcherExtras &extras,
|
|
|
|
|
- uint32_t seed, ISubmitter &out) {
|
|
|
|
|
- using HP = HumanProportions;
|
|
|
|
|
|
|
+ auto headPoint = [&](const QVector3D &normalized) -> QVector3D {
|
|
|
|
|
+ return headLocalPosition(head, normalized);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ QVector3D const band_top = headPoint(QVector3D(0.0F, 0.70F, 0.0F));
|
|
|
|
|
+ QVector3D const band_bot = headPoint(QVector3D(0.0F, 0.30F, 0.0F));
|
|
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
|
|
+ cylinderBetween(ctx.model, band_bot, band_top, head_r * 1.08F),
|
|
|
|
|
+ cloth_color, nullptr, 1.0F);
|
|
|
|
|
+
|
|
|
|
|
+ QVector3D const knot_center =
|
|
|
|
|
+ headPoint(QVector3D(0.10F, 0.60F, 0.72F));
|
|
|
|
|
+ QMatrix4x4 knot_m = ctx.model;
|
|
|
|
|
+ knot_m.translate(knot_center);
|
|
|
|
|
+ knot_m.scale(head_r * 0.32F);
|
|
|
|
|
+ out.mesh(getUnitSphere(), knot_m, cloth_color * 1.05F, nullptr, 1.0F);
|
|
|
|
|
+
|
|
|
|
|
+ QVector3D const tail_top = knot_center + head.right * (-0.08F) +
|
|
|
|
|
+ head.up * (-0.05F) + head.forward * (-0.06F);
|
|
|
|
|
+ QVector3D const tail_bot =
|
|
|
|
|
+ tail_top + head.right * 0.02F + head.up * (-0.28F) + head.forward * (-0.08F);
|
|
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
|
|
+ cylinderBetween(ctx.model, tail_top, tail_bot, head_r * 0.28F),
|
|
|
|
|
+ cloth_color * QVector3D(0.92F, 0.98F, 1.05F), nullptr, 1.0F);
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- QVector3D const spine_mid = (pose.shoulderL + pose.shoulderR) * 0.5F;
|
|
|
|
|
- QVector3D const quiver_offset(-0.08F, 0.10F, -0.25F);
|
|
|
|
|
- QVector3D const q_top = spine_mid + quiver_offset;
|
|
|
|
|
- QVector3D const q_base = q_top + QVector3D(-0.02F, -0.30F, 0.03F);
|
|
|
|
|
|
|
+static void drawQuiver(const DrawContext &ctx, const HumanoidVariant &v,
|
|
|
|
|
+ const HumanoidPose &pose, const ArcherExtras &extras,
|
|
|
|
|
+ uint32_t seed, ISubmitter &out) {
|
|
|
|
|
+ using HP = HumanProportions;
|
|
|
|
|
+
|
|
|
|
|
+ QVector3D const spine_mid = (pose.shoulderL + pose.shoulderR) * 0.5F;
|
|
|
|
|
+ QVector3D const quiver_offset(-0.08F, 0.10F, -0.25F);
|
|
|
|
|
+ QVector3D const q_top = spine_mid + quiver_offset;
|
|
|
|
|
+ QVector3D const q_base = q_top + QVector3D(-0.02F, -0.30F, 0.03F);
|
|
|
|
|
+
|
|
|
|
|
+ float const quiver_r = HP::HEAD_RADIUS * 0.45F;
|
|
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
|
|
+ cylinderBetween(ctx.model, q_base, q_top, quiver_r),
|
|
|
|
|
+ v.palette.leather, nullptr, 1.0F);
|
|
|
|
|
+
|
|
|
|
|
+ float const j = (hash_01(seed) - 0.5F) * 0.04F;
|
|
|
|
|
+ float const k = (hash_01(seed ^ HashXorShift::k_golden_ratio) - 0.5F) * 0.04F;
|
|
|
|
|
+
|
|
|
|
|
+ QVector3D const a1 = q_top + QVector3D(0.00F + j, 0.08F, 0.00F + k);
|
|
|
|
|
+ out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, q_top, a1, 0.010F),
|
|
|
|
|
+ v.palette.wood, nullptr, 1.0F);
|
|
|
|
|
+ out.mesh(getUnitCone(),
|
|
|
|
|
+ coneFromTo(ctx.model, a1, a1 + QVector3D(0, 0.05F, 0), 0.025F),
|
|
|
|
|
+ extras.fletch, nullptr, 1.0F);
|
|
|
|
|
+
|
|
|
|
|
+ QVector3D const a2 = q_top + QVector3D(0.02F - j, 0.07F, 0.02F - k);
|
|
|
|
|
+ out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, q_top, a2, 0.010F),
|
|
|
|
|
+ v.palette.wood, nullptr, 1.0F);
|
|
|
|
|
+ out.mesh(getUnitCone(),
|
|
|
|
|
+ coneFromTo(ctx.model, a2, a2 + QVector3D(0, 0.05F, 0), 0.025F),
|
|
|
|
|
+ extras.fletch, nullptr, 1.0F);
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- float const quiver_r = HP::HEAD_RADIUS * 0.45F;
|
|
|
|
|
- out.mesh(getUnitCylinder(),
|
|
|
|
|
- cylinderBetween(ctx.model, q_base, q_top, quiver_r),
|
|
|
|
|
- v.palette.leather, nullptr, 1.0F);
|
|
|
|
|
|
|
+static void drawBowAndArrow(const DrawContext &ctx, const HumanoidPose &pose,
|
|
|
|
|
+ const HumanoidVariant &v,
|
|
|
|
|
+ const ArcherExtras &extras, bool is_attacking,
|
|
|
|
|
+ float attack_phase, ISubmitter &out) {
|
|
|
|
|
+ const QVector3D up(0.0F, 1.0F, 0.0F);
|
|
|
|
|
+ const QVector3D forward(0.0F, 0.0F, 1.0F);
|
|
|
|
|
|
|
|
- float const j = (hash_01(seed) - 0.5F) * 0.04F;
|
|
|
|
|
- float const k =
|
|
|
|
|
- (hash_01(seed ^ HashXorShift::k_golden_ratio) - 0.5F) * 0.04F;
|
|
|
|
|
|
|
+ QVector3D const grip = pose.handL;
|
|
|
|
|
|
|
|
- QVector3D const a1 = q_top + QVector3D(0.00F + j, 0.08F, 0.00F + k);
|
|
|
|
|
- out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, q_top, a1, 0.010F),
|
|
|
|
|
- v.palette.wood, nullptr, 1.0F);
|
|
|
|
|
- out.mesh(getUnitCone(),
|
|
|
|
|
- coneFromTo(ctx.model, a1, a1 + QVector3D(0, 0.05F, 0), 0.025F),
|
|
|
|
|
- extras.fletch, nullptr, 1.0F);
|
|
|
|
|
|
|
+ float const bow_plane_z = 0.45F;
|
|
|
|
|
+ QVector3D const top_end(extras.bowX, extras.bowTopY, bow_plane_z);
|
|
|
|
|
+ QVector3D const bot_end(extras.bowX, extras.bowBotY, bow_plane_z);
|
|
|
|
|
|
|
|
- QVector3D const a2 = q_top + QVector3D(0.02F - j, 0.07F, 0.02F - k);
|
|
|
|
|
- out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, q_top, a2, 0.010F),
|
|
|
|
|
- v.palette.wood, nullptr, 1.0F);
|
|
|
|
|
- out.mesh(getUnitCone(),
|
|
|
|
|
- coneFromTo(ctx.model, a2, a2 + QVector3D(0, 0.05F, 0), 0.025F),
|
|
|
|
|
- extras.fletch, nullptr, 1.0F);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ QVector3D const nock(
|
|
|
|
|
+ extras.bowX,
|
|
|
|
|
+ clampf(pose.hand_r.y(), extras.bowBotY + 0.05F, extras.bowTopY - 0.05F),
|
|
|
|
|
+ clampf(pose.hand_r.z(), bow_plane_z - 0.30F, bow_plane_z + 0.30F));
|
|
|
|
|
|
|
|
- static void drawBowAndArrow(const DrawContext &ctx, const HumanoidPose &pose,
|
|
|
|
|
- const HumanoidVariant &v,
|
|
|
|
|
- const ArcherExtras &extras, bool is_attacking,
|
|
|
|
|
- float attack_phase, ISubmitter &out) {
|
|
|
|
|
- const QVector3D up(0.0F, 1.0F, 0.0F);
|
|
|
|
|
- const QVector3D forward(0.0F, 0.0F, 1.0F);
|
|
|
|
|
-
|
|
|
|
|
- QVector3D const grip = pose.handL;
|
|
|
|
|
-
|
|
|
|
|
- float const bow_plane_z = 0.45F;
|
|
|
|
|
- QVector3D const top_end(extras.bowX, extras.bowTopY, bow_plane_z);
|
|
|
|
|
- QVector3D const bot_end(extras.bowX, extras.bowBotY, bow_plane_z);
|
|
|
|
|
-
|
|
|
|
|
- QVector3D const nock(
|
|
|
|
|
- extras.bowX,
|
|
|
|
|
- clampf(pose.hand_r.y(), extras.bowBotY + 0.05F, extras.bowTopY - 0.05F),
|
|
|
|
|
- clampf(pose.hand_r.z(), bow_plane_z - 0.30F, bow_plane_z + 0.30F));
|
|
|
|
|
-
|
|
|
|
|
- constexpr int k_bowstring_segments = 22;
|
|
|
|
|
- auto q_bezier = [](const QVector3D &a, const QVector3D &c,
|
|
|
|
|
- const QVector3D &b, float t) {
|
|
|
|
|
- float const u = 1.0F - t;
|
|
|
|
|
- return u * u * a + 2.0F * u * t * c + t * t * b;
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ constexpr int k_bowstring_segments = 22;
|
|
|
|
|
+ auto q_bezier = [](const QVector3D &a, const QVector3D &c, const QVector3D &b,
|
|
|
|
|
+ float t) {
|
|
|
|
|
+ float const u = 1.0F - t;
|
|
|
|
|
+ return u * u * a + 2.0F * u * t * c + t * t * b;
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- float const bow_mid_y = (top_end.y() + bot_end.y()) * 0.5F;
|
|
|
|
|
|
|
+ float const bow_mid_y = (top_end.y() + bot_end.y()) * 0.5F;
|
|
|
|
|
|
|
|
- float const ctrl_y = bow_mid_y + 0.45F;
|
|
|
|
|
|
|
+ float const ctrl_y = bow_mid_y + 0.45F;
|
|
|
|
|
|
|
|
- QVector3D const ctrl(extras.bowX, ctrl_y,
|
|
|
|
|
- bow_plane_z + extras.bowDepth * 0.6F);
|
|
|
|
|
|
|
+ QVector3D const ctrl(extras.bowX, ctrl_y,
|
|
|
|
|
+ bow_plane_z + extras.bowDepth * 0.6F);
|
|
|
|
|
|
|
|
- QVector3D prev = bot_end;
|
|
|
|
|
- for (int i = 1; i <= k_bowstring_segments; ++i) {
|
|
|
|
|
- float const t = float(i) / float(k_bowstring_segments);
|
|
|
|
|
- QVector3D const cur = q_bezier(bot_end, ctrl, top_end, t);
|
|
|
|
|
- out.mesh(getUnitCylinder(),
|
|
|
|
|
- cylinderBetween(ctx.model, prev, cur, extras.bowRodR),
|
|
|
|
|
- v.palette.wood, nullptr, 1.0F);
|
|
|
|
|
- prev = cur;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ QVector3D prev = bot_end;
|
|
|
|
|
+ for (int i = 1; i <= k_bowstring_segments; ++i) {
|
|
|
|
|
+ float const t = float(i) / float(k_bowstring_segments);
|
|
|
|
|
+ QVector3D const cur = q_bezier(bot_end, ctrl, top_end, t);
|
|
|
out.mesh(getUnitCylinder(),
|
|
out.mesh(getUnitCylinder(),
|
|
|
- cylinderBetween(ctx.model, grip - up * 0.05F, grip + up * 0.05F,
|
|
|
|
|
- extras.bowRodR * 1.45F),
|
|
|
|
|
|
|
+ cylinderBetween(ctx.model, prev, cur, extras.bowRodR),
|
|
|
v.palette.wood, nullptr, 1.0F);
|
|
v.palette.wood, nullptr, 1.0F);
|
|
|
-
|
|
|
|
|
- out.mesh(getUnitCylinder(),
|
|
|
|
|
- cylinderBetween(ctx.model, top_end, nock, extras.stringR),
|
|
|
|
|
- extras.stringCol, nullptr, 1.0F);
|
|
|
|
|
- out.mesh(getUnitCylinder(),
|
|
|
|
|
- cylinderBetween(ctx.model, nock, bot_end, extras.stringR),
|
|
|
|
|
- extras.stringCol, nullptr, 1.0F);
|
|
|
|
|
- out.mesh(getUnitCylinder(),
|
|
|
|
|
- cylinderBetween(ctx.model, pose.hand_r, nock, 0.0045F),
|
|
|
|
|
- extras.stringCol * 0.9F, nullptr, 1.0F);
|
|
|
|
|
-
|
|
|
|
|
- bool const show_arrow =
|
|
|
|
|
- !is_attacking ||
|
|
|
|
|
- (is_attacking && attack_phase >= 0.0F && attack_phase < 0.52F);
|
|
|
|
|
-
|
|
|
|
|
- if (show_arrow) {
|
|
|
|
|
- QVector3D const tail = nock - forward * 0.06F;
|
|
|
|
|
- QVector3D const tip = tail + forward * 0.90F;
|
|
|
|
|
- out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, tail, tip, 0.018F),
|
|
|
|
|
- v.palette.wood, nullptr, 1.0F);
|
|
|
|
|
- QVector3D const head_base = tip - forward * 0.10F;
|
|
|
|
|
- out.mesh(getUnitCone(), coneFromTo(ctx.model, head_base, tip, 0.05F),
|
|
|
|
|
- extras.metalHead, nullptr, 1.0F);
|
|
|
|
|
- QVector3D const f1b = tail - forward * 0.02F;
|
|
|
|
|
- QVector3D const f1a = f1b - forward * 0.06F;
|
|
|
|
|
- QVector3D const f2b = tail + forward * 0.02F;
|
|
|
|
|
- QVector3D const f2a = f2b + forward * 0.06F;
|
|
|
|
|
- out.mesh(getUnitCone(), coneFromTo(ctx.model, f1b, f1a, 0.04F),
|
|
|
|
|
- extras.fletch, nullptr, 1.0F);
|
|
|
|
|
- out.mesh(getUnitCone(), coneFromTo(ctx.model, f2a, f2b, 0.04F),
|
|
|
|
|
- extras.fletch, nullptr, 1.0F);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ prev = cur;
|
|
|
|
|
+ }
|
|
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
|
|
+ cylinderBetween(ctx.model, grip - up * 0.05F, grip + up * 0.05F,
|
|
|
|
|
+ extras.bowRodR * 1.45F),
|
|
|
|
|
+ v.palette.wood, nullptr, 1.0F);
|
|
|
|
|
+
|
|
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
|
|
+ cylinderBetween(ctx.model, top_end, nock, extras.stringR),
|
|
|
|
|
+ extras.stringCol, nullptr, 1.0F);
|
|
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
|
|
+ cylinderBetween(ctx.model, nock, bot_end, extras.stringR),
|
|
|
|
|
+ extras.stringCol, nullptr, 1.0F);
|
|
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
|
|
+ cylinderBetween(ctx.model, pose.hand_r, nock, 0.0045F),
|
|
|
|
|
+ extras.stringCol * 0.9F, nullptr, 1.0F);
|
|
|
|
|
+
|
|
|
|
|
+ bool const show_arrow =
|
|
|
|
|
+ !is_attacking ||
|
|
|
|
|
+ (is_attacking && attack_phase >= 0.0F && attack_phase < 0.52F);
|
|
|
|
|
+
|
|
|
|
|
+ if (show_arrow) {
|
|
|
|
|
+ QVector3D const tail = nock - forward * 0.06F;
|
|
|
|
|
+ QVector3D const tip = tail + forward * 0.90F;
|
|
|
|
|
+ out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, tail, tip, 0.018F),
|
|
|
|
|
+ v.palette.wood, nullptr, 1.0F);
|
|
|
|
|
+ QVector3D const head_base = tip - forward * 0.10F;
|
|
|
|
|
+ out.mesh(getUnitCone(), coneFromTo(ctx.model, head_base, tip, 0.05F),
|
|
|
|
|
+ extras.metalHead, nullptr, 1.0F);
|
|
|
|
|
+ QVector3D const f1b = tail - forward * 0.02F;
|
|
|
|
|
+ QVector3D const f1a = f1b - forward * 0.06F;
|
|
|
|
|
+ QVector3D const f2b = tail + forward * 0.02F;
|
|
|
|
|
+ QVector3D const f2a = f2b + forward * 0.06F;
|
|
|
|
|
+ out.mesh(getUnitCone(), coneFromTo(ctx.model, f1b, f1a, 0.04F),
|
|
|
|
|
+ extras.fletch, nullptr, 1.0F);
|
|
|
|
|
+ out.mesh(getUnitCone(), coneFromTo(ctx.model, f2a, f2b, 0.04F),
|
|
|
|
|
+ extras.fletch, nullptr, 1.0F);
|
|
|
}
|
|
}
|
|
|
|
|
+}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
void registerArcherRenderer(Render::GL::EntityRendererRegistry ®istry) {
|
|
void registerArcherRenderer(Render::GL::EntityRendererRegistry ®istry) {
|
|
@@ -821,5 +844,4 @@ void registerArcherRenderer(Render::GL::EntityRendererRegistry ®istry) {
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-} // namespace Render::GL::Carthage
|
|
|
|
|
|
|
+}
|