Skip to content

Commit

Permalink
Merge pull request #1 from sparky-game/feature/2d-physics
Browse files Browse the repository at this point in the history
Box2D integration with the engine
  • Loading branch information
iWas-Coder authored Oct 14, 2024
2 parents 95a88fd + 4f9b819 commit bdc22c6
Show file tree
Hide file tree
Showing 13 changed files with 172 additions and 59 deletions.
2 changes: 2 additions & 0 deletions src/App/Entrypoint.hh
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ int main(void) {
renderer.WaitIdle(t);
}

if (scene.IsRunning() or scene.IsPaused()) scene.Stop();

app->Stop(scene);
delete app;
}
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ file(GLOB_RECURSE VOLT_SRCS CONFIGURE_DEPENDS "*.cc")

add_library(${CMAKE_PROJECT_NAME} STATIC
$<TARGET_OBJECTS:imgui>
$<TARGET_OBJECTS:box2d>
$<TARGET_OBJECTS:raylib>
$<TARGET_OBJECTS:imguizmo>
$<TARGET_OBJECTS:yaml_cpp>
Expand All @@ -15,6 +16,7 @@ target_include_directories(${CMAKE_PROJECT_NAME} SYSTEM PUBLIC ../vendor/imgui)
target_include_directories(${CMAKE_PROJECT_NAME} SYSTEM PUBLIC ../vendor/imguizmo)
target_include_directories(${CMAKE_PROJECT_NAME} SYSTEM PUBLIC ../vendor/entt/src)
target_include_directories(${CMAKE_PROJECT_NAME} SYSTEM PUBLIC ../vendor/raylib/src)
target_include_directories(${CMAKE_PROJECT_NAME} SYSTEM PUBLIC ../vendor/box2d/include)
target_include_directories(${CMAKE_PROJECT_NAME} SYSTEM PUBLIC ../vendor/spdlog/include)
target_include_directories(${CMAKE_PROJECT_NAME} SYSTEM PUBLIC ../vendor/yaml-cpp/include)
target_include_directories(${CMAKE_PROJECT_NAME} SYSTEM PUBLIC ../vendor/imgui_impl_raylib)
Expand Down
19 changes: 9 additions & 10 deletions src/Renderer/EditorLayer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,16 @@ namespace volt::renderer {
void EditorLayer::drawControls(runtime::Scene &s) noexcept {
if (ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_NoTitleBar)) {
if (s.IsRunning()) {
if (ImGui::Button("Pause")) {
s.Pause();
}
}
else if (ImGui::Button("Play")) {
s.Play();
if (ImGui::Button("Pause")) s.Pause();
ImGui::SameLine();
if (ImGui::Button("Stop")) s.Stop();
}
ImGui::SameLine();
if (ImGui::Button("Stop")) {
s.Stop();
else if (s.IsPaused()) {
if (ImGui::Button("Play")) s.Play();
ImGui::SameLine();
if (ImGui::Button("Stop")) s.Stop();
}
else if (ImGui::Button("Play")) s.Play();
}
ImGui::End();
}
Expand Down Expand Up @@ -185,7 +184,7 @@ namespace volt::renderer {
}

void EditorLayer::Setup(void) const noexcept {
static constexpr auto config_filename { "volt.ini" };
constexpr auto config_filename { "volt.ini" };
if (not std::filesystem::exists(config_filename) or not std::filesystem::is_regular_file(config_filename)) {
if (not ChangeDirectory(GetApplicationDirectory())) {
VOLT_LOG_WARN("volt::renderer::EditorLayer::Setup :: could not change CWD to Volt's editor binary directory");
Expand Down
6 changes: 3 additions & 3 deletions src/Renderer/Sprite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ namespace volt::renderer {
{
// NOTE: leave the rvalue Sprite object in an invalid state (id <= 0), so when it gets destroyed (after it's moved)
// it doesn't get unloaded from GPU's VRAM (i.e. leave the moved-from object as a hollow object (empty state)).
m_width = 0;
m_height = 0;
s.m_width = 0;
s.m_height = 0;
s.m_data = {};
m_name.clear();
s.m_name.clear();
}

Sprite::~Sprite(void) {
Expand Down
2 changes: 1 addition & 1 deletion src/Renderer/Sprite.hh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ extern "C" {
namespace volt::renderer {
class Sprite {
uint32_t m_width, m_height;
Texture m_data;
Texture2D m_data;
std::string m_name;
void loadDefaultTexture(void) noexcept;
public:
Expand Down
23 changes: 11 additions & 12 deletions src/Renderer/SpriteRendererComponent.cc
Original file line number Diff line number Diff line change
Expand Up @@ -49,26 +49,25 @@ namespace volt::renderer {
}

void SpriteRendererComponent::Draw(void) {
static bool is_first_time { true };
static size_t sprite_path_len {0};
static std::array<char, 32> sprite_path { "default" };
static ImVec4 sprite_color {1, 1, 1, 1};
if (is_first_time) {
size_t len { std::min(sprite.name().size(), sprite_path.size() - 1) };
std::copy_n(sprite.name().begin(), len, sprite_path.begin());
sprite_path[len] = 0;
Vector4 c { ColorNormalize(GetColor(color)) };
sprite_color.x = c.x;
sprite_color.y = c.y;
sprite_color.z = c.z;
sprite_color.w = c.w;
is_first_time = false;
}
if (ImGui::CollapsingHeader(SpriteRendererComponent::cmp_name)) {
if (sprite.name() != sprite_path.data()) {
sprite_path_len = std::min(sprite.name().size(), sprite_path.size() - 1);
std::copy_n(sprite.name().begin(), sprite_path_len, sprite_path.begin());
sprite_path[sprite_path_len] = 0;
}
ImGui::InputText("Sprite", sprite_path.data(), sprite_path.size());
try { sprite.Reset(std::string(sprite_path.data())); }
catch (const std::runtime_error &) {
ImGui::TextColored(ImVec4{1, 0.25f, 0.25f, 1}, "Path doesn't resolve to asset");
}
Vector4 c { ColorNormalize(GetColor(color)) };
sprite_color.x = c.x;
sprite_color.y = c.y;
sprite_color.z = c.z;
sprite_color.w = c.w;
if (ImGui::ColorEdit4("Color", &sprite_color.x, ImGuiColorEditFlags_AlphaBar)) {
Vector4 c {sprite_color.x * 255, sprite_color.y * 255, sprite_color.z * 255, sprite_color.w * 255};
color = (std::lround(c.x) << 24) | (std::lround(c.y) << 16) | (std::lround(c.z) << 8) | std::lround(c.w);
Expand Down
10 changes: 5 additions & 5 deletions src/Runtime/BehaviourBounce.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ namespace volt::runtime {
void BehaviourBounce::Update(Entity &e) {
if (e.HasComponent<TransformComponent>() && e.HasComponent<Rigidbody2DComponent>()) {
auto &t { e.GetComponent<TransformComponent>() };
auto &rb { e.GetComponent<Rigidbody2DComponent>() };
if (t.position.X() <= 0) { t.position.X(0); rb.velocity.X(-rb.velocity.X()); }
if (t.position.Y() <= 0) { t.position.Y(0); rb.velocity.Y(-rb.velocity.Y()); }
if (t.position.X() >= 640) { t.position.X(640); rb.velocity.X(-rb.velocity.X()); }
if (t.position.Y() >= 456) { t.position.Y(456); rb.velocity.Y(-rb.velocity.Y()); }
// auto &rb { e.GetComponent<Rigidbody2DComponent>() };
if (t.position.X() <= 0) { t.position.X(0); /*rb.velocity.X(-rb.velocity.X());*/ }
if (t.position.Y() <= 0) { t.position.Y(0); /*rb.velocity.Y(-rb.velocity.Y());*/ }
if (t.position.X() >= 640) { t.position.X(640); /*rb.velocity.X(-rb.velocity.X());*/ }
if (t.position.Y() >= 456) { t.position.Y(456); /*rb.velocity.Y(-rb.velocity.Y());*/ }
}
}
}
14 changes: 8 additions & 6 deletions src/Runtime/InputSystem.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ namespace volt::runtime {
}

void InputSystem::Update(Scene &s) {
s.ForAll<InputComponent, Rigidbody2DComponent>([](auto, auto &i, auto &rb) {
rb.velocity = {0, 0};
if (IsKeyDown(i.left)) rb.velocity.X((rb.velocity.X() - i.impulse) * core::GetDeltaTime());
if (IsKeyDown(i.right)) rb.velocity.X((rb.velocity.X() + i.impulse) * core::GetDeltaTime());
if (IsKeyDown(i.up)) rb.velocity.Y((rb.velocity.Y() - i.impulse) * core::GetDeltaTime());
if (IsKeyDown(i.down)) rb.velocity.Y((rb.velocity.Y() + i.impulse) * core::GetDeltaTime());
s.ForAll<InputComponent, Rigidbody2DComponent>([](auto, auto &/*i*/, auto &/*rb*/) {
/*
rb.velocity = {0, 0};
if (IsKeyDown(i.left)) rb.velocity.X((rb.velocity.X() - i.impulse) * core::GetDeltaTime());
if (IsKeyDown(i.right)) rb.velocity.X((rb.velocity.X() + i.impulse) * core::GetDeltaTime());
if (IsKeyDown(i.up)) rb.velocity.Y((rb.velocity.Y() - i.impulse) * core::GetDeltaTime());
if (IsKeyDown(i.down)) rb.velocity.Y((rb.velocity.Y() + i.impulse) * core::GetDeltaTime());
*/
});
}
}
14 changes: 13 additions & 1 deletion src/Runtime/PhysicsSystem.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
#include "TransformComponent.hh"
#include "Rigidbody2DComponent.hh"

extern "C" {
#include <raylib.h>
}

namespace volt::runtime {
PhysicsSystem::PhysicsSystem(void) {
VOLT_LOG_INFO("PhysicsSystem: created successfully");
Expand All @@ -13,8 +17,16 @@ namespace volt::runtime {
}

void PhysicsSystem::Update(Scene &s) {
// Box2D physics simulation update
b2World_Step(s.GetPhysicsWorld(), 1.0f / 60, 4);
// Box2D update Transform from Rigidbody2D
s.ForAll<TransformComponent, Rigidbody2DComponent>([](auto, auto &t, auto &rb) {
t.position += rb.velocity;
// Position
b2Vec2 pos = b2Body_GetWorldPoint(rb.GetBodyID(), -rb.GetExtent());
t.position.X(pos.x);
t.position.Y(pos.y);
// Rotation
t.rotation = b2Rot_GetAngle(b2Body_GetRotation(rb.GetBodyID())) * RAD2DEG;
});
}
}
70 changes: 54 additions & 16 deletions src/Runtime/Rigidbody2DComponent.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,74 @@
#include "Rigidbody2DComponent.hh"

namespace volt::runtime {
Rigidbody2DComponent::Rigidbody2DComponent(const core::Vector2<float> &v)
: velocity{v}
{}

void Rigidbody2DComponent::Serialize(YAML::Emitter &out) {
out << YAML::Key << Rigidbody2DComponent::cmp_name << YAML::BeginMap;
out << YAML::Key << "Velocity" << YAML::Value << velocity.ToString();
std::string type_str;
switch (type) {
case Rigidbody2DType::Static:
type_str = "Static";
break;
case Rigidbody2DType::Kinematic:
type_str = "Kinematic";
break;
case Rigidbody2DType::Dynamic:
type_str = "Dynamic";
break;
default:
assert(0 && "Unreachable");
}
out << YAML::Key << "Type" << YAML::Value << type_str;
out << YAML::Key << "FixedRotation" << YAML::Value << fixedRotation;
out << YAML::Key << "GravityScale" << YAML::Value << gravityScale;
out << YAML::EndMap;
}

bool Rigidbody2DComponent::Deserialize(YAML::Node &in) {
auto velocity_serialized { in["Velocity"].as<std::string>() };
float velocity_tmp_x {0}, velocity_tmp_y {0};
if (2 != std::sscanf(velocity_serialized.c_str(), "(%f, %f)", &velocity_tmp_x, &velocity_tmp_y)) {
VOLT_LOG_ERROR("volt::runtime::Rigidbody2DComponent::Deserialize :: `{}.Velocity` value format not valid",
Rigidbody2DComponent::cmp_name);
static constexpr auto func_name { "volt::runtime::Rigidbody2DComponent::Deserialize" };
auto type_serialized { in["Type"].as<std::string>() };
if (type_serialized == "Static") type = Rigidbody2DType::Static;
else if (type_serialized == "Kinematic") type = Rigidbody2DType::Kinematic;
else if (type_serialized == "Dynamic") type = Rigidbody2DType::Dynamic;
else {
VOLT_LOG_ERROR("{} :: `{}.Type` value not valid", func_name, Rigidbody2DComponent::cmp_name);
return false;
}
try { fixedRotation = in["FixedRotation"].as<bool>(); }
catch (const YAML::TypedBadConversion<bool> &) {
VOLT_LOG_ERROR("{} :: `{}.FixedRotation` value format not valid", func_name, Rigidbody2DComponent::cmp_name);
return false;
}
try { gravityScale = in["GravityScale"].as<float>(); }
catch (const YAML::TypedBadConversion<float> &) {
VOLT_LOG_ERROR("{} :: `{}.GravityScale` value format not valid", func_name, Rigidbody2DComponent::cmp_name);
return false;
}
velocity.X(velocity_tmp_x);
velocity.Y(velocity_tmp_y);
return true;
}

void Rigidbody2DComponent::Draw(void) {
if (ImGui::CollapsingHeader(Rigidbody2DComponent::cmp_name)) {
// TODO: use Vector2 to simplify this and be able to pass the data directly.
float velocity_tmp[] { velocity.X(), velocity.Y() };
ImGui::InputFloat2("Velocity", velocity_tmp);
velocity.X(velocity_tmp[0]);
velocity.Y(velocity_tmp[1]);
/*
float velocity_tmp[] { velocity.X(), velocity.Y() };
ImGui::InputFloat2("Velocity", velocity_tmp);
velocity.X(velocity_tmp[0]);
velocity.Y(velocity_tmp[1]);
*/
// Body Type
static std::array<const char *, 3> type_opts { "Static", "Kinematic", "Dynamic" };
static int type_opt_curr {0};
if (type == Rigidbody2DType::Static) type_opt_curr = 0;
else if (type == Rigidbody2DType::Kinematic) type_opt_curr = 1;
else if (type == Rigidbody2DType::Dynamic) type_opt_curr = 2;
ImGui::Combo("Body Type", &type_opt_curr, type_opts.data(), type_opts.size());
if (type_opt_curr == 0) type = Rigidbody2DType::Static;
else if (type_opt_curr == 1) type = Rigidbody2DType::Kinematic;
else if (type_opt_curr == 2) type = Rigidbody2DType::Dynamic;
// Freeze Rotation
ImGui::Checkbox("Freeze Rotation", &fixedRotation);
// Gravity Scale
ImGui::InputFloat("Gravity Scale", &gravityScale);
}
}
}
21 changes: 19 additions & 2 deletions src/Runtime/Rigidbody2DComponent.hh
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
#pragma once

#include <box2d/box2d.h>
#include "IComponent.hh"
#include "../Core/Vector2.hh"

namespace volt::runtime {
enum class Rigidbody2DType {
Static = b2_staticBody,
Kinematic = b2_kinematicBody,
Dynamic = b2_dynamicBody
};

struct Rigidbody2DComponent : public IComponent {
static inline auto cmp_name { "Rigidbody2D" };
core::Vector2<float> velocity;
Rigidbody2DComponent(const core::Vector2<float> &v = core::Vector2<>::zero());
Rigidbody2DType type { Rigidbody2DType::Static };
bool fixedRotation { false };
float gravityScale { 1 };
core::Vector2<float> linearVelocity { core::Vector2<>::zero() };
Rigidbody2DComponent(void) = default;
void Serialize(YAML::Emitter &out) final;
bool Deserialize(YAML::Node &in) final;
void Draw(void) final;
inline b2BodyId GetBodyID(void) const { return m_bodyID; }
inline void SetBodyID(b2BodyId id) { m_bodyID = id; }
inline b2Vec2 GetExtent(void) const { return m_extent; }
inline void SetExtent(const core::Vector2<float> &e) { m_extent = {e.X(), e.Y()}; }
private:
b2BodyId m_bodyID;
b2Vec2 m_extent;
};
}
43 changes: 40 additions & 3 deletions src/Runtime/Scene.cc
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
#include "Scene.hh"
#include <unordered_map>
#include "../Core/LogSystem.hh"
#include "TransformComponent.hh"
#include "Rigidbody2DComponent.hh"
#include "../Renderer/SpriteRendererComponent.hh"

namespace volt::runtime {
static std::unordered_map<core::SnowflakeID::value_type, core::Vector2<float>> initial_positions;
static std::unordered_map<core::SnowflakeID::value_type, float> initial_rotations;

Entity::Entity(EID id, Scene *scene)
: m_id{id}, m_scene{scene}
{}

Scene::Scene(const std::string &name)
: m_name{name}, m_running{false}
: m_name{name}, m_running{false}, m_paused{false}
{}

Scene::~Scene(void) {
Expand Down Expand Up @@ -52,14 +58,45 @@ namespace volt::runtime {

void Scene::Play(void) noexcept {
if (not m_running) m_running = true;
if (not m_paused) {
b2WorldDef worldDef = b2DefaultWorldDef();
worldDef.gravity = {0.0f, 9.81f};
m_worldID = b2CreateWorld(&worldDef);
}
ForAll<TransformComponent, Rigidbody2DComponent, renderer::SpriteRendererComponent>([this](auto e, auto &t, auto &rb, auto &sr) {
rb.SetExtent({t.scale * sr.sprite.width() * 0.5f, t.scale * sr.sprite.height() * 0.5f});
b2BodyDef bodyDef { b2DefaultBodyDef() };
bodyDef.type = static_cast<b2BodyType>(rb.type);
bodyDef.gravityScale = rb.gravityScale;
bodyDef.fixedRotation = rb.fixedRotation;
bodyDef.position = {t.position.X(), t.position.Y()};
bodyDef.rotation = b2MakeRot(t.rotation * DEG2RAD);
rb.SetBodyID(b2CreateBody(m_worldID, &bodyDef));
b2Polygon polygon { b2MakeBox(rb.GetExtent().x, rb.GetExtent().y) };
b2ShapeDef shapeDef { b2DefaultShapeDef() };
b2CreatePolygonShape(rb.GetBodyID(), &shapeDef, &polygon);
// NOTE: Save initial position and rotation (if wasn't in paused state, to not overwrite actual initial values)
if (not m_paused) {
initial_positions[e.GetID()] = t.position;
initial_rotations[e.GetID()] = t.rotation;
}
});
if (m_paused) m_paused = false;
}

void Scene::Pause(void) noexcept {
if (m_running) m_running = false;
if (not m_paused) m_paused = true;
}

void Scene::Stop(void) noexcept {
Pause();
// TODO: figure out how to reset/rewind the scene
if (m_running) m_running = false;
if (m_paused) m_paused = false;
for (auto &[id, pos] : initial_positions) {
auto &t { FindEntityByID(id)->GetComponent<TransformComponent>() };
t.position = pos;
t.rotation = initial_rotations[id];
}
b2DestroyWorld(m_worldID);
}
}
Loading

0 comments on commit bdc22c6

Please sign in to comment.