Compare commits

...

12 Commits

Author SHA1 Message Date
8b308db058 Audio Engine Fixes 2025-10-03 03:16:40 +05:30
6a87771d8f Fixes 2025-10-01 23:14:11 +05:30
f6c022f38e Fixes 2025-09-30 01:41:48 +05:30
0d5c2e639e Fixes 2025-09-29 19:04:14 +05:30
ae20f51e9a Fixes 2025-09-29 16:12:19 +05:30
9038fa54b0 Final Collisions 2025-09-29 15:52:27 +05:30
9269306146 Physics Debug Draw 2025-09-29 14:53:06 +05:30
b18bfe873f Collision 2/2 2025-09-29 13:03:18 +05:30
5f112f7531 Collision 1/2 2025-09-29 03:36:31 +05:30
3380eb993e Debug Draw 2025-09-29 01:24:46 +05:30
fe88538e3c Debug Draw Base 2025-09-29 01:02:02 +05:30
3c5792e975 Remove Box2D 2025-09-28 21:53:23 +05:30
29 changed files with 748 additions and 565 deletions

View File

@ -2,9 +2,9 @@
#include <IAEngine/Input.hpp>
#include <IAEngine/Rendering/Camera.hpp>
#include <IAEngine/Components/PhysicsBody2D.hpp>
#include <IAEngine/Components/BoxCollider2D.hpp>
#include <IAEngine/Rendering/DebugDraw.hpp>
#include <IAEngine/Components/SpriteRenderer.hpp>
#include <IAEngine/Components/Physics.hpp>
#include <IACore/File.hpp>
@ -13,7 +13,7 @@ namespace ia::iae::game
RefPtr<Scene> scene;
RefPtr<Node> g_player;
PhysicsBody2DComponent* g_playerPhysicsBody{};
PhysicsComponent* g_playerPhysics;
VOID Game::Initialize()
{
@ -34,20 +34,19 @@ namespace ia::iae::game
});
t->BakeAnimations();
}
{
g_playerPhysicsBody = g_player->AddComponent<PhysicsBody2DComponent>();
g_playerPhysicsBody->IsDynamic() = true;
const auto collider = g_player->AddComponent<BoxCollider2DComponent>();
collider->Rect() = {
0,
0,
g_player->DrawnSize().x,
g_player->DrawnSize().y,
};
collider->IsDebugDrawEnabled() = true;
}
g_playerPhysics = g_player->AddComponent<PhysicsComponent>();
g_playerPhysics->IsDynamic() = true;
g_playerPhysics->AddCollider({
.IsTrigger = false,
.Position = {
0.0f, 0.0f,
},
.Size = {
25.0f, 25.0f
}
});
g_player->Tags() = NODE_TAG_PLAYER;
g_player->SetLocalPosition({200, 200});
g_player->SetLocalPosition({200, 300});
const auto obstacle = MakeRefPtr<Node>();
{
@ -56,23 +55,23 @@ namespace ia::iae::game
.ShouldLoop = true,
.Keys = {
SpriteRendererComponent::AnimationKeyFrame {
.Scale = {3.0f, 0.5f},
.Scale = {1.0f, 0.5f},
.ColorOverlay = {0.75f, 0.0f, 0.0f, 1.0f}
}
},
});
t->BakeAnimations();
}
{
obstacle->AddComponent<PhysicsBody2DComponent>();
const auto collider = obstacle->AddComponent<BoxCollider2DComponent>();
collider->Rect() = {
0, 0,
obstacle->DrawnSize().x,
obstacle->DrawnSize().y
};
collider->IsDebugDrawEnabled() = true;
}
const auto obstaclePhysics = obstacle->AddComponent<PhysicsComponent>();
obstaclePhysics->AddCollider({
.IsTrigger = false,
.Position = {
0.0f, 0.0f,
},
.Size = {
100.0f, 50.0f
}
});
obstacle->Tags() = NODE_TAG_GROUND;
obstacle->SetLocalSortIndex(20);
obstacle->SetLocalPosition({200, 400});
@ -89,8 +88,7 @@ namespace ia::iae::game
VOID Game::Update()
{
if(Input::WasKeyPressed(Input::KEY_SPACE))
g_playerPhysicsBody->Jump(200.0f);
g_playerPhysicsBody->SetVelocityX(Input::GetDirectionalInput().x * 100.0f);
const auto d = Input::GetDirectionalInput();
g_playerPhysics->Move(d);
}
}

View File

