From c4be9b1cba4f04d4831a31d3a9f9061ee36c3c00 Mon Sep 17 00:00:00 2001 From: Xiao Qi Date: Sat, 19 Nov 2022 21:16:24 +0800 Subject: [PATCH 001/275] Initial commit, change of data structs containing data for model and animation assets. Changed all other references to previous mode and mesh asset names --- .../Asset Types/Models/SHAnimationAsset.h | 88 +++++++++++++++++++ .../Assets/Asset Types/Models/SHMeshAsset.h | 60 +++++++++++++ .../Asset Types/{ => Models}/SHModelAsset.h | 30 +++---- .../Assets/Asset Types/Models/SHRigAsset.h | 48 ++++++++++ .../src/Assets/Asset Types/SHAnimationAsset.h | 30 ------- .../src/Assets/Asset Types/SHAssetIncludes.h | 2 +- .../Libraries/Loaders/SHModelLoader.cpp | 8 +- .../Assets/Libraries/Loaders/SHModelLoader.h | 4 +- SHADE_Engine/src/Assets/SHAssetManager.cpp | 4 +- .../MiddleEnd/Interface/SHDebugDrawSystem.cpp | 4 +- .../MiddleEnd/Meshes/SHPrimitiveGenerator.cpp | 16 ++-- .../MiddleEnd/Meshes/SHPrimitiveGenerator.h | 22 ++--- SHADE_Engine/src/Resource/SHResourceManager.h | 4 +- 13 files changed, 238 insertions(+), 82 deletions(-) create mode 100644 SHADE_Engine/src/Assets/Asset Types/Models/SHAnimationAsset.h create mode 100644 SHADE_Engine/src/Assets/Asset Types/Models/SHMeshAsset.h rename SHADE_Engine/src/Assets/Asset Types/{ => Models}/SHModelAsset.h (61%) create mode 100644 SHADE_Engine/src/Assets/Asset Types/Models/SHRigAsset.h delete mode 100644 SHADE_Engine/src/Assets/Asset Types/SHAnimationAsset.h diff --git a/SHADE_Engine/src/Assets/Asset Types/Models/SHAnimationAsset.h b/SHADE_Engine/src/Assets/Asset Types/Models/SHAnimationAsset.h new file mode 100644 index 00000000..d7128977 --- /dev/null +++ b/SHADE_Engine/src/Assets/Asset Types/Models/SHAnimationAsset.h @@ -0,0 +1,88 @@ +/*************************************************************************//** + * \file SHAnimationAsset.h + * \author Loh Xiao Qi + * \date October 2022 + * \brief + * + * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. + *****************************************************************************/ +#pragma once + +#include "Math/SHMath.h" +#include "Assets/Asset Types/SHAssetData.h" + +#include + + +namespace SHADE +{ + enum class SHAnimationBehaviour : uint8_t + { + DEFAULT = 0x0, + CONSTANT = 0x1, + LINEAR = 0x2, + REPEAT = 0x3 + }; + + // Smallest data containers + struct PositionKey + { + float time; + SHVec3 value; + }; + + struct RotationKey + { + float time; + SHVec4 value; + }; + + struct ScaleKey + { + float time; + SHVec3 value; + }; + + // Headers for read/write + struct SHAnimNodeInfo + { + uint32_t charCount; + uint32_t posKeyCount; + uint32_t rotKeyCount; + uint32_t scaKeyCount; + }; + + struct SHAnimDataHeader + { + uint32_t charCount; + uint32_t animNodeCount; + std::vector nodeHeaders; + }; + + // Main data containers + struct SHAnimData + { + std::string name; + SHAnimationBehaviour pre; + SHAnimationBehaviour post; + + std::vector positionKeys; + std::vector rotationKeys; + std::vector scaleKeys; + + }; + + struct SH_API SHAnimAsset : SHAssetData + { + std::string name; + + double duration; + double ticksPerSecond; + + std::vector nodeChannels; + //std::vector meshChannels; + //std::vector morphMeshChannels; + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Assets/Asset Types/Models/SHMeshAsset.h b/SHADE_Engine/src/Assets/Asset Types/Models/SHMeshAsset.h new file mode 100644 index 00000000..c10e0cc3 --- /dev/null +++ b/SHADE_Engine/src/Assets/Asset Types/Models/SHMeshAsset.h @@ -0,0 +1,60 @@ +/****************************************************************************** + * \file SHMeshAsset.h + * \author Loh Xiao Qi + * \date 19 November 2022 + * \brief + * + * \copyright Copyright (c) 2021 Digipen Institute of Technology. Reproduction + * or disclosure of this file or its contents without the prior + * written consent of Digipen Institute of Technology is prohibited. + ******************************************************************************/ +#pragma once + +#include "Math/SHMath.h" +#include "Assets/Asset Types/SHAssetData.h" + +#include +#include + +namespace SHADE +{ + struct SHMeshDataHeader + { + uint32_t vertexCount; + uint32_t indexCount; + uint32_t charCount; + uint32_t boneCount; + std::string name; + }; + + struct MeshBoneInfo + { + uint32_t charCount; + uint32_t weightCount; // Size should be same as boneCount + }; + + struct BoneWeight + { + uint32_t index; + float weight; + }; + + struct MeshBone + { + std::string name; + SHMatrix offset; + std::vector weights; + }; + + struct SH_API SHMeshAsset : SHAssetData + { + SHMeshDataHeader header; + std::vector VertexPositions; + std::vector VertexTangents; + std::vector VertexNormals; + std::vector VertexTexCoords; + std::vector Indices; + std::vector BonesInfo; + std::vector Bones; + }; +} diff --git a/SHADE_Engine/src/Assets/Asset Types/SHModelAsset.h b/SHADE_Engine/src/Assets/Asset Types/Models/SHModelAsset.h similarity index 61% rename from SHADE_Engine/src/Assets/Asset Types/SHModelAsset.h rename to SHADE_Engine/src/Assets/Asset Types/Models/SHModelAsset.h index c057678b..4425f555 100644 --- a/SHADE_Engine/src/Assets/Asset Types/SHModelAsset.h +++ b/SHADE_Engine/src/Assets/Asset Types/Models/SHModelAsset.h @@ -13,36 +13,26 @@ #pragma once #include -#include "Math/SHMath.h" -#include "SHAssetData.h" + +#include "Assets/Asset Types/Models/SHAnimationAsset.h" +#include "Assets/Asset Types/Models/SHMeshAsset.h" +#include "Assets/Asset Types/Models/SHRigAsset.h" namespace SHADE { - struct SHMeshAssetHeader - { - uint32_t vertexCount; - uint32_t indexCount; - std::string name; - }; - struct SHModelAssetHeader { size_t meshCount; - }; - - struct SH_API SHMeshData : SHAssetData - { - SHMeshAssetHeader header; - std::vector VertexPositions; - std::vector VertexTangents; - std::vector VertexNormals; - std::vector VertexTexCoords; - std::vector Indices; + size_t animCount; }; struct SH_API SHModelAsset : SHAssetData { SHModelAssetHeader header; - std::vector subMeshes; + + SHRigAsset rig; + + std::vector meshes; + std::vector anims; }; } \ No newline at end of file diff --git a/SHADE_Engine/src/Assets/Asset Types/Models/SHRigAsset.h b/SHADE_Engine/src/Assets/Asset Types/Models/SHRigAsset.h new file mode 100644 index 00000000..09ce5658 --- /dev/null +++ b/SHADE_Engine/src/Assets/Asset Types/Models/SHRigAsset.h @@ -0,0 +1,48 @@ +/****************************************************************************** + * \file SHRigAsset.h + * \author Loh Xiao Qi + * \date 19 November 2022 + * \brief + * + * \copyright Copyright (c) 2021 Digipen Institute of Technology. Reproduction + * or disclosure of this file or its contents without the prior + * written consent of Digipen Institute of Technology is prohibited. + ******************************************************************************/ +#pragma once + +#include "Math/SHMath.h" +#include "Assets/Asset Types/SHAssetData.h" + +#include + +namespace SHADE +{ + struct RigDataHeader + { + uint32_t nodeCount; + std::vector charCounts; + }; + + struct RigNodeData + { + std::string name; + SHMatrix transform; + }; + + struct RigNode + { + uint32_t idRef; + std::vector children; + }; + + struct SH_API SHRigAsset : SHAssetData + { + ~SHRigAsset() + { + delete root; + } + + std::map nodeDataCollection; + RigNode* root; + }; +} diff --git a/SHADE_Engine/src/Assets/Asset Types/SHAnimationAsset.h b/SHADE_Engine/src/Assets/Asset Types/SHAnimationAsset.h deleted file mode 100644 index b411a11e..00000000 --- a/SHADE_Engine/src/Assets/Asset Types/SHAnimationAsset.h +++ /dev/null @@ -1,30 +0,0 @@ -/*************************************************************************//** - * \file SHAnimationAsset.h - * \author Loh Xiao Qi - * \date October 2022 - * \brief - * - * Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or - * disclosure of this file or its contents without the prior written consent - * of DigiPen Institute of Technology is prohibited. - *****************************************************************************/ -#pragma once - -#include -#include -#include "SHAssetData.h" - -namespace SHADE -{ - struct SH_API SHAnimationAsset : SHAssetData - { - std::string name; - - std::vector nodeChannels; - std::vector meshChannels; - std::vector morphMeshChannels; - - double duration; - double ticksPerSecond; - }; -} \ No newline at end of file diff --git a/SHADE_Engine/src/Assets/Asset Types/SHAssetIncludes.h b/SHADE_Engine/src/Assets/Asset Types/SHAssetIncludes.h index f4fb49f0..eb656516 100644 --- a/SHADE_Engine/src/Assets/Asset Types/SHAssetIncludes.h +++ b/SHADE_Engine/src/Assets/Asset Types/SHAssetIncludes.h @@ -1,4 +1,4 @@ #pragma once -#include "SHModelAsset.h" +#include "Models/SHModelAsset.h" #include "SHTextureAsset.h" \ No newline at end of file diff --git a/SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.cpp b/SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.cpp index 74aa5350..1f825a97 100644 --- a/SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.cpp +++ b/SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.cpp @@ -24,7 +24,7 @@ namespace SHADE ); } - void SHModelLoader::ReadData(std::ifstream& file, SHMeshLoaderHeader const& header, SHMeshData& data) noexcept + void SHModelLoader::ReadData(std::ifstream& file, SHMeshLoaderHeader const& header, SHMeshAsset& data) noexcept { auto const vertexVec3Byte{ sizeof(SHVec3) * header.vertexCount }; auto const vertexVec2Byte{ sizeof(SHVec2) * header.vertexCount }; @@ -64,13 +64,13 @@ namespace SHADE ); std::vector headers(model.header.meshCount); - model.subMeshes.resize(model.header.meshCount); + model.meshes.resize(model.header.meshCount); for (auto i{ 0 }; i < model.header.meshCount; ++i) { - model.subMeshes[i] = new SHMeshData(); + model.meshes[i] = new SHMeshAsset(); ReadHeader(file, headers[i]); - ReadData(file, headers[i], *model.subMeshes[i]); + ReadData(file, headers[i], *model.meshes[i]); } file.close(); } diff --git a/SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.h b/SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.h index 35dc4514..2074169c 100644 --- a/SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.h +++ b/SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.h @@ -10,7 +10,7 @@ * of DigiPen Institute of Technology is prohibited. *****************************************************************************/ #pragma once -#include "Assets/Asset Types/SHModelAsset.h" +#include "Assets/Asset Types/Models/SHModelAsset.h" #include "SHAssetLoader.h" #include @@ -27,7 +27,7 @@ namespace SHADE void ReadHeader(std::ifstream& file, SHMeshLoaderHeader& header) noexcept; - void ReadData(std::ifstream& file, SHMeshLoaderHeader const& header, SHMeshData& data) noexcept; + void ReadData(std::ifstream& file, SHMeshLoaderHeader const& header, SHMeshAsset& data) noexcept; public: void LoadSHMesh(AssetPath path, SHModelAsset& model) noexcept; diff --git a/SHADE_Engine/src/Assets/SHAssetManager.cpp b/SHADE_Engine/src/Assets/SHAssetManager.cpp index 1569a13c..2695b2fa 100644 --- a/SHADE_Engine/src/Assets/SHAssetManager.cpp +++ b/SHADE_Engine/src/Assets/SHAssetManager.cpp @@ -530,7 +530,7 @@ namespace SHADE { assetData.emplace( parent.subAssets[i]->id, - parentModel->subMeshes[i] + parentModel->meshes[i] ); } } @@ -588,7 +588,7 @@ namespace SHADE SHModelAsset* const data = reinterpret_cast(LoadData(newAsset)); assetData.emplace(newAsset.id, data); - for(auto const& subMesh : data->subMeshes) + for(auto const& subMesh : data->meshes) { SHAsset subAsset{ .name = subMesh->header.name, diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp index 60262607..392590ed 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp @@ -14,7 +14,7 @@ of DigiPen Institute of Technology is prohibited. // STL Includes #include // Project Includes -#include "Assets/Asset Types/SHModelAsset.h" +#include "Assets/Asset Types/Models/SHModelAsset.h" #include "../Meshes/SHPrimitiveGenerator.h" #include "ECS_Base/Managers/SHSystemManager.h" #include "SHGraphicsSystem.h" @@ -321,7 +321,7 @@ namespace SHADE { spherePoints.clear(); // Generate - static const SHMeshData SPHERE = SHPrimitiveGenerator::Sphere(); + static const SHMeshAsset SPHERE = SHPrimitiveGenerator::Sphere(); for (const auto& idx : SPHERE.Indices) { spherePoints.emplace_back(SPHERE.VertexPositions[idx] * radius + pos); diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp index 60decded..bf60c7df 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp @@ -21,15 +21,15 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ /* Static Member Definitions */ /*-----------------------------------------------------------------------------------*/ - SHMeshData SHPrimitiveGenerator::cubeMesh; - SHMeshData SHPrimitiveGenerator::sphereMesh; + SHMeshAsset SHPrimitiveGenerator::cubeMesh; + SHMeshAsset SHPrimitiveGenerator::sphereMesh; /*-----------------------------------------------------------------------------------*/ /* Primitive Generation Functions */ /*-----------------------------------------------------------------------------------*/ - SHMeshData SHPrimitiveGenerator::Cube() noexcept + SHMeshAsset SHPrimitiveGenerator::Cube() noexcept { - SHMeshData mesh; + SHMeshAsset mesh; mesh.VertexPositions = { @@ -214,9 +214,9 @@ namespace SHADE return addMeshDataTo(cubeMesh, gfxSystem); } - SHADE::SHMeshData SHPrimitiveGenerator::Sphere() noexcept + SHADE::SHMeshAsset SHPrimitiveGenerator::Sphere() noexcept { - SHMeshData meshData; + SHMeshAsset meshData; const int LAT_SLICES = 12; const int LONG_SLICES = 12; @@ -284,7 +284,7 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ /* Helper Functions */ /*-----------------------------------------------------------------------------------*/ - SHADE::Handle SHPrimitiveGenerator::addMeshDataTo(const SHMeshData& meshData, SHMeshLibrary& meshLibrary) noexcept + SHADE::Handle SHPrimitiveGenerator::addMeshDataTo(const SHMeshAsset& meshData, SHMeshLibrary& meshLibrary) noexcept { return meshLibrary.AddMesh ( @@ -298,7 +298,7 @@ namespace SHADE ); } - SHADE::Handle SHPrimitiveGenerator::addMeshDataTo(const SHMeshData& meshData, SHGraphicsSystem& gfxSystem) noexcept + SHADE::Handle SHPrimitiveGenerator::addMeshDataTo(const SHMeshAsset& meshData, SHGraphicsSystem& gfxSystem) noexcept { return gfxSystem.AddMesh ( diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.h b/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.h index 7719e4c3..7008ebb1 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.h @@ -13,7 +13,7 @@ of DigiPen Institute of Technology is prohibited. // Project Includes #include "Math/SHMath.h" -#include "Assets/Asset Types/SHModelAsset.h" +#include "Assets/Asset Types/Models/SHModelAsset.h" #include "Graphics/MiddleEnd/Interface/SHMeshLibrary.h" #include "SH_API.h" @@ -47,13 +47,13 @@ namespace SHADE /***********************************************************************************/ /*! \brief - Produces a cube and stores the data in a SHMeshData object. + Produces a cube and stores the data in a SHMeshAsset object. \return - SHMeshData object containing vertex data for the cube. + SHMeshAsset object containing vertex data for the cube. */ /***********************************************************************************/ - [[nodiscard]] static SHMeshData Cube() noexcept; + [[nodiscard]] static SHMeshAsset Cube() noexcept; /***********************************************************************************/ /*! \brief @@ -83,13 +83,13 @@ namespace SHADE /***********************************************************************************/ /*! \brief - Produces a sphere and stores the data in a SHMeshData object. + Produces a sphere and stores the data in a SHMeshAsset object. \return - SHMeshData object containing vertex data for the sphere. + SHMeshAsset object containing vertex data for the sphere. */ /***********************************************************************************/ - [[nodiscard]] static SHMeshData Sphere() noexcept; + [[nodiscard]] static SHMeshAsset Sphere() noexcept; /***********************************************************************************/ /*! \brief @@ -121,13 +121,13 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ /* Helper Functions */ /*---------------------------------------------------------------------------------*/ - static Handle addMeshDataTo(const SHMeshData& meshData, SHMeshLibrary& meshLibrary) noexcept; - static Handle addMeshDataTo(const SHMeshData& meshData, SHGraphicsSystem& gfxSystem) noexcept; + static Handle addMeshDataTo(const SHMeshAsset& meshData, SHMeshLibrary& meshLibrary) noexcept; + static Handle addMeshDataTo(const SHMeshAsset& meshData, SHGraphicsSystem& gfxSystem) noexcept; /*---------------------------------------------------------------------------------*/ /* Data Members */ /*---------------------------------------------------------------------------------*/ - static SHMeshData cubeMesh; - static SHMeshData sphereMesh; + static SHMeshAsset cubeMesh; + static SHMeshAsset sphereMesh; }; } \ No newline at end of file diff --git a/SHADE_Engine/src/Resource/SHResourceManager.h b/SHADE_Engine/src/Resource/SHResourceManager.h index dae20f99..e008afad 100644 --- a/SHADE_Engine/src/Resource/SHResourceManager.h +++ b/SHADE_Engine/src/Resource/SHResourceManager.h @@ -17,7 +17,7 @@ of DigiPen Institute of Technology is prohibited. #include "SH_API.h" #include "SHResourceLibrary.h" #include "Assets/SHAssetMacros.h" -#include "Assets/Asset Types/SHModelAsset.h" +#include "Assets/Asset Types/Models/SHModelAsset.h" #include "Assets/Asset Types/SHTextureAsset.h" #include "Assets/Asset Types/SHShaderAsset.h" #include "Graphics/Shaders/SHVkShaderModule.h" @@ -42,7 +42,7 @@ namespace SHADE /// template struct SHResourceLoader { using AssetType = void; }; - template<> struct SHResourceLoader { using AssetType = SHMeshData; }; + template<> struct SHResourceLoader { using AssetType = SHMeshAsset; }; template<> struct SHResourceLoader { using AssetType = SHTextureAsset; }; template<> struct SHResourceLoader { using AssetType = SHShaderAsset; }; template<> struct SHResourceLoader { using AssetType = SHMaterialAsset; }; From 6fa14aff8597debee5020795b5f18d5d7d90abc8 Mon Sep 17 00:00:00 2001 From: Xiao Qi Date: Sat, 19 Nov 2022 21:27:30 +0800 Subject: [PATCH 002/275] Fixed sizeof bug when reading old model header --- SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.cpp b/SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.cpp index 1f825a97..cd81a30a 100644 --- a/SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.cpp +++ b/SHADE_Engine/src/Assets/Libraries/Loaders/SHModelLoader.cpp @@ -58,9 +58,10 @@ namespace SHADE file.seekg(0); + //TODO Update to new mesh header with anim count when animation saving is done file.read( - reinterpret_cast(&model.header), - sizeof(SHModelAssetHeader) + reinterpret_cast(&model.header.meshCount), + sizeof(model.header.meshCount) ); std::vector headers(model.header.meshCount); From 841948b82cd56821036f2ccd115bc412e48d8d59 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Sun, 20 Nov 2022 01:33:55 +0800 Subject: [PATCH 003/275] Added support for Bone Weights and Bone Indices vertex attributes for meshes --- Assets/Shaders/DefaultSkinned_VS.glsl | 65 +++ .../Assets/Asset Types/Models/SHMeshAsset.h | 14 +- .../MiddleEnd/Interface/SHGraphicsConstants.h | 14 + .../MiddleEnd/Interface/SHGraphicsSystem.cpp | 11 +- .../MiddleEnd/Interface/SHGraphicsSystem.h | 2 +- .../MiddleEnd/Interface/SHMeshLibrary.cpp | 400 ++++++++++-------- .../MiddleEnd/Interface/SHMeshLibrary.h | 334 ++++++++------- .../MiddleEnd/Meshes/SHPrimitiveGenerator.cpp | 8 +- 8 files changed, 505 insertions(+), 343 deletions(-) create mode 100644 Assets/Shaders/DefaultSkinned_VS.glsl diff --git a/Assets/Shaders/DefaultSkinned_VS.glsl b/Assets/Shaders/DefaultSkinned_VS.glsl new file mode 100644 index 00000000..22199ff5 --- /dev/null +++ b/Assets/Shaders/DefaultSkinned_VS.glsl @@ -0,0 +1,65 @@ +#version 450 +#extension GL_KHR_vulkan_glsl : enable + +//#include "ShaderDescriptorDefinitions.glsl" + + +layout(location = 0) in vec3 aVertexPos; +layout(location = 1) in vec2 aUV; +layout(location = 2) in vec3 aNormal; +layout(location = 3) in vec3 aTangent; +layout(location = 4) in mat4 worldTransform; +layout(location = 5) in ivec4 aBoneIndices; +layout(location = 6) in vec4 aBoneWeights; +layout(location = 8) in uvec2 integerData; + + +layout(location = 0) out struct +{ + vec4 vertPos; // location 0 + vec2 uv; // location = 1 + vec4 normal; // location = 2 + +} Out; + +// material stuff +layout(location = 3) out struct +{ + int materialIndex; + uint eid; + uint lightLayerIndex; + +} Out2; + +layout(set = 2, binding = 0) uniform CameraData +{ + vec4 position; + mat4 vpMat; + mat4 viewMat; + mat4 projMat; +} cameraData; + +void main() +{ + Out2.materialIndex = gl_InstanceIndex; + Out2.eid = integerData[0]; + Out2.lightLayerIndex = integerData[1]; + + // for transforming gBuffer position and normal data + mat4 modelViewMat = cameraData.viewMat * worldTransform; + + // gBuffer position will be in view space + Out.vertPos = modelViewMat * vec4(aVertexPos, 1.0f); + + // uvs for texturing in fragment shader + Out.uv = aUV; + + mat3 transposeInv = mat3 (transpose(inverse(modelViewMat))); + + // normals are also in view space + Out.normal.rgb = transposeInv * aNormal.rgb; + Out.normal.rgb = normalize (Out.normal.rgb); + + // clip space for rendering + gl_Position = cameraData.vpMat * worldTransform * vec4 (aVertexPos, 1.0f); +} \ No newline at end of file diff --git a/SHADE_Engine/src/Assets/Asset Types/Models/SHMeshAsset.h b/SHADE_Engine/src/Assets/Asset Types/Models/SHMeshAsset.h index c10e0cc3..e8780669 100644 --- a/SHADE_Engine/src/Assets/Asset Types/Models/SHMeshAsset.h +++ b/SHADE_Engine/src/Assets/Asset Types/Models/SHMeshAsset.h @@ -48,13 +48,13 @@ namespace SHADE struct SH_API SHMeshAsset : SHAssetData { - SHMeshDataHeader header; - std::vector VertexPositions; - std::vector VertexTangents; - std::vector VertexNormals; - std::vector VertexTexCoords; + SHMeshDataHeader header; + std::vector VertexPositions; + std::vector VertexTangents; + std::vector VertexNormals; + std::vector VertexTexCoords; std::vector Indices; - std::vector BonesInfo; - std::vector Bones; + std::vector VertexBoneIndices; + std::vector VertexBoneWeights; }; } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h index 0a67cd9f..b1da9242 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h @@ -176,6 +176,20 @@ namespace SHADE */ /***************************************************************************/ static constexpr uint32_t INTEGER_DATA = 5; + /***************************************************************************/ + /*! + \brief + Vertex buffer bindings for the bone indices buffer. + */ + /***************************************************************************/ + static constexpr uint32_t BONE_INDICES = 6; + /***************************************************************************/ + /*! + \brief + Vertex buffer bindings for the bone weights buffer. + */ + /***************************************************************************/ + static constexpr uint32_t BONE_WEIGHTS = 7; }; diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp index 5b40a745..8f6ad9c9 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp @@ -432,6 +432,8 @@ namespace SHADE std::make_pair(meshLibrary.GetVertexTexCoordsBuffer(), SHGraphicsConstants::VertexBufferBindings::TEX_COORD), std::make_pair(meshLibrary.GetVertexNormalsBuffer(), SHGraphicsConstants::VertexBufferBindings::NORMAL), std::make_pair(meshLibrary.GetVertexTangentsBuffer(), SHGraphicsConstants::VertexBufferBindings::TANGENT), + std::make_pair(meshLibrary.GetVertexBoneIndicesBuffer(), SHGraphicsConstants::VertexBufferBindings::BONE_INDICES), + std::make_pair(meshLibrary.GetVertexBoneWeightsBuffer(), SHGraphicsConstants::VertexBufferBindings::BONE_WEIGHTS), std::make_pair(meshLibrary.GetIndexBuffer(), 0), }; @@ -483,6 +485,11 @@ namespace SHADE // Bind all the buffers required for meshes for (auto& [buffer, bindingPoint] : MESH_DATA) { + // Ignore invalid buffers + if (!buffer) + continue; + + // Assign based on type if (buffer->GetUsageBits() & vk::BufferUsageFlagBits::eVertexBuffer) currentCmdBuffer->BindVertexBuffer(bindingPoint, buffer, 0); else if (buffer->GetUsageBits() & vk::BufferUsageFlagBits::eIndexBuffer) @@ -721,9 +728,9 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ /* Mesh Registration Functions */ /*---------------------------------------------------------------------------------*/ - SHADE::Handle SHGraphicsSystem::AddMesh(uint32_t vertexCount, const SHMesh::VertexPosition* const positions, const SHMesh::VertexTexCoord* const texCoords, const SHMesh::VertexTangent* const tangents, const SHMesh::VertexNormal* const normals, uint32_t indexCount, const SHMesh::Index* const indices) + SHADE::Handle SHGraphicsSystem::AddMesh(uint32_t vertexCount, const SHMesh::VertexPosition* const positions, const SHMesh::VertexTexCoord* const texCoords, const SHMesh::VertexTangent* const tangents, const SHMesh::VertexNormal* const normals, uint32_t indexCount, const SHMesh::Index* const indices, const SHMesh::VertexBoneIndices* const boneIndices, const SHMesh::VertexWeights* const boneWeights) { - return meshLibrary.AddMesh(vertexCount, positions, texCoords, tangents, normals, indexCount, indices); + return meshLibrary.AddMesh(vertexCount, positions, texCoords, tangents, normals, boneIndices, boneWeights, indexCount, indices); } void SHGraphicsSystem::RemoveMesh(Handle mesh) diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h index 2a186041..d7e43aea 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h @@ -204,7 +204,7 @@ namespace SHADE */ /*******************************************************************************/ - Handle AddMesh(uint32_t vertexCount, const SHMesh::VertexPosition* const positions, const SHMesh::VertexTexCoord* const texCoords, const SHMesh::VertexTangent* const tangents, const SHMesh::VertexNormal* const normals, uint32_t indexCount, const SHMesh::Index* const indices); + Handle AddMesh(uint32_t vertexCount, const SHMesh::VertexPosition* const positions, const SHMesh::VertexTexCoord* const texCoords, const SHMesh::VertexTangent* const tangents, const SHMesh::VertexNormal* const normals, uint32_t indexCount, const SHMesh::Index* const indices, const SHMesh::VertexBoneIndices* const boneIndices = nullptr, const SHMesh::VertexWeights* const boneWeights = nullptr); /*******************************************************************************/ /*! diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMeshLibrary.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMeshLibrary.cpp index d34c1b7d..dda5d423 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMeshLibrary.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMeshLibrary.cpp @@ -3,7 +3,7 @@ \author Tng Kah Wei, kahwei.tng, 390009620 \par email: kahwei.tng\@digipen.edu \date Aug 30, 2022 -\brief Contains definitions for all of the functions of the classes that deal +\brief Contains definitions for all of the functions of the classes that deal with storage and management of vertex and index buffers of meshes. Copyright (C) 2022 DigiPen Institute of Technology. @@ -20,187 +20,239 @@ of DigiPen Institute of Technology is prohibited. namespace SHADE { - SHADE::Handle SHMeshLibrary::AddMesh(uint32_t vertexCount, const SHMesh::VertexPosition* const positions, const SHMesh::VertexTexCoord* const texCoords, const SHMesh::VertexTangent* const tangents, const SHMesh::VertexNormal* const normals, uint32_t indexCount, const SHMesh::Index* const indices) - { - isDirty = true; + SHADE::Handle SHMeshLibrary::AddMesh + ( + uint32_t vertexCount, const SHMesh::VertexPosition* const positions, + const SHMesh::VertexTexCoord* const texCoords, + const SHMesh::VertexTangent* const tangents, + const SHMesh::VertexNormal* const normals, + const SHMesh::VertexBoneIndices* const boneIndices, + const SHMesh::VertexWeights* const boneWeights, + uint32_t indexCount, const SHMesh::Index* const indices) + { + isDirty = true; - auto handle = meshes.Create(); - meshAddJobs.emplace_back( MeshAddJob - { - vertexCount, - positions, - texCoords, - tangents, - normals, - indexCount, - indices, - handle - }); - return handle; - } - - void SHMeshLibrary::RemoveMesh(Handle mesh) + auto handle = meshes.Create(); + meshAddJobs.emplace_back(MeshAddJob { - if (!mesh) - throw std::invalid_argument("Attempted to remove a Mesh that did not belong to the Mesh Library!"); - - meshRemoveJobs.emplace_back(mesh); - isDirty = true; + vertexCount, + positions, + texCoords, + tangents, + normals, + boneIndices, + boneWeights, + indexCount, + indices, + handle + }); + return handle; + } + + void SHMeshLibrary::RemoveMesh(Handle mesh) + { + if (!mesh) + throw std::invalid_argument("Attempted to remove a Mesh that did not belong to the Mesh Library!"); + + meshRemoveJobs.emplace_back(mesh); + isDirty = true; + } + + void SHMeshLibrary::BuildBuffers(Handle device, Handle cmdBuffer) + { + // No changes + if (!isDirty) + return; + + // Remove + if (!meshRemoveJobs.empty()) + { + // - Remove from order list + for (const auto& meshToRemove : meshRemoveJobs) + { + auto searchResult = std::find(meshOrder.begin(), meshOrder.end(), meshToRemove); + // Shouldn't happen, ignore + if (searchResult == meshOrder.end()) + continue; + + // Remove from mesh list + meshOrder.erase(searchResult); + } + meshRemoveJobs.clear(); + // - Shift existing elements in to close up the gaps + int32_t nextVertInsertPoint = 0; + uint32_t nextIdxInsertPoint = 0; + for (auto& mesh : meshOrder) + { + // Check if already in the correct place + if (nextVertInsertPoint != mesh->FirstVertex) + { + /* There's a gap, we need to shift */ + // Vertices + vertPosStorage.erase(vertPosStorage.begin() + nextVertInsertPoint, vertPosStorage.begin() + mesh->FirstVertex); + vertTexCoordStorage.erase(vertTexCoordStorage.begin() + nextVertInsertPoint, vertTexCoordStorage.begin() + mesh->FirstVertex); + vertTangentStorage.erase(vertTangentStorage.begin() + nextVertInsertPoint, vertTangentStorage.begin() + mesh->FirstVertex); + vertNormalStorage.erase(vertNormalStorage.begin() + nextVertInsertPoint, vertNormalStorage.begin() + mesh->FirstVertex); + vertBoneIdxStorage.erase(vertBoneIdxStorage.begin() + nextVertInsertPoint, vertBoneIdxStorage.begin() + mesh->FirstVertex); + vertBoneWeightStorage.erase(vertBoneWeightStorage.begin() + nextVertInsertPoint, vertBoneWeightStorage.begin() + mesh->FirstVertex); + // - Update mesh data + mesh->FirstVertex = nextVertInsertPoint; + + // Indices + indexStorage.erase(indexStorage.begin() + nextIdxInsertPoint, indexStorage.begin() + mesh->FirstIndex); + // - Update mesh data + mesh->FirstIndex = nextIdxInsertPoint; + + // Prepare for next + nextVertInsertPoint += mesh->VertexCount; + nextIdxInsertPoint += mesh->IndexCount; + } + } } - void SHMeshLibrary::BuildBuffers(Handle device, Handle cmdBuffer) + // Add + if (!meshAddJobs.empty()) { - // No changes - if (!isDirty) - return; - - // Remove - if (!meshRemoveJobs.empty()) + // - Compute updated size + size_t newVertElems = vertPosStorage.size(); + size_t newIdxElems = indexStorage.size(); + for (const auto& addJob : meshAddJobs) + { + newVertElems += addJob.VertexCount; + newIdxElems += addJob.IndexCount; + } + // - Reserve new memory + vertPosStorage.reserve(newVertElems); + vertTexCoordStorage.reserve(newVertElems); + vertTangentStorage.reserve(newVertElems); + vertNormalStorage.reserve(newVertElems); + vertBoneIdxStorage.reserve(newVertElems); + vertBoneWeightStorage.reserve(newVertElems); + indexStorage.reserve(newIdxElems); + // - Append new data + for (auto& addJob : meshAddJobs) + { + // Update handle + SHMesh& meshData = *addJob.Handle; + meshData = SHMesh { - // - Remove from order list - for (const auto& meshToRemove : meshRemoveJobs) - { - auto searchResult = std::find(meshOrder.begin(), meshOrder.end(), meshToRemove); - // Shouldn't happen, ignore - if (searchResult == meshOrder.end()) - continue; + .FirstVertex = static_cast(vertPosStorage.size()), + .VertexCount = static_cast(addJob.VertexCount), + .FirstIndex = static_cast(indexStorage.size()), + .IndexCount = static_cast(addJob.IndexCount), + }; - // Remove from mesh list - meshOrder.erase(searchResult); - } - meshRemoveJobs.clear(); - // - Shift existing elements in to close up the gaps - int32_t nextVertInsertPoint = 0; - uint32_t nextIdxInsertPoint = 0; - for (auto& mesh : meshOrder) - { - // Check if already in the correct place - if (nextVertInsertPoint != mesh->FirstVertex) - { - /* There's a gap, we need to shift */ - // Vertices - vertPosStorage.erase(vertPosStorage.begin() + nextVertInsertPoint, vertPosStorage.begin() + mesh->FirstVertex); - vertTexCoordStorage.erase(vertTexCoordStorage.begin() + nextVertInsertPoint, vertTexCoordStorage.begin() + mesh->FirstVertex); - vertTangentStorage.erase(vertTangentStorage.begin() + nextVertInsertPoint, vertTangentStorage.begin() + mesh->FirstVertex); - vertNormalStorage.erase(vertNormalStorage.begin() + nextVertInsertPoint, vertNormalStorage.begin() + mesh->FirstVertex); - // - Update mesh data - mesh->FirstVertex = nextVertInsertPoint; - - // Indices - indexStorage.erase(indexStorage.begin() + nextIdxInsertPoint, indexStorage.begin() + mesh->FirstIndex); - // - Update mesh data - mesh->FirstIndex = nextIdxInsertPoint; - - // Prepare for next - nextVertInsertPoint += mesh->VertexCount; - nextIdxInsertPoint += mesh->IndexCount; - } - } - } - - // Add - if (!meshAddJobs.empty()) + // Copy into storage + vertPosStorage.insert + ( + vertPosStorage.end(), + addJob.VertexPositions, addJob.VertexPositions + addJob.VertexCount + ); + vertTexCoordStorage.insert + ( + vertTexCoordStorage.end(), + addJob.VertexTexCoords, addJob.VertexTexCoords + addJob.VertexCount + ); + vertTangentStorage.insert + ( + vertTangentStorage.end(), + addJob.VertexTangents, addJob.VertexTangents + addJob.VertexCount + ); + vertNormalStorage.insert + ( + vertNormalStorage.end(), + addJob.VertexNormals, addJob.VertexNormals + addJob.VertexCount + ); + if (addJob.VertexBoneIndices) { - // - Compute updated size - size_t newVertElems = vertPosStorage.size(); - size_t newIdxElems = indexStorage.size(); - for (const auto& addJob : meshAddJobs) - { - newVertElems += addJob.VertexCount; - newIdxElems += addJob.IndexCount; - } - // - Reserve new memory - vertPosStorage .reserve(newVertElems); - vertTexCoordStorage.reserve(newVertElems); - vertTangentStorage .reserve(newVertElems); - vertNormalStorage .reserve(newVertElems); - indexStorage .reserve(newIdxElems); - // - Append new data - for (auto& addJob : meshAddJobs) - { - // Update handle - SHMesh& meshData = *addJob.Handle; - meshData = SHMesh - { - .FirstVertex = static_cast(vertPosStorage.size()), - .VertexCount = static_cast(addJob.VertexCount), - .FirstIndex = static_cast(indexStorage.size()), - .IndexCount = static_cast(addJob.IndexCount), - }; - - // Copy into storage - vertPosStorage.insert - ( - vertPosStorage.end(), - addJob.VertexPositions, addJob.VertexPositions + addJob.VertexCount - ); - vertTexCoordStorage.insert - ( - vertTexCoordStorage.end(), - addJob.VertexTexCoords, addJob.VertexTexCoords + addJob.VertexCount - ); - vertTangentStorage.insert - ( - vertTangentStorage.end(), - addJob.VertexTangents, addJob.VertexTangents + addJob.VertexCount - ); - vertNormalStorage.insert - ( - vertNormalStorage.end(), - addJob.VertexNormals, addJob.VertexNormals + addJob.VertexCount - ); - indexStorage.insert - ( - indexStorage.end(), - addJob.Indices, addJob.Indices + addJob.IndexCount - ); - } - meshAddJobs.clear(); + vertBoneIdxStorage.insert + ( + vertBoneIdxStorage.end(), + addJob.VertexBoneIndices, addJob.VertexBoneIndices + addJob.VertexCount * SHMesh::BONE_INDICES_PER_VERTEX + ); } - - // Send to GPU - using BuffUsage = vk::BufferUsageFlagBits; - SHVkUtil::EnsureBufferAndCopyData - ( - device, cmdBuffer, vertPosBuffer, - vertPosStorage.data(), - static_cast(vertPosStorage.size()) * sizeof(SHMesh::VertexPosition), - BuffUsage::eVertexBuffer, - "Mesh Library Vertex Positions" - ); - SHVkUtil::EnsureBufferAndCopyData - ( - device, cmdBuffer, vertTexCoordBuffer, - vertTexCoordStorage.data(), - static_cast(vertTexCoordStorage.size()) * sizeof(SHMesh::VertexTexCoord), - BuffUsage::eVertexBuffer, - "Mesh Library Vertex TexCoords" - ); - SHVkUtil::EnsureBufferAndCopyData - ( - device, cmdBuffer, vertTangentBuffer, - vertTangentStorage.data(), - static_cast(vertTangentStorage.size()) * sizeof(SHMesh::VertexTangent), - BuffUsage::eVertexBuffer, - "Mesh Library Vertex Tangents" - ); - SHVkUtil::EnsureBufferAndCopyData - ( - device, cmdBuffer, vertNormalBuffer, - vertNormalStorage.data(), - static_cast(vertNormalStorage.size()) * sizeof(SHMesh::VertexNormal), - BuffUsage::eVertexBuffer, - "Mesh Library Vertex Normals" - ); - SHVkUtil::EnsureBufferAndCopyData - ( - device, cmdBuffer, indexBuffer, - indexStorage.data(), - static_cast(indexStorage.size()) * sizeof(SHMesh::Index), - BuffUsage::eIndexBuffer, - "Mesh Library Indices" - ); - - isDirty = false; + if (addJob.VertexBoneWeights) + { + vertBoneWeightStorage.insert + ( + vertBoneWeightStorage.end(), + addJob.VertexBoneWeights, addJob.VertexBoneWeights + addJob.VertexCount + ); + } + indexStorage.insert + ( + indexStorage.end(), + addJob.Indices, addJob.Indices + addJob.IndexCount + ); + } + meshAddJobs.clear(); } + + // Send to GPU + using BuffUsage = vk::BufferUsageFlagBits; + SHVkUtil::EnsureBufferAndCopyData + ( + device, cmdBuffer, vertPosBuffer, + vertPosStorage.data(), + static_cast(vertPosStorage.size()) * sizeof(SHMesh::VertexPosition), + BuffUsage::eVertexBuffer, + "Mesh Library Vertex Positions" + ); + SHVkUtil::EnsureBufferAndCopyData + ( + device, cmdBuffer, vertTexCoordBuffer, + vertTexCoordStorage.data(), + static_cast(vertTexCoordStorage.size()) * sizeof(SHMesh::VertexTexCoord), + BuffUsage::eVertexBuffer, + "Mesh Library Vertex TexCoords" + ); + SHVkUtil::EnsureBufferAndCopyData + ( + device, cmdBuffer, vertTangentBuffer, + vertTangentStorage.data(), + static_cast(vertTangentStorage.size()) * sizeof(SHMesh::VertexTangent), + BuffUsage::eVertexBuffer, + "Mesh Library Vertex Tangents" + ); + SHVkUtil::EnsureBufferAndCopyData + ( + device, cmdBuffer, vertNormalBuffer, + vertNormalStorage.data(), + static_cast(vertNormalStorage.size()) * sizeof(SHMesh::VertexNormal), + BuffUsage::eVertexBuffer, + "Mesh Library Vertex Normals" + ); + if (!vertBoneIdxStorage.empty()) + { + SHVkUtil::EnsureBufferAndCopyData + ( + device, cmdBuffer, vertBoneIdxBuffer, + vertBoneIdxStorage.data(), + static_cast(vertBoneIdxStorage.size()) * sizeof(SHMesh::VertexBoneIndices), + BuffUsage::eVertexBuffer, + "Mesh Library Vertex Bone Indices" + ); + } + if (!vertBoneWeightStorage.empty()) + { + SHVkUtil::EnsureBufferAndCopyData + ( + device, cmdBuffer, vertBoneWeightBuffer, + vertBoneWeightStorage.data(), + static_cast(vertBoneWeightStorage.size()) * sizeof(SHMesh::VertexWeights), + BuffUsage::eVertexBuffer, + "Mesh Library Vertex Bone Weights" + ); + } + SHVkUtil::EnsureBufferAndCopyData + ( + device, cmdBuffer, indexBuffer, + indexStorage.data(), + static_cast(indexStorage.size()) * sizeof(SHMesh::Index), + BuffUsage::eIndexBuffer, + "Mesh Library Indices" + ); + + isDirty = false; + } } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMeshLibrary.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMeshLibrary.h index b0cbdce1..f61e4a20 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMeshLibrary.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMeshLibrary.h @@ -19,170 +19,192 @@ of DigiPen Institute of Technology is prohibited. #include "Resource/SHResourceLibrary.h" #include "Math/Vector/SHVec2.h" #include "Math/Vector/SHVec3.h" +#include "Math/Vector/SHVec4.h" namespace SHADE { - /*---------------------------------------------------------------------------------*/ - /* Forward Declarations */ - /*---------------------------------------------------------------------------------*/ - class SHVkBuffer; - class SHVkLogicalDevice; - class SHVkCommandBuffer; + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHVkBuffer; + class SHVkLogicalDevice; + class SHVkCommandBuffer; - /*---------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*---------------------------------------------------------------------------------*/ - /***********************************************************************************/ + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /***********************************************************************************/ + /*! + \brief + Represents a single mesh that is stored in a SHMeshLibrary. + */ + /***********************************************************************************/ + class SHMesh + { + public: + /*-----------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------*/ + using Index = uint32_t; + using VertexPosition = SHVec3; + using VertexTexCoord = SHVec2; + using VertexTangent = SHVec3; + using VertexNormal = SHVec3; + using VertexBoneIndices = int; + using VertexWeights = SHVec4; + + /*-----------------------------------------------------------------------------*/ + /* Constants */ + /*-----------------------------------------------------------------------------*/ + static constexpr size_t BONE_INDICES_PER_VERTEX = 4; + + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + int32_t FirstVertex; + uint32_t VertexCount; + uint32_t FirstIndex; + uint32_t IndexCount; + }; + /***********************************************************************************/ + /*! + \brief + Manages storage for all Meshes in the Graphics System as a single set of Vertex + and Index Buffers. + */ + /***********************************************************************************/ + class SHMeshLibrary + { + public: + /*-----------------------------------------------------------------------------*/ + /* Usage Functions */ + /*-----------------------------------------------------------------------------*/ + /*******************************************************************************/ /*! - \brief - Represents a single mesh that is stored in a SHMeshLibrary. - */ - /***********************************************************************************/ - class SHMesh - { - public: - /*-----------------------------------------------------------------------------*/ - /* Type Definitions */ - /*-----------------------------------------------------------------------------*/ - using Index = uint32_t; - using VertexPosition = SHVec3; - using VertexTexCoord = SHVec2; - using VertexTangent = SHVec3; - using VertexNormal = SHVec3; - /*-----------------------------------------------------------------------------*/ - /* Data Members */ - /*-----------------------------------------------------------------------------*/ - int32_t FirstVertex; - uint32_t VertexCount; - uint32_t FirstIndex; - uint32_t IndexCount; - }; - /***********************************************************************************/ + \brief + Adds a mesh to the Mesh Library. But this does not mean that the meshes have + been added yet. A call to "BuildBuffers()" is required to transfer all + meshes into the GPU. + + \param vertexCount + Number of vertices in this Mesh. + \param positions + Pointer to the first in a contiguous array of SHMathVec3s that define vertex + positions. + \param texCoords + Pointer to the first in a contiguous array of SHMathVec2s that define vertex + texture coordinates. + \param tangents + Pointer to the first in a contiguous array of SHMathVec3s that define vertex + tangents. + \param normals + Pointer to the first in a contiguous array of SHMathVec3s that define vertex + normals. + \param indexCount + Number of indices in this mesh. + \param indices + Pointer to the first in a contiguous array of uint32_ts that define mesh + indicies. + + \return + Handle to the created Mesh. This is not valid to be used until a call to + BuildBuffers(). + + */ + /*******************************************************************************/ + Handle AddMesh(uint32_t vertexCount, const SHMesh::VertexPosition* const positions, + const SHMesh::VertexTexCoord* const texCoords, + const SHMesh::VertexTangent* const tangents, + const SHMesh::VertexNormal* const normals, + const SHMesh::VertexBoneIndices* const boneIndices, + const SHMesh::VertexWeights* const boneWeights, + uint32_t indexCount, const SHMesh::Index* const indices); + /*******************************************************************************/ /*! - \brief - Manages storage for all Meshes in the Graphics System as a single set of Vertex - and Index Buffers. + + \brief + Removes a mesh from the MeshLibrary. But this does not mean that the meshes + have been removed yet. A call to "BuildBuffers()" is required to finalise all + changes. + + \param mesh + Handle to the mesh to remove. + */ - /***********************************************************************************/ - class SHMeshLibrary + /*******************************************************************************/ + void RemoveMesh(Handle mesh); + /***************************************************************************/ + /*! + + \brief + Finalises all changes to the MeshLibrary into the GPU buffers. + + \param device + Device used to create and update the buffers. + \param cmdBuffer + Command buffer used to set up transfers of data in the GPU memory. This + call must be preceded by calls to cmdBuffer's BeginRecording() and ended + with EndRecording(). Do recall to also submit the cmdBuffer to a transfer + queue. + */ + /***************************************************************************/ + void BuildBuffers(Handle device, Handle cmdBuffer); + + /*-----------------------------------------------------------------------------*/ + /* Getter Functions */ + /*-----------------------------------------------------------------------------*/ + Handle GetVertexPositionsBuffer() const noexcept { return vertPosBuffer; } + Handle GetVertexTexCoordsBuffer() const noexcept { return vertTexCoordBuffer; } + Handle GetVertexTangentsBuffer() const noexcept { return vertTangentBuffer; } + Handle GetVertexNormalsBuffer() const noexcept { return vertNormalBuffer; } + Handle GetVertexBoneIndicesBuffer() const noexcept { return vertBoneIdxBuffer; } + Handle GetVertexBoneWeightsBuffer() const noexcept { return vertBoneWeightBuffer; } + Handle GetIndexBuffer() const noexcept { return indexBuffer; } + + private: + /*-----------------------------------------------------------------------------*/ + /* Type Definition */ + /*-----------------------------------------------------------------------------*/ + struct MeshAddJob { - public: - /*-----------------------------------------------------------------------------*/ - /* Usage Functions */ - /*-----------------------------------------------------------------------------*/ - /*******************************************************************************/ - /*! - - \brief - Adds a mesh to the Mesh Library. But this does not mean that the meshes have - been added yet. A call to "BuildBuffers()" is required to transfer all - meshes into the GPU. - - \param vertexCount - Number of vertices in this Mesh. - \param positions - Pointer to the first in a contiguous array of SHMathVec3s that define vertex - positions. - \param texCoords - Pointer to the first in a contiguous array of SHMathVec2s that define vertex - texture coordinates. - \param tangents - Pointer to the first in a contiguous array of SHMathVec3s that define vertex - tangents. - \param normals - Pointer to the first in a contiguous array of SHMathVec3s that define vertex - normals. - \param indexCount - Number of indices in this mesh. - \param indices - Pointer to the first in a contiguous array of uint32_ts that define mesh - indicies. - - \return - Handle to the created Mesh. This is not valid to be used until a call to - BuildBuffers(). - - */ - /*******************************************************************************/ - Handle AddMesh(uint32_t vertexCount, const SHMesh::VertexPosition* const positions, const SHMesh::VertexTexCoord* const texCoords, const SHMesh::VertexTangent* const tangents, const SHMesh::VertexNormal* const normals, uint32_t indexCount, const SHMesh::Index* const indices); - /*******************************************************************************/ - /*! - - \brief - Removes a mesh from the MeshLibrary. But this does not mean that the meshes - have been removed yet. A call to "BuildBuffers()" is required to finalise all - changes. - - \param mesh - Handle to the mesh to remove. - - */ - /*******************************************************************************/ - void RemoveMesh(Handle mesh); - /***************************************************************************/ - /*! - - \brief - Finalises all changes to the MeshLibrary into the GPU buffers. - - \param device - Device used to create and update the buffers. - \param cmdBuffer - Command buffer used to set up transfers of data in the GPU memory. This - call must be preceded by calls to cmdBuffer's BeginRecording() and ended - with EndRecording(). Do recall to also submit the cmdBuffer to a transfer - queue. - */ - /***************************************************************************/ - void BuildBuffers(Handle device, Handle cmdBuffer); - - /*-----------------------------------------------------------------------------*/ - /* Getter Functions */ - /*-----------------------------------------------------------------------------*/ - Handle GetVertexPositionsBuffer() const noexcept { return vertPosBuffer; } - Handle GetVertexTexCoordsBuffer() const noexcept { return vertTexCoordBuffer; } - Handle GetVertexTangentsBuffer() const noexcept { return vertTangentBuffer; } - Handle GetVertexNormalsBuffer() const noexcept { return vertNormalBuffer; } - Handle GetIndexBuffer() const { return indexBuffer; } - - private: - /*-----------------------------------------------------------------------------*/ - /* Type Definition */ - /*-----------------------------------------------------------------------------*/ - struct MeshAddJob - { - uint32_t VertexCount = 0; - const SHMesh::VertexPosition* VertexPositions = nullptr; - const SHMesh::VertexTexCoord* VertexTexCoords = nullptr; - const SHMesh::VertexTangent * VertexTangents = nullptr; - const SHMesh::VertexNormal * VertexNormals = nullptr; - uint32_t IndexCount = 0; - const SHMesh::Index * Indices = nullptr; - Handle Handle; - }; - /*-----------------------------------------------------------------------------*/ - /* Data Members */ - /*-----------------------------------------------------------------------------*/ - // Manipulation Queues - std::vector meshAddJobs; - std::vector> meshRemoveJobs; - // Tracking - SHResourceLibrary meshes{}; - std::vector> meshOrder; - // CPU Storage - std::vector vertPosStorage; - std::vector vertTexCoordStorage; - std::vector vertTangentStorage; - std::vector vertNormalStorage; - std::vector indexStorage; - // GPU Storage - Handle vertPosBuffer{}; - Handle vertTexCoordBuffer{}; - Handle vertTangentBuffer{}; - Handle vertNormalBuffer{}; - Handle indexBuffer {}; - // Flags - bool isDirty = true; + uint32_t VertexCount = 0; + const SHMesh::VertexPosition* VertexPositions = nullptr; + const SHMesh::VertexTexCoord* VertexTexCoords = nullptr; + const SHMesh::VertexTangent* VertexTangents = nullptr; + const SHMesh::VertexNormal* VertexNormals = nullptr; + const SHMesh::VertexBoneIndices* VertexBoneIndices = nullptr; + const SHMesh::VertexWeights* VertexBoneWeights = nullptr; + uint32_t IndexCount = 0; + const SHMesh::Index* Indices = nullptr; + Handle Handle; }; + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + // Manipulation Queues + std::vector meshAddJobs; + std::vector> meshRemoveJobs; + // Tracking + SHResourceLibrary meshes; + std::vector> meshOrder; + // CPU Storage + std::vector vertPosStorage; + std::vector vertTexCoordStorage; + std::vector vertTangentStorage; + std::vector vertNormalStorage; + std::vector vertBoneIdxStorage; // Must be in multiples of 4 + std::vector vertBoneWeightStorage; + std::vector indexStorage; + // GPU Storage + Handle vertPosBuffer{}; + Handle vertTexCoordBuffer{}; + Handle vertTangentBuffer{}; + Handle vertNormalBuffer{}; + Handle vertBoneIdxBuffer{}; + Handle vertBoneWeightBuffer{}; + Handle indexBuffer{}; + // Flags + bool isDirty = true; + }; } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp index bf60c7df..c6200929 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp @@ -288,11 +288,13 @@ namespace SHADE { return meshLibrary.AddMesh ( - static_cast(meshData.VertexPositions.size()), + static_cast(meshData.VertexPositions.size()), meshData.VertexPositions.data(), meshData.VertexTexCoords.data(), meshData.VertexTangents.data(), meshData.VertexNormals.data(), + nullptr, + nullptr, static_cast(meshData.Indices.size()), meshData.Indices.data() ); @@ -302,12 +304,12 @@ namespace SHADE { return gfxSystem.AddMesh ( - static_cast(meshData.VertexPositions.size()), + static_cast(meshData.VertexPositions.size()), meshData.VertexPositions.data(), meshData.VertexTexCoords.data(), meshData.VertexTangents.data(), meshData.VertexNormals.data(), - static_cast(meshData.Indices.size()), + static_cast(meshData.Indices.size()), meshData.Indices.data() ); } From 2a6db58cd992f013d0f8ac014a157e9455a994fe Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Mon, 21 Nov 2022 00:22:46 +0800 Subject: [PATCH 004/275] Added SHRig and stubs for SHAnimatorComponent and SHAnimationSystem --- .../src/Animation/SHAnimationSystem.cpp | 20 ++++ .../src/Animation/SHAnimationSystem.h | 32 +++++++ .../src/Animation/SHAnimatorComponent.cpp | 28 ++++++ .../src/Animation/SHAnimatorComponent.h | 36 ++++++++ SHADE_Engine/src/Animation/SHRig.cpp | 89 ++++++++++++++++++ SHADE_Engine/src/Animation/SHRig.h | 91 +++++++++++++++++++ 6 files changed, 296 insertions(+) create mode 100644 SHADE_Engine/src/Animation/SHAnimationSystem.cpp create mode 100644 SHADE_Engine/src/Animation/SHAnimationSystem.h create mode 100644 SHADE_Engine/src/Animation/SHAnimatorComponent.cpp create mode 100644 SHADE_Engine/src/Animation/SHAnimatorComponent.h create mode 100644 SHADE_Engine/src/Animation/SHRig.cpp create mode 100644 SHADE_Engine/src/Animation/SHRig.h diff --git a/SHADE_Engine/src/Animation/SHAnimationSystem.cpp b/SHADE_Engine/src/Animation/SHAnimationSystem.cpp new file mode 100644 index 00000000..28b1a80e --- /dev/null +++ b/SHADE_Engine/src/Animation/SHAnimationSystem.cpp @@ -0,0 +1,20 @@ +/************************************************************************************//*! +\file SHAnimationSystem.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 20, 2022 +\brief Contains the function definitions of the SHAnimationSystem class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Pre-compiled Header +#include "SHpch.h" +// Primary Include +#include "SHAnimationSystem.h" + +namespace SHADE +{ + +} diff --git a/SHADE_Engine/src/Animation/SHAnimationSystem.h b/SHADE_Engine/src/Animation/SHAnimationSystem.h new file mode 100644 index 00000000..eac839ad --- /dev/null +++ b/SHADE_Engine/src/Animation/SHAnimationSystem.h @@ -0,0 +1,32 @@ +/************************************************************************************//*! +\file SHAnimationSystem.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 20, 2022 +\brief Contains the definition of the SHAnimationSystem class and related types. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// Project Includes +#include "SH_API.h" +#include "ECS_Base/System/SHSystem.h" +#include "ECS_Base/System/SHSystemRoutine.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + class SH_API SHAnimationSystem : public SHSystem + { + + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Animation/SHAnimatorComponent.cpp b/SHADE_Engine/src/Animation/SHAnimatorComponent.cpp new file mode 100644 index 00000000..271129f1 --- /dev/null +++ b/SHADE_Engine/src/Animation/SHAnimatorComponent.cpp @@ -0,0 +1,28 @@ +/************************************************************************************//*! +\file SHAnimatorComponent.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 20, 2022 +\brief Contains the definition of functions of the SHRenderable Component class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Pre-compiled Header +#include "SHpch.h" +// Primary Include +#include "SHAnimatorComponent.h" + +namespace SHADE +{ + +} + +RTTR_REGISTRATION +{ + using namespace SHADE; + using namespace rttr; + + registration::class_("Animator Component"); +} \ No newline at end of file diff --git a/SHADE_Engine/src/Animation/SHAnimatorComponent.h b/SHADE_Engine/src/Animation/SHAnimatorComponent.h new file mode 100644 index 00000000..86744aec --- /dev/null +++ b/SHADE_Engine/src/Animation/SHAnimatorComponent.h @@ -0,0 +1,36 @@ +/************************************************************************************//*! +\file SHAnimatorComponent.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 20, 2022 +\brief Contains the definition of the SHAnimationSystem class and related types. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// External Dependencies +#include +// Project Includes +#include "ECS_Base/Components/SHComponent.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + class SH_API SHAnimatorComponent final : public SHComponent + { + private: + /*-------------------------------------------------------------------------------*/ + /* Data Members */ + /*-------------------------------------------------------------------------------*/ + RTTR_ENABLE() + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Animation/SHRig.cpp b/SHADE_Engine/src/Animation/SHRig.cpp new file mode 100644 index 00000000..0ef2a0e6 --- /dev/null +++ b/SHADE_Engine/src/Animation/SHRig.cpp @@ -0,0 +1,89 @@ +/************************************************************************************//*! +\file SHRig.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 20, 2022 +\brief Contains the function definitions of the SHRig class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Pre-compiled Header +#include "SHpch.h" +// Primary Header +#include "SHRig.h" +// STL Includes +#include +// Project Headers +#include "Assets/Asset Types/Models/SHRigAsset.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------------*/ + SHRig::SHRig(const SHRigAsset& asset) + { + // Don't bother if empty + if (asset.root == nullptr) + return; + + // Do a recursive depth first traversal to populate the rig + rootNode = recurseCreateNode(asset, asset.root); + } + + /*-----------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*-----------------------------------------------------------------------------------*/ + const std::string& SHRig::GetName(Handle node) const noexcept + { + static const std::string EMPTY_STRING = ""; + + if (nodeNames.contains(node)) + return nodeNames.at(node); + + return EMPTY_STRING; + } + + Handle SHRig::GetNode(const std::string& name) const noexcept + { + if (nodesByName.contains(name)) + return nodesByName.at(name); + + return {}; + } + + /*-----------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------------*/ + Handle SHRig::recurseCreateNode(const SHRigAsset& asset, const RigNode* sourceNode) + { + // Construct the node + auto newNode = nodeStore.Create(); + + // Fill the node with data + const auto& NODE_DATA = asset.nodeDataCollection.at(sourceNode->idRef); + newNode->Transform = NODE_DATA.transform; + + // Populate maps + if (!NODE_DATA.name.empty()) + { + nodeNames.emplace(newNode, NODE_DATA.name); + nodesByName.emplace(NODE_DATA.name, newNode); + } + + // Fill child nodes + for (const auto& child : sourceNode->children) + { + // Ignore nulls + if (child == nullptr) + continue; + + // Recursively create children + newNode->Children.emplace_back(recurseCreateNode(asset, child)); + } + + return newNode; + } +} \ No newline at end of file diff --git a/SHADE_Engine/src/Animation/SHRig.h b/SHADE_Engine/src/Animation/SHRig.h new file mode 100644 index 00000000..39dbe1c4 --- /dev/null +++ b/SHADE_Engine/src/Animation/SHRig.h @@ -0,0 +1,91 @@ +/************************************************************************************//*! +\file SHRig.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 20, 2022 +\brief Contains the definition of the SHRig struct and related types. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// STL Includes +#include +#include +#include + +// Project Includes +#include "Math/SHMatrix.h" +#include "Resource/SHHandle.h" +#include "Resource/SHResourceLibrary.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + struct SHRigAsset; + struct RigNode; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + class SHRig + { + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + struct Node + { + SHMatrix Transform; + std::vector> Children; + }; + + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + /// + /// + /// + /// + explicit SHRig(const SHRigAsset& asset); + + /*---------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Retrieves the name of a node. + /// + /// Node to get the name of. + /// + /// Name of the node. If it does not have a name or is invalid, an empty string will + /// be provided. + /// + const std::string& GetName(Handle node) const noexcept; + /// + /// Retrieves a node via name. + /// + /// Name of the node to retrieve. + /// + /// Node with the specified name. If it does not have a name or is invalid, an empty + /// handle will be provided. + /// + Handle GetNode(const std::string& name) const noexcept; + + private: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + Handle rootNode; + std::unordered_map, std::string> nodeNames; + std::unordered_map> nodesByName; + SHResourceLibrary nodeStore; + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + Handle recurseCreateNode(const SHRigAsset& asset, const RigNode* sourceNode); + }; +} \ No newline at end of file From 611744f5d4d9c284548fd4bb3a4a29bf6fe7db37 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Mon, 21 Nov 2022 15:09:15 +0800 Subject: [PATCH 005/275] Fleshed out SHAnimationComponent more and added preliminary implementation of SHBatch for bone data --- .../src/Animation/SHAnimatorComponent.cpp | 3 + .../src/Animation/SHAnimatorComponent.h | 28 ++- .../Graphics/MiddleEnd/Batching/SHBatch.cpp | 49 ++++- .../src/Graphics/MiddleEnd/Batching/SHBatch.h | 206 +++++++++--------- 4 files changed, 177 insertions(+), 109 deletions(-) diff --git a/SHADE_Engine/src/Animation/SHAnimatorComponent.cpp b/SHADE_Engine/src/Animation/SHAnimatorComponent.cpp index 271129f1..3b4524e1 100644 --- a/SHADE_Engine/src/Animation/SHAnimatorComponent.cpp +++ b/SHADE_Engine/src/Animation/SHAnimatorComponent.cpp @@ -13,6 +13,9 @@ of DigiPen Institute of Technology is prohibited. #include "SHpch.h" // Primary Include #include "SHAnimatorComponent.h" +// Project Includes +#include "SHRig.h" +#include "Math/SHMatrix.h" namespace SHADE { diff --git a/SHADE_Engine/src/Animation/SHAnimatorComponent.h b/SHADE_Engine/src/Animation/SHAnimatorComponent.h index 86744aec..be1bc742 100644 --- a/SHADE_Engine/src/Animation/SHAnimatorComponent.h +++ b/SHADE_Engine/src/Animation/SHAnimatorComponent.h @@ -11,26 +11,48 @@ of DigiPen Institute of Technology is prohibited. *//*************************************************************************************/ #pragma once +// STL Includes +#include // External Dependencies #include // Project Includes #include "ECS_Base/Components/SHComponent.h" +#include "Resource/SHHandle.h" namespace SHADE { /*-----------------------------------------------------------------------------------*/ /* Forward Declarations */ /*-----------------------------------------------------------------------------------*/ + class SHRig; + struct SHMatrix; /*-----------------------------------------------------------------------------------*/ /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ + /// + /// Component that holds and controls the animation related properties of a skinned + /// mesh. + /// class SH_API SHAnimatorComponent final : public SHComponent { + public: + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + const std::vector& GetBoneMatrices() const noexcept { return boneMatrices; } + private: - /*-------------------------------------------------------------------------------*/ - /* Data Members */ - /*-------------------------------------------------------------------------------*/ + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + Handle rig; + std::vector boneMatrices; + float currPlaybackTime = 0.0f; + + /*---------------------------------------------------------------------------------*/ + /* RTTR */ + /*---------------------------------------------------------------------------------*/ RTTR_ENABLE() }; } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.cpp index 9b4b02b0..6b7b9111 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.cpp @@ -29,12 +29,13 @@ of DigiPen Institute of Technology is prohibited. #include "Graphics/Descriptors/SHVkDescriptorPool.h" #include "Scene/SHSceneManager.h" #include "UI/SHUIComponent.h" +#include "Animation/SHAnimatorComponent.h" namespace SHADE { - /*---------------------------------------------------------------------------------*/ - /* SHBatch - Usage Functions */ - /*---------------------------------------------------------------------------------*/ + /*-----------------------------------------------------------------------------------*/ + /* SHBatch - Constructors/Destructors */ + /*-----------------------------------------------------------------------------------*/ SHBatch::SHBatch(Handle pipeline) : pipeline{ pipeline } { @@ -124,6 +125,9 @@ namespace SHADE } } + /*-----------------------------------------------------------------------------------*/ + /* SHBatch - Usage Functions */ + /*-----------------------------------------------------------------------------------*/ void SHBatch::Add(const SHRenderable* renderable) { // Ignore if null @@ -407,8 +411,6 @@ namespace SHADE // - EID data instancedIntegerData.reserve(numTotalElements); instancedIntegerData.clear(); - - // - Material Properties Data const Handle SHADER_INFO = pipeline->GetPipelineLayout()->GetShaderBlockInterface ( @@ -429,6 +431,10 @@ namespace SHADE matPropsDataSize = matPropTotalBytes; } } + // - Bone Data + boneMatrixData.clear(); + boneMatrixIndices.clear(); + boneMatrixIndices.reserve(numTotalElements); // Build Sub Batches uint32_t nextInstanceIndex = 0; @@ -499,6 +505,16 @@ namespace SHADE //propsCurrPtr += singleMatPropAlignedSize; propsCurrPtr += singleMatPropSize; } + + // Bone Data + auto animator = SHComponentManager::GetComponent_s(rendId); + if (animator) + { + boneMatrixIndices.emplace_back(static_cast(boneMatrixData.size())); + const auto& BONE_MATRICES = animator->GetBoneMatrices(); + boneMatrixData.insert(boneMatrixData.end(), BONE_MATRICES.cbegin(), BONE_MATRICES.cend()); + } + } } @@ -533,6 +549,24 @@ namespace SHADE ); // - Material Properties Buffer rebuildMaterialBuffers(frameIndex, descPool); + // - Bone Buffers + if (!boneMatrixIndices.empty()) + { + const uint32_t BONE_IDX_DATA_BYTES = static_cast(boneMatrixIndices.size() * sizeof(uint32_t)); + SHVkUtil::EnsureBufferAndCopyHostVisibleData + ( + device, boneFirstIndexBuffer[frameIndex], boneMatrixIndices.data(), BONE_IDX_DATA_BYTES, + BuffUsage::eVertexBuffer, + "Batch Bone Index Buffer" + ); + const uint32_t BONE_MTX_DATA_BYTES = static_cast(boneMatrixData.size() * sizeof(uint32_t)); + SHVkUtil::EnsureBufferAndCopyHostVisibleData + ( + device, boneMatrixBuffer[frameIndex], boneMatrixData.data(), BONE_MTX_DATA_BYTES, + BuffUsage::eStorageBuffer, + "Batch Bone Matrix Buffer" + ); + } // Mark this frame as no longer dirty isDirty[frameIndex] = false; @@ -569,6 +603,11 @@ namespace SHADE dynamicOffset ); } + if (boneMatrixBuffer[frameIndex] && boneFirstIndexBuffer[frameIndex]) + { + cmdBuffer->BindVertexBuffer(SHGraphicsConstants::VertexBufferBindings::BONE_INDICES, boneFirstIndexBuffer[frameIndex], 0); + // TODO: Bind storage buffer + } cmdBuffer->DrawMultiIndirect(drawDataBuffer[frameIndex], static_cast(drawData.size())); cmdBuffer->EndLabeledSegment(); } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.h b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.h index dd4d33fd..08f07ea6 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.h @@ -27,114 +27,118 @@ of DigiPen Institute of Technology is prohibited. namespace SHADE { + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + class SHVkBuffer; + class SHVkCommandBuffer; + class SHVkPipeline; + class SHMesh; + class SHRenderable; + class SHVkLogicalDevice; + class SHMaterialInstance; + class SHVkDescriptorSetGroup; + class SHVkDescriptorPool; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + /*************************************************************************************/ + /*! + \brief + Describes a segment of the sub batch operation. + */ + /*************************************************************************************/ + struct SHSubBatch + { + public: /*---------------------------------------------------------------------------------*/ - /* Forward Declarations */ + /* Data Members */ /*---------------------------------------------------------------------------------*/ - class SHVkBuffer; - class SHVkCommandBuffer; - class SHVkPipeline; - class SHMesh; - class SHRenderable; - class SHVkLogicalDevice; - class SHMaterialInstance; - class SHVkDescriptorSetGroup; - class SHVkDescriptorPool; + Handle Mesh; + std::unordered_set Renderables; + }; + /*************************************************************************************/ + /*! + \brief + Describes a segment of the sub batch operation. + */ + /*************************************************************************************/ + class SHBatch + { + public: + /*---------------------------------------------------------------------------------*/ + /* Constructor/Destructors */ + /*---------------------------------------------------------------------------------*/ + SHBatch(Handle pipeline); + SHBatch(const SHBatch&) = delete; + SHBatch(SHBatch&& rhs); + SHBatch& operator=(const SHBatch&) = delete; + SHBatch& operator=(SHBatch&& rhs); + ~SHBatch(); /*---------------------------------------------------------------------------------*/ - /* Type Definitions */ + /* Usage Functions */ /*---------------------------------------------------------------------------------*/ - /***********************************************************************************/ - /*! - \brief - Describes a segment of the sub batch operation. - */ - /***********************************************************************************/ - struct SHSubBatch - { - public: - /*-----------------------------------------------------------------------------*/ - /* Data Members */ - /*-----------------------------------------------------------------------------*/ - Handle Mesh; - std::unordered_set Renderables; - }; - /***********************************************************************************/ - /*! - \brief - Describes a segment of the sub batch operation. - */ - /***********************************************************************************/ - class SHBatch - { - public: - /*-----------------------------------------------------------------------------*/ - /* Constructor/Destructors */ - /*-----------------------------------------------------------------------------*/ - SHBatch(Handle pipeline); - SHBatch(const SHBatch&) = delete; - SHBatch(SHBatch&& rhs); - SHBatch& operator=(const SHBatch&) = delete; - SHBatch& operator=(SHBatch&& rhs); - ~SHBatch(); + void Add(const SHRenderable* renderable); + void Remove(const SHRenderable* renderable); + void Clear(); + void UpdateMaterialBuffer(uint32_t frameIndex, Handle descPool); + void UpdateTransformBuffer(uint32_t frameIndex); + void UpdateInstancedIntegerBuffer(uint32_t frameIndex); + void Build(Handle device, Handle descPool, uint32_t frameIndex); + void Draw(Handle cmdBuffer, uint32_t frameIndex); - /*-----------------------------------------------------------------------------*/ - /* Usage Functions */ - /*-----------------------------------------------------------------------------*/ - void Add(const SHRenderable* renderable); - void Remove(const SHRenderable* renderable); - void Clear(); - void UpdateMaterialBuffer(uint32_t frameIndex, Handle descPool); - void UpdateTransformBuffer(uint32_t frameIndex); - void UpdateInstancedIntegerBuffer(uint32_t frameIndex); - void Build(Handle device, Handle descPool, uint32_t frameIndex) ; - void Draw(Handle cmdBuffer, uint32_t frameIndex); + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + Handle GetPipeline() const noexcept { return pipeline; }; + bool IsEmpty() const noexcept { return subBatches.empty(); } - /*-----------------------------------------------------------------------------*/ - /* Getter Functions */ - /*-----------------------------------------------------------------------------*/ - Handle GetPipeline() const noexcept { return pipeline; }; - bool IsEmpty() const noexcept { return subBatches.empty(); } + private: + /*---------------------------------------------------------------------------------*/ + /* Type Definition */ + /*---------------------------------------------------------------------------------*/ + using TripleBool = std::array; + using TripleBuffer = std::array, SHGraphicsConstants::NUM_FRAME_BUFFERS>; + using TripleDescSet = std::array, SHGraphicsConstants::NUM_FRAME_BUFFERS>; - private: - /*-----------------------------------------------------------------------------*/ - /* Type Definition */ - /*-----------------------------------------------------------------------------*/ - using TripleBool = std::array; - using TripleBuffer = std::array, SHGraphicsConstants::NUM_FRAME_BUFFERS>; - using TripleDescSet = std::array, SHGraphicsConstants::NUM_FRAME_BUFFERS>; + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + // Resources + Handle device; + // Batch Properties + Handle pipeline; + std::unordered_set> referencedMatInstances; + TripleBool matBufferDirty; + // Batch Tree + std::vector subBatches; + TripleBool isDirty; + // CPU Buffers + std::vector drawData; + std::vector transformData; + std::vector instancedIntegerData; + std::unique_ptr matPropsData; + Byte matPropsDataSize = 0; + Byte singleMatPropAlignedSize = 0; + Byte singleMatPropSize = 0; + std::vector boneMatrixData; + std::vector boneMatrixIndices; + bool isCPUBuffersDirty = true; + // GPU Buffers + TripleBuffer drawDataBuffer; + TripleBuffer transformDataBuffer; + TripleBuffer instancedIntegerBuffer; + TripleBuffer matPropsBuffer; + TripleDescSet matPropsDescSet; + TripleBuffer boneMatrixBuffer; + TripleBuffer boneFirstIndexBuffer; - /*-----------------------------------------------------------------------------*/ - /* Data Members */ - /*-----------------------------------------------------------------------------*/ - // Resources - Handle device; - // Batch Properties - Handle pipeline; - std::unordered_set> referencedMatInstances; - TripleBool matBufferDirty; - // Batch Tree - std::vector subBatches; - TripleBool isDirty; - // CPU Buffers - std::vector drawData; - std::vector transformData; - std::vector instancedIntegerData; - std::unique_ptr matPropsData; - Byte matPropsDataSize = 0; - Byte singleMatPropAlignedSize = 0; - Byte singleMatPropSize = 0; - bool isCPUBuffersDirty = true; - // GPU Buffers - TripleBuffer drawDataBuffer; - TripleBuffer transformDataBuffer; - TripleBuffer instancedIntegerBuffer; - TripleBuffer matPropsBuffer; - TripleDescSet matPropsDescSet; - - /*-----------------------------------------------------------------------------*/ - /* Helper Functions */ - /*-----------------------------------------------------------------------------*/ - void setAllDirtyFlags(); - void rebuildMaterialBuffers(uint32_t frameIndex, Handle descPool); - }; + /*-----------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------*/ + void setAllDirtyFlags(); + void rebuildMaterialBuffers(uint32_t frameIndex, Handle descPool); + }; } From 1a20eeed96de6fa21587bd83e378f53d9bf80802 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Mon, 21 Nov 2022 19:46:53 +0800 Subject: [PATCH 006/275] Added system routine stub for the SHAnimationSystem --- .../src/Animation/SHAnimationSystem.cpp | 20 +++++++++++++++++-- .../src/Animation/SHAnimationSystem.h | 18 ++++++++++++++++- .../src/Animation/SHAnimatorComponent.cpp | 10 ++++++++-- .../src/Animation/SHAnimatorComponent.h | 20 +++++++++++++++++++ SHADE_Engine/src/Animation/SHRig.cpp | 7 +++++++ SHADE_Engine/src/Animation/SHRig.h | 8 +++++++- 6 files changed, 77 insertions(+), 6 deletions(-) diff --git a/SHADE_Engine/src/Animation/SHAnimationSystem.cpp b/SHADE_Engine/src/Animation/SHAnimationSystem.cpp index 28b1a80e..8c514e7c 100644 --- a/SHADE_Engine/src/Animation/SHAnimationSystem.cpp +++ b/SHADE_Engine/src/Animation/SHAnimationSystem.cpp @@ -9,12 +9,28 @@ Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or disclosure of this file or its contents without the prior written consent of DigiPen Institute of Technology is prohibited. *//*************************************************************************************/ -// Pre-compiled Header +// Precompiled Header #include "SHpch.h" // Primary Include #include "SHAnimationSystem.h" +// Project Includes +#include "ECS_Base/Managers/SHComponentManager.h" +#include "SHAnimatorComponent.h" namespace SHADE { - + /*-----------------------------------------------------------------------------------*/ + /* System Routine Functions - UpdateRoutine */ + /*-----------------------------------------------------------------------------------*/ + SHAnimationSystem::UpdateRoutine::UpdateRoutine() + : SHSystemRoutine("Animation System Update", false) + {} + void SHAnimationSystem::UpdateRoutine::Execute(double dt) noexcept + { + auto& animators = SHComponentManager::GetDense(); + for (auto& animator : animators) + { + animator.Update(dt); + } + } } diff --git a/SHADE_Engine/src/Animation/SHAnimationSystem.h b/SHADE_Engine/src/Animation/SHAnimationSystem.h index eac839ad..96cd9a71 100644 --- a/SHADE_Engine/src/Animation/SHAnimationSystem.h +++ b/SHADE_Engine/src/Animation/SHAnimationSystem.h @@ -25,8 +25,24 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ + /// + /// System that is responsible for updating all animations. + /// class SH_API SHAnimationSystem : public SHSystem { - + public: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Responsible for updating the playback of all animator components and computing + /// the required bone matrices. + /// + class SH_API UpdateRoutine final : public SHSystemRoutine + { + public: + UpdateRoutine(); + void Execute(double dt) noexcept override final; + }; }; } \ No newline at end of file diff --git a/SHADE_Engine/src/Animation/SHAnimatorComponent.cpp b/SHADE_Engine/src/Animation/SHAnimatorComponent.cpp index 3b4524e1..61c0e6af 100644 --- a/SHADE_Engine/src/Animation/SHAnimatorComponent.cpp +++ b/SHADE_Engine/src/Animation/SHAnimatorComponent.cpp @@ -9,7 +9,7 @@ Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or disclosure of this file or its contents without the prior written consent of DigiPen Institute of Technology is prohibited. *//*************************************************************************************/ -// Pre-compiled Header +// Precompiled Header #include "SHpch.h" // Primary Include #include "SHAnimatorComponent.h" @@ -19,7 +19,13 @@ of DigiPen Institute of Technology is prohibited. namespace SHADE { - + /*-----------------------------------------------------------------------------------*/ + /* Update Functions */ + /*-----------------------------------------------------------------------------------*/ + void SHAnimatorComponent::Update(float dt) + { + // Set everything to identity + } } RTTR_REGISTRATION diff --git a/SHADE_Engine/src/Animation/SHAnimatorComponent.h b/SHADE_Engine/src/Animation/SHAnimatorComponent.h index be1bc742..dd71cc0e 100644 --- a/SHADE_Engine/src/Animation/SHAnimatorComponent.h +++ b/SHADE_Engine/src/Animation/SHAnimatorComponent.h @@ -37,10 +37,29 @@ namespace SHADE class SH_API SHAnimatorComponent final : public SHComponent { public: + /*---------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*---------------------------------------------------------------------------------*/ + /*void Play(); + void PlayFromStart(); + void Pause(); + void Stop();*/ + /*---------------------------------------------------------------------------------*/ /* Getter Functions */ /*---------------------------------------------------------------------------------*/ const std::vector& GetBoneMatrices() const noexcept { return boneMatrices; } + bool IsPlaying() const { return isPlaying; } + + /*---------------------------------------------------------------------------------*/ + /* Update Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Updates the current state of the animation if one is specified based on the + /// current animation clip and frames. This will update the bone matrices. + /// + /// Time passed since the last frame. + void Update(float dt); private: /*---------------------------------------------------------------------------------*/ @@ -49,6 +68,7 @@ namespace SHADE Handle rig; std::vector boneMatrices; float currPlaybackTime = 0.0f; + bool isPlaying = false; /*---------------------------------------------------------------------------------*/ /* RTTR */ diff --git a/SHADE_Engine/src/Animation/SHRig.cpp b/SHADE_Engine/src/Animation/SHRig.cpp index 0ef2a0e6..2214baa5 100644 --- a/SHADE_Engine/src/Animation/SHRig.cpp +++ b/SHADE_Engine/src/Animation/SHRig.cpp @@ -30,6 +30,7 @@ namespace SHADE return; // Do a recursive depth first traversal to populate the rig + nodeCount = 0; rootNode = recurseCreateNode(asset, asset.root); } @@ -54,6 +55,11 @@ namespace SHADE return {}; } + int SHRig::GetNodeCount() const noexcept + { + return nodeCount; + } + /*-----------------------------------------------------------------------------------*/ /* Helper Functions */ /*-----------------------------------------------------------------------------------*/ @@ -61,6 +67,7 @@ namespace SHADE { // Construct the node auto newNode = nodeStore.Create(); + ++nodeCount; // Fill the node with data const auto& NODE_DATA = asset.nodeDataCollection.at(sourceNode->idRef); diff --git a/SHADE_Engine/src/Animation/SHRig.h b/SHADE_Engine/src/Animation/SHRig.h index 39dbe1c4..af1b79ec 100644 --- a/SHADE_Engine/src/Animation/SHRig.h +++ b/SHADE_Engine/src/Animation/SHRig.h @@ -53,7 +53,7 @@ namespace SHADE explicit SHRig(const SHRigAsset& asset); /*---------------------------------------------------------------------------------*/ - /* Usage Functions */ + /* Getter Functions */ /*---------------------------------------------------------------------------------*/ /// /// Retrieves the name of a node. @@ -73,6 +73,11 @@ namespace SHADE /// handle will be provided. /// Handle GetNode(const std::string& name) const noexcept; + /// + /// Returns the number of nodes in the rig. This matches the number of bone matrices + /// needed. + /// + int GetNodeCount() const noexcept; private: /*---------------------------------------------------------------------------------*/ @@ -81,6 +86,7 @@ namespace SHADE Handle rootNode; std::unordered_map, std::string> nodeNames; std::unordered_map> nodesByName; + int nodeCount = 0; SHResourceLibrary nodeStore; /*---------------------------------------------------------------------------------*/ From f119acdb65ed5da5f4230685975d0e32cc646e46 Mon Sep 17 00:00:00 2001 From: Sri Sham Haran Date: Tue, 22 Nov 2022 21:15:18 +0800 Subject: [PATCH 007/275] collider tag panel --- .../ColliderTagPanel/SHColliderTagPanel.cpp | 62 +++++++++++++++++++ .../ColliderTagPanel/SHColliderTagPanel.h | 19 ++++++ .../EditorWindow/SHEditorWindowIncludes.h | 3 +- SHADE_Engine/src/Editor/SHEditor.cpp | 1 + 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 SHADE_Engine/src/Editor/EditorWindow/ColliderTagPanel/SHColliderTagPanel.cpp create mode 100644 SHADE_Engine/src/Editor/EditorWindow/ColliderTagPanel/SHColliderTagPanel.h diff --git a/SHADE_Engine/src/Editor/EditorWindow/ColliderTagPanel/SHColliderTagPanel.cpp b/SHADE_Engine/src/Editor/EditorWindow/ColliderTagPanel/SHColliderTagPanel.cpp new file mode 100644 index 00000000..5ff3842e --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/ColliderTagPanel/SHColliderTagPanel.cpp @@ -0,0 +1,62 @@ +#include "SHpch.h" +#include "SHColliderTagPanel.h" +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Physics/Collision/SHCollisionTagMatrix.h" +#include "Editor/SHEditorWidgets.hpp" + +namespace SHADE +{ + + void SHColliderTagPanel::Update() + { + if (Begin()) + { + ImGui::BeginTable("CollisionMtxTable", SHCollisionTag::NUM_LAYERS + 1, ImGuiTableRowFlags_Headers); + ImGui::TableNextRow(); + for (int i = 0; i <= SHCollisionTag::NUM_LAYERS; ++i) + { + ImGui::TableNextColumn(); + if(i == 0) continue; + std::string const& tagName = SHCollisionTagMatrix::GetTagName(i- 1); + auto tag = SHCollisionTagMatrix::GetTag(i - 1); + if (!tag) + continue; + //ImGui::Text(tagName.data()); + SHEditorWidgets::InputText("##" + std::to_string(i), [i]{return SHCollisionTagMatrix::GetTagName(i- 1);}, [i](std::string const& value){SHCollisionTagMatrix::GetTag(i)->SetName(value);}, tagName.data(), ImGuiInputTextFlags_EnterReturnsTrue); + } + for (int i = SHCollisionTag::NUM_LAYERS - 1; i >= 0; --i) + { + std::string tagName = SHCollisionTagMatrix::GetTagName(i); + auto tag = SHCollisionTagMatrix::GetTag(i); + if(!tag) + continue; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text(tagName.data()); + for (int j = 0; j < SHCollisionTag::NUM_LAYERS - i; ++j) + { + std::string tagName2 = SHCollisionTagMatrix::GetTagName(j); + auto tag2 = SHCollisionTagMatrix::GetTag(j); + + if(!tag2) + continue; + + if(tagName.empty()) + tagName = std::to_string(i); + if(tagName2.empty()) + tagName2 = std::to_string(j); + + ImGui::TableNextColumn(); + //if(i == j) + // continue; + std::string_view label = std::format("##{} vs {}", tagName, tagName2).data(); + SHEditorWidgets::CheckBox(label, [tag, &j]{return tag->GetLayerState(j);}, [tag, i, j](bool const& value){tag->SetLayerState(j, value); SHCollisionTagMatrix::GetTag(j)->SetLayerState(i, value);}, label.substr(2)); + + } + } + ImGui::EndTable(); + } + ImGui::End(); + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Editor/EditorWindow/ColliderTagPanel/SHColliderTagPanel.h b/SHADE_Engine/src/Editor/EditorWindow/ColliderTagPanel/SHColliderTagPanel.h new file mode 100644 index 00000000..94d17a6d --- /dev/null +++ b/SHADE_Engine/src/Editor/EditorWindow/ColliderTagPanel/SHColliderTagPanel.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Editor/EditorWindow/SHEditorWindow.h" +#include + +namespace SHADE +{ + class SH_API SHColliderTagPanel final : public SHEditorWindow + { + public: + SHColliderTagPanel():SHEditorWindow("Collider Tag Panel", ImGuiWindowFlags_MenuBar), isDirty(false){} + //void Init(); + void Update() override; + //void Exit(); + + private: + bool isDirty; + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindowIncludes.h b/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindowIncludes.h index 2fcde2b2..9aad6ede 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindowIncludes.h +++ b/SHADE_Engine/src/Editor/EditorWindow/SHEditorWindowIncludes.h @@ -5,4 +5,5 @@ #include "Profiling/SHEditorProfiler.h" //Profiler #include "ViewportWindow/SHEditorViewport.h" //Editor Viewport #include "AssetBrowser/SHAssetBrowser.h" //Asset Browser -#include "MaterialInspector/SHMaterialInspector.h" //Material Inspector \ No newline at end of file +#include "MaterialInspector/SHMaterialInspector.h" //Material Inspector +#include "ColliderTagPanel/SHColliderTagPanel.h" //Collider Tag Panel \ No newline at end of file diff --git a/SHADE_Engine/src/Editor/SHEditor.cpp b/SHADE_Engine/src/Editor/SHEditor.cpp index abddf457..dd24e158 100644 --- a/SHADE_Engine/src/Editor/SHEditor.cpp +++ b/SHADE_Engine/src/Editor/SHEditor.cpp @@ -101,6 +101,7 @@ namespace SHADE SHEditorWindowManager::CreateEditorWindow(); SHEditorWindowManager::CreateEditorWindow(); SHEditorWindowManager::CreateEditorWindow(); + SHEditorWindowManager::CreateEditorWindow(); SHEditorWindowManager::CreateEditorWindow(); From 807ad54d7dc97c46e4b96e06320946ea77518847 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Tue, 22 Nov 2022 21:31:37 +0800 Subject: [PATCH 008/275] Fixed Scene Node SetActive & SceneGraph CheckActiveInHierarchy --- SHADE_Engine/src/Scene/SHSceneGraph.cpp | 19 ++++++++++++++++++- SHADE_Engine/src/Scene/SHSceneNode.cpp | 5 ----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/SHADE_Engine/src/Scene/SHSceneGraph.cpp b/SHADE_Engine/src/Scene/SHSceneGraph.cpp index 6240b7bf..5e9f331d 100644 --- a/SHADE_Engine/src/Scene/SHSceneGraph.cpp +++ b/SHADE_Engine/src/Scene/SHSceneGraph.cpp @@ -204,7 +204,24 @@ namespace SHADE } //////////////////////////////////////// - return NODE_ITER->second->IsActive(); + // Recurse up the tree until the root. If any parent is inactive, this node is inactive in the hierarchy. + const SHSceneNode* PARENT_NODE = NODE_ITER->second->parent; + + while (PARENT_NODE->GetEntityID() != root->GetEntityID()) + { + if (!PARENT_NODE->IsActive()) + return false; + + if (!PARENT_NODE->parent) + { + SHLOGV_ERROR("Entity {}'s node that is not the root has no parent!", PARENT_NODE->GetEntityID()) + return false; + } + + PARENT_NODE = PARENT_NODE->parent; + } + + return true; } /*-----------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Scene/SHSceneNode.cpp b/SHADE_Engine/src/Scene/SHSceneNode.cpp index 8dac20bd..28f47989 100644 --- a/SHADE_Engine/src/Scene/SHSceneNode.cpp +++ b/SHADE_Engine/src/Scene/SHSceneNode.cpp @@ -133,11 +133,6 @@ namespace SHADE void SHSceneNode::SetActive(bool newActiveState) noexcept { active = newActiveState; - - for (auto* child : children) - { - child->SetActive(newActiveState); - } } } // namespace SHADE \ No newline at end of file From 9473359076fd1415a2d9949aa0fd11ca6d1e9fb7 Mon Sep 17 00:00:00 2001 From: Glence Date: Thu, 24 Nov 2022 14:05:02 +0800 Subject: [PATCH 009/275] added the base for raycast in pickandthrow --- Assets/Scenes/MainGame.shade | 5 ++- .../Implemented/LeafNodes/LeafPatrol.cs | 1 - .../Gameplay/Player/SC_PickAndThrow.cs | 44 +++++++++++++++---- .../Gameplay/Player/SC_PlayerController.cs | 2 +- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/Assets/Scenes/MainGame.shade b/Assets/Scenes/MainGame.shade index 600c6161..16de4ea8 100644 --- a/Assets/Scenes/MainGame.shade +++ b/Assets/Scenes/MainGame.shade @@ -8372,7 +8372,7 @@ NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 2.24178481, y: 1.4327563, z: 8.89205742} + Translate: {x: 2.24178481, y: 1.4327563, z: 9.3920002} Rotate: {x: -0, y: 0, z: -0} Scale: {x: 1, y: 1, z: 1} IsActive: true @@ -8394,7 +8394,7 @@ Colliders: - Is Trigger: true Type: Box - Half Extents: {x: 1.79999995, y: 1, z: 0.200000003} + Half Extents: {x: 1.79999995, y: 1, z: 1} Friction: 0.400000006 Bounciness: 0 Density: 1 @@ -8561,6 +8561,7 @@ throwForce: [50, 50, 50] delayTimer: 1 aimingLength: 0.5 + rayDistance: 1 - EID: 3 Name: HoldingPoint IsActive: true diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs index 1e68a7f2..bce56e59 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs @@ -81,7 +81,6 @@ public partial class LeafPatrol : BehaviourTreeNode } Vector3 remainingDistance = Vector3.Zero; - Debug.Log($"{waypoints.Count}"); if (currentWaypointIndex > 0) { diff --git a/Assets/Scripts/Gameplay/Player/SC_PickAndThrow.cs b/Assets/Scripts/Gameplay/Player/SC_PickAndThrow.cs index b9737685..27243029 100644 --- a/Assets/Scripts/Gameplay/Player/SC_PickAndThrow.cs +++ b/Assets/Scripts/Gameplay/Player/SC_PickAndThrow.cs @@ -24,24 +24,33 @@ public class PickAndThrow : Script private float lastZDir; private bool inRange = false; + private Collider collider; + private BoxCollider cs; + + [Tooltip("Lenght of ray")] + public float rayDistance = 3; protected override void awake() { - playerTran = GetComponent(); - if (!playerTran) - Debug.Log("PLAYERTRANSFORM EMPTY"); ; - pc = GetScript(); if(!pc) - Debug.Log("PLAYER CONTROLLER EMPTY"); + Debug.LogError("PLAYER CONTROLLER EMPTY"); raccoonHoldLocation = GetComponentInChildren(); if (!raccoonHoldLocation) - Debug.Log("CHILD EMPTY"); + Debug.LogError("CHILD EMPTY"); tpc = GetScriptInChildren(); if(!tpc) - Debug.Log("TPC EMPTY"); + Debug.LogError("TPC EMPTY"); + + collider = GetComponent(); + if (!collider) + Debug.LogError("COLLIDER EMPTY"); + else + { + cs = collider.GetCollisionShape(0); + } timer = delayTimer; } @@ -119,6 +128,11 @@ public class PickAndThrow : Script } } + protected override void fixedUpdate() + { + CastRay(); + } + private void ResetItemObject() { itemRidibody = null; @@ -171,11 +185,23 @@ public class PickAndThrow : Script } } - private void DelayCheck() + private void CastRay() { - timer += Time.DeltaTimeF; + if (pc != null && cs != null) + { + Vector3 dirNor = pc.tranform.Forward; + //change when cs.HalfExtents.z works + //cs.HalfExtents.z + Vector3 playerRayPos = pc.tranform.GlobalPosition + (dirNor * (0.3f + 0.1f)); + playerRayPos.y += 0.1f; + dirNor.Normalise(); + RaycastHit ray1 = Physics.Raycast(new Ray(playerRayPos, Vector3.RotateY(dirNor, SHADE.Math.DegreesToRadians(22.5f))), rayDistance); + RaycastHit ray2 = Physics.Raycast(new Ray(playerRayPos, Vector3.RotateY(dirNor, SHADE.Math.DegreesToRadians(-22.5f))), rayDistance); + RaycastHit ray3 = Physics.Raycast(new Ray(playerRayPos, dirNor), rayDistance * 0.5f); + } } + protected override void onCollisionEnter(CollisionInfo info) { } diff --git a/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs b/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs index b8a096e9..e1ad2512 100644 --- a/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs +++ b/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs @@ -27,7 +27,7 @@ public class PlayerController : Script }*/ public RigidBody rb { get; set; } - private Transform tranform; + public Transform tranform { get; set; } private Camera cam; public CameraArm camArm { get; set; } private PickAndThrow pat; From d6fab4439f266cb4b4bf42835c16bd6026df602a Mon Sep 17 00:00:00 2001 From: mushgunAX Date: Thu, 24 Nov 2022 21:56:06 +0800 Subject: [PATCH 010/275] Made player jumping more responsive - Changed the input check in the jump function from a GetKeyDown() / GetKeyUp() to a GetKey() / !GetKey() - This makes jumping a lot more responsive - This also means holding the jump button causes the raccoon to "bunny hop" until jump button is released --- Assets/Scripts/Gameplay/Player/SC_PlayerController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs b/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs index b8a096e9..ac00e5f7 100644 --- a/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs +++ b/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs @@ -285,7 +285,7 @@ public class PlayerController : Script { if (currentState == RaccoonStates.WALKING || currentState == RaccoonStates.RUNNING || currentState == RaccoonStates.IDLE) { - if (Input.GetKeyDown(Input.KeyCode.Space) && isGrounded && rb != null) + if (Input.GetKey(Input.KeyCode.Space) && isGrounded && rb != null) { currentState = RaccoonStates.JUMP; Vector3 v = rb.LinearVelocity; @@ -304,7 +304,7 @@ public class PlayerController : Script } } - if(!isGrounded && rb != null && (rb.LinearVelocity.y < 0.0f || Input.GetKeyUp(Input.KeyCode.Space))) + if(!isGrounded && rb != null && (rb.LinearVelocity.y < 0.0f || !Input.GetKey(Input.KeyCode.Space))) currentState = RaccoonStates.FALLING; } From c07fa3c5ae59400a3784a67c6fbaa3d76baf1753 Mon Sep 17 00:00:00 2001 From: Glence Date: Fri, 25 Nov 2022 00:54:45 +0800 Subject: [PATCH 011/275] remove the clamp from the phsysicSystem routines added cheats for maingame added raycast for pick throw --- Assets/Scenes/MainGame.shade | 97 ++----------------- .../Gameplay/Player/SC_PickAndThrow.cs | 63 +++++------- Assets/Scripts/Gameplay/SC_GameManager.cs | 33 +++++-- Assets/Scripts/SC_MainMenu.cs | 4 +- .../System/SHPhysicsSystemRoutines.cpp | 1 - 5 files changed, 62 insertions(+), 136 deletions(-) diff --git a/Assets/Scenes/MainGame.shade b/Assets/Scenes/MainGame.shade index 16de4ea8..7b0fa57d 100644 --- a/Assets/Scenes/MainGame.shade +++ b/Assets/Scenes/MainGame.shade @@ -8236,15 +8236,7 @@ Colliders: - Is Trigger: false Type: Box - Half Extents: {x: 0.200000003, y: 0.200000003, z: 0.200000003} - Friction: 0.400000006 - Bounciness: 0 - Density: 1 - Position Offset: {x: 0, y: 0, z: 0} - Rotation Offset: {x: 0, y: 0, z: 0} - - Is Trigger: true - Type: Box - Half Extents: {x: 0.400000006, y: 0.400000006, z: 0.400000006} + Half Extents: {x: 0.100000001, y: 0.100000001, z: 0.100000001} Friction: 0.400000006 Bounciness: 0 Density: 1 @@ -8288,15 +8280,7 @@ Colliders: - Is Trigger: false Type: Box - Half Extents: {x: 0.5, y: 0.150000006, z: 0.5} - Friction: 0.400000006 - Bounciness: 0 - Density: 1 - Position Offset: {x: 0, y: 0, z: 0} - Rotation Offset: {x: 0, y: 0, z: 0} - - Is Trigger: true - Type: Box - Half Extents: {x: 0.699999988, y: 0.300000012, z: 0.75} + Half Extents: {x: 0.25, y: 0.075000003, z: 0.25} Friction: 0.400000006 Bounciness: 0 Density: 1 @@ -8340,15 +8324,7 @@ Colliders: - Is Trigger: false Type: Box - Half Extents: {x: 0.300000012, y: 0.300000012, z: 0.300000012} - Friction: 0.400000006 - Bounciness: 0 - Density: 1 - Position Offset: {x: 0, y: 0, z: 0} - Rotation Offset: {x: 0, y: 0, z: 0} - - Is Trigger: true - Type: Box - Half Extents: {x: 0.5, y: 0.5, z: 0.5} + Half Extents: {x: 0.150000006, y: 0.150000006, z: 0.150000006} Friction: 0.400000006 Bounciness: 0 Density: 1 @@ -8558,10 +8534,12 @@ heavyMultiper: 0.25 - Type: PickAndThrow Enabled: true - throwForce: [50, 50, 50] + throwForce: [8, 10, 8] + item: 51000 delayTimer: 1 aimingLength: 0.5 - rayDistance: 1 + inRange: false + rayDistance: 0.5 - EID: 3 Name: HoldingPoint IsActive: true @@ -8633,63 +8611,4 @@ Rotate: {x: 0, y: 0, z: 0} Scale: {x: 1, y: 1, z: 1} IsActive: true - Scripts: ~ -- EID: 196 - Name: ====AI===== - IsActive: true - NumberOfChildren: 0 - Components: - Transform Component: - Translate: {x: 2.70000005, y: 0.100000001, z: -2} - Rotate: {x: -0, y: 0, z: -0} - Scale: {x: 1, y: 1, z: 1} - IsActive: true - Renderable Component: - Mesh: 140697366 - Material: 129495479 - IsActive: true - RigidBody Component: - Type: Dynamic - Drag: 0.00999999978 - Angular Drag: 0.00999999978 - Use Gravity: true - Interpolate: false - Sleeping Enabled: true - Freeze Position X: false - Freeze Position Y: false - Freeze Position Z: false - Freeze Rotation X: true - Freeze Rotation Y: false - Freeze Rotation Z: true - IsActive: true - Collider Component: - Colliders: - - Is Trigger: false - Type: Box - Half Extents: {x: 1, y: 1.79999995, z: 0.400000006} - Friction: 0.400000006 - Bounciness: 0 - Density: 1 - Position Offset: {x: 0, y: 0.899999976, z: 0} - Rotation Offset: {x: 0, y: 0, z: 0} - IsActive: true - Scripts: - - Type: Homeowner1 - Enabled: true - player: 2 - waypoints: - - [2.70000005, 0, -2] - - [-0.300000012, 0, -2.70000005] - - [-2, 0, -3.79999995] - - [-4, 0, -2.0999999] - - [-2.9000001, 0, 2.4000001] - - [-1, 0, 4] - - [2.70000005, 0, 4] - patrolSpeed: 1 - chaseSpeed: 2 - turningSpeed: 5 - sightDistance: 8 - eyeOffset: [0, 0, 0] - distanceToCapture: 0.5 - captureTime: 0.5 - footstepSFXIntervalMultiplier: 0.5 \ No newline at end of file + Scripts: ~ \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Player/SC_PickAndThrow.cs b/Assets/Scripts/Gameplay/Player/SC_PickAndThrow.cs index 27243029..af9ebc5e 100644 --- a/Assets/Scripts/Gameplay/Player/SC_PickAndThrow.cs +++ b/Assets/Scripts/Gameplay/Player/SC_PickAndThrow.cs @@ -7,7 +7,7 @@ using static Item; public class PickAndThrow : Script { public Vector3 throwForce = new Vector3(100.0f, 200.0f, 100.0f); - public GameObject item { get; set; } + public GameObject item; public float delayTimer = 1.0f; public float aimingLength = 1.5f; @@ -17,18 +17,16 @@ public class PickAndThrow : Script private Transform itemTransform; private RigidBody itemRidibody; private Transform raccoonHoldLocation; - private Transform playerTran; private Item itemScript; private ThirdPersonCamera tpc; private float lastXDir; private float lastZDir; - private bool inRange = false; + public bool inRange = false; private Collider collider; - private BoxCollider cs; [Tooltip("Lenght of ray")] - public float rayDistance = 3; + public float rayDistance = 1; protected override void awake() { @@ -49,7 +47,6 @@ public class PickAndThrow : Script Debug.LogError("COLLIDER EMPTY"); else { - cs = collider.GetCollisionShape(0); } timer = delayTimer; @@ -60,13 +57,14 @@ public class PickAndThrow : Script timer += Time.DeltaTimeF; CalculateDir(); + CastRay(); if (pc && itemRidibody && itemTransform) { if (pc.holdItem) { itemTransform.LocalPosition = raccoonHoldLocation.GlobalPosition; - itemTransform.LocalRotation = playerTran.LocalRotation; + itemTransform.LocalRotation = pc.tranform.LocalRotation; if (Input.GetMouseButtonDown(Input.MouseCode.LeftButton)) { @@ -86,6 +84,7 @@ public class PickAndThrow : Script if (itemScript) { Vector3 vec = new Vector3(throwForce.x * lastXDir, throwForce.y, throwForce.z * lastZDir); + //to be change if (itemScript.currCategory == ItemCategory.LIGHT) itemRidibody.AddForce(vec * 0.3f); if (itemScript.currCategory == ItemCategory.MEDIUM) @@ -130,7 +129,6 @@ public class PickAndThrow : Script protected override void fixedUpdate() { - CastRay(); } private void ResetItemObject() @@ -144,7 +142,7 @@ public class PickAndThrow : Script private void RetrieveItemComponets() { //get the transform of the given item - if (item.GetScript() && itemTransform == null && itemRidibody == null) + if (item.GetScript() && !itemTransform && !itemRidibody ) { itemRidibody = item.GetComponent(); if (itemRidibody == null) @@ -187,43 +185,34 @@ public class PickAndThrow : Script private void CastRay() { - if (pc != null && cs != null) + if (pc != null) { Vector3 dirNor = pc.tranform.Forward; - //change when cs.HalfExtents.z works - //cs.HalfExtents.z - Vector3 playerRayPos = pc.tranform.GlobalPosition + (dirNor * (0.3f + 0.1f)); - playerRayPos.y += 0.1f; + Vector3 playerRayPos = pc.tranform.GlobalPosition; + playerRayPos.y += 0.05f; dirNor.Normalise(); RaycastHit ray1 = Physics.Raycast(new Ray(playerRayPos, Vector3.RotateY(dirNor, SHADE.Math.DegreesToRadians(22.5f))), rayDistance); RaycastHit ray2 = Physics.Raycast(new Ray(playerRayPos, Vector3.RotateY(dirNor, SHADE.Math.DegreesToRadians(-22.5f))), rayDistance); - RaycastHit ray3 = Physics.Raycast(new Ray(playerRayPos, dirNor), rayDistance * 0.5f); + RaycastHit ray3 = Physics.Raycast(new Ray(playerRayPos, dirNor), rayDistance * 0.75f); + inRange = CheckForItem(ray1) || CheckForItem(ray2) || CheckForItem(ray3); } } + private bool CheckForItem(RaycastHit ray) + { + if (ray.Hit) + { + if (ray.Other.Value.GetScript() && !pc.holdItem) + { + item = ray.Other.Value; + return true; + } + else + return false; + } + return false; - protected override void onCollisionEnter(CollisionInfo info) - { - } - protected override void onTriggerEnter(CollisionInfo info) - { - if (info.GameObject.GetScript() && !pc.holdItem) - { - item = info.GameObject; - inRange = true; - } - } - protected override void onTriggerStay(CollisionInfo info) - { - //Debug.Log("STAY"); - } - protected override void onTriggerExit(CollisionInfo info) - { - //Debug.Log("EXIT"); - if (info.GameObject.GetScript() != null && !pc.holdItem) - { - inRange = false; - } } + } \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/SC_GameManager.cs b/Assets/Scripts/Gameplay/SC_GameManager.cs index b21bee42..ae98a54e 100644 --- a/Assets/Scripts/Gameplay/SC_GameManager.cs +++ b/Assets/Scripts/Gameplay/SC_GameManager.cs @@ -42,17 +42,12 @@ public class GameManager : Script Score = 0; currGameState = GameState.START; - if (zonePool) - { - listOfZone = zonePool.GetScriptsInChildren(); - if (listOfZone != null) - foreach (ScoringZone sz in listOfZone) - sz.gameManger = Owner.GetScript(); - } } protected override void update() { + Cheats(); + if (once) { if (itemPool) @@ -62,6 +57,14 @@ public class GameManager : Script foreach (Item i in listOfItems) totalItemCount += 1; } + + if (zonePool) + { + listOfZone = zonePool.GetScriptsInChildren(); + if (listOfZone != null) + foreach (ScoringZone sz in listOfZone) + sz.gameManger = Owner.GetScript(); + } once = false; } @@ -86,4 +89,20 @@ public class GameManager : Script } } + private void Cheats() + { + if (Input.GetKeyDown(Input.KeyCode.F1)) + { + SceneManager.ChangeScene(loseScene); + } + if (Input.GetKeyDown(Input.KeyCode.F2)) + { + SceneManager.ChangeScene(winScene); + } + if (Input.GetKeyDown(Input.KeyCode.Escape)) + { + SceneManager.ChangeScene(97158628); + } + } + } diff --git a/Assets/Scripts/SC_MainMenu.cs b/Assets/Scripts/SC_MainMenu.cs index 0cc5d0dd..cd2caf3f 100644 --- a/Assets/Scripts/SC_MainMenu.cs +++ b/Assets/Scripts/SC_MainMenu.cs @@ -9,14 +9,14 @@ public class MainMenu : Script } protected override void update() { - if (Input.GetKey(Input.KeyCode.Space)) + if (Input.GetKeyDown(Input.KeyCode.Space)) { Audio.PlaySFXOnce2D("event:/UI/mouse_down_element"); SceneManager.ChangeScene(86098106); Audio.StopAllSounds(); } - if (Input.GetKey(Input.KeyCode.Escape)) + if (Input.GetKeyDown(Input.KeyCode.Escape)) { Audio.StopAllSounds(); Application.Quit(); diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystemRoutines.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsSystemRoutines.cpp index 6168d673..6ab17077 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystemRoutines.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystemRoutines.cpp @@ -144,7 +144,6 @@ namespace SHADE // HACK: Clamp DT here to prevent a ridiculous amount of updates. This limits updates from large dt to 2. // HACK: This should be done by the FRC and not here for predictable behaviour. - dt = std::clamp(dt, 0.0, 1.0 / 30.0); accumulatedTime += dt; //testFunction(); From 4daaa8e8979f739155ffc21512be92f02dc7974d Mon Sep 17 00:00:00 2001 From: mushgunAX Date: Fri, 25 Nov 2022 00:56:40 +0800 Subject: [PATCH 012/275] Added SFX to AI - Footsteps, alert yell and unalert humming --- .../AIBehaviour/Implemented/Homeowner1.cs | 2 +- .../Implemented/LeafNodes/LeafChase.cs | 2 +- .../Implemented/LeafNodes/LeafSearch.cs | 35 ++++++++----------- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/Assets/Scripts/AIBehaviour/Implemented/Homeowner1.cs b/Assets/Scripts/AIBehaviour/Implemented/Homeowner1.cs index a90a3157..8ebd0537 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/Homeowner1.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/Homeowner1.cs @@ -103,7 +103,7 @@ public partial class Homeowner1 : BehaviourTree footstepTimeRemaining -= velocity * Time.DeltaTimeF; if (footstepTimeRemaining < 0.0f) { - Debug.Log("AI Play Footstep SFX"); + Audio.PlaySFXOnce2D("event:/Homeowner/homeowner_footsteps"); footstepTimeRemaining = footstepSFXIntervalMultiplier; } } diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs index 87e8ae1e..10562fc9 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs @@ -71,7 +71,7 @@ public partial class LeafChase : BehaviourTreeNode if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == true) { - Debug.Log("AI play unalert hmm"); + Audio.PlaySFXOnce2D("event:/Homeowner/humming"); } SetNodeData("isAlert", false); diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs index 056b5a4a..6d8e0237 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs @@ -41,7 +41,6 @@ public partial class LeafSearch : BehaviourTreeNode //Helper, find the nearest unobstructed waypoint to return to when chase is over private void reevaluateWaypoint() { - Debug.Log("Reevaluating Waypoints"); List waypoints = (List)GetNodeData("waypoints"); if (waypoints == null) @@ -62,6 +61,17 @@ public partial class LeafSearch : BehaviourTreeNode SetNodeData("currentWaypointIndex", nearestWaypointIndex); } + //Helper for handling stopping of chases + private void handleChaseStop() + { + if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == true) + { + Audio.PlaySFXOnce2D("event:/Homeowner/humming"); + reevaluateWaypoint(); + } + SetNodeData("isAlert", false); + } + public override BehaviourTreeNodeStatus Evaluate() { //Debug.LogWarning("LeafSearch"); @@ -94,12 +104,7 @@ public partial class LeafSearch : BehaviourTreeNode if ((plrT.GlobalPosition - transform.GlobalPosition).GetMagnitude() > sightDistance) { //Debug.Log("Failure: Too far"); - if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == true) - { - Debug.Log("AI play unalert hmm"); - reevaluateWaypoint(); - } - SetNodeData("isAlert", false); + handleChaseStop(); status = BehaviourTreeNodeStatus.FAILURE; onExit(BehaviourTreeNodeStatus.FAILURE); return status; @@ -114,12 +119,7 @@ public partial class LeafSearch : BehaviourTreeNode if (Vector3.Dot(difference, lookDirection) < 0.0f) { //Debug.Log("Failure: Out of FOV"); - if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == true) - { - Debug.Log("AI play unalert hmm"); - reevaluateWaypoint(); - } - SetNodeData("isAlert", false); + handleChaseStop(); status = BehaviourTreeNodeStatus.FAILURE; onExit(BehaviourTreeNodeStatus.FAILURE); return status; @@ -140,12 +140,7 @@ public partial class LeafSearch : BehaviourTreeNode if (sightRayHit.Hit && sightRayHit.Other != player) { //Debug.Log("Failure: Ray hit obstacle named " + sightRayHit.Other.GetValueOrDefault().Name + " ID" + sightRayHit.Other.GetValueOrDefault().EntityId); - if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == true) - { - Debug.Log("AI play unalert hmm"); - reevaluateWaypoint(); - } - SetNodeData("isAlert", false); + handleChaseStop(); status = BehaviourTreeNodeStatus.FAILURE; onExit(BehaviourTreeNodeStatus.FAILURE); return status; @@ -162,7 +157,7 @@ public partial class LeafSearch : BehaviourTreeNode if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == false) { - Debug.Log("AI Play Alerted Yell here"); + Audio.PlaySFXOnce2D("event:/Homeowner/homeowner_detect_raccoon"); } SetNodeData("isAlert", true); From c5d490b8b2b2fff747eab90afc8d1a8299507a0a Mon Sep 17 00:00:00 2001 From: mushgunAX Date: Fri, 25 Nov 2022 11:44:58 +0800 Subject: [PATCH 013/275] Added null checks for AI blackboard data - Null checks added to prevent AI causing the engine to crash --- .../AIBehaviour/Implemented/LeafNodes/LeafAttack.cs | 10 +++++++++- .../AIBehaviour/Implemented/LeafNodes/LeafChase.cs | 2 +- .../AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs | 4 ++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs index e64b63ad..56b5e1ef 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs @@ -41,7 +41,15 @@ public partial class LeafAttack : BehaviourTreeNode onEnter(BehaviourTreeNodeStatus.RUNNING); //Succeed when stand in hurt box for long enough - float captureTime = (float)GetNodeData("captureTimeLeft"); + //Debug.Log("Attempting to get blackboard data"); + float? captureTime = (float?)GetNodeData("captureTimeLeft"); + //Debug.Log("Got blackboard data"); + if (captureTime == null) + { + status = BehaviourTreeNodeStatus.FAILURE; + onExit(BehaviourTreeNodeStatus.FAILURE); + return status; + } captureTime -= Time.DeltaTimeF; SetNodeData("captureTimeLeft", captureTime); //Debug.Log(captureTime.ToString()); diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs index 10562fc9..cab81420 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs @@ -119,7 +119,7 @@ public partial class LeafChase : BehaviourTreeNode //Play SFX if (GetNodeData("isCapturing") != null && (bool)GetNodeData("isCapturing") == false) { - Debug.Log("AI Play capturing SFX"); + //Debug.Log("AI Play capturing SFX"); } SetNodeData("isCapturing", true); diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs index 248fa8b9..3f66bf94 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs @@ -84,6 +84,10 @@ public partial class LeafPatrol : BehaviourTreeNode } waypoints = (List)GetNodeData("waypoints"); + if (waypoints == null) + { + return; + } Vector3 targetPosition = waypoints[currentWaypointIndex].GetComponent().GlobalPosition; //Reach waypoint by X and Z being near enough From 6ac9bc4e9414f977add32dd860e60e6661939def Mon Sep 17 00:00:00 2001 From: Glence Date: Fri, 25 Nov 2022 14:45:19 +0800 Subject: [PATCH 014/275] added breakables to the main game and fix issues with it --- Assets/Models/MD_BreakableObjects1.gltf | 1244 +++++++++++++++++ Assets/Models/MD_BreakableObjects1.shmodel | Bin 0 -> 173714 bytes .../MD_BreakableObjects1.shmodel.shmeta | 46 + Assets/Scenes/Breakables.shade | 276 ++++ Assets/Scenes/Breakables.shade.shmeta | 3 + Assets/Scenes/MainGame.shade | 623 ++++++++- Assets/Scripts/Gameplay/Breakable.cs | 64 + Assets/Scripts/Gameplay/Breakable.cs.shmeta | 3 + .../Gameplay/Player/SC_PickAndThrow.cs | 50 +- .../Gameplay/Player/SC_PlayerController.cs | 2 +- Assets/Scripts/SC_MainMenu.cs | 2 +- 11 files changed, 2286 insertions(+), 27 deletions(-) create mode 100644 Assets/Models/MD_BreakableObjects1.gltf create mode 100644 Assets/Models/MD_BreakableObjects1.shmodel create mode 100644 Assets/Models/MD_BreakableObjects1.shmodel.shmeta create mode 100644 Assets/Scenes/Breakables.shade create mode 100644 Assets/Scenes/Breakables.shade.shmeta create mode 100644 Assets/Scripts/Gameplay/Breakable.cs create mode 100644 Assets/Scripts/Gameplay/Breakable.cs.shmeta diff --git a/Assets/Models/MD_BreakableObjects1.gltf b/Assets/Models/MD_BreakableObjects1.gltf new file mode 100644 index 00000000..30ade0f1 --- /dev/null +++ b/Assets/Models/MD_BreakableObjects1.gltf @@ -0,0 +1,1244 @@ +{ + "asset" : { + "generator" : "Khronos glTF Blender I/O v3.3.27", + "version" : "2.0" + }, + "extensionsUsed" : [ + "KHR_materials_specular", + "KHR_materials_ior" + ], + "scene" : 0, + "scenes" : [ + { + "name" : "Scene", + "nodes" : [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13 + ] + } + ], + "nodes" : [ + { + "mesh" : 0, + "name" : "Plate01_Unshattered" + }, + { + "mesh" : 1, + "name" : "Plate01_Shattered.004", + "translation" : [ + 0.026657864451408386, + -0.007859552279114723, + 0.055684760212898254 + ] + }, + { + "mesh" : 2, + "name" : "Plate01_Shattered.001", + "translation" : [ + -0.07043591886758804, + -0.005909430328756571, + 0.015931762754917145 + ] + }, + { + "mesh" : 3, + "name" : "Plate01_Shattered.002", + "translation" : [ + -0.024298690259456635, + -0.009387452155351639, + -0.036571405827999115 + ] + }, + { + "mesh" : 4, + "name" : "Plate01_Shattered.003", + "translation" : [ + 0.04137386009097099, + -0.00804806500673294, + -0.026985637843608856 + ] + }, + { + "mesh" : 5, + "name" : "Bowl01_Unshattered" + }, + { + "mesh" : 6, + "name" : "Bowl01_Shattered.001", + "translation" : [ + 0.031089508906006813, + -0.014782273210585117, + 0.0026974601205438375 + ] + }, + { + "mesh" : 7, + "name" : "Bowl01_Shattered.002", + "translation" : [ + -0.01576181873679161, + -0.01646549068391323, + -0.025849148631095886 + ] + }, + { + "mesh" : 8, + "name" : "Bowl01_Shattered.003", + "translation" : [ + -0.01686645671725273, + -0.01771463267505169, + 0.023550206795334816 + ] + }, + { + "mesh" : 9, + "name" : "Cup01_Unshattered", + "scale" : [ + 1, + 1.220760703086853, + 1 + ] + }, + { + "mesh" : 10, + "name" : "Cup01_Shattered.001", + "translation" : [ + 0.0007706800242885947, + -0.04436443746089935, + 0 + ] + }, + { + "mesh" : 11, + "name" : "Cup01_Shattered.002", + "translation" : [ + -0.03890189528465271, + 0.0030917779076844454, + 0 + ] + }, + { + "mesh" : 12, + "name" : "Cup01_Shattered.003", + "translation" : [ + 0.014016233384609222, + 0.004240443930029869, + -0.027260959148406982 + ] + }, + { + "mesh" : 13, + "name" : "Cup01_Shattered.004", + "translation" : [ + 0.015440782532095909, + 0.003909517545253038, + 0.025504697114229202 + ] + } + ], + "materials" : [ + { + "doubleSided" : true, + "extensions" : { + "KHR_materials_specular" : { + "specularColorFactor" : [ + 0, + 0, + 0 + ] + }, + "KHR_materials_ior" : { + "ior" : 1.4500000476837158 + } + }, + "name" : "Material", + "pbrMetallicRoughness" : { + "baseColorTexture" : { + "index" : 0 + }, + "metallicFactor" : 0 + } + }, + { + "doubleSided" : true, + "extensions" : { + "KHR_materials_specular" : { + "specularColorFactor" : [ + 0, + 0, + 0 + ] + }, + "KHR_materials_ior" : { + "ior" : 1.4500000476837158 + } + }, + "name" : "Material.001", + "pbrMetallicRoughness" : { + "baseColorTexture" : { + "index" : 1 + }, + "metallicFactor" : 0 + } + } + ], + "meshes" : [ + { + "name" : "Cylinder.003", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 0 + } + ] + }, + { + "name" : "Cylinder.007", + "primitives" : [ + { + "attributes" : { + "POSITION" : 4, + "NORMAL" : 5, + "TEXCOORD_0" : 6 + }, + "indices" : 7, + "material" : 0 + } + ] + }, + { + "name" : "Cylinder.008", + "primitives" : [ + { + "attributes" : { + "POSITION" : 8, + "NORMAL" : 9, + "TEXCOORD_0" : 10 + }, + "indices" : 11, + "material" : 0 + } + ] + }, + { + "name" : "Cylinder.009", + "primitives" : [ + { + "attributes" : { + "POSITION" : 12, + "NORMAL" : 13, + "TEXCOORD_0" : 14 + }, + "indices" : 15, + "material" : 0 + } + ] + }, + { + "name" : "Cylinder.010", + "primitives" : [ + { + "attributes" : { + "POSITION" : 16, + "NORMAL" : 17, + "TEXCOORD_0" : 18 + }, + "indices" : 19, + "material" : 0 + } + ] + }, + { + "name" : "Sphere.001", + "primitives" : [ + { + "attributes" : { + "POSITION" : 20, + "NORMAL" : 21, + "TEXCOORD_0" : 22 + }, + "indices" : 23, + "material" : 0 + } + ] + }, + { + "name" : "Sphere.004", + "primitives" : [ + { + "attributes" : { + "POSITION" : 24, + "NORMAL" : 25, + "TEXCOORD_0" : 26 + }, + "indices" : 27, + "material" : 1 + } + ] + }, + { + "name" : "Sphere.003", + "primitives" : [ + { + "attributes" : { + "POSITION" : 28, + "NORMAL" : 29, + "TEXCOORD_0" : 30 + }, + "indices" : 31, + "material" : 1 + } + ] + }, + { + "name" : "Sphere.002", + "primitives" : [ + { + "attributes" : { + "POSITION" : 32, + "NORMAL" : 33, + "TEXCOORD_0" : 34 + }, + "indices" : 35, + "material" : 1 + } + ] + }, + { + "name" : "Cylinder.006", + "primitives" : [ + { + "attributes" : { + "POSITION" : 36, + "NORMAL" : 37, + "TEXCOORD_0" : 38 + }, + "indices" : 39, + "material" : 0 + } + ] + }, + { + "name" : "Cylinder.011", + "primitives" : [ + { + "attributes" : { + "POSITION" : 40, + "NORMAL" : 41, + "TEXCOORD_0" : 42 + }, + "indices" : 43, + "material" : 0 + } + ] + }, + { + "name" : "Cylinder.012", + "primitives" : [ + { + "attributes" : { + "POSITION" : 44, + "NORMAL" : 45, + "TEXCOORD_0" : 46 + }, + "indices" : 47, + "material" : 0 + } + ] + }, + { + "name" : "Cylinder.013", + "primitives" : [ + { + "attributes" : { + "POSITION" : 48, + "NORMAL" : 49, + "TEXCOORD_0" : 50 + }, + "indices" : 51, + "material" : 0 + } + ] + }, + { + "name" : "Cylinder.005", + "primitives" : [ + { + "attributes" : { + "POSITION" : 52, + "NORMAL" : 53, + "TEXCOORD_0" : 54 + }, + "indices" : 55, + "material" : 0 + } + ] + } + ], + "textures" : [ + { + "sampler" : 0, + "source" : 0 + }, + { + "sampler" : 0, + "source" : 0 + } + ], + "images" : [ + { + "bufferView" : 4, + "mimeType" : "image/png", + "name" : "TX_StaticMesh" + } + ], + "accessors" : [ + { + "bufferView" : 0, + "componentType" : 5126, + "count" : 178, + "max" : [ + 0.12465937435626984, + 0.006883568596094847, + 0.12465937435626984 + ], + "min" : [ + -0.12465937435626984, + -0.01786988042294979, + -0.12465937435626984 + ], + "type" : "VEC3" + }, + { + "bufferView" : 1, + "componentType" : 5126, + "count" : 178, + "type" : "VEC3" + }, + { + "bufferView" : 2, + "componentType" : 5126, + "count" : 178, + "type" : "VEC2" + }, + { + "bufferView" : 3, + "componentType" : 5123, + "count" : 576, + "type" : "SCALAR" + }, + { + "bufferView" : 5, + "componentType" : 5126, + "count" : 172, + "max" : [ + 0.09191117435693741, + 0.014743121340870857, + 0.07289263606071472 + ], + "min" : [ + -0.057009562849998474, + -0.009894363582134247, + -0.04776472970843315 + ], + "type" : "VEC3" + }, + { + "bufferView" : 6, + "componentType" : 5126, + "count" : 172, + "type" : "VEC3" + }, + { + "bufferView" : 7, + "componentType" : 5126, + "count" : 172, + "type" : "VEC2" + }, + { + "bufferView" : 8, + "componentType" : 5123, + "count" : 420, + "type" : "SCALAR" + }, + { + "bufferView" : 9, + "componentType" : 5126, + "count" : 160, + "max" : [ + 0.04665448144078255, + 0.012792998924851418, + 0.10727512091398239 + ], + "min" : [ + -0.0605088546872139, + -0.011180143803358078, + -0.11256729811429977 + ], + "type" : "VEC3" + }, + { + "bufferView" : 10, + "componentType" : 5126, + "count" : 160, + "type" : "VEC3" + }, + { + "bufferView" : 11, + "componentType" : 5126, + "count" : 160, + "type" : "VEC2" + }, + { + "bufferView" : 12, + "componentType" : 5123, + "count" : 408, + "type" : "SCALAR" + }, + { + "bufferView" : 13, + "componentType" : 5126, + "count" : 172, + "max" : [ + 0.056986045092344284, + 0.016271021217107773, + 0.09429033100605011 + ], + "min" : [ + -0.05039750039577484, + -0.008289270102977753, + -0.09102458506822586 + ], + "type" : "VEC3" + }, + { + "bufferView" : 14, + "componentType" : 5126, + "count" : 172, + "type" : "VEC3" + }, + { + "bufferView" : 15, + "componentType" : 5126, + "count" : 172, + "type" : "VEC2" + }, + { + "bufferView" : 16, + "componentType" : 5123, + "count" : 408, + "type" : "SCALAR" + }, + { + "bufferView" : 17, + "componentType" : 5126, + "count" : 170, + "max" : [ + 0.08504810929298401, + 0.014931634068489075, + 0.07000371813774109 + ], + "min" : [ + -0.051118992269039154, + -0.00982181541621685, + -0.09179021418094635 + ], + "type" : "VEC3" + }, + { + "bufferView" : 18, + "componentType" : 5126, + "count" : 170, + "type" : "VEC3" + }, + { + "bufferView" : 19, + "componentType" : 5126, + "count" : 170, + "type" : "VEC2" + }, + { + "bufferView" : 20, + "componentType" : 5123, + "count" : 432, + "type" : "SCALAR" + }, + { + "bufferView" : 21, + "componentType" : 5126, + "count" : 370, + "max" : [ + 0.07135650515556335, + 0.023533688858151436, + 0.07135650515556335 + ], + "min" : [ + -0.07135650515556335, + -0.03516172245144844, + -0.07135650515556335 + ], + "type" : "VEC3" + }, + { + "bufferView" : 22, + "componentType" : 5126, + "count" : 370, + "type" : "VEC3" + }, + { + "bufferView" : 23, + "componentType" : 5126, + "count" : 370, + "type" : "VEC2" + }, + { + "bufferView" : 24, + "componentType" : 5123, + "count" : 1728, + "type" : "SCALAR" + }, + { + "bufferView" : 25, + "componentType" : 5126, + "count" : 263, + "max" : [ + 0.04180770367383957, + 0.03831596300005913, + 0.06366756558418274 + ], + "min" : [ + -0.029548805207014084, + -0.020379450172185898, + -0.05944325402379036 + ], + "type" : "VEC3" + }, + { + "bufferView" : 26, + "componentType" : 5126, + "count" : 263, + "type" : "VEC3" + }, + { + "bufferView" : 27, + "componentType" : 5126, + "count" : 263, + "type" : "VEC2" + }, + { + "bufferView" : 28, + "componentType" : 5123, + "count" : 918, + "type" : "SCALAR" + }, + { + "bufferView" : 29, + "componentType" : 5126, + "count" : 275, + "max" : [ + 0.054166436195373535, + 0.03999917954206467, + 0.034435875713825226 + ], + "min" : [ + -0.0552128441631794, + -0.01869623176753521, + -0.04638776183128357 + ], + "type" : "VEC3" + }, + { + "bufferView" : 30, + "componentType" : 5126, + "count" : 275, + "type" : "VEC3" + }, + { + "bufferView" : 31, + "componentType" : 5126, + "count" : 275, + "type" : "VEC2" + }, + { + "bufferView" : 32, + "componentType" : 5123, + "count" : 936, + "type" : "SCALAR" + }, + { + "bufferView" : 33, + "componentType" : 5126, + "count" : 289, + "max" : [ + 0.0421924851834774, + 0.041248321533203125, + 0.05022740364074707 + ], + "min" : [ + -0.056470949202775955, + -0.01744708977639675, + -0.0330074168741703 + ], + "type" : "VEC3" + }, + { + "bufferView" : 34, + "componentType" : 5126, + "count" : 289, + "type" : "VEC3" + }, + { + "bufferView" : 35, + "componentType" : 5126, + "count" : 289, + "type" : "VEC2" + }, + { + "bufferView" : 36, + "componentType" : 5123, + "count" : 999, + "type" : "SCALAR" + }, + { + "bufferView" : 37, + "componentType" : 5126, + "count" : 273, + "max" : [ + 0.04235110804438591, + 0.04235110804438591, + 0.04235110804438591 + ], + "min" : [ + -0.07133897393941879, + -0.04235110804438591, + -0.04235110804438591 + ], + "type" : "VEC3" + }, + { + "bufferView" : 38, + "componentType" : 5126, + "count" : 273, + "type" : "VEC3" + }, + { + "bufferView" : 39, + "componentType" : 5126, + "count" : 273, + "type" : "VEC2" + }, + { + "bufferView" : 40, + "componentType" : 5123, + "count" : 1056, + "type" : "SCALAR" + }, + { + "bufferView" : 41, + "componentType" : 5126, + "count" : 156, + "max" : [ + 0.041580427438020706, + 0.010478939861059189, + 0.04235110804438591 + ], + "min" : [ + -0.043121788650751114, + -0.009852748364210129, + -0.04235110804438591 + ], + "type" : "VEC3" + }, + { + "bufferView" : 42, + "componentType" : 5126, + "count" : 156, + "type" : "VEC3" + }, + { + "bufferView" : 43, + "componentType" : 5126, + "count" : 156, + "type" : "VEC2" + }, + { + "bufferView" : 44, + "componentType" : 5123, + "count" : 432, + "type" : "SCALAR" + }, + { + "bufferView" : 45, + "componentType" : 5126, + "count" : 271, + "max" : [ + 0.028453389182686806, + 0.04860879108309746, + 0.04089901223778725 + ], + "min" : [ + -0.035583943128585815, + -0.045555733144283295, + -0.04089842364192009 + ], + "type" : "VEC3" + }, + { + "bufferView" : 46, + "componentType" : 5126, + "count" : 271, + "type" : "VEC3" + }, + { + "bufferView" : 47, + "componentType" : 5126, + "count" : 271, + "type" : "VEC2" + }, + { + "bufferView" : 48, + "componentType" : 5123, + "count" : 870, + "type" : "SCALAR" + }, + { + "bufferView" : 49, + "componentType" : 5126, + "count" : 141, + "max" : [ + 0.028334874659776688, + 0.04903355985879898, + 0.03738982230424881 + ], + "min" : [ + -0.033730506896972656, + -0.045070454478263855, + -0.017362885177135468 + ], + "type" : "VEC3" + }, + { + "bufferView" : 50, + "componentType" : 5126, + "count" : 141, + "type" : "VEC3" + }, + { + "bufferView" : 51, + "componentType" : 5126, + "count" : 141, + "type" : "VEC2" + }, + { + "bufferView" : 52, + "componentType" : 5123, + "count" : 390, + "type" : "SCALAR" + }, + { + "bufferView" : 53, + "componentType" : 5126, + "count" : 143, + "max" : [ + 0.02691032551229, + 0.04814070463180542, + 0.01894431933760643 + ], + "min" : [ + -0.03515505790710449, + -0.045963309705257416, + -0.03315911814570427 + ], + "type" : "VEC3" + }, + { + "bufferView" : 54, + "componentType" : 5126, + "count" : 143, + "type" : "VEC3" + }, + { + "bufferView" : 55, + "componentType" : 5126, + "count" : 143, + "type" : "VEC2" + }, + { + "bufferView" : 56, + "componentType" : 5123, + "count" : 402, + "type" : "SCALAR" + } + ], + "bufferViews" : [ + { + "buffer" : 0, + "byteLength" : 2136, + "byteOffset" : 0, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 2136, + "byteOffset" : 2136, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1424, + "byteOffset" : 4272, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1152, + "byteOffset" : 5696, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 54895, + "byteOffset" : 6848 + }, + { + "buffer" : 0, + "byteLength" : 2064, + "byteOffset" : 61744, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 2064, + "byteOffset" : 63808, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1376, + "byteOffset" : 65872, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 840, + "byteOffset" : 67248, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 1920, + "byteOffset" : 68088, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1920, + "byteOffset" : 70008, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1280, + "byteOffset" : 71928, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 73208, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 2064, + "byteOffset" : 74024, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 2064, + "byteOffset" : 76088, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1376, + "byteOffset" : 78152, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 816, + "byteOffset" : 79528, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 2040, + "byteOffset" : 80344, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 2040, + "byteOffset" : 82384, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1360, + "byteOffset" : 84424, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 864, + "byteOffset" : 85784, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 4440, + "byteOffset" : 86648, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 4440, + "byteOffset" : 91088, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 2960, + "byteOffset" : 95528, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 3456, + "byteOffset" : 98488, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 3156, + "byteOffset" : 101944, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 3156, + "byteOffset" : 105100, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 2104, + "byteOffset" : 108256, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1836, + "byteOffset" : 110360, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 3300, + "byteOffset" : 112196, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 3300, + "byteOffset" : 115496, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 2200, + "byteOffset" : 118796, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1872, + "byteOffset" : 120996, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 3468, + "byteOffset" : 122868, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 3468, + "byteOffset" : 126336, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 2312, + "byteOffset" : 129804, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1998, + "byteOffset" : 132116, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 3276, + "byteOffset" : 134116, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 3276, + "byteOffset" : 137392, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 2184, + "byteOffset" : 140668, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 2112, + "byteOffset" : 142852, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 1872, + "byteOffset" : 144964, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1872, + "byteOffset" : 146836, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1248, + "byteOffset" : 148708, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 864, + "byteOffset" : 149956, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 3252, + "byteOffset" : 150820, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 3252, + "byteOffset" : 154072, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 2168, + "byteOffset" : 157324, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1740, + "byteOffset" : 159492, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 1692, + "byteOffset" : 161232, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1692, + "byteOffset" : 162924, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1128, + "byteOffset" : 164616, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 780, + "byteOffset" : 165744, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 1716, + "byteOffset" : 166524, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1716, + "byteOffset" : 168240, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 1144, + "byteOffset" : 169956, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 804, + "byteOffset" : 171100, + "target" : 34963 + } + ], + "samplers" : [ + { + "magFilter" : 9729, + "minFilter" : 9987 + } + ], + "buffers" : [ + { + "byteLength" : 171904, + "uri" : "data:application/octet-stream;base64,21XzMKdahbyqiru921XzMKdahbyqiru9yIkPPadahbwTRK29yIkPPadahbwTRK29q5yEPadahbysnIS9q5yEPadahbysnIS9EkStPadahbzIiQ+9EkStPadahbzIiQ+9qoq7PadahbxsURgxqoq7PadahbxsURgxE0StPadahbzJiQ89E0StPadahbzJiQ89rJyEPadahbyrnIQ9rJyEPadahbyrnIQ9yIkPPadahbwSRK09yIkPPadahbwSRK09Un2Asadahbyqirs9Un2Asadahbyqirs9yokPvadahbwTRK09yokPvadahbwTRK09q5yEvadahbysnIQ9q5yEvadahbysnIQ9EkStvadahbzIiQ89EkStvadahbzIiQ89qoq7vadahbwnVGKxqoq7vadahbwnVGKxE0StvadahbzJiQ+9E0StvadahbzJiQ+9rJyEvadahbyrnIS9rJyEvadahbyrnIS9yYkPvadahbwSRK29yYkPvadahbwSRK290JgJLfF5urv9o/S9ZD07PfF5uru2BOK9cHNAPStU2jsOT+i9cHNAPStU2jsOT+i90JgJLStU2jsBc/u90JgJLStU2jsBc/u9rfysPfF5urut/Ky9Lc2xPStU2jstzbG9Lc2xPStU2jstzbG9tgTiPfF5urtkPTu9Dk/oPStU2jtwc0C9Dk/oPStU2jtwc0C9/aP0PfF5ursgzfwuAXP7PStU2jvgQuEuAXP7PStU2jvgQuEutgTiPfF5urtkPTs9Dk/oPStU2jtwc0A9Dk/oPStU2jtwc0A9rfysPfF5urut/Kw9Lc2xPStU2jstzbE9Lc2xPStU2jstzbE9ZD07PfF5uru2BOI9cHNAPStU2jsOT+g9cHNAPStU2jsOT+g90JgJLfF5urv9o/Q90JgJLStU2jsBc/s90JgJLStU2jsBc/s9ZD07vfF5uru2BOI9cHNAvStU2jsOT+g9cHNAvStU2jsOT+g9rfysvfF5urut/Kw9Lc2xvStU2jstzbE9Lc2xvStU2jstzbE9tgTivfF5urtkPTs9Dk/ovStU2jtwc0A9Dk/ovStU2jtwc0A9/aP0vfF5ursgzfwuAXP7vStU2jvgQuEuAXP7vStU2jvgQuEutgTivfF5urtkPTu9Dk/ovStU2jtwc0C9Dk/ovStU2jtwc0C9rfysvfF5urut/Ky9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PgA/ADgANwA/ADgAPwBAADkAOABAADkAQABBADoAOQBBADoAQQBCADsAOgBCADsAQgBDADYANQA8ADYAPAA+AEMAQgBKAEMASgBLABsAIwCRABsAkQCNAFsAYwCwAFsAsACsAD4APABEAD4ARABGAD8APgBGAD8ARgBHAEAAPwBHAEAARwBIAEEAQABIAEEASABJAEIAQQBJAEIASQBKAEgARwBPAEgATwBQAEkASABQAEkAUABRAEoASQBRAEoAUQBSAEsASgBSAEsAUgBTAAsAEwCJAAsAiQCFAEsAUwCoAEsAqACkAEYARABMAEYATABOAEcARgBOAEcATgBPAGsAcwC4AGsAuAC0ADsAQwCgADsAoACdAE4ATABUAE4AVABWAE8ATgBWAE8AVgBXAFAATwBXAFAAVwBYAFEAUABYAFEAWABZAFIAUQBZAFIAWQBaAFMAUgBaAFMAWgBbAFgAVwBfAFgAXwBgAFkAWABgAFkAYABhAFoAWQBhAFoAYQBiAFsAWgBiAFsAYgBjAAMACwCFAAMAhQCBACsAMwCZACsAmQCVAFYAVABcAFYAXABeAFcAVgBeAFcAXgBfAGMAawC0AGMAtACwAFMAWwCsAFMArACoAF4AXABkAF4AZABmAF8AXgBmAF8AZgBnAGAAXwBnAGAAZwBoAGEAYABoAGEAaABpAGIAYQBpAGIAaQBqAGMAYgBqAGMAagBrAGkAaABwAGkAcABxAGoAaQBxAGoAcQByAGsAagByAGsAcgBzABMAGwCNABMAjQCJAHMAewC8AHMAvAC4AGYAZABsAGYAbABuAGcAZgBuAGcAbgBvAGgAZwBvAGgAbwBwACMAKwCVACMAlQCRAG4AbAB0AG4AdAB2AG8AbgB2AG8AdgB3AHAAbwB3AHAAdwB4AHEAcAB4AHEAeAB5AHIAcQB5AHIAeQB6AHMAcgB6AHMAegB7AEMASwCkAEMApACgAHoAeQB/AHoAfwACAHsAegACAHsAAgADAHsAAwCBAHsAgQC8ADMAOwCdADMAnQCZAHYAdAB9AHYAfQAAAHcAdgAAAHcAAAB+AHgAdwB+AHgAfgABAHkAeAABAHkAAQB/AIIAgACEAIIAhACHAIYAigBVAYYAVQFTAb8AgwBSAb8AUgFxAYcAhACIAIcAiACLAIsAiACMAIsAjACPAI4AkgBZAY4AWQFXAasArwBpAasAaQFnAY8AjACQAI8AkACTAJMAkACUAJMAlACXAJYAmgBdAZYAXQFbAZcAlACYAJcAmACbAIMAhgBTAYMAUwFSAZsAmACcAJsAnACfALMAtwBtAbMAbQFrAZ8AnAChAJ8AoQCjAJ4AogBhAZ4AYQFfAaMAoQClAKMApQCmAIoAjgBXAYoAVwFVAaYApQCpAKYAqQCqAKcAqwBnAacAZwFkAaoAqQCtAKoArQCuALsAvwBxAbsAcQFvAa4ArQCxAK4AsQCyAJIAlgBbAZIAWwFZAbIAsQC1ALIAtQC2AK8AswBrAa8AawFpAbYAtQC5ALYAuQC6AJoAngBfAZoAXwFdAbcAuwBvAbcAbwFtAboAuQC9ALoAvQC+AL4AvQCAAL4AgACCAKIApwBkAaIAZAFhAT4BxwDGAD4BxgDAAMEAyADHAMEAxwA+AT8ByQDIAD8ByADBAMIAygDJAMIAyQA/AcMAywDKAMMAygDCAMAAxgDEAMAAxAA8AcYAzgDMAMYAzADEAMcAzwDOAMcAzgDGAMgA0ADPAMgAzwDHAMkA0QDQAMkA0ADIAMoA0gDRAMoA0QDJAMsA0wDSAMsA0gDKANEA2QDYANEA2ADQANIA2gDZANIA2QDRANMA2wDaANMA2gDSAM4A1gDUAM4A1ADMAM8A1wDWAM8A1gDOANAA2ADXANAA1wDPANYA3gDcANYA3ADUANcA3wDeANcA3gDWANgA4ADfANgA3wDXANkA4QDgANkA4ADYANoA4gDhANoA4QDZANsA4wDiANsA4gDaAOIA6gDpAOIA6QDhAOMA6wDqAOMA6gDiAN4A5gDkAN4A5ADcAN8A5wDmAN8A5gDeAOAA6ADnAOAA5wDfAOEA6QDoAOEA6ADgAOcA7wDuAOcA7gDmAOgA8ADvAOgA7wDnAOkA8QDwAOkA8ADoAOoA8gDxAOoA8QDpAOsA8wDyAOsA8gDqAOYA7gDsAOYA7ADkAPMA+wD6APMA+gDyAO4A9gD0AO4A9ADsAO8A9wD2AO8A9gDuAPAA+AD3APAA9wDvAPEA+QD4APEA+ADwAPIA+gD5APIA+QDxAPcA/wD+APcA/gD2APgAAAH/APgA/wD3APkAAQEAAfkAAAH4APoAAgEBAfoAAQH5APsAAwECAfsAAgH6APYA/gD9APYA/QD0AAMBCwEKAQMBCgECAUQBRQHrAEQB6wDjAE0BTgErAU0BKwEjAf4ABgEFAf4ABQH9AP8ABwEGAf8ABgH+AAABCAEHAQABBwH/AAEBCQEIAQEBCAEAAQIBCgEJAQIBCQEBAQgBEAEPAQgBDwEHAQkBEQEQAQkBEAEIAQoBEgERAQoBEQEJAQsBEwESAQsBEgEKAUIBQwHbAEIB2wDTAEsBTAEbAUsBGwETAQYBDgENAQYBDQEFAQcBDwEOAQcBDgEGAU8BUAE7AU8BOwEzAUgBSQELAUgBCwEDAQ4BFgEVAQ4BFQENAQ8BFwEWAQ8BFgEOARABGAEXARABFwEPAREBGQEYAREBGAEQARIBGgEZARIBGQERARMBGwEaARMBGgESARgBIAEfARgBHwEXARkBIQEgARkBIAEYARoBIgEhARoBIQEZARsBIwEiARsBIgEaAUEBQgHTAEEB0wDLAEYBRwH7AEYB+wDzABYBHgEdARYBHQEVARcBHwEeARcBHgEWAU4BTwEzAU4BMwErAUwBTQEjAUwBIwEbAR4BJgElAR4BJQEdAR8BJwEmAR8BJgEeASABKAEnASABJwEfASEBKQEoASEBKAEgASIBKgEpASIBKQEhASMBKwEqASMBKgEiASkBMQEwASkBMAEoASoBMgExASoBMQEpASsBMwEyASsBMgEqAUMBRAHjAEMB4wDbAFABQAHDAFABwwA7ASYBLgEtASYBLQElAScBLwEuAScBLgEmASgBMAEvASgBLwEnAUUBRgHzAEUB8wDrAC4BNgE1AS4BNQEtAS8BNwE2AS8BNgEuATABOAE3ATABNwEvATEBOQE4ATEBOAEwATIBOgE5ATIBOQExATMBOwE6ATMBOgEyAUkBSwETAUkBEwELAToBwgA/AToBPwE5ATsBwwDCADsBwgA6AUcBSAEDAUcBAwH7ADYBwAA8ATYBPAE1ATcBPgHAADcBwAA2ATgBwQA+ATgBPgE3ATkBPwHBADkBwQA4AUoBQQFAAUoBQgFBAUoBQwFCAUoBRAFDAUoBRQFEAUoBRgFFAUoBRwFGAUoBSAFHAUoBSQFIAUoBSwFJAUoBTAFLAUoBTQFMAUoBTgFNAUoBTwFOAUoBUAFPAUoBQAFQAQwABADFAAwAxQDNABQADADNABQAzQDVABwAFADVABwA1QDdACQAHADdACQA3QDlACwAJADlACwA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LwAnAC8AMAAtACwAtgAuAC0AtgAuALYAugAvAC4AugAvALoAjQAvAI0ABAEQABgAQAAQAEAAPAAAAAgAOAAAADgAMwAgALIARQAIABAAPAAIADwAOAAYACAARQAYAEUAQAA0ADkAgAA0AIAAfgA1ADEANwA1ADcAOgA6ADcAOwA6ADsAPgA9AEEAhAA9AIQAggA+ADsAPwA+AD8AQgBCAD8AQwBCAEMASABHAPUAowBHAKMAhwBJAEQAsABJALAA9wA5AD0AggA5AIIAgABBAEYAhgBBAIYAhABRAMEA2wBQAFgAVwBQAFcATwBRAFkAWABRAFgAUABSAFoAWQBSAFkAUQBNAFUAUwBNAFMASwBOAFYAVQBOAFUATQBPAFcAVgBPAFYATgBVAF0AWwBVAFsAUwBWAF4AXQBWAF0AVQBXAF8AXgBXAF4AVgBYAGAAXwBYAF8AVwBZAGEAYABZAGAAWABaAGIAYQBaAGEAWQBhAGkAaABhAGgAYABiAGoAaQBiAGkAYQBdAGUAYwBdAGMAWwBeAGYAZQBeAGUAXQBfAGcAZgBfAGYAXgBgAGgAZwBgAGcAXwBmAG4AbQBmAG0AZQBnAG8AbgBnAG4AZgBoAHAAbwBoAG8AZwBpAHEAcABpAHAAaAABAXEAaQBlAG0AawBlAGsAYwBtAHQAcgBtAHIAawBuAHUAdABuAHQAbQBvAHYAdQBvAHUAbgBwAHcAdgBwAHYAbwDfAHcAcAB1AJMA1wB1ANcAdAB2AK0AkwB2AJMAdQB3AO4ArQB3AK0AdgB0ANcA3QB0AN0AcgDJAGoAYgB7AHwAWgB7AFoAUgB6AHsAUgB6AFIASgB8AH0AYgB8AGIAWgABAMUAvQABAL0ATAAJAAEATAAJAEwAVAARAAkAVAARAFQAXAAZABEAXAAZAFwAZAAhABkAZAAhAGQAbAApACEAbAApAGwAcwB5AHoASgB5AEoAnQCVAMMAfwCVAH8AgQCPANUATgCPAE4ATQCaAMYAAgCaAAIAAwCXAOYANACXADQAfgDJAKkAagDRANMAMACgAOoAAACRAKIAfQDvAP8A7gDvAO4AdwD5ALQAhQD5AIUAiADsAMEAUQDsAFEAUADoAL8AfADoAHwAewCLALgABQCLAAUABgDlAJ8AMgDlADIANgB4AJsAlgB4AJYA8QDKAN8AcQDiAJUA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NwA6ADsAOwA6ACsAOwArAC4AZABEAEMAZABDAD0APgBFAEQAPgBEAGQAZQBGAEUAZQBFAD4APwBHAEYAPwBGAGUAQACBABEBQAARAT8APQBDAEEAPQBBAGIAQwB0AI8AQwCPAEEARACfAHQARAB0AEMARQCLAJ8ARQCfAEQARgCuAIsARgCLAEUARwCUAK4ARwCuAEYAZwBoAGEAZwBhAFkAZgBnAFkAZgBZAFEATwDMANYASQBQAE8ASQBPAEgATwBXAFYATwBWAE4AUABYAFcAUABXAE8AUQBZAFgAUQBYAFAAaAB2AEAAaABAAGEATABUAFMATABTAEsATQBVAFQATQBUAEwATgBWAFUATgBVAE0AVABcAFsAVABbAFMAVQBdAFwAVQBcAFQAVgBeAF0AVgBdAFUAVwBfAF4AVwBeAFYAWABgAF8AWABfAFcAWQBhAGAAWQBgAFgAYAA/AGUAYABlAF8AYQBAAD8AYQA/AGAAXAA9AGIAXABiAFsAXQBkAD0AXQA9AFwAXgA+AGQAXgBkAF0AXwBlAD4AXwA+AF4ADwDvAPgADwD4AEoAFwAPAEoAFwBKAFIAHwAXAFIAHwBSAFoAJgAfAFoAJgBaAGMABAAmAGMABABjAEIAdgCBAEAAmwCsAAoAkACYAAQAkAAEAEIAfQCwAGsAoQCUAEcAuwD9AG4AuwBuAHAA+gC3ADAABQG/AE0ABQFNAEwAzQDRAAsABwFmAFEAnQB3AGgA4ADcAJYA4ACWAHoA7AC0AEkA+QAFAUwA+QBMAEsA/QDaAGwA/QBsAG4A8wDFAJIA3gDzAJIA3gCSAKoA3ADKALIA3ACyAJYACwHsAEkA2gAMAWwADAH0AGwA9gDqADQA9gA0AG0AUAC0AFEA0ADeAKoA0ACqAIkA8QD/AGgA8QBoAGcAxwC8ABEAxwARABIA1gDUAEgAwwDQAIkAwwCJAJ0A2ADxAGcA2ABnAGYAvAAAARAAvAAQABEAwQDYAGYAtgANAC8AtgDjAAwAtgAMAA0AAAHtAA4AAAEOABAA5gADAaMAsACyAHAA+gAwADIAvwDMAE4AvwBOAE0AzQALAAwAAwHgAHoAAwF6AKMA7gABAQQB7gAEAfcAAQG9AL4AAQG+AAQBywC+AL0AywC9AMgA1QDLAMgA1QDIANIA0wDVANIA0wDSAM4AzgAPAQkB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GQASABEAGQASABkAGgAGAA4ANAAGADQAMQAZABcAHwAZAB8AIQAaABkAIQAaACEAIgAbABoAIgAbACIAIwAcABsAIwAcACMAJAAdABwAJAAdACQAJQAeAB0AJQAeACUAJgAjACIAKgAjACoAKwAjAOQA4AAkAOAAHgEmACUA9wAmAPcADQEhAB8AJwAhACcAKQAiACEAKQAiACkAKgAeACYAQAAeAEAAPAApACcAAQEpAAEBEwEqACkAEwEqABMB0AArACoA0AArANAA2gAOABYAOAAOADgANAAAAAYAMQAAADEALQA/AEMAhQA/AIUAgwAvACwAMAAvADAAMwBFAP4AhwAzADAANQAzADUANwAyADYAfQAyAH0AewA3ADUAOQA3ADkAOgA6ADkAPQA6AD0APgA7AD8AgwA7AIMAgAA+AD0AQQA+AEEAQgBCAEEAzQBCAM0ARABEAM0A/ABDAEUAhwBDAIcAhQAuADIAewAuAHsAeQA2ADsAgAA2AIAAfQBJALUAqABPALcAwQBJAFEAUABJAFAARwBRAFkAWABRAFgAUAD1AHgAGgFMAFQAUwBMAFMASwBNAFUAVABNAFQATABOAFYAVQBOAFUATQBPAFcAVgBPAFYATgBQAFgAVwBQAFcATwBWAF4AXQBWAF0AVQBXAF8AXgBXAF4AVgBYAGAAXwBYAF8AVwBZAGEAYABZAGAAWAB2AHcAaQB2AGkAYQBUAFwAWwBUAFsAUwBVAF0AXABVAFwAVABzAHQAWQBzAFkAUQBcAGQAYwBcAGMAWwBdAGUAZABdAGQAXABeAGYAZQBeAGUAXQBfAGcAZgBfAGYAXgBgAGgAZwBgAGcAXwBhAGkAaABhAGgAYABmAG4AbQBmAG0AZQDoAG4AZgAcAeYAZwAAAWkAyQBxAHIASQBxAEkARgBkAGwAawBkAGsAYwBlAG0AbABlAGwAZAB3AHgAbwB3AG8AaQBsABcBCwFsAAsBawBtANMAFwFtABcBbABuAN8A0wBuANMAbQBvAPUAyQBxAJUAjgB0AHYAYQB0AGEAWQByAHMAUQByAFEASQB1AOIA1gB1APAA4gB1AAYB8AB1AIoA2AB1ANgABgF1AHAAjwB1AI8AigB1AHEAcAB1AHIAcQB1AHMAcgB1AHQAcwB1AHYAdAB1AHcAdgB1AHgAdwB1AOoA1QB1ANUAeAB1AAQB6gB1ABEBBAF1ANYAEQEIAAEAswAIALMASgAQAAgASgAQAEoAUgAYABAAUgAYAFIAWgAgABgAWgAgAFoAYgAoACAAYgAoAGIAagCBANwA7gCBAO4A8gCBAPIAFQGBABUB+ACBAPgAoACBAKAAvACBALwAkwCBAJMAegCBAHoAfACBAHwAfgCBAH4AfwCBAH8AggCBAIIAhACBAIQAhgCBAAgB7ACBAOwADwGBAA8BzgCBAM4A3ACuAIwATQCuAE0ATACdAL4AsACdALAAAACyAK4ATACyAEwASwCSALkALgCSAC4AeQCIAKQACwCIAAsADACcAJ0AAACcAAAALQCMAJgATgCMAE4ATQCiAAQACQCiAAkACgCoAMMApgCoAKYARgC6AJoALAC6ACwALwDGAAwADQCYALcATwCYAE8ATgCkAKIACgCkAAoACwC9AKEAiwC9AIsAkAAKAQIBKAAKASgAagDaAOQAKwDoAN8AbgAUAQMBDAEUAQwBGAHRABQBGAHRABgB0gDSAN4A2wDSANsA0QDeAOkA5QDeAOUA2wDpAOcA4QDpAOEA5QAgAeEA5wAgAecAHQH2AB8BGwH2ABsB/wAOAfYA/wAOAf8AyAD7AMsA9AD7APQAGQHMAA4ByADMAMgA9AAZAdQACQEZAQkB+gAIAYEAhgAIAYYA/QDUAOsA7QDUAO0ACQEQAe0A6wAQAesABQEFARIBzwAFAc8AEAHdAM8AEgHdABIB1wDXAOMA7wDXAO8A3QDjAPEA8wDjAPMA7wAWAfMA8QAWAfEABwH5ABYBBwH5AAcB2QChAPkA2QChANkAiwCRAJcAlACRAJQAvQCbALsAlgCbAJYAnwBGAKYAlQCnAMQAwACnAMAAngCfAJYApwDFAKkAsQDFALEAvwCtALEAqQCtAKkAtgCrAK0AtgCrALYASABIAMIAxwBIAMcAqwCJAMcAwgCJAMIAuAC4AJkApQC4AKUAiQCjAKUAmQCjAJkAjQAFAKMAjQAFAI0ArwCvALQAAwCvAAMABQAOAAYArACsAKoADgDgACQAIwAjACsA5AD3ACUAHgElACQAHgGoAEYASQBJAEcAtQDBAEcAUABQAE8AwQBvAHgA9QB4ANUAGgFmAGcA5gDmAOgAZgBnAGgAHAFoAAABHAFoAGkAAAFpAG8AyQCOAHAAcQBxAEYAlQANAKoAxgDGAIgADAAAAEQagi9beC29W3itrwAAAABbeC09PGANvQAAAABbeC09PGANvYzvx7yfcRC9jO/HvIzvx7yfcRC9jO/HvK1oWDxbeC09RJ0Cva1oWDxbeC09RJ0CvUSdAr2fcRC9rWhYvESdAr2fcRC9rWhYvIzvxzxbeC09jO/HvIzvxzxbeC09jO/HvDxgDb2fcRC9AAAAgDxgDb2fcRC9AAAAgDxgDb2fcRC9AAAAgESdAj1beC09rWhYvESdAj1beC09rWhYvESdAr2fcRC9rWhYPESdAr2fcRC9rWhYPDxgDT1beC09AAAAgDxgDT1beC09AAAAgIzvx7yfcRC9jO/HPIzvx7yfcRC9jO/HPESdAj1beC09rWhYPESdAj1beC09rWhYPK1oWLyfcRC9RJ0CPa1oWLyfcRC9RJ0CPYzvxzxbeC09jO/HPIzvxzxbeC09jO/HPAAAAACfcRC9PGANPQAAAACfcRC9PGANPa1oWDxbeC09RJ0CPa1oWDxbeC09RJ0CPa1oWDyfcRC9RJ0CPa1oWDyfcRC9RJ0CPQAAAABbeC09PGANPQAAAABbeC09PGANPYzvxzyfcRC9jO/HPIzvxzyfcRC9jO/HPK1oWLxbeC09RJ0CPa1oWLxbeC09RJ0CPUSdAj2fcRC9rWhYPESdAj2fcRC9rWhYPIzvx7xbeC09jO/HPIzvx7xbeC09jO/HPDxgDT2fcRC9AAAAgDxgDT2fcRC9AAAAgESdAr1beC09rWhYPESdAr1beC09rWhYPESdAj2fcRC9rWhYvESdAj2fcRC9rWhYvDxgDb1beC09AAAAgDxgDb1beC09AAAAgDxgDb1beC09AAAAgIzvxzyfcRC9jO/HvIzvxzyfcRC9jO/HvESdAr1beC09rWhYvESdAr1beC09rWhYvAAAAACfcRC9PGANvQAAAACfcRC9PGANvYzvx7xbeC09jO/HvIzvx7xbeC09jO/HvK1oWDyfcRC9RJ0Cva1oWDyfcRC9RJ0Cva1oWLxbeC09RJ0Cva1oWLxbeC09RJ0CvQAAAABbeC29W3gtvQAAAABbeC29W3gtvbDEhDxbeC29+EMgvbDEhDxbeC29+EMgvbDEhDxbeC09+EMgvbDEhDxbeC09+EMgvQAAAABbeC09W3gtvQAAAABbeC09W3gtveZS9TxbeC295lL1vOZS9TxbeC295lL1vOZS9TxbeC095lL1vOZS9TxbeC095lL1vPhDID1beC29sMSEvPhDID1beC29sMSEvPhDID1beC09sMSEvPhDID1beC09sMSEvFt4LT1beC29AAAAgFt4LT1beC29AAAAgFt4LT1beC09AAAAgFt4LT1beC09AAAAgPhDID1beC29sMSEPPhDID1beC29sMSEPPhDID1beC09sMSEPPhDID1beC09sMSEPOZS9TxbeC295lL1POZS9TxbeC295lL1POZS9TxbeC095lL1POZS9TxbeC095lL1PLDEhDxbeC29+EMgPbDEhDxbeC29+EMgPbDEhDxbeC09+EMgPbDEhDxbeC09+EMgPQAAAABbeC29W3gtPQAAAABbeC29W3gtPQAAAABbeC09W3gtPQAAAABbeC09W3gtPbDEhLxbeC29+EMgPbDEhLxbeC29+EMgPbDEhLxbeC09+EMgPbDEhLxbeC09+EMgPeZS9bxbeC295lL1POZS9bxbeC295lL1POZS9bxbeC095lL1POZS9bxbeC095lL1PLMIJ71reOC8qjK1O7MIJ71reOC8qjK1O7MIJ71reOA8qDK1O7MIJ71reOA8qDK1O1t4Lb3eU/a8W3ito1t4Lb3eU/a8W3ito1t4Lb3eU/a8W3ito1t4Lb3eU/Y8W3gto1t4Lb3eU/Y8W3gto1t4Lb3eU/Y8W3gto7MIJ71reOC8qDK1u7MIJ71reOC8qDK1u7MIJ71reOA8qDK1u7MIJ71reOA8qDK1u+ZS9bxbeC295lL1vOZS9bxbeC295lL1vOZS9bxbeC095lL1vOZS9bxbeC095lL1vLDEhLxbeC29+EMgvbDEhLxbeC29+EMgvbDEhLxbeC09+EMgvbDEhLxbeC09+EMgva1oWLyfcRC9RJ0Cva1oWLyfcRC9RJ0CvUQagq+ecRC9W3itr1t4LT1beK28AAAAgFt4LT0AAAAAAAAAgFt4LT1beK08AAAAgPhDID1beK28sMSEvPhDID0AAAAAsMSEvPhDID1beK08sMSEvOZS9TxbeK285lL1vOZS9TwAAAAA5lL1vOZS9TxbeK085lL1vLDEhDxbeK28+EMgvbDEhDwAAAAA+EMgvbDEhDxbeK08+EMgvQAAAABbeK08W3gtvQAAAAAAAAAAW3gtvQAAAABbeK28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SwBFAAAASQBNAAoADwBPAAoATwBLAAAATQBRAA8AEwBTAA8AUwBPAAAAUQBVABMAFwBXABMAVwBTAAAAVQBZABcAGwBbABcAWwBXAAAAWQBdABsAHwBfABsAXwBbAAAAXQBhAB8AIgBjAB8AYwBfAAAAYQBmACIAJgBoACIAaABjAAAAZgBqACYAKgBsACYAbABoAAAAagDOACoALgDBACoAwQBsAAAAzgDRAC4AMgC/AC4AvwDBAAAA0QDTADIANwDDADIAwwC/AAAA0wB8ADcAOwB+ADcAfgDDAAAAfACAADsAPwCCADsAggB+AAAAgABBAD8AAgBIAD8ASACCABoAFgAoABoAKAAkAAkABQA9AAkAPQA1ADgAMwANADgADQAIACcAIwAcACcAHAAZABYAEgAsABYALAAoAAEAQACEAAEAhAA5AAUAAQA5AAUAOQA9ADQALwARADQAEQAMACMAHgAgACMAIAAcABIADgAwABIAMAAsAEAAPAAEAEAABACEAC8AKwAVAC8AFQARAAsAEACFADwAOAAIADwACAAEAA4ACQA1AA4ANQAwAOMA5wDgAOMA4ADrADoAgwCFAC0AMQCFABAAFACFADEANgCFABQAGACFAIMAAwCFADYAPgCFABgAHQCFAD4AOgCFAB0AIQCFAAMABwCFACEAJQCFACUAKQCFAAcACwCFACkALQCFAO8A9ADfAO8A3wDmAOcA6ADqAOcA6gDgAKgAcAAEAagABAEHAegA5gDbAOgA2wDqANUAxAC3ANUAtwC4ANQA1QC4ANQAuAC5ANIA1AC5ANIAuQB7ALgAtwC6ALgAugC7ALkAuAC7ALkAuwC8AHsAuQC8AHsAvAB/ALsAugCSALsAkgCTALwAuwCTALwAkwCUAH8AvACUAH8AlABCAKoAqAAHAaoABwEDAWkApgDMAGkAzADNAKYApQDWAKYA1gDMAKUApADHAKUAxwDWAGUAowCmAGUApgBpAKMAogClAKMApQCmAKIAoQCkAKIApAClAGIAoACjAGIAowBlAKAAnwCiAKAAogCjAJ8AngChAJ8AoQCiAF4AnQCgAF4AoABiAJ0AnACfAJ0AnwCgAJwAmwCeAJwAngCfAFoAmgCdAFoAnQBeAJoAmQCcAJoAnACdAJkAmACbAJkAmwCcAFYAlwCaAFYAmgBaAJcAlgCZAJcAmQCaAJYAlQCYAJYAmACZAFIAhgCXAFIAlwBWAIYAhwCWAIYAlgCXAIcAiACVAIcAlQCWAE4AiQCGAE4AhgBSAIkAigCHAIkAhwCGAIoAiwCIAIoAiACHAEoAjACJAEoAiQBOAIwAjQCKAIwAigCJAI0AjgCLAI0AiwCKAEQAjwCMAEQAjABKAI8AkACNAI8AjQCMAJAAkQCOAJAAjgCNAEIAlACPAEIAjwBEAJQAkwCQAJQAkACPAJMAkgCRAJMAkQCQAHQAbwDAAHQAwAC+AHkAdQC9AHkAvQDCALMAsQDEALMAxADVALEAeQDCALEAwgDEAKcAqQDWAKcA1gDHAG8ApwDHAG8AxwDAAK4AswDVAK4A1QDLAKkArQDKAKkAygDWAKsAbQDNAKsAzQDMAG0AcQDQAG0A0ADNAHIAdwDSAHIA0gDPAHcAtQDUAHcA1ADSALUAxQDVALUA1QDUAMgAqwDMAMgAzADWAMUA2ADLAMUAywDVANcAyADWANcA1gDKAHoAsgD+AHoA/gABAeIA4wDrAOIA6wDdALQAsAD7ALQA+wD5AOUA4QDcAOUA3ADsAHYAegABAXYAAQH2AN8A3gDkAN8A5ADpAK8AqgADAa8AAwH6AOYA3wDpAOYA6QDbAP8A+ADrAP8A6wDgAAUB9wDbAAUB2wDpAPcAAAHqAPcA6gDbAPwAAgHsAPwA7ADcAAAB/wDgAAAB4ADqAAIBBgHkAAIB5ADsAAYBBQHpAAYB6QDkAPgA/QDdAPgA3QDrAA8BDQH0AA8B9ADvAA0BDgH1AA0B9QD0ABABCwHyABAB8gDxAAwBCgHwAAwB8ADzAAkBDwHvAAkB7wDuAAgBCQHuAAgB7gDtAAoBCAHtAAoB7QDwAA4BEAHxAA4B8QD1AP0A+AD5AP0A+QD7AAUBBgEHAQUBBwEEAQYBAgEDAQYBAwEHAf8AAAEBAf8AAQH+AAIB/AD6AAIB+gADAfcABQEEAfcABAH2AAAB9wD2AAAB9gABAfgA/wD+APgA/gD5AHAAdgD2AHAA9gAEAfQA9QDeAPQA3gDfAPEA8gDhAPEA4QDlAPMA8ADjAPMA4wDiAO4A7wDmAO4A5gDoAO0A7gDoAO0A6ADnAPAA7QDnAPAA5wDjAPUA8QDlAPUA5QDeAKwAyQAQAawAEAEOAcYAtgAIAcYACAEKAbYAeAAJAbYACQEIAXgAcwAPAXgADwEJAdkAxgAKAdkACgEMAckA2gALAckACwEQAW4ArAAOAW4ADgENAXMAbgANAXMADQEPAXIHSrpsbSG89F2Mrsg/zryghp26jO/HvMg/zryghp26jO/HvGLFBb2ghp26rWhYvGLFBb2ghp26rWhYvFqIEL2ghp263mCKL1qIEL2ghp263mCKL1qIEL2ghp263mCKL2LFBb2ghp26rWhYPGLFBb2ghp26rWhYPMg/zryghp26jO/HPMg/zryghp26jO/HPCQJZbyghp26RJ0CPSQJZbyghp26RJ0CPXYHSrqghp26PGANPXYHSrqghp26PGANPTbISzyghp26RJ0CPTbISzyghp26RJ0CPVCfwTyghp26jO/HPFCfwTyghp26jO/HPEzq/jyghp26rWhYPEzq/jyghp26rWhYPB44Cj2ghp263mCKLx44Cj2ghp263mCKL0zq/jyghp26rWhYvEzq/jyghp26rWhYvFCfwTyghp26jO/HvFCfwTyghp26jO/HvHYHSrqghp26PGANvXYHSrqghp26PGANvTbISzyghp26RJ0CvTbISzyghp26RJ0CvXYHSrpsbSG8W3gtvXYHSrpsbSG8W3gtvenofDxsbSG8+EMgvenofDxsbSG8+EMgvaoC7zxsbSG85lL1vKoC7zxsbSG85lL1vNobHT1sbSG8sMSEvNobHT1sbSG8sMSEvD1QKj1sbSG83mCKLz1QKj1sbSG83mCKL9obHT1sbSG8sMSEPNobHT1sbSG8sMSEPKoC7zxsbSG85lL1PKoC7zxsbSG85lL1POnofDxsbSG8+EMgPenofDxsbSG8+EMgPXYHSrpsbSG8W3gtPXYHSrpsbSG8W3gtPewUi7xsbSG8+EMgPewUi7xsbSG8+EMgPSKj+7xsbSG85lL1PCKj+7xsbSG85lL1PCKj+7xsbSG85lL1vCKj+7xsbSG85lL1vOwUi7xsbSG8+EMgvewUi7xsbSG8+EMgvSQJZbyghp26RJ0CvSQJZbyghp26RJ0CvXoHSrpghp269F2MrhZsI71sbSG8sMSEPBZsI71sbSG8sMSEPHmgML1sbSG83mCKL3mgML1sbSG83mCKL3mgML1sbSG83mCKLxZsI71sbSG8sMSEvBZsI71sbSG8sMSEvDbISzygOC07RJ0CvTbISzygOC07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fwBdAGcAgQCFAGEAgQBhAGkAhQCEAGYAhQBmAGEAdwB8AFoAdwBaAGgAjgCMAHIAjgByAGwAjACNAHMAjABzAHIAkACKAHAAkABwAG8AiwCJAG4AiwBuAHEAiACPAG0AiABtAGsAhwCIAGsAhwBrAGoAiQCHAGoAiQBqAG4AjQCQAG8AjQBvAHMAfAB3AHgAfAB4AHoAhACFAIYAhACGAIMAhQCBAIIAhQCCAIYAfgB/AIAAfgCAAH0AgQB7AHkAgQB5AIIAdgCEAIMAdgCDAHQAfwB2AHUAfwB1AIAAdwB+AH0AdwB9AHgAFgAeAHQAFgB0AIMAcgBzAFsAcgBbAFwAbwBwAF4AbwBeAGIAcQBuAGAAcQBgAF8AawBtAGMAawBjAGUAagBrAGUAagBlAGQAbgBqAGQAbgBkAGAAcwBvAGIAcwBiAFsAMQBNAJAAMQCQAI0ASgA7AIcASgCHAIkAOwAgAIgAOwCIAIcAIAAaAI8AIACPAIgAVgBKAIkAVgCJAIsATQBXAIoATQCKAJAAFAAxAI0AFACNAIwAGQAUAIwAGQCMAI4AnwCnAD4AnwA+AEAAsQCjACsAsQArAFAAqwDbAMoAqwDKACgApwCRAFEApwBRAD4AowCrACgAowAoACsAtQDhAD8AAgHQAOYAAgHmAP4A5wD9AOUA4wDtAKIA9gC3AKoAuQD/ALsAvQDEAA4A6wDeAA8A8wDRACUAzQABAcAAzQDAACcA3wDwAAAA3wAAABAAEQApAAcB1ADGAJ8A1ACfAEAArACkAK8ArACvAKEApAC0AKUApAClAK8AnAClALMAnACzAJcAmACUAK0AmACtAJsAkwCoAJkAkwCZAK0AqACgAKkAqACpAJkA9wCpAKAA9wCgAMcAoQDuANwAoQDcAKwAyADVALgAyAC4APgA1gD6AAYB1gAGAbgAugAGAfoAugD6AOIAAAG6AOIAAAHiALYAtgDZALwAtgC8AAAB2gAKAQQB2gAEAbwACgHSAL4ACgG+AAQB0gD1AMUA0gDFAL4A4ADsAOoA4ADqAPIADAHqAOwADAHsAAgB6AAMAQgB6AAIAcEAwgACAf4AwgD+AOgADgHmAM8ADgHPAPwA5AAOAfwA5AD8AMsAzADdAO8AzADvAOQA1wA8AAkBPAAjAAkBIwAlANEAAwABAOkACwHnAA0B5wDlAA0B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iCtLPmu9jjygHUE/J5+XPRhRMD6aGzI+gK1XPng1PT58tRg+eDU9Pny1GD4hcT48FE4uPt32iT1sKi4+1zU1PrQ5UT7XNTU+tDlRPuelkj2YGDI+gbMwPnT2VD6BszA+dPZUPhUADQAPABUADwAYABgADwARABgAEQAbABsAEQASABsAEgAeAFcAHgBMACMAVwBjADEAPQAnAAEAAwAMAAEADAAKAAMABQAOAAMADgAMAAUABwAQAAUAEAAOAAcACQATAAcAEwAQAAkAXABNAAkATQATAAQAAgCJAIkALwAxAAgABgAnAAgAJwAsAAAAgQB4AB8ARwBEAB4AJABHAB0AHAAfAB0AHwAgABwAGwAeABwAHgAfABoAGQAcABoAHAAdABkAGAAbABkAGwAcABcAFgAZABcAGQAaABYAFQAYABYAGAAZABQAbAAWABQAFgAXADwAGgAdACoANAAUACoAFAAXACYALgAdACYAHQAgADYAKgAXADYAFwAaADQAbwBzADQAcwAUAEQAXwBBAGYATgA/AGYAPwBeADgAUQAsADkAJgAgAE8AZwBRAFMAZQBVAHsAgwABAHsAAQAKAIgAFQAWAGkAfAALAHYAjAB0AGkADQAVAHIAbQAUAFYASwBbAFYAWwBUAFQAZABiAFQAYgBWAGQAUgAhAGQAIQBiAEYAIgBSAEYAUgBgAGAAUABDAGAAQwBFAFAAZgBeAFAAXgBDAEgAQABOAEgATgBYADoASgBaADoAWgA3ACUAOgA3ACUANwArAC0AJQArAC0AKwAoACgAPgA7ACgAOwAtAD4AMgA1AD4ANQA7ACkANQAyACkAMgAwADAAhQAzADAAMwApAIUAdQBuAIUAbgAzAHUAjgBxAHUAcQBuAGsAcACNAGsAjQB3AHcAiwCHAHcAhwBqAHkAggB6AHkAegBoAIoAgAB9AIoAfQCGAH8AAgAAAH8AAAB4AGgAfQCAAGgAgAB5ACMAHgBXAB4AEgBMACcABgAEAAQAMQAnAAIAfwCJAIkAdgAvAHYAdACEAC8AdgCEAEIAIABEACAAHwBEAEcAHwAeAB0ALgA8ADwANgAaAFMAVQAIAFUAXQAIAAgALABhADgAWQBPAGEAUwAIAE8AUQA4AFEAYQAsACAAQgA5AEIASQA5ABYAbACIAIgAfgAVABUAfgBpAGkACwANAIkAMQAEAA==" + } + ] +} diff --git a/Assets/Models/MD_BreakableObjects1.shmodel b/Assets/Models/MD_BreakableObjects1.shmodel new file mode 100644 index 0000000000000000000000000000000000000000..9d846e440be618cc2ee8536fe4c084367a765008 GIT binary patch literal 173714 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≠Yg9<|)L)(!9DxUjm^JARJ-8U3)WUtKyrY?iK% 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 literal 0 HcmV?d00001 diff --git a/Assets/Models/MD_BreakableObjects1.shmodel.shmeta b/Assets/Models/MD_BreakableObjects1.shmodel.shmeta new file mode 100644 index 00000000..00b578ff --- /dev/null +++ b/Assets/Models/MD_BreakableObjects1.shmodel.shmeta @@ -0,0 +1,46 @@ +Name: MD_BreakableObjects1 +ID: 68661596 +Type: 4 +Sub Assets: +Name: Plate01_Unshattered +ID: 140964851 +Type: 8 +Name: Plate01_Shattered.004 +ID: 136742160 +Type: 8 +Name: Plate01_Shattered.001 +ID: 146557542 +Type: 8 +Name: Plate01_Shattered.002 +ID: 145439064 +Type: 8 +Name: Plate01_Shattered.003 +ID: 139411134 +Type: 8 +Name: Bowl01_Unshattered +ID: 144894491 +Type: 8 +Name: Bowl01_Shattered.001 +ID: 140591337 +Type: 8 +Name: Bowl01_Shattered.002 +ID: 148464002 +Type: 8 +Name: Bowl01_Shattered.003 +ID: 144132426 +Type: 8 +Name: Cup01_Unshattered +ID: 134951952 +Type: 8 +Name: Cup01_Shattered.001 +ID: 140710075 +Type: 8 +Name: Cup01_Shattered.002 +ID: 147241586 +Type: 8 +Name: Cup01_Shattered.003 +ID: 141067850 +Type: 8 +Name: Cup01_Shattered.004 +ID: 142028102 +Type: 8 diff --git a/Assets/Scenes/Breakables.shade b/Assets/Scenes/Breakables.shade new file mode 100644 index 00000000..320cf4b2 --- /dev/null +++ b/Assets/Scenes/Breakables.shade @@ -0,0 +1,276 @@ +- EID: 0 + Name: Main Camera + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 0, y: 0.472120881, z: 2.53064919} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Camera Component: + Position: {x: 0, y: 0.472120881, z: 2.53064919} + Pitch: 0 + Yaw: 0 + Roll: 0 + Width: 1920 + Height: 1080 + Near: 0.00999999978 + Far: 10000 + Perspective: true + IsActive: true + Scripts: ~ +- EID: 2 + Name: Light + IsActive: true + NumberOfChildren: 0 + Components: + Light Component: + Position: {x: 0, y: 0, z: 0} + Type: Directional + Direction: {x: 0, y: 0, z: 1} + Color: {x: 1, y: 1, z: 1, w: 1} + Layer: 4294967295 + Strength: 0 + IsActive: true + Scripts: ~ +- EID: 1 + Name: Plate + IsActive: true + NumberOfChildren: 4 + Components: + Transform Component: + Translate: {x: 0.00451920554, y: 0.829197884, z: 1.75} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 0.999979734, y: 1, z: 0.999979734} + IsActive: true + Renderable Component: + Mesh: 140964851 + Material: 126974645 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.200000003, y: 0.100000001, z: 0.200000003} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: + - Type: Breakable + Enabled: true + threshHold: 0 + force: 1 +- EID: 4 + Name: Piece1 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 3.2922253e-07, y: 0, z: -0.0171992779} + Rotate: {x: -1.31316483e-07, y: 3.60887498e-09, z: 5.27542454e-11} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 146557542 + Material: 124370424 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.100000001, y: 0.0500000007, z: 0.150000006} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: ~ +- EID: 5 + Name: Piece2 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 2.47731805e-07, y: 0, z: 0.0325666666} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 145439064 + Material: 124370424 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.100000001, y: 0.0500000007, z: 0.200000003} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: ~ +- EID: 6 + Name: Piece3 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: -0.0502781346, y: 1.18017197e-05, z: 6.97374344e-05} + Rotate: {x: 0.021297913, y: 0.00146768149, z: -5.48749846e-08} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 139411134 + Material: 124370424 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.100000001, y: 0.0500000007, z: 0.150000006} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: ~ +- EID: 7 + Name: Piece4 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 0.0364143178, y: 7.39097595e-06, z: 6.61611557e-05} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 136742160 + Material: 124370424 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.100000001, y: 0.0500000007, z: 0.150000006} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: ~ +- EID: 3 + Name: Ground + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 0, y: -0.414889663, z: 0} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 5, y: 1, z: 5} + IsActive: true + RigidBody Component: + Type: Static + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 1, y: 1, z: 1} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: ~ \ No newline at end of file diff --git a/Assets/Scenes/Breakables.shade.shmeta b/Assets/Scenes/Breakables.shade.shmeta new file mode 100644 index 00000000..36920646 --- /dev/null +++ b/Assets/Scenes/Breakables.shade.shmeta @@ -0,0 +1,3 @@ +Name: Breakables +ID: 85040588 +Type: 5 diff --git a/Assets/Scenes/MainGame.shade b/Assets/Scenes/MainGame.shade index 78085e94..5b826678 100644 --- a/Assets/Scenes/MainGame.shade +++ b/Assets/Scenes/MainGame.shade @@ -8348,7 +8348,7 @@ NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 2.24178481, y: 1.4327563, z: 9.3920002} + Translate: {x: 2.24178481, y: 1.4327563, z: 9.46397972} Rotate: {x: -0, y: 0, z: -0} Scale: {x: 1, y: 1, z: 1} IsActive: true @@ -8535,10 +8535,9 @@ - Type: PickAndThrow Enabled: true throwForce: [8, 10, 8] - item: 51000 + cameraArmOffSet: [0, 0.25, 0] delayTimer: 1 aimingLength: 0.5 - inRange: false rayDistance: 0.5 - EID: 3 Name: HoldingPoint @@ -8612,4 +8611,622 @@ Rotate: {x: 0, y: 0, z: 0} Scale: {x: 1, y: 1, z: 1} IsActive: true + Scripts: ~ +- EID: 177 + Name: ====Breakables==== + IsActive: true + NumberOfChildren: 3 + Components: ~ + Scripts: ~ +- EID: 1 + Name: Plate + IsActive: true + NumberOfChildren: 4 + Components: + Transform Component: + Translate: {x: 1.10128331, y: 0.1594823, z: 7.20125246} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 0.999979734, y: 1, z: 0.999979734} + IsActive: true + Renderable Component: + Mesh: 140964851 + Material: 126974645 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.200000003, y: 0.100000001, z: 0.200000003} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: + - Type: Breakable + Enabled: true + threshHold: 0.100000001 + force: 1 +- EID: 196 + Name: Piece1 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 3.57627869e-07, y: 0, z: -0.0171995163} + Rotate: {x: -1.31316483e-07, y: 3.60887498e-09, z: 5.27542454e-11} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 146557542 + Material: 124370424 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.100000001, y: 0.0500000007, z: 0.150000006} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: ~ +- EID: 195 + Name: Piece2 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 2.38418579e-07, y: 0, z: 0.0325665474} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 145439064 + Material: 124370424 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.100000001, y: 0.0500000007, z: 0.200000003} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: ~ +- EID: 6 + Name: Piece3 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: -0.0502780676, y: 1.18017197e-05, z: 6.96182251e-05} + Rotate: {x: 0.021297913, y: 0.00146768149, z: -5.48749846e-08} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 139411134 + Material: 124370424 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.100000001, y: 0.0500000007, z: 0.150000006} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: ~ +- EID: 7 + Name: Piece4 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 0.0364142656, y: 7.39097595e-06, z: 6.6280365e-05} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 136742160 + Material: 124370424 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.100000001, y: 0.0500000007, z: 0.150000006} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: ~ +- EID: 176 + Name: Plate + IsActive: true + NumberOfChildren: 4 + Components: + Transform Component: + Translate: {x: 3.23750496, y: 0.1594823, z: 6.75670671} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 0.999979734, y: 1, z: 0.999979734} + IsActive: true + Renderable Component: + Mesh: 140964851 + Material: 126974645 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.200000003, y: 0.100000001, z: 0.200000003} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: + - Type: Breakable + Enabled: true + threshHold: 0.100000001 + force: 1 +- EID: 175 + Name: Piece1 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 3.57627869e-07, y: 0, z: -0.0171995163} + Rotate: {x: -1.31316483e-07, y: 3.60887498e-09, z: 5.27542454e-11} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 146557542 + Material: 124370424 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.100000001, y: 0.0500000007, z: 0.150000006} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: ~ +- EID: 174 + Name: Piece2 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 2.38418579e-07, y: 0, z: 0.0325665474} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 145439064 + Material: 124370424 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.100000001, y: 0.0500000007, z: 0.200000003} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: ~ +- EID: 173 + Name: Piece3 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: -0.0502780676, y: 1.18017197e-05, z: 6.96182251e-05} + Rotate: {x: 0.021297913, y: 0.00146768149, z: -5.48749846e-08} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 139411134 + Material: 124370424 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.100000001, y: 0.0500000007, z: 0.150000006} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: ~ +- EID: 172 + Name: Piece4 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 0.0364142656, y: 7.39097595e-06, z: 6.6280365e-05} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 136742160 + Material: 124370424 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.100000001, y: 0.0500000007, z: 0.150000006} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: ~ +- EID: 171 + Name: Plate + IsActive: true + NumberOfChildren: 4 + Components: + Transform Component: + Translate: {x: 3.48429918, y: 0.1594823, z: 7.54602051} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 0.999979734, y: 1, z: 0.999979734} + IsActive: true + Renderable Component: + Mesh: 140964851 + Material: 126974645 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.200000003, y: 0.100000001, z: 0.200000003} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: + - Type: Breakable + Enabled: true + threshHold: 0.100000001 + force: 1 +- EID: 170 + Name: Piece1 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 3.57627869e-07, y: 0, z: -0.0171995163} + Rotate: {x: -1.31316483e-07, y: 3.60887498e-09, z: 5.27542454e-11} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 146557542 + Material: 124370424 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.100000001, y: 0.0500000007, z: 0.150000006} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: ~ +- EID: 169 + Name: Piece2 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 2.38418579e-07, y: 0, z: 0.0325665474} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 145439064 + Material: 124370424 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.100000001, y: 0.0500000007, z: 0.200000003} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: ~ +- EID: 168 + Name: Piece3 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: -0.0502780676, y: 1.18017197e-05, z: 6.96182251e-05} + Rotate: {x: 0.021297913, y: 0.00146768149, z: -5.48749846e-08} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 139411134 + Material: 124370424 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.100000001, y: 0.0500000007, z: 0.150000006} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: ~ +- EID: 167 + Name: Piece4 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 0.0364142656, y: 7.39097595e-06, z: 6.6280365e-05} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 136742160 + Material: 124370424 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.100000001, y: 0.0500000007, z: 0.150000006} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true Scripts: ~ \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Breakable.cs b/Assets/Scripts/Gameplay/Breakable.cs new file mode 100644 index 00000000..06f8a5e3 --- /dev/null +++ b/Assets/Scripts/Gameplay/Breakable.cs @@ -0,0 +1,64 @@ +using SHADE; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Xml.Linq; + +public class Breakable : Script +{ + public float threshHold = 1.0f; + public float force = 2.0f; + private RigidBody rb; + private Transform trans; + private bool isBreak = false; + private List itemPieces = new List(); + private Random ran = new Random(); + protected override void awake() + { + rb = GetComponent(); + if (!rb) + Debug.LogError("RIGIDBODY EMPTY"); + + trans = GetComponent(); + if(!trans) + Debug.LogError("TRANSFORM EMPTY"); + + foreach (GameObject pieces in GameObject) + { + itemPieces.Add(pieces); + pieces.SetActive(false); + } + } + + protected override void update() + { + if (isBreak) + Break(); + } + + protected override void onCollisionEnter(CollisionInfo info) + { + if (rb.LinearVelocity.GetSqrMagnitude() > threshHold) + { + isBreak = true; + } + } + protected override void onTriggerEnter(CollisionInfo info) + { + + } + + private void Break() + { + foreach (GameObject item in itemPieces) + { + item.SetActive(true); + item.GetComponent().GlobalPosition = trans.LocalPosition + item.GetComponent().LocalPosition; + GameObject gO = item; + gO.Parent = GameObject.Null; + } + + isBreak = false; + Owner.SetActive(false); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Gameplay/Breakable.cs.shmeta b/Assets/Scripts/Gameplay/Breakable.cs.shmeta new file mode 100644 index 00000000..e8d90963 --- /dev/null +++ b/Assets/Scripts/Gameplay/Breakable.cs.shmeta @@ -0,0 +1,3 @@ +Name: Breakable +ID: 154790613 +Type: 9 diff --git a/Assets/Scripts/Gameplay/Player/SC_PickAndThrow.cs b/Assets/Scripts/Gameplay/Player/SC_PickAndThrow.cs index af9ebc5e..8cddbcd6 100644 --- a/Assets/Scripts/Gameplay/Player/SC_PickAndThrow.cs +++ b/Assets/Scripts/Gameplay/Player/SC_PickAndThrow.cs @@ -7,23 +7,22 @@ using static Item; public class PickAndThrow : Script { public Vector3 throwForce = new Vector3(100.0f, 200.0f, 100.0f); - public GameObject item; + public Vector3 cameraArmOffSet = new Vector3(0.0f, 0.25f, 0.0f); + public GameObject item { get; set; } public float delayTimer = 1.0f; public float aimingLength = 1.5f; private float timer; private PlayerController pc; - private Camera cam; private Transform itemTransform; private RigidBody itemRidibody; - private Transform raccoonHoldLocation; + private Collider itemCollider; private Item itemScript; + private Transform raccoonHoldLocation; private ThirdPersonCamera tpc; private float lastXDir; private float lastZDir; - public bool inRange = false; - - private Collider collider; + private bool inRange = false; [Tooltip("Lenght of ray")] public float rayDistance = 1; @@ -42,13 +41,6 @@ public class PickAndThrow : Script if(!tpc) Debug.LogError("TPC EMPTY"); - collider = GetComponent(); - if (!collider) - Debug.LogError("COLLIDER EMPTY"); - else - { - } - timer = delayTimer; } protected override void update() @@ -59,7 +51,7 @@ public class PickAndThrow : Script CalculateDir(); CastRay(); - if (pc && itemRidibody && itemTransform) + if (pc && itemRidibody && itemTransform && itemCollider) { if (pc.holdItem) { @@ -70,17 +62,20 @@ public class PickAndThrow : Script { pc.isAiming = true; pc.camArm.ArmLength = aimingLength; + pc.camArm.TargetOffset = cameraArmOffSet; } if (Input.GetMouseButtonUp(Input.MouseCode.LeftButton) && pc.isAiming) { Audio.PlaySFXOnce2D("event:/Raccoon/raccoon_throw"); pc.isAiming = false; - if(tpc) + pc.camArm.TargetOffset = Vector3.Zero; + if (tpc) pc.camArm.ArmLength = tpc.armLength; pc.holdItem = false; inRange = false; itemRidibody.IsGravityEnabled = true; + itemCollider.Enabled = true; if (itemScript) { Vector3 vec = new Vector3(throwForce.x * lastXDir, throwForce.y, throwForce.z * lastZDir); @@ -102,19 +97,24 @@ public class PickAndThrow : Script pc.holdItem = false; inRange = false; itemRidibody.IsGravityEnabled = true; + itemCollider.Enabled = true; ResetItemObject(); } if (Input.GetMouseButtonDown(Input.MouseCode.RightButton) && pc.isAiming) { pc.isAiming = false; + pc.camArm.TargetOffset = Vector3.Zero; if (tpc) pc.camArm.ArmLength = tpc.armLength; } } else if (!pc.holdItem) + { itemRidibody.IsGravityEnabled = true; + itemCollider.Enabled = true; + } } if (timer > delayTimer && pc && !pc.holdItem && inRange && Input.GetMouseButtonDown(Input.MouseCode.LeftButton)) @@ -135,6 +135,7 @@ public class PickAndThrow : Script { itemRidibody = null; itemTransform = null; + itemCollider = null; itemScript = null; item = new GameObject(); } @@ -145,7 +146,7 @@ public class PickAndThrow : Script if (item.GetScript() && !itemTransform && !itemRidibody ) { itemRidibody = item.GetComponent(); - if (itemRidibody == null) + if (!itemRidibody) Debug.Log("Item rb EMPTY"); else { @@ -155,27 +156,32 @@ public class PickAndThrow : Script } itemTransform = item.GetComponent(); - if (itemTransform == null) + if (!itemTransform) Debug.Log("Item transform EMPTY"); else { itemTransform.LocalEulerAngles = Vector3.Zero; } + itemCollider = item.GetComponent(); + if (!itemCollider) + Debug.Log("Item collider EMPTY"); + else + { + itemCollider.Enabled = false; + } + itemScript = item.GetScript(); if(!itemScript) Debug.Log("Item script EMPTY"); - } } private void CalculateDir() { - if (cam == null) - cam = GetComponentInChildren(); - else if (cam) + if (pc && pc.cam) { - Vector3 camerAixs = cam.GetForward(); + Vector3 camerAixs = pc.cam.GetForward(); camerAixs.y = 0; camerAixs.Normalise(); lastXDir = camerAixs.x; diff --git a/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs b/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs index e1ad2512..47dd2c41 100644 --- a/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs +++ b/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs @@ -28,7 +28,7 @@ public class PlayerController : Script public RigidBody rb { get; set; } public Transform tranform { get; set; } - private Camera cam; + public Camera cam { get; set; } public CameraArm camArm { get; set; } private PickAndThrow pat; private StateMachine stateMachine; diff --git a/Assets/Scripts/SC_MainMenu.cs b/Assets/Scripts/SC_MainMenu.cs index 3cf16aef..cd2caf3f 100644 --- a/Assets/Scripts/SC_MainMenu.cs +++ b/Assets/Scripts/SC_MainMenu.cs @@ -12,7 +12,7 @@ public class MainMenu : Script if (Input.GetKeyDown(Input.KeyCode.Space)) { Audio.PlaySFXOnce2D("event:/UI/mouse_down_element"); - SceneManager.ChangeScene(89830755); + SceneManager.ChangeScene(86098106); Audio.StopAllSounds(); } From 7003262a5d6ddde17a87b6a711ce9266b273338a Mon Sep 17 00:00:00 2001 From: mushgunAX Date: Fri, 25 Nov 2022 16:12:58 +0800 Subject: [PATCH 015/275] Fixed AI even after scene change -More reliance on the blackboard for keeping variables among a tree's nodes -Half extents of the AI fixed --- Assets/Scenes/MainGameWithAIFixed.shade | 2 +- .../BehaviourTree/Core/BehaviourTree.cs | 11 +++- .../AIBehaviour/Implemented/Homeowner1.cs | 54 +++++++++++++++-- .../Implemented/LeafNodes/LeafAttack.cs | 31 +++++++--- .../Implemented/LeafNodes/LeafChase.cs | 59 +++++++++++++------ .../Implemented/LeafNodes/LeafPatrol.cs | 39 +++++++----- .../Implemented/LeafNodes/LeafSearch.cs | 36 +++++++---- 7 files changed, 172 insertions(+), 60 deletions(-) diff --git a/Assets/Scenes/MainGameWithAIFixed.shade b/Assets/Scenes/MainGameWithAIFixed.shade index 2ec3155e..4a987708 100644 --- a/Assets/Scenes/MainGameWithAIFixed.shade +++ b/Assets/Scenes/MainGameWithAIFixed.shade @@ -8665,7 +8665,7 @@ Colliders: - Is Trigger: false Type: Box - Half Extents: {x: 1, y: 1.79999995, z: 0.400000006} + Half Extents: {x: 0.200000003, y: 0.899999976, z: 0.200000003} Friction: 0.400000006 Bounciness: 0 Density: 1 diff --git a/Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTree.cs b/Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTree.cs index 404e3df8..cdced78e 100644 --- a/Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTree.cs +++ b/Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTree.cs @@ -66,9 +66,16 @@ namespace SHADE_Scripting.AIBehaviour.BehaviourTree //Getters and setters for the blackboard public object GetData(string key) { + //Debug.Log("Getting Data " + key); object outData = null; - if (blackboard.TryGetValue(key, out outData)) return outData; - else return outData; + if (blackboard.TryGetValue(key, out outData)) + { + return outData; + } + else + { + return outData; + } } public void SetData(string key, object data) { diff --git a/Assets/Scripts/AIBehaviour/Implemented/Homeowner1.cs b/Assets/Scripts/AIBehaviour/Implemented/Homeowner1.cs index 8ebd0537..67c1034c 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/Homeowner1.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/Homeowner1.cs @@ -84,17 +84,56 @@ public partial class Homeowner1 : BehaviourTree _events = new Homeowner1Events(this); events.Initialise(); - //Initialise the waypoints here - if (waypointsPool) + //(25 Nov) DO NOT Initialise the data here + /*if (waypointsPool) { waypoints = (List)waypointsPool.GetChildren(); SetData("waypoints", waypoints); } + SetData("transform", GetComponent()); + SetData("rigidBody", GetComponent()); + SetData("eyeOffset", eyeOffset); + SetData("sightDistance", sightDistance); + SetData("patrolSpeed", patrolSpeed); + SetData("chaseSpeed", chaseSpeed); + SetData("turningSpeed", turningSpeed); + SetData("distanceToCapture", distanceToCapture); + SetData("baseCaptureTime", captureTime);*/ } //Called every tick protected override void Tick() { + Debug.Log("Ticking"); + //Update data + if (GetData("waypoints") == null) + { + if (waypointsPool) + SetData("waypoints", (List)waypointsPool.GetChildren()); + + List wpTemp = (List)GetData("waypoints"); + Debug.Log(wpTemp.Count.ToString()); + } + if (GetData("transform") == null) + SetData("transform", GetComponent()); + if (GetData("rigidBody") == null) + SetData("rigidBody", GetComponent()); + if (GetData("eyeOffset") == null || (Vector3)GetData("eyeOffset") != eyeOffset) + SetData("eyeOffset", eyeOffset); + if (GetData("sightDistance") == null || (float)GetData("sightDistance") != sightDistance) + SetData("sightDistance", sightDistance); + if (GetData("patrolSpeed") == null || (float)GetData("patrolSpeed") != patrolSpeed) + SetData("patrolSpeed", patrolSpeed); + if (GetData("chaseSpeed") == null || (float)GetData("chaseSpeed") != chaseSpeed) + SetData("chaseSpeed", chaseSpeed); + if (GetData("turningSpeed") == null || (float)GetData("turningSpeed") != turningSpeed) + SetData("turningSpeed", turningSpeed); + if (GetData("distanceToCapture") == null || (float)GetData("distanceToCapture") != distanceToCapture) + SetData("distanceToCapture", distanceToCapture); + if (GetData("baseCaptureTime") == null || (float)GetData("baseCaptureTime") != captureTime) + SetData("baseCaptureTime", captureTime); + + events.Tick(); //Footsteps SFX, move them somewhere else soon @@ -106,6 +145,7 @@ public partial class Homeowner1 : BehaviourTree Audio.PlaySFXOnce2D("event:/Homeowner/homeowner_footsteps"); footstepTimeRemaining = footstepSFXIntervalMultiplier; } + Debug.Log("Ticked"); } //Define the behaviour tree here @@ -113,20 +153,22 @@ public partial class Homeowner1 : BehaviourTree //The tree is called from the root every tick protected override BehaviourTreeNode CreateTree() { + Debug.Log("Creating Tree"); //Start from the root, structure it like this to make it look like a tree BehaviourTreeNode root = new BehaviourTreeSelector("Root", new List { new BehaviourTreeSequence("Alerted", new List { - new LeafSearch("SearchFOV", GetComponent(), eyeOffset, sightDistance), + new LeafSearch("SearchFOV"), new BehaviourTreeSequence("CatchPlayer", new List { - new LeafChase("Chasing", GetComponent(), GetComponent(), chaseSpeed, turningSpeed, distanceToCapture, captureTime), - new LeafAttack("Attacking", GameObject.Find("Player").GetValueOrDefault()) + new LeafChase("Chasing"), + new LeafAttack("Attacking") }) }), - new LeafPatrol("Patrol", GetComponent(), patrolSpeed, turningSpeed, GetComponent()) + new LeafPatrol("Patrol") }); + Debug.Log("Tree Created"); return root; } } \ No newline at end of file diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs index 56b5e1ef..b2cfeb52 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs @@ -28,28 +28,41 @@ public partial class LeafAttack : BehaviourTreeNode //FUNCTIONS public partial class LeafAttack : BehaviourTreeNode { - public LeafAttack(string name, GameObject p) : base (name) + public LeafAttack(string name) : base (name) { - player = p; + Debug.Log("LeafAttack ctor"); } public override BehaviourTreeNodeStatus Evaluate() { - //Debug.LogWarning("LeafAttack"); + Debug.LogWarning("LeafAttack"); //Fail if no target in blackboard? onEnter(BehaviourTreeNodeStatus.RUNNING); - //Succeed when stand in hurt box for long enough - //Debug.Log("Attempting to get blackboard data"); - float? captureTime = (float?)GetNodeData("captureTimeLeft"); - //Debug.Log("Got blackboard data"); - if (captureTime == null) + //Get Data + float? captureTime; + if (GetNodeData("captureTimeLeft") == null) { status = BehaviourTreeNodeStatus.FAILURE; onExit(BehaviourTreeNodeStatus.FAILURE); return status; } + else + captureTime = (float)GetNodeData("captureTime"); + + if (GameObject.Find("Player") == null) + { + status = BehaviourTreeNodeStatus.FAILURE; + onExit(BehaviourTreeNodeStatus.FAILURE); + return status; + } + else + player = GameObject.Find("Player").GetValueOrDefault(); + + //Succeed when stand in hurt box for long enough + //Debug.Log("Attempting to get blackboard data"); + //Debug.Log("Got blackboard data"); captureTime -= Time.DeltaTimeF; SetNodeData("captureTimeLeft", captureTime); //Debug.Log(captureTime.ToString()); @@ -64,7 +77,7 @@ public partial class LeafAttack : BehaviourTreeNode //Return running if not success - //Debug.Log("Success: Caught"); + Debug.Log("Success: Caught"); status = BehaviourTreeNodeStatus.RUNNING; onExit(BehaviourTreeNodeStatus.RUNNING); return status; diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs index cab81420..6beeb851 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs @@ -24,9 +24,9 @@ public partial class LeafChase : BehaviourTreeNode private Transform transform; private RigidBody rb; private float chaseSpeed; - private float turnSpeed; + private float turningSpeed; private float captureDistance; - private float captureTime; + private float baseCaptureTime; } //FUNCTIONS @@ -34,29 +34,54 @@ public partial class LeafChase : BehaviourTreeNode { //Despite inheriting from BehaviourTreeNode, we don't have children to this node, //and hence we don't need to inherit its constructors - public LeafChase(string name, Transform t, RigidBody rb, float cSpd, float tSpd, float capDist, float capTime) : base (name) + public LeafChase(string name) : base (name) { - transform = t; + Debug.Log("LeafChase ctor"); + } + + /* + * transform = t; this.rb = rb; chaseSpeed = cSpd; turnSpeed = tSpd; captureDistance = capDist; captureTime = capTime; - } + */ public override BehaviourTreeNodeStatus Evaluate() { - //Debug.LogWarning("LeafChase"); + Debug.LogWarning("LeafChase"); + + onEnter(BehaviourTreeNodeStatus.RUNNING); + + //Get Data + if (GetNodeData("transform") == null || + GetNodeData("rigidBody") == null || + GetNodeData("turningSpeed") == null || + GetNodeData("chaseSpeed") == null || + GetNodeData("distanceToCapture") == null || + GetNodeData("baseCaptureTime") == null) + { + status = BehaviourTreeNodeStatus.FAILURE; + onExit(BehaviourTreeNodeStatus.FAILURE); + return status; + } + else + { + transform = (Transform)GetNodeData("transform"); + rb = (RigidBody)GetNodeData("rigidBody"); + chaseSpeed = (float)GetNodeData("chaseSpeed"); + turningSpeed = (float)GetNodeData("turningSpeed"); + captureDistance = (float)GetNodeData("distanceToCapture"); + baseCaptureTime = (float)GetNodeData("baseCaptureTime"); + } //Fail if no target in blackboard if (GetNodeData("target") == null) { - //Debug.Log("Failure: No target in blackboard"); + Debug.Log("Failure: No target in blackboard"); return BehaviourTreeNodeStatus.FAILURE; } - - onEnter(BehaviourTreeNodeStatus.RUNNING); - Transform target = (Transform)GetNodeData("target"); Vector3 normalisedDifference = target.GlobalPosition - transform.GlobalPosition; @@ -66,7 +91,7 @@ public partial class LeafChase : BehaviourTreeNode //Over maximum distance, stop chase if ((transform.GlobalPosition - target.GlobalPosition).GetMagnitude() > 1000.0f) { - //Debug.Log("Failure: Over maximum distance"); + Debug.Log("Failure: Over maximum distance"); ClearNodeData("target"); if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == true) @@ -81,7 +106,7 @@ public partial class LeafChase : BehaviourTreeNode } else if (false) //TODO If collided against a wall { - //Debug.Log("Running: Collided against wall"); + Debug.Log("Running: Collided against wall"); SetNodeData("isPathfinding", true); status = BehaviourTreeNodeStatus.RUNNING; @@ -91,15 +116,15 @@ public partial class LeafChase : BehaviourTreeNode //Keep chasing else if ((transform.GlobalPosition - target.GlobalPosition).GetMagnitude() > captureDistance) { - //Debug.Log("Running: Chasing"); + Debug.Log("Running: Chasing"); Quaternion targetRotation = Quaternion.LookRotation(normalisedDifference, new Vector3(0.0f, 1.0f, 0.0f)); - transform.LocalRotation = Quaternion.Slerp(transform.LocalRotation, targetRotation, turnSpeed * Time.DeltaTimeF); + transform.LocalRotation = Quaternion.Slerp(transform.LocalRotation, targetRotation, turningSpeed * Time.DeltaTimeF); //TODO delete this when original intendd code above works with velocity being limited correctly rb.LinearVelocity = normalisedDifference * chaseSpeed; //Reset capture timing to base - SetNodeData("captureTimeLeft", captureTime); + SetNodeData("captureTimeLeft", baseCaptureTime); //Not capturing, don't play SFX SetNodeData("isCapturing", false); @@ -111,10 +136,10 @@ public partial class LeafChase : BehaviourTreeNode //Once player is close enough, perform attack else { - //Debug.Log("Success: Near enough. Begin attack"); + Debug.Log("Success: Near enough. Begin attack"); //Look at the correct direction Quaternion targetRotation = Quaternion.LookRotation(normalisedDifference, new Vector3(0.0f, 1.0f, 0.0f)); - transform.LocalRotation = Quaternion.Slerp(transform.LocalRotation, targetRotation, turnSpeed * Time.DeltaTimeF); + transform.LocalRotation = Quaternion.Slerp(transform.LocalRotation, targetRotation, turningSpeed * Time.DeltaTimeF); //Play SFX if (GetNodeData("isCapturing") != null && (bool)GetNodeData("isCapturing") == false) diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs index 3f66bf94..7a29b7c4 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs @@ -22,7 +22,7 @@ public partial class LeafPatrol : BehaviourTreeNode { //Waypoints and movement private Transform transform; - private List waypoints; + private List? waypoints; private RigidBody rb; private float patrolSpeed; private float turningSpeed; @@ -42,13 +42,8 @@ public partial class LeafPatrol : BehaviourTreeNode //Constructor, establish values here //Despite inheriting from BehaviourTreeNode, we don't have children to this //node, and hence we do not need to inherit its constructors - public LeafPatrol(string name, Transform t, float patrolSpeed, float turnSpeed, RigidBody rb) : base(name) + public LeafPatrol(string name) : base(name) { - transform = t; - this.patrolSpeed = patrolSpeed; - turningSpeed = turnSpeed; - this.rb = rb; - currentWaypointIndex = 0; } @@ -56,9 +51,28 @@ public partial class LeafPatrol : BehaviourTreeNode //le this node keep returning RUNNING as it is the last fallback node on tree public override BehaviourTreeNodeStatus Evaluate() { - //Debug.LogWarning("LeafPatrol"); + Debug.LogWarning("LeafPatrol"); onEnter(BehaviourTreeNodeStatus.RUNNING); - if(GetNodeData("currentWaypointIndex") == null) + + //Get data + if (GetNodeData("transform") == null || + GetNodeData("patrolSpeed") == null || + GetNodeData("turningSpeed") == null || + GetNodeData("rigidBody") == null) + { + status = BehaviourTreeNodeStatus.FAILURE; + onExit(BehaviourTreeNodeStatus.FAILURE); + return status; + } + else + { + transform = (Transform)GetNodeData("transform"); + patrolSpeed = (float)GetNodeData("patrolSpeed"); + turningSpeed = (float)GetNodeData("turningSpeed"); + rb = (RigidBody)GetNodeData("rigidBody"); + } + + if (GetNodeData("currentWaypointIndex") == null) { SetNodeData("currentWaypointIndex", 0); } @@ -73,7 +87,7 @@ public partial class LeafPatrol : BehaviourTreeNode //Move and cycle between waypoints private void MoveToWaypoint() { - //Debug.Log("MoveToWaypoint"); + Debug.Log("MoveToWaypoint"); //Waiting, do not move if (GetNodeData("isWaiting") != null) { @@ -82,19 +96,16 @@ public partial class LeafPatrol : BehaviourTreeNode ClearNodeData("isWaiting"); return; } - waypoints = (List)GetNodeData("waypoints"); if (waypoints == null) { return; } Vector3 targetPosition = waypoints[currentWaypointIndex].GetComponent().GlobalPosition; - //Reach waypoint by X and Z being near enough //Do not consider Y of waypoints yet Vector3 remainingDistance = targetPosition - transform.GlobalPosition; remainingDistance.y = 0.0f; - //Reached waypoint, cycle if (remainingDistance.GetSqrMagnitude() < 0.1f) { @@ -165,7 +176,7 @@ public partial class LeafPatrol : BehaviourTreeNode private void DelayAtWaypoint() { - //Debug.Log("DelayAtWaypoint"); + Debug.Log("DelayAtWaypoint"); waitCounter += Time.DeltaTimeF; if (waitCounter >= waitDuration) isWaiting = false; diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs index 6d8e0237..68a12be7 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs @@ -30,12 +30,9 @@ public partial class LeafSearch : BehaviourTreeNode //FUNCTIONS HERE public partial class LeafSearch : BehaviourTreeNode { - public LeafSearch(string name, Transform t, Vector3 eo, float sDist) : base(name) + public LeafSearch(string name) : base(name) { - transform = t; - eyeOffset = eo; - sightDistance = sDist; - player = null; + Debug.Log("LeafSearch ctor"); } //Helper, find the nearest unobstructed waypoint to return to when chase is over @@ -74,9 +71,26 @@ public partial class LeafSearch : BehaviourTreeNode public override BehaviourTreeNodeStatus Evaluate() { - //Debug.LogWarning("LeafSearch"); + Debug.LogWarning("LeafSearch"); onEnter(BehaviourTreeNodeStatus.RUNNING); + //Get data + if (GetNodeData("transform") == null || + GetNodeData("eyeOffset") == null || + GetNodeData("sightDistance") == null) + { + status = BehaviourTreeNodeStatus.FAILURE; + onExit(BehaviourTreeNodeStatus.FAILURE); + return status; + } + else + { + transform = (Transform)GetNodeData("transform"); + Debug.Log("Transform position: " + transform.GlobalPosition.x + " " + transform.GlobalPosition.y + " " + transform.GlobalPosition.z); + eyeOffset = (Vector3)GetNodeData("eyeOffset"); + sightDistance = (float)GetNodeData("sightDistance"); + } + //Search for player player = GameObject.Find("Player"); @@ -103,7 +117,7 @@ public partial class LeafSearch : BehaviourTreeNode //Fail if too far from vision range if ((plrT.GlobalPosition - transform.GlobalPosition).GetMagnitude() > sightDistance) { - //Debug.Log("Failure: Too far"); + Debug.Log("Failure: Too far"); handleChaseStop(); status = BehaviourTreeNodeStatus.FAILURE; onExit(BehaviourTreeNodeStatus.FAILURE); @@ -118,7 +132,7 @@ public partial class LeafSearch : BehaviourTreeNode //Debug.Log("Dot: " + Vector3.Dot(difference, lookDirection)); if (Vector3.Dot(difference, lookDirection) < 0.0f) { - //Debug.Log("Failure: Out of FOV"); + Debug.Log("Failure: Out of FOV"); handleChaseStop(); status = BehaviourTreeNodeStatus.FAILURE; onExit(BehaviourTreeNodeStatus.FAILURE); @@ -139,7 +153,7 @@ public partial class LeafSearch : BehaviourTreeNode //Diren may likely add ALL objects hit by the ray over December if (sightRayHit.Hit && sightRayHit.Other != player) { - //Debug.Log("Failure: Ray hit obstacle named " + sightRayHit.Other.GetValueOrDefault().Name + " ID" + sightRayHit.Other.GetValueOrDefault().EntityId); + Debug.Log("Failure: Ray hit obstacle named " + sightRayHit.Other.GetValueOrDefault().Name + " ID" + sightRayHit.Other.GetValueOrDefault().EntityId); handleChaseStop(); status = BehaviourTreeNodeStatus.FAILURE; onExit(BehaviourTreeNodeStatus.FAILURE); @@ -147,11 +161,11 @@ public partial class LeafSearch : BehaviourTreeNode } else if (sightRayHit.Hit && sightRayHit.Other == player) { - //Debug.Log("Ray hit player"); + Debug.Log("Ray hit player"); } //All checks for now succeeded - //Debug.Log("Success: Homeowner has sighted player"); + Debug.Log("Success: Homeowner has sighted player"); //Write player's transform into the blackboard SetNodeData("target", plrT); From 181af5b3e51118b7df25638105a629b199565637 Mon Sep 17 00:00:00 2001 From: Glence Date: Fri, 25 Nov 2022 16:23:26 +0800 Subject: [PATCH 016/275] fix throw food bug added inverse camera controls --- Assets/Application.SHConfig | 2 +- Assets/Scenes/MainGame.shade | 93 ++++++++++--------- .../Gameplay/Player/SC_PickAndThrow.cs | 66 +++++++------ .../Gameplay/Player/ThirdPersonCamera.cs | 13 ++- Assets/Scripts/Gameplay/SC_GameManager.cs | 5 + Assets/Scripts/Gameplay/SC_Item.cs | 10 +- 6 files changed, 105 insertions(+), 84 deletions(-) diff --git a/Assets/Application.SHConfig b/Assets/Application.SHConfig index 370665d2..5673556d 100644 --- a/Assets/Application.SHConfig +++ b/Assets/Application.SHConfig @@ -1,4 +1,4 @@ Start in Fullscreen: false -Starting Scene ID: 86098106 +Starting Scene ID: 97158628 Window Size: {x: 1920, y: 1080} Window Title: SHADE Engine \ No newline at end of file diff --git a/Assets/Scenes/MainGame.shade b/Assets/Scenes/MainGame.shade index 5b826678..53e7251a 100644 --- a/Assets/Scenes/MainGame.shade +++ b/Assets/Scenes/MainGame.shade @@ -8236,7 +8236,7 @@ Colliders: - Is Trigger: false Type: Box - Half Extents: {x: 0.100000001, y: 0.100000001, z: 0.100000001} + Half Extents: {x: 0.200000003, y: 0.200000003, z: 0.200000003} Friction: 0.400000006 Bounciness: 0 Density: 1 @@ -8280,7 +8280,7 @@ Colliders: - Is Trigger: false Type: Box - Half Extents: {x: 0.25, y: 0.075000003, z: 0.25} + Half Extents: {x: 0.5, y: 0.150000006, z: 0.5} Friction: 0.400000006 Bounciness: 0 Density: 1 @@ -8324,7 +8324,7 @@ Colliders: - Is Trigger: false Type: Box - Half Extents: {x: 0.150000006, y: 0.150000006, z: 0.150000006} + Half Extents: {x: 0.300000012, y: 0.300000012, z: 0.300000012} Friction: 0.400000006 Bounciness: 0 Density: 1 @@ -8538,6 +8538,7 @@ cameraArmOffSet: [0, 0.25, 0] delayTimer: 1 aimingLength: 0.5 + throwItem: false rayDistance: 0.5 - EID: 3 Name: HoldingPoint @@ -8586,6 +8587,8 @@ turnSpeedPitch: 0.300000012 turnSpeedYaw: 0.5 pitchClamp: 45 + inverseXControls: true + inverseYControls: true - EID: 9 Name: PlayerBag IsActive: true @@ -8668,8 +8671,8 @@ NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 3.57627869e-07, y: 0, z: -0.0171995163} - Rotate: {x: -1.31316483e-07, y: 3.60887498e-09, z: 5.27542454e-11} + Translate: {x: -0.0586929321, y: 0, z: 0} + Rotate: {x: -0, y: 3.6088712e-09, z: 1.97286229e-16} Scale: {x: 1, y: 1, z: 1} IsActive: true Renderable Component: @@ -8708,9 +8711,9 @@ NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 2.38418579e-07, y: 0, z: 0.0325665474} - Rotate: {x: 0, y: 0, z: 0} - Scale: {x: 1, y: 1, z: 1} + Translate: {x: -0.0104182959, y: 0, z: -0.0662035942} + Rotate: {x: -0, y: 0.174533099, z: -0} + Scale: {x: 0.999999881, y: 1, z: 0.999999881} IsActive: true Renderable Component: Mesh: 145439064 @@ -8748,9 +8751,9 @@ NumberOfChildren: 0 Components: Transform Component: - Translate: {x: -0.0502780676, y: 1.18017197e-05, z: 6.96182251e-05} - Rotate: {x: 0.021297913, y: 0.00146768149, z: -5.48749846e-08} - Scale: {x: 1, y: 1, z: 1} + Translate: {x: 0.0431699753, y: 1.18017197e-05, z: -0.0288243294} + Rotate: {x: -0, y: 0, z: 0.174533099} + Scale: {x: 0.999999881, y: 0.999999583, z: 1} IsActive: true Renderable Component: Mesh: 139411134 @@ -8788,8 +8791,8 @@ NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 0.0364142656, y: 7.39097595e-06, z: 6.6280365e-05} - Rotate: {x: 0, y: 0, z: 0} + Translate: {x: 0.0341523886, y: 0.0088942796, z: 0.0554270744} + Rotate: {x: -0, y: 0, z: -0} Scale: {x: 1, y: 1, z: 1} IsActive: true Renderable Component: @@ -8822,13 +8825,13 @@ Rotation Offset: {x: 0, y: 0, z: 0} IsActive: true Scripts: ~ -- EID: 176 +- EID: 65707 Name: Plate IsActive: true NumberOfChildren: 4 Components: Transform Component: - Translate: {x: 3.23750496, y: 0.1594823, z: 6.75670671} + Translate: {x: 2.97949171, y: 0.1594823, z: 7.20125246} Rotate: {x: -0, y: 0, z: -0} Scale: {x: 0.999979734, y: 1, z: 0.999979734} IsActive: true @@ -8866,14 +8869,14 @@ Enabled: true threshHold: 0.100000001 force: 1 -- EID: 175 +- EID: 65703 Name: Piece1 IsActive: true NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 3.57627869e-07, y: 0, z: -0.0171995163} - Rotate: {x: -1.31316483e-07, y: 3.60887498e-09, z: 5.27542454e-11} + Translate: {x: -0.0586929321, y: 0, z: 0} + Rotate: {x: -0, y: 3.6088712e-09, z: 1.97286229e-16} Scale: {x: 1, y: 1, z: 1} IsActive: true Renderable Component: @@ -8906,15 +8909,15 @@ Rotation Offset: {x: 0, y: 0, z: 0} IsActive: true Scripts: ~ -- EID: 174 +- EID: 65704 Name: Piece2 IsActive: true NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 2.38418579e-07, y: 0, z: 0.0325665474} - Rotate: {x: 0, y: 0, z: 0} - Scale: {x: 1, y: 1, z: 1} + Translate: {x: -0.0104182959, y: 0, z: -0.0662035942} + Rotate: {x: -0, y: 0.174533099, z: -0} + Scale: {x: 0.999999881, y: 1, z: 0.999999881} IsActive: true Renderable Component: Mesh: 145439064 @@ -8946,15 +8949,15 @@ Rotation Offset: {x: 0, y: 0, z: 0} IsActive: true Scripts: ~ -- EID: 173 +- EID: 65705 Name: Piece3 IsActive: true NumberOfChildren: 0 Components: Transform Component: - Translate: {x: -0.0502780676, y: 1.18017197e-05, z: 6.96182251e-05} - Rotate: {x: 0.021297913, y: 0.00146768149, z: -5.48749846e-08} - Scale: {x: 1, y: 1, z: 1} + Translate: {x: 0.0431699753, y: 1.18017197e-05, z: -0.0288243294} + Rotate: {x: -0, y: 0, z: 0.174533099} + Scale: {x: 0.999999881, y: 0.999999583, z: 1} IsActive: true Renderable Component: Mesh: 139411134 @@ -8986,14 +8989,14 @@ Rotation Offset: {x: 0, y: 0, z: 0} IsActive: true Scripts: ~ -- EID: 172 +- EID: 65706 Name: Piece4 IsActive: true NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 0.0364142656, y: 7.39097595e-06, z: 6.6280365e-05} - Rotate: {x: 0, y: 0, z: 0} + Translate: {x: 0.0341523886, y: 0.0088942796, z: 0.0554270744} + Rotate: {x: -0, y: 0, z: -0} Scale: {x: 1, y: 1, z: 1} IsActive: true Renderable Component: @@ -9026,13 +9029,13 @@ Rotation Offset: {x: 0, y: 0, z: 0} IsActive: true Scripts: ~ -- EID: 171 +- EID: 65712 Name: Plate IsActive: true NumberOfChildren: 4 Components: Transform Component: - Translate: {x: 3.48429918, y: 0.1594823, z: 7.54602051} + Translate: {x: 3.0075531, y: 0.1594823, z: 8.19898987} Rotate: {x: -0, y: 0, z: -0} Scale: {x: 0.999979734, y: 1, z: 0.999979734} IsActive: true @@ -9070,14 +9073,14 @@ Enabled: true threshHold: 0.100000001 force: 1 -- EID: 170 +- EID: 65708 Name: Piece1 IsActive: true NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 3.57627869e-07, y: 0, z: -0.0171995163} - Rotate: {x: -1.31316483e-07, y: 3.60887498e-09, z: 5.27542454e-11} + Translate: {x: -0.0586929321, y: 0, z: 0} + Rotate: {x: -0, y: 3.6088712e-09, z: 1.97286229e-16} Scale: {x: 1, y: 1, z: 1} IsActive: true Renderable Component: @@ -9110,15 +9113,15 @@ Rotation Offset: {x: 0, y: 0, z: 0} IsActive: true Scripts: ~ -- EID: 169 +- EID: 65709 Name: Piece2 IsActive: true NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 2.38418579e-07, y: 0, z: 0.0325665474} - Rotate: {x: 0, y: 0, z: 0} - Scale: {x: 1, y: 1, z: 1} + Translate: {x: -0.0104182959, y: 0, z: -0.0662035942} + Rotate: {x: -0, y: 0.174533099, z: -0} + Scale: {x: 0.999999881, y: 1, z: 0.999999881} IsActive: true Renderable Component: Mesh: 145439064 @@ -9150,15 +9153,15 @@ Rotation Offset: {x: 0, y: 0, z: 0} IsActive: true Scripts: ~ -- EID: 168 +- EID: 65710 Name: Piece3 IsActive: true NumberOfChildren: 0 Components: Transform Component: - Translate: {x: -0.0502780676, y: 1.18017197e-05, z: 6.96182251e-05} - Rotate: {x: 0.021297913, y: 0.00146768149, z: -5.48749846e-08} - Scale: {x: 1, y: 1, z: 1} + Translate: {x: 0.0431699753, y: 1.18017197e-05, z: -0.0288243294} + Rotate: {x: -0, y: 0, z: 0.174533099} + Scale: {x: 0.999999881, y: 0.999999583, z: 1} IsActive: true Renderable Component: Mesh: 139411134 @@ -9190,14 +9193,14 @@ Rotation Offset: {x: 0, y: 0, z: 0} IsActive: true Scripts: ~ -- EID: 167 +- EID: 65711 Name: Piece4 IsActive: true NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 0.0364142656, y: 7.39097595e-06, z: 6.6280365e-05} - Rotate: {x: 0, y: 0, z: 0} + Translate: {x: 0.0341523886, y: 0.0088942796, z: 0.0554270744} + Rotate: {x: -0, y: 0, z: -0} Scale: {x: 1, y: 1, z: 1} IsActive: true Renderable Component: diff --git a/Assets/Scripts/Gameplay/Player/SC_PickAndThrow.cs b/Assets/Scripts/Gameplay/Player/SC_PickAndThrow.cs index 8cddbcd6..9c879314 100644 --- a/Assets/Scripts/Gameplay/Player/SC_PickAndThrow.cs +++ b/Assets/Scripts/Gameplay/Player/SC_PickAndThrow.cs @@ -15,7 +15,7 @@ public class PickAndThrow : Script private float timer; private PlayerController pc; private Transform itemTransform; - private RigidBody itemRidibody; + private RigidBody itemRidigBody; private Collider itemCollider; private Item itemScript; private Transform raccoonHoldLocation; @@ -23,6 +23,7 @@ public class PickAndThrow : Script private float lastXDir; private float lastZDir; private bool inRange = false; + public bool throwItem = false; [Tooltip("Lenght of ray")] public float rayDistance = 1; @@ -45,18 +46,21 @@ public class PickAndThrow : Script } protected override void update() { + if(timer <= delayTimer) timer += Time.DeltaTimeF; CalculateDir(); CastRay(); - if (pc && itemRidibody && itemTransform && itemCollider) + if (pc && itemRidigBody && itemTransform && itemCollider) { if (pc.holdItem) { itemTransform.LocalPosition = raccoonHoldLocation.GlobalPosition; itemTransform.LocalRotation = pc.tranform.LocalRotation; + itemRidigBody.LinearVelocity = Vector3.Zero; + itemRidigBody.AngularVelocity = Vector3.Zero; if (Input.GetMouseButtonDown(Input.MouseCode.LeftButton)) { @@ -68,27 +72,15 @@ public class PickAndThrow : Script if (Input.GetMouseButtonUp(Input.MouseCode.LeftButton) && pc.isAiming) { Audio.PlaySFXOnce2D("event:/Raccoon/raccoon_throw"); + itemRidigBody.IsGravityEnabled = true; + itemCollider.GetCollisionShape(0).IsTrigger = false; pc.isAiming = false; pc.camArm.TargetOffset = Vector3.Zero; if (tpc) pc.camArm.ArmLength = tpc.armLength; pc.holdItem = false; inRange = false; - itemRidibody.IsGravityEnabled = true; - itemCollider.Enabled = true; - if (itemScript) - { - Vector3 vec = new Vector3(throwForce.x * lastXDir, throwForce.y, throwForce.z * lastZDir); - //to be change - if (itemScript.currCategory == ItemCategory.LIGHT) - itemRidibody.AddForce(vec * 0.3f); - if (itemScript.currCategory == ItemCategory.MEDIUM) - itemRidibody.AddForce(vec * 0.75f); - if (itemScript.currCategory == ItemCategory.HEAVY) - itemRidibody.AddForce(vec); - } - itemRidibody.LinearVelocity += pc.rb.LinearVelocity; - ResetItemObject(); + throwItem = true; timer = 0.0f; } @@ -96,8 +88,8 @@ public class PickAndThrow : Script { pc.holdItem = false; inRange = false; - itemRidibody.IsGravityEnabled = true; - itemCollider.Enabled = true; + itemRidigBody.IsGravityEnabled = true; + itemCollider.GetCollisionShape(0).IsTrigger = false; ResetItemObject(); } @@ -111,9 +103,9 @@ public class PickAndThrow : Script } else if (!pc.holdItem) - { - itemRidibody.IsGravityEnabled = true; - itemCollider.Enabled = true; + { + itemRidigBody.IsGravityEnabled = true; + itemCollider.GetCollisionShape(0).IsTrigger = false; } } @@ -129,11 +121,27 @@ public class PickAndThrow : Script protected override void fixedUpdate() { + if (throwItem && itemRidigBody && pc) + { + if (itemScript) + { + Vector3 vec = new Vector3(throwForce.x * lastXDir, throwForce.y, throwForce.z * lastZDir); + if (itemScript.currCategory == ItemCategory.LIGHT) + itemRidigBody.AddForce(vec * 0.2f); + if (itemScript.currCategory == ItemCategory.MEDIUM) + itemRidigBody.AddForce(vec * 0.75f); + if (itemScript.currCategory == ItemCategory.HEAVY) + itemRidigBody.AddForce(vec); + } + itemRidigBody.LinearVelocity += pc.rb.LinearVelocity; + throwItem = false; + ResetItemObject(); + } } private void ResetItemObject() { - itemRidibody = null; + itemRidigBody = null; itemTransform = null; itemCollider = null; itemScript = null; @@ -143,16 +151,14 @@ public class PickAndThrow : Script private void RetrieveItemComponets() { //get the transform of the given item - if (item.GetScript() && !itemTransform && !itemRidibody ) + if (item.GetScript() && !itemTransform && !itemRidigBody) { - itemRidibody = item.GetComponent(); - if (!itemRidibody) + itemRidigBody = item.GetComponent(); + if (!itemRidigBody) Debug.Log("Item rb EMPTY"); else { - itemRidibody.IsGravityEnabled = false; - itemRidibody.LinearVelocity = Vector3.Zero; - itemRidibody.AngularVelocity = Vector3.Zero; + itemRidigBody.IsGravityEnabled = false; } itemTransform = item.GetComponent(); @@ -168,7 +174,7 @@ public class PickAndThrow : Script Debug.Log("Item collider EMPTY"); else { - itemCollider.Enabled = false; + itemCollider.GetCollisionShape(0).IsTrigger = true; } itemScript = item.GetScript(); diff --git a/Assets/Scripts/Gameplay/Player/ThirdPersonCamera.cs b/Assets/Scripts/Gameplay/Player/ThirdPersonCamera.cs index dad87305..14eeb1d7 100644 --- a/Assets/Scripts/Gameplay/Player/ThirdPersonCamera.cs +++ b/Assets/Scripts/Gameplay/Player/ThirdPersonCamera.cs @@ -15,6 +15,8 @@ namespace SHADE_Scripting public float turnSpeedPitch = 0.3f; public float turnSpeedYaw = 0.5f; public float pitchClamp = 45.0f; + public bool inverseXControls = false; + public bool inverseYControls = false; protected override void awake() { @@ -42,8 +44,15 @@ namespace SHADE_Scripting if (arm) { Vector2 vel = Input.GetMouseVelocity(); - arm.Pitch -= vel.y * turnSpeedPitch * Time.DeltaTimeF; - arm.Yaw += vel.x * turnSpeedYaw * Time.DeltaTimeF; + if(inverseYControls) + arm.Pitch -= vel.y * turnSpeedPitch * Time.DeltaTimeF; + else + arm.Pitch += vel.y * turnSpeedPitch * Time.DeltaTimeF; + + if (inverseXControls) + arm.Yaw -= vel.x * turnSpeedYaw * Time.DeltaTimeF; + else + arm.Yaw += vel.x * turnSpeedYaw * Time.DeltaTimeF; if (arm.Pitch > pitchClamp) { diff --git a/Assets/Scripts/Gameplay/SC_GameManager.cs b/Assets/Scripts/Gameplay/SC_GameManager.cs index ae98a54e..0f54c33c 100644 --- a/Assets/Scripts/Gameplay/SC_GameManager.cs +++ b/Assets/Scripts/Gameplay/SC_GameManager.cs @@ -79,11 +79,13 @@ public class GameManager : Script if (timer > 0 && totalItemCount <= 0) { currGameState = GameState.WIN; + Audio.StopAllSounds(); SceneManager.ChangeScene(winScene); } else if(timer < 0) { currGameState = GameState.LOSE; + Audio.StopAllSounds(); SceneManager.ChangeScene(loseScene); } } @@ -93,14 +95,17 @@ public class GameManager : Script { if (Input.GetKeyDown(Input.KeyCode.F1)) { + Audio.StopAllSounds(); SceneManager.ChangeScene(loseScene); } if (Input.GetKeyDown(Input.KeyCode.F2)) { + Audio.StopAllSounds(); SceneManager.ChangeScene(winScene); } if (Input.GetKeyDown(Input.KeyCode.Escape)) { + Audio.StopAllSounds(); SceneManager.ChangeScene(97158628); } } diff --git a/Assets/Scripts/Gameplay/SC_Item.cs b/Assets/Scripts/Gameplay/SC_Item.cs index a08b78cc..86d8c518 100644 --- a/Assets/Scripts/Gameplay/SC_Item.cs +++ b/Assets/Scripts/Gameplay/SC_Item.cs @@ -18,12 +18,10 @@ public class Item : Script protected override void awake() { rb = GetComponent(); - if (rb) - { - rb.FreezeRotationX = false; - rb.FreezeRotationY = false; - rb.FreezeRotationZ = false; - } + } + + protected override void update() + { } protected override void onCollisionEnter(CollisionInfo info) From 27aadf67d3edc1834b9dc537c2b4ec795a2316bd Mon Sep 17 00:00:00 2001 From: Sri Sham Haran Date: Fri, 25 Nov 2022 19:03:23 +0800 Subject: [PATCH 017/275] threw in AI to maingame.shade --- Assets/Scenes/MainGame.shade | 136 ++++++++++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 1 deletion(-) diff --git a/Assets/Scenes/MainGame.shade b/Assets/Scenes/MainGame.shade index 53e7251a..b2f41cec 100644 --- a/Assets/Scenes/MainGame.shade +++ b/Assets/Scenes/MainGame.shade @@ -9232,4 +9232,138 @@ Position Offset: {x: 0, y: 0, z: 0} Rotation Offset: {x: 0, y: 0, z: 0} IsActive: true - Scripts: ~ \ No newline at end of file + Scripts: ~ +- EID: 166 + Name: ====WaypointPool==== + IsActive: true + NumberOfChildren: 7 + Components: ~ + Scripts: ~ +- EID: 165 + Name: 1 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 2.75, y: 0, z: -2} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Scripts: ~ +- EID: 164 + Name: 2 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: -0.25, y: 0, z: -3} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Scripts: ~ +- EID: 163 + Name: 3 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: -2, y: 0, z: -3.75} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Scripts: ~ +- EID: 162 + Name: 4 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: -3.75, y: 0, z: -2.25} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Scripts: ~ +- EID: 161 + Name: 5 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: -2.75, y: 0, z: 2.75} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Scripts: ~ +- EID: 160 + Name: 6 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: -1.5, y: 0, z: 4} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Scripts: ~ +- EID: 159 + Name: 7 + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 2.75, y: 0, z: 4} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Scripts: ~ +- EID: 158 + Name: ====AI===== + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 2.70000005, y: 0.100000001, z: -2} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 140697366 + Material: 129495479 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: false + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: true + Freeze Rotation Y: false + Freeze Rotation Z: true + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 1, y: 1.79999995, z: 0.400000006} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0.899999976, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: + - Type: Homeowner1 + Enabled: true + waypointsPool: 166 + patrolSpeed: 1 + chaseSpeed: 2 + turningSpeed: 5 + sightDistance: 8 + eyeOffset: [0, 1.64999998, 0] + distanceToCapture: 0.5 + captureTime: 0.5 + footstepSFXIntervalMultiplier: 0.5 \ No newline at end of file From 5b41e832c64772f4519116c4a6d670be48edbc82 Mon Sep 17 00:00:00 2001 From: mushgunAX Date: Fri, 25 Nov 2022 20:16:31 +0800 Subject: [PATCH 018/275] Enhanced AI with better chase logic - AI now keeps track of last waypoint he has seen player at and heads there in an attempt to chase him down - AI can be made to reverse his chase/patrol path by running around him - The scene with the AI fix is called "MainGameWithAIFixed" --- Assets/Scenes/MainGameWithAIFixed.shade | 5 +- .../BehaviourTree/Core/BehaviourTree.cs | 1 + .../AIBehaviour/Implemented/Homeowner1.cs | 15 ++- .../Implemented/LeafNodes/LeafAttack.cs | 36 +++++-- .../Implemented/LeafNodes/LeafChase.cs | 66 +++++++++--- .../Implemented/LeafNodes/LeafPatrol.cs | 102 ++++++++++++++++-- .../Implemented/LeafNodes/LeafSearch.cs | 51 ++++++--- 7 files changed, 219 insertions(+), 57 deletions(-) diff --git a/Assets/Scenes/MainGameWithAIFixed.shade b/Assets/Scenes/MainGameWithAIFixed.shade index 4a987708..d30799bc 100644 --- a/Assets/Scenes/MainGameWithAIFixed.shade +++ b/Assets/Scenes/MainGameWithAIFixed.shade @@ -8599,6 +8599,7 @@ Arm Length: 1 Look At Camera Origin: true Target Offset: {x: 0, y: 0, z: 0} + Camera Collision: false IsActive: true Scripts: - Type: SHADE_Scripting.ThirdPersonCamera @@ -8640,7 +8641,7 @@ Components: Transform Component: Translate: {x: 2.70000005, y: 0.100000001, z: -2} - Rotate: {x: -0, y: 0, z: -0} + Rotate: {x: -0, y: -2.09439516, z: -0} Scale: {x: 1, y: 1, z: 1} IsActive: true Renderable Component: @@ -8665,7 +8666,7 @@ Colliders: - Is Trigger: false Type: Box - Half Extents: {x: 0.200000003, y: 0.899999976, z: 0.200000003} + Half Extents: {x: 0.400000006, y: 1.79999995, z: 0.400000006} Friction: 0.400000006 Bounciness: 0 Density: 1 diff --git a/Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTree.cs b/Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTree.cs index cdced78e..092c4087 100644 --- a/Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTree.cs +++ b/Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTree.cs @@ -74,6 +74,7 @@ namespace SHADE_Scripting.AIBehaviour.BehaviourTree } else { + //Debug.LogError("Cannot retrieve data " + key); return outData; } } diff --git a/Assets/Scripts/AIBehaviour/Implemented/Homeowner1.cs b/Assets/Scripts/AIBehaviour/Implemented/Homeowner1.cs index 67c1034c..ff315df2 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/Homeowner1.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/Homeowner1.cs @@ -104,15 +104,14 @@ public partial class Homeowner1 : BehaviourTree //Called every tick protected override void Tick() { - Debug.Log("Ticking"); + //Debug.Log("Ticking"); //Update data if (GetData("waypoints") == null) { - if (waypointsPool) + if (waypointsPool != GameObject.Null) SetData("waypoints", (List)waypointsPool.GetChildren()); - - List wpTemp = (List)GetData("waypoints"); - Debug.Log(wpTemp.Count.ToString()); + else + Debug.LogError("No waypoints, no AI"); } if (GetData("transform") == null) SetData("transform", GetComponent()); @@ -145,7 +144,7 @@ public partial class Homeowner1 : BehaviourTree Audio.PlaySFXOnce2D("event:/Homeowner/homeowner_footsteps"); footstepTimeRemaining = footstepSFXIntervalMultiplier; } - Debug.Log("Ticked"); + //Debug.Log("Ticked"); } //Define the behaviour tree here @@ -153,7 +152,7 @@ public partial class Homeowner1 : BehaviourTree //The tree is called from the root every tick protected override BehaviourTreeNode CreateTree() { - Debug.Log("Creating Tree"); + //Debug.Log("Creating Tree"); //Start from the root, structure it like this to make it look like a tree BehaviourTreeNode root = new BehaviourTreeSelector("Root", new List { @@ -168,7 +167,7 @@ public partial class Homeowner1 : BehaviourTree }), new LeafPatrol("Patrol") }); - Debug.Log("Tree Created"); + //Debug.Log("Tree Created"); return root; } } \ No newline at end of file diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs index b2cfeb52..71a23115 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs @@ -21,7 +21,7 @@ using System.Threading.Tasks; //VARIABLES public partial class LeafAttack : BehaviourTreeNode { - //Holds the player game object + //Holds the player game object that is to be targeted private GameObject player; } @@ -30,12 +30,36 @@ public partial class LeafAttack : BehaviourTreeNode { public LeafAttack(string name) : base (name) { - Debug.Log("LeafAttack ctor"); + //Debug.Log("LeafAttack ctor"); + } + + //Helper, find the nearest unobstructed waypoint to return to when chase is over + public void reevaluateWaypoint() + { + List waypoints = (List)GetNodeData("waypoints"); + Transform transform = (Transform)GetNodeData("transform"); + + if (waypoints == null || transform == null) + { + SetNodeData("currentWaypointIndex", 0); + return; + } + + int nearestWaypointIndex = 0; + for (int i = 0; i < waypoints.Count; ++i) + { + if ((transform.GlobalPosition - waypoints[i].GetComponent().GlobalPosition).GetSqrMagnitude() < + (transform.GlobalPosition - waypoints[nearestWaypointIndex].GetComponent().GlobalPosition).GetSqrMagnitude()) + { + nearestWaypointIndex = i; + } + } + SetNodeData("currentWaypointIndex", nearestWaypointIndex); } public override BehaviourTreeNodeStatus Evaluate() { - Debug.LogWarning("LeafAttack"); + //Debug.LogWarning("LeafAttack"); //Fail if no target in blackboard? onEnter(BehaviourTreeNodeStatus.RUNNING); @@ -49,7 +73,7 @@ public partial class LeafAttack : BehaviourTreeNode return status; } else - captureTime = (float)GetNodeData("captureTime"); + captureTime = (float)GetNodeData("captureTimeLeft"); if (GameObject.Find("Player") == null) { @@ -69,6 +93,7 @@ public partial class LeafAttack : BehaviourTreeNode if (captureTime <= 0.0f) { //Catch player when in range for long enough + //Debug.Log("Success: Caught"); player.GetScript().currentState = PlayerController.RaccoonStates.CAUGHT; status = BehaviourTreeNodeStatus.SUCCESS; onExit(BehaviourTreeNodeStatus.SUCCESS); @@ -76,8 +101,7 @@ public partial class LeafAttack : BehaviourTreeNode } //Return running if not success - - Debug.Log("Success: Caught"); + //Debug.Log("Running: About to capture in " + captureTimeLeft); status = BehaviourTreeNodeStatus.RUNNING; onExit(BehaviourTreeNodeStatus.RUNNING); return status; diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs index 6beeb851..de3352d6 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs @@ -36,21 +36,36 @@ public partial class LeafChase : BehaviourTreeNode //and hence we don't need to inherit its constructors public LeafChase(string name) : base (name) { - Debug.Log("LeafChase ctor"); + //Debug.Log("LeafChase ctor"); } - /* - * transform = t; - this.rb = rb; - chaseSpeed = cSpd; - turnSpeed = tSpd; - captureDistance = capDist; - captureTime = capTime; - */ + //Helper, find which waypoint player is closest to + private void determinePlayerWaypoint() + { + List waypoints = (List)GetNodeData("waypoints"); + Transform target = (Transform)GetNodeData("target"); + if (waypoints == null || target == null) + { + return; + } + + int nearestWaypointIndex = 0; + for (int i = 0; i < waypoints.Count; ++i) + { + if ((target.GlobalPosition - waypoints[i].GetComponent().GlobalPosition).GetSqrMagnitude() < + (target.GlobalPosition - waypoints[nearestWaypointIndex].GetComponent().GlobalPosition).GetSqrMagnitude()) + { + nearestWaypointIndex = i; + } + } + //Debug.Log("Player is nearest " + nearestWaypointIndex); + //Debug.Log("I'm at " + (int)GetNodeData("currentWaypointIndex")); + SetNodeData("playerLastSightedWaypointIndex", nearestWaypointIndex); + } public override BehaviourTreeNodeStatus Evaluate() { - Debug.LogWarning("LeafChase"); + //Debug.LogWarning("LeafChase"); onEnter(BehaviourTreeNodeStatus.RUNNING); @@ -79,7 +94,7 @@ public partial class LeafChase : BehaviourTreeNode //Fail if no target in blackboard if (GetNodeData("target") == null) { - Debug.Log("Failure: No target in blackboard"); + //Debug.Log("Failure: No target in blackboard"); return BehaviourTreeNodeStatus.FAILURE; } Transform target = (Transform)GetNodeData("target"); @@ -91,7 +106,7 @@ public partial class LeafChase : BehaviourTreeNode //Over maximum distance, stop chase if ((transform.GlobalPosition - target.GlobalPosition).GetMagnitude() > 1000.0f) { - Debug.Log("Failure: Over maximum distance"); + //Debug.Log("Failure: Over maximum distance"); ClearNodeData("target"); if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == true) @@ -106,7 +121,7 @@ public partial class LeafChase : BehaviourTreeNode } else if (false) //TODO If collided against a wall { - Debug.Log("Running: Collided against wall"); + //Debug.Log("Running: Collided against wall"); SetNodeData("isPathfinding", true); status = BehaviourTreeNodeStatus.RUNNING; @@ -116,12 +131,31 @@ public partial class LeafChase : BehaviourTreeNode //Keep chasing else if ((transform.GlobalPosition - target.GlobalPosition).GetMagnitude() > captureDistance) { - Debug.Log("Running: Chasing"); + //Debug.Log("Running: Chasing"); Quaternion targetRotation = Quaternion.LookRotation(normalisedDifference, new Vector3(0.0f, 1.0f, 0.0f)); transform.LocalRotation = Quaternion.Slerp(transform.LocalRotation, targetRotation, turningSpeed * Time.DeltaTimeF); + + //Determine the player's nearest waypoint as long as the AI can see the player + //Head towards that waypoint in an attempt to chase the player + determinePlayerWaypoint(); + //TODO delete this when original intendd code above works with velocity being limited correctly - rb.LinearVelocity = normalisedDifference * chaseSpeed; + //Only chase the player directly if the player's waypoint matches the AI's own + if (GetNodeData("currentWaypointIndex") != null && GetNodeData("playerLastSightedWaypointIndex") != null) + { + if ((int)GetNodeData("currentWaypointIndex") == (int)GetNodeData("playerLastSightedWaypointIndex")) + { + //Debug.Log("Waypoint indicees matching. Chasing directly"); + rb.LinearVelocity = normalisedDifference * chaseSpeed; + } + else + { + status = BehaviourTreeNodeStatus.FAILURE; + onExit(BehaviourTreeNodeStatus.FAILURE); + return BehaviourTreeNodeStatus.FAILURE; + } + } //Reset capture timing to base SetNodeData("captureTimeLeft", baseCaptureTime); @@ -136,7 +170,7 @@ public partial class LeafChase : BehaviourTreeNode //Once player is close enough, perform attack else { - Debug.Log("Success: Near enough. Begin attack"); + //Debug.Log("Success: Near enough. Begin attack"); //Look at the correct direction Quaternion targetRotation = Quaternion.LookRotation(normalisedDifference, new Vector3(0.0f, 1.0f, 0.0f)); transform.LocalRotation = Quaternion.Slerp(transform.LocalRotation, targetRotation, turningSpeed * Time.DeltaTimeF); diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs index 7a29b7c4..2f9c8e1a 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs @@ -25,10 +25,12 @@ public partial class LeafPatrol : BehaviourTreeNode private List? waypoints; private RigidBody rb; private float patrolSpeed; + private float chaseSpeed; private float turningSpeed; private float retreatTimer = 0.0f; private int currentWaypointIndex = 0; private bool retreatState = false; + private bool goingForwards = true; //Small delays between waypoints private bool isWaiting = false; @@ -51,12 +53,13 @@ public partial class LeafPatrol : BehaviourTreeNode //le this node keep returning RUNNING as it is the last fallback node on tree public override BehaviourTreeNodeStatus Evaluate() { - Debug.LogWarning("LeafPatrol"); + //Debug.LogWarning("LeafPatrol"); onEnter(BehaviourTreeNodeStatus.RUNNING); //Get data if (GetNodeData("transform") == null || GetNodeData("patrolSpeed") == null || + GetNodeData("chaseSpeed") == null || GetNodeData("turningSpeed") == null || GetNodeData("rigidBody") == null) { @@ -68,6 +71,7 @@ public partial class LeafPatrol : BehaviourTreeNode { transform = (Transform)GetNodeData("transform"); patrolSpeed = (float)GetNodeData("patrolSpeed"); + chaseSpeed = (float)GetNodeData("chaseSpeed"); turningSpeed = (float)GetNodeData("turningSpeed"); rb = (RigidBody)GetNodeData("rigidBody"); } @@ -87,11 +91,14 @@ public partial class LeafPatrol : BehaviourTreeNode //Move and cycle between waypoints private void MoveToWaypoint() { - Debug.Log("MoveToWaypoint"); + //Debug.Log("MoveToWaypoint"); //Waiting, do not move if (GetNodeData("isWaiting") != null) { - waitCounter = 0.0f; + //Only wait to change waypoints if not alert + if (GetNodeData("isAlert") != null && !(bool)GetNodeData("isAlert")) + waitCounter = 0.0f; + isWaiting = true; ClearNodeData("isWaiting"); return; @@ -109,15 +116,69 @@ public partial class LeafPatrol : BehaviourTreeNode //Reached waypoint, cycle if (remainingDistance.GetSqrMagnitude() < 0.1f) { + //If alert, may reverse + if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert")) + { + //If alert, may reverse if it's closer to the player + if (GetNodeData("playerLastSightedWaypointIndex") != null) + { + int playerWaypoint = (int)GetNodeData("playerLastSightedWaypointIndex"); + int forwardDistance = 0; + int backDistance = 0; + if (playerWaypoint < currentWaypointIndex) + { + //Player waypoint is behind current waypoint + forwardDistance = playerWaypoint + waypoints.Count() - currentWaypointIndex; + backDistance = currentWaypointIndex - playerWaypoint; + } + else + { + //Player waypoint is ahead of current waypoint (or same) + forwardDistance = playerWaypoint - currentWaypointIndex; + backDistance = currentWaypointIndex + waypoints.Count() - playerWaypoint; + } + + if (backDistance < forwardDistance) + { + //Go backwards + goingForwards = false; + + } + else + { + //Go forward + goingForwards = true; + } + } + else + { + //Fallback if no player waypoint data, go forward + goingForwards = true; + } + } + //Cycle waypoints - ++currentWaypointIndex; - if (currentWaypointIndex >= waypoints.Count()) - currentWaypointIndex = 0; + if (goingForwards) + { + ++currentWaypointIndex; + if (currentWaypointIndex >= waypoints.Count()) + currentWaypointIndex = 0; + } + else + { + --currentWaypointIndex; + if (currentWaypointIndex < 0) + currentWaypointIndex = waypoints.Count() - 1; + } + //Write to blackboard SetNodeData("currentWaypointIndex", currentWaypointIndex); - waitCounter = 0.0f; + //Only wait to change waypoints if not alert + if (GetNodeData("isAlert") != null && !(bool)GetNodeData("isAlert")) + waitCounter = 0.0f; + isWaiting = true; } else if (false /*Physics.OverlapSphere(_selfTransform.position, 0.3f, 1 << 8).Length > 0 && retreatState == false*/) @@ -159,8 +220,30 @@ public partial class LeafPatrol : BehaviourTreeNode //TODO delete this when original intended code above works with velocity being limited correctly if (rb != null) { - //Debug.Log("Null check passed?"); - rb.LinearVelocity = normalisedDifference * patrolSpeed; + //Move quickly if alert + if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert")) + { + //Debug.Log("Fast Patrol"); + rb.LinearVelocity = normalisedDifference * chaseSpeed; + } + else + { + rb.LinearVelocity = normalisedDifference * patrolSpeed; + } + + //Unalert if AI reaches player nearest + if (GetNodeData("currentWaypointIndex") != null && GetNodeData("playerLastSightedWaypointIndex") != null) + { + if ((int)GetNodeData("currentWaypointIndex") == (int)GetNodeData("playerLastSightedWaypointIndex")) + { + if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert")) + { + //Debug.Log("Unalert"); + Audio.PlaySFXOnce2D("event:/Homeowner/humming"); + } + SetNodeData("isAlert", false); + } + } } if (retreatState) { @@ -176,7 +259,6 @@ public partial class LeafPatrol : BehaviourTreeNode private void DelayAtWaypoint() { - Debug.Log("DelayAtWaypoint"); waitCounter += Time.DeltaTimeF; if (waitCounter >= waitDuration) isWaiting = false; diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs index 68a12be7..4fa5e376 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs @@ -32,11 +32,11 @@ public partial class LeafSearch : BehaviourTreeNode { public LeafSearch(string name) : base(name) { - Debug.Log("LeafSearch ctor"); + //Debug.Log("LeafSearch ctor"); } //Helper, find the nearest unobstructed waypoint to return to when chase is over - private void reevaluateWaypoint() + public void reevaluateWaypoint() { List waypoints = (List)GetNodeData("waypoints"); @@ -58,6 +58,8 @@ public partial class LeafSearch : BehaviourTreeNode SetNodeData("currentWaypointIndex", nearestWaypointIndex); } + //Helper for handling being alert + //Helper for handling stopping of chases private void handleChaseStop() { @@ -71,7 +73,7 @@ public partial class LeafSearch : BehaviourTreeNode public override BehaviourTreeNodeStatus Evaluate() { - Debug.LogWarning("LeafSearch"); + //Debug.LogWarning("LeafSearch"); onEnter(BehaviourTreeNodeStatus.RUNNING); //Get data @@ -86,7 +88,6 @@ public partial class LeafSearch : BehaviourTreeNode else { transform = (Transform)GetNodeData("transform"); - Debug.Log("Transform position: " + transform.GlobalPosition.x + " " + transform.GlobalPosition.y + " " + transform.GlobalPosition.z); eyeOffset = (Vector3)GetNodeData("eyeOffset"); sightDistance = (float)GetNodeData("sightDistance"); } @@ -117,8 +118,8 @@ public partial class LeafSearch : BehaviourTreeNode //Fail if too far from vision range if ((plrT.GlobalPosition - transform.GlobalPosition).GetMagnitude() > sightDistance) { - Debug.Log("Failure: Too far"); - handleChaseStop(); + //Debug.Log("Failure: Too far"); + //handleChaseStop(); status = BehaviourTreeNodeStatus.FAILURE; onExit(BehaviourTreeNodeStatus.FAILURE); return status; @@ -132,8 +133,8 @@ public partial class LeafSearch : BehaviourTreeNode //Debug.Log("Dot: " + Vector3.Dot(difference, lookDirection)); if (Vector3.Dot(difference, lookDirection) < 0.0f) { - Debug.Log("Failure: Out of FOV"); - handleChaseStop(); + //Debug.Log("Failure: Out of FOV"); + //handleChaseStop(); status = BehaviourTreeNodeStatus.FAILURE; onExit(BehaviourTreeNodeStatus.FAILURE); return status; @@ -146,34 +147,54 @@ public partial class LeafSearch : BehaviourTreeNode //Draw a ray, succeed if ray is unobstructed Vector3 eyePosition = transform.GlobalPosition + eyeOffset; - Ray sightRay = new Ray(eyePosition, plrT.GlobalPosition - eyePosition); + BoxCollider playerCollider = player.GetValueOrDefault().GetComponent().GetCollisionShape(0); + if (playerCollider == null) + { + //Debug.Log("Failure: Player has no collider"); + status = BehaviourTreeNodeStatus.FAILURE; + onExit(BehaviourTreeNodeStatus.FAILURE); + return status; + } + //Ray destination to target the centre of the player's collider instead of transform position + //Since transform position is often the raccoon's base and the ray needs to hit somewhere higher to be more reliable + Vector3 rayDestination = plrT.GlobalPosition + plrT.GlobalScale * playerCollider.PositionOffset; + Ray sightRay = new Ray(eyePosition, rayDestination - eyePosition); RaycastHit sightRayHit = Physics.Raycast(sightRay); //As of November 2022, RaycastHit contains only the FIRST object hit by //the ray in the Other GameObject data member //Diren may likely add ALL objects hit by the ray over December if (sightRayHit.Hit && sightRayHit.Other != player) { - Debug.Log("Failure: Ray hit obstacle named " + sightRayHit.Other.GetValueOrDefault().Name + " ID" + sightRayHit.Other.GetValueOrDefault().EntityId); - handleChaseStop(); + //TODO sometimes the ray doesn't hit the player even if he's in plain sight because the ray hits the floor the player is on instead??? + //Debug.Log("Failure: Ray hit obstacle named " + sightRayHit.Other.GetValueOrDefault().Name + " ID" + sightRayHit.Other.GetValueOrDefault().EntityId); + //handleChaseStop(); status = BehaviourTreeNodeStatus.FAILURE; onExit(BehaviourTreeNodeStatus.FAILURE); return status; } else if (sightRayHit.Hit && sightRayHit.Other == player) { - Debug.Log("Ray hit player"); + //Debug.Log("Ray hit player"); } //All checks for now succeeded - Debug.Log("Success: Homeowner has sighted player"); + //Debug.Log("Success: Homeowner has sighted player"); //Write player's transform into the blackboard SetNodeData("target", plrT); - if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == false) + if (GetNodeData("isAlert") == null) { + SetNodeData("isAlert", true); Audio.PlaySFXOnce2D("event:/Homeowner/homeowner_detect_raccoon"); } - SetNodeData("isAlert", true); + else + { + if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == false) + { + Audio.PlaySFXOnce2D("event:/Homeowner/homeowner_detect_raccoon"); + } + SetNodeData("isAlert", true); + } status = BehaviourTreeNodeStatus.SUCCESS; onExit(BehaviourTreeNodeStatus.SUCCESS); From 609be908f967deff0df58c0ffe861ecfee2b4acf Mon Sep 17 00:00:00 2001 From: mushgunAX Date: Fri, 25 Nov 2022 20:18:36 +0800 Subject: [PATCH 019/275] Revert "Made player jumping more responsive" This reverts commit d6fab4439f266cb4b4bf42835c16bd6026df602a. --- Assets/Scripts/Gameplay/Player/SC_PlayerController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs b/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs index 5e5f7f08..47dd2c41 100644 --- a/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs +++ b/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs @@ -285,7 +285,7 @@ public class PlayerController : Script { if (currentState == RaccoonStates.WALKING || currentState == RaccoonStates.RUNNING || currentState == RaccoonStates.IDLE) { - if (Input.GetKey(Input.KeyCode.Space) && isGrounded && rb != null) + if (Input.GetKeyDown(Input.KeyCode.Space) && isGrounded && rb != null) { currentState = RaccoonStates.JUMP; Vector3 v = rb.LinearVelocity; @@ -304,7 +304,7 @@ public class PlayerController : Script } } - if(!isGrounded && rb != null && (rb.LinearVelocity.y < 0.0f || !Input.GetKey(Input.KeyCode.Space))) + if(!isGrounded && rb != null && (rb.LinearVelocity.y < 0.0f || Input.GetKeyUp(Input.KeyCode.Space))) currentState = RaccoonStates.FALLING; } From c84e33731b07face434803cfbbc33ef7c7e64052 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Fri, 25 Nov 2022 22:24:30 +0800 Subject: [PATCH 020/275] Fixed crash which occurs when changing the material of an object twice in a row --- .../src/Graphics/MiddleEnd/Interface/SHRenderable.cpp | 4 +++- SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderable.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderable.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderable.cpp index 5dc5d22c..d3518e67 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderable.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderable.cpp @@ -66,10 +66,12 @@ namespace SHADE { oldMaterial = material; material = {}; + oldMatIsShared = false; } else if (sharedMaterial) { oldMaterial = sharedMaterial; + oldMatIsShared = true; } // Update the material @@ -150,7 +152,7 @@ namespace SHADE { matChanged = false; meshChanged = false; - if (oldMaterial) + if (oldMaterial && !oldMatIsShared) oldMaterial.Free(); oldMaterial = {}; oldMesh = {}; diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderable.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderable.h index 39132ca0..3ab98ee1 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderable.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderable.h @@ -90,6 +90,7 @@ namespace SHADE bool matChanged = true; Handle oldMaterial; uint8_t lightLayer; + bool oldMatIsShared = false; RTTR_ENABLE() }; From fb512e7fc5fd711a7a328e23e3336f117519c19b Mon Sep 17 00:00:00 2001 From: Glence Date: Fri, 25 Nov 2022 22:32:41 +0800 Subject: [PATCH 021/275] added the new banks in removed unneeded bank added new audio --- Assets/Audio/Master.bank | Bin 2032 -> 2216 bytes Assets/Audio/Master.strings.bank | Bin 2048 -> 2220 bytes Assets/Audio/Music.bank | Bin 4517088 -> 4854048 bytes Assets/Audio/SFX.bank | Bin 297088 -> 408448 bytes Assets/Audio/footsteps.bank | Bin 136256 -> 0 bytes Assets/Scripts/Gameplay/Breakable.cs | 1 + Assets/Scripts/Gameplay/SC_GameManager.cs | 16 ++++------------ 7 files changed, 5 insertions(+), 12 deletions(-) delete mode 100644 Assets/Audio/footsteps.bank diff --git a/Assets/Audio/Master.bank b/Assets/Audio/Master.bank index f6cce22fa48ee078ae573687b3bada58567cca7f..2f87ff1701ccf88e3c3393492308bc92c08904c8 100644 GIT binary patch delta 253 zcmeyszd}$c$kWYj0S5zvn`@YYn{S8$kjc;o#63Xl;~5;%!m&~5A8UOGkQ?CZ6YS&c z7s3K$GeV_pAmaW&IVUwBJNvub>Q78JIGE0#oi){Z!i@X=fz=U9KDn_<>(3$kWa31v>+Sn`@YYn{S8$2Ll5`9}xEdv5#kP$QkyHO8;1eS%BOCU!P!* O5SSh0yZJTSUPb^xb`azM diff --git a/Assets/Audio/Master.strings.bank b/Assets/Audio/Master.strings.bank index 7f71c9b1c38927556d580a9b4b71868994ea8f13..456236446cfef2caf6d3a03163f9f5530a311a10 100644 GIT binary patch delta 956 zcmYk5O-NK>6o%jHI6D35XlkXFla7{YTA39VqNGy;O6E8vNW|5-nl=73jz7@P2qFlX z2-DBbAlyU|M73y9i&_d%5V#O>8xaI9YcV}Dw}r!ddB5*`=R4=#_q;Rhevwn!>G%1T zU6QZ$lE>HHo_U@&f1qp5^{Vud5qqB@8H8@ir9>i;T&J_`bxLWClC@KE4jLNG zA$Z5oM4jYFlgv!GTpG{foYF&E$=@f9QBI}A^I>ANyj1;N#NAR&vI+fS%42be z0yNSQDUu7Hq!7h)nu{f{PrA07hPTNpg1<|nGb+BdZbV>`PZd>@)YIX zHh6}6h46juQ8(=w^*!W2R7@e>QcE;)z^|o&G7>&YgS@pqbAhkXJ1JZMe~>15d;LNo zRL}+r?STK#hfa9+8j2=W^|JTC)rN)+Q`a;!mK;Q#p-?S*Uxy*PzmX{TO z`@gM~%*Cf2UrVazCx4V*ZhQJ<;b;C^*XsI(i?_O$H_kn;FYw+qt5)M3=}qp+mB3Oc zH`p|rJ@UKf)4D&tnEu6h95#D*dW0ocdhvRt}RnFdsW!{K4W=zFK%?QU) zE7DNg0`*0$kS6-fhFZ0&IndSGsbIi=RsmnPf_{0E1Fn=9A=5>_df`4DLnrCD#E6&{ zvQ$Wn523saE9DzB!f_~o+USEM$XyhZm=-!`)&p^-V8iy1Jl~TZNSIKYx4@s-T_e|$=i A{r~^~ delta 782 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

0e?mkPcm%eHvf^HO#opF1))fB;0Qp%fy$F+ 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

(>J#tqA_U9vuFYC39 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

^tVk_5x{C-`%npe)Nsi^Lc z^UuGlU?SzyHA!|t(U3b@eol4oL@)c6%^v2W`5JmZv$M}k&eIGdsM&nB#O(gP-8!g0 z9M6lM=bpXcewTatcNc#NU4^Quwta3FRaLeZVM|D|YsJ5$S?P8WSnwLjrs@iLCkjHo zZ9o#vabr*gaG1v*Uz^AW(qS{&{nEe9~0PS}6~ z5&{0}o%|ZBNrNbeCcogxtU(5oCPNMAjW+D4_nxIoC{|TP+o^w8;&k5+h}Wvrtd$9C zLG@Mc9*J&N*FAD@*Uq4ARh&wf8(r*c={FYSYJB?g<=5|sniyv3be3)fj%MpN&yHE< z{q?Vhd!5gjP{1Prxyr5T)gz@5F> z&)a7=yI<||t*3vX_oTD4VP{5ved+(afBkiay%_Iv*TwUX*SFNqB(UX}+ji{GFmjF! z#4nzDz2CtyvTS6FST>PAm!hTS-&J#9-ky^?J(cfu(QX zE6RKKZ^=kE>=53aki%vJ*#H>znLh4ZgkhS>B@luz!evdsb_&~;II+*-f7D~Ugz0{|^ zQ}OjrU8qbZIdX|*WTOeP%EA`fY~MJiN`IyyLHWOQ#Hv~~+ndKHv!bVGz|nj~QW z?w-$U5jTJ6bXqBkf4%j7Hc!xKC3l$S^YeFmWXvwWsM)0Xf1cmu%>zHYoA+T)G)BkI z+t>8YyJ7B?w__ktJk8>kbdWWbClN zD4f~2D1?|H)=U%|!vuFtxz=MB#4TK)0S%m-_<3aENCILR{b#aY| z#AQqRg&j~=L~6OIETI?SrCim+nJLr{$Ws^tB$xx|>Rk7w0_(u>&DkJBK2(+EBJ>j_ z+$Q8%6S0Arud<7VhNa|aU^pKHbt^G)RuO>JH}_fV53}#VbokYDEX`ej0tIq_;QOmz z-`0O>b7dXGj*_fS^P2E92mlcC{g2832><|ebb^Y)d3; z2wqNiQ_MR4l(=Gr=G-{wZ_@#gY2VLR9RPsro?2td=X97L zzslccO=Yz_YBKEfTz2GbpLRnL z)r<`pt^p|l_6aV!RGFeBk77T3d4y|-gnGM7)r?vBg6ws0lcojyV0CV

QFkVYGLYCXtE>bjeooXml1t^mL*v~Cjq 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

KraIq1{(TR|Z+iEA8<9FQ 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 delta 1263 zcmZvae{54#6vyvqMU0Zm2>sC6Ri_dgXqHRaUez<4L+ODKO%-(AYxUF?=Ljnw9 zX57}|mZ^(>Fo7LJS%{95C}Px*yHErEfT+>HACiGcLJTvRi7^lojT&Q2JTEcBrQGCm z?t9;J&b{Z}b0_|EOdMWQ-xUdkMqU+2D43E%Flw5~u(x4{9_DxrXy z74D+7K~++rc4$p1ASP{aG%3-w@BwNf1XBu3p*EM2s0787LaTvKE7SoAv^DU2T7iy~ z4Nj*e>V%7FrC}YO#u!G2jIL;Lzh-=&#v6+zI+jZ}fpzOHR=2I4J|HIu$urt{ zRnr4eWA5~8o!2;QGVI@8V2w`+6r$a2aJ<_+(^CZvJ+0zZ(cl03wyw>yFMNIKc-Z>= zUiQW)%=L)yP0ui&Zx-OkHLkXh7TAcJ6Bja!FJd3^gjXO|{5SL!7NE>eyidISj~StN%XK|Vq*Axnq}-$)(OjOfTNWC}TfoI|c6Yj30Bv4izUE28rR z4h~B2-ZKR_G*}J$`)|T?12M4n*TAm>E<>YUjPOZXJdqCgqtQ$_7~jy@5!&GOG;V4F z*T8ysct@fzvE#d`ZhE$xd*$0=a4Wy~QgNV8_}f#Ie-N^c;-bf-G!;!?%DIbkH%*I~ Fe*riHJNlbOMv=kWd0WD2)h!z|HAq4;+ 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�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.Text = $"Time Left: {timer.ToString("0.00")}"; - if (timer > 0 && totalItemCount <= 0) + if ((timer > 0 && totalItemCount <= 0) || Input.GetKeyDown(Input.KeyCode.F1)) { currGameState = GameState.WIN; Audio.StopAllSounds(); SceneManager.ChangeScene(winScene); + Audio.PlaySFXOnce2D("event:/Music/stingers/game_win"); } - else if(timer < 0) + else if(timer < 0 || Input.GetKeyDown(Input.KeyCode.F2)) { currGameState = GameState.LOSE; Audio.StopAllSounds(); SceneManager.ChangeScene(loseScene); + Audio.PlaySFXOnce2D("event:/Music/stingers/game_lose"); } } } private void Cheats() { - if (Input.GetKeyDown(Input.KeyCode.F1)) - { - Audio.StopAllSounds(); - SceneManager.ChangeScene(loseScene); - } - if (Input.GetKeyDown(Input.KeyCode.F2)) - { - Audio.StopAllSounds(); - SceneManager.ChangeScene(winScene); - } if (Input.GetKeyDown(Input.KeyCode.Escape)) { Audio.StopAllSounds(); From 1302a7e58d2080b2796de20d88e83d6c0dffc85e Mon Sep 17 00:00:00 2001 From: Glence Date: Fri, 25 Nov 2022 22:53:38 +0800 Subject: [PATCH 022/275] added jump var changes change jump to be in update added audio for AI --- Assets/Scenes/MainGame.shade | 10 +++++----- .../AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs | 2 ++ .../AIBehaviour/Implemented/LeafNodes/LeafSearch.cs | 6 ++++++ Assets/Scripts/Gameplay/Player/SC_PlayerController.cs | 3 +-- Assets/Scripts/SC_EndScene.cs | 2 ++ 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Assets/Scenes/MainGame.shade b/Assets/Scenes/MainGame.shade index b2f41cec..2bbb7070 100644 --- a/Assets/Scenes/MainGame.shade +++ b/Assets/Scenes/MainGame.shade @@ -8526,8 +8526,8 @@ moveForce: 50 sprintMultiplier: 1.5 rotationFactorPerFrame: 5 - maxJumpHeight: 1 - maxJumpTime: 0.5 + maxJumpHeight: 2 + maxJumpTime: 0.75 fallMultipler: 3 lightMultiper: 0.75 mediumMultiper: 0.5 @@ -8633,7 +8633,7 @@ IsActive: true Renderable Component: Mesh: 140964851 - Material: 126974645 + Material: 124370424 IsActive: true RigidBody Component: Type: Dynamic @@ -8837,7 +8837,7 @@ IsActive: true Renderable Component: Mesh: 140964851 - Material: 126974645 + Material: 124370424 IsActive: true RigidBody Component: Type: Dynamic @@ -9041,7 +9041,7 @@ IsActive: true Renderable Component: Mesh: 140964851 - Material: 126974645 + Material: 124370424 IsActive: true RigidBody Component: Type: Dynamic diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs index 2f9c8e1a..84c45779 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs @@ -240,6 +240,8 @@ public partial class LeafPatrol : BehaviourTreeNode { //Debug.Log("Unalert"); Audio.PlaySFXOnce2D("event:/Homeowner/humming"); + Audio.StopAllSounds(); + Audio.PlayBGMOnce2D("event:/Music/player_undetected"); } SetNodeData("isAlert", false); } diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs index 4fa5e376..7c68712c 100644 --- a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs +++ b/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs @@ -186,12 +186,18 @@ public partial class LeafSearch : BehaviourTreeNode { SetNodeData("isAlert", true); Audio.PlaySFXOnce2D("event:/Homeowner/homeowner_detect_raccoon"); + Audio.PlaySFXOnce2D("event:/Music/stingers/player_detected"); + Audio.StopAllSounds(); + Audio.PlayBGMOnce2D("event:/Music/player_detected"); } else { if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == false) { Audio.PlaySFXOnce2D("event:/Homeowner/homeowner_detect_raccoon"); + Audio.PlaySFXOnce2D("event:/Music/stingers/player_detected"); + Audio.StopAllSounds(); + Audio.PlayBGMOnce2D("event:/Music/player_detected"); } SetNodeData("isAlert", true); } diff --git a/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs b/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs index 47dd2c41..c9e7b81e 100644 --- a/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs +++ b/Assets/Scripts/Gameplay/Player/SC_PlayerController.cs @@ -119,7 +119,6 @@ public class PlayerController : Script protected override void lateUpdate() { - //rb.FreezePositionY = false; } protected override void update() { @@ -152,6 +151,7 @@ public class PlayerController : Script camArm = GetComponentInChildren(); Rotation(); + Jump(); GotCaught(); //Debug.Log($"{currentState}"); //Debug.Log($" axisX: {axisMove.x} axisY:{axisMove.y}"); @@ -165,7 +165,6 @@ public class PlayerController : Script MoveKey(); Move(); Sprint(); - Jump(); Gravity(); //Debug.Log($"X: {rb.LinearVelocity.x}" + $" Z: {rb.LinearVelocity.z}"); } diff --git a/Assets/Scripts/SC_EndScene.cs b/Assets/Scripts/SC_EndScene.cs index d22157a4..8d3104c5 100644 --- a/Assets/Scripts/SC_EndScene.cs +++ b/Assets/Scripts/SC_EndScene.cs @@ -14,12 +14,14 @@ public class EndScene : Script if (Input.GetKey(Input.KeyCode.R)) { Audio.PlaySFXOnce2D("event:/UI/mouse_down_element"); + Audio.StopAllSounds(); SceneManager.ChangeScene(mainGameScene); } if (Input.GetKey(Input.KeyCode.M)) { Audio.PlaySFXOnce2D("event:/UI/mouse_down_element"); + Audio.StopAllSounds(); SceneManager.ChangeScene(mainMainScene); } From cae835834119dfafb95d1e8d97f5452970580917 Mon Sep 17 00:00:00 2001 From: Glence Date: Fri, 25 Nov 2022 23:10:17 +0800 Subject: [PATCH 023/275] updated scene changes --- Assets/Scenes/MainGame.shade | 460 +++++++++++++++++++++++++++-------- 1 file changed, 362 insertions(+), 98 deletions(-) diff --git a/Assets/Scenes/MainGame.shade b/Assets/Scenes/MainGame.shade index 2bbb7070..82cfce3e 100644 --- a/Assets/Scenes/MainGame.shade +++ b/Assets/Scenes/MainGame.shade @@ -8201,105 +8201,17 @@ - EID: 240 Name: ====ItemPool==== IsActive: true - NumberOfChildren: 3 + NumberOfChildren: 9 Components: ~ Scripts: ~ -- EID: 16 - Name: Mesh_Apple - IsActive: true - NumberOfChildren: 0 - Components: - Transform Component: - Translate: {x: 1.20656121, y: 0.124672964, z: 5.97578335} - Rotate: {x: 0, y: 0, z: 0} - Scale: {x: 1, y: 1, z: 1} - IsActive: true - Renderable Component: - Mesh: 144128170 - Material: 131956078 - IsActive: true - RigidBody Component: - Type: Dynamic - Drag: 0.00999999978 - Angular Drag: 0.00999999978 - Use Gravity: true - Interpolate: true - Sleeping Enabled: true - Freeze Position X: false - Freeze Position Y: false - Freeze Position Z: false - Freeze Rotation X: false - Freeze Rotation Y: false - Freeze Rotation Z: false - IsActive: true - Collider Component: - Colliders: - - Is Trigger: false - Type: Box - Half Extents: {x: 0.200000003, y: 0.200000003, z: 0.200000003} - Friction: 0.400000006 - Bounciness: 0 - Density: 1 - Position Offset: {x: 0, y: 0, z: 0} - Rotation Offset: {x: 0, y: 0, z: 0} - IsActive: true - Scripts: - - Type: Item - Enabled: true - Score: 10 - currCategory: 0 -- EID: 242 - Name: Mesh_Cheese - IsActive: true - NumberOfChildren: 0 - Components: - Transform Component: - Translate: {x: 1.89451575, y: 0.156862095, z: 6.01846552} - Rotate: {x: 0, y: 0, z: 0} - Scale: {x: 1, y: 1, z: 1} - IsActive: true - Renderable Component: - Mesh: 141841143 - Material: 131956078 - IsActive: true - RigidBody Component: - Type: Dynamic - Drag: 0.00999999978 - Angular Drag: 0.00999999978 - Use Gravity: true - Interpolate: true - Sleeping Enabled: true - Freeze Position X: false - Freeze Position Y: false - Freeze Position Z: false - Freeze Rotation X: false - Freeze Rotation Y: false - Freeze Rotation Z: false - IsActive: true - Collider Component: - Colliders: - - Is Trigger: false - Type: Box - Half Extents: {x: 0.5, y: 0.150000006, z: 0.5} - Friction: 0.400000006 - Bounciness: 0 - Density: 1 - Position Offset: {x: 0, y: 0, z: 0} - Rotation Offset: {x: 0, y: 0, z: 0} - IsActive: true - Scripts: - - Type: Item - Enabled: true - Score: 100 - currCategory: 2 - EID: 241 Name: Mesh_Meat IsActive: true NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 2.83309579, y: 0.209537908, z: 5.95318222} - Rotate: {x: 0, y: 0, z: 0} + Translate: {x: 4.2743969, y: 0.209537908, z: 4.31536102} + Rotate: {x: -0, y: 0, z: -0} Scale: {x: 1, y: 1, z: 1} IsActive: true Renderable Component: @@ -8336,6 +8248,358 @@ Enabled: true Score: 50 currCategory: 1 +- EID: 157 + Name: Mesh_Meat + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 4.0829463, y: 1.37235689, z: -4.4815464} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 136892700 + Material: 131956078 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.300000012, y: 0.300000012, z: 0.300000012} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: + - Type: Item + Enabled: true + Score: 50 + currCategory: 1 +- EID: 156 + Name: Mesh_Meat + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: -1.74228501, y: 0.927017033, z: -1.76112592} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 136892700 + Material: 131956078 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.300000012, y: 0.300000012, z: 0.300000012} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: + - Type: Item + Enabled: true + Score: 50 + currCategory: 1 +- EID: 155 + Name: Mesh_Cheese + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: -2.89926314, y: 1.15123272, z: 4.36190414} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 141841143 + Material: 131956078 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.5, y: 0.150000006, z: 0.5} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: + - Type: Item + Enabled: true + Score: 100 + currCategory: 2 +- EID: 154 + Name: Mesh_Cheese + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: -0.26750499, y: 1.28282726, z: 0.749736428} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 141841143 + Material: 131956078 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.5, y: 0.150000006, z: 0.5} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: + - Type: Item + Enabled: true + Score: 100 + currCategory: 2 +- EID: 153 + Name: Mesh_Cheese + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: -4.28802347, y: 0.990184903, z: 3.08095932} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 141841143 + Material: 131956078 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.5, y: 0.150000006, z: 0.5} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: + - Type: Item + Enabled: true + Score: 100 + currCategory: 2 +- EID: 65778 + Name: Mesh_Apple + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: -0.667112291, y: 1.25251341, z: -4.2965107} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 144128170 + Material: 131956078 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.200000003, y: 0.200000003, z: 0.200000003} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: + - Type: Item + Enabled: true + Score: 10 + currCategory: 0 +- EID: 152 + Name: Mesh_Apple + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 4.5356307, y: 0.850251734, z: 4.42769098} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 144128170 + Material: 131956078 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.200000003, y: 0.200000003, z: 0.200000003} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: + - Type: Item + Enabled: true + Score: 10 + currCategory: 0 +- EID: 151 + Name: Mesh_Apple + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 2.47664404, y: 0.904378593, z: -4.45292473} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + Renderable Component: + Mesh: 144128170 + Material: 131956078 + IsActive: true + RigidBody Component: + Type: Dynamic + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Box + Half Extents: {x: 0.200000003, y: 0.200000003, z: 0.200000003} + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: + - Type: Item + Enabled: true + Score: 10 + currCategory: 0 - EID: 15 Name: ====ScoreZonePool==== IsActive: true @@ -8433,7 +8697,7 @@ currGameState: 0 totalItemCount: 0 Score: 0 - timer: 100 + timer: 200 scoreText: 237 timeText: 206 - EID: 199 @@ -8627,8 +8891,8 @@ NumberOfChildren: 4 Components: Transform Component: - Translate: {x: 1.10128331, y: 0.1594823, z: 7.20125246} - Rotate: {x: 0, y: 0, z: 0} + Translate: {x: 4.39069891, y: 1.33369851, z: -2.95690465} + Rotate: {x: -0, y: 0, z: -0} Scale: {x: 0.999979734, y: 1, z: 0.999979734} IsActive: true Renderable Component: @@ -8831,7 +9095,7 @@ NumberOfChildren: 4 Components: Transform Component: - Translate: {x: 2.97949171, y: 0.1594823, z: 7.20125246} + Translate: {x: -1.62782168, y: 1.24122202, z: 0.321960568} Rotate: {x: -0, y: 0, z: -0} Scale: {x: 0.999979734, y: 1, z: 0.999979734} IsActive: true @@ -8955,8 +9219,8 @@ NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 0.0431699753, y: 1.18017197e-05, z: -0.0288243294} - Rotate: {x: -0, y: 0, z: 0.174533099} + Translate: {x: 0.0431697369, y: 1.18017197e-05, z: -0.0288243294} + Rotate: {x: 0, y: 0, z: 0.174533099} Scale: {x: 0.999999881, y: 0.999999583, z: 1} IsActive: true Renderable Component: @@ -9035,7 +9299,7 @@ NumberOfChildren: 4 Components: Transform Component: - Translate: {x: 3.0075531, y: 0.1594823, z: 8.19898987} + Translate: {x: 2.44600534, y: 0.118150897, z: 5.19178057} Rotate: {x: -0, y: 0, z: -0} Scale: {x: 0.999979734, y: 1, z: 0.999979734} IsActive: true From 81978a90b60bd54393af55822edbbd56fa6b06cb Mon Sep 17 00:00:00 2001 From: Glence Date: Sat, 26 Nov 2022 08:28:37 +0800 Subject: [PATCH 024/275] added more audio and tidy up folder tidy up scripts folder added more audio (cause burden randall cant make up his mind) adjusted main scene abit --- Assets/Audio/Master.strings.bank | Bin 2220 -> 2318 bytes Assets/Audio/Music.bank | Bin 4854048 -> 4856128 bytes Assets/Audio/SFX.bank | Bin 408448 -> 1354656 bytes Assets/Audio/UI.bank | Bin 13984 -> 20096 bytes Assets/Scenes/MainGame.shade | 2 +- Assets/Scripts/AIPrototype.cs | 183 ------------------ Assets/Scripts/AIPrototype.cs.shmeta | 3 - .../BehaviourTree/Core/BehaviourTree.cs | 0 .../Core/BehaviourTree.cs.shmeta | 0 .../BehaviourTree/Core/BehaviourTreeEvents.cs | 0 .../Core/BehaviourTreeEvents.cs.shmeta | 0 .../BehaviourTree/Core/BehaviourTreeNode.cs | 0 .../Core/BehaviourTreeNode.cs.shmeta | 0 .../Flow/BehaviourTreeSelector.cs | 0 .../Flow/BehaviourTreeSelector.cs.shmeta | 0 .../Flow/BehaviourTreeSequence.cs | 0 .../Flow/BehaviourTreeSequence.cs.shmeta | 0 .../AIBehaviour/Implemented/Homeowner1.cs | 0 .../Implemented/Homeowner1.cs.shmeta | 0 .../Implemented/Homeowner1Events.cs | 0 .../Implemented/Homeowner1Events.cs.shmeta | 0 .../Implemented/LeafNodes/LeafAttack.cs | 0 .../LeafNodes/LeafAttack.cs.shmeta | 0 .../Implemented/LeafNodes/LeafChase.cs | 0 .../Implemented/LeafNodes/LeafChase.cs.shmeta | 0 .../Implemented/LeafNodes/LeafPatrol.cs | 0 .../LeafNodes/LeafPatrol.cs.shmeta | 0 .../Implemented/LeafNodes/LeafSearch.cs | 0 .../LeafNodes/LeafSearch.cs.shmeta | 0 Assets/Scripts/Gameplay/Breakable.cs.shmeta | 3 - .../{Breakable.cs => Item/SC_Breakable.cs} | 0 .../Gameplay/Item/SC_Breakable.cs.shmeta | 3 + Assets/Scripts/Gameplay/{ => Item}/SC_Item.cs | 0 .../Gameplay/{ => Item}/SC_Item.cs.shmeta | 0 .../UT_PlayerCaughtState.cs | 0 .../UT_PlayerCaughtState.cs.shmeta | 0 .../{ => PlayerStates}/UT_PlayerFallState.cs | 0 .../UT_PlayerFallState.cs.shmeta | 0 .../{ => PlayerStates}/UT_PlayerIdleState.cs | 0 .../UT_PlayerIdleState.cs.shmeta | 0 .../{ => PlayerStates}/UT_PlayerJumpState.cs | 0 .../UT_PlayerJumpState.cs.shmeta | 0 .../{ => PlayerStates}/UT_PlayerLandState.cs | 0 .../UT_PlayerLandState.cs.shmeta | 0 .../{ => PlayerStates}/UT_PlayerRunState.cs | 0 .../UT_PlayerRunState.cs.shmeta | 0 .../{ => PlayerStates}/UT_PlayerWalkState.cs | 0 .../UT_PlayerWalkState.cs.shmeta | 0 ...ersonCamera.cs => SC_ThirdPersonCamera.cs} | 0 .../Player/SC_ThirdPersonCamera.cs.shmeta | 3 + .../Player/ThirdPersonCamera.cs.shmeta | 3 - Assets/Scripts/Gameplay/SC_GameManager.cs | 1 + Assets/Scripts/{ => UI}/SC_EndScene.cs | 12 +- Assets/Scripts/{ => UI}/SC_EndScene.cs.shmeta | 0 Assets/Scripts/{ => UI}/SC_MainMenu.cs | 5 + Assets/Scripts/{ => UI}/SC_MainMenu.cs.shmeta | 0 56 files changed, 23 insertions(+), 195 deletions(-) delete mode 100644 Assets/Scripts/AIPrototype.cs delete mode 100644 Assets/Scripts/AIPrototype.cs.shmeta rename Assets/Scripts/{ => Gameplay}/AIBehaviour/BehaviourTree/Core/BehaviourTree.cs (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/BehaviourTree/Core/BehaviourTree.cs.shmeta (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/BehaviourTree/Core/BehaviourTreeEvents.cs (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/BehaviourTree/Core/BehaviourTreeEvents.cs.shmeta (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/BehaviourTree/Core/BehaviourTreeNode.cs (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/BehaviourTree/Core/BehaviourTreeNode.cs.shmeta (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSelector.cs (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSelector.cs.shmeta (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSequence.cs (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSequence.cs.shmeta (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/Implemented/Homeowner1.cs (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/Implemented/Homeowner1.cs.shmeta (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/Implemented/Homeowner1Events.cs (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/Implemented/Homeowner1Events.cs.shmeta (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs.shmeta (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/Implemented/LeafNodes/LeafChase.cs (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/Implemented/LeafNodes/LeafChase.cs.shmeta (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs.shmeta (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs (100%) rename Assets/Scripts/{ => Gameplay}/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs.shmeta (100%) delete mode 100644 Assets/Scripts/Gameplay/Breakable.cs.shmeta rename Assets/Scripts/Gameplay/{Breakable.cs => Item/SC_Breakable.cs} (100%) create mode 100644 Assets/Scripts/Gameplay/Item/SC_Breakable.cs.shmeta rename Assets/Scripts/Gameplay/{ => Item}/SC_Item.cs (100%) rename Assets/Scripts/Gameplay/{ => Item}/SC_Item.cs.shmeta (100%) rename Assets/Scripts/Gameplay/Player/{ => PlayerStates}/UT_PlayerCaughtState.cs (100%) rename Assets/Scripts/Gameplay/Player/{ => PlayerStates}/UT_PlayerCaughtState.cs.shmeta (100%) rename Assets/Scripts/Gameplay/Player/{ => PlayerStates}/UT_PlayerFallState.cs (100%) rename Assets/Scripts/Gameplay/Player/{ => PlayerStates}/UT_PlayerFallState.cs.shmeta (100%) rename Assets/Scripts/Gameplay/Player/{ => PlayerStates}/UT_PlayerIdleState.cs (100%) rename Assets/Scripts/Gameplay/Player/{ => PlayerStates}/UT_PlayerIdleState.cs.shmeta (100%) rename Assets/Scripts/Gameplay/Player/{ => PlayerStates}/UT_PlayerJumpState.cs (100%) rename Assets/Scripts/Gameplay/Player/{ => PlayerStates}/UT_PlayerJumpState.cs.shmeta (100%) rename Assets/Scripts/Gameplay/Player/{ => PlayerStates}/UT_PlayerLandState.cs (100%) rename Assets/Scripts/Gameplay/Player/{ => PlayerStates}/UT_PlayerLandState.cs.shmeta (100%) rename Assets/Scripts/Gameplay/Player/{ => PlayerStates}/UT_PlayerRunState.cs (100%) rename Assets/Scripts/Gameplay/Player/{ => PlayerStates}/UT_PlayerRunState.cs.shmeta (100%) rename Assets/Scripts/Gameplay/Player/{ => PlayerStates}/UT_PlayerWalkState.cs (100%) rename Assets/Scripts/Gameplay/Player/{ => PlayerStates}/UT_PlayerWalkState.cs.shmeta (100%) rename Assets/Scripts/Gameplay/Player/{ThirdPersonCamera.cs => SC_ThirdPersonCamera.cs} (100%) create mode 100644 Assets/Scripts/Gameplay/Player/SC_ThirdPersonCamera.cs.shmeta delete mode 100644 Assets/Scripts/Gameplay/Player/ThirdPersonCamera.cs.shmeta rename Assets/Scripts/{ => UI}/SC_EndScene.cs (67%) rename Assets/Scripts/{ => UI}/SC_EndScene.cs.shmeta (100%) rename Assets/Scripts/{ => UI}/SC_MainMenu.cs (82%) rename Assets/Scripts/{ => UI}/SC_MainMenu.cs.shmeta (100%) diff --git a/Assets/Audio/Master.strings.bank b/Assets/Audio/Master.strings.bank index 456236446cfef2caf6d3a03163f9f5530a311a10..3992afa162e5b668f1e09ccce857c611f88beee1 100644 GIT binary patch delta 814 zcmX|3?P%%-5A zMEIdrwQ419N9sSjT54~KnKQuyMv zKsYiKsFXCr_n2>>e{g8^Rq&Zr1u7(CB=3H-<{ICuDO*qZ(lixvSFEMLymm9c1>PV< ztEI9g>RRfxwKZ*n{n8Qdzq%1VB&9aPN2Q4dxJ#N0z-Qcqu?B#jN`W;fa zm(%xCx0)u{qpZ(V$RB1PFU5D#a9w9%(u|5?nhUa-n1nzKRMg8@WnH0{?+#Pp9?SABd+uP zW^d(%Tkdsl);_pjy?sr+saVoUk2oC@nM^8|Njt-LlDUzDlXh!;J=L+X)NnK&&E}F* z&ccAN(F(0PCACSofni9_WpRo0yY9tSwg!6 Sum%uEKwe4+Ng;Q-vhy$FHG4V$ delta 719 zcmXYvId2nD6otQMW8<)dB!tZ@vBB&R7KdFa5Q@PPG!Q7DKt_onC_#oKKml?QAw>eB zNJEs4Dh(11azz0W`~ZFc1vN-ibkLa3o{Y}u%zbn3yXT$z=6c?D=dYGarKJp%hHm6a zSB7(bGUxls_j38#@a%{DV(9pkhTzJ>=u60Urp5xbZo}t+$HnMZ=&SpmoiM#YUImpk zkl13}Y20i!*(7&C|BB_fS3Up}>*b^Hs7F2plRM;d*7eCHxVIUS&&=B@|A5+VxoQ4B z_s<2U-6MGm>Lc4MxDQnmnX+)TJY!*2er=%}C*Ij0XTbuDD3Z4}O3DqmyUYJyVNB6{ zGvCAT5ym&lpW%t(`3-jx@*fyoEB~{Pb##R~)x00sz*NhoLZF(H4+SO;yKyBj*)p~m z7&&5G3XC3;&j!ZVxqjZf?ee9-1D)e~;ORW d;q{53?ZR6V;~)sF6#Z>@kzy3JmFRnB;6JMgTlD|{ diff --git a/Assets/Audio/Music.bank b/Assets/Audio/Music.bank index beabe8857cb8e26d7804db9be9a35c830253f472..7a1390c69117d7bb6a1b58a2dff5cb489493469b 100644 GIT binary patch delta 1993 zcmaLX4@?tR90%~%mX?B`Ep%N$z*_!Hq?=3z;^beCf35VcP;O{4#vIBVLIgLq#4#wM z5%vcZFN;iY7)LfEB%-${1BN=1NgRg#ahuyrCXp;7Av4nr9U9p8fdP8Vz2wuozI*TY zd;ji^9X}zzaOQ;Eb(43;;l2_cQ}9@uTUe7U*F6fsll`LKp~5MpC; zbM2`dk6S`}2wUX3rnjm!g>Q5YC9IVEAO|rr0j{gp7WFUX_tW%MsNFl(_cHREeU;VojuHP!KnJ#bfr8uRE$ey zyN3qFw${nf|7?~oOse*4%}B&9NTe2B8qxtw zPfxpQXaqSZh=v?8ws4=dR;q&zB(l-t8Tv8$kQUm27K@$`B^pZCOu9G<>! z-zl*85wo4J`I+S6cNJw-C$(8;MQdxvki2+im^I+o}_Jt>LNO|QRt zkM>d%XVLcJvYkeT_BXl6ivw{&;R}Oor(dJbsT*zmG{jsZ3qi0z@^$(B-}*0%T5JA! z?M|up{%jfu8-f6VM0x9I?}1&fr0w0YI;)P=MbZ8!=qH;w!d)^<&^bSCdT3{kHmcgsEiu%!* zjY^b}H+4zKiS)D#GW3^s@^r)t5_};my6C0mHvMpMAELlP)uzE#+PY8G0TU&PW>g z!TI_=UF5nYf%&yJd;iqLF4`h`LMTE47Q}WBEHwIuh~9ob$Xv~6lg--a4lYekcaY8j z^$uVna9?saq~o^ynp!@5<;eRR4mL|SNH()2sl3xcBg~!-X$<#wP(OwkF1KBt-BtG> zrF0-pc5Z;Q4ILm)>n%3b~f(dmWgkZ6tc;~|uXBnVv4<#_44ync$SXiB@A zQk!1tzdN{;vvIk6Er#{T$kNXcjVv01zf{VPnS~psKxHWwCR~^{~3d=wPTJQsZ(7|J%hsVJH0T2kw zAqavY1Xh3%R)PsafsQ8}BH#&F1(C2CqQDGK!c(vYqTy*+3o-Bv#6ldbgLqgE&%y>s dfJ8`wWJm!EFpvsqkPaD;3D$E}VYZLP{{_LCabW-e delta 609 zcmYk%OHUI~6u|L2GacJf#f}{wEhu$bu+Sn1D2PuU9T?IQ)0rR%8@4P+Sg>G0hyj8d zB_VffjlslpMF@A{mX!+^F8u_q3`;+N{~-ie{E~am*!~2a$`oT-NP_%`LT|{n4$kVCH&( zT8(_v30pbwZ<=xw5yP4`g(xosS0mI?v((hDWWz{}>p}#T z`Kuh=R=P!gwnt=ZI;nQseru_4uQ%0-AMSg8u2t1aCSltD_{G}^@3On;e7Iw)PhAB= z?&p3gr~8Mgzst+NeHn966Cx+$MPIEI3z~6w=Izu=@9QNEIt)Z$;s&CKA&w>_a1*!C zjN3@U!X2bwqXn&KLp##QpaY$_i+kuo7Wd&GhdjE`g9j*}7kwxq+_xVOF@Qll!Vrcr ng2#A*QHGNNlalHWw@BZ?Du-f`+D{tC~dYm diff --git a/Assets/Audio/SFX.bank b/Assets/Audio/SFX.bank index e1a85b719d34164595d6a9afcd4d1a25b58b047e..8656abd66d8f76db656bca44323b76011ca67446 100644 GIT binary patch delta 953777 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

fq`U!72Up~09u%BBqvt=v_x91`pfoL+ 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 z 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 J0BeFE6H5$Hyu8I(K%PIuxaaP^2az@k{N4 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

3tsyHbHo1VVngANFt$Mf@Qzbok-+IPto zvO%n||W!5i>(NTg!kxy0PhGj(9-+@>gPiFn&=Z%qiw3Wvi>?G4)F_mb7@xmuko{=74T9TM+C@kXzpd@$zl*sPaIXrs;TjikiZXf)1yv7YQ zw786q47G0?HZ*~k#aP~Qke>;B@M~qU)P=RhU(n5%?zaufW|Qgk9;B<$MI%lH!@bO9 z0^aXY_T*1zPqL3e$yxx9y}z-Q-R2*F`?H~tZT?}>nip;O!|kW8pR2adghQn}*l)-F zbYBRmcO*KLC#`mW#+DE#NJnrsUe}QOmhg6^USkN-uT~yr{7w@XQ5dOI%TG2c5kYxk z8z39^Lf*X&H9FdCa|ll{;=?DP2W73x16!Q}9ZARfAf6T1fCAL2RA(vbHMEtmg82JC z@yBU|GQ9iyJYJ?GYtjQIT+natITcISLMf}Mgct>V_*X}pIWq>;4V~)Tw<>O8Mr>jZ z12E|uh+(v|2zz)xLw%6I+m=wDO;-_wHaP9a#ewZCfj7l?dqwR3wW=l`8pA( zXO}rB(YgeV^hVor(nq*m_hHC1U7EloyDvg-ZZ`t8CjES2$-rkVlv``yU~& z(L1`!T+O=_tEXWT$WDKQ`#Sbi>3p$usVy{XAoaxlfYR$ut;vFx|L8xc*+BZX^!B(+ zPpjK1d!2mV-WC7jruN&kVxux$Y}In}Ji{>!h#i8o3M#|NAi1;$3Q(#r?oHs~-WFwL z>_xzHk?7%i==P`(tTr;Hm+5xV+{&fS2IA&Cyi-ZiCA`ZF-agBEURrtMMVI|H5#FY7 z10`-iKcLDC(Oj2@IsY_Y<%GGSwb^hT%y)ejW@IZ(+^sY`p`Yyn644<$$a@U92+)H5u^5KZx-ld*cbV|rWgu@6Q+tdU0$E~ zNI5+&vG$#81)@4!Ely7xB2xG|RnGOi4Y>h)Y4V!kV%6EJM0?C@5hKEquTn)5@69iU z`=fX92*4v>tSA#^^k%s%2Z<}cK`ht#TV>CSiSsy1cWNZelZI)zUwH5DgMRyz5ocy?a2_e>13YG#e#S~Z1U?)|b69$0GDK4v z;pl+@4lu5CC?ipe4O)Xak{n7|+i&)tv0)9&ikvwHSpw2Zv|>20%AimqmsuK2(%us~ zsP{WMSqU`rauWzgfoROJCqI{iupb&#dD@VNBYwCP#H)ip)KG(0^+u`Pz_a^i@^nQd zj8}G$Th-dniZ6oKyi2*S@O?Ssl@mkXGQ2MujV!@5C~xM0cGk4$(WeLrdNDUhF^G@| zgBcW9%NaIO%V1h_xOxA~NG1Wpj@M+$O3Gx7?htZ=2!gyW?u*WIFUerz-X9DN(Q>}v zzM&|~4I_!Cv$n9B)kO;gm_xqC8|s!e+=x5o1tHkOLzq<<6&Qp=Sfs3aW)c{c>l40p z@3p(!ss!y<{BHbysS&u<926KSdRWUT)Q!ufd|>Dhh$LeCX;faK(`zJ0V4@d-_b&!Y=ASuv!99|t0Z}a;6`Es{`mFfxR;4zn zL5cq2T983APW7@FXR#^{yP1kvAIK(dJbfHBSI_S!hF!BRG|dtZZyRY&-qBUoUv5^R zpGfg03P|)|_6>y>cc6}sGY6r5%0A&jhrpAZLI7q*L|%wH!H_2EmF&78y~JN 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

ykI<;y^MC-H(&0 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 X#SP?zg96S6M=aMMwJg^l7svb$_E;yq delta 820 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 delta 303 zcmZpe%eWv{Dag~!ZH5^GgPUuZf}3xM0tW*FLmv?L0I`o}aL6k0iAujF&WqVx!FYvr z@;SDc%`4c?a4@}N*<2=2#WXoWP+*d<$m9(IIg_Ue3ryA#wAuVf_z(5eDH#l}Ll;HIA!~d=MzsN|#}q{84*yfW#c1Q$URf;1=v;$_V6w zunZ7u0I>s51wT{_NWsA|`=b&Y*UVdSFPW8dN@3(lV}{95l2Xh-mrtG~sk+(0z(EcG Dosdd> diff --git a/Assets/Scenes/MainGame.shade b/Assets/Scenes/MainGame.shade index 82cfce3e..29cfdda0 100644 --- a/Assets/Scenes/MainGame.shade +++ b/Assets/Scenes/MainGame.shade @@ -8562,7 +8562,7 @@ NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 2.47664404, y: 0.904378593, z: -4.45292473} + Translate: {x: 0.00604016334, y: 1.34554219, z: -4.45292473} Rotate: {x: -0, y: 0, z: -0} Scale: {x: 1, y: 1, z: 1} IsActive: true diff --git a/Assets/Scripts/AIPrototype.cs b/Assets/Scripts/AIPrototype.cs deleted file mode 100644 index 62255778..00000000 --- a/Assets/Scripts/AIPrototype.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading.Tasks; -using SHADE; - -public class AIPrototype : Script -{ - //This object's relevant components - private Transform transform; - private RigidBody rb; - - /*[SerializeField] - [Tooltip("The list of waypoints that the object will move around on")] - private Vector3[] waypoints;*/ - - private Vector3[] waypoints = { new Vector3(-8.0f, -2.0f, 3.5f), new Vector3(-8.0f, -2.0f, -13.0f), new Vector3(8.0f, -2.0f, -13.0f), new Vector3(8.0f, -2.0f, 3.5f) }; - - [SerializeField] - [Tooltip("How much force is applied in movement")] - private float movementForceMultiplier = 100.0f; - - [SerializeField] - [Tooltip("How fast the object moves about waypoints")] - private float patrolSpeed = 0.4f; - - [SerializeField] - [Tooltip("How fast the object moves while chasing")] - private float chaseSpeed = 0.8f; - - [SerializeField] - [Tooltip("How near the player must be to the AI for capture")] - private float distanceToCapture = 1.2f; - - [SerializeField] - [Tooltip("How near the player must be for the chase to begin. Should be less than distanceToEndChase")] - private float distanceToStartChase = 2.0f; - - [SerializeField] - [Tooltip("How far the player must be for the chase to end. Should be greater than distanceToStartChase")] - private float distanceToEndChase = 2.5f; - - //Whether the AI is chasing or not - private bool chaseMode; - - //To cycle depending on the length of waypoints - private int currentTargetWaypointIndex; - - private GameObject? player; - - - protected override void awake() - { - transform = GetComponent(); - if (transform == null) - { - Debug.LogError("Transform is NULL!"); - } - - rb = GetComponent(); - if (rb == null) - { - Debug.LogError("Rigidbody is NULL!"); - } - - currentTargetWaypointIndex = 0; - - player = GameObject.Find("Player"); - if (player == null) - { - Debug.LogError("Player is NULL!"); - } - - chaseMode = false; - } - - protected override void fixedUpdate() - { - //Patrolling - if (!chaseMode) - { - //Head towards the next target - Vector3 normalisedDifference = waypoints[currentTargetWaypointIndex] - transform.GlobalPosition; - normalisedDifference /= normalisedDifference.GetMagnitude(); - - //transform.GlobalPosition += normalisedDifference * moveSpeed * (float)Time.DeltaTime; - //rb.LinearVelocity = normalisedDifference * patrolSpeed; - - //ORIGINAL INTENDED CODE - /*rb.AddForce(new Vector3(normalisedDifference.x, 0.0f, normalisedDifference.z) * movementForceMultiplier); - float currentSpeed = MathF.Sqrt(rb.LinearVelocity.x * rb.LinearVelocity.x + rb.LinearVelocity.z * rb.LinearVelocity.z); - if (currentSpeed > patrolSpeed) - { - float adjustmentFactor = patrolSpeed / currentSpeed; - Vector3 adjustedVelocity = rb.LinearVelocity; - //adjustedVelocity *= adjustmentFactor; - adjustedVelocity.x = patrolSpeed; - adjustedVelocity.z = patrolSpeed; - rb.LinearVelocity = adjustedVelocity; - }*/ - - //TODO delete this when original intended code above works with velocity being limited correctly - rb.LinearVelocity = normalisedDifference * patrolSpeed; - - //transform.GlobalRotation.SetLookRotation(waypoints[currentTargetWaypointIndex], Vector3.Up); - - //Cycle to next waypoint if near enough current waypoint - if ((waypoints[currentTargetWaypointIndex] - transform.GlobalPosition).GetSqrMagnitude() <= 0.5f) - { - ++currentTargetWaypointIndex; - if (currentTargetWaypointIndex >= waypoints.Length) - { - currentTargetWaypointIndex = 0; //Recycle - } - } - - //Go chase if near enough to player - if (player != null) - { - Transform pTransform = player.GetValueOrDefault().GetComponent(); - if ((pTransform.GlobalPosition - transform.GlobalPosition).GetMagnitude() <= distanceToStartChase) - { - //Start the chase - chaseMode = true; - } - } - } - else //Chasing - { - if (player != null) - { - Transform pTransform = player.GetValueOrDefault().GetComponent(); - - //Chase the player - Vector3 normalisedDifference = pTransform.GlobalPosition - transform.GlobalPosition; - normalisedDifference /= normalisedDifference.GetMagnitude(); - - //transform.GlobalPosition += normalisedDifference * moveSpeed * (float)Time.DeltaTime; - - //ORIGINAL INTENDED CODE - /*rb.AddForce(new Vector3(normalisedDifference.x, 0.0f, normalisedDifference.z) * movementForceMultiplier); - float currentSpeed = MathF.Sqrt(rb.LinearVelocity.x * rb.LinearVelocity.x + rb.LinearVelocity.z * rb.LinearVelocity.z); - if (currentSpeed > chaseSpeed) - { - float adjustmentFactor = chaseSpeed / currentSpeed; - Vector3 adjustedVelocity = rb.LinearVelocity; - adjustedVelocity *= adjustmentFactor; - rb.LinearVelocity = adjustedVelocity; - }*/ - - //TODO delete this when original intended code above works with velocity being limited correctly - rb.LinearVelocity = normalisedDifference * chaseSpeed; - - //Capture player if near enough - if ((pTransform.GlobalPosition - transform.GlobalPosition).GetMagnitude() <= distanceToCapture) - { - player.GetValueOrDefault().GetScript().currentState = PlayerController.RaccoonStates.CAUGHT; - } - - //End chase if too far - if ((pTransform.GlobalPosition - transform.GlobalPosition).GetMagnitude() >= distanceToEndChase) - { - //Stop the chase - chaseMode = false; - - //Find the nearest waypoint to go instead - float nearestWaypointDistance = 99999999999999.9f; - for (int i = 0; i < waypoints.Length; ++i) - { - if ((waypoints[i] - transform.GlobalPosition).GetSqrMagnitude() < nearestWaypointDistance) - { - nearestWaypointDistance = waypoints[i].GetSqrMagnitude(); - currentTargetWaypointIndex = i; - } - } - } - } - } - } -} \ No newline at end of file diff --git a/Assets/Scripts/AIPrototype.cs.shmeta b/Assets/Scripts/AIPrototype.cs.shmeta deleted file mode 100644 index 80a7d4b4..00000000 --- a/Assets/Scripts/AIPrototype.cs.shmeta +++ /dev/null @@ -1,3 +0,0 @@ -Name: AIPrototype -ID: 163215061 -Type: 9 diff --git a/Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTree.cs b/Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Core/BehaviourTree.cs similarity index 100% rename from Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTree.cs rename to Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Core/BehaviourTree.cs diff --git a/Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTree.cs.shmeta b/Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Core/BehaviourTree.cs.shmeta similarity index 100% rename from Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTree.cs.shmeta rename to Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Core/BehaviourTree.cs.shmeta diff --git a/Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTreeEvents.cs b/Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Core/BehaviourTreeEvents.cs similarity index 100% rename from Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTreeEvents.cs rename to Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Core/BehaviourTreeEvents.cs diff --git a/Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTreeEvents.cs.shmeta b/Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Core/BehaviourTreeEvents.cs.shmeta similarity index 100% rename from Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTreeEvents.cs.shmeta rename to Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Core/BehaviourTreeEvents.cs.shmeta diff --git a/Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTreeNode.cs b/Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Core/BehaviourTreeNode.cs similarity index 100% rename from Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTreeNode.cs rename to Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Core/BehaviourTreeNode.cs diff --git a/Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTreeNode.cs.shmeta b/Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Core/BehaviourTreeNode.cs.shmeta similarity index 100% rename from Assets/Scripts/AIBehaviour/BehaviourTree/Core/BehaviourTreeNode.cs.shmeta rename to Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Core/BehaviourTreeNode.cs.shmeta diff --git a/Assets/Scripts/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSelector.cs b/Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSelector.cs similarity index 100% rename from Assets/Scripts/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSelector.cs rename to Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSelector.cs diff --git a/Assets/Scripts/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSelector.cs.shmeta b/Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSelector.cs.shmeta similarity index 100% rename from Assets/Scripts/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSelector.cs.shmeta rename to Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSelector.cs.shmeta diff --git a/Assets/Scripts/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSequence.cs b/Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSequence.cs similarity index 100% rename from Assets/Scripts/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSequence.cs rename to Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSequence.cs diff --git a/Assets/Scripts/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSequence.cs.shmeta b/Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSequence.cs.shmeta similarity index 100% rename from Assets/Scripts/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSequence.cs.shmeta rename to Assets/Scripts/Gameplay/AIBehaviour/BehaviourTree/Flow/BehaviourTreeSequence.cs.shmeta diff --git a/Assets/Scripts/AIBehaviour/Implemented/Homeowner1.cs b/Assets/Scripts/Gameplay/AIBehaviour/Implemented/Homeowner1.cs similarity index 100% rename from Assets/Scripts/AIBehaviour/Implemented/Homeowner1.cs rename to Assets/Scripts/Gameplay/AIBehaviour/Implemented/Homeowner1.cs diff --git a/Assets/Scripts/AIBehaviour/Implemented/Homeowner1.cs.shmeta b/Assets/Scripts/Gameplay/AIBehaviour/Implemented/Homeowner1.cs.shmeta similarity index 100% rename from Assets/Scripts/AIBehaviour/Implemented/Homeowner1.cs.shmeta rename to Assets/Scripts/Gameplay/AIBehaviour/Implemented/Homeowner1.cs.shmeta diff --git a/Assets/Scripts/AIBehaviour/Implemented/Homeowner1Events.cs b/Assets/Scripts/Gameplay/AIBehaviour/Implemented/Homeowner1Events.cs similarity index 100% rename from Assets/Scripts/AIBehaviour/Implemented/Homeowner1Events.cs rename to Assets/Scripts/Gameplay/AIBehaviour/Implemented/Homeowner1Events.cs diff --git a/Assets/Scripts/AIBehaviour/Implemented/Homeowner1Events.cs.shmeta b/Assets/Scripts/Gameplay/AIBehaviour/Implemented/Homeowner1Events.cs.shmeta similarity index 100% rename from Assets/Scripts/AIBehaviour/Implemented/Homeowner1Events.cs.shmeta rename to Assets/Scripts/Gameplay/AIBehaviour/Implemented/Homeowner1Events.cs.shmeta diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs b/Assets/Scripts/Gameplay/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs similarity index 100% rename from Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs rename to Assets/Scripts/Gameplay/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs.shmeta b/Assets/Scripts/Gameplay/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs.shmeta similarity index 100% rename from Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs.shmeta rename to Assets/Scripts/Gameplay/AIBehaviour/Implemented/LeafNodes/LeafAttack.cs.shmeta diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs b/Assets/Scripts/Gameplay/AIBehaviour/Implemented/LeafNodes/LeafChase.cs similarity index 100% rename from Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs rename to Assets/Scripts/Gameplay/AIBehaviour/Implemented/LeafNodes/LeafChase.cs diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs.shmeta b/Assets/Scripts/Gameplay/AIBehaviour/Implemented/LeafNodes/LeafChase.cs.shmeta similarity index 100% rename from Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafChase.cs.shmeta rename to Assets/Scripts/Gameplay/AIBehaviour/Implemented/LeafNodes/LeafChase.cs.shmeta diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs b/Assets/Scripts/Gameplay/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs similarity index 100% rename from Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs rename to Assets/Scripts/Gameplay/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs.shmeta b/Assets/Scripts/Gameplay/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs.shmeta similarity index 100% rename from Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs.shmeta rename to Assets/Scripts/Gameplay/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs.shmeta diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs b/Assets/Scripts/Gameplay/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs similarity index 100% rename from Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs rename to Assets/Scripts/Gameplay/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs diff --git a/Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs.shmeta b/Assets/Scripts/Gameplay/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs.shmeta similarity index 100% rename from Assets/Scripts/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs.shmeta rename to Assets/Scripts/Gameplay/AIBehaviour/Implemented/LeafNodes/LeafSearch.cs.shmeta diff --git a/Assets/Scripts/Gameplay/Breakable.cs.shmeta b/Assets/Scripts/Gameplay/Breakable.cs.shmeta deleted file mode 100644 index e8d90963..00000000 --- a/Assets/Scripts/Gameplay/Breakable.cs.shmeta +++ /dev/null @@ -1,3 +0,0 @@ -Name: Breakable -ID: 154790613 -Type: 9 diff --git a/Assets/Scripts/Gameplay/Breakable.cs b/Assets/Scripts/Gameplay/Item/SC_Breakable.cs similarity index 100% rename from Assets/Scripts/Gameplay/Breakable.cs rename to Assets/Scripts/Gameplay/Item/SC_Breakable.cs diff --git a/Assets/Scripts/Gameplay/Item/SC_Breakable.cs.shmeta b/Assets/Scripts/Gameplay/Item/SC_Breakable.cs.shmeta new file mode 100644 index 00000000..b0e30491 --- /dev/null +++ b/Assets/Scripts/Gameplay/Item/SC_Breakable.cs.shmeta @@ -0,0 +1,3 @@ +Name: SC_Breakable +ID: 161935110 +Type: 9 diff --git a/Assets/Scripts/Gameplay/SC_Item.cs b/Assets/Scripts/Gameplay/Item/SC_Item.cs similarity index 100% rename from Assets/Scripts/Gameplay/SC_Item.cs rename to Assets/Scripts/Gameplay/Item/SC_Item.cs diff --git a/Assets/Scripts/Gameplay/SC_Item.cs.shmeta b/Assets/Scripts/Gameplay/Item/SC_Item.cs.shmeta similarity index 100% rename from Assets/Scripts/Gameplay/SC_Item.cs.shmeta rename to Assets/Scripts/Gameplay/Item/SC_Item.cs.shmeta diff --git a/Assets/Scripts/Gameplay/Player/UT_PlayerCaughtState.cs b/Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerCaughtState.cs similarity index 100% rename from Assets/Scripts/Gameplay/Player/UT_PlayerCaughtState.cs rename to Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerCaughtState.cs diff --git a/Assets/Scripts/Gameplay/Player/UT_PlayerCaughtState.cs.shmeta b/Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerCaughtState.cs.shmeta similarity index 100% rename from Assets/Scripts/Gameplay/Player/UT_PlayerCaughtState.cs.shmeta rename to Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerCaughtState.cs.shmeta diff --git a/Assets/Scripts/Gameplay/Player/UT_PlayerFallState.cs b/Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerFallState.cs similarity index 100% rename from Assets/Scripts/Gameplay/Player/UT_PlayerFallState.cs rename to Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerFallState.cs diff --git a/Assets/Scripts/Gameplay/Player/UT_PlayerFallState.cs.shmeta b/Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerFallState.cs.shmeta similarity index 100% rename from Assets/Scripts/Gameplay/Player/UT_PlayerFallState.cs.shmeta rename to Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerFallState.cs.shmeta diff --git a/Assets/Scripts/Gameplay/Player/UT_PlayerIdleState.cs b/Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerIdleState.cs similarity index 100% rename from Assets/Scripts/Gameplay/Player/UT_PlayerIdleState.cs rename to Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerIdleState.cs diff --git a/Assets/Scripts/Gameplay/Player/UT_PlayerIdleState.cs.shmeta b/Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerIdleState.cs.shmeta similarity index 100% rename from Assets/Scripts/Gameplay/Player/UT_PlayerIdleState.cs.shmeta rename to Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerIdleState.cs.shmeta diff --git a/Assets/Scripts/Gameplay/Player/UT_PlayerJumpState.cs b/Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerJumpState.cs similarity index 100% rename from Assets/Scripts/Gameplay/Player/UT_PlayerJumpState.cs rename to Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerJumpState.cs diff --git a/Assets/Scripts/Gameplay/Player/UT_PlayerJumpState.cs.shmeta b/Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerJumpState.cs.shmeta similarity index 100% rename from Assets/Scripts/Gameplay/Player/UT_PlayerJumpState.cs.shmeta rename to Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerJumpState.cs.shmeta diff --git a/Assets/Scripts/Gameplay/Player/UT_PlayerLandState.cs b/Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerLandState.cs similarity index 100% rename from Assets/Scripts/Gameplay/Player/UT_PlayerLandState.cs rename to Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerLandState.cs diff --git a/Assets/Scripts/Gameplay/Player/UT_PlayerLandState.cs.shmeta b/Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerLandState.cs.shmeta similarity index 100% rename from Assets/Scripts/Gameplay/Player/UT_PlayerLandState.cs.shmeta rename to Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerLandState.cs.shmeta diff --git a/Assets/Scripts/Gameplay/Player/UT_PlayerRunState.cs b/Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerRunState.cs similarity index 100% rename from Assets/Scripts/Gameplay/Player/UT_PlayerRunState.cs rename to Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerRunState.cs diff --git a/Assets/Scripts/Gameplay/Player/UT_PlayerRunState.cs.shmeta b/Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerRunState.cs.shmeta similarity index 100% rename from Assets/Scripts/Gameplay/Player/UT_PlayerRunState.cs.shmeta rename to Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerRunState.cs.shmeta diff --git a/Assets/Scripts/Gameplay/Player/UT_PlayerWalkState.cs b/Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerWalkState.cs similarity index 100% rename from Assets/Scripts/Gameplay/Player/UT_PlayerWalkState.cs rename to Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerWalkState.cs diff --git a/Assets/Scripts/Gameplay/Player/UT_PlayerWalkState.cs.shmeta b/Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerWalkState.cs.shmeta similarity index 100% rename from Assets/Scripts/Gameplay/Player/UT_PlayerWalkState.cs.shmeta rename to Assets/Scripts/Gameplay/Player/PlayerStates/UT_PlayerWalkState.cs.shmeta diff --git a/Assets/Scripts/Gameplay/Player/ThirdPersonCamera.cs b/Assets/Scripts/Gameplay/Player/SC_ThirdPersonCamera.cs similarity index 100% rename from Assets/Scripts/Gameplay/Player/ThirdPersonCamera.cs rename to Assets/Scripts/Gameplay/Player/SC_ThirdPersonCamera.cs diff --git a/Assets/Scripts/Gameplay/Player/SC_ThirdPersonCamera.cs.shmeta b/Assets/Scripts/Gameplay/Player/SC_ThirdPersonCamera.cs.shmeta new file mode 100644 index 00000000..c88897d1 --- /dev/null +++ b/Assets/Scripts/Gameplay/Player/SC_ThirdPersonCamera.cs.shmeta @@ -0,0 +1,3 @@ +Name: SC_ThirdPersonCamera +ID: 166247489 +Type: 9 diff --git a/Assets/Scripts/Gameplay/Player/ThirdPersonCamera.cs.shmeta b/Assets/Scripts/Gameplay/Player/ThirdPersonCamera.cs.shmeta deleted file mode 100644 index 2f18c2fb..00000000 --- a/Assets/Scripts/Gameplay/Player/ThirdPersonCamera.cs.shmeta +++ /dev/null @@ -1,3 +0,0 @@ -Name: ThirdPersonCamera -ID: 154161201 -Type: 9 diff --git a/Assets/Scripts/Gameplay/SC_GameManager.cs b/Assets/Scripts/Gameplay/SC_GameManager.cs index e2f1b45f..23ef00e3 100644 --- a/Assets/Scripts/Gameplay/SC_GameManager.cs +++ b/Assets/Scripts/Gameplay/SC_GameManager.cs @@ -38,6 +38,7 @@ public class GameManager : Script protected override void awake() { Audio.PlayBGMOnce2D("event:/Music/player_undetected"); + Audio.PlayBGMOnce2D("event:/Ambience/roomtone_kitchen"); totalItemCount = 0; Score = 0; currGameState = GameState.START; diff --git a/Assets/Scripts/SC_EndScene.cs b/Assets/Scripts/UI/SC_EndScene.cs similarity index 67% rename from Assets/Scripts/SC_EndScene.cs rename to Assets/Scripts/UI/SC_EndScene.cs index 8d3104c5..3b8c6bf0 100644 --- a/Assets/Scripts/SC_EndScene.cs +++ b/Assets/Scripts/UI/SC_EndScene.cs @@ -11,16 +11,24 @@ public class EndScene : Script } protected override void update() { - if (Input.GetKey(Input.KeyCode.R)) + if (Input.GetKeyDown(Input.KeyCode.R)) { Audio.PlaySFXOnce2D("event:/UI/mouse_down_element"); + } + if (Input.GetKeyUp(Input.KeyCode.R)) + { + Audio.PlaySFXOnce2D("event:/UI/success"); Audio.StopAllSounds(); SceneManager.ChangeScene(mainGameScene); } - if (Input.GetKey(Input.KeyCode.M)) + if (Input.GetKeyDown(Input.KeyCode.M)) { Audio.PlaySFXOnce2D("event:/UI/mouse_down_element"); + } + if (Input.GetKeyUp(Input.KeyCode.M)) + { + Audio.PlaySFXOnce2D("event:/UI/success"); Audio.StopAllSounds(); SceneManager.ChangeScene(mainMainScene); } diff --git a/Assets/Scripts/SC_EndScene.cs.shmeta b/Assets/Scripts/UI/SC_EndScene.cs.shmeta similarity index 100% rename from Assets/Scripts/SC_EndScene.cs.shmeta rename to Assets/Scripts/UI/SC_EndScene.cs.shmeta diff --git a/Assets/Scripts/SC_MainMenu.cs b/Assets/Scripts/UI/SC_MainMenu.cs similarity index 82% rename from Assets/Scripts/SC_MainMenu.cs rename to Assets/Scripts/UI/SC_MainMenu.cs index cd2caf3f..4f9320ee 100644 --- a/Assets/Scripts/SC_MainMenu.cs +++ b/Assets/Scripts/UI/SC_MainMenu.cs @@ -12,6 +12,11 @@ public class MainMenu : Script if (Input.GetKeyDown(Input.KeyCode.Space)) { Audio.PlaySFXOnce2D("event:/UI/mouse_down_element"); + } + + if (Input.GetKeyUp(Input.KeyCode.Space)) + { + Audio.PlaySFXOnce2D("event:/UI/success"); SceneManager.ChangeScene(86098106); Audio.StopAllSounds(); } diff --git a/Assets/Scripts/SC_MainMenu.cs.shmeta b/Assets/Scripts/UI/SC_MainMenu.cs.shmeta similarity index 100% rename from Assets/Scripts/SC_MainMenu.cs.shmeta rename to Assets/Scripts/UI/SC_MainMenu.cs.shmeta From 52dc993941676f93f0af4985c197b63e38072016 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Fri, 2 Dec 2022 17:44:44 +0800 Subject: [PATCH 025/275] goodbye react --- .../src/Application/SBApplication.cpp | 29 -- SHADE_Application/src/Scenes/SBMainScene.cpp | 10 - SHADE_Engine/src/Camera/SHCameraSystem.cpp | 28 +- .../Inspector/SHEditorComponentView.hpp | 4 +- .../Physics/Collision/SHCollisionListener.cpp | 254 ----------- .../Physics/Collision/SHCollisionListener.h | 80 ---- .../Physics/Collision/SHPhysicsRaycaster.cpp | 350 -------------- .../Physics/Collision/SHPhysicsRaycaster.h | 134 ------ .../Physics/Interface/SHColliderComponent.cpp | 131 +----- .../Physics/Interface/SHColliderComponent.h | 9 - .../Physics/Interface/SHCollisionShape.cpp | 82 ++-- .../Interface/SHRigidBodyComponent.cpp | 310 ++----------- .../Physics/Interface/SHRigidBodyComponent.h | 28 +- .../Physics/PhysicsObject/SHPhysicsObject.cpp | 431 ------------------ .../Physics/PhysicsObject/SHPhysicsObject.h | 111 ----- .../PhysicsObject/SHPhysicsObjectManager.cpp | 309 ------------- .../PhysicsObject/SHPhysicsObjectManager.h | 181 -------- .../System/SHPhysicsDebugDrawSystem.cpp | 334 -------------- .../Physics/System/SHPhysicsDebugDrawSystem.h | 138 ------ .../src/Physics/System/SHPhysicsSystem.cpp | 394 ---------------- .../src/Physics/System/SHPhysicsSystem.h | 151 +----- .../System/SHPhysicsSystemInterface.cpp | 96 +--- .../Physics/System/SHPhysicsSystemInterface.h | 9 +- .../System/SHPhysicsSystemRoutines.cpp | 307 +------------ SHADE_Managed/src/Components/Collider.cxx | 2 +- SHADE_Managed/src/Physics/Physics.cxx | 18 +- SHADE_Managed/src/Utility/Convert.cxx | 32 -- SHADE_Managed/src/Utility/Convert.hxx | 14 - 28 files changed, 131 insertions(+), 3845 deletions(-) delete mode 100644 SHADE_Engine/src/Physics/Collision/SHCollisionListener.cpp delete mode 100644 SHADE_Engine/src/Physics/Collision/SHCollisionListener.h delete mode 100644 SHADE_Engine/src/Physics/Collision/SHPhysicsRaycaster.cpp delete mode 100644 SHADE_Engine/src/Physics/Collision/SHPhysicsRaycaster.h delete mode 100644 SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObject.cpp delete mode 100644 SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObject.h delete mode 100644 SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObjectManager.cpp delete mode 100644 SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObjectManager.h delete mode 100644 SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp delete mode 100644 SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h diff --git a/SHADE_Application/src/Application/SBApplication.cpp b/SHADE_Application/src/Application/SBApplication.cpp index 5aa1eb00..ce0a939c 100644 --- a/SHADE_Application/src/Application/SBApplication.cpp +++ b/SHADE_Application/src/Application/SBApplication.cpp @@ -31,7 +31,6 @@ #include "Input/SHInputManager.h" #include "Math/Transform/SHTransformSystem.h" #include "Physics/System/SHPhysicsSystem.h" -#include "Physics/System/SHPhysicsDebugDrawSystem.h" #include "Scripting/SHScriptEngine.h" #include "UI/SHUISystem.h" @@ -74,9 +73,6 @@ namespace Sandbox SHSystemManager::CreateSystem(); SHSystemManager::CreateSystem(); SHSystemManager::CreateSystem(); -#ifndef _PUBLISH - SHSystemManager::CreateSystem(); -#endif SHSystemManager::CreateSystem(); SHSystemManager::CreateSystem(); @@ -116,10 +112,6 @@ namespace Sandbox SHSystemManager::RegisterRoutine(); SHSystemManager::RegisterRoutine(); -#ifndef _PUBLISH - SHSystemManager::RegisterRoutine(); -#endif - SHSystemManager::RegisterRoutine(); SHSystemManager::RegisterRoutine(); @@ -192,27 +184,6 @@ namespace Sandbox #else SHSystemManager::RunRoutines(false, SHFrameRateController::GetRawDeltaTime()); #endif - // TODO: Move into an Editor menu - static bool drawContacts = false; - if (SHInputManager::GetKeyDown(SHInputManager::SH_KEYCODE::F9)) - { - drawContacts = !drawContacts; - SHSystemManager::GetSystem()->SetDebugDrawFlag(SHPhysicsDebugDrawSystem::DebugDrawFlags::CONTACT_POINTS, drawContacts); - SHSystemManager::GetSystem()->SetDebugDrawFlag(SHPhysicsDebugDrawSystem::DebugDrawFlags::CONTACT_NORMALS, drawContacts); - } - static bool drawColliders = false; - if (SHInputManager::GetKeyDown(SHInputManager::SH_KEYCODE::F10)) - { - drawColliders = !drawColliders; - SHSystemManager::GetSystem()->SetDebugDrawFlag(SHPhysicsDebugDrawSystem::DebugDrawFlags::COLLIDER, drawColliders); - } - static bool drawRays = false; - if (SHInputManager::GetKeyDown(SHInputManager::SH_KEYCODE::F11)) - { - drawRays = !drawRays; - SHSystemManager::GetSystem()->SetDebugDrawFlag(SHPhysicsDebugDrawSystem::DebugDrawFlags::RAYCASTS, drawRays); - } - } // Finish all graphics jobs first graphicsSystem->AwaitGraphicsExecution(); diff --git a/SHADE_Application/src/Scenes/SBMainScene.cpp b/SHADE_Application/src/Scenes/SBMainScene.cpp index 73926115..55ce32b0 100644 --- a/SHADE_Application/src/Scenes/SBMainScene.cpp +++ b/SHADE_Application/src/Scenes/SBMainScene.cpp @@ -51,16 +51,6 @@ namespace Sandbox return; } - #ifdef SHEDITOR - - physicsSystem->ForceBuild(SHSceneManager::GetCurrentSceneGraph()); - - #else - - physicsSystem->BuildScene(SHSceneManager::GetCurrentSceneGraph()); - - #endif - /*-----------------------------------------------------------------------*/ /* TESTING CODE */ /*-----------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Camera/SHCameraSystem.cpp b/SHADE_Engine/src/Camera/SHCameraSystem.cpp index 8ef7ff64..d94bd3f8 100644 --- a/SHADE_Engine/src/Camera/SHCameraSystem.cpp +++ b/SHADE_Engine/src/Camera/SHCameraSystem.cpp @@ -186,20 +186,20 @@ namespace SHADE //SHLOG_INFO("Ray position: {},{},{} direction:{},{},{}",pivot.ray.position.x, pivot.ray.position.y, pivot.ray.position.z,pivot.ray.direction.x, pivot.ray.direction.y, pivot.ray.direction.z) - auto result = physicsSystem->Raycast(pivot.ray ); - if (result && result.distance < pivot.GetArmLength()) - { - - SHVec3 newOffset = SHVec3{ 0.0f,0.0f, result.distance * 0.8f }; - newOffset = SHVec3::RotateX(newOffset, -(SHMath::DegreesToRadians(pivot.GetPitch()))); - newOffset = SHVec3::RotateY(newOffset, (SHMath::DegreesToRadians(pivot.GetYaw()))); - pivot.offset = newOffset; - //SHLOG_INFO("CAMERA COLLISION HIT, {}", result.distance); - } - else - { - //SHLOG_INFO("CAMERA COLLISION CANT HIT CAMERA"); - } + //auto result = physicsSystem->Raycast(pivot.ray ); + //if (result && result.distance < pivot.GetArmLength()) + //{ + // + // SHVec3 newOffset = SHVec3{ 0.0f,0.0f, result.distance * 0.8f }; + // newOffset = SHVec3::RotateX(newOffset, -(SHMath::DegreesToRadians(pivot.GetPitch()))); + // newOffset = SHVec3::RotateY(newOffset, (SHMath::DegreesToRadians(pivot.GetYaw()))); + // pivot.offset = newOffset; + // //SHLOG_INFO("CAMERA COLLISION HIT, {}", result.distance); + //} + //else + //{ + // //SHLOG_INFO("CAMERA COLLISION CANT HIT CAMERA"); + //} diff --git a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp index 0f3dce3e..f70ab889 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp +++ b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp @@ -285,8 +285,8 @@ namespace SHADE if(ImGui::CollapsingHeader("Debug Information", ImGuiTreeNodeFlags_DefaultOpen))//Dynamic or Kinematic only fields { SHEditorWidgets::DragFloat("Mass", [component] { return component->GetMass(); }, [](float value){}, "Mass", 0.1f, 0.0f, std::numeric_limits::infinity(), "%.3f", ImGuiSliderFlags_ReadOnly); - SHEditorWidgets::DragVec3("Position", { "X", "Y", "Z" }, [component] {return component->GetPosition(); }, [](SHVec3 const& value) {}, false, "Position", 0.1f, "%.3f", 0.0f, 0.0f, ImGuiSliderFlags_ReadOnly); - SHEditorWidgets::DragVec3("Rotation", { "X", "Y", "Z" }, [component] {return component->GetRotation(); }, [](SHVec3 const& value) {}, false, "Rotation", 0.1f, "%.3f", 0.0f, 0.0f, ImGuiSliderFlags_ReadOnly); + //SHEditorWidgets::DragVec3("Position", { "X", "Y", "Z" }, [component] {return component->GetPosition(); }, [](SHVec3 const& value) {}, false, "Position", 0.1f, "%.3f", 0.0f, 0.0f, ImGuiSliderFlags_ReadOnly); + //SHEditorWidgets::DragVec3("Rotation", { "X", "Y", "Z" }, [component] {return component->GetRotation(); }, [](SHVec3 const& value) {}, false, "Rotation", 0.1f, "%.3f", 0.0f, 0.0f, ImGuiSliderFlags_ReadOnly); if (rbType == SHRigidBodyComponent::Type::DYNAMIC || rbType == SHRigidBodyComponent::Type::KINEMATIC) //Dynamic or Kinematic only fields { SHEditorWidgets::DragVec3("Velocity", { "X", "Y", "Z" }, [component] {return component->GetLinearVelocity(); }, [](SHVec3 const& value) {}, false, "Linear Velocity", 0.1f, "%.3f", 0.0f, 0.0f, ImGuiSliderFlags_ReadOnly); diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionListener.cpp b/SHADE_Engine/src/Physics/Collision/SHCollisionListener.cpp deleted file mode 100644 index 3e485153..00000000 --- a/SHADE_Engine/src/Physics/Collision/SHCollisionListener.cpp +++ /dev/null @@ -1,254 +0,0 @@ -/**************************************************************************************** - * \file SHCollisionListener.cpp - * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Implementation for a Collision Listener. - * - * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or - * disclosure of this file or its contents without the prior written consent - * of DigiPen Institute of Technology is prohibited. -****************************************************************************************/ - -#include - -// Primary Header -#include "SHCollisionListener.h" - -// Project Headers -#include "ECS_Base/Managers/SHEntityManager.h" -#include "Physics/PhysicsObject/SHPhysicsObject.h" -#include "Physics/System/SHPhysicsSystem.h" -#include "Scene/SHSceneManager.h" - -/*-------------------------------------------------------------------------------------*/ -/* Local Helper Functions */ -/*-------------------------------------------------------------------------------------*/ - -uint32_t matchColliders(const SHADE::SHPhysicsObject&physicsObject, const rp3d::Entity colliderID) -{ - for (uint32_t i = 0; i < physicsObject.GetCollisionBody()->getNbColliders(); ++i) - { - const auto* collider = physicsObject.GetCollisionBody()->getCollider(i); - if (collider->getEntity() == colliderID) - return i; - } - - return std::numeric_limits::max(); -} - -namespace SHADE -{ - /*-----------------------------------------------------------------------------------*/ - /* Constructors & Destructor Definitions */ - /*-----------------------------------------------------------------------------------*/ - - SHCollisionListener::SHCollisionListener() noexcept - : system { nullptr } - {} - - /*-----------------------------------------------------------------------------------*/ - /* Getter Function Definitions */ - /*-----------------------------------------------------------------------------------*/ - - const std::vector& SHCollisionListener::GetCollisionInfoContainer() const noexcept - { - return collisionInfoContainer; - } - - const std::vector& SHCollisionListener::GetTriggerInfoContainer() const noexcept - { - return triggerInfoContainer; - } - - /*-----------------------------------------------------------------------------------*/ - /* Public Function Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHCollisionListener::BindToSystem(SHPhysicsSystem* physicsSystem) noexcept - { - system = physicsSystem; - } - - void SHCollisionListener::BindToWorld(rp3d::PhysicsWorld* world) noexcept - { - if (!world) - return; - - world->setEventListener(this); - } - - void SHCollisionListener::CleanContainers() noexcept - { - static const auto CLEAR = [](std::vector& container) - { - for (auto eventIter = container.begin(); eventIter != container.end();) - { - const SHCollisionInfo& C_INFO = *eventIter; - - const bool INVALID_ENTITY = !SHEntityManager::IsValidEID(C_INFO.GetEntityA()) || !SHEntityManager::IsValidEID(C_INFO.GetEntityB()); - if (INVALID_ENTITY) - { - eventIter = container.erase(eventIter); - continue; - } - else - { - const bool CLEAR_EVENT = C_INFO.GetCollisionState() == SHCollisionInfo::State::EXIT || C_INFO.GetCollisionState() == SHCollisionInfo::State::INVALID; - - const bool INACTIVE_OBJECT = !SHSceneManager::CheckNodeAndComponentsActive(C_INFO.GetEntityA()) - || !SHSceneManager::CheckNodeAndComponentsActive(C_INFO.GetEntityB()); - - if (CLEAR_EVENT || INACTIVE_OBJECT) - { - eventIter = container.erase(eventIter); - continue; - } - } - - ++eventIter; - } - }; - - CLEAR(collisionInfoContainer); - CLEAR(triggerInfoContainer); - } - - void SHCollisionListener::ClearContainers() noexcept - { - collisionInfoContainer.clear(); - triggerInfoContainer.clear(); - } - - void SHCollisionListener::onContact(const rp3d::CollisionCallback::CallbackData& callbackData) - { - for (uint32_t i = 0; i < callbackData.getNbContactPairs(); ++i) - { - const auto CONTACT_PAIR = callbackData.getContactPair(i); - const SHCollisionInfo NEW_INFO = generateCollisionInfo(CONTACT_PAIR); - - updateInfoContainers(NEW_INFO, collisionInfoContainer); - } - } - - void SHCollisionListener::onTrigger(const rp3d::OverlapCallback::CallbackData& callbackData) - { - for (uint32_t i = 0; i < callbackData.getNbOverlappingPairs(); ++i) - { - const auto OVERLAP_PAIR = callbackData.getOverlappingPair(i); - const SHCollisionInfo NEW_INFO = generateTriggerInfo(OVERLAP_PAIR); - - updateInfoContainers(NEW_INFO, triggerInfoContainer); - } - } - - /*-----------------------------------------------------------------------------------*/ - /* Private Function Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHCollisionListener::updateInfoContainers(const SHCollisionInfo& collisionEvent, std::vector& container) noexcept - { - const auto IT = std::ranges::find_if(container.begin(), container.end(), [&](const SHCollisionInfo& info) - { - const bool ENTITY_MATCH = (info.ids[0] == collisionEvent.ids[0] && info.ids[1] == collisionEvent.ids[1]) - || (info.ids[0] == collisionEvent.ids[1] && info.ids[1] == collisionEvent.ids[0]); - const bool COLLIDERS_MATCH = (info.ids[2] == collisionEvent.ids[2] && info.ids[3] == collisionEvent.ids[3]) - || (info.ids[2] == collisionEvent.ids[3] && info.ids[3] == collisionEvent.ids[2]); - return ENTITY_MATCH && COLLIDERS_MATCH; - }); - - if (IT == container.end()) - container.emplace_back(collisionEvent); - else - IT->collisionState = collisionEvent.collisionState; - } - - SHCollisionInfo SHCollisionListener::generateCollisionInfo(const rp3d::CollisionCallback::ContactPair& cp) const noexcept - { - SHCollisionInfo cInfo; - - // Update collision state - cInfo.collisionState = static_cast(cp.getEventType()); - - // Match body and collider for collision event - const rp3d::Entity body1 = cp.getBody1()->getEntity(); - const rp3d::Entity body2 = cp.getBody2()->getEntity(); - const rp3d::Entity collider1 = cp.getCollider1()->getEntity(); - const rp3d::Entity collider2 = cp.getCollider2()->getEntity(); - - // Find and match both ids - bool matched[2] = { false, false }; - - - for (auto& [entityID, physicsObject] : system->GetPhysicsObjects()) - { - // Match body 1 - if (matched[SHCollisionInfo::ENTITY_A] == false && physicsObject.GetCollisionBody()->getEntity() == body1) - { - cInfo.ids[SHCollisionInfo::ENTITY_A] = entityID; - cInfo.ids[SHCollisionInfo::COLLIDER_A] = matchColliders(physicsObject, collider1); - - matched[SHCollisionInfo::ENTITY_A] = true; - } - - // Match body 2 - if (matched[SHCollisionInfo::ENTITY_B] == false && physicsObject.GetCollisionBody()->getEntity() == body2) - { - cInfo.ids[SHCollisionInfo::ENTITY_B] = entityID; - cInfo.ids[SHCollisionInfo::COLLIDER_B] = matchColliders(physicsObject, collider2); - - matched[SHCollisionInfo::ENTITY_B] = true; - } - - if (matched[SHCollisionInfo::ENTITY_A] == true && matched[SHCollisionInfo::ENTITY_B] == true) - return cInfo; - } - - return cInfo; - } - - SHCollisionInfo SHCollisionListener::generateTriggerInfo(const rp3d::OverlapCallback::OverlapPair& cp) const noexcept - { - SHCollisionInfo cInfo; - - // Update collision state - cInfo.collisionState = static_cast(cp.getEventType()); - - // Match body and collider for collision event - const rp3d::Entity body1 = cp.getBody1()->getEntity(); - const rp3d::Entity body2 = cp.getBody2()->getEntity(); - const rp3d::Entity collider1 = cp.getCollider1()->getEntity(); - const rp3d::Entity collider2 = cp.getCollider2()->getEntity(); - - // Find and match both ids - bool matched[2] = { false, false }; - - - for (auto& [entityID, physicsObject] : system->GetPhysicsObjects()) - { - // Match body 1 - if (matched[SHCollisionInfo::ENTITY_A] == false && physicsObject.GetCollisionBody()->getEntity() == body1) - { - cInfo.ids[SHCollisionInfo::ENTITY_A] = entityID; - cInfo.ids[SHCollisionInfo::COLLIDER_A] = matchColliders(physicsObject, collider1); - - matched[SHCollisionInfo::ENTITY_A] = true; - } - - // Match body 2 - if (matched[SHCollisionInfo::ENTITY_B] == false && physicsObject.GetCollisionBody()->getEntity() == body2) - { - cInfo.ids[SHCollisionInfo::ENTITY_B] = entityID; - cInfo.ids[SHCollisionInfo::COLLIDER_B] = matchColliders(physicsObject, collider2); - - matched[SHCollisionInfo::ENTITY_B] = true; - } - - if (matched[SHCollisionInfo::ENTITY_A] == true && matched[SHCollisionInfo::ENTITY_B] == true) - return cInfo; - } - - return cInfo; - } - - - -} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionListener.h b/SHADE_Engine/src/Physics/Collision/SHCollisionListener.h deleted file mode 100644 index 62882556..00000000 --- a/SHADE_Engine/src/Physics/Collision/SHCollisionListener.h +++ /dev/null @@ -1,80 +0,0 @@ -/**************************************************************************************** - * \file SHCollisionListener.h - * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Interface for a Collision Listener. - * - * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or - * disclosure of this file or its contents without the prior written consent - * of DigiPen Institute of Technology is prohibited. -****************************************************************************************/ - -#pragma once - -// External Dependencies -#include - -// Project Headers -#include "SH_API.h" -#include "SHCollisionInfo.h" - -namespace SHADE -{ - /*-----------------------------------------------------------------------------------*/ - /* Forward Declarations */ - /*-----------------------------------------------------------------------------------*/ - - class SHPhysicsSystem; - - /*-----------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*-----------------------------------------------------------------------------------*/ - - class SH_API SHCollisionListener final : public rp3d::EventListener - { - public: - /*---------------------------------------------------------------------------------*/ - /* Constructors & Destructor */ - /*---------------------------------------------------------------------------------*/ - - SHCollisionListener() noexcept; - - /*---------------------------------------------------------------------------------*/ - /* Getter Functions */ - /*---------------------------------------------------------------------------------*/ - - [[nodiscard]] const std::vector& GetCollisionInfoContainer () const noexcept; - [[nodiscard]] const std::vector& GetTriggerInfoContainer () const noexcept; - - /*---------------------------------------------------------------------------------*/ - /* Function Members */ - /*---------------------------------------------------------------------------------*/ - - void BindToSystem (SHPhysicsSystem* physicsSystem) noexcept; - void BindToWorld (rp3d::PhysicsWorld* world) noexcept; - void CleanContainers () noexcept; - void ClearContainers () noexcept; - - void onContact (const rp3d::CollisionCallback::CallbackData& callbackData) override; - void onTrigger (const rp3d::OverlapCallback::CallbackData& callbackData) override; - - private: - - /*---------------------------------------------------------------------------------*/ - /* Data Members */ - /*---------------------------------------------------------------------------------*/ - - SHPhysicsSystem* system; - std::vector collisionInfoContainer; - std::vector triggerInfoContainer; - - /*---------------------------------------------------------------------------------*/ - /* Function Members */ - /*---------------------------------------------------------------------------------*/ - - static void updateInfoContainers (const SHCollisionInfo& collisionEvent, std::vector& container) noexcept; - - SHCollisionInfo generateCollisionInfo (const rp3d::CollisionCallback::ContactPair& cp) const noexcept; - SHCollisionInfo generateTriggerInfo (const rp3d::OverlapCallback::OverlapPair& cp) const noexcept; - }; - -} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHPhysicsRaycaster.cpp b/SHADE_Engine/src/Physics/Collision/SHPhysicsRaycaster.cpp deleted file mode 100644 index cab5c93b..00000000 --- a/SHADE_Engine/src/Physics/Collision/SHPhysicsRaycaster.cpp +++ /dev/null @@ -1,350 +0,0 @@ -/**************************************************************************************** - * \file SHPhysicsRaycaster.cpp - * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Implementation for a Physics Raycaster. - * - * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or - * disclosure of this file or its contents without the prior written consent - * of DigiPen Institute of Technology is prohibited. -****************************************************************************************/ - -#include - -// Primary Header -#include "SHPhysicsRaycaster.h" - -/* - * TODO(DIREN): - * Once the physics engine has been rebuilt, this whole implementation should change - * and just call PhysicsWorld.Raycast etc. - * - * SHRaycastResult can be converted to a bool when necessary. - */ - -namespace SHADE -{ - /*-----------------------------------------------------------------------------------*/ - /* Constructors & Destructor Definitions */ - /*-----------------------------------------------------------------------------------*/ - - SHPhysicsRaycaster::SHPhysicsRaycaster() noexcept - : world { nullptr } - {} - - /*-----------------------------------------------------------------------------------*/ - /* Getter Function Definitions */ - /*-----------------------------------------------------------------------------------*/ - - const SHPhysicsRaycaster::RaycastPairs& SHPhysicsRaycaster::GetRaycasts() const noexcept - { - return raycasts; - } - - /*-----------------------------------------------------------------------------------*/ - /* Getter Function Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHPhysicsRaycaster::SetObjectManager(SHPhysicsObjectManager* physicsObjectManager) noexcept - { - objectManager = physicsObjectManager; - } - - /*-----------------------------------------------------------------------------------*/ - /* Public Function Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHPhysicsRaycaster::BindToWorld(rp3d::PhysicsWorld* physicsWorld) noexcept - { - world = physicsWorld; - } - - void SHPhysicsRaycaster::ClearFrame() noexcept - { - raycasts.clear(); - } - - SHPhysicsRaycastResult SHPhysicsRaycaster::Raycast(const SHRay& ray, float distance, const SHCollisionTag& collisionTag) noexcept - { - // Reset temp - temp = SHPhysicsRaycastResult{}; - temp.distance = distance; - - if (!world) - { - SHLOG_ERROR("Physics world missing for raycasting!") - return temp; - } - - // If distance in infinity, cast to the default max distance of 2 km. - if (distance == std::numeric_limits::infinity()) - { - world->raycast(ray, this, collisionTag); - } - else - { - const SHVec3 END_POINT = ray.position + ray.direction * distance; - const rp3d::Ray RP3D_RAY{ ray.position, END_POINT }; - world->raycast(RP3D_RAY, this, collisionTag); - } - - // If a hit was found, populate temp info for return. - if (temp.hit) - { - temp.distance = SHVec3::Distance(ray.position, temp.position); - temp.angle = SHVec3::Angle(ray.position, temp.position); - } - - raycasts.emplace_back(ray, temp); - return temp; - } - - SHPhysicsRaycastResult SHPhysicsRaycaster::Linecast(const SHVec3& start, const SHVec3& end, const SHCollisionTag& collisionTag) noexcept - { - temp = SHPhysicsRaycastResult{}; - temp.distance = SHVec3::Distance(start, end); - - if (!world) - { - SHLOG_ERROR("Physics world missing for raycasting!") - return temp; - } - - const rp3d::Ray RP3D_RAY{ start, end }; - world->raycast(RP3D_RAY, this, collisionTag); - - if (temp.hit) - { - temp.distance = SHVec3::Distance(start, temp.position); - temp.angle = SHVec3::Angle(start, temp.position); - } - - raycasts.emplace_back(RP3D_RAY, temp); - return temp; - } - - SHPhysicsRaycastResult SHPhysicsRaycaster::ColliderRaycast(EntityID eid, const SHRay& ray, float distance) noexcept - { - SHPhysicsRaycastResult result; - result.distance = distance; - - // Get a valid physics object with at least 1 collider. - const auto* PHYSICS_OBJECT = validateColliderRaycast(eid); - if (!PHYSICS_OBJECT) - return result; - - auto* rp3dBody = PHYSICS_OBJECT->GetCollisionBody(); - - // Data to populate - rp3d::RaycastInfo rp3dRaycastInfo; - bool hit = false; - - if (distance == std::numeric_limits::infinity()) - { - hit = rp3dBody->raycast(ray, rp3dRaycastInfo); - } - else - { - const SHVec3 END_POINT = ray.position + ray.direction * distance; - const rp3d::Ray RP3D_RAY{ ray.position, END_POINT }; - hit = rp3dBody->raycast(RP3D_RAY, rp3dRaycastInfo); - } - - if (hit) - { - result.hit = true; - result.position = rp3dRaycastInfo.worldPoint; - result.normal = rp3dRaycastInfo.worldPoint; - result.distance = SHVec3::Distance(ray.position, result.position); - result.angle = SHVec3::Angle(ray.position, result.position); - result.entityHit = eid; - result.shapeIndex = findColliderIndex(rp3dBody, rp3dRaycastInfo.collider->getEntity()); - } - - raycasts.emplace_back(ray, result); - return result; - } - - SHPhysicsRaycastResult SHPhysicsRaycaster::ColliderRaycast(EntityID eid, int shapeIndex, const SHRay& ray, float distance) noexcept - { - SHPhysicsRaycastResult result; - result.distance = distance; - - // Get a valid physics object with at least 1 collider. - const auto* PHYSICS_OBJECT = validateColliderRaycast(eid); - if (!PHYSICS_OBJECT) - return result; - - // Boundary check for shape index - if (shapeIndex < 0 || shapeIndex >= static_cast(PHYSICS_OBJECT->GetCollisionBody()->getNbColliders())) - { - SHLOGV_WARNING("Invalid collision shape index passed in") - return result; - } - - auto* rp3dCollider = PHYSICS_OBJECT->GetCollisionBody()->getCollider(shapeIndex); - - rp3d::RaycastInfo rp3dRaycastInfo; - bool hit = false; - if (distance == std::numeric_limits::infinity()) - { - hit = rp3dCollider->raycast(ray, rp3dRaycastInfo); - } - else - { - const SHVec3 END_POINT = ray.position + ray.direction * distance; - const rp3d::Ray RP3D_RAY{ ray.position, END_POINT }; - hit = rp3dCollider->raycast(RP3D_RAY, rp3dRaycastInfo); - } - - if (hit) - { - result.hit = true; - result.position = rp3dRaycastInfo.worldPoint; - result.normal = rp3dRaycastInfo.worldPoint; - result.distance = SHVec3::Distance(ray.position, result.position); - result.angle = SHVec3::Angle(ray.position, result.position); - result.entityHit = eid; - result.shapeIndex = shapeIndex; - } - - raycasts.emplace_back(ray, result); - return result; - } - - SHPhysicsRaycastResult SHPhysicsRaycaster::ColliderLinecast(EntityID eid, const SHVec3& start, const SHVec3& end) noexcept - { - SHPhysicsRaycastResult result; - result.distance = SHVec3::Distance(start, end); - - const auto* PHYSICS_OBJECT = validateColliderRaycast(eid); - if (!PHYSICS_OBJECT) - return result; - - auto* rp3dBody = PHYSICS_OBJECT->GetCollisionBody(); - - rp3d::RaycastInfo rp3dRaycastInfo; - - const rp3d::Ray RP3D_RAY{ start, end }; - if (rp3dBody->raycast(RP3D_RAY, rp3dRaycastInfo)) - { - result.hit = true; - result.position = rp3dRaycastInfo.worldPoint; - result.normal = rp3dRaycastInfo.worldPoint; - result.distance = SHVec3::Distance(start, result.position); - result.angle = SHVec3::Angle(end, result.position); - result.entityHit = eid; - result.shapeIndex = findColliderIndex(rp3dBody, rp3dRaycastInfo.collider->getEntity()); - } - - raycasts.emplace_back(RP3D_RAY, result); - return result; - } - - SHPhysicsRaycastResult SHPhysicsRaycaster::ColliderLinecast(EntityID eid, int shapeIndex, const SHVec3& start, const SHVec3& end) noexcept - { - SHPhysicsRaycastResult result; - result.distance = SHVec3::Distance(start, end); - - const auto* PHYSICS_OBJECT = validateColliderRaycast(eid); - if (!PHYSICS_OBJECT) - return result; - - if (shapeIndex < 0 || shapeIndex >= static_cast(PHYSICS_OBJECT->GetCollisionBody()->getNbColliders())) - { - SHLOGV_WARNING("Invalid collision shape index passed in") - return result; - } - - auto* rp3dCollider = PHYSICS_OBJECT->GetCollisionBody()->getCollider(shapeIndex); - - rp3d::RaycastInfo rp3dRaycastInfo; - - const rp3d::Ray RP3D_RAY{ start, end }; - if (rp3dCollider->raycast(RP3D_RAY, rp3dRaycastInfo)) - { - result.hit = true; - result.position = rp3dRaycastInfo.worldPoint; - result.normal = rp3dRaycastInfo.worldPoint; - result.distance = SHVec3::Distance(start, result.position); - result.angle = SHVec3::Angle(end, result.position); - result.entityHit = eid; - result.shapeIndex = shapeIndex; - } - - raycasts.emplace_back(RP3D_RAY, result); - return result; - } - - rp3d::decimal SHPhysicsRaycaster::notifyRaycastHit(const rp3d::RaycastInfo& raycastInfo) - { - temp.hit = true; - temp.position = raycastInfo.worldPoint; - temp.normal = raycastInfo.worldNormal; - - if (!objectManager) - { - SHLOGV_ERROR("No physics object manager linked with raycaster to match bodies") - return 0.0f; - } - - // Compare body IDs to find the matching physics object - const auto HIT_BODY_EID = raycastInfo.body->getEntity(); - - for (const auto& [entityID, physicsObject] : objectManager->GetPhysicsObjects()) - { - const auto RP3D_BODY = physicsObject.GetCollisionBody(); - - // Match rp3d bodies - if (RP3D_BODY->getEntity() != HIT_BODY_EID) - continue; - - temp.entityHit = entityID; - - // Find collider index - if (const int INDEX = findColliderIndex(RP3D_BODY, raycastInfo.collider->getEntity()); INDEX > -1) - { - temp.shapeIndex = INDEX; - break; - } - } - - return 0.0f; - } - - /*-----------------------------------------------------------------------------------*/ - /* Private Function Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - SHPhysicsObject* SHPhysicsRaycaster::validateColliderRaycast(EntityID eid) noexcept - { - if (!objectManager) - { - SHLOGV_ERROR("No physics object manager linked with raycaster to match bodies") - return nullptr; - } - - auto* physicsObject = objectManager->GetPhysicsObject(eid); - if (!physicsObject || physicsObject->GetCollisionBody()->getNbColliders() == 0) - { - SHLOGV_WARNING("Cannot cast ray at an entity without colliders!") - return nullptr; - } - - return physicsObject; - } - - int SHPhysicsRaycaster::findColliderIndex(const rp3d::CollisionBody* rp3dBody, rp3d::Entity rp3dColliderEID) noexcept - { - const int NUM_COLLISION_SHAPES = static_cast(rp3dBody->getNbColliders()); - for (int i = 0; i < NUM_COLLISION_SHAPES; ++i) - { - const auto COLLIDER_EID = rp3dBody->getCollider(i)->getEntity(); - if (COLLIDER_EID == rp3dColliderEID) - return i; - } - - return -1; - } - - -} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHPhysicsRaycaster.h b/SHADE_Engine/src/Physics/Collision/SHPhysicsRaycaster.h deleted file mode 100644 index 447207c7..00000000 --- a/SHADE_Engine/src/Physics/Collision/SHPhysicsRaycaster.h +++ /dev/null @@ -1,134 +0,0 @@ -/**************************************************************************************** - * \file SHPhysicsRaycaster.h - * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Interface for a Physics Raycaster. - * - * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or - * disclosure of this file or its contents without the prior written consent - * of DigiPen Institute of Technology is prohibited. -****************************************************************************************/ - -#pragma once - -#include - -#include - -// Project Headers -#include "Math/SHRay.h" -#include "Physics/PhysicsObject/SHPhysicsObjectManager.h" -#include "Physics/SHPhysicsWorld.h" -#include "SH_API.h" -#include "SHCollisionTags.h" -#include "SHPhysicsRaycastResult.h" - -namespace SHADE -{ - /*-----------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*-----------------------------------------------------------------------------------*/ - - class SH_API SHPhysicsRaycaster : public reactphysics3d::RaycastCallback - { - private: - /*---------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*---------------------------------------------------------------------------------*/ - - using RaycastPair = std::pair; - using RaycastPairs = std::vector; - - public: - /*---------------------------------------------------------------------------------*/ - /* Constructors & Destructor */ - /*---------------------------------------------------------------------------------*/ - - SHPhysicsRaycaster() noexcept; - - /*---------------------------------------------------------------------------------*/ - /* Getter Functions */ - /*---------------------------------------------------------------------------------*/ - - [[nodiscard]] const RaycastPairs& GetRaycasts() const noexcept; - - /*---------------------------------------------------------------------------------*/ - /* Setter Functions */ - /*---------------------------------------------------------------------------------*/ - - void SetObjectManager(SHPhysicsObjectManager* physicsObjectManager) noexcept; - - /*---------------------------------------------------------------------------------*/ - /* Function Members */ - /*---------------------------------------------------------------------------------*/ - - void BindToWorld (rp3d::PhysicsWorld* physicsWorld) noexcept; - void ClearFrame () noexcept; - - // TODO(Diren): Filtering, return all shades ray hits - - SHPhysicsRaycastResult Raycast - ( - const SHRay& ray - , float distance = std::numeric_limits::infinity() - , const SHCollisionTag& collisionTag = SHCollisionTag{} - ) noexcept; - - SHPhysicsRaycastResult Linecast - ( - const SHVec3& start - , const SHVec3& end - , const SHCollisionTag& collisionTag = SHCollisionTag{} - ) noexcept; - - SHPhysicsRaycastResult ColliderRaycast - ( - EntityID eid - , const SHRay& ray - , float distance = std::numeric_limits::infinity() - ) noexcept; - - SHPhysicsRaycastResult ColliderRaycast - ( - EntityID eid - , int shapeIndex - , const SHRay& ray - , float distance = std::numeric_limits::infinity() - ) noexcept; - - SHPhysicsRaycastResult ColliderLinecast - ( - EntityID eid - , const SHVec3& start - , const SHVec3& end - ) noexcept; - - SHPhysicsRaycastResult ColliderLinecast - ( - EntityID eid - , int shapeIndex - , const SHVec3& start - , const SHVec3& end - ) noexcept; - - rp3d::decimal notifyRaycastHit(const rp3d::RaycastInfo& raycastInfo) override; - - private: - /*---------------------------------------------------------------------------------*/ - /* Data Members */ - /*---------------------------------------------------------------------------------*/ - - rp3d::PhysicsWorld* world; - SHPhysicsObjectManager* objectManager; // For - SHPhysicsRaycastResult temp; // Holds the temporary result after casting into the world - RaycastPairs raycasts; // Used for debug drawing - - /*---------------------------------------------------------------------------------*/ - /* Function Members */ - /*---------------------------------------------------------------------------------*/ - - SHPhysicsObject* validateColliderRaycast (EntityID eid) noexcept; - static int findColliderIndex (const rp3d::CollisionBody* rp3dBody, rp3d::Entity rp3dColliderEID) noexcept; - }; - - -} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/SHColliderComponent.cpp b/SHADE_Engine/src/Physics/Interface/SHColliderComponent.cpp index d7db2c64..eef92493 100644 --- a/SHADE_Engine/src/Physics/Interface/SHColliderComponent.cpp +++ b/SHADE_Engine/src/Physics/Interface/SHColliderComponent.cpp @@ -25,34 +25,12 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ SHColliderComponent::SHColliderComponent() noexcept - : system { nullptr } {} /*-----------------------------------------------------------------------------------*/ /* Getter Function Definitions */ /*-----------------------------------------------------------------------------------*/ - - const SHVec3& SHColliderComponent::GetPosition() const noexcept - { - return position; - } - - const SHQuaternion& SHColliderComponent::GetOrientation() const noexcept - { - return orientation; - } - - SHVec3 SHColliderComponent::GetRotation() const noexcept - { - return orientation.ToEuler(); - } - - const SHVec3& SHColliderComponent::GetScale() const noexcept - { - return scale; - } - const SHColliderComponent::CollisionShapes& SHColliderComponent::GetCollisionShapes() const noexcept { return collisionShapes; @@ -72,22 +50,7 @@ namespace SHADE void SHColliderComponent::OnCreate() { - auto* physicsSystem = SHSystemManager::GetSystem(); - if (!physicsSystem) - { - SHLOG_ERROR("Physics System does not exist to link with Physics Components!") - return; - } - system = physicsSystem; - - // Sync with transform if one already exists - if (auto* transformComponent = SHComponentManager::GetComponent_s(GetEID()); transformComponent) - { - position = transformComponent->GetWorldPosition(); - orientation = transformComponent->GetWorldOrientation(); - scale = transformComponent->GetWorldScale(); - } } void SHColliderComponent::OnDestroy() @@ -97,112 +60,22 @@ namespace SHADE void SHColliderComponent::RecomputeCollisionShapes() noexcept { - for (auto& collisionShape : collisionShapes) - { - switch (collisionShape.GetType()) - { - case SHCollisionShape::Type::BOX: - { - auto* box = reinterpret_cast(collisionShape.shape); - const SHVec3& RELATIVE_EXTENTS = box->GetRelativeExtents(); - // Recompute world extents based on new scale and fixed relative extents - - const SHVec3 WORLD_EXTENTS = RELATIVE_EXTENTS * (scale * 0.5f); - box->SetWorldExtents(WORLD_EXTENTS); - - continue; - } - case SHCollisionShape::Type::SPHERE: - { - auto* sphere = reinterpret_cast(collisionShape.shape); - const float RELATIVE_RADIUS = sphere->GetRelativeRadius(); - - // Recompute world radius based on new scale and fixed radius - - const float MAX_SCALE = SHMath::Max({ scale.x, scale.y, scale.z }); - const float WORLD_RADIUS = RELATIVE_RADIUS * MAX_SCALE * 0.5f; - sphere->SetWorldRadius(WORLD_RADIUS); - - continue; - } - default: continue; - } - } } int SHColliderComponent::AddBoundingBox(const SHVec3& halfExtents, const SHVec3& posOffset, const SHVec3& rotOffset) noexcept { - if (!system) - { - SHLOG_ERROR("Physics system does not exist, unable to add Box Collider!") - return -1; - } - - static constexpr auto TYPE = SHCollisionShape::Type::BOX; - - auto& collider = collisionShapes.emplace_back(SHCollisionShape{ GetEID(), TYPE }); - - collider.entityID = GetEID(); - collider.SetPositionOffset(posOffset); - collider.SetRotationOffset(rotOffset); - collider.SetBoundingBox(halfExtents); - - // Notify Physics System - const int NEW_SHAPE_INDEX = static_cast(collisionShapes.size()) - 1; - - system->AddCollisionShape(GetEID(), NEW_SHAPE_INDEX); - return NEW_SHAPE_INDEX; + return 0; } int SHColliderComponent::AddBoundingSphere(float radius, const SHVec3& posOffset) noexcept { - if (!system) - { - SHLOG_ERROR("Physics system does not exist, unable to add Sphere Collider!") - return -1; - } - - static constexpr auto TYPE = SHCollisionShape::Type::SPHERE; - - auto& collider = collisionShapes.emplace_back(SHCollisionShape{ GetEID(), TYPE }); - - collider.entityID = GetEID(); - collider.SetPositionOffset(posOffset); - collider.SetBoundingSphere(radius); - - // Notify Physics System - const int NEW_SHAPE_INDEX = static_cast(collisionShapes.size()) - 1; - - system->AddCollisionShape(GetEID(), NEW_SHAPE_INDEX); - return NEW_SHAPE_INDEX; + return 0; } void SHColliderComponent::RemoveCollider(int index) { - if (index < 0 || static_cast(index) >= collisionShapes.size()) - throw std::invalid_argument("Out-of-range access!"); - int idx = 0; - auto it = collisionShapes.begin(); - for (; it != collisionShapes.end(); ++it) - { - if (idx == index) - break; - - ++idx; - } - - it = collisionShapes.erase(it); - - // Notify Physics System - if (!system) - { - SHLOG_ERROR("Physics system does not exist, unable to remove Collider!") - return; - } - - system->RemoveCollisionShape(GetEID(), index); } } // namespace SHADE diff --git a/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h b/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h index 0781f3cf..15a02173 100644 --- a/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h +++ b/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h @@ -69,11 +69,6 @@ namespace SHADE [[nodiscard]] bool HasChanged () const noexcept; - [[nodiscard]] const SHVec3& GetPosition () const noexcept; - [[nodiscard]] const SHQuaternion& GetOrientation () const noexcept; - [[nodiscard]] SHVec3 GetRotation () const noexcept; - [[nodiscard]] const SHVec3& GetScale () const noexcept; - [[nodiscard]] const CollisionShapes& GetCollisionShapes() const noexcept; [[nodiscard]] SHCollisionShape& GetCollisionShape (int index); @@ -97,10 +92,6 @@ namespace SHADE /* Data Members */ /*---------------------------------------------------------------------------------*/ - SHPhysicsSystem* system; - - SHVec3 position; - SHQuaternion orientation; SHVec3 scale; CollisionShapes collisionShapes; diff --git a/SHADE_Engine/src/Physics/Interface/SHCollisionShape.cpp b/SHADE_Engine/src/Physics/Interface/SHCollisionShape.cpp index f597077f..4dd506f9 100644 --- a/SHADE_Engine/src/Physics/Interface/SHCollisionShape.cpp +++ b/SHADE_Engine/src/Physics/Interface/SHCollisionShape.cpp @@ -190,63 +190,63 @@ namespace SHADE void SHCollisionShape::SetBoundingBox(const SHVec3& halfExtents) { - dirty = true; + //dirty = true; - const auto* COLLIDER = SHComponentManager::GetComponent(entityID); - auto* box = reinterpret_cast(shape); + //const auto* COLLIDER = SHComponentManager::GetComponent(entityID); + //auto* box = reinterpret_cast(shape); - SHVec3 correctedHalfExtents = halfExtents; + //SHVec3 correctedHalfExtents = halfExtents; - // Get current relative halfExtents for error checking. 0 is ignored - const SHVec3& CURRENT_RELATIVE_EXTENTS = box->GetRelativeExtents(); - for (size_t i = 0; i < SHVec3::SIZE; ++i) - { - if (SHMath::CompareFloat(halfExtents[i], 0.0f)) - correctedHalfExtents[i] = CURRENT_RELATIVE_EXTENTS[i]; - } - - // Set the half extents relative to world scale - const SHVec3 WORLD_EXTENTS = correctedHalfExtents * COLLIDER->GetScale() * 0.5f; + //// Get current relative halfExtents for error checking. 0 is ignored + //const SHVec3& CURRENT_RELATIVE_EXTENTS = box->GetRelativeExtents(); + //for (size_t i = 0; i < SHVec3::SIZE; ++i) + //{ + // if (SHMath::CompareFloat(halfExtents[i], 0.0f)) + // correctedHalfExtents[i] = CURRENT_RELATIVE_EXTENTS[i]; + //} + // + //// Set the half extents relative to world scale + //const SHVec3 WORLD_EXTENTS = correctedHalfExtents * COLLIDER->GetScale() * 0.5f; - if (type != Type::BOX) - { - type = Type::BOX; + //if (type != Type::BOX) + //{ + // type = Type::BOX; - delete shape; - shape = new SHBox{ positionOffset, WORLD_EXTENTS }; - } + // delete shape; + // shape = new SHBox{ positionOffset, WORLD_EXTENTS }; + //} - box->SetWorldExtents(WORLD_EXTENTS); - box->SetRelativeExtents(correctedHalfExtents); + //box->SetWorldExtents(WORLD_EXTENTS); + //box->SetRelativeExtents(correctedHalfExtents); } void SHCollisionShape::SetBoundingSphere(float radius) { - dirty = true; + //dirty = true; - auto* sphere = reinterpret_cast(shape); - const auto* COLLIDER = SHComponentManager::GetComponent(entityID); + //auto* sphere = reinterpret_cast(shape); + //const auto* COLLIDER = SHComponentManager::GetComponent(entityID); - // Get current relative halfExtents for error checking. 0 is ignored - const float CURRENT_RELATIVE_RADIUS = sphere->GetRelativeRadius(); - if (SHMath::CompareFloat(radius, 0.0f)) - radius = CURRENT_RELATIVE_RADIUS; + //// Get current relative halfExtents for error checking. 0 is ignored + //const float CURRENT_RELATIVE_RADIUS = sphere->GetRelativeRadius(); + //if (SHMath::CompareFloat(radius, 0.0f)) + // radius = CURRENT_RELATIVE_RADIUS; - // Set the radius relative to world scale - const SHVec3 WORLD_SCALE = COLLIDER->GetScale(); - const float MAX_SCALE = SHMath::Max({ WORLD_SCALE.x, WORLD_SCALE.y, WORLD_SCALE.z }); - const float WORLD_RADIUS = radius * MAX_SCALE * 0.5f; + //// Set the radius relative to world scale + //const SHVec3 WORLD_SCALE = COLLIDER->GetScale(); + //const float MAX_SCALE = SHMath::Max({ WORLD_SCALE.x, WORLD_SCALE.y, WORLD_SCALE.z }); + //const float WORLD_RADIUS = radius * MAX_SCALE * 0.5f; - if (type != Type::SPHERE) - { - type = Type::SPHERE; + //if (type != Type::SPHERE) + //{ + // type = Type::SPHERE; - delete shape; - shape = new SHSphere{ positionOffset, WORLD_RADIUS }; - } + // delete shape; + // shape = new SHSphere{ positionOffset, WORLD_RADIUS }; + //} - sphere->SetWorldRadius(WORLD_RADIUS); - sphere->SetRelativeRadius(radius); + //sphere->SetWorldRadius(WORLD_RADIUS); + //sphere->SetRelativeRadius(radius); } void SHCollisionShape::SetIsTrigger(bool trigger) noexcept diff --git a/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp index 330c3abe..6dad9020 100644 --- a/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp +++ b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp @@ -28,18 +28,8 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ SHRigidBodyComponent::SHRigidBodyComponent() noexcept - : type { Type::DYNAMIC } - , flags { 0 } - , dirtyFlags { std::numeric_limits::max() } - , mass { 1.0f } - , drag { 0.01f } - , angularDrag { 0.01f } - , system { nullptr } { - // Initialise default flags - flags |= 1U << 0; // Gravity set to true - flags |= 1U << 1; // Sleeping allowed - flags |= 1U << 8; // Interpolate by default + } /*-----------------------------------------------------------------------------------*/ @@ -48,363 +38,166 @@ namespace SHADE bool SHRigidBodyComponent::IsGravityEnabled() const noexcept { - static constexpr int FLAG_POS = 0; - return flags & (1U << FLAG_POS); + return false; } bool SHRigidBodyComponent::IsAllowedToSleep() const noexcept { - static constexpr int FLAG_POS = 1; - return flags & (1U << FLAG_POS); + return false; } bool SHRigidBodyComponent::IsInterpolating() const noexcept { - static constexpr int FLAG_POS = 8; - return flags & (1U << FLAG_POS); + return false; } bool SHRigidBodyComponent::GetIsSleeping() const noexcept { - if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) - return physicsObject->GetRigidBody()->isSleeping(); - return false; } SHRigidBodyComponent::Type SHRigidBodyComponent::GetType() const noexcept { - return type; + return Type::STATIC; } bool SHRigidBodyComponent::GetFreezePositionX() const noexcept { - static constexpr int FLAG_POS = 2; - return flags & (1U << FLAG_POS); + return false; } bool SHRigidBodyComponent::GetFreezePositionY() const noexcept { - static constexpr int FLAG_POS = 3; - return flags & (1U << FLAG_POS); + return false; } bool SHRigidBodyComponent::GetFreezePositionZ() const noexcept { - static constexpr int FLAG_POS = 4; - return flags & (1U << FLAG_POS); + return false; } bool SHRigidBodyComponent::GetFreezeRotationX() const noexcept { - static constexpr int FLAG_POS = 5; - return flags & (1U << FLAG_POS); + return false; } bool SHRigidBodyComponent::GetFreezeRotationY() const noexcept { - static constexpr int FLAG_POS = 6; - return flags & (1U << FLAG_POS); + return false; } bool SHRigidBodyComponent::GetFreezeRotationZ() const noexcept { - static constexpr int FLAG_POS = 7; - return flags & (1U << FLAG_POS); + return false; } - //bool SHRigidBodyComponent::GetAutoMass() const noexcept - //{ - // static constexpr int FLAG_POS = 9; - // return flags & (1U << FLAG_POS); - //} - float SHRigidBodyComponent::GetMass() const noexcept { - return mass; + return 0.0f; } float SHRigidBodyComponent::GetDrag() const noexcept { - return drag; + return 0.0f; } float SHRigidBodyComponent::GetAngularDrag() const noexcept { - return angularDrag; + return 0.0f; } SHVec3 SHRigidBodyComponent::GetForce() const noexcept { - if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) - return physicsObject->GetRigidBody()->getForce(); - return SHVec3::Zero; } SHVec3 SHRigidBodyComponent::GetTorque() const noexcept { - if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) - return physicsObject->GetRigidBody()->getTorque(); - return SHVec3::Zero; } SHVec3 SHRigidBodyComponent::GetLinearVelocity() const noexcept { - if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) - return physicsObject->GetRigidBody()->getLinearVelocity(); - return SHVec3::Zero; } SHVec3 SHRigidBodyComponent::GetAngularVelocity() const noexcept { - if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) - return physicsObject->GetRigidBody()->getAngularVelocity(); - return SHVec3::Zero; } - const SHVec3& SHRigidBodyComponent::GetPosition() const noexcept - { - return position; - } - - const SHQuaternion& SHRigidBodyComponent::GetOrientation() const noexcept - { - return orientation; - } - - SHVec3 SHRigidBodyComponent::GetRotation() const noexcept - { - return orientation.ToEuler(); - } - /*-----------------------------------------------------------------------------------*/ /* Setter Function Definitions */ /*-----------------------------------------------------------------------------------*/ void SHRigidBodyComponent::SetType(Type newType) noexcept { - static constexpr int FLAG_POS = 8; - - if (type == newType) - return; - - type = newType; - dirtyFlags |= 1U << FLAG_POS; + } void SHRigidBodyComponent::SetGravityEnabled(bool enableGravity) noexcept { - static constexpr int FLAG_POS = 0; - - if (type != Type::DYNAMIC) - { - SHLOG_WARNING("Cannot enable gravity of a non-dynamic object {}", GetEID()) - return; - } - - dirtyFlags |= 1U << FLAG_POS; - enableGravity ? flags |= 1U << FLAG_POS : flags &= ~(1U << FLAG_POS); + } void SHRigidBodyComponent::SetIsAllowedToSleep(bool isAllowedToSleep) noexcept { - static constexpr int FLAG_POS = 1; - - if (type != Type::DYNAMIC) - { - SHLOG_WARNING("Cannot enable sleeping of a non-dynamic object {}", GetEID()) - return; - } - - dirtyFlags |= 1U << FLAG_POS; - isAllowedToSleep ? flags |= 1U << FLAG_POS : flags &= ~(1U << FLAG_POS); + } void SHRigidBodyComponent::SetFreezePositionX(bool freezePositionX) noexcept { - static constexpr int FLAG_POS = 2; - - if (type == Type::STATIC) - { - SHLOG_WARNING("Cannot set linear constraints of a static object {}", GetEID()) - return; - } - - dirtyFlags |= 1U << FLAG_POS; - freezePositionX ? flags |= 1U << FLAG_POS : flags &= ~(1U << FLAG_POS); + } void SHRigidBodyComponent::SetFreezePositionY(bool freezePositionY) noexcept { - static constexpr int FLAG_POS = 3; - - if (type == Type::STATIC) - { - SHLOG_WARNING("Cannot set linear constraints of a static object {}", GetEID()) - return; - } - - dirtyFlags |= 1U << FLAG_POS; - freezePositionY ? flags |= 1U << FLAG_POS : flags &= ~(1U << FLAG_POS); + } void SHRigidBodyComponent::SetFreezePositionZ(bool freezePositionZ) noexcept { - static constexpr int FLAG_POS = 4; - - if (type == Type::STATIC) - { - SHLOG_WARNING("Cannot set linear constraints of a static object {}", GetEID()) - return; - } - - dirtyFlags |= 1U << FLAG_POS; - freezePositionZ ? flags |= 1U << FLAG_POS : flags &= ~(1U << FLAG_POS); + } void SHRigidBodyComponent::SetFreezeRotationX(bool freezeRotationX) noexcept { - static constexpr int FLAG_POS = 5; - - if (type == Type::STATIC) - { - SHLOG_WARNING("Cannot set angular constraints of a static object {}", GetEID()) - return; - } - - dirtyFlags |= 1U << FLAG_POS; - freezeRotationX ? flags |= 1U << FLAG_POS : flags &= ~(1U << FLAG_POS); + } void SHRigidBodyComponent::SetFreezeRotationY(bool freezeRotationY) noexcept { - static constexpr int FLAG_POS = 6; - - if (type == Type::STATIC) - { - SHLOG_WARNING("Cannot set angular constraints of a static object {}", GetEID()) - return; - } - - dirtyFlags |= 1U << FLAG_POS; - freezeRotationY ? flags |= 1U << FLAG_POS : flags &= ~(1U << FLAG_POS); + } void SHRigidBodyComponent::SetFreezeRotationZ(bool freezeRotationZ) noexcept { - static constexpr int FLAG_POS = 7; - - if (type == Type::STATIC) - { - SHLOG_WARNING("Cannot set angular constraints of a static object {}", GetEID()) - return; - } - - dirtyFlags |= 1U << FLAG_POS; - freezeRotationZ ? flags |= 1U << FLAG_POS : flags &= ~(1U << FLAG_POS); + } void SHRigidBodyComponent::SetInterpolate(bool allowInterpolation) noexcept { - static constexpr int FLAG_POS = 8; - allowInterpolation ? flags |= 1U << FLAG_POS : flags &= ~(1U << FLAG_POS); + } - //void SHRigidBodyComponent::SetAutoMass(bool autoMass) noexcept - //{ - // static constexpr int FLAG_POS = 9; - // autoMass ? flags |= 1U << FLAG_POS : flags &= ~(1U << FLAG_POS); - - // dirtyFlags |= 1U << FLAG_POS; - //} - - //void SHRigidBodyComponent::SetMass(float newMass) noexcept - //{ - // static constexpr int FLAG_POS = 9; - - // if (newMass < 0.0f) - // return; - - // if (type != Type::DYNAMIC) - // { - // SHLOG_WARNING("Cannot set mass of a non-dynamic object {}", GetEID()) - // return; - // } - - // dirtyFlags |= 1U << FLAG_POS; - // mass = newMass; - - // // Turn off automass - // flags &= ~(1U << FLAG_POS); - //} - void SHRigidBodyComponent::SetDrag(float newDrag) noexcept { - static constexpr int FLAG_POS = 10; - - if (type != Type::DYNAMIC) - { - SHLOG_WARNING("Cannot set drag of a non-dynamic object {}", GetEID()) - return; - } - - dirtyFlags |= 1U << FLAG_POS; - drag = newDrag; + } void SHRigidBodyComponent::SetAngularDrag(float newAngularDrag) noexcept { - static constexpr int FLAG_POS = 11; - - if (type != Type::DYNAMIC) - { - SHLOG_WARNING("Cannot set angular drag of a non-dynamic object {}", GetEID()) - return; - } - - dirtyFlags |= 1U << FLAG_POS; - angularDrag = newAngularDrag; + } void SHRigidBodyComponent::SetLinearVelocity(const SHVec3& newLinearVelocity) noexcept { - - if (type == Type::STATIC) - { - SHLOG_WARNING("Cannot set linear velocity of a static object {}", GetEID()) - return; - } - - auto* physicsObject = system->GetPhysicsObject(GetEID()); - if (!physicsObject) - { - SHLOGV_ERROR("Unable to retrieve physics object of Entity {}", GetEID()) - return; - } - physicsObject->GetRigidBody()->setLinearVelocity(newLinearVelocity); + } void SHRigidBodyComponent::SetAngularVelocity(const SHVec3& newAngularVelocity) noexcept { - static constexpr int FLAG_POS = 13; - - if (type == Type::STATIC) - { - SHLOG_WARNING("Cannot set angular velocity of a static object {}", GetEID()) - return; - } - - auto* physicsObject = system->GetPhysicsObject(GetEID()); - if (!physicsObject) - { - SHLOGV_ERROR("Unable to retrieve physics object of Entity {}", GetEID()) - return; - } - physicsObject->GetRigidBody()->setAngularVelocity(newAngularVelocity); + } /*-----------------------------------------------------------------------------------*/ @@ -413,81 +206,57 @@ namespace SHADE void SHRigidBodyComponent::OnCreate() { - auto* physicsSystem = SHSystemManager::GetSystem(); - if (!physicsSystem) - { - SHLOG_ERROR("Physics System does not exist to link with Physics Components!") - return; - } - - system = physicsSystem; - - // Sync with transform if one already exists - if (auto* transformComponent = SHComponentManager::GetComponent_s(GetEID()); transformComponent) - { - position = transformComponent->GetWorldPosition(); - orientation = transformComponent->GetWorldOrientation(); - } + } void SHRigidBodyComponent::AddForce(const SHVec3& force) const noexcept { - if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) - physicsObject->GetRigidBody()->applyWorldForceAtCenterOfMass(force); + } void SHRigidBodyComponent::AddForceAtLocalPos(const SHVec3& force, const SHVec3& localPos) const noexcept { - if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) - physicsObject->GetRigidBody()->applyWorldForceAtLocalPosition(force, localPos); + } void SHRigidBodyComponent::AddForceAtWorldPos(const SHVec3& force, const SHVec3& worldPos) const noexcept { - if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) - physicsObject->GetRigidBody()->applyWorldForceAtWorldPosition(force, worldPos); + } void SHRigidBodyComponent::AddRelativeForce(const SHVec3& relativeForce) const noexcept { - if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) - physicsObject->GetRigidBody()->applyLocalForceAtCenterOfMass(relativeForce); + } void SHRigidBodyComponent::AddRelativeForceAtLocalPos(const SHVec3& relativeForce, const SHVec3& localPos) const noexcept { - if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) - physicsObject->GetRigidBody()->applyLocalForceAtLocalPosition(relativeForce, localPos); + } void SHRigidBodyComponent::AddRelativeForceAtWorldPos(const SHVec3& relativeForce, const SHVec3& worldPos) const noexcept { - if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) - physicsObject->GetRigidBody()->applyLocalForceAtWorldPosition(relativeForce, worldPos); + } void SHRigidBodyComponent::AddTorque(const SHVec3& torque) const noexcept { - if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) - physicsObject->GetRigidBody()->applyWorldTorque(torque); + } void SHRigidBodyComponent::AddRelativeTorque(const SHVec3& relativeTorque) const noexcept { - if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) - physicsObject->GetRigidBody()->applyLocalTorque(relativeTorque); + } void SHRigidBodyComponent::ClearForces() const noexcept { - if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) - physicsObject->GetRigidBody()->resetForce(); + } void SHRigidBodyComponent::ClearTorque() const noexcept { - if (auto* physicsObject = system->GetPhysicsObject(GetEID()); physicsObject) - physicsObject->GetRigidBody()->resetTorque(); + } } // namespace SHADE @@ -512,7 +281,6 @@ RTTR_REGISTRATION .property("Use Gravity" , &SHRigidBodyComponent::IsGravityEnabled , &SHRigidBodyComponent::SetGravityEnabled ) .property("Interpolate" , &SHRigidBodyComponent::IsInterpolating , &SHRigidBodyComponent::SetInterpolate ) .property("Sleeping Enabled" , &SHRigidBodyComponent::IsAllowedToSleep , &SHRigidBodyComponent::SetIsAllowedToSleep) - //.property("Auto Mass" , &SHRigidBodyComponent::GetAutoMass , &SHRigidBodyComponent::SetAutoMass ) .property("Freeze Position X" , &SHRigidBodyComponent::GetFreezePositionX , &SHRigidBodyComponent::SetFreezePositionX ) .property("Freeze Position Y" , &SHRigidBodyComponent::GetFreezePositionY , &SHRigidBodyComponent::SetFreezePositionY ) .property("Freeze Position Z" , &SHRigidBodyComponent::GetFreezePositionZ , &SHRigidBodyComponent::SetFreezePositionZ ) diff --git a/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h index 532b3312..021743da 100644 --- a/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h +++ b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h @@ -82,8 +82,6 @@ namespace SHADE [[nodiscard]] bool GetFreezeRotationY () const noexcept; [[nodiscard]] bool GetFreezeRotationZ () const noexcept; - //[[nodiscard]] bool GetAutoMass () const noexcept; - [[nodiscard]] float GetMass () const noexcept; [[nodiscard]] float GetDrag () const noexcept; [[nodiscard]] float GetAngularDrag () const noexcept; @@ -93,9 +91,9 @@ namespace SHADE [[nodiscard]] SHVec3 GetLinearVelocity () const noexcept; [[nodiscard]] SHVec3 GetAngularVelocity () const noexcept; - [[nodiscard]] const SHVec3& GetPosition () const noexcept; - [[nodiscard]] const SHQuaternion& GetOrientation () const noexcept; - [[nodiscard]] SHVec3 GetRotation () const noexcept; + //[[nodiscard]] const SHVec3& GetPosition () const noexcept; + //[[nodiscard]] const SHQuaternion& GetOrientation () const noexcept; + //[[nodiscard]] SHVec3 GetRotation () const noexcept; /*---------------------------------------------------------------------------------*/ /* Setter Functions */ @@ -144,26 +142,6 @@ namespace SHADE /* Data Members */ /*---------------------------------------------------------------------------------*/ - static constexpr size_t NUM_FLAGS = 8; - static constexpr size_t NUM_DIRTY_FLAGS = 12; - - Type type; - - uint16_t flags; // 0 0 0 0 0 0 am ip aZ aY aX lZ lY lX slp g - uint16_t dirtyFlags; // 0 0 0 0 aD d m t aZ aY aX lZ lY lX slp g - - float mass; - float drag; - float angularDrag; - - SHVec3 linearVelocity; - SHVec3 angularVelocity; - - SHPhysicsSystem* system; - - SHVec3 position; - SHQuaternion orientation; - RTTR_ENABLE() }; } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObject.cpp b/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObject.cpp deleted file mode 100644 index 71b08831..00000000 --- a/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObject.cpp +++ /dev/null @@ -1,431 +0,0 @@ -/**************************************************************************************** - * \file SHPhysicsObject.cpp - * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Implementation for a Physics Object. - * - * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or - * disclosure of this file or its contents without the prior written consent - * of DigiPen Institute of Technology is prohibited. -****************************************************************************************/ - -#include - -// Primary Header -#include "SHPhysicsObject.h" - -// Project Headers -#include "ECS_Base/Managers/SHSystemManager.h" -#include "ECS_Base/Managers/SHComponentManager.h" -#include "Scene/SHSceneManager.h" - - -namespace SHADE -{ - /*-----------------------------------------------------------------------------------*/ - /* Constructors & Destructor Definitions */ - /*-----------------------------------------------------------------------------------*/ - - SHPhysicsObject::SHPhysicsObject(EntityID eid, rp3d::PhysicsCommon* physicsFactory, rp3d::PhysicsWorld* physicsWorld) noexcept - : entityID { eid } - , collidersActive { true } - , factory { physicsFactory } - , world { physicsWorld } - , rp3dBody { nullptr } - { - // Implicitly create a static body. - - const auto* TRANSFORM = SHComponentManager::GetComponent(eid); - - const rp3d::Transform RP3D_TRANSFORM { TRANSFORM->GetWorldPosition(), TRANSFORM->GetWorldOrientation() }; - - rp3dBody = world->createRigidBody(RP3D_TRANSFORM); - rp3dBody->setType(rp3d::BodyType::STATIC); - } - - SHPhysicsObject::~SHPhysicsObject() noexcept - { - factory = nullptr; - world = nullptr; - rp3dBody = nullptr; - } - - /*-----------------------------------------------------------------------------------*/ - /* Getter Function Definitions */ - /*-----------------------------------------------------------------------------------*/ - - SHVec3 SHPhysicsObject::GetPosition() const noexcept - { - return rp3dBody->getTransform().getPosition(); - } - - SHQuaternion SHPhysicsObject::GetOrientation() const noexcept - { - return rp3dBody->getTransform().getOrientation(); - } - - SHVec3 SHPhysicsObject::GetRotation() const noexcept - { - return SHQuaternion{ rp3dBody->getTransform().getOrientation() }.ToEuler(); - } - - rp3d::CollisionBody* SHPhysicsObject::GetCollisionBody() const noexcept - { - return rp3dBody; - } - - rp3d::RigidBody* SHPhysicsObject::GetRigidBody() const noexcept - { - return rp3dBody; - } - - /*-----------------------------------------------------------------------------------*/ - /* Setter Function Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHPhysicsObject::SetStaticBody() const noexcept - { - if (!rp3dBody) - return; - - rp3dBody->setType(rp3d::BodyType::STATIC); - } - - /*-----------------------------------------------------------------------------------*/ - /* Public Function Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - int SHPhysicsObject::AddCollisionShape(int index) - { - // Get collider component - auto* colliderComponent = SHComponentManager::GetComponent_s(entityID); - if (!colliderComponent) - { - SHLOGV_ERROR("Unable to add Collision Shape to Entity {} due to Missing Collider Component!", entityID) - return -1; - } - - auto& collisionShape = colliderComponent->GetCollisionShape(index); - switch (collisionShape.GetType()) - { - // TODO(Diren): Add more collider shapes - - case SHCollisionShape::Type::BOX: - { - addBoxShape(collisionShape); - break; - } - - case SHCollisionShape::Type::SPHERE: - { - addSphereShape(collisionShape); - break; - } - default: break; - } - - auto* rp3dCollider = rp3dBody->getCollider(rp3dBody->getNbColliders() - 1); - syncColliderProperties(collisionShape, rp3dCollider); - - if (rp3dBody->getType() == rp3d::BodyType::DYNAMIC) - { - rp3dBody->updateMassPropertiesFromColliders(); - - if (auto* rigidBodyComponent = SHComponentManager::GetComponent_s(entityID); rigidBodyComponent) - rigidBodyComponent->mass = rp3dBody->getMass(); - } - - return index; - } - - void SHPhysicsObject::RemoveCollisionShape(int index) - { - const int NUM_COLLIDERS = static_cast(rp3dBody->getNbColliders()); - if (NUM_COLLIDERS == 0) - return; - - if (index < 0 || index >= NUM_COLLIDERS) - throw std::invalid_argument("Index out of range!"); - - auto* collider = rp3dBody->getCollider(index); - rp3dBody->removeCollider(collider); - - if (rp3dBody->getType() == rp3d::BodyType::DYNAMIC) - { - rp3dBody->updateMassPropertiesFromColliders(); - - auto* rigidBodyComponent = SHComponentManager::GetComponent_s(entityID); - if (rigidBodyComponent) - rigidBodyComponent->mass = rp3dBody->getMass(); - } - } - - void SHPhysicsObject::RemoveAllCollisionShapes() const noexcept - { - int numColliders = static_cast(rp3dBody->getNbColliders()); - if (numColliders == 0) - return; - - while (numColliders - 1 >= 0) - { - auto* collider = rp3dBody->getCollider(numColliders - 1); - rp3dBody->removeCollider(collider); - - --numColliders; - } - } - - void SHPhysicsObject::SyncRigidBody(SHRigidBodyComponent& component) const noexcept - { - // This state is synced in the pre-update routine - if (!rp3dBody->isActive()) - return; - - if (component.dirtyFlags == 0) - return; - - for (size_t i = 0; i < SHRigidBodyComponent::NUM_DIRTY_FLAGS; ++i) - { - if (const bool IS_DIRTY = component.dirtyFlags & (1U << i); IS_DIRTY) - { - switch (i) - { - case 0: // Gravity - { - const bool IS_ENABLED = component.flags & (1U << i); - rp3dBody->enableGravity(IS_ENABLED); - - break; - } - case 1: // Sleeping - { - const bool IS_ENABLED = component.flags & (1U << i); - rp3dBody->setIsAllowedToSleep(IS_ENABLED); - - break; - } - case 2: // Lock Position X - { - const bool IS_ENABLED = component.flags & (1U << i); - - auto positionLock = rp3dBody->getLinearLockAxisFactor(); - positionLock.x = IS_ENABLED ? 0.0f : 1.0f; - rp3dBody->setLinearLockAxisFactor(positionLock); - - break; - } - case 3: // Lock Position Y - { - const bool IS_ENABLED = component.flags & (1U << i); - - auto positionLock = rp3dBody->getLinearLockAxisFactor(); - positionLock.y = IS_ENABLED ? 0.0f : 1.0f; - rp3dBody->setLinearLockAxisFactor(positionLock); - - break; - } - case 4: // Lock Position Z - { - const bool IS_ENABLED = component.flags & (1U << i); - - auto positionLock = rp3dBody->getLinearLockAxisFactor(); - positionLock.z = IS_ENABLED ? 0.0f : 1.0f; - rp3dBody->setLinearLockAxisFactor(positionLock); - - break; - } - case 5: // Lock Rotation X - { - const bool IS_ENABLED = component.flags & (1U << i); - - auto rotationLock = rp3dBody->getAngularLockAxisFactor(); - rotationLock.x = IS_ENABLED ? 0.0f : 1.0f; - rp3dBody->setAngularLockAxisFactor(rotationLock); - - break; - } - case 6: // Lock Rotation Y - { - const bool IS_ENABLED = component.flags & (1U << i); - - auto rotationLock = rp3dBody->getAngularLockAxisFactor(); - rotationLock.y = IS_ENABLED ? 0.0f : 1.0f; - rp3dBody->setAngularLockAxisFactor(rotationLock); - - break; - } - case 7: // Lock Rotation Z - { - const bool IS_ENABLED = component.flags & (1U << i); - - auto rotationLock = rp3dBody->getAngularLockAxisFactor(); - rotationLock.z = IS_ENABLED ? 0.0f : 1.0f; - rp3dBody->setAngularLockAxisFactor(rotationLock); - - break; - } - case 8: // Type - { - rp3dBody->setType(static_cast(component.type)); - - break; - } - case 9: // Mass - { - //rp3dBody->setMass(component.mass); - - //if (component.GetAutoMass()) - //{ - // rp3dBody->updateMassPropertiesFromColliders(); - // component.mass = rp3dBody->getMass(); - //} - //else - //{ - // rp3dBody->setMass(component.mass); - // rp3dBody->updateLocalCenterOfMassFromColliders(); - // rp3dBody->updateLocalInertiaTensorFromColliders(); - //} - - break; - } - case 10: // Drag - { - rp3dBody->setLinearDamping(component.drag); - - break; - } - case 11: // Angular Drag - { - rp3dBody->setAngularDamping(component.angularDrag); - - break; - } - default: break; - } - } - } - - component.dirtyFlags = 0; - } - - void SHPhysicsObject::SyncColliders(SHColliderComponent& component) const noexcept - { - // This state is synced in the pre-update routine - if (!rp3dBody->isActive()) - return; - - const int NUM_SHAPES = static_cast(rp3dBody->getNbColliders()); - for (int i = 0; i < NUM_SHAPES; ++i) - { - auto& collisionShape = component.collisionShapes[i]; - - if (!collisionShape.dirty) - continue; - - switch (collisionShape.GetType()) - { - case SHCollisionShape::Type::BOX: syncBoxShape(i, collisionShape); break; - case SHCollisionShape::Type::SPHERE: syncSphereShape(i, collisionShape); break; - default: break; - } - - auto* rp3dCollider = rp3dBody->getCollider(i); - syncColliderProperties(collisionShape, rp3dCollider); - - collisionShape.dirty = false; - } - - // Set rigidbody mass if dynamic - auto* rigidBodyComponent = SHComponentManager::GetComponent_s(entityID); - if (rigidBodyComponent) - { - // This is generally expensive, will be optimised in the future with my own engine. - rp3dBody->updateMassPropertiesFromColliders(); - rigidBodyComponent->mass = rp3dBody->getMass(); - } - } - - /*-----------------------------------------------------------------------------------*/ - /* Private Function Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHPhysicsObject::syncColliderProperties(const SHCollisionShape& collisionShape, rp3d::Collider* rp3dCollider) const noexcept - { - rp3dCollider->setIsTrigger(collisionShape.IsTrigger()); - - auto& rp3dMaterial = rp3dCollider->getMaterial(); - - rp3dMaterial.setFrictionCoefficient(collisionShape.GetFriction()); - rp3dMaterial.setBounciness(collisionShape.GetBounciness()); - rp3dMaterial.setMassDensity(collisionShape.GetDensity()); - - const unsigned short MASK_BITS = collisionShape.GetCollisionTag(); - rp3dCollider->setCollisionCategoryBits(MASK_BITS); - rp3dCollider->setCollideWithMaskBits(MASK_BITS); - } - - void SHPhysicsObject::addBoxShape(const SHCollisionShape& boxShape) const noexcept - { - const rp3d::Transform OFFSETS - { - boxShape.GetPositionOffset() - , boxShape.GetRotationOffset() - }; - - const auto* BOX = reinterpret_cast(boxShape.GetShape()); - rp3d::BoxShape* newBox = factory->createBoxShape(BOX->GetWorldExtents()); - - rp3dBody->addCollider(newBox, OFFSETS); - } - - void SHPhysicsObject::syncBoxShape(int index, const SHCollisionShape& boxShape) const noexcept - { - const auto* BOX = reinterpret_cast(boxShape.GetShape()); - - auto* rp3dCollider = rp3dBody->getCollider(index); - auto* rp3dBox = reinterpret_cast(rp3dCollider->getCollisionShape()); - - const rp3d::Transform OFFSETS - { - boxShape.GetPositionOffset() - , boxShape.GetRotationOffset() - }; - - rp3dCollider->setIsTrigger(boxShape.IsTrigger()); - rp3dCollider->setLocalToBodyTransform(OFFSETS); - - rp3dBox->setHalfExtents(BOX->GetWorldExtents()); - } - - void SHPhysicsObject::addSphereShape(const SHCollisionShape& sphereShape) const noexcept - { - const rp3d::Transform OFFSETS - { - sphereShape.GetPositionOffset() - , sphereShape.GetRotationOffset() - }; - - const auto* SPHERE = reinterpret_cast(sphereShape.GetShape()); - rp3d::SphereShape* newSphere = factory->createSphereShape(SPHERE->GetWorldRadius()); - - rp3dBody->addCollider(newSphere, OFFSETS); - } - - void SHPhysicsObject::syncSphereShape(int index, const SHCollisionShape& sphereShape) const noexcept - { - const auto* SPHERE = reinterpret_cast(sphereShape.GetShape()); - - auto* rp3dCollider = rp3dBody->getCollider(index); - auto* rp3dSphere = reinterpret_cast(rp3dCollider->getCollisionShape()); - - const rp3d::Transform OFFSETS - { - sphereShape.GetPositionOffset() - , sphereShape.GetRotationOffset() - }; - - rp3dCollider->setIsTrigger(sphereShape.IsTrigger()); - rp3dCollider->setLocalToBodyTransform(OFFSETS); - - rp3dSphere->setRadius(SPHERE->GetWorldRadius()); - } -} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObject.h b/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObject.h deleted file mode 100644 index c572ca2e..00000000 --- a/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObject.h +++ /dev/null @@ -1,111 +0,0 @@ -/**************************************************************************************** - * \file SHPhysicsObject.h - * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Interface for a Physics Object. - * - * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or - * disclosure of this file or its contents without the prior written consent - * of DigiPen Institute of Technology is prohibited. -****************************************************************************************/ - -#pragma once - -#include - -// Project Headers -#include "Math/Transform/SHTransformComponent.h" -#include "Physics/Interface/SHRigidBodyComponent.h" -#include "Physics/Interface/SHColliderComponent.h" - -namespace SHADE -{ - /*-----------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*-----------------------------------------------------------------------------------*/ - - class SH_API SHPhysicsObject - { - private: - /*---------------------------------------------------------------------------------*/ - /* Friends */ - /*---------------------------------------------------------------------------------*/ - - friend class SHPhysicsSystem; - friend class SHPhysicsObjectManager; - - public: - /*---------------------------------------------------------------------------------*/ - /* Constructors & Destructor */ - /*---------------------------------------------------------------------------------*/ - - SHPhysicsObject (EntityID eid, rp3d::PhysicsCommon* physicsFactory, rp3d::PhysicsWorld* physicsWorld) noexcept; - SHPhysicsObject (const SHPhysicsObject& rhs) noexcept = default; - SHPhysicsObject (SHPhysicsObject&& rhs) noexcept = default; - virtual ~SHPhysicsObject () noexcept; - - /*---------------------------------------------------------------------------------*/ - /* Operator Overloads */ - /*---------------------------------------------------------------------------------*/ - - SHPhysicsObject& operator=(const SHPhysicsObject& rhs) noexcept = default; - SHPhysicsObject& operator=(SHPhysicsObject&& rhs) noexcept = default; - - /*---------------------------------------------------------------------------------*/ - /* Getter Functions */ - /*---------------------------------------------------------------------------------*/ - - [[nodiscard]] SHVec3 GetPosition () const noexcept; - [[nodiscard]] SHQuaternion GetOrientation () const noexcept; - [[nodiscard]] SHVec3 GetRotation () const noexcept; - - [[nodiscard]] rp3d::CollisionBody* GetCollisionBody () const noexcept; - [[nodiscard]] rp3d::RigidBody* GetRigidBody () const noexcept; - - /*---------------------------------------------------------------------------------*/ - /* Setter Functions */ - /*---------------------------------------------------------------------------------*/ - - void SetStaticBody () const noexcept; - - /*---------------------------------------------------------------------------------*/ - /* Function Members */ - /*---------------------------------------------------------------------------------*/ - - int AddCollisionShape (int index); - void RemoveCollisionShape (int index); - void RemoveAllCollisionShapes () const noexcept; - - void SyncRigidBody (SHRigidBodyComponent& component) const noexcept; - void SyncColliders (SHColliderComponent& component) const noexcept; - - private: - /*---------------------------------------------------------------------------------*/ - /* Data Members */ - /*---------------------------------------------------------------------------------*/ - - EntityID entityID; - bool collidersActive; // Only used to sync with SHADE components - - rp3d::PhysicsCommon* factory; - rp3d::PhysicsWorld* world; - - rp3d::RigidBody* rp3dBody; - rp3d::Transform prevTransform; // Cached transform for interpolation - - /*---------------------------------------------------------------------------------*/ - /* Function Members */ - /*---------------------------------------------------------------------------------*/ - - void syncColliderProperties (const SHCollisionShape& collisionShape, rp3d::Collider* rp3dCollider) const noexcept; - - // Box Shapes - - void addBoxShape (const SHCollisionShape& boxShape) const noexcept; - void syncBoxShape (int index, const SHCollisionShape& boxShape) const noexcept; - - // Sphere Shapes - - void addSphereShape (const SHCollisionShape& sphereShape) const noexcept; - void syncSphereShape (int index, const SHCollisionShape& sphereShape) const noexcept; - }; -} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObjectManager.cpp b/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObjectManager.cpp deleted file mode 100644 index 7c111a2d..00000000 --- a/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObjectManager.cpp +++ /dev/null @@ -1,309 +0,0 @@ -/**************************************************************************************** - * \file SHPhysicsObjectManager.cpp - * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Implementation for a Physics Object Manager. - * - * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or - * disclosure of this file or its contents without the prior written consent - * of DigiPen Institute of Technology is prohibited. -****************************************************************************************/ - -#include - -// Primary Header -#include "SHPhysicsObjectManager.h" - -// Project Headers -#include "ECS_Base/Managers/SHEntityManager.h" -#include "Tools/Utilities/SHUtilities.h" - -namespace SHADE -{ - /*-----------------------------------------------------------------------------------*/ - /* Static Data Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - SHPhysicsObjectManager::CommandFunctionPtr SHPhysicsObjectManager::componentFunc[3][2] - { - addRigidBody , addCollider , addCollisionShape - , removeRigidBody , removeCollider , removeCollisionShape - }; - - /*-----------------------------------------------------------------------------------*/ - /* Setter Function Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHPhysicsObjectManager::SetFactory(rp3d::PhysicsCommon& physicsFactory) noexcept - { - factory = &physicsFactory; - } - - void SHPhysicsObjectManager::SetWorld(rp3d::PhysicsWorld* physicsWorld) noexcept - { - world = physicsWorld; - } - - /*-----------------------------------------------------------------------------------*/ - /* Getter Function Definitions */ - /*-----------------------------------------------------------------------------------*/ - - SHPhysicsObject* SHPhysicsObjectManager::GetPhysicsObject(EntityID eid) noexcept - { - const auto it = physicsObjects.find(eid); - if (it == physicsObjects.end()) - return nullptr; - - return &it->second; - } - - const SHPhysicsObjectManager::PhysicsObjectEntityMap SHPhysicsObjectManager::GetPhysicsObjects() const noexcept - { - return physicsObjects; - } - - /*-----------------------------------------------------------------------------------*/ - /* Public Function Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHPhysicsObjectManager::AddRigidBody(EntityID eid) noexcept - { - const QueueCommand NEW_QUEUE_COMMAND - { - .eid = eid - , .command = QueueCommand::Command::ADD - , .component = PhysicsComponents::RIGID_BODY - }; - - commandQueue.push(NEW_QUEUE_COMMAND); - } - - void SHPhysicsObjectManager::AddCollider(EntityID eid) noexcept - { - const QueueCommand NEW_QUEUE_COMMAND - { - .eid = eid - , .command = QueueCommand::Command::ADD - , .component = PhysicsComponents::COLLIDER - }; - - commandQueue.push(NEW_QUEUE_COMMAND); - } - - void SHPhysicsObjectManager::AddCollisionShape(EntityID eid, int shapeIndex) noexcept - { - const QueueCommand NEW_QUEUE_COMMAND - { - .eid = eid - , .command = QueueCommand::Command::ADD - , .component = PhysicsComponents::COLLISION_SHAPE - , .shapeIndex = shapeIndex - }; - - commandQueue.push(NEW_QUEUE_COMMAND); - } - - void SHPhysicsObjectManager::RemoveRigidBody(EntityID eid) noexcept - { - const QueueCommand NEW_QUEUE_COMMAND - { - .eid = eid - , .command = QueueCommand::Command::REMOVE - , .component = PhysicsComponents::RIGID_BODY - }; - - commandQueue.push(NEW_QUEUE_COMMAND); - } - - void SHPhysicsObjectManager::RemoveCollider(EntityID eid) noexcept - { - const QueueCommand NEW_QUEUE_COMMAND - { - .eid = eid - , .command = QueueCommand::Command::REMOVE - , .component = PhysicsComponents::COLLIDER - }; - - commandQueue.push(NEW_QUEUE_COMMAND); - } - - void SHPhysicsObjectManager::RemoveCollisionShape(EntityID eid, int shapeIndex) noexcept - { - const QueueCommand NEW_QUEUE_COMMAND - { - .eid = eid - , .command = QueueCommand::Command::REMOVE - , .component = PhysicsComponents::COLLISION_SHAPE - , .shapeIndex = shapeIndex - }; - - commandQueue.push(NEW_QUEUE_COMMAND); - } - - void SHPhysicsObjectManager::UpdateCommands() - { - if (commandQueue.empty()) - return; - - while (!commandQueue.empty()) - { - const QueueCommand COMMAND = commandQueue.front(); - commandQueue.pop(); - - // Check validity of command - if (COMMAND.command == QueueCommand::Command::INVALID || COMMAND.component == PhysicsComponents::INVALID) - continue; - - // Get physics components - const PhysicsComponentGroup COMPONENT_GROUP - { - .eid = COMMAND.eid - , .rigidBodyComponent = SHComponentManager::GetComponent_s(COMMAND.eid) - , .colliderComponent = SHComponentManager::GetComponent_s(COMMAND.eid) - }; - - // Delete any object that is missing both components - // We infer that a remove command has been pushed for these, but we will ignore those if both components have already been removed. - if (!COMPONENT_GROUP.rigidBodyComponent && !COMPONENT_GROUP.colliderComponent) - { - destroyPhysicsObject(COMMAND.eid); - wakeAllObjects(); - continue; - } - - // Find the physics Object. If none found and attempting to add, create an object. - SHPhysicsObject* physicsObject = GetPhysicsObject(COMMAND.eid); - if (!physicsObject && COMMAND.command == QueueCommand::Command::ADD) - physicsObject = createPhysicsObject(COMMAND.eid); - - componentFunc[SHUtilities::ConvertEnum(COMMAND.command)][SHUtilities::ConvertEnum(COMMAND.component)](COMMAND, physicsObject, COMPONENT_GROUP); - - // If any removal was done, wake all objects - if (COMMAND.command == QueueCommand::Command::REMOVE) - wakeAllObjects(); - } - } - - void SHPhysicsObjectManager::RemoveAllObjects() - { - // Destroy all objects and clear - for (auto& physicsObject : physicsObjects | std::views::values) - { - world->destroyRigidBody(physicsObject.GetRigidBody()); - physicsObject.rp3dBody = nullptr; - } - - physicsObjects.clear(); - } - - /*-----------------------------------------------------------------------------------*/ - /* Private Function Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - SHPhysicsObject* SHPhysicsObjectManager::createPhysicsObject(EntityID eid) noexcept - { - // Force transforms to sync - SHVec3 worldPos = SHVec3::Zero; - SHQuaternion worldRot = SHQuaternion::Identity; - - const SHTransformComponent* TRANSFORM = nullptr; - if (SHEntityManager::IsValidEID(eid)) - TRANSFORM = SHComponentManager::GetComponent_s(eid); - - if (TRANSFORM) - { - worldPos = TRANSFORM->GetWorldPosition(); - worldRot = TRANSFORM->GetWorldOrientation(); - } - - const rp3d::Transform RP3D_TRANSFORM{ worldPos, worldRot }; - - auto& newPhysicsObject = physicsObjects.emplace(eid, SHPhysicsObject{ eid, factory, world }).first->second; - newPhysicsObject.GetRigidBody()->setTransform(RP3D_TRANSFORM); - newPhysicsObject.prevTransform = RP3D_TRANSFORM; - - return &newPhysicsObject; - } - - void SHPhysicsObjectManager::destroyPhysicsObject(EntityID eid) noexcept - { - const auto ITER = physicsObjects.find(eid); - if (ITER == physicsObjects.end()) - { - // Assume the object has already been successfully destroyed - return; - } - - world->destroyRigidBody(ITER->second.GetRigidBody()); - ITER->second.rp3dBody = nullptr; - - physicsObjects.erase(eid); - } - - void SHPhysicsObjectManager::addRigidBody(const QueueCommand&, SHPhysicsObject* physicsObject, const PhysicsComponentGroup& componentGroup) - { - SHASSERT(physicsObject != nullptr, "Valid physics object required to add body!") - - if (!componentGroup.rigidBodyComponent) - { - SHLOG_ERROR("Entity {} is missing a Rigidbody Component. Unable to update physics object!", componentGroup.eid) - return; - } - - // A static rigid body is implicitly created on creation of a physics object. - // Nothing is needed here. - } - - void SHPhysicsObjectManager::addCollider(const QueueCommand&, SHPhysicsObject* physicsObject, const PhysicsComponentGroup& componentGroup) - { - SHASSERT(physicsObject != nullptr, "Valid physics object required to add collider!") - - if (!componentGroup.colliderComponent) - { - SHLOG_ERROR("Entity {} is missing a Rigidbody Component. Unable to update physics object!", componentGroup.eid) - return; - } - - //const int NUM_SHAPES = static_cast(componentGroup.colliderComponent->GetCollisionShapes().size()); - //for (int i = 0; i < NUM_SHAPES; ++i) - // physicsObject->AddCollisionShape(i); - - physicsObject->SyncColliders(*componentGroup.colliderComponent); - } - - void SHPhysicsObjectManager::removeRigidBody(const QueueCommand&, SHPhysicsObject* physicsObject, const PhysicsComponentGroup& componentGroup) - { - SHASSERT(physicsObject != nullptr, "Valid physics object required to remove body!") - - if (componentGroup.colliderComponent) - physicsObject->SetStaticBody(); - } - - void SHPhysicsObjectManager::removeCollider(const QueueCommand&, SHPhysicsObject* physicsObject, const PhysicsComponentGroup&) - { - SHASSERT(physicsObject != nullptr, "Valid physics object required to remove collider!") - - physicsObject->RemoveAllCollisionShapes(); - } - - void SHPhysicsObjectManager::addCollisionShape(const QueueCommand& command, SHPhysicsObject* physicsObject, const PhysicsComponentGroup&) - { - SHASSERT(physicsObject != nullptr, "Valid physics object required to add collision shape!") - - physicsObject->AddCollisionShape(command.shapeIndex); - } - - void SHPhysicsObjectManager::removeCollisionShape(const QueueCommand& command, SHPhysicsObject* physicsObject, const PhysicsComponentGroup&) - { - SHASSERT(physicsObject != nullptr, "Valid physics object required to remove collision shape!") - - physicsObject->RemoveCollisionShape(command.shapeIndex); - } - - void SHPhysicsObjectManager::wakeAllObjects() noexcept - { - for (auto& physicsObject : physicsObjects | std::views::values) - physicsObject.GetRigidBody()->setIsSleeping(false); - } - - -} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObjectManager.h b/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObjectManager.h deleted file mode 100644 index f41c62ad..00000000 --- a/SHADE_Engine/src/Physics/PhysicsObject/SHPhysicsObjectManager.h +++ /dev/null @@ -1,181 +0,0 @@ -/**************************************************************************************** - * \file SHPhysicsObjectManager.h - * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Interface for a Physics Object Manager. - * - * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or - * disclosure of this file or its contents without the prior written consent - * of DigiPen Institute of Technology is prohibited. -****************************************************************************************/ - -#pragma once - -#include -#include - -#include - -// Project Headers -#include "SHPhysicsObject.h" - -namespace SHADE -{ - /*-----------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*-----------------------------------------------------------------------------------*/ - - class SH_API SHPhysicsObjectManager - { - private: - /*---------------------------------------------------------------------------------*/ - /* Friends */ - /*---------------------------------------------------------------------------------*/ - - friend class SHPhysicsSystem; - - /*---------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*---------------------------------------------------------------------------------*/ - - using PhysicsObjectEntityMap = std::unordered_map; - - public: - - /*---------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*---------------------------------------------------------------------------------*/ - - enum class PhysicsComponents - { - RIGID_BODY - , COLLIDER - , COLLISION_SHAPE - - , TOTAL - , INVALID = -1 - }; - - /*---------------------------------------------------------------------------------*/ - /* Constructors & Destructor */ - /*---------------------------------------------------------------------------------*/ - - SHPhysicsObjectManager () = default; - ~SHPhysicsObjectManager () = default; - - SHPhysicsObjectManager (const SHPhysicsObjectManager&) = delete; - SHPhysicsObjectManager (SHPhysicsObjectManager&&) = delete; - - /*---------------------------------------------------------------------------------*/ - /* Operator Overloads */ - /*---------------------------------------------------------------------------------*/ - - SHPhysicsObjectManager& operator=(const SHPhysicsObjectManager&) = delete; - SHPhysicsObjectManager& operator=(SHPhysicsObjectManager&&) = delete; - - /*---------------------------------------------------------------------------------*/ - /* Getter Functions */ - /*---------------------------------------------------------------------------------*/ - - [[nodiscard]] SHPhysicsObject* GetPhysicsObject (EntityID eid) noexcept; - [[nodiscard]] const PhysicsObjectEntityMap GetPhysicsObjects () const noexcept; - - /*---------------------------------------------------------------------------------*/ - /* Setter Functions */ - /*---------------------------------------------------------------------------------*/ - - void SetFactory (rp3d::PhysicsCommon& physicsFactory) noexcept; - void SetWorld (rp3d::PhysicsWorld* physicsWorld) noexcept; - - /*---------------------------------------------------------------------------------*/ - /* Function Members */ - /*---------------------------------------------------------------------------------*/ - - void AddRigidBody (EntityID eid) noexcept; - void AddCollider (EntityID eid) noexcept; - void AddCollisionShape (EntityID eid, int shapeIndex) noexcept; - - void RemoveRigidBody (EntityID eid) noexcept; - void RemoveCollider (EntityID eid) noexcept; - void RemoveCollisionShape (EntityID eid, int shapeIndex) noexcept; - - void UpdateCommands (); - void RemoveAllObjects (); - - private: - /*---------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*---------------------------------------------------------------------------------*/ - - struct QueueCommand - { - /*-------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*-------------------------------------------------------------------------------*/ - - enum class Command - { - ADD - , REMOVE - - , INVALID = -1 - }; - - /*-------------------------------------------------------------------------------*/ - /* Data Members */ - /*-------------------------------------------------------------------------------*/ - - EntityID eid = MAX_EID; - Command command = Command::INVALID; - PhysicsComponents component = PhysicsComponents::INVALID; - int shapeIndex = -1; // Only used when adding & removing collision shapes - }; - - struct PhysicsComponentGroup - { - public: - - /*-------------------------------------------------------------------------------*/ - /* Data Members */ - /*-------------------------------------------------------------------------------*/ - - - EntityID eid = MAX_EID; - SHRigidBodyComponent* rigidBodyComponent = nullptr; - SHColliderComponent* colliderComponent = nullptr; - }; - - using CommandFunctionPtr = void(*)(const QueueCommand&, SHPhysicsObject*, const PhysicsComponentGroup&); - - /*---------------------------------------------------------------------------------*/ - /* Data Members */ - /*---------------------------------------------------------------------------------*/ - - static CommandFunctionPtr componentFunc[3][2]; // 3 components, 2 commands - - rp3d::PhysicsCommon* factory = nullptr; - rp3d::PhysicsWorld* world = nullptr; - - PhysicsObjectEntityMap physicsObjects; - std::queue commandQueue; - - /*---------------------------------------------------------------------------------*/ - /* Function Members */ - /*---------------------------------------------------------------------------------*/ - - SHPhysicsObject* createPhysicsObject (EntityID eid) noexcept; - void destroyPhysicsObject (EntityID eid) noexcept; - - void wakeAllObjects () noexcept; - - static void addRigidBody (const QueueCommand& command, SHPhysicsObject* physicsObject, const PhysicsComponentGroup& componentGroup); - static void addCollider (const QueueCommand& command, SHPhysicsObject* physicsObject, const PhysicsComponentGroup& componentGroup); - static void removeRigidBody (const QueueCommand& command, SHPhysicsObject* physicsObject, const PhysicsComponentGroup& componentGroup); - static void removeCollider (const QueueCommand& command, SHPhysicsObject* physicsObject, const PhysicsComponentGroup& componentGroup); - - static void addCollisionShape (const QueueCommand& command, SHPhysicsObject* physicsObject, const PhysicsComponentGroup& componentGroup); - static void removeCollisionShape (const QueueCommand& command, SHPhysicsObject* physicsObject, const PhysicsComponentGroup& componentGroup); - - - }; - -} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp deleted file mode 100644 index 1ca7ae39..00000000 --- a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp +++ /dev/null @@ -1,334 +0,0 @@ -/**************************************************************************************** - * \file SHPhysicsDebugDrawSystem.cpp - * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Implementation for the Physics Debug Draw System - * - * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or - * disclosure of this file or its contents without the prior written consent - * of DigiPen Institute of Technology is prohibited. -****************************************************************************************/ - -#include - -// Primary Header -#include "SHPhysicsDebugDrawSystem.h" - -// Project Headers -#include "ECS_Base/Managers/SHSystemManager.h" -#include "Editor/SHEditor.h" -#include "Scene/SHSceneManager.h" - -namespace SHADE -{ - /*-----------------------------------------------------------------------------------*/ - /* Static Data Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - const SHPhysicsDebugDrawSystem::DebugDrawFunction SHPhysicsDebugDrawSystem::drawFunctions[NUM_FLAGS] = - { - drawColliders - , drawColliderAABBs - , drawBroadPhaseAABBs - , drawContactPoints - , drawContactNormals - , drawRaycasts - }; - - SHVec3 SHPhysicsDebugDrawSystem::boxVertices[NUM_BOX_VERTICES]; - - /*-----------------------------------------------------------------------------------*/ - /* Constructors & Destructor Definitions */ - /*-----------------------------------------------------------------------------------*/ - - SHPhysicsDebugDrawSystem::SHPhysicsDebugDrawSystem() noexcept - : debugDrawFlags { 0 } - , physicsSystem { nullptr } - { - debugColours[SHUtilities::ConvertEnum(DebugDrawFlags::COLLIDER)] = SHColour::GREEN; - debugColours[SHUtilities::ConvertEnum(DebugDrawFlags::COLLIDER_AABB)] = SHColour::YELLOW; - debugColours[SHUtilities::ConvertEnum(DebugDrawFlags::BROAD_PHASE_AABB)] = SHColour::CYAN; - debugColours[SHUtilities::ConvertEnum(DebugDrawFlags::CONTACT_POINTS)] = SHColour::RED; - debugColours[SHUtilities::ConvertEnum(DebugDrawFlags::CONTACT_NORMALS)] = SHColour::RED; - debugColours[SHUtilities::ConvertEnum(DebugDrawFlags::RAYCASTS)] = SHColour::ORANGE; - } - - SHPhysicsDebugDrawSystem::PhysicsDebugDrawRoutine::PhysicsDebugDrawRoutine() - : SHSystemRoutine { "Physics Debug Draw", true } - {} - - /*-----------------------------------------------------------------------------------*/ - /* Getter Function Definitions */ - /*-----------------------------------------------------------------------------------*/ - - bool SHPhysicsDebugDrawSystem::GetDebugDrawFlag(DebugDrawFlags flag) const noexcept - { - const auto INT_FLAG = SHUtilities::ConvertEnum(flag); - if (INT_FLAG < 0 || INT_FLAG >= NUM_FLAGS) - { - SHLOG_ERROR("Invalid Debug Draw Flag Passed {} in. Unable to get debug draw state!", INT_FLAG) - return false; - } - - return debugDrawFlags & 1U << SHUtilities::ConvertEnum(flag); - } - - /*-----------------------------------------------------------------------------------*/ - /* Setter Function Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHPhysicsDebugDrawSystem::SetDebugDrawFlag(DebugDrawFlags flag, bool value) noexcept - { - const auto INT_FLAG = SHUtilities::ConvertEnum(flag); - if (INT_FLAG < 0 || INT_FLAG >= NUM_FLAGS) - { - SHLOG_ERROR("Invalid Debug Draw Flag Passed {} in. Unable to set debug draw state!", INT_FLAG) - return; - } - - value ? (debugDrawFlags |= 1U << INT_FLAG) : (debugDrawFlags &= ~(1U << INT_FLAG)); - } - - /*-----------------------------------------------------------------------------------*/ - /* Public Function Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHPhysicsDebugDrawSystem::Init() - { - SystemFamily::GetID(); - - SHASSERT(physicsSystem == nullptr, "Non-existent physics system attached to the physics debug draw system!") - physicsSystem = SHSystemManager::GetSystem(); - - // Generate shapes - generateBox(); - } - - void SHPhysicsDebugDrawSystem::Exit() - { - physicsSystem = nullptr; - } - - void SHPhysicsDebugDrawSystem::PhysicsDebugDrawRoutine::Execute(double) noexcept - { - auto* system = reinterpret_cast(GetSystem()); - - auto* debugDrawSystem = SHSystemManager::GetSystem(); - if (debugDrawSystem == nullptr) - { - SHLOG_ERROR("Unable to get a debug draw system for Physics Debug Drawing!") - return; - } - - rp3d::DebugRenderer* rp3dRenderer = nullptr; - if (system->physicsSystem->worldState.world) - rp3dRenderer = &system->physicsSystem->worldState.world->getDebugRenderer(); - - for (int i = 0; i < SHUtilities::ConvertEnum(DebugDrawFlags::NUM_FLAGS); ++i) - { - const bool DRAW = (system->debugDrawFlags & (1U << i)) > 0; - if (DRAW) - { - drawFunctions[i](debugDrawSystem, rp3dRenderer); - } - else - { - if (rp3dRenderer && (i == 3 || i == 4)) - { - rp3dRenderer->setIsDebugItemDisplayed(reactphysics3d::DebugRenderer::DebugItem::CONTACT_POINT, false); - rp3dRenderer->setIsDebugItemDisplayed(reactphysics3d::DebugRenderer::DebugItem::CONTACT_NORMAL, false); - } - } - - } - - // Automatically clear the container of raycasts despite debug drawing state - // TODO(Diren): Move this somewhere else - system->physicsSystem->raycaster.ClearFrame(); - } - - /*-----------------------------------------------------------------------------------*/ - /* Private Function Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHPhysicsDebugDrawSystem::drawColliders(SHDebugDrawSystem* debugRenderer, rp3d::DebugRenderer* rp3dRenderer) noexcept - { - const auto& COLLIDER_SET = SHComponentManager::GetDense(); - for (const auto& COLLIDER : COLLIDER_SET) - { - // Skip inactive colliders - if (!SHSceneManager::CheckNodeAndComponentsActive(COLLIDER.GetEID())) - continue; - - for (auto& collisionShape : COLLIDER.GetCollisionShapes()) - { - switch (collisionShape.GetType()) - { - case SHCollisionShape::Type::BOX: debugDrawBox(debugRenderer, COLLIDER, collisionShape); break; - case SHCollisionShape::Type::SPHERE: debugDrawSphere(debugRenderer, COLLIDER, collisionShape); break; - default: break; - } - } - } - } - - void SHPhysicsDebugDrawSystem::drawColliderAABBs(SHDebugDrawSystem* debugRenderer, rp3d::DebugRenderer* rp3dRenderer) noexcept - { - - } - - void SHPhysicsDebugDrawSystem::drawBroadPhaseAABBs(SHDebugDrawSystem* debugRenderer, rp3d::DebugRenderer* rp3dRenderer) noexcept - { - - } - - void SHPhysicsDebugDrawSystem::drawContactPoints(SHDebugDrawSystem* debugRenderer, rp3d::DebugRenderer* rp3dRenderer) noexcept - { - #ifdef SHEDITOR - - const auto* EDITOR = SHSystemManager::GetSystem(); - if (EDITOR && EDITOR->editorState != SHEditor::State::STOP) - { - rp3dRenderer->setIsDebugItemDisplayed(rp3d::DebugRenderer::DebugItem::CONTACT_POINT, true); - const int NUM_TRIS = static_cast(rp3dRenderer->getNbTriangles()); - if (NUM_TRIS == 0) - return; - - const auto& TRI_ARRAY = rp3dRenderer->getTrianglesArray(); - for (int i = 0; i < NUM_TRIS; ++i) - debugRenderer->DrawTri(SHColour::RED, TRI_ARRAY[i].point1, TRI_ARRAY[i].point2, TRI_ARRAY[i].point3); - } - - #else - - rp3dRenderer->setIsDebugItemDisplayed(rp3d::DebugRenderer::DebugItem::CONTACT_POINT, true); - const int NUM_TRIS = static_cast(rp3dRenderer->getNbTriangles()); - if (NUM_TRIS == 0) - return; - - const auto& TRI_ARRAY = rp3dRenderer->getTrianglesArray(); - for (int i = 0; i < NUM_TRIS; ++i) - debugRenderer->DrawTri(SHColour::RED, TRI_ARRAY[i].point1, TRI_ARRAY[i].point2, TRI_ARRAY[i].point3); - - #endif - } - - void SHPhysicsDebugDrawSystem::drawContactNormals(SHDebugDrawSystem* debugRenderer, rp3d::DebugRenderer* rp3dRenderer) noexcept - { - #ifdef SHEDITOR - const auto* EDITOR = SHSystemManager::GetSystem(); - if (EDITOR && EDITOR->editorState != SHEditor::State::STOP) - { - rp3dRenderer->setIsDebugItemDisplayed(rp3d::DebugRenderer::DebugItem::CONTACT_NORMAL, true); - const int NUM_LINES = static_cast(rp3dRenderer->getNbLines()); - if (NUM_LINES == 0) - return; - - const auto& LINE_ARRAY = rp3dRenderer->getLinesArray(); - for (int i = 0; i < NUM_LINES; ++i) - debugRenderer->DrawLine(SHColour::RED, LINE_ARRAY[i].point1, LINE_ARRAY[i].point2); - } - - #else - - rp3dRenderer->setIsDebugItemDisplayed(rp3d::DebugRenderer::DebugItem::CONTACT_NORMAL, true); - const int NUM_LINES = static_cast(rp3dRenderer->getNbLines()); - if (NUM_LINES == 0) - return; - - const auto& LINE_ARRAY = rp3dRenderer->getLinesArray(); - for (int i = 0; i < NUM_LINES; ++i) - debugRenderer->DrawLine(SHColour::RED, LINE_ARRAY[i].point1, LINE_ARRAY[i].point2); - - #endif - } - - void SHPhysicsDebugDrawSystem::drawRaycasts(SHDebugDrawSystem* debugRenderer, rp3d::DebugRenderer* rp3dRenderer) noexcept - { - auto* physicsSystem = SHSystemManager::GetSystem(); - if (!physicsSystem) - { - SHLOG_ERROR("Unable to retrieve physics system for debug drawing raycasts!") - return; - } - - const SHColour& RAY_COLOUR = SHColour::ORANGE; - - // Draw all raycast pairs - for (const auto& [ray, raycastResult] : physicsSystem->raycaster.GetRaycasts()) - { - // If infinity, it is an infinite raycast. If not, render the distance in raycastResult. - // Ignore the hit variable as it will always correspond to the length of the raycast, hit or miss. - const float RENDER_DIST = raycastResult.distance == std::numeric_limits::infinity() ? SHRay::MAX_RAYCAST_DIST : raycastResult.distance; - const SHVec3 END_POS = ray.position + (ray.direction * RENDER_DIST); - - debugRenderer->DrawLine(RAY_COLOUR, ray.position, END_POS); - } - } - - - void SHPhysicsDebugDrawSystem::generateBox() noexcept - { - boxVertices[0] = { 0.5f, 0.5f, -0.5f }; // TOP_RIGHT_BACK - boxVertices[1] = { -0.5f, 0.5f, -0.5f }; // TOP_LEFT_BACK - boxVertices[2] = { 0.5f, -0.5f, -0.5f }; // BTM_RIGHT_BACK - boxVertices[3] = { -0.5f, -0.5f, -0.5f }; // BTM_LEFT_BACK - boxVertices[4] = { 0.5f, 0.5f, 0.5f }; // TOP_RIGHT_FRONT - boxVertices[5] = { -0.5f, 0.5f, 0.5f }; // TOP_LEFT_FRONT - boxVertices[6] = { 0.5f, -0.5f, 0.5f }; // BTM_RIGHT_FRONT - boxVertices[7] = { -0.5f, -0.5f, 0.5f }; // BTM_LEFT_FRONT - } - - void SHPhysicsDebugDrawSystem::debugDrawBox(SHDebugDrawSystem* debugRenderer, const SHColliderComponent& colliderComponent, const SHCollisionShape& collisionShape) noexcept - { - auto* BOX = reinterpret_cast(collisionShape.GetShape()); - - // Calculate final position & orientation - const SHVec3 COLLIDER_POS = colliderComponent.GetPosition(); - const SHVec3 BOX_POS = collisionShape.GetPositionOffset(); - const SHQuaternion COLLIDER_ROT = colliderComponent.GetOrientation(); - const SHQuaternion BOX_ROT = SHQuaternion::FromEuler(collisionShape.GetRotationOffset()); - - - const SHMatrix COLLIDER_TR = SHMatrix::Rotate(COLLIDER_ROT) * SHMatrix::Translate(COLLIDER_POS); - const SHMatrix BOX_TRS = SHMatrix::Scale(BOX->GetWorldExtents() * 2.0f) * SHMatrix::Rotate(BOX_ROT) * SHMatrix::Translate(BOX_POS); - - const SHMatrix FINAL_TRS = BOX_TRS * COLLIDER_TR; - - const SHColour COLLIDER_COLOUR = collisionShape.IsTrigger() ? SHColour::PURPLE : SHColour::GREEN; - - std::array transformedVertices; - for (uint32_t i = 0; i < NUM_BOX_VERTICES / 2; ++i) - { - const uint32_t IDX1 = i; - const uint32_t IDX2 = i + NUM_BOX_VERTICES / 2; - - transformedVertices[IDX1] = SHVec3::Transform(boxVertices[IDX1], FINAL_TRS); - transformedVertices[IDX2] = SHVec3::Transform(boxVertices[IDX2], FINAL_TRS); - - // Draw 4 line to connect the quads - debugRenderer->DrawLine(COLLIDER_COLOUR, transformedVertices[IDX1], transformedVertices[IDX2]); - } - - // A, B, C, D - std::array backQuad { transformedVertices[0], transformedVertices[1], transformedVertices[3], transformedVertices[2] }; - debugRenderer->DrawPoly(COLLIDER_COLOUR, backQuad.begin(), backQuad.end()); - - // E, F, G, H - std::array frontQuad { transformedVertices[4], transformedVertices[5], transformedVertices[7], transformedVertices[6] }; - debugRenderer->DrawPoly(COLLIDER_COLOUR, frontQuad.begin(), frontQuad.end()); - } - - void SHPhysicsDebugDrawSystem::debugDrawSphere(SHDebugDrawSystem* debugRenderer, const SHColliderComponent& colliderComponent, const SHCollisionShape& collisionShape) noexcept - { - auto* SPHERE = reinterpret_cast(collisionShape.GetShape()); - - const SHColour COLLIDER_COLOUR = collisionShape.IsTrigger() ? SHColour::PURPLE : SHColour::GREEN; - - // Calculate final position & orientation - const SHQuaternion FINAL_ROT = colliderComponent.GetOrientation() * SHQuaternion::FromEuler(collisionShape.GetRotationOffset()); - const SHMatrix TR = SHMatrix::Rotate(FINAL_ROT) * SHMatrix::Translate(colliderComponent.GetPosition()); - - debugRenderer->DrawSphere(COLLIDER_COLOUR, SHVec3::Transform(collisionShape.GetPositionOffset(), TR), SPHERE->GetWorldRadius()); - } - -} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h deleted file mode 100644 index dc703092..00000000 --- a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h +++ /dev/null @@ -1,138 +0,0 @@ -/**************************************************************************************** - * \file SHPhysicsDebugDrawSystem.h - * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Interface for the Physics Debug Draw System - * - * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or - * disclosure of this file or its contents without the prior written consent - * of DigiPen Institute of Technology is prohibited. -****************************************************************************************/ - -#pragma once - -#include - -// Project Headers -#include "ECS_Base/System/SHSystemRoutine.h" -#include "Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h" -#include "Math/SHColour.h" -#include "SHPhysicsSystem.h" -#include "Tools/Utilities/SHUtilities.h" - -namespace SHADE -{ - /*-----------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*-----------------------------------------------------------------------------------*/ - - class SH_API SHPhysicsDebugDrawSystem final : public SHSystem - { - public: - /*---------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*---------------------------------------------------------------------------------*/ - - enum class DebugDrawFlags - { - COLLIDER - , COLLIDER_AABB - , BROAD_PHASE_AABB - , CONTACT_POINTS - , CONTACT_NORMALS - , RAYCASTS - - , NUM_FLAGS - }; - - /*---------------------------------------------------------------------------------*/ - /* Constructors & Destructor */ - /*---------------------------------------------------------------------------------*/ - - SHPhysicsDebugDrawSystem() noexcept; - - /*---------------------------------------------------------------------------------*/ - /* Getter Functions */ - /*---------------------------------------------------------------------------------*/ - - bool GetDebugDrawFlag(DebugDrawFlags flag) const noexcept; - - /*---------------------------------------------------------------------------------*/ - /* Setter Functions */ - /*---------------------------------------------------------------------------------*/ - - void SetDebugDrawFlag(DebugDrawFlags flag, bool value) noexcept; - - /*---------------------------------------------------------------------------------*/ - /* Function Members */ - /*---------------------------------------------------------------------------------*/ - - void Init() override; - void Exit() override; - - /*---------------------------------------------------------------------------------*/ - /* System Routines */ - /*---------------------------------------------------------------------------------*/ - - class SH_API PhysicsDebugDrawRoutine final : public SHSystemRoutine - { - public: - /*-------------------------------------------------------------------------------*/ - /* Constructors & Destructor */ - /*-------------------------------------------------------------------------------*/ - - PhysicsDebugDrawRoutine(); - - /*-------------------------------------------------------------------------------*/ - /* Function Members */ - /*-------------------------------------------------------------------------------*/ - void Execute(double dt) noexcept override; - }; - - private: - /*---------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*---------------------------------------------------------------------------------*/ - - using DebugDrawFunction = void(*)(SHDebugDrawSystem*, rp3d::DebugRenderer*) noexcept; - - /*---------------------------------------------------------------------------------*/ - /* Data Members */ - /*---------------------------------------------------------------------------------*/ - - static constexpr int NUM_FLAGS = SHUtilities::ConvertEnum(DebugDrawFlags::NUM_FLAGS); - static const DebugDrawFunction drawFunctions[NUM_FLAGS]; - - // SHAPES INFO - - static constexpr size_t NUM_BOX_VERTICES = 8; - static SHVec3 boxVertices[NUM_BOX_VERTICES]; - - - uint8_t debugDrawFlags; - SHPhysicsSystem* physicsSystem; - SHColour debugColours[NUM_FLAGS]; - - /*---------------------------------------------------------------------------------*/ - /* Function Members */ - /*---------------------------------------------------------------------------------*/ - - // Generic Draw Functions - - static void drawColliders (SHDebugDrawSystem* debugRenderer, rp3d::DebugRenderer* rp3dRenderer) noexcept; - static void drawColliderAABBs (SHDebugDrawSystem* debugRenderer, rp3d::DebugRenderer* rp3dRenderer) noexcept; - static void drawBroadPhaseAABBs (SHDebugDrawSystem* debugRenderer, rp3d::DebugRenderer* rp3dRenderer) noexcept; - static void drawContactPoints (SHDebugDrawSystem* debugRenderer, rp3d::DebugRenderer* rp3dRenderer) noexcept; - static void drawContactNormals (SHDebugDrawSystem* debugRenderer, rp3d::DebugRenderer* rp3dRenderer) noexcept; - static void drawRaycasts (SHDebugDrawSystem* debugRenderer, rp3d::DebugRenderer* rp3dRenderer) noexcept; - - // Shape Generation Functions - - static void generateBox () noexcept; - - // Shape Draw Functions - - static void debugDrawBox (SHDebugDrawSystem* debugRenderer, const SHColliderComponent& colliderComponent, const SHCollisionShape& collisionShape) noexcept; - static void debugDrawSphere (SHDebugDrawSystem* debugRenderer, const SHColliderComponent& colliderComponent, const SHCollisionShape& collisionShape) noexcept; - }; - -} // namespace SHADE diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp index 768c9b77..eacd6ac9 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp @@ -45,32 +45,6 @@ namespace SHADE return 1.0 / fixedDT; } - const SHPhysicsWorldState::WorldSettings& SHPhysicsSystem::GetWorldSettings() const noexcept - { - return worldState.settings; - } - - const std::vector& SHPhysicsSystem::GetAllCollisionInfo() const noexcept - { - return collisionListener.GetCollisionInfoContainer(); - } - - const std::vector& SHPhysicsSystem::GetAllTriggerInfo() const noexcept - { - return collisionListener.GetTriggerInfoContainer(); - } - - const SHPhysicsObject* SHPhysicsSystem::GetPhysicsObject(EntityID eid) noexcept - { - return objectManager.GetPhysicsObject(eid); - } - - - const SHPhysicsObjectManager::PhysicsObjectEntityMap& SHPhysicsSystem::GetPhysicsObjects() const noexcept - { - return objectManager.physicsObjects; - } - /*-----------------------------------------------------------------------------------*/ /* Setter Function Definitions */ /*-----------------------------------------------------------------------------------*/ @@ -80,12 +54,6 @@ namespace SHADE fixedDT = 1.0 / fixedUpdateRate; } - void SHPhysicsSystem::SetWorldSettings(const SHPhysicsWorldState::WorldSettings& settings) noexcept - { - worldState.settings = settings; - worldState.UpdateSettings(); - } - /*-----------------------------------------------------------------------------------*/ /* Public Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ @@ -96,41 +64,10 @@ namespace SHADE std::filesystem::path defaultCollisionTagNameFilePath { ASSET_ROOT }; defaultCollisionTagNameFilePath.append("CollisionTags.SHConfig"); SHCollisionTagMatrix::Init(defaultCollisionTagNameFilePath); - - // Link Physics Object Manager with System & Raycaster - objectManager.SetFactory(factory); - raycaster.SetObjectManager(&objectManager); - - // Link Collision Listener with System - collisionListener.BindToSystem(this); - - // Subscribe to component events - const std::shared_ptr ADD_COMPONENT_RECEIVER { std::make_shared>(this, &SHPhysicsSystem::addPhysicsComponent) }; - const ReceiverPtr ADD_COMPONENT_RECEIVER_PTR = std::dynamic_pointer_cast(ADD_COMPONENT_RECEIVER); - SHEventManager::SubscribeTo(SH_COMPONENT_ADDED_EVENT, ADD_COMPONENT_RECEIVER_PTR); - - const std::shared_ptr REMOVE_COMPONENT_RECEIVER { std::make_shared>(this, &SHPhysicsSystem::removePhysicsComponent) }; - const ReceiverPtr REMOVE_COMPONENT_RECEIVER_PTR = std::dynamic_pointer_cast(REMOVE_COMPONENT_RECEIVER); - SHEventManager::SubscribeTo(SH_COMPONENT_REMOVED_EVENT, REMOVE_COMPONENT_RECEIVER_PTR); - - #ifdef SHEDITOR - - // Subscribe to Editor State Change Events - const std::shared_ptr ON_PLAY_RECEIVER { std::make_shared>(this, &SHPhysicsSystem::onPlay) }; - const ReceiverPtr ON_PLAY_RECEIVER_PTR = std::dynamic_pointer_cast(ON_PLAY_RECEIVER); - SHEventManager::SubscribeTo(SH_EDITOR_ON_PLAY_EVENT, ON_PLAY_RECEIVER_PTR); - - const std::shared_ptr ON_STOP_RECEIVER { std::make_shared>(this, &SHPhysicsSystem::onStop) }; - const ReceiverPtr ON_STOP_RECEIVER_PTR = std::dynamic_pointer_cast(ON_STOP_RECEIVER); - SHEventManager::SubscribeTo(SH_EDITOR_ON_STOP_EVENT, ON_STOP_RECEIVER_PTR); - - #endif - } void SHPhysicsSystem::Exit() { - worldState.DestroyWorld(factory); // Write collision tag names to file std::filesystem::path defaultCollisionTagNameFilePath { ASSET_ROOT }; @@ -138,344 +75,13 @@ namespace SHADE SHCollisionTagMatrix::Exit(defaultCollisionTagNameFilePath); } - void SHPhysicsSystem::BuildScene(SHSceneGraph& sceneGraph) - { - static const auto BUILD_NEW_SCENE_PHYSICS_OBJECT = [&](SHSceneNode* node) - { - const EntityID EID = node->GetEntityID(); - - if (SHComponentManager::HasComponent(EID)) - objectManager.AddRigidBody(EID); - - if (SHComponentManager::HasComponent(EID)) - { - objectManager.AddCollider(EID); - - auto* COLLIDER = SHComponentManager::GetComponent(EID); - for (size_t i = 0; i < COLLIDER->GetCollisionShapes().size(); ++i) - objectManager.AddCollisionShape(EID, i); - } - - }; - - //////////////////////////////// - - // Destroy an existing world - if (worldState.world != nullptr) - { - objectManager.RemoveAllObjects(); - objectManager.SetWorld(nullptr); - - collisionListener.ClearContainers(); - raycaster.ClearFrame(); - - worldState.DestroyWorld(factory); - } - - worldState.CreateWorld(factory); -#ifdef _PUBLISH - worldState.world->setIsDebugRenderingEnabled(false); -#else - worldState.world->setIsDebugRenderingEnabled(true); -#endif - - // Link Collision Listener & Raycaster - collisionListener.BindToWorld(worldState.world); - raycaster.BindToWorld(worldState.world); - - // Link with object manager & create all physics objects - objectManager.SetWorld(worldState.world); - - // When building a scene, clear the object manager command queue and build scene objects again. - // This is done to avoid duplicate adds. - while (!objectManager.commandQueue.empty()) - objectManager.commandQueue.pop(); - - sceneGraph.Traverse(BUILD_NEW_SCENE_PHYSICS_OBJECT); - objectManager.UpdateCommands(); - } - - void SHPhysicsSystem::ForceBuild(SHSceneGraph& sceneGraph) - { - // HACK: Band-aid fix. To be removed. - objectManager.UpdateCommands(); - } - - void SHPhysicsSystem::ForceUpdate() { - if (!worldState.world) - { - SHLOGV_ERROR("Unable to force update without a Physics world!") - return; - } - auto* scriptingSystem = SHSystemManager::GetSystem(); - if (scriptingSystem == nullptr) - { - SHLOGV_ERROR("Unable to invoke FixedUpdate() on scripts due to missing SHScriptEngine!"); - } - - // Force the physics world to update once - if (scriptingSystem != nullptr) - scriptingSystem->ExecuteFixedUpdates(); - - worldState.world->update(static_cast(fixedDT)); - - // Sync transforms. No interpolation applied here - for (auto& [entityID, physicsObject] : objectManager.physicsObjects) - { - auto* transformComponent = SHComponentManager::GetComponent_s(entityID); - auto* rigidBodyComponent = SHComponentManager::GetComponent_s(entityID); - auto* colliderComponent = SHComponentManager::GetComponent_s(entityID); - - const auto& CURRENT_TF = physicsObject.GetRigidBody()->getTransform(); - const auto& RENDER_POS = CURRENT_TF.getPosition(); - const auto& RENDER_ROT = CURRENT_TF.getOrientation(); - - // Cache transform - physicsObject.prevTransform = CURRENT_TF; - - // Sync with physics components - if (rigidBodyComponent && SHSceneManager::CheckNodeAndComponentsActive(entityID)) - { - rigidBodyComponent->position = RENDER_POS; - rigidBodyComponent->orientation = RENDER_ROT; - } - - if (colliderComponent && SHSceneManager::CheckNodeAndComponentsActive(entityID)) - { - colliderComponent->position = RENDER_POS; - colliderComponent->orientation = RENDER_ROT; - } - - // Set transform for rendering - if (transformComponent) - { - transformComponent->SetWorldPosition(RENDER_POS); - transformComponent->SetWorldOrientation(RENDER_ROT); - } - } - } - - SHPhysicsRaycastResult SHPhysicsSystem::Raycast(const SHRay& ray, float distance, const SHCollisionTag& collisionTag) noexcept - { - return raycaster.Raycast(ray, distance, collisionTag); - } - - SHPhysicsRaycastResult SHPhysicsSystem::Linecast(const SHVec3& start, const SHVec3& end, const SHCollisionTag& collisionTag) noexcept - { - return raycaster.Linecast(start, end, collisionTag); - } - - SHPhysicsRaycastResult SHPhysicsSystem::ColliderRaycast(EntityID eid, const SHRay& ray, float distance) noexcept - { - return raycaster.ColliderRaycast(eid, ray, distance); - } - - SHPhysicsRaycastResult SHPhysicsSystem::ColliderRaycast(EntityID eid, int shapeIndex, const SHRay& ray, float distance) noexcept - { - return raycaster.ColliderRaycast(eid, shapeIndex, ray, distance); - } - - SHPhysicsRaycastResult SHPhysicsSystem::ColliderLinecast(EntityID eid, const SHVec3& start, const SHVec3& end) noexcept - { - return raycaster.ColliderLinecast(eid, start, end); - } - - SHPhysicsRaycastResult SHPhysicsSystem::ColliderLinecast(EntityID eid, int shapeIndex, const SHVec3& start, const SHVec3& end) noexcept - { - return raycaster.ColliderLinecast(eid, shapeIndex, start, end); - } - - void SHPhysicsSystem::AddCollisionShape(EntityID eid, int shapeIndex) - { - static const auto ADD_SHAPE = [&](EntityID entityID, int index) - { - objectManager.AddCollisionShape(entityID, index); - - auto* colliderComponent = SHComponentManager::GetComponent(entityID); - auto& collisionShape = colliderComponent->GetCollisionShape(index); - - const SHPhysicsColliderAddedEvent COLLIDER_ADDED_EVENT_DATA - { - .entityID = entityID - , .colliderType = collisionShape.GetType() - , .colliderIndex = index - }; - - SHEventManager::BroadcastEvent(COLLIDER_ADDED_EVENT_DATA, SH_PHYSICS_COLLIDER_ADDED_EVENT); - }; - - #ifdef SHEDITOR - - const auto* EDITOR = SHSystemManager::GetSystem(); - if (EDITOR && EDITOR->editorState != SHEditor::State::STOP) - ADD_SHAPE(eid, shapeIndex); - - #else - - ADD_SHAPE(eid, shapeIndex); - - #endif - } - - void SHPhysicsSystem::RemoveCollisionShape(EntityID eid, int shapeIndex) - { - static const auto REMOVE_SHAPE = [&](EntityID entityID, int index) - { - objectManager.RemoveCollisionShape(entityID, index); - - const SHPhysicsColliderRemovedEvent COLLIDER_REMOVED_EVENT_DATA - { - .entityID = entityID - , .colliderIndex = index - }; - - SHEventManager::BroadcastEvent(COLLIDER_REMOVED_EVENT_DATA, SH_PHYSICS_COLLIDER_REMOVED_EVENT); - }; - - #ifdef SHEDITOR - - const auto* EDITOR = SHSystemManager::GetSystem(); - if (EDITOR && EDITOR->editorState != SHEditor::State::STOP) - REMOVE_SHAPE(eid, shapeIndex); - - #else - - REMOVE_SHAPE(eid, shapeIndex); - - #endif } /*-----------------------------------------------------------------------------------*/ /* Private Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ - SHEventHandle SHPhysicsSystem::addPhysicsComponent(SHEventPtr addComponentEvent) noexcept - { - const auto& EVENT_DATA = reinterpret_cast*>(addComponentEvent.get()); - - static const auto RIGID_BODY_ID = ComponentFamily::GetID(); - static const auto COLLIDER_ID = ComponentFamily::GetID(); - - const auto ADDED_ID = EVENT_DATA->data->addedComponentType; - const bool IS_PHYSICS_COMPONENT = ADDED_ID == RIGID_BODY_ID || ADDED_ID == COLLIDER_ID; - if (IS_PHYSICS_COMPONENT) - { - const EntityID EID = EVENT_DATA->data->eid; - - // We only add tell the physics object manager to add a component if the scene is played - - #ifdef SHEDITOR - - const auto* EDITOR = SHSystemManager::GetSystem(); - - if (EDITOR && EDITOR->editorState != SHEditor::State::STOP) - ADDED_ID == RIGID_BODY_ID ? objectManager.AddRigidBody(EID) : objectManager.AddCollider(EID); - - #else - - ADDED_ID == RIGID_BODY_ID ? objectManager.AddRigidBody(EID) : objectManager.AddCollider(EID); - - #endif - } - - return EVENT_DATA->handle; - } - - SHEventHandle SHPhysicsSystem::removePhysicsComponent(SHEventPtr removeComponentEvent) noexcept - { - const auto& EVENT_DATA = reinterpret_cast*>(removeComponentEvent.get()); - - static const auto RIGID_BODY_ID = ComponentFamily::GetID(); - static const auto COLLIDER_ID = ComponentFamily::GetID(); - - const auto REMOVED_ID = EVENT_DATA->data->removedComponentType; - const bool IS_PHYSICS_COMPONENT = REMOVED_ID == RIGID_BODY_ID || REMOVED_ID == COLLIDER_ID; - if (IS_PHYSICS_COMPONENT) - { - const EntityID EID = EVENT_DATA->data->eid; - - // We only add tell the physics object manager to remove a component if the scene is played - - #ifdef SHEDITOR - - const auto* EDITOR = SHSystemManager::GetSystem(); - - if (EDITOR && EDITOR->editorState != SHEditor::State::STOP) - REMOVED_ID == RIGID_BODY_ID ? objectManager.RemoveRigidBody(EID) : objectManager.RemoveCollider(EID); - - #else - - REMOVED_ID == RIGID_BODY_ID ? objectManager.RemoveRigidBody(EID) : objectManager.RemoveCollider(EID); - - #endif - } - - return EVENT_DATA->handle; - } - - SHEventHandle SHPhysicsSystem::onPlay(SHEventPtr onPlayEvent) - { - static const auto BUILD_PHYSICS_OBJECT = [&](SHSceneNode* node) - { - const EntityID EID = node->GetEntityID(); - - if (SHComponentManager::HasComponent(EID)) - objectManager.AddRigidBody(EID); - - if (SHComponentManager::HasComponent(EID)) - { - objectManager.AddCollider(EID); - - auto* COLLIDER = SHComponentManager::GetComponent(EID); - for (size_t i = 0; i < COLLIDER->GetCollisionShapes().size(); ++i) - objectManager.AddCollisionShape(EID, i); - } - }; - - //////////////////////////////// - - // Create physics world - if (worldState.world != nullptr) - return onPlayEvent->handle; - - worldState.CreateWorld(factory); -#ifdef _PUBLISH - worldState.world->setIsDebugRenderingEnabled(false); -#else - worldState.world->setIsDebugRenderingEnabled(true); -#endif - - // Link Collision Listener & Raycaster - collisionListener.BindToWorld(worldState.world); - raycaster.BindToWorld(worldState.world); - - // Link with object manager & create all physics objects - objectManager.SetWorld(worldState.world); - - // Build scene - SHSceneManager::GetCurrentSceneGraph().Traverse(BUILD_PHYSICS_OBJECT); - objectManager.UpdateCommands(); - - return onPlayEvent->handle; - } - - SHEventHandle SHPhysicsSystem::onStop(SHEventPtr onStopEvent) - { - // Remove all physics objects - objectManager.RemoveAllObjects(); - objectManager.SetWorld(nullptr); - - // Clear all collision info - // Collision listener is automatically unbound when world is destroyed - collisionListener.ClearContainers(); - raycaster.ClearFrame(); - - // Destroy the world - worldState.DestroyWorld(factory); - - return onStopEvent->handle; - } } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h index 99db493e..3fe05c09 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h @@ -10,22 +10,13 @@ #pragma once -#include -#include - -// External Dependencies -#include - // Project Headers #include "ECS_Base/System/SHSystemRoutine.h" #include "ECS_Base/System/SHFixedSystemRoutine.h" #include "Math/Transform/SHTransformComponent.h" -#include "Physics/Collision/SHCollisionListener.h" -#include "Physics/Collision/SHPhysicsRaycaster.h" #include "Physics/Interface/SHRigidBodyComponent.h" #include "Physics/Interface/SHColliderComponent.h" -#include "Physics/PhysicsObject/SHPhysicsObjectManager.h" #include "Physics/SHPhysicsWorld.h" #include "Scene/SHSceneGraph.h" @@ -55,14 +46,9 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] double GetFixedUpdateRate () const noexcept; - [[nodiscard]] const SHPhysicsWorldState::WorldSettings& GetWorldSettings () const noexcept; + [[nodiscard]] double GetFixedUpdateRate () const noexcept; + [[nodiscard]] const SHPhysicsWorldState::WorldSettings& GetWorldSettings () const noexcept; - [[nodiscard]] const std::vector& GetAllCollisionInfo () const noexcept; - [[nodiscard]] const std::vector& GetAllTriggerInfo () const noexcept; - - [[nodiscard]] const SHPhysicsObject* GetPhysicsObject (EntityID eid) noexcept; - [[nodiscard]] const SHPhysicsObjectManager::PhysicsObjectEntityMap& GetPhysicsObjects () const noexcept; /*---------------------------------------------------------------------------------*/ /* Setter Functions */ /*---------------------------------------------------------------------------------*/ @@ -77,105 +63,8 @@ namespace SHADE void Init () override; void Exit () override; - void BuildScene (SHSceneGraph& sceneGraph); - void ForceBuild (SHSceneGraph& sceneGraph); void ForceUpdate (); - /** - * @brief Casts a ray into the world. - * @param ray The ray to cast. - * @param distance The distance to cast the ray. Defaults to infinity. - * @param collisionTag The collision tag to use for filtering the raycast. - * @return The result of the raycast. - */ - SHPhysicsRaycastResult Raycast - ( - const SHRay& ray - , float distance = std::numeric_limits::infinity() - , const SHCollisionTag& collisionTag = SHCollisionTag{} - ) noexcept; - - /** - * @brief Casts a bounded ray into the world. - * @param start The starting point of the ray. - * @param end The end point of the ray. - * @param collisionTag The collision tag to use for filtering the bounded raycast. - * @return The result of the raycast. - */ - SHPhysicsRaycastResult Linecast - ( - const SHVec3& start - , const SHVec3& end - , const SHCollisionTag& collisionTag = SHCollisionTag{} - ) noexcept; - - /** - * @brief Casts a ray at a body with colliders. - * @param eid The entity to cast to. - * @param ray The ray to cast. - * @param distance The distance to cast the ray. Defaults to infinity. - * @return The result of the raycast. - */ - SHPhysicsRaycastResult ColliderRaycast - ( - EntityID eid - , const SHRay& ray - , float distance = std::numeric_limits::infinity() - ) noexcept; - - /** - * @brief Casts a ray at a collider. - * @param eid The entity to cast to. - * @param shapeIndex The index of the collision shape. - * @param ray The ray to cast. - * @param distance The distance to cast the ray. Defaults to infinity. - * @return The result of the raycast. - */ - SHPhysicsRaycastResult ColliderRaycast - ( - EntityID eid - , int shapeIndex - , const SHRay& ray - , float distance = std::numeric_limits::infinity() - ) noexcept; - - /** - * @brief Casts a bounded ray at a body with colliders. - * @param eid - * @param start - * @param end - * @return The result of the raycast. - */ - SHPhysicsRaycastResult ColliderLinecast - ( - EntityID eid - , const SHVec3& start - , const SHVec3& end - ) noexcept; - - /** - * @brief - * @param eid - * @param shapeIndex - * @param start - * @param end - * @return The result of the raycast. - */ - SHPhysicsRaycastResult ColliderLinecast - ( - EntityID eid - , int shapeIndex - , const SHVec3& start - , const SHVec3& end - ) noexcept; - - // Specific Handling for Collision Shapes as they are not under the Component System. - // This is done as events need to be sent out. - // TODO(Diren): Consider using a static method through the ColliderComponent. - - void AddCollisionShape (EntityID eid, int shapeIndex); - void RemoveCollisionShape (EntityID eid, int shapeIndex); - /*---------------------------------------------------------------------------------*/ /* System Routines */ /*---------------------------------------------------------------------------------*/ @@ -200,17 +89,6 @@ namespace SHADE /* Function Members */ /*-------------------------------------------------------------------------------*/ - void syncRigidBodyActive (EntityID eid, SHPhysicsObject& physicsObject) const noexcept; - void syncColliderActive (EntityID eid, SHPhysicsObject& physicsObject) const noexcept; - static void syncOnPlay (EntityID eid, SHPhysicsObject& physicsObject) noexcept; - - static void preUpdateSyncTransform - ( - SHPhysicsObject& physicsObject - , SHTransformComponent* transformComponent - , SHRigidBodyComponent* rigidBodyComponent - , SHColliderComponent* colliderComponent - ) noexcept; }; class SH_API PhysicsFixedUpdate final : public SHFixedSystemRoutine @@ -250,14 +128,6 @@ namespace SHADE /* Function Members */ /*-------------------------------------------------------------------------------*/ - static void postUpdateSyncTransforms - ( - SHPhysicsObject& physicsObject - , SHTransformComponent* transformComponent - , SHRigidBodyComponent* rigidBodyComponent - , SHColliderComponent* colliderComponent - , double interpolationFactor - ) noexcept; }; private: @@ -271,26 +141,9 @@ namespace SHADE double interpolationFactor; double fixedDT; - // rp3d - - rp3d::PhysicsCommon factory; - - // Interface objects - - SHPhysicsWorldState worldState; - SHPhysicsObjectManager objectManager; - SHCollisionListener collisionListener; - SHPhysicsRaycaster raycaster; - /*---------------------------------------------------------------------------------*/ /* Function Members */ /*---------------------------------------------------------------------------------*/ - SHEventHandle addPhysicsComponent (SHEventPtr addComponentEvent) noexcept; - SHEventHandle removePhysicsComponent (SHEventPtr removeComponentEvent) noexcept; - - SHEventHandle onPlay (SHEventPtr onPlayEvent); - SHEventHandle onStop (SHEventPtr onStopEvent); - SHEventHandle buildScene (SHEventPtr onSceneChangeEvent); }; } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.cpp index 1fb11274..06f1b464 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.cpp @@ -27,26 +27,26 @@ namespace SHADE { static std::vector emptyVec; - auto phySystem = SHSystemManager::GetSystem(); - if (phySystem) - { - return phySystem->GetAllCollisionInfo(); - } + //auto phySystem = SHSystemManager::GetSystem(); + //if (phySystem) + //{ + // return phySystem->GetAllCollisionInfo(); + //} - SHLOGV_WARNING("Failed to get collision events. Empty vector returned instead."); + //SHLOGV_WARNING("Failed to get collision events. Empty vector returned instead."); return emptyVec; } const std::vector& SHPhysicsSystemInterface::GetTriggerInfo() noexcept { static std::vector emptyVec; - auto phySystem = SHSystemManager::GetSystem(); - if (phySystem) - { - return phySystem->GetAllTriggerInfo(); - } + //auto phySystem = SHSystemManager::GetSystem(); + //if (phySystem) + //{ + // return phySystem->GetAllTriggerInfo(); + //} - SHLOGV_WARNING("Failed to get trigger events. Empty vector returned instead."); + //SHLOGV_WARNING("Failed to get trigger events. Empty vector returned instead."); return emptyVec; } @@ -61,76 +61,4 @@ namespace SHADE SHLOGV_WARNING("Failed to get fixed delta time. 0.0 returned instead."); return 0.0; } - - SHPhysicsRaycastResult SHPhysicsSystemInterface::Raycast(const SHRay& ray, float distance) noexcept - { - auto* physicsSystem = SHSystemManager::GetSystem(); - if (physicsSystem) - { - return physicsSystem->Raycast(ray, distance); - } - - SHLOGV_WARNING("Failed to get the physics system. No ray was casted."); - return SHPhysicsRaycastResult{}; - } - - SHPhysicsRaycastResult SHPhysicsSystemInterface::Linecast(const SHVec3& start, const SHVec3& end) noexcept - { - auto* physicsSystem = SHSystemManager::GetSystem(); - if (physicsSystem) - { - return physicsSystem->Linecast(start, end); - } - - SHLOGV_WARNING("Failed to get the physics system. No ray was casted."); - return SHPhysicsRaycastResult{}; - } - - SHPhysicsRaycastResult SHPhysicsSystemInterface::ColliderRaycast(EntityID eid, const SHRay& ray, float distance) noexcept - { - auto* physicsSystem = SHSystemManager::GetSystem(); - if (physicsSystem) - { - return physicsSystem->ColliderRaycast(eid, ray, distance); - } - - SHLOGV_WARNING("Failed to get the physics system. No ray was casted."); - return SHPhysicsRaycastResult{}; - } - - SHPhysicsRaycastResult SHPhysicsSystemInterface::ColliderRaycast(EntityID eid, int shapeIndex, const SHRay& ray, float distance) noexcept - { - auto* physicsSystem = SHSystemManager::GetSystem(); - if (physicsSystem) - { - return physicsSystem->ColliderRaycast(eid, shapeIndex, ray, distance); - } - - SHLOGV_WARNING("Failed to get the physics system. No ray was casted."); - return SHPhysicsRaycastResult{}; - } - - SHPhysicsRaycastResult SHPhysicsSystemInterface::ColliderLinecast(EntityID eid, const SHVec3& start, const SHVec3& end) noexcept - { - auto* physicsSystem = SHSystemManager::GetSystem(); - if (physicsSystem) - { - return physicsSystem->ColliderLinecast(eid, start, end); - } - - SHLOGV_WARNING("Failed to get the physics system. No ray was casted."); - return SHPhysicsRaycastResult{}; - } - - SHPhysicsRaycastResult SHPhysicsSystemInterface::ColliderLinecast(EntityID eid, int shapeIndex, const SHVec3& start, const SHVec3& end) noexcept - { - auto* physicsSystem = SHSystemManager::GetSystem(); - if (physicsSystem) - { - return physicsSystem->ColliderLinecast(eid, shapeIndex, start, end); - } - - SHLOGV_WARNING("Failed to get the physics system. No ray was casted."); - return SHPhysicsRaycastResult{}; - } } diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.h b/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.h index 0065aee3..0664f367 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.h +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.h @@ -16,6 +16,7 @@ of DigiPen Institute of Technology is prohibited. // Project Headers #include "ECS_Base/Entity/SHEntity.h" +#include "Physics/Collision/SHCollisionInfo.h" namespace SHADE @@ -24,7 +25,6 @@ namespace SHADE /* Forward Declarations */ /*-----------------------------------------------------------------------------------*/ - class SHCollisionInfo; class SHVec3; struct SHRay; struct SHPhysicsRaycastResult; @@ -52,12 +52,5 @@ namespace SHADE [[nodiscard]] static const std::vector& GetCollisionInfo() noexcept; [[nodiscard]] static const std::vector& GetTriggerInfo () noexcept; [[nodiscard]] static double GetFixedDT () noexcept; - - [[nodiscard]] static SHPhysicsRaycastResult Raycast (const SHRay& ray, float distance = std::numeric_limits::infinity()) noexcept; - [[nodiscard]] static SHPhysicsRaycastResult Linecast (const SHVec3& start, const SHVec3& end) noexcept; - [[nodiscard]] static SHPhysicsRaycastResult ColliderRaycast (EntityID eid, const SHRay& ray, float distance = std::numeric_limits::infinity()) noexcept; - [[nodiscard]] static SHPhysicsRaycastResult ColliderRaycast (EntityID eid, int shapeIndex, const SHRay& ray, float distance = std::numeric_limits::infinity()) noexcept; - [[nodiscard]] static SHPhysicsRaycastResult ColliderLinecast (EntityID eid, const SHVec3& start, const SHVec3& end) noexcept; - [[nodiscard]] static SHPhysicsRaycastResult ColliderLinecast (EntityID eid, int shapeIndex, const SHVec3& start, const SHVec3& end) noexcept; }; } diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystemRoutines.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsSystemRoutines.cpp index a5ca957a..0029a8e7 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystemRoutines.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystemRoutines.cpp @@ -55,85 +55,12 @@ namespace SHADE void SHPhysicsSystem::PhysicsPreUpdate::Execute(double) noexcept { auto* physicsSystem = reinterpret_cast(GetSystem()); - - #ifdef SHEDITOR - - // Only Sync on Play. - // Otherwise, Components are only holding data until the world is built on play. - const auto* EDITOR = SHSystemManager::GetSystem(); - if (EDITOR && EDITOR->editorState != SHEditor::State::STOP) - { - physicsSystem->objectManager.UpdateCommands(); - - for (auto& [entityID, physicsObject] : physicsSystem->objectManager.physicsObjects) - { - // Ensure a valid physics Object - if (physicsObject.rp3dBody == nullptr) - continue; - - // Sync active states between SHADE & RP3D - syncRigidBodyActive(entityID, physicsObject); - syncColliderActive(entityID, physicsObject); - - syncOnPlay(entityID, physicsObject); - } - } - else - { - auto& rigidBodyDense = SHComponentManager::GetDense(); - auto& colliderDense = SHComponentManager::GetDense(); - - for (auto& rigidBodyComponent : rigidBodyDense) - { - const auto* TRANSFORM = SHComponentManager::GetComponent_s(rigidBodyComponent.GetEID()); - - if (TRANSFORM && TRANSFORM->HasChanged()) - { - rigidBodyComponent.position = TRANSFORM->GetWorldPosition(); - rigidBodyComponent.orientation = TRANSFORM->GetWorldOrientation(); - } - } - - for (auto& colliderComponent : colliderDense) - { - const auto* TRANSFORM = SHComponentManager::GetComponent_s(colliderComponent.GetEID()); - - if (TRANSFORM && TRANSFORM->HasChanged()) - { - colliderComponent.position = TRANSFORM->GetWorldPosition(); - colliderComponent.orientation = TRANSFORM->GetWorldOrientation(); - colliderComponent.scale = TRANSFORM->GetWorldScale(); - - colliderComponent.RecomputeCollisionShapes(); - } - } - } - - #else - - // Always sync Rigid Body & Collider Components with Physics Objects - // Do not check for an editor here - - physicsSystem->objectManager.UpdateCommands(); - - for (auto& [entityID, physicsObject] : physicsSystem->objectManager.physicsObjects) - { - // Ensure a valid physics Object - if (physicsObject.rp3dBody == nullptr) - continue; - - syncRigidBodyActive(entityID, physicsObject); - syncColliderActive(entityID, physicsObject); - - syncOnPlay(entityID, physicsObject); - } - - #endif } void SHPhysicsSystem::PhysicsFixedUpdate::Execute(double dt) noexcept { auto* physicsSystem = reinterpret_cast(GetSystem()); + auto* scriptingSystem = SHSystemManager::GetSystem(); if (scriptingSystem == nullptr) { @@ -141,20 +68,16 @@ namespace SHADE } const double FIXED_DT = physicsSystem->fixedDT; - // HACK: Clamp DT here to prevent a ridiculous amount of updates. This limits updates from large dt to 2. - // HACK: This should be done by the FRC and not here for predictable behaviour. accumulatedTime += dt; - //testFunction(); - int count = 0; while (accumulatedTime > FIXED_DT) { if (scriptingSystem != nullptr) scriptingSystem->ExecuteFixedUpdates(); - physicsSystem->worldState.world->update(static_cast(FIXED_DT)); + // TODO: Update World accumulatedTime -= FIXED_DT; ++count; @@ -177,236 +100,16 @@ namespace SHADE } // Interpolate transforms for rendering - if (physicsSystem->worldUpdated) - { - for (auto& [entityID, physicsObject] : physicsSystem->objectManager.physicsObjects) - { - auto* transformComponent = SHComponentManager::GetComponent_s(entityID); - auto* rigidBodyComponent = SHComponentManager::GetComponent_s(entityID); - auto* colliderComponent = SHComponentManager::GetComponent_s(entityID); - postUpdateSyncTransforms - ( - physicsObject - , transformComponent - , rigidBodyComponent - , colliderComponent - , physicsSystem->interpolationFactor - ); - } + // Collision & Trigger messages + if (scriptingSystem != nullptr) + scriptingSystem->ExecuteCollisionFunctions(); - // Collision & Trigger messages - if (scriptingSystem != nullptr) - scriptingSystem->ExecuteCollisionFunctions(); - - // Since this function never runs when editor in not in play, execute the function anyway - physicsSystem->collisionListener.CleanContainers(); - } } /*-----------------------------------------------------------------------------------*/ /* Private Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ - - void SHPhysicsSystem::PhysicsPreUpdate::syncRigidBodyActive(EntityID eid, SHPhysicsObject& physicsObject) const noexcept - { - if (!SHComponentManager::HasComponent(eid)) - return; - - const bool IS_ACTIVE_IN_SCENE = SHSceneManager::CheckNodeAndComponentsActive(eid); - const bool IS_RP3D_BODY_ACTIVE = physicsObject.GetRigidBody()->isActive(); - - if (IS_ACTIVE_IN_SCENE != IS_RP3D_BODY_ACTIVE) - physicsObject.GetRigidBody()->setIsActive(IS_ACTIVE_IN_SCENE); - } - - void SHPhysicsSystem::PhysicsPreUpdate::syncColliderActive(EntityID eid, SHPhysicsObject& physicsObject) const noexcept - { - const auto* COLLIDER = SHComponentManager::GetComponent_s(eid); - if (!COLLIDER) - return; - - const bool IS_ACTIVE_IN_SCENE = SHSceneManager::CheckNodeAndComponentsActive(eid); - const bool IS_RP3D_COLLIDER_ACTIVE = physicsObject.collidersActive; - - if (IS_ACTIVE_IN_SCENE != IS_RP3D_COLLIDER_ACTIVE) - { - // HACK: If active state turned off, remove all collision shapes. If turned on, add them back. - auto* physicsSystem = reinterpret_cast(GetSystem()); - - const int NUM_SHAPES = static_cast(COLLIDER->GetCollisionShapes().size()); - if (IS_ACTIVE_IN_SCENE) - { - for (int i = 0; i < NUM_SHAPES; ++i) - physicsSystem->objectManager.AddCollisionShape(eid, i); - } - else - { - for (int i = NUM_SHAPES - 1; i >= 0; --i) - physicsSystem->objectManager.RemoveCollisionShape(eid, i); - } - - physicsObject.collidersActive = IS_ACTIVE_IN_SCENE; - } - } - - void SHPhysicsSystem::PhysicsPreUpdate::syncOnPlay(EntityID eid, SHPhysicsObject& physicsObject) noexcept - { - auto* transformComponent = SHComponentManager::GetComponent_s(eid); - auto* rigidBodyComponent = SHComponentManager::GetComponent_s(eid); - auto* colliderComponent = SHComponentManager::GetComponent_s(eid); - - // Sync transforms & physics components transforms - if (transformComponent && transformComponent->HasChanged()) - { - physicsObject.GetRigidBody()->setIsSleeping(false); - - preUpdateSyncTransform - ( - physicsObject - , transformComponent - , rigidBodyComponent - , colliderComponent - ); - } - - // Sync Rigid Bodies - if (rigidBodyComponent) - physicsObject.SyncRigidBody(*rigidBodyComponent); - - // Sync Colliders - if (colliderComponent) - physicsObject.SyncColliders(*colliderComponent); - } - - void SHPhysicsSystem::PhysicsPreUpdate::preUpdateSyncTransform - ( - SHPhysicsObject& physicsObject - , SHTransformComponent* transformComponent - , SHRigidBodyComponent* rigidBodyComponent - , SHColliderComponent* colliderComponent - ) noexcept - { - if (!transformComponent) - return; - - const SHVec3& WORLD_POS = transformComponent->GetWorldPosition(); - const SHQuaternion& WORLD_ROT = transformComponent->GetWorldOrientation(); - const SHVec3& WORLD_SCL = transformComponent->GetWorldScale(); - - const rp3d::Transform RP3D_TRANSFORM { WORLD_POS, WORLD_ROT }; - physicsObject.GetRigidBody()->setTransform(RP3D_TRANSFORM); - physicsObject.prevTransform = RP3D_TRANSFORM; - - if (rigidBodyComponent && SHSceneManager::CheckNodeAndComponentsActive(physicsObject.entityID)) - { - rigidBodyComponent->position = WORLD_POS; - rigidBodyComponent->orientation = WORLD_ROT; - } - - if (colliderComponent && SHSceneManager::CheckNodeAndComponentsActive(physicsObject.entityID)) - { - colliderComponent->position = WORLD_POS; - colliderComponent->orientation = WORLD_ROT; - colliderComponent->scale = WORLD_SCL; - - colliderComponent->RecomputeCollisionShapes(); - } - } - - void SHPhysicsSystem::PhysicsPostUpdate::postUpdateSyncTransforms - ( - SHPhysicsObject& physicsObject - , SHTransformComponent* transformComponent - , SHRigidBodyComponent* rigidBodyComponent - , SHColliderComponent* colliderComponent - , double interpolationFactor - ) noexcept - { - const rp3d::Transform& CURRENT_TF = physicsObject.GetRigidBody()->getTransform(); - auto renderPos = CURRENT_TF.getPosition(); - auto renderRot = CURRENT_TF.getOrientation(); - - // Cache transforms - if (physicsObject.GetRigidBody()->isActive()) - physicsObject.prevTransform = CURRENT_TF; - - // Sync with rigid bodies - if (rigidBodyComponent && SHSceneManager::CheckNodeAndComponentsActive(physicsObject.entityID)) - { - // Skip static bodies - if (rigidBodyComponent->GetType() == SHRigidBodyComponent::Type::STATIC) - return; - - // Check if transform should be interpolated - if (rigidBodyComponent->IsInterpolating()) - { - // Interpolate transforms between current and predicted next transform - - const rp3d::Transform PREV_TF = physicsObject.prevTransform; - const rp3d::Transform INTERPOLATED_TF = rp3d::Transform::interpolateTransforms(PREV_TF, CURRENT_TF, static_cast(interpolationFactor)); - - renderPos = INTERPOLATED_TF.getPosition(); - renderRot = INTERPOLATED_TF.getOrientation(); - } - - rigidBodyComponent->position = CURRENT_TF.getPosition(); - rigidBodyComponent->orientation = CURRENT_TF.getOrientation(); - - // Sync with colliders - if (colliderComponent && SHSceneManager::CheckNodeAndComponentsActive(physicsObject.entityID)) - { - // Skip colliders without rigidbody components. If any transform was updated, it was done in pre-update. - - colliderComponent->position = CURRENT_TF.getPosition(); - colliderComponent->orientation = CURRENT_TF.getOrientation(); - } - - // Set transform for rendering - if (transformComponent) - { - transformComponent->SetWorldPosition(renderPos); - transformComponent->SetWorldOrientation(renderRot); - } - } - } } // namespace SHADE -///////////////////////////////////////////////////////////////////////////////////////// -void testFunction() -{ - using namespace SHADE; - - // Test movement - const float forceModifier = 25.0f; - EntityID eid = 65538; - - if (SHEntityManager::IsValidEID(eid)) - { - auto* rb = SHComponentManager::GetComponent_s(eid); - if (rb) - { - if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::W)) - rb->AddForce(SHVec3::UnitZ * forceModifier); - - if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::A)) - rb->AddForce(-SHVec3::UnitX * forceModifier); - - if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::S)) - rb->AddForce(-SHVec3::UnitZ * forceModifier); - - if (SHInputManager::GetKey(SHInputManager::SH_KEYCODE::D)) - rb->AddForce(SHVec3::UnitX * forceModifier); - } - } - - // Cast rays - auto* tag = SHCollisionTagMatrix::GetTag(1); - tag->SetLayerState(SHCollisionTag::Layer::_1, false); - tag->SetLayerState(SHCollisionTag::Layer::_2, true); - - SHRay ray { SHVec3{3.0f, 3.5f, 0.0f}, -SHVec3::UnitX }; - auto* physicsSystem = SHSystemManager::GetSystem(); - physicsSystem->Raycast(ray, std::numeric_limits::infinity(), *tag); -} diff --git a/SHADE_Managed/src/Components/Collider.cxx b/SHADE_Managed/src/Components/Collider.cxx index b5f1444f..414e79b7 100644 --- a/SHADE_Managed/src/Components/Collider.cxx +++ b/SHADE_Managed/src/Components/Collider.cxx @@ -19,7 +19,7 @@ of DigiPen Institute of Technology is prohibited. namespace SHADE { - /*---------------------------------------------------------------------------------*/ + /*---------------------------------------------------------------------------------*/ /* CollisionShape - Constructors */ /*---------------------------------------------------------------------------------*/ CollisionShape::CollisionShape(int arrayIdx, Entity attachedEntity) diff --git a/SHADE_Managed/src/Physics/Physics.cxx b/SHADE_Managed/src/Physics/Physics.cxx index 9e2c1413..fc54f527 100644 --- a/SHADE_Managed/src/Physics/Physics.cxx +++ b/SHADE_Managed/src/Physics/Physics.cxx @@ -43,47 +43,47 @@ namespace SHADE RaycastHit Physics::Raycast(Ray ray) { - return Convert::ToCLI(SHPhysicsSystemInterface::Raycast(Convert::ToNative(ray))); + return RaycastHit{}; } RaycastHit Physics::Raycast(Ray ray, float distance) { - return Convert::ToCLI(SHPhysicsSystemInterface::Raycast(Convert::ToNative(ray), distance)); + return RaycastHit{}; } RaycastHit Physics::Linecast(Vector3 start, Vector3 end) { - return Convert::ToCLI(SHPhysicsSystemInterface::Linecast(Convert::ToNative(start), Convert::ToNative(end))); + return RaycastHit{}; } RaycastHit Physics::ColliderRaycast(GameObject object, Ray ray) { - return Convert::ToCLI(SHPhysicsSystemInterface::ColliderRaycast(object.EntityId, Convert::ToNative(ray))); + return RaycastHit{}; } RaycastHit Physics::ColliderRaycast(GameObject object, Ray ray, float distance) { - return Convert::ToCLI(SHPhysicsSystemInterface::ColliderRaycast(object.EntityId, Convert::ToNative(ray), distance)); + return RaycastHit{}; } RaycastHit Physics::ColliderRaycast(GameObject object, int shapeIndex, Ray ray) { - return Convert::ToCLI(SHPhysicsSystemInterface::ColliderRaycast(object.EntityId, shapeIndex, Convert::ToNative(ray))); + return RaycastHit{}; } RaycastHit Physics::ColliderRaycast(GameObject object, int shapeIndex, Ray ray, float distance) { - return Convert::ToCLI(SHPhysicsSystemInterface::ColliderRaycast(object.EntityId, shapeIndex, Convert::ToNative(ray), distance)); + return RaycastHit{}; } RaycastHit Physics::ColliderLineCast(GameObject object, Vector3 start, Vector3 end) { - return Convert::ToCLI(SHPhysicsSystemInterface::ColliderLinecast(object.EntityId, Convert::ToNative(start), Convert::ToNative(end))); + return RaycastHit{}; } RaycastHit Physics::ColliderLineCast(GameObject object, int shapeIndex, Vector3 start, Vector3 end) { - return Convert::ToCLI(SHPhysicsSystemInterface::ColliderLinecast(object.EntityId, shapeIndex, Convert::ToNative(start), Convert::ToNative(end))); + return RaycastHit{}; } /*-----------------------------------------------------------------------------------*/ diff --git a/SHADE_Managed/src/Utility/Convert.cxx b/SHADE_Managed/src/Utility/Convert.cxx index 590a3cf0..1d277274 100644 --- a/SHADE_Managed/src/Utility/Convert.cxx +++ b/SHADE_Managed/src/Utility/Convert.cxx @@ -103,38 +103,6 @@ namespace SHADE /* Physics Conversions */ /*---------------------------------------------------------------------------------*/ - SHPhysicsRaycastResult Convert::ToNative(RaycastHit cli) - { - // This function shouldn't be used anyway, so we leave the entityHit empty. - - SHPhysicsRaycastResult native; - - native.hit = cli.Hit; - native.position = ToNative(cli.Position); - native.normal = ToNative(cli.Normal); - native.distance = cli.Distance; - native.shapeIndex = cli.CollisionShapeIndex; - - return native; - } - - RaycastHit Convert::ToCLI(const SHPhysicsRaycastResult& native) - { - RaycastHit cli; - - cli.Hit = native.hit; - cli.Position = ToCLI(native.position); - cli.Normal = ToCLI(native.normal); - cli.Distance = native.distance; - cli.CollisionShapeIndex = native.shapeIndex; - - cli.Other = SHEntityManager::IsValidEID(native.entityHit) - ? GameObject(native.entityHit) - : System::Nullable(); - - return cli; - } - /*---------------------------------------------------------------------------------*/ /* Handle Conversions */ /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Managed/src/Utility/Convert.hxx b/SHADE_Managed/src/Utility/Convert.hxx index fb373c51..6fa4d935 100644 --- a/SHADE_Managed/src/Utility/Convert.hxx +++ b/SHADE_Managed/src/Utility/Convert.hxx @@ -141,20 +141,6 @@ namespace SHADE /* Physics Conversions */ /*-----------------------------------------------------------------------------*/ - ///

- /// Converts from a managed RaycastHit to a native SHPhysicsRaycastResult - /// - /// The managed RaycastHit to convert from. - /// Native copy of a managed RaycastHit. - static SHPhysicsRaycastResult ToNative(RaycastHit cli); - - /// - /// Converts from native SHPhysicsRaycastResult to a managed RaycastHit. - /// - /// The native SHPhysicsRaycastResult to convert from. - /// Managed copy of a native SHPhysicsRaycastResult. - static RaycastHit ToCLI(const SHPhysicsRaycastResult& native); - /*-----------------------------------------------------------------------------*/ /* Handle Conversions */ /*-----------------------------------------------------------------------------*/ From 6cd203179a6bb7d6951ce4b06993a6b0489e7fd6 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Fri, 2 Dec 2022 19:01:08 +0800 Subject: [PATCH 026/275] Added Rigid Body --- Assets/Application.SHConfig | 2 +- Assets/Scenes/PhysicsSandbox.shade | 1 + Assets/Scenes/PhysicsSandbox.shade.shmeta | 3 + .../src/Physics/Dynamics/SHRigidBody.cpp | 96 +++++ .../src/Physics/Dynamics/SHRigidBody.h | 189 +++++++++ .../src/Physics/Dynamics/SHRigidBody.inl | 375 ++++++++++++++++++ 6 files changed, 665 insertions(+), 1 deletion(-) create mode 100644 Assets/Scenes/PhysicsSandbox.shade create mode 100644 Assets/Scenes/PhysicsSandbox.shade.shmeta create mode 100644 SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp create mode 100644 SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h create mode 100644 SHADE_Engine/src/Physics/Dynamics/SHRigidBody.inl diff --git a/Assets/Application.SHConfig b/Assets/Application.SHConfig index 5673556d..d0fa83df 100644 --- a/Assets/Application.SHConfig +++ b/Assets/Application.SHConfig @@ -1,4 +1,4 @@ Start in Fullscreen: false -Starting Scene ID: 97158628 +Starting Scene ID: 97402985 Window Size: {x: 1920, y: 1080} Window Title: SHADE Engine \ No newline at end of file diff --git a/Assets/Scenes/PhysicsSandbox.shade b/Assets/Scenes/PhysicsSandbox.shade new file mode 100644 index 00000000..0637a088 --- /dev/null +++ b/Assets/Scenes/PhysicsSandbox.shade @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/Assets/Scenes/PhysicsSandbox.shade.shmeta b/Assets/Scenes/PhysicsSandbox.shade.shmeta new file mode 100644 index 00000000..97c00ca6 --- /dev/null +++ b/Assets/Scenes/PhysicsSandbox.shade.shmeta @@ -0,0 +1,3 @@ +Name: PhysicsSandbox +ID: 97402985 +Type: 5 diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp new file mode 100644 index 00000000..ad739847 --- /dev/null +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp @@ -0,0 +1,96 @@ +/**************************************************************************************** + * \file SHRigidBody.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Rigid Body. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHRigidBody.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHRigidBody::SHRigidBody(EntityID eid, Type type) noexcept + : entityID { eid } + , bodyType { type } + , invMass { type == Type::DYNAMIC ? 1.0f : 0.0f } + , linearDrag { type != Type::STATIC ? 0.01f : 0.0f } + , gravityScale { 1.0f } + , flags { 0U } + { + // Set default flags + flags |= 1U << 0; // Body is active + flags |= 1U << 2; // Sleeping is enabled + flags |= 1U << 3; // Gravity is enabled + + // TODO: Compute inertia if body is dynamic + } + + SHRigidBody::SHRigidBody(const SHRigidBody& rhs) noexcept + : entityID { rhs.entityID } + , bodyType { rhs.bodyType } + , invMass { rhs.invMass } + , linearDrag { rhs.linearDrag } + , gravityScale { rhs.gravityScale } + , flags { rhs.flags } + { + // All other properties are defaulted to 0 to not carry over any potential errors / invalid values. + } + + SHRigidBody::SHRigidBody(SHRigidBody&& rhs) noexcept + : entityID { rhs.entityID } + , bodyType { rhs.bodyType } + , invMass { rhs.invMass } + , linearDrag { rhs.linearDrag } + , gravityScale { rhs.gravityScale } + , flags { rhs.flags } + { + // All other properties are defaulted to 0 to not carry over any potential errors / invalid values. + } + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHRigidBody& SHRigidBody::operator=(const SHRigidBody& rhs) noexcept + { + // Handle self assignment + if (this == &rhs) + return *this; + + entityID = rhs.entityID; + bodyType = rhs.bodyType; + invMass = rhs.invMass; + linearDrag = rhs.linearDrag; + gravityScale = rhs.gravityScale; + flags = rhs.flags; + + // All other properties are defaulted to 0 to not carry over any potential errors / invalid values. + + return *this; + } + + SHRigidBody& SHRigidBody::operator=(SHRigidBody&& rhs) noexcept + { + entityID = rhs.entityID; + bodyType = rhs.bodyType; + invMass = rhs.invMass; + linearDrag = rhs.linearDrag; + gravityScale = rhs.gravityScale; + flags = rhs.flags; + + // All other properties are defaulted to 0 to not carry over any potential errors / invalid values. + + return *this; + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h new file mode 100644 index 00000000..598ed3aa --- /dev/null +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h @@ -0,0 +1,189 @@ +/**************************************************************************************** + * \file SHRigidBody.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Rigid Body. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "ECS_Base/SHECSMacros.h" +#include "Math/Vector/SHVec3.h" + +namespace SHADE +{ + /** + * @brief + * Encapsulates a Rigid Body used in Physics Simulations + */ + class SHRigidBody + { + private: + /*-----------------------------------------------------------------------------------*/ + /* Friends */ + /*-----------------------------------------------------------------------------------*/ + + friend class SHRigidBodyComponent; + + public: + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + enum class Type + { + STATIC // Immovable body with infinite mass + , KINEMATIC // Only movable by setting velocity, unaffected by forces. Has infinite mass. + , DYNAMIC // Affected by forces. + }; + + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*-----------------------------------------------------------------------------------*/ + + SHRigidBody (EntityID eid, Type type) noexcept; + SHRigidBody (const SHRigidBody& rhs) noexcept; + SHRigidBody (SHRigidBody&& rhs) noexcept; + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*-----------------------------------------------------------------------------------*/ + + SHRigidBody& operator= (const SHRigidBody& rhs) noexcept; + SHRigidBody& operator= (SHRigidBody&& rhs) noexcept; + + /*-----------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*-----------------------------------------------------------------------------------*/ + + [[nodiscard]] Type GetType () const noexcept; + + [[nodiscard]] float GetMass () const noexcept; + [[nodiscard]] float GetLinearDrag () const noexcept; + + [[nodiscard]] float GetGravityScale () const noexcept; + + [[nodiscard]] const SHVec3& GetAccumulatedForce () const noexcept; + [[nodiscard]] const SHVec3& GetLinearVelocity () const noexcept; + [[nodiscard]] const SHVec3& GetAngularVelocity () const noexcept; + + // Flags + + [[nodiscard]] bool IsActive () const noexcept; + [[nodiscard]] bool IsSleeping () const noexcept; + [[nodiscard]] bool IsSleepingEnabled () const noexcept; + [[nodiscard]] bool IsGravityEnabled () const noexcept; + [[nodiscard]] bool IsAutoMassEnabled () const noexcept; + [[nodiscard]] bool GetFreezePositionX () const noexcept; + [[nodiscard]] bool GetFreezePositionY () const noexcept; + [[nodiscard]] bool GetFreezePositionZ () const noexcept; + [[nodiscard]] bool GetFreezeRotationX () const noexcept; + [[nodiscard]] bool GetFreezeRotationY () const noexcept; + [[nodiscard]] bool GetFreezeRotationZ () const noexcept; + + /*-----------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*-----------------------------------------------------------------------------------*/ + + /** + * @brief + * Changing the type from non-Dynamic to Dynamic will set the default mass. + */ + void SetType (Type newType) noexcept; + + /** + * @brief + * Mass is only modifiable for Dynamic bodies. + * @param newMass + * The new mass to set. Values below 0 will be ignored. + */ + void SetMass (float newMass) noexcept; + + /** + * @brief + * Drag is only modifiable for non-Static bodies. + * @param newLinearDrag + * The new drag to set. Values below 0 will be ignored. + */ + void SetLinearDrag (float newLinearDrag) noexcept; + + + void SetGravityScale (float newGravityScale) noexcept; + + void SetLinearVelocity (const SHVec3& newLinearVelocity) noexcept; + + // Flags + + void SetIsActive (bool isActive) noexcept; + void SetIsSleeping (bool isSleeping) noexcept; + void SetSleepingEnabled (bool enableSleeping) noexcept; + void SetGravityEnabled (bool enableGravity) noexcept; + void SetAutoMassEnabled (bool enableAutoMass) noexcept; + void SetFreezePositionX (bool freezePositionX) noexcept; + void SetFreezePositionY (bool freezePositionY) noexcept; + void SetFreezePositionZ (bool freezePositionZ) noexcept; + void SetFreezeRotationX (bool freezeRotationX) noexcept; + void SetFreezeRotationY (bool freezeRotationY) noexcept; + void SetFreezeRotationZ (bool freezeRotationZ) noexcept; + + /*-----------------------------------------------------------------------------------*/ + /* Member Functions */ + /*-----------------------------------------------------------------------------------*/ + + /** + * @brief + * Adds a force to the body with an offset from it's center of mass. + * @param force + * The force to add to the body. + * @param pos + * The position from the center of mass to offset the force. Defaults to zero. + */ + void AddForce (const SHVec3& force, const SHVec3& pos = SHVec3::Zero) noexcept; + + /** + * @brief + * Adds an impulse to the body with an offset from it's center of mass. + * @param impulse + * The impulse to add to the body. + * @param pos + * The position from the center of mass to offset the impulse. Defaults to zero. + */ + void AddImpulse (const SHVec3& impulse, const SHVec3& pos = SHVec3::Zero) noexcept; + + /** + * @brief + * Removes all the forces from the body. + */ + void ClearForces () noexcept; + + private: + /*-----------------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------------*/ + + // The entityID here is only meant for linking with the actual component in the engine. + EntityID entityID; + + Type bodyType; + + float invMass; + float linearDrag; + + float gravityScale; + + SHVec3 accumulatedForce; + + SHVec3 linearVelocity; + SHVec3 angularVelocity; + + // aZ aY aX pZ pY pX 0 0 0 0 0 autoMass enableGravity enableSleeping sleeping active + uint16_t flags; + }; + +} // namespace SHADE + +#include "SHRigidBody.inl" diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.inl b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.inl new file mode 100644 index 00000000..28f98d12 --- /dev/null +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.inl @@ -0,0 +1,375 @@ +/**************************************************************************************** + * \file SHRigidBody.inl + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for inlined functions of a Rigid Body. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Primary Header +#include "SHRigidBody.h" + +// Project Headers +#include "Math/SHMathHelpers.h" +#include "Tools/Logger/SHLogger.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Getter Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + inline SHRigidBody::Type SHRigidBody::GetType() const noexcept + { + return bodyType; + } + + inline float SHRigidBody::GetMass() const noexcept + { + return 1.0f/ invMass; + } + + inline float SHRigidBody::GetLinearDrag() const noexcept + { + return linearDrag; + } + + inline float SHRigidBody::GetGravityScale() const noexcept + { + return gravityScale; + } + + inline const SHVec3& SHRigidBody::GetAccumulatedForce() const noexcept + { + return accumulatedForce; + } + + inline const SHVec3& SHRigidBody::GetLinearVelocity() const noexcept + { + return linearVelocity; + } + + inline const SHVec3& SHRigidBody::GetAngularVelocity() const noexcept + { + return angularVelocity; + } + + // Flags + + inline bool SHRigidBody::IsActive() const noexcept + { + static constexpr unsigned int FLAG_POS = 0; + return flags & (1U << FLAG_POS); + } + + inline bool SHRigidBody::IsSleeping() const noexcept + { + static constexpr unsigned int FLAG_POS = 1; + return flags & (1U << FLAG_POS); + } + + inline bool SHRigidBody::IsSleepingEnabled() const noexcept + { + static constexpr unsigned int FLAG_POS = 2; + return flags & (1U << FLAG_POS); + } + + inline bool SHRigidBody::IsGravityEnabled() const noexcept + { + static constexpr unsigned int FLAG_POS = 3; + return flags & (1U << FLAG_POS); + } + + inline bool SHRigidBody::IsAutoMassEnabled() const noexcept + { + static constexpr unsigned int FLAG_POS = 4; + return flags & (1U << FLAG_POS); + } + + inline bool SHRigidBody::GetFreezePositionX() const noexcept + { + static constexpr unsigned int FLAG_POS = 10; + return flags & (1U << FLAG_POS); + } + + inline bool SHRigidBody::GetFreezePositionY() const noexcept + { + static constexpr unsigned int FLAG_POS = 11; + return flags & (1U << FLAG_POS); + } + + inline bool SHRigidBody::GetFreezePositionZ() const noexcept + { + static constexpr unsigned int FLAG_POS = 12; + return flags & (1U << FLAG_POS); + } + + inline bool SHRigidBody::GetFreezeRotationX() const noexcept + { + static constexpr unsigned int FLAG_POS = 13; + return flags & (1U << FLAG_POS); + } + + inline bool SHRigidBody::GetFreezeRotationY() const noexcept + { + static constexpr unsigned int FLAG_POS = 14; + return flags & (1U << FLAG_POS); + } + + inline bool SHRigidBody::GetFreezeRotationZ() const noexcept + { + static constexpr unsigned int FLAG_POS = 15; + return flags & (1U << FLAG_POS); + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + inline void SHRigidBody::SetType(Type newType) noexcept + { + if (newType == bodyType) + return; + + bodyType = newType; + invMass = newType == Type::DYNAMIC ? 1.0f : 0.0f; + } + + inline void SHRigidBody::SetMass(float newMass) noexcept + { + if (bodyType != Type::DYNAMIC) + { + SHLOG_WARNING("Cannot set mass of a non-Dynamic Body {}", entityID) + return; + } + + if (newMass < 0.0f) + { + SHLOG_WARNING("Cannot set mass below 0. Object {}'s mass will remain unchanged.", entityID) + return; + } + + invMass = 1.0f / newMass; + + // TODO: Recompute inertia tensor + } + + inline void SHRigidBody::SetLinearDrag(float newLinearDrag) noexcept + { + if (bodyType == Type::STATIC) + { + SHLOG_WARNING("Cannot set linear drag of a Static Body {}", entityID) + return; + } + + if (newLinearDrag < 0.0f) + { + SHLOG_WARNING("Cannot set drag below 0. Object {}'s linear drag will remain unchanged.", entityID) + return; + } + + linearDrag = newLinearDrag; + } + + inline void SHRigidBody::SetGravityScale(float newGravityScale) noexcept + { + gravityScale = newGravityScale; + } + + inline void SHRigidBody::SetLinearVelocity(const SHVec3& newLinearVelocity) noexcept + { + linearVelocity = newLinearVelocity; + } + + inline void SHRigidBody::SetIsActive(bool isActive) noexcept + { + static constexpr unsigned int FLAG_POS = 0; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + isActive ? flags |= VALUE : flags &= ~VALUE; + } + + inline void SHRigidBody::SetIsSleeping(bool isSleeping) noexcept + { + static constexpr unsigned int FLAG_POS = 1; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + isSleeping ? flags |= VALUE : flags &= ~VALUE; + } + + inline void SHRigidBody::SetSleepingEnabled(bool enableSleeping) noexcept + { + static constexpr unsigned int FLAG_POS = 2; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + if (enableSleeping) + { + flags |= VALUE; + } + else + { + flags &= ~VALUE; + // Wake the body + SetIsSleeping(false); + } + } + + inline void SHRigidBody::SetGravityEnabled(bool enableGravity) noexcept + { + static constexpr unsigned int FLAG_POS = 3; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + enableGravity ? flags |= VALUE : flags &= ~VALUE; + } + + inline void SHRigidBody::SetAutoMassEnabled(bool enableAutoMass) noexcept + { + static constexpr unsigned int FLAG_POS = 4; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + if (enableAutoMass) + { + flags |= VALUE; + // TODO: Compute mass based on collider geometry + } + else + { + flags &= ~VALUE; + // Use default mass of 1 + invMass = 1.0f; + } + } + + inline void SHRigidBody::SetFreezePositionX(bool freezePositionX) noexcept + { + static constexpr unsigned int FLAG_POS = 10; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + if (freezePositionX) + { + flags |= VALUE; + // Reset linear velocity along X-axis + linearVelocity.x = 0.0f; + } + else + { + flags &= ~VALUE; + } + } + + inline void SHRigidBody::SetFreezePositionY(bool freezePositionY) noexcept + { + static constexpr unsigned int FLAG_POS = 11; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + if (freezePositionY) + { + flags |= VALUE; + // Reset linear velocity along Y-axis + linearVelocity.y = 0.0f; + } + else + { + flags &= ~VALUE; + } + } + + inline void SHRigidBody::SetFreezePositionZ(bool freezePositionZ) noexcept + { + static constexpr unsigned int FLAG_POS = 12; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + if (freezePositionZ) + { + flags |= VALUE; + // Reset linear velocity along Z-axis + linearVelocity.z = 0.0f; + } + else + { + flags &= ~VALUE; + } + } + + inline void SHRigidBody::SetFreezeRotationX(bool freezeRotationX) noexcept + { + static constexpr unsigned int FLAG_POS = 13; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + if (freezeRotationX) + { + flags |= VALUE; + // Reset angular velocity along X-axis + angularVelocity.x = 0.0f; + } + else + { + flags &= ~VALUE; + } + } + + inline void SHRigidBody::SetFreezeRotationY(bool freezeRotationY) noexcept + { + static constexpr unsigned int FLAG_POS = 14; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + if (freezeRotationY) + { + flags |= VALUE; + // Reset angular velocity along Y-axis + angularVelocity.y = 0.0f; + } + else + { + flags &= ~VALUE; + } + } + + inline void SHRigidBody::SetFreezeRotationZ(bool freezeRotationZ) noexcept + { + static constexpr unsigned int FLAG_POS = 15; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + if (freezeRotationZ) + { + flags |= VALUE; + // Reset angular velocity along Z-axis + angularVelocity.z = 0.0f; + } + else + { + flags &= ~VALUE; + } + } + + /*-----------------------------------------------------------------------------------*/ + /* Member Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + inline void SHRigidBody::AddForce(const SHVec3& force, const SHVec3& pos) noexcept + { + if (bodyType != Type::DYNAMIC) + return; + + accumulatedForce += force; + // Compute torque when force is offset + } + + inline void SHRigidBody::AddImpulse(const SHVec3& impulse, const SHVec3& pos) noexcept + { + if (bodyType != Type::DYNAMIC) + return; + + linearVelocity += impulse * invMass; + } + + inline void SHRigidBody::ClearForces() noexcept + { + accumulatedForce = SHVec3::Zero; + } + +} // namespace SHADE From ca45a12186f97621bfa4c91e1e76313bd7eda6ae Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Sun, 4 Dec 2022 17:31:22 +0800 Subject: [PATCH 027/275] Restructured Physics Systems & Interfaces --- Assets/Scenes/PhysicsSandbox.shade | 29 +- .../src/Application/SBApplication.cpp | 2 +- SHADE_Application/src/Scenes/SBMainScene.cpp | 9 +- SHADE_Application/src/Scenes/SBTestScene.cpp | 2 +- .../Inspector/SHEditorComponentView.hpp | 4 +- .../Inspector/SHEditorInspector.cpp | 2 +- SHADE_Engine/src/Events/SHEventDefines.h | 12 +- .../src/Physics/Collision/SHCollisionInfo.h | 2 +- .../src/Physics/Dynamics/SHRigidBody.cpp | 356 +++++++++++++++++ .../src/Physics/Dynamics/SHRigidBody.h | 16 +- .../src/Physics/Dynamics/SHRigidBody.inl | 375 ------------------ .../PhysicsObject/SHPhysicsObject.cpp | 74 ++++ .../Interface/PhysicsObject/SHPhysicsObject.h | 52 +++ .../PhysicsObject/SHPhysicsObjectManager.cpp | 138 +++++++ .../PhysicsObject/SHPhysicsObjectManager.h | 125 ++++++ .../SHRigidBodyComponent.cpp | 210 +++++++--- .../SHRigidBodyComponent.h | 35 +- SHADE_Engine/src/Physics/SHPhysicsWorld.cpp | 44 -- SHADE_Engine/src/Physics/SHPhysicsWorld.h | 11 - .../SHPhysicsPostUpdateRoutine.cpp} | 73 +--- .../Routines/SHPhysicsPreUpdateRoutine.cpp | 62 +++ .../Routines/SHPhysicsUpdateRoutine.cpp | 62 +++ .../src/Physics/System/SHPhysicsSystem.cpp | 160 +++++++- .../src/Physics/System/SHPhysicsSystem.h | 125 +++--- .../src/Serialization/SHSerialization.cpp | 2 +- SHADE_Managed/src/Components/RigidBody.cxx | 2 +- SHADE_Managed/src/Components/RigidBody.hxx | 2 +- SHADE_Managed/src/Engine/ECS.cxx | 2 +- 28 files changed, 1333 insertions(+), 655 deletions(-) delete mode 100644 SHADE_Engine/src/Physics/Dynamics/SHRigidBody.inl create mode 100644 SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.cpp create mode 100644 SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.h create mode 100644 SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.cpp create mode 100644 SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.h rename SHADE_Engine/src/Physics/Interface/{ => RigidBodyComponent}/SHRigidBodyComponent.cpp (59%) rename SHADE_Engine/src/Physics/Interface/{ => RigidBodyComponent}/SHRigidBodyComponent.h (88%) rename SHADE_Engine/src/Physics/System/{SHPhysicsSystemRoutines.cpp => Routines/SHPhysicsPostUpdateRoutine.cpp} (50%) create mode 100644 SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp create mode 100644 SHADE_Engine/src/Physics/System/Routines/SHPhysicsUpdateRoutine.cpp diff --git a/Assets/Scenes/PhysicsSandbox.shade b/Assets/Scenes/PhysicsSandbox.shade index 0637a088..a0b3c0c2 100644 --- a/Assets/Scenes/PhysicsSandbox.shade +++ b/Assets/Scenes/PhysicsSandbox.shade @@ -1 +1,28 @@ -[] \ No newline at end of file +- EID: 0 + Name: Default + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 0, y: 1.77475965, z: 0} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + RigidBody Component: + Type: Static + Auto Mass: false + Mass: 1 + Drag: 0 + Angular Drag: 0 + Use Gravity: true + Gravity Scale: 1 + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Scripts: ~ \ No newline at end of file diff --git a/SHADE_Application/src/Application/SBApplication.cpp b/SHADE_Application/src/Application/SBApplication.cpp index ce0a939c..a58839c5 100644 --- a/SHADE_Application/src/Application/SBApplication.cpp +++ b/SHADE_Application/src/Application/SBApplication.cpp @@ -109,7 +109,7 @@ namespace Sandbox SHSystemManager::RegisterRoutine(); SHSystemManager::RegisterRoutine(); - SHSystemManager::RegisterRoutine(); + SHSystemManager::RegisterRoutine(); SHSystemManager::RegisterRoutine(); SHSystemManager::RegisterRoutine(); diff --git a/SHADE_Application/src/Scenes/SBMainScene.cpp b/SHADE_Application/src/Scenes/SBMainScene.cpp index 55ce32b0..6c875518 100644 --- a/SHADE_Application/src/Scenes/SBMainScene.cpp +++ b/SHADE_Application/src/Scenes/SBMainScene.cpp @@ -11,7 +11,7 @@ #include "Scripting/SHScriptEngine.h" #include "Math/Transform/SHTransformComponent.h" #include "Graphics/MiddleEnd/Interface/SHMaterialInstance.h" -#include "Physics/Interface/SHRigidBodyComponent.h" +#include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" #include "Physics/Interface/SHColliderComponent.h" #include "Graphics/MiddleEnd/Lights/SHLightComponent.h" #include "Graphics/MiddleEnd/TextRendering/SHTextRenderableComponent.h" @@ -44,13 +44,6 @@ namespace Sandbox { sceneName = SHSerialization::DeserializeSceneFromFile(sceneAssetID); - auto* physicsSystem = SHSystemManager::GetSystem(); - if (!physicsSystem) - { - SHLOGV_CRITICAL("Failed to get the physics system for building the scene!") - return; - } - /*-----------------------------------------------------------------------*/ /* TESTING CODE */ /*-----------------------------------------------------------------------*/ diff --git a/SHADE_Application/src/Scenes/SBTestScene.cpp b/SHADE_Application/src/Scenes/SBTestScene.cpp index bcc7f09d..9e3fa8ab 100644 --- a/SHADE_Application/src/Scenes/SBTestScene.cpp +++ b/SHADE_Application/src/Scenes/SBTestScene.cpp @@ -10,7 +10,7 @@ #include "Scripting/SHScriptEngine.h" #include "Math/Transform/SHTransformComponent.h" #include "Graphics/MiddleEnd/Interface/SHMaterialInstance.h" -#include "Physics/Interface/SHRigidBodyComponent.h" +#include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" #include "Physics/Interface/SHColliderComponent.h" #include "Graphics/MiddleEnd/Lights/SHLightComponent.h" diff --git a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp index f70ab889..02614631 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp +++ b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp @@ -251,7 +251,7 @@ namespace SHADE if(rbType == SHRigidBodyComponent::Type::DYNAMIC) //Dynamic only fields { - SHEditorWidgets::CheckBox("Use Gravity", [component]{return component->IsGravityEnabled();}, [component](bool const& value){component->SetGravityEnabled(value);}, "Gravity"); + SHEditorWidgets::CheckBox("Use Gravity", [component]{return component->IsGravityEnabled();}, [component](bool const& value){component->SetIsGravityEnabled(value);}, "Gravity"); //SHEditorWidgets::DragFloat("Mass", [component] {return component->GetMass(); }, [component](float const& value) {component->SetMass(value); }, "Mass"); } if (rbType == SHRigidBodyComponent::Type::DYNAMIC || rbType == SHRigidBodyComponent::Type::KINEMATIC) //Dynamic or Kinematic only fields @@ -296,7 +296,7 @@ namespace SHADE { SHEditorWidgets::DragVec3("Force", { "X", "Y", "Z" }, [component] {return component->GetForce(); }, [](SHVec3 const& value) {}, false, "Force", 0.1f, "%.3f", 0.0f, 0.0f, ImGuiSliderFlags_ReadOnly); SHEditorWidgets::DragVec3("Torque", { "X", "Y", "Z" }, [component] {return component->GetTorque(); }, [](SHVec3 const& value) {}, false, "Torque", 0.1f, "%.3f", 0.0f, 0.0f, ImGuiSliderFlags_ReadOnly); - SHEditorWidgets::CheckBox("Is Asleep", [component] {return component->GetIsSleeping(); }, [](bool value) {}, "If the Rigid Body is asleep"); + SHEditorWidgets::CheckBox("Is Asleep", [component] {return component->IsSleeping(); }, [](bool value) {}, "If the Rigid Body is asleep"); } } diff --git a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorInspector.cpp b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorInspector.cpp index 83647da7..f0d3e3e5 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorInspector.cpp +++ b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorInspector.cpp @@ -14,7 +14,7 @@ #include "Scripting/SHScriptEngine.h" #include "ECS_Base/Managers/SHSystemManager.h" -#include "Physics/Interface/SHRigidBodyComponent.h" +#include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" #include "Physics/Interface/SHColliderComponent.h" #include "Camera/SHCameraComponent.h" #include "Camera/SHCameraArmComponent.h" diff --git a/SHADE_Engine/src/Events/SHEventDefines.h b/SHADE_Engine/src/Events/SHEventDefines.h index d7bbf5f0..7bf82cd5 100644 --- a/SHADE_Engine/src/Events/SHEventDefines.h +++ b/SHADE_Engine/src/Events/SHEventDefines.h @@ -13,9 +13,11 @@ constexpr SHEventIdentifier SH_COMPONENT_REMOVED_EVENT { 4 }; constexpr SHEventIdentifier SH_SCENEGRAPH_CHANGE_PARENT_EVENT { 5 }; constexpr SHEventIdentifier SH_SCENEGRAPH_ADD_CHILD_EVENT { 6 }; constexpr SHEventIdentifier SH_SCENEGRAPH_REMOVE_CHILD_EVENT { 7 }; -constexpr SHEventIdentifier SH_PHYSICS_COLLIDER_ADDED_EVENT { 8 }; -constexpr SHEventIdentifier SH_PHYSICS_COLLIDER_REMOVED_EVENT { 9 }; -constexpr SHEventIdentifier SH_EDITOR_ON_PLAY_EVENT { 10 }; -constexpr SHEventIdentifier SH_EDITOR_ON_PAUSE_EVENT { 11 }; -constexpr SHEventIdentifier SH_EDITOR_ON_STOP_EVENT { 12 }; +constexpr SHEventIdentifier SH_SCENE_ON_INIT_EVENT { 8 }; +constexpr SHEventIdentifier SH_SCENE_ON_EXIT_EVENT { 9 }; +constexpr SHEventIdentifier SH_PHYSICS_COLLIDER_ADDED_EVENT { 10 }; +constexpr SHEventIdentifier SH_PHYSICS_COLLIDER_REMOVED_EVENT { 11 }; +constexpr SHEventIdentifier SH_EDITOR_ON_PLAY_EVENT { 12 }; +constexpr SHEventIdentifier SH_EDITOR_ON_PAUSE_EVENT { 13 }; +constexpr SHEventIdentifier SH_EDITOR_ON_STOP_EVENT { 14 }; diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.h b/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.h index d2dad647..dfcf4f22 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.h +++ b/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.h @@ -12,7 +12,7 @@ // Project Headers #include "Physics/Interface/SHColliderComponent.h" -#include "Physics/Interface/SHRigidBodyComponent.h" +#include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" namespace SHADE diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp index ad739847..1867636c 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp @@ -13,6 +13,9 @@ // Primary Header #include "SHRigidBody.h" +// Project Headers +#include "Tools/Logger/SHLogger.h" + namespace SHADE { /*-----------------------------------------------------------------------------------*/ @@ -93,4 +96,357 @@ namespace SHADE return *this; } + /*-----------------------------------------------------------------------------------*/ + /* Getter Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHRigidBody::Type SHRigidBody::GetType() const noexcept + { + return bodyType; + } + + float SHRigidBody::GetMass() const noexcept + { + return 1.0f/ invMass; + } + + float SHRigidBody::GetLinearDrag() const noexcept + { + return linearDrag; + } + + float SHRigidBody::GetGravityScale() const noexcept + { + return gravityScale; + } + + const SHVec3& SHRigidBody::GetAccumulatedForce() const noexcept + { + return accumulatedForce; + } + + const SHVec3& SHRigidBody::GetLinearVelocity() const noexcept + { + return linearVelocity; + } + + const SHVec3& SHRigidBody::GetAngularVelocity() const noexcept + { + return angularVelocity; + } + + // Flags + + bool SHRigidBody::IsActive() const noexcept + { + static constexpr unsigned int FLAG_POS = 0; + return flags & (1U << FLAG_POS); + } + + bool SHRigidBody::IsSleeping() const noexcept + { + static constexpr unsigned int FLAG_POS = 1; + return flags & (1U << FLAG_POS); + } + + bool SHRigidBody::IsSleepingEnabled() const noexcept + { + static constexpr unsigned int FLAG_POS = 2; + return flags & (1U << FLAG_POS); + } + + bool SHRigidBody::IsGravityEnabled() const noexcept + { + static constexpr unsigned int FLAG_POS = 3; + return flags & (1U << FLAG_POS); + } + + bool SHRigidBody::IsAutoMassEnabled() const noexcept + { + static constexpr unsigned int FLAG_POS = 4; + return flags & (1U << FLAG_POS); + } + + bool SHRigidBody::GetFreezePositionX() const noexcept + { + static constexpr unsigned int FLAG_POS = 10; + return flags & (1U << FLAG_POS); + } + + bool SHRigidBody::GetFreezePositionY() const noexcept + { + static constexpr unsigned int FLAG_POS = 11; + return flags & (1U << FLAG_POS); + } + + bool SHRigidBody::GetFreezePositionZ() const noexcept + { + static constexpr unsigned int FLAG_POS = 12; + return flags & (1U << FLAG_POS); + } + + bool SHRigidBody::GetFreezeRotationX() const noexcept + { + static constexpr unsigned int FLAG_POS = 13; + return flags & (1U << FLAG_POS); + } + + bool SHRigidBody::GetFreezeRotationY() const noexcept + { + static constexpr unsigned int FLAG_POS = 14; + return flags & (1U << FLAG_POS); + } + + bool SHRigidBody::GetFreezeRotationZ() const noexcept + { + static constexpr unsigned int FLAG_POS = 15; + return flags & (1U << FLAG_POS); + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHRigidBody::SetType(Type newType) noexcept + { + if (newType == bodyType) + return; + + bodyType = newType; + invMass = newType == Type::DYNAMIC ? 1.0f : 0.0f; + } + + void SHRigidBody::SetMass(float newMass) noexcept + { + if (bodyType != Type::DYNAMIC) + { + SHLOG_WARNING("Cannot set mass of a non-Dynamic Body {}", entityID) + return; + } + + if (newMass < 0.0f) + { + SHLOG_WARNING("Cannot set mass below 0. Object {}'s mass will remain unchanged.", entityID) + return; + } + + invMass = 1.0f / newMass; + + // TODO: Recompute inertia tensor + } + + void SHRigidBody::SetLinearDrag(float newLinearDrag) noexcept + { + if (bodyType == Type::STATIC) + { + SHLOG_WARNING("Cannot set linear drag of a Static Body {}", entityID) + return; + } + + if (newLinearDrag < 0.0f) + { + SHLOG_WARNING("Cannot set drag below 0. Object {}'s linear drag will remain unchanged.", entityID) + return; + } + + linearDrag = newLinearDrag; + } + + void SHRigidBody::SetGravityScale(float newGravityScale) noexcept + { + gravityScale = newGravityScale; + } + + void SHRigidBody::SetLinearVelocity(const SHVec3& newLinearVelocity) noexcept + { + linearVelocity = newLinearVelocity; + } + + void SHRigidBody::SetIsActive(bool isActive) noexcept + { + static constexpr unsigned int FLAG_POS = 0; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + isActive ? flags |= VALUE : flags &= ~VALUE; + } + + void SHRigidBody::SetIsSleeping(bool isSleeping) noexcept + { + static constexpr unsigned int FLAG_POS = 1; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + isSleeping ? flags |= VALUE : flags &= ~VALUE; + } + + void SHRigidBody::SetSleepingEnabled(bool enableSleeping) noexcept + { + static constexpr unsigned int FLAG_POS = 2; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + if (enableSleeping) + { + flags |= VALUE; + } + else + { + flags &= ~VALUE; + // Wake the body + SetIsSleeping(false); + } + } + + void SHRigidBody::SetGravityEnabled(bool enableGravity) noexcept + { + static constexpr unsigned int FLAG_POS = 3; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + enableGravity ? flags |= VALUE : flags &= ~VALUE; + } + + void SHRigidBody::SetAutoMassEnabled(bool enableAutoMass) noexcept + { + static constexpr unsigned int FLAG_POS = 4; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + if (enableAutoMass) + { + flags |= VALUE; + // TODO: Compute mass based on collider geometry + } + else + { + flags &= ~VALUE; + // Use default mass of 1 + invMass = 1.0f; + } + } + + void SHRigidBody::SetFreezePositionX(bool freezePositionX) noexcept + { + static constexpr unsigned int FLAG_POS = 10; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + if (freezePositionX) + { + flags |= VALUE; + // Reset linear velocity along X-axis + linearVelocity.x = 0.0f; + } + else + { + flags &= ~VALUE; + } + } + + void SHRigidBody::SetFreezePositionY(bool freezePositionY) noexcept + { + static constexpr unsigned int FLAG_POS = 11; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + if (freezePositionY) + { + flags |= VALUE; + // Reset linear velocity along Y-axis + linearVelocity.y = 0.0f; + } + else + { + flags &= ~VALUE; + } + } + + void SHRigidBody::SetFreezePositionZ(bool freezePositionZ) noexcept + { + static constexpr unsigned int FLAG_POS = 12; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + if (freezePositionZ) + { + flags |= VALUE; + // Reset linear velocity along Z-axis + linearVelocity.z = 0.0f; + } + else + { + flags &= ~VALUE; + } + } + + void SHRigidBody::SetFreezeRotationX(bool freezeRotationX) noexcept + { + static constexpr unsigned int FLAG_POS = 13; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + if (freezeRotationX) + { + flags |= VALUE; + // Reset angular velocity along X-axis + angularVelocity.x = 0.0f; + } + else + { + flags &= ~VALUE; + } + } + + void SHRigidBody::SetFreezeRotationY(bool freezeRotationY) noexcept + { + static constexpr unsigned int FLAG_POS = 14; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + if (freezeRotationY) + { + flags |= VALUE; + // Reset angular velocity along Y-axis + angularVelocity.y = 0.0f; + } + else + { + flags &= ~VALUE; + } + } + + void SHRigidBody::SetFreezeRotationZ(bool freezeRotationZ) noexcept + { + static constexpr unsigned int FLAG_POS = 15; + static constexpr uint16_t VALUE = 1U << FLAG_POS; + + if (freezeRotationZ) + { + flags |= VALUE; + // Reset angular velocity along Z-axis + angularVelocity.z = 0.0f; + } + else + { + flags &= ~VALUE; + } + } + + /*-----------------------------------------------------------------------------------*/ + /* Member Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHRigidBody::AddForce(const SHVec3& force, const SHVec3& pos) noexcept + { + if (bodyType != Type::DYNAMIC) + return; + + accumulatedForce += force; + // Compute torque when force is offset + } + + void SHRigidBody::AddImpulse(const SHVec3& impulse, const SHVec3& pos) noexcept + { + if (bodyType != Type::DYNAMIC) + return; + + linearVelocity += impulse * invMass; + } + + void SHRigidBody::ClearForces() noexcept + { + accumulatedForce = SHVec3::Zero; + } + } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h index 598ed3aa..77b29292 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h @@ -13,22 +13,20 @@ // Project Headers #include "ECS_Base/SHECSMacros.h" #include "Math/Vector/SHVec3.h" +#include "SH_API.h" namespace SHADE { + /*-------------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-------------------------------------------------------------------------------------*/ + /** * @brief * Encapsulates a Rigid Body used in Physics Simulations */ - class SHRigidBody + class SH_API SHRigidBody { - private: - /*-----------------------------------------------------------------------------------*/ - /* Friends */ - /*-----------------------------------------------------------------------------------*/ - - friend class SHRigidBodyComponent; - public: /*-----------------------------------------------------------------------------------*/ /* Type Definitions */ @@ -185,5 +183,3 @@ namespace SHADE }; } // namespace SHADE - -#include "SHRigidBody.inl" diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.inl b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.inl deleted file mode 100644 index 28f98d12..00000000 --- a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.inl +++ /dev/null @@ -1,375 +0,0 @@ -/**************************************************************************************** - * \file SHRigidBody.inl - * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Implementation for inlined functions of a Rigid Body. - * - * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or - * disclosure of this file or its contents without the prior written consent - * of DigiPen Institute of Technology is prohibited. -****************************************************************************************/ - -#pragma once - -// Primary Header -#include "SHRigidBody.h" - -// Project Headers -#include "Math/SHMathHelpers.h" -#include "Tools/Logger/SHLogger.h" - -namespace SHADE -{ - /*-----------------------------------------------------------------------------------*/ - /* Getter Functions Definitions */ - /*-----------------------------------------------------------------------------------*/ - - inline SHRigidBody::Type SHRigidBody::GetType() const noexcept - { - return bodyType; - } - - inline float SHRigidBody::GetMass() const noexcept - { - return 1.0f/ invMass; - } - - inline float SHRigidBody::GetLinearDrag() const noexcept - { - return linearDrag; - } - - inline float SHRigidBody::GetGravityScale() const noexcept - { - return gravityScale; - } - - inline const SHVec3& SHRigidBody::GetAccumulatedForce() const noexcept - { - return accumulatedForce; - } - - inline const SHVec3& SHRigidBody::GetLinearVelocity() const noexcept - { - return linearVelocity; - } - - inline const SHVec3& SHRigidBody::GetAngularVelocity() const noexcept - { - return angularVelocity; - } - - // Flags - - inline bool SHRigidBody::IsActive() const noexcept - { - static constexpr unsigned int FLAG_POS = 0; - return flags & (1U << FLAG_POS); - } - - inline bool SHRigidBody::IsSleeping() const noexcept - { - static constexpr unsigned int FLAG_POS = 1; - return flags & (1U << FLAG_POS); - } - - inline bool SHRigidBody::IsSleepingEnabled() const noexcept - { - static constexpr unsigned int FLAG_POS = 2; - return flags & (1U << FLAG_POS); - } - - inline bool SHRigidBody::IsGravityEnabled() const noexcept - { - static constexpr unsigned int FLAG_POS = 3; - return flags & (1U << FLAG_POS); - } - - inline bool SHRigidBody::IsAutoMassEnabled() const noexcept - { - static constexpr unsigned int FLAG_POS = 4; - return flags & (1U << FLAG_POS); - } - - inline bool SHRigidBody::GetFreezePositionX() const noexcept - { - static constexpr unsigned int FLAG_POS = 10; - return flags & (1U << FLAG_POS); - } - - inline bool SHRigidBody::GetFreezePositionY() const noexcept - { - static constexpr unsigned int FLAG_POS = 11; - return flags & (1U << FLAG_POS); - } - - inline bool SHRigidBody::GetFreezePositionZ() const noexcept - { - static constexpr unsigned int FLAG_POS = 12; - return flags & (1U << FLAG_POS); - } - - inline bool SHRigidBody::GetFreezeRotationX() const noexcept - { - static constexpr unsigned int FLAG_POS = 13; - return flags & (1U << FLAG_POS); - } - - inline bool SHRigidBody::GetFreezeRotationY() const noexcept - { - static constexpr unsigned int FLAG_POS = 14; - return flags & (1U << FLAG_POS); - } - - inline bool SHRigidBody::GetFreezeRotationZ() const noexcept - { - static constexpr unsigned int FLAG_POS = 15; - return flags & (1U << FLAG_POS); - } - - /*-----------------------------------------------------------------------------------*/ - /* Setter Functions Definitions */ - /*-----------------------------------------------------------------------------------*/ - - inline void SHRigidBody::SetType(Type newType) noexcept - { - if (newType == bodyType) - return; - - bodyType = newType; - invMass = newType == Type::DYNAMIC ? 1.0f : 0.0f; - } - - inline void SHRigidBody::SetMass(float newMass) noexcept - { - if (bodyType != Type::DYNAMIC) - { - SHLOG_WARNING("Cannot set mass of a non-Dynamic Body {}", entityID) - return; - } - - if (newMass < 0.0f) - { - SHLOG_WARNING("Cannot set mass below 0. Object {}'s mass will remain unchanged.", entityID) - return; - } - - invMass = 1.0f / newMass; - - // TODO: Recompute inertia tensor - } - - inline void SHRigidBody::SetLinearDrag(float newLinearDrag) noexcept - { - if (bodyType == Type::STATIC) - { - SHLOG_WARNING("Cannot set linear drag of a Static Body {}", entityID) - return; - } - - if (newLinearDrag < 0.0f) - { - SHLOG_WARNING("Cannot set drag below 0. Object {}'s linear drag will remain unchanged.", entityID) - return; - } - - linearDrag = newLinearDrag; - } - - inline void SHRigidBody::SetGravityScale(float newGravityScale) noexcept - { - gravityScale = newGravityScale; - } - - inline void SHRigidBody::SetLinearVelocity(const SHVec3& newLinearVelocity) noexcept - { - linearVelocity = newLinearVelocity; - } - - inline void SHRigidBody::SetIsActive(bool isActive) noexcept - { - static constexpr unsigned int FLAG_POS = 0; - static constexpr uint16_t VALUE = 1U << FLAG_POS; - - isActive ? flags |= VALUE : flags &= ~VALUE; - } - - inline void SHRigidBody::SetIsSleeping(bool isSleeping) noexcept - { - static constexpr unsigned int FLAG_POS = 1; - static constexpr uint16_t VALUE = 1U << FLAG_POS; - - isSleeping ? flags |= VALUE : flags &= ~VALUE; - } - - inline void SHRigidBody::SetSleepingEnabled(bool enableSleeping) noexcept - { - static constexpr unsigned int FLAG_POS = 2; - static constexpr uint16_t VALUE = 1U << FLAG_POS; - - if (enableSleeping) - { - flags |= VALUE; - } - else - { - flags &= ~VALUE; - // Wake the body - SetIsSleeping(false); - } - } - - inline void SHRigidBody::SetGravityEnabled(bool enableGravity) noexcept - { - static constexpr unsigned int FLAG_POS = 3; - static constexpr uint16_t VALUE = 1U << FLAG_POS; - - enableGravity ? flags |= VALUE : flags &= ~VALUE; - } - - inline void SHRigidBody::SetAutoMassEnabled(bool enableAutoMass) noexcept - { - static constexpr unsigned int FLAG_POS = 4; - static constexpr uint16_t VALUE = 1U << FLAG_POS; - - if (enableAutoMass) - { - flags |= VALUE; - // TODO: Compute mass based on collider geometry - } - else - { - flags &= ~VALUE; - // Use default mass of 1 - invMass = 1.0f; - } - } - - inline void SHRigidBody::SetFreezePositionX(bool freezePositionX) noexcept - { - static constexpr unsigned int FLAG_POS = 10; - static constexpr uint16_t VALUE = 1U << FLAG_POS; - - if (freezePositionX) - { - flags |= VALUE; - // Reset linear velocity along X-axis - linearVelocity.x = 0.0f; - } - else - { - flags &= ~VALUE; - } - } - - inline void SHRigidBody::SetFreezePositionY(bool freezePositionY) noexcept - { - static constexpr unsigned int FLAG_POS = 11; - static constexpr uint16_t VALUE = 1U << FLAG_POS; - - if (freezePositionY) - { - flags |= VALUE; - // Reset linear velocity along Y-axis - linearVelocity.y = 0.0f; - } - else - { - flags &= ~VALUE; - } - } - - inline void SHRigidBody::SetFreezePositionZ(bool freezePositionZ) noexcept - { - static constexpr unsigned int FLAG_POS = 12; - static constexpr uint16_t VALUE = 1U << FLAG_POS; - - if (freezePositionZ) - { - flags |= VALUE; - // Reset linear velocity along Z-axis - linearVelocity.z = 0.0f; - } - else - { - flags &= ~VALUE; - } - } - - inline void SHRigidBody::SetFreezeRotationX(bool freezeRotationX) noexcept - { - static constexpr unsigned int FLAG_POS = 13; - static constexpr uint16_t VALUE = 1U << FLAG_POS; - - if (freezeRotationX) - { - flags |= VALUE; - // Reset angular velocity along X-axis - angularVelocity.x = 0.0f; - } - else - { - flags &= ~VALUE; - } - } - - inline void SHRigidBody::SetFreezeRotationY(bool freezeRotationY) noexcept - { - static constexpr unsigned int FLAG_POS = 14; - static constexpr uint16_t VALUE = 1U << FLAG_POS; - - if (freezeRotationY) - { - flags |= VALUE; - // Reset angular velocity along Y-axis - angularVelocity.y = 0.0f; - } - else - { - flags &= ~VALUE; - } - } - - inline void SHRigidBody::SetFreezeRotationZ(bool freezeRotationZ) noexcept - { - static constexpr unsigned int FLAG_POS = 15; - static constexpr uint16_t VALUE = 1U << FLAG_POS; - - if (freezeRotationZ) - { - flags |= VALUE; - // Reset angular velocity along Z-axis - angularVelocity.z = 0.0f; - } - else - { - flags &= ~VALUE; - } - } - - /*-----------------------------------------------------------------------------------*/ - /* Member Function Definitions */ - /*-----------------------------------------------------------------------------------*/ - - inline void SHRigidBody::AddForce(const SHVec3& force, const SHVec3& pos) noexcept - { - if (bodyType != Type::DYNAMIC) - return; - - accumulatedForce += force; - // Compute torque when force is offset - } - - inline void SHRigidBody::AddImpulse(const SHVec3& impulse, const SHVec3& pos) noexcept - { - if (bodyType != Type::DYNAMIC) - return; - - linearVelocity += impulse * invMass; - } - - inline void SHRigidBody::ClearForces() noexcept - { - accumulatedForce = SHVec3::Zero; - } - -} // namespace SHADE diff --git a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.cpp b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.cpp new file mode 100644 index 00000000..44452868 --- /dev/null +++ b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.cpp @@ -0,0 +1,74 @@ +/**************************************************************************************** + * \file SHPhysicsObject.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Physics Object. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHPhysicsObject.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructor & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsObject::SHPhysicsObject(EntityID eid) noexcept + : entityID { eid } + {} + + SHPhysicsObject::SHPhysicsObject(const SHPhysicsObject& rhs) noexcept + : entityID { rhs.entityID } + { + // Perform a deep copy of the components + *rigidBody = *rhs.rigidBody; + } + + SHPhysicsObject::SHPhysicsObject(SHPhysicsObject&& rhs) noexcept + : entityID { rhs.entityID } + { + // Perform a deep copy of the components + *rigidBody = *rhs.rigidBody; + } + + SHPhysicsObject::~SHPhysicsObject() noexcept + { + entityID = MAX_EID; + + delete rigidBody; + } + + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsObject& SHPhysicsObject::operator=(const SHPhysicsObject& rhs) noexcept + { + if (this == &rhs) + return *this; + + entityID = rhs.entityID; + + // Perform a deep copy of the components + *rigidBody = *rhs.rigidBody; + + return *this; + } + + SHPhysicsObject& SHPhysicsObject::operator=(SHPhysicsObject&& rhs) noexcept + { + entityID = rhs.entityID; + + // Perform a deep copy of the components + *rigidBody = *rhs.rigidBody; + + return *this; + } +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.h b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.h new file mode 100644 index 00000000..2be5844d --- /dev/null +++ b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.h @@ -0,0 +1,52 @@ +/**************************************************************************************** + * \file SHPhysicsObject.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Physics Object. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "Physics/Dynamics/SHRigidBody.h" + +namespace SHADE +{ + /*-------------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-------------------------------------------------------------------------------------*/ + + /** + * @brief + * Encapsulates a rigid body and a collider tied to an Entity. + */ + struct SH_API SHPhysicsObject + { + public: + /*-----------------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------------*/ + + EntityID entityID = MAX_EID; + SHRigidBody* rigidBody = nullptr; + + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsObject (EntityID eid) noexcept; + SHPhysicsObject (const SHPhysicsObject& rhs) noexcept; + SHPhysicsObject (SHPhysicsObject&& rhs) noexcept; + ~SHPhysicsObject () noexcept; + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsObject& operator=(const SHPhysicsObject& rhs) noexcept; + SHPhysicsObject& operator=(SHPhysicsObject&& rhs) noexcept; + }; +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.cpp b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.cpp new file mode 100644 index 00000000..dccca97b --- /dev/null +++ b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.cpp @@ -0,0 +1,138 @@ +/**************************************************************************************** + * \file SHPhysicsObjectManager.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Physics Object Manager. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHPhysicsObjectManager.h" + +// Project Headers +#include "Physics/Interface/SHColliderComponent.h" +#include "Physics/Interface/SHCollisionShape.h" +#include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" +#include "Tools/Utilities/SHUtilities.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsObjectManager::EntityObjectMap& SHPhysicsObjectManager::GetPhysicsObjects() noexcept + { + return physicsObjects; + } + + const SHPhysicsObject* SHPhysicsObjectManager::GetPhysicsObject(EntityID entityID) noexcept + { + const auto PHYSICS_OBJECT_ITERATOR = physicsObjects.find(entityID); + if (PHYSICS_OBJECT_ITERATOR == physicsObjects.end()) + { + SHLOG_ERROR("Cannot find physics object for entity {}!", entityID) + return nullptr; + } + + return &PHYSICS_OBJECT_ITERATOR->second; + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Member Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsObjectManager::AddRigidBody(EntityID entityID) noexcept + { + SHPhysicsObject* physicsObject = nullptr; + + const auto PHYSICS_OBJECT_ITERATOR = physicsObjects.find(entityID); + if (PHYSICS_OBJECT_ITERATOR == physicsObjects.end()) + physicsObject = createPhysicsObject(entityID); + else + physicsObject = &PHYSICS_OBJECT_ITERATOR->second; + + // Get the component + auto* rigidBodyComponent = SHComponentManager::GetComponent(entityID); + + // Create a new rigidbody in the physics object + // We assume none has already been made + + physicsObject->rigidBody = new SHRigidBody + { + entityID + , static_cast(rigidBodyComponent->GetType()) + }; + + // Link with the component + rigidBodyComponent->SetRigidBody(physicsObject->rigidBody); + + // TODO: Broadcast event + } + + void SHPhysicsObjectManager::RemoveRigidBody(EntityID entityID) noexcept + { + const auto PHYSICS_OBJECT_ITERATOR = physicsObjects.find(entityID); + if (PHYSICS_OBJECT_ITERATOR != physicsObjects.end()) + { + SHPhysicsObject* physicsObject = &PHYSICS_OBJECT_ITERATOR->second; + + delete physicsObject->rigidBody; + physicsObject->rigidBody = nullptr; + + // Destroy empty physics objects + if (physicsObject->rigidBody == nullptr) + destroyPhysicsObject(entityID); + } + + // TODO: Broadcast event + } + + void SHPhysicsObjectManager::AddCollider(EntityID entityID) noexcept + { + // Link with the component + + // TODO: Broadcast event + } + + void SHPhysicsObjectManager::RemoveCollider(EntityID entityID) noexcept + { + // Unlink with the component + + // TODO: Broadcast event + } + + void SHPhysicsObjectManager::AddCollisionShape(EntityID entityID, uint8_t shapeID) noexcept + { + // Link with the component + + // TODO: Broadcast event + } + + void SHPhysicsObjectManager::RemoveCollisionShape(EntityID entityID, uint8_t shapeID) noexcept + { + // Unlink with the component + + // TODO: Broadcast event + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Member Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsObject* SHPhysicsObjectManager::createPhysicsObject(EntityID entityID) + { + const auto& NEW_OBJECT = physicsObjects.emplace(entityID, SHPhysicsObject{entityID}).first; + return &NEW_OBJECT->second; + } + + void SHPhysicsObjectManager::destroyPhysicsObject(EntityID entityID) + { + physicsObjects.erase(entityID); + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.h b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.h new file mode 100644 index 00000000..17a1d3a1 --- /dev/null +++ b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.h @@ -0,0 +1,125 @@ +/**************************************************************************************** + * \file SHPhysicsObjectManager.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Physics Object Manager. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include + +// Project Headers +#include "SHPhysicsObject.h" + +namespace SHADE +{ + /*-------------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-------------------------------------------------------------------------------------*/ + + /** + * @brief + * Encapsulates a manager for physics objects that links raw physics components with the + * engine's components. + */ + class SHPhysicsObjectManager + { + private: + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + using EntityObjectMap = std::unordered_map; + + public: + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsObjectManager() noexcept = default; + + /*-----------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*-----------------------------------------------------------------------------------*/ + + [[nodiscard]] EntityObjectMap& GetPhysicsObjects () noexcept; + [[nodiscard]] const SHPhysicsObject* GetPhysicsObject (EntityID entityID) noexcept; + + /*-----------------------------------------------------------------------------------*/ + /* Member Functions */ + /*-----------------------------------------------------------------------------------*/ + + /** + * @brief + * Creates a rigid body and links it with the rigid body component. + * @param entityID + * The entity to link the new rigid body to. + */ + void AddRigidBody (EntityID entityID) noexcept; + + /** + * @brief + * Destroys a rigid body and removes the link with the rigid body component. + * @param entityID + * The entity to destroy the rigid body of. + */ + void RemoveRigidBody (EntityID entityID) noexcept; + + /** + * @brief + * Creates a collider and links it with the collider component. + * @param entityID + * The entity to link the new collider to. + */ + void AddCollider (EntityID entityID) noexcept; + + /** + * @brief + * Destroys a collider and removes the link with the collider component. + * @param entityID + * The entity to destroy the collider of. + */ + void RemoveCollider (EntityID entityID) noexcept; + + /** + * @brief + * Creates a collision shape for composite colliders and links it with the + * collider component. + * @param eientityIDd + * The entity to create a collision shape for. + * @param shapeID + * The id of the shape being created. + */ + void AddCollisionShape (EntityID entityID, uint8_t shapeID) noexcept; + + /** + * @brief + * Destroys a collision shape for composite colliders and removes the link with the + * collider component. + * @param entityID + * The entity to destroy the collision shape of. + * @param shapeID + * The id of the shape being destroyed. + */ + void RemoveCollisionShape (EntityID entityID, uint8_t shapeID) noexcept; + + private: + /*-----------------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------------*/ + + EntityObjectMap physicsObjects; + + /*-----------------------------------------------------------------------------------*/ + /* Member Functions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsObject* createPhysicsObject (EntityID entityID); + void destroyPhysicsObject (EntityID entityID); + }; + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp b/SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.cpp similarity index 59% rename from SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp rename to SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.cpp index 6dad9020..5924c612 100644 --- a/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp +++ b/SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.cpp @@ -18,7 +18,7 @@ // Project Headers #include "ECS_Base/Managers/SHSystemManager.h" -#include "Math/SHMathHelpers.h" +#include "Physics/Dynamics/SHRigidBody.h" #include "Physics/System/SHPhysicsSystem.h" namespace SHADE @@ -28,77 +28,127 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ SHRigidBodyComponent::SHRigidBodyComponent() noexcept + : type { Type::STATIC } + , interpolate { true } + , rigidBody { nullptr } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Getter Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHRigidBodyComponent::Type SHRigidBodyComponent::GetType() const noexcept { - + return type; } - /*-----------------------------------------------------------------------------------*/ - /* Getter Function Definitions */ - /*-----------------------------------------------------------------------------------*/ - bool SHRigidBodyComponent::IsGravityEnabled() const noexcept { + if (rigidBody) + return rigidBody->IsGravityEnabled(); + return false; } bool SHRigidBodyComponent::IsAllowedToSleep() const noexcept { + if (rigidBody) + return rigidBody->IsSleepingEnabled(); + return false; } bool SHRigidBodyComponent::IsInterpolating() const noexcept { + return interpolate; + } + + bool SHRigidBodyComponent::IsSleeping() const noexcept + { + if (rigidBody) + return rigidBody->IsSleeping(); + return false; } - bool SHRigidBodyComponent::GetIsSleeping() const noexcept + bool SHRigidBodyComponent::GetAutoMass() const noexcept { - return false; - } + if (rigidBody) + return rigidBody->IsAutoMassEnabled(); - SHRigidBodyComponent::Type SHRigidBodyComponent::GetType() const noexcept - { - return Type::STATIC; + return false; } bool SHRigidBodyComponent::GetFreezePositionX() const noexcept { + if (rigidBody) + return rigidBody->GetFreezePositionX(); + return false; } bool SHRigidBodyComponent::GetFreezePositionY() const noexcept { + if (rigidBody) + return rigidBody->GetFreezePositionY(); + return false; } bool SHRigidBodyComponent::GetFreezePositionZ() const noexcept { + if (rigidBody) + return rigidBody->GetFreezePositionZ(); + return false; } bool SHRigidBodyComponent::GetFreezeRotationX() const noexcept { + if (rigidBody) + return rigidBody->GetFreezeRotationX(); + return false; } bool SHRigidBodyComponent::GetFreezeRotationY() const noexcept { + if (rigidBody) + return rigidBody->GetFreezeRotationY(); + return false; } bool SHRigidBodyComponent::GetFreezeRotationZ() const noexcept { + if (rigidBody) + return rigidBody->GetFreezeRotationZ(); + return false; } - float SHRigidBodyComponent::GetMass() const noexcept + float SHRigidBodyComponent::GetGravityScale() const noexcept { + if (rigidBody) + return rigidBody->GetGravityScale(); + return 0.0f; } + float SHRigidBodyComponent::GetMass() const noexcept + { + if (rigidBody) + return rigidBody->GetMass(); + + return -1.0f; + } + float SHRigidBodyComponent::GetDrag() const noexcept { - return 0.0f; + if (rigidBody) + return rigidBody->GetLinearDrag(); + + return -1.0f; } float SHRigidBodyComponent::GetAngularDrag() const noexcept @@ -108,6 +158,9 @@ namespace SHADE SHVec3 SHRigidBodyComponent::GetForce() const noexcept { + if (rigidBody) + return rigidBody->GetAccumulatedForce(); + return SHVec3::Zero; } @@ -118,6 +171,9 @@ namespace SHADE SHVec3 SHRigidBodyComponent::GetLinearVelocity() const noexcept { + if (rigidBody) + return rigidBody->GetLinearVelocity(); + return SHVec3::Zero; } @@ -127,91 +183,126 @@ namespace SHADE } /*-----------------------------------------------------------------------------------*/ - /* Setter Function Definitions */ + /* Setter Functions Definitions */ /*-----------------------------------------------------------------------------------*/ void SHRigidBodyComponent::SetType(Type newType) noexcept { - + if (newType == type) + return; + + type = newType; + + if (rigidBody) + rigidBody->SetType(static_cast(newType)); } - void SHRigidBodyComponent::SetGravityEnabled(bool enableGravity) noexcept + void SHRigidBodyComponent::SetRigidBody(SHRigidBody* rb) noexcept { - + rigidBody = rb; + } + + void SHRigidBodyComponent::SetIsGravityEnabled(bool enableGravity) noexcept + { + if (rigidBody) + rigidBody->SetGravityEnabled(enableGravity); } void SHRigidBodyComponent::SetIsAllowedToSleep(bool isAllowedToSleep) noexcept { - + if (rigidBody) + rigidBody->SetSleepingEnabled(isAllowedToSleep); + } + + void SHRigidBodyComponent::SetAutoMass(bool autoMass) noexcept + { + if (rigidBody) + rigidBody->SetAutoMassEnabled(autoMass); } void SHRigidBodyComponent::SetFreezePositionX(bool freezePositionX) noexcept { - + if (rigidBody) + rigidBody->SetFreezePositionX(freezePositionX); } void SHRigidBodyComponent::SetFreezePositionY(bool freezePositionY) noexcept { - + if (rigidBody) + rigidBody->SetFreezePositionY(freezePositionY); } void SHRigidBodyComponent::SetFreezePositionZ(bool freezePositionZ) noexcept { - + if (rigidBody) + rigidBody->SetFreezePositionZ(freezePositionZ); } void SHRigidBodyComponent::SetFreezeRotationX(bool freezeRotationX) noexcept { - + if (rigidBody) + rigidBody->SetFreezeRotationX(freezeRotationX); } void SHRigidBodyComponent::SetFreezeRotationY(bool freezeRotationY) noexcept { - + if (rigidBody) + rigidBody->SetFreezeRotationY(freezeRotationY); } void SHRigidBodyComponent::SetFreezeRotationZ(bool freezeRotationZ) noexcept { - + if (rigidBody) + rigidBody->SetFreezeRotationZ(freezeRotationZ); } void SHRigidBodyComponent::SetInterpolate(bool allowInterpolation) noexcept { - + interpolate = allowInterpolation; + } + + void SHRigidBodyComponent::SetGravityScale(float gravityScale) noexcept + { + if (rigidBody) + rigidBody->SetGravityScale(gravityScale); + } + + void SHRigidBodyComponent::SetMass(float newMass) noexcept + { + if (rigidBody) + rigidBody->SetMass(newMass); } void SHRigidBodyComponent::SetDrag(float newDrag) noexcept { - + if (rigidBody) + rigidBody->SetLinearDrag(newDrag); } void SHRigidBodyComponent::SetAngularDrag(float newAngularDrag) noexcept { - + } void SHRigidBodyComponent::SetLinearVelocity(const SHVec3& newLinearVelocity) noexcept { - + if (rigidBody) + rigidBody->SetLinearVelocity(newLinearVelocity); } void SHRigidBodyComponent::SetAngularVelocity(const SHVec3& newAngularVelocity) noexcept { - + } /*-----------------------------------------------------------------------------------*/ - /* Public Function Member Definitions */ + /* Member Function Definitions */ /*-----------------------------------------------------------------------------------*/ - void SHRigidBodyComponent::OnCreate() - { - - } - void SHRigidBodyComponent::AddForce(const SHVec3& force) const noexcept { - + if (rigidBody) + rigidBody->AddForce(force); } void SHRigidBodyComponent::AddForceAtLocalPos(const SHVec3& force, const SHVec3& localPos) const noexcept @@ -221,12 +312,13 @@ namespace SHADE void SHRigidBodyComponent::AddForceAtWorldPos(const SHVec3& force, const SHVec3& worldPos) const noexcept { - + if (rigidBody) + rigidBody->AddForce(force, worldPos); } void SHRigidBodyComponent::AddRelativeForce(const SHVec3& relativeForce) const noexcept { - + } void SHRigidBodyComponent::AddRelativeForceAtLocalPos(const SHVec3& relativeForce, const SHVec3& localPos) const noexcept @@ -251,7 +343,8 @@ namespace SHADE void SHRigidBodyComponent::ClearForces() const noexcept { - + if (rigidBody) + rigidBody->ClearForces(); } void SHRigidBodyComponent::ClearTorque() const noexcept @@ -259,6 +352,15 @@ namespace SHADE } + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHRigidBodyComponent::OnCreate() + { + + } + } // namespace SHADE RTTR_REGISTRATION @@ -274,17 +376,19 @@ RTTR_REGISTRATION ); registration::class_("RigidBody Component") - .property("Type" , &SHRigidBodyComponent::GetType , &SHRigidBodyComponent::SetType ) - //.property("Mass" , &SHRigidBodyComponent::GetMass , &SHRigidBodyComponent::SetMass ) - .property("Drag" , &SHRigidBodyComponent::GetDrag , &SHRigidBodyComponent::SetDrag ) - .property("Angular Drag" , &SHRigidBodyComponent::GetAngularDrag , &SHRigidBodyComponent::SetAngularDrag ) - .property("Use Gravity" , &SHRigidBodyComponent::IsGravityEnabled , &SHRigidBodyComponent::SetGravityEnabled ) - .property("Interpolate" , &SHRigidBodyComponent::IsInterpolating , &SHRigidBodyComponent::SetInterpolate ) - .property("Sleeping Enabled" , &SHRigidBodyComponent::IsAllowedToSleep , &SHRigidBodyComponent::SetIsAllowedToSleep) - .property("Freeze Position X" , &SHRigidBodyComponent::GetFreezePositionX , &SHRigidBodyComponent::SetFreezePositionX ) - .property("Freeze Position Y" , &SHRigidBodyComponent::GetFreezePositionY , &SHRigidBodyComponent::SetFreezePositionY ) - .property("Freeze Position Z" , &SHRigidBodyComponent::GetFreezePositionZ , &SHRigidBodyComponent::SetFreezePositionZ ) - .property("Freeze Rotation X" , &SHRigidBodyComponent::GetFreezeRotationX , &SHRigidBodyComponent::SetFreezeRotationX ) - .property("Freeze Rotation Y" , &SHRigidBodyComponent::GetFreezeRotationY , &SHRigidBodyComponent::SetFreezeRotationY ) - .property("Freeze Rotation Z" , &SHRigidBodyComponent::GetFreezeRotationZ , &SHRigidBodyComponent::SetFreezeRotationZ ); + .property("Type" , &SHRigidBodyComponent::GetType , &SHRigidBodyComponent::SetType ) + .property("Auto Mass" , &SHRigidBodyComponent::GetAutoMass , &SHRigidBodyComponent::SetAutoMass ) + .property("Mass" , &SHRigidBodyComponent::GetMass , &SHRigidBodyComponent::SetMass ) + .property("Drag" , &SHRigidBodyComponent::GetDrag , &SHRigidBodyComponent::SetDrag ) + .property("Angular Drag" , &SHRigidBodyComponent::GetAngularDrag , &SHRigidBodyComponent::SetAngularDrag ) + .property("Use Gravity" , &SHRigidBodyComponent::IsGravityEnabled , &SHRigidBodyComponent::SetIsGravityEnabled ) + .property("Gravity Scale" , &SHRigidBodyComponent::GetGravityScale , &SHRigidBodyComponent::SetGravityScale ) + .property("Interpolate" , &SHRigidBodyComponent::IsInterpolating , &SHRigidBodyComponent::SetInterpolate ) + .property("Sleeping Enabled" , &SHRigidBodyComponent::IsAllowedToSleep , &SHRigidBodyComponent::SetIsAllowedToSleep ) + .property("Freeze Position X" , &SHRigidBodyComponent::GetFreezePositionX , &SHRigidBodyComponent::SetFreezePositionX ) + .property("Freeze Position Y" , &SHRigidBodyComponent::GetFreezePositionY , &SHRigidBodyComponent::SetFreezePositionY ) + .property("Freeze Position Z" , &SHRigidBodyComponent::GetFreezePositionZ , &SHRigidBodyComponent::SetFreezePositionZ ) + .property("Freeze Rotation X" , &SHRigidBodyComponent::GetFreezeRotationX , &SHRigidBodyComponent::SetFreezeRotationX ) + .property("Freeze Rotation Y" , &SHRigidBodyComponent::GetFreezeRotationY , &SHRigidBodyComponent::SetFreezeRotationY ) + .property("Freeze Rotation Z" , &SHRigidBodyComponent::GetFreezeRotationZ , &SHRigidBodyComponent::SetFreezeRotationZ ); } \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h b/SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h similarity index 88% rename from SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h rename to SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h index 021743da..ae0bd3ec 100644 --- a/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h +++ b/SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h @@ -15,10 +15,15 @@ // Project Headers #include "ECS_Base/Components/SHComponent.h" #include "Math/Vector/SHVec3.h" -#include "Math/SHQuaternion.h" namespace SHADE { + /*-------------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-------------------------------------------------------------------------------------*/ + + class SHRigidBody; + /*-----------------------------------------------------------------------------------*/ /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ @@ -67,13 +72,13 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ + [[nodiscard]] Type GetType () const noexcept; + [[nodiscard]] bool IsGravityEnabled () const noexcept; [[nodiscard]] bool IsAllowedToSleep () const noexcept; [[nodiscard]] bool IsInterpolating () const noexcept; - - [[nodiscard]] bool GetIsSleeping () const noexcept; - - [[nodiscard]] Type GetType () const noexcept; + [[nodiscard]] bool IsSleeping () const noexcept; + [[nodiscard]] bool GetAutoMass () const noexcept; [[nodiscard]] bool GetFreezePositionX () const noexcept; [[nodiscard]] bool GetFreezePositionY () const noexcept; @@ -82,6 +87,7 @@ namespace SHADE [[nodiscard]] bool GetFreezeRotationY () const noexcept; [[nodiscard]] bool GetFreezeRotationZ () const noexcept; + [[nodiscard]] float GetGravityScale () const noexcept; [[nodiscard]] float GetMass () const noexcept; [[nodiscard]] float GetDrag () const noexcept; [[nodiscard]] float GetAngularDrag () const noexcept; @@ -91,18 +97,18 @@ namespace SHADE [[nodiscard]] SHVec3 GetLinearVelocity () const noexcept; [[nodiscard]] SHVec3 GetAngularVelocity () const noexcept; - //[[nodiscard]] const SHVec3& GetPosition () const noexcept; - //[[nodiscard]] const SHQuaternion& GetOrientation () const noexcept; - //[[nodiscard]] SHVec3 GetRotation () const noexcept; - /*---------------------------------------------------------------------------------*/ /* Setter Functions */ /*---------------------------------------------------------------------------------*/ void SetType (Type newType) noexcept; - void SetGravityEnabled (bool enableGravity) noexcept; + void SetRigidBody (SHRigidBody* rb) noexcept; + + void SetIsGravityEnabled (bool enableGravity) noexcept; void SetIsAllowedToSleep (bool isAllowedToSleep) noexcept; + void SetAutoMass (bool autoMass) noexcept; + void SetFreezePositionX (bool freezePositionX) noexcept; void SetFreezePositionY (bool freezePositionY) noexcept; void SetFreezePositionZ (bool freezePositionZ) noexcept; @@ -110,9 +116,9 @@ namespace SHADE void SetFreezeRotationY (bool freezeRotationY) noexcept; void SetFreezeRotationZ (bool freezeRotationZ) noexcept; void SetInterpolate (bool allowInterpolation) noexcept; - //void SetAutoMass (bool autoMass) noexcept; - //void SetMass (float newMass) noexcept; + void SetGravityScale (float gravityScale) noexcept; + void SetMass (float newMass) noexcept; void SetDrag (float newDrag) noexcept; void SetAngularDrag (float newAngularDrag) noexcept; @@ -142,6 +148,11 @@ namespace SHADE /* Data Members */ /*---------------------------------------------------------------------------------*/ + Type type; + bool interpolate; + SHRigidBody* rigidBody; + RTTR_ENABLE() }; + } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/SHPhysicsWorld.cpp b/SHADE_Engine/src/Physics/SHPhysicsWorld.cpp index 85e76702..d54d3f86 100644 --- a/SHADE_Engine/src/Physics/SHPhysicsWorld.cpp +++ b/SHADE_Engine/src/Physics/SHPhysicsWorld.cpp @@ -19,53 +19,9 @@ namespace SHADE /* Constructors & Destructor Definitions */ /*-----------------------------------------------------------------------------------*/ - SHPhysicsWorldState::SHPhysicsWorldState() noexcept - : world { nullptr } - {} /*-----------------------------------------------------------------------------------*/ /* Public Function Members Definitions */ /*-----------------------------------------------------------------------------------*/ - void SHPhysicsWorldState::CreateWorld(rp3d::PhysicsCommon& factory) - { - rp3d::PhysicsWorld::WorldSettings rp3dWorldSettings; - rp3dWorldSettings.gravity = settings.gravity; - rp3dWorldSettings.defaultVelocitySolverNbIterations = settings.numVelocitySolverIterations; - rp3dWorldSettings.defaultPositionSolverNbIterations = settings.numPositionSolverIterations; - rp3dWorldSettings.isSleepingEnabled = settings.sleepingEnabled; - - // These are my preferred default values. QoL for modifying these. - rp3dWorldSettings.defaultBounciness = 0.0f; - rp3dWorldSettings.defaultFrictionCoefficient = 0.4f; - - world = factory.createPhysicsWorld(rp3dWorldSettings); - world->setContactsPositionCorrectionTechnique(rp3d::ContactsPositionCorrectionTechnique::SPLIT_IMPULSES); - } - - void SHPhysicsWorldState::DestroyWorld(rp3d::PhysicsCommon& factory) - { - if (!world) - return; - - factory.destroyPhysicsWorld(world); - world = nullptr; - } - - void SHPhysicsWorldState::UpdateSettings() const noexcept - { - if (!world) - { - SHLOGV_ERROR("Unable to update Physics World settings without creating a world!") - return; - } - - world->setGravity(settings.gravity); - world->setNbIterationsVelocitySolver(settings.numVelocitySolverIterations); - world->setNbIterationsPositionSolver(settings.numPositionSolverIterations); - world->enableSleeping(settings.sleepingEnabled); - } - - - } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/SHPhysicsWorld.h b/SHADE_Engine/src/Physics/SHPhysicsWorld.h index c5152c44..e872cd3c 100644 --- a/SHADE_Engine/src/Physics/SHPhysicsWorld.h +++ b/SHADE_Engine/src/Physics/SHPhysicsWorld.h @@ -10,8 +10,6 @@ #pragma once -#include - // Project Headers #include "Math/SHMath.h" #include "SH_API.h" @@ -47,7 +45,6 @@ namespace SHADE /* Data Members */ /*---------------------------------------------------------------------------------*/ - rp3d::PhysicsWorld* world; WorldSettings settings; /*---------------------------------------------------------------------------------*/ @@ -60,14 +57,6 @@ namespace SHADE /* Function Members */ /*---------------------------------------------------------------------------------*/ - void CreateWorld (rp3d::PhysicsCommon& factory); - void DestroyWorld (rp3d::PhysicsCommon& factory); - - /** - * @brief Applies the current settings to the physics world. The world must be created - * before this is called. - */ - void UpdateSettings () const noexcept; }; diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystemRoutines.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp similarity index 50% rename from SHADE_Engine/src/Physics/System/SHPhysicsSystemRoutines.cpp rename to SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp index 0029a8e7..9b1c0015 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystemRoutines.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp @@ -1,7 +1,7 @@ /**************************************************************************************** - * \file SHPhysicsSystemRoutines.h + * \file SHPhysicsPostUpdateRoutine.cpp * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Implementation for the Physics System Routines + * \brief Implementation for the Physics Post-Update Routine * * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or * disclosure of this file or its contents without the prior written consent @@ -11,24 +11,12 @@ #include // Primary Header -#include "SHPhysicsSystem.h" +#include "Physics/System/SHPhysicsSystem.h" + // Project Headers -#include "ECS_Base/Managers/SHEntityManager.h" #include "ECS_Base/Managers/SHSystemManager.h" -#include "Editor/SHEditor.h" -#include "Scene/SHSceneManager.h" #include "Scripting/SHScriptEngine.h" -#include "Input/SHInputManager.h" -#include "Physics/Collision/SHCollisionTagMatrix.h" - -/*-------------------------------------------------------------------------------------*/ -/* Local Functions */ -/*-------------------------------------------------------------------------------------*/ - -void testFunction(); - -///////////////////////////////////////////////////////////////////////////////////////// namespace SHADE { @@ -36,59 +24,14 @@ namespace SHADE /* Constructors & Destructor Definitions */ /*-----------------------------------------------------------------------------------*/ - SHPhysicsSystem::PhysicsPreUpdate::PhysicsPreUpdate() - : SHSystemRoutine { "Physics PreUpdate", true } - {} - - SHPhysicsSystem::PhysicsFixedUpdate::PhysicsFixedUpdate() - : SHFixedSystemRoutine { DEFAULT_FIXED_STEP, "Physics FixedUpdate", false } - {} - SHPhysicsSystem::PhysicsPostUpdate::PhysicsPostUpdate() - : SHSystemRoutine { "Physics PostUpdate", false } + : SHSystemRoutine { "Physics Post-Update", false } {} /*-----------------------------------------------------------------------------------*/ /* Public Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ - void SHPhysicsSystem::PhysicsPreUpdate::Execute(double) noexcept - { - auto* physicsSystem = reinterpret_cast(GetSystem()); - } - - void SHPhysicsSystem::PhysicsFixedUpdate::Execute(double dt) noexcept - { - auto* physicsSystem = reinterpret_cast(GetSystem()); - - auto* scriptingSystem = SHSystemManager::GetSystem(); - if (scriptingSystem == nullptr) - { - SHLOGV_ERROR("Unable to invoke FixedUpdate() on scripts due to missing SHScriptEngine!"); - } - - const double FIXED_DT = physicsSystem->fixedDT; - - accumulatedTime += dt; - - int count = 0; - while (accumulatedTime > FIXED_DT) - { - if (scriptingSystem != nullptr) - scriptingSystem->ExecuteFixedUpdates(); - - // TODO: Update World - - accumulatedTime -= FIXED_DT; - ++count; - } - - stats.numSteps = count; - physicsSystem->worldUpdated = count > 0; - - physicsSystem->interpolationFactor = accumulatedTime / fixedTimeStep; - } - void SHPhysicsSystem::PhysicsPostUpdate::Execute(double) noexcept { auto* physicsSystem = reinterpret_cast(GetSystem()); @@ -110,6 +53,10 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ /* Private Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ -} // namespace SHADE + void SHPhysicsSystem::PhysicsPostUpdate::syncTransforms(SHRigidBodyComponent* rbComponent) const noexcept + { + + } +} diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp new file mode 100644 index 00000000..a0f348f4 --- /dev/null +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp @@ -0,0 +1,62 @@ +/**************************************************************************************** + * \file SHPhysicsPreUpdateRoutine.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for the Physics Pre-Update Routine + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "Physics/System/SHPhysicsSystem.h" + +// Project Headers +#include "ECS_Base/Managers/SHComponentManager.h" +#include "Math/Transform/SHTransformComponent.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsSystem::PhysicsPreUpdate::PhysicsPreUpdate() + : SHSystemRoutine { "Physics Pre-Update", true } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsSystem::PhysicsPreUpdate::Execute(double) noexcept + { + auto* physicsSystem = reinterpret_cast(GetSystem()); + + // Get all physics objects & sync transforms + for (auto& physicsObject : physicsSystem->physicsObjectManager.GetPhysicsObjects() | std::views::values) + syncTransforms(&physicsObject); + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsSystem::PhysicsPreUpdate::syncTransforms(SHPhysicsObject* physicsObject) const noexcept + { + const EntityID EID = physicsObject->entityID; + + // Get relevant components: Transform, Rigidbody & Collider + const auto* TRANSFORM_COMPONENT = SHComponentManager::GetComponent_s(EID); + + auto* rigidBodyComponent = SHComponentManager::GetComponent_s(EID); + auto* colliderComponent = SHComponentManager::GetComponent_s(EID); + + if (TRANSFORM_COMPONENT && TRANSFORM_COMPONENT->HasChanged()) + { + // Sync the objects transforms + } + } +} diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsUpdateRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsUpdateRoutine.cpp new file mode 100644 index 00000000..77da8706 --- /dev/null +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsUpdateRoutine.cpp @@ -0,0 +1,62 @@ +/**************************************************************************************** + * \file SHPhysicsUpdateRoutine.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for the Physics Update Routine + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "Physics/System/SHPhysicsSystem.h" + +// Project Headers +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Scripting/SHScriptEngine.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsSystem::PhysicsUpdate::PhysicsUpdate() + : SHFixedSystemRoutine { DEFAULT_FIXED_STEP, "Physics Update", false } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsSystem::PhysicsUpdate::Execute(double dt) noexcept + { + auto* physicsSystem = reinterpret_cast(GetSystem()); + + auto* scriptEngine = SHSystemManager::GetSystem(); + if (!scriptEngine) + { + SHLOGV_ERROR("Unable to invoke FixedUpdate() on scripts due to missing ScriptEngine!") + } + + const double FIXED_DT = physicsSystem->fixedDT; + accumulatedTime += dt; + + int count = 0; + while (accumulatedTime > FIXED_DT) + { + + + accumulatedTime -= FIXED_DT; + ++count; + } + + stats.numSteps = count; + physicsSystem->worldUpdated = count > 0; + + physicsSystem->interpolationFactor = physicsSystem->worldUpdated ? accumulatedTime / FIXED_DT : 0.0; + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp index eacd6ac9..98a69087 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp @@ -20,9 +20,7 @@ #include "ECS_Base/Managers/SHSystemManager.h" #include "Editor/SHEditor.h" #include "Physics/Collision/SHCollisionTagMatrix.h" -#include "Physics/SHPhysicsEvents.h" -#include "Scene/SHSceneManager.h" -#include "Scripting/SHScriptEngine.h" +#include "Physics/Interface/SHColliderComponent.h" namespace SHADE { @@ -34,7 +32,18 @@ namespace SHADE : worldUpdated { false } , interpolationFactor { 0.0 } , fixedDT { DEFAULT_FIXED_STEP } - {} + { + // Add more events here to register them + + eventFunctions[0] = { &SHPhysicsSystem::onComponentAdded , SH_COMPONENT_ADDED_EVENT }; + eventFunctions[1] = { &SHPhysicsSystem::onComponentRemoved, SH_COMPONENT_REMOVED_EVENT }; + eventFunctions[2] = { &SHPhysicsSystem::onSceneInit , SH_SCENE_ON_INIT_EVENT }; + eventFunctions[3] = { &SHPhysicsSystem::onSceneExit , SH_SCENE_ON_EXIT_EVENT }; + + #ifdef SHEDITOR + eventFunctions[4] = { &SHPhysicsSystem::onEditorPlay , SH_EDITOR_ON_PLAY_EVENT }; + #endif + } /*-----------------------------------------------------------------------------------*/ /* Getter Function Definitions */ @@ -45,13 +54,48 @@ namespace SHADE return 1.0 / fixedDT; } + double SHPhysicsSystem::GetFixedDT() const noexcept + { + return fixedDT; + } + /*-----------------------------------------------------------------------------------*/ /* Setter Function Definitions */ /*-----------------------------------------------------------------------------------*/ - void SHPhysicsSystem::SetFixedUpdateRate(double fixedUpdateRate) noexcept + void SHPhysicsSystem::SetFixedUpdateRate(double fixedUpdateRate) noexcept { + // Handle invalid input + if (fixedUpdateRate <= 0.0) + { + SHLOGV_WARNING("Invalid value for setting fixed update rate! Fixed update rate unchanged.") + return; + } + fixedDT = 1.0 / fixedUpdateRate; + + // Handle potential incorrect / unintended input + if (fixedDT > 1.0) + { + SHLOGV_WARNING("Fixed Update Rate Time is set below 1. This may result in undesirable behaviour.") + } + } + + void SHPhysicsSystem::SetFixedDT(double fixedDt) noexcept + { + if (fixedDt <= 0.0) + { + SHLOGV_WARNING("Invalid value for setting fixed delta time! Fixed delta time unchanged.") + return; + } + + fixedDT = fixedDt; + + // Handle potential incorrect / unintended input + if (fixedDT > 1.0) + { + SHLOGV_WARNING("Fixed Delta Time is set above 1. This may result in undesirable behaviour.") + } } /*-----------------------------------------------------------------------------------*/ @@ -64,11 +108,21 @@ namespace SHADE std::filesystem::path defaultCollisionTagNameFilePath { ASSET_ROOT }; defaultCollisionTagNameFilePath.append("CollisionTags.SHConfig"); SHCollisionTagMatrix::Init(defaultCollisionTagNameFilePath); + + // Link Managers to system + + // Register Events + for (int i = 0; i < NUM_EVENT_FUNCTIONS; ++i) + { + const std::shared_ptr EVENT_RECEIVER = std::make_shared>(this, eventFunctions[i].first); + const ReceiverPtr EVENT_RECEIVER_PTR = std::dynamic_pointer_cast(EVENT_RECEIVER); + + SHEventManager::SubscribeTo(eventFunctions[i].second, EVENT_RECEIVER_PTR); + } } void SHPhysicsSystem::Exit() { - // Write collision tag names to file std::filesystem::path defaultCollisionTagNameFilePath { ASSET_ROOT }; defaultCollisionTagNameFilePath.append("CollisionTags.SHConfig"); @@ -84,4 +138,98 @@ namespace SHADE /* Private Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ + SHEventHandle SHPhysicsSystem::onSceneInit(SHEventPtr onSceneInitEvent) + { + /* + * TODO: + * If a world already exists, destroy it + * Recreate the world + * + * If there is an editor and editor mode is already playing, link all entities with the world immediately. + */ + + return onSceneInitEvent.get()->handle; + } + + SHEventHandle SHPhysicsSystem::onSceneExit(SHEventPtr onSceneExitEvent) + { + /* + * TODO: + * Destroy the physics world. + * Destroy all physics objects. + */ + + return onSceneExitEvent.get()->handle; + } + + SHEventHandle SHPhysicsSystem::onComponentAdded(SHEventPtr onComponentAddedEvent) + { + static const auto RIGID_BODY_COMPONENT_ID = ComponentFamily::GetID(); + static const auto COLLIDER_COMPONENT_ID = ComponentFamily::GetID(); + + const auto& EVENT_DATA = reinterpret_cast*>(onComponentAddedEvent.get())->data; + + const auto ADDED_ID = EVENT_DATA->addedComponentType; + + const bool IS_RIGID_BODY = ADDED_ID == RIGID_BODY_COMPONENT_ID; + const bool IS_COLLIDER = ADDED_ID == COLLIDER_COMPONENT_ID; + + // Check if its a physics component + if (IS_RIGID_BODY || IS_COLLIDER) + { + const EntityID EID = EVENT_DATA->eid; + + // Link engine components with physics object component + if (IS_RIGID_BODY) + physicsObjectManager.AddRigidBody(EID); + + if (IS_COLLIDER) + physicsObjectManager.AddCollider(EID); + } + + return onComponentAddedEvent.get()->handle; + } + + SHEventHandle SHPhysicsSystem::onComponentRemoved(SHEventPtr onComponentRemovedEvent) + { + static const auto RIGID_BODY_COMPONENT_ID = ComponentFamily::GetID(); + static const auto COLLIDER_COMPONENT_ID = ComponentFamily::GetID(); + + const auto& EVENT_DATA = reinterpret_cast*>(onComponentRemovedEvent.get())->data; + + const auto REMOVED_DATA = EVENT_DATA->removedComponentType; + + const bool IS_RIGID_BODY = REMOVED_DATA == RIGID_BODY_COMPONENT_ID; + const bool IS_COLLIDER = REMOVED_DATA == COLLIDER_COMPONENT_ID; + + // Check if its a physics component + if (IS_RIGID_BODY || IS_COLLIDER) + { + const EntityID EID = EVENT_DATA->eid; + + // Link engine components with physics object component + if (IS_RIGID_BODY) + physicsObjectManager.RemoveRigidBody(EID); + + if (IS_COLLIDER) + physicsObjectManager.RemoveCollider(EID); + } + + return onComponentRemovedEvent.get()->handle; + } + +#ifdef SHEDITOR + + SHEventHandle SHPhysicsSystem::onEditorPlay(SHEventPtr onEditorPlayEvent) + { + /* + * TODO: + * Link all entities with the world. + */ + + return onEditorPlayEvent.get()->handle; + } + +#endif + } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h index 3fe05c09..1562241f 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h @@ -11,14 +11,15 @@ #pragma once // Project Headers +#include "ECS_Base/System/SHSystem.h" #include "ECS_Base/System/SHSystemRoutine.h" #include "ECS_Base/System/SHFixedSystemRoutine.h" +#include "Events/SHEvent.h" -#include "Math/Transform/SHTransformComponent.h" -#include "Physics/Interface/SHRigidBodyComponent.h" +#include "Physics/Interface/PhysicsObject/SHPhysicsObjectManager.h" +#include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" #include "Physics/Interface/SHColliderComponent.h" -#include "Physics/SHPhysicsWorld.h" -#include "Scene/SHSceneGraph.h" + namespace SHADE { @@ -28,13 +29,6 @@ namespace SHADE class SH_API SHPhysicsSystem final : public SHSystem { - private: - /*---------------------------------------------------------------------------------*/ - /* Friends */ - /*---------------------------------------------------------------------------------*/ - - friend class SHPhysicsDebugDrawSystem; - public: /*---------------------------------------------------------------------------------*/ /* Constructors & Destructor */ @@ -46,104 +40,121 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] double GetFixedUpdateRate () const noexcept; - [[nodiscard]] const SHPhysicsWorldState::WorldSettings& GetWorldSettings () const noexcept; + [[nodiscard]] double GetFixedUpdateRate() const noexcept; + [[nodiscard]] double GetFixedDT() const noexcept; /*---------------------------------------------------------------------------------*/ /* Setter Functions */ /*---------------------------------------------------------------------------------*/ - void SetFixedUpdateRate (double fixedUpdateRate) noexcept; - void SetWorldSettings (const SHPhysicsWorldState::WorldSettings& settings) noexcept; + void SetFixedUpdateRate(double fixedUpdateRate) noexcept; + void SetFixedDT(double fixedDt) noexcept; /*---------------------------------------------------------------------------------*/ /* Function Members */ /*---------------------------------------------------------------------------------*/ - void Init () override; - void Exit () override; + void Init() override; + void Exit() override; - void ForceUpdate (); + void ForceUpdate(); /*---------------------------------------------------------------------------------*/ /* System Routines */ /*---------------------------------------------------------------------------------*/ + /** + * @brief + * The physics update routine that runs before the simulation is updated. + * This is always running, regardless of the editor state. + *
+ * This update game logic is applied before the simulation runs. + */ class SH_API PhysicsPreUpdate final : public SHSystemRoutine { public: - /*-------------------------------------------------------------------------------*/ - /* Constructors & Destructor */ - /*-------------------------------------------------------------------------------*/ - PhysicsPreUpdate(); - - /*-------------------------------------------------------------------------------*/ - /* Function Members */ - /*-------------------------------------------------------------------------------*/ - void Execute(double dt) noexcept override; private: - /*-------------------------------------------------------------------------------*/ - /* Function Members */ - /*-------------------------------------------------------------------------------*/ - + void syncTransforms(SHPhysicsObject* physicsObject) const noexcept; }; - class SH_API PhysicsFixedUpdate final : public SHFixedSystemRoutine + /** + * @brief + * The physics update routine that runs at a fixed rate. This is where the main + * simulation runs. If delta time is large enough, this may run more than once per + * frame. If delta time is small enough, this may not run at all. + */ + class SH_API PhysicsUpdate final : public SHFixedSystemRoutine { public: - /*-------------------------------------------------------------------------------*/ - /* Constructors & Destructor */ - /*-------------------------------------------------------------------------------*/ - - PhysicsFixedUpdate(); - - /*-------------------------------------------------------------------------------*/ - /* Function Members */ - /*-------------------------------------------------------------------------------*/ - - void Execute (double dt) noexcept override; + PhysicsUpdate(); + void Execute(double dt) noexcept override; }; + /** + * @brief + * The physics update that runs after the simulation. This sets the rendering + * transforms and sends messages to scripting system for collision & trigger events. + */ class SH_API PhysicsPostUpdate final : public SHSystemRoutine { public: - /*-------------------------------------------------------------------------------*/ - /* Constructors & Destructor */ - /*-------------------------------------------------------------------------------*/ - PhysicsPostUpdate(); - - /*-------------------------------------------------------------------------------*/ - /* Function Members */ - /*-------------------------------------------------------------------------------*/ - void Execute(double dt) noexcept override; private: - - /*-------------------------------------------------------------------------------*/ - /* Function Members */ - /*-------------------------------------------------------------------------------*/ - + void syncTransforms(SHRigidBodyComponent* rbComponent) const noexcept; }; private: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + using EventFunctionPair = std::pair; + /*---------------------------------------------------------------------------------*/ /* Data Members */ /*---------------------------------------------------------------------------------*/ + #ifdef SHEDITOR + static constexpr int NUM_EVENT_FUNCTIONS = 5; + #else + static constexpr int NUM_EVENT_FUNCTIONS = 4; + #endif + + // Event function container for cleanly registering to events + EventFunctionPair eventFunctions[NUM_EVENT_FUNCTIONS]; + // System data bool worldUpdated; double interpolationFactor; double fixedDT; + // Sub-systems / managers + + SHPhysicsObjectManager physicsObjectManager; + /*---------------------------------------------------------------------------------*/ /* Function Members */ /*---------------------------------------------------------------------------------*/ + SHEventHandle onSceneInit (SHEventPtr onSceneInitEvent); + SHEventHandle onSceneExit (SHEventPtr onSceneExitEvent); + + SHEventHandle onComponentAdded (SHEventPtr onComponentAddedEvent); + SHEventHandle onComponentRemoved (SHEventPtr onComponentRemovedEvent); + + + #ifdef SHEDITOR + + SHEventHandle onEditorPlay (SHEventPtr onEditorPlayEvent); + // We don't need an onEditorStop because on stop exits the scene, which is already handled above. + + #endif + }; } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Serialization/SHSerialization.cpp b/SHADE_Engine/src/Serialization/SHSerialization.cpp index 99e4fa41..9d648185 100644 --- a/SHADE_Engine/src/Serialization/SHSerialization.cpp +++ b/SHADE_Engine/src/Serialization/SHSerialization.cpp @@ -15,7 +15,7 @@ #include "Camera/SHCameraArmComponent.h" #include "Math/Transform/SHTransformComponent.h" #include "Graphics/MiddleEnd/Interface/SHRenderable.h" -#include "Physics/Interface/SHRigidBodyComponent.h" +#include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" #include "UI/SHCanvasComponent.h" #include "UI/SHButtonComponent.h" #include "ECS_Base/Managers/SHSystemManager.h" diff --git a/SHADE_Managed/src/Components/RigidBody.cxx b/SHADE_Managed/src/Components/RigidBody.cxx index a564402f..7b3604f2 100644 --- a/SHADE_Managed/src/Components/RigidBody.cxx +++ b/SHADE_Managed/src/Components/RigidBody.cxx @@ -34,7 +34,7 @@ namespace SHADE } void RigidBody::IsGravityEnabled::set(bool value) { - return GetNativeComponent()->SetGravityEnabled(value); + return GetNativeComponent()->SetIsGravityEnabled(value); } bool RigidBody::IsAllowedToSleep::get() { diff --git a/SHADE_Managed/src/Components/RigidBody.hxx b/SHADE_Managed/src/Components/RigidBody.hxx index 8bfe34aa..6ca39316 100644 --- a/SHADE_Managed/src/Components/RigidBody.hxx +++ b/SHADE_Managed/src/Components/RigidBody.hxx @@ -15,7 +15,7 @@ of DigiPen Institute of Technology is prohibited. #pragma once // External Dependencies -#include "Physics/Interface/SHRigidBodyComponent.h" +#include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" // Project Includes #include "Components/Component.hxx" diff --git a/SHADE_Managed/src/Engine/ECS.cxx b/SHADE_Managed/src/Engine/ECS.cxx index c388f0cd..3ace79ab 100644 --- a/SHADE_Managed/src/Engine/ECS.cxx +++ b/SHADE_Managed/src/Engine/ECS.cxx @@ -23,7 +23,7 @@ of DigiPen Institute of Technology is prohibited. #include "ECS_Base/Managers/SHEntityManager.h" #include "Math/Transform/SHTransformComponent.h" #include "Physics/Interface/SHColliderComponent.h" -#include "Physics/Interface/SHRigidBodyComponent.h" +#include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" #include "Scene/SHSceneManager.h" #include "Scene/SHSceneGraph.h" #include "Tools/Logger/SHLog.h" From 36ceec5855faa477d292e9e105ca37a5a93e9ed2 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Mon, 5 Dec 2022 00:19:48 +0800 Subject: [PATCH 028/275] Added SceneInit & SceneExit events --- SHADE_Engine/src/Scene/SHSceneEvents.h | 10 +++ SHADE_Engine/src/Scene/SHSceneGraph.cpp | 46 ++++++------- SHADE_Engine/src/Scene/SHSceneGraph.h | 81 +++++++++++++++++++---- SHADE_Engine/src/Scene/SHSceneManager.cpp | 9 ++- SHADE_Engine/src/Scene/SHSceneNode.h | 13 ++-- 5 files changed, 115 insertions(+), 44 deletions(-) diff --git a/SHADE_Engine/src/Scene/SHSceneEvents.h b/SHADE_Engine/src/Scene/SHSceneEvents.h index c0d7dbc1..e76894bd 100644 --- a/SHADE_Engine/src/Scene/SHSceneEvents.h +++ b/SHADE_Engine/src/Scene/SHSceneEvents.h @@ -38,4 +38,14 @@ namespace SHADE SHSceneNode* childRemoved = nullptr; }; + struct SHSceneInitEvent + { + uint32_t sceneID = std::numeric_limits::max(); + }; + + struct SHSceneExitEvent + { + uint32_t sceneID = std::numeric_limits::max(); + }; + } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Scene/SHSceneGraph.cpp b/SHADE_Engine/src/Scene/SHSceneGraph.cpp index 6240b7bf..c040808f 100644 --- a/SHADE_Engine/src/Scene/SHSceneGraph.cpp +++ b/SHADE_Engine/src/Scene/SHSceneGraph.cpp @@ -27,7 +27,7 @@ namespace SHADE : root { nullptr } { // The root is set to the maximum entity. It should not be interfaced with. - root = AllocateNode(MAX_EID); + root = allocateNode(MAX_EID); } SHSceneGraph::~SHSceneGraph() noexcept @@ -239,7 +239,7 @@ namespace SHADE if (newParent == nullptr) newParent = root; - ChangeParent(NODE_ITER->second, newParent); + changeParent(NODE_ITER->second, newParent); SHEventManager::BroadcastEvent(EVENT_DATA, SH_SCENEGRAPH_CHANGE_PARENT_EVENT); } @@ -284,7 +284,7 @@ namespace SHADE }; SHSceneNode* currentNode = NODE_ITER->second; - ChangeParent(currentNode, PARENT_ITER->second); + changeParent(currentNode, PARENT_ITER->second); SHEventManager::BroadcastEvent(EVENT_DATA, SH_SCENEGRAPH_CHANGE_PARENT_EVENT); } @@ -310,7 +310,7 @@ namespace SHADE return NODE_ITER->second; } - SHSceneNode* newNode = AllocateNode(entityID); + SHSceneNode* newNode = allocateNode(entityID); if (parent == nullptr) { @@ -320,7 +320,7 @@ namespace SHADE } else { - ChangeParent(newNode, parent); + changeParent(newNode, parent); } return newNode; @@ -347,9 +347,9 @@ namespace SHADE // Remove reference of current node from parent SHSceneNode* currentNode = NODE_ITER->second; if (currentNode->parent != nullptr) - RemoveChild(currentNode->parent, currentNode); + removeChild(currentNode->parent, currentNode); - ReleaseNode(currentNode); + releaseNode(currentNode); return true; } @@ -357,16 +357,16 @@ namespace SHADE { // Remove reference of current node from parent if (nodeToRemove->parent != nullptr) - RemoveChild(nodeToRemove->parent, nodeToRemove); + removeChild(nodeToRemove->parent, nodeToRemove); - ReleaseNode(nodeToRemove); + releaseNode(nodeToRemove); return true; } void SHSceneGraph::Reset() noexcept { for (auto* node : entityNodeMap | std::views::values) - ReleaseNode(node); + releaseNode(node); } bool SHSceneGraph::IsChildOf(EntityID entityID, SHSceneNode* targetNode) noexcept @@ -456,39 +456,39 @@ namespace SHADE void SHSceneGraph::Traverse (const UnaryFunction& function) const { - TraverseAndInvokeFunction(root, function); + traverseAndInvokeFunction(root, function); } /*-----------------------------------------------------------------------------------*/ /* Private Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ - SHSceneNode* SHSceneGraph::AllocateNode(EntityID entityID) + SHSceneNode* SHSceneGraph::allocateNode(EntityID entityID) { SHSceneNode* newNode = new SHSceneNode{entityID}; entityNodeMap.emplace(entityID, newNode); return newNode; } - void SHSceneGraph::ReleaseNode(SHSceneNode* node) noexcept + void SHSceneGraph::releaseNode(SHSceneNode* node) noexcept { SHASSERT(node != nullptr, "Attempting to release Invalid Node!") // Remove parent's reference to this node if there is a parent if (node->parent != nullptr) - RemoveChild(node->parent, node); + removeChild(node->parent, node); // Remove child's references to this node. Children end up as floating nodes. for (auto* child : node->GetChildren()) { - ChangeParent(child, nullptr); + changeParent(child, nullptr); } entityNodeMap.erase(node->GetEntityID()); delete node; } - void SHSceneGraph::ChangeParent(SHSceneNode* node, SHSceneNode* newParent) + void SHSceneGraph::changeParent(SHSceneNode* node, SHSceneNode* newParent) { // Handle self assignment if (node->parent != nullptr && newParent != nullptr && node->parent->entityID == newParent->entityID) @@ -496,7 +496,7 @@ namespace SHADE // Remove child if (node->parent) - RemoveChild(node->parent, node); + removeChild(node->parent, node); if (newParent == nullptr) { @@ -506,16 +506,16 @@ namespace SHADE node->parent = newParent; // Update parent's children - AddChild(newParent, node); + addChild(newParent, node); } - void SHSceneGraph::AddChild(SHSceneNode* node, SHSceneNode* newChild) + void SHSceneGraph::addChild(SHSceneNode* node, SHSceneNode* newChild) { SHASSERT(node != nullptr, "Attempting to modify a non-existent scene node!") SHASSERT(newChild != nullptr, "Attempting to add a non-existent child to a SceneNode!") if (newChild->parent) - RemoveChild(newChild->parent, newChild); + removeChild(newChild->parent, newChild); newChild->parent = node; node->children.emplace_back(newChild); @@ -529,7 +529,7 @@ namespace SHADE SHEventManager::BroadcastEvent(EVENT_DATA, SH_SCENEGRAPH_ADD_CHILD_EVENT); } - void SHSceneGraph::RemoveChild(SHSceneNode* node, SHSceneNode* childToRemove) + void SHSceneGraph::removeChild(SHSceneNode* node, SHSceneNode* childToRemove) { SHASSERT(node != nullptr, "Attempting to modify a non-existent scene node!") SHASSERT(childToRemove != nullptr, "Attempting to remove a non-existent child from a SceneNode!") @@ -550,12 +550,12 @@ namespace SHADE SHEventManager::BroadcastEvent(EVENT_DATA, SH_SCENEGRAPH_REMOVE_CHILD_EVENT); } - void SHSceneGraph::TraverseAndInvokeFunction(const SHSceneNode* node, const UnaryFunction& function) + void SHSceneGraph::traverseAndInvokeFunction(const SHSceneNode* node, const UnaryFunction& function) { for (auto* child : node->children) { function(child); - TraverseAndInvokeFunction(child, function); + traverseAndInvokeFunction(child, function); } } diff --git a/SHADE_Engine/src/Scene/SHSceneGraph.h b/SHADE_Engine/src/Scene/SHSceneGraph.h index 37d0e063..b72b1fb4 100644 --- a/SHADE_Engine/src/Scene/SHSceneGraph.h +++ b/SHADE_Engine/src/Scene/SHSceneGraph.h @@ -24,6 +24,10 @@ namespace SHADE /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ + /** + * @brief + * Encapsulates a hierarchical tree for objects in the scene. + */ class SH_API SHSceneGraph { public: @@ -64,22 +68,75 @@ namespace SHADE /* Setter Functions */ /*---------------------------------------------------------------------------------*/ + /** + * @brief + * Changing the parent of a node will broadcast three events in the following order: + *
+ * 1. SHSceneGraphChangeParentEvent + * 2. SHSceneGraphRemoveChildEvent + * 3. SHSceneGraphAddChildEvent + *

+ * See the corresponding header file for the contents of the event struct. + */ void SetParent (EntityID entityID, SHSceneNode* newParent) noexcept; + + /** + * @brief + * Changing the parent of a node will broadcast three events in the following order: + *
+ * 1. SHSceneGraphChangeParentEvent + * 2. SHSceneGraphRemoveChildEvent + * 3. SHSceneGraphAddChildEvent + *

+ * See the corresponding header file for the contents of the event struct. + */ void SetParent (EntityID entityID, EntityID newParent) noexcept; /*---------------------------------------------------------------------------------*/ /* Function Members */ /*---------------------------------------------------------------------------------*/ + /** + * @brief + * Adding a node will broadcast two events in the following order: + *
+ * 1. SHSceneGraphChangeParentEvent + * 2. SHSceneGraphAddChildEvent + *

+ * See the corresponding header file for the contents of the event struct. + */ SHSceneNode* AddNode (EntityID entityID, SHSceneNode* parent = nullptr); - bool RemoveNode (EntityID entityID) noexcept; - bool RemoveNode (SHSceneNode* nodeToRemove) noexcept; - void Reset () noexcept; - bool IsChildOf (EntityID entityID, SHSceneNode* targetNode) noexcept; - bool IsChildOf (EntityID entityID, EntityID targetID) noexcept; + /** + * @brief + * Removing a node will broadcast the SHSceneGraphRemoveChildEvent. + * See the corresponding header file for the contents of the event struct. + */ + bool RemoveNode (EntityID entityID) noexcept; - void Traverse (const UnaryFunction& function) const; + /** + * @brief + * Removing a node will broadcast the SHSceneGraphRemoveChildEvent. + * See the corresponding header file for the contents of the event struct. + */ + bool RemoveNode (SHSceneNode* nodeToRemove) noexcept; + + /** + * @brief + * Clears all the scene nodes in the scene graph. + */ + void Reset () noexcept; + + bool IsChildOf (EntityID entityID, SHSceneNode* targetNode) noexcept; + bool IsChildOf (EntityID entityID, EntityID targetID) noexcept; + + /** + * @brief + * Traverses the graph and a depth-first manner and applies the the function onto each node. + * @param function + * A unary function that takes in a SHSceneNode pointer. + */ + void Traverse (const UnaryFunction& function) const; private: /*---------------------------------------------------------------------------------*/ @@ -93,14 +150,14 @@ namespace SHADE /* Function Members */ /*---------------------------------------------------------------------------------*/ - SHSceneNode* AllocateNode (EntityID entityID); - void ReleaseNode (SHSceneNode* node) noexcept; + SHSceneNode* allocateNode (EntityID entityID); + void releaseNode (SHSceneNode* node) noexcept; - void ChangeParent (SHSceneNode* node, SHSceneNode* newParent); - void AddChild (SHSceneNode* node, SHSceneNode* newChild); - void RemoveChild (SHSceneNode* node, SHSceneNode* childToRemove); + void changeParent (SHSceneNode* node, SHSceneNode* newParent); + void addChild (SHSceneNode* node, SHSceneNode* newChild); + void removeChild (SHSceneNode* node, SHSceneNode* childToRemove); - static void TraverseAndInvokeFunction (const SHSceneNode* node, const UnaryFunction& function); + static void traverseAndInvokeFunction (const SHSceneNode* node, const UnaryFunction& function); }; } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Scene/SHSceneManager.cpp b/SHADE_Engine/src/Scene/SHSceneManager.cpp index 110aaea6..945e9ce1 100644 --- a/SHADE_Engine/src/Scene/SHSceneManager.cpp +++ b/SHADE_Engine/src/Scene/SHSceneManager.cpp @@ -17,6 +17,7 @@ #include "ECS_Base/Managers/SHSystemManager.h" //#include "FRC/SHFrameRateController.h" //#include "ECS_Base/System/SHApplication.h" +#include "SHSceneEvents.h" #include @@ -55,10 +56,11 @@ namespace SHADE if (currentScene) { currentScene->Free(); + SHEventManager::BroadcastEvent(SHSceneExitEvent{ currentSceneID }, SH_SCENE_ON_EXIT_EVENT); + currentScene->Unload(); SHEntityManager::DestroyAllEntity(); delete currentScene; - } if (!prevSceneReload) { @@ -84,6 +86,8 @@ namespace SHADE { currentScene->Load(); currentScene->Init(); + + SHEventManager::BroadcastEvent(SHSceneInitEvent{ currentSceneID }, SH_SCENE_ON_INIT_EVENT); } } else // restarting scene @@ -91,6 +95,8 @@ namespace SHADE nextSceneID = UINT32_MAX; currentScene->Free(); + SHEventManager::BroadcastEvent(SHSceneExitEvent{ currentSceneID }, SH_SCENE_ON_EXIT_EVENT); + if (cleanReload == true) { cleanReload = false; @@ -103,6 +109,7 @@ namespace SHADE SHEntityManager::DestroyAllEntity(); currentScene->Init(); + SHEventManager::BroadcastEvent(SHSceneInitEvent{ currentSceneID }, SH_SCENE_ON_INIT_EVENT); } } diff --git a/SHADE_Engine/src/Scene/SHSceneNode.h b/SHADE_Engine/src/Scene/SHSceneNode.h index 87bd8d0b..7f8e1dc3 100644 --- a/SHADE_Engine/src/Scene/SHSceneNode.h +++ b/SHADE_Engine/src/Scene/SHSceneNode.h @@ -15,20 +15,17 @@ // Project Headers #include "ECS_Base/Entity/SHEntity.h" #include "SH_API.h" -#include "SHSceneGraph.h" namespace SHADE { - /*-----------------------------------------------------------------------------------*/ - /* Forward Declarations */ - /*-----------------------------------------------------------------------------------*/ - - class SHSceneGraph; - /*-----------------------------------------------------------------------------------*/ /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ + /** + * @brief + * Encapsulates an object in the scene that is part of the scene's hierarchy. + */ class SH_API SHSceneNode { private: @@ -66,7 +63,7 @@ namespace SHADE /* Setter Functions */ /*---------------------------------------------------------------------------------*/ - void SetActive (bool newActiveState) noexcept; + void SetActive (bool newActiveState) noexcept; private: /*---------------------------------------------------------------------------------*/ From 38b1c46d1f96a77023fe2f47cd2631f0645e0c21 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Mon, 5 Dec 2022 00:20:29 +0800 Subject: [PATCH 029/275] Added physics world and tested applied gravity for linear movement --- Assets/Scenes/PhysicsSandbox.shade | 21 ++- .../src/ECS_Base/Managers/SHEntityManager.cpp | 2 +- .../Inspector/SHEditorComponentView.hpp | 10 +- .../src/Math/Transform/SHTransformSystem.cpp | 2 - .../src/Physics/Dynamics/SHMotionState.cpp | 100 ++++++++++++++ .../src/Physics/Dynamics/SHMotionState.h | 93 +++++++++++++ .../src/Physics/Dynamics/SHPhysicsWorld.cpp | 122 ++++++++++++++++++ .../Physics/{ => Dynamics}/SHPhysicsWorld.h | 52 ++++++-- .../src/Physics/Dynamics/SHRigidBody.cpp | 120 +++++++++-------- .../src/Physics/Dynamics/SHRigidBody.h | 37 ++++-- .../PhysicsObject/SHPhysicsObject.cpp | 13 +- .../Interface/PhysicsObject/SHPhysicsObject.h | 6 + .../PhysicsObject/SHPhysicsObjectManager.cpp | 42 +++++- .../PhysicsObject/SHPhysicsObjectManager.h | 10 +- .../SHRigidBodyComponent.cpp | 9 ++ .../RigidBodyComponent/SHRigidBodyComponent.h | 2 + SHADE_Engine/src/Physics/SHPhysicsWorld.cpp | 27 ---- .../Routines/SHPhysicsPostUpdateRoutine.cpp | 43 ++++-- .../Routines/SHPhysicsPreUpdateRoutine.cpp | 40 +++--- .../Routines/SHPhysicsUpdateRoutine.cpp | 4 + .../src/Physics/System/SHPhysicsSystem.cpp | 87 +++++++++++-- .../src/Physics/System/SHPhysicsSystem.h | 12 +- .../Physics/System/SHPhysicsSystemInterface.h | 7 +- 23 files changed, 693 insertions(+), 168 deletions(-) create mode 100644 SHADE_Engine/src/Physics/Dynamics/SHMotionState.cpp create mode 100644 SHADE_Engine/src/Physics/Dynamics/SHMotionState.h create mode 100644 SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp rename SHADE_Engine/src/Physics/{ => Dynamics}/SHPhysicsWorld.h (58%) delete mode 100644 SHADE_Engine/src/Physics/SHPhysicsWorld.cpp diff --git a/Assets/Scenes/PhysicsSandbox.shade b/Assets/Scenes/PhysicsSandbox.shade index a0b3c0c2..d6b77c17 100644 --- a/Assets/Scenes/PhysicsSandbox.shade +++ b/Assets/Scenes/PhysicsSandbox.shade @@ -9,10 +9,10 @@ Scale: {x: 1, y: 1, z: 1} IsActive: true RigidBody Component: - Type: Static + Type: Dynamic Auto Mass: false Mass: 1 - Drag: 0 + Drag: 1 Angular Drag: 0 Use Gravity: true Gravity Scale: 1 @@ -25,4 +25,21 @@ Freeze Rotation Y: false Freeze Rotation Z: false IsActive: true + Scripts: ~ +- EID: 1 + Name: Default + IsActive: true + NumberOfChildren: 0 + Components: + Camera Component: + Position: {x: 0, y: 2, z: 5} + Pitch: 0 + Yaw: 0 + Roll: 0 + Width: 1920 + Height: 1080 + Near: 0.00999999978 + Far: 10000 + Perspective: true + IsActive: true Scripts: ~ \ No newline at end of file diff --git a/SHADE_Engine/src/ECS_Base/Managers/SHEntityManager.cpp b/SHADE_Engine/src/ECS_Base/Managers/SHEntityManager.cpp index 1c603c57..f5f08674 100644 --- a/SHADE_Engine/src/ECS_Base/Managers/SHEntityManager.cpp +++ b/SHADE_Engine/src/ECS_Base/Managers/SHEntityManager.cpp @@ -162,7 +162,7 @@ namespace SHADE //SHSceneNode* parentNode = entityVec[eIndex]->GetSceneNode()->GetParent(); - //SHSceneGraph::RemoveChild(parentNode,entityVec[eIndex].get()); + //SHSceneGraph::removeChild(parentNode,entityVec[eIndex].get()); //TODO remove from parent and recursively delete child. diff --git a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp index 02614631..71d607f5 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp +++ b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp @@ -252,12 +252,13 @@ namespace SHADE if(rbType == SHRigidBodyComponent::Type::DYNAMIC) //Dynamic only fields { SHEditorWidgets::CheckBox("Use Gravity", [component]{return component->IsGravityEnabled();}, [component](bool const& value){component->SetIsGravityEnabled(value);}, "Gravity"); - //SHEditorWidgets::DragFloat("Mass", [component] {return component->GetMass(); }, [component](float const& value) {component->SetMass(value); }, "Mass"); + SHEditorWidgets::DragFloat("Gravity Scale", [component] { return component->GetGravityScale(); }, [component](float const& value) { component->SetGravityScale(value); }, "Gravity Scale", 0.1f, 0.0f); + SHEditorWidgets::DragFloat("Mass", [component] {return component->GetMass(); }, [component](float const& value) {component->SetMass(value); }, "Mass"); } if (rbType == SHRigidBodyComponent::Type::DYNAMIC || rbType == SHRigidBodyComponent::Type::KINEMATIC) //Dynamic or Kinematic only fields { - SHEditorWidgets::DragFloat("Drag", [component] {return component->GetDrag(); }, [component](float const& value) {component->SetDrag(value); }, "Drag"); - SHEditorWidgets::DragFloat("Angular Drag", [component] {return component->GetAngularDrag(); }, [component](float const& value) {component->SetAngularDrag(value); }, "Angular Drag"); + SHEditorWidgets::DragFloat("Drag", [component] {return component->GetDrag(); }, [component](float const& value) {component->SetDrag(value); }, "Drag", 0.1f, 0.0f); + SHEditorWidgets::DragFloat("Angular Drag", [component] {return component->GetAngularDrag(); }, [component](float const& value) {component->SetAngularDrag(value); }, "Angular Drag", 0.1f, 0.0f); SHEditorWidgets::CheckBox("Interpolate", [component] {return component->IsInterpolating(); }, [component](bool const& value) {component->SetInterpolate(value); }, "Interpolate"); @@ -284,8 +285,7 @@ namespace SHADE //Debug Info (Read-Only) if(ImGui::CollapsingHeader("Debug Information", ImGuiTreeNodeFlags_DefaultOpen))//Dynamic or Kinematic only fields { - SHEditorWidgets::DragFloat("Mass", [component] { return component->GetMass(); }, [](float value){}, "Mass", 0.1f, 0.0f, std::numeric_limits::infinity(), "%.3f", ImGuiSliderFlags_ReadOnly); - //SHEditorWidgets::DragVec3("Position", { "X", "Y", "Z" }, [component] {return component->GetPosition(); }, [](SHVec3 const& value) {}, false, "Position", 0.1f, "%.3f", 0.0f, 0.0f, ImGuiSliderFlags_ReadOnly); + SHEditorWidgets::DragVec3("Position", { "X", "Y", "Z" }, [component] {return component->GetPosition(); }, [](SHVec3 const& value) {}, false, "Position", 0.1f, "%.3f", 0.0f, 0.0f, ImGuiSliderFlags_ReadOnly); //SHEditorWidgets::DragVec3("Rotation", { "X", "Y", "Z" }, [component] {return component->GetRotation(); }, [](SHVec3 const& value) {}, false, "Rotation", 0.1f, "%.3f", 0.0f, 0.0f, ImGuiSliderFlags_ReadOnly); if (rbType == SHRigidBodyComponent::Type::DYNAMIC || rbType == SHRigidBodyComponent::Type::KINEMATIC) //Dynamic or Kinematic only fields { diff --git a/SHADE_Engine/src/Math/Transform/SHTransformSystem.cpp b/SHADE_Engine/src/Math/Transform/SHTransformSystem.cpp index 03de360d..0a0ec092 100644 --- a/SHADE_Engine/src/Math/Transform/SHTransformSystem.cpp +++ b/SHADE_Engine/src/Math/Transform/SHTransformSystem.cpp @@ -246,8 +246,6 @@ namespace SHADE tf.world.position = SHVec3::Transform(tf.local.position, localToWorld); tf.world.scale = tf.local.scale * (parent ? parent->GetLocalScale() : SHVec3::One); - - if (convertRotation) { tf.worldRotation = tf.localRotation + (parent ? parent->GetLocalRotation() : SHVec3::Zero); diff --git a/SHADE_Engine/src/Physics/Dynamics/SHMotionState.cpp b/SHADE_Engine/src/Physics/Dynamics/SHMotionState.cpp new file mode 100644 index 00000000..2cca1c45 --- /dev/null +++ b/SHADE_Engine/src/Physics/Dynamics/SHMotionState.cpp @@ -0,0 +1,100 @@ +/**************************************************************************************** + * \file SHMotionState.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Motion State. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHMotionState.h" + +// Project Headers +#include "Math/SHMathHelpers.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHMotionState::SHMotionState() noexcept + : hasMoved { false } + {} + + SHMotionState::SHMotionState(const SHMotionState& rhs) noexcept + : hasMoved { rhs.hasMoved } + , position { rhs.position } + , prevPosition { rhs.prevPosition } + {} + + SHMotionState::SHMotionState(SHMotionState&& rhs) noexcept + : hasMoved { rhs.hasMoved } + , position { rhs.position } + , prevPosition { rhs.prevPosition } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHMotionState& SHMotionState::operator=(const SHMotionState& rhs) noexcept + { + if (this == &rhs) + return *this; + + hasMoved = rhs.hasMoved; + position = rhs.position; + prevPosition = rhs.prevPosition; + + return *this; + } + + SHMotionState& SHMotionState::operator=(SHMotionState&& rhs) noexcept + { + hasMoved = rhs.hasMoved; + position = rhs.position; + prevPosition = rhs.prevPosition; + + return *this; + } + + SHMotionState::operator bool() const noexcept + { + return hasMoved; + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Member Function Definition */ + /*-----------------------------------------------------------------------------------*/ + + void SHMotionState::ForcePosition(const SHVec3& newPosition) noexcept + { + hasMoved = false; + + prevPosition = newPosition; + position = newPosition; + } + + void SHMotionState::IntegratePosition(const SHVec3& velocity, float dt) noexcept + { + // Velocities are 0 when objects are static or sleeping. We do not want to integrate them here. + // This call should never reach here. + + hasMoved = true; + + prevPosition = position; + position += velocity * dt; + } + + SHVec3 SHMotionState::InterpolatePositions(float factor) const noexcept + { + return SHVec3::ClampedLerp(prevPosition, position, factor); + } + + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHMotionState.h b/SHADE_Engine/src/Physics/Dynamics/SHMotionState.h new file mode 100644 index 00000000..4cff9a8f --- /dev/null +++ b/SHADE_Engine/src/Physics/Dynamics/SHMotionState.h @@ -0,0 +1,93 @@ +/**************************************************************************************** + * \file SHMotionState.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Motion State. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "Math/Vector/SHVec3.h" + +namespace SHADE +{ + /*-------------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-------------------------------------------------------------------------------------*/ + + /** + * @brief + * Encapsulates the motion state of a rigid body in physics. + */ + struct SH_API SHMotionState + { + public: + /*-----------------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------------*/ + + bool hasMoved; + + SHVec3 position; + SHVec3 prevPosition; + + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*-----------------------------------------------------------------------------------*/ + + SHMotionState () noexcept; + SHMotionState (const SHMotionState& rhs) noexcept; + SHMotionState (SHMotionState&& rhs) noexcept; + + ~SHMotionState() = default; + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*-----------------------------------------------------------------------------------*/ + + SHMotionState& operator= (const SHMotionState& rhs) noexcept; + SHMotionState& operator= (SHMotionState&& rhs) noexcept; + + operator bool () const noexcept; + + /*-----------------------------------------------------------------------------------*/ + /* Function Members */ + /*-----------------------------------------------------------------------------------*/ + + /** + * @brief + * Forcefully sets the position. Meant to be used when transform overrides the rigid body + * positions. + * @param newPosition + * The new position to set. + */ + void ForcePosition (const SHVec3& newPosition) noexcept; + + /** + * @brief + * Integrates the positions using velocity with respect to time. + * @param velocity + * The velocity to integrate. + * @param dt + * The delta time to integrate with respect to. + */ + void IntegratePosition (const SHVec3& velocity, float dt) noexcept; + + /** + * @brief + * Interpolates the position between the previous and the last using a given factor. + * @param factor + * The factor to interpolate by. Should be between 0 & 1. + * @returns + * The interpolated position meant for rendering. + */ + SHVec3 InterpolatePositions (float factor) const noexcept; + + }; + + +} // namespace SHADE diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp new file mode 100644 index 00000000..7d6c7c5c --- /dev/null +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp @@ -0,0 +1,122 @@ +/**************************************************************************************** + * \file SHPhysicsWorld.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Physics World. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHPhysicsWorld.h" + +// Project Headers + + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsWorld::SHPhysicsWorld(const WorldSettings& worldSettings) noexcept + : settings { worldSettings } + { + rigidBodies.clear(); + SHLOG_INFO_D("Creating Physics World") + } + + SHPhysicsWorld::~SHPhysicsWorld() noexcept + { + rigidBodies.clear(); + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Member Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsWorld::AddRigidBody(SHRigidBody* rigidBody) noexcept + { + const bool INSERTED = rigidBodies.emplace(rigidBody->entityID, rigidBody).second; + if (!INSERTED) + { + SHLOG_WARNING_D("Attempting to add duplicate rigid body {} to the Physics World!", rigidBody->entityID) + } + } + + void SHPhysicsWorld::RemoveRigidBody(SHRigidBody* rigidBody) noexcept + { + rigidBodies.erase(rigidBody->entityID); + } + + void SHPhysicsWorld::Step(float dt) + { + for (auto* rigidBody : rigidBodies | std::views::values) + integrateForces(*rigidBody, dt); + + for (auto* rigidBody : rigidBodies | std::views::values) + integrateVelocities(*rigidBody, dt); + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Member Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsWorld::integrateForces(SHRigidBody& rigidBody, float dt) const noexcept + { + if (rigidBody.bodyType != SHRigidBody::Type::DYNAMIC) + return; + + // Integrate forces and gravity into linear velocity + const SHVec3 LINEAR_ACCELERATION = rigidBody.accumulatedForce * rigidBody.invMass; + const SHVec3 GRAVITATIONAL_ACCELERATION = rigidBody.IsGravityEnabled() ? settings.gravity * rigidBody.gravityScale : SHVec3::Zero; + + rigidBody.linearVelocity += (LINEAR_ACCELERATION + GRAVITATIONAL_ACCELERATION) * dt; + + // Apply drag (exponentially applied) + rigidBody.linearVelocity *= 1.0f / (1.0f + dt * rigidBody.linearDrag); + } + + void SHPhysicsWorld::integrateVelocities(SHRigidBody& rigidBody, float dt) const noexcept + { + static const auto ENFORCE_CONSTRAINED_VELOCITIES = [](SHRigidBody& rigidBody) + { + // Enforce linear constraints + rigidBody.linearVelocity = SHVec3 + { + rigidBody.GetFreezePositionX() ? 0.0f : rigidBody.linearVelocity.x + , rigidBody.GetFreezePositionY() ? 0.0f : rigidBody.linearVelocity.y + , rigidBody.GetFreezePositionZ() ? 0.0f : rigidBody.linearVelocity.z + }; + + // TODO: Enforce angular constraints + }; + + // Always reset movement flag + rigidBody.motionState.hasMoved = false; + + // Set all velocities of static bodies to 0 + if (rigidBody.bodyType == SHRigidBody::Type::STATIC) + { + rigidBody.linearVelocity = SHVec3::Zero; + } + else // Dynamic & Kinematic bodies + { + // Both dynamic and kinematic can sleep when their velocities are under the thresholds. + if (!rigidBody.IsSleeping()) + { + ENFORCE_CONSTRAINED_VELOCITIES(rigidBody); + + rigidBody.motionState.IntegratePosition(rigidBody.linearVelocity, dt); + // TODO: Integrate orientations + } + } + + // Clear all forces + // We clear forces for static bodies as well for redundancy + rigidBody.ClearForces(); + } +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/SHPhysicsWorld.h b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h similarity index 58% rename from SHADE_Engine/src/Physics/SHPhysicsWorld.h rename to SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h index e872cd3c..eb96a021 100644 --- a/SHADE_Engine/src/Physics/SHPhysicsWorld.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h @@ -10,7 +10,10 @@ #pragma once +#include + // Project Headers +#include "SHRigidBody.h" #include "Math/SHMath.h" #include "SH_API.h" @@ -20,7 +23,7 @@ namespace SHADE /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ - struct SH_API SHPhysicsWorldState + class SH_API SHPhysicsWorld { public: @@ -41,22 +44,55 @@ namespace SHADE bool sleepingEnabled = true; }; - /*---------------------------------------------------------------------------------*/ - /* Data Members */ - /*---------------------------------------------------------------------------------*/ - - WorldSettings settings; - /*---------------------------------------------------------------------------------*/ /* Constructors & Destructor */ /*---------------------------------------------------------------------------------*/ - SHPhysicsWorldState() noexcept; + SHPhysicsWorld (const WorldSettings& worldSettings = WorldSettings{}) noexcept; + ~SHPhysicsWorld () noexcept; + + SHPhysicsWorld (const SHPhysicsWorld&) = delete; + SHPhysicsWorld (SHPhysicsWorld&&) = delete; + + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHPhysicsWorld& operator=(const SHPhysicsWorld&) = delete; + SHPhysicsWorld& operator=(SHPhysicsWorld&&) = delete; /*---------------------------------------------------------------------------------*/ /* Function Members */ /*---------------------------------------------------------------------------------*/ + void AddRigidBody (SHRigidBody* rigidBody) noexcept; + void RemoveRigidBody (SHRigidBody* rigidBody) noexcept; + + void Step (float dt); + + private: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + using RigidBodies = std::unordered_map; + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + WorldSettings settings; + + RigidBodies rigidBodies; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + void integrateForces (SHRigidBody& rigidBody, float dt) const noexcept; + void integrateVelocities (SHRigidBody& rigidBody, float dt) const noexcept; + }; diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp index 1867636c..8f99a8f5 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp @@ -26,7 +26,7 @@ namespace SHADE : entityID { eid } , bodyType { type } , invMass { type == Type::DYNAMIC ? 1.0f : 0.0f } - , linearDrag { type != Type::STATIC ? 0.01f : 0.0f } + , linearDrag { 0.01f } , gravityScale { 1.0f } , flags { 0U } { @@ -45,6 +45,7 @@ namespace SHADE , linearDrag { rhs.linearDrag } , gravityScale { rhs.gravityScale } , flags { rhs.flags } + , motionState { rhs.motionState } { // All other properties are defaulted to 0 to not carry over any potential errors / invalid values. } @@ -56,6 +57,7 @@ namespace SHADE , linearDrag { rhs.linearDrag } , gravityScale { rhs.gravityScale } , flags { rhs.flags } + , motionState { std::move(rhs.motionState) } { // All other properties are defaulted to 0 to not carry over any potential errors / invalid values. } @@ -70,28 +72,36 @@ namespace SHADE if (this == &rhs) return *this; - entityID = rhs.entityID; - bodyType = rhs.bodyType; - invMass = rhs.invMass; - linearDrag = rhs.linearDrag; - gravityScale = rhs.gravityScale; - flags = rhs.flags; + entityID = rhs.entityID; + bodyType = rhs.bodyType; + invMass = rhs.invMass; + linearDrag = rhs.linearDrag; + gravityScale = rhs.gravityScale; + flags = rhs.flags; + motionState = rhs.motionState; // All other properties are defaulted to 0 to not carry over any potential errors / invalid values. + accumulatedForce = SHVec3::Zero; + linearVelocity = SHVec3::Zero; + angularVelocity = SHVec3::Zero; return *this; } SHRigidBody& SHRigidBody::operator=(SHRigidBody&& rhs) noexcept { - entityID = rhs.entityID; - bodyType = rhs.bodyType; - invMass = rhs.invMass; - linearDrag = rhs.linearDrag; - gravityScale = rhs.gravityScale; - flags = rhs.flags; + entityID = rhs.entityID; + bodyType = rhs.bodyType; + invMass = rhs.invMass; + linearDrag = rhs.linearDrag; + gravityScale = rhs.gravityScale; + flags = rhs.flags; + motionState = std::move(rhs.motionState); // All other properties are defaulted to 0 to not carry over any potential errors / invalid values. + accumulatedForce = SHVec3::Zero; + linearVelocity = SHVec3::Zero; + angularVelocity = SHVec3::Zero; return *this; } @@ -105,118 +115,124 @@ namespace SHADE return bodyType; } - float SHRigidBody::GetMass() const noexcept + float SHRigidBody::GetMass() const noexcept { return 1.0f/ invMass; } - float SHRigidBody::GetLinearDrag() const noexcept + float SHRigidBody::GetLinearDrag() const noexcept { return linearDrag; } - float SHRigidBody::GetGravityScale() const noexcept + float SHRigidBody::GetGravityScale() const noexcept { return gravityScale; } - const SHVec3& SHRigidBody::GetAccumulatedForce() const noexcept + const SHVec3& SHRigidBody::GetAccumulatedForce() const noexcept { return accumulatedForce; } - const SHVec3& SHRigidBody::GetLinearVelocity() const noexcept + const SHVec3& SHRigidBody::GetLinearVelocity() const noexcept { return linearVelocity; } - const SHVec3& SHRigidBody::GetAngularVelocity() const noexcept + const SHVec3& SHRigidBody::GetAngularVelocity() const noexcept { return angularVelocity; } // Flags - bool SHRigidBody::IsActive() const noexcept + bool SHRigidBody::IsActive() const noexcept { static constexpr unsigned int FLAG_POS = 0; return flags & (1U << FLAG_POS); } - bool SHRigidBody::IsSleeping() const noexcept + bool SHRigidBody::IsSleeping() const noexcept { static constexpr unsigned int FLAG_POS = 1; return flags & (1U << FLAG_POS); } - bool SHRigidBody::IsSleepingEnabled() const noexcept + bool SHRigidBody::IsSleepingEnabled() const noexcept { static constexpr unsigned int FLAG_POS = 2; return flags & (1U << FLAG_POS); } - bool SHRigidBody::IsGravityEnabled() const noexcept + bool SHRigidBody::IsGravityEnabled() const noexcept { static constexpr unsigned int FLAG_POS = 3; return flags & (1U << FLAG_POS); } - bool SHRigidBody::IsAutoMassEnabled() const noexcept + bool SHRigidBody::IsAutoMassEnabled() const noexcept { static constexpr unsigned int FLAG_POS = 4; return flags & (1U << FLAG_POS); } - bool SHRigidBody::GetFreezePositionX() const noexcept + bool SHRigidBody::GetFreezePositionX() const noexcept { static constexpr unsigned int FLAG_POS = 10; return flags & (1U << FLAG_POS); } - bool SHRigidBody::GetFreezePositionY() const noexcept + bool SHRigidBody::GetFreezePositionY() const noexcept { static constexpr unsigned int FLAG_POS = 11; return flags & (1U << FLAG_POS); } - bool SHRigidBody::GetFreezePositionZ() const noexcept + bool SHRigidBody::GetFreezePositionZ() const noexcept { static constexpr unsigned int FLAG_POS = 12; return flags & (1U << FLAG_POS); } - bool SHRigidBody::GetFreezeRotationX() const noexcept + bool SHRigidBody::GetFreezeRotationX() const noexcept { static constexpr unsigned int FLAG_POS = 13; return flags & (1U << FLAG_POS); } - bool SHRigidBody::GetFreezeRotationY() const noexcept + bool SHRigidBody::GetFreezeRotationY() const noexcept { static constexpr unsigned int FLAG_POS = 14; return flags & (1U << FLAG_POS); } - bool SHRigidBody::GetFreezeRotationZ() const noexcept + bool SHRigidBody::GetFreezeRotationZ() const noexcept { static constexpr unsigned int FLAG_POS = 15; return flags & (1U << FLAG_POS); } + SHMotionState& SHRigidBody::GetMotionState() noexcept + { + return motionState; + } + + /*-----------------------------------------------------------------------------------*/ /* Setter Functions Definitions */ /*-----------------------------------------------------------------------------------*/ - void SHRigidBody::SetType(Type newType) noexcept + void SHRigidBody::SetType(Type newType) noexcept { if (newType == bodyType) return; - bodyType = newType; - invMass = newType == Type::DYNAMIC ? 1.0f : 0.0f; + bodyType = newType; + invMass = newType == Type::DYNAMIC ? 1.0f : 0.0f; } - void SHRigidBody::SetMass(float newMass) noexcept + void SHRigidBody::SetMass(float newMass) noexcept { if (bodyType != Type::DYNAMIC) { @@ -235,7 +251,7 @@ namespace SHADE // TODO: Recompute inertia tensor } - void SHRigidBody::SetLinearDrag(float newLinearDrag) noexcept + void SHRigidBody::SetLinearDrag(float newLinearDrag) noexcept { if (bodyType == Type::STATIC) { @@ -252,17 +268,17 @@ namespace SHADE linearDrag = newLinearDrag; } - void SHRigidBody::SetGravityScale(float newGravityScale) noexcept + void SHRigidBody::SetGravityScale(float newGravityScale) noexcept { gravityScale = newGravityScale; } - void SHRigidBody::SetLinearVelocity(const SHVec3& newLinearVelocity) noexcept + void SHRigidBody::SetLinearVelocity(const SHVec3& newLinearVelocity) noexcept { linearVelocity = newLinearVelocity; } - void SHRigidBody::SetIsActive(bool isActive) noexcept + void SHRigidBody::SetIsActive(bool isActive) noexcept { static constexpr unsigned int FLAG_POS = 0; static constexpr uint16_t VALUE = 1U << FLAG_POS; @@ -270,7 +286,7 @@ namespace SHADE isActive ? flags |= VALUE : flags &= ~VALUE; } - void SHRigidBody::SetIsSleeping(bool isSleeping) noexcept + void SHRigidBody::SetIsSleeping(bool isSleeping) noexcept { static constexpr unsigned int FLAG_POS = 1; static constexpr uint16_t VALUE = 1U << FLAG_POS; @@ -278,7 +294,7 @@ namespace SHADE isSleeping ? flags |= VALUE : flags &= ~VALUE; } - void SHRigidBody::SetSleepingEnabled(bool enableSleeping) noexcept + void SHRigidBody::SetSleepingEnabled(bool enableSleeping) noexcept { static constexpr unsigned int FLAG_POS = 2; static constexpr uint16_t VALUE = 1U << FLAG_POS; @@ -295,7 +311,7 @@ namespace SHADE } } - void SHRigidBody::SetGravityEnabled(bool enableGravity) noexcept + void SHRigidBody::SetGravityEnabled(bool enableGravity) noexcept { static constexpr unsigned int FLAG_POS = 3; static constexpr uint16_t VALUE = 1U << FLAG_POS; @@ -303,7 +319,7 @@ namespace SHADE enableGravity ? flags |= VALUE : flags &= ~VALUE; } - void SHRigidBody::SetAutoMassEnabled(bool enableAutoMass) noexcept + void SHRigidBody::SetAutoMassEnabled(bool enableAutoMass) noexcept { static constexpr unsigned int FLAG_POS = 4; static constexpr uint16_t VALUE = 1U << FLAG_POS; @@ -321,7 +337,7 @@ namespace SHADE } } - void SHRigidBody::SetFreezePositionX(bool freezePositionX) noexcept + void SHRigidBody::SetFreezePositionX(bool freezePositionX) noexcept { static constexpr unsigned int FLAG_POS = 10; static constexpr uint16_t VALUE = 1U << FLAG_POS; @@ -338,7 +354,7 @@ namespace SHADE } } - void SHRigidBody::SetFreezePositionY(bool freezePositionY) noexcept + void SHRigidBody::SetFreezePositionY(bool freezePositionY) noexcept { static constexpr unsigned int FLAG_POS = 11; static constexpr uint16_t VALUE = 1U << FLAG_POS; @@ -355,7 +371,7 @@ namespace SHADE } } - void SHRigidBody::SetFreezePositionZ(bool freezePositionZ) noexcept + void SHRigidBody::SetFreezePositionZ(bool freezePositionZ) noexcept { static constexpr unsigned int FLAG_POS = 12; static constexpr uint16_t VALUE = 1U << FLAG_POS; @@ -372,7 +388,7 @@ namespace SHADE } } - void SHRigidBody::SetFreezeRotationX(bool freezeRotationX) noexcept + void SHRigidBody::SetFreezeRotationX(bool freezeRotationX) noexcept { static constexpr unsigned int FLAG_POS = 13; static constexpr uint16_t VALUE = 1U << FLAG_POS; @@ -389,7 +405,7 @@ namespace SHADE } } - void SHRigidBody::SetFreezeRotationY(bool freezeRotationY) noexcept + void SHRigidBody::SetFreezeRotationY(bool freezeRotationY) noexcept { static constexpr unsigned int FLAG_POS = 14; static constexpr uint16_t VALUE = 1U << FLAG_POS; @@ -406,7 +422,7 @@ namespace SHADE } } - void SHRigidBody::SetFreezeRotationZ(bool freezeRotationZ) noexcept + void SHRigidBody::SetFreezeRotationZ(bool freezeRotationZ) noexcept { static constexpr unsigned int FLAG_POS = 15; static constexpr uint16_t VALUE = 1U << FLAG_POS; @@ -424,10 +440,10 @@ namespace SHADE } /*-----------------------------------------------------------------------------------*/ - /* Member Function Definitions */ + /* Public Member Function Definition */ /*-----------------------------------------------------------------------------------*/ - void SHRigidBody::AddForce(const SHVec3& force, const SHVec3& pos) noexcept + void SHRigidBody::AddForce(const SHVec3& force, const SHVec3& pos) noexcept { if (bodyType != Type::DYNAMIC) return; @@ -436,7 +452,7 @@ namespace SHADE // Compute torque when force is offset } - void SHRigidBody::AddImpulse(const SHVec3& impulse, const SHVec3& pos) noexcept + void SHRigidBody::AddImpulse(const SHVec3& impulse, const SHVec3& pos) noexcept { if (bodyType != Type::DYNAMIC) return; @@ -444,7 +460,7 @@ namespace SHADE linearVelocity += impulse * invMass; } - void SHRigidBody::ClearForces() noexcept + void SHRigidBody::ClearForces() noexcept { accumulatedForce = SHVec3::Zero; } diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h index 77b29292..dbf2344f 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h @@ -13,7 +13,7 @@ // Project Headers #include "ECS_Base/SHECSMacros.h" #include "Math/Vector/SHVec3.h" -#include "SH_API.h" +#include "SHMotionState.h" namespace SHADE { @@ -27,6 +27,13 @@ namespace SHADE */ class SH_API SHRigidBody { + private: + /*-----------------------------------------------------------------------------------*/ + /* Friends */ + /*-----------------------------------------------------------------------------------*/ + + friend class SHPhysicsWorld; + public: /*-----------------------------------------------------------------------------------*/ /* Type Definitions */ @@ -83,13 +90,16 @@ namespace SHADE [[nodiscard]] bool GetFreezeRotationY () const noexcept; [[nodiscard]] bool GetFreezeRotationZ () const noexcept; + [[nodiscard]] SHMotionState& GetMotionState () noexcept; + /*-----------------------------------------------------------------------------------*/ /* Setter Functions */ /*-----------------------------------------------------------------------------------*/ /** * @brief - * Changing the type from non-Dynamic to Dynamic will set the default mass. + * Changing the type from non-Dynamic to Dynamic will set the default + * mass and drag values. */ void SetType (Type newType) noexcept; @@ -111,7 +121,6 @@ namespace SHADE void SetGravityScale (float newGravityScale) noexcept; - void SetLinearVelocity (const SHVec3& newLinearVelocity) noexcept; // Flags @@ -164,22 +173,24 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ // The entityID here is only meant for linking with the actual component in the engine. - EntityID entityID; + EntityID entityID; - Type bodyType; + Type bodyType; - float invMass; - float linearDrag; + float invMass; + float linearDrag; - float gravityScale; + float gravityScale; + + SHVec3 accumulatedForce; - SHVec3 accumulatedForce; - - SHVec3 linearVelocity; - SHVec3 angularVelocity; + SHVec3 linearVelocity; + SHVec3 angularVelocity; // aZ aY aX pZ pY pX 0 0 0 0 0 autoMass enableGravity enableSleeping sleeping active - uint16_t flags; + uint16_t flags; + + SHMotionState motionState; }; } // namespace SHADE diff --git a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.cpp b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.cpp index 44452868..b6e5ac40 100644 --- a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.cpp +++ b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.cpp @@ -44,9 +44,8 @@ namespace SHADE delete rigidBody; } - /*-----------------------------------------------------------------------------------*/ - /* Operator Overload Definitions */ + /* Operator Overload Definitions */ /*-----------------------------------------------------------------------------------*/ SHPhysicsObject& SHPhysicsObject::operator=(const SHPhysicsObject& rhs) noexcept @@ -71,4 +70,14 @@ namespace SHADE return *this; } + + /*-----------------------------------------------------------------------------------*/ + /* Public Member Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHPhysicsObject::IsEmpty() const noexcept + { + return rigidBody == nullptr; + } + } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.h b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.h index 2be5844d..f297a6fb 100644 --- a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.h +++ b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.h @@ -48,5 +48,11 @@ namespace SHADE SHPhysicsObject& operator=(const SHPhysicsObject& rhs) noexcept; SHPhysicsObject& operator=(SHPhysicsObject&& rhs) noexcept; + + /*-----------------------------------------------------------------------------------*/ + /* Member Functions */ + /*-----------------------------------------------------------------------------------*/ + + bool IsEmpty() const noexcept; }; } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.cpp b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.cpp index dccca97b..e6e92ba5 100644 --- a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.cpp +++ b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.cpp @@ -14,6 +14,7 @@ #include "SHPhysicsObjectManager.h" // Project Headers +#include "Math/Transform/SHTransformComponent.h" #include "Physics/Interface/SHColliderComponent.h" #include "Physics/Interface/SHCollisionShape.h" #include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" @@ -21,16 +22,25 @@ namespace SHADE { + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsObjectManager::~SHPhysicsObjectManager() noexcept + { + RemoveAllObjects(); + } + /*-----------------------------------------------------------------------------------*/ /* Getter Function Definitions */ /*-----------------------------------------------------------------------------------*/ - SHPhysicsObjectManager::EntityObjectMap& SHPhysicsObjectManager::GetPhysicsObjects() noexcept + SHPhysicsObjectManager::EntityObjectMap& SHPhysicsObjectManager::GetPhysicsObjects() noexcept { return physicsObjects; } - const SHPhysicsObject* SHPhysicsObjectManager::GetPhysicsObject(EntityID entityID) noexcept + const SHPhysicsObject* SHPhysicsObjectManager::GetPhysicsObject(EntityID entityID) noexcept { const auto PHYSICS_OBJECT_ITERATOR = physicsObjects.find(entityID); if (PHYSICS_OBJECT_ITERATOR == physicsObjects.end()) @@ -68,6 +78,17 @@ namespace SHADE , static_cast(rigidBodyComponent->GetType()) }; + SHMotionState& motionState = physicsObject->rigidBody->GetMotionState(); + + if (const auto* TRANSFORM_COMPONENT = SHComponentManager::GetComponent(entityID); TRANSFORM_COMPONENT) + { + motionState.ForcePosition(TRANSFORM_COMPONENT->GetWorldPosition()); + } + else + { + motionState.ForcePosition(SHVec3::Zero); + } + // Link with the component rigidBodyComponent->SetRigidBody(physicsObject->rigidBody); @@ -85,7 +106,7 @@ namespace SHADE physicsObject->rigidBody = nullptr; // Destroy empty physics objects - if (physicsObject->rigidBody == nullptr) + if (physicsObject->IsEmpty()) destroyPhysicsObject(entityID); } @@ -101,7 +122,15 @@ namespace SHADE void SHPhysicsObjectManager::RemoveCollider(EntityID entityID) noexcept { - // Unlink with the component + const auto PHYSICS_OBJECT_ITERATOR = physicsObjects.find(entityID); + if (PHYSICS_OBJECT_ITERATOR != physicsObjects.end()) + { + SHPhysicsObject* physicsObject = &PHYSICS_OBJECT_ITERATOR->second; + + // Destroy empty physics objects + if (physicsObject->IsEmpty()) + destroyPhysicsObject(entityID); + } // TODO: Broadcast event } @@ -120,6 +149,11 @@ namespace SHADE // TODO: Broadcast event } + void SHPhysicsObjectManager::RemoveAllObjects() noexcept + { + physicsObjects.clear(); + } + /*-----------------------------------------------------------------------------------*/ /* Private Member Function Definitions */ /*-----------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.h b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.h index 17a1d3a1..6449a2d8 100644 --- a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.h +++ b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.h @@ -40,7 +40,8 @@ namespace SHADE /* Constructors & Destructor */ /*-----------------------------------------------------------------------------------*/ - SHPhysicsObjectManager() noexcept = default; + SHPhysicsObjectManager () noexcept = default; + ~SHPhysicsObjectManager () noexcept; /*-----------------------------------------------------------------------------------*/ /* Getter Functions */ @@ -107,6 +108,13 @@ namespace SHADE */ void RemoveCollisionShape (EntityID entityID, uint8_t shapeID) noexcept; + /** + * @brief + * Removes all physics object in the manager. This is only meant to be called when + * the world is being destroyed or the scene is being changed. + */ + void RemoveAllObjects () noexcept; + private: /*-----------------------------------------------------------------------------------*/ /* Data Members */ diff --git a/SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.cpp b/SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.cpp index 5924c612..ddc51fbc 100644 --- a/SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.cpp +++ b/SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.cpp @@ -182,6 +182,15 @@ namespace SHADE return SHVec3::Zero; } + SHVec3 SHRigidBodyComponent::GetPosition() const noexcept + { + if (rigidBody) + return rigidBody->GetMotionState().position; + + return SHVec3::Zero; + } + + /*-----------------------------------------------------------------------------------*/ /* Setter Functions Definitions */ /*-----------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h b/SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h index ae0bd3ec..bfb1717b 100644 --- a/SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h +++ b/SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h @@ -97,6 +97,8 @@ namespace SHADE [[nodiscard]] SHVec3 GetLinearVelocity () const noexcept; [[nodiscard]] SHVec3 GetAngularVelocity () const noexcept; + [[nodiscard]] SHVec3 GetPosition () const noexcept; + /*---------------------------------------------------------------------------------*/ /* Setter Functions */ /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/SHPhysicsWorld.cpp b/SHADE_Engine/src/Physics/SHPhysicsWorld.cpp deleted file mode 100644 index d54d3f86..00000000 --- a/SHADE_Engine/src/Physics/SHPhysicsWorld.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/**************************************************************************************** - * \file SHPhysicsWorld.cpp - * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Implementation for a Physics World. - * - * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or - * disclosure of this file or its contents without the prior written consent - * of DigiPen Institute of Technology is prohibited. -****************************************************************************************/ - -#include - -// Primary Header -#include "SHPhysicsWorld.h" - -namespace SHADE -{ - /*-----------------------------------------------------------------------------------*/ - /* Constructors & Destructor Definitions */ - /*-----------------------------------------------------------------------------------*/ - - - /*-----------------------------------------------------------------------------------*/ - /* Public Function Members Definitions */ - /*-----------------------------------------------------------------------------------*/ - -} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp index 9b1c0015..5f18c450 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp @@ -15,6 +15,7 @@ // Project Headers #include "ECS_Base/Managers/SHSystemManager.h" +#include "Math/Transform/SHTransformComponent.h" #include "Scripting/SHScriptEngine.h" @@ -39,24 +40,40 @@ namespace SHADE if (scriptingSystem == nullptr) { - SHLOGV_ERROR("Unable to invoke collision and trigger script events due to missing SHScriptEngine!"); + SHLOGV_ERROR("Unable to invoke collision and trigger script events due to missing SHScriptEngine!"); } - // Interpolate transforms for rendering + const float FACTOR = static_cast(physicsSystem->interpolationFactor); + + // Interpolate transforms for rendering. + // Only rigid bodies can move due to physics, so we run through the rigid body component dense set. + const auto& RIGIDBODY_DENSE = SHComponentManager::GetDense(); + for (auto& rigidBodyComponent : RIGIDBODY_DENSE) + { + auto* transformComponent = SHComponentManager::GetComponent_s(rigidBodyComponent.GetEID()); + if (!transformComponent) + continue; + + if (!rigidBodyComponent.rigidBody) + continue; + + if (const SHMotionState& MOTION_STATE = rigidBodyComponent.rigidBody->GetMotionState(); MOTION_STATE) + { + SHVec3 renderPosition = rigidBodyComponent.IsInterpolating() + ? MOTION_STATE.InterpolatePositions(FACTOR) + : MOTION_STATE.position; + + /* + * TODO: Test if the scene graph transforms abides by setting world position. Collisions will ignore the scene graph hierarchy. + */ + + transformComponent->SetWorldPosition(renderPosition); + // TODO: SetOrientation + } + } // Collision & Trigger messages if (scriptingSystem != nullptr) scriptingSystem->ExecuteCollisionFunctions(); - } - - /*-----------------------------------------------------------------------------------*/ - /* Private Function Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHPhysicsSystem::PhysicsPostUpdate::syncTransforms(SHRigidBodyComponent* rbComponent) const noexcept - { - - } - } diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp index a0f348f4..eda69161 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp @@ -36,27 +36,27 @@ namespace SHADE auto* physicsSystem = reinterpret_cast(GetSystem()); // Get all physics objects & sync transforms - for (auto& physicsObject : physicsSystem->physicsObjectManager.GetPhysicsObjects() | std::views::values) - syncTransforms(&physicsObject); - } - - /*-----------------------------------------------------------------------------------*/ - /* Private Function Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHPhysicsSystem::PhysicsPreUpdate::syncTransforms(SHPhysicsObject* physicsObject) const noexcept - { - const EntityID EID = physicsObject->entityID; - - // Get relevant components: Transform, Rigidbody & Collider - const auto* TRANSFORM_COMPONENT = SHComponentManager::GetComponent_s(EID); - - auto* rigidBodyComponent = SHComponentManager::GetComponent_s(EID); - auto* colliderComponent = SHComponentManager::GetComponent_s(EID); - - if (TRANSFORM_COMPONENT && TRANSFORM_COMPONENT->HasChanged()) + for (auto& [entityID, physicsObject] : physicsSystem->physicsObjectManager.GetPhysicsObjects()) { - // Sync the objects transforms + const auto* TRANSFORM_COMPONENT = SHComponentManager::GetComponent_s(entityID); + + // We assume that all engine components and physics object components have been successfully linked + + if (TRANSFORM_COMPONENT && TRANSFORM_COMPONENT->HasChanged()) + { + // Sync the objects transforms + const SHVec3 WORLD_POS = TRANSFORM_COMPONENT->GetWorldPosition(); + + if (physicsObject.rigidBody) + { + SHMotionState& motionState = physicsObject.rigidBody->GetMotionState(); + + motionState.ForcePosition(TRANSFORM_COMPONENT->GetWorldPosition()); + // TODO: Force Orientation + } + } + + // TODO: Sync Collider Transform with World Transform } } } diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsUpdateRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsUpdateRoutine.cpp index 77da8706..6ae53b31 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsUpdateRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsUpdateRoutine.cpp @@ -47,7 +47,11 @@ namespace SHADE int count = 0; while (accumulatedTime > FIXED_DT) { + if (scriptEngine) + scriptEngine->ExecuteFixedUpdates(); + if (physicsSystem->physicsWorld) + physicsSystem->physicsWorld->Step(static_cast(FIXED_DT)); accumulatedTime -= FIXED_DT; ++count; diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp index 98a69087..b4ff6961 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp @@ -28,10 +28,11 @@ namespace SHADE /* Constructors & Destructor Definitions */ /*-----------------------------------------------------------------------------------*/ - SHPhysicsSystem::SHPhysicsSystem() + SHPhysicsSystem::SHPhysicsSystem() noexcept : worldUpdated { false } , interpolationFactor { 0.0 } , fixedDT { DEFAULT_FIXED_STEP } + , physicsWorld { nullptr } { // Add more events here to register them @@ -45,6 +46,11 @@ namespace SHADE #endif } + SHPhysicsSystem::~SHPhysicsSystem() noexcept + { + delete physicsWorld; + } + /*-----------------------------------------------------------------------------------*/ /* Getter Function Definitions */ /*-----------------------------------------------------------------------------------*/ @@ -141,24 +147,66 @@ namespace SHADE SHEventHandle SHPhysicsSystem::onSceneInit(SHEventPtr onSceneInitEvent) { /* - * TODO: * If a world already exists, destroy it - * Recreate the world + * Recreate the world. * * If there is an editor and editor mode is already playing, link all entities with the world immediately. */ + // Destroy an existing world if there is one. + //! This should almost never happen. + if (physicsWorld) + { + // Remove all references of physics objects from the world + for (const auto& PHYSICS_OBJECT : physicsObjectManager.GetPhysicsObjects() | std::views::values) + { + if (PHYSICS_OBJECT.rigidBody) + physicsWorld->RemoveRigidBody(PHYSICS_OBJECT.rigidBody); + } + + delete physicsWorld; + physicsWorld = nullptr; + } + + // Create the physics world + physicsWorld = new SHPhysicsWorld; + + #ifdef SHEDITOR + + // Link all entities with the world if editor is already playing. + // This is for handling scene changes while the editor is active. + + const auto* EDITOR = SHSystemManager::GetSystem(); + if (EDITOR && EDITOR->editorState == SHEditor::State::PLAY) + { + for (const auto& PHYSICS_OBJECT : physicsObjectManager.GetPhysicsObjects() | std::views::values) + { + if (PHYSICS_OBJECT.rigidBody) + physicsWorld->AddRigidBody(PHYSICS_OBJECT.rigidBody); + } + } + + #endif + return onSceneInitEvent.get()->handle; } SHEventHandle SHPhysicsSystem::onSceneExit(SHEventPtr onSceneExitEvent) { /* - * TODO: * Destroy the physics world. * Destroy all physics objects. */ + for (const auto& PHYSICS_OBJECT : physicsObjectManager.GetPhysicsObjects() | std::views::values) + { + if (PHYSICS_OBJECT.rigidBody) + physicsWorld->RemoveRigidBody(PHYSICS_OBJECT.rigidBody); + } + + delete physicsWorld; + physicsWorld = nullptr; + return onSceneExitEvent.get()->handle; } @@ -181,8 +229,17 @@ namespace SHADE // Link engine components with physics object component if (IS_RIGID_BODY) + { physicsObjectManager.AddRigidBody(EID); + if (physicsWorld) + { + auto* rigidBody = physicsObjectManager.GetPhysicsObject(EID)->rigidBody; + physicsWorld->AddRigidBody(rigidBody); + } + } + + if (IS_COLLIDER) physicsObjectManager.AddCollider(EID); } @@ -209,7 +266,16 @@ namespace SHADE // Link engine components with physics object component if (IS_RIGID_BODY) + { + if (physicsWorld) + { + auto* rigidBody = physicsObjectManager.GetPhysicsObject(EID)->rigidBody; + physicsWorld->RemoveRigidBody(rigidBody); + } + physicsObjectManager.RemoveRigidBody(EID); + } + if (IS_COLLIDER) physicsObjectManager.RemoveCollider(EID); @@ -222,10 +288,15 @@ namespace SHADE SHEventHandle SHPhysicsSystem::onEditorPlay(SHEventPtr onEditorPlayEvent) { - /* - * TODO: - * Link all entities with the world. - */ + // Add all physics components to the physics world + for (const auto& PHYSICS_OBJECT : physicsObjectManager.GetPhysicsObjects() | std::views::values) + { + // Add rigid body if it exists + if (PHYSICS_OBJECT.rigidBody) + physicsWorld->AddRigidBody(PHYSICS_OBJECT.rigidBody); + + // TODO: Add Collider if it exists + } return onEditorPlayEvent.get()->handle; } diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h index 1562241f..7d7dece5 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h @@ -16,6 +16,7 @@ #include "ECS_Base/System/SHFixedSystemRoutine.h" #include "Events/SHEvent.h" +#include "Physics/Dynamics/SHPhysicsWorld.h" #include "Physics/Interface/PhysicsObject/SHPhysicsObjectManager.h" #include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" #include "Physics/Interface/SHColliderComponent.h" @@ -34,7 +35,8 @@ namespace SHADE /* Constructors & Destructor */ /*---------------------------------------------------------------------------------*/ - SHPhysicsSystem(); + SHPhysicsSystem () noexcept; + ~SHPhysicsSystem() noexcept; /*---------------------------------------------------------------------------------*/ /* Getter Functions */ @@ -75,9 +77,6 @@ namespace SHADE public: PhysicsPreUpdate(); void Execute(double dt) noexcept override; - - private: - void syncTransforms(SHPhysicsObject* physicsObject) const noexcept; }; /** @@ -103,9 +102,6 @@ namespace SHADE public: PhysicsPostUpdate(); void Execute(double dt) noexcept override; - - private: - void syncTransforms(SHRigidBodyComponent* rbComponent) const noexcept; }; private: @@ -136,6 +132,8 @@ namespace SHADE // Sub-systems / managers + SHPhysicsWorld* physicsWorld; + SHPhysicsObjectManager physicsObjectManager; /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.h b/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.h index 0664f367..ed190cc9 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.h +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.h @@ -49,8 +49,9 @@ namespace SHADE /* Static Usage Functions */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] static const std::vector& GetCollisionInfo() noexcept; - [[nodiscard]] static const std::vector& GetTriggerInfo () noexcept; - [[nodiscard]] static double GetFixedDT () noexcept; + [[nodiscard]] static const std::vector& GetCollisionInfo () noexcept; + [[nodiscard]] static const std::vector& GetTriggerInfo () noexcept; + [[nodiscard]] static double GetFixedDT () noexcept; + [[nodiscard]] static int GetFixedUpdateRate () noexcept; }; } From 9a5dc52d776f47aaf77478289a6b2d185dc58ad5 Mon Sep 17 00:00:00 2001 From: mushgunAX Date: Mon, 5 Dec 2022 23:25:43 +0800 Subject: [PATCH 030/275] Minor fixes to input manager - Pass binding names into functions by const reference instead of by value - Fixed oversight of not being able to modify or read mouse Y positive multiplier for a binding --- SHADE_Engine/src/Input/SHInputManager.cpp | 24 ++++---- SHADE_Engine/src/Input/SHInputManager.h | 70 +++++++++++------------ 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/SHADE_Engine/src/Input/SHInputManager.cpp b/SHADE_Engine/src/Input/SHInputManager.cpp index 4849a772..f6b58a94 100644 --- a/SHADE_Engine/src/Input/SHInputManager.cpp +++ b/SHADE_Engine/src/Input/SHInputManager.cpp @@ -575,7 +575,7 @@ namespace SHADE } //Only get of largest magnitude - double SHInputManager::GetBindingAxis(std::string bindingName, size_t cNum) noexcept + double SHInputManager::GetBindingAxis(std::string const& bindingName, size_t cNum) noexcept { //Over keycodes, prioritise positive for (SH_KEYCODE k : bindings[bindingName].positiveKeyCodes) @@ -606,7 +606,7 @@ namespace SHADE return largestMagnitude; } - bool SHInputManager::GetBindingPositiveButton(std::string bindingName, size_t cNum) noexcept + bool SHInputManager::GetBindingPositiveButton(std::string const& bindingName, size_t cNum) noexcept { if (cNum >= XUSER_MAX_COUNT) return false; @@ -625,7 +625,7 @@ namespace SHADE return false; } - bool SHInputManager::GetBindingNegativeButton(std::string bindingName, size_t cNum) noexcept + bool SHInputManager::GetBindingNegativeButton(std::string const& bindingName, size_t cNum) noexcept { if (cNum >= XUSER_MAX_COUNT) return false; @@ -644,7 +644,7 @@ namespace SHADE return false; } - bool SHInputManager::GetBindingPositiveButtonDown(std::string bindingName, size_t cNum) noexcept + bool SHInputManager::GetBindingPositiveButtonDown(std::string const& bindingName, size_t cNum) noexcept { if (cNum >= XUSER_MAX_COUNT) return false; @@ -663,7 +663,7 @@ namespace SHADE return false; } - bool SHInputManager::GetBindingNegativeButtonDown(std::string bindingName, size_t cNum) noexcept + bool SHInputManager::GetBindingNegativeButtonDown(std::string const& bindingName, size_t cNum) noexcept { if (cNum >= XUSER_MAX_COUNT) return false; @@ -682,7 +682,7 @@ namespace SHADE return false; } - bool SHInputManager::GetBindingPositiveButtonUp(std::string bindingName, size_t cNum) noexcept + bool SHInputManager::GetBindingPositiveButtonUp(std::string const& bindingName, size_t cNum) noexcept { if (cNum >= XUSER_MAX_COUNT) return false; @@ -701,7 +701,7 @@ namespace SHADE return false; } - bool SHInputManager::GetBindingNegativeButtonUp(std::string bindingName, size_t cNum) noexcept + bool SHInputManager::GetBindingNegativeButtonUp(std::string const& bindingName, size_t cNum) noexcept { if (cNum >= XUSER_MAX_COUNT) return false; @@ -721,7 +721,7 @@ namespace SHADE } //Fetches longest hold time - double SHInputManager::GetBindingPositiveHeldTime(std::string bindingName, size_t cNum) noexcept + double SHInputManager::GetBindingPositiveHeldTime(std::string const& bindingName, size_t cNum) noexcept { if (cNum >= XUSER_MAX_COUNT) return 0.0; @@ -742,7 +742,7 @@ namespace SHADE return maxHeldTime; } - double SHInputManager::GetBindingNegativeHeldTime(std::string bindingName, size_t cNum) noexcept + double SHInputManager::GetBindingNegativeHeldTime(std::string const& bindingName, size_t cNum) noexcept { if (cNum >= XUSER_MAX_COUNT) return 0.0; @@ -764,7 +764,7 @@ namespace SHADE } //Fetches shortest release time - double SHInputManager::GetBindingPositiveReleasedTime(std::string bindingName, size_t cNum) noexcept + double SHInputManager::GetBindingPositiveReleasedTime(std::string const& bindingName, size_t cNum) noexcept { if (cNum >= XUSER_MAX_COUNT) return 0.0; @@ -785,7 +785,7 @@ namespace SHADE return minReleaseTime; } - double SHInputManager::GetBindingNegativeReleasedTime(std::string bindingName, size_t cNum) noexcept + double SHInputManager::GetBindingNegativeReleasedTime(std::string const& bindingName, size_t cNum) noexcept { if (cNum >= XUSER_MAX_COUNT) return 0.0; @@ -808,7 +808,7 @@ namespace SHADE //Only for mouse movement //Get largest delta - double SHInputManager::GetBindingMouseVelocity(std::string bindingName, size_t cNum) noexcept + double SHInputManager::GetBindingMouseVelocity(std::string const& bindingName, size_t cNum) noexcept { if (cNum >= XUSER_MAX_COUNT) return 0.0; diff --git a/SHADE_Engine/src/Input/SHInputManager.h b/SHADE_Engine/src/Input/SHInputManager.h index 04e5871d..ce3e69aa 100644 --- a/SHADE_Engine/src/Input/SHInputManager.h +++ b/SHADE_Engine/src/Input/SHInputManager.h @@ -319,8 +319,8 @@ namespace SHADE std::set negativeControllerCodes; //Mouse movement mapped to axes? - double mouseXPositiveMultiplier; - double mouseYPositiveMultiplier; + double mouseXPositiveMultiplier = 0.0f; + double mouseYPositiveMultiplier = 0.0f; }; public: @@ -484,7 +484,7 @@ namespace SHADE } /*------------------------------------------------------------------------*/ - /* Input state accessors (KB & M) */ + /* Input state accessors (Controller) */ /*------------------------------------------------------------------------*/ //How many controller inputs of any kind are being used now @@ -622,14 +622,14 @@ namespace SHADE /*------------------------------------------------------------------------*/ //Add a new binding to the map - static inline void BindingsAdd(std::string newBindingName) noexcept + static inline void BindingsAdd(std::string const& newBindingName) noexcept { bindings.insert({ newBindingName, SHLogicalBindingData() }); } //Remove a binding from the map //Returns 1 if found and removed, 0 if not found - static inline size_t BindingsRemove(std::string targetBindingName) noexcept + static inline size_t BindingsRemove(std::string const& targetBindingName) noexcept { return bindings.erase(targetBindingName); } @@ -647,13 +647,13 @@ namespace SHADE } //Check positive keycodes to binding - static inline std::set const& BindingsGetPositiveKeyCodes(std::string bindingName) noexcept + static inline std::set const& BindingsGetPositiveKeyCodes(std::string const& bindingName) noexcept { return bindings[bindingName].positiveKeyCodes; } //Add positive SH_KEYCODE to binding - static inline void BindingsAddPositiveKeyCode(std::string targetBindingName, + static inline void BindingsAddPositiveKeyCode(std::string const& targetBindingName, SH_KEYCODE toAdd) noexcept { bindings[targetBindingName].positiveKeyCodes.insert(toAdd); @@ -661,20 +661,20 @@ namespace SHADE //Remove positive SH_KEYCODE from binding //If toRemove found and removed, returns 1. Otherwise, 0. - static inline size_t BindingsRemovePositiveKeyCode(std::string targetBindingName, + static inline size_t BindingsRemovePositiveKeyCode(std::string const& targetBindingName, SH_KEYCODE toRemove) noexcept { return bindings[targetBindingName].positiveKeyCodes.erase(toRemove); } //Check negative keycodes to binding - static inline std::set const& BindingsGetNegativeKeyCodes(std::string bindingName) noexcept + static inline std::set const& BindingsGetNegativeKeyCodes(std::string const& bindingName) noexcept { return bindings[bindingName].negativeKeyCodes; } //Add negative SH_KEYCODE to binding - static inline void BindingsAddNegativeKeyCode(std::string targetBindingName, + static inline void BindingsAddNegativeKeyCode(std::string const& targetBindingName, SH_KEYCODE toAdd) noexcept { bindings[targetBindingName].negativeKeyCodes.insert(toAdd); @@ -682,20 +682,20 @@ namespace SHADE //Remove negative SH_KEYCODE from binding //If toRemove found and removed, returns 1. Otherwise, 0. - static inline size_t BindingsRemoveNegativeKeyCode(std::string targetBindingName, + static inline size_t BindingsRemoveNegativeKeyCode(std::string const& targetBindingName, SH_KEYCODE toRemove) noexcept { return bindings[targetBindingName].negativeKeyCodes.erase(toRemove); } //Check positive controllercodes to binding - static inline std::set const& BindingsGetPositiveControllerCodes(std::string bindingName) noexcept + static inline std::set const& BindingsGetPositiveControllerCodes(std::string const& bindingName) noexcept { return bindings[bindingName].positiveControllerCodes; } //Add positive SH_CONTROLLERCODE to binding - static inline void BindingsAddPositiveControllerCode(std::string targetBindingName, + static inline void BindingsAddPositiveControllerCode(std::string const& targetBindingName, SH_CONTROLLERCODE toAdd) noexcept { bindings[targetBindingName].positiveControllerCodes.insert(toAdd); @@ -703,20 +703,20 @@ namespace SHADE //Remove positive SH_CONTROLLERCODE from binding //If toRemove found and removed, returns 1. Otherwise, 0. - static inline size_t BindingsRemovePositiveControllerCode(std::string targetBindingName, + static inline size_t BindingsRemovePositiveControllerCode(std::string const& targetBindingName, SH_CONTROLLERCODE toRemove) noexcept { return bindings[targetBindingName].positiveControllerCodes.erase(toRemove); } //Check negative controllercodes to binding - static inline std::set const& BindingsGetNegativeControllerCodes(std::string bindingName) noexcept + static inline std::set const& BindingsGetNegativeControllerCodes(std::string const& bindingName) noexcept { return bindings[bindingName].negativeControllerCodes; } //Add negative SH_CONTROLLERCODE to binding - static inline void BindingsAddNegativeControllerCode(std::string targetBindingName, + static inline void BindingsAddNegativeControllerCode(std::string const& targetBindingName, SH_CONTROLLERCODE toAdd) noexcept { bindings[targetBindingName].negativeControllerCodes.insert(toAdd); @@ -724,7 +724,7 @@ namespace SHADE //Remove negative SH_CONTROLLERCODE from binding //If toRemove found and removed, returns 1. Otherwise, 0. - static inline size_t BindingsRemoveNegativeControllerCode(std::string targetBindingName, + static inline size_t BindingsRemoveNegativeControllerCode(std::string const& targetBindingName, SH_CONTROLLERCODE toRemove) noexcept { return bindings[targetBindingName].negativeControllerCodes.erase(toRemove); @@ -732,57 +732,57 @@ namespace SHADE //Mouse movement bindings - static inline double const BindingsGetMouseXPositiveMultiplier(std::string bindingName) noexcept + static inline double const BindingsGetMouseXPositiveMultiplier(std::string const& bindingName) noexcept { return bindings[bindingName].mouseXPositiveMultiplier; } - static inline void BindingsSetMouseXPositiveMultiplier(std::string bindingName, double newValue) noexcept + static inline void BindingsSetMouseXPositiveMultiplier(std::string const& bindingName, double newValue) noexcept { bindings[bindingName].mouseXPositiveMultiplier = newValue; } - static inline double const BindingsGetMouseYPositiveMultiplier(std::string bindingName) noexcept + static inline double const BindingsGetMouseYPositiveMultiplier(std::string const& bindingName) noexcept { - return bindings[bindingName].mouseXPositiveMultiplier; + return bindings[bindingName].mouseYPositiveMultiplier; } - static inline void BindingsSetMouseYPositiveMultiplier(std::string bindingName, double newValue) noexcept + static inline void BindingsSetMouseYPositiveMultiplier(std::string const& bindingName, double newValue) noexcept { - bindings[bindingName].mouseXPositiveMultiplier = newValue; + bindings[bindingName].mouseYPositiveMultiplier = newValue; } //Get the axis value of binding, between -1 and 1 - static double GetBindingAxis(std::string bindingName, size_t controllerNumber = 0) noexcept; + static double GetBindingAxis(std::string const& bindingName, size_t controllerNumber = 0) noexcept; //Whether binding is being held or not //Does not work for mouse movement - static bool GetBindingPositiveButton(std::string bindingName, size_t controllerNumber = 0) noexcept; - static bool GetBindingNegativeButton(std::string bindingName, size_t controllerNumber = 0) noexcept; + static bool GetBindingPositiveButton(std::string const& bindingName, size_t controllerNumber = 0) noexcept; + static bool GetBindingNegativeButton(std::string const& bindingName, size_t controllerNumber = 0) noexcept; //Whether binding is pressed down IN THIS FRAME ONLY //Does not work for mouse movement - static bool GetBindingPositiveButtonDown(std::string bindingName, size_t controllerNumber = 0) noexcept; - static bool GetBindingNegativeButtonDown(std::string bindingName, size_t controllerNumber = 0) noexcept; + static bool GetBindingPositiveButtonDown(std::string const& bindingName, size_t controllerNumber = 0) noexcept; + static bool GetBindingNegativeButtonDown(std::string const& bindingName, size_t controllerNumber = 0) noexcept; //Whether binding is released IN THIS FRAME ONLY //Does not work for mouse movement - static bool GetBindingPositiveButtonUp(std::string bindingName, size_t controllerNumber = 0) noexcept; - static bool GetBindingNegativeButtonUp(std::string bindingName, size_t controllerNumber = 0) noexcept; + static bool GetBindingPositiveButtonUp(std::string const& bindingName, size_t controllerNumber = 0) noexcept; + static bool GetBindingNegativeButtonUp(std::string const& bindingName, size_t controllerNumber = 0) noexcept; //Binding times //Does not work for mouse movement - static double GetBindingPositiveHeldTime(std::string bindingName, size_t controllerNumber = 0) noexcept; - static double GetBindingNegativeHeldTime(std::string bindingName, size_t controllerNumber = 0) noexcept; + static double GetBindingPositiveHeldTime(std::string const& bindingName, size_t controllerNumber = 0) noexcept; + static double GetBindingNegativeHeldTime(std::string const& bindingName, size_t controllerNumber = 0) noexcept; //Does not work for mouse movement - static double GetBindingPositiveReleasedTime(std::string bindingName, size_t controllerNumber = 0) noexcept; - static double GetBindingNegativeReleasedTime(std::string bindingName, size_t controllerNumber = 0) noexcept; + static double GetBindingPositiveReleasedTime(std::string const& bindingName, size_t controllerNumber = 0) noexcept; + static double GetBindingNegativeReleasedTime(std::string const& bindingName, size_t controllerNumber = 0) noexcept; //Binding mouse velocity //Only for mouse movement - static double GetBindingMouseVelocity(std::string bindingName, size_t controllerNumber = 0) noexcept; + static double GetBindingMouseVelocity(std::string const& bindingName, size_t controllerNumber = 0) noexcept; /*------------------------------------------------------------------------*/ /* Other Functions */ From 4f1007e6be932430cc4e14efe0cdad418511a416 Mon Sep 17 00:00:00 2001 From: maverickdgg Date: Tue, 6 Dec 2022 13:09:47 +0800 Subject: [PATCH 031/275] Added scene init exit events --- SHADE_Engine/src/Events/SHEventDefines.h | 4 +++ SHADE_Engine/src/Scene/SHSceneEvents.h | 7 +++++ SHADE_Engine/src/Scene/SHSceneManager.cpp | 31 ++++++++++++++++++++++- 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/SHADE_Engine/src/Events/SHEventDefines.h b/SHADE_Engine/src/Events/SHEventDefines.h index d7bbf5f0..bdc4c505 100644 --- a/SHADE_Engine/src/Events/SHEventDefines.h +++ b/SHADE_Engine/src/Events/SHEventDefines.h @@ -18,4 +18,8 @@ constexpr SHEventIdentifier SH_PHYSICS_COLLIDER_REMOVED_EVENT { 9 }; constexpr SHEventIdentifier SH_EDITOR_ON_PLAY_EVENT { 10 }; constexpr SHEventIdentifier SH_EDITOR_ON_PAUSE_EVENT { 11 }; constexpr SHEventIdentifier SH_EDITOR_ON_STOP_EVENT { 12 }; +constexpr SHEventIdentifier SH_SCENE_INIT_PRE { 13 }; +constexpr SHEventIdentifier SH_SCENE_INIT_POST { 14 }; +constexpr SHEventIdentifier SH_SCENE_EXIT_PRE { 15 }; +constexpr SHEventIdentifier SH_SCENE_EXIT_POST { 16 }; diff --git a/SHADE_Engine/src/Scene/SHSceneEvents.h b/SHADE_Engine/src/Scene/SHSceneEvents.h index c0d7dbc1..5b4fded9 100644 --- a/SHADE_Engine/src/Scene/SHSceneEvents.h +++ b/SHADE_Engine/src/Scene/SHSceneEvents.h @@ -12,6 +12,7 @@ // Project Headers #include "SHSceneNode.h" +#include "Assets/SHAssetMacros.h" namespace SHADE { @@ -38,4 +39,10 @@ namespace SHADE SHSceneNode* childRemoved = nullptr; }; + + struct SHSceneInitExitEvent + { + AssetID sceneAssetID; + }; + } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Scene/SHSceneManager.cpp b/SHADE_Engine/src/Scene/SHSceneManager.cpp index 110aaea6..3614fa52 100644 --- a/SHADE_Engine/src/Scene/SHSceneManager.cpp +++ b/SHADE_Engine/src/Scene/SHSceneManager.cpp @@ -15,6 +15,8 @@ //#include "Rendering/Window/SHRenderingWindow.h" #include "ECS_Base/Managers/SHEntityManager.h" #include "ECS_Base/Managers/SHSystemManager.h" +#include "SHSceneEvents.h" +#include "Events/SHEventManager.hpp" //#include "FRC/SHFrameRateController.h" //#include "ECS_Base/System/SHApplication.h" @@ -54,9 +56,15 @@ namespace SHADE { if (currentScene) { + SHSceneInitExitEvent exitEvent; + exitEvent.sceneAssetID = currentScene->sceneAssetID; + SHEventManager::BroadcastEvent(exitEvent, SH_SCENE_EXIT_PRE); + currentScene->Free(); currentScene->Unload(); SHEntityManager::DestroyAllEntity(); + + SHEventManager::BroadcastEvent(exitEvent, SH_SCENE_EXIT_POST); delete currentScene; } @@ -82,27 +90,48 @@ namespace SHADE nextSceneID = UINT32_MAX; if (currentScene != nullptr) { + currentScene->Load(); + + SHSceneInitExitEvent initEvent; + initEvent.sceneAssetID = currentScene->sceneAssetID; + + SHEventManager::BroadcastEvent(initEvent, SH_SCENE_INIT_PRE); currentScene->Init(); + SHEventManager::BroadcastEvent(initEvent, SH_SCENE_INIT_POST); } } else // restarting scene { nextSceneID = UINT32_MAX; + SHSceneInitExitEvent exitEvent; + exitEvent.sceneAssetID = currentScene->sceneAssetID; + SHEventManager::BroadcastEvent(exitEvent, SH_SCENE_EXIT_PRE); + currentScene->Free(); if (cleanReload == true) { cleanReload = false; currentScene->Unload(); SHEntityManager::DestroyAllEntity(); + SHEventManager::BroadcastEvent(exitEvent, SH_SCENE_EXIT_POST); currentScene->sceneName = newSceneName; currentScene->Load(); } - else + else + { + SHEntityManager::DestroyAllEntity(); + SHEventManager::BroadcastEvent(exitEvent, SH_SCENE_EXIT_POST); + } + + SHSceneInitExitEvent initEvent; + initEvent.sceneAssetID = currentScene->sceneAssetID; + SHEventManager::BroadcastEvent(initEvent, SH_SCENE_INIT_PRE); currentScene->Init(); + SHEventManager::BroadcastEvent(initEvent, SH_SCENE_INIT_POST); } } From 74e50e10bdc5c7c203b96238c31617e6fdeb87aa Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Fri, 9 Dec 2022 01:15:43 +0800 Subject: [PATCH 032/275] Refactored the colliders? This took me 4 days omg --- SHADE_Application/src/Scenes/SBMainScene.cpp | 2 +- SHADE_Application/src/Scenes/SBTestScene.cpp | 30 +- .../Inspector/SHEditorComponentView.hpp | 34 +- .../Inspector/SHEditorInspector.cpp | 2 +- SHADE_Engine/src/Math/Geometry/SHSphere.cpp | 44 --- SHADE_Engine/src/Math/Geometry/SHSphere.h | 119 ++++-- .../CollisionShapes/SHCollisionShape.cpp | 191 +++++++++ .../CollisionShapes}/SHCollisionShape.h | 83 ++-- .../SHCollisionShapeFactory.cpp | 77 ++++ .../CollisionShapes/SHCollisionShapeFactory.h | 86 ++++ .../CollisionShapes/SHCollisionShapeID.h | 101 +++++ .../CollisionShapes/SHCollisionShapeID.hpp | 66 ++++ .../SHSphereCollisionShape.cpp | 183 +++++++++ .../CollisionShapes/SHSphereCollisionShape.h | 123 ++++++ .../src/Physics/Collision/SHCollider.cpp | 327 ++++++++++++++++ .../src/Physics/Collision/SHCollider.h | 170 ++++++++ .../src/Physics/Collision/SHCollisionInfo.cpp | 11 +- .../src/Physics/Collision/SHCollisionInfo.h | 2 +- .../SHPhysicsMaterial.cpp | 0 .../SHPhysicsMaterial.h | 0 .../src/Physics/Dynamics/SHMotionState.cpp | 2 +- .../src/Physics/Dynamics/SHPhysicsWorld.cpp | 27 +- .../src/Physics/Dynamics/SHPhysicsWorld.h | 20 +- .../src/Physics/Dynamics/SHRigidBody.cpp | 13 + .../src/Physics/Dynamics/SHRigidBody.h | 13 +- .../PhysicsObject/SHPhysicsObject.cpp | 89 ++++- .../Interface/PhysicsObject/SHPhysicsObject.h | 53 ++- .../PhysicsObject/SHPhysicsObjectManager.cpp | 110 +++--- .../PhysicsObject/SHPhysicsObjectManager.h | 45 +-- .../Physics/Interface/SHColliderComponent.cpp | 54 +-- .../Physics/Interface/SHColliderComponent.h | 38 +- .../Physics/Interface/SHCollisionShape.cpp | 368 ------------------ .../SHRigidBodyComponent.cpp | 8 - .../SHRigidBodyComponent.h | 7 +- SHADE_Engine/src/Physics/SHPhysicsEvents.h | 2 +- .../Routines/SHPhysicsPostUpdateRoutine.cpp | 23 +- .../Routines/SHPhysicsPreUpdateRoutine.cpp | 33 +- .../src/Physics/System/SHPhysicsSystem.cpp | 97 +++-- .../src/Physics/System/SHPhysicsSystem.h | 2 +- .../src/Serialization/SHSerialization.cpp | 2 +- .../src/Serialization/SHYAMLConverters.h | 34 +- SHADE_Managed/src/Components/Collider.cxx | 49 ++- SHADE_Managed/src/Components/Collider.h++ | 6 +- SHADE_Managed/src/Components/RigidBody.hxx | 2 +- SHADE_Managed/src/Engine/ECS.cxx | 2 +- 45 files changed, 1934 insertions(+), 816 deletions(-) create mode 100644 SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp rename SHADE_Engine/src/Physics/{Interface => Collision/CollisionShapes}/SHCollisionShape.h (75%) create mode 100644 SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.cpp create mode 100644 SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.h create mode 100644 SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeID.h create mode 100644 SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeID.hpp create mode 100644 SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp create mode 100644 SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h create mode 100644 SHADE_Engine/src/Physics/Collision/SHCollider.cpp create mode 100644 SHADE_Engine/src/Physics/Collision/SHCollider.h rename SHADE_Engine/src/Physics/{Interface => Collision}/SHPhysicsMaterial.cpp (100%) rename SHADE_Engine/src/Physics/{Interface => Collision}/SHPhysicsMaterial.h (100%) delete mode 100644 SHADE_Engine/src/Physics/Interface/SHCollisionShape.cpp rename SHADE_Engine/src/Physics/Interface/{RigidBodyComponent => }/SHRigidBodyComponent.cpp (98%) rename SHADE_Engine/src/Physics/Interface/{RigidBodyComponent => }/SHRigidBodyComponent.h (96%) diff --git a/SHADE_Application/src/Scenes/SBMainScene.cpp b/SHADE_Application/src/Scenes/SBMainScene.cpp index 6c875518..dd713980 100644 --- a/SHADE_Application/src/Scenes/SBMainScene.cpp +++ b/SHADE_Application/src/Scenes/SBMainScene.cpp @@ -11,7 +11,7 @@ #include "Scripting/SHScriptEngine.h" #include "Math/Transform/SHTransformComponent.h" #include "Graphics/MiddleEnd/Interface/SHMaterialInstance.h" -#include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" +#include "Physics/Interface/SHRigidBodyComponent.h" #include "Physics/Interface/SHColliderComponent.h" #include "Graphics/MiddleEnd/Lights/SHLightComponent.h" #include "Graphics/MiddleEnd/TextRendering/SHTextRenderableComponent.h" diff --git a/SHADE_Application/src/Scenes/SBTestScene.cpp b/SHADE_Application/src/Scenes/SBTestScene.cpp index 9e3fa8ab..a300e6b9 100644 --- a/SHADE_Application/src/Scenes/SBTestScene.cpp +++ b/SHADE_Application/src/Scenes/SBTestScene.cpp @@ -10,7 +10,7 @@ #include "Scripting/SHScriptEngine.h" #include "Math/Transform/SHTransformComponent.h" #include "Graphics/MiddleEnd/Interface/SHMaterialInstance.h" -#include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" +#include "Physics/Interface/SHRigidBodyComponent.h" #include "Physics/Interface/SHColliderComponent.h" #include "Graphics/MiddleEnd/Lights/SHLightComponent.h" @@ -92,7 +92,7 @@ namespace Sandbox floorRigidBody.SetType(SHRigidBodyComponent::Type::STATIC); - floorCollider.AddBoundingBox(); + //floorCollider.AddBoundingBox(); // Create blank entity with a script //testObj = SHADE::SHEntityManager::CreateEntity(); @@ -113,9 +113,9 @@ namespace Sandbox racoonTransform.SetWorldScale({ 2.0f, 2.0f, 2.0f }); racoonTransform.SetWorldPosition({ -3.0f, -2.0f, -5.0f }); - racoonCollider.AddBoundingBox(); - racoonCollider.GetCollisionShape(0).SetPositionOffset(SHVec3(0.0f,0.5f,0.0f)); - racoonCollider.GetCollisionShape(0).SetBoundingBox(SHVec3(0.5f, 0.5f, 0.5f)); + //racoonCollider.AddBoundingBox(); + //racoonCollider.GetCollisionShape(0).SetPositionOffset(SHVec3(0.0f,0.5f,0.0f)); + //racoonCollider.GetCollisionShape(0).SetBoundingBox(SHVec3(0.5f, 0.5f, 0.5f)); auto racoonItemLocation = SHEntityManager::CreateEntity(); auto& racoonItemLocationTransform = *SHComponentManager::GetComponent_s(racoonItemLocation); @@ -138,15 +138,15 @@ namespace Sandbox itemTransform.SetWorldScale({ 2.0f, 2.0f, 2.0f }); itemTransform.SetWorldPosition({ 0.0f, -2.0f, -5.0f }); - itemCollider.AddBoundingBox(); - itemCollider.AddBoundingBox(SHVec3(2.0f,2.0f,2.0f)); - itemCollider.GetCollisionShape(1).SetIsTrigger(true); + //itemCollider.AddBoundingBox(); + //itemCollider.AddBoundingBox(SHVec3(2.0f,2.0f,2.0f)); + //itemCollider.GetCollisionShape(1).SetIsTrigger(true); - itemCollider.GetCollisionShape(0).SetPositionOffset(SHVec3(0.0f, 0.5f, 0.0f)); - itemCollider.GetCollisionShape(0).SetBoundingBox(SHVec3(0.5f, 0.5f, 0.5f)); + //itemCollider.GetCollisionShape(0).SetPositionOffset(SHVec3(0.0f, 0.5f, 0.0f)); + //itemCollider.GetCollisionShape(0).SetBoundingBox(SHVec3(0.5f, 0.5f, 0.5f)); - itemCollider.GetCollisionShape(1).SetPositionOffset(SHVec3(0.0f, 0.5f, 0.0f)); - itemCollider.GetCollisionShape(1).SetBoundingBox(SHVec3(1.0f, 1.0f, 1.0f)); + //itemCollider.GetCollisionShape(1).SetPositionOffset(SHVec3(0.0f, 0.5f, 0.0f)); + //itemCollider.GetCollisionShape(1).SetBoundingBox(SHVec3(1.0f, 1.0f, 1.0f)); itemRigidBody.SetInterpolate(false); itemRigidBody.SetFreezeRotationX(true); @@ -167,9 +167,9 @@ namespace Sandbox AITransform.SetWorldScale({ 2.0f, 2.0f, 2.0f }); AITransform.SetWorldPosition({ -8.0f, -2.0f, 2.5f }); - AICollider.AddBoundingBox(); - AICollider.GetCollisionShape(0).SetPositionOffset(SHVec3(0.0f, 0.5f, 0.0f)); - AICollider.GetCollisionShape(0).SetBoundingBox(SHVec3(0.5f, 0.5f, 0.5f)); + //AICollider.AddBoundingBox(); + //AICollider.GetCollisionShape(0).SetPositionOffset(SHVec3(0.0f, 0.5f, 0.0f)); + //AICollider.GetCollisionShape(0).SetBoundingBox(SHVec3(0.5f, 0.5f, 0.5f)); AIRigidBody.SetInterpolate(false); AIRigidBody.SetFreezeRotationX(true); diff --git a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp index 71d607f5..774697f1 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp +++ b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp @@ -322,37 +322,37 @@ namespace SHADE { DrawContextMenu(component); - auto& colliders = component->GetCollisionShapes(); - int const size = static_cast(colliders.size()); - ImGui::BeginChild("Collision Shapes", { 0.0f, colliders.empty() ? 1.0f : 250.0f }, true); + auto* colliders = component->GetCollisionShapes(); + int const size = colliders ? static_cast(colliders->size()) : 0; + ImGui::BeginChild("Collision Shapes", { 0.0f, colliders->empty() ? 1.0f : 250.0f }, true); std::optional colliderToDelete{ std::nullopt }; for (int i{}; i < size; ++i) { ImGui::PushID(i); - SHCollisionShape* collider = &component->GetCollisionShape(i); + SHCollisionShape* collider = component->GetCollisionShape(i); auto cursorPos = ImGui::GetCursorPos(); //collider->IsTrigger if (collider->GetType() == SHCollisionShape::Type::BOX) { - SHEditorWidgets::BeginPanel(std::format("{} Box #{}", ICON_FA_CUBE, i).data(), { ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y }); - - const auto* BOX = reinterpret_cast(collider->GetShape()); - SHEditorWidgets::DragVec3 - ( - "Half Extents", { "X", "Y", "Z" }, - [BOX] { return BOX->GetRelativeExtents(); }, - [collider](SHVec3 const& vec) { collider->SetBoundingBox(vec); }); + //SHEditorWidgets::BeginPanel(std::format("{} Box #{}", ICON_FA_CUBE, i).data(), { ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y }); + // + //const auto* BOX = reinterpret_cast(collider->GetShape()); + //SHEditorWidgets::DragVec3 + //( + // "Half Extents", { "X", "Y", "Z" }, + // [BOX] { return BOX->GetRelativeExtents(); }, + // [collider](SHVec3 const& vec) { collider->SetBoundingBox(vec); }); } else if (collider->GetType() == SHCollisionShape::Type::SPHERE) { SHEditorWidgets::BeginPanel(std::format("{} Sphere #{}", ICON_MD_CIRCLE, i).data(), { ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y }); - const auto* SPHERE = reinterpret_cast(collider->GetShape()); + auto* SPHERE = reinterpret_cast(collider); SHEditorWidgets::DragFloat ( "Radius", [SPHERE] { return SPHERE->GetRelativeRadius(); }, - [collider](float const& value) { collider->SetBoundingSphere(value); }); + [SPHERE](float const& value) { SPHERE->SetRelativeRadius(value); }); } else if (collider->GetType() == SHCollisionShape::Type::CAPSULE) { @@ -393,7 +393,7 @@ namespace SHADE } if (colliderToDelete.has_value()) { - component->RemoveCollider(colliderToDelete.value()); + component->GetCollider()->RemoveCollisionShape(colliderToDelete.value()); } ImGui::EndChild(); @@ -401,11 +401,11 @@ namespace SHADE { if (ImGui::Selectable("Box Collider")) { - component->AddBoundingBox(); + //component->AddBoundingBox(); } if (ImGui::Selectable("Sphere Collider")) { - component->AddBoundingSphere(); + component->GetCollider()->AddSphereCollisionShape(1.0f); } ImGui::EndMenu(); } diff --git a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorInspector.cpp b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorInspector.cpp index f0d3e3e5..83647da7 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorInspector.cpp +++ b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorInspector.cpp @@ -14,7 +14,7 @@ #include "Scripting/SHScriptEngine.h" #include "ECS_Base/Managers/SHSystemManager.h" -#include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" +#include "Physics/Interface/SHRigidBodyComponent.h" #include "Physics/Interface/SHColliderComponent.h" #include "Camera/SHCameraComponent.h" #include "Camera/SHCameraArmComponent.h" diff --git a/SHADE_Engine/src/Math/Geometry/SHSphere.cpp b/SHADE_Engine/src/Math/Geometry/SHSphere.cpp index 54935251..7dca00c0 100644 --- a/SHADE_Engine/src/Math/Geometry/SHSphere.cpp +++ b/SHADE_Engine/src/Math/Geometry/SHSphere.cpp @@ -25,13 +25,11 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ SHSphere::SHSphere() noexcept - : RelativeRadius { 1.0f } { type = Type::SPHERE; } SHSphere::SHSphere(const SHVec3& center, float radius) noexcept - : RelativeRadius { 1.0f } { type = Type::SPHERE; @@ -48,7 +46,6 @@ namespace SHADE Center = rhs.Center; Radius = rhs.Radius; - RelativeRadius = rhs.RelativeRadius; } SHSphere::SHSphere(SHSphere&& rhs) noexcept @@ -57,7 +54,6 @@ namespace SHADE Center = rhs.Center; Radius = rhs.Radius; - RelativeRadius = rhs.RelativeRadius; } /*-----------------------------------------------------------------------------------*/ @@ -74,7 +70,6 @@ namespace SHADE { Center = rhs.Center; Radius = rhs.Radius; - RelativeRadius = rhs.RelativeRadius; } return *this; @@ -90,50 +85,11 @@ namespace SHADE { Center = rhs.Center; Radius = rhs.Radius; - RelativeRadius = rhs.RelativeRadius; } return *this; } - /*-----------------------------------------------------------------------------------*/ - /* Getter Function Definitions */ - /*-----------------------------------------------------------------------------------*/ - - SHVec3 SHSphere::GetCenter() const noexcept - { - return Center; - } - - float SHSphere::GetWorldRadius() const noexcept - { - return Radius; - } - - float SHSphere::GetRelativeRadius() const noexcept - { - return RelativeRadius; - } - - /*-----------------------------------------------------------------------------------*/ - /* Setter Function Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHSphere::SetCenter(const SHVec3& center) noexcept - { - Center = center; - } - - void SHSphere::SetWorldRadius(float newWorldRadius) noexcept - { - Radius = newWorldRadius; - } - - void SHSphere::SetRelativeRadius(float newRelativeRadius) noexcept - { - RelativeRadius = newRelativeRadius; - } - /*-----------------------------------------------------------------------------------*/ /* Public Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Math/Geometry/SHSphere.h b/SHADE_Engine/src/Math/Geometry/SHSphere.h index e056f21a..91771a21 100644 --- a/SHADE_Engine/src/Math/Geometry/SHSphere.h +++ b/SHADE_Engine/src/Math/Geometry/SHSphere.h @@ -23,7 +23,7 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ class SH_API SHSphere : public SHShape, - private DirectX::BoundingSphere + public DirectX::BoundingSphere { public: /*---------------------------------------------------------------------------------*/ @@ -32,8 +32,8 @@ namespace SHADE SHSphere () noexcept; SHSphere (const SHVec3& center, float radius) noexcept; - SHSphere (const SHSphere& rhs) noexcept; - SHSphere (SHSphere&& rhs) noexcept; + SHSphere (const SHSphere& rhs) noexcept; + SHSphere (SHSphere&& rhs) noexcept; ~SHSphere () override = default; @@ -44,49 +44,100 @@ namespace SHADE SHSphere& operator= (const SHSphere& rhs) noexcept; SHSphere& operator= (SHSphere&& rhs) noexcept; - /*---------------------------------------------------------------------------------*/ - /* Getter Functions */ - /*---------------------------------------------------------------------------------*/ - - [[nodiscard]] SHVec3 GetCenter () const noexcept; - [[nodiscard]] float GetWorldRadius () const noexcept; - [[nodiscard]] float GetRelativeRadius () const noexcept; - - /*---------------------------------------------------------------------------------*/ - /* Setter Functions */ - /*---------------------------------------------------------------------------------*/ - - void SetCenter (const SHVec3& center) noexcept; - void SetWorldRadius (float newWorldRadius) noexcept; - void SetRelativeRadius (float newRelativeRadius) noexcept; - /*---------------------------------------------------------------------------------*/ /* Function Members */ /*---------------------------------------------------------------------------------*/ + /** + * @brief + * Checks if a point is inside the sphere. + * @param point + * The point to check. + * @return + * True if the point is inside the sphere. + */ [[nodiscard]] bool TestPoint (const SHVec3& point) const noexcept override; - [[nodiscard]] SHRaycastResult Raycast(const SHRay& ray) const noexcept override; - [[nodiscard]] bool Contains (const SHSphere& rhs) const noexcept; - [[nodiscard]] float Volume () const noexcept; - [[nodiscard]] float SurfaceArea () const noexcept; + /** + * @brief + * Casts a ray against the sphere. + * @param ray + * The ray to cast. + * @return + * The result of the raycast.
+ * See the corresponding header for the contents of the raycast result object. + */ + [[nodiscard]] SHRaycastResult Raycast (const SHRay& ray) const noexcept override; + + /** + * @brief + * Checks if an entire other sphere is contained by this sphere. + * @param rhs + * The sphere to check. + * @return + * True if the other sphere is completely contained by this sphere. + */ + [[nodiscard]] bool Contains (const SHSphere& rhs) const noexcept; + + /** + * @brief + * Calculates the volume of the sphere. + */ + [[nodiscard]] float Volume () const noexcept; + + /** + * @brief + * Calculates the surface area of the sphere. + */ + [[nodiscard]] float SurfaceArea () const noexcept; /*---------------------------------------------------------------------------------*/ /* Static Function Members */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] static SHSphere Combine (const SHSphere& lhs, const SHSphere& rhs) noexcept; - [[nodiscard]] static bool Intersect (const SHSphere& lhs, const SHSphere& rhs) noexcept; - [[nodiscard]] static SHSphere BuildFromSpheres (const SHSphere* spheres, size_t numSpheres) noexcept; + /** + * @brief + * Combines two spheres to form a larger sphere. + * If one sphere is completely contained by the other, the result is the larger sphere. + * @return + * The combined sphere. + */ + [[nodiscard]] static SHSphere Combine (const SHSphere& lhs, const SHSphere& rhs) noexcept; + + /** + * @brief + * Checks if two spheres are intersecting. + * @return + * True if they are intersecting. + */ + [[nodiscard]] static bool Intersect (const SHSphere& lhs, const SHSphere& rhs) noexcept; + + /** + * @brief + * Builds a single sphere from multiple spheres. + * @param spheres + * The set of spheres to build from. + * @param numSpheres + * The number of spheres in the set to build from. + * @return + * A sphere that contains all the spheres in the set. + */ + [[nodiscard]] static SHSphere BuildFromSpheres (const SHSphere* spheres, size_t numSpheres) noexcept; + + /** + * @brief + * Builds a sphere from a set of vertices. + * @param vertices + * The vertices to build a sphere from. + * @param numVertices + * The number of vertices in the set to build from. + * @param stride + * The stride between each vertex, in the instance there is data in between each + * vertex that does not define the geometry of the object. + * @return + * A sphere that contains all the vertices in the set. + */ [[nodiscard]] static SHSphere BuildFromVertices (const SHVec3* vertices, size_t numVertices, size_t stride = 0) noexcept; - - private: - /*---------------------------------------------------------------------------------*/ - /* Data Members */ - /*---------------------------------------------------------------------------------*/ - - float RelativeRadius; - }; } // namespace SHADE diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp new file mode 100644 index 00000000..845634fe --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp @@ -0,0 +1,191 @@ +/**************************************************************************************** + * \file SHCollider.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Collider. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHCollisionShape.h" + +// Project Headers +#include "Physics/Collision/SHCollisionTagMatrix.h" +#include "Reflection/SHReflectionMetadata.h" +#include "Tools/Utilities/SHUtilities.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHCollisionShape::SHCollisionShape(SHCollisionShapeID id, Type colliderType) + : id { id } + , flags { 0 } + , collisionTag { SHCollisionTagMatrix::GetTag(0) } + { + flags |= 1U << SHUtilities::ConvertEnum(colliderType); + } + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + float SHCollisionShape::GetFriction() const noexcept + { + return material.GetFriction(); + } + + float SHCollisionShape::GetBounciness() const noexcept + { + return material.GetBounciness(); + } + + float SHCollisionShape::GetDensity() const noexcept + { + return material.GetDensity(); + } + + const SHPhysicsMaterial& SHCollisionShape::GetMaterial() const noexcept + { + return material; + } + + const SHVec3& SHCollisionShape::GetPositionOffset() const noexcept + { + return positionOffset; + } + + const SHVec3& SHCollisionShape::GetRotationOffset() const noexcept + { + return rotationOffset; + } + + SHCollisionShape::Type SHCollisionShape::GetType() const noexcept + { + for (int i = 0; i < SHUtilities::ConvertEnum(Type::COUNT); ++i) + { + const uint8_t FLAG_VALUE = 1U << SHUtilities::ConvertEnum(static_cast(i)); + + if (flags & FLAG_VALUE) + return static_cast(i); + } + + return Type::INVALID; + } + + bool SHCollisionShape::IsTrigger() const noexcept + { + static constexpr int FLAG_POS = 3; + static constexpr uint8_t FLAG_VALUE = 1U << FLAG_POS; + + return flags & FLAG_VALUE; + } + + bool SHCollisionShape::IsColliding() const noexcept + { + static constexpr int FLAG_POS = 4; + static constexpr uint8_t FLAG_VALUE = 1U << FLAG_POS; + + return flags & FLAG_VALUE; + } + + bool SHCollisionShape::GetDebugDrawState() const noexcept + { + static constexpr int FLAG_POS = 6; + static constexpr uint8_t FLAG_VALUE = 1U << FLAG_POS; + + return flags & FLAG_VALUE; + } + + const SHCollisionTag& SHCollisionShape::GetCollisionTag() const noexcept + { + return *collisionTag; + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHCollisionShape::SetFriction(float friction) noexcept + { + material.SetFriction(friction); + } + + void SHCollisionShape::SetBounciness(float bounciness) noexcept + { + material.SetBounciness(bounciness); + } + + void SHCollisionShape::SetDensity(float density) noexcept + { + material.SetDensity(density); + } + + void SHCollisionShape::SetMaterial(const SHPhysicsMaterial& newMaterial) noexcept + { + material = newMaterial; + } + + void SHCollisionShape::SetPositionOffset(const SHVec3& posOffset) noexcept + { + positionOffset = posOffset; + } + + void SHCollisionShape::SetRotationOffset(const SHVec3& rotOffset) noexcept + { + rotationOffset = rotOffset; + } + + void SHCollisionShape::SetIsTrigger(bool isTrigger) noexcept + { + static constexpr int FLAG_POS = 3; + static constexpr uint8_t FLAG_VALUE = 1U << FLAG_POS; + + isTrigger ? flags |= FLAG_VALUE : flags &= ~FLAG_VALUE; + } + + void SHCollisionShape::SetDebugDrawState(bool isDebugDrawing) noexcept + { + static constexpr int FLAG_POS = 6; + static constexpr uint8_t FLAG_VALUE = 1U << FLAG_POS; + + isDebugDrawing ? flags |= FLAG_VALUE : flags &= ~FLAG_VALUE; + } + + void SHCollisionShape::SetCollisionTag(SHCollisionTag* newCollisionTag) noexcept + { + collisionTag = newCollisionTag; + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + +} // namespace SHADE + +RTTR_REGISTRATION +{ + using namespace SHADE; + using namespace rttr; + + registration::enumeration("Collider Type") + ( + value("Box", SHCollisionShape::Type::BOX), + value("Sphere", SHCollisionShape::Type::SPHERE) + // TODO(Diren): Add More Shapes + ); + + registration::class_("Collider") + .property("IsTrigger" , &SHCollisionShape::IsTrigger , &SHCollisionShape::SetIsTrigger ) + .property("Friction" , &SHCollisionShape::GetFriction , &SHCollisionShape::SetFriction ) + .property("Bounciness" , &SHCollisionShape::GetBounciness , &SHCollisionShape::SetBounciness ) + .property("Density" , &SHCollisionShape::GetDensity , &SHCollisionShape::SetDensity ) + .property("Position Offset" , &SHCollisionShape::GetPositionOffset, &SHCollisionShape::SetPositionOffset) + .property("Rotation Offset" , &SHCollisionShape::GetRotationOffset, &SHCollisionShape::SetRotationOffset) (metadata(META::angleInRad, true)); +} \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/SHCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h similarity index 75% rename from SHADE_Engine/src/Physics/Interface/SHCollisionShape.h rename to SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h index 597814a6..420c0d6e 100644 --- a/SHADE_Engine/src/Physics/Interface/SHCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h @@ -1,7 +1,7 @@ /**************************************************************************************** - * \file SHCollider.h + * \file SHCollisionShape.h * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Interface for a Collider. + * \brief Interface for a Base CollisionShape Class. * * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or * disclosure of this file or its contents without the prior written consent @@ -16,8 +16,9 @@ #include "ECS_Base/Entity/SHEntity.h" #include "Math/Geometry/SHShape.h" #include "Math/SHQuaternion.h" -#include "SHPhysicsMaterial.h" #include "Physics/Collision/SHCollisionTags.h" +#include "Physics/Collision/SHPhysicsMaterial.h" +#include "SHCollisionShapeID.h" namespace SHADE { @@ -34,7 +35,7 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ friend class SHColliderComponent; - friend class SHPhysicsObject; + friend class SHCollisionShapeFactory; public: /*---------------------------------------------------------------------------------*/ @@ -46,87 +47,87 @@ namespace SHADE BOX , SPHERE , CAPSULE + + , COUNT + , INVALID = -1 }; /*---------------------------------------------------------------------------------*/ /* Constructors & Destructor */ /*---------------------------------------------------------------------------------*/ - SHCollisionShape (EntityID eid, Type colliderType = Type::BOX, const SHPhysicsMaterial& physicsMaterial = SHPhysicsMaterial::DEFAULT); + SHCollisionShape (SHCollisionShapeID id, Type colliderType = Type::BOX); - SHCollisionShape (const SHCollisionShape& rhs) noexcept; - SHCollisionShape (SHCollisionShape&& rhs) noexcept; - ~SHCollisionShape () noexcept; + SHCollisionShape (const SHCollisionShape& rhs) noexcept = default; + SHCollisionShape (SHCollisionShape&& rhs) noexcept = default; + virtual ~SHCollisionShape () noexcept = default; /*---------------------------------------------------------------------------------*/ /* Operator Overloads */ /*---------------------------------------------------------------------------------*/ - SHCollisionShape& operator=(const SHCollisionShape& rhs) noexcept; - SHCollisionShape& operator=(SHCollisionShape&& rhs) noexcept; + SHCollisionShape& operator=(const SHCollisionShape& rhs) noexcept = default; + SHCollisionShape& operator=(SHCollisionShape&& rhs) noexcept = default; /*---------------------------------------------------------------------------------*/ /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] bool HasChanged () const noexcept; - - [[nodiscard]] bool IsTrigger () const noexcept; - - [[nodiscard]] Type GetType () const noexcept; - - [[nodiscard]] const SHCollisionTag& GetCollisionTag () const noexcept; + // Material Properties + // TODO: Remove individual setters once instanced materials are supported [[nodiscard]] float GetFriction () const noexcept; [[nodiscard]] float GetBounciness () const noexcept; [[nodiscard]] float GetDensity () const noexcept; [[nodiscard]] const SHPhysicsMaterial& GetMaterial () const noexcept; + // Offsets + [[nodiscard]] const SHVec3& GetPositionOffset () const noexcept; [[nodiscard]] const SHVec3& GetRotationOffset () const noexcept; - [[nodiscard]] const SHShape* GetShape () const noexcept; + // Flags + + [[nodiscard]] Type GetType () const noexcept; + [[nodiscard]] bool IsTrigger () const noexcept; + [[nodiscard]] bool IsColliding () const noexcept; + [[nodiscard]] bool GetDebugDrawState () const noexcept; + + + [[nodiscard]] const SHCollisionTag& GetCollisionTag () const noexcept; /*---------------------------------------------------------------------------------*/ /* Setter Functions */ /*---------------------------------------------------------------------------------*/ - void SetBoundingBox (const SHVec3& halfExtents); - void SetBoundingSphere (float radius); - - void SetIsTrigger (bool isTrigger) noexcept; - void SetCollisionTag (SHCollisionTag* newCollisionTag) noexcept; void SetFriction (float friction) noexcept; void SetBounciness (float bounciness) noexcept; void SetDensity (float density) noexcept; void SetMaterial (const SHPhysicsMaterial& newMaterial) noexcept; - void SetPositionOffset (const SHVec3& posOffset) noexcept; - void SetRotationOffset (const SHVec3& rotOffset) noexcept; + void SetPositionOffset (const SHVec3& posOffset) noexcept; + void SetRotationOffset (const SHVec3& rotOffset) noexcept; - private: + // Flags + void SetIsTrigger (bool isTrigger) noexcept; + void SetDebugDrawState (bool isDebugDrawing) noexcept; + + void SetCollisionTag (SHCollisionTag* newCollisionTag) noexcept; + + protected: /*---------------------------------------------------------------------------------*/ /* Data Members */ /*---------------------------------------------------------------------------------*/ - Type type; - EntityID entityID; // The entity this collider belongs to - bool isTrigger; - bool dirty; + SHCollisionShapeID id; - SHShape* shape; - SHPhysicsMaterial material; + SHPhysicsMaterial material; - SHVec3 positionOffset; - SHVec3 rotationOffset; + SHVec3 positionOffset; + SHVec3 rotationOffset; - SHCollisionTag* collisionTag; - - /*---------------------------------------------------------------------------------*/ - /* Function Members */ - /*---------------------------------------------------------------------------------*/ - - void CopyShape(const SHShape* rhs); + uint8_t flags; // 0 debugDraw wasColliding isColliding trigger capsule sphere box + SHCollisionTag* collisionTag; RTTR_ENABLE() }; diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.cpp new file mode 100644 index 00000000..293d5c2a --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.cpp @@ -0,0 +1,77 @@ +/**************************************************************************************** + * \file SHCollisionShapeFactory.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Collison Shape Factory Class. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHCollisionShapeFactory.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHCollisionShapeFactory::~SHCollisionShapeFactory() noexcept + { + // Free all shapes in each container + for (auto* sphereCollisionShape : spheres | std::views::values) + DestroyShape(sphereCollisionShape); + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Member Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHSphereCollisionShape* SHCollisionShapeFactory::CreateSphere(SHCollisionShapeID id, const SHSphereCreateInfo& createInfo) + { + const auto RESULT = spheres.emplace(id, new SHSphereCollisionShape{ id }); + if (RESULT.second) + { + SHSphereCollisionShape* sphere = RESULT.first->second; + + sphere->Center = createInfo.Center; + sphere->Radius = createInfo.Radius; + sphere->relativeRadius = createInfo.RelativeRadius; + sphere->scale = createInfo.Scale; + + return sphere; + } + + return spheres.find(id)->second; + } + + void SHCollisionShapeFactory::DestroyShape(SHCollisionShape* shape) + { + switch (shape->GetType()) + { + case SHCollisionShape::Type::BOX: + { + break; + } + case SHCollisionShape::Type::SPHERE: + { + SHSphereCollisionShape* sphere = spheres.find(shape->id)->second; + + delete sphere; + sphere = nullptr; + + spheres.erase(shape->id); + + break; + } + case SHCollisionShape::Type::CAPSULE: + { + break; + } + default: break; + } + } +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.h new file mode 100644 index 00000000..f5820317 --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.h @@ -0,0 +1,86 @@ +/**************************************************************************************** + * \file SHCollisionShapeFactory.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Collison Shape Factory Class. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include + +// Project Header +#include "SHSphereCollisionShape.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + /** + * @brief + * Encapsulates a class for Creating and Destroying Collision Shapes.
+ * All memory for collision shapes are handled in this factory class.
+ * TODO: Support instancing of shapes + */ + class SH_API SHCollisionShapeFactory final + { + public: + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHCollisionShapeFactory () noexcept = default; + ~SHCollisionShapeFactory () noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + /** + * @brief + * Creates a sphere collision shape. + * @param id + * The ID of the shape. + * @param createInfo + * The info to create the sphere with. + * @return + * A newly sphere collision shape. + */ + SHSphereCollisionShape* CreateSphere (SHCollisionShapeID id, const SHSphereCreateInfo& createInfo); + + /** + * @brief + * Destroys a collision shape. + * * @param eid + * The entity the shape belongs to. + * @param shapeID + * The ID of the shape. + * @param shape + * The shape to destroy. + */ + void DestroyShape (SHCollisionShape* shape); + + private: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + // We use unordered maps for fast lookup when deleting. + // Since we are not instancing shapes (yet?), I'd rather not iterate through an entire vector to find the shape. + + using Spheres = std::unordered_map; + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + Spheres spheres; + // TODO: Add boxes, capsules and hulls + }; + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeID.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeID.h new file mode 100644 index 00000000..a0f78177 --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeID.h @@ -0,0 +1,101 @@ +/**************************************************************************************** + * \file SHCollisionShapeKey.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Collison Shape ID Class and it's hashing function + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include "ECS_Base/Entity/SHEntity.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + + struct SHCollisionShapeIDHash; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + /** + * @brief + * Encapsulates an identifier for a collision shape. + */ + union SHCollisionShapeID + { + private: + /*---------------------------------------------------------------------------------*/ + /* Friends */ + /*---------------------------------------------------------------------------------*/ + + friend struct SHCollisionShapeIDHash; + + public: + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHCollisionShapeID (EntityID eid, uint32_t shapeID) noexcept; + SHCollisionShapeID (const SHCollisionShapeID& rhs) noexcept; + SHCollisionShapeID (SHCollisionShapeID&& rhs) noexcept; + + ~SHCollisionShapeID () noexcept = default; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHCollisionShapeID& operator=(const SHCollisionShapeID& rhs) noexcept; + SHCollisionShapeID& operator=(SHCollisionShapeID&& rhs) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + bool operator==(const SHCollisionShapeID& rhs) const; + + private: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + struct IDs + { + public: + EntityID entityID = MAX_EID; + uint32_t shapeID = std::numeric_limits::max(); + }; + + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + uint64_t value; + IDs ids; + }; + + /** + * @brief + * Encapsulates a functor to hash a CollisionShapeKey + */ + struct SHCollisionShapeIDHash + { + public: + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + std::size_t operator()(const SHCollisionShapeID& id) const; + }; + +} // namespace SHADE + +#include "SHCollisionShapeID.hpp" diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeID.hpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeID.hpp new file mode 100644 index 00000000..234b2f6f --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeID.hpp @@ -0,0 +1,66 @@ +/**************************************************************************************** + * \file SHCollisionShapeKey.hpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Inlined Implementations for a Collison Shape ID Class and + * it's hashing function. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Primary Header +#include "SHCollisionShapeID.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + inline SHCollisionShapeID::SHCollisionShapeID(EntityID eid, uint32_t shapeID) noexcept + : ids { eid, shapeID } + {} + + inline SHCollisionShapeID::SHCollisionShapeID(const SHCollisionShapeID& rhs) noexcept + : ids { rhs.ids.entityID, rhs.ids.shapeID } + {} + + inline SHCollisionShapeID::SHCollisionShapeID(SHCollisionShapeID&& rhs) noexcept + : ids { rhs.ids.entityID, rhs.ids.shapeID } + {} + + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + inline SHCollisionShapeID& SHCollisionShapeID::operator=(const SHCollisionShapeID& rhs) noexcept + { + if (this == &rhs) + return *this; + + value = rhs.value; + + return *this; + } + + inline SHCollisionShapeID& SHCollisionShapeID::operator=(SHCollisionShapeID&& rhs) noexcept + { + value = rhs.value; + + return *this; + } + + inline bool SHCollisionShapeID::operator==(const SHCollisionShapeID& rhs) const + { + return value == rhs.value; + } + + inline std::size_t SHCollisionShapeIDHash::operator()(const SHCollisionShapeID& id) const + { + return std::hash()(id.value); + } +} diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp new file mode 100644 index 00000000..3f79b381 --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp @@ -0,0 +1,183 @@ +/**************************************************************************************** + * \file SHSphereCollisionShape.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Sphere Collision Shape. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHSphereCollisionShape.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHSphereCollisionShape::SHSphereCollisionShape(SHCollisionShapeID id) noexcept + : SHCollisionShape (id, SHCollisionShape::Type::SPHERE) + , SHSphere () + , relativeRadius { 1.0f } + , scale { 1.0f } + {} + + SHSphereCollisionShape::SHSphereCollisionShape(const SHSphereCollisionShape& rhs) noexcept + : SHCollisionShape (rhs.id, SHCollisionShape::Type::SPHERE) + , SHSphere (rhs.Center, rhs.Radius) + , relativeRadius { rhs.relativeRadius } + , scale { rhs.scale } + { + + material = rhs.material; + positionOffset = rhs.positionOffset; + rotationOffset = rhs.rotationOffset; + flags = rhs.flags; + // Since all collision tags are taken from the matrix, we do not need to do a deep copy here. + collisionTag = rhs.collisionTag; + } + + SHSphereCollisionShape::SHSphereCollisionShape(SHSphereCollisionShape&& rhs) noexcept + : SHCollisionShape (rhs.id, SHCollisionShape::Type::SPHERE) + , SHSphere (rhs.Center, rhs.Radius) + , relativeRadius { rhs.relativeRadius } + , scale { rhs.scale } + { + + material = rhs.material; + positionOffset = rhs.positionOffset; + rotationOffset = rhs.rotationOffset; + flags = rhs.flags; + // Since all collision tags are taken from the matrix, we do not need to do a deep copy here. + collisionTag = rhs.collisionTag; + } + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHSphereCollisionShape& SHSphereCollisionShape::operator=(const SHSphereCollisionShape& rhs) noexcept + { + if (this == &rhs) + return *this; + + // Collision Shape Properties + + id = rhs.id; + material = rhs.material; + positionOffset = rhs.positionOffset; + rotationOffset = rhs.rotationOffset; + flags = rhs.flags; + // Since all collision tags are taken from the matrix, we do not need to do a deep copy here. + collisionTag = rhs.collisionTag; + + // Sphere Properties + + Center = rhs.Center; + Radius = rhs.Radius; + + // Local Properties + + relativeRadius = rhs.relativeRadius; + scale = rhs.scale; + + return *this; + } + + SHSphereCollisionShape& SHSphereCollisionShape::operator=(SHSphereCollisionShape&& rhs) noexcept + { + // Collision Shape Properties + + id = rhs.id; + material = rhs.material; + positionOffset = rhs.positionOffset; + rotationOffset = rhs.rotationOffset; + flags = rhs.flags; + // Since all collision tags are taken from the matrix, we do not need to do a deep copy here. + collisionTag = rhs.collisionTag; + + // Sphere Properties + + Center = rhs.Center; + Radius = rhs.Radius; + + // Local Properties + + relativeRadius = rhs.relativeRadius; + scale = rhs.scale; + + return *this; + } + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + const SHVec3& SHSphereCollisionShape::GetCenter() const noexcept + { + return Center; + } + + float SHSphereCollisionShape::GetWorldRadius() const noexcept + { + return Radius; + } + + float SHSphereCollisionShape::GetRelativeRadius() const noexcept + { + return relativeRadius; + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHSphereCollisionShape::SetCenter(const SHVec3& newCenter) noexcept + { + Center = newCenter; + } + + void SHSphereCollisionShape::SetWorldRadius(float newWorldRadius) noexcept + { + Radius = newWorldRadius; + + // Recompute Relative radius + relativeRadius = 2.0f * Radius / scale; + } + + void SHSphereCollisionShape::SetRelativeRadius(float newRelativeRadius) noexcept + { + relativeRadius = newRelativeRadius; + + // Recompute world radius + Radius = relativeRadius * scale * 0.5f; + } + + void SHSphereCollisionShape::SetScale(float maxScale) noexcept + { + scale = std::fabs(maxScale); + + // Recompute world radius + Radius = relativeRadius * scale * 0.5f; + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Member Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHSphereCollisionShape::TestPoint(const SHVec3& point) const noexcept + { + return SHSphere::TestPoint(point); + } + + SHRaycastResult SHSphereCollisionShape::Raycast(const SHRay& ray) const noexcept + { + return SHSphere::Raycast(ray); + } + + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h new file mode 100644 index 00000000..0a8c0321 --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h @@ -0,0 +1,123 @@ +/**************************************************************************************** + * \file SHSphereCollisionShape.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Sphere Collision Shape. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "Math/Geometry/SHSphere.h" +#include "SHCollisionShape.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + /** + * @brief + * Encapsulates the information to create a sphere. + */ + struct SHSphereCreateInfo + { + public: + SHVec3 Center = SHVec3::Zero; + float Radius = 1.0f; + float RelativeRadius = 1.0f; + float Scale = 1.0f; + }; + + /** + * @brief + * Encapsulate a Sphere Collision Shape used for Physics Simulations. + */ + class SH_API SHSphereCollisionShape : public SHCollisionShape + , private SHSphere + { + private: + /*---------------------------------------------------------------------------------*/ + /* Friends */ + /*---------------------------------------------------------------------------------*/ + + friend class SHCollider; + friend class SHCompositeCollider; + friend class SHCollisionShapeFactory; + + public: + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHSphereCollisionShape (SHCollisionShapeID id) noexcept; + SHSphereCollisionShape (const SHSphereCollisionShape& rhs) noexcept; + SHSphereCollisionShape (SHSphereCollisionShape&& rhs) noexcept; + + ~SHSphereCollisionShape () override = default; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHSphereCollisionShape& operator= (const SHSphereCollisionShape& rhs) noexcept; + SHSphereCollisionShape& operator= (SHSphereCollisionShape&& rhs) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] const SHVec3& GetCenter () const noexcept; + [[nodiscard]] float GetWorldRadius () const noexcept; + [[nodiscard]] float GetRelativeRadius () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + + void SetCenter (const SHVec3& newCenter) noexcept; + void SetWorldRadius (float newWorldRadius) noexcept; + void SetRelativeRadius (float newRelativeRadius) noexcept; + void SetScale (float maxScale) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + /** + * @brief + * Tests if a point is inside the sphere. + * @param point + * The point to test. + * @return + * True if the point is inside the sphere. + */ + bool TestPoint (const SHVec3& point) const noexcept override; + + /** + * @brief + * Casts a ray against this sphere. + * @param ray + * The ray to cast. + * @return + * An object holding the results of the raycast.
+ * See the corresponding header for the contents of the object. + */ + SHRaycastResult Raycast (const SHRay& ray) const noexcept override; + + // TODO: Compute Moment of Inertia + + private: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + float relativeRadius; + float scale; // Intended to be passed in by the base collider. + }; + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp new file mode 100644 index 00000000..e950c4fb --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp @@ -0,0 +1,327 @@ +/**************************************************************************************** + * \file SHCollider.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Base Collider Class. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHCollider.h" + +// Project Headers +#include "Math/SHMathHelpers.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHCollider::SHCollider(EntityID eid, const SHTransform& worldTransform) noexcept + : entityID { eid } + , shapeIDCounter { 0 } + , rigidBody { nullptr } + , shapeFactory { nullptr } + , transform { worldTransform } + {} + + SHCollider::SHCollider(const SHCollider& rhs) noexcept + : entityID { rhs.entityID } + , shapeIDCounter { rhs.shapeIDCounter } + , rigidBody { rhs.rigidBody } + , shapeFactory { rhs.shapeFactory } + , transform { rhs.transform } + { + if (!shapeFactory) + { + SHLOGV_ERROR("Shape factory is unlinked with Collider {}. Unable to add copy shapes!", entityID) + return; + } + + copyShapes(rhs); + } + + SHCollider::SHCollider(SHCollider&& rhs) noexcept + : entityID { rhs.entityID } + , shapeIDCounter { rhs.shapeIDCounter } + , rigidBody { rhs.rigidBody } + , shapeFactory { rhs.shapeFactory } + , transform { rhs.transform } + { + if (!shapeFactory) + { + SHLOGV_ERROR("Shape factory is unlinked with Collider {}. Unable to add copy shapes!", entityID) + return; + } + + copyShapes(rhs); + } + + SHCollider::~SHCollider() noexcept + { + if (!shapeFactory) + { + SHLOGV_ERROR("Shape factory is unlinked with Composite Collider {}. Unable to add destroy collider!", entityID) + return; + } + + for (auto* shape : shapes) + shapeFactory->DestroyShape(shape); + } + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHCollider& SHCollider::operator=(const SHCollider& rhs) noexcept + { + if (this == &rhs) + return *this; + + if (!shapeFactory) + { + SHLOGV_ERROR("Shape factory is unlinked with Collider {}. Unable to add copy shapes!", entityID) + return *this; + } + + entityID = rhs.entityID; + rigidBody = rhs.rigidBody; + shapeFactory = rhs.shapeFactory; + transform = rhs.transform; + + copyShapes(rhs); + + return *this; + } + + SHCollider& SHCollider::operator=(SHCollider&& rhs) noexcept + { + if (!shapeFactory) + { + SHLOGV_ERROR("Shape factory is unlinked with Collider {}. Unable to add copy shapes!", entityID) + return *this; + } + + entityID = rhs.entityID; + rigidBody = rhs.rigidBody; + shapeFactory = rhs.shapeFactory; + transform = rhs.transform; + + copyShapes(rhs); + + return *this; + } + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + const SHTransform& SHCollider::GetTransform() const noexcept + { + return transform; + } + + const SHVec3& SHCollider::GetPosition() const noexcept + { + return transform.position; + } + + const SHQuaternion& SHCollider::GetOrientation() const noexcept + { + return transform.orientation; + } + + const SHVec3& SHCollider::GetScale() const noexcept + { + return transform.scale; + } + + const SHCollider::CollisionShapes& SHCollider::GetCollisionShapes() const noexcept + { + return shapes; + } + + SHCollisionShape* SHCollider::GetCollisionShape(int index) const + { + const int NUM_SHAPES = static_cast(shapes.size()); + + if (index < 0 || index >= NUM_SHAPES) + throw std::invalid_argument("Out-of-range index!"); + + return shapes[index]; + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHCollider::SetRigidBody(SHRigidBody* rb) noexcept + { + rigidBody = rb; + } + + void SHCollider::SetTransform(const SHTransform& newTransform) noexcept + { + transform = newTransform; + } + + void SHCollider::SetPosition(const SHVec3& newPosition) noexcept + { + transform.position = newPosition; + } + + void SHCollider::SetOrientation(const SHQuaternion& newOrientation) noexcept + { + transform.orientation = newOrientation; + } + + void SHCollider::SetScale(const SHVec3& newScale) noexcept + { + transform.scale = newScale; + } + + void SHCollider::SetFactory(SHCollisionShapeFactory* factory) noexcept + { + shapeFactory = factory; + } + + + /*-----------------------------------------------------------------------------------*/ + /* Public Member Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + const SHMatrix& SHCollider::ComputeTransformTRS() noexcept + { + return transform.ComputeTRS(); + } + + int SHCollider::AddSphereCollisionShape(float relativeRadius, const SHVec3& posOffset, const SHVec3& rotOffset) + { + if (!shapeFactory) + { + SHLOGV_ERROR("Shape factory is unlinked with Composite Collider {}. Unable to add new shape!", entityID) + return -1; + } + + // Compute world radius + const float SPHERE_SCALE = std::fabs(SHMath::Max({ transform.scale.x, transform.scale.y, transform.scale.z })); + + // Compute center + const SHQuaternion FINAL_ROT = transform.orientation * SHQuaternion::FromEuler(rotOffset); + const SHMatrix TRS = SHMatrix::Rotate(FINAL_ROT) * SHMatrix::Translate(transform.position); + + // Create Sphere + const SHSphereCreateInfo SPHERE_CREATE_INFO + { + .Center = SHVec3::Transform(posOffset, TRS) + , .Radius = relativeRadius * SPHERE_SCALE * 0.5f + , .RelativeRadius = relativeRadius + , .Scale = SPHERE_SCALE + }; + + const SHCollisionShapeID NEW_SHAPE_ID{ entityID, shapeIDCounter }; + + SHSphereCollisionShape* sphere = shapeFactory->CreateSphere(NEW_SHAPE_ID, SPHERE_CREATE_INFO); + ++shapeIDCounter; + + // Set offsets + sphere->positionOffset = posOffset; + sphere->rotationOffset = rotOffset; + + shapes.emplace_back(sphere); + return static_cast(shapes.size()); + } + + void SHCollider::RemoveCollisionShape(int index) + { + if (!shapeFactory) + { + SHLOGV_ERROR("Shape factory is unlinked with Composite Collider {}. Unable to add remove shape!", entityID) + return; + } + + const int NUM_SHAPES = static_cast(shapes.size()); + + if (index < 0 || index >= NUM_SHAPES) + throw std::invalid_argument("Out-of-range index!"); + + auto shapeIter = shapes.begin(); + for (int i = 0; i < NUM_SHAPES; ++i, ++shapeIter) + { + if (i == index) + break; + } + + shapeFactory->DestroyShape(*shapeIter); + *shapeIter = nullptr; + + // Remove the shape from the container to prevent accessing a nullptr + shapeIter = shapes.erase(shapeIter); + + SHLOG_INFO_D("Removing Collision Shape {} from Entity {}", index, entityID) + } + + void SHCollider::RecomputeShapes() const noexcept + { + + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Member Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHCollider::copyShapes(const SHCollider& rhsCollider) + { + shapeIDCounter = 0; + for (const auto* shape : rhsCollider.shapes) + { + copyShape(shape); + ++shapeIDCounter; + } + } + + void SHCollider::copyShape(const SHCollisionShape* rhsShape) + { + switch (rhsShape->GetType()) + { + case SHCollisionShape::Type::BOX: + { + break; + } + case SHCollisionShape::Type::SPHERE: + { + const SHSphereCollisionShape* RHS_SPHERE = dynamic_cast(rhsShape); + + const SHSphereCreateInfo SPHERE_CREATE_INFO + { + .Center = RHS_SPHERE->Center + , .Radius = RHS_SPHERE->Radius + , .RelativeRadius = RHS_SPHERE->relativeRadius + , .Scale = RHS_SPHERE->scale + }; + + const SHCollisionShapeID NEW_SHAPE_ID{ entityID, shapeIDCounter }; + + SHSphereCollisionShape* sphere = shapeFactory->CreateSphere(NEW_SHAPE_ID, SPHERE_CREATE_INFO); + *sphere = *RHS_SPHERE; + + shapes.emplace_back(sphere); + + break; + } + case SHCollisionShape::Type::CAPSULE: + { + break; + } + default: break; + } + + ++shapeIDCounter; + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.h b/SHADE_Engine/src/Physics/Collision/SHCollider.h new file mode 100644 index 00000000..37d96caa --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.h @@ -0,0 +1,170 @@ +/**************************************************************************************** + * \file SHCollider.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Base Collider Class. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "ECS_Base/Entity/SHEntity.h" +#include "Math/Transform/SHTransform.h" +#include "Physics/Collision/CollisionShapes/SHCollisionShapeFactory.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + + class SHRigidBody; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + /** + * @brief + * Base class for a collider. + * There are only two collider types supported by SHADE Engine: Composite & Hull + */ + class SH_API SHCollider + { + private: + /*---------------------------------------------------------------------------------*/ + /* Friends */ + /*---------------------------------------------------------------------------------*/ + + friend class SHPhysicsWorld; + + public: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + using CollisionShapes = std::vector; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + /** + * @brief + * Constructor for a collider. + * @param eid + * The entity this collider belongs to. + * @param worldTransform + * The world transform for the collider. Defaults to the identity transform. + * This is particularly important for composite colliders for offsets & relative sizes. + * @return + */ + SHCollider (EntityID eid, const SHTransform& worldTransform = SHTransform::Identity) noexcept; + SHCollider (const SHCollider& rhs) noexcept; + SHCollider (SHCollider&& rhs) noexcept; + ~SHCollider () noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHCollider& operator=(const SHCollider& rhs) noexcept; + SHCollider& operator=(SHCollider&& rhs) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] const SHTransform& GetTransform () const noexcept; + [[nodiscard]] const SHVec3& GetPosition () const noexcept; + [[nodiscard]] const SHQuaternion& GetOrientation () const noexcept; + [[nodiscard]] const SHVec3& GetScale () const noexcept; + + [[nodiscard]] const CollisionShapes& GetCollisionShapes () const noexcept; + [[nodiscard]] SHCollisionShape* GetCollisionShape (int index) const; + + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + + void SetRigidBody (SHRigidBody* rb) noexcept; + + void SetTransform (const SHTransform& newTransform) noexcept; + void SetPosition (const SHVec3& newPosition) noexcept; + void SetOrientation (const SHQuaternion& newOrientation) noexcept; + void SetScale (const SHVec3& newScale) noexcept; + + void SetFactory (SHCollisionShapeFactory* factory) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + /** + * @brief + * Computes the TRS for the collider's transform + * @return + * The computed TRS. + */ + const SHMatrix& ComputeTransformTRS() noexcept; + + /** + * @brief + * Adds a sphere collision shape. + * @param posOffset + * The position offset of the sphere from the center of the collider. Defaults to a Zero Vector. + * @param rotOffset + * The rotation offset of the sphere from the rotation of the collider. Defaults to a Zero Vector. + * @param relativeRadius + * The relative radius is constructed with respect to the world scale.
+ * Radius = max(scale.x, scale.y, scale.z) * 0.5 * relativeRadius + * @return + * The index of the newly added shape. + */ + int AddSphereCollisionShape (float relativeRadius, const SHVec3& posOffset = SHVec3::Zero, const SHVec3& rotOffset = SHVec3::Zero); + // TODO: Add Box & Capsule + + /** + * @brief + * Removes a shape from the container. Removal reduces the size of the container. + * If removing all, perform removal from back to front. + * @param index + * The index of the shape to remove. + * @throws + * Invalid argument for out-of-range indices. + */ + void RemoveCollisionShape (int index); + + /** + * @brief + * Recomputes the transforms for all shapes in this composite collider. + */ + void RecomputeShapes () const noexcept; + + protected: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + EntityID entityID; + uint32_t shapeIDCounter; // This increments everytime a shape is added to differentiate shapes. + + SHRigidBody* rigidBody; + SHCollisionShapeFactory* shapeFactory; + + SHTransform transform; + + CollisionShapes shapes; + + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + void copyShapes (const SHCollider& rhsCollider); + void copyShape (const SHCollisionShape* rhsShape); + }; + +} // namespace SHADE diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.cpp b/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.cpp index 43ad05ca..a28686e0 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.cpp +++ b/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.cpp @@ -13,6 +13,11 @@ // Primary Header #include "SHCollisionInfo.h" +// Project Headers +#include "Physics/Collision/SHCollider.h" +#include "Physics/Interface/SHColliderComponent.h" + + namespace SHADE { /*-----------------------------------------------------------------------------------*/ @@ -77,12 +82,14 @@ namespace SHADE const SHCollisionShape* SHCollisionInfo::GetColliderA() const noexcept { - return &SHComponentManager::GetComponent(ids[ENTITY_A])->GetCollisionShape(ids[COLLIDER_A]); + const auto* COLLIDER_COMPONENT = SHComponentManager::GetComponent(ids[ENTITY_A]); + return COLLIDER_COMPONENT->GetCollider()->GetCollisionShape(ids[COLLIDER_A]); } const SHCollisionShape* SHCollisionInfo::GetColliderB() const noexcept { - return &SHComponentManager::GetComponent(ids[ENTITY_B])->GetCollisionShape(ids[COLLIDER_B]); + const auto* COLLIDER_COMPONENT = SHComponentManager::GetComponent(ids[ENTITY_B]); + return COLLIDER_COMPONENT->GetCollider()->GetCollisionShape(ids[COLLIDER_B]); } SHCollisionInfo::State SHCollisionInfo::GetCollisionState() const noexcept diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.h b/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.h index dfcf4f22..d2dad647 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.h +++ b/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.h @@ -12,7 +12,7 @@ // Project Headers #include "Physics/Interface/SHColliderComponent.h" -#include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" +#include "Physics/Interface/SHRigidBodyComponent.h" namespace SHADE diff --git a/SHADE_Engine/src/Physics/Interface/SHPhysicsMaterial.cpp b/SHADE_Engine/src/Physics/Collision/SHPhysicsMaterial.cpp similarity index 100% rename from SHADE_Engine/src/Physics/Interface/SHPhysicsMaterial.cpp rename to SHADE_Engine/src/Physics/Collision/SHPhysicsMaterial.cpp diff --git a/SHADE_Engine/src/Physics/Interface/SHPhysicsMaterial.h b/SHADE_Engine/src/Physics/Collision/SHPhysicsMaterial.h similarity index 100% rename from SHADE_Engine/src/Physics/Interface/SHPhysicsMaterial.h rename to SHADE_Engine/src/Physics/Collision/SHPhysicsMaterial.h diff --git a/SHADE_Engine/src/Physics/Dynamics/SHMotionState.cpp b/SHADE_Engine/src/Physics/Dynamics/SHMotionState.cpp index 2cca1c45..91b5a688 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHMotionState.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHMotionState.cpp @@ -74,7 +74,7 @@ namespace SHADE void SHMotionState::ForcePosition(const SHVec3& newPosition) noexcept { - hasMoved = false; + hasMoved = true; prevPosition = newPosition; position = newPosition; diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp index 7d6c7c5c..0d7434f6 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp @@ -40,7 +40,7 @@ namespace SHADE void SHPhysicsWorld::AddRigidBody(SHRigidBody* rigidBody) noexcept { - const bool INSERTED = rigidBodies.emplace(rigidBody->entityID, rigidBody).second; + const bool INSERTED = rigidBodies.emplace(rigidBody).second; if (!INSERTED) { SHLOG_WARNING_D("Attempting to add duplicate rigid body {} to the Physics World!", rigidBody->entityID) @@ -49,15 +49,34 @@ namespace SHADE void SHPhysicsWorld::RemoveRigidBody(SHRigidBody* rigidBody) noexcept { - rigidBodies.erase(rigidBody->entityID); + rigidBodies.erase(rigidBody); + + // Run through the rigid body's contact graph and wake all of its non-static bodies that are asleep + } + + void SHPhysicsWorld::AddCollider(SHCollider* collider) noexcept + { + const bool INSERTED = colliders.emplace(collider).second; + if (!INSERTED) + { + SHLOG_WARNING_D("Attempting to add duplicate collider {} to the Physics World!", collider->entityID) + } + } + + void SHPhysicsWorld::RemoveCollider(SHCollider* collider) noexcept + { + colliders.erase(collider); + + // Get collider's rigid body + // Run through the rigid body's contact graph and wake all of its non-static bodies that are asleep } void SHPhysicsWorld::Step(float dt) { - for (auto* rigidBody : rigidBodies | std::views::values) + for (auto* rigidBody : rigidBodies) integrateForces(*rigidBody, dt); - for (auto* rigidBody : rigidBodies | std::views::values) + for (auto* rigidBody : rigidBodies) integrateVelocities(*rigidBody, dt); } diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h index eb96a021..499230dd 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h @@ -10,12 +10,12 @@ #pragma once -#include +#include // Project Headers +#include "Physics/Collision/SHCollider.h" #include "SHRigidBody.h" -#include "Math/SHMath.h" -#include "SH_API.h" + namespace SHADE { @@ -66,8 +66,11 @@ namespace SHADE /* Function Members */ /*---------------------------------------------------------------------------------*/ - void AddRigidBody (SHRigidBody* rigidBody) noexcept; - void RemoveRigidBody (SHRigidBody* rigidBody) noexcept; + void AddRigidBody (SHRigidBody* rigidBody) noexcept; + void RemoveRigidBody (SHRigidBody* rigidBody) noexcept; + + void AddCollider (SHCollider* collider) noexcept; + void RemoveCollider (SHCollider* collider) noexcept; void Step (float dt); @@ -76,7 +79,11 @@ namespace SHADE /* Type Definitions */ /*---------------------------------------------------------------------------------*/ - using RigidBodies = std::unordered_map; + //! I realise using a set may be dangerous with pointers. An unordered_map may be better, but I don't need the entityIDs so... + + using Colliders = std::unordered_set; + using RigidBodies = std::unordered_set; + /*---------------------------------------------------------------------------------*/ /* Data Members */ @@ -84,6 +91,7 @@ namespace SHADE WorldSettings settings; + Colliders colliders; RigidBodies rigidBodies; /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp index 8f99a8f5..e79a8c91 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp @@ -14,6 +14,7 @@ #include "SHRigidBody.h" // Project Headers +#include "Physics/Collision/SHCollider.h" #include "Tools/Logger/SHLogger.h" namespace SHADE @@ -24,6 +25,7 @@ namespace SHADE SHRigidBody::SHRigidBody(EntityID eid, Type type) noexcept : entityID { eid } + , collider { nullptr } , bodyType { type } , invMass { type == Type::DYNAMIC ? 1.0f : 0.0f } , linearDrag { 0.01f } @@ -40,6 +42,7 @@ namespace SHADE SHRigidBody::SHRigidBody(const SHRigidBody& rhs) noexcept : entityID { rhs.entityID } + , collider { nullptr } , bodyType { rhs.bodyType } , invMass { rhs.invMass } , linearDrag { rhs.linearDrag } @@ -52,6 +55,7 @@ namespace SHADE SHRigidBody::SHRigidBody(SHRigidBody&& rhs) noexcept : entityID { rhs.entityID } + , collider { nullptr } , bodyType { rhs.bodyType } , invMass { rhs.invMass } , linearDrag { rhs.linearDrag } @@ -73,6 +77,8 @@ namespace SHADE return *this; entityID = rhs.entityID; + // Deep copy the collider + *collider = *rhs.collider; bodyType = rhs.bodyType; invMass = rhs.invMass; linearDrag = rhs.linearDrag; @@ -91,6 +97,8 @@ namespace SHADE SHRigidBody& SHRigidBody::operator=(SHRigidBody&& rhs) noexcept { entityID = rhs.entityID; + // Deep copy the collider + *collider = *rhs.collider; bodyType = rhs.bodyType; invMass = rhs.invMass; linearDrag = rhs.linearDrag; @@ -223,6 +231,11 @@ namespace SHADE /* Setter Functions Definitions */ /*-----------------------------------------------------------------------------------*/ + void SHRigidBody::SetCollider(SHCollider* c) noexcept + { + collider = c; + } + void SHRigidBody::SetType(Type newType) noexcept { if (newType == bodyType) diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h index dbf2344f..1edf5cf6 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h @@ -11,12 +11,18 @@ #pragma once // Project Headers -#include "ECS_Base/SHECSMacros.h" +#include "ECS_Base/Entity/SHEntity.h" #include "Math/Vector/SHVec3.h" #include "SHMotionState.h" namespace SHADE { + /*-------------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-------------------------------------------------------------------------------------*/ + + class SHCollider; + /*-------------------------------------------------------------------------------------*/ /* Type Definitions */ /*-------------------------------------------------------------------------------------*/ @@ -96,6 +102,8 @@ namespace SHADE /* Setter Functions */ /*-----------------------------------------------------------------------------------*/ + void SetCollider (SHCollider* c) noexcept; + /** * @brief * Changing the type from non-Dynamic to Dynamic will set the default @@ -174,6 +182,7 @@ namespace SHADE // The entityID here is only meant for linking with the actual component in the engine. EntityID entityID; + SHCollider* collider; Type bodyType; @@ -187,7 +196,7 @@ namespace SHADE SHVec3 linearVelocity; SHVec3 angularVelocity; - // aZ aY aX pZ pY pX 0 0 0 0 0 autoMass enableGravity enableSleeping sleeping active + // aZ aY aX pZ pY pX 0 0 0 0 inIsland autoMass enableGravity enableSleeping sleeping active uint16_t flags; SHMotionState motionState; diff --git a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.cpp b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.cpp index b6e5ac40..ee33016a 100644 --- a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.cpp +++ b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.cpp @@ -26,15 +26,13 @@ namespace SHADE SHPhysicsObject::SHPhysicsObject(const SHPhysicsObject& rhs) noexcept : entityID { rhs.entityID } { - // Perform a deep copy of the components - *rigidBody = *rhs.rigidBody; + deepCopyComponents(rhs.rigidBody, rhs.collider); } SHPhysicsObject::SHPhysicsObject(SHPhysicsObject&& rhs) noexcept : entityID { rhs.entityID } { - // Perform a deep copy of the components - *rigidBody = *rhs.rigidBody; + deepCopyComponents(rhs.rigidBody, rhs.collider); } SHPhysicsObject::~SHPhysicsObject() noexcept @@ -42,6 +40,10 @@ namespace SHADE entityID = MAX_EID; delete rigidBody; + rigidBody = nullptr; + + delete collider; + collider = nullptr; } /*-----------------------------------------------------------------------------------*/ @@ -53,20 +55,18 @@ namespace SHADE if (this == &rhs) return *this; - entityID = rhs.entityID; + entityID = rhs.entityID; - // Perform a deep copy of the components - *rigidBody = *rhs.rigidBody; + deepCopyComponents(rhs.rigidBody, rhs.collider); return *this; } SHPhysicsObject& SHPhysicsObject::operator=(SHPhysicsObject&& rhs) noexcept { - entityID = rhs.entityID; + entityID = rhs.entityID; - // Perform a deep copy of the components - *rigidBody = *rhs.rigidBody; + deepCopyComponents(rhs.rigidBody, rhs.collider); return *this; } @@ -77,7 +77,74 @@ namespace SHADE bool SHPhysicsObject::IsEmpty() const noexcept { - return rigidBody == nullptr; + return rigidBody == nullptr && collider == nullptr; + } + + SHRigidBody* SHPhysicsObject::CreateRigidBody(SHRigidBody::Type bodyType) + { + if (rigidBody) + { + SHLOG_INFO_D("Rigid body for Entity {} has already been created!", entityID) + return rigidBody; + } + + rigidBody = new SHRigidBody{ entityID, bodyType }; + + // Link with collider if it exists + if (collider) + collider->SetRigidBody(rigidBody); + + return rigidBody; + } + + void SHPhysicsObject::DestroyRigidBody() noexcept + { + delete rigidBody; + rigidBody = nullptr; + + // Unlink with collider + if (collider) + collider->SetRigidBody(nullptr); + } + + SHCollider* SHPhysicsObject::CreateCollider(const SHTransform& transform) + { + if (collider) + { + SHLOG_INFO_D("Collider for Entity {} has already been created!", entityID) + return collider; + } + + collider = new SHCollider{ entityID, transform }; + + // Link with rigidBody if it exists + if (rigidBody) + rigidBody->SetCollider(collider); + + return collider; + } + + void SHPhysicsObject::DestroyCollider() noexcept + { + delete collider; + collider = nullptr; + + // Unlink with rigid body + if (rigidBody) + rigidBody->SetCollider(nullptr); + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Member Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsObject::deepCopyComponents(const SHRigidBody* rhsRigidBody, const SHCollider* rhsCollider) + { + if (rhsRigidBody) + rigidBody = new SHRigidBody{ *rhsRigidBody }; + + if (rhsCollider) + collider = new SHCollider { *rhsCollider }; } } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.h b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.h index f297a6fb..2550543b 100644 --- a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.h +++ b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.h @@ -11,6 +11,7 @@ #pragma once // Project Headers +#include "Physics/Collision/SHCollider.h" #include "Physics/Dynamics/SHRigidBody.h" namespace SHADE @@ -32,6 +33,7 @@ namespace SHADE EntityID entityID = MAX_EID; SHRigidBody* rigidBody = nullptr; + SHCollider* collider = nullptr; /*-----------------------------------------------------------------------------------*/ /* Constructors & Destructor */ @@ -53,6 +55,55 @@ namespace SHADE /* Member Functions */ /*-----------------------------------------------------------------------------------*/ - bool IsEmpty() const noexcept; + /** + * @brief + * Checks if the physics object has a rigid body or a collider. + * @return + * True if the physics object has neither a rigid body nor a collider. + */ + [[nodiscard]] bool IsEmpty () const noexcept; + + /** + * @brief + * Creates a rigid body for this physics object. + * @param bodyType + * The rigid body's type. Can be modified after creation. + * @return + * Pointer to the rigid body that was created. The memory of this rigid body is managed + * by the physics object itself. + */ + SHRigidBody* CreateRigidBody (SHRigidBody::Type bodyType); + + /** + * @brief + * Destroys the rigid body of this physics object and frees the memory. + */ + void DestroyRigidBody () noexcept; + + /** + * @brief + * Creates a collider for this physics object. + * @param colliderType + * The collider's type. Should not be modified after creation. + * @param transform. + * The world transform of the collider. Defaults to the identity transform. + * @return + * Pointer to the collider that was created. The memory of this collider is managed + * by the physics object itself. + */ + SHCollider* CreateCollider (const SHTransform& transform = SHTransform::Identity); + + /** + * @brief + * Destroys the collider of this physics object and frees the memory. + */ + void DestroyCollider () noexcept; + + private: + /*-----------------------------------------------------------------------------------*/ + /* Member Functions */ + /*-----------------------------------------------------------------------------------*/ + + void deepCopyComponents(const SHRigidBody* rhsRigidBody, const SHCollider* rhsCollider); }; } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.cpp b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.cpp index e6e92ba5..61a77f28 100644 --- a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.cpp +++ b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.cpp @@ -16,8 +16,7 @@ // Project Headers #include "Math/Transform/SHTransformComponent.h" #include "Physics/Interface/SHColliderComponent.h" -#include "Physics/Interface/SHCollisionShape.h" -#include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" +#include "Physics/Interface/SHRigidBodyComponent.h" #include "Tools/Utilities/SHUtilities.h" namespace SHADE @@ -58,66 +57,68 @@ namespace SHADE void SHPhysicsObjectManager::AddRigidBody(EntityID entityID) noexcept { - SHPhysicsObject* physicsObject = nullptr; - - const auto PHYSICS_OBJECT_ITERATOR = physicsObjects.find(entityID); - if (PHYSICS_OBJECT_ITERATOR == physicsObjects.end()) - physicsObject = createPhysicsObject(entityID); - else - physicsObject = &PHYSICS_OBJECT_ITERATOR->second; + SHPhysicsObject* physicsObject = ensurePhysicsObject(entityID); // Get the component auto* rigidBodyComponent = SHComponentManager::GetComponent(entityID); // Create a new rigidbody in the physics object - // We assume none has already been made + const auto RIGID_BODY_TYPE = static_cast(rigidBodyComponent->GetType()); + auto* rigidBody = physicsObject->CreateRigidBody(RIGID_BODY_TYPE); - physicsObject->rigidBody = new SHRigidBody + SHVec3 worldPos = SHVec3::Zero; + // TODO: Force orientation + + if (const auto* TRANSFORM_COMPONENT = SHComponentManager::GetComponent_s(entityID); TRANSFORM_COMPONENT) { - entityID - , static_cast(rigidBodyComponent->GetType()) - }; - - SHMotionState& motionState = physicsObject->rigidBody->GetMotionState(); - - if (const auto* TRANSFORM_COMPONENT = SHComponentManager::GetComponent(entityID); TRANSFORM_COMPONENT) - { - motionState.ForcePosition(TRANSFORM_COMPONENT->GetWorldPosition()); - } - else - { - motionState.ForcePosition(SHVec3::Zero); + worldPos = TRANSFORM_COMPONENT->GetWorldPosition(); } + SHMotionState& motionState = rigidBody->GetMotionState(); + motionState.ForcePosition(worldPos); + // Link with the component - rigidBodyComponent->SetRigidBody(physicsObject->rigidBody); - - // TODO: Broadcast event + rigidBodyComponent->SetRigidBody(rigidBody); } void SHPhysicsObjectManager::RemoveRigidBody(EntityID entityID) noexcept { const auto PHYSICS_OBJECT_ITERATOR = physicsObjects.find(entityID); - if (PHYSICS_OBJECT_ITERATOR != physicsObjects.end()) - { - SHPhysicsObject* physicsObject = &PHYSICS_OBJECT_ITERATOR->second; + if (PHYSICS_OBJECT_ITERATOR == physicsObjects.end()) + return; - delete physicsObject->rigidBody; - physicsObject->rigidBody = nullptr; + SHPhysicsObject* physicsObject = &PHYSICS_OBJECT_ITERATOR->second; - // Destroy empty physics objects - if (physicsObject->IsEmpty()) - destroyPhysicsObject(entityID); - } + physicsObject->DestroyRigidBody(); - // TODO: Broadcast event + // Destroy empty physics objects + if (physicsObject->IsEmpty()) + destroyPhysicsObject(entityID); } void SHPhysicsObjectManager::AddCollider(EntityID entityID) noexcept { - // Link with the component + SHPhysicsObject* physicsObject = ensurePhysicsObject(entityID); - // TODO: Broadcast event + // Get the component + auto* colliderComponent = SHComponentManager::GetComponent(entityID); + + SHTransform worldTransform = SHTransform::Identity; + if (const auto* TRANSFORM_COMPONENT = SHComponentManager::GetComponent_s(entityID); TRANSFORM_COMPONENT) + { + worldTransform.position = TRANSFORM_COMPONENT->GetWorldPosition(); + worldTransform.orientation = TRANSFORM_COMPONENT->GetWorldOrientation(); + worldTransform.scale = TRANSFORM_COMPONENT->GetWorldScale(); + + worldTransform.ComputeTRS(); + } + + // Create a new composite collider in the physics object + physicsObject->CreateCollider(worldTransform); + physicsObject->collider->SetFactory(&shapeFactory); + + // Link with the component + colliderComponent->SetCollider(physicsObject->collider); } void SHPhysicsObjectManager::RemoveCollider(EntityID entityID) noexcept @@ -127,30 +128,17 @@ namespace SHADE { SHPhysicsObject* physicsObject = &PHYSICS_OBJECT_ITERATOR->second; + physicsObject->DestroyCollider(); + // Destroy empty physics objects if (physicsObject->IsEmpty()) destroyPhysicsObject(entityID); } - - // TODO: Broadcast event - } - - void SHPhysicsObjectManager::AddCollisionShape(EntityID entityID, uint8_t shapeID) noexcept - { - // Link with the component - - // TODO: Broadcast event - } - - void SHPhysicsObjectManager::RemoveCollisionShape(EntityID entityID, uint8_t shapeID) noexcept - { - // Unlink with the component - - // TODO: Broadcast event } void SHPhysicsObjectManager::RemoveAllObjects() noexcept { + // Physics objects itself will delete the object physicsObjects.clear(); } @@ -164,6 +152,18 @@ namespace SHADE return &NEW_OBJECT->second; } + SHPhysicsObject* SHPhysicsObjectManager::ensurePhysicsObject(EntityID entityID) + { + const auto PHYSICS_OBJECT_ITERATOR = physicsObjects.find(entityID); + + SHPhysicsObject* physicsObject = PHYSICS_OBJECT_ITERATOR == physicsObjects.end() + ? createPhysicsObject(entityID) + : &PHYSICS_OBJECT_ITERATOR->second; + + return physicsObject; + } + + void SHPhysicsObjectManager::destroyPhysicsObject(EntityID entityID) { physicsObjects.erase(entityID); diff --git a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.h b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.h index 6449a2d8..ca260a02 100644 --- a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.h +++ b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObjectManager.h @@ -14,6 +14,7 @@ // Project Headers #include "SHPhysicsObject.h" +#include "Physics/Collision/CollisionShapes/SHCollisionShapeFactory.h" namespace SHADE { @@ -26,7 +27,7 @@ namespace SHADE * Encapsulates a manager for physics objects that links raw physics components with the * engine's components. */ - class SHPhysicsObjectManager + class SH_API SHPhysicsObjectManager { private: /*-----------------------------------------------------------------------------------*/ @@ -60,7 +61,7 @@ namespace SHADE * @param entityID * The entity to link the new rigid body to. */ - void AddRigidBody (EntityID entityID) noexcept; + void AddRigidBody (EntityID entityID) noexcept; /** * @brief @@ -68,66 +69,48 @@ namespace SHADE * @param entityID * The entity to destroy the rigid body of. */ - void RemoveRigidBody (EntityID entityID) noexcept; + void RemoveRigidBody (EntityID entityID) noexcept; /** * @brief - * Creates a collider and links it with the collider component. + * Creates a composite collider and links it with the collider component. * @param entityID * The entity to link the new collider to. */ - void AddCollider (EntityID entityID) noexcept; + void AddCollider (EntityID entityID) noexcept; /** * @brief - * Destroys a collider and removes the link with the collider component. + * Destroys a composite collider and removes the link with the collider component. * @param entityID * The entity to destroy the collider of. */ - void RemoveCollider (EntityID entityID) noexcept; - - /** - * @brief - * Creates a collision shape for composite colliders and links it with the - * collider component. - * @param eientityIDd - * The entity to create a collision shape for. - * @param shapeID - * The id of the shape being created. - */ - void AddCollisionShape (EntityID entityID, uint8_t shapeID) noexcept; - - /** - * @brief - * Destroys a collision shape for composite colliders and removes the link with the - * collider component. - * @param entityID - * The entity to destroy the collision shape of. - * @param shapeID - * The id of the shape being destroyed. - */ - void RemoveCollisionShape (EntityID entityID, uint8_t shapeID) noexcept; + void RemoveCollider (EntityID entityID) noexcept; /** * @brief * Removes all physics object in the manager. This is only meant to be called when * the world is being destroyed or the scene is being changed. */ - void RemoveAllObjects () noexcept; + void RemoveAllObjects () noexcept; private: /*-----------------------------------------------------------------------------------*/ /* Data Members */ /*-----------------------------------------------------------------------------------*/ - EntityObjectMap physicsObjects; + EntityObjectMap physicsObjects; + SHCollisionShapeFactory shapeFactory; /*-----------------------------------------------------------------------------------*/ /* Member Functions */ /*-----------------------------------------------------------------------------------*/ SHPhysicsObject* createPhysicsObject (EntityID entityID); + SHPhysicsObject* ensurePhysicsObject (EntityID entityID); void destroyPhysicsObject (EntityID entityID); + + }; } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/SHColliderComponent.cpp b/SHADE_Engine/src/Physics/Interface/SHColliderComponent.cpp index eef92493..d345290c 100644 --- a/SHADE_Engine/src/Physics/Interface/SHColliderComponent.cpp +++ b/SHADE_Engine/src/Physics/Interface/SHColliderComponent.cpp @@ -25,57 +25,43 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ SHColliderComponent::SHColliderComponent() noexcept + : collider { nullptr } {} /*-----------------------------------------------------------------------------------*/ /* Getter Function Definitions */ /*-----------------------------------------------------------------------------------*/ - const SHColliderComponent::CollisionShapes& SHColliderComponent::GetCollisionShapes() const noexcept + SHCollider* const SHColliderComponent::GetCollider() const noexcept { - return collisionShapes; + return collider; } - SHCollisionShape& SHColliderComponent::GetCollisionShape(int index) + const SHCollider::CollisionShapes* const SHColliderComponent::GetCollisionShapes() const noexcept { - if (index < 0 || static_cast(index) >= collisionShapes.size()) - throw std::invalid_argument("Out-of-range access!"); + if (!collider) + return nullptr; - return collisionShapes[index]; + return &collider->GetCollisionShapes(); } + + SHCollisionShape* const SHColliderComponent::GetCollisionShape(int index) const + { + if (!collider) + return nullptr; + + return collider->GetCollisionShape(index); + } + + /*-----------------------------------------------------------------------------------*/ - /* Public Function Member Definitions */ + /* Setter Function Definitions */ /*-----------------------------------------------------------------------------------*/ - void SHColliderComponent::OnCreate() + void SHColliderComponent::SetCollider(SHCollider* c) noexcept { - - } - - void SHColliderComponent::OnDestroy() - { - - } - - void SHColliderComponent::RecomputeCollisionShapes() noexcept - { - - } - - int SHColliderComponent::AddBoundingBox(const SHVec3& halfExtents, const SHVec3& posOffset, const SHVec3& rotOffset) noexcept - { - return 0; - } - - int SHColliderComponent::AddBoundingSphere(float radius, const SHVec3& posOffset) noexcept - { - return 0; - } - - void SHColliderComponent::RemoveCollider(int index) - { - + collider = c; } } // namespace SHADE diff --git a/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h b/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h index 15a02173..83fb235c 100644 --- a/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h +++ b/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h @@ -14,14 +14,7 @@ // Project Headers #include "ECS_Base/Components/SHComponent.h" -#include "Math/Geometry/SHBox.h" -#include "Math/Geometry/SHSphere.h" -#include "SHCollisionShape.h" - -//namespace SHADE -//{ -// class SHPhysicsSystem; -//} +#include "Physics/Collision/SHCollider.h" namespace SHADE { @@ -39,12 +32,6 @@ namespace SHADE friend class SHPhysicsSystem; friend class SHPhysicsObject; - /*---------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*---------------------------------------------------------------------------------*/ - - using CollisionShapes = std::vector; - public: /*---------------------------------------------------------------------------------*/ @@ -67,33 +54,22 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] bool HasChanged () const noexcept; - - [[nodiscard]] const CollisionShapes& GetCollisionShapes() const noexcept; - [[nodiscard]] SHCollisionShape& GetCollisionShape (int index); + [[nodiscard]] SHCollider* const GetCollider () const noexcept; + [[nodiscard]] const SHCollider::CollisionShapes* const GetCollisionShapes() const noexcept; + [[nodiscard]] SHCollisionShape* const GetCollisionShape (int index) const; /*---------------------------------------------------------------------------------*/ - /* Function Members */ + /* Setter Functions */ /*---------------------------------------------------------------------------------*/ - void OnCreate () override; - void OnDestroy () override; - - void RecomputeCollisionShapes () noexcept; - - void RemoveCollider (int index); - - int AddBoundingBox (const SHVec3& halfExtents = SHVec3::One, const SHVec3& posOffset = SHVec3::Zero, const SHVec3& rotOffset = SHVec3::Zero) noexcept; - int AddBoundingSphere (float radius = 1.0f, const SHVec3& posOffset = SHVec3::Zero) noexcept; + void SetCollider(SHCollider* c) noexcept; private: - /*---------------------------------------------------------------------------------*/ /* Data Members */ /*---------------------------------------------------------------------------------*/ - SHVec3 scale; - CollisionShapes collisionShapes; + SHCollider* collider; RTTR_ENABLE() }; diff --git a/SHADE_Engine/src/Physics/Interface/SHCollisionShape.cpp b/SHADE_Engine/src/Physics/Interface/SHCollisionShape.cpp deleted file mode 100644 index 4dd506f9..00000000 --- a/SHADE_Engine/src/Physics/Interface/SHCollisionShape.cpp +++ /dev/null @@ -1,368 +0,0 @@ -/**************************************************************************************** - * \file SHCollider.cpp - * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Implementation for a Collider. - * - * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or - * disclosure of this file or its contents without the prior written consent - * of DigiPen Institute of Technology is prohibited. -****************************************************************************************/ - -#include - -// Primary Header -#include "SHCollisionShape.h" -// Project Headers -#include "Math/Geometry/SHBox.h" -#include "Math/Geometry/SHSphere.h" -#include "Math/SHMathHelpers.h" -#include "Physics/Collision/SHCollisionTagMatrix.h" -#include "Reflection/SHReflectionMetadata.h" -#include "SHColliderComponent.h" - -namespace SHADE -{ - /*-----------------------------------------------------------------------------------*/ - /* Constructors & Destructor Definitions */ - /*-----------------------------------------------------------------------------------*/ - - SHCollisionShape::SHCollisionShape(EntityID eid, Type colliderType, const SHPhysicsMaterial& physicsMaterial) - : type { colliderType } - , entityID { eid } - , isTrigger { false } - , dirty { true } - , shape { nullptr } - , material { physicsMaterial } - , collisionTag { SHCollisionTagMatrix::GetTag(0) } - { - switch (type) - { - case Type::BOX: - { - shape = new SHBox{ SHVec3::Zero, SHVec3::One }; - break; - } - case Type::SPHERE: - { - shape = new SHSphere{ SHVec3::Zero, 0.5f }; - break; - } - default: break; - } - } - - SHCollisionShape::SHCollisionShape(const SHCollisionShape& rhs) noexcept - : type { rhs.type} - , entityID { rhs.entityID } - , isTrigger { rhs.isTrigger } - , dirty { true } - , shape { nullptr } - , material { rhs.material } - , positionOffset { rhs.positionOffset } - , rotationOffset { rhs.rotationOffset } - , collisionTag { rhs.collisionTag } - { - CopyShape(rhs.shape); - } - - SHCollisionShape::SHCollisionShape(SHCollisionShape&& rhs) noexcept - : type { rhs.type} - , entityID { rhs.entityID } - , isTrigger { rhs.isTrigger } - , dirty { true } - , shape { nullptr } - , material { rhs.material } - , positionOffset { rhs.positionOffset } - , rotationOffset { rhs.rotationOffset } - , collisionTag { rhs.collisionTag } - { - CopyShape(rhs.shape); - } - - SHCollisionShape::~SHCollisionShape() noexcept - { - shape = nullptr; - } - - /*-----------------------------------------------------------------------------------*/ - /* Operator Overload Definitions */ - /*-----------------------------------------------------------------------------------*/ - - SHCollisionShape& SHCollisionShape::operator=(const SHCollisionShape& rhs) noexcept - { - if (this == &rhs) - return *this; - - type = rhs.type; - entityID = rhs.entityID; - isTrigger = rhs.isTrigger; - dirty = true; - material = rhs.material; - positionOffset = rhs.positionOffset; - rotationOffset = rhs.rotationOffset; - collisionTag = rhs.collisionTag; - - delete shape; - CopyShape(rhs.shape); - - return *this; - } - - SHCollisionShape& SHCollisionShape::operator=(SHCollisionShape&& rhs) noexcept - { - type = rhs.type; - entityID = rhs.entityID; - isTrigger = rhs.isTrigger; - dirty = true; - material = rhs.material; - positionOffset = rhs.positionOffset; - rotationOffset = rhs.rotationOffset; - collisionTag = rhs.collisionTag; - - delete shape; - CopyShape(rhs.shape); - - return *this; - } - - /*-----------------------------------------------------------------------------------*/ - /* Getter Function Definitions */ - /*-----------------------------------------------------------------------------------*/ - - bool SHCollisionShape::HasChanged() const noexcept - { - return dirty; - } - - bool SHCollisionShape::IsTrigger() const noexcept - { - return isTrigger; - } - - SHCollisionShape::Type SHCollisionShape::GetType() const noexcept - { - return type; - } - - const SHCollisionTag& SHCollisionShape::GetCollisionTag() const noexcept - { - return *collisionTag; - } - - float SHCollisionShape::GetFriction() const noexcept - { - return material.GetFriction(); - } - - float SHCollisionShape::GetBounciness() const noexcept - { - return material.GetBounciness(); - } - - float SHCollisionShape::GetDensity() const noexcept - { - return material.GetDensity(); - } - - const SHPhysicsMaterial& SHCollisionShape::GetMaterial() const noexcept - { - return material; - } - - const SHVec3& SHCollisionShape::GetPositionOffset() const noexcept - { - return positionOffset; - } - - const SHVec3& SHCollisionShape::GetRotationOffset() const noexcept - { - return rotationOffset; - } - - const SHShape* SHCollisionShape::GetShape() const noexcept - { - return shape; - } - - /*-----------------------------------------------------------------------------------*/ - /* Setter Function Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHCollisionShape::SetBoundingBox(const SHVec3& halfExtents) - { - //dirty = true; - - //const auto* COLLIDER = SHComponentManager::GetComponent(entityID); - //auto* box = reinterpret_cast(shape); - - //SHVec3 correctedHalfExtents = halfExtents; - - //// Get current relative halfExtents for error checking. 0 is ignored - //const SHVec3& CURRENT_RELATIVE_EXTENTS = box->GetRelativeExtents(); - //for (size_t i = 0; i < SHVec3::SIZE; ++i) - //{ - // if (SHMath::CompareFloat(halfExtents[i], 0.0f)) - // correctedHalfExtents[i] = CURRENT_RELATIVE_EXTENTS[i]; - //} - // - //// Set the half extents relative to world scale - //const SHVec3 WORLD_EXTENTS = correctedHalfExtents * COLLIDER->GetScale() * 0.5f; - - //if (type != Type::BOX) - //{ - // type = Type::BOX; - - // delete shape; - // shape = new SHBox{ positionOffset, WORLD_EXTENTS }; - //} - - //box->SetWorldExtents(WORLD_EXTENTS); - //box->SetRelativeExtents(correctedHalfExtents); - } - - void SHCollisionShape::SetBoundingSphere(float radius) - { - //dirty = true; - - //auto* sphere = reinterpret_cast(shape); - //const auto* COLLIDER = SHComponentManager::GetComponent(entityID); - - //// Get current relative halfExtents for error checking. 0 is ignored - //const float CURRENT_RELATIVE_RADIUS = sphere->GetRelativeRadius(); - //if (SHMath::CompareFloat(radius, 0.0f)) - // radius = CURRENT_RELATIVE_RADIUS; - - //// Set the radius relative to world scale - //const SHVec3 WORLD_SCALE = COLLIDER->GetScale(); - //const float MAX_SCALE = SHMath::Max({ WORLD_SCALE.x, WORLD_SCALE.y, WORLD_SCALE.z }); - //const float WORLD_RADIUS = radius * MAX_SCALE * 0.5f; - - //if (type != Type::SPHERE) - //{ - // type = Type::SPHERE; - - // delete shape; - // shape = new SHSphere{ positionOffset, WORLD_RADIUS }; - //} - - //sphere->SetWorldRadius(WORLD_RADIUS); - //sphere->SetRelativeRadius(radius); - } - - void SHCollisionShape::SetIsTrigger(bool trigger) noexcept - { - dirty = true; - isTrigger = trigger; - } - - void SHCollisionShape::SetCollisionTag(SHCollisionTag* newCollisionTag) noexcept - { - dirty = true; - collisionTag = newCollisionTag; - } - - void SHCollisionShape::SetFriction(float friction) noexcept - { - dirty = true; - material.SetFriction(friction); - } - - void SHCollisionShape::SetBounciness(float bounciness) noexcept - { - dirty = true; - material.SetBounciness(bounciness); - } - - void SHCollisionShape::SetDensity(float density) noexcept - { - dirty = true; - material.SetDensity(density); - } - - void SHCollisionShape::SetMaterial(const SHPhysicsMaterial& newMaterial) noexcept - { - dirty = true; - material = newMaterial; - } - - void SHCollisionShape::SetPositionOffset(const SHVec3& posOffset) noexcept - { - dirty = true; - positionOffset = posOffset; - - switch (type) - { - case Type::BOX: - { - reinterpret_cast(shape)->SetCenter(positionOffset); - break; - } - case Type::SPHERE: - { - reinterpret_cast(shape)->SetCenter(positionOffset); - break; - } - default: break; - } - } - - void SHCollisionShape::SetRotationOffset(const SHVec3& rotOffset) noexcept - { - dirty = true; - rotationOffset = rotOffset; - } - - /*-----------------------------------------------------------------------------------*/ - /* Private Function Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHCollisionShape::CopyShape(const SHShape* rhs) - { - switch (type) - { - case Type::BOX: - { - const auto* RHS_BOX = reinterpret_cast(rhs); - - shape = new SHBox{ positionOffset, RHS_BOX->GetWorldExtents() }; - auto* lhsBox = reinterpret_cast(shape); - lhsBox->SetRelativeExtents(RHS_BOX->GetRelativeExtents()); - - break; - } - case Type::SPHERE: - { - const auto* RHS_SPHERE = reinterpret_cast(rhs); - - shape = new SHSphere{ positionOffset, RHS_SPHERE->GetWorldRadius() }; - auto* lhsSphere = reinterpret_cast(shape); - lhsSphere->SetRelativeRadius(RHS_SPHERE->GetRelativeRadius()); - - break; - } - default: break; - } - } - -} // namespace SHADE - -RTTR_REGISTRATION -{ - using namespace SHADE; - using namespace rttr; - - registration::enumeration("Collider Type") - ( - value("Box", SHCollisionShape::Type::BOX), - value("Sphere", SHCollisionShape::Type::SPHERE) - // TODO(Diren): Add More Shapes - ); - - registration::class_("Collider") - .property("IsTrigger" , &SHCollisionShape::IsTrigger , &SHCollisionShape::SetIsTrigger ) - .property("Friction" , &SHCollisionShape::GetFriction , &SHCollisionShape::SetFriction ) - .property("Bounciness" , &SHCollisionShape::GetBounciness , &SHCollisionShape::SetBounciness ) - .property("Density" , &SHCollisionShape::GetDensity , &SHCollisionShape::SetDensity ) - .property("Position Offset" , &SHCollisionShape::GetPositionOffset, &SHCollisionShape::SetPositionOffset) - .property("Rotation Offset" , &SHCollisionShape::GetRotationOffset, &SHCollisionShape::SetRotationOffset) (metadata(META::angleInRad, true)); -} \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.cpp b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp similarity index 98% rename from SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.cpp rename to SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp index ddc51fbc..da848f09 100644 --- a/SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.cpp +++ b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp @@ -10,17 +10,9 @@ #include -// External Dependencies -#include - // Primary Header #include "SHRigidBodyComponent.h" -// Project Headers -#include "ECS_Base/Managers/SHSystemManager.h" -#include "Physics/Dynamics/SHRigidBody.h" -#include "Physics/System/SHPhysicsSystem.h" - namespace SHADE { /*-----------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h similarity index 96% rename from SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h rename to SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h index bfb1717b..7d377184 100644 --- a/SHADE_Engine/src/Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h +++ b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h @@ -15,15 +15,10 @@ // Project Headers #include "ECS_Base/Components/SHComponent.h" #include "Math/Vector/SHVec3.h" +#include "Physics/Dynamics/SHRigidBody.h" namespace SHADE { - /*-------------------------------------------------------------------------------------*/ - /* Forward Declarations */ - /*-------------------------------------------------------------------------------------*/ - - class SHRigidBody; - /*-----------------------------------------------------------------------------------*/ /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/SHPhysicsEvents.h b/SHADE_Engine/src/Physics/SHPhysicsEvents.h index ae48a75b..35217347 100644 --- a/SHADE_Engine/src/Physics/SHPhysicsEvents.h +++ b/SHADE_Engine/src/Physics/SHPhysicsEvents.h @@ -11,7 +11,7 @@ #pragma once // Project Headers -#include "Interface/SHCollisionShape.h" +#include "Collision/CollisionShapes/SHCollisionShape.h" namespace SHADE diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp index 5f18c450..cd4b1cff 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp @@ -57,19 +57,20 @@ namespace SHADE if (!rigidBodyComponent.rigidBody) continue; - if (const SHMotionState& MOTION_STATE = rigidBodyComponent.rigidBody->GetMotionState(); MOTION_STATE) - { - SHVec3 renderPosition = rigidBodyComponent.IsInterpolating() - ? MOTION_STATE.InterpolatePositions(FACTOR) - : MOTION_STATE.position; + const SHMotionState& MOTION_STATE = rigidBodyComponent.rigidBody->GetMotionState(); - /* - * TODO: Test if the scene graph transforms abides by setting world position. Collisions will ignore the scene graph hierarchy. - */ + if (!MOTION_STATE) + continue; - transformComponent->SetWorldPosition(renderPosition); - // TODO: SetOrientation - } + SHVec3 renderPosition = rigidBodyComponent.IsInterpolating() ? MOTION_STATE.InterpolatePositions(FACTOR) : MOTION_STATE.position; + + /* + * TODO: Test if the scene graph transforms abides by setting world position. Collisions will ignore the scene graph hierarchy. + */ + + + transformComponent->SetWorldPosition(renderPosition); + // TODO: SetOrientation } // Collision & Trigger messages diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp index eda69161..c03571db 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp @@ -40,23 +40,32 @@ namespace SHADE { const auto* TRANSFORM_COMPONENT = SHComponentManager::GetComponent_s(entityID); + if (!TRANSFORM_COMPONENT || !TRANSFORM_COMPONENT->HasChanged()) + return; + + const SHVec3& WORLD_POS = TRANSFORM_COMPONENT->GetWorldPosition(); + const SHQuaternion& WORLD_ROT = TRANSFORM_COMPONENT->GetWorldOrientation(); + const SHVec3& WORLD_SCL = TRANSFORM_COMPONENT->GetWorldScale(); + // We assume that all engine components and physics object components have been successfully linked - if (TRANSFORM_COMPONENT && TRANSFORM_COMPONENT->HasChanged()) + if (physicsObject.rigidBody) { - // Sync the objects transforms - const SHVec3 WORLD_POS = TRANSFORM_COMPONENT->GetWorldPosition(); + SHMotionState& motionState = physicsObject.rigidBody->GetMotionState(); - if (physicsObject.rigidBody) - { - SHMotionState& motionState = physicsObject.rigidBody->GetMotionState(); - - motionState.ForcePosition(TRANSFORM_COMPONENT->GetWorldPosition()); - // TODO: Force Orientation - } + motionState.ForcePosition(TRANSFORM_COMPONENT->GetWorldPosition()); + // TODO: Force Orientation } - // TODO: Sync Collider Transform with World Transform + if (physicsObject.collider) + { + physicsObject.collider->SetPosition(WORLD_POS); + physicsObject.collider->SetOrientation(WORLD_ROT); + physicsObject.collider->SetScale(WORLD_SCL); + + physicsObject.collider->RecomputeShapes(); + } } } -} + +} // namespace SHADE diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp index b4ff6961..d7bece3b 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp @@ -162,6 +162,9 @@ namespace SHADE { if (PHYSICS_OBJECT.rigidBody) physicsWorld->RemoveRigidBody(PHYSICS_OBJECT.rigidBody); + + if (PHYSICS_OBJECT.collider) + physicsWorld->RemoveCollider(PHYSICS_OBJECT.collider); } delete physicsWorld; @@ -177,13 +180,16 @@ namespace SHADE // This is for handling scene changes while the editor is active. const auto* EDITOR = SHSystemManager::GetSystem(); - if (EDITOR && EDITOR->editorState == SHEditor::State::PLAY) + if (!EDITOR || EDITOR->editorState != SHEditor::State::PLAY) + return onSceneInitEvent.get()->handle; + + for (const auto& PHYSICS_OBJECT : physicsObjectManager.GetPhysicsObjects() | std::views::values) { - for (const auto& PHYSICS_OBJECT : physicsObjectManager.GetPhysicsObjects() | std::views::values) - { - if (PHYSICS_OBJECT.rigidBody) - physicsWorld->AddRigidBody(PHYSICS_OBJECT.rigidBody); - } + if (PHYSICS_OBJECT.rigidBody) + physicsWorld->AddRigidBody(PHYSICS_OBJECT.rigidBody); + + if (PHYSICS_OBJECT.collider) + physicsWorld->AddCollider(PHYSICS_OBJECT.collider); } #endif @@ -202,6 +208,9 @@ namespace SHADE { if (PHYSICS_OBJECT.rigidBody) physicsWorld->RemoveRigidBody(PHYSICS_OBJECT.rigidBody); + + if (PHYSICS_OBJECT.collider) + physicsWorld->RemoveCollider(PHYSICS_OBJECT.collider); } delete physicsWorld; @@ -223,25 +232,34 @@ namespace SHADE const bool IS_COLLIDER = ADDED_ID == COLLIDER_COMPONENT_ID; // Check if its a physics component - if (IS_RIGID_BODY || IS_COLLIDER) + const bool IS_PHYSICS_COMPONENT = IS_RIGID_BODY || IS_COLLIDER; + if (!IS_PHYSICS_COMPONENT) + return onComponentAddedEvent.get()->handle; + + const EntityID EID = EVENT_DATA->eid; + + // Link engine components with physics object component + if (IS_RIGID_BODY) { - const EntityID EID = EVENT_DATA->eid; + physicsObjectManager.AddRigidBody(EID); - // Link engine components with physics object component - if (IS_RIGID_BODY) + if (physicsWorld) { - physicsObjectManager.AddRigidBody(EID); - - if (physicsWorld) - { - auto* rigidBody = physicsObjectManager.GetPhysicsObject(EID)->rigidBody; - physicsWorld->AddRigidBody(rigidBody); - } + auto* rigidBody = physicsObjectManager.GetPhysicsObject(EID)->rigidBody; + physicsWorld->AddRigidBody(rigidBody); } + } - if (IS_COLLIDER) - physicsObjectManager.AddCollider(EID); + if (IS_COLLIDER) + { + physicsObjectManager.AddCollider(EID); + + if (physicsWorld) + { + auto* collider = physicsObjectManager.GetPhysicsObject(EID)->collider; + physicsWorld->AddCollider(collider); + } } return onComponentAddedEvent.get()->handle; @@ -260,27 +278,35 @@ namespace SHADE const bool IS_COLLIDER = REMOVED_DATA == COLLIDER_COMPONENT_ID; // Check if its a physics component - if (IS_RIGID_BODY || IS_COLLIDER) + const bool IS_PHYSICS_COMPONENT = IS_RIGID_BODY || IS_COLLIDER; + if (!IS_PHYSICS_COMPONENT) + return onComponentRemovedEvent.get()->handle; + + const EntityID EID = EVENT_DATA->eid; + + if (IS_RIGID_BODY) { - const EntityID EID = EVENT_DATA->eid; - - // Link engine components with physics object component - if (IS_RIGID_BODY) + if (physicsWorld) { - if (physicsWorld) - { - auto* rigidBody = physicsObjectManager.GetPhysicsObject(EID)->rigidBody; - physicsWorld->RemoveRigidBody(rigidBody); - } - - physicsObjectManager.RemoveRigidBody(EID); + auto* rigidBody = physicsObjectManager.GetPhysicsObject(EID)->rigidBody; + physicsWorld->RemoveRigidBody(rigidBody); } - - if (IS_COLLIDER) - physicsObjectManager.RemoveCollider(EID); + physicsObjectManager.RemoveRigidBody(EID); } + if (IS_COLLIDER) + { + if (physicsWorld) + { + auto* collider = physicsObjectManager.GetPhysicsObject(EID)->collider; + physicsWorld->RemoveCollider(collider); + } + + physicsObjectManager.RemoveCollider(EID); + } + + return onComponentRemovedEvent.get()->handle; } @@ -295,7 +321,8 @@ namespace SHADE if (PHYSICS_OBJECT.rigidBody) physicsWorld->AddRigidBody(PHYSICS_OBJECT.rigidBody); - // TODO: Add Collider if it exists + if (PHYSICS_OBJECT.collider) + physicsWorld->AddCollider(PHYSICS_OBJECT.collider); } return onEditorPlayEvent.get()->handle; diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h index 7d7dece5..2a351e86 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h @@ -18,7 +18,7 @@ #include "Physics/Dynamics/SHPhysicsWorld.h" #include "Physics/Interface/PhysicsObject/SHPhysicsObjectManager.h" -#include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" +#include "Physics/Interface/SHRigidBodyComponent.h" #include "Physics/Interface/SHColliderComponent.h" diff --git a/SHADE_Engine/src/Serialization/SHSerialization.cpp b/SHADE_Engine/src/Serialization/SHSerialization.cpp index 9d648185..99e4fa41 100644 --- a/SHADE_Engine/src/Serialization/SHSerialization.cpp +++ b/SHADE_Engine/src/Serialization/SHSerialization.cpp @@ -15,7 +15,7 @@ #include "Camera/SHCameraArmComponent.h" #include "Math/Transform/SHTransformComponent.h" #include "Graphics/MiddleEnd/Interface/SHRenderable.h" -#include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" +#include "Physics/Interface/SHRigidBodyComponent.h" #include "UI/SHCanvasComponent.h" #include "UI/SHButtonComponent.h" #include "ECS_Base/Managers/SHSystemManager.h" diff --git a/SHADE_Engine/src/Serialization/SHYAMLConverters.h b/SHADE_Engine/src/Serialization/SHYAMLConverters.h index e1cb8181..cbbe8505 100644 --- a/SHADE_Engine/src/Serialization/SHYAMLConverters.h +++ b/SHADE_Engine/src/Serialization/SHYAMLConverters.h @@ -1,9 +1,7 @@ #pragma once #include "Graphics/MiddleEnd/Interface/SHRenderable.h" #include "Graphics/MiddleEnd/Materials/SHMaterialSpec.h" -#include "Math/Geometry/SHBox.h" -#include "Math/Geometry/SHSphere.h" -#include "Physics/Interface/SHCollisionShape.h" +#include "Physics/Collision/CollisionShapes/SHSphereCollisionShape.h" #include "Resource/SHResourceManager.h" #include "Math/Vector/SHVec2.h" #include "Math/Vector/SHVec3.h" @@ -134,14 +132,14 @@ namespace YAML { case SHCollisionShape::Type::BOX: { - const auto* BOX = reinterpret_cast(rhs.GetShape()); - node[HalfExtents] = BOX->GetRelativeExtents(); + //const auto* BOX = reinterpret_cast(rhs.GetShape()); + //node[HalfExtents] = BOX->GetRelativeExtents(); } break; case SHCollisionShape::Type::SPHERE: { - const auto* SPHERE = reinterpret_cast(rhs.GetShape()); - node[Radius] = SPHERE->GetRelativeRadius(); + const auto& SPHERE = dynamic_cast(rhs); + node[Radius] = SPHERE.GetRelativeRadius(); } break; case SHCollisionShape::Type::CAPSULE: break; @@ -172,14 +170,17 @@ namespace YAML { case SHCollisionShape::Type::BOX: { - if (node[HalfExtents].IsDefined()) - rhs.SetBoundingBox(node[HalfExtents].as()); + //if (node[HalfExtents].IsDefined()) + // rhs.SetBoundingBox(node[HalfExtents].as()); } break; case SHCollisionShape::Type::SPHERE: { if (node[Radius].IsDefined()) - rhs.SetBoundingSphere(node[Radius].as()); + { + auto* sphere = dynamic_cast(&rhs); + sphere->SetRelativeRadius(node[Radius].as()); + } } break; case SHCollisionShape::Type::CAPSULE: break; @@ -207,11 +208,10 @@ namespace YAML static Node encode(SHColliderComponent& rhs) { Node node, collidersNode; - auto const& colliders = rhs.GetCollisionShapes(); - int const numColliders = static_cast(colliders.size()); + int const numColliders = static_cast(rhs.GetCollisionShapes()->size()); for (int i = 0; i < numColliders; ++i) { - auto& collider = rhs.GetCollisionShape(i); + auto& collider = *rhs.GetCollisionShape(i); Node colliderNode = convert::encode(collider); if (colliderNode.IsDefined()) collidersNode[i] = colliderNode; @@ -233,14 +233,16 @@ namespace YAML if (!ok) return false; + auto* collider = rhs.GetCollider(); + switch (colliderType) { - case SHCollisionShape::Type::BOX: rhs.AddBoundingBox(); break; - case SHCollisionShape::Type::SPHERE: rhs.AddBoundingSphere(); break; + case SHCollisionShape::Type::BOX: break; + case SHCollisionShape::Type::SPHERE: collider->AddSphereCollisionShape(1.0f); break; case SHCollisionShape::Type::CAPSULE: break; default:; } - YAML::convert::decode(colliderNode, rhs.GetCollisionShape(numColliders++)); + YAML::convert::decode(colliderNode, *collider->GetCollisionShape(numColliders++)); } } return true; diff --git a/SHADE_Managed/src/Components/Collider.cxx b/SHADE_Managed/src/Components/Collider.cxx index 414e79b7..1c84637a 100644 --- a/SHADE_Managed/src/Components/Collider.cxx +++ b/SHADE_Managed/src/Components/Collider.cxx @@ -107,7 +107,7 @@ namespace SHADE try { - auto& shape = collider->GetCollisionShape(arrayIndex); + auto& shape = *collider->GetCollisionShape(arrayIndex); return shape; } catch (std::invalid_argument&) @@ -128,35 +128,39 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ Vector3 BoxCollider::Center::get() { - return Convert::ToCLI(getNativeCollisionShape().GetCenter()); + //return Convert::ToCLI(getNativeCollisionShape().GetCenter()); + return Vector3::Zero; } void BoxCollider::Center::set(Vector3 value) { - getNativeCollisionShape().SetCenter(Convert::ToNative(value)); + //getNativeCollisionShape().SetCenter(Convert::ToNative(value)); } Vector3 BoxCollider::HalfExtents::get() { - return Convert::ToCLI(getNativeCollisionShape().GetWorldExtents()); + //return Convert::ToCLI(getNativeCollisionShape().GetWorldExtents()); + return Vector3::Zero; } void BoxCollider::HalfExtents::set(Vector3 value) { - getNativeCollisionShape().SetWorldExtents(Convert::ToNative(value)); + //getNativeCollisionShape().SetWorldExtents(Convert::ToNative(value)); } Vector3 BoxCollider::Min::get() { - return Convert::ToCLI(getNativeCollisionShape().GetMin()); + //return Convert::ToCLI(getNativeCollisionShape().GetMin()); + return Vector3::Zero; } void BoxCollider::Min::set(Vector3 value) { - getNativeCollisionShape().SetMin(Convert::ToNative(value)); + //getNativeCollisionShape().SetMin(Convert::ToNative(value)); } Vector3 BoxCollider::Max::get() { - return Convert::ToCLI(getNativeCollisionShape().GetMax()); + //return Convert::ToCLI(getNativeCollisionShape().GetMax()); + return Vector3::Zero; } void BoxCollider::Max::set(Vector3 value) { - getNativeCollisionShape().SetMax(Convert::ToNative(value)); + //getNativeCollisionShape().SetMax(Convert::ToNative(value)); } /*---------------------------------------------------------------------------------*/ @@ -164,11 +168,13 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ bool BoxCollider::TestPoint(Vector3 point) { - return getNativeCollisionShape().TestPoint(Convert::ToNative(point)); + //return getNativeCollisionShape().TestPoint(Convert::ToNative(point)); + return false; } bool BoxCollider::Raycast(Ray ray, float maxDistance) { - return getNativeCollisionShape().Raycast(Convert::ToNative(ray)); + //return getNativeCollisionShape().Raycast(Convert::ToNative(ray)); + return false; } /*---------------------------------------------------------------------------------*/ @@ -176,19 +182,19 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ Vector3 SphereCollider::Center::get() { - return Convert::ToCLI(getNativeCollisionShape().GetCenter()); + return Convert::ToCLI(getNativeCollisionShape().GetCenter()); } void SphereCollider::Center::set(Vector3 value) { - getNativeCollisionShape().SetCenter(Convert::ToNative(value)); + getNativeCollisionShape().SetCenter(Convert::ToNative(value)); } float SphereCollider::Radius::get() { - return getNativeCollisionShape().GetWorldRadius(); + return getNativeCollisionShape().GetWorldRadius(); } void SphereCollider::Radius::set(float value) { - getNativeCollisionShape().SetWorldRadius(value); + getNativeCollisionShape().SetWorldRadius(value); } /*---------------------------------------------------------------------------------*/ @@ -196,11 +202,11 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ bool SphereCollider::TestPoint(Vector3 point) { - return getNativeCollisionShape().TestPoint(Convert::ToNative(point)); + return getNativeCollisionShape().TestPoint(Convert::ToNative(point)); } bool SphereCollider::Raycast(Ray ray, float maxDistance) { - return getNativeCollisionShape().Raycast(Convert::ToNative(ray)); + return getNativeCollisionShape().Raycast(Convert::ToNative(ray)); } /*---------------------------------------------------------------------------------*/ @@ -231,7 +237,10 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ int Collider::CollisionShapeCount::get() { - return static_cast(GetNativeComponent()->GetCollisionShapes().size()); + if (const auto* shapes = GetNativeComponent()->GetCollisionShapes()) + return static_cast(shapes->size()); + + return -1; } /*---------------------------------------------------------------------------------*/ @@ -311,10 +320,10 @@ namespace SHADE // Populate the list int i = 0; - for (const auto& collider : GetNativeComponent()->GetCollisionShapes()) + for (const auto* collider : *GetNativeComponent()->GetCollisionShapes()) { CollisionShape^ bound = nullptr; - switch (collider.GetType()) + switch (collider->GetType()) { case SHCollisionShape::Type::BOX: bound = gcnew BoxCollider(i, Owner.GetEntity()); diff --git a/SHADE_Managed/src/Components/Collider.h++ b/SHADE_Managed/src/Components/Collider.h++ index 8ea648aa..c2e732f4 100644 --- a/SHADE_Managed/src/Components/Collider.h++ +++ b/SHADE_Managed/src/Components/Collider.h++ @@ -27,11 +27,11 @@ namespace SHADE try { - auto& shape = collider->GetCollisionShape(arrayIndex); - if (shape.GetType() != SHCollisionShape::Type::BOX) + auto* shape = collider->GetCollisionShape(arrayIndex); + if (!shape || shape->GetType() == SHCollisionShape::Type::INVALID) throw gcnew System::InvalidOperationException("Attempted to retrieve invalid CollisionShape."); - return reinterpret_cast(shape); + return dynamic_cast(*shape); } catch (std::invalid_argument&) { diff --git a/SHADE_Managed/src/Components/RigidBody.hxx b/SHADE_Managed/src/Components/RigidBody.hxx index 6ca39316..8bfe34aa 100644 --- a/SHADE_Managed/src/Components/RigidBody.hxx +++ b/SHADE_Managed/src/Components/RigidBody.hxx @@ -15,7 +15,7 @@ of DigiPen Institute of Technology is prohibited. #pragma once // External Dependencies -#include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" +#include "Physics/Interface/SHRigidBodyComponent.h" // Project Includes #include "Components/Component.hxx" diff --git a/SHADE_Managed/src/Engine/ECS.cxx b/SHADE_Managed/src/Engine/ECS.cxx index 3ace79ab..c388f0cd 100644 --- a/SHADE_Managed/src/Engine/ECS.cxx +++ b/SHADE_Managed/src/Engine/ECS.cxx @@ -23,7 +23,7 @@ of DigiPen Institute of Technology is prohibited. #include "ECS_Base/Managers/SHEntityManager.h" #include "Math/Transform/SHTransformComponent.h" #include "Physics/Interface/SHColliderComponent.h" -#include "Physics/Interface/RigidBodyComponent/SHRigidBodyComponent.h" +#include "Physics/Interface/SHRigidBodyComponent.h" #include "Scene/SHSceneManager.h" #include "Scene/SHSceneGraph.h" #include "Tools/Logger/SHLog.h" From c1d77029142e5631a0293b7e9bd2f1018de89525 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Sun, 11 Dec 2022 20:12:26 +0800 Subject: [PATCH 033/275] Moved debug draw state to colliders. Synced collider positions with rigid bodies --- Assets/Scenes/PhysicsSandbox.shade | 11 +++ .../src/Application/SBApplication.cpp | 3 + .../Inspector/SHEditorComponentView.hpp | 37 +++++----- .../CollisionShapes/SHCollisionShape.cpp | 18 +---- .../CollisionShapes/SHCollisionShape.h | 11 +-- .../SHCollisionTagMatrix.cpp | 0 .../SHCollisionTagMatrix.h | 0 .../{ => CollisionTags}/SHCollisionTags.cpp | 0 .../{ => CollisionTags}/SHCollisionTags.h | 0 .../src/Physics/Collision/SHCollider.cpp | 74 ++++++++++++++++++- .../src/Physics/Collision/SHCollider.h | 33 ++++++--- .../src/Physics/Dynamics/SHPhysicsWorld.cpp | 19 +++-- .../Routines/SHPhysicsDebugDrawRoutine.cpp | 60 +++++++++++++++ .../Routines/SHPhysicsPostUpdateRoutine.cpp | 4 +- .../src/Physics/System/SHPhysicsSystem.cpp | 10 +-- .../src/Physics/System/SHPhysicsSystem.h | 21 +++++- 16 files changed, 230 insertions(+), 71 deletions(-) rename SHADE_Engine/src/Physics/Collision/{ => CollisionTags}/SHCollisionTagMatrix.cpp (100%) rename SHADE_Engine/src/Physics/Collision/{ => CollisionTags}/SHCollisionTagMatrix.h (100%) rename SHADE_Engine/src/Physics/Collision/{ => CollisionTags}/SHCollisionTags.cpp (100%) rename SHADE_Engine/src/Physics/Collision/{ => CollisionTags}/SHCollisionTags.h (100%) create mode 100644 SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp diff --git a/Assets/Scenes/PhysicsSandbox.shade b/Assets/Scenes/PhysicsSandbox.shade index d6b77c17..f600c4ae 100644 --- a/Assets/Scenes/PhysicsSandbox.shade +++ b/Assets/Scenes/PhysicsSandbox.shade @@ -25,6 +25,17 @@ Freeze Rotation Y: false Freeze Rotation Z: false IsActive: true + Collider Component: + Colliders: + - Is Trigger: false + Type: Sphere + Radius: 1 + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true Scripts: ~ - EID: 1 Name: Default diff --git a/SHADE_Application/src/Application/SBApplication.cpp b/SHADE_Application/src/Application/SBApplication.cpp index a58839c5..3cb504c0 100644 --- a/SHADE_Application/src/Application/SBApplication.cpp +++ b/SHADE_Application/src/Application/SBApplication.cpp @@ -111,6 +111,9 @@ namespace Sandbox SHSystemManager::RegisterRoutine(); SHSystemManager::RegisterRoutine(); SHSystemManager::RegisterRoutine(); +#ifdef SHEDITOR + SHSystemManager::RegisterRoutine(); +#endif SHSystemManager::RegisterRoutine(); SHSystemManager::RegisterRoutine(); diff --git a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp index 774697f1..dfc89df1 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp +++ b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp @@ -322,18 +322,21 @@ namespace SHADE { DrawContextMenu(component); - auto* colliders = component->GetCollisionShapes(); - int const size = colliders ? static_cast(colliders->size()) : 0; - ImGui::BeginChild("Collision Shapes", { 0.0f, colliders->empty() ? 1.0f : 250.0f }, true); + auto* collider = component->GetCollider(); + SHEditorWidgets::CheckBox("Draw Colliders", [collider] { return collider->GetDebugDrawState(); }, [collider](bool value) { collider->SetDebugDrawState(value); }); + + auto* collisionShapes = component->GetCollisionShapes(); + int const size = collisionShapes ? static_cast(collisionShapes->size()) : 0; + ImGui::BeginChild("Collision Shapes", { 0.0f, collisionShapes->empty() ? 1.0f : 250.0f }, true); std::optional colliderToDelete{ std::nullopt }; for (int i{}; i < size; ++i) { ImGui::PushID(i); - SHCollisionShape* collider = component->GetCollisionShape(i); + SHCollisionShape* shape = component->GetCollisionShape(i); auto cursorPos = ImGui::GetCursorPos(); //collider->IsTrigger - if (collider->GetType() == SHCollisionShape::Type::BOX) + if (shape->GetType() == SHCollisionShape::Type::BOX) { //SHEditorWidgets::BeginPanel(std::format("{} Box #{}", ICON_FA_CUBE, i).data(), { ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y }); // @@ -344,42 +347,42 @@ namespace SHADE // [BOX] { return BOX->GetRelativeExtents(); }, // [collider](SHVec3 const& vec) { collider->SetBoundingBox(vec); }); } - else if (collider->GetType() == SHCollisionShape::Type::SPHERE) + else if (shape->GetType() == SHCollisionShape::Type::SPHERE) { SHEditorWidgets::BeginPanel(std::format("{} Sphere #{}", ICON_MD_CIRCLE, i).data(), { ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y }); - auto* SPHERE = reinterpret_cast(collider); + auto* SPHERE = reinterpret_cast(shape); SHEditorWidgets::DragFloat ( "Radius", [SPHERE] { return SPHERE->GetRelativeRadius(); }, [SPHERE](float const& value) { SPHERE->SetRelativeRadius(value); }); } - else if (collider->GetType() == SHCollisionShape::Type::CAPSULE) + else if (shape->GetType() == SHCollisionShape::Type::CAPSULE) { } { - SHEditorWidgets::CheckBox("Is Trigger", [collider] { return collider->IsTrigger(); }, [collider](bool value) { collider->SetIsTrigger(value); }); + SHEditorWidgets::CheckBox("Is Trigger", [shape] { return shape->IsTrigger(); }, [shape](bool value) { shape->SetIsTrigger(value); }); if(ImGui::CollapsingHeader("Physics Material")) { - SHEditorWidgets::DragFloat("Friction", [collider] { return collider->GetFriction(); }, [collider](float value) { collider->SetFriction(value); }, "Friction", 0.05f, 0.0f, 1.0f); - SHEditorWidgets::DragFloat("Bounciness", [collider] { return collider->GetBounciness(); }, [collider](float value) { collider->SetBounciness(value); }, "Bounciness", 0.05f, 0.0f, 1.0f); - SHEditorWidgets::DragFloat("Mass Density", [collider] { return collider->GetDensity(); }, [collider](float value) { collider->SetDensity(value); }, "Mass Density", 0.1f, 0.0f); + SHEditorWidgets::DragFloat("Friction", [shape] { return shape->GetFriction(); }, [shape](float value) { shape->SetFriction(value); }, "Friction", 0.05f, 0.0f, 1.0f); + SHEditorWidgets::DragFloat("Bounciness", [shape] { return shape->GetBounciness(); }, [shape](float value) { shape->SetBounciness(value); }, "Bounciness", 0.05f, 0.0f, 1.0f); + SHEditorWidgets::DragFloat("Mass Density", [shape] { return shape->GetDensity(); }, [shape](float value) { shape->SetDensity(value); }, "Mass Density", 0.1f, 0.0f); } SHEditorWidgets::BeginPanel("Offsets",{ ImGui::GetContentRegionAvail().x, 30.0f }); - SHEditorWidgets::DragVec3("Position", { "X", "Y", "Z" }, [&collider] {return collider->GetPositionOffset(); }, [&collider](SHVec3 const& vec) {collider->SetPositionOffset(vec); }); + SHEditorWidgets::DragVec3("Position", { "X", "Y", "Z" }, [&shape] {return shape->GetPositionOffset(); }, [&shape](SHVec3 const& vec) {shape->SetPositionOffset(vec); }); SHEditorWidgets::DragVec3("Rotation", { "X", "Y", "Z" }, - [&collider] + [&shape] { - auto offset = collider->GetRotationOffset(); + auto offset = shape->GetRotationOffset(); return offset; }, - [&collider](SHVec3 const& vec) + [&shape](SHVec3 const& vec) { - collider->SetRotationOffset(vec); + shape->SetRotationOffset(vec); }, true); SHEditorWidgets::EndPanel(); } diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp index 845634fe..2f4e3819 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp @@ -14,7 +14,7 @@ #include "SHCollisionShape.h" // Project Headers -#include "Physics/Collision/SHCollisionTagMatrix.h" +#include "Physics/Collision/CollisionTags/SHCollisionTagMatrix.h" #include "Reflection/SHReflectionMetadata.h" #include "Tools/Utilities/SHUtilities.h" @@ -95,14 +95,6 @@ namespace SHADE return flags & FLAG_VALUE; } - bool SHCollisionShape::GetDebugDrawState() const noexcept - { - static constexpr int FLAG_POS = 6; - static constexpr uint8_t FLAG_VALUE = 1U << FLAG_POS; - - return flags & FLAG_VALUE; - } - const SHCollisionTag& SHCollisionShape::GetCollisionTag() const noexcept { return *collisionTag; @@ -150,14 +142,6 @@ namespace SHADE isTrigger ? flags |= FLAG_VALUE : flags &= ~FLAG_VALUE; } - void SHCollisionShape::SetDebugDrawState(bool isDebugDrawing) noexcept - { - static constexpr int FLAG_POS = 6; - static constexpr uint8_t FLAG_VALUE = 1U << FLAG_POS; - - isDebugDrawing ? flags |= FLAG_VALUE : flags &= ~FLAG_VALUE; - } - void SHCollisionShape::SetCollisionTag(SHCollisionTag* newCollisionTag) noexcept { collisionTag = newCollisionTag; diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h index 420c0d6e..c05baddd 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h @@ -16,7 +16,7 @@ #include "ECS_Base/Entity/SHEntity.h" #include "Math/Geometry/SHShape.h" #include "Math/SHQuaternion.h" -#include "Physics/Collision/SHCollisionTags.h" +#include "Physics/Collision/CollisionTags/SHCollisionTags.h" #include "Physics/Collision/SHPhysicsMaterial.h" #include "SHCollisionShapeID.h" @@ -44,8 +44,8 @@ namespace SHADE enum class Type { - BOX - , SPHERE + SPHERE + , BOX , CAPSULE , COUNT @@ -91,8 +91,6 @@ namespace SHADE [[nodiscard]] Type GetType () const noexcept; [[nodiscard]] bool IsTrigger () const noexcept; [[nodiscard]] bool IsColliding () const noexcept; - [[nodiscard]] bool GetDebugDrawState () const noexcept; - [[nodiscard]] const SHCollisionTag& GetCollisionTag () const noexcept; @@ -110,7 +108,6 @@ namespace SHADE // Flags void SetIsTrigger (bool isTrigger) noexcept; - void SetDebugDrawState (bool isDebugDrawing) noexcept; void SetCollisionTag (SHCollisionTag* newCollisionTag) noexcept; @@ -126,7 +123,7 @@ namespace SHADE SHVec3 positionOffset; SHVec3 rotationOffset; - uint8_t flags; // 0 debugDraw wasColliding isColliding trigger capsule sphere box + uint8_t flags; // 0 0 wasColliding isColliding trigger capsule sphere box SHCollisionTag* collisionTag; RTTR_ENABLE() diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionTagMatrix.cpp b/SHADE_Engine/src/Physics/Collision/CollisionTags/SHCollisionTagMatrix.cpp similarity index 100% rename from SHADE_Engine/src/Physics/Collision/SHCollisionTagMatrix.cpp rename to SHADE_Engine/src/Physics/Collision/CollisionTags/SHCollisionTagMatrix.cpp diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionTagMatrix.h b/SHADE_Engine/src/Physics/Collision/CollisionTags/SHCollisionTagMatrix.h similarity index 100% rename from SHADE_Engine/src/Physics/Collision/SHCollisionTagMatrix.h rename to SHADE_Engine/src/Physics/Collision/CollisionTags/SHCollisionTagMatrix.h diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionTags.cpp b/SHADE_Engine/src/Physics/Collision/CollisionTags/SHCollisionTags.cpp similarity index 100% rename from SHADE_Engine/src/Physics/Collision/SHCollisionTags.cpp rename to SHADE_Engine/src/Physics/Collision/CollisionTags/SHCollisionTags.cpp diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionTags.h b/SHADE_Engine/src/Physics/Collision/CollisionTags/SHCollisionTags.h similarity index 100% rename from SHADE_Engine/src/Physics/Collision/SHCollisionTags.h rename to SHADE_Engine/src/Physics/Collision/CollisionTags/SHCollisionTags.h diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp index e950c4fb..1d29a585 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp @@ -25,14 +25,22 @@ namespace SHADE SHCollider::SHCollider(EntityID eid, const SHTransform& worldTransform) noexcept : entityID { eid } , shapeIDCounter { 0 } +#ifdef SHEDITOR + , debugDraw { false } +#endif , rigidBody { nullptr } , shapeFactory { nullptr } , transform { worldTransform } - {} + { + + } SHCollider::SHCollider(const SHCollider& rhs) noexcept : entityID { rhs.entityID } , shapeIDCounter { rhs.shapeIDCounter } +#ifdef SHEDITOR + , debugDraw { rhs.debugDraw } +#endif , rigidBody { rhs.rigidBody } , shapeFactory { rhs.shapeFactory } , transform { rhs.transform } @@ -49,6 +57,9 @@ namespace SHADE SHCollider::SHCollider(SHCollider&& rhs) noexcept : entityID { rhs.entityID } , shapeIDCounter { rhs.shapeIDCounter } +#ifdef SHEDITOR + , debugDraw { rhs.debugDraw } +#endif , rigidBody { rhs.rigidBody } , shapeFactory { rhs.shapeFactory } , transform { rhs.transform } @@ -94,6 +105,10 @@ namespace SHADE shapeFactory = rhs.shapeFactory; transform = rhs.transform; + #ifdef SHEDITOR + debugDraw = rhs.debugDraw; + #endif + copyShapes(rhs); return *this; @@ -112,6 +127,10 @@ namespace SHADE shapeFactory = rhs.shapeFactory; transform = rhs.transform; + #ifdef SHEDITOR + debugDraw = rhs.debugDraw; + #endif + copyShapes(rhs); return *this; @@ -121,6 +140,13 @@ namespace SHADE /* Getter Function Definitions */ /*-----------------------------------------------------------------------------------*/ +#ifdef SHEDITOR + bool SHCollider::GetDebugDrawState() const noexcept + { + return debugDraw; + } +#endif + const SHTransform& SHCollider::GetTransform() const noexcept { return transform; @@ -160,6 +186,13 @@ namespace SHADE /* Setter Function Definitions */ /*-----------------------------------------------------------------------------------*/ +#ifdef SHEDITOR + void SHCollider::SetDebugDrawState(bool state) noexcept + { + debugDraw = state; + } +#endif + void SHCollider::SetRigidBody(SHRigidBody* rb) noexcept { rigidBody = rb; @@ -190,7 +223,6 @@ namespace SHADE shapeFactory = factory; } - /*-----------------------------------------------------------------------------------*/ /* Public Member Function Definitions */ /*-----------------------------------------------------------------------------------*/ @@ -266,9 +298,28 @@ namespace SHADE SHLOG_INFO_D("Removing Collision Shape {} from Entity {}", index, entityID) } - void SHCollider::RecomputeShapes() const noexcept + void SHCollider::RecomputeShapes() noexcept { - + for (auto* shape : shapes) + { + switch (shape->GetType()) + { + case SHCollisionShape::Type::SPHERE: + { + recomputeSphere(dynamic_cast(shape)); + break; + } + case SHCollisionShape::Type::BOX: + { + break; + } + case SHCollisionShape::Type::CAPSULE: + { + break; + } + default: break; + } + } } /*-----------------------------------------------------------------------------------*/ @@ -324,4 +375,19 @@ namespace SHADE ++shapeIDCounter; } + void SHCollider::recomputeSphere(SHSphereCollisionShape* sphere) noexcept + { + // Recompute world radius + const float SPHERE_SCALE = std::fabs(SHMath::Max({ transform.scale.x, transform.scale.y, transform.scale.z })); + sphere->SetScale(SPHERE_SCALE); + + // Recompute center + const SHQuaternion FINAL_ROT = transform.orientation * SHQuaternion::FromEuler(sphere->rotationOffset); + const SHMatrix TRS = SHMatrix::Rotate(FINAL_ROT) * SHMatrix::Translate(transform.position); + + const SHVec3 NEW_CENTER = SHVec3::Transform(sphere->positionOffset, TRS); + sphere->SetCenter(NEW_CENTER); + } + + } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.h b/SHADE_Engine/src/Physics/Collision/SHCollider.h index 37d96caa..cab433a5 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.h +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.h @@ -78,6 +78,10 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ + #ifdef SHEDITOR + [[nodiscard]] bool GetDebugDrawState () const noexcept; + #endif + [[nodiscard]] const SHTransform& GetTransform () const noexcept; [[nodiscard]] const SHVec3& GetPosition () const noexcept; [[nodiscard]] const SHQuaternion& GetOrientation () const noexcept; @@ -90,14 +94,18 @@ namespace SHADE /* Setter Functions */ /*---------------------------------------------------------------------------------*/ - void SetRigidBody (SHRigidBody* rb) noexcept; + #ifdef SHEDITOR + void SetDebugDrawState (bool state) noexcept; + #endif - void SetTransform (const SHTransform& newTransform) noexcept; - void SetPosition (const SHVec3& newPosition) noexcept; - void SetOrientation (const SHQuaternion& newOrientation) noexcept; - void SetScale (const SHVec3& newScale) noexcept; + void SetRigidBody (SHRigidBody* rb) noexcept; - void SetFactory (SHCollisionShapeFactory* factory) noexcept; + void SetTransform (const SHTransform& newTransform) noexcept; + void SetPosition (const SHVec3& newPosition) noexcept; + void SetOrientation (const SHQuaternion& newOrientation) noexcept; + void SetScale (const SHVec3& newScale) noexcept; + + void SetFactory (SHCollisionShapeFactory* factory) noexcept; /*---------------------------------------------------------------------------------*/ /* Member Functions */ @@ -125,6 +133,7 @@ namespace SHADE * The index of the newly added shape. */ int AddSphereCollisionShape (float relativeRadius, const SHVec3& posOffset = SHVec3::Zero, const SHVec3& rotOffset = SHVec3::Zero); + // TODO: Add Box & Capsule /** @@ -142,7 +151,7 @@ namespace SHADE * @brief * Recomputes the transforms for all shapes in this composite collider. */ - void RecomputeShapes () const noexcept; + void RecomputeShapes () noexcept; protected: /*---------------------------------------------------------------------------------*/ @@ -152,6 +161,10 @@ namespace SHADE EntityID entityID; uint32_t shapeIDCounter; // This increments everytime a shape is added to differentiate shapes. + #ifdef SHEDITOR + bool debugDraw; + #endif + SHRigidBody* rigidBody; SHCollisionShapeFactory* shapeFactory; @@ -163,8 +176,10 @@ namespace SHADE /* Member Functions */ /*---------------------------------------------------------------------------------*/ - void copyShapes (const SHCollider& rhsCollider); - void copyShape (const SHCollisionShape* rhsShape); + void copyShapes (const SHCollider& rhsCollider); + void copyShape (const SHCollisionShape* rhsShape); + + void recomputeSphere (SHSphereCollisionShape* sphere) noexcept; }; } // namespace SHADE diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp index 0d7434f6..e6c02168 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp @@ -122,15 +122,20 @@ namespace SHADE { rigidBody.linearVelocity = SHVec3::Zero; } - else // Dynamic & Kinematic bodies + // Dynamic & Kinematic bodies + // Both dynamic and kinematic can sleep when their velocities are under the thresholds. + else if (!rigidBody.IsSleeping()) { - // Both dynamic and kinematic can sleep when their velocities are under the thresholds. - if (!rigidBody.IsSleeping()) - { - ENFORCE_CONSTRAINED_VELOCITIES(rigidBody); + ENFORCE_CONSTRAINED_VELOCITIES(rigidBody); - rigidBody.motionState.IntegratePosition(rigidBody.linearVelocity, dt); - // TODO: Integrate orientations + rigidBody.motionState.IntegratePosition(rigidBody.linearVelocity, dt); + // TODO: Integrate orientations + + // Sync with collider transforms if a collider is present + if (rigidBody.collider) + { + rigidBody.collider->SetPosition(rigidBody.motionState.position); + // TODO: Sync orientations } } diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp new file mode 100644 index 00000000..c73e8ab7 --- /dev/null +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp @@ -0,0 +1,60 @@ +/**************************************************************************************** + * \file SHPhysicsDebugDrawRoutine.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for the Physics Debug-Draw Routine + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "Physics/System/SHPhysicsSystem.h" + +// Project Headers +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h" + +#include "Scripting/SHScriptEngine.h" + + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsSystem::PhysicsDebugDraw::PhysicsDebugDraw() + : SHSystemRoutine { "Physics Debug Draw", false } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsSystem::PhysicsDebugDraw::Execute(double) noexcept + { + auto* physicsSystem = reinterpret_cast(GetSystem()); + + // Get debug drawing system + auto* debugDrawSystem = SHSystemManager::GetSystem(); + if (!debugDrawSystem) + { + SHLOG_ERROR("Debug draw system unavailable! Colliders cannot be drawn!") + return; + } + + //SHPhysicsDebugDraw& physicsDebugRenderer = physicsSystem->debugRenderer; + + //if (!physicsDebugRenderer.GetDebugDrawState()) + // return; + + //// Draw colliders + //if (physicsDebugRenderer.GetDrawSpecificCollidersState()) + // physicsDebugRenderer.DrawSpecificColliders(debugDrawSystem); + //else if (physicsDebugRenderer.GetDrawAllCollidersState()) + // physicsDebugRenderer.DrawAllColliders(debugDrawSystem); + } +} diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp index cd4b1cff..d5492cdf 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp @@ -15,6 +15,7 @@ // Project Headers #include "ECS_Base/Managers/SHSystemManager.h" +#include "Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h" #include "Math/Transform/SHTransformComponent.h" #include "Scripting/SHScriptEngine.h" @@ -77,4 +78,5 @@ namespace SHADE if (scriptingSystem != nullptr) scriptingSystem->ExecuteCollisionFunctions(); } -} + +} // namespace SHADE diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp index d7bece3b..59cd68fb 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp @@ -19,7 +19,7 @@ #include "ECS_Base/Managers/SHEntityManager.h" #include "ECS_Base/Managers/SHSystemManager.h" #include "Editor/SHEditor.h" -#include "Physics/Collision/SHCollisionTagMatrix.h" +#include "Physics/Collision/CollisionTags/SHCollisionTagMatrix.h" #include "Physics/Interface/SHColliderComponent.h" namespace SHADE @@ -42,7 +42,7 @@ namespace SHADE eventFunctions[3] = { &SHPhysicsSystem::onSceneExit , SH_SCENE_ON_EXIT_EVENT }; #ifdef SHEDITOR - eventFunctions[4] = { &SHPhysicsSystem::onEditorPlay , SH_EDITOR_ON_PLAY_EVENT }; + eventFunctions[4] = { &SHPhysicsSystem::onEditorPlay , SH_EDITOR_ON_PLAY_EVENT }; #endif } @@ -189,7 +189,7 @@ namespace SHADE physicsWorld->AddRigidBody(PHYSICS_OBJECT.rigidBody); if (PHYSICS_OBJECT.collider) - physicsWorld->AddCollider(PHYSICS_OBJECT.collider); + physicsWorld->AddCollider(PHYSICS_OBJECT.collider); } #endif @@ -210,7 +210,7 @@ namespace SHADE physicsWorld->RemoveRigidBody(PHYSICS_OBJECT.rigidBody); if (PHYSICS_OBJECT.collider) - physicsWorld->RemoveCollider(PHYSICS_OBJECT.collider); + physicsWorld->RemoveCollider(PHYSICS_OBJECT.collider); } delete physicsWorld; @@ -322,7 +322,7 @@ namespace SHADE physicsWorld->AddRigidBody(PHYSICS_OBJECT.rigidBody); if (PHYSICS_OBJECT.collider) - physicsWorld->AddCollider(PHYSICS_OBJECT.collider); + physicsWorld->AddCollider(PHYSICS_OBJECT.collider); } return onEditorPlayEvent.get()->handle; diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h index 2a351e86..ca73df81 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h @@ -21,7 +21,6 @@ #include "Physics/Interface/SHRigidBodyComponent.h" #include "Physics/Interface/SHColliderComponent.h" - namespace SHADE { /*-----------------------------------------------------------------------------------*/ @@ -95,7 +94,8 @@ namespace SHADE /** * @brief * The physics update that runs after the simulation. This sets the rendering - * transforms and sends messages to scripting system for collision & trigger events. + * transforms and sends messages to scripting system for collision & trigger events.
+ * */ class SH_API PhysicsPostUpdate final : public SHSystemRoutine { @@ -104,6 +104,21 @@ namespace SHADE void Execute(double dt) noexcept override; }; + #ifdef SHEDITOR + /** + * @brief + * If the editor is enabled, this routine invokes debug drawing for colliders. + * and collision information. + */ + class SH_API PhysicsDebugDraw final : public SHSystemRoutine + { + public: + PhysicsDebugDraw(); + void Execute(double dt) noexcept override; + }; + + #endif + private: /*---------------------------------------------------------------------------------*/ /* Type Definitions */ @@ -125,7 +140,6 @@ namespace SHADE EventFunctionPair eventFunctions[NUM_EVENT_FUNCTIONS]; // System data - bool worldUpdated; double interpolationFactor; double fixedDT; @@ -133,7 +147,6 @@ namespace SHADE // Sub-systems / managers SHPhysicsWorld* physicsWorld; - SHPhysicsObjectManager physicsObjectManager; /*---------------------------------------------------------------------------------*/ From bf8a410fa2a933656b68d5df749010c18c5182ee Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Sun, 11 Dec 2022 20:33:30 +0800 Subject: [PATCH 034/275] Fixed bug where colliders were not properly deserialised --- .../Collision/CollisionShapes/SHCollisionShapeFactory.cpp | 3 +-- SHADE_Engine/src/Serialization/SHYAMLConverters.h | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.cpp index 293d5c2a..d6ef5f0d 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.cpp +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.cpp @@ -59,12 +59,11 @@ namespace SHADE case SHCollisionShape::Type::SPHERE: { SHSphereCollisionShape* sphere = spheres.find(shape->id)->second; + spheres.erase(shape->id); delete sphere; sphere = nullptr; - spheres.erase(shape->id); - break; } case SHCollisionShape::Type::CAPSULE: diff --git a/SHADE_Engine/src/Serialization/SHYAMLConverters.h b/SHADE_Engine/src/Serialization/SHYAMLConverters.h index cbbe8505..73b22fd6 100644 --- a/SHADE_Engine/src/Serialization/SHYAMLConverters.h +++ b/SHADE_Engine/src/Serialization/SHYAMLConverters.h @@ -237,8 +237,8 @@ namespace YAML switch (colliderType) { - case SHCollisionShape::Type::BOX: break; case SHCollisionShape::Type::SPHERE: collider->AddSphereCollisionShape(1.0f); break; + case SHCollisionShape::Type::BOX: break; case SHCollisionShape::Type::CAPSULE: break; default:; } From 0cebedeee09f32c092966a9aba30e4da1a51616a Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Sun, 11 Dec 2022 20:44:40 +0800 Subject: [PATCH 035/275] Fixed compile errors with merged scene init and exit events --- SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp | 4 ++-- SHADE_Engine/src/Scene/SHSceneManager.cpp | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp index 59cd68fb..388adadc 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp @@ -38,8 +38,8 @@ namespace SHADE eventFunctions[0] = { &SHPhysicsSystem::onComponentAdded , SH_COMPONENT_ADDED_EVENT }; eventFunctions[1] = { &SHPhysicsSystem::onComponentRemoved, SH_COMPONENT_REMOVED_EVENT }; - eventFunctions[2] = { &SHPhysicsSystem::onSceneInit , SH_SCENE_ON_INIT_EVENT }; - eventFunctions[3] = { &SHPhysicsSystem::onSceneExit , SH_SCENE_ON_EXIT_EVENT }; + eventFunctions[2] = { &SHPhysicsSystem::onSceneInit , SH_SCENE_INIT_POST }; + eventFunctions[3] = { &SHPhysicsSystem::onSceneExit , SH_SCENE_EXIT_POST }; #ifdef SHEDITOR eventFunctions[4] = { &SHPhysicsSystem::onEditorPlay , SH_EDITOR_ON_PLAY_EVENT }; diff --git a/SHADE_Engine/src/Scene/SHSceneManager.cpp b/SHADE_Engine/src/Scene/SHSceneManager.cpp index d3b84d6a..79e8818a 100644 --- a/SHADE_Engine/src/Scene/SHSceneManager.cpp +++ b/SHADE_Engine/src/Scene/SHSceneManager.cpp @@ -62,8 +62,6 @@ namespace SHADE SHEventManager::BroadcastEvent(exitEvent, SH_SCENE_EXIT_PRE); currentScene->Free(); - SHEventManager::BroadcastEvent(SHSceneExitEvent{ currentSceneID }, SH_SCENE_ON_EXIT_EVENT); - currentScene->Unload(); SHEntityManager::DestroyAllEntity(); @@ -112,8 +110,6 @@ namespace SHADE SHEventManager::BroadcastEvent(exitEvent, SH_SCENE_EXIT_PRE); currentScene->Free(); - SHEventManager::BroadcastEvent(SHSceneExitEvent{ currentSceneID }, SH_SCENE_ON_EXIT_EVENT); - if (cleanReload == true) { cleanReload = false; From 82765648c41765a0e94b389c27ea83dee47486ee Mon Sep 17 00:00:00 2001 From: Sri Sham Haran Date: Mon, 12 Dec 2022 11:49:52 +0800 Subject: [PATCH 036/275] fix colliderpanel --- .../ColliderTagPanel/SHColliderTagPanel.cpp | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/SHADE_Engine/src/Editor/EditorWindow/ColliderTagPanel/SHColliderTagPanel.cpp b/SHADE_Engine/src/Editor/EditorWindow/ColliderTagPanel/SHColliderTagPanel.cpp index 5ff3842e..1dd0047d 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/ColliderTagPanel/SHColliderTagPanel.cpp +++ b/SHADE_Engine/src/Editor/EditorWindow/ColliderTagPanel/SHColliderTagPanel.cpp @@ -13,18 +13,18 @@ namespace SHADE { ImGui::BeginTable("CollisionMtxTable", SHCollisionTag::NUM_LAYERS + 1, ImGuiTableRowFlags_Headers); ImGui::TableNextRow(); - for (int i = 0; i <= SHCollisionTag::NUM_LAYERS; ++i) + for (int i = SHCollisionTag::NUM_LAYERS; i >= 0; --i) { ImGui::TableNextColumn(); - if(i == 0) continue; - std::string const& tagName = SHCollisionTagMatrix::GetTagName(i- 1); - auto tag = SHCollisionTagMatrix::GetTag(i - 1); + if(i == SHCollisionTag::NUM_LAYERS) continue; + std::string const& tagName = SHCollisionTagMatrix::GetTagName(i); + auto tag = SHCollisionTagMatrix::GetTag(i); if (!tag) continue; //ImGui::Text(tagName.data()); - SHEditorWidgets::InputText("##" + std::to_string(i), [i]{return SHCollisionTagMatrix::GetTagName(i- 1);}, [i](std::string const& value){SHCollisionTagMatrix::GetTag(i)->SetName(value);}, tagName.data(), ImGuiInputTextFlags_EnterReturnsTrue); + SHEditorWidgets::InputText("##" + std::to_string(i), [i]{return SHCollisionTagMatrix::GetTagName(i);}, [i](std::string const& value){SHCollisionTagMatrix::GetTag(i)->SetName(value);}, tagName.data(), ImGuiInputTextFlags_EnterReturnsTrue); } - for (int i = SHCollisionTag::NUM_LAYERS - 1; i >= 0; --i) + for (int i = 0; i < SHCollisionTag::NUM_LAYERS; ++i) { std::string tagName = SHCollisionTagMatrix::GetTagName(i); auto tag = SHCollisionTagMatrix::GetTag(i); @@ -33,10 +33,11 @@ namespace SHADE ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text(tagName.data()); - for (int j = 0; j < SHCollisionTag::NUM_LAYERS - i; ++j) + for (int j = (SHCollisionTag::NUM_LAYERS) - 1; j >= i; --j) { - std::string tagName2 = SHCollisionTagMatrix::GetTagName(j); - auto tag2 = SHCollisionTagMatrix::GetTag(j); + int idx = j; + std::string tagName2 = SHCollisionTagMatrix::GetTagName(idx); + auto tag2 = SHCollisionTagMatrix::GetTag(idx); if(!tag2) continue; @@ -44,14 +45,13 @@ namespace SHADE if(tagName.empty()) tagName = std::to_string(i); if(tagName2.empty()) - tagName2 = std::to_string(j); + tagName2 = std::to_string(idx); ImGui::TableNextColumn(); - //if(i == j) + //if(i == idx) // continue; std::string_view label = std::format("##{} vs {}", tagName, tagName2).data(); - SHEditorWidgets::CheckBox(label, [tag, &j]{return tag->GetLayerState(j);}, [tag, i, j](bool const& value){tag->SetLayerState(j, value); SHCollisionTagMatrix::GetTag(j)->SetLayerState(i, value);}, label.substr(2)); - + SHEditorWidgets::CheckBox(label, [tag, &idx]{return tag->GetLayerState(idx);}, [tag, i, idx](bool const& value){tag->SetLayerState(idx, value); SHCollisionTagMatrix::GetTag(idx)->SetLayerState(i, value);}, label.substr(2)); } } ImGui::EndTable(); From 9b17c62b1dff76b7d19554edc41b691f80cf1d6e Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Mon, 12 Dec 2022 17:07:18 +0800 Subject: [PATCH 037/275] Added active in hierarchy property to scene nodes --- SHADE_Engine/src/Scene/SHSceneGraph.cpp | 40 +++++++++------- SHADE_Engine/src/Scene/SHSceneGraph.h | 1 + SHADE_Engine/src/Scene/SHSceneNode.cpp | 62 ++++++++++++++++++------- SHADE_Engine/src/Scene/SHSceneNode.h | 18 +++++-- 4 files changed, 82 insertions(+), 39 deletions(-) diff --git a/SHADE_Engine/src/Scene/SHSceneGraph.cpp b/SHADE_Engine/src/Scene/SHSceneGraph.cpp index 5e9f331d..392cbbeb 100644 --- a/SHADE_Engine/src/Scene/SHSceneGraph.cpp +++ b/SHADE_Engine/src/Scene/SHSceneGraph.cpp @@ -186,6 +186,27 @@ namespace SHADE return NODE_ITER->second->GetChildren(); } + bool SHSceneGraph::IsActive(EntityID entityID) const noexcept + { + //////////////////////////////////////// + // Error handling + if (!SHEntityManager::IsValidEID(entityID)) + { + SHLOG_ERROR("Entity {} is invalid!", entityID) + return false; + } + + const auto NODE_ITER = entityNodeMap.find(entityID); + if (NODE_ITER == entityNodeMap.end()) + { + SHLOG_ERROR("Entity {} cannot be found in the scene!", entityID) + return false; + } + //////////////////////////////////////// + + return NODE_ITER->second->IsActive(); + } + bool SHSceneGraph::IsActiveInHierarchy(EntityID entityID) const noexcept { //////////////////////////////////////// @@ -204,24 +225,7 @@ namespace SHADE } //////////////////////////////////////// - // Recurse up the tree until the root. If any parent is inactive, this node is inactive in the hierarchy. - const SHSceneNode* PARENT_NODE = NODE_ITER->second->parent; - - while (PARENT_NODE->GetEntityID() != root->GetEntityID()) - { - if (!PARENT_NODE->IsActive()) - return false; - - if (!PARENT_NODE->parent) - { - SHLOGV_ERROR("Entity {}'s node that is not the root has no parent!", PARENT_NODE->GetEntityID()) - return false; - } - - PARENT_NODE = PARENT_NODE->parent; - } - - return true; + return NODE_ITER->second->IsActiveInHierarchy(); } /*-----------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Scene/SHSceneGraph.h b/SHADE_Engine/src/Scene/SHSceneGraph.h index 37d0e063..68e13b69 100644 --- a/SHADE_Engine/src/Scene/SHSceneGraph.h +++ b/SHADE_Engine/src/Scene/SHSceneGraph.h @@ -58,6 +58,7 @@ namespace SHADE [[nodiscard]] SHSceneNode* GetChild (EntityID entityID, EntityID childEntityID) const noexcept; [[nodiscard]] const std::vector& GetChildren (EntityID entityID) const noexcept; + [[nodiscard]] bool IsActive (EntityID entityID) const noexcept; [[nodiscard]] bool IsActiveInHierarchy (EntityID entityID) const noexcept; /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Scene/SHSceneNode.cpp b/SHADE_Engine/src/Scene/SHSceneNode.cpp index 28f47989..42046df4 100644 --- a/SHADE_Engine/src/Scene/SHSceneNode.cpp +++ b/SHADE_Engine/src/Scene/SHSceneNode.cpp @@ -23,24 +23,27 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ SHSceneNode::SHSceneNode(EntityID eid, SHSceneNode* parent) noexcept - : active { true } - , entityID { eid } - , parent { parent } + : active { true } + , isActiveInHierarchy { true } + , entityID { eid } + , parent { parent } {} SHSceneNode::SHSceneNode(const SHSceneNode& rhs) noexcept - : active { rhs.active } - , entityID { rhs.entityID } - , parent { rhs.parent } + : active { rhs.active } + , isActiveInHierarchy { rhs.isActiveInHierarchy } + , entityID { rhs.entityID } + , parent { rhs.parent } { std::ranges::copy(rhs.children.begin(), rhs.children.end(), std::back_inserter(children)); } SHSceneNode::SHSceneNode(SHSceneNode&& rhs) noexcept - : active { rhs.active } - , entityID { rhs.entityID } - , parent { rhs.parent } + : active { rhs.active } + , isActiveInHierarchy { rhs.isActiveInHierarchy } + , entityID { rhs.entityID } + , parent { rhs.parent } { std::ranges::copy(rhs.children.begin(), rhs.children.end(), std::back_inserter(children)); } @@ -50,9 +53,10 @@ namespace SHADE if (this == &rhs) return *this; - active = rhs.active; - entityID = rhs.entityID; - parent = rhs.parent; + active = rhs.active; + isActiveInHierarchy = rhs.isActiveInHierarchy; + entityID = rhs.entityID; + parent = rhs.parent; children.clear(); std::ranges::copy(rhs.children.begin(), rhs.children.end(), std::back_inserter(children)); @@ -62,9 +66,10 @@ namespace SHADE SHSceneNode& SHSceneNode::operator=(SHSceneNode&& rhs) noexcept { - active = rhs.active; - entityID = rhs.entityID; - parent = rhs.parent; + active = rhs.active; + isActiveInHierarchy = rhs.isActiveInHierarchy; + entityID = rhs.entityID; + parent = rhs.parent; children.clear(); std::ranges::copy(rhs.children.begin(), rhs.children.end(), std::back_inserter(children)); @@ -81,6 +86,11 @@ namespace SHADE return active; } + bool SHSceneNode::IsActiveInHierarchy() const noexcept + { + return isActiveInHierarchy; + } + EntityID SHSceneNode::GetEntityID() const noexcept { return entityID; @@ -132,7 +142,27 @@ namespace SHADE void SHSceneNode::SetActive(bool newActiveState) noexcept { - active = newActiveState; + active = newActiveState; + isActiveInHierarchy = newActiveState; + + // Recurse down the children to set the active in hierarchy state + recursiveSetActiveInHierarchy(active, children); + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Member Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHSceneNode::recursiveSetActiveInHierarchy(bool activeInHierarchy, const std::vector& childrenToSet) noexcept + { + if (childrenToSet.empty()) + return; + + for (auto* child : childrenToSet) + { + child->isActiveInHierarchy = activeInHierarchy; + recursiveSetActiveInHierarchy(activeInHierarchy, child->children); + } } } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Scene/SHSceneNode.h b/SHADE_Engine/src/Scene/SHSceneNode.h index 87bd8d0b..62979850 100644 --- a/SHADE_Engine/src/Scene/SHSceneNode.h +++ b/SHADE_Engine/src/Scene/SHSceneNode.h @@ -43,7 +43,7 @@ namespace SHADE /* Constructors & Destructor */ /*---------------------------------------------------------------------------------*/ - ~SHSceneNode () = default; + ~SHSceneNode () noexcept = default; SHSceneNode (EntityID eid, SHSceneNode* parent = nullptr) noexcept; SHSceneNode (const SHSceneNode& rhs) noexcept; @@ -55,10 +55,11 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] bool IsActive () const noexcept; - [[nodiscard]] EntityID GetEntityID () const noexcept; - [[nodiscard]] SHSceneNode* GetParent () const noexcept; - [[nodiscard]] const std::vector& GetChildren () const noexcept; + [[nodiscard]] bool IsActive () const noexcept; + [[nodiscard]] bool IsActiveInHierarchy () const noexcept; + [[nodiscard]] EntityID GetEntityID () const noexcept; + [[nodiscard]] SHSceneNode* GetParent () const noexcept; + [[nodiscard]] const std::vector& GetChildren () const noexcept; [[nodiscard]] SHSceneNode* GetChild (EntityID childID) const noexcept; @@ -74,9 +75,16 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ bool active; + bool isActiveInHierarchy; EntityID entityID; SHSceneNode* parent; std::vector children; + + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + static void recursiveSetActiveInHierarchy(bool activeInHierarchy, const std::vector& childrenToSet) noexcept; }; } // namespace SHADE From 7820d332b11cf26039d377773ad438bca88dee28 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Mon, 12 Dec 2022 17:58:15 +0800 Subject: [PATCH 038/275] Changed managed code's GameObject to synce with the node's active state --- SHADE_Engine/src/Scene/SHSceneGraph.cpp | 21 +++++++++++++++++++++ SHADE_Engine/src/Scene/SHSceneGraph.h | 2 ++ SHADE_Engine/src/Scene/SHSceneNode.cpp | 4 ++++ SHADE_Managed/src/Engine/GameObject.cxx | 13 ++++++++++--- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/SHADE_Engine/src/Scene/SHSceneGraph.cpp b/SHADE_Engine/src/Scene/SHSceneGraph.cpp index 392cbbeb..b876f5b0 100644 --- a/SHADE_Engine/src/Scene/SHSceneGraph.cpp +++ b/SHADE_Engine/src/Scene/SHSceneGraph.cpp @@ -310,6 +310,27 @@ namespace SHADE SHEventManager::BroadcastEvent(EVENT_DATA, SH_SCENEGRAPH_CHANGE_PARENT_EVENT); } + void SHSceneGraph::SetActive(EntityID entityID, bool isActive) noexcept + { + //////////////////////////////////////// + // Error handling + if (!SHEntityManager::IsValidEID(entityID)) + { + SHLOG_ERROR("Entity {} is invalid!", entityID) + return; + } + + const auto NODE_ITER = entityNodeMap.find(entityID); + if (NODE_ITER == entityNodeMap.end()) + { + SHLOG_ERROR("Entity {} cannot be found in the scene!", entityID) + return; + } + //////////////////////////////////////// + + NODE_ITER->second->SetActive(isActive); + } + /*-----------------------------------------------------------------------------------*/ /* Public Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Scene/SHSceneGraph.h b/SHADE_Engine/src/Scene/SHSceneGraph.h index 68e13b69..3285fc6f 100644 --- a/SHADE_Engine/src/Scene/SHSceneGraph.h +++ b/SHADE_Engine/src/Scene/SHSceneGraph.h @@ -68,6 +68,8 @@ namespace SHADE void SetParent (EntityID entityID, SHSceneNode* newParent) noexcept; void SetParent (EntityID entityID, EntityID newParent) noexcept; + void SetActive (EntityID entityID, bool isActive) noexcept; + /*---------------------------------------------------------------------------------*/ /* Function Members */ /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Scene/SHSceneNode.cpp b/SHADE_Engine/src/Scene/SHSceneNode.cpp index 42046df4..2b45b037 100644 --- a/SHADE_Engine/src/Scene/SHSceneNode.cpp +++ b/SHADE_Engine/src/Scene/SHSceneNode.cpp @@ -145,6 +145,10 @@ namespace SHADE active = newActiveState; isActiveInHierarchy = newActiveState; + // Set the entity's active state + // TODO(Daniel / Diren): Sync it based on active in hierarchy or active state. + SHEntityManager::GetEntityByID(entityID)->SetActive(active); + // Recurse down the children to set the active in hierarchy state recursiveSetActiveInHierarchy(active, children); } diff --git a/SHADE_Managed/src/Engine/GameObject.cxx b/SHADE_Managed/src/Engine/GameObject.cxx index 3557e8f1..0e03a643 100644 --- a/SHADE_Managed/src/Engine/GameObject.cxx +++ b/SHADE_Managed/src/Engine/GameObject.cxx @@ -76,7 +76,13 @@ namespace SHADE { if (!valid) throw gcnew System::NullReferenceException(); - return GetNativeEntity().GetActive(); + auto node = SHSceneManager::GetCurrentSceneGraph().GetNode(GetEntity()); + if (!node) + { + Debug::LogWarning("Attempting to access a GameObject's Active state which does not exist. Assuming inactive."); + return false; + } + return node->IsActive(); } bool GameObject::IsActiveInHierarchy::get() { @@ -88,7 +94,7 @@ namespace SHADE Debug::LogWarning("Attempting to access a GameObject's ActiveInHierarchy state which does not exist. Assuming inactive."); return false; } - return node->IsActive(); + return node->IsActiveInHierarchy(); } Entity GameObject::EntityId::get() { @@ -148,7 +154,8 @@ namespace SHADE { if (!valid) throw gcnew System::NullReferenceException(); - GetNativeEntity().SetActive(active); + + SHSceneManager::GetCurrentSceneGraph().SetActive(GetEntity(), active); } /*---------------------------------------------------------------------------------*/ From af3a5e7dc9313c5cbbc14123b3b8e533af715812 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Tue, 13 Dec 2022 03:54:37 +0800 Subject: [PATCH 039/275] Re-implemented Collider Debug Drawing --- Assets/Scenes/PhysicsSandbox.shade | 1 + .../src/Application/SBApplication.cpp | 7 +- .../Inspector/SHEditorComponentView.hpp | 3 +- SHADE_Engine/src/Events/SHEventDefines.h | 26 +-- .../src/Physics/Collision/SHCollider.cpp | 62 ++++--- .../src/Physics/Collision/SHCollider.h | 6 - .../src/Physics/Dynamics/SHPhysicsWorld.cpp | 2 + .../Physics/Interface/SHColliderComponent.cpp | 19 ++- .../Physics/Interface/SHColliderComponent.h | 10 +- SHADE_Engine/src/Physics/SHPhysicsEvents.h | 10 +- .../Routines/SHPhysicsDebugDrawRoutine.cpp | 78 ++++++--- .../Routines/SHPhysicsPostUpdateRoutine.cpp | 1 - .../System/SHPhysicsDebugDrawSystem.cpp | 158 ++++++++++++++++++ .../Physics/System/SHPhysicsDebugDrawSystem.h | 150 +++++++++++++++++ .../src/Physics/System/SHPhysicsSystem.cpp | 3 +- .../src/Physics/System/SHPhysicsSystem.h | 33 ++-- .../src/Serialization/SHYAMLConverters.h | 8 + 17 files changed, 482 insertions(+), 95 deletions(-) create mode 100644 SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp create mode 100644 SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h diff --git a/Assets/Scenes/PhysicsSandbox.shade b/Assets/Scenes/PhysicsSandbox.shade index f600c4ae..0005b35e 100644 --- a/Assets/Scenes/PhysicsSandbox.shade +++ b/Assets/Scenes/PhysicsSandbox.shade @@ -26,6 +26,7 @@ Freeze Rotation Z: false IsActive: true Collider Component: + DrawColliders: true Colliders: - Is Trigger: false Type: Sphere diff --git a/SHADE_Application/src/Application/SBApplication.cpp b/SHADE_Application/src/Application/SBApplication.cpp index 3cb504c0..aa5a4f0e 100644 --- a/SHADE_Application/src/Application/SBApplication.cpp +++ b/SHADE_Application/src/Application/SBApplication.cpp @@ -31,6 +31,7 @@ #include "Input/SHInputManager.h" #include "Math/Transform/SHTransformSystem.h" #include "Physics/System/SHPhysicsSystem.h" +#include "Physics/System/SHPhysicsDebugDrawSystem.h" #include "Scripting/SHScriptEngine.h" #include "UI/SHUISystem.h" @@ -83,7 +84,6 @@ namespace Sandbox SHSystemManager::CreateSystem(); SHGraphicsSystem* graphicsSystem = static_cast(SHSystemManager::GetSystem()); - SHPhysicsSystem* physicsSystem = SHSystemManager::GetSystem(); // Link up SHDebugDraw SHSystemManager::CreateSystem(); @@ -98,6 +98,8 @@ namespace Sandbox editor->SetSDLWindow(sdlWindow); editor->SetSHWindow(&window); } + + SHSystemManager::CreateSystem(); #endif // Create Routines @@ -111,8 +113,9 @@ namespace Sandbox SHSystemManager::RegisterRoutine(); SHSystemManager::RegisterRoutine(); SHSystemManager::RegisterRoutine(); + #ifdef SHEDITOR - SHSystemManager::RegisterRoutine(); + SHSystemManager::RegisterRoutine(); #endif SHSystemManager::RegisterRoutine(); diff --git a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp index dfc89df1..dd531708 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp +++ b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp @@ -322,8 +322,7 @@ namespace SHADE { DrawContextMenu(component); - auto* collider = component->GetCollider(); - SHEditorWidgets::CheckBox("Draw Colliders", [collider] { return collider->GetDebugDrawState(); }, [collider](bool value) { collider->SetDebugDrawState(value); }); + SHEditorWidgets::CheckBox("Draw Colliders", [component] { return component->GetDebugDrawState(); }, [component](bool value) { component->SetDebugDrawState(value); }); auto* collisionShapes = component->GetCollisionShapes(); int const size = collisionShapes ? static_cast(collisionShapes->size()) : 0; diff --git a/SHADE_Engine/src/Events/SHEventDefines.h b/SHADE_Engine/src/Events/SHEventDefines.h index bdc4c505..2a45da1b 100644 --- a/SHADE_Engine/src/Events/SHEventDefines.h +++ b/SHADE_Engine/src/Events/SHEventDefines.h @@ -10,16 +10,18 @@ constexpr SHEventIdentifier SH_ENTITY_DESTROYED_EVENT { 1 }; constexpr SHEventIdentifier SH_ENTITY_CREATION_EVENT { 2 }; constexpr SHEventIdentifier SH_COMPONENT_ADDED_EVENT { 3 }; constexpr SHEventIdentifier SH_COMPONENT_REMOVED_EVENT { 4 }; -constexpr SHEventIdentifier SH_SCENEGRAPH_CHANGE_PARENT_EVENT { 5 }; -constexpr SHEventIdentifier SH_SCENEGRAPH_ADD_CHILD_EVENT { 6 }; -constexpr SHEventIdentifier SH_SCENEGRAPH_REMOVE_CHILD_EVENT { 7 }; -constexpr SHEventIdentifier SH_PHYSICS_COLLIDER_ADDED_EVENT { 8 }; -constexpr SHEventIdentifier SH_PHYSICS_COLLIDER_REMOVED_EVENT { 9 }; -constexpr SHEventIdentifier SH_EDITOR_ON_PLAY_EVENT { 10 }; -constexpr SHEventIdentifier SH_EDITOR_ON_PAUSE_EVENT { 11 }; -constexpr SHEventIdentifier SH_EDITOR_ON_STOP_EVENT { 12 }; -constexpr SHEventIdentifier SH_SCENE_INIT_PRE { 13 }; -constexpr SHEventIdentifier SH_SCENE_INIT_POST { 14 }; -constexpr SHEventIdentifier SH_SCENE_EXIT_PRE { 15 }; -constexpr SHEventIdentifier SH_SCENE_EXIT_POST { 16 }; +constexpr SHEventIdentifier SH_SCENE_INIT_PRE { 5 }; +constexpr SHEventIdentifier SH_SCENE_INIT_POST { 6 }; +constexpr SHEventIdentifier SH_SCENE_EXIT_PRE { 7 }; +constexpr SHEventIdentifier SH_SCENE_EXIT_POST { 8 }; +constexpr SHEventIdentifier SH_SCENEGRAPH_CHANGE_PARENT_EVENT { 9 }; +constexpr SHEventIdentifier SH_SCENEGRAPH_ADD_CHILD_EVENT { 10 }; +constexpr SHEventIdentifier SH_SCENEGRAPH_REMOVE_CHILD_EVENT { 11 }; +constexpr SHEventIdentifier SH_PHYSICS_COLLIDER_ADDED_EVENT { 12 }; +constexpr SHEventIdentifier SH_PHYSICS_COLLIDER_REMOVED_EVENT { 13 }; +constexpr SHEventIdentifier SH_PHYSICS_COLLIDER_DRAW_EVENT { 14 }; +constexpr SHEventIdentifier SH_EDITOR_ON_PLAY_EVENT { 15 }; +constexpr SHEventIdentifier SH_EDITOR_ON_PAUSE_EVENT { 16 }; +constexpr SHEventIdentifier SH_EDITOR_ON_STOP_EVENT { 17 }; + diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp index 1d29a585..b64bab53 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp @@ -14,7 +14,10 @@ #include "SHCollider.h" // Project Headers +#include "Events/SHEvent.h" #include "Math/SHMathHelpers.h" +#include "Physics/SHPhysicsEvents.h" + namespace SHADE { @@ -25,9 +28,7 @@ namespace SHADE SHCollider::SHCollider(EntityID eid, const SHTransform& worldTransform) noexcept : entityID { eid } , shapeIDCounter { 0 } -#ifdef SHEDITOR , debugDraw { false } -#endif , rigidBody { nullptr } , shapeFactory { nullptr } , transform { worldTransform } @@ -38,9 +39,7 @@ namespace SHADE SHCollider::SHCollider(const SHCollider& rhs) noexcept : entityID { rhs.entityID } , shapeIDCounter { rhs.shapeIDCounter } -#ifdef SHEDITOR , debugDraw { rhs.debugDraw } -#endif , rigidBody { rhs.rigidBody } , shapeFactory { rhs.shapeFactory } , transform { rhs.transform } @@ -57,9 +56,7 @@ namespace SHADE SHCollider::SHCollider(SHCollider&& rhs) noexcept : entityID { rhs.entityID } , shapeIDCounter { rhs.shapeIDCounter } -#ifdef SHEDITOR , debugDraw { rhs.debugDraw } -#endif , rigidBody { rhs.rigidBody } , shapeFactory { rhs.shapeFactory } , transform { rhs.transform } @@ -101,14 +98,11 @@ namespace SHADE } entityID = rhs.entityID; + debugDraw = rhs.debugDraw; rigidBody = rhs.rigidBody; shapeFactory = rhs.shapeFactory; transform = rhs.transform; - #ifdef SHEDITOR - debugDraw = rhs.debugDraw; - #endif - copyShapes(rhs); return *this; @@ -123,14 +117,11 @@ namespace SHADE } entityID = rhs.entityID; + debugDraw = rhs.debugDraw; rigidBody = rhs.rigidBody; shapeFactory = rhs.shapeFactory; transform = rhs.transform; - #ifdef SHEDITOR - debugDraw = rhs.debugDraw; - #endif - copyShapes(rhs); return *this; @@ -140,12 +131,10 @@ namespace SHADE /* Getter Function Definitions */ /*-----------------------------------------------------------------------------------*/ -#ifdef SHEDITOR bool SHCollider::GetDebugDrawState() const noexcept { return debugDraw; } -#endif const SHTransform& SHCollider::GetTransform() const noexcept { @@ -186,12 +175,23 @@ namespace SHADE /* Setter Function Definitions */ /*-----------------------------------------------------------------------------------*/ -#ifdef SHEDITOR void SHCollider::SetDebugDrawState(bool state) noexcept { debugDraw = state; + + #ifdef SHEDITOR + + // Broadcast event for the Debug Draw system to catch + const SHColliderOnDebugDrawEvent EVENT_DATA + { + .entityID = entityID + , .debugDrawState = debugDraw + }; + + SHEventManager::BroadcastEvent(EVENT_DATA, SH_PHYSICS_COLLIDER_DRAW_EVENT); + + #endif } -#endif void SHCollider::SetRigidBody(SHRigidBody* rb) noexcept { @@ -236,7 +236,7 @@ namespace SHADE { if (!shapeFactory) { - SHLOGV_ERROR("Shape factory is unlinked with Composite Collider {}. Unable to add new shape!", entityID) + SHLOGV_ERROR("Shape factory is unlinked with Collider {}. Unable to add new shape!", entityID) return -1; } @@ -266,6 +266,17 @@ namespace SHADE sphere->rotationOffset = rotOffset; shapes.emplace_back(sphere); + + // Broadcast Event for adding a shape + const SHPhysicsColliderAddedEvent EVENT_DATA + { + .entityID = entityID + , .colliderType = SHCollisionShape::Type::SPHERE + , .colliderIndex = static_cast(shapes.size()) + }; + + SHEventManager::BroadcastEvent(EVENT_DATA, SH_PHYSICS_COLLIDER_ADDED_EVENT); + return static_cast(shapes.size()); } @@ -273,7 +284,7 @@ namespace SHADE { if (!shapeFactory) { - SHLOGV_ERROR("Shape factory is unlinked with Composite Collider {}. Unable to add remove shape!", entityID) + SHLOGV_ERROR("Shape factory is unlinked with Collider {}. Unable to add remove shape!", entityID) return; } @@ -289,12 +300,22 @@ namespace SHADE break; } + const SHPhysicsColliderRemovedEvent EVENT_DATA + { + .entityID = entityID + , .colliderType = (*shapeIter)->GetType() + , .colliderIndex = index + }; + shapeFactory->DestroyShape(*shapeIter); *shapeIter = nullptr; // Remove the shape from the container to prevent accessing a nullptr shapeIter = shapes.erase(shapeIter); + // Broadcast Event for removing a shape + SHEventManager::BroadcastEvent(EVENT_DATA, SH_PHYSICS_COLLIDER_REMOVED_EVENT); + SHLOG_INFO_D("Removing Collision Shape {} from Entity {}", index, entityID) } @@ -386,6 +407,7 @@ namespace SHADE const SHMatrix TRS = SHMatrix::Rotate(FINAL_ROT) * SHMatrix::Translate(transform.position); const SHVec3 NEW_CENTER = SHVec3::Transform(sphere->positionOffset, TRS); + sphere->SetCenter(NEW_CENTER); } diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.h b/SHADE_Engine/src/Physics/Collision/SHCollider.h index cab433a5..927c6155 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.h +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.h @@ -78,9 +78,7 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - #ifdef SHEDITOR [[nodiscard]] bool GetDebugDrawState () const noexcept; - #endif [[nodiscard]] const SHTransform& GetTransform () const noexcept; [[nodiscard]] const SHVec3& GetPosition () const noexcept; @@ -94,9 +92,7 @@ namespace SHADE /* Setter Functions */ /*---------------------------------------------------------------------------------*/ - #ifdef SHEDITOR void SetDebugDrawState (bool state) noexcept; - #endif void SetRigidBody (SHRigidBody* rb) noexcept; @@ -161,9 +157,7 @@ namespace SHADE EntityID entityID; uint32_t shapeIDCounter; // This increments everytime a shape is added to differentiate shapes. - #ifdef SHEDITOR bool debugDraw; - #endif SHRigidBody* rigidBody; SHCollisionShapeFactory* shapeFactory; diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp index e6c02168..7422d6ff 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp @@ -136,6 +136,8 @@ namespace SHADE { rigidBody.collider->SetPosition(rigidBody.motionState.position); // TODO: Sync orientations + + rigidBody.collider->RecomputeShapes(); } } diff --git a/SHADE_Engine/src/Physics/Interface/SHColliderComponent.cpp b/SHADE_Engine/src/Physics/Interface/SHColliderComponent.cpp index d345290c..7425f418 100644 --- a/SHADE_Engine/src/Physics/Interface/SHColliderComponent.cpp +++ b/SHADE_Engine/src/Physics/Interface/SHColliderComponent.cpp @@ -54,7 +54,14 @@ namespace SHADE return collider->GetCollisionShape(index); } + bool SHColliderComponent::GetDebugDrawState() const noexcept + { + if (!collider) + return false; + return collider->GetDebugDrawState(); + } + /*-----------------------------------------------------------------------------------*/ /* Setter Function Definitions */ /*-----------------------------------------------------------------------------------*/ @@ -64,6 +71,13 @@ namespace SHADE collider = c; } + void SHColliderComponent::SetDebugDrawState(bool state) noexcept + { + if (collider) + collider->SetDebugDrawState(state); + } + + } // namespace SHADE RTTR_REGISTRATION @@ -71,5 +85,6 @@ RTTR_REGISTRATION using namespace rttr; using namespace SHADE; - registration::class_("Collider Component"); -} \ No newline at end of file + registration::class_("Collider Component") + .property("Is Debug Drawing", &SHColliderComponent::GetDebugDrawState, &SHColliderComponent::SetDebugDrawState); +} \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h b/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h index 83fb235c..d62b9b90 100644 --- a/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h +++ b/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h @@ -58,11 +58,19 @@ namespace SHADE [[nodiscard]] const SHCollider::CollisionShapes* const GetCollisionShapes() const noexcept; [[nodiscard]] SHCollisionShape* const GetCollisionShape (int index) const; + // Required for serialisation + + [[nodiscard]] bool GetDebugDrawState () const noexcept; + /*---------------------------------------------------------------------------------*/ /* Setter Functions */ /*---------------------------------------------------------------------------------*/ - void SetCollider(SHCollider* c) noexcept; + void SetCollider (SHCollider* c) noexcept; + + // Required for serialisation + + void SetDebugDrawState (bool state) noexcept; private: /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/SHPhysicsEvents.h b/SHADE_Engine/src/Physics/SHPhysicsEvents.h index 35217347..2f78b6ee 100644 --- a/SHADE_Engine/src/Physics/SHPhysicsEvents.h +++ b/SHADE_Engine/src/Physics/SHPhysicsEvents.h @@ -13,7 +13,6 @@ // Project Headers #include "Collision/CollisionShapes/SHCollisionShape.h" - namespace SHADE { /*-----------------------------------------------------------------------------------*/ @@ -28,9 +27,16 @@ namespace SHADE }; struct SHPhysicsColliderRemovedEvent + { + EntityID entityID; + SHCollisionShape::Type colliderType; + int colliderIndex; + }; + + struct SHColliderOnDebugDrawEvent { EntityID entityID; - int colliderIndex; + bool debugDrawState; }; diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp index c73e8ab7..72b2aad5 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp @@ -1,7 +1,7 @@ /**************************************************************************************** - * \file SHPhysicsDebugDrawRoutine.cpp + * \file SHPhysicsDebugDrawRutine.cpp * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Implementation for the Physics Debug-Draw Routine + * \brief Implementation for the Physics Debut Draw Routine * * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or * disclosure of this file or its contents without the prior written consent @@ -11,14 +11,13 @@ #include // Primary Header -#include "Physics/System/SHPhysicsSystem.h" +#include "Physics/System/SHPhysicsDebugDrawSystem.h" // Project Headers #include "ECS_Base/Managers/SHSystemManager.h" #include "Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h" - -#include "Scripting/SHScriptEngine.h" - +#include "Math/Transform/SHTransformComponent.h" +#include "Physics/System/SHPhysicsSystem.h" namespace SHADE { @@ -26,35 +25,64 @@ namespace SHADE /* Constructors & Destructor Definitions */ /*-----------------------------------------------------------------------------------*/ - SHPhysicsSystem::PhysicsDebugDraw::PhysicsDebugDraw() - : SHSystemRoutine { "Physics Debug Draw", false } + SHPhysicsDebugDrawSystem::PhysicsDebugDraw::PhysicsDebugDraw() + : SHSystemRoutine { "Physics Debug-Draw", true } {} /*-----------------------------------------------------------------------------------*/ /* Public Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ - void SHPhysicsSystem::PhysicsDebugDraw::Execute(double) noexcept + void SHPhysicsDebugDrawSystem::PhysicsDebugDraw::Execute(double) noexcept { - auto* physicsSystem = reinterpret_cast(GetSystem()); - - // Get debug drawing system - auto* debugDrawSystem = SHSystemManager::GetSystem(); - if (!debugDrawSystem) - { - SHLOG_ERROR("Debug draw system unavailable! Colliders cannot be drawn!") + auto* physicsDebugDrawSystem = reinterpret_cast(GetSystem()); + + if (!physicsDebugDrawSystem->GetFlagState(DebugDrawFlags::ACTIVE)) return; + + auto* physicsSystem = SHSystemManager::GetSystem(); + auto* debugDrawSystem = SHSystemManager::GetSystem(); + + const bool DRAW_COLLIDERS = physicsDebugDrawSystem->GetFlagState(DebugDrawFlags::COLLIDERS); + const bool DRAW_CONTACTS = physicsDebugDrawSystem->GetFlagState(DebugDrawFlags::CONTACTS); + const bool DRAW_RAYCASTS = physicsDebugDrawSystem->GetFlagState(DebugDrawFlags::RAYCASTS); + const bool DRAW_BROADPHASE = physicsDebugDrawSystem->GetFlagState(DebugDrawFlags::BROADPHASE); + + // If draw all colliders is active, get all colliders from the dense set and draw. + // Else we check if any colliders have been flagged for drawing. + if (DRAW_COLLIDERS) + { + const auto& COLLIDER_COMPONENT_DENSE = SHComponentManager::GetDense(); + for (const auto& COLLIDER_COMPONENT : COLLIDER_COMPONENT_DENSE) + { + const auto* COLLIDER = COLLIDER_COMPONENT.GetCollider(); + drawCollider(debugDrawSystem, *COLLIDER); + } + } + else if (!physicsDebugDrawSystem->collidersToDraw.empty()) + { + for (const auto EID : physicsDebugDrawSystem->collidersToDraw) + { + const auto* COLLIDER = SHComponentManager::GetComponent(EID)->GetCollider(); + drawCollider(debugDrawSystem, *COLLIDER); + } } - //SHPhysicsDebugDraw& physicsDebugRenderer = physicsSystem->debugRenderer; + if (DRAW_CONTACTS) + { + // TODO + } - //if (!physicsDebugRenderer.GetDebugDrawState()) - // return; + if (DRAW_RAYCASTS) + { + // TODO + physicsDebugDrawSystem->raysToDraw.clear(); + } - //// Draw colliders - //if (physicsDebugRenderer.GetDrawSpecificCollidersState()) - // physicsDebugRenderer.DrawSpecificColliders(debugDrawSystem); - //else if (physicsDebugRenderer.GetDrawAllCollidersState()) - // physicsDebugRenderer.DrawAllColliders(debugDrawSystem); + if (DRAW_BROADPHASE) + { + // TODO + } } -} + +} // namespace SHADE diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp index d5492cdf..8e7450ce 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp @@ -15,7 +15,6 @@ // Project Headers #include "ECS_Base/Managers/SHSystemManager.h" -#include "Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h" #include "Math/Transform/SHTransformComponent.h" #include "Scripting/SHScriptEngine.h" diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp new file mode 100644 index 00000000..77241ae9 --- /dev/null +++ b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp @@ -0,0 +1,158 @@ +/**************************************************************************************** + * \file SHPhysicsDebugDrawSystem.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for the Physics Debug Draw System + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHPhysicsDebugDrawSystem.h" + +// Project Header +#include "ECS_Base/Managers/SHSystemManager.h" +#include "Math/Transform/SHTransformComponent.h" +#include "Physics/SHPhysicsEvents.h" +#include "Tools/Utilities/SHUtilities.h" + +namespace SHADE +{ + const SHColour SHPhysicsDebugDrawSystem::DEBUG_DRAW_COLOURS[SHUtilities::ConvertEnum(Colours::COUNT)] + { + SHColour::GREEN // Colliders + , SHColour::PURPLE // Triggers + , SHColour::RED // Contacts + , SHColour::ORANGE // Raycasts + , SHColour::CYAN // Broadphase + }; + + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHPhysicsDebugDrawSystem::SHPhysicsDebugDrawSystem() noexcept + : flags { 0 } + { + collidersToDraw.clear(); + } + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHPhysicsDebugDrawSystem::GetFlagState(DebugDrawFlags flag) const noexcept + { + const uint8_t ENUM_VALUE = SHUtilities::ConvertEnum(flag); + return flags & ENUM_VALUE; + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsDebugDrawSystem::SetFlagState(DebugDrawFlags flag, bool state) noexcept + { + const uint8_t ENUM_VALUE = SHUtilities::ConvertEnum(flag); + state ? flags |= ENUM_VALUE : flags &= ~ENUM_VALUE; + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsDebugDrawSystem::Init() + { + SystemFamily::GetID(); + + // Register collider draw event + const std::shared_ptr EVENT_RECEIVER = std::make_shared>(this, &SHPhysicsDebugDrawSystem::onColliderDraw); + const ReceiverPtr EVENT_RECEIVER_PTR = std::dynamic_pointer_cast(EVENT_RECEIVER); + + SHEventManager::SubscribeTo(SH_PHYSICS_COLLIDER_DRAW_EVENT, EVENT_RECEIVER_PTR); + } + + void SHPhysicsDebugDrawSystem::Exit() + { + + } + + void SHPhysicsDebugDrawSystem::AddRaycast(const SHRay& ray, const SHPhysicsRaycastResult& result) noexcept + { + + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHEventHandle SHPhysicsDebugDrawSystem::onColliderDraw(SHEventPtr onColliderDrawEvent) + { + const auto& EVENT_DATA = reinterpret_cast*>(onColliderDrawEvent.get())->data; + + if (EVENT_DATA->debugDrawState) + { + if (collidersToDraw.empty()) + SetFlagState(DebugDrawFlags::ACTIVE, true); + + collidersToDraw.emplace(EVENT_DATA->entityID); + } + else + { + collidersToDraw.erase(EVENT_DATA->entityID); + + if (collidersToDraw.empty()) + SetFlagState(DebugDrawFlags::ACTIVE, false); + } + + return onColliderDrawEvent.get()->handle; + } + + void SHPhysicsDebugDrawSystem::drawCollider(SHDebugDrawSystem* debugDrawSystem, const SHCollider& collider) noexcept + { + for (const auto* SHAPE : collider.GetCollisionShapes()) + { + switch (SHAPE->GetType()) + { + case SHCollisionShape::Type::SPHERE: + { + const SHSphereCollisionShape* SPHERE = dynamic_cast(SHAPE); + drawSphere(debugDrawSystem, *SPHERE); + + break; + } + case SHCollisionShape::Type::BOX: + { + break; + } + case SHCollisionShape::Type::CAPSULE: + { + break; + } + default: break; + } + } + } + + void SHPhysicsDebugDrawSystem::drawSphere(SHDebugDrawSystem* debugDrawSystem, const SHSphereCollisionShape& sphere) noexcept + { + const SHColour& DRAW_COLOUR = DEBUG_DRAW_COLOURS[SHUtilities::ConvertEnum(sphere.IsTrigger() ? Colours::TRIGGER : Colours::COLLIDER)]; + debugDrawSystem->DrawSphere(DRAW_COLOUR, sphere.GetCenter(), sphere.GetWorldRadius()); + } + + void SHPhysicsDebugDrawSystem::drawContact(SHDebugDrawSystem* debugDrawSystem, const DebugDrawInfo::Contact& contactInfo) noexcept + { + static const SHColour& DRAW_COLOUR = DEBUG_DRAW_COLOURS[SHUtilities::ConvertEnum(Colours::CONTACT)]; + } + + void SHPhysicsDebugDrawSystem::drawRaycast(SHDebugDrawSystem* debugDrawSystem, const DebugDrawInfo::Raycast& raycastInfo) noexcept + { + static const SHColour& DRAW_COLOUR = DEBUG_DRAW_COLOURS[SHUtilities::ConvertEnum(Colours::RAYCAST)]; + } + + + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h new file mode 100644 index 00000000..423ab62a --- /dev/null +++ b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h @@ -0,0 +1,150 @@ +/**************************************************************************************** + * \file SHPhysicsDebugDrawSystem.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for the Physics Debug Draw System + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include + +// Project Headers +#include "ECS_Base/Entity/SHEntity.h" +#include "ECS_Base/System/SHSystem.h" +#include "ECS_Base/System/SHSystemRoutine.h" +#include "Events/SHEvent.h" +#include "Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h" +#include "Physics/Collision/SHCollider.h" +#include "Physics/Collision/SHPhysicsRaycastResult.h" +#include "Physics/Collision/CollisionShapes/SHSphereCollisionShape.h" + + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHPhysicsDebugDrawSystem final : public SHSystem + { + public: + + enum class DebugDrawFlags : uint8_t + { + ACTIVE = 0x0001 + , COLLIDERS = 0x0002 + , CONTACTS = 0x0004 + , RAYCASTS = 0x0008 + , BROADPHASE = 0x0010 + }; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHPhysicsDebugDrawSystem () noexcept; + ~SHPhysicsDebugDrawSystem() noexcept = default; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] bool GetFlagState (DebugDrawFlags flag) const noexcept; + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + + void SetFlagState (DebugDrawFlags flag, bool state) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + void Init () override; + void Exit () override; + + void AddRaycast (const SHRay& ray, const SHPhysicsRaycastResult& result) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* System Routines */ + /*---------------------------------------------------------------------------------*/ + + /** + * @brief + * If the editor is enabled, this routine invokes debug drawing for colliders and collision information. + */ + class SH_API PhysicsDebugDraw final : public SHSystemRoutine + { + public: + PhysicsDebugDraw(); + void Execute(double dt) noexcept override; + }; + + private: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + union DebugDrawInfo + { + struct Contact + { + SHVec3 worldPos; + SHVec3 normal; + } contact; + + struct Raycast + { + SHVec3 start; + SHVec3 end; + } raycast; + }; + + using Colliders = std::unordered_set; + using Raycasts = std::vector; + using Contacts = std::vector; + + enum class Colours + { + COLLIDER + , TRIGGER + , CONTACT + , RAYCAST + , BROADPHASE + + , COUNT + }; + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + static const SHColour DEBUG_DRAW_COLOURS[static_cast(Colours::COUNT)]; + + // 0 0 0 drawBroadphase drawRaycasts drawContacts drawAllColliders debugDrawActive + uint8_t flags; + + Colliders collidersToDraw; + Raycasts raysToDraw; + Contacts contactToDraw; + + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + SHEventHandle onColliderDraw(SHEventPtr onColliderDrawEvent); + + static void drawCollider (SHDebugDrawSystem* debugDrawSystem, const SHCollider& collider) noexcept; + static void drawSphere (SHDebugDrawSystem* debugDrawSystem, const SHSphereCollisionShape& sphere) noexcept; + + static void drawContact (SHDebugDrawSystem* debugDrawSystem, const DebugDrawInfo::Contact& contactInfo) noexcept; + static void drawRaycast (SHDebugDrawSystem* debugDrawSystem, const DebugDrawInfo::Raycast& raycastInfo) noexcept; + + }; + + +} // namespace SHADE diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp index 388adadc..20084ebe 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp @@ -110,13 +110,12 @@ namespace SHADE void SHPhysicsSystem::Init() { + // TODO(Diren): Consider using a non-static collision tag matrix. // Initialise collision tags std::filesystem::path defaultCollisionTagNameFilePath { ASSET_ROOT }; defaultCollisionTagNameFilePath.append("CollisionTags.SHConfig"); SHCollisionTagMatrix::Init(defaultCollisionTagNameFilePath); - // Link Managers to system - // Register Events for (int i = 0; i < NUM_EVENT_FUNCTIONS; ++i) { diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h index ca73df81..ce1f1396 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h @@ -27,6 +27,10 @@ namespace SHADE /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ + /** + * @brief + * Encapsulates a system for running and managing the physics simulation of the engine. + */ class SH_API SHPhysicsSystem final : public SHSystem { public: @@ -41,23 +45,27 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] double GetFixedUpdateRate() const noexcept; - [[nodiscard]] double GetFixedDT() const noexcept; + [[nodiscard]] double GetFixedUpdateRate() const noexcept; + [[nodiscard]] double GetFixedDT() const noexcept; /*---------------------------------------------------------------------------------*/ /* Setter Functions */ /*---------------------------------------------------------------------------------*/ void SetFixedUpdateRate(double fixedUpdateRate) noexcept; - void SetFixedDT(double fixedDt) noexcept; + void SetFixedDT(double fixedDt) noexcept; /*---------------------------------------------------------------------------------*/ - /* Function Members */ + /* Member Functions */ /*---------------------------------------------------------------------------------*/ + /** + * @brief + * - Initialises the static collision tag matrix. + * - Registers the system to catch specific events. + */ void Init() override; void Exit() override; - void ForceUpdate(); /*---------------------------------------------------------------------------------*/ @@ -104,21 +112,6 @@ namespace SHADE void Execute(double dt) noexcept override; }; - #ifdef SHEDITOR - /** - * @brief - * If the editor is enabled, this routine invokes debug drawing for colliders. - * and collision information. - */ - class SH_API PhysicsDebugDraw final : public SHSystemRoutine - { - public: - PhysicsDebugDraw(); - void Execute(double dt) noexcept override; - }; - - #endif - private: /*---------------------------------------------------------------------------------*/ /* Type Definitions */ diff --git a/SHADE_Engine/src/Serialization/SHYAMLConverters.h b/SHADE_Engine/src/Serialization/SHYAMLConverters.h index 73b22fd6..b225a9a4 100644 --- a/SHADE_Engine/src/Serialization/SHYAMLConverters.h +++ b/SHADE_Engine/src/Serialization/SHYAMLConverters.h @@ -204,10 +204,15 @@ namespace YAML template<> struct convert { + static constexpr const char* DrawColliders = "DrawColliders"; static constexpr const char* Colliders = "Colliders"; + static Node encode(SHColliderComponent& rhs) { Node node, collidersNode; + + node[DrawColliders] = rhs.GetDebugDrawState(); + int const numColliders = static_cast(rhs.GetCollisionShapes()->size()); for (int i = 0; i < numColliders; ++i) { @@ -221,6 +226,9 @@ namespace YAML } static bool decode(Node const& node, SHColliderComponent& rhs) { + if (node[DrawColliders].IsDefined()) + rhs.SetDebugDrawState(node[DrawColliders].as()); + if (node[Colliders].IsDefined()) { int numColliders{}; From 06cc969658a406a926fe8a4cde02ee4ad65a9f2b Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Wed, 14 Dec 2022 01:20:12 +0800 Subject: [PATCH 040/275] Reworked DebugDraw system (only lines now) --- Assets/Shaders/DebugDrawMesh_VS.glsl | 27 + Assets/Shaders/DebugDrawMesh_VS.shshaderb | Bin 0 -> 1869 bytes .../Shaders/DebugDrawMesh_VS.shshaderb.shmeta | 3 + Assets/Shaders/DebugDraw_VS.glsl | 3 +- Assets/Shaders/DebugDraw_VS.shshaderb | Bin 1513 -> 1765 bytes .../MiddleEnd/Interface/SHDebugDrawSystem.cpp | 568 +++++++++--------- .../MiddleEnd/Interface/SHDebugDrawSystem.h | 269 ++++----- .../MiddleEnd/Interface/SHDebugDrawSystem.hpp | 65 +- .../MiddleEnd/Interface/SHGraphicsSystem.cpp | 85 ++- .../MiddleEnd/Interface/SHGraphicsSystem.h | 24 +- .../System/SHPhysicsDebugDrawSystem.cpp | 21 +- SHADE_Engine/src/Tools/SHDebugDraw.cpp | 26 +- 12 files changed, 571 insertions(+), 520 deletions(-) create mode 100644 Assets/Shaders/DebugDrawMesh_VS.glsl create mode 100644 Assets/Shaders/DebugDrawMesh_VS.shshaderb create mode 100644 Assets/Shaders/DebugDrawMesh_VS.shshaderb.shmeta diff --git a/Assets/Shaders/DebugDrawMesh_VS.glsl b/Assets/Shaders/DebugDrawMesh_VS.glsl new file mode 100644 index 00000000..f84dd35a --- /dev/null +++ b/Assets/Shaders/DebugDrawMesh_VS.glsl @@ -0,0 +1,27 @@ +#version 450 +#extension GL_KHR_vulkan_glsl : enable + +layout(location = 0) in vec4 aVertexPos; +layout(location = 1) in mat4 worldTransform; +layout(location = 5) in vec4 color; + +// Output +layout(location = 0) out struct +{ + vec4 Color; +} Out; + +layout(set = 2, binding = 0) uniform CameraData +{ + vec4 position; + mat4 vpMat; + mat4 viewMat; + mat4 perspectiveMat; + mat4 orthoMat; +} cameraData; + +void main() +{ + gl_Position = cameraData.vpMat * worldTransform * vec4 (aVertexPos.xyz, 1.0f); + Out.Color = color; +} \ No newline at end of file diff --git a/Assets/Shaders/DebugDrawMesh_VS.shshaderb b/Assets/Shaders/DebugDrawMesh_VS.shshaderb new file mode 100644 index 0000000000000000000000000000000000000000..55d82090c9be148c039e83910d671b2e8f7d6442 GIT binary patch literal 1869 zcmZ9M-EPxB5QVo*(iTcvO6dH$KeKv^V#0Y%2p>kb7p4eFX`OTqRCEWzA0(WoHr}xVNRKKGcU%NWwVgo?aoQ( zV4MyQ9zS`6%c`klp7^XOl2a8%t{)<=2v>ybLQA+WRP;Bm`?rFsifMQH?QXZ-dD9=n zaethC4ZMEfhhBf^C*C0SryhPwB|kqL1e0Oep(c-V-kR!NR*p|Wzvm?%yd?F$NEMs& z$k?K>pZZZKb}{p(kBs-j^u+(B%*5n2YL@LV@Z%$YoVww_JF_tbY?B~3v(?lm{c~#i zTi1Q)j=aP@a#J_21&$NjLL@H~zSJ^}-@0juqlT&Pox)NV@#K!ZWE|(i$^z&U8#9TL z^mCLylhv^yZcwU&S+S$jC<%t|6E_@xjFM4aGkbIM@o83Y6?0b{hdQl%7zI%x4m*49 zPEyI$Y~O>zb}WXSaO`b4dWGY*9KFKPpXHF@=&LOYGJe?YESu6f$MS;X&041CKIr&d zoZjpaCW;(iD=4sJG%p2~6P#QhO$9Gs1#fBK__wt8i+ zdoy}NItM#Dt_$>!&Ti2;=q=r4o!=@tHEk#hc^b+|?Z{h_7IZtpw&JM^of zIITiICz<@a()n+JKU`fByR>g$1CIPI=q4xj=Y`Ji3XVLhx*dUc$%DQoaCW4#Q{JVn zx+G?1XNb*$T$iNN8$RShXRdJe?aPwMh5u$@;|=tk!iG*fI&-)FyM;`C?C9(PJ2Pb7 z%!J;^hujd1`TmOc_a$K?zAlO1A2POky0O9WHaa_nv$@zQxt?mP^u$a!`1}>au3zhu zzP1Ef% R|3ES`wf^Y%{8MGmg#Rr}iaY=S literal 0 HcmV?d00001 diff --git a/Assets/Shaders/DebugDrawMesh_VS.shshaderb.shmeta b/Assets/Shaders/DebugDrawMesh_VS.shshaderb.shmeta new file mode 100644 index 00000000..043dd287 --- /dev/null +++ b/Assets/Shaders/DebugDrawMesh_VS.shshaderb.shmeta @@ -0,0 +1,3 @@ +Name: DebugDrawMesh_VS +ID: 42127043 +Type: 2 diff --git a/Assets/Shaders/DebugDraw_VS.glsl b/Assets/Shaders/DebugDraw_VS.glsl index 7b370730..a027d596 100644 --- a/Assets/Shaders/DebugDraw_VS.glsl +++ b/Assets/Shaders/DebugDraw_VS.glsl @@ -4,11 +4,10 @@ layout(location = 0) in vec4 aVertexPos; layout(location = 1) in vec4 aVertColor; - +// Output layout(location = 0) out struct { vec4 vertColor; // location 0 - } Out; layout(set = 2, binding = 0) uniform CameraData diff --git a/Assets/Shaders/DebugDraw_VS.shshaderb b/Assets/Shaders/DebugDraw_VS.shshaderb index 2d8cd0edd9ff60919d7c1931f06ee0ea6390bff0..30c8d6db49fe8eddd97693f148de32007989390b 100644 GIT binary patch delta 276 zcmaFK{gjuP@ebQY=6Q^+Yz(Xnf(#4{Oh8vo8ZQ7b%n>Y`S1}nf3PU^wGXZ3~z~)EH>`VZ1 Cq#P3f delta 35 rcmaFL`;wcP@d4{b=6Q^pUowg?ZeGf)#>mdXAjrVLz`pq&3p*14#ls00 diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp index 0bfa89a2..f090af0d 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp @@ -25,308 +25,326 @@ of DigiPen Institute of Technology is prohibited. namespace SHADE { - /*---------------------------------------------------------------------------------*/ - /* DrawRoutine */ - /*---------------------------------------------------------------------------------*/ - SHDebugDrawSystem::ProcessPointsRoutine::ProcessPointsRoutine() + /*-----------------------------------------------------------------------------------*/ + /* DrawRoutine */ + /*-----------------------------------------------------------------------------------*/ + SHDebugDrawSystem::ProcessPointsRoutine::ProcessPointsRoutine() : SHSystemRoutine("Debug Draw", true) + { + SystemFamily::GetID(); + } + + void SHDebugDrawSystem::ProcessPointsRoutine::Execute(double dt) noexcept + { + auto gfxSys = SHSystemManager::GetSystem(); + if (!gfxSys) { - SystemFamily::GetID(); + SHLOG_ERROR("[DebugDraw] Attempted to do debug draw without a graphics system."); + return; } - void SHDebugDrawSystem::ProcessPointsRoutine::Execute(double dt) noexcept + // Get the system + SHDebugDrawSystem* system = static_cast(GetSystem()); + + // Get current frame index + const uint32_t FRAME_IDX = gfxSys->GetCurrentFrameIndex(); + for (auto& batch : system->lineBatches) { - auto gfxSys = SHSystemManager::GetSystem(); - if (!gfxSys) - { - SHLOG_WARNING("[DebugDraw] Attempted to do debug draw without a graphics system."); - return; - } - - // Get the system - SHDebugDrawSystem* system = static_cast(GetSystem()); - - // Get current frame index - const uint32_t FRAME_IDX = gfxSys->GetCurrentFrameIndex(); - - /* Non-Persistent Buffer */ - // Update the buffer - system->numPoints[FRAME_IDX] = system->points.size(); - const uint32_t DATA_SIZE = sizeof(PointVertex) * system->points.size(); - if (DATA_SIZE > 0) - { - system->vertexBuffers[FRAME_IDX]->WriteToMemory(system->points.data(), DATA_SIZE, 0, 0); - } - - // Reset for next frame - system->points.clear(); - - /* Persistent Buffer */ - // Check if there are changes - if (system->persistentBuffersCleared[FRAME_IDX] - || - system->numPersistentPoints[FRAME_IDX] != system->persistentPoints.size()) - { - // Update Buffer - system->numPersistentPoints[FRAME_IDX] = system->persistentPoints.size(); - const uint32_t DATA_SIZE = sizeof(PointVertex) * system->persistentPoints.size(); - if (DATA_SIZE > 0) - { - system->persistentVertexBuffers[FRAME_IDX]->WriteToMemory(system->persistentPoints.data(), DATA_SIZE, 0, 0); - } - - // Reset Flag - system->persistentBuffersCleared[FRAME_IDX] = false; - } + system->prepareBatch(batch, FRAME_IDX); + batch.Points.clear(); } - - /*---------------------------------------------------------------------------------*/ - /* SHSystem overrides */ - /*---------------------------------------------------------------------------------*/ - void SHDebugDrawSystem::Init() + for (auto& batch : system->persistentLineBatches) { - // Register function for subpass - const auto* GFX_SYSTEM = SHSystemManager::GetSystem(); - auto const& RENDERERS = GFX_SYSTEM->GetDefaultViewport()->GetRenderers(); - auto renderGraph = RENDERERS[SHGraphicsConstants::RenderGraphIndices::WORLD]->GetRenderGraph(); - auto subPass = renderGraph->GetNode("Debug Draw")->GetSubpass("Debug Draw"); - subPass->AddExteriorDrawCalls([this, GFX_SYSTEM](Handle& cmdBuffer, uint32_t frameIndex) - { - // Get Current frame index - const uint32_t FRAME_IDX = GFX_SYSTEM->GetCurrentFrameIndex(); + system->prepareBatch(batch, FRAME_IDX); + } + } - // Don't draw if no points - if (numPoints[FRAME_IDX] > 0) - { - cmdBuffer->BeginLabeledSegment("SHDebugDraw"); - cmdBuffer->BindPipeline(GFX_SYSTEM->GetDebugDrawPipeline()); - cmdBuffer->SetLineWidth(LineWidth); - cmdBuffer->BindVertexBuffer(0, vertexBuffers[FRAME_IDX], 0); - cmdBuffer->DrawArrays(numPoints[FRAME_IDX], 1, 0, 0); - } - }); - auto subPassWithDepth = renderGraph->GetNode("Debug Draw with Depth")->GetSubpass("Debug Draw with Depth"); - subPassWithDepth->AddExteriorDrawCalls([this, GFX_SYSTEM](Handle& cmdBuffer, uint32_t frameIndex) - { - // Get Current frame index - const uint32_t FRAME_IDX = GFX_SYSTEM->GetCurrentFrameIndex(); - - // Don't draw if no points - if (numPersistentPoints[FRAME_IDX] > 0) - { - cmdBuffer->BeginLabeledSegment("SHDebugDraw (Persistent)"); - cmdBuffer->BindPipeline(GFX_SYSTEM->GetDebugDrawDepthPipeline()); - cmdBuffer->SetLineWidth(LineWidth); - cmdBuffer->BindVertexBuffer(0, persistentVertexBuffers[FRAME_IDX], 0); - cmdBuffer->DrawArrays(numPersistentPoints[FRAME_IDX], 1, 0, 0); - cmdBuffer->EndLabeledSegment(); - } - }); - - // Reset trackers - std::fill_n(numPoints.begin(), numPoints.size(), 0); - std::fill_n(numPersistentPoints.begin(), numPersistentPoints.size(), 0); - for (bool& cleared : persistentBuffersCleared) - cleared = true; - - // Allocate buffers - // - Non-Persistent Draws - static constexpr uint32_t BUFFER_SIZE = MAX_POINTS * sizeof(PointVertex); - for (Handle& bufHandle : vertexBuffers) - { - bufHandle = GFX_SYSTEM->GetDevice()->CreateBuffer - ( - BUFFER_SIZE, - nullptr, - 0, - vk::BufferUsageFlagBits::eVertexBuffer, - VmaMemoryUsage::VMA_MEMORY_USAGE_AUTO, - VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_MAPPED_BIT, - "Debug Draw Non-Persistent Vertex Buffer" - ); - } - // - Persistent Draws - for (Handle& bufHandle : persistentVertexBuffers) - { - bufHandle = GFX_SYSTEM->GetDevice()->CreateBuffer - ( - BUFFER_SIZE, - nullptr, - 0, - vk::BufferUsageFlagBits::eVertexBuffer, - VmaMemoryUsage::VMA_MEMORY_USAGE_AUTO, - VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_MAPPED_BIT, - "Debug Draw Persistent Vertex Buffer" - ); - } + /*-----------------------------------------------------------------------------------*/ + /* SHSystem overrides */ + /*-----------------------------------------------------------------------------------*/ + void SHDebugDrawSystem::Init() + { + const auto* GFX_SYSTEM = SHSystemManager::GetSystem(); + if (!GFX_SYSTEM) + { + SHLOG_ERROR("[DebugDraw] Attempted to do debug draw without a graphics system."); + return; } - void SHDebugDrawSystem::Exit() + // Create all batches + createLineBatches(); + createMeshBatches(); + + // Register function for subpass + auto const& RENDERERS = GFX_SYSTEM->GetDefaultViewport()->GetRenderers(); + auto renderGraph = RENDERERS[SHGraphicsConstants::RenderGraphIndices::WORLD]->GetRenderGraph(); + auto subPass = renderGraph->GetNode("Debug Draw")->GetSubpass("Debug Draw"); + subPass->AddExteriorDrawCalls([this, GFX_SYSTEM](Handle& cmdBuffer, uint32_t frameIndex) { - for (auto vertexBuffer : vertexBuffers) - { - if (vertexBuffer) - vertexBuffer.Free(); - } - for (auto vertexBuffer : persistentVertexBuffers) - { - if (vertexBuffer) - vertexBuffer.Free(); - } + const uint32_t FRAME_IDX = GFX_SYSTEM->GetCurrentFrameIndex(); + renderBatch(lineBatches[static_cast(LineRenderMode::NoDepthTest)], cmdBuffer, FRAME_IDX); + renderBatch(persistentLineBatches[static_cast(LineRenderMode::NoDepthTest)], cmdBuffer, FRAME_IDX); + }); + auto subPassWithDepth = renderGraph->GetNode("Debug Draw with Depth")->GetSubpass("Debug Draw with Depth"); + subPassWithDepth->AddExteriorDrawCalls([this, GFX_SYSTEM](Handle& cmdBuffer, uint32_t frameIndex) + { + const uint32_t FRAME_IDX = GFX_SYSTEM->GetCurrentFrameIndex(); + renderBatch(lineBatches[static_cast(LineRenderMode::DepthTested)], cmdBuffer, FRAME_IDX); + renderBatch(persistentLineBatches[static_cast(LineRenderMode::DepthTested)], cmdBuffer, FRAME_IDX); + }); + } + + void SHDebugDrawSystem::Exit() + { + // Destroy buffers in the batches + destroyLineBatches(); + destroyMeshBatches(); + } + + /*-----------------------------------------------------------------------------------*/ + /* Draw Functions */ + /*-----------------------------------------------------------------------------------*/ + void SHDebugDrawSystem::DrawLine(const SHVec3& start, const SHVec3& end, const SHColour& color, bool depthTested) + { + // Insert into the batch + drawLine(getLineBatch(depthTested), start, end, color); + } + + void SHDebugDrawSystem::DrawLineLoop(std::initializer_list points, const SHColour& color, bool depthTested) + { + DrawLineLoop(points.begin(), points.end(), color, depthTested); + } + + void SHDebugDrawSystem::DrawTri(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHColour& color, bool depthTested) + { + DrawLineLoop({ p1, p2, p3 }, color, depthTested); + } + + /*-----------------------------------------------------------------------------------*/ + /* Persistent Draw Functions */ + /*-----------------------------------------------------------------------------------*/ + void SHDebugDrawSystem::DrawPersistentLine(const SHVec3& start, const SHVec3& end, const SHColour& color, bool depthTested) + { + // Insert into the batch + drawLine(getPersistentLineBatch(depthTested), start, end, color); + } + void SHDebugDrawSystem::DrawPersistentLineLoop(std::initializer_list points, const SHColour& color, bool depthTested) + { + DrawPersistentLineLoop(points.begin(), points.end(), color, depthTested); + } + + void SHDebugDrawSystem::DrawPersistentTri(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHColour& color, bool depthTested) + { + DrawPersistentLineLoop({ p1, p2, p3 }, color, depthTested); + } + + /*-----------------------------------------------------------------------------------*/ + /* Helper Draw Functions */ + /*-----------------------------------------------------------------------------------*/ + void SHDebugDrawSystem::drawLine(LinesBatch& batch, const SHVec3& start, const SHVec3& end, const SHColour& color) + { + // Check if points exceeded max + if (batch.Points.size() >= MAX_POINTS) + { + SHLOG_WARNING("[SHDebugDrawSystem] Exceeded maximum size of drawable debug lines. Ignoring."); + return; } - /*---------------------------------------------------------------------------------*/ - /* Draw Functions */ - /*---------------------------------------------------------------------------------*/ - void SHDebugDrawSystem::DrawLine(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt) + batch.Points.emplace_back(start, color); + batch.Points.emplace_back(end, color); + } + + /*-----------------------------------------------------------------------------------*/ + /* Helper Batch Functions - Lines */ + /*-----------------------------------------------------------------------------------*/ + SHDebugDrawSystem::LinesBatch& SHDebugDrawSystem::getLineBatch(bool depthTested) + { + return depthTested ? lineBatches[static_cast(LineRenderMode::DepthTested)] + : lineBatches[static_cast(LineRenderMode::NoDepthTest)]; + } + SHDebugDrawSystem::LinesBatch& SHDebugDrawSystem::getPersistentLineBatch(bool depthTested) + { + return depthTested ? persistentLineBatches[static_cast(LineRenderMode::DepthTested)] + : persistentLineBatches[static_cast(LineRenderMode::NoDepthTest)]; + } + + void SHDebugDrawSystem::createLineBatches() + { + auto gfxSys = SHSystemManager::GetSystem(); + if (!gfxSys) { - drawLine(points, color, startPt, endPt); + SHLOG_ERROR("[DebugDraw] Attempted to do debug draw without a graphics system."); + return; } - void SHDebugDrawSystem::DrawTri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3) + // Line Batches + initBatch + ( + getLineBatch(false), + gfxSys->GetDebugDrawPipeline(DebugDrawPipelineType::LineNoDepthTest), + "Debug Draw Non-Persistent Vertex Buffer" + ); + initBatch + ( + getLineBatch(true), + gfxSys->GetDebugDrawPipeline(DebugDrawPipelineType::LineDepthTested), + "Debug Draw Depth Tested Non-Persistent Vertex Buffer" + ); + + // Persistent Line Batches + initBatch + ( + getPersistentLineBatch(false), + gfxSys->GetDebugDrawPipeline(DebugDrawPipelineType::LineNoDepthTest), + "Debug Draw Persistent Vertex Buffer" + ); + initBatch + ( + getPersistentLineBatch(true), + gfxSys->GetDebugDrawPipeline(DebugDrawPipelineType::LineDepthTested), + "Debug Draw Depth Tested Persistent Vertex Buffer" + ); + } + + void SHDebugDrawSystem::initBatch(LinesBatch& batch, Handle pipeline, const std::string& vertexBufferName) + { + auto gfxSys = SHSystemManager::GetSystem(); + if (!gfxSys) { - drawPoly(points, color, { pt1, pt2, pt3 }); + SHLOG_WARNING("[DebugDraw] Attempted to do debug draw without a graphics system."); + return; } - void SHDebugDrawSystem::DrawQuad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4) + batch.Pipeline = pipeline; + for (auto& vBuffer : batch.VertexBuffers) { - drawPoly(points, color, { pt1, pt2, pt3, pt4 }); + vBuffer = gfxSys->GetDevice()->CreateBuffer + ( + sizeof(PointVertex) * MAX_POINTS, + nullptr, + 0, + vk::BufferUsageFlagBits::eVertexBuffer, + VmaMemoryUsage::VMA_MEMORY_USAGE_AUTO, + VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | + VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_MAPPED_BIT, + vertexBufferName + ); + } + } + + + void SHDebugDrawSystem::prepareBatch(LinesBatch& batch, uint32_t frameIndex) + { + // Parameter checks + if (frameIndex > batch.VertexBuffers.size()) + { + SHLOG_ERROR("[SHDebugDrawSystem] An invalid frame index was specified for debug drawing. Skipping."); + return; } - void SHDebugDrawSystem::DrawPoly(const SHVec4& color, std::initializer_list pointList) + // Fill data into the buffers + batch.NumPoints[frameIndex] = batch.Points.size(); + const uint32_t DATA_SIZE = sizeof(PointVertex) * batch.Points.size(); + if (DATA_SIZE > 0) { - drawPoly(points, color, pointList.begin(), pointList.end()); + batch.VertexBuffers[frameIndex]->WriteToMemory(batch.Points.data(), DATA_SIZE, 0, 0); + } + } + + void SHDebugDrawSystem::renderBatch(LinesBatch& batch, Handle cmdBuffer, uint32_t frameIndex) + { + if (batch.NumPoints[frameIndex] > 0) + { + cmdBuffer->BeginLabeledSegment("SHDebugDraw"); + cmdBuffer->BindPipeline(batch.Pipeline); + cmdBuffer->SetLineWidth(LineWidth); + cmdBuffer->BindVertexBuffer(0, batch.VertexBuffers[frameIndex], 0); + cmdBuffer->DrawArrays(batch.NumPoints[frameIndex], 1, 0, 0); + } + } + + void SHDebugDrawSystem::destroyBatch(LinesBatch& batch) + { + for (auto& vBuffer : batch.VertexBuffers) + { + if (vBuffer) + { + vBuffer.Free(); + vBuffer = {}; + } + } + } + + void SHDebugDrawSystem::destroyLineBatches() + { + destroyBatch(getLineBatch(true)); + destroyBatch(getLineBatch(false)); + destroyBatch(getPersistentLineBatch(true)); + destroyBatch(getPersistentLineBatch(false)); + } + + /*-----------------------------------------------------------------------------------*/ + /* Helper Batch Functions - Meshes */ + /*-----------------------------------------------------------------------------------*/ + void SHDebugDrawSystem::createMeshBatches() + { + auto gfxSys = SHSystemManager::GetSystem(); + if (!gfxSys) + { + SHLOG_ERROR("[DebugDraw] Attempted to do debug draw without a graphics system."); + return; } - void SHDebugDrawSystem::DrawCube(const SHVec4& color, const SHVec3& pos, const SHVec3& size) + // Set up batches + initBatch + ( + meshBatches[static_cast(MeshRenderMode::FilledNoDepthTest)], + gfxSys->GetDebugDrawPipeline(DebugDrawPipelineType::FilledMeshNoDepthTest) + ); + initBatch + ( + meshBatches[static_cast(MeshRenderMode::FilledDepthTested)], + gfxSys->GetDebugDrawPipeline(DebugDrawPipelineType::FilledMeshDepthTested) + ); + initBatch + ( + meshBatches[static_cast(MeshRenderMode::WireNoDepthTest)], + gfxSys->GetDebugDrawPipeline(DebugDrawPipelineType::LineMeshNoDepthTest) + ); + initBatch + ( + meshBatches[static_cast(MeshRenderMode::WireDepthTested)], + gfxSys->GetDebugDrawPipeline(DebugDrawPipelineType::LineMeshDepthTested) + ); + } + void SHDebugDrawSystem::initBatch(MeshBatch& batch, Handle pipeline) + { + batch.Pipeline = pipeline; + } + void SHDebugDrawSystem::destroyBatch(MeshBatch& batch) + { + for (auto& buffer : batch.InstanceDataBuffer) { - drawCube(points, color, pos, size); + if (buffer) + { + buffer.Free(); + buffer = {}; + } } - - void SHDebugDrawSystem::DrawSphere(const SHVec4& color, const SHVec3& pos, double radius) + for (auto& buffer : batch.MDIBuffer) { - drawSphere(points, color, pos, radius); - } - - /*---------------------------------------------------------------------------------*/ - /* Persistent Draw Functions */ - /*---------------------------------------------------------------------------------*/ - void SHDebugDrawSystem::DrawPersistentLine(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt) - { - drawLine(persistentPoints, color, startPt, endPt); - } - - void SHDebugDrawSystem::DrawPersistentTri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3) - { - drawPoly(persistentPoints, color, { pt1, pt2, pt3 }); - } - - void SHDebugDrawSystem::DrawPersistentQuad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4) - { - drawPoly(persistentPoints, color, { pt1, pt2, pt3, pt4 }); - } - - void SHDebugDrawSystem::DrawPersistentPoly(const SHVec4& color, std::initializer_list pointList) - { - drawPoly(persistentPoints, color, pointList.begin(), pointList.end()); - } - - void SHDebugDrawSystem::DrawPersistentCube(const SHVec4& color, const SHVec3& pos, const SHVec3& size) - { - drawCube(persistentPoints, color, pos, size); - } - - void SHDebugDrawSystem::DrawPersistentSphere(const SHVec4& color, const SHVec3& pos, double radius) - { - drawSphere(persistentPoints, color, pos, radius); - } - - void SHDebugDrawSystem::ClearPersistentDraws() - { - persistentPoints.clear(); - for (bool& cleared : persistentBuffersCleared) - cleared = true; - } - - void SHDebugDrawSystem::drawLine(std::vector& storage, const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt) - { - if (storage.size() > MAX_POINTS) - { - SHLOG_WARNING("[DebugDraw] Exceeded maximum size of drawable debug elements."); - return; - } - - storage.emplace_back(PointVertex{ startPt, color }); - storage.emplace_back(PointVertex{ endPt, color }); - } - - void SHDebugDrawSystem::drawLineSet(std::vector& storage, const SHVec4& color, std::initializer_list pointList) - { - drawLineSet(storage, color, pointList.begin(), pointList.end()); - } - - void SHDebugDrawSystem::drawPoly(std::vector& storage, const SHVec4& color, std::initializer_list pointList) - { - drawPoly(storage, color, pointList.begin(), pointList.end()); - } - - void SHDebugDrawSystem::drawCube(std::vector& storage, const SHVec4& color, const SHVec3& pos, const SHVec3& size) - { - static const SHVec3 EXTENTS = SHVec3{ 0.5f, 0.5f, 0.5f }; - static const SHVec3 UNIT_BOT_LEFT_FRONT = SHVec3{ pos - EXTENTS }; - static const SHVec3 UNIT_BOT_RIGHT_FRONT = SHVec3{ pos + SHVec3 { EXTENTS.x, -EXTENTS.y, -EXTENTS.z } }; - static const SHVec3 UNIT_BOT_RIGHT_BACK = SHVec3{ pos + SHVec3 { EXTENTS.x, -EXTENTS.y, EXTENTS.z } }; - static const SHVec3 UNIT_BOT_LEFT_BACK = SHVec3{ pos + SHVec3 { -EXTENTS.x, -EXTENTS.y, EXTENTS.z } }; - static const SHVec3 UNIT_TOP_LEFT_BACK = SHVec3{ pos + SHVec3 { -EXTENTS.x, EXTENTS.y, EXTENTS.z } }; - static const SHVec3 UNIT_TOP_RIGHT_FRONT = SHVec3{ pos + SHVec3 { EXTENTS.x, EXTENTS.y, -EXTENTS.z } }; - static const SHVec3 UNIT_TOP_LEFT_FRONT = SHVec3{ pos + SHVec3 { -EXTENTS.x, EXTENTS.y, -EXTENTS.z } }; - static const SHVec3 UNIT_TOP_RIGHT_BACK = SHVec3{ pos + EXTENTS }; - - const SHVec3 BOT_LEFT_BACK = UNIT_BOT_LEFT_BACK * size; - const SHVec3 BOT_RIGHT_BACK = UNIT_BOT_RIGHT_BACK * size; - const SHVec3 BOT_LEFT_FRONT = UNIT_BOT_LEFT_FRONT * size; - const SHVec3 BOT_RIGHT_FRONT = UNIT_BOT_RIGHT_FRONT * size; - const SHVec3 TOP_LEFT_BACK = UNIT_TOP_LEFT_BACK * size; - const SHVec3 TOP_RIGHT_BACK = UNIT_TOP_RIGHT_BACK * size; - const SHVec3 TOP_LEFT_FRONT = UNIT_TOP_LEFT_FRONT * size; - const SHVec3 TOP_RIGHT_FRONT = UNIT_TOP_RIGHT_FRONT * size; - - drawLineSet - ( - storage, - color, - { - // Bottom Square - BOT_LEFT_BACK , BOT_RIGHT_BACK, - BOT_RIGHT_BACK , BOT_RIGHT_FRONT, - BOT_RIGHT_FRONT, BOT_LEFT_FRONT, - BOT_LEFT_FRONT , BOT_LEFT_BACK, - // Top Square - TOP_LEFT_BACK , TOP_RIGHT_BACK, - TOP_RIGHT_BACK , TOP_RIGHT_FRONT, - TOP_RIGHT_FRONT, TOP_LEFT_FRONT, - TOP_LEFT_FRONT , TOP_LEFT_BACK, - // Middle Lines - TOP_LEFT_BACK , BOT_LEFT_BACK, - TOP_RIGHT_BACK , BOT_RIGHT_BACK, - TOP_RIGHT_FRONT, BOT_RIGHT_FRONT, - TOP_LEFT_FRONT , BOT_LEFT_FRONT - } - ); - } - - void SHDebugDrawSystem::drawSphere(std::vector& storage, const SHVec4& color, const SHVec3& pos, double radius) - { - //if (spherePoints.empty()) - { - spherePoints.clear(); - // Generate - static const SHMeshData SPHERE = SHPrimitiveGenerator::Sphere(); - for (const auto& idx : SPHERE.Indices) - { - spherePoints.emplace_back(SPHERE.VertexPositions[idx] * radius + pos); - } - } - drawLineSet(storage, color, spherePoints.begin(), spherePoints.end()); + if (buffer) + { + buffer.Free(); + buffer = {}; + } } + } + void SHDebugDrawSystem::destroyMeshBatches() + { + destroyBatch(meshBatches[static_cast(MeshRenderMode::FilledNoDepthTest)]); + destroyBatch(meshBatches[static_cast(MeshRenderMode::FilledDepthTested)]); + destroyBatch(meshBatches[static_cast(MeshRenderMode::WireNoDepthTest)]); + destroyBatch(meshBatches[static_cast(MeshRenderMode::WireDepthTested)]); + } } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h index 20ddcd42..de3595e3 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h @@ -18,6 +18,7 @@ of DigiPen Institute of Technology is prohibited. #include "Math/Vector/SHVec2.h" #include "Math/Vector/SHVec3.h" #include "Math/Vector/SHVec4.h" +#include "Math/SHMatrix.h" #include "ECS_Base/System/SHSystem.h" #include "ECS_Base/System/SHSystemRoutine.h" #include "Resource/SHHandle.h" @@ -31,6 +32,8 @@ namespace SHADE /* Forward Declarations */ /*-----------------------------------------------------------------------------------*/ class SHVkBuffer; + class SHMesh; + class SHVkPipeline; /*-----------------------------------------------------------------------------------*/ /* Type Definitions */ @@ -42,6 +45,29 @@ namespace SHADE { public: /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Defines the rendering mode for debug lines. + /// + enum class LineRenderMode : uint32_t + { + NoDepthTest, + DepthTested, + Count + }; + /// + /// Defines the rendering mode for debug meshes. + /// + enum class MeshRenderMode : uint32_t + { + FilledNoDepthTest, + FilledDepthTested, + WireNoDepthTest, + WireDepthTested, + Count + }; + /*---------------------------------------------------------------------------------*/ /* System Routines */ /*---------------------------------------------------------------------------------*/ class SH_API ProcessPointsRoutine final : public SHSystemRoutine @@ -68,185 +94,130 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ /* Draw Functions */ /*---------------------------------------------------------------------------------*/ - /// - /// Renders a line between two points in world space. - /// - /// Colour of the line. - /// First point of the line. - /// Second point of the line. - void DrawLine(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt); - /// - /// Renders a triangle indicated by three points in world space. - /// - /// Colour of the triangle. - /// First point of the triangle. - /// Second point of the triangle. - /// Third point of the triangle. - void DrawTri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3); - /// - /// Renders a quadrilateral indicated by four points in world space. - /// - /// Colour of the quadrilateral. - /// First point of the triangle. - /// Second point of the quadrilateral. - /// Third point of the quadrilateral. - /// Third point of the quadrilateral. - void DrawQuad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4); - /// - /// Renders a polygon indicated by the specified set of points in world space. - /// - /// Colour of the polygon. - /// List of points for the polygon. - void DrawPoly(const SHVec4& color, std::initializer_list pointList); - /// - /// Renders a polygon indicated by the specified set of points in world space. - /// - /// Iterator for a STL-like container. - /// Colour of the polygon. - /// - /// Iterator to the first point of the point container. - /// - /// - /// One past last iterator of the point container. - /// + void DrawLine(const SHVec3& start, const SHVec3& end, const SHColour& color = SHColour::WHITE, bool depthTested = false); + void DrawLineLoop(std::initializer_list points, const SHColour& color = SHColour::WHITE, bool depthTested = false); template - void DrawPoly(const SHVec4& color, IterType pointListBegin, IterType pointListEnd); - /// - /// Renders a wireframe cube centered around the position specified in world space. - /// - /// Colour of the cube. - /// Position where the cube wil be centered at. - /// Size of the rendered cube. - void DrawCube(const SHVec4& color, const SHVec3& pos, const SHVec3& size); - /// - /// Renders a wireframe sphere centered around the position specified in world space. - /// - /// Colour of the sphere. - /// Position where the sphere wil be centered at. - /// Size of the rendered sphere. - void DrawSphere(const SHVec4& color, const SHVec3& pos, double radius); + void DrawLineLoop(IterType pointListBegin, IterType pointListEnd, const SHColour& color = SHColour::WHITE, bool depthTested = false); + void DrawTri(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHColour& color = SHColour::WHITE, bool depthTested = false); + //void DrawWireBox(const SHVec3& position, const SHVec3& scale, const SHColour& color = SHColour::WHITE, bool depthTested = false); /*---------------------------------------------------------------------------------*/ /* Persistent Draw Functions */ /*---------------------------------------------------------------------------------*/ - /// - /// Renders a line between two points in world space that will persist until - /// ClearPersistentDraws() is called. These lines are depth tested. - /// - /// Colour of the line. - /// First point of the line. - /// Second point of the line. - void DrawPersistentLine(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt); - /// - /// Renders a triangle indicated by three points in world space that will persist - /// until ClearPersistentDraws() is called. These lines are depth tested. - /// - /// Colour of the triangle. - /// First point of the triangle. - /// Second point of the triangle. - /// Third point of the triangle. - void DrawPersistentTri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3); - /// - /// Renders a quadrilateral indicated by four points in world space that will persist - /// until ClearPersistentDraws() is called. These lines are depth tested. - /// - /// Colour of the quadrilateral. - /// First point of the triangle. - /// Second point of the quadrilateral. - /// Third point of the quadrilateral. - /// Third point of the quadrilateral. - void DrawPersistentQuad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4); - /// - /// Renders a polygon indicated by the specified set of points in world space that - /// will persist until ClearPersistentDraws() is called. These lines are depth - /// tested. - /// - /// Colour of the polygon. - /// List of points for the polygon. - void DrawPersistentPoly(const SHVec4& color, std::initializer_list pointList); - /// - /// Renders a polygon indicated by the specified set of points in world space that - /// will persist until ClearPersistentDraws() is called. These lines are depth - /// tested. - /// - /// Iterator for a STL-like container. - /// Colour of the polygon. - /// - /// Iterator to the first point of the point container. - /// - /// - /// One past last iterator of the point container. - /// + void DrawPersistentLine(const SHVec3& start, const SHVec3& end, const SHColour& color = SHColour::WHITE, bool depthTested = false); + void DrawPersistentLineLoop(std::initializer_list points, const SHColour& color = SHColour::WHITE, bool depthTested = false); template - void DrawPersistentPoly(const SHVec4& color, IterType pointListBegin, IterType pointListEnd); - /// - /// Renders a wireframe cube centered around the position specified in world space - /// that will persist until ClearPersistentDraws() is called. These lines are depth - /// tested. - /// - /// Colour of the cube. - /// Position where the cube wil be centered at. - /// Size of the rendered cube. - void DrawPersistentCube(const SHVec4& color, const SHVec3& pos, const SHVec3& size); - /// - /// Renders a wireframe sphere centered around the position specified in world space - /// that will persist until ClearPersistentDraws() is called. These lines are depth - /// tested. - /// - /// Colour of the sphere. - /// Position where the sphere wil be centered at. - /// Size of the rendered sphere. - void DrawPersistentSphere(const SHVec4& color, const SHVec3& pos, double radius); - /// - /// Clears any persistent drawn debug primitives. - /// - void ClearPersistentDraws(); + void DrawPersistentLineLoop(IterType pointListBegin, IterType pointListEnd, const SHColour& color = SHColour::WHITE, bool depthTested = false); + void DrawPersistentTri(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHColour& color = SHColour::WHITE, bool depthTested = false); private: /*---------------------------------------------------------------------------------*/ /* Type Definitions */ /*---------------------------------------------------------------------------------*/ + using TripleBuffer = std::array, SHGraphicsConstants::NUM_FRAME_BUFFERS>; + using TripleUInt = std::array; + using TripleBool = std::array; + /// + /// Defines a coloured Vertex + /// struct SH_API PointVertex { SHVec4 Position; SHVec4 Color; }; - using TripleBuffer = std::array, SHGraphicsConstants::NUM_FRAME_BUFFERS>; - using TripleUInt = std::array; - using TripleBool = std::array; + struct Batch + { + /*-------------------------------------------------------------------------------*/ + /* Data Members */ + /*-------------------------------------------------------------------------------*/ + Handle Pipeline; + }; + + struct LinesBatch : public Batch + { + /*-------------------------------------------------------------------------------*/ + /* Data Members */ + /*-------------------------------------------------------------------------------*/ + // CPU Buffers + std::vector Points; + // GPU Buffers + TripleBuffer VertexBuffers; + TripleUInt NumPoints; + + }; + + struct MeshBatch : public Batch + { + /*-------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-------------------------------------------------------------------------------*/ + struct InstanceData + { + SHMatrix Transform; + SHVec4 Color; + }; + struct MultiDrawSet + { + Handle Mesh; + std::vector Instances; + }; + + /*-------------------------------------------------------------------------------*/ + /* Data Members */ + /*-------------------------------------------------------------------------------*/ + // CPU Buffers + std::vector SubBatches; + std::vector InstanceData; + // GPU Buffers + TripleBuffer MDIBuffer; + TripleBuffer InstanceDataBuffer; + }; /*---------------------------------------------------------------------------------*/ /* Constants */ /*---------------------------------------------------------------------------------*/ static constexpr uint32_t MAX_POINTS = 100'000; + static constexpr size_t LINE_MODE_COUNT = static_cast(LineRenderMode::Count); + static constexpr size_t MESH_MODE_COUNT = static_cast(MeshRenderMode::Count); /*---------------------------------------------------------------------------------*/ /* Data Members */ /*---------------------------------------------------------------------------------*/ - // CPU Buffers - std::vector points; - std::vector persistentPoints; - // GPU Buffers - TripleBuffer vertexBuffers; - TripleUInt numPoints; - TripleBuffer persistentVertexBuffers; - TripleUInt numPersistentPoints; - TripleBool persistentBuffersCleared; - // Cached Points for polygon drawing - std::vector spherePoints; + // Batches + std::array lineBatches; + std::array persistentLineBatches; + std::array meshBatches; + // Tracking + TripleBool persistentBuffersCleared; // TODO: Use this /*---------------------------------------------------------------------------------*/ /* Helper Draw Functions */ /*---------------------------------------------------------------------------------*/ - void drawLine(std::vector& storage, const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt); - void drawLineSet(std::vector& storage, const SHVec4& color, std::initializer_list pointList); + void drawLine(LinesBatch& batch, const SHVec3& start, const SHVec3& end, const SHColour& color); template - void drawLineSet(std::vector& storage, const SHVec4& color, IterType pointListBegin, IterType pointListEnd); - void drawPoly(std::vector& storage, const SHVec4& color, std::initializer_list pointList); - template - void drawPoly(std::vector& storage, const SHVec4& color, IterType pointListBegin, IterType pointListEnd); - void drawCube(std::vector& storage, const SHVec4& color, const SHVec3& pos, const SHVec3& size); - void drawSphere(std::vector& storage, const SHVec4& color, const SHVec3& pos, double radius); + void drawLineLoop(LinesBatch& batch, IterType pointListBegin, IterType pointListEnd, const SHColour& color); + + /*---------------------------------------------------------------------------------*/ + /* Helper Batch Functions - Lines */ + /*---------------------------------------------------------------------------------*/ + LinesBatch& getLineBatch(bool depthTested); + LinesBatch& getPersistentLineBatch(bool depthTested); + void createLineBatches(); + void initBatch(LinesBatch& batch, Handle pipeline, const std::string& vertexBufferName); + void prepareBatch(LinesBatch& batch, uint32_t frameIndex); + void renderBatch(LinesBatch& batch, Handle cmdBuffer, uint32_t frameIndex); + void destroyBatch(LinesBatch& batch); + void destroyLineBatches(); + + /*---------------------------------------------------------------------------------*/ + /* Helper Batch Functions - Meshes */ + /*---------------------------------------------------------------------------------*/ + void createMeshBatches(); + void initBatch(MeshBatch& batch, Handle pipeline); + void prepareBatch(MeshBatch& batch, uint32_t frameIndex); + void renderBatch(MeshBatch& batch, Handle cmdBuffer, uint32_t frameIndex); + void destroyBatch(MeshBatch& batch);; + void destroyMeshBatches(); }; } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.hpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.hpp index 2a171e73..36c7d2b0 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.hpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.hpp @@ -17,73 +17,44 @@ namespace SHADE { /*-----------------------------------------------------------------------------------*/ /* Draw Functions */ - /*-----------------------------------------------------------------------------------*/ + /*-----------------------------------------------------------------------------------*/ template - void SHDebugDrawSystem::DrawPoly(const SHVec4& color, IterType pointListBegin, IterType pointListEnd) + void SHDebugDrawSystem::DrawLineLoop(IterType pointListBegin, IterType pointListEnd, const SHColour& color, bool depthTested) { - drawPoly(points, color, pointListBegin, pointListEnd); + // Get Batch + drawLineLoop(getLineBatch(depthTested), pointListBegin, pointListEnd, color); } - + template + void SHADE::SHDebugDrawSystem::DrawPersistentLineLoop(IterType pointListBegin, IterType pointListEnd, const SHColour& color , bool depthTested) + { + // Get Batch + drawLineLoop(getPersistentLineBatch(depthTested), pointListBegin, pointListEnd, color); + } + /*-----------------------------------------------------------------------------------*/ /* Helper Draw Functions */ /*-----------------------------------------------------------------------------------*/ template - void SHDebugDrawSystem::drawLineSet(std::vector& storage, const SHVec4& color, IterType pointListBegin, IterType pointListEnd) + void SHDebugDrawSystem::drawLineLoop(LinesBatch& batch, IterType pointListBegin, IterType pointListEnd, const SHColour& color) { // Ensure dereferenced type is SHVec3 - static_assert(std::is_same_v>, "Parameters to DrawPoly must be SHVec3."); - - // Check if points exceeded max - if (storage.size() > MAX_POINTS) - { - SHLOG_WARNING("[DebugDraw] Exceeded maximum size of drawable debug elements."); - return; - } - - const size_t POINTS_COUNT = pointListEnd - pointListBegin; + static_assert(std::is_same_v>, "Parameters to DrawLineLoop must be SHVec3."); + // Invalid polygon + const size_t POINTS_COUNT = pointListEnd - pointListBegin; if (POINTS_COUNT < 2) { - SHLOG_WARNING("[SHDebugDraw] Invalid polygon provided to DrawPoly()."); - return; - } - - const size_t POINTS_ROUNDED_COUNT = POINTS_COUNT / 2 * 2; - for (auto pointIter = pointListBegin; pointIter != (pointListBegin + POINTS_ROUNDED_COUNT); ++pointIter) - { - storage.emplace_back(PointVertex{ *pointIter, color }); - } - } - template - void SHDebugDrawSystem::drawPoly(std::vector& storage, const SHVec4& color, IterType pointListBegin, IterType pointListEnd) - { - // Ensure dereferenced type is SHVec3 - static_assert(std::is_same_v>, "Parameters to DrawPoly must be SHVec3."); - - // Check if points exceeded max - if (storage.size() > MAX_POINTS) - { - SHLOG_WARNING("[DebugDraw] Exceeded maximum size of drawable debug elements."); - return; - } - - const size_t POINTS_COUNT = pointListEnd - pointListBegin; - // Invalid polygon - if (POINTS_COUNT < 2) - { - SHLOG_WARNING("[SHDebugDraw] Invalid polygon provided to DrawPoly()."); + SHLOG_WARNING("[SHDebugDrawSystem] Insufficient points provided to drawLineLoop()."); return; } // Trace the polygon for (auto pointIter = pointListBegin + 1; pointIter != pointListEnd; ++pointIter) { - storage.emplace_back(PointVertex{ *(pointIter - 1), color }); - storage.emplace_back(PointVertex{ *pointIter , color }); + drawLine(batch, *(pointIter - 1), *pointIter, color); } // Close the line loop - storage.emplace_back(PointVertex{ *(pointListEnd - 1), color }); - storage.emplace_back(PointVertex{ *pointListBegin , color }); + drawLine(batch, *(pointListEnd - 1), *pointListBegin, color); } } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp index bd1d60cd..71133df2 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp @@ -124,20 +124,12 @@ namespace SHADE SHFreetypeInstance::Init(); - //SHAssetManager::CompileAsset("../../Assets/Shaders/Text_VS.glsl", false); - //SHAssetManager::CompileAsset("../../Assets/Shaders/Text_FS.glsl", false); - //SHAssetManager::CompileAsset("../../Assets/Shaders/TestCube_VS.glsl", false); - //SHAssetManager::CompileAsset("../../Assets/Shaders/UI_VS.glsl", false); - //SHAssetManager::CompileAsset("../../Assets/Shaders/UI_FS.glsl", false); - //SHAssetManager::CompileAsset("../../Assets/Models/Quad.gltf", false); - //SHAssetManager::CompileAsset("../../Assets/Shaders/ToSwapchain_VS.glsl", false); - //SHAssetManager::CompileAsset("../../Assets/Shaders/ToSwapchain_FS.glsl", false); - // Load Built In Shaders static constexpr AssetID VS_DEFAULT = 39210065; defaultVertShader = SHResourceManager::LoadOrGet(VS_DEFAULT); static constexpr AssetID FS_DEFAULT = 46377769; defaultFragShader = SHResourceManager::LoadOrGet(FS_DEFAULT); static constexpr AssetID VS_DEBUG = 48002439; debugVertShader = SHResourceManager::LoadOrGet(VS_DEBUG); static constexpr AssetID FS_DEBUG = 36671027; debugFragShader = SHResourceManager::LoadOrGet(FS_DEBUG); + static constexpr AssetID VS_DEBUG_MESH = 42127043; debugMeshVertShader = SHResourceManager::LoadOrGet(VS_DEBUG_MESH); static constexpr AssetID CS_COMPOSITE = 45072428; deferredCompositeShader = SHResourceManager::LoadOrGet(CS_COMPOSITE); static constexpr AssetID SSAO = 38430899; ssaoShader = SHResourceManager::LoadOrGet(SSAO); static constexpr AssetID SSAO_BLUR = 39760835; ssaoBlurShader = SHResourceManager::LoadOrGet(SSAO_BLUR); @@ -335,14 +327,31 @@ namespace SHADE screenRenderer->SetCamera(screenCamera); screenRenderer->SetCameraDirector(worldCameraDirector); - // Create debug draw pipeline - debugDrawPipeline = createDebugDrawPipeline(debugDrawNode->GetRenderpass(), debugDrawSubpass); + debugDrawPipeline = createDebugDrawPipeline(debugDrawNode->GetRenderpass(), debugDrawSubpass, false, false, false); SET_VK_OBJ_NAME(device, vk::ObjectType::ePipeline, debugDrawPipeline->GetVkPipeline(), "[Pipeline] Debug Draw"); - SET_VK_OBJ_NAME(device, vk::ObjectType::ePipelineLayout, debugDrawPipeline->GetPipelineLayout()->GetVkPipelineLayout(), "[Pipeline Layout] Debug Draw Pipeline Layout"); - debugDrawDepthPipeline = createDebugDrawPipeline(debugDrawNodeDepth->GetRenderpass(), debugDrawDepthSubpass); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipelineLayout, debugDrawPipeline->GetPipelineLayout()->GetVkPipelineLayout(), "[Pipeline Layout] Debug Draw"); + debugDrawDepthPipeline = createDebugDrawPipeline(debugDrawNodeDepth->GetRenderpass(), debugDrawDepthSubpass, false, false, false); SET_VK_OBJ_NAME(device, vk::ObjectType::ePipeline, debugDrawDepthPipeline->GetVkPipeline(), "[Pipeline Layout] Debug Draw with Depth Test"); - SET_VK_OBJ_NAME(device, vk::ObjectType::ePipelineLayout, debugDrawDepthPipeline->GetPipelineLayout()->GetVkPipelineLayout(), "[Pipeline] Debug Draw with Depth Test Pipeline Layout"); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipelineLayout, debugDrawDepthPipeline->GetPipelineLayout()->GetVkPipelineLayout(), "[Pipeline] Debug Draw with Depth Test"); + debugDrawLineMeshPipeline = createDebugDrawPipeline(debugDrawNode->GetRenderpass(), debugDrawSubpass, false, false, true); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipeline, debugDrawLineMeshPipeline->GetVkPipeline(), "[Pipeline] Debug Draw Line Mesh"); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipelineLayout, debugDrawLineMeshPipeline->GetPipelineLayout()->GetVkPipelineLayout(), "[Pipeline Layout] Debug Draw Line Mesh"); + debugDrawLineMeshDepthPipeline = createDebugDrawPipeline(debugDrawNodeDepth->GetRenderpass(), debugDrawDepthSubpass, false, false, true); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipeline, debugDrawLineMeshDepthPipeline->GetVkPipeline(), "[Pipeline Layout] Debug Draw Line Mesh with Depth Test"); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipelineLayout, debugDrawLineMeshDepthPipeline->GetPipelineLayout()->GetVkPipelineLayout(), "[Pipeline] Debug Draw Line Mesh with Depth Test"); + debugDrawWireMeshPipeline = createDebugDrawPipeline(debugDrawNode->GetRenderpass(), debugDrawSubpass, false, true, true); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipeline, debugDrawWireMeshPipeline->GetVkPipeline(), "[Pipeline] Debug Draw Wire Mesh"); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipelineLayout, debugDrawWireMeshPipeline->GetPipelineLayout()->GetVkPipelineLayout(), "[Pipeline Layout] Debug Draw Wire Mesh"); + debugDrawWireMeshDepthPipeline = createDebugDrawPipeline(debugDrawNodeDepth->GetRenderpass(), debugDrawDepthSubpass, false, true, true); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipeline, debugDrawWireMeshDepthPipeline->GetVkPipeline(), "[Pipeline Layout] Debug Draw Wire Mesh with Depth Test"); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipelineLayout, debugDrawWireMeshDepthPipeline->GetPipelineLayout()->GetVkPipelineLayout(), "[Pipeline] Debug Draw Wire Mesh with Depth Test"); + debugDrawFilledPipeline = createDebugDrawPipeline(debugDrawNode->GetRenderpass(), debugDrawSubpass, true, true, true); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipeline, debugDrawFilledPipeline->GetVkPipeline(), "[Pipeline] Debug Draw Filled Mesh"); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipelineLayout, debugDrawFilledPipeline->GetPipelineLayout()->GetVkPipelineLayout(), "[Pipeline Layout] Debug Draw Filled Mesh"); + debugDrawFilledDepthPipeline = createDebugDrawPipeline(debugDrawNodeDepth->GetRenderpass(), debugDrawDepthSubpass, true, true, true); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipeline, debugDrawFilledDepthPipeline->GetVkPipeline(), "[Pipeline Layout] Debug Draw Filled Mesh with Depth Test"); + SET_VK_OBJ_NAME(device, vk::ObjectType::ePipelineLayout, debugDrawFilledDepthPipeline->GetPipelineLayout()->GetVkPipelineLayout(), "[Pipeline] Debug Draw Filled Mesh with Depth Test"); } void SHGraphicsSystem::InitMiddleEnd(void) noexcept @@ -1083,34 +1092,68 @@ namespace SHADE return worldRenderGraph->GetNode(G_BUFFER_RENDER_GRAPH_NODE_NAME.data()); } - SHADE::SHFontLibrary const& SHGraphicsSystem::GetFontLibrary(void) const noexcept + Handle SHGraphicsSystem::GetDebugDrawPipeline(DebugDrawPipelineType type) const noexcept + { + switch (type) + { + case DebugDrawPipelineType::LineNoDepthTest: return debugDrawPipeline; + case DebugDrawPipelineType::LineDepthTested: return debugDrawDepthPipeline; + case DebugDrawPipelineType::LineMeshNoDepthTest: return debugDrawLineMeshPipeline; + case DebugDrawPipelineType::LineMeshDepthTested: return debugDrawLineMeshDepthPipeline; + case DebugDrawPipelineType::WireMeshNoDepthTest: return debugDrawWireMeshPipeline; + case DebugDrawPipelineType::WireMeshDepthTested: return debugDrawWireMeshDepthPipeline; + case DebugDrawPipelineType::FilledMeshNoDepthTest: return debugDrawFilledPipeline; + case DebugDrawPipelineType::FilledMeshDepthTested: return debugDrawFilledDepthPipeline; + } + + SHLOG_WARNING("[SHGraphicsSystem] Attempted to retrieve an invalid Debug Draw Pipeline. Default Debug Draw Pipeline returned."); + return debugDrawPipeline; + } + + SHFontLibrary const& SHGraphicsSystem::GetFontLibrary(void) const noexcept { return fontLibrary; } - Handle SHGraphicsSystem::createDebugDrawPipeline(Handle renderPass, Handle subpass) + Handle SHGraphicsSystem::createDebugDrawPipeline(Handle renderPass, Handle subpass, bool filled, bool triMesh, bool instanced) { auto pipelineLayout = resourceManager.Create ( device, SHPipelineLayoutParams { - .shaderModules = { debugVertShader, debugFragShader }, + .shaderModules = { (triMesh ? debugMeshVertShader : debugVertShader) , debugFragShader }, .globalDescSetLayouts = SHGraphicsGlobalData::GetDescSetLayouts() } ); auto pipeline = resourceManager.Create(device, pipelineLayout, nullptr, renderPass, subpass); pipeline->GetPipelineState().SetRasterizationState(SHRasterizationState { - .polygonMode = vk::PolygonMode::eLine, - .cull_mode = vk::CullModeFlagBits::eNone + .polygonMode = filled ? vk::PolygonMode::eFill : vk::PolygonMode::eLine, + .cull_mode = filled ? vk::CullModeFlagBits::eBack : vk::CullModeFlagBits::eNone }); pipeline->GetPipelineState().SetInputAssemblyState(SHInputAssemblyState { - .topology = vk::PrimitiveTopology::eLineList + .topology = triMesh ? vk::PrimitiveTopology::eTriangleList : vk::PrimitiveTopology::eLineList }); SHVertexInputState debugDrawVertexInputState; - debugDrawVertexInputState.AddBinding(false, true, { SHVertexAttribute(SHAttribFormat::FLOAT_4D), SHVertexAttribute(SHAttribFormat::FLOAT_4D) }); + if (instanced) + { + debugDrawVertexInputState.AddBinding(false, false, { SHVertexAttribute(SHAttribFormat::FLOAT_4D) }); // 0: Vertex World Space Position + debugDrawVertexInputState.AddBinding(true , true , { SHVertexAttribute(SHAttribFormat::MAT_4D) }); // 1: Instance Transform Matrix (4 Slots) + debugDrawVertexInputState.AddBinding(true , true , { SHVertexAttribute(SHAttribFormat::FLOAT_4D) }); // 5: Instance Color + } + else + { + debugDrawVertexInputState.AddBinding + ( + false, true, + { + SHVertexAttribute(SHAttribFormat::FLOAT_4D), // Vertex World Space Position + SHVertexAttribute(SHAttribFormat::FLOAT_4D) // Vertex Color + } + ); + } pipeline->GetPipelineState().SetVertexInputState(debugDrawVertexInputState); SHColorBlendState colorBlendState{}; colorBlendState.logic_op_enable = VK_FALSE; diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h index 8c65f233..4a4d114f 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h @@ -73,6 +73,18 @@ namespace SHADE Sphere }; static constexpr int MAX_PRIMITIVE_TYPES = 2; + enum class DebugDrawPipelineType + { + LineNoDepthTest, + LineDepthTested, + LineMeshNoDepthTest, + LineMeshDepthTested, + WireMeshNoDepthTest, + WireMeshDepthTested, + FilledMeshNoDepthTest, + FilledMeshDepthTested + }; + static constexpr int MAX_DEBUG_DRAW_PIPELINE_TYPES = 8; /***********************************************************************************/ /*! @@ -371,8 +383,7 @@ namespace SHADE Handle GetMousePickSystem(void) const noexcept {return mousePickSystem;}; Handle GetPostOffscreenRenderSystem(void) const noexcept {return postOffscreenRender;}; Handle GetPrimaryRenderpass() const noexcept; - Handle GetDebugDrawPipeline(void) const noexcept { return debugDrawPipeline; } - Handle GetDebugDrawDepthPipeline(void) const noexcept { return debugDrawDepthPipeline; } + Handle GetDebugDrawPipeline(DebugDrawPipelineType type) const noexcept; uint32_t GetCurrentFrameIndex(void) const noexcept { return renderContext.GetCurrentFrame(); } SHFontLibrary const& GetFontLibrary (void) const noexcept; @@ -439,6 +450,7 @@ namespace SHADE Handle defaultFragShader; Handle debugVertShader; Handle debugFragShader; + Handle debugMeshVertShader; Handle deferredCompositeShader; Handle ssaoShader; Handle ssaoBlurShader; @@ -454,6 +466,12 @@ namespace SHADE Handle defaultMaterial; Handle debugDrawPipeline; Handle debugDrawDepthPipeline; + Handle debugDrawLineMeshPipeline; + Handle debugDrawLineMeshDepthPipeline; + Handle debugDrawWireMeshPipeline; + Handle debugDrawWireMeshDepthPipeline; + Handle debugDrawFilledPipeline; + Handle debugDrawFilledDepthPipeline; // Built-In Textures Handle defaultTexture; @@ -482,6 +500,6 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ /* Helper Functions */ /*---------------------------------------------------------------------------------*/ - Handle createDebugDrawPipeline(Handle renderPass, Handle subpass); + Handle createDebugDrawPipeline(Handle renderPass, Handle subpass, bool filled, bool triMesh, bool instanced); }; } \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp index 1ca7ae39..96af5e39 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp @@ -195,7 +195,7 @@ namespace SHADE const auto& TRI_ARRAY = rp3dRenderer->getTrianglesArray(); for (int i = 0; i < NUM_TRIS; ++i) - debugRenderer->DrawTri(SHColour::RED, TRI_ARRAY[i].point1, TRI_ARRAY[i].point2, TRI_ARRAY[i].point3); + debugRenderer->DrawTri(TRI_ARRAY[i].point1, TRI_ARRAY[i].point2, TRI_ARRAY[i].point3, SHColour::RED); } #else @@ -207,7 +207,7 @@ namespace SHADE const auto& TRI_ARRAY = rp3dRenderer->getTrianglesArray(); for (int i = 0; i < NUM_TRIS; ++i) - debugRenderer->DrawTri(SHColour::RED, TRI_ARRAY[i].point1, TRI_ARRAY[i].point2, TRI_ARRAY[i].point3); + debugRenderer->DrawTri(TRI_ARRAY[i].point1, TRI_ARRAY[i].point2, TRI_ARRAY[i].point3, SHColour::RED); #endif } @@ -225,7 +225,7 @@ namespace SHADE const auto& LINE_ARRAY = rp3dRenderer->getLinesArray(); for (int i = 0; i < NUM_LINES; ++i) - debugRenderer->DrawLine(SHColour::RED, LINE_ARRAY[i].point1, LINE_ARRAY[i].point2); + debugRenderer->DrawLine(LINE_ARRAY[i].point1, LINE_ARRAY[i].point2, SHColour::RED); } #else @@ -237,7 +237,7 @@ namespace SHADE const auto& LINE_ARRAY = rp3dRenderer->getLinesArray(); for (int i = 0; i < NUM_LINES; ++i) - debugRenderer->DrawLine(SHColour::RED, LINE_ARRAY[i].point1, LINE_ARRAY[i].point2); + debugRenderer->DrawLine(LINE_ARRAY[i].point1, LINE_ARRAY[i].point2, SHColour::RED); #endif } @@ -261,7 +261,7 @@ namespace SHADE const float RENDER_DIST = raycastResult.distance == std::numeric_limits::infinity() ? SHRay::MAX_RAYCAST_DIST : raycastResult.distance; const SHVec3 END_POS = ray.position + (ray.direction * RENDER_DIST); - debugRenderer->DrawLine(RAY_COLOUR, ray.position, END_POS); + debugRenderer->DrawLine(ray.position, END_POS, RAY_COLOUR); } } @@ -306,16 +306,16 @@ namespace SHADE transformedVertices[IDX2] = SHVec3::Transform(boxVertices[IDX2], FINAL_TRS); // Draw 4 line to connect the quads - debugRenderer->DrawLine(COLLIDER_COLOUR, transformedVertices[IDX1], transformedVertices[IDX2]); + debugRenderer->DrawLine(transformedVertices[IDX1], transformedVertices[IDX2], COLLIDER_COLOUR); } // A, B, C, D std::array backQuad { transformedVertices[0], transformedVertices[1], transformedVertices[3], transformedVertices[2] }; - debugRenderer->DrawPoly(COLLIDER_COLOUR, backQuad.begin(), backQuad.end()); + debugRenderer->DrawLineLoop(backQuad.begin(), backQuad.end(), COLLIDER_COLOUR); // E, F, G, H std::array frontQuad { transformedVertices[4], transformedVertices[5], transformedVertices[7], transformedVertices[6] }; - debugRenderer->DrawPoly(COLLIDER_COLOUR, frontQuad.begin(), frontQuad.end()); + debugRenderer->DrawLineLoop(frontQuad.begin(), frontQuad.end(), COLLIDER_COLOUR); } void SHPhysicsDebugDrawSystem::debugDrawSphere(SHDebugDrawSystem* debugRenderer, const SHColliderComponent& colliderComponent, const SHCollisionShape& collisionShape) noexcept @@ -327,8 +327,9 @@ namespace SHADE // Calculate final position & orientation const SHQuaternion FINAL_ROT = colliderComponent.GetOrientation() * SHQuaternion::FromEuler(collisionShape.GetRotationOffset()); const SHMatrix TR = SHMatrix::Rotate(FINAL_ROT) * SHMatrix::Translate(colliderComponent.GetPosition()); - - debugRenderer->DrawSphere(COLLIDER_COLOUR, SHVec3::Transform(collisionShape.GetPositionOffset(), TR), SPHERE->GetWorldRadius()); + + /* #KWFix */ + //debugRenderer->DrawSphere(COLLIDER_COLOUR, SHVec3::Transform(collisionShape.GetPositionOffset(), TR), SPHERE->GetWorldRadius()); } } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Tools/SHDebugDraw.cpp b/SHADE_Engine/src/Tools/SHDebugDraw.cpp index b8aa8b0e..839a9b84 100644 --- a/SHADE_Engine/src/Tools/SHDebugDraw.cpp +++ b/SHADE_Engine/src/Tools/SHDebugDraw.cpp @@ -43,67 +43,67 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ void SHDebugDraw::Line(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt) { - dbgDrawSys->DrawLine(color, startPt, endPt); + dbgDrawSys->DrawLine(startPt, endPt, color); } void SHDebugDraw::Tri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3) { - dbgDrawSys->DrawTri(color, pt1, pt2, pt3); + //dbgDrawSys->DrawTri(color, pt1, pt2, pt3); } void SHDebugDraw::Quad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4) { - dbgDrawSys->DrawQuad(color, pt1, pt2, pt3, pt4); + //dbgDrawSys->DrawQuad(color, pt1, pt2, pt3, pt4); } void SHDebugDraw::Poly(const SHVec4& color, std::initializer_list pointList) { - dbgDrawSys->DrawPoly(color, pointList); + //dbgDrawSys->DrawPoly(color, pointList); } void SHDebugDraw::Cube(const SHVec4& color, const SHVec3& pos, const SHVec3& size) { - dbgDrawSys->DrawCube(color, pos, size); + //dbgDrawSys->DrawCube(color, pos, size); } void SHDebugDraw::Sphere(const SHVec4& color, const SHVec3& pos, double radius) { - dbgDrawSys->DrawSphere(color, pos, radius); + //dbgDrawSys->DrawSphere(color, pos, radius); } void SHDebugDraw::PersistentLine(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt) { - dbgDrawSys->DrawPersistentLine(color, startPt, endPt); + dbgDrawSys->DrawPersistentLine(startPt, endPt, color); } void SHDebugDraw::PersistentTri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3) { - dbgDrawSys->DrawPersistentTri(color, pt1, pt2, pt3); + //dbgDrawSys->DrawPersistentTri(color, pt1, pt2, pt3); } void SHDebugDraw::PersistentQuad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4) { - dbgDrawSys->DrawPersistentQuad(color, pt1, pt2, pt3, pt4); + //dbgDrawSys->DrawPersistentQuad(color, pt1, pt2, pt3, pt4); } void SHDebugDraw::PersistentPoly(const SHVec4& color, std::initializer_list pointList) { - dbgDrawSys->DrawPersistentPoly(color, pointList); + //dbgDrawSys->DrawPersistentPoly(color, pointList); } void SHDebugDraw::PersistentCube(const SHVec4& color, const SHVec3& pos, const SHVec3& size) { - dbgDrawSys->DrawPersistentCube(color, pos, size); + //dbgDrawSys->DrawPersistentCube(color, pos, size); } void SHDebugDraw::PersistentSphere(const SHVec4& color, const SHVec3& pos, double radius) { - dbgDrawSys->DrawPersistentSphere(color, pos, radius); + //dbgDrawSys->DrawPersistentSphere(color, pos, radius); } void SHDebugDraw::ClearPersistentDraws() { - dbgDrawSys->ClearPersistentDraws(); + //dbgDrawSys->ClearPersistentDraws(); } } \ No newline at end of file From 98ff16d00c2993e30dbee4a73590806bed11b5b6 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Thu, 15 Dec 2022 01:45:44 +0800 Subject: [PATCH 041/275] Added deebug drawing of cubes of any transform --- Assets/Shaders/DebugDrawMesh_VS.glsl | 2 +- Assets/Shaders/DebugDrawMesh_VS.shshaderb | Bin 1869 -> 1853 bytes Assets/Shaders/DebugDraw_VS.glsl | 4 +- Assets/Shaders/DebugDraw_VS.shshaderb | Bin 1765 -> 1749 bytes .../MiddleEnd/Interface/SHDebugDrawSystem.cpp | 244 ++++++++++++++++-- .../MiddleEnd/Interface/SHDebugDrawSystem.h | 28 +- .../MiddleEnd/Interface/SHGraphicsSystem.cpp | 6 +- .../MiddleEnd/Interface/SHGraphicsSystem.h | 6 +- .../MiddleEnd/Meshes/SHPrimitiveGenerator.cpp | 74 +++++- .../MiddleEnd/Meshes/SHPrimitiveGenerator.h | 41 ++- SHADE_Engine/src/Tools/SHDebugDraw.cpp | 10 +- SHADE_Engine/src/Tools/SHDebugDraw.h | 3 + 12 files changed, 371 insertions(+), 47 deletions(-) diff --git a/Assets/Shaders/DebugDrawMesh_VS.glsl b/Assets/Shaders/DebugDrawMesh_VS.glsl index f84dd35a..2f035261 100644 --- a/Assets/Shaders/DebugDrawMesh_VS.glsl +++ b/Assets/Shaders/DebugDrawMesh_VS.glsl @@ -1,7 +1,7 @@ #version 450 #extension GL_KHR_vulkan_glsl : enable -layout(location = 0) in vec4 aVertexPos; +layout(location = 0) in vec3 aVertexPos; layout(location = 1) in mat4 worldTransform; layout(location = 5) in vec4 color; diff --git a/Assets/Shaders/DebugDrawMesh_VS.shshaderb b/Assets/Shaders/DebugDrawMesh_VS.shshaderb index 55d82090c9be148c039e83910d671b2e8f7d6442..442cd789d87ff082d13ea456d7891105d3a27d52 100644 GIT binary patch delta 463 zcmX|-&q@MO6vodT9fcsJHZC-qEc-M5St$fj5iQ!P3%4TTCV~NN(N14Mdlp;5w^d5?0MD{(j%OuA=d9I{Tb{^^&<+Xc@aj)0ZPl z>6MTcUiS@43uUwu3 If9)QA0jV7zE&u=k delta 479 zcmXw!&q_j35XR>my%s@6s}}j^R+iPRc7h-kQqVGM;Z{W4L~u!4w9{9R9-?))_7Qra zAn5nWIRiiL%s1cMGqWoHl)bxO_)$_dv+Vn2=~~K${qf{|{L#y&W{DN-3{6p@iuqms znorQw&z8^r%k65kCLi%&MdYpGz|B(93X+2j=!9B!X?xTfD?tG=YUKxDHow(;lT1C$ z*TP21)jJP8ZKi*#&Vokmt@8PsOlPv!LC-$g=K9GrXwns0$HNn6X@x^bK@qhh@)YEa zS|ezh?XjfZItI?<&Qx~nt_Y}5a2X44gwS3pyq?t>&fat#>*PYR? KTAn{vYr{Xpbs)X~ diff --git a/Assets/Shaders/DebugDraw_VS.glsl b/Assets/Shaders/DebugDraw_VS.glsl index a027d596..42a48c01 100644 --- a/Assets/Shaders/DebugDraw_VS.glsl +++ b/Assets/Shaders/DebugDraw_VS.glsl @@ -1,7 +1,7 @@ #version 450 #extension GL_KHR_vulkan_glsl : enable -layout(location = 0) in vec4 aVertexPos; +layout(location = 0) in vec3 aVertexPos; layout(location = 1) in vec4 aVertColor; // Output @@ -21,6 +21,6 @@ layout(set = 2, binding = 0) uniform CameraData void main() { - gl_Position = cameraData.vpMat * vec4 (aVertexPos.xyz, 1.0f); + gl_Position = cameraData.vpMat * vec4 (aVertexPos, 1.0f); Out.vertColor = aVertColor; } \ No newline at end of file diff --git a/Assets/Shaders/DebugDraw_VS.shshaderb b/Assets/Shaders/DebugDraw_VS.shshaderb index 30c8d6db49fe8eddd97693f148de32007989390b..9f5f7766a6fa15f0cfbb3fda364868075c99fad5 100644 GIT binary patch delta 444 zcmX|+F;4PMMp}@-I5+H;Q{{o?;w}6>=T|0pO58G~5AS^<~AOsciV_pZuKlogBaku2so&vMaw)7W7 z^5#I!1og#N^gPqoC7^F#SVLc)d5&k#X(Q>>VW&Iy zT)^9-?{<`}UwYLw{+zz5RY3t%z!Mzn+}R~b0uDioos>)X$cQrCfNS6l)*wz7KXGXT DuIM3( delta 460 zcmXw!y-osA5QXR7Wfz-`u>wJ1{auA85TnFcP=XDup}@*y6EwySUx85a6o!Pt_pr0o z=kR>w?rcu>%$YNHX1>$k)VrUBe~kCdEdP93xhvRYJe|Ex-$%uZS!|J=Nh1<{C4ZCU zzIZNX((&Z$o?p2=+FN)P1FJ~>i~4S!iPj}Kp(z?!=qy&?8aza)wZ+l! zmRc4xdPiE4Sw{l>_%pNd_MU|vrg$vW1Wb3*xbIX*gGetCurrentFrameIndex(); + + // Set up line batches for (auto& batch : system->lineBatches) { system->prepareBatch(batch, FRAME_IDX); batch.Points.clear(); } - for (auto& batch : system->persistentLineBatches) + + // Set up mesh batches + for (auto& batch : system->meshBatches) { system->prepareBatch(batch, FRAME_IDX); + for (auto& subBatch : batch.SubBatches) + { + subBatch.second.InstanceColors.clear(); + subBatch.second.InstanceTransforms.clear(); + } + } + + // Set up persistent batches if it was wiped + if (system->persistentBuffersCleared[FRAME_IDX]) + { + for (auto& batch : system->persistentLineBatches) + { + system->prepareBatch(batch, FRAME_IDX); + } + system->persistentBuffersCleared[FRAME_IDX] = false; } } @@ -64,8 +83,8 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ void SHDebugDrawSystem::Init() { - const auto* GFX_SYSTEM = SHSystemManager::GetSystem(); - if (!GFX_SYSTEM) + gfxSystem = SHSystemManager::GetSystem(); + if (!gfxSystem) { SHLOG_ERROR("[DebugDraw] Attempted to do debug draw without a graphics system."); return; @@ -76,21 +95,49 @@ namespace SHADE createMeshBatches(); // Register function for subpass - auto const& RENDERERS = GFX_SYSTEM->GetDefaultViewport()->GetRenderers(); + auto const& RENDERERS = gfxSystem->GetDefaultViewport()->GetRenderers(); auto renderGraph = RENDERERS[SHGraphicsConstants::RenderGraphIndices::WORLD]->GetRenderGraph(); auto subPass = renderGraph->GetNode("Debug Draw")->GetSubpass("Debug Draw"); - subPass->AddExteriorDrawCalls([this, GFX_SYSTEM](Handle& cmdBuffer, uint32_t frameIndex) + subPass->AddExteriorDrawCalls([this](Handle& cmdBuffer, uint32_t frameIndex) { - const uint32_t FRAME_IDX = GFX_SYSTEM->GetCurrentFrameIndex(); - renderBatch(lineBatches[static_cast(LineRenderMode::NoDepthTest)], cmdBuffer, FRAME_IDX); - renderBatch(persistentLineBatches[static_cast(LineRenderMode::NoDepthTest)], cmdBuffer, FRAME_IDX); + const uint32_t FRAME_IDX = gfxSystem->GetCurrentFrameIndex(); + cmdBuffer->BeginLabeledSegment("SHDebugDraw (No Depth Test)"); + { + cmdBuffer->BeginLabeledSegment("SHDebugDraw (Lines)"); + { + renderBatch(lineBatches[static_cast(LineRenderMode::NoDepthTest)], cmdBuffer, FRAME_IDX); + renderBatch(persistentLineBatches[static_cast(LineRenderMode::NoDepthTest)], cmdBuffer, FRAME_IDX); + } + cmdBuffer->EndLabeledSegment(); + + cmdBuffer->BeginLabeledSegment("SHDebugDraw (Meshes)"); + { + renderBatch(meshBatches[static_cast(MeshRenderMode::WireNoDepthTest)], cmdBuffer, FRAME_IDX); + } + cmdBuffer->EndLabeledSegment(); + } + cmdBuffer->EndLabeledSegment(); }); auto subPassWithDepth = renderGraph->GetNode("Debug Draw with Depth")->GetSubpass("Debug Draw with Depth"); - subPassWithDepth->AddExteriorDrawCalls([this, GFX_SYSTEM](Handle& cmdBuffer, uint32_t frameIndex) + subPassWithDepth->AddExteriorDrawCalls([this](Handle& cmdBuffer, uint32_t frameIndex) { - const uint32_t FRAME_IDX = GFX_SYSTEM->GetCurrentFrameIndex(); - renderBatch(lineBatches[static_cast(LineRenderMode::DepthTested)], cmdBuffer, FRAME_IDX); - renderBatch(persistentLineBatches[static_cast(LineRenderMode::DepthTested)], cmdBuffer, FRAME_IDX); + const uint32_t FRAME_IDX = gfxSystem->GetCurrentFrameIndex(); + cmdBuffer->BeginLabeledSegment("SHDebugDraw (Depth Tested)"); + { + cmdBuffer->BeginLabeledSegment("SHDebugDraw (Lines)"); + { + renderBatch(lineBatches[static_cast(LineRenderMode::DepthTested)], cmdBuffer, FRAME_IDX); + renderBatch(persistentLineBatches[static_cast(LineRenderMode::DepthTested)], cmdBuffer, FRAME_IDX); + } + cmdBuffer->EndLabeledSegment(); + + cmdBuffer->BeginLabeledSegment("SHDebugDraw (Meshes)"); + { + renderBatch(meshBatches[static_cast(MeshRenderMode::WireDepthTested)], cmdBuffer, FRAME_IDX); + } + cmdBuffer->EndLabeledSegment(); + } + cmdBuffer->EndLabeledSegment(); }); } @@ -120,6 +167,11 @@ namespace SHADE DrawLineLoop({ p1, p2, p3 }, color, depthTested); } + void SHDebugDrawSystem::DrawWireBox(const SHMatrix& matrix, const SHColour& color, bool depthTested) + { + drawWireBox(getMeshBatch(false, depthTested), matrix, color); + } + /*-----------------------------------------------------------------------------------*/ /* Persistent Draw Functions */ /*-----------------------------------------------------------------------------------*/ @@ -138,6 +190,14 @@ namespace SHADE DrawPersistentLineLoop({ p1, p2, p3 }, color, depthTested); } + void SHDebugDrawSystem::ClearPersistentDraws() + { + for (auto& batch : persistentLineBatches) + batch.Points.clear(); + for (bool& cleared : persistentBuffersCleared) + cleared = true; + } + /*-----------------------------------------------------------------------------------*/ /* Helper Draw Functions */ /*-----------------------------------------------------------------------------------*/ @@ -154,18 +214,41 @@ namespace SHADE batch.Points.emplace_back(end, color); } + void SHDebugDrawSystem::drawWireBox(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color) + { + + const auto* GFX_SYSTEM = SHSystemManager::GetSystem(); + if (!GFX_SYSTEM) + { + SHLOG_ERROR("[DebugDraw] Attempted to do debug draw without a graphics system."); + return; + } + + Handle box = GFX_SYSTEM->GetMeshPrimitive(PrimitiveType::LineCube); + + // Create if doesn't exist + if (!batch.SubBatches.contains(box)) + { + MeshBatch::MultiDrawSet set; + set.Mesh = box; + batch.SubBatches.emplace(box, std::move(set)); + } + + // Add to the batch + batch.SubBatches[box].InstanceTransforms.emplace_back(transformMatrix); + batch.SubBatches[box].InstanceColors.emplace_back(color); + } + /*-----------------------------------------------------------------------------------*/ /* Helper Batch Functions - Lines */ /*-----------------------------------------------------------------------------------*/ SHDebugDrawSystem::LinesBatch& SHDebugDrawSystem::getLineBatch(bool depthTested) { - return depthTested ? lineBatches[static_cast(LineRenderMode::DepthTested)] - : lineBatches[static_cast(LineRenderMode::NoDepthTest)]; + return lineBatches[static_cast(depthTested ? LineRenderMode::DepthTested : LineRenderMode::NoDepthTest)]; } SHDebugDrawSystem::LinesBatch& SHDebugDrawSystem::getPersistentLineBatch(bool depthTested) { - return depthTested ? persistentLineBatches[static_cast(LineRenderMode::DepthTested)] - : persistentLineBatches[static_cast(LineRenderMode::NoDepthTest)]; + return persistentLineBatches[static_cast(depthTested ? LineRenderMode::DepthTested : LineRenderMode::NoDepthTest)]; } void SHDebugDrawSystem::createLineBatches() @@ -255,14 +338,13 @@ namespace SHADE { if (batch.NumPoints[frameIndex] > 0) { - cmdBuffer->BeginLabeledSegment("SHDebugDraw"); cmdBuffer->BindPipeline(batch.Pipeline); cmdBuffer->SetLineWidth(LineWidth); cmdBuffer->BindVertexBuffer(0, batch.VertexBuffers[frameIndex], 0); cmdBuffer->DrawArrays(batch.NumPoints[frameIndex], 1, 0, 0); } } - + void SHDebugDrawSystem::destroyBatch(LinesBatch& batch) { for (auto& vBuffer : batch.VertexBuffers) @@ -286,6 +368,24 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ /* Helper Batch Functions - Meshes */ /*-----------------------------------------------------------------------------------*/ + SHDebugDrawSystem::MeshBatch& SHDebugDrawSystem::getMeshBatch(bool filled, bool depthTested) + { + MeshRenderMode mode = {}; + + if (filled) + { + mode = depthTested ? MeshRenderMode::FilledDepthTested + : MeshRenderMode::FilledNoDepthTest; + } + else + { + mode = depthTested ? MeshRenderMode::WireDepthTested + : MeshRenderMode::WireNoDepthTest; + } + + return meshBatches[static_cast(mode)]; + } + void SHDebugDrawSystem::createMeshBatches() { auto gfxSys = SHSystemManager::GetSystem(); @@ -321,9 +421,115 @@ namespace SHADE { batch.Pipeline = pipeline; } + + void SHDebugDrawSystem::prepareBatch(MeshBatch& batch, uint32_t frameIndex) + { + // Parameter checks + if (frameIndex > batch.MDIBuffer.size()) + { + SHLOG_ERROR("[SHDebugDrawSystem] An invalid frame index was specified for debug drawing. Skipping."); + return; + } + + // Clear existing data + batch.InstanceTransforms.clear(); + batch.InstanceColors.clear(); + batch.MDIData.clear(); + + // Populate + for (auto& subBatch : batch.SubBatches) + { + auto& multiDrawSet = subBatch.second; + + // Nothing to populate with + if (multiDrawSet.InstanceTransforms.empty()) + continue; + + // Populate batch data on CPU + batch.MDIData.emplace_back(vk::DrawIndexedIndirectCommand + { + .indexCount = multiDrawSet.Mesh->IndexCount, + .instanceCount = static_cast(multiDrawSet.InstanceTransforms.size()), + .firstIndex = multiDrawSet.Mesh->FirstIndex, + .vertexOffset = multiDrawSet.Mesh->FirstVertex, + .firstInstance = static_cast(batch.InstanceTransforms.size()) + }); + batch.InstanceTransforms.insert + ( + batch.InstanceTransforms.end(), + multiDrawSet.InstanceTransforms.begin(), + multiDrawSet.InstanceTransforms.end() + ); + batch.InstanceColors.insert + ( + batch.InstanceColors.end(), + multiDrawSet.InstanceColors.begin(), + multiDrawSet.InstanceColors.end() + ); + + // Copy to GPU + SHVkUtil::EnsureBufferAndCopyHostVisibleData + ( + gfxSystem->GetDevice(), + batch.MDIBuffer[frameIndex], + batch.MDIData.data(), static_cast(batch.MDIData.size() * sizeof(vk::DrawIndexedIndirectCommand)), + vk::BufferUsageFlagBits::eIndirectBuffer, + "Debug Draw Mesh Batch MDI Buffer" + ); + SHVkUtil::EnsureBufferAndCopyHostVisibleData + ( + gfxSystem->GetDevice(), + batch.InstanceTransformBuffer[frameIndex], + batch.InstanceTransforms.data(), static_cast(batch.InstanceTransforms.size() * sizeof(SHMatrix)), + vk::BufferUsageFlagBits::eVertexBuffer, + "Debug Draw Mesh Batch Instance Transform Buffer" + ); + SHVkUtil::EnsureBufferAndCopyHostVisibleData + ( + gfxSystem->GetDevice(), + batch.InstanceColorBuffer[frameIndex], + batch.InstanceColors.data(), static_cast(batch.InstanceColors.size() * sizeof(SHVec4)), + vk::BufferUsageFlagBits::eVertexBuffer, + "Debug Draw Mesh Batch Instance Color Buffer" + ); + } + } + + void SHDebugDrawSystem::renderBatch(MeshBatch& batch, Handle cmdBuffer, uint32_t frameIndex) + { + static constexpr uint32_t TRANSFORM_BIND_PT = 1; + static constexpr uint32_t COLOR_BIND_PT = 2; + + // Nothing to draw + if (batch.MDIData.empty()) + return; + + // Bind Pipeline + cmdBuffer->BindPipeline(batch.Pipeline); + + // Bind meshes + cmdBuffer->BindVertexBuffer(0, gfxSystem->GetMeshLibrary().GetVertexPositionsBuffer(), 0); + cmdBuffer->BindIndexBuffer(gfxSystem->GetMeshLibrary().GetIndexBuffer(), 0); + + // Bind instance attributes + cmdBuffer->BindVertexBuffer(TRANSFORM_BIND_PT, batch.InstanceTransformBuffer[frameIndex], 0); + cmdBuffer->BindVertexBuffer(COLOR_BIND_PT, batch.InstanceColorBuffer[frameIndex], 0); + + // Execute draw + cmdBuffer->DrawMultiIndirect(batch.MDIBuffer[frameIndex], static_cast(batch.MDIData.size())); + } + void SHDebugDrawSystem::destroyBatch(MeshBatch& batch) { - for (auto& buffer : batch.InstanceDataBuffer) + for (auto& buffer : batch.InstanceColorBuffer) + { + if (buffer) + { + buffer.Free(); + buffer = {}; + } + } + for (auto& buffer : batch.InstanceTransformBuffer) { if (buffer) { diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h index de3595e3..035357e3 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h @@ -31,6 +31,7 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ /* Forward Declarations */ /*-----------------------------------------------------------------------------------*/ + class SHGraphicsSystem; class SHVkBuffer; class SHMesh; class SHVkPipeline; @@ -99,7 +100,7 @@ namespace SHADE template void DrawLineLoop(IterType pointListBegin, IterType pointListEnd, const SHColour& color = SHColour::WHITE, bool depthTested = false); void DrawTri(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHColour& color = SHColour::WHITE, bool depthTested = false); - //void DrawWireBox(const SHVec3& position, const SHVec3& scale, const SHColour& color = SHColour::WHITE, bool depthTested = false); + void DrawWireCube(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); /*---------------------------------------------------------------------------------*/ /* Persistent Draw Functions */ @@ -109,6 +110,10 @@ namespace SHADE template void DrawPersistentLineLoop(IterType pointListBegin, IterType pointListEnd, const SHColour& color = SHColour::WHITE, bool depthTested = false); void DrawPersistentTri(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Clears any persistent drawn debug primitives. + /// + void ClearPersistentDraws(); private: /*---------------------------------------------------------------------------------*/ @@ -151,26 +156,25 @@ namespace SHADE /*-------------------------------------------------------------------------------*/ /* Type Definitions */ /*-------------------------------------------------------------------------------*/ - struct InstanceData - { - SHMatrix Transform; - SHVec4 Color; - }; struct MultiDrawSet { Handle Mesh; - std::vector Instances; + std::vector InstanceTransforms; + std::vector InstanceColors; }; /*-------------------------------------------------------------------------------*/ /* Data Members */ /*-------------------------------------------------------------------------------*/ // CPU Buffers - std::vector SubBatches; - std::vector InstanceData; + std::unordered_map, MultiDrawSet> SubBatches; + std::vector InstanceTransforms; + std::vector InstanceColors; + std::vector MDIData; // GPU Buffers TripleBuffer MDIBuffer; - TripleBuffer InstanceDataBuffer; + TripleBuffer InstanceTransformBuffer; + TripleBuffer InstanceColorBuffer; }; /*---------------------------------------------------------------------------------*/ @@ -183,6 +187,8 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ /* Data Members */ /*---------------------------------------------------------------------------------*/ + // References + SHGraphicsSystem* gfxSystem = nullptr; // Batches std::array lineBatches; std::array persistentLineBatches; @@ -196,6 +202,7 @@ namespace SHADE void drawLine(LinesBatch& batch, const SHVec3& start, const SHVec3& end, const SHColour& color); template void drawLineLoop(LinesBatch& batch, IterType pointListBegin, IterType pointListEnd, const SHColour& color); + void drawWireBox(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color); /*---------------------------------------------------------------------------------*/ /* Helper Batch Functions - Lines */ @@ -212,6 +219,7 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ /* Helper Batch Functions - Meshes */ /*---------------------------------------------------------------------------------*/ + MeshBatch& getMeshBatch(bool filled, bool depthTested); void createMeshBatches(); void initBatch(MeshBatch& batch, Handle pipeline); void prepareBatch(MeshBatch& batch, uint32_t frameIndex); diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp index 71133df2..8b18d4d3 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp @@ -413,6 +413,7 @@ namespace SHADE // Create default meshes primitiveMeshes[static_cast(PrimitiveType::Cube)] = SHPrimitiveGenerator::Cube(meshLibrary); + primitiveMeshes[static_cast(PrimitiveType::LineCube)] = SHPrimitiveGenerator::LineCube(meshLibrary); primitiveMeshes[static_cast(PrimitiveType::Sphere)] = SHPrimitiveGenerator::Sphere(meshLibrary); BuildMeshBuffers(); @@ -838,6 +839,7 @@ namespace SHADE { case PrimitiveType::Cube: case PrimitiveType::Sphere: + case PrimitiveType::LineCube: return primitiveMeshes[static_cast(type)]; default: return {}; @@ -1121,7 +1123,7 @@ namespace SHADE ( device, SHPipelineLayoutParams { - .shaderModules = { (triMesh ? debugMeshVertShader : debugVertShader) , debugFragShader }, + .shaderModules = { (instanced ? debugMeshVertShader : debugVertShader) , debugFragShader }, .globalDescSetLayouts = SHGraphicsGlobalData::GetDescSetLayouts() } ); @@ -1139,7 +1141,7 @@ namespace SHADE SHVertexInputState debugDrawVertexInputState; if (instanced) { - debugDrawVertexInputState.AddBinding(false, false, { SHVertexAttribute(SHAttribFormat::FLOAT_4D) }); // 0: Vertex World Space Position + debugDrawVertexInputState.AddBinding(false, false, { SHVertexAttribute(SHAttribFormat::FLOAT_3D) }); // 0: Vertex World Space Position debugDrawVertexInputState.AddBinding(true , true , { SHVertexAttribute(SHAttribFormat::MAT_4D) }); // 1: Instance Transform Matrix (4 Slots) debugDrawVertexInputState.AddBinding(true , true , { SHVertexAttribute(SHAttribFormat::FLOAT_4D) }); // 5: Instance Color } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h index 4a4d114f..609ff3df 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h @@ -70,9 +70,10 @@ namespace SHADE enum class PrimitiveType { Cube, - Sphere + Sphere, + LineCube }; - static constexpr int MAX_PRIMITIVE_TYPES = 2; + static constexpr int MAX_PRIMITIVE_TYPES = 3; enum class DebugDrawPipelineType { LineNoDepthTest, @@ -386,6 +387,7 @@ namespace SHADE Handle GetDebugDrawPipeline(DebugDrawPipelineType type) const noexcept; uint32_t GetCurrentFrameIndex(void) const noexcept { return renderContext.GetCurrentFrame(); } SHFontLibrary const& GetFontLibrary (void) const noexcept; + const SHMeshLibrary& GetMeshLibrary() const noexcept { return meshLibrary; }; /*-----------------------------------------------------------------------------*/ /* Getters */ diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp index 60decded..93e4be1a 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp @@ -23,6 +23,7 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ SHMeshData SHPrimitiveGenerator::cubeMesh; SHMeshData SHPrimitiveGenerator::sphereMesh; + SHMeshData SHPrimitiveGenerator::lineCubeMesh; /*-----------------------------------------------------------------------------------*/ /* Primitive Generation Functions */ @@ -207,14 +208,14 @@ namespace SHADE return addMeshDataTo(cubeMesh, meshLibrary); } - Handle SHPrimitiveGenerator::Cube(SHGraphicsSystem& gfxSystem) noexcept + Handle SHPrimitiveGenerator::Cube(SHGraphicsSystem& gfxSystem) noexcept { if (cubeMesh.VertexPositions.empty()) cubeMesh = Cube(); return addMeshDataTo(cubeMesh, gfxSystem); } - SHADE::SHMeshData SHPrimitiveGenerator::Sphere() noexcept + SHMeshData SHPrimitiveGenerator::Sphere() noexcept { SHMeshData meshData; @@ -265,7 +266,7 @@ namespace SHADE return meshData; } - SHADE::Handle SHPrimitiveGenerator::Sphere(SHMeshLibrary& meshLibrary) noexcept + Handle SHPrimitiveGenerator::Sphere(SHMeshLibrary& meshLibrary) noexcept { if (sphereMesh.VertexPositions.empty()) sphereMesh = Sphere(); @@ -273,7 +274,7 @@ namespace SHADE return addMeshDataTo(sphereMesh, meshLibrary); } - SHADE::Handle SHPrimitiveGenerator::Sphere(SHGraphicsSystem& gfxSystem) noexcept + Handle SHPrimitiveGenerator::Sphere(SHGraphicsSystem& gfxSystem) noexcept { if (sphereMesh.VertexPositions.empty()) sphereMesh = Sphere(); @@ -281,6 +282,65 @@ namespace SHADE return addMeshDataTo(sphereMesh, gfxSystem); } + SHMeshData SHPrimitiveGenerator::LineCube() noexcept + { + SHMeshData mesh; + + mesh.VertexPositions = + { + // Front + SHVec3(-0.5f, -0.5f, 0.5f), + SHVec3( 0.5f, -0.5f, 0.5f), + SHVec3( 0.5f, 0.5f, 0.5f), + SHVec3(-0.5f, 0.5f, 0.5f), + + // Back + SHVec3(-0.5f, -0.5f, -0.5f), + SHVec3( 0.5f, -0.5f, -0.5f), + SHVec3( 0.5f, 0.5f, -0.5f), + SHVec3(-0.5f, 0.5f, -0.5f) + }; + mesh.VertexNormals.resize(mesh.VertexPositions.size()); + mesh.VertexTangents.resize(mesh.VertexPositions.size()); + mesh.VertexTexCoords.resize(mesh.VertexPositions.size()); + mesh.Indices = + { + // Front + 0, 1, + 1, 2, + 2, 3, + 3, 0, + // Connectors + 0, 4, + 1, 5, + 2, 6, + 3, 7, + // Back + 4, 5, + 5, 6, + 6, 7, + 7, 4, + }; + + return mesh; + } + + Handle SHPrimitiveGenerator::LineCube(SHMeshLibrary& meshLibrary) noexcept + { + if (lineCubeMesh.VertexPositions.empty()) + lineCubeMesh = LineCube(); + + return addMeshDataTo(lineCubeMesh, meshLibrary); + } + + Handle SHPrimitiveGenerator::LineCube(SHGraphicsSystem& gfxSystem) noexcept + { + if (lineCubeMesh.VertexPositions.empty()) + lineCubeMesh = LineCube(); + + return addMeshDataTo(lineCubeMesh, gfxSystem); + } + /*-----------------------------------------------------------------------------------*/ /* Helper Functions */ /*-----------------------------------------------------------------------------------*/ @@ -288,7 +348,7 @@ namespace SHADE { return meshLibrary.AddMesh ( - static_cast(meshData.VertexPositions.size()), + static_cast(meshData.VertexPositions.size()), meshData.VertexPositions.data(), meshData.VertexTexCoords.data(), meshData.VertexTangents.data(), @@ -302,12 +362,12 @@ namespace SHADE { return gfxSystem.AddMesh ( - static_cast(meshData.VertexPositions.size()), + static_cast(meshData.VertexPositions.size()), meshData.VertexPositions.data(), meshData.VertexTexCoords.data(), meshData.VertexTangents.data(), meshData.VertexNormals.data(), - static_cast(meshData.Indices.size()), + static_cast(meshData.Indices.size()), meshData.Indices.data() ); } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.h b/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.h index 7719e4c3..8586b480 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.h @@ -43,7 +43,7 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ /* Primitive Generation Functions */ - /*---------------------------------------------------------------------------------*/ + /*---------------------------------------------------------------------------------*/ /***********************************************************************************/ /*! \brief @@ -116,6 +116,44 @@ namespace SHADE */ /***********************************************************************************/ [[nodiscard]] static Handle Sphere(SHGraphicsSystem& gfxSystem) noexcept; + /***********************************************************************************/ + /*! + \brief + Produces a cube that is comprised only of lines with no diagonal lines and store + the data in a SHMeshData object. + + \return + SHMeshData object containing vertex data for the line cube. + */ + /***********************************************************************************/ + [[nodiscard]] static SHMeshData LineCube() noexcept; + /***********************************************************************************/ + /*! + \brief + Produces a line cube and constructs a SHMesh using the SHGraphicsSystem provided. + + \param meshLibrary + Reference to the SHMeshLibrary to produce and store a line cube mesh in. + + \return + SHMesh object that points to the generated line cube mesh in the SHMeshLibrary. + */ + /***********************************************************************************/ + [[nodiscard]] static Handle LineCube(SHMeshLibrary& meshLibrary) noexcept; + /***********************************************************************************/ + /*! + \brief + Produces a line cube and constructs a SHMesh using the SHGraphicsSystem provided. + + \param gfxSystem + Reference to the SHGraphicsSystem to produce and store a line cube mesh in. + + \return + SHMesh object that points to the generated line cube mesh in the + SHGraphicsSystem. + */ + /***********************************************************************************/ + [[nodiscard]] static Handle LineCube(SHGraphicsSystem& gfxSystem) noexcept; private: /*---------------------------------------------------------------------------------*/ @@ -129,5 +167,6 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ static SHMeshData cubeMesh; static SHMeshData sphereMesh; + static SHMeshData lineCubeMesh; }; } \ No newline at end of file diff --git a/SHADE_Engine/src/Tools/SHDebugDraw.cpp b/SHADE_Engine/src/Tools/SHDebugDraw.cpp index 839a9b84..b04f8629 100644 --- a/SHADE_Engine/src/Tools/SHDebugDraw.cpp +++ b/SHADE_Engine/src/Tools/SHDebugDraw.cpp @@ -71,6 +71,11 @@ namespace SHADE //dbgDrawSys->DrawSphere(color, pos, radius); } + void SHDebugDraw::WireCube(const SHMatrix& mat, const SHVec4& color) + { + dbgDrawSys->DrawWireCube(mat, color); + } + void SHDebugDraw::PersistentLine(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt) { dbgDrawSys->DrawPersistentLine(startPt, endPt, color); @@ -103,7 +108,6 @@ namespace SHADE void SHDebugDraw::ClearPersistentDraws() { - //dbgDrawSys->ClearPersistentDraws(); + dbgDrawSys->ClearPersistentDraws(); } - -} \ No newline at end of file +} diff --git a/SHADE_Engine/src/Tools/SHDebugDraw.h b/SHADE_Engine/src/Tools/SHDebugDraw.h index 04504c3a..f3890e17 100644 --- a/SHADE_Engine/src/Tools/SHDebugDraw.h +++ b/SHADE_Engine/src/Tools/SHDebugDraw.h @@ -22,6 +22,7 @@ namespace SHADE class SHDebugDrawSystem; class SHVec4; class SHVec3; + class SHMatrix; class SHColour; /*-----------------------------------------------------------------------------------*/ @@ -92,6 +93,8 @@ namespace SHADE /// Size of the rendered sphere. static void Sphere(const SHVec4& color, const SHVec3& pos, double radius); + static void WireCube(const SHMatrix& mat, const SHVec4& color); + /*---------------------------------------------------------------------------------*/ /* Persistent Draw Functions */ /*---------------------------------------------------------------------------------*/ From 53edffebac0e69feaf190ab2594ed16be07b04fc Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Thu, 15 Dec 2022 02:08:25 +0800 Subject: [PATCH 042/275] Added (untested) rotational motion to rigidbodies Also added a temporary solution for debug drawing rotated spheres --- .../Inspector/SHEditorComponentView.hpp | 9 +- .../MiddleEnd/Interface/SHDebugDrawSystem.cpp | 12 +- .../MiddleEnd/Interface/SHDebugDrawSystem.h | 5 +- .../CollisionShapes/SHCollisionShape.h | 6 + .../SHSphereCollisionShape.cpp | 15 ++ .../CollisionShapes/SHSphereCollisionShape.h | 16 +- .../src/Physics/Dynamics/SHMotionState.cpp | 31 +++ .../src/Physics/Dynamics/SHMotionState.h | 50 ++++- .../src/Physics/Dynamics/SHPhysicsWorld.cpp | 23 +- .../src/Physics/Dynamics/SHRigidBody.cpp | 206 +++++++++++++++--- .../src/Physics/Dynamics/SHRigidBody.h | 74 ++++++- .../Interface/SHRigidBodyComponent.cpp | 144 ++++++------ .../Physics/Interface/SHRigidBodyComponent.h | 2 +- .../Routines/SHPhysicsPostUpdateRoutine.cpp | 18 +- .../Routines/SHPhysicsPreUpdateRoutine.cpp | 2 +- .../System/SHPhysicsDebugDrawSystem.cpp | 9 +- .../Physics/System/SHPhysicsDebugDrawSystem.h | 4 +- SHADE_Engine/src/Tools/SHDebugDraw.cpp | 2 +- SHADE_Managed/src/Components/RigidBody.cxx | 6 - SHADE_Managed/src/Components/RigidBody.hxx | 1 - 20 files changed, 468 insertions(+), 167 deletions(-) diff --git a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp index dd531708..2bbc0305 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp +++ b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp @@ -286,7 +286,14 @@ namespace SHADE if(ImGui::CollapsingHeader("Debug Information", ImGuiTreeNodeFlags_DefaultOpen))//Dynamic or Kinematic only fields { SHEditorWidgets::DragVec3("Position", { "X", "Y", "Z" }, [component] {return component->GetPosition(); }, [](SHVec3 const& value) {}, false, "Position", 0.1f, "%.3f", 0.0f, 0.0f, ImGuiSliderFlags_ReadOnly); - //SHEditorWidgets::DragVec3("Rotation", { "X", "Y", "Z" }, [component] {return component->GetRotation(); }, [](SHVec3 const& value) {}, false, "Rotation", 0.1f, "%.3f", 0.0f, 0.0f, ImGuiSliderFlags_ReadOnly); + SHEditorWidgets::DragVec3("Rotation", { "X", "Y", "Z" }, [component] + { + // Convert it to degrees... + auto rot = component->GetRotation(); + for (size_t i = 0; i < SHVec3::SIZE; ++i) + rot[i] = SHMath::RadiansToDegrees(rot[i]); + return rot; + }, [](SHVec3 const& value) {}, false, "Rotation", 0.1f, "%.3f", 0.0f, 0.0f, ImGuiSliderFlags_ReadOnly); if (rbType == SHRigidBodyComponent::Type::DYNAMIC || rbType == SHRigidBodyComponent::Type::KINEMATIC) //Dynamic or Kinematic only fields { SHEditorWidgets::DragVec3("Velocity", { "X", "Y", "Z" }, [component] {return component->GetLinearVelocity(); }, [](SHVec3 const& value) {}, false, "Linear Velocity", 0.1f, "%.3f", 0.0f, 0.0f, ImGuiSliderFlags_ReadOnly); diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp index 0bfa89a2..215c7fdb 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp @@ -203,9 +203,9 @@ namespace SHADE drawCube(points, color, pos, size); } - void SHDebugDrawSystem::DrawSphere(const SHVec4& color, const SHVec3& pos, double radius) + void SHDebugDrawSystem::DrawSphere(const SHVec4& color, const SHVec3& pos, const SHVec3& rot, double radius) { - drawSphere(points, color, pos, radius); + drawSphere(points, color, pos, rot, radius); } /*---------------------------------------------------------------------------------*/ @@ -238,7 +238,7 @@ namespace SHADE void SHDebugDrawSystem::DrawPersistentSphere(const SHVec4& color, const SHVec3& pos, double radius) { - drawSphere(persistentPoints, color, pos, radius); + drawSphere(persistentPoints, color, pos, SHVec3::Zero, radius); } void SHDebugDrawSystem::ClearPersistentDraws() @@ -315,7 +315,7 @@ namespace SHADE ); } - void SHDebugDrawSystem::drawSphere(std::vector& storage, const SHVec4& color, const SHVec3& pos, double radius) + void SHDebugDrawSystem::drawSphere(std::vector& storage, const SHVec4& color, const SHVec3& pos, const SHVec3& rot, double radius) { //if (spherePoints.empty()) { @@ -324,7 +324,9 @@ namespace SHADE static const SHMeshData SPHERE = SHPrimitiveGenerator::Sphere(); for (const auto& idx : SPHERE.Indices) { - spherePoints.emplace_back(SPHERE.VertexPositions[idx] * radius + pos); + SHVec3 SCALE { static_cast(radius) }; + const SHMatrix TRS = SHMatrix::Transform(pos, rot, { static_cast(radius) }); + spherePoints.emplace_back(SHVec3::Transform(SPHERE.VertexPositions[idx], TRS)); } } drawLineSet(storage, color, spherePoints.begin(), spherePoints.end()); diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h index 20ddcd42..f974ed9d 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h @@ -123,8 +123,9 @@ namespace SHADE ///

J^QwSS-6)HCA>jZqKr@rb_N@RJBX`%ZYpu4$ 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 z z`13+%z8mzJ^vE0>Ysd8?QH_(9hk_8i-Y6Wi-~T$^Kc05)UtuQ3n;#NI^1jwoNS(0Q z4RG0e2t#cYJ5N%-MYG_44hgY=$5dcYCK>gB7(8u^CpLUEzTashr#61+A2M~+(=;k# z9Y`g=T+_#qy#hl4Y2_cIFf)kFFfaB_vFy7-w93)Tg*~--zeTY0W=(AlW$N_+5{w{} zP#gf#DG0URG+J7)A8g>NQ9hftRX3U?SbkEfhgCndm0-pOwWv{Oy(=TH$vNqb8Hf_e zoI6UEcuu+HB<;_%8sa?=)TdobLdE|8&b$4D4qt{ZJQ(|D`#{DE zY+Wy{vql!eQ0ySP!GArO5kf}pnm+L|LG4|?C^B!xV0dJT%d&8>?e#~|b|w2it7)I$ zrC*%sf|0QW6MzO#S8MqDo&7!|W}&U%oMi^iEht{Tu5`v(T$VDrm zVUzfV2Z!VJ=N#KM(>Tz|mm8kAs$e8wymtb2M=;)>Xa8NhhYfga{FCA$QNSPwfz7xR zd^D73IM6q~=1br%5eKAMK(rV_jd`kDq7IkElB5-)HI6t-HffRymUD3CwLWsC_X~)_ zqV9-~b)MWcAk(){Y?I@81j^n1yjUFhTUz$IflGVR*G9NT-V_;t;K;84(yQdJx5-#A8-562N}B!sCw6W9V;>lh zE;?@M@>F;EdkY07(nZJh+-Kdk>vG_!@hv#0EZMiqxMj{xSufO6D}QmV0JX7XFxu^8mhGl9yPUmQ%FquZ z_3#8)yizD9nOqG_6^3#RNH+T6lm6v0vWB8t&BF=Aim7kj&~A}9`Tv-%zRpndK%~aQ z3+JkRdPM+6Jv5qK-eo9_uv$Wkb?!PLgcpDC0d=ard)1|h#zl$SLh`)`t)*X#`LHbh zM&hhvu9x*DpO_b;bkH@u>KHSOdD5I47#|JZyjlqdU?`m$d8tgPYfjk!)9I4~R9J1H zookHS!V(+VLsI(%qjY`fs?e=m|WIm6|JUJvEx=DcPE;W3IXwNG;GRyolrl8T!Y2ib2+q z(vTA%v+)sMzp+P|lyM1?Q@3aeUAu@7HobtEQJ~v}8TWPzjlb4JQIOeOnY}zcBDj`~ z($dzZM=HGYJCr+_&|nQ`nX;SN9ORv@uT~P59W9g1FgI|eYq(0%XqCo_earYzHg#ee zY0gd;!6}X3$M#97Ey{y3-lCOr_T|*yd*mEeoo(N)B{%ZAS7p*)>@wL6%AE}Hh?$j+^w|qS@+Jau7xOZ;8bDC`xt)FkV zb{iy>``{T_zkk)}W3jz&N4b^dxf6+UFv^Gx*9dw1_7jA31<*7;Vv-6kyj?qV(98Tp zNx})Of_Ps3N?M$d{Z)bug|0(0>EFLX2p8${n`>zPZ;~s2US?uT{ueTi4VVzSl|&nw zm5QkS8`SnaMf$4X;+>Trr!1#~1`9|^1=YX8G9pLNf~T@*kEO>_c&!ysU=IuNdE7VbPg8b-Wy+$Hn)~-=DcniG#U;?>8#O)Ws-ullpDt|+<>C>=&00AG>6YT5REDj>M zNi*572Us)LeSq$1&1h)jcS- znR)U4lh#?rAJg2(rurfu4vr<&+}5uiC{)3UD~W9b42MvLEe0XIBZkA=L$hixLyxnX zt@twg2(`W!HF0*l14WUAwhP15!{1?ThSSMvzb!jsKuBaR9}GcI^Xz_2-(;Ddkb)o4 znm4JT{vp}076*Xk3V3dKgjj9;sL&64^IF3`EO2Pe?Atz<&oX>@I#g=D!w)NRIFSG_ zu@=NgEQSthYlpxRn$|>ynVe^7L9okQQ4_CT8F$w6lj{vJ9e+lS5;D@jnc~00eCejX z+dZHh?pH;jO0Jol1d03hEO8R;dxrlh?sT5CgrE4Y1?%0WHw$7*o>1Rsbn}&kBxJ@v zCT7$;3Lm)8l36`i^4cCXzb!1<;ojVB@>+~84uyUSpZfP*YmGjC_o$6XPk#>g8|x|7 zXSWf~TdS}yc0Bt9EpZ3ue*^V4!#Et=ghi`&IOmhzFuj{Vj{azsizMI z?tsjO$58fVxa$XpLPR=)u7icRs!he9+lyxig0lt6yUdmjiyPUzVx_`jJQEjO;GO9g zN9cFKo3D}=KjO>dH><_C8sgjdAYKahk^~}}>SGp!oa%o|Xfm zV=?RZUs*L(K@!Dmu>3}=6zfbKl84{CJ&$|MG8RAKUvzzo$sDzOBdZRF$iKEw>3_L< zpL*ib>~rfj(?xNF$rveNl@Xfd<Y}A zZ$g|*IV$<9PEJ?1BJv5V?$U3DBIj@mMSTn$aNdoc7?F;kQj3nD$#)6>_rPkyBQwvd z+bdz8KMDUni(wf&qsj{}L?|uY*@YqI{29Dpo`(&5DFTsS%c>$aIejPprqU0njF`jwq346^6S2H(uNZ!632@=bpSSB*;UdHwfl48Eq zXyw4dG_JRsKGb!y-ZXFFKYofV4|Dp-Ai~!lmTRh8bh1DHaNgsse|qeFmQYw1z4znz zYUiyHan21gp3XKGbJ;KVbK#$g^-$WiUSIVKqtqJ9$FCK|7C7&24Dx+mXN`2sEO9sS z$qD3lXL!e{i%@lm>8HI5)ovT-kP65EyMBV`Jisy@r3ytnve_Xe%34Qyf6OCTgb#HP8a-J0QQ=&5snKj$m2T&UQ^q_v_J<oynlgLg;9m%^qX#k9fTi+sgq=F{SQ--9P{RhOay^GRz=?aJ7z*t?4F`zpdj zqjT9evB+*`y7xJ;^2M`Ic9EBb&rg)8%x?Ws!(mou8hWx?Nk)P{?f&)qt!8N_QWW>G3rcPK8 z_ANAMSZ(KzMZ)oKP^iUjMkE>1il!W=^_xgG(BR?6xf-Ec5iSY-hYW|}g2Qg|jl0M> zqq+hDtuHNsD#b5FzGHnabtZNr=_mtJVbBb(W8NdXQs`o6@<%RclXgV!b;E`@+%9^p zjPO4N$J;aU+-+#iwb6!d;*d0Bv^)*Gw&ysAW4`OgvdqF3%{ZoC zFjn3oeA=w6PSsI(T4^)V9N{@MolVhEvn9=td%FM7M%(_xasEnG-M%~(6RV@Zbk0A# zpg3Nsx1ywH*r%gFd-|=&?c(U~_%|gNNtu}(vqowM^PS-h^2eE<=3353BBD(shIWPW zpfk14)ldzUm>B9}&CQuKkD)brzUTHr$>^iYtKwow#h&()v7Ru>MzP<~8uf_#=*u6V z{`>!503OM$9Nc+`ke%*Pqbo1Lf{3Q&tnyih0MTKXu)2GSzWv`~AMbK((bf(AmH>iz zlI%+t)z-88No0+&;i(uc)xwCiwPa=f1ij53+zwCuk1w}J3qAiflh2n%%=vda$EWH( zIPD+ZftjhT?^a|J@m9}@+neGKl9%TjoN*F2Un-i6`_yS0rt7z`>((@Zz$P4zxL$NVx`^Fd+KSzdJu$7BRm;E0PgkNqZiN~j z*Hrwn_S-Gr8YEM>vGr*!3K!hHt#j8?U3G!6r)tnt-Tny-0(l*Q$NzuzKgI|SCsZ`y z)!I$tT>k7hM-t-V%^y}hJ`1~tA2M_EA{e{MALFB6y(KOv#six!eG;S1Y&#Jo0%A)v zU}uSRd@dtvgBkU|zf|yPkf|O*u~XEVqmw>R|5IR@pl6+qhcz!-W$tzC4|95aO3L57 z&U*r@)@NBUh8;bLimcSNDkq*P;@IDHA2={t z>e&UD7pLtlyDDCbdb%b;Tk>nAYiD=KhX~Jf@_wjw$tl)ZbC@zks zCTv?fHB?MVNl{!ld{~Q7&!R!uDRs*(5x2N)a;5}$`551<#N`gR@&=~7G&qZavek&k zcm1?SR0o1P9e((63`75*yHAOoG(LZE`}*#m zq&9Y+R`7W})>0x?lWyS(+H9*I69-E~vVW23ERVqWb{C}`>4-R!xO!$e$S6-;*s5y4 zT^La)$e?yqIXJP`&7)tq*>&HIM_r#NOP|v>bpd0-f(rtBfz3X@mxdT_&-Wa#8-xo% zWJc3FwQU6?Rvk3xonz<~gUXy#npu#>J-#X=d{Adb933ciFgdYc#9KV^X86TaTnqXA zbjc`8W}W5lYhbj}CjtB8x$Qjq`M1eu^`OepV5`k$ci5#xvdx zeO?y|(*@8cw2oW$1^JUA6jI6`?vD($voQ?Xd4U#$1Esaf6^x2)Hb|$xL@@?WsIx%nKdHexjWDHk88I=e#G(nsYalPiuaCg543gDtdk8f>8EY$EgDDwVF&>O4<0jRH+QR>sGXHHv-(q+ zO*8&$?JwJ&PNU+U!YeDTvp3TCR(L2^glLQ<~-{t)$8jr%em^0Yd<)$t8I%E5w9JoLy_O$Zy!3_wNj zzm!F7zbnvNXd^KgUshJ17X8=e;fS!z`uwvVga&A2N8c03ZmSg9x!MxHg>=S~NY18- zV(JG)z3Bo0Beyo5dsZr-Eh-?p@$kFR=%eC0a@ge2%(L!aS67VLij+YAI0?QPcVmN= z8ARLEGLR*dIVe6h;^oi2lkT=2>jni(0@6lMvg1{qM;qcF+5%&{CbOJ3-1HUDo))NE zm_Cic17pH0LFp+hP-p4BMs2>a0(wGoaj_Pfd7X|1YK@>D zfDQa_9VA-nV};Vx|FFxVuS?rfQyL*R7+xDtyI+_|{kOFIA~`(qH+Z|GVF0n}AYBbVj;6FWE-F^(AS{>O#^AosLF(s3Bl}5dBJ2;5f4rj}p+a4behp|O zmEnEpa~`=Z zrG_2Sd4IYnp4hapbr%NZEnNmWX>WEOQ@a&Cy8@{P{J^m8v{O0|ETW3`X}coj`#z2a z-L-;OFC%ydau+dh!}u?G6`8e zPxAFY9`_@Byo>$i4{~*#lZbvj3Fh8#P(Q}IM4dAuT!R7N2r8f%ls9G-YNvovBn#jI z+|DWhFpNCqo1L-lf>JDWc)i+0pPa`?3aMr_GK9CE-B1egTT?S~^>5US;m z1F7!<-pqE>RN~Hz!k%qISJlsFt^4!4fsB;5I1f*cUxqIqPlerZ8+`eJaqK43QQ)%6 z_$KhbU*%KKF|S9&Gu8Tz^J?RQvh`)hW^h*6Uimkae`(jMfmg@QF5z40ViFzO!{@GW z&YMtmg4fxAv;`h;*0`g6Db3KMNq`Q>XNQM&Wk|Wnz5lx&92IG(M)kkK#cs7e`RLvO5I$6y5Bd5*GxhEN!9S9$F|> zGmGX1{p|{eIbnZ&@%}TeJg8-jz|Ab%U>#-CE8oa`)SoV*vZDK)AZZLYecK5FVr{Uc z55n-{`P`>5v6|-6eiFb`nE(+&TDAP^$C8*PX?y?CY3L8AuiF(`^6Jr8Mrk`D)wemGyQDqaO;Kg+SQOfIxUc7pG^DlIVVr6#v4 zClwGWvYUSg4Oq*3kC1&_CPA^OMPp3Dp~%XZ4Psd8H<4++Re`*sbRrHo)%Q6k2SY z)VgHN{*mJItn$c5gg?CP2r>qbgY9TdpUB#jd|#HfW5JutWoSQD`g<_fea6n-#zqMiESF8#VmwM=z2SKrVG^7Vo5@!U~k7ls*HQq&*jUg})vI$cx}2 z0xj5^=~QjRWfAui3I1$jP|i7(_R_5OK&Ct-0=$PKL}q;d8-L=5W;w#&I#Abhl|Afi zuGWC*Kj~WU3O0M38VOM6DaxlZYSaxt5GE<$TV_@QUjY=N)?S+0Z*gHMw8p+yH}+X6 zOUvD-YT%#n>y70H=NB@=eAL2&0VdR(OCQL zM$21TyyK0{Pma*j0I@p3M|9xqpy}D20nyx5|0p)lCI&4x0OO6f2$1>3(?+4wV?Nxy2>+V9? zmUu@N!j}{Nz$SWu1oL}9AXx07jMAQe6ywsmZZ6HUpuTp8xYuojhlbr-f$_f!GQU>S zx4q9Z;}>z4cN(0n3Of(h3n&iGSdP5U)OncBUDcn-ASQNoC05M%jeK0irl;#>W>2H1 zeiEw^gB_AN`hF=R#KK#VFK?fghiGbm_y%PG2Lu6uBpcGVFmJA#=gQ{mxYpo{rpvA_ zUX8a&P!E=9ua#^;L=M91NBXywrsKg(0dqASVu~c)nw}2OzYa`sZ&q>NuFe`(U1Y5c1N{ z(fmmWTcT}%PeYpde%K*(D~UVLDzw4oa;XWLrHHr)K0%`B=Cym=_l9UBAPC;>dbOh{ z#Hz0-O*_}`mfPuhnF#*S89nx9gw$?EH`R1MB~Xc8D&=TzC!BgKTp$0pani`ZY@1P; z*W+ZJbXh4BtExZ1DIb;Nu`Ec9Q-%$^UpO)V2?B^g5Z@+#zVsHzqY43o3&5LdPeSXBQnW=^R(PYJ2dkw3? zcHA$#Y+<83D)S^%^GVUVk>C9BJX*T)!C*ARMbUG8YYp|Y&j@OFd1evzA@nD$Szv_` z9+kaa510Q7b!tBZlOgwN27X8la(_LTi~hexGAQ?9wd>&%&q9+A1dG1%2aD+qo~6MV z%U{!GZsa2SbW!+ITe*-`N`^HSBTQnYWjL&OZ0Gy#RprYQ9~E@(lAL0euWgv7&zgx{ zBz$oBWXG@BU=COkXsLFCTCJKj&TA?@MC@a~ezviW*`jz-4oh1Z9({`L4?)MBYXG5R zRbNY`|NSHTtBv$qu&Eb~SL!Gv>xsi|S5Vb0H&m7e6Ju^bm!?;lGqjPZHiE8wikYS_ zamvDUo=fh$fjzik!R$^D*%cqa0?{-a*(`ZQ+NL?_q?lz@~?ktmGLy5sd_4Q zB_FQ)CqMOa%e*&4A80EV#>dC^%QZSds|O=+yg4iL$a$A4o*0-}UG(8USQ+bO+4yX} zWYj$$eLAJ&gZ`_wgj`1%eR$sG==$mOu=%5sRkr@%vhS*g;oOXz)d_iF^Qmz3>E(lj zddM;u$UC)sODI9>oHwhz{|`ZvYLV`tn2YYU{$Q)NGdaB$3l7%7_7m z`YBt(7|sBJ1%_yaNvl-7O|i=A;Ipv5;W#N^8)1KcNu%wu9=$AIQxfEUs?cA1tx%~s zpu2nN=?GfmY%+ZNtU)bO{7ikn>vgQOccgwC6lyrP1^B{GJEU$8M;xR1aDqo9zq+ca zogboGVO%vV^9mLC!@4)dHKm+p>h?QzsHZ;j*CgU-qz#L-UyX<_-!3veqqCfoEF&u} zd7wJfA6jG5iO=OsEwS#z>jWIWQPVXp4-w@DrlBeC5vp7uBhu^TjOL!*j{iOua^ZVo zZstZWmA}r>MBUUIPl{YTlTUs*CM@+tpy@UK^5N~4ccsYHd_zT~24KF)Q1-@Db^8hn z))$I|C(BtOpY&Ax?^qO)IMt0 zK{ZhTS7xt4gU?)ffl~Lg`P+SsLZy=W!XjbMl?84%TD7YmtY(d&M``*^4ZT>(BA+C7 z_+_%_v$9tF(neir_MW81DFxl$5n&3_LI01YuKM>@PEJmy?3j&YPV{)_V(PKnV#;`=g@VdFhy!a#5Sh$i~p2j z=@RG^vwqrK2vS7X3!Z1`XK7eh?xx2CWG%xUN#|=CNh?S(AdJG_1HWeB@)%O1Vgevi zyt!MLKOm7%;P7L&1v{#aeOstJu#(=^|0t!(_+q60f*~ns4nV=gg$NP@z+al|*r`b8 z&k|U_N>To0eWL@j4G}QHdc6(BE>M;}c4cXa086V;St7?UCc5lW7=k3V4p@X%xae@xQyZKvdR7)-fb22f!HbBo<;uuKSRXx4r+lCISh|t5YFymUV%$VxW4zGM4&_ zAq4onIu~d?QT*MuksX?1gM(~Ig8rpC3+wAr*D<7P2CguDTn0Q?LD4skvuJ_RNh|_pWaz&W&uL>8!Hh^3|b(R1kFql1krve zEkMXUr#gdmBJmF>dr|<13S6rm_3qEd&~71=h9~Ub{4`=r31Lo|hB}_oKzLp0+=1-7 zTknM_D?k~Ltt8{pEu<0kB~{AUn{2z-qF$i=aN$nw3~$3asztkg?iKS5cX$*X-TM_r z7J`A;{Tcxy!4{!&e~swl#|W)1RpP?hs>^wFT=cMUA6}db*QJc5x{we1se9RkbguIJ z7pMj4sVxplVhC1BsPptf>?aKu#qt-SAbjC0C2Cm3d21dMin2x54sU` z$^<*{lZCFY#R%_W!J zEu?&o>ozzqG-d!vH+fv9t|y47@sHJ^@p)d#vE9-C{7uAp^;1xd)vzPy5CgV2`@HU+ zDZGIA98%ZQNI&2ZJ0#H))k_=o2QyUccdSf*GMzEJH?OfM20MV4BXQ9H|6d~0kdt{^V_G9a?{1pz|8N1w+)qEe{ymRKY3@juto1e*Z+@#U@|$QyY!acc zj)uX;uHSj(Yr}9)^r`PpBxkkJe-oDKfl$uT?PH*8ai_4nN(mE3F!bo!BPo3GmMxyk3v&Lsb0$8&Rjl{P% zirC!dAwk0$Fic9G!Er=1?b-v-?L5;PC%IaziiMKG1OV)%0XOVKqt+;yjJ(PL0`^s1 z`;Fu0pK;p1A;?5$g{#$M?c{gJQ1GDDy=6sguLuzLiK9Zq+{p;Wx5sXK}u&Q08Kw!PPMq(!WPp@V;yTy}}sKi1E zCL>-kGM2SJyXc$dM0T2MjP4L(w)=q?eL8GMMf4$hMAftsLa2xlgzzH*D6d+bdQE3hu+mGxTBM(WB5Aj!zn%692GWZy`8(MU?=LdUJ556Jk{(iSZ zCF{iFMVSi66`cF+_Y7U4O(kAg3h}^mV~+2o;Rm3*zE?clDpQZ8++}dP_b@7Ci@4@I z$0LNN2oV?ArtCYOE*CnN;dh`I3a5Z)cWE3xT{e1`4$;=i1y4fDiD=CK3hRd$PQQ?T9Tj^%!5mI$my3@<`r+fNVA|T^ zD8`eW1erYillR>_{itHg=&N=NjLAu8T~+_~Ym-3LuEsR{_3ePki@Ph?twSU~akH;} zThg*91VI5_IjbtR>dz}!txcoa8!&IJGR`LRy7buyK3dr@dF$=Wi$R*EXmV(-AIvQ= z#zp<+X2b$|73ee5)b@2LdC>EoiLdE+fnW2*t97!$v-8{4agwL=hQ{=en4hx8yW>%texg&^mzlgrpCh(* zZp5>slxf7mR~I2o{}`GYj0U`6>jpFJpGY}W9IRc<9C>z9lZamGxS<%kx;uo{S{}Hi znV|i}t@L&+x3&FQwkh8uE=a^eBxnm^>UIcGK!Rln8-0dxp06EQ!r>dX7B7!9J+{q; z!oAy->be;hwsHHu-aJG{2rT|=frR25Nze_qQ5HhJ5MbO2 zy(N1bl%{?7c3g3k>m{&VdSY`Rco*;TiPPSLm-t4YD4pFxU25_=Es;KeKR;&%%uSP7 zYOun=c~IWe-b$GB&*57Kw!858AZlt;Y~W++$-8hC1+@}c)TjJV{XydK7@FKYCbR)n zG(5K{mylsBXRjKntr)LK$z=lw+F$>rIu$Ui7d*4^$%yme-?pU7=xm7DNK*Q{F41M( znvog?*anbX)#c|gc)a)?=w1Up9*)WkfNA&={HaGQHi*k>{mT&S7SfYxD~q zzdQe%lXhB&yLs;!g{mn;YoGNOopmv?EwOR}tRd$H@N4v`A=r3spc&GSSaz_z&yl)L zh@N8Q%l)fD^n&#=K-@uN1{1`ZjpUH!R);l#!30T1dLArBJb+Dl6i^cas)T!O$yQY< zL6c<8ZhG`|FJ+;mWq|(IZf&AG0mxHQ8n2@!qlk`{*#yhpfCfbGvsbcrA`N3jq!S&u zO7RYRNro~jvdKS4Uney)`REC;=x&wsk)U=IxX5x zt8j4#>5b-(*4Y+U%Da18l)ZPKkburU7V)N)7-F})Ax!@6EQhnXX-6q9k{f*@ZrT9; ztnYA*Z|M*+6^mbCK zkeQl&qQ-%06&GIsXFAXx2k(-N9{Y@DKfN5gA%)nI>om-m%;@K%Q1|)O=EhpCu&?0??maxkOb9mljIlRe1PZL3fjrO~#-Vp8x$s*{^!xRnO()7ggk%+9MYOZmn-ALa9~*UKeZ= z>HTUkB=^p9$9_{43(*M^S^a#`MYoP+ypXH2jd$#a<;$P#oJTb7s;<;@v=ioZ-EgA{ zt7Pd+pmr2kmBjXEEbA1HW`pzHLRwq0g?j-}qyyvbVo%41&K;*OJ9S0)wjZw58MHDU zpReshv>$J_NyopA-~3}m&$i50;#x^y41dZ58XW5n{#h5DMKWhn`8rIJ{9_Gkq4#&)FLyvZ*6BhTr(a z^}%t+&c#6sE|=#i^>GEB>r~Gv5TWOnmgG;aj1pK!Ab>Kk?J;q(pJ}ri#>#_0<|SUW z&b{fOJ)1k;GVw@kq4#|7{BU^X$#A}EF?nRzE8VE^a#QykxzMSC^>Rr5$o`ie-<|1* zo3BlVfI-8Gvd+p6PTL5G*IYijbJ7RAITmZwcoqeiEJDN4&MaTx#BfAlK1$(g@p$zzZDopHbmn?RUxpas zG#gy3#(z#T88UZ7ztfx6_lsi^-3Yh@NE>~Q5xZK(kjLr*QfnYK_b!|#UV(q!aBpk< zTM4N_1SVSslH?>)QebV648T^oyr)ma({vTOZqKJd6Qd})IYBe_csYtKFdiy6FPc`o%%u-_!8XiZ^61nF^2e6;KC9VT#2Ppvqg}&CtLEB=*BNSaeJ&Vp z2qq-P)w~Y>OzJ>mZhP1|M@x7!-4i8ZKOGuf6ig`J)#2?9)@{te8SD}Y8H0&fX!r~ zFMdfW&A;tjE}~A!s_V_4VnebhShc7DAc-&rLc>Y*Uy2)CcB>lOvi>s;2@pEYK&E$* zy;b4blr7y%#B9v4!MgQw8-G^zJjfV(8L+JLRfzq1c_tr-YGmy2vx(4Y=Jdp6{pYh| zGuuFi`JlUb7gpKQWQ@BBUcy?2H_wf{TUF`nD*bi%E%!*G%rdO!_t4wCo`2zN9!++9e&j&?A(>d=*F45B;A-b zmPFy~L8U2U(i?ns!jmvoYJGSLL2}^uKi~ch@9TT#*V7h|`e)%X#m)Lo|I4O+MCzZp zFVpXUO~!aCPFd>^Jq?X_K!}T=Oew_S?(nnoXGGrwQRx_L+`Cubn$Sb+GQqa9q$V%l zwRBJ&qayFNg^_Q@Tj zzZGue#OR)VWjAQ%X;to;QeF~!SVqEcvtxN+oXRCnILMdkBUv+8+bgsbkzm3b@ry8< zpq0$%<>Oq1W0@!}{gsk_)YA0ifJ zZ56yHcfD!huBMcwJE;imOieo&kDa9G>o?}%sRsT1&#&n}Jcq44LBH9NTZdbWWO?SC zx7sic3A%j{SNI$8ttwkoy4wfv@u|3uOR3L+8I1yXa%Q)D!Z1r9<{qLC#zF~dSU^x; z0Evp+JlsC9K}2sR>|Q!LVH0Y6*LLE&8Y#MtNC%~b-~oxtHXFk{rE;k_iTttgKEVOCavePyY*e~B#AUoBA(7lgHYpL$lsw* z_wdYIZ@WWdjyuXHd?vu>^ecQNClgD1;+`O7J)-L|q~135U|g{I0&gnau?Ro#&oel* zPHi~v4H*bQYB41e{^&oW=ggO}+x}8|&Ld?PYBxR!D%St5EQd{JjNHP1y)}ihu)NG+ z*!X{~8Zhb@xh_E+l$mIaS|t$hKs*o?Ab~+%o!dx2rZY^77!d3t;-yU|o@OM>8Cd#r<-d&?W*&{(xgpFx z1Qj)*FE68LHs9K6lzmK^c!;dS4Tb!IJiX~GfytLz(`g;c{~{CMnr(qZ)5x0+zh^*_ z9!t4e(P%Jz)X}AScAaHq`$cueCUy>61cUnnvRbi|Vwp`CLD$%cd5Mc^CB(jxg}>(k ztaLM`GPmY(_?i9Ljop%Po|H~wnSO9dO`U6I_1m5R{JW0RK4KHa)ph5IIkAqn0%`b0 z&RHTUI~qA(rj_jDKghxL19dLxl95InE5tb`&5^iAf zV$Bu0`O~&LWFHfoP-QiA$d{MO4;FZB@R>NX*j@jgK$2&X03a{?Bi~9OS0ffq8U+Nz z?MqZd<^_r>k|!W(fT~m`yd61zIBjr`dkKk*Eqg~brKfTPCN+g5HLcAMReA(-@xQTZ z7nVP3HvDd*VA%KhhQq$&Zt_P7V!Zm>>jTovhY*E$+-o#_i^1kNNcaLvYaK?%jdEhH zqZf(YSmj6Tb~eNi)5zn!i^MWL!TOamAvh$C$=@-c&x5u+-5~(iU^W=u2)-G}Y<$Wp zaa3CC5Zxs4E1t!kNPMkRzb$O@4*pi0Q$p`|!3#qiB6Kg1N~oAqe}ZL=$WdVY7f(bF zBL9RmNmt)X_-=$k{`h{p-!Q%bo>P3sW#UQHb}Gx@1d8x3OScl=?*KIe9+~ubeN8fZGgXPdA_~S%_UYV0 z@5j=Ia}JWqBSH}?f4zQHMmqXP3Se}D3V_arr!~U5g)=`$&leQ#-_^UKNxczgP~KT= zIyj*2oaZ2nP$Cv3;J`-75aDR0@a7>e&J)5?kZ&~uR@_=2K5}v!+$B6(dweM@ zVn3PTnc-i(VzZ*rv&Ffv3*Wn?B|+;S&aNkZ+21+1bM7~GUoN7w{7QFtc5rrA@c1f{ z*08E51i3~!s*~k}=hWHh1T9|3`>j@$>#+pcDgz*xZbbwvZh6{Q^vIP89jI5lNKy6@^@=ArsbGXpJ_g@!W`+$O9U?Zq=O8%5LV216cwu4NvZ@?Ye?po6JoAf7GIT%+{{N*R$ZzWsqSpy()|+5cn@8`M+0pbe|^dZFbXuyBY`p z!vl2xEtvZ%9^XldVP6kneicMzZGgW)?Sqo%HCcG(qTVarAw{lzRJ?Hjkm1i|WvcWEajQ42;U&*a1>5)uDBr8k1(gfVYi=6OprJhg9s zDWetN1J6SWMf2voa@92E^lX;X=M>fKXbQ^**DI`vxT~V`G|H}UojCw%jqkl}w2YFT zFP$t@@bD$_R8|RxRuy)+j%)UKIq|Z~hBF^TG8J8|&kn36pvAJ1qWpmThAs@fmWIM9G^|Px2H` zs6Tlmt@_89uSKeJZ8R6Ruwyv35J=x5CkuiR2bq6Q^^e@L^y2kSWrQVKWb z-5{px|CCgS^%gNrtzLlRwOzWc2D`YL7yhj zQ38VO0hziSw3vJ0!m_IlpS12$QqX1~i=rYSApW%UVVVva#x8DDT9{mRh*|1y$2&rq z|E87DpWiH>%Fe)udZ6S#Pg~uRO{-TjqF|orKBcg( z$y^zIJ0(faSlHeAsAi_h9EZUz{2VkusjC`P{GduTJ;=BQrZ)nlD^P%<(P;LKJL6rq@lm0Y*_SEZXOSmj5ij2lHt9q$`iucLTKf_{`-|c% z%)l)~^Tg5toDBMjFB+6Ppz{UC9vsy;@Ku<&zmy6#WE8VHA>MMmL*})l6%aj`T1ceb z?*X#3FQRvo2ip+t1da{=g3oUyClGsz2OA(Q(~}0oo58=SLOmYhmG123*)%(ilYPP1 z4bi;jgdOD+g`T4|Riv0E_@5D)KPIEkzC{FZvV>5<@J<{|Ko?C^lD{!fdRIPyV-y(z076AUO4_5jHNs zUJvCQ7go*&u#7M80-;#VpdcEy4Gclb#{$*-(V%KgFo^WnE^16KQ1E>*`l!A81SLBr zblU_D(jY~&uk#4Q2lq^D1|v(JDaOqtP71WzM}PYK_3X=^TMV=Xw!`QP2!5|y*M8KH zz%5PLuo0Q8&!m%5n>CDDOooS*kQkqm*p9)E|}Y$K6hAQ_?6qMmD%(CNS03F(jR9`Mk4SH9FJ z^xKY39`3?Y?o>Y#!8-R^GUgv8voJ41ArE0t_Oz)qKSLB6C4s-l0+E*%<6X)yygLkP zSAA0bUTS?24G{QKlf&3*u|EAP0l@>RGR4|R`PrGpC=@=yDBaq`amwH(qldqgR+I)1 zy%snWl!QG|k2wJQ>&}Q2SXLHT6uM2rvTc6b;|~l0s~p}R#XDw`D2xb+L784ZQWfA- zHmW~zD*kr+PQ}J^UF}0oT+7O$#9KY3R9=tVhcQb{z2#HPV>_PoS@K$R^KAWs`%esU zEbD0@nbpN$0*`LA6FuI{rrThh)R;1aPNxz|_B?4+KY?T|0Z;ylhU!O-&iIX^0$dBk zw}WIVmS{ zgngNm?(fvy z;^rKj1mwoVcl|)*Oj?S?5ujWb!_8+)+U}E*jc%{dwI=*Z`}^Q6#L-LihTEcB8p;Zf z4+c1}{!Rd!+anAyAP^ibJW%i~hy}fvkuy^Fd6uHtq%S?vDn0VX$st4NxF-tSYygc` zridxzH@-VZ!>2wbimTaTUljeR5-*Adfc+5ntO|RjMM1x&E)B?W0&Lruz|wOIC_p(P zAgw>c00(%(GeJP4Wh6gP5O@K_y0Jn0IMC*kvdj%nW%EU~azK(Vs+p~{SJ;P9^w7}1 z%FvuKu7y1p>ia6&O8Km}#De#q8+qHi?;5Y3=LI8*Yj2Ef*_pCR=_GQ6ddO&E8jy0A z_lq6zu%zJua!*QxkD?DS5MA*LzIrFNi!=z#vlgl)2MKgz7qc~@XBQ&|q=Iq$|4O^= zv)Mx?VDNV#C+m(O2nZM<=J~Oj(>Ik=?NSZBzx}#bg$UnNH)gyhXe&qYmox4QB5>-x zLK+yf1P@H6bV;Isdm*hRlW|%jKiJA|zgEK{(YU8HEI7G8hj9Q{vX+Swo(*q~(?=k= zi5U#x=(fub4@Sf{k|6A!DWfu?Qujpb1`Mj1fy=_VDr|(!T3c^tL?iM>fEbDtKA2AKd0mCF=9Y6LUuW*7=zNCRDPE$_j&t< zdqhS3($ACwh7D%qt(tu#Hb2J%@H5mWG9VH<*_(O}4^=MhT+k`V7EImyooI1#D&xvw zAQ-yr&>h6gQAnKHcK)0+Y z4=mq5eQ6Hb@~w%>_W^nQNz^7UWqy~*fW}rn1gC+7R$5$&_X&-a>s%GRSNb5~0~{Fs zu>@WmAnz#>M1&I>fxuh7=l~zE0BTTXDK}d`JrW>Wfu0Wrb>CA=S#~czMotqUY@u9F zGf_Je&&Ci%XuEgRz~uR8JUJjvmqAKAe~#Yj|H$zE0G2Q%0E*kgg^uQDTCJ_ErfLri z1Rh5vUV~X(vx$F50Xgx2W9FZBJ9DF?0yVAAA(*b=Vm4mF?zkEZ1?f{{dObpY#U}>w z@LZDtL6OZaT5z;Ud@CMA^7D_YnHT`Z!X%VJspZ*xT%sU*oc@hf)F%iT7$pn?`Zs_X zAn1<$YKBsQhy!a_7A<>#K2*Jx{!H>z=7D&$O%KTYh9H_yB0>Ooq=1z;jBW{*H#e%e z)S_kCSglYnfj@e;QVvd#Yz5dHRU=o{R}EYGlt`Ni-3UvQ7!@!kLZ4#mQ-9kx&6XNQZR{93p_x6uj&V|FhfaDsU>w%#=^kLRZcKMN2x?AU;;$gyZ63 zm419&Mn)feL1~E}@Q{aRI4IMC4cJ{RTO$C{Pkyg(Q`fn;Rc2-BarB--t8m@wIv{|L zkqZD-x2vRGc?arR|BD^pIWU}ixan;f3%u^+@A#C7+Ua!D-Q^uw&vB|>Ldv_S%ko;PQ7juls85o0zNH8L;~FcJcF%ZfBMC$ zTKJ#q+cr1A;-c~d4ihPK(CFX}Rw&3D7R^QHIx?BIRNkY9pUkE1YH$Kh$bhf{DaBOQ z_@9B{5kRm22{g1Z`PsDGhCpxDrz zlFO`*m$&KwlqT(4g&0nkF9X=1e04CQ8riIQ?Q`2`N}DJKb0 zs%im|$96kO3Z}_;4EEZnG((6#5l{;aIoO^lKvME#$(M!<+L?BpZM>iev-qF44@`p( zk$l1Q5A~OS%#JV!Y%8ytcB9-4+Tn)>F((%Z5P@1$gKX~Wajm_=qQ&;aM;Ib1)QH_v*c9NgF03;hEJ%3iYx~IoC zh*ihNcHKAjFxIDW&gu^U)C6z#4>4i-2cH@T+SY-f@lWthKX^(?FY8nRbZE@(Yqaw3 zE(nl&g=LiVq;?NO&cv<2P|;r<$;E`PDIK=Y5Znk$%z251fUpQU6ac&XBbFa}cOi%k zpk88ET>jSBIe!b$>MF{$l$FYi@q*wlh#3&lKb3iu zTUd~MsbJ3Jh|h;B(!u<;pAuo{OgbO+y{p*ON1ktFS|b&*I(~BqW5*O7 zuW*eHQu9};n|S;o7s4IivVyQhgq<37;IOlQj8_%6S1Aq%1L7Nt{|*l1T*#h4UVXX< zR3| zkM|ZkxfDSxu!Bb7DVU0VeqC4cXeemRlvvj%7}BqW=W;1b%e)ApIuDy~2=3UyA_UuA zKGMQ3I$so{Qq>54y`K8~M`!J0sUZRf7~R=Sfmh z3miXSYLaMFC@}ETv#DJQMe~dNVP|FeP*!7#bDI-nZoE*S!CexmAn|yCC|LaK0}au` zy{6&ln^oP%aU>o<&p61#AwfsKxy4MFQal7-jL(v^b+3ugm4EL0D!VZjX;+-Dsj#08 z0hN}9nelMb0_})a8JsYnZpcTXWE|u0018p%7(lRGS{&7N;wy7o5wnQS75$n`+&M1N`^DsM#QSYd?6Z3Mmq*+gyo=5)Zx}_)`7Hd z=25k=4#+bNt@)W#DAZ4|$k`a+91yMqt0m9}O(@=N1uOIOOvXV4+;WYJ+L&6V0w4ia zt63@3i4%WLjSi9C6u+|`9_f0@uoGYo>^az1Rs2(nA63!eb||y>XLwQ8zLlph*iZfI zLTA{1)ls^Z?Z6;xv00-G&SfZ3XE>6J&3~wcenCL8l^d#*fVF#yv{htA%cBF%r;Ruw z^qc_x_|WL+ujEiYjcMy*+%z_PKginw)q;onW-a^{=Z?I@PRfvGVN(4SNOF-GuRQn% zPYPwQx3>i!zY%ld9@7vx5>kpmie_Le=yxDCaSN3(|1f|~L5q3u%b)Wa75{@a>bo4K zLS#mSK)|X&E+`0~?I;hBY&6r}RYii={;Dx#M1_p%7{UiAyn|xc$*H}2fqcvSZKc2^hv?NZmUc&ypnfGX z7E9vt#n3a&<(v*jO-8}~uFf0Y2S8Y3anmqm=gRLRxOodqJBL8wTcb-P_0(@4=`ecc z(D0}RYDI>f{`7c)&h#~U$$snoOddM&>!s%vLahb}oC9y3D{IoG5RO*(;6Ng6r%}=l zRzULFt*p2M2Gav zr~AZeLgGc;&~LxCQK_;k@Kx{t*`uBl0-H<3mx$2&aso~;+^qbVrzQQsrdezCyH6T4 z<$|`#-xo84(e&!_As|s^EW;?|oKpM@5%k88ihurpfF#e(CQW~#uB)l|@n=h(_oss9u=HH744-csS z)9YTZ2~Cd|*0OEefXvxyEi$p7@jW(EEVnfuUxm}O{A2LYP;1Yy;kkeQDhWacy;zgb zgcq3ueC@hY5G8RnHkIzJlM~@OitlFZS~w#a^7s`FxqtGSRHMz1+QZRkqcf+!|7i<_ z>^y$P*XOa#uo2>rrQm+RG3aV%4#_(9@*_L$NTt59p8ho??>BUqX4~rAHr`$(dMjLH zdvMmH4Q}GYxEg6?f0X>u$Clco1X|nW{^4H_#WAJihvW*7_7~r$zO)T}*??6Nj5Y1KSJ9ktR!k9ru z!d|9iAOB&=zM%A_CYe3B02i%ec3s%nr&;f%Hb$9QAaSU4X7J34UPmGzCq|3lzNsf- z2i63a39)7-l+q!5XER>1d}eIg$OQ=X9}tAJZeGGUExDLir#sh_YMgCbSEb)@5EX@< zCU2LYijn(kcrcRq|giq&_)RL48XH;L(;@B zMFWle9xkajUN|<>e@7k2cZfxKB$Iy|CMq~LQFA9+`tCaMO8vY7wY>6*YHerkb&Uv7 zG7v%e7z#U?_cMOwtuGn+q-Gsk8yfuhA_-wQv@VP1H=KQ~w(y`s((XKCKQ$(jr!u)q z4zWKtBH|}aD9_?N#*L^;91{P`=CjYdN!qX0pD_CJT|Cd`p+7&)tF2b+Sua0~EW+;@ z|~MD%3psmYpOxIBWbh43NgM!V5Sm2g*inv`mTEWEgGp{`aVQ+ zgE7`D*dDWcABOyB7d0Rm@_^G0-Je}$a)vkjtbvmsKjR~m8ck-B z<$Bw`0`SOlM@UVrb!$L3cZl%vazeb4bP}z_;*@yhZY#^v{^U0ETaB)ictQl5PCBx_ zG$yo>tQ%iXWZgWvj=7=3>u}>qO^T)h)Z@}c@u~g5Ym~y zM}Pb-x!B9W z{z#EIh75OP-?#n4eH+r3+4B!Q2{-MpZ6(Z!q!tW^YGz^@9x4Dc1dul*Az8QGYWtU* zy!?*Rp`iOQ+w-;VzGuIX791DBKy7Fc5tulo#n6(9peaF3=S z6FR=8 zSi`(?*Ii)M{6i=t`!IUSr)Bi$?#^k!Z=@zpCa>03?Nn(*J!gW(P@pjoBnpVn$#eZR zox<I_qf$m*}*SbtTbyB5h_6w%u*hPPj|Ix`Y^ z55IjWFDigs6}7+Up8Ou<^PU^mMhC-_Bn=+95GGxYPw^icX1E$M)#q@Yvfu6vtnO9b zUnQ-Qy2$pIIg<&wKXw-;hgBP@yJ9 z5TwO_cAaNx3Hg4*V3q2|JT!d2&r}VMUWV~As2K}#9THtiO;fDd5w1&N0F-9tI^f+D z$1fhOc|lMQu@l)b41f0LnX^HeM%s5VeFN25 zIOu7TA#sdJn*HuGTu4G{u%p?lTuVCV2Wr6tjoO@2kKv-Vbv(m;PY0r`klXm7$~HfJ zTvvs_qpbTwy|LxGDd+xeLvNEaHBX=J>f3C{;mk_Wl*o=-*W)?|b{&z)UJn14Il15) z)o0F&_F6ltYFmTC8xI-;+BmU*Zdhl{6C0&8jkdB+n(QYL)vTNV`*`WVu2fvU%4iN~ zumn?iOjxhhmxtd^VtWKmn1D8z32;lxGg!FNbyQT>U@0rc0`LIxH<_y&Y_9R<-k1G2tb#mO$pkm3#3dY01?owAo~=xz6zE;|TPfOXY>ex-*V|-q5;3U8a;=@9 zO+>fo8?245Hw{y?A#pU#6E}7Kore(I`nGj2i#$tzJn7NtCd%Q1INwzbw2aNJk3SFm zaXC6CJK8<0TN(4e4}e=f7rh}k^-B|)NkN>{4K9_vWTX~LASXL7K!|5~aKO7;3t)Gu zYOq=zSFk}c%mi@ZOb`FUA3!PXTiuJvCAe=ch-2{Jp|*C1Q7o1ncjj{n1Q1>ST;AgM ziOl{$t~Ovqg*K_b9;;4sW1yO2WQ<1e4@sz{SOrC36a(R;0pJAT%G^LDj1EAFp(_5` zU=)(>KWI(sKKDIE%ZAp!)XgRY-9sG12G}Fl?(kDek*=y4O3ce3F6q|==evp>cdU~DMwq!VyQA@S$i0|$G4ifZazQ7v@!BR%&nib_l9pLtC)EB9~sOROY) zPde_7cSpAD#vg<_AuS4qUR_ZGSHgX}%m!VN1NRR7`1|$F{TIy>1UGkgWF}U$RaGne zZ^w>L?cG6{GdD+aK2QD=BEs&TTA#1VhA{S%9vK&8Ym1zw?X?^OA&q2KhIRy*e^2YH z32f0cA>)jDZ`${7Qr~1$q66BAU3rsc)uQjFne*_704A(|p-m2crB8U$1=y|}Bx{Fl zPc$vlHv{A0)(gyVSWInOl$AhiQh`w4xi&XGh0)SOF%bAZa`W9ISB2Cb%)K#0K9l0pFBQlr3j9>V#0`-E)yQEuIKbNBRt z_pn^Tr?|}iTU!j5!i?3=MPvNV^=3oM>Bom_ht=hV)s(h!2m=leAg<5DX*GlEx-JY* zaaUo+^t9nLxtr?+p@n}=bkQmpDmmhjNqyGRkNju;YPtn$T{GjB&4;mzsc-bNEXr@; zuE`2(DNG$D=Th&|K;C4%Q)9=G2`Qj%{k|uwX5D~oHdNL*4zVdX{~2d?VlvS!0Ei!B z+2K`~M0emp$W`K|brH0+FsP!v9SpB1F(ep)%#X7VOd;b(xDq%l8ns=M`Fh54rRzRQ zzpRY`QqZz253NRI8XNf)YGLJDs!{DyXkB8as7eX0Yav~~v3isog*>Z;FuXLFXk>Hh zHx<7>j+R;lj&tr|nW4AnRu1*8?{wMX*DIp=zMTZfw26q4$s0Dz_wU^s;Y`OA#yWR6 zVn_FmcG)oG(isyN`~^N6afHN_DFR{S6Mzpl-xFK< zeVSeo{kEqw@@UOpN3&81MfFjr+lAJf&~msRWdsPhXkCA zAOR4TZlr%03Q0na9gcIzYq*=6jWC7GwW^wO(@eqD^<%On5*l?AY)+>i>JS&ia}61s zWUbx2SB0g#<90Y)aVSDB43zhea{gw~dd$P~Co)P-Cl-z&w z-z|k#v?ckpulAdDk8a^u(qFuG-I)zVZ+IcR#PRvUJEckg_M@H-ykA9p8X;;q(*BXH z+ZNf%6{c`t{GkIS?hLOFz=4^*W7|WONby|R!);A}1G;vFsz3$?A=PWMh{(7X`~b!> z;DW_P-se?-$wlf($XUow0Y<_<_ZLECRB}RrT6SQ-^Wn9+PnAz^x*p*HV34XT1;n5- zL}aH~PK+eLRg41aL!^9w^uC*%4aG;Hju6GYA0QtH zr5rI&O%+9MwQagd>$>mwS`0$Nhu$?;RtW6M)Ok{7iw785sGi~6Mp5b$GiwnBEN;Nk$uH}6-*&`&J~9YWhQU z4cDFf)e2b>nS7Oe5I^OtWOo_DZh`_dHv6o5{1SdA^9?boo@5Ss)}eQ2T}n($3IVA5 zd$r}+UhD~M;ogz^oVJ-3is$^I3`JNQqWEYaF817nt~8eTWOAs22p76;_Sh~qc@mMZ z5^X5=8@TI@J9QM)b&fUP=K`Ak=)mgGouK<#Ne)7IFmu*hx9t`9fTZAs%E3A);2&PXoAwSbGHG0lVU=C%QtWT=lZ@R|Mbp7^u7% zW)uO~z2O+t|9Ee%NuU7$Gw21iS_28E7rxW0!W#t(n^SQrMgnNQj;2~C1^mZBdY!k3 z$@}PF7YvLL+U!AvrZW5Y$U$4Vsp zkmL|fkiFjvxy}yG{At9E2o;< zsu4`BXaP87AfN2&G(z|KxD&P5)dH(e6PJHG6F{e>WbPg)%gVHfpN}< zy@G%+th#Z5c*rCeD#L+VMCvSsOiWzsvqD_Emh|ZM`SmBnGVei4ii>3-8m0JsMf9wU zrPFGMz@Ni4angSO!1R?Ff%W$Xzw*Rg7I&xYkWJ&DiHgXTR()fZFojT`5u16uVdkg7 zoGV$9&0XV~un&^oCjZ1~3U&PmU)XdKIP<6!?5F(WHN~yEmNAp6X^+cM-khR(qr3aM zHXyl{B)M^?bZb-UX^)E(`KcIAmj+dNg}gw1h|`nhB@&)Bn$V0 zB_@`UBY$)wU_Tj{)5`dTDuLkzKDH-!4*}Q!E*Qd*bE_a(!~|H~jCfEuRPNu%OJ&9PZZGnF!MBnhV9Qe=$*J0R~c((~SrX7S z1EnzSIA1`c;rLhxKAgD1#;I?;S!+xmjl0$%b>aLIwgvv>cb?a`elfQby&o|D&9z_! zIY3^jG9nRrYNlKw<`5Wx2!#8;rjsX^Q?{$S#79!zVH!dw>V8aJvtDO)S$ms;k2l+Q zm<~%Co12GK7kpx1Hh}xxKW2e}_N~TK{ei{JZGXDhS7#ai6sg>Z z#bN;1#@1{~a&CY@2|yg-9m)J*3>fANfkgjGbqHanyrrzA@7*}Ycwy^sfkRxW1ha;f zL;dO)QIT07w18o3a&rVon-$iIWwwqtEk&Q2!7d& zp>xrLzp40ZY(Gv;JDXz5tn!@5_2QA%J#Ub~vss{b-yc8UQ*r#^G`#|Fatjd_`sBth zEq2RcZL;!4Wyj3^PDtIQZQy;i6Z3ICrS18b`Im9AN8j({AdUf{o6!zjYX7oG_L75* zZ$q7)mbOsk3y1rVTz6d>#tL)x1VPtL+3sHUFk3;s@Bn3Gl&C8N<8rqIAd?6pnD`75H$e zUWL&%a=5jE#7H=HxP&uuHh4@btK82QF$oN0$4cLEos z0>{kjR@j2dH*&!!2@8|2tNi1@@NL3ao!J_%3YMTpWf9xM+%^j9Hk;gF2Ckh z00X@WPjlG%xOe=(7EM}6hqCwHCKP|8{Ihm&iDth&Q>pp{KLkO&NEc-zpRhs}#hqpf zVHD#I6Ca>mjd>kIpvF_T?MDD4XyHm_w@?5I(1tH>ZzvN7NUmhzXiKF)mUi~5e8fQ+ z9AcA+-2Da&Ta2I^Sut$l;wN#fA>Ua0660_pZtqQZiD7b>TGh7M(mYln-{Wp6U|$n* z&LzevTCp<$340j2;sqAQe|LN^~rpBPzdvNW;skr$06<>dT-euo8 z?^lzAN^D<1syhT`TxF^vm&qJWp!$O!y-+yct6Uh&qBQu^I_Ty&W?@{rxZz=QcU%bK~m$ zw1VjGNAbWY8^*D(jDD5g?}^zwx$+UzzUzX>o3*w+M9iLB{ioGsApB@CuruyfS4+&AEu#6wU(!6>a{)-8>Wj1BhtD4ume+D;aMPXF%)WIIeMUb0 zECL6;{j~w_LI9OnPy^tbU9hc&dc9?t5H8Ko&PXHKEzkh!CT&HK?4x!eo6Z4i$OuEd zB>1ERfx4Tp^jMcbM}z<{O3nv>#hIjggj|_$hEP!&VuTYUK07V!YeXj4!pPhE4Z=_x z>~Y*EOw=e5Y{f4!$W%Ird^USu#9zPrZuvch74lGi`38r>>Ds!gH{`(Qac(!IXT$wz zN5|gPQOCgab+PDIS=W$Kuxy%HV~MMK#(`~4P4;AVNa0sxCb#>AX%2;l-s8mfRkP`%ss)NU91sm}vkx9X1aK;X17P>`-zlK>=E6Ks%{Xs~hwY{E zvpxB+#nL|4Dd6)zVM*v;Vt|F_Jz*W7!1#M#PwPhvlf(+7t3n!)AogE3ghicBk%$9< zpj^8O*2PqZAVK^q44Z@4;~zbo9Oe)?wRtw-8l3=a+e1mJ(pUKR1E(bHrn_noscoxt zSjELnn<0D*O_@a0o9c_Xl9L!6#`af*@4_ct(tq~vysfqKvcKRgjGY3vkuH02Lngw_ zPPN&TYu$Gp17xpJdCQd^5dWo^*UL*blr1-OXUtt%UcToC-=Xp6v)xQb<-Jpg%UN$~ zN9KyhOyKHNPLK)_bRMA%-vBNtvr8HmN_0k_b$Feb2Le3!zB$kL@B=3=uO+PF@aHrY zbje_beB^F?1yP-RR4CupX$26Q6Uqf22?{{51A0Il0s(MVQ?n4#_~*}(m>)nuNM8_5 zZajMrT(XXLuBS+H+Y=7VpH8u=vPDnrQ=J4NXA=j9M2hDD418Erhrs(6*>+H`^+#BW zryCxbOB+N3YCZ~ClKlm*BIn{h{IeIqgo9%&DkoA=Zy`|lp(3WEwIE{JnK`8cQOV4m zHU~WoSJ(Z0JS5i7(bBBB33b)hBJo?&r{%%h+WmfD-%tIs2;py;DON!$zJ|?<<8XHzL)COG{Y^|? z7PeBh5vgQEl8;T=ryjR%kc{sfr|Pu6vb^}a$t`7*9)lOqwhr_)Ou|?np#mMi>12&60WLt>p6oA2xo2tj?yoEw zEn=nR*ojZ)7qGffmD_Q;B*!uxMtKe-&tv)nPrLNyml}#_IM~UG?M_4+a~0@DTw+pr zjAg5>XbVsG#JQ@nIDSE)!v%{OVrHMgT(+A^!=_8v??2A2VW_9{9pC|sEt9v-d2LXB zTtv3V6)H2*c{{9PdqfeW63hG9ISvr$F%7gBcQuyJ%S_DaUf-2!w&%m34Q(?iObZDV{#*xOKqC-a?EO8P)YchgWFAH25 zO~V-7)S6eXcXe1WHZrzEk|1|wyXg)sj@v11AxU$DkkM4;f)&WR|A^J0;Scq+=(&(3 z8I%)El8k%Kg0J6ozWM9qg|+()0_jU_JuXtWIB7zZq5}OK8CkH2gmPv<>u(x0 zw%5;)X(g||U`IjP+;7i~M^n;FYZ4pBe#K30-=CbycPf5MWoR-iWN46YQmb431K0kt zjQRoe7l6UHQD|kLtyI+Lu*hOGpwnK((0d4zVOJB*?m)Sn`S3$qxOCJW$)v!atp>_D zdHIXvBx)2|O!RBz};lCiyKD^hblE8t?<HUx6?Yba z6ub3n#U+xM0!I0_YJ<1SZVAW#YEn$e=|k`QF4Q2ElfU;=_>;a&pZ(R%->PRRjfz&1 zWG#=OCzL!kJhMzC(dn|K=F&IFkFAKrmzK~sAJafCcVn^!_KK2CxwY+Alr5A%P38D9 zENE}heki2sV#kF_g0(yYHe+-J1<#T*FNAIlmWySsNb8mJcwUp9VRpb~#>YHUsaw?m z`vr*0Djt^eorI~Hj1bl_lhSfl(o;uS1i#{*4@gvsBT-&n`P{DO?{l}3zVLrutaMfs zIv-$qDcw2j`zjKk;*yuO=LZt!@|ROIX9*jCm4%Ohy1j@nzjfSrWy(DgigP~6^pBu= zcX6#nr{iJX2@s@^zB(GN;@B9kvvccoKu}1+?Ub-lOHFf_siP1YLhZ-F_Pr7xOrV)< z51D3zBu$6Qfwg`%$SIVqZENuOU-5fWsp+W|e&iqDY6c@oB^kTcjj@rclM-YjtW;W{ z{oX0&%(VR>V~RrzH~Tv`Al~9>t*pKy&2^7wgAfJ#A^x); z_Fu;)#_jwduTh=y5=e#xdvPMl@#n||UK!aWE>>n2AAKq1RW2=*&fVMN=Yo=bpej*mVC^2+lZDZByX(ps3CFc)z7=w{7EBA zlk&FJWR=JTrQ;+}1TqO-G4@+|@&+%P!z|Zm_T<&-bSr`8Xav?9jo43Mry)sXYIRyN zSgmlk;kjbr9)ba%mgTGi#oPBSF;Y0Wa<`q)W-KTi>De>Gw4X6x$jG5`#`ryfn3w&Nucy6`y8%3R$Er414OYBq61K#nI5 zSRGDo?L`iD{;K~$>PQ4#+J}nlDI$3>OSzSjIrU$Tqh}$*4~B89I2S_Cxs@J0{UX~E z3OF{2+UrAnBt&XyZW55L!GfX8K7tO1mL->ckp_|*h-E(U2@HJH$+LR~%&iWNh%3tV z2|_<0%SqYrhC$wMn4Il5Xtf}@j;&kFay&b!ep)lq*)1}wKI4A%xP12B?Wiao5$xf~ zqH-vkeoMXBqATdn$tS3{Ki=|uqnj|29buFxbz-C7&DZoR+i-qNy`gY9CC;=xaf^T? zHMpz|)qE#U$8$>YT?x4!Tid;t1G(6^heH?9-9|S2He$4SJP8*%g+-A2|Vk>m~md+pV&`Pcrmp9_<(sx;pOUGefGn!qr+Ut zERVUr?RPB&H%+~C3I?6TZ{HpAe%24D{oQy^iuL;+!|kKtCt-S0jkTgi#Dy+z)9Zz_^s=d!>+HK+v-h#=Ms|oihLlA_G;~z!T^g=M^K1JyO-M>7WV$< zg8>Yw3fumDQ?7qJ)eqeiF;n+0@I6>)@$6xqiPDNQ& z)d%i0Jkuayemr|T6r)a8hw{w=-em{I&Ix2P=%ZXYBWuA!~(I87O#D*@p) zagRR6L@`B!CtgOxu3#NYDlAWA4yod;>$qJo0h%v5uGBNK7C#B#4&_(0WMXu>O0xlTlJb zl-xb4UWyeZpzUI}I)QO9=w4i`%aippULl->Xq{U4dMtotMybarp+Iq=;=i zEewZ`Kje9uixenNV-=x*KkL3d7gnpxRSIUOb9nQB4IU#O28Jc9fR%2vX8&<7Z$`zc zW(KelMw)TV(tD>cENp-bcK^n7mo9ZjB>UqhYiKm4#BYtl4}8z<#cDM36qs%PhOI`>9g0`GQ-H+#2eiw=Uw zU(@X z|EeF(?Yh|w`1OFAJ|-w! zXg!dyG=NqnKZ^13ZKrXO-3%!rKFt?|rGGmg%2m{v@?NT~@ZZoOKV0?{QkTwaS=_Xi zrO9C)$~`H^w@kaI!9z$a0QCTXxBqv$V?}8|2=!T+VRL-v=#C4K4v_#X{X@94r-+Ax zz1sV;9obr0=8YY&TeJQU5mEC%w7-3I>pXdSNkkxf>=uF?b$1v+tLryQhhOnkM^VbQ zWvug%7yUkFD9dK7mUr>$bQr5SYCV`9ZTspUM`PE1%B93YEaWOJZEVg1>lw2i7XADt zuVTF}hS18abaXqvw-H<8RRk1!^ZR0-K#^rW$ zZ1c{bc>~mO%|7xPfH|?eKzjz$=h>wO_GCG?=9C3ZibkRnc1vD|-o2MPY!`a`+pItS zBLIE4&OD;y1R~!3P{&{9+AvYEq1jjKRlORzp(sHO*URkR-tn^sAt@Yt?ye?R@)wXNh_&_y5(~6!U%XEQf9)kpQz2U&L2bc2gB1YLz8T((V6-)VMK&nmV@uRTka;j=4@0&d_aL5+u3`qdTyZ+G2myB}-ECbX3A z%Z4s{Cl)$9eA5nD28Y>j1U;(T2Lkj~bmxh-LT4GJm6u=6ONd&zzX)QUH-dDck7vj6 znUu$7IP^`8-3F`;9L@I-(IUxYb}@M>2#6PqmBZs!Kl4?*Z5Y z{LyrCgB&5jXl#;q@XFQk{(;*e=cDd!rLs+iI#LKV1U()dnCIIK_9mI%XSc)wz>=+r zOwMmYE0WQPu>%0?sU^f-u~u=qF@4>5%`$d8mdIX0Y)3M-gxJJNKGq99y|(C(_|c}P z28_>@<%df6e{0nljejoVUQ|Qokt1>VenoxG?^c+p3z%wtsfJ?5#L{{Ngn`yfq5nP3OpN*8^gP)D4tI4(oDUSRTl4t0o<1?G{q9cRu zRnv96j-?*EFUA7y-y`{IZl|s^-apsB=TiUV@NW9}JRb`Nrur(|!T1*dxC-&=&@anV zvIi(ibTsA}C|2&e5%7h06mn}|oqyg?Qi|Lwd7ag52D6msm!<(|m=t1e@T@-^X{FJ= zp6*qHoEe4YT{9vS-Ri)8QEyJn2z6k0%*|-67Ulp-0@p7Z2E>J0nNf#COfv+12C`SR zLFxp5YF<<|mAWDmA%N6dxU2U`UIJ9sKFxZ$)$!WcJ-R=@@N&Ns;a<8B-o#$-I1qR` zhN@f7U`QlWnfw_r(FY>(p3hiiJqUovQvXnGJ;_lr|?}@qbT9xMG4S`CIssC zQ;9~T>!&67Y-Xn%IbX*ksV4i`_UJa8kj64B&NQX%{h7 zfb7t?B_QP>AQGN49rqtC=no1=fVO)dGHmq0AQm=DSq#2z;=Lye|}}f6Pz1&QwW$Y1EE@SwTTU z&WJygf|tmAGF3|o@_ulcOa8>84Mk9@u{=11_aPXURC&VCCoQ1c9wfw#7+GP0sB%4O z%*d>IYOhn0|BPB2F?DLEA`1Uob`UJK=FeUv_1lFh_%j)b3k2s*@R4BGh#?fy9rTX) zuJ2@RNv=nt)qaD)DMX?aUhA5LwUylGKy-KY84Hy$+2$s|>gMX4H2) zJa5ZG$NKd*&MYliUjOi7QI}9cOdp0uANk-w^jh>K`~L?t8u3E=w{(?+l| zKFn;xvJ9A~J#hL6)?0IQ(+t)}nZ1p5Hobd95uvyL^6@u*ENoL1o%&_o8qVa4J%(4K zQlYjeI|!$&hiS_x&5+zC<1~630096GY2E0 z=gqQ7j&gkW>)+Y#jLu+3(pOFub|W$$@k)S;(Kx65VZ=4>`RYEGR`vQI)H$^;27OSF z6p-bp)FZAJ9Atd3?03G-7Q(8Y`?gtR-KN6)Lu81OaPZ7!iOwVEP2MN9c6_MvMOV=8 zG*dn+RH4NSFyNPCpWOq=2vI419Z$ghz;rrc#fKa&{j7tyJO0i!o%{Y7ueRp01wt9m z9q15Qd?MZg2Jn>x_RPz#F>jm$Bk$7AN*p83i%DbYM>nOQ@k+59H3-m6znZmoa6cZw zx!0$4V1@af;ri}?DtsT7qGj9`_#4puXRp#0 z5`&Sug#JUs8cX|L2ty+NXExzwf$0O!$N)LtULdy!Pp{QJ0xy=g$o%SK8X-iiSC=X$ zaDFj@w2i0r)JX4F7I@k`K$k`>hTZ;Gso+8M@yQrrz#PO_ob3x63p@aw-o$8R3?JM? zo!-mIDd(V8|Ks>WKXQhgOF;w?1T!oB8D+pxdzF-JmxH3Qz*D2WBH))>VfP_=2A0v7 zN?80=`H@uK;8e zwUrAFhM=g{R22N+gVHozC4X4BY5%*E_XW}asIDl|V!Pzq53YncA3O8Vov&JDr$TQ! z&S&>~Puwmk$*CR1Z68|zoS9WT4G_br$YQxrT*|EH{cHB*2)`DI?VZj&--$7$_?_zz zd#Ir0_tgN|nWj=w+yyjz#n+47mrRg!fktizE5yA=ZL+!|iCMEFPhw$1;Fb1k{cJ79 ziIe3&jKj+)kAQ%a8nVOt<+Y);#TW5=kj_T8C*{z&`&o>0Wz4y|BgM`;DuJv>B;bo4 zkObbk>-s(qwb{sqhm{{q#Z-cTemyR`JX|V?ybuIUb8}8)fW>m%C$J*4ufC2Aj#}yp za*O6{_Xz>7*A?^;jS*YoXGTQfj|fz!*g7>X;84QIeXGxD5iya7n;%fU?t_ z4dL0B-n3yz*}33+f4gYCbP7!(=$<&I$eIW=d^$&9465 zqgbK-xQDBOL@!I9WFlP)nuY(`b9*FRkTxF4{z) z*)Q%QP6>v79kcD%bT5HS{I*xhD%1jW;gaJJ;{KGY1LlMz)YYiKF{}43pazlr3?z-# zfSLAdv^B&Yf9a_dF@3*V{H(1aYYNU!{0AG7dAekE6@ga}wt{BJXZSr^e1&+wJk-*z z_i=mM$2i1JGWdXm5PHxmqLdB+G5Yo^5tw=&mnaUM*0&mr43%^P*kEyxD!Fe;wUP(g zLV{_yD5%4SWO9yddI*fkkTic9Fyiot29ky){5R^d8Qzt=e|@lwRKY}W*4^=gy3#4i z!ZTvGmq7<4w0;6Fh@I*s^JM>EncRkoI3c?oRb~))2glmex$dUMEAe|l;uZCx1`j+R z>bD|O^&vU@yfED$+1r_+GCcpY2@qEHgik2ozto)5gf-&r{>f}5vvMe zQ-1gUoYGa5M~wt;WIhB4l415J&C>;73W!@OHVZHPUWg3B^rbq93II4jy&puQizlDU z#ST)Lh=DC`Z)vO~EBdF1;Oo~cAAjEq?jSQL;Gk9gfcTxak^Rtv2-rG)iID9fe)yPW z<*HAI3Xp`rXJRLn7iKdlLK7V_q+APt(#`NGiHAv6I#6j2HneU7{cBa5j-o8;T?0%P zSAPzvLC_2cLeh^J1jtCX*_R2Qr~x4M1sj!#TCoOPR$hrt{ijYITDujB0Z5Z$AAg6- z#23Fz1QfvJ>7T<6t3hzcfFS6eexHECA)i=*kT=c4FgY6!4RQK65-`mU@em&*6;8jx zHm=6a_>gv6zxN^XY>?019|D7Jif zl?V|na0CG2YuM3%rLU$Zn@rGh{6>jxRuB|)G6tvT+b(15`hw2QeyJj!i3SBF=(ADxSSqFNn2Bl`aaOq;5F;=f?5Nz-_CqDzLP` zPyZK7E)w>65fjm#S}R_zPu>py1k6xWKSg=PaDQ7|dmSwcVb44J=Y~M8cYYkUU73HQ zmxL}3kS{`gKWPg>HknOdU!HecmA2pQM^mf_E>@e(WMKW0I@-9yhrB-TzC1sk3MoE0 zWmGcL{1jj6I$$pGIJh+j{*!q4w~zMeYVK?*smv(tRaQ%0GU%Ky(5c{+uecb+`?s%; z)+=h>f3jT{Bq67AJ)LRM5P&_z7Z=&Uk)S`^(u|AZ7!1jqE}Y{1wPcO1%o*n*Eu~;i zXnR-Q#WdSoRPf;>bQIDgb~##FRt+<8XxqXXErbq7tsa0qWtz-)AOoS1_jh|dZX$iC z?YfaCzfY2;i%q=9G(=r|d4J6v$8NDQC1{Xp{||6 z7bcxgWHC~)SOMS@|a{!$UDWtZR8kLLv>dlx2 zA!OyFhwRxRW5{Tp@w;mml+tFUxt+*5IEgQlap^Oz>bkQ;Hf>8ApYFs+?p?s#FbdL#sCB zs9)uHre%DxB_RZU85e4k610|fa1&ez$28Ln3TOQxQ_nc7OHxcmh^ zYj#a^#573+6fVrD85+ipVY*(^28`-w)-6WLiJ!afvzddn_yc;L(0^-r^=8VahV>4rKgDGjmaF_}%!Qi&3xb zKNs$)7;a*0Ip9H3*x!*O0l&B>Dw)u$oP+alVH0fH^Hd$(Gv4LItzPL!%b<>)6zjXm5=dm z7_(Bu(GIWWKX&W*uTH#0@%iS#vK8}~n#2U>CoGN$bLvfb z&IA-PcHfn;Y@lIE$r9V}aX>Js{J5mGVA4jG_T3i^JS%hzH}eF#G;BOnNk}cS#YL*r6A$Y_I-rY0Vwu}f}LE(&K7^WM-p+{bVK z9YqXP1qlObw@YHzgGr8ATbEud*dSL23Oe(=(;x1FW8%E}aaN51YY|Q7%|<3ik3}|Y zsq%312cSlXDtaEM>)d#jkOAT`9UbcxVZP36Q%0eWub+eqCo&%|=^x8 zoF*C4HP<94tgT}X3=?xt}rVeM0xVQ znJivbjxEm(%Z=)7#BCV2|Be^+#+?;T`$p(x>Vj2}s7JX4(@bm#IcI&Ri311=7E-?} zBoK4iV8$4g-r}>>Lb$mbf(=;0bdq#JLzg7_vahTf)+b#2*3X}HFdFCskFG>NPR<|v z{uXgI)q&J^H*7VgYiyl5x$c&uEM`>G&K2qI)}{4HB{=<8ks)aXQv$h-#(rWSGRbe2 ztToDl<4RR_S4SI?TrXKx7Bw|l^sQ#Lq?>d%Tc^Cb4U|y#dPws67&O6!qO_M5eBv$W{I5>1G-b0LHPXM0UI#4YgWXUz=T(u7LwvV8biBaVq?nWS9Ik5+jJ z*(o#jjH+IU--ZW&syt#Gz8SK7Z`9ErgR>lRKu}(lgo~k{NQc?nR8eQR@yJhmfJ_L> zlvJU}kRp*NvOsi8s)89Xfrt$BCco!HY+{X(xPdJu-MVrse*5gXPsi1!#cFTAop9K9 z1iYrI5sUw1t!NAMTRisvwm+a-{aUR1W=*QXFDP|o#Wv4|nLG*Ca?P9L+ zsMGHV$@JZwG-}6vjV6tow#q_jopVF+ZskGij4}8SeI24`bGyLwFYKLv!=!NzOXH_H zGS@quj#K}1%N$X`x@(JjjCM(?IeGL!aBY9DUnpHV(!7Tb;P{z)f7AkZ8Hi`?YNeb1 zi4$s=jCjviqo&~L|u$wOn47Eo|6|>)HpC-i2 z>w3HpV2Ai8gs4*o+{<3GnB>`!B7(9FokI}?B?r;V7R#WqDzTjX&LMJvubvomDe)R9 zV<>R*-@%)o1_CO)G?Wf01(0KdtoX2#;)=67g6X%YAdnsfm~ z{!gV{OR5y?^Q-nM>`C08UHWGKx>oo!cwq=uS-oWP2&xI$HRX)HL%1tsD)+%aga-zy zJM4t83XpW|=YGF#_+#C7E>Ok_&fXu*o;TqsF!K%Tlm1mryBJ8VjnuIcvW%{c3u~U8-xoUhQ6N z=%=WWUY#o?O?UDHwlWmQt%C4QXu5(p*CEr?54)`l-(&0yr|e67W6&ko2ITs-u#0rw!P2CT%KAwP1ACd*H$`WA*)LKOMzitwyodyA}wfjwK7AM z;?`huD9d`RU(~+i`xHuOs%8dCXUqJ;%hN_{K)$k#-enOV{m8iYtWoKFy*N3`MKMPm z2*zJ%$fSwW){QJ(HDB{_!9{D=K*T(4z2D;>T)3a&hqFyOS@z(TX=fLO7pya>#GvuO zzD2~OFiUwRIU$RD=x>A9g>_50H)GJqpG9(iQxVPV*>`^bmUi3(BNNC&w$RxC z9wRO`-gR}KUirTz38%aVAIlqTMK8w})5m!iMxuH%j?a>I6X%&b*6pqAA;d5&gx17) zjkNh+?JGd5yOps@K~8LmzG{OZ=oGDcQ1RxR`$^LyXxabuC}`j3g3I{r9qbYE)!#l* zh3Rr}U9aMSr#1+2Czacn4*|A<>?uMGjKd5zufxab*+O9yC$c4zHbf^su?=>(HuN~G?|CQr5$R-uTC z%1v5wk%a*zJUaMV?N)Jx5lH4FsBI(?<&7;inVbw5j@d7P5h+F4MC{imD{WEp@~R73 z8RfTpMO8eUt_$wo+fgaTqaB-b7&M?2yHIlYCq&QRqy_kJwIYqds$q`=`{xneDluk{ zwah(p(N0n6ZkZ6<@4b`bXBa4NXtiNMP@S9?ZivhKb7lj7ljewJx8!M&V&fR1jT>y@#{74=5+W*o5aY2VwPlPF!$GfD@5?)_Y#^z=Y8IAT?IcX z;&Ls;JLPH9j?@zS#p5%j^wCae#K`uQ+hthG%G4a*U9lSE(^PiCnhQ5?kjTp2{TwUT z1E~!KcSYapk)KZC?_atu{f@0x7H1YKQa=YJLO1~^;DI77PMLT8&F?!nhN1=468Q@z318GC#$UBkytRM-)0K8U?^X`u!lbkxkfDW2k{nn|WW zgn&&YQ(ac}6^4z9xOH5#lB!-&y-GMmB$DWtc|+t>6gA)w zI#ZFv%G@yVP@9;7y>6Y0y&{Mh>F*D4Qpi`7f%j8f9P5n65*hHUmG22qn0!?)r}is3 zarzOzqXWdl@~k;|T;@IXDMp#+nP(~mUidEg{d72^)BDVJ- z%8=z4O!Zw35Fx1B{*&^g8wfg3)+kRxEeA(zBH78@zs>89{ZT1-JaM{qg#b^5j-Fn> zBIY5=?UkQi^?XhhdG&(6!uE3c^?pn~^J$=KZ`4_1Sh`xLglyiu`0yi*cb_4Lxy^y# zSQp77STip8O}Or`MH=XPMf*|U{%sF<7&3ZurskiKkcsnzyG$u6YhpXu_EoWxWND2k zsaW_l|ElERwKHpBV9iz<%QH(XCmU?1%w(4Op?%|)GQ)TQ)mRqvR$}n!>|V6TmuBIj z3cm^g+Z+(&A}ULvVA3QQ=_!(QVB2Xs{$q)NS~kxg3PK^POf_2jG~2b&u2OFYh8Tvq zDtEgjS|(vs0KHOg2(EF!vvbG%YiIUo+~>kty?7L$Uv>n&NMJx9K+_zI2@KUNdAGH1 z9cnxs*u3hVTVITK9%7Er!HT`(-X>t`NMq=HCR+@#nyZ{=hjSIaTK@EO&^9-9e8@Dg z+kq-umZSEbg|KIBHK1{U|3uXg^1Q^eV*Dp-bz<3)@_He7g+iKHqf2{JMFDi5vg-46 zFjv`{EtLxQ^-w)7?9#l&ANcMfa* zW>V^LW(5`FVh>|_;`GXTE0M)C`*WRpd)WjMn67X5wPy#}Y{YGkm4eF0(?5(i~O{DL&dmwP4r#Iba$CXkWnpx+&Yv zh?8Q=11A8mtd{yzZ!w;UbWlxw8vLdHwbHuDuy@C&u58#0qIr$i<=_&gNL!fzy7J|??G)@!E>9>xb^tAL07mw!OoS#ik z8&zkOlo*!9kZP2WD?E0C1psnev}3GKjNV3&qa0w=;LpuJL~9vDrH~ZIHsiYd1}9Sc z9y7C1#u^XHVq7J9|D2YaY0{nmYM8pz*f#2Tt4hex45e|J_7C|opi5@j2-le;{LmKIl20^;JLApgG1O&eQ z&G!fF+_^KmJM%v8dCt-NqJBoozA7A5lJsdBlk)r5Y>o@jFRZNkpQy^^?Zrk^UFZb? z2ODG*#g+vM_uGGp@(fYLP=j&Ieb@(&Im*}Vp~(i7xVt4yg)zYqCsx$f=JSo-?-a1+ zHJZqo5P-=i`B(Ym-%S}L$8wwONPeObF=fQnM|(%{ju;rq9oS%P0HnNvRQNbWiBVs<`SYWe3@p9Y5lQv*f?i}7xj zAtYH(@;;l65rsCP0vu@)gM;rl9}-u>9K5G!kUjrKebs+~3?Ll}zaAYM-4xvFhmQjS zSg&1TjyXAG!@z+P1pD&F_jg1_$$$2;gbu=c_ITCCZ=n-ScibczhUVfFLXLs*s% zoKCY4=b=O^yOeUbE9n9xDHu#gQC+2M@%PP)cOZtsuP=yPcqCmB0dwGy)XqC&9kVg* zi686VZf5m*d*5YN5dEckII8|J45MQQ*_*mz+Ns2%cXrt4TrQdNV!Z|VpE%~qv3;Pc z)9U)7U&B-4T}E#op~V`&DW#Ea->{2CWHR$m(vbu3|8RuoEAFbHn!+;9ButSr?bB## z$44m1Q%l0y92YM?!Ic%d_+RN)9?!dJ7p~>Ja;&<8uk%TEaR1gSzytD`_V+|>?nVGM zEDR+3SSku9Hm{i0ymF8wO`5k85;;ltszv>LGbkTf76RBlF|g$)1W?&d9TrI?{2;Vi z`bMXMb#$7Y0O}v1V(H{DM{8~ z5J02z=isVgIN*YcUiC}-ygFOES2<8z0YgQR7Ys-Atm*1G=5z?@ zbQ!-5eh>vct*EG`qSZ;)jBU?<$S(hBHY)!mb%%LRTRK!yN3v(S`4O=!xz&$pJipZ7 zj!dZ_j zE=1vuMCFpO5sVK<i1zXWH5!?1N@sPOxGp=R<#;9gb z^8=PzwmyxmVaUtxS^9=VXe!zR7Mq0v1Zzg;KeKwWfNtvxm$IMOfLhbqx1Ubmx4Wo& z1R6Nn-Neq85sJ?HyX;;xGnRj;S*z-LldC3=&mn0SD?Sb4R9h5NyK z6kg^RS2JeppZ#nK@+Zuf+4PbgjA%YLLSX|nGF3H(=NydB`@cB=fMsx{oxAOqmV-B?xU}P);rmMWPdkd#*!?6BR zBsMlh#^>%xZ3R#&Dp8LRy8KI198ym>cFNXXh>Qyo<44EE#>-X!Zz13+mHQB0jICssUF`zfY#84Avr%5 zGyY?!tEQ@;gG0wfU6}%GGb;s2GnriQ6o2#6)5z$sD$7!O<kxz>Jtw zBa(xr_#|lcX5fevW}@^9C9E%_g*J`Gs-B?fAxN96>mc1$RY~uI+Nb6$9F+G3@KlSS zrMI(&_78bYcf&Q~-BMi{Y;*Xce#K;>z+N1Ol`4gsY*G57qknp|>>~MbT=6&n`0Q&< zig2wubs4Xp7lJ>UuUL<_xIT#ObnO*M`u>sru*QL=?wGp(8=N#Oxt8Hf=*)YGvh{Uv z@WVm4iS|mb)!^A z7{@&z*acK*Vgf%{Pw%6+lC_CEVjr87JK{bArYFTAv&q5yAuH z`#6Gm;b2%q94r_vGO;Us*TO@y4%A{hOmL#W@l`sas)d{8!L!B65&~rG4%AgyGc@ag zkTuX|(ov_pI}^HkKCQ$!Q&?+~u{gZsmJ@^oI73@Ol|0mM+v$fDz@I*z{gG{*J~2Ga4qT3}ia z5?V$mBTR}UL>nUEPiX4FqwgiE@?BQj;|(~`|;2zsRm?mE6qh8q@DlC zS&52tJ65aLFL8~2H!ykME>bjRLV2ih`FE>m6gj~TL~=BoS&mdl8F_t@W{v1X)Jkcy z@b?jTmgx#KOZa?Mk?GG=;dSvT_B!{#z;eIo8u%1j9W|(o?%W>w+v=Cy%^+-9fCuls zTll<{2k&9W)ql$Ed^h$S$JeM2Si6Z_jeE6u5N?f6&TF9jC+0&EJDQ3a2q69ED)5Gl zUk2M9dFtvZ0R^CSdOE@oA5rHsWq3$IY|YbPJ`ZS%Z(r9SSy>{xkmVQSI_!1B10k3s zTcb7};;A5%+BLNEV@-h91m@I(F%|e43$9*z&7_|1vV7%t#Ry7y1tNyzOC$dmTvE|M z`^*hiu4ob>Yy<;$z7&+LuSSjKfBDpTYR;G1aaH_EcCQJ!r-u%R1U&t?w6MR6l7cX(X9wRQBE_&~@XIt^fWWi> zK9~ys0d}4KRR_CZ>!y)ODHOWa-oF|bgyX_>DkEZiXpK-J+SQPv89c{%k=4f=(~#JR z0Ea(ILimK~8&XLipb;XVhjsZ&=dg6+ zF6mF26Aeq+cd3WxqG$Iljsk7X@+qOiz47=fzUFEeFvs79jlwptN)54*YoFmoNn96+ z22@;wCYeBeIA09U=Lt;jO=A4`!`MH>bH8HXYDJ`ghv`S_XOG-Ar~pnmruTuZwJ3W; z7{kFSvDAU9!Or*9wk$lq_BgF3#}4mP*xfYx;gs-_K+zTE{zs_9c)50w}bY$It@l*bG5PBZhmgfz_FYkgYGj`6}2*b|3TP0^a_MOujdetf39p zZZp>ar<4e>ap{uKK7+XPZPc3r*y8h8!|AT)-k3s!pp4Vc%yfwr+E8cdlI z@?&MJ6Ho1`&~nlxUVw_6fTWoCc-e>PhjGmcb6w4AFM2geW3g>M+ZPwa{-9#ge$spccxrbApoNE~v zE0_v()s1G~R^JFY+Q~Yqsbaqs{i`vG!X;S#P zJGzjAv}r66O)*)M<6$V#nOF=AkOp-im1zRnRi8*Ou93`ozGU6}QOfwr=7s#vw+^UC zycYuztPuh7dQaF&z!m^H%(B891z>PbXnGZnW_}T}FCLT~SzH}j{`L)tdZ8veisouU zO+aEGhS^Co3MqiBIUI$m$2x?DaAG~N6bDD=PlE?!WOwHX`62Wme&6^jOr5lOa)y)_ z6pA)qi0OtG$}JVxWElC+&-v82_@c1gq$(JlD(-}+hOenN>W2?$`0n%9VHegGkGE)s z`o#8t<=U&q)r>&tcx=(q$0^#@>VpP}L=x=U^qM5DJSi9|+eDzI22-V^DSU(jYb?NH z&{LVQN1V94yzXfMo0*YMPFd%J&|FV`ty<6a??1vEhHt_MIm9gmKPI zIwxe`e2y8{W=Fa=WZY51BL9|(bhvD+b??ob6cF7Ch$~7zvxoi5OD$P8GHiFg4eRDv z-W=?fa+(#kiH3z;e|*YK;uAT~^Pb#x!@o;WyqOcf^Xu91iKm;;DRFccZz<@4j1S#Nny`8kz|r$!4&!%f_L z-(GIGve;1?-@Jlbiz?q6fKsjS&*!&46@L$&8n^!l43g=tO~{twM1XCDXJ`G2W_ur? zh0K}!utnZ+u}FlK!^jwUsk@7`-9VB3FyU*0kYiBj&o;J+?^oQ>ITcacEHGXqm{-~oU2w3iDyJV(#>{%Maav%)m5jH;?jXQrV7)11o;RL^F#|*rJ z)Dd}KA0OI7X3rYm5&!5=_bP!12_v)|Z^^nwn)6FfMluZD$#|4_u7A+pw5v{RY{RJTomuMA>%Ef6G!X`^9`BN%c7U%CKgQxdsl0>mUnOLc)45I|dMc+HH zQVHcO{~vJ~khEP^e5Y9YRZm}Fsf0+MjZRwFJr~!YG?(n1HSc>)wrd{tH~q2QT=hYS zMKr85TdE>z5| zp58>!nG<{Di}-(74q#X~^HnC>hyK+w)D%4xRt0l*^H}Z4hkf3s!kw?-^Z?>Q6x)d& zQa@r0&T!@OqKX*$x`YW26Gw^Av0vL_=HtVT5;-uhE`rjwy3PncK5JiD-Spks%;n&U z7%asvZMb%(%rzXR#ajoJ_v*XupclzoSLLN+QYc!wEmlxz&%i`w`%Sq!5~Q)>BZBu7 zq_sXqM+EV@?nb{x=3xRy5*9#kf=Gg-;g;=hm2pCP4NzmmQ_WJ8$Z$**E8~0RO#%r< zJyj+eA@hyw-z8YFV0woM++#3bIfXFJx)(DytxC~y9#SaIZ?Lh3Do*p^Enof|^aNZD zbf9v)f0wnxgbzE}eFiow-On8F+ z0VeEJVvtnw7s>extIWJiZ^*%Y7MI8-%0@@Yr_st@TKdqs$ndKVK_{`fX+)uUj`E{# zb-u%fRaO0({(aqG_>EW}@B0H&yrhE~ z^3BBjxHmA*udlArkH1v$@)*!-gfHnEvXCp7|Cv)CHeXg=T)@IVjcs1Kp)_^?KnT>` zF$CuZ3EX$`iOqKi5Ba~>kFbe%g*sg%c3R4#RHpx^FozQIp$tZa(1{hr_F)sex$BO3 z)_eY0V*Hl3Wg74iF+R_+{I$`0mazR=ck&NEJ*-}sSS^r$Y&Vk=zFQ!C%yRFO-B!e& z<`wq^QrJAd@cNZonP+diN7*#XpjX>>?KU8kLj(*lJd!tO!qe6vifM|`=V6eyN)QG? zw;Fd1f+@X}2iG_Vkr=R-X|}oI|J7T>LYFiKd*9Y23M^~u+zb#JMQs3?%DaRrd-X^~WR0Tu(R|5A=gy1!S|zBLKfYd((cT>ATUb^3d^FF1iOo9?g&BU?)Y&t8(X$NJR;g@SB)&BZ+1*hBdF=y{(TqhUU7v*dAJ7ykO0y2$)SkiNkV{=JfGTiNb9YL;R9im4&(S~HX-eTBl&zmRw9EH6_l=y zUw%;d>#mxwC$%?op zdynTq@RyJ{c83xP1N5t5Y`WD&!e7*!E+47ac`Uonl-jmCGICCS9j?zU=HSm)TB&9~ zar$)=Sr%g@u(wvy@hb_)A$eqEaK%m#%i+mHnMF$+R1LL03Q;XdP41*;`;Gmp2lF23O^AaT+BmLB>kcG!}S?Tw8ZO{{$<#csdS7WztC~a5GTi>ZxtJ>Ou zO-4&9BM~0j#TY(Kjsx}RWU#6cvw2xLou<5JpRv~G-DH&d=F1M@JXVok>EBA_&tPf~QTm5RRsU2}+m|pBGLg6e{`adD zTSA-Mq92V+MZJ^Q>E}O&)MaVuXZvo5XXKY{A9T;s-FvV7V^dkEEq70n!x$#wtbC*c!Pp^(|}p}6R=C9}kY`MbaF(ex^w zB%rO2(&pK9Wh5&JCZAYC`6vR_ua3ykhJ9{&W$|&+-YC6RH0t3|>Fl=1PgSqFge~B- z36j`m|6@ZYuO(Us1O~*RoV~3osksfFpc%{H65uIzLv&uq=C(8~Syh^%RzXL>rr>6W z73u|Jg(^aw%c0t?X9q2UM@5NDITiL=h&HZw1S*3ZUo4WhXOdya=Wp|y=b{`i^B0cy z2sRnGZa%n9pLh$LW|23Z&BRY>SCgwpC%RiU_CHy9o$fj~+455XqXg<4Tm^${BE{U~HagQ`kL z@3SD;P~SgSO8U_~%J?+^ONasXdC2edd(Xu;;Z1cZ$;f=b5(|Uob__ghCuCGfoey^; zx^*4s{KKs_P);XH9&Z^Tar}4myQrbcB^@SxPyfs(qsiCZp}oUKoDbidqj|P@(Q2*6 z*l}TsuEe~k2F?)dj7$p=2KXsIP}CQL`RBeqC+~%!T7W5|dK_I`!@rBgF!?4w+)5|B_YE;TftQ8aIaG-WldWCe3zsF4`7J$v99joXAXj zF_3ZYq{HQyk`s`B2+PN+(1QIi3yCBcLQqmlBiW8SiL^;8&D>zMiZv|SJ$+r}>Lgv| zZwuI<#P14n#D)?W&0ooUo@W8UIn(wM^jgDJ&OBFM5AmzG-{?`*6qyWq!2U; z;@!Oyzy%D*KGX1Y8-EA*+n*r86V^@{Um>hRp3OO?`Z{JG;cHmiU)ExajC z^&-ek@Be-F$}q6wKMcWhnK+1jAlrC(WU{WG4>EPi{0IBN1){jHz>H#g^}hY{;Q$4k zm__aQpJ8e*(A<^IDS<@*kBQG4Z&b&GXPv(`2SD=F`m`Eyev0GXzU}rJpWK;33c$Z9 zSuD*M2qIH%l*42*IV_e9P?3UBG0bY6mW-w&!Ni?BS{ufwY>1|qTv~KfSSL*`-6Awfdcn3UVyv$8Ma>AvKU9bLwY~C#nN+QwO80_G<(-& z`U_VOfa4W!lrDaX4hrDo5n7GB$#$R1+i9_tjAq&s)3MmUP3^Y7NHe=S(Dpt+-61B1 z5=xQq@b>Qy?re;iN~}vm-+|=YID*C=yyQ+K7}JhkmVbe`&m~*7pV+O; z!%+DfdXaj<^GMj26A#~-Pp^J9Hk3FgO8bv*guZt_p%b&VJ1u*o6DO_7zA;lh@>pN- zTdA|W(*Avpaf$sO`2jbNR{y9E_T|K=;s68mhR`XFt&?%N{lSAXi&(aPDvY3g`Nn93 zSxW3-#m&9^rsDQkQ{d9SKx6)<`AohGR{NAv_uEN%!W|@nsG5zXD>jdmxjca6hpujz z>iT?3e~Kq)w&+zonfr-hKaPw@I%bYQXInk(qPq7FwJZNp{~j)nK@~+HFbh5w7rus5 z^w%a!SS{SjiF2pzRW;6?Mw92)a*2r_MOO+8W7OYmKfLU&=!cT{w$aWdKBsoT?1BH5 zVuJN;tZ>sLj;UorDOodO_&7^fQzO${x2#s3btmm~Oiq>^VlK%NXjN}26BjCp*GJfj z?)nhcC`&tJPq$(L&HagwFI#yM`NjGq#@5E-lB{c}H&2Quwr3C!8T;xF)%Ar3!&f%t zwmwPkDV?aH+D+8nbfSQ=%}dyex?@Upk~3s(vfx88JRIQ6O@f;s(p8!cnU?Yc zJ2AXQUE_xq$+C&m3ZV@$Eh#!5DEGmbLN$g%LD_YHH)y@FgQdedlqcOy4&`5z=*F3uzpS(QiatH3d`@*XC zTH$v~JfLLcD@?@FLnB9^O~Vem-dmSfE*CztOf`r5-oZN~(_nHuY7ndS%78viIbcL6 z2^9kc*ova_mLma;V}OC4AGI10JriQMJtywHqBT!c9arQ*jf@vFAG8y-%hDhny*BLv z=~Z#4GML!NSX9I`3X2xGStW`EGVSD345+STmks0FFj_35@D7FYlg60`9Nk7$hb3WyThN0M!Vv% z1&fV6shl2-maGBg6G*C@4U0aGLnDo+T@FM8dNO!T9}Z|tjYjGlR=J~E1>$0M!VE&) zyWukwSCk!N!C^Uu{h@ElHz)HTc+!!EfpeLa>I|is*7yA}5d^_Um87)8FU%({D`82W zgfDfw5y_F!CaG_wj?bHwQCLd(5b!qh@Tk1uQgT#4%#&if*MlJc{Z_`kd1hL zGe_;7?Q-LFk(p3swBMZ;7Kbeoz@|J#R?yrsqfKo)Fln?l-Po`!lh;S?#qb4-qqcz} z`$Wpg1YXce7s&_^KB(hh;l+c5QoB=_3fF$p#{$4lyeKyeghJ*uUjhwrE#u0V<@HuKT;ajEL5x)7 zWz$>xiDcO8W(1G_K8d)_Qn{_U*>!q5R6RdF-8k75Da4|ru^Ej=hO^MoMX|tv!209D zG8ri;*XAt_4CVuYRvj3wE!H|GjZML)^K?G9fy!*FPb4|tcG89v?);sw`NfG8 zjX<>GHA2}^nvsKz`+;3!RA6ZcO($G4KIKgW;XOSWM?!a$0!+FI+!dn>^hFkfi-fdg zz{g5Nzl)Ke_R8-xz^fFtDWMEXpZ%W&(BJ^YDsbU(-TXDa=sjVh8O9$kyROB?%CAy{ z$jQbC+Qi^27X!?oZf4TNnG-I@4R~T~RPX;;8$`5W{7nG&m3o`ztYD>N2m2V6>x!dk(ugaI%QRQw} z@y25PxuL|6b;Aj4iv4W$9(L8X+fQHE2FTi}UJhk*q7Bhe*`j5lZEa#=P9YB`?wp?( ztEg`H9Z9e^mS5+-yY2S+3IK0z>yH)@+%NQF;NcnvgW*_4~3}z_sa`(LtAf$6WnMgK`l+t5i3g|H_ zgmn*AI!0rSrud&!R-Dk3I37#Ou1P4~2qQE4h259IR0Z768b?pqG@}agtBHMj>AN(@ zO+O?oxl=vaWq0U5-LIOg&OKnCqAWjtOXM;vSb03<{;Mq;KaqAX$7$1>-j%n?bPo;6 z9cC~M$FirO+C+PnvL5HDj=kHt4vrXpb1(?Ir0P5Cdgt|OyuYD2MIrLV_E+Uh9oR={ z@ei{+W~X+io8MY}1$q8tiBMUVlIQR~bvefY;DM+6zbeo7QusQ|N#Cm$Z;jldgv#dl zh@=bP#9fH*-LLmjH`LZGlgNO&%Q`PG)Dsu#!U2m0Sq4eevoIooN&WaR96olU1-HZ4 zb+<$y#mPl~@xw>l*%FeqFa9yd>S96F*?}vuC`kEs7K|E#p$W}J{%Zlddyg83MAP?P z4HpH=>ZLgIluRyb{Gtg9{A?5n2-m?u+;55jOrQ<#Hzp`JxNvR92s)jt>7utk;NmEj zxRK`;prL51Bl9(0E;Ghqa(sdC3RpU(lMUXnH2F=5t|R;4S2CjLtAF&I6Z9#~zhHkV z*#w4XiWyKqB-)eeAdS=EVu{2CDz6p;S7p!+gXY`Qy3#`@g4oyAGrRrrZ$Nwc~0D;Cv>M=}d8W6!=f_w3 z_h(+3$jg|!ud&5nk@{p8)V^4XMn@oC>ge#f*>0Ess0R+CzhH4B!NTr68H6(eK(y}# zXFRG=4ba2>l1LC}?I;UR`5(R24YbMwK)`Q?^*K9#LrAHP+3gHHQ^U8>BRir?nbCp~{%<>F!H#)Be_9PEv7v_GJk zm;qccawa-?e9N=^xT}m>P^17RI`^ zC{I+7euGSub_Gf0UPNjJLX#}I<^G792IzR9Uc$Y-3*ZUH;SfLez_(>E|07)Afu1#E zMD79?kWs9Df-w@C!q=&%PJj)hhb8nO8DvrrRVJe}W+pg@FUJt65ex$U4C>;njY8?V zkg`n<1CvadGKNPY;#n7d*(kR-GhNHMo9JBhX&9{TOf1}5zUFTh`Iachf(4%qg6fE+ zeQLVh-AeL?m?%yK+i8?U?x*mt~#W z^or4x5}1`(j}b<<`niv*ZKxfR8Q z^6EHrC;DUiDaaP_m_*@|iyavK4WEmKYf+qx7`H1xPHeRnO0+3tT~|jm>}!$LnPE;t z%Aud{PuJ$t7YoL6mp#U2#I}Fwq$>ShgI$=5=S2UAaUzm{pbo(+;PT~+02!98Eia1g z9Ns&SUg~1xTP5rB$R9YQNe1+W$40X)!?KBGEh3P<^6>(4)sK%PSsIcJp} z3!J9?`T&X-r1&H~#%6iBFkTA2#>7MyfmlYnGV0I&JV%tcuEyQ&!x>-V*t{0BrCon~ zs=4^t`YI5vO}d>clu3s8k7J@A7J4L*tEfE=nEVA8XYG9f((1V?*H7^JTBXARR{PL=N_KD>C>(1;BgndB>P0*Zirq=IaD7skQok6MH0vO7|y(G>YP_ud)0=&huv!3Z@w$(nM6|w125Wp(XC5?=^3d znhieS0=&a2V-lSRgVe9buF0s$Mipcrdxe}PAe`jPTn9Fe43I9M5iS9>94Gc@aQW1o zJx4UKYirheiH$&nW9}OO7{F=++GxYwW(Wa=&QAWRd!dokZ`R0w-enp<<1*vj48F89 zxq=`ZGCkX2V_8!wbSQ(wJA;TP`t9J+q>M~P?mPUc+&wj)V+ES795SSVN*sVAw?cZg zMi_=(hQ5lJb-Z1*2yV9!o0r@ z724n+79iKPuzb{7ehZy1MANwul#g5)rNOT6oevZz0`1nq!4q9Ec8Z#lUR%P;KL8Pc zV_6I9HtO84KdbF-|6t9yC^1#E&#hiX|LNG!D7Q}lE7bGY+^f8Wu`?1Uw=45eJ6C}9 z`XnCpxu%eKqBI(k)nt;#M1N=mbN1%s^oj^97B^`vgCr>!G@|q#+rNNX!JLM5 z3Xk~5FI6^G@18yk0s?@g#hmcQfWzSLpSe~v?h{k*XTtrX7@3a0H7E;^ox{pMwi*9E z_M+CXYdB^5W_3RIY zYO}3)kH_)LncSmmqmJjklgdCGfbs8S7L@Aizqpj@u`Ib_x9^>`>6qqkfzF-XTs2?$ z!q!i8-nRtJ^U~Y-*xi#Uv0tY7S5@nukhb0yfW!bxtNzyY3CpGKgX~@Mm|^aQx-_zi z(lwpM_gn{s#^a|4ZcU3LZ8~2n5^t)8Z~beE^#w6oIyP%QYhg(sKJ2**v$E+Yy9KSV zqKu-f>@R1Oh*ni!G<2VDI7NkAGtM_Ia??>6+ffv1k*KgiG)FgV537!gwEqSAwhI0C zaI()it?SoSIXEF>czuTSD&!RN-8m%fu#7ah7we~TN9zIvBAmvAtLvHcPlrh%e?@0@ zKtc$j#c{~m%8?AF_8w+5+aSci_oAAAwq0st;CQEST)|D0e3=cg_o@aht(d!x;+k=C zCH6Up+;kE{++s9q@Kkr5vZZHVvk#`Q(wA5k?_|0TiGAo)_^63n1Pl_ebJ(ff;X_ki znP0z|;?f$mXP;-{*wMvAjJd4-W{*yUxBr`1cwPUFNrCot?l{a@dEaAMii`jHrQKUD z>86<0uT12FUCEcu?>dFyZP&a`Y&T0TtJl0^GESmy4PV=+C(g#`803A9 z-u$|G>Lza$f#`2*d;hX#m9U%W%D$DgW54<}DdJ3#uj$4{roH|Kc_84vY4Fd+iWbPK z;{uZcn=2-ICSN;P6+^+)5;aIVAMobr+iom-dsCPUma)bqHTZ}^l!TT;Kk-w8AS0i| z)Vs51kVJ>+r6;9ySsXFG5c_oVBrV4Gy}Tekf4;=i&??v}w!rte)!x?`zfuP5)*y<4 zR@If~bER6mZngq?%`bz17aUYks~IOUwi~b9GjI?T$b$eLdQQH1q1nr?0=V1oNX4lO zX~tQIckR6r_2eiATJi*^w!G|wew`6i?jsl1KGBo0fO96-y6{NpnZ)AR2TvRX2e^JX~O};8xKY4blG05PkQoPacn=Bs&V2 zx8{$yl!VqZ#}*T-Ibum0aQ0q*KdfNO#DdOqX}67+3%Vx${JI4(Q%d~QEtfptD>ynK zZQ#I}m$k)~ES1TA$T(7xSs=pCRbb_eL_aQ~aD##3G_N2Tnv!om-hWiz^$xK=2+pQzc zm;hCi-7uVfPtc(tIUEmW`*U!uxkc<8)<%9D6aHs$ZJ24ncri-AgISljbWPBmCatw6 zAQLsp$cLx|b9WgRWpX)r;WhU+luJa0XI>r!v1QLXFT)mS0`$kk{a%{xU%UT%stfK0 zp{;GF^c;ndN02nI=?GlPI?KwYrTH-Wl1%nF8B1<-y-nq?V+G$Al2KcjTu;xy67*3r zStb9Dpp&J)CPOr#Pt&+uKDMFKaE9#(gM-jaF?%%`vy2Dy&y>sTdOm(G29-t0^e=Mec*4l1kzpn&=I= zOOG?o;!5scG3yI&Qm@3movBM$qaPrKMPhs~v|uW#9&}DO7=-;v%qcL@2woE6Rl@Lz zX>i|~2KWa&m815cbkdk63i?|UPSN5GrSA=J*+YK#U4_azIT6YMYB@&nE?Fm_(z9g( zgi;0?m0VQ7OnvI}7?yENfJdu{SF71hVygZkgVUH6f02KSFW#!oBM){%na2vKyD?c* zp#g&=8t`w%Ccc|Iz!lQD1dSr+DrUW~ct;B|Dc~#5WaC|LVojH;!wRsDMnLk?Pvo+GJ<6+4gIeR!LS$KYHUfAq^G`O_uK7-Y3zS2&Frhap4H_ z(o-MWaM|c1(a*$MIk3;J*oUlv5oEI#Mc|yl0d=%EBNA3aysJ-FCPItK*jT~Aj7Z$b zAkaoDDh({V*a6z_A?JVX(Mnqx zS!a*rRP+gZT)C}35$De~V7fA7>2316 zlw=tqB6gZ1QI@DvNpY4T$>QWv3|2*9UJT&-c|ooNA5ZKpo@4c!eN%j@7;>|qo+w(U zNq6u+8O%}~*rb6yhlx9(x3)YbW)XG}U0kzHeyFWF$4q~{o4Mq}m71i8uR(9}x|!!_LueCuHH1|VmA&0=J}EK8!MVEk&S zPrF=d1j$KVr?tLSgb8Xp8`Hb)l_zq@n^FPB>}KP%F4j@$P0h6X)pa+z6fpkAGAMHW zuFM~XUCzx>%=0SPfkUX=`^2u@=pE}KC#@>Ik1bqcx$O)|x&fzo+Ezu4WQ2aNa3&n7 z9Y6NWh_ua|rvgx&p`{wes`hdOHe#Ry&rG_MbX!J;1s&>}?_}L~^Y>{R<`*7q~ z9o?-~s;$VkXPSv%=W!AP@J5FB7BqeN)j&XI8zb+Suejais2#5_j>pQv%N&r{b?hya zgJhi}vae(P%gD?*G}F0xWk6qjlvj;U!#@XQ%e=z=ip_#&uq%VAq`tB|7*1J1U{eA^ z)CBL9HmQc|H^(bY?7u+$JuPeY`169DOkf3k2T$)G*us*^4kibjhDC7FLg;Nj(I#U1 z2r;8h88s8WtJhw)a(S&CYA~dZXpPol@B@gH-g?L5K&g}V>%q$}zGtEW-qFW8so2OcPBiR_PGK$9dCnifI@ig^1z>j$Y+DyRc?-ANY7>_AyEip;o)i}}Wh8%3YusXE;&jpCq&JC=NE}es@15+Ef?{cMosU;) zhTAHXe9Tr2+{p4c`*U$`v~x3az3D@Pw=m-1QPN-#e*VtyX09~YUapMm{Tyr&ku8A2 z8O0EKE~>Rmi%PBp4ePglI=Y|Y@Xx3!AF@KDpggbYnwu@_pdBI%+ZBd_QXdm%-O@n;TA4&mv=2gi+nA zaj=lJRkVWiv&^mM`<5pBA_dzjKa^!R{9c)ys$gai6(|82rL8tYRE2=I$nuUOoQBygqU->gnb8SMpLEzH8G(> z7G25Q9Ra?eAp_{9V$fBj3@S&XKdPpN8c|4M;`c?G+?;;X*}ua9M)GExvp1?~Ozsd? zlL{7sWCKc=cUyd0-GMC_g%KoaZ`BhL3UE8>5GoYV0~h&y>CJyORKLNDY@N$$eZC~I z;*}DuWup8v%}=`D7d&&zxUye-uk*`7O7u#?+;i)H6rELARBaSS&kO^Sf^<8ibV-+_ zG*Ux%r*y{*NDE4rfOL0vN~eUBgrqbgA^p#PKUZg-=bZ1``+e8yxe;WIGx};=tcGdI z`)S@K0f?Iv;|ETktDOX0W}knrkVGv1K4ilNsQf@YJ7%4|(U|FQeNg3_>ISq{#DTkZ zJ;1->1-JU-)nd5kjr<=nCaZt@=Oeo?Mnb6MU4 z8Kj3a^zy~qVPdEyr`JhK!G;U_fl!>1gf0|iIMpA1=_oOn@TXTZzQ|U8SlGlQU_W*N z%4rHL<%ONxnzX?eNU&DuKQcB*F&~sl8LO^&TLxP(69eb0Y&~_POw#6NBRoAM(;oj6 z2LZ&dPxT5JEL0$`VFn%s4U~`68#VUjVs&JGh%N86mZkDE+x(DOe&>O(dthR<{4KUZlKQ>KO97Sa6=}sq?5W>26n$X4#_c zDo*6fzYh5Q*>juqW+CEFl1l-yVTy#ODpih4)fM}3xbd+D*G)~;+IfF(e3dx7rvJsu zi}IiAAtY+C1v^QPr&9nPbhC#Y`|34dmj}VH)v>hwUe+vqgFyn2`L(h4e&qJ=eKtY; zXLdUkq6#0+^;ng}UE`C=n?J`kuUF!4+U{^4^x5H;ual#{s$J^jYb>~4mgm!N?bB(b z00MxrQ4#kvr$N-%U+=mE9T6*QYoE+^rMX`fm(I94e|gIvS{k|<#pz0^o+f?%@TUdh z-`%A{-NDGWsX^b;lGggPe)>w|YX(Ld@|ytXoucFjn8w$MXMerwC=ryr6Fe&PJg2&1 z7G3ZHm3`#t;-Il?ynY^C5YSpp5K_ylVV<@1UN{>5PFj?7LPK@!xBsV1&!kmbc5CKJ z5G&(-zR3jrmw26v1HHo1&MvsowDrHSE^=h4Y#GyQjWAz5?v-FyxRJtx zR9i?D_vU?6UR9o7tx*H6Ks>dz>g>jQhG~v;2BFjqa|RKe_Y;aiPe(svW^rmuD>~OF zDXZRXO)yyh=*!~_(AF$n2CWlSQ%Sn4)rNZr3uSG&UpL)tE7RFKH`A2+&|@K_CJEsI zRRNoBM5un@v^XLZB(BOMpV_bgXk0iN091G|<-Gu+e*TgQ4(^Mk(`RF|yNVY&|7ZLF z4tM+cLog`%g^-&8i1qz*$hZXwl<)}s%GJY%CgaV!GJh`ws0I|I^m@sg)TRznEyfrQ zCL=eLLw|qCsTouvOpXlS-Ipyft{ zUWELXuDE>l?109q7Gld5R7-&A(M|NtBMjR2w0_j5g62?-{S4W() znr^g+2&we(WW4t47+X~LC7GGVc@wWj#h*CsZ964ysKCD~| zb0OzbtD)BK985y;6>&bZ)bz<`x(D-<*4vhBZ*I`*JzJRgR}7b~UbHw8r`O&4csE{@ zYA&= z7UuG6zVt%tY5%s|^@IPH-nY#UM!3WYydKi7a%JENfd_*ozv4C4(U?_^`tjHpVHlcz zKhbU8;q#DBm?bXXbX;jG^-j2yw8x;ipeS7Tfwg7Gh@8x9qP#_;*NG%nq$WMf`36P} z`nz`;gTnDF<;XT)q@x&YKI3%C_S9~W9Mk53c zWQz+p;MmE1nWo}SufI^5hwq23`6Y@lJk>0>mU0V*|M~aNfm%ko*dK~uuN`S_bI;)o zo+B~9L>*+(ZIsaBa#t3USUV?jQKgEcVe?P8H;R(7Qe|u9__Y06 zizx1|lUPb}D2}dLBq*YqR#+56DjRP|6oA20xLQ_-7AmF$=H;6vNE(ZPd>@VB+RXEK z_bLfS6iyKJ$Jk{V{#TxE=DK#Retg`FSSWT}-_E%ezWwFt+U#755JGguSjAxP7TPKH z6<6Ain)YQJT`-8MFb3HRzN=^{=iv)@!BrwqQLM1i^nLkx!q`JW)L_wv4Ez0<=hVKupM7@HTU&@?RKJAboV#5`noJcR~(x*hPL5=8VBkD7d%65jR4>U z^}ol$r;y(%AOJC>3#dsMSCnZ4t48j?6=qeb-T1BpQPMb(zw3dr(fSX2GcdR3QYJ`9 zzT$dQMd!FeH_J-FB_YQAwUZw(y%@PaG{azF0Y;VlJJU#y~m?+gY2kz01Jav!Wl0h`bm}KPp4z;fjj4R-cv)SuU zu5x!rHFqoeGfm9NCxRNix;fZ%n7}k4N?#eidr*@tV!`vYFR0j>%EeKZCvH9}o^g-zPrKyOiDHghW!|5biLB7mU-nqQf^yji~Ei$eKWHrj9y=p9|9zhVO61^y z`9cDvcqmMn=;rl1)D}>6%tsI)=5NQIlCj-m8T$dv5;3%T&u7ba$s93j7kJJ5hpyxW zMfSl<>F|Jt67G0IC%T^<(>X#H;#grx&w<&a`;}MLn0}JXkt}6ZOk6bIjZ8CwQ=Klp z*57%;z6@;E4)fyJ!ns+>@Dv(Mncip6M-s{6OTe68>k;hGMl#7`ko#E-!B9ekp9Qo( z6#}hdY@B+&&n|yhp|A^nzu|CenSTcWTp?-j9K^Z9z@ZmucrO|d$DxELQ}~GUF`*il zD6ZsQz!L#s3;pG4TeFb@2AN)c%kfExRD|L0yvqBw5cGlw&P)$4<=V*$Gju8cSXgpC zb<3ntpaFvy0t&WcQ|Ojt6t5(^JFNGobG>n6bIQZwSTi)aea6B0 z%Fb-J^`8g4MFP~T2||FqKt_oa9j7tNj8P%BV*5=&Cp&%HJS+2$9j->`9xh;*+( zx3=d4rPSX@({^Gav6m04%8r6g$8{sV=Yv6(s8Z9q#wrXszd(W*=4Qmd?c7M)yu|8x zd&G}ds}-7RUm^Jtjuv%?MK+NZz;7L1;c0{#3jl{^Ua?_OySB&6XMvy!4-aytON{@} z0H51QO0kFjf6oxWQg`csDG0;ZwpU1jOhTdt6Jn{70-});p#gxUuJb)UD1n+IQd3k7 zHf1I40a1XA?UQpL_`qa73{k9xGifT*pY`#LSBz&*reyJWleD$*a1D$pezDe7kJcIP zL^9u}o=42MFQXM0gMCaAOTAl`8vEJH@Dj>?fB(5dT)gY#^%u|LIo`W0EL5hl9-f368WCm(9xgZDyNH7e0)460njGb zu!l9)Xr$=pwM(Bu+E@y&4z9Jv#9kBup&jHBJddDU%-0kE2%$q*l0SPIgkWUGaM6XxUqeeXCsX_&g~@K&;fVSuvo|%Ygy;E{FuuW(XjY zA8m!En1oQNSOq4cO$M1fvciZ#Ts_E9?cjrs)>uL9-N<0XKU_Q%&n4Mf!n_-qatJBnEj}71n2k9S;X6g_@54q>t%Q zzppPbnpi5TcD??P)xFX*dwWQ-q zcnYXVNCxJYySqs9f8yQ`WkI}R`g7!fIy?_W!GnLmSpk=PLoOumS80Zg;{zW#AABlV zSk@n3BKP%E7QwxnN&q6(YUGv_5lwioo>zBHr;`B+o`+*YA;#dK^QJ#XPM z-DdLWl};d4EvGSzPx8qtuf@N8=YRU{;}v7`)5HD)%PCdzZZqHdshm^B*GjKCs??2# zot^6!@YFz&Uv;|Q)?P*2EK=pk%!i*FlBL1e_BKTGrKo+P4V@S~EtSu2}mkKLY?F z=qJ##U^z*QUTcU&ib1IdP?Y-uremKAu>m|?0A(Mtfezq;uF`f!UMqszB~%pX<~Jvy zm@uIix#Wo^0K$vSlewR${<7>#%@oiejFTMH6C~Q3wLY%4*4t7ImGx?&{W_ z4{8ezeahEnM43~1lxw=S{aE7nHU_S=^BaJ6`kpOPR+yt8YM|#N=sg?W7;331%r~-_ zsON!mtDpj2(wWOcSow zQ(9EroKUD-gJ`ukj8Rhbx2IsS?+f8uqO5(iLHd@HJf%BS&Q^-+)PWD%a0N_c<8L+T zsVL>JmiAr@h%LJVVmqmEStIC#TQ+TjTg9CZRlQ*Jp=mp5ZpLq9O556yLtf=y$1T6U zo}6Po4Gmz4eqQB^#jJVe8o|kV`pKejvZ;;4>)Aw~u#U=m45w>5ZY)-$8rJ9suIR78 z_FtdXGkGGlOkZw&OtoC9g&)yd?krd3Zc-b-d71@c8#D>}aX4g*k;7<0s6L&sc!Wk~@TO6_nEJ?-#@iKmip>JR0+i4>4qHGq@hM)UN z+HZjgwW6=T&3Z#UBYb|cWHuc(eF-vZJT9CJCPW`fE}BK2!|jE~;ES;aNIEHFkKE`A zXTkp#TC=A3ifXQA^abzTSv=iMLchy6Hy)PNu6hR_J5#l$E0*Q6X&}a=qD*joN;3N} z_6JAHBApPxyBA*9GhS40CgKmC#y&J?>U~;o^5~IvOjh|;mQy9HvLb`W!lt&rly8$y zhsyfoynTGD(Afw7i>%F9TKFU>YW)ab!kxA*x`<`Y)mx`~`MlN2gRfM8vuCIt!A$l>cAC3kakn50q=l9fWbf}|6da@Sw} z%_xubJXR8^`jn*TX-P&B%~U8{STURZef70>A|bNT-*yWpLJJKAxQc`HAT*k4Kv$ll zP5f(~SCbs9?Mb;h7Sv(C7sXzG_@LLyqI1+!L?w5*y+Arp#k5xLfBq}NLx-0r-Qc0_ z)!3i<2NUWe_ARz@VpWl?afeF&vBm!Mc78;Ej}lF&y!0ZiBr%&6HnB#tiX`(VB6jvK zy^;1P3WTrVV+d?C2#>rmDoqnMAYYSH9lGY^Py=3(3gLQwZc)X-6;u!@bpJYL{fe9Z zS^1<`)md&9Rl!JR|B1Fm_1M?iKsnm>6@iC;sNzaW5hrCQq~B!sGCg|<`&DPWbggE` z8y!~zVW!>_ z37WUf@eM~uf!n6sxZK*@0$QAZtaQIERlIv%p+eBD5$Ay7in&Y}qxywe!|wNadozEb zoTd^V(xdZ0eV685Tlgb&14xqaL!+^y{Xq={Gr?Fnhf+d=2`HdwW$V#LM>paNA3f1& z6rYX5(d)?7#uVB0v78bU%F^;uVn%79GJ={A3mD`%NkM6B!^iL21>UJOk*b#Y_@%^J zK}Gv&Wy$^mwNC*N^Gmj4`5+n#%3zo3_y*7XTPxDsJZih$@b?+!FUWeQ=tgzLURs(T z--rF!6^VY1zVTZ-)IzTMp=~{ixOkB?cc2dqF64UYVV*gssbBTZAOJxM`hUzr{SAX0 zVdW@Dj)#o(el}W`3k0i*pwT6|Yr|kf0$zrs+4Ap;37R4ZGTriB=BVWBQ+l&+^(aU= zJs#XY6L75DgC*6dHntgv;{020({P1UD?z&1+JJOK0p)=4aVPj;fZIf8$R$1BO#?nJ zFmkX`soFkL=-t3^7$bN{Pdy^?=A8kt-HK33KN+S#B9G}aaG1cT3mD4H!1WxRjqJN5 zPk0TQWSto1W5bN<^=erWK7?(&axN*-V~w${a*JcK@fUXN;}^b_{`R%-)dSx8s7&|} zI-kju#H-XFqD$UEH(+)AJ}yYROcDHgNLI+B*Ndp)R$zXs)ggvtxfg^9qHb4t{+1^l zn5_Ous}>g_WxYx|dqz4}>~(MW)BYsdD<&bTHi5L18q`cx5v*9SZX!PSH z%TAKsVbY7O%+!U4GA**#I-bjaZ6{ek)2!`~-?8O`6l<4s*bSn$gqm_fJK%Yxpu&mF$E^KBh1zL%#VhgCJKlyhU!dWZ?q zm13zojpR}?M~nzR+!n_1$NIt>2Mlaip7Rw~YT~F&SMA+0{i?rahlzPN2`$TXTrjO7 zfG6NFgZZ^O0MUlmnf<4&i;wq!!1i=zf4+GrFFN6GTI<(X#b$CwD> z*2KZFub#z*VEXF?&M_&MeT8{;zEUA`*N>CtR=e_hj8_d1bi-e&qnRq)$@2eD7vn`l za9Qjf!QP&RF)!_>8$Ay9Kq=9CX(5(&#p2KCug-3(yGGOh5~}`VMYrL-H9} zKLd&TQ;Ee|E&=4$t$YU?Som*UWNlSd`Ie^WJGaP6dW>?96-b2V;w)g5EZF=RT^jlN z!iSm9aYy!q7Rta-CGp$X8F>+Jf!2d)ME!5AvyXu<03r_m|M`c1^FeOZl`*JP!vmQ_ zLXBmP4{W;1poXT2)0{YbXAR9FlN2`?PqgaW(3EW#(svpx3!Peod4<{PcJ@Z!Z+~Wc z8#K05XNd?e{DhCB7XKv4ZyBKshg9HoaW&na>3dr$wrQ-LsQ;M@;&{fdOR5)7rAvhl z3Qx>?f4C(=(h2X4bq&`mFo<ZaS~&WfvWLJknT54 zAjj5#3kn+O(ca1UEf?}h7+0%&guY6I@QQ1s+2rn^l7w-SUYJ^bi&8@_{X=D(=bq1H zRegFB_+;{xS6VSYe0lM2ljORmimu7^#VjV!eq!$}OXMg{V=(o3koZIF1Am+U%(>~G+Ark2Mz1yIDEU#62LelGgYs4F zD5v*B54*H9jelj&6&H&#)%ryIj&F%{DPOO;D7`Vov-I|7sZX0}?s}yDW$_f9O`I+X`p6@w} zjFd24eUd8w4t`nL@1w_Wx}GPiOSCJi?1S7MpXK4p%5uKl)$-*y6@H7JRG(pH>80GH z>o(W4Wo5Nr7i}n`4{kE_K6PeE$y)4&!nl4G8O@fs_?c=%+57wMTFjfc>Li#*w0MedYJR7> ztc)IxAAhe{s+gw`FZu+IKb#byk#YY`6y{@_*2OErOAwN}hs{$LqZ@V(bI_9#qtQiU z#O)PkYS}WnzFPZQSWk^2`z{~n%@z@kBg*8FvT)mbfE|!ApuoIQPd(aw*=Qx=g;z`d zZ>!0y9EBnAO8;WoS*28XZDp&N&of?P^%4e09wyn=&xGw$Ik(#Hef&>?m0xI7GYien zM}@9le$?o%(XzsQV$N5{o_SjxA?qbAi!|TSuS`$hjo}NueTjJcqnL8zH^EcAJ})V8 zd*5iUP1VUw%FyHTQ(|KAZgqaOa@kKZohc9!onf-t5odYn`U})k%CFPo+HKk<)9wO~ z@Y*#`I?G-&TodEdUzvp$q1i9F+h`kE}}kl;J>5vH$~wFWQ?fNpFGreZ?;Z8!$d4KE9@;I%_cv3xPM)E5Y8+n zb5>PN_$sSNBX4{!ZtVQQ2Eq=An$*ExK8B98?5bD3M5SdxUGx6^dM9UJUtx#hbzx}< zdzxPri=G#waQEkpI$S}dmc){Gd!*0i)cTlBW3+B4_S90SU99}DO$!=wB$HnMlzn*Y^7&aVBG)~=^{?%~4pt@{W*?u-tk2F0YKyYM4~Ytc?h~Ckn>Pl>U7(DZ zr(Gs;>&0#vZM6gixV2^9F8N_E&ybRh#dn00v+(B(UdMEEdy`fxP7X}IzI`a&|J*n2 zLf&|>cg^X!+7i>zAxurYM1Kf(h`&s-R1FvngK%xed?`1STeKA}ns9h0jL9E#I$L<`(U=uat(z+%xWZa3CGCpM9?QE&eFiW-$+%wB}79#)G2+2 zrvIjc@fBnhvinuvd|Th2AkAllAZLS*epCd&CcqL4bVak41#ojzCy zB5YJgSQq!aTu~QxF#vbv{l^`bH5DH5^}YYeh`Zw72OErk`*iQ12UMxZ+x}7hdY#t= zPyR$%Sn+(mbA_iy0oGG|6MsI3 ze*Es|l(DH%I+Hk}Vs$;G=?cn>Zqs-*|9UBgINFeRnq`Qm0#&7-Rs!5VQ~M%G;g<$| z**R~~fk4qv)_3h&C3=N7CPpC0rV;`NtpjM1IW12D z9}iog4|L@Lcw;=I3^{SP=Y~*3HSUu6ng(G`Yddy&h*UO8sTr%Ldj<|BDjF@G=2I{f zoed9a0#{QkXqE~&Hk4XL!vr-64YZ-brVY$|^}=XnfY(@P-{JbSF{)s6&)SW+ttyUOHA|VXX93l@Ss0Su(Bj9 zAFQkQw^S*YN;b-w z(}Xh!j-n-O#`qhGjirEs{tV!5=?Py_VqCGl2Cx-s$F9|=G_0?HX-zTC;C1iem`^H@ z`TH#{w3VSsl*jjCe|ruleMIjc>UHvS`FHS@SX zV!+m3k2AAEyL;(l@%&>yqhj{Tw+eB0UpUwM4d;3JgwK&JI068rde3I36$@Gt(`w-H zk2?p37nV%DNP@+&%BtMI&kKW2@`2xG2phOVK?A* z>c0g+BeD7#0T$7%cu5j$>wL8Ysi);Er#=^){_8_`2hQ@igN{+}nX*&RY%0AF9QoZK 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 NUviG5Vl#p1=0E;S_9OrR diff --git a/Assets/Audio/SFX.bank b/Assets/Audio/SFX.bank index 5826bbc77d28aa6abbb28a992f2dac2b7c3f6172..e1a85b719d34164595d6a9afcd4d1a25b58b047e 100644 GIT binary patch delta 110995 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

/// Colour of the sphere. /// Position where the sphere wil be centered at. + /// Rotation of the sphere. /// Size of the rendered sphere. - void DrawSphere(const SHVec4& color, const SHVec3& pos, double radius); + void DrawSphere(const SHVec4& color, const SHVec3& pos, const SHVec3& rot, double radius); /*---------------------------------------------------------------------------------*/ /* Persistent Draw Functions */ @@ -246,7 +247,7 @@ namespace SHADE template void drawPoly(std::vector& storage, const SHVec4& color, IterType pointListBegin, IterType pointListEnd); void drawCube(std::vector& storage, const SHVec4& color, const SHVec3& pos, const SHVec3& size); - void drawSphere(std::vector& storage, const SHVec4& color, const SHVec3& pos, double radius); + void drawSphere(std::vector& storage, const SHVec4& color, const SHVec3& pos, const SHVec3& rot, double radius); }; } diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h index c05baddd..6a08f940 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h @@ -111,6 +111,12 @@ namespace SHADE void SetCollisionTag (SHCollisionTag* newCollisionTag) noexcept; + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] virtual SHMatrix GetInertiaTensor (float mass) const noexcept = 0; + protected: /*---------------------------------------------------------------------------------*/ /* Data Members */ diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp index 3f79b381..40671a96 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp @@ -13,6 +13,9 @@ // Primary Header #include "SHSphereCollisionShape.h" +// Project Headers +#include "Math/SHMatrix.h" + namespace SHADE { /*-----------------------------------------------------------------------------------*/ @@ -179,5 +182,17 @@ namespace SHADE return SHSphere::Raycast(ray); } + SHMatrix SHSphereCollisionShape::GetInertiaTensor(float mass) const noexcept + { + static constexpr float TWO_OVER_FIVE = 2.0f / 5.0f; + + const float DIAGONAL = TWO_OVER_FIVE * mass * (Radius * Radius); + + SHMatrix result; + result.m[0][0] = result.m[1][1] = result.m[2][2] = DIAGONAL; + return result; + } + + } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h index 0a8c0321..72ea05f5 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h @@ -96,7 +96,7 @@ namespace SHADE * @return * True if the point is inside the sphere. */ - bool TestPoint (const SHVec3& point) const noexcept override; + [[nodiscard]] bool TestPoint (const SHVec3& point) const noexcept override; /** * @brief @@ -107,9 +107,19 @@ namespace SHADE * An object holding the results of the raycast.
* See the corresponding header for the contents of the object. */ - SHRaycastResult Raycast (const SHRay& ray) const noexcept override; + [[nodiscard]] SHRaycastResult Raycast (const SHRay& ray) const noexcept override; - // TODO: Compute Moment of Inertia + /** + * @brief + * Computes the inertia tensor of the sphere. + * @param mass + * The mass of the sphere. + * @return + * The inertia tensor of the sphere. + */ + [[nodiscard]] SHMatrix GetInertiaTensor (float mass) const noexcept override; + + private: /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/Dynamics/SHMotionState.cpp b/SHADE_Engine/src/Physics/Dynamics/SHMotionState.cpp index 91b5a688..cc014050 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHMotionState.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHMotionState.cpp @@ -80,6 +80,15 @@ namespace SHADE position = newPosition; } + void SHMotionState::ForceOrientation(const SHQuaternion& newOrientation) noexcept + { + hasMoved = true; + + prevOrientation = newOrientation; + orientation = newOrientation; + } + + void SHMotionState::IntegratePosition(const SHVec3& velocity, float dt) noexcept { // Velocities are 0 when objects are static or sleeping. We do not want to integrate them here. @@ -91,10 +100,32 @@ namespace SHADE position += velocity * dt; } + void SHMotionState::IntegrateOrientation(const SHVec3& velocity, float dt) noexcept + { + // Velocities are 0 when objects are static or sleeping. We do not want to integrate them here. + // This call should never reach here. + + hasMoved = true; + + prevOrientation = orientation; + + SHQuaternion qv{ velocity.x * dt, velocity.y * dt, velocity.z * dt, 0.0f }; + qv *= orientation; + + orientation += qv * 0.5f; + orientation = SHQuaternion::Normalise(orientation); + } + + SHVec3 SHMotionState::InterpolatePositions(float factor) const noexcept { return SHVec3::ClampedLerp(prevPosition, position, factor); } + SHQuaternion SHMotionState::InterpolateOrientations(float factor) const noexcept + { + return SHQuaternion::ClampedSlerp(prevOrientation, orientation, factor); + } + } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHMotionState.h b/SHADE_Engine/src/Physics/Dynamics/SHMotionState.h index 4cff9a8f..778e6a8b 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHMotionState.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHMotionState.h @@ -11,8 +11,10 @@ #pragma once // Project Headers +#include "Math/SHQuaternion.h" #include "Math/Vector/SHVec3.h" + namespace SHADE { /*-------------------------------------------------------------------------------------*/ @@ -30,10 +32,13 @@ namespace SHADE /* Data Members */ /*-----------------------------------------------------------------------------------*/ - bool hasMoved; + bool hasMoved; - SHVec3 position; - SHVec3 prevPosition; + SHVec3 position; + SHVec3 prevPosition; + + SHQuaternion orientation; + SHQuaternion prevOrientation; /*-----------------------------------------------------------------------------------*/ /* Constructors & Destructor */ @@ -65,17 +70,36 @@ namespace SHADE * @param newPosition * The new position to set. */ - void ForcePosition (const SHVec3& newPosition) noexcept; + void ForcePosition (const SHVec3& newPosition) noexcept; /** * @brief - * Integrates the positions using velocity with respect to time. + * Forcefully sets the orientation. Meant to be used when transform overrides the rigid body + * orientations + * @param newOrientation + * The new orientation to set. + */ + void ForceOrientation (const SHQuaternion& newOrientation) noexcept; + + /** + * @brief + * Integrates the positions using linear velocity with respect to time. * @param velocity - * The velocity to integrate. + * The linear velocity to integrate. * @param dt * The delta time to integrate with respect to. */ - void IntegratePosition (const SHVec3& velocity, float dt) noexcept; + void IntegratePosition (const SHVec3& velocity, float dt) noexcept; + + /** + * @brief + * Integrates the orientation using angular velocity with respect to time. + * @param velocity + * The angular velocity to integrate. + * @param dt + * The delta time to integrate with respect to. + */ + void IntegrateOrientation (const SHVec3& velocity, float dt) noexcept; /** * @brief @@ -85,7 +109,17 @@ namespace SHADE * @returns * The interpolated position meant for rendering. */ - SHVec3 InterpolatePositions (float factor) const noexcept; + SHVec3 InterpolatePositions (float factor) const noexcept; + + /** + * @brief + * Interpolates the orientation between the previous and the last using a given factor. + * @param factor + * The factor to interpolate by. Should be between 0 & 1. + * @returns + * The interpolated orientation meant for rendering. + */ + SHQuaternion InterpolateOrientations (float factor) const noexcept; }; diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp index 7422d6ff..b1b94792 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp @@ -74,7 +74,10 @@ namespace SHADE void SHPhysicsWorld::Step(float dt) { for (auto* rigidBody : rigidBodies) + { + rigidBody->ComputeWorldData(); integrateForces(*rigidBody, dt); + } for (auto* rigidBody : rigidBodies) integrateVelocities(*rigidBody, dt); @@ -93,10 +96,14 @@ namespace SHADE const SHVec3 LINEAR_ACCELERATION = rigidBody.accumulatedForce * rigidBody.invMass; const SHVec3 GRAVITATIONAL_ACCELERATION = rigidBody.IsGravityEnabled() ? settings.gravity * rigidBody.gravityScale : SHVec3::Zero; - rigidBody.linearVelocity += (LINEAR_ACCELERATION + GRAVITATIONAL_ACCELERATION) * dt; + rigidBody.linearVelocity += (LINEAR_ACCELERATION + GRAVITATIONAL_ACCELERATION) * dt; + + // Integrate torque into angular velocity + rigidBody.angularVelocity += rigidBody.worldInvInertia * (rigidBody.accumulatedTorque * dt); // Apply drag (exponentially applied) - rigidBody.linearVelocity *= 1.0f / (1.0f + dt * rigidBody.linearDrag); + rigidBody.linearVelocity *= 1.0f / (1.0f + dt * rigidBody.linearDrag); + rigidBody.angularVelocity *= 1.0f / (1.0f + dt * rigidBody.angularDrag); } void SHPhysicsWorld::integrateVelocities(SHRigidBody& rigidBody, float dt) const noexcept @@ -111,7 +118,13 @@ namespace SHADE , rigidBody.GetFreezePositionZ() ? 0.0f : rigidBody.linearVelocity.z }; - // TODO: Enforce angular constraints + // Enforce angular constraints + rigidBody.angularVelocity = SHVec3 + { + rigidBody.GetFreezeRotationX() ? 0.0f : rigidBody.angularVelocity.x + , rigidBody.GetFreezeRotationY() ? 0.0f : rigidBody.angularVelocity.y + , rigidBody.GetFreezeRotationZ() ? 0.0f : rigidBody.angularVelocity.z + }; }; // Always reset movement flag @@ -129,13 +142,13 @@ namespace SHADE ENFORCE_CONSTRAINED_VELOCITIES(rigidBody); rigidBody.motionState.IntegratePosition(rigidBody.linearVelocity, dt); - // TODO: Integrate orientations + rigidBody.motionState.IntegrateOrientation(rigidBody.angularVelocity, dt); // Sync with collider transforms if a collider is present if (rigidBody.collider) { rigidBody.collider->SetPosition(rigidBody.motionState.position); - // TODO: Sync orientations + rigidBody.collider->SetOrientation(rigidBody.motionState.orientation); rigidBody.collider->RecomputeShapes(); } diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp index e79a8c91..c9f3b097 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp @@ -27,9 +27,10 @@ namespace SHADE : entityID { eid } , collider { nullptr } , bodyType { type } + , gravityScale { 1.0f } , invMass { type == Type::DYNAMIC ? 1.0f : 0.0f } , linearDrag { 0.01f } - , gravityScale { 1.0f } + , angularDrag { 0.01f } , flags { 0U } { // Set default flags @@ -41,14 +42,19 @@ namespace SHADE } SHRigidBody::SHRigidBody(const SHRigidBody& rhs) noexcept - : entityID { rhs.entityID } - , collider { nullptr } - , bodyType { rhs.bodyType } - , invMass { rhs.invMass } - , linearDrag { rhs.linearDrag } - , gravityScale { rhs.gravityScale } - , flags { rhs.flags } - , motionState { rhs.motionState } + : entityID { rhs.entityID } + , collider { nullptr } + , bodyType { rhs.bodyType } + , gravityScale { rhs.gravityScale } + , invMass { rhs.invMass } + , linearDrag { rhs.linearDrag } + , angularDrag { rhs.angularDrag } + , localInvInertia { rhs.localInvInertia } + , worldInvInertia { rhs.worldInvInertia } + , localCentroid { rhs.localCentroid } + , worldCentroid { rhs.worldCentroid } + , flags { rhs.flags } + , motionState { rhs.motionState } { // All other properties are defaulted to 0 to not carry over any potential errors / invalid values. } @@ -57,9 +63,14 @@ namespace SHADE : entityID { rhs.entityID } , collider { nullptr } , bodyType { rhs.bodyType } + , gravityScale { rhs.gravityScale } , invMass { rhs.invMass } , linearDrag { rhs.linearDrag } - , gravityScale { rhs.gravityScale } + , angularDrag { rhs.angularDrag } + , localInvInertia { rhs.localInvInertia } + , worldInvInertia { rhs.worldInvInertia } + , localCentroid { rhs.localCentroid } + , worldCentroid { rhs.worldCentroid } , flags { rhs.flags } , motionState { std::move(rhs.motionState) } { @@ -77,17 +88,24 @@ namespace SHADE return *this; entityID = rhs.entityID; + // Deep copy the collider - *collider = *rhs.collider; - bodyType = rhs.bodyType; - invMass = rhs.invMass; - linearDrag = rhs.linearDrag; - gravityScale = rhs.gravityScale; - flags = rhs.flags; - motionState = rhs.motionState; + *collider = *rhs.collider; + bodyType = rhs.bodyType; + gravityScale = rhs.gravityScale; + invMass = rhs.invMass; + linearDrag = rhs.linearDrag; + angularDrag = rhs.angularDrag; + localInvInertia = rhs.localInvInertia; + worldInvInertia = rhs.worldInvInertia; + localCentroid = rhs.localCentroid; + worldCentroid = rhs.worldCentroid; + flags = rhs.flags; + motionState = rhs.motionState; // All other properties are defaulted to 0 to not carry over any potential errors / invalid values. accumulatedForce = SHVec3::Zero; + accumulatedTorque = SHVec3::Zero; linearVelocity = SHVec3::Zero; angularVelocity = SHVec3::Zero; @@ -97,17 +115,24 @@ namespace SHADE SHRigidBody& SHRigidBody::operator=(SHRigidBody&& rhs) noexcept { entityID = rhs.entityID; + // Deep copy the collider *collider = *rhs.collider; bodyType = rhs.bodyType; + gravityScale = rhs.gravityScale; invMass = rhs.invMass; linearDrag = rhs.linearDrag; - gravityScale = rhs.gravityScale; + angularDrag = rhs.angularDrag; + localInvInertia = rhs.localInvInertia; + worldInvInertia = rhs.worldInvInertia; + localCentroid = rhs.localCentroid; + worldCentroid = rhs.worldCentroid; flags = rhs.flags; motionState = std::move(rhs.motionState); // All other properties are defaulted to 0 to not carry over any potential errors / invalid values. accumulatedForce = SHVec3::Zero; + accumulatedTorque = SHVec3::Zero; linearVelocity = SHVec3::Zero; angularVelocity = SHVec3::Zero; @@ -123,6 +148,11 @@ namespace SHADE return bodyType; } + float SHRigidBody::GetGravityScale() const noexcept + { + return gravityScale; + } + float SHRigidBody::GetMass() const noexcept { return 1.0f/ invMass; @@ -133,16 +163,41 @@ namespace SHADE return linearDrag; } - float SHRigidBody::GetGravityScale() const noexcept + float SHRigidBody::GetAngularDrag() const noexcept { - return gravityScale; + return angularDrag; } - const SHVec3& SHRigidBody::GetAccumulatedForce() const noexcept + const SHMatrix& SHRigidBody::GetLocalInvInertia() const noexcept + { + return localInvInertia; + } + + const SHMatrix& SHRigidBody::GetWorldInvInertia() const noexcept + { + return worldInvInertia; + } + + const SHVec3& SHRigidBody::GetLocalCentroid() const noexcept + { + return localCentroid; + } + + const SHVec3& SHRigidBody::GetWorldCentroid() const noexcept + { + return worldCentroid; + } + + const SHVec3& SHRigidBody::GetForce() const noexcept { return accumulatedForce; } + const SHVec3& SHRigidBody::GetTorque() const noexcept + { + return accumulatedTorque; + } + const SHVec3& SHRigidBody::GetLinearVelocity() const noexcept { return linearVelocity; @@ -226,7 +281,6 @@ namespace SHADE return motionState; } - /*-----------------------------------------------------------------------------------*/ /* Setter Functions Definitions */ /*-----------------------------------------------------------------------------------*/ @@ -245,6 +299,11 @@ namespace SHADE invMass = newType == Type::DYNAMIC ? 1.0f : 0.0f; } + void SHRigidBody::SetGravityScale(float newGravityScale) noexcept + { + gravityScale = newGravityScale; + } + void SHRigidBody::SetMass(float newMass) noexcept { if (bodyType != Type::DYNAMIC) @@ -261,7 +320,7 @@ namespace SHADE invMass = 1.0f / newMass; - // TODO: Recompute inertia tensor + ComputeMassData(); } void SHRigidBody::SetLinearDrag(float newLinearDrag) noexcept @@ -281,16 +340,45 @@ namespace SHADE linearDrag = newLinearDrag; } - void SHRigidBody::SetGravityScale(float newGravityScale) noexcept + void SHRigidBody::SetAngularDrag(float newAngularDrag) noexcept { - gravityScale = newGravityScale; + if (bodyType == Type::STATIC) + { + SHLOG_WARNING("Cannot set angular drag of a Static Body {}", entityID) + return; + } + + if (newAngularDrag < 0.0f) + { + SHLOG_WARNING("Cannot set drag below 0. Object {}'s angular drag will remain unchanged.", entityID) + return; + } + + angularDrag = newAngularDrag; } void SHRigidBody::SetLinearVelocity(const SHVec3& newLinearVelocity) noexcept { + if (bodyType == Type::STATIC) + { + SHLOG_WARNING("Cannot set linear velocity of a Static Body {}", entityID) + return; + } + linearVelocity = newLinearVelocity; } + void SHRigidBody::SetAngularVelocity(const SHVec3& newAngularVelocity) noexcept + { + if (bodyType == Type::STATIC) + { + SHLOG_WARNING("Cannot set angular velocity of a Static Body {}", entityID) + return; + } + + angularVelocity = newAngularVelocity; + } + void SHRigidBody::SetIsActive(bool isActive) noexcept { static constexpr unsigned int FLAG_POS = 0; @@ -304,7 +392,18 @@ namespace SHADE static constexpr unsigned int FLAG_POS = 1; static constexpr uint16_t VALUE = 1U << FLAG_POS; - isSleeping ? flags |= VALUE : flags &= ~VALUE; + if (isSleeping) + { + flags |= VALUE; + + ClearForces(); + linearVelocity = SHVec3::Zero; + angularVelocity = SHVec3::Zero; + } + else + { + flags &= ~VALUE; + } } void SHRigidBody::SetSleepingEnabled(bool enableSleeping) noexcept @@ -411,6 +510,8 @@ namespace SHADE flags |= VALUE; // Reset angular velocity along X-axis angularVelocity.x = 0.0f; + // Set inertia tensor on the x-axis to 0 + localInvInertia.m[0][0] = worldInvInertia.m[0][0] = 0.0f; } else { @@ -428,6 +529,8 @@ namespace SHADE flags |= VALUE; // Reset angular velocity along Y-axis angularVelocity.y = 0.0f; + // Set inertia tensor on the y-axis to 0 + localInvInertia.m[1][1] = worldInvInertia.m[1][1] = 0.0f; } else { @@ -445,6 +548,8 @@ namespace SHADE flags |= VALUE; // Reset angular velocity along Z-axis angularVelocity.z = 0.0f; + // Set inertia tensor on the z-axis to 0 + localInvInertia.m[2][2] = worldInvInertia.m[2][2] = 0.0f; } else { @@ -461,21 +566,60 @@ namespace SHADE if (bodyType != Type::DYNAMIC) return; - accumulatedForce += force; - // Compute torque when force is offset + accumulatedForce += force; + accumulatedTorque += SHVec3::Cross(pos, force); } void SHRigidBody::AddImpulse(const SHVec3& impulse, const SHVec3& pos) noexcept { if (bodyType != Type::DYNAMIC) - return; + return; - linearVelocity += impulse * invMass; + linearVelocity += impulse * invMass; + angularVelocity += worldInvInertia * SHVec3::Cross(pos, impulse); + } + + void SHRigidBody::AddTorque(const SHVec3& torque) noexcept + { + if (bodyType != Type::DYNAMIC) + return; + + accumulatedTorque += torque; } void SHRigidBody::ClearForces() noexcept { - accumulatedForce = SHVec3::Zero; + accumulatedForce = SHVec3::Zero; + accumulatedTorque = SHVec3::Zero; + } + + void SHRigidBody::ComputeWorldData() noexcept + { + const SHMatrix ROTATION = SHMatrix::Rotate(motionState.orientation); + + // Compute world inertia + worldInvInertia = ROTATION * localInvInertia * SHMatrix::Transpose(ROTATION); + + // Compute world centroid + worldCentroid = (ROTATION * localCentroid) + motionState.position; + } + + void SHRigidBody::ComputeMassData() noexcept + { + // TODO: Compute total inertia and centroid from composited colliders using the Parallel Axis Theorem. + // TODO: If auto mass in enabled, compute total mass based from each collider. + // TODO: If auto mass disabled, compute inertia tensor for each shape using the ratio of its mass / total mass by comparing the volume / total volume. + + if (collider && !collider->GetCollisionShapes().empty()) + { + // HACK: For now, take only the first shape as we are testing with non-composited colliders. We are using the center as the centroid. + const auto* FIRST_SHAPE = collider->GetCollisionShape(0); + localInvInertia = SHMatrix::Inverse(FIRST_SHAPE->GetInertiaTensor(1.0f / invMass)); + } + else + { + localInvInertia.m[0][0] = localInvInertia.m[1][1] = localInvInertia.m[2][2] = invMass; + } } } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h index 1edf5cf6..0d1457d9 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h @@ -12,6 +12,7 @@ // Project Headers #include "ECS_Base/Entity/SHEntity.h" +#include "Math/SHMatrix.h" #include "Math/Vector/SHVec3.h" #include "SHMotionState.h" @@ -73,12 +74,20 @@ namespace SHADE [[nodiscard]] Type GetType () const noexcept; - [[nodiscard]] float GetMass () const noexcept; - [[nodiscard]] float GetLinearDrag () const noexcept; - [[nodiscard]] float GetGravityScale () const noexcept; - [[nodiscard]] const SHVec3& GetAccumulatedForce () const noexcept; + [[nodiscard]] float GetMass () const noexcept; + [[nodiscard]] float GetLinearDrag () const noexcept; + [[nodiscard]] float GetAngularDrag () const noexcept; + + [[nodiscard]] const SHMatrix& GetLocalInvInertia () const noexcept; + [[nodiscard]] const SHMatrix& GetWorldInvInertia () const noexcept; + + [[nodiscard]] const SHVec3& GetLocalCentroid () const noexcept; + [[nodiscard]] const SHVec3& GetWorldCentroid () const noexcept; + + [[nodiscard]] const SHVec3& GetForce () const noexcept; + [[nodiscard]] const SHVec3& GetTorque () const noexcept; [[nodiscard]] const SHVec3& GetLinearVelocity () const noexcept; [[nodiscard]] const SHVec3& GetAngularVelocity () const noexcept; @@ -111,6 +120,8 @@ namespace SHADE */ void SetType (Type newType) noexcept; + void SetGravityScale (float newGravityScale) noexcept; + /** * @brief * Mass is only modifiable for Dynamic bodies. @@ -127,9 +138,16 @@ namespace SHADE */ void SetLinearDrag (float newLinearDrag) noexcept; + /** + * @brief + * Drag is only modifiable for non-Static bodies. + * @param newAngularDrag + * The new drag to set. Values below 0 will be ignored. + */ + void SetAngularDrag (float newAngularDrag) noexcept; - void SetGravityScale (float newGravityScale) noexcept; void SetLinearVelocity (const SHVec3& newLinearVelocity) noexcept; + void SetAngularVelocity (const SHVec3& newAngularVelocity)noexcept; // Flags @@ -151,29 +169,55 @@ namespace SHADE /** * @brief - * Adds a force to the body with an offset from it's center of mass. + * Adds a force to the body with an offset from it's center of mass.
+ * Non-dynamic bodies will be ignored. * @param force * The force to add to the body. * @param pos * The position from the center of mass to offset the force. Defaults to zero. */ - void AddForce (const SHVec3& force, const SHVec3& pos = SHVec3::Zero) noexcept; + void AddForce (const SHVec3& force, const SHVec3& pos = SHVec3::Zero) noexcept; /** * @brief - * Adds an impulse to the body with an offset from it's center of mass. + * Adds an impulse to the body with an offset from it's center of mass.
+ * Non-dynamic bodies will be ignored. * @param impulse * The impulse to add to the body. * @param pos * The position from the center of mass to offset the impulse. Defaults to zero. */ - void AddImpulse (const SHVec3& impulse, const SHVec3& pos = SHVec3::Zero) noexcept; + void AddImpulse (const SHVec3& impulse, const SHVec3& pos = SHVec3::Zero) noexcept; + + /** + * @brief + * Adds torque to rotate the body about it's centroid.
+ * Non-dynamic bodies will be ignored. + * @param torque + * The torque to add to the body. + */ + void AddTorque (const SHVec3& torque) noexcept; /** * @brief * Removes all the forces from the body. */ - void ClearForces () noexcept; + void ClearForces () noexcept; + + /** + * @brief + * Computes the centroid and invInertia in world space. + */ + void ComputeWorldData () noexcept; + + /** + * @brief + * Computes the centroid and inertia of the object.
+ * If auto-mass is enabled, computes the mass.
+ * If auto-mass is disabled, the inertia is computed based on the ratio each shape's volume over the total volume. + * + */ + void ComputeMassData () noexcept; private: /*-----------------------------------------------------------------------------------*/ @@ -186,12 +230,20 @@ namespace SHADE Type bodyType; + float gravityScale; + float invMass; float linearDrag; + float angularDrag; - float gravityScale; + SHMatrix localInvInertia; + SHMatrix worldInvInertia; + + SHVec3 localCentroid; + SHVec3 worldCentroid; SHVec3 accumulatedForce; + SHVec3 accumulatedTorque; SHVec3 linearVelocity; SHVec3 angularVelocity; diff --git a/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp index da848f09..1d1c06bf 100644 --- a/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp +++ b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp @@ -36,18 +36,12 @@ namespace SHADE bool SHRigidBodyComponent::IsGravityEnabled() const noexcept { - if (rigidBody) - return rigidBody->IsGravityEnabled(); - - return false; + return rigidBody ? rigidBody->IsGravityEnabled() : false; } bool SHRigidBodyComponent::IsAllowedToSleep() const noexcept { - if (rigidBody) - return rigidBody->IsSleepingEnabled(); - - return false; + return rigidBody ? rigidBody->IsSleepingEnabled() : false; } bool SHRigidBodyComponent::IsInterpolating() const noexcept @@ -57,131 +51,93 @@ namespace SHADE bool SHRigidBodyComponent::IsSleeping() const noexcept { - if (rigidBody) - return rigidBody->IsSleeping(); - - return false; + return rigidBody ? rigidBody->IsSleeping() : false; } bool SHRigidBodyComponent::GetAutoMass() const noexcept { - if (rigidBody) - return rigidBody->IsAutoMassEnabled(); - - return false; + return rigidBody ? rigidBody->IsAutoMassEnabled() : false; } bool SHRigidBodyComponent::GetFreezePositionX() const noexcept { - if (rigidBody) - return rigidBody->GetFreezePositionX(); - - return false; + return rigidBody ? rigidBody->GetFreezePositionX() : false; } bool SHRigidBodyComponent::GetFreezePositionY() const noexcept { - if (rigidBody) - return rigidBody->GetFreezePositionY(); - - return false; + return rigidBody ? rigidBody->GetFreezePositionY() : false; } bool SHRigidBodyComponent::GetFreezePositionZ() const noexcept { - if (rigidBody) - return rigidBody->GetFreezePositionZ(); - - return false; + return rigidBody ? rigidBody->GetFreezePositionZ() : false; } bool SHRigidBodyComponent::GetFreezeRotationX() const noexcept { - if (rigidBody) - return rigidBody->GetFreezeRotationX(); - - return false; + return rigidBody ? rigidBody->GetFreezeRotationX() : false; } bool SHRigidBodyComponent::GetFreezeRotationY() const noexcept { - if (rigidBody) - return rigidBody->GetFreezeRotationY(); - - return false; + return rigidBody ? rigidBody->GetFreezeRotationY() : false; } bool SHRigidBodyComponent::GetFreezeRotationZ() const noexcept { - if (rigidBody) - return rigidBody->GetFreezeRotationZ(); - - return false; + return rigidBody ? rigidBody->GetFreezeRotationZ() : false; } float SHRigidBodyComponent::GetGravityScale() const noexcept { - if (rigidBody) - return rigidBody->GetGravityScale(); - - return 0.0f; + return rigidBody ? rigidBody->GetGravityScale() : 0.0f; } float SHRigidBodyComponent::GetMass() const noexcept { - if (rigidBody) - return rigidBody->GetMass(); - - return -1.0f; + return rigidBody ? rigidBody->GetMass() : -1.0f; } float SHRigidBodyComponent::GetDrag() const noexcept { - if (rigidBody) - return rigidBody->GetLinearDrag(); - - return -1.0f; + return rigidBody ? rigidBody->GetLinearDrag() : -1.0f; } float SHRigidBodyComponent::GetAngularDrag() const noexcept { - return 0.0f; + return rigidBody ? rigidBody->GetAngularDrag() : -1.0f; } SHVec3 SHRigidBodyComponent::GetForce() const noexcept { - if (rigidBody) - return rigidBody->GetAccumulatedForce(); - - return SHVec3::Zero; + return rigidBody ? rigidBody->GetForce() : SHVec3::Zero; } SHVec3 SHRigidBodyComponent::GetTorque() const noexcept { - return SHVec3::Zero; + return rigidBody ? rigidBody->GetTorque() : SHVec3::Zero; } SHVec3 SHRigidBodyComponent::GetLinearVelocity() const noexcept { - if (rigidBody) - return rigidBody->GetLinearVelocity(); - - return SHVec3::Zero; + return rigidBody ? rigidBody->GetLinearVelocity() : SHVec3::Zero; } SHVec3 SHRigidBodyComponent::GetAngularVelocity() const noexcept { - return SHVec3::Zero; + return rigidBody ? rigidBody->GetAngularVelocity() : SHVec3::Zero; } SHVec3 SHRigidBodyComponent::GetPosition() const noexcept { - if (rigidBody) - return rigidBody->GetMotionState().position; - - return SHVec3::Zero; + return rigidBody ? rigidBody->GetMotionState().position : SHVec3::Zero; } + SHVec3 SHRigidBodyComponent::GetRotation() const noexcept + { + return rigidBody ? rigidBody->GetMotionState().orientation.ToEuler() : SHVec3::Zero; + } /*-----------------------------------------------------------------------------------*/ /* Setter Functions Definitions */ @@ -282,7 +238,8 @@ namespace SHADE void SHRigidBodyComponent::SetAngularDrag(float newAngularDrag) noexcept { - + if (rigidBody) + rigidBody->SetAngularDrag(newAngularDrag); } void SHRigidBodyComponent::SetLinearVelocity(const SHVec3& newLinearVelocity) noexcept @@ -293,7 +250,8 @@ namespace SHADE void SHRigidBodyComponent::SetAngularVelocity(const SHVec3& newAngularVelocity) noexcept { - + if (rigidBody) + rigidBody->SetAngularVelocity(newAngularVelocity); } /*-----------------------------------------------------------------------------------*/ @@ -308,38 +266,67 @@ namespace SHADE void SHRigidBodyComponent::AddForceAtLocalPos(const SHVec3& force, const SHVec3& localPos) const noexcept { - + if (rigidBody) + rigidBody->AddForce(force, localPos); } void SHRigidBodyComponent::AddForceAtWorldPos(const SHVec3& force, const SHVec3& worldPos) const noexcept { if (rigidBody) - rigidBody->AddForce(force, worldPos); + { + // Convert world pos into local space of the body + const SHVec3 LOCAL_POS = worldPos - rigidBody->GetWorldCentroid(); + rigidBody->AddForce(force, LOCAL_POS); + } } void SHRigidBodyComponent::AddRelativeForce(const SHVec3& relativeForce) const noexcept { - + if (rigidBody) + { + // Rotate force into world space + const SHVec3 FORCE = SHVec3::Rotate(relativeForce, rigidBody->GetMotionState().orientation); + rigidBody->AddForce(FORCE); + } } void SHRigidBodyComponent::AddRelativeForceAtLocalPos(const SHVec3& relativeForce, const SHVec3& localPos) const noexcept { - + if (rigidBody) + { + // Rotate force into world space + const SHVec3 FORCE = SHVec3::Rotate(relativeForce, rigidBody->GetMotionState().orientation); + rigidBody->AddForce(FORCE, localPos); + } } void SHRigidBodyComponent::AddRelativeForceAtWorldPos(const SHVec3& relativeForce, const SHVec3& worldPos) const noexcept { - + if (rigidBody) + { + // Rotate force into world space + const SHVec3 FORCE = SHVec3::Rotate(relativeForce, rigidBody->GetMotionState().orientation); + // Convert world pos into local space of the body + const SHVec3 LOCAL_POS = worldPos - rigidBody->GetWorldCentroid(); + + rigidBody->AddForce(FORCE, LOCAL_POS); + } } void SHRigidBodyComponent::AddTorque(const SHVec3& torque) const noexcept { - + if (rigidBody) + rigidBody->AddTorque(torque); } void SHRigidBodyComponent::AddRelativeTorque(const SHVec3& relativeTorque) const noexcept { - + if (rigidBody) + { + // Rotate force into world space + const SHVec3 TORQUE = SHVec3::Rotate(relativeTorque, rigidBody->GetMotionState().orientation); + rigidBody->AddTorque(TORQUE); + } } void SHRigidBodyComponent::ClearForces() const noexcept @@ -348,11 +335,6 @@ namespace SHADE rigidBody->ClearForces(); } - void SHRigidBodyComponent::ClearTorque() const noexcept - { - - } - /*-----------------------------------------------------------------------------------*/ /* Public Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h index 7d377184..d215c3f8 100644 --- a/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h +++ b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h @@ -93,6 +93,7 @@ namespace SHADE [[nodiscard]] SHVec3 GetAngularVelocity () const noexcept; [[nodiscard]] SHVec3 GetPosition () const noexcept; + [[nodiscard]] SHVec3 GetRotation () const noexcept; /*---------------------------------------------------------------------------------*/ /* Setter Functions */ @@ -138,7 +139,6 @@ namespace SHADE void AddRelativeTorque (const SHVec3& relativeTorque) const noexcept; void ClearForces () const noexcept; - void ClearTorque () const noexcept; private: /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp index 8e7450ce..200642e1 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp @@ -62,15 +62,23 @@ namespace SHADE if (!MOTION_STATE) continue; - SHVec3 renderPosition = rigidBodyComponent.IsInterpolating() ? MOTION_STATE.InterpolatePositions(FACTOR) : MOTION_STATE.position; + if (rigidBodyComponent.IsInterpolating()) + { + const SHVec3 RENDER_POSITION = MOTION_STATE.InterpolatePositions(FACTOR); + const SHQuaternion RENDER_ORIENTATION = MOTION_STATE.InterpolateOrientations(FACTOR); + + transformComponent->SetWorldPosition(RENDER_POSITION); + transformComponent->SetWorldOrientation(RENDER_ORIENTATION); + } + else + { + transformComponent->SetWorldPosition(MOTION_STATE.position); + transformComponent->SetWorldOrientation(MOTION_STATE.orientation); + } /* * TODO: Test if the scene graph transforms abides by setting world position. Collisions will ignore the scene graph hierarchy. */ - - - transformComponent->SetWorldPosition(renderPosition); - // TODO: SetOrientation } // Collision & Trigger messages diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp index c03571db..a26a414d 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp @@ -54,7 +54,7 @@ namespace SHADE SHMotionState& motionState = physicsObject.rigidBody->GetMotionState(); motionState.ForcePosition(TRANSFORM_COMPONENT->GetWorldPosition()); - // TODO: Force Orientation + motionState.ForceOrientation(TRANSFORM_COMPONENT->GetWorldOrientation()); } if (physicsObject.collider) diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp index 77241ae9..62654dbb 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp @@ -120,7 +120,10 @@ namespace SHADE case SHCollisionShape::Type::SPHERE: { const SHSphereCollisionShape* SPHERE = dynamic_cast(SHAPE); - drawSphere(debugDrawSystem, *SPHERE); + + // Compute rotation of sphere + const SHVec3 ROTATION = collider.GetOrientation().ToEuler() + SPHERE->GetRotationOffset(); + drawSphere(debugDrawSystem, *SPHERE, ROTATION); break; } @@ -137,10 +140,10 @@ namespace SHADE } } - void SHPhysicsDebugDrawSystem::drawSphere(SHDebugDrawSystem* debugDrawSystem, const SHSphereCollisionShape& sphere) noexcept + void SHPhysicsDebugDrawSystem::drawSphere(SHDebugDrawSystem* debugDrawSystem, const SHSphereCollisionShape& sphere, const SHVec3& rotation) noexcept { const SHColour& DRAW_COLOUR = DEBUG_DRAW_COLOURS[SHUtilities::ConvertEnum(sphere.IsTrigger() ? Colours::TRIGGER : Colours::COLLIDER)]; - debugDrawSystem->DrawSphere(DRAW_COLOUR, sphere.GetCenter(), sphere.GetWorldRadius()); + debugDrawSystem->DrawSphere(DRAW_COLOUR, sphere.GetCenter(), rotation, sphere.GetWorldRadius()); } void SHPhysicsDebugDrawSystem::drawContact(SHDebugDrawSystem* debugDrawSystem, const DebugDrawInfo::Contact& contactInfo) noexcept diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h index 423ab62a..1eb4e967 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h +++ b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h @@ -138,8 +138,8 @@ namespace SHADE SHEventHandle onColliderDraw(SHEventPtr onColliderDrawEvent); - static void drawCollider (SHDebugDrawSystem* debugDrawSystem, const SHCollider& collider) noexcept; - static void drawSphere (SHDebugDrawSystem* debugDrawSystem, const SHSphereCollisionShape& sphere) noexcept; + static void drawCollider (SHDebugDrawSystem* debugDrawSystem, const SHCollider& collider) noexcept; + static void drawSphere (SHDebugDrawSystem* debugDrawSystem, const SHSphereCollisionShape& sphere, const SHVec3& rotation) noexcept; static void drawContact (SHDebugDrawSystem* debugDrawSystem, const DebugDrawInfo::Contact& contactInfo) noexcept; static void drawRaycast (SHDebugDrawSystem* debugDrawSystem, const DebugDrawInfo::Raycast& raycastInfo) noexcept; diff --git a/SHADE_Engine/src/Tools/SHDebugDraw.cpp b/SHADE_Engine/src/Tools/SHDebugDraw.cpp index b8aa8b0e..e8c8af9d 100644 --- a/SHADE_Engine/src/Tools/SHDebugDraw.cpp +++ b/SHADE_Engine/src/Tools/SHDebugDraw.cpp @@ -68,7 +68,7 @@ namespace SHADE void SHDebugDraw::Sphere(const SHVec4& color, const SHVec3& pos, double radius) { - dbgDrawSys->DrawSphere(color, pos, radius); + dbgDrawSys->DrawSphere(color, pos, SHVec3::Zero, radius); } void SHDebugDraw::PersistentLine(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt) diff --git a/SHADE_Managed/src/Components/RigidBody.cxx b/SHADE_Managed/src/Components/RigidBody.cxx index 7b3604f2..6237f3a9 100644 --- a/SHADE_Managed/src/Components/RigidBody.cxx +++ b/SHADE_Managed/src/Components/RigidBody.cxx @@ -217,10 +217,4 @@ namespace SHADE { return Convert::ToCLI(GetNativeComponent()->GetTorque()); } - - void RigidBody::ClearTorque() - { - GetNativeComponent()->ClearTorque(); - } - } \ No newline at end of file diff --git a/SHADE_Managed/src/Components/RigidBody.hxx b/SHADE_Managed/src/Components/RigidBody.hxx index 8bfe34aa..8293cca4 100644 --- a/SHADE_Managed/src/Components/RigidBody.hxx +++ b/SHADE_Managed/src/Components/RigidBody.hxx @@ -155,7 +155,6 @@ namespace SHADE void AddRelativeTorque(Vector3 relativeForce); Vector3 GetTorque(); - void ClearTorque(); }; } \ No newline at end of file From b6ab7b44d9d00c2b0bf0e35f5888ce6c893b90b9 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Thu, 15 Dec 2022 02:19:30 +0800 Subject: [PATCH 043/275] Renamed DrawWireBox to DrawWireCube --- .../src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp | 6 +++--- .../src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp index 4f88966c..926adae8 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp @@ -167,9 +167,9 @@ namespace SHADE DrawLineLoop({ p1, p2, p3 }, color, depthTested); } - void SHDebugDrawSystem::DrawWireBox(const SHMatrix& matrix, const SHColour& color, bool depthTested) + void SHDebugDrawSystem::DrawWireCube(const SHMatrix& matrix, const SHColour& color, bool depthTested) { - drawWireBox(getMeshBatch(false, depthTested), matrix, color); + drawWireCube(getMeshBatch(false, depthTested), matrix, color); } /*-----------------------------------------------------------------------------------*/ @@ -214,7 +214,7 @@ namespace SHADE batch.Points.emplace_back(end, color); } - void SHDebugDrawSystem::drawWireBox(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color) + void SHDebugDrawSystem::drawWireCube(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color) { const auto* GFX_SYSTEM = SHSystemManager::GetSystem(); diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h index 035357e3..c913de84 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h @@ -202,7 +202,7 @@ namespace SHADE void drawLine(LinesBatch& batch, const SHVec3& start, const SHVec3& end, const SHColour& color); template void drawLineLoop(LinesBatch& batch, IterType pointListBegin, IterType pointListEnd, const SHColour& color); - void drawWireBox(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color); + void drawWireCube(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color); /*---------------------------------------------------------------------------------*/ /* Helper Batch Functions - Lines */ From 78575b11e4b95152c9c24f37403abd04a9664adb Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Thu, 15 Dec 2022 18:08:12 +0800 Subject: [PATCH 044/275] Added debug draw of wire spheres, filled cube and filled sphere --- SHADE_Application/src/Scenes/SBTestScene.cpp | 2 - .../MiddleEnd/Interface/SHDebugDrawSystem.cpp | 186 ++++++++++++++++-- .../MiddleEnd/Interface/SHDebugDrawSystem.h | 23 ++- SHADE_Engine/src/Tools/SHDebugDraw.cpp | 61 ++++-- SHADE_Engine/src/Tools/SHDebugDraw.h | 44 ++--- SHADE_Managed/src/Utility/Gizmos.cxx | 4 +- 6 files changed, 244 insertions(+), 76 deletions(-) diff --git a/SHADE_Application/src/Scenes/SBTestScene.cpp b/SHADE_Application/src/Scenes/SBTestScene.cpp index bcc7f09d..a5edd124 100644 --- a/SHADE_Application/src/Scenes/SBTestScene.cpp +++ b/SHADE_Application/src/Scenes/SBTestScene.cpp @@ -231,8 +231,6 @@ namespace Sandbox SHADE::SHScriptEngine* scriptEngine = static_cast(SHADE::SHSystemManager::GetSystem()); scriptEngine->RemoveAllScripts(testObj); } - - SHDebugDraw::Cube(SHColour::CRIMSON, SHVec3(1.0f, 0.0f, 0.0f), SHVec3(1.0f, 1.0f, 1.0f)); } void SBTestScene::Render() diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp index 926adae8..f3527ed2 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp @@ -67,14 +67,18 @@ namespace SHADE } } - // Set up persistent batches if it was wiped - if (system->persistentBuffersCleared[FRAME_IDX]) + // Set up persistent batches if it was changed + if (system->persistentBuffersUpdated[FRAME_IDX]) { for (auto& batch : system->persistentLineBatches) { system->prepareBatch(batch, FRAME_IDX); } - system->persistentBuffersCleared[FRAME_IDX] = false; + for (auto& batch : system->persistentMeshBatches) + { + system->prepareBatch(batch, FRAME_IDX); + } + system->persistentBuffersUpdated[FRAME_IDX] = false; } } @@ -113,6 +117,9 @@ namespace SHADE cmdBuffer->BeginLabeledSegment("SHDebugDraw (Meshes)"); { renderBatch(meshBatches[static_cast(MeshRenderMode::WireNoDepthTest)], cmdBuffer, FRAME_IDX); + renderBatch(meshBatches[static_cast(MeshRenderMode::FilledNoDepthTest)], cmdBuffer, FRAME_IDX); + renderBatch(persistentMeshBatches[static_cast(MeshRenderMode::WireNoDepthTest)], cmdBuffer, FRAME_IDX); + renderBatch(persistentMeshBatches[static_cast(MeshRenderMode::FilledNoDepthTest)], cmdBuffer, FRAME_IDX); } cmdBuffer->EndLabeledSegment(); } @@ -134,6 +141,9 @@ namespace SHADE cmdBuffer->BeginLabeledSegment("SHDebugDraw (Meshes)"); { renderBatch(meshBatches[static_cast(MeshRenderMode::WireDepthTested)], cmdBuffer, FRAME_IDX); + renderBatch(meshBatches[static_cast(MeshRenderMode::FilledDepthTested)], cmdBuffer, FRAME_IDX); + renderBatch(persistentMeshBatches[static_cast(MeshRenderMode::WireDepthTested)], cmdBuffer, FRAME_IDX); + renderBatch(persistentMeshBatches[static_cast(MeshRenderMode::FilledDepthTested)], cmdBuffer, FRAME_IDX); } cmdBuffer->EndLabeledSegment(); } @@ -167,11 +177,31 @@ namespace SHADE DrawLineLoop({ p1, p2, p3 }, color, depthTested); } + void SHDebugDrawSystem::DrawQuad(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHVec3& p4, const SHColour& color, bool depthTested) + { + DrawLineLoop({ p1, p2, p3, p4 }, color, depthTested); + } + void SHDebugDrawSystem::DrawWireCube(const SHMatrix& matrix, const SHColour& color, bool depthTested) { drawWireCube(getMeshBatch(false, depthTested), matrix, color); } + void SHDebugDrawSystem::DrawWireSphere(const SHMatrix& matrix, const SHColour& color /*= SHColour::WHITE*/, bool depthTested /*= false*/) + { + drawWireSphere(getMeshBatch(false, depthTested), matrix, color); + } + + void SHDebugDrawSystem::DrawCube(const SHMatrix& matrix, const SHColour& color /*= SHColour::WHITE*/, bool depthTested /*= false*/) + { + drawCube(getMeshBatch(true, depthTested), matrix, color); + } + + void SHDebugDrawSystem::DrawSphere(const SHMatrix& matrix, const SHColour& color /*= SHColour::WHITE*/, bool depthTested /*= false*/) + { + drawSphere(getMeshBatch(true, depthTested), matrix, color); + } + /*-----------------------------------------------------------------------------------*/ /* Persistent Draw Functions */ /*-----------------------------------------------------------------------------------*/ @@ -179,23 +209,55 @@ namespace SHADE { // Insert into the batch drawLine(getPersistentLineBatch(depthTested), start, end, color); + markPersistentDrawsDirty(); } void SHDebugDrawSystem::DrawPersistentLineLoop(std::initializer_list points, const SHColour& color, bool depthTested) { DrawPersistentLineLoop(points.begin(), points.end(), color, depthTested); + markPersistentDrawsDirty(); } void SHDebugDrawSystem::DrawPersistentTri(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHColour& color, bool depthTested) { DrawPersistentLineLoop({ p1, p2, p3 }, color, depthTested); + markPersistentDrawsDirty(); + } + + void SHDebugDrawSystem::DrawPersistentQuad(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHVec3& p4, const SHColour& color, bool depthTested) + { + DrawPersistentLineLoop({ p1, p2, p3, p4 }, color, depthTested); + markPersistentDrawsDirty(); + } + + void SHDebugDrawSystem::DrawPersistentWireCube(const SHMatrix& matrix, const SHColour& color, bool depthTested) + { + drawWireCube(getPersistentMeshBatch(false, depthTested), matrix, color); + markPersistentDrawsDirty(); + } + + void SHDebugDrawSystem::DrawPersistentWireSphere(const SHMatrix& matrix, const SHColour& color, bool depthTested) + { + drawWireSphere(getPersistentMeshBatch(false, depthTested), matrix, color); + markPersistentDrawsDirty(); + } + + void SHDebugDrawSystem::DrawPersistentCube(const SHMatrix& matrix, const SHColour& color, bool depthTested) + { + drawCube(getPersistentMeshBatch(true, depthTested), matrix, color); + markPersistentDrawsDirty(); + } + + void SHDebugDrawSystem::DrawPersistentSphere(const SHMatrix& matrix, const SHColour& color, bool depthTested) + { + drawSphere(getPersistentMeshBatch(true, depthTested), matrix, color); + markPersistentDrawsDirty(); } void SHDebugDrawSystem::ClearPersistentDraws() { for (auto& batch : persistentLineBatches) batch.Points.clear(); - for (bool& cleared : persistentBuffersCleared) - cleared = true; + markPersistentDrawsDirty(); } /*-----------------------------------------------------------------------------------*/ @@ -214,29 +276,56 @@ namespace SHADE batch.Points.emplace_back(end, color); } - void SHDebugDrawSystem::drawWireCube(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color) + void SHDebugDrawSystem::drawMesh(Handle mesh, MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color) { - - const auto* GFX_SYSTEM = SHSystemManager::GetSystem(); - if (!GFX_SYSTEM) - { - SHLOG_ERROR("[DebugDraw] Attempted to do debug draw without a graphics system."); - return; - } - - Handle box = GFX_SYSTEM->GetMeshPrimitive(PrimitiveType::LineCube); - // Create if doesn't exist - if (!batch.SubBatches.contains(box)) + if (!batch.SubBatches.contains(mesh)) { MeshBatch::MultiDrawSet set; - set.Mesh = box; - batch.SubBatches.emplace(box, std::move(set)); + set.Mesh = mesh; + batch.SubBatches.emplace(mesh, std::move(set)); } // Add to the batch - batch.SubBatches[box].InstanceTransforms.emplace_back(transformMatrix); - batch.SubBatches[box].InstanceColors.emplace_back(color); + auto& subBatch = batch.SubBatches[mesh]; + subBatch.InstanceTransforms.emplace_back(transformMatrix); + subBatch.InstanceColors.emplace_back(color); + } + + void SHDebugDrawSystem::drawWireCube(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color) + { + drawMesh + ( + gfxSystem->GetMeshPrimitive(PrimitiveType::LineCube), + batch, transformMatrix, color + ); + } + + void SHDebugDrawSystem::drawWireSphere(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color) + { + drawMesh + ( + gfxSystem->GetMeshPrimitive(PrimitiveType::Sphere), + batch, transformMatrix, color + ); + } + + void SHDebugDrawSystem::drawCube(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color) + { + drawMesh + ( + gfxSystem->GetMeshPrimitive(PrimitiveType::Cube), + batch, transformMatrix, color + ); + } + + void SHDebugDrawSystem::drawSphere(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color) + { + drawMesh + ( + gfxSystem->GetMeshPrimitive(PrimitiveType::Sphere), + batch, transformMatrix, color + ); } /*-----------------------------------------------------------------------------------*/ @@ -386,6 +475,24 @@ namespace SHADE return meshBatches[static_cast(mode)]; } + SHDebugDrawSystem::MeshBatch& SHDebugDrawSystem::getPersistentMeshBatch(bool filled, bool depthTested) + { + MeshRenderMode mode = {}; + + if (filled) + { + mode = depthTested ? MeshRenderMode::FilledDepthTested + : MeshRenderMode::FilledNoDepthTest; + } + else + { + mode = depthTested ? MeshRenderMode::WireDepthTested + : MeshRenderMode::WireNoDepthTest; + } + + return persistentMeshBatches[static_cast(mode)]; + } + void SHDebugDrawSystem::createMeshBatches() { auto gfxSys = SHSystemManager::GetSystem(); @@ -416,6 +523,28 @@ namespace SHADE meshBatches[static_cast(MeshRenderMode::WireDepthTested)], gfxSys->GetDebugDrawPipeline(DebugDrawPipelineType::LineMeshDepthTested) ); + + // Set up persistent batches + initBatch + ( + persistentMeshBatches[static_cast(MeshRenderMode::FilledNoDepthTest)], + gfxSys->GetDebugDrawPipeline(DebugDrawPipelineType::FilledMeshNoDepthTest) + ); + initBatch + ( + persistentMeshBatches[static_cast(MeshRenderMode::FilledDepthTested)], + gfxSys->GetDebugDrawPipeline(DebugDrawPipelineType::FilledMeshDepthTested) + ); + initBatch + ( + persistentMeshBatches[static_cast(MeshRenderMode::WireNoDepthTest)], + gfxSys->GetDebugDrawPipeline(DebugDrawPipelineType::LineMeshNoDepthTest) + ); + initBatch + ( + persistentMeshBatches[static_cast(MeshRenderMode::WireDepthTested)], + gfxSys->GetDebugDrawPipeline(DebugDrawPipelineType::LineMeshDepthTested) + ); } void SHDebugDrawSystem::initBatch(MeshBatch& batch, Handle pipeline) { @@ -552,5 +681,20 @@ namespace SHADE destroyBatch(meshBatches[static_cast(MeshRenderMode::FilledDepthTested)]); destroyBatch(meshBatches[static_cast(MeshRenderMode::WireNoDepthTest)]); destroyBatch(meshBatches[static_cast(MeshRenderMode::WireDepthTested)]); + + destroyBatch(persistentMeshBatches[static_cast(MeshRenderMode::FilledNoDepthTest)]); + destroyBatch(persistentMeshBatches[static_cast(MeshRenderMode::FilledDepthTested)]); + destroyBatch(persistentMeshBatches[static_cast(MeshRenderMode::WireNoDepthTest)]); + destroyBatch(persistentMeshBatches[static_cast(MeshRenderMode::WireDepthTested)]); } + + /*-----------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------------*/ + void SHDebugDrawSystem::markPersistentDrawsDirty() + { + for (bool& dirty : persistentBuffersUpdated) + dirty = true; + } + } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h index c913de84..99b74855 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h @@ -100,7 +100,11 @@ namespace SHADE template void DrawLineLoop(IterType pointListBegin, IterType pointListEnd, const SHColour& color = SHColour::WHITE, bool depthTested = false); void DrawTri(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHColour& color = SHColour::WHITE, bool depthTested = false); + void DrawQuad(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHVec3& p4, const SHColour& color = SHColour::WHITE, bool depthTested = false); void DrawWireCube(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); + void DrawWireSphere(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); + void DrawCube(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); + void DrawSphere(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); /*---------------------------------------------------------------------------------*/ /* Persistent Draw Functions */ @@ -110,6 +114,11 @@ namespace SHADE template void DrawPersistentLineLoop(IterType pointListBegin, IterType pointListEnd, const SHColour& color = SHColour::WHITE, bool depthTested = false); void DrawPersistentTri(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHColour& color = SHColour::WHITE, bool depthTested = false); + void DrawPersistentQuad(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHVec3& p4, const SHColour& color = SHColour::WHITE, bool depthTested = false); + void DrawPersistentWireCube(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); + void DrawPersistentWireSphere(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); + void DrawPersistentCube(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); + void DrawPersistentSphere(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); /// /// Clears any persistent drawn debug primitives. /// @@ -148,7 +157,6 @@ namespace SHADE // GPU Buffers TripleBuffer VertexBuffers; TripleUInt NumPoints; - }; struct MeshBatch : public Batch @@ -193,8 +201,9 @@ namespace SHADE std::array lineBatches; std::array persistentLineBatches; std::array meshBatches; + std::array persistentMeshBatches; // Tracking - TripleBool persistentBuffersCleared; // TODO: Use this + TripleBool persistentBuffersUpdated = { true, true, true }; /*---------------------------------------------------------------------------------*/ /* Helper Draw Functions */ @@ -202,7 +211,11 @@ namespace SHADE void drawLine(LinesBatch& batch, const SHVec3& start, const SHVec3& end, const SHColour& color); template void drawLineLoop(LinesBatch& batch, IterType pointListBegin, IterType pointListEnd, const SHColour& color); + void drawMesh(Handle mesh, MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color); void drawWireCube(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color); + void drawWireSphere(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color); + void drawCube(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color); + void drawSphere(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color); /*---------------------------------------------------------------------------------*/ /* Helper Batch Functions - Lines */ @@ -220,12 +233,18 @@ namespace SHADE /* Helper Batch Functions - Meshes */ /*---------------------------------------------------------------------------------*/ MeshBatch& getMeshBatch(bool filled, bool depthTested); + MeshBatch& getPersistentMeshBatch(bool filled, bool depthTested); void createMeshBatches(); void initBatch(MeshBatch& batch, Handle pipeline); void prepareBatch(MeshBatch& batch, uint32_t frameIndex); void renderBatch(MeshBatch& batch, Handle cmdBuffer, uint32_t frameIndex); void destroyBatch(MeshBatch& batch);; void destroyMeshBatches(); + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + void markPersistentDrawsDirty(); }; } diff --git a/SHADE_Engine/src/Tools/SHDebugDraw.cpp b/SHADE_Engine/src/Tools/SHDebugDraw.cpp index b04f8629..3b64eabf 100644 --- a/SHADE_Engine/src/Tools/SHDebugDraw.cpp +++ b/SHADE_Engine/src/Tools/SHDebugDraw.cpp @@ -15,6 +15,7 @@ of DigiPen Institute of Technology is prohibited. #include "SHDebugDraw.h" // Project Includes #include "Math/Vector/SHVec4.h" +#include "Math/SHQuaternion.h" #include "Math/SHColour.h" #include "Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h" #include "ECS_Base/Managers/SHSystemManager.h" @@ -48,34 +49,52 @@ namespace SHADE void SHDebugDraw::Tri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3) { - //dbgDrawSys->DrawTri(color, pt1, pt2, pt3); + dbgDrawSys->DrawTri(pt1, pt2, pt3, color); } void SHDebugDraw::Quad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4) { - //dbgDrawSys->DrawQuad(color, pt1, pt2, pt3, pt4); + dbgDrawSys->DrawQuad(pt1, pt2, pt3, pt4, color); } - void SHDebugDraw::Poly(const SHVec4& color, std::initializer_list pointList) + void SHDebugDraw::LineLoop(const SHVec4& color, std::initializer_list pointList) { - //dbgDrawSys->DrawPoly(color, pointList); + dbgDrawSys->DrawLineLoop(pointList, color); } - void SHDebugDraw::Cube(const SHVec4& color, const SHVec3& pos, const SHVec3& size) + void SHDebugDraw::Cube(const SHMatrix& mat, const SHVec4& color) { - //dbgDrawSys->DrawCube(color, pos, size); + dbgDrawSys->DrawCube(mat, color); } - void SHDebugDraw::Sphere(const SHVec4& color, const SHVec3& pos, double radius) + void SHDebugDraw::Sphere(const SHMatrix& mat, const SHVec4& color) { - //dbgDrawSys->DrawSphere(color, pos, radius); + dbgDrawSys->DrawSphere(mat, color); } void SHDebugDraw::WireCube(const SHMatrix& mat, const SHVec4& color) { dbgDrawSys->DrawWireCube(mat, color); } + + void SHDebugDraw::WireCube(const SHVec3& center, const SHVec3& scale, const SHVec4& color) + { + dbgDrawSys->DrawWireCube(SHMatrix::Transform(center, SHQuaternion(), scale), color); + } + void SHDebugDraw::WireSphere(const SHMatrix& mat, const SHVec4& color) + { + dbgDrawSys->DrawWireSphere(mat, color); + } + + void SHDebugDraw::WireSphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color) + { + dbgDrawSys->DrawWireSphere(SHMatrix::Transform(center, SHQuaternion(), scale), color); + } + + /*-----------------------------------------------------------------------------------*/ + /* Persistent Draw Functions */ + /*-----------------------------------------------------------------------------------*/ void SHDebugDraw::PersistentLine(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt) { dbgDrawSys->DrawPersistentLine(startPt, endPt, color); @@ -83,27 +102,37 @@ namespace SHADE void SHDebugDraw::PersistentTri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3) { - //dbgDrawSys->DrawPersistentTri(color, pt1, pt2, pt3); + dbgDrawSys->DrawPersistentTri(pt1, pt2, pt3, color); } void SHDebugDraw::PersistentQuad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4) { - //dbgDrawSys->DrawPersistentQuad(color, pt1, pt2, pt3, pt4); + dbgDrawSys->DrawPersistentQuad(pt1, pt2, pt3, pt4, color); } - void SHDebugDraw::PersistentPoly(const SHVec4& color, std::initializer_list pointList) + void SHDebugDraw::PersistentLineLoop(const SHVec4& color, std::initializer_list pointList) { - //dbgDrawSys->DrawPersistentPoly(color, pointList); + dbgDrawSys->DrawPersistentLineLoop(pointList, color); + } + + void SHDebugDraw::PersistentCube(const SHMatrix& mat, const SHVec4& color) + { + dbgDrawSys->DrawPersistentCube(mat, color); } - void SHDebugDraw::PersistentCube(const SHVec4& color, const SHVec3& pos, const SHVec3& size) + void SHDebugDraw::PersistentSphere(const SHMatrix& mat, const SHVec4& color) { - //dbgDrawSys->DrawPersistentCube(color, pos, size); + dbgDrawSys->DrawPersistentSphere(mat, color); } - void SHDebugDraw::PersistentSphere(const SHVec4& color, const SHVec3& pos, double radius) + void SHDebugDraw::PersistentWireCube(const SHMatrix& mat, const SHVec4& color) { - //dbgDrawSys->DrawPersistentSphere(color, pos, radius); + dbgDrawSys->DrawPersistentWireCube(mat, color); + } + + void SHDebugDraw::PersistentWireSphere(const SHMatrix& mat, const SHVec4& color) + { + dbgDrawSys->DrawPersistentWireSphere(mat, color); } void SHDebugDraw::ClearPersistentDraws() diff --git a/SHADE_Engine/src/Tools/SHDebugDraw.h b/SHADE_Engine/src/Tools/SHDebugDraw.h index f3890e17..e213dc45 100644 --- a/SHADE_Engine/src/Tools/SHDebugDraw.h +++ b/SHADE_Engine/src/Tools/SHDebugDraw.h @@ -77,23 +77,13 @@ namespace SHADE /// /// Colour of the polygon. /// List of points for the polygon. - static void Poly(const SHVec4& color, std::initializer_list pointList); - /// - /// Renders a wireframe cube centered around the position specified in world space. - /// - /// Colour of the cube. - /// Position where the cube wil be centered at. - /// Size of the rendered cube. - static void Cube(const SHVec4& color, const SHVec3& pos, const SHVec3& size); - /// - /// Renders a wireframe sphere centered around the position specified in world space. - /// - /// Colour of the sphere. - /// Position where the sphere wil be centered at. - /// Size of the rendered sphere. - static void Sphere(const SHVec4& color, const SHVec3& pos, double radius); - + static void LineLoop(const SHVec4& color, std::initializer_list pointList); + static void Cube(const SHMatrix& mat, const SHVec4& color); + static void Sphere(const SHMatrix& mat, const SHVec4& color); static void WireCube(const SHMatrix& mat, const SHVec4& color); + static void WireCube(const SHVec3& center, const SHVec3& scale, const SHVec4& color); + static void WireSphere(const SHMatrix& mat, const SHVec4& color); + static void WireSphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color); /*---------------------------------------------------------------------------------*/ /* Persistent Draw Functions */ @@ -131,23 +121,11 @@ namespace SHADE /// /// Colour of the polygon. /// List of points for the polygon. - static void PersistentPoly(const SHVec4& color, std::initializer_list pointList); - /// - /// Renders a wireframe cube centered around the position specified in world space - /// that will persist until ClearPersistentDraws() is called. - /// - /// Colour of the cube. - /// Position where the cube wil be centered at. - /// Size of the rendered cube. - static void PersistentCube(const SHVec4& color, const SHVec3& pos, const SHVec3& size); - /// - /// Renders a wireframe sphere centered around the position specified in world space - /// that will persist until ClearPersistentDraws() is called. - /// - /// Colour of the sphere. - /// Position where the sphere wil be centered at. - /// Size of the rendered sphere. - static void PersistentSphere(const SHVec4& color, const SHVec3& pos, double radius); + static void PersistentLineLoop(const SHVec4& color, std::initializer_list pointList); + static void PersistentCube(const SHMatrix& mat, const SHVec4& color); + static void PersistentSphere(const SHMatrix& mat, const SHVec4& color); + static void PersistentWireCube(const SHMatrix& mat, const SHVec4& color); + static void PersistentWireSphere(const SHMatrix& mat, const SHVec4& color); /// /// Clears any persistent drawn debug primitives. /// diff --git a/SHADE_Managed/src/Utility/Gizmos.cxx b/SHADE_Managed/src/Utility/Gizmos.cxx index 21636a5d..fe29e2ff 100644 --- a/SHADE_Managed/src/Utility/Gizmos.cxx +++ b/SHADE_Managed/src/Utility/Gizmos.cxx @@ -54,7 +54,7 @@ namespace SHADE void Gizmos::DrawWireCube(Vector3 center, Vector3 extents, SHADE::Color color) { - SHDebugDraw::Cube(Convert::ToNative(color), Convert::ToNative(center), Convert::ToNative(extents)); + SHDebugDraw::WireCube(Convert::ToNative(center), Convert::ToNative(extents), Convert::ToNative(color)); } void Gizmos::DrawWireSphere(Vector3 center, float radius) @@ -64,6 +64,6 @@ namespace SHADE void Gizmos::DrawWireSphere(Vector3 center, float radius, SHADE::Color color) { - SHDebugDraw::Sphere(Convert::ToNative(color), Convert::ToNative(center), radius); + SHDebugDraw::WireSphere(Convert::ToNative(center), SHVec3(radius, radius, radius), Convert::ToNative(color)); } } From eb06eebc324e32c885f9de59ddd9371611b3d10f Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Thu, 15 Dec 2022 20:30:20 +0800 Subject: [PATCH 045/275] Added ability to debug draw circles --- .../MiddleEnd/Interface/SHDebugDrawSystem.cpp | 20 +++++++ .../MiddleEnd/Interface/SHDebugDrawSystem.h | 3 ++ .../MiddleEnd/Interface/SHGraphicsSystem.cpp | 4 +- .../MiddleEnd/Interface/SHGraphicsSystem.h | 5 +- .../MiddleEnd/Meshes/SHPrimitiveGenerator.cpp | 53 ++++++++++++++++++- .../MiddleEnd/Meshes/SHPrimitiveGenerator.h | 41 ++++++++++++++ SHADE_Engine/src/Tools/SHDebugDraw.cpp | 10 ++++ SHADE_Engine/src/Tools/SHDebugDraw.h | 2 + 8 files changed, 134 insertions(+), 4 deletions(-) diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp index f3527ed2..d77fbeb0 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp @@ -182,6 +182,11 @@ namespace SHADE DrawLineLoop({ p1, p2, p3, p4 }, color, depthTested); } + void SHDebugDrawSystem::DrawCircle(const SHMatrix& matrix, const SHColour& color /*= SHColour::WHITE*/, bool depthTested /*= false*/) + { + drawCircle(getMeshBatch(false, depthTested), matrix, color); + } + void SHDebugDrawSystem::DrawWireCube(const SHMatrix& matrix, const SHColour& color, bool depthTested) { drawWireCube(getMeshBatch(false, depthTested), matrix, color); @@ -229,6 +234,12 @@ namespace SHADE markPersistentDrawsDirty(); } + void SHDebugDrawSystem::DrawPersistentCircle(const SHMatrix& matrix, const SHColour& color, bool depthTested) + { + drawCircle(getPersistentMeshBatch(false, depthTested), matrix, color); + markPersistentDrawsDirty(); + } + void SHDebugDrawSystem::DrawPersistentWireCube(const SHMatrix& matrix, const SHColour& color, bool depthTested) { drawWireCube(getPersistentMeshBatch(false, depthTested), matrix, color); @@ -328,6 +339,15 @@ namespace SHADE ); } + void SHDebugDrawSystem::drawCircle(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color) + { + drawMesh + ( + gfxSystem->GetMeshPrimitive(PrimitiveType::LineCircle), + batch, transformMatrix, color + ); + } + /*-----------------------------------------------------------------------------------*/ /* Helper Batch Functions - Lines */ /*-----------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h index 99b74855..f62a099e 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h @@ -101,6 +101,7 @@ namespace SHADE void DrawLineLoop(IterType pointListBegin, IterType pointListEnd, const SHColour& color = SHColour::WHITE, bool depthTested = false); void DrawTri(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHColour& color = SHColour::WHITE, bool depthTested = false); void DrawQuad(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHVec3& p4, const SHColour& color = SHColour::WHITE, bool depthTested = false); + void DrawCircle(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); void DrawWireCube(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); void DrawWireSphere(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); void DrawCube(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); @@ -115,6 +116,7 @@ namespace SHADE void DrawPersistentLineLoop(IterType pointListBegin, IterType pointListEnd, const SHColour& color = SHColour::WHITE, bool depthTested = false); void DrawPersistentTri(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHColour& color = SHColour::WHITE, bool depthTested = false); void DrawPersistentQuad(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHVec3& p4, const SHColour& color = SHColour::WHITE, bool depthTested = false); + void DrawPersistentCircle(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); void DrawPersistentWireCube(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); void DrawPersistentWireSphere(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); void DrawPersistentCube(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); @@ -216,6 +218,7 @@ namespace SHADE void drawWireSphere(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color); void drawCube(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color); void drawSphere(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color); + void drawCircle(MeshBatch& batch, const SHMatrix& transformMatrix, const SHColour& color); /*---------------------------------------------------------------------------------*/ /* Helper Batch Functions - Lines */ diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp index 8b18d4d3..9a599a07 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp @@ -413,8 +413,9 @@ namespace SHADE // Create default meshes primitiveMeshes[static_cast(PrimitiveType::Cube)] = SHPrimitiveGenerator::Cube(meshLibrary); - primitiveMeshes[static_cast(PrimitiveType::LineCube)] = SHPrimitiveGenerator::LineCube(meshLibrary); primitiveMeshes[static_cast(PrimitiveType::Sphere)] = SHPrimitiveGenerator::Sphere(meshLibrary); + primitiveMeshes[static_cast(PrimitiveType::LineCube)] = SHPrimitiveGenerator::LineCube(meshLibrary); + primitiveMeshes[static_cast(PrimitiveType::LineCircle)] = SHPrimitiveGenerator::LineCircle(meshLibrary); BuildMeshBuffers(); // Create default materials @@ -840,6 +841,7 @@ namespace SHADE case PrimitiveType::Cube: case PrimitiveType::Sphere: case PrimitiveType::LineCube: + case PrimitiveType::LineCircle: return primitiveMeshes[static_cast(type)]; default: return {}; diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h index 609ff3df..75b48c9b 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h @@ -71,9 +71,10 @@ namespace SHADE { Cube, Sphere, - LineCube + LineCube, + LineCircle }; - static constexpr int MAX_PRIMITIVE_TYPES = 3; + static constexpr int MAX_PRIMITIVE_TYPES = 4; enum class DebugDrawPipelineType { LineNoDepthTest, diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp index 93e4be1a..444a6630 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.cpp @@ -10,9 +10,13 @@ Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or disclosure of this file or its contents without the prior written consent of DigiPen Institute of Technology is prohibited. *//*************************************************************************************/ +// Precompiled Header #include "SHpch.h" +// Primary Includes #include "SHPrimitiveGenerator.h" - +// STL Includes +#include +// Project Includes #include "Graphics/MiddleEnd/Interface/SHGraphicsSystem.h" #include "Graphics/MiddleEnd/Interface/SHMeshLibrary.h" @@ -24,6 +28,7 @@ namespace SHADE SHMeshData SHPrimitiveGenerator::cubeMesh; SHMeshData SHPrimitiveGenerator::sphereMesh; SHMeshData SHPrimitiveGenerator::lineCubeMesh; + SHMeshData SHPrimitiveGenerator::lineCircleMesh; /*-----------------------------------------------------------------------------------*/ /* Primitive Generation Functions */ @@ -341,6 +346,52 @@ namespace SHADE return addMeshDataTo(lineCubeMesh, gfxSystem); } + SHMeshData SHPrimitiveGenerator::LineCircle() noexcept + { + SHMeshData mesh; + + // Generate points of the circle + static constexpr int SPLITS = 36; + static constexpr float ANGLE_INCREMENTS = (std::numbers::pi_v * 2.0f) / static_cast(SPLITS); + for (int i = 0; i < SPLITS; ++i) + { + const float ANGLE = ANGLE_INCREMENTS * i; + mesh.VertexPositions.emplace_back(cos(ANGLE) * 0.5f, sin(ANGLE) * 0.5f, 0.0f); + } + + // Generate lines of the circle + for (int i = 1; i < SPLITS; ++i) + { + mesh.Indices.emplace_back(static_cast(i - 1)); + mesh.Indices.emplace_back(static_cast(i)); + } + // Last line to complete the circle + mesh.Indices.emplace_back(static_cast(SPLITS - 1)); + mesh.Indices.emplace_back(static_cast(0)); + + mesh.VertexNormals.resize(mesh.VertexPositions.size()); + mesh.VertexTangents.resize(mesh.VertexPositions.size()); + mesh.VertexTexCoords.resize(mesh.VertexPositions.size()); + + return mesh; + } + + Handle SHPrimitiveGenerator::LineCircle(SHMeshLibrary& meshLibrary) noexcept + { + if (lineCircleMesh.VertexPositions.empty()) + lineCircleMesh = LineCircle(); + + return addMeshDataTo(lineCircleMesh, meshLibrary); + } + + Handle SHPrimitiveGenerator::LineCircle(SHGraphicsSystem& gfxSystem) noexcept + { + if (lineCircleMesh.VertexPositions.empty()) + lineCircleMesh = LineCircle(); + + return addMeshDataTo(lineCircleMesh, gfxSystem); + } + /*-----------------------------------------------------------------------------------*/ /* Helper Functions */ /*-----------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.h b/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.h index 8586b480..9bcd2f3c 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Meshes/SHPrimitiveGenerator.h @@ -154,6 +154,46 @@ namespace SHADE */ /***********************************************************************************/ [[nodiscard]] static Handle LineCube(SHGraphicsSystem& gfxSystem) noexcept; + /***********************************************************************************/ + /*! + \brief + Produces a circle that is comprised only of lines with no diagonal lines and + store the data in a SHMeshData object. + + \return + SHMeshData object containing vertex data for the line circle. + */ + /***********************************************************************************/ + [[nodiscard]] static SHMeshData LineCircle() noexcept; + /***********************************************************************************/ + /*! + \brief + Produces a line circle and constructs a SHMesh using the SHGraphicsSystem + provided. + + \param meshLibrary + Reference to the SHMeshLibrary to produce and store a line circle mesh in. + + \return + SHMesh object that points to the generated line circle mesh in the SHMeshLibrary. + */ + /***********************************************************************************/ + [[nodiscard]] static Handle LineCircle(SHMeshLibrary& meshLibrary) noexcept; + /***********************************************************************************/ + /*! + \brief + Produces a line circle and constructs a SHMesh using the SHGraphicsSystem + provided. + + \param gfxSystem + Reference to the SHGraphicsSystem to produce and store a line circle mesh in. + + \return + SHMesh object that points to the generated line circle mesh in the + SHGraphicsSystem. + */ + /***********************************************************************************/ + [[nodiscard]] static Handle LineCircle(SHGraphicsSystem& gfxSystem) noexcept; private: /*---------------------------------------------------------------------------------*/ @@ -168,5 +208,6 @@ namespace SHADE static SHMeshData cubeMesh; static SHMeshData sphereMesh; static SHMeshData lineCubeMesh; + static SHMeshData lineCircleMesh; }; } \ No newline at end of file diff --git a/SHADE_Engine/src/Tools/SHDebugDraw.cpp b/SHADE_Engine/src/Tools/SHDebugDraw.cpp index 3b64eabf..15a4bedd 100644 --- a/SHADE_Engine/src/Tools/SHDebugDraw.cpp +++ b/SHADE_Engine/src/Tools/SHDebugDraw.cpp @@ -57,6 +57,11 @@ namespace SHADE dbgDrawSys->DrawQuad(pt1, pt2, pt3, pt4, color); } + void SHDebugDraw::Circle(const SHMatrix& mat, const SHVec4& color) + { + dbgDrawSys->DrawCircle(mat, color); + } + void SHDebugDraw::LineLoop(const SHVec4& color, std::initializer_list pointList) { dbgDrawSys->DrawLineLoop(pointList, color); @@ -110,6 +115,11 @@ namespace SHADE dbgDrawSys->DrawPersistentQuad(pt1, pt2, pt3, pt4, color); } + void SHDebugDraw::PersistentCircle(const SHMatrix& mat, const SHVec4& color) + { + dbgDrawSys->DrawPersistentCircle(mat, color); + } + void SHDebugDraw::PersistentLineLoop(const SHVec4& color, std::initializer_list pointList) { dbgDrawSys->DrawPersistentLineLoop(pointList, color); diff --git a/SHADE_Engine/src/Tools/SHDebugDraw.h b/SHADE_Engine/src/Tools/SHDebugDraw.h index e213dc45..5438d8f3 100644 --- a/SHADE_Engine/src/Tools/SHDebugDraw.h +++ b/SHADE_Engine/src/Tools/SHDebugDraw.h @@ -72,6 +72,7 @@ namespace SHADE /// Third point of the quadrilateral. /// Third point of the quadrilateral. static void Quad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4); + static void Circle(const SHMatrix& mat,const SHVec4& color); /// /// Renders a polygon indicated by the specified set of points in world space. /// @@ -115,6 +116,7 @@ namespace SHADE /// Third point of the quadrilateral. /// Third point of the quadrilateral. static void PersistentQuad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4); + static void PersistentCircle(const SHMatrix& mat,const SHVec4& color); /// /// Renders a polygon indicated by the specified set of points in world space that /// will persist until ClearPersistentDraws() is called. From 27c7a1739776e2de7a135e145220f42bd11b1a35 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Thu, 15 Dec 2022 22:59:55 +0800 Subject: [PATCH 046/275] Fixed computation of global inverse inertia tensor --- SHADE_Engine/src/Physics/Collision/SHCollider.cpp | 7 +++++++ SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp index b64bab53..d7ca4b14 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp @@ -17,6 +17,7 @@ #include "Events/SHEvent.h" #include "Math/SHMathHelpers.h" #include "Physics/SHPhysicsEvents.h" +#include "Physics/Dynamics/SHRigidBody.h" namespace SHADE @@ -277,6 +278,9 @@ namespace SHADE SHEventManager::BroadcastEvent(EVENT_DATA, SH_PHYSICS_COLLIDER_ADDED_EVENT); + if (rigidBody) + rigidBody->ComputeMassData(); + return static_cast(shapes.size()); } @@ -316,6 +320,9 @@ namespace SHADE // Broadcast Event for removing a shape SHEventManager::BroadcastEvent(EVENT_DATA, SH_PHYSICS_COLLIDER_REMOVED_EVENT); + if (rigidBody) + rigidBody->ComputeMassData(); + SHLOG_INFO_D("Removing Collision Shape {} from Entity {}", index, entityID) } diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp index c9f3b097..00df0640 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp @@ -598,7 +598,7 @@ namespace SHADE const SHMatrix ROTATION = SHMatrix::Rotate(motionState.orientation); // Compute world inertia - worldInvInertia = ROTATION * localInvInertia * SHMatrix::Transpose(ROTATION); + worldInvInertia = SHMatrix::Transpose(ROTATION) * localInvInertia * ROTATION; // Compute world centroid worldCentroid = (ROTATION * localCentroid) + motionState.position; From 27760a95c98fa3a0a4e0574377f61af0f9483ca5 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Thu, 15 Dec 2022 23:00:15 +0800 Subject: [PATCH 047/275] Tested physics interactions with scripts --- Assets/Scenes/PhysicsSandbox.shade | 12 +- Assets/Scripts/Tests/PhysicsTestObj.cs | 112 ++++++++++++++++++ Assets/Scripts/Tests/PhysicsTestObj.cs.shmeta | 3 + 3 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 Assets/Scripts/Tests/PhysicsTestObj.cs create mode 100644 Assets/Scripts/Tests/PhysicsTestObj.cs.shmeta diff --git a/Assets/Scenes/PhysicsSandbox.shade b/Assets/Scenes/PhysicsSandbox.shade index 0005b35e..0952fd3c 100644 --- a/Assets/Scenes/PhysicsSandbox.shade +++ b/Assets/Scenes/PhysicsSandbox.shade @@ -5,7 +5,7 @@ Components: Transform Component: Translate: {x: 0, y: 1.77475965, z: 0} - Rotate: {x: -0, y: 0, z: -0} + Rotate: {x: 0, y: 0, z: -0} Scale: {x: 1, y: 1, z: 1} IsActive: true RigidBody Component: @@ -13,8 +13,8 @@ Auto Mass: false Mass: 1 Drag: 1 - Angular Drag: 0 - Use Gravity: true + Angular Drag: 1 + Use Gravity: false Gravity Scale: 1 Interpolate: true Sleeping Enabled: true @@ -37,7 +37,11 @@ Position Offset: {x: 0, y: 0, z: 0} Rotation Offset: {x: 0, y: 0, z: 0} IsActive: true - Scripts: ~ + Scripts: + - Type: PhysicsTestObj + Enabled: true + forceAmount: 50 + torqueAmount: 500 - EID: 1 Name: Default IsActive: true diff --git a/Assets/Scripts/Tests/PhysicsTestObj.cs b/Assets/Scripts/Tests/PhysicsTestObj.cs new file mode 100644 index 00000000..c36537b3 --- /dev/null +++ b/Assets/Scripts/Tests/PhysicsTestObj.cs @@ -0,0 +1,112 @@ +using SHADE; +using System; +using System.Collections.Generic; +using static Item; + + +public class PhysicsTestObj : Script +{ + public RigidBody rb { get; set; } + + // Movement input booleans + public enum Direction + { + UP, + DOWN, + FORWARD, + BACK, + LEFT, + RIGHT + }; + + internal bool[] move = new bool[6]; + internal bool[] rotate = new bool[6]; + + internal Vector3[] moveVec = new Vector3[6] + { + Vector3.Up, + Vector3.Down, + Vector3.Back, + Vector3.Forward, + Vector3.Left, + Vector3.Right + }; + + internal Vector3[] rotateVec = new Vector3[6] + { + Vector3.Right, + Vector3.Left, + Vector3.Forward, + Vector3.Down, + Vector3.Up, + Vector3.Down + }; + + internal Input.KeyCode[] moveInputKeys = new Input.KeyCode[6] + { + Input.KeyCode.Space, + Input.KeyCode.LeftControl, + Input.KeyCode.W, + Input.KeyCode.S, + Input.KeyCode.A, + Input.KeyCode.D + }; + + internal Input.KeyCode[] rotateInputKeys = new Input.KeyCode[6] + { + Input.KeyCode.I, + Input.KeyCode.K, + Input.KeyCode.U, + Input.KeyCode.O, + Input.KeyCode.J, + Input.KeyCode.L + }; + + public float forceAmount = 50.0f; + public float torqueAmount = 500.0f; + + protected override void awake() + { + rb = GetComponent(); + + for (int i = 0; i < 6; ++i) + { + move[i] = false; + rotate[i] = false; + } + } + + protected override void update() + { + for (int i = 0; i < 6; ++i) + { + if (Input.GetKeyDown(moveInputKeys[i])) + move[i] = true; + + //if (Input.GetKeyDown(rotateInputKeys[i])) + // rotate[i] = true; + } + } + + protected override void fixedUpdate() + { + for (int i = 0; i < 6; ++i) + { + bool shouldMove = move[i]; + bool shouldRotate = rotate[i]; + + if (shouldMove) + { + Vector3 offset = new Vector3(0.25f, 0.0f, 0.0f); + rb.AddForceAtLocalPos(moveVec[i] * forceAmount, offset); + move[i] = false; + } + + if (shouldRotate) + { + rb.AddTorque(rotateVec[i] * torqueAmount); + rotate[i] = false; + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Tests/PhysicsTestObj.cs.shmeta b/Assets/Scripts/Tests/PhysicsTestObj.cs.shmeta new file mode 100644 index 00000000..8c096651 --- /dev/null +++ b/Assets/Scripts/Tests/PhysicsTestObj.cs.shmeta @@ -0,0 +1,3 @@ +Name: PhysicsTestObj +ID: 159293012 +Type: 9 From 8978515cb99b9c58c8ceb1b14681f1cbbf0c33cf Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Thu, 15 Dec 2022 23:25:49 +0800 Subject: [PATCH 048/275] Reworked SHDebugDraw to fit new interface of SHDebugDrawSystem --- SHADE_Engine/src/Editor/SHEditor.cpp | 59 +-- .../MiddleEnd/Interface/SHDebugDrawSystem.h | 215 +++++++++-- SHADE_Engine/src/Tools/SHDebugDraw.cpp | 159 +++++--- SHADE_Engine/src/Tools/SHDebugDraw.h | 354 +++++++++++++++--- SHADE_Managed/src/Utility/Gizmos.cxx | 2 +- 5 files changed, 636 insertions(+), 153 deletions(-) diff --git a/SHADE_Engine/src/Editor/SHEditor.cpp b/SHADE_Engine/src/Editor/SHEditor.cpp index abddf457..4f1586ac 100644 --- a/SHADE_Engine/src/Editor/SHEditor.cpp +++ b/SHADE_Engine/src/Editor/SHEditor.cpp @@ -271,7 +271,7 @@ namespace SHADE void SHEditor::SetUpGridLines(bool drawGrid, bool drawAxes) { // Clear existing lines - SHDebugDraw::ClearPersistentDraws(); + SHDebugDraw::Persistent::ClearDraws(); static constexpr float DELTA = 1.0f; static constexpr int EXTENT_COUNT = static_cast(500.0f /* TODO: Remove hard code */ / DELTA); @@ -284,30 +284,34 @@ namespace SHADE for (int i = 1; i < EXTENT_COUNT; ++i) { // X-Axis Lines - SHDebugDraw::PersistentLine - ( - GRID_COL, + SHDebugDraw::Persistent::Line + ( SHVec3 { -LINE_HALF_LENGTH, 0.0f, i * DELTA }, - SHVec3 { LINE_HALF_LENGTH, 0.0f, i * DELTA } - ); - SHDebugDraw::PersistentLine - ( + SHVec3 { LINE_HALF_LENGTH, 0.0f, i * DELTA }, GRID_COL, + true + ); + SHDebugDraw::Persistent::Line + ( SHVec3 { -LINE_HALF_LENGTH, 0.0f, i * -DELTA }, - SHVec3 { LINE_HALF_LENGTH, 0.0f, i * -DELTA } + SHVec3 { LINE_HALF_LENGTH, 0.0f, i * -DELTA }, + GRID_COL, + true ); // Y-Axis Lines - SHDebugDraw::PersistentLine + SHDebugDraw::Persistent::Line ( - GRID_COL, SHVec3 { i * DELTA, 0.0f, -LINE_HALF_LENGTH }, - SHVec3 { i * DELTA, 0.0f, LINE_HALF_LENGTH } - ); - SHDebugDraw::PersistentLine - ( + SHVec3 { i * DELTA, 0.0f, LINE_HALF_LENGTH }, GRID_COL, + true + ); + SHDebugDraw::Persistent::Line + ( SHVec3 { -i * DELTA, 0.0f, -LINE_HALF_LENGTH }, - SHVec3 { -i * DELTA, 0.0f, LINE_HALF_LENGTH } + SHVec3 { -i * DELTA, 0.0f, LINE_HALF_LENGTH }, + GRID_COL, + true ); } } @@ -319,25 +323,28 @@ namespace SHADE const SHColour Y_AXIS_COL = drawAxes ? SHColour::GREEN : GRID_COL; const SHColour Z_AXIS_COL = drawAxes ? SHColour::BLUE : GRID_COL; // X - SHDebugDraw::PersistentLine + SHDebugDraw::Persistent::Line ( - X_AXIS_COL, SHVec3 { -LINE_HALF_LENGTH, 0.0f, 0.0f }, - SHVec3 { LINE_HALF_LENGTH, 0.0f, 0.0f } + SHVec3 { LINE_HALF_LENGTH, 0.0f, 0.0f }, + X_AXIS_COL, + true ); // Y - SHDebugDraw::PersistentLine + SHDebugDraw::Persistent::Line ( - Y_AXIS_COL, SHVec3 { 0.0f, -LINE_HALF_LENGTH, 0.0f }, - SHVec3 { 0.0f, LINE_HALF_LENGTH, 0.0f } + SHVec3 { 0.0f, LINE_HALF_LENGTH, 0.0f }, + Y_AXIS_COL, + true ); // Z - SHDebugDraw::PersistentLine + SHDebugDraw::Persistent::Line ( - Z_AXIS_COL, SHVec3 { 0.0f, 0.0f, -LINE_HALF_LENGTH }, - SHVec3 { 0.0f, 0.0f, LINE_HALF_LENGTH } + SHVec3 { 0.0f, 0.0f, LINE_HALF_LENGTH }, + Z_AXIS_COL, + true ); } } @@ -353,7 +360,7 @@ namespace SHADE break; case State::PLAY: default: - SHDebugDraw::ClearPersistentDraws(); + SHDebugDraw::Persistent::ClearDraws(); break; } return eventData->handle; diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h index f62a099e..2978d68e 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h @@ -46,35 +46,12 @@ namespace SHADE { public: /*---------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*---------------------------------------------------------------------------------*/ - /// - /// Defines the rendering mode for debug lines. - /// - enum class LineRenderMode : uint32_t - { - NoDepthTest, - DepthTested, - Count - }; - /// - /// Defines the rendering mode for debug meshes. - /// - enum class MeshRenderMode : uint32_t - { - FilledNoDepthTest, - FilledDepthTested, - WireNoDepthTest, - WireDepthTested, - Count - }; - /*---------------------------------------------------------------------------------*/ /* System Routines */ /*---------------------------------------------------------------------------------*/ class SH_API ProcessPointsRoutine final : public SHSystemRoutine { public: - ProcessPointsRoutine(); + ProcessPointsRoutine(); virtual void Execute(double dt) noexcept override final; }; @@ -95,31 +72,201 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ /* Draw Functions */ /*---------------------------------------------------------------------------------*/ + /// + /// Draws a line between two specified points. + /// + /// Starting point. + /// Ending point. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. void DrawLine(const SHVec3& start, const SHVec3& end, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Draws a set of points as a connected set of lines that loops back. + /// + /// List of points to draw the line across. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. void DrawLineLoop(std::initializer_list points, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Draws a set of points as a connected set of lines that loops back. + /// + /// + /// Type of iterator of the container that contains the points. + /// + /// Starting iterator to the line points. + /// One past end iterator to the line points. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. template void DrawLineLoop(IterType pointListBegin, IterType pointListEnd, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Draws a triangle from the specified set of points. + /// + /// 1st point. + /// 2nd point. + /// 3rd point. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. void DrawTri(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Draws a quad from the specified set of points. + /// + /// 1st point. + /// 2nd point. + /// 3rd point. + /// 4th point. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. void DrawQuad(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHVec3& p4, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Draws a 2-dimensional circle. + /// + /// + /// Matrix transformation that defines how the circle should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. void DrawCircle(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Draws the outline of a cube. + /// + /// + /// Matrix transformation that defines how the wire cube should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. void DrawWireCube(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Draws the wireframe of a sphere. + /// + /// + /// Matrix transformation that defines how the sphere should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. void DrawWireSphere(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Draws a filled cube. + /// + /// + /// Matrix transformation that defines how the cube should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. void DrawCube(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Draws a filled sphere. + /// + /// + /// Matrix transformation that defines how the sphere should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. void DrawSphere(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); /*---------------------------------------------------------------------------------*/ /* Persistent Draw Functions */ /*---------------------------------------------------------------------------------*/ + /// + /// Draws a persistent line between two specified points. + /// This will remain drawn until ClearPersistentDraws() is called. + /// + /// Starting point. + /// Ending point. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. void DrawPersistentLine(const SHVec3& start, const SHVec3& end, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Draws a persistent set of points as a connected set of lines that loops back. + /// This will remain drawn until ClearPersistentDraws() is called. + /// + /// List of points to draw the line across. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. void DrawPersistentLineLoop(std::initializer_list points, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Draws a persistent set of points as a connected set of lines that loops back. + /// This will remain drawn until ClearPersistentDraws() is called. + /// + /// + /// Type of iterator of the container that contains the points. + /// + /// Starting iterator to the line points. + /// One past end iterator to the line points. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. template void DrawPersistentLineLoop(IterType pointListBegin, IterType pointListEnd, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Draws a persistent triangle from the specified set of points. + /// This will remain drawn until ClearPersistentDraws() is called. + /// + /// 1st point. + /// 2nd point. + /// 3rd point. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. void DrawPersistentTri(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Draws a persistent quad from the specified set of points. + /// This will remain drawn until ClearPersistentDraws() is called. + /// + /// 1st point. + /// 2nd point. + /// 3rd point. + /// 4th point. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. void DrawPersistentQuad(const SHVec3& p1, const SHVec3& p2, const SHVec3& p3, const SHVec3& p4, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Draws a persistent 2-dimensional circle. + /// This will remain drawn until ClearPersistentDraws() is called. + /// + /// + /// Matrix transformation that defines how the circle should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. void DrawPersistentCircle(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Draws the outline of a persistent cube. + /// This will remain drawn until ClearPersistentDraws() is called. + /// + /// + /// Matrix transformation that defines how the wire cube should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. void DrawPersistentWireCube(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Draws the wireframe of a persistent sphere. + /// This will remain drawn until ClearPersistentDraws() is called. + /// + /// + /// Matrix transformation that defines how the sphere should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. void DrawPersistentWireSphere(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Draws a persistent filled cube. + /// This will remain drawn until ClearPersistentDraws() is called. + /// + /// + /// Matrix transformation that defines how the cube should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. void DrawPersistentCube(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); + /// + /// Draws a persistent filled sphere. + /// This will remain drawn until ClearPersistentDraws() is called. + /// + /// + /// Matrix transformation that defines how the sphere should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. void DrawPersistentSphere(const SHMatrix& matrix, const SHColour& color = SHColour::WHITE, bool depthTested = false); /// /// Clears any persistent drawn debug primitives. @@ -134,6 +281,26 @@ namespace SHADE using TripleUInt = std::array; using TripleBool = std::array; /// + /// Defines the rendering mode for debug lines. + /// + enum class LineRenderMode : uint32_t + { + NoDepthTest, + DepthTested, + Count + }; + /// + /// Defines the rendering mode for debug meshes. + /// + enum class MeshRenderMode : uint32_t + { + FilledNoDepthTest, + FilledDepthTested, + WireNoDepthTest, + WireDepthTested, + Count + }; + /// /// Defines a coloured Vertex /// struct SH_API PointVertex diff --git a/SHADE_Engine/src/Tools/SHDebugDraw.cpp b/SHADE_Engine/src/Tools/SHDebugDraw.cpp index 15a4bedd..598c8aa6 100644 --- a/SHADE_Engine/src/Tools/SHDebugDraw.cpp +++ b/SHADE_Engine/src/Tools/SHDebugDraw.cpp @@ -16,6 +16,7 @@ of DigiPen Institute of Technology is prohibited. // Project Includes #include "Math/Vector/SHVec4.h" #include "Math/SHQuaternion.h" +#include "Math/SHQuaternion.h" #include "Math/SHColour.h" #include "Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h" #include "ECS_Base/Managers/SHSystemManager.h" @@ -42,110 +43,180 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ /* Draw Functions */ /*-----------------------------------------------------------------------------------*/ - void SHDebugDraw::Line(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt) + void SHDebugDraw::Line(const SHVec3& startPt, const SHVec3& endPt, const SHVec4& color, bool depthTested) { - dbgDrawSys->DrawLine(startPt, endPt, color); + dbgDrawSys->DrawLine(startPt, endPt, color, depthTested); } - void SHDebugDraw::Tri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3) + void SHDebugDraw::Tri(const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec4& color, bool depthTested) { - dbgDrawSys->DrawTri(pt1, pt2, pt3, color); + dbgDrawSys->DrawTri(pt1, pt2, pt3, color, depthTested); } - void SHDebugDraw::Quad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4) + void SHDebugDraw::Quad(const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4, const SHVec4& color, bool depthTested) { - dbgDrawSys->DrawQuad(pt1, pt2, pt3, pt4, color); + dbgDrawSys->DrawQuad(pt1, pt2, pt3, pt4, color, depthTested); } - void SHDebugDraw::Circle(const SHMatrix& mat, const SHVec4& color) + void SHDebugDraw::Circle(const SHMatrix& mat, const SHVec4& color, bool depthTested) { - dbgDrawSys->DrawCircle(mat, color); + dbgDrawSys->DrawCircle(mat, color, depthTested); } - void SHDebugDraw::LineLoop(const SHVec4& color, std::initializer_list pointList) + void SHDebugDraw::LineLoop(const SHVec4& color, std::initializer_list pointList, bool depthTested) { - dbgDrawSys->DrawLineLoop(pointList, color); + dbgDrawSys->DrawLineLoop(pointList, color, depthTested); } - void SHDebugDraw::Cube(const SHMatrix& mat, const SHVec4& color) + void SHDebugDraw::Cube(const SHMatrix& mat, const SHVec4& color, bool depthTested) { - dbgDrawSys->DrawCube(mat, color); + dbgDrawSys->DrawCube(mat, color, depthTested); } - void SHDebugDraw::Sphere(const SHMatrix& mat, const SHVec4& color) + void SHDebugDraw::Cube(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested) { - dbgDrawSys->DrawSphere(mat, color); + dbgDrawSys->DrawCube(SHMatrix::Transform(center, SHQuaternion(), scale), color, depthTested); } - void SHDebugDraw::WireCube(const SHMatrix& mat, const SHVec4& color) + void SHDebugDraw::Cube(const SHVec3& center, const SHQuaternion& orientation, const SHVec3& scale, const SHVec4& color, bool depthTested) { - dbgDrawSys->DrawWireCube(mat, color); + dbgDrawSys->DrawCube(SHMatrix::Transform(center, orientation, scale), color, depthTested); + } + + void SHDebugDraw::Cube(const SHVec3& center, const SHVec3& eulerAngles, const SHVec3& scale, const SHVec4& color, bool depthTested) + { + dbgDrawSys->DrawCube(SHMatrix::Transform(center, eulerAngles, scale), color, depthTested); + } + + void SHDebugDraw::Sphere(const SHMatrix& mat, const SHVec4& color, bool depthTested) + { + dbgDrawSys->DrawSphere(mat, color, depthTested); + } + + void SHDebugDraw::Sphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested) + { + dbgDrawSys->DrawSphere(SHMatrix::Transform(center, SHQuaternion(), scale), color, depthTested); + } + + void SHDebugDraw::Sphere(const SHVec3& center, const SHQuaternion& orientation, SHVec3& scale, const SHVec4& color, bool depthTested) + { + dbgDrawSys->DrawSphere(SHMatrix::Transform(center, orientation, scale), color, depthTested); + } + + void SHDebugDraw::Sphere(const SHVec3& center, const SHVec3& eulerAngles, SHVec3& scale, const SHVec4& color, bool depthTested) + { + dbgDrawSys->DrawSphere(SHMatrix::Transform(center, eulerAngles, scale), color, depthTested); + } + + void SHDebugDraw::WireCube(const SHMatrix& mat, const SHVec4& color, bool depthTested) + { + dbgDrawSys->DrawWireCube(mat, color, depthTested); } - void SHDebugDraw::WireCube(const SHVec3& center, const SHVec3& scale, const SHVec4& color) + void SHDebugDraw::WireCube(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested) { - dbgDrawSys->DrawWireCube(SHMatrix::Transform(center, SHQuaternion(), scale), color); + dbgDrawSys->DrawWireCube(SHMatrix::Transform(center, SHQuaternion(), scale), color, depthTested); } - void SHDebugDraw::WireSphere(const SHMatrix& mat, const SHVec4& color) + void SHDebugDraw::WireSphere(const SHMatrix& mat, const SHVec4& color, bool depthTested) { - dbgDrawSys->DrawWireSphere(mat, color); + dbgDrawSys->DrawWireSphere(mat, color, depthTested); } - void SHDebugDraw::WireSphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color) + void SHDebugDraw::WireSphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested) { - dbgDrawSys->DrawWireSphere(SHMatrix::Transform(center, SHQuaternion(), scale), color); + dbgDrawSys->DrawWireSphere(SHMatrix::Transform(center, SHQuaternion(), scale), color, depthTested); } /*-----------------------------------------------------------------------------------*/ /* Persistent Draw Functions */ /*-----------------------------------------------------------------------------------*/ - void SHDebugDraw::PersistentLine(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt) + void SHDebugDraw::Persistent::Line(const SHVec3& startPt, const SHVec3& endPt, const SHVec4& color, bool depthTested) { - dbgDrawSys->DrawPersistentLine(startPt, endPt, color); + dbgDrawSys->DrawPersistentLine(startPt, endPt, color, depthTested); } - void SHDebugDraw::PersistentTri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3) + void SHDebugDraw::Persistent::Tri(const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec4& color, bool depthTested) { - dbgDrawSys->DrawPersistentTri(pt1, pt2, pt3, color); + dbgDrawSys->DrawPersistentTri(pt1, pt2, pt3, color, depthTested); } - void SHDebugDraw::PersistentQuad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4) + void SHDebugDraw::Persistent::Quad(const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4, const SHVec4& color, bool depthTested) { - dbgDrawSys->DrawPersistentQuad(pt1, pt2, pt3, pt4, color); + dbgDrawSys->DrawPersistentQuad(pt1, pt2, pt3, pt4, color, depthTested); } - void SHDebugDraw::PersistentCircle(const SHMatrix& mat, const SHVec4& color) + void SHDebugDraw::Persistent::Circle(const SHMatrix& mat, const SHVec4& color, bool depthTested) { - dbgDrawSys->DrawPersistentCircle(mat, color); + dbgDrawSys->DrawPersistentCircle(mat, color, depthTested); } - void SHDebugDraw::PersistentLineLoop(const SHVec4& color, std::initializer_list pointList) + void SHDebugDraw::Persistent::LineLoop(const SHVec4& color, std::initializer_list pointList, bool depthTested) { - dbgDrawSys->DrawPersistentLineLoop(pointList, color); + dbgDrawSys->DrawPersistentLineLoop(pointList, color, depthTested); + } + + void SHDebugDraw::Persistent::Cube(const SHMatrix& mat, const SHVec4& color, bool depthTested) + { + dbgDrawSys->DrawPersistentCube(mat, color, depthTested); + } + + void SHDebugDraw::Persistent::Cube(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested) + { + dbgDrawSys->DrawPersistentCube(SHMatrix::Transform(center, SHQuaternion(), scale), color, depthTested); + } + + void SHDebugDraw::Persistent::Cube(const SHVec3& center, const SHQuaternion& orientation, const SHVec3& scale, const SHVec4& color, bool depthTested) + { + dbgDrawSys->DrawPersistentCube(SHMatrix::Transform(center, orientation, scale), color, depthTested); + } + + void SHDebugDraw::Persistent::Cube(const SHVec3& center, const SHVec3& eulerAngles, const SHVec3& scale, const SHVec4& color, bool depthTested) + { + dbgDrawSys->DrawPersistentCube(SHMatrix::Transform(center, eulerAngles, scale), color, depthTested); + } + + void SHDebugDraw::Persistent::Sphere(const SHMatrix& mat, const SHVec4& color, bool depthTested) + { + dbgDrawSys->DrawPersistentSphere(mat, color, depthTested); + } + + void SHDebugDraw::Persistent::Sphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested) + { + dbgDrawSys->DrawPersistentSphere(SHMatrix::Transform(center, SHQuaternion(), scale), color, depthTested); + } + + void SHDebugDraw::Persistent::Sphere(const SHVec3& center, const SHQuaternion& orientation, SHVec3& scale, const SHVec4& color, bool depthTested) + { + dbgDrawSys->DrawPersistentSphere(SHMatrix::Transform(center, orientation, scale), color, depthTested); + } + + void SHDebugDraw::Persistent::Sphere(const SHVec3& center, const SHVec3& eulerAngles, SHVec3& scale, const SHVec4& color, bool depthTested) + { + dbgDrawSys->DrawPersistentSphere(SHMatrix::Transform(center, eulerAngles, scale), color, depthTested); + } + + void SHDebugDraw::Persistent::WireCube(const SHMatrix& mat, const SHVec4& color, bool depthTested) + { + dbgDrawSys->DrawPersistentWireCube(mat, color, depthTested); } - void SHDebugDraw::PersistentCube(const SHMatrix& mat, const SHVec4& color) + void SHDebugDraw::Persistent::WireCube(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested) { - dbgDrawSys->DrawPersistentCube(mat, color); + dbgDrawSys->DrawPersistentWireCube(SHMatrix::Transform(center, SHQuaternion(), scale), color, depthTested); } - void SHDebugDraw::PersistentSphere(const SHMatrix& mat, const SHVec4& color) + void SHDebugDraw::Persistent::WireSphere(const SHMatrix& mat, const SHVec4& color, bool depthTested) { - dbgDrawSys->DrawPersistentSphere(mat, color); + dbgDrawSys->DrawPersistentWireSphere(mat, color, depthTested); } - void SHDebugDraw::PersistentWireCube(const SHMatrix& mat, const SHVec4& color) + void SHDebugDraw::Persistent::WireSphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested) { - dbgDrawSys->DrawPersistentWireCube(mat, color); + dbgDrawSys->DrawPersistentWireSphere(SHMatrix::Transform(center, SHQuaternion(), scale), color, depthTested); } - void SHDebugDraw::PersistentWireSphere(const SHMatrix& mat, const SHVec4& color) - { - dbgDrawSys->DrawPersistentWireSphere(mat, color); - } - - void SHDebugDraw::ClearPersistentDraws() + void SHDebugDraw::Persistent::ClearDraws() { dbgDrawSys->ClearPersistentDraws(); } diff --git a/SHADE_Engine/src/Tools/SHDebugDraw.h b/SHADE_Engine/src/Tools/SHDebugDraw.h index 5438d8f3..4b1f44f0 100644 --- a/SHADE_Engine/src/Tools/SHDebugDraw.h +++ b/SHADE_Engine/src/Tools/SHDebugDraw.h @@ -22,6 +22,7 @@ namespace SHADE class SHDebugDrawSystem; class SHVec4; class SHVec3; + class SHQuaternion; class SHMatrix; class SHColour; @@ -51,87 +52,324 @@ namespace SHADE /// /// Renders a line between two points in world space. /// - /// Colour of the line. /// First point of the line. /// Second point of the line. - static void Line(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt); + /// Colour of the line. + /// Whether or not drawn object will be occluded. + static void Line(const SHVec3& startPt, const SHVec3& endPt, const SHVec4& color, bool depthTested = false); /// /// Renders a triangle indicated by three points in world space. /// - /// Colour of the triangle. /// First point of the triangle. /// Second point of the triangle. /// Third point of the triangle. - static void Tri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3); + /// Colour of the triangle. + /// Whether or not drawn object will be occluded. + static void Tri(const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec4& color, bool depthTested = false); /// /// Renders a quadrilateral indicated by four points in world space. /// - /// Colour of the quadrilateral. /// First point of the triangle. /// Second point of the quadrilateral. /// Third point of the quadrilateral. /// Third point of the quadrilateral. - static void Quad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4); - static void Circle(const SHMatrix& mat,const SHVec4& color); + /// Colour of the quadrilateral. + /// Whether or not drawn object will be occluded. + static void Quad(const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4, const SHVec4& color, bool depthTested = false); + /// + /// Draws a 2-dimensional circle in world space. + /// + /// + /// Matrix transformation that defines how the circle should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void Circle(const SHMatrix& mat,const SHVec4& color, bool depthTested = false); /// /// Renders a polygon indicated by the specified set of points in world space. /// /// Colour of the polygon. /// List of points for the polygon. - static void LineLoop(const SHVec4& color, std::initializer_list pointList); - static void Cube(const SHMatrix& mat, const SHVec4& color); - static void Sphere(const SHMatrix& mat, const SHVec4& color); - static void WireCube(const SHMatrix& mat, const SHVec4& color); - static void WireCube(const SHVec3& center, const SHVec3& scale, const SHVec4& color); - static void WireSphere(const SHMatrix& mat, const SHVec4& color); - static void WireSphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color); + /// Whether or not drawn object will be occluded. + static void LineLoop(const SHVec4& color, std::initializer_list pointList, bool depthTested = false); + /// + /// Draws a filled cube in world space. + /// + /// + /// Matrix transformation that defines how the cube should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void Cube(const SHMatrix& mat, const SHVec4& color, bool depthTested = false); + /// + /// Draws a filled cube in world space. + /// + /// Center of the Cube. + /// Size of the Cube. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void Cube(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + /// + /// Draws a filled cube in world space. + /// + /// Center of the Cube. + /// Orientation of the cube. + /// Size of the Cube. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void Cube(const SHVec3& center, const SHQuaternion& orientation, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + /// + /// Draws a filled cube in world space. + /// + /// Center of the Cube. + /// Euler angle rotation of the cube in radians. + /// Size of the Cube. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void Cube(const SHVec3& center, const SHVec3& eulerAngles, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + /// + /// Draws a filled sphere in world space. + /// + /// + /// Matrix transformation that defines how the sphere should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void Sphere(const SHMatrix& mat, const SHVec4& color, bool depthTested = false); + /// + /// Draws a filled sphere in world space. + /// + /// Center point of the sphere. + /// Size of the sphere. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void Sphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + /// + /// Draws a filled sphere in world space. + /// + /// Center point of the sphere. + /// Orientation of the sphere. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void Sphere(const SHVec3& center, const SHQuaternion& orientation, SHVec3& scale, const SHVec4& color, bool depthTested = false); + /// + /// Draws a filled sphere in world space. + /// + /// Center point of the sphere. + /// Euler angle rotation of the sphere in radians. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void Sphere(const SHVec3& center, const SHVec3& eulerAngles, SHVec3& scale, const SHVec4& color, bool depthTested = false); + /// + /// Draws the outline of a cube in world space. + /// + /// + /// Matrix transformation that defines how the wire cube should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void WireCube(const SHMatrix& mat, const SHVec4& color, bool depthTested = false); + /// + /// Draws the outline of a cube in world space. + /// + /// Center of the Cube. + /// Size of the Cube. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void WireCube(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + /// + /// Draws the wireframe of a sphere in world space. + /// + /// + /// Matrix transformation that defines how the sphere should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void WireSphere(const SHMatrix& mat, const SHVec4& color, bool depthTested = false); + /// + /// Draws the wireframe of a sphere in world space. + /// + /// Center point of the sphere. + /// Size of the sphere. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void WireSphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested = false); /*---------------------------------------------------------------------------------*/ - /* Persistent Draw Functions */ + /* Persistent Draw Function Class "Folder" */ /*---------------------------------------------------------------------------------*/ - /// - /// Renders a line between two points in world space that will persist until - /// ClearPersistentDraws() is called. - /// - /// Colour of the line. - /// First point of the line. - /// Second point of the line. - static void PersistentLine(const SHVec4& color, const SHVec3& startPt, const SHVec3& endPt); - /// - /// Renders a triangle indicated by three points in world space that will persist - /// until ClearPersistentDraws() is called. - /// - /// Colour of the triangle. - /// First point of the triangle. - /// Second point of the triangle. - /// Third point of the triangle. - static void PersistentTri(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3); - /// - /// Renders a quadrilateral indicated by four points in world space that will persist - /// until ClearPersistentDraws() is called. - /// - /// Colour of the quadrilateral. - /// First point of the triangle. - /// Second point of the quadrilateral. - /// Third point of the quadrilateral. - /// Third point of the quadrilateral. - static void PersistentQuad(const SHVec4& color, const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4); - static void PersistentCircle(const SHMatrix& mat,const SHVec4& color); - /// - /// Renders a polygon indicated by the specified set of points in world space that - /// will persist until ClearPersistentDraws() is called. - /// - /// Colour of the polygon. - /// List of points for the polygon. - static void PersistentLineLoop(const SHVec4& color, std::initializer_list pointList); - static void PersistentCube(const SHMatrix& mat, const SHVec4& color); - static void PersistentSphere(const SHMatrix& mat, const SHVec4& color); - static void PersistentWireCube(const SHMatrix& mat, const SHVec4& color); - static void PersistentWireSphere(const SHMatrix& mat, const SHVec4& color); - /// - /// Clears any persistent drawn debug primitives. - /// - static void ClearPersistentDraws(); + struct SH_API Persistent + { + /*-------------------------------------------------------------------------------*/ + /* Persistent Draw Functions */ + /*-------------------------------------------------------------------------------*/ + /// + /// Renders a line between two points in world space. + /// This will remain drawn until ClearDraws() is called. + /// + /// First point of the line. + /// Second point of the line. + /// Colour of the line. + /// Whether or not drawn object will be occluded. + static void Line(const SHVec3& startPt, const SHVec3& endPt, const SHVec4& color, bool depthTested = false); + /// + /// Renders a triangle indicated by three points in world space. + /// This will remain drawn until ClearDraws() is called. + /// + /// First point of the triangle. + /// Second point of the triangle. + /// Third point of the triangle. + /// Colour of the triangle. + /// Whether or not drawn object will be occluded. + static void Tri(const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec4& color, bool depthTested = false); + /// + /// Renders a quadrilateral indicated by four points in world space. + /// This will remain drawn until ClearDraws() is called. + /// + /// First point of the triangle. + /// Second point of the quadrilateral. + /// Third point of the quadrilateral. + /// Third point of the quadrilateral. + /// Colour of the quadrilateral. + /// Whether or not drawn object will be occluded. + static void Quad(const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4, const SHVec4& color, bool depthTested = false); + /// + /// Draws a 2-dimensional circle in world space. + /// This will remain drawn until ClearDraws() is called. + /// + /// + /// Matrix transformation that defines how the circle should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void Circle(const SHMatrix& mat,const SHVec4& color, bool depthTested = false); + /// + /// Renders a polygon indicated by the specified set of points in world space. + /// This will remain drawn until ClearDraws() is called. + /// + /// Colour of the polygon. + /// List of points for the polygon. + /// Whether or not drawn object will be occluded. + static void LineLoop(const SHVec4& color, std::initializer_list pointList, bool depthTested = false); + /// + /// Draws a filled cube in world space. + /// This will remain drawn until ClearDraws() is called. + /// + /// + /// Matrix transformation that defines how the cube should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void Cube(const SHMatrix& mat, const SHVec4& color, bool depthTested = false); + /// + /// Draws a filled cube in world space. + /// This will remain drawn until ClearDraws() is called. + /// + /// Center of the Cube. + /// Size of the Cube. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void Cube(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + /// + /// Draws a filled cube in world space. + /// This will remain drawn until ClearDraws() is called. + /// + /// Center of the Cube. + /// Orientation of the cube. + /// Size of the Cube. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void Cube(const SHVec3& center, const SHQuaternion& orientation, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + /// + /// Draws a filled cube in world space. + /// This will remain drawn until ClearDraws() is called. + /// + /// Center of the Cube. + /// Euler angle rotation of the cube in radians. + /// Size of the Cube. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void Cube(const SHVec3& center, const SHVec3& eulerAngles, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + /// + /// Draws a filled sphere in world space. + /// This will remain drawn until ClearDraws() is called. + /// + /// + /// Matrix transformation that defines how the sphere should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void Sphere(const SHMatrix& mat, const SHVec4& color, bool depthTested = false); + /// + /// Draws a filled sphere in world space. + /// This will remain drawn until ClearDraws() is called. + /// + /// Center point of the sphere. + /// Size of the sphere. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void Sphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + /// + /// Draws a filled sphere in world space. + /// This will remain drawn until ClearDraws() is called. + /// + /// Center point of the sphere. + /// Orientation of the sphere. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void Sphere(const SHVec3& center, const SHQuaternion& orientation, SHVec3& scale, const SHVec4& color, bool depthTested = false); + /// + /// Draws a filled sphere in world space. + /// This will remain drawn until ClearDraws() is called. + /// + /// Center point of the sphere. + /// Euler angle rotation of the sphere in radians. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void Sphere(const SHVec3& center, const SHVec3& eulerAngles, SHVec3& scale, const SHVec4& color, bool depthTested = false); + /// + /// Draws the outline of a cube in world space. + /// This will remain drawn until ClearDraws() is called. + /// + /// + /// Matrix transformation that defines how the wire cube should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void WireCube(const SHMatrix& mat, const SHVec4& color, bool depthTested = false); + /// + /// Draws the outline of a cube in world space. + /// This will remain drawn until ClearDraws() is called. + /// + /// Center of the Cube. + /// Size of the Cube. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void WireCube(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + /// + /// Draws the wireframe of a sphere in world space. + /// This will remain drawn until ClearDraws() is called. + /// + /// + /// Matrix transformation that defines how the sphere should be drawn. + /// + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void WireSphere(const SHMatrix& mat, const SHVec4& color, bool depthTested = false); + /// + /// Draws the wireframe of a sphere in world space. + /// This will remain drawn until ClearDraws() is called. + /// + /// Center point of the sphere. + /// Size of the sphere. + /// Colour to draw with. + /// Whether or not drawn object will be occluded. + static void WireSphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + /// + /// Clears any persistent drawn debug primitives. + /// + static void ClearDraws(); + }; private: /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Managed/src/Utility/Gizmos.cxx b/SHADE_Managed/src/Utility/Gizmos.cxx index fe29e2ff..458b64b6 100644 --- a/SHADE_Managed/src/Utility/Gizmos.cxx +++ b/SHADE_Managed/src/Utility/Gizmos.cxx @@ -44,7 +44,7 @@ namespace SHADE void Gizmos::DrawLine(Vector3 from, Vector3 to, SHADE::Color color) { - SHDebugDraw::Line(Convert::ToNative(color), Convert::ToNative(from), Convert::ToNative(to)); + SHDebugDraw::Line(Convert::ToNative(from), Convert::ToNative(to), Convert::ToNative(color)); } void Gizmos::DrawWireCube(Vector3 center, Vector3 extents) From 77a164cefbba7cd09fdae29529994189428bbd33 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Thu, 15 Dec 2022 23:33:53 +0800 Subject: [PATCH 049/275] Updated C# Gizmos class with DrawCube and DrawSphere in --- SHADE_Managed/src/Utility/Gizmos.cxx | 20 +++++++++++++++++ SHADE_Managed/src/Utility/Gizmos.hxx | 32 ++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/SHADE_Managed/src/Utility/Gizmos.cxx b/SHADE_Managed/src/Utility/Gizmos.cxx index 458b64b6..cccdbad1 100644 --- a/SHADE_Managed/src/Utility/Gizmos.cxx +++ b/SHADE_Managed/src/Utility/Gizmos.cxx @@ -47,6 +47,26 @@ namespace SHADE SHDebugDraw::Line(Convert::ToNative(from), Convert::ToNative(to), Convert::ToNative(color)); } + void Gizmos::DrawCube(Vector3 center, Vector3 extents) + { + DrawCube(center, extents, defaultColor); + } + + void Gizmos::DrawCube(Vector3 center, Vector3 extents, SHADE::Color color) + { + SHDebugDraw::Cube(Convert::ToNative(center), Convert::ToNative(extents), Convert::ToNative(color)); + } + + void Gizmos::DrawSphere(Vector3 center, float radius) + { + DrawSphere(center, radius, defaultColor); + } + + void Gizmos::DrawSphere(Vector3 center, float radius, SHADE::Color color) + { + SHDebugDraw::Sphere(Convert::ToNative(center), SHVec3(radius, radius, radius), Convert::ToNative(color)); + } + void Gizmos::DrawWireCube(Vector3 center, Vector3 extents) { DrawWireCube(center, extents, defaultColor); diff --git a/SHADE_Managed/src/Utility/Gizmos.hxx b/SHADE_Managed/src/Utility/Gizmos.hxx index 1878d867..f96f1261 100644 --- a/SHADE_Managed/src/Utility/Gizmos.hxx +++ b/SHADE_Managed/src/Utility/Gizmos.hxx @@ -56,6 +56,38 @@ namespace SHADE /// Colour of the line. static void DrawLine(Vector3 from, Vector3 to, SHADE::Color color); /// + /// Renders a cube centered around the position specified in world + /// space. + /// Uses Color to render. + /// + /// Position where the cube wil be centered at. + /// Size of the rendered cube. + static void DrawCube(Vector3 center, Vector3 extents); + /// + /// Renders a cube centered around the position specified in world + /// space. + /// + /// Position where the cube wil be centered at. + /// Size of the rendered cube. + /// Colour of the cube. + static void DrawCube(Vector3 center, Vector3 extents, SHADE::Color color); + /// + /// Renders a sphere centered around the position specified in world + /// space. + /// Uses Color to render. + /// + /// Position where the sphere wil be centered at. + /// Radius of the rendered sphere. + static void DrawSphere(Vector3 center, float radius); + /// + /// Renders a sphere centered around the position specified in world + /// space. + /// + /// Position where the sphere wil be centered at. + /// Radius of the rendered sphere. + /// Colour of the sphere. + static void DrawSphere(Vector3 center, float radius, SHADE::Color color); + /// /// Renders a wireframe cube centered around the position specified in world /// space. /// Uses Color to render. From 0a3ff527d9f2b86cb008d1743b2567cb849238dd Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Fri, 16 Dec 2022 00:05:03 +0800 Subject: [PATCH 050/275] SHDebugDraw drawing functions now have the colour parameter defaulted to white --- SHADE_Engine/src/Tools/SHDebugDraw.cpp | 4 +- SHADE_Engine/src/Tools/SHDebugDraw.h | 74 +++++++++++++------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/SHADE_Engine/src/Tools/SHDebugDraw.cpp b/SHADE_Engine/src/Tools/SHDebugDraw.cpp index 598c8aa6..02eca592 100644 --- a/SHADE_Engine/src/Tools/SHDebugDraw.cpp +++ b/SHADE_Engine/src/Tools/SHDebugDraw.cpp @@ -63,7 +63,7 @@ namespace SHADE dbgDrawSys->DrawCircle(mat, color, depthTested); } - void SHDebugDraw::LineLoop(const SHVec4& color, std::initializer_list pointList, bool depthTested) + void SHDebugDraw::LineLoop(std::initializer_list pointList, const SHVec4& color, bool depthTested) { dbgDrawSys->DrawLineLoop(pointList, color, depthTested); } @@ -151,7 +151,7 @@ namespace SHADE dbgDrawSys->DrawPersistentCircle(mat, color, depthTested); } - void SHDebugDraw::Persistent::LineLoop(const SHVec4& color, std::initializer_list pointList, bool depthTested) + void SHDebugDraw::Persistent::LineLoop(std::initializer_list pointList, const SHVec4& color, bool depthTested) { dbgDrawSys->DrawPersistentLineLoop(pointList, color, depthTested); } diff --git a/SHADE_Engine/src/Tools/SHDebugDraw.h b/SHADE_Engine/src/Tools/SHDebugDraw.h index 4b1f44f0..c28b93e6 100644 --- a/SHADE_Engine/src/Tools/SHDebugDraw.h +++ b/SHADE_Engine/src/Tools/SHDebugDraw.h @@ -13,6 +13,7 @@ of DigiPen Institute of Technology is prohibited. // Project Includes #include "SH_API.h" +#include "Math/SHColour.h" namespace SHADE { @@ -24,7 +25,6 @@ namespace SHADE class SHVec3; class SHQuaternion; class SHMatrix; - class SHColour; /*-----------------------------------------------------------------------------------*/ /* Type Definitions */ @@ -56,7 +56,7 @@ namespace SHADE /// Second point of the line. /// Colour of the line. /// Whether or not drawn object will be occluded. - static void Line(const SHVec3& startPt, const SHVec3& endPt, const SHVec4& color, bool depthTested = false); + static void Line(const SHVec3& startPt, const SHVec3& endPt, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Renders a triangle indicated by three points in world space. /// @@ -65,7 +65,7 @@ namespace SHADE /// Third point of the triangle. /// Colour of the triangle. /// Whether or not drawn object will be occluded. - static void Tri(const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec4& color, bool depthTested = false); + static void Tri(const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Renders a quadrilateral indicated by four points in world space. /// @@ -75,7 +75,7 @@ namespace SHADE /// Third point of the quadrilateral. /// Colour of the quadrilateral. /// Whether or not drawn object will be occluded. - static void Quad(const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4, const SHVec4& color, bool depthTested = false); + static void Quad(const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws a 2-dimensional circle in world space. /// @@ -84,14 +84,14 @@ namespace SHADE /// /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void Circle(const SHMatrix& mat,const SHVec4& color, bool depthTested = false); + static void Circle(const SHMatrix& mat,const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Renders a polygon indicated by the specified set of points in world space. /// - /// Colour of the polygon. /// List of points for the polygon. + /// Colour of the polygon. /// Whether or not drawn object will be occluded. - static void LineLoop(const SHVec4& color, std::initializer_list pointList, bool depthTested = false); + static void LineLoop(std::initializer_list pointList, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws a filled cube in world space. /// @@ -100,7 +100,7 @@ namespace SHADE /// /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void Cube(const SHMatrix& mat, const SHVec4& color, bool depthTested = false); + static void Cube(const SHMatrix& mat, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws a filled cube in world space. /// @@ -108,7 +108,7 @@ namespace SHADE /// Size of the Cube. /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void Cube(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + static void Cube(const SHVec3& center, const SHVec3& scale, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws a filled cube in world space. /// @@ -117,7 +117,7 @@ namespace SHADE /// Size of the Cube. /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void Cube(const SHVec3& center, const SHQuaternion& orientation, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + static void Cube(const SHVec3& center, const SHQuaternion& orientation, const SHVec3& scale, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws a filled cube in world space. /// @@ -126,7 +126,7 @@ namespace SHADE /// Size of the Cube. /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void Cube(const SHVec3& center, const SHVec3& eulerAngles, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + static void Cube(const SHVec3& center, const SHVec3& eulerAngles, const SHVec3& scale, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws a filled sphere in world space. /// @@ -135,7 +135,7 @@ namespace SHADE /// /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void Sphere(const SHMatrix& mat, const SHVec4& color, bool depthTested = false); + static void Sphere(const SHMatrix& mat, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws a filled sphere in world space. /// @@ -143,7 +143,7 @@ namespace SHADE /// Size of the sphere. /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void Sphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + static void Sphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws a filled sphere in world space. /// @@ -151,7 +151,7 @@ namespace SHADE /// Orientation of the sphere. /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void Sphere(const SHVec3& center, const SHQuaternion& orientation, SHVec3& scale, const SHVec4& color, bool depthTested = false); + static void Sphere(const SHVec3& center, const SHQuaternion& orientation, SHVec3& scale, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws a filled sphere in world space. /// @@ -159,7 +159,7 @@ namespace SHADE /// Euler angle rotation of the sphere in radians. /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void Sphere(const SHVec3& center, const SHVec3& eulerAngles, SHVec3& scale, const SHVec4& color, bool depthTested = false); + static void Sphere(const SHVec3& center, const SHVec3& eulerAngles, SHVec3& scale, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws the outline of a cube in world space. /// @@ -168,7 +168,7 @@ namespace SHADE /// /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void WireCube(const SHMatrix& mat, const SHVec4& color, bool depthTested = false); + static void WireCube(const SHMatrix& mat, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws the outline of a cube in world space. /// @@ -176,7 +176,7 @@ namespace SHADE /// Size of the Cube. /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void WireCube(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + static void WireCube(const SHVec3& center, const SHVec3& scale, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws the wireframe of a sphere in world space. /// @@ -185,7 +185,7 @@ namespace SHADE /// /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void WireSphere(const SHMatrix& mat, const SHVec4& color, bool depthTested = false); + static void WireSphere(const SHMatrix& mat, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws the wireframe of a sphere in world space. /// @@ -193,7 +193,7 @@ namespace SHADE /// Size of the sphere. /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void WireSphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + static void WireSphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /*---------------------------------------------------------------------------------*/ /* Persistent Draw Function Class "Folder" */ @@ -211,7 +211,7 @@ namespace SHADE /// Second point of the line. /// Colour of the line. /// Whether or not drawn object will be occluded. - static void Line(const SHVec3& startPt, const SHVec3& endPt, const SHVec4& color, bool depthTested = false); + static void Line(const SHVec3& startPt, const SHVec3& endPt, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Renders a triangle indicated by three points in world space. /// This will remain drawn until ClearDraws() is called. @@ -221,7 +221,7 @@ namespace SHADE /// Third point of the triangle. /// Colour of the triangle. /// Whether or not drawn object will be occluded. - static void Tri(const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec4& color, bool depthTested = false); + static void Tri(const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Renders a quadrilateral indicated by four points in world space. /// This will remain drawn until ClearDraws() is called. @@ -232,7 +232,7 @@ namespace SHADE /// Third point of the quadrilateral. /// Colour of the quadrilateral. /// Whether or not drawn object will be occluded. - static void Quad(const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4, const SHVec4& color, bool depthTested = false); + static void Quad(const SHVec3& pt1, const SHVec3& pt2, const SHVec3& pt3, const SHVec3& pt4, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws a 2-dimensional circle in world space. /// This will remain drawn until ClearDraws() is called. @@ -242,15 +242,15 @@ namespace SHADE /// /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void Circle(const SHMatrix& mat,const SHVec4& color, bool depthTested = false); + static void Circle(const SHMatrix& mat,const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Renders a polygon indicated by the specified set of points in world space. /// This will remain drawn until ClearDraws() is called. /// - /// Colour of the polygon. /// List of points for the polygon. + /// Colour of the polygon. /// Whether or not drawn object will be occluded. - static void LineLoop(const SHVec4& color, std::initializer_list pointList, bool depthTested = false); + static void LineLoop(std::initializer_list pointList, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws a filled cube in world space. /// This will remain drawn until ClearDraws() is called. @@ -260,7 +260,7 @@ namespace SHADE /// /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void Cube(const SHMatrix& mat, const SHVec4& color, bool depthTested = false); + static void Cube(const SHMatrix& mat, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws a filled cube in world space. /// This will remain drawn until ClearDraws() is called. @@ -269,7 +269,7 @@ namespace SHADE /// Size of the Cube. /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void Cube(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + static void Cube(const SHVec3& center, const SHVec3& scale, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws a filled cube in world space. /// This will remain drawn until ClearDraws() is called. @@ -279,7 +279,7 @@ namespace SHADE /// Size of the Cube. /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void Cube(const SHVec3& center, const SHQuaternion& orientation, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + static void Cube(const SHVec3& center, const SHQuaternion& orientation, const SHVec3& scale, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws a filled cube in world space. /// This will remain drawn until ClearDraws() is called. @@ -289,7 +289,7 @@ namespace SHADE /// Size of the Cube. /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void Cube(const SHVec3& center, const SHVec3& eulerAngles, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + static void Cube(const SHVec3& center, const SHVec3& eulerAngles, const SHVec3& scale, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws a filled sphere in world space. /// This will remain drawn until ClearDraws() is called. @@ -299,7 +299,7 @@ namespace SHADE /// /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void Sphere(const SHMatrix& mat, const SHVec4& color, bool depthTested = false); + static void Sphere(const SHMatrix& mat, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws a filled sphere in world space. /// This will remain drawn until ClearDraws() is called. @@ -308,7 +308,7 @@ namespace SHADE /// Size of the sphere. /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void Sphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + static void Sphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws a filled sphere in world space. /// This will remain drawn until ClearDraws() is called. @@ -317,7 +317,7 @@ namespace SHADE /// Orientation of the sphere. /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void Sphere(const SHVec3& center, const SHQuaternion& orientation, SHVec3& scale, const SHVec4& color, bool depthTested = false); + static void Sphere(const SHVec3& center, const SHQuaternion& orientation, SHVec3& scale, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws a filled sphere in world space. /// This will remain drawn until ClearDraws() is called. @@ -326,7 +326,7 @@ namespace SHADE /// Euler angle rotation of the sphere in radians. /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void Sphere(const SHVec3& center, const SHVec3& eulerAngles, SHVec3& scale, const SHVec4& color, bool depthTested = false); + static void Sphere(const SHVec3& center, const SHVec3& eulerAngles, SHVec3& scale, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws the outline of a cube in world space. /// This will remain drawn until ClearDraws() is called. @@ -336,7 +336,7 @@ namespace SHADE /// /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void WireCube(const SHMatrix& mat, const SHVec4& color, bool depthTested = false); + static void WireCube(const SHMatrix& mat, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws the outline of a cube in world space. /// This will remain drawn until ClearDraws() is called. @@ -345,7 +345,7 @@ namespace SHADE /// Size of the Cube. /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void WireCube(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + static void WireCube(const SHVec3& center, const SHVec3& scale, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws the wireframe of a sphere in world space. /// This will remain drawn until ClearDraws() is called. @@ -355,7 +355,7 @@ namespace SHADE /// /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void WireSphere(const SHMatrix& mat, const SHVec4& color, bool depthTested = false); + static void WireSphere(const SHMatrix& mat, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Draws the wireframe of a sphere in world space. /// This will remain drawn until ClearDraws() is called. @@ -364,7 +364,7 @@ namespace SHADE /// Size of the sphere. /// Colour to draw with. /// Whether or not drawn object will be occluded. - static void WireSphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color, bool depthTested = false); + static void WireSphere(const SHVec3& center, const SHVec3& scale, const SHVec4& color = SHColour::WHITE, bool depthTested = false); /// /// Clears any persistent drawn debug primitives. /// From 6b8232ae9150bdd25cff47165c8531f5498e3728 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Fri, 16 Dec 2022 02:02:20 +0800 Subject: [PATCH 051/275] Fixed bug where intertia tensors were not reset when unlocking axes --- Assets/Scenes/PhysicsSandbox.shade | 2 +- Assets/Scripts/Tests/PhysicsTestObj.cs | 9 ++-- .../src/Physics/Dynamics/SHRigidBody.cpp | 54 ++++++++++++++++--- .../src/Physics/Dynamics/SHRigidBody.h | 10 ++++ .../PhysicsObject/SHPhysicsObject.cpp | 10 ++++ 5 files changed, 74 insertions(+), 11 deletions(-) diff --git a/Assets/Scenes/PhysicsSandbox.shade b/Assets/Scenes/PhysicsSandbox.shade index 0952fd3c..7381b70c 100644 --- a/Assets/Scenes/PhysicsSandbox.shade +++ b/Assets/Scenes/PhysicsSandbox.shade @@ -11,7 +11,7 @@ RigidBody Component: Type: Dynamic Auto Mass: false - Mass: 1 + Mass: 10 Drag: 1 Angular Drag: 1 Use Gravity: false diff --git a/Assets/Scripts/Tests/PhysicsTestObj.cs b/Assets/Scripts/Tests/PhysicsTestObj.cs index c36537b3..20437eb3 100644 --- a/Assets/Scripts/Tests/PhysicsTestObj.cs +++ b/Assets/Scripts/Tests/PhysicsTestObj.cs @@ -83,8 +83,8 @@ public class PhysicsTestObj : Script if (Input.GetKeyDown(moveInputKeys[i])) move[i] = true; - //if (Input.GetKeyDown(rotateInputKeys[i])) - // rotate[i] = true; + if (Input.GetKeyDown(rotateInputKeys[i])) + rotate[i] = true; } } @@ -97,8 +97,9 @@ public class PhysicsTestObj : Script if (shouldMove) { - Vector3 offset = new Vector3(0.25f, 0.0f, 0.0f); - rb.AddForceAtLocalPos(moveVec[i] * forceAmount, offset); + //Vector3 offset = new Vector3(0.25f, 0.0f, 0.0f); + //rb.AddForceAtLocalPos(moveVec[i] * forceAmount, offset); + rb.AddForce(moveVec[i] * forceAmount); move[i] = false; } diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp index 00df0640..aa8027c7 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp @@ -511,11 +511,12 @@ namespace SHADE // Reset angular velocity along X-axis angularVelocity.x = 0.0f; // Set inertia tensor on the x-axis to 0 - localInvInertia.m[0][0] = worldInvInertia.m[0][0] = 0.0f; + localInvInertia.m[0][0] = 0.0f; } else { flags &= ~VALUE; + computeInertiaTensor(); } } @@ -530,11 +531,12 @@ namespace SHADE // Reset angular velocity along Y-axis angularVelocity.y = 0.0f; // Set inertia tensor on the y-axis to 0 - localInvInertia.m[1][1] = worldInvInertia.m[1][1] = 0.0f; + localInvInertia.m[1][1] = 0.0f; } else { flags &= ~VALUE; + computeInertiaTensor(); } } @@ -549,11 +551,12 @@ namespace SHADE // Reset angular velocity along Z-axis angularVelocity.z = 0.0f; // Set inertia tensor on the z-axis to 0 - localInvInertia.m[2][2] = worldInvInertia.m[2][2] = 0.0f; + localInvInertia.m[2][2] = 0.0f; } else { flags &= ~VALUE; + computeInertiaTensor(); } } @@ -606,20 +609,59 @@ namespace SHADE void SHRigidBody::ComputeMassData() noexcept { - // TODO: Compute total inertia and centroid from composited colliders using the Parallel Axis Theorem. + computeCentroid(); + computeMassAndInertiaTensor(); + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Member Function Definition */ + /*-----------------------------------------------------------------------------------*/ + + void SHRigidBody::computeCentroid() noexcept + { + // TODO + } + + void SHRigidBody::computeMass() noexcept + { // TODO: If auto mass in enabled, compute total mass based from each collider. // TODO: If auto mass disabled, compute inertia tensor for each shape using the ratio of its mass / total mass by comparing the volume / total volume. + } - if (collider && !collider->GetCollisionShapes().empty()) + void SHRigidBody::computeInertiaTensor() noexcept + { + // TODO: Compute total inertia from composited colliders using the Parallel Axis Theorem. + + if (!collider || collider->GetCollisionShapes().empty()) + { + localInvInertia.m[0][0] = localInvInertia.m[1][1] = localInvInertia.m[2][2] = invMass; + } + else { // HACK: For now, take only the first shape as we are testing with non-composited colliders. We are using the center as the centroid. const auto* FIRST_SHAPE = collider->GetCollisionShape(0); localInvInertia = SHMatrix::Inverse(FIRST_SHAPE->GetInertiaTensor(1.0f / invMass)); } - else + } + + void SHRigidBody::computeMassAndInertiaTensor() noexcept + { + // TODO: If auto mass in enabled, compute total mass based from each collider. + // TODO: If auto mass disabled, compute inertia tensor for each shape using the ratio of its mass / total mass by comparing the volume / total volume. + + // TODO: Compute total inertia from composited colliders using the Parallel Axis Theorem. + + if (!collider || collider->GetCollisionShapes().empty()) { localInvInertia.m[0][0] = localInvInertia.m[1][1] = localInvInertia.m[2][2] = invMass; } + else + { + // HACK: For now, take only the first shape as we are testing with non-composited colliders. We are using the center as the centroid. + const auto* FIRST_SHAPE = collider->GetCollisionShape(0); + localInvInertia = SHMatrix::Inverse(FIRST_SHAPE->GetInertiaTensor(1.0f / invMass)); + } + } } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h index 0d1457d9..ab30bbcf 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h @@ -252,6 +252,16 @@ namespace SHADE uint16_t flags; SHMotionState motionState; + + /*-----------------------------------------------------------------------------------*/ + /* Member Functions */ + /*-----------------------------------------------------------------------------------*/ + + void computeCentroid () noexcept; + void computeMass () noexcept; + void computeInertiaTensor () noexcept; + void computeMassAndInertiaTensor() noexcept; + }; } // namespace SHADE diff --git a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.cpp b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.cpp index ee33016a..56c2ac7b 100644 --- a/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.cpp +++ b/SHADE_Engine/src/Physics/Interface/PhysicsObject/SHPhysicsObject.cpp @@ -92,8 +92,12 @@ namespace SHADE // Link with collider if it exists if (collider) + { collider->SetRigidBody(rigidBody); + rigidBody->SetCollider(collider); + } + rigidBody->ComputeMassData(); return rigidBody; } @@ -119,7 +123,10 @@ namespace SHADE // Link with rigidBody if it exists if (rigidBody) + { rigidBody->SetCollider(collider); + collider->SetRigidBody(rigidBody); + } return collider; } @@ -131,7 +138,10 @@ namespace SHADE // Unlink with rigid body if (rigidBody) + { rigidBody->SetCollider(nullptr); + rigidBody->ComputeMassData(); + } } /*-----------------------------------------------------------------------------------*/ From ddf2d8bde9b424d0f140b1c1fbf3eb4815a930fc Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Fri, 16 Dec 2022 14:38:01 +0800 Subject: [PATCH 052/275] Fixed warnings for subscript operator in vectors and removed react conversions --- SHADE_Engine/src/Math/SHColour.cpp | 8 ++++++++ SHADE_Engine/src/Math/SHQuaternion.cpp | 20 ------------------- SHADE_Engine/src/Math/SHQuaternion.h | 8 -------- SHADE_Engine/src/Math/SHRay.cpp | 12 ------------ SHADE_Engine/src/Math/SHRay.h | 6 ------ SHADE_Engine/src/Math/Vector/SHVec2.cpp | 17 ++++++++-------- SHADE_Engine/src/Math/Vector/SHVec2.h | 6 ------ SHADE_Engine/src/Math/Vector/SHVec3.cpp | 26 ++++++++----------------- SHADE_Engine/src/Math/Vector/SHVec3.h | 7 ------- SHADE_Engine/src/Math/Vector/SHVec4.cpp | 8 ++++++++ 10 files changed, 32 insertions(+), 86 deletions(-) diff --git a/SHADE_Engine/src/Math/SHColour.cpp b/SHADE_Engine/src/Math/SHColour.cpp index fc2f2a08..7c372376 100644 --- a/SHADE_Engine/src/Math/SHColour.cpp +++ b/SHADE_Engine/src/Math/SHColour.cpp @@ -260,6 +260,8 @@ namespace SHADE case 1: return y; case 2: return z; case 3: return w; + // This will never hit + default: return x; } } @@ -274,6 +276,8 @@ namespace SHADE case 1: return y; case 2: return z; case 3: return w; + // This will never hit + default: return x; } } @@ -288,6 +292,8 @@ namespace SHADE case 1: return y; case 2: return z; case 3: return w; + // This will never hit + default: return x; } } @@ -302,6 +308,8 @@ namespace SHADE case 1: return y; case 2: return z; case 3: return w; + // This will never hit + default: return x; } } diff --git a/SHADE_Engine/src/Math/SHQuaternion.cpp b/SHADE_Engine/src/Math/SHQuaternion.cpp index 8904cb05..021f0a6a 100644 --- a/SHADE_Engine/src/Math/SHQuaternion.cpp +++ b/SHADE_Engine/src/Math/SHQuaternion.cpp @@ -44,16 +44,6 @@ namespace SHADE : XMFLOAT4( _x, _y, _z, _w ) {} - SHQuaternion::SHQuaternion(const reactphysics3d::Vector3& rp3dEuler) noexcept - : XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f ) - { - XMStoreFloat4(this, XMQuaternionRotationRollPitchYawFromVector(SHVec3 { rp3dEuler })); - } - - SHQuaternion::SHQuaternion(const reactphysics3d::Quaternion& rp3dQuat) noexcept - : XMFLOAT4( rp3dQuat.x, rp3dQuat.y, rp3dQuat.z, rp3dQuat.w ) - {} - /*-----------------------------------------------------------------------------------*/ /* Operator Overload Definitions */ /*-----------------------------------------------------------------------------------*/ @@ -141,16 +131,6 @@ namespace SHADE return XMQuaternionNotEqual(*this, rhs); } - SHQuaternion::operator reactphysics3d::Quaternion() const noexcept - { - return reactphysics3d::Quaternion{ x, y, z, w }; - } - - SHQuaternion::operator reactphysics3d::Vector3() const noexcept - { - return reactphysics3d::Vector3{ ToEuler() }; - } - SHQuaternion::operator XMVECTOR() const noexcept { return XMLoadFloat4(this); diff --git a/SHADE_Engine/src/Math/SHQuaternion.h b/SHADE_Engine/src/Math/SHQuaternion.h index fa5b5d36..93c546ca 100644 --- a/SHADE_Engine/src/Math/SHQuaternion.h +++ b/SHADE_Engine/src/Math/SHQuaternion.h @@ -11,7 +11,6 @@ #pragma once #include -#include #include @@ -52,11 +51,6 @@ namespace SHADE SHQuaternion (const SHVec4& vec4) noexcept; SHQuaternion (float x, float y, float z, float w) noexcept; - // Conversion from other math types - - SHQuaternion (const reactphysics3d::Vector3& rp3dEuler) noexcept; - SHQuaternion (const reactphysics3d::Quaternion& rp3dQuat) noexcept; - /*---------------------------------------------------------------------------------*/ /* Operator Overloads */ /*---------------------------------------------------------------------------------*/ @@ -82,8 +76,6 @@ namespace SHADE // Conversion to other math types used by SHADE - operator reactphysics3d::Quaternion () const noexcept; - operator reactphysics3d::Vector3 () const noexcept; operator DirectX::XMVECTOR () const noexcept; /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Math/SHRay.cpp b/SHADE_Engine/src/Math/SHRay.cpp index c4931aba..622087c6 100644 --- a/SHADE_Engine/src/Math/SHRay.cpp +++ b/SHADE_Engine/src/Math/SHRay.cpp @@ -30,12 +30,6 @@ namespace SHADE , direction { dir } {} - SHRay::SHRay(const reactphysics3d::Ray rp3dRay) noexcept - : position { rp3dRay.point1 } - , direction { SHVec3::Normalise(rp3dRay.point2 - rp3dRay.point1) } - {} - - /*-----------------------------------------------------------------------------------*/ /* Operator Overload Definitions */ /*-----------------------------------------------------------------------------------*/ @@ -62,12 +56,6 @@ namespace SHADE return XMVector3NotEqual(LHS_POS, RHS_POS) || XMVector3NotEqual(LHS_DIR, RHS_DIR); } - SHRay::operator reactphysics3d::Ray() const noexcept - { - // We use 2km. Temp solution. - return reactphysics3d::Ray{ position, position + (direction * MAX_RAYCAST_DIST) }; - } - SHRaycastResult::operator bool() const noexcept { return hit; diff --git a/SHADE_Engine/src/Math/SHRay.h b/SHADE_Engine/src/Math/SHRay.h index 18efc224..56599018 100644 --- a/SHADE_Engine/src/Math/SHRay.h +++ b/SHADE_Engine/src/Math/SHRay.h @@ -10,10 +10,7 @@ #pragma once -#include - // Project Headers -#include "SH_API.h" #include "Vector/SHVec3.h" namespace SHADE @@ -40,7 +37,6 @@ namespace SHADE SHRay () noexcept; SHRay (const SHVec3& pos, const SHVec3& dir) noexcept; - SHRay (const reactphysics3d::Ray rp3dRay) noexcept; SHRay (const SHRay&) noexcept = default; SHRay (SHRay&& ) noexcept = default; @@ -55,8 +51,6 @@ namespace SHADE [[nodiscard]] bool operator==(const SHRay& rhs) const noexcept; [[nodiscard]] bool operator!=(const SHRay& rhs) const noexcept; - - operator reactphysics3d::Ray() const noexcept; }; struct SH_API SHRaycastResult diff --git a/SHADE_Engine/src/Math/Vector/SHVec2.cpp b/SHADE_Engine/src/Math/Vector/SHVec2.cpp index 9573be01..da0215f4 100644 --- a/SHADE_Engine/src/Math/Vector/SHVec2.cpp +++ b/SHADE_Engine/src/Math/Vector/SHVec2.cpp @@ -50,10 +50,6 @@ namespace SHADE : XMFLOAT2( _x, _y ) {} - SHVec2::SHVec2(const reactphysics3d::Vector2& rp3dVec2) noexcept - : XMFLOAT2( rp3dVec2.x, rp3dVec2.y ) - {} - /*-----------------------------------------------------------------------------------*/ /* Operator Overload Definitions */ /*-----------------------------------------------------------------------------------*/ @@ -165,6 +161,8 @@ namespace SHADE { case 0: return x; case 1: return y; + // This will never hit + default: return x; } } @@ -177,6 +175,8 @@ namespace SHADE { case 0: return x; case 1: return y; + // This will never hit + default: return x; } } @@ -189,6 +189,8 @@ namespace SHADE { case 0: return x; case 1: return y; + // This will never hit + default: return x; } } @@ -201,14 +203,11 @@ namespace SHADE { case 0: return x; case 1: return y; + // This will never hit + default: return x; } } - SHVec2::operator reactphysics3d::Vector2() const noexcept - { - return reactphysics3d::Vector2{ x, y }; - } - SHVec2 operator* (float lhs, const SHVec2& rhs) noexcept { SHVec2 result; diff --git a/SHADE_Engine/src/Math/Vector/SHVec2.h b/SHADE_Engine/src/Math/Vector/SHVec2.h index e780d3ac..78c1e6e8 100644 --- a/SHADE_Engine/src/Math/Vector/SHVec2.h +++ b/SHADE_Engine/src/Math/Vector/SHVec2.h @@ -11,7 +11,6 @@ #pragma once #include -#include #include #include @@ -59,10 +58,6 @@ namespace SHADE SHVec2 (float n) noexcept; SHVec2 (float x, float y) noexcept; - // Conversion from other math types to SHADE - - SHVec2 (const reactphysics3d::Vector2& rp3dVec2) noexcept; - /*---------------------------------------------------------------------------------*/ /* Operator Overloads */ /*---------------------------------------------------------------------------------*/ @@ -73,7 +68,6 @@ namespace SHADE // Conversion to other math types used by SHADE operator DirectX::XMVECTOR () const noexcept; - operator reactphysics3d::Vector2 () const noexcept; SHVec2& operator+= (const SHVec2& rhs) noexcept; SHVec2& operator-= (const SHVec2& rhs) noexcept; diff --git a/SHADE_Engine/src/Math/Vector/SHVec3.cpp b/SHADE_Engine/src/Math/Vector/SHVec3.cpp index 4b77636a..6b042c61 100644 --- a/SHADE_Engine/src/Math/Vector/SHVec3.cpp +++ b/SHADE_Engine/src/Math/Vector/SHVec3.cpp @@ -57,14 +57,6 @@ namespace SHADE : XMFLOAT3( _x, _y, _z ) {} - SHVec3::SHVec3(const reactphysics3d::Vector3& rp3dVec3) noexcept - : XMFLOAT3( rp3dVec3.x, rp3dVec3.y, rp3dVec3.z ) - {} - - SHVec3::SHVec3(const reactphysics3d::Quaternion& rp3dVec3) noexcept - : XMFLOAT3( SHQuaternion{rp3dVec3}.ToEuler() ) - {} - /*-----------------------------------------------------------------------------------*/ /* Operator Overload Definitions */ /*-----------------------------------------------------------------------------------*/ @@ -179,6 +171,8 @@ namespace SHADE case 0: return x; case 1: return y; case 2: return z; + // This will never hit + default: return x; } } @@ -192,6 +186,8 @@ namespace SHADE case 0: return x; case 1: return y; case 2: return z; + // This will never hit + default: return x; } } @@ -205,6 +201,8 @@ namespace SHADE case 0: return x; case 1: return y; case 2: return z; + // This will never hit + default: return x; } } @@ -218,19 +216,11 @@ namespace SHADE case 0: return x; case 1: return y; case 2: return z; + // This will never hit + default: return x; } } - SHVec3::operator reactphysics3d::Vector3() const noexcept - { - return reactphysics3d::Vector3{ x, y , z }; - } - - SHVec3::operator reactphysics3d::Quaternion() const noexcept - { - return reactphysics3d::Quaternion::fromEulerAngles(x, y, z); - } - SHVec3 operator* (float lhs, const SHVec3& rhs) noexcept { SHVec3 result; diff --git a/SHADE_Engine/src/Math/Vector/SHVec3.h b/SHADE_Engine/src/Math/Vector/SHVec3.h index de37d6a5..657e167e 100644 --- a/SHADE_Engine/src/Math/Vector/SHVec3.h +++ b/SHADE_Engine/src/Math/Vector/SHVec3.h @@ -11,8 +11,6 @@ #pragma once #include -#include -#include #include #include @@ -69,9 +67,6 @@ namespace SHADE // Conversion from other math types to SHADE - SHVec3 (const reactphysics3d::Vector3& rp3dVec3) noexcept; - SHVec3 (const reactphysics3d::Quaternion& rp3dVec3) noexcept; - /*---------------------------------------------------------------------------------*/ /* Operator Overloads */ /*---------------------------------------------------------------------------------*/ @@ -81,8 +76,6 @@ namespace SHADE // Conversion to other math types used by SHADE - operator reactphysics3d::Vector3 () const noexcept; - operator reactphysics3d::Quaternion () const noexcept; operator DirectX::XMVECTOR () const noexcept; SHVec3& operator+= (const SHVec3& rhs) noexcept; diff --git a/SHADE_Engine/src/Math/Vector/SHVec4.cpp b/SHADE_Engine/src/Math/Vector/SHVec4.cpp index c6f01d9e..c164e7f4 100644 --- a/SHADE_Engine/src/Math/Vector/SHVec4.cpp +++ b/SHADE_Engine/src/Math/Vector/SHVec4.cpp @@ -164,6 +164,8 @@ namespace SHADE case 1: return y; case 2: return z; case 3: return w; + // This will never hit + default: return x; } } @@ -178,6 +180,8 @@ namespace SHADE case 1: return y; case 2: return z; case 3: return w; + // This will never hit + default: return x; } } @@ -192,6 +196,8 @@ namespace SHADE case 1: return y; case 2: return z; case 3: return w; + // This will never hit + default: return x; } } @@ -206,6 +212,8 @@ namespace SHADE case 1: return y; case 2: return z; case 3: return w; + // This will never hit + default: return x; } } From 1b91f60c4ae3619feb75833d3e047eb09e5fedca Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Fri, 16 Dec 2022 14:38:22 +0800 Subject: [PATCH 053/275] Fixed warning with wrongly declared friends --- SHADE_Engine/src/Physics/Interface/SHColliderComponent.h | 2 +- SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h b/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h index d62b9b90..d0cc7064 100644 --- a/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h +++ b/SHADE_Engine/src/Physics/Interface/SHColliderComponent.h @@ -30,7 +30,7 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ friend class SHPhysicsSystem; - friend class SHPhysicsObject; + friend struct SHPhysicsObject; public: diff --git a/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h index d215c3f8..1173c1d5 100644 --- a/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h +++ b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.h @@ -31,7 +31,7 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ friend class SHPhysicsSystem; - friend class SHPhysicsObject; + friend struct SHPhysicsObject; public: /*---------------------------------------------------------------------------------*/ From 2bd90e7c143ddd7a665c4e5d5f58f07d16203c11 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Fri, 16 Dec 2022 14:38:46 +0800 Subject: [PATCH 054/275] Adjusted physics debug draw to fit new debug draw interface --- .../Routines/SHPhysicsPreUpdateRoutine.cpp | 5 +++-- .../System/SHPhysicsDebugDrawSystem.cpp | 19 +++++++++++-------- .../Physics/System/SHPhysicsDebugDrawSystem.h | 4 +--- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp index a26a414d..88ea8d79 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp @@ -36,12 +36,13 @@ namespace SHADE auto* physicsSystem = reinterpret_cast(GetSystem()); // Get all physics objects & sync transforms - for (auto& [entityID, physicsObject] : physicsSystem->physicsObjectManager.GetPhysicsObjects()) + auto& physicsObjects = physicsSystem->physicsObjectManager.GetPhysicsObjects(); + for (auto& [entityID, physicsObject] : physicsObjects) { const auto* TRANSFORM_COMPONENT = SHComponentManager::GetComponent_s(entityID); if (!TRANSFORM_COMPONENT || !TRANSFORM_COMPONENT->HasChanged()) - return; + continue; const SHVec3& WORLD_POS = TRANSFORM_COMPONENT->GetWorldPosition(); const SHQuaternion& WORLD_ROT = TRANSFORM_COMPONENT->GetWorldOrientation(); diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp index 62654dbb..dda437bc 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp @@ -93,6 +93,7 @@ namespace SHADE { const auto& EVENT_DATA = reinterpret_cast*>(onColliderDrawEvent.get())->data; + // Add to the container to draw all colliders if (EVENT_DATA->debugDrawState) { if (collidersToDraw.empty()) @@ -115,15 +116,23 @@ namespace SHADE { for (const auto* SHAPE : collider.GetCollisionShapes()) { + const SHColour& DRAW_COLOUR = DEBUG_DRAW_COLOURS[SHUtilities::ConvertEnum(SHAPE->IsTrigger() ? Colours::TRIGGER : Colours::COLLIDER)]; + switch (SHAPE->GetType()) { case SHCollisionShape::Type::SPHERE: { const SHSphereCollisionShape* SPHERE = dynamic_cast(SHAPE); - // Compute rotation of sphere + // Compute transforms of sphere + const SHVec3 POSITION = SPHERE->GetCenter(); // Position offset is already computed here const SHVec3 ROTATION = collider.GetOrientation().ToEuler() + SPHERE->GetRotationOffset(); - drawSphere(debugDrawSystem, *SPHERE, ROTATION); + const SHVec3 SCALE { SPHERE->GetWorldRadius() }; + + // Compute TRS for the sphere + const SHMatrix TRS = SHMatrix::Transform(POSITION, ROTATION, SCALE); + + debugDrawSystem->DrawWireSphere(TRS, DRAW_COLOUR, true); break; } @@ -140,12 +149,6 @@ namespace SHADE } } - void SHPhysicsDebugDrawSystem::drawSphere(SHDebugDrawSystem* debugDrawSystem, const SHSphereCollisionShape& sphere, const SHVec3& rotation) noexcept - { - const SHColour& DRAW_COLOUR = DEBUG_DRAW_COLOURS[SHUtilities::ConvertEnum(sphere.IsTrigger() ? Colours::TRIGGER : Colours::COLLIDER)]; - debugDrawSystem->DrawSphere(DRAW_COLOUR, sphere.GetCenter(), rotation, sphere.GetWorldRadius()); - } - void SHPhysicsDebugDrawSystem::drawContact(SHDebugDrawSystem* debugDrawSystem, const DebugDrawInfo::Contact& contactInfo) noexcept { static const SHColour& DRAW_COLOUR = DEBUG_DRAW_COLOURS[SHUtilities::ConvertEnum(Colours::CONTACT)]; diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h index 1eb4e967..79b88d3f 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h +++ b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h @@ -138,9 +138,7 @@ namespace SHADE SHEventHandle onColliderDraw(SHEventPtr onColliderDrawEvent); - static void drawCollider (SHDebugDrawSystem* debugDrawSystem, const SHCollider& collider) noexcept; - static void drawSphere (SHDebugDrawSystem* debugDrawSystem, const SHSphereCollisionShape& sphere, const SHVec3& rotation) noexcept; - + static void drawCollider (SHDebugDrawSystem* debugDrawSystem, const SHCollider& collider) noexcept; static void drawContact (SHDebugDrawSystem* debugDrawSystem, const DebugDrawInfo::Contact& contactInfo) noexcept; static void drawRaycast (SHDebugDrawSystem* debugDrawSystem, const DebugDrawInfo::Raycast& raycastInfo) noexcept; From a6e1064e64dcc8515a76c445e95f9349d6e9f706 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Fri, 16 Dec 2022 15:03:55 +0800 Subject: [PATCH 055/275] Fixed bug where collider offsets were not recomputed --- .../CollisionShapes/SHCollisionShape.cpp | 31 ++++++++ .../CollisionShapes/SHCollisionShape.h | 35 ++++++--- .../CollisionShapes/SHSphereCollisionShape.h | 4 +- .../src/Physics/Collision/SHCollider.cpp | 78 ++++++++++++++----- .../src/Physics/Collision/SHCollider.h | 10 ++- .../Routines/SHPhysicsPreUpdateRoutine.cpp | 15 ++-- 6 files changed, 135 insertions(+), 38 deletions(-) diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp index 2f4e3819..d0706ed0 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp @@ -95,6 +95,15 @@ namespace SHADE return flags & FLAG_VALUE; } + bool SHCollisionShape::IsDirty() const noexcept + { + static constexpr int FLAG_POS = 6; + static constexpr uint8_t FLAG_VALUE = 1U << FLAG_POS; + + return flags & FLAG_VALUE; + } + + const SHCollisionTag& SHCollisionShape::GetCollisionTag() const noexcept { return *collisionTag; @@ -116,21 +125,25 @@ namespace SHADE void SHCollisionShape::SetDensity(float density) noexcept { + setDirty(true); material.SetDensity(density); } void SHCollisionShape::SetMaterial(const SHPhysicsMaterial& newMaterial) noexcept { + setDirty(true); material = newMaterial; } void SHCollisionShape::SetPositionOffset(const SHVec3& posOffset) noexcept { + setDirty(true); positionOffset = posOffset; } void SHCollisionShape::SetRotationOffset(const SHVec3& rotOffset) noexcept { + setDirty(true); rotationOffset = rotOffset; } @@ -147,10 +160,28 @@ namespace SHADE collisionTag = newCollisionTag; } + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHCollisionShape::ClearDirty() noexcept + { + setDirty(false); + } + /*-----------------------------------------------------------------------------------*/ /* Private Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ + void SHCollisionShape::setDirty(bool isDirty) noexcept + { + static constexpr int FLAG_POS = 6; + static constexpr uint8_t FLAG_VALUE = 1U << FLAG_POS; + + isDirty ? flags |= FLAG_VALUE : flags &= ~FLAG_VALUE; + } + + } // namespace SHADE RTTR_REGISTRATION diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h index 6a08f940..90020727 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h @@ -91,6 +91,7 @@ namespace SHADE [[nodiscard]] Type GetType () const noexcept; [[nodiscard]] bool IsTrigger () const noexcept; [[nodiscard]] bool IsColliding () const noexcept; + [[nodiscard]] bool IsDirty () const noexcept; [[nodiscard]] const SHCollisionTag& GetCollisionTag () const noexcept; @@ -98,24 +99,31 @@ namespace SHADE /* Setter Functions */ /*---------------------------------------------------------------------------------*/ - void SetFriction (float friction) noexcept; - void SetBounciness (float bounciness) noexcept; - void SetDensity (float density) noexcept; - void SetMaterial (const SHPhysicsMaterial& newMaterial) noexcept; + void SetFriction (float friction) noexcept; + void SetBounciness (float bounciness) noexcept; + void SetDensity (float density) noexcept; + void SetMaterial (const SHPhysicsMaterial& newMaterial) noexcept; - void SetPositionOffset (const SHVec3& posOffset) noexcept; - void SetRotationOffset (const SHVec3& rotOffset) noexcept; + void SetPositionOffset (const SHVec3& posOffset) noexcept; + void SetRotationOffset (const SHVec3& rotOffset) noexcept; // Flags - void SetIsTrigger (bool isTrigger) noexcept; + + void SetIsTrigger (bool isTrigger) noexcept; - void SetCollisionTag (SHCollisionTag* newCollisionTag) noexcept; + void SetCollisionTag (SHCollisionTag* newCollisionTag) noexcept; /*---------------------------------------------------------------------------------*/ /* Member Functions */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] virtual SHMatrix GetInertiaTensor (float mass) const noexcept = 0; + /** + * @brief + * Clears the dirty flag of the collider. + */ + void ClearDirty () noexcept; + + [[nodiscard]] virtual SHMatrix GetInertiaTensor (float mass) const noexcept = 0; protected: /*---------------------------------------------------------------------------------*/ @@ -129,10 +137,17 @@ namespace SHADE SHVec3 positionOffset; SHVec3 rotationOffset; - uint8_t flags; // 0 0 wasColliding isColliding trigger capsule sphere box + uint8_t flags; // 0 dirty wasColliding isColliding trigger capsule sphere box SHCollisionTag* collisionTag; RTTR_ENABLE() + + private: + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + void setDirty (bool isDirty) noexcept; }; } // namespace SHADE diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h index 72ea05f5..8276578e 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h @@ -37,8 +37,8 @@ namespace SHADE * @brief * Encapsulate a Sphere Collision Shape used for Physics Simulations. */ - class SH_API SHSphereCollisionShape : public SHCollisionShape - , private SHSphere + class SH_API SHSphereCollisionShape final : public SHCollisionShape + , private SHSphere { private: /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp index d7ca4b14..9aedef16 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp @@ -30,6 +30,7 @@ namespace SHADE : entityID { eid } , shapeIDCounter { 0 } , debugDraw { false } + , dirty { true } , rigidBody { nullptr } , shapeFactory { nullptr } , transform { worldTransform } @@ -41,6 +42,7 @@ namespace SHADE : entityID { rhs.entityID } , shapeIDCounter { rhs.shapeIDCounter } , debugDraw { rhs.debugDraw } + , dirty { rhs.dirty } , rigidBody { rhs.rigidBody } , shapeFactory { rhs.shapeFactory } , transform { rhs.transform } @@ -58,6 +60,7 @@ namespace SHADE : entityID { rhs.entityID } , shapeIDCounter { rhs.shapeIDCounter } , debugDraw { rhs.debugDraw } + , dirty { rhs.dirty } , rigidBody { rhs.rigidBody } , shapeFactory { rhs.shapeFactory } , transform { rhs.transform } @@ -100,6 +103,7 @@ namespace SHADE entityID = rhs.entityID; debugDraw = rhs.debugDraw; + dirty = rhs.dirty; rigidBody = rhs.rigidBody; shapeFactory = rhs.shapeFactory; transform = rhs.transform; @@ -119,6 +123,7 @@ namespace SHADE entityID = rhs.entityID; debugDraw = rhs.debugDraw; + dirty = rhs.dirty; rigidBody = rhs.rigidBody; shapeFactory = rhs.shapeFactory; transform = rhs.transform; @@ -201,21 +206,25 @@ namespace SHADE void SHCollider::SetTransform(const SHTransform& newTransform) noexcept { + dirty = true; transform = newTransform; } void SHCollider::SetPosition(const SHVec3& newPosition) noexcept { + dirty = true; transform.position = newPosition; } void SHCollider::SetOrientation(const SHQuaternion& newOrientation) noexcept { + dirty = true; transform.orientation = newOrientation; } void SHCollider::SetScale(const SHVec3& newScale) noexcept { + dirty = true; transform.scale = newScale; } @@ -284,6 +293,28 @@ namespace SHADE return static_cast(shapes.size()); } + void SHCollider::Update() + { + // Forcefully recompute everything since the transform was changed + if (dirty) + { + RecomputeShapes(); + dirty = false; + return; + } + + // Recompute any shapes set to dirty + for (auto* shape : shapes) + { + if (shape->IsDirty()) + { + recomputeShape(shape); + shape->ClearDirty(); + } + } + } + + void SHCollider::RemoveCollisionShape(int index) { if (!shapeFactory) @@ -329,25 +360,7 @@ namespace SHADE void SHCollider::RecomputeShapes() noexcept { for (auto* shape : shapes) - { - switch (shape->GetType()) - { - case SHCollisionShape::Type::SPHERE: - { - recomputeSphere(dynamic_cast(shape)); - break; - } - case SHCollisionShape::Type::BOX: - { - break; - } - case SHCollisionShape::Type::CAPSULE: - { - break; - } - default: break; - } - } + recomputeShape(shape); } /*-----------------------------------------------------------------------------------*/ @@ -403,6 +416,33 @@ namespace SHADE ++shapeIDCounter; } + void SHCollider::recomputeShape(SHCollisionShape* shape) noexcept + { + if (!shape) + { + SHLOGV_ERROR_D("Shape missing from Collider {}", entityID) + return; + } + + switch (shape->GetType()) + { + case SHCollisionShape::Type::SPHERE: + { + recomputeSphere(dynamic_cast(shape)); + break; + } + case SHCollisionShape::Type::BOX: + { + break; + } + case SHCollisionShape::Type::CAPSULE: + { + break; + } + default: break; + } + } + void SHCollider::recomputeSphere(SHSphereCollisionShape* sphere) noexcept { // Recompute world radius diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.h b/SHADE_Engine/src/Physics/Collision/SHCollider.h index 927c6155..8a183dcc 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.h +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.h @@ -132,6 +132,12 @@ namespace SHADE // TODO: Add Box & Capsule + /** + * @brief + * Recomputes any shapes that were set to dirty. + */ + void Update (); + /** * @brief * Removes a shape from the container. Removal reduces the size of the container. @@ -158,6 +164,7 @@ namespace SHADE uint32_t shapeIDCounter; // This increments everytime a shape is added to differentiate shapes. bool debugDraw; + bool dirty; SHRigidBody* rigidBody; SHCollisionShapeFactory* shapeFactory; @@ -173,7 +180,8 @@ namespace SHADE void copyShapes (const SHCollider& rhsCollider); void copyShape (const SHCollisionShape* rhsShape); - void recomputeSphere (SHSphereCollisionShape* sphere) noexcept; + void recomputeShape (SHCollisionShape* shape) noexcept; + void recomputeSphere (SHSphereCollisionShape* sphere) noexcept; }; } // namespace SHADE diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp index 88ea8d79..0094153e 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp @@ -41,7 +41,7 @@ namespace SHADE { const auto* TRANSFORM_COMPONENT = SHComponentManager::GetComponent_s(entityID); - if (!TRANSFORM_COMPONENT || !TRANSFORM_COMPONENT->HasChanged()) + if (!TRANSFORM_COMPONENT) continue; const SHVec3& WORLD_POS = TRANSFORM_COMPONENT->GetWorldPosition(); @@ -50,7 +50,7 @@ namespace SHADE // We assume that all engine components and physics object components have been successfully linked - if (physicsObject.rigidBody) + if (physicsObject.rigidBody && TRANSFORM_COMPONENT->HasChanged()) { SHMotionState& motionState = physicsObject.rigidBody->GetMotionState(); @@ -60,11 +60,14 @@ namespace SHADE if (physicsObject.collider) { - physicsObject.collider->SetPosition(WORLD_POS); - physicsObject.collider->SetOrientation(WORLD_ROT); - physicsObject.collider->SetScale(WORLD_SCL); + if (TRANSFORM_COMPONENT->HasChanged()) + { + physicsObject.collider->SetPosition(WORLD_POS); + physicsObject.collider->SetOrientation(WORLD_ROT); + physicsObject.collider->SetScale(WORLD_SCL); + } - physicsObject.collider->RecomputeShapes(); + physicsObject.collider->Update(); } } } From 6a20e93704b2eda4d680f913cb6d430d87081e1b Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Fri, 16 Dec 2022 18:34:29 +0800 Subject: [PATCH 056/275] Refactored colliders to use a parent-child transform logic --- .../CollisionShapes/SHCollisionShape.cpp | 56 ++++-------- .../CollisionShapes/SHCollisionShape.h | 30 +++---- .../SHSphereCollisionShape.cpp | 37 +++++++- .../CollisionShapes/SHSphereCollisionShape.h | 14 ++- .../src/Physics/Collision/SHCollider.cpp | 85 ++----------------- .../src/Physics/Collision/SHCollider.h | 10 --- .../Routines/SHPhysicsPreUpdateRoutine.cpp | 15 ++-- .../System/SHPhysicsDebugDrawSystem.cpp | 12 +-- 8 files changed, 82 insertions(+), 177 deletions(-) diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp index d0706ed0..5dd17d35 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp @@ -25,9 +25,10 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ SHCollisionShape::SHCollisionShape(SHCollisionShapeID id, Type colliderType) - : id { id } - , flags { 0 } - , collisionTag { SHCollisionTagMatrix::GetTag(0) } + : id { id } + , flags { 0 } + , parentTransform { nullptr } + , collisionTag { SHCollisionTagMatrix::GetTag(0) } { flags |= 1U << SHUtilities::ConvertEnum(colliderType); } @@ -58,7 +59,7 @@ namespace SHADE const SHVec3& SHCollisionShape::GetPositionOffset() const noexcept { - return positionOffset; + return transform.position; } const SHVec3& SHCollisionShape::GetRotationOffset() const noexcept @@ -95,15 +96,6 @@ namespace SHADE return flags & FLAG_VALUE; } - bool SHCollisionShape::IsDirty() const noexcept - { - static constexpr int FLAG_POS = 6; - static constexpr uint8_t FLAG_VALUE = 1U << FLAG_POS; - - return flags & FLAG_VALUE; - } - - const SHCollisionTag& SHCollisionShape::GetCollisionTag() const noexcept { return *collisionTag; @@ -125,26 +117,32 @@ namespace SHADE void SHCollisionShape::SetDensity(float density) noexcept { - setDirty(true); material.SetDensity(density); } void SHCollisionShape::SetMaterial(const SHPhysicsMaterial& newMaterial) noexcept { - setDirty(true); material = newMaterial; } + void SHCollisionShape::SetParentTransform(SHTransform& parentTF) noexcept + { + parentTransform = &parentTF; + } + void SHCollisionShape::SetPositionOffset(const SHVec3& posOffset) noexcept { - setDirty(true); - positionOffset = posOffset; + transform.position = posOffset; + + ComputeTransforms(); } void SHCollisionShape::SetRotationOffset(const SHVec3& rotOffset) noexcept { - setDirty(true); rotationOffset = rotOffset; + transform.orientation = SHQuaternion::FromEuler(rotationOffset); + + ComputeTransforms(); } void SHCollisionShape::SetIsTrigger(bool isTrigger) noexcept @@ -160,28 +158,6 @@ namespace SHADE collisionTag = newCollisionTag; } - /*-----------------------------------------------------------------------------------*/ - /* Public Function Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHCollisionShape::ClearDirty() noexcept - { - setDirty(false); - } - - /*-----------------------------------------------------------------------------------*/ - /* Private Function Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHCollisionShape::setDirty(bool isDirty) noexcept - { - static constexpr int FLAG_POS = 6; - static constexpr uint8_t FLAG_VALUE = 1U << FLAG_POS; - - isDirty ? flags |= FLAG_VALUE : flags &= ~FLAG_VALUE; - } - - } // namespace SHADE RTTR_REGISTRATION diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h index 90020727..3c08e21a 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h @@ -14,11 +14,10 @@ // Project Headers #include "ECS_Base/Entity/SHEntity.h" -#include "Math/Geometry/SHShape.h" -#include "Math/SHQuaternion.h" #include "Physics/Collision/CollisionTags/SHCollisionTags.h" #include "Physics/Collision/SHPhysicsMaterial.h" #include "SHCollisionShapeID.h" +#include "Math/Transform/SHTransform.h" namespace SHADE { @@ -91,7 +90,6 @@ namespace SHADE [[nodiscard]] Type GetType () const noexcept; [[nodiscard]] bool IsTrigger () const noexcept; [[nodiscard]] bool IsColliding () const noexcept; - [[nodiscard]] bool IsDirty () const noexcept; [[nodiscard]] const SHCollisionTag& GetCollisionTag () const noexcept; @@ -104,6 +102,8 @@ namespace SHADE void SetDensity (float density) noexcept; void SetMaterial (const SHPhysicsMaterial& newMaterial) noexcept; + void SetParentTransform (SHTransform& parentTF) noexcept; + void SetPositionOffset (const SHVec3& posOffset) noexcept; void SetRotationOffset (const SHVec3& rotOffset) noexcept; @@ -117,13 +117,9 @@ namespace SHADE /* Member Functions */ /*---------------------------------------------------------------------------------*/ - /** - * @brief - * Clears the dirty flag of the collider. - */ - void ClearDirty () noexcept; - - [[nodiscard]] virtual SHMatrix GetInertiaTensor (float mass) const noexcept = 0; + virtual void ComputeTransforms () noexcept = 0; + [[nodiscard]] virtual SHMatrix GetInertiaTensor (float mass) const noexcept = 0; + [[nodiscard]] virtual SHMatrix ComputeWorldTransform () const noexcept = 0; protected: /*---------------------------------------------------------------------------------*/ @@ -134,20 +130,16 @@ namespace SHADE SHPhysicsMaterial material; - SHVec3 positionOffset; + SHTransform* parentTransform; + SHTransform transform; + + // Needed for conversion to euler angles SHVec3 rotationOffset; - uint8_t flags; // 0 dirty wasColliding isColliding trigger capsule sphere box + uint8_t flags; // 0 0 wasColliding isColliding trigger capsule sphere box SHCollisionTag* collisionTag; RTTR_ENABLE() - - private: - /*---------------------------------------------------------------------------------*/ - /* Member Functions */ - /*---------------------------------------------------------------------------------*/ - - void setDirty (bool isDirty) noexcept; }; } // namespace SHADE diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp index 40671a96..e4bf68c3 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp @@ -14,6 +14,7 @@ #include "SHSphereCollisionShape.h" // Project Headers +#include "Math/SHMathHelpers.h" #include "Math/SHMatrix.h" namespace SHADE @@ -37,7 +38,8 @@ namespace SHADE { material = rhs.material; - positionOffset = rhs.positionOffset; + parentTransform = rhs.parentTransform; + transform = rhs.transform; rotationOffset = rhs.rotationOffset; flags = rhs.flags; // Since all collision tags are taken from the matrix, we do not need to do a deep copy here. @@ -52,7 +54,8 @@ namespace SHADE { material = rhs.material; - positionOffset = rhs.positionOffset; + parentTransform = rhs.parentTransform; + transform = rhs.transform; rotationOffset = rhs.rotationOffset; flags = rhs.flags; // Since all collision tags are taken from the matrix, we do not need to do a deep copy here. @@ -72,7 +75,8 @@ namespace SHADE id = rhs.id; material = rhs.material; - positionOffset = rhs.positionOffset; + parentTransform = rhs.parentTransform; + transform = rhs.transform; rotationOffset = rhs.rotationOffset; flags = rhs.flags; // Since all collision tags are taken from the matrix, we do not need to do a deep copy here. @@ -97,7 +101,8 @@ namespace SHADE id = rhs.id; material = rhs.material; - positionOffset = rhs.positionOffset; + parentTransform = rhs.parentTransform; + transform = rhs.transform; rotationOffset = rhs.rotationOffset; flags = rhs.flags; // Since all collision tags are taken from the matrix, we do not need to do a deep copy here. @@ -172,6 +177,18 @@ namespace SHADE /* Public Member Function Definitions */ /*-----------------------------------------------------------------------------------*/ + void SHSphereCollisionShape::ComputeTransforms() noexcept + { + const float SPHERE_SCALE = std::fabs(SHMath::Max({ parentTransform->scale.x, parentTransform->scale.y, parentTransform->scale.z })); + SetScale(SPHERE_SCALE); + + // Recompute center + const SHQuaternion FINAL_ROT = parentTransform->orientation * transform.orientation; + const SHMatrix TRS = SHMatrix::Rotate(FINAL_ROT) * SHMatrix::Translate(parentTransform->position); + + Center = SHVec3::Transform(transform.position, TRS); + } + bool SHSphereCollisionShape::TestPoint(const SHVec3& point) const noexcept { return SHSphere::TestPoint(point); @@ -193,6 +210,18 @@ namespace SHADE return result; } + SHMatrix SHSphereCollisionShape::ComputeWorldTransform() const noexcept + { + const SHQuaternion ROTATION = parentTransform->orientation * transform.orientation; + const SHVec3 SCALE{ Radius }; + + return SHMatrix::Transform + ( + Center + , ROTATION + , SCALE +); + } } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h index 8276578e..798c70b6 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h @@ -88,6 +88,12 @@ namespace SHADE /* Function Members */ /*---------------------------------------------------------------------------------*/ + /** + * @brief + * Recomputes the transform of this sphere. + */ + void ComputeTransforms () noexcept override; + /** * @brief * Tests if a point is inside the sphere. @@ -96,7 +102,7 @@ namespace SHADE * @return * True if the point is inside the sphere. */ - [[nodiscard]] bool TestPoint (const SHVec3& point) const noexcept override; + [[nodiscard]] bool TestPoint (const SHVec3& point) const noexcept override; /** * @brief @@ -107,7 +113,7 @@ namespace SHADE * An object holding the results of the raycast.
* See the corresponding header for the contents of the object. */ - [[nodiscard]] SHRaycastResult Raycast (const SHRay& ray) const noexcept override; + [[nodiscard]] SHRaycastResult Raycast (const SHRay& ray) const noexcept override; /** * @brief @@ -117,9 +123,9 @@ namespace SHADE * @return * The inertia tensor of the sphere. */ - [[nodiscard]] SHMatrix GetInertiaTensor (float mass) const noexcept override; + [[nodiscard]] SHMatrix GetInertiaTensor (float mass) const noexcept override; - + [[nodiscard]] SHMatrix ComputeWorldTransform () const noexcept override; private: /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp index 9aedef16..a6527e48 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp @@ -30,19 +30,15 @@ namespace SHADE : entityID { eid } , shapeIDCounter { 0 } , debugDraw { false } - , dirty { true } , rigidBody { nullptr } , shapeFactory { nullptr } , transform { worldTransform } - { - - } + {} SHCollider::SHCollider(const SHCollider& rhs) noexcept : entityID { rhs.entityID } , shapeIDCounter { rhs.shapeIDCounter } , debugDraw { rhs.debugDraw } - , dirty { rhs.dirty } , rigidBody { rhs.rigidBody } , shapeFactory { rhs.shapeFactory } , transform { rhs.transform } @@ -60,7 +56,6 @@ namespace SHADE : entityID { rhs.entityID } , shapeIDCounter { rhs.shapeIDCounter } , debugDraw { rhs.debugDraw } - , dirty { rhs.dirty } , rigidBody { rhs.rigidBody } , shapeFactory { rhs.shapeFactory } , transform { rhs.transform } @@ -103,7 +98,6 @@ namespace SHADE entityID = rhs.entityID; debugDraw = rhs.debugDraw; - dirty = rhs.dirty; rigidBody = rhs.rigidBody; shapeFactory = rhs.shapeFactory; transform = rhs.transform; @@ -123,7 +117,6 @@ namespace SHADE entityID = rhs.entityID; debugDraw = rhs.debugDraw; - dirty = rhs.dirty; rigidBody = rhs.rigidBody; shapeFactory = rhs.shapeFactory; transform = rhs.transform; @@ -206,25 +199,21 @@ namespace SHADE void SHCollider::SetTransform(const SHTransform& newTransform) noexcept { - dirty = true; transform = newTransform; } void SHCollider::SetPosition(const SHVec3& newPosition) noexcept { - dirty = true; transform.position = newPosition; } void SHCollider::SetOrientation(const SHQuaternion& newOrientation) noexcept { - dirty = true; transform.orientation = newOrientation; } void SHCollider::SetScale(const SHVec3& newScale) noexcept { - dirty = true; transform.scale = newScale; } @@ -272,8 +261,9 @@ namespace SHADE ++shapeIDCounter; // Set offsets - sphere->positionOffset = posOffset; - sphere->rotationOffset = rotOffset; + sphere->SetParentTransform(transform); + sphere->SetPositionOffset(posOffset); + sphere->SetRotationOffset(rotOffset); shapes.emplace_back(sphere); @@ -293,28 +283,6 @@ namespace SHADE return static_cast(shapes.size()); } - void SHCollider::Update() - { - // Forcefully recompute everything since the transform was changed - if (dirty) - { - RecomputeShapes(); - dirty = false; - return; - } - - // Recompute any shapes set to dirty - for (auto* shape : shapes) - { - if (shape->IsDirty()) - { - recomputeShape(shape); - shape->ClearDirty(); - } - } - } - - void SHCollider::RemoveCollisionShape(int index) { if (!shapeFactory) @@ -360,7 +328,7 @@ namespace SHADE void SHCollider::RecomputeShapes() noexcept { for (auto* shape : shapes) - recomputeShape(shape); + shape->ComputeTransforms(); } /*-----------------------------------------------------------------------------------*/ @@ -416,47 +384,4 @@ namespace SHADE ++shapeIDCounter; } - void SHCollider::recomputeShape(SHCollisionShape* shape) noexcept - { - if (!shape) - { - SHLOGV_ERROR_D("Shape missing from Collider {}", entityID) - return; - } - - switch (shape->GetType()) - { - case SHCollisionShape::Type::SPHERE: - { - recomputeSphere(dynamic_cast(shape)); - break; - } - case SHCollisionShape::Type::BOX: - { - break; - } - case SHCollisionShape::Type::CAPSULE: - { - break; - } - default: break; - } - } - - void SHCollider::recomputeSphere(SHSphereCollisionShape* sphere) noexcept - { - // Recompute world radius - const float SPHERE_SCALE = std::fabs(SHMath::Max({ transform.scale.x, transform.scale.y, transform.scale.z })); - sphere->SetScale(SPHERE_SCALE); - - // Recompute center - const SHQuaternion FINAL_ROT = transform.orientation * SHQuaternion::FromEuler(sphere->rotationOffset); - const SHMatrix TRS = SHMatrix::Rotate(FINAL_ROT) * SHMatrix::Translate(transform.position); - - const SHVec3 NEW_CENTER = SHVec3::Transform(sphere->positionOffset, TRS); - - sphere->SetCenter(NEW_CENTER); - } - - } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.h b/SHADE_Engine/src/Physics/Collision/SHCollider.h index 8a183dcc..566bae3e 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.h +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.h @@ -132,12 +132,6 @@ namespace SHADE // TODO: Add Box & Capsule - /** - * @brief - * Recomputes any shapes that were set to dirty. - */ - void Update (); - /** * @brief * Removes a shape from the container. Removal reduces the size of the container. @@ -164,7 +158,6 @@ namespace SHADE uint32_t shapeIDCounter; // This increments everytime a shape is added to differentiate shapes. bool debugDraw; - bool dirty; SHRigidBody* rigidBody; SHCollisionShapeFactory* shapeFactory; @@ -179,9 +172,6 @@ namespace SHADE void copyShapes (const SHCollider& rhsCollider); void copyShape (const SHCollisionShape* rhsShape); - - void recomputeShape (SHCollisionShape* shape) noexcept; - void recomputeSphere (SHSphereCollisionShape* sphere) noexcept; }; } // namespace SHADE diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp index 0094153e..88ea8d79 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp @@ -41,7 +41,7 @@ namespace SHADE { const auto* TRANSFORM_COMPONENT = SHComponentManager::GetComponent_s(entityID); - if (!TRANSFORM_COMPONENT) + if (!TRANSFORM_COMPONENT || !TRANSFORM_COMPONENT->HasChanged()) continue; const SHVec3& WORLD_POS = TRANSFORM_COMPONENT->GetWorldPosition(); @@ -50,7 +50,7 @@ namespace SHADE // We assume that all engine components and physics object components have been successfully linked - if (physicsObject.rigidBody && TRANSFORM_COMPONENT->HasChanged()) + if (physicsObject.rigidBody) { SHMotionState& motionState = physicsObject.rigidBody->GetMotionState(); @@ -60,14 +60,11 @@ namespace SHADE if (physicsObject.collider) { - if (TRANSFORM_COMPONENT->HasChanged()) - { - physicsObject.collider->SetPosition(WORLD_POS); - physicsObject.collider->SetOrientation(WORLD_ROT); - physicsObject.collider->SetScale(WORLD_SCL); - } + physicsObject.collider->SetPosition(WORLD_POS); + physicsObject.collider->SetOrientation(WORLD_ROT); + physicsObject.collider->SetScale(WORLD_SCL); - physicsObject.collider->Update(); + physicsObject.collider->RecomputeShapes(); } } } diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp index dda437bc..958ac61a 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp @@ -122,17 +122,7 @@ namespace SHADE { case SHCollisionShape::Type::SPHERE: { - const SHSphereCollisionShape* SPHERE = dynamic_cast(SHAPE); - - // Compute transforms of sphere - const SHVec3 POSITION = SPHERE->GetCenter(); // Position offset is already computed here - const SHVec3 ROTATION = collider.GetOrientation().ToEuler() + SPHERE->GetRotationOffset(); - const SHVec3 SCALE { SPHERE->GetWorldRadius() }; - - // Compute TRS for the sphere - const SHMatrix TRS = SHMatrix::Transform(POSITION, ROTATION, SCALE); - - debugDrawSystem->DrawWireSphere(TRS, DRAW_COLOUR, true); + debugDrawSystem->DrawWireSphere(SHAPE->ComputeWorldTransform(), DRAW_COLOUR, true); break; } From 24b13ed6e42815573900870a65906a61afa975d3 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Fri, 16 Dec 2022 18:34:29 +0800 Subject: [PATCH 057/275] Refactored colliders to use parent-child transform logic --- .../CollisionShapes/SHCollisionShape.cpp | 56 ++++-------- .../CollisionShapes/SHCollisionShape.h | 30 +++---- .../SHSphereCollisionShape.cpp | 37 +++++++- .../CollisionShapes/SHSphereCollisionShape.h | 14 ++- .../src/Physics/Collision/SHCollider.cpp | 85 ++----------------- .../src/Physics/Collision/SHCollider.h | 10 --- .../Routines/SHPhysicsPreUpdateRoutine.cpp | 15 ++-- .../System/SHPhysicsDebugDrawSystem.cpp | 12 +-- 8 files changed, 82 insertions(+), 177 deletions(-) diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp index d0706ed0..5dd17d35 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp @@ -25,9 +25,10 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ SHCollisionShape::SHCollisionShape(SHCollisionShapeID id, Type colliderType) - : id { id } - , flags { 0 } - , collisionTag { SHCollisionTagMatrix::GetTag(0) } + : id { id } + , flags { 0 } + , parentTransform { nullptr } + , collisionTag { SHCollisionTagMatrix::GetTag(0) } { flags |= 1U << SHUtilities::ConvertEnum(colliderType); } @@ -58,7 +59,7 @@ namespace SHADE const SHVec3& SHCollisionShape::GetPositionOffset() const noexcept { - return positionOffset; + return transform.position; } const SHVec3& SHCollisionShape::GetRotationOffset() const noexcept @@ -95,15 +96,6 @@ namespace SHADE return flags & FLAG_VALUE; } - bool SHCollisionShape::IsDirty() const noexcept - { - static constexpr int FLAG_POS = 6; - static constexpr uint8_t FLAG_VALUE = 1U << FLAG_POS; - - return flags & FLAG_VALUE; - } - - const SHCollisionTag& SHCollisionShape::GetCollisionTag() const noexcept { return *collisionTag; @@ -125,26 +117,32 @@ namespace SHADE void SHCollisionShape::SetDensity(float density) noexcept { - setDirty(true); material.SetDensity(density); } void SHCollisionShape::SetMaterial(const SHPhysicsMaterial& newMaterial) noexcept { - setDirty(true); material = newMaterial; } + void SHCollisionShape::SetParentTransform(SHTransform& parentTF) noexcept + { + parentTransform = &parentTF; + } + void SHCollisionShape::SetPositionOffset(const SHVec3& posOffset) noexcept { - setDirty(true); - positionOffset = posOffset; + transform.position = posOffset; + + ComputeTransforms(); } void SHCollisionShape::SetRotationOffset(const SHVec3& rotOffset) noexcept { - setDirty(true); rotationOffset = rotOffset; + transform.orientation = SHQuaternion::FromEuler(rotationOffset); + + ComputeTransforms(); } void SHCollisionShape::SetIsTrigger(bool isTrigger) noexcept @@ -160,28 +158,6 @@ namespace SHADE collisionTag = newCollisionTag; } - /*-----------------------------------------------------------------------------------*/ - /* Public Function Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHCollisionShape::ClearDirty() noexcept - { - setDirty(false); - } - - /*-----------------------------------------------------------------------------------*/ - /* Private Function Member Definitions */ - /*-----------------------------------------------------------------------------------*/ - - void SHCollisionShape::setDirty(bool isDirty) noexcept - { - static constexpr int FLAG_POS = 6; - static constexpr uint8_t FLAG_VALUE = 1U << FLAG_POS; - - isDirty ? flags |= FLAG_VALUE : flags &= ~FLAG_VALUE; - } - - } // namespace SHADE RTTR_REGISTRATION diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h index 90020727..3c08e21a 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h @@ -14,11 +14,10 @@ // Project Headers #include "ECS_Base/Entity/SHEntity.h" -#include "Math/Geometry/SHShape.h" -#include "Math/SHQuaternion.h" #include "Physics/Collision/CollisionTags/SHCollisionTags.h" #include "Physics/Collision/SHPhysicsMaterial.h" #include "SHCollisionShapeID.h" +#include "Math/Transform/SHTransform.h" namespace SHADE { @@ -91,7 +90,6 @@ namespace SHADE [[nodiscard]] Type GetType () const noexcept; [[nodiscard]] bool IsTrigger () const noexcept; [[nodiscard]] bool IsColliding () const noexcept; - [[nodiscard]] bool IsDirty () const noexcept; [[nodiscard]] const SHCollisionTag& GetCollisionTag () const noexcept; @@ -104,6 +102,8 @@ namespace SHADE void SetDensity (float density) noexcept; void SetMaterial (const SHPhysicsMaterial& newMaterial) noexcept; + void SetParentTransform (SHTransform& parentTF) noexcept; + void SetPositionOffset (const SHVec3& posOffset) noexcept; void SetRotationOffset (const SHVec3& rotOffset) noexcept; @@ -117,13 +117,9 @@ namespace SHADE /* Member Functions */ /*---------------------------------------------------------------------------------*/ - /** - * @brief - * Clears the dirty flag of the collider. - */ - void ClearDirty () noexcept; - - [[nodiscard]] virtual SHMatrix GetInertiaTensor (float mass) const noexcept = 0; + virtual void ComputeTransforms () noexcept = 0; + [[nodiscard]] virtual SHMatrix GetInertiaTensor (float mass) const noexcept = 0; + [[nodiscard]] virtual SHMatrix ComputeWorldTransform () const noexcept = 0; protected: /*---------------------------------------------------------------------------------*/ @@ -134,20 +130,16 @@ namespace SHADE SHPhysicsMaterial material; - SHVec3 positionOffset; + SHTransform* parentTransform; + SHTransform transform; + + // Needed for conversion to euler angles SHVec3 rotationOffset; - uint8_t flags; // 0 dirty wasColliding isColliding trigger capsule sphere box + uint8_t flags; // 0 0 wasColliding isColliding trigger capsule sphere box SHCollisionTag* collisionTag; RTTR_ENABLE() - - private: - /*---------------------------------------------------------------------------------*/ - /* Member Functions */ - /*---------------------------------------------------------------------------------*/ - - void setDirty (bool isDirty) noexcept; }; } // namespace SHADE diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp index 40671a96..e4bf68c3 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp @@ -14,6 +14,7 @@ #include "SHSphereCollisionShape.h" // Project Headers +#include "Math/SHMathHelpers.h" #include "Math/SHMatrix.h" namespace SHADE @@ -37,7 +38,8 @@ namespace SHADE { material = rhs.material; - positionOffset = rhs.positionOffset; + parentTransform = rhs.parentTransform; + transform = rhs.transform; rotationOffset = rhs.rotationOffset; flags = rhs.flags; // Since all collision tags are taken from the matrix, we do not need to do a deep copy here. @@ -52,7 +54,8 @@ namespace SHADE { material = rhs.material; - positionOffset = rhs.positionOffset; + parentTransform = rhs.parentTransform; + transform = rhs.transform; rotationOffset = rhs.rotationOffset; flags = rhs.flags; // Since all collision tags are taken from the matrix, we do not need to do a deep copy here. @@ -72,7 +75,8 @@ namespace SHADE id = rhs.id; material = rhs.material; - positionOffset = rhs.positionOffset; + parentTransform = rhs.parentTransform; + transform = rhs.transform; rotationOffset = rhs.rotationOffset; flags = rhs.flags; // Since all collision tags are taken from the matrix, we do not need to do a deep copy here. @@ -97,7 +101,8 @@ namespace SHADE id = rhs.id; material = rhs.material; - positionOffset = rhs.positionOffset; + parentTransform = rhs.parentTransform; + transform = rhs.transform; rotationOffset = rhs.rotationOffset; flags = rhs.flags; // Since all collision tags are taken from the matrix, we do not need to do a deep copy here. @@ -172,6 +177,18 @@ namespace SHADE /* Public Member Function Definitions */ /*-----------------------------------------------------------------------------------*/ + void SHSphereCollisionShape::ComputeTransforms() noexcept + { + const float SPHERE_SCALE = std::fabs(SHMath::Max({ parentTransform->scale.x, parentTransform->scale.y, parentTransform->scale.z })); + SetScale(SPHERE_SCALE); + + // Recompute center + const SHQuaternion FINAL_ROT = parentTransform->orientation * transform.orientation; + const SHMatrix TRS = SHMatrix::Rotate(FINAL_ROT) * SHMatrix::Translate(parentTransform->position); + + Center = SHVec3::Transform(transform.position, TRS); + } + bool SHSphereCollisionShape::TestPoint(const SHVec3& point) const noexcept { return SHSphere::TestPoint(point); @@ -193,6 +210,18 @@ namespace SHADE return result; } + SHMatrix SHSphereCollisionShape::ComputeWorldTransform() const noexcept + { + const SHQuaternion ROTATION = parentTransform->orientation * transform.orientation; + const SHVec3 SCALE{ Radius }; + + return SHMatrix::Transform + ( + Center + , ROTATION + , SCALE +); + } } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h index 8276578e..798c70b6 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h @@ -88,6 +88,12 @@ namespace SHADE /* Function Members */ /*---------------------------------------------------------------------------------*/ + /** + * @brief + * Recomputes the transform of this sphere. + */ + void ComputeTransforms () noexcept override; + /** * @brief * Tests if a point is inside the sphere. @@ -96,7 +102,7 @@ namespace SHADE * @return * True if the point is inside the sphere. */ - [[nodiscard]] bool TestPoint (const SHVec3& point) const noexcept override; + [[nodiscard]] bool TestPoint (const SHVec3& point) const noexcept override; /** * @brief @@ -107,7 +113,7 @@ namespace SHADE * An object holding the results of the raycast.
* See the corresponding header for the contents of the object. */ - [[nodiscard]] SHRaycastResult Raycast (const SHRay& ray) const noexcept override; + [[nodiscard]] SHRaycastResult Raycast (const SHRay& ray) const noexcept override; /** * @brief @@ -117,9 +123,9 @@ namespace SHADE * @return * The inertia tensor of the sphere. */ - [[nodiscard]] SHMatrix GetInertiaTensor (float mass) const noexcept override; + [[nodiscard]] SHMatrix GetInertiaTensor (float mass) const noexcept override; - + [[nodiscard]] SHMatrix ComputeWorldTransform () const noexcept override; private: /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp index 9aedef16..a6527e48 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp @@ -30,19 +30,15 @@ namespace SHADE : entityID { eid } , shapeIDCounter { 0 } , debugDraw { false } - , dirty { true } , rigidBody { nullptr } , shapeFactory { nullptr } , transform { worldTransform } - { - - } + {} SHCollider::SHCollider(const SHCollider& rhs) noexcept : entityID { rhs.entityID } , shapeIDCounter { rhs.shapeIDCounter } , debugDraw { rhs.debugDraw } - , dirty { rhs.dirty } , rigidBody { rhs.rigidBody } , shapeFactory { rhs.shapeFactory } , transform { rhs.transform } @@ -60,7 +56,6 @@ namespace SHADE : entityID { rhs.entityID } , shapeIDCounter { rhs.shapeIDCounter } , debugDraw { rhs.debugDraw } - , dirty { rhs.dirty } , rigidBody { rhs.rigidBody } , shapeFactory { rhs.shapeFactory } , transform { rhs.transform } @@ -103,7 +98,6 @@ namespace SHADE entityID = rhs.entityID; debugDraw = rhs.debugDraw; - dirty = rhs.dirty; rigidBody = rhs.rigidBody; shapeFactory = rhs.shapeFactory; transform = rhs.transform; @@ -123,7 +117,6 @@ namespace SHADE entityID = rhs.entityID; debugDraw = rhs.debugDraw; - dirty = rhs.dirty; rigidBody = rhs.rigidBody; shapeFactory = rhs.shapeFactory; transform = rhs.transform; @@ -206,25 +199,21 @@ namespace SHADE void SHCollider::SetTransform(const SHTransform& newTransform) noexcept { - dirty = true; transform = newTransform; } void SHCollider::SetPosition(const SHVec3& newPosition) noexcept { - dirty = true; transform.position = newPosition; } void SHCollider::SetOrientation(const SHQuaternion& newOrientation) noexcept { - dirty = true; transform.orientation = newOrientation; } void SHCollider::SetScale(const SHVec3& newScale) noexcept { - dirty = true; transform.scale = newScale; } @@ -272,8 +261,9 @@ namespace SHADE ++shapeIDCounter; // Set offsets - sphere->positionOffset = posOffset; - sphere->rotationOffset = rotOffset; + sphere->SetParentTransform(transform); + sphere->SetPositionOffset(posOffset); + sphere->SetRotationOffset(rotOffset); shapes.emplace_back(sphere); @@ -293,28 +283,6 @@ namespace SHADE return static_cast(shapes.size()); } - void SHCollider::Update() - { - // Forcefully recompute everything since the transform was changed - if (dirty) - { - RecomputeShapes(); - dirty = false; - return; - } - - // Recompute any shapes set to dirty - for (auto* shape : shapes) - { - if (shape->IsDirty()) - { - recomputeShape(shape); - shape->ClearDirty(); - } - } - } - - void SHCollider::RemoveCollisionShape(int index) { if (!shapeFactory) @@ -360,7 +328,7 @@ namespace SHADE void SHCollider::RecomputeShapes() noexcept { for (auto* shape : shapes) - recomputeShape(shape); + shape->ComputeTransforms(); } /*-----------------------------------------------------------------------------------*/ @@ -416,47 +384,4 @@ namespace SHADE ++shapeIDCounter; } - void SHCollider::recomputeShape(SHCollisionShape* shape) noexcept - { - if (!shape) - { - SHLOGV_ERROR_D("Shape missing from Collider {}", entityID) - return; - } - - switch (shape->GetType()) - { - case SHCollisionShape::Type::SPHERE: - { - recomputeSphere(dynamic_cast(shape)); - break; - } - case SHCollisionShape::Type::BOX: - { - break; - } - case SHCollisionShape::Type::CAPSULE: - { - break; - } - default: break; - } - } - - void SHCollider::recomputeSphere(SHSphereCollisionShape* sphere) noexcept - { - // Recompute world radius - const float SPHERE_SCALE = std::fabs(SHMath::Max({ transform.scale.x, transform.scale.y, transform.scale.z })); - sphere->SetScale(SPHERE_SCALE); - - // Recompute center - const SHQuaternion FINAL_ROT = transform.orientation * SHQuaternion::FromEuler(sphere->rotationOffset); - const SHMatrix TRS = SHMatrix::Rotate(FINAL_ROT) * SHMatrix::Translate(transform.position); - - const SHVec3 NEW_CENTER = SHVec3::Transform(sphere->positionOffset, TRS); - - sphere->SetCenter(NEW_CENTER); - } - - } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.h b/SHADE_Engine/src/Physics/Collision/SHCollider.h index 8a183dcc..566bae3e 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.h +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.h @@ -132,12 +132,6 @@ namespace SHADE // TODO: Add Box & Capsule - /** - * @brief - * Recomputes any shapes that were set to dirty. - */ - void Update (); - /** * @brief * Removes a shape from the container. Removal reduces the size of the container. @@ -164,7 +158,6 @@ namespace SHADE uint32_t shapeIDCounter; // This increments everytime a shape is added to differentiate shapes. bool debugDraw; - bool dirty; SHRigidBody* rigidBody; SHCollisionShapeFactory* shapeFactory; @@ -179,9 +172,6 @@ namespace SHADE void copyShapes (const SHCollider& rhsCollider); void copyShape (const SHCollisionShape* rhsShape); - - void recomputeShape (SHCollisionShape* shape) noexcept; - void recomputeSphere (SHSphereCollisionShape* sphere) noexcept; }; } // namespace SHADE diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp index 0094153e..88ea8d79 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp @@ -41,7 +41,7 @@ namespace SHADE { const auto* TRANSFORM_COMPONENT = SHComponentManager::GetComponent_s(entityID); - if (!TRANSFORM_COMPONENT) + if (!TRANSFORM_COMPONENT || !TRANSFORM_COMPONENT->HasChanged()) continue; const SHVec3& WORLD_POS = TRANSFORM_COMPONENT->GetWorldPosition(); @@ -50,7 +50,7 @@ namespace SHADE // We assume that all engine components and physics object components have been successfully linked - if (physicsObject.rigidBody && TRANSFORM_COMPONENT->HasChanged()) + if (physicsObject.rigidBody) { SHMotionState& motionState = physicsObject.rigidBody->GetMotionState(); @@ -60,14 +60,11 @@ namespace SHADE if (physicsObject.collider) { - if (TRANSFORM_COMPONENT->HasChanged()) - { - physicsObject.collider->SetPosition(WORLD_POS); - physicsObject.collider->SetOrientation(WORLD_ROT); - physicsObject.collider->SetScale(WORLD_SCL); - } + physicsObject.collider->SetPosition(WORLD_POS); + physicsObject.collider->SetOrientation(WORLD_ROT); + physicsObject.collider->SetScale(WORLD_SCL); - physicsObject.collider->Update(); + physicsObject.collider->RecomputeShapes(); } } } diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp index dda437bc..958ac61a 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp @@ -122,17 +122,7 @@ namespace SHADE { case SHCollisionShape::Type::SPHERE: { - const SHSphereCollisionShape* SPHERE = dynamic_cast(SHAPE); - - // Compute transforms of sphere - const SHVec3 POSITION = SPHERE->GetCenter(); // Position offset is already computed here - const SHVec3 ROTATION = collider.GetOrientation().ToEuler() + SPHERE->GetRotationOffset(); - const SHVec3 SCALE { SPHERE->GetWorldRadius() }; - - // Compute TRS for the sphere - const SHMatrix TRS = SHMatrix::Transform(POSITION, ROTATION, SCALE); - - debugDrawSystem->DrawWireSphere(TRS, DRAW_COLOUR, true); + debugDrawSystem->DrawWireSphere(SHAPE->ComputeWorldTransform(), DRAW_COLOUR, true); break; } From 51479d2bd09d3670722e0405cf7751e3065d2c64 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Fri, 16 Dec 2022 21:43:33 +0800 Subject: [PATCH 058/275] Fixed bug where meshes added to the mesh library will not have correct corresponding "null" vertex bone weights and indices --- .../src/Graphics/MiddleEnd/Interface/SHMeshLibrary.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMeshLibrary.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMeshLibrary.cpp index dda5d423..df21343e 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMeshLibrary.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHMeshLibrary.cpp @@ -171,6 +171,10 @@ namespace SHADE addJob.VertexBoneIndices, addJob.VertexBoneIndices + addJob.VertexCount * SHMesh::BONE_INDICES_PER_VERTEX ); } + else + { + vertBoneIdxStorage.resize(vertBoneIdxStorage.size() + addJob.VertexCount * SHMesh::BONE_INDICES_PER_VERTEX); + } if (addJob.VertexBoneWeights) { vertBoneWeightStorage.insert @@ -179,6 +183,10 @@ namespace SHADE addJob.VertexBoneWeights, addJob.VertexBoneWeights + addJob.VertexCount ); } + else + { + vertBoneWeightStorage.resize(vertBoneWeightStorage.size() + addJob.VertexCount); + } indexStorage.insert ( indexStorage.end(), From 3b55888fa125177f49588107746e881038c3b04c Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Fri, 16 Dec 2022 23:19:44 +0800 Subject: [PATCH 059/275] Added conversions to and from wxyz quaternion representations --- SHADE_Engine/src/Math/SHQuaternion.cpp | 12 ++++++++++++ SHADE_Engine/src/Math/SHQuaternion.h | 9 ++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/SHADE_Engine/src/Math/SHQuaternion.cpp b/SHADE_Engine/src/Math/SHQuaternion.cpp index 8904cb05..1fa4e246 100644 --- a/SHADE_Engine/src/Math/SHQuaternion.cpp +++ b/SHADE_Engine/src/Math/SHQuaternion.cpp @@ -443,4 +443,16 @@ namespace SHADE return result; } + SHQuaternion SHQuaternion::FromWXYZ(float w, float x, float y, float z) noexcept + { + return SHQuaternion{ x, y, z, w }; + } + + void SHQuaternion::ToWXYZ(const SHQuaternion& from, float& w, float& x, float& y, float& z) noexcept + { + x = from.x; + y = from.y; + z = from.z; + w = from.w; + } } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/SHQuaternion.h b/SHADE_Engine/src/Math/SHQuaternion.h index fa5b5d36..29f6df7e 100644 --- a/SHADE_Engine/src/Math/SHQuaternion.h +++ b/SHADE_Engine/src/Math/SHQuaternion.h @@ -125,9 +125,12 @@ namespace SHADE [[nodiscard]] static SHQuaternion ClampedLerp (const SHQuaternion& q1, const SHQuaternion& q2, float t, float tMin = 0.0f, float tMax = 1.0f) noexcept; [[nodiscard]] static SHQuaternion ClampedSlerp (const SHQuaternion& q1, const SHQuaternion& q2, float t, float tMin = 0.0f, float tMax = 1.0f) noexcept; - [[nodiscard]] static SHQuaternion FromToRotation (const SHVec3& from, const SHVec3& to) noexcept; - [[nodiscard]] static SHQuaternion LookRotation (const SHVec3& forward, const SHVec3& up) noexcept; - [[nodiscard]] static SHQuaternion RotateTowards (const SHQuaternion& from, const SHQuaternion& to, float maxAngleInRad) noexcept; + [[nodiscard]] static SHQuaternion FromToRotation (const SHVec3& from, const SHVec3& to) noexcept; + [[nodiscard]] static SHQuaternion LookRotation (const SHVec3& forward, const SHVec3& up) noexcept; + [[nodiscard]] static SHQuaternion RotateTowards (const SHQuaternion& from, const SHQuaternion& to, float maxAngleInRad) noexcept; + + [[nodiscard]] static SHQuaternion FromWXYZ (float w, float x, float y, float z) noexcept; + static void ToWXYZ (const SHQuaternion& from, float& w, float& x, float& y, float& z) noexcept; }; SHQuaternion operator*(float lhs, const SHQuaternion& rhs) noexcept; From cf9d4ef04bf518a3a370ad6312dbbdbe352a5f42 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Mon, 19 Dec 2022 16:56:34 +0800 Subject: [PATCH 060/275] Implemented backbone for collision detection with broadphase --- Assets/Scenes/PhysicsSandbox.shade | 42 +- SHADE_Engine/src/Math/Geometry/SHBox.h | 2 +- .../Broadphase/SHDynamicAABBTree.cpp | 607 ++++++++++++++++++ .../Collision/Broadphase/SHDynamicAABBTree.h | 150 +++++ .../CollisionShapes/SHCollisionShape.h | 6 +- .../CollisionShapes/SHCollisionShapeID.h | 14 +- .../CollisionShapes/SHCollisionShapeID.hpp | 20 +- .../SHSphereCollisionShape.cpp | 7 +- .../CollisionShapes/SHSphereCollisionShape.h | 3 + .../Collision/Contacts/SHCollisionEvents.h | 60 ++ .../Collision/Contacts/SHCollisionID.cpp | 152 +++++ .../Collision/Contacts/SHCollisionID.h | 121 ++++ .../Physics/Collision/Contacts/SHContact.h | 70 ++ .../Physics/Collision/Contacts/SHManifold.h | 46 ++ .../Collision/Narrowphase/SHCollision.h | 52 ++ .../Narrowphase/SHCollisionDispatch.cpp | 95 +++ .../Narrowphase/SHCollisionDispatch.h | 87 +++ .../Narrowphase/SHSphereVsSphere.cpp | 78 +++ .../src/Physics/Collision/SHCollider.cpp | 56 +- .../src/Physics/Collision/SHCollider.h | 4 +- .../src/Physics/Dynamics/SHPhysicsWorld.cpp | 373 ++++++++++- .../src/Physics/Dynamics/SHPhysicsWorld.h | 74 ++- 22 files changed, 2064 insertions(+), 55 deletions(-) create mode 100644 SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.cpp create mode 100644 SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.h create mode 100644 SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionEvents.h create mode 100644 SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionID.cpp create mode 100644 SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionID.h create mode 100644 SHADE_Engine/src/Physics/Collision/Contacts/SHContact.h create mode 100644 SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h create mode 100644 SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollision.h create mode 100644 SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollisionDispatch.cpp create mode 100644 SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollisionDispatch.h create mode 100644 SHADE_Engine/src/Physics/Collision/Narrowphase/SHSphereVsSphere.cpp diff --git a/Assets/Scenes/PhysicsSandbox.shade b/Assets/Scenes/PhysicsSandbox.shade index 7381b70c..8817b62e 100644 --- a/Assets/Scenes/PhysicsSandbox.shade +++ b/Assets/Scenes/PhysicsSandbox.shade @@ -4,7 +4,7 @@ NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 0, y: 1.77475965, z: 0} + Translate: {x: 0, y: 3, z: 0} Rotate: {x: 0, y: 0, z: -0} Scale: {x: 1, y: 1, z: 1} IsActive: true @@ -58,4 +58,44 @@ Far: 10000 Perspective: true IsActive: true + Scripts: ~ +- EID: 2 + Name: Default + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 0, y: 1, z: 0} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + RigidBody Component: + Type: Static + Auto Mass: false + Mass: .inf + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: true + Gravity Scale: 1 + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + DrawColliders: true + Colliders: + - Is Trigger: false + Type: Sphere + Radius: 1 + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true Scripts: ~ \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Geometry/SHBox.h b/SHADE_Engine/src/Math/Geometry/SHBox.h index a0ca9458..63383c00 100644 --- a/SHADE_Engine/src/Math/Geometry/SHBox.h +++ b/SHADE_Engine/src/Math/Geometry/SHBox.h @@ -77,7 +77,7 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ [[nodiscard]] bool TestPoint (const SHVec3& point) const noexcept override; - [[nodiscard]] SHRaycastResult Raycast(const SHRay& ray) const noexcept override; + [[nodiscard]] SHRaycastResult Raycast (const SHRay& ray) const noexcept override; [[nodiscard]] bool Contains (const SHBox& rhs) const noexcept; [[nodiscard]] float Volume () const noexcept; diff --git a/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.cpp b/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.cpp new file mode 100644 index 00000000..db30222b --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.cpp @@ -0,0 +1,607 @@ +/**************************************************************************************** + * \file SHDynamicAABBTree.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Dynamic AABB Tree for broadphase collision detection. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +#include + +// Primary Header +#include "SHDynamicAABBTree.h" + +// Project Headers +#include "Math/SHMathHelpers.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHAABBTree::SHAABBTree() noexcept + : root { NULL_NODE } + , nodes { nullptr } + , nodeCount { 0 } + , capacity { 1024 } + , freeList { NULL_NODE } + { + // Build initial tree + nodes = new Node[1024]; + + addToFreeList(0); + } + + SHAABBTree::~SHAABBTree() noexcept + { + delete[] nodes; + } + + SHAABBTree::Node::Node() noexcept + : id { MAX_EID, std::numeric_limits::max() } + , parent { NULL_NODE } + , left { NULL_NODE } + , right { NULL_NODE } + , height { NULL_NODE } + {} + + SHAABBTree::Node::Node(const Node& rhs) noexcept + : AABB { rhs.AABB } + , id { rhs.id } + , next { rhs.next } + , left { rhs.left } + , right { rhs.right } + , height { rhs.height } + {} + + SHAABBTree::Node::Node(Node&& rhs) noexcept + : AABB { rhs.AABB } + , id { rhs.id } + , next { rhs.next } + , left { rhs.left } + , right { rhs.right } + , height { rhs.height } + {} + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHAABBTree::Node& SHAABBTree::Node::operator=(const Node& rhs) noexcept + { + if (this == &rhs) + return *this; + + AABB = rhs.AABB; + id = rhs.id; + parent = rhs.parent; + next = rhs.next; + left = rhs.left; + right = rhs.right; + height = rhs.height; + + return *this; + } + + SHAABBTree::Node& SHAABBTree::Node::operator=(Node&& rhs) noexcept + { + AABB = std::move(rhs.AABB); + id = std::move(rhs.id); + parent = rhs.parent; + next = rhs.next; + left = rhs.left; + right = rhs.right; + height = rhs.height; + + return *this; + } + + /*-----------------------------------------------------------------------------------*/ + /* Getter Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + const std::vector& SHAABBTree::GetAABBs() const noexcept + { + static std::vector aabbs; + static std::stack nodeIndices; + + aabbs.clear(); + + nodeIndices.push(root); + while (!nodeIndices.empty()) + { + // Pop the top node + const int INDEX = nodeIndices.top(); + nodeIndices.pop(); + + // Skip null nodes + if (INDEX == NULL_NODE) + continue; + + const Node& CURRENT_NODE = nodes[INDEX]; + + aabbs.emplace_back(CURRENT_NODE.AABB); + + if (!isLeaf(INDEX)) + { + nodeIndices.push(CURRENT_NODE.left); + nodeIndices.push(CURRENT_NODE.right); + } + } + + return aabbs; + } + + + /*-----------------------------------------------------------------------------------*/ + /* Public Member Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHAABBTree::Insert(SHCollisionShapeID id, const SHBox& AABB) + { + const int32_t NEW_INDEX = allocateNode(); + + if (!nodeMap.emplace(id, NEW_INDEX).second) + { + // Attempted to add a duplicate node + freeNode(NEW_INDEX); + return; + } + + Node& newNode = nodes[NEW_INDEX]; + newNode.AABB = AABB; + newNode.id = id; + newNode.height = 0; + + // Fatten the AABB + const SHVec3 EXTENSION{ AABB_EXTENSION }; + + const SHVec3 newMin = newNode.AABB.GetMin() - EXTENSION; + const SHVec3 newMax = newNode.AABB.GetMax() + EXTENSION; + + newNode.AABB.SetMin(newMin); + newNode.AABB.SetMax(newMax); + + insertLeaf(NEW_INDEX); + } + + void SHAABBTree::Update(SHCollisionShapeID id, const SHBox& AABB) + { + // Get node index + const int32_t INDEX_TO_UPDATE = nodeMap[id]; + + Node& nodeToUpdate = nodes[INDEX_TO_UPDATE]; + + // If new AABB has not moved enough, skip. + if (nodeToUpdate.AABB.Contains(AABB)) + return; + + removeLeaf(INDEX_TO_UPDATE); + + nodeToUpdate.AABB = AABB; + + // Fatten the AABB + const SHVec3 EXTENSION{ AABB_EXTENSION }; + + const SHVec3 newMin = nodeToUpdate.AABB.GetMin() - EXTENSION; + const SHVec3 newMax = nodeToUpdate.AABB.GetMax() + EXTENSION; + + nodeToUpdate.AABB.SetMin(newMin); + nodeToUpdate.AABB.SetMax(newMax); + + insertLeaf(INDEX_TO_UPDATE); + } + + void SHAABBTree::Remove(SHCollisionShapeID id) noexcept + { + // Get node index + const int32_t INDEX_TO_REMOVE = nodeMap[id]; + + removeLeaf(INDEX_TO_REMOVE); + freeNode(INDEX_TO_REMOVE); + } + + const std::vector& SHAABBTree::Query(SHCollisionShapeID id, const SHBox& AABB) const noexcept + { + static std::vector potentialCollisions; + static std::stack nodeIndices; + + potentialCollisions.clear(); + + // We use this to ignore shapes on the same entity + const EntityID EID = id.GetEntityID(); + + nodeIndices.push(root); + while (!nodeIndices.empty()) + { + const int32_t INDEX = nodeIndices.top(); + nodeIndices.pop(); + + if (INDEX == NULL_NODE) + continue; + + const Node& NODE = nodes[INDEX]; + if (!SHBox::Intersect(AABB, NODE.AABB)) + continue; + + // Avoid checking against shapes of the same composite collider (and itself) + if (isLeaf(INDEX) && NODE.id.GetEntityID() != EID) + { + potentialCollisions.emplace_back(NODE.id); + } + else + { + nodeIndices.push(NODE.left); + nodeIndices.push(NODE.right); + } + } + + return potentialCollisions; + } + + const std::vector& SHAABBTree::Query(const SHRay& ray, float distance) const noexcept + { + static std::vector potentialHits; + + potentialHits.clear(); + + return potentialHits; + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Member Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHAABBTree::isLeaf(int32_t index) const noexcept + { + const Node& NODE = nodes[index]; + return NODE.left == NULL_NODE && NODE.right == NULL_NODE; + } + + int32_t SHAABBTree::allocateNode() + { + if (freeList == NULL_NODE) + { + // No more free nodes available, so we need to resize the tree for more nodes + capacity *= 2; + + Node* newNodes = new Node[capacity]; + + // Copy all the nodes manually. I do this instead of memcpy to guarantee it copies properly. + for (int32_t i = 0; i < nodeCount; ++i) + { + newNodes[i].AABB = nodes[i].AABB; + newNodes[i].id = nodes[i].id; + newNodes[i].parent = nodes[i].parent; + newNodes[i].left = nodes[i].left; + newNodes[i].right = nodes[i].right; + newNodes[i].height = nodes[i].height; + } + + delete[] nodes; + nodes = newNodes; + + addToFreeList(nodeCount); + } + + const int32_t FREE_NODE = freeList; + freeList = nodes[FREE_NODE].next; + + // Set node to default + Node& newNode = nodes[FREE_NODE]; + newNode.parent = NULL_NODE; + newNode.left = NULL_NODE; + newNode.right = NULL_NODE; + newNode.height = NULL_NODE; + + ++nodeCount; + return FREE_NODE; + } + + void SHAABBTree::freeNode(int32_t index) noexcept + { + SHASSERT(index >= 0 && index < capacity, "Trying to free an invalid AABB Tree node!") + + nodes[index].next = NULL_NODE; + nodes[index].height = NULL_NODE; + + // Put it back on the free list + freeList = index; + + --nodeCount; + } + + void SHAABBTree::insertLeaf(int32_t index) + { + // If there is no root, the first insert must make the root + if (root == NULL_NODE) + { + root = index; + nodes[root].parent = NULL_NODE; + return; + } + + // Find best sibling for new leaf + // Utilise Surface Area Heuristic + const SHBox& LEAF_AABB = nodes[index].AABB; + + uint32_t searchIndex = root; + while (!isLeaf(searchIndex)) + { + const SHBox COMBINED_AABB = SHBox::Combine(LEAF_AABB, nodes[searchIndex].AABB); + const float COMBINED_AREA = COMBINED_AABB.SurfaceArea(); + + const float INHERITED_COST = 2.0f * (COMBINED_AREA - nodes[searchIndex].AABB.SurfaceArea()); + + const int32_t LEFT_INDEX = nodes[searchIndex].left; + const int32_t RIGHT_INDEX = nodes[searchIndex].right; + + float leftCost = 0.0f; + float rightCost = 0.0f; + + const float LEFT_COMBINED_AREA = SHBox::Combine(LEAF_AABB, nodes[LEFT_INDEX].AABB).SurfaceArea(); + const float RIGHT_COMBINED_AREA = SHBox::Combine(LEAF_AABB, nodes[RIGHT_INDEX].AABB).SurfaceArea(); + + // Compute cost for descending into the left + if (isLeaf(LEFT_INDEX)) + leftCost = LEFT_COMBINED_AREA + INHERITED_COST; + else + leftCost = LEFT_COMBINED_AREA - nodes[LEFT_INDEX].AABB.SurfaceArea() + INHERITED_COST; + + // Compute cost for descending into the right + if (isLeaf(RIGHT_INDEX)) + rightCost = RIGHT_COMBINED_AREA + INHERITED_COST; + else + rightCost = RIGHT_COMBINED_AREA - nodes[RIGHT_INDEX].AABB.SurfaceArea() + INHERITED_COST; + + // Early out + const float BRANCH_COST = 2.0f * COMBINED_AREA; + if (BRANCH_COST < leftCost && BRANCH_COST < rightCost) + break; + + // Traverse + searchIndex = leftCost < rightCost ? LEFT_INDEX : RIGHT_INDEX; + } + + const int32_t BEST_SIBLING = searchIndex; + + // Create a new parent for the leaf + const int32_t OLD_PARENT = nodes[BEST_SIBLING].parent; + const int32_t NEW_PARENT = allocateNode(); + + Node& newParent = nodes[NEW_PARENT]; + newParent.parent = OLD_PARENT; + newParent.id = SHCollisionShapeID{ MAX_EID, std::numeric_limits::max() }; + newParent.AABB = SHBox::Combine(LEAF_AABB, nodes[BEST_SIBLING].AABB); + newParent.height = nodes[BEST_SIBLING].height + 1; + + newParent.left = BEST_SIBLING; + newParent.right = index; + + nodes[BEST_SIBLING].parent = NEW_PARENT; + nodes[index].parent = NEW_PARENT; + + // If sibling was the root + if (OLD_PARENT == NULL_NODE) + root = NEW_PARENT; + else + (nodes[OLD_PARENT].left == BEST_SIBLING ? nodes[OLD_PARENT].left : nodes[OLD_PARENT].right) = NEW_PARENT; + + syncHierarchy(NEW_PARENT); + } + + void SHAABBTree::removeLeaf(int32_t index) + { + if (index == root) + { + root = NULL_NODE; + return; + } + + const int32_t PARENT = nodes[index].parent; + const int32_t GRANDPARENT = nodes[PARENT].parent; + const int32_t SIBLING = nodes[PARENT].left == index ? nodes[PARENT].right : nodes[PARENT].left; + + if (GRANDPARENT != NULL_NODE) + { + // Replace parent with sibling + (nodes[GRANDPARENT].left == PARENT ? nodes[GRANDPARENT].left : nodes[GRANDPARENT].right) = SIBLING; + nodes[SIBLING].parent = GRANDPARENT; + } + else + { + // Parent was root + root = SIBLING; + nodes[SIBLING].parent = NULL_NODE; + } + + freeNode(PARENT); + syncHierarchy(GRANDPARENT); + } + + void SHAABBTree::syncHierarchy(int32_t index) + { + while (index != NULL_NODE) + { + index = balance(index); + + const int32_t LEFT_INDEX = nodes[index].left; + const Node& LEFT_NODE = nodes[LEFT_INDEX]; + + const int32_t RIGHT_INDEX = nodes[index].right; + const Node& RIGHT_NODE = nodes[RIGHT_INDEX]; + + nodes[index].height = 1 + SHMath::Max(LEFT_NODE.height, RIGHT_NODE.height); + nodes[index].AABB = SHBox::Combine(LEFT_NODE.AABB, RIGHT_NODE.AABB); + + // Sync up to the root + index = nodes[index].parent; + } + } + + int32_t SHAABBTree::balance(int32_t index) + { + if (isLeaf(index) || nodes[index].height == 1) + return index; + + Node& nodeA = nodes[index]; + + const int32_t LEFT = nodeA.left; + const int32_t RIGHT = nodeA.right; + + const int32_t DIFF = nodes[RIGHT].height - nodes[LEFT].height; + + if (DIFF > 1) + return rotateLeft(index); + + if (DIFF < -1) + return rotateRight(index); + + return index; + } + + int32_t SHAABBTree::rotateLeft(int32_t index) + { + /**************************** + A C + / \ / \ + B C --> A F/G + / \ / \ + F G B G/F + ****************************/ + + // Promote C + + Node& nodeA = nodes[index]; + + const int32_t B = nodeA.left; + const int32_t C = nodeA.right; + + Node& nodeB = nodes[B]; + Node& nodeC = nodes[C]; + + const int32_t F = nodeC.left; + const int32_t G = nodeC.right; + + Node& nodeF = nodes[F]; + Node& nodeG = nodes[G]; + + if (nodeA.parent != NULL_NODE) + (nodes[nodeA.parent].left == index ? nodes[nodeA.parent].left : nodes[nodeA.parent].right) = C; + else + root = C; + + nodeC.left = index; + nodeC.parent = nodeA.parent; + nodeA.parent = C; + + if (nodeF.height > nodeG.height) + { + nodeC.right = F; + nodeA.right = G; + nodeG.parent = index; + + nodeA.AABB = SHBox::Combine(nodeB.AABB, nodeG.AABB); + nodeC.AABB = SHBox::Combine(nodeA.AABB, nodeF.AABB); + + nodeA.height = 1 + SHMath::Max(nodeB.height, nodeG.height); + nodeC.height = 1 + SHMath::Max(nodeA.height, nodeF.height); + } + else + { + nodeC.right = G; + nodeA.right = F; + nodeF.parent = index; + + nodeA.AABB = SHBox::Combine(nodeB.AABB, nodeF.AABB); + nodeC.AABB = SHBox::Combine(nodeA.AABB, nodeG.AABB); + + nodeA.height = 1 + SHMath::Max(nodeB.height, nodeF.height); + nodeC.height = 1 + SHMath::Max(nodeA.height, nodeG.height); + } + + return C; + } + + int32_t SHAABBTree::rotateRight(int32_t index) + { + /************************* + A B + / \ / \ + B C --> D/E A + / \ / \ + D E E/D C + *************************/ + + // Promote B + + Node& nodeA = nodes[index]; + + const int32_t B = nodeA.left; + const int32_t C = nodeA.right; + + Node& nodeB = nodes[B]; + Node& nodeC = nodes[C]; + + const int32_t D = nodeB.left; + const int32_t E = nodeB.right; + + Node& nodeD = nodes[D]; + Node& nodeE = nodes[E]; + + if (nodeA.parent != NULL_NODE) + (nodes[nodeA.parent].left == index ? nodes[nodeA.parent].left : nodes[nodeA.parent].right) = B; + else + root = B; + + nodeB.right = index; + nodeB.parent = nodeA.parent; + nodeA.parent = B; + + if (nodeD.height > nodeE.height) + { + nodeB.left = D; + nodeA.left = E; + nodeE.parent = index; + + nodeA.AABB = SHBox::Combine(nodeC.AABB, nodeE.AABB); + nodeB.AABB = SHBox::Combine(nodeA.AABB, nodeD.AABB); + + nodeA.height = 1 + SHMath::Max(nodeC.height, nodeE.height); + nodeB.height = 1 + SHMath::Max(nodeA.height, nodeD.height); + } + else + { + nodeB.left = E; + nodeA.left = D; + nodeD.parent = index; + + nodeA.AABB = SHBox::Combine(nodeC.AABB, nodeD.AABB); + nodeB.AABB = SHBox::Combine(nodeA.AABB, nodeE.AABB); + + nodeA.height = 1 + SHMath::Max(nodeC.height, nodeD.height); + nodeB.height = 1 + SHMath::Max(nodeA.height, nodeE.height); + } + + return B; + } + + void SHAABBTree::addToFreeList(int32_t index) noexcept + { + for (int32_t i = index; i < capacity; ++i) + { + nodes[i].next = i + 1; + nodes[i].height = NULL_NODE; + } + + nodes[capacity - 1].next = NULL_NODE; + nodes[capacity - 1].height = NULL_NODE; + + freeList = index; + } +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.h b/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.h new file mode 100644 index 00000000..973631a1 --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.h @@ -0,0 +1,150 @@ +/**************************************************************************************** + * \file SHDynamicAABBTree.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Dynamic AABB Tree for broadphase collision detection. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include + +// Project Headers +#include "Physics/Collision/CollisionShapes/SHCollisionShape.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + /** + * @brief + * Encapsulates a dynamic AABB Tree for collision detection. + */ + class SH_API SHAABBTree + { + public: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + static constexpr int NULL_NODE = -1; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHAABBTree () noexcept; + ~SHAABBTree () noexcept; + + SHAABBTree(const SHAABBTree& other) = delete; + SHAABBTree(SHAABBTree&& other) noexcept = delete; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHAABBTree& operator=(const SHAABBTree& other) = delete; + SHAABBTree& operator=(SHAABBTree&& other) noexcept = delete; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] const std::vector& GetAABBs () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + void Insert (SHCollisionShapeID id, const SHBox& AABB); + void Update (SHCollisionShapeID id, const SHBox& AABB); + void Remove (SHCollisionShapeID id) noexcept; + + [[nodiscard]] const std::vector& Query(SHCollisionShapeID id, const SHBox& AABB) const noexcept; + [[nodiscard]] const std::vector& Query(const SHRay& ray, float distance) const noexcept; + + private: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + struct Node + { + public: + /*-------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*-------------------------------------------------------------------------------*/ + + Node () noexcept; + Node (const Node& rhs) noexcept; + Node (Node&& rhs) noexcept; + + ~Node () noexcept = default; + + /*-------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*-------------------------------------------------------------------------------*/ + + Node& operator=(const Node& rhs) noexcept; + Node& operator=(Node&& rhs) noexcept; + + /*-------------------------------------------------------------------------------*/ + /* Data Members */ + /*-------------------------------------------------------------------------------*/ + + SHBox AABB; + SHCollisionShapeID id; // Used to lookup the collision shape & entity for culling against itself + + union + { + int32_t parent; + int32_t next; + }; + + + int32_t left; + int32_t right; + int32_t height; // Leaves have a height of 0. Free nodes have a height of -1 + }; + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + static constexpr float AABB_EXTENSION = 0.2f; + + // For quick access + std::unordered_map nodeMap; + + int32_t root; + Node* nodes; // Dynamically allocated array of nodes. I use dynamic allocation as in the past, using a vector causes weird issues. + int32_t nodeCount; + int32_t capacity; // Used for resizing the tree. + int32_t freeList; // Stores the next available node on the free list. + + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + bool isLeaf (int32_t index) const noexcept; + + int32_t allocateNode (); + void freeNode (int32_t index) noexcept; + + void insertLeaf (int32_t index); + void removeLeaf (int32_t index); + void syncHierarchy (int32_t index); + int32_t balance (int32_t index); + int32_t rotateLeft (int32_t index); + int32_t rotateRight (int32_t index); + + void addToFreeList (int32_t index) noexcept; + }; + + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h index 3c08e21a..dad59881 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h @@ -17,6 +17,7 @@ #include "Physics/Collision/CollisionTags/SHCollisionTags.h" #include "Physics/Collision/SHPhysicsMaterial.h" #include "SHCollisionShapeID.h" +#include "Math/Geometry/SHBox.h" #include "Math/Transform/SHTransform.h" namespace SHADE @@ -33,8 +34,10 @@ namespace SHADE /* Friends */ /*---------------------------------------------------------------------------------*/ + friend class SHCollider; friend class SHColliderComponent; friend class SHCollisionShapeFactory; + friend class SHPhysicsWorld; public: /*---------------------------------------------------------------------------------*/ @@ -120,6 +123,7 @@ namespace SHADE virtual void ComputeTransforms () noexcept = 0; [[nodiscard]] virtual SHMatrix GetInertiaTensor (float mass) const noexcept = 0; [[nodiscard]] virtual SHMatrix ComputeWorldTransform () const noexcept = 0; + [[nodiscard]] virtual SHBox ComputeAABB () const noexcept = 0; protected: /*---------------------------------------------------------------------------------*/ @@ -136,7 +140,7 @@ namespace SHADE // Needed for conversion to euler angles SHVec3 rotationOffset; - uint8_t flags; // 0 0 wasColliding isColliding trigger capsule sphere box + uint8_t flags; // 0 0 0 isColliding trigger capsule sphere box SHCollisionTag* collisionTag; RTTR_ENABLE() diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeID.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeID.h index a0f78177..09f712d1 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeID.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeID.h @@ -10,6 +10,7 @@ #pragma once +// Project Headers #include "ECS_Base/Entity/SHEntity.h" namespace SHADE @@ -55,11 +56,14 @@ namespace SHADE SHCollisionShapeID& operator=(const SHCollisionShapeID& rhs) noexcept; SHCollisionShapeID& operator=(SHCollisionShapeID&& rhs) noexcept; + bool operator==(const SHCollisionShapeID& rhs) const; + /*---------------------------------------------------------------------------------*/ - /* Member Functions */ + /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - bool operator==(const SHCollisionShapeID& rhs) const; + [[nodiscard]] EntityID GetEntityID () const noexcept; + [[nodiscard]] uint32_t GetShapeIndex () const noexcept; private: /*---------------------------------------------------------------------------------*/ @@ -69,8 +73,8 @@ namespace SHADE struct IDs { public: - EntityID entityID = MAX_EID; - uint32_t shapeID = std::numeric_limits::max(); + EntityID entityID = MAX_EID; + uint32_t shapeIndex = std::numeric_limits::max(); }; @@ -84,7 +88,7 @@ namespace SHADE /** * @brief - * Encapsulates a functor to hash a CollisionShapeKey + * Encapsulates a functor to hash a CollisionShapeID */ struct SHCollisionShapeIDHash { diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeID.hpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeID.hpp index 234b2f6f..bb9ccb1f 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeID.hpp +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeID.hpp @@ -25,11 +25,11 @@ namespace SHADE {} inline SHCollisionShapeID::SHCollisionShapeID(const SHCollisionShapeID& rhs) noexcept - : ids { rhs.ids.entityID, rhs.ids.shapeID } + : ids { rhs.ids.entityID, rhs.ids.shapeIndex } {} inline SHCollisionShapeID::SHCollisionShapeID(SHCollisionShapeID&& rhs) noexcept - : ids { rhs.ids.entityID, rhs.ids.shapeID } + : ids { rhs.ids.entityID, rhs.ids.shapeIndex } {} @@ -61,6 +61,20 @@ namespace SHADE inline std::size_t SHCollisionShapeIDHash::operator()(const SHCollisionShapeID& id) const { - return std::hash()(id.value); + return std::hash{}(id.value); + } + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + inline EntityID SHCollisionShapeID::GetEntityID() const noexcept + { + return ids.entityID; + } + + inline uint32_t SHCollisionShapeID::GetShapeIndex() const noexcept + { + return ids.shapeIndex; } } diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp index e4bf68c3..a871a5eb 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp @@ -220,7 +220,12 @@ namespace SHADE Center , ROTATION , SCALE -); + ); + } + + SHBox SHSphereCollisionShape::ComputeAABB() const noexcept + { + return SHBox{ Center, SHVec3{ Radius } }; } diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h index 798c70b6..1325fc56 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h @@ -46,6 +46,7 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ friend class SHCollider; + friend class SHCollision; friend class SHCompositeCollider; friend class SHCollisionShapeFactory; @@ -127,6 +128,8 @@ namespace SHADE [[nodiscard]] SHMatrix ComputeWorldTransform () const noexcept override; + [[nodiscard]] SHBox ComputeAABB () const noexcept override; + private: /*---------------------------------------------------------------------------------*/ /* Data Members */ diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionEvents.h b/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionEvents.h new file mode 100644 index 00000000..7abb397b --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionEvents.h @@ -0,0 +1,60 @@ +/**************************************************************************************** + * \file SHCollisionID.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for Collision Information for Collision & Triggers. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "SHCollisionID.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + enum class SHCollisionState + { + ENTER + , STAY + , EXIT + + , TOTAL + , INVALID = -1 + }; + + /** + * @brief + * Encapsulates the event for an intersection between two collision shapes that do not + * have physical resolution. + */ + struct SH_API SHTriggerEvent + { + public: + SHCollisionID info; + SHCollisionState state; + }; + + /** + * @brief + * Encapsulates the event for an intersection between two collision shapes that do + * have physical resolution. + */ + struct SH_API SHCollisionEvent + { + public: + static constexpr int MAX_NUM_CONTACTS = 4; + + SHCollisionID info; + SHCollisionState state; + SHVec3 normal; + SHVec3 contactPoints[MAX_NUM_CONTACTS]; + }; + +} // namespace SHADE diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionID.cpp b/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionID.cpp new file mode 100644 index 00000000..fae14aca --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionID.cpp @@ -0,0 +1,152 @@ +/**************************************************************************************** + * \file SHCollisionInfo.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for Collision Info. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHCollisionID.h" + +// Project Headers +#include "Physics/Collision/SHCollider.h" +#include "Physics/Interface/SHColliderComponent.h" + + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHCollisionID::SHCollisionID() noexcept + { + ids[ENTITY_A] = MAX_EID; + ids[ENTITY_B] = MAX_EID; + ids[SHAPE_INDEX_A] = std::numeric_limits::max(); + ids[SHAPE_INDEX_B] = std::numeric_limits::max(); + } + + SHCollisionID::SHCollisionID(const SHCollisionID& rhs) noexcept + { + value[0] = rhs.value[0]; + value[1] = rhs.value[1]; + } + + SHCollisionID::SHCollisionID(SHCollisionID&& rhs) noexcept + { + value[0] = rhs.value[0]; + value[1] = rhs.value[1]; + } + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHCollisionID& SHCollisionID::operator=(const SHCollisionID& rhs) noexcept + { + if (this == &rhs) + return *this; + + value[0] = rhs.value[0]; + value[1] = rhs.value[1]; + + return *this; + } + + SHCollisionID& SHCollisionID::operator=(SHCollisionID&& rhs) noexcept + { + value[0] = rhs.value[0]; + value[1] = rhs.value[1]; + + return *this; + } + + bool SHCollisionID::operator==(const SHCollisionID& rhs) const + { + // When checking for equal, check both ways. + // Exact Match (A, idxA, B, idxB) + const bool EXACT_MATCH = value[0] == rhs.value[0] && value[1] == rhs.value[1]; + + // Flipped Match: (B, idxB, A, idxA) + const bool FLIPPED_MATCH = value[0] == rhs.value[1] && value[1] == rhs.value[0]; + + return EXACT_MATCH || FLIPPED_MATCH; + } + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + EntityID SHCollisionID::GetEntityA() const noexcept + { + return ids[ENTITY_A]; + } + + EntityID SHCollisionID::GetEntityB() const noexcept + { + return ids[ENTITY_B]; + } + + uint32_t SHCollisionID::GetShapeIndexA() const noexcept + { + return ids[SHAPE_INDEX_A]; + } + + uint32_t SHCollisionID::GetShapeIndexB() const noexcept + { + return ids[SHAPE_INDEX_B]; + } + + const SHRigidBodyComponent* SHCollisionID::GetRigidBodyA() const noexcept + { + return SHComponentManager::GetComponent_s(ids[ENTITY_A]); + } + + const SHRigidBodyComponent* SHCollisionID::GetRigidBodyB() const noexcept + { + return SHComponentManager::GetComponent_s(ids[ENTITY_B]); + } + + const SHCollisionShape* SHCollisionID::GetCollisionShapeA() const noexcept + { + const auto* COLLIDER_COMPONENT = SHComponentManager::GetComponent(ids[ENTITY_A]); + return COLLIDER_COMPONENT->GetCollider()->GetCollisionShape(ids[SHAPE_INDEX_A]); + } + + const SHCollisionShape* SHCollisionID::GetCollisionShapeB() const noexcept + { + const auto* COLLIDER_COMPONENT = SHComponentManager::GetComponent(ids[ENTITY_B]); + return COLLIDER_COMPONENT->GetCollider()->GetCollisionShape(ids[SHAPE_INDEX_B]); + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHCollisionID::SetEntityA(EntityID entityID) noexcept + { + ids[ENTITY_A] = entityID; + } + + void SHCollisionID::SetEntityB(EntityID entityID) noexcept + { + ids[ENTITY_B] = entityID; + } + + void SHCollisionID::SetCollisionShapeA(uint32_t shapeIndexA) noexcept + { + ids[SHAPE_INDEX_A] = shapeIndexA; + } + + void SHCollisionID::SetCollisionShapeB(uint32_t shapeIndexB) noexcept + { + ids[SHAPE_INDEX_B] = shapeIndexB; + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionID.h b/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionID.h new file mode 100644 index 00000000..78b28a96 --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionID.h @@ -0,0 +1,121 @@ +/**************************************************************************************** + * \file SHCollisionID.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for Collision Information for Collision & Triggers. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "Physics/Interface/SHColliderComponent.h" +#include "Physics/Interface/SHRigidBodyComponent.h" + + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + + struct SHCollisionIDHash; + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + /** + * @brief + * Encapsulates the information when two colliders intersect and do not have physical + * resolution. + */ + class SH_API SHCollisionID + { + private: + /*---------------------------------------------------------------------------------*/ + /* Friends */ + /*---------------------------------------------------------------------------------*/ + + friend struct SHCollisionIDHash; + + public: + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHCollisionID () noexcept; + SHCollisionID (const SHCollisionID& rhs) noexcept; + SHCollisionID (SHCollisionID&& rhs) noexcept; + + ~SHCollisionID () noexcept = default; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHCollisionID& operator= (const SHCollisionID& rhs) noexcept; + SHCollisionID& operator= (SHCollisionID&& rhs) noexcept; + + bool operator==(const SHCollisionID& rhs) const; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] EntityID GetEntityA () const noexcept; + [[nodiscard]] EntityID GetEntityB () const noexcept; + [[nodiscard]] uint32_t GetShapeIndexA () const noexcept; + [[nodiscard]] uint32_t GetShapeIndexB () const noexcept; + [[nodiscard]] const SHRigidBodyComponent* GetRigidBodyA () const noexcept; + [[nodiscard]] const SHRigidBodyComponent* GetRigidBodyB () const noexcept; + [[nodiscard]] const SHCollisionShape* GetCollisionShapeA () const noexcept; + [[nodiscard]] const SHCollisionShape* GetCollisionShapeB () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + + void SetEntityA (EntityID entityID) noexcept; + void SetEntityB (EntityID entityID) noexcept; + void SetCollisionShapeA (uint32_t shapeIndexA) noexcept; + void SetCollisionShapeB (uint32_t shapeIndexB) noexcept; + + private: + + static constexpr uint32_t ENTITY_A = 0; + static constexpr uint32_t SHAPE_INDEX_A = 1; + static constexpr uint32_t ENTITY_B = 2; + static constexpr uint32_t SHAPE_INDEX_B = 3; + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + union + { + uint64_t value[2]; // EntityValue, ShapeIndexValue + uint32_t ids [4]; // EntityA, EntityB, ShapeIndexA, ShapeIndexB + }; + }; + + /** + * @brief + * Encapsulates a functor to hash a CollisionID + */ + struct SHCollisionIDHash + { + public: + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + inline std::size_t operator()(const SHCollisionID& id) const + { + return std::hash{}(std::u32string_view(reinterpret_cast::const_pointer>(id.ids), 4)); + } + }; + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHContact.h b/SHADE_Engine/src/Physics/Collision/Contacts/SHContact.h new file mode 100644 index 00000000..c70f6e39 --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHContact.h @@ -0,0 +1,70 @@ +/**************************************************************************************** + * \file SHManifold.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Collision Manifold + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Primary Header +#include "Physics/Dynamics/SHRigidBody.h" +#include "Physics/Collision/CollisionShapes/SHCollisionShape.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + /** + * @brief + * Encapsulates a value that represents the touching features of a contact. + */ + union SHContactFeatures + { + public: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + struct + { + uint8_t incomingIncident; + uint8_t outgoingIncident; + uint8_t incomingReference; + uint8_t outgoingReference; + }; + + uint32_t key = 0; + }; + + /** + * @brief + * Encapsulates a physical collision contact. + */ + struct SH_API SHContact + { + public: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + static constexpr int NUM_TANGENTS = 2; + + float penetration = 0.0f; + float bias = 0.0f; + float normalImpulse = 0.0f; + float normalMass = 0.0f; + float tangentImpulse[NUM_TANGENTS] = { 0.0f }; + float tangentMass[NUM_TANGENTS] = { 0.0f }; + + SHVec3 position; + SHContactFeatures featurePair; + }; +} + +#pragma once diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h b/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h new file mode 100644 index 00000000..da6fe233 --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h @@ -0,0 +1,46 @@ +/**************************************************************************************** + * \file SHManifold.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Collision Manifold + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Primary Header +#include "Physics/Dynamics/SHRigidBody.h" +#include "Physics/Collision/CollisionShapes/SHCollisionShape.h" +#include "SHContact.h" +#include "SHCollisionEvents.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + struct SH_API SHManifold + { + public: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + // We only need 4 contact points to build a stable manifold. + static constexpr int MAX_NUM_CONTACTS = 4; + + SHCollisionShape* A = nullptr; + SHCollisionShape* B = nullptr; + + SHVec3 normal; + SHVec3 tangents[SHContact::NUM_TANGENTS]; + + SHContact contacts[MAX_NUM_CONTACTS]; + uint32_t numContacts = 0; + SHCollisionState state; + }; + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollision.h b/SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollision.h new file mode 100644 index 00000000..befb16f6 --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollision.h @@ -0,0 +1,52 @@ +/**************************************************************************************** + * \file SHCollision.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for the Detecting Collisions between two shapes + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "Physics/Collision/CollisionShapes/SHCollisionShape.h" +#include "Physics/Collision/Contacts/SHManifold.h" +#include "Physics/Collision/Contacts/SHCollisionID.h" + + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + /** + * @brief + * Encapsulates static methods for testing for collision between two shapes. + */ + class SH_API SHCollision + { + public: + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + /* Spheres VS X */ + + [[nodiscard]] static bool SphereVsSphere (const SHCollisionShape& A, const SHCollisionShape& B) noexcept; + [[nodiscard]] static bool SphereVsSphere (SHManifold& manifold, const SHCollisionShape& A, const SHCollisionShape& B) noexcept; + + + /* Capsule VS X */ + + /* Polygon VS X */ + + private: + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + }; +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollisionDispatch.cpp b/SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollisionDispatch.cpp new file mode 100644 index 00000000..c06e30ae --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollisionDispatch.cpp @@ -0,0 +1,95 @@ +/**************************************************************************************** + * \file SHCollisionDispatch.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for the static Collision Dispatcher + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHCollisionDispatch.h" + +// Project Header +#include "SHCollision.h" +#include "Tools/Utilities/SHUtilities.h" + + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Static Data Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + const SHCollisionDispatcher::ManifoldCollide SHCollisionDispatcher::manifoldCollide[NUM_SHAPES][NUM_SHAPES] + { + // TODO + // vs Sphere / Box / Capsule + + { SHCollision::SphereVsSphere, nullptr, nullptr } // Sphere + , { nullptr, nullptr, nullptr } // Box + , { nullptr, nullptr, nullptr } // Capsule + }; + + const SHCollisionDispatcher::TriggerCollide SHCollisionDispatcher::triggerCollide[NUM_SHAPES][NUM_SHAPES] + { + // TODO + // vs Sphere / Box / Capsule + + { SHCollision::SphereVsSphere, nullptr, nullptr } // Sphere + , { nullptr, nullptr, nullptr } // Box + , { nullptr, nullptr, nullptr } // Capsule + }; + + const bool SHCollisionDispatcher::collisionTable[NUM_TYPES][NUM_TYPES] + { + /* S ST K KT D DT */ + /* S */ { false, false, false, true, true, true } + , /* ST */ { false, false, true, true, true, true } + , /* K */ { false, true, false, true, true, true } + , /* KT */ { true, true, true, true, true, true } + , /* D */ { true, true, true, true, true, true } + , /* DT */ { true, true, true, true, true, true } + }; + + /*-----------------------------------------------------------------------------------*/ + /* Public Member Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHCollisionDispatcher::ShouldCollide(const SHCollisionShape& A, const SHCollisionShape& B) noexcept + { + // Filter through collision table + const int TYPE_A = SHUtilities::ConvertEnum(A.GetType()) + A.IsTrigger() ? TYPE_OFFSET : 0; + const int TYPE_B = SHUtilities::ConvertEnum(B.GetType()) + B.IsTrigger() ? TYPE_OFFSET : 0; + + if (!collisionTable[TYPE_A][TYPE_B]) + return false; + + // Filter through tags + const uint16_t TAG_A = A.GetCollisionTag().GetMask(); + const uint16_t TAG_B = B.GetCollisionTag().GetMask(); + + const uint16_t MATCH = TAG_A & TAG_B; + return MATCH > 0; + } + + bool SHCollisionDispatcher::Collide(SHManifold& manifold, const SHCollisionShape& A, const SHCollisionShape& B) noexcept + { + const int TYPE_A = SHUtilities::ConvertEnum(A.GetType()); + const int TYPE_B = SHUtilities::ConvertEnum(B.GetType()); + + return manifoldCollide[TYPE_A][TYPE_B](manifold, A, B); + } + + bool SHCollisionDispatcher::Collide(const SHCollisionShape& A, const SHCollisionShape& B) noexcept + { + const int TYPE_A = SHUtilities::ConvertEnum(A.GetType()); + const int TYPE_B = SHUtilities::ConvertEnum(B.GetType()); + + return triggerCollide[TYPE_A][TYPE_B](A, B); + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollisionDispatch.h b/SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollisionDispatch.h new file mode 100644 index 00000000..4c132746 --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollisionDispatch.h @@ -0,0 +1,87 @@ +/**************************************************************************************** + * \file SHCollisionDispatch.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for the static Collision Dispatcher + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "Physics/Collision/Contacts/SHManifold.h" +#include "Physics/Collision/Contacts/SHCollisionID.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + /** + * @brief + * Encapsulates static methods for running narrow-phase collision detection. + */ + class SH_API SHCollisionDispatcher + { + public: + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + /** + * @brief + * Filters the collision through the collision table and layer matching. + * @param A + * A Collision Shape. + * @param B + * A Collision Shape. + * @return + * True if both shapes should be tested for collision. + */ + static bool ShouldCollide (const SHCollisionShape& A, const SHCollisionShape& B) noexcept; + + static bool Collide (SHManifold& manifold, const SHCollisionShape& A, const SHCollisionShape& B) noexcept; + static bool Collide (const SHCollisionShape& A, const SHCollisionShape& B) noexcept; + + private: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + using ManifoldCollide = bool(*)(SHManifold&, const SHCollisionShape& A, const SHCollisionShape& B); + using TriggerCollide = bool(*)(const SHCollisionShape& A, const SHCollisionShape& B); + + enum class Types + { + STATIC + , KINEMATIC + , DYNAMIC + , STATIC_TRIGGER + , KINEMATIC_TRIGGER + , DYNAMIC_TRIGGER + + , COUNT + }; + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + // Read the Types enum class, then see where it's used and it'll make sense + static constexpr int TYPE_OFFSET = 3; + + static constexpr int NUM_SHAPES = static_cast(SHCollisionShape::Type::COUNT); + static constexpr int NUM_TYPES = static_cast(Types::COUNT); + + static const ManifoldCollide manifoldCollide [NUM_SHAPES][NUM_SHAPES]; + static const TriggerCollide triggerCollide [NUM_SHAPES][NUM_SHAPES]; + + static const bool collisionTable [NUM_TYPES][NUM_TYPES]; + + }; + + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/Narrowphase/SHSphereVsSphere.cpp b/SHADE_Engine/src/Physics/Collision/Narrowphase/SHSphereVsSphere.cpp new file mode 100644 index 00000000..b26a33dd --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/Narrowphase/SHSphereVsSphere.cpp @@ -0,0 +1,78 @@ +/**************************************************************************************** + * \file SHSphereVsSphere.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for the Detecting Collisions between two spheres + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHCollision.h" + +// Project Headers +#include "Math/SHMathHelpers.h" +#include "Physics/Collision/CollisionShapes/SHSphereCollisionShape.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Public Member Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHCollision::SphereVsSphere(const SHCollisionShape& A, const SHCollisionShape& B) noexcept + { + const SHSphereCollisionShape& SPHERE_A = dynamic_cast(A); + const SHSphereCollisionShape& SPHERE_B = dynamic_cast(B); + + return SHSphere::Intersect(SPHERE_A, SPHERE_B); + } + + bool SHCollision::SphereVsSphere(SHManifold& manifold, const SHCollisionShape& A, const SHCollisionShape& B) noexcept + { + // Convert to spheres + const SHSphereCollisionShape& SPHERE_A = dynamic_cast(A); + const SHSphereCollisionShape& SPHERE_B = dynamic_cast(B); + + const SHVec3 A_TO_B = SPHERE_B.GetCenter() - SPHERE_A.GetCenter(); + const float DISTANCE_BETWEEN_CENTERS_SQUARED = A_TO_B.LengthSquared(); + + const float COMBINED_RADIUS = (SPHERE_A.GetWorldRadius() + SPHERE_B.GetWorldRadius()); + const float COMBINED_RADIUS_SQUARED = COMBINED_RADIUS * COMBINED_RADIUS; + + if (DISTANCE_BETWEEN_CENTERS_SQUARED > COMBINED_RADIUS_SQUARED) + return false; + + // Only populate the manifold if there is a collision + + uint32_t numContacts = 0; + + SHContact contact; + contact.featurePair.key = 0; + + if (SHMath::CompareFloat(DISTANCE_BETWEEN_CENTERS_SQUARED, 0.0f)) + { + manifold.normal = SHVec3::UnitY; + contact.position = SPHERE_A.GetCenter(); + contact.penetration = SPHERE_B.GetWorldRadius(); + + manifold.contacts[numContacts++] = contact; + } + else + { + manifold.normal = SHVec3::Normalise(A_TO_B); + contact.position = SPHERE_B.GetCenter() - (manifold.normal * SPHERE_B.GetWorldRadius()); + contact.penetration = COMBINED_RADIUS - A_TO_B.Length(); + + manifold.contacts[numContacts++] = contact; + } + + manifold.numContacts = numContacts; + + return true; + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp index a6527e48..24ba26b7 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp @@ -14,6 +14,7 @@ #include "SHCollider.h" // Project Headers +#include "Broadphase/SHDynamicAABBTree.h" #include "Events/SHEvent.h" #include "Math/SHMathHelpers.h" #include "Physics/SHPhysicsEvents.h" @@ -28,19 +29,21 @@ namespace SHADE SHCollider::SHCollider(EntityID eid, const SHTransform& worldTransform) noexcept : entityID { eid } - , shapeIDCounter { 0 } , debugDraw { false } + , hasMoved { true } , rigidBody { nullptr } , shapeFactory { nullptr } + , broadphase { nullptr } , transform { worldTransform } {} SHCollider::SHCollider(const SHCollider& rhs) noexcept : entityID { rhs.entityID } - , shapeIDCounter { rhs.shapeIDCounter } , debugDraw { rhs.debugDraw } + , hasMoved { rhs.hasMoved } , rigidBody { rhs.rigidBody } , shapeFactory { rhs.shapeFactory } + , broadphase { rhs.broadphase } , transform { rhs.transform } { if (!shapeFactory) @@ -54,10 +57,11 @@ namespace SHADE SHCollider::SHCollider(SHCollider&& rhs) noexcept : entityID { rhs.entityID } - , shapeIDCounter { rhs.shapeIDCounter } , debugDraw { rhs.debugDraw } + , hasMoved { rhs.hasMoved } , rigidBody { rhs.rigidBody } , shapeFactory { rhs.shapeFactory } + , broadphase { rhs.broadphase } , transform { rhs.transform } { if (!shapeFactory) @@ -98,8 +102,10 @@ namespace SHADE entityID = rhs.entityID; debugDraw = rhs.debugDraw; + hasMoved = rhs.hasMoved; rigidBody = rhs.rigidBody; shapeFactory = rhs.shapeFactory; + broadphase = rhs.broadphase; transform = rhs.transform; copyShapes(rhs); @@ -117,8 +123,10 @@ namespace SHADE entityID = rhs.entityID; debugDraw = rhs.debugDraw; + hasMoved = rhs.hasMoved; rigidBody = rhs.rigidBody; shapeFactory = rhs.shapeFactory; + broadphase = rhs.broadphase; transform = rhs.transform; copyShapes(rhs); @@ -199,21 +207,25 @@ namespace SHADE void SHCollider::SetTransform(const SHTransform& newTransform) noexcept { + hasMoved = true; transform = newTransform; } void SHCollider::SetPosition(const SHVec3& newPosition) noexcept { + hasMoved = true; transform.position = newPosition; } void SHCollider::SetOrientation(const SHQuaternion& newOrientation) noexcept { + hasMoved = true; transform.orientation = newOrientation; } void SHCollider::SetScale(const SHVec3& newScale) noexcept { + hasMoved = true; transform.scale = newScale; } @@ -255,10 +267,10 @@ namespace SHADE , .Scale = SPHERE_SCALE }; - const SHCollisionShapeID NEW_SHAPE_ID{ entityID, shapeIDCounter }; + const uint32_t NEW_INDEX = static_cast(shapes.size()); + const SHCollisionShapeID NEW_SHAPE_ID{ entityID, NEW_INDEX }; SHSphereCollisionShape* sphere = shapeFactory->CreateSphere(NEW_SHAPE_ID, SPHERE_CREATE_INFO); - ++shapeIDCounter; // Set offsets sphere->SetParentTransform(transform); @@ -267,12 +279,15 @@ namespace SHADE shapes.emplace_back(sphere); + if (broadphase) + broadphase->Insert(NEW_SHAPE_ID, sphere->ComputeAABB()); + // Broadcast Event for adding a shape const SHPhysicsColliderAddedEvent EVENT_DATA { .entityID = entityID , .colliderType = SHCollisionShape::Type::SPHERE - , .colliderIndex = static_cast(shapes.size()) + , .colliderIndex = static_cast(NEW_INDEX) }; SHEventManager::BroadcastEvent(EVENT_DATA, SH_PHYSICS_COLLIDER_ADDED_EVENT); @@ -280,7 +295,7 @@ namespace SHADE if (rigidBody) rigidBody->ComputeMassData(); - return static_cast(shapes.size()); + return static_cast(NEW_INDEX); } void SHCollider::RemoveCollisionShape(int index) @@ -296,8 +311,8 @@ namespace SHADE if (index < 0 || index >= NUM_SHAPES) throw std::invalid_argument("Out-of-range index!"); - auto shapeIter = shapes.begin(); - for (int i = 0; i < NUM_SHAPES; ++i, ++shapeIter) + auto shape = shapes.begin(); + for (int i = 0; i < NUM_SHAPES; ++i, ++shape) { if (i == index) break; @@ -306,15 +321,21 @@ namespace SHADE const SHPhysicsColliderRemovedEvent EVENT_DATA { .entityID = entityID - , .colliderType = (*shapeIter)->GetType() + , .colliderType = (*shape)->GetType() , .colliderIndex = index }; - shapeFactory->DestroyShape(*shapeIter); - *shapeIter = nullptr; + // Remove from broadphase + if (broadphase) + broadphase->Remove((*shape)->id); + + shapeFactory->DestroyShape(*shape); + *shape = nullptr; // Remove the shape from the container to prevent accessing a nullptr - shapeIter = shapes.erase(shapeIter); + shape = shapes.erase(shape); + + // Broadcast Event for removing a shape SHEventManager::BroadcastEvent(EVENT_DATA, SH_PHYSICS_COLLIDER_REMOVED_EVENT); @@ -337,12 +358,8 @@ namespace SHADE void SHCollider::copyShapes(const SHCollider& rhsCollider) { - shapeIDCounter = 0; for (const auto* shape : rhsCollider.shapes) - { copyShape(shape); - ++shapeIDCounter; - } } void SHCollider::copyShape(const SHCollisionShape* rhsShape) @@ -365,7 +382,8 @@ namespace SHADE , .Scale = RHS_SPHERE->scale }; - const SHCollisionShapeID NEW_SHAPE_ID{ entityID, shapeIDCounter }; + const uint32_t NEW_INDEX = static_cast(shapes.size()); + const SHCollisionShapeID NEW_SHAPE_ID{ entityID, NEW_INDEX }; SHSphereCollisionShape* sphere = shapeFactory->CreateSphere(NEW_SHAPE_ID, SPHERE_CREATE_INFO); *sphere = *RHS_SPHERE; @@ -380,8 +398,6 @@ namespace SHADE } default: break; } - - ++shapeIDCounter; } } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.h b/SHADE_Engine/src/Physics/Collision/SHCollider.h index 566bae3e..89ba5ff2 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.h +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.h @@ -22,6 +22,7 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ class SHRigidBody; + class SHAABBTree; /*-----------------------------------------------------------------------------------*/ /* Type Definitions */ @@ -155,12 +156,13 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ EntityID entityID; - uint32_t shapeIDCounter; // This increments everytime a shape is added to differentiate shapes. bool debugDraw; + bool hasMoved; SHRigidBody* rigidBody; SHCollisionShapeFactory* shapeFactory; + SHAABBTree* broadphase; SHTransform transform; diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp index b1b94792..4ff94b7d 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp @@ -14,6 +14,8 @@ #include "SHPhysicsWorld.h" // Project Headers +#include "Physics/Collision/Narrowphase/SHCollision.h" +#include "Physics/Collision/Narrowphase/SHCollisionDispatch.h" namespace SHADE @@ -25,13 +27,52 @@ namespace SHADE SHPhysicsWorld::SHPhysicsWorld(const WorldSettings& worldSettings) noexcept : settings { worldSettings } { - rigidBodies.clear(); SHLOG_INFO_D("Creating Physics World") } SHPhysicsWorld::~SHPhysicsWorld() noexcept { - rigidBodies.clear(); + + } + + /*-----------------------------------------------------------------------------------*/ + /* Getter Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + const SHPhysicsWorld::TriggerEvents& SHPhysicsWorld::GetTriggerEvents() const noexcept + { + static TriggerEvents triggerEvents; + + triggerEvents.clear(); + + for (auto& [id, state] : triggers) + triggerEvents.emplace_back(SHTriggerEvent{ id, state }); + + return triggerEvents; + } + + const SHPhysicsWorld::CollisionEvents& SHPhysicsWorld::GetCollisionEvents() const noexcept + { + static CollisionEvents collisionEvents; + + collisionEvents.clear(); + + for (auto& [id, manifold] : manifolds) + { + SHCollisionEvent collisionEvent + { + .info = id + , .state = manifold.state + , .normal = manifold.normal + }; + + for (uint32_t i = 0; i < manifold.numContacts; ++i) + collisionEvent.contactPoints[i] = manifold.contacts[i].position; + + collisionEvents.emplace_back(collisionEvent); + } + + return collisionEvents; } /*-----------------------------------------------------------------------------------*/ @@ -40,7 +81,7 @@ namespace SHADE void SHPhysicsWorld::AddRigidBody(SHRigidBody* rigidBody) noexcept { - const bool INSERTED = rigidBodies.emplace(rigidBody).second; + const bool INSERTED = rigidBodies.emplace(rigidBody->entityID, rigidBody).second; if (!INSERTED) { SHLOG_WARNING_D("Attempting to add duplicate rigid body {} to the Physics World!", rigidBody->entityID) @@ -49,37 +90,89 @@ namespace SHADE void SHPhysicsWorld::RemoveRigidBody(SHRigidBody* rigidBody) noexcept { - rigidBodies.erase(rigidBody); + rigidBodies.erase(rigidBody->entityID); - // Run through the rigid body's contact graph and wake all of its non-static bodies that are asleep + // Attempt to remove any invalidated manifolds + if (manifolds.empty()) + return; + + for (auto manifoldPair = manifolds.begin(); manifoldPair != manifolds.end();) + { + const auto& ID = manifoldPair->first; + + const bool MATCHES_A = ID.GetEntityA() == rigidBody->entityID; + const bool MATCHES_B = ID.GetEntityB() == rigidBody->entityID; + + if (MATCHES_A || MATCHES_B) + manifoldPair = manifolds.erase(manifoldPair); + else + ++manifoldPair; + } + + // TODO: Run through the rigid body's contact graph and wake all of its non-static bodies that are asleep } void SHPhysicsWorld::AddCollider(SHCollider* collider) noexcept { - const bool INSERTED = colliders.emplace(collider).second; + const bool INSERTED = colliders.emplace(collider->entityID, collider).second; if (!INSERTED) { SHLOG_WARNING_D("Attempting to add duplicate collider {} to the Physics World!", collider->entityID) + return; } + + collider->broadphase = &broadphase; + + // Add all existing shapes to the broadphase + for (const auto* shape : collider->shapes) + broadphase.Insert(shape->id, shape->ComputeAABB()); } void SHPhysicsWorld::RemoveCollider(SHCollider* collider) noexcept { - colliders.erase(collider); + colliders.erase(collider->entityID); - // Get collider's rigid body - // Run through the rigid body's contact graph and wake all of its non-static bodies that are asleep + const uint32_t NUM_SHAPES = static_cast(collider->shapes.size()); + if (NUM_SHAPES == 0) + return; + + for (uint32_t i = 0; i < NUM_SHAPES; ++i) + { + const SHCollisionShape* SHAPE = collider->shapes[i]; + + broadphase.Remove(SHAPE->id); + + if (SHAPE->IsTrigger()) + removeInvalidatedTrigger(collider->entityID, i); + else + removeInvalidatedManifold(collider->entityID, i); + } + + /* + * TODO: + * Get collider's rigid body + * Run through the rigid body's contact graph and wake all of its non-static bodies that are asleep + */ + + } void SHPhysicsWorld::Step(float dt) { - for (auto* rigidBody : rigidBodies) + // Clear containers of exit state collisions + updateEvents(); + + // TODO: Profile each of these + runBroadphase (); + runNarrowphase(); + + for (auto* rigidBody : rigidBodies | std::views::values) { rigidBody->ComputeWorldData(); integrateForces(*rigidBody, dt); } - for (auto* rigidBody : rigidBodies) + for (auto* rigidBody : rigidBodies | std::views::values) integrateVelocities(*rigidBody, dt); } @@ -158,4 +251,262 @@ namespace SHADE // We clear forces for static bodies as well for redundancy rigidBody.ClearForces(); } + + void SHPhysicsWorld::runBroadphase() noexcept + { + // Update any colliders that have moved + for (auto& collider : colliders | std::views::values) + { + if (!collider->hasMoved) + continue; + + // Clear hasMoved flag here + collider->hasMoved = false; + + // Update moved shapes in broadphase + for (auto* shape : collider->shapes) + broadphase.Update(shape->id, shape->ComputeAABB()); + } + + // Query: Kinematic Triggers, Awake Dynamic Bodies & Dynamic Triggers + for (auto& collider : colliders | std::views::values) + { + // Default static bodies + if (!collider->rigidBody) + continue; + + // Explicit static bodies + const bool IS_STATIC = collider->rigidBody->GetType() == SHRigidBody::Type::STATIC; + if (IS_STATIC) + continue; + + // All remaining are kinematic or dynamic + // Iterate through shapes: if kinematic / dynamic trigger, else if dynamic & awake + if (collider->rigidBody->GetType() == SHRigidBody::Type::KINEMATIC) + queryKinematic(collider); + + if (collider->rigidBody->GetType() == SHRigidBody::Type::DYNAMIC) + queryDynamic(collider); + } + + } + + void SHPhysicsWorld::queryKinematic(SHCollider* collider) noexcept + { + for (auto* shape : collider->shapes) + { + // For kinematic shapes, we only query triggers against everything else + if (!shape->IsTrigger()) + continue; + + auto& potentialCollisions = broadphase.Query(shape->id, shape->ComputeAABB()); + + // Build narrow-phase pairs + auto* shapeA = shape; + + const EntityID ID_A = shape->id.GetEntityID(); + const uint32_t INDEX_A = shape->id.GetShapeIndex(); + + for (auto& id : potentialCollisions) + { + // Get corresponding shape + const EntityID ID_B = id.GetEntityID(); + const uint32_t INDEX_B = id.GetShapeIndex(); + + auto* shapeB = colliders[ID_B]->GetCollisionShape(INDEX_B); + + // Build collision ID + SHCollisionID collisionKey; + collisionKey.SetEntityA(ID_A); + collisionKey.SetEntityB(ID_B); + collisionKey.SetCollisionShapeA(INDEX_A); + collisionKey.SetCollisionShapeB(INDEX_B); + + // Check if it already exists. If it doesn't, put into batch. + auto narrowphasePair = narrowphaseBatch.find(collisionKey); + if (narrowphasePair == narrowphaseBatch.end()) + narrowphaseBatch.emplace(collisionKey, NarrowphasePair{ shapeA, shapeB }); + } + } + } + + void SHPhysicsWorld::queryDynamic(SHCollider* collider) noexcept + { + for (auto* shape : collider->shapes) + { + auto& potentialCollisions = broadphase.Query(shape->id, shape->ComputeAABB()); + + // Build narrow-phase pairs + auto* shapeA = shape; + + const EntityID ID_A = shape->id.GetEntityID(); + const uint32_t INDEX_A = shape->id.GetShapeIndex(); + + for (auto& id : potentialCollisions) + { + // Get corresponding shape + const EntityID ID_B = id.GetEntityID(); + const uint32_t INDEX_B = id.GetShapeIndex(); + + auto* shapeB = colliders[ID_B]->GetCollisionShape(INDEX_B); + + // Build collision ID + SHCollisionID collisionKey; + collisionKey.SetEntityA(ID_A); + collisionKey.SetEntityB(ID_B); + collisionKey.SetCollisionShapeA(INDEX_A); + collisionKey.SetCollisionShapeB(INDEX_B); + + // Check if it already exists. If it doesn't, put into batch. + auto narrowphasePair = narrowphaseBatch.find(collisionKey); + if (narrowphasePair == narrowphaseBatch.end()) + narrowphaseBatch.emplace(collisionKey, NarrowphasePair{ shapeA, shapeB }); + } + } + } + + void SHPhysicsWorld::runNarrowphase() noexcept + { + for (auto& [id, narrowphasePair] : narrowphaseBatch) + { + const bool IS_A_TRIGGER = narrowphasePair.A->IsTrigger(); + const bool IS_B_TRIGGER = narrowphasePair.B->IsTrigger(); + + // Check if ID exists in trigger + if (IS_A_TRIGGER || IS_B_TRIGGER) + collideTriggers(id, narrowphasePair); + else + collideManifolds(id, narrowphasePair); + } + } + + void SHPhysicsWorld::collideTriggers(const SHCollisionID& id, NarrowphasePair& narrowphasePair) noexcept + { + const auto* A = narrowphasePair.A; + const auto* B = narrowphasePair.B; + + const bool COLLIDING = SHCollisionDispatcher::Collide(*A, *B); + + auto trigger = triggers.find(id); + + // If id not found, emplace new object. + // New object is in the invalid state + if (trigger == triggers.end()) + trigger = triggers.emplace(id, SHCollisionState::INVALID).first; + + SHCollisionState& state = trigger->second; + updateCollisionState(COLLIDING, state); + + // If it was a false positive, remove the manifold immediately. + // Remove using iterator as it is on average faster. + if (state == SHCollisionState::INVALID) + trigger = triggers.erase(trigger); + } + + void SHPhysicsWorld::collideManifolds(const SHCollisionID& id, NarrowphasePair& narrowphasePair) noexcept + { + auto* A = narrowphasePair.A; + auto* B = narrowphasePair.B; + + SHManifold newManifold { .A = A, .B = B }; + const bool COLLIDING = SHCollisionDispatcher::Collide(newManifold, *A, *B); + + auto manifold = manifolds.find(id); + + // If id not found, emplace new manifold + if (manifold == manifolds.end()) + manifold = manifolds.emplace(id, newManifold).first; + else + { + // TODO: Update existing manifolds with new data + } + + SHCollisionState& state = manifold->second.state; + updateCollisionState(COLLIDING, state); + + // If it was a false positive, remove the manifold immediately. + // Remove using iterator as it is on average faster. + if (state == SHCollisionState::INVALID) + manifold = manifolds.erase(manifold); + } + + void SHPhysicsWorld::updateCollisionState(bool isColliding, SHCollisionState& state) noexcept + { + if (isColliding) + state = state == SHCollisionState::INVALID ? SHCollisionState::ENTER : SHCollisionState::STAY; + else + state = SHCollisionState::EXIT; + } + + void SHPhysicsWorld::updateEvents() noexcept + { + // Clear expired or invalid collisions + for (auto manifold = manifolds.begin(); manifold != manifolds.end();) + { + const auto COLLISION_STATE = manifold->second.state; + + const bool IS_EXIT = COLLISION_STATE == SHCollisionState::EXIT; + const bool IS_INVALID = COLLISION_STATE == SHCollisionState::INVALID; + + if (IS_EXIT || IS_INVALID) + manifold = manifolds.erase(manifold); + else + ++manifold; + } + + // Clear expired or invalid triggers + for (auto trigger = triggers.begin(); trigger != triggers.end();) + { + const auto COLLISION_STATE = trigger->second; + + const bool IS_EXIT = COLLISION_STATE == SHCollisionState::EXIT; + const bool IS_INVALID = COLLISION_STATE == SHCollisionState::INVALID; + + if (IS_EXIT || IS_INVALID) + trigger = triggers.erase(trigger); + else + ++trigger; + } + } + + void SHPhysicsWorld::removeInvalidatedTrigger(EntityID eid, uint32_t shapeIndex) + { + if (triggers.empty()) + return; + + for (auto invalidatedTrigger = triggers.begin(); invalidatedTrigger != triggers.end();) + { + const auto& ID = invalidatedTrigger->first; + + const bool MATCHES_A = ID.GetEntityA() == eid && ID.GetShapeIndexA() == shapeIndex; + const bool MATCHES_B = ID.GetEntityB() == eid && ID.GetShapeIndexB() == shapeIndex; + + if (MATCHES_A || MATCHES_B) + invalidatedTrigger = triggers.erase(invalidatedTrigger); + else + ++invalidatedTrigger; + } + } + + void SHPhysicsWorld::removeInvalidatedManifold(EntityID eid, uint32_t shapeIndex) + { + if (manifolds.empty()) + return; + + for (auto invalidatedManifold = manifolds.begin(); invalidatedManifold != manifolds.end();) + { + const auto& ID = invalidatedManifold->first; + + const bool MATCHES_A = ID.GetEntityA() == eid && ID.GetShapeIndexA() == shapeIndex; + const bool MATCHES_B = ID.GetEntityB() == eid && ID.GetShapeIndexB() == shapeIndex; + + if (MATCHES_A || MATCHES_B) + invalidatedManifold = manifolds.erase(invalidatedManifold); + else + ++invalidatedManifold; + } + } + + + } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h index 499230dd..f0d2cd5b 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h @@ -10,9 +10,12 @@ #pragma once -#include +#include // Project Headers +#include "Physics/Collision/Broadphase/SHDynamicAABBTree.h" +#include "Physics/Collision/Contacts/SHCollisionEvents.h" +#include "Physics/Collision/Contacts/SHManifold.h" #include "Physics/Collision/SHCollider.h" #include "SHRigidBody.h" @@ -44,6 +47,9 @@ namespace SHADE bool sleepingEnabled = true; }; + using TriggerEvents = std::vector; + using CollisionEvents = std::vector; + /*---------------------------------------------------------------------------------*/ /* Constructors & Destructor */ /*---------------------------------------------------------------------------------*/ @@ -54,7 +60,6 @@ namespace SHADE SHPhysicsWorld (const SHPhysicsWorld&) = delete; SHPhysicsWorld (SHPhysicsWorld&&) = delete; - /*---------------------------------------------------------------------------------*/ /* Operator Overloads */ /*---------------------------------------------------------------------------------*/ @@ -63,7 +68,14 @@ namespace SHADE SHPhysicsWorld& operator=(SHPhysicsWorld&&) = delete; /*---------------------------------------------------------------------------------*/ - /* Function Members */ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + const TriggerEvents& GetTriggerEvents () const noexcept; + const CollisionEvents& GetCollisionEvents () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ /*---------------------------------------------------------------------------------*/ void AddRigidBody (SHRigidBody* rigidBody) noexcept; @@ -79,27 +91,67 @@ namespace SHADE /* Type Definitions */ /*---------------------------------------------------------------------------------*/ - //! I realise using a set may be dangerous with pointers. An unordered_map may be better, but I don't need the entityIDs so... + struct NarrowphasePair + { + SHCollisionShape* A = nullptr; + SHCollisionShape* B = nullptr; + }; + + // EntityIDs are used to map resolved contraints back to bodies + using RigidBodies = std::unordered_map; + using Colliders = std::unordered_map; + + // Collisions + + using NarrowphaseBatch = std::unordered_map; + using Manifolds = std::unordered_map; + using Triggers = std::unordered_map; - using Colliders = std::unordered_set; - using RigidBodies = std::unordered_set; /*---------------------------------------------------------------------------------*/ /* Data Members */ /*---------------------------------------------------------------------------------*/ - WorldSettings settings; + WorldSettings settings; - Colliders colliders; - RigidBodies rigidBodies; + // Containers + + RigidBodies rigidBodies; + Colliders colliders; + + NarrowphaseBatch narrowphaseBatch; + Manifolds manifolds; + Triggers triggers; + + // World components + + SHAABBTree broadphase; /*---------------------------------------------------------------------------------*/ /* Function Members */ /*---------------------------------------------------------------------------------*/ - void integrateForces (SHRigidBody& rigidBody, float dt) const noexcept; - void integrateVelocities (SHRigidBody& rigidBody, float dt) const noexcept; + // TODO: Move to island when islands are set up + void integrateForces (SHRigidBody& rigidBody, float dt) const noexcept; + void integrateVelocities (SHRigidBody& rigidBody, float dt) const noexcept; + + // Broadphase helpers + + void runBroadphase () noexcept; + void queryKinematic (SHCollider* collider) noexcept; + void queryDynamic (SHCollider* collider) noexcept; + + // Narrowphase helpers + + void runNarrowphase () noexcept; + void collideTriggers (const SHCollisionID& id, NarrowphasePair& narrowphasePair) noexcept; + void collideManifolds (const SHCollisionID& id, NarrowphasePair& narrowphasePair) noexcept; + void updateCollisionState (bool isColliding, SHCollisionState& state) noexcept; + + void updateEvents () noexcept; + void removeInvalidatedTrigger (EntityID eid, uint32_t shapeIndex); + void removeInvalidatedManifold (EntityID eid, uint32_t shapeIndex); }; From 751a16dcc3a64a9f22eae66f11a66d858c766c1a Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Tue, 20 Dec 2022 02:13:06 +0800 Subject: [PATCH 061/275] Tested collision detection with collision states --- Assets/Scenes/PhysicsSandbox.shade | 10 ++--- .../src/Application/SBApplication.cpp | 7 ++++ SHADE_Engine/src/Math/Geometry/SHBox.cpp | 2 +- .../Broadphase/SHDynamicAABBTree.cpp | 2 +- .../Collision/Broadphase/SHDynamicAABBTree.h | 10 ++++- .../SHSphereCollisionShape.cpp | 2 +- .../CollisionShapes/SHSphereCollisionShape.h | 6 +-- .../Physics/Collision/Contacts/SHManifold.h | 7 ++-- .../Narrowphase/SHSphereVsSphere.cpp | 10 ++--- .../src/Physics/Dynamics/SHPhysicsWorld.cpp | 30 ++++++++++++-- .../src/Physics/Dynamics/SHPhysicsWorld.h | 14 ++++++- .../Routines/SHPhysicsDebugDrawRoutine.cpp | 17 +++++++- .../Routines/SHPhysicsPreUpdateRoutine.cpp | 2 + .../src/Physics/System/SHPhysicsSystem.cpp | 40 +++++++++++++------ .../src/Physics/System/SHPhysicsSystem.h | 13 +++--- 15 files changed, 125 insertions(+), 47 deletions(-) diff --git a/Assets/Scenes/PhysicsSandbox.shade b/Assets/Scenes/PhysicsSandbox.shade index 8817b62e..21840aca 100644 --- a/Assets/Scenes/PhysicsSandbox.shade +++ b/Assets/Scenes/PhysicsSandbox.shade @@ -14,7 +14,7 @@ Mass: 10 Drag: 1 Angular Drag: 1 - Use Gravity: false + Use Gravity: true Gravity Scale: 1 Interpolate: true Sleeping Enabled: true @@ -48,7 +48,7 @@ NumberOfChildren: 0 Components: Camera Component: - Position: {x: 0, y: 2, z: 5} + Position: {x: 0, y: 2, z: 3} Pitch: 0 Yaw: 0 Roll: 0 @@ -70,12 +70,12 @@ Scale: {x: 1, y: 1, z: 1} IsActive: true RigidBody Component: - Type: Static + Type: Dynamic Auto Mass: false - Mass: .inf + Mass: 1 Drag: 0.00999999978 Angular Drag: 0.00999999978 - Use Gravity: true + Use Gravity: false Gravity Scale: 1 Interpolate: true Sleeping Enabled: true diff --git a/SHADE_Application/src/Application/SBApplication.cpp b/SHADE_Application/src/Application/SBApplication.cpp index aa5a4f0e..8607702d 100644 --- a/SHADE_Application/src/Application/SBApplication.cpp +++ b/SHADE_Application/src/Application/SBApplication.cpp @@ -185,6 +185,13 @@ namespace Sandbox #endif SHSceneManager::SceneUpdate(0.016f); #ifdef SHEDITOR + static bool drawBP = false; + if (SHInputManager::GetKeyDown(SHInputManager::SH_KEYCODE::RIGHT_CTRL)) + { + drawBP = !drawBP; + SHSystemManager::GetSystem()->SetFlagState(SHPhysicsDebugDrawSystem::DebugDrawFlags::BROADPHASE, drawBP); + } + SHSystemManager::RunRoutines(editor->editorState != SHEditor::State::PLAY, SHFrameRateController::GetRawDeltaTime()); editor->PollPicking(); #else diff --git a/SHADE_Engine/src/Math/Geometry/SHBox.cpp b/SHADE_Engine/src/Math/Geometry/SHBox.cpp index 7261749b..05595fe1 100644 --- a/SHADE_Engine/src/Math/Geometry/SHBox.cpp +++ b/SHADE_Engine/src/Math/Geometry/SHBox.cpp @@ -199,7 +199,7 @@ namespace SHADE bool SHBox::Contains(const SHBox& rhs) const noexcept { - return BoundingBox::Contains(rhs); + return BoundingBox::Contains(rhs) == CONTAINS; } float SHBox::Volume() const noexcept diff --git a/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.cpp b/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.cpp index db30222b..540d5375 100644 --- a/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.cpp +++ b/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.cpp @@ -107,7 +107,7 @@ namespace SHADE const std::vector& SHAABBTree::GetAABBs() const noexcept { - static std::vector aabbs; + static AABBs aabbs; static std::stack nodeIndices; aabbs.clear(); diff --git a/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.h b/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.h index 973631a1..f23fd946 100644 --- a/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.h +++ b/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.h @@ -28,6 +28,12 @@ namespace SHADE class SH_API SHAABBTree { public: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + using AABBs = std::vector; + /*---------------------------------------------------------------------------------*/ /* Data Members */ /*---------------------------------------------------------------------------------*/ @@ -55,7 +61,7 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] const std::vector& GetAABBs () const noexcept; + [[nodiscard]] const AABBs& GetAABBs () const noexcept; /*---------------------------------------------------------------------------------*/ /* Member Functions */ @@ -116,7 +122,7 @@ namespace SHADE /* Data Members */ /*---------------------------------------------------------------------------------*/ - static constexpr float AABB_EXTENSION = 0.2f; + static constexpr float AABB_EXTENSION = 0.1f; // For quick access std::unordered_map nodeMap; diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp index a871a5eb..b1110489 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp @@ -125,7 +125,7 @@ namespace SHADE /* Getter Function Definitions */ /*-----------------------------------------------------------------------------------*/ - const SHVec3& SHSphereCollisionShape::GetCenter() const noexcept + SHVec3 SHSphereCollisionShape::GetCenter() const noexcept { return Center; } diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h index 1325fc56..eb41f93c 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h @@ -72,9 +72,9 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] const SHVec3& GetCenter () const noexcept; - [[nodiscard]] float GetWorldRadius () const noexcept; - [[nodiscard]] float GetRelativeRadius () const noexcept; + [[nodiscard]] SHVec3 GetCenter () const noexcept; + [[nodiscard]] float GetWorldRadius () const noexcept; + [[nodiscard]] float GetRelativeRadius () const noexcept; /*---------------------------------------------------------------------------------*/ /* Setter Functions */ diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h b/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h index da6fe233..0534f749 100644 --- a/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h @@ -35,12 +35,13 @@ namespace SHADE SHCollisionShape* A = nullptr; SHCollisionShape* B = nullptr; + uint32_t numContacts = 0; + SHCollisionState state = SHCollisionState::INVALID; + SHVec3 normal; SHVec3 tangents[SHContact::NUM_TANGENTS]; - SHContact contacts[MAX_NUM_CONTACTS]; - uint32_t numContacts = 0; - SHCollisionState state; + }; } \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/Narrowphase/SHSphereVsSphere.cpp b/SHADE_Engine/src/Physics/Collision/Narrowphase/SHSphereVsSphere.cpp index b26a33dd..8b529241 100644 --- a/SHADE_Engine/src/Physics/Collision/Narrowphase/SHSphereVsSphere.cpp +++ b/SHADE_Engine/src/Physics/Collision/Narrowphase/SHSphereVsSphere.cpp @@ -25,8 +25,8 @@ namespace SHADE bool SHCollision::SphereVsSphere(const SHCollisionShape& A, const SHCollisionShape& B) noexcept { - const SHSphereCollisionShape& SPHERE_A = dynamic_cast(A); - const SHSphereCollisionShape& SPHERE_B = dynamic_cast(B); + const SHSphereCollisionShape& SPHERE_A = reinterpret_cast(A); + const SHSphereCollisionShape& SPHERE_B = reinterpret_cast(B); return SHSphere::Intersect(SPHERE_A, SPHERE_B); } @@ -34,10 +34,10 @@ namespace SHADE bool SHCollision::SphereVsSphere(SHManifold& manifold, const SHCollisionShape& A, const SHCollisionShape& B) noexcept { // Convert to spheres - const SHSphereCollisionShape& SPHERE_A = dynamic_cast(A); - const SHSphereCollisionShape& SPHERE_B = dynamic_cast(B); + const SHSphereCollisionShape& SPHERE_A = reinterpret_cast(A); + const SHSphereCollisionShape& SPHERE_B = reinterpret_cast(B); - const SHVec3 A_TO_B = SPHERE_B.GetCenter() - SPHERE_A.GetCenter(); + const SHVec3 A_TO_B = SPHERE_B.GetCenter() - SPHERE_A.GetCenter(); const float DISTANCE_BETWEEN_CENTERS_SQUARED = A_TO_B.LengthSquared(); const float COMBINED_RADIUS = (SPHERE_A.GetWorldRadius() + SPHERE_B.GetWorldRadius()); diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp index 4ff94b7d..ff362d69 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp @@ -39,6 +39,11 @@ namespace SHADE /* Getter Functions Definitions */ /*-----------------------------------------------------------------------------------*/ + const SHAABBTree::AABBs& SHPhysicsWorld::GetBroadphaseAABBs() const noexcept + { + return broadphase.GetAABBs(); + } + const SHPhysicsWorld::TriggerEvents& SHPhysicsWorld::GetTriggerEvents() const noexcept { static TriggerEvents triggerEvents; @@ -153,8 +158,13 @@ namespace SHADE * Get collider's rigid body * Run through the rigid body's contact graph and wake all of its non-static bodies that are asleep */ + } - + void SHPhysicsWorld::UpdateBroadphase(SHCollider* collider) noexcept + { + auto& shapes = collider->GetCollisionShapes(); + for (auto* shape : shapes) + broadphase.Update(shape->id, shape->ComputeAABB()); } void SHPhysicsWorld::Step(float dt) @@ -367,6 +377,9 @@ namespace SHADE void SHPhysicsWorld::runNarrowphase() noexcept { + if (narrowphaseBatch.empty()) + return; + for (auto& [id, narrowphasePair] : narrowphaseBatch) { const bool IS_A_TRIGGER = narrowphasePair.A->IsTrigger(); @@ -378,6 +391,9 @@ namespace SHADE else collideManifolds(id, narrowphasePair); } + + // Clear every frame + narrowphaseBatch.clear(); } void SHPhysicsWorld::collideTriggers(const SHCollisionID& id, NarrowphasePair& narrowphasePair) noexcept @@ -432,10 +448,18 @@ namespace SHADE void SHPhysicsWorld::updateCollisionState(bool isColliding, SHCollisionState& state) noexcept { - if (isColliding) + if (isColliding) + { + // New states start at invalid. In the first frame of collision, move to enter. + // If it already in enter, move to stay state = state == SHCollisionState::INVALID ? SHCollisionState::ENTER : SHCollisionState::STAY; + } else - state = SHCollisionState::EXIT; + { + // New states start at invalid. In false positive, remain unchanged. + // If previously colliding, move to exit. + state = state == SHCollisionState::INVALID ? SHCollisionState::INVALID : SHCollisionState::EXIT; + } } void SHPhysicsWorld::updateEvents() noexcept diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h index f0d2cd5b..e0e4f796 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h @@ -71,8 +71,10 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - const TriggerEvents& GetTriggerEvents () const noexcept; - const CollisionEvents& GetCollisionEvents () const noexcept; + const SHAABBTree::AABBs& GetBroadphaseAABBs () const noexcept; + + const TriggerEvents& GetTriggerEvents () const noexcept; + const CollisionEvents& GetCollisionEvents () const noexcept; /*---------------------------------------------------------------------------------*/ /* Member Functions */ @@ -84,6 +86,14 @@ namespace SHADE void AddCollider (SHCollider* collider) noexcept; void RemoveCollider (SHCollider* collider) noexcept; + /** + * @brief + * Invoke this method to update the broadphase of a collider while the simulation + * is not running. + * @param collider + * The collider to update. + */ + void UpdateBroadphase (SHCollider* collider) noexcept; void Step (float dt); private: diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp index 72b2aad5..b43b5b77 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp @@ -18,6 +18,7 @@ #include "Graphics/MiddleEnd/Interface/SHDebugDrawSystem.h" #include "Math/Transform/SHTransformComponent.h" #include "Physics/System/SHPhysicsSystem.h" +#include "Tools/Utilities/SHUtilities.h" namespace SHADE { @@ -40,7 +41,6 @@ namespace SHADE if (!physicsDebugDrawSystem->GetFlagState(DebugDrawFlags::ACTIVE)) return; - auto* physicsSystem = SHSystemManager::GetSystem(); auto* debugDrawSystem = SHSystemManager::GetSystem(); const bool DRAW_COLLIDERS = physicsDebugDrawSystem->GetFlagState(DebugDrawFlags::COLLIDERS); @@ -68,6 +68,10 @@ namespace SHADE } } + auto* physicsSystem = SHSystemManager::GetSystem(); + if (!physicsSystem) + return; + if (DRAW_CONTACTS) { // TODO @@ -81,7 +85,16 @@ namespace SHADE if (DRAW_BROADPHASE) { - // TODO + const SHColour& AABB_COLOUR = physicsDebugDrawSystem->DEBUG_DRAW_COLOURS[SHUtilities::ConvertEnum(Colours::BROADPHASE)]; + + auto& broadphaseAABBs = physicsSystem->GetWorld()->GetBroadphaseAABBs(); + + for (auto& aabb : broadphaseAABBs) + { + // Compute AABB Transform + const SHMatrix TRS = SHMatrix::Transform(aabb.GetCenter(), SHQuaternion::Identity, aabb.GetWorldExtents() * 2.0f); + debugDrawSystem->DrawWireCube(TRS, AABB_COLOUR); + } } } diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp index 88ea8d79..178a6120 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp @@ -65,6 +65,8 @@ namespace SHADE physicsObject.collider->SetScale(WORLD_SCL); physicsObject.collider->RecomputeShapes(); + + physicsSystem->physicsWorld->UpdateBroadphase(physicsObject.collider); } } } diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp index 20084ebe..975c561b 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp @@ -41,9 +41,9 @@ namespace SHADE eventFunctions[2] = { &SHPhysicsSystem::onSceneInit , SH_SCENE_INIT_POST }; eventFunctions[3] = { &SHPhysicsSystem::onSceneExit , SH_SCENE_EXIT_POST }; - #ifdef SHEDITOR - eventFunctions[4] = { &SHPhysicsSystem::onEditorPlay , SH_EDITOR_ON_PLAY_EVENT }; - #endif + //#ifdef SHEDITOR + // eventFunctions[4] = { &SHPhysicsSystem::onEditorPlay , SH_EDITOR_ON_PLAY_EVENT }; + //#endif } SHPhysicsSystem::~SHPhysicsSystem() noexcept @@ -55,6 +55,11 @@ namespace SHADE /* Getter Function Definitions */ /*-----------------------------------------------------------------------------------*/ + const SHPhysicsWorld* SHPhysicsSystem::GetWorld() const noexcept + { + return physicsWorld; + } + double SHPhysicsSystem::GetFixedUpdateRate() const noexcept { return 1.0 / fixedDT; @@ -173,15 +178,6 @@ namespace SHADE // Create the physics world physicsWorld = new SHPhysicsWorld; - #ifdef SHEDITOR - - // Link all entities with the world if editor is already playing. - // This is for handling scene changes while the editor is active. - - const auto* EDITOR = SHSystemManager::GetSystem(); - if (!EDITOR || EDITOR->editorState != SHEditor::State::PLAY) - return onSceneInitEvent.get()->handle; - for (const auto& PHYSICS_OBJECT : physicsObjectManager.GetPhysicsObjects() | std::views::values) { if (PHYSICS_OBJECT.rigidBody) @@ -191,7 +187,25 @@ namespace SHADE physicsWorld->AddCollider(PHYSICS_OBJECT.collider); } - #endif + //#ifdef SHEDITOR + + // // Link all entities with the world if editor is already playing. + // // This is for handling scene changes while the editor is active. + // + // const auto* EDITOR = SHSystemManager::GetSystem(); + // if (!EDITOR || EDITOR->editorState != SHEditor::State::PLAY) + // return onSceneInitEvent.get()->handle; + + // for (const auto& PHYSICS_OBJECT : physicsObjectManager.GetPhysicsObjects() | std::views::values) + // { + // if (PHYSICS_OBJECT.rigidBody) + // physicsWorld->AddRigidBody(PHYSICS_OBJECT.rigidBody); + + // if (PHYSICS_OBJECT.collider) + // physicsWorld->AddCollider(PHYSICS_OBJECT.collider); + // } + + //#endif return onSceneInitEvent.get()->handle; } diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h index ce1f1396..f9866ebe 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h @@ -45,8 +45,9 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] double GetFixedUpdateRate() const noexcept; - [[nodiscard]] double GetFixedDT() const noexcept; + [[nodiscard]] const SHPhysicsWorld* GetWorld () const noexcept; + [[nodiscard]] double GetFixedUpdateRate() const noexcept; + [[nodiscard]] double GetFixedDT () const noexcept; /*---------------------------------------------------------------------------------*/ /* Setter Functions */ @@ -123,11 +124,11 @@ namespace SHADE /* Data Members */ /*---------------------------------------------------------------------------------*/ - #ifdef SHEDITOR - static constexpr int NUM_EVENT_FUNCTIONS = 5; - #else + //#ifdef SHEDITOR + // static constexpr int NUM_EVENT_FUNCTIONS = 5; + //#else static constexpr int NUM_EVENT_FUNCTIONS = 4; - #endif + //#endif // Event function container for cleanly registering to events EventFunctionPair eventFunctions[NUM_EVENT_FUNCTIONS]; From 5def5392a1986250fbb0ddc6d90aa92646fbfad0 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Tue, 20 Dec 2022 02:26:31 +0800 Subject: [PATCH 062/275] Cleaned up CollisionKey object --- .../Collision/Contacts/SHCollisionEvents.h | 8 +-- .../{SHCollisionID.cpp => SHCollisionKey.cpp} | 52 ++++++++++++------- .../{SHCollisionID.h => SHCollisionKey.h} | 32 +++++------- .../Collision/Narrowphase/SHCollision.h | 2 +- .../Narrowphase/SHCollisionDispatch.h | 2 +- .../src/Physics/Dynamics/SHPhysicsWorld.cpp | 8 +-- .../src/Physics/Dynamics/SHPhysicsWorld.h | 10 ++-- .../src/Physics/System/SHPhysicsSystem.cpp | 49 ++--------------- .../src/Physics/System/SHPhysicsSystem.h | 13 ----- 9 files changed, 66 insertions(+), 110 deletions(-) rename SHADE_Engine/src/Physics/Collision/Contacts/{SHCollisionID.cpp => SHCollisionKey.cpp} (66%) rename SHADE_Engine/src/Physics/Collision/Contacts/{SHCollisionID.h => SHCollisionKey.h} (83%) diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionEvents.h b/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionEvents.h index 7abb397b..db55ca50 100644 --- a/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionEvents.h +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionEvents.h @@ -1,5 +1,5 @@ /**************************************************************************************** - * \file SHCollisionID.h + * \file SHCollisionKey.h * \author Diren D Bharwani, diren.dbharwani, 390002520 * \brief Interface for Collision Information for Collision & Triggers. * @@ -11,7 +11,7 @@ #pragma once // Project Headers -#include "SHCollisionID.h" +#include "SHCollisionKey.h" namespace SHADE { @@ -37,7 +37,7 @@ namespace SHADE struct SH_API SHTriggerEvent { public: - SHCollisionID info; + SHCollisionKey info; SHCollisionState state; }; @@ -51,7 +51,7 @@ namespace SHADE public: static constexpr int MAX_NUM_CONTACTS = 4; - SHCollisionID info; + SHCollisionKey info; SHCollisionState state; SHVec3 normal; SHVec3 contactPoints[MAX_NUM_CONTACTS]; diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionID.cpp b/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionKey.cpp similarity index 66% rename from SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionID.cpp rename to SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionKey.cpp index fae14aca..4bb22697 100644 --- a/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionID.cpp +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionKey.cpp @@ -11,7 +11,7 @@ #include // Primary Header -#include "SHCollisionID.h" +#include "SHCollisionKey.h" // Project Headers #include "Physics/Collision/SHCollider.h" @@ -24,7 +24,7 @@ namespace SHADE /* Constructors & Destructor Definitions */ /*-----------------------------------------------------------------------------------*/ - SHCollisionID::SHCollisionID() noexcept + SHCollisionKey::SHCollisionKey() noexcept { ids[ENTITY_A] = MAX_EID; ids[ENTITY_B] = MAX_EID; @@ -32,13 +32,13 @@ namespace SHADE ids[SHAPE_INDEX_B] = std::numeric_limits::max(); } - SHCollisionID::SHCollisionID(const SHCollisionID& rhs) noexcept + SHCollisionKey::SHCollisionKey(const SHCollisionKey& rhs) noexcept { value[0] = rhs.value[0]; value[1] = rhs.value[1]; } - SHCollisionID::SHCollisionID(SHCollisionID&& rhs) noexcept + SHCollisionKey::SHCollisionKey(SHCollisionKey&& rhs) noexcept { value[0] = rhs.value[0]; value[1] = rhs.value[1]; @@ -48,7 +48,7 @@ namespace SHADE /* Operator Overload Definitions */ /*-----------------------------------------------------------------------------------*/ - SHCollisionID& SHCollisionID::operator=(const SHCollisionID& rhs) noexcept + SHCollisionKey& SHCollisionKey::operator=(const SHCollisionKey& rhs) noexcept { if (this == &rhs) return *this; @@ -59,7 +59,7 @@ namespace SHADE return *this; } - SHCollisionID& SHCollisionID::operator=(SHCollisionID&& rhs) noexcept + SHCollisionKey& SHCollisionKey::operator=(SHCollisionKey&& rhs) noexcept { value[0] = rhs.value[0]; value[1] = rhs.value[1]; @@ -67,7 +67,7 @@ namespace SHADE return *this; } - bool SHCollisionID::operator==(const SHCollisionID& rhs) const + bool SHCollisionKey::operator==(const SHCollisionKey& rhs) const { // When checking for equal, check both ways. // Exact Match (A, idxA, B, idxB) @@ -83,43 +83,43 @@ namespace SHADE /* Getter Function Definitions */ /*-----------------------------------------------------------------------------------*/ - EntityID SHCollisionID::GetEntityA() const noexcept + EntityID SHCollisionKey::GetEntityA() const noexcept { return ids[ENTITY_A]; } - EntityID SHCollisionID::GetEntityB() const noexcept + EntityID SHCollisionKey::GetEntityB() const noexcept { return ids[ENTITY_B]; } - uint32_t SHCollisionID::GetShapeIndexA() const noexcept + uint32_t SHCollisionKey::GetShapeIndexA() const noexcept { return ids[SHAPE_INDEX_A]; } - uint32_t SHCollisionID::GetShapeIndexB() const noexcept + uint32_t SHCollisionKey::GetShapeIndexB() const noexcept { return ids[SHAPE_INDEX_B]; } - const SHRigidBodyComponent* SHCollisionID::GetRigidBodyA() const noexcept + const SHRigidBodyComponent* SHCollisionKey::GetRigidBodyA() const noexcept { return SHComponentManager::GetComponent_s(ids[ENTITY_A]); } - const SHRigidBodyComponent* SHCollisionID::GetRigidBodyB() const noexcept + const SHRigidBodyComponent* SHCollisionKey::GetRigidBodyB() const noexcept { return SHComponentManager::GetComponent_s(ids[ENTITY_B]); } - const SHCollisionShape* SHCollisionID::GetCollisionShapeA() const noexcept + const SHCollisionShape* SHCollisionKey::GetCollisionShapeA() const noexcept { const auto* COLLIDER_COMPONENT = SHComponentManager::GetComponent(ids[ENTITY_A]); return COLLIDER_COMPONENT->GetCollider()->GetCollisionShape(ids[SHAPE_INDEX_A]); } - const SHCollisionShape* SHCollisionID::GetCollisionShapeB() const noexcept + const SHCollisionShape* SHCollisionKey::GetCollisionShapeB() const noexcept { const auto* COLLIDER_COMPONENT = SHComponentManager::GetComponent(ids[ENTITY_B]); return COLLIDER_COMPONENT->GetCollider()->GetCollisionShape(ids[SHAPE_INDEX_B]); @@ -129,24 +129,38 @@ namespace SHADE /* Setter Function Definitions */ /*-----------------------------------------------------------------------------------*/ - void SHCollisionID::SetEntityA(EntityID entityID) noexcept + void SHCollisionKey::SetEntityA(EntityID entityID) noexcept { ids[ENTITY_A] = entityID; } - void SHCollisionID::SetEntityB(EntityID entityID) noexcept + void SHCollisionKey::SetEntityB(EntityID entityID) noexcept { ids[ENTITY_B] = entityID; } - void SHCollisionID::SetCollisionShapeA(uint32_t shapeIndexA) noexcept + void SHCollisionKey::SetCollisionShapeA(uint32_t shapeIndexA) noexcept { ids[SHAPE_INDEX_A] = shapeIndexA; } - void SHCollisionID::SetCollisionShapeB(uint32_t shapeIndexB) noexcept + void SHCollisionKey::SetCollisionShapeB(uint32_t shapeIndexB) noexcept { ids[SHAPE_INDEX_B] = shapeIndexB; } + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + std::size_t SHCollisionKeyHash::operator()(const SHCollisionKey& id) const + { + static constexpr int NUM_IDS = ARRAYSIZE(id.ids); + + // Hashable is not a word. Sue me. + auto hashablePtr = reinterpret_cast::const_pointer>(id.ids); + return std::hash{}(std::u32string_view(hashablePtr, NUM_IDS)); + } + + } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionID.h b/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionKey.h similarity index 83% rename from SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionID.h rename to SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionKey.h index 78b28a96..811f8375 100644 --- a/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionID.h +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionKey.h @@ -21,7 +21,7 @@ namespace SHADE /* Forward Declarations */ /*-----------------------------------------------------------------------------------*/ - struct SHCollisionIDHash; + struct SHCollisionKeyHash; /*-----------------------------------------------------------------------------------*/ /* Type Definitions */ @@ -29,37 +29,36 @@ namespace SHADE /** * @brief - * Encapsulates the information when two colliders intersect and do not have physical - * resolution. + * Encapsulates the information when two collision shapes intersect. */ - class SH_API SHCollisionID + class SH_API SHCollisionKey { private: /*---------------------------------------------------------------------------------*/ /* Friends */ /*---------------------------------------------------------------------------------*/ - friend struct SHCollisionIDHash; + friend struct SHCollisionKeyHash; public: /*---------------------------------------------------------------------------------*/ /* Constructors & Destructor */ /*---------------------------------------------------------------------------------*/ - SHCollisionID () noexcept; - SHCollisionID (const SHCollisionID& rhs) noexcept; - SHCollisionID (SHCollisionID&& rhs) noexcept; + SHCollisionKey () noexcept; + SHCollisionKey (const SHCollisionKey& rhs) noexcept; + SHCollisionKey (SHCollisionKey&& rhs) noexcept; - ~SHCollisionID () noexcept = default; + ~SHCollisionKey () noexcept = default; /*---------------------------------------------------------------------------------*/ /* Operator Overloads */ /*---------------------------------------------------------------------------------*/ - SHCollisionID& operator= (const SHCollisionID& rhs) noexcept; - SHCollisionID& operator= (SHCollisionID&& rhs) noexcept; + SHCollisionKey& operator= (const SHCollisionKey& rhs) noexcept; + SHCollisionKey& operator= (SHCollisionKey&& rhs) noexcept; - bool operator==(const SHCollisionID& rhs) const; + bool operator==(const SHCollisionKey& rhs) const; /*---------------------------------------------------------------------------------*/ /* Getter Functions */ @@ -103,19 +102,16 @@ namespace SHADE /** * @brief - * Encapsulates a functor to hash a CollisionID + * Encapsulates a functor to hash a CollisionKey */ - struct SHCollisionIDHash + struct SHCollisionKeyHash { public: /*---------------------------------------------------------------------------------*/ /* Member Functions */ /*---------------------------------------------------------------------------------*/ - inline std::size_t operator()(const SHCollisionID& id) const - { - return std::hash{}(std::u32string_view(reinterpret_cast::const_pointer>(id.ids), 4)); - } + std::size_t operator()(const SHCollisionKey& id) const; }; } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollision.h b/SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollision.h index befb16f6..a64c101c 100644 --- a/SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollision.h +++ b/SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollision.h @@ -13,7 +13,7 @@ // Project Headers #include "Physics/Collision/CollisionShapes/SHCollisionShape.h" #include "Physics/Collision/Contacts/SHManifold.h" -#include "Physics/Collision/Contacts/SHCollisionID.h" +#include "Physics/Collision/Contacts/SHCollisionKey.h" namespace SHADE diff --git a/SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollisionDispatch.h b/SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollisionDispatch.h index 4c132746..2dea7f1d 100644 --- a/SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollisionDispatch.h +++ b/SHADE_Engine/src/Physics/Collision/Narrowphase/SHCollisionDispatch.h @@ -12,7 +12,7 @@ // Project Headers #include "Physics/Collision/Contacts/SHManifold.h" -#include "Physics/Collision/Contacts/SHCollisionID.h" +#include "Physics/Collision/Contacts/SHCollisionKey.h" namespace SHADE { diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp index ff362d69..8d0b69f8 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp @@ -326,7 +326,7 @@ namespace SHADE auto* shapeB = colliders[ID_B]->GetCollisionShape(INDEX_B); // Build collision ID - SHCollisionID collisionKey; + SHCollisionKey collisionKey; collisionKey.SetEntityA(ID_A); collisionKey.SetEntityB(ID_B); collisionKey.SetCollisionShapeA(INDEX_A); @@ -361,7 +361,7 @@ namespace SHADE auto* shapeB = colliders[ID_B]->GetCollisionShape(INDEX_B); // Build collision ID - SHCollisionID collisionKey; + SHCollisionKey collisionKey; collisionKey.SetEntityA(ID_A); collisionKey.SetEntityB(ID_B); collisionKey.SetCollisionShapeA(INDEX_A); @@ -396,7 +396,7 @@ namespace SHADE narrowphaseBatch.clear(); } - void SHPhysicsWorld::collideTriggers(const SHCollisionID& id, NarrowphasePair& narrowphasePair) noexcept + void SHPhysicsWorld::collideTriggers(const SHCollisionKey& id, NarrowphasePair& narrowphasePair) noexcept { const auto* A = narrowphasePair.A; const auto* B = narrowphasePair.B; @@ -419,7 +419,7 @@ namespace SHADE trigger = triggers.erase(trigger); } - void SHPhysicsWorld::collideManifolds(const SHCollisionID& id, NarrowphasePair& narrowphasePair) noexcept + void SHPhysicsWorld::collideManifolds(const SHCollisionKey& id, NarrowphasePair& narrowphasePair) noexcept { auto* A = narrowphasePair.A; auto* B = narrowphasePair.B; diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h index e0e4f796..a0743a30 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h @@ -113,9 +113,9 @@ namespace SHADE // Collisions - using NarrowphaseBatch = std::unordered_map; - using Manifolds = std::unordered_map; - using Triggers = std::unordered_map; + using NarrowphaseBatch = std::unordered_map; + using Manifolds = std::unordered_map; + using Triggers = std::unordered_map; @@ -155,8 +155,8 @@ namespace SHADE // Narrowphase helpers void runNarrowphase () noexcept; - void collideTriggers (const SHCollisionID& id, NarrowphasePair& narrowphasePair) noexcept; - void collideManifolds (const SHCollisionID& id, NarrowphasePair& narrowphasePair) noexcept; + void collideTriggers (const SHCollisionKey& id, NarrowphasePair& narrowphasePair) noexcept; + void collideManifolds (const SHCollisionKey& id, NarrowphasePair& narrowphasePair) noexcept; void updateCollisionState (bool isColliding, SHCollisionState& state) noexcept; void updateEvents () noexcept; diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp index 975c561b..65bbc5d4 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp @@ -40,10 +40,6 @@ namespace SHADE eventFunctions[1] = { &SHPhysicsSystem::onComponentRemoved, SH_COMPONENT_REMOVED_EVENT }; eventFunctions[2] = { &SHPhysicsSystem::onSceneInit , SH_SCENE_INIT_POST }; eventFunctions[3] = { &SHPhysicsSystem::onSceneExit , SH_SCENE_EXIT_POST }; - - //#ifdef SHEDITOR - // eventFunctions[4] = { &SHPhysicsSystem::onEditorPlay , SH_EDITOR_ON_PLAY_EVENT }; - //#endif } SHPhysicsSystem::~SHPhysicsSystem() noexcept @@ -178,6 +174,10 @@ namespace SHADE // Create the physics world physicsWorld = new SHPhysicsWorld; + // Immediately add all existing bodies and colliders to the world. + // Since we recreated the scene and the world, the initial data has been reset and determinism is guaranteed. + // Only if the current scene data changes, then so would the results of the simulation. + for (const auto& PHYSICS_OBJECT : physicsObjectManager.GetPhysicsObjects() | std::views::values) { if (PHYSICS_OBJECT.rigidBody) @@ -187,26 +187,6 @@ namespace SHADE physicsWorld->AddCollider(PHYSICS_OBJECT.collider); } - //#ifdef SHEDITOR - - // // Link all entities with the world if editor is already playing. - // // This is for handling scene changes while the editor is active. - // - // const auto* EDITOR = SHSystemManager::GetSystem(); - // if (!EDITOR || EDITOR->editorState != SHEditor::State::PLAY) - // return onSceneInitEvent.get()->handle; - - // for (const auto& PHYSICS_OBJECT : physicsObjectManager.GetPhysicsObjects() | std::views::values) - // { - // if (PHYSICS_OBJECT.rigidBody) - // physicsWorld->AddRigidBody(PHYSICS_OBJECT.rigidBody); - - // if (PHYSICS_OBJECT.collider) - // physicsWorld->AddCollider(PHYSICS_OBJECT.collider); - // } - - //#endif - return onSceneInitEvent.get()->handle; } @@ -262,7 +242,6 @@ namespace SHADE physicsWorld->AddRigidBody(rigidBody); } } - if (IS_COLLIDER) { @@ -323,24 +302,4 @@ namespace SHADE return onComponentRemovedEvent.get()->handle; } -#ifdef SHEDITOR - - SHEventHandle SHPhysicsSystem::onEditorPlay(SHEventPtr onEditorPlayEvent) - { - // Add all physics components to the physics world - for (const auto& PHYSICS_OBJECT : physicsObjectManager.GetPhysicsObjects() | std::views::values) - { - // Add rigid body if it exists - if (PHYSICS_OBJECT.rigidBody) - physicsWorld->AddRigidBody(PHYSICS_OBJECT.rigidBody); - - if (PHYSICS_OBJECT.collider) - physicsWorld->AddCollider(PHYSICS_OBJECT.collider); - } - - return onEditorPlayEvent.get()->handle; - } - -#endif - } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h index f9866ebe..fca12498 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h @@ -124,11 +124,7 @@ namespace SHADE /* Data Members */ /*---------------------------------------------------------------------------------*/ - //#ifdef SHEDITOR - // static constexpr int NUM_EVENT_FUNCTIONS = 5; - //#else static constexpr int NUM_EVENT_FUNCTIONS = 4; - //#endif // Event function container for cleanly registering to events EventFunctionPair eventFunctions[NUM_EVENT_FUNCTIONS]; @@ -152,14 +148,5 @@ namespace SHADE SHEventHandle onComponentAdded (SHEventPtr onComponentAddedEvent); SHEventHandle onComponentRemoved (SHEventPtr onComponentRemovedEvent); - - - #ifdef SHEDITOR - - SHEventHandle onEditorPlay (SHEventPtr onEditorPlayEvent); - // We don't need an onEditorStop because on stop exits the scene, which is already handled above. - - #endif - }; } // namespace SHADE \ No newline at end of file From b402a44d95c99fbd0afd4289043dabe4dfc20677 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Tue, 20 Dec 2022 17:26:27 +0800 Subject: [PATCH 063/275] Added SHAnimationClip stub --- .../src/Animation/SHAnimationClip.cpp | 32 +++++++++ SHADE_Engine/src/Animation/SHAnimationClip.h | 71 +++++++++++++++++++ .../src/Animation/SHAnimatorComponent.cpp | 22 ++++++ .../src/Animation/SHAnimatorComponent.h | 5 ++ 4 files changed, 130 insertions(+) create mode 100644 SHADE_Engine/src/Animation/SHAnimationClip.cpp create mode 100644 SHADE_Engine/src/Animation/SHAnimationClip.h diff --git a/SHADE_Engine/src/Animation/SHAnimationClip.cpp b/SHADE_Engine/src/Animation/SHAnimationClip.cpp new file mode 100644 index 00000000..1f199b3f --- /dev/null +++ b/SHADE_Engine/src/Animation/SHAnimationClip.cpp @@ -0,0 +1,32 @@ +/************************************************************************************//*! +\file SHAnimationClip.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Nov 20, 2022 +\brief Contains the function definitions of the SHAnimationClip class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Pre-compiled Header +#include "SHpch.h" +// Primary Header +#include "SHAnimationClip.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------------*/ + + + /*-----------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*-----------------------------------------------------------------------------------*/ + + /*-----------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*-----------------------------------------------------------------------------------*/ + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Animation/SHAnimationClip.h b/SHADE_Engine/src/Animation/SHAnimationClip.h new file mode 100644 index 00000000..ed1b7b81 --- /dev/null +++ b/SHADE_Engine/src/Animation/SHAnimationClip.h @@ -0,0 +1,71 @@ +/************************************************************************************//*! +\file SHAnimationClip.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Dec 12, 2022 +\brief Contains the definition of the SHAnimationClip struct and related types. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +#pragma once + +// STL Includes + +// Project Includes +#include "Math/SHMatrix.h" +#include "Assets/Asset Types/Models/SHAnimationAsset.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + + + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + struct SHAnimationKeyFrame + { + float TimeStamp; + SHVec3 Position; + SHQuaternion Orientation; + SHVec3 Scale; + SHMatrix TransformationMatrix; + }; + + class SHAnimationClip + { + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + struct Channel + { + std::string Name; + std::vector KeyFrames; + }; + + /*---------------------------------------------------------------------------------*/ + /* Constructors */ + /*---------------------------------------------------------------------------------*/ + explicit SHAnimationClip(const SHAnimAsset& asset); + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + private: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + std::vector Channels; + + /*---------------------------------------------------------------------------------*/ + /* Helper Functions */ + /*---------------------------------------------------------------------------------*/ + + }; +} \ No newline at end of file diff --git a/SHADE_Engine/src/Animation/SHAnimatorComponent.cpp b/SHADE_Engine/src/Animation/SHAnimatorComponent.cpp index 61c0e6af..debb2fb2 100644 --- a/SHADE_Engine/src/Animation/SHAnimatorComponent.cpp +++ b/SHADE_Engine/src/Animation/SHAnimatorComponent.cpp @@ -19,6 +19,28 @@ of DigiPen Institute of Technology is prohibited. namespace SHADE { + /*-----------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*-----------------------------------------------------------------------------------*/ + void SHAnimatorComponent::SetRig(Handle newRig) + { + // Same rig, don't bother + if (rig == newRig) + return; + + rig = newRig; + + // Populate bone matrices based on new rig's default pose + if (rig) + { + + } + else + { + boneMatrices.clear(); + } + } + /*-----------------------------------------------------------------------------------*/ /* Update Functions */ /*-----------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Animation/SHAnimatorComponent.h b/SHADE_Engine/src/Animation/SHAnimatorComponent.h index 9e596130..446014cc 100644 --- a/SHADE_Engine/src/Animation/SHAnimatorComponent.h +++ b/SHADE_Engine/src/Animation/SHAnimatorComponent.h @@ -45,6 +45,11 @@ namespace SHADE void Pause(); void Stop();*/ + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + void SetRig(Handle newRig); + /*---------------------------------------------------------------------------------*/ /* Getter Functions */ /*---------------------------------------------------------------------------------*/ From 8212ed2280396a7f7383e96cd57a24a8c1f6eac4 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Tue, 20 Dec 2022 20:29:28 +0800 Subject: [PATCH 064/275] Application::Quit() no longer kills the application if in editor --- SHADE_Managed/src/Engine/Application.cxx | 9 ++++++++- SHADE_Managed/src/Engine/Application.hxx | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/SHADE_Managed/src/Engine/Application.cxx b/SHADE_Managed/src/Engine/Application.cxx index c19bafa6..06ad632f 100644 --- a/SHADE_Managed/src/Engine/Application.cxx +++ b/SHADE_Managed/src/Engine/Application.cxx @@ -44,6 +44,10 @@ namespace SHADE return false; } + bool Application::IsEditor::get() + { + return SHSystemManager::GetSystem() != nullptr; + } int Application::WindowWidth::get() { return SHGraphicsSystemInterface::GetWindowWidth(); @@ -66,6 +70,9 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ void Application::Quit() { - SHGraphicsSystemInterface::CloseWindow(); + if (!IsEditor) + { + SHGraphicsSystemInterface::CloseWindow(); + } } } diff --git a/SHADE_Managed/src/Engine/Application.hxx b/SHADE_Managed/src/Engine/Application.hxx index 8629cf75..4467ec3b 100644 --- a/SHADE_Managed/src/Engine/Application.hxx +++ b/SHADE_Managed/src/Engine/Application.hxx @@ -43,6 +43,13 @@ namespace SHADE bool get(); } /// + /// True if the engine is running in the editor. + /// + static property bool IsEditor + { + bool get(); + } + /// /// Retrieves the designated width of the current window. /// static property int WindowWidth @@ -71,6 +78,7 @@ namespace SHADE /*-----------------------------------------------------------------------------*/ /// /// Marks the application to stop at the end of the current frame. + /// If running in the editor, this function does nothing. /// static void Quit(); }; From 88e89a226a895c2ee0d62ac6db50e50301ac706f Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Tue, 20 Dec 2022 22:35:47 +0800 Subject: [PATCH 065/275] Added the option to open the script csproj via menu bar --- .../EditorWindow/MenuBar/SHEditorMenuBar.cpp | 5 ++ SHADE_Engine/src/Scripting/SHScriptEngine.cpp | 73 ++++++++++++------- SHADE_Engine/src/Scripting/SHScriptEngine.h | 5 ++ 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp b/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp index a1335e19..22d78173 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp +++ b/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp @@ -120,6 +120,11 @@ namespace SHADE auto* scriptEngine = static_cast(SHSystemManager::GetSystem()); scriptEngine->GenerateScriptsCsProjFile(); } + if (ImGui::Selectable("Open Visual Studio Project")) + { + auto* scriptEngine = static_cast(SHSystemManager::GetSystem()); + scriptEngine->OpenSolution(); + } ImGui::BeginDisabled(SHSystemManager::GetSystem()->editorState != SHEditor::State::STOP); if (ImGui::Selectable("Build Scripts - Debug")) { diff --git a/SHADE_Engine/src/Scripting/SHScriptEngine.cpp b/SHADE_Engine/src/Scripting/SHScriptEngine.cpp index 3746d1d0..76a57242 100644 --- a/SHADE_Engine/src/Scripting/SHScriptEngine.cpp +++ b/SHADE_Engine/src/Scripting/SHScriptEngine.cpp @@ -306,6 +306,22 @@ namespace SHADE file.close(); } + void SHScriptEngine::OpenSolution() + { + // Generate csproj file if it doesn't exist + if (!std::filesystem::exists(CSPROJ_PATH)) + { + GenerateScriptsCsProjFile(CSPROJ_PATH); + } + + // Open it + execProcessNoBlock + ( + L"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\Common7\\IDE\\devenv.exe", + L"/Edit " + SHStringUtilities::StrToWstr(CSPROJ_PATH) + ); + } + /*-----------------------------------------------------------------------------------*/ /* Event Handler Functions */ /*-----------------------------------------------------------------------------------*/ @@ -612,31 +628,7 @@ namespace SHADE DWORD SHScriptEngine::execProcess(const std::wstring& path, const std::wstring& args) { - STARTUPINFOW startInfo; - PROCESS_INFORMATION procInfo; - ZeroMemory(&startInfo, sizeof(startInfo)); - ZeroMemory(&procInfo, sizeof(procInfo)); - startInfo.cb = sizeof(startInfo); - - std::wstring argsWstr = args; - - // Start Process - const auto SUCCESS = CreateProcess - ( - path.data(), argsWstr.data(), - nullptr, nullptr, false, NULL, nullptr, nullptr, - &startInfo, &procInfo - ); - - // Error Check - if (!SUCCESS) - { - auto err = GetLastError(); - std::ostringstream oss; - oss << "[ScriptEngine] Failed to launch process. Error code: " << std::hex << err - << " (" << SHStringUtilities::GetWin32ErrorMessage(err) << ")"; - throw std::runtime_error(oss.str()); - } + PROCESS_INFORMATION procInfo = execProcessNoBlock(path, args); // Wait for execution to end DWORD status; @@ -662,6 +654,37 @@ namespace SHADE } } + PROCESS_INFORMATION SHScriptEngine::execProcessNoBlock(const std::wstring& path, const std::wstring& args) + { + STARTUPINFOW startInfo; + PROCESS_INFORMATION procInfo; + ZeroMemory(&startInfo, sizeof(startInfo)); + ZeroMemory(&procInfo, sizeof(procInfo)); + startInfo.cb = sizeof(startInfo); + + std::wstring argsWstr = args; + + // Start Process + const auto SUCCESS = CreateProcess + ( + path.data(), argsWstr.data(), + nullptr, nullptr, false, NULL, nullptr, nullptr, + &startInfo, &procInfo + ); + + // Error Check + if (!SUCCESS) + { + auto err = GetLastError(); + std::ostringstream oss; + oss << "[ScriptEngine] Failed to launch process. Error code: " << std::hex << err + << " (" << SHStringUtilities::GetWin32ErrorMessage(err) << ")"; + throw std::runtime_error(oss.str()); + } + + return procInfo; + } + std::wstring SHScriptEngine::generateBuildCommand(bool debug) { std::wostringstream oss; diff --git a/SHADE_Engine/src/Scripting/SHScriptEngine.h b/SHADE_Engine/src/Scripting/SHScriptEngine.h index 1a38a678..40efa042 100644 --- a/SHADE_Engine/src/Scripting/SHScriptEngine.h +++ b/SHADE_Engine/src/Scripting/SHScriptEngine.h @@ -217,6 +217,10 @@ namespace SHADE ///
/// File path to the generated file. void GenerateScriptsCsProjFile(const std::filesystem::path& path = CSPROJ_PATH) const; + /// + /// Opens the script solution in Visual Studio 2019. + /// + void OpenSolution(); private: /*-----------------------------------------------------------------------------*/ @@ -321,6 +325,7 @@ namespace SHADE static bool fileExists(const std::filesystem::path& filePath); static bool copyFile(const std::filesystem::path& from, const std::filesystem::path& to, const std::filesystem::copy_options options) noexcept; static DWORD execProcess(const std::wstring& path, const std::wstring& args); + static PROCESS_INFORMATION execProcessNoBlock(const std::wstring& path, const std::wstring& args); static std::wstring generateBuildCommand(bool debug); }; } From b58b475c04cb0496515e576be9d7f1e057554eb8 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Tue, 20 Dec 2022 23:10:23 +0800 Subject: [PATCH 066/275] Separated collision detection and added contact manager --- SHADE_Engine/src/Math/SHMathHelpers.h | 12 +- .../Collision/Broadphase/SHDynamicAABBTree.h | 2 +- .../CollisionShapes/SHCollisionShape.h | 2 +- .../src/Physics/Collision/SHCollider.cpp | 34 ++ .../src/Physics/Collision/SHCollider.h | 6 +- .../src/Physics/Collision/SHCollisionInfo.cpp | 100 ----- .../src/Physics/Collision/SHCollisionInfo.h | 102 ----- .../Physics/Collision/SHCollisionSpace.cpp | 223 +++++++++ .../src/Physics/Collision/SHCollisionSpace.h | 131 ++++++ .../src/Physics/Dynamics/SHContactManager.cpp | 173 +++++++ .../src/Physics/Dynamics/SHContactManager.h | 103 +++++ .../src/Physics/Dynamics/SHContactManager.hpp | 61 +++ .../src/Physics/Dynamics/SHPhysicsWorld.cpp | 425 ++---------------- .../src/Physics/Dynamics/SHPhysicsWorld.h | 99 ++-- .../Routines/SHPhysicsDebugDrawRoutine.cpp | 5 +- .../Routines/SHPhysicsPostUpdateRoutine.cpp | 13 +- .../Routines/SHPhysicsPreUpdateRoutine.cpp | 50 ++- .../src/Physics/System/SHPhysicsSystem.cpp | 99 +++- .../src/Physics/System/SHPhysicsSystem.h | 12 +- .../System/SHPhysicsSystemInterface.cpp | 56 +-- .../Physics/System/SHPhysicsSystemInterface.h | 12 +- SHADE_Managed/src/Scripts/ScriptStore.cxx | 25 +- 22 files changed, 1011 insertions(+), 734 deletions(-) delete mode 100644 SHADE_Engine/src/Physics/Collision/SHCollisionInfo.cpp delete mode 100644 SHADE_Engine/src/Physics/Collision/SHCollisionInfo.h create mode 100644 SHADE_Engine/src/Physics/Collision/SHCollisionSpace.cpp create mode 100644 SHADE_Engine/src/Physics/Collision/SHCollisionSpace.h create mode 100644 SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp create mode 100644 SHADE_Engine/src/Physics/Dynamics/SHContactManager.h create mode 100644 SHADE_Engine/src/Physics/Dynamics/SHContactManager.hpp diff --git a/SHADE_Engine/src/Math/SHMathHelpers.h b/SHADE_Engine/src/Math/SHMathHelpers.h index 427011a6..b053beff 100644 --- a/SHADE_Engine/src/Math/SHMathHelpers.h +++ b/SHADE_Engine/src/Math/SHMathHelpers.h @@ -46,14 +46,16 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ /** Standard Epsilon value for comparing Single-Precision Floating-Point values. */ - static constexpr float EPSILON = 0.001f; + static constexpr float EPSILON = 0.001f; /** Single-Precision Floating-Point value of infinity */ - static constexpr float INF = std::numeric_limits::infinity(); + static constexpr float INF = std::numeric_limits::infinity(); - static constexpr float PI = std::numbers::pi_v; - static constexpr float HALF_PI = PI * 0.5f; - static constexpr float TWO_PI = 2.0f * PI; + static constexpr float PI = std::numbers::pi_v; + static constexpr float HALF_PI = PI * 0.5f; + static constexpr float TWO_PI = 2.0f * PI; + + static constexpr float EULER_CONSTANT = std::numbers::egamma_v; /*---------------------------------------------------------------------------------*/ /* Static Function Members */ diff --git a/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.h b/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.h index f23fd946..420f30e7 100644 --- a/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.h +++ b/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.h @@ -122,7 +122,7 @@ namespace SHADE /* Data Members */ /*---------------------------------------------------------------------------------*/ - static constexpr float AABB_EXTENSION = 0.1f; + static constexpr float AABB_EXTENSION = 0.2f; // For quick access std::unordered_map nodeMap; diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h index dad59881..6e779eea 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h @@ -37,7 +37,7 @@ namespace SHADE friend class SHCollider; friend class SHColliderComponent; friend class SHCollisionShapeFactory; - friend class SHPhysicsWorld; + friend class SHCollisionSpace; public: /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp index 24ba26b7..928945c9 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp @@ -29,6 +29,7 @@ namespace SHADE SHCollider::SHCollider(EntityID eid, const SHTransform& worldTransform) noexcept : entityID { eid } + , active { true } , debugDraw { false } , hasMoved { true } , rigidBody { nullptr } @@ -39,6 +40,7 @@ namespace SHADE SHCollider::SHCollider(const SHCollider& rhs) noexcept : entityID { rhs.entityID } + , active { rhs.active } , debugDraw { rhs.debugDraw } , hasMoved { rhs.hasMoved } , rigidBody { rhs.rigidBody } @@ -57,6 +59,7 @@ namespace SHADE SHCollider::SHCollider(SHCollider&& rhs) noexcept : entityID { rhs.entityID } + , active { rhs.active } , debugDraw { rhs.debugDraw } , hasMoved { rhs.hasMoved } , rigidBody { rhs.rigidBody } @@ -101,6 +104,7 @@ namespace SHADE } entityID = rhs.entityID; + active = rhs.active; debugDraw = rhs.debugDraw; hasMoved = rhs.hasMoved; rigidBody = rhs.rigidBody; @@ -122,6 +126,7 @@ namespace SHADE } entityID = rhs.entityID; + active = rhs.active; debugDraw = rhs.debugDraw; hasMoved = rhs.hasMoved; rigidBody = rhs.rigidBody; @@ -138,6 +143,16 @@ namespace SHADE /* Getter Function Definitions */ /*-----------------------------------------------------------------------------------*/ + EntityID SHCollider::GetEntityID() const noexcept + { + return entityID; + } + + bool SHCollider::IsActive() const noexcept + { + return active; + } + bool SHCollider::GetDebugDrawState() const noexcept { return debugDraw; @@ -182,6 +197,25 @@ namespace SHADE /* Setter Function Definitions */ /*-----------------------------------------------------------------------------------*/ + void SHCollider::SetIsActive(bool state) noexcept + { + if (active == state) + return; + + active = state; + + if (!broadphase) + return; + + for (auto* shape : shapes) + { + if (active) // Previously inactive + broadphase->Insert(shape->id, shape->ComputeAABB()); + else // Previously active + broadphase->Remove(shape->id); + } + } + void SHCollider::SetDebugDrawState(bool state) noexcept { debugDraw = state; diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.h b/SHADE_Engine/src/Physics/Collision/SHCollider.h index 89ba5ff2..c31be5eb 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.h +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.h @@ -40,7 +40,7 @@ namespace SHADE /* Friends */ /*---------------------------------------------------------------------------------*/ - friend class SHPhysicsWorld; + friend class SHCollisionSpace; public: /*---------------------------------------------------------------------------------*/ @@ -79,6 +79,8 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ + [[nodiscard]] EntityID GetEntityID () const noexcept; + [[nodiscard]] bool IsActive () const noexcept; [[nodiscard]] bool GetDebugDrawState () const noexcept; [[nodiscard]] const SHTransform& GetTransform () const noexcept; @@ -93,6 +95,7 @@ namespace SHADE /* Setter Functions */ /*---------------------------------------------------------------------------------*/ + void SetIsActive (bool state) noexcept; void SetDebugDrawState (bool state) noexcept; void SetRigidBody (SHRigidBody* rb) noexcept; @@ -157,6 +160,7 @@ namespace SHADE EntityID entityID; + bool active; bool debugDraw; bool hasMoved; diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.cpp b/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.cpp deleted file mode 100644 index a28686e0..00000000 --- a/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/**************************************************************************************** - * \file SHCollisionInfo.cpp - * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Implementation for Collision Info. - * - * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or - * disclosure of this file or its contents without the prior written consent - * of DigiPen Institute of Technology is prohibited. -****************************************************************************************/ - -#include - -// Primary Header -#include "SHCollisionInfo.h" - -// Project Headers -#include "Physics/Collision/SHCollider.h" -#include "Physics/Interface/SHColliderComponent.h" - - -namespace SHADE -{ - /*-----------------------------------------------------------------------------------*/ - /* Constructors & Destructor Definitions */ - /*-----------------------------------------------------------------------------------*/ - - SHCollisionInfo::SHCollisionInfo() noexcept - : collisionState { State::INVALID } - { - ids[ENTITY_A] = MAX_EID; - ids[ENTITY_B] = MAX_EID; - ids[COLLIDER_A] = std::numeric_limits::max(); - ids[COLLIDER_B] = std::numeric_limits::max(); - } - - SHCollisionInfo::SHCollisionInfo(EntityID entityA, EntityID entityB) noexcept - : collisionState { State::INVALID } - { - ids[ENTITY_A] = entityA; - ids[ENTITY_B] = entityB; - ids[COLLIDER_A] = std::numeric_limits::max(); - ids[COLLIDER_B] = std::numeric_limits::max(); - } - - /*-----------------------------------------------------------------------------------*/ - /* Operator Overload Definitions */ - /*-----------------------------------------------------------------------------------*/ - - bool SHCollisionInfo::operator==(const SHCollisionInfo& rhs) const noexcept - { - return value[0] == rhs.value[0] && value[1] == rhs.value[1]; - } - - bool SHCollisionInfo::operator!=(const SHCollisionInfo& rhs) const noexcept - { - return value[0] != rhs.value[0] || value[1] != rhs.value[1]; - } - - /*-----------------------------------------------------------------------------------*/ - /* Getter Function Definitions */ - /*-----------------------------------------------------------------------------------*/ - - EntityID SHCollisionInfo::GetEntityA() const noexcept - { - return ids[ENTITY_A]; - } - - EntityID SHCollisionInfo::GetEntityB() const noexcept - { - return ids[ENTITY_B]; - } - - const SHRigidBodyComponent* SHCollisionInfo::GetRigidBodyA() const noexcept - { - return SHComponentManager::GetComponent_s(ids[ENTITY_A]); - } - - const SHRigidBodyComponent* SHCollisionInfo::GetRigidBodyB() const noexcept - { - return SHComponentManager::GetComponent_s(ids[ENTITY_B]); - } - - const SHCollisionShape* SHCollisionInfo::GetColliderA() const noexcept - { - const auto* COLLIDER_COMPONENT = SHComponentManager::GetComponent(ids[ENTITY_A]); - return COLLIDER_COMPONENT->GetCollider()->GetCollisionShape(ids[COLLIDER_A]); - } - - const SHCollisionShape* SHCollisionInfo::GetColliderB() const noexcept - { - const auto* COLLIDER_COMPONENT = SHComponentManager::GetComponent(ids[ENTITY_B]); - return COLLIDER_COMPONENT->GetCollider()->GetCollisionShape(ids[COLLIDER_B]); - } - - SHCollisionInfo::State SHCollisionInfo::GetCollisionState() const noexcept - { - return collisionState; - } - -} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.h b/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.h deleted file mode 100644 index d2dad647..00000000 --- a/SHADE_Engine/src/Physics/Collision/SHCollisionInfo.h +++ /dev/null @@ -1,102 +0,0 @@ -/**************************************************************************************** - * \file SHCollisionInfo.h - * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Interface for Collision Information for Collision & Triggers. - * - * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or - * disclosure of this file or its contents without the prior written consent - * of DigiPen Institute of Technology is prohibited. -****************************************************************************************/ - -#pragma once - -// Project Headers -#include "Physics/Interface/SHColliderComponent.h" -#include "Physics/Interface/SHRigidBodyComponent.h" - - -namespace SHADE -{ - /*-----------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*-----------------------------------------------------------------------------------*/ - - class SH_API SHCollisionInfo - { - private: - /*---------------------------------------------------------------------------------*/ - /* Friends */ - /*---------------------------------------------------------------------------------*/ - - friend class SHCollisionListener; - - public: - /*---------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*---------------------------------------------------------------------------------*/ - - enum class State - { - ENTER - , STAY - , EXIT - - , TOTAL - , INVALID = -1 - }; - - /*---------------------------------------------------------------------------------*/ - /* Constructors & Destructor */ - /*---------------------------------------------------------------------------------*/ - - SHCollisionInfo () noexcept; - SHCollisionInfo (EntityID entityA, EntityID entityB) noexcept; - - - SHCollisionInfo (const SHCollisionInfo& rhs) = default; - SHCollisionInfo (SHCollisionInfo&& rhs) = default; - ~SHCollisionInfo () = default; - - /*---------------------------------------------------------------------------------*/ - /* Operator Overloads */ - /*---------------------------------------------------------------------------------*/ - - bool operator== (const SHCollisionInfo& rhs) const noexcept; - bool operator!= (const SHCollisionInfo& rhs) const noexcept; - - SHCollisionInfo& operator= (const SHCollisionInfo& rhs) = default; - SHCollisionInfo& operator= (SHCollisionInfo&& rhs) = default; - - /*---------------------------------------------------------------------------------*/ - /* Getter Functions */ - /*---------------------------------------------------------------------------------*/ - - [[nodiscard]] EntityID GetEntityA () const noexcept; - [[nodiscard]] EntityID GetEntityB () const noexcept; - [[nodiscard]] const SHRigidBodyComponent* GetRigidBodyA () const noexcept; - [[nodiscard]] const SHRigidBodyComponent* GetRigidBodyB () const noexcept; - [[nodiscard]] const SHCollisionShape* GetColliderA () const noexcept; - [[nodiscard]] const SHCollisionShape* GetColliderB () const noexcept; - [[nodiscard]] State GetCollisionState () const noexcept; - - private: - - static constexpr uint32_t ENTITY_A = 0; - static constexpr uint32_t ENTITY_B = 1; - static constexpr uint32_t COLLIDER_A = 2; - static constexpr uint32_t COLLIDER_B = 3; - - /*---------------------------------------------------------------------------------*/ - /* Data Members */ - /*---------------------------------------------------------------------------------*/ - - union - { - uint64_t value[2]; // EntityValue, ColliderIndexValue - uint32_t ids [4]; // EntityA, EntityB, ColliderIndexA, ColliderIndexB - }; - - State collisionState; - }; - -} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionSpace.cpp b/SHADE_Engine/src/Physics/Collision/SHCollisionSpace.cpp new file mode 100644 index 00000000..bc76d14a --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/SHCollisionSpace.cpp @@ -0,0 +1,223 @@ +/**************************************************************************************** + * \file SHCollisionSpace.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Collision Space that handles collision detetction. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + + +#include + +// Primary Header +#include "SHCollisionSpace.h" + +#include "Narrowphase/SHCollisionDispatch.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Getter Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + const SHAABBTree::AABBs& SHCollisionSpace::GetBroadphaseAABBs() const noexcept + { + return broadphase.GetAABBs(); + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHCollisionSpace::SetContactManager(SHContactManager* _contactManager) noexcept + { + contactManager = _contactManager; + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Member Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHCollisionSpace::AddCollider(SHCollider* collider) noexcept + { + const bool INSERTED = colliders.emplace(collider->entityID, collider).second; + if (!INSERTED) + { + SHLOG_WARNING_D("Attempting to add duplicate collider {} to the Physics World!", collider->entityID) + return; + } + + collider->broadphase = &broadphase; + + // Add all existing shapes to the broadphase + for (const auto* shape : collider->shapes) + broadphase.Insert(shape->id, shape->ComputeAABB()); + } + + void SHCollisionSpace::RemoveCollider(SHCollider* collider) noexcept + { + colliders.erase(collider->entityID); + + const uint32_t NUM_SHAPES = static_cast(collider->shapes.size()); + if (NUM_SHAPES == 0) + return; + + for (uint32_t i = 0; i < NUM_SHAPES; ++i) + broadphase.Remove(collider->shapes[i]->id); + + if (contactManager) + { + contactManager->RemoveInvalidatedTrigger(collider->entityID); + contactManager->RemoveInvalidatedManifold(collider->entityID); + } + + /* + * TODO: + * Get collider's rigid body + * Run through the rigid body's contact graph and wake all of its non-static bodies that are asleep + */ + } + + void SHCollisionSpace::UpdateBroadphase() noexcept + { + // Update any colliders that have moved + for (auto& collider : colliders | std::views::values) + { + const bool IS_ACTIVE = collider->active; + const bool HAS_MOVED = collider->hasMoved; + + if (!IS_ACTIVE || !HAS_MOVED) + continue; + + // Clear hasMoved flag here + collider->hasMoved = false; + + // Update moved shapes in broadphase + for (auto* shape : collider->shapes) + broadphase.Update(shape->id, shape->ComputeAABB()); + } + } + + void SHCollisionSpace::DetectCollisions() noexcept + { + /* + * Broad-phase + */ + + // Broadphase Queries: Kinematic Triggers, Awake Dynamic Bodies & Dynamic Triggers + for (auto& collider : colliders | std::views::values) + { + // Colliders without bodies are considered to be static bodies + // This is specific to this engine because of Unity's stupid convention. + const bool IS_IMPLICIT_STATIC = !collider->rigidBody; + const bool IS_EXPLICIT_STATIC = collider->rigidBody->GetType() == SHRigidBody::Type::STATIC; + const bool IS_ACTIVE = collider->active; + + // Skip inactive colliders + if (!IS_ACTIVE || IS_IMPLICIT_STATIC || IS_EXPLICIT_STATIC) + continue; + + // All remaining are kinematic or dynamic + // Iterate through shapes: if kinematic / dynamic trigger, else if dynamic & awake + // Results are loaded into the narrowphase batch + broadphaseQuery(collider->rigidBody->GetType(), collider); + } + + /* + * Narrow-phase + */ + + // If no potential collisions, we can skip the entire narrow phase. No further updates necessary. + // All contact / trigger states persist in this step. + if (narrowphaseBatch.empty()) + return; + + // All narrowphase IDs are unique, there should be no duplicate collision checks. + // This applies both ways: A -> B and B -> A. + for (auto& [key, narrowphasePair] : narrowphaseBatch) + { + const bool IS_A_TRIGGER = narrowphasePair.A->IsTrigger(); + const bool IS_B_TRIGGER = narrowphasePair.B->IsTrigger(); + + if (IS_A_TRIGGER || IS_B_TRIGGER) + collideTriggers(key, narrowphasePair); + else + collideManifolds(key, narrowphasePair); + } + + // Clear every frame + narrowphaseBatch.clear(); + } + + + /*-----------------------------------------------------------------------------------*/ + /* Private Member Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHCollisionSpace::broadphaseQuery(SHRigidBody::Type rigidBodyType, SHCollider* collider) noexcept + { + for (auto* shape : collider->shapes) + { + // For kinematic shapes, we only query triggers against everything else + if (rigidBodyType == SHRigidBody::Type::KINEMATIC && !shape->IsTrigger()) + continue; + + auto& potentialCollisions = broadphase.Query(shape->id, shape->ComputeAABB()); + + // Build narrow-phase pairs + auto* shapeA = shape; + + const EntityID ID_A = shape->id.GetEntityID(); + const uint32_t INDEX_A = shape->id.GetShapeIndex(); + + for (auto& id : potentialCollisions) + { + // Get corresponding shape + const EntityID ID_B = id.GetEntityID(); + const uint32_t INDEX_B = id.GetShapeIndex(); + + auto* shapeB = colliders[ID_B]->GetCollisionShape(INDEX_B); + + // Build collision ID + SHCollisionKey collisionKey; + collisionKey.SetEntityA(ID_A); + collisionKey.SetEntityB(ID_B); + collisionKey.SetCollisionShapeA(INDEX_A); + collisionKey.SetCollisionShapeB(INDEX_B); + + // Check if it already exists. If it doesn't, put into batch. + // The overloaded equality operator ensures no duplicate collision tests are performed. + auto narrowphasePair = narrowphaseBatch.find(collisionKey); + if (narrowphasePair == narrowphaseBatch.end()) + narrowphaseBatch.emplace(collisionKey, NarrowphasePair{ shapeA, shapeB }); + } + } + } + + void SHCollisionSpace::collideTriggers(const SHCollisionKey& key, NarrowphasePair& narrowphasePair) const noexcept + { + const auto* A = narrowphasePair.A; + const auto* B = narrowphasePair.B; + + const bool COLLIDING = SHCollisionDispatcher::Collide(*A, *B); + + // Send results to contact manager + if (contactManager) + contactManager->AddTrigger(COLLIDING, key); + } + + void SHCollisionSpace::collideManifolds(const SHCollisionKey& key, NarrowphasePair& narrowphasePair) const noexcept + { + auto* A = narrowphasePair.A; + auto* B = narrowphasePair.B; + + SHManifold newManifold { .A = A, .B = B }; + const bool COLLIDING = SHCollisionDispatcher::Collide(newManifold, *A, *B); + + // Send results to contact manager + if (contactManager) + contactManager->AddManifold(COLLIDING, key, newManifold); + } +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionSpace.h b/SHADE_Engine/src/Physics/Collision/SHCollisionSpace.h new file mode 100644 index 00000000..d89404dd --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/SHCollisionSpace.h @@ -0,0 +1,131 @@ +/**************************************************************************************** + * \file SHCollisionSpace.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Collision Space that handles collision detetction. + * This is to separate the logic between dynamics and collision detection, + * but the collision space does send information to the contact manager + * for dynamic resolution and collision state reporting. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "Broadphase/SHDynamicAABBTree.h" +#include "Contacts/SHCollisionEvents.h" +#include "Physics/Dynamics/SHContactManager.h" +#include "SHCollider.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + /** + * @brief + * Allows collision detection to be performed with the use of colliders & collision shapes. + * The space will generate manifold data for resolution when needed. + */ + class SH_API SHCollisionSpace + { + public: + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHCollisionSpace () noexcept = default; + ~SHCollisionSpace () noexcept = default; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + const SHAABBTree::AABBs& GetBroadphaseAABBs () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + + void SetContactManager(SHContactManager* contactManager) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + /** + * @brief + * Adds a collider to the collision space for it to be tested for collision with + * other colliders. + * @param collider + * A collider to add. Duplicates will be ignored. + */ + void AddCollider (SHCollider* collider) noexcept; + + /** + * @brief + * Removes a collider from the collision space. This will prevent any collisions + * being detected between it and other colliders unless manually tested. + * @param collider + * A collider to remove. If a reference to it doesn't exist, it will be ignored. + */ + void RemoveCollider (SHCollider* collider) noexcept; + + /** + * @brief + * Invoke this method to update the broadphase of colliders that have been moved since + * the last frame. + */ + void UpdateBroadphase () noexcept; + + /** + * @brief + * Detects collisions between all colliders. Results are sent to the attached contact + * manager for resolution. + */ + void DetectCollisions () noexcept; + + private: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + struct NarrowphasePair + { + SHCollisionShape* A = nullptr; + SHCollisionShape* B = nullptr; + }; + + using Colliders = std::unordered_map; + using NarrowphaseBatch = std::unordered_map; + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + SHContactManager* contactManager = nullptr; + + Colliders colliders; + NarrowphaseBatch narrowphaseBatch; + + SHAABBTree broadphase; + + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + // Broadphase helpers + + void broadphaseQuery (SHRigidBody::Type rigidBodyType, SHCollider* collider) noexcept; + + // Narrowphase helpers + + void collideTriggers (const SHCollisionKey& key, NarrowphasePair& narrowphasePair) const noexcept; + void collideManifolds (const SHCollisionKey& key, NarrowphasePair& narrowphasePair) const noexcept; + + }; + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp new file mode 100644 index 00000000..be65128d --- /dev/null +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp @@ -0,0 +1,173 @@ +/**************************************************************************************** + * \file SHContactManager.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Contact Manager that stores collision information and + * resolves contact constraints. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHContactManager.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Getter Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + const SHContactManager::TriggerEvents& SHContactManager::GetTriggerEvents() const noexcept + { + static TriggerEvents triggerEvents; + + triggerEvents.clear(); + + for (auto& [id, state] : triggers) + triggerEvents.emplace_back(SHTriggerEvent{ id, state }); + + return triggerEvents; + } + + const SHContactManager::CollisionEvents& SHContactManager::GetCollisionEvents() const noexcept + { + static CollisionEvents collisionEvents; + + collisionEvents.clear(); + + for (auto& [id, manifold] : manifolds) + { + SHCollisionEvent collisionEvent + { + .info = id + , .state = manifold.state + , .normal = manifold.normal + }; + + for (uint32_t i = 0; i < manifold.numContacts; ++i) + collisionEvent.contactPoints[i] = manifold.contacts[i].position; + + collisionEvents.emplace_back(collisionEvent); + } + + return collisionEvents; + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Member Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHContactManager::Update() noexcept + { + // Clear expired or invalid collisions + for (auto manifold = manifolds.begin(); manifold != manifolds.end();) + { + const auto COLLISION_STATE = manifold->second.state; + + const bool IS_EXIT = COLLISION_STATE == SHCollisionState::EXIT; + const bool IS_INVALID = COLLISION_STATE == SHCollisionState::INVALID; + + if (IS_EXIT || IS_INVALID) + manifold = manifolds.erase(manifold); + else + ++manifold; + } + + // Clear expired or invalid triggers + for (auto trigger = triggers.begin(); trigger != triggers.end();) + { + const auto COLLISION_STATE = trigger->second; + + const bool IS_EXIT = COLLISION_STATE == SHCollisionState::EXIT; + const bool IS_INVALID = COLLISION_STATE == SHCollisionState::INVALID; + + if (IS_EXIT || IS_INVALID) + trigger = triggers.erase(trigger); + else + ++trigger; + } + } + + void SHContactManager::AddTrigger(bool isColliding, const SHCollisionKey& key) noexcept + { + auto trigger = triggers.find(key); + + // If id not found, emplace new object. + // New object is in the invalid state + if (trigger == triggers.end()) + trigger = triggers.emplace(key, SHCollisionState::INVALID).first; + + SHCollisionState& state = trigger->second; + updateCollisionState(isColliding, state); + + // If it was a false positive, remove the manifold immediately. + // Remove using iterator as it is on average faster. + if (state == SHCollisionState::INVALID) + trigger = triggers.erase(trigger); + } + + void SHContactManager::AddManifold(bool isColliding, const SHCollisionKey& key, const SHManifold& newManifold) noexcept + { + auto manifold = manifolds.find(key); + + // If id not found, emplace new manifold + if (manifold == manifolds.end()) + manifold = manifolds.emplace(key, newManifold).first; + else + { + // TODO: Update existing manifolds with new data + } + + SHCollisionState& state = manifold->second.state; + updateCollisionState(isColliding, state); + + // If it was a false positive, remove the manifold immediately. + // Remove using iterator as it is on average faster. + if (state == SHCollisionState::INVALID) + manifold = manifolds.erase(manifold); + } + + void SHContactManager::RemoveInvalidatedTrigger(EntityID eid) noexcept + { + remove(triggers, eid); + } + + void SHContactManager::RemoveInvalidatedTrigger(EntityID eid, uint32_t shapeIndex) noexcept + { + remove(triggers, eid, shapeIndex); + } + + void SHContactManager::RemoveInvalidatedManifold(EntityID eid) noexcept + { + remove(manifolds, eid); + } + + void SHContactManager::RemoveInvalidatedManifold(EntityID eid, uint32_t shapeIndex) noexcept + { + remove(manifolds, eid, shapeIndex); + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Member Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHContactManager::updateCollisionState(bool isColliding, SHCollisionState& state) noexcept + { + if (isColliding) + { + // New states start at invalid. In the first frame of collision, move to enter. + // If it already in enter, move to stay + state = state == SHCollisionState::INVALID ? SHCollisionState::ENTER : SHCollisionState::STAY; + } + else + { + // New states start at invalid. In false positive, remain unchanged. + // If previously colliding, move to exit. + state = state == SHCollisionState::INVALID ? SHCollisionState::INVALID : SHCollisionState::EXIT; + } + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h new file mode 100644 index 00000000..06a03a6a --- /dev/null +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h @@ -0,0 +1,103 @@ +/**************************************************************************************** + * \file SHContactManager.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Contact Manager that stores collision information and + * resolves contact constraints. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include + +// Project Headers +#include "Physics/Collision/Contacts/SHCollisionEvents.h" +#include "Physics/Collision/Contacts/SHCollisionKey.h" +#include "Physics/Collision/Contacts/SHManifold.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHContactManager + { + public: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + using TriggerEvents = std::vector; + using CollisionEvents = std::vector; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHContactManager () noexcept = default; + ~SHContactManager () noexcept = default; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + const TriggerEvents& GetTriggerEvents () const noexcept; + const CollisionEvents& GetCollisionEvents () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + /** + * @brief + * Removes any invalidated contacts and triggers. + * @return + */ + void Update () noexcept; + + void AddTrigger (bool isColliding, const SHCollisionKey& key) noexcept; + void AddManifold (bool isColliding, const SHCollisionKey& key, const SHManifold& newManifold) noexcept; + + void RemoveInvalidatedTrigger (EntityID eid) noexcept; + void RemoveInvalidatedTrigger (EntityID eid, uint32_t shapeIndex) noexcept; + void RemoveInvalidatedManifold (EntityID eid) noexcept; + void RemoveInvalidatedManifold (EntityID eid, uint32_t shapeIndex) noexcept; + + private: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + using Manifolds = std::unordered_map; + using Triggers = std::unordered_map; + + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + Manifolds manifolds; + Triggers triggers; + + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + void updateCollisionState (bool isColliding, SHCollisionState& state) noexcept; + + // Removal Helpers + + template + void remove (std::unordered_map& container, EntityID eid); + template + void remove (std::unordered_map& container, EntityID eid, uint32_t shapeIndex); + + }; + + +} // namespace SHADE + +#include "SHContactManager.hpp" \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.hpp b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.hpp new file mode 100644 index 00000000..1403cd79 --- /dev/null +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.hpp @@ -0,0 +1,61 @@ +/**************************************************************************************** + * \file SHContactManager.hpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for templated methods of the Contact Manager. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Primary Header +#include "SHContactManager.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Private Member Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + template + void SHContactManager::remove(std::unordered_map& container, EntityID eid) + { + if (container.empty()) + return; + + for (auto invalidated = container.begin(); invalidated != container.end();) + { + const auto& ID = invalidated->first; + + const bool MATCHES_A = ID.GetEntityA() == eid; + const bool MATCHES_B = ID.GetEntityB() == eid; + + if (MATCHES_A || MATCHES_B) + invalidated = container.erase(invalidated); + else + ++invalidated; + } + } + + template + void SHContactManager::remove(std::unordered_map& container, EntityID eid, uint32_t shapeIndex) + { + if (container.empty()) + return; + + for (auto invalidated = container.begin(); invalidated != container.end();) + { + const auto& ID = invalidated->first; + + const bool MATCHES_A = ID.GetEntityA() == eid && ID.GetShapeIndexA() == shapeIndex; + const bool MATCHES_B = ID.GetEntityB() == eid && ID.GetShapeIndexB() == shapeIndex; + + if (MATCHES_A || MATCHES_B) + invalidated = container.erase(invalidated); + else + ++invalidated; + } + } +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp index 8d0b69f8..b5dba378 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp @@ -13,11 +13,6 @@ // Primary Header #include "SHPhysicsWorld.h" -// Project Headers -#include "Physics/Collision/Narrowphase/SHCollision.h" -#include "Physics/Collision/Narrowphase/SHCollisionDispatch.h" - - namespace SHADE { /*-----------------------------------------------------------------------------------*/ @@ -25,59 +20,39 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ SHPhysicsWorld::SHPhysicsWorld(const WorldSettings& worldSettings) noexcept - : settings { worldSettings } + : settings { worldSettings } + , collisionSpace { nullptr } { SHLOG_INFO_D("Creating Physics World") } SHPhysicsWorld::~SHPhysicsWorld() noexcept { - + collisionSpace = nullptr; } /*-----------------------------------------------------------------------------------*/ /* Getter Functions Definitions */ /*-----------------------------------------------------------------------------------*/ - const SHAABBTree::AABBs& SHPhysicsWorld::GetBroadphaseAABBs() const noexcept + const SHContactManager::TriggerEvents& SHPhysicsWorld::GetTriggerEvents() const noexcept { - return broadphase.GetAABBs(); + return contactManager.GetTriggerEvents(); } - const SHPhysicsWorld::TriggerEvents& SHPhysicsWorld::GetTriggerEvents() const noexcept + const SHContactManager::CollisionEvents& SHPhysicsWorld::GetCollisionEvents() const noexcept { - static TriggerEvents triggerEvents; - - triggerEvents.clear(); - - for (auto& [id, state] : triggers) - triggerEvents.emplace_back(SHTriggerEvent{ id, state }); - - return triggerEvents; + return contactManager.GetCollisionEvents(); } - const SHPhysicsWorld::CollisionEvents& SHPhysicsWorld::GetCollisionEvents() const noexcept + /*-----------------------------------------------------------------------------------*/ + /* Setter Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHPhysicsWorld::SetCollisionSpace(SHCollisionSpace* _collisionSpace) noexcept { - static CollisionEvents collisionEvents; - - collisionEvents.clear(); - - for (auto& [id, manifold] : manifolds) - { - SHCollisionEvent collisionEvent - { - .info = id - , .state = manifold.state - , .normal = manifold.normal - }; - - for (uint32_t i = 0; i < manifold.numContacts; ++i) - collisionEvent.contactPoints[i] = manifold.contacts[i].position; - - collisionEvents.emplace_back(collisionEvent); - } - - return collisionEvents; + collisionSpace = _collisionSpace; + _collisionSpace->SetContactManager(&contactManager); } /*-----------------------------------------------------------------------------------*/ @@ -97,93 +72,54 @@ namespace SHADE { rigidBodies.erase(rigidBody->entityID); - // Attempt to remove any invalidated manifolds - if (manifolds.empty()) - return; - - for (auto manifoldPair = manifolds.begin(); manifoldPair != manifolds.end();) - { - const auto& ID = manifoldPair->first; - - const bool MATCHES_A = ID.GetEntityA() == rigidBody->entityID; - const bool MATCHES_B = ID.GetEntityB() == rigidBody->entityID; - - if (MATCHES_A || MATCHES_B) - manifoldPair = manifolds.erase(manifoldPair); - else - ++manifoldPair; - } + // Contact manager to remove invalidated contacts + contactManager.RemoveInvalidatedManifold(rigidBody->entityID); // TODO: Run through the rigid body's contact graph and wake all of its non-static bodies that are asleep } - void SHPhysicsWorld::AddCollider(SHCollider* collider) noexcept - { - const bool INSERTED = colliders.emplace(collider->entityID, collider).second; - if (!INSERTED) - { - SHLOG_WARNING_D("Attempting to add duplicate collider {} to the Physics World!", collider->entityID) - return; - } - - collider->broadphase = &broadphase; - - // Add all existing shapes to the broadphase - for (const auto* shape : collider->shapes) - broadphase.Insert(shape->id, shape->ComputeAABB()); - } - - void SHPhysicsWorld::RemoveCollider(SHCollider* collider) noexcept - { - colliders.erase(collider->entityID); - - const uint32_t NUM_SHAPES = static_cast(collider->shapes.size()); - if (NUM_SHAPES == 0) - return; - - for (uint32_t i = 0; i < NUM_SHAPES; ++i) - { - const SHCollisionShape* SHAPE = collider->shapes[i]; - - broadphase.Remove(SHAPE->id); - - if (SHAPE->IsTrigger()) - removeInvalidatedTrigger(collider->entityID, i); - else - removeInvalidatedManifold(collider->entityID, i); - } - - /* - * TODO: - * Get collider's rigid body - * Run through the rigid body's contact graph and wake all of its non-static bodies that are asleep - */ - } - - void SHPhysicsWorld::UpdateBroadphase(SHCollider* collider) noexcept - { - auto& shapes = collider->GetCollisionShapes(); - for (auto* shape : shapes) - broadphase.Update(shape->id, shape->ComputeAABB()); - } void SHPhysicsWorld::Step(float dt) { - // Clear containers of exit state collisions - updateEvents(); + // Contact manager to clear expired contacts + contactManager.Update(); - // TODO: Profile each of these - runBroadphase (); - runNarrowphase(); + /* + * Detect Collisions + */ + + if (collisionSpace) + collisionSpace->DetectCollisions(); + + /* + * Integrate Forces + */ for (auto* rigidBody : rigidBodies | std::views::values) { + if (!rigidBody->IsActive()) + continue; + rigidBody->ComputeWorldData(); integrateForces(*rigidBody, dt); } + + /* + * TODO: Resolve Contacts + */ + + /* + * Integrate Velocities + */ + for (auto* rigidBody : rigidBodies | std::views::values) + { + if (!rigidBody->IsActive()) + continue; + integrateVelocities(*rigidBody, dt); + } } /*-----------------------------------------------------------------------------------*/ @@ -262,275 +198,4 @@ namespace SHADE rigidBody.ClearForces(); } - void SHPhysicsWorld::runBroadphase() noexcept - { - // Update any colliders that have moved - for (auto& collider : colliders | std::views::values) - { - if (!collider->hasMoved) - continue; - - // Clear hasMoved flag here - collider->hasMoved = false; - - // Update moved shapes in broadphase - for (auto* shape : collider->shapes) - broadphase.Update(shape->id, shape->ComputeAABB()); - } - - // Query: Kinematic Triggers, Awake Dynamic Bodies & Dynamic Triggers - for (auto& collider : colliders | std::views::values) - { - // Default static bodies - if (!collider->rigidBody) - continue; - - // Explicit static bodies - const bool IS_STATIC = collider->rigidBody->GetType() == SHRigidBody::Type::STATIC; - if (IS_STATIC) - continue; - - // All remaining are kinematic or dynamic - // Iterate through shapes: if kinematic / dynamic trigger, else if dynamic & awake - if (collider->rigidBody->GetType() == SHRigidBody::Type::KINEMATIC) - queryKinematic(collider); - - if (collider->rigidBody->GetType() == SHRigidBody::Type::DYNAMIC) - queryDynamic(collider); - } - - } - - void SHPhysicsWorld::queryKinematic(SHCollider* collider) noexcept - { - for (auto* shape : collider->shapes) - { - // For kinematic shapes, we only query triggers against everything else - if (!shape->IsTrigger()) - continue; - - auto& potentialCollisions = broadphase.Query(shape->id, shape->ComputeAABB()); - - // Build narrow-phase pairs - auto* shapeA = shape; - - const EntityID ID_A = shape->id.GetEntityID(); - const uint32_t INDEX_A = shape->id.GetShapeIndex(); - - for (auto& id : potentialCollisions) - { - // Get corresponding shape - const EntityID ID_B = id.GetEntityID(); - const uint32_t INDEX_B = id.GetShapeIndex(); - - auto* shapeB = colliders[ID_B]->GetCollisionShape(INDEX_B); - - // Build collision ID - SHCollisionKey collisionKey; - collisionKey.SetEntityA(ID_A); - collisionKey.SetEntityB(ID_B); - collisionKey.SetCollisionShapeA(INDEX_A); - collisionKey.SetCollisionShapeB(INDEX_B); - - // Check if it already exists. If it doesn't, put into batch. - auto narrowphasePair = narrowphaseBatch.find(collisionKey); - if (narrowphasePair == narrowphaseBatch.end()) - narrowphaseBatch.emplace(collisionKey, NarrowphasePair{ shapeA, shapeB }); - } - } - } - - void SHPhysicsWorld::queryDynamic(SHCollider* collider) noexcept - { - for (auto* shape : collider->shapes) - { - auto& potentialCollisions = broadphase.Query(shape->id, shape->ComputeAABB()); - - // Build narrow-phase pairs - auto* shapeA = shape; - - const EntityID ID_A = shape->id.GetEntityID(); - const uint32_t INDEX_A = shape->id.GetShapeIndex(); - - for (auto& id : potentialCollisions) - { - // Get corresponding shape - const EntityID ID_B = id.GetEntityID(); - const uint32_t INDEX_B = id.GetShapeIndex(); - - auto* shapeB = colliders[ID_B]->GetCollisionShape(INDEX_B); - - // Build collision ID - SHCollisionKey collisionKey; - collisionKey.SetEntityA(ID_A); - collisionKey.SetEntityB(ID_B); - collisionKey.SetCollisionShapeA(INDEX_A); - collisionKey.SetCollisionShapeB(INDEX_B); - - // Check if it already exists. If it doesn't, put into batch. - auto narrowphasePair = narrowphaseBatch.find(collisionKey); - if (narrowphasePair == narrowphaseBatch.end()) - narrowphaseBatch.emplace(collisionKey, NarrowphasePair{ shapeA, shapeB }); - } - } - } - - void SHPhysicsWorld::runNarrowphase() noexcept - { - if (narrowphaseBatch.empty()) - return; - - for (auto& [id, narrowphasePair] : narrowphaseBatch) - { - const bool IS_A_TRIGGER = narrowphasePair.A->IsTrigger(); - const bool IS_B_TRIGGER = narrowphasePair.B->IsTrigger(); - - // Check if ID exists in trigger - if (IS_A_TRIGGER || IS_B_TRIGGER) - collideTriggers(id, narrowphasePair); - else - collideManifolds(id, narrowphasePair); - } - - // Clear every frame - narrowphaseBatch.clear(); - } - - void SHPhysicsWorld::collideTriggers(const SHCollisionKey& id, NarrowphasePair& narrowphasePair) noexcept - { - const auto* A = narrowphasePair.A; - const auto* B = narrowphasePair.B; - - const bool COLLIDING = SHCollisionDispatcher::Collide(*A, *B); - - auto trigger = triggers.find(id); - - // If id not found, emplace new object. - // New object is in the invalid state - if (trigger == triggers.end()) - trigger = triggers.emplace(id, SHCollisionState::INVALID).first; - - SHCollisionState& state = trigger->second; - updateCollisionState(COLLIDING, state); - - // If it was a false positive, remove the manifold immediately. - // Remove using iterator as it is on average faster. - if (state == SHCollisionState::INVALID) - trigger = triggers.erase(trigger); - } - - void SHPhysicsWorld::collideManifolds(const SHCollisionKey& id, NarrowphasePair& narrowphasePair) noexcept - { - auto* A = narrowphasePair.A; - auto* B = narrowphasePair.B; - - SHManifold newManifold { .A = A, .B = B }; - const bool COLLIDING = SHCollisionDispatcher::Collide(newManifold, *A, *B); - - auto manifold = manifolds.find(id); - - // If id not found, emplace new manifold - if (manifold == manifolds.end()) - manifold = manifolds.emplace(id, newManifold).first; - else - { - // TODO: Update existing manifolds with new data - } - - SHCollisionState& state = manifold->second.state; - updateCollisionState(COLLIDING, state); - - // If it was a false positive, remove the manifold immediately. - // Remove using iterator as it is on average faster. - if (state == SHCollisionState::INVALID) - manifold = manifolds.erase(manifold); - } - - void SHPhysicsWorld::updateCollisionState(bool isColliding, SHCollisionState& state) noexcept - { - if (isColliding) - { - // New states start at invalid. In the first frame of collision, move to enter. - // If it already in enter, move to stay - state = state == SHCollisionState::INVALID ? SHCollisionState::ENTER : SHCollisionState::STAY; - } - else - { - // New states start at invalid. In false positive, remain unchanged. - // If previously colliding, move to exit. - state = state == SHCollisionState::INVALID ? SHCollisionState::INVALID : SHCollisionState::EXIT; - } - } - - void SHPhysicsWorld::updateEvents() noexcept - { - // Clear expired or invalid collisions - for (auto manifold = manifolds.begin(); manifold != manifolds.end();) - { - const auto COLLISION_STATE = manifold->second.state; - - const bool IS_EXIT = COLLISION_STATE == SHCollisionState::EXIT; - const bool IS_INVALID = COLLISION_STATE == SHCollisionState::INVALID; - - if (IS_EXIT || IS_INVALID) - manifold = manifolds.erase(manifold); - else - ++manifold; - } - - // Clear expired or invalid triggers - for (auto trigger = triggers.begin(); trigger != triggers.end();) - { - const auto COLLISION_STATE = trigger->second; - - const bool IS_EXIT = COLLISION_STATE == SHCollisionState::EXIT; - const bool IS_INVALID = COLLISION_STATE == SHCollisionState::INVALID; - - if (IS_EXIT || IS_INVALID) - trigger = triggers.erase(trigger); - else - ++trigger; - } - } - - void SHPhysicsWorld::removeInvalidatedTrigger(EntityID eid, uint32_t shapeIndex) - { - if (triggers.empty()) - return; - - for (auto invalidatedTrigger = triggers.begin(); invalidatedTrigger != triggers.end();) - { - const auto& ID = invalidatedTrigger->first; - - const bool MATCHES_A = ID.GetEntityA() == eid && ID.GetShapeIndexA() == shapeIndex; - const bool MATCHES_B = ID.GetEntityB() == eid && ID.GetShapeIndexB() == shapeIndex; - - if (MATCHES_A || MATCHES_B) - invalidatedTrigger = triggers.erase(invalidatedTrigger); - else - ++invalidatedTrigger; - } - } - - void SHPhysicsWorld::removeInvalidatedManifold(EntityID eid, uint32_t shapeIndex) - { - if (manifolds.empty()) - return; - - for (auto invalidatedManifold = manifolds.begin(); invalidatedManifold != manifolds.end();) - { - const auto& ID = invalidatedManifold->first; - - const bool MATCHES_A = ID.GetEntityA() == eid && ID.GetShapeIndexA() == shapeIndex; - const bool MATCHES_B = ID.GetEntityB() == eid && ID.GetShapeIndexB() == shapeIndex; - - if (MATCHES_A || MATCHES_B) - invalidatedManifold = manifolds.erase(invalidatedManifold); - else - ++invalidatedManifold; - } - } - - - } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h index a0743a30..8248299a 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h @@ -13,10 +13,8 @@ #include // Project Headers -#include "Physics/Collision/Broadphase/SHDynamicAABBTree.h" -#include "Physics/Collision/Contacts/SHCollisionEvents.h" -#include "Physics/Collision/Contacts/SHManifold.h" -#include "Physics/Collision/SHCollider.h" +#include "Physics/Collision/SHCollisionSpace.h" +#include "SHContactManager.h" #include "SHRigidBody.h" @@ -26,10 +24,15 @@ namespace SHADE /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ + /** + * @brief + * Encapsulates the overall simulation of physics. The bulk of dynamics are handled here, + * with the collision detection handled by an attached collision space.
+ * A collision space must be created separately and attached with the world. + */ class SH_API SHPhysicsWorld { public: - /*---------------------------------------------------------------------------------*/ /* Type Definitions */ /*---------------------------------------------------------------------------------*/ @@ -47,9 +50,6 @@ namespace SHADE bool sleepingEnabled = true; }; - using TriggerEvents = std::vector; - using CollisionEvents = std::vector; - /*---------------------------------------------------------------------------------*/ /* Constructors & Destructor */ /*---------------------------------------------------------------------------------*/ @@ -71,29 +71,44 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - const SHAABBTree::AABBs& GetBroadphaseAABBs () const noexcept; + const SHContactManager::TriggerEvents& GetTriggerEvents () const noexcept; + const SHContactManager::CollisionEvents& GetCollisionEvents () const noexcept; - const TriggerEvents& GetTriggerEvents () const noexcept; - const CollisionEvents& GetCollisionEvents () const noexcept; + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + + void SetCollisionSpace(SHCollisionSpace* collisionSpace) noexcept; /*---------------------------------------------------------------------------------*/ /* Member Functions */ /*---------------------------------------------------------------------------------*/ + /** + * @brief + * Adds a rigid body to the world for it to be simulated using motion dynamics. + * @param rigidBody + * A rigid body to add. Duplicates will be ignored. + */ void AddRigidBody (SHRigidBody* rigidBody) noexcept; - void RemoveRigidBody (SHRigidBody* rigidBody) noexcept; - - void AddCollider (SHCollider* collider) noexcept; - void RemoveCollider (SHCollider* collider) noexcept; /** * @brief - * Invoke this method to update the broadphase of a collider while the simulation - * is not running. - * @param collider - * The collider to update. + * Removes a rigid body from the world. It's motion will not be affected unless + * explicitly modified. + * @param rigidBody + * A rigid body to add. Duplicates will be ignored. + */ + void RemoveRigidBody (SHRigidBody* rigidBody) noexcept; + + /** + * @brief + * Performs a single simulation step.
+ * Detect Collisions -> Integrate Forces -> Resolve Contacts -> Integrate Velocities + * @param dt + * A discrete time step for the simulation. This should be consistent for deteministic + * behaviour. */ - void UpdateBroadphase (SHCollider* collider) noexcept; void Step (float dt); private: @@ -101,42 +116,18 @@ namespace SHADE /* Type Definitions */ /*---------------------------------------------------------------------------------*/ - struct NarrowphasePair - { - SHCollisionShape* A = nullptr; - SHCollisionShape* B = nullptr; - }; - // EntityIDs are used to map resolved contraints back to bodies using RigidBodies = std::unordered_map; - using Colliders = std::unordered_map; - - // Collisions - - using NarrowphaseBatch = std::unordered_map; - using Manifolds = std::unordered_map; - using Triggers = std::unordered_map; - - /*---------------------------------------------------------------------------------*/ /* Data Members */ /*---------------------------------------------------------------------------------*/ WorldSettings settings; - - // Containers + SHCollisionSpace* collisionSpace; RigidBodies rigidBodies; - Colliders colliders; - - NarrowphaseBatch narrowphaseBatch; - Manifolds manifolds; - Triggers triggers; - - // World components - - SHAABBTree broadphase; + SHContactManager contactManager; /*---------------------------------------------------------------------------------*/ /* Function Members */ @@ -146,22 +137,8 @@ namespace SHADE void integrateForces (SHRigidBody& rigidBody, float dt) const noexcept; void integrateVelocities (SHRigidBody& rigidBody, float dt) const noexcept; - // Broadphase helpers - - void runBroadphase () noexcept; - void queryKinematic (SHCollider* collider) noexcept; - void queryDynamic (SHCollider* collider) noexcept; - - // Narrowphase helpers - - void runNarrowphase () noexcept; - void collideTriggers (const SHCollisionKey& id, NarrowphasePair& narrowphasePair) noexcept; - void collideManifolds (const SHCollisionKey& id, NarrowphasePair& narrowphasePair) noexcept; - void updateCollisionState (bool isColliding, SHCollisionState& state) noexcept; - void updateEvents () noexcept; - void removeInvalidatedTrigger (EntityID eid, uint32_t shapeIndex); - void removeInvalidatedManifold (EntityID eid, uint32_t shapeIndex); + }; diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp index b43b5b77..9bf43115 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp @@ -87,9 +87,8 @@ namespace SHADE { const SHColour& AABB_COLOUR = physicsDebugDrawSystem->DEBUG_DRAW_COLOURS[SHUtilities::ConvertEnum(Colours::BROADPHASE)]; - auto& broadphaseAABBs = physicsSystem->GetWorld()->GetBroadphaseAABBs(); - - for (auto& aabb : broadphaseAABBs) + const auto& BROADPHASE_AABBS = physicsSystem->collisionSpace->GetBroadphaseAABBs(); + for (auto& aabb : BROADPHASE_AABBS) { // Compute AABB Transform const SHMatrix TRS = SHMatrix::Transform(aabb.GetCenter(), SHQuaternion::Identity, aabb.GetWorldExtents() * 2.0f); diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp index 200642e1..5b0e5563 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPostUpdateRoutine.cpp @@ -16,6 +16,7 @@ // Project Headers #include "ECS_Base/Managers/SHSystemManager.h" #include "Math/Transform/SHTransformComponent.h" +#include "Scene/SHSceneManager.h" #include "Scripting/SHScriptEngine.h" @@ -50,15 +51,25 @@ namespace SHADE const auto& RIGIDBODY_DENSE = SHComponentManager::GetDense(); for (auto& rigidBodyComponent : RIGIDBODY_DENSE) { - auto* transformComponent = SHComponentManager::GetComponent_s(rigidBodyComponent.GetEID()); + const EntityID EID = rigidBodyComponent.GetEID(); + + // Skip missing transforms + auto* transformComponent = SHComponentManager::GetComponent_s(EID); if (!transformComponent) continue; + // Skip invalid bodies (Should not occur) if (!rigidBodyComponent.rigidBody) continue; + // Skip inactive bodies + const bool IS_ACTIVE = SHSceneManager::CheckNodeAndComponentsActive(EID); + if (!IS_ACTIVE) + continue; + const SHMotionState& MOTION_STATE = rigidBodyComponent.rigidBody->GetMotionState(); + // Skip objects that have not moved if (!MOTION_STATE) continue; diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp index 178a6120..17f3f83d 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsPreUpdateRoutine.cpp @@ -16,6 +16,7 @@ // Project Headers #include "ECS_Base/Managers/SHComponentManager.h" #include "Math/Transform/SHTransformComponent.h" +#include "Scene/SHSceneManager.h" namespace SHADE { @@ -40,35 +41,54 @@ namespace SHADE for (auto& [entityID, physicsObject] : physicsObjects) { const auto* TRANSFORM_COMPONENT = SHComponentManager::GetComponent_s(entityID); - - if (!TRANSFORM_COMPONENT || !TRANSFORM_COMPONENT->HasChanged()) - continue; - - const SHVec3& WORLD_POS = TRANSFORM_COMPONENT->GetWorldPosition(); - const SHQuaternion& WORLD_ROT = TRANSFORM_COMPONENT->GetWorldOrientation(); - const SHVec3& WORLD_SCL = TRANSFORM_COMPONENT->GetWorldScale(); + // Assume transform is always active + const bool UPDATE_TRANSFORM = TRANSFORM_COMPONENT && TRANSFORM_COMPONENT->HasChanged(); // We assume that all engine components and physics object components have been successfully linked if (physicsObject.rigidBody) { - SHMotionState& motionState = physicsObject.rigidBody->GetMotionState(); + const bool IS_ACTIVE = SHSceneManager::CheckNodeAndComponentsActive(entityID); + const bool RIGIDBODY_ACTIVE = physicsObject.rigidBody->IsActive(); - motionState.ForcePosition(TRANSFORM_COMPONENT->GetWorldPosition()); - motionState.ForceOrientation(TRANSFORM_COMPONENT->GetWorldOrientation()); + if (IS_ACTIVE != RIGIDBODY_ACTIVE) + physicsObject.rigidBody->SetIsActive(IS_ACTIVE); + + if (UPDATE_TRANSFORM) + { + const SHVec3& WORLD_POS = TRANSFORM_COMPONENT->GetWorldPosition(); + const SHQuaternion& WORLD_ROT = TRANSFORM_COMPONENT->GetWorldOrientation(); + + SHMotionState& motionState = physicsObject.rigidBody->GetMotionState(); + motionState.ForcePosition(WORLD_POS); + motionState.ForceOrientation(WORLD_ROT); + } } if (physicsObject.collider) { - physicsObject.collider->SetPosition(WORLD_POS); - physicsObject.collider->SetOrientation(WORLD_ROT); - physicsObject.collider->SetScale(WORLD_SCL); + const bool IS_ACTIVE = SHSceneManager::CheckNodeAndComponentsActive(entityID); + const bool COLLIDER_ACTIVE = physicsObject.collider->IsActive(); - physicsObject.collider->RecomputeShapes(); + if (IS_ACTIVE != COLLIDER_ACTIVE) + physicsObject.collider->SetIsActive(IS_ACTIVE); - physicsSystem->physicsWorld->UpdateBroadphase(physicsObject.collider); + if (UPDATE_TRANSFORM) + { + const SHVec3& WORLD_POS = TRANSFORM_COMPONENT->GetWorldPosition(); + const SHQuaternion& WORLD_ROT = TRANSFORM_COMPONENT->GetWorldOrientation(); + const SHVec3& WORLD_SCL = TRANSFORM_COMPONENT->GetWorldScale(); + + physicsObject.collider->SetPosition(WORLD_POS); + physicsObject.collider->SetOrientation(WORLD_ROT); + physicsObject.collider->SetScale(WORLD_SCL); + + physicsObject.collider->RecomputeShapes(); + } } } + + physicsSystem->collisionSpace->UpdateBroadphase(); } } // namespace SHADE diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp index 65bbc5d4..508c19ad 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.cpp @@ -21,6 +21,7 @@ #include "Editor/SHEditor.h" #include "Physics/Collision/CollisionTags/SHCollisionTagMatrix.h" #include "Physics/Interface/SHColliderComponent.h" +#include "Scripting/SHScriptEngine.h" namespace SHADE { @@ -33,6 +34,7 @@ namespace SHADE , interpolationFactor { 0.0 } , fixedDT { DEFAULT_FIXED_STEP } , physicsWorld { nullptr } + , collisionSpace { nullptr } { // Add more events here to register them @@ -44,6 +46,7 @@ namespace SHADE SHPhysicsSystem::~SHPhysicsSystem() noexcept { + delete collisionSpace; delete physicsWorld; } @@ -51,11 +54,6 @@ namespace SHADE /* Getter Function Definitions */ /*-----------------------------------------------------------------------------------*/ - const SHPhysicsWorld* SHPhysicsSystem::GetWorld() const noexcept - { - return physicsWorld; - } - double SHPhysicsSystem::GetFixedUpdateRate() const noexcept { return 1.0 / fixedDT; @@ -66,6 +64,16 @@ namespace SHADE return fixedDT; } + const std::vector& SHPhysicsSystem::GetTriggerInfo() const noexcept + { + return physicsWorld->GetTriggerEvents(); + } + + const std::vector& SHPhysicsSystem::GetCollisionInfo() const noexcept + { + return physicsWorld->GetCollisionEvents(); + } + /*-----------------------------------------------------------------------------------*/ /* Setter Function Definitions */ /*-----------------------------------------------------------------------------------*/ @@ -137,7 +145,48 @@ namespace SHADE void SHPhysicsSystem::ForceUpdate() { + if (!physicsWorld) + return; + auto* scriptingSystem = SHSystemManager::GetSystem(); + + if (scriptingSystem == nullptr) + { + SHLOGV_ERROR("Unable to invoke collision and trigger script events due to missing SHScriptEngine!"); + } + + scriptingSystem->ExecuteFixedUpdates(); + physicsWorld->Step(static_cast(fixedDT)); + + const auto& RIGIDBODY_DENSE = SHComponentManager::GetDense(); + for (auto& rigidBodyComponent : RIGIDBODY_DENSE) + { + const EntityID EID = rigidBodyComponent.GetEID(); + + // Skip missing transforms + auto* transformComponent = SHComponentManager::GetComponent_s(EID); + if (!transformComponent) + continue; + + // Skip invalid bodies (Should not occur) + if (!rigidBodyComponent.rigidBody) + continue; + + // Skip inactive bodies + const bool IS_ACTIVE = SHSceneManager::CheckNodeAndComponentsActive(EID); + if (!IS_ACTIVE) + continue; + + const SHMotionState& MOTION_STATE = rigidBodyComponent.rigidBody->GetMotionState(); + + // Skip objects that have not moved + if (!MOTION_STATE) + continue; + + // We ignore interpolations here because we are only stepping once + transformComponent->SetWorldPosition(MOTION_STATE.position); + transformComponent->SetWorldOrientation(MOTION_STATE.orientation); + } } /*-----------------------------------------------------------------------------------*/ @@ -162,17 +211,29 @@ namespace SHADE { if (PHYSICS_OBJECT.rigidBody) physicsWorld->RemoveRigidBody(PHYSICS_OBJECT.rigidBody); - - if (PHYSICS_OBJECT.collider) - physicsWorld->RemoveCollider(PHYSICS_OBJECT.collider); } delete physicsWorld; physicsWorld = nullptr; } - // Create the physics world - physicsWorld = new SHPhysicsWorld; + if (collisionSpace) + { + for (const auto& PHYSICS_OBJECT : physicsObjectManager.GetPhysicsObjects() | std::views::values) + { + if (PHYSICS_OBJECT.collider) + collisionSpace->RemoveCollider(PHYSICS_OBJECT.collider); + } + + delete collisionSpace; + collisionSpace = nullptr; + } + + // Create the physics world & collision space + physicsWorld = new SHPhysicsWorld; + collisionSpace = new SHCollisionSpace; + + physicsWorld->SetCollisionSpace(collisionSpace); // Immediately add all existing bodies and colliders to the world. // Since we recreated the scene and the world, the initial data has been reset and determinism is guaranteed. @@ -184,7 +245,7 @@ namespace SHADE physicsWorld->AddRigidBody(PHYSICS_OBJECT.rigidBody); if (PHYSICS_OBJECT.collider) - physicsWorld->AddCollider(PHYSICS_OBJECT.collider); + collisionSpace->AddCollider(PHYSICS_OBJECT.collider); } return onSceneInitEvent.get()->handle; @@ -203,9 +264,12 @@ namespace SHADE physicsWorld->RemoveRigidBody(PHYSICS_OBJECT.rigidBody); if (PHYSICS_OBJECT.collider) - physicsWorld->RemoveCollider(PHYSICS_OBJECT.collider); + collisionSpace->RemoveCollider(PHYSICS_OBJECT.collider); } + delete collisionSpace; + collisionSpace = nullptr; + delete physicsWorld; physicsWorld = nullptr; @@ -247,10 +311,10 @@ namespace SHADE { physicsObjectManager.AddCollider(EID); - if (physicsWorld) + if (collisionSpace) { auto* collider = physicsObjectManager.GetPhysicsObject(EID)->collider; - physicsWorld->AddCollider(collider); + collisionSpace->AddCollider(collider); } } @@ -289,17 +353,16 @@ namespace SHADE if (IS_COLLIDER) { - if (physicsWorld) + if (collisionSpace) { auto* collider = physicsObjectManager.GetPhysicsObject(EID)->collider; - physicsWorld->RemoveCollider(collider); + collisionSpace->RemoveCollider(collider); } physicsObjectManager.RemoveCollider(EID); } - return onComponentRemovedEvent.get()->handle; } -} // namespace SHADE \ No newline at end of file +} // namespace SHADE diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h index fca12498..ab28a299 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h @@ -33,6 +33,13 @@ namespace SHADE */ class SH_API SHPhysicsSystem final : public SHSystem { + private: + /*---------------------------------------------------------------------------------*/ + /* Friends */ + /*---------------------------------------------------------------------------------*/ + + friend class SHPhysicsDebugDrawSystem; + public: /*---------------------------------------------------------------------------------*/ /* Constructors & Destructor */ @@ -45,7 +52,6 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] const SHPhysicsWorld* GetWorld () const noexcept; [[nodiscard]] double GetFixedUpdateRate() const noexcept; [[nodiscard]] double GetFixedDT () const noexcept; @@ -56,6 +62,9 @@ namespace SHADE void SetFixedUpdateRate(double fixedUpdateRate) noexcept; void SetFixedDT(double fixedDt) noexcept; + const std::vector& GetTriggerInfo () const noexcept; + const std::vector& GetCollisionInfo () const noexcept; + /*---------------------------------------------------------------------------------*/ /* Member Functions */ /*---------------------------------------------------------------------------------*/ @@ -137,6 +146,7 @@ namespace SHADE // Sub-systems / managers SHPhysicsWorld* physicsWorld; + SHCollisionSpace* collisionSpace; SHPhysicsObjectManager physicsObjectManager; /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.cpp index 06f1b464..f72eb271 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.cpp @@ -1,8 +1,6 @@ /************************************************************************************//*! \file SHPhysicsSystemInterface.cpp -\author Tng Kah Wei, kahwei.tng, 390009620 -\par email: kahwei.tng\@digipen.edu -\date Oct 31, 2022 +\author Diren D Bharwani, diren.dbharwani, 390002520 \brief Contains the definitions of the functions of the static SHPhysicsSystemInterface class. @@ -10,10 +8,13 @@ Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or disclosure of this file or its contents without the prior written consent of DigiPen Institute of Technology is prohibited. *//*************************************************************************************/ -// Precompiled Headers + + #include "SHpch.h" + // Primary Header #include "SHPhysicsSystemInterface.h" + // Project Includes #include "ECS_Base/Managers/SHSystemManager.h" #include "Physics/System/SHPhysicsSystem.h" @@ -23,42 +24,47 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ /* Static Usage Functions */ /*-----------------------------------------------------------------------------------*/ - const std::vector& SHPhysicsSystemInterface::GetCollisionInfo() noexcept + const std::vector& SHPhysicsSystemInterface::GetCollisionInfo() noexcept { - static std::vector emptyVec; + static std::vector emptyVec; - //auto phySystem = SHSystemManager::GetSystem(); - //if (phySystem) - //{ - // return phySystem->GetAllCollisionInfo(); - //} + auto* physicsSystem = SHSystemManager::GetSystem(); + if (physicsSystem) + return physicsSystem->GetCollisionInfo(); - //SHLOGV_WARNING("Failed to get collision events. Empty vector returned instead."); + SHLOGV_WARNING("Failed to get collision events. Empty vector returned instead."); return emptyVec; } - const std::vector& SHPhysicsSystemInterface::GetTriggerInfo() noexcept + const std::vector& SHPhysicsSystemInterface::GetTriggerInfo() noexcept { - static std::vector emptyVec; + static std::vector emptyVec; - //auto phySystem = SHSystemManager::GetSystem(); - //if (phySystem) - //{ - // return phySystem->GetAllTriggerInfo(); - //} + auto* physicsSystem = SHSystemManager::GetSystem(); + if (physicsSystem) + return physicsSystem->GetTriggerInfo(); - //SHLOGV_WARNING("Failed to get trigger events. Empty vector returned instead."); + SHLOGV_WARNING("Failed to get trigger events. Empty vector returned instead."); return emptyVec; } double SHPhysicsSystemInterface::GetFixedDT() noexcept { - auto phySystem = SHSystemManager::GetSystem(); - if (phySystem) - { - return 1.0 / phySystem->GetFixedUpdateRate(); - } + auto* physicsSystem = SHSystemManager::GetSystem(); + if (physicsSystem) + return physicsSystem->GetFixedDT(); SHLOGV_WARNING("Failed to get fixed delta time. 0.0 returned instead."); return 0.0; } + + int SHPhysicsSystemInterface::GetFixedUpdateRate() noexcept + { + auto* physicsSystem = SHSystemManager::GetSystem(); + if (physicsSystem) + return physicsSystem->GetFixedUpdateRate(); + + SHLOGV_WARNING("Failed to get fixed update rate. 0.0 returned instead."); + return 0.0; + } + } diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.h b/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.h index ed190cc9..e6103e87 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.h +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystemInterface.h @@ -1,22 +1,20 @@ /************************************************************************************//*! \file SHPhysicsSystemInterface.h -\author Tng Kah Wei, kahwei.tng, 390009620 -\par email: kahwei.tng\@digipen.edu -\date Oct 31, 2022 +\author Diren D Bharwani, diren.dbharwani, 390002520 \brief Contains the definition of the SHGraphicsSystemInterface static class. Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or disclosure of this file or its contents without the prior written consent of DigiPen Institute of Technology is prohibited. *//*************************************************************************************/ + #pragma once -// STL Includes #include // Project Headers #include "ECS_Base/Entity/SHEntity.h" -#include "Physics/Collision/SHCollisionInfo.h" +#include "Physics/Collision/Contacts/SHCollisionEvents.h" namespace SHADE @@ -49,8 +47,8 @@ namespace SHADE /* Static Usage Functions */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] static const std::vector& GetCollisionInfo () noexcept; - [[nodiscard]] static const std::vector& GetTriggerInfo () noexcept; + [[nodiscard]] static const std::vector& GetCollisionInfo () noexcept; + [[nodiscard]] static const std::vector& GetTriggerInfo () noexcept; [[nodiscard]] static double GetFixedDT () noexcept; [[nodiscard]] static int GetFixedUpdateRate () noexcept; }; diff --git a/SHADE_Managed/src/Scripts/ScriptStore.cxx b/SHADE_Managed/src/Scripts/ScriptStore.cxx index a5a0ebc7..1393fe33 100644 --- a/SHADE_Managed/src/Scripts/ScriptStore.cxx +++ b/SHADE_Managed/src/Scripts/ScriptStore.cxx @@ -30,7 +30,6 @@ of DigiPen Institute of Technology is prohibited. #include "Engine/Application.hxx" #include "Physics/System/SHPhysicsSystemInterface.h" #include "Physics/SHPhysicsEvents.h" -#include "Physics/Collision/SHCollisionInfo.h" namespace SHADE { @@ -605,8 +604,8 @@ namespace SHADE { auto entities = { - std::make_pair(collisionInfo.GetEntityA(), collisionInfo.GetEntityB()), - std::make_pair(collisionInfo.GetEntityB(), collisionInfo.GetEntityA()) + std::make_pair(collisionInfo.info.GetEntityA(), collisionInfo.info.GetEntityB()), + std::make_pair(collisionInfo.info.GetEntityB(), collisionInfo.info.GetEntityA()) }; for (auto entity : entities) { @@ -625,15 +624,15 @@ namespace SHADE for (int i = 0; i < entityScripts->Count; ++i) { Script^ script = entityScripts[i]; - switch (collisionInfo.GetCollisionState()) + switch (collisionInfo.state) { - case SHCollisionInfo::State::ENTER: + case SHCollisionState::ENTER: script->OnCollisionEnter(info); break; - case SHCollisionInfo::State::STAY: + case SHCollisionState::STAY: script->OnCollisionStay(info); break; - case SHCollisionInfo::State::EXIT: + case SHCollisionState::EXIT: script->OnCollisionExit(info); break; } @@ -647,8 +646,8 @@ namespace SHADE { auto entities = { - std::make_pair(triggerInfo.GetEntityA(), triggerInfo.GetEntityB()), - std::make_pair(triggerInfo.GetEntityB(), triggerInfo.GetEntityA()) + std::make_pair(triggerInfo.info.GetEntityA(), triggerInfo.info.GetEntityB()), + std::make_pair(triggerInfo.info.GetEntityB(), triggerInfo.info.GetEntityA()) }; for (auto entity : entities) { @@ -667,15 +666,15 @@ namespace SHADE for (int i = 0; i < entityScripts->Count; ++i) { Script^ script = entityScripts[i]; - switch (triggerInfo.GetCollisionState()) + switch (triggerInfo.state) { - case SHCollisionInfo::State::ENTER: + case SHCollisionState::ENTER: script->OnTriggerEnter(info); break; - case SHCollisionInfo::State::STAY: + case SHCollisionState::STAY: script->OnTriggerStay(info); break; - case SHCollisionInfo::State::EXIT: + case SHCollisionState::EXIT: script->OnTriggerExit(info); break; } From 265a5bece8aa8ab0804a692550faa2c2697bd730 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Wed, 21 Dec 2022 00:40:01 +0800 Subject: [PATCH 067/275] Slight refactor to fix collision states for very fast moving objects --- Assets/Scenes/PhysicsSandbox.shade | 8 +- .../Physics/Collision/Contacts/SHManifold.h | 26 ++- .../Physics/Collision/Contacts/SHManifold.hpp | 151 ++++++++++++++++++ .../Physics/Collision/SHCollisionSpace.cpp | 30 ++-- .../src/Physics/Dynamics/SHContactManager.cpp | 118 +++++++------- .../src/Physics/Dynamics/SHContactManager.h | 34 ++-- .../src/Physics/Dynamics/SHContactManager.hpp | 4 +- .../src/Physics/Dynamics/SHPhysicsWorld.cpp | 3 - 8 files changed, 283 insertions(+), 91 deletions(-) create mode 100644 SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.hpp diff --git a/Assets/Scenes/PhysicsSandbox.shade b/Assets/Scenes/PhysicsSandbox.shade index 21840aca..d983c22b 100644 --- a/Assets/Scenes/PhysicsSandbox.shade +++ b/Assets/Scenes/PhysicsSandbox.shade @@ -4,8 +4,8 @@ NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 0, y: 3, z: 0} - Rotate: {x: 0, y: 0, z: -0} + Translate: {x: 0, y: 0.0579863191, z: 0} + Rotate: {x: -0, y: 0, z: -0} Scale: {x: 1, y: 1, z: 1} IsActive: true RigidBody Component: @@ -15,7 +15,7 @@ Drag: 1 Angular Drag: 1 Use Gravity: true - Gravity Scale: 1 + Gravity Scale: 10 Interpolate: true Sleeping Enabled: true Freeze Position X: false @@ -48,7 +48,7 @@ NumberOfChildren: 0 Components: Camera Component: - Position: {x: 0, y: 2, z: 3} + Position: {x: 0, y: 0.5, z: 2} Pitch: 0 Yaw: 0 Roll: 0 diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h b/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h index 0534f749..c2b6ddb2 100644 --- a/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h @@ -25,6 +25,23 @@ namespace SHADE struct SH_API SHManifold { public: + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHManifold (SHCollisionShape* a, SHCollisionShape* b) noexcept; + SHManifold (const SHManifold& rhs) noexcept; + SHManifold (SHManifold&& rhs) noexcept; + + ~SHManifold () noexcept = default; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHManifold& operator=(const SHManifold& rhs) noexcept; + SHManifold& operator=(SHManifold&& rhs) noexcept; + /*---------------------------------------------------------------------------------*/ /* Data Members */ /*---------------------------------------------------------------------------------*/ @@ -32,8 +49,8 @@ namespace SHADE // We only need 4 contact points to build a stable manifold. static constexpr int MAX_NUM_CONTACTS = 4; - SHCollisionShape* A = nullptr; - SHCollisionShape* B = nullptr; + SHCollisionShape* A; + SHCollisionShape* B; uint32_t numContacts = 0; SHCollisionState state = SHCollisionState::INVALID; @@ -44,4 +61,7 @@ namespace SHADE }; -} \ No newline at end of file +} // namespace SHADE + +#include "SHManifold.hpp" + diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.hpp b/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.hpp new file mode 100644 index 00000000..f0e1374f --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.hpp @@ -0,0 +1,151 @@ +/**************************************************************************************** + * \file SHManifold.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Collision Manifold + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Primary Header +#include "SHManifold.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + inline SHManifold::SHManifold(SHCollisionShape* a, SHCollisionShape* b) noexcept + : A { a } + , B { b } + {} + + inline SHManifold::SHManifold(const SHManifold& rhs) noexcept + : A { rhs.A } + , B { rhs.B } + , numContacts { rhs.numContacts } + , state { rhs.state } + , normal { rhs.normal } + { + for (int i = 0; i < SHContact::NUM_TANGENTS; ++i) + tangents[i] = rhs.tangents[i]; + + for (int i = 0; i < MAX_NUM_CONTACTS; ++i) + { + contacts[i].penetration = rhs.contacts[i].penetration; + contacts[i].bias = rhs.contacts[i].bias; + contacts[i].normalImpulse = rhs.contacts[i].normalImpulse; + contacts[i].normalMass = rhs.contacts[i].normalMass; + + for (int j = 0; j < SHContact::NUM_TANGENTS; ++j) + { + contacts[i].tangentImpulse[j] = rhs.contacts[i].tangentImpulse[j]; + contacts[i].tangentMass[j] = rhs.contacts[i].tangentMass[j]; + } + + contacts[i].position = rhs.contacts[i].position; + contacts[i].featurePair.key = rhs.contacts[i].featurePair.key; + } + } + + inline SHManifold::SHManifold(SHManifold&& rhs) noexcept + : A { rhs.A } + , B { rhs.B } + , numContacts { rhs.numContacts } + , state { rhs.state } + , normal { rhs.normal } + { + for (int i = 0; i < SHContact::NUM_TANGENTS; ++i) + tangents[i] = rhs.tangents[i]; + + for (int i = 0; i < MAX_NUM_CONTACTS; ++i) + { + contacts[i].penetration = rhs.contacts[i].penetration; + contacts[i].bias = rhs.contacts[i].bias; + contacts[i].normalImpulse = rhs.contacts[i].normalImpulse; + contacts[i].normalMass = rhs.contacts[i].normalMass; + + for (int j = 0; j < SHContact::NUM_TANGENTS; ++j) + { + contacts[i].tangentImpulse[j] = rhs.contacts[i].tangentImpulse[j]; + contacts[i].tangentMass[j] = rhs.contacts[i].tangentMass[j]; + } + + contacts[i].position = rhs.contacts[i].position; + contacts[i].featurePair.key = rhs.contacts[i].featurePair.key; + } + } + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + inline SHManifold& SHManifold::operator=(const SHManifold& rhs) noexcept + { + if (this == &rhs) + return *this; + + A = rhs.A; + B = rhs.B; + numContacts = rhs.numContacts; + state = rhs.state; + normal = rhs.normal; + + for (int i = 0; i < SHContact::NUM_TANGENTS; ++i) + tangents[i] = rhs.tangents[i]; + + for (int i = 0; i < MAX_NUM_CONTACTS; ++i) + { + contacts[i].penetration = rhs.contacts[i].penetration; + contacts[i].bias = rhs.contacts[i].bias; + contacts[i].normalImpulse = rhs.contacts[i].normalImpulse; + contacts[i].normalMass = rhs.contacts[i].normalMass; + + for (int j = 0; j < SHContact::NUM_TANGENTS; ++j) + { + contacts[i].tangentImpulse[j] = rhs.contacts[i].tangentImpulse[j]; + contacts[i].tangentMass[j] = rhs.contacts[i].tangentMass[j]; + } + + contacts[i].position = rhs.contacts[i].position; + contacts[i].featurePair.key = rhs.contacts[i].featurePair.key; + } + + return *this; + } + + inline SHManifold& SHManifold::operator=(SHManifold&& rhs) noexcept + { + A = rhs.A; + B = rhs.B; + numContacts = rhs.numContacts; + state = rhs.state; + normal = rhs.normal; + + for (int i = 0; i < SHContact::NUM_TANGENTS; ++i) + tangents[i] = rhs.tangents[i]; + + for (int i = 0; i < MAX_NUM_CONTACTS; ++i) + { + contacts[i].penetration = rhs.contacts[i].penetration; + contacts[i].bias = rhs.contacts[i].bias; + contacts[i].normalImpulse = rhs.contacts[i].normalImpulse; + contacts[i].normalMass = rhs.contacts[i].normalMass; + + for (int j = 0; j < SHContact::NUM_TANGENTS; ++j) + { + contacts[i].tangentImpulse[j] = rhs.contacts[i].tangentImpulse[j]; + contacts[i].tangentMass[j] = rhs.contacts[i].tangentMass[j]; + } + + contacts[i].position = rhs.contacts[i].position; + contacts[i].featurePair.key = rhs.contacts[i].featurePair.key; + } + + return *this; + } +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionSpace.cpp b/SHADE_Engine/src/Physics/Collision/SHCollisionSpace.cpp index bc76d14a..ab4520ee 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollisionSpace.cpp +++ b/SHADE_Engine/src/Physics/Collision/SHCollisionSpace.cpp @@ -102,6 +102,8 @@ namespace SHADE void SHCollisionSpace::DetectCollisions() noexcept { + // TODO: Profile broad-phase and narrow-phase + /* * Broad-phase */ @@ -112,11 +114,14 @@ namespace SHADE // Colliders without bodies are considered to be static bodies // This is specific to this engine because of Unity's stupid convention. const bool IS_IMPLICIT_STATIC = !collider->rigidBody; - const bool IS_EXPLICIT_STATIC = collider->rigidBody->GetType() == SHRigidBody::Type::STATIC; const bool IS_ACTIVE = collider->active; // Skip inactive colliders - if (!IS_ACTIVE || IS_IMPLICIT_STATIC || IS_EXPLICIT_STATIC) + if (!IS_ACTIVE || IS_IMPLICIT_STATIC) + continue; + + const bool IS_EXPLICIT_STATIC = collider->rigidBody->GetType() == SHRigidBody::Type::STATIC; + if (IS_EXPLICIT_STATIC) continue; // All remaining are kinematic or dynamic @@ -149,6 +154,10 @@ namespace SHADE // Clear every frame narrowphaseBatch.clear(); + + // Test all collisions + if (contactManager) + contactManager->Update(); } @@ -198,14 +207,12 @@ namespace SHADE void SHCollisionSpace::collideTriggers(const SHCollisionKey& key, NarrowphasePair& narrowphasePair) const noexcept { - const auto* A = narrowphasePair.A; - const auto* B = narrowphasePair.B; + auto* A = narrowphasePair.A; + auto* B = narrowphasePair.B; - const bool COLLIDING = SHCollisionDispatcher::Collide(*A, *B); - - // Send results to contact manager + // Send to contact manager if (contactManager) - contactManager->AddTrigger(COLLIDING, key); + contactManager->AddTrigger(key, A, B); } void SHCollisionSpace::collideManifolds(const SHCollisionKey& key, NarrowphasePair& narrowphasePair) const noexcept @@ -213,11 +220,8 @@ namespace SHADE auto* A = narrowphasePair.A; auto* B = narrowphasePair.B; - SHManifold newManifold { .A = A, .B = B }; - const bool COLLIDING = SHCollisionDispatcher::Collide(newManifold, *A, *B); - - // Send results to contact manager + // Send to contact manager if (contactManager) - contactManager->AddManifold(COLLIDING, key, newManifold); + contactManager->AddManifold(key, A, B); } } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp index be65128d..0ab20b38 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp @@ -14,6 +14,9 @@ // Primary Header #include "SHContactManager.h" +// Project Headers +#include "Physics/Collision/Narrowphase/SHCollisionDispatch.h" + namespace SHADE { /*-----------------------------------------------------------------------------------*/ @@ -26,8 +29,8 @@ namespace SHADE triggerEvents.clear(); - for (auto& [id, state] : triggers) - triggerEvents.emplace_back(SHTriggerEvent{ id, state }); + for (auto& [id, trigger] : triggers) + triggerEvents.emplace_back(SHTriggerEvent{ id, trigger.state }); return triggerEvents; } @@ -62,92 +65,83 @@ namespace SHADE void SHContactManager::Update() noexcept { - // Clear expired or invalid collisions - for (auto manifold = manifolds.begin(); manifold != manifolds.end();) + // Clear expired or invalid collisions. If not, test collision. + for (auto manifoldPair = manifolds.begin(); manifoldPair != manifolds.end();) { - const auto COLLISION_STATE = manifold->second.state; + // Test collision of every manifold. + SHManifold& oldManifold = manifoldPair->second; + SHManifold newManifold = oldManifold; - const bool IS_EXIT = COLLISION_STATE == SHCollisionState::EXIT; - const bool IS_INVALID = COLLISION_STATE == SHCollisionState::INVALID; + const bool IS_COLLIDING = SHCollisionDispatcher::Collide(newManifold, *newManifold.A, *newManifold.B); - if (IS_EXIT || IS_INVALID) - manifold = manifolds.erase(manifold); - else - ++manifold; + auto& collisionState = newManifold.state; + updateCollisionState(IS_COLLIDING, collisionState); + + const bool IS_INVALID = collisionState == SHCollisionState::INVALID; + if (IS_INVALID) + { + manifoldPair = manifolds.erase(manifoldPair); + continue; + } + + updateManifold(oldManifold, newManifold); + ++manifoldPair; } - // Clear expired or invalid triggers - for (auto trigger = triggers.begin(); trigger != triggers.end();) + // Clear expired or invalid triggers, If not, test collision. + for (auto triggerPair = triggers.begin(); triggerPair != triggers.end();) { - const auto COLLISION_STATE = trigger->second; + // Test collision of every trigger. + Trigger& oldTrigger = triggerPair->second; + Trigger newTrigger = oldTrigger; - const bool IS_EXIT = COLLISION_STATE == SHCollisionState::EXIT; - const bool IS_INVALID = COLLISION_STATE == SHCollisionState::INVALID; + const bool IS_COLLIDING = SHCollisionDispatcher::Collide(*newTrigger.A, *newTrigger.B); - if (IS_EXIT || IS_INVALID) - trigger = triggers.erase(trigger); + auto& collisionState = newTrigger.state; + updateCollisionState(IS_COLLIDING, collisionState); + + const bool IS_INVALID = collisionState == SHCollisionState::INVALID; + if (IS_INVALID) + triggerPair = triggers.erase(triggerPair); else - ++trigger; + ++triggerPair; } } - void SHContactManager::AddTrigger(bool isColliding, const SHCollisionKey& key) noexcept + void SHContactManager::AddTrigger(const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) noexcept { + // If id not found, emplace new trigger. auto trigger = triggers.find(key); - - // If id not found, emplace new object. - // New object is in the invalid state if (trigger == triggers.end()) - trigger = triggers.emplace(key, SHCollisionState::INVALID).first; - - SHCollisionState& state = trigger->second; - updateCollisionState(isColliding, state); - - // If it was a false positive, remove the manifold immediately. - // Remove using iterator as it is on average faster. - if (state == SHCollisionState::INVALID) - trigger = triggers.erase(trigger); + triggers.emplace(key, Trigger{ A, B, SHCollisionState::INVALID }).first; } - void SHContactManager::AddManifold(bool isColliding, const SHCollisionKey& key, const SHManifold& newManifold) noexcept + void SHContactManager::AddManifold(const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) noexcept { - auto manifold = manifolds.find(key); - // If id not found, emplace new manifold + auto manifold = manifolds.find(key); if (manifold == manifolds.end()) - manifold = manifolds.emplace(key, newManifold).first; - else - { - // TODO: Update existing manifolds with new data - } - - SHCollisionState& state = manifold->second.state; - updateCollisionState(isColliding, state); - - // If it was a false positive, remove the manifold immediately. - // Remove using iterator as it is on average faster. - if (state == SHCollisionState::INVALID) - manifold = manifolds.erase(manifold); + manifolds.emplace(key, SHManifold{ A, B }).first; } void SHContactManager::RemoveInvalidatedTrigger(EntityID eid) noexcept { - remove(triggers, eid); + removeInvalidObject(triggers, eid); } void SHContactManager::RemoveInvalidatedTrigger(EntityID eid, uint32_t shapeIndex) noexcept { - remove(triggers, eid, shapeIndex); + removeInvalidObject(triggers, eid, shapeIndex); } void SHContactManager::RemoveInvalidatedManifold(EntityID eid) noexcept { - remove(manifolds, eid); + removeInvalidObject(manifolds, eid); } void SHContactManager::RemoveInvalidatedManifold(EntityID eid, uint32_t shapeIndex) noexcept { - remove(manifolds, eid, shapeIndex); + removeInvalidObject(manifolds, eid, shapeIndex); } /*-----------------------------------------------------------------------------------*/ @@ -164,10 +158,26 @@ namespace SHADE } else { - // New states start at invalid. In false positive, remain unchanged. + // If already exited and still not colliding, the collision has expired. + // Invalid states are removed in the next frame + if (state == SHCollisionState::EXIT) + state = SHCollisionState::INVALID; + // If previously colliding, move to exit. - state = state == SHCollisionState::INVALID ? SHCollisionState::INVALID : SHCollisionState::EXIT; + if (state == SHCollisionState::ENTER || state == SHCollisionState::STAY) + state = SHCollisionState::EXIT; } } + void SHContactManager::updateManifold(SHManifold& oldManifold, SHManifold& newManifold) noexcept + { + oldManifold.state = newManifold.state; + + // Early out since exiting a collision does not require an update beyond updating the state + if (newManifold.state == SHCollisionState::EXIT) + return; + + + } + } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h index 06a03a6a..9e082db1 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h @@ -54,26 +54,35 @@ namespace SHADE /** * @brief - * Removes any invalidated contacts and triggers. + * Removes any invalidated contacts and triggers, then performs narrowphase collision + * detection on existing triggers and manifolds. * @return */ - void Update () noexcept; + void Update () noexcept; - void AddTrigger (bool isColliding, const SHCollisionKey& key) noexcept; - void AddManifold (bool isColliding, const SHCollisionKey& key, const SHManifold& newManifold) noexcept; + void AddTrigger (const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) noexcept; + void AddManifold (const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) noexcept; - void RemoveInvalidatedTrigger (EntityID eid) noexcept; - void RemoveInvalidatedTrigger (EntityID eid, uint32_t shapeIndex) noexcept; - void RemoveInvalidatedManifold (EntityID eid) noexcept; - void RemoveInvalidatedManifold (EntityID eid, uint32_t shapeIndex) noexcept; + void RemoveInvalidatedTrigger (EntityID eid) noexcept; + void RemoveInvalidatedTrigger (EntityID eid, uint32_t shapeIndex) noexcept; + void RemoveInvalidatedManifold (EntityID eid) noexcept; + void RemoveInvalidatedManifold (EntityID eid, uint32_t shapeIndex) noexcept; private: /*---------------------------------------------------------------------------------*/ /* Type Definitions */ /*---------------------------------------------------------------------------------*/ + struct Trigger + { + SHCollisionShape* A = nullptr; + SHCollisionShape* B = nullptr; + + SHCollisionState state = SHCollisionState::INVALID; + }; + using Manifolds = std::unordered_map; - using Triggers = std::unordered_map; + using Triggers = std::unordered_map; /*---------------------------------------------------------------------------------*/ /* Data Members */ @@ -86,14 +95,15 @@ namespace SHADE /* Member Functions */ /*---------------------------------------------------------------------------------*/ - void updateCollisionState (bool isColliding, SHCollisionState& state) noexcept; + static void updateCollisionState (bool isColliding, SHCollisionState& state) noexcept; + static void updateManifold (SHManifold& oldManifold, SHManifold& newManifold) noexcept; // Removal Helpers template - void remove (std::unordered_map& container, EntityID eid); + static void removeInvalidObject (std::unordered_map& container, EntityID eid) noexcept; template - void remove (std::unordered_map& container, EntityID eid, uint32_t shapeIndex); + static void removeInvalidObject (std::unordered_map& container, EntityID eid, uint32_t shapeIndex) noexcept; }; diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.hpp b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.hpp index 1403cd79..04a4b234 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.hpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.hpp @@ -20,7 +20,7 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ template - void SHContactManager::remove(std::unordered_map& container, EntityID eid) + void SHContactManager::removeInvalidObject(std::unordered_map& container, EntityID eid) noexcept { if (container.empty()) return; @@ -40,7 +40,7 @@ namespace SHADE } template - void SHContactManager::remove(std::unordered_map& container, EntityID eid, uint32_t shapeIndex) + void SHContactManager::removeInvalidObject(std::unordered_map& container, EntityID eid, uint32_t shapeIndex) noexcept { if (container.empty()) return; diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp index b5dba378..bb42364d 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp @@ -81,9 +81,6 @@ namespace SHADE void SHPhysicsWorld::Step(float dt) { - // Contact manager to clear expired contacts - contactManager.Update(); - /* * Detect Collisions */ From 33ef5e0d3d1ffbe433993944d1644b3eec6c897b Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Wed, 21 Dec 2022 01:10:28 +0800 Subject: [PATCH 068/275] Implemented accumulated impulses untested --- .../src/Physics/Dynamics/SHContactManager.cpp | 58 ++++++++++++++++--- .../src/Physics/Dynamics/SHContactManager.h | 4 +- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp index 0ab20b38..1159f445 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp @@ -15,6 +15,7 @@ #include "SHContactManager.h" // Project Headers +#include "Math/SHMathHelpers.h" #include "Physics/Collision/Narrowphase/SHCollisionDispatch.h" namespace SHADE @@ -69,12 +70,12 @@ namespace SHADE for (auto manifoldPair = manifolds.begin(); manifoldPair != manifolds.end();) { // Test collision of every manifold. - SHManifold& oldManifold = manifoldPair->second; - SHManifold newManifold = oldManifold; + SHManifold& manifold = manifoldPair->second; + SHManifold oldManifold = manifold; - const bool IS_COLLIDING = SHCollisionDispatcher::Collide(newManifold, *newManifold.A, *newManifold.B); + const bool IS_COLLIDING = SHCollisionDispatcher::Collide(manifold, *manifold.A, *manifold.B); - auto& collisionState = newManifold.state; + auto& collisionState = oldManifold.state; updateCollisionState(IS_COLLIDING, collisionState); const bool IS_INVALID = collisionState == SHCollisionState::INVALID; @@ -84,7 +85,7 @@ namespace SHADE continue; } - updateManifold(oldManifold, newManifold); + updateManifold(manifold, oldManifold); ++manifoldPair; } @@ -169,15 +170,56 @@ namespace SHADE } } - void SHContactManager::updateManifold(SHManifold& oldManifold, SHManifold& newManifold) noexcept + void SHContactManager::updateManifold(SHManifold& manifold, const SHManifold& oldManifold) noexcept { - oldManifold.state = newManifold.state; + manifold.state = oldManifold.state; // Early out since exiting a collision does not require an update beyond updating the state - if (newManifold.state == SHCollisionState::EXIT) + if (manifold.state == SHCollisionState::EXIT) return; + const SHVec3& NORMAL = manifold.normal; + SHVec3& tangent0 = manifold.tangents[0]; + SHVec3& tangent1 = manifold.tangents[1]; + const SHVec3& OLD_TANGENT_0 = oldManifold.tangents[0]; + const SHVec3& OLD_TANGENT_1 = oldManifold.tangents[1]; + + // Compute tangents + if (std::fabs(NORMAL.x) >= SHMath::EULER_CONSTANT) + tangent0 = SHVec3{ NORMAL.y, -NORMAL.x, 0.0f }; + else + tangent0 = SHVec3{ 0.0f, NORMAL.z, -NORMAL.y }; + + tangent0 = SHVec3::Normalise(tangent0); + tangent1 = SHVec3::Cross(NORMAL, tangent0); + + // Accumulate impulses + for (uint32_t i = 0; i < manifold.numContacts; ++i) + { + SHContact& contact = manifold.contacts[i]; + + // Reset impulses + contact.normalImpulse = 0.0f; + contact.tangentImpulse[0] = contact.tangentImpulse[1] = 0.0f; + + for (uint32_t j = 0; j < oldManifold.numContacts; ++j) + { + const SHContact& OLD_CONTACT = oldManifold.contacts[j]; + + if (OLD_CONTACT.featurePair.key != contact.featurePair.key) + continue; + + // If contact features persists, re-project old solution + contact.normalImpulse = OLD_CONTACT.normalImpulse; + + const SHVec3 FRICTION_FORCE = OLD_TANGENT_0 * OLD_CONTACT.tangentImpulse[0] + OLD_TANGENT_1 * OLD_CONTACT.tangentImpulse[1]; + contact.tangentImpulse[0] = SHVec3::Dot(FRICTION_FORCE, tangent0); + contact.tangentImpulse[1] = SHVec3::Dot(FRICTION_FORCE, tangent1); + + break; + } + } } } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h index 9e082db1..ecbf6dd4 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h @@ -95,8 +95,8 @@ namespace SHADE /* Member Functions */ /*---------------------------------------------------------------------------------*/ - static void updateCollisionState (bool isColliding, SHCollisionState& state) noexcept; - static void updateManifold (SHManifold& oldManifold, SHManifold& newManifold) noexcept; + static void updateCollisionState (bool isColliding, SHCollisionState& state) noexcept; + static void updateManifold (SHManifold& manifold, const SHManifold& newManifold) noexcept; // Removal Helpers From 360b362b7b4500052790f2d61ac8b367812ad8c2 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Wed, 21 Dec 2022 16:47:10 +0800 Subject: [PATCH 069/275] Moved command and process execution helpers to SHExecUtilities --- SHADE_Engine/src/Scripting/SHScriptEngine.cpp | 68 +----------- SHADE_Engine/src/Scripting/SHScriptEngine.h | 4 +- .../src/Tools/Utilities/SHExecUtilities.cpp | 103 ++++++++++++++++++ .../src/Tools/Utilities/SHExecUtilities.h | 84 ++++++++++++++ 4 files changed, 191 insertions(+), 68 deletions(-) create mode 100644 SHADE_Engine/src/Tools/Utilities/SHExecUtilities.cpp create mode 100644 SHADE_Engine/src/Tools/Utilities/SHExecUtilities.h diff --git a/SHADE_Engine/src/Scripting/SHScriptEngine.cpp b/SHADE_Engine/src/Scripting/SHScriptEngine.cpp index 76a57242..b4f893d7 100644 --- a/SHADE_Engine/src/Scripting/SHScriptEngine.cpp +++ b/SHADE_Engine/src/Scripting/SHScriptEngine.cpp @@ -30,6 +30,7 @@ of DigiPen Institute of Technology is prohibited. #include "Scene/SHSceneEvents.h" #include "Assets/SHAssetMacros.h" +#include "Tools/Utilities/SHExecUtilities.h" namespace SHADE { @@ -189,11 +190,7 @@ namespace SHADE oss << "[ScriptEngine] Building " << (debug ? " debug " : "") << "Managed Script Assembly (" << MANAGED_SCRIPT_LIB_NAME << ")!"; SHLOG_INFO(oss.str()); oss.str(""); - const bool BUILD_SUCCESS = execProcess - ( - L"C:\\Windows\\system32\\cmd.exe", - L"/K \"" + generateBuildCommand(debug) + L" & exit\"" - ) == 0; + const bool BUILD_SUCCESS = SHExecUtilties::ExecBlockingCommand(generateBuildCommand(debug)) == 0; if (BUILD_SUCCESS) { // Copy to built dll to the working directory and replace @@ -315,7 +312,7 @@ namespace SHADE } // Open it - execProcessNoBlock + SHExecUtilties::ExecProcess ( L"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\Common7\\IDE\\devenv.exe", L"/Edit " + SHStringUtilities::StrToWstr(CSPROJ_PATH) @@ -626,65 +623,6 @@ namespace SHADE } } - DWORD SHScriptEngine::execProcess(const std::wstring& path, const std::wstring& args) - { - PROCESS_INFORMATION procInfo = execProcessNoBlock(path, args); - - // Wait for execution to end - DWORD status; - while (true) - { - const auto EXEC_SUCCESS = GetExitCodeProcess(procInfo.hProcess, &status); - if (!EXEC_SUCCESS) - { - auto err = GetLastError(); - std::ostringstream oss; - oss << "[ScriptEngine] Failed to query process. Error code: " << std::hex << err - << " (" << SHStringUtilities::GetWin32ErrorMessage(err) << ")"; - throw std::runtime_error(oss.str()); - } - - // Break only if process ends - if (status != STILL_ACTIVE) - { - CloseHandle(procInfo.hProcess); - CloseHandle(procInfo.hThread); - return status; - } - } - } - - PROCESS_INFORMATION SHScriptEngine::execProcessNoBlock(const std::wstring& path, const std::wstring& args) - { - STARTUPINFOW startInfo; - PROCESS_INFORMATION procInfo; - ZeroMemory(&startInfo, sizeof(startInfo)); - ZeroMemory(&procInfo, sizeof(procInfo)); - startInfo.cb = sizeof(startInfo); - - std::wstring argsWstr = args; - - // Start Process - const auto SUCCESS = CreateProcess - ( - path.data(), argsWstr.data(), - nullptr, nullptr, false, NULL, nullptr, nullptr, - &startInfo, &procInfo - ); - - // Error Check - if (!SUCCESS) - { - auto err = GetLastError(); - std::ostringstream oss; - oss << "[ScriptEngine] Failed to launch process. Error code: " << std::hex << err - << " (" << SHStringUtilities::GetWin32ErrorMessage(err) << ")"; - throw std::runtime_error(oss.str()); - } - - return procInfo; - } - std::wstring SHScriptEngine::generateBuildCommand(bool debug) { std::wostringstream oss; diff --git a/SHADE_Engine/src/Scripting/SHScriptEngine.h b/SHADE_Engine/src/Scripting/SHScriptEngine.h index 40efa042..d26b360e 100644 --- a/SHADE_Engine/src/Scripting/SHScriptEngine.h +++ b/SHADE_Engine/src/Scripting/SHScriptEngine.h @@ -1,5 +1,5 @@ /************************************************************************************//*! -\file ScriptEngine.h +\file SHScriptEngine.h \author Tng Kah Wei, kahwei.tng, 390009620 \par email: kahwei.tng\@digipen.edu \date Sep 17, 2021 @@ -324,8 +324,6 @@ namespace SHADE /// True if the file exists static bool fileExists(const std::filesystem::path& filePath); static bool copyFile(const std::filesystem::path& from, const std::filesystem::path& to, const std::filesystem::copy_options options) noexcept; - static DWORD execProcess(const std::wstring& path, const std::wstring& args); - static PROCESS_INFORMATION execProcessNoBlock(const std::wstring& path, const std::wstring& args); static std::wstring generateBuildCommand(bool debug); }; } diff --git a/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.cpp b/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.cpp new file mode 100644 index 00000000..c1bae764 --- /dev/null +++ b/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.cpp @@ -0,0 +1,103 @@ +/************************************************************************************//*! +\file SHExecUtilities.cpp +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Dec 21, 2022 +\brief Contains the implementation for SHExecUtilities static class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ +// Precompiled Headers +#include +// Primary Header +#include "SHExecUtilities.h" +// Project Includes +#include "SHStringUtilities.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Process Execution Functions */ + /*-----------------------------------------------------------------------------------*/ + PROCESS_INFORMATION SHExecUtilties::ExecProcess(const std::wstring& path, const std::wstring& args) + { + STARTUPINFOW startInfo; + PROCESS_INFORMATION procInfo; + ZeroMemory(&startInfo, sizeof(startInfo)); + ZeroMemory(&procInfo, sizeof(procInfo)); + startInfo.cb = sizeof(startInfo); + + std::wstring argsWstr = args; + + // Start Process + const auto SUCCESS = CreateProcess + ( + path.data(), argsWstr.data(), + nullptr, nullptr, false, NULL, nullptr, nullptr, + &startInfo, &procInfo + ); + + // Error Check + if (!SUCCESS) + { + auto err = GetLastError(); + std::ostringstream oss; + oss << "[SHExecUtilties] Failed to launch process. Error code: " << std::hex << err + << " (" << SHStringUtilities::GetWin32ErrorMessage(err) << ")"; + throw std::runtime_error(oss.str()); + } + + return procInfo; + } + + DWORD SHExecUtilties::ExecBlockingProcess(const std::wstring& path, const std::wstring& args) + { + PROCESS_INFORMATION procInfo = ExecProcess(path, args); + + // Wait for execution to end + DWORD status; + while (true) + { + const auto EXEC_SUCCESS = GetExitCodeProcess(procInfo.hProcess, &status); + if (!EXEC_SUCCESS) + { + auto err = GetLastError(); + std::ostringstream oss; + oss << "[SHExecUtilties] Failed to query process. Error code: " << std::hex << err + << " (" << SHStringUtilities::GetWin32ErrorMessage(err) << ")"; + throw std::runtime_error(oss.str()); + } + + // Break only if process ends + if (status != STILL_ACTIVE) + { + CloseHandle(procInfo.hProcess); + CloseHandle(procInfo.hThread); + return status; + } + } + } + + /*-----------------------------------------------------------------------------------*/ + /* Command Execution Functions */ + /*-----------------------------------------------------------------------------------*/ + PROCESS_INFORMATION SHExecUtilties::ExecCommand(const std::wstring& command) + { + return ExecProcess + ( + L"C:\\Windows\\system32\\cmd.exe", + L"/K \"" + command + L" & exit\"" + ); + } + + DWORD SHExecUtilties::ExecBlockingCommand(const std::wstring& command) + { + return ExecBlockingProcess + ( + L"C:\\Windows\\system32\\cmd.exe", + L"/K \"" + command + L" & exit\"" + ); + } +} diff --git a/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.h b/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.h new file mode 100644 index 00000000..838cee41 --- /dev/null +++ b/SHADE_Engine/src/Tools/Utilities/SHExecUtilities.h @@ -0,0 +1,84 @@ +/************************************************************************************//*! +\file SHExecUtilties.h +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Dec 21, 2022 +\brief Contains the interface for SHExecUtilities class. + +Copyright (C) 2022 DigiPen Institute of Technology. +Reproduction or disclosure of this file or its contents without the prior written consent +of DigiPen Institute of Technology is prohibited. +*//*************************************************************************************/ + +// STL Includes +#include +// External Dependencies +#include + +namespace SHADE +{ + /// + /// Static class containing functions for executing external processes or commands. + /// + class SH_API SHExecUtilties final + { + public: + /*---------------------------------------------------------------------------------*/ + /* Process Execution Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Executes a process at the specified path with the specified arguments. This call + /// does not wait for the process to finish executing. + /// + /// Path to the processs to start. + /// Arguments to pass to the process. + /// Information about the started process. + /// + /// Thrown if failed to start the process. + /// + static PROCESS_INFORMATION ExecProcess(const std::wstring& path, const std::wstring& args); + /// + /// Executes a process at the specified path with the specified arguments and waits + /// for that process to finish executing. + /// + /// Path to the processs to start. + /// Arguments to pass to the process. + /// Return value of the process. + /// + /// Thrown if failed to start the process. + /// + static DWORD ExecBlockingProcess(const std::wstring& path, const std::wstring& args); + + /*---------------------------------------------------------------------------------*/ + /* Command Execution Functions */ + /*---------------------------------------------------------------------------------*/ + /// + /// Executes a specified command in cmd. + /// This call does not wait for the command to finish executing. + /// + /// Command to execute. + /// + /// Information about the started cmd process that executes the command. + /// + /// + /// Thrown if failed to start the process. + /// + static PROCESS_INFORMATION ExecCommand(const std::wstring& command); + /// + /// Executes a specified command in cmd and waits for that process to finish + /// executing. + /// + /// Command to execute. + /// Return value of the process. + /// + /// Thrown if failed to start the process. + /// + static DWORD ExecBlockingCommand(const std::wstring& command); + + private: + /*-------------------------------------------------------------------------------*/ + /* Constructors/Destructors */ + /*-------------------------------------------------------------------------------*/ + SHExecUtilties() = delete; + }; +} From d109d06764fc653dfe7ca34e1ec1ed96a025285e Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Wed, 21 Dec 2022 18:57:10 +0800 Subject: [PATCH 070/275] Implemented sequential impulses using baumgarte stabilisation There is a bug with masses of static bodies not being properly set --- Assets/Scenes/PhysicsSandbox.shade | 8 +- .../CollisionShapes/SHCollisionShape.cpp | 10 + .../CollisionShapes/SHCollisionShape.h | 3 + .../Collision/Contacts/SHCollisionEvents.h | 2 +- .../Physics/Collision/Contacts/SHContact.h | 30 +- .../Physics/Collision/Contacts/SHManifold.hpp | 80 +---- .../Constraints/SHContactConstraint.h | 57 ++++ .../src/Physics/Dynamics/SHContactManager.cpp | 37 ++- .../src/Physics/Dynamics/SHContactManager.h | 30 +- .../src/Physics/Dynamics/SHContactSolver.cpp | 290 ++++++++++++++++++ .../src/Physics/Dynamics/SHContactSolver.h | 108 +++++++ .../src/Physics/Dynamics/SHPhysicsWorld.cpp | 26 +- .../src/Physics/Dynamics/SHPhysicsWorld.h | 4 +- .../src/Physics/Dynamics/SHRigidBody.cpp | 16 +- .../src/Physics/Dynamics/SHRigidBody.h | 1 + 15 files changed, 599 insertions(+), 103 deletions(-) create mode 100644 SHADE_Engine/src/Physics/Dynamics/Constraints/SHContactConstraint.h create mode 100644 SHADE_Engine/src/Physics/Dynamics/SHContactSolver.cpp create mode 100644 SHADE_Engine/src/Physics/Dynamics/SHContactSolver.h diff --git a/Assets/Scenes/PhysicsSandbox.shade b/Assets/Scenes/PhysicsSandbox.shade index d983c22b..8323a26e 100644 --- a/Assets/Scenes/PhysicsSandbox.shade +++ b/Assets/Scenes/PhysicsSandbox.shade @@ -4,7 +4,7 @@ NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 0, y: 0.0579863191, z: 0} + Translate: {x: 0, y: 3, z: 0} Rotate: {x: -0, y: 0, z: -0} Scale: {x: 1, y: 1, z: 1} IsActive: true @@ -15,7 +15,7 @@ Drag: 1 Angular Drag: 1 Use Gravity: true - Gravity Scale: 10 + Gravity Scale: 1 Interpolate: true Sleeping Enabled: true Freeze Position X: false @@ -48,7 +48,7 @@ NumberOfChildren: 0 Components: Camera Component: - Position: {x: 0, y: 0.5, z: 2} + Position: {x: 0, y: 0.5, z: 3} Pitch: 0 Yaw: 0 Roll: 0 @@ -70,7 +70,7 @@ Scale: {x: 1, y: 1, z: 1} IsActive: true RigidBody Component: - Type: Dynamic + Type: Static Auto Mass: false Mass: 1 Drag: 0.00999999978 diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp index 5dd17d35..d3f26a8b 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp @@ -37,6 +37,16 @@ namespace SHADE /* Getter Function Definitions */ /*-----------------------------------------------------------------------------------*/ + EntityID SHCollisionShape::GetEntityID() const noexcept + { + return id.GetEntityID(); + } + + uint32_t SHCollisionShape::GetIndex() const noexcept + { + return id.GetShapeIndex(); + } + float SHCollisionShape::GetFriction() const noexcept { return material.GetFriction(); diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h index 6e779eea..2cc156e1 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h @@ -75,6 +75,9 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ + [[nodiscard]] EntityID GetEntityID () const noexcept; + [[nodiscard]] uint32_t GetIndex () const noexcept; + // Material Properties // TODO: Remove individual setters once instanced materials are supported diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionEvents.h b/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionEvents.h index db55ca50..3dbb16d0 100644 --- a/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionEvents.h +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHCollisionEvents.h @@ -51,7 +51,7 @@ namespace SHADE public: static constexpr int MAX_NUM_CONTACTS = 4; - SHCollisionKey info; + SHCollisionKey info; SHCollisionState state; SHVec3 normal; SHVec3 contactPoints[MAX_NUM_CONTACTS]; diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHContact.h b/SHADE_Engine/src/Physics/Collision/Contacts/SHContact.h index c70f6e39..15c24ef2 100644 --- a/SHADE_Engine/src/Physics/Collision/Contacts/SHContact.h +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHContact.h @@ -27,16 +27,26 @@ namespace SHADE union SHContactFeatures { public: + /*---------------------------------------------------------------------------------*/ + /* Type Definit */ + /*---------------------------------------------------------------------------------*/ + + enum class Type : uint8_t + { + VERTEX = 0 + , FACE = 1 + }; + /*---------------------------------------------------------------------------------*/ /* Data Members */ /*---------------------------------------------------------------------------------*/ struct { - uint8_t incomingIncident; - uint8_t outgoingIncident; - uint8_t incomingReference; - uint8_t outgoingReference; + uint8_t indexA; + uint8_t indexB; + uint8_t typeA; + uint8_t typeB; }; uint32_t key = 0; @@ -56,13 +66,15 @@ namespace SHADE static constexpr int NUM_TANGENTS = 2; float penetration = 0.0f; - float bias = 0.0f; - float normalImpulse = 0.0f; - float normalMass = 0.0f; - float tangentImpulse[NUM_TANGENTS] = { 0.0f }; - float tangentMass[NUM_TANGENTS] = { 0.0f }; + float bias = 0.0f; // Restitution + Baumguarte factor + float normalImpulse = 0.0f; // Accumulated normal impulse + float normalMass = 0.0f; // Effective mass along the normal + float tangentImpulse[NUM_TANGENTS] = { 0.0f }; // Accumulated tangent impulses + float tangentMass[NUM_TANGENTS] = { 0.0f }; // Effective masses along the tangents SHVec3 position; + SHVec3 rA; // Vector from COM of A to the contact + SHVec3 rB; // Vector from COM of B to the contact SHContactFeatures featurePair; }; } diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.hpp b/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.hpp index f0e1374f..e43b9625 100644 --- a/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.hpp +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.hpp @@ -31,25 +31,11 @@ namespace SHADE , state { rhs.state } , normal { rhs.normal } { + static constexpr size_t SIZE_OF_CONTACTS = sizeof(SHContact) * static_cast(MAX_NUM_CONTACTS); + memcpy_s(contacts, SIZE_OF_CONTACTS, rhs.contacts, SIZE_OF_CONTACTS); + for (int i = 0; i < SHContact::NUM_TANGENTS; ++i) tangents[i] = rhs.tangents[i]; - - for (int i = 0; i < MAX_NUM_CONTACTS; ++i) - { - contacts[i].penetration = rhs.contacts[i].penetration; - contacts[i].bias = rhs.contacts[i].bias; - contacts[i].normalImpulse = rhs.contacts[i].normalImpulse; - contacts[i].normalMass = rhs.contacts[i].normalMass; - - for (int j = 0; j < SHContact::NUM_TANGENTS; ++j) - { - contacts[i].tangentImpulse[j] = rhs.contacts[i].tangentImpulse[j]; - contacts[i].tangentMass[j] = rhs.contacts[i].tangentMass[j]; - } - - contacts[i].position = rhs.contacts[i].position; - contacts[i].featurePair.key = rhs.contacts[i].featurePair.key; - } } inline SHManifold::SHManifold(SHManifold&& rhs) noexcept @@ -59,25 +45,11 @@ namespace SHADE , state { rhs.state } , normal { rhs.normal } { + static constexpr size_t SIZE_OF_CONTACTS = sizeof(SHContact) * static_cast(MAX_NUM_CONTACTS); + memcpy_s(contacts, SIZE_OF_CONTACTS, rhs.contacts, SIZE_OF_CONTACTS); + for (int i = 0; i < SHContact::NUM_TANGENTS; ++i) tangents[i] = rhs.tangents[i]; - - for (int i = 0; i < MAX_NUM_CONTACTS; ++i) - { - contacts[i].penetration = rhs.contacts[i].penetration; - contacts[i].bias = rhs.contacts[i].bias; - contacts[i].normalImpulse = rhs.contacts[i].normalImpulse; - contacts[i].normalMass = rhs.contacts[i].normalMass; - - for (int j = 0; j < SHContact::NUM_TANGENTS; ++j) - { - contacts[i].tangentImpulse[j] = rhs.contacts[i].tangentImpulse[j]; - contacts[i].tangentMass[j] = rhs.contacts[i].tangentMass[j]; - } - - contacts[i].position = rhs.contacts[i].position; - contacts[i].featurePair.key = rhs.contacts[i].featurePair.key; - } } /*-----------------------------------------------------------------------------------*/ @@ -95,26 +67,12 @@ namespace SHADE state = rhs.state; normal = rhs.normal; + static constexpr size_t SIZE_OF_CONTACTS = sizeof(SHContact) * static_cast(MAX_NUM_CONTACTS); + memcpy_s(contacts, SIZE_OF_CONTACTS, rhs.contacts, SIZE_OF_CONTACTS); + for (int i = 0; i < SHContact::NUM_TANGENTS; ++i) tangents[i] = rhs.tangents[i]; - for (int i = 0; i < MAX_NUM_CONTACTS; ++i) - { - contacts[i].penetration = rhs.contacts[i].penetration; - contacts[i].bias = rhs.contacts[i].bias; - contacts[i].normalImpulse = rhs.contacts[i].normalImpulse; - contacts[i].normalMass = rhs.contacts[i].normalMass; - - for (int j = 0; j < SHContact::NUM_TANGENTS; ++j) - { - contacts[i].tangentImpulse[j] = rhs.contacts[i].tangentImpulse[j]; - contacts[i].tangentMass[j] = rhs.contacts[i].tangentMass[j]; - } - - contacts[i].position = rhs.contacts[i].position; - contacts[i].featurePair.key = rhs.contacts[i].featurePair.key; - } - return *this; } @@ -126,26 +84,12 @@ namespace SHADE state = rhs.state; normal = rhs.normal; + static constexpr size_t SIZE_OF_CONTACTS = sizeof(SHContact) * static_cast(MAX_NUM_CONTACTS); + memcpy_s(contacts, SIZE_OF_CONTACTS, rhs.contacts, SIZE_OF_CONTACTS); + for (int i = 0; i < SHContact::NUM_TANGENTS; ++i) tangents[i] = rhs.tangents[i]; - for (int i = 0; i < MAX_NUM_CONTACTS; ++i) - { - contacts[i].penetration = rhs.contacts[i].penetration; - contacts[i].bias = rhs.contacts[i].bias; - contacts[i].normalImpulse = rhs.contacts[i].normalImpulse; - contacts[i].normalMass = rhs.contacts[i].normalMass; - - for (int j = 0; j < SHContact::NUM_TANGENTS; ++j) - { - contacts[i].tangentImpulse[j] = rhs.contacts[i].tangentImpulse[j]; - contacts[i].tangentMass[j] = rhs.contacts[i].tangentMass[j]; - } - - contacts[i].position = rhs.contacts[i].position; - contacts[i].featurePair.key = rhs.contacts[i].featurePair.key; - } - return *this; } } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/Constraints/SHContactConstraint.h b/SHADE_Engine/src/Physics/Dynamics/Constraints/SHContactConstraint.h new file mode 100644 index 00000000..55e195a6 --- /dev/null +++ b/SHADE_Engine/src/Physics/Dynamics/Constraints/SHContactConstraint.h @@ -0,0 +1,57 @@ +/**************************************************************************************** + * \file SHContactConstraint.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Contact Constraint. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "Physics/Collision/Contacts/SHManifold.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + struct SH_API SHContactConstraint + { + public: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + // Use the entity IDs to map resolved constraints back to the bodies + + EntityID idA = MAX_EID; + EntityID idB = MAX_EID; + + uint32_t numContacts = 0; + + // Material Data + + float friction = 0.0f; + float restitution = 0.0f; + + // Mass Data + + float invMassA = 0.0f; + float invMassB = 0.0f; + SHMatrix invInertiaA; + SHMatrix invInertiaB; + SHVec3 centerOfMassA; + SHVec3 centerOfMassB; + + // Collision Data + + SHVec3 normal; + SHVec3 tangents[SHContact::NUM_TANGENTS]; + SHContact contacts[SHManifold::MAX_NUM_CONTACTS]; + }; +} // namespace SHADE + diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp index 1159f445..3ad3c0b3 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp @@ -60,6 +60,25 @@ namespace SHADE return collisionEvents; } + const SHContactManager::ContactPoints& SHContactManager::GetContactPoints() const noexcept + { + static ContactPoints contactPoints; + + contactPoints.clear(); + + for (auto& manifold : manifolds | std::views::values) + { + // Skip exit state manifolds + if (manifold.state == SHCollisionState::EXIT) + continue; + + for (uint32_t i = 0; i < manifold.numContacts; ++i) + contactPoints.emplace_back(manifold.contacts[i].position); + } + + return contactPoints; + } + /*-----------------------------------------------------------------------------------*/ /* Public Member Functions Definitions */ /*-----------------------------------------------------------------------------------*/ @@ -207,17 +226,17 @@ namespace SHADE { const SHContact& OLD_CONTACT = oldManifold.contacts[j]; - if (OLD_CONTACT.featurePair.key != contact.featurePair.key) - continue; + if (OLD_CONTACT.featurePair.key == contact.featurePair.key) + { + // If contact features persists, re-project old solution + contact.normalImpulse = OLD_CONTACT.normalImpulse; - // If contact features persists, re-project old solution - contact.normalImpulse = OLD_CONTACT.normalImpulse; + const SHVec3 FRICTION_FORCE = OLD_TANGENT_0 * OLD_CONTACT.tangentImpulse[0] + OLD_TANGENT_1 * OLD_CONTACT.tangentImpulse[1]; + contact.tangentImpulse[0] = SHVec3::Dot(FRICTION_FORCE, tangent0); + contact.tangentImpulse[1] = SHVec3::Dot(FRICTION_FORCE, tangent1); - const SHVec3 FRICTION_FORCE = OLD_TANGENT_0 * OLD_CONTACT.tangentImpulse[0] + OLD_TANGENT_1 * OLD_CONTACT.tangentImpulse[1]; - contact.tangentImpulse[0] = SHVec3::Dot(FRICTION_FORCE, tangent0); - contact.tangentImpulse[1] = SHVec3::Dot(FRICTION_FORCE, tangent1); - - break; + break; + } } } } diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h index ecbf6dd4..692dc2e8 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h @@ -1,8 +1,7 @@ /**************************************************************************************** * \file SHContactManager.h * \author Diren D Bharwani, diren.dbharwani, 390002520 - * \brief Interface for a Contact Manager that stores collision information and - * resolves contact constraints. + * \brief Interface for a Contact Manager that stores collision information. * * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or * disclosure of this file or its contents without the prior written consent @@ -24,8 +23,19 @@ namespace SHADE /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ + /** + * @brief + * Encapsulates a class that stores collision information. + */ class SH_API SHContactManager { + private: + /*---------------------------------------------------------------------------------*/ + /* Friends */ + /*---------------------------------------------------------------------------------*/ + + friend class SHPhysicsWorld; + public: /*---------------------------------------------------------------------------------*/ /* Type Definitions */ @@ -33,6 +43,7 @@ namespace SHADE using TriggerEvents = std::vector; using CollisionEvents = std::vector; + using ContactPoints = std::vector; /*---------------------------------------------------------------------------------*/ /* Constructors & Destructor */ @@ -42,11 +53,12 @@ namespace SHADE ~SHContactManager () noexcept = default; /*---------------------------------------------------------------------------------*/ - /* Operator Overloads */ + /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - const TriggerEvents& GetTriggerEvents () const noexcept; - const CollisionEvents& GetCollisionEvents () const noexcept; + const TriggerEvents& GetTriggerEvents () const noexcept; + const CollisionEvents& GetCollisionEvents () const noexcept; + const ContactPoints& GetContactPoints () const noexcept; /*---------------------------------------------------------------------------------*/ /* Member Functions */ @@ -58,7 +70,7 @@ namespace SHADE * detection on existing triggers and manifolds. * @return */ - void Update () noexcept; + void Update () noexcept; void AddTrigger (const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) noexcept; void AddManifold (const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) noexcept; @@ -88,15 +100,15 @@ namespace SHADE /* Data Members */ /*---------------------------------------------------------------------------------*/ - Manifolds manifolds; - Triggers triggers; + Manifolds manifolds; + Triggers triggers; /*---------------------------------------------------------------------------------*/ /* Member Functions */ /*---------------------------------------------------------------------------------*/ static void updateCollisionState (bool isColliding, SHCollisionState& state) noexcept; - static void updateManifold (SHManifold& manifold, const SHManifold& newManifold) noexcept; + static void updateManifold (SHManifold& manifold, const SHManifold& oldManifold) noexcept; // Removal Helpers diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.cpp b/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.cpp new file mode 100644 index 00000000..84805c57 --- /dev/null +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.cpp @@ -0,0 +1,290 @@ +/**************************************************************************************** + * \file SHContactSolver.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Contact Solver. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +***************************************************************************************/ + +#include + +// Primary Header +#include "SHContactSolver.h" + +// Project Headers +#include "Math/SHMathHelpers.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Getter Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + const SHContactSolver::VelocityStates& SHContactSolver::GetVelocities() const noexcept + { + return velocityStates; + } + + const SHContactSolver::ContactConstraints& SHContactSolver::GetContantConstraints() const noexcept + { + return contactConstraints; + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Member Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHContactSolver::AddContact(const SHManifold& manifold, const SHRigidBody* rigidBodyA, const SHRigidBody* rigidBodyB) noexcept + { + SHContactConstraint& newConstraint = contactConstraints.emplace_back(SHContactConstraint{}); + + const auto* SHAPE_A = manifold.A; + const auto* SHAPE_B = manifold.B; + + newConstraint.idA = SHAPE_A->GetEntityID(); + newConstraint.idB = SHAPE_B->GetEntityID(); + + // Add velocities if it doesn't already exist + velocityStates.emplace(newConstraint.idA, VelocityState{ rigidBodyA->linearVelocity, rigidBodyB->angularVelocity }); + velocityStates.emplace(newConstraint.idB, VelocityState{ rigidBodyB->linearVelocity, rigidBodyB->angularVelocity }); + + // Mix friction & restitution + const float FRICTION_A = manifold.A->GetFriction(); + const float RESTITUTION_A = manifold.A->GetBounciness(); + + const float FRICTION_B = manifold.B->GetFriction(); + const float RESTITUTION_B = manifold.B->GetBounciness(); + + newConstraint.friction = std::sqrtf(FRICTION_A * FRICTION_B); + newConstraint.restitution = std::max(RESTITUTION_A, RESTITUTION_B); + + // Mass data + + newConstraint.invMassA = rigidBodyA->invMass; + newConstraint.invMassB = rigidBodyB->invMass; + + newConstraint.invInertiaA = rigidBodyA->worldInvInertia; + newConstraint.invInertiaB = rigidBodyB->worldInvInertia; + + newConstraint.centerOfMassA = rigidBodyA->worldCentroid; + newConstraint.centerOfMassB = rigidBodyB->worldCentroid; + + // Collision data + + newConstraint.numContacts = manifold.numContacts; + + newConstraint.normal = manifold.normal; + + static constexpr size_t TANGENTS_SIZE = sizeof(SHVec3) * SHContact::NUM_TANGENTS; + static constexpr size_t CONTACTS_SIZE = sizeof(SHContact) * SHManifold::MAX_NUM_CONTACTS; + + memcpy_s(newConstraint.tangents, TANGENTS_SIZE, manifold.tangents, TANGENTS_SIZE); + memcpy_s(newConstraint.contacts, CONTACTS_SIZE, manifold.contacts, CONTACTS_SIZE); + + // Compute rA & rB for contacts + for (uint32_t i = 0; i < newConstraint.numContacts; ++i) + { + newConstraint.contacts[i].rA = newConstraint.contacts[i].position - newConstraint.centerOfMassA; + newConstraint.contacts[i].rB = newConstraint.contacts[i].position - newConstraint.centerOfMassB; + } + } + + void SHContactSolver::ClearContacts() noexcept + { + contactConstraints.clear(); + } + + void SHContactSolver::SolveContacts(int numIterations, float dt) noexcept + { + preSolve(dt); + + for (int i = 0; i < numIterations; ++i) + solve(); + } + + /*-----------------------------------------------------------------------------------*/ + /* Private Member Functions Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHContactSolver::preSolve(float dt) noexcept + { + const float INV_DT = 1.0f / dt; + + for (auto& constraint : contactConstraints) + { + const float INV_MASS_SUM = constraint.invMassA + constraint.invMassB; + + SHVec3 vA = velocityStates[constraint.idA].linearVelocity; + SHVec3 wA = velocityStates[constraint.idA].angularVelocity; + SHVec3 vB = velocityStates[constraint.idB].linearVelocity; + SHVec3 wB = velocityStates[constraint.idB].angularVelocity; + + for (uint32_t i = 0; i < constraint.numContacts; ++i) + { + SHContact& contact = constraint.contacts[i]; + + // Calculate JM-1JT (Effective mass) + /* + * rXnT * I-1 * rXn: + * + * 1. 3x3 * 3x1 = 3x1 + * 2. 1x3 * 3x1 = 1x1 + * + * First do I-1 * rXn + * | ix 0 0 || x | | ix * x | + * | 0 iy 0 || y | = | iy * y | + * | 0 0 iz || z | | iz * z | + * + * Then dot product the result with rXnT + * | ix * x |[ u v w ] + * | iy * y | = [ ix * x * w + iy * y * v + iz * z * w ] + * | iz * z | + * + * Simplified: + * + * rXnT /dot (I-1 * rXn) + */ + + // Effective mass along Normal + const SHVec3 RA_CROSS_N = SHVec3::Cross(contact.rA, constraint.normal); + const SHVec3 RB_CROSS_N = SHVec3::Cross(contact.rB, constraint.normal); + + contact.normalMass = INV_MASS_SUM; + contact.normalMass += SHVec3::Dot(RA_CROSS_N, constraint.invInertiaA * RA_CROSS_N); + contact.normalMass += SHVec3::Dot(RB_CROSS_N, constraint.invInertiaB * RB_CROSS_N); + + // Invert the normal mass (we want the actual mass, not the inverse mass) + contact.normalMass = contact.normalMass == 0.0f ? 0.0f : 1.0f / contact.normalMass; + + // Effective mass along tangents (same steps as above) + + for (int j = 0; j < SHContact::NUM_TANGENTS; ++j) + { + const SHVec3 RA_CROSS_T = SHVec3::Cross(contact.rA, constraint.tangents[j]); + const SHVec3 RB_CROSS_T = SHVec3::Cross(contact.rB, constraint.tangents[j]); + + contact.tangentMass[j] = INV_MASS_SUM; + contact.tangentMass[j] += SHVec3::Dot(RA_CROSS_T, constraint.invInertiaA * RA_CROSS_T); + contact.tangentMass[j] += SHVec3::Dot(RB_CROSS_T, constraint.invInertiaB * RB_CROSS_T); + + contact.tangentMass[j] = contact.tangentMass[j] == 0.0f ? 0.0f : 1.0f / contact.tangentMass[j]; + } + + // Warm starting + // Compute impulses + SHVec3 impulse = constraint.normal * contact.normalImpulse; + for (int j = 0; j < SHContact::NUM_TANGENTS; ++j) + impulse += constraint.tangents[j] * contact.tangentImpulse[j]; + + // Apply impulses onto velocities + vA -= impulse * constraint.invMassA; + wA -= constraint.invInertiaA * SHVec3::Cross(contact.rA, impulse); + + vB += impulse * constraint.invMassB; + wB += constraint.invInertiaB * SHVec3::Cross(contact.rB, impulse); + + // Calculate bias per contact + /* + * error bias = baumgarte factor / dt * penetration + * restituion bias = restitution * (relative velocity /dot normal) + */ + + const SHVec3 RV_A = vA + SHVec3::Cross(wA, contact.rA); + const SHVec3 RV_B = vB + SHVec3::Cross(wB, contact.rB); + const float RV_N = SHVec3::Dot(RV_B - RV_A, constraint.normal); + + const float ERROR_BIAS = BAUMGARTE_FACTOR * INV_DT * contact.penetration; + const float RESTITUTION_BIAS = -constraint.restitution * RV_N; + + contact.bias = ERROR_BIAS + RESTITUTION_BIAS; + } + + velocityStates[constraint.idA].linearVelocity = vA; + velocityStates[constraint.idA].angularVelocity = wA; + velocityStates[constraint.idB].linearVelocity = vB; + velocityStates[constraint.idB].angularVelocity = wB; + + } + } + + void SHContactSolver::solve() noexcept + { + for (auto& constraint : contactConstraints) + { + SHVec3 vA = velocityStates[constraint.idA].linearVelocity; + SHVec3 wA = velocityStates[constraint.idA].angularVelocity; + SHVec3 vB = velocityStates[constraint.idB].linearVelocity; + SHVec3 wB = velocityStates[constraint.idB].angularVelocity; + + for (uint32_t i = 0; i < constraint.numContacts; ++i) + { + SHContact& contact = constraint.contacts[i]; + + // Compute relative velocity + SHVec3 velocityA = vA + SHVec3::Cross(wA, contact.rA); + SHVec3 velocityB = vB + SHVec3::Cross(wB, contact.rB); + SHVec3 relativeVelocity = velocityB - velocityA; + + // Solve tangent impulse + for (int j = 0; j < SHContact::NUM_TANGENTS; ++j) + { + // Get scalar of relative velocity along tangent + const float VT = SHVec3::Dot(relativeVelocity, constraint.tangents[j]); + + // Compute true tangent impulse + const float MAX_TANGENT_IMPULSE = constraint.friction * contact.normalImpulse; + const float OLD_TANGENT_IMPULSE = contact.tangentImpulse[j]; + + // We cannot exceed the maximum frictional force (coulumb's law) + // Compute true tangent impulse + float newTangentImpulse = -VT * contact.tangentMass[j]; + contact.tangentImpulse[j] = std::clamp(OLD_TANGENT_IMPULSE + newTangentImpulse, -MAX_TANGENT_IMPULSE, MAX_TANGENT_IMPULSE); + newTangentImpulse = contact.tangentImpulse[j] - OLD_TANGENT_IMPULSE; + + const SHVec3 TANGENT_IMPULSE = newTangentImpulse * constraint.tangents[j]; + + // Apply impulses + vA -= TANGENT_IMPULSE * constraint.invMassA; + wA -= constraint.invInertiaA * SHVec3::Cross(contact.rA, TANGENT_IMPULSE); + + vB += TANGENT_IMPULSE * constraint.invMassB; + wB += constraint.invInertiaB * SHVec3::Cross(contact.rB, TANGENT_IMPULSE); + } + + // Solve normal impulse + // Re-compute relative velocity + velocityA = vA + SHVec3::Cross(wA, contact.rA); + velocityB = vB + SHVec3::Cross(wB, contact.rB); + relativeVelocity = velocityB - velocityA; + + // Get scalar of relative velocity along the normal + const float VN = SHVec3::Dot(relativeVelocity, constraint.normal); + + // Compute true normal impulse + const float OLD_NORMAL_IMPULSE = contact.normalImpulse; + + float newNormalImpulse = -(VN + contact.bias) * contact.normalMass; + contact.normalImpulse = std::max(OLD_NORMAL_IMPULSE + newNormalImpulse, 0.0f); + newNormalImpulse = contact.normalImpulse - OLD_NORMAL_IMPULSE; + + const SHVec3 NORMAL_IMPULSE = newNormalImpulse * constraint.normal; + + // Apply impulses + vA -= NORMAL_IMPULSE * constraint.invMassA; + wA -= constraint.invInertiaA * SHVec3::Cross(contact.rA, NORMAL_IMPULSE); + + vB += NORMAL_IMPULSE * constraint.invMassB; + wB += constraint.invInertiaB * SHVec3::Cross(contact.rB, NORMAL_IMPULSE); + } + + velocityStates[constraint.idA].linearVelocity = vA; + velocityStates[constraint.idA].angularVelocity = wA; + velocityStates[constraint.idB].linearVelocity = vB; + velocityStates[constraint.idB].angularVelocity = wB; + } + } + + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.h b/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.h new file mode 100644 index 00000000..28d5de6f --- /dev/null +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.h @@ -0,0 +1,108 @@ +/**************************************************************************************** + * \file SHContactSolver.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Contact Solver that builds contacct constraints and solves + * them. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "Constraints/SHContactConstraint.h" +#include "SHContactManager.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + /** + * @brief + * Encapsulates an object that builds contact constraints and solves them. + */ + class SH_API SHContactSolver + { + public: + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + + struct VelocityState + { + // Velocities + + SHVec3 linearVelocity; + SHVec3 angularVelocity; + }; + + using VelocityStates = std::unordered_map; + using ContactConstraints = std::vector; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHContactSolver () noexcept = default; + ~SHContactSolver () noexcept = default; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] const VelocityStates& GetVelocities () const noexcept; + [[nodiscard]] const ContactConstraints& GetContantConstraints () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + /** + * @brief + * Build a contact constraint from a new manifold. + * @param manifold + * A manifold to build a contact constraint from. + * @param rigidBodyA + * The rigid body belonging to the first collision shape. + * @param rigidBodyB + * The rigid body belonging to the second collision shape. + */ + void AddContact (const SHManifold& manifold, const SHRigidBody* rigidBodyA, const SHRigidBody* rigidBodyB) noexcept; + + void ClearContacts () noexcept; + + /** + * @brief + * Solves all the contact constraints. + * @param numIterations + * The number of times to iterate over constraints when solving them. + * @param dt + * The delta time of the simulation step. + */ + void SolveContacts (int numIterations, float dt) noexcept; + + private: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + static constexpr float BAUMGARTE_FACTOR = 0.2f; + static constexpr float PENETRATION_SLOP = 0.05f; + + VelocityStates velocityStates; + ContactConstraints contactConstraints; + + /*---------------------------------------------------------------------------------*/ + /* Member Functions */ + /*---------------------------------------------------------------------------------*/ + + void preSolve (float dt) noexcept; + void solve () noexcept; + + }; + +} // namespace SHADE diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp index bb42364d..cb453d24 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp @@ -103,9 +103,33 @@ namespace SHADE /* - * TODO: Resolve Contacts + * Resolve Contacts */ + // Build constraints + for (auto& [id, manifold] : contactManager.manifolds) + { + SHRigidBody* bodyA = rigidBodies[id.GetEntityA()]; + SHRigidBody* bodyB = rigidBodies[id.GetEntityB()]; + + contactSolver.AddContact(manifold, bodyA, bodyB); + } + + // Solve contacts + contactSolver.SolveContacts(settings.numVelocitySolverIterations, dt); + + // Map velocities back to bodies + const auto& VELOCITY_STATES = contactSolver.GetVelocities(); + for (auto& [id, velocityState] : VELOCITY_STATES) + { + SHRigidBody* body = rigidBodies[id]; + body->linearVelocity = velocityState.linearVelocity; + body->angularVelocity = velocityState.angularVelocity; + } + + // Clear contacts + contactSolver.ClearContacts(); + /* * Integrate Velocities */ diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h index 8248299a..9bc2199e 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h @@ -15,6 +15,7 @@ // Project Headers #include "Physics/Collision/SHCollisionSpace.h" #include "SHContactManager.h" +#include "SHContactSolver.h" #include "SHRigidBody.h" @@ -46,7 +47,7 @@ namespace SHADE SHVec3 gravity = SHVec3{ 0.0f, -9.81f, 0.0f }; uint16_t numVelocitySolverIterations = 10; - uint16_t numPositionSolverIterations = 5; + uint16_t numPositionSolverIterations = 5; // Unused until PGS is implemented bool sleepingEnabled = true; }; @@ -128,6 +129,7 @@ namespace SHADE RigidBodies rigidBodies; SHContactManager contactManager; + SHContactSolver contactSolver; /*---------------------------------------------------------------------------------*/ /* Function Members */ diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp index aa8027c7..542b6082 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp @@ -296,7 +296,18 @@ namespace SHADE return; bodyType = newType; - invMass = newType == Type::DYNAMIC ? 1.0f : 0.0f; + + if (bodyType != Type::DYNAMIC) + { + invMass = 0.0f; + localInvInertia.m[0][0] = localInvInertia.m[1][1] = localInvInertia.m[2][2] = 0.0f; + worldInvInertia.m[0][0] = worldInvInertia.m[1][1] = worldInvInertia.m[2][2] = 0.0f; + } + else + { + invMass = 1.0f; + localInvInertia = SHMatrix::Identity; + } } void SHRigidBody::SetGravityScale(float newGravityScale) noexcept @@ -598,6 +609,9 @@ namespace SHADE void SHRigidBody::ComputeWorldData() noexcept { + if (bodyType == Type::STATIC) + return; + const SHMatrix ROTATION = SHMatrix::Rotate(motionState.orientation); // Compute world inertia diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h index ab30bbcf..606895ee 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h @@ -40,6 +40,7 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ friend class SHPhysicsWorld; + friend class SHContactSolver; public: /*-----------------------------------------------------------------------------------*/ From 92ed8a29ffaa97895631f3b787df635b357073b3 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Wed, 21 Dec 2022 19:04:10 +0800 Subject: [PATCH 071/275] Fixed bug with non-dynamic masses being overriden --- Assets/Scenes/PhysicsSandbox.shade | 2 +- SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp | 1 + SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Assets/Scenes/PhysicsSandbox.shade b/Assets/Scenes/PhysicsSandbox.shade index 8323a26e..f2a5dd95 100644 --- a/Assets/Scenes/PhysicsSandbox.shade +++ b/Assets/Scenes/PhysicsSandbox.shade @@ -72,7 +72,7 @@ RigidBody Component: Type: Static Auto Mass: false - Mass: 1 + Mass: .inf Drag: 0.00999999978 Angular Drag: 0.00999999978 Use Gravity: false diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp index 542b6082..b724321b 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp @@ -319,6 +319,7 @@ namespace SHADE { if (bodyType != Type::DYNAMIC) { + invMass = 0.0f; SHLOG_WARNING("Cannot set mass of a non-Dynamic Body {}", entityID) return; } diff --git a/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp index 1d1c06bf..04bb5b6b 100644 --- a/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp +++ b/SHADE_Engine/src/Physics/Interface/SHRigidBodyComponent.cpp @@ -20,7 +20,7 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ SHRigidBodyComponent::SHRigidBodyComponent() noexcept - : type { Type::STATIC } + : type { Type::DYNAMIC } , interpolate { true } , rigidBody { nullptr } {} From f4f6cb7eae178a333730ab6fed7f461b94c57829 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Thu, 22 Dec 2022 01:10:25 +0800 Subject: [PATCH 072/275] Fixed sequential impulses --- Assets/Scenes/PhysicsSandbox.shade | 2 +- SHADE_Engine/src/Math/Geometry/SHSphere.cpp | 2 +- .../CollisionShapes/SHCollisionShape.cpp | 7 +- .../CollisionShapes/SHCollisionShape.h | 21 ++-- .../SHSphereCollisionShape.cpp | 20 ++-- .../Physics/Collision/Contacts/SHContact.h | 2 +- .../Physics/Collision/Contacts/SHManifold.h | 7 +- .../Physics/Collision/Contacts/SHManifold.hpp | 33 ++++-- .../src/Physics/Collision/SHCollider.cpp | 2 +- .../src/Physics/Collision/SHCollider.h | 3 +- .../Constraints/SHContactConstraint.h | 7 +- .../src/Physics/Dynamics/SHContactManager.cpp | 110 ++++++++++++------ .../src/Physics/Dynamics/SHContactManager.h | 25 ++-- .../src/Physics/Dynamics/SHContactSolver.cpp | 87 +++++++------- .../src/Physics/Dynamics/SHContactSolver.h | 11 +- .../src/Physics/Dynamics/SHPhysicsWorld.cpp | 25 +--- 16 files changed, 201 insertions(+), 163 deletions(-) diff --git a/Assets/Scenes/PhysicsSandbox.shade b/Assets/Scenes/PhysicsSandbox.shade index f2a5dd95..90cba8d0 100644 --- a/Assets/Scenes/PhysicsSandbox.shade +++ b/Assets/Scenes/PhysicsSandbox.shade @@ -4,7 +4,7 @@ NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 0, y: 3, z: 0} + Translate: {x: 0.186280191, y: 4.3224473, z: 0} Rotate: {x: -0, y: 0, z: -0} Scale: {x: 1, y: 1, z: 1} IsActive: true diff --git a/SHADE_Engine/src/Math/Geometry/SHSphere.cpp b/SHADE_Engine/src/Math/Geometry/SHSphere.cpp index 7dca00c0..7dbc8f41 100644 --- a/SHADE_Engine/src/Math/Geometry/SHSphere.cpp +++ b/SHADE_Engine/src/Math/Geometry/SHSphere.cpp @@ -115,7 +115,7 @@ namespace SHADE bool SHSphere::Contains(const SHSphere& rhs) const noexcept { - return BoundingSphere::Contains(rhs); + return BoundingSphere::Contains(rhs) == CONTAINS; } float SHSphere::Volume() const noexcept diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp index d3f26a8b..3696dd50 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.cpp @@ -27,7 +27,7 @@ namespace SHADE SHCollisionShape::SHCollisionShape(SHCollisionShapeID id, Type colliderType) : id { id } , flags { 0 } - , parentTransform { nullptr } + , collider { nullptr } , collisionTag { SHCollisionTagMatrix::GetTag(0) } { flags |= 1U << SHUtilities::ConvertEnum(colliderType); @@ -135,11 +135,6 @@ namespace SHADE material = newMaterial; } - void SHCollisionShape::SetParentTransform(SHTransform& parentTF) noexcept - { - parentTransform = &parentTF; - } - void SHCollisionShape::SetPositionOffset(const SHVec3& posOffset) noexcept { transform.position = posOffset; diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h index 2cc156e1..0690b071 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h @@ -22,6 +22,12 @@ namespace SHADE { + /*-----------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*-----------------------------------------------------------------------------------*/ + + class SHRigidBody; + /*-----------------------------------------------------------------------------------*/ /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ @@ -34,10 +40,11 @@ namespace SHADE /* Friends */ /*---------------------------------------------------------------------------------*/ - friend class SHCollider; - friend class SHColliderComponent; - friend class SHCollisionShapeFactory; - friend class SHCollisionSpace; + friend class SHCollider; + friend class SHColliderComponent; + friend class SHCollisionShapeFactory; + friend class SHCollisionSpace; + friend struct SHManifold; public: /*---------------------------------------------------------------------------------*/ @@ -108,8 +115,6 @@ namespace SHADE void SetDensity (float density) noexcept; void SetMaterial (const SHPhysicsMaterial& newMaterial) noexcept; - void SetParentTransform (SHTransform& parentTF) noexcept; - void SetPositionOffset (const SHVec3& posOffset) noexcept; void SetRotationOffset (const SHVec3& rotOffset) noexcept; @@ -137,13 +142,13 @@ namespace SHADE SHPhysicsMaterial material; - SHTransform* parentTransform; + SHCollider* collider; // The collider it belongs to. SHTransform transform; // Needed for conversion to euler angles SHVec3 rotationOffset; - uint8_t flags; // 0 0 0 isColliding trigger capsule sphere box + uint8_t flags; // 0 0 0 isColliding trigger capsule sphere box SHCollisionTag* collisionTag; RTTR_ENABLE() diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp index b1110489..30497f36 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp @@ -16,6 +16,7 @@ // Project Headers #include "Math/SHMathHelpers.h" #include "Math/SHMatrix.h" +#include "Physics/Collision/SHCollider.h" namespace SHADE { @@ -38,7 +39,7 @@ namespace SHADE { material = rhs.material; - parentTransform = rhs.parentTransform; + collider = rhs.collider; transform = rhs.transform; rotationOffset = rhs.rotationOffset; flags = rhs.flags; @@ -54,7 +55,7 @@ namespace SHADE { material = rhs.material; - parentTransform = rhs.parentTransform; + collider = rhs.collider; transform = rhs.transform; rotationOffset = rhs.rotationOffset; flags = rhs.flags; @@ -75,7 +76,7 @@ namespace SHADE id = rhs.id; material = rhs.material; - parentTransform = rhs.parentTransform; + collider = rhs.collider; transform = rhs.transform; rotationOffset = rhs.rotationOffset; flags = rhs.flags; @@ -101,7 +102,7 @@ namespace SHADE id = rhs.id; material = rhs.material; - parentTransform = rhs.parentTransform; + collider = rhs.collider; transform = rhs.transform; rotationOffset = rhs.rotationOffset; flags = rhs.flags; @@ -179,12 +180,14 @@ namespace SHADE void SHSphereCollisionShape::ComputeTransforms() noexcept { - const float SPHERE_SCALE = std::fabs(SHMath::Max({ parentTransform->scale.x, parentTransform->scale.y, parentTransform->scale.z })); + const SHTransform& PARENT_TRANSFORM = collider->GetTransform(); + + const float SPHERE_SCALE = std::fabs(SHMath::Max({ PARENT_TRANSFORM.scale.x, PARENT_TRANSFORM.scale.y, PARENT_TRANSFORM.scale.z })); SetScale(SPHERE_SCALE); // Recompute center - const SHQuaternion FINAL_ROT = parentTransform->orientation * transform.orientation; - const SHMatrix TRS = SHMatrix::Rotate(FINAL_ROT) * SHMatrix::Translate(parentTransform->position); + const SHQuaternion FINAL_ROT = PARENT_TRANSFORM.orientation * transform.orientation; + const SHMatrix TRS = SHMatrix::Rotate(FINAL_ROT) * SHMatrix::Translate(PARENT_TRANSFORM.position); Center = SHVec3::Transform(transform.position, TRS); } @@ -212,7 +215,8 @@ namespace SHADE SHMatrix SHSphereCollisionShape::ComputeWorldTransform() const noexcept { - const SHQuaternion ROTATION = parentTransform->orientation * transform.orientation; + const SHTransform& PARENT_TRANSFORM = collider->GetTransform(); + const SHQuaternion ROTATION = PARENT_TRANSFORM.orientation * transform.orientation; const SHVec3 SCALE{ Radius }; return SHMatrix::Transform diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHContact.h b/SHADE_Engine/src/Physics/Collision/Contacts/SHContact.h index 15c24ef2..0c137f2f 100644 --- a/SHADE_Engine/src/Physics/Collision/Contacts/SHContact.h +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHContact.h @@ -49,7 +49,7 @@ namespace SHADE uint8_t typeB; }; - uint32_t key = 0; + uint32_t key = std::numeric_limits::max(); }; /** diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h b/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h index c2b6ddb2..15741276 100644 --- a/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h @@ -49,8 +49,11 @@ namespace SHADE // We only need 4 contact points to build a stable manifold. static constexpr int MAX_NUM_CONTACTS = 4; - SHCollisionShape* A; - SHCollisionShape* B; + SHCollisionShape* shapeA; + SHCollisionShape* shapeB; + + SHRigidBody* bodyA = nullptr; + SHRigidBody* bodyB = nullptr; uint32_t numContacts = 0; SHCollisionState state = SHCollisionState::INVALID; diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.hpp b/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.hpp index e43b9625..23be8c79 100644 --- a/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.hpp +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.hpp @@ -20,13 +20,18 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ inline SHManifold::SHManifold(SHCollisionShape* a, SHCollisionShape* b) noexcept - : A { a } - , B { b } - {} + : shapeA { a } + , shapeB { b } + { + bodyA = shapeA->collider->rigidBody; + bodyB = shapeB->collider->rigidBody; + } inline SHManifold::SHManifold(const SHManifold& rhs) noexcept - : A { rhs.A } - , B { rhs.B } + : shapeA { rhs.shapeA } + , shapeB { rhs.shapeB } + , bodyA { rhs.bodyA } + , bodyB { rhs.bodyB } , numContacts { rhs.numContacts } , state { rhs.state } , normal { rhs.normal } @@ -39,8 +44,10 @@ namespace SHADE } inline SHManifold::SHManifold(SHManifold&& rhs) noexcept - : A { rhs.A } - , B { rhs.B } + : shapeA { rhs.shapeA } + , shapeB { rhs.shapeB } + , bodyA { rhs.bodyA } + , bodyB { rhs.bodyB } , numContacts { rhs.numContacts } , state { rhs.state } , normal { rhs.normal } @@ -61,8 +68,10 @@ namespace SHADE if (this == &rhs) return *this; - A = rhs.A; - B = rhs.B; + shapeA = rhs.shapeA; + shapeB = rhs.shapeB; + bodyA = rhs.bodyA; + bodyB = rhs.bodyB; numContacts = rhs.numContacts; state = rhs.state; normal = rhs.normal; @@ -78,8 +87,10 @@ namespace SHADE inline SHManifold& SHManifold::operator=(SHManifold&& rhs) noexcept { - A = rhs.A; - B = rhs.B; + shapeA = rhs.shapeA; + shapeB = rhs.shapeB; + bodyA = rhs.bodyA; + bodyB = rhs.bodyB; numContacts = rhs.numContacts; state = rhs.state; normal = rhs.normal; diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp index 928945c9..7d7d1a60 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp @@ -307,7 +307,7 @@ namespace SHADE SHSphereCollisionShape* sphere = shapeFactory->CreateSphere(NEW_SHAPE_ID, SPHERE_CREATE_INFO); // Set offsets - sphere->SetParentTransform(transform); + sphere->collider = this; sphere->SetPositionOffset(posOffset); sphere->SetRotationOffset(rotOffset); diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.h b/SHADE_Engine/src/Physics/Collision/SHCollider.h index c31be5eb..51b33cfb 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.h +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.h @@ -40,7 +40,8 @@ namespace SHADE /* Friends */ /*---------------------------------------------------------------------------------*/ - friend class SHCollisionSpace; + friend class SHCollisionSpace; + friend struct SHManifold; public: /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/Dynamics/Constraints/SHContactConstraint.h b/SHADE_Engine/src/Physics/Dynamics/Constraints/SHContactConstraint.h index 55e195a6..04be00b4 100644 --- a/SHADE_Engine/src/Physics/Dynamics/Constraints/SHContactConstraint.h +++ b/SHADE_Engine/src/Physics/Dynamics/Constraints/SHContactConstraint.h @@ -28,10 +28,7 @@ namespace SHADE // Use the entity IDs to map resolved constraints back to the bodies - EntityID idA = MAX_EID; - EntityID idB = MAX_EID; - - uint32_t numContacts = 0; + SHCollisionKey key; // Material Data @@ -52,6 +49,8 @@ namespace SHADE SHVec3 normal; SHVec3 tangents[SHContact::NUM_TANGENTS]; SHContact contacts[SHManifold::MAX_NUM_CONTACTS]; + + uint32_t numContacts = 0; }; } // namespace SHADE diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp index 3ad3c0b3..3b80b3b3 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp @@ -83,6 +83,42 @@ namespace SHADE /* Public Member Functions Definitions */ /*-----------------------------------------------------------------------------------*/ + void SHContactManager::AddTrigger(const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) noexcept + { + // If id not found, emplace new trigger. + auto trigger = triggers.find(key); + if (trigger == triggers.end()) + triggers.emplace(key, Trigger{ A, B, SHCollisionState::INVALID }); + } + + void SHContactManager::AddManifold(const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) noexcept + { + // If id not found, emplace new manifold + auto manifold = manifolds.find(key); + if (manifold == manifolds.end()) + manifolds.emplace(key, SHManifold{ A, B }); + } + + void SHContactManager::RemoveInvalidatedTrigger(EntityID eid) noexcept + { + removeInvalidObject(triggers, eid); + } + + void SHContactManager::RemoveInvalidatedTrigger(EntityID eid, uint32_t shapeIndex) noexcept + { + removeInvalidObject(triggers, eid, shapeIndex); + } + + void SHContactManager::RemoveInvalidatedManifold(EntityID eid) noexcept + { + removeInvalidObject(manifolds, eid); + } + + void SHContactManager::RemoveInvalidatedManifold(EntityID eid, uint32_t shapeIndex) noexcept + { + removeInvalidObject(manifolds, eid, shapeIndex); + } + void SHContactManager::Update() noexcept { // Clear expired or invalid collisions. If not, test collision. @@ -92,9 +128,9 @@ namespace SHADE SHManifold& manifold = manifoldPair->second; SHManifold oldManifold = manifold; - const bool IS_COLLIDING = SHCollisionDispatcher::Collide(manifold, *manifold.A, *manifold.B); + const bool IS_COLLIDING = SHCollisionDispatcher::Collide(manifold, *manifold.shapeA, *manifold.shapeB); - auto& collisionState = oldManifold.state; + auto& collisionState = manifold.state; updateCollisionState(IS_COLLIDING, collisionState); const bool IS_INVALID = collisionState == SHCollisionState::INVALID; @@ -128,41 +164,45 @@ namespace SHADE } } - void SHContactManager::AddTrigger(const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) noexcept + void SHContactManager::SolveCollisions(int numIterations, float dt) noexcept { - // If id not found, emplace new trigger. - auto trigger = triggers.find(key); - if (trigger == triggers.end()) - triggers.emplace(key, Trigger{ A, B, SHCollisionState::INVALID }).first; + static constexpr int SIZE_CONTACTS = sizeof(SHContact) * SHManifold::MAX_NUM_CONTACTS; + + // Build constraints + for (auto& [key, manifold] : manifolds) + contactSolver.AddContact(key, manifold); + + // Solve contacts + contactSolver.SolveContacts(numIterations, dt); + + // Map impulses back to manifolds + const auto& CONTACT_CONSTRAINTS = contactSolver.GetContantConstraints(); + const auto& VELOCITY_STATES = contactSolver.GetVelocities(); + + for (auto& [key, contactConstraint] : CONTACT_CONSTRAINTS) + { + SHManifold& manifold = manifolds.find(key)->second; + + for (uint32_t i = 0; i < contactConstraint.numContacts; ++i) + manifold.contacts[i] = contactConstraint.contacts[i]; + + // Assign velocities back to the bodies + SHRigidBody* bodyA = manifold.bodyA; + SHRigidBody* bodyB = manifold.bodyB; + + const auto& STATE_A = VELOCITY_STATES.find(key.GetEntityA())->second; + const auto& STATE_B = VELOCITY_STATES.find(key.GetEntityB())->second; + + bodyA->SetLinearVelocity(STATE_A.linearVelocity); + bodyB->SetLinearVelocity(STATE_B.linearVelocity); + + bodyA->SetAngularVelocity(STATE_A.angularVelocity); + bodyB->SetAngularVelocity(STATE_B.angularVelocity); + } + + contactSolver.Reset(); } - void SHContactManager::AddManifold(const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) noexcept - { - // If id not found, emplace new manifold - auto manifold = manifolds.find(key); - if (manifold == manifolds.end()) - manifolds.emplace(key, SHManifold{ A, B }).first; - } - - void SHContactManager::RemoveInvalidatedTrigger(EntityID eid) noexcept - { - removeInvalidObject(triggers, eid); - } - - void SHContactManager::RemoveInvalidatedTrigger(EntityID eid, uint32_t shapeIndex) noexcept - { - removeInvalidObject(triggers, eid, shapeIndex); - } - - void SHContactManager::RemoveInvalidatedManifold(EntityID eid) noexcept - { - removeInvalidObject(manifolds, eid); - } - - void SHContactManager::RemoveInvalidatedManifold(EntityID eid, uint32_t shapeIndex) noexcept - { - removeInvalidObject(manifolds, eid, shapeIndex); - } /*-----------------------------------------------------------------------------------*/ /* Private Member Functions Definitions */ @@ -191,8 +231,6 @@ namespace SHADE void SHContactManager::updateManifold(SHManifold& manifold, const SHManifold& oldManifold) noexcept { - manifold.state = oldManifold.state; - // Early out since exiting a collision does not require an update beyond updating the state if (manifold.state == SHCollisionState::EXIT) return; diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h index 692dc2e8..c2433345 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h @@ -16,6 +16,7 @@ #include "Physics/Collision/Contacts/SHCollisionEvents.h" #include "Physics/Collision/Contacts/SHCollisionKey.h" #include "Physics/Collision/Contacts/SHManifold.h" +#include "SHContactSolver.h" namespace SHADE { @@ -64,14 +65,6 @@ namespace SHADE /* Member Functions */ /*---------------------------------------------------------------------------------*/ - /** - * @brief - * Removes any invalidated contacts and triggers, then performs narrowphase collision - * detection on existing triggers and manifolds. - * @return - */ - void Update () noexcept; - void AddTrigger (const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) noexcept; void AddManifold (const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) noexcept; @@ -80,6 +73,20 @@ namespace SHADE void RemoveInvalidatedManifold (EntityID eid) noexcept; void RemoveInvalidatedManifold (EntityID eid, uint32_t shapeIndex) noexcept; + /** + * @brief + * Removes any invalidated contacts and triggers, then performs narrowphase collision + * detection on existing triggers and manifolds. + */ + void Update () noexcept; + + /** + * @brief + * Builds contact constraints and solves them. Results are stored in the corresponding + * manifolds abiding by the sequential impulse method. + */ + void SolveCollisions (int numIterations, float dt) noexcept; + private: /*---------------------------------------------------------------------------------*/ /* Type Definitions */ @@ -103,6 +110,8 @@ namespace SHADE Manifolds manifolds; Triggers triggers; + SHContactSolver contactSolver; + /*---------------------------------------------------------------------------------*/ /* Member Functions */ /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.cpp b/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.cpp index 84805c57..c14c9eb5 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.cpp @@ -36,40 +36,39 @@ namespace SHADE /* Public Member Functions Definitions */ /*-----------------------------------------------------------------------------------*/ - void SHContactSolver::AddContact(const SHManifold& manifold, const SHRigidBody* rigidBodyA, const SHRigidBody* rigidBodyB) noexcept + void SHContactSolver::AddContact(const SHCollisionKey& key, const SHManifold& manifold) noexcept { - SHContactConstraint& newConstraint = contactConstraints.emplace_back(SHContactConstraint{}); + SHContactConstraint& newConstraint = contactConstraints.emplace(key, SHContactConstraint{}).first->second; - const auto* SHAPE_A = manifold.A; - const auto* SHAPE_B = manifold.B; + const auto* SHAPE_A = manifold.shapeA; + const auto* SHAPE_B = manifold.shapeB; - newConstraint.idA = SHAPE_A->GetEntityID(); - newConstraint.idB = SHAPE_B->GetEntityID(); + const auto* BODY_A = manifold.bodyA; + const auto* BODY_B = manifold.bodyB; // Add velocities if it doesn't already exist - velocityStates.emplace(newConstraint.idA, VelocityState{ rigidBodyA->linearVelocity, rigidBodyB->angularVelocity }); - velocityStates.emplace(newConstraint.idB, VelocityState{ rigidBodyB->linearVelocity, rigidBodyB->angularVelocity }); + velocityStates.emplace(key.GetEntityA(), VelocityState{ BODY_A->linearVelocity, BODY_A->angularVelocity }); + velocityStates.emplace(key.GetEntityB(), VelocityState{ BODY_B->linearVelocity, BODY_B->angularVelocity }); // Mix friction & restitution - const float FRICTION_A = manifold.A->GetFriction(); - const float RESTITUTION_A = manifold.A->GetBounciness(); + const float FRICTION_A = SHAPE_A->GetFriction(); + const float RESTITUTION_A = SHAPE_A->GetBounciness(); - const float FRICTION_B = manifold.B->GetFriction(); - const float RESTITUTION_B = manifold.B->GetBounciness(); + const float FRICTION_B = SHAPE_B->GetFriction(); + const float RESTITUTION_B = SHAPE_B->GetBounciness(); - newConstraint.friction = std::sqrtf(FRICTION_A * FRICTION_B); - newConstraint.restitution = std::max(RESTITUTION_A, RESTITUTION_B); + newConstraint.friction = std::sqrtf(FRICTION_A * FRICTION_B); + newConstraint.restitution = std::max(RESTITUTION_A, RESTITUTION_B); // Mass data - newConstraint.invMassA = rigidBodyA->invMass; - newConstraint.invMassB = rigidBodyB->invMass; + newConstraint.invMassA = BODY_A->invMass; + newConstraint.invInertiaA = BODY_A->worldInvInertia; + newConstraint.centerOfMassA = BODY_A->worldCentroid; - newConstraint.invInertiaA = rigidBodyA->worldInvInertia; - newConstraint.invInertiaB = rigidBodyB->worldInvInertia; - - newConstraint.centerOfMassA = rigidBodyA->worldCentroid; - newConstraint.centerOfMassB = rigidBodyB->worldCentroid; + newConstraint.invMassB = BODY_B->invMass; + newConstraint.invInertiaB = BODY_B->worldInvInertia; + newConstraint.centerOfMassB = BODY_B->worldCentroid; // Collision data @@ -91,8 +90,9 @@ namespace SHADE } } - void SHContactSolver::ClearContacts() noexcept + void SHContactSolver::Reset() noexcept { + velocityStates.clear(); contactConstraints.clear(); } @@ -112,14 +112,14 @@ namespace SHADE { const float INV_DT = 1.0f / dt; - for (auto& constraint : contactConstraints) + for (auto& [key, constraint] : contactConstraints) { const float INV_MASS_SUM = constraint.invMassA + constraint.invMassB; - SHVec3 vA = velocityStates[constraint.idA].linearVelocity; - SHVec3 wA = velocityStates[constraint.idA].angularVelocity; - SHVec3 vB = velocityStates[constraint.idB].linearVelocity; - SHVec3 wB = velocityStates[constraint.idB].angularVelocity; + SHVec3 vA = velocityStates[key.GetEntityA()].linearVelocity; + SHVec3 wA = velocityStates[key.GetEntityA()].angularVelocity; + SHVec3 vB = velocityStates[key.GetEntityB()].linearVelocity; + SHVec3 wB = velocityStates[key.GetEntityB()].angularVelocity; for (uint32_t i = 0; i < constraint.numContacts; ++i) { @@ -156,7 +156,7 @@ namespace SHADE contact.normalMass += SHVec3::Dot(RB_CROSS_N, constraint.invInertiaB * RB_CROSS_N); // Invert the normal mass (we want the actual mass, not the inverse mass) - contact.normalMass = contact.normalMass == 0.0f ? 0.0f : 1.0f / contact.normalMass; + contact.normalMass = 1.0f / contact.normalMass; // Effective mass along tangents (same steps as above) @@ -169,7 +169,7 @@ namespace SHADE contact.tangentMass[j] += SHVec3::Dot(RA_CROSS_T, constraint.invInertiaA * RA_CROSS_T); contact.tangentMass[j] += SHVec3::Dot(RB_CROSS_T, constraint.invInertiaB * RB_CROSS_T); - contact.tangentMass[j] = contact.tangentMass[j] == 0.0f ? 0.0f : 1.0f / contact.tangentMass[j]; + contact.tangentMass[j] = 1.0f / contact.tangentMass[j]; } // Warm starting @@ -195,28 +195,27 @@ namespace SHADE const SHVec3 RV_B = vB + SHVec3::Cross(wB, contact.rB); const float RV_N = SHVec3::Dot(RV_B - RV_A, constraint.normal); - const float ERROR_BIAS = BAUMGARTE_FACTOR * INV_DT * contact.penetration; + const float ERROR_BIAS = BAUMGARTE_FACTOR * INV_DT * std::min(0.0f, -contact.penetration + PENETRATION_SLOP); const float RESTITUTION_BIAS = -constraint.restitution * RV_N; contact.bias = ERROR_BIAS + RESTITUTION_BIAS; } - velocityStates[constraint.idA].linearVelocity = vA; - velocityStates[constraint.idA].angularVelocity = wA; - velocityStates[constraint.idB].linearVelocity = vB; - velocityStates[constraint.idB].angularVelocity = wB; - + velocityStates[key.GetEntityA()].linearVelocity = vA; + velocityStates[key.GetEntityA()].angularVelocity = wA; + velocityStates[key.GetEntityB()].linearVelocity = vB; + velocityStates[key.GetEntityB()].angularVelocity = wB; } } void SHContactSolver::solve() noexcept { - for (auto& constraint : contactConstraints) + for (auto& [key, constraint] : contactConstraints) { - SHVec3 vA = velocityStates[constraint.idA].linearVelocity; - SHVec3 wA = velocityStates[constraint.idA].angularVelocity; - SHVec3 vB = velocityStates[constraint.idB].linearVelocity; - SHVec3 wB = velocityStates[constraint.idB].angularVelocity; + SHVec3 vA = velocityStates[key.GetEntityA()].linearVelocity; + SHVec3 wA = velocityStates[key.GetEntityA()].angularVelocity; + SHVec3 vB = velocityStates[key.GetEntityB()].linearVelocity; + SHVec3 wB = velocityStates[key.GetEntityB()].angularVelocity; for (uint32_t i = 0; i < constraint.numContacts; ++i) { @@ -279,10 +278,10 @@ namespace SHADE wB += constraint.invInertiaB * SHVec3::Cross(contact.rB, NORMAL_IMPULSE); } - velocityStates[constraint.idA].linearVelocity = vA; - velocityStates[constraint.idA].angularVelocity = wA; - velocityStates[constraint.idB].linearVelocity = vB; - velocityStates[constraint.idB].angularVelocity = wB; + velocityStates[key.GetEntityA()].linearVelocity = vA; + velocityStates[key.GetEntityA()].angularVelocity = wA; + velocityStates[key.GetEntityB()].linearVelocity = vB; + velocityStates[key.GetEntityB()].angularVelocity = wB; } } diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.h b/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.h index 28d5de6f..7a4f2d47 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.h @@ -13,7 +13,6 @@ // Project Headers #include "Constraints/SHContactConstraint.h" -#include "SHContactManager.h" namespace SHADE { @@ -41,7 +40,7 @@ namespace SHADE }; using VelocityStates = std::unordered_map; - using ContactConstraints = std::vector; + using ContactConstraints = std::unordered_map; /*---------------------------------------------------------------------------------*/ /* Constructors & Destructor */ @@ -66,14 +65,10 @@ namespace SHADE * Build a contact constraint from a new manifold. * @param manifold * A manifold to build a contact constraint from. - * @param rigidBodyA - * The rigid body belonging to the first collision shape. - * @param rigidBodyB - * The rigid body belonging to the second collision shape. */ - void AddContact (const SHManifold& manifold, const SHRigidBody* rigidBodyA, const SHRigidBody* rigidBodyB) noexcept; + void AddContact (const SHCollisionKey& key, const SHManifold& manifold) noexcept; - void ClearContacts () noexcept; + void Reset () noexcept; /** * @brief diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp index cb453d24..dfa3f482 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp @@ -103,32 +103,11 @@ namespace SHADE /* + * TODO: A lot of this needs to be cleaned up. * Resolve Contacts */ - // Build constraints - for (auto& [id, manifold] : contactManager.manifolds) - { - SHRigidBody* bodyA = rigidBodies[id.GetEntityA()]; - SHRigidBody* bodyB = rigidBodies[id.GetEntityB()]; - - contactSolver.AddContact(manifold, bodyA, bodyB); - } - - // Solve contacts - contactSolver.SolveContacts(settings.numVelocitySolverIterations, dt); - - // Map velocities back to bodies - const auto& VELOCITY_STATES = contactSolver.GetVelocities(); - for (auto& [id, velocityState] : VELOCITY_STATES) - { - SHRigidBody* body = rigidBodies[id]; - body->linearVelocity = velocityState.linearVelocity; - body->angularVelocity = velocityState.angularVelocity; - } - - // Clear contacts - contactSolver.ClearContacts(); + contactManager.SolveCollisions(settings.numVelocitySolverIterations, dt); /* * Integrate Velocities From b667e4df872316bebb69afb1a117f43e8f5db47b Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Thu, 22 Dec 2022 03:11:14 +0800 Subject: [PATCH 073/275] Implemented axis locking constraints --- Assets/Scenes/PhysicsSandbox.shade | 162 +++++++++++------- .../Dynamics/Constraints/SHVelocityState.h | 56 ++++++ .../src/Physics/Dynamics/SHContactManager.cpp | 12 +- .../src/Physics/Dynamics/SHContactSolver.cpp | 94 +++++----- .../src/Physics/Dynamics/SHContactSolver.h | 11 +- .../src/Physics/Dynamics/SHPhysicsWorld.cpp | 25 +-- .../src/Physics/Dynamics/SHRigidBody.cpp | 19 +- .../src/Physics/Dynamics/SHRigidBody.h | 5 +- 8 files changed, 249 insertions(+), 135 deletions(-) create mode 100644 SHADE_Engine/src/Physics/Dynamics/Constraints/SHVelocityState.h diff --git a/Assets/Scenes/PhysicsSandbox.shade b/Assets/Scenes/PhysicsSandbox.shade index 90cba8d0..1438b558 100644 --- a/Assets/Scenes/PhysicsSandbox.shade +++ b/Assets/Scenes/PhysicsSandbox.shade @@ -4,7 +4,7 @@ NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 0.186280191, y: 4.3224473, z: 0} + Translate: {x: 0, y: 3, z: 0} Rotate: {x: -0, y: 0, z: -0} Scale: {x: 1, y: 1, z: 1} IsActive: true @@ -19,6 +19,107 @@ Interpolate: true Sleeping Enabled: true Freeze Position X: false + Freeze Position Y: true + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + DrawColliders: true + Colliders: + - Is Trigger: false + Type: Sphere + Radius: 1 + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: + - Type: PhysicsTestObj + Enabled: true + forceAmount: 50 + torqueAmount: 500 +- EID: 1 + Name: Default + IsActive: true + NumberOfChildren: 0 + Components: + Camera Component: + Position: {x: 0, y: 0.5, z: 5} + Pitch: 0 + Yaw: 0 + Roll: 0 + Width: 1920 + Height: 1080 + Near: 0.00999999978 + Far: 10000 + Perspective: true + IsActive: true + Scripts: ~ +- EID: 2 + Name: Default + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 0, y: -2.5, z: 0} + Rotate: {x: 0, y: 0, z: 0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + RigidBody Component: + Type: Static + Auto Mass: false + Mass: .inf + Drag: 0.00999999978 + Angular Drag: 0.00999999978 + Use Gravity: false + Gravity Scale: 1 + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false + Freeze Position Y: false + Freeze Position Z: false + Freeze Rotation X: false + Freeze Rotation Y: false + Freeze Rotation Z: false + IsActive: true + Collider Component: + DrawColliders: true + Colliders: + - Is Trigger: false + Type: Sphere + Radius: 5 + Friction: 0.400000006 + Bounciness: 0 + Density: 1 + Position Offset: {x: 0, y: 0, z: 0} + Rotation Offset: {x: 0, y: 0, z: 0} + IsActive: true + Scripts: ~ +- EID: 3 + Name: Default + IsActive: true + NumberOfChildren: 0 + Components: + Transform Component: + Translate: {x: 0, y: 5, z: 0} + Rotate: {x: -0, y: 0, z: -0} + Scale: {x: 1, y: 1, z: 1} + IsActive: true + RigidBody Component: + Type: Dynamic + Auto Mass: false + Mass: 1 + Drag: 1 + Angular Drag: 1 + Use Gravity: true + Gravity Scale: 1 + Interpolate: true + Sleeping Enabled: true + Freeze Position X: false Freeze Position Y: false Freeze Position Z: false Freeze Rotation X: false @@ -41,61 +142,4 @@ - Type: PhysicsTestObj Enabled: true forceAmount: 50 - torqueAmount: 500 -- EID: 1 - Name: Default - IsActive: true - NumberOfChildren: 0 - Components: - Camera Component: - Position: {x: 0, y: 0.5, z: 3} - Pitch: 0 - Yaw: 0 - Roll: 0 - Width: 1920 - Height: 1080 - Near: 0.00999999978 - Far: 10000 - Perspective: true - IsActive: true - Scripts: ~ -- EID: 2 - Name: Default - IsActive: true - NumberOfChildren: 0 - Components: - Transform Component: - Translate: {x: 0, y: 1, z: 0} - Rotate: {x: 0, y: 0, z: 0} - Scale: {x: 1, y: 1, z: 1} - IsActive: true - RigidBody Component: - Type: Static - Auto Mass: false - Mass: .inf - Drag: 0.00999999978 - Angular Drag: 0.00999999978 - Use Gravity: false - Gravity Scale: 1 - Interpolate: true - Sleeping Enabled: true - Freeze Position X: false - Freeze Position Y: false - Freeze Position Z: false - Freeze Rotation X: false - Freeze Rotation Y: false - Freeze Rotation Z: false - IsActive: true - Collider Component: - DrawColliders: true - Colliders: - - Is Trigger: false - Type: Sphere - Radius: 1 - Friction: 0.400000006 - Bounciness: 0 - Density: 1 - Position Offset: {x: 0, y: 0, z: 0} - Rotation Offset: {x: 0, y: 0, z: 0} - IsActive: true - Scripts: ~ \ No newline at end of file + torqueAmount: 500 \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/Constraints/SHVelocityState.h b/SHADE_Engine/src/Physics/Dynamics/Constraints/SHVelocityState.h new file mode 100644 index 00000000..6cbb6efb --- /dev/null +++ b/SHADE_Engine/src/Physics/Dynamics/Constraints/SHVelocityState.h @@ -0,0 +1,56 @@ +/**************************************************************************************** + * \file SHVelocityState.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Velocity State for constraint solving. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "Physics/Dynamics/SHRigidBody.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + struct SH_API SHVelocityState + { + public: + SHVec3 LinearVelocity; + SHVec3 AngularVelocity; + + SHVec3 LinearLockFactor; + SHVec3 AngularLockFactor; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHVelocityState (const SHRigidBody* rigidBody) noexcept + { + LinearVelocity = rigidBody->GetLinearVelocity(); + AngularVelocity = rigidBody->GetAngularVelocity(); + + LinearLockFactor = SHVec3 + { + rigidBody->GetFreezePositionX() ? 0.0f : 1.0f + , rigidBody->GetFreezePositionY() ? 0.0f : 1.0f + , rigidBody->GetFreezePositionZ() ? 0.0f : 1.0f + }; + + AngularLockFactor = SHVec3 + { + rigidBody->GetFreezeRotationX() ? 0.0f : 1.0f + , rigidBody->GetFreezeRotationY() ? 0.0f : 1.0f + , rigidBody->GetFreezeRotationZ() ? 0.0f : 1.0f + }; + } + }; + +} // namespace SHADE diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp index 3b80b3b3..83047635 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp @@ -190,14 +190,14 @@ namespace SHADE SHRigidBody* bodyA = manifold.bodyA; SHRigidBody* bodyB = manifold.bodyB; - const auto& STATE_A = VELOCITY_STATES.find(key.GetEntityA())->second; - const auto& STATE_B = VELOCITY_STATES.find(key.GetEntityB())->second; + const auto& STATE_A = VELOCITY_STATES.find(key.GetEntityA())->second; + const auto& STATE_B = VELOCITY_STATES.find(key.GetEntityB())->second; - bodyA->SetLinearVelocity(STATE_A.linearVelocity); - bodyB->SetLinearVelocity(STATE_B.linearVelocity); + bodyA->SetLinearVelocity(STATE_A.LinearVelocity); + bodyB->SetLinearVelocity(STATE_B.LinearVelocity); - bodyA->SetAngularVelocity(STATE_A.angularVelocity); - bodyB->SetAngularVelocity(STATE_B.angularVelocity); + bodyA->SetAngularVelocity(STATE_A.AngularVelocity); + bodyB->SetAngularVelocity(STATE_B.AngularVelocity); } contactSolver.Reset(); diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.cpp b/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.cpp index c14c9eb5..ed4c9aa8 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.cpp @@ -47,8 +47,8 @@ namespace SHADE const auto* BODY_B = manifold.bodyB; // Add velocities if it doesn't already exist - velocityStates.emplace(key.GetEntityA(), VelocityState{ BODY_A->linearVelocity, BODY_A->angularVelocity }); - velocityStates.emplace(key.GetEntityB(), VelocityState{ BODY_B->linearVelocity, BODY_B->angularVelocity }); + velocityStates.emplace(key.GetEntityA(), SHVelocityState{ BODY_A }); + velocityStates.emplace(key.GetEntityB(), SHVelocityState{ BODY_B }); // Mix friction & restitution const float FRICTION_A = SHAPE_A->GetFriction(); @@ -116,10 +116,18 @@ namespace SHADE { const float INV_MASS_SUM = constraint.invMassA + constraint.invMassB; - SHVec3 vA = velocityStates[key.GetEntityA()].linearVelocity; - SHVec3 wA = velocityStates[key.GetEntityA()].angularVelocity; - SHVec3 vB = velocityStates[key.GetEntityB()].linearVelocity; - SHVec3 wB = velocityStates[key.GetEntityB()].angularVelocity; + SHVelocityState& velocityStateA = velocityStates.find(key.GetEntityA())->second; + SHVelocityState& velocityStateB = velocityStates.find(key.GetEntityB())->second; + + SHVec3 vA = velocityStateA.LinearVelocity; + SHVec3 wA = velocityStateA.AngularVelocity; + SHVec3 vB = velocityStateB.LinearVelocity; + SHVec3 wB = velocityStateB.AngularVelocity; + + const SHVec3& LINEAR_LOCK_A = velocityStateA.LinearLockFactor; + const SHVec3& ANGULAR_LOCK_A = velocityStateA.AngularLockFactor; + const SHVec3& LINEAR_LOCK_B = velocityStateB.LinearLockFactor; + const SHVec3& ANGULAR_LOCK_B = velocityStateB.AngularLockFactor; for (uint32_t i = 0; i < constraint.numContacts; ++i) { @@ -172,19 +180,6 @@ namespace SHADE contact.tangentMass[j] = 1.0f / contact.tangentMass[j]; } - // Warm starting - // Compute impulses - SHVec3 impulse = constraint.normal * contact.normalImpulse; - for (int j = 0; j < SHContact::NUM_TANGENTS; ++j) - impulse += constraint.tangents[j] * contact.tangentImpulse[j]; - - // Apply impulses onto velocities - vA -= impulse * constraint.invMassA; - wA -= constraint.invInertiaA * SHVec3::Cross(contact.rA, impulse); - - vB += impulse * constraint.invMassB; - wB += constraint.invInertiaB * SHVec3::Cross(contact.rB, impulse); - // Calculate bias per contact /* * error bias = baumgarte factor / dt * penetration @@ -199,12 +194,25 @@ namespace SHADE const float RESTITUTION_BIAS = -constraint.restitution * RV_N; contact.bias = ERROR_BIAS + RESTITUTION_BIAS; + + // Warm starting + // Compute impulses + SHVec3 impulse = constraint.normal * contact.normalImpulse; + for (int j = 0; j < SHContact::NUM_TANGENTS; ++j) + impulse += constraint.tangents[j] * contact.tangentImpulse[j]; + + // Apply impulses onto velocities + vA -= impulse * constraint.invMassA * LINEAR_LOCK_A; + wA -= constraint.invInertiaA * SHVec3::Cross(contact.rA, impulse) * ANGULAR_LOCK_A; + + vB += impulse * constraint.invMassB * LINEAR_LOCK_B; + wB += constraint.invInertiaB * SHVec3::Cross(contact.rB, impulse) * ANGULAR_LOCK_B; } - velocityStates[key.GetEntityA()].linearVelocity = vA; - velocityStates[key.GetEntityA()].angularVelocity = wA; - velocityStates[key.GetEntityB()].linearVelocity = vB; - velocityStates[key.GetEntityB()].angularVelocity = wB; + velocityStateA.LinearVelocity = vA; + velocityStateA.AngularVelocity = wA; + velocityStateB.LinearVelocity = vB; + velocityStateB.AngularVelocity = wB; } } @@ -212,10 +220,18 @@ namespace SHADE { for (auto& [key, constraint] : contactConstraints) { - SHVec3 vA = velocityStates[key.GetEntityA()].linearVelocity; - SHVec3 wA = velocityStates[key.GetEntityA()].angularVelocity; - SHVec3 vB = velocityStates[key.GetEntityB()].linearVelocity; - SHVec3 wB = velocityStates[key.GetEntityB()].angularVelocity; + SHVelocityState& velocityStateA = velocityStates.find(key.GetEntityA())->second; + SHVelocityState& velocityStateB = velocityStates.find(key.GetEntityB())->second; + + SHVec3 vA = velocityStateA.LinearVelocity; + SHVec3 wA = velocityStateA.AngularVelocity; + SHVec3 vB = velocityStateB.LinearVelocity; + SHVec3 wB = velocityStateB.AngularVelocity; + + const SHVec3& LINEAR_LOCK_A = velocityStateA.LinearLockFactor; + const SHVec3& ANGULAR_LOCK_A = velocityStateA.AngularLockFactor; + const SHVec3& LINEAR_LOCK_B = velocityStateB.LinearLockFactor; + const SHVec3& ANGULAR_LOCK_B = velocityStateB.AngularLockFactor; for (uint32_t i = 0; i < constraint.numContacts; ++i) { @@ -245,11 +261,11 @@ namespace SHADE const SHVec3 TANGENT_IMPULSE = newTangentImpulse * constraint.tangents[j]; // Apply impulses - vA -= TANGENT_IMPULSE * constraint.invMassA; - wA -= constraint.invInertiaA * SHVec3::Cross(contact.rA, TANGENT_IMPULSE); + vA -= TANGENT_IMPULSE * constraint.invMassA * LINEAR_LOCK_A; + wA -= constraint.invInertiaA * SHVec3::Cross(contact.rA, TANGENT_IMPULSE) * ANGULAR_LOCK_A; - vB += TANGENT_IMPULSE * constraint.invMassB; - wB += constraint.invInertiaB * SHVec3::Cross(contact.rB, TANGENT_IMPULSE); + vB += TANGENT_IMPULSE * constraint.invMassB * LINEAR_LOCK_B; + wB += constraint.invInertiaB * SHVec3::Cross(contact.rB, TANGENT_IMPULSE) * ANGULAR_LOCK_B; } // Solve normal impulse @@ -271,17 +287,17 @@ namespace SHADE const SHVec3 NORMAL_IMPULSE = newNormalImpulse * constraint.normal; // Apply impulses - vA -= NORMAL_IMPULSE * constraint.invMassA; - wA -= constraint.invInertiaA * SHVec3::Cross(contact.rA, NORMAL_IMPULSE); + vA -= NORMAL_IMPULSE * constraint.invMassA * LINEAR_LOCK_A; + wA -= constraint.invInertiaA * SHVec3::Cross(contact.rA, NORMAL_IMPULSE) * ANGULAR_LOCK_A; - vB += NORMAL_IMPULSE * constraint.invMassB; - wB += constraint.invInertiaB * SHVec3::Cross(contact.rB, NORMAL_IMPULSE); + vB += NORMAL_IMPULSE * constraint.invMassB * LINEAR_LOCK_B; + wB += constraint.invInertiaB * SHVec3::Cross(contact.rB, NORMAL_IMPULSE) * ANGULAR_LOCK_B; } - velocityStates[key.GetEntityA()].linearVelocity = vA; - velocityStates[key.GetEntityA()].angularVelocity = wA; - velocityStates[key.GetEntityB()].linearVelocity = vB; - velocityStates[key.GetEntityB()].angularVelocity = wB; + velocityStateA.LinearVelocity = vA; + velocityStateA.AngularVelocity = wA; + velocityStateB.LinearVelocity = vB; + velocityStateB.AngularVelocity = wB; } } diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.h b/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.h index 7a4f2d47..56955f74 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactSolver.h @@ -13,6 +13,7 @@ // Project Headers #include "Constraints/SHContactConstraint.h" +#include "Constraints/SHVelocityState.h" namespace SHADE { @@ -31,15 +32,7 @@ namespace SHADE /* Type Definitions */ /*---------------------------------------------------------------------------------*/ - struct VelocityState - { - // Velocities - - SHVec3 linearVelocity; - SHVec3 angularVelocity; - }; - - using VelocityStates = std::unordered_map; + using VelocityStates = std::unordered_map; using ContactConstraints = std::unordered_map; /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp index dfa3f482..8451bd5b 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp @@ -143,29 +143,13 @@ namespace SHADE // Apply drag (exponentially applied) rigidBody.linearVelocity *= 1.0f / (1.0f + dt * rigidBody.linearDrag); rigidBody.angularVelocity *= 1.0f / (1.0f + dt * rigidBody.angularDrag); + + rigidBody.constrainLinearVelocities(); + rigidBody.constrainAngularVelocities(); } void SHPhysicsWorld::integrateVelocities(SHRigidBody& rigidBody, float dt) const noexcept { - static const auto ENFORCE_CONSTRAINED_VELOCITIES = [](SHRigidBody& rigidBody) - { - // Enforce linear constraints - rigidBody.linearVelocity = SHVec3 - { - rigidBody.GetFreezePositionX() ? 0.0f : rigidBody.linearVelocity.x - , rigidBody.GetFreezePositionY() ? 0.0f : rigidBody.linearVelocity.y - , rigidBody.GetFreezePositionZ() ? 0.0f : rigidBody.linearVelocity.z - }; - - // Enforce angular constraints - rigidBody.angularVelocity = SHVec3 - { - rigidBody.GetFreezeRotationX() ? 0.0f : rigidBody.angularVelocity.x - , rigidBody.GetFreezeRotationY() ? 0.0f : rigidBody.angularVelocity.y - , rigidBody.GetFreezeRotationZ() ? 0.0f : rigidBody.angularVelocity.z - }; - }; - // Always reset movement flag rigidBody.motionState.hasMoved = false; @@ -178,7 +162,8 @@ namespace SHADE // Both dynamic and kinematic can sleep when their velocities are under the thresholds. else if (!rigidBody.IsSleeping()) { - ENFORCE_CONSTRAINED_VELOCITIES(rigidBody); + rigidBody.constrainLinearVelocities(); + rigidBody.constrainAngularVelocities(); rigidBody.motionState.IntegratePosition(rigidBody.linearVelocity, dt); rigidBody.motionState.IntegrateOrientation(rigidBody.angularVelocity, dt); diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp index b724321b..3e24fa27 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.cpp @@ -200,6 +200,8 @@ namespace SHADE const SHVec3& SHRigidBody::GetLinearVelocity() const noexcept { + // Check if linear velocity needs to be constrained + return linearVelocity; } @@ -378,6 +380,7 @@ namespace SHADE } linearVelocity = newLinearVelocity; + constrainLinearVelocities(); } void SHRigidBody::SetAngularVelocity(const SHVec3& newAngularVelocity) noexcept @@ -389,6 +392,7 @@ namespace SHADE } angularVelocity = newAngularVelocity; + constrainAngularVelocities(); } void SHRigidBody::SetIsActive(bool isActive) noexcept @@ -676,7 +680,20 @@ namespace SHADE const auto* FIRST_SHAPE = collider->GetCollisionShape(0); localInvInertia = SHMatrix::Inverse(FIRST_SHAPE->GetInertiaTensor(1.0f / invMass)); } - + } + + void SHRigidBody::constrainLinearVelocities() noexcept + { + linearVelocity.x = GetFreezePositionX() ? 0.0f : linearVelocity.x; + linearVelocity.y = GetFreezePositionY() ? 0.0f : linearVelocity.y; + linearVelocity.z = GetFreezePositionZ() ? 0.0f : linearVelocity.z; + } + + void SHRigidBody::constrainAngularVelocities() noexcept + { + angularVelocity.x = GetFreezeRotationX() ? 0.0f : angularVelocity.x; + angularVelocity.y = GetFreezeRotationY() ? 0.0f : angularVelocity.y; + angularVelocity.z = GetFreezeRotationZ() ? 0.0f : angularVelocity.z; } } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h index 606895ee..34424a1c 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHRigidBody.h @@ -249,7 +249,7 @@ namespace SHADE SHVec3 linearVelocity; SHVec3 angularVelocity; - // aZ aY aX pZ pY pX 0 0 0 0 inIsland autoMass enableGravity enableSleeping sleeping active + // aZ aY aX rotLockActive pZ pY pX posLockActive 0 0 inIsland autoMass enableGravity enableSleeping sleeping active uint16_t flags; SHMotionState motionState; @@ -262,6 +262,9 @@ namespace SHADE void computeMass () noexcept; void computeInertiaTensor () noexcept; void computeMassAndInertiaTensor() noexcept; + + void constrainLinearVelocities () noexcept; + void constrainAngularVelocities () noexcept; }; From 861e47812f40b64ad711e02988b04b511520e5af Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Thu, 22 Dec 2022 15:06:52 +0800 Subject: [PATCH 074/275] Fixed bug where StrToWstr and WstrToStr may contain invalid characters from a previous call --- .../src/Tools/Utilities/SHStringUtilities.cpp | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/SHADE_Engine/src/Tools/Utilities/SHStringUtilities.cpp b/SHADE_Engine/src/Tools/Utilities/SHStringUtilities.cpp index b1e4aa92..b9698071 100644 --- a/SHADE_Engine/src/Tools/Utilities/SHStringUtilities.cpp +++ b/SHADE_Engine/src/Tools/Utilities/SHStringUtilities.cpp @@ -26,27 +26,29 @@ namespace SHADE std::vector SHStringUtilities::Split(const std::wstring& str, const wchar_t& delim) { return Split(str, delim); - } - std::string SHStringUtilities::WstrToStr(const std::wstring& wstr) - { - static std::vector buffer; - const int STR_SIZE = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast(wstr.size()), nullptr, 0, nullptr, nullptr) + 1 /* Null Terminator */; - buffer.resize(STR_SIZE); - WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast(wstr.size()), buffer.data(), MAX_PATH, nullptr, nullptr); - return std::string(buffer.data()); - } - std::wstring SHStringUtilities::StrToWstr(const std::string& str) - { - static std::vector buffer; - const int WSTR_SIZE = MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.size()), nullptr, 0) + 1 /* Null Terminator */; - buffer.resize(WSTR_SIZE); - MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.size()), buffer.data(), WSTR_SIZE); - return std::wstring(buffer.data()); - } + } + std::string SHStringUtilities::WstrToStr(const std::wstring& wstr) + { + static std::vector buffer; + buffer.clear(); + const int STR_SIZE = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast(wstr.size()), nullptr, 0, nullptr, nullptr) + 1 /* Null Terminator */; + buffer.resize(STR_SIZE, '\0'); + WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast(wstr.size()), buffer.data(), MAX_PATH, nullptr, nullptr); + return std::string(buffer.data()); + } + std::wstring SHStringUtilities::StrToWstr(const std::string& str) + { + static std::vector buffer; + buffer.clear(); + const int WSTR_SIZE = MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.size()), nullptr, 0) + 1 /* Null Terminator */; + buffer.resize(WSTR_SIZE, '\0'); + MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.size()), buffer.data(), WSTR_SIZE); + return std::wstring(buffer.data()); + } - std::string SHStringUtilities::GetWin32ErrorMessage(unsigned long errorCode) - { - return std::system_category().message(errorCode); - } + std::string SHStringUtilities::GetWin32ErrorMessage(unsigned long errorCode) + { + return std::system_category().message(errorCode); + } } \ No newline at end of file From 22c0a14081e2109de18a977382dd4412cbc38b4e Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Fri, 23 Dec 2022 00:55:36 +0800 Subject: [PATCH 075/275] Renamed SHBox to SHAABB for clarity The future SHBox will represent an OBB --- .../src/Camera/SHCameraArmComponent.h | 2 +- SHADE_Engine/src/Camera/SHCameraSystem.cpp | 2 +- .../Inspector/SHEditorComponentView.hpp | 2 +- .../Math/Geometry/{SHBox.cpp => SHAABB.cpp} | 74 +++++------- .../src/Math/Geometry/{SHBox.h => SHAABB.h} | 114 ++++++++++++++---- SHADE_Engine/src/Math/Geometry/SHSphere.h | 16 +-- .../Broadphase/SHDynamicAABBTree.cpp | 38 +++--- .../Collision/Broadphase/SHDynamicAABBTree.h | 10 +- .../CollisionShapes/SHCollisionShape.h | 4 +- .../SHSphereCollisionShape.cpp | 4 +- .../CollisionShapes/SHSphereCollisionShape.h | 2 +- .../src/Serialization/SHYAMLConverters.h | 5 +- SHADE_Managed/src/Components/Collider.cxx | 20 +-- 13 files changed, 171 insertions(+), 122 deletions(-) rename SHADE_Engine/src/Math/Geometry/{SHBox.cpp => SHAABB.cpp} (71%) rename SHADE_Engine/src/Math/Geometry/{SHBox.h => SHAABB.h} (55%) diff --git a/SHADE_Engine/src/Camera/SHCameraArmComponent.h b/SHADE_Engine/src/Camera/SHCameraArmComponent.h index 9d8ec853..dfae1cd5 100644 --- a/SHADE_Engine/src/Camera/SHCameraArmComponent.h +++ b/SHADE_Engine/src/Camera/SHCameraArmComponent.h @@ -10,7 +10,7 @@ namespace SHADE { - class SHBox; + class SHAABB; class SHRay; class SH_API SHCameraArmComponent final: public SHComponent diff --git a/SHADE_Engine/src/Camera/SHCameraSystem.cpp b/SHADE_Engine/src/Camera/SHCameraSystem.cpp index d94bd3f8..af7d0f98 100644 --- a/SHADE_Engine/src/Camera/SHCameraSystem.cpp +++ b/SHADE_Engine/src/Camera/SHCameraSystem.cpp @@ -10,7 +10,7 @@ #include "Scene/SHSceneManager.h" #include "ECS_Base/Managers/SHSystemManager.h" #include "Editor/SHEditor.h" -#include "Math/Geometry/SHBox.h" +#include "Math/Geometry/SHAABB.h" #include "Math/SHRay.h" #include "Physics/System/SHPhysicsSystem.h" diff --git a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp index 2bbc0305..622a2d37 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp +++ b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp @@ -346,7 +346,7 @@ namespace SHADE { //SHEditorWidgets::BeginPanel(std::format("{} Box #{}", ICON_FA_CUBE, i).data(), { ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y }); // - //const auto* BOX = reinterpret_cast(collider->GetShape()); + //const auto* BOX = reinterpret_cast(collider->GetShape()); //SHEditorWidgets::DragVec3 //( // "Half Extents", { "X", "Y", "Z" }, diff --git a/SHADE_Engine/src/Math/Geometry/SHBox.cpp b/SHADE_Engine/src/Math/Geometry/SHAABB.cpp similarity index 71% rename from SHADE_Engine/src/Math/Geometry/SHBox.cpp rename to SHADE_Engine/src/Math/Geometry/SHAABB.cpp index 05595fe1..8a44457f 100644 --- a/SHADE_Engine/src/Math/Geometry/SHBox.cpp +++ b/SHADE_Engine/src/Math/Geometry/SHAABB.cpp @@ -11,7 +11,7 @@ #include // Primary Header -#include "SHBox.h" +#include "SHAABB.h" // Project Headers #include "Math/SHMathHelpers.h" #include "Math/SHRay.h" @@ -24,14 +24,12 @@ namespace SHADE /* Constructors & Destructor Definitions */ /*-----------------------------------------------------------------------------------*/ - SHBox::SHBox() noexcept - : RelativeExtents { SHVec3::One } + SHAABB::SHAABB() noexcept { type = Type::BOX; } - SHBox::SHBox(const SHVec3& c, const SHVec3& hE) noexcept - : RelativeExtents { SHVec3::One } + SHAABB::SHAABB(const SHVec3& c, const SHVec3& hE) noexcept { type = Type::BOX; @@ -40,7 +38,7 @@ namespace SHADE } - SHBox::SHBox(const SHBox& rhs) noexcept + SHAABB::SHAABB(const SHAABB& rhs) noexcept { if (this == &rhs) return; @@ -49,23 +47,21 @@ namespace SHADE Center = rhs.Center; Extents = rhs.Extents; - RelativeExtents = rhs.RelativeExtents; } - SHBox::SHBox(SHBox&& rhs) noexcept + SHAABB::SHAABB(SHAABB&& rhs) noexcept { type = Type::BOX; Center = rhs.Center; Extents = rhs.Extents; - RelativeExtents = rhs.RelativeExtents; } /*-----------------------------------------------------------------------------------*/ /* Operator Overload Definitions */ /*-----------------------------------------------------------------------------------*/ - SHBox& SHBox::operator=(const SHBox& rhs) noexcept + SHAABB& SHAABB::operator=(const SHAABB& rhs) noexcept { if (rhs.type != Type::BOX) { @@ -75,13 +71,12 @@ namespace SHADE { Center = rhs.Center; Extents = rhs.Extents; - RelativeExtents = rhs.RelativeExtents; } return *this; } - SHBox& SHBox::operator=(SHBox&& rhs) noexcept + SHAABB& SHAABB::operator=(SHAABB&& rhs) noexcept { if (rhs.type != Type::BOX) { @@ -91,7 +86,6 @@ namespace SHADE { Center = rhs.Center; Extents = rhs.Extents; - RelativeExtents = rhs.RelativeExtents; } return *this; @@ -101,27 +95,22 @@ namespace SHADE /* Getter Function Definitions */ /*-----------------------------------------------------------------------------------*/ - SHVec3 SHBox::GetCenter() const noexcept + SHVec3 SHAABB::GetCenter() const noexcept { return Center; } - SHVec3 SHBox::GetWorldExtents() const noexcept + SHVec3 SHAABB::GetWorldExtents() const noexcept { return Extents; } - const SHVec3& SHBox::GetRelativeExtents() const noexcept - { - return RelativeExtents; - } - - SHVec3 SHBox::GetMin() const noexcept + SHVec3 SHAABB::GetMin() const noexcept { return SHVec3{ Center.x - Extents.x, Center.y - Extents.y, Center.z - Extents.z }; } - SHVec3 SHBox::GetMax() const noexcept + SHVec3 SHAABB::GetMax() const noexcept { return SHVec3{ Center.x + Extents.x, Center.y + Extents.y, Center.z + Extents.z }; } @@ -130,22 +119,17 @@ namespace SHADE /* Setter Function Definitions */ /*-----------------------------------------------------------------------------------*/ - void SHBox::SetCenter(const SHVec3& newCenter) noexcept + void SHAABB::SetCenter(const SHVec3& newCenter) noexcept { Center = newCenter; } - void SHBox::SetWorldExtents(const SHVec3& newWorldExtents) noexcept + void SHAABB::SetWorldExtents(const SHVec3& newWorldExtents) noexcept { Extents = newWorldExtents; } - void SHBox::SetRelativeExtents(const SHVec3& newRelativeExtents) noexcept - { - RelativeExtents = newRelativeExtents; - } - - void SHBox::SetMin(const SHVec3& min) noexcept + void SHAABB::SetMin(const SHVec3& min) noexcept { const SHVec3 MAX = GetMax(); @@ -153,7 +137,7 @@ namespace SHADE Extents = SHVec3::Abs((MAX - min) * 0.5f); } - void SHBox::SetMax(const SHVec3& max) noexcept + void SHAABB::SetMax(const SHVec3& max) noexcept { const SHVec3 MIN = GetMin(); @@ -161,13 +145,13 @@ namespace SHADE Extents = SHVec3::Abs((max - MIN) * 0.5f); } - void SHBox::SetMinMax(const SHVec3& min, const SHVec3& max) noexcept + void SHAABB::SetMinMax(const SHVec3& min, const SHVec3& max) noexcept { Center = SHVec3::Lerp(min, max, 0.5f); Extents = SHVec3::Abs((max - min) * 0.5f); } - std::vector SHBox::GetVertices() const noexcept + std::vector SHAABB::GetVertices() const noexcept { std::vector vertices{ 8 }; GetCorners(vertices.data()); @@ -178,12 +162,12 @@ namespace SHADE /* Public Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ - bool SHBox::TestPoint(const SHVec3& point) const noexcept + bool SHAABB::TestPoint(const SHVec3& point) const noexcept { return BoundingBox::Contains(point); } - SHRaycastResult SHBox::Raycast(const SHRay& ray) const noexcept + SHRaycastResult SHAABB::Raycast(const SHRay& ray) const noexcept { SHRaycastResult result; @@ -197,17 +181,17 @@ namespace SHADE return result; } - bool SHBox::Contains(const SHBox& rhs) const noexcept + bool SHAABB::Contains(const SHAABB& rhs) const noexcept { return BoundingBox::Contains(rhs) == CONTAINS; } - float SHBox::Volume() const noexcept + float SHAABB::Volume() const noexcept { return 8.0f * (Extents.x * Extents.y * Extents.z); } - float SHBox::SurfaceArea() const noexcept + float SHAABB::SurfaceArea() const noexcept { return 8.0f * ((Extents.x * Extents.y) + (Extents.x * Extents.z) @@ -218,21 +202,21 @@ namespace SHADE /* Static Function Member Definitions */ /*-----------------------------------------------------------------------------------*/ - SHBox SHBox::Combine(const SHBox& lhs, const SHBox& rhs) noexcept + SHAABB SHAABB::Combine(const SHAABB& lhs, const SHAABB& rhs) noexcept { - SHBox result; + SHAABB result; CreateMerged(result, lhs, rhs); return result; } - bool SHBox::Intersect(const SHBox& lhs, const SHBox& rhs) noexcept + bool SHAABB::Intersect(const SHAABB& lhs, const SHAABB& rhs) noexcept { return lhs.Intersects(rhs); } - SHBox SHBox::BuildFromBoxes(const SHBox* boxes, size_t numBoxes) noexcept + SHAABB SHAABB::BuildFromBoxes(const SHAABB* boxes, size_t numBoxes) noexcept { - SHBox result; + SHAABB result; for (size_t i = 1; i < numBoxes; ++i) CreateMerged(result, boxes[i - 1], boxes[i]); @@ -240,9 +224,9 @@ namespace SHADE return result; } - SHBox SHBox::BuildFromVertices(const SHVec3* vertices, size_t numVertices, size_t stride) noexcept + SHAABB SHAABB::BuildFromVertices(const SHVec3* vertices, size_t numVertices, size_t stride) noexcept { - SHBox result; + SHAABB result; CreateFromPoints(result, numVertices, vertices, stride); return result; } diff --git a/SHADE_Engine/src/Math/Geometry/SHBox.h b/SHADE_Engine/src/Math/Geometry/SHAABB.h similarity index 55% rename from SHADE_Engine/src/Math/Geometry/SHBox.h rename to SHADE_Engine/src/Math/Geometry/SHAABB.h index 63383c00..46608ded 100644 --- a/SHADE_Engine/src/Math/Geometry/SHBox.h +++ b/SHADE_Engine/src/Math/Geometry/SHAABB.h @@ -22,7 +22,7 @@ namespace SHADE /* Type Definitions */ /*-----------------------------------------------------------------------------------*/ - class SH_API SHBox : public SHShape, + class SH_API SHAABB : public SHShape, private DirectX::BoundingBox { public: @@ -36,19 +36,19 @@ namespace SHADE /* Constructors & Destructor */ /*---------------------------------------------------------------------------------*/ - ~SHBox () override = default; + ~SHAABB () override = default; - SHBox () noexcept; - SHBox (const SHVec3& center, const SHVec3& halfExtents) noexcept; - SHBox (const SHBox& rhs) noexcept; - SHBox (SHBox&& rhs) noexcept; + SHAABB () noexcept; + SHAABB (const SHVec3& center, const SHVec3& halfExtents) noexcept; + SHAABB (const SHAABB& rhs) noexcept; + SHAABB (SHAABB&& rhs) noexcept; /*---------------------------------------------------------------------------------*/ /* Operator Overloads */ /*---------------------------------------------------------------------------------*/ - SHBox& operator= (const SHBox& rhs) noexcept; - SHBox& operator= (SHBox&& rhs) noexcept; + SHAABB& operator= (const SHAABB& rhs) noexcept; + SHAABB& operator= (SHAABB&& rhs) noexcept; /*---------------------------------------------------------------------------------*/ /* Getter Functions */ @@ -56,7 +56,6 @@ namespace SHADE [[nodiscard]] SHVec3 GetCenter () const noexcept; [[nodiscard]] SHVec3 GetWorldExtents () const noexcept; - [[nodiscard]] const SHVec3& GetRelativeExtents () const noexcept; [[nodiscard]] SHVec3 GetMin () const noexcept; [[nodiscard]] SHVec3 GetMax () const noexcept; [[nodiscard]] std::vector GetVertices () const noexcept; @@ -67,7 +66,6 @@ namespace SHADE void SetCenter (const SHVec3& newCenter) noexcept; void SetWorldExtents (const SHVec3& newWorldExtents) noexcept; - void SetRelativeExtents (const SHVec3& newRelativeExtents) noexcept; void SetMin (const SHVec3& min) noexcept; void SetMax (const SHVec3& max) noexcept; void SetMinMax (const SHVec3& min, const SHVec3& max) noexcept; @@ -76,28 +74,96 @@ namespace SHADE /* Function Members */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] bool TestPoint (const SHVec3& point) const noexcept override; - [[nodiscard]] SHRaycastResult Raycast (const SHRay& ray) const noexcept override; + /** + * @brief + * Checks if a point is inside the aabb. + * @param point + * The point to check. + * @return + * True if the point is inside the aabb. + */ + [[nodiscard]] bool TestPoint (const SHVec3& point) const noexcept override; - [[nodiscard]] bool Contains (const SHBox& rhs) const noexcept; - [[nodiscard]] float Volume () const noexcept; - [[nodiscard]] float SurfaceArea () const noexcept; + /** + * @brief + * Casts a ray against the aabb. + * @param ray + * The ray to cast. + * @return + * The result of the raycast.
+ * See the corresponding header for the contents of the raycast result object. + */ + [[nodiscard]] SHRaycastResult Raycast (const SHRay& ray) const noexcept override; + + /** + * @brief + * Checks if an entire other aabb is contained by this aabb. + * @param rhs + * The aabb to check. + * @return + * True if the other sphere is completely contained by this aabb. + */ + [[nodiscard]] bool Contains (const SHAABB& rhs) const noexcept; + + /** + * @brief + * Calculates the volume of the aabb. + */ + [[nodiscard]] float Volume () const noexcept; + + /** + * @brief + * Calculates the surface area of the aabb. + */ + [[nodiscard]] float SurfaceArea () const noexcept; /*---------------------------------------------------------------------------------*/ /* Static Function Members */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] static SHBox Combine (const SHBox& lhs, const SHBox& rhs) noexcept; - [[nodiscard]] static bool Intersect (const SHBox& lhs, const SHBox& rhs) noexcept; - [[nodiscard]] static SHBox BuildFromBoxes (const SHBox* boxes, size_t numBoxes) noexcept; - [[nodiscard]] static SHBox BuildFromVertices (const SHVec3* vertices, size_t numVertices, size_t stride = 0) noexcept; + /** + * @brief + * Combines two aabbs to form a larger aabb. + * If one aabb is completely contained by the other, the result is the larger aabb. + * @return + * The combined aabb. + */ + [[nodiscard]] static SHAABB Combine (const SHAABB& lhs, const SHAABB& rhs) noexcept; - private: - /*---------------------------------------------------------------------------------*/ - /* Data Members */ - /*---------------------------------------------------------------------------------*/ + /** + * @brief + * Checks if two aabbs are intersecting. + * @return + * True if they are intersecting. + */ + [[nodiscard]] static bool Intersect (const SHAABB& lhs, const SHAABB& rhs) noexcept; - SHVec3 RelativeExtents; + /** + * @brief + * Builds a single aabb from multiple aabbs. + * @param spheres + * The set of aabbs to build from. + * @param numSpheres + * The number of aabbs in the set to build from. + * @return + * An aabb that contains all the spheres in the set. + */ + [[nodiscard]] static SHAABB BuildFromBoxes (const SHAABB* boxes, size_t numBoxes) noexcept; + + /** + * @brief + * Builds a aabb from a set of vertices. + * @param vertices + * The vertices to build a aabb from. + * @param numVertices + * The number of vertices in the set to build from. + * @param stride + * The stride between each vertex, in the instance there is data in between each + * vertex that does not define the geometry of the object. + * @return + * An aabb that contains all the vertices in the set. + */ + [[nodiscard]] static SHAABB BuildFromVertices (const SHVec3* vertices, size_t numVertices, size_t stride = 0) noexcept; }; diff --git a/SHADE_Engine/src/Math/Geometry/SHSphere.h b/SHADE_Engine/src/Math/Geometry/SHSphere.h index 91771a21..80a21aa4 100644 --- a/SHADE_Engine/src/Math/Geometry/SHSphere.h +++ b/SHADE_Engine/src/Math/Geometry/SHSphere.h @@ -56,7 +56,7 @@ namespace SHADE * @return * True if the point is inside the sphere. */ - [[nodiscard]] bool TestPoint (const SHVec3& point) const noexcept override; + [[nodiscard]] bool TestPoint (const SHVec3& point) const noexcept override; /** * @brief @@ -67,7 +67,7 @@ namespace SHADE * The result of the raycast.
* See the corresponding header for the contents of the raycast result object. */ - [[nodiscard]] SHRaycastResult Raycast (const SHRay& ray) const noexcept override; + [[nodiscard]] SHRaycastResult Raycast (const SHRay& ray) const noexcept override; /** * @brief @@ -77,19 +77,19 @@ namespace SHADE * @return * True if the other sphere is completely contained by this sphere. */ - [[nodiscard]] bool Contains (const SHSphere& rhs) const noexcept; + [[nodiscard]] bool Contains (const SHSphere& rhs) const noexcept; /** * @brief * Calculates the volume of the sphere. */ - [[nodiscard]] float Volume () const noexcept; + [[nodiscard]] float Volume () const noexcept; /** * @brief * Calculates the surface area of the sphere. */ - [[nodiscard]] float SurfaceArea () const noexcept; + [[nodiscard]] float SurfaceArea () const noexcept; /*---------------------------------------------------------------------------------*/ @@ -103,7 +103,7 @@ namespace SHADE * @return * The combined sphere. */ - [[nodiscard]] static SHSphere Combine (const SHSphere& lhs, const SHSphere& rhs) noexcept; + [[nodiscard]] static SHSphere Combine (const SHSphere& lhs, const SHSphere& rhs) noexcept; /** * @brief @@ -111,7 +111,7 @@ namespace SHADE * @return * True if they are intersecting. */ - [[nodiscard]] static bool Intersect (const SHSphere& lhs, const SHSphere& rhs) noexcept; + [[nodiscard]] static bool Intersect (const SHSphere& lhs, const SHSphere& rhs) noexcept; /** * @brief @@ -123,7 +123,7 @@ namespace SHADE * @return * A sphere that contains all the spheres in the set. */ - [[nodiscard]] static SHSphere BuildFromSpheres (const SHSphere* spheres, size_t numSpheres) noexcept; + [[nodiscard]] static SHSphere BuildFromSpheres (const SHSphere* spheres, size_t numSpheres) noexcept; /** * @brief diff --git a/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.cpp b/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.cpp index 540d5375..5fa93021 100644 --- a/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.cpp +++ b/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.cpp @@ -105,7 +105,7 @@ namespace SHADE /* Getter Functions Definitions */ /*-----------------------------------------------------------------------------------*/ - const std::vector& SHAABBTree::GetAABBs() const noexcept + const std::vector& SHAABBTree::GetAABBs() const noexcept { static AABBs aabbs; static std::stack nodeIndices; @@ -142,7 +142,7 @@ namespace SHADE /* Public Member Functions Definitions */ /*-----------------------------------------------------------------------------------*/ - void SHAABBTree::Insert(SHCollisionShapeID id, const SHBox& AABB) + void SHAABBTree::Insert(SHCollisionShapeID id, const SHAABB& AABB) { const int32_t NEW_INDEX = allocateNode(); @@ -170,7 +170,7 @@ namespace SHADE insertLeaf(NEW_INDEX); } - void SHAABBTree::Update(SHCollisionShapeID id, const SHBox& AABB) + void SHAABBTree::Update(SHCollisionShapeID id, const SHAABB& AABB) { // Get node index const int32_t INDEX_TO_UPDATE = nodeMap[id]; @@ -206,7 +206,7 @@ namespace SHADE freeNode(INDEX_TO_REMOVE); } - const std::vector& SHAABBTree::Query(SHCollisionShapeID id, const SHBox& AABB) const noexcept + const std::vector& SHAABBTree::Query(SHCollisionShapeID id, const SHAABB& AABB) const noexcept { static std::vector potentialCollisions; static std::stack nodeIndices; @@ -226,7 +226,7 @@ namespace SHADE continue; const Node& NODE = nodes[INDEX]; - if (!SHBox::Intersect(AABB, NODE.AABB)) + if (!SHAABB::Intersect(AABB, NODE.AABB)) continue; // Avoid checking against shapes of the same composite collider (and itself) @@ -328,12 +328,12 @@ namespace SHADE // Find best sibling for new leaf // Utilise Surface Area Heuristic - const SHBox& LEAF_AABB = nodes[index].AABB; + const SHAABB& LEAF_AABB = nodes[index].AABB; uint32_t searchIndex = root; while (!isLeaf(searchIndex)) { - const SHBox COMBINED_AABB = SHBox::Combine(LEAF_AABB, nodes[searchIndex].AABB); + const SHAABB COMBINED_AABB = SHAABB::Combine(LEAF_AABB, nodes[searchIndex].AABB); const float COMBINED_AREA = COMBINED_AABB.SurfaceArea(); const float INHERITED_COST = 2.0f * (COMBINED_AREA - nodes[searchIndex].AABB.SurfaceArea()); @@ -344,8 +344,8 @@ namespace SHADE float leftCost = 0.0f; float rightCost = 0.0f; - const float LEFT_COMBINED_AREA = SHBox::Combine(LEAF_AABB, nodes[LEFT_INDEX].AABB).SurfaceArea(); - const float RIGHT_COMBINED_AREA = SHBox::Combine(LEAF_AABB, nodes[RIGHT_INDEX].AABB).SurfaceArea(); + const float LEFT_COMBINED_AREA = SHAABB::Combine(LEAF_AABB, nodes[LEFT_INDEX].AABB).SurfaceArea(); + const float RIGHT_COMBINED_AREA = SHAABB::Combine(LEAF_AABB, nodes[RIGHT_INDEX].AABB).SurfaceArea(); // Compute cost for descending into the left if (isLeaf(LEFT_INDEX)) @@ -377,7 +377,7 @@ namespace SHADE Node& newParent = nodes[NEW_PARENT]; newParent.parent = OLD_PARENT; newParent.id = SHCollisionShapeID{ MAX_EID, std::numeric_limits::max() }; - newParent.AABB = SHBox::Combine(LEAF_AABB, nodes[BEST_SIBLING].AABB); + newParent.AABB = SHAABB::Combine(LEAF_AABB, nodes[BEST_SIBLING].AABB); newParent.height = nodes[BEST_SIBLING].height + 1; newParent.left = BEST_SIBLING; @@ -437,7 +437,7 @@ namespace SHADE const Node& RIGHT_NODE = nodes[RIGHT_INDEX]; nodes[index].height = 1 + SHMath::Max(LEFT_NODE.height, RIGHT_NODE.height); - nodes[index].AABB = SHBox::Combine(LEFT_NODE.AABB, RIGHT_NODE.AABB); + nodes[index].AABB = SHAABB::Combine(LEFT_NODE.AABB, RIGHT_NODE.AABB); // Sync up to the root index = nodes[index].parent; @@ -506,8 +506,8 @@ namespace SHADE nodeA.right = G; nodeG.parent = index; - nodeA.AABB = SHBox::Combine(nodeB.AABB, nodeG.AABB); - nodeC.AABB = SHBox::Combine(nodeA.AABB, nodeF.AABB); + nodeA.AABB = SHAABB::Combine(nodeB.AABB, nodeG.AABB); + nodeC.AABB = SHAABB::Combine(nodeA.AABB, nodeF.AABB); nodeA.height = 1 + SHMath::Max(nodeB.height, nodeG.height); nodeC.height = 1 + SHMath::Max(nodeA.height, nodeF.height); @@ -518,8 +518,8 @@ namespace SHADE nodeA.right = F; nodeF.parent = index; - nodeA.AABB = SHBox::Combine(nodeB.AABB, nodeF.AABB); - nodeC.AABB = SHBox::Combine(nodeA.AABB, nodeG.AABB); + nodeA.AABB = SHAABB::Combine(nodeB.AABB, nodeF.AABB); + nodeC.AABB = SHAABB::Combine(nodeA.AABB, nodeG.AABB); nodeA.height = 1 + SHMath::Max(nodeB.height, nodeF.height); nodeC.height = 1 + SHMath::Max(nodeA.height, nodeG.height); @@ -569,8 +569,8 @@ namespace SHADE nodeA.left = E; nodeE.parent = index; - nodeA.AABB = SHBox::Combine(nodeC.AABB, nodeE.AABB); - nodeB.AABB = SHBox::Combine(nodeA.AABB, nodeD.AABB); + nodeA.AABB = SHAABB::Combine(nodeC.AABB, nodeE.AABB); + nodeB.AABB = SHAABB::Combine(nodeA.AABB, nodeD.AABB); nodeA.height = 1 + SHMath::Max(nodeC.height, nodeE.height); nodeB.height = 1 + SHMath::Max(nodeA.height, nodeD.height); @@ -581,8 +581,8 @@ namespace SHADE nodeA.left = D; nodeD.parent = index; - nodeA.AABB = SHBox::Combine(nodeC.AABB, nodeD.AABB); - nodeB.AABB = SHBox::Combine(nodeA.AABB, nodeE.AABB); + nodeA.AABB = SHAABB::Combine(nodeC.AABB, nodeD.AABB); + nodeB.AABB = SHAABB::Combine(nodeA.AABB, nodeE.AABB); nodeA.height = 1 + SHMath::Max(nodeC.height, nodeD.height); nodeB.height = 1 + SHMath::Max(nodeA.height, nodeE.height); diff --git a/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.h b/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.h index 420f30e7..32b5095a 100644 --- a/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.h +++ b/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.h @@ -32,7 +32,7 @@ namespace SHADE /* Type Definitions */ /*---------------------------------------------------------------------------------*/ - using AABBs = std::vector; + using AABBs = std::vector; /*---------------------------------------------------------------------------------*/ /* Data Members */ @@ -67,11 +67,11 @@ namespace SHADE /* Member Functions */ /*---------------------------------------------------------------------------------*/ - void Insert (SHCollisionShapeID id, const SHBox& AABB); - void Update (SHCollisionShapeID id, const SHBox& AABB); + void Insert (SHCollisionShapeID id, const SHAABB& AABB); + void Update (SHCollisionShapeID id, const SHAABB& AABB); void Remove (SHCollisionShapeID id) noexcept; - [[nodiscard]] const std::vector& Query(SHCollisionShapeID id, const SHBox& AABB) const noexcept; + [[nodiscard]] const std::vector& Query(SHCollisionShapeID id, const SHAABB& AABB) const noexcept; [[nodiscard]] const std::vector& Query(const SHRay& ray, float distance) const noexcept; private: @@ -103,7 +103,7 @@ namespace SHADE /* Data Members */ /*-------------------------------------------------------------------------------*/ - SHBox AABB; + SHAABB AABB; SHCollisionShapeID id; // Used to lookup the collision shape & entity for culling against itself union diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h index 0690b071..61f2b43c 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShape.h @@ -17,7 +17,7 @@ #include "Physics/Collision/CollisionTags/SHCollisionTags.h" #include "Physics/Collision/SHPhysicsMaterial.h" #include "SHCollisionShapeID.h" -#include "Math/Geometry/SHBox.h" +#include "Math/Geometry/SHAABB.h" #include "Math/Transform/SHTransform.h" namespace SHADE @@ -131,7 +131,7 @@ namespace SHADE virtual void ComputeTransforms () noexcept = 0; [[nodiscard]] virtual SHMatrix GetInertiaTensor (float mass) const noexcept = 0; [[nodiscard]] virtual SHMatrix ComputeWorldTransform () const noexcept = 0; - [[nodiscard]] virtual SHBox ComputeAABB () const noexcept = 0; + [[nodiscard]] virtual SHAABB ComputeAABB () const noexcept = 0; protected: /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp index 30497f36..71d965c5 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.cpp @@ -227,9 +227,9 @@ namespace SHADE ); } - SHBox SHSphereCollisionShape::ComputeAABB() const noexcept + SHAABB SHSphereCollisionShape::ComputeAABB() const noexcept { - return SHBox{ Center, SHVec3{ Radius } }; + return SHAABB{ Center, SHVec3{ Radius } }; } diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h index eb41f93c..6e119cd1 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h @@ -128,7 +128,7 @@ namespace SHADE [[nodiscard]] SHMatrix ComputeWorldTransform () const noexcept override; - [[nodiscard]] SHBox ComputeAABB () const noexcept override; + [[nodiscard]] SHAABB ComputeAABB () const noexcept override; private: /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Serialization/SHYAMLConverters.h b/SHADE_Engine/src/Serialization/SHYAMLConverters.h index b225a9a4..59ee532a 100644 --- a/SHADE_Engine/src/Serialization/SHYAMLConverters.h +++ b/SHADE_Engine/src/Serialization/SHYAMLConverters.h @@ -132,7 +132,7 @@ namespace YAML { case SHCollisionShape::Type::BOX: { - //const auto* BOX = reinterpret_cast(rhs.GetShape()); + //const auto* BOX = reinterpret_cast(rhs.GetShape()); //node[HalfExtents] = BOX->GetRelativeExtents(); } break; @@ -170,8 +170,7 @@ namespace YAML { case SHCollisionShape::Type::BOX: { - //if (node[HalfExtents].IsDefined()) - // rhs.SetBoundingBox(node[HalfExtents].as()); + } break; case SHCollisionShape::Type::SPHERE: diff --git a/SHADE_Managed/src/Components/Collider.cxx b/SHADE_Managed/src/Components/Collider.cxx index 1c84637a..3dd1446e 100644 --- a/SHADE_Managed/src/Components/Collider.cxx +++ b/SHADE_Managed/src/Components/Collider.cxx @@ -128,39 +128,39 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ Vector3 BoxCollider::Center::get() { - //return Convert::ToCLI(getNativeCollisionShape().GetCenter()); + //return Convert::ToCLI(getNativeCollisionShape().GetCenter()); return Vector3::Zero; } void BoxCollider::Center::set(Vector3 value) { - //getNativeCollisionShape().SetCenter(Convert::ToNative(value)); + //getNativeCollisionShape().SetCenter(Convert::ToNative(value)); } Vector3 BoxCollider::HalfExtents::get() { - //return Convert::ToCLI(getNativeCollisionShape().GetWorldExtents()); + //return Convert::ToCLI(getNativeCollisionShape().GetWorldExtents()); return Vector3::Zero; } void BoxCollider::HalfExtents::set(Vector3 value) { - //getNativeCollisionShape().SetWorldExtents(Convert::ToNative(value)); + //getNativeCollisionShape().SetWorldExtents(Convert::ToNative(value)); } Vector3 BoxCollider::Min::get() { - //return Convert::ToCLI(getNativeCollisionShape().GetMin()); + //return Convert::ToCLI(getNativeCollisionShape().GetMin()); return Vector3::Zero; } void BoxCollider::Min::set(Vector3 value) { - //getNativeCollisionShape().SetMin(Convert::ToNative(value)); + //getNativeCollisionShape().SetMin(Convert::ToNative(value)); } Vector3 BoxCollider::Max::get() { - //return Convert::ToCLI(getNativeCollisionShape().GetMax()); + //return Convert::ToCLI(getNativeCollisionShape().GetMax()); return Vector3::Zero; } void BoxCollider::Max::set(Vector3 value) { - //getNativeCollisionShape().SetMax(Convert::ToNative(value)); + //getNativeCollisionShape().SetMax(Convert::ToNative(value)); } /*---------------------------------------------------------------------------------*/ @@ -168,12 +168,12 @@ namespace SHADE /*---------------------------------------------------------------------------------*/ bool BoxCollider::TestPoint(Vector3 point) { - //return getNativeCollisionShape().TestPoint(Convert::ToNative(point)); + //return getNativeCollisionShape().TestPoint(Convert::ToNative(point)); return false; } bool BoxCollider::Raycast(Ray ray, float maxDistance) { - //return getNativeCollisionShape().Raycast(Convert::ToNative(ray)); + //return getNativeCollisionShape().Raycast(Convert::ToNative(ray)); return false; } From 605d408a3aafb975a0e13c2dcfcf7e6fffdf8574 Mon Sep 17 00:00:00 2001 From: mushgunAX Date: Fri, 23 Dec 2022 15:24:12 +0800 Subject: [PATCH 076/275] Binding types, scroll wheel support, bind clears --- SHADE_Engine/src/Input/SHInputManager.cpp | 46 ++++---- SHADE_Engine/src/Input/SHInputManager.h | 126 +++++++++++++++++----- 2 files changed, 126 insertions(+), 46 deletions(-) diff --git a/SHADE_Engine/src/Input/SHInputManager.cpp b/SHADE_Engine/src/Input/SHInputManager.cpp index f6b58a94..01bd4c13 100644 --- a/SHADE_Engine/src/Input/SHInputManager.cpp +++ b/SHADE_Engine/src/Input/SHInputManager.cpp @@ -577,18 +577,42 @@ namespace SHADE //Only get of largest magnitude double SHInputManager::GetBindingAxis(std::string const& bindingName, size_t cNum) noexcept { + if (cNum >= XUSER_MAX_COUNT) return 0.0f; + + //Over mouse movement, if used for this axis + if (bindings[bindingName].bindingType == SHLogicalBindingData::SH_BINDINGTYPE::MOUSE_X) + { + double velX = 0.0f; + GetMouseVelocity(&velX, nullptr); + return velX; + } + + if (bindings[bindingName].bindingType == SHLogicalBindingData::SH_BINDINGTYPE::MOUSE_Y) + { + double velY = 0.0f; + GetMouseVelocity(nullptr, &velY); + return velY; + } + + //Over mouse scroll, if used for this axis + if (bindings[bindingName].bindingType == SHLogicalBindingData::SH_BINDINGTYPE::MOUSE_SCROLL) + { + return mouseWheelVerticalDelta; + } + + //The largest magnitude recorded so far + double largestMagnitude = 0.0; + //Over keycodes, prioritise positive for (SH_KEYCODE k : bindings[bindingName].positiveKeyCodes) { - if (GetKey(k)) return 1.0; + if (GetKey(k)) if (std::abs(1.0f) > std::abs(largestMagnitude)) largestMagnitude = 1.0f; } for (SH_KEYCODE k : bindings[bindingName].negativeKeyCodes) { - if (GetKey(k)) return -1.0; + if (GetKey(k)) if (std::abs(-1.0f) > std::abs(largestMagnitude)) largestMagnitude = -1.0f; } - double largestMagnitude = 0.0; - //Over controllerCodes for (SH_CONTROLLERCODE c : bindings[bindingName].positiveControllerCodes) { @@ -806,18 +830,4 @@ namespace SHADE return minReleaseTime; } - //Only for mouse movement - //Get largest delta - double SHInputManager::GetBindingMouseVelocity(std::string const& bindingName, size_t cNum) noexcept - { - if (cNum >= XUSER_MAX_COUNT) return 0.0; - - //Mouse velocity - double velX = 0.0; - double velY = 0.0; - GetMouseVelocity(&velX, &velY); - - return bindings[bindingName].mouseXPositiveMultiplier * velX + bindings[bindingName].mouseYPositiveMultiplier * velY; - } - } //namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Input/SHInputManager.h b/SHADE_Engine/src/Input/SHInputManager.h index ce3e69aa..3c503336 100644 --- a/SHADE_Engine/src/Input/SHInputManager.h +++ b/SHADE_Engine/src/Input/SHInputManager.h @@ -306,6 +306,20 @@ namespace SHADE /*------------------------------------------------------------------------*/ struct SH_API SHLogicalBindingData { + //BINDING TYPES/////////////////////////////////////////////////////////// + enum class SH_BINDINGTYPE + { + KB_MB_CONTROLLER, + MOUSE_X, + MOUSE_Y, + MOUSE_SCROLL + }; + + //BINDINGS//////////////////////////////////////////////////////////////// + + //The type of the binding + SH_BINDINGTYPE bindingType = SH_BINDINGTYPE::KB_MB_CONTROLLER; + //Key codes mapped to positive std::set positiveKeyCodes; @@ -318,9 +332,32 @@ namespace SHADE //Controller Codes mapped to negative std::set negativeControllerCodes; - //Mouse movement mapped to axes? - double mouseXPositiveMultiplier = 0.0f; - double mouseYPositiveMultiplier = 0.0f; + //VALUES////////////////////////////////////////////////////////////////// + + //The current value of the axis binding + double value = 0.0f; + + //Whether the input is inverted, + //If so, positive bindings will make the value negative, + // negative bindings will make the value positive, + // moving the mouse up will make the value negative, + // scrolling the mousewheel up will make the value negative, + bool inverted = false; + + //When no input is present, how fast does the value fall back to neutral? + double gravity = 1.0f; + + //How far the user needs to move an analog stick before application + //registers the movement + double dead = 0.1f; + + //Speed in units per second that the axis will move toward target value + //For digital inputs only + double sensitivity = 1.0f; + + //If enabled, axis value will reset to zero when pressing a button + //that corresponds in the opposite direction + bool snap = false; }; public: @@ -350,6 +387,12 @@ namespace SHADE mouseWheelVerticalDeltaPoll += GET_WHEEL_DELTA_WPARAM(wParam); } + //Get if controller or KB/M is presently being used + static inline bool const GetControllerInUse() noexcept + { + return controllerInUse; + } + //For testing purposes //static void PrintCurrentState() noexcept; @@ -621,13 +664,19 @@ namespace SHADE /* Binding Functions */ /*------------------------------------------------------------------------*/ + //Get a read-only map of the bindings + static inline std::map const& getBindings() noexcept + { + return bindings; + } + //Add a new binding to the map static inline void BindingsAdd(std::string const& newBindingName) noexcept { bindings.insert({ newBindingName, SHLogicalBindingData() }); } - //Remove a binding from the map + //Remove a binding and all its associated inputs from the list //Returns 1 if found and removed, 0 if not found static inline size_t BindingsRemove(std::string const& targetBindingName) noexcept { @@ -646,6 +695,20 @@ namespace SHADE return bindings.size(); } + //BINDING TYPE////////////////////////////////////////////////////////////// + + static inline void BindingsSetType(std::string const& targetBindingName, SHLogicalBindingData::SH_BINDINGTYPE const newType) + { + bindings[targetBindingName].bindingType = newType; + } + + static inline SHLogicalBindingData::SH_BINDINGTYPE const BindingsGetType(std::string const& targetBindingName) + { + return bindings[targetBindingName].bindingType; + } + + //POSITIVE KEYCODES///////////////////////////////////////////////////////// + //Check positive keycodes to binding static inline std::set const& BindingsGetPositiveKeyCodes(std::string const& bindingName) noexcept { @@ -667,6 +730,14 @@ namespace SHADE return bindings[targetBindingName].positiveKeyCodes.erase(toRemove); } + //Clear all positive SH_KEYCODEs from binding + static inline void BindingsClearPositiveKeyCodes(std::string const& targetBindingName) noexcept + { + bindings[targetBindingName].positiveKeyCodes.clear(); + } + + //NEGATIVE KEYCODES///////////////////////////////////////////////////////// + //Check negative keycodes to binding static inline std::set const& BindingsGetNegativeKeyCodes(std::string const& bindingName) noexcept { @@ -688,6 +759,14 @@ namespace SHADE return bindings[targetBindingName].negativeKeyCodes.erase(toRemove); } + //Clear all negative SH_KEYCODEs from binding + static inline void BindingsClearNegativeKeyCodes(std::string const& targetBindingName) noexcept + { + bindings[targetBindingName].negativeKeyCodes.clear(); + } + + //POSITIVE CONTROLLERCODES////////////////////////////////////////////////// + //Check positive controllercodes to binding static inline std::set const& BindingsGetPositiveControllerCodes(std::string const& bindingName) noexcept { @@ -709,6 +788,14 @@ namespace SHADE return bindings[targetBindingName].positiveControllerCodes.erase(toRemove); } + //Clear all positive SH_CONTROLLERCODEs from binding + static inline void BindingsClearPositiveControllerCodes(std::string const& targetBindingName) noexcept + { + bindings[targetBindingName].positiveControllerCodes.clear(); + } + + //NEGATIVE CONTROLLERCODES////////////////////////////////////////////////// + //Check negative controllercodes to binding static inline std::set const& BindingsGetNegativeControllerCodes(std::string const& bindingName) noexcept { @@ -730,29 +817,16 @@ namespace SHADE return bindings[targetBindingName].negativeControllerCodes.erase(toRemove); } - //Mouse movement bindings - - static inline double const BindingsGetMouseXPositiveMultiplier(std::string const& bindingName) noexcept + //Clear all negative SH_CONTROLLERCODEs from binding + static inline void BindingsClearNegativeControllerCodes(std::string const& targetBindingName) noexcept { - return bindings[bindingName].mouseXPositiveMultiplier; + bindings[targetBindingName].negativeControllerCodes.clear(); } - static inline void BindingsSetMouseXPositiveMultiplier(std::string const& bindingName, double newValue) noexcept - { - bindings[bindingName].mouseXPositiveMultiplier = newValue; - } - - static inline double const BindingsGetMouseYPositiveMultiplier(std::string const& bindingName) noexcept - { - return bindings[bindingName].mouseYPositiveMultiplier; - } - - static inline void BindingsSetMouseYPositiveMultiplier(std::string const& bindingName, double newValue) noexcept - { - bindings[bindingName].mouseYPositiveMultiplier = newValue; - } - - //Get the axis value of binding, between -1 and 1 + //Get the axis value of binding, between -1 and 1 for non-mouse + //For mouse, it won't be between -1 and 1. It will also be multiplied by sensitivity + //To avoid interference between mouse movement/wheel and keyboard/mouse/controller input, + //Set mouseXBound, mouseYBound and mouseScrollBound to false static double GetBindingAxis(std::string const& bindingName, size_t controllerNumber = 0) noexcept; //Whether binding is being held or not @@ -780,10 +854,6 @@ namespace SHADE static double GetBindingPositiveReleasedTime(std::string const& bindingName, size_t controllerNumber = 0) noexcept; static double GetBindingNegativeReleasedTime(std::string const& bindingName, size_t controllerNumber = 0) noexcept; - //Binding mouse velocity - //Only for mouse movement - static double GetBindingMouseVelocity(std::string const& bindingName, size_t controllerNumber = 0) noexcept; - /*------------------------------------------------------------------------*/ /* Other Functions */ /*------------------------------------------------------------------------*/ From 89f1f600640c9d7420a674b4192ae8ad950f8c58 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Sat, 24 Dec 2022 02:19:53 +0800 Subject: [PATCH 077/275] Added physics settings menu for easily toggling debug draw states --- Assets/Scenes/PhysicsSandbox.shade | 16 +++++++------- .../src/Application/SBApplication.cpp | 6 ------ .../EditorWindow/MenuBar/SHEditorMenuBar.cpp | 21 +++++++++++++++++++ .../src/Physics/Dynamics/SHPhysicsWorld.cpp | 5 +++++ .../src/Physics/Dynamics/SHPhysicsWorld.h | 1 + .../Routines/SHPhysicsDebugDrawRoutine.cpp | 11 ++++++++-- .../System/SHPhysicsDebugDrawSystem.cpp | 16 +++++++++++--- .../Physics/System/SHPhysicsDebugDrawSystem.h | 20 ++++++++++-------- .../src/Physics/System/SHPhysicsSystem.h | 10 ++++----- 9 files changed, 73 insertions(+), 33 deletions(-) diff --git a/Assets/Scenes/PhysicsSandbox.shade b/Assets/Scenes/PhysicsSandbox.shade index 1438b558..60a97885 100644 --- a/Assets/Scenes/PhysicsSandbox.shade +++ b/Assets/Scenes/PhysicsSandbox.shade @@ -4,7 +4,7 @@ NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 0, y: 3, z: 0} + Translate: {x: 0.0700113177, y: 2.5, z: 0} Rotate: {x: -0, y: 0, z: -0} Scale: {x: 1, y: 1, z: 1} IsActive: true @@ -19,14 +19,14 @@ Interpolate: true Sleeping Enabled: true Freeze Position X: false - Freeze Position Y: true + Freeze Position Y: false Freeze Position Z: false Freeze Rotation X: false Freeze Rotation Y: false Freeze Rotation Z: false IsActive: true Collider Component: - DrawColliders: true + DrawColliders: false Colliders: - Is Trigger: false Type: Sphere @@ -65,8 +65,8 @@ NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 0, y: -2.5, z: 0} - Rotate: {x: 0, y: 0, z: 0} + Translate: {x: 0, y: 0, z: 0} + Rotate: {x: -0, y: 0, z: -0} Scale: {x: 1, y: 1, z: 1} IsActive: true RigidBody Component: @@ -87,11 +87,11 @@ Freeze Rotation Z: false IsActive: true Collider Component: - DrawColliders: true + DrawColliders: false Colliders: - Is Trigger: false Type: Sphere - Radius: 5 + Radius: 2.5 Friction: 0.400000006 Bounciness: 0 Density: 1 @@ -127,7 +127,7 @@ Freeze Rotation Z: false IsActive: true Collider Component: - DrawColliders: true + DrawColliders: false Colliders: - Is Trigger: false Type: Sphere diff --git a/SHADE_Application/src/Application/SBApplication.cpp b/SHADE_Application/src/Application/SBApplication.cpp index 8607702d..dfeb781b 100644 --- a/SHADE_Application/src/Application/SBApplication.cpp +++ b/SHADE_Application/src/Application/SBApplication.cpp @@ -185,12 +185,6 @@ namespace Sandbox #endif SHSceneManager::SceneUpdate(0.016f); #ifdef SHEDITOR - static bool drawBP = false; - if (SHInputManager::GetKeyDown(SHInputManager::SH_KEYCODE::RIGHT_CTRL)) - { - drawBP = !drawBP; - SHSystemManager::GetSystem()->SetFlagState(SHPhysicsDebugDrawSystem::DebugDrawFlags::BROADPHASE, drawBP); - } SHSystemManager::RunRoutines(editor->editorState != SHEditor::State::PLAY, SHFrameRateController::GetRawDeltaTime()); editor->PollPicking(); diff --git a/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp b/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp index a1335e19..a08f0f75 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp +++ b/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp @@ -25,6 +25,7 @@ #include "Serialization/SHSerialization.h" #include "Serialization/Configurations/SHConfigurationManager.h" #include "Editor/EditorWindow/SHEditorWindowManager.h" +#include "Physics/System/SHPhysicsDebugDrawSystem.h" const std::string LAYOUT_FOLDER_PATH{ std::string(ASSET_ROOT) + "/Editor/Layouts" }; @@ -202,6 +203,26 @@ namespace SHADE ImGui::EndMenu(); } + if (ImGui::BeginMenu("Physics Settings")) + { + if (auto* physicsDebugDraw = SHSystemManager::GetSystem()) + { + bool drawColliders = physicsDebugDraw->GetFlagState(SHPhysicsDebugDrawSystem::DebugDrawFlags::COLLIDERS); + if (ImGui::Checkbox("Draw Colliders", &drawColliders)) + physicsDebugDraw->SetFlagState(SHPhysicsDebugDrawSystem::DebugDrawFlags::COLLIDERS, drawColliders); + + bool drawContactPoints = physicsDebugDraw->GetFlagState(SHPhysicsDebugDrawSystem::DebugDrawFlags::CONTACTS); + if (ImGui::Checkbox("Draw Contact Points", &drawContactPoints)) + physicsDebugDraw->SetFlagState(SHPhysicsDebugDrawSystem::DebugDrawFlags::CONTACTS, drawContactPoints); + + bool drawBroadphase = physicsDebugDraw->GetFlagState(SHPhysicsDebugDrawSystem::DebugDrawFlags::BROADPHASE); + if (ImGui::Checkbox("Draw Broadphase", &drawBroadphase)) + physicsDebugDraw->SetFlagState(SHPhysicsDebugDrawSystem::DebugDrawFlags::BROADPHASE, drawBroadphase); + } + + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); } diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp index 8451bd5b..4e6e8165 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp @@ -35,6 +35,11 @@ namespace SHADE /* Getter Functions Definitions */ /*-----------------------------------------------------------------------------------*/ + const SHContactManager::ContactPoints& SHPhysicsWorld::GetContactPoints() const noexcept + { + return contactManager.GetContactPoints(); + } + const SHContactManager::TriggerEvents& SHPhysicsWorld::GetTriggerEvents() const noexcept { return contactManager.GetTriggerEvents(); diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h index 9bc2199e..f63bac40 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.h @@ -72,6 +72,7 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ + const SHContactManager::ContactPoints& GetContactPoints () const noexcept; const SHContactManager::TriggerEvents& GetTriggerEvents () const noexcept; const SHContactManager::CollisionEvents& GetCollisionEvents () const noexcept; diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp index 9bf43115..8aad19f5 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp @@ -38,7 +38,7 @@ namespace SHADE { auto* physicsDebugDrawSystem = reinterpret_cast(GetSystem()); - if (!physicsDebugDrawSystem->GetFlagState(DebugDrawFlags::ACTIVE)) + if (!physicsDebugDrawSystem->IsDebugDrawActive()) return; auto* debugDrawSystem = SHSystemManager::GetSystem(); @@ -74,7 +74,14 @@ namespace SHADE if (DRAW_CONTACTS) { - // TODO + const SHColour& CONTACT_COLOUR = physicsDebugDrawSystem->DEBUG_DRAW_COLOURS[SHUtilities::ConvertEnum(Colours::CONTACT)]; + + const auto& CONTACT_POINTS = physicsSystem->physicsWorld->GetContactPoints(); + for (auto& contactPoint : CONTACT_POINTS) + { + const SHMatrix TRS = SHMatrix::Transform(contactPoint, SHQuaternion::Identity, SHVec3{ 0.1f }); + debugDrawSystem->DrawCube(TRS, CONTACT_COLOUR); + } } if (DRAW_RAYCASTS) diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp index 958ac61a..9940ad20 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp @@ -44,6 +44,11 @@ namespace SHADE /* Getter Function Definitions */ /*-----------------------------------------------------------------------------------*/ + bool SHPhysicsDebugDrawSystem::IsDebugDrawActive() const noexcept + { + return flags & ACTIVE_FLAG; + } + bool SHPhysicsDebugDrawSystem::GetFlagState(DebugDrawFlags flag) const noexcept { const uint8_t ENUM_VALUE = SHUtilities::ConvertEnum(flag); @@ -58,6 +63,10 @@ namespace SHADE { const uint8_t ENUM_VALUE = SHUtilities::ConvertEnum(flag); state ? flags |= ENUM_VALUE : flags &= ~ENUM_VALUE; + + // If no other debug drawing state is active, turn active off. Otherwise, we maintain + // the active state. + flags == ACTIVE_FLAG ? flags = 0 : flags |= ACTIVE_FLAG; } /*-----------------------------------------------------------------------------------*/ @@ -97,7 +106,7 @@ namespace SHADE if (EVENT_DATA->debugDrawState) { if (collidersToDraw.empty()) - SetFlagState(DebugDrawFlags::ACTIVE, true); + flags |= ACTIVE_FLAG; collidersToDraw.emplace(EVENT_DATA->entityID); } @@ -105,8 +114,9 @@ namespace SHADE { collidersToDraw.erase(EVENT_DATA->entityID); - if (collidersToDraw.empty()) - SetFlagState(DebugDrawFlags::ACTIVE, false); + // if no colliders queued for drawing and no other debug drawing is enabled, disable debug drawing + if (collidersToDraw.empty() && flags == ACTIVE_FLAG) + flags = 0; } return onColliderDrawEvent.get()->handle; diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h index 79b88d3f..14f86364 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h +++ b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.h @@ -35,11 +35,10 @@ namespace SHADE enum class DebugDrawFlags : uint8_t { - ACTIVE = 0x0001 - , COLLIDERS = 0x0002 - , CONTACTS = 0x0004 - , RAYCASTS = 0x0008 - , BROADPHASE = 0x0010 + COLLIDERS = 0x02 + , CONTACTS = 0x04 + , RAYCASTS = 0x08 + , BROADPHASE = 0x10 }; /*---------------------------------------------------------------------------------*/ @@ -53,7 +52,8 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] bool GetFlagState (DebugDrawFlags flag) const noexcept; + [[nodiscard]] bool IsDebugDrawActive () const noexcept; + [[nodiscard]] bool GetFlagState (DebugDrawFlags flag) const noexcept; /*---------------------------------------------------------------------------------*/ /* Setter Functions */ /*---------------------------------------------------------------------------------*/ @@ -64,10 +64,10 @@ namespace SHADE /* Member Functions */ /*---------------------------------------------------------------------------------*/ - void Init () override; - void Exit () override; + void Init () override; + void Exit () override; - void AddRaycast (const SHRay& ray, const SHPhysicsRaycastResult& result) noexcept; + void AddRaycast (const SHRay& ray, const SHPhysicsRaycastResult& result) noexcept; /*---------------------------------------------------------------------------------*/ /* System Routines */ @@ -123,6 +123,8 @@ namespace SHADE /* Data Members */ /*---------------------------------------------------------------------------------*/ + static constexpr uint8_t ACTIVE_FLAG = 0x01; + static const SHColour DEBUG_DRAW_COLOURS[static_cast(Colours::COUNT)]; // 0 0 0 drawBroadphase drawRaycasts drawContacts drawAllColliders debugDrawActive diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h index ab28a299..c7dff6c6 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h +++ b/SHADE_Engine/src/Physics/System/SHPhysicsSystem.h @@ -52,8 +52,11 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] double GetFixedUpdateRate() const noexcept; - [[nodiscard]] double GetFixedDT () const noexcept; + [[nodiscard]] double GetFixedUpdateRate() const noexcept; + [[nodiscard]] double GetFixedDT () const noexcept; + + [[nodiscard]] const std::vector& GetTriggerInfo () const noexcept; + [[nodiscard]] const std::vector& GetCollisionInfo () const noexcept; /*---------------------------------------------------------------------------------*/ /* Setter Functions */ @@ -62,9 +65,6 @@ namespace SHADE void SetFixedUpdateRate(double fixedUpdateRate) noexcept; void SetFixedDT(double fixedDt) noexcept; - const std::vector& GetTriggerInfo () const noexcept; - const std::vector& GetCollisionInfo () const noexcept; - /*---------------------------------------------------------------------------------*/ /* Member Functions */ /*---------------------------------------------------------------------------------*/ From 0df6e09ed6334de0ca070b1bc24d20456e6d4ee6 Mon Sep 17 00:00:00 2001 From: Diren D Bharwani Date: Sat, 24 Dec 2022 13:32:50 +0800 Subject: [PATCH 078/275] Added box collision shapes --- Assets/Scenes/PhysicsSandbox.shade | 5 +- .../Inspector/SHEditorComponentView.hpp | 24 +- SHADE_Engine/src/Math/Geometry/SHAABB.cpp | 20 +- SHADE_Engine/src/Math/Geometry/SHAABB.h | 22 +- SHADE_Engine/src/Math/Geometry/SHBox.cpp | 169 +++++++++++ SHADE_Engine/src/Math/Geometry/SHBox.h | 135 +++++++++ SHADE_Engine/src/Math/Geometry/SHShape.h | 7 +- SHADE_Engine/src/Math/SHQuaternion.cpp | 4 + SHADE_Engine/src/Math/SHQuaternion.h | 7 +- .../Broadphase/SHDynamicAABBTree.cpp | 9 + .../CollisionShapes/SHBoxCollisionShape.cpp | 265 ++++++++++++++++++ .../CollisionShapes/SHBoxCollisionShape.h | 158 +++++++++++ .../SHCollisionShapeFactory.cpp | 24 ++ .../CollisionShapes/SHCollisionShapeFactory.h | 21 +- .../CollisionShapes/SHSphereCollisionShape.h | 24 +- .../src/Physics/Collision/SHCollider.cpp | 54 ++++ .../src/Physics/Collision/SHCollider.h | 29 +- .../Routines/SHPhysicsDebugDrawRoutine.cpp | 2 +- .../System/SHPhysicsDebugDrawSystem.cpp | 2 +- .../src/Serialization/SHYAMLConverters.h | 10 +- SHADE_Managed/src/Components/Collider.cxx | 4 +- 21 files changed, 929 insertions(+), 66 deletions(-) create mode 100644 SHADE_Engine/src/Math/Geometry/SHBox.cpp create mode 100644 SHADE_Engine/src/Math/Geometry/SHBox.h create mode 100644 SHADE_Engine/src/Physics/Collision/CollisionShapes/SHBoxCollisionShape.cpp create mode 100644 SHADE_Engine/src/Physics/Collision/CollisionShapes/SHBoxCollisionShape.h diff --git a/Assets/Scenes/PhysicsSandbox.shade b/Assets/Scenes/PhysicsSandbox.shade index 60a97885..e001867a 100644 --- a/Assets/Scenes/PhysicsSandbox.shade +++ b/Assets/Scenes/PhysicsSandbox.shade @@ -106,7 +106,7 @@ Components: Transform Component: Translate: {x: 0, y: 5, z: 0} - Rotate: {x: -0, y: 0, z: -0} + Rotate: {x: -0, y: 0, z: 0} Scale: {x: 1, y: 1, z: 1} IsActive: true RigidBody Component: @@ -130,8 +130,7 @@ DrawColliders: false Colliders: - Is Trigger: false - Type: Sphere - Radius: 1 + Type: Box Friction: 0.400000006 Bounciness: 0 Density: 1 diff --git a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp index 622a2d37..cf5cdeb1 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp +++ b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp @@ -344,24 +344,24 @@ namespace SHADE //collider->IsTrigger if (shape->GetType() == SHCollisionShape::Type::BOX) { - //SHEditorWidgets::BeginPanel(std::format("{} Box #{}", ICON_FA_CUBE, i).data(), { ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y }); - // - //const auto* BOX = reinterpret_cast(collider->GetShape()); - //SHEditorWidgets::DragVec3 - //( - // "Half Extents", { "X", "Y", "Z" }, - // [BOX] { return BOX->GetRelativeExtents(); }, - // [collider](SHVec3 const& vec) { collider->SetBoundingBox(vec); }); + SHEditorWidgets::BeginPanel(std::format("{} Box #{}", ICON_FA_CUBE, i).data(), { ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y }); + + auto* box = reinterpret_cast(shape); + SHEditorWidgets::DragVec3 + ( + "Half Extents", { "X", "Y", "Z" }, + [box] { return box->GetRelativeExtents(); }, + [box](SHVec3 const& vec) { box->SetRelativeExtents(vec); }); } else if (shape->GetType() == SHCollisionShape::Type::SPHERE) { SHEditorWidgets::BeginPanel(std::format("{} Sphere #{}", ICON_MD_CIRCLE, i).data(), { ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y }); - auto* SPHERE = reinterpret_cast(shape); + auto* sphere = reinterpret_cast(shape); SHEditorWidgets::DragFloat ( "Radius", - [SPHERE] { return SPHERE->GetRelativeRadius(); }, - [SPHERE](float const& value) { SPHERE->SetRelativeRadius(value); }); + [sphere] { return sphere->GetRelativeRadius(); }, + [sphere](float const& value) { sphere->SetRelativeRadius(value); }); } else if (shape->GetType() == SHCollisionShape::Type::CAPSULE) { @@ -410,7 +410,7 @@ namespace SHADE { if (ImGui::Selectable("Box Collider")) { - //component->AddBoundingBox(); + component->GetCollider()->AddBoxCollisionShape(SHVec3::One); } if (ImGui::Selectable("Sphere Collider")) { diff --git a/SHADE_Engine/src/Math/Geometry/SHAABB.cpp b/SHADE_Engine/src/Math/Geometry/SHAABB.cpp index 8a44457f..30683216 100644 --- a/SHADE_Engine/src/Math/Geometry/SHAABB.cpp +++ b/SHADE_Engine/src/Math/Geometry/SHAABB.cpp @@ -1,5 +1,5 @@ /**************************************************************************************** - * \file SHBox.cpp + * \file SHAABB.cpp * \author Diren D Bharwani, diren.dbharwani, 390002520 * \brief Implementation for a 3-Dimensional Axis Aligned Bounding Box * @@ -26,12 +26,12 @@ namespace SHADE SHAABB::SHAABB() noexcept { - type = Type::BOX; + type = Type::AABB; } SHAABB::SHAABB(const SHVec3& c, const SHVec3& hE) noexcept { - type = Type::BOX; + type = Type::AABB; Center = c; Extents = hE; @@ -43,7 +43,7 @@ namespace SHADE if (this == &rhs) return; - type = Type::BOX; + type = Type::AABB; Center = rhs.Center; Extents = rhs.Extents; @@ -51,7 +51,7 @@ namespace SHADE SHAABB::SHAABB(SHAABB&& rhs) noexcept { - type = Type::BOX; + type = Type::AABB; Center = rhs.Center; Extents = rhs.Extents; @@ -63,7 +63,7 @@ namespace SHADE SHAABB& SHAABB::operator=(const SHAABB& rhs) noexcept { - if (rhs.type != Type::BOX) + if (rhs.type != Type::AABB) { SHLOG_WARNING("Cannot assign a non-bounding box to a bounding box!") } @@ -78,7 +78,7 @@ namespace SHADE SHAABB& SHAABB::operator=(SHAABB&& rhs) noexcept { - if (rhs.type != Type::BOX) + if (rhs.type != Type::AABB) { SHLOG_WARNING("Cannot assign a non-bounding box to a bounding box!") } @@ -100,7 +100,7 @@ namespace SHADE return Center; } - SHVec3 SHAABB::GetWorldExtents() const noexcept + SHVec3 SHAABB::GetExtents() const noexcept { return Extents; } @@ -124,9 +124,9 @@ namespace SHADE Center = newCenter; } - void SHAABB::SetWorldExtents(const SHVec3& newWorldExtents) noexcept + void SHAABB::SetExtents(const SHVec3& newHalfExtents) noexcept { - Extents = newWorldExtents; + Extents = newHalfExtents; } void SHAABB::SetMin(const SHVec3& min) noexcept diff --git a/SHADE_Engine/src/Math/Geometry/SHAABB.h b/SHADE_Engine/src/Math/Geometry/SHAABB.h index 46608ded..76e0b2b7 100644 --- a/SHADE_Engine/src/Math/Geometry/SHAABB.h +++ b/SHADE_Engine/src/Math/Geometry/SHAABB.h @@ -1,5 +1,5 @@ /**************************************************************************************** - * \file SHBox.h + * \file SHAABB.h * \author Diren D Bharwani, diren.dbharwani, 390002520 * \brief Interface for a 3-Dimensional Axis Aligned Bounding Box * @@ -54,21 +54,21 @@ namespace SHADE /* Getter Functions */ /*---------------------------------------------------------------------------------*/ - [[nodiscard]] SHVec3 GetCenter () const noexcept; - [[nodiscard]] SHVec3 GetWorldExtents () const noexcept; - [[nodiscard]] SHVec3 GetMin () const noexcept; - [[nodiscard]] SHVec3 GetMax () const noexcept; - [[nodiscard]] std::vector GetVertices () const noexcept; + [[nodiscard]] SHVec3 GetCenter () const noexcept; + [[nodiscard]] SHVec3 GetExtents () const noexcept; + [[nodiscard]] SHVec3 GetMin () const noexcept; + [[nodiscard]] SHVec3 GetMax () const noexcept; + [[nodiscard]] std::vector GetVertices () const noexcept; /*---------------------------------------------------------------------------------*/ /* Setter Functions */ /*---------------------------------------------------------------------------------*/ - void SetCenter (const SHVec3& newCenter) noexcept; - void SetWorldExtents (const SHVec3& newWorldExtents) noexcept; - void SetMin (const SHVec3& min) noexcept; - void SetMax (const SHVec3& max) noexcept; - void SetMinMax (const SHVec3& min, const SHVec3& max) noexcept; + void SetCenter (const SHVec3& newCenter) noexcept; + void SetExtents (const SHVec3& newHalfExtents) noexcept; + void SetMin (const SHVec3& min) noexcept; + void SetMax (const SHVec3& max) noexcept; + void SetMinMax (const SHVec3& min, const SHVec3& max) noexcept; /*---------------------------------------------------------------------------------*/ /* Function Members */ diff --git a/SHADE_Engine/src/Math/Geometry/SHBox.cpp b/SHADE_Engine/src/Math/Geometry/SHBox.cpp new file mode 100644 index 00000000..9dacc623 --- /dev/null +++ b/SHADE_Engine/src/Math/Geometry/SHBox.cpp @@ -0,0 +1,169 @@ +/**************************************************************************************** + * \file SHBox.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a 3-Dimensional Oriented Bounding Box + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHBox.h" +// Project Headers +#include "Math/SHMathHelpers.h" +#include "Math/SHRay.h" +#include "Math/SHQuaternion.h" + +using namespace DirectX; + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHBox::SHBox() noexcept + { + type = Type::BOX; + } + + SHBox::SHBox(const SHVec3& c, const SHVec3& hE, const SHQuaternion& o) noexcept + { + type = Type::BOX; + + Center = c; + Extents = hE; + Orientation = o; + } + + + SHBox::SHBox(const SHBox& rhs) noexcept + { + if (this == &rhs) + return; + + type = Type::BOX; + + Center = rhs.Center; + Extents = rhs.Extents; + Orientation = rhs.Orientation; + } + + SHBox::SHBox(SHBox&& rhs) noexcept + { + type = Type::BOX; + + Center = rhs.Center; + Extents = rhs.Extents; + Orientation = rhs.Orientation; + } + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHBox& SHBox::operator=(const SHBox& rhs) noexcept + { + if (rhs.type != Type::BOX) + { + SHLOG_WARNING("Cannot assign a non-bounding box to a bounding box!") + } + else if (this != &rhs) + { + Center = rhs.Center; + Extents = rhs.Extents; + Orientation = rhs.Orientation; + } + + return *this; + } + + SHBox& SHBox::operator=(SHBox&& rhs) noexcept + { + if (rhs.type != Type::BOX) + { + SHLOG_WARNING("Cannot assign a non-bounding box to a bounding box!") + } + else + { + Center = rhs.Center; + Extents = rhs.Extents; + Orientation = rhs.Orientation; + } + + return *this; + } + + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + std::vector SHBox::GetVertices() const noexcept + { + std::vector vertices; + vertices.resize(8); + GetCorners(vertices.data()); + return vertices; + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHBox::TestPoint(const SHVec3& point) const noexcept + { + return BoundingOrientedBox::Contains(point); + } + + SHRaycastResult SHBox::Raycast(const SHRay& ray) const noexcept + { + SHRaycastResult result; + + result.hit = Intersects(ray.position, ray.direction, result.distance); + if (result.hit) + { + result.position = ray.position + ray.direction * result.distance; + result.angle = SHVec3::Angle(ray.position, result.position); + } + + return result; + } + + bool SHBox::Contains(const SHBox& rhs) const noexcept + { + return BoundingOrientedBox::Contains(rhs) == CONTAINS; + } + + float SHBox::Volume() const noexcept + { + return 8.0f * (Extents.x * Extents.y * Extents.z); + } + + float SHBox::SurfaceArea() const noexcept + { + return 8.0f * ((Extents.x * Extents.y) + + (Extents.x * Extents.z) + + (Extents.y * Extents.z)); + } + + /*-----------------------------------------------------------------------------------*/ + /* Static Function Member Definitions */ + /*-----------------------------------------------------------------------------------*/ + + bool SHBox::Intersect(const SHBox& lhs, const SHBox& rhs) noexcept + { + return lhs.Intersects(rhs); + } + + SHBox SHBox::BuildFromVertices(const SHVec3* vertices, size_t numVertices, size_t stride) noexcept + { + SHBox result; + CreateFromPoints(result, numVertices, vertices, stride); + return result; + } + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Geometry/SHBox.h b/SHADE_Engine/src/Math/Geometry/SHBox.h new file mode 100644 index 00000000..bb1098bc --- /dev/null +++ b/SHADE_Engine/src/Math/Geometry/SHBox.h @@ -0,0 +1,135 @@ +/**************************************************************************************** + * \file SHBox.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a 3-Dimensional Oriented Bounding Box + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +#include + +// Project Headers +#include "SHShape.h" +#include "SH_API.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + class SH_API SHBox : public SHShape, + public DirectX::BoundingOrientedBox + { + public: + /*---------------------------------------------------------------------------------*/ + /* Static Data Members */ + /*---------------------------------------------------------------------------------*/ + + static constexpr size_t NUM_VERTICES = 8; + + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + ~SHBox () override = default; + + SHBox () noexcept; + SHBox (const SHVec3& center, const SHVec3& halfExtents, const SHQuaternion& orientation) noexcept; + SHBox (const SHBox& rhs) noexcept; + SHBox (SHBox&& rhs) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHBox& operator= (const SHBox& rhs) noexcept; + SHBox& operator= (SHBox&& rhs) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] std::vector GetVertices () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + /** + * @brief + * Checks if a point is inside the box. + * @param point + * The point to check. + * @return + * True if the point is inside the box. + */ + [[nodiscard]] bool TestPoint (const SHVec3& point) const noexcept override; + + /** + * @brief + * Casts a ray against the box. + * @param ray + * The ray to cast. + * @return + * The result of the raycast.
+ * See the corresponding header for the contents of the raycast result object. + */ + [[nodiscard]] SHRaycastResult Raycast (const SHRay& ray) const noexcept override; + + /** + * @brief + * Checks if an entire other box is contained by this box. + * @param rhs + * The box to check. + * @return + * True if the other sphere is completely contained by this box. + */ + [[nodiscard]] bool Contains (const SHBox& rhs) const noexcept; + + /** + * @brief + * Calculates the volume of the box. + */ + [[nodiscard]] float Volume () const noexcept; + + /** + * @brief + * Calculates the surface area of the box. + */ + [[nodiscard]] float SurfaceArea () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Static Function Members */ + /*---------------------------------------------------------------------------------*/ + + /** + * @brief + * Checks if two boxes are intersecting. + * @return + * True if they are intersecting. + */ + [[nodiscard]] static bool Intersect (const SHBox& lhs, const SHBox& rhs) noexcept; + + /** + * @brief + * Builds a box from a set of vertices. + * @param vertices + * The vertices to build a box from. + * @param numVertices + * The number of vertices in the set to build from. + * @param stride + * The stride between each vertex, in the instance there is data in between each + * vertex that does not define the geometry of the object. + * @return + * An box that contains all the vertices in the set. + */ + [[nodiscard]] static SHBox BuildFromVertices (const SHVec3* vertices, size_t numVertices, size_t stride = 0) noexcept; + }; + + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Math/Geometry/SHShape.h b/SHADE_Engine/src/Math/Geometry/SHShape.h index 812cb169..2b644917 100644 --- a/SHADE_Engine/src/Math/Geometry/SHShape.h +++ b/SHADE_Engine/src/Math/Geometry/SHShape.h @@ -30,10 +30,9 @@ namespace SHADE enum class Type { - BOX - , SPHERE - , CAPSULE - , CONVEX_HULL + SPHERE + , AABB + , BOX , COUNT , NONE = -1 diff --git a/SHADE_Engine/src/Math/SHQuaternion.cpp b/SHADE_Engine/src/Math/SHQuaternion.cpp index 021f0a6a..1f4645df 100644 --- a/SHADE_Engine/src/Math/SHQuaternion.cpp +++ b/SHADE_Engine/src/Math/SHQuaternion.cpp @@ -40,6 +40,10 @@ namespace SHADE : XMFLOAT4( vec4.x, vec4.y, vec4.z, vec4.w ) {} + SHQuaternion::SHQuaternion(const XMFLOAT4& xmfloat4) noexcept + : XMFLOAT4( xmfloat4 ) + {} + SHQuaternion::SHQuaternion(float _x, float _y, float _z, float _w) noexcept : XMFLOAT4( _x, _y, _z, _w ) {} diff --git a/SHADE_Engine/src/Math/SHQuaternion.h b/SHADE_Engine/src/Math/SHQuaternion.h index 93c546ca..3342c4cb 100644 --- a/SHADE_Engine/src/Math/SHQuaternion.h +++ b/SHADE_Engine/src/Math/SHQuaternion.h @@ -47,9 +47,10 @@ namespace SHADE SHQuaternion (const SHQuaternion& rhs) = default; SHQuaternion (SHQuaternion&& rhs) = default; - SHQuaternion () noexcept; - SHQuaternion (const SHVec4& vec4) noexcept; - SHQuaternion (float x, float y, float z, float w) noexcept; + SHQuaternion () noexcept; + SHQuaternion (const SHVec4& vec4) noexcept; + SHQuaternion (const XMFLOAT4& xmfloat4) noexcept; + SHQuaternion (float x, float y, float z, float w) noexcept; /*---------------------------------------------------------------------------------*/ /* Operator Overloads */ diff --git a/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.cpp b/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.cpp index 5fa93021..127494cb 100644 --- a/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.cpp +++ b/SHADE_Engine/src/Physics/Collision/Broadphase/SHDynamicAABBTree.cpp @@ -204,6 +204,8 @@ namespace SHADE removeLeaf(INDEX_TO_REMOVE); freeNode(INDEX_TO_REMOVE); + + nodeMap.erase(id); } const std::vector& SHAABBTree::Query(SHCollisionShapeID id, const SHAABB& AABB) const noexcept @@ -404,6 +406,13 @@ namespace SHADE } const int32_t PARENT = nodes[index].parent; + + if (PARENT == NULL_NODE) + { + freeNode(index); + return; + } + const int32_t GRANDPARENT = nodes[PARENT].parent; const int32_t SIBLING = nodes[PARENT].left == index ? nodes[PARENT].right : nodes[PARENT].left; diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHBoxCollisionShape.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHBoxCollisionShape.cpp new file mode 100644 index 00000000..67e497a8 --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHBoxCollisionShape.cpp @@ -0,0 +1,265 @@ +/**************************************************************************************** + * \file SHBoxCollisionShape.cpp + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Implementation for a Box Collision Shape. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#include + +// Primary Header +#include "SHBoxCollisionShape.h" + +// Project Headers +#include "Math/SHMathHelpers.h" +#include "Math/SHMatrix.h" +#include "Physics/Collision/SHCollider.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHBoxCollisionShape::SHBoxCollisionShape(SHCollisionShapeID id) noexcept + : SHCollisionShape (id, SHCollisionShape::Type::BOX) + , SHBox () + , relativeExtents { SHVec3::One } + , scale { SHVec3::One } + {} + + SHBoxCollisionShape::SHBoxCollisionShape(const SHBoxCollisionShape& rhs) noexcept + : SHCollisionShape (rhs.id, SHCollisionShape::Type::BOX) + , SHBox (rhs.Center, rhs.Extents, rhs.Orientation) + , relativeExtents { rhs.relativeExtents } + , scale { rhs.scale } + { + + material = rhs.material; + collider = rhs.collider; + transform = rhs.transform; + rotationOffset = rhs.rotationOffset; + flags = rhs.flags; + // Since all collision tags are taken from the matrix, we do not need to do a deep copy here. + collisionTag = rhs.collisionTag; + } + + SHBoxCollisionShape::SHBoxCollisionShape(SHBoxCollisionShape&& rhs) noexcept + : SHCollisionShape (rhs.id, SHCollisionShape::Type::BOX) + , SHBox (rhs.Center, rhs.Extents, rhs.Orientation) + , relativeExtents { rhs.relativeExtents } + , scale { rhs.scale } + { + + material = rhs.material; + collider = rhs.collider; + transform = rhs.transform; + rotationOffset = rhs.rotationOffset; + flags = rhs.flags; + // Since all collision tags are taken from the matrix, we do not need to do a deep copy here. + collisionTag = rhs.collisionTag; + } + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHBoxCollisionShape& SHBoxCollisionShape::operator=(const SHBoxCollisionShape& rhs) noexcept + { + if (this == &rhs) + return *this; + + // Collision Shape Properties + + id = rhs.id; + material = rhs.material; + collider = rhs.collider; + transform = rhs.transform; + rotationOffset = rhs.rotationOffset; + flags = rhs.flags; + // Since all collision tags are taken from the matrix, we do not need to do a deep copy here. + collisionTag = rhs.collisionTag; + + // Box Properties + + Center = rhs.Center; + Extents = rhs.Extents; + Orientation = rhs.Orientation; + + // Local Properties + + relativeExtents = rhs.relativeExtents; + scale = rhs.scale; + + return *this; + } + + SHBoxCollisionShape& SHBoxCollisionShape::operator=(SHBoxCollisionShape&& rhs) noexcept + { + // Collision Shape Properties + + id = rhs.id; + material = rhs.material; + collider = rhs.collider; + transform = rhs.transform; + rotationOffset = rhs.rotationOffset; + flags = rhs.flags; + // Since all collision tags are taken from the matrix, we do not need to do a deep copy here. + collisionTag = rhs.collisionTag; + + // Box Properties + + Center = rhs.Center; + Extents = rhs.Extents; + Orientation = rhs.Orientation; + + // Local Properties + + relativeExtents = rhs.relativeExtents; + scale = rhs.scale; + + return *this; + } + + /*-----------------------------------------------------------------------------------*/ + /* Getter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + SHVec3 SHBoxCollisionShape::GetCenter() const noexcept + { + return Center; + } + + SHVec3 SHBoxCollisionShape::GetWorldExtents() const noexcept + { + return Extents; + } + + SHVec3 SHBoxCollisionShape::GetRelativeExtents() const noexcept + { + return relativeExtents; + } + + SHQuaternion SHBoxCollisionShape::GetOrientation() const noexcept + { + return Orientation; + } + + /*-----------------------------------------------------------------------------------*/ + /* Setter Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHBoxCollisionShape::SetCenter(const SHVec3& newCenter) noexcept + { + Center = newCenter; + } + + void SHBoxCollisionShape::SetWorldExtents(const SHVec3& newWorldExtents) noexcept + { + Extents = newWorldExtents; + + // Recompute Relative radius + relativeExtents = 2.0f * Extents / scale; + } + + void SHBoxCollisionShape::SetRelativeExtents(const SHVec3& newRelativeExtents) noexcept + { + relativeExtents = newRelativeExtents; + + // Recompute world radius + Extents = relativeExtents * scale * 0.5f; + } + + void SHBoxCollisionShape::SetOrientation(const SHQuaternion& newOrientation) noexcept + { + Orientation = newOrientation; + } + + + void SHBoxCollisionShape::SetScale(const SHVec3& newScale) noexcept + { + scale = SHVec3::Abs(newScale); + + // Recompute world radius + Extents = relativeExtents * scale * 0.5f; + } + + /*-----------------------------------------------------------------------------------*/ + /* Public Member Function Definitions */ + /*-----------------------------------------------------------------------------------*/ + + void SHBoxCollisionShape::ComputeTransforms() noexcept + { + const SHTransform& PARENT_TRANSFORM = collider->GetTransform(); + + SetScale(PARENT_TRANSFORM.scale); + + // Recompute center + const SHQuaternion FINAL_ROT = PARENT_TRANSFORM.orientation * transform.orientation; + const SHMatrix TRS = SHMatrix::Rotate(FINAL_ROT) * SHMatrix::Translate(PARENT_TRANSFORM.position); + + Center = SHVec3::Transform(transform.position, TRS); + } + + bool SHBoxCollisionShape::TestPoint(const SHVec3& point) const noexcept + { + return SHBox::TestPoint(point); + } + + SHRaycastResult SHBoxCollisionShape::Raycast(const SHRay& ray) const noexcept + { + return SHBox::Raycast(ray); + } + + SHMatrix SHBoxCollisionShape::GetInertiaTensor(float mass) const noexcept + { + static constexpr float ONE_OVER_TWELVE = (1.0f / 12.0f); + + const float H2_PLUS_D2 = Extents.y * Extents.y + Extents.z * Extents.z; + const float W2_PLUS_H2 = Extents.x * Extents.x + Extents.y * Extents.y; + const float W2_PLUS_D2 = Extents.x * Extents.x + Extents.z * Extents.z; + + SHMatrix result; + result.m[0][0] = ONE_OVER_TWELVE * mass * H2_PLUS_D2; + result.m[1][1] = ONE_OVER_TWELVE * mass * W2_PLUS_H2; + result.m[2][2] = ONE_OVER_TWELVE * mass * W2_PLUS_D2; + return result; + } + + SHMatrix SHBoxCollisionShape::ComputeWorldTransform() const noexcept + { + const SHTransform& PARENT_TRANSFORM = collider->GetTransform(); + const SHQuaternion ROTATION = PARENT_TRANSFORM.orientation * transform.orientation; + const SHVec3 SCALE = SHVec3{ Extents } *2.0f; + + return SHMatrix::Transform + ( + Center + , ROTATION + , SCALE + ); + } + + SHAABB SHBoxCollisionShape::ComputeAABB() const noexcept + { + SHVec3 min{ std::numeric_limits::max() }; + SHVec3 max{ std::numeric_limits::lowest() }; + + const auto& VERTICES = GetVertices(); + for (auto& vtx : VERTICES) + { + min = SHVec3::Min({ vtx, min }); + max = SHVec3::Max({ vtx, max }); + } + + const SHVec3 HALF_EXTENTS = (max - min) * 0.5f; + const SHVec3 CENTROID = min + HALF_EXTENTS; + + return SHAABB{ CENTROID, HALF_EXTENTS }; + } + + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHBoxCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHBoxCollisionShape.h new file mode 100644 index 00000000..35da5e6e --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHBoxCollisionShape.h @@ -0,0 +1,158 @@ +/**************************************************************************************** + * \file SHBoxCollisionShape.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Box Collision Shape. + * + * \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or + * disclosure of this file or its contents without the prior written consent + * of DigiPen Institute of Technology is prohibited. +****************************************************************************************/ + +#pragma once + +// Project Headers +#include "Math/Geometry/SHBox.h" +#include "SHCollisionShape.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------------*/ + + /** + * @brief + * Encapsulates the information to create a box. + */ + struct SHBoxCreateInfo + { + public: + SHVec3 Center = SHVec3::Zero; + SHVec3 Extents = SHVec3::One * 0.5f; + SHVec3 RelativeExtents = SHVec3::One; + SHQuaternion Orientation = SHQuaternion::Identity; + SHVec3 Scale = SHVec3::One; + }; + + /** + * @brief + * Encapsulate a Box Collision Shape used for Physics Simulations. + */ + class SH_API SHBoxCollisionShape final : public SHCollisionShape + , private SHBox + { + private: + /*---------------------------------------------------------------------------------*/ + /* Friends */ + /*---------------------------------------------------------------------------------*/ + + friend class SHCollider; + friend class SHCollision; + friend class SHCompositeCollider; + friend class SHCollisionShapeFactory; + + public: + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHBoxCollisionShape (SHCollisionShapeID id) noexcept; + SHBoxCollisionShape (const SHBoxCollisionShape& rhs) noexcept; + SHBoxCollisionShape (SHBoxCollisionShape&& rhs) noexcept; + + ~SHBoxCollisionShape () override = default; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHBoxCollisionShape& operator= (const SHBoxCollisionShape& rhs) noexcept; + SHBoxCollisionShape& operator= (SHBoxCollisionShape&& rhs) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Getter Functions */ + /*---------------------------------------------------------------------------------*/ + + [[nodiscard]] SHVec3 GetCenter () const noexcept; + [[nodiscard]] SHVec3 GetWorldExtents () const noexcept; + [[nodiscard]] SHVec3 GetRelativeExtents () const noexcept; + [[nodiscard]] SHQuaternion GetOrientation () const noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Setter Functions */ + /*---------------------------------------------------------------------------------*/ + + void SetCenter (const SHVec3& newCenter) noexcept; + void SetWorldExtents (const SHVec3& newWorldExtents) noexcept; + void SetRelativeExtents (const SHVec3& newRelativeExtents) noexcept; + void SetOrientation (const SHQuaternion& newOrientation) noexcept; + void SetScale (const SHVec3& newScale) noexcept; + + /*---------------------------------------------------------------------------------*/ + /* Function Members */ + /*---------------------------------------------------------------------------------*/ + + /** + * @brief + * Recomputes the transform of this box. + */ + void ComputeTransforms () noexcept override; + + /** + * @brief + * Tests if a point is inside the box. + * @param point + * The point to test. + * @return + * True if the point is inside the box. + */ + [[nodiscard]] bool TestPoint (const SHVec3& point) const noexcept override; + + /** + * @brief + * Casts a ray against this box. + * @param ray + * The ray to cast. + * @return + * An object holding the results of the raycast.
+ * See the corresponding header for the contents of the object. + */ + [[nodiscard]] SHRaycastResult Raycast (const SHRay& ray) const noexcept override; + + /** + * @brief + * Computes the inertia tensor of the box. + * @param mass + * The mass of the sphere. + * @return + * The inertia tensor of the box. + */ + [[nodiscard]] SHMatrix GetInertiaTensor (float mass) const noexcept override; + + /** + * @brief + * Computes the transformation matrix of the box. + * @return + * The transformation matrix of the box. + */ + [[nodiscard]] SHMatrix ComputeWorldTransform () const noexcept override; + + /** + * @brief + * Computes the a tight-fitting AABB that contains this box. + * @return + * A tight-fitting AABB that contains this box. + */ + [[nodiscard]] SHAABB ComputeAABB () const noexcept override; + + private: + /*---------------------------------------------------------------------------------*/ + /* Data Members */ + /*---------------------------------------------------------------------------------*/ + + SHVec3 relativeExtents; + SHVec3 scale; // Intended to be passed in by the base collider. + }; + + +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.cpp b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.cpp index d6ef5f0d..97409cf0 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.cpp +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.cpp @@ -24,6 +24,10 @@ namespace SHADE // Free all shapes in each container for (auto* sphereCollisionShape : spheres | std::views::values) DestroyShape(sphereCollisionShape); + + // Free all shapes in each container + for (auto* boxCollisionShape : boxes | std::views::values) + DestroyShape(boxCollisionShape); } /*-----------------------------------------------------------------------------------*/ @@ -48,6 +52,26 @@ namespace SHADE return spheres.find(id)->second; } + SHBoxCollisionShape* SHCollisionShapeFactory::CreateBox(SHCollisionShapeID id, const SHBoxCreateInfo& createInfo) + { + const auto RESULT = boxes.emplace(id, new SHBoxCollisionShape{ id }); + if (RESULT.second) + { + SHBoxCollisionShape* box = RESULT.first->second; + + box->Center = createInfo.Center; + box->Extents = createInfo.Extents; + box->relativeExtents = createInfo.RelativeExtents; + box->Orientation = createInfo.Orientation; + box->scale = createInfo.Scale; + + return box; + } + + return boxes.find(id)->second; + } + + void SHCollisionShapeFactory::DestroyShape(SHCollisionShape* shape) { switch (shape->GetType()) diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.h index f5820317..c82d7e26 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHCollisionShapeFactory.h @@ -14,6 +14,7 @@ // Project Header #include "SHSphereCollisionShape.h" +#include "SHBoxCollisionShape.h" namespace SHADE { @@ -49,10 +50,22 @@ namespace SHADE * @param createInfo * The info to create the sphere with. * @return - * A newly sphere collision shape. + * A new sphere collision shape. */ SHSphereCollisionShape* CreateSphere (SHCollisionShapeID id, const SHSphereCreateInfo& createInfo); + /** + * @brief + * Creates a box collision shape. + * @param id + * The ID of the shape. + * @param createInfo + * The info to create the box with. + * @return + * A new box collision shape. + */ + SHBoxCollisionShape* CreateBox (SHCollisionShapeID id, const SHBoxCreateInfo& createInfo); + /** * @brief * Destroys a collision shape. @@ -63,7 +76,7 @@ namespace SHADE * @param shape * The shape to destroy. */ - void DestroyShape (SHCollisionShape* shape); + void DestroyShape (SHCollisionShape* shape); private: /*---------------------------------------------------------------------------------*/ @@ -74,13 +87,15 @@ namespace SHADE // Since we are not instancing shapes (yet?), I'd rather not iterate through an entire vector to find the shape. using Spheres = std::unordered_map; + using Boxes = std::unordered_map; /*---------------------------------------------------------------------------------*/ /* Data Members */ /*---------------------------------------------------------------------------------*/ Spheres spheres; - // TODO: Add boxes, capsules and hulls + Boxes boxes; + // TODO: Add capsules and hulls }; } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h index 6e119cd1..a1ebf6a3 100644 --- a/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h +++ b/SHADE_Engine/src/Physics/Collision/CollisionShapes/SHSphereCollisionShape.h @@ -93,7 +93,7 @@ namespace SHADE * @brief * Recomputes the transform of this sphere. */ - void ComputeTransforms () noexcept override; + void ComputeTransforms () noexcept override; /** * @brief @@ -103,7 +103,7 @@ namespace SHADE * @return * True if the point is inside the sphere. */ - [[nodiscard]] bool TestPoint (const SHVec3& point) const noexcept override; + [[nodiscard]] bool TestPoint (const SHVec3& point) const noexcept override; /** * @brief @@ -114,7 +114,7 @@ namespace SHADE * An object holding the results of the raycast.
* See the corresponding header for the contents of the object. */ - [[nodiscard]] SHRaycastResult Raycast (const SHRay& ray) const noexcept override; + [[nodiscard]] SHRaycastResult Raycast (const SHRay& ray) const noexcept override; /** * @brief @@ -124,11 +124,23 @@ namespace SHADE * @return * The inertia tensor of the sphere. */ - [[nodiscard]] SHMatrix GetInertiaTensor (float mass) const noexcept override; + [[nodiscard]] SHMatrix GetInertiaTensor (float mass) const noexcept override; - [[nodiscard]] SHMatrix ComputeWorldTransform () const noexcept override; + /** + * @brief + * Computes the transformation matrix of the sphere. + * @return + * The transformation matrix of the sphere. + */ + [[nodiscard]] SHMatrix ComputeWorldTransform () const noexcept override; - [[nodiscard]] SHAABB ComputeAABB () const noexcept override; + /** + * @brief + * Computes the a tight-fitting AABB that contains this sphere. + * @return + * A tight-fitting AABB that contains this sphere. + */ + [[nodiscard]] SHAABB ComputeAABB () const noexcept override; private: /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp index 7d7d1a60..4ce819ac 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.cpp +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.cpp @@ -332,6 +332,60 @@ namespace SHADE return static_cast(NEW_INDEX); } + int SHCollider::AddBoxCollisionShape(const SHVec3& relativeExtents, const SHVec3& posOffset, const SHVec3& rotOffset) + { + if (!shapeFactory) + { + SHLOGV_ERROR("Shape factory is unlinked with Collider {}. Unable to add new shape!", entityID) + return -1; + } + + // Compute center + const SHQuaternion FINAL_ROT = transform.orientation * SHQuaternion::FromEuler(rotOffset); + const SHMatrix TRS = SHMatrix::Rotate(FINAL_ROT) * SHMatrix::Translate(transform.position); + + // Create Sphere + const SHBoxCreateInfo BOX_CREATE_INFO + { + .Center = SHVec3::Transform(posOffset, TRS) + , .Extents = relativeExtents * SHVec3::Abs(transform.scale) * 0.5f + , .RelativeExtents = relativeExtents + , .Orientation = FINAL_ROT + , .Scale = SHVec3::Abs(transform.scale) + }; + + const uint32_t NEW_INDEX = static_cast(shapes.size()); + const SHCollisionShapeID NEW_SHAPE_ID{ entityID, NEW_INDEX }; + + SHBoxCollisionShape* box = shapeFactory->CreateBox(NEW_SHAPE_ID, BOX_CREATE_INFO); + + // Set offsets + box->collider = this; + box->SetPositionOffset(posOffset); + box->SetRotationOffset(rotOffset); + + shapes.emplace_back(box); + + if (broadphase) + broadphase->Insert(NEW_SHAPE_ID, box->ComputeAABB()); + + // Broadcast Event for adding a shape + const SHPhysicsColliderAddedEvent EVENT_DATA + { + .entityID = entityID + , .colliderType = SHCollisionShape::Type::BOX + , .colliderIndex = static_cast(NEW_INDEX) + }; + + SHEventManager::BroadcastEvent(EVENT_DATA, SH_PHYSICS_COLLIDER_ADDED_EVENT); + + if (rigidBody) + rigidBody->ComputeMassData(); + + return static_cast(NEW_INDEX); + } + + void SHCollider::RemoveCollisionShape(int index) { if (!shapeFactory) diff --git a/SHADE_Engine/src/Physics/Collision/SHCollider.h b/SHADE_Engine/src/Physics/Collision/SHCollider.h index 51b33cfb..3d80118e 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollider.h +++ b/SHADE_Engine/src/Physics/Collision/SHCollider.h @@ -123,19 +123,34 @@ namespace SHADE /** * @brief * Adds a sphere collision shape. + * @param relativeRadius + * The relative radius is constructed with respect to the world scale.
+ * Radius = max(scale.x, scale.y, scale.z) * 0.5 * relativeRadius * @param posOffset * The position offset of the sphere from the center of the collider. Defaults to a Zero Vector. * @param rotOffset * The rotation offset of the sphere from the rotation of the collider. Defaults to a Zero Vector. - * @param relativeRadius - * The relative radius is constructed with respect to the world scale.
- * Radius = max(scale.x, scale.y, scale.z) * 0.5 * relativeRadius * @return * The index of the newly added shape. */ - int AddSphereCollisionShape (float relativeRadius, const SHVec3& posOffset = SHVec3::Zero, const SHVec3& rotOffset = SHVec3::Zero); + int AddSphereCollisionShape (float relativeRadius, const SHVec3& posOffset = SHVec3::Zero, const SHVec3& rotOffset = SHVec3::Zero); - // TODO: Add Box & Capsule + /** + * @brief + * Adds a box collision shape. + * @param relativeExtents + * The relative extents are constructed with respect to the world scale.
+ * Extents = scale * 0.5 * relativeExtents + * @param posOffset + * The position offset of the box from the center of the collider. Defaults to a Zero Vector. + * @param rotOffset + * The rotation offset of the box from the rotation of the collider. Defaults to a Zero Vector. + * @return + * The index of the newly added shape. + */ + int AddBoxCollisionShape (const SHVec3& relativeExtents, const SHVec3& posOffset = SHVec3::Zero, const SHVec3& rotOffset = SHVec3::Zero); + + // TODO: Add Capsule /** * @brief @@ -146,13 +161,13 @@ namespace SHADE * @throws * Invalid argument for out-of-range indices. */ - void RemoveCollisionShape (int index); + void RemoveCollisionShape (int index); /** * @brief * Recomputes the transforms for all shapes in this composite collider. */ - void RecomputeShapes () noexcept; + void RecomputeShapes () noexcept; protected: /*---------------------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp index 8aad19f5..f4a72f82 100644 --- a/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp +++ b/SHADE_Engine/src/Physics/System/Routines/SHPhysicsDebugDrawRoutine.cpp @@ -98,7 +98,7 @@ namespace SHADE for (auto& aabb : BROADPHASE_AABBS) { // Compute AABB Transform - const SHMatrix TRS = SHMatrix::Transform(aabb.GetCenter(), SHQuaternion::Identity, aabb.GetWorldExtents() * 2.0f); + const SHMatrix TRS = SHMatrix::Transform(aabb.GetCenter(), SHQuaternion::Identity, aabb.GetExtents() * 2.0f); debugDrawSystem->DrawWireCube(TRS, AABB_COLOUR); } } diff --git a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp index 9940ad20..f0add8f2 100644 --- a/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp +++ b/SHADE_Engine/src/Physics/System/SHPhysicsDebugDrawSystem.cpp @@ -133,11 +133,11 @@ namespace SHADE case SHCollisionShape::Type::SPHERE: { debugDrawSystem->DrawWireSphere(SHAPE->ComputeWorldTransform(), DRAW_COLOUR, true); - break; } case SHCollisionShape::Type::BOX: { + debugDrawSystem->DrawWireCube(SHAPE->ComputeWorldTransform(), DRAW_COLOUR, true); break; } case SHCollisionShape::Type::CAPSULE: diff --git a/SHADE_Engine/src/Serialization/SHYAMLConverters.h b/SHADE_Engine/src/Serialization/SHYAMLConverters.h index 59ee532a..909aa665 100644 --- a/SHADE_Engine/src/Serialization/SHYAMLConverters.h +++ b/SHADE_Engine/src/Serialization/SHYAMLConverters.h @@ -132,8 +132,8 @@ namespace YAML { case SHCollisionShape::Type::BOX: { - //const auto* BOX = reinterpret_cast(rhs.GetShape()); - //node[HalfExtents] = BOX->GetRelativeExtents(); + const auto& BOX = dynamic_cast(rhs); + node[HalfExtents] = BOX.GetRelativeExtents(); } break; case SHCollisionShape::Type::SPHERE: @@ -170,7 +170,11 @@ namespace YAML { case SHCollisionShape::Type::BOX: { - + if (node[HalfExtents].IsDefined()) + { + auto* box = dynamic_cast(&rhs); + box->SetRelativeExtents(node[HalfExtents].as()); + } } break; case SHCollisionShape::Type::SPHERE: diff --git a/SHADE_Managed/src/Components/Collider.cxx b/SHADE_Managed/src/Components/Collider.cxx index 3dd1446e..79140d2b 100644 --- a/SHADE_Managed/src/Components/Collider.cxx +++ b/SHADE_Managed/src/Components/Collider.cxx @@ -137,12 +137,12 @@ namespace SHADE } Vector3 BoxCollider::HalfExtents::get() { - //return Convert::ToCLI(getNativeCollisionShape().GetWorldExtents()); + //return Convert::ToCLI(getNativeCollisionShape().GetExtents()); return Vector3::Zero; } void BoxCollider::HalfExtents::set(Vector3 value) { - //getNativeCollisionShape().SetWorldExtents(Convert::ToNative(value)); + //getNativeCollisionShape().SetExtents(Convert::ToNative(value)); } Vector3 BoxCollider::Min::get() { From ee4ec83f7a33a9e235043f613c72240e909b21e4 Mon Sep 17 00:00:00 2001 From: mushgunAX Date: Sun, 25 Dec 2022 14:13:21 +0800 Subject: [PATCH 079/275] Progress on Input Manager Fixes --- SHADE_Engine/src/Input/SHInputManager.cpp | 188 +++++++++++++++------- SHADE_Engine/src/Input/SHInputManager.h | 129 ++++++++++----- 2 files changed, 225 insertions(+), 92 deletions(-) diff --git a/SHADE_Engine/src/Input/SHInputManager.cpp b/SHADE_Engine/src/Input/SHInputManager.cpp index 01bd4c13..a1ebe1a6 100644 --- a/SHADE_Engine/src/Input/SHInputManager.cpp +++ b/SHADE_Engine/src/Input/SHInputManager.cpp @@ -159,7 +159,7 @@ namespace SHADE } } - //Mouse Positioning///////////////////////////////////// + //Mouse Positioning///////////////////////////////////////////////////////// //https://stackoverflow.com/a/6423739 //Set last positioning @@ -181,7 +181,7 @@ namespace SHADE mouseWheelVerticalDelta = mouseWheelVerticalDeltaPoll; mouseWheelVerticalDeltaPoll = 0; - //Controllers////////////////////////////////////////////////////////////// + //Controllers/////////////////////////////////////////////////////////////// controllersConnectedCount = 0; @@ -447,6 +447,137 @@ namespace SHADE } } } + + //Bindings////////////////////////////////////////////////////////////////// + for (std::pair binding : bindings) + { + SHLogicalBindingData& data = binding.second; + + if (data.bindingType == SHLogicalBindingData::SH_BINDINGTYPE::MOUSE_X) + { + double velX = 0.0; + GetMouseVelocity(&velX, nullptr); + data.value = velX * data.sensitivity * (data.inverted ? -1.0 : 1.0); + } + else if (data.bindingType == SHLogicalBindingData::SH_BINDINGTYPE::MOUSE_Y) + { + double velY = 0.0; + GetMouseVelocity(nullptr, &velY); + data.value = velY * data.sensitivity * (data.inverted ? -1.0 : 1.0); + } + else if (data.bindingType == SHLogicalBindingData::SH_BINDINGTYPE::MOUSE_SCROLL) + { + data.value = mouseWheelVerticalDelta * data.sensitivity * (data.inverted ? -1.0 : 1.0); + } + else if (data.bindingType == SHLogicalBindingData::SH_BINDINGTYPE::KB_MB_CONTROLLER) + { + //Prioritise the largest magnitude + double largestMagnitude = 0.0; + + //If digital input was in, use sensitivity + bool digitalInput = false; + + //If data was read + bool positiveInputRead = false; + bool negativeInputRead = false; + + //Over keycodes + for (SH_KEYCODE k : data.positiveKeyCodes) + { + if (GetKey(k)) + { + if (std::abs(1.0) > std::abs(largestMagnitude)) largestMagnitude = 1.0; + digitalInput = true; + positiveInputRead = true; + } + } + for (SH_KEYCODE k : data.negativeKeyCodes) + { + if (GetKey(k)) + { + if (std::abs(-1.0) > std::abs(largestMagnitude)) largestMagnitude = -1.0; + digitalInput = true; + negativeInputRead = true; + } + } + + //Over controllerCodes + for (SH_CONTROLLERCODE c : data.positiveControllerCodes) + { + double newValue = 0.0; + if (GetControllerInput(c, &newValue)) + { + positiveInputRead = true; + if (static_cast(c) < NUM_CONTROLLER_BUTTON) + { + digitalInput = true; + } + if (std::abs(newValue) > std::abs(largestMagnitude)) + largestMagnitude = newValue * data.sensitivity * (data.inverted ? -1.0 : 1.0); + } + } + + for (SH_CONTROLLERCODE c : data.negativeControllerCodes) + { + double newValue = 0.0; + if (GetControllerInput(c, &newValue)) + { + negativeInputRead = true; + if (static_cast(c) < NUM_CONTROLLER_BUTTON) + { + digitalInput = true; + } + if (std::abs(newValue) > std::abs(largestMagnitude)) + largestMagnitude = -newValue * data.sensitivity * (data.inverted ? -1.0 : 1.0); + } + } + + //If both positive and negative inputs read, do not modify value + if (positiveInputRead && negativeInputRead) + { + data.value = data.value; + } + else + { + //If no data received, use gravity + if (!positiveInputRead && !negativeInputRead) + { + if (data.value > 0.0) + { + data.value -= data.gravity * dt; + if (data.value < 0.0) data.value = 0.0; + } + if (data.value < 0.0) + { + data.value += data.gravity * dt; + if (data.value > 0.0) data.value = 0.0; + } + } + else //Either positive OR negative input was read + { + //If digital input was in, use sensitivity + if (digitalInput) + { + data.value += data.sensitivity * largestMagnitude * dt; + if (data.value > 1.0) data.value = 1.0; + else if (data.value < -1.0) data.value = -1.0; + } + else + { + data.value = largestMagnitude; + } + + if (data.snap) //Snapping + { + if (data.value > 0.0 && negativeInputRead) + data.value = 0.0; + if (data.value < 0.0 && positiveInputRead) + data.value = 0.0; + } + } + } + } + } } bool SHInputManager::AnyKeyDown(SH_KEYCODE* firstDetected) noexcept @@ -574,60 +705,9 @@ namespace SHADE return false; } - //Only get of largest magnitude double SHInputManager::GetBindingAxis(std::string const& bindingName, size_t cNum) noexcept { - if (cNum >= XUSER_MAX_COUNT) return 0.0f; - - //Over mouse movement, if used for this axis - if (bindings[bindingName].bindingType == SHLogicalBindingData::SH_BINDINGTYPE::MOUSE_X) - { - double velX = 0.0f; - GetMouseVelocity(&velX, nullptr); - return velX; - } - - if (bindings[bindingName].bindingType == SHLogicalBindingData::SH_BINDINGTYPE::MOUSE_Y) - { - double velY = 0.0f; - GetMouseVelocity(nullptr, &velY); - return velY; - } - - //Over mouse scroll, if used for this axis - if (bindings[bindingName].bindingType == SHLogicalBindingData::SH_BINDINGTYPE::MOUSE_SCROLL) - { - return mouseWheelVerticalDelta; - } - - //The largest magnitude recorded so far - double largestMagnitude = 0.0; - - //Over keycodes, prioritise positive - for (SH_KEYCODE k : bindings[bindingName].positiveKeyCodes) - { - if (GetKey(k)) if (std::abs(1.0f) > std::abs(largestMagnitude)) largestMagnitude = 1.0f; - } - for (SH_KEYCODE k : bindings[bindingName].negativeKeyCodes) - { - if (GetKey(k)) if (std::abs(-1.0f) > std::abs(largestMagnitude)) largestMagnitude = -1.0f; - } - - //Over controllerCodes - for (SH_CONTROLLERCODE c : bindings[bindingName].positiveControllerCodes) - { - double newValue = 0.0; - if (GetControllerInput(c, &newValue, nullptr, nullptr, cNum)) - if (std::abs(newValue) > std::abs(largestMagnitude)) largestMagnitude = newValue; - } - for (SH_CONTROLLERCODE c : bindings[bindingName].negativeControllerCodes) - { - double newValue = 0.0; - if (GetControllerInput(c, &newValue, nullptr, nullptr, cNum)) - if (std::abs(newValue) > std::abs(largestMagnitude)) largestMagnitude = -newValue; - } - - return largestMagnitude; + return bindings[bindingName].value; } bool SHInputManager::GetBindingPositiveButton(std::string const& bindingName, size_t cNum) noexcept diff --git a/SHADE_Engine/src/Input/SHInputManager.h b/SHADE_Engine/src/Input/SHInputManager.h index 3c503336..df996647 100644 --- a/SHADE_Engine/src/Input/SHInputManager.h +++ b/SHADE_Engine/src/Input/SHInputManager.h @@ -335,7 +335,7 @@ namespace SHADE //VALUES////////////////////////////////////////////////////////////////// //The current value of the axis binding - double value = 0.0f; + double value = 0.0; //Whether the input is inverted, //If so, positive bindings will make the value negative, @@ -345,15 +345,16 @@ namespace SHADE bool inverted = false; //When no input is present, how fast does the value fall back to neutral? - double gravity = 1.0f; + //Best to be non-negative + double gravity = 1.0; //How far the user needs to move an analog stick before application //registers the movement - double dead = 0.1f; + double dead = 0.1; - //Speed in units per second that the axis will move toward target value - //For digital inputs only - double sensitivity = 1.0f; + //Speed in units per second that the axis will move toward target value for digital + //For mouse movement / scrolling, serves as multiplier + double sensitivity = 1.0; //If enabled, axis value will reset to zero when pressing a button //that corresponds in the opposite direction @@ -388,7 +389,7 @@ namespace SHADE } //Get if controller or KB/M is presently being used - static inline bool const GetControllerInUse() noexcept + static inline bool GetControllerInUse() noexcept { return controllerInUse; } @@ -678,9 +679,9 @@ namespace SHADE //Remove a binding and all its associated inputs from the list //Returns 1 if found and removed, 0 if not found - static inline size_t BindingsRemove(std::string const& targetBindingName) noexcept + static inline size_t BindingsRemove(std::string const& bindingName) noexcept { - return bindings.erase(targetBindingName); + return bindings.erase(bindingName); } //Clears all bindings from the list @@ -695,16 +696,68 @@ namespace SHADE return bindings.size(); } - //BINDING TYPE////////////////////////////////////////////////////////////// + //BINDING VALUES//////////////////////////////////////////////////////////// - static inline void BindingsSetType(std::string const& targetBindingName, SHLogicalBindingData::SH_BINDINGTYPE const newType) + static inline bool BindingsGetInverted(std::string const& bindingName) { - bindings[targetBindingName].bindingType = newType; + return bindings[bindingName].inverted; } - static inline SHLogicalBindingData::SH_BINDINGTYPE const BindingsGetType(std::string const& targetBindingName) + static inline void BindingsSetInverted(std::string const& bindingName, bool const newValue) { - return bindings[targetBindingName].bindingType; + bindings[bindingName].inverted = newValue; + } + + static inline double BindingsGetGravity(std::string const& bindingName) + { + return bindings[bindingName].gravity; + } + + static inline void BindingsSetGravity(std::string const& bindingName, double const newValue) + { + bindings[bindingName].gravity = newValue; + } + + static inline double BindingsGetDead(std::string const& bindingName) + { + return bindings[bindingName].dead; + } + + static inline void BindingsSetDead(std::string const& bindingName, double const newValue) + { + bindings[bindingName].dead = newValue; + } + + static inline double BindingsGetSensitivity(std::string const& bindingName) + { + return bindings[bindingName].sensitivity; + } + + static inline void BindingsSetSensitivity(std::string const& bindingName, double const newValue) + { + bindings[bindingName].sensitivity = newValue; + } + + static inline bool BindingsGetSnap(std::string const& bindingName) + { + return bindings[bindingName].snap; + } + + static inline void BindingsSetSnap(std::string const& bindingName, bool const newValue) + { + bindings[bindingName].snap = newValue; + } + + //BINDING TYPE////////////////////////////////////////////////////////////// + + static inline SHLogicalBindingData::SH_BINDINGTYPE BindingsGetType(std::string const& bindingName) + { + return bindings[bindingName].bindingType; + } + + static inline void BindingsSetType(std::string const& bindingName, SHLogicalBindingData::SH_BINDINGTYPE const newType) + { + bindings[bindingName].bindingType = newType; } //POSITIVE KEYCODES///////////////////////////////////////////////////////// @@ -716,24 +769,24 @@ namespace SHADE } //Add positive SH_KEYCODE to binding - static inline void BindingsAddPositiveKeyCode(std::string const& targetBindingName, + static inline void BindingsAddPositiveKeyCode(std::string const& bindingName, SH_KEYCODE toAdd) noexcept { - bindings[targetBindingName].positiveKeyCodes.insert(toAdd); + bindings[bindingName].positiveKeyCodes.insert(toAdd); } //Remove positive SH_KEYCODE from binding //If toRemove found and removed, returns 1. Otherwise, 0. - static inline size_t BindingsRemovePositiveKeyCode(std::string const& targetBindingName, + static inline size_t BindingsRemovePositiveKeyCode(std::string const& bindingName, SH_KEYCODE toRemove) noexcept { - return bindings[targetBindingName].positiveKeyCodes.erase(toRemove); + return bindings[bindingName].positiveKeyCodes.erase(toRemove); } //Clear all positive SH_KEYCODEs from binding - static inline void BindingsClearPositiveKeyCodes(std::string const& targetBindingName) noexcept + static inline void BindingsClearPositiveKeyCodes(std::string const& bindingName) noexcept { - bindings[targetBindingName].positiveKeyCodes.clear(); + bindings[bindingName].positiveKeyCodes.clear(); } //NEGATIVE KEYCODES///////////////////////////////////////////////////////// @@ -745,24 +798,24 @@ namespace SHADE } //Add negative SH_KEYCODE to binding - static inline void BindingsAddNegativeKeyCode(std::string const& targetBindingName, + static inline void BindingsAddNegativeKeyCode(std::string const& bindingName, SH_KEYCODE toAdd) noexcept { - bindings[targetBindingName].negativeKeyCodes.insert(toAdd); + bindings[bindingName].negativeKeyCodes.insert(toAdd); } //Remove negative SH_KEYCODE from binding //If toRemove found and removed, returns 1. Otherwise, 0. - static inline size_t BindingsRemoveNegativeKeyCode(std::string const& targetBindingName, + static inline size_t BindingsRemoveNegativeKeyCode(std::string const& bindingName, SH_KEYCODE toRemove) noexcept { - return bindings[targetBindingName].negativeKeyCodes.erase(toRemove); + return bindings[bindingName].negativeKeyCodes.erase(toRemove); } //Clear all negative SH_KEYCODEs from binding - static inline void BindingsClearNegativeKeyCodes(std::string const& targetBindingName) noexcept + static inline void BindingsClearNegativeKeyCodes(std::string const& bindingName) noexcept { - bindings[targetBindingName].negativeKeyCodes.clear(); + bindings[bindingName].negativeKeyCodes.clear(); } //POSITIVE CONTROLLERCODES////////////////////////////////////////////////// @@ -774,24 +827,24 @@ namespace SHADE } //Add positive SH_CONTROLLERCODE to binding - static inline void BindingsAddPositiveControllerCode(std::string const& targetBindingName, + static inline void BindingsAddPositiveControllerCode(std::string const& bindingName, SH_CONTROLLERCODE toAdd) noexcept { - bindings[targetBindingName].positiveControllerCodes.insert(toAdd); + bindings[bindingName].positiveControllerCodes.insert(toAdd); } //Remove positive SH_CONTROLLERCODE from binding //If toRemove found and removed, returns 1. Otherwise, 0. - static inline size_t BindingsRemovePositiveControllerCode(std::string const& targetBindingName, + static inline size_t BindingsRemovePositiveControllerCode(std::string const& bindingName, SH_CONTROLLERCODE toRemove) noexcept { - return bindings[targetBindingName].positiveControllerCodes.erase(toRemove); + return bindings[bindingName].positiveControllerCodes.erase(toRemove); } //Clear all positive SH_CONTROLLERCODEs from binding - static inline void BindingsClearPositiveControllerCodes(std::string const& targetBindingName) noexcept + static inline void BindingsClearPositiveControllerCodes(std::string const& bindingName) noexcept { - bindings[targetBindingName].positiveControllerCodes.clear(); + bindings[bindingName].positiveControllerCodes.clear(); } //NEGATIVE CONTROLLERCODES////////////////////////////////////////////////// @@ -803,24 +856,24 @@ namespace SHADE } //Add negative SH_CONTROLLERCODE to binding - static inline void BindingsAddNegativeControllerCode(std::string const& targetBindingName, + static inline void BindingsAddNegativeControllerCode(std::string const& bindingName, SH_CONTROLLERCODE toAdd) noexcept { - bindings[targetBindingName].negativeControllerCodes.insert(toAdd); + bindings[bindingName].negativeControllerCodes.insert(toAdd); } //Remove negative SH_CONTROLLERCODE from binding //If toRemove found and removed, returns 1. Otherwise, 0. - static inline size_t BindingsRemoveNegativeControllerCode(std::string const& targetBindingName, + static inline size_t BindingsRemoveNegativeControllerCode(std::string const& bindingName, SH_CONTROLLERCODE toRemove) noexcept { - return bindings[targetBindingName].negativeControllerCodes.erase(toRemove); + return bindings[bindingName].negativeControllerCodes.erase(toRemove); } //Clear all negative SH_CONTROLLERCODEs from binding - static inline void BindingsClearNegativeControllerCodes(std::string const& targetBindingName) noexcept + static inline void BindingsClearNegativeControllerCodes(std::string const& bindingName) noexcept { - bindings[targetBindingName].negativeControllerCodes.clear(); + bindings[bindingName].negativeControllerCodes.clear(); } //Get the axis value of binding, between -1 and 1 for non-mouse From b035582b304fe749349d1221e3692f9c1973cf9c Mon Sep 17 00:00:00 2001 From: Brandon Mak Date: Sun, 25 Dec 2022 14:32:50 +0800 Subject: [PATCH 080/275] Renamed SHGraphicsGlobalData to SHPredefinedData - SHPredefinedData now contains the font data descriptor set layout as well - Added a function for SHPredefinedData to retrieve descriptor sets based on a bitfield - Modified descriptor sets to not be tied to a set index anymore - Descriptor set layout doesn't have a set anymore - Removed desc set index constants from SHGraphicsConstants since they aren't really needed anymore --- .../Descriptors/SHVkDescriptorSetGroup.cpp | 17 +- .../Descriptors/SHVkDescriptorSetGroup.h | 9 +- .../Descriptors/SHVkDescriptorSetLayout.cpp | 256 +++++++++--------- .../Descriptors/SHVkDescriptorSetLayout.h | 194 +++++++------ .../Graphics/Devices/SHVkLogicalDevice.cpp | 4 +- .../src/Graphics/Devices/SHVkLogicalDevice.h | 2 +- .../Graphics/MiddleEnd/Batching/SHBatch.cpp | 4 +- ...icsGlobalData.cpp => SHPredefinedData.cpp} | 77 ++++-- ...raphicsGlobalData.h => SHPredefinedData.h} | 14 +- .../MiddleEnd/Interface/SHGraphicsConstants.h | 133 ++++----- .../MiddleEnd/Interface/SHGraphicsSystem.cpp | 33 ++- .../MiddleEnd/Interface/SHRenderer.cpp | 7 +- .../Graphics/MiddleEnd/Interface/SHRenderer.h | 2 +- .../MiddleEnd/Lights/SHLightingSubSystem.cpp | 4 +- .../MiddleEnd/Pipeline/SHPipelineLibrary.cpp | 6 +- .../MiddleEnd/Pipeline/SHPipelineLibrary.h | 2 +- .../MiddleEnd/TextRendering/SHFont.cpp | 2 +- .../SHTextRenderingSubSystem.cpp | 31 +-- .../TextRendering/SHTextRenderingSubSystem.h | 4 +- .../MiddleEnd/Textures/SHTextureLibrary.cpp | 4 +- .../Graphics/RenderGraph/SHRenderGraph.cpp | 2 +- .../src/Graphics/RenderGraph/SHRenderGraph.h | 2 +- .../Graphics/RenderGraph/SHRenderGraphNode.h | 2 +- .../RenderGraph/SHRenderGraphNodeCompute.cpp | 4 +- .../RenderGraph/SHRenderGraphStorage.h | 2 +- .../SHRenderToSwapchainImageSystem.cpp | 4 +- .../src/Tools/Utilities/SHUtilities.h | 67 +++++ 27 files changed, 483 insertions(+), 405 deletions(-) rename SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/{SHGraphicsGlobalData.cpp => SHPredefinedData.cpp} (63%) rename SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/{SHGraphicsGlobalData.h => SHPredefinedData.h} (76%) diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.cpp b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.cpp index adb51586..e77234ca 100644 --- a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.cpp +++ b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.cpp @@ -53,7 +53,6 @@ namespace SHADE for (uint32_t i = 0; i < layouts.size(); ++i) { vkLayouts[i] = layouts[i]->GetVkHandle(); - setIndexing.emplace(layouts[i]->GetSetIndex(), i); } // Check for variable descriptor count @@ -87,7 +86,7 @@ namespace SHADE for (auto& binding : bindings) { BindingAndSetHash writeHash = binding.BindPoint; - writeHash |= static_cast(layouts[i]->GetSetIndex()) << 32; + writeHash |= static_cast(i) << 32; // new write for the binding updater.writeInfos.emplace_back(); @@ -208,16 +207,13 @@ namespace SHADE // Get binding + set hash BindingAndSetHash bsHash = SHVkUtil::GenBindingSetHash(set, binding); - // to index a set - uint32_t setIndex = setIndexing[set]; - // to index a write for a binding uint32_t writeInfoIndex = updater.writeHashMap[bsHash]; // Initialize info for write - writeDescSet.descriptorType = layoutsUsed[setIndex]->GetBindings()[binding].Type; + writeDescSet.descriptorType = layoutsUsed[set]->GetBindings()[binding].Type; writeDescSet.dstArrayElement = 0; - writeDescSet.dstSet = descSets[setIndex]; + writeDescSet.dstSet = descSets[set]; writeDescSet.dstBinding = binding; writeDescSet.pImageInfo = updater.writeInfos[writeInfoIndex].descImageInfos.data(); @@ -233,16 +229,13 @@ namespace SHADE // Get binding + set hash BindingAndSetHash bsHash = SHVkUtil::GenBindingSetHash(set, binding); - // to index a set - uint32_t setIndex = setIndexing[set]; - // to index a write for a binding uint32_t writeInfoIndex = updater.writeHashMap[bsHash]; // Initialize info for write - writeDescSet.descriptorType = layoutsUsed[setIndex]->GetBindings()[binding].Type; + writeDescSet.descriptorType = layoutsUsed[set]->GetBindings()[binding].Type; writeDescSet.dstArrayElement = 0; - writeDescSet.dstSet = descSets[setIndex]; + writeDescSet.dstSet = descSets[set]; writeDescSet.dstBinding = binding; writeDescSet.pBufferInfo = updater.writeInfos[writeInfoIndex].descBufferInfos.data(); diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.h b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.h index 3f42afcc..a228bc66 100644 --- a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.h +++ b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetGroup.h @@ -21,7 +21,6 @@ namespace SHADE class SHVkImageView; class SHVkBuffer; - /*---------------------------------------------------------------------------------*/ /* Type Definitions */ /*---------------------------------------------------------------------------------*/ @@ -91,10 +90,10 @@ namespace SHADE //! Descriptor pool to allocate descriptor sets Handle descPool; - //! Sometimes when we pass in a layout, the set of the layout used in the - //! shader cannot be used to index into descSets. This is to mitigate that issue - //! when we update descriptor sets. - std::unordered_map setIndexing; + ////! Sometimes when we pass in a layout, the set of the layout used in the + ////! shader cannot be used to index into descSets. This is to mitigate that issue + ////! when we update descriptor sets. + //std::unordered_map setIndexing; //! Descriptor sets std::vector descSets; diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.cpp b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.cpp index 4be8cc9e..9b921411 100644 --- a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.cpp +++ b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.cpp @@ -5,146 +5,138 @@ namespace SHADE { - /*---------------------------------------------------------------------------------*/ - /* Constructor/Destructor */ - /*---------------------------------------------------------------------------------*/ - SHVkDescriptorSetLayout::SHVkDescriptorSetLayout(Handle device, SetIndex set, const std::vector& bindings, bool genImmutableSamplers/* = false*/) - : device { device } - , layoutDesc { bindings } - , setIndex {set} + /*---------------------------------------------------------------------------------*/ + /* Constructor/Destructor */ + /*---------------------------------------------------------------------------------*/ + SHVkDescriptorSetLayout::SHVkDescriptorSetLayout(Handle device, const std::vector& bindings, bool genImmutableSamplers/* = false*/) + : device{ device } + , layoutDesc{ bindings } , immutableSampler{} + { + // Check if auto-binding point calculation configuration is valid + bool autoCalc = false; + for (const auto& binding : bindings) { - // Check if auto-binding point calculation configuration is valid - bool autoCalc = false; - for (const auto& binding : bindings) - { - if (binding.BindPoint == Binding::AUTO_CALC_BINDING) - { - autoCalc = true; - } - else if (autoCalc) - { - throw std::invalid_argument("For auto calculation of bindings, all bindings must be set to AUTO_CALC_BINDING!"); - } - } - - vk::Sampler tempVkSampler = nullptr; - if (genImmutableSamplers) - { - // Create sampler - immutableSampler = device->CreateSampler( - { - .minFilter = vk::Filter::eLinear, - .magFilter = vk::Filter::eLinear, - .addressMode = vk::SamplerAddressMode::eRepeat, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .minLod = -1000, - .maxLod = 1000 - } - ); - - tempVkSampler = immutableSampler->GetVkSampler(); - } - - - // Fill up VK bindings with auto calculated bind points if needed - std::vector layoutBindings; - layoutBindings.reserve(bindings.size()); - int bindCount = 0; - for (const auto& binding : bindings) - { - const uint32_t CURR_BIND_POINT = autoCalc ? bindCount : binding.BindPoint; - const vk::DescriptorSetLayoutBinding VK_BINDING = - { - .binding = CURR_BIND_POINT, - .descriptorType = binding.Type, - .descriptorCount = binding.DescriptorCount, - .stageFlags = binding.Stage, - .pImmutableSamplers = genImmutableSamplers ? &tempVkSampler : nullptr, - }; - layoutBindings.emplace_back(VK_BINDING); - - // Save for future reference - layoutDesc[bindCount++].BindPoint = CURR_BIND_POINT; - } - - // TODO: Check layout support with physical device - - // Prepare binding flags - std::vector combinedBindings(bindings.size()); - for (uint32_t i = 0; i < bindings.size(); ++i) - combinedBindings[i] = bindings[i].flags; - - const vk::DescriptorSetLayoutBindingFlagsCreateInfo BINDING_FLAGS_CREATE_INFO - { - .bindingCount = static_cast(bindings.size()), // Number of flags = number of bindings - .pBindingFlags = combinedBindings.data(), // address to flags - }; - - // Create the layout - const vk::DescriptorSetLayoutCreateInfo DESC_SET_LAYOUT_CREATE_INFO - { - .pNext = &BINDING_FLAGS_CREATE_INFO, - .flags = {}, - .bindingCount = static_cast(layoutBindings.size()), - .pBindings = layoutBindings.data(), - }; - setLayout = device->GetVkLogicalDevice().createDescriptorSetLayout(DESC_SET_LAYOUT_CREATE_INFO); - } - - SHVkDescriptorSetLayout::SHVkDescriptorSetLayout(SHVkDescriptorSetLayout&& rhs) noexcept - : device {rhs.device} - , setLayout {rhs.setLayout} - , layoutDesc{std::move (rhs.layoutDesc)} - , setIndex{ rhs.setIndex } - , immutableSampler{ rhs.immutableSampler } - { - rhs.setLayout = VK_NULL_HANDLE; - } - - SHVkDescriptorSetLayout::~SHVkDescriptorSetLayout() noexcept - { - // Destroy layout - if (setLayout) - device->GetVkLogicalDevice().destroyDescriptorSetLayout(setLayout); - } - - std::vector const& SHVkDescriptorSetLayout::GetBindings(void) const noexcept - { - return layoutDesc; - } - - SetIndex SHVkDescriptorSetLayout::GetSetIndex(void) const noexcept - { - return setIndex; - } - - uint32_t SHVkDescriptorSetLayout::GetNumDynamicOffsetsRequired(void) const noexcept - { - uint32_t numDynamicBindings = 0; - for (auto& binding : layoutDesc) + if (binding.BindPoint == Binding::AUTO_CALC_BINDING) { - if (binding.Type == vk::DescriptorType::eUniformBufferDynamic || binding.Type == vk::DescriptorType::eStorageBufferDynamic) - ++numDynamicBindings; + autoCalc = true; + } + else if (autoCalc) + { + throw std::invalid_argument("For auto calculation of bindings, all bindings must be set to AUTO_CALC_BINDING!"); } - - return numDynamicBindings; } - SHVkDescriptorSetLayout& SHVkDescriptorSetLayout::operator=(SHVkDescriptorSetLayout&& rhs) noexcept + vk::Sampler tempVkSampler = nullptr; + if (genImmutableSamplers) { - if (&rhs == this) - return *this; + // Create sampler + immutableSampler = device->CreateSampler( + { + .minFilter = vk::Filter::eLinear, + .magFilter = vk::Filter::eLinear, + .addressMode = vk::SamplerAddressMode::eRepeat, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .minLod = -1000, + .maxLod = 1000 + } + ); - device = rhs.device; - setLayout = rhs.setLayout; - layoutDesc = std::move(rhs.layoutDesc); - setIndex = rhs.setIndex; - immutableSampler = rhs.immutableSampler; - - rhs.setLayout = VK_NULL_HANDLE; - - return *this; + tempVkSampler = immutableSampler->GetVkSampler(); } + + // Fill up VK bindings with auto calculated bind points if needed + std::vector layoutBindings; + layoutBindings.reserve(bindings.size()); + int bindCount = 0; + for (const auto& binding : bindings) + { + const uint32_t CURR_BIND_POINT = autoCalc ? bindCount : binding.BindPoint; + const vk::DescriptorSetLayoutBinding VK_BINDING = + { + .binding = CURR_BIND_POINT, + .descriptorType = binding.Type, + .descriptorCount = binding.DescriptorCount, + .stageFlags = binding.Stage, + .pImmutableSamplers = genImmutableSamplers ? &tempVkSampler : nullptr, + }; + layoutBindings.emplace_back(VK_BINDING); + + // Save for future reference + layoutDesc[bindCount++].BindPoint = CURR_BIND_POINT; + } + + // TODO: Check layout support with physical device + + // Prepare binding flags + std::vector combinedBindings(bindings.size()); + for (uint32_t i = 0; i < bindings.size(); ++i) + combinedBindings[i] = bindings[i].flags; + + const vk::DescriptorSetLayoutBindingFlagsCreateInfo BINDING_FLAGS_CREATE_INFO + { + .bindingCount = static_cast(bindings.size()), // Number of flags = number of bindings + .pBindingFlags = combinedBindings.data(), // address to flags + }; + + // Create the layout + const vk::DescriptorSetLayoutCreateInfo DESC_SET_LAYOUT_CREATE_INFO + { + .pNext = &BINDING_FLAGS_CREATE_INFO, + .flags = {}, + .bindingCount = static_cast(layoutBindings.size()), + .pBindings = layoutBindings.data(), + }; + setLayout = device->GetVkLogicalDevice().createDescriptorSetLayout(DESC_SET_LAYOUT_CREATE_INFO); + } + + SHVkDescriptorSetLayout::SHVkDescriptorSetLayout(SHVkDescriptorSetLayout&& rhs) noexcept + : device{ rhs.device } + , setLayout{ rhs.setLayout } + , layoutDesc{ std::move(rhs.layoutDesc) } + , immutableSampler{ rhs.immutableSampler } + { + rhs.setLayout = VK_NULL_HANDLE; + } + + SHVkDescriptorSetLayout::~SHVkDescriptorSetLayout() noexcept + { + // Destroy layout + if (setLayout) + device->GetVkLogicalDevice().destroyDescriptorSetLayout(setLayout); + } + + std::vector const& SHVkDescriptorSetLayout::GetBindings(void) const noexcept + { + return layoutDesc; + } + + uint32_t SHVkDescriptorSetLayout::GetNumDynamicOffsetsRequired(void) const noexcept + { + uint32_t numDynamicBindings = 0; + for (auto& binding : layoutDesc) + { + if (binding.Type == vk::DescriptorType::eUniformBufferDynamic || binding.Type == vk::DescriptorType::eStorageBufferDynamic) + ++numDynamicBindings; + } + + return numDynamicBindings; + } + + SHVkDescriptorSetLayout& SHVkDescriptorSetLayout::operator=(SHVkDescriptorSetLayout&& rhs) noexcept + { + if (&rhs == this) + return *this; + + device = rhs.device; + setLayout = rhs.setLayout; + layoutDesc = std::move(rhs.layoutDesc); + immutableSampler = rhs.immutableSampler; + + rhs.setLayout = VK_NULL_HANDLE; + + return *this; + } + } diff --git a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.h b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.h index caa3c057..f0e1fe4e 100644 --- a/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.h +++ b/SHADE_Engine/src/Graphics/Descriptors/SHVkDescriptorSetLayout.h @@ -6,109 +6,107 @@ namespace SHADE { - /*---------------------------------------------------------------------------------*/ - /* Forward Declarations */ - /*---------------------------------------------------------------------------------*/ - class SHVkLogicalDevice; - class SHVkSampler; + /*---------------------------------------------------------------------------------*/ + /* Forward Declarations */ + /*---------------------------------------------------------------------------------*/ + class SHVkLogicalDevice; + class SHVkSampler; - /*---------------------------------------------------------------------------------*/ - /* Type Definitions */ - /*---------------------------------------------------------------------------------*/ + /*---------------------------------------------------------------------------------*/ + /* Type Definitions */ + /*---------------------------------------------------------------------------------*/ + /// + /// RAII wrapper object for a Vulkan Descriptor Set Layout object. + /// + class SHVkDescriptorSetLayout + { + public: + /*-----------------------------------------------------------------------------*/ + /* Type Definitions */ + /*-----------------------------------------------------------------------------*/ /// - /// RAII wrapper object for a Vulkan Descriptor Set Layout object. + /// Object that describes how a descriptor binding in a DescriptorSetLayout is + /// structured. /// - class SHVkDescriptorSetLayout + struct Binding { - public: - /*-----------------------------------------------------------------------------*/ - /* Type Definitions */ - /*-----------------------------------------------------------------------------*/ - /// - /// Object that describes how a descriptor binding in a DescriptorSetLayout is - /// structured. - /// - struct Binding - { - /*-------------------------------------------------------------------------*/ - /* Constants */ - /*-------------------------------------------------------------------------*/ - /// - /// If set for the "BindPoint", binding points are automatically calculated. - /// - static constexpr uint32_t AUTO_CALC_BINDING = std::numeric_limits::max(); + /*-------------------------------------------------------------------------*/ + /* Constants */ + /*-------------------------------------------------------------------------*/ + /// + /// If set for the "BindPoint", binding points are automatically calculated. + /// + static constexpr uint32_t AUTO_CALC_BINDING = std::numeric_limits::max(); - /// - /// For use in Binding DescriptorCount. - /// - static constexpr uint32_t VARIABLE_DESCRIPTOR_UPPER_BOUND = 2000; - /*-------------------------------------------------------------------------*/ - /* Data Members */ - /*-------------------------------------------------------------------------*/ - /// - /// Type of element for the descriptor. - /// - vk::DescriptorType Type = {}; - /// - /// Shader stage that this binding is for. - /// - vk::ShaderStageFlags Stage = {}; - /// - /// Binding point for the Descriptor within the Descriptor Set. - /// - uint32_t BindPoint = AUTO_CALC_BINDING; - /// - /// Number of elements in the binding. When VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT - /// is used in VkDescriptorBindingFlagBits, this value represents the upper bound. - /// - uint32_t DescriptorCount = 1; + /// + /// For use in Binding DescriptorCount. + /// + static constexpr uint32_t VARIABLE_DESCRIPTOR_UPPER_BOUND = 2000; + /*-------------------------------------------------------------------------*/ + /* Data Members */ + /*-------------------------------------------------------------------------*/ + /// + /// Type of element for the descriptor. + /// + vk::DescriptorType Type = {}; + /// + /// Shader stage that this binding is for. + /// + vk::ShaderStageFlags Stage = {}; + /// + /// Binding point for the Descriptor within the Descriptor Set. + /// + uint32_t BindPoint = AUTO_CALC_BINDING; + /// + /// Number of elements in the binding. When VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT + /// is used in VkDescriptorBindingFlagBits, this value represents the upper bound. + /// + uint32_t DescriptorCount = 1; - vk::DescriptorBindingFlags flags = {}; - }; - - /*-----------------------------------------------------------------------------*/ - /* Constructor/Destructors */ - /*-----------------------------------------------------------------------------*/ - SHVkDescriptorSetLayout() = delete; - /// - /// Constructs a DescriptorSetLayout with the specified properties and device. - /// - /// - /// - SHVkDescriptorSetLayout(Handle device, SetIndex setIndex, const std::vector& bindings, bool genImmutableSamplers = false); - SHVkDescriptorSetLayout(const SHVkDescriptorSetLayout&) = delete; - SHVkDescriptorSetLayout(SHVkDescriptorSetLayout&& rhs) noexcept; - /// - /// Destructor which will unload and deallocate all resources for this Set. - /// - ~SHVkDescriptorSetLayout() noexcept; - - /*-----------------------------------------------------------------------------*/ - /* Overloaded Operators */ - /*-----------------------------------------------------------------------------*/ - SHVkDescriptorSetLayout& operator=(const SHVkDescriptorSetLayout&) = delete; - SHVkDescriptorSetLayout& operator=(SHVkDescriptorSetLayout&& rhs) noexcept; - - /*-----------------------------------------------------------------------------*/ - /* Getter Functions */ - /*-----------------------------------------------------------------------------*/ - /// - /// Retrieves the handle to the Vulkan Descriptor Set Layout handle. - /// - /// Handle to the Vulkan Descriptor Set Layout handle. - inline const vk::DescriptorSetLayout& GetVkHandle() const { return setLayout; } - std::vector const& GetBindings (void) const noexcept; - SetIndex GetSetIndex (void) const noexcept; - uint32_t GetNumDynamicOffsetsRequired (void) const noexcept; - - private: - /*-----------------------------------------------------------------------------*/ - /* Data Members */ - /*-----------------------------------------------------------------------------*/ - Handle device; - vk::DescriptorSetLayout setLayout; - std::vector layoutDesc; // Stores description of the layout - SetIndex setIndex; // Index of the set - Handle immutableSampler; + vk::DescriptorBindingFlags flags = {}; }; + + /*-----------------------------------------------------------------------------*/ + /* Constructor/Destructors */ + /*-----------------------------------------------------------------------------*/ + SHVkDescriptorSetLayout() = delete; + /// + /// Constructs a DescriptorSetLayout with the specified properties and device. + /// + /// + /// + SHVkDescriptorSetLayout(Handle device, const std::vector& bindings, bool genImmutableSamplers = false); + SHVkDescriptorSetLayout(const SHVkDescriptorSetLayout&) = delete; + SHVkDescriptorSetLayout(SHVkDescriptorSetLayout&& rhs) noexcept; + /// + /// Destructor which will unload and deallocate all resources for this Set. + /// + ~SHVkDescriptorSetLayout() noexcept; + + /*-----------------------------------------------------------------------------*/ + /* Overloaded Operators */ + /*-----------------------------------------------------------------------------*/ + SHVkDescriptorSetLayout& operator=(const SHVkDescriptorSetLayout&) = delete; + SHVkDescriptorSetLayout& operator=(SHVkDescriptorSetLayout&& rhs) noexcept; + + /*-----------------------------------------------------------------------------*/ + /* Getter Functions */ + /*-----------------------------------------------------------------------------*/ + /// + /// Retrieves the handle to the Vulkan Descriptor Set Layout handle. + /// + /// Handle to the Vulkan Descriptor Set Layout handle. + inline const vk::DescriptorSetLayout& GetVkHandle() const { return setLayout; } + std::vector const& GetBindings(void) const noexcept; + uint32_t GetNumDynamicOffsetsRequired(void) const noexcept; + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + Handle device; + vk::DescriptorSetLayout setLayout; + std::vector layoutDesc; // Stores description of the layout + Handle immutableSampler; + }; } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.cpp b/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.cpp index 95cf2e91..6a6e385f 100644 --- a/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.cpp +++ b/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.cpp @@ -561,9 +561,9 @@ namespace SHADE } - Handle SHVkLogicalDevice::CreateDescriptorSetLayout(SetIndex setIndex, std::vector const& bindings, bool genImmutableSamplers/* = false*/) noexcept + Handle SHVkLogicalDevice::CreateDescriptorSetLayout(std::vector const& bindings, bool genImmutableSamplers/* = false*/) noexcept { - return SHVkInstance::GetResourceManager().Create (GetHandle(), setIndex, bindings, genImmutableSamplers); + return SHVkInstance::GetResourceManager().Create (GetHandle(), bindings, genImmutableSamplers); } Handle SHVkLogicalDevice::CreateDescriptorPools(const SHVkDescriptorPool::Config& config /*= {}*/) noexcept diff --git a/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.h b/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.h index ed09b482..6e3bb0ce 100644 --- a/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.h +++ b/SHADE_Engine/src/Graphics/Devices/SHVkLogicalDevice.h @@ -190,7 +190,7 @@ namespace SHADE Handle CreateRenderpass (std::span const vkDescriptions, std::vector const& subpasses) noexcept; Handle CreateRenderpass (std::span const vkDescriptions, std::span const spDescs, std::span const spDeps) noexcept; Handle CreateFramebuffer (Handle const& renderpassHdl, std::vector> const& attachments, uint32_t inWidth, uint32_t inHeight) noexcept; - Handle CreateDescriptorSetLayout (SetIndex setIndex, std::vector const& bindings, bool genImmutableSamplers = false) noexcept; + Handle CreateDescriptorSetLayout (std::vector const& bindings, bool genImmutableSamplers = false) noexcept; Handle CreateDescriptorPools (const SHVkDescriptorPool::Config& config = {}) noexcept; Handle CreateDescriptorSetGroup(Handle pool, std::vector> const& layouts, diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.cpp index 211c50b7..cb2806aa 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.cpp @@ -25,7 +25,7 @@ of DigiPen Institute of Technology is prohibited. #include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" #include "ECS_Base/Managers/SHComponentManager.h" #include "Math/Transform/SHTransformComponent.h" -#include "Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h" +#include "Graphics/MiddleEnd/GlobalData/SHPredefinedData.h" #include "Graphics/Descriptors/SHVkDescriptorPool.h" #include "Scene/SHSceneManager.h" #include "UI/SHUIComponent.h" @@ -607,7 +607,7 @@ namespace SHADE { matPropsDescSet[frameIndex] = descPool->Allocate ( - { SHGraphicsGlobalData::GetDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE] }, + { SHPredefinedData::GetPredefinedDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE] }, { 0 } ); #ifdef _DEBUG diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.cpp similarity index 63% rename from SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.cpp rename to SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.cpp index 87234a6b..d93e073b 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.cpp @@ -1,30 +1,24 @@ #include "SHpch.h" -#include "SHGraphicsGlobalData.h" +#include "SHPredefinedData.h" #include "Graphics/Devices/SHVkLogicalDevice.h" #include "Graphics/Pipeline/SHPipelineState.h" #include "Graphics/Pipeline/SHVkPipelineLayout.h" #include "Graphics/Descriptors/SHVkDescriptorSetLayout.h" #include "Graphics/MiddleEnd/Lights/SHLightData.h" -#include "Tools/Utilities/SHUtilities.h" namespace SHADE { /*-----------------------------------------------------------------------------------*/ /* Static Definitions */ /*-----------------------------------------------------------------------------------*/ - std::vector> SHGraphicsGlobalData::globalDescSetLayouts; - SHVertexInputState SHGraphicsGlobalData::defaultVertexInputState; - Handle SHGraphicsGlobalData::dummyPipelineLayout; - - void SHGraphicsGlobalData::InitHighFrequencyGlobalData(void) noexcept - { - - } + std::vector> SHPredefinedData::predefinedLayouts; + SHVertexInputState SHPredefinedData::defaultVertexInputState; + Handle SHPredefinedData::dummyPipelineLayout; /*-----------------------------------------------------------------------------------*/ /* Function Definitions */ /*-----------------------------------------------------------------------------------*/ - void SHGraphicsGlobalData::InitDescSetLayouts(Handle logicalDevice) noexcept + void SHPredefinedData::InitDescSetLayouts(Handle logicalDevice) noexcept { SHVkDescriptorSetLayout::Binding genericDataBinding { @@ -44,7 +38,7 @@ namespace SHADE }; // For global data (generic data and textures) - Handle staticGlobalLayout = logicalDevice->CreateDescriptorSetLayout(SHGraphicsConstants::DescriptorSetIndex::STATIC_GLOBALS, { genericDataBinding, texturesBinding }); + Handle staticGlobalLayout = logicalDevice->CreateDescriptorSetLayout({ genericDataBinding, texturesBinding }); SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, staticGlobalLayout->GetVkHandle(), "[Descriptor Set Layout] Static Globals"); @@ -72,8 +66,8 @@ namespace SHADE } // For Dynamic global data (lights) - Handle dynamicGlobalLayout = logicalDevice->CreateDescriptorSetLayout(SHGraphicsConstants::DescriptorSetIndex::DYNAMIC_GLOBALS, lightBindings); - SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, dynamicGlobalLayout->GetVkHandle(), "[Descriptor Set Layout] Dynamic Globals"); + Handle lightDataDescSetLayout = logicalDevice->CreateDescriptorSetLayout(lightBindings); + SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, lightDataDescSetLayout->GetVkHandle(), "[Descriptor Set Layout] Dynamic Globals"); // For High frequency global data (camera) SHVkDescriptorSetLayout::Binding cameraDataBinding @@ -83,7 +77,7 @@ namespace SHADE .BindPoint = SHGraphicsConstants::DescriptorSetBindings::CAMERA_DATA, .DescriptorCount = 1, }; - Handle cameraDataGlobalLayout = logicalDevice->CreateDescriptorSetLayout(SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS, { cameraDataBinding }); + Handle cameraDataGlobalLayout = logicalDevice->CreateDescriptorSetLayout({ cameraDataBinding }); SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, cameraDataGlobalLayout->GetVkHandle(), "[Descriptor Set Layout] High Frequency Globals"); // For per instance data (transforms, materials, etc.) @@ -94,21 +88,41 @@ namespace SHADE .BindPoint = SHGraphicsConstants::DescriptorSetBindings::BATCHED_PER_INST_DATA, .DescriptorCount = 1, }; - Handle materialDataPerInstanceLayout = logicalDevice->CreateDescriptorSetLayout(SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE, { materialDataBinding }); + Handle materialDataPerInstanceLayout = logicalDevice->CreateDescriptorSetLayout({ materialDataBinding }); SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, materialDataPerInstanceLayout->GetVkHandle(), "[Descriptor Set Layout] Material Globals"); + // font bitmap data (texture) + SHVkDescriptorSetLayout::Binding fontBitmapBinding + { + .Type = vk::DescriptorType::eCombinedImageSampler, + .Stage = vk::ShaderStageFlagBits::eFragment, + .BindPoint = SHGraphicsConstants::DescriptorSetBindings::FONT_BITMAP_DATA, + .DescriptorCount = 1, + }; + + // font data in the form of matrices + SHVkDescriptorSetLayout::Binding fontMatrixBinding + { + .Type = vk::DescriptorType::eStorageBuffer, + .Stage = vk::ShaderStageFlagBits::eVertex, + .BindPoint = SHGraphicsConstants::DescriptorSetBindings::FONT_MATRIX_DATA, + .DescriptorCount = 1, + }; + + Handle fontDataDescSetLayout = logicalDevice->CreateDescriptorSetLayout({ fontBitmapBinding, fontMatrixBinding }); + SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, fontDataDescSetLayout->GetVkHandle(), "[Descriptor Set Layout] Font Data"); - globalDescSetLayouts.push_back(staticGlobalLayout); - globalDescSetLayouts.push_back(dynamicGlobalLayout); - globalDescSetLayouts.push_back(cameraDataGlobalLayout); - globalDescSetLayouts.push_back(materialDataPerInstanceLayout); + predefinedLayouts.push_back(staticGlobalLayout); + predefinedLayouts.push_back(lightDataDescSetLayout); + predefinedLayouts.push_back(cameraDataGlobalLayout); + predefinedLayouts.push_back(materialDataPerInstanceLayout); + predefinedLayouts.push_back(fontDataDescSetLayout); - - dummyPipelineLayout = logicalDevice->CreatePipelineLayoutDummy(SHPipelineLayoutParamsDummy{globalDescSetLayouts}); + dummyPipelineLayout = logicalDevice->CreatePipelineLayoutDummy(SHPipelineLayoutParamsDummy{predefinedLayouts}); } - void SHGraphicsGlobalData::InitDefaultVertexInputState(void) noexcept + void SHPredefinedData::InitDefaultVertexInputState(void) noexcept { defaultVertexInputState.AddBinding(false, false, { SHVertexAttribute(SHAttribFormat::FLOAT_3D) }); // positions at binding 0 defaultVertexInputState.AddBinding(false, false, { SHVertexAttribute(SHAttribFormat::FLOAT_2D) }); // UVs at binding 1 @@ -118,24 +132,31 @@ namespace SHADE defaultVertexInputState.AddBinding(true, true, { SHVertexAttribute(SHAttribFormat::UINT32_2D) }); // Instanced integer data at index 8 } - void SHGraphicsGlobalData::Init(Handle logicalDevice) noexcept + void SHPredefinedData::Init(Handle logicalDevice) noexcept { InitDescSetLayouts(logicalDevice); InitDefaultVertexInputState(); } - std::vector> const& SHGraphicsGlobalData::GetDescSetLayouts(void) noexcept + std::vector> SHPredefinedData::GetPredefinedDescSetLayouts(SHEnumWrapper types) noexcept { - return globalDescSetLayouts; + std::vector> layoutsFound; + for (uint8_t i = 0; i < SHGraphicsConstants::numPredefinedDescSetLayoutTypes; ++i) + { + if (types & (static_cast(1) << i)) + layoutsFound.push_back(predefinedLayouts[i]); + } + + return layoutsFound; } - SHVertexInputState const& SHGraphicsGlobalData::GetDefaultViState(void) noexcept + SHVertexInputState const& SHPredefinedData::GetDefaultViState(void) noexcept { return defaultVertexInputState; } - Handle SHGraphicsGlobalData::GetDummyPipelineLayout(void) noexcept + Handle SHPredefinedData::GetDummyPipelineLayout(void) noexcept { return dummyPipelineLayout; } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.h similarity index 76% rename from SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h rename to SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.h index 439acba5..5f13a100 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.h @@ -3,6 +3,7 @@ #include "SH_API.h" #include "Graphics/Pipeline/SHPipelineState.h" #include "Graphics/MiddleEnd/Interface/SHGraphicsConstants.h" +#include "Tools/Utilities/SHUtilities.h" namespace SHADE { @@ -11,11 +12,11 @@ namespace SHADE class SHVkDescriptorSetGroup; class SHVkPipelineLayout; - class SH_API SHGraphicsGlobalData + class SH_API SHPredefinedData { private: //! Global descriptor set layouts. Used to allocate descriptor sets - static std::vector> globalDescSetLayouts; + static std::vector> predefinedLayouts; //! Default vertex input state (used by everything). static SHVertexInputState defaultVertexInputState; @@ -24,7 +25,6 @@ namespace SHADE //! we create a dummy pipeline layout to use it for binding. static Handle dummyPipelineLayout; - static void InitHighFrequencyGlobalData (void) noexcept; static void InitDescSetLayouts (Handle logicalDevice) noexcept; static void InitDefaultVertexInputState (void) noexcept; @@ -32,7 +32,7 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* Constructors */ /*-----------------------------------------------------------------------*/ - SHGraphicsGlobalData() = delete; + SHPredefinedData() = delete; /*-----------------------------------------------------------------------*/ /* PUBLIC MEMBER FUNCTIONS */ @@ -42,8 +42,8 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* SETTERS AND GETTERS */ /*-----------------------------------------------------------------------*/ - static std::vector> const& GetDescSetLayouts (void) noexcept; - static SHVertexInputState const& GetDefaultViState (void) noexcept; - static Handle GetDummyPipelineLayout (void) noexcept; + static std::vector> GetPredefinedDescSetLayouts (SHEnumWrapper types) noexcept; + static SHVertexInputState const& GetDefaultViState (void) noexcept; + static Handle GetDummyPipelineLayout (void) noexcept; }; } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h index e6051841..e0b76555 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h @@ -25,74 +25,85 @@ namespace SHADE struct SHGraphicsConstants { public: + static constexpr uint8_t numPredefinedDescSetLayoutTypes = 64; + + enum class SHPredefinedDescSetLayoutTypes : uint64_t + { + STATIC_DATA = 0x01, + LIGHTS = 0x02, + CAMERA = 0x04, + MATERIALS = 0x08, + FONT = 0x10, + }; + struct RenderGraphIndices { static constexpr uint32_t WORLD = 0; static constexpr uint32_t EDITOR = 0; }; - struct DescriptorSetIndex - { - /***************************************************************************/ - /*! - \brief - DescriptorSet Index for static global values like generic data, and - texture samplers - */ - /***************************************************************************/ - static constexpr uint32_t STATIC_GLOBALS = 0; - /***************************************************************************/ - /*! - \brief - DescriptorSet Index for dynamic global values like lights. - */ - /***************************************************************************/ - static constexpr uint32_t DYNAMIC_GLOBALS = 1; - /***************************************************************************/ - /*! - \brief - DescriptorSet Index for high frequency changing global values like - camera matrices. - */ - /***************************************************************************/ - static constexpr uint32_t HIGH_FREQUENCY_GLOBALS = 2; - /***************************************************************************/ - /*! - \brief - DescriptorSet Index for per-instance/material changing values. - */ - /***************************************************************************/ - static constexpr uint32_t PER_INSTANCE = 3; - /***************************************************************************/ - /*! - \brief - DescriptorSet Index for render graph resources. Unlike the sets from - 1 to 3 and 6, this set index does not have hard coded bindings and is - NOT part of the layouts included in the global data. - */ - /***************************************************************************/ - static constexpr uint32_t RENDERGRAPH_RESOURCE = 4; - /***************************************************************************/ - /*! - \brief - DescriptorSet Index for render graph node compute resources. For data - that we wish to pass to compute shaders in the render graph, this is - the set to use. Unlike the sets from 1 to 3 and 6, this set index does not have - hard coded bindings and is NOT part of the layouts included in the global - data. - */ - /***************************************************************************/ - static constexpr uint32_t RENDERGRAPH_NODE_COMPUTE_RESOURCE = 5; + //struct DescriptorSetIndex + //{ + // /***************************************************************************/ + // /*! + // \brief + // DescriptorSet Index for static global values like generic data, and + // texture samplers + // */ + // /***************************************************************************/ + // static constexpr uint32_t STATIC_GLOBALS = 0; + // /***************************************************************************/ + // /*! + // \brief + // DescriptorSet Index for dynamic global values like lights. + // */ + // /***************************************************************************/ + // static constexpr uint32_t DYNAMIC_GLOBALS = 1; + // /***************************************************************************/ + // /*! + // \brief + // DescriptorSet Index for high frequency changing global values like + // camera matrices. + // */ + // /***************************************************************************/ + // static constexpr uint32_t HIGH_FREQUENCY_GLOBALS = 2; + // /***************************************************************************/ + // /*! + // \brief + // DescriptorSet Index for per-instance/material changing values. + // */ + // /***************************************************************************/ + // static constexpr uint32_t PER_INSTANCE = 3; + // /***************************************************************************/ + // /*! + // \brief + // DescriptorSet Index for render graph resources. Unlike the sets from + // 1 to 3 and 6, this set index does not have hard coded bindings and is + // NOT part of the layouts included in the global data. + // */ + // /***************************************************************************/ + // static constexpr uint32_t RENDERGRAPH_RESOURCE = 4; + // /***************************************************************************/ + // /*! + // \brief + // DescriptorSet Index for render graph node compute resources. For data + // that we wish to pass to compute shaders in the render graph, this is + // the set to use. Unlike the sets from 1 to 3 and 6, this set index does not have + // hard coded bindings and is NOT part of the layouts included in the global + // data. + // */ + // /***************************************************************************/ + // static constexpr uint32_t RENDERGRAPH_NODE_COMPUTE_RESOURCE = 5; - /***************************************************************************/ - /*! - \brief - To store font data. - - */ - /***************************************************************************/ - static constexpr uint32_t FONT_DATA = 4; - }; + // /***************************************************************************/ + // /*! + // \brief + // To store font data. + // + // */ + // /***************************************************************************/ + // static constexpr uint32_t FONT_DATA = 4; + //}; struct DescriptorSetBindings { diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp index 9a599a07..c9e7f2d2 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp @@ -31,7 +31,7 @@ of DigiPen Institute of Technology is prohibited. #include "Graphics/MiddleEnd/Interface/SHRenderable.h" #include "Graphics/MiddleEnd/Batching/SHSuperBatch.h" #include "SHGraphicsConstants.h" -#include "Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h" +#include "Graphics/MiddleEnd/GlobalData/SHPredefinedData.h" #include "Graphics/Buffers/SHVkBuffer.h" #include "Graphics/Images/SHVkSampler.h" #include "Assets/Asset Types/SHTextureAsset.h" @@ -124,6 +124,9 @@ namespace SHADE SHFreetypeInstance::Init(); + SHAssetManager::CompileAsset("../../Assets/Shaders/DebugDraw_VS.glsl", false); + SHAssetManager::CompileAsset("../../Assets/Shaders/DebugDraw_FS.glsl", false); + // Load Built In Shaders static constexpr AssetID VS_DEFAULT = 39210065; defaultVertShader = SHResourceManager::LoadOrGet(VS_DEFAULT); static constexpr AssetID FS_DEFAULT = 46377769; defaultFragShader = SHResourceManager::LoadOrGet(FS_DEFAULT); @@ -318,12 +321,12 @@ namespace SHADE /* BIND RENDER GRAPH TO RENDERER */ /*-----------------------------------------------------------------------*/ // Add world renderer to default viewport - worldRenderer = worldViewport->AddRenderer(resourceManager, swapchain->GetNumImages(), renderContextCmdPools, descPool, SHGraphicsGlobalData::GetDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS], worldRenderGraph); + worldRenderer = worldViewport->AddRenderer(resourceManager, swapchain->GetNumImages(), renderContextCmdPools, descPool, SHPredefinedData::GetPredefinedDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS], worldRenderGraph); worldRenderer->SetCamera(worldCamera); worldRenderer->SetCameraDirector(worldCameraDirector); // Add screen renderer to default viewport - screenRenderer = worldViewport->AddRenderer(resourceManager, swapchain->GetNumImages(), renderContextCmdPools, descPool, SHGraphicsGlobalData::GetDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS], screenRenderGraph); + screenRenderer = worldViewport->AddRenderer(resourceManager, swapchain->GetNumImages(), renderContextCmdPools, descPool, SHPredefinedData::GetPredefinedDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS], screenRenderGraph); screenRenderer->SetCamera(screenCamera); screenRenderer->SetCameraDirector(worldCameraDirector); @@ -356,7 +359,7 @@ namespace SHADE void SHGraphicsSystem::InitMiddleEnd(void) noexcept { - SHGraphicsGlobalData::Init(device); + SHPredefinedData::Init(device); InitSceneRenderGraph(); @@ -454,7 +457,7 @@ namespace SHADE editorRenderGraph->Generate(); // Add world renderer to default viewport - editorRenderer = editorViewport->AddRenderer(resourceManager, swapchain->GetNumImages(), renderContextCmdPools, descPool, SHGraphicsGlobalData::GetDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS], editorRenderGraph); + editorRenderer = editorViewport->AddRenderer(resourceManager, swapchain->GetNumImages(), renderContextCmdPools, descPool, SHPredefinedData::GetPredefinedDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS], editorRenderGraph); editorRenderer->SetCamera(worldCamera); } #endif @@ -567,8 +570,8 @@ namespace SHADE currentCmdBuffer->SetViewportScissor (static_cast(w), static_cast(h), w, h); // Force set the pipeline layout - currentCmdBuffer->ForceSetPipelineLayout(SHGraphicsGlobalData::GetDummyPipelineLayout(), SH_PIPELINE_TYPE::GRAPHICS); - currentCmdBuffer->ForceSetPipelineLayout(SHGraphicsGlobalData::GetDummyPipelineLayout(), SH_PIPELINE_TYPE::COMPUTE); + currentCmdBuffer->ForceSetPipelineLayout(SHPredefinedData::GetDummyPipelineLayout(), SH_PIPELINE_TYPE::GRAPHICS); + currentCmdBuffer->ForceSetPipelineLayout(SHPredefinedData::GetDummyPipelineLayout(), SH_PIPELINE_TYPE::COMPUTE); // Bind all the buffers required for meshes for (auto& [buffer, bindingPoint] : MESH_DATA) @@ -900,7 +903,7 @@ namespace SHADE void SHGraphicsSystem::BuildFonts(void) noexcept { - fontLibrary.BuildFonts(device, graphicsQueue, graphicsCmdPool, descPool, textRenderingSubSystem->GetFontDataDescSetLayout(), resourceManager); + fontLibrary.BuildFonts(device, graphicsQueue, graphicsCmdPool, descPool, SHPredefinedData::GetPredefinedDescSetLayouts(SHGraphicsConstants::SHPredefinedDescSetLayoutTypes::FONT)[0], resourceManager); } #pragma endregion ADD_REMOVE @@ -1044,6 +1047,10 @@ namespace SHADE graphSemaphores[0].Free(); graphSemaphores[1].Free(); + for (auto& semaHandle : graphSemaphores) + semaHandle = device->CreateSemaphore(); + + auto windowDims = window->GetWindowSize(); // Resize the swapchain @@ -1054,6 +1061,12 @@ namespace SHADE worldRenderGraph->HandleResize(resizeWidth, resizeHeight); #ifdef SHEDITOR + + // NOTE: These 2 lines are actually not necessary because the editor viewport is not even used for + // setting dynamic viewport or scissor state. ImGUI takes care of that for us. + //editorViewport->SetWidth(windowDims.first); + //editorViewport->SetHeight(windowDims.second); + editorRenderGraph->HandleResize(windowDims.first, windowDims.second); #endif @@ -1076,8 +1089,6 @@ namespace SHADE #endif - for (auto& semaHandle : graphSemaphores) - semaHandle = device->CreateSemaphore(); } void SHGraphicsSystem::AwaitGraphicsExecution() @@ -1126,7 +1137,7 @@ namespace SHADE device, SHPipelineLayoutParams { .shaderModules = { (instanced ? debugMeshVertShader : debugVertShader) , debugFragShader }, - .globalDescSetLayouts = SHGraphicsGlobalData::GetDescSetLayouts() + .globalDescSetLayouts = SHPredefinedData::GetPredefinedDescSetLayouts(SHGraphicsConstants::SHPredefinedDescSetLayoutTypes::CAMERA) } ); auto pipeline = resourceManager.Create(device, pipelineLayout, nullptr, renderPass, subpass); diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.cpp index e47055df..1136a2c9 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.cpp @@ -51,9 +51,12 @@ namespace SHADE std::array cameraBufferArray{cameraBuffer}; - cameraDescriptorSet->ModifyWriteDescBuffer(SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS, SHGraphicsConstants::DescriptorSetBindings::CAMERA_DATA, std::span>{ cameraBufferArray.data(), cameraBufferArray.size()}, 0, sizeof (SHShaderCameraData)); + // We use index 0 because the descriptor set is standalone created from a single desc set layout. What the driver sees is that this set is at index 0 during updating. + static constexpr uint8_t SET_0 = 0; - cameraDescriptorSet->UpdateDescriptorSetBuffer(SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS, SHGraphicsConstants::DescriptorSetBindings::CAMERA_DATA); + cameraDescriptorSet->ModifyWriteDescBuffer(SET_0, SHGraphicsConstants::DescriptorSetBindings::CAMERA_DATA, std::span>{ cameraBufferArray.data(), cameraBufferArray.size()}, 0, sizeof (SHShaderCameraData)); + + cameraDescriptorSet->UpdateDescriptorSetBuffer(SET_0, SHGraphicsConstants::DescriptorSetBindings::CAMERA_DATA); } SHRenderer::~SHRenderer(void) diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.h index 83291700..4bd205be 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.h @@ -37,7 +37,7 @@ namespace SHADE class SHVkCommandBuffer; class SHCamera; class SHVkDescriptorSetGroup; - class SHGraphicsGlobalData; + class SHPredefinedData; class SHVkDescriptorPool; class SHVkBuffer; class SHCameraDirector; diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.cpp index 3d5a5773..448c3bed 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.cpp @@ -1,6 +1,6 @@ #include "SHpch.h" #include "SHLightingSubSystem.h" -#include "Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h" +#include "Graphics/MiddleEnd/GlobalData/SHPredefinedData.h" #include "Tools/Utilities/SHUtilities.h" #include "Graphics/Devices/SHVkLogicalDevice.h" #include "Graphics/Buffers/SHVkBuffer.h" @@ -385,7 +385,7 @@ namespace SHADE std::fill (variableSizes.begin(), variableSizes.end(), 1); // Create the descriptor set - lightingDataDescSet = descPool->Allocate({ SHGraphicsGlobalData::GetDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::DYNAMIC_GLOBALS] }, variableSizes); + lightingDataDescSet = descPool->Allocate({ SHPredefinedData::GetPredefinedDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::DYNAMIC_GLOBALS] }, variableSizes); #ifdef _DEBUG const auto& CAM_DESC_SETS = lightingDataDescSet->GetVkHandle(); for (int i = 0; i < static_cast(CAM_DESC_SETS.size()); ++i) diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.cpp index 05bd8813..9e6f1bd7 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.cpp @@ -1,7 +1,7 @@ #include "SHpch.h" #include "SHPipelineLibrary.h" #include "Graphics/Devices/SHVkLogicalDevice.h" -#include "Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h" +#include "Graphics/MiddleEnd/GlobalData/SHPredefinedData.h" #include "Graphics/RenderGraph/SHSubpass.h" #include "Graphics/SHVkUtil.h" @@ -13,7 +13,7 @@ namespace SHADE SHPipelineLayoutParams params { .shaderModules = {vsFsPair.first, vsFsPair.second}, - .globalDescSetLayouts = SHGraphicsGlobalData::GetDescSetLayouts() + .globalDescSetLayouts = SHPredefinedData::GetPredefinedDescSetLayouts() }; // Create the pipeline layout @@ -21,7 +21,7 @@ namespace SHADE // Create the pipeline and configure the default vertex input state auto newPipeline = logicalDevice->CreateGraphicsPipeline(pipelineLayout, nullptr, renderpass, subpass); - newPipeline->GetPipelineState().SetVertexInputState(SHGraphicsGlobalData::GetDefaultViState()); + newPipeline->GetPipelineState().SetVertexInputState(SHPredefinedData::GetDefaultViState()); SHColorBlendState colorBlendState{}; colorBlendState.logic_op_enable = VK_FALSE; diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.h b/SHADE_Engine/src/Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.h index aeb023c5..389f5fa8 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.h @@ -10,7 +10,7 @@ namespace SHADE class SHVkDescriptorSetLayouts; class SHVkPipeline; class SHSubpass; - class SHGraphicsGlobalData; + class SHPredefinedData; // Pipeline library is a PURELY MIDDLE END SYSTEM. It is responsible for only creating pipelines from shaders and caching // them so that they don't need to be recreated again. diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHFont.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHFont.cpp index 3dd54ca5..f0273940 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHFont.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHFont.cpp @@ -1,7 +1,7 @@ #include "SHpch.h" #include "SHFont.h" #include "Graphics/Devices/SHVkLogicalDevice.h" -#include "Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h" +#include "Graphics/MiddleEnd/GlobalData/SHPredefinedData.h" #include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" #include "Graphics/Buffers/SHVkBuffer.h" #include "Graphics/Images/SHVkSampler.h" diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp index 6748311e..f0e375e6 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp @@ -6,7 +6,7 @@ #include "Graphics/Devices/SHVkLogicalDevice.h" #include "Graphics/MiddleEnd/TextRendering/SHFont.h" #include "Graphics/Buffers/SHVkBuffer.h" -#include "Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h" +#include "Graphics/MiddleEnd/GlobalData/SHPredefinedData.h" #include "Graphics/Pipeline/SHVkPipeline.h" #include "Graphics/SHVkUtil.h" #include "Graphics/RenderGraph/SHSubpass.h" @@ -103,7 +103,7 @@ namespace SHADE SHPipelineLayoutParams plParams { .shaderModules = {textVS, textFS}, - .globalDescSetLayouts = SHGraphicsGlobalData::GetDescSetLayouts() + .globalDescSetLayouts = SHPredefinedData::GetPredefinedDescSetLayouts() }; pipelineLayout = logicalDevice->CreatePipelineLayout(plParams); @@ -157,24 +157,6 @@ namespace SHADE // Construct pipeline pipeline->ConstructPipeline(); - SHVkDescriptorSetLayout::Binding fontBitmapBinding - { - .Type = vk::DescriptorType::eCombinedImageSampler, - .Stage = vk::ShaderStageFlagBits::eFragment, - .BindPoint = SHGraphicsConstants::DescriptorSetBindings::FONT_BITMAP_DATA, - .DescriptorCount = 1, - }; - - SHVkDescriptorSetLayout::Binding fontMatrixBinding - { - .Type = vk::DescriptorType::eStorageBuffer, - .Stage = vk::ShaderStageFlagBits::eVertex, - .BindPoint = SHGraphicsConstants::DescriptorSetBindings::FONT_MATRIX_DATA, - .DescriptorCount = 1, - }; - - fontDataDescSetLayout = logicalDevice->CreateDescriptorSetLayout(SHGraphicsConstants::DescriptorSetIndex::FONT_DATA, { fontBitmapBinding, fontMatrixBinding }); - } void SHTextRenderingSubSystem::Run(uint32_t frameIndex) noexcept @@ -209,6 +191,7 @@ namespace SHADE cmdBuffer->BindVertexBuffer(SHGraphicsConstants::VertexBufferBindings::CALCULATED_GLYPH_POSITION, comp.charPositionDataBuffer, 0); cmdBuffer->BindVertexBuffer(SHGraphicsConstants::VertexBufferBindings::GLYPH_INDEX, comp.indexingDataBuffer, 0); + // bind camera desc set (again). Necessary because pipeline layout is not compatible. cameraDescSetBind(cmdBuffer, frameIndex); // bind descriptors for font (matrices) @@ -234,9 +217,9 @@ namespace SHADE } - Handle SHTextRenderingSubSystem::GetFontDataDescSetLayout(void) const noexcept - { - return fontDataDescSetLayout; - } + //Handle SHTextRenderingSubSystem::GetFontDataDescSetLayout(void) const noexcept + //{ + // return fontDataDescSetLayout; + //} } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.h index 05ab01da..78b363d4 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.h @@ -41,7 +41,7 @@ namespace SHADE Handle pipelineLayout; //! Descriptor set for font data access in shaders - Handle fontDataDescSetLayout; + //Handle fontDataDescSetLayout; //! Super temporary. Global descriptor set needs to be revamped along with //! entire graphics system. @@ -58,7 +58,7 @@ namespace SHADE void Exit(void) noexcept; - Handle GetFontDataDescSetLayout (void) const noexcept; + //Handle GetFontDataDescSetLayout (void) const noexcept; }; diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Textures/SHTextureLibrary.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Textures/SHTextureLibrary.cpp index dfb3f3b9..a31f54de 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Textures/SHTextureLibrary.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Textures/SHTextureLibrary.cpp @@ -24,7 +24,7 @@ of DigiPen Institute of Technology is prohibited. #include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" #include "Graphics/Images/SHVkImage.h" #include "Graphics/Images/SHVkImageView.h" -#include "Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h" +#include "Graphics/MiddleEnd/GlobalData/SHPredefinedData.h" #include "Assets/Asset Types/SHTextureAsset.h" namespace SHADE @@ -168,7 +168,7 @@ namespace SHADE } texDescriptors = descPool->Allocate ( - { SHGraphicsGlobalData::GetDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::STATIC_GLOBALS] }, + { SHPredefinedData::GetPredefinedDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::STATIC_GLOBALS] }, { static_cast(texOrder.size()) } ); #ifdef _DEBUG diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp index 2ffd6d13..fc029161 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp @@ -12,7 +12,7 @@ #include "SHRenderGraphStorage.h" #include "Graphics/RenderGraph/SHRenderGraphNodeCompute.h" #include "Tools/Utilities/SHUtilities.h" -#include "Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h" +#include "Graphics/MiddleEnd/GlobalData/SHPredefinedData.h" #include "Graphics/RenderGraph/SHRenderToSwapchainImageSystem.h" namespace SHADE diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h index 0a9ed376..f892483f 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h @@ -29,7 +29,7 @@ namespace SHADE class SHVkCommandPool; class SHVkCommandBuffer; class SHRenderGraphNode; - class SHGraphicsGlobalData; + class SHPredefinedData; class SHVkDescriptorPool; class SHRenderGraphStorage; class SHRenderToSwapchainImageSystem; diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.h b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.h index 775d64f7..2311ee0c 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.h +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.h @@ -19,7 +19,7 @@ namespace SHADE class SHVkLogicalDevice; class SHVkRenderpass; class SHVkDescriptorPool; - class SHGraphicsGlobalData; + class SHPredefinedData; class SHRenderGraphStorage; class SHRenderGraphNodeCompute; diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.cpp index 2f8fd968..aca987d8 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.cpp +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.cpp @@ -6,7 +6,7 @@ #include "Graphics/Descriptors/SHVkDescriptorSetLayout.h" #include "Graphics/Devices/SHVkLogicalDevice.h" #include "Graphics/Pipeline/SHVkPipelineLayout.h" -#include "Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h" +#include "Graphics/MiddleEnd/GlobalData/SHPredefinedData.h" #include "SHRenderGraphStorage.h" #include "SHRenderGraphResource.h" #include "Graphics/Commands/SHVkCommandBuffer.h" @@ -27,7 +27,7 @@ namespace SHADE SHPipelineLayoutParams pipelineLayoutParams { .shaderModules = {computeShaderModule}, - .globalDescSetLayouts = SHGraphicsGlobalData::GetDescSetLayouts(), + .globalDescSetLayouts = SHPredefinedData::GetPredefinedDescSetLayouts(), .dynamicBufferBindings = std::move(dynamicBufferBindings), }; diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphStorage.h b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphStorage.h index d02d8d39..d473dd2a 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphStorage.h +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphStorage.h @@ -7,7 +7,7 @@ namespace SHADE { class SHVkLogicalDevice; class SHVkSwapchain; - class SHGraphicsGlobalData; + class SHPredefinedData; class SHVkDescriptorPool; class SHRenderGraphResource; diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderToSwapchainImageSystem.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderToSwapchainImageSystem.cpp index 770217ee..b8717925 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderToSwapchainImageSystem.cpp +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderToSwapchainImageSystem.cpp @@ -1,7 +1,7 @@ #include "SHpch.h" #include "SHRenderToSwapchainImageSystem.h" #include "Graphics/Devices/SHVkLogicalDevice.h" -#include "Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h" +#include "Graphics/MiddleEnd/GlobalData/SHPredefinedData.h" #include "Graphics/RenderGraph/SHRenderGraphNode.h" #include "Graphics/RenderGraph/SHSubpass.h" #include "Graphics/SHVkUtil.h" @@ -24,7 +24,7 @@ namespace SHADE auto pipelineLayout = logicalDevice->CreatePipelineLayout(SHPipelineLayoutParams { .shaderModules = {shaderModules.first, shaderModules.second}, - .globalDescSetLayouts = SHGraphicsGlobalData::GetDescSetLayouts(), + .globalDescSetLayouts = SHPredefinedData::GetPredefinedDescSetLayouts(), }); pipeline = logicalDevice->CreateGraphicsPipeline(pipelineLayout, nullptr, renderGraphNode->GetRenderpass(), subpass); diff --git a/SHADE_Engine/src/Tools/Utilities/SHUtilities.h b/SHADE_Engine/src/Tools/Utilities/SHUtilities.h index 6cdd91ee..c3492f13 100644 --- a/SHADE_Engine/src/Tools/Utilities/SHUtilities.h +++ b/SHADE_Engine/src/Tools/Utilities/SHUtilities.h @@ -43,6 +43,73 @@ namespace SHADE static constexpr OutputType ConvertEnum(InputType enumClassMember) noexcept; }; + template + class SHEnumWrapper + { + public: + using UnderlyingType = typename std::underlying_type_t; + + private: + UnderlyingType mask; + + public: + + constexpr SHEnumWrapper(void) noexcept + : mask{ 0 } + { + + }; + + constexpr SHEnumWrapper(BitType bit) noexcept + : mask{ static_cast(bit) } + { + + }; + + constexpr SHEnumWrapper(SHEnumWrapper const& rhs) noexcept = default; + constexpr SHEnumWrapper& operator= (SHEnumWrapper const& rhs) noexcept = default; + + constexpr explicit SHEnumWrapper(UnderlyingType flags) noexcept + : mask{ flags } + { + + }; + + constexpr SHEnumWrapper operator| (SHEnumWrapper const& rhs) const noexcept + { + return static_cast> (mask | rhs.mask); + }; + + constexpr SHEnumWrapper operator& (SHEnumWrapper const& rhs) const noexcept + { + return static_cast> (mask & rhs.mask); + }; + + constexpr operator UnderlyingType() const noexcept + { + return mask; + }; + }; + + template>> + inline BitType operator|(const BitType& left, const BitType& right) + { + return static_cast(static_cast(left) | static_cast(right)); + } + + template>> + inline BitType operator&(const BitType& left, const BitType& right) + { + return static_cast(static_cast(left) & static_cast(right)); + } + + template + std::ostream& operator<<(std::ostream& os, EnumType const& type) + { + os << static_cast(type); + return os; + } + } // namespace SHADE #include "SHUtilities.hpp" From 5f2fa7fdf5d0b0d6ff8dc0a50ff9e1050f408d99 Mon Sep 17 00:00:00 2001 From: Brandon Mak Date: Mon, 26 Dec 2022 09:28:15 +0800 Subject: [PATCH 081/275] WIP - Created a class that allows custom mappings of descriptor types to set indices - SHPredefinedData now contains objects of the above class with predefined mappings for the different sub systems in the Graphics System. - These mappings are also accompanied with descriptor set layout vectors that are only for that system. This helps the sub systems have access to these layouts easily without having to pass them around. - Created another class to manage global descriptor sets such as lights. - Modified pipeline layout creation code to take in the correct descriptor set layouts. --- .../Graphics/MiddleEnd/Batching/SHBatch.cpp | 18 ++- .../GlobalData/SHDescriptorMappings.cpp | 17 +++ .../GlobalData/SHDescriptorMappings.h | 29 ++++ .../GlobalData/SHGlobalDescriptorSets.cpp | 30 ++++ .../GlobalData/SHGlobalDescriptorSets.h | 24 +++ .../MiddleEnd/GlobalData/SHPredefinedData.cpp | 106 ++++++++++--- .../MiddleEnd/GlobalData/SHPredefinedData.h | 36 ++++- .../MiddleEnd/Interface/SHGraphicsConstants.h | 141 ++++++++++-------- .../MiddleEnd/Interface/SHGraphicsSystem.cpp | 4 +- .../MiddleEnd/Pipeline/SHPipelineLibrary.cpp | 2 +- .../SHTextRenderingSubSystem.cpp | 2 +- .../Pipeline/SHPipelineLayoutParams.h | 2 +- .../RenderGraph/SHRenderGraphNodeCompute.cpp | 34 +++-- .../SHRenderToSwapchainImageSystem.cpp | 2 +- 14 files changed, 329 insertions(+), 118 deletions(-) create mode 100644 SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHDescriptorMappings.cpp create mode 100644 SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHDescriptorMappings.h create mode 100644 SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.cpp create mode 100644 SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.cpp index cb2806aa..c92ad808 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Batching/SHBatch.cpp @@ -411,11 +411,12 @@ namespace SHADE instancedIntegerData.reserve(numTotalElements); instancedIntegerData.clear(); + auto const& descMappings = SHPredefinedData::GetBatchingSystemData().descMappings; // - Material Properties Data const Handle SHADER_INFO = pipeline->GetPipelineLayout()->GetShaderBlockInterface ( - SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE, + descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::MATERIALS), SHGraphicsConstants::DescriptorSetBindings::BATCHED_PER_INST_DATA, vk::ShaderStageFlagBits::eFragment ); @@ -570,11 +571,14 @@ namespace SHADE cmdBuffer->BindVertexBuffer(SHGraphicsConstants::VertexBufferBindings::INTEGER_DATA, instancedIntegerBuffer[frameIndex], 0); if (matPropsDescSet[frameIndex]) { + auto const& descMappings = SHPredefinedData::GetBatchingSystemData().descMappings; + cmdBuffer->BindDescriptorSet ( matPropsDescSet[frameIndex], SH_PIPELINE_TYPE::GRAPHICS, - SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE, + //SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE, + descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::MATERIALS), dynamicOffset ); } @@ -607,7 +611,7 @@ namespace SHADE { matPropsDescSet[frameIndex] = descPool->Allocate ( - { SHPredefinedData::GetPredefinedDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE] }, + SHPredefinedData::GetPredefinedDescSetLayouts(SHGraphicsConstants::PredefinedDescSetLayoutTypes::MATERIALS), { 0 } ); #ifdef _DEBUG @@ -618,17 +622,21 @@ namespace SHADE } #endif } + + auto const& descMappings = SHPredefinedData::GetBatchingSystemData().descMappings; + uint32_t const MATERIAL_DESC_SET_INDEX = descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::MATERIALS); + std::array, 1> bufferList = { matPropsBuffer[frameIndex] }; matPropsDescSet[frameIndex]->ModifyWriteDescBuffer ( - SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE, + MATERIAL_DESC_SET_INDEX, SHGraphicsConstants::DescriptorSetBindings::BATCHED_PER_INST_DATA, bufferList, 0, static_cast(matPropsDataSize) ); matPropsDescSet[frameIndex]->UpdateDescriptorSetBuffer ( - SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE, + MATERIAL_DESC_SET_INDEX, SHGraphicsConstants::DescriptorSetBindings::BATCHED_PER_INST_DATA ); } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHDescriptorMappings.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHDescriptorMappings.cpp new file mode 100644 index 00000000..c2b7c042 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHDescriptorMappings.cpp @@ -0,0 +1,17 @@ +#include "SHpch.h" +#include "SHDescriptorMappings.h" + +namespace SHADE +{ + void SHDescriptorMappings::AddMappings(std::initializer_list> inMappings) noexcept + { + for (auto& map : inMappings) + mappings.emplace(map); + } + + SHDescriptorMappings::MapType const& SHDescriptorMappings::GetMappings(void) const noexcept + { + return mappings; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHDescriptorMappings.h b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHDescriptorMappings.h new file mode 100644 index 00000000..67480e7e --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHDescriptorMappings.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include "Graphics/MiddleEnd/Interface/SHGraphicsConstants.h" + +namespace SHADE +{ + class SHDescriptorMappings + { + public: + using MapType = std::unordered_map; + + private: + //! To map an enum value from descriptor set types to set indices + MapType mappings; + + public: + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void AddMappings (std::initializer_list> inMappings) noexcept; + + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + MapType const& GetMappings (void) const noexcept; + }; + +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.cpp new file mode 100644 index 00000000..2d6cc9e1 --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.cpp @@ -0,0 +1,30 @@ +#include "SHpch.h" +#include "SHGlobalDescriptorSets.h" + +namespace SHADE +{ + + Handle SHGlobalDescriptorSets::lightDescriptorSet; + + /***************************************************************************/ + /*! + + \brief + Sets the Handle to descriptor set for lights. + + \param lightDescSet + The handle to set to. + + */ + /***************************************************************************/ + void SHGlobalDescriptorSets::SetLightDescriptorSet(Handle lightDescSet) noexcept + { + lightDescriptorSet = lightDescSet; + } + + Handle SHGlobalDescriptorSets::GetLightDescriptorSet(void) noexcept + { + return lightDescriptorSet; + } + +} \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h new file mode 100644 index 00000000..ce6c42bb --- /dev/null +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" +#include "Resource/SHHandle.h" + +namespace SHADE +{ + // This class is mainly for descriptors that are truly global, meaning they only come from 1 place and they are shared between many systems + class SHGlobalDescriptorSets + { + private: + //! Light data descriptor set + static Handle lightDescriptorSet; + + public: + /*-----------------------------------------------------------------------*/ + /* SETTERS AND GETTERS */ + /*-----------------------------------------------------------------------*/ + static void SetLightDescriptorSet (Handle lightDescSet) noexcept; + + static Handle GetLightDescriptorSet (void) noexcept; + + }; +} diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.cpp index d93e073b..ac7fac17 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.cpp @@ -13,7 +13,41 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ std::vector> SHPredefinedData::predefinedLayouts; SHVertexInputState SHPredefinedData::defaultVertexInputState; - Handle SHPredefinedData::dummyPipelineLayout; + SHPredefinedData::PerSystem SHPredefinedData::batchingSystemData; + SHPredefinedData::PerSystem SHPredefinedData::textSystemData; + SHPredefinedData::PerSystem SHPredefinedData::renderGraphNodeComputeData; + + void SHPredefinedData::InitDescMappings(void) noexcept + { + batchingSystemData.descMappings.AddMappings + ({ + {SHGraphicsConstants::DescriptorSetTypes::STATIC_DATA, 0}, + {SHGraphicsConstants::DescriptorSetTypes::CAMERA, 1}, + {SHGraphicsConstants::DescriptorSetTypes::MATERIALS, 2}, + }); + + textSystemData.descMappings.AddMappings + ({ + {SHGraphicsConstants::DescriptorSetTypes::STATIC_DATA, 0}, + {SHGraphicsConstants::DescriptorSetTypes::CAMERA, 1}, + {SHGraphicsConstants::DescriptorSetTypes::FONT, 2}, + }); + + renderGraphNodeComputeData.descMappings.AddMappings + ({ + {SHGraphicsConstants::DescriptorSetTypes::STATIC_DATA, 0}, + {SHGraphicsConstants::DescriptorSetTypes::LIGHTS, 1}, + {SHGraphicsConstants::DescriptorSetTypes::CAMERA, 2}, + {SHGraphicsConstants::DescriptorSetTypes::RENDER_GRAPH_RESOURCE, 3}, + {SHGraphicsConstants::DescriptorSetTypes::RENDER_GRAPH_NODE_COMPUTE_RESOURCE, 4}, + }); + } + + void SHPredefinedData::InitDummyPipelineLayouts(Handle logicalDevice) noexcept + { + batchingSystemData.dummyPipelineLayout = logicalDevice->CreatePipelineLayoutDummy(SHPipelineLayoutParamsDummy{ batchingSystemData.descSetLayouts }); + textSystemData.dummyPipelineLayout = logicalDevice->CreatePipelineLayoutDummy(SHPipelineLayoutParamsDummy{ textSystemData.descSetLayouts }); + } /*-----------------------------------------------------------------------------------*/ /* Function Definitions */ @@ -91,35 +125,46 @@ namespace SHADE Handle materialDataPerInstanceLayout = logicalDevice->CreateDescriptorSetLayout({ materialDataBinding }); SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, materialDataPerInstanceLayout->GetVkHandle(), "[Descriptor Set Layout] Material Globals"); - // font bitmap data (texture) - SHVkDescriptorSetLayout::Binding fontBitmapBinding - { - .Type = vk::DescriptorType::eCombinedImageSampler, - .Stage = vk::ShaderStageFlagBits::eFragment, - .BindPoint = SHGraphicsConstants::DescriptorSetBindings::FONT_BITMAP_DATA, - .DescriptorCount = 1, - }; + //// font bitmap data (texture) + //SHVkDescriptorSetLayout::Binding fontBitmapBinding + //{ + // .Type = vk::DescriptorType::eCombinedImageSampler, + // .Stage = vk::ShaderStageFlagBits::eFragment, + // .BindPoint = SHGraphicsConstants::DescriptorSetBindings::FONT_BITMAP_DATA, + // .DescriptorCount = 1, + //}; - // font data in the form of matrices - SHVkDescriptorSetLayout::Binding fontMatrixBinding - { - .Type = vk::DescriptorType::eStorageBuffer, - .Stage = vk::ShaderStageFlagBits::eVertex, - .BindPoint = SHGraphicsConstants::DescriptorSetBindings::FONT_MATRIX_DATA, - .DescriptorCount = 1, - }; + //// font data in the form of matrices + //SHVkDescriptorSetLayout::Binding fontMatrixBinding + //{ + // .Type = vk::DescriptorType::eStorageBuffer, + // .Stage = vk::ShaderStageFlagBits::eVertex, + // .BindPoint = SHGraphicsConstants::DescriptorSetBindings::FONT_MATRIX_DATA, + // .DescriptorCount = 1, + //}; - Handle fontDataDescSetLayout = logicalDevice->CreateDescriptorSetLayout({ fontBitmapBinding, fontMatrixBinding }); - SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, fontDataDescSetLayout->GetVkHandle(), "[Descriptor Set Layout] Font Data"); + //Handle fontDataDescSetLayout = logicalDevice->CreateDescriptorSetLayout({ fontBitmapBinding, fontMatrixBinding }); + //SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, fontDataDescSetLayout->GetVkHandle(), "[Descriptor Set Layout] Font Data"); predefinedLayouts.push_back(staticGlobalLayout); predefinedLayouts.push_back(lightDataDescSetLayout); predefinedLayouts.push_back(cameraDataGlobalLayout); predefinedLayouts.push_back(materialDataPerInstanceLayout); - predefinedLayouts.push_back(fontDataDescSetLayout); + //predefinedLayouts.push_back(fontDataDescSetLayout); - dummyPipelineLayout = logicalDevice->CreatePipelineLayoutDummy(SHPipelineLayoutParamsDummy{predefinedLayouts}); + batchingSystemData.descSetLayouts = GetPredefinedDescSetLayouts + ( + SHGraphicsConstants::PredefinedDescSetLayoutTypes::STATIC_DATA | + SHGraphicsConstants::PredefinedDescSetLayoutTypes::CAMERA | + SHGraphicsConstants::PredefinedDescSetLayoutTypes::MATERIALS + ); + + textSystemData.descSetLayouts = GetPredefinedDescSetLayouts + ( + SHGraphicsConstants::PredefinedDescSetLayoutTypes::STATIC_DATA | + SHGraphicsConstants::PredefinedDescSetLayoutTypes::CAMERA + ); } void SHPredefinedData::InitDefaultVertexInputState(void) noexcept @@ -136,9 +181,11 @@ namespace SHADE { InitDescSetLayouts(logicalDevice); InitDefaultVertexInputState(); + InitDescMappings(); + InitDummyPipelineLayouts (logicalDevice); } - std::vector> SHPredefinedData::GetPredefinedDescSetLayouts(SHEnumWrapper types) noexcept + std::vector> SHPredefinedData::GetPredefinedDescSetLayouts(SHEnumWrapper types) noexcept { std::vector> layoutsFound; for (uint8_t i = 0; i < SHGraphicsConstants::numPredefinedDescSetLayoutTypes; ++i) @@ -156,9 +203,20 @@ namespace SHADE return defaultVertexInputState; } - Handle SHPredefinedData::GetDummyPipelineLayout(void) noexcept + + SHPredefinedData::PerSystem const& SHPredefinedData::GetBatchingSystemData(void) noexcept { - return dummyPipelineLayout; + return batchingSystemData; + } + + SHPredefinedData::PerSystem const& SHPredefinedData::GetTextSystemData(void) noexcept + { + return textSystemData; + } + + SHPredefinedData::PerSystem const& SHPredefinedData::GetRenderGraphNodeComputeData(void) noexcept + { + return renderGraphNodeComputeData; } } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.h b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.h index 5f13a100..b19a1e0b 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.h @@ -3,6 +3,7 @@ #include "SH_API.h" #include "Graphics/Pipeline/SHPipelineState.h" #include "Graphics/MiddleEnd/Interface/SHGraphicsConstants.h" +#include "Graphics/MiddleEnd/GlobalData/SHDescriptorMappings.h" #include "Tools/Utilities/SHUtilities.h" namespace SHADE @@ -12,8 +13,22 @@ namespace SHADE class SHVkDescriptorSetGroup; class SHVkPipelineLayout; + class SH_API SHPredefinedData { + public: + struct PerSystem + { + //! vector of descriptor set layouts used by a system + std::vector> descSetLayouts; + + //! pipeline layout used for binding descriptor sets in the system + static Handle dummyPipelineLayout; + + //! Descriptor type mappings for the system + SHDescriptorMappings descMappings; + }; + private: //! Global descriptor set layouts. Used to allocate descriptor sets static std::vector> predefinedLayouts; @@ -21,10 +36,17 @@ namespace SHADE //! Default vertex input state (used by everything). static SHVertexInputState defaultVertexInputState; - //! Since we want to bind global data but can't do so without a pipeline layout, - //! we create a dummy pipeline layout to use it for binding. - static Handle dummyPipelineLayout; + //! predefined data for the batching system + static PerSystem batchingSystemData; + //! predefined data for the text system + static PerSystem textSystemData; + + //! predefined data for the render graph node computes + static PerSystem renderGraphNodeComputeData; + + static void InitDescMappings (void) noexcept; + static void InitDummyPipelineLayouts (Handle logicalDevice) noexcept; static void InitDescSetLayouts (Handle logicalDevice) noexcept; static void InitDefaultVertexInputState (void) noexcept; @@ -42,8 +64,10 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* SETTERS AND GETTERS */ /*-----------------------------------------------------------------------*/ - static std::vector> GetPredefinedDescSetLayouts (SHEnumWrapper types) noexcept; - static SHVertexInputState const& GetDefaultViState (void) noexcept; - static Handle GetDummyPipelineLayout (void) noexcept; + static std::vector> GetPredefinedDescSetLayouts (SHEnumWrapper types) noexcept; + static SHVertexInputState const& GetDefaultViState (void) noexcept; + static PerSystem const& GetBatchingSystemData(void) noexcept; + static PerSystem const& GetTextSystemData(void) noexcept; + static PerSystem const& GetRenderGraphNodeComputeData(void) noexcept; }; } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h index e0b76555..c80b9de5 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h @@ -27,13 +27,28 @@ namespace SHADE public: static constexpr uint8_t numPredefinedDescSetLayoutTypes = 64; - enum class SHPredefinedDescSetLayoutTypes : uint64_t + // This enum is mainly to initialize a bit field to retrieve bit fields from SHPRedefinedData + enum class PredefinedDescSetLayoutTypes : uint64_t { STATIC_DATA = 0x01, LIGHTS = 0x02, CAMERA = 0x04, MATERIALS = 0x08, - FONT = 0x10, + }; + + // This enum is different from the one above in that it is used to initialize a hash table to + // with the values here as keys and set indices as values. It is worth noting that some values here + // are not in the above table. This is because those values don't have predefined descriptor set layouts. + // Their layouts and set indices are instead created through introspection in the pipeline layout. + enum class DescriptorSetTypes + { + STATIC_DATA, + LIGHTS, + CAMERA, + MATERIALS, + FONT, + RENDER_GRAPH_RESOURCE, + RENDER_GRAPH_NODE_COMPUTE_RESOURCE, }; struct RenderGraphIndices @@ -42,68 +57,68 @@ namespace SHADE static constexpr uint32_t EDITOR = 0; }; - //struct DescriptorSetIndex - //{ - // /***************************************************************************/ - // /*! - // \brief - // DescriptorSet Index for static global values like generic data, and - // texture samplers - // */ - // /***************************************************************************/ - // static constexpr uint32_t STATIC_GLOBALS = 0; - // /***************************************************************************/ - // /*! - // \brief - // DescriptorSet Index for dynamic global values like lights. - // */ - // /***************************************************************************/ - // static constexpr uint32_t DYNAMIC_GLOBALS = 1; - // /***************************************************************************/ - // /*! - // \brief - // DescriptorSet Index for high frequency changing global values like - // camera matrices. - // */ - // /***************************************************************************/ - // static constexpr uint32_t HIGH_FREQUENCY_GLOBALS = 2; - // /***************************************************************************/ - // /*! - // \brief - // DescriptorSet Index for per-instance/material changing values. - // */ - // /***************************************************************************/ - // static constexpr uint32_t PER_INSTANCE = 3; - // /***************************************************************************/ - // /*! - // \brief - // DescriptorSet Index for render graph resources. Unlike the sets from - // 1 to 3 and 6, this set index does not have hard coded bindings and is - // NOT part of the layouts included in the global data. - // */ - // /***************************************************************************/ - // static constexpr uint32_t RENDERGRAPH_RESOURCE = 4; - // /***************************************************************************/ - // /*! - // \brief - // DescriptorSet Index for render graph node compute resources. For data - // that we wish to pass to compute shaders in the render graph, this is - // the set to use. Unlike the sets from 1 to 3 and 6, this set index does not have - // hard coded bindings and is NOT part of the layouts included in the global - // data. - // */ - // /***************************************************************************/ - // static constexpr uint32_t RENDERGRAPH_NODE_COMPUTE_RESOURCE = 5; + struct DescriptorSetIndex + { + /***************************************************************************/ + /*! + \brief + DescriptorSet Index for static global values like generic data, and + texture samplers + */ + /***************************************************************************/ + static constexpr uint32_t STATIC_GLOBALS = 0; + /***************************************************************************/ + /*! + \brief + DescriptorSet Index for dynamic global values like lights. + */ + /***************************************************************************/ + static constexpr uint32_t DYNAMIC_GLOBALS = 1; + /***************************************************************************/ + /*! + \brief + DescriptorSet Index for high frequency changing global values like + camera matrices. + */ + /***************************************************************************/ + static constexpr uint32_t HIGH_FREQUENCY_GLOBALS = 2; + /***************************************************************************/ + /*! + \brief + DescriptorSet Index for per-instance/material changing values. + */ + /***************************************************************************/ + static constexpr uint32_t PER_INSTANCE = 3; + /***************************************************************************/ + /*! + \brief + DescriptorSet Index for render graph resources. Unlike the sets from + 1 to 3 and 6, this set index does not have hard coded bindings and is + NOT part of the layouts included in the global data. + */ + /***************************************************************************/ + static constexpr uint32_t RENDERGRAPH_RESOURCE = 4; + /***************************************************************************/ + /*! + \brief + DescriptorSet Index for render graph node compute resources. For data + that we wish to pass to compute shaders in the render graph, this is + the set to use. Unlike the sets from 1 to 3 and 6, this set index does not have + hard coded bindings and is NOT part of the layouts included in the global + data. + */ + /***************************************************************************/ + static constexpr uint32_t RENDERGRAPH_NODE_COMPUTE_RESOURCE = 5; - // /***************************************************************************/ - // /*! - // \brief - // To store font data. - // - // */ - // /***************************************************************************/ - // static constexpr uint32_t FONT_DATA = 4; - //}; + /***************************************************************************/ + /*! + \brief + To store font data. + + */ + /***************************************************************************/ + static constexpr uint32_t FONT_DATA = 4; + }; struct DescriptorSetBindings { diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp index c9e7f2d2..6881d7a8 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp @@ -903,7 +903,7 @@ namespace SHADE void SHGraphicsSystem::BuildFonts(void) noexcept { - fontLibrary.BuildFonts(device, graphicsQueue, graphicsCmdPool, descPool, SHPredefinedData::GetPredefinedDescSetLayouts(SHGraphicsConstants::SHPredefinedDescSetLayoutTypes::FONT)[0], resourceManager); + fontLibrary.BuildFonts(device, graphicsQueue, graphicsCmdPool, descPool, SHPredefinedData::GetPredefinedDescSetLayouts(SHGraphicsConstants::PredefinedDescSetLayoutTypes::FONT)[0], resourceManager); } #pragma endregion ADD_REMOVE @@ -1137,7 +1137,7 @@ namespace SHADE device, SHPipelineLayoutParams { .shaderModules = { (instanced ? debugMeshVertShader : debugVertShader) , debugFragShader }, - .globalDescSetLayouts = SHPredefinedData::GetPredefinedDescSetLayouts(SHGraphicsConstants::SHPredefinedDescSetLayoutTypes::CAMERA) + .globalDescSetLayouts = SHPredefinedData::GetPredefinedDescSetLayouts(SHGraphicsConstants::PredefinedDescSetLayoutTypes::CAMERA) } ); auto pipeline = resourceManager.Create(device, pipelineLayout, nullptr, renderPass, subpass); diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.cpp index 9e6f1bd7..9b16a279 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Pipeline/SHPipelineLibrary.cpp @@ -13,7 +13,7 @@ namespace SHADE SHPipelineLayoutParams params { .shaderModules = {vsFsPair.first, vsFsPair.second}, - .globalDescSetLayouts = SHPredefinedData::GetPredefinedDescSetLayouts() + .globalDescSetLayouts = SHPredefinedData::GetBatchingSystemData().descSetLayouts }; // Create the pipeline layout diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp index f0e375e6..f117b26c 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp @@ -103,7 +103,7 @@ namespace SHADE SHPipelineLayoutParams plParams { .shaderModules = {textVS, textFS}, - .globalDescSetLayouts = SHPredefinedData::GetPredefinedDescSetLayouts() + .predefinedDescSetLayouts = SHPredefinedData::GetTextSystemData().descSetLayouts }; pipelineLayout = logicalDevice->CreatePipelineLayout(plParams); diff --git a/SHADE_Engine/src/Graphics/Pipeline/SHPipelineLayoutParams.h b/SHADE_Engine/src/Graphics/Pipeline/SHPipelineLayoutParams.h index 010bed0e..3288d196 100644 --- a/SHADE_Engine/src/Graphics/Pipeline/SHPipelineLayoutParams.h +++ b/SHADE_Engine/src/Graphics/Pipeline/SHPipelineLayoutParams.h @@ -25,7 +25,7 @@ namespace SHADE //! used just for textures or lights for example). In that case, we still //! want to use the layout to initialize the pipeline layout but we do not //! want to use it for allocating descriptor sets. - std::vector> const& globalDescSetLayouts = {}; + std::vector> const& predefinedDescSetLayouts = {}; //! Since both SPIRV-Reflect and GLSL don't provide ways to describe UBOs or //! SSBOs as dynamic, we need to do it ourselves. This will store bindings diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.cpp index aca987d8..ea574158 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.cpp +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.cpp @@ -27,7 +27,7 @@ namespace SHADE SHPipelineLayoutParams pipelineLayoutParams { .shaderModules = {computeShaderModule}, - .globalDescSetLayouts = SHPredefinedData::GetPredefinedDescSetLayouts(), + .predefinedDescSetLayouts = SHPredefinedData::GetRenderGraphNodeComputeData().descSetLayouts, .dynamicBufferBindings = std::move(dynamicBufferBindings), }; @@ -45,10 +45,13 @@ namespace SHADE // save the resources resources = std::move (subpassComputeResources); + auto const& descMappings = SHPredefinedData::GetRenderGraphNodeComputeData().descMappings; + auto const& layouts = computePipeline->GetPipelineLayout()->GetDescriptorSetLayoutsPipeline(); - //Get the descriptor set layouts required to allocate. We only want the ones for allocate because - //global descriptors are already bound in the main system. - auto const& graphResourceLayout = computePipeline->GetPipelineLayout()->GetDescriptorSetLayoutsPipeline()[SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_RESOURCE]; + + //Get the descriptor set layouts required to allocate. We only want the ones for allocate because + //global descriptors are already bound in the main system. + auto const& graphResourceLayout = layouts[descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::RENDER_GRAPH_RESOURCE)]; // Allocate descriptor sets to hold the images for reading (STORAGE_IMAGE) for (uint32_t i = 0; i < SHGraphicsConstants::NUM_FRAME_BUFFERS; ++i) @@ -60,14 +63,12 @@ namespace SHADE #endif } - - auto const& layouts = computePipeline->GetPipelineLayout()->GetDescriptorSetLayoutsPipeline(); - - if (layouts.size() == SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_NODE_COMPUTE_RESOURCE + 1) + // check if all layouts are there + if (layouts.size() == descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::RENDER_GRAPH_NODE_COMPUTE_RESOURCE) + 1) { // create compute resources computeResource = graphStorage->resourceHub->Create(); - auto computeResourceLayout = layouts[SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_NODE_COMPUTE_RESOURCE]; + auto computeResourceLayout = layouts[descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::RENDER_GRAPH_NODE_COMPUTE_RESOURCE)]; computeResource->descSet = graphStorage->descriptorPool->Allocate({ computeResourceLayout }, { 1 }); #ifdef _DEBUG for (auto set : computeResource->descSet->GetVkHandle()) @@ -91,12 +92,14 @@ namespace SHADE // bind the compute pipeline cmdBuffer->BindPipeline(computePipeline); + auto const& descMappings = SHPredefinedData::GetRenderGraphNodeComputeData().descMappings; + // bind descriptor sets - cmdBuffer->BindDescriptorSet(graphResourceDescSets[frameIndex], SH_PIPELINE_TYPE::COMPUTE, SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_RESOURCE, {}); + cmdBuffer->BindDescriptorSet(graphResourceDescSets[frameIndex], SH_PIPELINE_TYPE::COMPUTE, descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::RENDER_GRAPH_RESOURCE), {}); if (computeResource) { - cmdBuffer->BindDescriptorSet(computeResource->descSet, SH_PIPELINE_TYPE::COMPUTE, SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_NODE_COMPUTE_RESOURCE, computeResource->dynamicOffsets[frameIndex]); + cmdBuffer->BindDescriptorSet(computeResource->descSet, SH_PIPELINE_TYPE::COMPUTE, descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::RENDER_GRAPH_NODE_COMPUTE_RESOURCE), computeResource->dynamicOffsets[frameIndex]); } // dispatch compute @@ -109,8 +112,11 @@ namespace SHADE void SHRenderGraphNodeCompute::HandleResize(void) noexcept { + auto const& descMappings = SHPredefinedData::GetRenderGraphNodeComputeData().descMappings; + uint32_t renderGraphResourceSetIndex = descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::RENDER_GRAPH_RESOURCE); + // Get the layout for the render graph resource. We can index it this way because the container returned is a container of layouts that includes the global ones - auto pipelineDescSetLayouts = computePipeline->GetPipelineLayout()->GetDescriptorSetLayoutsPipeline()[SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_RESOURCE]; + auto pipelineDescSetLayouts = computePipeline->GetPipelineLayout()->GetDescriptorSetLayoutsPipeline()[renderGraphResourceSetIndex]; // everything below here needs resizing for (uint32_t frameIndex = 0; frameIndex < SHGraphicsConstants::NUM_FRAME_BUFFERS; ++frameIndex) @@ -123,8 +129,8 @@ namespace SHADE uint32_t imageIndex = (resources[i]->resourceTypeFlags & static_cast(SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR_PRESENT)) ? frameIndex : 0; SHVkDescriptorSetGroup::viewSamplerLayout vsl = std::make_tuple(resources[i]->GetImageView(imageIndex), Handle{}, vk::ImageLayout::eGeneral); - graphResourceDescSets[frameIndex]->ModifyWriteDescImage(SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_RESOURCE, binding.BindPoint, { &vsl, 1 }); - graphResourceDescSets[frameIndex]->UpdateDescriptorSetImages(SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_RESOURCE, binding.BindPoint); + graphResourceDescSets[frameIndex]->ModifyWriteDescImage(renderGraphResourceSetIndex, binding.BindPoint, { &vsl, 1 }); + graphResourceDescSets[frameIndex]->UpdateDescriptorSetImages(renderGraphResourceSetIndex, binding.BindPoint); ++i; } } diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderToSwapchainImageSystem.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderToSwapchainImageSystem.cpp index b8717925..4c575c99 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderToSwapchainImageSystem.cpp +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderToSwapchainImageSystem.cpp @@ -24,7 +24,7 @@ namespace SHADE auto pipelineLayout = logicalDevice->CreatePipelineLayout(SHPipelineLayoutParams { .shaderModules = {shaderModules.first, shaderModules.second}, - .globalDescSetLayouts = SHPredefinedData::GetPredefinedDescSetLayouts(), + .predefinedDescSetLayouts = SHPredefinedData::GetBatchingSystemData().descSetLayouts }); pipeline = logicalDevice->CreateGraphicsPipeline(pipelineLayout, nullptr, renderGraphNode->GetRenderpass(), subpass); From 37f62fdd24889f368d7f29f81f6c858ccc2455a7 Mon Sep 17 00:00:00 2001 From: Sri Sham Haran Date: Mon, 26 Dec 2022 14:35:48 +0800 Subject: [PATCH 082/275] Collision Tag Matrix --- .../ColliderTagPanel/SHColliderTagPanel.cpp | 9 ++++-- .../Inspector/SHEditorComponentView.hpp | 29 +++++++++++++++---- .../src/Serialization/SHYAMLConverters.h | 8 ++++- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/SHADE_Engine/src/Editor/EditorWindow/ColliderTagPanel/SHColliderTagPanel.cpp b/SHADE_Engine/src/Editor/EditorWindow/ColliderTagPanel/SHColliderTagPanel.cpp index 1dd0047d..8169aa5c 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/ColliderTagPanel/SHColliderTagPanel.cpp +++ b/SHADE_Engine/src/Editor/EditorWindow/ColliderTagPanel/SHColliderTagPanel.cpp @@ -13,6 +13,8 @@ namespace SHADE { ImGui::BeginTable("CollisionMtxTable", SHCollisionTag::NUM_LAYERS + 1, ImGuiTableRowFlags_Headers); ImGui::TableNextRow(); + ImGui::PushID("CollisionTagNames"); + for (int i = SHCollisionTag::NUM_LAYERS; i >= 0; --i) { ImGui::TableNextColumn(); @@ -22,8 +24,11 @@ namespace SHADE if (!tag) continue; //ImGui::Text(tagName.data()); - SHEditorWidgets::InputText("##" + std::to_string(i), [i]{return SHCollisionTagMatrix::GetTagName(i);}, [i](std::string const& value){SHCollisionTagMatrix::GetTag(i)->SetName(value);}, tagName.data(), ImGuiInputTextFlags_EnterReturnsTrue); + ImGui::PushID(i); + SHEditorWidgets::InputText("##", [i]{return SHCollisionTagMatrix::GetTagName(i);}, [i](std::string const& value){SHCollisionTagMatrix::GetTag(i)->SetName(value);}, tagName.data(), ImGuiInputTextFlags_EnterReturnsTrue); + ImGui::PopID(); } + ImGui::PopID(); for (int i = 0; i < SHCollisionTag::NUM_LAYERS; ++i) { std::string tagName = SHCollisionTagMatrix::GetTagName(i); @@ -50,7 +55,7 @@ namespace SHADE ImGui::TableNextColumn(); //if(i == idx) // continue; - std::string_view label = std::format("##{} vs {}", tagName, tagName2).data(); + std::string label = std::format("##{} vs {}", tagName, tagName2); SHEditorWidgets::CheckBox(label, [tag, &idx]{return tag->GetLayerState(idx);}, [tag, i, idx](bool const& value){tag->SetLayerState(idx, value); SHCollisionTagMatrix::GetTag(idx)->SetLayerState(i, value);}, label.substr(2)); } } diff --git a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp index 0f3dce3e..e4ec8d58 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp +++ b/SHADE_Engine/src/Editor/EditorWindow/Inspector/SHEditorComponentView.hpp @@ -18,7 +18,7 @@ #include "Physics/Interface/SHColliderComponent.h" #include "Reflection/SHReflectionMetadata.h" #include "Resource/SHResourceManager.h" - +#include "Physics/Collision/SHCollisionTagMatrix.h" namespace SHADE { template @@ -314,6 +314,12 @@ namespace SHADE if (!component) return; ImGui::PushID(SHFamilyID::GetID()); + std::vector collisionTagNames(SHCollisionTag::NUM_LAYERS); + //Fetch tag names + for(int i{}; i < SHCollisionTag::NUM_LAYERS; ++i) + { + collisionTagNames[i] = SHCollisionTagMatrix::GetTagName(i).c_str(); + } const auto componentType = rttr::type::get(*component); SHEditorWidgets::CheckBox("##IsActive", [component]() {return component->isActive; }, [component](bool const& active) {component->isActive = active; }, "Is Component Active"); @@ -332,7 +338,7 @@ namespace SHADE SHCollisionShape* collider = &component->GetCollisionShape(i); auto cursorPos = ImGui::GetCursorPos(); - //collider->IsTrigger + if (collider->GetType() == SHCollisionShape::Type::BOX) { SHEditorWidgets::BeginPanel(std::format("{} Box #{}", ICON_FA_CUBE, i).data(), { ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y }); @@ -361,7 +367,7 @@ namespace SHADE { SHEditorWidgets::CheckBox("Is Trigger", [collider] { return collider->IsTrigger(); }, [collider](bool value) { collider->SetIsTrigger(value); }); - + SHEditorWidgets::ComboBox("Tag", collisionTagNames, [collider]{return SHCollisionTagMatrix::GetTagIndex(collider->GetCollisionTag().GetName());}, [collider](int const& value){collider->SetCollisionTag(SHCollisionTagMatrix::GetTag(value));}); if(ImGui::CollapsingHeader("Physics Material")) { SHEditorWidgets::DragFloat("Friction", [collider] { return collider->GetFriction(); }, [collider](float value) { collider->SetFriction(value); }, "Friction", 0.05f, 0.0f, 1.0f); @@ -388,6 +394,7 @@ namespace SHADE { colliderToDelete = i; } + SHEditorWidgets::EndPanel(); ImGui::PopID(); } @@ -399,14 +406,26 @@ namespace SHADE if (ImGui::BeginMenu("Add Collider")) { + int newColl = -1; + if (ImGui::Selectable("Box Collider")) { - component->AddBoundingBox(); + newColl = component->AddBoundingBox(); } if (ImGui::Selectable("Sphere Collider")) { - component->AddBoundingSphere(); + newColl = component->AddBoundingSphere(); } + + //No idea why this doesn't work + //if (newColl > 0) + //{ + // auto newCollisionShape = component->GetCollisionShape(newColl); + // auto prevCollisionShapeInSeq = component->GetCollisionShape(newColl - 1); + // newCollisionShape.SetCollisionTag(SHCollisionTagMatrix::GetTag(prevCollisionShapeInSeq.GetCollisionTag().GetName())); + + //} + ImGui::EndMenu(); } } diff --git a/SHADE_Engine/src/Serialization/SHYAMLConverters.h b/SHADE_Engine/src/Serialization/SHYAMLConverters.h index e1cb8181..efb49c4b 100644 --- a/SHADE_Engine/src/Serialization/SHYAMLConverters.h +++ b/SHADE_Engine/src/Serialization/SHYAMLConverters.h @@ -14,6 +14,7 @@ #include "Physics/Interface/SHColliderComponent.h" #include "Graphics/MiddleEnd/TextRendering/SHTextRenderableComponent.h" #include "Graphics/MiddleEnd/TextRendering/SHFont.h" +#include "Physics/Collision/SHCollisionTagMatrix.h" namespace YAML { @@ -117,13 +118,14 @@ namespace YAML static constexpr const char* Density = "Density"; static constexpr const char* PositionOffset = "Position Offset"; static constexpr const char* RotationOffset = "Rotation Offset"; + static constexpr const char* CollisionTag = "Collision Tag"; static Node encode(SHCollisionShape& rhs) { Node node; node[IsTrigger] = rhs.IsTrigger(); - + node[CollisionTag] = rhs.GetCollisionTag().GetName(); rttr::type const shapeRttrType = rttr::type::get(); rttr::enumeration const enumAlign = shapeRttrType.get_enumeration(); SHCollisionShape::Type colliderType = rhs.GetType(); @@ -160,6 +162,10 @@ namespace YAML { if (node[IsTrigger].IsDefined()) rhs.SetIsTrigger(node[IsTrigger].as()); + + if(node[CollisionTag].IsDefined()) + rhs.SetCollisionTag(SHCollisionTagMatrix::GetTag(node[CollisionTag].as())); + if (!node[Type].IsDefined()) return false; rttr::type const shapeRttrType = rttr::type::get(); From f49ecdbb14b2d3aa44b51ca99820bf672e80eb3f Mon Sep 17 00:00:00 2001 From: SHAM-DP Date: Tue, 27 Dec 2022 16:23:53 +0800 Subject: [PATCH 083/275] Remove a few redundancies. Audio System now creates handles to audio clips --- .../src/AudioSystem/SHAudioSystem.cpp | 143 +++++++++++------- SHADE_Engine/src/AudioSystem/SHAudioSystem.h | 30 ++-- 2 files changed, 107 insertions(+), 66 deletions(-) diff --git a/SHADE_Engine/src/AudioSystem/SHAudioSystem.cpp b/SHADE_Engine/src/AudioSystem/SHAudioSystem.cpp index e98d895a..cd04f841 100644 --- a/SHADE_Engine/src/AudioSystem/SHAudioSystem.cpp +++ b/SHADE_Engine/src/AudioSystem/SHAudioSystem.cpp @@ -325,27 +325,70 @@ namespace SHADE return std::nullopt; } - AudioClip* SHAudioSystem::CreateAudioClip(const char* path) + Handle SHAudioSystem::CreateAudioClip(const char* path) { - AudioClipID newID{}; - AudioClip* clip = nullptr; - auto it = eventMap.find(path); - if (it != eventMap.end()) + Handle audioClipHandle{}; + + if(auto it = eventMap.find(path); it != eventMap.end()) { - FMOD::Studio::EventInstance* event = nullptr; - it->second->createInstance(&event); - if (event) - { - //event->start(); - newID = clipID; - clipID++; - eventInstances.emplace(newID, AudioClip(newID, event)); - clip = &eventInstances[newID]; - } + audioClipHandle = audioClipLibrary.Create(); + it->second->createInstance(&audioClipHandle->instance); } - return clip; + + return audioClipHandle; } + void SHAudioSystem::AddAudioClipToBGMChannelGroup(Handle handle) + { + if(!handle->instance) + return; + FMOD::ChannelGroup* channelGroup; + handle->instance->getChannelGroup(&channelGroup); + + if(!channelGroup) + { + SHLOG_ERROR("Event instance has no channel group") + return; + } + bgmChannelGroup->addGroup(channelGroup); + } + + void SHAudioSystem::AddAudioClipToSFXChannelGroup(Handle handle) + { + if (!handle->instance) + return; + FMOD::ChannelGroup* channelGroup; + handle->instance->getChannelGroup(&channelGroup); + + if (!channelGroup) + { + SHLOG_ERROR("Event instance has no channel group") + return; + } + sfxChannelGroup->addGroup(channelGroup); + } + + //AudioClip* SHAudioSystem::CreateAudioClip(const char* path) + //{ + // AudioClipID newID{}; + // AudioClip* clip = nullptr; + // auto it = eventMap.find(path); + // if (it != eventMap.end()) + // { + // FMOD::Studio::EventInstance* event = nullptr; + // it->second->createInstance(&event); + // if (event) + // { + // //event->start(); + // newID = clipID; + // clipID++; + // eventInstances.emplace(newID, AudioClip(newID, event)); + // clip = &eventInstances[newID]; + // } + // } + // return clip; + //} + //std::vector SHAudioSystem::GetAllEvents() //{ // int count{}; @@ -489,41 +532,39 @@ namespace SHADE } } - AudioClip::AudioClip(AudioClipID clipID, FMOD::Studio::EventInstance* inst) - :instance(inst), id(clipID) + void AudioClip::Play() { - } - - AudioClip::~AudioClip() - { - } - - void AudioClip::Play(bool isSfx) - { - if (!instance) + if(!instance) return; instance->start(); - auto audioSystem = SHSystemManager::GetSystem(); - instance->setVolume(audioSystem->GetMasterVolume() * (isSfx ? audioSystem->GetSfxVolume() : audioSystem->GetBgmVolume())); } - void AudioClip::Play(SHVec3 position, bool isSfx) - { - if (!instance) - return; - instance->start(); - FMOD_3D_ATTRIBUTES attributes{ {} }; - attributes.forward.z = 1.0f; - attributes.up.y = 1.0f; + //void AudioClip::Play(bool isSfx) + //{ + // if (!instance) + // return; + // instance->start(); + // auto audioSystem = SHSystemManager::GetSystem(); + // instance->setVolume(audioSystem->GetMasterVolume() * (isSfx ? audioSystem->GetSfxVolume() : audioSystem->GetBgmVolume())); + //} - auto audioSystem = SHSystemManager::GetSystem(); - SHVec3 listenerPos = audioSystem->GetListenerPosition(); - attributes.position.x = position[0]; - attributes.position.y = position[1]; - attributes.position.z = listenerPos[2]; - instance->set3DAttributes(&attributes); - instance->setVolume(audioSystem->GetMasterVolume() * (isSfx ? audioSystem->GetSfxVolume() : audioSystem->GetBgmVolume())); - } + //void AudioClip::Play(SHVec3 position, bool isSfx) + //{ + // if (!instance) + // return; + // instance->start(); + // FMOD_3D_ATTRIBUTES attributes{ {} }; + // attributes.forward.z = 1.0f; + // attributes.up.y = 1.0f; + + // auto audioSystem = SHSystemManager::GetSystem(); + // SHVec3 listenerPos = audioSystem->GetListenerPosition(); + // attributes.position.x = position[0]; + // attributes.position.y = position[1]; + // attributes.position.z = listenerPos[2]; + // instance->set3DAttributes(&attributes); + // instance->setVolume(audioSystem->GetMasterVolume() * (isSfx ? audioSystem->GetSfxVolume() : audioSystem->GetBgmVolume())); + //} void AudioClip::Stop(bool fadeOut) { @@ -557,12 +598,12 @@ namespace SHADE instance->setParameterByName(paramName, value); } - void AudioClip::SetParameterLabel(const char* paramName, const char* label) - { - if (!instance) - return; - instance->setParameterByNameWithLabel(paramName, label); - } + //void AudioClip::SetParameterLabel(const char* paramName, const char* label) + //{ + // if (!instance) + // return; + // instance->setParameterByNameWithLabel(paramName, label); + //} float AudioClip::GetParameterValue(const char* paramName) { diff --git a/SHADE_Engine/src/AudioSystem/SHAudioSystem.h b/SHADE_Engine/src/AudioSystem/SHAudioSystem.h index 777334e6..08505ea9 100644 --- a/SHADE_Engine/src/AudioSystem/SHAudioSystem.h +++ b/SHADE_Engine/src/AudioSystem/SHAudioSystem.h @@ -13,6 +13,7 @@ #include "Events/SHEvent.h" #include "SH_API.h" +#include #define AUDIO_SYS_MAX_CHANNELS 1024 namespace SHADE @@ -22,27 +23,22 @@ namespace SHADE class SHAudioListenerComponent; - typedef uint64_t AudioClipID; - class AudioClip { public: - AudioClip() = default; - AudioClip(AudioClipID clipID, FMOD::Studio::EventInstance* inst); - ~AudioClip(); - void Play(bool isSfx = true); - void Play(SHVec3 position, bool isSfx = true); + void Play(); + //void Play(SHVec3 position); void Stop(bool fadeOut = true); void SetPause(bool pause); bool IsPaused(); void SetParameter(const char* paramName, float value); - void SetParameterLabel(const char* paramName, const char* label); + //void SetParameterLabel(const char* paramName, const char* label); float GetParameterValue(const char* paramName); friend class SHAudioSystem; private: - FMOD::Studio::EventInstance* instance; - AudioClipID id; + FMOD::Studio::EventInstance* instance = nullptr; + //SHTransformComponent* transformRef; }; class SH_API SHAudioSystem : public SHSystem @@ -62,7 +58,7 @@ namespace SHADE void Exit(); int GetAvailableChannelIndex(); - /*std::vector::size_type CreateSound(const char* filepath, bool loop = false);*/ + void PlaySFX(EntityID id, EntityID eid, const bool& loop, const bool& spatial, float min = 5.0f, float max = 1000.0f); void PlayBGM(EntityID id, EntityID eid, const bool& loop, const bool& spatial, float min = 5.0f, float max = 1000.0f); void PlayEventOnce(const char* path, bool isSFX = true, EntityID eid = MAX_EID, bool spatial = false); @@ -71,8 +67,10 @@ namespace SHADE void StopAllSounds(); std::optional GetEventGUID(const char* path); - AudioClip* CreateAudioClip(const char* path); - //std::vector GetAllEvents(); + //AudioClip* CreateAudioClip(const char* path); + Handle CreateAudioClip(const char* path); + void AddAudioClipToBGMChannelGroup(Handle handle); + void AddAudioClipToSFXChannelGroup(Handle handle); float GetBgmVolume(); float GetSfxVolume(); @@ -84,6 +82,7 @@ namespace SHADE bool GetPaused() const; SHVec3 GetListenerPosition(); void LoadBank(const char* path); + private: FMOD::Studio::System* fmodStudioSystem; FMOD::System* fmodSystem; @@ -95,7 +94,9 @@ namespace SHADE //std::unordered_map bankMap; std::unordered_map bankMap; std::unordered_map eventMap; - std::unordered_map eventInstances; + //std::unordered_map eventInstances; + SHResourceLibrary audioClipLibrary{}; + FMOD::ChannelGroup* bgmChannelGroup, * sfxChannelGroup, * masterGroup; FMOD::Channel* audioChannels[AUDIO_SYS_MAX_CHANNELS]; FMOD_RESULT result; @@ -105,7 +106,6 @@ namespace SHADE SHBank masterBank, stringsBank, musicBank, sfxBank; //To do: change to map of banks loaded by resource manager std::vector* denseListener; - AudioClipID clipID = 0; SHEventHandle onPlay(SHEventPtr onStopEvent); SHEventHandle onStop(SHEventPtr onStopEvent); From 44ca317e1da5eba19d778d1c4ca704a76af14e99 Mon Sep 17 00:00:00 2001 From: Brandon Mak Date: Wed, 28 Dec 2022 10:22:01 +0800 Subject: [PATCH 084/275] WIP will update later, afraid for BSOD again --- Assets/Shaders/DebugDrawMesh_VS.glsl | 3 +- Assets/Shaders/DebugDraw_VS.glsl | 3 +- Assets/Shaders/TestCube_Tile_VS.glsl | 3 +- Assets/Shaders/TestCube_VS.glsl | 3 +- Assets/Shaders/Text_VS.glsl | 5 +- Assets/Shaders/UI_VS.glsl | 5 +- SHADE_Engine/src/Editor/SHEditor.cpp | 7 +- .../Graphics/Commands/SHVkCommandBuffer.cpp | 2 +- .../src/Graphics/Commands/SHVkCommandBuffer.h | 2 +- .../GlobalData/SHGlobalDescriptorSets.cpp | 31 +- .../GlobalData/SHGlobalDescriptorSets.h | 24 +- .../MiddleEnd/GlobalData/SHPredefinedData.cpp | 41 +- .../MiddleEnd/Interface/SHDebugDrawSystem.cpp | 8 +- .../MiddleEnd/Interface/SHGraphicsConstants.h | 1 + .../MiddleEnd/Interface/SHGraphicsSystem.cpp | 359 ++++++++++++------ .../MiddleEnd/Interface/SHGraphicsSystem.h | 30 +- .../MiddleEnd/Interface/SHRenderer.cpp | 89 ++--- .../Graphics/MiddleEnd/Interface/SHRenderer.h | 34 +- .../MiddleEnd/Interface/SHViewport.cpp | 28 -- .../Graphics/MiddleEnd/Interface/SHViewport.h | 14 +- .../MiddleEnd/Lights/SHLightingSubSystem.cpp | 11 +- .../MiddleEnd/Lights/SHLightingSubSystem.h | 11 +- .../SHTextRenderingSubSystem.cpp | 30 +- .../TextRendering/SHTextRenderingSubSystem.h | 8 +- .../Graphics/RenderGraph/SHRenderGraph.cpp | 53 ++- .../src/Graphics/RenderGraph/SHRenderGraph.h | 16 +- .../RenderGraph/SHRenderGraphNode.cpp | 14 +- .../Graphics/RenderGraph/SHRenderGraphNode.h | 6 +- .../RenderGraph/SHRenderGraphNodeCompute.h | 2 + .../src/Graphics/RenderGraph/SHSubpass.cpp | 34 +- .../src/Graphics/RenderGraph/SHSubpass.h | 24 +- 31 files changed, 548 insertions(+), 353 deletions(-) diff --git a/Assets/Shaders/DebugDrawMesh_VS.glsl b/Assets/Shaders/DebugDrawMesh_VS.glsl index 2f035261..19c1a5b9 100644 --- a/Assets/Shaders/DebugDrawMesh_VS.glsl +++ b/Assets/Shaders/DebugDrawMesh_VS.glsl @@ -16,8 +16,7 @@ layout(set = 2, binding = 0) uniform CameraData vec4 position; mat4 vpMat; mat4 viewMat; - mat4 perspectiveMat; - mat4 orthoMat; + mat4 projMat; } cameraData; void main() diff --git a/Assets/Shaders/DebugDraw_VS.glsl b/Assets/Shaders/DebugDraw_VS.glsl index 42a48c01..ce2dd544 100644 --- a/Assets/Shaders/DebugDraw_VS.glsl +++ b/Assets/Shaders/DebugDraw_VS.glsl @@ -15,8 +15,7 @@ layout(set = 2, binding = 0) uniform CameraData vec4 position; mat4 vpMat; mat4 viewMat; - mat4 perspectiveMat; - mat4 orthoMat; + mat4 projMat; } cameraData; void main() diff --git a/Assets/Shaders/TestCube_Tile_VS.glsl b/Assets/Shaders/TestCube_Tile_VS.glsl index 31a448fe..d27805ef 100644 --- a/Assets/Shaders/TestCube_Tile_VS.glsl +++ b/Assets/Shaders/TestCube_Tile_VS.glsl @@ -39,8 +39,7 @@ layout(set = 2, binding = 0) uniform CameraData vec4 position; mat4 vpMat; mat4 viewMat; - mat4 perspectiveMat; - mat4 orthoMat; + mat4 projMat; } cameraData; layout (std430, set = 3, binding = 0) buffer MaterialProperties // For materials diff --git a/Assets/Shaders/TestCube_VS.glsl b/Assets/Shaders/TestCube_VS.glsl index 774bc580..0e055395 100644 --- a/Assets/Shaders/TestCube_VS.glsl +++ b/Assets/Shaders/TestCube_VS.glsl @@ -34,8 +34,7 @@ layout(set = 2, binding = 0) uniform CameraData vec4 position; mat4 vpMat; mat4 viewMat; - mat4 perspectiveMat; - mat4 orthoMat; + mat4 projMat; } cameraData; void main() diff --git a/Assets/Shaders/Text_VS.glsl b/Assets/Shaders/Text_VS.glsl index 3501a13d..0498ae39 100644 --- a/Assets/Shaders/Text_VS.glsl +++ b/Assets/Shaders/Text_VS.glsl @@ -30,8 +30,7 @@ layout(set = 2, binding = 0) uniform CameraData vec4 position; mat4 vpMat; mat4 viewMat; - mat4 perspectiveMat; - mat4 orthoMat; + mat4 projMat; } cameraData; // push constants @@ -96,6 +95,6 @@ void main() Out2.textColor = testPushConstant.textColor; // transform the vertex position to font space - gl_Position = cameraData.orthoMat * localModel * vec4(vertexPos, 1.0f); + gl_Position = cameraData.projMat * localModel * vec4(vertexPos, 1.0f); // gl_Position = vec4(vertexPos, 1.0f); } \ No newline at end of file diff --git a/Assets/Shaders/UI_VS.glsl b/Assets/Shaders/UI_VS.glsl index c4393b98..622cf827 100644 --- a/Assets/Shaders/UI_VS.glsl +++ b/Assets/Shaders/UI_VS.glsl @@ -34,8 +34,7 @@ layout(set = 2, binding = 0) uniform CameraData vec4 position; mat4 vpMat; mat4 viewMat; - mat4 perspectiveMat; - mat4 orthoMat; + mat4 projMat; } cameraData; void main() @@ -60,7 +59,7 @@ void main() Out.normal.rgb = normalize (Out.normal.rgb); // clip space for rendering - gl_Position = cameraData.orthoMat * worldTransform * vec4 (aVertexPos, 1.0f); + gl_Position = cameraData.projMat * worldTransform * vec4 (aVertexPos, 1.0f); gl_Position.z += 0.1f; // HAX // gl_Position = vec4 (aVertexPos, 1.0f); } \ No newline at end of file diff --git a/SHADE_Engine/src/Editor/SHEditor.cpp b/SHADE_Engine/src/Editor/SHEditor.cpp index 4f1586ac..a12a19f7 100644 --- a/SHADE_Engine/src/Editor/SHEditor.cpp +++ b/SHADE_Engine/src/Editor/SHEditor.cpp @@ -500,10 +500,7 @@ namespace SHADE imguiCommandBuffer = imguiCommandPool->RequestCommandBuffer(SH_CMD_BUFFER_TYPE::PRIMARY); //auto const& renderers = gfxSystem->GetDefaultViewport()->GetRenderers(); - auto const& renderers = gfxSystem->GetEditorViewport()->GetRenderers(); - - SHASSERT(!renderers.empty(), "No Renderers available") - auto renderGraph = renderers[SHGraphicsConstants::RenderGraphIndices::EDITOR]->GetRenderGraph(); + auto renderGraph = gfxSystem->GetRenderGraph(); auto renderPass = renderGraph->GetNode("ImGui Node")->GetRenderpass(); if(ImGui_ImplVulkan_Init(&initInfo, renderPass->GetVkRenderpass()) == false) @@ -523,7 +520,7 @@ namespace SHADE ImGui_ImplVulkan_DestroyFontUploadObjects(); - renderGraph->GetNode("ImGui Node")->GetSubpass("ImGui Draw")->AddExteriorDrawCalls([](Handle& cmd, uint32_t frameIndex) + renderGraph->GetNode("ImGui Node")->GetSubpass("ImGui Draw")->AddExteriorDrawCalls([](Handle cmd, Handle renderer, uint32_t frameIndex) { cmd->BeginLabeledSegment("ImGui Draw"); ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), cmd->GetVkCommandBuffer()); diff --git a/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp index 05fd4288..37b89883 100644 --- a/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp +++ b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp @@ -367,7 +367,7 @@ namespace SHADE } } - void SHVkCommandBuffer::BindDescriptorSet(Handle descSetGroup, SH_PIPELINE_TYPE bindPoint, uint32_t firstSet, std::span dynamicOffsets) + void SHVkCommandBuffer::BindDescriptorSet(Handle descSetGroup, SH_PIPELINE_TYPE bindPoint, uint32_t firstSet, std::span const dynamicOffsets) { uint32_t bindPointIndex = static_cast(bindPoint); vkCommandBuffer.bindDescriptorSets(SHVkUtil::GetPipelineBindPointFromType(bindPoint), bindPointData[bindPointIndex].boundPipelineLayoutHdl->GetVkPipelineLayout(), firstSet, descSetGroup->GetVkHandle(), dynamicOffsets); diff --git a/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h index fc348487..c6a17d2a 100644 --- a/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h +++ b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h @@ -125,7 +125,7 @@ namespace SHADE void BindPipeline (Handle const& pipelineHdl) noexcept; void BindVertexBuffer (uint32_t bindingPoint, Handle const& buffer, vk::DeviceSize offset) noexcept; void BindIndexBuffer (Handle const& buffer, uint32_t startingIndex) const noexcept; - void BindDescriptorSet (Handle descSetGroup, SH_PIPELINE_TYPE bindPoint, uint32_t firstSet, std::span dynamicOffsets); + void BindDescriptorSet (Handle descSetGroup, SH_PIPELINE_TYPE bindPoint, uint32_t firstSet, std::span const dynamicOffsets); // Draw Commands void DrawArrays (uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, uint32_t firstInstance) const noexcept; diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.cpp index 2d6cc9e1..09dbef51 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.cpp @@ -1,10 +1,31 @@ #include "SHpch.h" #include "SHGlobalDescriptorSets.h" +#include "Graphics/MiddleEnd/Lights/SHLightingSubSystem.h" +#include "Graphics/Commands/SHVkCommandBuffer.h" namespace SHADE { - Handle SHGlobalDescriptorSets::lightDescriptorSet; + Handle SHGlobalDescriptorSets::staticGlobalDataDescriptorSet; + Handle SHGlobalDescriptorSets::lightingSubSystem; + + //void SHGlobalDescriptorSets::BindLightingData(Handle cmdBuffer, SH_PIPELINE_TYPE pipelineType, uint32_t firstSet) noexcept + //{ + // // Bind descriptor set for light data + // cmdBuffer->BindDescriptorSet(SHGlobalDescriptorSets::GetLightDescriptorSet(), SH_PIPELINE_TYPE::COMPUTE, descMappings[SHGraphicsConstants::PredefinedDescSetLayoutTypes::LIGHTS], const std::span{ TEX_DYNAMIC_OFFSET.data(), 1 }); + //} + + void SHGlobalDescriptorSets::BindLightingData(Handle cmdBuffer, SH_PIPELINE_TYPE pipelineType, uint32_t setIndex, uint32_t frameIndex) noexcept + { + lightingSubSystem->BindDescSet(cmdBuffer, setIndex, frameIndex); + } + + void SHGlobalDescriptorSets::BindStaticGlobalData(Handle cmdBuffer, SH_PIPELINE_TYPE pipelineType, uint32_t setIndex) noexcept + { + // Bind descriptor set for static global data + static constexpr std::array TEX_DYNAMIC_OFFSET{ 0 }; + cmdBuffer->BindDescriptorSet(staticGlobalDataDescriptorSet, pipelineType, setIndex, const std::span{ TEX_DYNAMIC_OFFSET.data(), 1 }); + } /***************************************************************************/ /*! @@ -17,14 +38,14 @@ namespace SHADE */ /***************************************************************************/ - void SHGlobalDescriptorSets::SetLightDescriptorSet(Handle lightDescSet) noexcept + void SHGlobalDescriptorSets::SetLightingSubSystem(Handle system) noexcept { - lightDescriptorSet = lightDescSet; + lightingSubSystem = system; } - Handle SHGlobalDescriptorSets::GetLightDescriptorSet(void) noexcept + void SHGlobalDescriptorSets::SetStaticGlobalDataDescriptorSet(Handle staticGlobalDescSet) noexcept { - return lightDescriptorSet; + staticGlobalDataDescriptorSet = staticGlobalDescSet; } } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h index ce6c42bb..2e2dca7d 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h @@ -2,23 +2,35 @@ #include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" #include "Resource/SHHandle.h" +#include "Graphics/Pipeline/SHPipelineType.h" namespace SHADE { + class SHLightingSubSystem; + class SHVkCommandBuffer; + // This class is mainly for descriptors that are truly global, meaning they only come from 1 place and they are shared between many systems class SHGlobalDescriptorSets { private: - //! Light data descriptor set - static Handle lightDescriptorSet; + + //! Static global descriptor sets for miscellaneous data and textures + static Handle staticGlobalDataDescriptorSet; + //! Lighting sub system required to get information to bind descriptor sets for light data + static Handle lightingSubSystem; + public: + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + static void BindLightingData (Handle cmdBuffer, SH_PIPELINE_TYPE pipelineType, uint32_t setIndex, uint32_t frameIndex) noexcept; + static void BindStaticGlobalData (Handle cmdBuffer, SH_PIPELINE_TYPE pipelineType, uint32_t setIndex) noexcept; + /*-----------------------------------------------------------------------*/ /* SETTERS AND GETTERS */ /*-----------------------------------------------------------------------*/ - static void SetLightDescriptorSet (Handle lightDescSet) noexcept; - - static Handle GetLightDescriptorSet (void) noexcept; - + static void SetLightingSubSystem (Handle system) noexcept; + static void SetStaticGlobalDataDescriptorSet (Handle staticGlobalDescSet) noexcept; }; } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.cpp index ac7fac17..ac7ab982 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.cpp @@ -125,33 +125,33 @@ namespace SHADE Handle materialDataPerInstanceLayout = logicalDevice->CreateDescriptorSetLayout({ materialDataBinding }); SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, materialDataPerInstanceLayout->GetVkHandle(), "[Descriptor Set Layout] Material Globals"); - //// font bitmap data (texture) - //SHVkDescriptorSetLayout::Binding fontBitmapBinding - //{ - // .Type = vk::DescriptorType::eCombinedImageSampler, - // .Stage = vk::ShaderStageFlagBits::eFragment, - // .BindPoint = SHGraphicsConstants::DescriptorSetBindings::FONT_BITMAP_DATA, - // .DescriptorCount = 1, - //}; + // font bitmap data (texture) + SHVkDescriptorSetLayout::Binding fontBitmapBinding + { + .Type = vk::DescriptorType::eCombinedImageSampler, + .Stage = vk::ShaderStageFlagBits::eFragment, + .BindPoint = SHGraphicsConstants::DescriptorSetBindings::FONT_BITMAP_DATA, + .DescriptorCount = 1, + }; - //// font data in the form of matrices - //SHVkDescriptorSetLayout::Binding fontMatrixBinding - //{ - // .Type = vk::DescriptorType::eStorageBuffer, - // .Stage = vk::ShaderStageFlagBits::eVertex, - // .BindPoint = SHGraphicsConstants::DescriptorSetBindings::FONT_MATRIX_DATA, - // .DescriptorCount = 1, - //}; + // font data in the form of matrices + SHVkDescriptorSetLayout::Binding fontMatrixBinding + { + .Type = vk::DescriptorType::eStorageBuffer, + .Stage = vk::ShaderStageFlagBits::eVertex, + .BindPoint = SHGraphicsConstants::DescriptorSetBindings::FONT_MATRIX_DATA, + .DescriptorCount = 1, + }; - //Handle fontDataDescSetLayout = logicalDevice->CreateDescriptorSetLayout({ fontBitmapBinding, fontMatrixBinding }); - //SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, fontDataDescSetLayout->GetVkHandle(), "[Descriptor Set Layout] Font Data"); + Handle fontDataDescSetLayout = logicalDevice->CreateDescriptorSetLayout({ fontBitmapBinding, fontMatrixBinding }); + SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, fontDataDescSetLayout->GetVkHandle(), "[Descriptor Set Layout] Font Data"); predefinedLayouts.push_back(staticGlobalLayout); predefinedLayouts.push_back(lightDataDescSetLayout); predefinedLayouts.push_back(cameraDataGlobalLayout); predefinedLayouts.push_back(materialDataPerInstanceLayout); - //predefinedLayouts.push_back(fontDataDescSetLayout); + predefinedLayouts.push_back(fontDataDescSetLayout); batchingSystemData.descSetLayouts = GetPredefinedDescSetLayouts ( @@ -163,7 +163,8 @@ namespace SHADE textSystemData.descSetLayouts = GetPredefinedDescSetLayouts ( SHGraphicsConstants::PredefinedDescSetLayoutTypes::STATIC_DATA | - SHGraphicsConstants::PredefinedDescSetLayoutTypes::CAMERA + SHGraphicsConstants::PredefinedDescSetLayoutTypes::CAMERA | + SHGraphicsConstants::PredefinedDescSetLayoutTypes::FONT ); } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp index d77fbeb0..b57249de 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp @@ -99,10 +99,10 @@ namespace SHADE createMeshBatches(); // Register function for subpass - auto const& RENDERERS = gfxSystem->GetDefaultViewport()->GetRenderers(); - auto renderGraph = RENDERERS[SHGraphicsConstants::RenderGraphIndices::WORLD]->GetRenderGraph(); + //auto const& RENDERERS = gfxSystem->GetDefaultViewport()->GetRenderers(); + auto renderGraph = gfxSystem->GetRenderGraph(); auto subPass = renderGraph->GetNode("Debug Draw")->GetSubpass("Debug Draw"); - subPass->AddExteriorDrawCalls([this](Handle& cmdBuffer, uint32_t frameIndex) + subPass->AddExteriorDrawCalls([this](Handle cmdBuffer, Handle renderer, uint32_t frameIndex) { const uint32_t FRAME_IDX = gfxSystem->GetCurrentFrameIndex(); cmdBuffer->BeginLabeledSegment("SHDebugDraw (No Depth Test)"); @@ -126,7 +126,7 @@ namespace SHADE cmdBuffer->EndLabeledSegment(); }); auto subPassWithDepth = renderGraph->GetNode("Debug Draw with Depth")->GetSubpass("Debug Draw with Depth"); - subPassWithDepth->AddExteriorDrawCalls([this](Handle& cmdBuffer, uint32_t frameIndex) + subPassWithDepth->AddExteriorDrawCalls([this](Handle cmdBuffer, Handle renderer, uint32_t frameIndex) { const uint32_t FRAME_IDX = gfxSystem->GetCurrentFrameIndex(); cmdBuffer->BeginLabeledSegment("SHDebugDraw (Depth Tested)"); diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h index c80b9de5..06ffe381 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h @@ -34,6 +34,7 @@ namespace SHADE LIGHTS = 0x02, CAMERA = 0x04, MATERIALS = 0x08, + FONT = 0x10, }; // This enum is different from the one above in that it is used to initialize a hash table to diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp index 6881d7a8..461b8783 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp @@ -44,6 +44,7 @@ of DigiPen Institute of Technology is prohibited. #include "../Meshes/SHPrimitiveGenerator.h" #include "Graphics/MiddleEnd/TextRendering/SHFreetypeInstance.h" #include "Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.h" +#include "Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h" namespace SHADE { @@ -142,7 +143,7 @@ namespace SHADE static constexpr AssetID RENDER_SC_FS = 36869006; renderToSwapchainFS = SHResourceManager::LoadOrGet(RENDER_SC_FS); } - void SHGraphicsSystem::InitSceneRenderGraph(void) noexcept + void SHGraphicsSystem::InitRenderGraph(void) noexcept { /*-----------------------------------------------------------------------*/ /* MIDDLE END SETUP @@ -158,55 +159,66 @@ namespace SHADE auto cameraSystem = SHSystemManager::GetSystem(); // Set Up Cameras - screenCamera = resourceManager.Create(); - screenCamera->SetLookAt(SHVec3(0.0f, 0.0f, -1.0f), SHVec3(0.0f, 0.0f, 1.0f), SHVec3(0.0f, 1.0f, 0.0f)); - screenCamera->SetOrthographic(static_cast(windowDims.first), static_cast(windowDims.second), 0.01f, 100.0f); + //screenCamera = resourceManager.Create(); + //screenCamera->SetLookAt(SHVec3(0.0f, 0.0f, -1.0f), SHVec3(0.0f, 0.0f, 1.0f), SHVec3(0.0f, 1.0f, 0.0f)); + //screenCamera->SetOrthographic(static_cast(windowDims.first), static_cast(windowDims.second), 0.01f, 100.0f); - worldCamera = resourceManager.Create(); - worldCamera->SetLookAt(SHVec3(0.0f, 0.0f, 0.0f), SHVec3(0.0f, 0.0f, -2.0f), SHVec3(0.0f, 1.0f, 0.0f)); - worldCamera->SetPerspective(90.0f, static_cast(windowDims.first), static_cast(windowDims.second), 0.0f, 100.0f); + //worldCamera = resourceManager.Create(); + //worldCamera->SetLookAt(SHVec3(0.0f, 0.0f, 0.0f), SHVec3(0.0f, 0.0f, -2.0f), SHVec3(0.0f, 1.0f, 0.0f)); + //worldCamera->SetPerspective(90.0f, static_cast(windowDims.first), static_cast(windowDims.second), 0.0f, 100.0f); worldCameraDirector = cameraSystem->CreateDirector(); + /*-----------------------------------------------------------------------*/ + /* PREPARE RENDERERS */ + /*-----------------------------------------------------------------------*/ + // Add world renderer to default viewport + worldRenderer = AddRenderer(SHRenderer::PROJECTION_TYPE::PERSPECTIVE); + worldRenderer->SetCameraDirector(worldCameraDirector); + + // Add screen renderer to default viewport + screenRenderer = AddRenderer(SHRenderer::PROJECTION_TYPE::ORTHOGRAPHIC); + screenRenderer->SetCameraDirector(worldCameraDirector); + // Create Default Viewport worldViewport = AddViewport(vk::Viewport(0.0f, 0.0f, static_cast(window->GetWindowSize().first), static_cast(window->GetWindowSize().second), 0.0f, 1.0f)); - // Get render graph from default viewport world renderer - worldRenderGraph = resourceManager.Create(); - std::vector> renderContextCmdPools{ swapchain->GetNumImages() }; for (uint32_t i = 0; i < renderContextCmdPools.size(); ++i) { renderContextCmdPools[i] = renderContext.GetFrameData(i).cmdPoolHdls[0]; } + // Get render graph from default viewport world renderer + renderGraph = resourceManager.Create(); + /*-----------------------------------------------------------------------*/ /* WORLD RENDER GRAPH RESOURCES */ /*-----------------------------------------------------------------------*/ // Initialize world render graph - worldRenderGraph->Init("World Render Graph", device, swapchain, &resourceManager); - worldRenderGraph->AddResource("Position", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR32G32B32A32Sfloat); - worldRenderGraph->AddResource("Normals", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR32G32B32A32Sfloat); + renderGraph->Init("World Render Graph", device, swapchain, &resourceManager, renderContextCmdPools); + renderGraph->AddResource("Position", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR32G32B32A32Sfloat); + renderGraph->AddResource("Normals", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR32G32B32A32Sfloat); //worldRenderGraph->AddResource("Tangents", { SH_ATT_DESC_TYPE_FLAGS::COLOR, SH_ATT_DESC_TYPE_FLAGS::INPUT, SH_ATT_DESC_TYPE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR32G32B32A32Sfloat); - worldRenderGraph->AddResource("Albedo", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second); - worldRenderGraph->AddResource("Depth Buffer", { SH_RENDER_GRAPH_RESOURCE_FLAGS::DEPTH_STENCIL }, windowDims.first, windowDims.second, vk::Format::eD32SfloatS8Uint); - worldRenderGraph->AddResource("Entity ID", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::SHARED }, windowDims.first, windowDims.second, vk::Format::eR32Uint, 1, vk::ImageUsageFlagBits::eTransferSrc); - worldRenderGraph->AddResource("Light Layer Indices", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR32Uint, 1, vk::ImageUsageFlagBits::eTransferSrc); - worldRenderGraph->AddResource("Scene", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE, SH_RENDER_GRAPH_RESOURCE_FLAGS::SHARED }, windowDims.first, windowDims.second); - worldRenderGraph->AddResource("SSAO", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR8Unorm); - worldRenderGraph->AddResource("SSAO Blur", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR8Unorm); + renderGraph->AddResource("Albedo", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second); + renderGraph->AddResource("Depth Buffer", { SH_RENDER_GRAPH_RESOURCE_FLAGS::DEPTH_STENCIL }, windowDims.first, windowDims.second, vk::Format::eD32SfloatS8Uint); + renderGraph->AddResource("Entity ID", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::SHARED }, windowDims.first, windowDims.second, vk::Format::eR32Uint, 1, vk::ImageUsageFlagBits::eTransferSrc); + renderGraph->AddResource("Light Layer Indices", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR32Uint, 1, vk::ImageUsageFlagBits::eTransferSrc); + renderGraph->AddResource("Scene", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE, SH_RENDER_GRAPH_RESOURCE_FLAGS::SHARED }, windowDims.first, windowDims.second); + renderGraph->AddResource("SSAO", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR8Unorm); + renderGraph->AddResource("SSAO Blur", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR8Unorm); + renderGraph->AddResource("Present", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR_PRESENT }, windowDims.first, windowDims.second, vk::Format::eR8Unorm); /*-----------------------------------------------------------------------*/ /* MAIN NODE */ /*-----------------------------------------------------------------------*/ - auto gBufferNode = worldRenderGraph->AddNode("G-Buffer", + auto gBufferNode = renderGraph->AddNode("G-Buffer", { "Position", "Entity ID", "Light Layer Indices", "Normals", - //"Tangents", "Albedo", "Depth Buffer", "Scene", @@ -218,20 +230,21 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* G-BUFFER SUBPASS INIT */ /*-----------------------------------------------------------------------*/ - auto gBufferSubpass = gBufferNode->AddSubpass("G-Buffer Write"); + auto gBufferSubpass = gBufferNode->AddSubpass("G-Buffer Write", worldViewport, worldRenderer); gBufferSubpass->AddColorOutput("Position"); gBufferSubpass->AddColorOutput("Entity ID"); gBufferSubpass->AddColorOutput("Light Layer Indices"); gBufferSubpass->AddColorOutput("Normals"); - //gBufferSubpass->AddColorOutput("Tangents"); gBufferSubpass->AddColorOutput("Albedo"); gBufferSubpass->AddDepthOutput("Depth Buffer", SH_RENDER_GRAPH_RESOURCE_FLAGS::DEPTH_STENCIL); + /*-----------------------------------------------------------------------*/ /* SSAO PASS AND DATA INIT */ /*-----------------------------------------------------------------------*/ ssaoStorage = resourceManager.Create(); + // command buffer operation to transfer data for ssao ssaoTransferCmdBuffer = graphicsCmdPool->RequestCommandBuffer(SH_CMD_BUFFER_TYPE::PRIMARY); SET_VK_OBJ_NAME(device, vk::ObjectType::eCommandBuffer, ssaoTransferCmdBuffer->GetVkCommandBuffer(), "[Command Buffer] SSAO Pass (Graphics)"); ssaoTransferCmdBuffer->BeginRecording(); @@ -240,50 +253,80 @@ namespace SHADE ssaoTransferCmdBuffer->EndRecording(); graphicsQueue->SubmitCommandBuffer({ ssaoTransferCmdBuffer }); - // Set up Debug Draw Passes - // - Depth Tested - auto debugDrawNodeDepth = worldRenderGraph->AddNode("Debug Draw with Depth", { "Scene", "Depth Buffer" }, {"G-Buffer"}); - auto debugDrawDepthSubpass = debugDrawNodeDepth->AddSubpass("Debug Draw with Depth"); - debugDrawDepthSubpass->AddColorOutput("Scene"); - debugDrawDepthSubpass->AddDepthOutput("Depth Buffer"); - // - No Depth Test - auto debugDrawNode = worldRenderGraph->AddNode("Debug Draw", { "Scene" }, { "Debug Draw with Depth" }); - auto debugDrawSubpass = debugDrawNode->AddSubpass("Debug Draw"); - debugDrawSubpass->AddColorOutput("Scene"); + // wait for command buffer to finish executing graphicsQueue->WaitIdle(); - ssaoStorage->PrepareRotationVectorsVkData(device); + // Add the pass to generate an image with just SSAO data Handle ssaoPass = gBufferNode->AddNodeCompute("SSAO", ssaoShader, { "Position", "Normals", "SSAO" }); auto ssaoDataBuffer = ssaoStorage->GetBuffer(); ssaoPass->ModifyWriteDescBufferComputeResource(SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_NODE_COMPUTE_RESOURCE, SHSSAO::DESC_SET_BUFFER_BINDING, { &ssaoDataBuffer, 1 }, 0, ssaoStorage->GetBuffer()->GetSizeStored()); auto viewSamplerLayout = ssaoStorage->GetViewSamplerLayout(); - ssaoPass->ModifyWriteDescImageComputeResource(SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_NODE_COMPUTE_RESOURCE, SHSSAO::DESC_SET_IMAGE_BINDING, {&viewSamplerLayout, 1}); + ssaoPass->ModifyWriteDescImageComputeResource(SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_NODE_COMPUTE_RESOURCE, SHSSAO::DESC_SET_IMAGE_BINDING, { &viewSamplerLayout, 1 }); - - Handle ssaoBlurPass = gBufferNode->AddNodeCompute("SSAO Blur Step", ssaoBlurShader, {"SSAO", "SSAO Blur"}); + // Add another pass to blur SSAO + Handle ssaoBlurPass = gBufferNode->AddNodeCompute("SSAO Blur Step", ssaoBlurShader, { "SSAO", "SSAO Blur" }); /*-----------------------------------------------------------------------*/ /* DEFERRED COMPOSITE SUBPASS INIT */ /*-----------------------------------------------------------------------*/ - gBufferNode->AddNodeCompute("Deferred Composite", deferredCompositeShader, {"Position", "Normals", "Albedo", "Light Layer Indices", "SSAO Blur", "Scene"}); + gBufferNode->AddNodeCompute("Deferred Composite", deferredCompositeShader, { "Position", "Normals", "Albedo", "Light Layer Indices", "SSAO Blur", "Scene" }); + + /*-----------------------------------------------------------------------*/ + /* DEBUG DRAW PASS INIT */ + /*-----------------------------------------------------------------------*/ + // Set up Debug Draw Passes + // - Depth Tested + auto debugDrawNodeDepth = renderGraph->AddNode("Debug Draw with Depth", { "Scene", "Depth Buffer" }, {"G-Buffer"}); + auto debugDrawDepthSubpass = debugDrawNodeDepth->AddSubpass("Debug Draw with Depth", worldViewport, worldRenderer); + debugDrawDepthSubpass->AddColorOutput("Scene"); + debugDrawDepthSubpass->AddDepthOutput("Depth Buffer"); + // - No Depth Test + auto debugDrawNode = renderGraph->AddNode("Debug Draw", { "Scene" }, { "Debug Draw with Depth" }); + auto debugDrawSubpass = debugDrawNode->AddSubpass("Debug Draw", worldViewport, worldRenderer); + debugDrawSubpass->AddColorOutput("Scene"); + + /*-----------------------------------------------------------------------*/ + /* SCREEN SPACE PASS */ + /*-----------------------------------------------------------------------*/ + auto screenSpaceNode = renderGraph->AddNode("Screen Space Pass", { "Scene", "Entity ID" }, {"G-Buffer", "Debug Draw" }); + auto uiSubpass = screenSpaceNode->AddSubpass("UI", worldViewport, screenRenderer); + uiSubpass->AddColorOutput("Scene"); + uiSubpass->AddColorOutput("Entity ID"); + uiSubpass->AddExteriorDrawCalls([=](Handle cmdBuffer, Handle renderer, uint32_t frameIndex) + { + textRenderingSubSystem->Render(cmdBuffer, renderer, frameIndex); + }); + + /*-----------------------------------------------------------------------*/ + /* RENDER TO SWAPCHAIN IMAGE FOR PRESENT PASS */ + /*-----------------------------------------------------------------------*/ +#ifdef SHEDITOR { - //// Dummy Node to transition scene render graph resource - //auto dummyNode = worldRenderGraph->AddNode("Dummy Pass", { "Scene" }, { "Debug Draw" }); // no predecessors - //auto dummySubpass = dummyNode->AddSubpass("Dummy Subpass"); - //dummySubpass->AddInput("Scene"); + // Dummy Node to transition scene render graph resource + auto dummyNode = renderGraph->AddNode("Dummy Pass", { "Scene" }, { "Screen Space Pass" }); + auto dummySubpass = dummyNode->AddSubpass("Dummy Subpass", {}, {}); + dummySubpass->AddInput("Scene"); + + auto imGuiNode = renderGraph->AddNode("ImGui Node", { "Present" }, {}); + auto imGuiSubpass = imGuiNode->AddSubpass("ImGui Draw", {}, {}); + imGuiSubpass->AddColorOutput("Present"); } +#else + renderGraph->AddRenderToSwapchainNode("Scene", "Present", { "Screen Space Pass" }, { renderToSwapchainVS, renderToSwapchainFS }); +#endif + /*-----------------------------------------------------------------------*/ - /* GENERATE WORLD RENDER GRAPH */ + /* GENERATE RENDER GRAPH */ /*-----------------------------------------------------------------------*/ - // Generate world render graph - worldRenderGraph->Generate(); - + // Generate render graph + renderGraph->Generate(); +#if 0 /*-----------------------------------------------------------------------*/ /* SCREEN RENDER GRAPH */ /*-----------------------------------------------------------------------*/ @@ -317,18 +360,7 @@ namespace SHADE screenRenderGraph->Generate(); - /*-----------------------------------------------------------------------*/ - /* BIND RENDER GRAPH TO RENDERER */ - /*-----------------------------------------------------------------------*/ - // Add world renderer to default viewport - worldRenderer = worldViewport->AddRenderer(resourceManager, swapchain->GetNumImages(), renderContextCmdPools, descPool, SHPredefinedData::GetPredefinedDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS], worldRenderGraph); - worldRenderer->SetCamera(worldCamera); - worldRenderer->SetCameraDirector(worldCameraDirector); - - // Add screen renderer to default viewport - screenRenderer = worldViewport->AddRenderer(resourceManager, swapchain->GetNumImages(), renderContextCmdPools, descPool, SHPredefinedData::GetPredefinedDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS], screenRenderGraph); - screenRenderer->SetCamera(screenCamera); - screenRenderer->SetCameraDirector(worldCameraDirector); +#endif // Create debug draw pipeline debugDrawPipeline = createDebugDrawPipeline(debugDrawNode->GetRenderpass(), debugDrawSubpass, false, false, false); @@ -361,10 +393,12 @@ namespace SHADE { SHPredefinedData::Init(device); - InitSceneRenderGraph(); + InitRenderGraph(); -#ifdef SHEDITOR - InitEditorRenderGraph(); +#if 0 + #ifdef SHEDITOR + InitEditorRenderGraph(); + #endif #endif // Create Semaphore @@ -377,7 +411,7 @@ namespace SHADE void SHGraphicsSystem::InitSubsystems(void) noexcept { - mousePickSystem = resourceManager.Create(); + mousePickSubSystem = resourceManager.Create(); std::vector> cmdPools; cmdPools.reserve(swapchain->GetNumImages()); @@ -385,11 +419,11 @@ namespace SHADE cmdPools.push_back(renderContext.GetFrameData(i).cmdPoolHdls[0]); // Mouse picking system for the editor (Will still run with editor disabled) - mousePickSystem->Init(device, cmdPools, worldRenderGraph->GetRenderGraphResource("Entity ID")); + mousePickSubSystem->Init(device, cmdPools, renderGraph->GetRenderGraphResource("Entity ID")); // Register the post offscreen render to the system - postOffscreenRender = resourceManager.Create(); - postOffscreenRender->Init(device, worldRenderGraph->GetRenderGraphResource("Scene"), descPool); + postOffscreenRenderSubSystem = resourceManager.Create(); + postOffscreenRenderSubSystem->Init(device, renderGraph->GetRenderGraphResource("Scene"), descPool); lightingSubSystem = resourceManager.Create(); lightingSubSystem->Init(device, descPool); @@ -397,11 +431,11 @@ namespace SHADE textRenderingSubSystem = resourceManager.Create(); // initialize the text renderer - auto uiNode = screenRenderGraph->GetNode("Screen Space Pass"); - textRenderingSubSystem->Init(device, uiNode->GetRenderpass(), uiNode->GetSubpass("UI"), descPool, textVS, textFS, [=](Handle cmdBuffer, uint32_t frameIndex) - { - screenRenderer->BindDescSet(cmdBuffer, frameIndex); - }); + auto uiNode = renderGraph->GetNode("Screen Space Pass"); + textRenderingSubSystem->Init(device, uiNode->GetRenderpass(), uiNode->GetSubpass("UI"), descPool, textVS, textFS); + + SHGlobalDescriptorSets::SetStaticGlobalDataDescriptorSet(texLibrary.GetTextureDescriptorSetGroup()); + SHGlobalDescriptorSets::SetLightingSubSystem(lightingSubSystem); } @@ -425,41 +459,42 @@ namespace SHADE defaultMaterial = AddMaterial ( defaultVertShader, defaultFragShader, - worldRenderGraph->GetNode("G-Buffer")->GetSubpass("G-Buffer Write") + renderGraph->GetNode("G-Buffer")->GetSubpass("G-Buffer Write") ); defaultMaterial->SetProperty("data.textureIndex", defaultTexture->TextureArrayIndex); } -#ifdef SHEDITOR - void SHGraphicsSystem::InitEditorRenderGraph(void) noexcept - { - auto windowDims = window->GetWindowSize(); +#if 0 + #ifdef SHEDITOR + void SHGraphicsSystem::InitEditorRenderGraph(void) noexcept + { + auto windowDims = window->GetWindowSize(); - // Create Default Viewport - editorViewport = AddViewport(vk::Viewport(0.0f, 0.0f, static_cast(windowDims.first), static_cast(windowDims.second), 0.0f, 1.0f)); + // Create Default Viewport + editorViewport = AddViewport(vk::Viewport(0.0f, 0.0f, static_cast(windowDims.first), static_cast(windowDims.second), 0.0f, 1.0f)); - // Get render graph from viewport editor renderer - editorRenderGraph = resourceManager.Create(); + // Get render graph from viewport editor renderer + editorRenderGraph = resourceManager.Create(); - std::vector> renderContextCmdPools{ swapchain->GetNumImages() }; - for (uint32_t i = 0; i < renderContextCmdPools.size(); ++i) - renderContextCmdPools[i] = renderContext.GetFrameData(i).cmdPoolHdls[0]; + std::vector> renderContextCmdPools{ swapchain->GetNumImages() }; + for (uint32_t i = 0; i < renderContextCmdPools.size(); ++i) + renderContextCmdPools[i] = renderContext.GetFrameData(i).cmdPoolHdls[0]; - editorRenderGraph->Init("Editor Render Graph", device, swapchain, &resourceManager); - editorRenderGraph->AddResource("Present", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR_PRESENT }, windowDims.first, windowDims.second); + editorRenderGraph->Init("Editor Render Graph", device, swapchain, &resourceManager); + editorRenderGraph->AddResource("Present", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR_PRESENT }, windowDims.first, windowDims.second); - auto imguiNode = editorRenderGraph->AddNode("ImGui Node", { "Present"}, {}); - auto imguiSubpass = imguiNode->AddSubpass("ImGui Draw"); - imguiSubpass->AddColorOutput("Present"); + auto imguiNode = editorRenderGraph->AddNode("ImGui Node", { "Present"}, {}); + auto imguiSubpass = imguiNode->AddSubpass("ImGui Draw"); + imguiSubpass->AddColorOutput("Present"); - // Generate world render graph - editorRenderGraph->Generate(); + // Generate world render graph + editorRenderGraph->Generate(); - // Add world renderer to default viewport - editorRenderer = editorViewport->AddRenderer(resourceManager, swapchain->GetNumImages(), renderContextCmdPools, descPool, SHPredefinedData::GetPredefinedDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS], editorRenderGraph); - editorRenderer->SetCamera(worldCamera); - } + // Add world renderer to default viewport + editorRenderer = AddRenderer(SHRenderer::PROJECTION_TYPE::PERSPECTIVE); + } + #endif #endif /*---------------------------------------------------------------------------------*/ @@ -547,6 +582,41 @@ namespace SHADE textRenderingSubSystem->Run(frameIndex); + + for (auto renderer : renderers) + { +#ifdef SHEDITOR + if (renderer == worldRenderer) + { + auto editorSystem = SHSystemManager::GetSystem(); + if (editorSystem->editorState != SHEditor::State::PLAY) + worldRenderer->UpdateDataManual(frameIndex, cameraSystem->GetEditorCamera()->GetViewMatrix(), cameraSystem->GetEditorCamera()->GetProjMatrix()); + else + renderer->UpdateData(frameIndex); + } + else + renderer->UpdateData(frameIndex); +#else + renderers[renIndex]->UpdateDataAndBind(frameIndex); +#endif + } + + renderGraph->Begin(frameIndex); + auto cmdBuffer = renderGraph->GetCommandBuffer(frameIndex); + + // Bind all the buffers required for meshes + for (auto& [buffer, bindingPoint] : MESH_DATA) + { + if (buffer->GetUsageBits() & vk::BufferUsageFlagBits::eVertexBuffer) + cmdBuffer->BindVertexBuffer(bindingPoint, buffer, 0); + else if (buffer->GetUsageBits() & vk::BufferUsageFlagBits::eIndexBuffer) + cmdBuffer->BindIndexBuffer(buffer, 0); + } + + renderGraph->Execute(frameIndex, descPool); + renderGraph->End(frameIndex); + +#if 0 // For every viewport for (int vpIndex = 0; vpIndex < static_cast(viewports.size()); ++vpIndex) { @@ -564,10 +634,10 @@ namespace SHADE // Begin recording the command buffer currentCmdBuffer->BeginRecording(); - // set viewport and scissor - uint32_t w = static_cast(viewports[vpIndex]->GetWidth()); - uint32_t h = static_cast(viewports[vpIndex]->GetHeight()); - currentCmdBuffer->SetViewportScissor (static_cast(w), static_cast(h), w, h); + //// set viewport and scissor + //uint32_t w = static_cast(viewports[vpIndex]->GetWidth()); + //uint32_t h = static_cast(viewports[vpIndex]->GetHeight()); + //currentCmdBuffer->SetViewportScissor (static_cast(w), static_cast(h), w, h); // Force set the pipeline layout currentCmdBuffer->ForceSetPipelineLayout(SHPredefinedData::GetDummyPipelineLayout(), SH_PIPELINE_TYPE::GRAPHICS); @@ -601,20 +671,20 @@ namespace SHADE // bind camera data //renderers[renIndex]->UpdateDataAndBind(currentCmdBuffer, frameIndex); -#ifdef SHEDITOR - if (renderers[renIndex] == worldRenderer) - { - auto editorSystem = SHSystemManager::GetSystem(); - if (editorSystem->editorState != SHEditor::State::PLAY) - worldRenderer->UpdateDataAndBind(currentCmdBuffer, frameIndex, cameraSystem->GetEditorCamera()->GetViewMatrix(), cameraSystem->GetEditorCamera()->GetProjMatrix(), cameraSystem->GetEditorCamera()->GetOrthoMatrix()); - else - renderers[renIndex]->UpdateDataAndBind(currentCmdBuffer, frameIndex); - } - else - renderers[renIndex]->UpdateDataAndBind(currentCmdBuffer, frameIndex); -#else - renderers[renIndex]->UpdateDataAndBind(currentCmdBuffer, frameIndex); -#endif +//#ifdef SHEDITOR +// if (renderers[renIndex] == worldRenderer) +// { +// auto editorSystem = SHSystemManager::GetSystem(); +// if (editorSystem->editorState != SHEditor::State::PLAY) +// worldRenderer->UpdateDataManual(currentCmdBuffer, frameIndex, cameraSystem->GetEditorCamera()->GetViewMatrix(), cameraSystem->GetEditorCamera()->GetProjMatrix(), cameraSystem->GetEditorCamera()->GetOrthoMatrix()); +// else +// renderers[renIndex]->UpdateDataAndBind(currentCmdBuffer, frameIndex); +// } +// else +// renderers[renIndex]->UpdateDataAndBind(currentCmdBuffer, frameIndex); +//#else +// renderers[renIndex]->UpdateDataAndBind(currentCmdBuffer, frameIndex); +//#endif // Draw the scene renderers[renIndex]->Draw(frameIndex, descPool); @@ -638,8 +708,10 @@ namespace SHADE semIndex = !semIndex; } } +#endif } + /***************************************************************************/ /*! @@ -677,12 +749,8 @@ namespace SHADE // #BackEndTest: For for the fence initialized by queue submit renderContext.WaitForFence(); - // Finalise all batches - for (auto vp : viewports) - for (auto renderer : vp->GetRenderers()) - { - renderer->GetRenderGraph()->FinaliseBatch(renderContext.GetCurrentFrame(), descPool); - } + // finalize batches for render graph + renderGraph->FinaliseBatch(renderContext.GetCurrentFrame(), descPool); // #BackEndTest: Acquire the next image in the swapchain available renderContext.AcquireNextIamge(); @@ -723,7 +791,7 @@ namespace SHADE const uint32_t CURR_FRAME_IDX = renderContext.GetCurrentFrame(); auto& currFrameData = renderContext.GetCurrentFrameData(); - mousePickSystem->Run(graphicsQueue, CURR_FRAME_IDX); + mousePickSubSystem->Run(graphicsQueue, CURR_FRAME_IDX); // #BackEndTest: queues an image for presentation if (auto result = graphicsQueue->Present(swapchain, { currFrameData.semRenderFinishHdl }, CURR_FRAME_IDX); result != vk::Result::eSuccess) @@ -763,6 +831,40 @@ namespace SHADE viewports.erase(iter); } + /*---------------------------------------------------------------------------------*/ + /* Renderer Registration Functions */ + /*---------------------------------------------------------------------------------*/ + Handle SHGraphicsSystem::AddRenderer(SHRenderer::PROJECTION_TYPE projectionType) + { + std::vector> renderContextCmdPools{ swapchain->GetNumImages() }; + for (uint32_t i = 0; i < renderContextCmdPools.size(); ++i) + { + renderContextCmdPools[i] = renderContext.GetFrameData(i).cmdPoolHdls[0]; + } + + // Create the renderer + auto renderer = resourceManager.Create(device, swapchain->GetNumImages(), renderContextCmdPools, descPool); + + // Store + renderers.emplace_back(renderer); + + // Return + return renderer; + } + void SHGraphicsSystem::RemoveRenderer(Handle renderer) + { + auto iter = std::find(renderers.begin(), renderers.end(), renderer); + if (iter == renderers.end()) + { + SHLOG_WARNING("Attempted to remove a Renderer that does not belong to a viewport!"); + return; + } + + // Remove it + iter->Free(); + renderers.erase(iter); + } + Handle SHGraphicsSystem::AddMaterial(Handle vertShader, Handle fragShader, Handle subpass) { // Retrieve pipeline from pipeline storage or create if unavailable @@ -1058,7 +1160,7 @@ namespace SHADE renderContext.HandleResize(); - worldRenderGraph->HandleResize(resizeWidth, resizeHeight); + renderGraph->HandleResize(resizeWidth, resizeHeight); #ifdef SHEDITOR @@ -1067,13 +1169,13 @@ namespace SHADE //editorViewport->SetWidth(windowDims.first); //editorViewport->SetHeight(windowDims.second); - editorRenderGraph->HandleResize(windowDims.first, windowDims.second); + //editorRenderGraph->HandleResize(windowDims.first, windowDims.second); #endif - screenRenderGraph->HandleResize(resizeWidth, resizeHeight); + //screenRenderGraph->HandleResize(resizeWidth, resizeHeight); - mousePickSystem->HandleResize(); - postOffscreenRender->HandleResize(); + mousePickSubSystem->HandleResize(); + postOffscreenRenderSubSystem->HandleResize(); worldViewport->SetWidth(static_cast(resizeWidth)); worldViewport->SetHeight(static_cast(resizeHeight)); @@ -1102,9 +1204,14 @@ namespace SHADE } + Handle SHGraphicsSystem::GetRenderGraph(void) const noexcept + { + return renderGraph; + } + Handle SHGraphicsSystem::GetPrimaryRenderpass() const noexcept { - return worldRenderGraph->GetNode(G_BUFFER_RENDER_GRAPH_NODE_NAME.data()); + return renderGraph->GetNode(G_BUFFER_RENDER_GRAPH_NODE_NAME.data()); } Handle SHGraphicsSystem::GetDebugDrawPipeline(DebugDrawPipelineType type) const noexcept @@ -1137,7 +1244,7 @@ namespace SHADE device, SHPipelineLayoutParams { .shaderModules = { (instanced ? debugMeshVertShader : debugVertShader) , debugFragShader }, - .globalDescSetLayouts = SHPredefinedData::GetPredefinedDescSetLayouts(SHGraphicsConstants::PredefinedDescSetLayoutTypes::CAMERA) + .predefinedDescSetLayouts = SHPredefinedData::GetPredefinedDescSetLayouts(SHGraphicsConstants::PredefinedDescSetLayoutTypes::CAMERA) } ); auto pipeline = resourceManager.Create(device, pipelineLayout, nullptr, renderPass, subpass); diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h index 75b48c9b..0aa83f21 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h @@ -34,6 +34,7 @@ of DigiPen Institute of Technology is prohibited. #include "Graphics/MiddleEnd/PostProcessing/SHSSAO.h" #include "Camera/SHCameraDirector.h" #include "Graphics/MiddleEnd/TextRendering/SHFontLibrary.h" +#include "Graphics/MiddleEnd/Interface/SHRenderer.h" namespace SHADE { @@ -49,7 +50,6 @@ namespace SHADE class SHVkImage; class SHVkFramebuffer; class SHVkCommandBuffer; - class SHRenderer; class SHViewport; class SHCamera; class SHVkShaderModule; @@ -99,7 +99,7 @@ namespace SHADE { private: void InitBoilerplate (void) noexcept; - void InitSceneRenderGraph (void) noexcept; + void InitRenderGraph (void) noexcept; void InitMiddleEnd (void) noexcept; void InitSubsystems (void) noexcept; void InitBuiltInResources (void); @@ -171,6 +171,13 @@ namespace SHADE Handle AddViewport(const vk::Viewport& viewport); void RemoveViewport(Handle viewport); + /*-----------------------------------------------------------------------------*/ + /* Renderers Registration Functions */ + /*-----------------------------------------------------------------------------*/ + Handle AddRenderer(SHRenderer::PROJECTION_TYPE projectionType); + void RemoveRenderer(Handle renderer); + + /*-----------------------------------------------------------------------------*/ /* Material Functions */ /*-----------------------------------------------------------------------------*/ @@ -382,8 +389,9 @@ namespace SHADE #ifdef SHEDITOR Handle GetEditorViewport () const {return editorViewport;}; #endif - Handle GetMousePickSystem(void) const noexcept {return mousePickSystem;}; - Handle GetPostOffscreenRenderSystem(void) const noexcept {return postOffscreenRender;}; + Handle GetMousePickSystem(void) const noexcept {return mousePickSubSystem;}; + Handle GetPostOffscreenRenderSystem(void) const noexcept {return postOffscreenRenderSubSystem;}; + Handle GetRenderGraph (void) const noexcept; Handle GetPrimaryRenderpass() const noexcept; Handle GetDebugDrawPipeline(DebugDrawPipelineType type) const noexcept; uint32_t GetCurrentFrameIndex(void) const noexcept { return renderContext.GetCurrentFrame(); } @@ -441,10 +449,8 @@ namespace SHADE // Renderers Handle worldRenderer; Handle screenRenderer; + std::vector> renderers; - // Temp Cameras - Handle worldCamera; - Handle screenCamera; DirectorHandle worldCameraDirector; @@ -483,15 +489,15 @@ namespace SHADE std::array, MAX_PRIMITIVE_TYPES> primitiveMeshes; // Render Graphs - Handle worldRenderGraph; - Handle screenRenderGraph; + Handle renderGraph; + //Handle screenRenderGraph; #ifdef SHEDITOR - Handle editorRenderGraph; + //Handle editorRenderGraph; #endif // Sub systems - Handle mousePickSystem; - Handle postOffscreenRender; + Handle mousePickSubSystem; + Handle postOffscreenRenderSubSystem; Handle lightingSubSystem; Handle textRenderingSubSystem; Handle ssaoStorage; diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.cpp index 1136a2c9..be9f0482 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.cpp @@ -14,7 +14,6 @@ of DigiPen Institute of Technology is prohibited. #include "SHRenderer.h" #include "Graphics/Devices/SHVkLogicalDevice.h" -#include "SHViewport.h" #include "Graphics/Buffers/SHVkBuffer.h" #include "Graphics/Framebuffer/SHVkFramebuffer.h" #include "SHMaterial.h" @@ -22,22 +21,22 @@ of DigiPen Institute of Technology is prohibited. #include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" #include "Graphics/Buffers/SHVkBuffer.h" #include "Camera/SHCameraDirector.h" +#include "Graphics/MiddleEnd/GlobalData/SHPredefinedData.h" namespace SHADE { /*-----------------------------------------------------------------------------------*/ /* Constructor/Destructors */ /*-----------------------------------------------------------------------------------*/ - SHRenderer::SHRenderer(Handle logicalDevice, uint32_t numFrames, std::vector>& cmdPools, Handle descriptorPool, Handle cameraDescLayout, Handle viewport, Handle renderGraph) - : viewport { viewport } - , renderGraph { renderGraph } + SHRenderer::SHRenderer(Handle logicalDevice, uint32_t numFrames, Handle descriptorPool, PROJECTION_TYPE type) + : projectionType{type} { - commandBuffers.resize(static_cast(numFrames)); + //commandBuffers.resize(static_cast(numFrames)); - for (uint32_t i = 0; i < commandBuffers.size(); ++i) - commandBuffers[i] = cmdPools[i]->RequestCommandBuffer(SH_CMD_BUFFER_TYPE::PRIMARY); + //for (uint32_t i = 0; i < commandBuffers.size(); ++i) + // commandBuffers[i] = cmdPools[i]->RequestCommandBuffer(SH_CMD_BUFFER_TYPE::PRIMARY); - cameraDescriptorSet = descriptorPool->Allocate({ cameraDescLayout }, { 1 }); + cameraDescriptorSet = descriptorPool->Allocate(SHPredefinedData::GetPredefinedDescSetLayouts(SHGraphicsConstants::PredefinedDescSetLayoutTypes::CAMERA), { 1 }); #ifdef _DEBUG const auto& CAM_DESC_SETS = cameraDescriptorSet->GetVkHandle(); @@ -61,18 +60,6 @@ namespace SHADE SHRenderer::~SHRenderer(void) { - //for (auto& cmdBuffer : commandBuffers) - //{ - // cmdBuffer.Free(); - //} - } - - /*-----------------------------------------------------------------------------------*/ - /* Camera Registration */ - /*-----------------------------------------------------------------------------------*/ - void SHRenderer::SetCamera(Handle _camera) - { - camera = _camera; } void SHRenderer::SetCameraDirector(Handle director) noexcept @@ -80,63 +67,55 @@ namespace SHADE cameraDirector = director; } - /*-----------------------------------------------------------------------------------*/ - /* Drawing Functions */ - /*-----------------------------------------------------------------------------------*/ - void SHRenderer::Draw(uint32_t frameIndex, Handle descPool) noexcept + void SHRenderer::UpdateData(uint32_t frameIndex) noexcept { - renderGraph->Execute(frameIndex, commandBuffers[frameIndex], descPool); - } - - void SHRenderer::UpdateDataAndBind(Handle cmdBuffer, uint32_t frameIndex) noexcept - { - if (camera && cameraDirector) + if (cameraDirector) { - //UpdateDataAndBind(cmdBuffer, frameIndex, SHMatrix::Transpose(cameraDirector->GetVPMatrix())); - UpdateDataAndBind(cmdBuffer, frameIndex, cameraDirector->GetViewMatrix(), cameraDirector->GetProjMatrix(), cameraDirector->GetOrthoMatrix()); + switch (projectionType) + { + case PROJECTION_TYPE::DEFAULT: + UpdateDataManual(frameIndex, cameraDirector->GetViewMatrix(), cameraDirector->GetProjMatrix()); + break; + case PROJECTION_TYPE::PERSPECTIVE: + UpdateDataManual(frameIndex, cameraDirector->GetViewMatrix(), cameraDirector->GetPerspectiveMatrix()); + break; + case PROJECTION_TYPE::ORTHOGRAPHIC: + UpdateDataManual(frameIndex, cameraDirector->GetViewMatrix(), cameraDirector->GetOrthoMatrix()); + break; + default: + UpdateDataManual(frameIndex, cameraDirector->GetViewMatrix(), cameraDirector->GetProjMatrix()); + break; + } } } - void SHRenderer::UpdateDataAndBind(Handle cmdBuffer, uint32_t frameIndex, SHMatrix const& viewMatrix, SHMatrix const& projMatrix, SHMatrix const& orthoMatrix) noexcept + void SHRenderer::UpdateDataManual(uint32_t frameIndex, SHMatrix const& viewMatrix, SHMatrix const& projMatrix) noexcept { - SetViewProjectionMatrix(viewMatrix, projMatrix, orthoMatrix); + SetViewProjectionMatrix(viewMatrix, projMatrix); - //cpuCameraData.viewProjectionMatrix = camera->GetViewProjectionMatrix(); cameraBuffer->WriteToMemory(&cpuCameraData, sizeof(SHShaderCameraData), 0, cameraDataAlignedSize * frameIndex); - BindDescSet(cmdBuffer, frameIndex); + //BindDescSet(cmdBuffer, frameIndex); } - void SHRenderer::BindDescSet(Handle cmdBuffer, uint32_t frameIndex) noexcept + void SHRenderer::BindDescriptorSet(Handle cmdBuffer, SH_PIPELINE_TYPE pipelineType, uint32_t setIndex, uint32_t frameIndex) noexcept { std::array dynamicOffsets{ frameIndex * cameraDataAlignedSize }; - cmdBuffer->BindDescriptorSet(cameraDescriptorSet, SH_PIPELINE_TYPE::GRAPHICS, SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS, std::span{ dynamicOffsets.data(), 1 }); - cmdBuffer->BindDescriptorSet(cameraDescriptorSet, SH_PIPELINE_TYPE::COMPUTE, SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS, std::span{ dynamicOffsets.data(), 1 }); + cmdBuffer->BindDescriptorSet(cameraDescriptorSet, pipelineType, setIndex, std::span{ dynamicOffsets.data(), 1 }); } - void SHRenderer::UpdateCameraDataToBuffer(void) noexcept + void SHRenderer::SetViewProjectionMatrix(SHMatrix const& viewMatrix, SHMatrix const& projMatrix) noexcept { - } - - void SHRenderer::SetViewProjectionMatrix(SHMatrix const& viewMatrix, SHMatrix const& projMatrix, SHMatrix const& orthoMatrix) noexcept - { - //cpuCameraData.viewProjectionMatrix = camera->GetViewMatrix() * camera->GetProjectionMatrix(); cpuCameraData.viewProjectionMatrix = SHMatrix::Transpose(projMatrix * viewMatrix); cpuCameraData.viewMatrix = SHMatrix::Transpose(viewMatrix); cpuCameraData.projectionMatrix = SHMatrix::Transpose(projMatrix); - cpuCameraData.orthoMatrix = SHMatrix::Transpose (orthoMatrix); } - Handle SHRenderer::GetRenderGraph(void) const noexcept - { - return renderGraph; - } - - Handle SHRenderer::GetCommandBuffer(uint32_t frameIndex) const noexcept - { - return commandBuffers[frameIndex]; - } + //Handle SHRenderer::GetCommandBuffer(uint32_t frameIndex) const noexcept + //{ + // return commandBuffers[frameIndex]; + //} Handle SHRenderer::GetCameraDirector(void) const noexcept { diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.h index 4bd205be..c93050d7 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.h @@ -32,7 +32,6 @@ namespace SHADE class SHVkFramebuffer; class SHMaterial; class SHVkLogicalDevice; - class SHViewport; class SHVkImageView; class SHVkCommandBuffer; class SHCamera; @@ -48,7 +47,6 @@ namespace SHADE SHMatrix viewProjectionMatrix; SHMatrix viewMatrix; SHMatrix projectionMatrix; - SHMatrix orthoMatrix; }; /*---------------------------------------------------------------------------------*/ @@ -64,35 +62,36 @@ namespace SHADE /***********************************************************************************/ class SHRenderer { - public: + enum class PROJECTION_TYPE + { + DEFAULT, + PERSPECTIVE, + ORTHOGRAPHIC + }; + /*-----------------------------------------------------------------------------*/ /* Constructor/Destructors */ /*-----------------------------------------------------------------------------*/ - SHRenderer(Handle logicalDevice, uint32_t numFrames, std::vector>& cmdPools, Handle descriptorPool, Handle cameraDescLayout, Handle viewport, Handle renderGraph); + SHRenderer(Handle logicalDevice, uint32_t numFrames, Handle descriptorPool, PROJECTION_TYPE type); ~SHRenderer(void); /*-----------------------------------------------------------------------------*/ /* Camera Registration */ /*-----------------------------------------------------------------------------*/ - void SetCamera(Handle _camera); void SetCameraDirector (Handle director) noexcept; /*-----------------------------------------------------------------------------*/ /* Drawing Functions */ /*-----------------------------------------------------------------------------*/ - void Draw(uint32_t frameIndex, Handle descPool) noexcept; - void UpdateDataAndBind(Handle cmdBuffer, uint32_t frameIndex) noexcept; - void UpdateDataAndBind(Handle cmdBuffer, uint32_t frameIndex, SHMatrix const& viewMatrix, SHMatrix const& projMatrix, SHMatrix const& orthoMatrix) noexcept; - void BindDescSet (Handle cmdBuffer, uint32_t frameIndex) noexcept; - void UpdateCameraDataToBuffer (void) noexcept; - void SetViewProjectionMatrix (SHMatrix const& viewMatrix, SHMatrix const& projMatrix, SHMatrix const& orthoMatrix) noexcept; + void UpdateData(uint32_t frameIndex) noexcept; + void UpdateDataManual(uint32_t frameIndex, SHMatrix const& viewMatrix, SHMatrix const& projMatrix) noexcept; + void BindDescriptorSet (Handle cmdBuffer, SH_PIPELINE_TYPE pipelineType, uint32_t setIndex, uint32_t frameIndex) noexcept; + void SetViewProjectionMatrix (SHMatrix const& viewMatrix, SHMatrix const& projMatrix) noexcept; /*-----------------------------------------------------------------------------*/ /* Setters and Getters */ /*-----------------------------------------------------------------------------*/ - Handle GetRenderGraph (void) const noexcept; - Handle GetCommandBuffer(uint32_t frameIndex) const noexcept; Handle GetCameraDirector (void) const noexcept; private: @@ -102,9 +101,6 @@ namespace SHADE //! Vulkan UBOs need to be aligned, this is pad SHShaderCameraData struct uint32_t cameraDataAlignedSize; - Handle viewport; - Handle camera; - Handle renderGraph; Handle cameraDescriptorSet; Handle cameraBuffer; @@ -114,10 +110,10 @@ namespace SHADE // GPU. SHShaderCameraData cpuCameraData; - //! Command buffers for the render graph - std::vector> commandBuffers; - + ////! Command buffers for the render graph + //std::vector> commandBuffers; + PROJECTION_TYPE projectionType; }; } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.cpp index 7bd0049f..078261a5 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.cpp @@ -46,34 +46,6 @@ namespace SHADE ); } - /*---------------------------------------------------------------------------------*/ - /* Renderer Registration Functions */ - /*---------------------------------------------------------------------------------*/ - Handle SHViewport::AddRenderer(SHResourceHub& resourceManager, uint32_t numFrames, std::vector>& cmdPools, Handle descriptorPool, Handle cameraDescLayout, Handle renderGraph) - { - // Create the renderer - auto renderer = resourceManager.Create(device, numFrames, cmdPools, descriptorPool, cameraDescLayout, GetHandle(), renderGraph); - - // Store - renderers.emplace_back(renderer); - - // Return - return renderer; - } - void SHViewport::RemoveRenderer(Handle renderer) - { - auto iter = std::find(renderers.begin(), renderers.end(), renderer); - if (iter == renderers.end()) - { - SHLOG_WARNING("Attempted to remove a Renderer that does not belong to a viewport!"); - return; - } - - // Remove it - iter->Free(); - renderers.erase(iter); - } - void SHViewport::SetWidth(float w) noexcept { viewport.width = w; diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.h index 26c0a6bd..60bd6e95 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.h @@ -56,11 +56,11 @@ namespace SHADE /*-----------------------------------------------------------------------------*/ void SetUp(Handle commandBuffer); - /*-----------------------------------------------------------------------------*/ - /* Renderers Registration Functions */ - /*-----------------------------------------------------------------------------*/ - Handle AddRenderer(SHResourceHub& resourceManager, uint32_t numFrames, std::vector>& cmdPools, Handle descriptorPool, Handle cameraDescLayout, Handle renderGraph); - void RemoveRenderer(Handle renderer); + ///*-----------------------------------------------------------------------------*/ + ///* Renderers Registration Functions */ + ///*-----------------------------------------------------------------------------*/ + //Handle AddRenderer(SHResourceHub& resourceManager, uint32_t numFrames, std::vector>& cmdPools, Handle descriptorPool, Handle cameraDescLayout, Handle renderGraph); + //void RemoveRenderer(Handle renderer); /*-----------------------------------------------------------------------------*/ /* Setters */ @@ -79,7 +79,7 @@ namespace SHADE float GetHeight() const { return viewport.height; } float GetMinDepth() const { return viewport.minDepth; } float GetMaxDepth() const { return viewport.maxDepth; } - std::vector>& GetRenderers() { return renderers; } + //std::vector>& GetRenderers() { return renderers; } private: @@ -88,7 +88,7 @@ namespace SHADE /*-----------------------------------------------------------------------------*/ Handle device; vk::Viewport viewport; - std::vector> renderers; + //std::vector> renderers; }; } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.cpp index 448c3bed..abbf88c3 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.cpp @@ -385,7 +385,7 @@ namespace SHADE std::fill (variableSizes.begin(), variableSizes.end(), 1); // Create the descriptor set - lightingDataDescSet = descPool->Allocate({ SHPredefinedData::GetPredefinedDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::DYNAMIC_GLOBALS] }, variableSizes); + lightingDataDescSet = descPool->Allocate({ SHPredefinedData::GetPredefinedDescSetLayouts(SHGraphicsConstants::PredefinedDescSetLayoutTypes::LIGHTS) }, variableSizes); #ifdef _DEBUG const auto& CAM_DESC_SETS = lightingDataDescSet->GetVkHandle(); for (int i = 0; i < static_cast(CAM_DESC_SETS.size()); ++i) @@ -517,11 +517,16 @@ namespace SHADE } - void SHLightingSubSystem::BindDescSet(Handle cmdBuffer, uint32_t frameIndex) noexcept + void SHLightingSubSystem::BindDescSet(Handle cmdBuffer, uint32_t setIndex, uint32_t frameIndex) noexcept { //Bind descriptor set(We bind at an offset because the buffer holds NUM_FRAME_BUFFERS sets of data). - cmdBuffer->BindDescriptorSet(lightingDataDescSet, SH_PIPELINE_TYPE::COMPUTE, SHGraphicsConstants::DescriptorSetIndex::DYNAMIC_GLOBALS, { dynamicOffsets[frameIndex] }); + cmdBuffer->BindDescriptorSet(lightingDataDescSet, SH_PIPELINE_TYPE::COMPUTE, setIndex, { dynamicOffsets[frameIndex] }); } + Handle SHLightingSubSystem::GetLightDataDescriptorSet(void) const noexcept + { + return lightingDataDescSet; + } + } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.h index ae6caead..fa103136 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.h @@ -4,6 +4,7 @@ #include "Math/Vector/SHVec3.h" #include "Math/Vector/SHVec4.h" #include "SHLightData.h" +#include #include "Graphics/MiddleEnd/Interface/SHGraphicsConstants.h" namespace SHADE @@ -57,8 +58,11 @@ namespace SHADE class SH_API SHLightingSubSystem { - private: + public: + using DynamicOffsetArray = std::array, static_cast(SHGraphicsConstants::NUM_FRAME_BUFFERS)>; + + private: class PerTypeData { private: @@ -130,7 +134,7 @@ namespace SHADE std::array(SH_LIGHT_TYPE::NUM_TYPES)> perTypeData; //! Container to store dynamic offsets for binding descriptor sets - std::array, static_cast(SHGraphicsConstants::NUM_FRAME_BUFFERS)> dynamicOffsets; + DynamicOffsetArray dynamicOffsets; //! holds the data that represents how many lights are in the scene std::array(SH_LIGHT_TYPE::NUM_TYPES)> lightCountsData; @@ -162,7 +166,8 @@ namespace SHADE void Run (SHMatrix const& viewMat, uint32_t frameIndex) noexcept; void Exit (void) noexcept; - void BindDescSet (Handle cmdBuffer, uint32_t frameIndex) noexcept; + void BindDescSet (Handle cmdBuffer, uint32_t setIndex, uint32_t frameIndex) noexcept; + Handle GetLightDataDescriptorSet (void) const noexcept; }; } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp index f117b26c..f9afa48f 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp @@ -11,6 +11,8 @@ #include "Graphics/SHVkUtil.h" #include "Graphics/RenderGraph/SHSubpass.h" #include "Math/Transform/SHTransformComponent.h" +#include "Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h" +#include "Graphics/MiddleEnd/Interface/SHRenderer.h" namespace SHADE { @@ -91,12 +93,10 @@ namespace SHADE } - void SHTextRenderingSubSystem::Init(Handle device, Handle compatibleRenderpass, Handle subpass, Handle descPool, Handle textVS, Handle textFS, std::function, uint32_t)> const& bindFunction) noexcept + void SHTextRenderingSubSystem::Init(Handle device, Handle compatibleRenderpass, Handle subpass, Handle descPool, Handle textVS, Handle textFS) noexcept { SHComponentManager::CreateComponentSparseSet(); - cameraDescSetBind = bindFunction; - logicalDevice = device; // prepare pipeline layout params @@ -174,9 +174,14 @@ namespace SHADE } } - void SHTextRenderingSubSystem::Render(Handle cmdBuffer, uint32_t frameIndex) noexcept + void SHTextRenderingSubSystem::Render(Handle cmdBuffer, Handle renderer, uint32_t frameIndex) noexcept { auto& textRendererComps = SHComponentManager::GetDense(); + auto const& mappings = SHPredefinedData::GetTextSystemData().descMappings; + uint32_t fontSetIndex = mappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::FONT); + uint32_t staticGlobalSetIndex = mappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::STATIC_DATA); + uint32_t cameraSetIndex = mappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::CAMERA); + for (auto& comp : textRendererComps) { auto* transform = SHComponentManager::GetComponent(comp.GetEID()); @@ -187,16 +192,19 @@ namespace SHADE // bind the pipeline cmdBuffer->BindPipeline(pipeline); + // Bind global data + SHGlobalDescriptorSets::BindStaticGlobalData(cmdBuffer, SH_PIPELINE_TYPE::GRAPHICS, staticGlobalSetIndex); + + // Bind camera data + renderer->BindDescriptorSet(cmdBuffer, SH_PIPELINE_TYPE::GRAPHICS, cameraSetIndex, frameIndex); + + // bind descriptors for font (matrices) + cmdBuffer->BindDescriptorSet(fontHandle->GetDescriptorSet(), SH_PIPELINE_TYPE::GRAPHICS, fontSetIndex, {}); + // bind VBO (position and indices) cmdBuffer->BindVertexBuffer(SHGraphicsConstants::VertexBufferBindings::CALCULATED_GLYPH_POSITION, comp.charPositionDataBuffer, 0); cmdBuffer->BindVertexBuffer(SHGraphicsConstants::VertexBufferBindings::GLYPH_INDEX, comp.indexingDataBuffer, 0); - // bind camera desc set (again). Necessary because pipeline layout is not compatible. - cameraDescSetBind(cmdBuffer, frameIndex); - - // bind descriptors for font (matrices) - cmdBuffer->BindDescriptorSet(fontHandle->GetDescriptorSet(), SH_PIPELINE_TYPE::GRAPHICS, SHGraphicsConstants::DescriptorSetIndex::FONT_DATA, {}); - cmdBuffer->SetPushConstantVariable("TestPushConstant.worldTransform", transform->GetTRS(), SH_PIPELINE_TYPE::GRAPHICS); cmdBuffer->SetPushConstantVariable("TestPushConstant.eid", comp.GetEID(), SH_PIPELINE_TYPE::GRAPHICS); cmdBuffer->SetPushConstantVariable("TestPushConstant.textColor", SHVec3 (1.0f, 1.0f, 1.0f), SH_PIPELINE_TYPE::GRAPHICS); @@ -206,9 +214,7 @@ namespace SHADE // call draw call cmdBuffer->DrawArrays(4, comp.text.size(), 0, 0); //glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, static_cast(textComp.lastRenderedCharacterIndex) + 1); - } - } } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.h index 78b363d4..c9a89129 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.h @@ -43,18 +43,14 @@ namespace SHADE //! Descriptor set for font data access in shaders //Handle fontDataDescSetLayout; - //! Super temporary. Global descriptor set needs to be revamped along with - //! entire graphics system. - std::function, uint32_t)> cameraDescSetBind; - private: void RecomputePositions(SHTextRenderableComponent& textComp) noexcept; public: - void Init(Handle device, Handle compatibleRenderpass, Handle subpass, Handle descPool, Handle textVS, Handle textFS, std::function, uint32_t)> const& bindFunction) noexcept; + void Init(Handle device, Handle compatibleRenderpass, Handle subpass, Handle descPool, Handle textVS, Handle textFS) noexcept; void Run(uint32_t frameIndex) noexcept; - void Render (Handle cmdBuffer, uint32_t frameIndex) noexcept; + void Render (Handle cmdBuffer, Handle renderer, uint32_t frameIndex) noexcept; void Exit(void) noexcept; diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp index fc029161..d431cf47 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp @@ -14,6 +14,8 @@ #include "Tools/Utilities/SHUtilities.h" #include "Graphics/MiddleEnd/GlobalData/SHPredefinedData.h" #include "Graphics/RenderGraph/SHRenderToSwapchainImageSystem.h" +#include "Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h" + namespace SHADE { @@ -424,7 +426,7 @@ namespace SHADE */ /***************************************************************************/ - void SHRenderGraph::Init(std::string graphName, Handle logicalDevice, Handle swapchain, SHResourceHub* resourceHub) noexcept + void SHRenderGraph::Init(std::string graphName, Handle logicalDevice, Handle swapchain, SHResourceHub* resourceHub, std::vector>& cmdPools) noexcept { //resourceHub = std::make_shared(); @@ -437,6 +439,11 @@ namespace SHADE renderGraphStorage->resourceHub = resourceHub; renderGraphStorage->descriptorPool = logicalDevice->CreateDescriptorPools(); + commandBuffers.resize(static_cast(swapchain->GetNumImages())); + + for (uint32_t i = 0; i < commandBuffers.size(); ++i) + commandBuffers[i] = cmdPools[i]->RequestCommandBuffer(SH_CMD_BUFFER_TYPE::PRIMARY); + name = std::move(graphName); } @@ -555,16 +562,22 @@ namespace SHADE if (renderGraphStorage->graphResources->contains(toSwapchainResource) && renderGraphStorage->graphResources->contains(swapchainResource)) { auto newNode = AddNode("Render To Present", { ResourceInstruction (toSwapchainResource.c_str()), ResourceInstruction(swapchainResource.c_str()) }, predecessorNodes); - auto newSubpass = newNode->AddSubpass("Render"); + auto newSubpass = newNode->AddSubpass("Render", {}, {}); newSubpass->AddColorOutput(swapchainResource); newSubpass->AddInput(toSwapchainResource); renderToSwapchainImageSystem = renderGraphStorage->resourceHub->Create (newNode, newSubpass, shaderModules); - newSubpass->AddExteriorDrawCalls([=](Handle& cmdBuffer, uint32_t frameIndex) + newSubpass->AddExteriorDrawCalls([=](Handle cmdBuffer, Handle renderer, uint32_t frameIndex) { cmdBuffer->BindPipeline(renderToSwapchainImageSystem->GetPipeline()); - + + // If we are rendering to present image, the width and height will be the dimensions of that image. So we need to set viewport scissor. + auto resource = renderGraphStorage->graphResources->at(swapchainResource); + uint32_t w = static_cast(resource->GetWidth()); + uint32_t h = static_cast(resource->GetHeight()); + cmdBuffer->SetViewportScissor(static_cast(w), static_cast(h), w, h); + newSubpass->BindDescriptorInputDescriptorSets (cmdBuffer, frameIndex); // draw a quad. @@ -616,14 +629,39 @@ namespace SHADE // TODO: The graph scope buffers were meant to bind vertex buffers and index buffers for meshes. Find a // better way to manage these - void SHRenderGraph::Execute(uint32_t frameIndex, Handle cmdBuffer, Handle descPool) noexcept + void SHRenderGraph::Execute(uint32_t frameIndex, Handle descPool) noexcept { + auto cmdBuffer = commandBuffers[frameIndex]; cmdBuffer->BeginLabeledSegment(name); + + // Force bind pipeline layout + cmdBuffer->ForceSetPipelineLayout(SHPredefinedData::GetBatchingSystemData().dummyPipelineLayout, SH_PIPELINE_TYPE::GRAPHICS); + cmdBuffer->ForceSetPipelineLayout(SHPredefinedData::GetBatchingSystemData().dummyPipelineLayout, SH_PIPELINE_TYPE::COMPUTE); + + auto const& descMappings = SHPredefinedData::GetBatchingSystemData().descMappings; + for (auto& node : nodes) + { + // bind static global data + SHGlobalDescriptorSets::BindStaticGlobalData(cmdBuffer, SH_PIPELINE_TYPE::GRAPHICS, descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::STATIC_DATA)); + node->Execute(cmdBuffer, descPool, frameIndex); + } + cmdBuffer->EndLabeledSegment(); } + void SHRenderGraph::Begin(uint32_t frameIndex) noexcept + { + commandBuffers[frameIndex]->BeginRecording(); + + } + + void SHRenderGraph::End(uint32_t frameIndex) noexcept + { + commandBuffers[frameIndex]->EndRecording(); + } + void SHRenderGraph::FinaliseBatch(uint32_t frameIndex, Handle descPool) { for (auto& node : nodes) @@ -670,4 +708,9 @@ namespace SHADE return {}; } + Handle SHRenderGraph::GetCommandBuffer(uint32_t frameIndex) const noexcept + { + return commandBuffers[frameIndex]; + } + } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h index f892483f..c69e83b1 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h @@ -74,6 +74,9 @@ namespace SHADE //! For rendering onto the swapchain Handle renderToSwapchainImageSystem; + //! Command buffer to issue rendering commands + std::vector> commandBuffers; + public: /*-----------------------------------------------------------------------*/ /* CTORS AND DTORS */ @@ -86,7 +89,7 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* PUBLIC MEMBER FUNCTIONS */ /*-----------------------------------------------------------------------*/ - void Init (std::string graphName, Handle logicalDevice, Handle swapchain, SHResourceHub* resourceHub) noexcept; + void Init (std::string graphName, Handle logicalDevice, Handle swapchain, SHResourceHub* resourceHub, std::vector>& cmdPools) noexcept; void AddResource(std::string resourceName, std::initializer_list typeFlags, uint32_t w = static_cast(-1), uint32_t h = static_cast(-1), vk::Format format = vk::Format::eB8G8R8A8Unorm, uint8_t levels = 1, vk::ImageUsageFlagBits usageFlags = {}, vk::ImageCreateFlagBits createFlags = {}); void LinkNonOwningResource (Handle resourceOrigin, std::string resourceName) noexcept; Handle AddNode (std::string nodeName, std::initializer_list resourceInstruction, std::initializer_list predecessorNodes) noexcept; @@ -94,16 +97,19 @@ namespace SHADE void Generate (void) noexcept; void CheckForNodeComputes (void) noexcept; - void Execute (uint32_t frameIndex, Handle cmdBuffer, Handle descPool) noexcept; + void Execute (uint32_t frameIndex, Handle descPool) noexcept; + void Begin (uint32_t frameIndex) noexcept; + void End (uint32_t frameIndex) noexcept; void FinaliseBatch (uint32_t frameIndex, Handle descPool); void HandleResize (uint32_t newWidth, uint32_t newHeight) noexcept; /*-----------------------------------------------------------------------*/ /* SETTERS AND GETTERS */ /*-----------------------------------------------------------------------*/ - Handle GetNode (std::string const& nodeName) const noexcept; - std::vector> const& GetNodes (void) const noexcept; - Handle GetRenderGraphResource (std::string const& resourceName) const noexcept; + Handle GetNode (std::string const& nodeName) const noexcept; + std::vector> const& GetNodes (void) const noexcept; + Handle GetRenderGraphResource (std::string const& resourceName) const noexcept; + Handle GetCommandBuffer (uint32_t frameIndex) const noexcept; }; } diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.cpp index 0f9379fe..1d8cea62 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.cpp +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.cpp @@ -9,6 +9,8 @@ #include "SHRenderGraphStorage.h" #include "Graphics/RenderGraph/SHRenderGraphNodeCompute.h" #include "Graphics/SHVkUtil.h" +#include "Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h" +#include "Graphics/MiddleEnd/GlobalData/SHPredefinedData.h" namespace SHADE { @@ -238,7 +240,7 @@ namespace SHADE */ /***************************************************************************/ - Handle SHRenderGraphNode::AddSubpass(std::string subpassName) noexcept + Handle SHRenderGraphNode::AddSubpass(std::string subpassName, Handle viewport, Handle renderer) noexcept { // if subpass already exists, don't add. if (subpassIndexing.contains(subpassName)) @@ -253,6 +255,8 @@ namespace SHADE graphStorage->resourceHub->Create ( subpassName, + viewport, + renderer, graphStorage, GetHandle(), static_cast(subpasses.size()), resourceAttachmentMapping.get() ) @@ -318,7 +322,7 @@ namespace SHADE } // insert them all for a subpass to transition them. This subpass is the last subpass - auto dummySubpass = AddSubpass("dummy"); + auto dummySubpass = AddSubpass("dummy", {}, {}); for (auto& resource : resourcesInvolved) { dummySubpass->AddGeneralInput(resource); @@ -331,7 +335,7 @@ namespace SHADE } } - void SHRenderGraphNode::Execute(Handle& commandBuffer, Handle descPool, uint32_t frameIndex) noexcept + void SHRenderGraphNode::Execute(Handle commandBuffer, Handle descPool, uint32_t frameIndex) noexcept { uint32_t framebufferIndex = (framebuffers.size() > 1) ? frameIndex : 0; commandBuffer->BeginRenderpass(renderpass, framebuffers[framebufferIndex]); @@ -347,10 +351,14 @@ namespace SHADE commandBuffer->EndRenderpass(); + auto const& descMappings = SHPredefinedData::GetBatchingSystemData().descMappings; // Execute all subpass computes for (auto& sbCompute : nodeComputes) { + // bind lighting data + SHGlobalDescriptorSets::BindLightingData(commandBuffer, SH_PIPELINE_TYPE::COMPUTE, descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::LIGHTS), frameIndex); + sbCompute->Execute(commandBuffer, frameIndex); } } diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.h b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.h index 2311ee0c..f7e55d4a 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.h +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.h @@ -22,6 +22,8 @@ namespace SHADE class SHPredefinedData; class SHRenderGraphStorage; class SHRenderGraphNodeCompute; + class SHRenderer; + class SHViewport; class SH_API SHRenderGraphNode : public ISelfHandle { @@ -102,12 +104,12 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* PUBLIC MEMBER FUNCTIONS */ /*-----------------------------------------------------------------------*/ - Handle AddSubpass(std::string subpassName) noexcept; + Handle AddSubpass(std::string subpassName, Handle viewport, Handle renderer) noexcept; Handle AddNodeCompute(std::string nodeName, Handle computeShaderModule, std::initializer_list resources, std::unordered_set&& dynamicBufferBindings = {}, float numWorkGroupScale = 1.0f) noexcept; void AddDummySubpassIfNeeded (void) noexcept; // TODO: RemoveSubpass() - void Execute(Handle& commandBuffer, Handle descPool, uint32_t frameIndex) noexcept; + void Execute(Handle commandBuffer, Handle descPool, uint32_t frameIndex) noexcept; Handle GetOrCreatePipeline(std::pair, Handle> const& vsFsPair, Handle subpass) noexcept; void FinaliseBatch(uint32_t frameIndex, Handle descPool); diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.h b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.h index 580f018c..a9a6ac68 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.h +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.h @@ -50,6 +50,8 @@ namespace SHADE //! Compute resources Handle computeResource; + //! + //! vector of resources needed by the subpass compute std::vector> resources; diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.cpp index c1d53632..e5052f59 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.cpp +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.cpp @@ -11,6 +11,9 @@ #include "Graphics/Swapchain/SHVkSwapchain.h" #include "Graphics/Images/SHVkSampler.h" #include "SHRenderGraphResource.h" +#include "Graphics/MiddleEnd/Interface/SHViewport.h" +#include "Graphics/MiddleEnd/Interface/SHRenderer.h" +#include "Graphics/MiddleEnd/GlobalData/SHPredefinedData.h" namespace SHADE { @@ -30,7 +33,7 @@ namespace SHADE */ /***************************************************************************/ - SHSubpass::SHSubpass(const std::string& name, Handle renderGraphStorage, Handle const& parent, uint32_t index, std::unordered_map const* mapping) noexcept + SHSubpass::SHSubpass(const std::string& name, Handle inViewport, Handle inRenderer, Handle renderGraphStorage, Handle const& parent, uint32_t index, std::unordered_map const* mapping) noexcept : resourceAttachmentMapping{ mapping } , parentNode{ parent } , subpassIndex{ index } @@ -41,6 +44,8 @@ namespace SHADE , name { name } , graphStorage{ renderGraphStorage } , inputImageDescriptorSets{} + , viewport {inViewport} + , renderer {inRenderer} { } @@ -71,6 +76,8 @@ namespace SHADE , inputDescriptorLayout{ rhs.inputDescriptorLayout } , inputSamplers{ rhs.inputSamplers } , name { rhs.name } + , viewport {rhs.viewport} + , renderer {rhs.renderer} { } @@ -106,6 +113,9 @@ namespace SHADE inputDescriptorLayout = rhs.inputDescriptorLayout; inputSamplers = rhs.inputSamplers; name = std::move(rhs.name); + renderer = rhs.renderer; + viewport = rhs.viewport; + return *this; } @@ -199,21 +209,33 @@ namespace SHADE inputReferences.push_back({ resourceAttachmentMapping->at(graphStorage->graphResources->at(resourceToReference).GetId().Raw), vk::ImageLayout::eGeneral }); } - void SHSubpass::Execute(Handle& commandBuffer, Handle descPool, uint32_t frameIndex) noexcept + void SHSubpass::Execute(Handle commandBuffer, Handle descPool, uint32_t frameIndex) noexcept { commandBuffer->BeginLabeledSegment(name); - // Ensure correct transforms are provided superBatch->UpdateBuffers(frameIndex, descPool); + if (viewport) + { + // set viewport and scissor + uint32_t w = static_cast(viewport->GetWidth()); + uint32_t h = static_cast(viewport->GetHeight()); + commandBuffer->SetViewportScissor(static_cast(w), static_cast(h), w, h); + } + + auto const& descMappings = SHPredefinedData::GetBatchingSystemData().descMappings; + + if (renderer) + renderer->BindDescriptorSet(commandBuffer, SH_PIPELINE_TYPE::GRAPHICS, descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::CAMERA), frameIndex); + // Draw all the batches superBatch->Draw(commandBuffer, frameIndex); // Draw all the exterior draw calls for (auto& drawCall : exteriorDrawCalls) { - drawCall(commandBuffer, frameIndex); + drawCall(commandBuffer, renderer, frameIndex); } commandBuffer->EndLabeledSegment(); } @@ -231,7 +253,7 @@ namespace SHADE } } - void SHSubpass::AddExteriorDrawCalls(std::function&, uint32_t)> const& newDrawCall) noexcept + void SHSubpass::AddExteriorDrawCalls(ExteriorDrawCallFunction const& newDrawCall) noexcept { exteriorDrawCalls.push_back(newDrawCall); } @@ -266,7 +288,7 @@ namespace SHADE } // We build a new descriptor set layout to store our images - inputDescriptorLayout = graphStorage->logicalDevice->CreateDescriptorSetLayout(SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_RESOURCE, bindings); + inputDescriptorLayout = graphStorage->logicalDevice->CreateDescriptorSetLayout(bindings); // we store a sampler if its an input attachment. if it is storage image, no need sampler, store an empty handle. for (uint32_t i = 0; i < bindings.size(); ++i) diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.h b/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.h index 69b8fd56..b5c5c8b1 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.h +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.h @@ -19,15 +19,28 @@ namespace SHADE class SHRenderGraphStorage; class SHVkShaderModule; class SHVkSampler; + class SHRenderer; + class SHViewport; class SH_API SHSubpass : public ISelfHandle { + public: + using ExteriorDrawCallFunction = std::function, Handle, uint32_t)>; + private: /*---------------------------------------------------------------------*/ /* PRIVATE MEMBER VARIABLES */ /*---------------------------------------------------------------------*/ Handle graphStorage; + //! Viewport to specify what part of the screen we want to draw on. This + //! will be used in vkCmdSetViewport/Scissor. + Handle viewport; + + //! Renderer used during the subpass execution. This dictates what matrix gets + //! passed to the shaders. + Handle renderer; + //! The index of the subpass in the render graph uint32_t subpassIndex; @@ -79,8 +92,9 @@ namespace SHADE //! after we draw everything from the batch. Because of this, these draw calls //! are always the last things drawn, so DO NOT USE THIS FUNCTIONALITY FOR ANYTHING //! COMPLEX. - std::vector&, uint32_t)>> exteriorDrawCalls; - /// For identifying subpasses + std::vector exteriorDrawCalls; + + // For identifying subpasses std::string name; @@ -88,7 +102,7 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* CTORS AND DTORS */ /*-----------------------------------------------------------------------*/ - SHSubpass(const std::string& name, Handle renderGraphStorage, Handle const& parent, uint32_t index, std::unordered_map const* mapping) noexcept; + SHSubpass(const std::string& name, Handle inViewport, Handle inRenderer, Handle renderGraphStorage, Handle const& parent, uint32_t index, std::unordered_map const* mapping) noexcept; SHSubpass(SHSubpass&& rhs) noexcept; SHSubpass& operator=(SHSubpass&& rhs) noexcept; @@ -102,10 +116,10 @@ namespace SHADE void AddGeneralDepthOutput(std::string resourceToReference) noexcept; void AddInput(std::string resourceToReference) noexcept; void AddGeneralInput (std::string resourceToReference) noexcept; - void AddExteriorDrawCalls(std::function&, uint32_t)> const& newDrawCall) noexcept; + void AddExteriorDrawCalls(ExteriorDrawCallFunction const& newDrawCall) noexcept; // Runtime functions - void Execute(Handle& commandBuffer, Handle descPool, uint32_t frameIndex) noexcept; + void Execute(Handle commandBuffer, Handle descPool, uint32_t frameIndex) noexcept; void HandleResize (void) noexcept; void BindDescriptorInputDescriptorSets (Handle cmdBuffer, uint32_t frameIndex) const noexcept; From 3bfec1e54ff0ff5d9a3fd5b75f37e7ca9a976bfe Mon Sep 17 00:00:00 2001 From: Brandon Mak Date: Wed, 28 Dec 2022 10:22:01 +0800 Subject: [PATCH 085/275] WIP will update later, afraid for BSOD again - All Shaders now take in a single projection matrix. The type of projection matrix is dependent on the SHRenderer projection type. - SHGraphicsSystem now only has a single render graph. - SHGlobalDescriptorSets now store a descriptor set for static global data and a handle to the lighting system. Functions to bind their descriptor sets are also available. - Font desc set layout is added back into SHPredefinedData because while its possible to introspect the layouts from the shaders, the layouts is required beforehand to generate the font objects - SHRenderers and SHViewport are now 2 separate entities, both passable to SHSubpass to be contained and used to set viewport/scissor and send camera matrices to shaders. - SHRenderer descriptor sets are now updated separately from the binding. They happen directly before the render graph executes. --- Assets/Shaders/DebugDrawMesh_VS.glsl | 3 +- Assets/Shaders/DebugDraw_VS.glsl | 3 +- Assets/Shaders/TestCube_Tile_VS.glsl | 3 +- Assets/Shaders/TestCube_VS.glsl | 3 +- Assets/Shaders/Text_VS.glsl | 5 +- Assets/Shaders/UI_VS.glsl | 5 +- SHADE_Engine/src/Editor/SHEditor.cpp | 7 +- .../Graphics/Commands/SHVkCommandBuffer.cpp | 2 +- .../src/Graphics/Commands/SHVkCommandBuffer.h | 2 +- .../GlobalData/SHGlobalDescriptorSets.cpp | 31 +- .../GlobalData/SHGlobalDescriptorSets.h | 24 +- .../MiddleEnd/GlobalData/SHPredefinedData.cpp | 41 +- .../MiddleEnd/Interface/SHDebugDrawSystem.cpp | 8 +- .../MiddleEnd/Interface/SHGraphicsConstants.h | 1 + .../MiddleEnd/Interface/SHGraphicsSystem.cpp | 359 ++++++++++++------ .../MiddleEnd/Interface/SHGraphicsSystem.h | 30 +- .../MiddleEnd/Interface/SHRenderer.cpp | 89 ++--- .../Graphics/MiddleEnd/Interface/SHRenderer.h | 34 +- .../MiddleEnd/Interface/SHViewport.cpp | 28 -- .../Graphics/MiddleEnd/Interface/SHViewport.h | 14 +- .../MiddleEnd/Lights/SHLightingSubSystem.cpp | 11 +- .../MiddleEnd/Lights/SHLightingSubSystem.h | 11 +- .../SHTextRenderingSubSystem.cpp | 30 +- .../TextRendering/SHTextRenderingSubSystem.h | 8 +- .../Graphics/RenderGraph/SHRenderGraph.cpp | 53 ++- .../src/Graphics/RenderGraph/SHRenderGraph.h | 16 +- .../RenderGraph/SHRenderGraphNode.cpp | 14 +- .../Graphics/RenderGraph/SHRenderGraphNode.h | 6 +- .../RenderGraph/SHRenderGraphNodeCompute.h | 2 + .../src/Graphics/RenderGraph/SHSubpass.cpp | 34 +- .../src/Graphics/RenderGraph/SHSubpass.h | 24 +- 31 files changed, 548 insertions(+), 353 deletions(-) diff --git a/Assets/Shaders/DebugDrawMesh_VS.glsl b/Assets/Shaders/DebugDrawMesh_VS.glsl index 2f035261..19c1a5b9 100644 --- a/Assets/Shaders/DebugDrawMesh_VS.glsl +++ b/Assets/Shaders/DebugDrawMesh_VS.glsl @@ -16,8 +16,7 @@ layout(set = 2, binding = 0) uniform CameraData vec4 position; mat4 vpMat; mat4 viewMat; - mat4 perspectiveMat; - mat4 orthoMat; + mat4 projMat; } cameraData; void main() diff --git a/Assets/Shaders/DebugDraw_VS.glsl b/Assets/Shaders/DebugDraw_VS.glsl index 42a48c01..ce2dd544 100644 --- a/Assets/Shaders/DebugDraw_VS.glsl +++ b/Assets/Shaders/DebugDraw_VS.glsl @@ -15,8 +15,7 @@ layout(set = 2, binding = 0) uniform CameraData vec4 position; mat4 vpMat; mat4 viewMat; - mat4 perspectiveMat; - mat4 orthoMat; + mat4 projMat; } cameraData; void main() diff --git a/Assets/Shaders/TestCube_Tile_VS.glsl b/Assets/Shaders/TestCube_Tile_VS.glsl index 31a448fe..d27805ef 100644 --- a/Assets/Shaders/TestCube_Tile_VS.glsl +++ b/Assets/Shaders/TestCube_Tile_VS.glsl @@ -39,8 +39,7 @@ layout(set = 2, binding = 0) uniform CameraData vec4 position; mat4 vpMat; mat4 viewMat; - mat4 perspectiveMat; - mat4 orthoMat; + mat4 projMat; } cameraData; layout (std430, set = 3, binding = 0) buffer MaterialProperties // For materials diff --git a/Assets/Shaders/TestCube_VS.glsl b/Assets/Shaders/TestCube_VS.glsl index 774bc580..0e055395 100644 --- a/Assets/Shaders/TestCube_VS.glsl +++ b/Assets/Shaders/TestCube_VS.glsl @@ -34,8 +34,7 @@ layout(set = 2, binding = 0) uniform CameraData vec4 position; mat4 vpMat; mat4 viewMat; - mat4 perspectiveMat; - mat4 orthoMat; + mat4 projMat; } cameraData; void main() diff --git a/Assets/Shaders/Text_VS.glsl b/Assets/Shaders/Text_VS.glsl index 3501a13d..0498ae39 100644 --- a/Assets/Shaders/Text_VS.glsl +++ b/Assets/Shaders/Text_VS.glsl @@ -30,8 +30,7 @@ layout(set = 2, binding = 0) uniform CameraData vec4 position; mat4 vpMat; mat4 viewMat; - mat4 perspectiveMat; - mat4 orthoMat; + mat4 projMat; } cameraData; // push constants @@ -96,6 +95,6 @@ void main() Out2.textColor = testPushConstant.textColor; // transform the vertex position to font space - gl_Position = cameraData.orthoMat * localModel * vec4(vertexPos, 1.0f); + gl_Position = cameraData.projMat * localModel * vec4(vertexPos, 1.0f); // gl_Position = vec4(vertexPos, 1.0f); } \ No newline at end of file diff --git a/Assets/Shaders/UI_VS.glsl b/Assets/Shaders/UI_VS.glsl index c4393b98..622cf827 100644 --- a/Assets/Shaders/UI_VS.glsl +++ b/Assets/Shaders/UI_VS.glsl @@ -34,8 +34,7 @@ layout(set = 2, binding = 0) uniform CameraData vec4 position; mat4 vpMat; mat4 viewMat; - mat4 perspectiveMat; - mat4 orthoMat; + mat4 projMat; } cameraData; void main() @@ -60,7 +59,7 @@ void main() Out.normal.rgb = normalize (Out.normal.rgb); // clip space for rendering - gl_Position = cameraData.orthoMat * worldTransform * vec4 (aVertexPos, 1.0f); + gl_Position = cameraData.projMat * worldTransform * vec4 (aVertexPos, 1.0f); gl_Position.z += 0.1f; // HAX // gl_Position = vec4 (aVertexPos, 1.0f); } \ No newline at end of file diff --git a/SHADE_Engine/src/Editor/SHEditor.cpp b/SHADE_Engine/src/Editor/SHEditor.cpp index 4f1586ac..a12a19f7 100644 --- a/SHADE_Engine/src/Editor/SHEditor.cpp +++ b/SHADE_Engine/src/Editor/SHEditor.cpp @@ -500,10 +500,7 @@ namespace SHADE imguiCommandBuffer = imguiCommandPool->RequestCommandBuffer(SH_CMD_BUFFER_TYPE::PRIMARY); //auto const& renderers = gfxSystem->GetDefaultViewport()->GetRenderers(); - auto const& renderers = gfxSystem->GetEditorViewport()->GetRenderers(); - - SHASSERT(!renderers.empty(), "No Renderers available") - auto renderGraph = renderers[SHGraphicsConstants::RenderGraphIndices::EDITOR]->GetRenderGraph(); + auto renderGraph = gfxSystem->GetRenderGraph(); auto renderPass = renderGraph->GetNode("ImGui Node")->GetRenderpass(); if(ImGui_ImplVulkan_Init(&initInfo, renderPass->GetVkRenderpass()) == false) @@ -523,7 +520,7 @@ namespace SHADE ImGui_ImplVulkan_DestroyFontUploadObjects(); - renderGraph->GetNode("ImGui Node")->GetSubpass("ImGui Draw")->AddExteriorDrawCalls([](Handle& cmd, uint32_t frameIndex) + renderGraph->GetNode("ImGui Node")->GetSubpass("ImGui Draw")->AddExteriorDrawCalls([](Handle cmd, Handle renderer, uint32_t frameIndex) { cmd->BeginLabeledSegment("ImGui Draw"); ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), cmd->GetVkCommandBuffer()); diff --git a/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp index 05fd4288..37b89883 100644 --- a/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp +++ b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp @@ -367,7 +367,7 @@ namespace SHADE } } - void SHVkCommandBuffer::BindDescriptorSet(Handle descSetGroup, SH_PIPELINE_TYPE bindPoint, uint32_t firstSet, std::span dynamicOffsets) + void SHVkCommandBuffer::BindDescriptorSet(Handle descSetGroup, SH_PIPELINE_TYPE bindPoint, uint32_t firstSet, std::span const dynamicOffsets) { uint32_t bindPointIndex = static_cast(bindPoint); vkCommandBuffer.bindDescriptorSets(SHVkUtil::GetPipelineBindPointFromType(bindPoint), bindPointData[bindPointIndex].boundPipelineLayoutHdl->GetVkPipelineLayout(), firstSet, descSetGroup->GetVkHandle(), dynamicOffsets); diff --git a/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h index fc348487..c6a17d2a 100644 --- a/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h +++ b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h @@ -125,7 +125,7 @@ namespace SHADE void BindPipeline (Handle const& pipelineHdl) noexcept; void BindVertexBuffer (uint32_t bindingPoint, Handle const& buffer, vk::DeviceSize offset) noexcept; void BindIndexBuffer (Handle const& buffer, uint32_t startingIndex) const noexcept; - void BindDescriptorSet (Handle descSetGroup, SH_PIPELINE_TYPE bindPoint, uint32_t firstSet, std::span dynamicOffsets); + void BindDescriptorSet (Handle descSetGroup, SH_PIPELINE_TYPE bindPoint, uint32_t firstSet, std::span const dynamicOffsets); // Draw Commands void DrawArrays (uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, uint32_t firstInstance) const noexcept; diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.cpp index 2d6cc9e1..09dbef51 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.cpp @@ -1,10 +1,31 @@ #include "SHpch.h" #include "SHGlobalDescriptorSets.h" +#include "Graphics/MiddleEnd/Lights/SHLightingSubSystem.h" +#include "Graphics/Commands/SHVkCommandBuffer.h" namespace SHADE { - Handle SHGlobalDescriptorSets::lightDescriptorSet; + Handle SHGlobalDescriptorSets::staticGlobalDataDescriptorSet; + Handle SHGlobalDescriptorSets::lightingSubSystem; + + //void SHGlobalDescriptorSets::BindLightingData(Handle cmdBuffer, SH_PIPELINE_TYPE pipelineType, uint32_t firstSet) noexcept + //{ + // // Bind descriptor set for light data + // cmdBuffer->BindDescriptorSet(SHGlobalDescriptorSets::GetLightDescriptorSet(), SH_PIPELINE_TYPE::COMPUTE, descMappings[SHGraphicsConstants::PredefinedDescSetLayoutTypes::LIGHTS], const std::span{ TEX_DYNAMIC_OFFSET.data(), 1 }); + //} + + void SHGlobalDescriptorSets::BindLightingData(Handle cmdBuffer, SH_PIPELINE_TYPE pipelineType, uint32_t setIndex, uint32_t frameIndex) noexcept + { + lightingSubSystem->BindDescSet(cmdBuffer, setIndex, frameIndex); + } + + void SHGlobalDescriptorSets::BindStaticGlobalData(Handle cmdBuffer, SH_PIPELINE_TYPE pipelineType, uint32_t setIndex) noexcept + { + // Bind descriptor set for static global data + static constexpr std::array TEX_DYNAMIC_OFFSET{ 0 }; + cmdBuffer->BindDescriptorSet(staticGlobalDataDescriptorSet, pipelineType, setIndex, const std::span{ TEX_DYNAMIC_OFFSET.data(), 1 }); + } /***************************************************************************/ /*! @@ -17,14 +38,14 @@ namespace SHADE */ /***************************************************************************/ - void SHGlobalDescriptorSets::SetLightDescriptorSet(Handle lightDescSet) noexcept + void SHGlobalDescriptorSets::SetLightingSubSystem(Handle system) noexcept { - lightDescriptorSet = lightDescSet; + lightingSubSystem = system; } - Handle SHGlobalDescriptorSets::GetLightDescriptorSet(void) noexcept + void SHGlobalDescriptorSets::SetStaticGlobalDataDescriptorSet(Handle staticGlobalDescSet) noexcept { - return lightDescriptorSet; + staticGlobalDataDescriptorSet = staticGlobalDescSet; } } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h index ce6c42bb..2e2dca7d 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h @@ -2,23 +2,35 @@ #include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" #include "Resource/SHHandle.h" +#include "Graphics/Pipeline/SHPipelineType.h" namespace SHADE { + class SHLightingSubSystem; + class SHVkCommandBuffer; + // This class is mainly for descriptors that are truly global, meaning they only come from 1 place and they are shared between many systems class SHGlobalDescriptorSets { private: - //! Light data descriptor set - static Handle lightDescriptorSet; + + //! Static global descriptor sets for miscellaneous data and textures + static Handle staticGlobalDataDescriptorSet; + //! Lighting sub system required to get information to bind descriptor sets for light data + static Handle lightingSubSystem; + public: + /*-----------------------------------------------------------------------*/ + /* PUBLIC MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + static void BindLightingData (Handle cmdBuffer, SH_PIPELINE_TYPE pipelineType, uint32_t setIndex, uint32_t frameIndex) noexcept; + static void BindStaticGlobalData (Handle cmdBuffer, SH_PIPELINE_TYPE pipelineType, uint32_t setIndex) noexcept; + /*-----------------------------------------------------------------------*/ /* SETTERS AND GETTERS */ /*-----------------------------------------------------------------------*/ - static void SetLightDescriptorSet (Handle lightDescSet) noexcept; - - static Handle GetLightDescriptorSet (void) noexcept; - + static void SetLightingSubSystem (Handle system) noexcept; + static void SetStaticGlobalDataDescriptorSet (Handle staticGlobalDescSet) noexcept; }; } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.cpp index ac7fac17..ac7ab982 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/GlobalData/SHPredefinedData.cpp @@ -125,33 +125,33 @@ namespace SHADE Handle materialDataPerInstanceLayout = logicalDevice->CreateDescriptorSetLayout({ materialDataBinding }); SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, materialDataPerInstanceLayout->GetVkHandle(), "[Descriptor Set Layout] Material Globals"); - //// font bitmap data (texture) - //SHVkDescriptorSetLayout::Binding fontBitmapBinding - //{ - // .Type = vk::DescriptorType::eCombinedImageSampler, - // .Stage = vk::ShaderStageFlagBits::eFragment, - // .BindPoint = SHGraphicsConstants::DescriptorSetBindings::FONT_BITMAP_DATA, - // .DescriptorCount = 1, - //}; + // font bitmap data (texture) + SHVkDescriptorSetLayout::Binding fontBitmapBinding + { + .Type = vk::DescriptorType::eCombinedImageSampler, + .Stage = vk::ShaderStageFlagBits::eFragment, + .BindPoint = SHGraphicsConstants::DescriptorSetBindings::FONT_BITMAP_DATA, + .DescriptorCount = 1, + }; - //// font data in the form of matrices - //SHVkDescriptorSetLayout::Binding fontMatrixBinding - //{ - // .Type = vk::DescriptorType::eStorageBuffer, - // .Stage = vk::ShaderStageFlagBits::eVertex, - // .BindPoint = SHGraphicsConstants::DescriptorSetBindings::FONT_MATRIX_DATA, - // .DescriptorCount = 1, - //}; + // font data in the form of matrices + SHVkDescriptorSetLayout::Binding fontMatrixBinding + { + .Type = vk::DescriptorType::eStorageBuffer, + .Stage = vk::ShaderStageFlagBits::eVertex, + .BindPoint = SHGraphicsConstants::DescriptorSetBindings::FONT_MATRIX_DATA, + .DescriptorCount = 1, + }; - //Handle fontDataDescSetLayout = logicalDevice->CreateDescriptorSetLayout({ fontBitmapBinding, fontMatrixBinding }); - //SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, fontDataDescSetLayout->GetVkHandle(), "[Descriptor Set Layout] Font Data"); + Handle fontDataDescSetLayout = logicalDevice->CreateDescriptorSetLayout({ fontBitmapBinding, fontMatrixBinding }); + SET_VK_OBJ_NAME(logicalDevice, vk::ObjectType::eDescriptorSetLayout, fontDataDescSetLayout->GetVkHandle(), "[Descriptor Set Layout] Font Data"); predefinedLayouts.push_back(staticGlobalLayout); predefinedLayouts.push_back(lightDataDescSetLayout); predefinedLayouts.push_back(cameraDataGlobalLayout); predefinedLayouts.push_back(materialDataPerInstanceLayout); - //predefinedLayouts.push_back(fontDataDescSetLayout); + predefinedLayouts.push_back(fontDataDescSetLayout); batchingSystemData.descSetLayouts = GetPredefinedDescSetLayouts ( @@ -163,7 +163,8 @@ namespace SHADE textSystemData.descSetLayouts = GetPredefinedDescSetLayouts ( SHGraphicsConstants::PredefinedDescSetLayoutTypes::STATIC_DATA | - SHGraphicsConstants::PredefinedDescSetLayoutTypes::CAMERA + SHGraphicsConstants::PredefinedDescSetLayoutTypes::CAMERA | + SHGraphicsConstants::PredefinedDescSetLayoutTypes::FONT ); } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp index d77fbeb0..b57249de 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHDebugDrawSystem.cpp @@ -99,10 +99,10 @@ namespace SHADE createMeshBatches(); // Register function for subpass - auto const& RENDERERS = gfxSystem->GetDefaultViewport()->GetRenderers(); - auto renderGraph = RENDERERS[SHGraphicsConstants::RenderGraphIndices::WORLD]->GetRenderGraph(); + //auto const& RENDERERS = gfxSystem->GetDefaultViewport()->GetRenderers(); + auto renderGraph = gfxSystem->GetRenderGraph(); auto subPass = renderGraph->GetNode("Debug Draw")->GetSubpass("Debug Draw"); - subPass->AddExteriorDrawCalls([this](Handle& cmdBuffer, uint32_t frameIndex) + subPass->AddExteriorDrawCalls([this](Handle cmdBuffer, Handle renderer, uint32_t frameIndex) { const uint32_t FRAME_IDX = gfxSystem->GetCurrentFrameIndex(); cmdBuffer->BeginLabeledSegment("SHDebugDraw (No Depth Test)"); @@ -126,7 +126,7 @@ namespace SHADE cmdBuffer->EndLabeledSegment(); }); auto subPassWithDepth = renderGraph->GetNode("Debug Draw with Depth")->GetSubpass("Debug Draw with Depth"); - subPassWithDepth->AddExteriorDrawCalls([this](Handle& cmdBuffer, uint32_t frameIndex) + subPassWithDepth->AddExteriorDrawCalls([this](Handle cmdBuffer, Handle renderer, uint32_t frameIndex) { const uint32_t FRAME_IDX = gfxSystem->GetCurrentFrameIndex(); cmdBuffer->BeginLabeledSegment("SHDebugDraw (Depth Tested)"); diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h index c80b9de5..06ffe381 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsConstants.h @@ -34,6 +34,7 @@ namespace SHADE LIGHTS = 0x02, CAMERA = 0x04, MATERIALS = 0x08, + FONT = 0x10, }; // This enum is different from the one above in that it is used to initialize a hash table to diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp index 6881d7a8..461b8783 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp @@ -44,6 +44,7 @@ of DigiPen Institute of Technology is prohibited. #include "../Meshes/SHPrimitiveGenerator.h" #include "Graphics/MiddleEnd/TextRendering/SHFreetypeInstance.h" #include "Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.h" +#include "Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h" namespace SHADE { @@ -142,7 +143,7 @@ namespace SHADE static constexpr AssetID RENDER_SC_FS = 36869006; renderToSwapchainFS = SHResourceManager::LoadOrGet(RENDER_SC_FS); } - void SHGraphicsSystem::InitSceneRenderGraph(void) noexcept + void SHGraphicsSystem::InitRenderGraph(void) noexcept { /*-----------------------------------------------------------------------*/ /* MIDDLE END SETUP @@ -158,55 +159,66 @@ namespace SHADE auto cameraSystem = SHSystemManager::GetSystem(); // Set Up Cameras - screenCamera = resourceManager.Create(); - screenCamera->SetLookAt(SHVec3(0.0f, 0.0f, -1.0f), SHVec3(0.0f, 0.0f, 1.0f), SHVec3(0.0f, 1.0f, 0.0f)); - screenCamera->SetOrthographic(static_cast(windowDims.first), static_cast(windowDims.second), 0.01f, 100.0f); + //screenCamera = resourceManager.Create(); + //screenCamera->SetLookAt(SHVec3(0.0f, 0.0f, -1.0f), SHVec3(0.0f, 0.0f, 1.0f), SHVec3(0.0f, 1.0f, 0.0f)); + //screenCamera->SetOrthographic(static_cast(windowDims.first), static_cast(windowDims.second), 0.01f, 100.0f); - worldCamera = resourceManager.Create(); - worldCamera->SetLookAt(SHVec3(0.0f, 0.0f, 0.0f), SHVec3(0.0f, 0.0f, -2.0f), SHVec3(0.0f, 1.0f, 0.0f)); - worldCamera->SetPerspective(90.0f, static_cast(windowDims.first), static_cast(windowDims.second), 0.0f, 100.0f); + //worldCamera = resourceManager.Create(); + //worldCamera->SetLookAt(SHVec3(0.0f, 0.0f, 0.0f), SHVec3(0.0f, 0.0f, -2.0f), SHVec3(0.0f, 1.0f, 0.0f)); + //worldCamera->SetPerspective(90.0f, static_cast(windowDims.first), static_cast(windowDims.second), 0.0f, 100.0f); worldCameraDirector = cameraSystem->CreateDirector(); + /*-----------------------------------------------------------------------*/ + /* PREPARE RENDERERS */ + /*-----------------------------------------------------------------------*/ + // Add world renderer to default viewport + worldRenderer = AddRenderer(SHRenderer::PROJECTION_TYPE::PERSPECTIVE); + worldRenderer->SetCameraDirector(worldCameraDirector); + + // Add screen renderer to default viewport + screenRenderer = AddRenderer(SHRenderer::PROJECTION_TYPE::ORTHOGRAPHIC); + screenRenderer->SetCameraDirector(worldCameraDirector); + // Create Default Viewport worldViewport = AddViewport(vk::Viewport(0.0f, 0.0f, static_cast(window->GetWindowSize().first), static_cast(window->GetWindowSize().second), 0.0f, 1.0f)); - // Get render graph from default viewport world renderer - worldRenderGraph = resourceManager.Create(); - std::vector> renderContextCmdPools{ swapchain->GetNumImages() }; for (uint32_t i = 0; i < renderContextCmdPools.size(); ++i) { renderContextCmdPools[i] = renderContext.GetFrameData(i).cmdPoolHdls[0]; } + // Get render graph from default viewport world renderer + renderGraph = resourceManager.Create(); + /*-----------------------------------------------------------------------*/ /* WORLD RENDER GRAPH RESOURCES */ /*-----------------------------------------------------------------------*/ // Initialize world render graph - worldRenderGraph->Init("World Render Graph", device, swapchain, &resourceManager); - worldRenderGraph->AddResource("Position", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR32G32B32A32Sfloat); - worldRenderGraph->AddResource("Normals", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR32G32B32A32Sfloat); + renderGraph->Init("World Render Graph", device, swapchain, &resourceManager, renderContextCmdPools); + renderGraph->AddResource("Position", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR32G32B32A32Sfloat); + renderGraph->AddResource("Normals", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR32G32B32A32Sfloat); //worldRenderGraph->AddResource("Tangents", { SH_ATT_DESC_TYPE_FLAGS::COLOR, SH_ATT_DESC_TYPE_FLAGS::INPUT, SH_ATT_DESC_TYPE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR32G32B32A32Sfloat); - worldRenderGraph->AddResource("Albedo", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second); - worldRenderGraph->AddResource("Depth Buffer", { SH_RENDER_GRAPH_RESOURCE_FLAGS::DEPTH_STENCIL }, windowDims.first, windowDims.second, vk::Format::eD32SfloatS8Uint); - worldRenderGraph->AddResource("Entity ID", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::SHARED }, windowDims.first, windowDims.second, vk::Format::eR32Uint, 1, vk::ImageUsageFlagBits::eTransferSrc); - worldRenderGraph->AddResource("Light Layer Indices", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR32Uint, 1, vk::ImageUsageFlagBits::eTransferSrc); - worldRenderGraph->AddResource("Scene", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE, SH_RENDER_GRAPH_RESOURCE_FLAGS::SHARED }, windowDims.first, windowDims.second); - worldRenderGraph->AddResource("SSAO", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR8Unorm); - worldRenderGraph->AddResource("SSAO Blur", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR8Unorm); + renderGraph->AddResource("Albedo", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second); + renderGraph->AddResource("Depth Buffer", { SH_RENDER_GRAPH_RESOURCE_FLAGS::DEPTH_STENCIL }, windowDims.first, windowDims.second, vk::Format::eD32SfloatS8Uint); + renderGraph->AddResource("Entity ID", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::SHARED }, windowDims.first, windowDims.second, vk::Format::eR32Uint, 1, vk::ImageUsageFlagBits::eTransferSrc); + renderGraph->AddResource("Light Layer Indices", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR32Uint, 1, vk::ImageUsageFlagBits::eTransferSrc); + renderGraph->AddResource("Scene", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE, SH_RENDER_GRAPH_RESOURCE_FLAGS::SHARED }, windowDims.first, windowDims.second); + renderGraph->AddResource("SSAO", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR8Unorm); + renderGraph->AddResource("SSAO Blur", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR, SH_RENDER_GRAPH_RESOURCE_FLAGS::INPUT, SH_RENDER_GRAPH_RESOURCE_FLAGS::STORAGE }, windowDims.first, windowDims.second, vk::Format::eR8Unorm); + renderGraph->AddResource("Present", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR_PRESENT }, windowDims.first, windowDims.second, vk::Format::eR8Unorm); /*-----------------------------------------------------------------------*/ /* MAIN NODE */ /*-----------------------------------------------------------------------*/ - auto gBufferNode = worldRenderGraph->AddNode("G-Buffer", + auto gBufferNode = renderGraph->AddNode("G-Buffer", { "Position", "Entity ID", "Light Layer Indices", "Normals", - //"Tangents", "Albedo", "Depth Buffer", "Scene", @@ -218,20 +230,21 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* G-BUFFER SUBPASS INIT */ /*-----------------------------------------------------------------------*/ - auto gBufferSubpass = gBufferNode->AddSubpass("G-Buffer Write"); + auto gBufferSubpass = gBufferNode->AddSubpass("G-Buffer Write", worldViewport, worldRenderer); gBufferSubpass->AddColorOutput("Position"); gBufferSubpass->AddColorOutput("Entity ID"); gBufferSubpass->AddColorOutput("Light Layer Indices"); gBufferSubpass->AddColorOutput("Normals"); - //gBufferSubpass->AddColorOutput("Tangents"); gBufferSubpass->AddColorOutput("Albedo"); gBufferSubpass->AddDepthOutput("Depth Buffer", SH_RENDER_GRAPH_RESOURCE_FLAGS::DEPTH_STENCIL); + /*-----------------------------------------------------------------------*/ /* SSAO PASS AND DATA INIT */ /*-----------------------------------------------------------------------*/ ssaoStorage = resourceManager.Create(); + // command buffer operation to transfer data for ssao ssaoTransferCmdBuffer = graphicsCmdPool->RequestCommandBuffer(SH_CMD_BUFFER_TYPE::PRIMARY); SET_VK_OBJ_NAME(device, vk::ObjectType::eCommandBuffer, ssaoTransferCmdBuffer->GetVkCommandBuffer(), "[Command Buffer] SSAO Pass (Graphics)"); ssaoTransferCmdBuffer->BeginRecording(); @@ -240,50 +253,80 @@ namespace SHADE ssaoTransferCmdBuffer->EndRecording(); graphicsQueue->SubmitCommandBuffer({ ssaoTransferCmdBuffer }); - // Set up Debug Draw Passes - // - Depth Tested - auto debugDrawNodeDepth = worldRenderGraph->AddNode("Debug Draw with Depth", { "Scene", "Depth Buffer" }, {"G-Buffer"}); - auto debugDrawDepthSubpass = debugDrawNodeDepth->AddSubpass("Debug Draw with Depth"); - debugDrawDepthSubpass->AddColorOutput("Scene"); - debugDrawDepthSubpass->AddDepthOutput("Depth Buffer"); - // - No Depth Test - auto debugDrawNode = worldRenderGraph->AddNode("Debug Draw", { "Scene" }, { "Debug Draw with Depth" }); - auto debugDrawSubpass = debugDrawNode->AddSubpass("Debug Draw"); - debugDrawSubpass->AddColorOutput("Scene"); + // wait for command buffer to finish executing graphicsQueue->WaitIdle(); - ssaoStorage->PrepareRotationVectorsVkData(device); + // Add the pass to generate an image with just SSAO data Handle ssaoPass = gBufferNode->AddNodeCompute("SSAO", ssaoShader, { "Position", "Normals", "SSAO" }); auto ssaoDataBuffer = ssaoStorage->GetBuffer(); ssaoPass->ModifyWriteDescBufferComputeResource(SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_NODE_COMPUTE_RESOURCE, SHSSAO::DESC_SET_BUFFER_BINDING, { &ssaoDataBuffer, 1 }, 0, ssaoStorage->GetBuffer()->GetSizeStored()); auto viewSamplerLayout = ssaoStorage->GetViewSamplerLayout(); - ssaoPass->ModifyWriteDescImageComputeResource(SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_NODE_COMPUTE_RESOURCE, SHSSAO::DESC_SET_IMAGE_BINDING, {&viewSamplerLayout, 1}); + ssaoPass->ModifyWriteDescImageComputeResource(SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_NODE_COMPUTE_RESOURCE, SHSSAO::DESC_SET_IMAGE_BINDING, { &viewSamplerLayout, 1 }); - - Handle ssaoBlurPass = gBufferNode->AddNodeCompute("SSAO Blur Step", ssaoBlurShader, {"SSAO", "SSAO Blur"}); + // Add another pass to blur SSAO + Handle ssaoBlurPass = gBufferNode->AddNodeCompute("SSAO Blur Step", ssaoBlurShader, { "SSAO", "SSAO Blur" }); /*-----------------------------------------------------------------------*/ /* DEFERRED COMPOSITE SUBPASS INIT */ /*-----------------------------------------------------------------------*/ - gBufferNode->AddNodeCompute("Deferred Composite", deferredCompositeShader, {"Position", "Normals", "Albedo", "Light Layer Indices", "SSAO Blur", "Scene"}); + gBufferNode->AddNodeCompute("Deferred Composite", deferredCompositeShader, { "Position", "Normals", "Albedo", "Light Layer Indices", "SSAO Blur", "Scene" }); + + /*-----------------------------------------------------------------------*/ + /* DEBUG DRAW PASS INIT */ + /*-----------------------------------------------------------------------*/ + // Set up Debug Draw Passes + // - Depth Tested + auto debugDrawNodeDepth = renderGraph->AddNode("Debug Draw with Depth", { "Scene", "Depth Buffer" }, {"G-Buffer"}); + auto debugDrawDepthSubpass = debugDrawNodeDepth->AddSubpass("Debug Draw with Depth", worldViewport, worldRenderer); + debugDrawDepthSubpass->AddColorOutput("Scene"); + debugDrawDepthSubpass->AddDepthOutput("Depth Buffer"); + // - No Depth Test + auto debugDrawNode = renderGraph->AddNode("Debug Draw", { "Scene" }, { "Debug Draw with Depth" }); + auto debugDrawSubpass = debugDrawNode->AddSubpass("Debug Draw", worldViewport, worldRenderer); + debugDrawSubpass->AddColorOutput("Scene"); + + /*-----------------------------------------------------------------------*/ + /* SCREEN SPACE PASS */ + /*-----------------------------------------------------------------------*/ + auto screenSpaceNode = renderGraph->AddNode("Screen Space Pass", { "Scene", "Entity ID" }, {"G-Buffer", "Debug Draw" }); + auto uiSubpass = screenSpaceNode->AddSubpass("UI", worldViewport, screenRenderer); + uiSubpass->AddColorOutput("Scene"); + uiSubpass->AddColorOutput("Entity ID"); + uiSubpass->AddExteriorDrawCalls([=](Handle cmdBuffer, Handle renderer, uint32_t frameIndex) + { + textRenderingSubSystem->Render(cmdBuffer, renderer, frameIndex); + }); + + /*-----------------------------------------------------------------------*/ + /* RENDER TO SWAPCHAIN IMAGE FOR PRESENT PASS */ + /*-----------------------------------------------------------------------*/ +#ifdef SHEDITOR { - //// Dummy Node to transition scene render graph resource - //auto dummyNode = worldRenderGraph->AddNode("Dummy Pass", { "Scene" }, { "Debug Draw" }); // no predecessors - //auto dummySubpass = dummyNode->AddSubpass("Dummy Subpass"); - //dummySubpass->AddInput("Scene"); + // Dummy Node to transition scene render graph resource + auto dummyNode = renderGraph->AddNode("Dummy Pass", { "Scene" }, { "Screen Space Pass" }); + auto dummySubpass = dummyNode->AddSubpass("Dummy Subpass", {}, {}); + dummySubpass->AddInput("Scene"); + + auto imGuiNode = renderGraph->AddNode("ImGui Node", { "Present" }, {}); + auto imGuiSubpass = imGuiNode->AddSubpass("ImGui Draw", {}, {}); + imGuiSubpass->AddColorOutput("Present"); } +#else + renderGraph->AddRenderToSwapchainNode("Scene", "Present", { "Screen Space Pass" }, { renderToSwapchainVS, renderToSwapchainFS }); +#endif + /*-----------------------------------------------------------------------*/ - /* GENERATE WORLD RENDER GRAPH */ + /* GENERATE RENDER GRAPH */ /*-----------------------------------------------------------------------*/ - // Generate world render graph - worldRenderGraph->Generate(); - + // Generate render graph + renderGraph->Generate(); +#if 0 /*-----------------------------------------------------------------------*/ /* SCREEN RENDER GRAPH */ /*-----------------------------------------------------------------------*/ @@ -317,18 +360,7 @@ namespace SHADE screenRenderGraph->Generate(); - /*-----------------------------------------------------------------------*/ - /* BIND RENDER GRAPH TO RENDERER */ - /*-----------------------------------------------------------------------*/ - // Add world renderer to default viewport - worldRenderer = worldViewport->AddRenderer(resourceManager, swapchain->GetNumImages(), renderContextCmdPools, descPool, SHPredefinedData::GetPredefinedDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS], worldRenderGraph); - worldRenderer->SetCamera(worldCamera); - worldRenderer->SetCameraDirector(worldCameraDirector); - - // Add screen renderer to default viewport - screenRenderer = worldViewport->AddRenderer(resourceManager, swapchain->GetNumImages(), renderContextCmdPools, descPool, SHPredefinedData::GetPredefinedDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS], screenRenderGraph); - screenRenderer->SetCamera(screenCamera); - screenRenderer->SetCameraDirector(worldCameraDirector); +#endif // Create debug draw pipeline debugDrawPipeline = createDebugDrawPipeline(debugDrawNode->GetRenderpass(), debugDrawSubpass, false, false, false); @@ -361,10 +393,12 @@ namespace SHADE { SHPredefinedData::Init(device); - InitSceneRenderGraph(); + InitRenderGraph(); -#ifdef SHEDITOR - InitEditorRenderGraph(); +#if 0 + #ifdef SHEDITOR + InitEditorRenderGraph(); + #endif #endif // Create Semaphore @@ -377,7 +411,7 @@ namespace SHADE void SHGraphicsSystem::InitSubsystems(void) noexcept { - mousePickSystem = resourceManager.Create(); + mousePickSubSystem = resourceManager.Create(); std::vector> cmdPools; cmdPools.reserve(swapchain->GetNumImages()); @@ -385,11 +419,11 @@ namespace SHADE cmdPools.push_back(renderContext.GetFrameData(i).cmdPoolHdls[0]); // Mouse picking system for the editor (Will still run with editor disabled) - mousePickSystem->Init(device, cmdPools, worldRenderGraph->GetRenderGraphResource("Entity ID")); + mousePickSubSystem->Init(device, cmdPools, renderGraph->GetRenderGraphResource("Entity ID")); // Register the post offscreen render to the system - postOffscreenRender = resourceManager.Create(); - postOffscreenRender->Init(device, worldRenderGraph->GetRenderGraphResource("Scene"), descPool); + postOffscreenRenderSubSystem = resourceManager.Create(); + postOffscreenRenderSubSystem->Init(device, renderGraph->GetRenderGraphResource("Scene"), descPool); lightingSubSystem = resourceManager.Create(); lightingSubSystem->Init(device, descPool); @@ -397,11 +431,11 @@ namespace SHADE textRenderingSubSystem = resourceManager.Create(); // initialize the text renderer - auto uiNode = screenRenderGraph->GetNode("Screen Space Pass"); - textRenderingSubSystem->Init(device, uiNode->GetRenderpass(), uiNode->GetSubpass("UI"), descPool, textVS, textFS, [=](Handle cmdBuffer, uint32_t frameIndex) - { - screenRenderer->BindDescSet(cmdBuffer, frameIndex); - }); + auto uiNode = renderGraph->GetNode("Screen Space Pass"); + textRenderingSubSystem->Init(device, uiNode->GetRenderpass(), uiNode->GetSubpass("UI"), descPool, textVS, textFS); + + SHGlobalDescriptorSets::SetStaticGlobalDataDescriptorSet(texLibrary.GetTextureDescriptorSetGroup()); + SHGlobalDescriptorSets::SetLightingSubSystem(lightingSubSystem); } @@ -425,41 +459,42 @@ namespace SHADE defaultMaterial = AddMaterial ( defaultVertShader, defaultFragShader, - worldRenderGraph->GetNode("G-Buffer")->GetSubpass("G-Buffer Write") + renderGraph->GetNode("G-Buffer")->GetSubpass("G-Buffer Write") ); defaultMaterial->SetProperty("data.textureIndex", defaultTexture->TextureArrayIndex); } -#ifdef SHEDITOR - void SHGraphicsSystem::InitEditorRenderGraph(void) noexcept - { - auto windowDims = window->GetWindowSize(); +#if 0 + #ifdef SHEDITOR + void SHGraphicsSystem::InitEditorRenderGraph(void) noexcept + { + auto windowDims = window->GetWindowSize(); - // Create Default Viewport - editorViewport = AddViewport(vk::Viewport(0.0f, 0.0f, static_cast(windowDims.first), static_cast(windowDims.second), 0.0f, 1.0f)); + // Create Default Viewport + editorViewport = AddViewport(vk::Viewport(0.0f, 0.0f, static_cast(windowDims.first), static_cast(windowDims.second), 0.0f, 1.0f)); - // Get render graph from viewport editor renderer - editorRenderGraph = resourceManager.Create(); + // Get render graph from viewport editor renderer + editorRenderGraph = resourceManager.Create(); - std::vector> renderContextCmdPools{ swapchain->GetNumImages() }; - for (uint32_t i = 0; i < renderContextCmdPools.size(); ++i) - renderContextCmdPools[i] = renderContext.GetFrameData(i).cmdPoolHdls[0]; + std::vector> renderContextCmdPools{ swapchain->GetNumImages() }; + for (uint32_t i = 0; i < renderContextCmdPools.size(); ++i) + renderContextCmdPools[i] = renderContext.GetFrameData(i).cmdPoolHdls[0]; - editorRenderGraph->Init("Editor Render Graph", device, swapchain, &resourceManager); - editorRenderGraph->AddResource("Present", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR_PRESENT }, windowDims.first, windowDims.second); + editorRenderGraph->Init("Editor Render Graph", device, swapchain, &resourceManager); + editorRenderGraph->AddResource("Present", { SH_RENDER_GRAPH_RESOURCE_FLAGS::COLOR_PRESENT }, windowDims.first, windowDims.second); - auto imguiNode = editorRenderGraph->AddNode("ImGui Node", { "Present"}, {}); - auto imguiSubpass = imguiNode->AddSubpass("ImGui Draw"); - imguiSubpass->AddColorOutput("Present"); + auto imguiNode = editorRenderGraph->AddNode("ImGui Node", { "Present"}, {}); + auto imguiSubpass = imguiNode->AddSubpass("ImGui Draw"); + imguiSubpass->AddColorOutput("Present"); - // Generate world render graph - editorRenderGraph->Generate(); + // Generate world render graph + editorRenderGraph->Generate(); - // Add world renderer to default viewport - editorRenderer = editorViewport->AddRenderer(resourceManager, swapchain->GetNumImages(), renderContextCmdPools, descPool, SHPredefinedData::GetPredefinedDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS], editorRenderGraph); - editorRenderer->SetCamera(worldCamera); - } + // Add world renderer to default viewport + editorRenderer = AddRenderer(SHRenderer::PROJECTION_TYPE::PERSPECTIVE); + } + #endif #endif /*---------------------------------------------------------------------------------*/ @@ -547,6 +582,41 @@ namespace SHADE textRenderingSubSystem->Run(frameIndex); + + for (auto renderer : renderers) + { +#ifdef SHEDITOR + if (renderer == worldRenderer) + { + auto editorSystem = SHSystemManager::GetSystem(); + if (editorSystem->editorState != SHEditor::State::PLAY) + worldRenderer->UpdateDataManual(frameIndex, cameraSystem->GetEditorCamera()->GetViewMatrix(), cameraSystem->GetEditorCamera()->GetProjMatrix()); + else + renderer->UpdateData(frameIndex); + } + else + renderer->UpdateData(frameIndex); +#else + renderers[renIndex]->UpdateDataAndBind(frameIndex); +#endif + } + + renderGraph->Begin(frameIndex); + auto cmdBuffer = renderGraph->GetCommandBuffer(frameIndex); + + // Bind all the buffers required for meshes + for (auto& [buffer, bindingPoint] : MESH_DATA) + { + if (buffer->GetUsageBits() & vk::BufferUsageFlagBits::eVertexBuffer) + cmdBuffer->BindVertexBuffer(bindingPoint, buffer, 0); + else if (buffer->GetUsageBits() & vk::BufferUsageFlagBits::eIndexBuffer) + cmdBuffer->BindIndexBuffer(buffer, 0); + } + + renderGraph->Execute(frameIndex, descPool); + renderGraph->End(frameIndex); + +#if 0 // For every viewport for (int vpIndex = 0; vpIndex < static_cast(viewports.size()); ++vpIndex) { @@ -564,10 +634,10 @@ namespace SHADE // Begin recording the command buffer currentCmdBuffer->BeginRecording(); - // set viewport and scissor - uint32_t w = static_cast(viewports[vpIndex]->GetWidth()); - uint32_t h = static_cast(viewports[vpIndex]->GetHeight()); - currentCmdBuffer->SetViewportScissor (static_cast(w), static_cast(h), w, h); + //// set viewport and scissor + //uint32_t w = static_cast(viewports[vpIndex]->GetWidth()); + //uint32_t h = static_cast(viewports[vpIndex]->GetHeight()); + //currentCmdBuffer->SetViewportScissor (static_cast(w), static_cast(h), w, h); // Force set the pipeline layout currentCmdBuffer->ForceSetPipelineLayout(SHPredefinedData::GetDummyPipelineLayout(), SH_PIPELINE_TYPE::GRAPHICS); @@ -601,20 +671,20 @@ namespace SHADE // bind camera data //renderers[renIndex]->UpdateDataAndBind(currentCmdBuffer, frameIndex); -#ifdef SHEDITOR - if (renderers[renIndex] == worldRenderer) - { - auto editorSystem = SHSystemManager::GetSystem(); - if (editorSystem->editorState != SHEditor::State::PLAY) - worldRenderer->UpdateDataAndBind(currentCmdBuffer, frameIndex, cameraSystem->GetEditorCamera()->GetViewMatrix(), cameraSystem->GetEditorCamera()->GetProjMatrix(), cameraSystem->GetEditorCamera()->GetOrthoMatrix()); - else - renderers[renIndex]->UpdateDataAndBind(currentCmdBuffer, frameIndex); - } - else - renderers[renIndex]->UpdateDataAndBind(currentCmdBuffer, frameIndex); -#else - renderers[renIndex]->UpdateDataAndBind(currentCmdBuffer, frameIndex); -#endif +//#ifdef SHEDITOR +// if (renderers[renIndex] == worldRenderer) +// { +// auto editorSystem = SHSystemManager::GetSystem(); +// if (editorSystem->editorState != SHEditor::State::PLAY) +// worldRenderer->UpdateDataManual(currentCmdBuffer, frameIndex, cameraSystem->GetEditorCamera()->GetViewMatrix(), cameraSystem->GetEditorCamera()->GetProjMatrix(), cameraSystem->GetEditorCamera()->GetOrthoMatrix()); +// else +// renderers[renIndex]->UpdateDataAndBind(currentCmdBuffer, frameIndex); +// } +// else +// renderers[renIndex]->UpdateDataAndBind(currentCmdBuffer, frameIndex); +//#else +// renderers[renIndex]->UpdateDataAndBind(currentCmdBuffer, frameIndex); +//#endif // Draw the scene renderers[renIndex]->Draw(frameIndex, descPool); @@ -638,8 +708,10 @@ namespace SHADE semIndex = !semIndex; } } +#endif } + /***************************************************************************/ /*! @@ -677,12 +749,8 @@ namespace SHADE // #BackEndTest: For for the fence initialized by queue submit renderContext.WaitForFence(); - // Finalise all batches - for (auto vp : viewports) - for (auto renderer : vp->GetRenderers()) - { - renderer->GetRenderGraph()->FinaliseBatch(renderContext.GetCurrentFrame(), descPool); - } + // finalize batches for render graph + renderGraph->FinaliseBatch(renderContext.GetCurrentFrame(), descPool); // #BackEndTest: Acquire the next image in the swapchain available renderContext.AcquireNextIamge(); @@ -723,7 +791,7 @@ namespace SHADE const uint32_t CURR_FRAME_IDX = renderContext.GetCurrentFrame(); auto& currFrameData = renderContext.GetCurrentFrameData(); - mousePickSystem->Run(graphicsQueue, CURR_FRAME_IDX); + mousePickSubSystem->Run(graphicsQueue, CURR_FRAME_IDX); // #BackEndTest: queues an image for presentation if (auto result = graphicsQueue->Present(swapchain, { currFrameData.semRenderFinishHdl }, CURR_FRAME_IDX); result != vk::Result::eSuccess) @@ -763,6 +831,40 @@ namespace SHADE viewports.erase(iter); } + /*---------------------------------------------------------------------------------*/ + /* Renderer Registration Functions */ + /*---------------------------------------------------------------------------------*/ + Handle SHGraphicsSystem::AddRenderer(SHRenderer::PROJECTION_TYPE projectionType) + { + std::vector> renderContextCmdPools{ swapchain->GetNumImages() }; + for (uint32_t i = 0; i < renderContextCmdPools.size(); ++i) + { + renderContextCmdPools[i] = renderContext.GetFrameData(i).cmdPoolHdls[0]; + } + + // Create the renderer + auto renderer = resourceManager.Create(device, swapchain->GetNumImages(), renderContextCmdPools, descPool); + + // Store + renderers.emplace_back(renderer); + + // Return + return renderer; + } + void SHGraphicsSystem::RemoveRenderer(Handle renderer) + { + auto iter = std::find(renderers.begin(), renderers.end(), renderer); + if (iter == renderers.end()) + { + SHLOG_WARNING("Attempted to remove a Renderer that does not belong to a viewport!"); + return; + } + + // Remove it + iter->Free(); + renderers.erase(iter); + } + Handle SHGraphicsSystem::AddMaterial(Handle vertShader, Handle fragShader, Handle subpass) { // Retrieve pipeline from pipeline storage or create if unavailable @@ -1058,7 +1160,7 @@ namespace SHADE renderContext.HandleResize(); - worldRenderGraph->HandleResize(resizeWidth, resizeHeight); + renderGraph->HandleResize(resizeWidth, resizeHeight); #ifdef SHEDITOR @@ -1067,13 +1169,13 @@ namespace SHADE //editorViewport->SetWidth(windowDims.first); //editorViewport->SetHeight(windowDims.second); - editorRenderGraph->HandleResize(windowDims.first, windowDims.second); + //editorRenderGraph->HandleResize(windowDims.first, windowDims.second); #endif - screenRenderGraph->HandleResize(resizeWidth, resizeHeight); + //screenRenderGraph->HandleResize(resizeWidth, resizeHeight); - mousePickSystem->HandleResize(); - postOffscreenRender->HandleResize(); + mousePickSubSystem->HandleResize(); + postOffscreenRenderSubSystem->HandleResize(); worldViewport->SetWidth(static_cast(resizeWidth)); worldViewport->SetHeight(static_cast(resizeHeight)); @@ -1102,9 +1204,14 @@ namespace SHADE } + Handle SHGraphicsSystem::GetRenderGraph(void) const noexcept + { + return renderGraph; + } + Handle SHGraphicsSystem::GetPrimaryRenderpass() const noexcept { - return worldRenderGraph->GetNode(G_BUFFER_RENDER_GRAPH_NODE_NAME.data()); + return renderGraph->GetNode(G_BUFFER_RENDER_GRAPH_NODE_NAME.data()); } Handle SHGraphicsSystem::GetDebugDrawPipeline(DebugDrawPipelineType type) const noexcept @@ -1137,7 +1244,7 @@ namespace SHADE device, SHPipelineLayoutParams { .shaderModules = { (instanced ? debugMeshVertShader : debugVertShader) , debugFragShader }, - .globalDescSetLayouts = SHPredefinedData::GetPredefinedDescSetLayouts(SHGraphicsConstants::PredefinedDescSetLayoutTypes::CAMERA) + .predefinedDescSetLayouts = SHPredefinedData::GetPredefinedDescSetLayouts(SHGraphicsConstants::PredefinedDescSetLayoutTypes::CAMERA) } ); auto pipeline = resourceManager.Create(device, pipelineLayout, nullptr, renderPass, subpass); diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h index 75b48c9b..0aa83f21 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.h @@ -34,6 +34,7 @@ of DigiPen Institute of Technology is prohibited. #include "Graphics/MiddleEnd/PostProcessing/SHSSAO.h" #include "Camera/SHCameraDirector.h" #include "Graphics/MiddleEnd/TextRendering/SHFontLibrary.h" +#include "Graphics/MiddleEnd/Interface/SHRenderer.h" namespace SHADE { @@ -49,7 +50,6 @@ namespace SHADE class SHVkImage; class SHVkFramebuffer; class SHVkCommandBuffer; - class SHRenderer; class SHViewport; class SHCamera; class SHVkShaderModule; @@ -99,7 +99,7 @@ namespace SHADE { private: void InitBoilerplate (void) noexcept; - void InitSceneRenderGraph (void) noexcept; + void InitRenderGraph (void) noexcept; void InitMiddleEnd (void) noexcept; void InitSubsystems (void) noexcept; void InitBuiltInResources (void); @@ -171,6 +171,13 @@ namespace SHADE Handle AddViewport(const vk::Viewport& viewport); void RemoveViewport(Handle viewport); + /*-----------------------------------------------------------------------------*/ + /* Renderers Registration Functions */ + /*-----------------------------------------------------------------------------*/ + Handle AddRenderer(SHRenderer::PROJECTION_TYPE projectionType); + void RemoveRenderer(Handle renderer); + + /*-----------------------------------------------------------------------------*/ /* Material Functions */ /*-----------------------------------------------------------------------------*/ @@ -382,8 +389,9 @@ namespace SHADE #ifdef SHEDITOR Handle GetEditorViewport () const {return editorViewport;}; #endif - Handle GetMousePickSystem(void) const noexcept {return mousePickSystem;}; - Handle GetPostOffscreenRenderSystem(void) const noexcept {return postOffscreenRender;}; + Handle GetMousePickSystem(void) const noexcept {return mousePickSubSystem;}; + Handle GetPostOffscreenRenderSystem(void) const noexcept {return postOffscreenRenderSubSystem;}; + Handle GetRenderGraph (void) const noexcept; Handle GetPrimaryRenderpass() const noexcept; Handle GetDebugDrawPipeline(DebugDrawPipelineType type) const noexcept; uint32_t GetCurrentFrameIndex(void) const noexcept { return renderContext.GetCurrentFrame(); } @@ -441,10 +449,8 @@ namespace SHADE // Renderers Handle worldRenderer; Handle screenRenderer; + std::vector> renderers; - // Temp Cameras - Handle worldCamera; - Handle screenCamera; DirectorHandle worldCameraDirector; @@ -483,15 +489,15 @@ namespace SHADE std::array, MAX_PRIMITIVE_TYPES> primitiveMeshes; // Render Graphs - Handle worldRenderGraph; - Handle screenRenderGraph; + Handle renderGraph; + //Handle screenRenderGraph; #ifdef SHEDITOR - Handle editorRenderGraph; + //Handle editorRenderGraph; #endif // Sub systems - Handle mousePickSystem; - Handle postOffscreenRender; + Handle mousePickSubSystem; + Handle postOffscreenRenderSubSystem; Handle lightingSubSystem; Handle textRenderingSubSystem; Handle ssaoStorage; diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.cpp index 1136a2c9..be9f0482 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.cpp @@ -14,7 +14,6 @@ of DigiPen Institute of Technology is prohibited. #include "SHRenderer.h" #include "Graphics/Devices/SHVkLogicalDevice.h" -#include "SHViewport.h" #include "Graphics/Buffers/SHVkBuffer.h" #include "Graphics/Framebuffer/SHVkFramebuffer.h" #include "SHMaterial.h" @@ -22,22 +21,22 @@ of DigiPen Institute of Technology is prohibited. #include "Graphics/Descriptors/SHVkDescriptorSetGroup.h" #include "Graphics/Buffers/SHVkBuffer.h" #include "Camera/SHCameraDirector.h" +#include "Graphics/MiddleEnd/GlobalData/SHPredefinedData.h" namespace SHADE { /*-----------------------------------------------------------------------------------*/ /* Constructor/Destructors */ /*-----------------------------------------------------------------------------------*/ - SHRenderer::SHRenderer(Handle logicalDevice, uint32_t numFrames, std::vector>& cmdPools, Handle descriptorPool, Handle cameraDescLayout, Handle viewport, Handle renderGraph) - : viewport { viewport } - , renderGraph { renderGraph } + SHRenderer::SHRenderer(Handle logicalDevice, uint32_t numFrames, Handle descriptorPool, PROJECTION_TYPE type) + : projectionType{type} { - commandBuffers.resize(static_cast(numFrames)); + //commandBuffers.resize(static_cast(numFrames)); - for (uint32_t i = 0; i < commandBuffers.size(); ++i) - commandBuffers[i] = cmdPools[i]->RequestCommandBuffer(SH_CMD_BUFFER_TYPE::PRIMARY); + //for (uint32_t i = 0; i < commandBuffers.size(); ++i) + // commandBuffers[i] = cmdPools[i]->RequestCommandBuffer(SH_CMD_BUFFER_TYPE::PRIMARY); - cameraDescriptorSet = descriptorPool->Allocate({ cameraDescLayout }, { 1 }); + cameraDescriptorSet = descriptorPool->Allocate(SHPredefinedData::GetPredefinedDescSetLayouts(SHGraphicsConstants::PredefinedDescSetLayoutTypes::CAMERA), { 1 }); #ifdef _DEBUG const auto& CAM_DESC_SETS = cameraDescriptorSet->GetVkHandle(); @@ -61,18 +60,6 @@ namespace SHADE SHRenderer::~SHRenderer(void) { - //for (auto& cmdBuffer : commandBuffers) - //{ - // cmdBuffer.Free(); - //} - } - - /*-----------------------------------------------------------------------------------*/ - /* Camera Registration */ - /*-----------------------------------------------------------------------------------*/ - void SHRenderer::SetCamera(Handle _camera) - { - camera = _camera; } void SHRenderer::SetCameraDirector(Handle director) noexcept @@ -80,63 +67,55 @@ namespace SHADE cameraDirector = director; } - /*-----------------------------------------------------------------------------------*/ - /* Drawing Functions */ - /*-----------------------------------------------------------------------------------*/ - void SHRenderer::Draw(uint32_t frameIndex, Handle descPool) noexcept + void SHRenderer::UpdateData(uint32_t frameIndex) noexcept { - renderGraph->Execute(frameIndex, commandBuffers[frameIndex], descPool); - } - - void SHRenderer::UpdateDataAndBind(Handle cmdBuffer, uint32_t frameIndex) noexcept - { - if (camera && cameraDirector) + if (cameraDirector) { - //UpdateDataAndBind(cmdBuffer, frameIndex, SHMatrix::Transpose(cameraDirector->GetVPMatrix())); - UpdateDataAndBind(cmdBuffer, frameIndex, cameraDirector->GetViewMatrix(), cameraDirector->GetProjMatrix(), cameraDirector->GetOrthoMatrix()); + switch (projectionType) + { + case PROJECTION_TYPE::DEFAULT: + UpdateDataManual(frameIndex, cameraDirector->GetViewMatrix(), cameraDirector->GetProjMatrix()); + break; + case PROJECTION_TYPE::PERSPECTIVE: + UpdateDataManual(frameIndex, cameraDirector->GetViewMatrix(), cameraDirector->GetPerspectiveMatrix()); + break; + case PROJECTION_TYPE::ORTHOGRAPHIC: + UpdateDataManual(frameIndex, cameraDirector->GetViewMatrix(), cameraDirector->GetOrthoMatrix()); + break; + default: + UpdateDataManual(frameIndex, cameraDirector->GetViewMatrix(), cameraDirector->GetProjMatrix()); + break; + } } } - void SHRenderer::UpdateDataAndBind(Handle cmdBuffer, uint32_t frameIndex, SHMatrix const& viewMatrix, SHMatrix const& projMatrix, SHMatrix const& orthoMatrix) noexcept + void SHRenderer::UpdateDataManual(uint32_t frameIndex, SHMatrix const& viewMatrix, SHMatrix const& projMatrix) noexcept { - SetViewProjectionMatrix(viewMatrix, projMatrix, orthoMatrix); + SetViewProjectionMatrix(viewMatrix, projMatrix); - //cpuCameraData.viewProjectionMatrix = camera->GetViewProjectionMatrix(); cameraBuffer->WriteToMemory(&cpuCameraData, sizeof(SHShaderCameraData), 0, cameraDataAlignedSize * frameIndex); - BindDescSet(cmdBuffer, frameIndex); + //BindDescSet(cmdBuffer, frameIndex); } - void SHRenderer::BindDescSet(Handle cmdBuffer, uint32_t frameIndex) noexcept + void SHRenderer::BindDescriptorSet(Handle cmdBuffer, SH_PIPELINE_TYPE pipelineType, uint32_t setIndex, uint32_t frameIndex) noexcept { std::array dynamicOffsets{ frameIndex * cameraDataAlignedSize }; - cmdBuffer->BindDescriptorSet(cameraDescriptorSet, SH_PIPELINE_TYPE::GRAPHICS, SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS, std::span{ dynamicOffsets.data(), 1 }); - cmdBuffer->BindDescriptorSet(cameraDescriptorSet, SH_PIPELINE_TYPE::COMPUTE, SHGraphicsConstants::DescriptorSetIndex::HIGH_FREQUENCY_GLOBALS, std::span{ dynamicOffsets.data(), 1 }); + cmdBuffer->BindDescriptorSet(cameraDescriptorSet, pipelineType, setIndex, std::span{ dynamicOffsets.data(), 1 }); } - void SHRenderer::UpdateCameraDataToBuffer(void) noexcept + void SHRenderer::SetViewProjectionMatrix(SHMatrix const& viewMatrix, SHMatrix const& projMatrix) noexcept { - } - - void SHRenderer::SetViewProjectionMatrix(SHMatrix const& viewMatrix, SHMatrix const& projMatrix, SHMatrix const& orthoMatrix) noexcept - { - //cpuCameraData.viewProjectionMatrix = camera->GetViewMatrix() * camera->GetProjectionMatrix(); cpuCameraData.viewProjectionMatrix = SHMatrix::Transpose(projMatrix * viewMatrix); cpuCameraData.viewMatrix = SHMatrix::Transpose(viewMatrix); cpuCameraData.projectionMatrix = SHMatrix::Transpose(projMatrix); - cpuCameraData.orthoMatrix = SHMatrix::Transpose (orthoMatrix); } - Handle SHRenderer::GetRenderGraph(void) const noexcept - { - return renderGraph; - } - - Handle SHRenderer::GetCommandBuffer(uint32_t frameIndex) const noexcept - { - return commandBuffers[frameIndex]; - } + //Handle SHRenderer::GetCommandBuffer(uint32_t frameIndex) const noexcept + //{ + // return commandBuffers[frameIndex]; + //} Handle SHRenderer::GetCameraDirector(void) const noexcept { diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.h index 4bd205be..c93050d7 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHRenderer.h @@ -32,7 +32,6 @@ namespace SHADE class SHVkFramebuffer; class SHMaterial; class SHVkLogicalDevice; - class SHViewport; class SHVkImageView; class SHVkCommandBuffer; class SHCamera; @@ -48,7 +47,6 @@ namespace SHADE SHMatrix viewProjectionMatrix; SHMatrix viewMatrix; SHMatrix projectionMatrix; - SHMatrix orthoMatrix; }; /*---------------------------------------------------------------------------------*/ @@ -64,35 +62,36 @@ namespace SHADE /***********************************************************************************/ class SHRenderer { - public: + enum class PROJECTION_TYPE + { + DEFAULT, + PERSPECTIVE, + ORTHOGRAPHIC + }; + /*-----------------------------------------------------------------------------*/ /* Constructor/Destructors */ /*-----------------------------------------------------------------------------*/ - SHRenderer(Handle logicalDevice, uint32_t numFrames, std::vector>& cmdPools, Handle descriptorPool, Handle cameraDescLayout, Handle viewport, Handle renderGraph); + SHRenderer(Handle logicalDevice, uint32_t numFrames, Handle descriptorPool, PROJECTION_TYPE type); ~SHRenderer(void); /*-----------------------------------------------------------------------------*/ /* Camera Registration */ /*-----------------------------------------------------------------------------*/ - void SetCamera(Handle _camera); void SetCameraDirector (Handle director) noexcept; /*-----------------------------------------------------------------------------*/ /* Drawing Functions */ /*-----------------------------------------------------------------------------*/ - void Draw(uint32_t frameIndex, Handle descPool) noexcept; - void UpdateDataAndBind(Handle cmdBuffer, uint32_t frameIndex) noexcept; - void UpdateDataAndBind(Handle cmdBuffer, uint32_t frameIndex, SHMatrix const& viewMatrix, SHMatrix const& projMatrix, SHMatrix const& orthoMatrix) noexcept; - void BindDescSet (Handle cmdBuffer, uint32_t frameIndex) noexcept; - void UpdateCameraDataToBuffer (void) noexcept; - void SetViewProjectionMatrix (SHMatrix const& viewMatrix, SHMatrix const& projMatrix, SHMatrix const& orthoMatrix) noexcept; + void UpdateData(uint32_t frameIndex) noexcept; + void UpdateDataManual(uint32_t frameIndex, SHMatrix const& viewMatrix, SHMatrix const& projMatrix) noexcept; + void BindDescriptorSet (Handle cmdBuffer, SH_PIPELINE_TYPE pipelineType, uint32_t setIndex, uint32_t frameIndex) noexcept; + void SetViewProjectionMatrix (SHMatrix const& viewMatrix, SHMatrix const& projMatrix) noexcept; /*-----------------------------------------------------------------------------*/ /* Setters and Getters */ /*-----------------------------------------------------------------------------*/ - Handle GetRenderGraph (void) const noexcept; - Handle GetCommandBuffer(uint32_t frameIndex) const noexcept; Handle GetCameraDirector (void) const noexcept; private: @@ -102,9 +101,6 @@ namespace SHADE //! Vulkan UBOs need to be aligned, this is pad SHShaderCameraData struct uint32_t cameraDataAlignedSize; - Handle viewport; - Handle camera; - Handle renderGraph; Handle cameraDescriptorSet; Handle cameraBuffer; @@ -114,10 +110,10 @@ namespace SHADE // GPU. SHShaderCameraData cpuCameraData; - //! Command buffers for the render graph - std::vector> commandBuffers; - + ////! Command buffers for the render graph + //std::vector> commandBuffers; + PROJECTION_TYPE projectionType; }; } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.cpp index 7bd0049f..078261a5 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.cpp @@ -46,34 +46,6 @@ namespace SHADE ); } - /*---------------------------------------------------------------------------------*/ - /* Renderer Registration Functions */ - /*---------------------------------------------------------------------------------*/ - Handle SHViewport::AddRenderer(SHResourceHub& resourceManager, uint32_t numFrames, std::vector>& cmdPools, Handle descriptorPool, Handle cameraDescLayout, Handle renderGraph) - { - // Create the renderer - auto renderer = resourceManager.Create(device, numFrames, cmdPools, descriptorPool, cameraDescLayout, GetHandle(), renderGraph); - - // Store - renderers.emplace_back(renderer); - - // Return - return renderer; - } - void SHViewport::RemoveRenderer(Handle renderer) - { - auto iter = std::find(renderers.begin(), renderers.end(), renderer); - if (iter == renderers.end()) - { - SHLOG_WARNING("Attempted to remove a Renderer that does not belong to a viewport!"); - return; - } - - // Remove it - iter->Free(); - renderers.erase(iter); - } - void SHViewport::SetWidth(float w) noexcept { viewport.width = w; diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.h b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.h index 26c0a6bd..60bd6e95 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHViewport.h @@ -56,11 +56,11 @@ namespace SHADE /*-----------------------------------------------------------------------------*/ void SetUp(Handle commandBuffer); - /*-----------------------------------------------------------------------------*/ - /* Renderers Registration Functions */ - /*-----------------------------------------------------------------------------*/ - Handle AddRenderer(SHResourceHub& resourceManager, uint32_t numFrames, std::vector>& cmdPools, Handle descriptorPool, Handle cameraDescLayout, Handle renderGraph); - void RemoveRenderer(Handle renderer); + ///*-----------------------------------------------------------------------------*/ + ///* Renderers Registration Functions */ + ///*-----------------------------------------------------------------------------*/ + //Handle AddRenderer(SHResourceHub& resourceManager, uint32_t numFrames, std::vector>& cmdPools, Handle descriptorPool, Handle cameraDescLayout, Handle renderGraph); + //void RemoveRenderer(Handle renderer); /*-----------------------------------------------------------------------------*/ /* Setters */ @@ -79,7 +79,7 @@ namespace SHADE float GetHeight() const { return viewport.height; } float GetMinDepth() const { return viewport.minDepth; } float GetMaxDepth() const { return viewport.maxDepth; } - std::vector>& GetRenderers() { return renderers; } + //std::vector>& GetRenderers() { return renderers; } private: @@ -88,7 +88,7 @@ namespace SHADE /*-----------------------------------------------------------------------------*/ Handle device; vk::Viewport viewport; - std::vector> renderers; + //std::vector> renderers; }; } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.cpp index 448c3bed..abbf88c3 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.cpp @@ -385,7 +385,7 @@ namespace SHADE std::fill (variableSizes.begin(), variableSizes.end(), 1); // Create the descriptor set - lightingDataDescSet = descPool->Allocate({ SHPredefinedData::GetPredefinedDescSetLayouts()[SHGraphicsConstants::DescriptorSetIndex::DYNAMIC_GLOBALS] }, variableSizes); + lightingDataDescSet = descPool->Allocate({ SHPredefinedData::GetPredefinedDescSetLayouts(SHGraphicsConstants::PredefinedDescSetLayoutTypes::LIGHTS) }, variableSizes); #ifdef _DEBUG const auto& CAM_DESC_SETS = lightingDataDescSet->GetVkHandle(); for (int i = 0; i < static_cast(CAM_DESC_SETS.size()); ++i) @@ -517,11 +517,16 @@ namespace SHADE } - void SHLightingSubSystem::BindDescSet(Handle cmdBuffer, uint32_t frameIndex) noexcept + void SHLightingSubSystem::BindDescSet(Handle cmdBuffer, uint32_t setIndex, uint32_t frameIndex) noexcept { //Bind descriptor set(We bind at an offset because the buffer holds NUM_FRAME_BUFFERS sets of data). - cmdBuffer->BindDescriptorSet(lightingDataDescSet, SH_PIPELINE_TYPE::COMPUTE, SHGraphicsConstants::DescriptorSetIndex::DYNAMIC_GLOBALS, { dynamicOffsets[frameIndex] }); + cmdBuffer->BindDescriptorSet(lightingDataDescSet, SH_PIPELINE_TYPE::COMPUTE, setIndex, { dynamicOffsets[frameIndex] }); } + Handle SHLightingSubSystem::GetLightDataDescriptorSet(void) const noexcept + { + return lightingDataDescSet; + } + } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.h index ae6caead..fa103136 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Lights/SHLightingSubSystem.h @@ -4,6 +4,7 @@ #include "Math/Vector/SHVec3.h" #include "Math/Vector/SHVec4.h" #include "SHLightData.h" +#include #include "Graphics/MiddleEnd/Interface/SHGraphicsConstants.h" namespace SHADE @@ -57,8 +58,11 @@ namespace SHADE class SH_API SHLightingSubSystem { - private: + public: + using DynamicOffsetArray = std::array, static_cast(SHGraphicsConstants::NUM_FRAME_BUFFERS)>; + + private: class PerTypeData { private: @@ -130,7 +134,7 @@ namespace SHADE std::array(SH_LIGHT_TYPE::NUM_TYPES)> perTypeData; //! Container to store dynamic offsets for binding descriptor sets - std::array, static_cast(SHGraphicsConstants::NUM_FRAME_BUFFERS)> dynamicOffsets; + DynamicOffsetArray dynamicOffsets; //! holds the data that represents how many lights are in the scene std::array(SH_LIGHT_TYPE::NUM_TYPES)> lightCountsData; @@ -162,7 +166,8 @@ namespace SHADE void Run (SHMatrix const& viewMat, uint32_t frameIndex) noexcept; void Exit (void) noexcept; - void BindDescSet (Handle cmdBuffer, uint32_t frameIndex) noexcept; + void BindDescSet (Handle cmdBuffer, uint32_t setIndex, uint32_t frameIndex) noexcept; + Handle GetLightDataDescriptorSet (void) const noexcept; }; } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp index f117b26c..f9afa48f 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp @@ -11,6 +11,8 @@ #include "Graphics/SHVkUtil.h" #include "Graphics/RenderGraph/SHSubpass.h" #include "Math/Transform/SHTransformComponent.h" +#include "Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h" +#include "Graphics/MiddleEnd/Interface/SHRenderer.h" namespace SHADE { @@ -91,12 +93,10 @@ namespace SHADE } - void SHTextRenderingSubSystem::Init(Handle device, Handle compatibleRenderpass, Handle subpass, Handle descPool, Handle textVS, Handle textFS, std::function, uint32_t)> const& bindFunction) noexcept + void SHTextRenderingSubSystem::Init(Handle device, Handle compatibleRenderpass, Handle subpass, Handle descPool, Handle textVS, Handle textFS) noexcept { SHComponentManager::CreateComponentSparseSet(); - cameraDescSetBind = bindFunction; - logicalDevice = device; // prepare pipeline layout params @@ -174,9 +174,14 @@ namespace SHADE } } - void SHTextRenderingSubSystem::Render(Handle cmdBuffer, uint32_t frameIndex) noexcept + void SHTextRenderingSubSystem::Render(Handle cmdBuffer, Handle renderer, uint32_t frameIndex) noexcept { auto& textRendererComps = SHComponentManager::GetDense(); + auto const& mappings = SHPredefinedData::GetTextSystemData().descMappings; + uint32_t fontSetIndex = mappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::FONT); + uint32_t staticGlobalSetIndex = mappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::STATIC_DATA); + uint32_t cameraSetIndex = mappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::CAMERA); + for (auto& comp : textRendererComps) { auto* transform = SHComponentManager::GetComponent(comp.GetEID()); @@ -187,16 +192,19 @@ namespace SHADE // bind the pipeline cmdBuffer->BindPipeline(pipeline); + // Bind global data + SHGlobalDescriptorSets::BindStaticGlobalData(cmdBuffer, SH_PIPELINE_TYPE::GRAPHICS, staticGlobalSetIndex); + + // Bind camera data + renderer->BindDescriptorSet(cmdBuffer, SH_PIPELINE_TYPE::GRAPHICS, cameraSetIndex, frameIndex); + + // bind descriptors for font (matrices) + cmdBuffer->BindDescriptorSet(fontHandle->GetDescriptorSet(), SH_PIPELINE_TYPE::GRAPHICS, fontSetIndex, {}); + // bind VBO (position and indices) cmdBuffer->BindVertexBuffer(SHGraphicsConstants::VertexBufferBindings::CALCULATED_GLYPH_POSITION, comp.charPositionDataBuffer, 0); cmdBuffer->BindVertexBuffer(SHGraphicsConstants::VertexBufferBindings::GLYPH_INDEX, comp.indexingDataBuffer, 0); - // bind camera desc set (again). Necessary because pipeline layout is not compatible. - cameraDescSetBind(cmdBuffer, frameIndex); - - // bind descriptors for font (matrices) - cmdBuffer->BindDescriptorSet(fontHandle->GetDescriptorSet(), SH_PIPELINE_TYPE::GRAPHICS, SHGraphicsConstants::DescriptorSetIndex::FONT_DATA, {}); - cmdBuffer->SetPushConstantVariable("TestPushConstant.worldTransform", transform->GetTRS(), SH_PIPELINE_TYPE::GRAPHICS); cmdBuffer->SetPushConstantVariable("TestPushConstant.eid", comp.GetEID(), SH_PIPELINE_TYPE::GRAPHICS); cmdBuffer->SetPushConstantVariable("TestPushConstant.textColor", SHVec3 (1.0f, 1.0f, 1.0f), SH_PIPELINE_TYPE::GRAPHICS); @@ -206,9 +214,7 @@ namespace SHADE // call draw call cmdBuffer->DrawArrays(4, comp.text.size(), 0, 0); //glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, static_cast(textComp.lastRenderedCharacterIndex) + 1); - } - } } diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.h b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.h index 78b363d4..c9a89129 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.h @@ -43,18 +43,14 @@ namespace SHADE //! Descriptor set for font data access in shaders //Handle fontDataDescSetLayout; - //! Super temporary. Global descriptor set needs to be revamped along with - //! entire graphics system. - std::function, uint32_t)> cameraDescSetBind; - private: void RecomputePositions(SHTextRenderableComponent& textComp) noexcept; public: - void Init(Handle device, Handle compatibleRenderpass, Handle subpass, Handle descPool, Handle textVS, Handle textFS, std::function, uint32_t)> const& bindFunction) noexcept; + void Init(Handle device, Handle compatibleRenderpass, Handle subpass, Handle descPool, Handle textVS, Handle textFS) noexcept; void Run(uint32_t frameIndex) noexcept; - void Render (Handle cmdBuffer, uint32_t frameIndex) noexcept; + void Render (Handle cmdBuffer, Handle renderer, uint32_t frameIndex) noexcept; void Exit(void) noexcept; diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp index fc029161..d431cf47 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.cpp @@ -14,6 +14,8 @@ #include "Tools/Utilities/SHUtilities.h" #include "Graphics/MiddleEnd/GlobalData/SHPredefinedData.h" #include "Graphics/RenderGraph/SHRenderToSwapchainImageSystem.h" +#include "Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h" + namespace SHADE { @@ -424,7 +426,7 @@ namespace SHADE */ /***************************************************************************/ - void SHRenderGraph::Init(std::string graphName, Handle logicalDevice, Handle swapchain, SHResourceHub* resourceHub) noexcept + void SHRenderGraph::Init(std::string graphName, Handle logicalDevice, Handle swapchain, SHResourceHub* resourceHub, std::vector>& cmdPools) noexcept { //resourceHub = std::make_shared(); @@ -437,6 +439,11 @@ namespace SHADE renderGraphStorage->resourceHub = resourceHub; renderGraphStorage->descriptorPool = logicalDevice->CreateDescriptorPools(); + commandBuffers.resize(static_cast(swapchain->GetNumImages())); + + for (uint32_t i = 0; i < commandBuffers.size(); ++i) + commandBuffers[i] = cmdPools[i]->RequestCommandBuffer(SH_CMD_BUFFER_TYPE::PRIMARY); + name = std::move(graphName); } @@ -555,16 +562,22 @@ namespace SHADE if (renderGraphStorage->graphResources->contains(toSwapchainResource) && renderGraphStorage->graphResources->contains(swapchainResource)) { auto newNode = AddNode("Render To Present", { ResourceInstruction (toSwapchainResource.c_str()), ResourceInstruction(swapchainResource.c_str()) }, predecessorNodes); - auto newSubpass = newNode->AddSubpass("Render"); + auto newSubpass = newNode->AddSubpass("Render", {}, {}); newSubpass->AddColorOutput(swapchainResource); newSubpass->AddInput(toSwapchainResource); renderToSwapchainImageSystem = renderGraphStorage->resourceHub->Create (newNode, newSubpass, shaderModules); - newSubpass->AddExteriorDrawCalls([=](Handle& cmdBuffer, uint32_t frameIndex) + newSubpass->AddExteriorDrawCalls([=](Handle cmdBuffer, Handle renderer, uint32_t frameIndex) { cmdBuffer->BindPipeline(renderToSwapchainImageSystem->GetPipeline()); - + + // If we are rendering to present image, the width and height will be the dimensions of that image. So we need to set viewport scissor. + auto resource = renderGraphStorage->graphResources->at(swapchainResource); + uint32_t w = static_cast(resource->GetWidth()); + uint32_t h = static_cast(resource->GetHeight()); + cmdBuffer->SetViewportScissor(static_cast(w), static_cast(h), w, h); + newSubpass->BindDescriptorInputDescriptorSets (cmdBuffer, frameIndex); // draw a quad. @@ -616,14 +629,39 @@ namespace SHADE // TODO: The graph scope buffers were meant to bind vertex buffers and index buffers for meshes. Find a // better way to manage these - void SHRenderGraph::Execute(uint32_t frameIndex, Handle cmdBuffer, Handle descPool) noexcept + void SHRenderGraph::Execute(uint32_t frameIndex, Handle descPool) noexcept { + auto cmdBuffer = commandBuffers[frameIndex]; cmdBuffer->BeginLabeledSegment(name); + + // Force bind pipeline layout + cmdBuffer->ForceSetPipelineLayout(SHPredefinedData::GetBatchingSystemData().dummyPipelineLayout, SH_PIPELINE_TYPE::GRAPHICS); + cmdBuffer->ForceSetPipelineLayout(SHPredefinedData::GetBatchingSystemData().dummyPipelineLayout, SH_PIPELINE_TYPE::COMPUTE); + + auto const& descMappings = SHPredefinedData::GetBatchingSystemData().descMappings; + for (auto& node : nodes) + { + // bind static global data + SHGlobalDescriptorSets::BindStaticGlobalData(cmdBuffer, SH_PIPELINE_TYPE::GRAPHICS, descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::STATIC_DATA)); + node->Execute(cmdBuffer, descPool, frameIndex); + } + cmdBuffer->EndLabeledSegment(); } + void SHRenderGraph::Begin(uint32_t frameIndex) noexcept + { + commandBuffers[frameIndex]->BeginRecording(); + + } + + void SHRenderGraph::End(uint32_t frameIndex) noexcept + { + commandBuffers[frameIndex]->EndRecording(); + } + void SHRenderGraph::FinaliseBatch(uint32_t frameIndex, Handle descPool) { for (auto& node : nodes) @@ -670,4 +708,9 @@ namespace SHADE return {}; } + Handle SHRenderGraph::GetCommandBuffer(uint32_t frameIndex) const noexcept + { + return commandBuffers[frameIndex]; + } + } \ No newline at end of file diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h index f892483f..c69e83b1 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraph.h @@ -74,6 +74,9 @@ namespace SHADE //! For rendering onto the swapchain Handle renderToSwapchainImageSystem; + //! Command buffer to issue rendering commands + std::vector> commandBuffers; + public: /*-----------------------------------------------------------------------*/ /* CTORS AND DTORS */ @@ -86,7 +89,7 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* PUBLIC MEMBER FUNCTIONS */ /*-----------------------------------------------------------------------*/ - void Init (std::string graphName, Handle logicalDevice, Handle swapchain, SHResourceHub* resourceHub) noexcept; + void Init (std::string graphName, Handle logicalDevice, Handle swapchain, SHResourceHub* resourceHub, std::vector>& cmdPools) noexcept; void AddResource(std::string resourceName, std::initializer_list typeFlags, uint32_t w = static_cast(-1), uint32_t h = static_cast(-1), vk::Format format = vk::Format::eB8G8R8A8Unorm, uint8_t levels = 1, vk::ImageUsageFlagBits usageFlags = {}, vk::ImageCreateFlagBits createFlags = {}); void LinkNonOwningResource (Handle resourceOrigin, std::string resourceName) noexcept; Handle AddNode (std::string nodeName, std::initializer_list resourceInstruction, std::initializer_list predecessorNodes) noexcept; @@ -94,16 +97,19 @@ namespace SHADE void Generate (void) noexcept; void CheckForNodeComputes (void) noexcept; - void Execute (uint32_t frameIndex, Handle cmdBuffer, Handle descPool) noexcept; + void Execute (uint32_t frameIndex, Handle descPool) noexcept; + void Begin (uint32_t frameIndex) noexcept; + void End (uint32_t frameIndex) noexcept; void FinaliseBatch (uint32_t frameIndex, Handle descPool); void HandleResize (uint32_t newWidth, uint32_t newHeight) noexcept; /*-----------------------------------------------------------------------*/ /* SETTERS AND GETTERS */ /*-----------------------------------------------------------------------*/ - Handle GetNode (std::string const& nodeName) const noexcept; - std::vector> const& GetNodes (void) const noexcept; - Handle GetRenderGraphResource (std::string const& resourceName) const noexcept; + Handle GetNode (std::string const& nodeName) const noexcept; + std::vector> const& GetNodes (void) const noexcept; + Handle GetRenderGraphResource (std::string const& resourceName) const noexcept; + Handle GetCommandBuffer (uint32_t frameIndex) const noexcept; }; } diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.cpp index 0f9379fe..1d8cea62 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.cpp +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.cpp @@ -9,6 +9,8 @@ #include "SHRenderGraphStorage.h" #include "Graphics/RenderGraph/SHRenderGraphNodeCompute.h" #include "Graphics/SHVkUtil.h" +#include "Graphics/MiddleEnd/GlobalData/SHGlobalDescriptorSets.h" +#include "Graphics/MiddleEnd/GlobalData/SHPredefinedData.h" namespace SHADE { @@ -238,7 +240,7 @@ namespace SHADE */ /***************************************************************************/ - Handle SHRenderGraphNode::AddSubpass(std::string subpassName) noexcept + Handle SHRenderGraphNode::AddSubpass(std::string subpassName, Handle viewport, Handle renderer) noexcept { // if subpass already exists, don't add. if (subpassIndexing.contains(subpassName)) @@ -253,6 +255,8 @@ namespace SHADE graphStorage->resourceHub->Create ( subpassName, + viewport, + renderer, graphStorage, GetHandle(), static_cast(subpasses.size()), resourceAttachmentMapping.get() ) @@ -318,7 +322,7 @@ namespace SHADE } // insert them all for a subpass to transition them. This subpass is the last subpass - auto dummySubpass = AddSubpass("dummy"); + auto dummySubpass = AddSubpass("dummy", {}, {}); for (auto& resource : resourcesInvolved) { dummySubpass->AddGeneralInput(resource); @@ -331,7 +335,7 @@ namespace SHADE } } - void SHRenderGraphNode::Execute(Handle& commandBuffer, Handle descPool, uint32_t frameIndex) noexcept + void SHRenderGraphNode::Execute(Handle commandBuffer, Handle descPool, uint32_t frameIndex) noexcept { uint32_t framebufferIndex = (framebuffers.size() > 1) ? frameIndex : 0; commandBuffer->BeginRenderpass(renderpass, framebuffers[framebufferIndex]); @@ -347,10 +351,14 @@ namespace SHADE commandBuffer->EndRenderpass(); + auto const& descMappings = SHPredefinedData::GetBatchingSystemData().descMappings; // Execute all subpass computes for (auto& sbCompute : nodeComputes) { + // bind lighting data + SHGlobalDescriptorSets::BindLightingData(commandBuffer, SH_PIPELINE_TYPE::COMPUTE, descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::LIGHTS), frameIndex); + sbCompute->Execute(commandBuffer, frameIndex); } } diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.h b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.h index 2311ee0c..f7e55d4a 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.h +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.h @@ -22,6 +22,8 @@ namespace SHADE class SHPredefinedData; class SHRenderGraphStorage; class SHRenderGraphNodeCompute; + class SHRenderer; + class SHViewport; class SH_API SHRenderGraphNode : public ISelfHandle { @@ -102,12 +104,12 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* PUBLIC MEMBER FUNCTIONS */ /*-----------------------------------------------------------------------*/ - Handle AddSubpass(std::string subpassName) noexcept; + Handle AddSubpass(std::string subpassName, Handle viewport, Handle renderer) noexcept; Handle AddNodeCompute(std::string nodeName, Handle computeShaderModule, std::initializer_list resources, std::unordered_set&& dynamicBufferBindings = {}, float numWorkGroupScale = 1.0f) noexcept; void AddDummySubpassIfNeeded (void) noexcept; // TODO: RemoveSubpass() - void Execute(Handle& commandBuffer, Handle descPool, uint32_t frameIndex) noexcept; + void Execute(Handle commandBuffer, Handle descPool, uint32_t frameIndex) noexcept; Handle GetOrCreatePipeline(std::pair, Handle> const& vsFsPair, Handle subpass) noexcept; void FinaliseBatch(uint32_t frameIndex, Handle descPool); diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.h b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.h index 580f018c..a9a6ac68 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.h +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.h @@ -50,6 +50,8 @@ namespace SHADE //! Compute resources Handle computeResource; + //! + //! vector of resources needed by the subpass compute std::vector> resources; diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.cpp index c1d53632..e5052f59 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.cpp +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.cpp @@ -11,6 +11,9 @@ #include "Graphics/Swapchain/SHVkSwapchain.h" #include "Graphics/Images/SHVkSampler.h" #include "SHRenderGraphResource.h" +#include "Graphics/MiddleEnd/Interface/SHViewport.h" +#include "Graphics/MiddleEnd/Interface/SHRenderer.h" +#include "Graphics/MiddleEnd/GlobalData/SHPredefinedData.h" namespace SHADE { @@ -30,7 +33,7 @@ namespace SHADE */ /***************************************************************************/ - SHSubpass::SHSubpass(const std::string& name, Handle renderGraphStorage, Handle const& parent, uint32_t index, std::unordered_map const* mapping) noexcept + SHSubpass::SHSubpass(const std::string& name, Handle inViewport, Handle inRenderer, Handle renderGraphStorage, Handle const& parent, uint32_t index, std::unordered_map const* mapping) noexcept : resourceAttachmentMapping{ mapping } , parentNode{ parent } , subpassIndex{ index } @@ -41,6 +44,8 @@ namespace SHADE , name { name } , graphStorage{ renderGraphStorage } , inputImageDescriptorSets{} + , viewport {inViewport} + , renderer {inRenderer} { } @@ -71,6 +76,8 @@ namespace SHADE , inputDescriptorLayout{ rhs.inputDescriptorLayout } , inputSamplers{ rhs.inputSamplers } , name { rhs.name } + , viewport {rhs.viewport} + , renderer {rhs.renderer} { } @@ -106,6 +113,9 @@ namespace SHADE inputDescriptorLayout = rhs.inputDescriptorLayout; inputSamplers = rhs.inputSamplers; name = std::move(rhs.name); + renderer = rhs.renderer; + viewport = rhs.viewport; + return *this; } @@ -199,21 +209,33 @@ namespace SHADE inputReferences.push_back({ resourceAttachmentMapping->at(graphStorage->graphResources->at(resourceToReference).GetId().Raw), vk::ImageLayout::eGeneral }); } - void SHSubpass::Execute(Handle& commandBuffer, Handle descPool, uint32_t frameIndex) noexcept + void SHSubpass::Execute(Handle commandBuffer, Handle descPool, uint32_t frameIndex) noexcept { commandBuffer->BeginLabeledSegment(name); - // Ensure correct transforms are provided superBatch->UpdateBuffers(frameIndex, descPool); + if (viewport) + { + // set viewport and scissor + uint32_t w = static_cast(viewport->GetWidth()); + uint32_t h = static_cast(viewport->GetHeight()); + commandBuffer->SetViewportScissor(static_cast(w), static_cast(h), w, h); + } + + auto const& descMappings = SHPredefinedData::GetBatchingSystemData().descMappings; + + if (renderer) + renderer->BindDescriptorSet(commandBuffer, SH_PIPELINE_TYPE::GRAPHICS, descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::CAMERA), frameIndex); + // Draw all the batches superBatch->Draw(commandBuffer, frameIndex); // Draw all the exterior draw calls for (auto& drawCall : exteriorDrawCalls) { - drawCall(commandBuffer, frameIndex); + drawCall(commandBuffer, renderer, frameIndex); } commandBuffer->EndLabeledSegment(); } @@ -231,7 +253,7 @@ namespace SHADE } } - void SHSubpass::AddExteriorDrawCalls(std::function&, uint32_t)> const& newDrawCall) noexcept + void SHSubpass::AddExteriorDrawCalls(ExteriorDrawCallFunction const& newDrawCall) noexcept { exteriorDrawCalls.push_back(newDrawCall); } @@ -266,7 +288,7 @@ namespace SHADE } // We build a new descriptor set layout to store our images - inputDescriptorLayout = graphStorage->logicalDevice->CreateDescriptorSetLayout(SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_RESOURCE, bindings); + inputDescriptorLayout = graphStorage->logicalDevice->CreateDescriptorSetLayout(bindings); // we store a sampler if its an input attachment. if it is storage image, no need sampler, store an empty handle. for (uint32_t i = 0; i < bindings.size(); ++i) diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.h b/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.h index 69b8fd56..b5c5c8b1 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.h +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHSubpass.h @@ -19,15 +19,28 @@ namespace SHADE class SHRenderGraphStorage; class SHVkShaderModule; class SHVkSampler; + class SHRenderer; + class SHViewport; class SH_API SHSubpass : public ISelfHandle { + public: + using ExteriorDrawCallFunction = std::function, Handle, uint32_t)>; + private: /*---------------------------------------------------------------------*/ /* PRIVATE MEMBER VARIABLES */ /*---------------------------------------------------------------------*/ Handle graphStorage; + //! Viewport to specify what part of the screen we want to draw on. This + //! will be used in vkCmdSetViewport/Scissor. + Handle viewport; + + //! Renderer used during the subpass execution. This dictates what matrix gets + //! passed to the shaders. + Handle renderer; + //! The index of the subpass in the render graph uint32_t subpassIndex; @@ -79,8 +92,9 @@ namespace SHADE //! after we draw everything from the batch. Because of this, these draw calls //! are always the last things drawn, so DO NOT USE THIS FUNCTIONALITY FOR ANYTHING //! COMPLEX. - std::vector&, uint32_t)>> exteriorDrawCalls; - /// For identifying subpasses + std::vector exteriorDrawCalls; + + // For identifying subpasses std::string name; @@ -88,7 +102,7 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* CTORS AND DTORS */ /*-----------------------------------------------------------------------*/ - SHSubpass(const std::string& name, Handle renderGraphStorage, Handle const& parent, uint32_t index, std::unordered_map const* mapping) noexcept; + SHSubpass(const std::string& name, Handle inViewport, Handle inRenderer, Handle renderGraphStorage, Handle const& parent, uint32_t index, std::unordered_map const* mapping) noexcept; SHSubpass(SHSubpass&& rhs) noexcept; SHSubpass& operator=(SHSubpass&& rhs) noexcept; @@ -102,10 +116,10 @@ namespace SHADE void AddGeneralDepthOutput(std::string resourceToReference) noexcept; void AddInput(std::string resourceToReference) noexcept; void AddGeneralInput (std::string resourceToReference) noexcept; - void AddExteriorDrawCalls(std::function&, uint32_t)> const& newDrawCall) noexcept; + void AddExteriorDrawCalls(ExteriorDrawCallFunction const& newDrawCall) noexcept; // Runtime functions - void Execute(Handle& commandBuffer, Handle descPool, uint32_t frameIndex) noexcept; + void Execute(Handle commandBuffer, Handle descPool, uint32_t frameIndex) noexcept; void HandleResize (void) noexcept; void BindDescriptorInputDescriptorSets (Handle cmdBuffer, uint32_t frameIndex) const noexcept; From b84364ffe97d3725600e51266b48908526daea32 Mon Sep 17 00:00:00 2001 From: Brandon Mak Date: Wed, 28 Dec 2022 12:43:40 +0800 Subject: [PATCH 086/275] Minor changes - Render Node Compute now has access to camera to send camera data to shaders - Fonts now have functions to bind descriptor set --- .../MiddleEnd/Interface/SHGraphicsSystem.cpp | 1 + .../Graphics/MiddleEnd/TextRendering/SHFont.cpp | 5 +++++ .../src/Graphics/MiddleEnd/TextRendering/SHFont.h | 1 + .../TextRendering/SHTextRenderingSubSystem.cpp | 4 ++-- .../Graphics/RenderGraph/SHRenderGraphNode.cpp | 13 +++++++++---- .../RenderGraph/SHRenderGraphNodeCompute.cpp | 15 ++++++++++++++- .../RenderGraph/SHRenderGraphNodeCompute.h | 8 ++++++-- 7 files changed, 38 insertions(+), 9 deletions(-) diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp index 461b8783..43b05311 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/Interface/SHGraphicsSystem.cpp @@ -265,6 +265,7 @@ namespace SHADE auto viewSamplerLayout = ssaoStorage->GetViewSamplerLayout(); ssaoPass->ModifyWriteDescImageComputeResource(SHGraphicsConstants::DescriptorSetIndex::RENDERGRAPH_NODE_COMPUTE_RESOURCE, SHSSAO::DESC_SET_IMAGE_BINDING, { &viewSamplerLayout, 1 }); + ssaoPass->SetRenderer (worldRenderer); // Add another pass to blur SSAO Handle ssaoBlurPass = gBufferNode->AddNodeCompute("SSAO Blur Step", ssaoBlurShader, { "SSAO", "SSAO Blur" }); diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHFont.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHFont.cpp index f0273940..b0d02b4c 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHFont.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHFont.cpp @@ -132,6 +132,11 @@ namespace SHADE } + void SHFont::BindDescriptorSet(Handle commandBuffer, SH_PIPELINE_TYPE pipelineType, uint32_t setIndex) noexcept + { + commandBuffer->BindDescriptorSet(descSet, SH_PIPELINE_TYPE::GRAPHICS, setIndex, {}); + } + std::unordered_map SHFont::GetUnicodeIndexing(void) const noexcept { return unicodeIndexing; diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHFont.h b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHFont.h index 1439281a..ff15cff0 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHFont.h +++ b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHFont.h @@ -57,6 +57,7 @@ namespace SHADE SHFont (Handle inLogicalDeviceHdl, SHFontAsset const& asset) noexcept; void TransferToGPU (Handle commandBuffer) noexcept; void DoPostTransfer (Handle descPool, Handle layout) noexcept; + void BindDescriptorSet (Handle commandBuffer, SH_PIPELINE_TYPE pipelineType, uint32_t setIndex) noexcept; /*-----------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp index f9afa48f..ab58b626 100644 --- a/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp +++ b/SHADE_Engine/src/Graphics/MiddleEnd/TextRendering/SHTextRenderingSubSystem.cpp @@ -199,8 +199,8 @@ namespace SHADE renderer->BindDescriptorSet(cmdBuffer, SH_PIPELINE_TYPE::GRAPHICS, cameraSetIndex, frameIndex); // bind descriptors for font (matrices) - cmdBuffer->BindDescriptorSet(fontHandle->GetDescriptorSet(), SH_PIPELINE_TYPE::GRAPHICS, fontSetIndex, {}); - + fontHandle->BindDescriptorSet(cmdBuffer, SH_PIPELINE_TYPE::GRAPHICS, fontSetIndex); + // bind VBO (position and indices) cmdBuffer->BindVertexBuffer(SHGraphicsConstants::VertexBufferBindings::CALCULATED_GLYPH_POSITION, comp.charPositionDataBuffer, 0); cmdBuffer->BindVertexBuffer(SHGraphicsConstants::VertexBufferBindings::GLYPH_INDEX, comp.indexingDataBuffer, 0); diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.cpp index 1d8cea62..c23e19d7 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.cpp +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNode.cpp @@ -353,14 +353,19 @@ namespace SHADE auto const& descMappings = SHPredefinedData::GetBatchingSystemData().descMappings; - // Execute all subpass computes - for (auto& sbCompute : nodeComputes) + // We bind these 2 descriptor sets here because they apply to all node computes + if (!nodeComputes.empty()) { + // bind static global data + SHGlobalDescriptorSets::BindStaticGlobalData(commandBuffer, SH_PIPELINE_TYPE::COMPUTE, descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::STATIC_DATA)); + // bind lighting data SHGlobalDescriptorSets::BindLightingData(commandBuffer, SH_PIPELINE_TYPE::COMPUTE, descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::LIGHTS), frameIndex); - - sbCompute->Execute(commandBuffer, frameIndex); } + + // Execute all subpass computes + for (auto& sbCompute : nodeComputes) + sbCompute->Execute(commandBuffer, frameIndex); } Handle SHRenderGraphNode::GetOrCreatePipeline(std::pair, Handle> const& vsFsPair, Handle subpass) noexcept diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.cpp b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.cpp index ea574158..bb748913 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.cpp +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.cpp @@ -10,6 +10,7 @@ #include "SHRenderGraphStorage.h" #include "SHRenderGraphResource.h" #include "Graphics/Commands/SHVkCommandBuffer.h" +#include "Graphics/MiddleEnd/Interface/SHRenderer.h" namespace SHADE { @@ -23,6 +24,7 @@ namespace SHADE , numWorkGroupScale {std::clamp(inNumWorkGroupScale, 0.0f, 1.0f)} , computeResource{} , name { std::move(nodeName) } + , renderer{ } { SHPipelineLayoutParams pipelineLayoutParams { @@ -94,14 +96,20 @@ namespace SHADE auto const& descMappings = SHPredefinedData::GetRenderGraphNodeComputeData().descMappings; - // bind descriptor sets + + // bind render graph resource cmdBuffer->BindDescriptorSet(graphResourceDescSets[frameIndex], SH_PIPELINE_TYPE::COMPUTE, descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::RENDER_GRAPH_RESOURCE), {}); + // bind compute resource if (computeResource) { cmdBuffer->BindDescriptorSet(computeResource->descSet, SH_PIPELINE_TYPE::COMPUTE, descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::RENDER_GRAPH_NODE_COMPUTE_RESOURCE), computeResource->dynamicOffsets[frameIndex]); } + // bind camera data + if (renderer) + renderer->BindDescriptorSet (cmdBuffer, SH_PIPELINE_TYPE::COMPUTE, descMappings.GetMappings().at(SHGraphicsConstants::DescriptorSetTypes::CAMERA), frameIndex); + // dispatch compute cmdBuffer->ComputeDispatch(groupSizeX, groupSizeY, 1); @@ -187,6 +195,11 @@ namespace SHADE } } + void SHRenderGraphNodeCompute::SetRenderer(Handle inRenderer) noexcept + { + renderer = inRenderer; + } + void SHRenderGraphNodeCompute::ModifyWriteDescBufferComputeResource(uint32_t set, uint32_t binding, std::span> const& buffers, uint32_t offset, uint32_t range) noexcept { computeResource->descSet->ModifyWriteDescBuffer(set, binding, buffers, offset, range); diff --git a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.h b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.h index a9a6ac68..cba37f80 100644 --- a/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.h +++ b/SHADE_Engine/src/Graphics/RenderGraph/SHRenderGraphNodeCompute.h @@ -7,6 +7,7 @@ #include #include #include +#include "Resource/SHHandle.h" namespace SHADE { @@ -19,6 +20,7 @@ namespace SHADE class SHVkShaderModule; class SHVkCommandBuffer; class SHVkBuffer; + class SHRenderer; class SHRenderGraphNodeCompute @@ -50,11 +52,12 @@ namespace SHADE //! Compute resources Handle computeResource; - //! - //! vector of resources needed by the subpass compute std::vector> resources; + //! For binding optional camera data to the post compute + Handle renderer; + //! X dimension work group size. Should scale with resource size. uint32_t groupSizeX; @@ -77,6 +80,7 @@ namespace SHADE void HandleResize (void) noexcept; void SetDynamicOffsets (std::span perFrameSizes) noexcept; + void SetRenderer (Handle inRenderer) noexcept; void ModifyWriteDescBufferComputeResource (uint32_t set, uint32_t binding, std::span> const& buffers, uint32_t offset, uint32_t range) noexcept; void ModifyWriteDescImageComputeResource(uint32_t set, uint32_t binding, std::span const& viewSamplerLayouts) noexcept; From 51c9058ab8779e1fdc3a60a725c294e998e14adb Mon Sep 17 00:00:00 2001 From: SHAM-DP Date: Wed, 28 Dec 2022 17:00:54 +0800 Subject: [PATCH 087/275] Window now maximized by default Application now loads working scene if run with editor Added editor config to save: - Window size - Window Maximized - Working Scene - Editor Style --- .../src/Application/SBApplication.cpp | 8 ++++++- .../EditorWindow/MenuBar/SHEditorMenuBar.cpp | 2 +- SHADE_Engine/src/Editor/SHEditor.cpp | 23 +++++++++++++++++-- SHADE_Engine/src/Editor/SHEditor.h | 5 +++- SHADE_Engine/src/Editor/SHEditorWidgets.hpp | 2 +- .../src/Graphics/Windowing/SHWindow.cpp | 9 ++++++++ .../src/Graphics/Windowing/SHWindow.h | 2 ++ .../Configurations/SHConfigurationManager.cpp | 22 ++++++++++++++++-- .../Configurations/SHConfigurationManager.h | 9 +++++--- 9 files changed, 71 insertions(+), 11 deletions(-) diff --git a/SHADE_Application/src/Application/SBApplication.cpp b/SHADE_Application/src/Application/SBApplication.cpp index 5aa1eb00..fcceacab 100644 --- a/SHADE_Application/src/Application/SBApplication.cpp +++ b/SHADE_Application/src/Application/SBApplication.cpp @@ -67,6 +67,9 @@ namespace Sandbox SHFileUtilities::SetWorkDirToExecDir(); WindowData wndData{}; auto& appConfig = SHConfigurationManager::LoadApplicationConfig(&wndData); +#if SHEDITOR + auto& editorConfig = SHConfigurationManager::LoadEditorConfig(&wndData); +#endif window.Create(hInstance, hPrevInstance, lpCmdLine, nCmdShow, wndData); // Create Systems @@ -158,8 +161,11 @@ namespace Sandbox SHSystemManager::Init(); +#if SHEDITOR + SHSceneManager::InitSceneManager(editorConfig.workingSceneID); +#else SHSceneManager::InitSceneManager(appConfig.startingSceneID); - +#endif SHFrameRateController::UpdateFRC(); // Link up SHDebugDraw diff --git a/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp b/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp index a1335e19..a364ea83 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp +++ b/SHADE_Engine/src/Editor/EditorWindow/MenuBar/SHEditorMenuBar.cpp @@ -183,7 +183,7 @@ namespace SHADE //ImGui::InputScalar("Starting Scene", ImGuiDataType_U32, &appConfig.startingSceneID); auto sceneAsset = SHAssetManager::GetData(appConfig.startingSceneID); - if(ImGui::BeginCombo("Starting Scne", sceneAsset ? sceneAsset->name.data() : "")) + if(ImGui::BeginCombo("Starting Scene", sceneAsset ? sceneAsset->name.data() : "")) { auto scenes = SHAssetManager::GetAllRecordOfType(AssetType::SCENE); for(auto const& scene : scenes) diff --git a/SHADE_Engine/src/Editor/SHEditor.cpp b/SHADE_Engine/src/Editor/SHEditor.cpp index 6466533f..7d713ff2 100644 --- a/SHADE_Engine/src/Editor/SHEditor.cpp +++ b/SHADE_Engine/src/Editor/SHEditor.cpp @@ -93,6 +93,8 @@ namespace SHADE SHLOG_CRITICAL("Failed to create ImGui Context") } } + + editorConfig = &SHConfigurationManager::LoadEditorConfig(); //Add editor windows SHEditorWindowManager::CreateEditorWindow(); @@ -122,7 +124,7 @@ namespace SHADE InitBackend(); - SetStyle(Style::SHADE); + SetStyle(static_cast