Merge remote-tracking branch 'origin/master' into AndersTest

# Conflicts:
#	include/Game/Game.h
#	src/Engine/Core/OctTree.cpp
#	src/Game/Game.cpp
#	src/Tests/CMakeLists.txt
#	src/Tests/OctTreeTest.cpp
#	src/Tests/WorldTest.cpp
This commit is contained in:
verysecrethero
2015-12-18 11:57:21 +01:00
42 changed files with 1804 additions and 310 deletions
+59
View File
@@ -0,0 +1,59 @@
#ifndef Collision_h__
#define Collision_h__
//NOTE: Collision.h needs to be #included before <GLFW/glfw3.h>,
//because Collision #includes "RawModel.h", which has "Texture.h", which has "OpenGL.h" which must be #included first
//or you will get "fatal error C1189: #error: gl.h included before glew.h"
#include <vector>
#include "Core/Ray.h"
#include "Core/AABB.h"
#include "Engine/Rendering/RawModel.h"
#include "Core/Entity.h"
class World;
struct ComponentWrapper;
namespace Collision
{
//Return true if the ray hits the box.
bool RayAABBIntr(const Ray& ray, const AABB& box);
bool RayVsAABB(const Ray& ray, const AABB& box);
//Return true if the ray hits the box, also outputs distance from ray origin to intersection point in [outDistance].
bool RayVsAABB(const Ray& ray, const AABB& box, float& outDistance);
//Return true if the ray hits any of the triangles in the model. Stops checking when a hit is detected.
bool RayVsModel(const Ray& ray,
const std::vector<RawModel::Vertex>& modelVertices,
const std::vector<unsigned int>& modelIndices);
//Return true if the ray hits any of the triangles in the model.
//Also returns the position of the intersection point. Will loop through all the whole model indices.
bool RayVsModel(const Ray& ray,
const std::vector<RawModel::Vertex>& modelVertices,
const std::vector<unsigned int>& modelIndices,
glm::vec3& outHitPosition);
//Return true if the ray hits any of the triangles in the model.
//Also returns the distance from the ray origin to the closest
//intersection point, and the barycentric u,v-coordinates. Will loop through all the whole model indices.
bool RayVsModel(const Ray& ray,
const std::vector<RawModel::Vertex>& modelVertices,
const std::vector<unsigned int>& modelIndices,
float& outDistance,
float& outUCoord,
float& outVCoord);
//Return true if the boxes are intersecting.
bool AABBVsAABB(const AABB& a, const AABB& b);
//Return true if the boxes are intersecting.
//Also outputs the minimum translation that box [a] would need in order to resolve collision.
bool AABBVsAABB(const AABB& a, const AABB& b, glm::vec3& minimumTranslation);
bool IsSameBoxProbably(const AABB& first, const AABB& second, const float epsilon = 0.0001f);
//Returns true if the entity has a boundingbox. Outputs the aabb in [outBox].
bool GetEntityBox(World* world, EntityID entity, AABB& outBox, bool forceBoxFromModel = false);
bool GetEntityBox(World* world, ComponentWrapper& AABBComponent, AABB& outBox);
}
#endif
@@ -0,0 +1,31 @@
#ifndef CollisionSystem_h__
#define CollisionSystem_h__
#include <GLFW/glfw3.h>
#include <glm/common.hpp>
#include "Common.h"
#include "Core/System.h"
#include "Core/EventBroker.h"
#include "Core/EKeyUp.h"
class CollisionSystem : public PureSystem
{
public:
CollisionSystem(EventBroker* eventBroker)
: PureSystem(eventBroker, "AABB")
, zPress(false)
{
//TODO: Debug stuff, remove later.
EVENT_SUBSCRIBE_MEMBER(m_EKeyUp, &CollisionSystem::OnKeyUp);
}
virtual void UpdateComponent(World* world, ComponentWrapper& cAABB, double dt) override;
private:
bool zPress;
EventRelay<CollisionSystem, Events::KeyUp> m_EKeyUp;
bool OnKeyUp(const Events::KeyUp &event);
};
#endif
+39
View File
@@ -0,0 +1,39 @@
#ifndef Events_TriggerEnter_h__
#define Events_TriggerEnter_h__
#include "../Core/EventBroker.h"
#include "../Core/Entity.h"
namespace Events
{
/** Thrown once, when an entity is only touching a trigger. */
struct TriggerTouch : Event
{
/** The id of the entity that touches the trigger. */
EntityID Entity;
/** The id of the trigger entity. */
EntityID Trigger;
};
/** Thrown once, when an entity has completely left a trigger. */
struct TriggerLeave : Event
{
/** The id of the entity that left the trigger. */
EntityID Entity;
/** The id of the trigger entity. */
EntityID Trigger;
};
/** Thrown once, when an entity is completely contained inside a trigger. */
struct TriggerEnter : Event
{
/** The id of the entity that entered the trigger. */
EntityID Entity;
/** The id of the trigger entity. */
EntityID Trigger;
};
}
#endif
+38
View File
@@ -0,0 +1,38 @@
#ifndef TriggerSystem_h__
#define TriggerSystem_h__
#include <glm/common.hpp>
#include <unordered_set>
#include "Core/System.h"
#include "Core/EventBroker.h"
#include "ETrigger.h"
class AABB;
class TriggerSystem : public PureSystem
{
public:
TriggerSystem(EventBroker* eventBroker)
: PureSystem(eventBroker, "Trigger")
{}
virtual void UpdateComponent(World* world, ComponentWrapper& collision, double dt) override;
private:
std::unordered_map<EntityID, std::unordered_set<EntityID>> m_EntitiesTouchingTrigger;
std::unordered_map<EntityID, std::unordered_set<EntityID>> m_EntitiesCompletelyInTrigger;
//True if leave event was thrown.
bool throwLeaveIfWasInTrigger(std::unordered_set<EntityID>& triggerSet, EntityID pId, EntityID tId);
template<typename Event>
void publish(EntityID pId, EntityID tId)
{
Event e;
e.Trigger = tId;
e.Entity = pId;
m_EventBroker->Publish(e);
}
};
#endif
+2 -1
View File
@@ -4,4 +4,5 @@
#include <map>
#include <unordered_map>
#include "Core/Util/Logging.h"
#include "Core/Util/Logging.h"
#include "Core/Util/IfDebug.h"
+2 -1
View File
@@ -9,6 +9,7 @@ public:
AABB() = default;
//No checks are made. Values in minPos must be less than values in maxPos, i.e. min.x < max.x, etc.
AABB(const glm::vec3& minPos, const glm::vec3& maxPos);
AABB(const glm::vec4& minPos, const glm::vec4& maxPos);
//No checks are made. Size must consist of non-negative numbers.
virtual void CreateFromCenter(const glm::vec3& center, const glm::vec3& size);
virtual ~AABB();
@@ -16,7 +17,7 @@ public:
const glm::vec3& MinCorner() const { return m_MinCorner; }
const glm::vec3& MaxCorner() const { return m_MaxCorner; }
const glm::vec3& Center() const { return m_Center; }
const glm::vec3& Size() const { return 2.0f * m_HalfSize; }
const glm::vec3 Size() const { return 2.0f * m_HalfSize; }
const glm::vec3& HalfSize() const { return m_HalfSize; }
private:
glm::vec3 m_MinCorner;
-17
View File
@@ -1,17 +0,0 @@
#ifndef Collision_h__
#define Collision_h__
#include "Core/Ray.h"
#include "Core/AABB.h"
namespace Collision
{
bool RayAABBIntr(const Ray& ray, const AABB& box);
bool RayVsAABB(const Ray& ray, const AABB& box);
bool RayVsAABB(const Ray& ray, const AABB& box, float& outDistance);
bool AABBVsAABB(const AABB& a, const AABB& b);
}
#endif
+1 -1
View File
@@ -20,7 +20,7 @@ public:
~ComponentPoolForwardIterator() = default;
ComponentPoolForwardIterator& operator=(const ComponentPoolForwardIterator& other) = default;
ComponentPoolForwardIterator& operator++();
ComponentPoolForwardIterator& operator++(int);
ComponentPoolForwardIterator operator++(int);
bool operator!=(const ComponentPoolForwardIterator& other) const;
bool operator==(const ComponentPoolForwardIterator& other) const;
ComponentWrapper operator*() const;
+1 -1
View File
@@ -253,7 +253,7 @@ public:
}
//Postfix increment i.e. iter++. Prefer pre-increment (++iter) for efficiency.
MemoryPoolForwardIterator& operator++(int)
MemoryPoolForwardIterator operator++(int)
{
MemoryPoolForwardIterator<T> copyIter(*this);
operator++();
+65 -27
View File
@@ -3,9 +3,7 @@
#include "Core/AABB.h"
struct Ray;
class World;
class Camera;
class Ray;
class OctTree
{
@@ -13,54 +11,94 @@ public:
struct Output
{
float CollideDistance;
};
};
OctTree();
~OctTree();
//For the root OctTree, [octTreeBounds] should be a box containing the entire level.
OctTree(const AABB& octTreeBounds, int subDivisions);
//We should only ever need one OctTree in the game, and it should not need to be copied.
//Define these if the OctTree suddenly needs to be copied, think of the children OctTree* ptrs.
//We cannot copy the OctTree as of now, because of the recursive dynamic allocation.
//Define these if the OctTree suddenly needs to be copied, think of the children OctChild* ptrs.
OctTree(const OctTree& other) = delete;
OctTree(const OctTree&& other) = delete;
OctTree& operator= (const OctTree& other) = delete;
//Collision test function. WTODO: Probably remove or relocate elsewhere, Collision system?
void Update(float dt, World* world, Camera* cam);
//Add a dynamic object (one that moves around) into the tree.
void AddDynamicObject(const AABB& box);
//Add a static object (that does not move) into the tree.
void AddStaticObject(const AABB& box);
void BoxesInSameRegion(const AABB& box, std::vector<AABB>& outBoxes) const;
//Get the boxes that are in the same area as the input [box], the boxes are put in [outBoxes].
void BoxesInSameRegion(const AABB& box, std::vector<AABB>& outBoxes);
//Empty the tree of all objects, static and dynamic.
void ClearObjects();
//Empty the tree of all dynamic objects. Static objects remain in the tree.
void ClearDynamicObjects();
//Returns true if the ray collides with something in the tree. Result is written to [data].
bool RayCollides(const Ray& ray, Output& data) const;
bool RayCollides(const Ray& ray, Output& data);
//Returns true if the box collides with something in the tree.
//On collision with a box, that box is written to [outBoxIntersected].
bool BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected) const;
//Note: More efficient than calling BoxesInSameRegion from outside and testing there.
bool BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected);
private:
OctTree* m_Children[8];
//WTODO: Do -derived class from AABB- struct containing AABB, with a bool Tested, falsify at
//start of Collision test, set on check, don't check if set already. Solves duplicate boxes in tree.
//Store indices in the struct, pointing to grand ancestor list of boxes, need the same AABB not copies to save Tested.
//WTODO: Boxes collide with themselves? Fix somehow, maybe float epsilon stuff.
std::vector<AABB> m_StaticObjects;
std::vector<AABB> m_DynamicObjects;
AABB m_Box;
struct OctChild; //Fwd declaration;
struct ContainedObject
{
ContainedObject()
: Box(AABB())
, Checked(false)
{}
ContainedObject(AABB box)
: Box(box)
, Checked(false)
{}
AABB Box;
bool Checked;
};
OctChild* m_Root;
std::vector<ContainedObject> m_StaticObjects;
std::vector<ContainedObject> m_DynamicObjects;
bool m_UpdatedOnce;
unsigned int m_BoxID;
glm::vec3 m_PrevPos;
glm::quat m_PrevOri;
inline bool hasChildren() const;
public:
int childIndexContainingPoint(const glm::vec3& point) const;
std::vector<int> childIndicesContainingBox(const AABB& box) const;
void falsifyObjectChecks();
struct OctChild
{
~OctChild();
OctChild(const AABB& octTreeBounds,
int subDivisions,
std::vector<OctTree::ContainedObject>& staticObjects,
std::vector<OctTree::ContainedObject>& dynamicObjects);
OctChild(const OctChild& other) = delete;
OctChild(const OctChild&& other) = delete;
OctChild& operator= (const OctChild& other) = delete;
void AddDynamicObject(const AABB& box);
void AddStaticObject(const AABB& box);
void BoxesInSameRegion(const AABB& box, std::vector<AABB>& outBoxes) const;
void ClearObjects();
void ClearDynamicObjects();
bool RayCollides(const Ray& ray, Output& data) const;
bool BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected) const;
OctChild* m_Children[8];
//Indices into the lists in OctTree.
std::vector<int> m_StaticObjIndices;
std::vector<int> m_DynamicObjIndices;
AABB m_Box;
//Reference to the lists in OctTree.
std::vector<OctTree::ContainedObject>& m_StaticObjectsRef;
std::vector<OctTree::ContainedObject>& m_DynamicObjectsRef;
inline bool hasChildren() const;
int childIndexContainingPoint(const glm::vec3& point) const;
std::vector<int> childIndicesContainingBox(const AABB& box) const;
};
};
#endif
+22 -3
View File
@@ -2,11 +2,30 @@
#define Ray_h__
#include "../GLM.h"
#include "Common.h"
struct Ray
class Ray
{
glm::vec3 Origin;
glm::vec3 Direction;
public:
Ray(const glm::vec3& origin, const glm::vec3& dir)
: m_Origin(origin)
, m_Direction(glm::normalize(dir))
{
DEBUG_IF(true) {
if (glm::any(glm::isnan(m_Direction))) {
LOG_WARNING("Ray Direction was set to the zero-vector, expect unknown side effects and/or crashes.");
}
}
}
const glm::vec3& Origin() const { return m_Origin; }
const glm::vec3& Direction() const { return m_Direction; }
//Sets the ray origin at parameter.
void SetOrigin(const glm::vec3& origin) { m_Origin = origin; }
//Normalizes the parameter and sets direction to it.
void SetDirection(const glm::vec3& direction) { m_Direction = glm::normalize(direction); }
private:
glm::vec3 m_Origin;
glm::vec3 m_Direction;
};
#endif // Ray_h__
+12
View File
@@ -0,0 +1,12 @@
// Example:
// DEBUG_IF(condition) {
// // This code is executed only in debug mode and if condition is true.
// }
// NOTE: condition statement is not executed at all in release mode.
#ifndef DEBUG_IF
#ifndef DEBUG
#define DEBUG_IF(c) if(c)
#else
#define DEBUG_IF(c) if(false)
#endif
#endif
-3
View File
@@ -19,8 +19,6 @@
#include "PlayerSystem.h"
#include "Editor/EditorSystem.h"
class OctTree;
class Game
{
public:
@@ -41,7 +39,6 @@ private:
World* m_World;
SystemPipeline* m_SystemPipeline;
RenderQueueFactory* m_RenderQueueFactory;
OctTree* m_OctTree;
EventRelay<Game, Events::InputCommand> m_EInputCommand;
bool debugOnInputCommand(const Events::InputCommand& e);
+10
View File
@@ -9,6 +9,7 @@
#include "Core/EventBroker.h"
#include "Core/EKeyDown.h"
#include "Core/EKeyUp.h"
#include "Collision/ETrigger.h"
struct KeyInput
{
@@ -26,6 +27,9 @@ public:
{
EVENT_SUBSCRIBE_MEMBER(m_EKeyDown, &PlayerSystem::OnKeyDown);
EVENT_SUBSCRIBE_MEMBER(m_EKeyUp, &PlayerSystem::OnKeyUp);
EVENT_SUBSCRIBE_MEMBER(m_ETouch, &PlayerSystem::OnTouch);
EVENT_SUBSCRIBE_MEMBER(m_EEnter, &PlayerSystem::OnEnter);
EVENT_SUBSCRIBE_MEMBER(m_ELeave, &PlayerSystem::OnLeave);
}
virtual void UpdateComponent(World* world, ComponentWrapper& player, double dt) override;
@@ -39,6 +43,12 @@ private:
bool OnKeyDown(const Events::KeyDown &event);
EventRelay<PlayerSystem, Events::KeyUp> m_EKeyUp;
bool OnKeyUp(const Events::KeyUp &event);
EventRelay<PlayerSystem, Events::TriggerEnter> m_EEnter;
bool OnEnter(const Events::TriggerEnter &event);
EventRelay<PlayerSystem, Events::TriggerTouch> m_ETouch;
bool PlayerSystem::OnTouch(const Events::TriggerTouch &event);
EventRelay<PlayerSystem, Events::TriggerLeave> m_ELeave;
bool PlayerSystem::OnLeave(const Events::TriggerLeave &event);
};
#endif
+2 -1
View File
@@ -11,7 +11,8 @@
#include "Rendering/RenderQueueFactory.h"
#include "OctTreeTestHardCodedTestWorld.h"
#include "Core\Collision.h"
#include "Collision/Collision.h"
class Game
{
+25 -25
View File
@@ -7,6 +7,7 @@
#include <vector>
//last!
//#include "OldOctTree.h"
#define private public
#include <Engine\Core\OctTree.h>
@@ -15,9 +16,9 @@ class HardcodedTestWorld : public World
public:
struct LinkOctTreeAndModel {
EntityID entId;
OctTree* child;
OctTree::OctChild* child;
glm::vec3 posxyz;
LinkOctTreeAndModel(EntityID eId, OctTree* ch, glm::vec3 pos)
LinkOctTreeAndModel(EntityID eId, OctTree::OctChild* ch, glm::vec3 pos)
{
entId = eId;
child = ch;
@@ -76,7 +77,7 @@ private:
auto someAABB = AABB(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(1.0f, 1.0f, 1.0f));
//draw main box first
AddBoxModel(someAABB.Center(), someAABB.HalfSize().x, &someOctTree, tempId);
AddBoxModel(someAABB.Center(), someAABB.HalfSize().x, someOctTree.m_Root, tempId);
//add anotherbox in octTree
auto anotherBox = AABB(glm::vec3(0.1f, 0.1f, 0.1f), glm::vec3(0.2f, 0.2f, 0.2f));
@@ -84,15 +85,15 @@ private:
someOctTree.AddDynamicObject(anotherBox);
//draw anotherbox and save it in anotherBoxTransformId
AddBoxModel(anotherBox.Center(), anotherBox.HalfSize().x, &someOctTree, anotherBoxTransformId);
AddBoxModel(anotherBox.Center(), anotherBox.HalfSize().x, someOctTree.m_Root, anotherBoxTransformId);
//draw the octTree
for (size_t j = 0; j < 8; j++)
{
AddBoxModel(someOctTree.m_Children[j]->m_Box.Center(),
someOctTree.m_Children[j]->m_Box.HalfSize().x, someOctTree.m_Children[j], tempId);
AddBoxModel(someOctTree.m_Root->m_Children[j]->m_Box.Center(),
someOctTree.m_Root->m_Children[j]->m_Box.HalfSize().x, someOctTree.m_Root->m_Children[j], tempId);
auto someChild = someOctTree.m_Children[j];
auto someChild = someOctTree.m_Root->m_Children[j];
for (size_t i = 0; i < 8; i++)
{
@@ -103,24 +104,6 @@ private:
}
}//end CreateEnt
void AddBoxModel(const glm::vec3 &center, const float &halfSize, OctTree* child, EntityID &outEntityId) {
World& world = *this;
EntityID entityDummyScene = world.CreateEntity();
outEntityId = entityDummyScene;
ComponentWrapper transform = world.AttachComponent(entityDummyScene, "Transform");
transform["Position"] = center;
transform["Scale"] = glm::vec3(1.0f, 1.0f, 1.0f)*halfSize*2.0f*0.97f;
ComponentWrapper model = world.AttachComponent(entityDummyScene, "Model");
model["Resource"] = "Models/Core/UnitBox.obj";
model["Color"] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
if (child->m_DynamicObjects.size() != 0)
model["Color"] = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);
linkOM.emplace_back(entityDummyScene, child, center);
//extra
//allModels.push_back(model);
}
void createTestEntitiesTest2()
{
World& world = *this;
@@ -131,4 +114,21 @@ private:
ComponentWrapper model = world.AttachComponent(entityCollisionBox, "Model");
model["Resource"] = "Models/Core/UnitBox.obj";
}
void AddBoxModel(const glm::vec3 &center, const float &halfSize, OctTree::OctChild* child, EntityID &outEntityId) {
World& world = *this;
EntityID entityDummyScene = world.CreateEntity();
outEntityId = entityDummyScene;
ComponentWrapper transform = world.AttachComponent(entityDummyScene, "Transform");
transform["Position"] = center;
transform["Scale"] = glm::vec3(1.0f, 1.0f, 1.0f)*halfSize*2.0f*0.97f;
ComponentWrapper model = world.AttachComponent(entityDummyScene, "Model");
model["Resource"] = "Models/Core/UnitBox.obj";
model["Color"] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
if (child->m_DynamicObjIndices.size() != 0)
model["Color"] = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);
linkOM.emplace_back(entityDummyScene, child, center);
}
};
+2
View File
@@ -6,4 +6,6 @@
<xs:include schemaLocation="Components/Test.xsd"/>
<xs:include schemaLocation="Components/RaptorCopter.xsd"/>
<xs:include schemaLocation="Components/Player.xsd"/>
<xs:include schemaLocation="Components/AABB.xsd"/>
<xs:include schemaLocation="Components/Trigger.xsd"/>
</xs:schema>
+4
View File
@@ -0,0 +1,4 @@
<c:AABB>
<BoxCenter X="0" Y="0" Z="0"/>
<BoxSize X="1" Y="1" Z="1"/>
</c:AABB>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:t="types">
<xs:import schemaLocation="../Types.xsd" namespace="types"/>
<xs:element name="AABB">
<xs:complexType>
<xs:all>
<xs:element name="BoxCenter" type="t:Vector" minOccurs="0"/>
<xs:element name="BoxSize" type="t:Vector" minOccurs="0"/>
</xs:all>
</xs:complexType>
</xs:element>
</xs:schema>
+2
View File
@@ -0,0 +1,2 @@
<c:Trigger>
</c:Trigger>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:t="types">
<xs:import schemaLocation="../Types.xsd" namespace="types"/>
<xs:element name="Trigger">
</xs:element>
</xs:schema>
@@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8"?>
<Entity xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xi="http://www.w3.org/2001/XInclude" xsi:noNamespaceSchemaLocation="../Types/Entity.xsd" xmlns:c="components">
<Components>
<c:Transform>
<Orientation X="0" Y="0" Z="0"/>
</c:Transform>
<c:Model>
<Resource>Models/DummyScene.obj</Resource>
</c:Model>
</Components>
<Children>
<Entity>
<Components>
<c:Transform>
<Position X="-1.5"/>
<Scale X="1" Y="1" Z="1"/>
</c:Transform>
<c:Model>
<Resource>Models/ScaleWidget.obj</Resource>
</c:Model>
</Components>
</Entity>
<Entity>
<Components>
<c:Transform>
<Position X="1.5"/>
<Scale X="2" Y="2" Z="2"/>
</c:Transform>
<c:Model>
<Resource>Models/RotationWidgetX.obj</Resource>
</c:Model>
<c:Trigger>
</c:Trigger>
</Components>
</Entity>
<Entity>
<Components>
<c:Player>
<Velocity X="0" Y="0" Z="0"/>
</c:Player>
<c:Transform>
<Position X="2.5"/>
</c:Transform>
<c:Model>
<Resource>Models/Core/UnitCube.obj</Resource>
</c:Model>
<c:AABB>
</c:AABB>
</Components>
</Entity>
<Entity>
<Components>
<c:Transform>
<Position X="0" Y="-0"/>
<Scale X="1" Y="1" Z="1"/>
</c:Transform>
<!--<c:Move>
<Speed>1</Speed>
<Direction X="-1"/>
<Rotation Y="3.14"/>
</c:Move>-->
</Components>
<Children>
<Entity>
<Components>
<c:Transform>
<Position X="0" Y="0"/>
<Orientation X="0.0" Y="0" Z="1.0"/>
</c:Transform>
<c:Model>
<Resource>Models/Core/UnitRaptor.obj</Resource>
<Color R="1" G="0.4" B="0.8"/>
</c:Model>
</Components>
<Children>
<Entity>
<Components>
<c:Transform>
<Position X="-0.01" Y="0.55"/>
<Orientation X="0" Y="0" Z="-1"/>
</c:Transform>
<c:RaptorCopter>
<Speed>20</Speed>
<Axis Y="1"/>
</c:RaptorCopter>
</Components>
<Children>
<Entity>
<Components>
<c:Transform>
<Position X="0" Y="0"/>
<Scale X="1.7" Y="0.03" Z="0.1"/>
<Orientation X="0" Y="0" Z="0"/>
</c:Transform>
<c:Model>
<Resource>Models/Core/UnitCube.obj</Resource>
<Color R="1" G="0.4" B="0.8"/>
</c:Model>
</Components>
</Entity>
<Entity>
<Components>
<c:Transform>
<Position X="0" Y="0"/>
<Scale X="1.7" Y="0.03" Z="0.1"/>
<Orientation X="0" Y="1.57" Z="0"/>
</c:Transform>
<c:Model>
<Resource>Models/Core/UnitCube.obj</Resource>
<Color R="1" G="0.4" B="0.8"/>
</c:Model>
</Components>
</Entity>
</Children>
</Entity>
</Children>
</Entity>
</Children>
</Entity>
</Children>
</Entity>
+5
View File
@@ -25,10 +25,13 @@
<Components>
<c:Transform>
<Position X="1.5"/>
<Scale X="2" Y="2" Z="2"/>
</c:Transform>
<c:Model>
<Resource>Models/RotationWidget.obj</Resource>
</c:Model>
<c:Trigger>
</c:Trigger>
</Components>
</Entity>
<Entity>
@@ -42,6 +45,8 @@
<c:Model>
<Resource>Models/Core/UnitCube.obj</Resource>
</c:Model>
<c:AABB>
</c:AABB>
</Components>
</Entity>
<Entity>
+7
View File
@@ -69,6 +69,12 @@ file(GLOB SOURCE_FILES_GUI
)
source_group(GUI FILES ${SOURCE_FILES_GUI})
file(GLOB SOURCE_FILES_Collision
"${INCLUDE_PATH}/Collision/*.h"
"Collision/*.cpp"
)
source_group(Collision FILES ${SOURCE_FILES_Collision})
file(GLOB SOURCE_FILES_Editor
"${INCLUDE_PATH}/Editor/*.h"
"Editor/*.cpp"
@@ -83,6 +89,7 @@ set(SOURCE_FILES
${SOURCE_FILES_GUI}
${SOURCE_FILES_Rendering}
${SOURCE_FILES_Rendering_Util}
${SOURCE_FILES_Collision}
${CMAKE_SOURCE_DIR}/deps/include/imgui/imgui.cpp
${CMAKE_SOURCE_DIR}/deps/include/imgui/imgui_draw.cpp
${CMAKE_SOURCE_DIR}/deps/include/imgui/imgui_demo.cpp
+279
View File
@@ -0,0 +1,279 @@
#include <algorithm>
#include "Collision/Collision.h"
#include "Engine/GLM.h"
#include "Core/World.h"
#include "Rendering/Model.h"
namespace Collision
{
//note: this one hasnt been delta adjusted like RayVsAABB has
bool RayAABBIntr(const Ray& ray, const AABB& box)
{
glm::vec3 w = 75.0f * ray.Direction();
glm::vec3 v = glm::abs(w);
glm::vec3 c = ray.Origin() - box.Center() + w;
glm::vec3 half = box.HalfSize();
if (abs(c.x) > v.x + half.x) {
return false;
}
if (abs(c.y) > v.y + half.y) {
return false;
}
if (abs(c.z) > v.z + half.z) {
return false;
}
if (abs(c.y*w.z - c.z*w.y) > half.y*v.z + half.z*v.y) {
return false;
}
if (abs(c.x*w.z - c.z*w.x) > half.x*v.z + half.z*v.x) {
return false;
}
return !(abs(c.x*w.y - c.y*w.x) > half.x*v.y + half.y*v.x);
}
bool RayVsAABB(const Ray& ray, const AABB& box)
{
float dummy;
return RayVsAABB(ray, box, dummy);
}
bool RayVsAABB(const Ray& ray, const AABB& box, float& outDistance)
{
glm::vec3 invdir = 1.0f / ray.Direction();
glm::vec3 origin = ray.Origin();
float t1 = (box.MinCorner().x - origin.x)*invdir.x;
float t2 = (box.MaxCorner().x - origin.x)*invdir.x;
float t3 = (box.MinCorner().y - origin.y)*invdir.y;
float t4 = (box.MaxCorner().y - origin.y)*invdir.y;
float t5 = (box.MinCorner().z - origin.z)*invdir.z;
float t6 = (box.MaxCorner().z - origin.z)*invdir.z;
float tmin = std::max(std::max(std::min(t1, t2), std::min(t3, t4)), std::min(t5, t6));
float tmax = std::min(std::min(std::max(t1, t2), std::max(t3, t4)), std::max(t5, t6));
//if (tmax < 0 || tmin > tmax)
//if tmin,tmax are almost the same (i.e. hitting exactly in the corner) then tmin might be slightly
//greater than tmax becuase of floating-precision problems. fixed by adding a small delta to tmax
if (tmax < 0 || tmin>(tmax + 0.0001f))
return false;
outDistance = (tmin > 0) ? tmin : tmax;
return true;
}
bool AABBVsAABB(const AABB& a, const AABB& b)
{
const glm::vec3& aCenter = a.Center();
const glm::vec3& bCenter = b.Center();
const glm::vec3& aHSize = a.HalfSize();
const glm::vec3& bHSize = b.HalfSize();
//Test will probably exit because of the X and Z axes more often, so test them first.
if (abs(aCenter[0] - bCenter[0]) > (aHSize[0] + bHSize[0])) {
return false;
}
if (abs(aCenter[2] - bCenter[2]) > (aHSize[2] + bHSize[2])) {
return false;
}
return (abs(aCenter[1] - bCenter[1]) <= (aHSize[1] + bHSize[1]));
}
bool AABBVsAABB(const AABB& a, const AABB& b, glm::vec3& minimumTranslation)
{
minimumTranslation = glm::vec3(0, 0, 0);
const glm::vec3& aMax = a.MaxCorner();
const glm::vec3& bMax = b.MaxCorner();
const glm::vec3& aMin = a.MinCorner();
const glm::vec3& bMin = b.MinCorner();
const glm::vec3& bSize = b.Size();
const glm::vec3& aSize = a.Size();
float minOffset = INFINITY;
float off;
auto axisesIntersecting = glm::tvec3<bool, glm::highp>(false, false, false);
for (int i = 0; i < 3; ++i) {
off = bMax[i] - aMin[i];
if (off > 0 && off < bSize[i] + aSize[i]) {
if (off < minOffset) {
minimumTranslation = glm::vec3();
minimumTranslation[i] = minOffset = off;
}
axisesIntersecting[i] = true;
}
off = aMax[i] - bMin[i];
if (off > 0 && off < bSize[i] + aSize[i]) {
if (off < minOffset) {
minOffset = off;
minimumTranslation = glm::vec3();
minimumTranslation[i] = -off;
}
axisesIntersecting[i] = true;
}
}
return glm::all(axisesIntersecting);
}
bool RayVsModel(const Ray& ray,
const std::vector<RawModel::Vertex>& modelVertices,
const std::vector<unsigned int>& modelIndices)
{
for (int i = 0; i < modelIndices.size(); ++i) {
glm::vec3 v0 = modelVertices[modelIndices[i]].Position;
glm::vec3 e1 = modelVertices[modelIndices[++i]].Position - v0; //v1 - v0
glm::vec3 e2 = modelVertices[modelIndices[++i]].Position - v0; //v2 - v0
glm::vec3 m = ray.Origin() - v0;
glm::vec3 MxE1 = glm::cross(m, e1);
glm::vec3 DxE2 = glm::cross(ray.Direction(), e2);
float DetInv = glm::dot(e1, DxE2);
if (std::abs(DetInv) < FLT_EPSILON) {
continue;
}
DetInv = 1.0f / DetInv;
float u = glm::dot(m, DxE2) * DetInv;
float v = glm::dot(ray.Direction(), MxE1) * DetInv;
//u,v can be very close to 0 but still negative sometimes. added a deltafactor to compensate for that problem
if ((u + 0.001f) < 0 || (v + 0.001f) < 0 || 1 < u + v) {
continue;
}
//Here, u and v are positive, u+v <= 1, and if distance is positive - triangle is hit.
if (0 <= glm::dot(e2, MxE1) * DetInv) {
return true;
}
}
return false;
}
bool RayVsModel(const Ray& ray,
const std::vector<RawModel::Vertex>& modelVertices,
const std::vector<unsigned int>& modelIndices,
float& outDistance,
float& outUCoord,
float& outVCoord)
{
outDistance = INFINITY;
bool hit = false;
for (int i = 0; i < modelIndices.size(); ++i) {
glm::vec3 v0 = modelVertices[modelIndices[i]].Position;
glm::vec3 e1 = modelVertices[modelIndices[++i]].Position - v0; //v1 - v0
glm::vec3 e2 = modelVertices[modelIndices[++i]].Position - v0; //v2 - v0
glm::vec3 m = ray.Origin() - v0;
glm::vec3 MxE1 = glm::cross(m, e1);
glm::vec3 DxE2 = glm::cross(ray.Direction(), e2);//pVec
float DetInv = glm::dot(e1, DxE2);
if (std::abs(DetInv) < FLT_EPSILON) {
continue;
}
DetInv = 1.0f / DetInv;
float dist = glm::dot(e2, MxE1) * DetInv;
if (dist >= outDistance) {
continue;
}
float u = glm::dot(m, DxE2) * DetInv;
float v = glm::dot(ray.Direction(), MxE1) * DetInv;
//u,v can be very close to 0 but still negative sometimes. added a deltafactor to compensate for that problem
//If u and v are positive, u+v <= 1, dist is positive, and less than closest.
if (0 <= (u + 0.001f) && 0 <= (v + 0.001f) && u + v <= 1 && 0 <= dist) {
outDistance = dist;
outUCoord = u;
outVCoord = v;
hit = true;
}
}
return hit;
}
bool RayVsModel(const Ray& ray,
const std::vector<RawModel::Vertex>& modelVertices,
const std::vector<unsigned int>& modelIndices,
glm::vec3& outHitPosition)
{
float u;
float v;
float dist;
bool hit = RayVsModel(ray, modelVertices, modelIndices, dist, u, v);
outHitPosition = ray.Origin() + dist * ray.Direction();
return hit;
}
bool IsSameBoxProbably(const AABB& first, const AABB& second, const float epsilon)
{
const glm::vec3& ma1 = first.MaxCorner();
const glm::vec3& ma2 = first.MaxCorner();
const glm::vec3& mi1 = second.MinCorner();
const glm::vec3& mi2 = second.MinCorner();
return (std::abs(ma1.x - ma2.x) < epsilon) &&
(std::abs(mi1.x - mi2.x) < epsilon) &&
(std::abs(ma1.z - ma2.z) < epsilon) &&
(std::abs(mi1.z - mi2.z) < epsilon) &&
(std::abs(ma1.y - ma2.y) < epsilon) &&
(std::abs(mi1.y - mi2.y) < epsilon);
}
bool attachAABBComponentFromModel(World* world, EntityID id)
{
if (!world->HasComponent(id, "Model")) {
return false;
}
ComponentWrapper model = world->GetComponent(id, "Model");
ComponentWrapper collision = world->AttachComponent(id, "AABB");
Model* modelRes = ResourceManager::Load<Model>(model["Resource"]);
if (modelRes == nullptr) {
return false;
}
glm::mat4 modelMatrix = modelRes->m_Matrix;
glm::vec3 mini = glm::vec3(INFINITY, INFINITY, INFINITY);
glm::vec3 maxi = glm::vec3(-INFINITY, -INFINITY, -INFINITY);
for (const auto& v : modelRes->m_Vertices) {
const auto& wPos = modelMatrix * glm::vec4(v.Position.x, v.Position.y, v.Position.z, 1);
maxi.x = std::max(wPos.x, maxi.x);
maxi.y = std::max(wPos.y, maxi.y);
maxi.z = std::max(wPos.z, maxi.z);
mini.x = std::min(wPos.x, mini.x);
mini.y = std::min(wPos.y, mini.y);
mini.z = std::min(wPos.z, mini.z);
}
collision["BoxCenter"] = 0.5f * (maxi + mini);
collision["BoxSize"] = maxi - mini;
return true;
}
bool GetEntityBox(World* world, ComponentWrapper& AABBComponent, AABB& outBox)
{
ComponentWrapper& cTrans = world->GetComponent(AABBComponent.EntityID, "Transform");
ComponentWrapper model = world->GetComponent(AABBComponent.EntityID, "Model");
Model* modelRes = ResourceManager::Load<Model>(model["Resource"]);
outBox.CreateFromCenter(AABBComponent["BoxCenter"], AABBComponent["BoxSize"]);
glm::vec3 mini = outBox.MinCorner();
glm::vec3 maxi = outBox.MaxCorner();
glm::mat4 modelMatrix = modelRes->m_Matrix *
glm::translate(glm::mat4(), (glm::vec3)cTrans["Position"]) *
glm::scale((glm::vec3)cTrans["Scale"]);
outBox = AABB(modelMatrix * glm::vec4(mini.x, mini.y, mini.z, 1),
modelMatrix * glm::vec4(maxi.x, maxi.y, maxi.z, 1));
return true;
}
bool GetEntityBox(World* world, EntityID entity, AABB& outBox, bool forceBoxFromModel)
{
if (!world->HasComponent(entity, "AABB")) {
if (forceBoxFromModel) {
if (!attachAABBComponentFromModel(world, entity))
return false;
} else {
return false;
}
}
ComponentWrapper& cBox = world->GetComponent(entity, "AABB");
return GetEntityBox(world, cBox, outBox);
}
}
+42
View File
@@ -0,0 +1,42 @@
#include "Collision/Collision.h"
#include "Collision/CollisionSystem.h"
#include "Core/AABB.h"
void CollisionSystem::UpdateComponent(World * world, ComponentWrapper & cAABB, double dt)
{
//TODO: Update CollisionSystem system after PlayerSystem.
//Right now, cAABB is a component attached to any entity that should be collideable.
AABB thisBox;
if (!Collision::GetEntityBox(world, cAABB, thisBox)) {
return;
}
//Press 'Z' to enable/disable collision.
if (zPress) {
return;
}
//Here, mover should be an object that moves, currently only players.
for (auto& mover : *world->GetComponents("Player")) {
if (cAABB.EntityID == mover.EntityID) {
continue;
}
AABB otherBox;
if (!Collision::GetEntityBox(world, mover.EntityID, otherBox)) {
continue;
}
glm::vec3 resolveTranslation;
if (Collision::AABBVsAABB(otherBox, thisBox, resolveTranslation)) {
ComponentWrapper& trans = world->GetComponent(mover.EntityID, "Transform");
//TODO: Special treatment if both are movers.
trans["Position"] = (glm::vec3)trans["Position"] + resolveTranslation;
}
}
}
bool CollisionSystem::OnKeyUp(const Events::KeyUp & event)
{
if (event.KeyCode == GLFW_KEY_Z) {
zPress = !zPress;
}
return false;
}
+81
View File
@@ -0,0 +1,81 @@
#include "Collision/TriggerSystem.h"
#include "Collision/Collision.h"
#include "Core/AABB.h"
#include "Rendering/Model.h"
void TriggerSystem::UpdateComponent(World* world, ComponentWrapper& trigger, double dt)
{
//Currently only players can trigger things.
auto players = world->GetComponents("Player");
if (players == nullptr) {
return;
}
EntityID tId = trigger.EntityID;
AABB triggerBox;
//The trigger *should* have a bounding box, or something, to test against so it can be triggered.
if (!Collision::GetEntityBox(world, tId, triggerBox, true)) {
return;
}
for (auto& pc : *players) {
EntityID pId = pc.EntityID;
AABB playerBox;
//The player can't trigger anything without an AABB.
if (!Collision::GetEntityBox(world, pId, playerBox, true)) {
continue;
}
if (!Collision::AABBVsAABB(triggerBox, playerBox)) {
//Entity is not touching the trigger,
//Throw event if it was previously.
if (throwLeaveIfWasInTrigger(m_EntitiesTouchingTrigger[tId], pId, tId)) {
continue;
}
//This only occurs if the entity was completely inside the trigger one frame,
//then completely outside the trigger, e.g. when dying and respawning.
throwLeaveIfWasInTrigger(m_EntitiesCompletelyInTrigger[tId], pId, tId);
} else {
//Entity is at least touching the trigger.
AABB completelyInsideBox;
completelyInsideBox.CreateFromCenter(triggerBox.Center(), triggerBox.Size() - 2.0f * playerBox.Size());
if (Collision::AABBVsAABB(completelyInsideBox, playerBox) &&
glm::all(glm::greaterThan(triggerBox.Size(), playerBox.Size()))) {
//Entity is completely inside the trigger.
//If it was only touching before, it is erased.
m_EntitiesTouchingTrigger[tId].erase(pId);
std::unordered_set<EntityID>& completeSet = m_EntitiesCompletelyInTrigger[tId];
if (completeSet.count(pId) == 0) {
//If it wasn't completely in the trigger, throw Enter and add to the set.
completeSet.insert(pId);
publish<Events::TriggerEnter>(pId, tId);
}
} else {
//Entity is only touching the trigger.
std::unordered_set<EntityID>& touchSet = m_EntitiesTouchingTrigger[tId];
std::unordered_set<EntityID>& completeSet = m_EntitiesCompletelyInTrigger[tId];
const auto& it = completeSet.find(pId);
//If it was completely inside before.
if (it != completeSet.end()) {
completeSet.erase(it);
touchSet.insert(pId);
//If it was completely outside before.
} else if (touchSet.count(pId) == 0) {
publish<Events::TriggerTouch>(pId, tId);
touchSet.insert(pId);
}
//Else, it was touching the trigger last frame too and nothing is done.
}
}
}
}
bool TriggerSystem::throwLeaveIfWasInTrigger(std::unordered_set<EntityID>& triggerSet, EntityID pId, EntityID tId)
{
const auto& it = triggerSet.find(pId);
if (it != triggerSet.end()) {
//If it was in the trigger, but not anymore, throw leaveEvent and erase from the set.
triggerSet.erase(it);
publish<Events::TriggerLeave>(pId, tId);
return true;
}
return false;
}
+15
View File
@@ -1,10 +1,25 @@
#include "Core/AABB.h"
#include "Common.h"
AABB::AABB(const glm::vec3& minPos, const glm::vec3& maxPos)
: m_MinCorner(minPos)
, m_MaxCorner(maxPos)
, m_Center(0.5f * (maxPos + minPos))
, m_HalfSize(0.5f * (maxPos - minPos))
{
DEBUG_IF(glm::any(glm::lessThan(m_MaxCorner, m_MinCorner))) {
LOG_WARNING("AABB maxCorner coordinates are not greater than minCorner");
m_MaxCorner.x = glm::max(m_MaxCorner.x, m_MinCorner.x);
m_MinCorner.x = glm::min(m_MaxCorner.x, m_MinCorner.x);
m_MaxCorner.y = glm::max(m_MaxCorner.y, m_MinCorner.y);
m_MinCorner.y = glm::min(m_MaxCorner.y, m_MinCorner.y);
m_MaxCorner.z = glm::max(m_MaxCorner.z, m_MinCorner.z);
m_MinCorner.z = glm::min(m_MaxCorner.z, m_MinCorner.z);
}
}
AABB::AABB(const glm::vec4& minPos, const glm::vec4& maxPos)
: AABB(glm::vec3(minPos), glm::vec3(maxPos))
{}
void AABB::CreateFromCenter(const glm::vec3& center, const glm::vec3& size)
-77
View File
@@ -1,77 +0,0 @@
#include "Core/Collision.h"
#include "Engine/GLM.h"
#include <algorithm>
namespace Collision
{
bool RayAABBIntr(const Ray& ray, const AABB& box)
{
glm::vec3 w = 75.0f * ray.Direction;
glm::vec3 v = glm::abs(w);
glm::vec3 c = ray.Origin - box.Center() + w;
glm::vec3 half = box.HalfSize();
if (abs(c.x) > v.x + half.x) {
return false;
}
if (abs(c.y) > v.y + half.y) {
return false;
}
if (abs(c.z) > v.z + half.z) {
return false;
}
if (abs(c.y*w.z - c.z*w.y) > half.y*v.z + half.z*v.y) {
return false;
}
if (abs(c.x*w.z - c.z*w.x) > half.x*v.z + half.z*v.x) {
return false;
}
return !(abs(c.x*w.y - c.y*w.x) > half.x*v.y + half.y*v.x);
}
bool RayVsAABB(const Ray& ray, const AABB& box)
{
float dummy;
return RayVsAABB(ray, box, dummy);
}
bool RayVsAABB(const Ray& ray, const AABB& box, float& outDistance)
{
glm::vec3 invdir = 1.0f / ray.Direction;
float t1 = (box.MinCorner().x - ray.Origin.x)*invdir.x;
float t2 = (box.MaxCorner().x - ray.Origin.x)*invdir.x;
float t3 = (box.MinCorner().y - ray.Origin.y)*invdir.y;
float t4 = (box.MaxCorner().y - ray.Origin.y)*invdir.y;
float t5 = (box.MinCorner().z - ray.Origin.z)*invdir.z;
float t6 = (box.MaxCorner().z - ray.Origin.z)*invdir.z;
float tmin = std::max(std::max(std::min(t1, t2), std::min(t3, t4)), std::min(t5, t6));
float tmax = std::min(std::min(std::max(t1, t2), std::max(t3, t4)), std::max(t5, t6));
if (tmax < 0 || tmin > tmax)
return false;
outDistance = (tmin > 0) ? tmin : tmax;
return true;
}
bool AABBVsAABB(const AABB& a, const AABB& b)
{
const glm::vec3& aCenter = a.Center();
const glm::vec3& bCenter = b.Center();
const glm::vec3& aHSize = a.HalfSize();
const glm::vec3& bHSize = b.HalfSize();
//Test will probably exit because of the X and Z axes more often, so test them first.
if (abs(aCenter[0] - bCenter[0]) > (aHSize[0] + bHSize[0])) {
return false;
}
if (abs(aCenter[2] - bCenter[2]) > (aHSize[2] + bHSize[2])) {
return false;
}
return (abs(aCenter[1] - bCenter[1]) <= (aHSize[1] + bHSize[1]));
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ bool ComponentPoolForwardIterator::operator!=(const ComponentPoolForwardIterator
return m_MemoryPoolIterator != other.m_MemoryPoolIterator;
}
ComponentPoolForwardIterator& ComponentPoolForwardIterator::operator++(int)
ComponentPoolForwardIterator ComponentPoolForwardIterator::operator++(int)
{
ComponentPoolForwardIterator copyIter(*this);
operator++();
+153 -87
View File
@@ -3,9 +3,7 @@
#include <bitset>
#include "Core/OctTree.h"
#include "Core/Collision.h"
#include "Core/World.h"
#include "Rendering/Camera.h"
#include "Collision/Collision.h"
namespace
{
@@ -28,11 +26,79 @@ OctTree::OctTree()
{}
OctTree::OctTree(const AABB& octTreeBounds, int subDivisions)
: m_Box(octTreeBounds)
: m_Root(new OctChild(octTreeBounds, subDivisions, m_StaticObjects, m_DynamicObjects))
, m_UpdatedOnce(false)
{}
OctTree::~OctTree()
{
delete m_Root;
}
void OctTree::AddDynamicObject(const AABB& box)
{
m_Root->AddDynamicObject(box);
m_DynamicObjects.push_back(box);
}
void OctTree::AddStaticObject(const AABB& box)
{
m_Root->AddStaticObject(box);
m_StaticObjects.push_back(box);
}
void OctTree::BoxesInSameRegion(const AABB& box, std::vector<AABB>& outBoxes)
{
falsifyObjectChecks();
m_Root->BoxesInSameRegion(box, outBoxes);
}
void OctTree::ClearObjects()
{
m_StaticObjects.clear();
m_DynamicObjects.clear();
m_Root->ClearObjects();
}
void OctTree::ClearDynamicObjects()
{
m_DynamicObjects.clear();
m_Root->ClearDynamicObjects();
}
bool OctTree::RayCollides(const Ray& ray, Output& data)
{
falsifyObjectChecks();
data.CollideDistance = -1;
return m_Root->RayCollides(ray, data);
}
bool OctTree::BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected)
{
falsifyObjectChecks();
return m_Root->BoxCollides(boxToTest, outBoxIntersected);
}
void OctTree::falsifyObjectChecks()
{
for (auto& obj : m_StaticObjects) {
obj.Checked = false;
}
for (auto& obj : m_DynamicObjects) {
obj.Checked = false;
}
}
OctTree::OctChild::OctChild(const AABB& octTreeBounds,
int subDivisions,
std::vector<ContainedObject>& staticObjects,
std::vector<ContainedObject>& dynamicObjects)
: m_Box(octTreeBounds)
, m_StaticObjectsRef(staticObjects)
, m_DynamicObjectsRef(dynamicObjects)
{
if (subDivisions == 0) {
for (OctTree*& c : m_Children) {
for (OctChild*& c : m_Children) {
c = nullptr;
}
} else {
@@ -68,14 +134,14 @@ OctTree::OctTree(const AABB& octTreeBounds, int subDivisions)
minPos.z = parentMin.z;
maxPos.z = parentCenter.z;
}
m_Children[i] = new OctTree(AABB(minPos, maxPos), subDivisions);
m_Children[i] = new OctChild(AABB(minPos, maxPos), subDivisions, m_StaticObjectsRef, m_DynamicObjectsRef);
}
}
}
OctTree::~OctTree()
OctTree::OctChild::~OctChild()
{
for (OctTree*& c : m_Children) {
for (OctChild*& c : m_Children) {
if (c != nullptr) {
delete c;
c = nullptr;
@@ -83,47 +149,7 @@ OctTree::~OctTree()
}
}
void OctTree::Update(float dt, World* world, Camera* cam)
{
//AABB aabb;
//for (ComponentWrapper& c : world->GetComponents("Collision")) {
// aabb.CreateFromCenter(c["BoxCenter"], c["BoxSize"]);
// AddStaticObject(aabb);
//}
//const glm::vec4 redCol = glm::vec4(1, 0.2f, 0, 1);
//const glm::vec4 greenCol = glm::vec4(0.1f, 1.0f, 0.25f, 1);
//const glm::vec3 boxSize = 0.1f*glm::vec3(1.0f, 1.0f, 1.0f);
//if (!m_UpdatedOnce) {
// m_BoxID = world->CreateEntity();
// ComponentWrapper transform = world->AttachComponent(m_BoxID, "Transform");
// transform["Scale"] = boxSize;
// ComponentWrapper model = world->AttachComponent(m_BoxID, "Model");
// model["Resource"] = "Models/Core/UnitBox.obj";
// m_UpdatedOnce = true;
//}
//AABB box;
//auto boxPos = cam->Position() + 1.2f*cam->Forward();
//box.CreateFromCenter(boxPos, boxSize);
//ComponentWrapper transform = world->GetComponent(m_BoxID, "Transform");
//transform["Position"] = boxPos;
//ComponentWrapper model = world->GetComponent(m_BoxID, "Model");
//if (BoxCollides(box, AABB())) {
////if (Collision::AABBVsAABB(box, aabb)) {
// cam->SetPosition(m_PrevPos);
// cam->SetOrientation(m_PrevOri);
// model["Color"] = greenCol;
//} else {
// model["Color"] = redCol;
//}
//m_PrevPos = cam->Position();
//m_PrevOri = cam->Orientation();
//ClearObjects();
}
bool OctTree::BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected) const
bool OctTree::OctChild::BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected) const
{
if (hasChildren()) {
for (int i : childIndicesContainingBox(boxToTest)) {
@@ -131,23 +157,32 @@ bool OctTree::BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected) const
return true;
}
} else {
std::vector<std::vector<AABB>> objVectors = {
m_StaticObjects,
m_DynamicObjects
};
for (const auto& objVector : objVectors) {
for (const auto& obj : objVector) {
if (Collision::AABBVsAABB(boxToTest, obj)) {
outBoxIntersected = obj;
for (int i : m_StaticObjIndices) {
if (!m_StaticObjectsRef[i].Checked) {
const AABB& objBox = m_StaticObjectsRef[i].Box;
if (Collision::AABBVsAABB(boxToTest, objBox)) {
outBoxIntersected = objBox;
return true;
}
m_StaticObjectsRef[i].Checked = true;
}
}
for (int i : m_DynamicObjIndices) {
if (!m_DynamicObjectsRef[i].Checked) {
const AABB& objBox = m_DynamicObjectsRef[i].Box;
if (!Collision::IsSameBoxProbably(boxToTest, objBox) &&
Collision::AABBVsAABB(boxToTest, objBox)) {
outBoxIntersected = objBox;
return true;
}
m_DynamicObjectsRef[i].Checked = true;
}
}
}
return false;
}
bool OctTree::RayCollides(const Ray& ray, Output& data) const
bool OctTree::OctChild::RayCollides(const Ray& ray, Output& data) const
{
//If the node AABB is missed, everything it contains is missed.
if (Collision::RayAABBIntr(ray, m_Box)) {
@@ -157,7 +192,7 @@ bool OctTree::RayCollides(const Ray& ray, Output& data) const
std::vector<ChildInfo> childInfos;
childInfos.reserve(8);
for (int i = 0; i < 8; ++i) {
childInfos.push_back({ i, glm::distance(ray.Origin, m_Children[i]->m_Box.Center()) });
childInfos.push_back({ i, glm::distance(ray.Origin(), m_Children[i]->m_Box.Center()) });
}
std::sort(childInfos.begin(), childInfos.end(), isFirstLower);
//Loop through the children, starting with the one closest to the ray origin. I.e the first to be hit.
@@ -170,18 +205,25 @@ bool OctTree::RayCollides(const Ray& ray, Output& data) const
//Check against boxes in the node.
float minDist = INFINITY;
bool intersected = false;
std::vector<std::vector<AABB>> objVectors = {
m_StaticObjects,
m_DynamicObjects
};
for (const auto& objVector : objVectors) {
for (const auto& obj : objVector) {
float dist;
if (Collision::RayVsAABB(ray, obj, dist)) {
minDist = std::min(dist, minDist);
intersected = true;
}
for (int i : m_StaticObjIndices) {
float dist;
//If we haven't tested against this object before, and the ray hits.
if (!m_StaticObjectsRef[i].Checked &&
Collision::RayVsAABB(ray, m_StaticObjectsRef[i].Box, dist)) {
minDist = std::min(dist, minDist);
intersected = true;
}
m_StaticObjectsRef[i].Checked = true;
}
for (int i : m_DynamicObjIndices) {
float dist;
//If we haven't tested against this object before, and the ray hits.
if (!m_DynamicObjectsRef[i].Checked &&
Collision::RayVsAABB(ray, m_DynamicObjectsRef[i].Box, dist)) {
minDist = std::min(dist, minDist);
intersected = true;
}
m_DynamicObjectsRef[i].Checked = true;
}
data.CollideDistance = minDist;
@@ -192,60 +234,84 @@ bool OctTree::RayCollides(const Ray& ray, Output& data) const
}
void OctTree::AddDynamicObject(const AABB& box)
void OctTree::OctChild::AddDynamicObject(const AABB& box)
{
if (hasChildren()) {
for (auto i : childIndicesContainingBox(box)) {
m_Children[i]->AddDynamicObject(box);
}
} else {
m_DynamicObjects.push_back(box);
//Since it hasn't been added yet to the real object list, the index is after the last =size.
m_DynamicObjIndices.push_back((int)m_DynamicObjectsRef.size());
}
}
void OctTree::AddStaticObject(const AABB& box)
void OctTree::OctChild::AddStaticObject(const AABB& box)
{
if (hasChildren()) {
for (auto i : childIndicesContainingBox(box)) {
m_Children[i]->AddStaticObject(box);
}
} else {
m_StaticObjects.push_back(box);
//Since it hasn't been added yet to the real object list, the index is after the last =size.
m_StaticObjIndices.push_back((int)m_StaticObjectsRef.size());
}
}
void OctTree::BoxesInSameRegion(const AABB& box, std::vector<AABB>& outBoxes) const
void OctTree::OctChild::BoxesInSameRegion(const AABB& box, std::vector<AABB>& outBoxes) const
{
if (hasChildren()) {
for (auto i : childIndicesContainingBox(box)) {
m_Children[i]->BoxesInSameRegion(box, outBoxes);
}
} else {
outBoxes.insert(outBoxes.end(), m_StaticObjects.begin(), m_StaticObjects.end());
outBoxes.insert(outBoxes.end(), m_DynamicObjects.begin(), m_DynamicObjects.end());
size_t startIndex = outBoxes.size();
int numDuplicates = 0;
outBoxes.resize(outBoxes.size() + m_StaticObjIndices.size() + m_DynamicObjIndices.size());
for (size_t i = 0; i < m_StaticObjIndices.size(); ++i){
ContainedObject& obj = m_StaticObjectsRef[m_StaticObjIndices[i]];
if (obj.Checked) {
++numDuplicates;
} else {
obj.Checked = true;
outBoxes[startIndex + i - numDuplicates] = obj.Box;
}
}
for (size_t i = 0; i < m_DynamicObjIndices.size(); ++i) {
ContainedObject& obj = m_DynamicObjectsRef[m_DynamicObjIndices[i]];
if (obj.Checked) {
++numDuplicates;
} else {
obj.Checked = true;
outBoxes[startIndex + i - numDuplicates] = obj.Box;
}
}
for (size_t i = 0; i < numDuplicates; ++i) {
outBoxes.pop_back();
}
}
}
void OctTree::ClearObjects()
void OctTree::OctChild::ClearObjects()
{
if (hasChildren()) {
for (OctTree*& c : m_Children) {
for (OctChild*& c : m_Children) {
c->ClearObjects();
}
} else {
m_DynamicObjects.clear();
m_StaticObjects.clear();
m_DynamicObjIndices.clear();
m_StaticObjIndices.clear();
}
}
void OctTree::ClearDynamicObjects()
void OctTree::OctChild::ClearDynamicObjects()
{
if (hasChildren()) {
for (OctTree*& c : m_Children) {
for (OctChild*& c : m_Children) {
c->ClearObjects();
}
} else {
m_DynamicObjects.clear();
m_DynamicObjIndices.clear();
}
}
@@ -261,13 +327,13 @@ void OctTree::ClearDynamicObjects()
// x : - - - - + + + +
// y : - - + + - - + +
// z : - + - + - + - +
int OctTree::childIndexContainingPoint(const glm::vec3& point) const
int OctTree::OctChild::childIndexContainingPoint(const glm::vec3& point) const
{
const glm::vec3& c = m_Box.Center();
return (1 << 2) * (point.x >= c.x) | (1 << 1) * (point.y >= c.y) | (point.z >= c.z);
}
std::vector<int> OctTree::childIndicesContainingBox(const AABB& box) const
std::vector<int> OctTree::OctChild::childIndicesContainingBox(const AABB& box) const
{
int minInd = childIndexContainingPoint(box.MinCorner());
int maxInd = childIndexContainingPoint(box.MaxCorner());
@@ -305,7 +371,7 @@ std::vector<int> OctTree::childIndicesContainingBox(const AABB& box) const
}
}
inline bool OctTree::hasChildren() const
inline bool OctTree::OctChild::hasChildren() const
{
return m_Children[0] != nullptr;
}
+4
View File
@@ -1,4 +1,6 @@
#include "Game.h"
#include "Collision/TriggerSystem.h"
#include "Collision/CollisionSystem.h"
Game::Game(int argc, char* argv[])
{
@@ -53,6 +55,8 @@ Game::Game(int argc, char* argv[])
m_SystemPipeline->AddSystem<RaptorCopterSystem>();
m_SystemPipeline->AddSystem<PlayerSystem>();
m_SystemPipeline->AddSystem<EditorSystem>(m_Renderer);
m_SystemPipeline->AddSystem<CollisionSystem>();
m_SystemPipeline->AddSystem<TriggerSystem>();
m_LastTime = glfwGetTime();
+19 -1
View File
@@ -16,7 +16,7 @@ void PlayerSystem::UpdateComponent(World * world, ComponentWrapper & player, dou
} else {
m_Direction.x = 0;
}
m_EventBroker->Process<PlayerSystem>();
ComponentWrapper& transform = world->GetComponent(player.EntityID, "Transform");
(glm::vec3&)player["Velocity"] = m_Speed * float(dt) * m_Direction;
(glm::vec3&)transform["Position"] += (glm::vec3)player["Velocity"];
@@ -55,3 +55,21 @@ bool PlayerSystem::OnKeyUp(const Events::KeyUp & event)
}
return false;
}
bool PlayerSystem::OnTouch(const Events::TriggerTouch &event)
{
LOG_INFO("Player entity %i touched widget (entity %i).", event.Entity, event.Trigger);
return false;
}
bool PlayerSystem::OnEnter(const Events::TriggerEnter &event)
{
LOG_INFO("Player entity %i entered widget (entity %i).", event.Entity, event.Trigger);
return false;
}
bool PlayerSystem::OnLeave(const Events::TriggerLeave &event)
{
LOG_INFO("Player entity %i left widget (entity %i).", event.Entity, event.Trigger);
return false;
}
+1 -1
View File
@@ -12,7 +12,7 @@ include_directories(
)
file(GLOB SOURCE_FILES
"${INCLUDE_PATH}/Tests/*.h"
"*.h"
"*.cpp"
)
+131 -17
View File
@@ -3,11 +3,19 @@
#include <boost/test/execution_monitor.hpp>
using boost::unit_test_framework::test_suite;
using boost::unit_test_framework::test_case;
#include <Engine\Core\Collision.h>
#include "Engine/Collision/Collision.h"
#include "Engine/Core/AABB.h"
#include "Engine/Core/Ray.h"
#include <stdlib.h>//srand
#include "Engine/Core/OctTree.h"
//vs model
#include <sstream>
#include <string>
//ray vs model
#include "Engine\Core\ResourceManager.h"
#include "Engine\Rendering\Model.h"
#include "Engine\Core\Ray.h"
//vs memleaks
//#define _CRTDBG_MAP_ALLOC
@@ -16,6 +24,20 @@ using boost::unit_test_framework::test_case;
//#define DEBUG_CLIENTBLOCK new( _CLIENT_BLOCK, __FILE__, __LINE__)
//#define new DEBUG_CLIENTBLOCK
void RayTest(std::string fileName) {
//simple box test
Ray ray(glm::vec3(-50, 0, 0), glm::vec3(1, 0, 0));
//using a rawmodel here, else we have to init the renderingsystem
ResourceManager::RegisterType<RawModel>("RawModel");
auto unitBox = ResourceManager::Load<RawModel>(fileName);
BOOST_REQUIRE(unitBox != nullptr);
bool hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices);
BOOST_CHECK(hit);
ray.SetDirection(glm::vec3(-1, 0, 0));
hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices);
BOOST_CHECK(!hit);
}
BOOST_AUTO_TEST_SUITE(collisionTests)
BOOST_AUTO_TEST_CASE(collisionTest)
@@ -25,7 +47,6 @@ BOOST_AUTO_TEST_CASE(collisionTest)
//fixed seed
srand(2);
Ray ray;
AABB someAABB;
glm::vec3 minPos;
glm::vec3 maxPos;
@@ -33,12 +54,10 @@ BOOST_AUTO_TEST_CASE(collisionTest)
int test = 0;
for (size_t i = 0; i < 10; i++)
{
ray.Origin.x = rand() % 100;
ray.Origin.y = rand() % 100;
ray.Origin.z = rand() % 100;
ray.Direction.x = rand() % 100;
ray.Direction.y = rand() % 100;
ray.Direction.z = rand() % 100;
Ray ray(
glm::vec3(rand() % 100, rand() % 100, rand() % 100),
glm::vec3(rand() % 100, rand() % 100, rand() % 100)
);
minPos.x = rand() % 100;
minPos.y = rand() % 100;
minPos.z = rand() % 100;
@@ -59,7 +78,6 @@ BOOST_AUTO_TEST_CASE(collisionTest2)
{
//fixed seed
srand(2);
Ray ray;
AABB someAABB;
glm::vec3 minPos;
glm::vec3 maxPos;
@@ -67,12 +85,10 @@ BOOST_AUTO_TEST_CASE(collisionTest2)
int test = 0;
for (size_t i = 0; i < 1000000; i++)
{
ray.Origin.x = rand() % 100;
ray.Origin.y = rand() % 100;
ray.Origin.z = rand() % 100;
ray.Direction.x = rand() % 100;
ray.Direction.y = rand() % 100;
ray.Direction.z = rand() % 100;
Ray ray(
glm::vec3(rand() % 100, rand() % 100, rand() % 100),
glm::vec3(rand() % 100, rand() % 100, rand() % 100)
);
minPos.x = rand() % 100;
minPos.y = rand() % 100;
minPos.z = rand() % 100;
@@ -87,6 +103,104 @@ BOOST_AUTO_TEST_CASE(collisionTest2)
BOOST_CHECK(test >= 0);
}
BOOST_AUTO_TEST_CASE(rayVsModelTest)
{
//simple box test
RayTest("Models/Core/UnitCube.obj");
}
BOOST_AUTO_TEST_CASE(rayVsModelTest2)
{
//advanced test, this will check so rayVSAABB and rayVsModel(with boxmodel) gives the same result (hit/miss)
//testing with different seeds
// srand(7676762);
// srand(7676462);
// srand(7462);
srand(72);
AABB someAABB;
glm::vec3 minPos;
glm::vec3 maxPos;
bool z;
int test = 0;
//min/max is the same as the rawmodels boundaries ofcourse
minPos = glm::vec3(-0.5f, -0.5f, -0.5f);
maxPos = glm::vec3(0.5f, 0.5f, 0.5f);
someAABB = AABB(minPos, maxPos);
//using a rawmodel here, else we have to init the renderingsystem
ResourceManager::RegisterType<RawModel>("RawModel");
auto unitBox = ResourceManager::Load<RawModel>("Models/Core/UnitCube.obj");
BOOST_CHECK(unitBox != nullptr);
for (size_t i = 0; i < 1000000; i++)
{
Ray ray(
glm::vec3(-2, 0, 0),
glm::vec3(rand() % 100, rand() % 100, rand() % 100)
);
//if we normalize the ray.direction when its 0,0,0 then we get nan,nan,nan - thus we have this check to prevent that
if (glm::any(glm::isnan(ray.Direction())))
continue;
z = Collision::RayVsAABB(ray, someAABB);
if (z) {
//hit
bool hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices);
if (!hit) {
//if rayvsaabb hit but rayvvmodel didnt hit, we get to here
glm::vec3 outtttttttt;
hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices, outtttttttt);
hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices);
}
else {
hit = hit;
}
BOOST_CHECK(hit);
}
////breakpoint test
//if (!z) {
// z = z;
//}
//
bool hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices);
////breakpoint test
//if (!hit) {
// hit = hit;
//}
if (hit) {
//hit
z = Collision::RayVsAABB(ray, someAABB);
if (!z) {
//if rayvsmodel hit but rayvsaabb didnt hit then we get to here
z = Collision::RayVsAABB(ray, someAABB);
glm::vec3 outtttttttt;
hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices, outtttttttt);
hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices);
}
else {
z = z;
}
BOOST_CHECK(hit);
}
}
}
BOOST_AUTO_TEST_CASE(rayVsModelTest3)
{
//simple test
RayTest("Models/Core/UnitSphere.obj");
}
BOOST_AUTO_TEST_CASE(rayVsModelTest4)
{
//simple test
RayTest("Models/Core/UnitCylinder.obj");
}
BOOST_AUTO_TEST_CASE(rayVsModelTest5)
{
//simple test
RayTest("Models/Core/UnitRaptor.obj");
}
BOOST_AUTO_TEST_CASE(octTest)
{
glm::vec3 mini = glm::vec3(-1, -1, -1);
@@ -95,10 +209,10 @@ BOOST_AUTO_TEST_CASE(octTest)
tree.AddDynamicObject(AABB(mini, -0.9f*maxi));
OctTree::Output data;
glm::vec3 origin = 3.0f * mini;
bool rayIntersected = tree.RayCollides({ origin , glm::normalize(mini - origin) }, data);
bool rayIntersected = tree.RayCollides(Ray(origin , mini - origin), data);
BOOST_CHECK(rayIntersected);
tree.ClearDynamicObjects();
rayIntersected = tree.RayCollides({ origin , glm::normalize(mini - origin) }, data);
rayIntersected = tree.RayCollides(Ray(origin, mini - origin), data);
BOOST_CHECK(!rayIntersected);
}
+147 -35
View File
@@ -2,47 +2,159 @@
using boost::unit_test_framework::test_suite;
using boost::unit_test_framework::test_case;
#include <stdlib.h>//srand
#include "OctTreeTestGameClass.h"
//HACK! Needed for white box testing
//else we would have to "open up" the octTree class more with get/sets, public methods, etc. which is not good encapsulation-wise
//friend class and refactoringIntoNewClass is some extra work and needs to be updated when the original class is updated, and can contain bugs that
//isnt in the original class
//Reflection-inspection seems to be only available for C#
//http://stackoverflow.com/questions/6778496/how-to-do-unit-testing-on-private-members-and-methods-of-c-classes
//http://stackoverflow.com/questions/3676664/unit-testing-of-private-methods
#define private public
#include <Engine\Core\OctTree.h>
BOOST_AUTO_TEST_SUITE(octTreeTests)
#include "Engine/Core/OctTree.h"
#include "Engine/Core/Ray.h"
#include "OldOctTree.h"
BOOST_AUTO_TEST_CASE(octTreeTest)
BOOST_AUTO_TEST_SUITE(octTreeTestsW)
BOOST_AUTO_TEST_CASE(octSameRegionTest)
{
//white box testing
//http://softwaretestingfundamentals.com/differences-between-black-box-testing-and-white-box-testing/
//http://technologyconversations.com/2013/12/11/black-box-vs-white-box-testing/
//simple AABB constructor check
auto minCorner = glm::vec3(0.0f, 0.0f, 0.0f);
auto maxCorner = glm::vec3(1.0f, 1.0f, 1.0f);
auto someAABB = AABB(minCorner,maxCorner);
BOOST_CHECK(someAABB.MinCorner() == minCorner);
BOOST_CHECK(someAABB.MaxCorner() == maxCorner);
BOOST_CHECK(someAABB.Center() == 0.5f * (minCorner + maxCorner));
//simple OctTree constructor check
OctTree someOctTree(someAABB, 5);
BOOST_CHECK(someOctTree.m_Children[0] != nullptr);
//simple destructor check in the end, just look for memleaks, then it didnt clear the AABB structure
glm::vec3 mini = glm::vec3(-1, -1, -1);
glm::vec3 maxi = glm::vec3(1, 1, 1);
OctTree tree(AABB(mini, maxi), 2);
AABB firstQuadrant(mini, 0.8f*mini);
tree.AddStaticObject(firstQuadrant);
AABB testBox(0.9f*mini, 0.8f*mini);
std::vector<AABB> region;
tree.BoxesInSameRegion(testBox, region);
BOOST_REQUIRE(region.size() == 1);
AABB& box = region[0];
BOOST_CHECK_CLOSE_FRACTION(box.Center().x, firstQuadrant.Center().x, 0.00001f);
BOOST_CHECK_CLOSE_FRACTION(box.Center().y, firstQuadrant.Center().y, 0.00001f);
BOOST_CHECK_CLOSE_FRACTION(box.Center().z, firstQuadrant.Center().z, 0.00001f);
BOOST_CHECK_CLOSE_FRACTION(box.HalfSize().x, firstQuadrant.HalfSize().x, 0.00001f);
BOOST_CHECK_CLOSE_FRACTION(box.HalfSize().y, firstQuadrant.HalfSize().y, 0.00001f);
BOOST_CHECK_CLOSE_FRACTION(box.HalfSize().z, firstQuadrant.HalfSize().z, 0.00001f);
}
BOOST_AUTO_TEST_CASE(octTreeTest2)
const int LEVEL_BOUNDS = 500;
const int MAXSIZE = 50;
const int BOXES = 400;
const int NUM_DYNAMICS = 0;
const int NUM_STATICS = BOXES - NUM_DYNAMICS;
const int SEED = 6548;
const int TEST_FRAMES = 300;
const int NUM_FUNCTION_LOOPS = 25;
const int TESTS = 0; //10
template<typename Tree>
void RegionTest(Tree& tree)
{
//octtree ritningen osv
//Game game(0, nullptr);
//while (game.Running()) {
// game.Tick();
//}
AABB aabb;
aabb.CreateFromCenter(glm::vec3(rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS),
glm::vec3(rand() % MAXSIZE, rand() % MAXSIZE, rand() % MAXSIZE));
std::vector<AABB> outVec;
tree.BoxesInSameRegion(aabb, outVec);
}
template<typename Tree>
void RayTest(Tree& tree)
{
Tree::Output data;
glm::vec3 rayStart = glm::vec3(rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS);
glm::vec3 rayEnd = glm::vec3(rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS);
tree.RayCollides({ rayStart , glm::normalize(rayEnd - rayStart) }, data);
}
template<typename Tree>
void BoxTest(Tree& tree)
{
AABB outBox;
AABB aabb;
aabb.CreateFromCenter(glm::vec3(rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS),
glm::vec3(rand() % MAXSIZE, rand() % MAXSIZE, rand() % MAXSIZE));
tree.BoxCollides(aabb, outBox);
}
template<typename Tree>
void NopTest(Tree& tree)
{
}
template<typename Tree, typename TestFunction>
void TestLoop(TestFunction xTest)
{
srand(SEED);
glm::vec3 mini = glm::vec3(0, 0, 0);
glm::vec3 maxi = glm::vec3(LEVEL_BOUNDS, LEVEL_BOUNDS, LEVEL_BOUNDS);
Tree tree(AABB(mini, maxi), 3);
AABB aabb;
glm::vec3 center;
glm::vec3 size;
for (int t = 0; t < TESTS; ++t) {
for (int i = 0; i < NUM_STATICS; ++i) {
center = glm::vec3(rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS);
size = glm::vec3(rand() % MAXSIZE, rand() % MAXSIZE, rand() % MAXSIZE);
aabb.CreateFromCenter(center, size);
tree.AddStaticObject(aabb);
}
for (int fr = 0; fr < TEST_FRAMES; ++fr) {
for (int i = 0; i < NUM_DYNAMICS; ++i) {
center = glm::vec3(rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS);
size = glm::vec3(rand() % MAXSIZE, rand() % MAXSIZE, rand() % MAXSIZE);
aabb.CreateFromCenter(center, size);
tree.AddDynamicObject(aabb);
}
for (int fl = 0; fl < NUM_FUNCTION_LOOPS; ++fl) {
xTest(tree);
}
tree.ClearDynamicObjects();
}
tree.ClearObjects();
}
}
BOOST_AUTO_TEST_CASE(octRegionPerfTestWithDuplicates)
{
TestLoop<Old::OctTree>(RegionTest<Old::OctTree>);
BOOST_CHECK(true);
}
BOOST_AUTO_TEST_CASE(octRegionPerfTestNoDuplicates)
{
TestLoop<OctTree>(RegionTest<OctTree>);
BOOST_CHECK(true);
}
BOOST_AUTO_TEST_CASE(octBoxPerfTestWithDuplicates)
{
TestLoop<Old::OctTree>(BoxTest<Old::OctTree>);
BOOST_CHECK(true);
}
BOOST_AUTO_TEST_CASE(octBoxPerfTestNoDuplicates)
{
TestLoop<OctTree>(BoxTest<OctTree>);
BOOST_CHECK(true);
}
BOOST_AUTO_TEST_CASE(octRayPerfTestWithDuplicates)
{
TestLoop<Old::OctTree>(RayTest<Old::OctTree>);
BOOST_CHECK(true);
}
BOOST_AUTO_TEST_CASE(octRayPerfTestNoDuplicates)
{
TestLoop<OctTree>(RayTest<OctTree>);
BOOST_CHECK(true);
}
BOOST_AUTO_TEST_CASE(octNopPerfTestWithDuplicates)
{
TestLoop<Old::OctTree>(NopTest<Old::OctTree>);
BOOST_CHECK(true);
}
BOOST_AUTO_TEST_CASE(octNopPerfTestNoDuplicates)
{
TestLoop<OctTree>(NopTest<OctTree>);
BOOST_CHECK(true);
}
BOOST_AUTO_TEST_SUITE_END()
+53
View File
@@ -0,0 +1,53 @@
#include <boost/test/unit_test.hpp>
using boost::unit_test_framework::test_suite;
using boost::unit_test_framework::test_case;
#include <stdlib.h>//srand
//#define private public//HACK! Needed for white box testing
//#include "Engine/Core/OctTree.h"
//#include "OldOctTree.h"
//friend class and refactoringIntoNewClass is some extra work and needs to be updated when the original class is updated, and can contain bugs that
//isnt in the original class
//Reflection-inspection seems to be only available for C#
//http://stackoverflow.com/questions/6778496/how-to-do-unit-testing-on-private-members-and-methods-of-c-classes
//http://stackoverflow.com/questions/3676664/unit-testing-of-private-methods
#include "OctTreeTestGameClass.h"
#define private public//HACK! Needed for white box testing
#include <Engine\Core\OctTree.h>
//else we would have to "open up" the octTree class more with get/sets, public methods, etc. which is not good encapsulation-wise
BOOST_AUTO_TEST_SUITE(octTreeTestsA)
BOOST_AUTO_TEST_CASE(octTreeTest)
{
//white box testing
//http://softwaretestingfundamentals.com/differences-between-black-box-testing-and-white-box-testing/
//http://technologyconversations.com/2013/12/11/black-box-vs-white-box-testing/
//simple AABB constructor check
auto minCorner = glm::vec3(0.0f, 0.0f, 0.0f);
auto maxCorner = glm::vec3(1.0f, 1.0f, 1.0f);
auto someAABB = AABB(minCorner, maxCorner);
BOOST_CHECK(someAABB.MinCorner() == minCorner);
BOOST_CHECK(someAABB.MaxCorner() == maxCorner);
BOOST_CHECK(someAABB.Center() == 0.5f * (minCorner + maxCorner));
//simple OctTree constructor check
//OctTree someOctTree(someAABB, 5);
//BOOST_CHECK(someOctTree.m_Children[0] != nullptr);
//simple destructor check in the end, just look for memleaks, then it didnt clear the AABB structure
}
BOOST_AUTO_TEST_CASE(octTreeTest2)
{
//octtree ritningen osv
Game game(0, nullptr);
while (game.Running()) {
game.Tick();
}
}
BOOST_AUTO_TEST_SUITE_END()
+5 -5
View File
@@ -57,7 +57,7 @@ void Game::Tick()
m_Renderer->Update(dt);
m_EventBroker->Swap();
#define TEST2
#define TEST1
//this draws the octTree and you can set the cube inside it and see what boxes in the tree that it belongs to
#ifdef TEST1
if (!m_UpdatedOnce) {
@@ -81,13 +81,13 @@ void Game::Tick()
//check all children again in the tree if they have a box in them or not, and colormark them if they do
//contentboxarna får man ut - inte childboxarna!
std::vector<int> boxIndex;
boxIndex = m_World->someOctTree.childIndicesContainingBox(boxi);
boxIndex = m_World->someOctTree.m_Root->childIndicesContainingBox(boxi);
for (auto& oneLinkedObject : m_World->linkOM)
{
ComponentWrapper model = m_World->GetComponent(oneLinkedObject.entId, "Model");
model["Color"] = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);
if (oneLinkedObject.child->m_DynamicObjects.size() != 0) {
if (oneLinkedObject.child->m_DynamicObjIndices.size() != 0) {
model["Color"] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
}
@@ -95,7 +95,7 @@ void Game::Tick()
//REQUIRED: childIndicesContainingBox must be public to test this!
for each (auto someBoxIndex in boxIndex)
{
glm::vec3 pos = m_World->someOctTree.m_Children[someBoxIndex]->m_Box.Center();
glm::vec3 pos = m_World->someOctTree.m_Root->m_Children[someBoxIndex]->m_Box.Center();
if (abs(pos.x - oneLinkedObject.posxyz.x) < 0.005f &&
abs(pos.y - oneLinkedObject.posxyz.y) < 0.005f &&
abs(pos.z - oneLinkedObject.posxyz.z) < 0.005f) {
@@ -122,7 +122,7 @@ void Game::Tick()
aabb.CreateFromCenter(glm::vec3(0, 2.0f, 0.0f), glm::vec3(1.0f, 1.0f, 1.0f));
if (m_UpdatedOnce) {
auto test = someOctTree.childIndicesContainingBox(aabb);
//auto test = someOctTree.childIndicesContainingBox(aabb);
std::vector<AABB> test2;
someOctTree.BoxesInSameRegion(aabb, test2);
}
+1 -1
View File
@@ -3,7 +3,7 @@
#include <boost/test/execution_monitor.hpp>
using boost::unit_test_framework::test_suite;
using boost::unit_test_framework::test_case;
#include <Engine\Core\Collision.h>
#include "Engine/Collision/Collision.h"
#include "Engine/Core/AABB.h"
#include "Engine/Core/Ray.h"
#include <stdlib.h>//srand
+328
View File
@@ -0,0 +1,328 @@
#include <vector>
#include <algorithm>
#include <bitset>
#include "OldOctTree.h"
#include "Collision/Collision.h"
#include "Core/World.h"
#include "Rendering/Camera.h"
namespace Old
{
namespace
{
//To be able to sort nodes based on distance to ray origin.
struct ChildInfo
{
int Index;
float Distance;
};
bool isFirstLower(const ChildInfo& first, const ChildInfo& second)
{
return first.Distance < second.Distance;
}
bool isSameBoxProbably(const AABB& first, const AABB& second)
{
const float EPS = 0.0001f;
const auto& ma = first.MaxCorner();
const auto& mi = first.MinCorner();
return (std::abs(ma.x - mi.x) < EPS) &&
(std::abs(ma.z - mi.z) < EPS) &&
(std::abs(ma.y - mi.y) < EPS);
}
}
OctTree::OctTree()
: OctTree(AABB(), 0)
{}
OctTree::OctTree(const AABB& octTreeBounds, int subDivisions)
: m_Box(octTreeBounds)
, m_UpdatedOnce(false)
{
if (subDivisions == 0) {
for (OctTree*& c : m_Children) {
c = nullptr;
}
} else {
--subDivisions;
for (int i = 0; i < 8; ++i) {
glm::vec3 minPos, maxPos;
const glm::vec3& parentMin = m_Box.MinCorner();
const glm::vec3& parentMax = m_Box.MaxCorner();
const glm::vec3& parentCenter = m_Box.Center();
std::bitset<3> bits(i);
//If child is 4,5,6,7.
if (bits.test(2)) {
minPos.x = parentCenter.x;
maxPos.x = parentMax.x;
} else {
minPos.x = parentMin.x;
maxPos.x = parentCenter.x;
}
//If child is 2,3,6,7
if (bits.test(1)) {
minPos.y = parentCenter.y;
maxPos.y = parentMax.y;
} else {
minPos.y = parentMin.y;
maxPos.y = parentCenter.y;
}
//If child is 1,3,5,7
if (bits.test(0)) {
minPos.z = parentCenter.z;
maxPos.z = parentMax.z;
} else {
minPos.z = parentMin.z;
maxPos.z = parentCenter.z;
}
m_Children[i] = new OctTree(AABB(minPos, maxPos), subDivisions);
}
}
}
OctTree::~OctTree()
{
for (OctTree*& c : m_Children) {
if (c != nullptr) {
delete c;
c = nullptr;
}
}
}
void OctTree::Update(float dt, World* world, Camera* cam)
{
AABB aabb;
for (ComponentWrapper& c : *world->GetComponents("Collision")) {
aabb.CreateFromCenter(c["BoxCenter"], c["BoxSize"]);
AddStaticObject(aabb);
}
const glm::vec4 redCol = glm::vec4(1, 0.2f, 0, 1);
const glm::vec4 greenCol = glm::vec4(0.1f, 1.0f, 0.25f, 1);
const glm::vec3 boxSize = 0.1f*glm::vec3(1.0f, 1.0f, 1.0f);
if (!m_UpdatedOnce) {
m_BoxID = world->CreateEntity();
ComponentWrapper transform = world->AttachComponent(m_BoxID, "Transform");
transform["Scale"] = boxSize;
ComponentWrapper model = world->AttachComponent(m_BoxID, "Model");
model["Resource"] = "Models/Core/UnitBox.obj";
m_UpdatedOnce = true;
}
AABB box;
auto boxPos = cam->Position() + 1.2f*cam->Forward();
box.CreateFromCenter(boxPos, boxSize);
ComponentWrapper transform = world->GetComponent(m_BoxID, "Transform");
transform["Position"] = boxPos;
ComponentWrapper model = world->GetComponent(m_BoxID, "Model");
//if (BoxCollides(box, AABB())) {
if (Collision::AABBVsAABB(box, aabb)) {
cam->SetPosition(m_PrevPos);
cam->SetOrientation(m_PrevOri);
model["Color"] = greenCol;
} else {
model["Color"] = redCol;
}
m_PrevPos = cam->Position();
m_PrevOri = cam->Orientation();
ClearObjects();
}
bool OctTree::BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected) const
{
if (hasChildren()) {
for (int i : childIndicesContainingBox(boxToTest)) {
if (m_Children[i]->BoxCollides(boxToTest, outBoxIntersected))
return true;
}
} else {
for (const auto& obj : m_StaticObjects) {
if (Collision::AABBVsAABB(boxToTest, obj)) {
outBoxIntersected = obj;
return true;
}
}
for (const auto& obj : m_DynamicObjects) {
//If there is a collision and it is not testing against itself.
if (!isSameBoxProbably(boxToTest, obj) &&
Collision::AABBVsAABB(boxToTest, obj)) {
outBoxIntersected = obj;
return true;
}
}
}
return false;
}
bool OctTree::RayCollides(const Ray& ray, Output& data) const
{
//If the node AABB is missed, everything it contains is missed.
if (Collision::RayAABBIntr(ray, m_Box)) {
//If the ray shoots the tree, and it is a parent to 8 children :o
if (hasChildren()) {
//Sort children according to their distance from the ray origin.
std::vector<ChildInfo> childInfos;
childInfos.reserve(8);
for (int i = 0; i < 8; ++i) {
childInfos.push_back({ i, glm::distance(ray.Origin(), m_Children[i]->m_Box.Center()) });
}
std::sort(childInfos.begin(), childInfos.end(), isFirstLower);
//Loop through the children, starting with the one closest to the ray origin. I.e the first to be hit.
for (const ChildInfo& info : childInfos) {
if (m_Children[info.Index]->RayCollides(ray, data)) {
return true;
}
}
} else {
//Check against boxes in the node.
float minDist = INFINITY;
bool intersected = false;
for (const auto& obj : m_StaticObjects) {
float dist;
if (Collision::RayVsAABB(ray, obj, dist)) {
minDist = std::min(dist, minDist);
intersected = true;
}
}
for (const auto& obj : m_DynamicObjects) {
float dist;
if (Collision::RayVsAABB(ray, obj, dist)) {
minDist = std::min(dist, minDist);
intersected = true;
}
}
data.CollideDistance = minDist;
return intersected;
}
}
return false;
}
void OctTree::AddDynamicObject(const AABB& box)
{
if (hasChildren()) {
for (auto i : childIndicesContainingBox(box)) {
m_Children[i]->AddDynamicObject(box);
}
} else {
m_DynamicObjects.push_back(box);
}
}
void OctTree::AddStaticObject(const AABB& box)
{
if (hasChildren()) {
for (auto i : childIndicesContainingBox(box)) {
m_Children[i]->AddStaticObject(box);
}
} else {
m_StaticObjects.push_back(box);
}
}
void OctTree::BoxesInSameRegion(const AABB& box, std::vector<AABB>& outBoxes) const
{
if (hasChildren()) {
for (auto i : childIndicesContainingBox(box)) {
m_Children[i]->BoxesInSameRegion(box, outBoxes);
}
} else {
outBoxes.insert(outBoxes.end(), m_StaticObjects.begin(), m_StaticObjects.end());
outBoxes.insert(outBoxes.end(), m_DynamicObjects.begin(), m_DynamicObjects.end());
}
}
void OctTree::ClearObjects()
{
if (hasChildren()) {
for (OctTree*& c : m_Children) {
c->ClearObjects();
}
} else {
m_DynamicObjects.clear();
m_StaticObjects.clear();
}
}
void OctTree::ClearDynamicObjects()
{
if (hasChildren()) {
for (OctTree*& c : m_Children) {
c->ClearObjects();
}
} else {
m_DynamicObjects.clear();
}
}
//: 3 7
//:
//: 2 6
//: |
//: 1 5 \ y
//: z
//: 0 4 0 x-->
//
// child: 0 1 2 3 4 5 6 7
// x : - - - - + + + +
// y : - - + + - - + +
// z : - + - + - + - +
int OctTree::childIndexContainingPoint(const glm::vec3& point) const
{
const glm::vec3& c = m_Box.Center();
return (1 << 2) * (point.x >= c.x) | (1 << 1) * (point.y >= c.y) | (point.z >= c.z);
}
std::vector<int> OctTree::childIndicesContainingBox(const AABB& box) const
{
int minInd = childIndexContainingPoint(box.MinCorner());
int maxInd = childIndexContainingPoint(box.MaxCorner());
//Because of the predictable ordering of the child indices,
//the number of bits set when xor:ing the indices will determine the number of children containing the box.
std::bitset<3> bits(minInd ^ maxInd);
switch (bits.count()) {
//Box contained completely in one child.
case 0:
return{ minInd };
//Two children.
case 1:
return{ minInd, maxInd };
//Four children.
case 2:
{
std::vector<int> ret;
//Bit-hax to calculate the correct 4 children containing the box.
//This works because of the childrens index determine what part of
//the dimensions they are responsible for (which octant).
bits.flip();
//At this point the bits necessarily have exactly one bit set.
for (int c = 0; c < 8; ++c) {
//If the child index have the same bit set as the bits, add box to it.
if (bits.to_ulong() & c) {
ret.push_back(c);
}
}
return ret;
}
case 3: //Eight children.
return{ 0,1,2,3,4,5,6,7 };
default:
return std::vector<int>();
}
}
inline bool OctTree::hasChildren() const
{
return m_Children[0] != nullptr;
}
}
+66
View File
@@ -0,0 +1,66 @@
#ifndef OldOctTree_h__
#define OldOctTree_h__
#include "Core/AABB.h"
class Ray;
class World;
class Camera;
namespace Old
{
class OctTree
{
public:
struct Output
{
float CollideDistance;
};
OctTree();
~OctTree();
//For the root OctTree, [octTreeBounds] should be a box containing the entire level.
OctTree(const AABB& octTreeBounds, int subDivisions);
//We should only ever need one OctTree in the game, and it should not need to be copied.
//Define these if the OctTree suddenly needs to be copied, think of the children OctTree* ptrs.
OctTree(const OctTree& other) = delete;
OctTree(const OctTree&& other) = delete;
OctTree& operator= (const OctTree& other) = delete;
void AddDynamicObject(const AABB& box);
void AddStaticObject(const AABB& box);
void BoxesInSameRegion(const AABB& box, std::vector<AABB>& outBoxes) const;
void ClearObjects();
void ClearDynamicObjects();
//Collision test function.
void Update(float dt, World* world, Camera* cam);
//Returns true if the ray collides with something in the tree. Result is written to [data].
bool RayCollides(const Ray& ray, Output& data) const;
//Returns true if the box collides with something in the tree.
//On collision with a box, that box is written to [outBoxIntersected].
//Note: More efficient than calling BoxesInSameRegion from outside and testing there.
bool BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected) const;
private:
OctTree* m_Children[8];
std::vector<AABB> m_StaticObjects;
std::vector<AABB> m_DynamicObjects;
AABB m_Box;
bool m_UpdatedOnce;
unsigned int m_BoxID;
glm::vec3 m_PrevPos;
glm::quat m_PrevOri;
inline bool hasChildren() const;
int childIndexContainingPoint(const glm::vec3& point) const;
std::vector<int> childIndicesContainingBox(const AABB& box) const;
};
}
#endif
+5 -5
View File
@@ -63,9 +63,9 @@ BOOST_AUTO_TEST_CASE(WorldTestMultipleAllocations, * utf::tolerance(0.00001))
}
// Loop through them and check data
//int i = 0;
//for (auto& c : w.GetComponents("Test")) {
// BOOST_TEST((int)c["TestInteger"] == i);
// i++;
//}
int i = 0;
for (auto& c : *w.GetComponents("Test")) {
BOOST_TEST((int)c["TestInteger"] == i);
i++;
}
}