添加简单物理模拟模式功能

This commit is contained in:
2025-06-24 22:08:22 +08:00
parent a32ce57503
commit fdb1ada6cd
7 changed files with 203 additions and 47 deletions

View File

@ -3,6 +3,7 @@
#ifdef HZ_PLATFORM_WINDOWS #ifdef HZ_PLATFORM_WINDOWS
#include <SDL3/SDL_main.h>
extern Hazel::Application* Hazel::CreateApplication(); extern Hazel::Application* Hazel::CreateApplication();
@ -22,6 +23,8 @@ int main(int, char**) {
HZ_PROFILE_BEGIN_SESSION("End", "End.json"); HZ_PROFILE_BEGIN_SESSION("End", "End.json");
delete app; delete app;
HZ_PROFILE_END_SESSION(); HZ_PROFILE_END_SESSION();
return 0;
} }
#endif // HZ_PLATFORM_WINDOWS #endif // HZ_PLATFORM_WINDOWS

View File

@ -44,7 +44,7 @@ namespace Hazel
ProjectionType GetProjectionType() const { return m_ProjectionType; } ProjectionType GetProjectionType() const { return m_ProjectionType; }
void SetProjectionType(const ProjectionType type) { m_ProjectionType = type; } void SetProjectionType(const ProjectionType type) { m_ProjectionType = type; RecalculateProjection(); }
private: private:
void RecalculateProjection(); void RecalculateProjection();
private: private:

View File