@ -13,9 +13,11 @@ set(IAEngine_Sources
imp/cpp/Rendering/Renderer.cpp
imp/cpp/Rendering/GPUBuffer.cpp
imp/cpp/Rendering/GPUTexture.cpp
imp/cpp/Rendering/DebugDraw.cpp
imp/cpp/Rendering/Pipeline/Pipeline.cpp
imp/cpp/Rendering/Pipeline/UnlitMesh.cpp
imp/cpp/Rendering/Pipeline/PostProcess.cpp
imp/cpp/Physics/Physics.cpp
@ -25,12 +27,11 @@ set(IAEngine_Sources
imp/cpp/Nodes/Node.cpp
imp/cpp/Components/AtlasRenderer.cpp
imp/cpp/Components/BoxCollider2D.cpp
imp/cpp/Components/SpriteRenderer.cpp
imp/cpp/Components/SoundEmitter.cpp
imp/cpp/Components/ParticleEmitter.cpp
imp/cpp/Components/PhysicsBody2D.cpp
imp/cpp/Components/TextureRenderer.cpp
imp/cpp/Components/Physics.cpp
)
add_library(IAEngine STATIC ${IAEngine_Sources})
@ -39,4 +40,4 @@ target_include_directories(IAEngine PUBLIC inc/)
target_include_directories(IAEngine PRIVATE imp/hpp)
target_link_libraries(IAEngine PUBLIC IACore ImGui glm::glm)
target_link_libraries(IAEngine PRIVATE SDL3::SDL3 SDL3_mixer::SDL3_mixer box2d RmlUi::RmlUi)
target_link_libraries(IAEngine PRIVATE SDL3::SDL3 SDL3_mixer::SDL3_mixer RmlUi::RmlUi)

View File

@ -19,12 +19,26 @@
#include <SDL3/SDL_iostream.h>
#include <SDL3_mixer/SDL_mixer.h>
#include <condition_variable>
#include <thread>
namespace ia::iae
{
struct TrackData
{
BOOL IsActive{};
MIX_Track *Track{};
INT32 LoopTimes{};
TimePeriod LoopDelay{};
std::thread *PlaybackThread;
std::mutex Mutex;
std::condition_variable CV;
};
MIX_Mixer *g_mixer{};
Vector<MIX_Track *> g_tracks;
Vector<TrackData*> g_tracks;
Vector<INT64> g_destroyQueue;
Vector<MIX_Audio *> g_audioData;
VOID Audio::Initialize()
@ -38,7 +52,48 @@ namespace ia::iae
VOID Audio::Terminate()
{
for (SIZE_T i = 0; i < g_tracks.size(); i++)
{
{
std::lock_guard<std::mutex> lock(g_tracks[i]->Mutex);
g_tracks[i]->IsActive = false;
}
g_tracks[i]->CV.notify_one();
g_tracks[i]->PlaybackThread->join();
delete g_tracks[i];
}
MIX_StopAllTracks(g_mixer, 0);
g_audioData.clear();
g_tracks.clear();
}
VOID Audio::Update()
{
for (SIZE_T i = 0; i < g_tracks.size(); i++)
{
auto &t = g_tracks[i];
if (!t->IsActive && !MIX_TrackPlaying(t->Track))
DestroyTrack(i);
}
// [IATODO]: IMPL: Destroy queue
}
INT64 Audio::CreateTrack()
{
g_tracks.pushBack(new TrackData{true, MIX_CreateTrack(g_mixer)});
return g_tracks.size() - 1;
}
VOID Audio::DestroyTrack(IN INT64 trackHandle)
{
if(trackHandle >= g_tracks.size())
return;
auto& t = g_tracks[trackHandle];
ClearTrack(trackHandle);
MIX_StopTrack(t->Track, 0);
t->IsActive = false;
g_destroyQueue.pushBack(trackHandle);
}
Sound Audio::CreateSound(IN PCUINT8 audioData, IN SIZE_T audioDataSize)
@ -47,62 +102,55 @@ namespace ia::iae
return Sound(g_audioData.size() - 1);
}
INT64 Audio::CreateTrack()
{
g_tracks.pushBack(MIX_CreateTrack(g_mixer));
return g_tracks.size() - 1;
}
VOID Audio::DestroyTrack(IN INT64 trackHandle)
{
ClearTrack(trackHandle);
}
VOID Audio::QueueTrackData(IN INT64 trackHandle, IN INT64 dataHandle)
{
MIX_SetTrackAudio(g_tracks[trackHandle], g_audioData[dataHandle]);
MIX_SetTrackAudio(g_tracks[trackHandle]->Track, g_audioData[dataHandle]);
}
VOID Audio::PlayTrack(IN INT64 trackHandle, IN INT32 loopTimes, IN TimePeriod loopDelay)
{
struct __callback_data
{
INT32 loopTimes;
TimePeriod loopDelay;
};
const auto callbackData = new __callback_data{.loopTimes = loopTimes, .loopDelay = loopDelay};
MIX_PlayTrack(g_tracks[trackHandle], 0);
MIX_SetTrackStoppedCallback(
g_tracks[trackHandle],
[](PVOID _callbackData, MIX_Track *track) {
const auto callbackData = (__callback_data *) _callbackData;
if (callbackData->loopTimes < 0)
goto loop_next_iteration;
else if (callbackData->loopTimes == 0)
return;
else
callbackData->loopTimes -= 1;
loop_next_iteration:
std::this_thread::sleep_for(std::chrono::milliseconds((INT32) (callbackData->loopDelay.GetValue() * 1000)));
MIX_PlayTrack(track, 0);
},
callbackData);
auto &t = g_tracks[trackHandle];
t->LoopTimes = loopTimes;
t->LoopDelay = loopDelay;
if (t->LoopTimes == 0)
t->IsActive = false;
else
t->PlaybackThread = new std::thread([trackHandle]() {
auto &t = g_tracks[trackHandle];
while (t->IsActive)
{
if (MIX_TrackPlaying(t->Track))
continue;
if (t->LoopTimes < 0)
goto loop_next_iteration;
else if (!t->LoopTimes)
{
t->IsActive = false;
return;
}
t->LoopTimes--;
loop_next_iteration:
std::unique_lock<std::mutex> lock(t->Mutex);
t->CV.wait_for(lock, std::chrono::milliseconds((INT32) (t->LoopDelay.GetValue() * 1000)),
[t]() { return !t->IsActive; });
MIX_PlayTrack(t->Track, 0);
}
});
MIX_PlayTrack(t->Track, 0);
}
VOID Audio::ClearTrack(IN INT64 trackHandle)
{
MIX_StopTrack(g_tracks[trackHandle], 0);
MIX_StopTrack(g_tracks[trackHandle]->Track, 0);
}
VOID Audio::PauseTrack(IN INT64 trackHandle)
{
MIX_PauseTrack(g_tracks[trackHandle]);
MIX_PauseTrack(g_tracks[trackHandle]->Track);
}
VOID Audio::ResumeTrack(IN INT64 trackHandle)
{
MIX_ResumeTrack(g_tracks[trackHandle]);
MIX_ResumeTrack(g_tracks[trackHandle]->Track);
}
} // namespace ia::iae

View File

@ -1,62 +0,0 @@
// IAEngine: 2D Game Engine by IA
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IAEngine/Components/BoxCollider2D.hpp>
#include <IAEngine/Components/PhysicsBody2D.hpp>
#include <IAEngine/Components/TextureRenderer.hpp>
#include <IAEngine/Rendering/Renderer.hpp>
#include <IAEngine/Physics/Physics.hpp>
#include <IAEngine/Nodes/Node.hpp>
namespace ia::iae
{
EXTERN Texture g_whiteStrokeTexture;
BoxCollider2DComponent::BoxCollider2DComponent(IN Node *node) : IComponent(node)
{
IA_RELEASE_ASSERT(m_body = node->GetComponent<PhysicsBody2DComponent>());
m_physicsHandle = Physics::AddColliderToBody(m_body->PhysicsHandle(), this);
}
VOID BoxCollider2DComponent::Draw()
{
if (!m_isDebugDrawEnabled)
return;
Renderer::Draw(Renderer::GetMesh_Quad(), g_whiteStrokeTexture.GetHandle(), m_node->GetPosition() + glm::vec2{m_rect.x, m_rect.y},
glm::vec2{m_rect.z, m_rect.w}, 0, Renderer::MAX_LAYER_INDEX,
0, {1.0f, 1.0f, 1.0f, 1.0f});
}
VOID BoxCollider2DComponent::Update()
{
}
VOID BoxCollider2DComponent::OnCollisionEnter(IN Node *other)
{
if (other->HasTag(NODE_TAG_GROUND))
m_body->IsGrounded() = true;
if(m_collisionEnterCallback)
m_collisionEnterCallback(other);
}
VOID BoxCollider2DComponent::OnCollisionExit(IN Node *other)
{
if (other->HasTag(NODE_TAG_GROUND))
m_body->IsGrounded() = false;
if(m_collisionExitCallback)
m_collisionExitCallback(other);
}
} // namespace ia::iae

View File

@ -0,0 +1,78 @@
// IAEngine: 2D Game Engine by IA
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IAEngine/Components/Physics.hpp>
#include <IAEngine/Physics/Physics.hpp>
#include <IAEngine/Nodes/Node.hpp>
namespace ia::iae
{
PhysicsComponent::PhysicsComponent(IN Node *node) : IComponent(node)
{
m_physicsHandle = Physics::RegisterComponent(this);
}
PhysicsComponent::~PhysicsComponent()
{
}
VOID PhysicsComponent::Draw()
{
}
VOID PhysicsComponent::Update()
{
m_velocity = {};
}
Handle PhysicsComponent::CreateCollider()
{
m_colliders.pushBack({});
return m_colliders.size() - 1;
}
Handle PhysicsComponent::AddCollider(IN Collider collider)
{
m_colliders.pushBack(collider);
return m_colliders.size() - 1;
}
PhysicsComponent::Collider &PhysicsComponent::GetCollider(IN Handle handle)
{
return m_colliders[handle];
}
VOID PhysicsComponent::Move(IN glm::vec2 direction)
{
IA_ASSERT(m_isDynamic);
const auto v = direction * m_movementSpeed;
m_velocity += v;
for(const auto& t: m_colliders)
{
if(!Physics::CanMove(m_physicsHandle, t, v))
return;
}
m_node->SetLocalPosition(m_node->GetLocalPosition() + v);
}
VOID PhysicsComponent::Jump(IN FLOAT32 force)
{
}
VOID PhysicsComponent::OnCollision(IN PhysicsComponent *other)
{
}
} // namespace ia::iae

