stuff
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
.vs
|
||||||
+37
-70
@@ -1,7 +1,9 @@
|
|||||||
#include "Character.h"
|
#include "Character.h"
|
||||||
#include "Animator.h"
|
#include "Animator.h"
|
||||||
|
#include "Ragdoll.h"
|
||||||
#include "tFunctions.h"
|
#include "tFunctions.h"
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include <cfloat>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <glm/gtc/quaternion.hpp>
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
|
||||||
@@ -89,46 +91,6 @@ void Character::TurnToDirection(float dt) {
|
|||||||
body->t.RotateToQuaternion(glm::normalize(glm::slerp(current, target, blend)));
|
body->t.RotateToQuaternion(glm::normalize(glm::slerp(current, target, blend)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Character::SetPunchAnimation(Animation* animation) {
|
|
||||||
if (!body || !animation)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!punchAnimator) {
|
|
||||||
punchAnimator = body->AddAnimator(10);
|
|
||||||
punchAnimator->blendDuration = 0.08f;
|
|
||||||
}
|
|
||||||
|
|
||||||
punchAnimation = animation;
|
|
||||||
punchAnimator->SetAnimation(animation);
|
|
||||||
punchAnimator->loops = false;
|
|
||||||
punchAnimator->playbackSpeed = punchAnimationSpeed;
|
|
||||||
punchAnimator->onAnimationEnd = [this]() { OnPunchAnimationEnd(); };
|
|
||||||
punchAnimator->StopAnimation();
|
|
||||||
punchHitbox.active = false;
|
|
||||||
punchHitbox.hitTargets.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Character::SetPunchLooping(bool looping) {
|
|
||||||
if (punchAnimator)
|
|
||||||
punchAnimator->loops = looping;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Character::Punch(const glm::vec3& lookDirection) {
|
|
||||||
if (!punchAnimation || !punchAnimator || punchAnimator->isPlaying)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const glm::vec3 flatLook(lookDirection.x, 0.0f, lookDirection.z);
|
|
||||||
punchLookLocked = glm::length(flatLook) > 0.0001f;
|
|
||||||
if (punchLookLocked) {
|
|
||||||
punchLookDirection = glm::normalize(flatLook);
|
|
||||||
SetTurnDirection(punchLookDirection);
|
|
||||||
}
|
|
||||||
|
|
||||||
punchHitbox.hitTargets.clear();
|
|
||||||
punchAnimator->playbackSpeed = punchAnimationSpeed;
|
|
||||||
punchAnimator->PlayAnimation();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Character::SetWalking(bool walking) {
|
void Character::SetWalking(bool walking) {
|
||||||
if (body && body->animator)
|
if (body && body->animator)
|
||||||
body->animator->playbackSpeed = walking ? 1.0f : 0.0f;
|
body->animator->playbackSpeed = walking ? 1.0f : 0.0f;
|
||||||
@@ -146,24 +108,38 @@ glm::vec3 Character::GetForwardDirection() const {
|
|||||||
return forward / length;
|
return forward / length;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Character::OnPunchAnimationEnd() {
|
void Character::EnableRagdoll(Physics& physics) {
|
||||||
|
if (!ragdoll)
|
||||||
|
ragdoll = new Ragdoll(*body, GetRigMatrix(), physics);
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::mat4 Character::GetRigMatrix() const {
|
||||||
|
// body->t's scale is the hitbox only; the rig is drawn at visualScale so it fills that hitbox
|
||||||
|
return body->t.GetTranslationMatrix() * body->t.GetRotationMatrix()
|
||||||
|
* glm::scale(glm::mat4(1.0f), glm::vec3(visualScale));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Character::GetWorldBounds(BoundingBox& bounds) const {
|
||||||
if (!body)
|
if (!body)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
punchHitbox.hitTargets.clear();
|
if (ragdoll && !ragdoll->GetParts().empty()) {
|
||||||
|
bounds.min = glm::vec3(FLT_MAX);
|
||||||
|
bounds.max = glm::vec3(-FLT_MAX);
|
||||||
|
for (const Ragdoll::Part* part : ragdoll->GetParts()) {
|
||||||
|
const BoundingBox partBounds = part->transform.t.GetAABB();
|
||||||
|
bounds.min = glm::min(bounds.min, partBounds.min);
|
||||||
|
bounds.max = glm::max(bounds.max, partBounds.max);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
glm::vec3 forward = -body->t.GetFrontVector();
|
// The rig is about 2 units across natively in every direction it reaches, arms out included.
|
||||||
forward.y = 0.0f;
|
// A cube that size around the body covers any pose and any turn.
|
||||||
if (glm::length(forward) <= 0.0001f)
|
const glm::vec3 reach(visualScale);
|
||||||
return;
|
bounds.min = body->t.GetTranslation() - reach;
|
||||||
forward = glm::normalize(forward);
|
bounds.max = body->t.GetTranslation() + reach;
|
||||||
|
return true;
|
||||||
punchHitbox.transform.TranslateTo(body->t.GetTranslation() + forward * punchReach);
|
|
||||||
punchHitbox.transform.RotateToQuaternion(LookAt(forward));
|
|
||||||
punchHitbox.transform.ScaleTo(punchHitboxSize);
|
|
||||||
punchHitbox.damage = (int)punchDamage;
|
|
||||||
punchHitbox.knockback = 3.5f;
|
|
||||||
punchHitbox.active = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Character::Character() {
|
Character::Character() {
|
||||||
@@ -174,24 +150,15 @@ Character::Character() {
|
|||||||
|
|
||||||
void Character::Render(Shader& shader) {
|
void Character::Render(Shader& shader) {
|
||||||
if (!body || !body->rig || !body->animator) return;
|
if (!body || !body->rig || !body->animator) return;
|
||||||
if (body->animator->m_CurrentAnimation == nullptr) {
|
const std::vector<glm::mat4> transforms = ragdoll ? ragdoll->GetBoneMatrices()
|
||||||
for (int i = 0; i < 100; i++)
|
: body->animator->m_CurrentAnimation ? body->GetBlendedBoneMatrices()
|
||||||
shader.SetMat4(("finalBonesMatrices[" + std::to_string(i) + "]").c_str(), glm::mat4(1.0f));
|
: std::vector<glm::mat4>(100, glm::mat4(1.0f));
|
||||||
}
|
shader.SetMat4Array("finalBonesMatrices", transforms.data(), glm::min((int)transforms.size(), 100));
|
||||||
else {
|
|
||||||
std::vector<glm::mat4> transforms = body->GetBlendedBoneMatrices();
|
|
||||||
|
|
||||||
for (int i = 0; i < transforms.size(); i++)
|
|
||||||
shader.SetMat4(("finalBonesMatrices[" + std::to_string(i) + "]").c_str(), transforms[i]);
|
|
||||||
}
|
|
||||||
shader.Set4F("colorOverride", colorOverride);
|
shader.Set4F("colorOverride", colorOverride);
|
||||||
shader.SetInt("useTexture", 0);
|
surface.Apply(shader, 1, glm::vec3(visualScale));
|
||||||
shader.SetInt("tex", 1);
|
|
||||||
shader.Set1F("metallic", metallic);
|
shader.Set1F("metallic", metallic);
|
||||||
shader.Set1F("roughness", roughness);
|
shader.Set1F("roughness", roughness);
|
||||||
shader.Set1F("ao", ao);
|
shader.Set1F("ao", ao);
|
||||||
// body->t's scale is the hitbox only; the rig is drawn at visualScale so it fills that hitbox
|
body->rig->Render(shader, ragdoll ? glm::mat4(1.0f) : GetRigMatrix()); // ragdoll bones are already in world space
|
||||||
body->rig->Render(shader, body->t.GetTranslationMatrix() * body->t.GetRotationMatrix()
|
|
||||||
* glm::scale(glm::mat4(1.0f), glm::vec3(visualScale)));
|
|
||||||
shader.Set4F("colorOverride", glm::vec4(0.0f)); // don't leak into other rig draws
|
shader.Set4F("colorOverride", glm::vec4(0.0f)); // don't leak into other rig draws
|
||||||
}
|
}
|
||||||
+8
-36
@@ -14,22 +14,8 @@ class Character;
|
|||||||
class CharacterManager;
|
class CharacterManager;
|
||||||
class CharacterBody;
|
class CharacterBody;
|
||||||
class BodyComponent;
|
class BodyComponent;
|
||||||
|
class Ragdoll;
|
||||||
struct Hitbox {
|
class Physics;
|
||||||
t transform;
|
|
||||||
int damage = 0;
|
|
||||||
float knockback = 0.0f;
|
|
||||||
bool active = false;
|
|
||||||
std::set<Character*> hitTargets;
|
|
||||||
|
|
||||||
bool CanHit(Character* target) const {
|
|
||||||
return target && hitTargets.find(target) == hitTargets.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RegisterHit(Character* target) {
|
|
||||||
if (target) hitTargets.insert(target);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class Action {
|
class Action {
|
||||||
public:
|
public:
|
||||||
@@ -52,7 +38,6 @@ public:
|
|||||||
|
|
||||||
class CharacterBody : public t_package {
|
class CharacterBody : public t_package {
|
||||||
public:
|
public:
|
||||||
int Health = 100;
|
|
||||||
Rig* rig = nullptr;
|
Rig* rig = nullptr;
|
||||||
|
|
||||||
std::vector<Animator*> animators;
|
std::vector<Animator*> animators;
|
||||||
@@ -135,16 +120,14 @@ class Character : public Object, public Renderable {
|
|||||||
public:
|
public:
|
||||||
CharacterMind* mind = nullptr;
|
CharacterMind* mind = nullptr;
|
||||||
CharacterBody* body = nullptr;
|
CharacterBody* body = nullptr;
|
||||||
|
Ragdoll* ragdoll = nullptr; // when set, physics drives the rig instead of the animators
|
||||||
|
SurfaceTexture surface;
|
||||||
glm::vec4 colorOverride = glm::vec4(0.0f); // rgb + strength (0 = keep the rig's own texture)
|
glm::vec4 colorOverride = glm::vec4(0.0f); // rgb + strength (0 = keep the rig's own texture)
|
||||||
float visualScale = 1.4f; // rig is ~1.8 tall natively; hitbox is 2.5
|
float visualScale = 1.4f; // rig is ~1.8 tall natively; hitbox is 2.5
|
||||||
float metallic = 0.0f;
|
float metallic = 0.0f;
|
||||||
float roughness = 0.7f;
|
float roughness = 0.7f;
|
||||||
float ao = 1.0f;
|
float ao = 1.0f;
|
||||||
float turnSpeed = 12.0f;
|
float turnSpeed = 12.0f;
|
||||||
float punchDamage = 12.0f;
|
|
||||||
float punchReach = 1.15f;
|
|
||||||
float punchAnimationSpeed = 0.6f;
|
|
||||||
glm::vec3 punchHitboxSize = glm::vec3(1.1f, 1.5f, 1.4f);
|
|
||||||
|
|
||||||
void PressInput(char letter);
|
void PressInput(char letter);
|
||||||
void ReleaseInput(char letter);
|
void ReleaseInput(char letter);
|
||||||
@@ -152,37 +135,26 @@ public:
|
|||||||
glm::vec3 GetMovementDirection(const glm::vec3& forward, const glm::vec3& right) const;
|
glm::vec3 GetMovementDirection(const glm::vec3& forward, const glm::vec3& right) const;
|
||||||
void SetTurnDirection(const glm::vec3& direction);
|
void SetTurnDirection(const glm::vec3& direction);
|
||||||
const glm::vec3& GetTurnDirection() const { return turnDirection; }
|
const glm::vec3& GetTurnDirection() const { return turnDirection; }
|
||||||
void SetPunchAnimation(Animation* animation);
|
|
||||||
void SetPunchLooping(bool looping);
|
|
||||||
void Punch(const glm::vec3& lookDirection = glm::vec3(0.0f));
|
|
||||||
void SetWalking(bool walking);
|
void SetWalking(bool walking);
|
||||||
glm::vec3 GetForwardDirection() const;
|
glm::vec3 GetForwardDirection() const;
|
||||||
const Hitbox& GetPunchHitbox() const { return punchHitbox; }
|
void EnableRagdoll(Physics& physics);
|
||||||
|
|
||||||
Character();
|
Character();
|
||||||
void Render(Shader& shader) override;
|
void Render(Shader& shader) override;
|
||||||
|
bool GetWorldBounds(BoundingBox& bounds) const override;
|
||||||
|
|
||||||
void Step(float dt) {
|
void Step(float dt) {
|
||||||
if (punchLookLocked && punchAnimator && punchAnimator->isPlaying)
|
if (ragdoll) return;
|
||||||
turnDirection = punchLookDirection;
|
|
||||||
TurnToDirection(dt);
|
TurnToDirection(dt);
|
||||||
punchHitbox.active = false;
|
|
||||||
body->Step(dt);
|
body->Step(dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class CharacterManager;
|
|
||||||
|
|
||||||
void TurnToDirection(float dt);
|
void TurnToDirection(float dt);
|
||||||
void OnPunchAnimationEnd();
|
glm::mat4 GetRigMatrix() const;
|
||||||
|
|
||||||
std::set<char> inputLetters;
|
std::set<char> inputLetters;
|
||||||
glm::vec3 turnDirection = glm::vec3(0.0f);
|
glm::vec3 turnDirection = glm::vec3(0.0f);
|
||||||
Animation* punchAnimation = nullptr;
|
|
||||||
Animator* punchAnimator = nullptr;
|
|
||||||
Hitbox punchHitbox;
|
|
||||||
glm::vec3 punchLookDirection = glm::vec3(0.0f);
|
|
||||||
bool punchLookLocked = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
#include "CharacterManager.h"
|
#include "CharacterManager.h"
|
||||||
#include "Animator.h"
|
#include "Animator.h"
|
||||||
#include "Character.h"
|
#include "Character.h"
|
||||||
#include "Physics.h"
|
|
||||||
#include "Rig.h"
|
#include "Rig.h"
|
||||||
#include "tFunctions.h"
|
|
||||||
|
|
||||||
CharacterManager::CharacterManager() = default;
|
CharacterManager::CharacterManager() = default;
|
||||||
CharacterManager::~CharacterManager() = default;
|
CharacterManager::~CharacterManager() = default;
|
||||||
@@ -89,33 +87,3 @@ void CharacterManager::Step(float dt) {
|
|||||||
for (Character* character : characterOrder)
|
for (Character* character : characterOrder)
|
||||||
character->Step(dt);
|
character->Step(dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterManager::ResolveCombat(Physics& physics) {
|
|
||||||
for (Character* attacker : characterOrder) {
|
|
||||||
Hitbox& hitbox = attacker->punchHitbox;
|
|
||||||
if (!hitbox.active || hitbox.damage <= 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (Character* target : characterOrder) {
|
|
||||||
if (target == attacker || !hitbox.CanHit(target))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!BoundingBoxInBoundingBox(hitbox.transform.GetAABB(), target->body->t.GetAABB()))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
hitbox.RegisterHit(target);
|
|
||||||
target->body->Health = std::max(0, target->body->Health - hitbox.damage);
|
|
||||||
|
|
||||||
glm::vec3 knockback = target->body->t.GetTranslation() -
|
|
||||||
attacker->body->t.GetTranslation();
|
|
||||||
knockback.y = 0.0f;
|
|
||||||
if (glm::length(knockback) > 0.0001f)
|
|
||||||
knockback = glm::normalize(knockback) * hitbox.knockback;
|
|
||||||
knockback.y = 1.5f;
|
|
||||||
if (p* targetBody = physics.PPointerOfObject(target->body)) {
|
|
||||||
targetBody->linearvelocity += knockback;
|
|
||||||
targetBody->WakeUp();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class Character;
|
class Character;
|
||||||
class Physics;
|
|
||||||
class Rig;
|
class Rig;
|
||||||
class Animation;
|
class Animation;
|
||||||
|
|
||||||
@@ -33,7 +32,6 @@ public:
|
|||||||
void ClearInput();
|
void ClearInput();
|
||||||
|
|
||||||
void Step(float dt);
|
void Step(float dt);
|
||||||
void ResolveCombat(Physics& physics);
|
|
||||||
|
|
||||||
const std::vector<Character*>& GetCharacters() const { return characterOrder; }
|
const std::vector<Character*>& GetCharacters() const { return characterOrder; }
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
#include "CollisionMesh.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cfloat>
|
||||||
|
|
||||||
|
static constexpr int LEAF_TRIANGLES = 4;
|
||||||
|
|
||||||
|
static glm::vec3 TriangleCentroid(const Triangle& tri)
|
||||||
|
{
|
||||||
|
return (tri.a + tri.b + tri.c) / 3.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bounds overlap plus the triangle's plane, which is what prunes huge floor triangles
|
||||||
|
static bool TriangleOverlapsBox(const Triangle& tri, const glm::vec3& boxMin, const glm::vec3& boxMax)
|
||||||
|
{
|
||||||
|
const glm::vec3 triMin = glm::min(tri.a, glm::min(tri.b, tri.c));
|
||||||
|
const glm::vec3 triMax = glm::max(tri.a, glm::max(tri.b, tri.c));
|
||||||
|
if (glm::any(glm::lessThan(triMax, boxMin)) || glm::any(glm::greaterThan(triMin, boxMax)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const glm::vec3 normal = glm::cross(tri.b - tri.a, tri.c - tri.a);
|
||||||
|
const glm::vec3 center = (boxMin + boxMax) * 0.5f;
|
||||||
|
const glm::vec3 halfSize = (boxMax - boxMin) * 0.5f;
|
||||||
|
const float distance = glm::dot(normal, center - tri.a);
|
||||||
|
const float radius = glm::dot(glm::abs(normal), halfSize);
|
||||||
|
return glm::abs(distance) <= radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
CollisionMesh::CollisionMesh(const std::vector<Triangle>& meshTriangles)
|
||||||
|
: triangles(meshTriangles)
|
||||||
|
{
|
||||||
|
if (!triangles.empty())
|
||||||
|
Build(0, (int)triangles.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
int CollisionMesh::Build(int start, int count)
|
||||||
|
{
|
||||||
|
const int index = (int)nodes.size();
|
||||||
|
nodes.push_back(Node());
|
||||||
|
|
||||||
|
glm::vec3 boundsMin(FLT_MAX);
|
||||||
|
glm::vec3 boundsMax(-FLT_MAX);
|
||||||
|
glm::vec3 centroidMin(FLT_MAX);
|
||||||
|
glm::vec3 centroidMax(-FLT_MAX);
|
||||||
|
for (int i = start; i < start + count; i++) {
|
||||||
|
const Triangle& tri = triangles[i];
|
||||||
|
boundsMin = glm::min(boundsMin, glm::min(tri.a, glm::min(tri.b, tri.c)));
|
||||||
|
boundsMax = glm::max(boundsMax, glm::max(tri.a, glm::max(tri.b, tri.c)));
|
||||||
|
const glm::vec3 centroid = TriangleCentroid(tri);
|
||||||
|
centroidMin = glm::min(centroidMin, centroid);
|
||||||
|
centroidMax = glm::max(centroidMax, centroid);
|
||||||
|
}
|
||||||
|
nodes[index].boundsMin = boundsMin;
|
||||||
|
nodes[index].boundsMax = boundsMax;
|
||||||
|
|
||||||
|
if (count <= LEAF_TRIANGLES) {
|
||||||
|
nodes[index].start = start;
|
||||||
|
nodes[index].count = count;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split at the median along the widest axis
|
||||||
|
const glm::vec3 extent = centroidMax - centroidMin;
|
||||||
|
const int axis = extent.x > extent.y ? (extent.x > extent.z ? 0 : 2) : (extent.y > extent.z ? 1 : 2);
|
||||||
|
const int half = count / 2;
|
||||||
|
std::nth_element(triangles.begin() + start, triangles.begin() + start + half, triangles.begin() + start + count,
|
||||||
|
[axis](const Triangle& a, const Triangle& b) {
|
||||||
|
return TriangleCentroid(a)[axis] < TriangleCentroid(b)[axis];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build grows nodes, so index stays valid but references into it wouldn't
|
||||||
|
const int left = Build(start, half);
|
||||||
|
const int right = Build(start + half, count - half);
|
||||||
|
nodes[index].left = left;
|
||||||
|
nodes[index].right = right;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollisionMesh::Query(const Triangle& tri, std::vector<int>& hits) const
|
||||||
|
{
|
||||||
|
if (nodes.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
int stack[64];
|
||||||
|
int stackSize = 0;
|
||||||
|
stack[stackSize++] = 0;
|
||||||
|
|
||||||
|
while (stackSize > 0) {
|
||||||
|
const Node& node = nodes[stack[--stackSize]];
|
||||||
|
if (!TriangleOverlapsBox(tri, node.boundsMin, node.boundsMax))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (node.left == -1) {
|
||||||
|
for (int i = node.start; i < node.start + node.count; i++)
|
||||||
|
hits.push_back(i);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stack[stackSize++] = node.left;
|
||||||
|
stack[stackSize++] = node.right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CollisionMesh& CollisionMesh::UnitBox()
|
||||||
|
{
|
||||||
|
static const CollisionMesh box = []() {
|
||||||
|
const glm::vec3 axes[3] = { {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f} };
|
||||||
|
|
||||||
|
std::vector<Triangle> faces;
|
||||||
|
for (int axis = 0; axis < 3; axis++) {
|
||||||
|
for (float side : { 1.0f, -1.0f }) {
|
||||||
|
const glm::vec3 normal = axes[axis] * side;
|
||||||
|
const glm::vec3 u = axes[(axis + 1) % 3];
|
||||||
|
const glm::vec3 v = glm::cross(normal, u); // u x v = normal, so the winding faces outward
|
||||||
|
|
||||||
|
const glm::vec3 center = normal * 0.5f;
|
||||||
|
const glm::vec3 p0 = center - u * 0.5f - v * 0.5f;
|
||||||
|
const glm::vec3 p1 = center + u * 0.5f - v * 0.5f;
|
||||||
|
const glm::vec3 p2 = center + u * 0.5f + v * 0.5f;
|
||||||
|
const glm::vec3 p3 = center - u * 0.5f + v * 0.5f;
|
||||||
|
faces.push_back({ p0, p1, p2 });
|
||||||
|
faces.push_back({ p0, p2, p3 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CollisionMesh(faces);
|
||||||
|
}();
|
||||||
|
return box;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#ifndef COLLISION_MESH_CLASS
|
||||||
|
#define COLLISION_MESH_CLASS
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
#include "Structures.h"
|
||||||
|
|
||||||
|
// Triangle soup for triangle by triangle collision, wrapped in a BVH.
|
||||||
|
// Triangles live in the body's unit space, so t.GetMatrix() takes them to world space
|
||||||
|
// the same way it does for a Mesh. Winding must be counter clockwise seen from outside.
|
||||||
|
class CollisionMesh {
|
||||||
|
public:
|
||||||
|
CollisionMesh(const std::vector<Triangle>& meshTriangles);
|
||||||
|
|
||||||
|
// The 12 triangles of a unit cube, used for any body without its own mesh
|
||||||
|
static const CollisionMesh& UnitBox();
|
||||||
|
|
||||||
|
const std::vector<Triangle>& GetTriangles() const { return triangles; }
|
||||||
|
|
||||||
|
// Appends the index of every triangle whose BVH leaf could touch tri (same space as the mesh)
|
||||||
|
void Query(const Triangle& tri, std::vector<int>& hits) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Node {
|
||||||
|
glm::vec3 boundsMin;
|
||||||
|
glm::vec3 boundsMax;
|
||||||
|
int left = -1; // -1 = leaf
|
||||||
|
int right = -1;
|
||||||
|
int start = 0;
|
||||||
|
int count = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
int Build(int start, int count);
|
||||||
|
|
||||||
|
std::vector<Triangle> triangles;
|
||||||
|
std::vector<Node> nodes;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
+443
-262
@@ -68,12 +68,14 @@
|
|||||||
#include "Sound.h"
|
#include "Sound.h"
|
||||||
#include "Gui.h"
|
#include "Gui.h"
|
||||||
#include "font.h"
|
#include "font.h"
|
||||||
|
#include "TexturePainter.h"
|
||||||
|
|
||||||
// Character / animation
|
// Character / animation
|
||||||
#include "Rig.h"
|
#include "Rig.h"
|
||||||
#include "Animator.h"
|
#include "Animator.h"
|
||||||
#include "Character.h"
|
#include "Character.h"
|
||||||
#include "CharacterManager.h"
|
#include "CharacterManager.h"
|
||||||
|
#include "Ragdoll.h"
|
||||||
#include "DefaultBodyComponents.h"
|
#include "DefaultBodyComponents.h"
|
||||||
#include "BodyComponentActions.h"
|
#include "BodyComponentActions.h"
|
||||||
|
|
||||||
@@ -108,6 +110,12 @@ Engine::Engine()
|
|||||||
|
|
||||||
rendersystem->RigShader = new Shader("shaders/rig.vert", "shaders/default.frag");
|
rendersystem->RigShader = new Shader("shaders/rig.vert", "shaders/default.frag");
|
||||||
rendersystem->MeshShader = new Shader("shaders/default.vert", "shaders/default.frag");
|
rendersystem->MeshShader = new Shader("shaders/default.vert", "shaders/default.frag");
|
||||||
|
rendersystem->GeometryShader = new Shader("shaders/geometry.vert", "shaders/geometry.frag");
|
||||||
|
rendersystem->GeometryRigShader = new Shader("shaders/geometry_rig.vert", "shaders/geometry.frag");
|
||||||
|
rendersystem->DeferredLightingShader = new Shader("shaders/deferred_lighting.vert", "shaders/deferred_lighting.frag");
|
||||||
|
rendersystem->GBufferDebugShader = new Shader("shaders/gbuffer_debug.vert", "shaders/gbuffer_debug.frag");
|
||||||
|
rendersystem->BloomExtractShader = new Shader("shaders/ScreenShader.vert", "shaders/bloom_extract.frag");
|
||||||
|
rendersystem->BloomBlurShader = new Shader("shaders/ScreenShader.vert", "shaders/bloom_blur.frag");
|
||||||
rendersystem->MeshShadowShader = new Shader("shaders/meshshadow.vert", "shaders/shadow.frag");
|
rendersystem->MeshShadowShader = new Shader("shaders/meshshadow.vert", "shaders/shadow.frag");
|
||||||
rendersystem->RigShadowShader = new Shader("shaders/rigshadow.vert", "shaders/shadow.frag");
|
rendersystem->RigShadowShader = new Shader("shaders/rigshadow.vert", "shaders/shadow.frag");
|
||||||
rendersystem->SkyboxShader = new Shader("shaders/skybox.vert", "shaders/skybox.frag");
|
rendersystem->SkyboxShader = new Shader("shaders/skybox.vert", "shaders/skybox.frag");
|
||||||
@@ -124,14 +132,12 @@ Engine::Engine()
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
void Engine::Initiate()
|
void Engine::Initiate()
|
||||||
{
|
{
|
||||||
Font* arial = new Font("assets/ft/arial.ttf");
|
Font* arial = resourcemanager->GetFont("assets/ft/arial.ttf");
|
||||||
Texture* blanktex = new Texture("assets/blanktexture.png");
|
Texture* blanktex = resourcemanager->GetTexture("assets/blanktexture.png");
|
||||||
Texture* snowflake = new Texture("assets/snowflake.png");
|
|
||||||
|
|
||||||
charactermanager = new CharacterManager();
|
charactermanager = new CharacterManager();
|
||||||
charactermanager->LoadRig("humanoid", "assets/rig.glb");
|
charactermanager->LoadRig("humanoid", "assets/rig.glb");
|
||||||
charactermanager->LoadAnimation("walk", "assets/walkanim.glb", "humanoid");
|
charactermanager->LoadAnimation("walk", "assets/walkanim.glb", "humanoid");
|
||||||
charactermanager->LoadAnimation("punch", "assets/punchanim.glb", "humanoid");
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// Player character
|
// Player character
|
||||||
@@ -144,7 +150,6 @@ void Engine::Initiate()
|
|||||||
testcharacter.body->t.ScaleBy({ 0.7f, 2.5f, 0.7f });
|
testcharacter.body->t.ScaleBy({ 0.7f, 2.5f, 0.7f });
|
||||||
testcharacter.body->t.TranslateTo({ 0.0f, 1.5f, 0.0f });
|
testcharacter.body->t.TranslateTo({ 0.0f, 1.5f, 0.0f });
|
||||||
testcharacter.body->animator->loops = true;
|
testcharacter.body->animator->loops = true;
|
||||||
testcharacter.SetPunchAnimation(charactermanager->GetAnimation("punch"));
|
|
||||||
testcharacter.BindToRenderSystem();
|
testcharacter.BindToRenderSystem();
|
||||||
|
|
||||||
p* characterBody = new p(testcharacter.body);
|
p* characterBody = new p(testcharacter.body);
|
||||||
@@ -163,39 +168,11 @@ void Engine::Initiate()
|
|||||||
npc.roughness = 0.5f;
|
npc.roughness = 0.5f;
|
||||||
npc.body->t.ScaleBy({ 0.7f, 2.5f, 0.7f });
|
npc.body->t.ScaleBy({ 0.7f, 2.5f, 0.7f });
|
||||||
npc.body->t.TranslateTo({ 3.0f, 1.25f, 3.0f });
|
npc.body->t.TranslateTo({ 3.0f, 1.25f, 3.0f });
|
||||||
npc.SetPunchAnimation(charactermanager->GetAnimation("punch"));
|
|
||||||
npc.SetPunchLooping(true);
|
|
||||||
npc.Punch();
|
|
||||||
npc.BindToRenderSystem();
|
npc.BindToRenderSystem();
|
||||||
|
|
||||||
p* npcBody = new p(npc.body);
|
|
||||||
npcBody->mass = 80.0f;
|
|
||||||
npcBody->lockRotation = true;
|
|
||||||
npcBody->friction = 0.0f;
|
|
||||||
physicsengine->AddObject(npcBody);
|
|
||||||
|
|
||||||
testcharacter.SetWalking(false);
|
testcharacter.SetWalking(false);
|
||||||
npc.SetWalking(false);
|
npc.SetWalking(false);
|
||||||
|
npc.EnableRagdoll(*physicsengine);
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// Rain particles
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
ParticleEmitter* rain = new ParticleEmitter();
|
|
||||||
rain->name = "rain";
|
|
||||||
rain->tex = snowflake;
|
|
||||||
rain->opaque = false;
|
|
||||||
rain->speed = 35.0f;
|
|
||||||
rain->lifespan = 0.6f;
|
|
||||||
rain->angularvelocity = { 0.0f, 0.0f, glm::radians(10.0f) };
|
|
||||||
rain->size = { 0.04f, 0.4f, 0.0f };
|
|
||||||
rain->emitangle = { glm::radians(60.0f), glm::radians(360.0f), glm::radians(60.0f) };
|
|
||||||
rain->facecamera = false;
|
|
||||||
rain->color = { 1.0f, 1.0f, 1.0f, 1.0f };
|
|
||||||
rain->emitdirection = ParticleEmitter::EmitDirection::Perpendicular;
|
|
||||||
rain->t.TranslateTo({ 0.0f, 10.0f, 0.0f });
|
|
||||||
rain->t.RotateToQuaternion(glm::quat(glm::vec3(glm::radians(85.0f), 0.0f, 0.0f)));
|
|
||||||
rain->BindToRenderSystem();
|
|
||||||
mainf->AddChild(rain);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 3D audio
|
// 3D audio
|
||||||
@@ -221,149 +198,272 @@ void Engine::Initiate()
|
|||||||
fpsCounter->t2d.size = { 0.1f, 0.1f, 0.0f, 0.0f };
|
fpsCounter->t2d.size = { 0.1f, 0.1f, 0.0f, 0.0f };
|
||||||
fpsCounter->BindToRenderSystem();
|
fpsCounter->BindToRenderSystem();
|
||||||
|
|
||||||
TextBox* healthText = new TextBox();
|
|
||||||
healthText->font = arial;
|
|
||||||
healthText->text = "";
|
|
||||||
healthText->fontsize = 0.5f;
|
|
||||||
healthText->Color = { 0.95f, 0.97f, 1.0f };
|
|
||||||
healthText->t2d.position = { 0.02f, 0.02f, 0.0f, 0.0f };
|
|
||||||
healthText->t2d.size = { 0.45f, 0.0f, 0.0f, 40.0f };
|
|
||||||
healthText->textCenter = { 0.0f, 0.0f };
|
|
||||||
healthText->BindToRenderSystem();
|
|
||||||
|
|
||||||
const float healthBarFillWidth = 314.0f;
|
|
||||||
|
|
||||||
Box* healthBarBack = new Box();
|
|
||||||
healthBarBack->Color = { 0.03f, 0.04f, 0.06f };
|
|
||||||
healthBarBack->Opacity = 0.85f;
|
|
||||||
healthBarBack->t2d.position = { 0.02f, 1.0f, 0.0f, -30.0f };
|
|
||||||
healthBarBack->t2d.center = { 0.0f, 1.0f };
|
|
||||||
healthBarBack->t2d.size = { 0.0f, 0.0f, 320.0f, 26.0f };
|
|
||||||
healthBarBack->BindToRenderSystem();
|
|
||||||
|
|
||||||
Box* healthBarFill = new Box();
|
|
||||||
healthBarFill->Color = { 0.2f, 0.85f, 0.35f };
|
|
||||||
healthBarFill->t2d.position = { 0.02f, 1.0f, 3.0f, -33.0f };
|
|
||||||
healthBarFill->t2d.center = { 0.0f, 1.0f };
|
|
||||||
healthBarFill->t2d.size = { 0.0f, 0.0f, healthBarFillWidth, 20.0f };
|
|
||||||
healthBarFill->BindToRenderSystem();
|
|
||||||
|
|
||||||
struct MenuButton {
|
struct MenuButton {
|
||||||
Box* box;
|
Box* box;
|
||||||
|
Box* accent;
|
||||||
TextBox* label;
|
TextBox* label;
|
||||||
glm::vec3 color;
|
glm::vec3 color;
|
||||||
|
glm::vec3 hoverColor;
|
||||||
|
float opacity;
|
||||||
|
float labelX;
|
||||||
|
float labelSlide; // how far the label moves right on hover
|
||||||
|
bool hovered = false;
|
||||||
|
float hover = 0.0f; // eased 0-1
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class MenuScreen { Main, Paused, Settings };
|
enum class MenuScreen { Main, Paused, Settings, Graphics, Drawer };
|
||||||
MenuScreen menuScreen = MenuScreen::Main;
|
MenuScreen menuScreen = MenuScreen::Main;
|
||||||
MenuScreen settingsReturn = MenuScreen::Main;
|
MenuScreen settingsReturn = MenuScreen::Main;
|
||||||
bool menuActive = true;
|
bool menuActive = true;
|
||||||
bool prevMouseDown = false;
|
bool prevMouseDown = false;
|
||||||
std::vector<Box*> menuItems;
|
std::vector<Box*> menuItems;
|
||||||
|
|
||||||
|
// Everything hangs off a left side panel so the live scene stays visible.
|
||||||
|
// Items are positioned in pixels from the left edge and the vertical center of the window.
|
||||||
|
const float menuPanelWidth = 460.0f;
|
||||||
|
const float menuMargin = 48.0f;
|
||||||
|
const float menuContentWidth = 364.0f;
|
||||||
|
const glm::vec3 menuAccent(0.96f, 0.58f, 0.16f);
|
||||||
|
const glm::vec3 menuText(0.95f, 0.97f, 1.0f);
|
||||||
|
const glm::vec3 menuMutedText(0.62f, 0.68f, 0.77f);
|
||||||
|
const glm::vec3 menuButtonColor(0.11f, 0.14f, 0.19f);
|
||||||
|
const glm::vec3 menuButtonHover(0.20f, 0.25f, 0.33f);
|
||||||
|
|
||||||
Box* menuVeil = new Box();
|
Box* menuVeil = new Box();
|
||||||
menuVeil->Color = { 0.02f, 0.03f, 0.05f };
|
menuVeil->Color = { 0.02f, 0.03f, 0.05f };
|
||||||
menuVeil->Opacity = 0.55f;
|
|
||||||
menuVeil->t2d.position = { 0.0f, 0.0f, 0.0f, 0.0f };
|
menuVeil->t2d.position = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||||
menuVeil->t2d.size = { 1.0f, 1.0f, 0.0f, 0.0f };
|
menuVeil->t2d.size = { 1.0f, 1.0f, 0.0f, 0.0f };
|
||||||
menuVeil->BindToRenderSystem();
|
menuVeil->BindToRenderSystem();
|
||||||
menuItems.push_back(menuVeil);
|
menuItems.push_back(menuVeil);
|
||||||
|
|
||||||
TextBox* menuTitle = new TextBox();
|
Box* menuPanel = new Box();
|
||||||
menuTitle->font = arial;
|
menuPanel->Color = { 0.03f, 0.04f, 0.06f };
|
||||||
menuTitle->text = "EIIFWHRN";
|
menuPanel->t2d.position = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||||
menuTitle->fontsize = 3.0f;
|
menuPanel->t2d.size = { 0.0f, 1.0f, menuPanelWidth, 0.0f };
|
||||||
menuTitle->Color = { 0.95f, 0.97f, 1.0f };
|
menuPanel->BindToRenderSystem();
|
||||||
menuTitle->t2d.position = { 0.5f, 0.17f, 0.0f, 0.0f };
|
menuItems.push_back(menuPanel);
|
||||||
menuTitle->t2d.center = { 0.5f, 0.5f };
|
|
||||||
menuTitle->t2d.size = { 0.8f, 0.0f, 0.0f, 170.0f };
|
|
||||||
menuTitle->textCenter = { 0.5f, 0.5f };
|
|
||||||
menuTitle->BindToRenderSystem();
|
|
||||||
menuItems.push_back(menuTitle);
|
|
||||||
|
|
||||||
TextBox* menuSubtitle = new TextBox();
|
Box* menuPanelEdge = new Box();
|
||||||
menuSubtitle->font = arial;
|
menuPanelEdge->Color = menuAccent;
|
||||||
menuSubtitle->text = "MAIN MENU";
|
menuPanelEdge->t2d.position = { 0.0f, 0.0f, menuPanelWidth, 0.0f };
|
||||||
menuSubtitle->fontsize = 0.65f;
|
menuPanelEdge->t2d.size = { 0.0f, 1.0f, 3.0f, 0.0f };
|
||||||
menuSubtitle->Color = { 0.55f, 0.65f, 0.78f };
|
menuPanelEdge->BindToRenderSystem();
|
||||||
menuSubtitle->t2d.position = { 0.5f, 0.285f, 0.0f, 0.0f };
|
menuItems.push_back(menuPanelEdge);
|
||||||
menuSubtitle->t2d.center = { 0.5f, 0.5f };
|
|
||||||
menuSubtitle->t2d.size = { 0.4f, 0.0f, 0.0f, 50.0f };
|
|
||||||
menuSubtitle->textCenter = { 0.5f, 0.5f };
|
|
||||||
menuSubtitle->BindToRenderSystem();
|
|
||||||
menuItems.push_back(menuSubtitle);
|
|
||||||
|
|
||||||
auto MakeMenuButton = [&](const char* text, float x, float y, glm::vec3 color,
|
|
||||||
float width = 340.0f, float height = 62.0f,
|
|
||||||
float fontsize = 0.85f) {
|
|
||||||
Box* box = new Box();
|
|
||||||
box->Color = color;
|
|
||||||
box->Opacity = 0.92f;
|
|
||||||
box->t2d.position = { x, y, 0.0f, 0.0f };
|
|
||||||
box->t2d.center = { 0.5f, 0.5f };
|
|
||||||
box->t2d.size = { 0.0f, 0.0f, width, height };
|
|
||||||
box->BindToRenderSystem();
|
|
||||||
menuItems.push_back(box);
|
|
||||||
|
|
||||||
|
auto MakeMenuText = [&](const char* text, float x, float y, float width, float height,
|
||||||
|
float fontsize, glm::vec3 color, float alignX = 0.0f) {
|
||||||
TextBox* label = new TextBox();
|
TextBox* label = new TextBox();
|
||||||
label->font = arial;
|
label->font = arial;
|
||||||
label->text = text;
|
label->text = text;
|
||||||
label->fontsize = fontsize;
|
label->fontsize = fontsize;
|
||||||
label->Color = { 0.95f, 0.97f, 1.0f };
|
label->Color = color;
|
||||||
label->t2d.position = { x, y, 0.0f, 0.0f };
|
label->t2d.position = { 0.0f, 0.5f, x, y };
|
||||||
label->t2d.center = { 0.5f, 0.5f };
|
label->t2d.center = { 0.0f, 0.5f };
|
||||||
label->t2d.size = { 0.0f, 0.0f, width, height };
|
label->t2d.size = { 0.0f, 0.0f, width, height };
|
||||||
label->textCenter = { 0.5f, 0.5f };
|
label->textCenter = { alignX, 0.5f };
|
||||||
label->BindToRenderSystem();
|
label->BindToRenderSystem();
|
||||||
menuItems.push_back(label);
|
menuItems.push_back(label);
|
||||||
|
return label;
|
||||||
return MenuButton{ box, label, color };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuButton playButton = MakeMenuButton("PLAY", 0.5f, 0.46f, glm::vec3(0.12f, 0.30f, 0.22f));
|
TextBox* menuTitle = MakeMenuText("EIIFWHRN", menuMargin, -235.0f, 400.0f, 100.0f, 1.6f, menuText);
|
||||||
MenuButton settingsButton = MakeMenuButton("SETTINGS", 0.5f, 0.58f, glm::vec3(0.15f, 0.23f, 0.34f));
|
TextBox* menuSubtitle = MakeMenuText("MAIN MENU", menuMargin, -165.0f, menuContentWidth, 36.0f, 0.5f, menuAccent);
|
||||||
MenuButton quitButton = MakeMenuButton("QUIT", 0.5f, 0.70f, glm::vec3(0.17f, 0.18f, 0.22f));
|
|
||||||
|
|
||||||
MenuButton sensitivityDown = MakeMenuButton("- SENS", 0.40f, 0.47f,
|
auto MakeMenuButton = [&](const char* text, float x, float y, float width, float height,
|
||||||
glm::vec3(0.15f, 0.23f, 0.34f), 170.0f, 50.0f, 0.65f);
|
float fontsize, glm::vec3 color, glm::vec3 hoverColor,
|
||||||
MenuButton sensitivityUp = MakeMenuButton("+ SENS", 0.60f, 0.47f,
|
float opacity = 0.6f, bool compact = false) {
|
||||||
glm::vec3(0.15f, 0.23f, 0.34f), 170.0f, 50.0f, 0.65f);
|
Box* box = new Box();
|
||||||
MenuButton fovDown = MakeMenuButton("- FOV", 0.40f, 0.58f,
|
box->Color = color;
|
||||||
glm::vec3(0.15f, 0.23f, 0.34f), 170.0f, 50.0f, 0.65f);
|
box->t2d.position = { 0.0f, 0.5f, x, y };
|
||||||
MenuButton fovUp = MakeMenuButton("+ FOV", 0.60f, 0.58f,
|
box->t2d.center = { 0.0f, 0.5f };
|
||||||
glm::vec3(0.15f, 0.23f, 0.34f), 170.0f, 50.0f, 0.65f);
|
box->t2d.size = { 0.0f, 0.0f, width, height };
|
||||||
MenuButton hitboxToggle = MakeMenuButton("HITBOXES: OFF", 0.5f, 0.68f,
|
box->rounding = 0.15f;
|
||||||
glm::vec3(0.20f, 0.22f, 0.28f), 340.0f, 50.0f, 0.65f);
|
box->BindToRenderSystem();
|
||||||
MenuButton resetButton = MakeMenuButton("RESET DEFAULTS", 0.5f, 0.77f,
|
menuItems.push_back(box);
|
||||||
glm::vec3(0.20f, 0.22f, 0.28f), 340.0f, 52.0f, 0.65f);
|
|
||||||
MenuButton backButton = MakeMenuButton("BACK", 0.5f, 0.86f,
|
|
||||||
glm::vec3(0.12f, 0.30f, 0.22f), 340.0f, 52.0f, 0.85f);
|
|
||||||
|
|
||||||
TextBox* settingsInfo = new TextBox();
|
Box* accent = new Box();
|
||||||
settingsInfo->font = arial;
|
accent->Color = menuAccent;
|
||||||
settingsInfo->fontsize = 0.65f;
|
accent->t2d.position = { 0.0f, 0.5f, x, y };
|
||||||
settingsInfo->Color = { 0.85f, 0.90f, 0.97f };
|
accent->t2d.center = { 0.0f, 0.5f };
|
||||||
settingsInfo->t2d.position = { 0.5f, 0.37f, 0.0f, 0.0f };
|
accent->t2d.size = { 0.0f, 0.0f, 0.0f, height };
|
||||||
settingsInfo->t2d.center = { 0.5f, 0.5f };
|
accent->BindToRenderSystem();
|
||||||
settingsInfo->t2d.size = { 0.8f, 0.0f, 0.0f, 90.0f };
|
menuItems.push_back(accent);
|
||||||
settingsInfo->textCenter = { 0.5f, 0.5f };
|
|
||||||
settingsInfo->BindToRenderSystem();
|
// Compact buttons hold a single centered symbol, the rest are left aligned and slide on hover
|
||||||
menuItems.push_back(settingsInfo);
|
const float padding = compact ? 0.0f : 20.0f;
|
||||||
|
TextBox* label = MakeMenuText(text, x + padding, y, width - padding * 2.0f, height,
|
||||||
|
fontsize, menuText, compact ? 0.5f : 0.0f);
|
||||||
|
|
||||||
|
return MenuButton{ box, accent, label, color, hoverColor, opacity,
|
||||||
|
x + padding, compact ? 0.0f : 8.0f };
|
||||||
|
};
|
||||||
|
|
||||||
|
const float mainButtonHeight = 60.0f;
|
||||||
|
MenuButton playButton = MakeMenuButton("PLAY", menuMargin, -60.0f, menuContentWidth, mainButtonHeight, 0.75f,
|
||||||
|
glm::vec3(0.82f, 0.45f, 0.10f), menuAccent, 0.95f);
|
||||||
|
MenuButton drawerButton = MakeMenuButton("TEXTURE DRAWER", menuMargin, 12.0f, menuContentWidth, mainButtonHeight, 0.75f,
|
||||||
|
menuButtonColor, menuButtonHover);
|
||||||
|
MenuButton settingsButton = MakeMenuButton("SETTINGS", menuMargin, 84.0f, menuContentWidth, mainButtonHeight, 0.75f,
|
||||||
|
menuButtonColor, menuButtonHover);
|
||||||
|
MenuButton quitButton = MakeMenuButton("QUIT", menuMargin, 156.0f, menuContentWidth, mainButtonHeight, 0.75f,
|
||||||
|
menuButtonColor, glm::vec3(0.42f, 0.14f, 0.16f));
|
||||||
|
|
||||||
|
// Settings rows: LABEL [-] VALUE [+]
|
||||||
|
const float stepperDownX = menuMargin + 176.0f;
|
||||||
|
const float stepperValueX = stepperDownX + 44.0f;
|
||||||
|
const float stepperUpX = menuMargin + menuContentWidth - 44.0f;
|
||||||
|
const float stepperValueWidth = stepperUpX - stepperValueX;
|
||||||
|
const float sensRowY = -120.0f;
|
||||||
|
const float fovRowY = -64.0f;
|
||||||
|
|
||||||
|
TextBox* sensLabel = MakeMenuText("SENSITIVITY", menuMargin, sensRowY, 172.0f, 44.0f, 0.5f, menuMutedText);
|
||||||
|
TextBox* sensValue = MakeMenuText("", stepperValueX, sensRowY, stepperValueWidth, 44.0f, 0.5f, menuText, 0.5f);
|
||||||
|
TextBox* fovLabel = MakeMenuText("FIELD OF VIEW", menuMargin, fovRowY, 172.0f, 44.0f, 0.5f, menuMutedText);
|
||||||
|
TextBox* fovValue = MakeMenuText("", stepperValueX, fovRowY, stepperValueWidth, 44.0f, 0.5f, menuText, 0.5f);
|
||||||
|
|
||||||
|
MenuButton sensitivityDown = MakeMenuButton("-", stepperDownX, sensRowY, 44.0f, 44.0f, 0.7f,
|
||||||
|
menuButtonColor, menuButtonHover, 0.6f, true);
|
||||||
|
MenuButton sensitivityUp = MakeMenuButton("+", stepperUpX, sensRowY, 44.0f, 44.0f, 0.7f,
|
||||||
|
menuButtonColor, menuButtonHover, 0.6f, true);
|
||||||
|
MenuButton fovDown = MakeMenuButton("-", stepperDownX, fovRowY, 44.0f, 44.0f, 0.7f,
|
||||||
|
menuButtonColor, menuButtonHover, 0.6f, true);
|
||||||
|
MenuButton fovUp = MakeMenuButton("+", stepperUpX, fovRowY, 44.0f, 44.0f, 0.7f,
|
||||||
|
menuButtonColor, menuButtonHover, 0.6f, true);
|
||||||
|
MenuButton hitboxToggle = MakeMenuButton("HITBOXES: OFF", menuMargin, -18.0f, menuContentWidth, 40.0f, 0.6f,
|
||||||
|
menuButtonColor, menuButtonHover);
|
||||||
|
MenuButton graphicsButton = MakeMenuButton("GRAPHICS", menuMargin, 26.0f, menuContentWidth, 40.0f, 0.6f,
|
||||||
|
menuButtonColor, menuButtonHover);
|
||||||
|
MenuButton resetButton = MakeMenuButton("RESET DEFAULTS", menuMargin, 70.0f, menuContentWidth, 40.0f, 0.6f,
|
||||||
|
menuButtonColor, menuButtonHover);
|
||||||
|
|
||||||
|
MenuButton bloomToggle = MakeMenuButton("", menuMargin, -128.0f, menuContentWidth, 44.0f, 0.6f,
|
||||||
|
menuButtonColor, menuButtonHover);
|
||||||
|
MenuButton shadowToggle = MakeMenuButton("", menuMargin, -78.0f, menuContentWidth, 44.0f, 0.6f,
|
||||||
|
menuButtonColor, menuButtonHover);
|
||||||
|
MenuButton lightShadowToggle = MakeMenuButton("", menuMargin, -28.0f, menuContentWidth, 44.0f, 0.6f,
|
||||||
|
menuButtonColor, menuButtonHover);
|
||||||
|
MenuButton vsyncToggle = MakeMenuButton("", menuMargin, 22.0f, menuContentWidth, 44.0f, 0.6f,
|
||||||
|
menuButtonColor, menuButtonHover);
|
||||||
|
MenuButton cullingToggle = MakeMenuButton("", menuMargin, 72.0f, menuContentWidth, 44.0f, 0.6f,
|
||||||
|
menuButtonColor, menuButtonHover);
|
||||||
|
MenuButton backButton = MakeMenuButton("BACK", menuMargin, 120.0f, menuContentWidth, 50.0f, 0.6f,
|
||||||
|
glm::vec3(0.82f, 0.45f, 0.10f), menuAccent, 0.95f);
|
||||||
|
|
||||||
|
// Texture drawer. Filled in further down, once the things it paints on exist.
|
||||||
|
struct DrawableSurface {
|
||||||
|
const char* name;
|
||||||
|
SurfaceTexture* surface;
|
||||||
|
glm::vec4* colorOverride;
|
||||||
|
bool hasCanvas = false;
|
||||||
|
};
|
||||||
|
std::vector<DrawableSurface> drawableSurfaces;
|
||||||
|
int selectedSurface = 0;
|
||||||
|
int selectedSwatch = 0;
|
||||||
|
TexturePainter painter;
|
||||||
|
bool painting = false;
|
||||||
|
|
||||||
|
// The first edit swaps in a private copy, so painting never touches the shared image file.
|
||||||
|
// A colour override would hide the paint, so that goes too.
|
||||||
|
auto EnsureCanvas = [&](DrawableSurface& drawable) {
|
||||||
|
if (drawable.hasCanvas)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SurfaceTexture& surface = *drawable.surface;
|
||||||
|
surface.texture = surface.texture ? resourcemanager->CopyTexture(*surface.texture)
|
||||||
|
: resourcemanager->CreateTexture(256, 256);
|
||||||
|
drawable.colorOverride->w = 0.0f;
|
||||||
|
drawable.hasCanvas = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const float surfaceRowY = -120.0f;
|
||||||
|
const float swatchRowY = -64.0f;
|
||||||
|
const float brushRowY = -8.0f;
|
||||||
|
|
||||||
|
MenuButton surfacePrevious = MakeMenuButton("<", menuMargin, surfaceRowY, 44.0f, 44.0f, 0.6f,
|
||||||
|
menuButtonColor, menuButtonHover, 0.6f, true);
|
||||||
|
MenuButton surfaceNext = MakeMenuButton(">", stepperUpX, surfaceRowY, 44.0f, 44.0f, 0.6f,
|
||||||
|
menuButtonColor, menuButtonHover, 0.6f, true);
|
||||||
|
TextBox* surfaceName = MakeMenuText("", menuMargin + 44.0f, surfaceRowY, menuContentWidth - 88.0f, 44.0f, 0.5f, menuText, 0.5f);
|
||||||
|
|
||||||
|
const glm::vec3 swatchColors[] = {
|
||||||
|
{ 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f }, { 0.90f, 0.15f, 0.15f },
|
||||||
|
{ 0.96f, 0.58f, 0.16f }, { 0.20f, 0.75f, 0.30f }, { 0.20f, 0.45f, 0.95f }
|
||||||
|
};
|
||||||
|
const float swatchSize = 52.0f;
|
||||||
|
const float swatchSpacing = 60.0f;
|
||||||
|
|
||||||
|
std::vector<MenuButton> swatches;
|
||||||
|
swatches.reserve(std::size(swatchColors));
|
||||||
|
for (int i = 0; i < (int)std::size(swatchColors); i++) {
|
||||||
|
swatches.push_back(MakeMenuButton("", menuMargin + swatchSpacing * i, swatchRowY, swatchSize, 44.0f, 0.5f,
|
||||||
|
swatchColors[i], glm::mix(swatchColors[i], glm::vec3(1.0f), 0.2f), 1.0f, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
Box* swatchMarker = new Box();
|
||||||
|
swatchMarker->Color = menuAccent;
|
||||||
|
swatchMarker->t2d.center = { 0.0f, 0.5f };
|
||||||
|
swatchMarker->t2d.size = { 0.0f, 0.0f, swatchSize, 4.0f };
|
||||||
|
swatchMarker->BindToRenderSystem();
|
||||||
|
menuItems.push_back(swatchMarker);
|
||||||
|
|
||||||
|
TextBox* brushLabel = MakeMenuText("BRUSH SIZE", menuMargin, brushRowY, 172.0f, 44.0f, 0.5f, menuMutedText);
|
||||||
|
TextBox* brushValue = MakeMenuText("", stepperValueX, brushRowY, stepperValueWidth, 44.0f, 0.5f, menuText, 0.5f);
|
||||||
|
MenuButton brushDown = MakeMenuButton("-", stepperDownX, brushRowY, 44.0f, 44.0f, 0.7f,
|
||||||
|
menuButtonColor, menuButtonHover, 0.6f, true);
|
||||||
|
MenuButton brushUp = MakeMenuButton("+", stepperUpX, brushRowY, 44.0f, 44.0f, 0.7f,
|
||||||
|
menuButtonColor, menuButtonHover, 0.6f, true);
|
||||||
|
MenuButton clearButton = MakeMenuButton("CLEAR", menuMargin, 52.0f, menuContentWidth, 50.0f, 0.6f,
|
||||||
|
menuButtonColor, menuButtonHover);
|
||||||
|
|
||||||
|
// The canvas sits in the middle of the space right of the panel
|
||||||
|
const float canvasSize = 640.0f;
|
||||||
|
|
||||||
|
Box* canvasBorder = new Box();
|
||||||
|
canvasBorder->Color = menuAccent;
|
||||||
|
canvasBorder->t2d.position = { 0.5f, 0.5f, menuPanelWidth * 0.5f, 0.0f };
|
||||||
|
canvasBorder->t2d.center = { 0.5f, 0.5f };
|
||||||
|
canvasBorder->t2d.size = { 0.0f, 0.0f, canvasSize + 8.0f, canvasSize + 8.0f };
|
||||||
|
canvasBorder->BindToRenderSystem();
|
||||||
|
menuItems.push_back(canvasBorder);
|
||||||
|
|
||||||
|
Box* canvasBlank = new Box(); // shows through until the surface has a texture
|
||||||
|
canvasBlank->t2d.position = canvasBorder->t2d.position;
|
||||||
|
canvasBlank->t2d.center = { 0.5f, 0.5f };
|
||||||
|
canvasBlank->t2d.size = { 0.0f, 0.0f, canvasSize, canvasSize };
|
||||||
|
canvasBlank->BindToRenderSystem();
|
||||||
|
menuItems.push_back(canvasBlank);
|
||||||
|
|
||||||
|
ImageBox* canvasImage = new ImageBox();
|
||||||
|
canvasImage->t2d.position = canvasBorder->t2d.position;
|
||||||
|
canvasImage->t2d.center = { 0.5f, 0.5f };
|
||||||
|
canvasImage->t2d.size = { 0.0f, 0.0f, canvasSize, canvasSize };
|
||||||
|
canvasImage->BindToRenderSystem();
|
||||||
|
menuItems.push_back(canvasImage);
|
||||||
|
|
||||||
|
std::vector<MenuButton*> menuButtons = {
|
||||||
|
&playButton, &drawerButton, &settingsButton, &quitButton,
|
||||||
|
&sensitivityDown, &sensitivityUp, &fovDown, &fovUp,
|
||||||
|
&hitboxToggle, &graphicsButton, &resetButton, &backButton,
|
||||||
|
&bloomToggle, &shadowToggle, &lightShadowToggle, &vsyncToggle, &cullingToggle,
|
||||||
|
&surfacePrevious, &surfaceNext, &brushDown, &brushUp, &clearButton
|
||||||
|
};
|
||||||
|
for (MenuButton& swatch : swatches)
|
||||||
|
menuButtons.push_back(&swatch);
|
||||||
|
|
||||||
TextBox* menuHint = new TextBox();
|
TextBox* menuHint = new TextBox();
|
||||||
menuHint->font = arial;
|
menuHint->font = arial;
|
||||||
menuHint->text = "WASD TO MOVE | LEFT CLICK TO PUNCH | ESC FOR MENU";
|
menuHint->fontsize = 0.42f;
|
||||||
menuHint->fontsize = 0.5f;
|
menuHint->Color = menuMutedText;
|
||||||
menuHint->Color = { 0.55f, 0.61f, 0.70f };
|
menuHint->t2d.position = { 0.0f, 1.0f, menuMargin, -28.0f };
|
||||||
menuHint->t2d.position = { 0.5f, 0.92f, 0.0f, 0.0f };
|
menuHint->t2d.center = { 0.0f, 1.0f };
|
||||||
menuHint->t2d.center = { 0.5f, 0.5f };
|
menuHint->t2d.size = { 0.0f, 0.0f, menuContentWidth, 110.0f };
|
||||||
menuHint->t2d.size = { 0.9f, 0.0f, 0.0f, 40.0f };
|
menuHint->textCenter = { 0.0f, 1.0f };
|
||||||
menuHint->textCenter = { 0.5f, 0.5f };
|
|
||||||
menuHint->BindToRenderSystem();
|
menuHint->BindToRenderSystem();
|
||||||
menuItems.push_back(menuHint);
|
menuItems.push_back(menuHint);
|
||||||
|
|
||||||
auto SetButtonVisible = [](MenuButton& button, bool visible) {
|
auto SetButtonVisible = [](MenuButton& button, bool visible) {
|
||||||
button.box->Opacity = visible ? 0.92f : 0.0f;
|
button.box->Opacity = visible ? button.opacity : 0.0f;
|
||||||
|
button.accent->Opacity = visible ? 1.0f : 0.0f;
|
||||||
button.label->Opacity = visible ? 1.0f : 0.0f;
|
button.label->Opacity = visible ? 1.0f : 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -371,38 +471,79 @@ void Engine::Initiate()
|
|||||||
const bool main = menuScreen == MenuScreen::Main;
|
const bool main = menuScreen == MenuScreen::Main;
|
||||||
const bool paused = menuScreen == MenuScreen::Paused;
|
const bool paused = menuScreen == MenuScreen::Paused;
|
||||||
const bool settings = menuScreen == MenuScreen::Settings;
|
const bool settings = menuScreen == MenuScreen::Settings;
|
||||||
|
const bool graphics = menuScreen == MenuScreen::Graphics;
|
||||||
|
const bool drawer = menuScreen == MenuScreen::Drawer;
|
||||||
|
const bool mainButtons = main || paused;
|
||||||
|
|
||||||
menuVeil->Opacity = 0.55f;
|
menuVeil->Opacity = 0.22f;
|
||||||
|
menuPanel->Opacity = 0.82f;
|
||||||
|
menuPanelEdge->Opacity = 1.0f;
|
||||||
menuTitle->Opacity = 1.0f;
|
menuTitle->Opacity = 1.0f;
|
||||||
menuSubtitle->Opacity = 1.0f;
|
menuSubtitle->Opacity = 1.0f;
|
||||||
menuHint->Opacity = 1.0f;
|
menuHint->Opacity = 1.0f;
|
||||||
menuSubtitle->text = main ? "MAIN MENU" : (paused ? "PAUSED" : "SETTINGS");
|
menuSubtitle->text = main ? "MAIN MENU" : (paused ? "PAUSED"
|
||||||
|
: (drawer ? "TEXTURE DRAWER" : (graphics ? "GRAPHICS" : "SETTINGS")));
|
||||||
playButton.label->text = paused ? "RESUME" : "PLAY";
|
playButton.label->text = paused ? "RESUME" : "PLAY";
|
||||||
quitButton.label->text = paused ? "QUIT TO MENU" : "QUIT";
|
quitButton.label->text = paused ? "QUIT TO MENU" : "QUIT";
|
||||||
menuHint->text = settings
|
menuHint->text = drawer
|
||||||
? "ESC TO GO BACK | ADJUST MOUSE, FOV, AND HITBOXES"
|
? "DRAG ON THE CANVAS TO DRAW\nESC TO GO BACK"
|
||||||
: "WASD TO MOVE | LEFT CLICK TO PUNCH | ESC FOR MENU";
|
: (settings || graphics ? "ESC TO GO BACK" : "WASD TO MOVE\nESC FOR MENU");
|
||||||
|
|
||||||
|
SetButtonVisible(playButton, mainButtons);
|
||||||
|
SetButtonVisible(drawerButton, mainButtons);
|
||||||
|
SetButtonVisible(settingsButton, mainButtons);
|
||||||
|
SetButtonVisible(quitButton, mainButtons);
|
||||||
|
SetButtonVisible(surfacePrevious, drawer);
|
||||||
|
SetButtonVisible(surfaceNext, drawer);
|
||||||
|
SetButtonVisible(brushDown, drawer);
|
||||||
|
SetButtonVisible(brushUp, drawer);
|
||||||
|
SetButtonVisible(clearButton, drawer);
|
||||||
|
for (MenuButton& swatch : swatches)
|
||||||
|
SetButtonVisible(swatch, drawer);
|
||||||
|
|
||||||
|
Texture* canvasTexture = drawableSurfaces[selectedSurface].surface->texture;
|
||||||
|
canvasImage->surface = SurfaceTexture::Stretched(canvasTexture);
|
||||||
|
canvasImage->Opacity = drawer && canvasTexture ? 1.0f : 0.0f;
|
||||||
|
canvasBlank->Opacity = drawer ? 1.0f : 0.0f;
|
||||||
|
canvasBorder->Opacity = drawer ? 1.0f : 0.0f;
|
||||||
|
surfaceName->Opacity = drawer ? 1.0f : 0.0f;
|
||||||
|
surfaceName->text = drawableSurfaces[selectedSurface].name;
|
||||||
|
brushLabel->Opacity = drawer ? 1.0f : 0.0f;
|
||||||
|
brushValue->Opacity = drawer ? 1.0f : 0.0f;
|
||||||
|
brushValue->text = std::to_string(painter.brushSize);
|
||||||
|
swatchMarker->Opacity = drawer ? 1.0f : 0.0f;
|
||||||
|
swatchMarker->t2d.position = { 0.0f, 0.5f, menuMargin + swatchSpacing * selectedSwatch, swatchRowY + 28.0f };
|
||||||
|
|
||||||
SetButtonVisible(playButton, !settings);
|
|
||||||
SetButtonVisible(settingsButton, !settings);
|
|
||||||
SetButtonVisible(quitButton, !settings);
|
|
||||||
SetButtonVisible(sensitivityDown, settings);
|
SetButtonVisible(sensitivityDown, settings);
|
||||||
SetButtonVisible(sensitivityUp, settings);
|
SetButtonVisible(sensitivityUp, settings);
|
||||||
SetButtonVisible(fovDown, settings);
|
SetButtonVisible(fovDown, settings);
|
||||||
SetButtonVisible(fovUp, settings);
|
SetButtonVisible(fovUp, settings);
|
||||||
SetButtonVisible(hitboxToggle, settings);
|
SetButtonVisible(hitboxToggle, settings);
|
||||||
|
SetButtonVisible(graphicsButton, settings);
|
||||||
SetButtonVisible(resetButton, settings);
|
SetButtonVisible(resetButton, settings);
|
||||||
SetButtonVisible(backButton, settings);
|
SetButtonVisible(backButton, settings || drawer || graphics);
|
||||||
settingsInfo->Opacity = settings ? 1.0f : 0.0f;
|
|
||||||
|
auto ShowToggle = [&](MenuButton& button, const char* name, bool enabled) {
|
||||||
|
SetButtonVisible(button, graphics);
|
||||||
|
button.label->text = std::string(name) + (enabled ? ": ON" : ": OFF");
|
||||||
|
button.color = enabled ? glm::vec3(0.82f, 0.45f, 0.10f) : menuButtonColor;
|
||||||
|
};
|
||||||
|
ShowToggle(bloomToggle, "BLOOM", rendersystem->graphics.bloom);
|
||||||
|
ShowToggle(shadowToggle, "SUN SHADOWS", rendersystem->graphics.shadows);
|
||||||
|
ShowToggle(lightShadowToggle, "LIGHT SHADOWS", rendersystem->graphics.lightShadows);
|
||||||
|
ShowToggle(vsyncToggle, "VSYNC", rendersystem->graphics.vsync);
|
||||||
|
ShowToggle(cullingToggle, "CULLING", rendersystem->graphics.culling);
|
||||||
|
|
||||||
|
sensLabel->Opacity = settings ? 1.0f : 0.0f;
|
||||||
|
sensValue->Opacity = settings ? 1.0f : 0.0f;
|
||||||
|
fovLabel->Opacity = settings ? 1.0f : 0.0f;
|
||||||
|
fovValue->Opacity = settings ? 1.0f : 0.0f;
|
||||||
|
|
||||||
hitboxToggle.label->text = showHitboxes ? "HITBOXES: ON" : "HITBOXES: OFF";
|
hitboxToggle.label->text = showHitboxes ? "HITBOXES: ON" : "HITBOXES: OFF";
|
||||||
hitboxToggle.color = showHitboxes
|
hitboxToggle.color = showHitboxes ? glm::vec3(0.82f, 0.45f, 0.10f) : menuButtonColor;
|
||||||
? glm::vec3(0.12f, 0.30f, 0.22f)
|
|
||||||
: glm::vec3(0.20f, 0.22f, 0.28f);
|
|
||||||
|
|
||||||
settingsInfo->text = "MOUSE SENSITIVITY: " +
|
sensValue->text = std::to_string((int)std::lround(StartupCmPer360)) + " CM";
|
||||||
std::to_string((int)std::lround(StartupCmPer360)) +
|
fovValue->text = std::to_string((int)std::lround(camera->FOV));
|
||||||
" CM / 360\nFIELD OF VIEW: " + std::to_string((int)std::lround(camera->FOV));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
auto OpenMenu = [&](MenuScreen screen) {
|
auto OpenMenu = [&](MenuScreen screen) {
|
||||||
@@ -412,9 +553,6 @@ void Engine::Initiate()
|
|||||||
testcharacter.SetWalking(false);
|
testcharacter.SetWalking(false);
|
||||||
npc.SetWalking(false);
|
npc.SetWalking(false);
|
||||||
fpsCounter->Opacity = 0.0f;
|
fpsCounter->Opacity = 0.0f;
|
||||||
healthText->Opacity = 0.0f;
|
|
||||||
healthBarBack->Opacity = 0.0f;
|
|
||||||
healthBarFill->Opacity = 0.0f;
|
|
||||||
camera->lockedcursor = false;
|
camera->lockedcursor = false;
|
||||||
glfwSetInputMode(window->handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
glfwSetInputMode(window->handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
||||||
prevMouseDown = mouse->IsLeftDown();
|
prevMouseDown = mouse->IsLeftDown();
|
||||||
@@ -425,9 +563,6 @@ void Engine::Initiate()
|
|||||||
charactermanager->ClearInput();
|
charactermanager->ClearInput();
|
||||||
for (Box* item : menuItems) item->Opacity = 0.0f;
|
for (Box* item : menuItems) item->Opacity = 0.0f;
|
||||||
fpsCounter->Opacity = 1.0f;
|
fpsCounter->Opacity = 1.0f;
|
||||||
healthText->Opacity = 1.0f;
|
|
||||||
healthBarBack->Opacity = 0.85f;
|
|
||||||
healthBarFill->Opacity = 1.0f;
|
|
||||||
camera->lockedcursor = true;
|
camera->lockedcursor = true;
|
||||||
glfwSetInputMode(window->handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
glfwSetInputMode(window->handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
||||||
int ww, wh;
|
int ww, wh;
|
||||||
@@ -436,9 +571,6 @@ void Engine::Initiate()
|
|||||||
};
|
};
|
||||||
|
|
||||||
fpsCounter->Opacity = 0.0f;
|
fpsCounter->Opacity = 0.0f;
|
||||||
healthText->Opacity = 0.0f;
|
|
||||||
healthBarBack->Opacity = 0.0f;
|
|
||||||
healthBarFill->Opacity = 0.0f;
|
|
||||||
camera->lockedcursor = false;
|
camera->lockedcursor = false;
|
||||||
glfwSetInputMode(window->handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
glfwSetInputMode(window->handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
||||||
|
|
||||||
@@ -447,9 +579,9 @@ void Engine::Initiate()
|
|||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
Mesh* floor = CreateCubeMesh();
|
Mesh* floor = CreateCubeMesh();
|
||||||
floor->name = "baseplate";
|
floor->name = "baseplate";
|
||||||
floor->meshTexture = blanktex;
|
floor->surface = SurfaceTexture::Tiled(blanktex, 8.0f);
|
||||||
floor->roughness = 0.9f;
|
floor->roughness = 0.9f;
|
||||||
floor->t.ScaleTo({ 32.0f, 2.0f, 32.0f });
|
floor->t.ScaleTo({ 200.0f, 2.0f, 200.0f });
|
||||||
floor->t.TranslateTo({ 0.0f, -1.0f, 0.0f });
|
floor->t.TranslateTo({ 0.0f, -1.0f, 0.0f });
|
||||||
floor->BindToRenderSystem();
|
floor->BindToRenderSystem();
|
||||||
mainf->AddChild(floor);
|
mainf->AddChild(floor);
|
||||||
@@ -459,30 +591,36 @@ void Engine::Initiate()
|
|||||||
physicsengine->AddObject(floorBody);
|
physicsengine->AddObject(floorBody);
|
||||||
const BoundingBox floorBounds = floor->t.GetRotationlessAABB();
|
const BoundingBox floorBounds = floor->t.GetRotationlessAABB();
|
||||||
|
|
||||||
const float wallHeight = 4.0f;
|
// ------------------------------------------------------------------------
|
||||||
const float wallThickness = 1.0f;
|
// Light cubes
|
||||||
const float wallOffset = 16.0f + wallThickness * 0.5f;
|
// ------------------------------------------------------------------------
|
||||||
const float wallSpan = 32.0f + wallThickness;
|
auto AddLightCube = [&](const char* name, const glm::vec3& position, float size, const glm::vec3& color) -> Mesh* {
|
||||||
|
Mesh* cube = CreateCubeMesh();
|
||||||
auto AddWall = [&](const char* name, const glm::vec3& position, const glm::vec3& scale) {
|
cube->name = name;
|
||||||
Mesh* wall = CreateCubeMesh();
|
cube->colorOverride = glm::vec4(color, 1.0f);
|
||||||
wall->name = name;
|
cube->emissive = 1.0f;
|
||||||
wall->meshTexture = blanktex;
|
cube->t.ScaleTo(size);
|
||||||
wall->roughness = 0.9f;
|
cube->t.TranslateTo(position);
|
||||||
wall->t.ScaleTo(scale);
|
cube->BindToRenderSystem();
|
||||||
wall->t.TranslateTo(position);
|
mainf->AddChild(cube);
|
||||||
wall->BindToRenderSystem();
|
return cube;
|
||||||
mainf->AddChild(wall);
|
|
||||||
|
|
||||||
p* wallBody = new p(wall);
|
|
||||||
wallBody->velocity = false;
|
|
||||||
physicsengine->AddObject(wallBody);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
AddWall("wall_north", { 0.0f, wallHeight * 0.5f, -wallOffset }, { wallSpan, wallHeight, wallThickness });
|
const glm::vec3 skyLightColor(0.45f, 0.65f, 1.0f);
|
||||||
AddWall("wall_south", { 0.0f, wallHeight * 0.5f, wallOffset }, { wallSpan, wallHeight, wallThickness });
|
const glm::vec3 skyLightPosition(-6.0f, 7.0f, -6.0f);
|
||||||
AddWall("wall_west", { -wallOffset, wallHeight * 0.5f, 0.0f }, { wallThickness, wallHeight, 32.0f });
|
Mesh* skyLightCube = AddLightCube("spot_light_cube", skyLightPosition, 1.5f, skyLightColor);
|
||||||
AddWall("wall_east", { wallOffset, wallHeight * 0.5f, 0.0f }, { wallThickness, wallHeight, 32.0f });
|
rendersystem->lighting->AddSpotLight(skyLightPosition, { 0.0f, -1.0f, 0.0f }, skyLightColor, 1200.0f, 25.0f, 18.0f, 26.0f);
|
||||||
|
|
||||||
|
const glm::vec3 pointLightPosition(6.0f, 3.0f, -6.0f);
|
||||||
|
const glm::vec3 pointLightColor(1.0f, 0.35f, 0.05f);
|
||||||
|
Mesh* pointLightCube = AddLightCube("point_light_cube", pointLightPosition, 0.8f, pointLightColor);
|
||||||
|
rendersystem->lighting->AddPointLight(pointLightPosition, pointLightColor, 300.0f, 30.0f);
|
||||||
|
|
||||||
|
drawableSurfaces.push_back({ "BASEPLATE", &floor->surface, &floor->colorOverride });
|
||||||
|
drawableSurfaces.push_back({ "PLAYER", &testcharacter.surface, &testcharacter.colorOverride });
|
||||||
|
drawableSurfaces.push_back({ "DUMMY", &npc.surface, &npc.colorOverride });
|
||||||
|
drawableSurfaces.push_back({ "SKY LIGHT CUBE", &skyLightCube->surface, &skyLightCube->colorOverride });
|
||||||
|
drawableSurfaces.push_back({ "POINT LIGHT CUBE", &pointLightCube->surface, &pointLightCube->colorOverride });
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// Ground check + respawn
|
// Ground check + respawn
|
||||||
@@ -515,10 +653,12 @@ void Engine::Initiate()
|
|||||||
if (showHitboxes) {
|
if (showHitboxes) {
|
||||||
for (Character* character : charactermanager->GetCharacters()) {
|
for (Character* character : charactermanager->GetCharacters()) {
|
||||||
if (!character->body) continue;
|
if (!character->body) continue;
|
||||||
|
if (character->ragdoll) {
|
||||||
|
for (Ragdoll::Part* part : character->ragdoll->GetParts())
|
||||||
|
lines.push_back({ part->transform.t.GetMatrix(), glm::vec3(0.2f, 0.9f, 0.35f) });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
lines.push_back({ character->body->t.GetMatrix(), glm::vec3(0.2f, 0.9f, 0.35f) });
|
lines.push_back({ character->body->t.GetMatrix(), glm::vec3(0.2f, 0.9f, 0.35f) });
|
||||||
const Hitbox& punch = character->GetPunchHitbox();
|
|
||||||
if (punch.active)
|
|
||||||
lines.push_back({ punch.transform.GetMatrix(), glm::vec3(1.0f, 0.25f, 0.1f) });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rendersystem->SetHitboxDebugLines(lines);
|
rendersystem->SetHitboxDebugLines(lines);
|
||||||
@@ -545,8 +685,10 @@ void Engine::Initiate()
|
|||||||
if (escapePressed) {
|
if (escapePressed) {
|
||||||
if (!menuActive)
|
if (!menuActive)
|
||||||
OpenMenu(MenuScreen::Paused);
|
OpenMenu(MenuScreen::Paused);
|
||||||
else if (menuScreen == MenuScreen::Settings)
|
else if (menuScreen == MenuScreen::Settings || menuScreen == MenuScreen::Drawer)
|
||||||
menuScreen = settingsReturn;
|
menuScreen = settingsReturn;
|
||||||
|
else if (menuScreen == MenuScreen::Graphics)
|
||||||
|
menuScreen = MenuScreen::Settings;
|
||||||
else if (menuScreen == MenuScreen::Paused)
|
else if (menuScreen == MenuScreen::Paused)
|
||||||
ResumeGame();
|
ResumeGame();
|
||||||
}
|
}
|
||||||
@@ -561,9 +703,6 @@ void Engine::Initiate()
|
|||||||
float yoffset = mouse->GetLastY() - mouse->GetY();
|
float yoffset = mouse->GetLastY() - mouse->GetY();
|
||||||
camera->ProcessMouseMovement(xoffset, yoffset);
|
camera->ProcessMouseMovement(xoffset, yoffset);
|
||||||
}
|
}
|
||||||
else if (buffer.GetType() == Mouse::Event::Type::LPress) {
|
|
||||||
testcharacter.Punch(camera->t.GetFrontVector());
|
|
||||||
}
|
|
||||||
else if (buffer.GetType() == Mouse::Event::Type::RPress) {
|
else if (buffer.GetType() == Mouse::Event::Type::RPress) {
|
||||||
camera->lockedcursor = !camera->lockedcursor;
|
camera->lockedcursor = !camera->lockedcursor;
|
||||||
if (camera->lockedcursor) {
|
if (camera->lockedcursor) {
|
||||||
@@ -582,7 +721,8 @@ void Engine::Initiate()
|
|||||||
ApplyMenuLayout();
|
ApplyMenuLayout();
|
||||||
|
|
||||||
const bool liveBackdrop = menuScreen == MenuScreen::Main ||
|
const bool liveBackdrop = menuScreen == MenuScreen::Main ||
|
||||||
(menuScreen == MenuScreen::Settings && settingsReturn == MenuScreen::Main);
|
((menuScreen == MenuScreen::Settings || menuScreen == MenuScreen::Graphics || menuScreen == MenuScreen::Drawer)
|
||||||
|
&& settingsReturn == MenuScreen::Main);
|
||||||
if (liveBackdrop) {
|
if (liveBackdrop) {
|
||||||
static float menuAngle = 0.6f;
|
static float menuAngle = 0.6f;
|
||||||
menuAngle += deltatime * 0.12f;
|
menuAngle += deltatime * 0.12f;
|
||||||
@@ -593,11 +733,7 @@ void Engine::Initiate()
|
|||||||
camera->t.RotateToQuaternion(LookAt(menuTarget - menuCamera));
|
camera->t.RotateToQuaternion(LookAt(menuTarget - menuCamera));
|
||||||
camera->Step(deltatime);
|
camera->Step(deltatime);
|
||||||
|
|
||||||
npc.SetTurnDirection(testcharacter.body->t.GetTranslation() -
|
|
||||||
npc.body->t.GetTranslation());
|
|
||||||
charactermanager->Step(deltatime);
|
charactermanager->Step(deltatime);
|
||||||
rain->t.TranslateTo(camera->t.GetTranslation() + glm::vec3(0.0f, 20.0f, 0.0f));
|
|
||||||
rain->Step(deltatime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
soundsystem->SetListenerPosition(
|
soundsystem->SetListenerPosition(
|
||||||
@@ -607,89 +743,146 @@ void Engine::Initiate()
|
|||||||
);
|
);
|
||||||
soundsystem->Update();
|
soundsystem->Update();
|
||||||
|
|
||||||
MenuButton* buttons[] = {
|
|
||||||
&playButton, &settingsButton, &quitButton,
|
|
||||||
&sensitivityDown, &sensitivityUp, &fovDown, &fovUp,
|
|
||||||
&hitboxToggle, &resetButton, &backButton
|
|
||||||
};
|
|
||||||
for (MenuButton* button : buttons)
|
|
||||||
button->box->t2d.Recalculate();
|
|
||||||
|
|
||||||
const glm::vec2 mousePos((float)mouse->GetX(), (float)mouse->GetY());
|
const glm::vec2 mousePos((float)mouse->GetX(), (float)mouse->GetY());
|
||||||
auto Hovered = [&](const MenuButton& button) {
|
const float hoverEase = 1.0f - std::exp(-12.0f * deltatime);
|
||||||
return mousePos.x > button.box->t2d.pixelposition.x &&
|
|
||||||
mousePos.y > button.box->t2d.pixelposition.y &&
|
|
||||||
mousePos.x < button.box->t2d.pixelposition.x + button.box->t2d.pixelsize.x &&
|
|
||||||
mousePos.y < button.box->t2d.pixelposition.y + button.box->t2d.pixelsize.y;
|
|
||||||
};
|
|
||||||
|
|
||||||
const bool playHovered = Hovered(playButton);
|
for (MenuButton* button : menuButtons) {
|
||||||
const bool settingsHovered = Hovered(settingsButton);
|
t2d_package& t2d = button->box->t2d;
|
||||||
const bool quitHovered = Hovered(quitButton);
|
t2d.Recalculate();
|
||||||
const bool sensDownHovered = Hovered(sensitivityDown);
|
button->hovered = button->box->Opacity > 0.0f &&
|
||||||
const bool sensUpHovered = Hovered(sensitivityUp);
|
mousePos.x > t2d.pixelposition.x &&
|
||||||
const bool fovDownHovered = Hovered(fovDown);
|
mousePos.y > t2d.pixelposition.y &&
|
||||||
const bool fovUpHovered = Hovered(fovUp);
|
mousePos.x < t2d.pixelposition.x + t2d.pixelsize.x &&
|
||||||
const bool hitboxHovered = Hovered(hitboxToggle);
|
mousePos.y < t2d.pixelposition.y + t2d.pixelsize.y;
|
||||||
const bool resetHovered = Hovered(resetButton);
|
|
||||||
const bool backHovered = Hovered(backButton);
|
|
||||||
|
|
||||||
playButton.box->Color = playHovered ? glm::vec3(0.18f, 0.44f, 0.32f) : playButton.color;
|
button->hover += ((button->hovered ? 1.0f : 0.0f) - button->hover) * hoverEase;
|
||||||
settingsButton.box->Color = settingsHovered ? glm::vec3(0.22f, 0.34f, 0.50f) : settingsButton.color;
|
button->box->Color = glm::mix(button->color, button->hoverColor, button->hover);
|
||||||
quitButton.box->Color = quitHovered ? glm::vec3(0.30f, 0.20f, 0.22f) : quitButton.color;
|
button->accent->t2d.size.z = 5.0f * button->hover;
|
||||||
sensitivityDown.box->Color = sensDownHovered ? glm::vec3(0.22f, 0.34f, 0.50f) : sensitivityDown.color;
|
button->label->t2d.position.z = button->labelX + button->labelSlide * button->hover;
|
||||||
sensitivityUp.box->Color = sensUpHovered ? glm::vec3(0.22f, 0.34f, 0.50f) : sensitivityUp.color;
|
}
|
||||||
fovDown.box->Color = fovDownHovered ? glm::vec3(0.22f, 0.34f, 0.50f) : fovDown.color;
|
|
||||||
fovUp.box->Color = fovUpHovered ? glm::vec3(0.22f, 0.34f, 0.50f) : fovUp.color;
|
|
||||||
hitboxToggle.box->Color = hitboxHovered ? glm::vec3(0.22f, 0.34f, 0.50f) : hitboxToggle.color;
|
|
||||||
resetButton.box->Color = resetHovered ? glm::vec3(0.30f, 0.32f, 0.40f) : resetButton.color;
|
|
||||||
backButton.box->Color = backHovered ? glm::vec3(0.18f, 0.44f, 0.32f) : backButton.color;
|
|
||||||
|
|
||||||
const bool mouseDown = mouse->IsLeftDown();
|
const bool mouseDown = mouse->IsLeftDown();
|
||||||
if (mouseDown && !prevMouseDown) {
|
if (mouseDown && !prevMouseDown) {
|
||||||
if (menuScreen == MenuScreen::Settings) {
|
if (menuScreen == MenuScreen::Settings) {
|
||||||
if (sensDownHovered) {
|
if (sensitivityDown.hovered) {
|
||||||
StartupCmPer360 = glm::clamp(StartupCmPer360 + 2.0f, 4.0f, 80.0f);
|
StartupCmPer360 = glm::clamp(StartupCmPer360 + 2.0f, 4.0f, 80.0f);
|
||||||
camera->SetSensitivityCm(StartupCmPer360, StartupMouseDpi);
|
camera->SetSensitivityCm(StartupCmPer360, StartupMouseDpi);
|
||||||
}
|
}
|
||||||
else if (sensUpHovered) {
|
else if (sensitivityUp.hovered) {
|
||||||
StartupCmPer360 = glm::clamp(StartupCmPer360 - 2.0f, 4.0f, 80.0f);
|
StartupCmPer360 = glm::clamp(StartupCmPer360 - 2.0f, 4.0f, 80.0f);
|
||||||
camera->SetSensitivityCm(StartupCmPer360, StartupMouseDpi);
|
camera->SetSensitivityCm(StartupCmPer360, StartupMouseDpi);
|
||||||
}
|
}
|
||||||
else if (fovDownHovered) {
|
else if (fovDown.hovered) {
|
||||||
camera->FOV = glm::clamp(camera->FOV - 5.0f, 60.0f, 130.0f);
|
camera->FOV = glm::clamp(camera->FOV - 5.0f, 60.0f, 130.0f);
|
||||||
}
|
}
|
||||||
else if (fovUpHovered) {
|
else if (fovUp.hovered) {
|
||||||
camera->FOV = glm::clamp(camera->FOV + 5.0f, 60.0f, 130.0f);
|
camera->FOV = glm::clamp(camera->FOV + 5.0f, 60.0f, 130.0f);
|
||||||
}
|
}
|
||||||
else if (hitboxHovered) {
|
else if (hitboxToggle.hovered) {
|
||||||
showHitboxes = !showHitboxes;
|
showHitboxes = !showHitboxes;
|
||||||
}
|
}
|
||||||
else if (resetHovered) {
|
else if (graphicsButton.hovered) {
|
||||||
|
menuScreen = MenuScreen::Graphics;
|
||||||
|
}
|
||||||
|
else if (resetButton.hovered) {
|
||||||
StartupCmPer360 = 16.0f;
|
StartupCmPer360 = 16.0f;
|
||||||
StartupMouseDpi = 800.0f;
|
StartupMouseDpi = 800.0f;
|
||||||
camera->SetSensitivityCm(StartupCmPer360, StartupMouseDpi);
|
camera->SetSensitivityCm(StartupCmPer360, StartupMouseDpi);
|
||||||
camera->FOV = 110.0f;
|
camera->FOV = 110.0f;
|
||||||
showHitboxes = false;
|
showHitboxes = false;
|
||||||
}
|
}
|
||||||
else if (backHovered) {
|
else if (backButton.hovered) {
|
||||||
menuScreen = settingsReturn;
|
menuScreen = settingsReturn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (playHovered) {
|
else if (menuScreen == MenuScreen::Graphics) {
|
||||||
|
GraphicsSettings& graphics = rendersystem->graphics;
|
||||||
|
if (bloomToggle.hovered) {
|
||||||
|
graphics.bloom = !graphics.bloom;
|
||||||
|
}
|
||||||
|
else if (shadowToggle.hovered) {
|
||||||
|
graphics.shadows = !graphics.shadows;
|
||||||
|
}
|
||||||
|
else if (lightShadowToggle.hovered) {
|
||||||
|
graphics.lightShadows = !graphics.lightShadows;
|
||||||
|
}
|
||||||
|
else if (vsyncToggle.hovered) {
|
||||||
|
graphics.vsync = !graphics.vsync;
|
||||||
|
glfwSwapInterval(graphics.vsync ? 1 : 0);
|
||||||
|
}
|
||||||
|
else if (cullingToggle.hovered) {
|
||||||
|
graphics.culling = !graphics.culling;
|
||||||
|
}
|
||||||
|
else if (backButton.hovered) {
|
||||||
|
menuScreen = MenuScreen::Settings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (menuScreen == MenuScreen::Drawer) {
|
||||||
|
const int surfaceCount = (int)drawableSurfaces.size();
|
||||||
|
if (surfacePrevious.hovered) {
|
||||||
|
selectedSurface = (selectedSurface + surfaceCount - 1) % surfaceCount;
|
||||||
|
}
|
||||||
|
else if (surfaceNext.hovered) {
|
||||||
|
selectedSurface = (selectedSurface + 1) % surfaceCount;
|
||||||
|
}
|
||||||
|
else if (brushDown.hovered) {
|
||||||
|
painter.brushSize = glm::max(painter.brushSize - 2, 2);
|
||||||
|
}
|
||||||
|
else if (brushUp.hovered) {
|
||||||
|
painter.brushSize = glm::min(painter.brushSize + 2, 64);
|
||||||
|
}
|
||||||
|
else if (clearButton.hovered) {
|
||||||
|
DrawableSurface& drawable = drawableSurfaces[selectedSurface];
|
||||||
|
EnsureCanvas(drawable);
|
||||||
|
painter.Clear(*drawable.surface->texture);
|
||||||
|
}
|
||||||
|
else if (backButton.hovered) {
|
||||||
|
menuScreen = settingsReturn;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < (int)swatches.size(); i++) {
|
||||||
|
if (swatches[i].hovered) {
|
||||||
|
selectedSwatch = i;
|
||||||
|
painter.color = swatchColors[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (playButton.hovered) {
|
||||||
ResumeGame();
|
ResumeGame();
|
||||||
}
|
}
|
||||||
else if (settingsHovered) {
|
else if (drawerButton.hovered) {
|
||||||
|
settingsReturn = menuScreen;
|
||||||
|
menuScreen = MenuScreen::Drawer;
|
||||||
|
}
|
||||||
|
else if (settingsButton.hovered) {
|
||||||
settingsReturn = menuScreen;
|
settingsReturn = menuScreen;
|
||||||
menuScreen = MenuScreen::Settings;
|
menuScreen = MenuScreen::Settings;
|
||||||
}
|
}
|
||||||
else if (quitHovered) {
|
else if (quitButton.hovered) {
|
||||||
if (menuScreen == MenuScreen::Main)
|
if (menuScreen == MenuScreen::Main)
|
||||||
glfwSetWindowShouldClose(window->handle, 1);
|
glfwSetWindowShouldClose(window->handle, 1);
|
||||||
else
|
else
|
||||||
menuScreen = MenuScreen::Main;
|
menuScreen = MenuScreen::Main;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// A stroke has to start on the canvas, but carries on if the mouse wanders off it
|
||||||
|
canvasImage->t2d.Recalculate();
|
||||||
|
const glm::vec2 canvasUV = (mousePos - canvasImage->t2d.pixelposition) / canvasImage->t2d.pixelsize;
|
||||||
|
if (!mouseDown || menuScreen != MenuScreen::Drawer) {
|
||||||
|
painting = false;
|
||||||
|
}
|
||||||
|
else if (!prevMouseDown) {
|
||||||
|
painting = canvasUV.x >= 0.0f && canvasUV.x <= 1.0f && canvasUV.y >= 0.0f && canvasUV.y <= 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (painting) {
|
||||||
|
DrawableSurface& drawable = drawableSurfaces[selectedSurface];
|
||||||
|
EnsureCanvas(drawable);
|
||||||
|
painter.Stroke(*drawable.surface->texture, glm::clamp(canvasUV, glm::vec2(0.0f), glm::vec2(1.0f)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
painter.EndStroke();
|
||||||
|
}
|
||||||
|
|
||||||
prevMouseDown = mouseDown;
|
prevMouseDown = mouseDown;
|
||||||
|
|
||||||
UpdateHitboxDebug();
|
UpdateHitboxDebug();
|
||||||
@@ -712,11 +905,6 @@ void Engine::Initiate()
|
|||||||
testcharacter.SetTurnDirection(inputDirection);
|
testcharacter.SetTurnDirection(inputDirection);
|
||||||
testcharacter.SetWalking(hasMoveInput);
|
testcharacter.SetWalking(hasMoveInput);
|
||||||
|
|
||||||
const glm::vec3 npcVelocity(npcBody->linearvelocity.x, 0.0f, npcBody->linearvelocity.z);
|
|
||||||
npc.SetWalking(glm::length(npcVelocity) > 0.1f);
|
|
||||||
npc.SetTurnDirection(testcharacter.body->t.GetTranslation() -
|
|
||||||
npc.body->t.GetTranslation());
|
|
||||||
|
|
||||||
charactermanager->Step(deltatime);
|
charactermanager->Step(deltatime);
|
||||||
|
|
||||||
const glm::vec3 inputVel = hasMoveInput
|
const glm::vec3 inputVel = hasMoveInput
|
||||||
@@ -746,13 +934,12 @@ void Engine::Initiate()
|
|||||||
characterBody->linearvelocity.z += wishDir.z * add;
|
characterBody->linearvelocity.z += wishDir.z * add;
|
||||||
}
|
}
|
||||||
|
|
||||||
charactermanager->ResolveCombat(*physicsengine);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const int substeps = std::max(1, (int)std::ceil(deltatime * 120.0f));
|
const int substeps = std::max(1, (int)std::ceil(deltatime * 120.0f));
|
||||||
const float subDt = deltatime / float(substeps);
|
const float subDt = deltatime / float(substeps);
|
||||||
for (int i = 0; i < substeps; i++) physicsengine->Step(subDt);
|
for (int i = 0; i < substeps; i++) physicsengine->Step(subDt);
|
||||||
}
|
}
|
||||||
|
npc.ragdoll->Update(deltatime);
|
||||||
|
|
||||||
if (testcharacter.body->t.GetTranslation().y < -20.0f) {
|
if (testcharacter.body->t.GetTranslation().y < -20.0f) {
|
||||||
testcharacter.body->t.TranslateTo(RandomSpawn());
|
testcharacter.body->t.TranslateTo(RandomSpawn());
|
||||||
@@ -784,6 +971,8 @@ void Engine::Initiate()
|
|||||||
for (p* obj : physicsengine->GetObjects()) {
|
for (p* obj : physicsengine->GetObjects()) {
|
||||||
if (obj->pointer == testcharacter.body || !obj->collision)
|
if (obj->pointer == testcharacter.body || !obj->collision)
|
||||||
continue;
|
continue;
|
||||||
|
if (obj->collisionGroup != 0) // flailing ragdoll limbs would make the camera jump
|
||||||
|
continue;
|
||||||
if (obj == floorBody && ignoreFloorCollision)
|
if (obj == floorBody && ignoreFloorCollision)
|
||||||
continue;
|
continue;
|
||||||
auto hit = IsRayInT(cameraSweep, obj->pointer->t);
|
auto hit = IsRayInT(cameraSweep, obj->pointer->t);
|
||||||
@@ -800,9 +989,6 @@ void Engine::Initiate()
|
|||||||
}
|
}
|
||||||
camera->Step(deltatime);
|
camera->Step(deltatime);
|
||||||
|
|
||||||
rain->t.TranslateTo(camera->t.GetTranslation() + glm::vec3(0.0f, 20.0f, 0.0f));
|
|
||||||
rain->Step(deltatime);
|
|
||||||
|
|
||||||
Sound* music = static_cast<Sound*>(mainf->GetFirstChildOfName("music"));
|
Sound* music = static_cast<Sound*>(mainf->GetFirstChildOfName("music"));
|
||||||
if (music) music->Update3DPosition(0.0f, 0.0f, 0.0f);
|
if (music) music->Update3DPosition(0.0f, 0.0f, 0.0f);
|
||||||
|
|
||||||
@@ -833,14 +1019,6 @@ void Engine::Initiate()
|
|||||||
fpsCounter->text = std::to_string(measuredFps) + " FPS";
|
fpsCounter->text = std::to_string(measuredFps) + " FPS";
|
||||||
}
|
}
|
||||||
|
|
||||||
healthText->text = "PLAYER " + std::to_string((int)std::lround(testcharacter.body->Health)) +
|
|
||||||
" HP DUMMY " + std::to_string((int)std::lround(npc.body->Health)) + " HP";
|
|
||||||
|
|
||||||
const float healthRatio = glm::clamp((float)testcharacter.body->Health / 100.0f, 0.0f, 1.0f);
|
|
||||||
healthBarFill->t2d.size.z = healthBarFillWidth * healthRatio;
|
|
||||||
healthBarFill->Color = glm::mix(glm::vec3(0.85f, 0.20f, 0.12f),
|
|
||||||
glm::vec3(0.20f, 0.85f, 0.35f), healthRatio);
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
// Render
|
// Render
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
@@ -878,9 +1056,12 @@ void key_callback(GLFWwindow* window, int key, int scancode, int action, int mod
|
|||||||
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
|
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
|
||||||
glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
|
glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
|
||||||
}
|
}
|
||||||
glfwSwapInterval(1);
|
glfwSwapInterval(engine && engine->rendersystem && !engine->rendersystem->graphics.vsync ? 0 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (key == GLFW_KEY_H && action == GLFW_PRESS && engine && engine->rendersystem)
|
||||||
|
engine->rendersystem->CycleDebugView();
|
||||||
|
|
||||||
if (action == GLFW_PRESS) keyboard->KeyDown(key);
|
if (action == GLFW_PRESS) keyboard->KeyDown(key);
|
||||||
else if (action == GLFW_RELEASE) keyboard->KeyUp(key);
|
else if (action == GLFW_RELEASE) keyboard->KeyUp(key);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "QuadVertices.h"
|
#include "QuadVertices.h"
|
||||||
#include "shaderClass.h"
|
#include "shaderClass.h"
|
||||||
#include "texture.h"
|
#include "texture.h"
|
||||||
|
#include "SurfaceTexture.h"
|
||||||
#include "t2d.h"
|
#include "t2d.h"
|
||||||
#include "font.h"
|
#include "font.h"
|
||||||
#include "RenderSystem.h"
|
#include "RenderSystem.h"
|
||||||
@@ -190,7 +191,7 @@ public:
|
|||||||
|
|
||||||
class ImageBox : public Box {
|
class ImageBox : public Box {
|
||||||
public:
|
public:
|
||||||
Texture* tex = nullptr;
|
SurfaceTexture surface;
|
||||||
|
|
||||||
ImageBox() {
|
ImageBox() {
|
||||||
shadertype = ShaderType::ImageBoxShader;
|
shadertype = ShaderType::ImageBoxShader;
|
||||||
@@ -208,14 +209,7 @@ public:
|
|||||||
ShaderProgram.Set2F("pixelScale", t2d.pixelsize);
|
ShaderProgram.Set2F("pixelScale", t2d.pixelsize);
|
||||||
ShaderProgram.Set2F("pixelPos", t2d.pixelposition);
|
ShaderProgram.Set2F("pixelPos", t2d.pixelposition);
|
||||||
ShaderProgram.Set1F("rounding", rounding);
|
ShaderProgram.Set1F("rounding", rounding);
|
||||||
glActiveTexture(GL_TEXTURE0);
|
surface.Apply(ShaderProgram, 0, glm::vec3(t2d.pixelsize, 0.0f));
|
||||||
if (tex) {
|
|
||||||
glBindTexture(GL_TEXTURE_2D, tex->ID);
|
|
||||||
ShaderProgram.SetInt("tex", 0);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
}
|
|
||||||
boxVAO.Bind();
|
boxVAO.Bind();
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||||
boxVAO.Unbind();
|
boxVAO.Unbind();
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#ifndef LIGHT_CLASS
|
||||||
|
#define LIGHT_CLASS
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
// These have to match the sizes in deferred_lighting.frag and default.frag
|
||||||
|
static constexpr int MAX_DIRECTIONAL_LIGHTS = 4;
|
||||||
|
static constexpr int MAX_POINT_LIGHTS = 16;
|
||||||
|
static constexpr int MAX_SPOT_LIGHTS = 8;
|
||||||
|
|
||||||
|
// Spot and point shadows share one depth texture cut into tiles.
|
||||||
|
// A spot light takes 1 tile, a point light takes 6. Lights past the limit just don't cast shadows.
|
||||||
|
static constexpr int SHADOW_ATLAS_COLUMNS = 6; // has to match the shaders too
|
||||||
|
static constexpr int SHADOW_ATLAS_TILES = SHADOW_ATLAS_COLUMNS * SHADOW_ATLAS_COLUMNS;
|
||||||
|
static constexpr int SHADOW_ATLAS_TILE_SIZE = 640;
|
||||||
|
|
||||||
|
class Light {
|
||||||
|
public:
|
||||||
|
glm::vec3 color = glm::vec3(1.0f); // linear rgb
|
||||||
|
float intensity = 1.0f;
|
||||||
|
bool castShadow = true; // spot and point lights, the sun always has its own shadow map
|
||||||
|
int shadowTile = -1; // first atlas tile, set each frame by the render system
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parallel rays with no position, so it lights the whole scene from one direction
|
||||||
|
class DirectionalLight : public Light {
|
||||||
|
public:
|
||||||
|
glm::vec3 direction = glm::vec3(0.0f, -1.0f, 0.0f); // the way the light travels
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fades with distance and reaches nothing past radius
|
||||||
|
class PointLight : public Light {
|
||||||
|
public:
|
||||||
|
glm::vec3 position = glm::vec3(0.0f);
|
||||||
|
float radius = 10.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A cone from a point. Full brightness inside innerAngle, fading to nothing at outerAngle.
|
||||||
|
class SpotLight : public Light {
|
||||||
|
public:
|
||||||
|
glm::vec3 position = glm::vec3(0.0f);
|
||||||
|
glm::vec3 direction = glm::vec3(0.0f, -1.0f, 0.0f); // the way the cone points
|
||||||
|
float radius = 20.0f;
|
||||||
|
float innerAngle = 20.0f; // degrees from the center of the cone
|
||||||
|
float outerAngle = 30.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
+43
-5
@@ -1,18 +1,56 @@
|
|||||||
#ifndef LIGHTING_CLASS
|
#ifndef LIGHTING_CLASS
|
||||||
#define LIGHTING_CLASS
|
#define LIGHTING_CLASS
|
||||||
|
|
||||||
#include "t.h"
|
#include <vector>
|
||||||
#include "tFunctions.h"
|
|
||||||
|
|
||||||
|
#include "Light.h"
|
||||||
|
|
||||||
|
// Every light in the scene. Directional lights are the only ones without shadows, apart from the sun.
|
||||||
class Lighting {
|
class Lighting {
|
||||||
public:
|
public:
|
||||||
t dirlighting;
|
DirectionalLight sun;
|
||||||
|
std::vector<DirectionalLight*> directionalLights;
|
||||||
|
std::vector<PointLight*> pointLights;
|
||||||
|
std::vector<SpotLight*> spotLights;
|
||||||
|
std::vector<glm::mat4> shadowTileMatrices; // world to light clip space, one per used atlas tile
|
||||||
|
|
||||||
Lighting() {
|
Lighting() {
|
||||||
dirlighting.RotateToQuaternion(glm::quat({ glm::radians(90.0f),0.0f,0.0f}));
|
sun.color = glm::vec3(1.0f, 0.97f, 0.92f);
|
||||||
dirlighting.TranslateTo({0.0f,0.0f,0.0f});
|
sun.intensity = 3.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DirectionalLight* AddDirectionalLight(const glm::vec3& direction, const glm::vec3& color, float intensity) {
|
||||||
|
DirectionalLight* light = new DirectionalLight();
|
||||||
|
light->direction = glm::normalize(direction);
|
||||||
|
light->color = color;
|
||||||
|
light->intensity = intensity;
|
||||||
|
directionalLights.push_back(light);
|
||||||
|
return light;
|
||||||
|
}
|
||||||
|
|
||||||
|
PointLight* AddPointLight(const glm::vec3& position, const glm::vec3& color, float intensity, float radius) {
|
||||||
|
PointLight* light = new PointLight();
|
||||||
|
light->position = position;
|
||||||
|
light->color = color;
|
||||||
|
light->intensity = intensity;
|
||||||
|
light->radius = radius;
|
||||||
|
pointLights.push_back(light);
|
||||||
|
return light;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpotLight* AddSpotLight(const glm::vec3& position, const glm::vec3& direction, const glm::vec3& color,
|
||||||
|
float intensity, float radius, float innerAngle, float outerAngle) {
|
||||||
|
SpotLight* light = new SpotLight();
|
||||||
|
light->position = position;
|
||||||
|
light->direction = glm::normalize(direction);
|
||||||
|
light->color = color;
|
||||||
|
light->intensity = intensity;
|
||||||
|
light->radius = radius;
|
||||||
|
light->innerAngle = innerAngle;
|
||||||
|
light->outerAngle = outerAngle;
|
||||||
|
spotLights.push_back(light);
|
||||||
|
return light;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "tFunctions.h"
|
#include "tFunctions.h"
|
||||||
#include "Vertex.h"
|
#include "Vertex.h"
|
||||||
#include "texture.h"
|
#include "texture.h"
|
||||||
|
#include "SurfaceTexture.h"
|
||||||
#include "MeshData.h"
|
#include "MeshData.h"
|
||||||
|
|
||||||
struct IntersectionData {
|
struct IntersectionData {
|
||||||
@@ -36,11 +37,12 @@ class Mesh : public Object , public Renderable, public t_package , public MeshDa
|
|||||||
friend class Model;
|
friend class Model;
|
||||||
protected:
|
protected:
|
||||||
public:
|
public:
|
||||||
Texture* meshTexture = nullptr;
|
SurfaceTexture surface;
|
||||||
glm::vec4 colorOverride = glm::vec4(0.0f); // rgb + strength; 0 = use the texture's own colour
|
glm::vec4 colorOverride = glm::vec4(0.0f); // rgb + strength; 0 = use the texture's own colour
|
||||||
float metallic = 0.0f;
|
float metallic = 0.0f;
|
||||||
float roughness = 0.8f;
|
float roughness = 0.8f;
|
||||||
float ao = 1.0f;
|
float ao = 1.0f;
|
||||||
|
float emissive = 0.0f; // 0-1, how much of its own colour it shows with no light on it
|
||||||
|
|
||||||
VAO vao;
|
VAO vao;
|
||||||
VBO vbo;
|
VBO vbo;
|
||||||
@@ -461,26 +463,24 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vertices are normalized into the unit cube, so t's box already holds the whole mesh
|
||||||
|
bool GetWorldBounds(BoundingBox& bounds) const override {
|
||||||
|
bounds = t.GetAABB();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void Render(Shader& ShaderProgram) override {
|
void Render(Shader& ShaderProgram) override {
|
||||||
glm::mat4 topass = t.GetMatrix();
|
glm::mat4 topass = t.GetMatrix();
|
||||||
|
|
||||||
ShaderProgram.Activate();
|
ShaderProgram.Activate();
|
||||||
glActiveTexture(GL_TEXTURE1);
|
surface.Apply(ShaderProgram, 1, t.GetScale());
|
||||||
if (meshTexture != nullptr) {
|
|
||||||
glBindTexture(GL_TEXTURE_2D, meshTexture->ID);
|
|
||||||
ShaderProgram.SetInt("tex", 1);
|
|
||||||
ShaderProgram.SetInt("useTexture", 1);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
ShaderProgram.SetInt("useTexture", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderProgram.SetMat4("modl", topass);
|
ShaderProgram.SetMat4("modl", topass);
|
||||||
ShaderProgram.Set4F("colorOverride", colorOverride);
|
ShaderProgram.Set4F("colorOverride", colorOverride);
|
||||||
ShaderProgram.Set1F("metallic", metallic);
|
ShaderProgram.Set1F("metallic", metallic);
|
||||||
ShaderProgram.Set1F("roughness", roughness);
|
ShaderProgram.Set1F("roughness", roughness);
|
||||||
ShaderProgram.Set1F("ao", ao);
|
ShaderProgram.Set1F("ao", ao);
|
||||||
|
ShaderProgram.Set1F("emissive", emissive);
|
||||||
vao.Bind();
|
vao.Bind();
|
||||||
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
|
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
|
||||||
vao.Unbind();
|
vao.Unbind();
|
||||||
|
|||||||
@@ -1,626 +0,0 @@
|
|||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#define NOMINMAX
|
|
||||||
#include <winsock2.h>
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
#pragma comment(lib, "ws2_32.lib")
|
|
||||||
|
|
||||||
#include "Net.h"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstring>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#ifndef SIO_UDP_CONNRESET
|
|
||||||
#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr uint32_t NET_MAGIC = 0x45494946; // "EIIF"
|
|
||||||
constexpr uint16_t NET_VERSION = 3;
|
|
||||||
constexpr unsigned short NET_PORT = 27015;
|
|
||||||
constexpr double NET_TIMEOUT = 2.0; // seconds of silence before a player is dropped
|
|
||||||
|
|
||||||
// One datagram = Header followed by 'count' Entry records.
|
|
||||||
// Client -> host: count 1 (the client's own state, id ignored).
|
|
||||||
// Host -> client: one entry per other player, host included (id 0).
|
|
||||||
#pragma pack(push, 1)
|
|
||||||
struct Header {
|
|
||||||
uint32_t magic;
|
|
||||||
uint16_t version;
|
|
||||||
uint32_t seq;
|
|
||||||
uint8_t count;
|
|
||||||
};
|
|
||||||
struct Entry {
|
|
||||||
uint8_t id;
|
|
||||||
float pos[3];
|
|
||||||
float rot[4]; // x, y, z, w
|
|
||||||
uint8_t sliding;
|
|
||||||
};
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
constexpr uint32_t NET_MAGIC_BLAST = 0x45494942; // "EIIB"
|
|
||||||
constexpr int BLAST_COPIES = 4; // each blast is sent this many times
|
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
|
||||||
struct BlastPacket {
|
|
||||||
uint32_t magic;
|
|
||||||
uint16_t version;
|
|
||||||
uint8_t sender;
|
|
||||||
uint32_t id;
|
|
||||||
float center[3];
|
|
||||||
float radius;
|
|
||||||
float power;
|
|
||||||
};
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
constexpr uint32_t NET_MAGIC_HIT = 0x45494948; // "EIIH"
|
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
|
||||||
struct HitPacket {
|
|
||||||
uint32_t magic;
|
|
||||||
uint16_t version;
|
|
||||||
uint8_t sender;
|
|
||||||
uint8_t target;
|
|
||||||
uint32_t id;
|
|
||||||
int16_t damage;
|
|
||||||
float knock[3];
|
|
||||||
};
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
constexpr uint32_t NET_MAGIC_SPAWN = 0x45494953; // "EIIS"
|
|
||||||
constexpr uint32_t NET_MAGIC_CUBES = 0x45494943; // "EIIC"
|
|
||||||
constexpr int CUBES_PER_PACKET = 36;
|
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
|
||||||
struct SpawnPacket {
|
|
||||||
uint32_t magic;
|
|
||||||
uint16_t version;
|
|
||||||
uint8_t sender;
|
|
||||||
uint32_t id;
|
|
||||||
float pos[3];
|
|
||||||
};
|
|
||||||
struct CubesHeader {
|
|
||||||
uint32_t magic;
|
|
||||||
uint16_t version;
|
|
||||||
uint8_t count;
|
|
||||||
};
|
|
||||||
struct CubeEntry {
|
|
||||||
uint32_t id;
|
|
||||||
float pos[3];
|
|
||||||
float rot[4]; // x, y, z, w
|
|
||||||
};
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
constexpr int MAX_ENTRIES = Lan::MaxPlayers;
|
|
||||||
constexpr int MAX_DATAGRAM = (int)sizeof(Header) + MAX_ENTRIES * (int)sizeof(Entry);
|
|
||||||
|
|
||||||
double Now() {
|
|
||||||
using namespace std::chrono;
|
|
||||||
return duration<double>(steady_clock::now().time_since_epoch()).count();
|
|
||||||
}
|
|
||||||
|
|
||||||
Entry MakeEntry(uint8_t id, const NetState& s) {
|
|
||||||
Entry e;
|
|
||||||
e.id = id;
|
|
||||||
e.pos[0] = s.position.x; e.pos[1] = s.position.y; e.pos[2] = s.position.z;
|
|
||||||
e.rot[0] = s.rotation.x; e.rot[1] = s.rotation.y; e.rot[2] = s.rotation.z; e.rot[3] = s.rotation.w;
|
|
||||||
e.sliding = s.sliding ? 1 : 0;
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetState ReadEntry(const Entry& e) {
|
|
||||||
NetState s;
|
|
||||||
s.position = glm::vec3(e.pos[0], e.pos[1], e.pos[2]);
|
|
||||||
s.rotation = glm::quat(e.rot[3], e.rot[0], e.rot[1], e.rot[2]); // glm ctor order is w, x, y, z
|
|
||||||
s.sliding = e.sliding != 0;
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SameAddress(const sockaddr_in& a, const sockaddr_in& b) {
|
|
||||||
return a.sin_addr.s_addr == b.sin_addr.s_addr && a.sin_port == b.sin_port;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Lan::Impl {
|
|
||||||
struct Peer {
|
|
||||||
sockaddr_in addr{};
|
|
||||||
uint8_t id = 0;
|
|
||||||
double lastRecv = 0.0;
|
|
||||||
uint32_t lastSeq = 0;
|
|
||||||
bool started = false; // has sent at least one datagram (so lastSeq is valid)
|
|
||||||
NetState state;
|
|
||||||
bool hasState = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
SOCKET sock = INVALID_SOCKET;
|
|
||||||
bool wsaStarted = false;
|
|
||||||
uint32_t sendSeq = 0;
|
|
||||||
|
|
||||||
// Host
|
|
||||||
std::vector<Peer> peers;
|
|
||||||
|
|
||||||
// Client
|
|
||||||
sockaddr_in hostAddr{};
|
|
||||||
bool anyRecv = false;
|
|
||||||
uint32_t lastSeq = 0;
|
|
||||||
double lastRecvTime = 0.0;
|
|
||||||
|
|
||||||
// Blasts waiting for their remaining copies to go out
|
|
||||||
struct PendingBlast { BlastPacket pkt; int copiesLeft; };
|
|
||||||
std::vector<PendingBlast> pendingBlasts;
|
|
||||||
uint32_t blastSeq = 0;
|
|
||||||
std::vector<uint64_t> seenBlasts; // (type, sender, id) of recent blasts/hits, for de-duplication
|
|
||||||
|
|
||||||
struct PendingSpawn { SpawnPacket pkt; int copiesLeft; };
|
|
||||||
std::vector<PendingSpawn> pendingSpawns;
|
|
||||||
uint32_t spawnSeq = 0;
|
|
||||||
size_t cubeCursor = 0; // host: where the next slice of cubes starts
|
|
||||||
|
|
||||||
struct PendingHit { HitPacket pkt; int copiesLeft; };
|
|
||||||
std::vector<PendingHit> pendingHits;
|
|
||||||
uint32_t hitSeq = 0;
|
|
||||||
|
|
||||||
void SendHitPacket(const sockaddr_in& to, const HitPacket& pkt) {
|
|
||||||
sendto(sock, (const char*)&pkt, (int)sizeof(pkt), 0, (const sockaddr*)&to, (int)sizeof(to));
|
|
||||||
}
|
|
||||||
|
|
||||||
// True the first time a (type, sender, id) triple is seen. type: 0 = blast, 1 = hit.
|
|
||||||
bool FirstSighting(uint8_t type, uint8_t sender, uint32_t id) {
|
|
||||||
const uint64_t key = ((uint64_t)type << 40) | ((uint64_t)sender << 32) | id;
|
|
||||||
if (std::find(seenBlasts.begin(), seenBlasts.end(), key) != seenBlasts.end()) return false;
|
|
||||||
seenBlasts.push_back(key);
|
|
||||||
if (seenBlasts.size() > 64) seenBlasts.erase(seenBlasts.begin());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendBlastPacket(const sockaddr_in& to, const BlastPacket& pkt) {
|
|
||||||
sendto(sock, (const char*)&pkt, (int)sizeof(pkt), 0, (const sockaddr*)&to, (int)sizeof(to));
|
|
||||||
}
|
|
||||||
|
|
||||||
void SendDatagram(const sockaddr_in& to, const std::vector<Entry>& entries) {
|
|
||||||
char buf[MAX_DATAGRAM];
|
|
||||||
Header h;
|
|
||||||
h.magic = NET_MAGIC;
|
|
||||||
h.version = NET_VERSION;
|
|
||||||
h.seq = ++sendSeq;
|
|
||||||
h.count = (uint8_t)entries.size();
|
|
||||||
std::memcpy(buf, &h, sizeof(h));
|
|
||||||
std::memcpy(buf + sizeof(h), entries.data(), entries.size() * sizeof(Entry));
|
|
||||||
sendto(sock, buf, (int)(sizeof(h) + entries.size() * sizeof(Entry)), 0,
|
|
||||||
(const sockaddr*)&to, (int)sizeof(to));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Lan::Lan() : impl(new Impl()) {}
|
|
||||||
|
|
||||||
Lan::~Lan() {
|
|
||||||
Stop();
|
|
||||||
delete impl;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Lan::Start(Mode m, const std::string& hostIp) {
|
|
||||||
if (m == Mode::Off) return false;
|
|
||||||
|
|
||||||
WSADATA wsa;
|
|
||||||
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
|
|
||||||
std::cout << "[net] WSAStartup failed\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
impl->wsaStarted = true;
|
|
||||||
|
|
||||||
impl->sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
||||||
if (impl->sock == INVALID_SOCKET) {
|
|
||||||
std::cout << "[net] socket() failed: " << WSAGetLastError() << "\n";
|
|
||||||
Stop();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
u_long nonBlocking = 1;
|
|
||||||
ioctlsocket(impl->sock, FIONBIO, &nonBlocking);
|
|
||||||
|
|
||||||
// Don't let an ICMP "port unreachable" (other side not up yet) kill recvfrom with WSAECONNRESET
|
|
||||||
BOOL newBehavior = FALSE;
|
|
||||||
DWORD bytes = 0;
|
|
||||||
WSAIoctl(impl->sock, SIO_UDP_CONNRESET, &newBehavior, sizeof(newBehavior), NULL, 0, &bytes, NULL, NULL);
|
|
||||||
|
|
||||||
sockaddr_in local{};
|
|
||||||
local.sin_family = AF_INET;
|
|
||||||
local.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
||||||
local.sin_port = htons(m == Mode::Host ? NET_PORT : 0); // clients use any free port
|
|
||||||
if (bind(impl->sock, (sockaddr*)&local, sizeof(local)) == SOCKET_ERROR) {
|
|
||||||
std::cout << "[net] bind() failed: " << WSAGetLastError()
|
|
||||||
<< " (is another copy already hosting on this PC?)\n";
|
|
||||||
Stop();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m == Mode::Client) {
|
|
||||||
impl->hostAddr.sin_family = AF_INET;
|
|
||||||
impl->hostAddr.sin_port = htons(NET_PORT);
|
|
||||||
if (inet_pton(AF_INET, hostIp.c_str(), &impl->hostAddr.sin_addr) != 1) {
|
|
||||||
std::cout << "[net] '" << hostIp << "' is not a valid IPv4 address\n";
|
|
||||||
Stop();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mode = m;
|
|
||||||
active = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lan::Stop() {
|
|
||||||
if (impl->sock != INVALID_SOCKET) {
|
|
||||||
closesocket(impl->sock);
|
|
||||||
impl->sock = INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
if (impl->wsaStarted) {
|
|
||||||
WSACleanup();
|
|
||||||
impl->wsaStarted = false;
|
|
||||||
}
|
|
||||||
impl->peers.clear();
|
|
||||||
impl->pendingBlasts.clear();
|
|
||||||
impl->pendingHits.clear();
|
|
||||||
impl->pendingSpawns.clear();
|
|
||||||
impl->seenBlasts.clear();
|
|
||||||
impl->anyRecv = false;
|
|
||||||
active = false;
|
|
||||||
mode = Mode::Off;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lan::SendBlast(const NetBlast& b) {
|
|
||||||
if (!active) return;
|
|
||||||
Impl::PendingBlast pb;
|
|
||||||
pb.pkt.magic = NET_MAGIC_BLAST;
|
|
||||||
pb.pkt.version = NET_VERSION;
|
|
||||||
pb.pkt.sender = 0; // the host stamps the real id when relaying a client's blast
|
|
||||||
pb.pkt.id = ++impl->blastSeq;
|
|
||||||
pb.pkt.center[0] = b.center.x; pb.pkt.center[1] = b.center.y; pb.pkt.center[2] = b.center.z;
|
|
||||||
pb.pkt.radius = b.radius;
|
|
||||||
pb.pkt.power = b.power;
|
|
||||||
pb.copiesLeft = BLAST_COPIES;
|
|
||||||
impl->pendingBlasts.push_back(pb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lan::SendHit(uint8_t target, const NetHit& h) {
|
|
||||||
if (!active) return;
|
|
||||||
Impl::PendingHit ph;
|
|
||||||
ph.pkt.magic = NET_MAGIC_HIT;
|
|
||||||
ph.pkt.version = NET_VERSION;
|
|
||||||
ph.pkt.sender = 0; // the host stamps the real id when relaying a client's hit
|
|
||||||
ph.pkt.target = target;
|
|
||||||
ph.pkt.id = ++impl->hitSeq;
|
|
||||||
ph.pkt.damage = (int16_t)std::clamp(h.damage, 0, 1000);
|
|
||||||
ph.pkt.knock[0] = h.knockback.x; ph.pkt.knock[1] = h.knockback.y; ph.pkt.knock[2] = h.knockback.z;
|
|
||||||
ph.copiesLeft = BLAST_COPIES;
|
|
||||||
impl->pendingHits.push_back(ph);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lan::SendCubeSpawn(const glm::vec3& position) {
|
|
||||||
if (!active || mode != Mode::Client) return;
|
|
||||||
Impl::PendingSpawn ps;
|
|
||||||
ps.pkt.magic = NET_MAGIC_SPAWN;
|
|
||||||
ps.pkt.version = NET_VERSION;
|
|
||||||
ps.pkt.sender = 0; // the host stamps the real id
|
|
||||||
ps.pkt.id = ++impl->spawnSeq;
|
|
||||||
ps.pkt.pos[0] = position.x; ps.pkt.pos[1] = position.y; ps.pkt.pos[2] = position.z;
|
|
||||||
ps.copiesLeft = BLAST_COPIES;
|
|
||||||
impl->pendingSpawns.push_back(ps);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lan::SendCubes(const std::vector<NetCube>& cubes) {
|
|
||||||
if (!active || mode != Mode::Host || cubes.empty() || impl->peers.empty()) return;
|
|
||||||
|
|
||||||
const size_t n = cubes.size();
|
|
||||||
const size_t count = std::min(n, (size_t)CUBES_PER_PACKET);
|
|
||||||
if (impl->cubeCursor >= n) impl->cubeCursor = 0;
|
|
||||||
|
|
||||||
char buf[sizeof(CubesHeader) + CUBES_PER_PACKET * sizeof(CubeEntry)];
|
|
||||||
CubesHeader h;
|
|
||||||
h.magic = NET_MAGIC_CUBES;
|
|
||||||
h.version = NET_VERSION;
|
|
||||||
h.count = (uint8_t)count;
|
|
||||||
std::memcpy(buf, &h, sizeof(h));
|
|
||||||
for (size_t i = 0; i < count; i++) {
|
|
||||||
const NetCube& c = cubes[(impl->cubeCursor + i) % n];
|
|
||||||
CubeEntry e;
|
|
||||||
e.id = c.id;
|
|
||||||
e.pos[0] = c.position.x; e.pos[1] = c.position.y; e.pos[2] = c.position.z;
|
|
||||||
e.rot[0] = c.rotation.x; e.rot[1] = c.rotation.y; e.rot[2] = c.rotation.z; e.rot[3] = c.rotation.w;
|
|
||||||
std::memcpy(buf + sizeof(h) + i * sizeof(e), &e, sizeof(e));
|
|
||||||
}
|
|
||||||
impl->cubeCursor = (impl->cubeCursor + count) % n;
|
|
||||||
|
|
||||||
const int len = (int)(sizeof(h) + count * sizeof(CubeEntry));
|
|
||||||
for (const Impl::Peer& to : impl->peers)
|
|
||||||
sendto(impl->sock, buf, len, 0, (const sockaddr*)&to.addr, (int)sizeof(to.addr));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lan::Send(const NetState& s) {
|
|
||||||
if (!active) return;
|
|
||||||
|
|
||||||
// One more copy of every pending cube spawn request (client -> host)
|
|
||||||
for (Impl::PendingSpawn& ps : impl->pendingSpawns) {
|
|
||||||
sendto(impl->sock, (const char*)&ps.pkt, (int)sizeof(ps.pkt), 0,
|
|
||||||
(const sockaddr*)&impl->hostAddr, (int)sizeof(impl->hostAddr));
|
|
||||||
ps.copiesLeft--;
|
|
||||||
}
|
|
||||||
impl->pendingSpawns.erase(
|
|
||||||
std::remove_if(impl->pendingSpawns.begin(), impl->pendingSpawns.end(),
|
|
||||||
[](const Impl::PendingSpawn& ps) { return ps.copiesLeft <= 0; }),
|
|
||||||
impl->pendingSpawns.end());
|
|
||||||
|
|
||||||
// One more copy of every pending hit: clients send to the host, the host to the target only
|
|
||||||
for (Impl::PendingHit& ph : impl->pendingHits) {
|
|
||||||
if (mode == Mode::Client) impl->SendHitPacket(impl->hostAddr, ph.pkt);
|
|
||||||
else for (const Impl::Peer& to : impl->peers)
|
|
||||||
if (to.id == ph.pkt.target) impl->SendHitPacket(to.addr, ph.pkt);
|
|
||||||
ph.copiesLeft--;
|
|
||||||
}
|
|
||||||
impl->pendingHits.erase(
|
|
||||||
std::remove_if(impl->pendingHits.begin(), impl->pendingHits.end(),
|
|
||||||
[](const Impl::PendingHit& ph) { return ph.copiesLeft <= 0; }),
|
|
||||||
impl->pendingHits.end());
|
|
||||||
|
|
||||||
// One more copy of every pending blast (to the host, or from the host to everyone)
|
|
||||||
for (Impl::PendingBlast& pb : impl->pendingBlasts) {
|
|
||||||
if (mode == Mode::Client) impl->SendBlastPacket(impl->hostAddr, pb.pkt);
|
|
||||||
else for (const Impl::Peer& to : impl->peers) impl->SendBlastPacket(to.addr, pb.pkt);
|
|
||||||
pb.copiesLeft--;
|
|
||||||
}
|
|
||||||
impl->pendingBlasts.erase(
|
|
||||||
std::remove_if(impl->pendingBlasts.begin(), impl->pendingBlasts.end(),
|
|
||||||
[](const Impl::PendingBlast& pb) { return pb.copiesLeft <= 0; }),
|
|
||||||
impl->pendingBlasts.end());
|
|
||||||
|
|
||||||
if (mode == Mode::Client) {
|
|
||||||
impl->SendDatagram(impl->hostAddr, { MakeEntry(0, s) });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Host: to each player, send the host's state plus every other player's latest state
|
|
||||||
for (const Impl::Peer& to : impl->peers) {
|
|
||||||
std::vector<Entry> entries;
|
|
||||||
entries.push_back(MakeEntry(0, s));
|
|
||||||
for (const Impl::Peer& other : impl->peers) {
|
|
||||||
if (other.id != to.id && other.hasState && (int)entries.size() < MAX_ENTRIES)
|
|
||||||
entries.push_back(MakeEntry(other.id, other.state));
|
|
||||||
}
|
|
||||||
impl->SendDatagram(to.addr, entries);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lan::Receive(std::vector<NetUpdate>& out, std::vector<NetBlast>* blasts, std::vector<NetHit>* hits,
|
|
||||||
std::vector<NetCubeRequest>* spawns, std::vector<NetCube>* cubes) {
|
|
||||||
if (!active) return;
|
|
||||||
|
|
||||||
char buf[1500];
|
|
||||||
for (int i = 0; i < 512; i++) { // bounded so a flood can't stall the frame
|
|
||||||
sockaddr_in from{};
|
|
||||||
int fromLen = (int)sizeof(from);
|
|
||||||
int r = recvfrom(impl->sock, buf, (int)sizeof(buf), 0, (sockaddr*)&from, &fromLen);
|
|
||||||
if (r == SOCKET_ERROR) {
|
|
||||||
if (WSAGetLastError() == WSAEWOULDBLOCK) break;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (r < (int)sizeof(Header)) continue;
|
|
||||||
|
|
||||||
uint32_t magic;
|
|
||||||
std::memcpy(&magic, buf, sizeof(magic));
|
|
||||||
if (magic == NET_MAGIC_BLAST) {
|
|
||||||
if (r != (int)sizeof(BlastPacket)) continue;
|
|
||||||
BlastPacket bp;
|
|
||||||
std::memcpy(&bp, buf, sizeof(bp));
|
|
||||||
if (bp.version != NET_VERSION) continue;
|
|
||||||
|
|
||||||
if (mode == Mode::Host) {
|
|
||||||
// Only known players may blast. Stamp their id and relay to everyone else.
|
|
||||||
const Impl::Peer* sender = nullptr;
|
|
||||||
for (const Impl::Peer& pe : impl->peers)
|
|
||||||
if (SameAddress(pe.addr, from)) { sender = &pe; break; }
|
|
||||||
if (!sender) continue;
|
|
||||||
bp.sender = sender->id;
|
|
||||||
for (const Impl::Peer& to : impl->peers)
|
|
||||||
if (to.id != sender->id) impl->SendBlastPacket(to.addr, bp);
|
|
||||||
}
|
|
||||||
else if (!SameAddress(from, impl->hostAddr)) continue; // clients only listen to the host
|
|
||||||
|
|
||||||
if (blasts && impl->FirstSighting(0, bp.sender, bp.id)) {
|
|
||||||
NetBlast nb;
|
|
||||||
nb.sender = bp.sender;
|
|
||||||
nb.id = bp.id;
|
|
||||||
nb.center = glm::vec3(bp.center[0], bp.center[1], bp.center[2]);
|
|
||||||
nb.radius = bp.radius;
|
|
||||||
nb.power = bp.power;
|
|
||||||
blasts->push_back(nb);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (magic == NET_MAGIC_SPAWN) {
|
|
||||||
if (mode != Mode::Host || r != (int)sizeof(SpawnPacket)) continue;
|
|
||||||
SpawnPacket sp;
|
|
||||||
std::memcpy(&sp, buf, sizeof(sp));
|
|
||||||
if (sp.version != NET_VERSION) continue;
|
|
||||||
|
|
||||||
// Only known players may spawn cubes
|
|
||||||
const Impl::Peer* sender = nullptr;
|
|
||||||
for (const Impl::Peer& pe : impl->peers)
|
|
||||||
if (SameAddress(pe.addr, from)) { sender = &pe; break; }
|
|
||||||
if (!sender) continue;
|
|
||||||
|
|
||||||
if (spawns && impl->FirstSighting(2, sender->id, sp.id)) {
|
|
||||||
NetCubeRequest req;
|
|
||||||
req.sender = sender->id;
|
|
||||||
req.id = sp.id;
|
|
||||||
req.position = glm::vec3(sp.pos[0], sp.pos[1], sp.pos[2]);
|
|
||||||
spawns->push_back(req);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (magic == NET_MAGIC_CUBES) {
|
|
||||||
if (mode != Mode::Client || !SameAddress(from, impl->hostAddr)) continue;
|
|
||||||
CubesHeader ch;
|
|
||||||
if (r < (int)sizeof(ch)) continue;
|
|
||||||
std::memcpy(&ch, buf, sizeof(ch));
|
|
||||||
if (ch.version != NET_VERSION || ch.count > CUBES_PER_PACKET) continue;
|
|
||||||
if (r != (int)(sizeof(ch) + ch.count * sizeof(CubeEntry))) continue;
|
|
||||||
|
|
||||||
if (cubes) {
|
|
||||||
for (int k = 0; k < ch.count; k++) {
|
|
||||||
CubeEntry e;
|
|
||||||
std::memcpy(&e, buf + sizeof(ch) + k * sizeof(e), sizeof(e));
|
|
||||||
NetCube c;
|
|
||||||
c.id = e.id;
|
|
||||||
c.position = glm::vec3(e.pos[0], e.pos[1], e.pos[2]);
|
|
||||||
c.rotation = glm::quat(e.rot[3], e.rot[0], e.rot[1], e.rot[2]);
|
|
||||||
cubes->push_back(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (magic == NET_MAGIC_HIT) {
|
|
||||||
if (r != (int)sizeof(HitPacket)) continue;
|
|
||||||
HitPacket hp;
|
|
||||||
std::memcpy(&hp, buf, sizeof(hp));
|
|
||||||
if (hp.version != NET_VERSION) continue;
|
|
||||||
|
|
||||||
bool forUs = true;
|
|
||||||
if (mode == Mode::Host) {
|
|
||||||
// Only known players may shoot. Stamp their id; deliver to us or forward to the target.
|
|
||||||
const Impl::Peer* sender = nullptr;
|
|
||||||
for (const Impl::Peer& pe : impl->peers)
|
|
||||||
if (SameAddress(pe.addr, from)) { sender = &pe; break; }
|
|
||||||
if (!sender) continue;
|
|
||||||
hp.sender = sender->id;
|
|
||||||
if (hp.target != 0) {
|
|
||||||
forUs = false;
|
|
||||||
for (const Impl::Peer& to : impl->peers)
|
|
||||||
if (to.id == hp.target) impl->SendHitPacket(to.addr, hp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!SameAddress(from, impl->hostAddr)) continue; // clients only listen to the host
|
|
||||||
|
|
||||||
if (forUs && hits && impl->FirstSighting(1, hp.sender, hp.id)) {
|
|
||||||
NetHit nh;
|
|
||||||
nh.sender = hp.sender;
|
|
||||||
nh.id = hp.id;
|
|
||||||
nh.damage = hp.damage;
|
|
||||||
nh.knockback = glm::vec3(hp.knock[0], hp.knock[1], hp.knock[2]);
|
|
||||||
hits->push_back(nh);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Header h;
|
|
||||||
std::memcpy(&h, buf, sizeof(h));
|
|
||||||
if (h.magic != NET_MAGIC || h.version != NET_VERSION) continue;
|
|
||||||
if (h.count == 0 || h.count > MAX_ENTRIES) continue;
|
|
||||||
if (r != (int)(sizeof(Header) + h.count * sizeof(Entry))) continue;
|
|
||||||
|
|
||||||
const double now = Now();
|
|
||||||
|
|
||||||
if (mode == Mode::Host) {
|
|
||||||
// Find this sender, or admit them as a new player
|
|
||||||
Impl::Peer* peer = nullptr;
|
|
||||||
for (Impl::Peer& p : impl->peers)
|
|
||||||
if (SameAddress(p.addr, from)) { peer = &p; break; }
|
|
||||||
|
|
||||||
if (!peer) {
|
|
||||||
if ((int)impl->peers.size() >= MaxPlayers - 1) continue; // pit is full
|
|
||||||
uint8_t id = 1;
|
|
||||||
while (std::any_of(impl->peers.begin(), impl->peers.end(),
|
|
||||||
[&](const Impl::Peer& p) { return p.id == id; }))
|
|
||||||
id++;
|
|
||||||
Impl::Peer np;
|
|
||||||
np.addr = from;
|
|
||||||
np.id = id;
|
|
||||||
impl->peers.push_back(np);
|
|
||||||
peer = &impl->peers.back();
|
|
||||||
std::cout << "[net] player " << (int)id << " joined\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (peer->started && (int32_t)(h.seq - peer->lastSeq) <= 0) continue; // stale / duplicate
|
|
||||||
peer->started = true;
|
|
||||||
peer->lastSeq = h.seq;
|
|
||||||
peer->lastRecv = now;
|
|
||||||
|
|
||||||
Entry e;
|
|
||||||
std::memcpy(&e, buf + sizeof(Header), sizeof(Entry));
|
|
||||||
peer->state = ReadEntry(e);
|
|
||||||
peer->hasState = true;
|
|
||||||
|
|
||||||
NetUpdate u;
|
|
||||||
u.id = peer->id;
|
|
||||||
u.state = peer->state;
|
|
||||||
out.push_back(u);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (!SameAddress(from, impl->hostAddr)) continue; // only listen to the host
|
|
||||||
|
|
||||||
// After a long silence the host may have restarted (sequence back to 1): accept anything
|
|
||||||
if (impl->anyRecv && now - impl->lastRecvTime >= NET_TIMEOUT) impl->anyRecv = false;
|
|
||||||
if (impl->anyRecv && (int32_t)(h.seq - impl->lastSeq) <= 0) continue;
|
|
||||||
impl->anyRecv = true;
|
|
||||||
impl->lastSeq = h.seq;
|
|
||||||
impl->lastRecvTime = now;
|
|
||||||
|
|
||||||
for (int k = 0; k < h.count; k++) {
|
|
||||||
Entry e;
|
|
||||||
std::memcpy(&e, buf + sizeof(Header) + k * sizeof(Entry), sizeof(Entry));
|
|
||||||
NetUpdate u;
|
|
||||||
u.id = e.id;
|
|
||||||
u.state = ReadEntry(e);
|
|
||||||
out.push_back(u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Host: forget players who stopped sending
|
|
||||||
if (mode == Mode::Host) {
|
|
||||||
const double now = Now();
|
|
||||||
for (size_t i = 0; i < impl->peers.size();) {
|
|
||||||
if (now - impl->peers[i].lastRecv > NET_TIMEOUT) {
|
|
||||||
std::cout << "[net] player " << (int)impl->peers[i].id << " left\n";
|
|
||||||
impl->peers.erase(impl->peers.begin() + i);
|
|
||||||
}
|
|
||||||
else i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Lan::IsConnected() const {
|
|
||||||
if (!active) return false;
|
|
||||||
if (mode == Mode::Host) return !impl->peers.empty();
|
|
||||||
return impl->anyRecv && (Now() - impl->lastRecvTime) < NET_TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Lan::PeerCount() const {
|
|
||||||
return (int)impl->peers.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Lan::LocalAddresses() {
|
|
||||||
WSADATA wsa;
|
|
||||||
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return "";
|
|
||||||
|
|
||||||
std::string result;
|
|
||||||
char name[256];
|
|
||||||
if (gethostname(name, sizeof(name)) == 0) {
|
|
||||||
addrinfo hints{};
|
|
||||||
hints.ai_family = AF_INET;
|
|
||||||
addrinfo* list = nullptr;
|
|
||||||
if (getaddrinfo(name, nullptr, &hints, &list) == 0) {
|
|
||||||
for (addrinfo* a = list; a; a = a->ai_next) {
|
|
||||||
char ip[INET_ADDRSTRLEN];
|
|
||||||
const sockaddr_in* sa = (const sockaddr_in*)a->ai_addr;
|
|
||||||
if (inet_ntop(AF_INET, &sa->sin_addr, ip, sizeof(ip)))
|
|
||||||
result += std::string(result.empty() ? "" : ", ") + ip;
|
|
||||||
}
|
|
||||||
freeaddrinfo(list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WSACleanup();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
#ifndef NET_CLASS
|
|
||||||
#define NET_CLASS
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
#include <glm/gtc/quaternion.hpp>
|
|
||||||
|
|
||||||
// Multiplayer UDP link for a LAN pit. One player hosts (UDP port 27015), everyone else joins by IP.
|
|
||||||
// Clients send their own transform to the host every frame; the host relays everyone's
|
|
||||||
// transform to everyone else. The host is player id 0, joiners get ids 1, 2, 3, ...
|
|
||||||
// All Winsock code lives in Net.cpp so windows.h never leaks into the rest of the engine.
|
|
||||||
|
|
||||||
struct NetState {
|
|
||||||
glm::vec3 position = glm::vec3(0.0f);
|
|
||||||
glm::quat rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f);
|
|
||||||
bool sliding = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// A state received for some other player.
|
|
||||||
struct NetUpdate {
|
|
||||||
uint8_t id = 0;
|
|
||||||
NetState state;
|
|
||||||
};
|
|
||||||
|
|
||||||
// A shockwave explosion. Every machine applies the launch to its own player, since remote
|
|
||||||
// players are only puppets here. Blasts are re-sent a few times (UDP) and de-duplicated on receive.
|
|
||||||
struct NetBlast {
|
|
||||||
uint8_t sender = 0; // filled in by the host; ignored on send
|
|
||||||
uint32_t id = 0; // filled in by SendBlast
|
|
||||||
glm::vec3 center = glm::vec3(0.0f);
|
|
||||||
float radius = 0.0f;
|
|
||||||
float power = 0.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Damage dealt to another player. Each machine owns its own player's health, so the shooter
|
|
||||||
// aims locally and sends this to the victim, who applies it. Re-sent a few times and de-duplicated.
|
|
||||||
struct NetHit {
|
|
||||||
uint8_t sender = 0; // filled in by the host
|
|
||||||
uint32_t id = 0; // filled in by SendHit
|
|
||||||
int damage = 0;
|
|
||||||
glm::vec3 knockback = glm::vec3(0.0f); // added to the victim's velocity
|
|
||||||
};
|
|
||||||
|
|
||||||
// A physics cube owned by the host. The host simulates every cube and streams their poses to the
|
|
||||||
// clients, which only draw them. The host also decides when a cube despawns.
|
|
||||||
struct NetCube {
|
|
||||||
uint32_t id = 0;
|
|
||||||
glm::vec3 position = glm::vec3(0.0f);
|
|
||||||
glm::quat rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f);
|
|
||||||
};
|
|
||||||
|
|
||||||
// A client asking the host to spawn a cube. Re-sent a few times (UDP) and de-duplicated by the host.
|
|
||||||
struct NetCubeRequest {
|
|
||||||
uint8_t sender = 0; // filled in by the host
|
|
||||||
uint32_t id = 0;
|
|
||||||
glm::vec3 position = glm::vec3(0.0f);
|
|
||||||
};
|
|
||||||
|
|
||||||
class Lan {
|
|
||||||
public:
|
|
||||||
enum class Mode { Off, Host, Client };
|
|
||||||
|
|
||||||
// Total players in the pit, host included.
|
|
||||||
static constexpr int MaxPlayers = 8;
|
|
||||||
|
|
||||||
Lan();
|
|
||||||
~Lan();
|
|
||||||
|
|
||||||
// Opens the socket. For Client, hostIp is the host's IPv4 address. Returns false on failure.
|
|
||||||
bool Start(Mode mode, const std::string& hostIp);
|
|
||||||
void Stop();
|
|
||||||
|
|
||||||
Mode GetMode() const { return mode; }
|
|
||||||
bool IsActive() const { return active; }
|
|
||||||
|
|
||||||
// Call once per frame with our own state.
|
|
||||||
void Send(const NetState& state);
|
|
||||||
// Drains all waiting packets and appends the other players' newest states to 'out'.
|
|
||||||
// If 'blasts' is given, new (de-duplicated) shockwave blasts from other players are appended to it.
|
|
||||||
// 'hits' gets damage aimed at us (de-duplicated).
|
|
||||||
// 'spawns' (host only) gets cube spawn requests from clients; 'cubes' (client only) gets the
|
|
||||||
// host's cube poses.
|
|
||||||
void Receive(std::vector<NetUpdate>& out, std::vector<NetBlast>* blasts = nullptr,
|
|
||||||
std::vector<NetHit>* hits = nullptr, std::vector<NetCubeRequest>* spawns = nullptr,
|
|
||||||
std::vector<NetCube>* cubes = nullptr);
|
|
||||||
// Client: ask the host to spawn a cube at 'position'. Sent over the next few Send() calls.
|
|
||||||
void SendCubeSpawn(const glm::vec3& position);
|
|
||||||
// Host: stream cube poses to every client. Call once per frame; large sets go out in rotating
|
|
||||||
// slices, so a cube is refreshed every few frames.
|
|
||||||
void SendCubes(const std::vector<NetCube>& cubes);
|
|
||||||
// Damage another player. 'target' is the id from their NetUpdate (the host is 0).
|
|
||||||
void SendHit(uint8_t target, const NetHit& hit);
|
|
||||||
// Tell the other players about a blast we caused. Sent over the next few Send() calls.
|
|
||||||
void SendBlast(const NetBlast& blast);
|
|
||||||
|
|
||||||
// Host: at least one player has joined. Client: the host has been heard from in the last 2 s.
|
|
||||||
bool IsConnected() const;
|
|
||||||
// Host only: number of joined players, not counting the host.
|
|
||||||
int PeerCount() const;
|
|
||||||
|
|
||||||
// This machine's IPv4 addresses (for the host to read out to the joiners).
|
|
||||||
static std::string LocalAddresses();
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Impl;
|
|
||||||
Impl* impl;
|
|
||||||
Mode mode = Mode::Off;
|
|
||||||
bool active = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline Lan* lan = nullptr;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
+7
-10
@@ -66,10 +66,12 @@ public:
|
|||||||
public:
|
public:
|
||||||
std::vector<Particle*> particles;
|
std::vector<Particle*> particles;
|
||||||
|
|
||||||
Texture* tex;
|
SurfaceTexture surface;
|
||||||
|
|
||||||
Mesh* mesh;
|
Mesh* mesh;
|
||||||
|
|
||||||
|
unsigned int instanceBuffer = 0;
|
||||||
|
|
||||||
ParticleEmitter() {
|
ParticleEmitter() {
|
||||||
shadertype = ParticleShader;
|
shadertype = ParticleShader;
|
||||||
mesh = new Mesh(quadVertices3D, quadIndices3D);
|
mesh = new Mesh(quadVertices3D, quadIndices3D);
|
||||||
@@ -81,6 +83,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~ParticleEmitter() {
|
~ParticleEmitter() {
|
||||||
|
if (instanceBuffer != 0) glDeleteBuffers(1, &instanceBuffer);
|
||||||
mesh->Delete();
|
mesh->Delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,13 +134,8 @@ public:
|
|||||||
|
|
||||||
ShaderProgram.Set4F("color",color);
|
ShaderProgram.Set4F("color",color);
|
||||||
|
|
||||||
glActiveTexture(0);
|
surface.Apply(ShaderProgram, 0, size);
|
||||||
|
|
||||||
if (tex != nullptr) {
|
|
||||||
glBindTexture(GL_TEXTURE_2D, tex->ID);
|
|
||||||
ShaderProgram.SetInt("tex", 0);
|
|
||||||
}
|
|
||||||
else glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
std::vector<glm::mat4> matrices;
|
std::vector<glm::mat4> matrices;
|
||||||
|
|
||||||
if (color.w != 1.0f) {
|
if (color.w != 1.0f) {
|
||||||
@@ -167,9 +165,8 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unsigned int buffer;
|
if (instanceBuffer == 0) glGenBuffers(1, &instanceBuffer);
|
||||||
glGenBuffers(1, &buffer);
|
glBindBuffer(GL_ARRAY_BUFFER, instanceBuffer);
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, particles.size() * sizeof(glm::mat4), &matrices[0], GL_STATIC_DRAW);
|
glBufferData(GL_ARRAY_BUFFER, particles.size() * sizeof(glm::mat4), &matrices[0], GL_STATIC_DRAW);
|
||||||
|
|
||||||
mesh->vao.Bind();
|
mesh->vao.Bind();
|
||||||
|
|||||||
+426
-14
@@ -1,5 +1,8 @@
|
|||||||
#include "Physics.h"
|
#include "Physics.h"
|
||||||
|
|
||||||
|
#include <glm/gtc/constants.hpp>
|
||||||
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// ComputeInvInertiaTensor
|
// ComputeInvInertiaTensor
|
||||||
// Returns zero matrix for static or rotation-locked bodies.
|
// Returns zero matrix for static or rotation-locked bodies.
|
||||||
@@ -27,7 +30,8 @@ glm::mat3 Physics::ComputeInvInertiaTensor(const p* body)
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
bool Physics::UpdateSleep(p* body, float dt)
|
bool Physics::UpdateSleep(p* body, float dt)
|
||||||
{
|
{
|
||||||
if (!body->velocity) return true;
|
if (!body->velocity || body->frozen) return true;
|
||||||
|
if (body->collisionGroup != 0) return false; // groups settle as a whole, a joint can't hold a sleeping body
|
||||||
|
|
||||||
const float linSq = glm::dot(body->linearvelocity, body->linearvelocity);
|
const float linSq = glm::dot(body->linearvelocity, body->linearvelocity);
|
||||||
const float angSq = glm::dot(body->angularvelocity, body->angularvelocity);
|
const float angSq = glm::dot(body->angularvelocity, body->angularvelocity);
|
||||||
@@ -53,21 +57,27 @@ bool Physics::UpdateSleep(p* body, float dt)
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
void Physics::Step(float dt)
|
void Physics::Step(float dt)
|
||||||
{
|
{
|
||||||
|
// The damping constants are per 1/60 s step; rescale so drag doesn't depend on frame rate
|
||||||
|
const float steps = dt * 60.0f;
|
||||||
|
const float linearDamping = std::pow(PHYSICS_LINEAR_DAMPING, steps);
|
||||||
|
const float angularDamping = std::pow(PHYSICS_ANGULAR_DAMPING, steps);
|
||||||
|
|
||||||
// Integrate forces ? velocities
|
// Integrate forces ? velocities
|
||||||
for (p* body : objects) {
|
for (p* body : objects) {
|
||||||
if (!body->velocity || body->isSleeping) continue;
|
// Nothing rotates until the end of the step, so everything until then can share this
|
||||||
|
body->invInertia = ComputeInvInertiaTensor(body);
|
||||||
|
|
||||||
|
if (!body->velocity || body->isSleeping || body->frozen) continue;
|
||||||
|
|
||||||
body->force += body->mass * gravity;
|
body->force += body->mass * gravity;
|
||||||
|
|
||||||
body->linearvelocity += (body->force / body->mass) * dt;
|
body->linearvelocity += (body->force / body->mass) * dt;
|
||||||
|
|
||||||
if (!body->lockRotation)
|
if (!body->lockRotation)
|
||||||
body->angularvelocity += (ComputeInvInertiaTensor(body) * body->torque) * dt;
|
body->angularvelocity += (body->invInertia * body->torque) * dt;
|
||||||
|
|
||||||
// The damping constants are per 1/60 s step; rescale so drag doesn't depend on frame rate
|
body->linearvelocity *= linearDamping;
|
||||||
const float steps = dt * 60.0f;
|
body->angularvelocity *= angularDamping;
|
||||||
body->linearvelocity *= std::pow(PHYSICS_LINEAR_DAMPING, steps);
|
|
||||||
body->angularvelocity *= std::pow(PHYSICS_ANGULAR_DAMPING, steps);
|
|
||||||
|
|
||||||
body->force = glm::vec3(0.0f);
|
body->force = glm::vec3(0.0f);
|
||||||
body->torque = glm::vec3(0.0f);
|
body->torque = glm::vec3(0.0f);
|
||||||
@@ -80,17 +90,415 @@ void Physics::Step(float dt)
|
|||||||
if (!body->velocity) continue;
|
if (!body->velocity) continue;
|
||||||
if (UpdateSleep(body, dt)) continue;
|
if (UpdateSleep(body, dt)) continue;
|
||||||
|
|
||||||
|
if (body->maxSpeed > 0.0f && glm::length(body->linearvelocity) > body->maxSpeed)
|
||||||
|
body->linearvelocity = glm::normalize(body->linearvelocity) * body->maxSpeed;
|
||||||
|
if (body->maxAngularSpeed > 0.0f && glm::length(body->angularvelocity) > body->maxAngularSpeed)
|
||||||
|
body->angularvelocity = glm::normalize(body->angularvelocity) * body->maxAngularSpeed;
|
||||||
|
|
||||||
body->pointer->t.TranslateBy(body->linearvelocity * dt);
|
body->pointer->t.TranslateBy(body->linearvelocity * dt);
|
||||||
|
|
||||||
if (!body->lockRotation)
|
if (!body->lockRotation) {
|
||||||
body->pointer->t.RotateByQuaternion(glm::quat(body->angularvelocity * dt));
|
body->pointer->t.RotateByQuaternion(glm::quat(body->angularvelocity * dt));
|
||||||
|
if (body->collisionMesh) body->pointer->t.NormalizeRotation();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// ResolveCollisions
|
||||||
|
// ============================================================================
|
||||||
|
void Physics::ResolveCollisions(float dt)
|
||||||
|
{
|
||||||
|
FindCollisions();
|
||||||
|
for (Collision& col : collisions)
|
||||||
|
Resolve(&col);
|
||||||
|
|
||||||
|
if (joints.empty()) return;
|
||||||
|
|
||||||
|
// The first contact pass nudges positions, so these have to be worked out after it
|
||||||
|
for (BallJoint* joint : joints) {
|
||||||
|
DampJoint(*joint, dt);
|
||||||
|
PrepareJoint(*joint, dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Joints and contacts fight each other, so iterate them together or limbs get pulled through the floor
|
||||||
|
for (int i = 0; i < PHYSICS_JOINT_ITERATIONS; i++) {
|
||||||
|
for (BallJoint* joint : joints)
|
||||||
|
SolveJoint(*joint);
|
||||||
|
for (Collision& col : collisions)
|
||||||
|
Resolve(&col, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FindCollisions
|
||||||
|
// Sort and sweep: once sorted along x, a body can only touch the ones after it
|
||||||
|
// that start before it ends.
|
||||||
|
// ============================================================================
|
||||||
|
void Physics::FindCollisions()
|
||||||
|
{
|
||||||
|
collisions.clear();
|
||||||
|
broadPhase.clear();
|
||||||
|
|
||||||
|
for (p* body : objects) {
|
||||||
|
if (!body->collision) continue;
|
||||||
|
BroadPhaseEntry entry;
|
||||||
|
entry.body = body;
|
||||||
|
entry.bounds = body->pointer->t.GetAABB();
|
||||||
|
broadPhase.push_back(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(broadPhase.begin(), broadPhase.end(), [](const BroadPhaseEntry& first, const BroadPhaseEntry& second) {
|
||||||
|
return first.bounds.min.x < second.bounds.min.x;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)broadPhase.size(); i++) {
|
||||||
|
const BroadPhaseEntry& first = broadPhase[i];
|
||||||
|
p* a = first.body;
|
||||||
|
|
||||||
|
for (int j = i + 1; j < (int)broadPhase.size(); j++) {
|
||||||
|
const BroadPhaseEntry& second = broadPhase[j];
|
||||||
|
if (second.bounds.min.x >= first.bounds.max.x) break;
|
||||||
|
|
||||||
|
p* b = second.body;
|
||||||
|
if (!a->velocity && !b->velocity) continue;
|
||||||
|
if (a->isSleeping && b->isSleeping) continue;
|
||||||
|
if ((a->frozen || !a->velocity) && (b->frozen || !b->velocity)) continue;
|
||||||
|
if (a->collisionGroup != 0 && a->collisionGroup == b->collisionGroup) continue;
|
||||||
|
if (!BoundingBoxInBoundingBox(first.bounds, second.bounds)) continue;
|
||||||
|
|
||||||
|
if (a->collisionMesh || b->collisionMesh) {
|
||||||
|
CollideTriangles(a, b);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<TInTInfo> e = TInT(a->pointer->t, b->pointer->t);
|
||||||
|
if (e.has_value()) {
|
||||||
|
Collision tp(a, b);
|
||||||
|
tp.POI = e.value().POI;
|
||||||
|
tp.CN = e.value().CN;
|
||||||
|
tp.overlap = e.value().overlap;
|
||||||
|
collisions.push_back(tp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// CollideTriangles
|
||||||
|
// Tests every triangle of one body against the other's BVH. Triangles have no
|
||||||
|
// volume, so depth comes from how far the intersecting triangle reaches behind
|
||||||
|
// the other triangle's outward facing plane.
|
||||||
|
// ============================================================================
|
||||||
|
static Triangle TransformTriangle(const Triangle& tri, const glm::mat4& matrix)
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
glm::vec3(matrix * glm::vec4(tri.a, 1.0f)),
|
||||||
|
glm::vec3(matrix * glm::vec4(tri.b, 1.0f)),
|
||||||
|
glm::vec3(matrix * glm::vec4(tri.c, 1.0f))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keeps the deepest contact, then whichever contacts spread the rest out the most
|
||||||
|
static void KeepSpreadContacts(std::vector<Collision>& contacts)
|
||||||
|
{
|
||||||
|
if ((int)contacts.size() <= PHYSICS_MAX_MESH_CONTACTS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int deepest = 0;
|
||||||
|
for (int i = 1; i < (int)contacts.size(); i++) {
|
||||||
|
if (contacts[i].overlap > contacts[deepest].overlap)
|
||||||
|
deepest = i;
|
||||||
|
}
|
||||||
|
std::swap(contacts[0], contacts[deepest]);
|
||||||
|
|
||||||
|
for (int kept = 1; kept < PHYSICS_MAX_MESH_CONTACTS; kept++) {
|
||||||
|
int farthest = kept;
|
||||||
|
float farthestDistance = -1.0f;
|
||||||
|
for (int i = kept; i < (int)contacts.size(); i++) {
|
||||||
|
float nearest = FLT_MAX;
|
||||||
|
for (int j = 0; j < kept; j++) {
|
||||||
|
const glm::vec3 offset = contacts[i].POI - contacts[j].POI;
|
||||||
|
nearest = glm::min(nearest, glm::dot(offset, offset));
|
||||||
|
}
|
||||||
|
if (nearest > farthestDistance) {
|
||||||
|
farthestDistance = nearest;
|
||||||
|
farthest = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::swap(contacts[kept], contacts[farthest]);
|
||||||
|
}
|
||||||
|
|
||||||
|
contacts.erase(contacts.begin() + PHYSICS_MAX_MESH_CONTACTS, contacts.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two meshes can tangle: a hand between two faces gets contacts pushing opposite ways, which cancel
|
||||||
|
// and pin it in place. So the whole pair shares one normal that always pushes them apart.
|
||||||
|
static void UnifyContactNormals(const p* a, const p* b, std::vector<Collision>& contacts)
|
||||||
|
{
|
||||||
|
glm::vec3 summed(0.0f);
|
||||||
|
float totalDepth = 0.0f;
|
||||||
|
for (const Collision& contact : contacts) {
|
||||||
|
summed += contact.CN * contact.overlap;
|
||||||
|
totalDepth += contact.overlap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Faces that mostly disagree say nothing useful, so fall back to the line between the two bodies
|
||||||
|
const glm::vec3 apart = a->pointer->t.GetTranslation() - b->pointer->t.GetTranslation();
|
||||||
|
glm::vec3 normal = summed;
|
||||||
|
if (glm::length(summed) < totalDepth * 0.3f)
|
||||||
|
normal = apart;
|
||||||
|
if (glm::length(normal) < 1e-6f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
normal = glm::normalize(normal);
|
||||||
|
if (glm::dot(normal, apart) < 0.0f)
|
||||||
|
normal = -normal;
|
||||||
|
|
||||||
|
for (Collision& contact : contacts) {
|
||||||
|
contact.CN = normal;
|
||||||
|
contact.frictionScale = PHYSICS_MESH_PAIR_FRICTION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Physics::CollideTriangles(p* a, p* b)
|
||||||
|
{
|
||||||
|
const CollisionMesh& meshA = a->collisionMesh ? *a->collisionMesh : CollisionMesh::UnitBox();
|
||||||
|
const CollisionMesh& meshB = b->collisionMesh ? *b->collisionMesh : CollisionMesh::UnitBox();
|
||||||
|
|
||||||
|
// Loop over the smaller mesh and search the larger one
|
||||||
|
const bool queryIsA = meshA.GetTriangles().size() <= meshB.GetTriangles().size();
|
||||||
|
const CollisionMesh& queryMesh = queryIsA ? meshA : meshB;
|
||||||
|
const CollisionMesh& targetMesh = queryIsA ? meshB : meshA;
|
||||||
|
|
||||||
|
const glm::mat4 queryToWorld = (queryIsA ? a : b)->pointer->t.GetMatrix();
|
||||||
|
const glm::mat4 targetToWorld = (queryIsA ? b : a)->pointer->t.GetMatrix();
|
||||||
|
const glm::mat4 queryToTarget = glm::inverse(targetToWorld) * queryToWorld;
|
||||||
|
|
||||||
|
// Box faces are the reliable surface. Between two meshes, b's faces are used.
|
||||||
|
const bool surfaceIsA = !a->collisionMesh && b->collisionMesh;
|
||||||
|
const bool surfaceIsQuery = surfaceIsA == queryIsA;
|
||||||
|
|
||||||
|
meshContacts.clear();
|
||||||
|
|
||||||
|
for (const Triangle& queryTriangle : queryMesh.GetTriangles()) {
|
||||||
|
const Triangle queryInTarget = TransformTriangle(queryTriangle, queryToTarget);
|
||||||
|
|
||||||
|
meshCandidates.clear();
|
||||||
|
targetMesh.Query(queryInTarget, meshCandidates);
|
||||||
|
|
||||||
|
for (int index : meshCandidates) {
|
||||||
|
const Triangle& targetTriangle = targetMesh.GetTriangles()[index];
|
||||||
|
if (!TriangleInTriangle(queryInTarget, targetTriangle))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const Triangle queryWorld = TransformTriangle(queryTriangle, queryToWorld);
|
||||||
|
const Triangle targetWorld = TransformTriangle(targetTriangle, targetToWorld);
|
||||||
|
const Triangle& surface = surfaceIsQuery ? queryWorld : targetWorld;
|
||||||
|
const Triangle& other = surfaceIsQuery ? targetWorld : queryWorld;
|
||||||
|
|
||||||
|
const glm::vec3 faceNormal = glm::cross(surface.b - surface.a, surface.c - surface.a);
|
||||||
|
const float faceLength = glm::length(faceNormal);
|
||||||
|
if (faceLength < 1e-8f)
|
||||||
|
continue;
|
||||||
|
const glm::vec3 normal = faceNormal / faceLength;
|
||||||
|
|
||||||
|
glm::vec3 deepestPoint = other.a;
|
||||||
|
float deepestDistance = glm::dot(other.a - surface.a, normal);
|
||||||
|
for (const glm::vec3& corner : { other.b, other.c }) {
|
||||||
|
const float distance = glm::dot(corner - surface.a, normal);
|
||||||
|
if (distance < deepestDistance) {
|
||||||
|
deepestDistance = distance;
|
||||||
|
deepestPoint = corner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (deepestDistance >= 0.0f)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// CN points from b to a, the same way TInT does
|
||||||
|
Collision contact(a, b);
|
||||||
|
contact.POI = deepestPoint;
|
||||||
|
contact.CN = surfaceIsA ? -normal : normal;
|
||||||
|
contact.overlap = glm::min(-deepestDistance, PHYSICS_MAX_MESH_DEPTH);
|
||||||
|
meshContacts.push_back(contact);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (meshContacts.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (a->frozen) WakeGroup(a->collisionGroup);
|
||||||
|
if (b->frozen) WakeGroup(b->collisionGroup);
|
||||||
|
|
||||||
|
if (a->collisionMesh && b->collisionMesh)
|
||||||
|
UnifyContactNormals(a, b, meshContacts);
|
||||||
|
|
||||||
|
KeepSpreadContacts(meshContacts);
|
||||||
|
for (Collision& contact : meshContacts) {
|
||||||
|
contact.correctionWeight = 1.0f / (float)meshContacts.size();
|
||||||
|
collisions.push_back(contact);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Physics::WakeGroup(int group)
|
||||||
|
{
|
||||||
|
for (p* body : objects) {
|
||||||
|
if (body->collisionGroup == group)
|
||||||
|
body->frozen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Joints
|
||||||
|
// Everything that only depends on where the bodies are gets worked out once in
|
||||||
|
// Prepare. Solve then just pushes velocities around, and runs many times a step.
|
||||||
|
// ============================================================================
|
||||||
|
static glm::mat3 SkewMatrix(const glm::vec3& v)
|
||||||
|
{
|
||||||
|
// Skew * x = cross(v, x). glm matrices are built from columns.
|
||||||
|
return glm::mat3(
|
||||||
|
glm::vec3(0.0f, v.z, -v.y),
|
||||||
|
glm::vec3(-v.z, 0.0f, v.x),
|
||||||
|
glm::vec3(v.y, -v.x, 0.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asks for the child's spin relative to the parent around axis to close error
|
||||||
|
static void AddAngularRow(BallJoint& joint, const glm::vec3& axis, float error, float dt, bool oneSided)
|
||||||
|
{
|
||||||
|
AngularRow& row = joint.rows[joint.rowCount++];
|
||||||
|
row.axis = axis;
|
||||||
|
row.oneSided = oneSided;
|
||||||
|
row.targetSpeed = glm::clamp(error * (PHYSICS_JOINT_BIAS / dt),
|
||||||
|
-PHYSICS_JOINT_MAX_BIAS_SPEED, PHYSICS_JOINT_MAX_BIAS_SPEED);
|
||||||
|
row.inverseDenom = 1.0f / (glm::dot(axis, joint.parent->invInertia * axis) + glm::dot(axis, joint.child->invInertia * axis));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Physics::PrepareJoint(BallJoint& joint, float dt)
|
||||||
|
{
|
||||||
|
p* a = joint.parent;
|
||||||
|
p* b = joint.child;
|
||||||
|
if (a->frozen && b->frozen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const glm::quat orientA = a->pointer->t.GetRotationQuaternion();
|
||||||
|
const glm::quat orientB = b->pointer->t.GetRotationQuaternion();
|
||||||
|
|
||||||
|
const glm::vec3 rA = orientA * joint.anchorParent;
|
||||||
|
const glm::vec3 rB = orientB * joint.anchorChild;
|
||||||
|
joint.worldAnchorParent = rA;
|
||||||
|
joint.worldAnchorChild = rB;
|
||||||
|
|
||||||
|
const glm::mat3 skewA = SkewMatrix(rA);
|
||||||
|
const glm::mat3 skewB = SkewMatrix(rB);
|
||||||
|
const glm::mat3 K = glm::mat3(a->GetInvMass() + b->GetInvMass())
|
||||||
|
- skewA * a->invInertia * skewA - skewB * b->invInertia * skewB;
|
||||||
|
joint.inverseK = glm::inverse(K);
|
||||||
|
|
||||||
|
const glm::vec3 drift = (b->pointer->t.GetTranslation() + rB) - (a->pointer->t.GetTranslation() + rA);
|
||||||
|
joint.bias = drift * (PHYSICS_JOINT_BIAS / dt);
|
||||||
|
if (glm::length(joint.bias) > PHYSICS_JOINT_MAX_BIAS_SPEED)
|
||||||
|
joint.bias = glm::normalize(joint.bias) * PHYSICS_JOINT_MAX_BIAS_SPEED;
|
||||||
|
|
||||||
|
joint.rowCount = 0;
|
||||||
|
|
||||||
|
// ---- Hinge ----
|
||||||
|
if (joint.isHinge) {
|
||||||
|
const glm::vec3 hingeA = orientA * joint.hingeAxisParent;
|
||||||
|
const glm::vec3 hingeB = orientB * joint.hingeAxisChild;
|
||||||
|
|
||||||
|
// Keep both hinge axes lined up, leaving only spin around the hinge free
|
||||||
|
const glm::vec3 misalignment = glm::cross(hingeB, hingeA);
|
||||||
|
const glm::vec3 side = glm::normalize(orientA * joint.hingeRefParent);
|
||||||
|
const glm::vec3 otherSide = glm::cross(hingeA, side);
|
||||||
|
AddAngularRow(joint, side, glm::dot(misalignment, side), dt, false);
|
||||||
|
AddAngularRow(joint, otherSide, glm::dot(misalignment, otherSide), dt, false);
|
||||||
|
|
||||||
|
const glm::vec3 refA = side;
|
||||||
|
const glm::vec3 refB = orientB * joint.hingeRefChild;
|
||||||
|
const float bend = std::atan2(glm::dot(glm::cross(refA, refB), hingeA), glm::dot(refA, refB));
|
||||||
|
|
||||||
|
if (bend < joint.minBend)
|
||||||
|
AddAngularRow(joint, hingeA, joint.minBend - bend, dt, true);
|
||||||
|
else if (bend > joint.maxBend)
|
||||||
|
AddAngularRow(joint, -hingeA, bend - joint.maxBend, dt, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Swing cone ----
|
||||||
|
const glm::vec3 axisA = orientA * joint.axisParent;
|
||||||
|
const glm::vec3 axisB = orientB * joint.axisChild;
|
||||||
|
const float angle = glm::acos(glm::clamp(glm::dot(axisA, axisB), -1.0f, 1.0f));
|
||||||
|
if (angle > joint.maxAngle) {
|
||||||
|
const glm::vec3 swingAxis = glm::cross(axisB, axisA);
|
||||||
|
const float swingLength = glm::length(swingAxis);
|
||||||
|
if (swingLength > 1e-5f)
|
||||||
|
AddAngularRow(joint, swingAxis / swingLength, angle - joint.maxAngle, dt, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Twist limit ----
|
||||||
|
const glm::quat relative = glm::conjugate(orientA) * orientB;
|
||||||
|
const float twistAlongAxis = glm::dot(glm::vec3(relative.x, relative.y, relative.z), joint.axisChild);
|
||||||
|
float twist = 2.0f * std::atan2(twistAlongAxis, relative.w);
|
||||||
|
const float pi = glm::pi<float>();
|
||||||
|
if (twist > pi) twist -= 2.0f * pi;
|
||||||
|
else if (twist < -pi) twist += 2.0f * pi;
|
||||||
|
|
||||||
|
const glm::vec3 twistAxis = orientA * joint.axisChild;
|
||||||
|
if (twist > joint.maxTwist)
|
||||||
|
AddAngularRow(joint, -twistAxis, twist - joint.maxTwist, dt, true);
|
||||||
|
else if (twist < -joint.maxTwist)
|
||||||
|
AddAngularRow(joint, twistAxis, -joint.maxTwist - twist, dt, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Physics::SolveJoint(BallJoint& joint)
|
||||||
|
{
|
||||||
|
p* a = joint.parent;
|
||||||
|
p* b = joint.child;
|
||||||
|
if (a->frozen && b->frozen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const glm::vec3& rA = joint.worldAnchorParent;
|
||||||
|
const glm::vec3& rB = joint.worldAnchorChild;
|
||||||
|
|
||||||
|
const glm::vec3 relVel = b->linearvelocity + glm::cross(b->angularvelocity, rB)
|
||||||
|
- a->linearvelocity - glm::cross(a->angularvelocity, rA);
|
||||||
|
|
||||||
|
const glm::vec3 impulse = joint.inverseK * (-relVel - joint.bias);
|
||||||
|
a->linearvelocity -= a->GetInvMass() * impulse;
|
||||||
|
a->angularvelocity -= a->invInertia * glm::cross(rA, impulse);
|
||||||
|
b->linearvelocity += b->GetInvMass() * impulse;
|
||||||
|
b->angularvelocity += b->invInertia * glm::cross(rB, impulse);
|
||||||
|
|
||||||
|
for (int i = 0; i < joint.rowCount; i++) {
|
||||||
|
const AngularRow& row = joint.rows[i];
|
||||||
|
const float speed = glm::dot(b->angularvelocity - a->angularvelocity, row.axis);
|
||||||
|
const float rowImpulse = (row.targetSpeed - speed) * row.inverseDenom;
|
||||||
|
if (row.oneSided && rowImpulse <= 0.0f)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
a->angularvelocity -= a->invInertia * row.axis * rowImpulse;
|
||||||
|
b->angularvelocity += b->invInertia * row.axis * rowImpulse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Joints have no friction of their own, so without this limbs flail for a long time
|
||||||
|
void Physics::DampJoint(BallJoint& joint, float dt)
|
||||||
|
{
|
||||||
|
p* a = joint.parent;
|
||||||
|
p* b = joint.child;
|
||||||
|
if (a->frozen && b->frozen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const glm::vec3 relative = b->angularvelocity - a->angularvelocity;
|
||||||
|
const glm::vec3 removed = relative * (1.0f - std::exp(-PHYSICS_JOINT_DAMPING * dt)) * 0.5f;
|
||||||
|
a->angularvelocity += removed;
|
||||||
|
b->angularvelocity -= removed;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Resolve
|
// Resolve
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
void Physics::Resolve(Collision* c)
|
void Physics::Resolve(Collision* c, bool firstPass)
|
||||||
{
|
{
|
||||||
p* a = c->ObjA;
|
p* a = c->ObjA;
|
||||||
p* b = c->ObjB;
|
p* b = c->ObjB;
|
||||||
@@ -102,8 +510,8 @@ void Physics::Resolve(Collision* c)
|
|||||||
const glm::vec3 rA = c->POI - a->pointer->t.GetTranslation();
|
const glm::vec3 rA = c->POI - a->pointer->t.GetTranslation();
|
||||||
const glm::vec3 rB = c->POI - b->pointer->t.GetTranslation();
|
const glm::vec3 rB = c->POI - b->pointer->t.GetTranslation();
|
||||||
|
|
||||||
const glm::mat3 invIA = ComputeInvInertiaTensor(a);
|
const glm::mat3& invIA = a->invInertia;
|
||||||
const glm::mat3 invIB = ComputeInvInertiaTensor(b);
|
const glm::mat3& invIB = b->invInertia;
|
||||||
|
|
||||||
const float invMassA = a->GetInvMass();
|
const float invMassA = a->GetInvMass();
|
||||||
const float invMassB = b->GetInvMass();
|
const float invMassB = b->GetInvMass();
|
||||||
@@ -115,7 +523,7 @@ void Physics::Resolve(Collision* c)
|
|||||||
const float relVelN = glm::dot(relVel, n);
|
const float relVelN = glm::dot(relVel, n);
|
||||||
if (relVelN > 0.0f) return;
|
if (relVelN > 0.0f) return;
|
||||||
|
|
||||||
const float e = (glm::abs(relVelN) < PHYSICS_RESTITUTION_THRESHOLD)
|
const float e = (!firstPass || glm::abs(relVelN) < PHYSICS_RESTITUTION_THRESHOLD)
|
||||||
? 0.0f
|
? 0.0f
|
||||||
: glm::min(a->restitution, b->restitution);
|
: glm::min(a->restitution, b->restitution);
|
||||||
|
|
||||||
@@ -148,7 +556,7 @@ void Physics::Resolve(Collision* c)
|
|||||||
const float denomT = invMassA + invMassB + angDenom(invIA, rA, tangent) + angDenom(invIB, rB, tangent);
|
const float denomT = invMassA + invMassB + angDenom(invIA, rA, tangent) + angDenom(invIB, rB, tangent);
|
||||||
float jT = -glm::dot(relVel, tangent) / denomT;
|
float jT = -glm::dot(relVel, tangent) / denomT;
|
||||||
|
|
||||||
const float mu = (a->friction + b->friction) * 0.5f;
|
const float mu = (a->friction + b->friction) * 0.5f * c->frictionScale;
|
||||||
jT = glm::clamp(jT, -mu * jN, mu * jN);
|
jT = glm::clamp(jT, -mu * jN, mu * jN);
|
||||||
|
|
||||||
const glm::vec3 impulseT = jT * tangent;
|
const glm::vec3 impulseT = jT * tangent;
|
||||||
@@ -165,9 +573,13 @@ void Physics::Resolve(Collision* c)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---- Baumgarte positional correction ----
|
// ---- Baumgarte positional correction ----
|
||||||
|
if (!firstPass)
|
||||||
|
return;
|
||||||
|
|
||||||
const float correction = glm::max(c->overlap - PHYSICS_BAUMGARTE_SLOP, 0.0f)
|
const float correction = glm::max(c->overlap - PHYSICS_BAUMGARTE_SLOP, 0.0f)
|
||||||
/ (invMassA + invMassB)
|
/ (invMassA + invMassB)
|
||||||
* PHYSICS_BAUMGARTE_FACTOR;
|
* PHYSICS_BAUMGARTE_FACTOR
|
||||||
|
* c->correctionWeight;
|
||||||
const glm::vec3 corrVec = correction * n;
|
const glm::vec3 corrVec = correction * n;
|
||||||
if (a->velocity) a->pointer->t.TranslateBy(invMassA * corrVec);
|
if (a->velocity) a->pointer->t.TranslateBy(invMassA * corrVec);
|
||||||
if (b->velocity) b->pointer->t.TranslateBy(-invMassB * corrVec);
|
if (b->velocity) b->pointer->t.TranslateBy(-invMassB * corrVec);
|
||||||
|
|||||||
@@ -12,11 +12,13 @@
|
|||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
#include <glm/gtc/type_ptr.hpp>
|
#include <glm/gtc/type_ptr.hpp>
|
||||||
|
#include <glm/gtc/quaternion.hpp>
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "Engine.h"
|
#include "Engine.h"
|
||||||
#include "Camera.h"
|
#include "Camera.h"
|
||||||
#include "t.h"
|
#include "t.h"
|
||||||
#include "tFunctions.h"
|
#include "tFunctions.h"
|
||||||
|
#include "CollisionMesh.h"
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Tuneable constants
|
// Tuneable constants
|
||||||
@@ -29,6 +31,13 @@ static constexpr float PHYSICS_SLEEP_LINEAR_SQ = 0.01f * 0.01f;
|
|||||||
static constexpr float PHYSICS_SLEEP_ANGULAR_SQ = 0.01f * 0.01f;
|
static constexpr float PHYSICS_SLEEP_ANGULAR_SQ = 0.01f * 0.01f;
|
||||||
static constexpr float PHYSICS_SLEEP_TIME_THRESHOLD = 0.5f;
|
static constexpr float PHYSICS_SLEEP_TIME_THRESHOLD = 0.5f;
|
||||||
static constexpr float PHYSICS_RESTITUTION_THRESHOLD = 1.0f;
|
static constexpr float PHYSICS_RESTITUTION_THRESHOLD = 1.0f;
|
||||||
|
static constexpr int PHYSICS_JOINT_ITERATIONS = 12;
|
||||||
|
static constexpr float PHYSICS_JOINT_BIAS = 0.2f;
|
||||||
|
static constexpr float PHYSICS_JOINT_MAX_BIAS_SPEED = 5.0f;
|
||||||
|
static constexpr float PHYSICS_JOINT_DAMPING = 5.0f;
|
||||||
|
static constexpr int PHYSICS_MAX_MESH_CONTACTS = 4;
|
||||||
|
static constexpr float PHYSICS_MAX_MESH_DEPTH = 0.1f;
|
||||||
|
static constexpr float PHYSICS_MESH_PAIR_FRICTION = 0.25f; // two meshes catch on each other, so they slide easier
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Physics body
|
// Physics body
|
||||||
@@ -65,15 +74,23 @@ public:
|
|||||||
bool velocity = true;
|
bool velocity = true;
|
||||||
bool isSleeping = false;
|
bool isSleeping = false;
|
||||||
bool lockRotation = false;
|
bool lockRotation = false;
|
||||||
|
bool frozen = false; // parked until something touches its group
|
||||||
|
|
||||||
|
// Set this to collide triangle by triangle, otherwise the body is its t's box
|
||||||
|
const CollisionMesh* collisionMesh = nullptr;
|
||||||
|
int collisionGroup = 0; // non zero groups don't collide with themselves
|
||||||
|
|
||||||
float mass = 1.0f;
|
float mass = 1.0f;
|
||||||
float restitution = 0.2f;
|
float restitution = 0.2f;
|
||||||
float friction = 0.5f;
|
float friction = 0.5f;
|
||||||
|
float maxSpeed = 0.0f; // 0 = no limit
|
||||||
|
float maxAngularSpeed = 0.0f;
|
||||||
|
|
||||||
glm::vec3 linearvelocity = { 0.0f, 0.0f, 0.0f };
|
glm::vec3 linearvelocity = { 0.0f, 0.0f, 0.0f };
|
||||||
glm::vec3 angularvelocity = { 0.0f, 0.0f, 0.0f };
|
glm::vec3 angularvelocity = { 0.0f, 0.0f, 0.0f };
|
||||||
glm::vec3 force = { 0.0f, 0.0f, 0.0f };
|
glm::vec3 force = { 0.0f, 0.0f, 0.0f };
|
||||||
glm::vec3 torque = { 0.0f, 0.0f, 0.0f };
|
glm::vec3 torque = { 0.0f, 0.0f, 0.0f };
|
||||||
|
glm::mat3 invInertia = glm::mat3(0.0f); // world space, refreshed at the start of each step
|
||||||
|
|
||||||
float sleepTimer = 0.0f;
|
float sleepTimer = 0.0f;
|
||||||
};
|
};
|
||||||
@@ -85,23 +102,87 @@ struct Collision {
|
|||||||
glm::vec3 POI;
|
glm::vec3 POI;
|
||||||
glm::vec3 CN;
|
glm::vec3 CN;
|
||||||
float overlap = 0.0f;
|
float overlap = 0.0f;
|
||||||
|
float correctionWeight = 1.0f; // shares positional correction between a pair's contacts
|
||||||
|
float frictionScale = 1.0f;
|
||||||
p* ObjA = nullptr;
|
p* ObjA = nullptr;
|
||||||
p* ObjB = nullptr;
|
p* ObjB = nullptr;
|
||||||
|
|
||||||
Collision(p* a, p* b) : ObjA(a), ObjB(b) {}
|
Collision(p* a, p* b) : ObjA(a), ObjB(b) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// One angular constraint on a joint, worked out once per step
|
||||||
|
// ============================================================================
|
||||||
|
struct AngularRow {
|
||||||
|
glm::vec3 axis = glm::vec3(0.0f);
|
||||||
|
float targetSpeed = 0.0f;
|
||||||
|
float inverseDenom = 0.0f;
|
||||||
|
bool oneSided = false; // a limit only pushes back, the rest also hold the bodies together
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Ball joint with a swing cone and a twist limit, or a one axis hinge
|
||||||
|
// Anchors and axes are local to each body: rotated, but not scaled.
|
||||||
|
// The twist limit assumes both bodies started out unrotated.
|
||||||
|
// ============================================================================
|
||||||
|
struct BallJoint {
|
||||||
|
p* parent = nullptr;
|
||||||
|
p* child = nullptr;
|
||||||
|
glm::vec3 anchorParent = glm::vec3(0.0f);
|
||||||
|
glm::vec3 anchorChild = glm::vec3(0.0f);
|
||||||
|
glm::vec3 axisParent = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||||
|
glm::vec3 axisChild = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||||
|
float maxAngle = 0.0f; // radians between the two axes
|
||||||
|
float maxTwist = 0.0f; // radians either way around axisChild
|
||||||
|
|
||||||
|
// Hinges ignore the limits above and bend around one axis, like an elbow.
|
||||||
|
// The bend is the angle from hingeRefParent to hingeRefChild around the hinge axis.
|
||||||
|
bool isHinge = false;
|
||||||
|
glm::vec3 hingeAxisParent = glm::vec3(1.0f, 0.0f, 0.0f);
|
||||||
|
glm::vec3 hingeAxisChild = glm::vec3(1.0f, 0.0f, 0.0f);
|
||||||
|
glm::vec3 hingeRefParent = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||||
|
glm::vec3 hingeRefChild = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||||
|
float minBend = 0.0f;
|
||||||
|
float maxBend = 0.0f;
|
||||||
|
|
||||||
|
// Filled in by PrepareJoint, these can't change during a step's iterations
|
||||||
|
glm::vec3 worldAnchorParent = glm::vec3(0.0f);
|
||||||
|
glm::vec3 worldAnchorChild = glm::vec3(0.0f);
|
||||||
|
glm::vec3 bias = glm::vec3(0.0f);
|
||||||
|
glm::mat3 inverseK = glm::mat3(1.0f);
|
||||||
|
AngularRow rows[3];
|
||||||
|
int rowCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BroadPhaseEntry {
|
||||||
|
p* body = nullptr;
|
||||||
|
BoundingBox bounds;
|
||||||
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Physics world
|
// Physics world
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
class Physics {
|
class Physics {
|
||||||
private:
|
private:
|
||||||
std::vector<p*> objects;
|
std::vector<p*> objects;
|
||||||
|
std::vector<BallJoint*> joints;
|
||||||
glm::vec3 gravity = glm::vec3(0.0f, -9.81f, 0.0f);
|
glm::vec3 gravity = glm::vec3(0.0f, -9.81f, 0.0f);
|
||||||
|
|
||||||
|
// Kept between steps so they don't get reallocated
|
||||||
|
std::vector<BroadPhaseEntry> broadPhase;
|
||||||
|
std::vector<Collision> collisions;
|
||||||
|
std::vector<Collision> meshContacts;
|
||||||
|
std::vector<int> meshCandidates;
|
||||||
|
|
||||||
static glm::mat3 ComputeInvInertiaTensor(const p* body);
|
static glm::mat3 ComputeInvInertiaTensor(const p* body);
|
||||||
static bool UpdateSleep(p* body, float dt);
|
static bool UpdateSleep(p* body, float dt);
|
||||||
void Resolve(Collision* c);
|
void Resolve(Collision* c, bool firstPass = true);
|
||||||
|
void FindCollisions();
|
||||||
|
void CollideTriangles(p* a, p* b);
|
||||||
|
void WakeGroup(int group);
|
||||||
|
void PrepareJoint(BallJoint& joint, float dt);
|
||||||
|
void SolveJoint(BallJoint& joint);
|
||||||
|
void DampJoint(BallJoint& joint, float dt);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Physics() {}
|
Physics() {}
|
||||||
@@ -111,6 +192,7 @@ public:
|
|||||||
|
|
||||||
int GetObjectAmount() const noexcept { return (int)objects.size(); }
|
int GetObjectAmount() const noexcept { return (int)objects.size(); }
|
||||||
void AddObject(p* obj) { objects.push_back(obj); }
|
void AddObject(p* obj) { objects.push_back(obj); }
|
||||||
|
void AddJoint(BallJoint* joint) { joints.push_back(joint); }
|
||||||
const std::vector<p*>& GetObjects() const { return objects; }
|
const std::vector<p*>& GetObjects() const { return objects; }
|
||||||
|
|
||||||
p* PPointerOfObject(t_package* pointer) const {
|
p* PPointerOfObject(t_package* pointer) const {
|
||||||
@@ -135,37 +217,7 @@ public:
|
|||||||
|
|
||||||
void Step(float dt);
|
void Step(float dt);
|
||||||
|
|
||||||
void SolveCollisions(std::vector<Collision>& collisions) {
|
void ResolveCollisions(float dt);
|
||||||
for (Collision& col : collisions)
|
|
||||||
Resolve(&col);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ResolveCollisions(float dt) {
|
|
||||||
std::vector<Collision> collisions;
|
|
||||||
|
|
||||||
for (p* a : objects) {
|
|
||||||
if (!a->collision) continue;
|
|
||||||
for (p* b : objects) {
|
|
||||||
if (b <= a || !b->collision) continue;
|
|
||||||
if (!a->velocity && !b->velocity) continue;
|
|
||||||
if (a->isSleeping && b->isSleeping) continue;
|
|
||||||
|
|
||||||
if (TNearT(a->pointer->t, b->pointer->t) &&
|
|
||||||
BoundingBoxInBoundingBox(a->pointer->t.GetAABB(), b->pointer->t.GetAABB()))
|
|
||||||
{
|
|
||||||
std::optional<TInTInfo> e = TInT(a->pointer->t, b->pointer->t);
|
|
||||||
if (e.has_value()) {
|
|
||||||
Collision tp(a, b);
|
|
||||||
tp.POI = e.value().POI;
|
|
||||||
tp.CN = e.value().CN;
|
|
||||||
tp.overlap = e.value().overlap;
|
|
||||||
collisions.push_back(tp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SolveCollisions(collisions);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
+329
@@ -0,0 +1,329 @@
|
|||||||
|
#include "Ragdoll.h"
|
||||||
|
|
||||||
|
#include <cfloat>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "Animator.h"
|
||||||
|
#include "Character.h"
|
||||||
|
#include "CollisionMesh.h"
|
||||||
|
#include "Physics.h"
|
||||||
|
|
||||||
|
struct PartDefinition {
|
||||||
|
const char* bone;
|
||||||
|
const char* endBone; // set for hinges (elbows, knees): the far end of the part, used to find the bend
|
||||||
|
float mass;
|
||||||
|
float maxSwing; // degrees the part can swing around the joint, in any direction
|
||||||
|
float coneTilt; // degrees the swing cone leans forward, so hips reach further forward than back
|
||||||
|
float maxTwist; // degrees the part can twist along its own length, either way
|
||||||
|
float minBend; // hinge range in degrees, 0 = straight
|
||||||
|
float maxBend;
|
||||||
|
float bendDirection; // 1 = folds forward, -1 = folds backward. Only used if the pose starts straight
|
||||||
|
};
|
||||||
|
|
||||||
|
// The hand, foot, finger and neck bones ride along with the nearest part above them.
|
||||||
|
// The root part has to come first, and parents have to come before their children.
|
||||||
|
static const PartDefinition partDefinitions[] = {
|
||||||
|
// bone endBone mass swing tilt twist bend range direction
|
||||||
|
{ "Hip", nullptr, 14.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },
|
||||||
|
{ "UpperTorso", nullptr, 22.0f, 30.0f, 0.0f, 25.0f, 0.0f, 0.0f, 0.0f },
|
||||||
|
{ "Head", nullptr, 5.0f, 45.0f, 0.0f, 60.0f, 0.0f, 0.0f, 0.0f },
|
||||||
|
{ "L_UpperArm", nullptr, 3.0f, 110.0f, 0.0f, 60.0f, 0.0f, 0.0f, 0.0f },
|
||||||
|
{ "L_LowerArm", "L_Hand", 2.5f, 0.0f, 0.0f, 0.0f, 0.0f, 150.0f, 1.0f },
|
||||||
|
{ "R_UpperArm", nullptr, 3.0f, 110.0f, 0.0f, 60.0f, 0.0f, 0.0f, 0.0f },
|
||||||
|
{ "R_LowerArm", "R_Hand", 2.5f, 0.0f, 0.0f, 0.0f, 0.0f, 150.0f, 1.0f },
|
||||||
|
{ "L_UpperLeg", nullptr, 9.0f, 65.0f, 35.0f, 30.0f, 0.0f, 0.0f, 0.0f },
|
||||||
|
{ "L_LowerLeg", "L_Foot", 6.0f, 0.0f, 0.0f, 0.0f, 0.0f, 140.0f, -1.0f },
|
||||||
|
{ "R_UpperLeg", nullptr, 9.0f, 65.0f, 35.0f, 30.0f, 0.0f, 0.0f, 0.0f },
|
||||||
|
{ "R_LowerLeg", "R_Foot", 6.0f, 0.0f, 0.0f, 0.0f, 0.0f, 140.0f, -1.0f },
|
||||||
|
};
|
||||||
|
static constexpr int PART_COUNT = sizeof(partDefinitions) / sizeof(partDefinitions[0]);
|
||||||
|
|
||||||
|
static constexpr float PART_MIN_SIZE = 0.05f;
|
||||||
|
static constexpr float PART_MAX_SPEED = 30.0f;
|
||||||
|
static constexpr float PART_MAX_ANGULAR_SPEED = 30.0f;
|
||||||
|
static constexpr float SETTLE_SPEED = 0.2f;
|
||||||
|
static constexpr float SETTLE_ANGULAR_SPEED = 0.4f;
|
||||||
|
static constexpr float SETTLE_TIME = 0.6f;
|
||||||
|
static constexpr float FALL_LIMIT = -20.0f;
|
||||||
|
static constexpr float HINGE_MIN_POSE_BEND = 0.17f; // sin of 10 degrees, below this the pose counts as straight
|
||||||
|
|
||||||
|
static const glm::quat IDENTITY_ROTATION(1.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
|
||||||
|
// Walks the skeleton, handing every bone to the part it hangs from and recording each part's parent
|
||||||
|
static void AssignBones(const AssimpNodeData& node, int currentPart, const Rig& rig,
|
||||||
|
std::vector<int>& boneParts, int (&partBones)[PART_COUNT], int (&partParents)[PART_COUNT])
|
||||||
|
{
|
||||||
|
const auto info = rig.m_BoneInfoMap.find(node.name);
|
||||||
|
|
||||||
|
for (int i = 0; i < PART_COUNT; i++) {
|
||||||
|
if (node.name != partDefinitions[i].bone || info == rig.m_BoneInfoMap.end())
|
||||||
|
continue;
|
||||||
|
partBones[i] = info->second.id;
|
||||||
|
partParents[i] = i == 0 ? -1 : currentPart;
|
||||||
|
currentPart = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info != rig.m_BoneInfoMap.end() && info->second.id >= 0 && info->second.id < (int)boneParts.size())
|
||||||
|
boneParts[info->second.id] = currentPart;
|
||||||
|
|
||||||
|
for (const AssimpNodeData& child : node.children)
|
||||||
|
AssignBones(child, currentPart, rig, boneParts, partBones, partParents);
|
||||||
|
}
|
||||||
|
|
||||||
|
static glm::vec3 SkinVertex(const RigVertex& vertex, const std::vector<glm::mat4>& bones)
|
||||||
|
{
|
||||||
|
glm::vec4 position(0.0f);
|
||||||
|
float totalWeight = 0.0f;
|
||||||
|
for (int i = 0; i < MAX_BONE_INFLUENCE; i++) {
|
||||||
|
const int bone = vertex.m_BoneIDs[i];
|
||||||
|
if (bone < 0 || bone >= (int)bones.size())
|
||||||
|
continue;
|
||||||
|
position += bones[bone] * glm::vec4(vertex.Position, 1.0f) * vertex.m_Weights[i];
|
||||||
|
totalWeight += vertex.m_Weights[i];
|
||||||
|
}
|
||||||
|
return totalWeight > 0.0f ? glm::vec3(position) / totalWeight : vertex.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ragdoll::Ragdoll(CharacterBody& characterBody, const glm::mat4& rigMatrix, Physics& physics)
|
||||||
|
{
|
||||||
|
static int nextGroup = 1;
|
||||||
|
group = nextGroup++;
|
||||||
|
|
||||||
|
Rig* rig = characterBody.rig;
|
||||||
|
Animator* animator = characterBody.animator;
|
||||||
|
if (!rig || !animator || !animator->m_CurrentAnimation) {
|
||||||
|
std::cout << "RAGDOLL NEEDS A RIG AND AN ANIMATION\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bone matrices aren't filled in until the animator has stepped once
|
||||||
|
characterBody.Step(0.001f);
|
||||||
|
const std::vector<glm::mat4> poseBones = characterBody.GetBlendedBoneMatrices();
|
||||||
|
|
||||||
|
int partBones[PART_COUNT];
|
||||||
|
int partParents[PART_COUNT];
|
||||||
|
for (int i = 0; i < PART_COUNT; i++) {
|
||||||
|
partBones[i] = -1;
|
||||||
|
partParents[i] = -1;
|
||||||
|
}
|
||||||
|
boneParts.assign(poseBones.size(), 0);
|
||||||
|
AssignBones(animator->m_CurrentAnimation->GetRootNode(), 0, *rig, boneParts, partBones, partParents);
|
||||||
|
|
||||||
|
restBones.resize(poseBones.size());
|
||||||
|
for (int i = 0; i < (int)poseBones.size(); i++)
|
||||||
|
restBones[i] = rigMatrix * poseBones[i];
|
||||||
|
|
||||||
|
// Each triangle goes to whichever part most of its skin weight belongs to
|
||||||
|
std::vector<Triangle> partTriangles[PART_COUNT];
|
||||||
|
for (RigMesh* mesh : rig->meshes) {
|
||||||
|
for (int i = 0; i + 2 < (int)mesh->indices.size(); i += 3) {
|
||||||
|
const RigVertex* corners[3] = {
|
||||||
|
&mesh->vertices[mesh->indices[i]],
|
||||||
|
&mesh->vertices[mesh->indices[i + 1]],
|
||||||
|
&mesh->vertices[mesh->indices[i + 2]]
|
||||||
|
};
|
||||||
|
|
||||||
|
float partWeights[PART_COUNT] = {};
|
||||||
|
for (const RigVertex* corner : corners) {
|
||||||
|
for (int j = 0; j < MAX_BONE_INFLUENCE; j++) {
|
||||||
|
const int bone = corner->m_BoneIDs[j];
|
||||||
|
if (bone >= 0 && bone < (int)boneParts.size())
|
||||||
|
partWeights[boneParts[bone]] += corner->m_Weights[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int bestPart = 0;
|
||||||
|
for (int j = 1; j < PART_COUNT; j++) {
|
||||||
|
if (partWeights[j] > partWeights[bestPart])
|
||||||
|
bestPart = j;
|
||||||
|
}
|
||||||
|
|
||||||
|
partTriangles[bestPart].push_back({
|
||||||
|
glm::vec3(rigMatrix * glm::vec4(SkinVertex(*corners[0], poseBones), 1.0f)),
|
||||||
|
glm::vec3(rigMatrix * glm::vec4(SkinVertex(*corners[1], poseBones), 1.0f)),
|
||||||
|
glm::vec3(rigMatrix * glm::vec4(SkinVertex(*corners[2], poseBones), 1.0f))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parts start axis aligned around their triangles, in unit space like every other body
|
||||||
|
for (int i = 0; i < PART_COUNT; i++) {
|
||||||
|
glm::vec3 boundsMin(FLT_MAX);
|
||||||
|
glm::vec3 boundsMax(-FLT_MAX);
|
||||||
|
for (const Triangle& tri : partTriangles[i]) {
|
||||||
|
boundsMin = glm::min(boundsMin, glm::min(tri.a, glm::min(tri.b, tri.c)));
|
||||||
|
boundsMax = glm::max(boundsMax, glm::max(tri.a, glm::max(tri.b, tri.c)));
|
||||||
|
}
|
||||||
|
if (partTriangles[i].empty()) {
|
||||||
|
const int bone = partBones[i] >= 0 ? partBones[i] : 0;
|
||||||
|
boundsMin = boundsMax = glm::vec3(restBones[bone][3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const glm::vec3 center = (boundsMin + boundsMax) * 0.5f;
|
||||||
|
const glm::vec3 size = glm::max(boundsMax - boundsMin, glm::vec3(PART_MIN_SIZE));
|
||||||
|
|
||||||
|
std::vector<Triangle> unitTriangles;
|
||||||
|
unitTriangles.reserve(partTriangles[i].size());
|
||||||
|
for (const Triangle& tri : partTriangles[i])
|
||||||
|
unitTriangles.push_back({ (tri.a - center) / size, (tri.b - center) / size, (tri.c - center) / size });
|
||||||
|
|
||||||
|
Part* part = new Part();
|
||||||
|
part->restPosition = center;
|
||||||
|
part->transform.t.TranslateTo(center);
|
||||||
|
part->transform.t.ScaleTo(size);
|
||||||
|
part->transform.t.RotateToQuaternion(IDENTITY_ROTATION);
|
||||||
|
part->mesh = new CollisionMesh(unitTriangles);
|
||||||
|
|
||||||
|
part->body = new p(&part->transform);
|
||||||
|
part->body->mass = partDefinitions[i].mass;
|
||||||
|
part->body->restitution = 0.0f;
|
||||||
|
part->body->friction = 0.6f;
|
||||||
|
part->body->collisionMesh = part->mesh;
|
||||||
|
part->body->collisionGroup = group;
|
||||||
|
part->body->maxSpeed = PART_MAX_SPEED;
|
||||||
|
part->body->maxAngularSpeed = PART_MAX_ANGULAR_SPEED;
|
||||||
|
physics.AddObject(part->body);
|
||||||
|
|
||||||
|
parts.push_back(part);
|
||||||
|
restInverses.push_back(glm::inverse(part->transform.t.GetTranslationMatrix()));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto BonePosition = [&](const char* name, const glm::vec3& fallback) {
|
||||||
|
const auto info = rig->m_BoneInfoMap.find(name);
|
||||||
|
if (info == rig->m_BoneInfoMap.end())
|
||||||
|
return fallback;
|
||||||
|
return glm::vec3(restBones[info->second.id] * glm::inverse(info->second.offset) * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||||
|
};
|
||||||
|
|
||||||
|
// The rig faces its local -Z, same as Character::GetForwardDirection
|
||||||
|
const glm::vec3 forward = glm::normalize(glm::vec3(rigMatrix * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f)));
|
||||||
|
|
||||||
|
// Joints sit on the child bone's origin. Everything starts unrotated, so local = world here.
|
||||||
|
glm::vec3 jointAnchors[PART_COUNT];
|
||||||
|
jointAnchors[0] = parts[0]->restPosition;
|
||||||
|
|
||||||
|
for (int i = 1; i < PART_COUNT; i++) {
|
||||||
|
const PartDefinition& definition = partDefinitions[i];
|
||||||
|
if (partBones[i] < 0 || partParents[i] < 0) {
|
||||||
|
std::cout << "RAGDOLL BONE NOT FOUND: " << definition.bone << "\n";
|
||||||
|
jointAnchors[i] = parts[i]->restPosition;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const glm::vec3 anchor = BonePosition(definition.bone, parts[i]->restPosition);
|
||||||
|
jointAnchors[i] = anchor;
|
||||||
|
|
||||||
|
Part* parent = parts[partParents[i]];
|
||||||
|
Part* child = parts[i];
|
||||||
|
glm::vec3 axis = child->restPosition - anchor;
|
||||||
|
axis = glm::length(axis) > 0.0001f ? glm::normalize(axis) : glm::vec3(0.0f, -1.0f, 0.0f);
|
||||||
|
|
||||||
|
// Leaning only the parent's axis moves the cone without moving the part
|
||||||
|
glm::vec3 coneAxis = axis;
|
||||||
|
const glm::vec3 forwardOfAxis = forward - axis * glm::dot(forward, axis);
|
||||||
|
if (glm::length(forwardOfAxis) > 0.001f) {
|
||||||
|
const float tilt = glm::radians(definition.coneTilt);
|
||||||
|
coneAxis = glm::normalize(axis * std::cos(tilt) + glm::normalize(forwardOfAxis) * std::sin(tilt));
|
||||||
|
}
|
||||||
|
|
||||||
|
BallJoint* limits = new BallJoint();
|
||||||
|
limits->parent = parent->body;
|
||||||
|
limits->child = child->body;
|
||||||
|
limits->anchorParent = anchor - parent->restPosition;
|
||||||
|
limits->anchorChild = anchor - child->restPosition;
|
||||||
|
limits->axisParent = coneAxis;
|
||||||
|
limits->axisChild = axis;
|
||||||
|
limits->maxAngle = glm::radians(definition.maxSwing);
|
||||||
|
limits->maxTwist = glm::radians(definition.maxTwist);
|
||||||
|
|
||||||
|
if (definition.endBone) {
|
||||||
|
const glm::vec3 upperDirection = glm::normalize(anchor - jointAnchors[partParents[i]]);
|
||||||
|
const glm::vec3 lowerDirection = glm::normalize(BonePosition(definition.endBone, child->restPosition) - anchor);
|
||||||
|
|
||||||
|
// A bent pose shows the real hinge. A straight one has to go by which way the limb should fold.
|
||||||
|
glm::vec3 hinge = glm::cross(upperDirection, lowerDirection);
|
||||||
|
if (glm::length(hinge) < HINGE_MIN_POSE_BEND)
|
||||||
|
hinge = glm::cross(upperDirection, forward * definition.bendDirection);
|
||||||
|
hinge = glm::length(hinge) > 0.001f ? glm::normalize(hinge) : glm::vec3(1.0f, 0.0f, 0.0f);
|
||||||
|
|
||||||
|
limits->isHinge = true;
|
||||||
|
limits->hingeAxisParent = hinge;
|
||||||
|
limits->hingeAxisChild = hinge;
|
||||||
|
limits->hingeRefParent = glm::normalize(upperDirection - hinge * glm::dot(upperDirection, hinge));
|
||||||
|
limits->hingeRefChild = glm::normalize(lowerDirection - hinge * glm::dot(lowerDirection, hinge));
|
||||||
|
limits->minBend = glm::radians(definition.minBend);
|
||||||
|
limits->maxBend = glm::radians(definition.maxBend);
|
||||||
|
}
|
||||||
|
|
||||||
|
physics.AddJoint(limits);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A nudge so it topples instead of balancing perfectly on its feet
|
||||||
|
parts[1]->body->linearvelocity = glm::vec3(0.8f, 0.0f, 0.4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Ragdoll::Update(float dt)
|
||||||
|
{
|
||||||
|
if (parts.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (parts[0]->transform.t.GetTranslationY() < FALL_LIMIT) {
|
||||||
|
Reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parts[0]->body->frozen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool settled = true;
|
||||||
|
for (Part* part : parts) {
|
||||||
|
if (glm::length(part->body->linearvelocity) > SETTLE_SPEED ||
|
||||||
|
glm::length(part->body->angularvelocity) > SETTLE_ANGULAR_SPEED) {
|
||||||
|
settled = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settleTimer = settled ? settleTimer + dt : 0.0f;
|
||||||
|
if (settleTimer < SETTLE_TIME)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Freezing saves the triangle tests and stops resting contacts jittering. Contact wakes it again.
|
||||||
|
settleTimer = 0.0f;
|
||||||
|
for (Part* part : parts) {
|
||||||
|
part->body->frozen = true;
|
||||||
|
part->body->linearvelocity = glm::vec3(0.0f);
|
||||||
|
part->body->angularvelocity = glm::vec3(0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Ragdoll::Reset()
|
||||||
|
{
|
||||||
|
settleTimer = 0.0f;
|
||||||
|
for (Part* part : parts) {
|
||||||
|
part->transform.t.TranslateTo(part->restPosition);
|
||||||
|
part->transform.t.RotateToQuaternion(IDENTITY_ROTATION);
|
||||||
|
part->body->linearvelocity = glm::vec3(0.0f);
|
||||||
|
part->body->angularvelocity = glm::vec3(0.0f);
|
||||||
|
part->body->frozen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<glm::mat4> Ragdoll::GetBoneMatrices() const
|
||||||
|
{
|
||||||
|
std::vector<glm::mat4> bones = restBones;
|
||||||
|
if (parts.empty())
|
||||||
|
return bones;
|
||||||
|
|
||||||
|
std::vector<glm::mat4> partMoves;
|
||||||
|
partMoves.reserve(parts.size());
|
||||||
|
for (int i = 0; i < (int)parts.size(); i++) {
|
||||||
|
const t& transform = parts[i]->transform.t;
|
||||||
|
partMoves.push_back(transform.GetTranslationMatrix() * transform.GetRotationMatrix() * restInverses[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)bones.size(); i++)
|
||||||
|
bones[i] = partMoves[boneParts[i]] * restBones[i];
|
||||||
|
return bones;
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#ifndef RAGDOLL_CLASS
|
||||||
|
#define RAGDOLL_CLASS
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
#include "t.h"
|
||||||
|
|
||||||
|
class CharacterBody;
|
||||||
|
class CollisionMesh;
|
||||||
|
class Physics;
|
||||||
|
class p;
|
||||||
|
|
||||||
|
// Splits a character's rig into rigid bodies joined by ball joints.
|
||||||
|
// Each part collides using the rig triangles skinned mostly to its bones.
|
||||||
|
class Ragdoll {
|
||||||
|
public:
|
||||||
|
struct Part {
|
||||||
|
t_package transform; // scale is the part's bounding box, orientation starts at identity
|
||||||
|
p* body = nullptr;
|
||||||
|
CollisionMesh* mesh = nullptr;
|
||||||
|
glm::vec3 restPosition = glm::vec3(0.0f);
|
||||||
|
};
|
||||||
|
|
||||||
|
// rigMatrix is the model matrix the rig was being drawn with
|
||||||
|
Ragdoll(CharacterBody& characterBody, const glm::mat4& rigMatrix, Physics& physics);
|
||||||
|
|
||||||
|
// Call once a frame after the physics steps
|
||||||
|
void Update(float dt);
|
||||||
|
|
||||||
|
// Bone matrices in world space, draw the rig with an identity model matrix
|
||||||
|
std::vector<glm::mat4> GetBoneMatrices() const;
|
||||||
|
|
||||||
|
const std::vector<Part*>& GetParts() const { return parts; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
std::vector<Part*> parts;
|
||||||
|
std::vector<int> boneParts; // part index of each bone
|
||||||
|
std::vector<glm::mat4> restBones; // world space bone matrices at the moment of going limp
|
||||||
|
std::vector<glm::mat4> restInverses; // inverse of each part's starting transform
|
||||||
|
int group = 0;
|
||||||
|
float settleTimer = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
+520
-57
@@ -30,11 +30,54 @@ static const float hitboxLineVertices[] = {
|
|||||||
|
|
||||||
static void ConfigurePBR(Shader* shader, Lighting* lighting)
|
static void ConfigurePBR(Shader* shader, Lighting* lighting)
|
||||||
{
|
{
|
||||||
shader->Set3F("dirLight.direction", lighting->dirlighting.GetFrontVector());
|
shader->Set3F("dirLight.direction", lighting->sun.direction);
|
||||||
shader->Set3F("dirLight.color", glm::vec3(1.0f, 0.97f, 0.92f));
|
shader->Set3F("dirLight.color", lighting->sun.color);
|
||||||
shader->Set1F("dirLight.intensity", 3.0f);
|
shader->Set1F("dirLight.intensity", lighting->sun.intensity);
|
||||||
shader->Set1F("envIntensity", 0.8f);
|
shader->Set1F("envIntensity", 0.8f);
|
||||||
shader->SetInt("environmentMap", 2);
|
shader->SetInt("environmentMap", 2);
|
||||||
|
|
||||||
|
const int directionalCount = glm::min((int)lighting->directionalLights.size(), MAX_DIRECTIONAL_LIGHTS);
|
||||||
|
shader->SetInt("directionalLightCount", directionalCount);
|
||||||
|
for (int i = 0; i < directionalCount; i++) {
|
||||||
|
const DirectionalLight* light = lighting->directionalLights[i];
|
||||||
|
const std::string name = "directionalLights[" + std::to_string(i) + "].";
|
||||||
|
shader->Set3F((name + "direction").c_str(), light->direction);
|
||||||
|
shader->Set3F((name + "color").c_str(), light->color);
|
||||||
|
shader->Set1F((name + "intensity").c_str(), light->intensity);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int pointCount = glm::min((int)lighting->pointLights.size(), MAX_POINT_LIGHTS);
|
||||||
|
shader->SetInt("pointLightCount", pointCount);
|
||||||
|
for (int i = 0; i < pointCount; i++) {
|
||||||
|
const PointLight* light = lighting->pointLights[i];
|
||||||
|
const std::string name = "pointLights[" + std::to_string(i) + "].";
|
||||||
|
shader->Set3F((name + "position").c_str(), light->position);
|
||||||
|
shader->Set3F((name + "color").c_str(), light->color);
|
||||||
|
shader->Set1F((name + "intensity").c_str(), light->intensity);
|
||||||
|
shader->Set1F((name + "radius").c_str(), light->radius);
|
||||||
|
shader->SetInt((name + "shadowTile").c_str(), light->shadowTile);
|
||||||
|
}
|
||||||
|
|
||||||
|
shader->SetInt("localShadowMap", 10);
|
||||||
|
if (!lighting->shadowTileMatrices.empty()) {
|
||||||
|
shader->SetMat4Array("shadowTileMatrices", lighting->shadowTileMatrices.data(),
|
||||||
|
(int)lighting->shadowTileMatrices.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
const int spotCount = glm::min((int)lighting->spotLights.size(), MAX_SPOT_LIGHTS);
|
||||||
|
shader->SetInt("spotLightCount", spotCount);
|
||||||
|
for (int i = 0; i < spotCount; i++) {
|
||||||
|
const SpotLight* light = lighting->spotLights[i];
|
||||||
|
const std::string name = "spotLights[" + std::to_string(i) + "].";
|
||||||
|
shader->Set3F((name + "position").c_str(), light->position);
|
||||||
|
shader->Set3F((name + "direction").c_str(), light->direction);
|
||||||
|
shader->Set3F((name + "color").c_str(), light->color);
|
||||||
|
shader->Set1F((name + "intensity").c_str(), light->intensity);
|
||||||
|
shader->Set1F((name + "radius").c_str(), light->radius);
|
||||||
|
shader->Set1F((name + "innerCos").c_str(), std::cos(glm::radians(light->innerAngle)));
|
||||||
|
shader->Set1F((name + "outerCos").c_str(), std::cos(glm::radians(light->outerAngle)));
|
||||||
|
shader->SetInt((name + "shadowTile").c_str(), light->shadowTile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -97,7 +140,7 @@ void RenderSystem::Initialize()
|
|||||||
screenfbo->Bind();
|
screenfbo->Bind();
|
||||||
glGenTextures(1, &textureColorbuffer);
|
glGenTextures(1, &textureColorbuffer);
|
||||||
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
|
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, window->width, window->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, window->width, window->height, 0, GL_RGBA, GL_FLOAT, NULL);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
@@ -112,6 +155,50 @@ void RenderSystem::Initialize()
|
|||||||
std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!\n";
|
std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!\n";
|
||||||
screenfbo->Unbind();
|
screenfbo->Unbind();
|
||||||
|
|
||||||
|
// G-buffer
|
||||||
|
gBuffer = new FBO();
|
||||||
|
gBuffer->Bind();
|
||||||
|
unsigned int* gTextures[] = { &gPosition, &gNormal, &gAlbedo, &gMaterial, &gDepth };
|
||||||
|
for (unsigned int* texture : gTextures) {
|
||||||
|
glGenTextures(1, texture);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, *texture);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
}
|
||||||
|
AllocateGBuffer(window->width, window->height);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gPosition, 0);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gNormal, 0);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gAlbedo, 0);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, GL_TEXTURE_2D, gMaterial, 0);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, gDepth, 0);
|
||||||
|
const GLenum gDrawBuffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 };
|
||||||
|
glDrawBuffers(4, gDrawBuffers);
|
||||||
|
|
||||||
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||||
|
std::cout << "ERROR::FRAMEBUFFER:: G-buffer is not complete!\n";
|
||||||
|
gBuffer->Unbind();
|
||||||
|
|
||||||
|
// Bloom framebuffers, the blur bounces between these two
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
bloomFBO[i] = new FBO();
|
||||||
|
glGenTextures(1, &bloomTextures[i]);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, bloomTextures[i]);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
}
|
||||||
|
AllocateBloom(window->width, window->height);
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
bloomFBO[i]->Bind();
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, bloomTextures[i], 0);
|
||||||
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||||
|
std::cout << "ERROR::FRAMEBUFFER:: Bloom framebuffer is not complete!\n";
|
||||||
|
bloomFBO[i]->Unbind();
|
||||||
|
}
|
||||||
|
|
||||||
// Shadow framebuffer
|
// Shadow framebuffer
|
||||||
shadowdepthmapfbo = new FBO();
|
shadowdepthmapfbo = new FBO();
|
||||||
SHADOW_RESOLUTION = 4096;
|
SHADOW_RESOLUTION = 4096;
|
||||||
@@ -134,16 +221,88 @@ void RenderSystem::Initialize()
|
|||||||
glReadBuffer(GL_NONE);
|
glReadBuffer(GL_NONE);
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
|
||||||
|
// Spot and point light shadow atlas. Unit 10 stays reserved for it, so it's only bound here.
|
||||||
|
const int shadowAtlasSize = SHADOW_ATLAS_COLUMNS * SHADOW_ATLAS_TILE_SIZE;
|
||||||
|
localShadowFBO = new FBO();
|
||||||
|
glGenTextures(1, &localShadowAtlas);
|
||||||
|
glActiveTexture(GL_TEXTURE10);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, localShadowAtlas);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, shadowAtlasSize, shadowAtlasSize, 0,
|
||||||
|
GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
|
||||||
|
localShadowFBO->Bind();
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, localShadowAtlas, 0);
|
||||||
|
glDrawBuffer(GL_NONE);
|
||||||
|
glReadBuffer(GL_NONE);
|
||||||
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||||
|
std::cout << "ERROR::FRAMEBUFFER:: Shadow atlas is not complete!\n";
|
||||||
|
localShadowFBO->Unbind();
|
||||||
|
|
||||||
|
// The shadow map uses compare mode, which can't be read as plain depth without this
|
||||||
|
glGenSamplers(1, &shadowDebugSampler);
|
||||||
|
glSamplerParameteri(shadowDebugSampler, GL_TEXTURE_COMPARE_MODE, GL_NONE);
|
||||||
|
glSamplerParameteri(shadowDebugSampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glSamplerParameteri(shadowDebugSampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
|
||||||
lighting = new Lighting();
|
lighting = new Lighting();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RenderSystem::AllocateGBuffer(int width, int height)
|
||||||
|
{
|
||||||
|
glBindTexture(GL_TEXTURE_2D, gPosition);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, NULL);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, gNormal);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, NULL);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, gAlbedo);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, NULL);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, gMaterial);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||||
|
// Same format as the screen rbo, the depth blit between them requires it
|
||||||
|
glBindTexture(GL_TEXTURE_2D, gDepth);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, width, height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quarter resolution, the blur reaches much further for the same number of samples
|
||||||
|
void RenderSystem::AllocateBloom(int width, int height)
|
||||||
|
{
|
||||||
|
bloomWidth = glm::max(width / 4, 1);
|
||||||
|
bloomHeight = glm::max(height / 4, 1);
|
||||||
|
for (unsigned int texture : bloomTextures) {
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, bloomWidth, bloomHeight, 0, GL_RGBA, GL_FLOAT, NULL);
|
||||||
|
}
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderSystem::CycleDebugView()
|
||||||
|
{
|
||||||
|
static const char* const viewNames[] = { "FINAL", "POSITION", "NORMAL", "ALBEDO", "MATERIAL", "DEPTH", "SHADOW MAP", "BLOOM", "SHADOW ATLAS" };
|
||||||
|
debugView = (debugView + 1) % 9;
|
||||||
|
std::cout << "DEBUG VIEW: " << viewNames[debugView] << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
void RenderSystem::Resize(int width, int height)
|
void RenderSystem::Resize(int width, int height)
|
||||||
{
|
{
|
||||||
if (!screenfbo || textureColorbuffer == 0 || rbo == 0 || width <= 0 || height <= 0)
|
if (!screenfbo || !gBuffer || !bloomFBO[0] || textureColorbuffer == 0 || rbo == 0 || width <= 0 || height <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
AllocateBloom(width, height);
|
||||||
|
AllocateGBuffer(width, height);
|
||||||
|
gBuffer->Bind();
|
||||||
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||||
|
std::cout << "ERROR::FRAMEBUFFER:: G-buffer is not complete after resize!\n";
|
||||||
|
gBuffer->Unbind();
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
|
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, NULL);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
|
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
|
||||||
@@ -226,7 +385,7 @@ void RenderSystem::RenderTranslucent(Camera& camera, const glm::mat4& lightSpace
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
void RenderSystem::Render(Camera& camera)
|
void RenderSystem::Render(Camera& camera)
|
||||||
{
|
{
|
||||||
lighting->dirlighting.RotateByEulerAngles({ glm::radians(0.05f), 0.0f, 0.0f });
|
lighting->sun.direction = glm::normalize(glm::quat(glm::vec3(glm::radians(0.05f), 0.0f, 0.0f)) * lighting->sun.direction);
|
||||||
|
|
||||||
const glm::mat4 proj = camera.GetProjectionMatrix(0.05f, 2000.0f);
|
const glm::mat4 proj = camera.GetProjectionMatrix(0.05f, 2000.0f);
|
||||||
const glm::mat4 view = camera.GetViewMatrix();
|
const glm::mat4 view = camera.GetViewMatrix();
|
||||||
@@ -235,7 +394,7 @@ void RenderSystem::Render(Camera& camera)
|
|||||||
const float near_plane = 0.1f, far_plane = 100.0f;
|
const float near_plane = 0.1f, far_plane = 100.0f;
|
||||||
const glm::mat4 lightSpaceMatrix =
|
const glm::mat4 lightSpaceMatrix =
|
||||||
glm::ortho(-32.0f, 32.0f, -32.0f, 32.0f, near_plane, far_plane)
|
glm::ortho(-32.0f, 32.0f, -32.0f, 32.0f, near_plane, far_plane)
|
||||||
* glm::lookAt(camPos - lighting->dirlighting.GetFrontVector() * 10.0f,
|
* glm::lookAt(camPos - lighting->sun.direction * 10.0f,
|
||||||
camPos, camera.t.GetUpVector());
|
camPos, camera.t.GetUpVector());
|
||||||
|
|
||||||
// ---- Shadow pass ----
|
// ---- Shadow pass ----
|
||||||
@@ -245,32 +404,29 @@ void RenderSystem::Render(Camera& camera)
|
|||||||
shadowdepthmapfbo->Bind();
|
shadowdepthmapfbo->Bind();
|
||||||
glClear(GL_DEPTH_BUFFER_BIT);
|
glClear(GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
MeshShadowShader->Activate();
|
if (graphics.shadows)
|
||||||
MeshShadowShader->SetMat4("lightSpaceMatrix", lightSpaceMatrix);
|
RenderShadowCasters(lightSpaceMatrix); // with nothing drawn, the cleared map leaves everything lit
|
||||||
for (Renderable* r : renderables) {
|
RenderLocalShadows();
|
||||||
if (!r->castshadow || r->shadertype != Renderable::MeshShader) continue;
|
|
||||||
r->Render(*MeshShadowShader);
|
|
||||||
}
|
|
||||||
|
|
||||||
RigShadowShader->Activate();
|
|
||||||
RigShadowShader->SetMat4("lightSpaceMatrix", lightSpaceMatrix);
|
|
||||||
for (Renderable* r : renderables) {
|
|
||||||
if (!r->castshadow || r->shadertype != Renderable::RigShader) continue;
|
|
||||||
r->Render(*RigShadowShader);
|
|
||||||
}
|
|
||||||
|
|
||||||
glCullFace(GL_BACK);
|
glCullFace(GL_BACK);
|
||||||
|
|
||||||
// ---- Main scene ----
|
// ---- Geometry pass ----
|
||||||
screenfbo->Bind();
|
RenderGeometryPass(proj, view, camPos);
|
||||||
glViewport(0, 0, window->width, window->height);
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
|
||||||
glClearColor(212.f / 255.f, 223.f / 255.f, 232.f / 255.f, 1.0f);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
||||||
|
|
||||||
// Skybox
|
// ---- Main scene ----
|
||||||
|
// Translucents and hitboxes are still forward, they depth test against the G-buffer's depth
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, gBuffer->ID);
|
||||||
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, screenfbo->ID);
|
||||||
|
glBlitFramebuffer(0, 0, window->width, window->height, 0, 0, window->width, window->height,
|
||||||
|
GL_DEPTH_BUFFER_BIT, GL_NEAREST);
|
||||||
|
screenfbo->Bind();
|
||||||
|
glClearColor(212.f / 255.f, 223.f / 255.f, 232.f / 255.f, 1.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
// Skybox, depth tested so nothing covered by geometry gets shaded
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||||
glDisable(GL_DEPTH_TEST);
|
glDepthFunc(GL_LEQUAL);
|
||||||
|
glDepthMask(GL_FALSE);
|
||||||
SkyboxShader->Activate();
|
SkyboxShader->Activate();
|
||||||
SkyboxShader->SetMat4("proj", proj);
|
SkyboxShader->SetMat4("proj", proj);
|
||||||
SkyboxShader->SetMat4("view", glm::mat4(glm::mat3(view)));
|
SkyboxShader->SetMat4("view", glm::mat4(glm::mat3(view)));
|
||||||
@@ -279,28 +435,14 @@ void RenderSystem::Render(Camera& camera)
|
|||||||
skybox->Bind();
|
skybox->Bind();
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 36);
|
glDrawArrays(GL_TRIANGLES, 0, 36);
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
|
glDepthMask(GL_TRUE);
|
||||||
|
glDepthFunc(GL_LESS);
|
||||||
|
|
||||||
|
// ---- Lighting pass ----
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
RenderLightingPass(camPos, lightSpaceMatrix);
|
||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
|
||||||
// ---- Opaque meshes ----
|
|
||||||
PrepareMeshShader(camera);
|
|
||||||
MeshShader->SetMat4("lightSpaceMatrix", lightSpaceMatrix);
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, depthMaptexture);
|
|
||||||
for (Renderable* r : renderables) {
|
|
||||||
if (r->shadertype == Renderable::MeshShader && r->opaque)
|
|
||||||
r->Render(*MeshShader);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Opaque rigs ----
|
|
||||||
PrepareRigShader(camera);
|
|
||||||
RigShader->SetMat4("lightSpaceMatrix", lightSpaceMatrix);
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, depthMaptexture);
|
|
||||||
for (Renderable* r : renderables) {
|
|
||||||
if (r->shadertype == Renderable::RigShader && r->opaque)
|
|
||||||
r->Render(*RigShader);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Translucent pass (meshes + particles + rigs, sorted back-to-front) ----
|
// ---- Translucent pass (meshes + particles + rigs, sorted back-to-front) ----
|
||||||
// Disable face culling so thin translucent quads render both sides
|
// Disable face culling so thin translucent quads render both sides
|
||||||
glDisable(GL_CULL_FACE);
|
glDisable(GL_CULL_FACE);
|
||||||
@@ -309,17 +451,33 @@ void RenderSystem::Render(Camera& camera)
|
|||||||
|
|
||||||
RenderHitboxDebug(camera);
|
RenderHitboxDebug(camera);
|
||||||
|
|
||||||
// ---- Framebuffer blit to screen ----
|
// ---- Bloom ----
|
||||||
screenfbo->Unbind();
|
|
||||||
glDisable(GL_DEPTH_TEST);
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
if (graphics.bloom)
|
||||||
|
RenderBloom();
|
||||||
|
else
|
||||||
|
screenfbo->Unbind();
|
||||||
|
|
||||||
|
// ---- Framebuffer blit to screen ----
|
||||||
|
glViewport(0, 0, window->width, window->height);
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
ScreenShader->Activate();
|
if (debugView == 0) {
|
||||||
ScreenShader->Set1F("exposure", 1.0f);
|
ScreenShader->Activate();
|
||||||
quadVAO->Bind();
|
ScreenShader->Set1F("exposure", 1.0f);
|
||||||
glActiveTexture(GL_TEXTURE0);
|
ScreenShader->Set1F("bloomStrength", graphics.bloom ? 0.6f : 0.0f);
|
||||||
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
|
ScreenShader->SetInt("screenTexture", 0);
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
ScreenShader->SetInt("bloomTexture", 1);
|
||||||
|
quadVAO->Bind();
|
||||||
|
glActiveTexture(GL_TEXTURE1);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, bloomTextures[0]);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
RenderDebugView();
|
||||||
|
}
|
||||||
|
|
||||||
// ---- GUI (always on top, no depth) ----
|
// ---- GUI (always on top, no depth) ----
|
||||||
glDisable(GL_CULL_FACE);
|
glDisable(GL_CULL_FACE);
|
||||||
@@ -347,6 +505,309 @@ void RenderSystem::Render(Camera& camera)
|
|||||||
glfwSwapBuffers(window->handle);
|
glfwSwapBuffers(window->handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Deferred passes
|
||||||
|
// ============================================================================
|
||||||
|
void RenderSystem::RenderGeometryPass(const glm::mat4& proj, const glm::mat4& view, const glm::vec3& camPos)
|
||||||
|
{
|
||||||
|
gBuffer->Bind();
|
||||||
|
glViewport(0, 0, window->width, window->height);
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
glDisable(GL_BLEND); // the attachments store data in alpha, blending would corrupt them
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
// Frustum planes as (normal, distance), pulled straight out of the view projection matrix
|
||||||
|
const glm::mat4 viewProjection = proj * view;
|
||||||
|
glm::vec4 frustum[6];
|
||||||
|
for (int axis = 0; axis < 3; axis++) {
|
||||||
|
const glm::vec4 row(viewProjection[0][axis], viewProjection[1][axis], viewProjection[2][axis], viewProjection[3][axis]);
|
||||||
|
const glm::vec4 last(viewProjection[0][3], viewProjection[1][3], viewProjection[2][3], viewProjection[3][3]);
|
||||||
|
frustum[axis * 2] = last + row;
|
||||||
|
frustum[axis * 2 + 1] = last - row;
|
||||||
|
}
|
||||||
|
occlusionCandidates.clear();
|
||||||
|
|
||||||
|
GeometryShader->Activate();
|
||||||
|
GeometryShader->SetMat4("proj", proj);
|
||||||
|
GeometryShader->SetMat4("view", view);
|
||||||
|
for (Renderable* r : renderables) {
|
||||||
|
if (r->shadertype == Renderable::MeshShader && r->opaque && IsVisible(r, frustum, camPos))
|
||||||
|
r->Render(*GeometryShader);
|
||||||
|
}
|
||||||
|
|
||||||
|
GeometryRigShader->Activate();
|
||||||
|
GeometryRigShader->SetMat4("proj", proj);
|
||||||
|
GeometryRigShader->SetMat4("view", view);
|
||||||
|
for (Renderable* r : renderables) {
|
||||||
|
if (r->shadertype == Renderable::RigShader && r->opaque && IsVisible(r, frustum, camPos))
|
||||||
|
r->Render(*GeometryRigShader);
|
||||||
|
}
|
||||||
|
|
||||||
|
IssueOcclusionQueries(proj, view);
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bounds padding, so a box sitting exactly on its object's surface can't fail its own depth test
|
||||||
|
static constexpr float OCCLUSION_PADDING = 0.05f;
|
||||||
|
|
||||||
|
bool RenderSystem::IsVisible(Renderable* r, const glm::vec4 (&frustum)[6], const glm::vec3& camPos)
|
||||||
|
{
|
||||||
|
BoundingBox bounds;
|
||||||
|
if (!graphics.culling || !r->GetWorldBounds(bounds)) {
|
||||||
|
r->occluded = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frustum: outside if the corner furthest along a plane's normal is still behind it
|
||||||
|
for (const glm::vec4& plane : frustum) {
|
||||||
|
const glm::vec3 normal(plane);
|
||||||
|
const glm::vec3 furthest(
|
||||||
|
normal.x >= 0.0f ? bounds.max.x : bounds.min.x,
|
||||||
|
normal.y >= 0.0f ? bounds.max.y : bounds.min.y,
|
||||||
|
normal.z >= 0.0f ? bounds.max.z : bounds.min.z);
|
||||||
|
if (glm::dot(normal, furthest) + plane.w < 0.0f) {
|
||||||
|
r->occluded = false; // so it draws straight away when it comes back into view
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only use an answer the GPU already has, waiting for one would stall the frame
|
||||||
|
if (r->occlusionPending) {
|
||||||
|
GLuint available = 0;
|
||||||
|
glGetQueryObjectuiv(r->occlusionQuery, GL_QUERY_RESULT_AVAILABLE, &available);
|
||||||
|
if (available) {
|
||||||
|
GLuint samplesPassed = 0;
|
||||||
|
glGetQueryObjectuiv(r->occlusionQuery, GL_QUERY_RESULT, &samplesPassed);
|
||||||
|
r->occluded = samplesPassed == 0;
|
||||||
|
r->occlusionPending = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The query box can't be seen from inside it, so never trust it then
|
||||||
|
const glm::vec3 padding(OCCLUSION_PADDING * 2.0f);
|
||||||
|
if (glm::all(glm::greaterThan(camPos, bounds.min - padding)) && glm::all(glm::lessThan(camPos, bounds.max + padding)))
|
||||||
|
r->occluded = false;
|
||||||
|
|
||||||
|
occlusionCandidates.push_back(r);
|
||||||
|
return !r->occluded;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draws each candidate's box against this frame's depth, colour and depth writes off.
|
||||||
|
// The results get used next frame.
|
||||||
|
void RenderSystem::IssueOcclusionQueries(const glm::mat4& proj, const glm::mat4& view)
|
||||||
|
{
|
||||||
|
if (occlusionCandidates.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
|
||||||
|
glDepthMask(GL_FALSE);
|
||||||
|
glDisable(GL_CULL_FACE); // the skybox cube faces inward
|
||||||
|
|
||||||
|
HitboxShader->Activate();
|
||||||
|
HitboxShader->SetMat4("proj", proj);
|
||||||
|
HitboxShader->SetMat4("view", view);
|
||||||
|
skyboxVAO->Bind();
|
||||||
|
|
||||||
|
for (Renderable* r : occlusionCandidates) {
|
||||||
|
if (r->occlusionPending)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
BoundingBox bounds;
|
||||||
|
r->GetWorldBounds(bounds);
|
||||||
|
const glm::vec3 center = (bounds.min + bounds.max) * 0.5f;
|
||||||
|
const glm::vec3 halfSize = (bounds.max - bounds.min) * 0.5f + glm::vec3(OCCLUSION_PADDING);
|
||||||
|
|
||||||
|
if (r->occlusionQuery == 0)
|
||||||
|
glGenQueries(1, &r->occlusionQuery);
|
||||||
|
|
||||||
|
// The skybox cube runs from -1 to 1, so half size is the scale
|
||||||
|
HitboxShader->SetMat4("modl", glm::scale(glm::translate(glm::mat4(1.0f), center), halfSize));
|
||||||
|
glBeginQuery(GL_ANY_SAMPLES_PASSED, r->occlusionQuery);
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 36);
|
||||||
|
glEndQuery(GL_ANY_SAMPLES_PASSED);
|
||||||
|
r->occlusionPending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindVertexArray(0);
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
glDepthMask(GL_TRUE);
|
||||||
|
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderSystem::RenderShadowCasters(const glm::mat4& lightSpaceMatrix)
|
||||||
|
{
|
||||||
|
MeshShadowShader->Activate();
|
||||||
|
MeshShadowShader->SetMat4("lightSpaceMatrix", lightSpaceMatrix);
|
||||||
|
for (Renderable* r : renderables) {
|
||||||
|
if (!r->castshadow || r->shadertype != Renderable::MeshShader) continue;
|
||||||
|
r->Render(*MeshShadowShader);
|
||||||
|
}
|
||||||
|
|
||||||
|
RigShadowShader->Activate();
|
||||||
|
RigShadowShader->SetMat4("lightSpaceMatrix", lightSpaceMatrix);
|
||||||
|
for (Renderable* r : renderables) {
|
||||||
|
if (!r->castshadow || r->shadertype != Renderable::RigShader) continue;
|
||||||
|
r->Render(*RigShadowShader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draws the casters from each spot and point light into its tiles of the atlas
|
||||||
|
void RenderSystem::RenderLocalShadows()
|
||||||
|
{
|
||||||
|
static constexpr float nearPlane = 0.2f;
|
||||||
|
|
||||||
|
std::vector<glm::mat4>& tiles = lighting->shadowTileMatrices;
|
||||||
|
tiles.clear();
|
||||||
|
|
||||||
|
if (!graphics.lightShadows) {
|
||||||
|
for (SpotLight* light : lighting->spotLights)
|
||||||
|
light->shadowTile = -1;
|
||||||
|
for (PointLight* light : lighting->pointLights)
|
||||||
|
light->shadowTile = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
localShadowFBO->Bind();
|
||||||
|
glClear(GL_DEPTH_BUFFER_BIT);
|
||||||
|
glEnable(GL_SCISSOR_TEST);
|
||||||
|
|
||||||
|
auto RenderTile = [&](const glm::mat4& matrix) {
|
||||||
|
const int index = (int)tiles.size();
|
||||||
|
const int x = (index % SHADOW_ATLAS_COLUMNS) * SHADOW_ATLAS_TILE_SIZE;
|
||||||
|
const int y = (index / SHADOW_ATLAS_COLUMNS) * SHADOW_ATLAS_TILE_SIZE;
|
||||||
|
glViewport(x, y, SHADOW_ATLAS_TILE_SIZE, SHADOW_ATLAS_TILE_SIZE);
|
||||||
|
glScissor(x, y, SHADOW_ATLAS_TILE_SIZE, SHADOW_ATLAS_TILE_SIZE);
|
||||||
|
RenderShadowCasters(matrix);
|
||||||
|
tiles.push_back(matrix);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (SpotLight* light : lighting->spotLights) {
|
||||||
|
light->shadowTile = -1;
|
||||||
|
if (!light->castShadow || (int)tiles.size() >= SHADOW_ATLAS_TILES)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
light->shadowTile = (int)tiles.size();
|
||||||
|
const glm::vec3 up = glm::abs(light->direction.y) > 0.99f ? glm::vec3(0.0f, 0.0f, 1.0f) : glm::vec3(0.0f, 1.0f, 0.0f);
|
||||||
|
RenderTile(glm::perspective(glm::radians(light->outerAngle * 2.0f + 2.0f), 1.0f, nearPlane, light->radius)
|
||||||
|
* glm::lookAt(light->position, light->position + light->direction, up));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order has to match PointShadowFace in the shaders. Slightly over 90 degrees so the faces overlap at the seams.
|
||||||
|
static const glm::vec3 faceDirections[6] = {
|
||||||
|
{ 1.0f, 0.0f, 0.0f }, { -1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f },
|
||||||
|
{ 0.0f, -1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f }, { 0.0f, 0.0f, -1.0f }
|
||||||
|
};
|
||||||
|
static const glm::vec3 faceUps[6] = {
|
||||||
|
{ 0.0f, 1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f },
|
||||||
|
{ 0.0f, 0.0f, 1.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }
|
||||||
|
};
|
||||||
|
|
||||||
|
for (PointLight* light : lighting->pointLights) {
|
||||||
|
light->shadowTile = -1;
|
||||||
|
if (!light->castShadow || (int)tiles.size() + 6 > SHADOW_ATLAS_TILES)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
light->shadowTile = (int)tiles.size();
|
||||||
|
const glm::mat4 projection = glm::perspective(glm::radians(92.0f), 1.0f, nearPlane, light->radius);
|
||||||
|
for (int face = 0; face < 6; face++)
|
||||||
|
RenderTile(projection * glm::lookAt(light->position, light->position + faceDirections[face], faceUps[face]));
|
||||||
|
}
|
||||||
|
|
||||||
|
glDisable(GL_SCISSOR_TEST);
|
||||||
|
localShadowFBO->Unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leaves the finished glow in bloomTextures[0] and the default framebuffer bound
|
||||||
|
void RenderSystem::RenderBloom()
|
||||||
|
{
|
||||||
|
static constexpr int blurPasses = 8; // has to stay even, or the result ends up in bloomTextures[1]
|
||||||
|
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
glViewport(0, 0, bloomWidth, bloomHeight);
|
||||||
|
quadVAO->Bind();
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
|
||||||
|
bloomFBO[0]->Bind();
|
||||||
|
BloomExtractShader->Activate();
|
||||||
|
BloomExtractShader->SetInt("screenTexture", 0);
|
||||||
|
BloomExtractShader->Set1F("threshold", 1.8f);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||||
|
|
||||||
|
BloomBlurShader->Activate();
|
||||||
|
BloomBlurShader->SetInt("image", 0);
|
||||||
|
for (int pass = 0; pass < blurPasses; pass++) {
|
||||||
|
const int source = pass % 2;
|
||||||
|
bloomFBO[1 - source]->Bind();
|
||||||
|
BloomBlurShader->SetInt("horizontal", source == 0 ? 1 : 0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, bloomTextures[source]);
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
bloomFBO[0]->Unbind();
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Units 0 and 2 stay the shadow map and environment map, same as the forward shaders
|
||||||
|
void RenderSystem::BindGBufferTextures(Shader* shader)
|
||||||
|
{
|
||||||
|
shader->SetInt("gPosition", 3);
|
||||||
|
shader->SetInt("gNormal", 4);
|
||||||
|
shader->SetInt("gAlbedo", 5);
|
||||||
|
shader->SetInt("gMaterial", 6);
|
||||||
|
glActiveTexture(GL_TEXTURE3);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, gPosition);
|
||||||
|
glActiveTexture(GL_TEXTURE4);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, gNormal);
|
||||||
|
glActiveTexture(GL_TEXTURE5);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, gAlbedo);
|
||||||
|
glActiveTexture(GL_TEXTURE6);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, gMaterial);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expects depth testing off, the fullscreen quad shouldn't touch the depth buffer
|
||||||
|
void RenderSystem::RenderLightingPass(const glm::vec3& camPos, const glm::mat4& lightSpaceMatrix)
|
||||||
|
{
|
||||||
|
DeferredLightingShader->Activate();
|
||||||
|
DeferredLightingShader->Set3F("viewPos", camPos);
|
||||||
|
DeferredLightingShader->SetMat4("lightSpaceMatrix", lightSpaceMatrix);
|
||||||
|
DeferredLightingShader->SetInt("shadowMap", 0);
|
||||||
|
ConfigurePBR(DeferredLightingShader, lighting);
|
||||||
|
BindGBufferTextures(DeferredLightingShader);
|
||||||
|
glActiveTexture(GL_TEXTURE2);
|
||||||
|
skybox->Bind();
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, depthMaptexture);
|
||||||
|
quadVAO->Bind();
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderSystem::RenderDebugView()
|
||||||
|
{
|
||||||
|
GBufferDebugShader->Activate();
|
||||||
|
const bool showAtlas = debugView == 8; // reuses the shadow map view
|
||||||
|
GBufferDebugShader->SetInt("debugMode", showAtlas ? 6 : debugView);
|
||||||
|
GBufferDebugShader->Set1F("nearPlane", 0.05f);
|
||||||
|
GBufferDebugShader->Set1F("farPlane", 2000.0f);
|
||||||
|
BindGBufferTextures(GBufferDebugShader);
|
||||||
|
GBufferDebugShader->SetInt("depthTex", 7);
|
||||||
|
GBufferDebugShader->SetInt("shadowTex", 8);
|
||||||
|
glActiveTexture(GL_TEXTURE7);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, gDepth);
|
||||||
|
glActiveTexture(GL_TEXTURE8);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, showAtlas ? localShadowAtlas : depthMaptexture);
|
||||||
|
glBindSampler(8, shadowDebugSampler);
|
||||||
|
GBufferDebugShader->SetInt("bloomTex", 9);
|
||||||
|
glActiveTexture(GL_TEXTURE9);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, bloomTextures[0]);
|
||||||
|
quadVAO->Bind();
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||||
|
glBindSampler(8, 0);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Shader preparation helpers
|
// Shader preparation helpers
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -438,6 +899,8 @@ Renderable::Renderable() {}
|
|||||||
|
|
||||||
Renderable::~Renderable()
|
Renderable::~Renderable()
|
||||||
{
|
{
|
||||||
|
if (occlusionQuery != 0)
|
||||||
|
glDeleteQueries(1, &occlusionQuery);
|
||||||
engine->rendersystem->RemoveRenderable(this);
|
engine->rendersystem->RemoveRenderable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
#include "Structures.h"
|
||||||
const float skyboxVertices[] = {
|
const float skyboxVertices[] = {
|
||||||
-1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f,
|
-1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f,
|
||||||
1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f,
|
1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f,
|
||||||
@@ -43,7 +44,23 @@ public:
|
|||||||
void BindToRenderSystem();
|
void BindToRenderSystem();
|
||||||
virtual void Render(Shader& ShaderProgram) {}
|
virtual void Render(Shader& ShaderProgram) {}
|
||||||
virtual glm::vec3 GetWorldPosition() const { return glm::vec3(0.0f); }
|
virtual glm::vec3 GetWorldPosition() const { return glm::vec3(0.0f); }
|
||||||
|
|
||||||
|
// A world space box around everything this draws. Without one it's never culled.
|
||||||
|
virtual bool GetWorldBounds(BoundingBox& bounds) const { return false; }
|
||||||
|
|
||||||
|
// Occlusion culling state, only the render system touches these
|
||||||
|
unsigned int occlusionQuery = 0;
|
||||||
|
bool occlusionPending = false; // a query was sent and hasn't been read back yet
|
||||||
|
bool occluded = false; // the last answered query saw none of it
|
||||||
};
|
};
|
||||||
|
struct GraphicsSettings {
|
||||||
|
bool bloom = true;
|
||||||
|
bool shadows = true; // the sun's shadow map
|
||||||
|
bool lightShadows = true; // spot and point light shadows
|
||||||
|
bool vsync = true;
|
||||||
|
bool culling = true; // frustum and occlusion culling for opaque meshes and characters
|
||||||
|
};
|
||||||
|
|
||||||
struct HitboxDebugLine {
|
struct HitboxDebugLine {
|
||||||
glm::mat4 transform;
|
glm::mat4 transform;
|
||||||
glm::vec3 color;
|
glm::vec3 color;
|
||||||
@@ -52,8 +69,23 @@ struct HitboxDebugLine {
|
|||||||
class RenderSystem {
|
class RenderSystem {
|
||||||
public:
|
public:
|
||||||
Lighting* lighting = nullptr;
|
Lighting* lighting = nullptr;
|
||||||
|
GraphicsSettings graphics;
|
||||||
FBO* screenfbo = nullptr;
|
FBO* screenfbo = nullptr;
|
||||||
FBO* shadowdepthmapfbo = nullptr;
|
FBO* shadowdepthmapfbo = nullptr;
|
||||||
|
FBO* gBuffer = nullptr;
|
||||||
|
unsigned int gPosition = 0;
|
||||||
|
unsigned int gNormal = 0;
|
||||||
|
unsigned int gAlbedo = 0;
|
||||||
|
unsigned int gMaterial = 0;
|
||||||
|
unsigned int gDepth = 0;
|
||||||
|
unsigned int shadowDebugSampler = 0;
|
||||||
|
FBO* localShadowFBO = nullptr;
|
||||||
|
unsigned int localShadowAtlas = 0;
|
||||||
|
FBO* bloomFBO[2] = { nullptr, nullptr };
|
||||||
|
unsigned int bloomTextures[2] = { 0, 0 };
|
||||||
|
int bloomWidth = 0;
|
||||||
|
int bloomHeight = 0;
|
||||||
|
int debugView = 0; // 0 = final image, otherwise a debugMode in gbuffer_debug.frag
|
||||||
unsigned int depthMaptexture = 0;
|
unsigned int depthMaptexture = 0;
|
||||||
unsigned int textureColorbuffer = 0;
|
unsigned int textureColorbuffer = 0;
|
||||||
unsigned int rbo = 0;
|
unsigned int rbo = 0;
|
||||||
@@ -66,6 +98,12 @@ public:
|
|||||||
Shader* ScreenShader = nullptr;
|
Shader* ScreenShader = nullptr;
|
||||||
Shader* RigShader = nullptr;
|
Shader* RigShader = nullptr;
|
||||||
Shader* MeshShader = nullptr;
|
Shader* MeshShader = nullptr;
|
||||||
|
Shader* GeometryShader = nullptr;
|
||||||
|
Shader* GeometryRigShader = nullptr;
|
||||||
|
Shader* DeferredLightingShader = nullptr;
|
||||||
|
Shader* GBufferDebugShader = nullptr;
|
||||||
|
Shader* BloomExtractShader = nullptr;
|
||||||
|
Shader* BloomBlurShader = nullptr;
|
||||||
Shader* ImageBoxShader = nullptr;
|
Shader* ImageBoxShader = nullptr;
|
||||||
Shader* BoxShader = nullptr;
|
Shader* BoxShader = nullptr;
|
||||||
Shader* Text2DShader = nullptr;
|
Shader* Text2DShader = nullptr;
|
||||||
@@ -79,6 +117,7 @@ public:
|
|||||||
void Initialize();
|
void Initialize();
|
||||||
void Resize(int width, int height);
|
void Resize(int width, int height);
|
||||||
void Render(Camera& camera);
|
void Render(Camera& camera);
|
||||||
|
void CycleDebugView();
|
||||||
void SetHitboxDebugLines(const std::vector<HitboxDebugLine>& lines) { hitboxDebugLines = lines; }
|
void SetHitboxDebugLines(const std::vector<HitboxDebugLine>& lines) { hitboxDebugLines = lines; }
|
||||||
void AddRenderable(Renderable* r) { renderables.push_back(r); }
|
void AddRenderable(Renderable* r) { renderables.push_back(r); }
|
||||||
void RemoveRenderable(Renderable* r) {
|
void RemoveRenderable(Renderable* r) {
|
||||||
@@ -98,5 +137,17 @@ private:
|
|||||||
void PrepareTextShader(Camera& camera);
|
void PrepareTextShader(Camera& camera);
|
||||||
void RenderTranslucent(Camera& camera, const glm::mat4& lightSpaceMatrix, const glm::vec3& camPos);
|
void RenderTranslucent(Camera& camera, const glm::mat4& lightSpaceMatrix, const glm::vec3& camPos);
|
||||||
void RenderHitboxDebug(Camera& camera);
|
void RenderHitboxDebug(Camera& camera);
|
||||||
|
void AllocateGBuffer(int width, int height);
|
||||||
|
void AllocateBloom(int width, int height);
|
||||||
|
void RenderBloom();
|
||||||
|
void RenderShadowCasters(const glm::mat4& lightSpaceMatrix);
|
||||||
|
void RenderLocalShadows();
|
||||||
|
void BindGBufferTextures(Shader* shader);
|
||||||
|
void RenderGeometryPass(const glm::mat4& proj, const glm::mat4& view, const glm::vec3& camPos);
|
||||||
|
bool IsVisible(Renderable* r, const glm::vec4 (&frustum)[6], const glm::vec3& camPos);
|
||||||
|
void IssueOcclusionQueries(const glm::mat4& proj, const glm::mat4& view);
|
||||||
|
std::vector<Renderable*> occlusionCandidates; // drawn into the queries after the geometry pass
|
||||||
|
void RenderLightingPass(const glm::vec3& camPos, const glm::mat4& lightSpaceMatrix);
|
||||||
|
void RenderDebugView();
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
#include "ResourceManager.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "texture.h"
|
||||||
|
#include "font.h"
|
||||||
|
|
||||||
|
ResourceManager::~ResourceManager()
|
||||||
|
{
|
||||||
|
for (auto& [path, texture] : textures) {
|
||||||
|
if (!texture) continue;
|
||||||
|
texture->Delete();
|
||||||
|
delete texture;
|
||||||
|
}
|
||||||
|
for (Texture* texture : createdTextures) {
|
||||||
|
texture->Delete();
|
||||||
|
delete texture;
|
||||||
|
}
|
||||||
|
for (auto& [path, font] : fonts)
|
||||||
|
delete font;
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture* ResourceManager::CreateTexture(int width, int height)
|
||||||
|
{
|
||||||
|
createdTextures.push_back(new Texture(width, height));
|
||||||
|
return createdTextures.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture* ResourceManager::CopyTexture(const Texture& source)
|
||||||
|
{
|
||||||
|
createdTextures.push_back(new Texture(source));
|
||||||
|
return createdTextures.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failed loads are cached as nullptr too, so a missing file only gets reported once
|
||||||
|
Texture* ResourceManager::GetTexture(const std::string& path)
|
||||||
|
{
|
||||||
|
auto existing = textures.find(path);
|
||||||
|
if (existing != textures.end())
|
||||||
|
return existing->second;
|
||||||
|
|
||||||
|
Texture* texture = nullptr;
|
||||||
|
try {
|
||||||
|
texture = new Texture(path.c_str());
|
||||||
|
}
|
||||||
|
catch (const std::exception& error) {
|
||||||
|
std::cout << "RESOURCE MANAGER: " << error.what() << "\n";
|
||||||
|
}
|
||||||
|
textures[path] = texture;
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
Font* ResourceManager::GetFont(const std::string& path)
|
||||||
|
{
|
||||||
|
auto existing = fonts.find(path);
|
||||||
|
if (existing != fonts.end())
|
||||||
|
return existing->second;
|
||||||
|
|
||||||
|
Font* font = nullptr;
|
||||||
|
try {
|
||||||
|
font = new Font(path.c_str());
|
||||||
|
}
|
||||||
|
catch (const std::exception& error) {
|
||||||
|
std::cout << "RESOURCE MANAGER: " << error.what() << ": " << path << "\n";
|
||||||
|
}
|
||||||
|
fonts[path] = font;
|
||||||
|
return font;
|
||||||
|
}
|
||||||
+21
-8
@@ -1,20 +1,33 @@
|
|||||||
#ifndef RESOURCE_MANAGER_CLASS
|
#ifndef RESOURCE_MANAGER_CLASS
|
||||||
#define RESOURCE_MANAGER_CLASS
|
#define RESOURCE_MANAGER_CLASS
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Folder.h"
|
class Texture;
|
||||||
//#include "font.h"
|
class Font;
|
||||||
#include "Model.h"
|
|
||||||
#include "SoundSystem.h"
|
|
||||||
|
|
||||||
|
// Owns every texture and font, so each file only loads once however many things use it.
|
||||||
|
// Sounds are cached by SoundSystem, rigs and animations by CharacterManager.
|
||||||
class ResourceManager {
|
class ResourceManager {
|
||||||
public:
|
public:
|
||||||
//std::vector<Font*> fonts;
|
ResourceManager() {}
|
||||||
std::vector<Model*> models;
|
~ResourceManager(); // needs the GL context, delete this before the window
|
||||||
std::vector<SoundData*> sounds;
|
ResourceManager(const ResourceManager&) = delete;
|
||||||
public:
|
|
||||||
|
|
||||||
|
// Returns nullptr and logs it if the file can't be loaded
|
||||||
|
Texture* GetTexture(const std::string& path);
|
||||||
|
Font* GetFont(const std::string& path);
|
||||||
|
|
||||||
|
// Not cached, every call makes a new texture that's free to be edited
|
||||||
|
Texture* CreateTexture(int width, int height);
|
||||||
|
Texture* CopyTexture(const Texture& source);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::string, Texture*> textures;
|
||||||
|
std::unordered_map<std::string, Font*> fonts;
|
||||||
|
std::vector<Texture*> createdTextures;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#ifndef SURFACE_TEXTURE_CLASS
|
||||||
|
#define SURFACE_TEXTURE_CLASS
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
#include "shaderClass.h"
|
||||||
|
#include "texture.h"
|
||||||
|
|
||||||
|
// How one game element wears a texture. Texture is the shared image, this is per element,
|
||||||
|
// so the same image can be stretched on one thing and tiled on another.
|
||||||
|
class SurfaceTexture {
|
||||||
|
public:
|
||||||
|
enum class Mode {
|
||||||
|
Stretch, // the image fills each face once, whatever size it is
|
||||||
|
Tile // the image repeats every tileSize, so it keeps its size as the element grows
|
||||||
|
};
|
||||||
|
|
||||||
|
Texture* texture = nullptr;
|
||||||
|
Mode mode = Mode::Stretch;
|
||||||
|
float tileSize = 1.0f; // world units on meshes, pixels on gui
|
||||||
|
glm::vec2 offset = glm::vec2(0.0f); // slides the image, in repeats of it
|
||||||
|
|
||||||
|
SurfaceTexture() {}
|
||||||
|
|
||||||
|
static SurfaceTexture Stretched(Texture* texture) {
|
||||||
|
SurfaceTexture surface;
|
||||||
|
surface.texture = texture;
|
||||||
|
return surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SurfaceTexture Tiled(Texture* texture, float tileSize) {
|
||||||
|
SurfaceTexture surface;
|
||||||
|
surface.texture = texture;
|
||||||
|
surface.mode = Mode::Tile;
|
||||||
|
surface.tileSize = tileSize;
|
||||||
|
return surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binds the image and sets the uv uniforms. size is the element's size, with the shader active.
|
||||||
|
void Apply(Shader& shader, GLuint unit, const glm::vec3& size) const {
|
||||||
|
glActiveTexture(GL_TEXTURE0 + unit);
|
||||||
|
shader.SetInt("tex", unit);
|
||||||
|
if (texture == nullptr) {
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
shader.SetInt("useTexture", 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture->ID);
|
||||||
|
shader.SetInt("useTexture", 1);
|
||||||
|
shader.SetInt("uvMode", mode == Mode::Tile ? 1 : 0);
|
||||||
|
shader.Set3F("tileScale", size / glm::max(tileSize, 0.0001f));
|
||||||
|
shader.Set2F("uvOffset", offset);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#ifndef TEXTURE_PAINTER_CLASS
|
||||||
|
#define TEXTURE_PAINTER_CLASS
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
#include "texture.h"
|
||||||
|
|
||||||
|
// The brush behind the texture drawer. Edits a Texture's pixels and uploads them.
|
||||||
|
class TexturePainter {
|
||||||
|
public:
|
||||||
|
glm::vec3 color = glm::vec3(0.0f);
|
||||||
|
int brushSize = 8; // radius in pixels of a 256 wide texture, so it looks the same on any size
|
||||||
|
|
||||||
|
// uv runs 0-1 from the top left of the image. Joins up with the last call until EndStroke.
|
||||||
|
void Stroke(Texture& texture, const glm::vec2& uv) {
|
||||||
|
const glm::vec2 point(uv.x * texture.width, (1.0f - uv.y) * texture.height);
|
||||||
|
const float radius = glm::max(1.0f, brushSize * texture.width / 256.0f);
|
||||||
|
|
||||||
|
if (!stroking) {
|
||||||
|
Stamp(texture, point, radius);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Fill the gap, the mouse can cross a lot of pixels in one frame
|
||||||
|
const int steps = (int)std::ceil(glm::length(point - lastPoint) / (radius * 0.5f));
|
||||||
|
for (int i = 1; i <= steps; i++)
|
||||||
|
Stamp(texture, glm::mix(lastPoint, point, (float)i / (float)steps), radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
stroking = true;
|
||||||
|
lastPoint = point;
|
||||||
|
texture.Upload();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EndStroke() {
|
||||||
|
stroking = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clear(Texture& texture) {
|
||||||
|
texture.pixels.assign(texture.pixels.size(), 255);
|
||||||
|
texture.Upload();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool stroking = false;
|
||||||
|
glm::vec2 lastPoint = glm::vec2(0.0f);
|
||||||
|
|
||||||
|
void Stamp(Texture& texture, const glm::vec2& center, float radius) {
|
||||||
|
const int minX = glm::max((int)(center.x - radius), 0);
|
||||||
|
const int maxX = glm::min((int)(center.x + radius), texture.width - 1);
|
||||||
|
const int minY = glm::max((int)(center.y - radius), 0);
|
||||||
|
const int maxY = glm::min((int)(center.y + radius), texture.height - 1);
|
||||||
|
|
||||||
|
for (int y = minY; y <= maxY; y++) {
|
||||||
|
for (int x = minX; x <= maxX; x++) {
|
||||||
|
const glm::vec2 offset = glm::vec2(x + 0.5f, y + 0.5f) - center;
|
||||||
|
if (glm::dot(offset, offset) > radius * radius)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
unsigned char* pixel = &texture.pixels[((size_t)y * texture.width + x) * 4];
|
||||||
|
pixel[0] = (unsigned char)(color.r * 255.0f);
|
||||||
|
pixel[1] = (unsigned char)(color.g * 255.0f);
|
||||||
|
pixel[2] = (unsigned char)(color.b * 255.0f);
|
||||||
|
pixel[3] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
+12
-2
@@ -166,15 +166,17 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="Character.cpp" />
|
<ClCompile Include="Character.cpp" />
|
||||||
<ClCompile Include="CharacterManager.cpp" />
|
<ClCompile Include="CharacterManager.cpp" />
|
||||||
|
<ClCompile Include="CollisionMesh.cpp" />
|
||||||
<ClCompile Include="Cubemap.h" />
|
<ClCompile Include="Cubemap.h" />
|
||||||
<ClCompile Include="EBO.cpp" />
|
<ClCompile Include="EBO.cpp" />
|
||||||
<ClCompile Include="Engine.cpp" />
|
<ClCompile Include="Engine.cpp" />
|
||||||
<ClCompile Include="glad.c" />
|
<ClCompile Include="glad.c" />
|
||||||
<ClCompile Include="main.cpp" />
|
<ClCompile Include="main.cpp" />
|
||||||
<ClCompile Include="Mouse.cpp" />
|
<ClCompile Include="Mouse.cpp" />
|
||||||
<ClCompile Include="Net.cpp" />
|
|
||||||
<ClCompile Include="Physics.cpp" />
|
<ClCompile Include="Physics.cpp" />
|
||||||
|
<ClCompile Include="Ragdoll.cpp" />
|
||||||
<ClCompile Include="RenderSystem.cpp" />
|
<ClCompile Include="RenderSystem.cpp" />
|
||||||
|
<ClCompile Include="ResourceManager.cpp" />
|
||||||
<ClCompile Include="shaderClass.cpp" />
|
<ClCompile Include="shaderClass.cpp" />
|
||||||
<ClCompile Include="Sound.cpp" />
|
<ClCompile Include="Sound.cpp" />
|
||||||
<ClCompile Include="SoundSystem.cpp" />
|
<ClCompile Include="SoundSystem.cpp" />
|
||||||
@@ -188,18 +190,20 @@
|
|||||||
<ClInclude Include="BodyComponentActions.h" />
|
<ClInclude Include="BodyComponentActions.h" />
|
||||||
<ClInclude Include="Character.h" />
|
<ClInclude Include="Character.h" />
|
||||||
<ClInclude Include="CharacterManager.h" />
|
<ClInclude Include="CharacterManager.h" />
|
||||||
|
<ClInclude Include="CollisionMesh.h" />
|
||||||
<ClInclude Include="common.h" />
|
<ClInclude Include="common.h" />
|
||||||
<ClInclude Include="DefaultBodyComponents.h" />
|
<ClInclude Include="DefaultBodyComponents.h" />
|
||||||
<ClInclude Include="Engine.h" />
|
<ClInclude Include="Engine.h" />
|
||||||
<ClInclude Include="Folder.h" />
|
<ClInclude Include="Folder.h" />
|
||||||
<ClInclude Include="font.h" />
|
<ClInclude Include="font.h" />
|
||||||
<ClInclude Include="Gui.h" />
|
<ClInclude Include="Gui.h" />
|
||||||
|
<ClInclude Include="Light.h" />
|
||||||
<ClInclude Include="Lighting.h" />
|
<ClInclude Include="Lighting.h" />
|
||||||
<ClInclude Include="MeshData.h" />
|
<ClInclude Include="MeshData.h" />
|
||||||
<ClInclude Include="Net.h" />
|
|
||||||
<ClInclude Include="Particle.h" />
|
<ClInclude Include="Particle.h" />
|
||||||
<ClInclude Include="Physics.h" />
|
<ClInclude Include="Physics.h" />
|
||||||
<ClInclude Include="QuadVertices.h" />
|
<ClInclude Include="QuadVertices.h" />
|
||||||
|
<ClInclude Include="Ragdoll.h" />
|
||||||
<ClInclude Include="RenderSystem.h" />
|
<ClInclude Include="RenderSystem.h" />
|
||||||
<ClInclude Include="ResourceManager.h" />
|
<ClInclude Include="ResourceManager.h" />
|
||||||
<ClInclude Include="Rig.h" />
|
<ClInclude Include="Rig.h" />
|
||||||
@@ -218,6 +222,8 @@
|
|||||||
<ClInclude Include="Object.h" />
|
<ClInclude Include="Object.h" />
|
||||||
<ClInclude Include="shaderClass.h" />
|
<ClInclude Include="shaderClass.h" />
|
||||||
<ClInclude Include="SoundSystem.h" />
|
<ClInclude Include="SoundSystem.h" />
|
||||||
|
<ClInclude Include="SurfaceTexture.h" />
|
||||||
|
<ClInclude Include="TexturePainter.h" />
|
||||||
<ClInclude Include="stb_image.h" />
|
<ClInclude Include="stb_image.h" />
|
||||||
<ClInclude Include="t.h" />
|
<ClInclude Include="t.h" />
|
||||||
<ClInclude Include="t2d.h" />
|
<ClInclude Include="t2d.h" />
|
||||||
@@ -231,12 +237,16 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="shaders\2DText.frag" />
|
<None Include="shaders\2DText.frag" />
|
||||||
<None Include="shaders\2DText.vert" />
|
<None Include="shaders\2DText.vert" />
|
||||||
|
<None Include="shaders\bloom_blur.frag" />
|
||||||
|
<None Include="shaders\bloom_extract.frag" />
|
||||||
<None Include="shaders\Box.frag" />
|
<None Include="shaders\Box.frag" />
|
||||||
<None Include="shaders\Box.vert" />
|
<None Include="shaders\Box.vert" />
|
||||||
<None Include="shaders\default.frag" />
|
<None Include="shaders\default.frag" />
|
||||||
<None Include="shaders\default.vert" />
|
<None Include="shaders\default.vert" />
|
||||||
<None Include="shaders\deferred_lighting.frag" />
|
<None Include="shaders\deferred_lighting.frag" />
|
||||||
<None Include="shaders\deferred_lighting.vert" />
|
<None Include="shaders\deferred_lighting.vert" />
|
||||||
|
<None Include="shaders\gbuffer_debug.frag" />
|
||||||
|
<None Include="shaders\gbuffer_debug.vert" />
|
||||||
<None Include="shaders\geometry.frag" />
|
<None Include="shaders\geometry.frag" />
|
||||||
<None Include="shaders\geometry.vert" />
|
<None Include="shaders\geometry.vert" />
|
||||||
<None Include="shaders\geometry_rig.vert" />
|
<None Include="shaders\geometry_rig.vert" />
|
||||||
|
|||||||
@@ -43,6 +43,15 @@
|
|||||||
<ClCompile Include="Physics.cpp">
|
<ClCompile Include="Physics.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="CollisionMesh.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="Ragdoll.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="ResourceManager.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="Sound.cpp">
|
<ClCompile Include="Sound.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@@ -120,6 +129,18 @@
|
|||||||
<ClInclude Include="Physics.h">
|
<ClInclude Include="Physics.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="CollisionMesh.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="Ragdoll.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="SurfaceTexture.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="TexturePainter.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="Gui.h">
|
<ClInclude Include="Gui.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
@@ -162,6 +183,9 @@
|
|||||||
<ClInclude Include="MeshData.h">
|
<ClInclude Include="MeshData.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="Light.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="Lighting.h">
|
<ClInclude Include="Lighting.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
@@ -205,6 +229,18 @@
|
|||||||
<None Include="shaders\deferred_lighting.frag">
|
<None Include="shaders\deferred_lighting.frag">
|
||||||
<Filter>Shaders</Filter>
|
<Filter>Shaders</Filter>
|
||||||
</None>
|
</None>
|
||||||
|
<None Include="shaders\gbuffer_debug.frag">
|
||||||
|
<Filter>Shaders</Filter>
|
||||||
|
</None>
|
||||||
|
<None Include="shaders\bloom_blur.frag">
|
||||||
|
<Filter>Shaders</Filter>
|
||||||
|
</None>
|
||||||
|
<None Include="shaders\bloom_extract.frag">
|
||||||
|
<Filter>Shaders</Filter>
|
||||||
|
</None>
|
||||||
|
<None Include="shaders\gbuffer_debug.vert">
|
||||||
|
<Filter>Shaders</Filter>
|
||||||
|
</None>
|
||||||
<None Include="shaders\deferred_lighting.vert">
|
<None Include="shaders\deferred_lighting.vert">
|
||||||
<Filter>Shaders</Filter>
|
<Filter>Shaders</Filter>
|
||||||
</None>
|
</None>
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ int main() {
|
|||||||
engine->Initiate();
|
engine->Initiate();
|
||||||
|
|
||||||
std::cout << "engine stopped\n";
|
std::cout << "engine stopped\n";
|
||||||
|
delete resourcemanager;
|
||||||
delete window;
|
delete window;
|
||||||
delete mouse;
|
delete mouse;
|
||||||
delete keyboard;
|
delete keyboard;
|
||||||
|
|||||||
+27
-12
@@ -6,6 +6,7 @@
|
|||||||
#include<glm/glm.hpp>
|
#include<glm/glm.hpp>
|
||||||
|
|
||||||
#include<string>
|
#include<string>
|
||||||
|
#include<unordered_map>
|
||||||
#include<fstream>
|
#include<fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -19,35 +20,49 @@ public:
|
|||||||
Shader(const char* vertexFile, const char* fragFile);
|
Shader(const char* vertexFile, const char* fragFile);
|
||||||
|
|
||||||
void Set1F(const char* uniform, float value) {
|
void Set1F(const char* uniform, float value) {
|
||||||
GLint location = glGetUniformLocation(Shader::ID, uniform);
|
glUniform1f(GetLocation(uniform), value);
|
||||||
glUniform1f(location, value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void Set2F(const char* uniform, glm::vec2 value) {
|
void Set2F(const char* uniform, glm::vec2 value) {
|
||||||
GLint location = glGetUniformLocation(Shader::ID, uniform);
|
glUniform2f(GetLocation(uniform), value.x,value.y);
|
||||||
glUniform2f(location, value.x,value.y);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void Set3F(const char* uniform, glm::vec3 value) {
|
void Set3F(const char* uniform, glm::vec3 value) {
|
||||||
GLint location = glGetUniformLocation(Shader::ID, uniform);
|
glUniform3f(GetLocation(uniform), value.x,value.y,value.z);
|
||||||
glUniform3f(location, value.x,value.y,value.z);
|
|
||||||
};
|
};
|
||||||
void Set4F(const char* uniform, glm::vec4 value) {
|
void Set4F(const char* uniform, glm::vec4 value) {
|
||||||
GLint location = glGetUniformLocation(Shader::ID, uniform);
|
glUniform4f(GetLocation(uniform), value.x, value.y, value.z, value.w);
|
||||||
glUniform4f(location, value.x, value.y, value.z, value.w);
|
|
||||||
};
|
};
|
||||||
void SetMat4(const char* uniform, glm::mat4 value) {
|
void SetMat4(const char* uniform, glm::mat4 value) {
|
||||||
GLint location = glGetUniformLocation(Shader::ID, uniform);
|
glUniformMatrix4fv(GetLocation(uniform),1,GL_FALSE,&value[0][0]);
|
||||||
glUniformMatrix4fv(location,1,GL_FALSE,&value[0][0]);
|
};
|
||||||
|
|
||||||
|
// Uploads a whole uniform array in one call
|
||||||
|
void SetMat4Array(const char* uniform, const glm::mat4* values, int count) {
|
||||||
|
glUniformMatrix4fv(GetLocation(uniform),count,GL_FALSE,&values[0][0][0]);
|
||||||
};
|
};
|
||||||
|
|
||||||
void SetInt(const char* uniform, GLuint value) {
|
void SetInt(const char* uniform, GLuint value) {
|
||||||
GLint location = glGetUniformLocation(Shader::ID, uniform);
|
glUniform1i(GetLocation(uniform), value);
|
||||||
glUniform1i(location, value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void Activate();
|
void Activate();
|
||||||
void Delete();
|
void Delete();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Asking the driver is slow and locations never change once linked.
|
||||||
|
// Uniforms the shader doesn't have cache as -1, which glUniform ignores.
|
||||||
|
GLint GetLocation(const char* uniform) {
|
||||||
|
auto existing = uniformLocations.find(uniform);
|
||||||
|
if (existing != uniformLocations.end())
|
||||||
|
return existing->second;
|
||||||
|
|
||||||
|
GLint location = glGetUniformLocation(ID, uniform);
|
||||||
|
uniformLocations.emplace(uniform, location);
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<std::string, GLint> uniformLocations;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
+3
-3
@@ -12,7 +12,6 @@ uniform vec2 pixelScale;
|
|||||||
uniform vec2 pixelPos;
|
uniform vec2 pixelPos;
|
||||||
|
|
||||||
uniform vec4 Color = {0.0f,0.0f,0.0f,1.0f};
|
uniform vec4 Color = {0.0f,0.0f,0.0f,1.0f};
|
||||||
uniform vec4 BackgroundColor = {0.0f,0.0f,0.0f,1.0f};
|
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
@@ -27,10 +26,11 @@ void main()
|
|||||||
vec2 absrelcoord = abs(fragPosTopLeft - pixelScale/2 - pixelPos);
|
vec2 absrelcoord = abs(fragPosTopLeft - pixelScale/2 - pixelPos);
|
||||||
|
|
||||||
if (!(absrelcoord.x < r1.x || absrelcoord.y < r1.y)) {
|
if (!(absrelcoord.x < r1.x || absrelcoord.y < r1.y)) {
|
||||||
if (pow(absrelcoord.y-r1.y,2) > pow(roundingfactor,2) - pow((absrelcoord.x-r1.x),2)) {
|
vec2 cornerOffset = absrelcoord - r1;
|
||||||
|
if (dot(cornerOffset, cornerOffset) > roundingfactor * roundingfactor) {
|
||||||
discard;
|
discard;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ uniform vec2 pixelScale;
|
|||||||
uniform vec2 pixelPos;
|
uniform vec2 pixelPos;
|
||||||
|
|
||||||
uniform vec4 Color = {0.0f,0.0f,0.0f,1.0f};
|
uniform vec4 Color = {0.0f,0.0f,0.0f,1.0f};
|
||||||
uniform vec4 BackgroundColor = {0.0f,0.0f,0.0f,1.0f};
|
|
||||||
|
|
||||||
uniform sampler2D tex;
|
uniform sampler2D tex;
|
||||||
|
|
||||||
@@ -29,10 +28,11 @@ void main()
|
|||||||
vec2 absrelcoord = abs(fragPosTopLeft - pixelScale/2 - pixelPos);
|
vec2 absrelcoord = abs(fragPosTopLeft - pixelScale/2 - pixelPos);
|
||||||
|
|
||||||
if (!(absrelcoord.x < r1.x || absrelcoord.y < r1.y)) {
|
if (!(absrelcoord.x < r1.x || absrelcoord.y < r1.y)) {
|
||||||
if (pow(absrelcoord.y-r1.y,2) > pow(roundingfactor,2) - pow((absrelcoord.x-r1.x),2)) {
|
vec2 cornerOffset = absrelcoord - r1;
|
||||||
|
if (dot(cornerOffset, cornerOffset) > roundingfactor * roundingfactor) {
|
||||||
discard;
|
discard;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ uniform vec2 pixelScale;
|
|||||||
uniform float z;
|
uniform float z;
|
||||||
uniform float rotation;
|
uniform float rotation;
|
||||||
|
|
||||||
|
uniform int uvMode = 0; // 0 = stretch, 1 = tile
|
||||||
|
uniform vec3 tileScale = vec3(1.0);
|
||||||
|
uniform vec2 uvOffset = vec2(0.0);
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
vec2 unitPos = aPos * 0.5 + 0.5;
|
vec2 unitPos = aPos * 0.5 + 0.5;
|
||||||
@@ -20,5 +24,7 @@ void main()
|
|||||||
|
|
||||||
gl_Position = vec4(ndc, 0.0f, 1.0f);
|
gl_Position = vec4(ndc, 0.0f, 1.0f);
|
||||||
|
|
||||||
TexCoords = aTexCoords;
|
// Screen y runs down here, so flip v to keep images the right way up
|
||||||
|
vec2 uv = vec2(aTexCoords.x, 1.0 - aTexCoords.y);
|
||||||
|
TexCoords = (uvMode == 1 ? uv * tileScale.xy : uv) + uvOffset;
|
||||||
}
|
}
|
||||||
@@ -11,12 +11,14 @@ out vec3 FragPos;
|
|||||||
uniform mat4 view;
|
uniform mat4 view;
|
||||||
uniform mat4 proj;
|
uniform mat4 proj;
|
||||||
|
|
||||||
|
uniform int uvMode = 0; // 0 = stretch, 1 = tile
|
||||||
|
uniform vec3 tileScale = vec3(1.0);
|
||||||
|
uniform vec2 uvOffset = vec2(0.0);
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
FragPos = vec3(modl * vec4(aPos, 1.0));
|
FragPos = vec3(modl * vec4(aPos, 1.0));
|
||||||
normal = aNormal;
|
normal = aNormal;
|
||||||
texCoord = aTex;
|
texCoord = (uvMode == 1 ? aPos.xy * tileScale.xy : aTex) + uvOffset;
|
||||||
gl_Position = proj* view * modl * vec4(aPos,1.0f);
|
gl_Position = proj* view * modl * vec4(aPos,1.0f);
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,8 @@ out vec4 FragColor;
|
|||||||
in vec2 TexCoords;
|
in vec2 TexCoords;
|
||||||
|
|
||||||
uniform sampler2D screenTexture;
|
uniform sampler2D screenTexture;
|
||||||
|
uniform sampler2D bloomTexture;
|
||||||
|
uniform float bloomStrength = 0.0f;
|
||||||
uniform float brightness = 0.0f;
|
uniform float brightness = 0.0f;
|
||||||
uniform float colorContrast = 0.0f;
|
uniform float colorContrast = 0.0f;
|
||||||
uniform float saturation = 1.0f;
|
uniform float saturation = 1.0f;
|
||||||
@@ -21,21 +23,19 @@ vec3 ACESFilm(vec3 x)
|
|||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
vec3 originalcolor = texture(screenTexture, TexCoords).rgb;
|
// Added before tonemapping, while everything is still in HDR
|
||||||
|
vec3 originalcolor = texture(screenTexture, TexCoords).rgb
|
||||||
|
+ texture(bloomTexture, TexCoords).rgb * bloomStrength;
|
||||||
vec3 finalcolor = ACESFilm(originalcolor * exposure);
|
vec3 finalcolor = ACESFilm(originalcolor * exposure);
|
||||||
|
|
||||||
const float gamma = 2.2;
|
const float gamma = 2.2;
|
||||||
finalcolor = pow(finalcolor, vec3(1.0 / gamma));
|
finalcolor = pow(finalcolor, vec3(1.0 / gamma));
|
||||||
|
|
||||||
vec3 contrast = vec3(1.0f) - finalcolor;
|
|
||||||
|
|
||||||
finalcolor = mix(finalcolor,vec3(1.0f) - finalcolor,colorContrast);
|
finalcolor = mix(finalcolor,vec3(1.0f) - finalcolor,colorContrast);
|
||||||
|
|
||||||
float average = (finalcolor[0]+finalcolor[1]+finalcolor[2])/3.0f;
|
float average = (finalcolor[0]+finalcolor[1]+finalcolor[2])/3.0f;
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++) {
|
finalcolor = mix(vec3(average), finalcolor, saturation);
|
||||||
finalcolor[i] = average + saturation * (finalcolor[i] - average);
|
|
||||||
}
|
|
||||||
|
|
||||||
FragColor = vec4(finalcolor,1.0f);
|
FragColor = vec4(finalcolor,1.0f);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#version 330 core
|
||||||
|
out vec4 FragColor;
|
||||||
|
|
||||||
|
in vec2 TexCoords;
|
||||||
|
|
||||||
|
uniform sampler2D image;
|
||||||
|
uniform int horizontal;
|
||||||
|
|
||||||
|
const float weights[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec2 texel = 1.0 / vec2(textureSize(image, 0));
|
||||||
|
vec2 direction = horizontal != 0 ? vec2(texel.x, 0.0) : vec2(0.0, texel.y);
|
||||||
|
|
||||||
|
vec3 color = texture(image, TexCoords).rgb * weights[0];
|
||||||
|
for (int i = 1; i < 5; i++) {
|
||||||
|
color += texture(image, TexCoords + direction * float(i)).rgb * weights[i];
|
||||||
|
color += texture(image, TexCoords - direction * float(i)).rgb * weights[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
FragColor = vec4(color, 1.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#version 330 core
|
||||||
|
out vec4 FragColor;
|
||||||
|
|
||||||
|
in vec2 TexCoords;
|
||||||
|
|
||||||
|
uniform sampler2D screenTexture;
|
||||||
|
uniform float threshold;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
// Averaged, since a quarter size target would otherwise skip most of the screen's pixels
|
||||||
|
vec2 texel = 1.0 / vec2(textureSize(screenTexture, 0));
|
||||||
|
vec3 color = (texture(screenTexture, TexCoords + texel * vec2(-1.0, -1.0)).rgb
|
||||||
|
+ texture(screenTexture, TexCoords + texel * vec2(1.0, -1.0)).rgb
|
||||||
|
+ texture(screenTexture, TexCoords + texel * vec2(-1.0, 1.0)).rgb
|
||||||
|
+ texture(screenTexture, TexCoords + texel * vec2(1.0, 1.0)).rgb) * 0.25;
|
||||||
|
|
||||||
|
// A single blown out specular pixel would otherwise flash a huge glow
|
||||||
|
color = min(color, vec3(20.0));
|
||||||
|
|
||||||
|
// Keeps the hue of whatever is over the threshold, and fades in instead of cutting off
|
||||||
|
float brightness = max(color.r, max(color.g, color.b));
|
||||||
|
float contribution = max(brightness - threshold, 0.0) / max(brightness, 1e-4);
|
||||||
|
|
||||||
|
FragColor = vec4(color * contribution, 1.0);
|
||||||
|
}
|
||||||
+150
-13
@@ -12,7 +12,42 @@ struct DirLight {
|
|||||||
float intensity;
|
float intensity;
|
||||||
};
|
};
|
||||||
|
|
||||||
uniform DirLight dirLight;
|
struct PointLight {
|
||||||
|
vec3 position;
|
||||||
|
vec3 color;
|
||||||
|
float intensity;
|
||||||
|
float radius;
|
||||||
|
int shadowTile;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpotLight {
|
||||||
|
vec3 position;
|
||||||
|
vec3 direction;
|
||||||
|
vec3 color;
|
||||||
|
float intensity;
|
||||||
|
float radius;
|
||||||
|
float innerCos;
|
||||||
|
float outerCos;
|
||||||
|
int shadowTile;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The array sizes have to match Light.h
|
||||||
|
#define MAX_DIRECTIONAL_LIGHTS 4
|
||||||
|
#define MAX_POINT_LIGHTS 16
|
||||||
|
#define MAX_SPOT_LIGHTS 8
|
||||||
|
#define SHADOW_ATLAS_COLUMNS 6
|
||||||
|
#define SHADOW_ATLAS_TILES (SHADOW_ATLAS_COLUMNS * SHADOW_ATLAS_COLUMNS)
|
||||||
|
|
||||||
|
uniform DirLight dirLight; // the sun, the only light with a shadow map
|
||||||
|
uniform DirLight directionalLights[MAX_DIRECTIONAL_LIGHTS];
|
||||||
|
uniform int directionalLightCount = 0;
|
||||||
|
uniform PointLight pointLights[MAX_POINT_LIGHTS];
|
||||||
|
uniform int pointLightCount = 0;
|
||||||
|
uniform SpotLight spotLights[MAX_SPOT_LIGHTS];
|
||||||
|
uniform int spotLightCount = 0;
|
||||||
|
uniform sampler2DShadow localShadowMap;
|
||||||
|
uniform mat4 shadowTileMatrices[SHADOW_ATLAS_TILES];
|
||||||
|
|
||||||
uniform vec3 viewPos;
|
uniform vec3 viewPos;
|
||||||
|
|
||||||
uniform sampler2DShadow shadowMap;
|
uniform sampler2DShadow shadowMap;
|
||||||
@@ -23,6 +58,7 @@ uniform int useTexture = 0;
|
|||||||
uniform float metallic = 0.0;
|
uniform float metallic = 0.0;
|
||||||
uniform float roughness = 0.7;
|
uniform float roughness = 0.7;
|
||||||
uniform float ao = 1.0;
|
uniform float ao = 1.0;
|
||||||
|
uniform float emissive = 0.0;
|
||||||
uniform float envIntensity = 0.8;
|
uniform float envIntensity = 0.8;
|
||||||
|
|
||||||
const float PI = 3.14159265359;
|
const float PI = 3.14159265359;
|
||||||
@@ -72,6 +108,65 @@ vec3 EnvironmentBRDF(vec3 F0, float rough, float NdotV)
|
|||||||
return F0 * AB.x + AB.y;
|
return F0 * AB.x + AB.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Light from one direction, L points from the surface toward the light
|
||||||
|
vec3 ShadeLight(vec3 L, vec3 radiance, vec3 N, vec3 V, vec3 albedo, float metal, float rough, vec3 F0, float NdotV)
|
||||||
|
{
|
||||||
|
float NdotL = max(dot(N, L), 0.0);
|
||||||
|
if (NdotL <= 0.0)
|
||||||
|
return vec3(0.0);
|
||||||
|
|
||||||
|
vec3 H = normalize(V + L);
|
||||||
|
vec3 F = FresnelSchlick(max(dot(H, V), 0.0), F0);
|
||||||
|
float NDF = DistributionGGX(N, H, rough);
|
||||||
|
float G = GeometrySmith(N, V, L, rough);
|
||||||
|
vec3 specular = (NDF * G * F) / max(4.0 * NdotV * NdotL, 1e-4);
|
||||||
|
vec3 kD = (vec3(1.0) - F) * (1.0 - metal);
|
||||||
|
return (kD * albedo / PI + specular) * radiance * NdotL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inverse square falloff, windowed so it reaches exactly zero at the radius
|
||||||
|
float PointAttenuation(float distanceSquared, float radius)
|
||||||
|
{
|
||||||
|
float ratio = distanceSquared / (radius * radius);
|
||||||
|
float window = clamp(1.0 - ratio * ratio, 0.0, 1.0);
|
||||||
|
return window * window / max(distanceSquared, 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Which of a point light's 6 atlas tiles covers offset, the order is set in RenderLocalShadows
|
||||||
|
int PointShadowFace(vec3 offset)
|
||||||
|
{
|
||||||
|
vec3 size = abs(offset);
|
||||||
|
if (size.x >= size.y && size.x >= size.z) return offset.x > 0.0 ? 0 : 1;
|
||||||
|
if (size.y >= size.z) return offset.y > 0.0 ? 2 : 3;
|
||||||
|
return offset.z > 0.0 ? 4 : 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1 = lit, 0 = shadowed. Stays inside the tile so the filter can't pick up its neighbours.
|
||||||
|
float LocalShadow(int tile, vec3 worldPos, vec3 N)
|
||||||
|
{
|
||||||
|
vec4 clipPos = shadowTileMatrices[tile] * vec4(worldPos + N * 0.04, 1.0);
|
||||||
|
if (clipPos.w <= 0.0)
|
||||||
|
return 1.0;
|
||||||
|
|
||||||
|
vec3 projCoords = clipPos.xyz / clipPos.w * 0.5 + 0.5;
|
||||||
|
if (any(lessThan(projCoords, vec3(0.0))) || any(greaterThan(projCoords, vec3(1.0))))
|
||||||
|
return 1.0;
|
||||||
|
|
||||||
|
float tileSize = 1.0 / float(SHADOW_ATLAS_COLUMNS);
|
||||||
|
vec2 tileOrigin = vec2(float(tile % SHADOW_ATLAS_COLUMNS), float(tile / SHADOW_ATLAS_COLUMNS)) * tileSize;
|
||||||
|
vec2 texelSize = 1.0 / vec2(textureSize(localShadowMap, 0));
|
||||||
|
vec2 uv = tileOrigin + projCoords.xy * tileSize;
|
||||||
|
|
||||||
|
float shadow = 0.0;
|
||||||
|
for (int x = -1; x <= 1; x++) {
|
||||||
|
for (int y = -1; y <= 1; y++) {
|
||||||
|
vec2 sampleUV = clamp(uv + vec2(x, y) * texelSize, tileOrigin + texelSize, tileOrigin + tileSize - texelSize);
|
||||||
|
shadow += texture(localShadowMap, vec3(sampleUV, projCoords.z - 0.0002));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shadow / 9.0;
|
||||||
|
}
|
||||||
|
|
||||||
float ShadowCalculation(vec4 fragPosLightSpace, vec3 N, vec3 L)
|
float ShadowCalculation(vec4 fragPosLightSpace, vec3 N, vec3 L)
|
||||||
{
|
{
|
||||||
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
|
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
|
||||||
@@ -112,21 +207,63 @@ void main()
|
|||||||
|
|
||||||
vec3 N = normalize(normal);
|
vec3 N = normalize(normal);
|
||||||
vec3 V = normalize(viewPos - FragPos);
|
vec3 V = normalize(viewPos - FragPos);
|
||||||
vec3 L = normalize(-dirLight.direction);
|
|
||||||
vec3 H = normalize(V + L);
|
|
||||||
float NdotV = max(dot(N, V), 1e-4);
|
float NdotV = max(dot(N, V), 1e-4);
|
||||||
float NdotL = max(dot(N, L), 0.0);
|
|
||||||
|
|
||||||
vec3 F0 = mix(vec3(0.04), albedo, metal);
|
vec3 F0 = mix(vec3(0.04), albedo, metal);
|
||||||
vec3 F = FresnelSchlick(max(dot(H, V), 0.0), F0);
|
vec3 direct = vec3(0.0);
|
||||||
float NDF = DistributionGGX(N, H, rough);
|
|
||||||
float G = GeometrySmith(N, V, L, rough);
|
|
||||||
vec3 specular = (NDF * G * F) / max(4.0 * NdotV * NdotL, 1e-4);
|
|
||||||
vec3 kD = (vec3(1.0) - F) * (1.0 - metal);
|
|
||||||
vec3 radiance = dirLight.color * dirLight.intensity;
|
|
||||||
|
|
||||||
float shadow = ShadowCalculation(fragposlightspace, N, L);
|
// Facing away from the sun has no direct light, so skip the BRDF and the 9 shadow samples
|
||||||
vec3 direct = (kD * albedo / PI + specular) * radiance * NdotL * shadow;
|
vec3 sunL = normalize(-dirLight.direction);
|
||||||
|
if (dot(N, sunL) > 0.0) {
|
||||||
|
float shadow = ShadowCalculation(fragposlightspace, N, sunL);
|
||||||
|
direct += ShadeLight(sunL, dirLight.color * dirLight.intensity, N, V, albedo, metal, rough, F0, NdotV) * shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < directionalLightCount; i++) {
|
||||||
|
vec3 L = normalize(-directionalLights[i].direction);
|
||||||
|
vec3 radiance = directionalLights[i].color * directionalLights[i].intensity;
|
||||||
|
direct += ShadeLight(L, radiance, N, V, albedo, metal, rough, F0, NdotV);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < pointLightCount; i++) {
|
||||||
|
vec3 toLight = pointLights[i].position - FragPos;
|
||||||
|
float distanceSquared = dot(toLight, toLight);
|
||||||
|
float radius = pointLights[i].radius;
|
||||||
|
if (distanceSquared >= radius * radius)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
vec3 radiance = pointLights[i].color * pointLights[i].intensity * PointAttenuation(distanceSquared, radius);
|
||||||
|
vec3 L = toLight / sqrt(distanceSquared);
|
||||||
|
if (dot(N, L) <= 0.0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
vec3 lit = ShadeLight(L, radiance, N, V, albedo, metal, rough, F0, NdotV);
|
||||||
|
if (pointLights[i].shadowTile >= 0)
|
||||||
|
lit *= LocalShadow(pointLights[i].shadowTile + PointShadowFace(-toLight), FragPos, N);
|
||||||
|
direct += lit;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < spotLightCount; i++) {
|
||||||
|
vec3 toLight = spotLights[i].position - FragPos;
|
||||||
|
float distanceSquared = dot(toLight, toLight);
|
||||||
|
float radius = spotLights[i].radius;
|
||||||
|
if (distanceSquared >= radius * radius)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
vec3 L = toLight / sqrt(distanceSquared);
|
||||||
|
float cone = smoothstep(spotLights[i].outerCos, spotLights[i].innerCos, dot(-L, spotLights[i].direction));
|
||||||
|
if (cone <= 0.0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (dot(N, L) <= 0.0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
vec3 radiance = spotLights[i].color * spotLights[i].intensity * PointAttenuation(distanceSquared, radius) * cone;
|
||||||
|
vec3 lit = ShadeLight(L, radiance, N, V, albedo, metal, rough, F0, NdotV);
|
||||||
|
if (spotLights[i].shadowTile >= 0)
|
||||||
|
lit *= LocalShadow(spotLights[i].shadowTile, FragPos, N);
|
||||||
|
direct += lit;
|
||||||
|
}
|
||||||
|
|
||||||
vec3 ambientF = FresnelSchlickRoughness(NdotV, F0, rough);
|
vec3 ambientF = FresnelSchlickRoughness(NdotV, F0, rough);
|
||||||
vec3 ambientKD = (vec3(1.0) - ambientF) * (1.0 - metal);
|
vec3 ambientKD = (vec3(1.0) - ambientF) * (1.0 - metal);
|
||||||
@@ -136,5 +273,5 @@ void main()
|
|||||||
vec3 specularIBL = prefiltered * EnvironmentBRDF(F0, rough, NdotV);
|
vec3 specularIBL = prefiltered * EnvironmentBRDF(F0, rough, NdotV);
|
||||||
vec3 ambient = (ambientKD * irradiance * albedo + specularIBL) * ao;
|
vec3 ambient = (ambientKD * irradiance * albedo + specularIBL) * ao;
|
||||||
|
|
||||||
FragColor = vec4(direct + ambient, albedoSample.a);
|
FragColor = vec4(direct + ambient + albedo * emissive * 4.0, albedoSample.a);
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-5
@@ -13,11 +13,35 @@ uniform mat4 proj;
|
|||||||
|
|
||||||
uniform mat4 lightSpaceMatrix;
|
uniform mat4 lightSpaceMatrix;
|
||||||
|
|
||||||
|
uniform int uvMode = 0; // 0 = stretch, 1 = tile
|
||||||
|
uniform vec3 tileScale = vec3(1.0);
|
||||||
|
uniform vec2 uvOffset = vec2(0.0);
|
||||||
|
|
||||||
|
// Same direction as transpose(inverse(m)) for normals, without the per vertex inverse
|
||||||
|
mat3 CofactorMatrix(mat3 m)
|
||||||
|
{
|
||||||
|
return mat3(cross(m[1], m[2]), cross(m[2], m[0]), cross(m[0], m[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tiling ignores the mesh's uvs and projects from whichever axis the surface faces,
|
||||||
|
// so every face of a stretched cube repeats at the same size
|
||||||
|
vec2 SurfaceUV()
|
||||||
|
{
|
||||||
|
if (uvMode == 0) return aTex + uvOffset;
|
||||||
|
|
||||||
|
vec3 n = abs(aNormal);
|
||||||
|
vec3 p = aPos * tileScale;
|
||||||
|
if (n.y >= n.x && n.y >= n.z) return p.xz + uvOffset;
|
||||||
|
if (n.x >= n.z) return p.zy + uvOffset;
|
||||||
|
return p.xy + uvOffset;
|
||||||
|
}
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
FragPos = vec3(modl * vec4(aPos, 1.0));
|
vec4 worldPos = modl * vec4(aPos, 1.0);
|
||||||
normal = transpose(inverse(mat3(modl))) * aNormal;
|
FragPos = worldPos.xyz;
|
||||||
texCoord = aTex;
|
normal = CofactorMatrix(mat3(modl)) * aNormal;
|
||||||
fragposlightspace = lightSpaceMatrix * vec4(FragPos, 1.0);
|
texCoord = SurfaceUV();
|
||||||
gl_Position = proj* view * modl * vec4(aPos,1.0f);
|
fragposlightspace = lightSpaceMatrix * worldPos;
|
||||||
|
gl_Position = proj * view * worldPos;
|
||||||
}
|
}
|
||||||
+251
-37
@@ -3,59 +3,273 @@ out vec4 FragColor;
|
|||||||
|
|
||||||
in vec2 TexCoord;
|
in vec2 TexCoord;
|
||||||
|
|
||||||
uniform sampler2D gPosition;
|
|
||||||
uniform sampler2D gNormal;
|
|
||||||
uniform sampler2D gAlbedo;
|
|
||||||
uniform sampler2D gLightSpacePos;
|
|
||||||
uniform sampler2DShadow shadowMap;
|
|
||||||
|
|
||||||
struct DirLight {
|
struct DirLight {
|
||||||
vec3 direction;
|
vec3 direction;
|
||||||
vec3 ambient;
|
vec3 color;
|
||||||
vec3 diffuse;
|
float intensity;
|
||||||
vec3 specular;
|
|
||||||
};
|
};
|
||||||
uniform DirLight dirLight;
|
|
||||||
uniform vec3 viewPos;
|
|
||||||
|
|
||||||
float ShadowCalculation(vec4 fragPosLightSpace)
|
struct PointLight {
|
||||||
|
vec3 position;
|
||||||
|
vec3 color;
|
||||||
|
float intensity;
|
||||||
|
float radius;
|
||||||
|
int shadowTile;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpotLight {
|
||||||
|
vec3 position;
|
||||||
|
vec3 direction;
|
||||||
|
vec3 color;
|
||||||
|
float intensity;
|
||||||
|
float radius;
|
||||||
|
float innerCos;
|
||||||
|
float outerCos;
|
||||||
|
int shadowTile;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The array sizes have to match Light.h
|
||||||
|
#define MAX_DIRECTIONAL_LIGHTS 4
|
||||||
|
#define MAX_POINT_LIGHTS 16
|
||||||
|
#define MAX_SPOT_LIGHTS 8
|
||||||
|
#define SHADOW_ATLAS_COLUMNS 6
|
||||||
|
#define SHADOW_ATLAS_TILES (SHADOW_ATLAS_COLUMNS * SHADOW_ATLAS_COLUMNS)
|
||||||
|
|
||||||
|
uniform DirLight dirLight; // the sun, the only light with a shadow map
|
||||||
|
uniform DirLight directionalLights[MAX_DIRECTIONAL_LIGHTS];
|
||||||
|
uniform int directionalLightCount = 0;
|
||||||
|
uniform PointLight pointLights[MAX_POINT_LIGHTS];
|
||||||
|
uniform int pointLightCount = 0;
|
||||||
|
uniform SpotLight spotLights[MAX_SPOT_LIGHTS];
|
||||||
|
uniform int spotLightCount = 0;
|
||||||
|
uniform sampler2DShadow localShadowMap;
|
||||||
|
uniform mat4 shadowTileMatrices[SHADOW_ATLAS_TILES];
|
||||||
|
|
||||||
|
uniform vec3 viewPos;
|
||||||
|
uniform mat4 lightSpaceMatrix;
|
||||||
|
|
||||||
|
uniform sampler2D gPosition;
|
||||||
|
uniform sampler2D gNormal;
|
||||||
|
uniform sampler2D gAlbedo;
|
||||||
|
uniform sampler2D gMaterial;
|
||||||
|
uniform sampler2DShadow shadowMap;
|
||||||
|
uniform samplerCube environmentMap;
|
||||||
|
uniform float envIntensity = 0.8;
|
||||||
|
|
||||||
|
const float PI = 3.14159265359;
|
||||||
|
|
||||||
|
float DistributionGGX(vec3 N, vec3 H, float rough)
|
||||||
{
|
{
|
||||||
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
|
float a = rough * rough;
|
||||||
projCoords = projCoords * 0.5 + 0.5;
|
float a2 = a * a;
|
||||||
if (projCoords.z > 1.0) return 0.0;
|
float NdotH = max(dot(N, H), 0.0);
|
||||||
return texture(shadowMap, vec3(projCoords.xy, projCoords.z));
|
float NdotH2 = NdotH * NdotH;
|
||||||
|
float denom = NdotH2 * (a2 - 1.0) + 1.0;
|
||||||
|
return a2 / max(PI * denom * denom, 1e-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir, vec4 fragPosLS, vec3 albedo)
|
float GeometrySchlickGGX(float NdotV, float rough)
|
||||||
{
|
{
|
||||||
vec3 lightDir = normalize(-light.direction);
|
float r = rough + 1.0;
|
||||||
float diff = max(dot(normal, lightDir), 0.0);
|
float k = (r * r) / 8.0;
|
||||||
vec3 halfwayDir = normalize(lightDir + viewDir);
|
return NdotV / (NdotV * (1.0 - k) + k);
|
||||||
float spec = pow(max(dot(normal, halfwayDir), 0.0), 32.0);
|
}
|
||||||
|
|
||||||
vec3 ambient = light.ambient * albedo;
|
float GeometrySmith(vec3 N, vec3 V, vec3 L, float rough)
|
||||||
vec3 diffuse = light.diffuse * diff * albedo;
|
{
|
||||||
vec3 specular = light.specular * spec;
|
float NdotV = max(dot(N, V), 0.0);
|
||||||
|
float NdotL = max(dot(N, L), 0.0);
|
||||||
|
return GeometrySchlickGGX(NdotV, rough) * GeometrySchlickGGX(NdotL, rough);
|
||||||
|
}
|
||||||
|
|
||||||
float shadow = ShadowCalculation(fragPosLS);
|
vec3 FresnelSchlick(float cosTheta, vec3 F0)
|
||||||
return ambient + (diffuse + specular) * shadow;
|
{
|
||||||
|
return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 FresnelSchlickRoughness(float cosTheta, vec3 F0, float rough)
|
||||||
|
{
|
||||||
|
vec3 f = max(vec3(1.0 - rough), F0);
|
||||||
|
return F0 + (f - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 EnvironmentBRDF(vec3 F0, float rough, float NdotV)
|
||||||
|
{
|
||||||
|
const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022);
|
||||||
|
const vec4 c1 = vec4(1.0, 0.0425, 1.04, -0.04);
|
||||||
|
vec4 r = rough * c0 + c1;
|
||||||
|
float a004 = min(r.x * r.x, exp2(-9.28 * NdotV)) * r.x + r.y;
|
||||||
|
vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw;
|
||||||
|
return F0 * AB.x + AB.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Light from one direction, L points from the surface toward the light
|
||||||
|
vec3 ShadeLight(vec3 L, vec3 radiance, vec3 N, vec3 V, vec3 albedo, float metal, float rough, vec3 F0, float NdotV)
|
||||||
|
{
|
||||||
|
float NdotL = max(dot(N, L), 0.0);
|
||||||
|
if (NdotL <= 0.0)
|
||||||
|
return vec3(0.0);
|
||||||
|
|
||||||
|
vec3 H = normalize(V + L);
|
||||||
|
vec3 F = FresnelSchlick(max(dot(H, V), 0.0), F0);
|
||||||
|
float NDF = DistributionGGX(N, H, rough);
|
||||||
|
float G = GeometrySmith(N, V, L, rough);
|
||||||
|
vec3 specular = (NDF * G * F) / max(4.0 * NdotV * NdotL, 1e-4);
|
||||||
|
vec3 kD = (vec3(1.0) - F) * (1.0 - metal);
|
||||||
|
return (kD * albedo / PI + specular) * radiance * NdotL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inverse square falloff, windowed so it reaches exactly zero at the radius
|
||||||
|
float PointAttenuation(float distanceSquared, float radius)
|
||||||
|
{
|
||||||
|
float ratio = distanceSquared / (radius * radius);
|
||||||
|
float window = clamp(1.0 - ratio * ratio, 0.0, 1.0);
|
||||||
|
return window * window / max(distanceSquared, 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Which of a point light's 6 atlas tiles covers offset, the order is set in RenderLocalShadows
|
||||||
|
int PointShadowFace(vec3 offset)
|
||||||
|
{
|
||||||
|
vec3 size = abs(offset);
|
||||||
|
if (size.x >= size.y && size.x >= size.z) return offset.x > 0.0 ? 0 : 1;
|
||||||
|
if (size.y >= size.z) return offset.y > 0.0 ? 2 : 3;
|
||||||
|
return offset.z > 0.0 ? 4 : 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1 = lit, 0 = shadowed. Stays inside the tile so the filter can't pick up its neighbours.
|
||||||
|
float LocalShadow(int tile, vec3 worldPos, vec3 N)
|
||||||
|
{
|
||||||
|
vec4 clipPos = shadowTileMatrices[tile] * vec4(worldPos + N * 0.04, 1.0);
|
||||||
|
if (clipPos.w <= 0.0)
|
||||||
|
return 1.0;
|
||||||
|
|
||||||
|
vec3 projCoords = clipPos.xyz / clipPos.w * 0.5 + 0.5;
|
||||||
|
if (any(lessThan(projCoords, vec3(0.0))) || any(greaterThan(projCoords, vec3(1.0))))
|
||||||
|
return 1.0;
|
||||||
|
|
||||||
|
float tileSize = 1.0 / float(SHADOW_ATLAS_COLUMNS);
|
||||||
|
vec2 tileOrigin = vec2(float(tile % SHADOW_ATLAS_COLUMNS), float(tile / SHADOW_ATLAS_COLUMNS)) * tileSize;
|
||||||
|
vec2 texelSize = 1.0 / vec2(textureSize(localShadowMap, 0));
|
||||||
|
vec2 uv = tileOrigin + projCoords.xy * tileSize;
|
||||||
|
|
||||||
|
float shadow = 0.0;
|
||||||
|
for (int x = -1; x <= 1; x++) {
|
||||||
|
for (int y = -1; y <= 1; y++) {
|
||||||
|
vec2 sampleUV = clamp(uv + vec2(x, y) * texelSize, tileOrigin + texelSize, tileOrigin + tileSize - texelSize);
|
||||||
|
shadow += texture(localShadowMap, vec3(sampleUV, projCoords.z - 0.0002));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shadow / 9.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float ShadowCalculation(vec4 fragPosLightSpace, vec3 N, vec3 L)
|
||||||
|
{
|
||||||
|
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
|
||||||
|
projCoords = projCoords * 0.5 + 0.5;
|
||||||
|
|
||||||
|
if (projCoords.z > 1.0 || any(lessThan(projCoords, vec3(0.0))) ||
|
||||||
|
any(greaterThan(projCoords, vec3(1.0))))
|
||||||
|
return 1.0;
|
||||||
|
|
||||||
|
float bias = max(0.004 * (1.0 - dot(N, L)), 0.001);
|
||||||
|
vec2 texelSize = 1.0 / vec2(textureSize(shadowMap, 0));
|
||||||
|
float shadow = 0.0;
|
||||||
|
|
||||||
|
for (int x = -1; x <= 1; x++) {
|
||||||
|
for (int y = -1; y <= 1; y++) {
|
||||||
|
vec2 offset = vec2(x, y) * texelSize;
|
||||||
|
shadow += texture(shadowMap, vec3(projCoords.xy + offset, projCoords.z - bias));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shadow / 9.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
vec3 fragPos = texture(gPosition, TexCoord).rgb;
|
vec3 normal = texture(gNormal, TexCoord).rgb;
|
||||||
vec3 normal = normalize(texture(gNormal, TexCoord).rgb);
|
|
||||||
vec4 albedaSample = texture(gAlbedo, TexCoord);
|
|
||||||
vec4 fragPosLS = texture(gLightSpacePos, TexCoord);
|
|
||||||
|
|
||||||
// Sky (no geometry written) — output clear color
|
// Nothing was written here, keep the skybox underneath
|
||||||
if (length(normal) < 0.1) {
|
if (dot(normal, normal) < 0.01)
|
||||||
FragColor = vec4(0.0, 0.0, 0.0, 0.0);
|
discard;
|
||||||
return;
|
|
||||||
|
vec3 FragPos = texture(gPosition, TexCoord).rgb;
|
||||||
|
vec4 albedoSample = texture(gAlbedo, TexCoord);
|
||||||
|
vec4 material = texture(gMaterial, TexCoord);
|
||||||
|
|
||||||
|
vec3 albedo = albedoSample.rgb;
|
||||||
|
float metal = material.r;
|
||||||
|
float rough = material.g;
|
||||||
|
float ao = material.b;
|
||||||
|
|
||||||
|
vec3 N = normalize(normal);
|
||||||
|
vec3 V = normalize(viewPos - FragPos);
|
||||||
|
float NdotV = max(dot(N, V), 1e-4);
|
||||||
|
|
||||||
|
vec3 F0 = mix(vec3(0.04), albedo, metal);
|
||||||
|
vec3 direct = vec3(0.0);
|
||||||
|
|
||||||
|
// Facing away from the sun has no direct light, so skip the BRDF and the 9 shadow samples
|
||||||
|
vec3 sunL = normalize(-dirLight.direction);
|
||||||
|
if (dot(N, sunL) > 0.0) {
|
||||||
|
float shadow = ShadowCalculation(lightSpaceMatrix * vec4(FragPos, 1.0), N, sunL);
|
||||||
|
direct += ShadeLight(sunL, dirLight.color * dirLight.intensity, N, V, albedo, metal, rough, F0, NdotV) * shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 viewDir = normalize(viewPos - fragPos);
|
for (int i = 0; i < directionalLightCount; i++) {
|
||||||
vec3 lit = CalcDirLight(dirLight, normal, viewDir, fragPosLS, albedaSample.rgb);
|
vec3 L = normalize(-directionalLights[i].direction);
|
||||||
|
vec3 radiance = directionalLights[i].color * directionalLights[i].intensity;
|
||||||
|
direct += ShadeLight(L, radiance, N, V, albedo, metal, rough, F0, NdotV);
|
||||||
|
}
|
||||||
|
|
||||||
FragColor = vec4(lit, albedaSample.a);
|
for (int i = 0; i < pointLightCount; i++) {
|
||||||
|
vec3 toLight = pointLights[i].position - FragPos;
|
||||||
|
float distanceSquared = dot(toLight, toLight);
|
||||||
|
float radius = pointLights[i].radius;
|
||||||
|
if (distanceSquared >= radius * radius)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
vec3 radiance = pointLights[i].color * pointLights[i].intensity * PointAttenuation(distanceSquared, radius);
|
||||||
|
vec3 L = toLight / sqrt(distanceSquared);
|
||||||
|
if (dot(N, L) <= 0.0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
vec3 lit = ShadeLight(L, radiance, N, V, albedo, metal, rough, F0, NdotV);
|
||||||
|
if (pointLights[i].shadowTile >= 0)
|
||||||
|
lit *= LocalShadow(pointLights[i].shadowTile + PointShadowFace(-toLight), FragPos, N);
|
||||||
|
direct += lit;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < spotLightCount; i++) {
|
||||||
|
vec3 toLight = spotLights[i].position - FragPos;
|
||||||
|
float distanceSquared = dot(toLight, toLight);
|
||||||
|
float radius = spotLights[i].radius;
|
||||||
|
if (distanceSquared >= radius * radius)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
vec3 L = toLight / sqrt(distanceSquared);
|
||||||
|
float cone = smoothstep(spotLights[i].outerCos, spotLights[i].innerCos, dot(-L, spotLights[i].direction));
|
||||||
|
if (cone <= 0.0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (dot(N, L) <= 0.0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
vec3 radiance = spotLights[i].color * spotLights[i].intensity * PointAttenuation(distanceSquared, radius) * cone;
|
||||||
|
vec3 lit = ShadeLight(L, radiance, N, V, albedo, metal, rough, F0, NdotV);
|
||||||
|
if (spotLights[i].shadowTile >= 0)
|
||||||
|
lit *= LocalShadow(spotLights[i].shadowTile, FragPos, N);
|
||||||
|
direct += lit;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 ambientF = FresnelSchlickRoughness(NdotV, F0, rough);
|
||||||
|
vec3 ambientKD = (vec3(1.0) - ambientF) * (1.0 - metal);
|
||||||
|
vec3 irradiance = pow(textureLod(environmentMap, N, 7.0).rgb, vec3(2.2)) * envIntensity;
|
||||||
|
vec3 R = reflect(-V, N);
|
||||||
|
vec3 prefiltered = pow(textureLod(environmentMap, R, rough * 7.0).rgb, vec3(2.2)) * envIntensity;
|
||||||
|
vec3 specularIBL = prefiltered * EnvironmentBRDF(F0, rough, NdotV);
|
||||||
|
vec3 ambient = (ambientKD * irradiance * albedo + specularIBL) * ao;
|
||||||
|
|
||||||
|
vec3 emission = albedo * material.a * 4.0;
|
||||||
|
|
||||||
|
FragColor = vec4(direct + ambient + emission, albedoSample.a);
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-18
@@ -6,36 +6,46 @@ in vec2 TexCoord;
|
|||||||
uniform sampler2D gPosition;
|
uniform sampler2D gPosition;
|
||||||
uniform sampler2D gNormal;
|
uniform sampler2D gNormal;
|
||||||
uniform sampler2D gAlbedo;
|
uniform sampler2D gAlbedo;
|
||||||
uniform sampler2D gLightSpacePos;
|
uniform sampler2D gMaterial;
|
||||||
uniform sampler2DShadow shadowMap;
|
|
||||||
uniform sampler2D depthTex;
|
uniform sampler2D depthTex;
|
||||||
|
uniform sampler2D shadowTex;
|
||||||
|
uniform sampler2D bloomTex;
|
||||||
|
|
||||||
uniform int debugMode; // 0 = final, 1 = position, 2 = normal, 3 = albedo, 4 = lightspace, 5 = depth
|
uniform float nearPlane;
|
||||||
|
uniform float farPlane;
|
||||||
|
|
||||||
|
uniform int debugMode; // 1 = position, 2 = normal, 3 = albedo, 4 = material, 5 = depth, 6 = shadow map, 7 = bloom
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
|
FragColor = vec4(0.0, 0.0, 0.0, 1.0);
|
||||||
|
|
||||||
if (debugMode == 1) {
|
if (debugMode == 1) {
|
||||||
vec3 v = texture(gPosition, TexCoord).rgb;
|
vec3 v = texture(gPosition, TexCoord).rgb;
|
||||||
FragColor = vec4(v * 0.01 + 0.5, 1.0);
|
FragColor = vec4(v * 0.01 + 0.5, 1.0);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (debugMode == 2) {
|
else if (debugMode == 2) {
|
||||||
vec3 n = normalize(texture(gNormal, TexCoord).rgb);
|
vec3 n = texture(gNormal, TexCoord).rgb;
|
||||||
FragColor = vec4(n * 0.5 + 0.5, 1.0);
|
if (length(n) >= 0.1)
|
||||||
return;
|
FragColor = vec4(normalize(n) * 0.5 + 0.5, 1.0);
|
||||||
}
|
}
|
||||||
if (debugMode == 3) {
|
else if (debugMode == 3) {
|
||||||
FragColor = texture(gAlbedo, TexCoord);
|
FragColor = vec4(pow(texture(gAlbedo, TexCoord).rgb, vec3(1.0 / 2.2)), 1.0);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (debugMode == 4) {
|
else if (debugMode == 4) {
|
||||||
FragColor = texture(gLightSpacePos, TexCoord);
|
FragColor = vec4(texture(gMaterial, TexCoord).rgb, 1.0);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (debugMode == 5) {
|
else if (debugMode == 5) {
|
||||||
float d = texture(depthTex, TexCoord).r;
|
// Perspective depth is nearly 1.0 everywhere, so linearize it and spread it out
|
||||||
|
float z = texture(depthTex, TexCoord).r * 2.0 - 1.0;
|
||||||
|
float linearDepth = (2.0 * nearPlane * farPlane) / (farPlane + nearPlane - z * (farPlane - nearPlane));
|
||||||
|
float d = log(1.0 + linearDepth) / log(1.0 + farPlane);
|
||||||
FragColor = vec4(vec3(d), 1.0);
|
FragColor = vec4(vec3(d), 1.0);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
FragColor = vec4(0.0);
|
else if (debugMode == 6) {
|
||||||
|
FragColor = vec4(vec3(texture(shadowTex, TexCoord).r), 1.0);
|
||||||
|
}
|
||||||
|
else if (debugMode == 7) {
|
||||||
|
FragColor = vec4(texture(bloomTex, TexCoord).rgb, 1.0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-9
@@ -1,23 +1,35 @@
|
|||||||
#version 330 core
|
#version 330 core
|
||||||
layout (location = 0) out vec3 gPosition;
|
layout (location = 0) out vec3 gPosition;
|
||||||
layout (location = 1) out vec3 gNormal;
|
layout (location = 1) out vec3 gNormal;
|
||||||
layout (location = 2) out vec4 gAlbedo;
|
layout (location = 2) out vec4 gAlbedo; // linear rgb + alpha
|
||||||
layout (location = 3) out vec4 gLightSpacePos;
|
layout (location = 3) out vec4 gMaterial; // r = metallic, g = roughness, b = ao, a = emissive
|
||||||
|
|
||||||
in vec3 FragPos;
|
in vec3 FragPos;
|
||||||
in vec3 Normal;
|
in vec3 Normal;
|
||||||
in vec2 TexCoord;
|
in vec2 TexCoord;
|
||||||
in vec4 FragPosLightSpace;
|
|
||||||
|
|
||||||
uniform sampler2D tex;
|
uniform sampler2D tex;
|
||||||
|
uniform vec4 colorOverride = vec4(0.0);
|
||||||
|
uniform int useTexture = 0;
|
||||||
|
uniform float metallic = 0.0;
|
||||||
|
uniform float roughness = 0.7;
|
||||||
|
uniform float ao = 1.0;
|
||||||
|
uniform float emissive = 0.0;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
vec4 albedo = texture(tex, TexCoord);
|
vec4 albedo = vec4(0.8, 0.8, 0.8, 1.0);
|
||||||
if (albedo.a < 0.1) discard;
|
if (useTexture != 0) {
|
||||||
|
albedo = texture(tex, TexCoord);
|
||||||
|
if (albedo.a < 0.1)
|
||||||
|
discard;
|
||||||
|
albedo.rgb = pow(albedo.rgb, vec3(2.2));
|
||||||
|
}
|
||||||
|
|
||||||
gPosition = FragPos;
|
albedo.rgb = mix(albedo.rgb, colorOverride.rgb, colorOverride.w);
|
||||||
gNormal = normalize(Normal);
|
|
||||||
gAlbedo = albedo;
|
gPosition = FragPos;
|
||||||
gLightSpacePos = FragPosLightSpace;
|
gNormal = normalize(Normal);
|
||||||
|
gAlbedo = albedo;
|
||||||
|
gMaterial = vec4(clamp(metallic, 0.0, 1.0), clamp(roughness, 0.04, 1.0), ao, clamp(emissive, 0.0, 1.0));
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-5
@@ -6,19 +6,39 @@ layout (location = 2) in vec2 aTexCoord;
|
|||||||
out vec3 FragPos;
|
out vec3 FragPos;
|
||||||
out vec3 Normal;
|
out vec3 Normal;
|
||||||
out vec2 TexCoord;
|
out vec2 TexCoord;
|
||||||
out vec4 FragPosLightSpace;
|
|
||||||
|
|
||||||
uniform mat4 proj;
|
uniform mat4 proj;
|
||||||
uniform mat4 view;
|
uniform mat4 view;
|
||||||
uniform mat4 modl;
|
uniform mat4 modl;
|
||||||
uniform mat4 lightSpaceMatrix;
|
|
||||||
|
uniform int uvMode = 0; // 0 = stretch, 1 = tile
|
||||||
|
uniform vec3 tileScale = vec3(1.0);
|
||||||
|
uniform vec2 uvOffset = vec2(0.0);
|
||||||
|
|
||||||
|
// Same direction as transpose(inverse(m)) for normals, without the per vertex inverse
|
||||||
|
mat3 CofactorMatrix(mat3 m)
|
||||||
|
{
|
||||||
|
return mat3(cross(m[1], m[2]), cross(m[2], m[0]), cross(m[0], m[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tiling ignores the mesh's uvs and projects from whichever axis the surface faces,
|
||||||
|
// so every face of a stretched cube repeats at the same size
|
||||||
|
vec2 SurfaceUV()
|
||||||
|
{
|
||||||
|
if (uvMode == 0) return aTexCoord + uvOffset;
|
||||||
|
|
||||||
|
vec3 n = abs(aNormal);
|
||||||
|
vec3 p = aPos * tileScale;
|
||||||
|
if (n.y >= n.x && n.y >= n.z) return p.xz + uvOffset;
|
||||||
|
if (n.x >= n.z) return p.zy + uvOffset;
|
||||||
|
return p.xy + uvOffset;
|
||||||
|
}
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
vec4 worldPos = modl * vec4(aPos, 1.0);
|
vec4 worldPos = modl * vec4(aPos, 1.0);
|
||||||
FragPos = worldPos.xyz;
|
FragPos = worldPos.xyz;
|
||||||
Normal = normalize(mat3(transpose(inverse(modl))) * aNormal);
|
Normal = normalize(CofactorMatrix(mat3(modl)) * aNormal);
|
||||||
TexCoord = aTexCoord;
|
TexCoord = SurfaceUV();
|
||||||
FragPosLightSpace = lightSpaceMatrix * worldPos;
|
|
||||||
gl_Position = proj * view * worldPos;
|
gl_Position = proj * view * worldPos;
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-14
@@ -8,27 +8,60 @@ layout (location = 4) in vec4 weights;
|
|||||||
out vec3 FragPos;
|
out vec3 FragPos;
|
||||||
out vec3 Normal;
|
out vec3 Normal;
|
||||||
out vec2 TexCoord;
|
out vec2 TexCoord;
|
||||||
out vec4 FragPosLightSpace;
|
|
||||||
|
|
||||||
uniform mat4 proj;
|
uniform mat4 proj;
|
||||||
uniform mat4 view;
|
uniform mat4 view;
|
||||||
uniform mat4 modl;
|
uniform mat4 modl;
|
||||||
uniform mat4 lightSpaceMatrix;
|
|
||||||
uniform mat4 finalBonesMatrices[100];
|
const int MAX_BONES = 100;
|
||||||
|
const int MAX_BONE_INFLUENCE = 4;
|
||||||
|
uniform mat4 finalBonesMatrices[MAX_BONES];
|
||||||
|
|
||||||
|
uniform int uvMode = 0; // 0 = stretch, 1 = tile
|
||||||
|
uniform vec3 tileScale = vec3(1.0);
|
||||||
|
uniform vec2 uvOffset = vec2(0.0);
|
||||||
|
|
||||||
|
// Same direction as transpose(inverse(m)) for normals, without the per vertex inverse
|
||||||
|
mat3 CofactorMatrix(mat3 m)
|
||||||
|
{
|
||||||
|
return mat3(cross(m[1], m[2]), cross(m[2], m[0]), cross(m[0], m[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tiling ignores the mesh's uvs and projects from whichever axis the surface faces.
|
||||||
|
// Done on the bind pose, so the pattern stays stuck to the character as it moves.
|
||||||
|
vec2 SurfaceUV()
|
||||||
|
{
|
||||||
|
if (uvMode == 0) return aTexCoord + uvOffset;
|
||||||
|
|
||||||
|
vec3 n = abs(aNormal);
|
||||||
|
vec3 p = aPos * tileScale;
|
||||||
|
if (n.y >= n.x && n.y >= n.z) return p.xz + uvOffset;
|
||||||
|
if (n.x >= n.z) return p.zy + uvOffset;
|
||||||
|
return p.xy + uvOffset;
|
||||||
|
}
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
mat4 boneTransform = finalBonesMatrices[boneIds[0]] * weights[0];
|
vec4 totalPosition = vec4(0.0);
|
||||||
boneTransform += finalBonesMatrices[boneIds[1]] * weights[1];
|
vec3 totalNormal = vec3(0.0);
|
||||||
boneTransform += finalBonesMatrices[boneIds[2]] * weights[2];
|
|
||||||
boneTransform += finalBonesMatrices[boneIds[3]] * weights[3];
|
|
||||||
|
|
||||||
vec4 localPos = boneTransform * vec4(aPos, 1.0);
|
for (int i = 0; i < MAX_BONE_INFLUENCE; i++)
|
||||||
vec4 worldPos = modl * localPos;
|
{
|
||||||
|
if (boneIds[i] == -1) continue;
|
||||||
|
if (boneIds[i] >= MAX_BONES)
|
||||||
|
{
|
||||||
|
totalPosition = vec4(aPos, 1.0);
|
||||||
|
totalNormal = aNormal;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
totalPosition += (finalBonesMatrices[boneIds[i]] * vec4(aPos, 1.0)) * weights[i];
|
||||||
|
totalNormal += mat3(finalBonesMatrices[boneIds[i]]) * aNormal * weights[i];
|
||||||
|
}
|
||||||
|
|
||||||
FragPos = worldPos.xyz;
|
vec4 worldPos = modl * totalPosition;
|
||||||
Normal = normalize(mat3(transpose(inverse(modl * boneTransform))) * aNormal);
|
|
||||||
TexCoord = aTexCoord;
|
FragPos = worldPos.xyz;
|
||||||
FragPosLightSpace = lightSpaceMatrix * worldPos;
|
Normal = normalize(CofactorMatrix(mat3(modl)) * totalNormal);
|
||||||
gl_Position = proj * view * worldPos;
|
TexCoord = SurfaceUV();
|
||||||
|
gl_Position = proj * view * worldPos;
|
||||||
}
|
}
|
||||||
+29
-5
@@ -21,6 +21,29 @@ out vec2 texCoord;
|
|||||||
out vec3 FragPos;
|
out vec3 FragPos;
|
||||||
out vec4 fragposlightspace;
|
out vec4 fragposlightspace;
|
||||||
|
|
||||||
|
uniform int uvMode = 0; // 0 = stretch, 1 = tile
|
||||||
|
uniform vec3 tileScale = vec3(1.0);
|
||||||
|
uniform vec2 uvOffset = vec2(0.0);
|
||||||
|
|
||||||
|
// Same direction as transpose(inverse(m)) for normals, without the per vertex inverse
|
||||||
|
mat3 CofactorMatrix(mat3 m)
|
||||||
|
{
|
||||||
|
return mat3(cross(m[1], m[2]), cross(m[2], m[0]), cross(m[0], m[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tiling ignores the mesh's uvs and projects from whichever axis the surface faces.
|
||||||
|
// Done on the bind pose, so the pattern stays stuck to the character as it moves.
|
||||||
|
vec2 SurfaceUV()
|
||||||
|
{
|
||||||
|
if (uvMode == 0) return tex + uvOffset;
|
||||||
|
|
||||||
|
vec3 n = abs(norm);
|
||||||
|
vec3 p = pos * tileScale;
|
||||||
|
if (n.y >= n.x && n.y >= n.z) return p.xz + uvOffset;
|
||||||
|
if (n.x >= n.z) return p.zy + uvOffset;
|
||||||
|
return p.xy + uvOffset;
|
||||||
|
}
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
vec4 totalPosition = vec4(0.0);
|
vec4 totalPosition = vec4(0.0);
|
||||||
@@ -40,9 +63,10 @@ void main()
|
|||||||
totalPosition += (finalBonesMatrices[boneIds[i]] * vec4(pos, 1.0))* weights[i];
|
totalPosition += (finalBonesMatrices[boneIds[i]] * vec4(pos, 1.0))* weights[i];
|
||||||
totalNormal += mat3(finalBonesMatrices[boneIds[i]]) * norm * weights[i];
|
totalNormal += mat3(finalBonesMatrices[boneIds[i]]) * norm * weights[i];
|
||||||
}
|
}
|
||||||
FragPos = vec3(modl * totalPosition);
|
vec4 worldPos = modl * totalPosition;
|
||||||
fragposlightspace = lightSpaceMatrix * vec4(FragPos, 1.0);
|
FragPos = worldPos.xyz;
|
||||||
gl_Position = proj * view * modl * totalPosition;
|
fragposlightspace = lightSpaceMatrix * worldPos;
|
||||||
normal = normalize(mat3(transpose(inverse(modl))) * totalNormal);
|
gl_Position = proj * view * worldPos;
|
||||||
texCoord = tex;
|
normal = normalize(CofactorMatrix(mat3(modl)) * totalNormal);
|
||||||
|
texCoord = SurfaceUV();
|
||||||
}
|
}
|
||||||
+1
-2
@@ -10,6 +10,5 @@ void main()
|
|||||||
{
|
{
|
||||||
TexCoords = aPos;
|
TexCoords = aPos;
|
||||||
vec4 pos = proj * view * vec4(aPos, 1.0);
|
vec4 pos = proj * view * vec4(aPos, 1.0);
|
||||||
//gl_Position = pos.xyww
|
gl_Position = pos.xyww; // sits on the far plane, so it only draws where no geometry is
|
||||||
gl_Position = pos;
|
|
||||||
}
|
}
|
||||||
@@ -30,31 +30,18 @@ public:
|
|||||||
return tr;
|
return tr;
|
||||||
}
|
}
|
||||||
|
|
||||||
BoundingBox GetAABB() {
|
BoundingBox GetAABB() const {
|
||||||
std::vector<glm::vec3> corners = {
|
// Each world axis reaches as far as the rotated box's axes add up to, no need to visit the corners
|
||||||
{0.5f,0.5f,0.5f},
|
const glm::mat3 rotation = glm::mat3_cast(orient);
|
||||||
{0.5f,-0.5f,0.5f},
|
const glm::vec3 half = scale * 0.5f;
|
||||||
{0.5f,-0.5f,-0.5f},
|
const glm::vec3 extent =
|
||||||
{0.5f,0.5f,-0.5f},
|
glm::abs(rotation[0]) * half.x +
|
||||||
{-0.5f,0.5f,0.5f},
|
glm::abs(rotation[1]) * half.y +
|
||||||
{-0.5f,-0.5f,0.5f},
|
glm::abs(rotation[2]) * half.z;
|
||||||
{-0.5f,0.5f,-0.5f},
|
|
||||||
{-0.5f,-0.5f,-0.5f},
|
|
||||||
};
|
|
||||||
|
|
||||||
glm::vec3 min(FLT_MAX);
|
|
||||||
glm::vec3 max(-FLT_MAX);
|
|
||||||
for (glm::vec3& corner : corners) {
|
|
||||||
corner *= scale;
|
|
||||||
corner = orient * corner;
|
|
||||||
corner += pos;
|
|
||||||
min = { glm::min(min.x, corner.x) ,glm::min(min.y, corner.y), glm::min(min.z, corner.z) };
|
|
||||||
max = { glm::max(max.x, corner.x) ,glm::max(max.y, corner.y), glm::max(max.z, corner.z) };
|
|
||||||
}
|
|
||||||
BoundingBox tr;
|
BoundingBox tr;
|
||||||
tr.min = min;
|
tr.min = pos - extent;
|
||||||
tr.max = max;
|
tr.max = pos + extent;
|
||||||
//std::cout << tr.min.x << ' ' << tr.min.y << ' ' << tr.min.z << ' ' << tr.max.x << ' ' << tr.max.y << ' ' << tr.max.z << '\n';
|
|
||||||
return tr;
|
return tr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,45 @@ glm::vec3 CalculateTriangleNormal(Triangle t) {
|
|||||||
return glm::normalize(glm::cross(t.b - t.a, t.c - t.a));
|
return glm::normalize(glm::cross(t.b - t.a, t.c - t.a));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool TriangleAxisSeparates(const Triangle& t1, const Triangle& t2, const glm::vec3& axis) {
|
||||||
|
const float a1 = glm::dot(t1.a, axis), b1 = glm::dot(t1.b, axis), c1 = glm::dot(t1.c, axis);
|
||||||
|
const float a2 = glm::dot(t2.a, axis), b2 = glm::dot(t2.b, axis), c2 = glm::dot(t2.c, axis);
|
||||||
|
const float min1 = glm::min(a1, glm::min(b1, c1)), max1 = glm::max(a1, glm::max(b1, c1));
|
||||||
|
const float min2 = glm::min(a2, glm::min(b2, c2)), max2 = glm::max(a2, glm::max(b2, c2));
|
||||||
|
return max1 < min2 || max2 < min1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//SEPARATING AXIS TEST, BOTH NORMALS PLUS EVERY EDGE PAIR
|
||||||
|
bool TriangleInTriangle(const Triangle& t1, const Triangle& t2) {
|
||||||
|
const glm::vec3 edges1[3] = { t1.b - t1.a, t1.c - t1.b, t1.a - t1.c };
|
||||||
|
const glm::vec3 edges2[3] = { t2.b - t2.a, t2.c - t2.b, t2.a - t2.c };
|
||||||
|
const glm::vec3 normal1 = glm::cross(edges1[0], edges1[1]);
|
||||||
|
const glm::vec3 normal2 = glm::cross(edges2[0], edges2[1]);
|
||||||
|
|
||||||
|
if (TriangleAxisSeparates(t1, t2, normal1) || TriangleAxisSeparates(t1, t2, normal2))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool coplanar = true;
|
||||||
|
for (const glm::vec3& edge1 : edges1) {
|
||||||
|
for (const glm::vec3& edge2 : edges2) {
|
||||||
|
const glm::vec3 axis = glm::cross(edge1, edge2);
|
||||||
|
if (glm::dot(axis, axis) < 1e-14f) continue;
|
||||||
|
coplanar = false;
|
||||||
|
if (TriangleAxisSeparates(t1, t2, axis)) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//EDGE PAIRS GIVE NOTHING FOR COPLANAR TRIANGLES, SO TEST WITHIN THE PLANE
|
||||||
|
if (coplanar) {
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (TriangleAxisSeparates(t1, t2, glm::cross(normal1, edges1[i]))) return false;
|
||||||
|
if (TriangleAxisSeparates(t1, t2, glm::cross(normal1, edges2[i]))) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
float Magnitude2(const glm::vec3& v) {
|
float Magnitude2(const glm::vec3& v) {
|
||||||
return v.x*v.x + v.y*v.y + v.z*v.z;
|
return v.x*v.x + v.y*v.y + v.z*v.z;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ struct AxisCollisionCN {
|
|||||||
};
|
};
|
||||||
|
|
||||||
glm::vec3 CalculateTriangleNormal(Triangle t);
|
glm::vec3 CalculateTriangleNormal(Triangle t);
|
||||||
|
bool TriangleInTriangle(const Triangle& t1, const Triangle& t2);
|
||||||
|
|
||||||
float Magnitude2(const glm::vec3& v);
|
float Magnitude2(const glm::vec3& v);
|
||||||
|
|
||||||
|
|||||||
+35
-25
@@ -1,48 +1,58 @@
|
|||||||
#include "texture.h"
|
#include "texture.h"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "stb_image.h"
|
||||||
|
|
||||||
Texture::Texture(const char* filename) : path(filename)
|
Texture::Texture(const char* filename) : path(filename)
|
||||||
{
|
{
|
||||||
int widthImg, heightImg, nrComponents;
|
int nrComponents;
|
||||||
stbi_set_flip_vertically_on_load(true);
|
stbi_set_flip_vertically_on_load(true);
|
||||||
unsigned char* bytes = stbi_load(filename, &widthImg, &heightImg, &nrComponents, 0);
|
unsigned char* bytes = stbi_load(filename, &width, &height, &nrComponents, 4);
|
||||||
if (not bytes)
|
if (not bytes)
|
||||||
{
|
throw std::runtime_error("Failed to load texture: " + path);
|
||||||
std::cout << "Failed to load texture" << std::endl;
|
|
||||||
throw;
|
pixels.assign(bytes, bytes + (size_t)width * height * 4);
|
||||||
}
|
stbi_image_free(bytes);
|
||||||
|
|
||||||
|
CreateGLTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture::Texture(int newWidth, int newHeight) : width(newWidth), height(newHeight)
|
||||||
|
{
|
||||||
|
pixels.assign((size_t)width * height * 4, 255);
|
||||||
|
CreateGLTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture::Texture(const Texture& source)
|
||||||
|
: path(source.path), width(source.width), height(source.height), pixels(source.pixels)
|
||||||
|
{
|
||||||
|
CreateGLTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture::CreateGLTexture()
|
||||||
|
{
|
||||||
glGenTextures(1, &ID);
|
glGenTextures(1, &ID);
|
||||||
glBindTexture(GL_TEXTURE_2D, ID);
|
glBindTexture(GL_TEXTURE_2D, ID);
|
||||||
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
// Mipmaps when shrunk, otherwise tiled textures shimmer in the distance
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||||
|
|
||||||
GLenum format;
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
||||||
if (nrComponents == 1)
|
|
||||||
format = GL_RED;
|
|
||||||
else if (nrComponents == 3)
|
|
||||||
format = GL_RGB;
|
|
||||||
else if (nrComponents == 4)
|
|
||||||
format = GL_RGBA;
|
|
||||||
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, format, widthImg, heightImg, 0, format, GL_UNSIGNED_BYTE, bytes);
|
|
||||||
glGenerateMipmap(GL_TEXTURE_2D);
|
glGenerateMipmap(GL_TEXTURE_2D);
|
||||||
|
|
||||||
stbi_image_free(bytes);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Texture::texUnit(Shader shader, const char* uniform, GLuint unit) {
|
void Texture::Upload()
|
||||||
shader.SetInt(uniform,unit);
|
{
|
||||||
}
|
|
||||||
|
|
||||||
void Texture::Bind() {
|
|
||||||
glBindTexture(GL_TEXTURE_2D, ID);
|
glBindTexture(GL_TEXTURE_2D, ID);
|
||||||
}
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
||||||
|
glGenerateMipmap(GL_TEXTURE_2D);
|
||||||
void Texture::Unbind() {
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,20 +2,31 @@
|
|||||||
#ifndef TEXTURE_CLASS_H
|
#ifndef TEXTURE_CLASS_H
|
||||||
#define TEXTURE_CLASS_H
|
#define TEXTURE_CLASS_H
|
||||||
|
|
||||||
#include "stb_image.h"
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
|
|
||||||
#include "shaderClass.h"
|
// A loaded image and nothing else. Game elements wear one through a SurfaceTexture,
|
||||||
|
// get these from the ResourceManager.
|
||||||
class Texture {
|
class Texture {
|
||||||
public:
|
public:
|
||||||
GLuint ID;
|
GLuint ID;
|
||||||
std::string path;
|
std::string path;
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
std::vector<unsigned char> pixels; // RGBA, row 0 is the bottom of the image
|
||||||
|
|
||||||
Texture(const char* filename);
|
Texture(const char* filename);
|
||||||
void texUnit(Shader shader, const char* uniform, GLuint unit);
|
Texture(int newWidth, int newHeight); // blank white
|
||||||
void Bind();
|
Texture(const Texture& source); // an independent copy with its own GL texture
|
||||||
void Unbind();
|
|
||||||
|
// Sends pixels to the GPU after they've been edited
|
||||||
|
void Upload();
|
||||||
void Delete();
|
void Delete();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void CreateGLTexture();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user