Public release of the DLU server code!

Have fun!
This commit is contained in:
Unknown
2021-12-05 18:54:36 +01:00
parent 5f7270e4ad
commit 0545adfac3
1146 changed files with 368646 additions and 1 deletions

19
dPhysics/Singleton.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
template <typename T>
class Singleton {
public:
static T& Instance() {
static T instance{};
return instance;
}
virtual ~Singleton() = default;
Singleton(const Singleton& other) = delete;
Singleton(Singleton&& other) = delete;
Singleton& operator=(const Singleton& other) = delete;
Singleton& operator=(Singleton&& other) = delete;
protected:
Singleton() = default;
};

View File

@@ -0,0 +1,117 @@
#include "dpCollisionChecks.h"
#include "dpEntity.h"
#include "dpShapeBase.h"
#include "dpShapeSphere.h"
#include "dpShapeBox.h"
#include <iostream>
#include <algorithm>
using namespace dpCollisionChecks;
bool dpCollisionChecks::AreColliding(dpEntity* a, dpEntity* b) {
auto shapeA = a->GetShape();
auto shapeB = b->GetShape();
//Sphere to sphere collision
if (shapeA->GetShapeType() == dpShapeType::Sphere && shapeB->GetShapeType() == dpShapeType::Sphere) {
return CheckSpheres(a, b);
}
return false;
}
bool dpCollisionChecks::CheckSpheres(dpEntity* a, dpEntity* b) {
if (!a || !b) return false;
auto posA = a->GetPosition();
auto distance = Vector3::DistanceSquared(posA, b->GetPosition());
auto sphereA = static_cast<dpShapeSphere*>(a->GetShape());
auto sphereB = static_cast<dpShapeSphere*>(b->GetShape());
const auto radius = sphereA->GetRadius() + sphereB->GetRadius();
if (distance <= radius * radius)
return true;
return false;
}
bool dpCollisionChecks::CheckBoxes(dpEntity* a, dpEntity* b) {
if (!a || !b) return false;
auto boxA = static_cast<dpShapeBox*>(a->GetShape());
auto boxB = static_cast<dpShapeBox*>(b->GetShape());
const auto& posA = a->GetPosition();
const auto& posB = b->GetPosition();
for (const auto& vert : boxA->GetVertices()) {
if (boxB->IsVertInBox(vert))
return true;
}
/*//Check if we're overlapping on X/Z:
if ((boxA->GetMaxWidth() >= boxB->GetMinWidth()) && //If our max width is greater than starting X of b
(boxA->GetMinWidth() <= boxB->GetMaxWidth()) && //If our start x is less than b's max width
(boxA->GetMaxDepth() >= boxB->GetMinDepth()) &&
(boxA->GetMinDepth() <= boxB->GetMaxDepth())) {
//Check if we're in the right height
if (boxA->GetTop() <= boxB->GetTop() && boxA->GetTop() >= boxB->GetBottom() || //If our top Y is within their minY/maxY bounds
boxA->GetBottom() <= boxB->GetTop() && boxA->GetBottom() >= boxB->GetBottom()) //Or our bottom Y
return true; //We definitely are colliding.
}*/
/*//Check if we're overlapping on X/Z:
if ((boxA->GetMaxWidth() >= posB.x) && //If our max width is greater than starting X of b
(posA.x <= boxB->GetMaxWidth()) && //If our start x is less than b's max width
(boxA->GetMaxDepth() >= posB.z) &&
(posA.z <= boxB->GetMaxDepth())) {
//Check if we're in the right height
if (boxA->GetTop() <= boxB->GetTop() && boxA->GetTop() >= posB.y || //If our top Y is within their minY/maxY bounds
posA.y <= boxB->GetTop() && posA.y >= posB.y) //Or our bottom Y
return true; //We definitely are colliding.
}*/
return false;
}
bool dpCollisionChecks::CheckSphereBox(dpEntity* a, dpEntity* b) {
if (!a || !b) return false;
NiPoint3 boxPos;
dpShapeBox* box;
NiPoint3 spherePos;
dpShapeSphere* sphere;
//Figure out which is the box and which is the sphere
if (a->GetShape()->GetShapeType() == dpShapeType::Box) {
box = static_cast<dpShapeBox*>(a->GetShape());
sphere = static_cast<dpShapeSphere*>(b->GetShape());
boxPos = a->GetPosition();
spherePos = b->GetPosition();
}
else {
box = static_cast<dpShapeBox*>(b->GetShape());
sphere = static_cast<dpShapeSphere*>(a->GetShape());
boxPos = b->GetPosition();
spherePos = a->GetPosition();
}
//Get closest point from the box to the sphere center by clamping
float x = std::max(box->m_MinX, std::min(spherePos.x, box->m_MaxX));
float y = std::max(box->m_MinY, std::min(spherePos.y, box->m_MaxY));
float z = std::max(box->m_MinZ, std::min(spherePos.z, box->m_MaxZ));
//Check the distance between that point & our sphere
float dX = x - spherePos.x;
float dY = y - spherePos.y;
float dZ = z - spherePos.z;
float distanceSquared = (dX * dX) + (dY * dY) + (dZ * dZ);
const float radius = sphere->GetRadius();
return distanceSquared < radius * radius;
}

View File

@@ -0,0 +1,12 @@
#pragma once
class dpEntity;
namespace dpCollisionChecks {
bool AreColliding(dpEntity* a, dpEntity* b);
bool CheckSpheres(dpEntity* a, dpEntity* b);
bool CheckBoxes(dpEntity* a, dpEntity* b);
bool CheckSphereBox(dpEntity* a, dpEntity* b);
};

View File

@@ -0,0 +1,14 @@
#pragma once
/*
* Collision Groups
*/
enum eCollisionGroup : uint8_t
{
COLLISION_GROUP_ALL = 0 << 0,
COLLISION_GROUP_NEUTRAL = 1 << 0,
COLLISION_GROUP_FRIENDLY = 1 << 1,
COLLISION_GROUP_ENEMY = 1 << 2,
COLLISION_GROUP_DYNAMIC = 1 << 3,
};