View File

@ -1,66 +0,0 @@
// IAEngine: 2D Game Engine by IA
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IAEngine/Components/BoxCollider2D.hpp>
#include <IAEngine/Components/PhysicsBody2D.hpp>
#include <IAEngine/Nodes/Node.hpp>
#include <IAEngine/Physics/Physics.hpp>
namespace ia::iae
{
PhysicsBody2DComponent::PhysicsBody2DComponent(IN Node *node) : IComponent(node)
{
m_physicsHandle = Physics::AddBody(this);
}
VOID PhysicsBody2DComponent::Draw()
{
}
VOID PhysicsBody2DComponent::Update()
{
if (m_isDynamic)
m_node->SetLocalPosition(Physics::GetBodyPosition(m_physicsHandle));
if (!m_isRotationLocked)
m_node->SetLocalRotation(Physics::GetBodyRotation(m_physicsHandle));
}
VOID PhysicsBody2DComponent::SetVelocity(IN glm::vec2 v)
{
Physics::SetBodyVelocity(m_physicsHandle, v);
}
VOID PhysicsBody2DComponent::SetVelocityX(IN FLOAT32 v)
{
Physics::SetBodyVelocityX(m_physicsHandle, v);
}
VOID PhysicsBody2DComponent::SetVelocityY(IN FLOAT32 v)
{
Physics::SetBodyVelocityY(m_physicsHandle, v);
}
VOID PhysicsBody2DComponent::ApplyForce(IN glm::vec2 force)
{
Physics::ApplyBodyForce(m_physicsHandle, force);
}
VOID PhysicsBody2DComponent::Jump(IN FLOAT32 velocity)
{
if(m_isGrounded)
Physics::SetBodyVelocityY(m_physicsHandle, -velocity);
}
} // namespace ia::iae

View File

@ -36,6 +36,8 @@ namespace ia::iae
{
CONSTEXPR FLOAT32 GAME_UPDATE_INTERVAL = 1000.0f / 60.0f;
EXTERN BOOL g_physicsDebugDrawEnabled;
SDL_Event g_event{};
FLOAT32 g_updateTimer{};
BOOL g_shouldClose{false};
@ -132,11 +134,16 @@ namespace ia::iae
VOID Engine::UpdateGame()
{
if(Input::WasKeyPressed(Input::KEY_F6))
g_physicsDebugDrawEnabled = !g_physicsDebugDrawEnabled;
Physics::Update();
Audio::Update();
if B_LIKELY (g_activeScene)
g_activeScene->Update();
UI::Update();
Physics::Update();
}
VOID Engine::RenderGame()

View File

@ -72,9 +72,10 @@ namespace ia::iae
c->Disable();
}
VOID Node::AddChild(IN RefPtr<Node> node)
Node* Node::AddChild(IN RefPtr<Node> node)
{
m_children.pushBack(node);
return node.get();
}
VOID Node::AddComponent(IN RefPtr<IComponent> component)

View File