@ -37,6 +37,7 @@ namespace Hazel
Scene::~Scene() Scene::~Scene()
{ {
delete m_PhysicsWorld;
} }
template<typename Component> template<typename Component>
@ -123,11 +124,12 @@ namespace Hazel
void Scene::OnRuntimeStart()
void Scene::OnPhysics2DStart()
{ {
b2WorldDef worldDef = b2DefaultWorldDef(); b2WorldDef worldDef = b2DefaultWorldDef();
worldDef.gravity = b2Vec2(0.0f, -9.81f); worldDef.gravity = b2Vec2(0.0f, -9.81f);
m_PhysicsWorld = b2CreateWorld( &worldDef ); m_PhysicsWorld = new b2WorldId(b2CreateWorld( &worldDef ));
auto view = m_Registry.view<RigidBody2DComponent>(); auto view = m_Registry.view<RigidBody2DComponent>();
for (auto e : view) for (auto e : view)
@ -144,7 +146,7 @@ namespace Hazel
// bodyDef.fixedRotation = true; // bodyDef.fixedRotation = true;
b2BodyId body = b2CreateBody(m_PhysicsWorld, &bodyDef); b2BodyId body = b2CreateBody(*m_PhysicsWorld, &bodyDef);
rb2D.RuntimeBody = body; rb2D.RuntimeBody = body;
if (entity.HasComponent<BoxCollider2DComponent>()) if (entity.HasComponent<BoxCollider2DComponent>())
@ -169,8 +171,8 @@ namespace Hazel
auto& cc2d = entity.GetComponent<CircleCollider2DComponent>(); auto& cc2d = entity.GetComponent<CircleCollider2DComponent>();
b2Circle circle; b2Circle circle;
circle.center.x = cc2d.Offset.x; circle.center.x = transform.Scale.x * cc2d.Offset.x;
circle.center.y = cc2d.Offset.y; circle.center.y = transform.Scale.y * cc2d.Offset.y;
circle.radius = transform.Scale.x * cc2d.Radius; circle.radius = transform.Scale.x * cc2d.Radius;
b2ShapeDef shapedef = b2DefaultShapeDef(); b2ShapeDef shapedef = b2DefaultShapeDef();
@ -184,10 +186,58 @@ namespace Hazel
} }
} }
void Scene::OnPhysics2DStop()
{
b2DestroyWorld( *m_PhysicsWorld );
m_PhysicsWorld = nullptr;
}
void Scene::RenderScene(const EditorCamera& camera)
{
Renderer2D::BeginScene(camera);
// Draw Sprites
auto group = m_Registry.group<TransformComponent>(entt::get<SpriteRendererComponent>);
for (auto entity : group)
{
auto [transform, sprite] = group.get<TransformComponent, SpriteRendererComponent>(entity);
// Renderer2D::DrawQuad(transform.GetTransform(), sprite.Color);
Renderer2D::DrawSprite(transform.GetTransform(), sprite, (int)entity);
}
// Draw Circles
auto view = m_Registry.view<TransformComponent, CircleRendererComponent>();
for (auto entity : view)
{
auto [transform, circle] = view.get<TransformComponent, CircleRendererComponent>(entity);
// Renderer2D::DrawQuad(transform.GetTransform(), sprite.Color);
Renderer2D::DrawCircle(transform.GetTransform(), circle.Color, circle.Thickness, circle.Fade, (int)entity);
}
Renderer2D::EndScene();
}
void Scene::OnRuntimeStart()
{
OnPhysics2DStart();
}
void Scene::OnSimulationStart()
{
OnPhysics2DStart();
}
void Scene::OnSimulationStop()
{
OnPhysics2DStop();
}
void Scene::OnRuntimeStop() void Scene::OnRuntimeStop()
{ {
OnPhysics2DStop();
b2DestroyWorld( m_PhysicsWorld );
} }
void Scene::OnUpdateRuntime(TimeStep& ts) void Scene::OnUpdateRuntime(TimeStep& ts)
@ -212,10 +262,8 @@ namespace Hazel
// Physics // Physics
{ {
const int32_t velocityIteration = 6;
const int32_t positionIteration = 2;
b2World_Step(m_PhysicsWorld, ts, 4); b2World_Step(*m_PhysicsWorld, ts, 4);
auto view = m_Registry.view<RigidBody2DComponent>(); auto view = m_Registry.view<RigidBody2DComponent>();
for (auto e : view) for (auto e : view)
@ -280,32 +328,38 @@ namespace Hazel
} }
} }
void Scene::OnUpdateSimulation(TimeStep& ts, const EditorCamera& camera)
{
// Physics
{
b2World_Step(*m_PhysicsWorld, ts, 4);
auto view = m_Registry.view<RigidBody2DComponent>();
for (auto e : view)
{
Entity entity = {e, this};
auto& transform = entity.GetComponent<TransformComponent>();
auto& rb2D = entity.GetComponent<RigidBody2DComponent>();
b2BodyId &body = rb2D.RuntimeBody;
const auto& position = b2Body_GetPosition(body);
transform.Translation.x = position.x;
transform.Translation.y = position.y;
const float angle = b2Rot_GetAngle(b2Body_GetRotation(body));
transform.Rotation.z = angle;
}
}
// Render
RenderScene(camera);
}
void Scene::OnUpdateEditor(TimeStep & ts, const EditorCamera & camera) void Scene::OnUpdateEditor(TimeStep & ts, const EditorCamera & camera)
{ {
Renderer2D::BeginScene(camera); // Render
RenderScene(camera);
// Draw Sprites
auto group = m_Registry.group<TransformComponent>(entt::get<SpriteRendererComponent>);
for (auto entity : group)
{
auto [transform, sprite] = group.get<TransformComponent, SpriteRendererComponent>(entity);
// Renderer2D::DrawQuad(transform.GetTransform(), sprite.Color);
Renderer2D::DrawSprite(transform.GetTransform(), sprite, (int)entity);
}
// Draw Circles
auto view = m_Registry.view<TransformComponent, CircleRendererComponent>();
for (auto entity : view)
{
auto [transform, circle] = view.get<TransformComponent, CircleRendererComponent>(entity);
// Renderer2D::DrawQuad(transform.GetTransform(), sprite.Color);
Renderer2D::DrawCircle(transform.GetTransform(), circle.Color, circle.Thickness, circle.Fade, (int)entity);
}
Renderer2D::EndScene();
} }
void Scene::OnViewportResize(uint32_t width, uint32_t height) void Scene::OnViewportResize(uint32_t width, uint32_t height)

View File