7
dPhysics/dpCommon.h Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
enum class dpShapeType : unsigned short {
Invalid = 0,
Sphere,
Box
};

151
dPhysics/dpEntity.cpp Normal file
View File

@@ -0,0 +1,151 @@
#include "dpEntity.h"
#include "dpShapeSphere.h"
#include "dpShapeBox.h"
#include "dpGrid.h"
#include <iostream>
dpEntity::dpEntity(const LWOOBJID& objectID, dpShapeType shapeType, bool isStatic) {
m_ObjectID = objectID;
m_IsStatic = isStatic;
m_CollisionShape = nullptr;
m_Scale = 1.0f;
m_CollisionGroup = COLLISION_GROUP_ALL;
switch (shapeType) {
case dpShapeType::Sphere:
m_CollisionShape = new dpShapeSphere(this, 1.0f);
break;
case dpShapeType::Box:
m_CollisionShape = new dpShapeBox(this, 1.0f, 1.0f, 1.0f);
break;
default:
std::cout << "No shape for shapeType: " << (int)shapeType << std::endl;
}
}
dpEntity::dpEntity(const LWOOBJID& objectID, NiPoint3 boxDimensions, bool isStatic) {
m_ObjectID = objectID;
m_IsStatic = isStatic;
m_CollisionShape = nullptr;
m_Scale = 1.0f;
m_CollisionGroup = COLLISION_GROUP_ALL;
m_CollisionShape = new dpShapeBox(this, boxDimensions.x, boxDimensions.y, boxDimensions.z);
if (boxDimensions.x > 100.0f) m_IsGargantuan = true;
}
dpEntity::dpEntity(const LWOOBJID& objectID, float width, float height, float depth, bool isStatic) {
m_ObjectID = objectID;
m_IsStatic = isStatic;
m_CollisionShape = nullptr;
m_Scale = 1.0f;
m_CollisionGroup = COLLISION_GROUP_ALL;
m_CollisionShape = new dpShapeBox(this, width, height, depth);
if (width > 100.0f) m_IsGargantuan = true;
}
dpEntity::dpEntity(const LWOOBJID& objectID, float radius, bool isStatic) {
m_ObjectID = objectID;
m_IsStatic = isStatic;
m_CollisionShape = nullptr;
m_Scale = 1.0f;
m_CollisionGroup = COLLISION_GROUP_ALL;
m_CollisionShape = new dpShapeSphere(this, radius);
if (radius > 200.0f) m_IsGargantuan = true;
}
dpEntity::~dpEntity() {
delete m_CollisionShape;
m_CollisionShape = nullptr;
}
void dpEntity::Update(float deltaTime) {
m_NewObjects.clear();
m_RemovedObjects.clear();
if (m_IsStatic) return;
//m_Position = m_Position + (m_Velocity * deltaTime);
}
void dpEntity::CheckCollision(dpEntity* other) {
if (!m_CollisionShape) return;
if ((m_CollisionGroup & other->m_CollisionGroup) & (~COLLISION_GROUP_DYNAMIC)) {
return;
}
bool wasFound = (m_CurrentlyCollidingObjects.find(other->GetObjectID()) != m_CurrentlyCollidingObjects.end());
bool isColliding = m_CollisionShape->IsColliding(other->GetShape());
if (isColliding && !wasFound) {
m_CurrentlyCollidingObjects.emplace(other->GetObjectID(), other);
m_NewObjects.push_back(other);
//if (m_CollisionShape->GetShapeType() == dpShapeType::Sphere && other->GetShape()->GetShapeType() == dpShapeType::Sphere)
//std::cout << "started sphere col at: " << other->GetPosition().x << ", " << other->GetPosition().y << ", " << other->GetPosition().z << std::endl;
}
else if (!isColliding && wasFound) {
m_CurrentlyCollidingObjects.erase(other->GetObjectID());
m_RemovedObjects.push_back(other);
//if (m_CollisionShape->GetShapeType() == dpShapeType::Sphere && other->GetShape()->GetShapeType() == dpShapeType::Sphere)
// std::cout << "stopped sphere col at: " << other->GetPosition().x << ", " << other->GetPosition().y << ", " << other->GetPosition().z << std::endl;
}
}
void dpEntity::SetPosition(const NiPoint3& newPos) {
if (!m_CollisionShape) return;
//Update the grid if needed:
if (m_Grid) m_Grid->Move(this, newPos.x, newPos.z);
//If we're a box, we need to first undo the previous position, otherwise things get screwy:
if (m_CollisionShape->GetShapeType() == dpShapeType::Box) {
auto box = static_cast<dpShapeBox*>(m_CollisionShape);
if (m_Position != NiPoint3()) {
box->SetPosition(NiPoint3(-m_Position.x, -m_Position.y, -m_Position.z));
}
box->SetPosition(newPos);
}
m_Position = newPos;
}
void dpEntity::SetRotation(const NiQuaternion& newRot) {
m_Rotation = newRot;
if (m_CollisionShape->GetShapeType() == dpShapeType::Box) {
auto box = static_cast<dpShapeBox*>(m_CollisionShape);
box->SetRotation(newRot);
}
}
void dpEntity::SetScale(float newScale) {
m_Scale = newScale;
if (m_CollisionShape->GetShapeType() == dpShapeType::Box) {
auto box = static_cast<dpShapeBox*>(m_CollisionShape);
box->SetScale(newScale);
}
}
void dpEntity::SetVelocity(const NiPoint3& newVelocity) {
m_Velocity = newVelocity;
}
void dpEntity::SetAngularVelocity(const NiPoint3& newAngularVelocity) {
m_AngularVelocity = newAngularVelocity;
}
void dpEntity::SetGrid(dpGrid* grid) {
m_Grid = grid;
m_Grid->Add(this);
}

