Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Box2D integration with the engine #1

Merged
merged 14 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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