@ -34,7 +34,11 @@ namespace Hazel
void OnRuntimeStart(); void OnRuntimeStart();
void OnRuntimeStop(); void OnRuntimeStop();
void OnSimulationStart();
void OnSimulationStop();
void OnUpdateRuntime(TimeStep& ts); void OnUpdateRuntime(TimeStep& ts);
void OnUpdateSimulation(TimeStep& ts, const EditorCamera& camera);
void OnUpdateEditor(TimeStep& ts, const EditorCamera& camera); void OnUpdateEditor(TimeStep& ts, const EditorCamera& camera);
void OnViewportResize(uint32_t width, uint32_t height); void OnViewportResize(uint32_t width, uint32_t height);
@ -49,6 +53,12 @@ namespace Hazel
} }
private: private:
void OnPhysics2DStart();
void OnPhysics2DStop();
void RenderScene(const EditorCamera& camera);
template<typename T> template<typename T>
void OnComponentAdded(Entity entity, T& component); void OnComponentAdded(Entity entity, T& component);
@ -56,7 +66,7 @@ namespace Hazel
entt::registry m_Registry; entt::registry m_Registry;
uint32_t m_ViewportWidth = 0, m_ViewportHeight = 0; uint32_t m_ViewportWidth = 0, m_ViewportHeight = 0;
b2WorldId m_PhysicsWorld; b2WorldId* m_PhysicsWorld = nullptr;
friend class Entity; friend class Entity;
friend class SceneSerializer; friend class SceneSerializer;

View File

@ -21,6 +21,6 @@ file(GLOB_RECURSE SOURCES
src/logo/logo.rc src/logo/logo.rc
) )
add_executable(${PROJECT_NAME} ${SOURCES}) add_executable(${PROJECT_NAME} WIN32 ${SOURCES})
target_link_libraries(${PROJECT_NAME} PRIVATE Hazel) target_link_libraries(${PROJECT_NAME} PRIVATE Hazel)

View File