86
dPhysics/dpEntity.h Normal file
View File

@@ -0,0 +1,86 @@
#pragma once
#include "NiPoint3.h"
#include "NiQuaternion.h"
#include <vector>
#include <map>
#include "dCommonVars.h"
#include "dpCommon.h"
#include "dpShapeBase.h"
#include "dpCollisionGroups.h"
#include "dpGrid.h"
class dpEntity {
friend class dpGrid; //using friend here for now so grid can access everything
public:
dpEntity(const LWOOBJID& objectID, dpShapeType shapeType, bool isStatic = true);
dpEntity(const LWOOBJID& objectID, NiPoint3 boxDimensions, bool isStatic = true);
dpEntity(const LWOOBJID& objectID, float width, float height, float depth, bool isStatic = true);
dpEntity(const LWOOBJID& objectID, float radius, bool isStatic = true);
~dpEntity();
void Update(float deltaTime);
void CheckCollision(dpEntity* other);
const NiPoint3& GetPosition() const { return m_Position; }
const NiQuaternion& GetRotation() const { return m_Rotation; }
const float GetScale() const { return m_Scale; }
const NiPoint3& GetVelocity() const { return m_Velocity; }
const NiPoint3& GetAngularVelocity() const { return m_AngularVelocity; }
void SetPosition(const NiPoint3& newPos);
void SetRotation(const NiQuaternion& newRot);
void SetScale(float newScale);
void SetVelocity(const NiPoint3& newVelocity);
void SetAngularVelocity(const NiPoint3& newAngularVelocity);
dpShapeBase* GetShape() { return m_CollisionShape; }
bool GetIsStatic() const { return m_IsStatic; }
uint8_t GetCollisionGroup() const { return m_CollisionGroup; }
void SetCollisionGroup(uint8_t value) { m_CollisionGroup = value; }
bool GetSleeping() const { return m_Sleeping; }
void SetSleeping(bool value) { m_Sleeping = value; }
const std::vector<dpEntity*>& GetNewObjects() const { return m_NewObjects; }
const std::vector<dpEntity*>& GetRemovedObjects() const { return m_RemovedObjects; }
const std::map<LWOOBJID, dpEntity*>& GetCurrentlyCollidingObjects() const { return m_CurrentlyCollidingObjects; }
void PreUpdate() { m_NewObjects.clear(); m_RemovedObjects.clear(); }
const LWOOBJID& GetObjectID() const { return m_ObjectID; }
void SetGrid(dpGrid* grid);
bool GetIsGargantuan() const { return m_IsGargantuan; }
private:
LWOOBJID m_ObjectID;
dpShapeBase* m_CollisionShape;
bool m_IsStatic;
NiPoint3 m_Position;
NiQuaternion m_Rotation;
float m_Scale;
NiPoint3 m_Velocity;
NiPoint3 m_AngularVelocity;
dpGrid* m_Grid = nullptr;
uint8_t m_CollisionGroup;
bool m_Sleeping = false;
bool m_IsGargantuan = false;
std::vector<dpEntity*> m_NewObjects;
std::vector<dpEntity*> m_RemovedObjects;
std::map<LWOOBJID, dpEntity*> m_CurrentlyCollidingObjects;
};

163
dPhysics/dpGrid.cpp Normal file
View File

