|
|
@@ -1,318 +1,420 @@
|
|
|
#include "archer_renderer.h"
|
|
|
#include "../../game/core/component.h"
|
|
|
#include "../../game/core/entity.h"
|
|
|
+#include "../../game/core/world.h"
|
|
|
#include "../../game/visuals/team_colors.h"
|
|
|
+#include "../geom/math_utils.h"
|
|
|
#include "../geom/selection_ring.h"
|
|
|
+#include "../geom/transforms.h"
|
|
|
#include "../gl/mesh.h"
|
|
|
+#include "../gl/primitives.h"
|
|
|
#include "../gl/texture.h"
|
|
|
#include "registry.h"
|
|
|
+
|
|
|
#include <QMatrix4x4>
|
|
|
#include <QVector3D>
|
|
|
-#include <algorithm>
|
|
|
#include <cmath>
|
|
|
-#include <memory>
|
|
|
-#include <vector>
|
|
|
+#include <cstdint>
|
|
|
|
|
|
namespace Render::GL {
|
|
|
|
|
|
-static Mesh *createUnitCylinderMesh() {
|
|
|
- const int radial = 24;
|
|
|
- const float radius = 1.0f;
|
|
|
- const float halfH = 0.5f;
|
|
|
- std::vector<Vertex> v;
|
|
|
- std::vector<unsigned int> idx;
|
|
|
-
|
|
|
- for (int y = 0; y <= 1; ++y) {
|
|
|
- float py = y ? halfH : -halfH;
|
|
|
- float vCoord = float(y);
|
|
|
- for (int i = 0; i <= radial; ++i) {
|
|
|
- float u = float(i) / float(radial);
|
|
|
- float ang = u * 6.28318530718f;
|
|
|
- float px = radius * std::cos(ang);
|
|
|
- float pz = radius * std::sin(ang);
|
|
|
- QVector3D n(px, 0.0f, pz);
|
|
|
- n.normalize();
|
|
|
- v.push_back({{px, py, pz}, {n.x(), n.y(), n.z()}, {u, vCoord}});
|
|
|
+using Render::Geom::clamp01;
|
|
|
+using Render::Geom::clampf;
|
|
|
+using Render::Geom::clampVec01;
|
|
|
+using Render::Geom::coneFromTo;
|
|
|
+using Render::Geom::cylinderBetween;
|
|
|
+using Render::Geom::sphereAt;
|
|
|
+
|
|
|
+struct HumanProportions {
|
|
|
+
|
|
|
+ static constexpr float TOTAL_HEIGHT = 2.00f;
|
|
|
+ static constexpr float HEAD_HEIGHT = 0.25f;
|
|
|
+
|
|
|
+ static constexpr float GROUND_Y = 0.0f;
|
|
|
+ static constexpr float HEAD_TOP_Y = GROUND_Y + TOTAL_HEIGHT;
|
|
|
+ static constexpr float CHIN_Y = HEAD_TOP_Y - HEAD_HEIGHT;
|
|
|
+ static constexpr float NECK_BASE_Y = CHIN_Y - 0.10f;
|
|
|
+ static constexpr float SHOULDER_Y = NECK_BASE_Y - 0.15f;
|
|
|
+ static constexpr float CHEST_Y = SHOULDER_Y - 0.35f;
|
|
|
+ static constexpr float WAIST_Y = CHEST_Y - 0.30f;
|
|
|
+ static constexpr float HIP_Y = WAIST_Y - 0.15f;
|
|
|
+ static constexpr float KNEE_Y = HIP_Y - 0.35f;
|
|
|
+
|
|
|
+ static constexpr float SHOULDER_WIDTH = HEAD_HEIGHT * 1.6f;
|
|
|
+ static constexpr float HEAD_RADIUS = HEAD_HEIGHT * 0.40f;
|
|
|
+ static constexpr float NECK_RADIUS = HEAD_RADIUS * 0.35f;
|
|
|
+ static constexpr float TORSO_TOP_R = HEAD_RADIUS * 1.0f;
|
|
|
+ static constexpr float TORSO_BOT_R = HEAD_RADIUS * 0.9f;
|
|
|
+ static constexpr float UPPER_ARM_R = HEAD_RADIUS * 0.30f;
|
|
|
+ static constexpr float FORE_ARM_R = HEAD_RADIUS * 0.25f;
|
|
|
+ static constexpr float HAND_RADIUS = HEAD_RADIUS * 0.22f;
|
|
|
+ static constexpr float UPPER_LEG_R = HEAD_RADIUS * 0.38f;
|
|
|
+ static constexpr float LOWER_LEG_R = HEAD_RADIUS * 0.32f;
|
|
|
+
|
|
|
+ static constexpr float UPPER_ARM_LEN = 0.28f;
|
|
|
+ static constexpr float FORE_ARM_LEN = 0.30f;
|
|
|
+ static constexpr float UPPER_LEG_LEN = 0.35f;
|
|
|
+ static constexpr float LOWER_LEG_LEN = 0.35f;
|
|
|
+};
|
|
|
+
|
|
|
+struct ArcherColors {
|
|
|
+ QVector3D tunic, skin, leather, leatherDark, wood, metal, metalHead,
|
|
|
+ stringCol, fletch;
|
|
|
+};
|
|
|
+
|
|
|
+struct ArcherPose {
|
|
|
+ using P = HumanProportions;
|
|
|
+
|
|
|
+ QVector3D headPos{0.0f, (P::HEAD_TOP_Y + P::CHIN_Y) * 0.5f, 0.0f};
|
|
|
+ float headR = P::HEAD_RADIUS;
|
|
|
+ QVector3D neckBase{0.0f, P::NECK_BASE_Y, 0.0f};
|
|
|
+
|
|
|
+ QVector3D shoulderL{-P::SHOULDER_WIDTH * 0.5f, P::SHOULDER_Y, 0.1f};
|
|
|
+ QVector3D shoulderR{P::SHOULDER_WIDTH * 0.5f, P::SHOULDER_Y, 0.1f};
|
|
|
+
|
|
|
+ QVector3D elbowL, elbowR;
|
|
|
+ QVector3D handL, handR;
|
|
|
+
|
|
|
+ float hipSpacing = P::SHOULDER_WIDTH * 0.55f;
|
|
|
+
|
|
|
+ float hipXFactor = 0.45f;
|
|
|
+ float hipZOffset = 0.01f;
|
|
|
+ QVector3D hipL{-hipSpacing * hipXFactor,
|
|
|
+ std::max(P::HIP_Y + 0.05f, P::GROUND_Y + 0.3f), hipZOffset};
|
|
|
+ QVector3D hipR{hipSpacing * hipXFactor,
|
|
|
+ std::max(P::HIP_Y + 0.05f, P::GROUND_Y + 0.3f), -hipZOffset};
|
|
|
+
|
|
|
+ float footYOffset = 0.02f;
|
|
|
+ QVector3D footL{-hipSpacing * 1.05f, P::GROUND_Y + footYOffset, 0.18f};
|
|
|
+ QVector3D footR{hipSpacing * 1.05f, P::GROUND_Y + footYOffset, -0.14f};
|
|
|
+ float bowX = 0.0f;
|
|
|
+ float bowTopY = P::SHOULDER_Y + 0.55f;
|
|
|
+ float bowBotY = P::HIP_Y - 0.25f;
|
|
|
+ float bowRodR = 0.035f;
|
|
|
+ float stringR = 0.008f;
|
|
|
+ float bowDepth = 0.25f;
|
|
|
+};
|
|
|
+
|
|
|
+static inline ArcherPose makePose(uint32_t seed, float animTime, bool isMoving,
|
|
|
+ bool isAttacking) {
|
|
|
+ (void)seed;
|
|
|
+ ArcherPose P;
|
|
|
+
|
|
|
+ using HP = HumanProportions;
|
|
|
+
|
|
|
+ P.handL = QVector3D(P.bowX - 0.05f, HP::SHOULDER_Y + 0.05f, 0.55f);
|
|
|
+ P.handR = QVector3D(0.15f, HP::SHOULDER_Y + 0.15f, 0.20f);
|
|
|
+
|
|
|
+ if (isAttacking) {
|
|
|
+ float attackCycleTime = 1.2f;
|
|
|
+ float attackPhase = fmod(animTime * (1.0f / attackCycleTime), 1.0f);
|
|
|
+
|
|
|
+ QVector3D restPos(0.15f, HP::SHOULDER_Y + 0.15f, 0.20f);
|
|
|
+ QVector3D drawPos(0.35f, HP::SHOULDER_Y + 0.08f, -0.15f);
|
|
|
+
|
|
|
+ if (attackPhase < 0.3f) {
|
|
|
+
|
|
|
+ float t = attackPhase / 0.3f;
|
|
|
+ t = t * t;
|
|
|
+ P.handR = restPos * (1.0f - t) + drawPos * t;
|
|
|
+ } else if (attackPhase < 0.6f) {
|
|
|
+
|
|
|
+ P.handR = drawPos;
|
|
|
+ } else {
|
|
|
+
|
|
|
+ float t = (attackPhase - 0.6f) / 0.4f;
|
|
|
+ t = 1.0f - (1.0f - t) * (1.0f - t);
|
|
|
+ P.handR = drawPos * (1.0f - t) + restPos * t;
|
|
|
}
|
|
|
}
|
|
|
- int row = radial + 1;
|
|
|
-
|
|
|
- for (int i = 0; i < radial; ++i) {
|
|
|
- int a = 0 * row + i;
|
|
|
- int b = 0 * row + i + 1;
|
|
|
- int c = 1 * row + i + 1;
|
|
|
- int d = 1 * row + i;
|
|
|
- idx.push_back(a);
|
|
|
- idx.push_back(b);
|
|
|
- idx.push_back(c);
|
|
|
- idx.push_back(c);
|
|
|
- idx.push_back(d);
|
|
|
- idx.push_back(a);
|
|
|
- }
|
|
|
|
|
|
- int baseTop = (int)v.size();
|
|
|
- v.push_back({{0.0f, halfH, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.5f, 0.5f}});
|
|
|
- for (int i = 0; i <= radial; ++i) {
|
|
|
- float u = float(i) / float(radial);
|
|
|
- float ang = u * 6.28318530718f;
|
|
|
- float px = radius * std::cos(ang);
|
|
|
- float pz = radius * std::sin(ang);
|
|
|
- v.push_back({{px, halfH, pz},
|
|
|
- {0.0f, 1.0f, 0.0f},
|
|
|
- {0.5f + 0.5f * std::cos(ang), 0.5f + 0.5f * std::sin(ang)}});
|
|
|
- }
|
|
|
- for (int i = 1; i <= radial; ++i) {
|
|
|
- idx.push_back(baseTop);
|
|
|
- idx.push_back(baseTop + i);
|
|
|
- idx.push_back(baseTop + i + 1);
|
|
|
- }
|
|
|
+ if (isMoving) {
|
|
|
+ float walkCycleTime = 0.8f;
|
|
|
+ float walkPhase = fmod(animTime * (1.0f / walkCycleTime), 1.0f);
|
|
|
+ float leftPhase = walkPhase;
|
|
|
+ float rightPhase = fmod(walkPhase + 0.5f, 1.0f);
|
|
|
|
|
|
- int baseBot = (int)v.size();
|
|
|
- v.push_back({{0.0f, -halfH, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.5f, 0.5f}});
|
|
|
- for (int i = 0; i <= radial; ++i) {
|
|
|
- float u = float(i) / float(radial);
|
|
|
- float ang = u * 6.28318530718f;
|
|
|
- float px = radius * std::cos(ang);
|
|
|
- float pz = radius * std::sin(ang);
|
|
|
- v.push_back({{px, -halfH, pz},
|
|
|
- {0.0f, -1.0f, 0.0f},
|
|
|
- {0.5f + 0.5f * std::cos(ang), 0.5f + 0.5f * std::sin(ang)}});
|
|
|
- }
|
|
|
- for (int i = 1; i <= radial; ++i) {
|
|
|
- idx.push_back(baseBot);
|
|
|
- idx.push_back(baseBot + i + 1);
|
|
|
- idx.push_back(baseBot + i);
|
|
|
- }
|
|
|
- return new Mesh(v, idx);
|
|
|
-}
|
|
|
+ const float footYOffset = P.footYOffset;
|
|
|
+ const float groundY = HP::GROUND_Y;
|
|
|
|
|
|
-static Mesh *createUnitSphereMesh() {
|
|
|
- const int lat = 12;
|
|
|
- const int lon = 24;
|
|
|
- const float r = 1.0f;
|
|
|
- std::vector<Vertex> v;
|
|
|
- std::vector<unsigned int> idx;
|
|
|
-
|
|
|
- for (int y = 0; y <= lat; ++y) {
|
|
|
- float vy = float(y) / float(lat);
|
|
|
- float phi = vy * 3.1415926535f;
|
|
|
- float py = r * std::cos(phi - 1.57079632679f);
|
|
|
- float pr = r * std::sin(phi);
|
|
|
- for (int x = 0; x <= lon; ++x) {
|
|
|
- float vx = float(x) / float(lon);
|
|
|
- float theta = vx * 6.28318530718f;
|
|
|
- float px = pr * std::cos(theta);
|
|
|
- float pz = pr * std::sin(theta);
|
|
|
- QVector3D n(px, py, pz);
|
|
|
- n.normalize();
|
|
|
- v.push_back({{px, py, pz}, {n.x(), n.y(), n.z()}, {vx, vy}});
|
|
|
- }
|
|
|
- }
|
|
|
- int row = lon + 1;
|
|
|
- for (int y = 0; y < lat; ++y) {
|
|
|
- for (int x = 0; x < lon; ++x) {
|
|
|
- int a = y * row + x;
|
|
|
- int b = a + 1;
|
|
|
- int c = (y + 1) * row + x + 1;
|
|
|
- int d = (y + 1) * row + x;
|
|
|
- idx.push_back(a);
|
|
|
- idx.push_back(b);
|
|
|
- idx.push_back(c);
|
|
|
- idx.push_back(c);
|
|
|
- idx.push_back(d);
|
|
|
- idx.push_back(a);
|
|
|
- }
|
|
|
- }
|
|
|
- return new Mesh(v, idx);
|
|
|
-}
|
|
|
+ auto animateFoot = [groundY, footYOffset](QVector3D &foot, float phase) {
|
|
|
+ float lift = std::sin(phase * 2.0f * 3.14159f);
|
|
|
+ if (lift > 0.0f) {
|
|
|
+ foot.setY(groundY + footYOffset + lift * 0.15f);
|
|
|
+ }
|
|
|
|
|
|
-static Mesh *createUnitConeMesh() {
|
|
|
- const int radial = 24;
|
|
|
- const float baseR = 1.0f;
|
|
|
- const float halfH = 0.5f;
|
|
|
- std::vector<Vertex> v;
|
|
|
- std::vector<unsigned int> idx;
|
|
|
-
|
|
|
- int apexIdx = 0;
|
|
|
- v.push_back({{0.0f, +halfH, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.5f, 1.0f}});
|
|
|
-
|
|
|
- for (int i = 0; i <= radial; ++i) {
|
|
|
- float u = float(i) / float(radial);
|
|
|
- float ang = u * 6.28318530718f;
|
|
|
- float px = baseR * std::cos(ang);
|
|
|
- float pz = baseR * std::sin(ang);
|
|
|
-
|
|
|
- QVector3D n(px, baseR, pz);
|
|
|
- n.normalize();
|
|
|
- v.push_back({{px, -halfH, pz}, {n.x(), n.y(), n.z()}, {u, 0.0f}});
|
|
|
- }
|
|
|
+ foot.setZ(foot.z() + std::sin((phase - 0.25f) * 2.0f * 3.14159f) * 0.20f);
|
|
|
+ };
|
|
|
|
|
|
- for (int i = 1; i <= radial; ++i) {
|
|
|
- int a = apexIdx;
|
|
|
- int b = i;
|
|
|
- int c = i + 1;
|
|
|
- idx.push_back(a);
|
|
|
- idx.push_back(b);
|
|
|
- idx.push_back(c);
|
|
|
+ animateFoot(P.footL, leftPhase);
|
|
|
+ animateFoot(P.footR, rightPhase);
|
|
|
}
|
|
|
|
|
|
- int baseCenter = (int)v.size();
|
|
|
- v.push_back({{0.0f, -halfH, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.5f, 0.5f}});
|
|
|
-
|
|
|
- int baseStart = (int)v.size();
|
|
|
- for (int i = 0; i <= radial; ++i) {
|
|
|
- float u = float(i) / float(radial);
|
|
|
- float ang = u * 6.28318530718f;
|
|
|
- float px = baseR * std::cos(ang);
|
|
|
- float pz = baseR * std::sin(ang);
|
|
|
- v.push_back({{px, -halfH, pz},
|
|
|
- {0.0f, -1.0f, 0.0f},
|
|
|
- {0.5f + 0.5f * std::cos(ang), 0.5f + 0.5f * std::sin(ang)}});
|
|
|
- }
|
|
|
- for (int i = 0; i < radial; ++i) {
|
|
|
- idx.push_back(baseCenter);
|
|
|
- idx.push_back(baseStart + i + 1);
|
|
|
- idx.push_back(baseStart + i);
|
|
|
- }
|
|
|
- return new Mesh(v, idx);
|
|
|
-}
|
|
|
+ QVector3D shoulderToHandL = P.handL - P.shoulderL;
|
|
|
+ float distL = shoulderToHandL.length();
|
|
|
+ QVector3D dirL = shoulderToHandL.normalized();
|
|
|
|
|
|
-static Mesh *createCapsuleMesh() {
|
|
|
- const int radial = 24;
|
|
|
- const int heightSegments = 1;
|
|
|
- const float radius = 0.25f;
|
|
|
- const float halfH = 0.5f;
|
|
|
- std::vector<Vertex> verts;
|
|
|
- std::vector<unsigned int> idx;
|
|
|
-
|
|
|
- for (int y = 0; y <= heightSegments; ++y) {
|
|
|
- float v = float(y) / float(heightSegments);
|
|
|
- float py = -halfH + v * (2.0f * halfH);
|
|
|
- for (int i = 0; i <= radial; ++i) {
|
|
|
- float u = float(i) / float(radial);
|
|
|
- float ang = u * 6.2831853f;
|
|
|
- float px = radius * std::cos(ang);
|
|
|
- float pz = radius * std::sin(ang);
|
|
|
- QVector3D n(px, 0.0f, pz);
|
|
|
- n.normalize();
|
|
|
- verts.push_back({{px, py, pz}, {n.x(), n.y(), n.z()}, {u, v}});
|
|
|
- }
|
|
|
- }
|
|
|
- int row = radial + 1;
|
|
|
- for (int y = 0; y < heightSegments; ++y) {
|
|
|
- for (int i = 0; i < radial; ++i) {
|
|
|
- int a = y * row + i;
|
|
|
- int b = y * row + i + 1;
|
|
|
- int c = (y + 1) * row + i + 1;
|
|
|
- int d = (y + 1) * row + i;
|
|
|
- idx.push_back(a);
|
|
|
- idx.push_back(b);
|
|
|
- idx.push_back(c);
|
|
|
- idx.push_back(c);
|
|
|
- idx.push_back(d);
|
|
|
- idx.push_back(a);
|
|
|
- }
|
|
|
- }
|
|
|
+ QVector3D perpL(-dirL.z(), 0.0f, dirL.x());
|
|
|
+ float elbowOffsetL = 0.15f;
|
|
|
+ P.elbowL = P.shoulderL + dirL * (distL * 0.45f) + perpL * elbowOffsetL +
|
|
|
+ QVector3D(0, -0.08f, 0);
|
|
|
|
|
|
- int baseTop = (int)verts.size();
|
|
|
- verts.push_back({{0.0f, halfH, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.5f, 0.5f}});
|
|
|
- for (int i = 0; i <= radial; ++i) {
|
|
|
- float u = float(i) / float(radial);
|
|
|
- float ang = u * 6.2831853f;
|
|
|
- float px = radius * std::cos(ang);
|
|
|
- float pz = radius * std::sin(ang);
|
|
|
- verts.push_back(
|
|
|
- {{px, halfH, pz},
|
|
|
- {0.0f, 1.0f, 0.0f},
|
|
|
- {0.5f + 0.5f * std::cos(ang), 0.5f + 0.5f * std::sin(ang)}});
|
|
|
- }
|
|
|
- for (int i = 1; i <= radial; ++i) {
|
|
|
- idx.push_back(baseTop);
|
|
|
- idx.push_back(baseTop + i);
|
|
|
- idx.push_back(baseTop + i + 1);
|
|
|
- }
|
|
|
+ QVector3D shoulderToHandR = P.handR - P.shoulderR;
|
|
|
+ float distR = shoulderToHandR.length();
|
|
|
+ QVector3D dirR = shoulderToHandR.normalized();
|
|
|
|
|
|
- int baseBot = (int)verts.size();
|
|
|
- verts.push_back({{0.0f, -halfH, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.5f, 0.5f}});
|
|
|
- for (int i = 0; i <= radial; ++i) {
|
|
|
- float u = float(i) / float(radial);
|
|
|
- float ang = u * 6.2831853f;
|
|
|
- float px = radius * std::cos(ang);
|
|
|
- float pz = radius * std::sin(ang);
|
|
|
- verts.push_back(
|
|
|
- {{px, -halfH, pz},
|
|
|
- {0.0f, -1.0f, 0.0f},
|
|
|
- {0.5f + 0.5f * std::cos(ang), 0.5f + 0.5f * std::sin(ang)}});
|
|
|
- }
|
|
|
- for (int i = 1; i <= radial; ++i) {
|
|
|
- idx.push_back(baseBot);
|
|
|
- idx.push_back(baseBot + i + 1);
|
|
|
- idx.push_back(baseBot + i);
|
|
|
- }
|
|
|
- return new Mesh(verts, idx);
|
|
|
+ QVector3D perpR(-dirR.z(), 0.0f, dirR.x());
|
|
|
+ float elbowOffsetR = 0.12f;
|
|
|
+ P.elbowR = P.shoulderR + dirR * (distR * 0.48f) + perpR * elbowOffsetR +
|
|
|
+ QVector3D(0, 0.02f, 0);
|
|
|
+
|
|
|
+ return P;
|
|
|
}
|
|
|
|
|
|
-static Mesh *getUnitCylinder() {
|
|
|
- static std::unique_ptr<Mesh> m(createUnitCylinderMesh());
|
|
|
- return m.get();
|
|
|
+static inline ArcherColors makeColors(const QVector3D &teamTint) {
|
|
|
+ ArcherColors C;
|
|
|
+ auto tint = [&](float k) {
|
|
|
+ return QVector3D(clamp01(teamTint.x() * k), clamp01(teamTint.y() * k),
|
|
|
+ clamp01(teamTint.z() * k));
|
|
|
+ };
|
|
|
+ C.tunic = teamTint;
|
|
|
+ C.skin = QVector3D(0.96f, 0.80f, 0.69f);
|
|
|
+ C.leather = QVector3D(0.35f, 0.22f, 0.12f);
|
|
|
+ C.leatherDark = C.leather * 0.9f;
|
|
|
+ C.wood = QVector3D(0.16f, 0.10f, 0.05f);
|
|
|
+ C.metal = QVector3D(0.65f, 0.66f, 0.70f);
|
|
|
+ C.metalHead = clampVec01(C.metal * 1.1f);
|
|
|
+ C.stringCol = QVector3D(0.30f, 0.30f, 0.32f);
|
|
|
+ C.fletch = tint(0.9f);
|
|
|
+ return C;
|
|
|
}
|
|
|
-static Mesh *getUnitSphere() {
|
|
|
- static std::unique_ptr<Mesh> m(createUnitSphereMesh());
|
|
|
- return m.get();
|
|
|
+
|
|
|
+static inline void drawTorso(const DrawContext &p, ISubmitter &out,
|
|
|
+ const ArcherColors &C, const ArcherPose &P) {
|
|
|
+ using HP = HumanProportions;
|
|
|
+
|
|
|
+ QVector3D torsoTop{0.0f, HP::NECK_BASE_Y - 0.05f, 0.0f};
|
|
|
+ QVector3D torsoBot{0.0f, HP::WAIST_Y, 0.0f};
|
|
|
+
|
|
|
+ float torsoRadius = HP::TORSO_TOP_R;
|
|
|
+
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ p.model * cylinderBetween(torsoTop, torsoBot, torsoRadius), C.tunic,
|
|
|
+ nullptr, 1.0f);
|
|
|
+
|
|
|
+ QVector3D waist{0.0f, HP::WAIST_Y, 0.0f};
|
|
|
+ QVector3D hipCenter = (P.hipL + P.hipR) * 0.5f;
|
|
|
+
|
|
|
+ out.mesh(getUnitCone(),
|
|
|
+ p.model * coneFromTo(waist, hipCenter, HP::TORSO_BOT_R),
|
|
|
+ C.tunic * 0.9f, nullptr, 1.0f);
|
|
|
}
|
|
|
-static Mesh *getUnitCone() {
|
|
|
- static std::unique_ptr<Mesh> m(createUnitConeMesh());
|
|
|
- return m.get();
|
|
|
+
|
|
|
+static inline void drawHeadAndNeck(const DrawContext &p, ISubmitter &out,
|
|
|
+ const ArcherPose &P, const ArcherColors &C) {
|
|
|
+ using HP = HumanProportions;
|
|
|
+
|
|
|
+ QVector3D chinPos{0.0f, HP::CHIN_Y, 0.0f};
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ p.model * cylinderBetween(P.neckBase, chinPos, HP::NECK_RADIUS),
|
|
|
+ C.skin * 0.9f, nullptr, 1.0f);
|
|
|
+
|
|
|
+ out.mesh(getUnitSphere(), p.model * sphereAt(P.headPos, P.headR), C.skin,
|
|
|
+ nullptr, 1.0f);
|
|
|
+
|
|
|
+ float headTopOffset = P.headR * 0.7f;
|
|
|
+ QVector3D helmBase = P.headPos + QVector3D(0.0f, headTopOffset, 0.0f);
|
|
|
+ QVector3D helmApex = P.headPos + QVector3D(0.0f, P.headR * 2.4f, 0.0f);
|
|
|
+ float helmBaseR = P.headR * 1.45f;
|
|
|
+ out.mesh(getUnitCone(), p.model * coneFromTo(helmBase, helmApex, helmBaseR),
|
|
|
+ C.tunic, nullptr, 1.0f);
|
|
|
+
|
|
|
+ QVector3D iris(0.06f, 0.06f, 0.07f);
|
|
|
+ float eyeZ = P.headR * 0.7f;
|
|
|
+ float eyeY = P.headPos.y() + P.headR * 0.1f;
|
|
|
+ float eyeSpacing = P.headR * 0.35f;
|
|
|
+ out.mesh(getUnitSphere(),
|
|
|
+ p.model *
|
|
|
+ sphereAt(QVector3D(-eyeSpacing, eyeY, eyeZ), P.headR * 0.15f),
|
|
|
+ iris, nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitSphere(),
|
|
|
+ p.model *
|
|
|
+ sphereAt(QVector3D(eyeSpacing, eyeY, eyeZ), P.headR * 0.15f),
|
|
|
+ iris, nullptr, 1.0f);
|
|
|
}
|
|
|
-static Mesh *getArcherCapsule() {
|
|
|
- static std::unique_ptr<Mesh> m(createCapsuleMesh());
|
|
|
- return m.get();
|
|
|
+
|
|
|
+static inline void drawArms(const DrawContext &p, ISubmitter &out,
|
|
|
+ const ArcherPose &P, const ArcherColors &C) {
|
|
|
+ using HP = HumanProportions;
|
|
|
+
|
|
|
+ const float upperArmR = HP::UPPER_ARM_R;
|
|
|
+ const float foreArmR = HP::FORE_ARM_R;
|
|
|
+ const float jointR = HP::HAND_RADIUS * 1.05f;
|
|
|
+
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ p.model * cylinderBetween(P.shoulderL, P.elbowL, upperArmR), C.tunic,
|
|
|
+ nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitSphere(), p.model * sphereAt(P.elbowL, jointR),
|
|
|
+ C.tunic * 0.95f, nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ p.model * cylinderBetween(P.elbowL, P.handL, foreArmR),
|
|
|
+ C.skin * 0.95f, nullptr, 1.0f);
|
|
|
+
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ p.model * cylinderBetween(P.shoulderR, P.elbowR, upperArmR), C.tunic,
|
|
|
+ nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitSphere(), p.model * sphereAt(P.elbowR, jointR),
|
|
|
+ C.tunic * 0.95f, nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ p.model * cylinderBetween(P.elbowR, P.handR, foreArmR),
|
|
|
+ C.skin * 0.95f, nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
-static inline float clamp01(float x) {
|
|
|
- return std::max(0.0f, std::min(1.0f, x));
|
|
|
+static inline void drawLegs(const DrawContext &p, ISubmitter &out,
|
|
|
+ const ArcherPose &P, const ArcherColors &C) {
|
|
|
+ using HP = HumanProportions;
|
|
|
+
|
|
|
+ QVector3D kneeL = P.hipL + (P.footL - P.hipL) * 0.45f;
|
|
|
+ QVector3D kneeR = P.hipR + (P.footR - P.hipR) * 0.45f;
|
|
|
+ kneeL.setY(HP::KNEE_Y + 0.05f);
|
|
|
+ kneeR.setY(HP::KNEE_Y + 0.05f);
|
|
|
+
|
|
|
+ const float thighR = HP::UPPER_LEG_R;
|
|
|
+ const float shinR = HP::LOWER_LEG_R;
|
|
|
+ const float kneeJointR = thighR * 1.15f;
|
|
|
+
|
|
|
+ out.mesh(getUnitCone(), p.model * coneFromTo(P.hipL, kneeL, thighR),
|
|
|
+ C.leather, nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitCone(), p.model * coneFromTo(P.hipR, kneeR, thighR),
|
|
|
+ C.leather, nullptr, 1.0f);
|
|
|
+
|
|
|
+ out.mesh(getUnitSphere(), p.model * sphereAt(kneeL, kneeJointR),
|
|
|
+ C.leather * 0.95f, nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitSphere(), p.model * sphereAt(kneeR, kneeJointR),
|
|
|
+ C.leather * 0.95f, nullptr, 1.0f);
|
|
|
+
|
|
|
+ out.mesh(getUnitCone(), p.model * coneFromTo(kneeL, P.footL, shinR),
|
|
|
+ C.leatherDark, nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitCone(), p.model * coneFromTo(kneeR, P.footR, shinR),
|
|
|
+ C.leatherDark, nullptr, 1.0f);
|
|
|
+
|
|
|
+ QVector3D down(0.0f, -0.02f, 0.0f);
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ p.model * cylinderBetween(P.footL, P.footL + down, shinR * 1.1f),
|
|
|
+ C.leatherDark, nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ p.model * cylinderBetween(P.footR, P.footR + down, shinR * 1.1f),
|
|
|
+ C.leatherDark, nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
-static QMatrix4x4 cylinderBetween(const QVector3D &a, const QVector3D &b,
|
|
|
- float radius) {
|
|
|
- QVector3D mid = (a + b) * 0.5f;
|
|
|
- QVector3D dir = b - a;
|
|
|
- float len = dir.length();
|
|
|
- QMatrix4x4 M;
|
|
|
- M.translate(mid);
|
|
|
- if (len > 1e-6f) {
|
|
|
- QVector3D yAxis(0, 1, 0);
|
|
|
- QVector3D d = dir / len;
|
|
|
- float dot = std::clamp(QVector3D::dotProduct(yAxis, d), -1.0f, 1.0f);
|
|
|
- float angleDeg = std::acos(dot) * 57.2957795131f;
|
|
|
- QVector3D axis = QVector3D::crossProduct(yAxis, d);
|
|
|
- if (axis.lengthSquared() < 1e-6f) {
|
|
|
- if (dot < 0.0f)
|
|
|
- M.rotate(180.0f, 1.0f, 0.0f, 0.0f);
|
|
|
- } else {
|
|
|
- axis.normalize();
|
|
|
- M.rotate(angleDeg, axis);
|
|
|
- }
|
|
|
- M.scale(radius, len, radius);
|
|
|
- } else {
|
|
|
- M.scale(radius, 1.0f, radius);
|
|
|
- }
|
|
|
- return M;
|
|
|
+static inline void drawQuiver(const DrawContext &p, ISubmitter &out,
|
|
|
+ const ArcherColors &C, const ArcherPose &P,
|
|
|
+ uint32_t seed) {
|
|
|
+ using HP = HumanProportions;
|
|
|
+
|
|
|
+ auto hash01 = [](uint32_t x) {
|
|
|
+ x ^= x << 13;
|
|
|
+ x ^= x >> 17;
|
|
|
+ x ^= x << 5;
|
|
|
+ return (x & 0x00FFFFFF) / float(0x01000000);
|
|
|
+ };
|
|
|
+
|
|
|
+ QVector3D qTop(-0.08f, HP::SHOULDER_Y + 0.10f, -0.25f);
|
|
|
+ QVector3D qBase(-0.10f, HP::CHEST_Y, -0.22f);
|
|
|
+
|
|
|
+ float quiverR = HP::HEAD_RADIUS * 0.45f;
|
|
|
+ out.mesh(getUnitCylinder(), p.model * cylinderBetween(qBase, qTop, quiverR),
|
|
|
+ C.leather, nullptr, 1.0f);
|
|
|
+
|
|
|
+ float j = (hash01(seed) - 0.5f) * 0.04f;
|
|
|
+ float k = (hash01(seed ^ 0x9E3779B9u) - 0.5f) * 0.04f;
|
|
|
+
|
|
|
+ QVector3D a1 = qTop + QVector3D(0.00f + j, 0.08f, 0.00f + k);
|
|
|
+ out.mesh(getUnitCylinder(), p.model * cylinderBetween(qTop, a1, 0.010f),
|
|
|
+ C.wood, nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitCone(),
|
|
|
+ p.model * coneFromTo(a1, a1 + QVector3D(0, 0.05f, 0), 0.025f),
|
|
|
+ C.fletch, nullptr, 1.0f);
|
|
|
+
|
|
|
+ QVector3D a2 = qTop + QVector3D(0.02f - j, 0.07f, 0.02f - k);
|
|
|
+ out.mesh(getUnitCylinder(), p.model * cylinderBetween(qTop, a2, 0.010f),
|
|
|
+ C.wood, nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitCone(),
|
|
|
+ p.model * coneFromTo(a2, a2 + QVector3D(0, 0.05f, 0), 0.025f),
|
|
|
+ C.fletch, nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
-static QMatrix4x4 sphereAt(const QVector3D &pos, float radius) {
|
|
|
- QMatrix4x4 M;
|
|
|
- M.translate(pos);
|
|
|
- M.scale(radius, radius, radius);
|
|
|
- return M;
|
|
|
+static inline void drawBowAndArrow(const DrawContext &p, ISubmitter &out,
|
|
|
+ const ArcherPose &P, const ArcherColors &C) {
|
|
|
+ const QVector3D up(0.0f, 1.0f, 0.0f);
|
|
|
+ const QVector3D forward(0.0f, 0.0f, 1.0f);
|
|
|
+
|
|
|
+ QVector3D grip = P.handL;
|
|
|
+ QVector3D topEnd(P.bowX, P.bowTopY, grip.z());
|
|
|
+ QVector3D botEnd(P.bowX, P.bowBotY, grip.z());
|
|
|
+
|
|
|
+ QVector3D nock(P.bowX,
|
|
|
+ clampf(P.handR.y(), P.bowBotY + 0.05f, P.bowTopY - 0.05f),
|
|
|
+ clampf(P.handR.z(), grip.z() - 0.30f, grip.z() + 0.30f));
|
|
|
+
|
|
|
+ const int segs = 22;
|
|
|
+ auto qBezier = [](const QVector3D &a, const QVector3D &c, const QVector3D &b,
|
|
|
+ float t) {
|
|
|
+ float u = 1.0f - t;
|
|
|
+ return u * u * a + 2.0f * u * t * c + t * t * b;
|
|
|
+ };
|
|
|
+ QVector3D ctrl = nock + forward * P.bowDepth;
|
|
|
+ QVector3D prev = botEnd;
|
|
|
+ for (int i = 1; i <= segs; ++i) {
|
|
|
+ float t = float(i) / float(segs);
|
|
|
+ QVector3D cur = qBezier(botEnd, ctrl, topEnd, t);
|
|
|
+ out.mesh(getUnitCylinder(), p.model * cylinderBetween(prev, cur, P.bowRodR),
|
|
|
+ C.wood, nullptr, 1.0f);
|
|
|
+ prev = cur;
|
|
|
+ }
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ p.model * cylinderBetween(grip - up * 0.05f, grip + up * 0.05f,
|
|
|
+ P.bowRodR * 1.45f),
|
|
|
+ C.wood, nullptr, 1.0f);
|
|
|
+
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ p.model * cylinderBetween(topEnd, nock, P.stringR), C.stringCol,
|
|
|
+ nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitCylinder(),
|
|
|
+ p.model * cylinderBetween(nock, botEnd, P.stringR), C.stringCol,
|
|
|
+ nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitCylinder(), p.model * cylinderBetween(P.handR, nock, 0.0045f),
|
|
|
+ C.stringCol * 0.9f, nullptr, 1.0f);
|
|
|
+
|
|
|
+ QVector3D tail = nock - forward * 0.06f;
|
|
|
+ QVector3D tip = tail + forward * 0.90f;
|
|
|
+ out.mesh(getUnitCylinder(), p.model * cylinderBetween(tail, tip, 0.018f),
|
|
|
+ C.wood, nullptr, 1.0f);
|
|
|
+ QVector3D headBase = tip - forward * 0.10f;
|
|
|
+ out.mesh(getUnitCone(), p.model * coneFromTo(headBase, tip, 0.05f),
|
|
|
+ C.metalHead, nullptr, 1.0f);
|
|
|
+ QVector3D f1b = tail - forward * 0.02f, f1a = f1b - forward * 0.06f;
|
|
|
+ QVector3D f2b = tail + forward * 0.02f, f2a = f2b + forward * 0.06f;
|
|
|
+ out.mesh(getUnitCone(), p.model * coneFromTo(f1b, f1a, 0.04f), C.fletch,
|
|
|
+ nullptr, 1.0f);
|
|
|
+ out.mesh(getUnitCone(), p.model * coneFromTo(f2a, f2b, 0.04f), C.fletch,
|
|
|
+ nullptr, 1.0f);
|
|
|
}
|
|
|
|
|
|
-static QMatrix4x4 coneFromTo(const QVector3D &baseCenter, const QVector3D &apex,
|
|
|
- float baseRadius) {
|
|
|
- return cylinderBetween(baseCenter, apex, baseRadius);
|
|
|
+static inline void drawSelectionFX(const DrawContext &p, ISubmitter &out) {
|
|
|
+ if (p.selected || p.hovered) {
|
|
|
+ QMatrix4x4 ringM;
|
|
|
+ QVector3D pos = p.model.column(3).toVector3D();
|
|
|
+ ringM.translate(pos.x(), 0.01f, pos.z());
|
|
|
+ ringM.scale(0.5f, 1.0f, 0.5f);
|
|
|
+ if (p.selected)
|
|
|
+ out.selectionRing(ringM, 0.6f, 0.25f, QVector3D(0.2f, 0.8f, 0.2f));
|
|
|
+ else
|
|
|
+ out.selectionRing(ringM, 0.35f, 0.15f, QVector3D(0.90f, 0.90f, 0.25f));
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-void registerArcherRenderer(EntityRendererRegistry ®istry) {
|
|
|
+void registerArcherRenderer(Render::GL::EntityRendererRegistry ®istry) {
|
|
|
registry.registerRenderer("archer", [](const DrawContext &p,
|
|
|
ISubmitter &out) {
|
|
|
QVector3D tunic(0.8f, 0.9f, 1.0f);
|
|
|
@@ -327,157 +429,114 @@ void registerArcherRenderer(EntityRendererRegistry ®istry) {
|
|
|
} else if (rc) {
|
|
|
tunic = QVector3D(rc->color[0], rc->color[1], rc->color[2]);
|
|
|
}
|
|
|
- auto tint = [&](float k) -> QVector3D {
|
|
|
- return QVector3D(clamp01(tunic.x() * k), clamp01(tunic.y() * k),
|
|
|
- clamp01(tunic.z() * k));
|
|
|
- };
|
|
|
-
|
|
|
- const QVector3D skin(0.96f, 0.80f, 0.69f);
|
|
|
- const QVector3D leather(0.35f, 0.22f, 0.12f);
|
|
|
- const QVector3D wood(0.16f, 0.10f, 0.05f);
|
|
|
- const QVector3D metal(0.65f, 0.66f, 0.70f);
|
|
|
- const QVector3D fletch = tint(0.9f);
|
|
|
|
|
|
- const QVector3D headPos(0.0f, 0.82f, 0.0f);
|
|
|
- const QVector3D hipL(-0.12f, -0.45f, 0.03f);
|
|
|
- const QVector3D hipR(0.12f, -0.45f, -0.03f);
|
|
|
- const QVector3D footL(-0.14f, -1.00f, 0.06f);
|
|
|
- const QVector3D footR(0.14f, -1.00f, -0.06f);
|
|
|
+ uint32_t seed = 0u;
|
|
|
+ if (unit)
|
|
|
+ seed ^= uint32_t(unit->ownerId * 2654435761u);
|
|
|
+ if (p.entity)
|
|
|
+ seed ^= uint32_t(reinterpret_cast<uintptr_t>(p.entity) & 0xFFFFFFFFu);
|
|
|
|
|
|
- const QVector3D shoulderL(-0.18f, 0.35f, 0.00f);
|
|
|
- const QVector3D shoulderR(0.18f, 0.35f, 0.00f);
|
|
|
+ const int rows = 2;
|
|
|
+ const int cols = 5;
|
|
|
+ const float spacing = 0.75f;
|
|
|
|
|
|
- const QVector3D elbowL(-0.45f, 0.27f, 0.02f);
|
|
|
- const QVector3D handL(-0.60f, 0.20f, 0.05f);
|
|
|
+ ArcherColors colors = makeColors(tunic);
|
|
|
|
|
|
- const QVector3D elbowR(0.36f, 0.35f, 0.10f);
|
|
|
- const QVector3D handR(0.25f, 0.45f, 0.15f);
|
|
|
-
|
|
|
- const float bowTopY = 0.65f;
|
|
|
- const float bowBotY = -0.15f;
|
|
|
- const float bowX = -0.60f;
|
|
|
- const float bowZMid = 0.05f;
|
|
|
- const float bowDepth = 0.10f;
|
|
|
- const float bowRodR = 0.02f;
|
|
|
- const float stringR = 0.006f;
|
|
|
-
|
|
|
- out.mesh(getArcherCapsule(), p.model, tint(0.8f), nullptr, 1.0f);
|
|
|
-
|
|
|
- {
|
|
|
- QMatrix4x4 M = p.model * sphereAt(headPos, 0.18f);
|
|
|
- out.mesh(getUnitSphere(), M, skin, nullptr, 1.0f);
|
|
|
- }
|
|
|
+ bool isMoving = false;
|
|
|
+ bool isAttacking = false;
|
|
|
+ float targetRotationY = 0.0f;
|
|
|
|
|
|
- {
|
|
|
- QMatrix4x4 ML = p.model * cylinderBetween(hipL, footL, 0.08f);
|
|
|
- QMatrix4x4 MR = p.model * cylinderBetween(hipR, footR, 0.08f);
|
|
|
- out.mesh(getUnitCylinder(), ML, leather, nullptr, 1.0f);
|
|
|
- out.mesh(getUnitCylinder(), MR, leather, nullptr, 1.0f);
|
|
|
- }
|
|
|
-
|
|
|
- {
|
|
|
-
|
|
|
- QMatrix4x4 M1 = p.model * cylinderBetween(shoulderL, elbowL, 0.06f);
|
|
|
- QMatrix4x4 M2 = p.model * cylinderBetween(elbowL, handL, 0.055f);
|
|
|
- out.mesh(getUnitCylinder(), M1, leather, nullptr, 1.0f);
|
|
|
- out.mesh(getUnitCylinder(), M2, leather, nullptr, 1.0f);
|
|
|
-
|
|
|
- QMatrix4x4 M3 = p.model * cylinderBetween(shoulderR, elbowR, 0.06f);
|
|
|
- QMatrix4x4 M4 = p.model * cylinderBetween(elbowR, handR, 0.055f);
|
|
|
- out.mesh(getUnitCylinder(), M3, leather, nullptr, 1.0f);
|
|
|
- out.mesh(getUnitCylinder(), M4, leather, nullptr, 1.0f);
|
|
|
-
|
|
|
- out.mesh(getUnitSphere(), p.model * sphereAt(handL, 0.07f), skin, nullptr,
|
|
|
- 1.0f);
|
|
|
- out.mesh(getUnitSphere(), p.model * sphereAt(handR, 0.07f), skin, nullptr,
|
|
|
- 1.0f);
|
|
|
+ if (p.entity) {
|
|
|
+ auto *movement =
|
|
|
+ p.entity->getComponent<Engine::Core::MovementComponent>();
|
|
|
+ auto *attack = p.entity->getComponent<Engine::Core::AttackComponent>();
|
|
|
+ auto *attackTarget =
|
|
|
+ p.entity->getComponent<Engine::Core::AttackTargetComponent>();
|
|
|
+ auto *transform =
|
|
|
+ p.entity->getComponent<Engine::Core::TransformComponent>();
|
|
|
+
|
|
|
+ isMoving = (movement && movement->hasTarget);
|
|
|
+ isAttacking = (attack && attackTarget && attackTarget->targetId > 0);
|
|
|
+
|
|
|
+ if (isAttacking && attackTarget && p.world && transform) {
|
|
|
+ auto *target = p.world->getEntity(attackTarget->targetId);
|
|
|
+ if (target) {
|
|
|
+ auto *targetTransform =
|
|
|
+ target->getComponent<Engine::Core::TransformComponent>();
|
|
|
+ if (targetTransform) {
|
|
|
+
|
|
|
+ float dx = targetTransform->position.x - transform->position.x;
|
|
|
+ float dz = targetTransform->position.z - transform->position.z;
|
|
|
+
|
|
|
+ targetRotationY = std::atan2(dx, dz) * 180.0f / 3.14159f;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- {
|
|
|
- QVector3D qTop(-0.05f, 0.85f, -0.28f);
|
|
|
- QVector3D qBase(-0.10f, 0.25f, -0.22f);
|
|
|
- QMatrix4x4 MQ = p.model * cylinderBetween(qBase, qTop, 0.06f);
|
|
|
- out.mesh(getUnitCylinder(), MQ, leather, nullptr, 1.0f);
|
|
|
-
|
|
|
- QVector3D a1 = qTop + QVector3D(0.00f, 0.06f, 0.00f);
|
|
|
- QMatrix4x4 Mshaft1 = p.model * cylinderBetween(qTop, a1, 0.01f);
|
|
|
- out.mesh(getUnitCylinder(), Mshaft1, wood, nullptr, 1.0f);
|
|
|
- out.mesh(getUnitCone(),
|
|
|
- p.model *
|
|
|
- coneFromTo(a1, a1 + QVector3D(0.0f, 0.06f, 0.0f), 0.03f),
|
|
|
- fletch, nullptr, 1.0f);
|
|
|
-
|
|
|
- QVector3D a2 = qTop + QVector3D(0.02f, 0.05f, 0.02f);
|
|
|
- QMatrix4x4 Mshaft2 = p.model * cylinderBetween(qTop, a2, 0.01f);
|
|
|
- out.mesh(getUnitCylinder(), Mshaft2, wood, nullptr, 1.0f);
|
|
|
- out.mesh(getUnitCone(),
|
|
|
- p.model *
|
|
|
- coneFromTo(a2, a2 + QVector3D(0.0f, 0.06f, 0.0f), 0.03f),
|
|
|
- fletch, nullptr, 1.0f);
|
|
|
+ int visibleCount = rows * cols;
|
|
|
+ if (unit) {
|
|
|
+ int mh = std::max(1, unit->maxHealth);
|
|
|
+ float ratio = std::clamp(unit->health / float(mh), 0.0f, 1.0f);
|
|
|
+ visibleCount = std::max(1, (int)std::ceil(ratio * float(rows * cols)));
|
|
|
}
|
|
|
|
|
|
- {
|
|
|
- const int segs = 12;
|
|
|
- std::vector<QVector3D> bowPts;
|
|
|
- bowPts.reserve(segs + 1);
|
|
|
- for (int i = 0; i <= segs; ++i) {
|
|
|
- float t = float(i) / float(segs);
|
|
|
- float y = bowBotY + t * (bowTopY - bowBotY);
|
|
|
-
|
|
|
- float z = bowZMid + bowDepth * std::sin((t - 0.5f) * 3.14159265f);
|
|
|
- bowPts.push_back(QVector3D(bowX, y, z));
|
|
|
- }
|
|
|
- for (int i = 0; i < segs; ++i) {
|
|
|
- QMatrix4x4 Mb =
|
|
|
- p.model * cylinderBetween(bowPts[i], bowPts[i + 1], bowRodR);
|
|
|
- out.mesh(getUnitCylinder(), Mb, wood, nullptr, 1.0f);
|
|
|
+ int idx = 0;
|
|
|
+ for (; idx < visibleCount; ++idx) {
|
|
|
+ int r = idx / cols;
|
|
|
+ int c = idx % cols;
|
|
|
+
|
|
|
+ float offsetX = (c - (cols - 1) * 0.5f) * spacing;
|
|
|
+ float offsetZ = (r - (rows - 1) * 0.5f) * spacing;
|
|
|
+
|
|
|
+ QMatrix4x4 instModel = p.model;
|
|
|
+
|
|
|
+ uint32_t instSeed = seed ^ uint32_t(idx * 9176u);
|
|
|
+
|
|
|
+ float yawOffset = (float)((int)(instSeed & 0xFFu) - 128) / 128.0f * 6.0f;
|
|
|
+
|
|
|
+ float phaseOffset = float((instSeed >> 8) & 0xFFu) / 255.0f * 0.25f;
|
|
|
+
|
|
|
+ if (p.entity) {
|
|
|
+ if (auto *entT =
|
|
|
+ p.entity->getComponent<Engine::Core::TransformComponent>()) {
|
|
|
+ QMatrix4x4 M;
|
|
|
+ M.setToIdentity();
|
|
|
+ M.translate(entT->position.x, entT->position.y, entT->position.z);
|
|
|
+ float baseYaw = entT->rotation.y;
|
|
|
+ float appliedYaw = baseYaw + (isAttacking ? yawOffset : 0.0f);
|
|
|
+ M.rotate(appliedYaw, 0.0f, 1.0f, 0.0f);
|
|
|
+ M.scale(entT->scale.x, entT->scale.y, entT->scale.z);
|
|
|
+
|
|
|
+ M.translate(offsetX, 0.0f, offsetZ);
|
|
|
+ instModel = M;
|
|
|
+ } else {
|
|
|
+ instModel = p.model;
|
|
|
+ if (isAttacking)
|
|
|
+ instModel.rotate(yawOffset, 0.0f, 1.0f, 0.0f);
|
|
|
+ instModel.translate(offsetX, 0.0f, offsetZ);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ instModel = p.model;
|
|
|
+ if (isAttacking)
|
|
|
+ instModel.rotate(yawOffset, 0.0f, 1.0f, 0.0f);
|
|
|
+ instModel.translate(offsetX, 0.0f, offsetZ);
|
|
|
}
|
|
|
|
|
|
- QVector3D topEnd = bowPts.back();
|
|
|
- QVector3D botEnd = bowPts.front();
|
|
|
- QMatrix4x4 Ms1 = p.model * cylinderBetween(topEnd, handR, stringR);
|
|
|
- QMatrix4x4 Ms2 = p.model * cylinderBetween(handR, botEnd, stringR);
|
|
|
- out.mesh(getUnitCylinder(), Ms1, metal, nullptr, 1.0f);
|
|
|
- out.mesh(getUnitCylinder(), Ms2, metal, nullptr, 1.0f);
|
|
|
- }
|
|
|
+ DrawContext instCtx{p.resources, p.entity, p.world, instModel};
|
|
|
|
|
|
- {
|
|
|
- QVector3D tail = handR + QVector3D(-0.05f, 0.0f, 0.0f);
|
|
|
- QVector3D tip = tail + QVector3D(0.0f, 0.0f, 0.70f);
|
|
|
- QMatrix4x4 Mshaft = p.model * cylinderBetween(tail, tip, 0.01f);
|
|
|
- out.mesh(getUnitCylinder(), Mshaft, wood, nullptr, 1.0f);
|
|
|
-
|
|
|
- float headLen = 0.08f;
|
|
|
- QVector3D headBase = tip - QVector3D(0.0f, 0.0f, headLen);
|
|
|
- QMatrix4x4 Mhead = p.model * coneFromTo(headBase, tip, 0.03f);
|
|
|
- out.mesh(getUnitCone(), Mhead, metal, nullptr, 1.0f);
|
|
|
-
|
|
|
- QVector3D f1b = tail - QVector3D(0.0f, 0.0f, 0.02f);
|
|
|
- QVector3D f1a = f1b - QVector3D(0.0f, 0.0f, 0.05f);
|
|
|
- out.mesh(getUnitCone(), p.model * coneFromTo(f1b, f1a, 0.025f), fletch,
|
|
|
- nullptr, 1.0f);
|
|
|
-
|
|
|
- QVector3D f2b = tail + QVector3D(0.0f, 0.0f, 0.02f);
|
|
|
- QVector3D f2a = f2b + QVector3D(0.0f, 0.0f, 0.05f);
|
|
|
- out.mesh(getUnitCone(), p.model * coneFromTo(f2a, f2b, 0.025f), fletch,
|
|
|
- nullptr, 1.0f);
|
|
|
- }
|
|
|
+ ArcherPose pose = makePose(instSeed, p.animationTime + phaseOffset,
|
|
|
+ isMoving, isAttacking);
|
|
|
|
|
|
- if (p.selected) {
|
|
|
- QMatrix4x4 ringM;
|
|
|
- QVector3D pos = p.model.column(3).toVector3D();
|
|
|
- ringM.translate(pos.x(), 0.01f, pos.z());
|
|
|
- ringM.scale(0.5f, 1.0f, 0.5f);
|
|
|
- out.selectionRing(ringM, 0.6f, 0.25f, QVector3D(0.2f, 0.8f, 0.2f));
|
|
|
- }
|
|
|
- if (p.hovered && !p.selected) {
|
|
|
- QMatrix4x4 ringM;
|
|
|
- QVector3D pos = p.model.column(3).toVector3D();
|
|
|
- ringM.translate(pos.x(), 0.01f, pos.z());
|
|
|
- ringM.scale(0.5f, 1.0f, 0.5f);
|
|
|
- out.selectionRing(ringM, 0.35f, 0.15f, QVector3D(0.90f, 0.90f, 0.25f));
|
|
|
+ drawQuiver(instCtx, out, colors, pose, instSeed);
|
|
|
+ drawLegs(instCtx, out, pose, colors);
|
|
|
+ drawTorso(instCtx, out, colors, pose);
|
|
|
+ drawArms(instCtx, out, pose, colors);
|
|
|
+ drawHeadAndNeck(instCtx, out, pose, colors);
|
|
|
+ drawBowAndArrow(instCtx, out, pose, colors);
|
|
|
}
|
|
|
+
|
|
|
+ drawSelectionFX(p, out);
|
|
|
});
|
|
|
}
|
|
|
-
|
|
|
} // namespace Render::GL
|