@ -16,174 +16,81 @@
#include <IAEngine/IAEngine.hpp>
#include <IAEngine/Physics/Physics.hpp>
#include <IAEngine/Rendering/DebugDraw.hpp>
#include <IAEngine/Rendering/Camera.hpp>
#include <IAEngine/Rendering/Renderer.hpp>
#include <IAEngine/Nodes/Node.hpp>
#include <box2d/box2d.h>
#include <map>
namespace ia::iae
{
struct Body
{
struct Collider
{
b2ShapeId ShapeId{};
BoxCollider2DComponent *ColliderComponent{};
};
BOOL g_physicsDebugDrawEnabled = false;
b2BodyId BodyId{.world0 = 0xFFFF};
PhysicsBody2DComponent *BodyComponent;
Vector<Collider> Colliders;
};
b2WorldId g_worldId{};
Vector<Body> g_bodies;
std::map<Handle, BoxCollider2DComponent*> g_shapeColliders;
INLINE Handle ShapeIdToHandle(IN b2ShapeId id)
{
return *reinterpret_cast<Handle*>(&id.index1);
}
Vector<PhysicsComponent *> g_physicsComponents;
VOID Physics::Initialize()
{
auto worldDef = b2DefaultWorldDef();
worldDef.gravity = b2Vec2{0.0f, 1000.0f};
g_worldId = b2CreateWorld(&worldDef);
}
VOID Physics::Terminate()
{
b2DestroyWorld(g_worldId);
}
VOID Physics::Update()
{
CONSTEXPR FLOAT32 TIME_STEP = 1.0f / 60.0f;
CONSTEXPR INT32 SUB_STEP_COUNT = 4;
b2World_Step(g_worldId, TIME_STEP, SUB_STEP_COUNT);
// Process contact events
const auto contactEvents = b2World_GetContactEvents(g_worldId);
for(INT32 i = 0; i < contactEvents.beginCount; i++)
{
const auto& shapeA = g_shapeColliders[ShapeIdToHandle(contactEvents.beginEvents[i].shapeIdA)];
const auto& shapeB = g_shapeColliders[ShapeIdToHandle(contactEvents.beginEvents[i].shapeIdB)];
shapeA->OnCollisionEnter(shapeB->GetNode());
shapeB->OnCollisionEnter(shapeA->GetNode());
}
for(INT32 i = 0; i < contactEvents.endCount; i++)
{
const auto& shapeA = g_shapeColliders[ShapeIdToHandle(contactEvents.endEvents[i].shapeIdA)];
const auto& shapeB = g_shapeColliders[ShapeIdToHandle(contactEvents.endEvents[i].shapeIdB)];
shapeA->OnCollisionExit(shapeB->GetNode());
shapeB->OnCollisionExit(shapeA->GetNode());
}
// Process sensor events
const auto sensorEvents = b2World_GetSensorEvents(g_worldId);
for(INT32 i = 0; i < sensorEvents.beginCount; i++)
{
const auto& shapeA = g_shapeColliders[ShapeIdToHandle(sensorEvents.beginEvents[i].sensorShapeId)];
const auto& shapeB = g_shapeColliders[ShapeIdToHandle(sensorEvents.beginEvents[i].visitorShapeId)];
shapeA->OnCollisionEnter(shapeB->GetNode());
shapeB->OnCollisionEnter(shapeA->GetNode());
}
for(INT32 i = 0; i < sensorEvents.endCount; i++)
{
const auto& shapeA = g_shapeColliders[ShapeIdToHandle(sensorEvents.endEvents[i].sensorShapeId)];
const auto& shapeB = g_shapeColliders[ShapeIdToHandle(sensorEvents.endEvents[i].visitorShapeId)];
shapeA->OnCollisionExit(shapeB->GetNode());
shapeB->OnCollisionExit(shapeA->GetNode());
}
}
VOID Physics::Bake()
VOID Physics::DebugDraw()
{
for (auto &b : g_bodies)
if(!g_physicsDebugDrawEnabled)
return;
for(const auto& t: g_physicsComponents)
{
const auto drawnSize = b.BodyComponent->GetNode()->DrawnSize();
const auto pos = b.BodyComponent->GetNode()->GetPosition();
auto bodyDef = b2DefaultBodyDef();
if (b.BodyComponent->IsDynamic())
bodyDef.type = b2_dynamicBody;
bodyDef.position = b2Vec2{pos.x + drawnSize.x/2.0f, pos.y + drawnSize.y/2.0f};
if (b.BodyId.world0 != 0xFFFF)
b2DestroyBody(b.BodyId);
b.BodyId = b2CreateBody(g_worldId, &bodyDef);
for (auto &c : b.Colliders)
for(const auto& c: t->Colliders())
{
const auto rect = c.ColliderComponent->Rect();
const auto halfW = rect.z/2.0f;
const auto halfH = rect.w/2.0f;
const auto box = b2MakeOffsetBox(halfW, halfH, {rect.x, rect.y}, b2MakeRot(0));
auto boxShapeDef = b2DefaultShapeDef();
boxShapeDef.density = 1.0f;
boxShapeDef.isSensor = c.ColliderComponent->IsTrigger();
boxShapeDef.enableContactEvents = c.ColliderComponent->CollisionsEnabled();
boxShapeDef.enableSensorEvents = boxShapeDef.enableContactEvents;
c.ShapeId = b2CreatePolygonShape(b.BodyId, &boxShapeDef, &box);
g_shapeColliders[ShapeIdToHandle(c.ShapeId)] = c.ColliderComponent;
auto color = glm::vec4{0.75f, 0.0f, 0.0f, 1.0f};
if(c.IsTrigger)
color = {0.25f, 0.45f, 0.75f, 0.75f};
DebugDraw::DrawRect(t->GetNode()->GetPosition() + c.Position - Renderer::GetCamera()->Position(), c.Size, color, 2.0f);
}
}
}
Handle Physics::AddBody(IN PhysicsBody2DComponent *body)
Handle Physics::RegisterComponent(IN PhysicsComponent *component)
{
g_bodies.pushBack(Body{
.BodyComponent = body,
});
return g_bodies.size() - 1;
g_physicsComponents.pushBack(component);
return g_physicsComponents.size() - 1;
}
Handle Physics::AddColliderToBody(IN Handle bodyHandle, IN BoxCollider2DComponent *collider)
BOOL Physics::CanMove(IN Handle handle, IN CONST PhysicsComponent::Collider &collider, IN glm::vec2 movement)
{
auto &b = g_bodies[bodyHandle];
b.Colliders.pushBack(Body::Collider{
.ColliderComponent = collider,
});
return b.Colliders.size() - 1;
}
const auto comp = g_physicsComponents[handle];
const auto pos = comp->GetNode()->GetPosition() + movement + collider.Position;
for (const auto &t : g_physicsComponents)
{
if (t == comp)
continue;
for (const auto &tc : t->Colliders())
{
const auto tPos = t->GetNode()->GetPosition() + tc.Position;
const auto xColliding = ((pos.x + collider.Size.x) >= tPos.x) && ((tPos.x + tc.Size.x) >= pos.x);
const auto yColliding = ((pos.y + collider.Size.y) >= tPos.y) && ((tPos.y + tc.Size.y) >= pos.y);
if (xColliding && yColliding)
{
// Collision callback
comp->OnCollision(t);
t->OnCollision(comp);
FLOAT32 Physics::GetBodyRotation(IN Handle handle)
{
const auto &b = g_bodies[handle];
return acosf(b2Body_GetRotation(b.BodyId).c);
}
glm::vec2 Physics::GetBodyPosition(IN Handle handle)
{
const auto &b = g_bodies[handle];
const auto drawnSize = b.BodyComponent->GetNode()->DrawnSize();
const auto v = b2Body_GetPosition(b.BodyId);
return {v.x - drawnSize.x/2.0f, v.y - drawnSize.y/2.0f};
}
VOID Physics::ApplyBodyForce(IN Handle handle, IN glm::vec2 force)
{
const auto &b = g_bodies[handle];
b2Body_ApplyForce(b.BodyId, {force.x, force.y}, b2Body_GetLocalCenterOfMass(b.BodyId), true);
}
VOID Physics::SetBodyVelocity(IN Handle handle, IN glm::vec2 v)
{
const auto &b = g_bodies[handle];
b2Body_SetLinearVelocity(b.BodyId, {v.x, v.y});
}
VOID Physics::SetBodyVelocityX(IN Handle handle, IN FLOAT32 v)
{
const auto &b = g_bodies[handle];
b2Body_SetLinearVelocity(b.BodyId, {v, b2Body_GetLinearVelocity(b.BodyId).y});
}
VOID Physics::SetBodyVelocityY(IN Handle handle, IN FLOAT32 v)
{
const auto &b = g_bodies[handle];
b2Body_SetLinearVelocity(b.BodyId, {b2Body_GetLinearVelocity(b.BodyId).x, v});
// Overlap block
if (tc.IsTrigger)
continue;
return false;
}
}
}
return true;
}
} // namespace ia::iae

View File

@ -0,0 +1,130 @@
// IAEngine: 2D Game Engine by IA
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IAEngine/IAEngine.hpp>
#include <IAEngine/Rendering/Camera.hpp>
#include <IAEngine/Rendering/DebugDraw.hpp>
#include <IAEngine/Rendering/GPUBuffer.hpp>
#include <IAEngine/Rendering/GPUTexture.hpp>
#include <IAEngine/Rendering/Pipeline/UnlitMesh.hpp>
#include <IAEngine/Rendering/Renderer.hpp>
#include <IAEngine/Physics/Physics.hpp>
#include <SDL3/SDL_gpu.h>
#include <backends/imgui_impl_sdl3.h>
#include <backends/imgui_impl_sdlgpu3.h>
namespace ia::iae
{
EXTERN SDL_Window *g_windowHandle;
EXTERN SDL_GPUDevice *g_gpuDevice;
ImGuiIO g_imGUIIO{};
struct UIWindow
{
PCCHAR Title{""};
glm::vec2 Position{};
glm::vec2 Size{};
std::function<VOID()> ContentDrawCallback{};
BOOL Visibility{true};
};
Vector<UIWindow> g_debugUIWindows;
VOID DebugDraw::Initailize()
{
const auto mainScale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
IMGUI_CHECKVERSION();
ImGui::CreateContext();
g_imGUIIO = ImGui::GetIO();
g_imGUIIO.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
g_imGUIIO.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
ImGui::StyleColorsClassic();
ImGuiStyle &style = ImGui::GetStyle();
style.ScaleAllSizes(mainScale);
style.FontScaleDpi = mainScale;
ImGui_ImplSDLGPU3_InitInfo initInfo{.Device = g_gpuDevice,
.ColorTargetFormat =
SDL_GetGPUSwapchainTextureFormat(g_gpuDevice, g_windowHandle),
.PresentMode = SDL_GPU_PRESENTMODE_VSYNC};
ImGui_ImplSDL3_InitForSDLGPU(g_windowHandle);
ImGui_ImplSDLGPU3_Init(&initInfo);
}
VOID DebugDraw::Terminate()
{
ImGui_ImplSDL3_Shutdown();
ImGui_ImplSDLGPU3_Shutdown();
ImGui::DestroyContext();
}
VOID DebugDraw::Draw()
{
for (const auto &w : g_debugUIWindows)
{
if (!w.Visibility)
continue;
ImGui::Begin(w.Title);
ImGui::SetWindowPos({w.Position.x, w.Position.y});
ImGui::SetWindowSize({w.Size.x, w.Size.y});
w.ContentDrawCallback();
ImGui::End();
}
Physics::DebugDraw();
}
VOID DebugDraw::DrawLine(IN CONST glm::vec2 &from, IN CONST glm::vec2 &to, IN CONST glm::vec4 &color,
IN FLOAT32 thickness)
{
const auto drawList = ImGui::GetForegroundDrawList();
drawList->AddLine(ImVec2(from.x, from.y), ImVec2(to.x, to.y), IM_COL32(color.x * 255, color.y * 255, color.z * 255, color.w * 255), thickness);
}
VOID DebugDraw::DrawRect(IN CONST glm::vec2 &position, IN CONST glm::vec2 &size, IN CONST glm::vec4 &color, IN FLOAT32 thickness)
{
const auto drawList = ImGui::GetForegroundDrawList();
drawList->AddRect(ImVec2(position.x, position.y), ImVec2(position.x + size.x, position.y + size.y), IM_COL32(color.x * 255, color.y * 255, color.z * 255, color.w * 255), 0.0f, 0, thickness);
}
VOID DebugDraw::DrawRectFilled(IN CONST glm::vec2 &position, IN CONST glm::vec2 &size, IN CONST glm::vec4 &color)
{
const auto drawList = ImGui::GetForegroundDrawList();
drawList->AddRectFilled(ImVec2(position.x, position.y), ImVec2(position.x + size.x, position.y + size.y), IM_COL32(color.x * 255, color.y * 255, color.z * 255, color.w * 255));
}
Handle DebugDraw::AddUIWindow(IN PCCHAR title, IN CONST glm::vec2 &position, IN CONST glm::vec2 &size,
IN std::function<VOID()> contentDrawCallback)
{
g_debugUIWindows.pushBack(UIWindow{title, position, size, contentDrawCallback});
return g_debugUIWindows.size() - 1;
}
VOID DebugDraw::SetUIWindowVisibility(IN Handle handle, IN BOOL visible)
{
g_debugUIWindows[handle].Visibility = visible;
}
VOID DebugDraw::ToggleUIWindowVisibility(IN Handle handle)
{
g_debugUIWindows[handle].Visibility = !g_debugUIWindows[handle].Visibility;
}
} // namespace ia::iae