@@ -0,0 +1,163 @@
#include "dpGrid.h"
#include "dpEntity.h"
#include <cmath>
dpGrid::dpGrid(int numCells, int cellSize) {
NUM_CELLS = numCells;
CELL_SIZE = cellSize;
//dumb method but i can't be bothered
//fill x
for (int i = 0; i < NUM_CELLS; i++) {
m_Cells.push_back(std::vector<std::forward_list<dpEntity*>>());
}
//fill z
for (int i = 0; i < NUM_CELLS; i++) {
for (int i = 0; i < NUM_CELLS; i++) {
m_Cells[i].push_back(std::forward_list<dpEntity*>());
}
}
}
dpGrid::~dpGrid() {
for (auto& x : m_Cells) { //x
for (auto& y : x) { //y
for (auto en : y) {
if (!en) continue;
delete en;
en = nullptr;
}
}
}
}
void dpGrid::Add(dpEntity* entity) {
//Determine which grid cell it's in.
int cellX = (int)std::round(entity->m_Position.x) / dpGrid::CELL_SIZE + NUM_CELLS / 2;
int cellZ = (int)std::round(entity->m_Position.z) / dpGrid::CELL_SIZE + NUM_CELLS / 2;
if (cellX < 0) cellX = 0;
if (cellZ < 0) cellZ = 0;
if (cellX > NUM_CELLS) cellX = NUM_CELLS;
if (cellZ > NUM_CELLS) cellZ = NUM_CELLS;
//Add to cell:
m_Cells[cellX][cellZ].push_front(entity);
//To verify that the object isn't gargantuan:
if (entity->GetScale() >= CELL_SIZE * 2 || entity->GetIsGargantuan())
m_GargantuanObjects.insert(std::make_pair(entity->m_ObjectID, entity));
}
void dpGrid::Move(dpEntity* entity, float x, float z) {
int oldCellX = (int)std::round(entity->m_Position.x) / dpGrid::CELL_SIZE + NUM_CELLS / 2;
int oldCellZ = (int)std::round(entity->m_Position.z) / dpGrid::CELL_SIZE + NUM_CELLS / 2;
int cellX = (int)std::round(x) / dpGrid::CELL_SIZE + NUM_CELLS / 2;
int cellZ = (int)std::round(z) / dpGrid::CELL_SIZE + NUM_CELLS / 2;
if (cellX < 0) cellX = 0;
if (cellZ < 0) cellZ = 0;
if (cellX > NUM_CELLS) cellX = NUM_CELLS;
if (cellZ > NUM_CELLS) cellZ = NUM_CELLS;
if (oldCellX < 0) oldCellX = 0;
if (oldCellZ < 0) oldCellZ = 0;
if (oldCellX > NUM_CELLS) oldCellX = NUM_CELLS;
if (oldCellZ > NUM_CELLS) oldCellZ = NUM_CELLS;
if (oldCellX == cellX && oldCellZ == cellZ) return;
//Remove from perv cell:
m_Cells[oldCellX][oldCellZ].remove(entity);
//Add to the new cell
m_Cells[cellX][cellZ].push_front(entity);
}
void dpGrid::Delete(dpEntity* entity) {
if (!entity) return;
int oldCellX = (int)std::round(entity->m_Position.x) / dpGrid::CELL_SIZE + NUM_CELLS / 2;
int oldCellZ = (int)std::round(entity->m_Position.z) / dpGrid::CELL_SIZE + NUM_CELLS / 2;
if (oldCellX < 0) oldCellX = 0;
if (oldCellZ < 0) oldCellZ = 0;
if (oldCellX > NUM_CELLS) oldCellX = NUM_CELLS;
if (oldCellZ > NUM_CELLS) oldCellZ = NUM_CELLS;
m_Cells[oldCellX][oldCellZ].remove(entity);
if (m_GargantuanObjects.find(entity->m_ObjectID) != m_GargantuanObjects.end())
m_GargantuanObjects.erase(entity->m_ObjectID);
if (entity) delete entity;
entity = nullptr;
}
void dpGrid::Update(float deltaTime) {
//Pre-update:
for (auto& x : m_Cells) { //x
for (auto& y : x) { //y
for (auto en : y) {
if (!en) continue;
en->PreUpdate();
}
}
}
//Actual collision detection update:
for (int x = 0; x < NUM_CELLS; x++) {
for (int y = 0; y < NUM_CELLS; y++) {
HandleCell(x, y, deltaTime);
}
}
}
void dpGrid::HandleEntity(dpEntity* entity, dpEntity* other) {
if (!entity || !other) return;
if (other->GetIsStatic())
other->CheckCollision(entity); //swap "other" and "entity" if you want dyn objs to handle collisions.
}
void dpGrid::HandleCell(int x, int z, float deltaTime) {
auto& entities = m_Cells[x][z]; //vector of entities contained within this cell.
for (auto en : entities) {
if (!en) continue;
if (en->GetIsStatic() || en->GetSleeping()) continue;
//Check against all entities that are in the same cell as us
for (auto other : entities)
HandleEntity(en, other);
//To try neighbouring cells as well: (can be disabled if needed)
//we only check 4 of the 8 neighbouring cells, otherwise we'd get duplicates and cpu cycles wasted...
if (x > 0 && z > 0) {
for (auto other : m_Cells[x - 1][z - 1])
HandleEntity(en, other);
}
if (x > 0) {
for (auto other : m_Cells[x - 1][z])
HandleEntity(en, other);
}
if (z > 0) {
for (auto other : m_Cells[x][z - 1])
HandleEntity(en, other);
}
if (x > 0 && z < NUM_CELLS - 1) {
for (auto other : m_Cells[x - 1][z + 1])
HandleEntity(en, other);
}
for (auto other : m_GargantuanObjects)
HandleEntity(en, other.second);
}
}

34
dPhysics/dpGrid.h Normal file
View File

@@ -0,0 +1,34 @@
#pragma once
#include <vector>
#include <list>
#include <forward_list>
#include <map>
#include "dCommonVars.h"
class dpEntity;
class dpGrid {
public:
//LU has a chunk size of 64x64, with each chunk unit being 3.2 ingame units.
int NUM_CELLS = 12; //Most worlds consist of 10 or 11 chunks, so I'm picking 12 to be safe.
int CELL_SIZE = 205; //64 * 3.2 = 204.8 rounded up
public:
dpGrid(int numCells, int cellSize);
~dpGrid();
void Add(dpEntity* entity);
void Move(dpEntity* entity, float x, float z);
void Delete(dpEntity* entity);
void Update(float deltaTime);
private:
void HandleEntity(dpEntity* entity, dpEntity* other);
void HandleCell(int x, int z, float deltaTime);
private:
//cells on X, cells on Y for that X, then another vector that contains the entities within that cell.
std::vector<std::vector<std::forward_list<dpEntity*>>> m_Cells;
std::map<LWOOBJID, dpEntity*> m_GargantuanObjects;
};

17
dPhysics/dpShapeBase.cpp Normal file
View File

@@ -0,0 +1,17 @@
#include "dpShapeBase.h"
#include "dpEntity.h"
#include <iostream>
dpShapeBase::dpShapeBase(dpEntity* parentEntity) :
m_ParentEntity(parentEntity)
{
}
dpShapeBase::~dpShapeBase() {
}
bool dpShapeBase::IsColliding(dpShapeBase* other) {
std::cout << "Base shapes do not have any *shape* to them, and thus cannot be overlapping." << std::endl;
std::cout << "You should be using a shape class inherited from this base class." << std::endl;
return false;
}

20
dPhysics/dpShapeBase.h Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include "dpCommon.h"
class dpEntity;
class dpShapeBase {
public:
dpShapeBase(dpEntity* parentEntity);
virtual ~dpShapeBase();
virtual bool IsColliding(dpShapeBase* other) = 0;
const dpShapeType& GetShapeType() const { return m_ShapeType; }
dpEntity* GetParentEntity() const { return m_ParentEntity; }
protected:
dpEntity* m_ParentEntity;
dpShapeType m_ShapeType = dpShapeType::Invalid;
};

176
dPhysics/dpShapeBox.cpp Normal file
View File

