From 19f6bdf7e0530213c9e1add2b75eb7cfc6815828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Leclercq?= Date: Wed, 16 Mar 2022 08:24:57 +0100 Subject: [PATCH] Add Physics2D components and systems (WIP) --- examples/Physics2DDemo/main.cpp | 191 ++++++++++++++++++ examples/Physics2DDemo/xmake.lua | 6 + include/Nazara/Physics2D/Components.hpp | 34 ++++ .../Components/RigidBody2DComponent.hpp | 32 +++ .../Components/RigidBody2DComponent.inl | 12 ++ include/Nazara/Physics2D/RigidBody2D.hpp | 4 +- include/Nazara/Physics2D/Systems.hpp | 34 ++++ .../Physics2D/Systems/Physics2DSystem.hpp | 46 +++++ .../Physics2D/Systems/Physics2DSystem.inl | 27 +++ .../Components/RigidBody2DComponent.cpp | 10 + src/Nazara/Physics2D/RigidBody2D.cpp | 29 +-- .../Physics2D/Systems/Physics2DSystem.cpp | 65 ++++++ 12 files changed, 476 insertions(+), 14 deletions(-) create mode 100644 examples/Physics2DDemo/main.cpp create mode 100644 examples/Physics2DDemo/xmake.lua create mode 100644 include/Nazara/Physics2D/Components.hpp create mode 100644 include/Nazara/Physics2D/Components/RigidBody2DComponent.hpp create mode 100644 include/Nazara/Physics2D/Components/RigidBody2DComponent.inl create mode 100644 include/Nazara/Physics2D/Systems.hpp create mode 100644 include/Nazara/Physics2D/Systems/Physics2DSystem.hpp create mode 100644 include/Nazara/Physics2D/Systems/Physics2DSystem.inl create mode 100644 src/Nazara/Physics2D/Components/RigidBody2DComponent.cpp create mode 100644 src/Nazara/Physics2D/Systems/Physics2DSystem.cpp diff --git a/examples/Physics2DDemo/main.cpp b/examples/Physics2DDemo/main.cpp new file mode 100644 index 000000000..61f79725f --- /dev/null +++ b/examples/Physics2DDemo/main.cpp @@ -0,0 +1,191 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +NAZARA_REQUEST_DEDICATED_GPU() + +int main() +{ + std::filesystem::path resourceDir = "resources"; + if (!std::filesystem::is_directory(resourceDir) && std::filesystem::is_directory(".." / resourceDir)) + resourceDir = ".." / resourceDir; + + Nz::Renderer::Config rendererConfig; + std::cout << "Run using Vulkan? (y/n)" << std::endl; + if (std::getchar() != 'n') + rendererConfig.preferredAPI = Nz::RenderAPI::Vulkan; + else + rendererConfig.preferredAPI = Nz::RenderAPI::OpenGL; + + std::cin.ignore(std::numeric_limits::max(), '\n'); + + Nz::Modules nazara(rendererConfig); + + Nz::RenderWindow window; + + std::shared_ptr device = Nz::Graphics::Instance()->GetRenderDevice(); + + std::string windowTitle = "Graphics Test"; + if (!window.Create(device, Nz::VideoMode(1920, 1080, 32), windowTitle)) + { + std::cout << "Failed to create Window" << std::endl; + return __LINE__; + } + + entt::registry registry; + + Nz::Physics2DSystem physSytem(registry); + physSytem.GetPhysWorld().SetGravity({ 0.f, -9.81f }); + + Nz::RenderSystem renderSystem(registry); + + entt::entity viewer = registry.create(); + { + registry.emplace(viewer); + auto& cameraComponent = registry.emplace(viewer, window.GetRenderTarget(), Nz::ProjectionType::Orthographic); + cameraComponent.UpdateRenderMask(1); + cameraComponent.UpdateClearColor(Nz::Color(127, 127, 127)); + } + + std::shared_ptr material = std::make_shared(); + + std::shared_ptr materialPass = std::make_shared(Nz::BasicMaterial::GetSettings()); + material->AddPass("ForwardPass", materialPass); + + Nz::TextureSamplerInfo samplerInfo; + samplerInfo.anisotropyLevel = 8; + + Nz::TextureParams texParams; + texParams.renderDevice = device; + texParams.loadFormat = Nz::PixelFormat::RGBA8_SRGB; + + Nz::BasicMaterial basicMat(*materialPass); + basicMat.SetDiffuseMap(Nz::Texture::LoadFromFile(resourceDir / "Spaceship/Texture/diffuse.png", texParams)); + basicMat.SetDiffuseSampler(samplerInfo); + + for (std::size_t y = 0; y < 10; ++y) + { + for (std::size_t x = 0; x < 10; ++x) + { + entt::entity spriteEntity = registry.create(); + { + std::shared_ptr sprite = std::make_shared(material); + sprite->SetSize({ 32.f, 32.f }); + sprite->SetOrigin({ 16.f, 16.f, 0.f }); + + registry.emplace(spriteEntity).SetPosition(1920 / 2 + x * 36.f, 1080 / 2 + y * 36.f); + registry.emplace(spriteEntity).AttachRenderable(sprite, 1); + auto& rigidBody = registry.emplace(spriteEntity, physSytem.CreateRigidBody(50.f, std::make_shared(Nz::Vector2f(32.f, 32.f)))); + rigidBody.SetElasticity(0.99f); + } + } + } + + entt::entity groundEntity = registry.create(); + { + std::shared_ptr whiteMaterial = std::make_shared(); + + std::shared_ptr materialPass = std::make_shared(Nz::BasicMaterial::GetSettings()); + whiteMaterial->AddPass("ForwardPass", materialPass); + + std::shared_ptr sprite = std::make_shared(whiteMaterial); + sprite->SetSize({ 800.f, 20.f }); + sprite->SetOrigin({ 400.f, 10.f, 0.f }); + + registry.emplace(groundEntity).SetPosition(1920.f / 2.f, 50.f); + registry.emplace(groundEntity).AttachRenderable(sprite, 1); + auto& rigidBody = registry.emplace(groundEntity, physSytem.CreateRigidBody(0.f, std::make_shared(Nz::Vector2f(800.f, 20.f)))); + rigidBody.SetElasticity(0.99f); + } + + Nz::EulerAnglesf camAngles(0.f, 0.f, 0.f); + Nz::Quaternionf camQuat(camAngles); + + window.EnableEventPolling(true); + + Nz::Clock updateClock; + Nz::Clock secondClock; + unsigned int fps = 0; + + //Nz::Mouse::SetRelativeMouseMode(true); + + float elapsedTime = 0.f; + Nz::UInt64 time = Nz::GetElapsedMicroseconds(); + + Nz::PidController headingController(0.5f, 0.f, 0.05f); + Nz::PidController upController(1.f, 0.f, 0.1f); + + bool showColliders = false; + while (window.IsOpen()) + { + Nz::UInt64 now = Nz::GetElapsedMicroseconds(); + elapsedTime = (now - time) / 1'000'000.f; + time = now; + + Nz::WindowEvent event; + while (window.PollEvent(&event)) + { + switch (event.type) + { + case Nz::WindowEventType::Quit: + window.Close(); + break; + + case Nz::WindowEventType::KeyPressed: + break; + + case Nz::WindowEventType::MouseMoved: + break; + + default: + break; + } + } + + if (updateClock.GetMilliseconds() > 1000 / 60) + { + float updateTime = updateClock.Restart() / 1'000'000.f; + + physSytem.Update(registry, 1000.f / 60.f); + } + + Nz::RenderFrame frame = window.AcquireFrame(); + if (!frame) + continue; + + renderSystem.Render(registry, frame); + + frame.Present(); + + fps++; + + if (secondClock.GetMilliseconds() >= 1000) + { + window.SetTitle(windowTitle + " - " + Nz::NumberToString(fps) + " FPS" + " - " + Nz::NumberToString(registry.alive()) + " entities"); + + fps = 0; + + secondClock.Restart(); + } + } + + return EXIT_SUCCESS; +} diff --git a/examples/Physics2DDemo/xmake.lua b/examples/Physics2DDemo/xmake.lua new file mode 100644 index 000000000..7501d538c --- /dev/null +++ b/examples/Physics2DDemo/xmake.lua @@ -0,0 +1,6 @@ +target("Physics2DDemo") + set_group("Examples") + set_kind("binary") + add_deps("NazaraGraphics", "NazaraPhysics2D") + add_packages("entt") + add_files("main.cpp") diff --git a/include/Nazara/Physics2D/Components.hpp b/include/Nazara/Physics2D/Components.hpp new file mode 100644 index 000000000..d2111705c --- /dev/null +++ b/include/Nazara/Physics2D/Components.hpp @@ -0,0 +1,34 @@ +// this file was automatically generated and should not be edited + +/* + Nazara Engine - Physics2D module + + Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#pragma once + +#ifndef NAZARA_PHYSICS2D_COMPONENTS_HPP +#define NAZARA_PHYSICS2D_COMPONENTS_HPP + +#include + +#endif // NAZARA_PHYSICS2D_COMPONENTS_HPP diff --git a/include/Nazara/Physics2D/Components/RigidBody2DComponent.hpp b/include/Nazara/Physics2D/Components/RigidBody2DComponent.hpp new file mode 100644 index 000000000..1ee8156a5 --- /dev/null +++ b/include/Nazara/Physics2D/Components/RigidBody2DComponent.hpp @@ -0,0 +1,32 @@ +// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Physics2D module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_PHYSICS2D_COMPONENTS_RIGIDBODY2DCOMPONENT_HPP +#define NAZARA_PHYSICS2D_COMPONENTS_RIGIDBODY2DCOMPONENT_HPP + +#include +#include + +namespace Nz +{ + class NAZARA_PHYSICS2D_API RigidBody2DComponent : public RigidBody2D + { + friend class Physics2DSystem; + + public: + using RigidBody2D::RigidBody2D; + RigidBody2DComponent(const RigidBody2DComponent&) = default; + RigidBody2DComponent(RigidBody2DComponent&&) noexcept = default; + ~RigidBody2DComponent() = default; + + RigidBody2DComponent& operator=(const RigidBody2DComponent&) = default; + RigidBody2DComponent& operator=(RigidBody2DComponent&&) noexcept = default; + }; +} + +#include + +#endif // NAZARA_PHYSICS2D_COMPONENTS_RIGIDBODY2DCOMPONENT_HPP diff --git a/include/Nazara/Physics2D/Components/RigidBody2DComponent.inl b/include/Nazara/Physics2D/Components/RigidBody2DComponent.inl new file mode 100644 index 000000000..69050480b --- /dev/null +++ b/include/Nazara/Physics2D/Components/RigidBody2DComponent.inl @@ -0,0 +1,12 @@ +// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Physics2D module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +namespace Nz +{ +} + +#include diff --git a/include/Nazara/Physics2D/RigidBody2D.hpp b/include/Nazara/Physics2D/RigidBody2D.hpp index 52e7ded5a..183fa6b1d 100644 --- a/include/Nazara/Physics2D/RigidBody2D.hpp +++ b/include/Nazara/Physics2D/RigidBody2D.hpp @@ -110,9 +110,11 @@ namespace Nz static constexpr std::size_t InvalidShapeIndex = std::numeric_limits::max(); + protected: + void Destroy(); + private: cpBody* Create(float mass = 1.f, float moment = 1.f); - void Destroy(); void RegisterToSpace(); void UnregisterFromSpace(); diff --git a/include/Nazara/Physics2D/Systems.hpp b/include/Nazara/Physics2D/Systems.hpp new file mode 100644 index 000000000..a899e4c0b --- /dev/null +++ b/include/Nazara/Physics2D/Systems.hpp @@ -0,0 +1,34 @@ +// this file was automatically generated and should not be edited + +/* + Nazara Engine - Physics2D module + + Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#pragma once + +#ifndef NAZARA_PHYSICS2D_SYSTEMS_HPP +#define NAZARA_PHYSICS2D_SYSTEMS_HPP + +#include + +#endif // NAZARA_PHYSICS2D_SYSTEMS_HPP diff --git a/include/Nazara/Physics2D/Systems/Physics2DSystem.hpp b/include/Nazara/Physics2D/Systems/Physics2DSystem.hpp new file mode 100644 index 000000000..fd83534bc --- /dev/null +++ b/include/Nazara/Physics2D/Systems/Physics2DSystem.hpp @@ -0,0 +1,46 @@ +// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Physics2D module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_PHYSICS2D_SYSTEMS_PHYSICS2DSYSTEM_HPP +#define NAZARA_PHYSICS2D_SYSTEMS_PHYSICS2DSYSTEM_HPP + +#include +#include +#include +#include + +namespace Nz +{ + class NAZARA_PHYSICS2D_API Physics2DSystem + { + public: + Physics2DSystem(entt::registry& registry); + Physics2DSystem(const Physics2DSystem&) = delete; + Physics2DSystem(Physics2DSystem&&) = delete; + ~Physics2DSystem(); + + template RigidBody2DComponent CreateRigidBody(Args&&... args); + + inline PhysWorld2D& GetPhysWorld(); + inline const PhysWorld2D& GetPhysWorld() const; + + void Update(entt::registry& registry, float elapsedTime); + + Physics2DSystem& operator=(const Physics2DSystem&) = delete; + Physics2DSystem& operator=(Physics2DSystem&&) = delete; + + private: + static void OnConstruct(entt::registry& registry, entt::entity entity); + + entt::registry& m_registry; + entt::connection m_constructConnection; + PhysWorld2D m_physWorld; + }; +} + +#include + +#endif // NAZARA_PHYSICS2D_SYSTEMS_PHYSICS2DSYSTEM_HPP diff --git a/include/Nazara/Physics2D/Systems/Physics2DSystem.inl b/include/Nazara/Physics2D/Systems/Physics2DSystem.inl new file mode 100644 index 000000000..3b0aae224 --- /dev/null +++ b/include/Nazara/Physics2D/Systems/Physics2DSystem.inl @@ -0,0 +1,27 @@ +// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Physics2D module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +namespace Nz +{ + template + RigidBody2DComponent Physics2DSystem::CreateRigidBody(Args&&... args) + { + return RigidBody2DComponent(&m_physWorld, std::forward(args)...); + } + + inline PhysWorld2D& Physics2DSystem::GetPhysWorld() + { + return m_physWorld; + } + + inline const PhysWorld2D& Physics2DSystem::GetPhysWorld() const + { + return m_physWorld; + } +} + +#include diff --git a/src/Nazara/Physics2D/Components/RigidBody2DComponent.cpp b/src/Nazara/Physics2D/Components/RigidBody2DComponent.cpp new file mode 100644 index 000000000..c24c7fa31 --- /dev/null +++ b/src/Nazara/Physics2D/Components/RigidBody2DComponent.cpp @@ -0,0 +1,10 @@ +// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Physics2D module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include + +namespace Nz +{ +} diff --git a/src/Nazara/Physics2D/RigidBody2D.cpp b/src/Nazara/Physics2D/RigidBody2D.cpp index 5bd593741..419d79656 100644 --- a/src/Nazara/Physics2D/RigidBody2D.cpp +++ b/src/Nazara/Physics2D/RigidBody2D.cpp @@ -620,6 +620,22 @@ namespace Nz return *this; } + void RigidBody2D::Destroy() + { + UnregisterFromSpace(); + + for (cpShape* shape : m_shapes) + cpShapeFree(shape); + + if (m_handle) + { + cpBodyFree(m_handle); + m_handle = nullptr; + } + + m_shapes.clear(); + } + cpBody* RigidBody2D::Create(float mass, float moment) { cpBody* handle; @@ -638,19 +654,6 @@ namespace Nz return handle; } - void RigidBody2D::Destroy() - { - UnregisterFromSpace(); - - for (cpShape* shape : m_shapes) - cpShapeFree(shape); - - if (m_handle) - cpBodyFree(m_handle); - - m_shapes.clear(); - } - void RigidBody2D::RegisterToSpace() { if (!m_isRegistered) diff --git a/src/Nazara/Physics2D/Systems/Physics2DSystem.cpp b/src/Nazara/Physics2D/Systems/Physics2DSystem.cpp new file mode 100644 index 000000000..6946a1e8d --- /dev/null +++ b/src/Nazara/Physics2D/Systems/Physics2DSystem.cpp @@ -0,0 +1,65 @@ +// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "Nazara Engine - Physics2D module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include + +namespace Nz +{ + namespace + { + inline Nz::RadianAnglef AngleFromQuaternion(const Nz::Quaternionf& quat) + { + float siny_cosp = 2.f * (quat.w * quat.z + quat.x * quat.y); + float cosy_cosp = 1.f - 2.f * (quat.y * quat.y + quat.z * quat.z); + + return std::atan2(siny_cosp, cosy_cosp); //().connect(); + } + + Physics2DSystem::~Physics2DSystem() + { + // Ensure every NewtonBody is destroyed before world is + auto rigidBodyView = m_registry.view(); + for (auto [entity, rigidBodyComponent] : rigidBodyView.each()) + rigidBodyComponent.Destroy(); + + m_constructConnection.release(); + } + + void Physics2DSystem::Update(entt::registry& registry, float elapsedTime) + { + m_physWorld.Step(elapsedTime); + + // Replicate rigid body position to their node components + auto view = registry.view(); + for (auto [entity, nodeComponent, rigidBodyComponent] : view.each()) + { + if (rigidBodyComponent.IsSleeping()) + continue; + + nodeComponent.SetPosition(rigidBodyComponent.GetPosition(), CoordSys::Global); + nodeComponent.SetRotation(rigidBodyComponent.GetRotation(), CoordSys::Global); + } + } + + void Physics2DSystem::OnConstruct(entt::registry& registry, entt::entity entity) + { + // If our entity already has a node component when adding a rigid body, initialize it with its position/rotation + NodeComponent* node = registry.try_get(entity); + if (node) + { + RigidBody2DComponent& rigidBody = registry.get(entity); + rigidBody.SetPosition(Vector2f(node->GetPosition())); + rigidBody.SetRotation(AngleFromQuaternion(node->GetRotation())); + } + } +}