diff --git a/README.md b/README.md index 217cb69e..7c5f2268 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,11 @@ Libraries bundled along with binaries for Windows (MSVC14), available as a submo | **[zlib](http://www.zlib.net)** | 1.28 | [zlib License](http://www.zlib.net/zlib_license.html) | | **[libpng](http://www.libpng.org/pub/png/libpng.html)** | 1.6.19 | [libpng License](http://www.libpng.org/pub/png/src/libpng-LICENSE.txt) | | **[Xerces-C++](https://xerces.apache.org/xerces-c)** | 3.1.2 | [Apache License Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) | +| **[ImGui](https://github.com/ocornut/imgui)** | 2015-12-12 | [MIT License](https://github.com/ocornut/imgui/blob/de3a154f3801de22c8e0bd2aeabf663a70c05972/LICENSE) | #### External libraries Libraries that are too big to be bundled with the project. | Project | Version | License | Root folder environment variable (Windows) | | ---------------------------------------------------------- | ----------- | --------------------------------------------------------------------------- | ------------------------------------------ | -| **[Boost](http://www.boost.org)** | 1.59.0+ | [Boost Software License, Version 1.0](http://www.boost.org/LICENSE_1_0.txt) | BOOST_ROOT | +| **[Boost](http://www.boost.org)** | 1.60.0+ | [Boost Software License, Version 1.0](http://www.boost.org/LICENSE_1_0.txt) | BOOST_ROOT | diff --git a/assets b/assets index 56305dcc..c8e631f4 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 56305dcca629b8adaf57efc4e6a853b6c5f345f8 +Subproject commit c8e631f449515cdbe3647b96ce472839748e28f9 diff --git a/cmake/FindXerces.cmake b/cmake/FindXerces.cmake index 8ea0fb1d..651453bd 100644 --- a/cmake/FindXerces.cmake +++ b/cmake/FindXerces.cmake @@ -1,13 +1,13 @@ -# XERCES_FOUND -# XERCES_INCLUDE_DIRS -# XERCES_LIBRARIES +# Xerces_FOUND +# Xerces_INCLUDE_DIRS +# Xerces_LIBRARIES -find_path(XERCES_INCLUDE_DIR xercesc/dom/dom.hpp +find_path(Xerces_INCLUDE_DIR xercesc/dom/dom.hpp /usr/local/include /usr/include ) -find_library(XERCES_LIBRARY +find_library(Xerces_LIBRARY NAMES xerces-c_3 xerces-c_3D @@ -16,8 +16,8 @@ find_library(XERCES_LIBRARY /usr/lib ) -set(XERCES_INCLUDE_DIRS ${XERCES_INCLUDE_DIR}) -set(XERCES_LIBRARIES ${XERCES_LIBRARY}) +set(Xerces_INCLUDE_DIRS ${Xerces_INCLUDE_DIR}) +set(Xerces_LIBRARIES ${Xerces_LIBRARY}) -find_package_handle_standard_args(Xerces DEFAULT_MSG XERCES_LIBRARY XERCES_INCLUDE_DIR) -mark_as_advanced(Xerces_FOUND XERCES_INCLUDE_DIR XERCES_LIBRARY) \ No newline at end of file +find_package_handle_standard_args(Xerces DEFAULT_MSG Xerces_LIBRARY Xerces_INCLUDE_DIR) +mark_as_advanced(Xerces_FOUND Xerces_INCLUDE_DIR Xerces_LIBRARY) diff --git a/deps b/deps index 1b478d31..1ae6ba5b 160000 --- a/deps +++ b/deps @@ -1 +1 @@ -Subproject commit 1b478d3159f12273059a684ee8e187f4a25c89f0 +Subproject commit 1ae6ba5b1297ed71b560aee211b9f0007ba52547 diff --git a/include/Engine/Collision/Collision.h b/include/Engine/Collision/Collision.h new file mode 100644 index 00000000..714cee3f --- /dev/null +++ b/include/Engine/Collision/Collision.h @@ -0,0 +1,59 @@ +#ifndef Collision_h__ +#define Collision_h__ + +//NOTE: Collision.h needs to be #included before , +//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 + +#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& modelVertices, + const std::vector& 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& modelVertices, + const std::vector& 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& modelVertices, + const std::vector& 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 \ No newline at end of file diff --git a/include/Engine/Collision/CollisionSystem.h b/include/Engine/Collision/CollisionSystem.h new file mode 100644 index 00000000..254a2461 --- /dev/null +++ b/include/Engine/Collision/CollisionSystem.h @@ -0,0 +1,31 @@ +#ifndef CollisionSystem_h__ +#define CollisionSystem_h__ + +#include +#include + +#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 m_EKeyUp; + bool OnKeyUp(const Events::KeyUp &event); +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Collision/ETrigger.h b/include/Engine/Collision/ETrigger.h new file mode 100644 index 00000000..687c73fa --- /dev/null +++ b/include/Engine/Collision/ETrigger.h @@ -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 diff --git a/include/Engine/Collision/TriggerSystem.h b/include/Engine/Collision/TriggerSystem.h new file mode 100644 index 00000000..7e6ef008 --- /dev/null +++ b/include/Engine/Collision/TriggerSystem.h @@ -0,0 +1,38 @@ +#ifndef TriggerSystem_h__ +#define TriggerSystem_h__ + +#include +#include + +#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> m_EntitiesTouchingTrigger; + std::unordered_map> m_EntitiesCompletelyInTrigger; + + //True if leave event was thrown. + bool throwLeaveIfWasInTrigger(std::unordered_set& triggerSet, EntityID pId, EntityID tId); + template + void publish(EntityID pId, EntityID tId) + { + Event e; + e.Trigger = tId; + e.Entity = pId; + m_EventBroker->Publish(e); + } +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Common.h b/include/Engine/Common.h index 2469a11d..ebdc90d0 100644 --- a/include/Engine/Common.h +++ b/include/Engine/Common.h @@ -4,4 +4,5 @@ #include #include -#include "Core/Util/Logging.h" \ No newline at end of file +#include "Core/Util/Logging.h" +#include "Core/Util/IfDebug.h" \ No newline at end of file diff --git a/include/Engine/Core/AABB.h b/include/Engine/Core/AABB.h new file mode 100644 index 00000000..c8e05248 --- /dev/null +++ b/include/Engine/Core/AABB.h @@ -0,0 +1,29 @@ +#ifndef AABB_h__ +#define AABB_h__ + +#include "../GLM.h" + +class AABB +{ +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(); + + 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& HalfSize() const { return m_HalfSize; } +private: + glm::vec3 m_MinCorner; + glm::vec3 m_MaxCorner; + glm::vec3 m_Center; + glm::vec3 m_HalfSize; +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Core/ComponentPool.h b/include/Engine/Core/ComponentPool.h index f0d53864..619aade8 100644 --- a/include/Engine/Core/ComponentPool.h +++ b/include/Engine/Core/ComponentPool.h @@ -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; @@ -54,6 +54,8 @@ public: ComponentWrapper Allocate(EntityID entity); // Get the component belonging to a specific entity ComponentWrapper GetByEntity(EntityID ent); + // Returns true if the pool contains a component for the specified entity + bool KnowsEntity(EntityID ent); // Delete a component and free its memory void Delete(ComponentWrapper& wrapper); diff --git a/include/Engine/Core/ComponentWrapper.h b/include/Engine/Core/ComponentWrapper.h index 95a31240..56ae943f 100644 --- a/include/Engine/Core/ComponentWrapper.h +++ b/include/Engine/Core/ComponentWrapper.h @@ -2,7 +2,7 @@ #define ComponentWrapper_h__ #include "../Common.h" -#include "EntityWrapper.h" +#include "Entity.h" #include "ComponentInfo.h" #include "Util/Any.h" @@ -11,7 +11,7 @@ struct ComponentWrapper ComponentWrapper(const ComponentInfo& componentInfo, char* data) : Info(componentInfo) , EntityID(*reinterpret_cast<::EntityID*>(data)) - , Data(data + sizeof(EntityID)) + , Data(data + sizeof(::EntityID)) { } const ComponentInfo& Info; diff --git a/include/Engine/Core/ConfigFile.h b/include/Engine/Core/ConfigFile.h index 28c34bd3..54bbbad4 100644 --- a/include/Engine/Core/ConfigFile.h +++ b/include/Engine/Core/ConfigFile.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "../Common.h" #include "ResourceManager.h" @@ -19,12 +20,14 @@ private: public: template T Get(std::string key, T defaultValue); + template + std::vector> GetAll(std::string key); template void Set(std::string key, T value); void SaveToDisk(); - private: +private: boost::filesystem::path m_Path; boost::property_tree::ptree m_PTreeDefaults; boost::property_tree::ptree m_PTreeOverrides; @@ -37,6 +40,21 @@ T ConfigFile::Get(std::string key, T defaultValue) return m_PTreeMerged.get(key, defaultValue); } +template +std::vector> ConfigFile::GetAll(std::string key) +{ + std::vector> out; + auto parent = m_PTreeMerged.find(key); + if (parent == m_PTreeMerged.not_found()) { + return out; + } + for (auto& child : parent->second) { + T value = boost::lexical_cast(child.second.data()); + out.push_back(std::make_pair(child.first, value)); + } + return out; +} + template void ConfigFile::Set(std::string key, T value) { diff --git a/include/Engine/Core/EFileDropped.h b/include/Engine/Core/EFileDropped.h new file mode 100644 index 00000000..1ad22a2f --- /dev/null +++ b/include/Engine/Core/EFileDropped.h @@ -0,0 +1,16 @@ +#ifndef EFileDropped_h__ +#define EFileDropped_h__ + +#include "EventBroker.h" + +namespace Events +{ + +struct FileDropped : Event +{ + std::string Path; +}; + +} + +#endif \ No newline at end of file diff --git a/include/Engine/Core/EKeyboardChar.h b/include/Engine/Core/EKeyboardChar.h new file mode 100644 index 00000000..8c1654ce --- /dev/null +++ b/include/Engine/Core/EKeyboardChar.h @@ -0,0 +1,17 @@ +#ifndef Events_KeyboardChar_h__ +#define Events_KeyboardChar_h__ + +#include "EventBroker.h" + +namespace Events +{ + +struct KeyboardChar : Event +{ + double Timestamp = 0.f; + unsigned int Char = 0; +}; + +} + +#endif \ No newline at end of file diff --git a/include/Engine/Core/EMouseScroll.h b/include/Engine/Core/EMouseScroll.h new file mode 100644 index 00000000..8b2826a7 --- /dev/null +++ b/include/Engine/Core/EMouseScroll.h @@ -0,0 +1,17 @@ +#ifndef Events_MouseScroll_h__ +#define Events_MouseScroll_h__ + +#include "EventBroker.h" + +namespace Events +{ + +struct MouseScroll : Event +{ + double DeltaX; + double DeltaY; +}; + +} + +#endif diff --git a/include/Engine/Core/EPlayerDamage.h b/include/Engine/Core/EPlayerDamage.h new file mode 100644 index 00000000..87ad67aa --- /dev/null +++ b/include/Engine/Core/EPlayerDamage.h @@ -0,0 +1,20 @@ +#ifndef EPlayerDamage_h__ +#define EPlayerDamage_h__ + +#include "EventBroker.h" +#include "../Core/Entity.h" + +namespace Events +{ + +struct PlayerDamage : Event +{ + double DamageAmount; + EntityID PlayerDamagedID; + //optional TypeOfDamage + std::string TypeOfDamage; +}; + +} + +#endif \ No newline at end of file diff --git a/include/Engine/Core/EPlayerDeath.h b/include/Engine/Core/EPlayerDeath.h new file mode 100644 index 00000000..00ede5ed --- /dev/null +++ b/include/Engine/Core/EPlayerDeath.h @@ -0,0 +1,20 @@ +#ifndef EPlayerDeath_h__ +#define EPlayerDeath_h__ + +#include "EventBroker.h" +#include "../Core/Entity.h" + +namespace Events +{ + +struct PlayerDeath : Event +{ + //KilledBy,KilledByWhat is optional for now. It might be used later in the playerlog-system + EntityID KilledBy; + EntityID PlayerID; + std::string KilledByWhat; +}; + +} + +#endif \ No newline at end of file diff --git a/include/Engine/Core/EPlayerHealthPickup.h b/include/Engine/Core/EPlayerHealthPickup.h new file mode 100644 index 00000000..f3158f92 --- /dev/null +++ b/include/Engine/Core/EPlayerHealthPickup.h @@ -0,0 +1,18 @@ +#ifndef EPlayerHealthPickup_h__ +#define EPlayerHealthPickup_h__ + +#include "EventBroker.h" +#include "../Core/Entity.h" + +namespace Events +{ + +struct PlayerHealthPickup : Event +{ + double HealthAmount; + EntityID PlayerHealedID; +}; + +} + +#endif \ No newline at end of file diff --git a/include/Engine/Core/Entity.h b/include/Engine/Core/Entity.h new file mode 100644 index 00000000..c6a475e3 --- /dev/null +++ b/include/Engine/Core/Entity.h @@ -0,0 +1,6 @@ +#ifndef Entity_h__ +#define Entity_h__ + +typedef unsigned int EntityID; + +#endif \ No newline at end of file diff --git a/include/Engine/Core/EntityFile.h b/include/Engine/Core/EntityFile.h deleted file mode 100644 index 7fa423d6..00000000 --- a/include/Engine/Core/EntityFile.h +++ /dev/null @@ -1,12 +0,0 @@ -#include "ResourceManager.h" - -class EntityFile : public Resource -{ - friend class ResourceManager; - -private: - EntityFile(std::string path); - -public: - -}; \ No newline at end of file diff --git a/include/Engine/Core/EntityWrapper.h b/include/Engine/Core/EntityWrapper.h deleted file mode 100644 index d5e723ca..00000000 --- a/include/Engine/Core/EntityWrapper.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef Entity_h__ -#define Entity_h__ - -typedef unsigned int EntityID; - -struct EntityWrapper -{ - EntityWrapper(EntityID entityID) - : ID(entityID) - { } - - EntityID ID; -}; - -#endif \ No newline at end of file diff --git a/include/Engine/Core/EntityXMLFile.h b/include/Engine/Core/EntityXMLFile.h new file mode 100644 index 00000000..6fa71dad --- /dev/null +++ b/include/Engine/Core/EntityXMLFile.h @@ -0,0 +1,141 @@ +#ifndef EntityXMLFile_h__ +#define EntityXMLFile_h__ + +#include + +#include "../Common.h" +#include "../GLM.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ResourceManager.h" +#include "Entity.h" +#include "ComponentInfo.h" +class World; + +class EntityPreprocessorXMLErrorHandler : public xercesc::DOMErrorHandler +{ +public: + bool handleError(const xercesc::DOMError &e) override + { + char* message = xercesc::XMLString::transcode(e.getMessage()); + std::cerr << "Preprocessor DOMError: " << message << std::endl; + xercesc::XMLString::release(&message); + return false; + } +}; + +class EntityParserXMLErrorHandler : public xercesc::ErrorHandler +{ +public: + void warning(const xercesc::SAXParseException& e) override + { + reportParseException("Warning", e); + } + void error(const xercesc::SAXParseException& e) override + { + reportParseException("Error", e); + } + void fatalError(const xercesc::SAXParseException& e) override + { + reportParseException("FATAL ERROR", e); + } + void resetErrors() override { } + +private: + void reportParseException(std::string type, const xercesc::SAXParseException& e) + { + char* message = xercesc::XMLString::transcode(e.getMessage()); + char* systemID = xercesc::XMLString::transcode(e.getSystemId()); + std::cerr << systemID << ":" << e.getLineNumber() << ":" << e.getColumnNumber() << std::endl; + std::cerr << type << ": " << message << std::endl; + xercesc::XMLString::release(&systemID); + xercesc::XMLString::release(&message); + } +}; + +class XSTR +{ +public: + XSTR(const XMLCh* const xmlString) + { + m_AsChar = xercesc::XMLString::transcode(xmlString); + } + + XSTR(const char* normalString) + { + m_AsXMLCh = xercesc::XMLString::transcode(normalString); + } + + ~XSTR() + { + if (m_AsChar != nullptr) { + xercesc::XMLString::release(&m_AsChar); + } + if (m_AsXMLCh != nullptr) { + xercesc::XMLString::release(&m_AsXMLCh); + } + } + + operator const char*() const { return m_AsChar; } + operator const XMLCh*() const { return m_AsXMLCh; } + +private: + char* m_AsChar = nullptr; + XMLCh* m_AsXMLCh = nullptr; +}; + +class EntityXMLFile : public Resource +{ + friend class ResourceManager; + +private: + EntityXMLFile(std::string path); + +public: + ~EntityXMLFile(); + + void PopulateWorld(World* world); + +private: + static unsigned int InstanceCount; + + std::string m_EntityFile; + xercesc::XMLGrammarPool* m_GrammarPool = nullptr; + EntityParserXMLErrorHandler* m_ErrorHandler = nullptr; + xercesc::XercesDOMParser* m_DOMParser = nullptr; + xercesc::DOMDocument* m_DOMDocument = nullptr; + std::map m_ComponentInfo; + + // Preprocesses the entity file to insert include-by-copy child entities + // TODO: Make this work in memory instead of saving to file + void preprocess(std::string inPath, std::string outPath); + + void parseComponentInfo(); + void parseDefaults(); + void predictComponentAllocation(); + void parseEntityGraph(World* world, xercesc::DOMElement* parent, EntityID parentEntity); + std::size_t getTypeStride(std::string typeName); + float getFloatAttribute(const xercesc::DOMElement* element, const char* attribute) const; + void writeData(const xercesc::DOMElement* element, std::string typeName, char* outData); +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Core/EventBroker.h b/include/Engine/Core/EventBroker.h index 29d82c17..dde5242a 100644 --- a/include/Engine/Core/EventBroker.h +++ b/include/Engine/Core/EventBroker.h @@ -13,123 +13,127 @@ relay = decltype(relay)(std::bind(handler, this, std::placeholders::_1)); \ m_EventBroker->Subscribe(relay); +typedef unsigned int EventID; + class EventBroker; class BaseEventRelay { -friend class EventBroker; + friend class EventBroker; protected: - BaseEventRelay(std::string contextTypeName, std::string eventTypeName) - : m_ContextTypeName(contextTypeName) - , m_EventTypeName(eventTypeName) - , m_Broker(nullptr) - { } - ~BaseEventRelay(); + BaseEventRelay(std::string contextTypeName, std::string eventTypeName) + : m_ContextTypeName(contextTypeName) + , m_EventTypeName(eventTypeName) + , m_Broker(nullptr) + { } + ~BaseEventRelay(); public: - virtual bool Receive(const std::shared_ptr event) = 0; + virtual bool Receive(const std::shared_ptr event) = 0; protected: - std::string m_ContextTypeName; - std::string m_EventTypeName; - EventBroker* m_Broker; + EventID m_EventID; + std::string m_ContextTypeName; + std::string m_EventTypeName; + EventBroker* m_Broker; }; template class EventRelay : public BaseEventRelay { public: - typedef std::function CallbackType; + typedef std::function CallbackType; - EventRelay() - : m_Callback(nullptr) - , BaseEventRelay(typeid(ContextType).name(), typeid(EventType).name()) - { } - EventRelay(CallbackType callback) - : m_Callback(callback) - , BaseEventRelay(typeid(ContextType).name(), typeid(EventType).name()) - { } + EventRelay() + : m_Callback(nullptr) + , BaseEventRelay(typeid(ContextType).name(), typeid(EventType).name()) + { } + EventRelay(CallbackType callback) + : m_Callback(callback) + , BaseEventRelay(typeid(ContextType).name(), typeid(EventType).name()) + { } protected: - bool Receive(const std::shared_ptr event) override; + bool Receive(const std::shared_ptr event) override; private: - CallbackType m_Callback; + CallbackType m_Callback; }; template bool EventRelay::Receive(const std::shared_ptr event) { - if (m_Callback != nullptr) { - return m_Callback(*static_cast(event.get())); - } else { - return false; - } + if (m_Callback != nullptr) { + return m_Callback(*static_cast(event.get())); + } else { + return false; + } } class EventBroker { -template friend class EventRelay; + template friend class EventRelay; public: - EventBroker() - { - m_EventQueueRead = std::make_shared(); - m_EventQueueWrite = std::make_shared(); - } + EventBroker() + { + m_EventQueueRead = std::make_shared(); + m_EventQueueWrite = std::make_shared(); + } - void Subscribe(BaseEventRelay &relay); - template - void Publish(const EventType &event); - /* - Process all events in a given context. - Returns: Number of events processed - */ - template - int Process(); - int Process(std::string contextTypeName); - void Swap(); - void Clear(); - void Unsubscribe(BaseEventRelay &relay); + void Subscribe(BaseEventRelay &relay); + template + void Publish(const EventType &event); + /* + Process all events in a given context. + Returns: Number of events processed + */ + template + int Process(); + int Process(std::string contextTypeName); + void Swap(); + void Clear(); + void Unsubscribe(BaseEventRelay &relay); private: - bool m_IsProcessing = false; + bool m_IsProcessing = false; + EventID m_NextEventID = 0; - typedef std::string ContextTypeName_t; // typeid(ContextType).name() - typedef std::string EventTypeName_t; // typeid(EventType).name() + typedef std::string ContextTypeName_t; // typeid(ContextType).name() + typedef std::string EventTypeName_t; // typeid(EventType).name() - typedef std::unordered_multimap EventRelays_t; - typedef std::unordered_map ContextRelays_t; - ContextRelays_t m_ContextRelays; - std::vector m_RelaysToSubscribe; - std::vector m_RelaysToUnsubscribe; + typedef std::unordered_multimap EventRelays_t; + typedef std::unordered_map ContextRelays_t; + ContextRelays_t m_ContextRelays; + std::vector m_RelaysToSubscribe; + std::vector> m_RelaysToUnsubscribe; - typedef std::list>> EventQueue_t; - std::shared_ptr m_EventQueueRead; - std::shared_ptr m_EventQueueWrite; + typedef std::list>> EventQueue_t; + std::shared_ptr m_EventQueueRead; + std::shared_ptr m_EventQueueWrite; - void subscribeImmediate(BaseEventRelay& relay); - void unsubscribeImmediate(BaseEventRelay& relay); + void subscribeImmediate(BaseEventRelay& relay); + void unsubscribeImmediate(std::tuple identifier); }; template void EventBroker::Publish(const EventType &event) { - /*auto itpair = m_Subscribers.equal_range(typeid(EventType).name()); - for (auto it = itpair.first; it != itpair.second; ++it) - { - it->second->Receive(event); - }*/ + /*auto itpair = m_Subscribers.equal_range(typeid(EventType).name()); + for (auto it = itpair.first; it != itpair.second; ++it) + { + it->second->Receive(event); + }*/ - m_EventQueueWrite->push_back(std::make_pair(typeid(EventType).name(), std::shared_ptr(new EventType(event)))); + m_EventQueueWrite->push_back(std::make_pair(typeid(EventType).name(), std::shared_ptr(new EventType(event)))); } template int EventBroker::Process() { - const std::string contextTypeName = typeid(ContextType).name(); - return Process(contextTypeName); + const std::string contextTypeName = typeid(ContextType).name(); + return Process(contextTypeName); } #endif diff --git a/include/Engine/Core/InputController.h b/include/Engine/Core/InputController.h index b91fb448..b87d1eff 100644 --- a/include/Engine/Core/InputController.h +++ b/include/Engine/Core/InputController.h @@ -11,25 +11,22 @@ template class InputController { public: - InputController(std::shared_ptr eventBroker) - : EventBroker(eventBroker) + InputController(EventBroker* eventBroker) + : m_EventBroker(eventBroker) { Initialize(); } virtual void Initialize() { EVENT_SUBSCRIBE_MEMBER(m_EInputCommand, &InputController::OnCommand); - EVENT_SUBSCRIBE_MEMBER(m_EMouseMove, &InputController::OnMouseMove); } - virtual bool OnCommand(const Events::InputCommand &event) { return false; } - virtual bool OnMouseMove(const Events::MouseMove &event) { return false; } + virtual bool OnCommand(const Events::InputCommand& e) { return false; } protected: - std::shared_ptr EventBroker; + EventBroker* m_EventBroker; private: EventRelay m_EInputCommand; - EventRelay m_EMouseMove; }; #endif diff --git a/include/Engine/Core/InputManager.h b/include/Engine/Core/InputManager.h index b8fe7f7a..3b035dbe 100644 --- a/include/Engine/Core/InputManager.h +++ b/include/Engine/Core/InputManager.h @@ -8,12 +8,15 @@ #include "EventBroker.h" #include "EKeyDown.h" #include "EKeyUp.h" +#include "EKeyboardChar.h" #include "EMousePress.h" #include "EMouseRelease.h" #include "EMouseMove.h" +#include "EMouseScroll.h" #include "ELockMouse.h" #include "EGamepadAxis.h" #include "EGamepadButton.h" +#include "EFileDropped.h" class InputManager { @@ -64,6 +67,13 @@ private: void PublishGamepadAxisIfChanged(int gamepadID, Gamepad::Axis axis); void PublishGamepadButtonIfChanged(int gamepadID, Gamepad::Button button); + + static std::vector GLFWCharCallbackQueue; + static void GLFWCharCallback(GLFWwindow* window, unsigned int c); + static std::vector> GLFWScrollCallbackQueue; + static void GLFWScrollCallback(GLFWwindow* window, double xoffset, double yoffset); + static std::vector GLFWDropCallbackQueue; + static void GLFWDropCallback(GLFWwindow* window, int count, const char* paths[]); }; #endif diff --git a/include/Engine/Core/MemoryPool.h b/include/Engine/Core/MemoryPool.h index 034e6dc4..ff24ac80 100644 --- a/include/Engine/Core/MemoryPool.h +++ b/include/Engine/Core/MemoryPool.h @@ -253,7 +253,7 @@ public: } //Postfix increment i.e. iter++. Prefer pre-increment (++iter) for efficiency. - MemoryPoolForwardIterator& operator++(int) + MemoryPoolForwardIterator operator++(int) { MemoryPoolForwardIterator copyIter(*this); operator++(); diff --git a/include/Engine/Core/OctTree.h b/include/Engine/Core/OctTree.h new file mode 100644 index 00000000..cdb21fbb --- /dev/null +++ b/include/Engine/Core/OctTree.h @@ -0,0 +1,104 @@ +#ifndef OctTree_h__ +#define OctTree_h__ + +#include "Core/AABB.h" + +class Ray; + +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 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; + //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); + //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& 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); + //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); + +private: + 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 m_StaticObjects; + std::vector m_DynamicObjects; + + bool m_UpdatedOnce; + unsigned int m_BoxID; + glm::vec3 m_PrevPos; + glm::quat m_PrevOri; + + void falsifyObjectChecks(); + + struct OctChild + { + ~OctChild(); + OctChild(const AABB& octTreeBounds, + int subDivisions, + std::vector& staticObjects, + std::vector& 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& 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 m_StaticObjIndices; + std::vector m_DynamicObjIndices; + AABB m_Box; + //Reference to the lists in OctTree. + std::vector& m_StaticObjectsRef; + std::vector& m_DynamicObjectsRef; + + inline bool hasChildren() const; + int childIndexContainingPoint(const glm::vec3& point) const; + std::vector childIndicesContainingBox(const AABB& box) const; + }; +}; + + +#endif \ No newline at end of file diff --git a/include/Engine/Core/Ray.h b/include/Engine/Core/Ray.h new file mode 100644 index 00000000..0fcef01e --- /dev/null +++ b/include/Engine/Core/Ray.h @@ -0,0 +1,31 @@ +#ifndef Ray_h__ +#define Ray_h__ + +#include "../GLM.h" +#include "Common.h" + +class Ray +{ +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__ diff --git a/include/Engine/Core/ResourceManager.h b/include/Engine/Core/ResourceManager.h index d408f5af..86ed6254 100644 --- a/include/Engine/Core/ResourceManager.h +++ b/include/Engine/Core/ResourceManager.h @@ -81,6 +81,8 @@ public: @param resourceName Fully qualified name of the resource to reload. */ static void Reload(std::string resourceName); + + static void Release(std::string resourceType, std::string resourceName); static void Update(); diff --git a/include/Engine/Core/System.h b/include/Engine/Core/System.h new file mode 100644 index 00000000..75bd3882 --- /dev/null +++ b/include/Engine/Core/System.h @@ -0,0 +1,50 @@ +#ifndef System_h__ +#define System_h__ + +#include "EventBroker.h" +#include "World.h" +#include "ComponentWrapper.h" + +class System +{ + friend class SystemPipeline; + +protected: + System(EventBroker* eventBroker) + : m_EventBroker(eventBroker) + { } + virtual ~System() = default; + + EventBroker* m_EventBroker; +}; + +class PureSystem : public System +{ + friend class SystemPipeline; + +protected: + PureSystem(EventBroker* eventBroker, std::string componentType) + : System(eventBroker) + , m_ComponentType(componentType) + { } + virtual ~PureSystem() = default; + + const std::string m_ComponentType; + + virtual void UpdateComponent(World* world, ComponentWrapper& component, double dt) = 0; +}; + +class ImpureSystem : public System +{ + friend class SystemPipeline; + +protected: + ImpureSystem(EventBroker* eventBroker) + : System(eventBroker) + { } + virtual ~ImpureSystem() = default; + + virtual void Update(World* world, double dt) = 0; +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Core/SystemPipeline.h b/include/Engine/Core/SystemPipeline.h new file mode 100644 index 00000000..d6a6b371 --- /dev/null +++ b/include/Engine/Core/SystemPipeline.h @@ -0,0 +1,89 @@ +#ifndef SystemPipeline_h__ +#define SystemPipeline_h__ + +#include "../Common.h" +#include "EventBroker.h" +#include "System.h" +#include "World.h" + +class SystemPipeline +{ +public: + SystemPipeline(EventBroker* eventBroker) + : m_EventBroker(eventBroker) + { } + ~SystemPipeline() + { + for (UnorderedSystems& group : m_OrderedSystemGroups) { + for (auto& pair : group.Systems) { + delete pair.second; + } + } + } + + template + //All systems with orderlevel 0 will be updated first, then 1, 2, etc. + void AddSystem(int updateOrderLevel, Arguments... args) + { + if (updateOrderLevel + 1 > m_OrderedSystemGroups.size()) { + m_OrderedSystemGroups.resize(updateOrderLevel + 1); + } + UnorderedSystems& group = m_OrderedSystemGroups[updateOrderLevel]; + System* system = new T(m_EventBroker, args...); + group.Systems[typeid(T).name()] = system; + + if (std::is_base_of::value) { + PureSystem* pureSystem = static_cast(system); + if (!pureSystem->m_ComponentType.empty()) { + group.PureSystems[pureSystem->m_ComponentType].push_back(pureSystem); + } else { + LOG_ERROR("Failed to add pure system \"%s\": Missing component type!", typeid(T).name()); + } + } + + if (std::is_base_of::value) { + ImpureSystem* impureSystem = static_cast(system); + group.ImpureSystems.push_back(impureSystem); + } + } + + void Update(World* world, double dt) + { + for (UnorderedSystems& group : m_OrderedSystemGroups) { + // Process events + for (auto& pair : group.Systems) { + m_EventBroker->Process(pair.first); + } + + // Update + for (auto& pair : group.PureSystems) { + const std::string& componentName = pair.first; + auto& systems = pair.second; + const ComponentPool* pool = world->GetComponents(componentName); + if (pool == nullptr) { + continue; + } + for (auto& component : *pool) { + for (auto& system : systems) { + system->UpdateComponent(world, component, dt); + } + } + } + for (auto& system : group.ImpureSystems) { + system->Update(world, dt); + } + } + } + +private: + EventBroker* m_EventBroker; + struct UnorderedSystems + { + std::map Systems; + std::map> PureSystems; + std::vector ImpureSystems; + }; + std::vector m_OrderedSystemGroups; +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Core/Util/IfDebug.h b/include/Engine/Core/Util/IfDebug.h new file mode 100644 index 00000000..79cb3a4c --- /dev/null +++ b/include/Engine/Core/Util/IfDebug.h @@ -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 \ No newline at end of file diff --git a/include/Engine/Core/World.h b/include/Engine/Core/World.h index 20bbe288..92378816 100644 --- a/include/Engine/Core/World.h +++ b/include/Engine/Core/World.h @@ -2,7 +2,7 @@ #define World_h__ #include "../Common.h" -#include "EntityWrapper.h" +#include "Entity.h" #include "ObjectPool.h" #include "ComponentPool.h" @@ -12,21 +12,37 @@ public: World() = default; ~World(); + // Create empty entity EntityID CreateEntity(EntityID parent = 0); + // Delete entity and all components within + void DeleteEntity(EntityID entity); // Register a component type and allocate space for it void RegisterComponent(ComponentInfo& ci); // Attach a component to an entity and fill it with default values ComponentWrapper AttachComponent(EntityID entity, std::string componentType); + // Check if an entity has a component + bool HasComponent(EntityID entity, std::string componentType); // Get a component of an entity ComponentWrapper GetComponent(EntityID entity, std::string componentType); + // Delete a component off an entity + void DeleteComponent(EntityID entity, std::string componentType); // Get all components of the specified type - const ComponentPool& GetComponents(std::string componentType); + const ComponentPool* GetComponents(std::string componentType); + // Get entity parent + EntityID GetParent(EntityID entity); + // Change the parent of an entity + void SetParent(EntityID entity, EntityID parent); + // Get all component pools + const std::unordered_map& GetComponentPools() const { return m_ComponentPools; } + // Get the entity children map + const std::unordered_multimap& GetEntityChildren() const { return m_EntityChildren; } private: - EntityID m_CurrentEntityID = 0; + EntityID m_CurrentEntityID = 1; std::unordered_map m_EntityParents; + // TODO: This should be a more effective structure std::unordered_multimap m_EntityChildren; std::unordered_map m_ComponentPools; diff --git a/include/Engine/Editor/EditorSystem.h b/include/Engine/Editor/EditorSystem.h new file mode 100644 index 00000000..779b7f97 --- /dev/null +++ b/include/Engine/Editor/EditorSystem.h @@ -0,0 +1,79 @@ +#include +#include +#include +#include "../Core/System.h" +#include "../Core/EMousePress.h" +#include "../Core/EMouseRelease.h" +#include "../Core/EMouseMove.h" +#include "../Core/ConfigFile.h" +#include "../Input/EInputCommand.h" +#include "../Rendering/IRenderer.h" +#include "../Rendering/EPicking.h" +#include "../Core/EFileDropped.h" +#include "../Rendering/RenderQueueFactory.h" + +class EditorSystem : public ImpureSystem +{ +public: + EditorSystem(EventBroker* eventBroker, IRenderer* renderer); + + virtual void Update(World* world, double dt) override; + +private: + IRenderer* m_Renderer; + World* m_World = nullptr; + + bool m_Enabled; + bool m_Visible; + std::vector m_PickingQueue; + + enum class WidgetMode + { + None, + Translate, + Rotate, + Scale + } m_WidgetMode = WidgetMode::None; + + enum class WidgetSpace + { + Local, + Global + } m_WidgetSpace = WidgetSpace::Global; + + EntityID m_Widget = 0; + EntityID m_WidgetX = 0; + EntityID m_WidgetPlaneX = 0; + EntityID m_WidgetY = 0; + EntityID m_WidgetPlaneY = 0; + EntityID m_WidgetZ = 0; + EntityID m_WidgetPlaneZ = 0; + EntityID m_WidgetOrigin = 0; + glm::vec3 m_WidgetCurrentAxis; + float m_WidgetPickingDepth = 0.f; + + EntityID m_Selection = 0; + EntityID m_LastSelection = 0; + glm::vec3 m_Position; + std::string m_LastDroppedFile; + + EventRelay m_EInputCommand; + bool OnInputCommand(const Events::InputCommand& e); + EventRelay m_EMouseRelease; + bool OnMouseRelease(const Events::MouseRelease& e); + EventRelay m_EMousePress; + bool OnMousePress(const Events::MousePress& e); + EventRelay m_EMouseMove; + bool OnMouseMove(const Events::MouseMove& e); + EventRelay m_EPicking; + bool OnPicking(const Events::Picking& e); + EventRelay m_EFileDropped; + bool OnFileDropped(const Events::FileDropped& e); + + void updateWidget(); + void setWidgetMode(WidgetMode newMode); + void setWidgetSpace(WidgetSpace space); + void drawUI(World* world, double dt); + bool createDeleteButton(std::string componentType); + void changeParent(EntityID entity, EntityID newParent); +}; \ No newline at end of file diff --git a/include/Engine/Input/EBindGamepadAxis.h b/include/Engine/Input/EBindGamepadAxis.h deleted file mode 100644 index 1f2665ad..00000000 --- a/include/Engine/Input/EBindGamepadAxis.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef Events_BindGamepadAxis_h__ -#define Events_BindGamepadAxis_h__ - -#include "Core/EventBroker.h" -#include "Core/EGamepadAxis.h" - -namespace Events -{ - -/** Called to bind a gamepad axis to an input command. */ -struct BindGamepadAxis : Event -{ - /** The axis to bind. */ - Gamepad::Axis Axis; - /** The command to send. */ - std::string Command; - /** The value to send for positive stimulation. - - Multiplied by the 0-1 clamped value of the axis. - */ - float Value; -}; - -} diff --git a/include/Engine/Input/EBindGamepadButton.h b/include/Engine/Input/EBindGamepadButton.h deleted file mode 100644 index 47dddf31..00000000 --- a/include/Engine/Input/EBindGamepadButton.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef Events_BindGamepadButton_h__ -#define Events_BindGamepadButton_h__ - -#include "Core/EventBroker.h" -#include "Core/EGamepadButton.h" - -namespace Events -{ - -/** Called to bind a gamepad button to an input command. */ -struct BindGamepadButton : Event -{ - /** The gamepad button to bind. */ - Gamepad::Button Button; - /** The command to send. */ - std::string Command; - /** The value to send for positive stimulation. - - Multiplied by the 0-1 clamped value of the button. - */ - float Value; -}; - -} - -#endif diff --git a/include/Engine/Input/EBindKey.h b/include/Engine/Input/EBindKey.h deleted file mode 100644 index fb1c199a..00000000 --- a/include/Engine/Input/EBindKey.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef Events_BindKey_h__ -#define Events_BindKey_h__ - -#include "Core/EventBroker.h" - -namespace Events -{ - -/** Called to bind a keyboard key to an input command. */ -struct BindKey : Event -{ - /** The GLFW key code to bind. */ - int KeyCode; - /** The command to send. */ - std::string Command; - /** The value to send for positive stimulation. - - Multiplied by the 0-1 clamped value of the key. - */ - float Value; -}; - -} - -#endif diff --git a/include/Engine/Input/EBindMouseButton.h b/include/Engine/Input/EBindMouseButton.h deleted file mode 100644 index 78b5dfa3..00000000 --- a/include/Engine/Input/EBindMouseButton.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef Events_BindMouseButton_h__ -#define Events_BindMouseButton_h__ - -#include "Core/EventBroker.h" - -namespace Events -{ - -/** Called to bind a mouse button to an input command. */ -struct BindMouseButton : Event -{ - /** The GLFW mouse button code to bind. */ - int Button; - /** The command to send. */ - std::string Command; - /** The value to send for positive stimulation. - - Multiplied by the 0-1 clamped value of the button. - */ - float Value; -}; - -} - -#endif diff --git a/include/Engine/Input/EBindOrigin.h b/include/Engine/Input/EBindOrigin.h new file mode 100644 index 00000000..e39a6023 --- /dev/null +++ b/include/Engine/Input/EBindOrigin.h @@ -0,0 +1,22 @@ +#ifndef Events_BindOrigin_h__ +#define Events_BindOrigin_h__ + +#include "Core/EventBroker.h" + +namespace Events +{ + +/** Called to bind an input origin to an input command. */ +struct BindOrigin : Event +{ + /** The input origin to bind. */ + std::string Origin; + /** The command to send. */ + std::string Command; + /** The value to send for positive stimulation. */ + float Value = 1.f; +}; + +} + +#endif diff --git a/include/Engine/Input/EInputCommand.h b/include/Engine/Input/EInputCommand.h index 9c190a1e..9ec897d3 100644 --- a/include/Engine/Input/EInputCommand.h +++ b/include/Engine/Input/EInputCommand.h @@ -13,7 +13,7 @@ struct InputCommand : Event /** The command that was sent. */ std::string Command; /** The value of the command. */ - float Value; + float Value = 0; }; } diff --git a/include/Engine/Input/FirstPersonInputController.h b/include/Engine/Input/FirstPersonInputController.h new file mode 100644 index 00000000..d8584494 --- /dev/null +++ b/include/Engine/Input/FirstPersonInputController.h @@ -0,0 +1,70 @@ +#ifndef FirstPersonInputController_h__ +#define FirstPersonInputController_h__ + +#include "../GLM.h" +#include "../Core/InputController.h" +#include "../Core/ELockMouse.h" + +template +class FirstPersonInputController : public InputController +{ +public: + FirstPersonInputController(EventBroker* eventBroker, unsigned int playerID) + : InputController(eventBroker) + , m_PlayerID(playerID) + { + EVENT_SUBSCRIBE_MEMBER(m_ELockMouse, &FirstPersonInputController::OnLockMouse); + EVENT_SUBSCRIBE_MEMBER(m_EUnlockMouse, &FirstPersonInputController::OnUnlockMouse); + } + + const glm::quat Orientation() const { return m_Orientation; } + + void LockMouse() + { + Events::LockMouse e; + m_EventBroker->Publish(e); + m_MouseLocked = true; + } + + void UnlockMouse() + { + Events::UnlockMouse e; + m_EventBroker->Publish(e); + m_MouseLocked = false; + } + + virtual bool OnCommand(const Events::InputCommand& e) override + { + if (m_PlayerID != e.PlayerID) { + return false; + } + + if (m_MouseLocked) { + if (e.Command == "Pitch") { + float val = glm::radians(e.Value); + m_Orientation = m_Orientation * glm::angleAxis(-val, glm::vec3(1, 0, 0)); + return true; + } + + if (e.Command == "Yaw") { + float val = glm::radians(e.Value); + m_Orientation = glm::angleAxis(-val, glm::vec3(0, 1, 0)) * m_Orientation; + return true; + } + } + + return false; + } + +protected: + const unsigned int m_PlayerID; + glm::quat m_Orientation; + bool m_MouseLocked = false; + + EventRelay m_ELockMouse; + bool OnLockMouse(const Events::LockMouse& e) { m_MouseLocked = true; return true; } + EventRelay m_EUnlockMouse; + bool OnUnlockMouse(const Events::UnlockMouse& e) { m_MouseLocked = false; return true; } +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Input/InputHandler.h b/include/Engine/Input/InputHandler.h new file mode 100644 index 00000000..a9b3c038 --- /dev/null +++ b/include/Engine/Input/InputHandler.h @@ -0,0 +1,25 @@ +#ifndef InputHandler_h__ +#define InputHandler_h__ + +#include "../Common.h" +#include "../Core/EventBroker.h" +#include "InputProxy.h" + +class InputHandler +{ +public: + InputHandler(EventBroker* eventBroker, InputProxy* inputProxy) + : m_EventBroker(eventBroker) + , m_InputProxy(inputProxy) + { } + + virtual bool BindOrigin(std::string origin, std::string command, float value) = 0; + virtual void Update(double dt) { } + virtual float GetCommandValue(std::string command) = 0; + +protected: + EventBroker* m_EventBroker; + InputProxy* m_InputProxy; +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Input/InputProxy.h b/include/Engine/Input/InputProxy.h new file mode 100644 index 00000000..c7817c22 --- /dev/null +++ b/include/Engine/Input/InputProxy.h @@ -0,0 +1,46 @@ +#ifndef InputProxy_h__ +#define InputProxy_h__ + +#include "../Common.h" +#include "../Core/ResourceManager.h" +#include "../Core/ConfigFile.h" +#include "EInputCommand.h" +#include "EBindOrigin.h" + +class InputHandler; + +class InputProxy +{ +public: + InputProxy(EventBroker* eventBroker); + ~InputProxy(); + + void LoadBindings(std::string file); + void Update(double dt); + void Process(); + template + void AddHandler(); + void Publish(const Events::InputCommand& e); + +protected: + EventBroker* m_EventBroker; + std::vector m_Handlers; + std::map> m_CommandHandlers; + + // Represents every unique command (has of PlayerID & Command) and all values reported for that command this frame + std::map, std::vector> m_CommandQueue; + + std::map m_CurrentCommandValues; + std::map m_LastCommandValues; + + EventRelay m_EBindOrigin; + bool OnBindOrigin(const Events::BindOrigin& e); +}; + +template +void InputProxy::AddHandler() +{ + m_Handlers.push_back(new T(m_EventBroker, this)); +} + +#endif diff --git a/include/Engine/Input/InputSystem.h b/include/Engine/Input/InputSystem.h deleted file mode 100644 index 2023880b..00000000 --- a/include/Engine/Input/InputSystem.h +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef InputSystem_h__ -#define InputSystem_h__ - -#include -#include - -#include "Core/System.h" -#include "Core/EKeyUp.h" -#include "Core/EKeyDown.h" -#include "Core/EMousePress.h" -#include "Core/EMouseRelease.h" -#include "Core/EGamepadAxis.h" -#include "Core/EGamepadButton.h" -#include "Core/Util/EnumClassHash.h" -#include "EBindKey.h" -#include "EBindMouseButton.h" -#include "EBindGamepadAxis.h" -#include "EBindGamepadButton.h" -#include "EInputCommand.h" - -namespace Systems -{ - -class InputSystem : public System -{ -public: - InputSystem(World* world, std::shared_ptr eventBroker) - : System(world, eventBroker) - { } - - void RegisterComponents(ComponentFactory* cf) override; - void Initialize() override; - - void Update(double dt) override; - -private: - std::unordered_map> m_CommandKeyboardValues; // command string -> keyboard key value for command - std::unordered_map> m_CommandMouseButtonValues; // command string -> mouse button value for command - std::unordered_map> m_CommandGamepadAxisValues; // command string -> gamepad axis value for command - std::unordered_map> m_CommandGamepadButtonValues; // command string -> gamepad button value for command - // Input binding tables - std::unordered_multimap> m_KeyBindings; // GLFW_KEY... -> command string & value - std::unordered_multimap> m_MouseButtonBindings; // GLFW_MOUSE_BUTTON... -> command string - std::unordered_multimap, EnumClassHash> m_GamepadAxisBindings; // Gamepad::Axis -> command string & value - std::unordered_multimap, EnumClassHash> m_GamepadButtonBindings; // Gamepad::Button -> command string - - // Input events - EventRelay m_EKeyDown; - bool OnKeyDown(const Events::KeyDown &event); - EventRelay m_EKeyUp; - bool OnKeyUp(const Events::KeyUp &event); - EventRelay m_EMousePress; - bool OnMousePress(const Events::MousePress &event); - EventRelay m_EMouseRelease; - bool OnMouseRelease(const Events::MouseRelease &event); - EventRelay m_EGamepadAxis; - bool OnGamepadAxis(const Events::GamepadAxis &event); - EventRelay m_EGamepadButtonDown; - bool OnGamepadButtonDown(const Events::GamepadButtonDown &event); - EventRelay m_EGamepadButtonUp; - bool OnGamepadButtonUp(const Events::GamepadButtonUp &event); - // Input binding events - EventRelay m_EBindKey; - bool OnBindKey(const Events::BindKey &event); - EventRelay m_EBindMouseButton; - bool OnBindMouseButton(const Events::BindMouseButton &event); - EventRelay m_EBindGamepadAxis; - bool OnBindGamepadAxis(const Events::BindGamepadAxis &event); - EventRelay m_EBindGamepadButton; - bool OnBindGamepadButton(const Events::BindGamepadButton &event); - - float GetCommandTotalValue(std::string command); - void PublishCommand(int playerID, std::string command, float value); -}; - -} - -#endif diff --git a/include/Engine/Input/KeyboardInputHandler.h b/include/Engine/Input/KeyboardInputHandler.h new file mode 100644 index 00000000..49597fce --- /dev/null +++ b/include/Engine/Input/KeyboardInputHandler.h @@ -0,0 +1,28 @@ +#ifndef KeyboardInputHandler_h__ +#define KeyboardInputHandler_h__ + +#include +#include "InputHandler.h" +#include "Core/EKeyDown.h" +#include "Core/EKeyUp.h" + +class KeyboardInputHandler : public InputHandler +{ +public: + KeyboardInputHandler(EventBroker* eventBroker, InputProxy* inputProxy); + + bool BindOrigin(std::string origin, std::string command, float value) override; + virtual float GetCommandValue(std::string command) override; + +private: + std::unordered_map m_OriginKeyCodes; + std::unordered_map> m_KeyBindings; // GLFW_KEY... -> command string & value + std::unordered_map m_CommandValues; + + EventRelay m_EKeyDown; + bool OnKeyDown(const Events::KeyDown& e); + EventRelay m_EKeyUp; + bool OnKeyUp(const Events::KeyUp& e); +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Input/MouseInputHandler.h b/include/Engine/Input/MouseInputHandler.h new file mode 100644 index 00000000..b9193a2d --- /dev/null +++ b/include/Engine/Input/MouseInputHandler.h @@ -0,0 +1,38 @@ +#ifndef MouseInputHandler_h__ +#define MouseInputHandler_h__ + +#include +#include "InputHandler.h" +#include "Core/EMousePress.h" +#include "Core/EMouseRelease.h" +#include "Core/EMouseMove.h" + +class MouseInputHandler : public InputHandler +{ +public: + MouseInputHandler(EventBroker* eventBroker, InputProxy* inputProxy); + + bool BindOrigin(std::string origin, std::string command, float value) override; + virtual float GetCommandValue(std::string command) override; + +private: + std::unordered_map m_OriginCodes; + std::unordered_map m_OriginAxes; + std::unordered_map> m_Bindings; // GLFW_MOUSE_BUTTON... -> command string & value + std::unordered_map> m_Axes; // Axis -> command string & value + std::unordered_map m_CommandValues; + std::unordered_map m_ContinuousCommandValues; + + EventRelay m_EMousePress; + bool OnMousePress(const Events::MousePress& e); + EventRelay m_EMouseRelease; + bool OnMouseRelease(const Events::MouseRelease& e); + EventRelay m_EMouseMove; + bool OnMouseMove(const Events::MouseMove& e); + + bool hasOrigin(std::string origin); + + +}; + +#endif diff --git a/include/Engine/Network/Client.h b/include/Engine/Network/Client.h index af7c9072..95888b1f 100644 --- a/include/Engine/Network/Client.h +++ b/include/Engine/Network/Client.h @@ -1,14 +1,84 @@ #ifndef Client_h__ #define Client_h__ -#include +#include +#include -class Client +#include +#include + +#include "Network/Network.h" +#include "Network/MessageType.h" +#include "Network/PlayerDefinition.h" +#include "Network/SnapshotDefinitions.h" +#include "Core/World.h" +#include "Core/EventBroker.h" +#include "Core/ConfigFile.h" +#include "Input/EInputCommand.h" + +class Client : public Network { - Client(); - ~Client(); +public: + Client(ConfigFile* config); + ~Client(); + void Start(World* world, EventBroker* eventBroker) override; + void Update() override; + void Close(); +private: + // Assio UDP logic + boost::asio::ip::udp::endpoint m_ReceiverEndpoint; + boost::asio::io_service m_IOService; + boost::asio::ip::udp::socket m_Socket; + // Sending message to server logic + int bytesRead = -1; + char readBuf[1024] = { 0 }; + int snapshotInterval = 33; + std::clock_t previousSnapshotMessage = std::clock(); + // Packet loss logic + unsigned int m_PacketID = 0; + unsigned int m_PreviousPacketID = 0; + unsigned int m_SendPacketID = 0; + + // Game logic + World* m_World; + std::string m_PlayerName; + int m_PlayerID = -1; + + // Network logic + PlayerDefinition m_PlayerDefinitions[MAXCONNECTIONS]; + SnapshotDefinitions m_NextSnapshot; + bool m_ThreadIsRunning = true; + double m_DurationOfPingTime; + std::clock_t m_StartPingTime; + // Use to check if we should send disconnect message + // if game is turned of by closing window. + bool m_WasStarted = false; + + // Private member functions + void readFromServer(); + void sendSnapshotToServer(); + int receive(char* data, size_t length); + void send(Packet& packet); + void connect(); + void disconnect(); + void ping(); + void moveMessageHead(char*& data, size_t& length, size_t stepSize); + void parseMessageType(Packet& packet); + void parseEventMessage(Packet& packet); + void parseConnect(Packet& packet); + void parsePing(); + void parseServerPing(); + void parseSnapshot(Packet& packet); + void identifyPacketLoss(); + bool isConnected(); + EntityID createPlayer(); + + // Events + EventBroker* m_EventBroker; + EventRelay m_EInputCommand; + bool OnInputCommand(const Events::InputCommand &e); }; #endif diff --git a/include/Engine/Network/MessageType.h b/include/Engine/Network/MessageType.h new file mode 100644 index 00000000..8d09c7ee --- /dev/null +++ b/include/Engine/Network/MessageType.h @@ -0,0 +1,17 @@ +#ifndef MessageType_h__ +#define MessageType_h__ + +// Message types used by both server and client. +// Used to determine what type of message was sent. +enum class MessageType +{ + Connect, + Disconnect, + ClientPing, + ServerPing, + Message, + Snapshot, + Event, +}; + +#endif diff --git a/include/Engine/Network/Network.h b/include/Engine/Network/Network.h new file mode 100644 index 00000000..0f7baefe --- /dev/null +++ b/include/Engine/Network/Network.h @@ -0,0 +1,19 @@ +#ifndef Network_h__ +#define Network_h__ + +#include "Core/World.h" +#include "Core/EventBroker.h" +#include "Network/Packet.h" + +#define MAXCONNECTIONS 8 +#define INPUTSIZE 128 + +class Network +{ +public: + virtual ~Network() { }; + virtual void Start(World* m_world, EventBroker *eventBroker) = 0; + virtual void Update() = 0; +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Network/Packet.h b/include/Engine/Network/Packet.h new file mode 100644 index 00000000..daf39962 --- /dev/null +++ b/include/Engine/Network/Packet.h @@ -0,0 +1,61 @@ +#ifndef Packet_h__ +#define Packet_h__ + +#include +#include "Network/MessageType.h" +#include "Core/Util/Logging.h" + +// Defines the +class Packet +{ +public: + // arg1: Type of message (Connect, Disconnect...) + // arg2: PacketID for identifying packet loss. + Packet(MessageType type, unsigned int& packetID); + // Used to create packet from already existing data buffer. + Packet(char* data, const int sizeOfPacket); + + ~Packet(); + // Add primitive types like int, float, char... + template + void WritePrimitive(T val) + { + // Check if we are trying to add more than the package can fit. + if (m_MaxPacketSize < m_Offset + sizeof(T)) { + LOG_WARNING("Packet AddPrimitive(): You are trying to add more than we have allocated for!"); + } + memcpy(m_Data + m_Offset, &val, sizeof(T)); + m_Offset += sizeof(T); + } + // Pops the first element as if it was a primitive. + template + T ReadPrimitive() + { + if (m_Offset < m_ReturnDataOffset + sizeof(T)) { + LOG_WARNING("Packet PopFrontPrimitive(): You are trying to remove more than what exists in this packet!"); + return -1; + } + T returnValue; + memcpy(&returnValue, m_Data + m_ReturnDataOffset, sizeof(T)); + m_ReturnDataOffset += sizeof(T); + return returnValue; + } + // Add a string to the message + void WriteString(std::string str); + // Add data to the message + void WriteData(char* data, int sizeOfData); + // Pops the first element as if it was a string. + std::string ReadString(); + char* ReadData(int SizeOfData); + + int Size() { return m_Offset; }; + char* Data() { return m_Data; }; + +private: + char* m_Data; + unsigned int m_ReturnDataOffset = 0; + int m_Offset = 0; + unsigned int m_MaxPacketSize = 128; +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Network/PlayerDefinition.h b/include/Engine/Network/PlayerDefinition.h new file mode 100644 index 00000000..dbacda95 --- /dev/null +++ b/include/Engine/Network/PlayerDefinition.h @@ -0,0 +1,11 @@ +#ifndef PlayerDefinition_h__ +#define PlayerDefinition_h__ +#include + +struct PlayerDefinition { + int EntityID = -1; + std::string Name = ""; + boost::asio::ip::udp::endpoint Endpoint; +}; + +#endif diff --git a/include/Engine/Network/Server.h b/include/Engine/Network/Server.h index bb198b7e..5c1ac1fb 100644 --- a/include/Engine/Network/Server.h +++ b/include/Engine/Network/Server.h @@ -1,12 +1,85 @@ #ifndef Server_h__ #define Server_h__ -#include +#include +#include -class Server +#include +#include + +#include "Network/MessageType.h" +#include "Network/PlayerDefinition.h" +#include "Core/World.h" +#include "Core/EventBroker.h" +#include "Network/Network.h" + +class Server : public Network { - Server(); - ~Server(); +public: + Server(); + ~Server(); + void Start(World* m_world, EventBroker *eventBroker) override; + void Update() override; + void Close(); + +private: + // UDP logic + boost::asio::ip::udp::endpoint m_ReceiverEndpoint; + boost::asio::io_service m_IOService; + boost::asio::ip::udp::socket m_Socket; + PlayerDefinition m_PlayerDefinitions[MAXCONNECTIONS]; + + // Sending messages to client logic + char readBuffer[1024] = { 0 }; + int bytesRead = 0; + // time for previouse message + std::clock_t previousePingMessage = std::clock(); + std::clock_t previousSnapshotMessage = std::clock(); + std::clock_t timOutTimer = std::clock(); + // How often we send messages (milliseconds) + int intervalMs = 1000; + int snapshotInterval = 50; + int checkTimeOutInterval = 100; + + //Timers + std::clock_t m_StartPingTime; + std::clock_t m_StopTimes[8]; + + // Game logic + World* m_World; + EventBroker* m_EventBroker; + // vec.size() = ammount of players to create, stores playerID's + std::vector m_PlayersToCreate; + + // Packet loss logic + unsigned int m_PacketID; + unsigned int m_PreviousPacketID; + unsigned int m_SendPacketID; + + // Close logic + bool m_ThreadIsRunning = true; + + // Private member functions + int receive(char* data, size_t length); + void readFromClients(); + void send(Packet& packet, int playerID); + void send(Packet& packet); + void moveMessageHead(char*& data, size_t& length, size_t stepSize); + void broadcast(std::string message); + void broadcast(Packet& packet); + void sendSnapshot(); + void sendPing(); + void checkForTimeOuts(); + void disconnect(int i); + void parseMessageType(Packet& packet); + void parseEvent(Packet& packet); + void parseConnect(Packet& packet); + void parseDisconnect(); + void parseClientPing(); + void parseServerPing(); + void parseSnapshot(Packet& packet); + void identifyPacketLoss(); + EntityID createPlayer(); }; #endif diff --git a/include/Engine/Network/SnapshotDefinitions.h b/include/Engine/Network/SnapshotDefinitions.h new file mode 100644 index 00000000..b643d18c --- /dev/null +++ b/include/Engine/Network/SnapshotDefinitions.h @@ -0,0 +1,12 @@ +#ifndef SnapshotDefinitions_h__ +#define SnapshotDefinitions_h__ + +struct SnapshotDefinitions +{ + // "+Forward" is 8 characters * sizeof(char) = 8 + std::string InputForward; + // "+Right" is 6 characters * sizeof(char) = 6 + std::string InputRight; +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Rendering/DebugCameraInputController.h b/include/Engine/Rendering/DebugCameraInputController.h new file mode 100644 index 00000000..614b071c --- /dev/null +++ b/include/Engine/Rendering/DebugCameraInputController.h @@ -0,0 +1,63 @@ +#include +#include "../Input/FirstPersonInputController.h" + +template +class DebugCameraInputController : public FirstPersonInputController +{ +public: + DebugCameraInputController(EventBroker* eventBroker, unsigned int playerID) + : FirstPersonInputController(eventBroker, playerID) + { } + + const glm::vec3 Position() const { return m_Position; } + void SetBaseSpeed(float speed) { m_BaseSpeed = speed; } + + virtual bool OnCommand(const Events::InputCommand& e) override + { + ImGuiIO& io = ImGui::GetIO(); + + if (e.Command == "PrimaryFire") { + if (e.Value > 0) { + if (!io.WantCaptureMouse) { + LockMouse(); + } + } else { + UnlockMouse(); + } + return false; + } + + if (!io.WantCaptureKeyboard) { + if (e.Command == "Right") { + float value = std::max(-1.f, std::min(e.Value, 1.f)); + m_Velocity.x = value; + } + if (e.Command == "Forward") { + float value = std::max(-1.f, std::min(e.Value, 1.f)); + m_Velocity.z = -value; + } + if (e.Command == "Sprint") { + if (e.Value > 0.f) { + m_Speed = m_BaseSpeed * 2.f * (e.Value); + } else { + m_Speed = m_BaseSpeed; + } + } + } + + return FirstPersonInputController::OnCommand(e); + } + + void Update(double dt) + { + if (glm::length2(m_Velocity) > 0) { + m_Position += m_Orientation * (glm::normalize(m_Velocity) * m_Speed * (float)dt); + } + } + +protected: + glm::vec3 m_Position = glm::vec3(0, 0, 0); + glm::vec3 m_Velocity = glm::vec3(0, 0, 0); + float m_BaseSpeed = 2.0f; + float m_Speed = m_BaseSpeed; +}; \ No newline at end of file diff --git a/include/Engine/Rendering/DrawFinalPass.h b/include/Engine/Rendering/DrawFinalPass.h new file mode 100644 index 00000000..1a201daf --- /dev/null +++ b/include/Engine/Rendering/DrawFinalPass.h @@ -0,0 +1,38 @@ +#ifndef DrawFinalPass_h__ +#define DrawFinalPass_h__ + +#include "IRenderer.h" +#include "DrawFinalPassState.h" +#include "LightCullingPass.h" +#include "FrameBuffer.h" +#include "ShaderProgram.h" +#include "Util/UnorderedMapVec2.h" +#include "Texture.h" + +class DrawFinalPass +{ +public: + DrawFinalPass(IRenderer* renderer, LightCullingPass* lightCullingPass); + ~DrawFinalPass() { } + void InitializeTextures(); + void InitializeFrameBuffers(); + void InitializeShaderPrograms(); + + void Draw(RenderQueueCollection& rq); + + //Getters + + +private: + void GenerateTexture(GLuint* texture, GLenum wrapping, GLenum filtering, glm::vec2 dimensions, GLint internalFormat, GLint format, GLenum type) const; + + Texture* m_WhiteTexture; + + const IRenderer* m_Renderer; + const LightCullingPass* m_LightCullingPass; + + ShaderProgram* m_ForwardPlusProgram; + +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Rendering/DrawFinalPassState.h b/include/Engine/Rendering/DrawFinalPassState.h new file mode 100644 index 00000000..72d8e392 --- /dev/null +++ b/include/Engine/Rendering/DrawFinalPassState.h @@ -0,0 +1,15 @@ +#ifndef DrawFinalPassState_h__ +#define DrawFinalPassState_h__ + +#include "Rendering/RenderState.h" + +class DrawFinalPassState : public RenderState +{ +public: + DrawFinalPassState(); + ~DrawFinalPassState(); +private: + +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Rendering/DrawScenePass.h b/include/Engine/Rendering/DrawScenePass.h new file mode 100644 index 00000000..782c5a49 --- /dev/null +++ b/include/Engine/Rendering/DrawScenePass.h @@ -0,0 +1,36 @@ +#ifndef DrawScenePass_h__ +#define DrawScenePass_h__ + +#include "IRenderer.h" +#include "DrawScenePassState.h" +#include "FrameBuffer.h" +#include "ShaderProgram.h" +#include "Util/UnorderedMapVec2.h" +#include "Texture.h" + +class DrawScenePass +{ +public: + DrawScenePass(IRenderer* renderer); + ~DrawScenePass() { } + void InitializeTextures(); + void InitializeFrameBuffers(); + void InitializeShaderPrograms(); + + void Draw(RenderQueueCollection& rq); + + //Getters + + +private: + void GenerateTexture(GLuint* texture, GLenum wrapping, GLenum filtering, glm::vec2 dimensions, GLint internalFormat, GLint format, GLenum type) const; + + Texture* m_WhiteTexture; + + const IRenderer* m_Renderer; + + ShaderProgram* m_BasicForwardProgram; + +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Rendering/DrawScenePassState.h b/include/Engine/Rendering/DrawScenePassState.h new file mode 100644 index 00000000..7ce74006 --- /dev/null +++ b/include/Engine/Rendering/DrawScenePassState.h @@ -0,0 +1,15 @@ +#ifndef DrawScenePassState_h__ +#define DrawScenePassState_h__ + +#include "Rendering/RenderState.h" + +class DrawScenePassState : public RenderState +{ +public: + DrawScenePassState(); + ~DrawScenePassState(); +private: + +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Rendering/EPicking.h b/include/Engine/Rendering/EPicking.h new file mode 100644 index 00000000..85c4b629 --- /dev/null +++ b/include/Engine/Rendering/EPicking.h @@ -0,0 +1,74 @@ +#ifndef Events_Picking_h__ +#define Events_Picking_h__ + +#include "../OpenGL.h" +#include "../GLM.h" + +#include "../Core/EventBroker.h" +#include "Util/ScreenCoords.h" +#include "FrameBuffer.h" +#include "../Core/Entity.h" +#include "Util/UnorderedMapVec2.h" + +namespace Events +{ + +/** Thrown Every frame, use functions to pick*/ +struct Picking : Event +{ +public: + Picking(FrameBuffer* pickingBuffer, GLuint* depthBuffer, glm::mat4 projectionMatrix, glm::mat4 viewMatrix, Rectangle resolution, const std::unordered_map* pickingColorsToEntity) + : PickingBuffer(pickingBuffer) + , DepthBuffer(depthBuffer) + , ProjectionMatrix(projectionMatrix) + , ViewMatrix(viewMatrix) + , Resolution(resolution) + , PickingColorsToEntity(pickingColorsToEntity) + { } + + + + struct PickData + { + //Picked Entity + EntityID Entity; + //World position of the "pick" + glm::vec3 Position; + // Depth + float Depth; + }; + + PickData Pick(glm::vec2 screenCoord) const + { + PickData pickData; + + // Invert screen y coordinate + screenCoord.y = Resolution.Height - screenCoord.y; + ScreenCoords::PixelData data = ScreenCoords::ToPixelData(screenCoord, PickingBuffer, *DepthBuffer); + pickData.Depth = data.Depth; + + auto it = PickingColorsToEntity->find(glm::vec2(data.Color[0], data.Color[1])); + if (it != PickingColorsToEntity->end()) { + pickData.Entity = it->second; + } else { + pickData.Entity = 0; + } + pickData.Position = ScreenCoords::ToWorldPos(screenCoord.x, screenCoord.y, data.Depth, Resolution, ProjectionMatrix, ViewMatrix); + + return pickData; + } + + +private: + FrameBuffer* PickingBuffer; + GLuint* DepthBuffer; + const glm::mat4 ProjectionMatrix; + const glm::mat4 ViewMatrix; + const Rectangle Resolution; + const std::unordered_map* PickingColorsToEntity; + +}; + +} + +#endif diff --git a/include/Engine/Rendering/IRenderer.h b/include/Engine/Rendering/IRenderer.h index 1acb2454..5dce14c7 100644 --- a/include/Engine/Rendering/IRenderer.h +++ b/include/Engine/Rendering/IRenderer.h @@ -35,7 +35,7 @@ public: virtual void Draw(RenderQueueCollection& rq) = 0; protected: - Rectangle m_Resolution = Rectangle(1280, 720); + Rectangle m_Resolution = Rectangle::Rectangle(1280, 720); bool m_Fullscreen = false; bool m_VSYNC = false; int m_GLVersion[2]; diff --git a/include/Engine/Rendering/ImGuiRenderPass.h b/include/Engine/Rendering/ImGuiRenderPass.h new file mode 100644 index 00000000..e9d359ba --- /dev/null +++ b/include/Engine/Rendering/ImGuiRenderPass.h @@ -0,0 +1,77 @@ +#include +#include "../OpenGL.h" +#include "IRenderer.h" +#include "RenderState.h" +#include "../Core/EventBroker.h" +#include "../Core/EMousePress.h" +#include "../Core/EMouseRelease.h" +#include "../Core/EMouseMove.h" +#include "../Core/EMouseScroll.h" +#include "../Core/EKeyDown.h" +#include "../Core/EKeyUp.h" +#include "../Core/EKeyboardChar.h" + +class ImGuiRenderState : public RenderState +{ +public: + ImGuiRenderState() + : RenderState() + { + BindFramebuffer(0); + Enable(GL_BLEND); + BlendEquation(GL_FUNC_ADD); + BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + Disable(GL_CULL_FACE); + Disable(GL_DEPTH_TEST); + Enable(GL_SCISSOR_TEST); + } +}; + +class ImGuiRenderPass +{ +public: + ImGuiRenderPass(IRenderer* renderer, EventBroker* eventBroker); + + void Update(double dt); + void Draw(); + +private: + IRenderer* m_Renderer; + EventBroker* m_EventBroker; + + GLFWwindow* g_Window; + double g_DeltaTime = 0.0; + float g_MouseWheel = 0.f; + GLuint g_FontTexture; + int g_ShaderHandle; + int g_VertHandle; + int g_FragHandle; + int g_AttribLocationTex; + int g_AttribLocationProjMtx; + int g_AttribLocationPosition; + int g_AttribLocationUV; + int g_AttribLocationColor; + GLuint g_VboHandle; + GLuint g_VaoHandle; + GLuint g_ElementsHandle; + + EventRelay m_EMousePress; + bool OnMousePress(const Events::MousePress& e); + EventRelay m_EMouseRelease; + bool OnMouseRelease(const Events::MouseRelease& e); + EventRelay m_EMouseMove; + bool OnMouseMove(const Events::MouseMove& e); + EventRelay m_EMouseScroll; + bool OnMouseScroll(const Events::MouseScroll& e); + EventRelay m_EKeyDown; + bool OnKeyDown(const Events::KeyDown& e); + EventRelay m_EKeyUp; + bool OnKeyUp(const Events::KeyUp& e); + EventRelay m_EKeyboardChar; + bool OnKeyboardChar(const Events::KeyboardChar& e); + + bool createDeviceObjects(); + bool createFontsTexture(); + + void newFrame(); +}; \ No newline at end of file diff --git a/include/Engine/Rendering/LightCullingPass.h b/include/Engine/Rendering/LightCullingPass.h new file mode 100644 index 00000000..e08aacdf --- /dev/null +++ b/include/Engine/Rendering/LightCullingPass.h @@ -0,0 +1,78 @@ +#ifndef LightCullingPass_h__ +#define LightCullingPass_h__ + +#define TILE_SIZE 16 +#define NUM_LIGHTS 1000 + +#include "IRenderer.h" +#include "LightCullingPassState.h" +#include "ShaderProgram.h" + + +class LightCullingPass +{ +public: + LightCullingPass(IRenderer* renderer); + ~LightCullingPass(); + + void GenerateNewFrustum(); + void CullLights(); + void FillLightList(RenderQueueCollection& rq); + + GLuint FrustumSSBO() const { return m_FrustumSSBO; } + GLuint LightSSBO() const { return m_LightSSBO; } + GLuint LightGridSSBO() const { return m_LightGridSSBO; } + GLuint LightOffsetSSBO() const { return m_LightOffsetSSBO; } + GLuint LightIndexSSBO() const { return m_LightIndexSSBO; } +private: + + void InitializeSSBOs(); + void InitializeShaderPrograms(); + + const IRenderer* m_Renderer; + + GLuint m_FrustumSSBO = 0; + GLuint m_LightSSBO = 0; + GLuint m_LightGridSSBO = 0; + GLuint m_LightOffsetSSBO = 0; + GLuint m_LightIndexSSBO = 0; + + ShaderProgram* m_CalculateFrustumProgram; + ShaderProgram* m_LightCullProgram; + + struct Plane { + glm::vec3 Normal; + float d; + }; + + struct Frustum { + Plane Planes[4]; + }; + Frustum m_Frustums[80*45]; //TODO: Renderer: Make this change with resolution + + //This should be a component + struct PointLight { + glm::vec4 Position = glm::vec4(0.f); + glm::vec4 Color = glm::vec4(1.f); + float Radius = 5.f; + float Intensity = 0.8f; + float Falloff = 0.3f; + float Padding = 1337; + }; + std::vector m_PointLights; + + struct LightGrid { + float Start; + float Amount; + glm::vec2 Padding; + }; + + LightGrid m_LightGrid[80*45]; //TODO: Renderer: Make this change with resolution + + int m_LightOffset = 0; + + float m_LightIndex[80*45*200]; //TODO: Renderer: Make this change with resolution +}; + + +#endif \ No newline at end of file diff --git a/include/Engine/Rendering/LightCullingPassState.h b/include/Engine/Rendering/LightCullingPassState.h new file mode 100644 index 00000000..e69de29b diff --git a/include/Engine/Rendering/PickingPass.h b/include/Engine/Rendering/PickingPass.h new file mode 100644 index 00000000..0c1261ce --- /dev/null +++ b/include/Engine/Rendering/PickingPass.h @@ -0,0 +1,51 @@ +#ifndef PickingPass_h__ +#define PickingPass_h__ + + + +#include "IRenderer.h" +#include "PickingPassState.h" +#include "FrameBuffer.h" +#include "ShaderProgram.h" +#include "Util/UnorderedMapVec2.h" +#include "../Core/EventBroker.h" +#include "EPicking.h" + + + +class PickingPass +{ +public: + PickingPass(IRenderer* renderer, EventBroker* eb); + ~PickingPass(); + void InitializeTextures(); + void InitializeFrameBuffers(); + void InitializeShaderPrograms(); + + void Draw(RenderQueueCollection& rq); + + //Getters + const ShaderProgram& PickingProgram() const { return *m_PickingProgram; } + const std::unordered_map& PickingColorsToEntity() const { return m_PickingColorsToEntity; } + GLuint PickingTexture() const { return m_PickingTexture; } + GLuint DepthBuffer() const { return m_DepthBuffer; } + const FrameBuffer& PickingBuffer() const { return m_PickingBuffer; } + +private: + void GenerateTexture(GLuint* texture, GLenum wrapping, GLenum filtering, glm::vec2 dimensions, GLint internalFormat, GLint format, GLenum type) const; + + EventBroker* m_EventBroker; + + const IRenderer* m_Renderer; + + ShaderProgram* m_PickingProgram; + + std::unordered_map m_PickingColorsToEntity; + + GLuint m_PickingTexture; + GLuint m_DepthBuffer; + + FrameBuffer m_PickingBuffer; +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Rendering/PickingPassState.h b/include/Engine/Rendering/PickingPassState.h new file mode 100644 index 00000000..4f6bfe40 --- /dev/null +++ b/include/Engine/Rendering/PickingPassState.h @@ -0,0 +1,15 @@ +#ifndef PickingPassState_h__ +#define PickingPassState_h__ + +#include "Rendering/RenderState.h" + +class PickingPassState : public RenderState +{ +public: + PickingPassState(GLuint frameBuffer); + ~PickingPassState(); + +private: +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Rendering/RenderQueue.h b/include/Engine/Rendering/RenderQueue.h index 982b1813..44d5e172 100644 --- a/include/Engine/Rendering/RenderQueue.h +++ b/include/Engine/Rendering/RenderQueue.h @@ -7,7 +7,7 @@ #include "../Common.h" #include "../GLM.h" #include "../Core/Util/Rectangle.h" -#include "../Core/EntityWrapper.h" +#include "../Core/Entity.h" class Model; class Skeleton; @@ -82,11 +82,12 @@ struct SpriteJob : RenderJob struct PointLightJob : RenderJob { - glm::vec3 Position; - glm::vec3 SpecularColor = glm::vec3(1, 1, 1); - glm::vec3 DiffuseColor = glm::vec3(1, 1, 1); - float Radius = 1.f; - float Intensity = 0.8f; + glm::vec4 Position; + glm::vec4 Color; + float Radius; + float Intensity; + float Falloff; + float padding = 123; void CalculateHash() override { diff --git a/include/Engine/Rendering/RenderQueueFactory.h b/include/Engine/Rendering/RenderQueueFactory.h index 918692ac..be2c55ca 100644 --- a/include/Engine/Rendering/RenderQueueFactory.h +++ b/include/Engine/Rendering/RenderQueueFactory.h @@ -13,8 +13,12 @@ public: RenderQueueFactory(); void Update(World* world); - RenderQueueCollection RenderQueues() const { return m_RenderQueues; } + + static glm::vec3 AbsolutePosition(World* world, EntityID entity); + static glm::quat AbsoluteOrientation(World* world, EntityID entity); + static glm::vec3 AbsoluteScale(World* world, EntityID entity); + private: RenderQueueCollection m_RenderQueues; @@ -22,7 +26,6 @@ private: void FillLights(World* world, RenderQueue* renderQueue); glm::mat4 ModelMatrix(World* world, EntityID entity); - glm::vec3 GetAbsolutePosition(World* world, ComponentWrapper transformComponent); }; #endif \ No newline at end of file diff --git a/include/Engine/Rendering/RenderState.h b/include/Engine/Rendering/RenderState.h new file mode 100644 index 00000000..c6eb8775 --- /dev/null +++ b/include/Engine/Rendering/RenderState.h @@ -0,0 +1,27 @@ +#ifndef RenderState_h__ +#define RenderState_h__ + +#include +#include "../Common.h" +#include "../OpenGL.h" +#include "../GLM.h" + +class RenderState +{ +public: + RenderState() = default; + ~RenderState(); + + bool Enable(GLenum cap); + bool Disable(GLenum cap); + bool CullFace(GLenum mode); + bool ClearColor(glm::vec4 color); + bool Clear(GLbitfield mask); + bool BindFramebuffer(GLint framebuffer); + bool BlendEquation(GLenum mode); + bool BlendFunc(GLenum sfactor, GLenum dfactor); + +private: + std::vector> m_ResetFunctions; +}; +#endif \ No newline at end of file diff --git a/include/Engine/Rendering/Renderer.h b/include/Engine/Rendering/Renderer.h index b1b76468..c9f5d76f 100644 --- a/include/Engine/Rendering/Renderer.h +++ b/include/Engine/Rendering/Renderer.h @@ -10,48 +10,61 @@ #include "Util/UnorderedMapVec2.h" #include "FrameBuffer.h" #include "../Core/World.h" +#include "PickingPass.h" +#include "DrawScenePass.h" +#include "DebugCameraInputController.h" +#include "LightCullingPass.h" +#include "DrawFinalPass.h" + +#include "../Core/EventBroker.h" +#include "EPicking.h" +#include "ImGuiRenderPass.h" class Renderer : public IRenderer { public: - virtual void Initialize() override; - virtual void Update(double dt) override; - virtual void Draw(RenderQueueCollection& rq) override; + Renderer(EventBroker* eventBroker) + : m_EventBroker(eventBroker) + { } + + virtual void Initialize() override; + virtual void Update(double dt) override; + virtual void Draw(RenderQueueCollection& rq) override; private: - //----------------------Variables----------------------// - Texture* m_ErrorTexture; - Texture* m_WhiteTexture; - float m_CameraMoveSpeed; - FrameBuffer m_PickingBuffer; - GLuint m_PickingTexture; - GLuint m_DepthBuffer; + //----------------------Variables----------------------// + EventBroker* m_EventBroker; + + std::shared_ptr> m_DebugCameraInputController; + + Texture* m_ErrorTexture; + Texture* m_WhiteTexture; + float m_CameraMoveSpeed; Model* m_ScreenQuad; Model* m_UnitQuad; Model* m_UnitSphere; + DrawScenePass* m_DrawScenePass; + PickingPass* m_PickingPass; + LightCullingPass* m_LightCullingPass; + ImGuiRenderPass* m_ImGuiRenderPass; + DrawFinalPass* m_DrawFinalPass; - - std::unordered_map m_PickingColorsToEntity; - - //----------------------Functions----------------------// - void InitializeWindow(); - void InitializeShaders(); + //----------------------Functions----------------------// + void InitializeWindow(); + void InitializeShaders(); void InitializeTextures(); - void InitializeFrameBuffers(); + void InitializeRenderPasses(); //TODO: Renderer: Get InputUpdate out of renderer - void InputUpdate(double dt); - void PickingPass(RenderQueueCollection& rq); + void InputUpdate(double dt); + //void PickingPass(RenderQueueCollection& rq); void DrawScreenQuad(GLuint textureToDraw); - void DrawScene(RenderQueueCollection& rq); - void GenerateTexture(GLuint* texture, GLenum wrapping, GLenum filtering, glm::vec2 dimensions, GLint internalFormat, GLint format, GLenum type); //--------------------ShaderPrograms-------------------// - ShaderProgram m_BasicForwardProgram; - ShaderProgram m_PickingProgram; - ShaderProgram m_DrawScreenQuadProgram; + ShaderProgram* m_BasicForwardProgram; + ShaderProgram* m_DrawScreenQuadProgram; }; #endif \ No newline at end of file diff --git a/include/Engine/Rendering/ShaderProgram.h b/include/Engine/Rendering/ShaderProgram.h index 1b87650e..ad01c029 100644 --- a/include/Engine/Rendering/ShaderProgram.h +++ b/include/Engine/Rendering/ShaderProgram.h @@ -1,6 +1,9 @@ +#ifndef ShaderProgram_h__ +#define ShaderProgram_h__ #include "../Common.h" #include "../OpenGL.h" +#include "../Core/ResourceManager.h" #include class Shader @@ -60,11 +63,13 @@ public: : ShaderType(fileName) { } }; -class ShaderProgram +class ShaderProgram : public Resource { -public: - ShaderProgram() + friend class ResourceManager; +private: + ShaderProgram(std::string) : m_ShaderProgramHandle(0) { } +public: ~ShaderProgram(); void AddShader(std::shared_ptr shader); @@ -79,3 +84,5 @@ private: GLuint m_ShaderProgramHandle; std::vector> m_Shaders; }; + +#endif \ No newline at end of file diff --git a/include/Engine/Rendering/Util/GLError.h b/include/Engine/Rendering/Util/GLError.h index 8202f433..754623d1 100644 --- a/include/Engine/Rendering/Util/GLError.h +++ b/include/Engine/Rendering/Util/GLError.h @@ -9,7 +9,7 @@ inline bool _GLERROR(const char* info, const char* file, const char* func, unsig GLenum error = glGetError(); if (error != GL_NO_ERROR) { - _LOG(LOG_LEVEL_ERROR, file, func, line, "GL Error: %s %i %s", info, error, gluErrorString(error)); + _LOG(LOG_LEVEL_ERROR, file, func, line, "GL Error: %s, %i, %s", info, error, gluErrorString(error)); return true; } diff --git a/include/Engine/Rendering/Util/ScreenCoords.h b/include/Engine/Rendering/Util/ScreenCoords.h index 22c5b9e0..92e92bdc 100644 --- a/include/Engine/Rendering/Util/ScreenCoords.h +++ b/include/Engine/Rendering/Util/ScreenCoords.h @@ -11,6 +11,13 @@ class ScreenCoords { public: ScreenCoords() = delete; + + struct PixelData + { + int Color[2]; + float Depth; + }; + //Return world position from given screenspace coordinates and depth value in viewspace. static glm::vec3 ToWorldPos(glm::vec2 screenCoord, float depth, Rectangle resolution, glm::mat4 cameraProjectionMat, glm::mat4 cameraViewMat); static glm::vec3 ToWorldPos(float x, float y, float depth, Rectangle resolution, glm::mat4 cameraProjectionMat, glm::mat4 cameraViewMat); @@ -18,8 +25,8 @@ public: static glm::vec3 ToWorldPos(float x, float y, float depth, float screenWidth, float screenHeight, glm::mat4 cameraProjectionMat, glm::mat4 cameraViewMat); //Return data from the given buffers at the coordinates given in screenspace. Buffer should probably have a texture that covers the screen. //Data is given as R = x, B = y, and - static glm::vec3 ToPixelData(glm::vec2 screenCoord, FrameBuffer* PickDataBuffer, GLuint DepthBuffer); - static glm::vec3 ToPixelData(float x, float y, FrameBuffer* PickDataBuffer, GLuint DepthBuffer); + static PixelData ToPixelData(glm::vec2 screenCoord, FrameBuffer* PickDataBuffer, GLuint DepthBuffer); + static PixelData ToPixelData(float x, float y, FrameBuffer* PickDataBuffer, GLuint DepthBuffer); //Return EntityID of the clicked coordinate in given screenspace coordinates. //EntityID ScreenCoordsToEntityID(glm::vec2 screenCoord, float depth); diff --git a/include/Game/Game.h b/include/Game/Game.h index cd16a3dd..33dc88a6 100644 --- a/include/Game/Game.h +++ b/include/Game/Game.h @@ -9,6 +9,22 @@ #include "GUI/Frame.h" #include "Core/World.h" #include "Rendering/RenderQueueFactory.h" +#include "Input/InputProxy.h" +#include "Input/KeyboardInputHandler.h" +#include "Input/MouseInputHandler.h" +#include "Core/EKeyDown.h" +#include "Core/EntityXMLFile.h" +#include "Core/SystemPipeline.h" +#include "RaptorCopterSystem.h" +#include "PlayerSystem.h" +#include "Editor/EditorSystem.h" + +// Network +#include +#include "Network/Network.h" +#include "Network/Server.h" +#include "Network/Client.h" + class Game { @@ -25,9 +41,26 @@ private: EventBroker* m_EventBroker; IRenderer* m_Renderer; InputManager* m_InputManager; + InputProxy* m_InputProxy; GUI::Frame* m_FrameStack; World* m_World; + SystemPipeline* m_SystemPipeline; RenderQueueFactory* m_RenderQueueFactory; + // Network variables + boost::thread m_NetworkThread; + + // Network methods + void networkFunction(); + Network* m_ClientOrServer; + bool m_IsClientOrServer = false; + + EventRelay m_EInputCommand; + bool debugOnInputCommand(const Events::InputCommand& e); + + void debugInitialize(); + void debugTick(double dt); + EventRelay m_EKeyDown; + }; #endif diff --git a/include/Game/HardcodedTestWorld.h b/include/Game/HardcodedTestWorld.h deleted file mode 100644 index f183330f..00000000 --- a/include/Game/HardcodedTestWorld.h +++ /dev/null @@ -1,110 +0,0 @@ -#include -#include -#include -#include "GLM.h" -#include "Core/World.h" -#include "Core/Util/Any.h" - -class HardcodedTestWorld : public World -{ -public: - HardcodedTestWorld() - : World() - { - registerTestComponents(); - createTestEntities(); - } - -private: - void registerTestComponents() - { - ComponentWrapperFactory f; - - - f = ComponentWrapperFactory("Test"); - f.AddProperty("TestInteger", 1337); - f.AddProperty("TestFloat", 13.37f); - f.AddProperty("TestString", std::string("Carlito")); - RegisterComponent(f); - - f = ComponentWrapperFactory("Debug"); - f.AddProperty("Name", std::string("Unnamed")); - RegisterComponent(f); - - f = ComponentWrapperFactory("Transform"); - f.AddProperty("Position", glm::vec3(0.f, 0.f, 0.f)); - f.AddProperty("Orientation", glm::quat()); - f.AddProperty("Scale", glm::vec3(1.f, 1.f, 1.f)); - RegisterComponent(f); - - f = ComponentWrapperFactory("Model"); - f.AddProperty("Resource", std::string()); - f.AddProperty("Color", glm::vec4(1.f, 1.f, 1.f, 1.f)); - f.AddProperty("Visible", true); - RegisterComponent(f); - } - - void createTestEntities() - { - World& world = *this; - - // Create an entity - EntityID e = world.CreateEntity(); - - // Attach a Debug component - ComponentWrapper debug = world.AttachComponent(e, "Debug"); - // Set the Name field of the Debug component using subscript operator - debug["Name"] = "Carlito"; - - // Attach a Transform component - world.AttachComponent(e, "Transform"); - // Fetch the component based on EntityID and component type - ComponentWrapper transform = world.GetComponent(e, "Transform"); - // Set the fields of the Transform component - transform["Position"] = glm::vec3(0.f, 0.f, 0.f); - transform["Scale"] = glm::vec3(1.f, 1.f, 1.f); - - // Move on the X axis by fetching field as reference - ((glm::vec3&)transform["Position"]).x += 10.f; - // Shrink by a factor of 100 - ((glm::vec3&)transform["Scale"]) /= 100.f; - - // Loop through all Transform components and print them - for (auto& transform : world.GetComponents("Transform")) { - glm::vec3 pos = transform["Position"]; - std::cout << "Position: " << pos.x << " " << pos.y << " " << pos.z << std::endl; - glm::vec3 scale = transform["Scale"]; - std::cout << "Scale: " << scale.x << " " << scale.y << " " << scale.z << std::endl; - - // Fetch the Debug component also present in this entity - ComponentWrapper debug = world.GetComponent(transform.EntityID, "Debug"); - std::cout << "Name: " << (std::string)debug["Name"] << std::endl; - } - - //Create some test widgets - { - EntityID entityScaleWidget = world.CreateEntity(); - ComponentWrapper transform = world.AttachComponent(entityScaleWidget, "Transform"); - transform["Position"] = glm::vec3(-1.5f, 0.f, 0.f); - ComponentWrapper model = world.AttachComponent(entityScaleWidget, "Model"); - model["Resource"] = "Models/ScaleWidget.obj"; - } - { - EntityID entityRotationWidget = world.CreateEntity(); - ComponentWrapper transform = world.AttachComponent(entityRotationWidget, "Transform"); - transform["Position"] = glm::vec3(1.5f, 0.f, 0.f); - ComponentWrapper model = world.AttachComponent(entityRotationWidget, "Model"); - model["Resource"] = "Models/RotationWidget.obj"; - } - { - EntityID entityDummyScene = world.CreateEntity(); - ComponentWrapper transform = world.AttachComponent(entityDummyScene, "Transform"); - transform["Position"] = glm::vec3(0, 0.f, 0.f); - ComponentWrapper model = world.AttachComponent(entityDummyScene, "Model"); - model["Resource"] = "Models/DummyScene.obj"; - } - - - - } -}; \ No newline at end of file diff --git a/include/Game/HealthSystem.h b/include/Game/HealthSystem.h new file mode 100644 index 00000000..a836e797 --- /dev/null +++ b/include/Game/HealthSystem.h @@ -0,0 +1,36 @@ +#ifndef HealthSystem_h__ +#define HealthSystem_h__ + +#include +#include + +#include "Common.h" +#include "Core/System.h" +#include "Core\EPlayerDamage.h"; +#include "Core\EPlayerHealthPickup.h"; +#include "Core\EPlayerDeath.h"; + +#include +#include + +class HealthSystem : public PureSystem +{ +public: + HealthSystem(EventBroker* eventBroker); + + //updatecomponent + virtual void UpdateComponent(World* world, ComponentWrapper& health, double dt) override; + +private: + //methods which will take care of specific events + EventRelay m_EPlayerDamage; + bool HealthSystem::OnPlayerDamaged(const Events::PlayerDamage& e); + EventRelay m_EPlayerHealthPickup; + bool HealthSystem::OnPlayerHealthPickup(const Events::PlayerHealthPickup& e); + + //vector which will keep track of health changes + std::vector> m_DeltaHealthVector; + +}; + +#endif \ No newline at end of file diff --git a/include/Game/PlayerSystem.h b/include/Game/PlayerSystem.h new file mode 100644 index 00000000..577fbbb0 --- /dev/null +++ b/include/Game/PlayerSystem.h @@ -0,0 +1,33 @@ +#ifndef PlayerSystem_h__ +#define PlayerSystem_h__ + +#include +#include + +#include "Common.h" +#include "Core/System.h" +#include "Collision/ETrigger.h" + +class PlayerSystem : public PureSystem +{ +public: + PlayerSystem(EventBroker* eventBroker) + : PureSystem(eventBroker, "Player") + { + 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; +private: + float m_Speed = 5; + EventRelay m_EEnter; + bool OnEnter(const Events::TriggerEnter &event); + EventRelay m_ETouch; + bool PlayerSystem::OnTouch(const Events::TriggerTouch &event); + EventRelay m_ELeave; + bool PlayerSystem::OnLeave(const Events::TriggerLeave &event); +}; + +#endif \ No newline at end of file diff --git a/include/Game/RaptorCopterSystem.h b/include/Game/RaptorCopterSystem.h new file mode 100644 index 00000000..913efdb3 --- /dev/null +++ b/include/Game/RaptorCopterSystem.h @@ -0,0 +1,16 @@ +#include "Common.h" +#include "Core/System.h" + +class RaptorCopterSystem : public PureSystem +{ +public: + RaptorCopterSystem(EventBroker* eventBroker) + : PureSystem(eventBroker, "RaptorCopter") + { } + + virtual void UpdateComponent(World* world, ComponentWrapper& raptorCopter, double dt) override + { + ComponentWrapper& transform = world->GetComponent(raptorCopter.EntityID, "Transform"); + (glm::vec3&)transform["Orientation"] += (float)(double)raptorCopter["Speed"] * (float)dt * (glm::vec3)raptorCopter["Axis"]; + } +}; \ No newline at end of file diff --git a/resources/DefaultConfig.ini b/resources/DefaultConfig.ini index ac7e9ec2..3ab402ad 100644 --- a/resources/DefaultConfig.ini +++ b/resources/DefaultConfig.ini @@ -1,8 +1,19 @@ [Debug] LogLevel=1 +LoadMap= +EditorEnabled=false + [Video] Fullscreen=false VSYNC=false Width=1280 -Height=720 \ No newline at end of file +Height=720 +FOV=45 + +[Networking] +StartNetwork=false +IsServer=false +Name=Bob +Address=127.0.0.1 +Port=13 \ No newline at end of file diff --git a/resources/DefaultInput.ini b/resources/DefaultInput.ini new file mode 100644 index 00000000..95ebbc22 --- /dev/null +++ b/resources/DefaultInput.ini @@ -0,0 +1,24 @@ +[Mouse] +Sensitivity=0.5 +InvertPitch=false + +[Bindings] +MouseLeft=PrimaryFire +MouseX=Yaw +MouseY=Pitch +W=+Forward +S=-Forward +D=+Right +A=-Right +R=Reload +Space=Jump +LeftControl=Crouch +LeftShift=Sprint +F1=ToggleEditor +1=EditorToolMove +2=EditorToolRotate +3=EditorToolScale +X=EditorToggleTransformSpace +C=ConnectToServer +N=SwitchToServer +M=SwitchToClient \ No newline at end of file diff --git a/resources/Schema/Components.xsd b/resources/Schema/Components.xsd index 41564706..459fd9d7 100644 --- a/resources/Schema/Components.xsd +++ b/resources/Schema/Components.xsd @@ -2,5 +2,12 @@ + + + + + + + \ No newline at end of file diff --git a/resources/Schema/Components/AABB.xml b/resources/Schema/Components/AABB.xml new file mode 100644 index 00000000..341d1d0d --- /dev/null +++ b/resources/Schema/Components/AABB.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/resources/Schema/Components/AABB.xsd b/resources/Schema/Components/AABB.xsd new file mode 100644 index 00000000..8fac860e --- /dev/null +++ b/resources/Schema/Components/AABB.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/Schema/Components/Health.xml b/resources/Schema/Components/Health.xml new file mode 100644 index 00000000..143a91d1 --- /dev/null +++ b/resources/Schema/Components/Health.xml @@ -0,0 +1,4 @@ + + 100 + 100 + \ No newline at end of file diff --git a/resources/Schema/Components/Health.xsd b/resources/Schema/Components/Health.xsd new file mode 100644 index 00000000..0da80c1f --- /dev/null +++ b/resources/Schema/Components/Health.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/Schema/Components/Model.xml b/resources/Schema/Components/Model.xml new file mode 100644 index 00000000..bd20147f --- /dev/null +++ b/resources/Schema/Components/Model.xml @@ -0,0 +1,5 @@ + + + + true + \ No newline at end of file diff --git a/resources/Schema/Components/Model.xsd b/resources/Schema/Components/Model.xsd new file mode 100644 index 00000000..fb0774f8 --- /dev/null +++ b/resources/Schema/Components/Model.xsd @@ -0,0 +1,24 @@ + + + + + + + + A visible model loaded from disk + + + + + Model file + + + Color tint + + + Wether the model is visible or not + + + + + \ No newline at end of file diff --git a/resources/Schema/Components/Player.xml b/resources/Schema/Components/Player.xml new file mode 100644 index 00000000..190f2ed0 --- /dev/null +++ b/resources/Schema/Components/Player.xml @@ -0,0 +1,7 @@ + + + false + false + false + false + \ No newline at end of file diff --git a/resources/Schema/Components/Player.xsd b/resources/Schema/Components/Player.xsd new file mode 100644 index 00000000..76a6a8fb --- /dev/null +++ b/resources/Schema/Components/Player.xsd @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/Schema/Components/PointLight.xml b/resources/Schema/Components/PointLight.xml new file mode 100644 index 00000000..6b1382db --- /dev/null +++ b/resources/Schema/Components/PointLight.xml @@ -0,0 +1,7 @@ + + + 1.0 + 0.8 + 0.3 + true + \ No newline at end of file diff --git a/resources/Schema/Components/PointLight.xsd b/resources/Schema/Components/PointLight.xsd new file mode 100644 index 00000000..d1a9f52c --- /dev/null +++ b/resources/Schema/Components/PointLight.xsd @@ -0,0 +1,20 @@ + + + + + + + + A pointlight that lights up geometry in a radius. + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/Schema/Components/RaptorCopter.xml b/resources/Schema/Components/RaptorCopter.xml new file mode 100644 index 00000000..cc1ece52 --- /dev/null +++ b/resources/Schema/Components/RaptorCopter.xml @@ -0,0 +1,4 @@ + + 0 + + \ No newline at end of file diff --git a/resources/Schema/Components/RaptorCopter.xsd b/resources/Schema/Components/RaptorCopter.xsd new file mode 100644 index 00000000..23ee8a96 --- /dev/null +++ b/resources/Schema/Components/RaptorCopter.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/Schema/Components/Test.xml b/resources/Schema/Components/Test.xml deleted file mode 100644 index ccf459fe..00000000 --- a/resources/Schema/Components/Test.xml +++ /dev/null @@ -1,6 +0,0 @@ - - 1 - 1.333 - - - \ No newline at end of file diff --git a/resources/Schema/Components/Test.xsd b/resources/Schema/Components/Test.xsd deleted file mode 100644 index e31f83af..00000000 --- a/resources/Schema/Components/Test.xsd +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - ECS Test Component - - - - - - - - - - - - \ No newline at end of file diff --git a/resources/Schema/Components/Transform.xml b/resources/Schema/Components/Transform.xml index e093c41a..00202aa4 100644 --- a/resources/Schema/Components/Transform.xml +++ b/resources/Schema/Components/Transform.xml @@ -1,5 +1,5 @@ - + - + - \ No newline at end of file + \ No newline at end of file diff --git a/resources/Schema/Components/Transform.xsd b/resources/Schema/Components/Transform.xsd index d28ff601..b8d24777 100644 --- a/resources/Schema/Components/Transform.xsd +++ b/resources/Schema/Components/Transform.xsd @@ -12,7 +12,7 @@ The position vector - + diff --git a/resources/Schema/Components/Trigger.xml b/resources/Schema/Components/Trigger.xml new file mode 100644 index 00000000..4c8aad58 --- /dev/null +++ b/resources/Schema/Components/Trigger.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/Schema/Components/Trigger.xsd b/resources/Schema/Components/Trigger.xsd new file mode 100644 index 00000000..a8bc8865 --- /dev/null +++ b/resources/Schema/Components/Trigger.xsd @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/resources/Schema/Entities/CollisionTestLevel.xml b/resources/Schema/Entities/CollisionTestLevel.xml new file mode 100644 index 00000000..99842b88 --- /dev/null +++ b/resources/Schema/Entities/CollisionTestLevel.xml @@ -0,0 +1,122 @@ + + + + + + + + + Models/DummyScene.obj + + + + + + + + + + + Models/ScaleWidget.obj + + + + + + + + + + + Models/RotationWidgetX.obj + + + + + + + + + + + + + + + Models/Core/UnitCube.obj + + + + + + + + + + + + + + + + + + + + + + Models/Core/UnitRaptor.obj + + + + + + + + + + + + 20 + + + + + + + + + + + + + Models/Core/UnitCube.obj + + + + + + + + + + + + + Models/Core/UnitCube.obj + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/Schema/Entities/EditorTestWorld.xml b/resources/Schema/Entities/EditorTestWorld.xml new file mode 100755 index 00000000..7fa8e96a --- /dev/null +++ b/resources/Schema/Entities/EditorTestWorld.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + Models/Core/UnitPlane.obj + + + + + + + + + + + An error + + + + + + + + + + + An error + + + + + + + + + \ No newline at end of file diff --git a/resources/Schema/Entities/Test.xml b/resources/Schema/Entities/Test.xml index 8ca6b684..809bca51 100644 --- a/resources/Schema/Entities/Test.xml +++ b/resources/Schema/Entities/Test.xml @@ -3,18 +3,120 @@ - - - + - - 12 - 11.11 - Hello World - + + Models/DummyScene.obj + - - + + + + + + + + + + + + + + + + + + + + Models/Core/UnitRaptor.obj + + + + + + + + + + + + 20 + + + + + + + + + + + + + Models/Core/UnitCube.obj + + + + + + + + + + + + + Models/Core/UnitCube.obj + + + + + + + + + + \ No newline at end of file diff --git a/resources/Schema/Types.xsd b/resources/Schema/Types.xsd index 939c88da..cd464201 100644 --- a/resources/Schema/Types.xsd +++ b/resources/Schema/Types.xsd @@ -3,6 +3,10 @@ + + + + diff --git a/resources/Schema/Types/Color.xsd b/resources/Schema/Types/Color.xsd new file mode 100644 index 00000000..4fb9572e --- /dev/null +++ b/resources/Schema/Types/Color.xsd @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/resources/Schema/Types/Entity.xsd b/resources/Schema/Types/Entity.xsd index 2f0a6311..d1d63518 100644 --- a/resources/Schema/Types/Entity.xsd +++ b/resources/Schema/Types/Entity.xsd @@ -7,11 +7,16 @@ - + + + + + + diff --git a/resources/Shaders/CullLights.comp.glsl b/resources/Shaders/CullLights.comp.glsl new file mode 100644 index 00000000..bdd3d758 --- /dev/null +++ b/resources/Shaders/CullLights.comp.glsl @@ -0,0 +1,154 @@ +#version 430 + +//in uvec3 gl_NumWorkGroups; //contains the number of workgroups that have been dispatched to a compute shader +//in uvec3 gl_WorkGroupID; //contains the index of the workgroup currently being operated on by a compute shader +//in uvec3 gl_LocalInvocationID; //contains the index of work item currently being operated on by a compute shader +//in uvec3 gl_GlobalInvocationID; //contains the global index of work item currently being operated on by a compute shader +//in uint gl_LocalInvocationIndex; //contains the local linear index of work item currently being operated on by a compute shader + + + +#define MAX_LIGHTS_PER_TILE 200 +#define NUM_TILES 3600 +#define TILE_SIZE 16 + +uniform mat4 V; + +struct Plane { + vec3 Normal; + float d; +}; +struct Frustum { + Plane Planes[4]; +}; + +layout (std430, binding = 0) buffer FrustumBuffer +{ + Frustum Data[]; +} Frustums; + +struct PointLight { + vec4 Position; + vec4 Color; + float Radius; + float Intensity; + float Falloff; + float Padding; +}; + +layout (std430, binding = 1) buffer LightBuffer +{ + PointLight List[]; +} PointLights; + +struct LightGrid { + float Start; + float Amount; + vec2 Padding; +}; + +layout (std430, binding = 2) buffer LightGridBuffer +{ + LightGrid Data[]; +} LightGrids; + +layout (std430, binding = 3) buffer LightOffsetBuffer +{ + int LightOffset[]; +}; + +layout (std430, binding = 4) buffer LightIndexBuffer +{ + float LightIndex[]; +}; + +shared int GroupLightCount; +shared int GroupLightIndexStartOffset; +shared int GroupLightIndex[MAX_LIGHTS_PER_TILE]; +shared Frustum GroupFrustum; +int GroupIndex; + +bool SphereInsidePlane(vec3 center, float radius, Plane plane) +{ + return dot(plane.Normal, center) - plane.d > -radius; +} + +bool SphereInsideFrustrum(vec3 center, float radius, Frustum frustum/*, float zNear, float zFar*/) +{ + + //Check depth here + //if ( sphere.c.z - sphere.r > zNear || sphere.c.z + sphere.r < zFar ) + //{ + // result = false; + //} + + for (int i =0; i < 4; i++) + { + if(! SphereInsidePlane(center, radius, frustum.Planes[i])) + { + return false; + } + } + return true; +} + +void AppendLight(int li) +{ + int index; + index = atomicAdd(GroupLightCount, 1); + if( index < MAX_LIGHTS_PER_TILE ) + { + GroupLightIndex[index] = int(li); + } +} + +layout (local_size_x = 16, local_size_y = 16, local_size_z = 1) in; +void main () +{ + GroupIndex = int(gl_WorkGroupID.x + (gl_WorkGroupID.y * 80)); + if(gl_LocalInvocationIndex == 0) + { + GroupLightCount = 0; + GroupFrustum = Frustums.Data[GroupIndex]; + } + + barrier(); + memoryBarrierShared(); + + for(int i = int(gl_LocalInvocationIndex); i < PointLights.List.length(); i += TILE_SIZE*TILE_SIZE) + { + PointLight light = PointLights.List[i]; + + //if pointlight + //Pos i view antagligen + if(SphereInsideFrustrum( vec3(V * light.Position), light.Radius, GroupFrustum)) + { + //TODO: Fix transparent and opaque list, and depth test. + AppendLight( i ); + } + + + //if conelight + + //if directional + + } + + barrier(); + memoryBarrierShared(); + + if(gl_LocalInvocationIndex == 0) + { + GroupLightIndexStartOffset = atomicAdd(LightOffset[0], GroupLightCount); + LightGrids.Data[GroupIndex].Start = GroupLightIndexStartOffset; + LightGrids.Data[GroupIndex].Amount = GroupLightCount; + } + + barrier(); + + + for (uint i = gl_LocalInvocationIndex; i < GroupLightCount; i += TILE_SIZE * TILE_SIZE ) + { + LightIndex[GroupLightIndexStartOffset + i] = GroupLightIndex[i]; + } +} \ No newline at end of file diff --git a/resources/Shaders/ForwardPlus.frag.glsl b/resources/Shaders/ForwardPlus.frag.glsl new file mode 100644 index 00000000..79d5411c --- /dev/null +++ b/resources/Shaders/ForwardPlus.frag.glsl @@ -0,0 +1,132 @@ +#version 430 + +uniform mat4 M; +uniform mat4 V; +uniform mat4 P; +uniform vec4 Color; + +uniform sampler2D texture0; + +#define TILE_SIZE 16 + +struct PointLight { + vec4 Position; + vec4 Color; + float Radius; + float Intensity; + float Falloff; + float Padding; +}; + +layout (std430, binding = 1) buffer LightBuffer +{ + PointLight List[]; +} PointLights; + +struct LightGrid { + float Start; + float Amount; + vec2 Padding; +}; + +layout (std430, binding = 2) buffer LightGridBuffer +{ + LightGrid Data[]; +} LightGrids; + +layout (std430, binding = 4) buffer LightIndexBuffer +{ + float LightIndex[]; +}; + + +in VertexData{ + vec3 Position; + vec3 Normal; + vec2 TextureCoordinate; + vec4 DiffuseColor; +}Input; + +out vec4 fragmentColor; + +vec4 scene_ambient = vec4(0.3,0.3,0.3,1); + +struct LightResult { + vec4 Diffuse; + vec4 Specular; +}; + +float CalcAttenuation(float radius, float dist) { + return 1.0 - smoothstep(radius * 0.3, radius, dist); +} + +vec4 CalcSpecular(vec4 lightColor, vec4 viewVec, vec4 lightVec, vec4 normal) { + vec4 R = normalize( reflect(-lightVec, normal)); + float RdotV = max( dot(R, viewVec), 0.0); + return lightColor * pow(RdotV, 90.0); +} + +vec4 CalcDiffuse(vec4 lightColor, vec4 lightVec, vec4 normal) { + float power = max( dot(normal, lightVec), 0.0); + return lightColor * power; +} + +LightResult CalcPointLight(vec4 lightPos, float lightRadius, vec4 lightColor, float intensity, vec4 viewVec, vec4 position, vec4 normal) +{ + vec4 L = lightPos - position; + float dist = length(L); + L = normalize(L); + + float attenuation = CalcAttenuation(lightRadius, dist); + + LightResult result; + result.Diffuse = CalcDiffuse(lightColor, L, normal) * attenuation * intensity; + result.Specular = CalcSpecular(lightColor, viewVec, L, normal) * attenuation * intensity; + return result; +} + + +void main() +{ + vec4 texel = texture2D(texture0, Input.TextureCoordinate); + vec4 position = V * M * vec4(Input.Position, 1.0); + vec4 normal = V * vec4(Input.Normal, 0.0); + vec4 viewVec = normalize(-position); + + vec2 tilePos; + tilePos.x = int(gl_FragCoord.x/16); + tilePos.y = int(gl_FragCoord.y/16); + + LightResult totalLighting; + totalLighting.Diffuse = scene_ambient; + int currentTile = int(floor(gl_FragCoord.x/TILE_SIZE) + (floor(gl_FragCoord.y/TILE_SIZE) * 80)); + + int start = int(LightGrids.Data[currentTile].Start); + int amount = int(LightGrids.Data[currentTile].Amount); + //for(int i = 0; i < 3; i++) + for(int i = start; i < start + amount; i++) + { + int l = int(LightIndex[i]); + + LightResult result = CalcPointLight(V * PointLights.List[l].Position, PointLights.List[l].Radius, PointLights.List[l].Color, PointLights.List[l].Intensity, viewVec, position, normal); + + totalLighting.Diffuse += result.Diffuse; + totalLighting.Specular += result.Specular; + } + + fragmentColor += Input.DiffuseColor * (totalLighting.Diffuse + totalLighting.Specular) * texel * Color; + //fragmentColor += Input.DiffuseColor * (totalLighting.Diffuse) * texel * Color; + //fragmentColor += vec4(0.0, LightGrids.Data[currentTile].Amount/3.0, 0, 1); + //fragmentColor = texel * Input.DiffuseColor * Color; + if(int(gl_FragCoord.x)%16 == 0 || int(gl_FragCoord.y)%16 == 0 ) + { + //fragmentColor += vec4(0.5, 0, 0, 0); + } else { + //fragmentColor += vec4(LightGrids.Data[int(tilePos.x + tilePos.y*80)].Amount/3.0, 0, 0, 1); + + + } + +} + + diff --git a/resources/Shaders/ForwardPlus.vert.glsl b/resources/Shaders/ForwardPlus.vert.glsl new file mode 100644 index 00000000..20ab9051 --- /dev/null +++ b/resources/Shaders/ForwardPlus.vert.glsl @@ -0,0 +1,34 @@ +#version 430 + +uniform mat4 M; +uniform mat4 V; +uniform mat4 P; + +layout(location = 0) in vec3 Position; +layout(location = 1) in vec3 Normal; +layout(location = 2) in vec3 Tangent; +layout(location = 3) in vec3 BiTangent; +layout(location = 4) in vec2 TextureCoords; +layout(location = 5) in vec4 DiffuseVertexColor; +layout(location = 6) in vec4 SpecularVertexColor; +layout(location = 7) in vec4 BoneIndices1; +layout(location = 8) in vec4 BoneIndices2; +layout(location = 9) in vec4 BoneWeights1; +layout(location = 10) in vec4 BoneWeights2; + +out VertexData{ + vec3 Position; + vec3 Normal; + vec2 TextureCoordinate; + vec4 DiffuseColor; +}Output; + +void main() +{ + gl_Position = P*V*M * vec4(Position, 1.0); + + Output.Position = Position; + Output.TextureCoordinate = TextureCoords; + Output.Normal = Normal; + Output.DiffuseColor = DiffuseVertexColor; +} \ No newline at end of file diff --git a/resources/Shaders/GridFrustum.comp.glsl b/resources/Shaders/GridFrustum.comp.glsl new file mode 100644 index 00000000..bb2a4fb7 --- /dev/null +++ b/resources/Shaders/GridFrustum.comp.glsl @@ -0,0 +1,76 @@ +#version 430 + +#define TILE_SIZE 16 +#define NUM_TILES 3600 + +uniform mat4 P; +uniform vec2 ScreenDimensions; + +struct Plane { + vec3 Normal; + float d; +}; +struct Frustum { + Plane Planes[4]; +}; + +layout (std430, binding = 0) buffer FrustumBuffer +{ + Frustum Data[]; +} Frustums; + +vec4 ConvertToView(vec4 ScreenCoords) +{ + vec2 normalizedScreenCoords = ScreenCoords.xy / ScreenDimensions; + vec4 clipSpace = vec4( vec2(normalizedScreenCoords.x, normalizedScreenCoords.y) * 2.0 - 1.0, ScreenCoords.z, ScreenCoords.w); + vec4 view = inverse(P) * clipSpace; + view = view / view.w; + return view; +} + +Plane ComputePlane( vec3 p0, vec3 p1, vec3 p2 ) +{ + Plane plane; + + vec3 v0 = p1 - p0; + vec3 v2 = p2 - p0; + + plane.Normal = normalize( cross( v0, v2 ) ); + plane.d = dot( vec3(plane.Normal), p0 ); // Always 0 probably + return plane; +} + +layout (local_size_x = 16, local_size_y = 16, local_size_z = 1) in; +void main () +{ + //Top-Left = 0 | Top-Right = 1 + //Bottom-Left = 2 | Bottom-Right = 3 + vec4 ScreenCoords[4]; + ScreenCoords[0] = vec4(gl_GlobalInvocationID.x * TILE_SIZE, (gl_GlobalInvocationID.y + 1) * TILE_SIZE, -1.0, 1.0); + ScreenCoords[1] = vec4((gl_GlobalInvocationID.x + 1) * TILE_SIZE, (gl_GlobalInvocationID.y + 1) * TILE_SIZE, -1.0, 1.0); + ScreenCoords[2] = vec4(gl_GlobalInvocationID.x * TILE_SIZE, (gl_GlobalInvocationID.y) * TILE_SIZE, -1.0, 1.0); + ScreenCoords[3] = vec4((gl_GlobalInvocationID.x + 1) * TILE_SIZE, (gl_GlobalInvocationID.y) * TILE_SIZE, -1.0, 1.0); + + + + vec3 ViewVectors[4]; + for(int i = 0; i < 4; i++) { + ViewVectors[i] = vec3(ConvertToView(ScreenCoords[i])); + } + + vec3 EyePos = vec3(0.0, 0.0 ,0.0); + + Frustum f; + f.Planes[0] = ComputePlane(EyePos, ViewVectors[2], ViewVectors[0]); // left plane + f.Planes[1] = ComputePlane(EyePos, ViewVectors[1], ViewVectors[3]); // right plane + f.Planes[2] = ComputePlane(EyePos, ViewVectors[0], ViewVectors[1]); // top plane + f.Planes[3] = ComputePlane(EyePos, ViewVectors[3], ViewVectors[2]); // bottom plane + + + + + if ( gl_GlobalInvocationID.x < ScreenDimensions.x / TILE_SIZE && gl_GlobalInvocationID.y < ScreenDimensions.y / TILE_SIZE ) { // inside the screen + Frustums.Data[gl_GlobalInvocationID.x + gl_GlobalInvocationID.y*80] = f; + + } +} \ No newline at end of file diff --git a/resources/Shaders/Picking.frag.glsl b/resources/Shaders/Picking.frag.glsl index 11f529b0..8c95ead3 100644 --- a/resources/Shaders/Picking.frag.glsl +++ b/resources/Shaders/Picking.frag.glsl @@ -13,7 +13,7 @@ out vec4 TextureFragment; void main() { - TextureFragment = vec4(PickingColor, 0, 1); + TextureFragment = vec4(PickingColor/255, 0, 1); } diff --git a/src/Engine/CMakeLists.txt b/src/Engine/CMakeLists.txt index de24b9cf..5d43db8b 100644 --- a/src/Engine/CMakeLists.txt +++ b/src/Engine/CMakeLists.txt @@ -60,7 +60,6 @@ file(GLOB SOURCE_FILES_Rendering_Util "${INCLUDE_PATH}/Rendering/Util/*.h" "Rendering/Util/*.cpp" ) - source_group(Rendering FILES ${SOURCE_FILES_Rendering}) source_group(Rendering\\Util FILES ${SOURCE_FILES_Rendering_Util}) @@ -70,14 +69,31 @@ 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" +) +source_group(Editor FILES ${SOURCE_FILES_Editor}) + set(SOURCE_FILES ${SOURCE_FILES_Core} ${SOURCE_FILES_Core_Util} - #${SOURCE_FILES_Input} + ${SOURCE_FILES_Input} ${SOURCE_FILES_Network} ${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 + ${SOURCE_FILES_Editor} ) set(LIBRARIES @@ -103,4 +119,4 @@ target_link_libraries(Engine ${LIBRARIES} ) #set_target_properties(Engine PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "${INCLUDE_PATH}/PrecompiledHeader.h") -#cotire(Engine) \ No newline at end of file +#cotire(Engine) diff --git a/src/Engine/Collision/Collision.cpp b/src/Engine/Collision/Collision.cpp new file mode 100644 index 00000000..c4af6258 --- /dev/null +++ b/src/Engine/Collision/Collision.cpp @@ -0,0 +1,282 @@ +#include + +#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(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& modelVertices, + const std::vector& 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& modelVertices, + const std::vector& 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& modelVertices, + const std::vector& 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["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["Resource"]); + outBox.CreateFromCenter(AABBComponent["BoxCenter"], AABBComponent["BoxSize"]); + glm::vec3 mini = outBox.MinCorner(); + glm::vec3 maxi = outBox.MaxCorner(); + + if (modelRes == nullptr) { + return false; + } + 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); +} + +} diff --git a/src/Engine/Collision/CollisionSystem.cpp b/src/Engine/Collision/CollisionSystem.cpp new file mode 100644 index 00000000..69929c6d --- /dev/null +++ b/src/Engine/Collision/CollisionSystem.cpp @@ -0,0 +1,40 @@ +#include "Collision/Collision.h" +#include "Collision/CollisionSystem.h" +#include "Core/AABB.h" + +void CollisionSystem::UpdateComponent(World * world, ComponentWrapper & cAABB, double dt) +{ + //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; +} diff --git a/src/Engine/Collision/TriggerSystem.cpp b/src/Engine/Collision/TriggerSystem.cpp new file mode 100644 index 00000000..09092461 --- /dev/null +++ b/src/Engine/Collision/TriggerSystem.cpp @@ -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& 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(pId, tId); + } + } else { + //Entity is only touching the trigger. + std::unordered_set& touchSet = m_EntitiesTouchingTrigger[tId]; + std::unordered_set& 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(pId, tId); + touchSet.insert(pId); + } + //Else, it was touching the trigger last frame too and nothing is done. + } + } + } +} + +bool TriggerSystem::throwLeaveIfWasInTrigger(std::unordered_set& 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(pId, tId); + return true; + } + return false; +} + diff --git a/src/Engine/Core/AABB.cpp b/src/Engine/Core/AABB.cpp new file mode 100644 index 00000000..0df56229 --- /dev/null +++ b/src/Engine/Core/AABB.cpp @@ -0,0 +1,34 @@ +#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) +{ + m_Center = center; + m_HalfSize = 0.5f * size; + m_MinCorner = m_Center - m_HalfSize; + m_MaxCorner = m_Center + m_HalfSize; +} + +AABB::~AABB() +{} diff --git a/src/Engine/Core/ComponentPool.cpp b/src/Engine/Core/ComponentPool.cpp index 146b33f6..ce24c1f7 100644 --- a/src/Engine/Core/ComponentPool.cpp +++ b/src/Engine/Core/ComponentPool.cpp @@ -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++(); @@ -50,10 +50,16 @@ ComponentWrapper ComponentPool::GetByEntity(EntityID ent) return ComponentWrapper(m_ComponentInfo, m_EntityToComponent.at(ent)); } + +bool ComponentPool::KnowsEntity(EntityID ent) +{ + return m_EntityToComponent.find(ent) != m_EntityToComponent.end(); +} + void ComponentPool::Delete(ComponentWrapper& wrapper) { m_EntityToComponent.erase(wrapper.EntityID); - m_Pool.Free(wrapper.Data); + m_Pool.Free(wrapper.Data - sizeof(EntityID)); } ComponentPool::iterator ComponentPool::begin() const diff --git a/src/Engine/Core/ConfigFile.cpp b/src/Engine/Core/ConfigFile.cpp index 004ebc43..00ab4993 100644 --- a/src/Engine/Core/ConfigFile.cpp +++ b/src/Engine/Core/ConfigFile.cpp @@ -24,8 +24,11 @@ ConfigFile::ConfigFile(std::string path) if (boost::filesystem::exists(m_Path)) { try { boost::property_tree::ini_parser::read_ini(m_Path.string(), m_PTreeOverrides); - for (auto& node : m_PTreeOverrides) { - m_PTreeMerged.put_child(node.first, node.second); + for (auto& topLevelNode : m_PTreeOverrides) { + auto& mergedTopLevelNode = m_PTreeMerged.find(topLevelNode.first); + for (auto& childOverrideNode : topLevelNode.second) { + mergedTopLevelNode->second.put_child(childOverrideNode.first, childOverrideNode.second); + } } } catch (boost::property_tree::ptree_error& e) { LOG_ERROR("Failed to parse \"%s\":\n%s", m_Path.filename().string().c_str(), e.what()); diff --git a/src/Engine/Core/EntityXMLFile.cpp b/src/Engine/Core/EntityXMLFile.cpp new file mode 100644 index 00000000..c6e00a1b --- /dev/null +++ b/src/Engine/Core/EntityXMLFile.cpp @@ -0,0 +1,495 @@ +#include "Core/EntityXMLFile.h" +#include "Core/World.h" + +unsigned int EntityXMLFile::InstanceCount = 0; + +EntityXMLFile::EntityXMLFile(std::string path) + : m_EntityFile(path) +{ + using namespace xercesc; + + if (InstanceCount == 0) { + XMLPlatformUtils::Initialize(); + } + InstanceCount++; + + m_GrammarPool = new XMLGrammarPoolImpl(); + m_ErrorHandler = new EntityParserXMLErrorHandler(); + m_DOMParser = new XercesDOMParser(nullptr, XMLPlatformUtils::fgMemoryManager, m_GrammarPool); + m_DOMParser->setErrorHandler(m_ErrorHandler); + m_DOMParser->setDoNamespaces(true); + m_DOMParser->setDoXInclude(true); + m_DOMParser->setDoSchema(true); + m_DOMParser->setValidationSchemaFullChecking(true); + m_DOMParser->setValidationScheme(xercesc::XercesDOMParser::Val_Auto); + m_DOMParser->setValidationSchemaFullChecking(true); + m_DOMParser->setValidationConstraintFatal(false); + m_DOMParser->setIncludeIgnorableWhitespace(false); + // Make sure schema grammar is kept after validation + m_DOMParser->cacheGrammarFromParse(true); + + // HACK: Use Sax2 parser instead so the entire DOM doesn't have to reside in memory + m_DOMParser->parse(m_EntityFile.c_str()); + m_DOMDocument = m_DOMParser->getDocument(); + + // 1. Fill in ComponentInfo name, fields, default values and metadata from PSVI + parseComponentInfo(); + // 2. Parse default value files for those components + parseDefaults(); + // 3. Allocate component structures + predictComponentAllocation(); +} + +EntityXMLFile::~EntityXMLFile() +{ + using namespace xercesc; + + if (m_DOMParser != nullptr) { + delete m_DOMParser; + } + if (m_ErrorHandler != nullptr) { + delete m_ErrorHandler; + } + if (m_GrammarPool != nullptr) { + delete m_GrammarPool; + } + + InstanceCount--; + if (InstanceCount == 0) { + XMLPlatformUtils::Terminate(); + } +} + +void EntityXMLFile::PopulateWorld(World* world) +{ + for (auto& pair : m_ComponentInfo) { + world->RegisterComponent(pair.second); + } + + // 4. Parse entity hierarchy + auto root = m_DOMDocument->getDocumentElement(); + parseEntityGraph(world, root, 0); +} + + +void EntityXMLFile::preprocess(std::string inPath, std::string outPath) +{ + using namespace xercesc; + + static const XMLCh gLS[] = { 'L', 'S', '\0' }; + DOMImplementationLS* di = static_cast(DOMImplementationRegistry::getDOMImplementation(gLS)); + + // Parse the file + DOMLSParser* parser = di->createLSParser(DOMImplementationLS::MODE_SYNCHRONOUS, nullptr); + DOMConfiguration* config = parser->getDomConfig(); + config->setParameter(XMLUni::fgDOMNamespaces, true); + config->setParameter(XMLUni::fgXercesSchema, true); + config->setParameter(XMLUni::fgXercesHandleMultipleImports, true); + config->setParameter(XMLUni::fgXercesSchemaFullChecking, true); + config->setParameter(XMLUni::fgXercesDoXInclude, true); + auto errHandler = new EntityPreprocessorXMLErrorHandler(); + config->setParameter(XMLUni::fgDOMErrorHandler, errHandler); + + auto source = new LocalFileInputSource(XSTR(inPath.c_str())); + Wrapper4InputSource* domSourceWrapper = new Wrapper4InputSource(source); + DOMDocument* doc = parser->parse(dynamic_cast(domSourceWrapper)); + + // Serialize and output the new XML + DOMLSSerializer* writer = di->createLSSerializer(); + DOMLSOutput* output = di->createLSOutput(); + XMLFormatTarget* formatTarget = new LocalFileFormatTarget(outPath.c_str()); + // TODO: MemBufFormatTarget* formatTarget = new MemBufFormatTarget() + output->setByteStream(formatTarget); + writer->write(doc, output); + + delete formatTarget; + output->release(); + writer->release(); + parser->release(); +} + +void EntityXMLFile::parseComponentInfo() +{ + using namespace xercesc; + bool wasChanged; + XSModel* xsModel = m_GrammarPool->getXSModel(wasChanged); + + // Find component xsd element declarations + std::cout << "Enumerating components..." << std::endl; + // + auto topLevelElements = xsModel->getComponents(XSConstants::ELEMENT_DECLARATION); + for (unsigned int i = 0; i < topLevelElements->getLength(); ++i) { + auto element = static_cast(topLevelElements->item(i)); + + std::string nameSpace(XSTR(element->getNamespace())); + if (nameSpace != "components") { + continue; + } + + ComponentInfo compInfo; + + // Name + compInfo.Name = XSTR(element->getName()); + // Annotation + auto componentAnnotation = element->getAnnotation(); + if (componentAnnotation != nullptr) { + // Parse annotation XML + char* annotationString = XMLString::transcode(componentAnnotation->getAnnotationString()); + MemBufInputSource annotationInput(reinterpret_cast(annotationString), strlen(annotationString), "MemBuf: Annotation String"); + XercesDOMParser parser(nullptr, XMLPlatformUtils::fgMemoryManager, m_GrammarPool); + parser.setErrorHandler(m_ErrorHandler); + parser.parse(annotationInput); + XMLString::release(&annotationString); + auto doc = parser.getDocument(); + + // Add allocation estimation(s) + auto allocationTags = doc->getElementsByTagName(XSTR("meta:allocation")); + for (int i = 0; i < allocationTags->getLength(); ++i) { + auto allocation = dynamic_cast(allocationTags->item(i)); + auto child = allocation->getFirstChild(); + if (child == nullptr) { + continue; + } + + XSValue::Status status; + XSValue* val = XSValue::getActualValue(child->getNodeValue(), XSValue::dt_integer, status); + compInfo.Meta.Allocation += val->fData.fValue.f_int; + } + + // Save documentation string + auto documentationTags = doc->getElementsByTagName(XSTR("xs:documentation")); + if (documentationTags->getLength() != 0) { + auto child = documentationTags->item(0)->getFirstChild(); + if (child != nullptr) { + compInfo.Meta.Annotation = XSTR(child->getNodeValue()); + } + } + // TODO: Parse annotation string XML + // compInfo.Meta.Allocation = ... + } else { + std::cout << "Warning: Component is missing an annotation!" << std::endl; + } + + // + auto typeDefinition = element->getTypeDefinition(); + if (typeDefinition->getTypeCategory() != XSTypeDefinition::COMPLEX_TYPE) { + std::cerr << "Error: Type definition wasn't COMPLEX_TYPE! Skipping." << std::endl; + continue; + } + auto complexTypeDefinition = dynamic_cast(typeDefinition); + + // + auto modelGroupParticle = complexTypeDefinition->getParticle(); + if (modelGroupParticle->getTermType() != XSParticle::TERM_MODELGROUP) { + std::cerr << "Error: Model group particle wasn't TERM_MODELGROUP! Skipping." << std::endl; + continue; + } + auto modelGroup = modelGroupParticle->getModelGroupTerm(); + + // getParticles(); + for (unsigned int i = 0; i < particles->size(); ++i) { + auto particle = particles->elementAt(i); + if (particle->getTermType() != XSParticle::TERM_ELEMENT) { + std::cerr << "Error: Particle wasn't TERM_ELEMENT! Skipping." << std::endl; + continue; + } + auto elementDeclaration = particle->getElementTerm(); + + std::string name = XSTR(elementDeclaration->getName()); + std::string type = XSTR(elementDeclaration->getTypeDefinition()->getName()); + + size_t stride = getTypeStride(type); + if (stride == 0) { + std::cout << "Warning: Field \"" << name << "\" in component \"" << compInfo.Name << "\" uses unexpected field type \"" << type << "\". Skipping." << std::endl; + continue; + } + + compInfo.FieldTypes[name] = type; + compInfo.FieldOffsets[name] = fieldOffset; + fieldOffset += getTypeStride(type); + } + + compInfo.Meta.Stride = fieldOffset; + m_ComponentInfo[compInfo.Name] = compInfo; + } +} + +void EntityXMLFile::parseDefaults() +{ + using namespace xercesc; + + for (auto& ci : m_ComponentInfo) { + // Allocate memory for default values + ci.second.Defaults = std::shared_ptr(new char[ci.second.Meta.Stride]); + memset(ci.second.Defaults.get(), 0, ci.second.Meta.Stride); + + XercesDOMParser parser(nullptr, XMLPlatformUtils::fgMemoryManager); + parser.setErrorHandler(m_ErrorHandler); + + std::string componentName = ci.first; + LOG_DEBUG("Parsing defaults for component %s", componentName.c_str()); + boost::filesystem::path defaultsFile = "Schema/Components/" + componentName + ".xml"; + + parser.parse(defaultsFile.string().c_str()); + auto doc = parser.getDocument(); + if (doc == nullptr) { + LOG_ERROR("%s not found! Skipping.", defaultsFile.string().c_str()); + continue; + } + + // Find the node in the components namespace matching the component name + std::string tagName = "c:" + componentName; + auto rootNodes = doc->getElementsByTagName(XSTR(tagName.c_str())); + if (rootNodes->getLength() == 0) { + LOG_ERROR("Couldn't find defaults for component \"%s\"! Skipping.", componentName.c_str()); + continue; + } + auto componentElement = dynamic_cast(rootNodes->item(0)); + + // Fill the default value buffer with values + for (auto& field : ci.second.FieldOffsets) { + std::string fieldName = field.first; + auto fieldNodes = componentElement->getElementsByTagName(XSTR(fieldName.c_str())); + auto fieldNode = fieldNodes->item(0); + if (fieldNode == nullptr) { + LOG_ERROR("Defaults for component \"%s\" is missing field \"%s\"!", componentName.c_str(), fieldName.c_str()); + continue; + } + auto fieldElement = dynamic_cast(fieldNode); + + std::string fieldType = ci.second.FieldTypes.at(fieldName); + unsigned int fieldOffset = ci.second.FieldOffsets.at(fieldName); + writeData(fieldElement, fieldType, ci.second.Defaults.get() + fieldOffset); + } + } +} + +void EntityXMLFile::predictComponentAllocation() +{ + using namespace xercesc; + + auto root = m_DOMDocument->getDocumentElement(); + + // Count static instances of components present in entity hierarchy + auto components = m_DOMDocument->getElementsByTagNameNS(XSTR("components"), XSTR("*")); + for (int i = 0; i < components->getLength(); ++i) { + auto component = dynamic_cast(components->item(i)); + + std::string componentName = XSTR(component->getLocalName()); + auto& compInfo = m_ComponentInfo.at(componentName); + compInfo.Meta.Allocation += 1; + } + + std::cout << "COMPONENT INFO" << std::endl; + for (auto& pair : m_ComponentInfo) { + ComponentInfo& ci = pair.second; + std::cout << "Component: " << ci.Name << " (" << ci.Meta.Annotation << ")" << std::endl; + std::cout << " Allocation: " << ci.Meta.Allocation << std::endl; + std::cout << " Fields:" << std::endl; + + // Calculate component size + std::size_t stride = 0; + // Add size of fields + for (auto& field : ci.FieldTypes) { + std::cout << " " << field.second << " " << field.first << " (" << getTypeStride(field.second) << " byte)" << std::endl; + stride += getTypeStride(field.second); + } + std::cout << " Stride: " << ci.Meta.Stride << std::endl; + } +} + +void EntityXMLFile::parseEntityGraph(World* world, xercesc::DOMElement* element, EntityID parentEntity) +{ + using namespace xercesc; + + // Create entity + EntityID entity = world->CreateEntity(parentEntity); + LOG_DEBUG("Created entity %i, parent %i", entity, parentEntity); + + // Add components + auto components = m_DOMDocument->evaluate(XSTR("Components/*"), element, nullptr, DOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE, nullptr); + for (int i = 0; i < components->getSnapshotLength(); i++) { + components->snapshotItem(i); + auto componentElement = dynamic_cast(components->getNodeValue()); + std::string componentName = XSTR(componentElement->getLocalName()); + auto& ci = m_ComponentInfo.at(componentName); + + // Attach the component + auto c = world->AttachComponent(entity, componentName); + LOG_DEBUG("Attached %s component", componentName.c_str()); + + // Write field data + auto fields = componentElement->getChildNodes(); + for (int j = 0; j < fields->getLength(); ++j) { + auto fieldNode = fields->item(j); + auto nodeType = fieldNode->getNodeType(); + if (nodeType != DOMNode::ELEMENT_NODE) { + continue; + } + auto field = dynamic_cast(fields->item(j)); + //const XMLCh* value = fields->item(j)->getTextContent(); + std::string fieldName(XSTR(field->getLocalName())); + if (ci.FieldTypes.find(fieldName) == ci.FieldTypes.end()) { + std::cout << "Warning: Component \"" << componentName << "\" contains invalid field \"" << fieldName << "\". Skipping." << std::endl; + continue; + } + + std::string fieldType = ci.FieldTypes.at(fieldName); + unsigned int fieldOffset = ci.FieldOffsets.at(fieldName); + std::string fieldValue(XSTR(field->getTextContent())); + LOG_DEBUG(" %s %s = %s", fieldType.c_str(), fieldName.c_str(), fieldValue.c_str()); + writeData(field, fieldType, c.Data + fieldOffset); + } + } + + // Recurse children + auto children = m_DOMDocument->evaluate(XSTR("Children/Entity"), element, nullptr, DOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE, nullptr); + for (int i = 0; i < children->getSnapshotLength(); i++) { + children->snapshotItem(i); + parseEntityGraph(world, dynamic_cast(children->getNodeValue()), entity); + } + + //auto components = m_DOMDocument->getElementsByTagNameNS(XSTR("components"), XSTR("*")); + //for (int i = 0; i < components->getLength(); ++i) { + // auto component = dynamic_cast(components->item(i)); + + // std::string componentName = XSTR(component->getLocalName()); + // auto& compStore = m_ComponentStore.at(componentName); + // auto& compInfo = compStore.Info; + + // char* data = &compStore.Data[compStore.Size*compStore.Stride]; + // compStore.Size += 1; + + // auto fields = component->getChildNodes(); + // for (int j = 0; j < fields->getLength(); ++j) { + // auto field = fields->item(j); + // auto nodeType = field->getNodeType(); + // if (nodeType != DOMNode::ELEMENT_NODE) { + // continue; + // } + // //auto field = dynamic_cast(fields->item(j)); + // //const XMLCh* value = fields->item(j)->getTextContent(); + // std::string fieldName = XSTR(field->getLocalName()); + // if (compInfo.FieldTypes.find(fieldName) == compInfo.FieldTypes.end()) { + // std::cout << "Warning: Component \"" << componentName << "\" contains invalid field \"" << fieldName << "\". Skipping." << std::endl; + // continue; + // } + + // std::string fieldType = compInfo.FieldTypes.at(fieldName); + // unsigned int fieldOffset = compInfo.FieldOffsets.at(fieldName); + + // XSValue::DataType dataType = XSValue::getDataType(XSTR(fieldType.c_str())); + // if (dataType == XSValue::DataType::dt_MAXCOUNT) { + // // TODO: + // continue; + // } + // if (dataType == XSValue::DataType::dt_string) { + // char* str = XMLString::transcode(field->getTextContent()); + // std::string standardString(str); + // XMLString::release(&str); + // memcpy(&data[fieldOffset], reinterpret_cast(&standardString), getTypeStride(fieldType)); + // } else { + // XSValue::Status status; + // XSValue* val = XSValue::getActualValue(field->getTextContent(), dataType, status); + // memcpy(&data[fieldOffset], reinterpret_cast(&val->fData.fValue), getTypeStride(fieldType)); + // } + // } + //} + + //auto entities = m_DOMDocument->getElementsByTagName(XSTR("Entity")); + //for (int i = 0; i < entities->getLength(); ++i) { + // auto entity = dynamic_cast(entities->item(i)); + + + // //entity->setIdAttribute() + + // std::cout << "ENTITY " << i + 1 << std::endl; + //} +} + +std::size_t EntityXMLFile::getTypeStride(std::string typeName) +{ + std::map typeStrides{ + { "bool", sizeof(bool) }, + { "int", sizeof(int) }, + { "float", sizeof(float) }, + { "double", sizeof(double) }, + { "string", sizeof(std::string) }, + { "Vector", sizeof(glm::vec3) }, + { "Quaternion", sizeof(glm::quat) }, + { "Color", sizeof(glm::vec4) } + }; + + auto it = typeStrides.find(typeName); + return (it != typeStrides.end()) ? it->second : 0; +} + +float EntityXMLFile::getFloatAttribute(const xercesc::DOMElement* element, const char* attribute) const +{ + using namespace xercesc; + XSValue::Status status; + XSValue* val = XSValue::getActualValue(element->getAttribute(XSTR(attribute)), xercesc::XSValue::DataType::dt_double, status); + if (val == nullptr) { + LOG_ERROR("Element \"%s\" doesn't have an \"%s\" attribute!", XSTR(element->getTagName()), attribute); + return 0.f; + } else { + return static_cast(val->fData.fValue.f_double); + } +} + +void EntityXMLFile::writeData(const xercesc::DOMElement* element, std::string typeName, char* outData) +{ + using namespace xercesc; + + if (typeName == "Vector") { + glm::vec3 vec; + vec.x = getFloatAttribute(element, "X"); + vec.y = getFloatAttribute(element, "Y"); + vec.z = getFloatAttribute(element, "Z"); + memcpy(outData, reinterpret_cast(&vec), getTypeStride(typeName)); + } else if (typeName == "Color") { + glm::vec4 vec; + vec.r = getFloatAttribute(element, "R"); + vec.g = getFloatAttribute(element, "G"); + vec.b = getFloatAttribute(element, "B"); + vec.a = getFloatAttribute(element, "A"); + memcpy(outData, reinterpret_cast(&vec), getTypeStride(typeName)); + } else if (typeName == "Quaternion") { + glm::quat q; + q.x = getFloatAttribute(element, "X"); + q.y = getFloatAttribute(element, "Y"); + q.z = getFloatAttribute(element, "Z"); + q.w = getFloatAttribute(element, "W"); + memcpy(outData, reinterpret_cast(&q), getTypeStride(typeName)); + } else if (typeName == "float") { + XSValue::Status status; + XSValue* val = XSValue::getActualValue(element->getTextContent(), xercesc::XSValue::DataType::dt_float, status); + memcpy(outData, reinterpret_cast(&val->fData.fValue.f_float), getTypeStride(typeName)); + } else if (typeName == "double") { + XSValue::Status status; + XSValue* val = XSValue::getActualValue(element->getTextContent(), xercesc::XSValue::DataType::dt_double, status); + memcpy(outData, reinterpret_cast(&val->fData.fValue.f_double), getTypeStride(typeName)); + } else if (typeName == "bool") { + XSValue::Status status; + XSValue* val = XSValue::getActualValue(element->getTextContent(), xercesc::XSValue::DataType::dt_boolean, status); + memcpy(outData, reinterpret_cast(&val->fData.fValue.f_bool), getTypeStride(typeName)); + } else { + XSValue::DataType dataType = XSValue::getDataType(XSTR(typeName.c_str())); + if (dataType == XSValue::DataType::dt_string) { + char* str = XMLString::transcode(element->getTextContent()); + std::string standardString(str); + new (outData) std::string(str); + XMLString::release(&str); + //memcpy(outData, reinterpret_cast(&standardString), getTypeStride(typeName)); + } else { + //XSValue::Status status; + //XSValue* val = XSValue::getActualValue(element->getTextContent(), dataType, status); + //memcpy(outData, reinterpret_cast(&val->fData.fValue), getTypeStride(typeName)); + LOG_WARNING("Unknown native data type: %s", typeName.c_str()); + } + } +} + diff --git a/src/Engine/Core/EventBroker.cpp b/src/Engine/Core/EventBroker.cpp index 76c243e8..d847e1a2 100644 --- a/src/Engine/Core/EventBroker.cpp +++ b/src/Engine/Core/EventBroker.cpp @@ -2,21 +2,24 @@ BaseEventRelay::~BaseEventRelay() { - if (m_Broker != nullptr) { + if (m_Broker != nullptr) { m_Broker->Unsubscribe(*this); - } + } } -void EventBroker::Unsubscribe(BaseEventRelay &relay) // ? +void EventBroker::Unsubscribe(BaseEventRelay& relay) // ? { - if (m_IsProcessing) { - m_RelaysToUnsubscribe.push_back(&relay); - } else { - unsubscribeImmediate(relay); - } + auto identifier = std::make_tuple(relay.m_EventID, relay.m_ContextTypeName, relay.m_EventTypeName); + + relay.m_Broker = nullptr; + if (m_IsProcessing) { + m_RelaysToUnsubscribe.push_back(identifier); + } else { + unsubscribeImmediate(identifier); + } } -void EventBroker::Subscribe(BaseEventRelay &relay) +void EventBroker::Subscribe(BaseEventRelay& relay) { if (m_IsProcessing) { m_RelaysToSubscribe.push_back(&relay); @@ -38,12 +41,11 @@ int EventBroker::Process(std::string contextTypeName) int eventsProcessed = 0; for (auto &pair : *m_EventQueueRead) { - std::string &eventTypeName = pair.first; + std::string& eventTypeName = pair.first; std::shared_ptr event = pair.second; auto itpair = relays.equal_range(eventTypeName); - for (auto it2 = itpair.first; it2 != itpair.second; it2++) - { + for (auto it2 = itpair.first; it2 != itpair.second; it2++) { std::string name = it2->first; BaseEventRelay* relay = it2->second; relay->Receive(event); @@ -60,8 +62,8 @@ int EventBroker::Process(std::string contextTypeName) m_RelaysToSubscribe.clear(); // Process pending unsubscriptions - for (auto& r : m_RelaysToUnsubscribe) { - unsubscribeImmediate(*r); + for (auto& identifier : m_RelaysToUnsubscribe) { + unsubscribeImmediate(identifier); } m_RelaysToUnsubscribe.clear(); @@ -81,21 +83,26 @@ void EventBroker::Clear() void EventBroker::subscribeImmediate(BaseEventRelay& relay) { relay.m_Broker = this; + relay.m_EventID = m_NextEventID++; m_ContextRelays[relay.m_ContextTypeName].insert(std::make_pair(relay.m_EventTypeName, &relay)); } -void EventBroker::unsubscribeImmediate(BaseEventRelay& relay) +void EventBroker::unsubscribeImmediate(std::tuple identifier) { - auto contextIt = m_ContextRelays.find(relay.m_ContextTypeName); + EventID eventID; + ContextTypeName_t contextTypeName; + EventTypeName_t eventTypeName; + std::tie(eventID, contextTypeName, eventTypeName) = identifier; + + auto contextIt = m_ContextRelays.find(contextTypeName); if (contextIt == m_ContextRelays.end()) { return; } auto eventRelays = contextIt->second; - auto itpair = eventRelays.equal_range(relay.m_EventTypeName); + auto itpair = eventRelays.equal_range(eventTypeName); for (auto it = itpair.first; it != itpair.second; ++it) { - if (it->second == &relay) { - relay.m_Broker = nullptr; + if (it->second->m_EventID == eventID) { eventRelays.erase(it); break; } diff --git a/src/Engine/Core/InputManager.cpp b/src/Engine/Core/InputManager.cpp index 597fd7f6..50dcac7c 100644 --- a/src/Engine/Core/InputManager.cpp +++ b/src/Engine/Core/InputManager.cpp @@ -1,10 +1,17 @@ #include "Core/InputManager.h" +std::vector InputManager::GLFWCharCallbackQueue; +std::vector> InputManager::GLFWScrollCallbackQueue; +std::vector InputManager::GLFWDropCallbackQueue; + void InputManager::Initialize() { // TODO: Gamepad //m_LastGamepadAxisState = std::array(); //m_LastGamepadButtonState = std::array(); + glfwSetCharCallback(m_GLFWWindow, &InputManager::GLFWCharCallback); + glfwSetScrollCallback(m_GLFWWindow, &InputManager::GLFWScrollCallback); + glfwSetDropCallback(m_GLFWWindow, &InputManager::GLFWDropCallback); EVENT_SUBSCRIBE_MEMBER(m_ELockMouse, &InputManager::OnLockMouse); EVENT_SUBSCRIBE_MEMBER(m_EUnlockMouse, &InputManager::OnUnlockMouse); @@ -36,6 +43,15 @@ void InputManager::Update(double dt) } } + // Keyboard text input + for (unsigned int& c : GLFWCharCallbackQueue) { + Events::KeyboardChar e; + e.Timestamp = glfwGetTime(); + e.Char = c; + m_EventBroker->Publish(e); + } + GLFWCharCallbackQueue.clear(); + // Mouse buttons for (int i = 0; i <= GLFW_MOUSE_BUTTON_LAST; ++i) { m_CurrentMouseState[i] = glfwGetMouseButton(m_GLFWWindow, i); @@ -73,6 +89,22 @@ void InputManager::Update(double dt) m_EventBroker->Publish(e); } + // Mouse scroll + for (auto& pair : GLFWScrollCallbackQueue) { + Events::MouseScroll e; + std::tie(e.DeltaX, e.DeltaY) = pair; + m_EventBroker->Publish(e); + } + GLFWScrollCallbackQueue.clear(); + + // File drop + for (auto& path : GLFWDropCallbackQueue) { + Events::FileDropped e; + e.Path = path; + m_EventBroker->Publish(e); + } + GLFWDropCallbackQueue.clear(); + // // Lock mouse while holding LMB // if (m_CurrentMouseState[GLFW_MOUSE_BUTTON_LEFT]) // { @@ -196,6 +228,25 @@ void InputManager::PublishGamepadButtonIfChanged(int gamepadID, Gamepad::Button } } +void InputManager::GLFWCharCallback(GLFWwindow* window, unsigned int c) +{ + GLFWCharCallbackQueue.push_back(c); +} + + +void InputManager::GLFWScrollCallback(GLFWwindow* window, double xoffset, double yoffset) +{ + GLFWScrollCallbackQueue.push_back(std::make_pair(xoffset, yoffset)); +} + + +void InputManager::GLFWDropCallback(GLFWwindow* window, int count, const char* paths[]) +{ + for (int i = 0; i < count; i++) { + GLFWDropCallbackQueue.push_back(std::string(paths[i])); + } +} + bool InputManager::OnLockMouse(const Events::LockMouse &event) { m_MouseLocked = true; diff --git a/src/Engine/Core/OctTree.cpp b/src/Engine/Core/OctTree.cpp new file mode 100644 index 00000000..c1d8c64e --- /dev/null +++ b/src/Engine/Core/OctTree.cpp @@ -0,0 +1,377 @@ +#include +#include +#include + +#include "Core/OctTree.h" +#include "Collision/Collision.h" + +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; +} + +} + +OctTree::OctTree() + : OctTree(AABB(), 0) +{} + +OctTree::OctTree(const AABB& octTreeBounds, int subDivisions) + : 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& 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& staticObjects, + std::vector& dynamicObjects) + : m_Box(octTreeBounds) + , m_StaticObjectsRef(staticObjects) + , m_DynamicObjectsRef(dynamicObjects) +{ + if (subDivisions == 0) { + for (OctChild*& 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 OctChild(AABB(minPos, maxPos), subDivisions, m_StaticObjectsRef, m_DynamicObjectsRef); + } + } +} + +OctTree::OctChild::~OctChild() +{ + for (OctChild*& c : m_Children) { + if (c != nullptr) { + delete c; + c = nullptr; + } + } +} + +bool OctTree::OctChild::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 (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::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)) { + //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 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 (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; + return intersected; + } + } + return false; +} + + +void OctTree::OctChild::AddDynamicObject(const AABB& box) +{ + if (hasChildren()) { + for (auto i : childIndicesContainingBox(box)) { + m_Children[i]->AddDynamicObject(box); + } + } else { + //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::OctChild::AddStaticObject(const AABB& box) +{ + if (hasChildren()) { + for (auto i : childIndicesContainingBox(box)) { + m_Children[i]->AddStaticObject(box); + } + } else { + //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::OctChild::BoxesInSameRegion(const AABB& box, std::vector& outBoxes) const +{ + if (hasChildren()) { + for (auto i : childIndicesContainingBox(box)) { + m_Children[i]->BoxesInSameRegion(box, outBoxes); + } + } else { + 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::OctChild::ClearObjects() +{ + if (hasChildren()) { + for (OctChild*& c : m_Children) { + c->ClearObjects(); + } + } else { + m_DynamicObjIndices.clear(); + m_StaticObjIndices.clear(); + } +} + +void OctTree::OctChild::ClearDynamicObjects() +{ + if (hasChildren()) { + for (OctChild*& c : m_Children) { + c->ClearObjects(); + } + } else { + m_DynamicObjIndices.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::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 OctTree::OctChild::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 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(); + } +} + +inline bool OctTree::OctChild::hasChildren() const +{ + return m_Children[0] != nullptr; +} \ No newline at end of file diff --git a/src/Engine/Core/Ray.cpp b/src/Engine/Core/Ray.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/Engine/Core/ResourceManager.cpp b/src/Engine/Core/ResourceManager.cpp index 325bfab8..62a60f14 100644 --- a/src/Engine/Core/ResourceManager.cpp +++ b/src/Engine/Core/ResourceManager.cpp @@ -35,6 +35,20 @@ void ResourceManager::Reload(std::string resourceName) } } + +void ResourceManager::Release(std::string resourceType, std::string resourceName) +{ + auto key = std::make_pair(resourceType, resourceName); + if (m_ResourceCache.find(key) == m_ResourceCache.end()) { + return; + } + auto resource = m_ResourceCache.at(key); + m_ResourceCache.erase(key); + m_ResourceFromName.erase(resourceName); + m_ResourceParents.erase(resource); + delete resource; +} + unsigned int ResourceManager::GetNewResourceID(unsigned int typeID) { return m_ResourceCount[typeID]++; @@ -86,7 +100,7 @@ Resource* ResourceManager::Load(std::string resourceType, std::string resourceNa LOG_WARNING("Hot-loading resource \"%s\"", resourceName.c_str()); } - return CreateResource(resourceType, resourceName, parent); + return CreateResource(resourceType, resourceName, parent); } Resource* ResourceManager::CreateResource(std::string resourceType, std::string resourceName, Resource* parent) @@ -98,10 +112,16 @@ Resource* ResourceManager::CreateResource(std::string resourceType, std::string } // Call the factory function - Resource* resource = facIt->second(resourceName); - // Store IDs - resource->TypeID = GetTypeID(resourceType); - resource->ResourceID = GetNewResourceID(resource->TypeID); + Resource* resource; + try { + resource = facIt->second(resourceName); + // Store IDs + resource->TypeID = GetTypeID(resourceType); + resource->ResourceID = GetNewResourceID(resource->TypeID); + } catch (const std::exception& e) { + resource = nullptr; + LOG_ERROR("Failed to load resource \"%s\" of type \"%s\": %s", resourceName.c_str(), resourceType.c_str(), e.what()); + } // Cache m_ResourceCache[std::make_pair(resourceType, resourceName)] = resource; m_ResourceFromName[resourceName] = resource; diff --git a/src/Engine/Core/World.cpp b/src/Engine/Core/World.cpp index 26d3f1e4..5443d388 100644 --- a/src/Engine/Core/World.cpp +++ b/src/Engine/Core/World.cpp @@ -11,12 +11,43 @@ EntityID World::CreateEntity(EntityID parent /*= 0*/) { EntityID newEntity = generateEntityID(); m_EntityParents[newEntity] = parent; - if (parent != 0) { - m_EntityChildren.insert(std::make_pair(parent, newEntity)); - } + m_EntityChildren.insert(std::make_pair(parent, newEntity)); return newEntity; } + +void World::DeleteEntity(EntityID entity) +{ + // Delete components + for (auto& pair : m_ComponentPools) { + auto& pool = pair.second; + if (pool->KnowsEntity(entity)) { + auto& c = pool->GetByEntity(entity); + pool->Delete(c); + } + } + + // Loop through children + std::vector childrenToDelete; + auto children = m_EntityChildren.equal_range(entity); + for (auto it = children.first; it != children.second; ++it) { + childrenToDelete.push_back(it->second); + } + for (auto& child : childrenToDelete) { + DeleteEntity(child); + } + + EntityID parent = m_EntityParents.at(entity); + m_EntityParents.erase(entity); + auto parentChildren = m_EntityChildren.equal_range(parent); + for (auto it = parentChildren.first; it != parentChildren.second; ++it) { + if (it->second == entity) { + m_EntityChildren.erase(it); + break; + } + } +} + void World::RegisterComponent(ComponentInfo& ci) { m_ComponentPools[ci.Name] = new ComponentPool(ci); @@ -35,15 +66,53 @@ ComponentWrapper World::AttachComponent(EntityID entity, std::string componentTy return c; } + +bool World::HasComponent(EntityID entity, std::string componentType) +{ + ComponentPool* pool = m_ComponentPools.at(componentType); + return pool->KnowsEntity(entity); +} + ComponentWrapper World::GetComponent(EntityID entity, std::string componentType) { ComponentPool* pool = m_ComponentPools.at(componentType); return pool->GetByEntity(entity); } -const ComponentPool& World::GetComponents(std::string componentType) + +void World::DeleteComponent(EntityID entity, std::string componentType) { - return *m_ComponentPools.at(componentType); + ComponentPool* pool = m_ComponentPools.at(componentType); + ComponentWrapper c = pool->GetByEntity(entity); + return pool->Delete(c); +} + +const ComponentPool* World::GetComponents(std::string componentType) +{ + auto it = m_ComponentPools.find(componentType); + return (it != m_ComponentPools.end()) ? it->second : nullptr; +} + + +EntityID World::GetParent(EntityID entity) +{ + return m_EntityParents.at(entity); +} + + +void World::SetParent(EntityID entity, EntityID parent) +{ + EntityID lastParent = m_EntityParents.at(entity); + auto parentChildren = m_EntityChildren.equal_range(lastParent); + for (auto it = parentChildren.first; it != parentChildren.second; it++) { + if (it->second == entity) { + m_EntityChildren.erase(it); + break; + } + } + + m_EntityParents[entity] = parent; + m_EntityChildren.insert(std::make_pair(parent, entity)); } EntityID World::generateEntityID() diff --git a/src/Engine/Editor/EditorSystem.cpp b/src/Engine/Editor/EditorSystem.cpp new file mode 100644 index 00000000..a43ca900 --- /dev/null +++ b/src/Engine/Editor/EditorSystem.cpp @@ -0,0 +1,596 @@ +#include "Editor/EditorSystem.h" +#define IMGUI_DEFINE_MATH_OPERATORS +#include + +EditorSystem::EditorSystem(EventBroker* eventBroker, IRenderer* renderer) + : ImpureSystem(eventBroker) + , m_Renderer(renderer) +{ + auto config = ResourceManager::Load("Config.ini"); + m_Enabled = config->Get("Debug.EditorEnabled", false); + m_Visible = m_Enabled; + + if (!m_Enabled) { + return; + } + + EVENT_SUBSCRIBE_MEMBER(m_EInputCommand, &EditorSystem::OnInputCommand); + EVENT_SUBSCRIBE_MEMBER(m_EMousePress, &EditorSystem::OnMousePress); + EVENT_SUBSCRIBE_MEMBER(m_EMouseRelease, &EditorSystem::OnMouseRelease); + EVENT_SUBSCRIBE_MEMBER(m_EMouseMove, &EditorSystem::OnMouseMove); + EVENT_SUBSCRIBE_MEMBER(m_EPicking, &EditorSystem::OnPicking); + EVENT_SUBSCRIBE_MEMBER(m_EFileDropped, &EditorSystem::OnFileDropped); +} + +void EditorSystem::Update(World* world, double dt) +{ + m_World = world; + + if (!m_Enabled) { + return; + } + + if (!m_Visible) { + return; + } + + updateWidget(); + + drawUI(world, dt); + + // Clear drop queue if it wasn't handled by any UI element + if (!m_LastDroppedFile.empty()) { + m_LastDroppedFile = ""; + } +} + +bool EditorSystem::OnInputCommand(const Events::InputCommand& e) +{ + if (e.Command == "ToggleEditor" && e.Value > 0) { + m_Visible = !m_Visible; + } + + if (e.Command == "EditorToolMove" && e.Value > 0) { + setWidgetMode(WidgetMode::Translate); + } + if (e.Command == "EditorToolRotate" && e.Value > 0) { + setWidgetMode(WidgetMode::Rotate); + } + if (e.Command == "EditorToolScale" && e.Value > 0) { + setWidgetMode(WidgetMode::Scale); + } + + if (e.Command == "EditorToggleTransformSpace" && e.Value > 0) { + if (m_WidgetSpace == WidgetSpace::Global) { + setWidgetSpace(WidgetSpace::Local); + } else if (m_WidgetSpace == WidgetSpace::Local) { + setWidgetSpace(WidgetSpace::Global); + } + } + + return true; +} + +bool EditorSystem::OnMousePress(const Events::MousePress& e) +{ + if (e.Button == GLFW_MOUSE_BUTTON_RIGHT) { + m_PickingQueue.push_back(glm::vec2((int)e.X, (int)e.Y)); + } + return true; +} + +bool EditorSystem::OnMouseMove(const Events::MouseMove& e) +{ + if (m_Widget == 0) { + return false; + } + + if (m_Selection == 0) { + return false; + } + auto widgetTransform = m_World->GetComponent(m_Widget, "Transform"); + glm::vec3 widgetOrientation = widgetTransform["Orientation"]; + + glm::quat totalOrientation = m_Renderer->Camera()->Orientation() * glm::inverse(glm::quat(widgetOrientation)); + + int width; + int height; + glfwGetFramebufferSize(m_Renderer->Window(), &width, &height); + Rectangle res(width, height); + + glm::vec2 delta2(res.Width / 2.f + e.DeltaX, res.Height / 2.f + -e.DeltaY); + glm::vec3 deltaWorld = ScreenCoords::ToWorldPos( + delta2, + m_WidgetPickingDepth, + res, + m_Renderer->Camera()->ProjectionMatrix(), + glm::toMat4(glm::inverse(totalOrientation)) + ); + glm::vec3 origin = ScreenCoords::ToWorldPos( + glm::vec2(res.Width / 2.f, res.Height / 2.f), + m_WidgetPickingDepth, + res, + m_Renderer->Camera()->ProjectionMatrix(), + glm::toMat4(glm::inverse(totalOrientation)) + ); + deltaWorld = deltaWorld - origin; + glm::vec3 movement = deltaWorld * m_WidgetCurrentAxis; + + if (glm::length2(m_WidgetCurrentAxis) > 0.f) { + auto widgetTransform = m_World->GetComponent(m_Widget, "Transform"); + if (m_WidgetMode == WidgetMode::Translate) { + if (m_WidgetSpace == WidgetSpace::Global) { + EntityID parent = m_World->GetParent(m_Selection); + glm::quat inverseParentOrientation; + if (parent != 0) { + inverseParentOrientation = glm::inverse(RenderQueueFactory::AbsoluteOrientation(m_World, parent)); + } + (glm::vec3&)m_World->GetComponent(m_Selection, "Transform")["Position"] += inverseParentOrientation * movement; + } else if (m_WidgetSpace == WidgetSpace::Local) { + auto selectionTransform = m_World->GetComponent(m_Selection, "Transform"); + (glm::vec3&)selectionTransform["Position"] += glm::quat((glm::vec3)selectionTransform["Orientation"]) * movement; + } + } else if (m_WidgetMode == WidgetMode::Rotate) { + glm::vec3 finalMovement; + finalMovement.x = -deltaWorld.y * m_WidgetCurrentAxis.x; + finalMovement.y = deltaWorld.x * m_WidgetCurrentAxis.y; + finalMovement.z = deltaWorld.y * m_WidgetCurrentAxis.z; + if (m_WidgetSpace == WidgetSpace::Global) { + EntityID parent = m_World->GetParent(m_Selection); + glm::quat parentOrientation; + if (parent != 0) { + parentOrientation = RenderQueueFactory::AbsoluteOrientation(m_World, parent); + } + glm::vec3& selectionOrientation = m_World->GetComponent(m_Selection, "Transform")["Orientation"]; + //glm::quat currentOrientation = RenderQueueFactory::AbsoluteOrientation(m_World, m_Selection); + glm::quat currentOrientation = parentOrientation * glm::quat(selectionOrientation); + glm::quat deltaOrientation(finalMovement); + selectionOrientation = glm::eulerAngles(glm::inverse(parentOrientation) * (deltaOrientation * currentOrientation)); + } else if (m_WidgetSpace == WidgetSpace::Local) { + glm::vec3& selectionOrientation = m_World->GetComponent(m_Selection, "Transform")["Orientation"]; + glm::quat currentOrientation(selectionOrientation); + glm::quat deltaOrientation(finalMovement); + selectionOrientation = glm::eulerAngles(currentOrientation * deltaOrientation); + } + } else if (m_WidgetMode == WidgetMode::Scale) { + glm::vec3& scaleX = m_World->GetComponent(m_WidgetX, "Transform")["Scale"]; + glm::vec3& scaleY = m_World->GetComponent(m_WidgetY, "Transform")["Scale"]; + glm::vec3& scaleZ = m_World->GetComponent(m_WidgetZ, "Transform")["Scale"]; + + if (m_WidgetCurrentAxis.x > 0 && m_WidgetCurrentAxis.y > 0 && m_WidgetCurrentAxis.z > 0) { + float movementLength = glm::length(movement); + float dot = glm::dot((glm::vec3)widgetOrientation, movement); + movement = glm::vec3(movementLength) * glm::sign(dot); + (glm::vec3&)m_World->GetComponent(m_WidgetOrigin, "Transform")["Scale"] += movement; + } + if (m_WidgetCurrentAxis.x > 0) { + scaleX.x += movement.x; + } + if (m_WidgetCurrentAxis.y > 0) { + scaleY.y += movement.y; + } + if (m_WidgetCurrentAxis.z > 0) { + scaleZ.z += movement.z; + } + (glm::vec3&)m_World->GetComponent(m_Selection, "Transform")["Scale"] += movement; + } + } + + + /*LOG_DEBUG("DELTA %f", e.DeltaX); + if (e.X < 0) { + glfwSetCursorPos(m_Renderer->Window(), width - 1, e.Y); + } + if (e.X >= width) { + glfwSetCursorPos(m_Renderer->Window(), 0, e.Y); + }*/ + + return true; +} + +bool EditorSystem::OnMouseRelease(const Events::MouseRelease& e) +{ + if (glm::length2(m_WidgetCurrentAxis) > 0.f) { + m_WidgetCurrentAxis = glm::vec3(0.f); + //setWidgetMode(m_WidgetMode); + } + + return true; +} + +bool EditorSystem::OnPicking(const Events::Picking& e) +{ + for (auto& pos : m_PickingQueue) { + auto result = e.Pick(pos); + EntityID entity = result.Entity; + if (glm::length2(m_WidgetCurrentAxis) > 0.f) { + } else { + LOG_INFO("Selected %i", entity); + if (entity != 0) { + EntityID parent = m_World->GetParent(entity); + if (parent == m_Widget) { + m_WidgetCurrentAxis = glm::vec3( + (entity == m_WidgetX) || (entity == m_WidgetOrigin) || (entity == m_WidgetPlaneY || entity == m_WidgetPlaneZ), + (entity == m_WidgetY) || (entity == m_WidgetOrigin) || (entity == m_WidgetPlaneX || entity == m_WidgetPlaneZ), + (entity == m_WidgetZ) || (entity == m_WidgetOrigin) || (entity == m_WidgetPlaneX || entity == m_WidgetPlaneY) + ); + m_WidgetPickingDepth = result.Depth; + + //auto widgetTransform = m_World->GetComponent(m_Widget, "Transform"); + //auto selectionTransform = m_World->GetComponent(m_Selection, "Transform"); + //widgetTransform["Position"] = (glm::vec3)selectionTransform["Position"]; + } else { + ImGui::SetActiveID(0, nullptr); + m_Selection = entity; + setWidgetMode(m_WidgetMode); + } + } else { + m_Selection = 0; + } + } + } + m_PickingQueue.clear(); + return true; +}; + +bool EditorSystem::OnFileDropped(const Events::FileDropped& e) +{ + m_LastDroppedFile = boost::filesystem::path(e.Path).lexically_relative(boost::filesystem::current_path()).string(); + std::replace(m_LastDroppedFile.begin(), m_LastDroppedFile.end(), '\\', '/'); + return true; +} + +void EditorSystem::updateWidget() +{ + if (m_Widget == 0) { + m_Widget = m_World->CreateEntity(); + m_World->AttachComponent(m_Widget, "Transform"); + m_WidgetX = m_World->CreateEntity(m_Widget); + m_World->AttachComponent(m_WidgetX, "Transform"); + m_World->AttachComponent(m_WidgetX, "Model"); + m_WidgetPlaneX = m_World->CreateEntity(m_Widget); + m_World->AttachComponent(m_WidgetPlaneX, "Transform"); + m_World->AttachComponent(m_WidgetPlaneX, "Model"); + m_World->GetComponent(m_WidgetPlaneX, "Model")["Resource"] = "Models/WidgetPlaneX.obj"; + m_WidgetY = m_World->CreateEntity(m_Widget); + m_World->AttachComponent(m_WidgetY, "Transform"); + m_World->AttachComponent(m_WidgetY, "Model"); + m_WidgetPlaneY = m_World->CreateEntity(m_Widget); + m_World->AttachComponent(m_WidgetPlaneY, "Transform"); + m_World->AttachComponent(m_WidgetPlaneY, "Model"); + m_World->GetComponent(m_WidgetPlaneY, "Model")["Resource"] = "Models/WidgetPlaneY.obj"; + m_WidgetZ = m_World->CreateEntity(m_Widget); + m_World->AttachComponent(m_WidgetZ, "Transform"); + m_World->AttachComponent(m_WidgetZ, "Model"); + m_WidgetPlaneZ = m_World->CreateEntity(m_Widget); + m_World->AttachComponent(m_WidgetPlaneZ, "Transform"); + m_World->AttachComponent(m_WidgetPlaneZ, "Model"); + m_World->GetComponent(m_WidgetPlaneZ, "Model")["Resource"] = "Models/WidgetPlaneZ.obj"; + m_WidgetOrigin = m_World->CreateEntity(m_Widget); + m_World->AttachComponent(m_WidgetOrigin, "Transform"); + m_World->AttachComponent(m_WidgetOrigin, "Model"); + setWidgetMode(WidgetMode::Translate); + } + + if (m_Selection != 0) { + auto widgetTransform = m_World->GetComponent(m_Widget, "Transform"); + glm::vec3 selectionPosition = RenderQueueFactory::AbsolutePosition(m_World, m_Selection); + widgetTransform["Position"] = selectionPosition; + if (m_WidgetSpace == WidgetSpace::Local) { + widgetTransform["Orientation"] = glm::eulerAngles(RenderQueueFactory::AbsoluteOrientation(m_World, m_Selection)); + } + } +} + +void EditorSystem::setWidgetMode(WidgetMode newMode) +{ + if (m_Widget == 0) { + return; + } + + auto widgetTransform = m_World->GetComponent(m_Widget, "Transform"); + widgetTransform["Orientation"] = glm::vec3(0.f); + m_World->GetComponent(m_WidgetX, "Transform")["Scale"] = glm::vec3(1.f); + m_World->GetComponent(m_WidgetPlaneX, "Model")["Visible"] = false; + m_World->GetComponent(m_WidgetY, "Transform")["Scale"] = glm::vec3(1.f); + m_World->GetComponent(m_WidgetPlaneY, "Model")["Visible"] = false; + m_World->GetComponent(m_WidgetZ, "Transform")["Scale"] = glm::vec3(1.f); + m_World->GetComponent(m_WidgetPlaneZ, "Model")["Visible"] = false; + m_World->GetComponent(m_WidgetOrigin, "Transform")["Scale"] = glm::vec3(1.f); + m_World->GetComponent(m_WidgetOrigin, "Model")["Visible"] = false; + + if (newMode == WidgetMode::Translate) { + m_World->GetComponent(m_WidgetX, "Model")["Resource"] = "Models/TranslationWidgetX.obj"; + m_World->GetComponent(m_WidgetY, "Model")["Resource"] = "Models/TranslationWidgetY.obj"; + m_World->GetComponent(m_WidgetZ, "Model")["Resource"] = "Models/TranslationWidgetZ.obj"; + // Temporarily disabled for local space until I can figure out what's wrong with the math + if (m_WidgetSpace != WidgetSpace::Local) { + m_World->GetComponent(m_WidgetPlaneX, "Model")["Visible"] = true; + m_World->GetComponent(m_WidgetPlaneY, "Model")["Visible"] = true; + m_World->GetComponent(m_WidgetPlaneZ, "Model")["Visible"] = true; + } + if (m_Selection != 0) { + if (m_WidgetSpace == WidgetSpace::Local) { + auto selectionTransform = m_World->GetComponent(m_Selection, "Transform"); + widgetTransform["Orientation"] = glm::eulerAngles(RenderQueueFactory::AbsoluteOrientation(m_World, m_Selection)); + } + } + } else if (newMode == WidgetMode::Scale) { + m_World->GetComponent(m_WidgetX, "Model")["Resource"] = "Models/ScaleWidgetX.obj"; + m_World->GetComponent(m_WidgetY, "Model")["Resource"] = "Models/ScaleWidgetY.obj"; + m_World->GetComponent(m_WidgetZ, "Model")["Resource"] = "Models/ScaleWidgetZ.obj"; + m_World->GetComponent(m_WidgetOrigin, "Model")["Visible"] = true; + m_World->GetComponent(m_WidgetOrigin, "Model")["Resource"] = "Models/ScaleWidgetOrigin.obj"; + if (m_Selection != 0) { + auto selectionTransform = m_World->GetComponent(m_Selection, "Transform"); + widgetTransform["Orientation"] = glm::eulerAngles(RenderQueueFactory::AbsoluteOrientation(m_World, m_Selection)); + } + } else if (newMode == WidgetMode::Rotate) { + m_World->GetComponent(m_WidgetX, "Model")["Resource"] = "Models/RotationWidgetX.obj"; + m_World->GetComponent(m_WidgetY, "Model")["Resource"] = "Models/RotationWidgetY.obj"; + m_World->GetComponent(m_WidgetZ, "Model")["Resource"] = "Models/RotationWidgetZ.obj"; + if (m_Selection != 0) { + auto selectionTransform = m_World->GetComponent(m_Selection, "Transform"); + if (m_WidgetSpace == WidgetSpace::Local) { + widgetTransform["Orientation"] = glm::eulerAngles(RenderQueueFactory::AbsoluteOrientation(m_World, m_Selection)); + } + } + } + m_WidgetMode = newMode; +} + + +void EditorSystem::setWidgetSpace(WidgetSpace space) +{ + m_WidgetSpace = space; + setWidgetMode(m_WidgetMode); +} + +void EditorSystem::drawUI(World* world, double dt) +{ + ImGui::ShowTestWindow(); + //ImGui::ShowStyleEditor(); + + if (ImGui::BeginMainMenuBar()) { + if (ImGui::BeginMenu("File")) { + + if (ImGui::MenuItem("New")) { } + if (ImGui::MenuItem("Open", "Ctrl+O")) { } + if (ImGui::MenuItem("Save", "Ctrl+S")) { } + if (ImGui::MenuItem("Save As...", "Ctrl+Shift+S")) { } + ImGui::Separator(); + if (ImGui::MenuItem("Close Editor", "F1")) { } + + ImGui::EndMenu(); + } + + ImGui::SameLine(); + if (ImGui::Button("Move")) { + setWidgetMode(WidgetMode::Translate); + } + ImGui::SameLine(); + if (ImGui::Button("Rotate")) { + setWidgetMode(WidgetMode::Rotate); + } + ImGui::SameLine(); + if (ImGui::Button("Scale")) { + setWidgetMode(WidgetMode::Scale); + } + ImGui::SameLine(); + if (m_WidgetSpace == WidgetSpace::Global) { + if (ImGui::Button("(Global)")) { + setWidgetSpace(WidgetSpace::Local); + } + } else if (m_WidgetSpace == WidgetSpace::Local) { + if (ImGui::Button("(Local)")) { + setWidgetSpace(WidgetSpace::Global); + } + } + + ImGui::EndMainMenuBar(); + } + + std::string title = std::string("Components #") + std::to_string(m_Selection) + std::string("###Components"); + if (ImGui::Begin(title.c_str())) { + if (m_Selection != 0) { + auto& pools = world->GetComponentPools(); + + std::vector componentTypes; + for (auto& pair : pools) { + // Only add components the entity doesn't already have + if (!pair.second->KnowsEntity(m_Selection)) { + componentTypes.push_back(pair.first.c_str()); + } + } + int item = -1; + ImGui::PushItemWidth(ImGui::GetWindowContentRegionWidth() - 5.f); + if (ImGui::Combo("", &item, componentTypes.data(), componentTypes.size())) { + if (item != -1) { + std::string chosenType = std::string(componentTypes.at(item)); + world->AttachComponent(m_Selection, chosenType); + } + } + ImGui::PopItemWidth(); + + for (auto& pair : pools) { + const std::string& componentType = pair.first; + auto pool = pair.second; + if (!pool->KnowsEntity(m_Selection)) { + continue; + } + auto& ci = pool->ComponentInfo(); + + bool deletePressed = createDeleteButton(componentType); + if (deletePressed) { + world->DeleteComponent(m_Selection, componentType); + continue; + } + + if (ImGui::CollapsingHeader(componentType.c_str())) { + if (!ci.Meta.Annotation.empty()) { + ImGui::Text(ci.Meta.Annotation.c_str()); + } + + auto& component = world->GetComponent(m_Selection, componentType); + for (auto& pair : ci.FieldTypes) { + const std::string& field = pair.first; + const std::string& type = pair.second; + + ImGui::PushID(field.c_str()); + if (type == "Vector") { + auto& val = component.Property(field); + if (field == "Scale") { + ImGui::DragFloat3("", glm::value_ptr(val), 0.1f, 0.f, std::numeric_limits::max()); + } else if (field == "Orientation") { + glm::vec3 tempVal = glm::fmod(val, glm::vec3(glm::two_pi())); + if (ImGui::SliderFloat3("", glm::value_ptr(tempVal), 0.f, glm::two_pi())) { + val = tempVal; + } + } else { + ImGui::DragFloat3("", glm::value_ptr(val), 0.1f, std::numeric_limits::lowest(), std::numeric_limits::max()); + } + } else if (type == "Color") { + auto& val = component.Property(field); + ImGui::ColorEdit4("", glm::value_ptr(val), true); + } else if (type == "string") { + std::string& val = component.Property(field); + char tempString[1024]; + memcpy(tempString, val.c_str(), std::min(val.length() + 1, sizeof(tempString))); + if (ImGui::InputText("", tempString, sizeof(tempString))) { + val = std::string(tempString); + LOG_DEBUG("%s::%s changed!", componentType.c_str(), field.c_str()); + } + // DROP STUFF + if (ImGui::IsItemHovered() && !m_LastDroppedFile.empty()) { + val = m_LastDroppedFile; + m_LastDroppedFile = ""; + } + + } else if (type == "double") { + float tempVal = static_cast(component.Property(field)); + if (ImGui::InputFloat("", &tempVal, 0.01f, 1.f)) { + component.SetProperty(field, static_cast(tempVal)); + } + } else if (type == "bool") { + auto& val = component.Property(field); + ImGui::Checkbox("", &val); + } else { + ImGui::TextDisabled(type.c_str()); + } + ImGui::PopID(); + + ImGui::SameLine(); + ImGui::Text(field.c_str()); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("field annotation goes here"); + } + } + } + } + } + + } + ImGui::End(); + + if (ImGui::Begin("Entitites")) { + static EntityID draggingEntity = 0; + auto entityChildren = world->GetEntityChildren(); + std::function recurse = [&](EntityID parent) { + auto range = entityChildren.equal_range(parent); + for (auto it = range.first; it != range.second; it++) { + + ImVec2 pos = ImGui::GetCursorScreenPos(); + float width = ImGui::GetContentRegionAvailWidth(); + ImRect bb(pos + ImVec2(20, 0), pos + ImVec2(width, 13)); + auto window = ImGui::GetCurrentWindow(); + if (m_Selection == it->second) { + const ImU32 col = window->Color(ImGuiCol_HeaderActive); + window->DrawList->AddRectFilled(bb.Min, bb.Max, col); + } + ImGuiID id = window->GetID((std::string("#SelectButton") + std::to_string(it->second)).c_str()); + bool hovered = false; + bool held = false; + if (ImGui::ButtonBehavior(bb, id, &hovered, &held)) { + m_Selection = it->second; + } + if (held) { + ImVec2 entityDragDelta = ImGui::GetMouseDragDelta(0); + if (std::abs(entityDragDelta.x) > 0 && std::abs(entityDragDelta.y) > 0) { + if (draggingEntity == 0) { + draggingEntity = it->second; + LOG_DEBUG("Started drag of entity %i", draggingEntity); + } + ImGui::SetNextWindowPos(ImGui::GetIO().MousePos + ImVec2(20, 0)); + ImGui::Begin("Change parent", nullptr, ImVec2(0, 0), 0.3f, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings); + ImGui::Text("#%i", draggingEntity); + ImGui::End(); + } + } + + ImGui::SetNextTreeNodeOpened(true, ImGuiSetCond_Once); + if (ImGui::TreeNode((std::string("#") + std::to_string(it->second)).c_str())) { + if (draggingEntity != 0 && ImGui::IsItemHoveredRect() && ImGui::IsMouseReleased(0)) { + LOG_DEBUG("Changed parent of %i to %i", draggingEntity, it->second); + changeParent(draggingEntity, it->second); + draggingEntity = 0; + } + + if (ImGui::BeginPopupContextItem("item context menu")) { + if (ImGui::Button("Add")) { + EntityID entity = world->CreateEntity(it->second); + world->AttachComponent(entity, "Transform"); + } + ImGui::SameLine(); + if (ImGui::Button("Delete")) { + world->DeleteEntity(it->second); + ImGui::CloseCurrentPopup(); + if (m_Selection == it->second) { + m_Selection = 0; + } + } + ImGui::EndPopup(); + } + recurse(it->second); + ImGui::TreePop(); + } + } + }; + recurse(0); + } + ImGui::End(); +} + +bool EditorSystem::createDeleteButton(std::string componentType) +{ + float width = ImGui::GetContentRegionAvailWidth(); + ImGuiWindow* window = ImGui::GetCurrentWindow(); + auto pos = ImGui::GetCursorScreenPos() + ImVec2(width - 14.f, 1); + ImRect bb = ImRect(pos, pos + ImVec2(14.f, 14.f)); + std::string idString = "#DELETE"; + idString += componentType; + ImGuiID id = window->GetID(idString.c_str()); + bool hovered; + bool held; + bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held); + //ImU32 col = window->Color((held && hovered) ? ImGuiCol_CloseButtonActive : hovered ? ImGuiCol_CloseButtonHovered : ImGuiCol_CloseButton); + ImU32 col = window->Color((held && hovered) ? ImGuiCol_CloseButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + window->DrawList->AddCircleFilled(bb.GetCenter(), 7.f, col, 16); + return pressed; +} + +void EditorSystem::changeParent(EntityID entity, EntityID newParent) +{ + if (entity == newParent) { + return; + } + + // An entity can't be a child to one of its own children + auto children = m_World->GetEntityChildren().equal_range(entity); + for (auto it = children.first; it != children.second; it++) { + if (it->second == newParent) { + return; + } + } + + m_World->SetParent(entity, newParent); +} diff --git a/src/Engine/Input/InputProxy.cpp b/src/Engine/Input/InputProxy.cpp new file mode 100644 index 00000000..c3e4d669 --- /dev/null +++ b/src/Engine/Input/InputProxy.cpp @@ -0,0 +1,112 @@ +#include "Input/InputProxy.h" +#include "Input/InputHandler.h" + +InputProxy::InputProxy(EventBroker* eventBroker) + : m_EventBroker(eventBroker) +{ + EVENT_SUBSCRIBE_MEMBER(m_EBindOrigin, &InputProxy::OnBindOrigin); +} + +InputProxy::~InputProxy() +{ + for (auto& handler : m_Handlers) { + delete handler; + } +} + +void InputProxy::LoadBindings(std::string file) +{ + auto config = ResourceManager::Load(file); + for (auto& origin : config->GetAll("Bindings")) { + Events::BindOrigin e; + e.Origin = origin.first; + e.Command = origin.second; + e.Value = 1.f; + if (!e.Command.empty()) { + char prefix = e.Command.at(0); + if (prefix == '+' || prefix == '-') { + e.Command = e.Command.substr(1); + if (prefix == '-') { + e.Value *= -1.f; + } + } + OnBindOrigin(e); + } + } +} + +void InputProxy::Update(double dt) +{ + m_EventBroker->Process(); + m_EventBroker->Process(); + for (auto& handler : m_Handlers) { + handler->Update(dt); + } +} + +void InputProxy::Process() +{ + for (auto& pair : m_CommandHandlers) { + const std::string& command = pair.first; + auto handlers = pair.second; + m_CurrentCommandValues[command] = 0.f; + for (auto& handler : handlers) { + m_CurrentCommandValues[command] += handler->GetCommandValue(command); + } + + auto last = m_LastCommandValues.find(command); + float currentValue = m_CurrentCommandValues[command]; + if (last == m_LastCommandValues.end() || last->second != currentValue) { + Events::InputCommand e; + e.PlayerID = -1; + e.Command = command; + e.Value = currentValue; + m_EventBroker->Publish(e); + LOG_DEBUG("Input: Published command %s=%f for player %i", e.Command.c_str(), e.Value, e.PlayerID); + m_LastCommandValues[command] = currentValue; + } + } + + // Accumulate the input values of all unique commands published by input handlers + for (auto& pair : m_CommandQueue) { + Events::InputCommand e; + e.PlayerID = pair.first.first; + e.Command = pair.first.second; + e.Value = 0; + for (auto& value : pair.second) { + e.Value += value; + } + //e.Value = std::max(-1.f, std::min(e.Value, 1.f)); + m_EventBroker->Publish(e); + LOG_DEBUG("Input: Published command %s=%f for player %i", e.Command.c_str(), e.Value, e.PlayerID); + } + m_CommandQueue.clear(); +} + +void InputProxy::Publish(const Events::InputCommand& e) +{ + auto key = std::make_pair(e.PlayerID, e.Command); + m_CommandQueue[key].push_back(e.Value); +} + +bool InputProxy::OnBindOrigin(const Events::BindOrigin& e) +{ + bool originBound = false; + for (auto& handler : m_Handlers) { + bool result = handler->BindOrigin(e.Origin, e.Command, e.Value); + if (result) { + m_CommandHandlers[e.Command].insert(handler); + m_LastCommandValues[e.Command] = 0.f; + if (originBound) { + LOG_WARNING("Multiple handlers responded to binding input origin \"%s\"!", e.Origin.c_str()); + } + originBound = true; + } + } + + if (!originBound) { + LOG_ERROR("No input handler responded to binding input origin \"%s\"!", e.Origin.c_str()); + } + + return originBound; +} diff --git a/src/Engine/Input/InputSystem.cpp b/src/Engine/Input/InputSystem.cpp deleted file mode 100644 index d11cd73d..00000000 --- a/src/Engine/Input/InputSystem.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include "PrecompiledHeader.h" -#include "Input/InputSystem.h" -#include "Core/World.h" - -void Systems::InputSystem::RegisterComponents(ComponentFactory* cf) -{ - -} - -void Systems::InputSystem::Initialize() -{ - // Subscribe to events - EVENT_SUBSCRIBE_MEMBER(m_EKeyDown, &Systems::InputSystem::OnKeyDown); - EVENT_SUBSCRIBE_MEMBER(m_EKeyUp, &Systems::InputSystem::OnKeyUp); - EVENT_SUBSCRIBE_MEMBER(m_EMousePress, &Systems::InputSystem::OnMousePress); - EVENT_SUBSCRIBE_MEMBER(m_EMouseRelease, &Systems::InputSystem::OnMouseRelease); - EVENT_SUBSCRIBE_MEMBER(m_EGamepadAxis, &Systems::InputSystem::OnGamepadAxis); - EVENT_SUBSCRIBE_MEMBER(m_EGamepadButtonDown, &Systems::InputSystem::OnGamepadButtonDown); - EVENT_SUBSCRIBE_MEMBER(m_EGamepadButtonUp, &Systems::InputSystem::OnGamepadButtonUp); - EVENT_SUBSCRIBE_MEMBER(m_EBindKey, &Systems::InputSystem::OnBindKey); - EVENT_SUBSCRIBE_MEMBER(m_EBindMouseButton, &Systems::InputSystem::OnBindMouseButton); - EVENT_SUBSCRIBE_MEMBER(m_EBindGamepadAxis, &Systems::InputSystem::OnBindGamepadAxis); - EVENT_SUBSCRIBE_MEMBER(m_EBindGamepadButton, &Systems::InputSystem::OnBindGamepadButton); -} - -void Systems::InputSystem::Update(double dt) -{ - -} - -bool Systems::InputSystem::OnKeyDown(const Events::KeyDown &event) -{ - auto range = m_KeyBindings.equal_range(event.KeyCode); - for (auto bindingIt = range.first; bindingIt != range.second; bindingIt++) { - std::string command; - float value; - std::tie(command, value) = bindingIt->second; - m_CommandKeyboardValues[command][event.KeyCode] = value; - PublishCommand(1, command, GetCommandTotalValue(command)); - } - - return true; -} - -bool Systems::InputSystem::OnKeyUp(const Events::KeyUp &event) -{ - auto range = m_KeyBindings.equal_range(event.KeyCode); - for (auto bindingIt = range.first; bindingIt != range.second; bindingIt++) { - std::string command; - float value; - std::tie(command, value) = bindingIt->second; - m_CommandKeyboardValues[command][event.KeyCode] = 0; - PublishCommand(1, command, GetCommandTotalValue(command));; - } - - return true; -} - -bool Systems::InputSystem::OnMousePress(const Events::MousePress &event) -{ - auto range = m_MouseButtonBindings.equal_range(event.Button); - for (auto bindingIt = range.first; bindingIt != range.second; bindingIt++) { - std::string command; - float value; - std::tie(command, value) = bindingIt->second; - m_CommandMouseButtonValues[command][event.Button] = value; - PublishCommand(1, command, GetCommandTotalValue(command)); - } - - return true; -} - -bool Systems::InputSystem::OnMouseRelease(const Events::MouseRelease &event) -{ - auto range = m_MouseButtonBindings.equal_range(event.Button); - for (auto bindingIt = range.first; bindingIt != range.second; bindingIt++) { - std::string command; - float value; - std::tie(command, value) = bindingIt->second; - m_CommandMouseButtonValues[command][event.Button] = 0; - PublishCommand(1, command, GetCommandTotalValue(command)); - } - - return true; -} - -bool Systems::InputSystem::OnGamepadAxis(const Events::GamepadAxis &event) -{ - auto range = m_GamepadAxisBindings.equal_range(event.Axis); - for (auto bindingIt = range.first; bindingIt != range.second; bindingIt++) { - std::string command; - float value; - std::tie(command, value) = bindingIt->second; - m_CommandGamepadAxisValues[command][event.Axis] = event.Value * value; - PublishCommand(event.GamepadID + 1, command, GetCommandTotalValue(command)); - } - - return true; -} - -bool Systems::InputSystem::OnGamepadButtonDown(const Events::GamepadButtonDown &event) -{ - auto range = m_GamepadButtonBindings.equal_range(event.Button); - for (auto bindingIt = range.first; bindingIt != range.second; bindingIt++) { - std::string command; - float value; - std::tie(command, value) = bindingIt->second; - m_CommandGamepadButtonValues[command][event.Button] = value; - PublishCommand(event.GamepadID + 1, command, GetCommandTotalValue(command)); - } - - return true; -} - -bool Systems::InputSystem::OnGamepadButtonUp(const Events::GamepadButtonUp &event) -{ - auto range = m_GamepadButtonBindings.equal_range(event.Button); - for (auto bindingIt = range.first; bindingIt != range.second; bindingIt++) { - std::string command; - float value; - std::tie(command, value) = bindingIt->second; - m_CommandGamepadButtonValues[command][event.Button] = 0; - PublishCommand(event.GamepadID + 1, command, GetCommandTotalValue(command)); - } - - return true; -} - -bool Systems::InputSystem::OnBindKey(const Events::BindKey &event) -{ - if (event.Command.empty()) { - return false; - } - - m_KeyBindings.insert(std::make_pair(event.KeyCode, std::make_tuple(event.Command, event.Value))); - LOG_DEBUG("Input: Bound key %i to %s", event.KeyCode, event.Command.c_str()); - - return true; -} - -bool Systems::InputSystem::OnBindMouseButton(const Events::BindMouseButton &event) -{ - if (event.Command.empty()) { - return false; - } - - m_MouseButtonBindings.insert(std::make_pair(event.Button, std::make_tuple(event.Command, event.Value))); - LOG_DEBUG("Input: Bound mouse button %i to %s", event.Button, event.Command.c_str()); - - return true; -} - -bool Systems::InputSystem::OnBindGamepadAxis(const Events::BindGamepadAxis &event) -{ - if (event.Command.empty()) { - return false; - } - - m_GamepadAxisBindings.insert(std::make_pair(event.Axis, std::make_tuple(event.Command, event.Value))); - LOG_DEBUG("Input: Bound gamepad axis %i to %s", event.Axis, event.Command.c_str()); - - return true; -} - -bool Systems::InputSystem::OnBindGamepadButton(const Events::BindGamepadButton &event) -{ - if (event.Command.empty()) { - return false; - } - - m_GamepadButtonBindings.insert(std::make_pair(event.Button, std::make_tuple(event.Command, event.Value))); - LOG_DEBUG("Input: Bound gamepad axis %i to %s", event.Button, event.Command.c_str()); - - return true; -} - -float Systems::InputSystem::GetCommandTotalValue(std::string command) -{ - float value = 0.f; - - auto keyboardIt = m_CommandKeyboardValues.find(command); - if (keyboardIt != m_CommandKeyboardValues.end()) { - for (auto &key : keyboardIt->second) { - value += key.second; - } - } - - auto mouseButtonIt = m_CommandMouseButtonValues.find(command); - if (mouseButtonIt != m_CommandMouseButtonValues.end()) { - for (auto &button : mouseButtonIt->second) { - value += button.second; - } - } - - auto gamepadAxisIt = m_CommandGamepadAxisValues.find(command); - if (gamepadAxisIt != m_CommandGamepadAxisValues.end()) { - for (auto &axis : gamepadAxisIt->second) { - value += axis.second; - } - } - - auto gamepadButtonIt = m_CommandGamepadButtonValues.find(command); - if (gamepadButtonIt != m_CommandGamepadButtonValues.end()) { - for (auto &button : gamepadButtonIt->second) { - value += button.second; - } - } - - return std::max(-1.f, std::min(value, 1.f)); -} - -void Systems::InputSystem::PublishCommand(int playerID, std::string command, float value) -{ - Events::InputCommand e; - e.PlayerID = playerID; - e.Command = command; - e.Value = value; - EventBroker->Publish(e); - - LOG_DEBUG("Input: Published command %s=%f for player %i", e.Command.c_str(), e.Value, playerID); -} diff --git a/src/Engine/Input/KeyboardInputHandler.cpp b/src/Engine/Input/KeyboardInputHandler.cpp new file mode 100644 index 00000000..0a0994aa --- /dev/null +++ b/src/Engine/Input/KeyboardInputHandler.cpp @@ -0,0 +1,181 @@ +#include "Input/KeyboardInputHandler.h" + +KeyboardInputHandler::KeyboardInputHandler(EventBroker* eventBroker, InputProxy* inputProxy) : InputHandler(eventBroker, inputProxy) +{ + EVENT_SUBSCRIBE_MEMBER(m_EKeyDown, &KeyboardInputHandler::OnKeyDown); + EVENT_SUBSCRIBE_MEMBER(m_EKeyUp, &KeyboardInputHandler::OnKeyUp); + + m_OriginKeyCodes["Space"] = GLFW_KEY_SPACE; + m_OriginKeyCodes["Apostrophe"] = GLFW_KEY_APOSTROPHE; + m_OriginKeyCodes["Comma"] = GLFW_KEY_COMMA; + m_OriginKeyCodes["Minus"] = GLFW_KEY_MINUS; + m_OriginKeyCodes["Period"] = GLFW_KEY_PERIOD; + m_OriginKeyCodes["Slash"] = GLFW_KEY_SLASH; + m_OriginKeyCodes["0"] = GLFW_KEY_0; + m_OriginKeyCodes["1"] = GLFW_KEY_1; + m_OriginKeyCodes["2"] = GLFW_KEY_2; + m_OriginKeyCodes["3"] = GLFW_KEY_3; + m_OriginKeyCodes["4"] = GLFW_KEY_4; + m_OriginKeyCodes["5"] = GLFW_KEY_5; + m_OriginKeyCodes["6"] = GLFW_KEY_6; + m_OriginKeyCodes["7"] = GLFW_KEY_7; + m_OriginKeyCodes["8"] = GLFW_KEY_8; + m_OriginKeyCodes["9"] = GLFW_KEY_9; + m_OriginKeyCodes["Semicolon"] = GLFW_KEY_SEMICOLON; + m_OriginKeyCodes["Equal"] = GLFW_KEY_EQUAL; + m_OriginKeyCodes["A"] = GLFW_KEY_A; + m_OriginKeyCodes["B"] = GLFW_KEY_B; + m_OriginKeyCodes["C"] = GLFW_KEY_C; + m_OriginKeyCodes["D"] = GLFW_KEY_D; + m_OriginKeyCodes["E"] = GLFW_KEY_E; + m_OriginKeyCodes["F"] = GLFW_KEY_F; + m_OriginKeyCodes["G"] = GLFW_KEY_G; + m_OriginKeyCodes["H"] = GLFW_KEY_H; + m_OriginKeyCodes["I"] = GLFW_KEY_I; + m_OriginKeyCodes["J"] = GLFW_KEY_J; + m_OriginKeyCodes["K"] = GLFW_KEY_K; + m_OriginKeyCodes["L"] = GLFW_KEY_L; + m_OriginKeyCodes["M"] = GLFW_KEY_M; + m_OriginKeyCodes["N"] = GLFW_KEY_N; + m_OriginKeyCodes["O"] = GLFW_KEY_O; + m_OriginKeyCodes["P"] = GLFW_KEY_P; + m_OriginKeyCodes["Q"] = GLFW_KEY_Q; + m_OriginKeyCodes["R"] = GLFW_KEY_R; + m_OriginKeyCodes["S"] = GLFW_KEY_S; + m_OriginKeyCodes["T"] = GLFW_KEY_T; + m_OriginKeyCodes["U"] = GLFW_KEY_U; + m_OriginKeyCodes["V"] = GLFW_KEY_V; + m_OriginKeyCodes["W"] = GLFW_KEY_W; + m_OriginKeyCodes["X"] = GLFW_KEY_X; + m_OriginKeyCodes["Y"] = GLFW_KEY_Y; + m_OriginKeyCodes["Z"] = GLFW_KEY_Z; + m_OriginKeyCodes["LeftBracket"] = GLFW_KEY_LEFT_BRACKET; + m_OriginKeyCodes["Backslash"] = GLFW_KEY_BACKSLASH; + m_OriginKeyCodes["RightBracket"] = GLFW_KEY_RIGHT_BRACKET; + m_OriginKeyCodes["Accent"] = GLFW_KEY_GRAVE_ACCENT; + m_OriginKeyCodes["W1"] = GLFW_KEY_WORLD_1; + m_OriginKeyCodes["W2"] = GLFW_KEY_WORLD_2; + m_OriginKeyCodes["Escape"] = GLFW_KEY_ESCAPE; + m_OriginKeyCodes["Enter"] = GLFW_KEY_ENTER; + m_OriginKeyCodes["Tab"] = GLFW_KEY_TAB; + m_OriginKeyCodes["Backspace"] = GLFW_KEY_BACKSPACE; + m_OriginKeyCodes["Insert"] = GLFW_KEY_INSERT; + m_OriginKeyCodes["Delete"] = GLFW_KEY_DELETE; + m_OriginKeyCodes["Right"] = GLFW_KEY_RIGHT; + m_OriginKeyCodes["Left"] = GLFW_KEY_LEFT; + m_OriginKeyCodes["Down"] = GLFW_KEY_DOWN; + m_OriginKeyCodes["Up"] = GLFW_KEY_UP; + m_OriginKeyCodes["PgUp"] = GLFW_KEY_PAGE_UP; + m_OriginKeyCodes["PgDn"] = GLFW_KEY_PAGE_DOWN; + m_OriginKeyCodes["Home"] = GLFW_KEY_HOME; + m_OriginKeyCodes["End"] = GLFW_KEY_END; + m_OriginKeyCodes["CapsLock"] = GLFW_KEY_CAPS_LOCK; + m_OriginKeyCodes["ScrollLock"] = GLFW_KEY_SCROLL_LOCK; + m_OriginKeyCodes["NumLock"] = GLFW_KEY_NUM_LOCK; + m_OriginKeyCodes["PrintScreen"] = GLFW_KEY_PRINT_SCREEN; + m_OriginKeyCodes["Pause"] = GLFW_KEY_PAUSE; + m_OriginKeyCodes["F1"] = GLFW_KEY_F1; + m_OriginKeyCodes["F2"] = GLFW_KEY_F2; + m_OriginKeyCodes["F3"] = GLFW_KEY_F3; + m_OriginKeyCodes["F4"] = GLFW_KEY_F4; + m_OriginKeyCodes["F5"] = GLFW_KEY_F5; + m_OriginKeyCodes["F6"] = GLFW_KEY_F6; + m_OriginKeyCodes["F7"] = GLFW_KEY_F7; + m_OriginKeyCodes["F8"] = GLFW_KEY_F8; + m_OriginKeyCodes["F9"] = GLFW_KEY_F9; + m_OriginKeyCodes["F10"] = GLFW_KEY_F10; + m_OriginKeyCodes["F11"] = GLFW_KEY_F11; + m_OriginKeyCodes["F12"] = GLFW_KEY_F12; + m_OriginKeyCodes["F13"] = GLFW_KEY_F13; + m_OriginKeyCodes["F14"] = GLFW_KEY_F14; + m_OriginKeyCodes["F15"] = GLFW_KEY_F15; + m_OriginKeyCodes["F16"] = GLFW_KEY_F16; + m_OriginKeyCodes["F17"] = GLFW_KEY_F17; + m_OriginKeyCodes["F18"] = GLFW_KEY_F18; + m_OriginKeyCodes["F19"] = GLFW_KEY_F19; + m_OriginKeyCodes["F20"] = GLFW_KEY_F20; + m_OriginKeyCodes["F21"] = GLFW_KEY_F21; + m_OriginKeyCodes["F22"] = GLFW_KEY_F22; + m_OriginKeyCodes["F23"] = GLFW_KEY_F23; + m_OriginKeyCodes["F24"] = GLFW_KEY_F24; + m_OriginKeyCodes["F25"] = GLFW_KEY_F25; + m_OriginKeyCodes["KP0"] = GLFW_KEY_KP_0; + m_OriginKeyCodes["KP1"] = GLFW_KEY_KP_1; + m_OriginKeyCodes["KP2"] = GLFW_KEY_KP_2; + m_OriginKeyCodes["KP3"] = GLFW_KEY_KP_3; + m_OriginKeyCodes["KP4"] = GLFW_KEY_KP_4; + m_OriginKeyCodes["KP5"] = GLFW_KEY_KP_5; + m_OriginKeyCodes["KP6"] = GLFW_KEY_KP_6; + m_OriginKeyCodes["KP7"] = GLFW_KEY_KP_7; + m_OriginKeyCodes["KP8"] = GLFW_KEY_KP_8; + m_OriginKeyCodes["KP9"] = GLFW_KEY_KP_9; + m_OriginKeyCodes["KPDecimal"] = GLFW_KEY_KP_DECIMAL; + m_OriginKeyCodes["KPDivide"] = GLFW_KEY_KP_DIVIDE; + m_OriginKeyCodes["KPMultiply"] = GLFW_KEY_KP_MULTIPLY; + m_OriginKeyCodes["KPSubtract"] = GLFW_KEY_KP_SUBTRACT; + m_OriginKeyCodes["KPAdd"] = GLFW_KEY_KP_ADD; + m_OriginKeyCodes["KPEnter"] = GLFW_KEY_KP_ENTER; + m_OriginKeyCodes["KPEqual"] = GLFW_KEY_KP_EQUAL; + m_OriginKeyCodes["LeftShift"] = GLFW_KEY_LEFT_SHIFT; + m_OriginKeyCodes["LeftControl"] = GLFW_KEY_LEFT_CONTROL; + m_OriginKeyCodes["LeftAlt"] = GLFW_KEY_LEFT_ALT; + m_OriginKeyCodes["LeftSuper"] = GLFW_KEY_LEFT_SUPER; + m_OriginKeyCodes["RightShift"] = GLFW_KEY_RIGHT_SHIFT; + m_OriginKeyCodes["RightControl"] = GLFW_KEY_RIGHT_CONTROL; + m_OriginKeyCodes["RightAlt"] = GLFW_KEY_RIGHT_ALT; + m_OriginKeyCodes["RightSuper"] = GLFW_KEY_RIGHT_SUPER; + m_OriginKeyCodes["Menu"] = GLFW_KEY_MENU; +} + +bool KeyboardInputHandler::BindOrigin(std::string origin, std::string command, float value) +{ + auto originIt = m_OriginKeyCodes.find(origin); + if (originIt == m_OriginKeyCodes.end()) { + return false; + } + + int keyCode = originIt->second; + m_KeyBindings[keyCode] = std::make_tuple(command, value); + return true; +} + +bool KeyboardInputHandler::OnKeyDown(const Events::KeyDown& e) +{ + auto it = m_KeyBindings.find(e.KeyCode); + if (it == m_KeyBindings.end()) { + return false; + } + + std::string command; + float value; + std::tie(command, value) = it->second; + m_CommandValues[command] += value; + + return true; +} + +bool KeyboardInputHandler::OnKeyUp(const Events::KeyUp& e) +{ + auto it = m_KeyBindings.find(e.KeyCode); + if (it == m_KeyBindings.end()) { + return false; + } + + std::string command; + float value; + std::tie(command, value) = it->second; + m_CommandValues[command] -= value; + + return true; +} + +float KeyboardInputHandler::GetCommandValue(std::string command) +{ + auto it = m_CommandValues.find(command); + if (it != m_CommandValues.end()) { + return m_CommandValues[command]; + } else { + return 0.f; + } +} + diff --git a/src/Engine/Input/MouseInputHandler.cpp b/src/Engine/Input/MouseInputHandler.cpp new file mode 100644 index 00000000..5996b43d --- /dev/null +++ b/src/Engine/Input/MouseInputHandler.cpp @@ -0,0 +1,129 @@ +#include "Input/MouseInputHandler.h" + +MouseInputHandler::MouseInputHandler(EventBroker* eventBroker, InputProxy* inputProxy) + : InputHandler(eventBroker, inputProxy) +{ + EVENT_SUBSCRIBE_MEMBER(m_EMousePress, &MouseInputHandler::OnMousePress); + EVENT_SUBSCRIBE_MEMBER(m_EMouseRelease, &MouseInputHandler::OnMouseRelease); + EVENT_SUBSCRIBE_MEMBER(m_EMouseMove, &MouseInputHandler::OnMouseMove); + + m_OriginCodes["Mouse1"] = GLFW_MOUSE_BUTTON_1; + m_OriginCodes["MouseLeft"] = GLFW_MOUSE_BUTTON_LEFT; + m_OriginCodes["Mouse2"] = GLFW_MOUSE_BUTTON_2; + m_OriginCodes["MouseRight"] = GLFW_MOUSE_BUTTON_RIGHT; + m_OriginCodes["Mouse3"] = GLFW_MOUSE_BUTTON_3; + m_OriginCodes["MouseMiddle"] = GLFW_MOUSE_BUTTON_MIDDLE; + m_OriginCodes["Mouse4"] = GLFW_MOUSE_BUTTON_4; + m_OriginCodes["Mouse5"] = GLFW_MOUSE_BUTTON_5; + m_OriginCodes["Mouse6"] = GLFW_MOUSE_BUTTON_6; + m_OriginCodes["Mouse7"] = GLFW_MOUSE_BUTTON_7; + m_OriginCodes["Mouse8"] = GLFW_MOUSE_BUTTON_8; + + m_OriginAxes["MouseX"] = 'X'; + m_OriginAxes["MouseY"] = 'Y'; +} + +bool MouseInputHandler::BindOrigin(std::string origin, std::string command, float value) +{ + auto originCode = m_OriginCodes.find(origin); + if (originCode != m_OriginCodes.end()) { + int code = originCode->second; + m_Bindings[code] = std::make_tuple(command, value); + return true; + } + + auto originAxis = m_OriginAxes.find(origin); + if (originAxis != m_OriginAxes.end()) { + char axis = originAxis->second; + float multiplier = 1.f; + // Sensitivity + multiplier *= ResourceManager::Load("Input.ini")->Get("Mouse.Sensitivity", 1.f); + if (axis == 'Y') { + if (ResourceManager::Load("Input.ini")->Get("Mouse.InvertPitch", false)) { + multiplier *= -1.f; + } + } + m_Axes[axis] = std::make_tuple(command, value * multiplier); + return true; + } + + return false; +} + +float MouseInputHandler::GetCommandValue(std::string command) +{ + auto it = m_CommandValues.find(command); + if (it != m_CommandValues.end()) { + return m_CommandValues[command]; + } else { + return 0.f; + } +} + +bool MouseInputHandler::OnMousePress(const Events::MousePress& e) +{ + auto it = m_Bindings.find(e.Button); + if (it == m_Bindings.end()) { + return false; + } + + std::string command; + float value; + std::tie(command, value) = it->second; + m_CommandValues[command] += value; + + return true; +} + +bool MouseInputHandler::OnMouseRelease(const Events::MouseRelease& e) +{ + auto it = m_Bindings.find(e.Button); + if (it == m_Bindings.end()) { + return false; + } + + std::string command; + float value; + std::tie(command, value) = it->second; + m_CommandValues[command] -= value; + + return true; +} + +bool MouseInputHandler::OnMouseMove(const Events::MouseMove& e) +{ + if (std::abs(e.DeltaX) > 0) { + auto it = m_Axes.find('X'); + if (it != m_Axes.end()) { + Events::InputCommand ic; + ic.PlayerID = -1; + std::tie(ic.Command, ic.Value) = it->second; + ic.Value *= e.DeltaX; + m_InputProxy->Publish(ic); + } + } + + if (std::abs(e.DeltaY) > 0) { + auto it = m_Axes.find('Y'); + if (it != m_Axes.end()) { + Events::InputCommand ic; + ic.PlayerID = -1; + std::tie(ic.Command, ic.Value) = it->second; + ic.Value *= e.DeltaY; + m_InputProxy->Publish(ic); + } + } + + return true; +} + +bool MouseInputHandler::hasOrigin(std::string origin) +{ + if (m_OriginCodes.find(origin) == m_OriginCodes.end()) { + return false; + } + if (m_OriginAxes.find(origin) == m_OriginAxes.end()) { + return false; + } + return true; +} \ No newline at end of file diff --git a/src/Engine/Network/Client.cpp b/src/Engine/Network/Client.cpp index bc513799..5c7e9c8f 100644 --- a/src/Engine/Network/Client.cpp +++ b/src/Engine/Network/Client.cpp @@ -1,11 +1,334 @@ -#include "Network\Client.h" +#include "Network/Client.h" -Client::Client() +using namespace boost::asio::ip; + + +Client::Client(ConfigFile* config) : m_Socket(m_IOService) { - + // Default is local host + std::string address = config->Get("Networking.Address", "127.0.0.1"); + int port = config->Get("Networking.Port", 13); + m_ReceiverEndpoint = udp::endpoint(boost::asio::ip::address::from_string(address), port); + // Set up network stream + m_PlayerName = config->Get("Networking.Name", "Raptorcopter"); + m_NextSnapshot.InputForward = ""; + m_NextSnapshot.InputRight = ""; } Client::~Client() { } + +void Client::Start(World* world, EventBroker* eventBroker) +{ + m_WasStarted = true; + m_EventBroker = eventBroker; + m_World = world; + + // Subscribe to events + m_EInputCommand = decltype(m_EInputCommand)(std::bind(&Client::OnInputCommand, this, std::placeholders::_1)); + m_EventBroker->Subscribe(m_EInputCommand); + + + //while (m_PlayerName.size() > 7) { + // LOG_INFO("Please enter your name (No longer than 7 characters):"); + // std::cin >> m_PlayerName; + //} + m_Socket.connect(m_ReceiverEndpoint); + LOG_INFO("I am client. BIP BOP"); +} + +void Client::Update() +{ + readFromServer(); +} + +void Client::Close() +{ + if (m_WasStarted) { + disconnect(); + m_ThreadIsRunning = false; + m_EventBroker->Unsubscribe(m_EInputCommand); + } +} + +void Client::readFromServer() +{ + if (m_Socket.available()) { + bytesRead = receive(readBuf, INPUTSIZE); + if (bytesRead > 0) { + Packet packet(readBuf, bytesRead); + parseMessageType(packet); + } + } + std::clock_t currentTime = std::clock(); + if (snapshotInterval < (1000 * (currentTime - previousSnapshotMessage) / (double)CLOCKS_PER_SEC)) { + if (isConnected()) { + sendSnapshotToServer(); + } + previousSnapshotMessage = currentTime; + } +} + +void Client::sendSnapshotToServer() +{ + // Reset previouse key state in snapshot. + m_NextSnapshot.InputForward = ""; + m_NextSnapshot.InputRight = ""; + + auto player = m_World->GetComponent(m_PlayerDefinitions[m_PlayerID].EntityID, "Player"); + + + // See if any movement keys are down + // We dont care if it's overwritten by later + // if statement. Watcha gonna do, right! + if (player["Forward"]) { + m_NextSnapshot.InputForward = "+Forward"; + } + if (player["Left"]) { + m_NextSnapshot.InputRight = "-Right"; + } + if (player["Back"]) { + m_NextSnapshot.InputForward = "-Forward"; + } + if (player["Right"]) { + m_NextSnapshot.InputRight = "+Right"; + } + + if (m_NextSnapshot.InputForward != "") { + Packet packet(MessageType::Event, m_SendPacketID); + packet.WriteString(m_NextSnapshot.InputForward); + send(packet); + } else { + Packet packet(MessageType::Event, m_SendPacketID); + packet.WriteString("0Forward"); + send(packet); + } + + if (m_NextSnapshot.InputRight != "") { + Packet packet(MessageType::Event, m_SendPacketID); + packet.WriteString(m_NextSnapshot.InputRight); + send(packet); + } else { + Packet packet(MessageType::Event, m_SendPacketID); + packet.WriteString("0Right"); + send(packet); + } +} + +void Client::parseMessageType(Packet& packet) +{ + int messageType = packet.ReadPrimitive(); + if (messageType == -1) + return; + // Read packet ID + m_PreviousPacketID = m_PacketID; // Set previous packet id + m_PacketID = packet.ReadPrimitive(); //Read new packet id + //IdentifyPacketLoss(); + + switch (static_cast(messageType)) { + case MessageType::Connect: + parseConnect(packet); + break; + case MessageType::ClientPing: + parsePing(); + break; + case MessageType::ServerPing: + parseServerPing(); + break; + case MessageType::Message: + break; + case MessageType::Snapshot: + parseSnapshot(packet); + break; + case MessageType::Disconnect: + break; + case MessageType::Event: + parseEventMessage(packet); + break; + default: + break; + } +} + +void Client::parseConnect(Packet& packet) +{ + m_PlayerID = packet.ReadPrimitive(); + LOG_INFO("%i: I am player: %i", m_PacketID, m_PlayerID); +} + +void Client::parsePing() +{ + m_DurationOfPingTime = 1000 * (std::clock() - m_StartPingTime) / static_cast(CLOCKS_PER_SEC); + LOG_INFO("%i: response time with ctime(ms): %f", m_PacketID, m_DurationOfPingTime); +} + +void Client::parseServerPing() +{ + Packet packet(MessageType::ServerPing, m_SendPacketID); + packet.WriteString("Ping recieved"); + send(packet); +} + +void Client::parseEventMessage(Packet& packet) +{ + int Id = -1; + std::string command = packet.ReadString(); + if (command.find("+Player") != std::string::npos) { + Id = packet.ReadPrimitive(); + // Sett Player name + m_PlayerDefinitions[Id].Name = command.erase(0, 7); + } else { + LOG_INFO("%i: Event message: %s", m_PacketID, command.c_str()); + } +} + +void Client::parseSnapshot(Packet& packet) +{ + std::string tempName; + for (size_t i = 0; i < MAXCONNECTIONS; i++) { + // We're checking for empty name for now. This might not be the best way, + // but it is to avoid sending redundant data. + tempName = packet.ReadString(); + + + // Apply the position data read to the player entity + // New player connected on the server side + if (m_PlayerDefinitions[i].Name == "" && tempName != "") { + m_PlayerDefinitions[i].Name = tempName; + m_PlayerDefinitions[i].EntityID = createPlayer(); + } else if (m_PlayerDefinitions[i].Name != "" && tempName == "") { + // Someone disconnected + // TODO: Insert code here + break; + } else if (m_PlayerDefinitions[i].Name == "" && tempName == "") { + // Not a connected player + break; + } + if (m_PlayerDefinitions[i].EntityID != -1) { + + // Move player to server position + int dataSize = m_World->GetComponent(m_PlayerDefinitions[i].EntityID, "Transform").Info.Meta.Stride; + memcpy(m_World->GetComponent(m_PlayerDefinitions[i].EntityID, "Transform").Data, packet.ReadData(dataSize), dataSize); + } + } +} + +int Client::receive(char* data, size_t length) +{ + boost::system::error_code error; + + int bytesReceived = m_Socket.receive_from(boost + ::asio::buffer((void*)data, length), + m_ReceiverEndpoint, + 0, error); + + if (error) { + //LOG_ERROR("receive: %s", error.message().c_str()); + } + + return bytesReceived; +} + +void Client::send(Packet& packet) +{ + m_Socket.send_to(boost::asio::buffer( + packet.Data(), + packet.Size()), + m_ReceiverEndpoint, 0); +} + +void Client::connect() +{ + Packet packet(MessageType::Connect, m_SendPacketID); + packet.WriteString(m_PlayerName); + m_StartPingTime = std::clock(); + send(packet); +} + +void Client::disconnect() +{ + Packet packet(MessageType::Connect, m_SendPacketID); + packet.WriteString("+Disconnect"); + send(packet); +} + +void Client::ping() +{ + Packet packet(MessageType::Connect, m_SendPacketID); + packet.WriteString("Ping"); + m_StartPingTime = std::clock(); + send(packet); +} + +void Client::moveMessageHead(char*& data, size_t& length, size_t stepSize) +{ + data += stepSize; + length -= stepSize; +} + +bool Client::OnInputCommand(const Events::InputCommand & e) +{ + if (isConnected()) { + ComponentWrapper& player = m_World->GetComponent(m_PlayerDefinitions[m_PlayerID].EntityID, "Player"); + if (e.Command == "Forward") { + if (e.Value > 0) { + (bool&)player["Forward"] = true; + (bool&)player["Back"] = false; + } else if (e.Value < 0) { + (bool&)player["Back"] = true; + (bool&)player["Forward"] = false; + } else { + (bool&)player["Forward"] = false; + (bool&)player["Back"] = false; + } + } + if (e.Command == "Right") { + if (e.Value > 0) { + (bool&)player["Right"] = true; + (bool&)player["Left"] = false; + } else if (e.Value < 0) { + (bool&)player["Left"] = true; + (bool&)player["Right"] = false; + } else { + (bool&)player["Left"] = false; + (bool&)player["Right"] = false; + } + } + } + if (e.Command == "ConnectToServer") { // Connect for now + connect(); + } + return false; +} + + +void Client::identifyPacketLoss() +{ + // if no packets lost, difference should be equal to 1 + int difference = m_PacketID - m_PreviousPacketID; + if (difference != 1) { + LOG_INFO("%i Packet(s) were lost...", difference); + } +} + +bool Client::isConnected() +{ + if (m_PlayerID != -1) { + if (m_PlayerDefinitions[m_PlayerID].EntityID != -1) { + return true; + } + } + return false; +} + +EntityID Client::createPlayer() +{ + EntityID entityID = m_World->CreateEntity(); + ComponentWrapper transform = m_World->AttachComponent(entityID, "Transform"); + ComponentWrapper model = m_World->AttachComponent(entityID, "Model"); + model["Resource"] = "Models/Core/UnitSphere.obj"; + ComponentWrapper player = m_World->AttachComponent(entityID, "Player"); + return entityID; +} diff --git a/src/Engine/Network/Packet.cpp b/src/Engine/Network/Packet.cpp new file mode 100644 index 00000000..52b15065 --- /dev/null +++ b/src/Engine/Network/Packet.cpp @@ -0,0 +1,72 @@ +#include "Network/Packet.h" + +Packet::Packet(MessageType type, unsigned int& packetID) +{ + m_Data = new char[m_MaxPacketSize]; + // Create message header + // Add message type + int messageType = static_cast(type); + Packet::WritePrimitive(messageType); + packetID = packetID % 1000; // Packet id modulos + Packet::WritePrimitive(packetID); + packetID++; +} + +// Create message +Packet::Packet(char* data, const int sizeOfPacket) +{ + // Resize message + m_MaxPacketSize = sizeOfPacket; + // Copy data newly allocated memory + m_Data = new char[sizeOfPacket]; + memcpy(m_Data, data, sizeOfPacket); + m_Offset = sizeOfPacket; +} + +Packet::~Packet() +{ + delete[] m_Data; +} + +void Packet::WriteString(std::string str) +{ + // Message, add one extra byte for null terminator + int sizeOfString = str.size() + 1; + if (m_Offset + sizeOfString > m_MaxPacketSize) { + LOG_WARNING("Package::WriteString(): Data size in packet exceeded maximum package size.\n"); + } + memcpy(m_Data + m_Offset, str.data(), sizeOfString * sizeof(char)); + m_Offset += sizeOfString * sizeof(char); +} + +void Packet::WriteData(char * data, int sizeOfData) +{ + if (m_Offset + sizeOfData > m_MaxPacketSize) { + LOG_WARNING("Packet::WriteData(): Data size in packet exceeded maximum packet size.\n"); + } + memcpy(m_Data + m_Offset, data, sizeOfData); + m_Offset += sizeOfData; +} + +std::string Packet::ReadString() +{ + std::string returnValue(m_Data + m_ReturnDataOffset); + if (m_Offset < m_ReturnDataOffset + returnValue.size()) { + LOG_WARNING("packet ReadString(): Oh no! You are trying to remove things outside my memory kingdom"); + return "PopFrontString Failed"; + } + // +1 for null terminator. + m_ReturnDataOffset += returnValue.size() + 1; + return returnValue; +} + +char * Packet::ReadData(int SizeOfData) +{ + if (m_Offset < m_ReturnDataOffset + SizeOfData) { + LOG_WARNING("packet ReadData(): Oh no! You are trying to remove things outside my memory kingdom"); + return nullptr; + } + unsigned int oldReturnDataOffset = m_ReturnDataOffset; + m_ReturnDataOffset += SizeOfData; + return (m_Data + oldReturnDataOffset); +} \ No newline at end of file diff --git a/src/Engine/Network/Server.cpp b/src/Engine/Network/Server.cpp index f65dc834..261583cc 100644 --- a/src/Engine/Network/Server.cpp +++ b/src/Engine/Network/Server.cpp @@ -1,11 +1,355 @@ -#include "Network\Server.h" +#include "Network/Server.h" -Server::Server() -{ - -} +Server::Server() : m_Socket(m_IOService, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 13)) +{ } Server::~Server() -{ +{ } + +void Server::Start(World* world, EventBroker* eventBroker) +{ + m_World = world; + m_EventBroker = eventBroker; + for (size_t i = 0; i < MAXCONNECTIONS; i++) { + m_StopTimes[i] = std::clock(); + } + LOG_INFO("I am Server. BIP BOP\n"); +} + +void Server::Update() +{ + readFromClients(); +} + +void Server::Close() +{ + m_ThreadIsRunning = false; +} + +void Server::readFromClients() +{ + // m_ThreadIsRunning might be unnecessary but the + // program crashed if it executed m_Socket.available() + // when closing the program. + + if (m_Socket.available()) { + try { + bytesRead = receive(readBuffer, INPUTSIZE); + Packet packet(readBuffer, bytesRead); + parseMessageType(packet); + } catch (const std::exception& err) { + //LOG_ERROR("%i: Read from client crashed %s", m_PacketID, err.what()); + } + + } + std::clock_t currentTime = std::clock(); + // Send snapshot + if (snapshotInterval < (1000 * (currentTime - previousSnapshotMessage) / (double)CLOCKS_PER_SEC)) { + sendSnapshot(); + previousSnapshotMessage = currentTime; + } + + // Send pings each + if (intervalMs < (1000 * (currentTime - previousePingMessage) / (double)CLOCKS_PER_SEC)) { + sendPing(); + previousePingMessage = currentTime; + } + + // Time out logic + if (checkTimeOutInterval < (1000 * (currentTime - timOutTimer) / (double)CLOCKS_PER_SEC)) { + checkForTimeOuts(); + timOutTimer = currentTime; + } +} + +void Server::parseMessageType(Packet& packet) +{ + int messageType = packet.ReadPrimitive(); // Read what type off message was sent from server + + // Read packet ID + m_PreviousPacketID = m_PacketID; // Set previous packet id + m_PacketID = packet.ReadPrimitive(); //Read new packet id + //IdentifyPacketLoss(); + switch (static_cast(messageType)) { + case MessageType::Connect: + parseConnect(packet); + break; + case MessageType::ClientPing: + //parseClientPing(); + break; + case MessageType::ServerPing: + parseServerPing(); + break; + case MessageType::Message: + break; + case MessageType::Snapshot: + parseSnapshot(packet); + break; + case MessageType::Disconnect: + parseDisconnect(); + break; + case MessageType::Event: + parseEvent(packet); + break; + default: + break; + } +} + +int Server::receive(char * data, size_t length) +{ + length = m_Socket.receive_from( + boost::asio::buffer((void*)data + , length) + , m_ReceiverEndpoint, 0); + return length; +} + +void Server::send(Packet& packet, int playerID) +{ + m_Socket.send_to( + boost::asio::buffer(packet.Data(), packet.Size()), + m_PlayerDefinitions[playerID].Endpoint, + 0); +} + +void Server::send(Packet & packet) +{ + m_Socket.send_to( + boost::asio::buffer( + packet.Data(), + packet.Size()), + m_ReceiverEndpoint, + 0); +} + +void Server::moveMessageHead(char *& data, size_t & length, size_t stepSize) +{ + data += stepSize; + length -= stepSize; +} + +void Server::broadcast(std::string message) +{ + Packet packet(MessageType::Event, m_SendPacketID); + packet.WriteString(message); + for (int i = 0; i < MAXCONNECTIONS; i++) { + if (m_PlayerDefinitions[i].Endpoint.address() != boost::asio::ip::address()) { + send(packet, i); + } + } +} + +void Server::broadcast(Packet& packet) +{ + for (int i = 0; i < MAXCONNECTIONS; ++i) { + if (m_PlayerDefinitions[i].Endpoint.address() != boost::asio::ip::address()) { + send(packet, i); + } + } +} + +void Server::sendSnapshot() +{ + Packet packet(MessageType::Snapshot, m_SendPacketID); + for (size_t i = 0; i < MAXCONNECTIONS; i++) { + + // Send an empty name if there is no player connected on this position. + packet.WriteString(m_PlayerDefinitions[i].Name); + + if (m_PlayerDefinitions[i].EntityID == -1) { + continue; + } + // Pack transfrom component into data packet + auto transform = m_World->GetComponent(m_PlayerDefinitions[i].EntityID, "Transform"); + packet.WriteData(transform.Data, transform.Info.Meta.Stride); + } + broadcast(packet); +} + +void Server::sendPing() +{ + // Prints connected players ping + for (size_t i = 0; i < MAXCONNECTIONS; i++) { + if (m_PlayerDefinitions[i].Endpoint.address() != boost::asio::ip::address()) { + int ping = 1000 * (m_StopTimes[i] - m_StartPingTime) / static_cast(CLOCKS_PER_SEC); + LOG_INFO("%i: Player %i's ping: %i", m_PacketID, i, ping); + } + } + + // Create ping message + Packet packet(MessageType::ServerPing, m_SendPacketID); + packet.WriteString("Ping from server"); + // Time message + m_StartPingTime = std::clock(); + // Send message + broadcast(packet); +} + +void Server::checkForTimeOuts() +{ + int timeOutTimeMs = 5000; + int startPing = 1000 * m_StartPingTime + / static_cast(CLOCKS_PER_SEC); + + for (size_t i = 0; i < MAXCONNECTIONS; i++) { + if (m_PlayerDefinitions[i].Endpoint.address() != boost::asio::ip::address()) { + int stopPing = 1000 * m_StopTimes[i] + / static_cast(CLOCKS_PER_SEC); + if (startPing > stopPing + timeOutTimeMs) { + LOG_INFO("Player %i timed out!", i); + disconnect(i); + } + } + } +} + +void Server::disconnect(int i) +{ + broadcast("A player disconnected"); + LOG_INFO("Player %i disconnected/timed out", i); + + // Remove enteties and stuff + m_PlayerDefinitions[i].Endpoint = boost::asio::ip::udp::endpoint(); + m_PlayerDefinitions[i].EntityID = -1; + m_PlayerDefinitions[i].Name = ""; +} + +void Server::parseEvent(Packet& packet) +{ + size_t i; + for (i = 0; i < MAXCONNECTIONS; i++) { + if (m_PlayerDefinitions[i].Endpoint.address() == m_ReceiverEndpoint.address()) { + break; + } + } + // If no player matches the address return. + if (i >= 8) + return; + + unsigned int entityId = m_PlayerDefinitions[i].EntityID; + std::string eventString = packet.ReadString(); + if ("+Forward" == eventString) { + m_World->GetComponent(entityId, "Player")["Forward"] = true; + m_World->GetComponent(entityId, "Player")["Back"] = false; + } else if ("-Forward" == eventString) { + m_World->GetComponent(entityId, "Player")["Forward"] = false; + m_World->GetComponent(entityId, "Player")["Back"] = true; + } else if ("0Forward" == eventString) { + m_World->GetComponent(entityId, "Player")["Forward"] = false; + m_World->GetComponent(entityId, "Player")["Back"] = false; + } + if ("+Right" == eventString) { + m_World->GetComponent(entityId, "Player")["Left"] = false; + m_World->GetComponent(entityId, "Player")["Right"] = true; + } else if ("-Right" == eventString) { + m_World->GetComponent(entityId, "Player")["Right"] = false; + m_World->GetComponent(entityId, "Player")["Left"] = true; + } else if ("0Right" == eventString) { + m_World->GetComponent(entityId, "Player")["Right"] = false; + m_World->GetComponent(entityId, "Player")["Left"] = false; + } +} + +void Server::parseConnect(Packet& packet) +{ + LOG_INFO("Parsing connections"); + // Check if player is already connected + for (int i = 0; i < MAXCONNECTIONS; i++) { + if (m_PlayerDefinitions[i].Endpoint.address() == m_ReceiverEndpoint.address()) { + return; + } + } + + for (int i = 0; i < MAXCONNECTIONS; i++) { + if (m_PlayerDefinitions[i].Endpoint.address() == boost::asio::ip::address()) { + // Create new player + m_PlayerDefinitions[i].EntityID = createPlayer(); + m_PlayerDefinitions[i].Endpoint = m_ReceiverEndpoint; + m_PlayerDefinitions[i].Name = packet.ReadString(); + + m_StopTimes[i] = std::clock(); + + LOG_INFO("Player \"%s\" connected on IP: %s", m_PlayerDefinitions[i].Name, m_PlayerDefinitions[i].Endpoint.address().to_string()); + + Packet packet(MessageType::Connect, m_SendPacketID); + packet.WritePrimitive(i); // Player ID + + send(packet, i); + + // Send notification that a player has connected + std::string str = m_PacketID + "Player " + m_PlayerDefinitions[i].Name + " connected on: " + + m_PlayerDefinitions[i].Endpoint.address().to_string(); + broadcast(str); + break; + } + } +} + +void Server::parseDisconnect() +{ + LOG_INFO("%i: Parsing disconnect", m_PacketID); + + for (int i = 0; i < MAXCONNECTIONS; i++) { + if (m_PlayerDefinitions[i].Endpoint.address() == m_ReceiverEndpoint.address()) { + disconnect(i); + break; + } + } +} + +void Server::parseClientPing() +{ + LOG_INFO("%i: Parsing ping", m_PacketID); + // Return ping + Packet packet(MessageType::ClientPing, m_SendPacketID); + packet.WriteString("Ping received"); + send(packet); // This dosen't work for multiple users +} + +void Server::parseServerPing() +{ + for (int i = 0; i < MAXCONNECTIONS; i++) { + if (m_PlayerDefinitions[i].Endpoint.address() == m_ReceiverEndpoint.address()) { + m_StopTimes[i] = std::clock(); + break; + } + } +} + +// NOT USED +void Server::parseSnapshot(Packet& packet) +{ + // Does no logic. Returns snapshot if client request one + // The snapshot is not a real snapshot tho... + for (int i = 0; i < MAXCONNECTIONS; i++) { + if (m_PlayerDefinitions[i].Endpoint.address() != boost::asio::ip::address()) { + m_Socket.send_to( + boost::asio::buffer("I'm sending a snapshot to you guys!"), + m_PlayerDefinitions[i].Endpoint, + 0); + } + } +} + +void Server::identifyPacketLoss() +{ + // if no packets lost, difference should be equal to 1 + int difference = m_PacketID - m_PreviousPacketID; + if (difference != 1) { + LOG_INFO("%i Packet(s) were lost...", difference); + } +} + +EntityID Server::createPlayer() +{ + EntityID entityID = m_World->CreateEntity(); + ComponentWrapper transform = m_World->AttachComponent(entityID, "Transform"); + transform["Position"] = glm::vec3(-1.5f, 0.f, 0.f); + ComponentWrapper model = m_World->AttachComponent(entityID, "Model"); + model["Resource"] = "Models/Core/UnitSphere.obj"; + model["Color"] = glm::vec4(rand()%255 / 255.f, rand()%255 / 255.f, rand() %255 / 255.f, 1.f); + ComponentWrapper player = m_World->AttachComponent(entityID, "Player"); + return entityID; } diff --git a/src/Engine/Rendering/DrawFinalPass.cpp b/src/Engine/Rendering/DrawFinalPass.cpp new file mode 100644 index 00000000..359981d3 --- /dev/null +++ b/src/Engine/Rendering/DrawFinalPass.cpp @@ -0,0 +1,65 @@ +#include "Rendering/DrawFinalPass.h" + +DrawFinalPass::DrawFinalPass(IRenderer* renderer, LightCullingPass* lightCullingPass) +{ + m_Renderer = renderer; + m_LightCullingPass = lightCullingPass; + InitializeTextures(); + InitializeShaderPrograms(); +} + +void DrawFinalPass::InitializeTextures() +{ + m_WhiteTexture = ResourceManager::Load("Textures/Core/Blank.png"); +} + +void DrawFinalPass::InitializeShaderPrograms() +{ + m_ForwardPlusProgram = ResourceManager::Load("#ForwardPlusProgram"); + m_ForwardPlusProgram->AddShader(std::shared_ptr(new VertexShader("Shaders/ForwardPlus.vert.glsl"))); + m_ForwardPlusProgram->AddShader(std::shared_ptr(new FragmentShader("Shaders/ForwardPlus.frag.glsl"))); + m_ForwardPlusProgram->Compile(); + m_ForwardPlusProgram->Link(); +} + +void DrawFinalPass::Draw(RenderQueueCollection& rq) +{ + GLERROR("DrawFinalPass::Draw: Pre"); + + DrawFinalPassState state; + m_ForwardPlusProgram->Bind(); + GLuint shaderHandle = m_ForwardPlusProgram->GetHandle(); + + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_LightCullingPass->LightSSBO()); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, m_LightCullingPass->LightGridSSBO()); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, m_LightCullingPass->LightIndexSSBO()); + + glUniformMatrix4fv(glGetUniformLocation(shaderHandle, "V"), 1, GL_FALSE, glm::value_ptr(m_Renderer->Camera()->ViewMatrix())); + glUniformMatrix4fv(glGetUniformLocation(shaderHandle, "P"), 1, GL_FALSE, glm::value_ptr(m_Renderer->Camera()->ProjectionMatrix())); + + //TODO: Render: Add code for more jobs than modeljobs. + for (auto &job : rq.Forward) { + auto modelJob = std::dynamic_pointer_cast(job); + if(modelJob) { + //TODO: Kolla upp "header/include/common" shader saken så man slipper skicka in asmycket uniforms + glUniformMatrix4fv(glGetUniformLocation(shaderHandle, "M"), 1, GL_FALSE, glm::value_ptr(modelJob->ModelMatrix)); + glUniform4fv(glGetUniformLocation(shaderHandle, "Color"), 1, glm::value_ptr(modelJob->Color)); + + if(modelJob->DiffuseTexture != nullptr) { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, modelJob->DiffuseTexture->m_Texture); + } else { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_WhiteTexture->m_Texture); + } + + glBindVertexArray(modelJob->Model->VAO); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, modelJob->Model->ElementBuffer); + glDrawElementsBaseVertex(GL_TRIANGLES, modelJob->EndIndex - modelJob->StartIndex + 1, GL_UNSIGNED_INT, 0, modelJob->StartIndex); + + continue; + } + } + GLERROR("DrawFinalPass::Draw: END"); + +} diff --git a/src/Engine/Rendering/DrawFinalPassState.cpp b/src/Engine/Rendering/DrawFinalPassState.cpp new file mode 100644 index 00000000..7bfb99b5 --- /dev/null +++ b/src/Engine/Rendering/DrawFinalPassState.cpp @@ -0,0 +1,17 @@ +#include "Rendering/DrawFinalPassState.h" + + +DrawFinalPassState::DrawFinalPassState() +{ + BindFramebuffer(0); + + Enable(GL_DEPTH_TEST); + Enable(GL_CULL_FACE); + ClearColor(glm::vec4(200.f / 255, 0.f / 255, 200.f / 255, 0.f)); + Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +DrawFinalPassState::~DrawFinalPassState() +{ + +} diff --git a/src/Engine/Rendering/DrawScenePass.cpp b/src/Engine/Rendering/DrawScenePass.cpp new file mode 100644 index 00000000..19de3d58 --- /dev/null +++ b/src/Engine/Rendering/DrawScenePass.cpp @@ -0,0 +1,69 @@ +#include "Rendering/DrawScenePass.h" + +DrawScenePass::DrawScenePass(IRenderer* renderer) +{ + m_Renderer = renderer; + InitializeTextures(); + InitializeShaderPrograms(); +} + +void DrawScenePass::InitializeTextures() +{ + m_WhiteTexture = ResourceManager::Load("Textures/Core/Blank.png"); +} + +void DrawScenePass::InitializeShaderPrograms() +{ + m_BasicForwardProgram = ResourceManager::Load("#BasicForwardProgram"); + + m_BasicForwardProgram->AddShader(std::shared_ptr(new VertexShader("Shaders/BasicForward.vert.glsl"))); + m_BasicForwardProgram->AddShader(std::shared_ptr(new FragmentShader("Shaders/BasicForward.frag.glsl"))); + m_BasicForwardProgram->Compile(); + m_BasicForwardProgram->Link(); +} + +void DrawScenePass::Draw(RenderQueueCollection& rq) +{ + //glBindFramebuffer(GL_FRAMEBUFFER, 0); + GLERROR("DrawScenePass::Draw: Pre"); + + DrawScenePassState state; + m_BasicForwardProgram->Bind(); + + + //TODO: Render: Add code for more jobs than modeljobs. + for (auto &job : rq.Forward) { + auto modelJob = std::dynamic_pointer_cast(job); + if (modelJob) { + GLuint ShaderHandle = m_BasicForwardProgram->GetHandle(); + + //TODO: Kolla upp "header/include/common" shader saken så man slipper skicka in asmycket uniforms + glUniformMatrix4fv(glGetUniformLocation(ShaderHandle, "M"), 1, GL_FALSE, glm::value_ptr(modelJob->ModelMatrix)); + glUniformMatrix4fv(glGetUniformLocation(ShaderHandle, "V"), 1, GL_FALSE, glm::value_ptr(m_Renderer->Camera()->ViewMatrix())); + glUniformMatrix4fv(glGetUniformLocation(ShaderHandle, "P"), 1, GL_FALSE, glm::value_ptr(m_Renderer->Camera()->ProjectionMatrix())); + glUniform4fv(glGetUniformLocation(ShaderHandle, "Color"), 1, glm::value_ptr(modelJob->Color)); + + //TODO: Renderer: bättre textur felhantering samt fler texturer stöd + if (modelJob->DiffuseTexture != nullptr) { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, modelJob->DiffuseTexture->m_Texture); + } else { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_WhiteTexture->m_Texture); + } + + glBindVertexArray(modelJob->Model->VAO); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, modelJob->Model->ElementBuffer); + glDrawElementsBaseVertex(GL_TRIANGLES, modelJob->EndIndex - modelJob->StartIndex + 1, GL_UNSIGNED_INT, 0, modelJob->StartIndex); + + continue; + } + auto spriteJob = std::dynamic_pointer_cast(job); + if(spriteJob) + { + //Hello im a sprite, please draw me. + } + + } + GLERROR("DrawScenePass::Draw: End"); +} diff --git a/src/Engine/Rendering/DrawScenePassState.cpp b/src/Engine/Rendering/DrawScenePassState.cpp new file mode 100644 index 00000000..9e7497a3 --- /dev/null +++ b/src/Engine/Rendering/DrawScenePassState.cpp @@ -0,0 +1,18 @@ +#include "Rendering/DrawScenePassState.h" + + +DrawScenePassState::DrawScenePassState() +{ + GLERROR("---"); + BindFramebuffer(0); + GLERROR("---"); + Enable(GL_DEPTH_TEST); + Enable(GL_CULL_FACE); + ClearColor(glm::vec4(255.f / 255, 163.f / 255, 176.f / 255, 0.f)); + Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +DrawScenePassState::~DrawScenePassState() +{ + +} diff --git a/src/Engine/Rendering/FrameBuffer.cpp b/src/Engine/Rendering/FrameBuffer.cpp index 1b53d868..b2b29626 100644 --- a/src/Engine/Rendering/FrameBuffer.cpp +++ b/src/Engine/Rendering/FrameBuffer.cpp @@ -48,13 +48,21 @@ void FrameBuffer::Generate() switch ((*it)->m_ResourceType) { case GL_TEXTURE_2D: glFramebufferTexture2D(GL_FRAMEBUFFER, (*it)->m_Attachment, (*it)->m_ResourceType, *(*it)->m_ResourceHandle, 0); + GLERROR("FrameBuffer generate: glFramebufferTexture2D"); + break; case GL_RENDERBUFFER: glFramebufferRenderbuffer(GL_FRAMEBUFFER, (*it)->m_Attachment, (*it)->m_ResourceType, *(*it)->m_ResourceHandle); + GLERROR("FrameBuffer generate: glFramebufferRenderbuffer"); + if ( (*it)->m_Attachment != GL_COLOR_ATTACHMENT0 || + (*it)->m_Attachment != GL_DEPTH_ATTACHMENT || + (*it)->m_Attachment != GL_STENCIL_ATTACHMENT) + { + LOG_ERROR("RenderBuffer Attachment not valid."); + } break; } - GLERROR("FrameBuffer generate"); if ((*it)->m_Attachment != GL_DEPTH_ATTACHMENT) { attachments.push_back((*it)->m_Attachment); diff --git a/src/Engine/Rendering/ImGuiRenderPass.cpp b/src/Engine/Rendering/ImGuiRenderPass.cpp new file mode 100644 index 00000000..96f987d9 --- /dev/null +++ b/src/Engine/Rendering/ImGuiRenderPass.cpp @@ -0,0 +1,307 @@ +#include "Rendering/ImGuiRenderPass.h" + +ImGuiRenderPass::ImGuiRenderPass(IRenderer* renderer, EventBroker* eventBroker) + : m_Renderer(renderer) + , m_EventBroker(eventBroker) +{ + g_Window = renderer->Window(); + + ImGuiIO& io = ImGui::GetIO(); + io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array. + io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP; + io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN; + io.KeyMap[ImGuiKey_PageUp] = GLFW_KEY_PAGE_UP; + io.KeyMap[ImGuiKey_PageDown] = GLFW_KEY_PAGE_DOWN; + io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME; + io.KeyMap[ImGuiKey_End] = GLFW_KEY_END; + io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE; + io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE; + io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER; + io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE; + io.KeyMap[ImGuiKey_A] = GLFW_KEY_A; + io.KeyMap[ImGuiKey_C] = GLFW_KEY_C; + io.KeyMap[ImGuiKey_V] = GLFW_KEY_V; + io.KeyMap[ImGuiKey_X] = GLFW_KEY_X; + io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y; + io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z; + + ImGuiStyle& style = ImGui::GetStyle(); + style.Alpha = 1.f; + style.WindowPadding = ImVec2(8.f, 7.f); + style.WindowRounding = 4.f; + style.ChildWindowRounding = 0.f; + style.FramePadding = ImVec2(4.f, 2.f); + style.FrameRounding = 2.f; + style.ItemSpacing = ImVec2(6.f, 2.f); + style.ItemInnerSpacing = ImVec2(3.f, 4.f); + style.IndentSpacing = 16.f; + style.ScrollbarSize = 12; + style.ScrollbarRounding = 2.f; + style.GrabMinSize = 13.f; + style.GrabRounding = 3.f; + + createDeviceObjects(); + + EVENT_SUBSCRIBE_MEMBER(m_EMousePress, &ImGuiRenderPass::OnMousePress); + EVENT_SUBSCRIBE_MEMBER(m_EMouseRelease, &ImGuiRenderPass::OnMouseRelease); + EVENT_SUBSCRIBE_MEMBER(m_EMouseMove, &ImGuiRenderPass::OnMouseMove); + EVENT_SUBSCRIBE_MEMBER(m_EMouseScroll, &ImGuiRenderPass::OnMouseScroll); + EVENT_SUBSCRIBE_MEMBER(m_EKeyDown, &ImGuiRenderPass::OnKeyDown); + EVENT_SUBSCRIBE_MEMBER(m_EKeyUp, &ImGuiRenderPass::OnKeyUp); + EVENT_SUBSCRIBE_MEMBER(m_EKeyboardChar, &ImGuiRenderPass::OnKeyboardChar); + + // Prime the first frame + newFrame(); +} + +void ImGuiRenderPass::Update(double dt) +{ + g_DeltaTime = dt; +} + +void ImGuiRenderPass::Draw() +{ + ImGuiIO& io = ImGui::GetIO(); + + ImGui::Render(); + + ImDrawData* draw_data = ImGui::GetDrawData(); + + // Set up render state + ImGuiRenderState state; + glActiveTexture(GL_TEXTURE0); + + // Handle cases of screen coordinates != from framebuffer coordinates (e.g. retina displays) + float fb_height = io.DisplaySize.y * io.DisplayFramebufferScale.y; + draw_data->ScaleClipRects(io.DisplayFramebufferScale); + + // Setup viewport, orthographic projection matrix + glViewport(0, 0, (GLsizei)io.DisplaySize.x, (GLsizei)io.DisplaySize.y); + const float ortho_projection[4][4] = + { + { 2.0f/io.DisplaySize.x, 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/-io.DisplaySize.y, 0.0f, 0.0f }, + { 0.0f, 0.0f, -1.0f, 0.0f }, + { -1.0f, 1.0f, 0.0f, 1.0f }, + }; + glUseProgram(g_ShaderHandle); + glUniform1i(g_AttribLocationTex, 0); + glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); + glBindVertexArray(g_VaoHandle); + + for (int n = 0; n < draw_data->CmdListsCount; n++) { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + const ImDrawIdx* idx_buffer_offset = 0; + + glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmd_list->VtxBuffer.size() * sizeof(ImDrawVert), (GLvoid*)&cmd_list->VtxBuffer.front(), GL_STREAM_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmd_list->IdxBuffer.size() * sizeof(ImDrawIdx), (GLvoid*)&cmd_list->IdxBuffer.front(), GL_STREAM_DRAW); + + for (const ImDrawCmd* pcmd = cmd_list->CmdBuffer.begin(); pcmd != cmd_list->CmdBuffer.end(); pcmd++) { + if (pcmd->UserCallback) { + pcmd->UserCallback(cmd_list, pcmd); + } else { + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); + glScissor((int)pcmd->ClipRect.x, (int)(fb_height - pcmd->ClipRect.w), (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y)); + glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset); + } + idx_buffer_offset += pcmd->ElemCount; + } + } + + // Start next frame + newFrame(); +} + +bool ImGuiRenderPass::OnMousePress(const Events::MousePress& e) +{ + ImGuiIO& io = ImGui::GetIO(); + io.MouseDown[e.Button] = true; + return false; +} + +bool ImGuiRenderPass::OnMouseRelease(const Events::MouseRelease& e) +{ + ImGuiIO& io = ImGui::GetIO(); + io.MouseDown[e.Button] = false; + return false; +} + +bool ImGuiRenderPass::OnMouseMove(const Events::MouseMove& e) +{ + ImGuiIO& io = ImGui::GetIO(); + io.MousePos.x = e.X; + io.MousePos.y = e.Y; + return true; +} + +bool ImGuiRenderPass::OnMouseScroll(const Events::MouseScroll& e) +{ + g_MouseWheel += (float)e.DeltaY; + return true; +} + +bool ImGuiRenderPass::OnKeyDown(const Events::KeyDown& e) +{ + ImGuiIO& io = ImGui::GetIO(); + io.KeysDown[e.KeyCode] = true; + return true; +} + +bool ImGuiRenderPass::OnKeyUp(const Events::KeyUp& e) +{ + ImGuiIO& io = ImGui::GetIO(); + io.KeysDown[e.KeyCode] = false; + return true; +} + +bool ImGuiRenderPass::OnKeyboardChar(const Events::KeyboardChar& e) +{ + ImGuiIO& io = ImGui::GetIO(); + if (e.Char > 0 && e.Char < 0x10000) { + io.AddInputCharacter((unsigned short)e.Char); + return true; + } else { + return false; + } +} + +bool ImGuiRenderPass::createDeviceObjects() +{ + // Backup GL state + GLint last_texture, last_array_buffer, last_vertex_array; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); + + const GLchar *vertex_shader = + "#version 330\n" + "uniform mat4 ProjMtx;\n" + "in vec2 Position;\n" + "in vec2 UV;\n" + "in vec4 Color;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* fragment_shader = + "#version 330\n" + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture( Texture, Frag_UV.st);\n" + "}\n"; + + g_ShaderHandle = glCreateProgram(); + g_VertHandle = glCreateShader(GL_VERTEX_SHADER); + g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(g_VertHandle, 1, &vertex_shader, 0); + glShaderSource(g_FragHandle, 1, &fragment_shader, 0); + glCompileShader(g_VertHandle); + glCompileShader(g_FragHandle); + glAttachShader(g_ShaderHandle, g_VertHandle); + glAttachShader(g_ShaderHandle, g_FragHandle); + glLinkProgram(g_ShaderHandle); + + g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture"); + g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx"); + g_AttribLocationPosition = glGetAttribLocation(g_ShaderHandle, "Position"); + g_AttribLocationUV = glGetAttribLocation(g_ShaderHandle, "UV"); + g_AttribLocationColor = glGetAttribLocation(g_ShaderHandle, "Color"); + + glGenBuffers(1, &g_VboHandle); + glGenBuffers(1, &g_ElementsHandle); + + glGenVertexArrays(1, &g_VaoHandle); + glBindVertexArray(g_VaoHandle); + glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); + glEnableVertexAttribArray(g_AttribLocationPosition); + glEnableVertexAttribArray(g_AttribLocationUV); + glEnableVertexAttribArray(g_AttribLocationColor); + +#define OFFSETOF(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT)) + glVertexAttribPointer(g_AttribLocationPosition, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)OFFSETOF(ImDrawVert, pos)); + glVertexAttribPointer(g_AttribLocationUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)OFFSETOF(ImDrawVert, uv)); + glVertexAttribPointer(g_AttribLocationColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)OFFSETOF(ImDrawVert, col)); +#undef OFFSETOF + + createFontsTexture(); + + // Restore modified GL state + glBindTexture(GL_TEXTURE_2D, last_texture); + glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); + glBindVertexArray(last_vertex_array); + + return true; +} + +bool ImGuiRenderPass::createFontsTexture() +{ + // Build texture atlas + ImGuiIO& io = ImGui::GetIO(); + + io.Fonts->AddFontFromFileTTF("Fonts/DroidSans.ttf", 13.f); + //io.Fonts->AddFontFromFileTTF("Fonts/ProggyClean.ttf", 13.f); + //io.Fonts->AddFontFromFileTTF("Fonts/ProggyTiny.ttf", 10.f); + //io.Fonts->AddFontFromFileTTF("Fonts/Karla-Regular.ttf", 15.0f); + + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits for OpenGL3 demo because it is more likely to be compatible with user's existing shader. + + // Upload texture to graphics system + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGenTextures(1, &g_FontTexture); + glBindTexture(GL_TEXTURE_2D, g_FontTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Store our identifier + io.Fonts->TexID = (void *)(intptr_t)g_FontTexture; + + // Restore state + glBindTexture(GL_TEXTURE_2D, last_texture); + + return true; +} + +void ImGuiRenderPass::newFrame() +{ + ImGuiIO& io = ImGui::GetIO(); + + // Setup display size (every frame to accommodate for window resizing) + int w, h; + int display_w, display_h; + glfwGetWindowSize(g_Window, &w, &h); + glfwGetFramebufferSize(g_Window, &display_w, &display_h); + io.DisplaySize = ImVec2((float)w, (float)h); + io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h); + + io.DeltaTime = g_DeltaTime; + + io.KeyCtrl = glfwGetKey(g_Window, GLFW_KEY_LEFT_CONTROL) || glfwGetKey(g_Window, GLFW_KEY_RIGHT_CONTROL); + io.KeyShift = glfwGetKey(g_Window, GLFW_KEY_LEFT_SHIFT) || glfwGetKey(g_Window, GLFW_KEY_RIGHT_SHIFT); + io.KeyAlt = glfwGetKey(g_Window, GLFW_KEY_LEFT_ALT) || glfwGetKey(g_Window, GLFW_KEY_RIGHT_ALT); + + io.MouseWheel = g_MouseWheel; + g_MouseWheel = 0; + + m_EventBroker->Process(); + + ImGui::NewFrame(); +} + diff --git a/src/Engine/Rendering/LightCullingPass.cpp b/src/Engine/Rendering/LightCullingPass.cpp new file mode 100644 index 00000000..8c11285d --- /dev/null +++ b/src/Engine/Rendering/LightCullingPass.cpp @@ -0,0 +1,126 @@ +#include "Rendering/LightCullingPass.h" + +LightCullingPass::LightCullingPass(IRenderer* renderer) +{ + m_Renderer = renderer; + InitializeSSBOs(); + InitializeShaderPrograms(); + GenerateNewFrustum(); +} + +LightCullingPass::~LightCullingPass() +{ + +} + +void LightCullingPass::GenerateNewFrustum() +{ + GLERROR("CalculateFrustum Error: Pre"); + + m_CalculateFrustumProgram->Bind(); + + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_FrustumSSBO); + glUniformMatrix4fv(glGetUniformLocation(m_CalculateFrustumProgram->GetHandle(), "P"), 1, false, glm::value_ptr(m_Renderer->Camera()->ProjectionMatrix())); + glUniform2f(glGetUniformLocation(m_CalculateFrustumProgram->GetHandle(), "ScreenDimensions"), m_Renderer->Resolution().Width, m_Renderer->Resolution().Height); + glDispatchCompute(5, 3, 1); //TODO: Renderer: This needs change so resolution will be right. + + GLERROR("CalculateFrustum Error: End"); +} + +void LightCullingPass::CullLights() +{ + GLERROR("CullLights Error: Pre"); + m_LightOffset = 0; + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_LightOffsetSSBO); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(m_LightOffset), &m_LightOffset, GL_DYNAMIC_COPY); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_LightSSBO); + if (m_PointLights.size() > 0) { + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(PointLight) * m_PointLights.size(), &(m_PointLights[0]), GL_DYNAMIC_COPY); + } else { + GLfloat zero = 0.f; + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GLfloat), &zero , GL_DYNAMIC_COPY); + + } + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + m_LightCullProgram->Bind(); + glUniformMatrix4fv(glGetUniformLocation(m_LightCullProgram->GetHandle(), "V"), 1, false, glm::value_ptr(m_Renderer->Camera()->ViewMatrix())); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_FrustumSSBO); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_LightSSBO); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, m_LightGridSSBO); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, m_LightOffsetSSBO); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, m_LightIndexSSBO); + glDispatchCompute(m_Renderer->Resolution().Width / TILE_SIZE, m_Renderer->Resolution().Height / TILE_SIZE, 1); + + GLERROR("CullLights Error: End"); +} + +void LightCullingPass::FillLightList(RenderQueueCollection& rq) +{ + m_PointLights.clear(); + for(auto &job : rq.Lights) { + auto pointLightjob = std::dynamic_pointer_cast(job); + if (pointLightjob) { + PointLight p; + p.Color = pointLightjob->Color; + p.Falloff = pointLightjob->Falloff; + p.Intensity = pointLightjob->Intensity; + p.Position = glm::vec4(glm::vec3(pointLightjob->Position), 1.f); + p.Radius = pointLightjob->Radius; + p.Padding = 123.f; + m_PointLights.push_back(p); + continue; + } + } +} + +void LightCullingPass::InitializeSSBOs() +{ + glGenBuffers(1, &m_FrustumSSBO); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_FrustumSSBO); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(m_Frustums), &m_Frustums, GL_DYNAMIC_COPY); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + GLERROR("m_FrustumSSBO"); + + glGenBuffers(1, &m_LightSSBO); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_LightSSBO); + if(m_PointLights.size() > 0) { + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(PointLight) * m_PointLights.size(), &(m_PointLights[0]), GL_DYNAMIC_COPY); + } + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + GLERROR("m_LightSSBO"); + + glGenBuffers(1, &m_LightGridSSBO); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_LightGridSSBO); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(m_LightGrid), &m_LightGrid, GL_DYNAMIC_COPY); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + GLERROR("m_LightGridSSBO"); + + + glGenBuffers(1, &m_LightOffsetSSBO); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_LightOffsetSSBO); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(m_LightOffset), &m_LightOffset, GL_DYNAMIC_COPY); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + GLERROR("m_LightOffsetSSBO"); + + glGenBuffers(1, &m_LightIndexSSBO); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_LightIndexSSBO); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(m_LightIndex), &m_LightIndex, GL_DYNAMIC_COPY); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + GLERROR("m_LightIndexSSBO"); +} + +void LightCullingPass::InitializeShaderPrograms() +{ + m_CalculateFrustumProgram = ResourceManager::Load("#CalculateFrustumProgram"); + m_CalculateFrustumProgram->AddShader(std::shared_ptr(new ComputeShader("Shaders/GridFrustum.comp.glsl"))); + m_CalculateFrustumProgram->Compile(); + m_CalculateFrustumProgram->Link(); + + m_LightCullProgram = ResourceManager::Load("#LightCullProgram"); + m_LightCullProgram->AddShader(std::shared_ptr(new ComputeShader("Shaders/CullLights.comp.glsl"))); + m_LightCullProgram->Compile(); + m_LightCullProgram->Link(); +} + diff --git a/src/Engine/Rendering/LightCullingPassState.cpp b/src/Engine/Rendering/LightCullingPassState.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/Engine/Rendering/PickingPass.cpp b/src/Engine/Rendering/PickingPass.cpp new file mode 100644 index 00000000..148b272c --- /dev/null +++ b/src/Engine/Rendering/PickingPass.cpp @@ -0,0 +1,123 @@ +#include "Rendering/PickingPass.h" + +PickingPass::PickingPass(IRenderer* renderer, EventBroker* eb) +{ + m_Renderer = renderer; + m_EventBroker = eb; + + InitializeTextures(); + InitializeFrameBuffers(); + InitializeShaderPrograms(); +} + +PickingPass::~PickingPass() +{ + +} + +void PickingPass::InitializeTextures() +{ + GenerateTexture(&m_PickingTexture, GL_CLAMP_TO_BORDER, GL_LINEAR, + glm::vec2(m_Renderer->Resolution().Width, m_Renderer->Resolution().Height), GL_RG8, GL_RG, GL_UNSIGNED_BYTE); +} + +void PickingPass::InitializeFrameBuffers() +{ + glGenRenderbuffers(1, &m_DepthBuffer); + glBindRenderbuffer(GL_RENDERBUFFER, m_DepthBuffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, m_Renderer->Resolution().Width, m_Renderer->Resolution().Height); + + m_PickingBuffer.AddResource(std::shared_ptr(new RenderBuffer(&m_DepthBuffer, GL_DEPTH_ATTACHMENT))); + m_PickingBuffer.AddResource(std::shared_ptr(new Texture2D(&m_PickingTexture, GL_COLOR_ATTACHMENT0))); + m_PickingBuffer.Generate(); +} + +void PickingPass::InitializeShaderPrograms() +{ + m_PickingProgram = ResourceManager::Load("#PickingProgram"); + + m_PickingProgram->AddShader(std::shared_ptr(new VertexShader("Shaders/Picking.vert.glsl"))); + m_PickingProgram->AddShader(std::shared_ptr(new FragmentShader("Shaders/Picking.frag.glsl"))); + m_PickingProgram->Compile(); + m_PickingProgram->BindFragDataLocation(0, "TextureFragment"); + m_PickingProgram->Link(); +} + +void PickingPass::Draw(RenderQueueCollection& rq) +{ + m_PickingColorsToEntity.clear(); + PickingPassState* state = new PickingPassState(m_PickingBuffer.GetHandle()); + + int r = 0; + int g = 0; + //TODO: Render: Add code for more jobs than modeljobs. + + GLuint ShaderHandle = m_PickingProgram->GetHandle(); + m_PickingProgram->Bind(); + + std::map entityColors; + + for (auto &job : rq.Forward) { + auto modelJob = std::dynamic_pointer_cast(job); + + if (modelJob) { + int pickColor[2] = { r, g }; + auto color = entityColors.find(modelJob->Entity); + if (color != entityColors.end()) { + pickColor[0] = color->second[0]; + pickColor[1] = color->second[1]; + } else { + entityColors[modelJob->Entity] = glm::vec2(pickColor[0], pickColor[1]); + if (r + 10 > 255) { + r = 0; + g += 1; + } else { + r += 1; + } + } + m_PickingColorsToEntity[glm::vec2(pickColor[0], pickColor[1])] = modelJob->Entity; + + //Render picking stuff + //TODO: Kolla upp "header/include/common" shader saken så man slipper skicka in asmycket uniforms + glUniformMatrix4fv(glGetUniformLocation(ShaderHandle, "M"), 1, GL_FALSE, glm::value_ptr(modelJob->ModelMatrix)); + glUniformMatrix4fv(glGetUniformLocation(ShaderHandle, "V"), 1, GL_FALSE, glm::value_ptr(m_Renderer->Camera()->ViewMatrix())); + glUniformMatrix4fv(glGetUniformLocation(ShaderHandle, "P"), 1, GL_FALSE, glm::value_ptr(m_Renderer->Camera()->ProjectionMatrix())); + glUniform2fv(glGetUniformLocation(ShaderHandle, "PickingColor"), 1, glm::value_ptr(glm::vec2(pickColor[0], pickColor[1]))); + + glBindVertexArray(modelJob->Model->VAO); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, modelJob->Model->ElementBuffer); + glDrawElementsBaseVertex(GL_TRIANGLES, modelJob->EndIndex - modelJob->StartIndex + 1, GL_UNSIGNED_INT, nullptr, modelJob->StartIndex); + } + } + m_PickingBuffer.Unbind(); + GLERROR("PickingPass Error"); + + //Publish pick event every frame with the pick data that can be picked by the event + int fbWidth; + int fbHeight; + glfwGetFramebufferSize(m_Renderer->Window(), &fbWidth, &fbHeight); + Events::Picking pickEvent = Events::Picking( + &m_PickingBuffer, + &m_DepthBuffer, + m_Renderer->Camera()->ProjectionMatrix(), + m_Renderer->Camera()->ViewMatrix(), + Rectangle(fbWidth, fbHeight), + &m_PickingColorsToEntity); + + m_EventBroker->Publish(pickEvent); + + delete state; +} + +void PickingPass::GenerateTexture(GLuint* texture, GLenum wrapping, GLenum filtering, glm::vec2 dimensions, GLint internalFormat, GLint format, GLenum type) const +{ + //TODO: Renderer: Make this in a sparate class + glGenTextures(1, texture); + glBindTexture(GL_TEXTURE_2D, *texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapping); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapping); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, dimensions.x, dimensions.y, 0, format, type, nullptr);//TODO: Renderer: Fix the precision and Resolution + GLERROR("Texture initialization failed"); +} diff --git a/src/Engine/Rendering/PickingPassState.cpp b/src/Engine/Rendering/PickingPassState.cpp new file mode 100644 index 00000000..1e28ea66 --- /dev/null +++ b/src/Engine/Rendering/PickingPassState.cpp @@ -0,0 +1,20 @@ +#include "Rendering/PickingPassState.h" + + +PickingPassState::PickingPassState(GLuint frameBuffer) +{ + GLERROR("---2"); + BindFramebuffer(frameBuffer); + GLERROR("---3"); + Enable(GL_DEPTH_TEST); + Enable(GL_CULL_FACE); + + glm::vec4 clearColor = glm::vec4(0.f); + ClearColor(clearColor); + Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +PickingPassState::~PickingPassState() +{ + +} diff --git a/src/Engine/Rendering/RawModel.cpp b/src/Engine/Rendering/RawModel.cpp index c14fec08..95a75a15 100644 --- a/src/Engine/Rendering/RawModel.cpp +++ b/src/Engine/Rendering/RawModel.cpp @@ -8,7 +8,7 @@ RawModel::RawModel(std::string fileName) if (scene == nullptr) { LOG_ERROR("Failed to load model \"%s\"", fileName.c_str()); LOG_ERROR("Assimp error: %s", importer.GetErrorString()); - return; + throw std::runtime_error("Failed to open model file."); } auto m = scene->mRootNode->mTransformation; @@ -34,10 +34,10 @@ RawModel::RawModel(std::string fileName) numIndices += face.mNumIndices; } } - LOG_DEBUG("Vertex count %i", numVertices); - LOG_DEBUG("Index count %i", numIndices); + //LOG_DEBUG("Vertex count %i", numVertices); + //LOG_DEBUG("Index count %i", numIndices); - LOG_DEBUG("Model has %i embedded textures", scene->mNumTextures); + //LOG_DEBUG("Model has %i embedded textures", scene->mNumTextures); std::vector> boneInfo; std::map boneNameMapping; @@ -76,13 +76,15 @@ RawModel::RawModel(std::string fileName) } // Material diffuse color - aiColor4D diffuse; + aiColor3D diffuse; material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse); - desc.DiffuseVertexColor = glm::vec4(diffuse.r, diffuse.g, diffuse.b, diffuse.a); + float opacity; + material->Get(AI_MATKEY_OPACITY, opacity); + desc.DiffuseVertexColor = glm::vec4(diffuse.r, diffuse.g, diffuse.b, opacity); // Material specular color - aiColor4D specular; + aiColor3D specular; material->Get(AI_MATKEY_COLOR_SPECULAR, specular); - desc.SpecularVertexColor = glm::vec4(specular.r, specular.g, specular.b, specular.a); + desc.SpecularVertexColor = glm::vec4(specular.r, specular.g, specular.b, 1.f); m_Vertices.push_back(desc); } @@ -132,35 +134,35 @@ RawModel::RawModel(std::string fileName) matGroup.EndIndex = m_Indices.size() - 1; // Material shininess material->Get(AI_MATKEY_SHININESS, matGroup.Shininess); - LOG_DEBUG("Shininess: %f", matGroup.Shininess); + //LOG_DEBUG("Shininess: %f", matGroup.Shininess); // Diffuse texture - LOG_DEBUG("%i diffuse textures found", material->GetTextureCount(aiTextureType_DIFFUSE)); + //LOG_DEBUG("%i diffuse textures found", material->GetTextureCount(aiTextureType_DIFFUSE)); if (material->GetTextureCount(aiTextureType_DIFFUSE)) { aiString path; aiTextureMapping mapping; material->GetTexture(aiTextureType_DIFFUSE, 0, &path, &mapping); std::string absolutePath = (boost::filesystem::path(fileName).branch_path() / path.C_Str()).string(); - LOG_DEBUG("Diffuse texture: %s", absolutePath.c_str()); + //LOG_DEBUG("Diffuse texture: %s", absolutePath.c_str()); matGroup.Texture = std::shared_ptr(ResourceManager::Load(absolutePath)); } // Normal map - LOG_DEBUG("%i normal maps found", material->GetTextureCount(aiTextureType_HEIGHT)); + //LOG_DEBUG("%i normal maps found", material->GetTextureCount(aiTextureType_HEIGHT)); if (material->GetTextureCount(aiTextureType_HEIGHT)) { aiString path; aiTextureMapping mapping; material->GetTexture(aiTextureType_HEIGHT, 0, &path, &mapping); std::string absolutePath = (boost::filesystem::path(fileName).branch_path() / path.C_Str()).string(); - LOG_DEBUG("Normal map: %s", absolutePath.c_str()); + //LOG_DEBUG("Normal map: %s", absolutePath.c_str()); matGroup.NormalMap = std::shared_ptr(ResourceManager::Load(absolutePath)); } // Specular map - LOG_DEBUG("%i specular maps found", material->GetTextureCount(aiTextureType_SPECULAR)); + //LOG_DEBUG("%i specular maps found", material->GetTextureCount(aiTextureType_SPECULAR)); if (material->GetTextureCount(aiTextureType_SPECULAR)) { aiString path; aiTextureMapping mapping; material->GetTexture(aiTextureType_SPECULAR, 0, &path, &mapping); std::string absolutePath = (boost::filesystem::path(fileName).branch_path() / path.C_Str()).string(); - LOG_DEBUG("Specular map: %s", absolutePath.c_str()); + //LOG_DEBUG("Specular map: %s", absolutePath.c_str()); matGroup.SpecularMap = std::shared_ptr(ResourceManager::Load(absolutePath)); } TextureGroups.push_back(matGroup); @@ -216,20 +218,20 @@ RawModel::RawModel(std::string fileName) m_Skeleton = new Skeleton(); CreateSkeleton(boneInfo, boneNameMapping, scene->mRootNode, -1); int numBones = m_Skeleton->Bones.size(); - LOG_DEBUG("Bone count: %i", numBones); + //LOG_DEBUG("Bone count: %i", numBones); if (numBones > 0) { m_Skeleton->PrintSkeleton(); } } // Animations - LOG_DEBUG("Animation count: %i", scene->mNumAnimations); + //LOG_DEBUG("Animation count: %i", scene->mNumAnimations); for (int i = 0; i < scene->mNumAnimations; ++i) { auto animation = scene->mAnimations[i]; std::string animationName = animation->mName.C_Str(); - LOG_DEBUG("Animation: %s", animationName.c_str()); - LOG_DEBUG("Duration: %f", animation->mDuration); - LOG_DEBUG("Ticks per second: %f", animation->mTicksPerSecond); + //LOG_DEBUG("Animation: %s", animationName.c_str()); + //LOG_DEBUG("Duration: %f", animation->mDuration); + //LOG_DEBUG("Ticks per second: %f", animation->mTicksPerSecond); Skeleton::Animation skelAnim; skelAnim.Name = animationName; @@ -303,7 +305,7 @@ void RawModel::CreateSkeleton(std::vector> &b // Find the bone by name in the bone info list if (boneNameMapping.find(nodeName) == boneNameMapping.end()) { - LOG_DEBUG("Node \"%s\" was not a bone", nodeName.c_str()); + //LOG_DEBUG("Node \"%s\" was not a bone", nodeName.c_str()); } else { glm::mat4 offsetMatrix; int ID = boneNameMapping[nodeName]; diff --git a/src/Engine/Rendering/RenderQueueFactory.cpp b/src/Engine/Rendering/RenderQueueFactory.cpp index 9a7d89a1..429d773c 100644 --- a/src/Engine/Rendering/RenderQueueFactory.cpp +++ b/src/Engine/Rendering/RenderQueueFactory.cpp @@ -15,53 +15,130 @@ void RenderQueueFactory::Update(World* world) glm::mat4 RenderQueueFactory::ModelMatrix(World* world, EntityID entity) { - //should really return absolute model matrix based on parents position, scale and orientation - //GetAbsolutePosition(World* world, ComponentWrapper transformComponent) + glm::vec3 position = AbsolutePosition(world, entity); + glm::quat orientation = AbsoluteOrientation(world, entity); + glm::vec3 scale = AbsoluteScale(world, entity); - ComponentWrapper transformComponent = world->GetComponent(entity, "Transform"); - glm::vec3 position = transformComponent["Position"]; - glm::vec3 scale = transformComponent["Scale"]; - glm::quat oritentation = transformComponent["Orientation"]; - - glm::mat4 modelMatrix = glm::translate(glm::mat4(), position) * glm::toMat4(oritentation) * glm::scale(scale); + glm::mat4 modelMatrix = glm::translate(glm::mat4(), position) * glm::toMat4(orientation) * glm::scale(scale); return modelMatrix; } -glm::vec3 GetAbsolutePosition(World* world, ComponentWrapper transformComponent) +glm::vec3 RenderQueueFactory::AbsolutePosition(World* world, EntityID entity) { - // positionComponent.EntityID - return glm::vec3(); + glm::vec3 position; + + do { + ComponentWrapper transform = world->GetComponent(entity, "Transform"); + EntityID parent = world->GetParent(entity); + if (parent != 0) { + position += AbsoluteOrientation(world, parent) * (glm::vec3)transform["Position"]; + } else { + position += (glm::vec3)transform["Position"]; + } + entity = parent; + } while (entity != 0); + + return position; +} + +glm::quat RenderQueueFactory::AbsoluteOrientation(World* world, EntityID entity) +{ + glm::quat orientation; + + do { + ComponentWrapper transform = world->GetComponent(entity, "Transform"); + orientation = glm::quat((glm::vec3)transform["Orientation"]) * orientation; + entity = world->GetParent(entity); + } while (entity != 0); + + return orientation; +} + +glm::vec3 RenderQueueFactory::AbsoluteScale(World* world, EntityID entity) +{ + glm::vec3 scale(1.f); + + do { + ComponentWrapper transform = world->GetComponent(entity, "Transform"); + scale *= (glm::vec3)transform["Scale"]; + entity = world->GetParent(entity); + } while (entity != 0); + + return scale; } void RenderQueueFactory::FillModels(World* world, RenderQueue* renderQueue) { - for(auto& modelC : world->GetComponents("Model")) { - ModelJob job; - std::string resource = modelC["Resource"]; - glm::vec4 color = modelC["Color"]; - Model* model = ResourceManager::Load(resource); + auto models = world->GetComponents("Model"); + if (models == nullptr) { + return; + } - for (auto texGroup : model->TextureGroups) { - job.TextureID = (texGroup.Texture) ? texGroup.Texture->ResourceID : 0; - job.DiffuseTexture = texGroup.Texture.get(); - job.NormalTexture = texGroup.NormalMap.get(); - job.SpecularTexture = texGroup.SpecularMap.get(); - job.Model = model; - job.StartIndex = texGroup.StartIndex; - job.EndIndex = texGroup.EndIndex; - job.ModelMatrix = model->m_Matrix * ModelMatrix(world, modelC.EntityID); - job.Color = color; + for (auto& modelC : *models) { + bool visible = modelC["Visible"]; + if (!visible) { + continue; + } + std::string resource = modelC["Resource"]; + if (resource.empty()) { + continue; + } + glm::vec4 color = modelC["Color"]; + Model* model = ResourceManager::Load(resource); + if (model == nullptr) { + model = ResourceManager::Load("Models/Core/Error.obj"); + } - //TODO: RENDERER: Not sure if the best solution for pickingColor to entity link is this - job.Entity = modelC.EntityID; + for (auto texGroup : model->TextureGroups) { + ModelJob job; + job.TextureID = (texGroup.Texture) ? texGroup.Texture->ResourceID : 0; + job.DiffuseTexture = texGroup.Texture.get(); + job.NormalTexture = texGroup.NormalMap.get(); + job.SpecularTexture = texGroup.SpecularMap.get(); + job.Model = model; + job.StartIndex = texGroup.StartIndex; + job.EndIndex = texGroup.EndIndex; + job.ModelMatrix = model->m_Matrix * ModelMatrix(world, modelC.EntityID); + job.Color = color; - renderQueue->Add(job); - } - } + //TODO: RENDERER: Not sure if the best solution for pickingColor to entity link is this + job.Entity = modelC.EntityID; + + renderQueue->Add(job); + } + } } void RenderQueueFactory::FillLights(World* world, RenderQueue* renderQueue) { + auto pointLights = world->GetComponents("PointLight"); + if(pointLights == nullptr) { + return; + } + for(auto& pointlightC : *pointLights) { + bool visible = pointlightC["Visible"]; + if(!visible) { + continue; + } + auto transformC = world->GetComponent(pointlightC.EntityID, "Transform"); + if(&transformC == nullptr) { + return; + } + + glm::vec4 color = pointlightC["Color"]; + float radius = (double)pointlightC["Radius"]; + float intensity = (double)pointlightC["Intensity"]; + float falloff = (double)pointlightC["Falloff"]; + + PointLightJob job; + job.Position = glm::vec4(AbsolutePosition(world, transformC.EntityID), 1.f); + job.Color = color; + job.Radius = radius; + job.Intensity = intensity; + job.Falloff = falloff; + + renderQueue->Add(job); + } } diff --git a/src/Engine/Rendering/RenderState.cpp b/src/Engine/Rendering/RenderState.cpp new file mode 100644 index 00000000..4c47a8a2 --- /dev/null +++ b/src/Engine/Rendering/RenderState.cpp @@ -0,0 +1,100 @@ +#include "Rendering/RenderState.h" + +bool RenderState::Enable(GLenum cap) +{ + if (glIsEnabled(cap)) { + //LOG_WARNING("Trying to enable somthing that is already enabled."); + return false; + } + m_ResetFunctions.push_back(std::bind(glDisable, cap)); + glEnable(cap); + return !GLERROR("RenderState::Enable"); +} + +bool RenderState::Disable(GLenum cap) +{ + if (!glIsEnabled(cap)) { + return false; + } + m_ResetFunctions.push_back(std::bind(glEnable, cap)); + glDisable(cap); + return !GLERROR("RenderState::Disable"); +} + +bool RenderState::CullFace(GLenum mode) +{ + if (!glIsEnabled(GL_CULL_FACE)) { + LOG_ERROR("Setting GL_CULL_FACE without enabling it."); + return false; + } + + GLint original; + glGetIntegerv(GL_CULL_FACE_MODE, &original); + m_ResetFunctions.push_back(std::bind(glCullFace, original)); + glCullFace(mode); + return !GLERROR("RenderState::CullFace"); +} + +bool RenderState::ClearColor(glm::vec4 color) +{ + GLfloat original[4]; + glGetFloatv(GL_COLOR_CLEAR_VALUE, &original[0]); + m_ResetFunctions.push_back(std::bind(glClearColor, original[0], original[1], original[2], original[3])); + glClearColor(color.r, color.g, color.b, color.a); + return !GLERROR("RenderState::ClearColor"); +} + +bool RenderState::Clear(GLbitfield mask) +{ + glClear(mask); + return !GLERROR("RenderState::Clear"); +} + +bool RenderState::BindFramebuffer(GLint framebuffer) +{ + GLint originalRead; + glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &originalRead); + GLint originalDraw; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &originalDraw); + m_ResetFunctions.push_back([originalRead, originalDraw]() { + glBindFramebuffer(GL_READ_FRAMEBUFFER, originalRead); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, originalDraw); + }); + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + return !GLERROR("RenderState::BindBuffer"); +} + + +bool RenderState::BlendEquation(GLenum mode) +{ + GLint originalRGB; + glGetIntegerv(GL_BLEND_EQUATION_RGB, &originalRGB); + GLint originalAlpha; + glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &originalAlpha); + m_ResetFunctions.push_back(std::bind(glBlendEquationSeparate, originalRGB, originalAlpha)); + glBlendEquation(mode); + return !GLERROR("RenderState::BlendEquation"); +} + +bool RenderState::BlendFunc(GLenum sfactor, GLenum dfactor) +{ + GLint originalSrcRGB; + glGetIntegerv(GL_BLEND_SRC_RGB, &originalSrcRGB); + GLint originalSrcAlpha; + glGetIntegerv(GL_BLEND_SRC_ALPHA, &originalSrcAlpha); + GLint originalDestRGB; + glGetIntegerv(GL_BLEND_DST_RGB, &originalDestRGB); + GLint originalDestAlpha; + glGetIntegerv(GL_BLEND_DST_ALPHA, &originalDestAlpha); + m_ResetFunctions.push_back(std::bind(glBlendFuncSeparate, originalSrcRGB, originalSrcAlpha, originalDestRGB, originalDestAlpha)); + glBlendFunc(sfactor, dfactor); + return !GLERROR("RenderState::BlendFunc"); +} + +RenderState::~RenderState() +{ + for (auto& f : m_ResetFunctions) { + f(); + } +} + diff --git a/src/Engine/Rendering/Renderer.cpp b/src/Engine/Rendering/Renderer.cpp index f39580b7..8d3863e2 100644 --- a/src/Engine/Rendering/Renderer.cpp +++ b/src/Engine/Rendering/Renderer.cpp @@ -3,23 +3,24 @@ void Renderer::Initialize() { InitializeWindow(); - // Create default camera - m_DefaultCamera = new ::Camera((float)m_Resolution.Width / m_Resolution.Height, glm::radians(45.f), 0.01f, 5000.f); - m_DefaultCamera->SetPosition(glm::vec3(0, 0, 10)); + m_DefaultCamera = new ::Camera((float)m_Resolution.Width / m_Resolution.Height, glm::radians(90.0f), 0.01f, 5000.f); + m_DefaultCamera->SetPosition(glm::vec3(0, 1, 10)); if (m_Camera == nullptr) { m_Camera = m_DefaultCamera; } + m_DebugCameraInputController = std::make_shared>(m_EventBroker, -1); + InitializeRenderPasses(); glfwSwapInterval(m_VSYNC); InitializeShaders(); InitializeTextures(); - InitializeFrameBuffers(); - m_ScreenQuad = ResourceManager::Load("Models/Core/ScreenQuad.obj"); m_UnitQuad = ResourceManager::Load("Models/Core/UnitQuad.obj"); m_UnitSphere = ResourceManager::Load("Models/Core/UnitSphere.obj"); + + m_ImGuiRenderPass = new ImGuiRenderPass(this, m_EventBroker); } void Renderer::InitializeWindow() @@ -64,204 +65,44 @@ void Renderer::InitializeWindow() void Renderer::InitializeShaders() { - m_BasicForwardProgram.AddShader(std::shared_ptr(new VertexShader("Shaders/BasicForward.vert.glsl"))); - m_BasicForwardProgram.AddShader(std::shared_ptr(new FragmentShader("Shaders/BasicForward.frag.glsl"))); - m_BasicForwardProgram.Compile(); - m_BasicForwardProgram.Link(); - - m_PickingProgram.AddShader(std::shared_ptr(new VertexShader("Shaders/Picking.vert.glsl"))); - m_PickingProgram.AddShader(std::shared_ptr(new FragmentShader("Shaders/Picking.frag.glsl"))); - m_PickingProgram.Compile(); - m_PickingProgram.BindFragDataLocation(0, "TextureFragment"); - m_PickingProgram.Link(); - - m_DrawScreenQuadProgram.AddShader(std::shared_ptr(new VertexShader("Shaders/DrawScreenQuad.vert.glsl"))); - m_DrawScreenQuadProgram.AddShader(std::shared_ptr(new FragmentShader("Shaders/DrawScreenQuad.frag.glsl"))); - m_DrawScreenQuadProgram.Compile(); - m_DrawScreenQuadProgram.Link(); + m_BasicForwardProgram = ResourceManager::Load("#m_BasicForwardProgram"); + m_DrawScreenQuadProgram = ResourceManager::Load("#DrawScreenQuadProgram"); + m_DrawScreenQuadProgram->AddShader(std::shared_ptr(new VertexShader("Shaders/DrawScreenQuad.vert.glsl"))); + m_DrawScreenQuadProgram->AddShader(std::shared_ptr(new FragmentShader("Shaders/DrawScreenQuad.frag.glsl"))); + m_DrawScreenQuadProgram->Compile(); + m_DrawScreenQuadProgram->Link(); } void Renderer::InputUpdate(double dt) { - glm::vec3 m_Position = m_Camera->Position(); - if (glfwGetKey(m_Window, GLFW_KEY_O) == GLFW_PRESS) - { - m_Position = glm::vec3(0.f, 0.f, 5.f); - } - if (glfwGetKey(m_Window, GLFW_KEY_W) == GLFW_PRESS) - { - m_Position += m_Camera->Forward() * m_CameraMoveSpeed * (float)dt; - } - if (glfwGetKey(m_Window, GLFW_KEY_S) == GLFW_PRESS) - { - m_Position -= m_Camera->Forward() * m_CameraMoveSpeed * (float)dt; - } - if (glfwGetKey(m_Window, GLFW_KEY_D) == GLFW_PRESS) - { - m_Position += m_Camera->Right() * m_CameraMoveSpeed * (float)dt; - } - if (glfwGetKey(m_Window, GLFW_KEY_A) == GLFW_PRESS) - { - m_Position -= m_Camera->Right() * m_CameraMoveSpeed * (float)dt; - } - if (glfwGetKey(m_Window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) - { - m_CameraMoveSpeed = 5.f; - } - else { - m_CameraMoveSpeed = 0.5f; - } - - - static double mousePosX, mousePosY; - glfwGetCursorPos(m_Window, &mousePosX, &mousePosY); - if (glfwGetMouseButton(m_Window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS) { - glm::vec3 data = ScreenCoords::ToPixelData(mousePosX, m_Resolution.Height - mousePosY, &m_PickingBuffer, m_DepthBuffer); - glm::vec2 color = glm::vec2(data); - float depth = data.z; - - glm::vec3 viewPos = ScreenCoords::ToWorldPos(mousePosX, m_Resolution.Height - mousePosY, depth, m_Resolution, m_Camera->ProjectionMatrix(), m_Camera->ViewMatrix()); - // glm::vec3 worldPos = glm::vec3(glm::inverse(m_Camera->ViewMatrix()) * glm::vec4(viewPos, 1.f)); - - //printf("R: %f, G: %f, Depth: %f\n", color.r, color.g, depth); - //printf("view: x: %f, y: %f z: %f, Length: %f\n\n", viewPos.x, viewPos.y, viewPos.z, glm::length(viewPos)); - - if (color != glm::vec2(0, 0)) { - EntityID pickedEntity = m_PickingColorsToEntity[color]; - printf("Picked Entity: %i", pickedEntity); - - } - } - - if (glfwGetKey(m_Window, GLFW_KEY_SPACE) == GLFW_PRESS) { - - - double deltaX, deltaY; - deltaX = mousePosX - (float)Resolution().Width / 2; - deltaY = mousePosY - (float)Resolution().Height / 2; - - float rotationY = -deltaY / 300.f; - float rotationX = -deltaX / 300.f; - glm::quat orientation = m_Camera->Orientation(); - - - orientation = orientation * glm::angleAxis(rotationY, glm::vec3(1, 0, 0)); - orientation = glm::angleAxis(rotationX, glm::vec3(0, 1, 0)) * orientation; - - m_Camera->SetOrientation(orientation); - - glfwSetCursorPos(m_Window, Resolution().Width / 2, Resolution().Height / 2); - } - m_Camera->SetPosition(m_Position); + m_DebugCameraInputController->Update(dt); + m_Camera->SetOrientation(m_DebugCameraInputController->Orientation()); + m_Camera->SetPosition(m_DebugCameraInputController->Position()); } void Renderer::Update(double dt) { - InputUpdate(dt); - + m_EventBroker->Process(); + InputUpdate(dt); + m_ImGuiRenderPass->Update(dt); } void Renderer::Draw(RenderQueueCollection& rq) { - //TODO: Renderer: Kanske borde vara längst upp i update. - PickingPass(rq); - DrawScreenQuad(m_PickingTexture); + m_PickingPass->Draw(rq); + //DrawScreenQuad(m_PickingPass->PickingTexture()); + m_LightCullingPass->FillLightList(rq); + m_LightCullingPass->CullLights(); - DrawScene(rq); + //m_DrawScenePass->Draw(rq); + m_DrawFinalPass->Draw(rq); + glClearColor(255.f / 255, 163.f / 255, 176.f / 255, 1.f); + GLERROR("Renderer::Draw m_DrawScenePass->Draw"); + m_ImGuiRenderPass->Draw(); glfwSwapBuffers(m_Window); } -void Renderer::DrawScene(RenderQueueCollection& rq) -{ - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - //TODO: Render: Clean up draw code - glEnable(GL_DEPTH_TEST); - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - - glClearColor(255.f / 255, 163.f / 255, 176.f / 255, 0.f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - //TODO: Render: Add code for more jobs than modeljobs. - for (auto &job : rq.Forward) { - auto modelJob = std::dynamic_pointer_cast(job); - if (modelJob) { - GLuint ShaderHandle = m_BasicForwardProgram.GetHandle(); - - m_BasicForwardProgram.Bind(); - //TODO: Kolla upp "header/include/common" shader saken så man slipper skicka in asmycket uniforms - glUniformMatrix4fv(glGetUniformLocation(ShaderHandle, "M"), 1, GL_FALSE, glm::value_ptr(modelJob->ModelMatrix)); - glUniformMatrix4fv(glGetUniformLocation(ShaderHandle, "V"), 1, GL_FALSE, glm::value_ptr(m_Camera->ViewMatrix())); - glUniformMatrix4fv(glGetUniformLocation(ShaderHandle, "P"), 1, GL_FALSE, glm::value_ptr(m_Camera->ProjectionMatrix())); - glUniform4fv(glGetUniformLocation(ShaderHandle, "Color"), 1, glm::value_ptr(modelJob->Color)); - - //TODO: Renderer: bättre textur felhantering samt fler texturer stöd - if (modelJob->DiffuseTexture != nullptr) { - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, modelJob->DiffuseTexture->m_Texture); - } else { - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, m_WhiteTexture->m_Texture); - } - - glBindVertexArray(modelJob->Model->VAO); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, modelJob->Model->ElementBuffer); - glDrawElementsBaseVertex(GL_TRIANGLES, modelJob->EndIndex - modelJob->StartIndex + 1, GL_UNSIGNED_INT, 0, modelJob->StartIndex); - - continue; - } - } -} - -void Renderer::PickingPass(RenderQueueCollection& rq) -{ - m_PickingBuffer.Bind(); - - glEnable(GL_DEPTH_TEST); - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - - glClearColor(0.f, 0.f, 0.f, 1.f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - int r = 30; - int g = 0; - //TODO: Render: Add code for more jobs than modeljobs. - - - GLuint ShaderHandle = m_PickingProgram.GetHandle(); - m_PickingProgram.Bind(); - - for (auto &job : rq.Forward) { - auto modelJob = std::dynamic_pointer_cast(job); - - if (modelJob) { - glm::vec2 pickColor = glm::vec2(r/255.f, g/255.f); - m_PickingColorsToEntity[pickColor] = modelJob->Entity; - - - //Render picking stuff - //TODO: Kolla upp "header/include/common" shader saken så man slipper skicka in asmycket uniforms - glUniformMatrix4fv(glGetUniformLocation(ShaderHandle, "M"), 1, GL_FALSE, glm::value_ptr(modelJob->ModelMatrix)); - glUniformMatrix4fv(glGetUniformLocation(ShaderHandle, "V"), 1, GL_FALSE, glm::value_ptr(m_Camera->ViewMatrix())); - glUniformMatrix4fv(glGetUniformLocation(ShaderHandle, "P"), 1, GL_FALSE, glm::value_ptr(m_Camera->ProjectionMatrix())); - glUniform2fv(glGetUniformLocation(ShaderHandle, "PickingColor"), 1, glm::value_ptr(pickColor)); - - glBindVertexArray(modelJob->Model->VAO); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, modelJob->Model->ElementBuffer); - glDrawElementsBaseVertex(GL_TRIANGLES, modelJob->EndIndex - modelJob->StartIndex + 1, GL_UNSIGNED_INT, 0, modelJob->StartIndex); - r+=50; - if(r > 255) { - r = 0; - g+=50; - } - } - } - m_PickingBuffer.Unbind(); -} - - void Renderer::DrawScreenQuad(GLuint textureToDraw) { glBindFramebuffer(GL_FRAMEBUFFER, 0); @@ -273,7 +114,7 @@ void Renderer::DrawScreenQuad(GLuint textureToDraw) glClear(GL_COLOR_BUFFER_BIT); - m_DrawScreenQuadProgram.Bind(); + m_DrawScreenQuadProgram->Bind(); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, textureToDraw); @@ -283,24 +124,10 @@ void Renderer::DrawScreenQuad(GLuint textureToDraw) , GL_UNSIGNED_INT, 0, m_ScreenQuad->TextureGroups[0].StartIndex); } - void Renderer::InitializeTextures() { m_ErrorTexture=ResourceManager::Load("Textures/Core/ErrorTexture.png"); m_WhiteTexture=ResourceManager::Load("Textures/Core/Blank.png"); - /* - glGenTextures(1, &m_PickingTexture); - glBindTexture(GL_TEXTURE_2D, m_PickingTexture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, m_Resolution.Width, m_Resolution.Height, 0, GL_RG, GL_FLOAT, NULL);//TODO: Renderer: Fix the precision and Resolution - GLERROR("m_PickingTexture initialization failed"); - */ - - GenerateTexture(&m_PickingTexture, GL_CLAMP_TO_BORDER, GL_LINEAR, - glm::vec2(m_Resolution.Width, m_Resolution.Height), GL_RG8, GL_RG, GL_FLOAT); } void Renderer::GenerateTexture(GLuint* texture, GLenum wrapping, GLenum filtering, glm::vec2 dimensions, GLint internalFormat, GLint format, GLenum type) @@ -315,15 +142,10 @@ void Renderer::GenerateTexture(GLuint* texture, GLenum wrapping, GLenum filterin GLERROR("Texture initialization failed"); } - - -void Renderer::InitializeFrameBuffers()//TODO: Renderer: Get this to a better location, as its really big +void Renderer::InitializeRenderPasses() { - glGenRenderbuffers(1, &m_DepthBuffer); - glBindRenderbuffer(GL_RENDERBUFFER, m_DepthBuffer); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, m_Resolution.Width, m_Resolution.Height); - - m_PickingBuffer.AddResource(std::shared_ptr(new RenderBuffer(&m_DepthBuffer, GL_DEPTH_ATTACHMENT))); - m_PickingBuffer.AddResource(std::shared_ptr(new Texture2D(&m_PickingTexture, GL_COLOR_ATTACHMENT0))); - m_PickingBuffer.Generate(); + m_DrawScenePass = new DrawScenePass(this); + m_PickingPass = new PickingPass(this, m_EventBroker); + m_LightCullingPass = new LightCullingPass(this); + m_DrawFinalPass = new DrawFinalPass(this, m_LightCullingPass); } \ No newline at end of file diff --git a/src/Engine/Rendering/Util/ScreenCoords.cpp b/src/Engine/Rendering/Util/ScreenCoords.cpp index aad94d67..36f1295e 100644 --- a/src/Engine/Rendering/Util/ScreenCoords.cpp +++ b/src/Engine/Rendering/Util/ScreenCoords.cpp @@ -29,11 +29,11 @@ glm::vec3 ScreenCoords::ToWorldPos(glm::vec2 screenCoord, float depth, float scr return ToWorldPos(screenCoord.x, screenCoord.y, depth, screenWidth, screenHeight, cameraProjectionMat, cameraViewMat); } -glm::vec3 ScreenCoords::ToPixelData(float x, float y, FrameBuffer* PickDataBuffer, GLuint DepthBuffer) +ScreenCoords::PixelData ScreenCoords::ToPixelData(float x, float y, FrameBuffer* PickDataBuffer, GLuint DepthBuffer) { PickDataBuffer->Bind(); - glm::vec2 pixelData; - glReadPixels(x, y, 1, 1, GL_RG, GL_FLOAT, &pixelData); + unsigned char pdata[3]; + glReadPixels(x, y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &pdata); PickDataBuffer->Unbind(); glBindFramebuffer(GL_FRAMEBUFFER, DepthBuffer); @@ -41,10 +41,18 @@ glm::vec3 ScreenCoords::ToPixelData(float x, float y, FrameBuffer* PickDataBuffe glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depthData); glBindFramebuffer(GL_FRAMEBUFFER, 0); - return glm::vec3(pixelData, depthData); + PixelData p; + p.Color[0] = (int)pdata[0]; + p.Color[1] = (int)pdata[1]; + p.Depth = depthData; + + GLERROR("ScreenCoords::ToPixelData Error"); + + return p; } -glm::vec3 ScreenCoords::ToPixelData(glm::vec2 screenCoord, FrameBuffer* PickDataBuffer, GLuint DepthBuffer) +ScreenCoords::PixelData ScreenCoords::ToPixelData(glm::vec2 screenCoord, FrameBuffer* PickDataBuffer, GLuint DepthBuffer) { return ToPixelData(screenCoord.x, screenCoord.y, PickDataBuffer, DepthBuffer); } + diff --git a/src/Game/CMakeLists.txt b/src/Game/CMakeLists.txt index c21aaa8a..04146670 100644 --- a/src/Game/CMakeLists.txt +++ b/src/Game/CMakeLists.txt @@ -19,6 +19,8 @@ file(GLOB SOURCE_FILES set(SOURCE_FILES ${SOURCE_FILES} "Game.cpp" + "HealthSystem.cpp" + "PlayerSystem.cpp" ) set(LIBRARIES diff --git a/src/Game/Game.cpp b/src/Game/Game.cpp index 416b054e..7d294355 100644 --- a/src/Game/Game.cpp +++ b/src/Game/Game.cpp @@ -1,69 +1,148 @@ #include "Game.h" -#include "HardcodedTestWorld.h" +#include "Collision/TriggerSystem.h" +#include "Collision/CollisionSystem.h" +#include "Game/HealthSystem.h" Game::Game(int argc, char* argv[]) { - ResourceManager::RegisterType("ConfigFile"); - ResourceManager::RegisterType("Model"); - ResourceManager::RegisterType("Texture"); + ResourceManager::RegisterType("ConfigFile"); + ResourceManager::RegisterType("Model"); + ResourceManager::RegisterType("Texture"); + ResourceManager::RegisterType("EntityXMLFile"); + ResourceManager::RegisterType("ShaderProgram"); - m_Config = ResourceManager::Load("Config.ini"); - LOG_LEVEL = static_cast<_LOG_LEVEL>(m_Config->Get("Debug.LogLevel", 1)); + m_Config = ResourceManager::Load("Config.ini"); + LOG_LEVEL = static_cast<_LOG_LEVEL>(m_Config->Get("Debug.LogLevel", 1)); - // Create the core event broker - m_EventBroker = new EventBroker(); + // Create the core event broker + m_EventBroker = new EventBroker(); m_RenderQueueFactory = new RenderQueueFactory(); - // Create the renderer - m_Renderer = new Renderer(); - m_Renderer->SetFullscreen(m_Config->Get("Video.Fullscreen", false)); - m_Renderer->SetVSYNC(m_Config->Get("Video.VSYNC", false)); - m_Renderer->SetResolution(Rectangle( - 0, - 0, - m_Config->Get("Video.Width", 1280), - m_Config->Get("Video.Height", 720) - )); - m_Renderer->Initialize(); + // Create the renderer + m_Renderer = new Renderer(m_EventBroker); + m_Renderer->SetFullscreen(m_Config->Get("Video.Fullscreen", false)); + m_Renderer->SetVSYNC(m_Config->Get("Video.VSYNC", false)); + m_Renderer->SetResolution(Rectangle::Rectangle( + 0, + 0, + m_Config->Get("Video.Width", 1280), + m_Config->Get("Video.Height", 720) + )); + m_Renderer->Initialize(); + m_Renderer->Camera()->SetFOV(glm::radians(m_Config->Get("Video.FOV", 90.f))); - // Create input manager - m_InputManager = new InputManager(m_Renderer->Window(), m_EventBroker); + // Create input manager + m_InputManager = new InputManager(m_Renderer->Window(), m_EventBroker); + m_InputProxy = new InputProxy(m_EventBroker); + m_InputProxy->AddHandler(); + m_InputProxy->AddHandler(); + m_InputProxy->LoadBindings("Input.ini"); - // Create the root level GUI frame - m_FrameStack = new GUI::Frame(m_EventBroker); - m_FrameStack->Width = m_Renderer->Resolution().Width; - m_FrameStack->Height = m_Renderer->Resolution().Height; + // Create the root level GUI frame + m_FrameStack = new GUI::Frame(m_EventBroker); + m_FrameStack->Width = m_Renderer->Resolution().Width; + m_FrameStack->Height = m_Renderer->Resolution().Height; - // Create a TEST WORLD - m_World = new HardcodedTestWorld(); + // Create a world + m_World = new World(); + std::string mapToLoad = m_Config->Get("Debug.LoadMap", ""); + if (!mapToLoad.empty()) { + ResourceManager::Load(mapToLoad)->PopulateWorld(m_World); + } - m_LastTime = glfwGetTime(); + // Create system pipeline + m_SystemPipeline = new SystemPipeline(m_EventBroker); + + //All systems with orderlevel 0 will be updated first. + unsigned int updateOrderLevel = 0; + m_SystemPipeline->AddSystem(updateOrderLevel); + m_SystemPipeline->AddSystem(updateOrderLevel); + m_SystemPipeline->AddSystem(updateOrderLevel, m_Renderer); + m_SystemPipeline->AddSystem(updateOrderLevel); + + //Collision and TriggerSystem should update after player. + ++updateOrderLevel; + m_SystemPipeline->AddSystem(updateOrderLevel); + m_SystemPipeline->AddSystem(updateOrderLevel); + + // Invoke network + if (m_Config->Get("Networking.StartNetwork", false)) { + //boost::thread workerThread(&Game::networkFunction, this); + networkFunction(); + } + m_LastTime = glfwGetTime(); } Game::~Game() { - delete m_FrameStack; - delete m_EventBroker; + delete m_SystemPipeline; + delete m_World; + delete m_FrameStack; + delete m_InputProxy; + delete m_InputManager; + delete m_Renderer; + delete m_RenderQueueFactory; + delete m_EventBroker; } void Game::Tick() { - double currentTime = glfwGetTime(); - double dt = currentTime - m_LastTime; - m_LastTime = currentTime; + glfwPollEvents(); - m_EventBroker->Swap(); - m_InputManager->Update(dt); - m_Renderer->Update(dt); - m_EventBroker->Swap(); + double currentTime = glfwGetTime(); + double dt = currentTime - m_LastTime; + m_LastTime = currentTime; + + // Handle input in a weird looking but responsive way + m_EventBroker->Process(); + m_EventBroker->Swap(); + m_InputManager->Update(dt); + m_EventBroker->Swap(); + m_InputProxy->Update(dt); + m_EventBroker->Swap(); + m_InputProxy->Process(); + m_EventBroker->Swap(); + + // Update network + if (m_IsClientOrServer) { + m_ClientOrServer->Update(); + } + + // Iterate through systems and update world! + m_SystemPipeline->Update(m_World, dt); + m_Renderer->Update(dt); + m_EventBroker->Process(); m_RenderQueueFactory->Update(m_World); - - m_Renderer->Draw(m_RenderQueueFactory->RenderQueues()); - - m_EventBroker->Swap(); - m_EventBroker->Clear(); - - glfwPollEvents(); + GLERROR("Game::Tick m_RenderQueueFactory->Update"); + m_Renderer->Draw(m_RenderQueueFactory->RenderQueues()); + GLERROR("Game::Tick m_Renderer->Draw"); + m_EventBroker->Swap(); + m_EventBroker->Clear(); } + +void Game::debugTick(double dt) +{ + m_EventBroker->Process(); +} + +void Game::networkFunction() +{ + bool isServer = m_Config->Get("Networking.IsServer", false); + if (!isServer) { + m_IsClientOrServer = true; + m_ClientOrServer = new Client(m_Config); + } + if (isServer) { + m_IsClientOrServer = true; + m_ClientOrServer = new Server(); + } + m_ClientOrServer->Start(m_World, m_EventBroker); + // I don't think we are reaching this part of the code right now. + // ~Game() is not called if the game is exited by closing console windows + // When server or client is done set it to false. + //m_IsClientOrServer = false; + // Destroy it + //delete m_ClientOrServer; +} \ No newline at end of file diff --git a/src/Game/HealthSystem.cpp b/src/Game/HealthSystem.cpp new file mode 100644 index 00000000..7a1d5005 --- /dev/null +++ b/src/Game/HealthSystem.cpp @@ -0,0 +1,59 @@ +#include "HealthSystem.h" +#include + +HealthSystem::HealthSystem(EventBroker* eventBroker) + : PureSystem(eventBroker, "Health") +{ + //subscribe/listenTo playerdamage,healthpickup events (using the eventBroker) + EVENT_SUBSCRIBE_MEMBER(m_EPlayerDamage, &HealthSystem::OnPlayerDamaged); + EVENT_SUBSCRIBE_MEMBER(m_EPlayerHealthPickup, &HealthSystem::OnPlayerHealthPickup); +} + +void HealthSystem::UpdateComponent(World *world, ComponentWrapper &health, double dt) +{ + //if entityID of health is 9 then the players ID is also 9 (player,health are connected to the same entity) + ComponentWrapper player = world->GetComponent(health.EntityID, "Player"); + double maxHealth = (double)health["MaxHealth"]; + + //process the DeltaHealthVector and change the entitys health accordingly + for (size_t i = m_DeltaHealthVector.size(); i > 0; i--) + { + auto deltaHP = m_DeltaHealthVector[i - 1]; + //if we have a healthchange for the current player and health is greater than 0, then apply it + if (std::get<0>(deltaHP) == player.EntityID && (double)health["Health"] > 0.0f) { + //get the deltaHP value from the tuple and make sure you dont get more than maxHealth + double newHealth = std::min((double)health["Health"] + (double)std::get<1>(deltaHP), maxHealth); + health["Health"] = newHealth; + m_DeltaHealthVector.erase(m_DeltaHealthVector.begin() + i - 1); + //check if health is <= 0 + if ((double)health["Health"] <= 0.0f) { + //publish death event + Events::PlayerDeath e; + e.PlayerID = player.EntityID; + m_EventBroker->Publish(e); + //clear the remaining hpDeltas for the dead player + for (size_t j = m_DeltaHealthVector.size(); j > 0; j--) + { + if (std::get<0>(m_DeltaHealthVector[j - 1]) == player.EntityID) + m_DeltaHealthVector.erase(m_DeltaHealthVector.begin() + j - 1); + } + //break the loop if the player is dead + break; + } + } + } +} + +bool HealthSystem::OnPlayerDamaged(const Events::PlayerDamage& e) +{ + //save the changed HP to a vector. it will be taken care of in UpdateComponent + m_DeltaHealthVector.push_back(std::make_tuple(e.PlayerDamagedID, -e.DamageAmount)); + return true; +} + +bool HealthSystem::OnPlayerHealthPickup(const Events::PlayerHealthPickup& e) +{ + //save the changed HP to a vector. it will be taken care of in UpdateComponent + m_DeltaHealthVector.push_back(std::make_tuple(e.PlayerHealedID, e.HealthAmount)); + return true; +} diff --git a/src/Game/PlayerSystem.cpp b/src/Game/PlayerSystem.cpp new file mode 100644 index 00000000..17b9a7f5 --- /dev/null +++ b/src/Game/PlayerSystem.cpp @@ -0,0 +1,42 @@ +#include "PlayerSystem.h" + +void PlayerSystem::UpdateComponent(World * world, ComponentWrapper & player, double dt) +{ + player["Velocity"] = glm::vec3(0.f, 0.f, 0.f); + if ((bool&)player["Forward"] == true) { + ((glm::vec3&)player["Velocity"]).z = m_Speed * float(dt) * -1; + + } + if ((bool&)player["Left"] == true) { + ((glm::vec3&)player["Velocity"]).x = m_Speed * float(dt) * -1; + } + if ((bool&)player["Back"] == true) { + ((glm::vec3&)player["Velocity"]).z = m_Speed * float(dt); + } + if ((bool&)player["Right"] == true) { + ((glm::vec3&)player["Velocity"]).x = m_Speed * float(dt); + } + + if ((glm::vec3)player["Velocity"] != glm::vec3(0.f)) { + ComponentWrapper& transform = world->GetComponent(player.EntityID, "Transform"); + (glm::vec3&)transform["Position"] += (glm::vec3)player["Velocity"]; + } +} + +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; +} \ No newline at end of file diff --git a/src/Tests/CMakeLists.txt b/src/Tests/CMakeLists.txt index 697dea29..a3f95a37 100644 --- a/src/Tests/CMakeLists.txt +++ b/src/Tests/CMakeLists.txt @@ -12,6 +12,7 @@ include_directories( ) file(GLOB SOURCE_FILES + "*.h" "*.cpp" ) diff --git a/src/Tests/CollisionTest.cpp b/src/Tests/CollisionTest.cpp new file mode 100644 index 00000000..e6a29298 --- /dev/null +++ b/src/Tests/CollisionTest.cpp @@ -0,0 +1,220 @@ +//#define BOOST_TEST_MODULE collTest +#include +#include +using boost::unit_test_framework::test_suite; +using boost::unit_test_framework::test_case; +#include "Engine/Collision/Collision.h" +#include "Engine/Core/AABB.h" +#include "Engine/Core/Ray.h" +#include //srand +#include "Engine/Core/OctTree.h" +//vs model +#include +#include + +//ray vs model +#include "Engine\Core\ResourceManager.h" +#include "Engine\Rendering\Model.h" +#include "Engine\Core\Ray.h" + +//vs memleaks +//#define _CRTDBG_MAP_ALLOC +//#include +//#include +//#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"); + auto unitBox = ResourceManager::Load(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) +{ + //memleak + int* globalLeak = new int[5]; + + //fixed seed + srand(2); + AABB someAABB; + glm::vec3 minPos; + glm::vec3 maxPos; + bool z; + int test = 0; + for (size_t i = 0; i < 10; i++) + { + 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; + maxPos.x = rand() % 100; + maxPos.y = rand() % 100; + maxPos.z = rand() % 100; + + someAABB = AABB(minPos, maxPos); + z = Collision::RayVsAABB(ray, someAABB); + if (z) ++test; + } + BOOST_CHECK(test >= 0); + + //_CrtDumpMemoryLeaks(); +} + +BOOST_AUTO_TEST_CASE(collisionTest2) +{ + //fixed seed + srand(2); + AABB someAABB; + glm::vec3 minPos; + glm::vec3 maxPos; + bool z; + int test = 0; + for (size_t i = 0; i < 1000000; i++) + { + 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; + maxPos.x = rand() % 100; + maxPos.y = rand() % 100; + maxPos.z = rand() % 100; + + someAABB = AABB(minPos, maxPos); + z = Collision::RayAABBIntr(ray, someAABB); + if (z) ++test; + } + 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"); + auto unitBox = ResourceManager::Load("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); + glm::vec3 maxi = glm::vec3(1, 1, 1); + OctTree tree(AABB(mini, maxi), 2); + tree.AddDynamicObject(AABB(mini, -0.9f*maxi)); + OctTree::Output data; + glm::vec3 origin = 3.0f * mini; + bool rayIntersected = tree.RayCollides(Ray(origin , mini - origin), data); + BOOST_CHECK(rayIntersected); + tree.ClearDynamicObjects(); + rayIntersected = tree.RayCollides(Ray(origin, mini - origin), data); + BOOST_CHECK(!rayIntersected); +} + +BOOST_AUTO_TEST_SUITE_END() + diff --git a/src/Tests/ConfigFileTest.cpp b/src/Tests/ConfigFileTest.cpp new file mode 100644 index 00000000..28be5589 --- /dev/null +++ b/src/Tests/ConfigFileTest.cpp @@ -0,0 +1,66 @@ +#include +#include +using boost::unit_test_framework::test_suite; +using boost::unit_test_framework::test_case; +#include //srand + +//#define private public +#include "Engine\Core\ConfigFile.h" + +#define _CRTDBG_MAP_ALLOC +#include +#define DEBUG_CLIENTBLOCK new( _CLIENT_BLOCK, __FILE__, __LINE__) +#define new DEBUG_CLIENTBLOCK + +BOOST_AUTO_TEST_SUITE(confTest) + +BOOST_AUTO_TEST_CASE(configFileTest) +{ + //note: this ConfigFileclass currently has memleaks! + + ResourceManager::RegisterType("ConfigFile"); + auto m_Config = ResourceManager::Load("ConfigTest.ini"); + + //bägge måste vara av samma typ, T typen är string + //http://www.boost.org/doc/libs/1_42_0/doc/html/boost_propertytree/tutorial.html + //"Note that we construct the path to the value by separating the individual keys with dots" + + //get from tree tests + auto getSomething = m_Config->Get("Test.Test1", 0); + BOOST_CHECK(getSomething == 423); + + auto getSomething2 = m_Config->Get("fsdfdsfd.T", std::string("")); + BOOST_CHECK(getSomething2 == "\"gfdjakflsdl!\""); + + //set/get tests + m_Config->Set("Test.4321", 123); + auto getSomething3 = m_Config->Get("Test.4321", 0); + BOOST_CHECK(getSomething3 == 123); + + m_Config->Set("3_2_1_0_5", "t454j54hj5k32"); + auto getSomething4 = m_Config->Get("3_2_1_0_5", std::string("")); + BOOST_CHECK(getSomething4 == "t454j54hj5k32"); + + //***check so outputwindow says: EE: Failed to find "DefaultConfigTestNotExists.ini"! Relying on hardcoded default values! + auto m_Config2 = ResourceManager::Load("ConfigTestNotExists.ini"); + + //set value/savetodisk/load/checkvalue... + m_Config->SaveToDisk(); + m_Config->Set("Test.4321", 145); + m_Config->SaveToDisk(); + auto m_Config3 = ResourceManager::Load("ConfigTest.ini"); + auto getSomething5 = m_Config->Get("Test.4321", 0); + BOOST_CHECK(getSomething5 == 145); + + //***check so outputwindow says: EE: Failed to parse "DefaultConfigTestFailed.ini" + //***check so outputwindow says: EE: Failed to parse "ConfigTestFailed.ini": + auto m_Config4 = ResourceManager::Load("ConfigTestFailed.ini"); + + //reload,onchildreload unimplemented + + //NOTE:still massive amount of memoryleaks from this method + _CrtDumpMemoryLeaks(); +} + +BOOST_AUTO_TEST_SUITE_END() + diff --git a/src/Tests/EventFixture.h b/src/Tests/EventFixture.h new file mode 100644 index 00000000..42258932 --- /dev/null +++ b/src/Tests/EventFixture.h @@ -0,0 +1,54 @@ +#ifndef EVENTFIXTURE_H +#define EVENTFIXTURE_H + +#include +#include "Core\EventBroker.h" + +template +struct EventFixture +{ + EventFixture() + { + this->ventBroker = new EventBroker(); + m_EEventType = decltype(m_EEventType)(std::bind(&EventFixture::OnEvent, this, std::placeholders::_1)); + this->ventBroker->Subscribe(m_EEventType); + Run(); + Check(); + } + ~EventFixture() + { + this->ventBroker->Unsubscribe(m_EEventType); + delete this->ventBroker; + } + + EventBroker* ventBroker = nullptr; + EventRelay m_EEventType; + bool m_EventRecieved = false; + EventType Before; + EventType After; + + bool OnEvent(const EventType& event) + { + m_EventRecieved = true; + After = event; + + return true; + } + + void Run() + { + // Publish the event + this->ventBroker->Publish(Before); + // Clear to swap buffers + this->ventBroker->Swap(); + // Process the event + this->ventBroker->template Process(); + } + + void Check() + { + BOOST_CHECK(m_EventRecieved); + } +}; + +#endif \ No newline at end of file diff --git a/src/Tests/EventTest.cpp b/src/Tests/EventTest.cpp new file mode 100644 index 00000000..4a030055 --- /dev/null +++ b/src/Tests/EventTest.cpp @@ -0,0 +1,19 @@ +#include +#include "EventFixture.h" + +struct ETestEvent : public Event +{ + int Int = 5; + float Float = 1.33333f; + double Double = 1.33333; + std::string String = "Hello World"; +}; + +BOOST_AUTO_TEST_CASE(EventBrokerTest) +{ + EventFixture f; + BOOST_CHECK(f.Before.Int == f.After.Int); + BOOST_CHECK_CLOSE(f.Before.Float, f.After.Float, 0.00001f); + BOOST_CHECK_CLOSE(f.Before.Double, f.After.Double, 0.00001f); + BOOST_CHECK(f.Before.String == f.After.String); +} \ No newline at end of file diff --git a/src/Tests/HealthSystemTest.cpp b/src/Tests/HealthSystemTest.cpp new file mode 100644 index 00000000..84d6199d --- /dev/null +++ b/src/Tests/HealthSystemTest.cpp @@ -0,0 +1,114 @@ +#include +using boost::unit_test_framework::test_suite; +using boost::unit_test_framework::test_case; + +#include "HealthSystemTest.h" +#include "Game/HealthSystem.h" + +BOOST_AUTO_TEST_SUITE(HealthSystemSuite) + +BOOST_AUTO_TEST_CASE(HealthSystemTest) +{ + //this tests 2 healthevents and the healthsystem + GameHealthSystemTest game; + //100 loops will be more than enough to do the test + int loops = 100; + bool success = false; + while (loops > 0) { + game.Tick(); + if (game.TestSucceeded) { + success = true; + break; + } + loops--; + } + //The system will process the events, hence it will take a while before we can read anything + BOOST_TEST(success); +} +BOOST_AUTO_TEST_SUITE_END() + +GameHealthSystemTest::GameHealthSystemTest() +{ + ResourceManager::RegisterType("ConfigFile"); + ResourceManager::RegisterType("EntityXMLFile"); + + m_Config = ResourceManager::Load("Config.ini"); + LOG_LEVEL = static_cast<_LOG_LEVEL>(m_Config->Get("Debug.LogLevel", 1)); + + // Create the core event broker + m_EventBroker = new EventBroker(); + + // Create a world + m_World = new World(); + std::string mapToLoad = m_Config->Get("Debug.LoadMap", ""); + if (!mapToLoad.empty()) { + ResourceManager::Load(mapToLoad)->PopulateWorld(m_World); + } + + // Create system pipeline + m_SystemPipeline = new SystemPipeline(m_EventBroker); + m_SystemPipeline->AddSystem(0); + m_SystemPipeline->AddSystem(0); + + //The Test + //create entity which has transorm,player,model,health in it. i.e. is a player + EntityID playerID = m_World->CreateEntity(); + ComponentWrapper transform = m_World->AttachComponent(playerID, "Transform"); + ComponentWrapper model = m_World->AttachComponent(playerID, "Model"); + model["Resource"] = "Models/Core/UnitSphere.obj"; + ComponentWrapper player = m_World->AttachComponent(playerID, "Player"); + ComponentWrapper health = m_World->AttachComponent(playerID, "Health"); + healthsID = playerID; + double currentHealth = (double)m_World->GetComponent(healthsID, "Health")["Health"]; + + //heal player with 40 + Events::PlayerHealthPickup e3; + e3.HealthAmount = 40.0f; + e3.PlayerHealedID = healthsID; + m_EventBroker->Publish(e3); + //damage player with 50 + Events::PlayerDamage e; + e.DamageAmount = 50.0f; + e.PlayerDamagedID = healthsID; + m_EventBroker->Publish(e); + //heal some other player with 40 + Events::PlayerHealthPickup e2; + e2.HealthAmount = 40.0f; + e2.PlayerHealedID = healthsID+1; + m_EventBroker->Publish(e2); + + EntityID playerID2 = m_World->CreateEntity(); + ComponentWrapper transform2 = m_World->AttachComponent(playerID2, "Transform"); + ComponentWrapper model2 = m_World->AttachComponent(playerID2, "Model"); + model2["Resource"] = "Models/Core/UnitSphere.obj"; + ComponentWrapper player2 = m_World->AttachComponent(playerID2, "Player"); + ComponentWrapper health2 = m_World->AttachComponent(playerID2, "Health"); + //END TEST +} + +GameHealthSystemTest::~GameHealthSystemTest() +{ + delete m_SystemPipeline; + delete m_World; + delete m_EventBroker; +} + +void GameHealthSystemTest::Tick() +{ + glfwPollEvents(); + + double currentTime = glfwGetTime(); + double dt = currentTime - m_LastTime; + m_LastTime = currentTime; + + // Iterate through systems and update world! + m_SystemPipeline->Update(m_World, dt); + + m_EventBroker->Swap(); + m_EventBroker->Clear(); + + //if health reaches 90 then we know the test has succeeded (start with 100hp, remove 50hp, add 40hp) + double currentHealth = (double)m_World->GetComponent(healthsID, "Health")["Health"]; + if (currentHealth==90) + TestSucceeded = true; +} diff --git a/src/Tests/HealthSystemTest.h b/src/Tests/HealthSystemTest.h new file mode 100644 index 00000000..664d2ef3 --- /dev/null +++ b/src/Tests/HealthSystemTest.h @@ -0,0 +1,40 @@ +#ifndef HealthTest_h__ +#define HealthTest_h__ + +#include "Core/ResourceManager.h" +#include "Core/ConfigFile.h" +#include "Core/EventBroker.h" +#include "Rendering/Renderer.h" +#include "Core/InputManager.h" +#include "GUI/Frame.h" +#include "Core/World.h" +#include "Rendering/RenderQueueFactory.h" +#include "Input/InputProxy.h" +#include "Input/KeyboardInputHandler.h" +#include "Input/MouseInputHandler.h" +#include "Core/EKeyDown.h" +#include "Core/EntityXMLFile.h" +#include "Core/SystemPipeline.h" +#include "RaptorCopterSystem.h" +#include "PlayerSystem.h" +#include "Editor/EditorSystem.h" + +class GameHealthSystemTest +{ +public: + GameHealthSystemTest(); + ~GameHealthSystemTest(); + + void Tick(); + bool TestSucceeded = false; + +private: + double m_LastTime; + ConfigFile* m_Config = nullptr; + EventBroker* m_EventBroker; + World* m_World; + SystemPipeline* m_SystemPipeline; + int healthsID; +}; + +#endif diff --git a/src/Tests/InputManagerTest.cpp b/src/Tests/InputManagerTest.cpp new file mode 100644 index 00000000..5b447c2b --- /dev/null +++ b/src/Tests/InputManagerTest.cpp @@ -0,0 +1,12 @@ +#include + +#include "Engine\Core\InputManager.h" + +BOOST_AUTO_TEST_SUITE(inputManagerTests) + +BOOST_AUTO_TEST_CASE(inputManagerTest) +{ + //already tested eventbroker so inputManager is indirectly already tested +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/Tests/ObjectPoolTest.cpp b/src/Tests/ObjectPoolTest.cpp new file mode 100644 index 00000000..e136da70 --- /dev/null +++ b/src/Tests/ObjectPoolTest.cpp @@ -0,0 +1,476 @@ +#include +using boost::unit_test_framework::test_suite; +using boost::unit_test_framework::test_case; +#include "Engine/Core/ObjectPool.h" +#include + +struct S +{ + S() = default; + S(int i, float ff) : k(i), f(ff) { } + ~S() { } + int k; + float f; +}; + +//BOOST_GLOBAL_FIXTURE(S); +BOOST_AUTO_TEST_SUITE(memProtoTypeTestSuite) + +BOOST_AUTO_TEST_CASE(testPool) +{ + ObjectPool pool(32);//32 true/false values = 32 slots + BOOST_CHECK(pool.empty() == true); + + const size_t size = 12;//12 platser i structen addresses, som håller en int, en float vardera + S* addresses[size]; + addresses[0] = pool.New(7, 0.035f); + //"Not empty after allocating one element." + BOOST_CHECK(!pool.empty()); + + //"Element created correctly with k==7" + BOOST_CHECK(addresses[0]->k == 7); + //"Element created correctly with f==0.035f" + BOOST_CHECK_CLOSE_FRACTION(addresses[0]->f, 0.035f, 0.0001f); + addresses[0]->k = 5; + BOOST_CHECK(addresses[0]->k == 5); + + //"Empty after delete" + pool.Delete(addresses[0]); + BOOST_CHECK(pool.empty()); + + addresses[0] = pool.New(7, 0.035f); + addresses[1] = pool.New(5, 0.035f); + pool.Delete(addresses[1]); + BOOST_CHECK(!pool.empty()); + pool.Delete(addresses[0]); + BOOST_CHECK(pool.empty()); + + //INT32_MAX, FLT_MAX test + addresses[0] = pool.New(INT32_MAX, FLT_MAX); + BOOST_CHECK(!pool.empty()); + BOOST_CHECK(addresses[0]->k == INT32_MAX); + BOOST_CHECK_CLOSE_FRACTION(addresses[0]->f, FLT_MAX, 0.0001f); +} +/* +BOOST_AUTO_TEST_CASE(testPoolArray) +{ +ObjectPool pool(32); +S* addresses; + +//Add array size 5 to pool." +addresses = pool.NewArray(5);// <-> addresses = new S[5]; +addresses[0] = S(12, 0.030f); +addresses[1] = S(13, 0.031f); +addresses[2] = S(14, 0.032f); +addresses[3] = S(15, 0.033f); +addresses[4] = S(16, 0.034f); + +//"Not empty after allocating +BOOST_CHECK(!pool.empty()); +//"Element created correctly with k==12" +BOOST_CHECK(addresses->k == 12); +//"Element created correctly with f==0.030f" +BOOST_CHECK_CLOSE_FRACTION(addresses->f, 0.030f, 0.0001f); + +//add a few other structs so it becomes bigger than the original size (32), +//which means it must push back the rest of the values into a vector +S* test2, *test3, *test4, *test5; +test2 = pool.NewArray(5);// <-> test2 = new S[5]; +test3 = pool.NewArray(40);//+40 +test4 = pool.NewArray(40);//+40 +test5 = pool.NewArray(40);//+40=120 +BOOST_CHECK(pool.ExtraSize() == 120); +BOOST_CHECK(pool.PoolSize() == 10); +BOOST_CHECK(pool.size() == 120 + 10); + +//testar "perfekt delete", dvs bryr mig inte om att testa att deleta bara 38 om storleken egentligen är 40 osv +pool.DeleteArray(test2, 5);//callar destructorn på test2 också +pool.DeleteArray(test3, 40); + +pool.DeleteArray(addresses, 5); + +//add / del array +S* another = pool.NewArray(64); +for (int i = 0; i < 64; ++i) +another[i] = S(i, 0.1f*i); +pool.DeleteArray(another, 64); +} +*/ +BOOST_AUTO_TEST_CASE(testIterationNormal) +{ + //extra vector check + S* test4, *test5; + ObjectPool pool(4); + test4 = pool.New(); + test5 = pool.New(); + //Check so iterate over pool doesn't throw compile-time errors. + for (auto &o : pool) + o.k = 14; + for (size_t i = 0; i < 1; ++i) + BOOST_CHECK(test4[i].k == 14); + for (size_t i = 0; i < 1; ++i) + BOOST_CHECK(test5[i].k == 14); +} + +BOOST_AUTO_TEST_CASE(testOutOfScopeDelete) +{ + //extra vector check + S* test4, *test5; + { + ObjectPool pool(4); + test4 = pool.New(); + test5 = pool.New(); + //Check so iterate over pool doesn't throw compile-time errors. + for (auto &o : pool) + o.k = 14; + for (size_t i = 0; i < 1; ++i) + BOOST_CHECK(test4[i].k == 14); + for (size_t i = 0; i < 1; ++i) + BOOST_CHECK(test5[i].k == 14); + } + //pool goes out of scope here, and thus the test4 values become undefined (memory is killed at out of scope) + BOOST_CHECK(test4[0].k != 14); + BOOST_CHECK(test5[0].k != 15); +} + +BOOST_AUTO_TEST_CASE(testIterationOneExtra) +{ + //extra vector check + ObjectPool pool(1); + S* test4, *test5; + test4 = pool.New(); + test5 = pool.New(); + //Check so iterate over pool doesn't throw compile-time errors. + for (auto &o : pool) + o.k = 14; + for (size_t i = 0; i < 1; ++i) + BOOST_CHECK(test4[i].k == 14); + for (size_t i = 0; i < 1; ++i) + BOOST_CHECK(test5[i].k == 14); +} + +BOOST_AUTO_TEST_CASE(testIterationTwoExtra) +{ + //extra vector check + ObjectPool pool(1); + S* test4, *test5; + test4 = pool.New(); + test5 = pool.New(); + //Check so iterate over pool doesn't throw compile-time errors. + for (auto &o : pool) + o.k = 14; + for (size_t i = 0; i < 1; ++i) + BOOST_CHECK(test4[i].k == 14); + for (size_t i = 0; i < 1; ++i) + BOOST_CHECK(test5[i].k == 14); +} +/* +BOOST_AUTO_TEST_CASE(releaseModeTest_RandomAllocateDeallocate) +{ +//run this in releasemode +struct I +{ +I() = default; +I(size_t i, size_t ff) : k(i), f(ff) { } +~I() { } +size_t k; +size_t f; +}; +srand((unsigned int)time(nullptr)); + +const size_t SIZE = 128; +ObjectPool pool(SIZE); +I* addresses[SIZE]; +std::vector allocated(SIZE, false); +std::vector arrSizes(SIZE, 0); +size_t slotsAlloced = 0; +size_t superCount = 0; +size_t slot; +size_t i; + +while (superCount++ < 1000) { +if (rand() % 2 == 0) { +i = 0; +//ta slumpmässig slot som inte är allokerad +do { +slot = (size_t)((SIZE - 1) * ((float)rand() / RAND_MAX)); +} while (allocated[slot] && ++i < 512); + +if (i < 512) { +arrSizes[slot] = 1 + (size_t)((24 - 1) * ((float)rand() / RAND_MAX)); +addresses[slot] = pool.NewArray(arrSizes[slot]); +for (size_t a = 0; a < arrSizes[slot]; ++a) +addresses[slot][a] = I(slot, a); +allocated[slot] = true; +++slotsAlloced; +} +} +//Deallocate +else { +i = 0; +//ta slumpmässig slot som är allokerad +do { +slot = (size_t)((SIZE - 1) * ((float)rand() / RAND_MAX)); +} while (!allocated[slot] && ++i < 512); + +if (i < 512) { +pool.DeleteArray(addresses[slot], arrSizes[slot]); +arrSizes[slot] = 0; +allocated[slot] = false; +--slotsAlloced; +} +} +//Check content. +for (size_t a = 0; a < SIZE; ++a) { +if (allocated[a]) { +for (size_t e = 0; e < arrSizes[a]; ++e) { +BOOST_CHECK(!(addresses[a][e].k != a || addresses[a][e].f != e)); +} +} +} +} +} +*/ + +BOOST_AUTO_TEST_CASE(testConstructors) +{ + //http://stackoverflow.com/questions/357929/is-it-important-to-unit-test-a-constructor + //"If your constructor has, for example, an if (condition), you need to test both flows (true,false). + //If your constructor does some kind of job before setting. You should check the job is done" + + //testing the constructors with different T values and a small check so size is initialized to 0 + MemoryPool memPoolI; + BOOST_CHECK(memPoolI.empty()); + BOOST_CHECK(memPoolI.size() == 0); + MemoryPool memPoolF; + BOOST_CHECK(memPoolF.empty()); + BOOST_CHECK(memPoolF.size() == 0); + MemoryPool memPoolD; + BOOST_CHECK(memPoolD.empty()); + BOOST_CHECK(memPoolD.size() == 0); + MemoryPool memPoolS; + BOOST_CHECK(memPoolS.empty()); + BOOST_CHECK(memPoolS.size() == 0); + + ObjectPool objPoolI(64); + BOOST_CHECK(objPoolI.empty()); + BOOST_CHECK(objPoolI.size() == 0); + ObjectPool objPoolF(32); + BOOST_CHECK(objPoolF.empty()); + BOOST_CHECK(objPoolF.size() == 0); + ObjectPool objPoolD(16); + BOOST_CHECK(objPoolD.empty()); + BOOST_CHECK(objPoolD.size() == 0); + ObjectPool objPoolS(128); + BOOST_CHECK(objPoolS.empty()); + BOOST_CHECK(objPoolS.size() == 0); +} + +BOOST_AUTO_TEST_CASE(testOperators) +{ + ObjectPool pool(100); + // S* s[12] = pool.NewArray(12); + S* s[12]; + s[0] = pool.New(); + s[11] = pool.New(); + + s[0]->k = 2; + s[11]->k = 3; + //testing operators: ++i,!= + auto& iter = pool.begin(); + for (iter; iter != pool.end(); ++iter) { + //testing operators:*,== + auto dereferencedIterator = *iter; + if (iter == pool.begin()) { + BOOST_CHECK(dereferencedIterator.k == 2); + } + if (iter == pool.end()) { + BOOST_CHECK(dereferencedIterator.k == 3); + } + //testing operators:-> + iter->k += 2; + } + BOOST_CHECK(s[0]->k == 4); + BOOST_CHECK(s[11]->k == 5); + BOOST_CHECK(iter == pool.end()); + + //testing operators:i++ + s[0]->k = 2; + s[11]->k = 2; + for (auto& iter = pool.begin(); iter != pool.end(); iter++) + iter->k += 2; + BOOST_CHECK(s[0]->k == 4); + BOOST_CHECK(s[11]->k == 4); +} +/* +BOOST_AUTO_TEST_CASE(testBranchFree) +{ +//testing Free , which is the only untested +//via delete/deletearray + +//1. no extra memory delete +ObjectPool pool(32);//32 true/false values = 32 slots +S* addresses[12]; +addresses[0] = pool.New(7, 0.035f); +pool.Delete(addresses[0]); +BOOST_CHECK(pool.empty()); + +//1b. no extra memory deleteArray +ObjectPool pool1b(32);//32 true/false values = 32 slots +S* test1b; +test1b = pool1b.NewArray(5);// <-> test2 = new S[5]; +BOOST_CHECK(pool1b.size() == 5); +pool1b.DeleteArray(test1b, 5);//callar destructorn på test2 också +BOOST_CHECK(pool1b.empty()); + +//2. extra memory delete +ObjectPool pool2(2); +S* addresses2[12]; +addresses2[0] = pool2.New(7, 0.035f); +addresses2[1] = pool2.New(7, 0.035f); +addresses2[2] = pool2.New(7, 0.035f); +addresses2[3] = pool2.New(7, 0.035f); +addresses2[4] = pool2.New(7, 0.035f); +BOOST_CHECK(pool2.size() == 5); +pool2.Delete(addresses2[0]); +BOOST_CHECK(pool2.size() == 4); +pool2.Delete(addresses2[1]); +BOOST_CHECK(pool2.size() == 3); +pool2.Delete(addresses2[2]); +BOOST_CHECK(pool2.size() == 2); +pool2.Delete(addresses2[3]); +BOOST_CHECK(pool2.size() == 1); +pool2.Delete(addresses2[4]); +BOOST_CHECK(pool2.empty()); +//reverse delete +addresses2[0] = pool2.New(7, 0.035f); +addresses2[1] = pool2.New(7, 0.035f); +addresses2[2] = pool2.New(7, 0.035f); +addresses2[3] = pool2.New(7, 0.035f); +addresses2[4] = pool2.New(7, 0.035f); +BOOST_CHECK(pool2.size() == 5); +pool2.Delete(addresses2[4]); +BOOST_CHECK(pool2.size() == 4); +pool2.Delete(addresses2[3]); +BOOST_CHECK(pool2.size() == 3); +pool2.Delete(addresses2[2]); +BOOST_CHECK(pool2.size() == 2); +pool2.Delete(addresses2[1]); +BOOST_CHECK(pool2.size() == 1); +pool2.Delete(addresses2[0]); +BOOST_CHECK(pool2.empty()); + +//2b. extra memory deleteArray +ObjectPool pool2b(32);//32 true/false values = 32 slots +S* test2b,*test2bb; +test2b = pool2b.NewArray(5);// <-> test2 = new S[5]; +BOOST_CHECK(pool2b.size() == 5); +test2bb = pool2b.NewArray(40);// <-> test2 = new S[5]; +BOOST_CHECK(pool2b.size() == 45); +pool2b.DeleteArray(test2b, 5);//callar destructorn på test2 också +BOOST_CHECK(pool2b.size() == 40); +pool2b.DeleteArray(test2bb, 40);//callar destructorn på test2 också +BOOST_CHECK(pool2b.empty()); +} +*/ +BOOST_AUTO_TEST_CASE(testBranchAllocate) +{ + //1 slot else many slots + //see testBranchFree + + //out of mem vs not out of mem allocate + //see testBranchFree +} +BOOST_AUTO_TEST_CASE(testEdgeCase) +{ + //test with a very small pool + ObjectPool pool(1); + BOOST_CHECK(pool.empty()); + S* test4; + test4 = pool.New(7, 0.035f); + BOOST_CHECK(!pool.empty()); + BOOST_CHECK(test4->k == 7); + BOOST_CHECK_CLOSE_FRACTION(test4->f, 0.035f, 0.0001f); + + //test with a very small pool and array, iterating + ObjectPool poolA(1); + S* test5; + test5 = poolA.New(); + //Check so iterate over pool doesn't throw compile-time errors. + for (auto &o : poolA) + o.k = 14; + for (size_t i = 0; i < 1; ++i) + BOOST_CHECK(test5[i].k == 14); +} + +BOOST_AUTO_TEST_CASE(testBadlyAlignedData) +{ + //small test with non-aligned data 4+1bytes + struct S + { + S() = default; + S(float f, char c) : m_f(f), m_c(c) { } + ~S() { } + float m_f; + char m_c; + }; + MemoryPool memPoolS; + BOOST_CHECK(memPoolS.empty()); + BOOST_CHECK(memPoolS.size() == 0); + ObjectPool objPoolS(64); + BOOST_CHECK(objPoolS.empty()); + BOOST_CHECK(objPoolS.size() == 0); + S* test4; + test4 = objPoolS.New(); + //Check so iterate over pool doesn't throw compile-time errors. + for (auto &o : objPoolS) { + o.m_c = 'v'; + o.m_f = 0.15534543f; + } + for (size_t i = 0; i < 1; ++i) { + BOOST_CHECK(test4[i].m_c == 'v'); + BOOST_CHECK_CLOSE_FRACTION(test4[i].m_f, 0.15534543f, 0.0001f); + } +} +BOOST_AUTO_TEST_CASE(testBadlyAlignedData2) +{ + //small test with non-aligned data 1+1+1bytes + struct S + { + S() = default; + S(char c, char c2, char c3) : m_c(c), m_c2(c2), m_c3(c3) { } + ~S() { } + char m_c; + char m_c2; + char m_c3; + }; + MemoryPool memPoolS; + BOOST_CHECK(memPoolS.empty()); + BOOST_CHECK(memPoolS.size() == 0); + ObjectPool objPoolS(64); + BOOST_CHECK(objPoolS.empty()); + BOOST_CHECK(objPoolS.size() == 0); + S* test4; + test4 = objPoolS.New(); + //Check so iterate over pool doesn't throw compile-time errors. + for (auto &o : objPoolS) { + o.m_c = 'v'; + o.m_c2 = 'w'; + o.m_c3 = 'x'; + } + for (size_t i = 0; i < 1; ++i) { + BOOST_CHECK(test4[i].m_c == 'v'); + BOOST_CHECK(test4[i].m_c2 == 'w'); + BOOST_CHECK(test4[i].m_c3 == 'x'); + } +} + +BOOST_AUTO_TEST_CASE(testWrongData) +{ +} +BOOST_AUTO_TEST_CASE(testFillDeleteFillAgain) { + //already done in BOOST_AUTO_TEST_CASE(releaseModeTest_RandomAllocateDeallocate) + +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/Tests/OctTreeTest.cpp b/src/Tests/OctTreeTest.cpp new file mode 100644 index 00000000..0206ce50 --- /dev/null +++ b/src/Tests/OctTreeTest.cpp @@ -0,0 +1,160 @@ +#include +using boost::unit_test_framework::test_suite; +using boost::unit_test_framework::test_case; +#include //srand + +#include "Engine/Core/OctTree.h" +#include "Engine/Core/Ray.h" +#include "OldOctTree.h" + +BOOST_AUTO_TEST_SUITE(octTreeTestsW) + +BOOST_AUTO_TEST_CASE(octSameRegionTest) +{ + 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 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); +} + +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 +void RegionTest(Tree& tree) +{ + 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 outVec; + tree.BoxesInSameRegion(aabb, outVec); +} + +template +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 +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 +void NopTest(Tree& tree) +{ + +} + +template +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(RegionTest); + BOOST_CHECK(true); +} + +BOOST_AUTO_TEST_CASE(octRegionPerfTestNoDuplicates) +{ + TestLoop(RegionTest); + BOOST_CHECK(true); +} + +BOOST_AUTO_TEST_CASE(octBoxPerfTestWithDuplicates) +{ + TestLoop(BoxTest); + BOOST_CHECK(true); +} + +BOOST_AUTO_TEST_CASE(octBoxPerfTestNoDuplicates) +{ + TestLoop(BoxTest); + BOOST_CHECK(true); +} + +BOOST_AUTO_TEST_CASE(octRayPerfTestWithDuplicates) +{ + TestLoop(RayTest); + BOOST_CHECK(true); +} + +BOOST_AUTO_TEST_CASE(octRayPerfTestNoDuplicates) +{ + TestLoop(RayTest); + BOOST_CHECK(true); +} + +BOOST_AUTO_TEST_CASE(octNopPerfTestWithDuplicates) +{ + TestLoop(NopTest); + BOOST_CHECK(true); +} + +BOOST_AUTO_TEST_CASE(octNopPerfTestNoDuplicates) +{ + TestLoop(NopTest); + BOOST_CHECK(true); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/Tests/OctTreeTestAnders.cpp b/src/Tests/OctTreeTestAnders.cpp new file mode 100644 index 00000000..b61caead --- /dev/null +++ b/src/Tests/OctTreeTestAnders.cpp @@ -0,0 +1,49 @@ +#include +using boost::unit_test_framework::test_suite; +using boost::unit_test_framework::test_case; +#include //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 +//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 destructor check in the end, just look for memleaks, then it didnt clear the AABB structure +} + +BOOST_AUTO_TEST_CASE(octTreeTest2) +{ + //octtree draw etc + Game game(0, nullptr); + while (game.Running()) { + game.Tick(); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/Tests/OctTreeTestGameClass.cpp b/src/Tests/OctTreeTestGameClass.cpp new file mode 100644 index 00000000..10d4d6a5 --- /dev/null +++ b/src/Tests/OctTreeTestGameClass.cpp @@ -0,0 +1,193 @@ +#include "OctTreeTestGameClass.h" + +Game::Game(int argc, char* argv[]) : someOctTree(AABB(-0.5f*worldSize, 0.5f*worldSize), 2) +{ + ResourceManager::RegisterType("ConfigFile"); + ResourceManager::RegisterType("Model"); + ResourceManager::RegisterType("Texture"); + ResourceManager::RegisterType("EntityXMLFile"); + ResourceManager::RegisterType("ShaderProgram"); + + m_Config = ResourceManager::Load("Config.ini"); + LOG_LEVEL = static_cast<_LOG_LEVEL>(m_Config->Get("Debug.LogLevel", 1)); + + // Create the core event broker + m_EventBroker = new EventBroker(); + + m_RenderQueueFactory = new RenderQueueFactory(); + + // Create the renderer + m_Renderer = new Renderer(m_EventBroker); + m_Renderer->SetFullscreen(m_Config->Get("Video.Fullscreen", false)); + m_Renderer->SetVSYNC(m_Config->Get("Video.VSYNC", false)); + m_Renderer->SetResolution(Rectangle( + 0, + 0, + m_Config->Get("Video.Width", 1280), + m_Config->Get("Video.Height", 720) + )); + m_Renderer->Initialize(); + m_Renderer->Camera()->SetFOV(glm::radians(m_Config->Get("Video.FOV", 90.f))); + + // Create input manager + m_InputManager = new InputManager(m_Renderer->Window(), m_EventBroker); + m_InputProxy = new InputProxy(m_EventBroker); + m_InputProxy->AddHandler(); + m_InputProxy->AddHandler(); + m_InputProxy->LoadBindings("Input.ini"); + + // Create the root level GUI frame + m_FrameStack = new GUI::Frame(m_EventBroker); + m_FrameStack->Width = m_Renderer->Resolution().Width; + m_FrameStack->Height = m_Renderer->Resolution().Height; + + // Create a TEST WORLD + m_World = new HardcodedTestWorld(); + + m_SystemPipeline = new SystemPipeline(m_EventBroker); + m_SystemPipeline->AddSystem(0); + + m_LastTime = glfwGetTime(); +} + +Game::~Game() +{ + delete m_FrameStack; + delete m_EventBroker; +} + +void Game::Tick() +{ + double currentTime = glfwGetTime(); + double dt = currentTime - m_LastTime; + m_LastTime = currentTime; + + // Handle input in a weird looking but responsive way + m_EventBroker->Process(); + m_EventBroker->Swap(); + m_InputManager->Update(dt); + m_EventBroker->Swap(); + m_InputProxy->Update(dt); + m_EventBroker->Swap(); + m_InputProxy->Process(); + m_EventBroker->Swap(); + +#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) { + m_UpdatedOnce = true; + m_World->createTestEntitiesTest1(); + } + + //add/move the trigger box + auto pos = m_Renderer->Camera()->Forward() + m_Renderer->Camera()->Position(); + AABB boxi; + boxi.CreateFromCenter(pos, maxPos - minPos); + frameCounter++; + if (frameCounter > 1) { + m_World->someOctTree.ClearDynamicObjects(); + m_World->someOctTree.AddDynamicObject(boxi); + frameCounter = 0; + } + ComponentWrapper transform = m_World->GetComponent(m_World->anotherBoxTransformId, "Transform"); + transform["Position"] = boxi.Center(); + + //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 boxIndex; + 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_DynamicObjIndices.size() != 0) { + model["Color"] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); + } + + //next check if the childIndicesContainingBox method returns the correct boxes + //REQUIRED: childIndicesContainingBox must be public to test this! + for each (auto someBoxIndex in boxIndex) + { + 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) { + model["Color"] = glm::vec4(0.0f, 1.0f, 0.0f, 1.0f); + + } + } + } + m_RenderQueueFactory->Update(m_World); + + //wireframe + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); +#endif + //this tests AABB vs AABB collision and AABB vs OctTree with AABB in it +#ifdef TEST2 + + //only add 1 for now... + //grey box + + 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); + AABB aabb; + 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); + std::vector test2; + someOctTree.BoxesInSameRegion(aabb, test2); + } + if (!m_UpdatedOnce) { + m_UpdatedOnce = true; + someOctTree.AddStaticObject(aabb); + //create the "small red box" + m_BoxID = m_World->CreateEntity(); + ComponentWrapper transform = m_World->AttachComponent(m_BoxID, "Transform"); + transform["Scale"] = boxSize; + ComponentWrapper model = m_World->AttachComponent(m_BoxID, "Model"); + model["Resource"] = "Models/Core/UnitBox.obj"; + m_World->createTestEntitiesTest2(); + } + + //red box + AABB redBox; + auto boxPos = m_Renderer->Camera()->Position() + 1.2f*m_Renderer->Camera()->Forward(); + redBox.CreateFromCenter(boxPos, boxSize); + ComponentWrapper transform = m_World->GetComponent(m_BoxID, "Transform"); + transform["Position"] = boxPos; + ComponentWrapper model = m_World->GetComponent(m_BoxID, "Model"); + //this checks AABB vs an AABB in the octTree + if (someOctTree.BoxCollides(redBox, AABB())) { + //this checks AABB vs AABB + //if (Collision::AABBVsAABB(redBox, aabb)) { + //m_Renderer->Camera()->SetPosition(m_PrevPos); + //m_Renderer->Camera()->SetOrientation(m_PrevOri); + model["Color"] = greenCol; + } + else { + model["Color"] = redCol; + } + + m_PrevPos = m_Renderer->Camera()->Position(); + m_PrevOri = m_Renderer->Camera()->Orientation(); + + m_RenderQueueFactory->Update(m_World); +#endif + + // Iterate through systems and update world! + m_SystemPipeline->Update(m_World, dt); + m_Renderer->Update(dt); + + m_RenderQueueFactory->Update(m_World); + GLERROR("Game::Tick m_RenderQueueFactory->Update"); + m_Renderer->Draw(m_RenderQueueFactory->RenderQueues()); + GLERROR("Game::Tick m_Renderer->Draw"); + m_EventBroker->Swap(); + m_EventBroker->Clear(); + + glfwPollEvents(); +} diff --git a/src/Tests/OctTreeTestGameClass.h b/src/Tests/OctTreeTestGameClass.h new file mode 100644 index 00000000..6dc9404e --- /dev/null +++ b/src/Tests/OctTreeTestGameClass.h @@ -0,0 +1,62 @@ +#ifndef Game_h__ +#define Game_h__ + +#include "Core/ResourceManager.h" +#include "Core/ConfigFile.h" +#include "Core/EventBroker.h" +#include "Rendering/Renderer.h" +#include "Core/InputManager.h" +#include "GUI/Frame.h" +#include "Core/World.h" +#include "Rendering/RenderQueueFactory.h" +#include "Input/InputProxy.h" +#include "Input/KeyboardInputHandler.h" +#include "Input/MouseInputHandler.h" +#include "Core/EKeyDown.h" +#include "Core/EntityXMLFile.h" +#include "Core/SystemPipeline.h" +#include "RaptorCopterSystem.h" +#include "PlayerSystem.h" +#include "Editor/EditorSystem.h" + +#include "OctTreeTestHardCodedTestWorld.h" +#include "Collision/Collision.h" + +class Game +{ +public: + Game(int argc, char* argv[]); + ~Game(); + + bool Running() const { return !glfwWindowShouldClose(m_Renderer->Window()); } + void Tick(); + +private: + double m_LastTime; + ConfigFile* m_Config = nullptr; + EventBroker* m_EventBroker; + IRenderer* m_Renderer; + InputManager* m_InputManager; + GUI::Frame* m_FrameStack; + HardcodedTestWorld* m_World; + RenderQueueFactory* m_RenderQueueFactory; + InputProxy* m_InputProxy; + SystemPipeline* m_SystemPipeline; + + //Test1 + int frameCounter = 0; + glm::vec3 minPos = glm::vec3(0.1f, 0.1f, 0.1f); + glm::vec3 maxPos = glm::vec3(0.2f, 0.2f, 0.2f); + + //Test2 + bool m_UpdatedOnce = false; + unsigned int m_BoxID; + glm::vec3 m_PrevPos; + glm::quat m_PrevOri; + + glm::vec3 worldSize = glm::vec3(50, 50, 50); + OctTree someOctTree; + +}; + +#endif diff --git a/src/Tests/OctTreeTestGameMain.cpp b/src/Tests/OctTreeTestGameMain.cpp new file mode 100644 index 00000000..43789cea --- /dev/null +++ b/src/Tests/OctTreeTestGameMain.cpp @@ -0,0 +1,21 @@ +//#define BOOST_TEST_MODULE collTest +#include +#include +using boost::unit_test_framework::test_suite; +using boost::unit_test_framework::test_case; +#include "Engine/Collision/Collision.h" +#include "Engine/Core/AABB.h" +#include "Engine/Core/Ray.h" +#include //srand +#include "Engine/Core/OctTree.h" + +//vs memleaks +//#define _CRTDBG_MAP_ALLOC +//#include +//#include +//#define DEBUG_CLIENTBLOCK new( _CLIENT_BLOCK, __FILE__, __LINE__) +//#define new DEBUG_CLIENTBLOCK + +BOOST_AUTO_TEST_SUITE(cTest) +BOOST_AUTO_TEST_SUITE_END() + diff --git a/src/Tests/OctTreeTestHardCodedTestWorld.h b/src/Tests/OctTreeTestHardCodedTestWorld.h new file mode 100644 index 00000000..512716df --- /dev/null +++ b/src/Tests/OctTreeTestHardCodedTestWorld.h @@ -0,0 +1,134 @@ +#include +#include +#include +#include "GLM.h" +#include "Core/World.h" +#include "Core/Util/Any.h" + +#include +//last! +//#include "OldOctTree.h" +#define private public +#include + +class HardcodedTestWorld : public World +{ +public: + struct LinkOctTreeAndModel { + EntityID entId; + OctTree::OctChild* child; + glm::vec3 posxyz; + LinkOctTreeAndModel(EntityID eId, OctTree::OctChild* ch, glm::vec3 pos) + { + entId = eId; + child = ch; + posxyz = pos; + } + }; + EntityID anotherBoxTransformId; + std::vector linkOM; + OctTree someOctTree; + + //constructor + HardcodedTestWorld() + : World() + , someOctTree(AABB(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(1.0f, 1.0f, 1.0f)), 2) + { + registerTestComponents(); + //createTestEntities(); + } + +private: + void registerTestComponents() + { + ComponentWrapperFactory f; + + + f = ComponentWrapperFactory("Test"); + f.AddProperty("TestInteger", 1337); + f.AddProperty("TestFloat", 13.37f); + f.AddProperty("TestString", std::string("Carlito")); + RegisterComponent(f); + + f = ComponentWrapperFactory("Debug"); + f.AddProperty("Name", std::string("Unnamed")); + RegisterComponent(f); + + f = ComponentWrapperFactory("Transform"); + f.AddProperty("Position", glm::vec3(0.f, 0.f, 0.f)); + f.AddProperty("Orientation", glm::quat()); + f.AddProperty("Scale", glm::vec3(1.f, 1.f, 1.f)); + RegisterComponent(f); + + f = ComponentWrapperFactory("Model"); + f.AddProperty("Resource", std::string()); + f.AddProperty("Color", glm::vec4(1.f, 1.f, 1.f, 1.f)); + f.AddProperty("Visible", true); + RegisterComponent(f); + } + + void createTestEntitiesTest1() + { + World& world = *this; + EntityID tempId; + //add octTree + { + //copy of mainbox + 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.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)); + //note: have to delete the box in the tree first, since were trying to move the box + someOctTree.AddDynamicObject(anotherBox); + + //draw anotherbox and save it in 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_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_Root->m_Children[j]; + + for (size_t i = 0; i < 8; i++) + { + AddBoxModel(someChild->m_Children[i]->m_Box.Center(), + someChild->m_Children[i]->m_Box.HalfSize().x, someChild->m_Children[i], tempId); + } + } + } + }//end CreateEnt + + void createTestEntitiesTest2() + { + World& world = *this; + + EntityID entityCollisionBox = world.CreateEntity(); + ComponentWrapper transform = world.AttachComponent(entityCollisionBox, "Transform"); + transform["Position"] = glm::vec3(0.f, 2.f, 0.f); + ComponentWrapper model = world.AttachComponent(entityCollisionBox, "Model"); + model["Resource"] = "Models/Core/UnitBox.obj"; + } + + void AddBoxModel(const glm::vec3 ¢er, 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); + } +}; \ No newline at end of file diff --git a/src/Tests/OldOctTree.cpp b/src/Tests/OldOctTree.cpp new file mode 100644 index 00000000..d3738ee6 --- /dev/null +++ b/src/Tests/OldOctTree.cpp @@ -0,0 +1,328 @@ +#include +#include +#include + +#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 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& 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 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 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(); + } +} + +inline bool OctTree::hasChildren() const +{ + return m_Children[0] != nullptr; +} +} \ No newline at end of file diff --git a/src/Tests/OldOctTree.h b/src/Tests/OldOctTree.h new file mode 100644 index 00000000..3b0eefdc --- /dev/null +++ b/src/Tests/OldOctTree.h @@ -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& 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 m_StaticObjects; + std::vector 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 childIndicesContainingBox(const AABB& box) const; +}; + +} + +#endif \ No newline at end of file diff --git a/src/Tests/ResourceManagerTest.cpp b/src/Tests/ResourceManagerTest.cpp new file mode 100644 index 00000000..a3edb7b8 --- /dev/null +++ b/src/Tests/ResourceManagerTest.cpp @@ -0,0 +1,36 @@ +#include +#include "Core/World.h" + +//private->public hack doesnt work, tons of link errors +//so there is currently no good way to test this class +//#define private public +#include "Core/ResourceManager.h" +#include "Core/ConfigFile.h" +#include "Rendering/Renderer.h" +#include "Core/EntityXMLFile.h" +#include "Engine\Rendering\Texture.h" + +BOOST_AUTO_TEST_SUITE(resourceManagerTests) + +BOOST_AUTO_TEST_CASE(resourceManagerTest) +{ + World m_World; + + //private static metoder/variabler + + ResourceManager::RegisterType("ConfigFile"); + BOOST_CHECK(!ResourceManager::IsResourceLoaded("ConfigFile", "Config.ini")); + auto m_Config = ResourceManager::Load("Config.ini"); + BOOST_CHECK(ResourceManager::IsResourceLoaded("ConfigFile", "Config.ini")); + ResourceManager::Release("ConfigFile", "Config.ini"); + BOOST_CHECK(!ResourceManager::IsResourceLoaded("ConfigFile", "Config.ini")); + + //configfile without register + //check so output says "EE failed to load: type not registered..." + auto m_ScreenQuadNoRegister = ResourceManager::Load("Models/Core/ScreenQuad.obj"); + BOOST_CHECK(!ResourceManager::IsResourceLoaded("Model", "Models/Core/ScreenQuad.obj")); + + //there is no error feedback to check if you try to release the wrong resources - hence that cant be tested either +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/Tests/WorldTest.cpp b/src/Tests/WorldTest.cpp index 4fd4ceed..8d92a328 100644 --- a/src/Tests/WorldTest.cpp +++ b/src/Tests/WorldTest.cpp @@ -64,7 +64,7 @@ BOOST_AUTO_TEST_CASE(WorldTestMultipleAllocations, * utf::tolerance(0.00001)) // Loop through them and check data int i = 0; - for (auto& c : w.GetComponents("Test")) { + for (auto& c : *w.GetComponents("Test")) { BOOST_TEST((int)c["TestInteger"] == i); i++; } diff --git a/tools/CodeRules.h b/tools/CodeRules.h index f09ae81d..8d72a6f2 100755 --- a/tools/CodeRules.h +++ b/tools/CodeRules.h @@ -86,8 +86,7 @@ T* ClassType::PublicMemberFunction(bool value) if (m_PrivateMember2->PublicMember == 1) { return new T(); - } - else { + } else { return nullptr; } } diff --git a/tools/deploy.bat b/tools/deploy.bat index 5ef074ef..f629058c 100755 --- a/tools/deploy.bat +++ b/tools/deploy.bat @@ -11,6 +11,8 @@ RMDIR "%DeployLocation%\Textures" MKLINK "%DeployLocation%\Textures\" "assets\Textures\" /J RMDIR "%DeployLocation%\Audio" MKLINK "%DeployLocation%\Audio\" "assets\Audio\" /J +RMDIR "%DeployLocation%\Fonts" +MKLINK "%DeployLocation%\Fonts\" "assets\Fonts\" /J ECHO Deploying resources to %DeployLocation% :: Schemas @@ -21,6 +23,7 @@ RMDIR /S /Q "%DeployLocation%\Shaders" MKLINK "%DeployLocation%\Shaders\" "resources\Shaders" /J :: Configuration files MKLINK "%DeployLocation%\DefaultConfig.ini" "resources\DefaultConfig.ini" /H +MKLINK "%DeployLocation%\DefaultInput.ini" "resources\DefaultInput.ini" /H :: Platform specific binaries IF "%~1"=="" GOTO :EOF