@@ -0,0 +1,176 @@
#include "dpShapeBase.h"
#include "dpShapeBox.h"
#include "dpShapeSphere.h"
#include "dpCollisionChecks.h"
#include "dpEntity.h"
#include "NiPoint3.h"
#include "NiQuaternion.h"
#include <iostream>
dpShapeBox::dpShapeBox(dpEntity* parentEntity, float width, float height, float depth) :
dpShapeBase(parentEntity),
m_Width(width/2),
m_Height(height/2),
m_Depth(depth/2),
m_Scale(1.0f)
{
m_ShapeType = dpShapeType::Box;
InitVertices();
}
dpShapeBox::~dpShapeBox() {
}
bool dpShapeBox::IsColliding(dpShapeBase* other) {
if (!other) return false;
switch (other->GetShapeType()) {
case dpShapeType::Sphere:
return dpCollisionChecks::CheckSphereBox(m_ParentEntity, other->GetParentEntity());
case dpShapeType::Box:
return dpCollisionChecks::CheckBoxes(m_ParentEntity, other->GetParentEntity());
default:
std::cout << "No collision detection for: " << (int)m_ShapeType << "-to-" << (int)other->GetShapeType() << " collision!" << std::endl;
}
return false;
}
const float dpShapeBox::GetMaxWidth() {
return m_ParentEntity->GetPosition().x + m_Width;
}
const float dpShapeBox::GetTop() {
return m_ParentEntity->GetPosition().y + (m_Height * 2);
}
const float dpShapeBox::GetMaxDepth() {
return m_ParentEntity->GetPosition().z + m_Depth;
}
const float dpShapeBox::GetMinWidth() {
return m_ParentEntity->GetPosition().x - m_Width;
}
const float dpShapeBox::GetBottom() {
return m_ParentEntity->GetPosition().y; //- m_Height;
}
const float dpShapeBox::GetMinDepth() {
return m_ParentEntity->GetPosition().z - m_Depth;
}
void dpShapeBox::SetScale(float scale) {
if (isScaled) return;
isScaled = true;
m_Width *= scale;
m_Height *= scale;
m_Depth *= scale;
//fuuuckkk yoouu
InitVertices();
//SetRotation(m_ParentEntity->GetRotation());
}
void dpShapeBox::SetRotation(const NiQuaternion& rotation) {
if (m_HasBeenRotated) return; //Boxes cannot be rotated more than once.
m_HasBeenRotated = true;
m_TopMinLeft = m_TopMinLeft.RotateByQuaternion(rotation);
m_TopMaxLeft = m_TopMaxLeft.RotateByQuaternion(rotation);
m_TopMinRight = m_TopMinRight.RotateByQuaternion(rotation);
m_TopMaxRight = m_TopMaxRight.RotateByQuaternion(rotation);
m_BottomMinLeft = m_BottomMinLeft.RotateByQuaternion(rotation);
m_BottomMinRight = m_BottomMinRight.RotateByQuaternion(rotation);
m_BottomMaxLeft = m_BottomMaxLeft.RotateByQuaternion(rotation);
m_BottomMaxRight = m_BottomMaxRight.RotateByQuaternion(rotation);
InsertVertices();
}
bool dpShapeBox::IsVertInBox(const NiPoint3& vert) {
//if we are in the correct height
if (vert.y >= m_MinY && vert.y <= m_MaxY) {
//if we're inside the x bounds
if (vert.x >= m_MinX && vert.x <= m_MaxX) {
//if we're inside the z bounds
if (vert.z >= m_MinZ && vert.z <= m_MaxZ)
return true;
}
}
return false;
}
void dpShapeBox::InitVertices() {
//The four top verts
m_TopMinLeft = NiPoint3(GetMinWidth(), GetTop(), GetMinDepth());
m_TopMaxLeft = NiPoint3(GetMinWidth(), GetTop(), GetMaxDepth());
m_TopMinRight = NiPoint3(GetMaxWidth(), GetTop(), GetMinDepth());
m_TopMaxRight = NiPoint3(GetMaxWidth(), GetTop(), GetMaxDepth());
//The four bottom verts
m_BottomMinLeft = NiPoint3(GetMinWidth(), GetBottom(), GetMinDepth());
m_BottomMaxLeft = NiPoint3(GetMinWidth(), GetBottom(), GetMaxDepth());
m_BottomMinRight = NiPoint3(GetMaxWidth(), GetBottom(), GetMinDepth());
m_BottomMaxRight = NiPoint3(GetMaxWidth(), GetBottom(), GetMaxDepth());
InsertVertices();
}
void dpShapeBox::SetPosition(const NiPoint3& position) {
if (isTransformed) return;
isTransformed = true;
for (auto& vert : m_Vertices) {
vert.x += position.x;
vert.y += position.y;
vert.z += position.z;
}
m_TopMinLeft = m_Vertices[0];
m_TopMaxLeft = m_Vertices[1];
m_TopMinRight = m_Vertices[2];
m_TopMaxRight = m_Vertices[3];
m_BottomMinLeft = m_Vertices[4];
m_BottomMaxLeft = m_Vertices[5];
m_BottomMinRight = m_Vertices[6];
m_BottomMaxRight = m_Vertices[7];
for (auto& vert : m_Vertices) {
if (m_MinX >= vert.x) m_MinX = vert.x;
if (m_MinY >= vert.y) m_MinY = vert.y;
if (m_MinZ >= vert.z) m_MinZ = vert.z;
if (m_MaxX <= vert.x) m_MaxX = vert.x;
if (m_MaxY <= vert.y) m_MaxY = vert.y;
if (m_MaxZ <= vert.z) m_MaxZ = vert.z;
}
}
void dpShapeBox::InsertVertices() {
//Insert into our vector:
m_Vertices.clear();
m_Vertices.push_back(m_TopMinLeft);
m_Vertices.push_back(m_TopMaxLeft);
m_Vertices.push_back(m_TopMinRight);
m_Vertices.push_back(m_TopMaxRight);
m_Vertices.push_back(m_BottomMinLeft);
m_Vertices.push_back(m_BottomMaxLeft);
m_Vertices.push_back(m_BottomMinRight);
m_Vertices.push_back(m_BottomMaxRight);
}