View File

@ -0,0 +1,81 @@
// IAEngine: 2D Game Engine by IA
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IAEngine/Rendering/Pipeline/PostProcess.hpp>
#include <SDL3/SDL_gpu.h>
#include <EmbeddedShaders.hpp>
namespace ia::iae
{
EXTERN SDL_GPUDevice *g_gpuDevice;
EXTERN SDL_Window *g_windowHandle;
Pipeline_PostProcess::~Pipeline_PostProcess()
{
if (m_handle)
SDL_ReleaseGPUGraphicsPipeline(g_gpuDevice, (SDL_GPUGraphicsPipeline *) m_handle);
}
RefPtr<Pipeline_PostProcess> Pipeline_PostProcess::Create()
{
const auto res = MakeRefPtr<Pipeline_PostProcess>();
const auto vertexShader = LoadShaderFromMemory(ShaderStage::VERTEX, SHADER_SOURCE_POSTPROCESS_VERT,
sizeof(SHADER_SOURCE_POSTPROCESS_VERT), 0, 0, 0, 0);
const auto pixelShader = LoadShaderFromMemory(ShaderStage::PIXEL, SHADER_SOURCE_POSTPROCESS_FRAG,
sizeof(SHADER_SOURCE_POSTPROCESS_FRAG), 2, 0, 0, 0);
SDL_GPUColorTargetDescription colorTargets[] = {{
.format = SDL_GetGPUSwapchainTextureFormat(g_gpuDevice, g_windowHandle),
}};
SDL_GPUGraphicsPipelineCreateInfo createInfo = {
.vertex_shader = (SDL_GPUShader *) vertexShader,
.fragment_shader = (SDL_GPUShader *) pixelShader,
.vertex_input_state = SDL_GPUVertexInputState{.vertex_buffer_descriptions = nullptr,
.num_vertex_buffers = 0,
.vertex_attributes = nullptr,
.num_vertex_attributes = 0},
.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
.rasterizer_state = SDL_GPURasterizerState{.fill_mode = SDL_GPU_FILLMODE_FILL,
.cull_mode = SDL_GPU_CULLMODE_NONE,
.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE},
.target_info = {.color_target_descriptions = colorTargets,
.num_color_targets = sizeof(colorTargets) / sizeof(colorTargets[0]),
.has_depth_stencil_target = false},
};
SDL_GPUGraphicsPipeline *handle{};
if (!(handle = SDL_CreateGPUGraphicsPipeline(g_gpuDevice, &createInfo)))
{
IAE_LOG_ERROR("Failed to create a SDL graphics pipeline: ", SDL_GetError());
return nullptr;
}
UnloadShader(pixelShader);
UnloadShader(vertexShader);
res->m_handle = (Handle) handle;
return res;
}
VOID Pipeline_PostProcess::Bind(IN Handle renderPassHandle)
{
SDL_BindGPUGraphicsPipeline((SDL_GPURenderPass *) renderPassHandle, (SDL_GPUGraphicsPipeline *) m_handle);
}
} // namespace ia::iae

View File

