|
|
@@ -1,15 +1,15 @@
|
|
|
#include "horse_renderer.h"
|
|
|
|
|
|
-#include "../humanoid_base.h"
|
|
|
-#include "../palette.h"
|
|
|
#include "../geom/math_utils.h"
|
|
|
#include "../geom/transforms.h"
|
|
|
#include "../gl/mesh.h"
|
|
|
#include "../gl/primitives.h"
|
|
|
+#include "../humanoid_base.h"
|
|
|
+#include "../palette.h"
|
|
|
|
|
|
#include <QMatrix4x4>
|
|
|
-#include <QVector3D>
|
|
|
#include <QRandomGenerator>
|
|
|
+#include <QVector3D>
|
|
|
|
|
|
#include <algorithm>
|
|
|
#include <cmath>
|
|
|
@@ -17,25 +17,21 @@
|
|
|
|
|
|
namespace Render::GL {
|
|
|
|
|
|
+using Render::Geom::clamp01;
|
|
|
using Render::Geom::coneFromTo;
|
|
|
using Render::Geom::cylinderBetween;
|
|
|
-using Render::Geom::clamp01;
|
|
|
|
|
|
-// ------------------------------
|
|
|
-// helpers (private to this file)
|
|
|
-// ------------------------------
|
|
|
namespace {
|
|
|
|
|
|
constexpr float kPi = 3.14159265358979323846f;
|
|
|
|
|
|
-// simple uint->float hash in [0,1)
|
|
|
inline float hash01(uint32_t x) {
|
|
|
x ^= x >> 16;
|
|
|
x *= 0x7feb352dU;
|
|
|
x ^= x >> 15;
|
|
|
x *= 0x846ca68bU;
|
|
|
x ^= x >> 16;
|
|
|
- return (x & 0xFFFFFF) / float(0x1000000); // 24-bit precision is plenty
|
|
|
+ return (x & 0xFFFFFF) / float(0x1000000);
|
|
|
}
|
|
|
|
|
|
inline float randBetween(uint32_t seed, uint32_t salt, float minV, float maxV) {
|
|
|
@@ -43,7 +39,7 @@ inline float randBetween(uint32_t seed, uint32_t salt, float minV, float maxV) {
|
|
|
return minV + (maxV - minV) * t;
|
|
|
}
|
|
|
|
|
|
-inline QVector3D lerpVec(const QVector3D& a, const QVector3D& b, float t) {
|
|
|
+inline QVector3D lerpVec(const QVector3D &a, const QVector3D &b, float t) {
|
|
|
return a * (1.0f - t) + b * t;
|
|
|
}
|
|
|
|
|
|
@@ -54,180 +50,155 @@ inline float smoothstep(float e0, float e1, float x) {
|
|
|
return t * t * (3.0f - 2.0f * t);
|
|
|
}
|
|
|
|
|
|
-// rotate v around the given axis by angle (radians)
|
|
|
-inline QVector3D rotateAroundY(const QVector3D& v, float angle) {
|
|
|
+inline QVector3D rotateAroundY(const QVector3D &v, float angle) {
|
|
|
float s = std::sin(angle), c = std::cos(angle);
|
|
|
- return QVector3D(v.x()*c + v.z()*s, v.y(), -v.x()*s + v.z()*c);
|
|
|
+ return QVector3D(v.x() * c + v.z() * s, v.y(), -v.x() * s + v.z() * c);
|
|
|
}
|
|
|
-inline QVector3D rotateAroundZ(const QVector3D& v, float angle) {
|
|
|
+inline QVector3D rotateAroundZ(const QVector3D &v, float angle) {
|
|
|
float s = std::sin(angle), c = std::cos(angle);
|
|
|
- return QVector3D(v.x()*c - v.y()*s, v.x()*s + v.y()*c, v.z());
|
|
|
+ return QVector3D(v.x() * c - v.y() * s, v.x() * s + v.y() * c, v.z());
|
|
|
}
|
|
|
|
|
|
-inline QVector3D darken(const QVector3D& c, float k) { return c * k; }
|
|
|
-inline QVector3D lighten(const QVector3D& c, float k) { return QVector3D(
|
|
|
- saturate(c.x()*k), saturate(c.y()*k), saturate(c.z()*k)); }
|
|
|
+inline QVector3D darken(const QVector3D &c, float k) { return c * k; }
|
|
|
+inline QVector3D lighten(const QVector3D &c, float k) {
|
|
|
+ return QVector3D(saturate(c.x() * k), saturate(c.y() * k),
|
|
|
+ saturate(c.z() * k));
|
|
|
+}
|
|
|
|
|
|
-// deterministic pseudo-random from a color (used to vary markings without changing interfaces)
|
|
|
-inline uint32_t colorHash(const QVector3D& c) {
|
|
|
+inline uint32_t colorHash(const QVector3D &c) {
|
|
|
uint32_t r = uint32_t(saturate(c.x()) * 255.0f);
|
|
|
uint32_t g = uint32_t(saturate(c.y()) * 255.0f);
|
|
|
uint32_t b = uint32_t(saturate(c.z()) * 255.0f);
|
|
|
uint32_t v = (r << 16) | (g << 8) | b;
|
|
|
- // avalanche
|
|
|
- v ^= v >> 16; v *= 0x7feb352dU;
|
|
|
- v ^= v >> 15; v *= 0x846ca68bU;
|
|
|
+
|
|
|
+ v ^= v >> 16;
|
|
|
+ v *= 0x7feb352dU;
|
|
|
+ v ^= v >> 15;
|
|
|
+ v *= 0x846ca68bU;
|
|
|
v ^= v >> 16;
|
|
|
return v;
|
|
|
}
|
|
|
|
|
|
-} // anonymous namespace
|
|
|
-
|
|
|
-// -----------------------------------
|
|
|
-// profile & variant construction
|
|
|
-// -----------------------------------
|
|
|
+} // namespace
|
|
|
|
|
|
HorseDimensions makeHorseDimensions(uint32_t seed) {
|
|
|
HorseDimensions d;
|
|
|
|
|
|
- // barrel/body - raised for longer legs
|
|
|
- d.bodyLength = randBetween(seed, 0x12u, 0.74f, 0.84f);
|
|
|
- d.bodyWidth = randBetween(seed, 0x34u, 0.17f, 0.20f);
|
|
|
- d.bodyHeight = randBetween(seed, 0x56u, 0.33f, 0.37f);
|
|
|
- d.barrelCenterY = randBetween(seed, 0x78u, 0.02f, 0.08f); // Slightly raised
|
|
|
+ d.bodyLength = randBetween(seed, 0x12u, 0.74f, 0.84f);
|
|
|
+ d.bodyWidth = randBetween(seed, 0x34u, 0.17f, 0.20f);
|
|
|
+ d.bodyHeight = randBetween(seed, 0x56u, 0.33f, 0.37f);
|
|
|
+ d.barrelCenterY = randBetween(seed, 0x78u, 0.02f, 0.08f);
|
|
|
|
|
|
- // neck / head
|
|
|
- d.neckLength = randBetween(seed, 0x9Au, 0.32f, 0.37f);
|
|
|
- d.neckRise = randBetween(seed, 0xBCu, 0.20f, 0.26f);
|
|
|
- d.headLength = randBetween(seed, 0xDEu, 0.24f, 0.28f);
|
|
|
- d.headWidth = randBetween(seed, 0xF1u, 0.13f, 0.16f);
|
|
|
- d.headHeight = randBetween(seed, 0x1357u, 0.16f, 0.19f);
|
|
|
- d.muzzleLength = randBetween(seed, 0x2468u, 0.11f, 0.14f);
|
|
|
+ d.neckLength = randBetween(seed, 0x9Au, 0.32f, 0.37f);
|
|
|
+ d.neckRise = randBetween(seed, 0xBCu, 0.20f, 0.26f);
|
|
|
+ d.headLength = randBetween(seed, 0xDEu, 0.24f, 0.28f);
|
|
|
+ d.headWidth = randBetween(seed, 0xF1u, 0.13f, 0.16f);
|
|
|
+ d.headHeight = randBetween(seed, 0x1357u, 0.16f, 0.19f);
|
|
|
+ d.muzzleLength = randBetween(seed, 0x2468u, 0.11f, 0.14f);
|
|
|
|
|
|
- // legs / hooves - longer for realistic proportions (1.5x original)
|
|
|
- d.legLength = randBetween(seed, 0x369Cu, 0.87f, 0.99f);
|
|
|
- d.hoofHeight = randBetween(seed, 0x48AEu, 0.070f, 0.080f);
|
|
|
+ d.legLength = randBetween(seed, 0x369Cu, 0.87f, 0.99f);
|
|
|
+ d.hoofHeight = randBetween(seed, 0x48AEu, 0.070f, 0.080f);
|
|
|
|
|
|
- // tail
|
|
|
- d.tailLength = randBetween(seed, 0x5ABCu, 0.30f, 0.36f);
|
|
|
+ d.tailLength = randBetween(seed, 0x5ABCu, 0.30f, 0.36f);
|
|
|
|
|
|
- // tack / saddle
|
|
|
d.saddleThickness = randBetween(seed, 0x6CDEu, 0.040f, 0.052f);
|
|
|
d.seatForwardOffset = randBetween(seed, 0x7531u, 0.020f, 0.050f);
|
|
|
- d.stirrupOut = d.bodyWidth * randBetween(seed, 0x8642u, 0.88f, 0.98f);
|
|
|
- d.stirrupDrop = randBetween(seed, 0x9753u, 0.23f, 0.27f);
|
|
|
+ d.stirrupOut = d.bodyWidth * randBetween(seed, 0x8642u, 0.88f, 0.98f);
|
|
|
+ d.stirrupDrop = randBetween(seed, 0x9753u, 0.23f, 0.27f);
|
|
|
|
|
|
- // motion
|
|
|
d.idleBobAmplitude = randBetween(seed, 0xA864u, 0.005f, 0.008f);
|
|
|
d.moveBobAmplitude = randBetween(seed, 0xB975u, 0.020f, 0.028f);
|
|
|
|
|
|
- // derived
|
|
|
d.saddleHeight = d.barrelCenterY + d.bodyHeight * 0.55f + d.saddleThickness;
|
|
|
|
|
|
return d;
|
|
|
}
|
|
|
|
|
|
-HorseVariant makeHorseVariant(uint32_t seed,
|
|
|
- const QVector3D& leatherBase,
|
|
|
- const QVector3D& clothBase) {
|
|
|
+HorseVariant makeHorseVariant(uint32_t seed, const QVector3D &leatherBase,
|
|
|
+ const QVector3D &clothBase) {
|
|
|
HorseVariant v;
|
|
|
|
|
|
- // pick a believable coat given a pseudo hue bucket
|
|
|
float coatHue = hash01(seed ^ 0x23456u);
|
|
|
if (coatHue < 0.18f) {
|
|
|
- v.coatColor = QVector3D(0.70f, 0.68f, 0.63f); // grey
|
|
|
+ v.coatColor = QVector3D(0.70f, 0.68f, 0.63f);
|
|
|
} else if (coatHue < 0.38f) {
|
|
|
- v.coatColor = QVector3D(0.40f, 0.30f, 0.22f); // bay
|
|
|
+ v.coatColor = QVector3D(0.40f, 0.30f, 0.22f);
|
|
|
} else if (coatHue < 0.65f) {
|
|
|
- v.coatColor = QVector3D(0.28f, 0.22f, 0.19f); // dark bay/brown
|
|
|
+ v.coatColor = QVector3D(0.28f, 0.22f, 0.19f);
|
|
|
} else if (coatHue < 0.85f) {
|
|
|
- v.coatColor = QVector3D(0.18f, 0.15f, 0.13f); // near-black
|
|
|
+ v.coatColor = QVector3D(0.18f, 0.15f, 0.13f);
|
|
|
} else {
|
|
|
- v.coatColor = QVector3D(0.48f, 0.42f, 0.39f); // light brown
|
|
|
+ v.coatColor = QVector3D(0.48f, 0.42f, 0.39f);
|
|
|
}
|
|
|
|
|
|
- // facial blaze chance nudges overall tone slightly lighter
|
|
|
float blazeChance = hash01(seed ^ 0x1122u);
|
|
|
if (blazeChance > 0.82f) {
|
|
|
v.coatColor = lerpVec(v.coatColor, QVector3D(0.92f, 0.92f, 0.90f), 0.25f);
|
|
|
}
|
|
|
|
|
|
- // mane/tail typically darker than coat
|
|
|
v.maneColor = lerpVec(v.coatColor, QVector3D(0.10f, 0.09f, 0.08f),
|
|
|
randBetween(seed, 0x3344u, 0.55f, 0.85f));
|
|
|
v.tailColor = lerpVec(v.maneColor, v.coatColor, 0.35f);
|
|
|
|
|
|
- // muzzle & hoof tone
|
|
|
v.muzzleColor = lerpVec(v.coatColor, QVector3D(0.18f, 0.14f, 0.12f), 0.65f);
|
|
|
- v.hoofColor = lerpVec(QVector3D(0.16f, 0.14f, 0.12f),
|
|
|
- QVector3D(0.40f, 0.35f, 0.32f),
|
|
|
- randBetween(seed, 0x5566u, 0.15f, 0.65f));
|
|
|
+ v.hoofColor =
|
|
|
+ lerpVec(QVector3D(0.16f, 0.14f, 0.12f), QVector3D(0.40f, 0.35f, 0.32f),
|
|
|
+ randBetween(seed, 0x5566u, 0.15f, 0.65f));
|
|
|
|
|
|
- // leather & tack tints
|
|
|
float leatherTone = randBetween(seed, 0x7788u, 0.78f, 0.96f);
|
|
|
- float tackTone = randBetween(seed, 0x88AAu, 0.58f, 0.78f);
|
|
|
+ float tackTone = randBetween(seed, 0x88AAu, 0.58f, 0.78f);
|
|
|
QVector3D leatherTint = leatherBase * leatherTone;
|
|
|
- QVector3D tackTint = leatherBase * tackTone;
|
|
|
+ QVector3D tackTint = leatherBase * tackTone;
|
|
|
if (blazeChance > 0.90f) {
|
|
|
- // chance of darker/burnished tack
|
|
|
+
|
|
|
tackTint = lerpVec(tackTint, QVector3D(0.18f, 0.19f, 0.22f), 0.25f);
|
|
|
}
|
|
|
v.saddleColor = leatherTint;
|
|
|
- v.tackColor = tackTint;
|
|
|
+ v.tackColor = tackTint;
|
|
|
|
|
|
- // blanket tone from a cloth base
|
|
|
v.blanketColor = clothBase * randBetween(seed, 0x99B0u, 0.92f, 1.05f);
|
|
|
|
|
|
return v;
|
|
|
}
|
|
|
|
|
|
-HorseProfile makeHorseProfile(uint32_t seed,
|
|
|
- const QVector3D& leatherBase,
|
|
|
- const QVector3D& clothBase) {
|
|
|
+HorseProfile makeHorseProfile(uint32_t seed, const QVector3D &leatherBase,
|
|
|
+ const QVector3D &clothBase) {
|
|
|
HorseProfile profile;
|
|
|
- profile.dims = makeHorseDimensions(seed);
|
|
|
+ profile.dims = makeHorseDimensions(seed);
|
|
|
profile.variant = makeHorseVariant(seed, leatherBase, clothBase);
|
|
|
|
|
|
- // gait
|
|
|
- profile.gait.cycleTime = randBetween(seed, 0xAA12u, 0.50f, 0.58f);
|
|
|
- profile.gait.frontLegPhase= randBetween(seed, 0xBB34u, 0.10f, 0.18f);
|
|
|
- float diagonalLead = randBetween(seed, 0xCC56u, 0.48f, 0.56f);
|
|
|
- profile.gait.rearLegPhase = std::fmod(profile.gait.frontLegPhase + diagonalLead, 1.0f);
|
|
|
- profile.gait.strideSwing = randBetween(seed, 0xDD78u, 0.18f, 0.24f);
|
|
|
- profile.gait.strideLift = randBetween(seed, 0xEE9Au, 0.11f, 0.15f);
|
|
|
+ profile.gait.cycleTime = randBetween(seed, 0xAA12u, 0.50f, 0.58f);
|
|
|
+ profile.gait.frontLegPhase = randBetween(seed, 0xBB34u, 0.10f, 0.18f);
|
|
|
+ float diagonalLead = randBetween(seed, 0xCC56u, 0.48f, 0.56f);
|
|
|
+ profile.gait.rearLegPhase =
|
|
|
+ std::fmod(profile.gait.frontLegPhase + diagonalLead, 1.0f);
|
|
|
+ profile.gait.strideSwing = randBetween(seed, 0xDD78u, 0.18f, 0.24f);
|
|
|
+ profile.gait.strideLift = randBetween(seed, 0xEE9Au, 0.11f, 0.15f);
|
|
|
|
|
|
return profile;
|
|
|
}
|
|
|
|
|
|
-// -----------------------------------
|
|
|
-// rendering
|
|
|
-// -----------------------------------
|
|
|
-
|
|
|
-void HorseRenderer::render(const DrawContext& ctx,
|
|
|
- const AnimationInputs& anim,
|
|
|
- const HorseProfile& profile,
|
|
|
- ISubmitter& out) const {
|
|
|
- const HorseDimensions& d = profile.dims;
|
|
|
- const HorseVariant& v = profile.variant;
|
|
|
- const HorseGait& g = profile.gait;
|
|
|
+void HorseRenderer::render(const DrawContext &ctx, const AnimationInputs &anim,
|
|
|
+ const HorseProfile &profile, ISubmitter &out) const {
|
|
|
+ const HorseDimensions &d = profile.dims;
|
|
|
+ const HorseVariant &v = profile.variant;
|
|
|
+ const HorseGait &g = profile.gait;
|
|
|
|
|
|
float phase = 0.0f;
|
|
|
- float bob = 0.0f;
|
|
|
+ float bob = 0.0f;
|
|
|
|
|
|
if (anim.isMoving) {
|
|
|
float cycle = std::max(0.20f, g.cycleTime);
|
|
|
phase = std::fmod(anim.time / cycle, 1.0f);
|
|
|
- bob = std::sin(phase * 2.0f * kPi) * d.moveBobAmplitude;
|
|
|
+ bob = std::sin(phase * 2.0f * kPi) * d.moveBobAmplitude;
|
|
|
} else {
|
|
|
phase = std::fmod(anim.time * 0.25f, 1.0f);
|
|
|
- bob = std::sin(phase * 2.0f * kPi) * d.idleBobAmplitude;
|
|
|
+ bob = std::sin(phase * 2.0f * kPi) * d.idleBobAmplitude;
|
|
|
}
|
|
|
|
|
|
- // subtle head nod synced with gait
|
|
|
float headNod = anim.isMoving ? std::sin((phase + 0.25f) * 2.0f * kPi) * 0.04f
|
|
|
: std::sin(anim.time * 1.5f) * 0.01f;
|
|
|
|
|
|
- // derive pseudo-random per-variant toggles (socks, star/blaze)
|
|
|
uint32_t vhash = colorHash(v.coatColor);
|
|
|
float sockChanceFL = hash01(vhash ^ 0x101u);
|
|
|
float sockChanceFR = hash01(vhash ^ 0x202u);
|
|
|
@@ -236,48 +207,48 @@ void HorseRenderer::render(const DrawContext& ctx,
|
|
|
bool hasBlaze = hash01(vhash ^ 0x505u) > 0.82f;
|
|
|
|
|
|
QVector3D barrelCenter(0.0f, d.barrelCenterY + bob, 0.0f);
|
|
|
- QVector3D chestCenter = barrelCenter + QVector3D(0.0f, d.bodyHeight * 0.12f, d.bodyLength * 0.34f);
|
|
|
- QVector3D rumpCenter = barrelCenter + QVector3D(0.0f, d.bodyHeight * 0.08f, -d.bodyLength * 0.36f);
|
|
|
- QVector3D bellyCenter = barrelCenter + QVector3D(0.0f,-d.bodyHeight * 0.35f, -d.bodyLength * 0.05f);
|
|
|
+ QVector3D chestCenter = barrelCenter + QVector3D(0.0f, d.bodyHeight * 0.12f,
|
|
|
+ d.bodyLength * 0.34f);
|
|
|
+ QVector3D rumpCenter = barrelCenter + QVector3D(0.0f, d.bodyHeight * 0.08f,
|
|
|
+ -d.bodyLength * 0.36f);
|
|
|
+ QVector3D bellyCenter = barrelCenter + QVector3D(0.0f, -d.bodyHeight * 0.35f,
|
|
|
+ -d.bodyLength * 0.05f);
|
|
|
|
|
|
- // ----------------
|
|
|
- // BODY MASS (torso)
|
|
|
- // ----------------
|
|
|
-
|
|
|
- // chest (pectorals / withers influence slightly darker top)
|
|
|
{
|
|
|
QMatrix4x4 chest = ctx.model;
|
|
|
chest.translate(chestCenter);
|
|
|
- chest.scale(d.bodyWidth * 1.12f, d.bodyHeight * 0.95f, d.bodyLength * 0.36f);
|
|
|
+ chest.scale(d.bodyWidth * 1.12f, d.bodyHeight * 0.95f,
|
|
|
+ d.bodyLength * 0.36f);
|
|
|
out.mesh(getUnitSphere(), chest, v.coatColor * 1.03f, nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
- // withers bulge
|
|
|
{
|
|
|
QMatrix4x4 withers = ctx.model;
|
|
|
- withers.translate(chestCenter + QVector3D(0.0f, d.bodyHeight * 0.55f, -d.bodyLength * 0.03f));
|
|
|
- withers.scale(d.bodyWidth * 0.75f, d.bodyHeight * 0.35f, d.bodyLength * 0.18f);
|
|
|
+ withers.translate(chestCenter + QVector3D(0.0f, d.bodyHeight * 0.55f,
|
|
|
+ -d.bodyLength * 0.03f));
|
|
|
+ withers.scale(d.bodyWidth * 0.75f, d.bodyHeight * 0.35f,
|
|
|
+ d.bodyLength * 0.18f);
|
|
|
out.mesh(getUnitSphere(), withers, v.coatColor * 0.96f, nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
- // belly (lighter underside / countershading)
|
|
|
{
|
|
|
QMatrix4x4 belly = ctx.model;
|
|
|
belly.translate(bellyCenter);
|
|
|
- belly.scale(d.bodyWidth * 0.98f, d.bodyHeight * 0.64f, d.bodyLength * 0.40f);
|
|
|
+ belly.scale(d.bodyWidth * 0.98f, d.bodyHeight * 0.64f,
|
|
|
+ d.bodyLength * 0.40f);
|
|
|
out.mesh(getUnitSphere(), belly, v.coatColor * 1.06f, nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
- // ribcage lateral rounding
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
|
- float side = (i==0) ? 1.0f : -1.0f;
|
|
|
+ float side = (i == 0) ? 1.0f : -1.0f;
|
|
|
QMatrix4x4 ribs = ctx.model;
|
|
|
- ribs.translate(barrelCenter + QVector3D(side * d.bodyWidth * 0.90f, -d.bodyHeight * 0.10f, -d.bodyLength * 0.05f));
|
|
|
+ ribs.translate(barrelCenter + QVector3D(side * d.bodyWidth * 0.90f,
|
|
|
+ -d.bodyHeight * 0.10f,
|
|
|
+ -d.bodyLength * 0.05f));
|
|
|
ribs.scale(d.bodyWidth * 0.38f, d.bodyHeight * 0.42f, d.bodyLength * 0.30f);
|
|
|
out.mesh(getUnitSphere(), ribs, v.coatColor * 1.00f, nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
- // rump / croup
|
|
|
{
|
|
|
QMatrix4x4 rump = ctx.model;
|
|
|
rump.translate(rumpCenter);
|
|
|
@@ -285,84 +256,122 @@ void HorseRenderer::render(const DrawContext& ctx,
|
|
|
out.mesh(getUnitSphere(), rump, v.coatColor * 0.98f, nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
- // hip bulges
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
|
- float side = (i==0) ? 1.0f : -1.0f;
|
|
|
+ float side = (i == 0) ? 1.0f : -1.0f;
|
|
|
QMatrix4x4 hip = ctx.model;
|
|
|
- hip.translate(rumpCenter + QVector3D(side * d.bodyWidth * 0.95f, -d.bodyHeight * 0.10f, -d.bodyLength * 0.08f));
|
|
|
+ hip.translate(rumpCenter + QVector3D(side * d.bodyWidth * 0.95f,
|
|
|
+ -d.bodyHeight * 0.10f,
|
|
|
+ -d.bodyLength * 0.08f));
|
|
|
hip.scale(d.bodyWidth * 0.45f, d.bodyHeight * 0.42f, d.bodyLength * 0.26f);
|
|
|
out.mesh(getUnitSphere(), hip, v.coatColor * 0.99f, nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
- // ----------------
|
|
|
- // NECK + HEAD
|
|
|
- // ----------------
|
|
|
+ QVector3D neckBase =
|
|
|
+ chestCenter + QVector3D(0.0f, d.bodyHeight * 0.38f, d.bodyLength * 0.06f);
|
|
|
+ QVector3D neckTop = neckBase + QVector3D(0.0f, d.neckRise, d.neckLength);
|
|
|
+ float neckRadius = d.bodyWidth * 0.42f;
|
|
|
|
|
|
- QVector3D neckBase = chestCenter + QVector3D(0.0f, d.bodyHeight * 0.38f, d.bodyLength * 0.06f);
|
|
|
- QVector3D neckTop = neckBase + QVector3D(0.0f, d.neckRise, d.neckLength);
|
|
|
- float neckRadius = d.bodyWidth * 0.42f;
|
|
|
-
|
|
|
- // neck S-curve: two blended cylinders to avoid a straight "pipe"
|
|
|
- QVector3D neckMid = lerpVec(neckBase, neckTop, 0.55f) + QVector3D(0.0f, d.bodyHeight * 0.02f, d.bodyLength * 0.02f);
|
|
|
- out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, neckBase, neckMid, neckRadius * 1.00f),
|
|
|
+ QVector3D neckMid =
|
|
|
+ lerpVec(neckBase, neckTop, 0.55f) +
|
|
|
+ QVector3D(0.0f, d.bodyHeight * 0.02f, d.bodyLength * 0.02f);
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ cylinderBetween(ctx.model, neckBase, neckMid, neckRadius * 1.00f),
|
|
|
v.coatColor * 1.03f, nullptr, 1.0f);
|
|
|
- out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, neckMid, neckTop, neckRadius * 0.86f),
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ cylinderBetween(ctx.model, neckMid, neckTop, neckRadius * 0.86f),
|
|
|
v.coatColor * 1.04f, nullptr, 1.0f);
|
|
|
|
|
|
- QVector3D headCenter = neckTop + QVector3D(0.0f, d.headHeight * (0.10f - headNod * 0.15f), d.headLength * 0.40f);
|
|
|
- // skull mass (forehead)
|
|
|
+ QVector3D headCenter =
|
|
|
+ neckTop + QVector3D(0.0f, d.headHeight * (0.10f - headNod * 0.15f),
|
|
|
+ d.headLength * 0.40f);
|
|
|
+
|
|
|
{
|
|
|
QMatrix4x4 skull = ctx.model;
|
|
|
- skull.translate(headCenter + QVector3D(0.0f, d.headHeight * 0.10f, -d.headLength * 0.10f));
|
|
|
- skull.scale(d.headWidth * 0.95f, d.headHeight * 0.90f, d.headLength * 0.80f);
|
|
|
+ skull.translate(headCenter + QVector3D(0.0f, d.headHeight * 0.10f,
|
|
|
+ -d.headLength * 0.10f));
|
|
|
+ skull.scale(d.headWidth * 0.95f, d.headHeight * 0.90f,
|
|
|
+ d.headLength * 0.80f);
|
|
|
out.mesh(getUnitSphere(), skull, v.coatColor * 1.05f, nullptr, 1.0f);
|
|
|
}
|
|
|
- // cheek / jaw mass
|
|
|
- for (int i=0;i<2;++i){
|
|
|
- float side = (i==0)?1.0f:-1.0f;
|
|
|
+
|
|
|
+ for (int i = 0; i < 2; ++i) {
|
|
|
+ float side = (i == 0) ? 1.0f : -1.0f;
|
|
|
QMatrix4x4 cheek = ctx.model;
|
|
|
- cheek.translate(headCenter + QVector3D(side * d.headWidth * 0.55f, -d.headHeight * 0.15f, 0.0f));
|
|
|
- cheek.scale(d.headWidth * 0.45f, d.headHeight * 0.50f, d.headLength * 0.60f);
|
|
|
+ cheek.translate(headCenter + QVector3D(side * d.headWidth * 0.55f,
|
|
|
+ -d.headHeight * 0.15f, 0.0f));
|
|
|
+ cheek.scale(d.headWidth * 0.45f, d.headHeight * 0.50f,
|
|
|
+ d.headLength * 0.60f);
|
|
|
out.mesh(getUnitSphere(), cheek, v.coatColor * 1.02f, nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
- // muzzle (tapered and slightly down-tilted)
|
|
|
- QVector3D muzzleCenter = headCenter + QVector3D(0.0f, -d.headHeight * 0.18f, d.headLength * 0.58f);
|
|
|
+ QVector3D muzzleCenter =
|
|
|
+ headCenter + QVector3D(0.0f, -d.headHeight * 0.18f, d.headLength * 0.58f);
|
|
|
{
|
|
|
QMatrix4x4 muzzle = ctx.model;
|
|
|
- muzzle.translate(muzzleCenter + QVector3D(0.0f, -d.headHeight * 0.05f, 0.0f));
|
|
|
- muzzle.scale(d.headWidth * 0.68f, d.headHeight * 0.60f, d.muzzleLength * 1.05f);
|
|
|
+ muzzle.translate(muzzleCenter +
|
|
|
+ QVector3D(0.0f, -d.headHeight * 0.05f, 0.0f));
|
|
|
+ muzzle.scale(d.headWidth * 0.68f, d.headHeight * 0.60f,
|
|
|
+ d.muzzleLength * 1.05f);
|
|
|
out.mesh(getUnitSphere(), muzzle, v.muzzleColor, nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
- // nostrils (dark inward cones)
|
|
|
{
|
|
|
- QVector3D nostrilBase = muzzleCenter + QVector3D(0.0f, -d.headHeight * 0.02f, d.muzzleLength * 0.60f);
|
|
|
- QVector3D leftBase = nostrilBase + QVector3D( d.headWidth * 0.26f, 0.0f, 0.0f);
|
|
|
- QVector3D rightBase = nostrilBase + QVector3D(-d.headWidth * 0.26f, 0.0f, 0.0f);
|
|
|
- QVector3D inward = QVector3D(0.0f, -d.headHeight * 0.02f, d.muzzleLength * -0.30f);
|
|
|
- out.mesh(getUnitCone(), coneFromTo(ctx.model, leftBase + inward, leftBase, d.headWidth * 0.11f), darken(v.muzzleColor, 0.6f), nullptr, 1.0f);
|
|
|
- out.mesh(getUnitCone(), coneFromTo(ctx.model, rightBase + inward, rightBase, d.headWidth * 0.11f), darken(v.muzzleColor, 0.6f), nullptr, 1.0f);
|
|
|
+ QVector3D nostrilBase =
|
|
|
+ muzzleCenter +
|
|
|
+ QVector3D(0.0f, -d.headHeight * 0.02f, d.muzzleLength * 0.60f);
|
|
|
+ QVector3D leftBase =
|
|
|
+ nostrilBase + QVector3D(d.headWidth * 0.26f, 0.0f, 0.0f);
|
|
|
+ QVector3D rightBase =
|
|
|
+ nostrilBase + QVector3D(-d.headWidth * 0.26f, 0.0f, 0.0f);
|
|
|
+ QVector3D inward =
|
|
|
+ QVector3D(0.0f, -d.headHeight * 0.02f, d.muzzleLength * -0.30f);
|
|
|
+ out.mesh(
|
|
|
+ getUnitCone(),
|
|
|
+ coneFromTo(ctx.model, leftBase + inward, leftBase, d.headWidth * 0.11f),
|
|
|
+ darken(v.muzzleColor, 0.6f), nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitCone(),
|
|
|
+ coneFromTo(ctx.model, rightBase + inward, rightBase,
|
|
|
+ d.headWidth * 0.11f),
|
|
|
+ darken(v.muzzleColor, 0.6f), nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
- // ears with slight flick (independent phase)
|
|
|
float earFlickL = std::sin(anim.time * 1.7f + 1.3f) * 0.15f;
|
|
|
float earFlickR = std::sin(anim.time * 1.9f + 2.1f) * -0.12f;
|
|
|
|
|
|
- QVector3D earBaseLeft = headCenter + QVector3D( d.headWidth * 0.45f, d.headHeight * 0.42f, -d.headLength * 0.20f);
|
|
|
- QVector3D earTipLeft = earBaseLeft + rotateAroundY(QVector3D(d.headWidth * 0.08f, d.headHeight * 0.42f, -d.headLength * 0.10f), earFlickL);
|
|
|
- QVector3D earBaseRight = headCenter + QVector3D(-d.headWidth * 0.45f, d.headHeight * 0.42f, -d.headLength * 0.20f);
|
|
|
- QVector3D earTipRight = earBaseRight + rotateAroundY(QVector3D(-d.headWidth * 0.08f, d.headHeight * 0.42f, -d.headLength * 0.10f), earFlickR);
|
|
|
-
|
|
|
- out.mesh(getUnitCone(), coneFromTo(ctx.model, earTipLeft, earBaseLeft, d.headWidth * 0.11f), v.maneColor, nullptr, 1.0f);
|
|
|
- out.mesh(getUnitCone(), coneFromTo(ctx.model, earTipRight, earBaseRight, d.headWidth * 0.11f), v.maneColor, nullptr, 1.0f);
|
|
|
-
|
|
|
- // eyes: dark globe + pupil + tiny highlight
|
|
|
- QVector3D eyeLeft = headCenter + QVector3D( d.headWidth * 0.48f, d.headHeight * 0.10f, d.headLength * 0.05f);
|
|
|
- QVector3D eyeRight = headCenter + QVector3D(-d.headWidth * 0.48f, d.headHeight * 0.10f, d.headLength * 0.05f);
|
|
|
+ QVector3D earBaseLeft =
|
|
|
+ headCenter + QVector3D(d.headWidth * 0.45f, d.headHeight * 0.42f,
|
|
|
+ -d.headLength * 0.20f);
|
|
|
+ QVector3D earTipLeft =
|
|
|
+ earBaseLeft +
|
|
|
+ rotateAroundY(QVector3D(d.headWidth * 0.08f, d.headHeight * 0.42f,
|
|
|
+ -d.headLength * 0.10f),
|
|
|
+ earFlickL);
|
|
|
+ QVector3D earBaseRight =
|
|
|
+ headCenter + QVector3D(-d.headWidth * 0.45f, d.headHeight * 0.42f,
|
|
|
+ -d.headLength * 0.20f);
|
|
|
+ QVector3D earTipRight =
|
|
|
+ earBaseRight +
|
|
|
+ rotateAroundY(QVector3D(-d.headWidth * 0.08f, d.headHeight * 0.42f,
|
|
|
+ -d.headLength * 0.10f),
|
|
|
+ earFlickR);
|
|
|
+
|
|
|
+ out.mesh(getUnitCone(),
|
|
|
+ coneFromTo(ctx.model, earTipLeft, earBaseLeft, d.headWidth * 0.11f),
|
|
|
+ v.maneColor, nullptr, 1.0f);
|
|
|
+ out.mesh(
|
|
|
+ getUnitCone(),
|
|
|
+ coneFromTo(ctx.model, earTipRight, earBaseRight, d.headWidth * 0.11f),
|
|
|
+ v.maneColor, nullptr, 1.0f);
|
|
|
+
|
|
|
+ QVector3D eyeLeft =
|
|
|
+ headCenter + QVector3D(d.headWidth * 0.48f, d.headHeight * 0.10f,
|
|
|
+ d.headLength * 0.05f);
|
|
|
+ QVector3D eyeRight =
|
|
|
+ headCenter + QVector3D(-d.headWidth * 0.48f, d.headHeight * 0.10f,
|
|
|
+ d.headLength * 0.05f);
|
|
|
QVector3D eyeBaseColor(0.10f, 0.10f, 0.10f);
|
|
|
|
|
|
- auto drawEye = [&](const QVector3D& pos) {
|
|
|
+ auto drawEye = [&](const QVector3D &pos) {
|
|
|
{
|
|
|
QMatrix4x4 eye = ctx.model;
|
|
|
eye.translate(pos);
|
|
|
@@ -370,121 +379,128 @@ void HorseRenderer::render(const DrawContext& ctx,
|
|
|
out.mesh(getUnitSphere(), eye, eyeBaseColor, nullptr, 1.0f);
|
|
|
}
|
|
|
{
|
|
|
- // pupil
|
|
|
+
|
|
|
QMatrix4x4 pupil = ctx.model;
|
|
|
pupil.translate(pos + QVector3D(0.0f, 0.0f, d.headWidth * 0.04f));
|
|
|
pupil.scale(d.headWidth * 0.05f);
|
|
|
- out.mesh(getUnitSphere(), pupil, QVector3D(0.03f, 0.03f, 0.03f), nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitSphere(), pupil, QVector3D(0.03f, 0.03f, 0.03f), nullptr,
|
|
|
+ 1.0f);
|
|
|
}
|
|
|
{
|
|
|
- // specular highlight
|
|
|
+
|
|
|
QMatrix4x4 spec = ctx.model;
|
|
|
- spec.translate(pos + QVector3D(d.headWidth * 0.03f, d.headWidth * 0.03f, d.headWidth * 0.03f));
|
|
|
+ spec.translate(pos + QVector3D(d.headWidth * 0.03f, d.headWidth * 0.03f,
|
|
|
+ d.headWidth * 0.03f));
|
|
|
spec.scale(d.headWidth * 0.02f);
|
|
|
- out.mesh(getUnitSphere(), spec, QVector3D(0.95f, 0.95f, 0.95f), nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitSphere(), spec, QVector3D(0.95f, 0.95f, 0.95f), nullptr,
|
|
|
+ 1.0f);
|
|
|
}
|
|
|
};
|
|
|
drawEye(eyeLeft);
|
|
|
drawEye(eyeRight);
|
|
|
|
|
|
- // subtle facial blaze/star (without new interfaces)
|
|
|
if (hasBlaze) {
|
|
|
QMatrix4x4 blaze = ctx.model;
|
|
|
- blaze.translate(headCenter + QVector3D(0.0f, d.headHeight * 0.15f, d.headLength * 0.10f));
|
|
|
- blaze.scale(d.headWidth * 0.22f, d.headHeight * 0.32f, d.headLength * 0.10f);
|
|
|
- out.mesh(getUnitSphere(), blaze, QVector3D(0.92f, 0.92f, 0.90f), nullptr, 1.0f);
|
|
|
+ blaze.translate(headCenter + QVector3D(0.0f, d.headHeight * 0.15f,
|
|
|
+ d.headLength * 0.10f));
|
|
|
+ blaze.scale(d.headWidth * 0.22f, d.headHeight * 0.32f,
|
|
|
+ d.headLength * 0.10f);
|
|
|
+ out.mesh(getUnitSphere(), blaze, QVector3D(0.92f, 0.92f, 0.90f), nullptr,
|
|
|
+ 1.0f);
|
|
|
}
|
|
|
|
|
|
- // mane: more segments, gentle gravity + gait sway
|
|
|
- QVector3D maneRoot = neckTop + QVector3D(0.0f, d.headHeight * 0.20f, -d.headLength * 0.20f);
|
|
|
+ QVector3D maneRoot =
|
|
|
+ neckTop + QVector3D(0.0f, d.headHeight * 0.20f, -d.headLength * 0.20f);
|
|
|
for (int i = 0; i < 12; ++i) {
|
|
|
float t = i / 11.0f;
|
|
|
QVector3D segStart = lerpVec(maneRoot, neckBase, t);
|
|
|
segStart.setY(segStart.y() + (0.07f - t * 0.05f));
|
|
|
- float sway = (anim.isMoving ? std::sin((phase + t * 0.15f) * 2.0f * kPi) * 0.04f
|
|
|
- : std::sin((anim.time * 0.8f + t * 2.3f)) * 0.02f);
|
|
|
- QVector3D segEnd = segStart + QVector3D(sway, 0.07f - t * 0.05f, -0.05f - t * 0.03f);
|
|
|
+ float sway =
|
|
|
+ (anim.isMoving ? std::sin((phase + t * 0.15f) * 2.0f * kPi) * 0.04f
|
|
|
+ : std::sin((anim.time * 0.8f + t * 2.3f)) * 0.02f);
|
|
|
+ QVector3D segEnd =
|
|
|
+ segStart + QVector3D(sway, 0.07f - t * 0.05f, -0.05f - t * 0.03f);
|
|
|
out.mesh(getUnitCylinder(),
|
|
|
- cylinderBetween(ctx.model, segStart, segEnd, d.headWidth * (0.10f * (1.0f - t * 0.4f))),
|
|
|
+ cylinderBetween(ctx.model, segStart, segEnd,
|
|
|
+ d.headWidth * (0.10f * (1.0f - t * 0.4f))),
|
|
|
v.maneColor * (0.98f + t * 0.05f), nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
- // ----------------
|
|
|
- // TAIL
|
|
|
- // ----------------
|
|
|
- QVector3D tailBase = rumpCenter + QVector3D(0.0f, d.bodyHeight * 0.36f, -d.bodyLength * 0.48f);
|
|
|
+ QVector3D tailBase =
|
|
|
+ rumpCenter + QVector3D(0.0f, d.bodyHeight * 0.36f, -d.bodyLength * 0.48f);
|
|
|
for (int i = 0; i < 8; ++i) {
|
|
|
float t = i / 8.0f;
|
|
|
- float swing = (anim.isMoving
|
|
|
- ? std::sin((phase + t * 0.10f) * 2.0f * kPi) * (0.05f + 0.02f * (1.0f - t))
|
|
|
- : std::sin((phase * 0.7f + t * 0.20f) * 2.0f * kPi) * 0.04f);
|
|
|
- QVector3D segStart = tailBase + QVector3D(swing, -i * 0.06f, -t * d.tailLength * 0.65f);
|
|
|
- QVector3D segEnd = segStart + QVector3D(swing * 0.6f, -0.08f - t * 0.05f, -d.tailLength * 0.16f);
|
|
|
+ float swing =
|
|
|
+ (anim.isMoving
|
|
|
+ ? std::sin((phase + t * 0.10f) * 2.0f * kPi) *
|
|
|
+ (0.05f + 0.02f * (1.0f - t))
|
|
|
+ : std::sin((phase * 0.7f + t * 0.20f) * 2.0f * kPi) * 0.04f);
|
|
|
+ QVector3D segStart =
|
|
|
+ tailBase + QVector3D(swing, -i * 0.06f, -t * d.tailLength * 0.65f);
|
|
|
+ QVector3D segEnd = segStart + QVector3D(swing * 0.6f, -0.08f - t * 0.05f,
|
|
|
+ -d.tailLength * 0.16f);
|
|
|
float radius = d.bodyWidth * (0.22f - 0.025f * i);
|
|
|
- out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, segStart, segEnd, radius),
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ cylinderBetween(ctx.model, segStart, segEnd, radius),
|
|
|
v.tailColor * (0.95f - 0.05f * i), nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
- // ----------------
|
|
|
- // LEGS
|
|
|
- // ----------------
|
|
|
- auto drawLeg = [&](const QVector3D& anchor, float lateralSign,
|
|
|
- float forwardBias, float phaseOffset,
|
|
|
- float sockChance) {
|
|
|
+ auto drawLeg = [&](const QVector3D &anchor, float lateralSign,
|
|
|
+ float forwardBias, float phaseOffset, float sockChance) {
|
|
|
float legPhase = std::fmod(phase + phaseOffset, 1.0f);
|
|
|
- float stride = 0.0f;
|
|
|
- float lift = 0.0f;
|
|
|
+ float stride = 0.0f;
|
|
|
+ float lift = 0.0f;
|
|
|
|
|
|
if (anim.isMoving) {
|
|
|
float angle = legPhase * 2.0f * kPi;
|
|
|
stride = std::sin(angle) * g.strideSwing + forwardBias;
|
|
|
float liftRaw = std::sin(angle);
|
|
|
- lift = liftRaw > 0.0f ? liftRaw * g.strideLift : liftRaw * g.strideLift * 0.22f;
|
|
|
+ lift = liftRaw > 0.0f ? liftRaw * g.strideLift
|
|
|
+ : liftRaw * g.strideLift * 0.22f;
|
|
|
} else {
|
|
|
float idle = std::sin(legPhase * 2.0f * kPi);
|
|
|
stride = idle * g.strideSwing * 0.06f + forwardBias;
|
|
|
- lift = idle * d.idleBobAmplitude * 2.0f;
|
|
|
+ lift = idle * d.idleBobAmplitude * 2.0f;
|
|
|
}
|
|
|
|
|
|
- // joint chain with more anatomical placement
|
|
|
- bool tightenLegs = anim.isMoving;
|
|
|
- float shoulderOut = d.bodyWidth * (tightenLegs ? 0.48f : 0.58f);
|
|
|
- QVector3D shoulder = anchor + QVector3D(lateralSign * shoulderOut,
|
|
|
- 0.05f + lift * 0.05f, stride);
|
|
|
- bool isRear = (forwardBias < 0.0f);
|
|
|
-
|
|
|
- // Full gallop cycle parameters
|
|
|
- float gallopAngle = legPhase * 2.0f * kPi;
|
|
|
- float hipSwing = anim.isMoving ? std::sin(gallopAngle) : 0.0f;
|
|
|
- float liftFactor = anim.isMoving ? std::max(0.0f, std::sin(gallopAngle + (isRear ? 0.35f : -0.25f))) : 0.0f;
|
|
|
-
|
|
|
- // Move whole hip forward/back slightly for stride reach
|
|
|
- shoulder.setZ(shoulder.z() + hipSwing * (isRear ? -0.12f : 0.10f));
|
|
|
- if (tightenLegs)
|
|
|
- shoulder.setX(shoulder.x() - lateralSign * liftFactor * 0.05f);
|
|
|
-
|
|
|
- // Thigh: majority of leg length; hips pitch heavily during gallop
|
|
|
- float thighLength = d.legLength * 0.55f;
|
|
|
- float hipPitch = hipSwing * (isRear ? 0.70f : 0.55f);
|
|
|
- float inwardLean = tightenLegs ? (-0.06f - liftFactor * 0.04f) : -0.015f;
|
|
|
- QVector3D thighDir(lateralSign * inwardLean,
|
|
|
- -std::cos(hipPitch) * 0.90f,
|
|
|
+ bool tightenLegs = anim.isMoving;
|
|
|
+ float shoulderOut = d.bodyWidth * (tightenLegs ? 0.48f : 0.58f);
|
|
|
+ QVector3D shoulder = anchor + QVector3D(lateralSign * shoulderOut,
|
|
|
+ 0.05f + lift * 0.05f, stride);
|
|
|
+ bool isRear = (forwardBias < 0.0f);
|
|
|
+
|
|
|
+ float gallopAngle = legPhase * 2.0f * kPi;
|
|
|
+ float hipSwing = anim.isMoving ? std::sin(gallopAngle) : 0.0f;
|
|
|
+ float liftFactor =
|
|
|
+ anim.isMoving
|
|
|
+ ? std::max(0.0f, std::sin(gallopAngle + (isRear ? 0.35f : -0.25f)))
|
|
|
+ : 0.0f;
|
|
|
+
|
|
|
+ shoulder.setZ(shoulder.z() + hipSwing * (isRear ? -0.12f : 0.10f));
|
|
|
+ if (tightenLegs)
|
|
|
+ shoulder.setX(shoulder.x() - lateralSign * liftFactor * 0.05f);
|
|
|
+
|
|
|
+ float thighLength = d.legLength * 0.55f;
|
|
|
+ float hipPitch = hipSwing * (isRear ? 0.70f : 0.55f);
|
|
|
+ float inwardLean = tightenLegs ? (-0.06f - liftFactor * 0.04f) : -0.015f;
|
|
|
+ QVector3D thighDir(lateralSign * inwardLean, -std::cos(hipPitch) * 0.90f,
|
|
|
(isRear ? -1.0f : 1.0f) * std::sin(hipPitch) * 0.65f);
|
|
|
if (thighDir.lengthSquared() > 1e-6f)
|
|
|
thighDir.normalize();
|
|
|
|
|
|
- QVector3D knee = shoulder + thighDir * thighLength;
|
|
|
- knee.setY(knee.y() + liftFactor * thighLength * 0.28f);
|
|
|
+ QVector3D knee = shoulder + thighDir * thighLength;
|
|
|
+ knee.setY(knee.y() + liftFactor * thighLength * 0.28f);
|
|
|
|
|
|
- // Knee flexion controls cannon orientation
|
|
|
- float kneeFlex = anim.isMoving ? clamp01(std::sin(gallopAngle + (isRear ? 0.65f : -0.45f)) * 0.7f + 0.45f)
|
|
|
- : 0.35f;
|
|
|
+ float kneeFlex =
|
|
|
+ anim.isMoving
|
|
|
+ ? clamp01(std::sin(gallopAngle + (isRear ? 0.65f : -0.45f)) * 0.7f +
|
|
|
+ 0.45f)
|
|
|
+ : 0.35f;
|
|
|
|
|
|
float forearmLength = d.legLength * 0.30f;
|
|
|
float bendCos = std::cos(kneeFlex * kPi * 0.5f);
|
|
|
float bendSin = std::sin(kneeFlex * kPi * 0.5f);
|
|
|
- QVector3D forearmDir(0.0f,
|
|
|
- -bendCos,
|
|
|
+ QVector3D forearmDir(0.0f, -bendCos,
|
|
|
(isRear ? -1.0f : 1.0f) * bendSin * 0.85f);
|
|
|
if (forearmDir.lengthSquared() < 1e-6f)
|
|
|
forearmDir = QVector3D(0.0f, -1.0f, 0.0f);
|
|
|
@@ -492,94 +508,103 @@ void HorseRenderer::render(const DrawContext& ctx,
|
|
|
forearmDir.normalize();
|
|
|
QVector3D cannon = knee + forearmDir * forearmLength;
|
|
|
|
|
|
- float pasternLength = d.legLength * 0.12f;
|
|
|
- QVector3D fetlock = cannon + QVector3D(0.0f, -pasternLength, 0.0f);
|
|
|
+ float pasternLength = d.legLength * 0.12f;
|
|
|
+ QVector3D fetlock = cannon + QVector3D(0.0f, -pasternLength, 0.0f);
|
|
|
|
|
|
- // hoof angle (toes down slightly during swing)
|
|
|
- float hoofPitch = anim.isMoving ? (-0.20f + std::sin(legPhase * 2.0f * kPi + (isRear?0.2f:-0.1f)) * 0.10f)
|
|
|
- : 0.0f;
|
|
|
+ float hoofPitch = anim.isMoving
|
|
|
+ ? (-0.20f + std::sin(legPhase * 2.0f * kPi +
|
|
|
+ (isRear ? 0.2f : -0.1f)) *
|
|
|
+ 0.10f)
|
|
|
+ : 0.0f;
|
|
|
QVector3D hoofDir = rotateAroundZ(QVector3D(0.0f, -1.0f, 0.0f), hoofPitch);
|
|
|
QVector3D hoofTop = fetlock;
|
|
|
QVector3D hoofBottom = hoofTop + hoofDir * d.hoofHeight;
|
|
|
|
|
|
- // MASSIVE muscular thigh with convex profile
|
|
|
float shoulderR = d.bodyWidth * (isRear ? 0.48f : 0.42f);
|
|
|
float thighBellyR = d.bodyWidth * (isRear ? 0.60f : 0.54f);
|
|
|
float kneeR = d.bodyWidth * 0.26f;
|
|
|
float cannonR = d.bodyWidth * 0.19f;
|
|
|
float pasternR = d.bodyWidth * 0.14f;
|
|
|
|
|
|
- QVector3D thighBelly = shoulder + (knee - shoulder) * 0.62f;
|
|
|
+ QVector3D thighBelly = shoulder + (knee - shoulder) * 0.62f;
|
|
|
|
|
|
- // Upper thigh - small at hip growing to belly
|
|
|
- out.mesh(getUnitCone(), coneFromTo(ctx.model, thighBelly, shoulder, thighBellyR),
|
|
|
+ out.mesh(getUnitCone(),
|
|
|
+ coneFromTo(ctx.model, thighBelly, shoulder, thighBellyR),
|
|
|
v.coatColor * 1.01f, nullptr, 1.0f);
|
|
|
|
|
|
{
|
|
|
QMatrix4x4 muscle = ctx.model;
|
|
|
- muscle.translate(thighBelly + QVector3D(0.0f, 0.0f, isRear ? -0.015f : 0.020f));
|
|
|
+ muscle.translate(thighBelly +
|
|
|
+ QVector3D(0.0f, 0.0f, isRear ? -0.015f : 0.020f));
|
|
|
muscle.scale(thighBellyR * QVector3D(1.05f, 0.85f, 0.92f));
|
|
|
out.mesh(getUnitSphere(), muscle, v.coatColor * 1.04f, nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
- // Lower thigh - belly down to knee, tapering
|
|
|
out.mesh(getUnitCone(), coneFromTo(ctx.model, knee, thighBelly, kneeR),
|
|
|
v.coatColor * 0.98f, nullptr, 1.0f);
|
|
|
|
|
|
{
|
|
|
- // Prominent knee joint with patella hint
|
|
|
+
|
|
|
QMatrix4x4 joint = ctx.model;
|
|
|
joint.translate(knee + QVector3D(0.0f, 0.0f, isRear ? -0.025f : 0.030f));
|
|
|
joint.scale(QVector3D(kneeR * 1.22f, kneeR * 1.08f, kneeR * 1.40f));
|
|
|
out.mesh(getUnitSphere(), joint, v.coatColor * 0.95f, nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
- // Forearm/cannon - power stroke
|
|
|
- out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, knee, cannon, cannonR),
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ cylinderBetween(ctx.model, knee, cannon, cannonR),
|
|
|
v.coatColor * 0.94f, nullptr, 1.0f);
|
|
|
-
|
|
|
+
|
|
|
{
|
|
|
- // Fetlock joint
|
|
|
+
|
|
|
QMatrix4x4 joint = ctx.model;
|
|
|
joint.translate(fetlock);
|
|
|
joint.scale(pasternR * 1.18f);
|
|
|
out.mesh(getUnitSphere(), joint, v.coatColor * 0.95f, nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
- // distal coloring (socks): lighten towards hoof if selected
|
|
|
- float sock = sockChance > 0.78f ? 1.0f : (sockChance > 0.58f ? 0.55f : 0.0f);
|
|
|
- QVector3D distalColor = (sock > 0.0f) ? lighten(v.coatColor, 1.18f) : v.coatColor * 0.92f;
|
|
|
+ float sock =
|
|
|
+ sockChance > 0.78f ? 1.0f : (sockChance > 0.58f ? 0.55f : 0.0f);
|
|
|
+ QVector3D distalColor =
|
|
|
+ (sock > 0.0f) ? lighten(v.coatColor, 1.18f) : v.coatColor * 0.92f;
|
|
|
float tSock = smoothstep(0.0f, 1.0f, sock);
|
|
|
|
|
|
- out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, cannon, fetlock, pasternR * 1.05f),
|
|
|
- lerpVec(v.coatColor*0.94f, distalColor, tSock * 0.8f), nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ cylinderBetween(ctx.model, cannon, fetlock, pasternR * 1.05f),
|
|
|
+ lerpVec(v.coatColor * 0.94f, distalColor, tSock * 0.8f), nullptr,
|
|
|
+ 1.0f);
|
|
|
|
|
|
- // hoof: short cone + flat bottom via tiny sphere suggestion
|
|
|
QVector3D hoofColor = v.hoofColor;
|
|
|
- out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, hoofTop, hoofBottom, pasternR * 0.95f),
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ cylinderBetween(ctx.model, hoofTop, hoofBottom, pasternR * 0.95f),
|
|
|
hoofColor, nullptr, 1.0f);
|
|
|
|
|
|
{
|
|
|
- // toe bevel (cone)
|
|
|
+
|
|
|
QVector3D toe = hoofBottom + QVector3D(0.0f, -d.hoofHeight * 0.15f, 0.0f);
|
|
|
- out.mesh(getUnitCone(), coneFromTo(ctx.model, toe, hoofBottom, pasternR * 0.88f), hoofColor * 0.96f, nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitCone(),
|
|
|
+ coneFromTo(ctx.model, toe, hoofBottom, pasternR * 0.88f),
|
|
|
+ hoofColor * 0.96f, nullptr, 1.0f);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- QVector3D frontAnchor = barrelCenter + QVector3D(0.0f, d.bodyHeight * 0.05f, d.bodyLength * 0.28f);
|
|
|
- QVector3D rearAnchor = barrelCenter + QVector3D(0.0f, d.bodyHeight * 0.02f, -d.bodyLength * 0.32f);
|
|
|
+ QVector3D frontAnchor = barrelCenter + QVector3D(0.0f, d.bodyHeight * 0.05f,
|
|
|
+ d.bodyLength * 0.28f);
|
|
|
+ QVector3D rearAnchor = barrelCenter + QVector3D(0.0f, d.bodyHeight * 0.02f,
|
|
|
+ -d.bodyLength * 0.32f);
|
|
|
|
|
|
- // FL, FR, RL, RR with per-variant sock chances
|
|
|
- drawLeg(frontAnchor, 1.0f, d.bodyLength * 0.30f, g.frontLegPhase, sockChanceFL);
|
|
|
- drawLeg(frontAnchor, -1.0f, d.bodyLength * 0.30f, g.frontLegPhase + 0.5f, sockChanceFR);
|
|
|
- drawLeg(rearAnchor, 1.0f, -d.bodyLength * 0.28f, g.rearLegPhase, sockChanceRL);
|
|
|
- drawLeg(rearAnchor, -1.0f, -d.bodyLength * 0.28f, g.rearLegPhase + 0.5f, sockChanceRR);
|
|
|
+ drawLeg(frontAnchor, 1.0f, d.bodyLength * 0.30f, g.frontLegPhase,
|
|
|
+ sockChanceFL);
|
|
|
+ drawLeg(frontAnchor, -1.0f, d.bodyLength * 0.30f, g.frontLegPhase + 0.5f,
|
|
|
+ sockChanceFR);
|
|
|
+ drawLeg(rearAnchor, 1.0f, -d.bodyLength * 0.28f, g.rearLegPhase,
|
|
|
+ sockChanceRL);
|
|
|
+ drawLeg(rearAnchor, -1.0f, -d.bodyLength * 0.28f, g.rearLegPhase + 0.5f,
|
|
|
+ sockChanceRR);
|
|
|
|
|
|
- // ----------------
|
|
|
- // SADDLE + TACK
|
|
|
- // ----------------
|
|
|
float saddleTop = d.saddleHeight;
|
|
|
- QVector3D saddleCenter(0.0f, saddleTop - d.saddleThickness * 0.3f, d.seatForwardOffset * 0.4f);
|
|
|
+ QVector3D saddleCenter(0.0f, saddleTop - d.saddleThickness * 0.3f,
|
|
|
+ d.seatForwardOffset * 0.4f);
|
|
|
{
|
|
|
QMatrix4x4 saddle = ctx.model;
|
|
|
saddle.translate(saddleCenter);
|
|
|
@@ -587,79 +612,121 @@ void HorseRenderer::render(const DrawContext& ctx,
|
|
|
out.mesh(getUnitSphere(), saddle, v.saddleColor, nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
- // blanket (slightly softer/lighter)
|
|
|
- QVector3D blanketCenter = saddleCenter + QVector3D(0.0f, -d.saddleThickness, 0.0f);
|
|
|
+ QVector3D blanketCenter =
|
|
|
+ saddleCenter + QVector3D(0.0f, -d.saddleThickness, 0.0f);
|
|
|
{
|
|
|
QMatrix4x4 blanket = ctx.model;
|
|
|
blanket.translate(blanketCenter);
|
|
|
- blanket.scale(d.bodyWidth * 1.24f, d.saddleThickness * 0.35f, d.bodyLength * 0.40f);
|
|
|
+ blanket.scale(d.bodyWidth * 1.24f, d.saddleThickness * 0.35f,
|
|
|
+ d.bodyLength * 0.40f);
|
|
|
out.mesh(getUnitSphere(), blanket, v.blanketColor * 1.02f, nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
- // girth straps
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
|
float side = (i == 0) ? 1.0f : -1.0f;
|
|
|
- QVector3D strapTop = saddleCenter + QVector3D(side * d.bodyWidth * 0.92f, d.saddleThickness * 0.35f, 0.0f);
|
|
|
- QVector3D strapBottom = strapTop + QVector3D(0.0f, -d.bodyHeight * 0.95f, 0.02f);
|
|
|
- out.mesh(getUnitCylinder(),
|
|
|
- cylinderBetween(ctx.model, strapTop, strapBottom, d.bodyWidth * 0.07f),
|
|
|
- v.tackColor * 0.94f, nullptr, 1.0f);
|
|
|
+ QVector3D strapTop =
|
|
|
+ saddleCenter +
|
|
|
+ QVector3D(side * d.bodyWidth * 0.92f, d.saddleThickness * 0.35f, 0.0f);
|
|
|
+ QVector3D strapBottom =
|
|
|
+ strapTop + QVector3D(0.0f, -d.bodyHeight * 0.95f, 0.02f);
|
|
|
+ out.mesh(
|
|
|
+ getUnitCylinder(),
|
|
|
+ cylinderBetween(ctx.model, strapTop, strapBottom, d.bodyWidth * 0.07f),
|
|
|
+ v.tackColor * 0.94f, nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
- // stirrups
|
|
|
auto drawStirrup = [&](float sideSign) {
|
|
|
- QVector3D stirrupAttach = saddleCenter + QVector3D(sideSign * d.bodyWidth * 0.92f, -d.saddleThickness * 0.1f, d.seatForwardOffset * 0.3f);
|
|
|
- QVector3D stirrupBottom = stirrupAttach + QVector3D(0.0f, -d.stirrupDrop, 0.0f);
|
|
|
+ QVector3D stirrupAttach =
|
|
|
+ saddleCenter + QVector3D(sideSign * d.bodyWidth * 0.92f,
|
|
|
+ -d.saddleThickness * 0.1f,
|
|
|
+ d.seatForwardOffset * 0.3f);
|
|
|
+ QVector3D stirrupBottom =
|
|
|
+ stirrupAttach + QVector3D(0.0f, -d.stirrupDrop, 0.0f);
|
|
|
|
|
|
out.mesh(getUnitCylinder(),
|
|
|
- cylinderBetween(ctx.model, stirrupAttach, stirrupBottom, d.bodyWidth * 0.05f),
|
|
|
+ cylinderBetween(ctx.model, stirrupAttach, stirrupBottom,
|
|
|
+ d.bodyWidth * 0.05f),
|
|
|
v.tackColor, nullptr, 1.0f);
|
|
|
|
|
|
QMatrix4x4 stirrup = ctx.model;
|
|
|
- stirrup.translate(stirrupBottom + QVector3D(0.0f, -d.bodyWidth * 0.06f, 0.0f));
|
|
|
- stirrup.scale(d.bodyWidth * 0.20f, d.bodyWidth * 0.07f, d.bodyWidth * 0.16f);
|
|
|
- out.mesh(getUnitSphere(), stirrup, QVector3D(0.66f, 0.65f, 0.62f), nullptr, 1.0f);
|
|
|
+ stirrup.translate(stirrupBottom +
|
|
|
+ QVector3D(0.0f, -d.bodyWidth * 0.06f, 0.0f));
|
|
|
+ stirrup.scale(d.bodyWidth * 0.20f, d.bodyWidth * 0.07f,
|
|
|
+ d.bodyWidth * 0.16f);
|
|
|
+ out.mesh(getUnitSphere(), stirrup, QVector3D(0.66f, 0.65f, 0.62f), nullptr,
|
|
|
+ 1.0f);
|
|
|
};
|
|
|
|
|
|
- drawStirrup( 1.0f);
|
|
|
+ drawStirrup(1.0f);
|
|
|
drawStirrup(-1.0f);
|
|
|
|
|
|
- // bridle (cheek straps + noseband + browband), add bit ring & short reins hint
|
|
|
- QVector3D cheekLeftTop = headCenter + QVector3D( d.headWidth * 0.60f, -d.headHeight * 0.10f, d.headLength * 0.25f);
|
|
|
- QVector3D cheekLeftBottom = cheekLeftTop + QVector3D(0.0f, -d.headHeight, -d.headLength * 0.12f);
|
|
|
- QVector3D cheekRightTop = headCenter + QVector3D(-d.headWidth * 0.60f, -d.headHeight * 0.10f, d.headLength * 0.25f);
|
|
|
- QVector3D cheekRightBottom = cheekRightTop + QVector3D(0.0f, -d.headHeight, -d.headLength * 0.12f);
|
|
|
-
|
|
|
- out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, cheekLeftTop, cheekLeftBottom, d.headWidth * 0.08f), v.tackColor, nullptr, 1.0f);
|
|
|
- out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, cheekRightTop, cheekRightBottom, d.headWidth * 0.08f), v.tackColor, nullptr, 1.0f);
|
|
|
+ QVector3D cheekLeftTop =
|
|
|
+ headCenter + QVector3D(d.headWidth * 0.60f, -d.headHeight * 0.10f,
|
|
|
+ d.headLength * 0.25f);
|
|
|
+ QVector3D cheekLeftBottom =
|
|
|
+ cheekLeftTop + QVector3D(0.0f, -d.headHeight, -d.headLength * 0.12f);
|
|
|
+ QVector3D cheekRightTop =
|
|
|
+ headCenter + QVector3D(-d.headWidth * 0.60f, -d.headHeight * 0.10f,
|
|
|
+ d.headLength * 0.25f);
|
|
|
+ QVector3D cheekRightBottom =
|
|
|
+ cheekRightTop + QVector3D(0.0f, -d.headHeight, -d.headLength * 0.12f);
|
|
|
+
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ cylinderBetween(ctx.model, cheekLeftTop, cheekLeftBottom,
|
|
|
+ d.headWidth * 0.08f),
|
|
|
+ v.tackColor, nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ cylinderBetween(ctx.model, cheekRightTop, cheekRightBottom,
|
|
|
+ d.headWidth * 0.08f),
|
|
|
+ v.tackColor, nullptr, 1.0f);
|
|
|
|
|
|
- QVector3D noseBandFront = muzzleCenter + QVector3D(0.0f, d.headHeight * 0.02f, d.muzzleLength * 0.35f);
|
|
|
- QVector3D noseBandLeft = noseBandFront + QVector3D( d.headWidth * 0.55f, 0.0f, 0.0f);
|
|
|
- QVector3D noseBandRight = noseBandFront + QVector3D(-d.headWidth * 0.55f, 0.0f, 0.0f);
|
|
|
- out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, noseBandLeft, noseBandRight, d.headWidth * 0.08f),
|
|
|
+ QVector3D noseBandFront = muzzleCenter + QVector3D(0.0f, d.headHeight * 0.02f,
|
|
|
+ d.muzzleLength * 0.35f);
|
|
|
+ QVector3D noseBandLeft =
|
|
|
+ noseBandFront + QVector3D(d.headWidth * 0.55f, 0.0f, 0.0f);
|
|
|
+ QVector3D noseBandRight =
|
|
|
+ noseBandFront + QVector3D(-d.headWidth * 0.55f, 0.0f, 0.0f);
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ cylinderBetween(ctx.model, noseBandLeft, noseBandRight,
|
|
|
+ d.headWidth * 0.08f),
|
|
|
v.tackColor * 0.92f, nullptr, 1.0f);
|
|
|
|
|
|
- QVector3D browBandFront = headCenter + QVector3D(0.0f, d.headHeight * 0.28f, d.headLength * 0.15f);
|
|
|
- QVector3D browBandLeft = browBandFront + QVector3D( d.headWidth * 0.58f, 0.0f, 0.0f);
|
|
|
- QVector3D browBandRight = browBandFront + QVector3D(-d.headWidth * 0.58f, 0.0f, 0.0f);
|
|
|
- out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, browBandLeft, browBandRight, d.headWidth * 0.07f),
|
|
|
+ QVector3D browBandFront =
|
|
|
+ headCenter + QVector3D(0.0f, d.headHeight * 0.28f, d.headLength * 0.15f);
|
|
|
+ QVector3D browBandLeft =
|
|
|
+ browBandFront + QVector3D(d.headWidth * 0.58f, 0.0f, 0.0f);
|
|
|
+ QVector3D browBandRight =
|
|
|
+ browBandFront + QVector3D(-d.headWidth * 0.58f, 0.0f, 0.0f);
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ cylinderBetween(ctx.model, browBandLeft, browBandRight,
|
|
|
+ d.headWidth * 0.07f),
|
|
|
v.tackColor, nullptr, 1.0f);
|
|
|
|
|
|
- // bit & short reins indication
|
|
|
- QVector3D bitLeft = muzzleCenter + QVector3D( d.headWidth * 0.55f, -d.headHeight * 0.08f, d.muzzleLength * 0.10f);
|
|
|
- QVector3D bitRight = muzzleCenter + QVector3D(-d.headWidth * 0.55f, -d.headHeight * 0.08f, d.muzzleLength * 0.10f);
|
|
|
- out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, bitLeft, bitRight, d.headWidth * 0.05f),
|
|
|
+ QVector3D bitLeft =
|
|
|
+ muzzleCenter + QVector3D(d.headWidth * 0.55f, -d.headHeight * 0.08f,
|
|
|
+ d.muzzleLength * 0.10f);
|
|
|
+ QVector3D bitRight =
|
|
|
+ muzzleCenter + QVector3D(-d.headWidth * 0.55f, -d.headHeight * 0.08f,
|
|
|
+ d.muzzleLength * 0.10f);
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ cylinderBetween(ctx.model, bitLeft, bitRight, d.headWidth * 0.05f),
|
|
|
QVector3D(0.55f, 0.55f, 0.55f), nullptr, 1.0f);
|
|
|
|
|
|
- // short reins to saddle area (loose)
|
|
|
- for (int i=0;i<2;++i){
|
|
|
- float side = (i==0)?1.0f:-1.0f;
|
|
|
- QVector3D reinStart = (i==0)? bitLeft : bitRight;
|
|
|
- QVector3D reinEnd = saddleCenter + QVector3D(side * d.bodyWidth * 0.65f, -d.saddleThickness * 0.3f, d.seatForwardOffset * 0.15f);
|
|
|
- // add a small sag in the middle
|
|
|
- QVector3D mid = lerpVec(reinStart, reinEnd, 0.5f) + QVector3D(0.0f, -d.bodyHeight * 0.10f, 0.0f);
|
|
|
- out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, reinStart, mid, d.bodyWidth * 0.02f), v.tackColor * 0.95f, nullptr, 1.0f);
|
|
|
- out.mesh(getUnitCylinder(), cylinderBetween(ctx.model, mid, reinEnd, d.bodyWidth * 0.02f), v.tackColor * 0.95f, nullptr, 1.0f);
|
|
|
+ for (int i = 0; i < 2; ++i) {
|
|
|
+ float side = (i == 0) ? 1.0f : -1.0f;
|
|
|
+ QVector3D reinStart = (i == 0) ? bitLeft : bitRight;
|
|
|
+ QVector3D reinEnd = saddleCenter + QVector3D(side * d.bodyWidth * 0.65f,
|
|
|
+ -d.saddleThickness * 0.3f,
|
|
|
+ d.seatForwardOffset * 0.15f);
|
|
|
+
|
|
|
+ QVector3D mid = lerpVec(reinStart, reinEnd, 0.5f) +
|
|
|
+ QVector3D(0.0f, -d.bodyHeight * 0.10f, 0.0f);
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ cylinderBetween(ctx.model, reinStart, mid, d.bodyWidth * 0.02f),
|
|
|
+ v.tackColor * 0.95f, nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ cylinderBetween(ctx.model, mid, reinEnd, d.bodyWidth * 0.02f),
|
|
|
+ v.tackColor * 0.95f, nullptr, 1.0f);
|
|
|
}
|
|
|
}
|
|
|
|