72
dPhysics/dpShapeBox.h Normal file
View File

@@ -0,0 +1,72 @@
#pragma once
#include "dpShapeBase.h"
#include <vector>
#include "NiPoint3.h"
#include "NiQuaternion.h"
class dpShapeBox : public dpShapeBase {
public:
dpShapeBox(dpEntity* parentEntity, float width, float height, float depth);
~dpShapeBox();
bool IsColliding(dpShapeBase* other);
const float GetScale() const { return m_Scale; }
const float GetWidth() const { return m_Width; }
const float GetHeight() const { return m_Height; }
const float GetDepth() const { return m_Depth; }
const float GetMaxWidth();
const float GetTop();
const float GetMaxDepth();
const float GetMinWidth();
const float GetBottom();
const float GetMinDepth();
void SetScale(float scale);
void SetRotation(const NiQuaternion& rotation);
const std::vector<NiPoint3>& GetVertices() const { return m_Vertices; }
bool IsVertInBox(const NiPoint3& vert);
void InitVertices();
void SetPosition(const NiPoint3& position);
//idc atm
float m_MinX = 9999.0f;
float m_MaxX = -9999.0f;
float m_MinY = 9999.0f;
float m_MaxY = -9999.0f;
float m_MinZ = 9999.0f;
float m_MaxZ = -9999.0f;
private:
float m_Width; //X
float m_Height; //Y
float m_Depth; //Z
std::vector<NiPoint3> m_Vertices;
NiPoint3 m_TopMinLeft;
NiPoint3 m_TopMinRight;
NiPoint3 m_TopMaxLeft;
NiPoint3 m_TopMaxRight;
NiPoint3 m_BottomMinLeft;
NiPoint3 m_BottomMinRight;
NiPoint3 m_BottomMaxLeft;
NiPoint3 m_BottomMaxRight;
float m_Scale;
bool m_HasBeenRotated = false;
bool isScaled = false;
bool isTransformed = false;
void InsertVertices();
};

View File

@@ -0,0 +1,30 @@
#include "dpShapeSphere.h"
#include "dpCollisionChecks.h"
#include <iostream>
dpShapeSphere::dpShapeSphere(dpEntity* parentEntity, float radius) :
dpShapeBase(parentEntity),
m_Radius(radius)
{
m_ShapeType = dpShapeType::Sphere;
}
dpShapeSphere::~dpShapeSphere() {
}
bool dpShapeSphere::IsColliding(dpShapeBase* other) {
if (!other) return false;
switch (other->GetShapeType()) {
case dpShapeType::Sphere:
return dpCollisionChecks::CheckSpheres(m_ParentEntity, other->GetParentEntity());
case dpShapeType::Box:
return dpCollisionChecks::CheckSphereBox(m_ParentEntity, other->GetParentEntity());
default:
std::cout << "No collision detection for: " << (int)m_ShapeType << "-to-" << (int)other->GetShapeType() << " collision!" << std::endl;
}
return false;
}

17
dPhysics/dpShapeSphere.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include "dpShapeBase.h"
class dpShapeSphere : public dpShapeBase {
public:
dpShapeSphere(dpEntity* parentEntity, float radius);
~dpShapeSphere();
bool IsColliding(dpShapeBase* other);
const float GetRadius() const { return m_Radius; }
void SetScale(float scale) { m_Radius = scale; }
private:
float m_Radius;
};

357
dPhysics/dpWorld.cpp Normal file
View File