@ -16,8 +16,10 @@
#include <IAEngine/IAEngine.hpp>
#include <IAEngine/Rendering/Camera.hpp>
#include <IAEngine/Rendering/DebugDraw.hpp>
#include <IAEngine/Rendering/GPUBuffer.hpp>
#include <IAEngine/Rendering/GPUTexture.hpp>
#include <IAEngine/Rendering/Pipeline/PostProcess.hpp>
#include <IAEngine/Rendering/Pipeline/UnlitMesh.hpp>
#include <IAEngine/Rendering/Renderer.hpp>
@ -45,7 +47,7 @@ namespace ia::iae
SDL_Rect Scissor{0, 0, 0, 0};
};
Vector<Mesh*> g_meshes{};
Vector<Mesh *> g_meshes{};
RenderState g_renderState{};
} // namespace ia::iae
@ -55,21 +57,21 @@ namespace ia::iae
INT32 Renderer::s_width{};
INT32 Renderer::s_height{};
Vector<Renderer::DebugUIWindow> Renderer::s_debugUIWindows;
SDL_GPUDevice *g_gpuDevice{};
ImDrawData *g_imDrawData{};
// Render State
SDL_GPUCommandBuffer *g_cmdBuffer{};
SDL_GPURenderPass *g_renderPass{};
SDL_GPUTexture *g_swpChainTexture{};
SDL_GPUTexture *g_depthBufferTexture{};
// ImGUI State
ImGuiIO g_imGUIIO{};
ImDrawData *g_imDrawData{};
SDL_GPUTexture *g_sceneDrawBufferTexture{};
SDL_GPUTexture *g_debugDrawBufferTexture{};
RefPtr<Pipeline_UnlitMesh> g_pipelineUnlitMesh;
RefPtr<Pipeline_PostProcess> g_pipelinePostProcess;
Camera2D g_camera{};
@ -86,6 +88,9 @@ namespace ia::iae
Texture g_whiteFillTexture{};
Texture g_whiteStrokeTexture{};
SDL_GPUColorTargetInfo colorTargetInfo = {0};
SDL_GPUDepthStencilTargetInfo depthStencilTargetInfo = {0};
BOOL Renderer::Initialize()
{
SDL_GetWindowSizeInPixels(g_windowHandle, &s_width, &s_height);
@ -104,30 +109,24 @@ namespace ia::iae
SDL_SetGPUSwapchainParameters(g_gpuDevice, g_windowHandle, SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
SDL_GPU_PRESENTMODE_VSYNC);
const auto mainScale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
IMGUI_CHECKVERSION();
ImGui::CreateContext();
g_imGUIIO = ImGui::GetIO();
g_imGUIIO.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
g_imGUIIO.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
ImGui::StyleColorsClassic();
ImGuiStyle &style = ImGui::GetStyle();
style.ScaleAllSizes(mainScale);
style.FontScaleDpi = mainScale;
ImGui_ImplSDLGPU3_InitInfo initInfo{.Device = g_gpuDevice,
.ColorTargetFormat =
SDL_GetGPUSwapchainTextureFormat(g_gpuDevice, g_windowHandle),
.PresentMode = SDL_GPU_PRESENTMODE_VSYNC};
ImGui_ImplSDL3_InitForSDLGPU(g_windowHandle);
ImGui_ImplSDLGPU3_Init(&initInfo);
DebugDraw::Initailize();
if (!GPUBuffer::InitializeStagingBuffer())
return false;
{
SDL_GPUTextureCreateInfo createInfo{.type = SDL_GPU_TEXTURETYPE_2D,
.format = SDL_GetGPUSwapchainTextureFormat(g_gpuDevice, g_windowHandle),
.usage =
SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER,
.width = (UINT32) s_width,
.height = (UINT32) s_height,
.layer_count_or_depth = 1,
.num_levels = 1,
.sample_count = SDL_GPU_SAMPLECOUNT_1};
g_sceneDrawBufferTexture = SDL_CreateGPUTexture(g_gpuDevice, &createInfo);
g_debugDrawBufferTexture = SDL_CreateGPUTexture(g_gpuDevice, &createInfo);
}
{
SDL_GPUTextureCreateInfo createInfo{.type = SDL_GPU_TEXTURETYPE_2D,
.format = SDL_GPU_TEXTUREFORMAT_D16_UNORM,
@ -143,6 +142,7 @@ namespace ia::iae
GPUTexture::Initialize();
g_pipelineUnlitMesh = Pipeline_UnlitMesh::Create();
g_pipelinePostProcess = Pipeline_PostProcess::Create();
matProjection = glm::orthoLH(0.0f, (FLOAT32) s_width, (FLOAT32) s_height, 0.0f, -2097152.0f, 2097152.0f);
@ -182,6 +182,18 @@ namespace ia::iae
},
{0, 1, 2, 2, 3, 0});
colorTargetInfo.clear_color = SDL_FColor{0.33f, 0.33f, 0.33f, 1.0f};
colorTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR;
colorTargetInfo.store_op = SDL_GPU_STOREOP_STORE;
depthStencilTargetInfo.cycle = true;
depthStencilTargetInfo.clear_depth = 0;
depthStencilTargetInfo.clear_stencil = 0;
depthStencilTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR;
depthStencilTargetInfo.store_op = SDL_GPU_STOREOP_STORE;
depthStencilTargetInfo.stencil_load_op = SDL_GPU_LOADOP_CLEAR;
depthStencilTargetInfo.stencil_store_op = SDL_GPU_STOREOP_STORE;
return true;
}
@ -197,77 +209,32 @@ namespace ia::iae
}
g_pipelineUnlitMesh.reset();
g_pipelinePostProcess.reset();
GPUTexture::Terminate();
GPUBuffer::TerminateStagingBuffer();
SDL_ReleaseGPUTexture(g_gpuDevice, g_depthBufferTexture);
SDL_ReleaseGPUTexture(g_gpuDevice, g_sceneDrawBufferTexture);
SDL_ReleaseGPUTexture(g_gpuDevice, g_debugDrawBufferTexture);
ImGui_ImplSDL3_Shutdown();
ImGui_ImplSDLGPU3_Shutdown();
ImGui::DestroyContext();
DebugDraw::Terminate();
SDL_ReleaseWindowFromGPUDevice(g_gpuDevice, g_windowHandle);
SDL_DestroyGPUDevice(g_gpuDevice);
}
VOID Renderer::AddDebugUIWindow(IN PCCHAR title, IN CONST glm::vec2 &position, IN CONST glm::vec2 &size,
IN std::function<VOID()> contentDrawCallback)
{
s_debugUIWindows.pushBack(DebugUIWindow{title, position, size, contentDrawCallback});
}
VOID Renderer::BeginFrame()
{
ImGui_ImplSDLGPU3_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
for (const auto &w : s_debugUIWindows)
{
ImGui::Begin(w.Title);
ImGui::SetWindowPos({w.Position.x, w.Position.y});
ImGui::SetWindowSize({w.Size.x, w.Size.y});
w.ContentDrawCallback();
ImGui::End();
}
ImGui::Render();
if (!(g_cmdBuffer = SDL_AcquireGPUCommandBuffer(g_gpuDevice)))
{
IAE_LOG_ERROR("Couldn't acquire SDL3 GPU command buffer: ", SDL_GetError());
return;
}
if (!SDL_WaitAndAcquireGPUSwapchainTexture(g_cmdBuffer, g_windowHandle, &g_swpChainTexture, (PUINT32) &s_width,
(PUINT32) &s_height))
{
IAE_LOG_ERROR("Couldn't acquire SDL3 GPU Swapchain texture: ", SDL_GetError());
return;
}
if (!g_swpChainTexture)
return;
g_imDrawData = ImGui::GetDrawData();
ImGui_ImplSDLGPU3_PrepareDrawData(g_imDrawData, g_cmdBuffer);
SDL_GPUColorTargetInfo colorTargetInfo = {0};
colorTargetInfo.texture = g_swpChainTexture;
colorTargetInfo.clear_color = SDL_FColor{0.33f, 0.33f, 0.33f, 1.0f};
colorTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR;
colorTargetInfo.store_op = SDL_GPU_STOREOP_STORE;
SDL_GPUDepthStencilTargetInfo depthStencilTargetInfo = {0};
colorTargetInfo.texture = g_sceneDrawBufferTexture;
depthStencilTargetInfo.texture = g_depthBufferTexture;
depthStencilTargetInfo.cycle = true;
depthStencilTargetInfo.clear_depth = 0;
depthStencilTargetInfo.clear_stencil = 0;
depthStencilTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR;
depthStencilTargetInfo.store_op = SDL_GPU_STOREOP_STORE;
depthStencilTargetInfo.stencil_load_op = SDL_GPU_LOADOP_CLEAR;
depthStencilTargetInfo.stencil_store_op = SDL_GPU_STOREOP_STORE;
g_renderPass = SDL_BeginGPURenderPass(g_cmdBuffer, &colorTargetInfo, 1, &depthStencilTargetInfo);
@ -279,11 +246,48 @@ namespace ia::iae
VOID Renderer::EndFrame()
{
SDL_EndGPURenderPass(g_renderPass);
ImGui_ImplSDLGPU3_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
DebugDraw::Draw();
ImGui::Render();
g_imDrawData = ImGui::GetDrawData();
ImGui_ImplSDLGPU3_PrepareDrawData(g_imDrawData, g_cmdBuffer);
colorTargetInfo.texture = g_debugDrawBufferTexture;
g_renderPass = SDL_BeginGPURenderPass(g_cmdBuffer, &colorTargetInfo, 1, nullptr);
ImGui_ImplSDLGPU3_RenderDrawData(g_imDrawData, g_cmdBuffer, g_renderPass);
SDL_EndGPURenderPass(g_renderPass);
if (!SDL_WaitAndAcquireGPUSwapchainTexture(g_cmdBuffer, g_windowHandle, &g_swpChainTexture, (PUINT32) &s_width,
(PUINT32) &s_height))
{
IAE_LOG_ERROR("Couldn't acquire SDL3 GPU Swapchain texture: ", SDL_GetError());
return;
}
if (!g_swpChainTexture)
return;
ImGui_ImplSDLGPU3_RenderDrawData(g_imDrawData, g_cmdBuffer, g_renderPass);
colorTargetInfo.texture = g_swpChainTexture;
g_renderPass = SDL_BeginGPURenderPass(g_cmdBuffer, &colorTargetInfo, 1, nullptr);
g_pipelinePostProcess->Bind((Handle) g_renderPass);
SDL_GPUTextureSamplerBinding textureBindings[2] = {
{
.texture = g_sceneDrawBufferTexture,
.sampler = (SDL_GPUSampler*)GPUTexture::GetDefaultSampler()
},
{
.texture = g_debugDrawBufferTexture,
.sampler = (SDL_GPUSampler*)GPUTexture::GetDefaultSampler()
},
};
SDL_BindGPUFragmentSamplers(g_renderPass, 0, textureBindings, 2);
SDL_DrawGPUPrimitives(g_renderPass, 6, 1, 0, 0);
SDL_EndGPURenderPass(g_renderPass);
SDL_SubmitGPUCommandBuffer(g_cmdBuffer);
}
@ -336,7 +340,7 @@ namespace ia::iae
Handle Renderer::CreateMesh(IN CONST Vector<MeshVertex> &vertices, IN CONST Vector<INT32> &indices)
{
const auto mesh = CreateUnmanagedMesh(vertices, indices);
g_meshes.pushBack((Mesh*)mesh);
g_meshes.pushBack((Mesh *) mesh);
return mesh;
}
@ -344,16 +348,16 @@ namespace ia::iae
{
const auto mesh = new Mesh();
mesh->VertexBuffer = GPUBuffer::Create(GPUBuffer::Usage::VERTEX, vertices.data(),
static_cast<UINT32>(vertices.size() * sizeof(vertices[0])));
static_cast<UINT32>(vertices.size() * sizeof(vertices[0])));
mesh->IndexBuffer = GPUBuffer::Create(GPUBuffer::Usage::INDEX, indices.data(),
static_cast<UINT32>(indices.size() * sizeof(indices[0])));
static_cast<UINT32>(indices.size() * sizeof(indices[0])));
mesh->IndexCount = static_cast<UINT32>(indices.size());
return (Handle)mesh;
return (Handle) mesh;
}
VOID Renderer::DestroyUnmanagedMesh(IN Handle handle)
{
delete ((Mesh*)handle);
delete ((Mesh *) handle);
}
VOID Renderer::Draw(IN Handle meshHandle, IN Handle textureHandle, IN CONST glm::vec2 &position,
@ -402,7 +406,7 @@ namespace ia::iae
else
SDL_SetGPUScissor(g_renderPass, &g_defaultScissor);
const auto mesh = (Mesh*)meshHandle;
const auto mesh = (Mesh *) meshHandle;
SDL_GPUBufferBinding bufferBindings[] = {
{.buffer = (SDL_GPUBuffer *) mesh->VertexBuffer->GetHandle(), .offset = 0},
{.buffer = (SDL_GPUBuffer *) mesh->IndexBuffer->GetHandle(), .offset = 0}};

View File

@ -27,7 +27,6 @@ namespace ia::iae
VOID Scene::OnActivate()
{
Physics::Bake();
}
VOID Scene::OnDeactivate()

View File

@ -0,0 +1,23 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec2 inTexCoord;
layout(set = 2, binding = 0) uniform sampler2D sceneDrawTexture;
layout(set = 2, binding = 1) uniform sampler2D debugDrawTexture;
layout(location = 0) out vec4 outColor;
vec4 overlay(vec4 background, vec4 foreground) {
return mix(
2.0 * background * foreground,
1.0 - 2.0 * (1.0 - background) * (1.0 - foreground),
step(0.5, background)
);
}
void main()
{
vec2 uv = vec2(inTexCoord.x, 1.0 - inTexCoord.y);
outColor = overlay(texture(debugDrawTexture, uv), texture(sceneDrawTexture, uv));
}

View File

@ -0,0 +1,10 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) out vec2 outTexCoord;
void main()
{
outTexCoord = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
gl_Position = vec4(outTexCoord * 2.0f - 1.0f, 0.0f, 1.0f);
}