@ -38,8 +38,9 @@ namespace Hazel
m_LogoTexture = Texture2D::Create("assets/textures/iceLogo.png"); m_LogoTexture = Texture2D::Create("assets/textures/iceLogo.png");
m_CheckerBoardTexture = Texture2D::Create("assets/textures/Checkerboard.png"); m_CheckerBoardTexture = Texture2D::Create("assets/textures/Checkerboard.png");
m_PlayIcon = Texture2D::Create("Resources/Icons/PlayButton.png"); m_IconPlay = Texture2D::Create("Resources/Icons/PlayButton.png");
m_StopIcon = Texture2D::Create("Resources/Icons/PauseButton.png"); m_IconStop = Texture2D::Create("Resources/Icons/PauseButton.png");
m_IconSimulate = Texture2D::Create("Resources/Icons/SimulateButton.png");
m_EditorScene = CreateRef<Scene>(); m_EditorScene = CreateRef<Scene>();
m_EditorScene->OnViewportResize((uint32_t)m_ViewPortSize.x, (uint32_t)m_ViewPortSize.y); m_EditorScene->OnViewportResize((uint32_t)m_ViewPortSize.x, (uint32_t)m_ViewPortSize.y);
@ -94,6 +95,10 @@ namespace Hazel
m_EditorCamera.OnUpdate(ts); m_EditorCamera.OnUpdate(ts);
m_ActiveScene->OnUpdateEditor(ts, m_EditorCamera); m_ActiveScene->OnUpdateEditor(ts, m_EditorCamera);
break; break;
case SceneState::Simulate:
m_EditorCamera.OnUpdate(ts);
m_ActiveScene->OnUpdateSimulation(ts, m_EditorCamera);
break;
} }
@ -127,8 +132,12 @@ namespace Hazel
if (m_SceneState == SceneState::Play) if (m_SceneState == SceneState::Play)
{ {
Entity camera = m_ActiveScene->GetPrimaryCameraEntity(); Entity camera = m_ActiveScene->GetPrimaryCameraEntity();
if (camera)
Renderer2D::BeginScene(camera.GetComponent<CameraComponent>().Camera, camera.GetComponent<TransformComponent>().GetTransform()); Renderer2D::BeginScene(camera.GetComponent<CameraComponent>().Camera, camera.GetComponent<TransformComponent>().GetTransform());
else
Renderer2D::BeginScene(m_EditorCamera);
}else }else
{ {
Renderer2D::BeginScene(m_EditorCamera); Renderer2D::BeginScene(m_EditorCamera);
@ -137,6 +146,46 @@ namespace Hazel
if (m_ShowPhysicsColliders) if (m_ShowPhysicsColliders)
{ {
// box collider // box collider
auto bcview = m_ActiveScene->GetAllEntitiesWith<TransformComponent, BoxCollider2DComponent>();
for (auto entity : bcview)
{
auto [tc, bc2D] = bcview.get<TransformComponent, BoxCollider2DComponent>(entity);
glm::vec3 scaledOffset = glm::vec3(
bc2D.Offset.x * tc.Scale.x,
bc2D.Offset.y * tc.Scale.y,
0.0f
);
glm::vec3 translation1 = tc.Translation + scaledOffset + glm::vec3(0.0f, 0.0f, 0.001f);
// 创建完整的模型变换矩阵
glm::mat4 model = glm::translate(glm::mat4(1.0f), translation1)
* glm::rotate(glm::mat4(1.0f), tc.Rotation.z, glm::vec3(0.0f, 0.0f, 1.0f))
* glm::scale(glm::mat4(1.0f), tc.Scale);
// 将 offset 从局部空间变换到世界空间
glm::vec4 worldOffset = model * glm::vec4(bc2D.Offset.x, bc2D.Offset.y, 0.0f, 1.0f);
// 最终位置 = 实体位置 + 变换后的offset + Z偏移
glm::vec3 translation = glm::vec3(worldOffset) + glm::vec3(0.0f, 0.0f, 0.001f);
// 计算缩放 (Size 是半宽半高所以要乘2)
glm::vec3 scale = tc.Scale * glm::vec3(bc2D.Size * 2.0f, 1.0f);
// 创建碰撞体变换矩阵 (包含旋转)
glm::mat4 transform =
glm::translate(glm::mat4(1.0f), translation)
* glm::rotate(glm::mat4(1.0f), tc.Rotation.z, glm::vec3(0.0f, 0.0f, 1.0f))
* glm::scale(glm::mat4(1.0f), scale);
Renderer2D::DrawRect(transform, glm::vec4(0.2f, 1.0f, 0.2f, 1.0f));
}
/*
auto bcview = m_ActiveScene->GetAllEntitiesWith<TransformComponent, BoxCollider2DComponent>(); auto bcview = m_ActiveScene->GetAllEntitiesWith<TransformComponent, BoxCollider2DComponent>();
for (auto entity : bcview) for (auto entity : bcview)
{ {
@ -149,6 +198,7 @@ namespace Hazel
Renderer2D::DrawRect(transform, glm::vec4(0.2, 1.0f, 0.2f, 1.0f)); Renderer2D::DrawRect(transform, glm::vec4(0.2, 1.0f, 0.2f, 1.0f));
} }
*/
// circle collider // circle collider
auto ccview = m_ActiveScene->GetAllEntitiesWith<TransformComponent, CircleCollider2DComponent>(); auto ccview = m_ActiveScene->GetAllEntitiesWith<TransformComponent, CircleCollider2DComponent>();
for (auto entity : ccview) for (auto entity : ccview)
@ -357,7 +407,7 @@ namespace Hazel
cameraProjection = camera.GetProjection(); cameraProjection = camera.GetProjection();
cameraView = glm::inverse(cameraEntity.GetComponent<TransformComponent>().GetTransform()); cameraView = glm::inverse(cameraEntity.GetComponent<TransformComponent>().GetTransform());
}else if (m_SceneState == SceneState::Edit) }else
{ {
cameraProjection = m_EditorCamera.GetProjection(); cameraProjection = m_EditorCamera.GetProjection();
cameraView = m_EditorCamera.GetViewMatrix(); cameraView = m_EditorCamera.GetViewMatrix();
@ -477,6 +527,9 @@ namespace Hazel
void EditorLayer::OnScenePlay() void EditorLayer::OnScenePlay()
{ {
if (m_SceneState == SceneState::Simulate)
OnSceneStop();
// SaveScene(); // SaveScene();
m_SceneState = SceneState::Play; m_SceneState = SceneState::Play;
@ -488,9 +541,31 @@ namespace Hazel
void EditorLayer::OnSceneStop() void EditorLayer::OnSceneStop()
{ {
m_SceneState = SceneState::Edit; if (m_SceneState != SceneState::Play && m_SceneState != SceneState::Simulate)
{
HZ_CORE_WARN("Scene is not playing or simulating!");
return;
}
if (m_SceneState == SceneState::Play)
m_ActiveScene->OnRuntimeStop(); m_ActiveScene->OnRuntimeStop();
else if (m_SceneState == SceneState::Simulate)
m_ActiveScene->OnSimulationStop();
m_SceneState = SceneState::Edit;
m_ActiveScene = m_EditorScene; m_ActiveScene = m_EditorScene;
m_SceneHierachyPanel.SetContext(m_ActiveScene);
}
void EditorLayer::OnSceneSimulation()
{
if (m_SceneState == SceneState::Simulate)
OnSceneStop();
// SaveScene();
m_SceneState = SceneState::Simulate;
m_ActiveScene = Scene::Copy(m_EditorScene);
m_ActiveScene->OnSimulationStart();
m_SceneHierachyPanel.SetContext(m_ActiveScene); m_SceneHierachyPanel.SetContext(m_ActiveScene);
} }
@ -583,16 +658,28 @@ namespace Hazel
// ImGui::Begin("##ToolBar", nullptr); // ImGui::Begin("##ToolBar", nullptr);
float size = ImGui::GetWindowHeight() - 10.0f; float size = ImGui::GetWindowHeight() - 10.0f;
Ref<Texture2D> icon = m_SceneState == SceneState::Edit ? m_PlayIcon : m_StopIcon;
ImGui::SetCursorPosX(ImGui::GetWindowContentRegionMax().x * 0.5f - size * 0.5f); ImGui::SetCursorPosX(ImGui::GetWindowContentRegionMax().x * 0.5f - size * 0.5f);
ImGui::SetCursorPosX(ImGui::GetWindowContentRegionMax().x * 0.5f - (size * 0.5f)); ImGui::SetCursorPosX(ImGui::GetWindowContentRegionMax().x * 0.5f - (size * 0.5f));
if (ImGui::ImageButton("toolbar", icon->GetRendererID(), ImVec2{size, size}, ImVec2{0, 0}, ImVec2{1, 1}))
Ref<Texture2D> icon = (m_SceneState == SceneState::Edit || m_SceneState == SceneState::Simulate)? m_IconPlay : m_IconStop;
if (ImGui::ImageButton("toolbar-play-edit", icon->GetRendererID(), ImVec2{size, size}, ImVec2{0, 0}, ImVec2{1, 1}))
{ {
if (m_SceneState == SceneState::Edit) if (m_SceneState == SceneState::Edit || m_SceneState == SceneState::Simulate)
OnScenePlay(); OnScenePlay();
else if (m_SceneState == SceneState::Play) else if (m_SceneState == SceneState::Play)
OnSceneStop(); OnSceneStop();
}
ImGui::SameLine();
{
Ref<Texture2D> icon = (m_SceneState == SceneState::Edit || m_SceneState == SceneState::Play)? m_IconSimulate : m_IconStop;
if (ImGui::ImageButton("toolbar-simulate", icon->GetRendererID(), ImVec2{size, size}, ImVec2{0, 0}, ImVec2{1, 1}))
{
if (m_SceneState == SceneState::Edit || m_SceneState == SceneState::Play)
OnSceneSimulation();
else if (m_SceneState == SceneState::Simulate)
OnSceneStop();
}
} }
ImGui::PopStyleVar(2); ImGui::PopStyleVar(2);

View File

@ -27,8 +27,10 @@ namespace Hazel
void UI_ToolBar(); void UI_ToolBar();
void OnScenePlay(); void OnScenePlay();
void OnSceneSimulation();
void OnSceneStop(); void OnSceneStop();
void SaveSceneAs() const; void SaveSceneAs() const;
void OpenScene(); void OpenScene();
void OpenScene(const std::filesystem::path& scenePath); void OpenScene(const std::filesystem::path& scenePath);
@ -76,12 +78,12 @@ namespace Hazel
enum class SceneState enum class SceneState
{ {
Edit = 0, Play = 1 Edit = 0, Play = 1, Simulate = 2
}; };
SceneState m_SceneState = SceneState::Edit; SceneState m_SceneState = SceneState::Edit;
Ref<Texture2D> m_PlayIcon, m_StopIcon; Ref<Texture2D> m_IconPlay, m_IconStop, m_IconSimulate;
}; };
} }