@@ -0,0 +1,357 @@
#include "dpWorld.h"
#include "dpEntity.h"
#include "dpGrid.h"
#include "DetourCommon.h"
#include <string>
#include "Game.h"
#include "dLogger.h"
#include "dConfig.h"
void dpWorld::Initialize(unsigned int zoneID) {
phys_sp_tilecount = std::atoi(Game::config->GetValue("phys_sp_tilecount").c_str());
phys_sp_tilesize = std::atoi(Game::config->GetValue("phys_sp_tilesize").c_str());
//If spatial partitioning is enabled, then we need to create the m_Grid.
//if m_Grid exists, then the old method will be used.
//SP will NOT be used unless it is added to ShouldUseSP();
if (std::atoi(Game::config->GetValue("phys_spatial_partitioning").c_str()) == 1
&& ShouldUseSP(zoneID)) {
m_Grid = new dpGrid(phys_sp_tilecount, phys_sp_tilesize);
}
Game::logger->Log("dpWorld", "Physics world initialized!\n");
if (ShouldLoadNavmesh(zoneID)) {
if (LoadNavmeshByZoneID(zoneID)) Game::logger->Log("dpWorld", "Loaded navmesh!\n");
else Game::logger->Log("dpWorld", "Error(s) occurred during navmesh load.\n");
}
}
dpWorld::~dpWorld() {
if (m_Grid) {
delete m_Grid;
m_Grid = nullptr;
}
RecastCleanup();
}
void dpWorld::StepWorld(float deltaTime) {
if (m_Grid) {
m_Grid->Update(deltaTime);
return;
}
//Pre update:
for (auto entity : m_StaticEntities) {
if (!entity || entity->GetSleeping()) continue;
entity->PreUpdate();
}
//Do actual update:
for (auto entity : m_DynamicEntites) {
if (!entity || entity->GetSleeping()) continue;
entity->Update(deltaTime);
for (auto other : m_StaticEntities) {
if (!other || other->GetSleeping() || entity->GetObjectID() == other->GetObjectID()) continue;
other->CheckCollision(entity); //swap "other" and "entity" if you want dyn objs to handle collisions.
}
}
}
void dpWorld::AddEntity(dpEntity* entity) {
if (m_Grid) entity->SetGrid(m_Grid); //This sorts this entity into the right cell
else { //old method, slow
if (entity->GetIsStatic()) m_StaticEntities.push_back(entity);
else m_DynamicEntites.push_back(entity);
}
}
void dpWorld::RemoveEntity(dpEntity* entity) {
if (!entity) return;
if (m_Grid) {
m_Grid->Delete(entity);
}
else {
if (entity->GetIsStatic()) {
for (size_t i = 0; i < m_StaticEntities.size(); ++i) {
if (m_StaticEntities[i] == entity) {
delete m_StaticEntities[i];
m_StaticEntities[i] = nullptr;
break;
}
}
}
else {
for (size_t i = 0; i < m_DynamicEntites.size(); ++i) {
if (m_DynamicEntites[i] == entity) {
delete m_DynamicEntites[i];
m_DynamicEntites[i] = nullptr;
break;
}
}
}
}
}
void dpWorld::RecastCleanup() {
if (m_triareas) delete[] m_triareas;
m_triareas = 0;
rcFreeHeightField(m_solid);
m_solid = 0;
rcFreeCompactHeightfield(m_chf);
m_chf = 0;
rcFreeContourSet(m_cset);
m_cset = 0;
rcFreePolyMesh(m_pmesh);
m_pmesh = 0;
rcFreePolyMeshDetail(m_dmesh);
m_dmesh = 0;
dtFreeNavMesh(m_navMesh);
m_navMesh = 0;
dtFreeNavMeshQuery(m_navQuery);
m_navQuery = 0;
if (m_ctx) delete m_ctx;
}
bool dpWorld::LoadNavmeshByZoneID(unsigned int zoneID) {
std::string path = "./res/maps/navmeshes/" + std::to_string(zoneID) + ".bin";
m_navMesh = LoadNavmesh(path.c_str());
if (m_navMesh) { m_navQuery = dtAllocNavMeshQuery(); m_navQuery->init(m_navMesh, 2048); }
else return false;
return true;
}
dtNavMesh* dpWorld::LoadNavmesh(const char* path) {
FILE* fp;
#ifdef _WIN32
fopen_s(&fp, path, "rb");
#elif __APPLE__
// macOS has 64bit file IO by default
fp = fopen(path, "rb");
#else
fp = fopen64(path, "rb");
#endif
if (!fp) {
return 0;
}
// Read header.
NavMeshSetHeader header;
size_t readLen = fread(&header, sizeof(NavMeshSetHeader), 1, fp);
if (readLen != 1) {
fclose(fp);
return 0;
}
if (header.magic != NAVMESHSET_MAGIC) {
fclose(fp);
return 0;
}
if (header.version != NAVMESHSET_VERSION) {
fclose(fp);
return 0;
}
dtNavMesh* mesh = dtAllocNavMesh();
if (!mesh) {
fclose(fp);
return 0;
}
dtStatus status = mesh->init(&header.params);
if (dtStatusFailed(status)) {
fclose(fp);
return 0;
}
// Read tiles.
for (int i = 0; i < header.numTiles; ++i) {
NavMeshTileHeader tileHeader;
readLen = fread(&tileHeader, sizeof(tileHeader), 1, fp);
if (readLen != 1)
return 0;
if (!tileHeader.tileRef || !tileHeader.dataSize)
break;
unsigned char* data = (unsigned char*)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM);
if (!data) break;
memset(data, 0, tileHeader.dataSize);
readLen = fread(data, tileHeader.dataSize, 1, fp);
if (readLen != 1)
return 0;
mesh->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0);
}
fclose(fp);
return mesh;
}
bool dpWorld::ShouldLoadNavmesh(unsigned int zoneID) {
return true; //We use default paths now. Might re-tool this function later.
//TODO: Add to this list as the navmesh folder grows.
switch (zoneID) {
case 1100:
case 1150:
case 1151:
case 1200:
case 1201:
case 1300:
case 1400:
case 1603:
return true;
}
return false;
}
bool dpWorld::ShouldUseSP(unsigned int zoneID) {
//TODO: Add to this list as needed. Only large maps should be added as tiling likely makes little difference on small maps.
switch (zoneID) {
case 1100: //Avant Gardens
case 1200: //Nimbus Station
case 1300: //Gnarled Forest
case 1400: //Forbidden Valley
case 1800: //Crux Prime
case 1900: //Nexus Tower
case 2000: //Ninjago
return true;
}
return false;
}
float dpWorld::GetHeightAtPoint(const NiPoint3& location) {
if (m_navMesh == nullptr) {
return location.y;
}
float toReturn = 0.0f;
float pos[3];
pos[0] = location.x;
pos[1] = location.y;
pos[2] = location.z;
dtPolyRef nearestRef = 0;
float polyPickExt[3] = { 32.0f, 32.0f, 32.0f };
dtQueryFilter filter{};
m_navQuery->findNearestPoly(pos, polyPickExt, &filter, &nearestRef, 0);
m_navQuery->getPolyHeight(nearestRef, pos, &toReturn);
if (toReturn == 0.0f) {
toReturn = location.y;
}
return toReturn;
}
std::vector<NiPoint3> dpWorld::GetPath(const NiPoint3& startPos, const NiPoint3& endPos, float speed) {
std::vector<NiPoint3> path;
//allows for non-navmesh maps (like new custom maps) to have "basic" enemies.
if (m_navMesh == nullptr) {
//how many points to generate between start/end?
//note: not actually 100% accurate due to rounding, but worst case it causes them to go a tiny bit faster
//than their speed value would normally allow at the end.
int numPoints = startPos.Distance(startPos, endPos) / speed;
path.push_back(startPos); //insert the start pos
//Linearly interpolate between these two points:
for (int i = 0; i < numPoints; i++) {
NiPoint3 newPoint{ startPos };
newPoint.x += speed;
newPoint.y = newPoint.y + (((endPos.y - startPos.y) / (endPos.x - startPos.x)) * (newPoint.x - startPos.x));
path.push_back(newPoint);
}
path.push_back(endPos); //finally insert our end pos
return path;
}
float sPos[3];
float ePos[3];
sPos[0] = startPos.x;
sPos[1] = startPos.y;
sPos[2] = startPos.z;
ePos[0] = endPos.x;
ePos[1] = endPos.y;
ePos[2] = endPos.z;
dtStatus pathFindStatus;
dtPolyRef startRef;
dtPolyRef endRef;
float polyPickExt[3] = { 32.0f, 32.0f, 32.0f };
dtQueryFilter filter{};
//Find our start poly
m_navQuery->findNearestPoly(sPos, polyPickExt, &filter, &startRef, 0);
//Find our end poly
m_navQuery->findNearestPoly(ePos, polyPickExt, &filter, &endRef, 0);
pathFindStatus = DT_FAILURE;
int m_nstraightPath = 0;
int m_npolys = 0;
dtPolyRef m_polys[MAX_POLYS];
float m_straightPath[MAX_POLYS * 3];
unsigned char m_straightPathFlags[MAX_POLYS];
dtPolyRef m_straightPathPolys[MAX_POLYS];
int m_straightPathOptions = 0;
if (startRef && endRef) {
m_navQuery->findPath(startRef, endRef, sPos, ePos, &filter, m_polys, &m_npolys, MAX_POLYS);
if (m_npolys) {
// In case of partial path, make sure the end point is clamped to the last polygon.
float epos[3];
dtVcopy(epos, ePos);
if (m_polys[m_npolys - 1] != endRef)
m_navQuery->closestPointOnPoly(m_polys[m_npolys - 1], ePos, epos, 0);
m_navQuery->findStraightPath(sPos, epos, m_polys, m_npolys,
m_straightPath, m_straightPathFlags,
m_straightPathPolys, &m_nstraightPath, MAX_POLYS, m_straightPathOptions);
// At this point we have our path. Copy it to the path store
int nIndex = 0;
for (int nVert = 0; nVert < m_nstraightPath; nVert++) {
/*m_PathStore[nPathSlot].PosX[nVert] = StraightPath[nIndex++];
m_PathStore[nPathSlot].PosY[nVert] = StraightPath[nIndex++];
m_PathStore[nPathSlot].PosZ[nVert] = StraightPath[nIndex++];*/
NiPoint3 newPoint{ m_straightPath[nIndex++], m_straightPath[nIndex++], m_straightPath[nIndex++] };
path.push_back(newPoint);
}
}
}
else {
m_npolys = 0;
m_nstraightPath = 0;
}
return path;
}