File diff suppressed because one or more lines are too long

View File

@ -63,12 +63,14 @@ namespace ia::iae
public:
STATIC VOID Initialize();
STATIC VOID Terminate();
STATIC Sound CreateSound(IN PCUINT8 audioData, IN SIZE_T audioDataSize);
STATIC VOID Update();
STATIC INT64 CreateTrack();
STATIC VOID DestroyTrack(IN INT64 trackHandle);
STATIC Sound CreateSound(IN PCUINT8 audioData, IN SIZE_T audioDataSize);
STATIC VOID QueueTrackData(IN INT64 trackHandle, IN INT64 dataHandle);
STATIC VOID PlayTrack(IN INT64 trackHandle, IN INT32 loopTimes, IN TimePeriod loopDelay);
STATIC VOID ClearTrack(IN INT64 trackHandle);

View File

@ -1,85 +0,0 @@
// IAEngine: 2D Game Engine by IA
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <IAEngine/Components/Component.hpp>
namespace ia::iae
{
class Physics;
class PhysicsBody2DComponent;
class BoxCollider2DComponent : public IComponent
{
public:
BoxCollider2DComponent(IN Node *node);
VOID OnCollisionEnter(IN Node *other);
VOID OnCollisionExit(IN Node *other);
public:
VOID SetCollisionEnterCallback(IN std::function<VOID(IN Node *)> callback)
{
m_collisionEnterCallback = callback;
}
VOID SetCollisionExitCallback(IN std::function<VOID(IN Node *)> callback)
{
m_collisionExitCallback = callback;
}
public:
BOOL &IsDebugDrawEnabled()
{
return m_isDebugDrawEnabled;
}
BOOL &CollisionsEnabled()
{
return m_collisionsEnabled;
}
BOOL &IsTrigger()
{
return m_isTrigger;
}
glm::vec4 &Rect()
{
return m_rect;
}
Handle PhysicsHandle() CONST
{
return m_physicsHandle;
}
public:
VOID Draw();
VOID Update();
private:
glm::vec4 m_rect{};
BOOL m_isTrigger{false};
BOOL m_isDebugDrawEnabled{false};
BOOL m_collisionsEnabled{true};
Handle m_physicsHandle{INVALID_HANDLE};
PhysicsBody2DComponent *m_body{};
std::function<VOID(IN Node *)> m_collisionEnterCallback{};
std::function<VOID(IN Node *)> m_collisionExitCallback{};
};
} // namespace ia::iae

View File