80
dPhysics/dpWorld.h Normal file
View File

@@ -0,0 +1,80 @@
#pragma once
#include "Singleton.h"
#include <vector>
//Navmesh includes:
#include "Recast.h"
#include "DetourNavMesh.h"
#include "DetourNavMeshBuilder.h"
#include "DetourNavMeshQuery.h"
#include <vector>
#include <map>
static const int NAVMESHSET_MAGIC = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; //'MSET';
static const int NAVMESHSET_VERSION = 1;
struct NavMeshSetHeader {
int magic;
int version;
int numTiles;
dtNavMeshParams params;
};
struct NavMeshTileHeader {
dtTileRef tileRef;
int dataSize;
};
static const int MAX_POLYS = 256;
static const int MAX_SMOOTH = 2048;
class NiPoint3;
class dpEntity;
class dpGrid;
class dpWorld : public Singleton<dpWorld> {
public:
void Initialize(unsigned int zoneID);
~dpWorld();
void RecastCleanup();
bool LoadNavmeshByZoneID(unsigned int zoneID);
dtNavMesh* LoadNavmesh(const char* path);
bool ShouldLoadNavmesh(unsigned int zoneID);
bool ShouldUseSP(unsigned int zoneID);
float GetHeightAtPoint(const NiPoint3& location);
std::vector<NiPoint3> GetPath(const NiPoint3& startPos, const NiPoint3& endPos, float speed = 10.0f);
bool IsLoaded() const { return m_navMesh != nullptr; }
void StepWorld(float deltaTime);
void AddEntity(dpEntity* entity);
void RemoveEntity(dpEntity* entity);
private:
dpGrid* m_Grid;
bool phys_spatial_partitioning = 1;
int phys_sp_tilesize = 205;
int phys_sp_tilecount = 12;
std::vector<dpEntity*> m_StaticEntities;
std::vector<dpEntity*> m_DynamicEntites;
//Navmesh stuffs:
unsigned char* m_triareas;
rcHeightfield* m_solid;
rcCompactHeightfield* m_chf;
rcContourSet* m_cset;
rcPolyMesh* m_pmesh;
rcConfig m_cfg;
rcPolyMeshDetail* m_dmesh;
class InputGeom* m_geom;
class dtNavMesh* m_navMesh;
class dtNavMeshQuery* m_navQuery;
unsigned char m_navMeshDrawFlags;
rcContext* m_ctx;
};

35
dPhysics/main.cpp Normal file
View File

@@ -0,0 +1,35 @@
//This file included for reference only
/*#include <iostream>
#include <chrono>
#include <thread>
#include "dpWorld.h"
#include "NiQuaternion.hpp"
#include "NiPoint3.hpp"
int main() {
std::cout << "dPhysics test engine" << std::endl;
//Test rotation code:
NiPoint3 p(1.0f, 0.0f, 0.0f);
float angle = 45.0f;
NiQuaternion q = NiQuaternion::CreateFromAxisAngle(NiPoint3(0.0f, 0.0f, 1.0f), angle);
NiPoint3 rotated = p.RotateByQuaternion(q);
std::cout << "OG: " << p.x << ", " << p.y << ", " << p.z << std::endl;
std::cout << "Quater: " << q.x << ", " << q.y << ", " << q.z << ", " << q.w << " angle: " << angle << std::endl;
std::cout << "Rotated: " << rotated.x << ", " << rotated.y << ", " << rotated.z << std::endl;
//Test some collisions:
dpWorld::GetInstance().Initialize(1000);
while (true) {
dpWorld::GetInstance().StepWorld(1.0f/60.0f);
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
return 0;
}*/