@ -20,37 +20,47 @@
namespace ia::iae
{
class PhysicsBody2DComponent : public IComponent
class PhysicsComponent : public IComponent
{
public:
PhysicsBody2DComponent(IN Node *node);
VOID SetVelocity(IN glm::vec2 v);
VOID SetVelocityX(IN FLOAT32 v);
VOID SetVelocityY(IN FLOAT32 v);
VOID ApplyForce(IN glm::vec2 force);
VOID Jump(IN FLOAT32 velocity);
struct Collider
{
BOOL IsTrigger{false};
glm::vec2 Position{};
glm::vec2 Size{};
};
public:
BOOL &IsGrounded()
{
return m_isGrounded;
}
PhysicsComponent(IN Node *node);
~PhysicsComponent();
Handle CreateCollider();
Handle AddCollider(IN Collider collider);
Collider &GetCollider(IN Handle handle);
VOID Move(IN glm::vec2 direction);
VOID Jump(IN FLOAT32 force);
public:
BOOL &IsDynamic()
{
return m_isDynamic;
}
BOOL &IsRotationLocked()
FLOAT32 &MovementSpeed()
{
return m_isRotationLocked;
return m_movementSpeed;
}
Handle PhysicsHandle() CONST
Vector<Collider> &Colliders()
{
return m_physicsHandle;
return m_colliders;
}
CONST glm::vec2& GetVelocity() CONST
{
return m_velocity;
}
public:
@ -58,9 +68,14 @@ namespace ia::iae
VOID Update();
private:
BOOL m_isDynamic{};
BOOL m_isGrounded{};
BOOL m_isRotationLocked{true};
glm::vec2 m_velocity{};
BOOL m_isDynamic{false};
Vector<Collider> m_colliders;
FLOAT32 m_movementSpeed{1.0f};
Handle m_physicsHandle{INVALID_HANDLE};
VOID OnCollision(IN PhysicsComponent *other);
friend class Physics;
};
} // namespace ia::iae

View File

@ -255,8 +255,8 @@ namespace ia::iae
enum class DirectionalInput: UINT8
{
NONE = 0,
DOWN,
NONE = 255,
DOWN = 0,
DOWN_LEFT,
LEFT,
UP_LEFT,

View File

@ -39,7 +39,7 @@ namespace ia::iae
VIRTUAL VOID Disable();
public:
VOID AddChild(IN RefPtr<Node> node);
Node* AddChild(IN RefPtr<Node> node);
template<typename _component_type> _component_type *AddComponent();

View File

@ -23,6 +23,30 @@ namespace ia::iae
template<typename _node_type> class Transform
{
public:
VOID Translate(IN glm::vec2 v)
{
m_local.Position += v;
RecalculatePosition();
}
VOID Scale(IN FLOAT32 v)
{
m_local.Scale *= v;
RecalculateScale();
}
VOID Scale(IN glm::vec2 v)
{
m_local.Scale *= v;
RecalculateScale();
}
VOID Rotate(IN FLOAT32 v)
{
m_local.Rotation += v;
RecalculateRotation();
}
VOID SetLocalSortIndex(IN INT16 index)
{
m_local.SortIndex = index;

View File

@ -16,29 +16,26 @@
#pragma once
#include <IAEngine/Components/BoxCollider2D.hpp>
#include <IAEngine/Components/PhysicsBody2D.hpp>
#include <IAEngine/Physics/Physics.hpp>
#include <IAEngine/Components/Physics.hpp>
namespace ia::iae
{
class Physics
{
public:
STATIC Handle RegisterComponent(IN PhysicsComponent* component);
STATIC BOOL CanMove(IN Handle handle, IN CONST PhysicsComponent::Collider& collider, IN glm::vec2 movement);
private:
STATIC VOID Initialize();
STATIC VOID Terminate();
STATIC VOID Bake();
STATIC VOID Update();
STATIC VOID DebugDraw();
STATIC Handle AddBody(IN PhysicsBody2DComponent* body);
STATIC Handle AddColliderToBody(IN Handle bodyHandle, IN BoxCollider2DComponent* collider);
STATIC FLOAT32 GetBodyRotation(IN Handle handle);
STATIC glm::vec2 GetBodyPosition(IN Handle handle);
STATIC VOID SetBodyVelocity(IN Handle handle, IN glm::vec2 v);
STATIC VOID SetBodyVelocityX(IN Handle handle, IN FLOAT32 v);
STATIC VOID SetBodyVelocityY(IN Handle handle, IN FLOAT32 v);
STATIC VOID ApplyBodyForce(IN Handle handle, IN glm::vec2 force);
friend class Engine;
friend class DebugDraw;
};
}

View File

@ -0,0 +1,46 @@
// IAEngine: 2D Game Engine by IA
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <IAEngine/Rendering/Types.hpp>
namespace ia::iae
{
class DebugDraw
{
public:
STATIC Handle AddUIWindow(IN PCCHAR title, IN CONST glm::vec2 &position, IN CONST glm::vec2 &size,
IN std::function<VOID()> contentDrawCallback);
STATIC VOID ToggleUIWindowVisibility(IN Handle handle);
STATIC VOID SetUIWindowVisibility(IN Handle handle, IN BOOL visible);
public:
STATIC VOID DrawLine(IN CONST glm::vec2 &from, IN CONST glm::vec2 &to, IN CONST glm::vec4 &color,
IN FLOAT32 thickness = 1.0f);
STATIC VOID DrawRect(IN CONST glm::vec2 &position, IN CONST glm::vec2 &size, IN CONST glm::vec4 &color, IN FLOAT32 thickness = 1.0f);
STATIC VOID DrawRectFilled(IN CONST glm::vec2 &position, IN CONST glm::vec2 &size, IN CONST glm::vec4 &color);
private:
STATIC VOID Initailize();
STATIC VOID Terminate();
STATIC VOID Draw();
friend class Renderer;
};
} // namespace ia::iae

View File

@ -0,0 +1,35 @@
// IAEngine: 2D Game Engine by IA
// Copyright (C) 2025 IAS (ias@iasoft.dev)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <IAEngine/Rendering/Pipeline/Pipeline.hpp>
namespace ia::iae
{
class Pipeline_PostProcess: public IPipeline
{
public:
~Pipeline_PostProcess();
STATIC RefPtr<Pipeline_PostProcess> Create();
VOID Bind(IN Handle renderPassHandle);
private:
Handle m_handle{INVALID_HANDLE};
};
}

View File

@ -24,14 +24,6 @@ namespace ia::iae
class Renderer
{
struct DebugUIWindow
{
PCCHAR Title{""};
glm::vec2 Position{};
glm::vec2 Size{};
std::function<VOID()> ContentDrawCallback{};
};
public:
STATIC CONSTEXPR UINT8 MAX_LAYER_INDEX = 255;
@ -39,10 +31,6 @@ namespace ia::iae
STATIC BOOL Initialize();
STATIC VOID Terminate();
public:
STATIC VOID AddDebugUIWindow(IN PCCHAR title, IN CONST glm::vec2 &position, IN CONST glm::vec2 &size,
IN std::function<VOID()> contentDrawCallback);
public:
STATIC VOID BeginFrame();
STATIC VOID EndFrame();
@ -82,6 +70,5 @@ namespace ia::iae
private:
STATIC INT32 s_width;
STATIC INT32 s_height;
STATIC Vector<DebugUIWindow> s_debugUIWindows;
};
} // namespace ia::iae

View File

@ -34,7 +34,9 @@ SHADER_SOURCE_PATH = "Src/IAEngine/imp/glsl"
SHADER_SOURCE_FILES = [
"UnlitMesh/UnlitMesh.vert",
"UnlitMesh/UnlitMesh.frag"
"UnlitMesh/UnlitMesh.frag",
"PostProcessing/PostProcess.vert",
"PostProcessing/PostProcess.frag"
]
def file_to_source_array(arrayName, filePath):

View File

@ -54,7 +54,3 @@ add_library(Freetype::Freetype ALIAS freetype)
# -----------------------------------------------
add_subdirectory(RmlUI/)
# -----------------------------------------------
# Box2D
# -----------------------------------------------
add_subdirectory(box2d/)

1
Vendor/box2d vendored

Submodule Vendor/box2d deleted from f86d1827eb