aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrtk0c <[email protected]>2022-05-06 19:52:12 -0700
committerrtk0c <[email protected]>2022-05-06 19:52:12 -0700
commitcde94efdd44553f3f6575ce84b44c6799e1a1425 (patch)
tree593b9f280f2e223268f8d5c73f5d1dd1aba50c36
parent4c9f5ee706faa1435b7dc2f3b6d4753e1289c351 (diff)
Changeset: 22 Improved camera and various cleanups
-rw-r--r--source/App.cpp44
-rw-r--r--source/App.hpp13
-rw-r--r--source/Camera.hpp2
-rw-r--r--source/EditorCore.cpp181
-rw-r--r--source/EditorCore.hpp12
-rw-r--r--source/EditorNotification.hpp2
-rw-r--r--source/EditorUtils.cpp261
-rw-r--r--source/EditorUtils.hpp18
-rw-r--r--source/GameObject.cpp6
-rw-r--r--source/GameObject.hpp1
-rw-r--r--source/Ires.cpp6
-rw-r--r--source/Player.cpp2
-rw-r--r--source/Sprite.cpp2
13 files changed, 457 insertions, 93 deletions
diff --git a/source/App.cpp b/source/App.cpp
index 9c18fea..421adb5 100644
--- a/source/App.cpp
+++ b/source/App.cpp
@@ -1,8 +1,12 @@
#include "App.hpp"
+#include <string>
#include <utility>
-App::App() {
+using namespace std::literals;
+
+App::App()
+ : mActiveCamera{ &mMainCamera } {
auto& worldRoot = mWorld.GetRoot();
constexpr int kPlayerCount = 2;
@@ -19,18 +23,27 @@ App::App() {
SetGameRunning(true);
#endif
- // TODO this renders nothing, fix
- mGameCamera.Move(glm::vec3(0, 0, 1));
- mGameCamera.LookAt(glm::vec3(0, 0, 0));
- mGameCamera.SetHasPerspective(false);
- mEditorCamera.Move(glm::vec3(0, 0, 1));
- mEditorCamera.LookAt(glm::vec3(0, 0, 0));
- mEditorCamera.SetHasPerspective(false);
+ mMainCamera.name = "Main Camera"s;
+ mMainCamera.Move(glm::vec3(0, 0, 1));
+ mMainCamera.LookAt(glm::vec3(0, 0, 0));
+ mMainCamera.SetHasPerspective(false);
}
App::~App() {
}
+Camera* App::GetActiveCamera() const {
+ return mActiveCamera;
+}
+
+void App::BindActiveCamera(Camera* camera) {
+ mActiveCamera = camera;
+}
+
+void App::UnbindActiveCamera() {
+ mActiveCamera = &mMainCamera;
+}
+
bool App::IsGameRunning() const {
return mGameRunning;
}
@@ -70,18 +83,11 @@ void App::Show() {
void App::Update() {
if (IsGameRunning()) {
mWorld.Update();
- mEditor->OnUpdate();
}
}
void App::Draw(float currentTime, float deltaTime) {
- Camera* camera;
- if (IsGameRunning()) {
- camera = &mGameCamera;
- } else {
- camera = &mEditorCamera;
- }
- mRenderer.BeginFrame(*camera, currentTime, deltaTime);
+ mWorldRenderer.BeginFrame(*mActiveCamera, currentTime, deltaTime);
PodVector<GameObject*> stack;
stack.push_back(&mWorld.GetRoot());
@@ -95,12 +101,10 @@ void App::Draw(float currentTime, float deltaTime) {
}
auto renderObjects = obj->GetRenderObjects();
- mRenderer.Draw(renderObjects.data(), renderObjects.size());
+ mWorldRenderer.Draw(renderObjects.data(), renderObjects.size());
}
- mRenderer.EndFrame();
- // TODO pass camera info to editor without strong coupling to Renderer
- mEditor->OnDraw(mRenderer.GetLastFrameInfo());
+ mWorldRenderer.EndFrame();
}
void App::HandleMouse(int button, int action) {
diff --git a/source/App.hpp b/source/App.hpp
index 26857db..54e1fb5 100644
--- a/source/App.hpp
+++ b/source/App.hpp
@@ -17,15 +17,18 @@
using KeyCaptureCallback = std::function<bool(int, int)>;
+// TODO how should we split responsibilities between App and Editor?
class App {
+ friend class EditorInstance;
+
private:
std::deque<KeyCaptureCallback> mKeyCaptureCallbacks;
PodVector<Player*> mPlayers;
std::unique_ptr<EditorInstance> mEditor;
GameWorld mWorld;
- Camera mGameCamera;
- Camera mEditorCamera;
- Renderer mRenderer;
+ Renderer mWorldRenderer;
+ Camera mMainCamera;
+ Camera* mActiveCamera;
// NOTE: should only be true when mEditor != nullptr
bool mEditorVisible = false;
bool mGameRunning = false;
@@ -37,6 +40,10 @@ public:
EditorInstance* GetEditor() { return mEditor.get(); }
GameWorld* GetWorld() { return &mWorld; }
+ Camera* GetActiveCamera() const;
+ void BindActiveCamera(Camera* camera);
+ void UnbindActiveCamera();
+
bool IsGameRunning() const;
void SetGameRunning(bool running);
diff --git a/source/Camera.hpp b/source/Camera.hpp
index d203622..f70d402 100644
--- a/source/Camera.hpp
+++ b/source/Camera.hpp
@@ -1,9 +1,11 @@
#pragma once
#include <glm/glm.hpp>
+#include <string>
class Camera {
public:
+ std::string name;
glm::vec3 pos;
glm::vec3 lookAt;
bool perspective = false;
diff --git a/source/EditorCore.cpp b/source/EditorCore.cpp
index bd5d78d..1841dfb 100644
--- a/source/EditorCore.cpp
+++ b/source/EditorCore.cpp
@@ -203,21 +203,22 @@ void EditorContentBrowser::Show(bool* open) {
ImGui::End();
}
-void EditorTransformEdit::ShowWorld(float* cameraView, float* cameraProjection) {
- glm::mat4 identityMatrix(1.00f);
+void EditorTransformEdit::ShowWorld(Camera* camera) {
+ // TODO
+ // glm::mat4 identityMatrix(1.00f);
- ImGuiIO& io = ImGui::GetIO();
- float viewManipulateRight = io.DisplaySize.x;
- float viewManipulateTop = 0;
- static ImGuiWindowFlags gizmoWindowFlags = 0;
+ // ImGuiIO& io = ImGui::GetIO();
+ // float viewManipulateRight = io.DisplaySize.x;
+ // float viewManipulateTop = 0;
+ // static ImGuiWindowFlags gizmoWindowFlags = 0;
- ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y);
+ // ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y);
- if (showGrid) {
- ImGuizmo::DrawGrid(cameraView, cameraProjection, &identityMatrix[0][0], 100.f);
- }
+ // if (showGrid) {
+ // ImGuizmo::DrawGrid(cameraView, cameraProjection, &identityMatrix[0][0], 100.f);
+ // }
- ImGuizmo::ViewManipulate(cameraView, camDistance, ImVec2(viewManipulateRight - 128, viewManipulateTop), ImVec2(128, 128), 0x10101010);
+ // ImGuizmo::ViewManipulate(cameraView, camDistance, ImVec2(viewManipulateRight - 128, viewManipulateTop), ImVec2(128, 128), 0x10101010);
}
bool EditorTransformEdit::ShowObjectInspector(float* cameraView, float* cameraProjection, glm::vec3* pos, glm::quat* rotation, glm::vec3* scale) {
@@ -412,23 +413,22 @@ struct CreatableGameObject {
EditorInstance::EditorInstance(App* app)
: mApp{ app }
- , mFallbackFrameInfo{
- .camera = nullptr,
- .matrixCombined = {},
- .time = 0.0f,
- .deltaTime = 0.0f,
- }
- , mCurrentFrameInfo{ &mFallbackFrameInfo }
- , mEdContentBrowser(&mEdInspector) {}
-
-EditorInstance::~EditorInstance() {
+ , mEdContentBrowser(&mEdInspector) {
+ mEditorCamera.name = "Editor Camera"s;
+ mEditorCamera.Move(glm::vec3(0, 0, 1));
+ mEditorCamera.LookAt(glm::vec3(0, 0, 0));
+ mEditorCamera.SetHasPerspective(false);
}
-void EditorInstance::OnUpdate() {
+EditorInstance::~EditorInstance() {
}
-void EditorInstance::OnDraw(const RendererFrameInfo& frameInfo) {
- mCurrentFrameInfo = &frameInfo;
+void EditorInstance::OnGameStateChanged(bool running) {
+ if (running) {
+ mApp->UnbindActiveCamera();
+ } else {
+ mApp->BindActiveCamera(&mEditorCamera);
+ }
}
void EditorInstance::Show() {
@@ -468,17 +468,56 @@ void EditorInstance::Show() {
mEdContentBrowser.Show(&mWindowVisible_ContentBrowser);
}
- auto cameraViewMat = mCurrentFrameInfo->matrixView;
- auto cameraView = &cameraViewMat[0][0];
- auto cameraProjMat = mCurrentFrameInfo->matrixProj;
- auto cameraProj = &cameraProjMat[0][0];
- // auto cameraView = &mCurrentFrameInfo->matrixView[0][0];
- // auto cameraProj = &mCurrentFrameInfo->matrixProj[0][0];
- // TODO moving camera
- // mEdTransformEdit.ShowWorld(cameraView, cameraProj);
+ auto& camera = *mApp->GetActiveCamera();
ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y);
ImGuizmo::SetDrawlist(ImGui::GetForegroundDrawList());
+ auto cameraPos = camera.pos;
+ {
+ mEdTransformEdit.ShowWorld(&camera);
+ if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && !io.WantCaptureMouse && !mDragCam_Happening) {
+ mDragCam_CamInitial = camera.pos;
+ mDragCam_CursorInitial = ImGui::GetMousePos();
+ mDragCam_Happening = true;
+ }
+ if (mDragCam_Happening) {
+ auto newPos = ImGui::GetMousePos();
+ // NOTE: we are emulating as if the mouse is dragging the "canvas", through moving the camera in the opposite direction of the natural position delta
+ cameraPos.x = mDragCam_CamInitial.x + mDragCam_CursorInitial.x - newPos.x;
+ cameraPos.y = mDragCam_CamInitial.y + -(mDragCam_CursorInitial.y - newPos.y); // Invert Y delta because ImGui uses top-left origin (mouse moving down translates to positive value, but in our coordinate system down is negative)
+
+ // Draw movement indicator
+ auto drawList = ImGui::GetForegroundDrawList();
+ ImGui::DrawArrow(drawList, ImVec2(mDragCam_CursorInitial.x, mDragCam_CursorInitial.y), newPos, IM_COL32(0, 255, 255, 255));
+ }
+ if (ImGui::IsMouseReleased(ImGuiMouseButton_Right)) {
+ mDragCam_Happening = false;
+ }
+
+ constexpr float kCameraMoveSpeed = 5.0f;
+ if (ImGui::IsKeyDown(ImGuiKey_W)) {
+ cameraPos.y += kCameraMoveSpeed;
+ }
+ if (ImGui::IsKeyDown(ImGuiKey_S)) {
+ cameraPos.y -= kCameraMoveSpeed;
+ }
+ if (ImGui::IsKeyDown(ImGuiKey_A)) {
+ cameraPos.x -= kCameraMoveSpeed;
+ }
+ if (ImGui::IsKeyDown(ImGuiKey_D)) {
+ cameraPos.x += kCameraMoveSpeed;
+ }
+ }
+ // 2D camera: always look straight ahead
+ camera.pos = cameraPos;
+ camera.lookAt = cameraPos;
+
+ // TODO get rid of this massive hack: how to manage const better for intuitively read-only, but write doesn't-care data?
+ auto& lastFrameInfo = const_cast<RendererFrameInfo&>(mApp->mWorldRenderer.GetLastFrameInfo());
+ auto& cameraViewMat = lastFrameInfo.matrixView;
+ float* cameraView = &cameraViewMat[0][0];
+ auto& cameraProjMat = lastFrameInfo.matrixProj;
+ float* cameraProj = &cameraProjMat[0][0];
if (mWindowVisible_Inspector) {
ImGui::Begin("Inspector");
switch (mEdInspector.selectedItt) {
@@ -516,46 +555,48 @@ void EditorInstance::Show() {
if (mWindowVisible_WorldStructure) {
ImGui::Begin("World structure");
- {
- GobjTreeNodeShowInfo showInfo{
- .in_editor = this,
- };
- GobjTreeNode(showInfo, &world->GetRoot());
+ GobjTreeNodeShowInfo showInfo{
+ .in_editor = this,
+ };
+ GobjTreeNode(showInfo, &world->GetRoot());
- if (showInfo.out_openPopup) {
- mPopupCurrent_GameObject = showInfo.out_openPopup;
+ if (showInfo.out_openPopup) {
+ mPopupCurrent_GameObject = showInfo.out_openPopup;
- ImGui::OpenPopup("GameObject Popup");
- ImGui::SetNextWindowPos(ImGui::GetMousePos());
+ ImGui::OpenPopup("GameObject Popup");
+ ImGui::SetNextWindowPos(ImGui::GetMousePos());
+ }
+ if (ImGui::BeginPopup("GameObject Popup")) {
+ // Target no longer selected during popup open
+ if (!mPopupCurrent_GameObject) {
+ ImGui::CloseCurrentPopup();
}
- if (ImGui::BeginPopup("GameObject Popup")) {
- // Target no longer selected during popup open
- if (!mPopupCurrent_GameObject) {
- ImGui::CloseCurrentPopup();
- }
- if (ImGui::BeginMenu("Add child")) {
- for (size_t i = 0; i < std::size(creatableGameObjects); ++i) {
- auto& info = creatableGameObjects[i];
- if (ImGui::MenuItem(info.name)) {
- auto object = info.factory(world);
- mPopupCurrent_GameObject->AddChild(object);
- }
+ if (ImGui::BeginMenu("Add child")) {
+ for (size_t i = 0; i < std::size(creatableGameObjects); ++i) {
+ auto& info = creatableGameObjects[i];
+ if (ImGui::MenuItem(info.name)) {
+ auto object = info.factory(world);
+ mPopupCurrent_GameObject->AddChild(object);
}
- ImGui::EndMenu();
- }
- ImGui::Separator();
- if (ImGui::MenuItem("Remove")) {
- // TODO
}
- ImGui::EndPopup();
+ ImGui::EndMenu();
+ }
+ ImGui::Separator();
+ if (ImGui::MenuItem("Remove")) {
+ ImGui::DialogConfirmation("Are you sure you want to delete this GameObject?", [object = mPopupCurrent_GameObject](bool yes) {
+ object->RemoveSelfFromParent();
+ delete object;
+ });
}
+ ImGui::EndPopup();
}
ImGui::End();
}
ShowSpriteViewer();
+ ImGui::ShowDialogs();
ImGui::ShowNotifications();
}
@@ -575,6 +616,28 @@ void EditorInstance::ShowWorldProperties() {
ImGui::SetTooltip("The game is currently paused. Click to run.");
}
}
+
+ auto& camera = *mApp->GetActiveCamera();
+ ImGui::TextUnformatted("Active camera:");
+ ImGui::Indent();
+#define CAMERA_POS_TEXT "at (%.3f, %.3f, %.3f)\nlooking at (%.3f, %.3f, %.3f)", camera.pos.x, camera.pos.y, camera.pos.z, camera.lookAt.x, camera.lookAt.y, camera.lookAt.z
+ ImGui::TextUnformatted(camera.name.c_str());
+ ImGui::Text(CAMERA_POS_TEXT);
+ if (ImGui::BeginPopupContextItem("##CTXMENU")) {
+ if (ImGui::MenuItem("Reset to origin")) {
+ camera.pos = {};
+ camera.lookAt = {};
+ }
+ if (ImGui::MenuItem("Copy")) {
+ char buffer[2048];
+ int result = snprintf(buffer, sizeof(buffer), CAMERA_POS_TEXT);
+ ImGui::SetClipboardText(buffer);
+ ImGui::AddNotification(ImGuiToast(ImGuiToastType_Success, "Copied to clipboard."));
+ }
+ ImGui::EndPopup();
+ }
+#undef CAMERA_POS_TEXT
+ ImGui::Unindent();
}
// TOOD move resource-specific and gameobject-specific inspector code into attachments mechanism
diff --git a/source/EditorCore.hpp b/source/EditorCore.hpp
index 785fc6b..8b70ee0 100644
--- a/source/EditorCore.hpp
+++ b/source/EditorCore.hpp
@@ -71,23 +71,25 @@ private:
bool showGrid = false;
public:
- void ShowWorld(float* cameraView, float* cameraProjection);
+ void ShowWorld(Camera* camera);
bool ShowObjectInspector(float* cameraView, float* cameraProjection, glm::vec3* pos, glm::quat* rotation, glm::vec3* scale);
bool ShowObject(float* cameraView, float* cameraProjection, glm::vec3* pos, glm::quat* rotation, glm::vec3* scale);
};
+// TODO editor undo stack
class App;
class EditorInstance {
private:
App* mApp;
- RendererFrameInfo mFallbackFrameInfo;
- const RendererFrameInfo* mCurrentFrameInfo;
GameObject* mPopupCurrent_GameObject = nullptr;
+ Camera mEditorCamera;
RcPtr<SpriteDefinition> mSpriteView_Instance;
EditorCommandPalette mEdCommandPalette;
EditorInspector mEdInspector;
EditorContentBrowser mEdContentBrowser;
EditorTransformEdit mEdTransformEdit;
+ glm::vec3 mDragCam_CamInitial;
+ ImVec2 mDragCam_CursorInitial;
int mSpriteView_Frame;
bool mSpriteView_OpenNextFrame = false;
bool mWindowVisible_ImGuiDemo = false;
@@ -96,13 +98,13 @@ private:
bool mWindowVisible_ContentBrowser = true;
bool mWindowVisible_WorldStructure = true;
bool mWindowVisible_WorldProperties = true;
+ bool mDragCam_Happening = false;
public:
EditorInstance(App* app);
~EditorInstance();
- void OnUpdate();
- void OnDraw(const RendererFrameInfo& frameInfo);
+ void OnGameStateChanged(bool running);
void Show();
EditorInspector& GetInspector() { return mEdInspector; }
diff --git a/source/EditorNotification.hpp b/source/EditorNotification.hpp
index 7bb6dab..3af8c2d 100644
--- a/source/EditorNotification.hpp
+++ b/source/EditorNotification.hpp
@@ -64,7 +64,7 @@ public:
ImVec4 GetColor();
const char* GetIcon();
const char* GetContent();
- ;
+
uint64_t GetElapsedTime();
ImGuiToastPhase GetPhase();
float GetFadePercent();
diff --git a/source/EditorUtils.cpp b/source/EditorUtils.cpp
index 39d66fb..20caef7 100644
--- a/source/EditorUtils.cpp
+++ b/source/EditorUtils.cpp
@@ -4,8 +4,10 @@
#include <imgui_internal.h>
#include <backends/imgui_impl_glfw.h>
+#include <cmath>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/quaternion.hpp>
+#include <numbers>
const char* ImGui::GetKeyNameGlfw(int key) {
return GetKeyName(ImGui_ImplGlfw_KeyToImGuiKey(key));
@@ -163,13 +165,270 @@ bool ImGui::Splitter(bool splitVertically, float thickness, float* size1, float*
return held;
}
-void ImGui ::AddUnderLine(ImColor col) {
+void ImGui::AddUnderLine(ImColor col) {
auto min = ImGui::GetItemRectMin();
auto max = ImGui::GetItemRectMax();
min.y = max.y;
ImGui::GetWindowDrawList()->AddLine(min, max, col, 1.0f);
}
+void ImGui::DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor) {
+ // Taken from https://github.com/thedmd/imgui-node-editor/blob/master/examples/blueprints-example/utilities/drawing.cpp
+ // ax::NodeEditor::DrawIcon
+
+ auto rect = ImRect(a, b);
+ auto rect_x = rect.Min.x;
+ auto rect_y = rect.Min.y;
+ auto rect_w = rect.Max.x - rect.Min.x;
+ auto rect_h = rect.Max.y - rect.Min.y;
+ auto rect_center_x = (rect.Min.x + rect.Max.x) * 0.5f;
+ auto rect_center_y = (rect.Min.y + rect.Max.y) * 0.5f;
+ auto rect_center = ImVec2(rect_center_x, rect_center_y);
+ const auto outline_scale = rect_w / 24.0f;
+ const auto extra_segments = static_cast<int>(2 * outline_scale); // for full circle
+
+ if (type == IconType::Flow) {
+ const auto origin_scale = rect_w / 24.0f;
+
+ const auto offset_x = 1.0f * origin_scale;
+ const auto offset_y = 0.0f * origin_scale;
+ const auto margin = 2.0f * origin_scale;
+ const auto rounding = 0.1f * origin_scale;
+ const auto tip_round = 0.7f; // percentage of triangle edge (for tip)
+ // const auto edge_round = 0.7f; // percentage of triangle edge (for corner)
+ const auto canvas = ImRect(
+ rect.Min.x + margin + offset_x,
+ rect.Min.y + margin + offset_y,
+ rect.Max.x - margin + offset_x,
+ rect.Max.y - margin + offset_y);
+ const auto canvas_x = canvas.Min.x;
+ const auto canvas_y = canvas.Min.y;
+ const auto canvas_w = canvas.Max.x - canvas.Min.x;
+ const auto canvas_h = canvas.Max.y - canvas.Min.y;
+
+ const auto left = canvas_x + canvas_w * 0.5f * 0.3f;
+ const auto right = canvas_x + canvas_w - canvas_w * 0.5f * 0.3f;
+ const auto top = canvas_y + canvas_h * 0.5f * 0.2f;
+ const auto bottom = canvas_y + canvas_h - canvas_h * 0.5f * 0.2f;
+ const auto center_y = (top + bottom) * 0.5f;
+ // const auto angle = AX_PI * 0.5f * 0.5f * 0.5f;
+
+ const auto tip_top = ImVec2(canvas_x + canvas_w * 0.5f, top);
+ const auto tip_right = ImVec2(right, center_y);
+ const auto tip_bottom = ImVec2(canvas_x + canvas_w * 0.5f, bottom);
+
+ drawList->PathLineTo(ImVec2(left, top) + ImVec2(0, rounding));
+ drawList->PathBezierCubicCurveTo(
+ ImVec2(left, top),
+ ImVec2(left, top),
+ ImVec2(left, top) + ImVec2(rounding, 0));
+ drawList->PathLineTo(tip_top);
+ drawList->PathLineTo(tip_top + (tip_right - tip_top) * tip_round);
+ drawList->PathBezierCubicCurveTo(
+ tip_right,
+ tip_right,
+ tip_bottom + (tip_right - tip_bottom) * tip_round);
+ drawList->PathLineTo(tip_bottom);
+ drawList->PathLineTo(ImVec2(left, bottom) + ImVec2(rounding, 0));
+ drawList->PathBezierCubicCurveTo(
+ ImVec2(left, bottom),
+ ImVec2(left, bottom),
+ ImVec2(left, bottom) - ImVec2(0, rounding));
+
+ if (!filled) {
+ if (innerColor & 0xFF000000) {
+ drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor);
+ }
+
+ drawList->PathStroke(color, true, 2.0f * outline_scale);
+ } else {
+ drawList->PathFillConvex(color);
+ }
+ } else {
+ auto triangleStart = rect_center_x + 0.32f * rect_w;
+
+ auto rect_offset = -static_cast<int>(rect_w * 0.25f * 0.25f);
+
+ rect.Min.x += rect_offset;
+ rect.Max.x += rect_offset;
+ rect_x += rect_offset;
+ rect_center_x += rect_offset * 0.5f;
+ rect_center.x += rect_offset * 0.5f;
+
+ if (type == IconType::Circle) {
+ const auto c = rect_center;
+
+ if (!filled) {
+ const auto r = 0.5f * rect_w / 2.0f - 0.5f;
+
+ if (innerColor & 0xFF000000)
+ drawList->AddCircleFilled(c, r, innerColor, 12 + extra_segments);
+ drawList->AddCircle(c, r, color, 12 + extra_segments, 2.0f * outline_scale);
+ } else {
+ drawList->AddCircleFilled(c, 0.5f * rect_w / 2.0f, color, 12 + extra_segments);
+ }
+ }
+
+ if (type == IconType::Square) {
+ if (filled) {
+ const auto r = 0.5f * rect_w / 2.0f;
+ const auto p0 = rect_center - ImVec2(r, r);
+ const auto p1 = rect_center + ImVec2(r, r);
+
+ drawList->AddRectFilled(p0, p1, color, 0, 15 + extra_segments);
+ } else {
+ const auto r = 0.5f * rect_w / 2.0f - 0.5f;
+ const auto p0 = rect_center - ImVec2(r, r);
+ const auto p1 = rect_center + ImVec2(r, r);
+
+ if (innerColor & 0xFF000000)
+ drawList->AddRectFilled(p0, p1, innerColor, 0, 15 + extra_segments);
+
+ drawList->AddRect(p0, p1, color, 0, 15 + extra_segments, 2.0f * outline_scale);
+ }
+ }
+
+ if (type == IconType::Grid) {
+ const auto r = 0.5f * rect_w / 2.0f;
+ const auto w = ceilf(r / 3.0f);
+
+ const auto baseTl = ImVec2(floorf(rect_center_x - w * 2.5f), floorf(rect_center_y - w * 2.5f));
+ const auto baseBr = ImVec2(floorf(baseTl.x + w), floorf(baseTl.y + w));
+
+ auto tl = baseTl;
+ auto br = baseBr;
+ for (int i = 0; i < 3; ++i) {
+ tl.x = baseTl.x;
+ br.x = baseBr.x;
+ drawList->AddRectFilled(tl, br, color);
+ tl.x += w * 2;
+ br.x += w * 2;
+ if (i != 1 || filled)
+ drawList->AddRectFilled(tl, br, color);
+ tl.x += w * 2;
+ br.x += w * 2;
+ drawList->AddRectFilled(tl, br, color);
+
+ tl.y += w * 2;
+ br.y += w * 2;
+ }
+
+ triangleStart = br.x + w + 1.0f / 24.0f * rect_w;
+ }
+
+ if (type == IconType::RoundSquare) {
+ if (filled) {
+ const auto r = 0.5f * rect_w / 2.0f;
+ const auto cr = r * 0.5f;
+ const auto p0 = rect_center - ImVec2(r, r);
+ const auto p1 = rect_center + ImVec2(r, r);
+
+ drawList->AddRectFilled(p0, p1, color, cr, 15);
+ } else {
+ const auto r = 0.5f * rect_w / 2.0f - 0.5f;
+ const auto cr = r * 0.5f;
+ const auto p0 = rect_center - ImVec2(r, r);
+ const auto p1 = rect_center + ImVec2(r, r);
+
+ if (innerColor & 0xFF000000)
+ drawList->AddRectFilled(p0, p1, innerColor, cr, 15);
+
+ drawList->AddRect(p0, p1, color, cr, 15, 2.0f * outline_scale);
+ }
+ } else if (type == IconType::Diamond) {
+ if (filled) {
+ const auto r = 0.607f * rect_w / 2.0f;
+ const auto c = rect_center;
+
+ drawList->PathLineTo(c + ImVec2(0, -r));
+ drawList->PathLineTo(c + ImVec2(r, 0));
+ drawList->PathLineTo(c + ImVec2(0, r));
+ drawList->PathLineTo(c + ImVec2(-r, 0));
+ drawList->PathFillConvex(color);
+ } else {
+ const auto r = 0.607f * rect_w / 2.0f - 0.5f;
+ const auto c = rect_center;
+
+ drawList->PathLineTo(c + ImVec2(0, -r));
+ drawList->PathLineTo(c + ImVec2(r, 0));
+ drawList->PathLineTo(c + ImVec2(0, r));
+ drawList->PathLineTo(c + ImVec2(-r, 0));
+
+ if (innerColor & 0xFF000000)
+ drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor);
+
+ drawList->PathStroke(color, true, 2.0f * outline_scale);
+ }
+ } else {
+ const auto triangleTip = triangleStart + rect_w * (0.45f - 0.32f);
+
+ drawList->AddTriangleFilled(
+ ImVec2(ceilf(triangleTip), rect_y + rect_h * 0.5f),
+ ImVec2(triangleStart, rect_center_y + 0.15f * rect_h),
+ ImVec2(triangleStart, rect_center_y - 0.15f * rect_h),
+ color);
+ }
+ }
+}
+
+void ImGui::Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color, const ImVec4& innerColor) {
+ // Taken from https://github.com/thedmd/imgui-node-editor/blob/master/examples/blueprints-example/utilities/widgets.cpp
+ // ax::NodeEditor::Icon
+
+ if (ImGui::IsRectVisible(size)) {
+ auto cursorPos = ImGui::GetCursorScreenPos();
+ auto drawList = ImGui::GetWindowDrawList();
+ DrawIcon(drawList, cursorPos, cursorPos + size, type, filled, ImColor(color), ImColor(innerColor));
+ }
+
+ ImGui::Dummy(size);
+}
+
+void ImGui::DrawArrow(ImDrawList* drawList, ImVec2 from, ImVec2 to, ImU32 color, float lineThickness) {
+ // Adapted from https://stackoverflow.com/a/6333775
+
+ using namespace std::numbers;
+ constexpr float kHeadLength = 10;
+
+ auto angle = std::atan2(to.y - from.y, to.x - from.x);
+ drawList->AddLine(from, to, color, lineThickness);
+ drawList->AddLine(to, ImVec2(to.x - kHeadLength * std::cos(angle - pi / 6), to.y - kHeadLength * std::sin(angle - pi / 6)), color, lineThickness);
+ drawList->AddLine(to, ImVec2(to.x - kHeadLength * std::cos(angle + pi / 6), to.y - kHeadLength * std::sin(angle + pi / 6)), color, lineThickness);
+}
+
+struct DialogObject {
+ std::string message;
+ std::function<void(int)> callback;
+};
+
+static DialogObject gConfirmationDialog{};
+
+void ImGui::DialogConfirmation(std::string message, std::function<void(bool)> callback) {
+ gConfirmationDialog.message = std::move(message);
+ // TODO is converting void(bool) to void(int) fine?
+ gConfirmationDialog.callback = std::move(callback);
+}
+
+void ImGui::ShowDialogs() {
+ if (gConfirmationDialog.callback) {
+ if (ImGui::BeginPopupModal("Confirmation")) {
+ ImGui::Text("%s", gConfirmationDialog.message.c_str());
+ if (ImGui::Button("Cancel")) {
+ gConfirmationDialog.callback(false);
+ gConfirmationDialog.callback = {};
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::SameLine();
+ if (ImGui::Button("Confirm")) {
+ gConfirmationDialog.callback(true);
+ gConfirmationDialog.callback = {};
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::EndPopup();
+ }
+ }
+}
+
float Utils::CalcImageHeight(glm::vec2 original, int targetWidth) {
// Xorig / Yorig = Xnew / Ynew
// Ynew = Xnew * Yorig / Xorig
diff --git a/source/EditorUtils.hpp b/source/EditorUtils.hpp
index b079f13..d832447 100644
--- a/source/EditorUtils.hpp
+++ b/source/EditorUtils.hpp
@@ -33,6 +33,24 @@ bool Splitter(bool splitVertically, float thickness, float* size1, float* size2,
void AddUnderLine(ImColor col);
+enum class IconType {
+ Flow,
+ Circle,
+ Square,
+ Grid,
+ RoundSquare,
+ Diamond,
+};
+
+void DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor);
+void Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color = ImVec4(1, 1, 1, 1), const ImVec4& innerColor = ImVec4(0, 0, 0, 0));
+
+void DrawArrow(ImDrawList* drawList, ImVec2 from, ImVec2 to, ImU32 color, float lineThickness = 1.0f);
+
+// NOTE: string is copied into an internal storage
+void DialogConfirmation(std::string message, std::function<void(bool)> callback);
+void ShowDialogs();
+
} // namespace ImGui
namespace Utils {
diff --git a/source/GameObject.cpp b/source/GameObject.cpp
index bb86acc..0dccf38 100644
--- a/source/GameObject.cpp
+++ b/source/GameObject.cpp
@@ -124,6 +124,12 @@ GameObject* GameObject::RemoveChild(GameObject* child) {
return nullptr;
}
+void GameObject::RemoveSelfFromParent() {
+ if (mParent) {
+ mParent->RemoveChild(this);
+ }
+}
+
PodVector<GameObject*> GameObject::RemoveAllChildren() {
for (auto& child : mChildren) {
child->SetParent(nullptr);
diff --git a/source/GameObject.hpp b/source/GameObject.hpp
index d0f94a5..c30b91b 100644
--- a/source/GameObject.hpp
+++ b/source/GameObject.hpp
@@ -58,6 +58,7 @@ public:
void AddChild(GameObject* child);
GameObject* RemoveChild(int index);
GameObject* RemoveChild(GameObject* child);
+ void RemoveSelfFromParent();
PodVector<GameObject*> RemoveAllChildren();
EditorAttachment* GetEditorAttachment() const { return mEditorAttachment.get(); }
diff --git a/source/Ires.cpp b/source/Ires.cpp
index 346e6e6..0421a54 100644
--- a/source/Ires.cpp
+++ b/source/Ires.cpp
@@ -303,10 +303,12 @@ std::pair<IresObject*, bool> IresManager::Add(IresObject* ires) {
auto& name = ires->mName;
if (name.empty()) {
int n = std::rand();
+#define IRES_NAME_ERR_MESSAGE "Unnamed %s #%d", IresObject::ToString(ires->GetKind()).data(), n
// NOTE: does not include null-terminator
- int size = snprintf(nullptr, 0, "Unnamed %s #%d", IresObject::ToString(ires->GetKind()).data(), n);
+ int size = snprintf(nullptr, 0, IRES_NAME_ERR_MESSAGE);
name.resize(size); // std::string::resize handles storage for null-terminator alreaedy
- snprintf(name.data(), size, "Unnamed %s #%d", IresObject::ToString(ires->GetKind()).data(), n);
+ snprintf(name.data(), size, IRES_NAME_ERR_MESSAGE);
+#undef IRES_NAME_ERR_MESSAGE
}
auto& uid = ires->mUid;
diff --git a/source/Player.cpp b/source/Player.cpp
index b37a232..34c4549 100644
--- a/source/Player.cpp
+++ b/source/Player.cpp
@@ -110,7 +110,7 @@ void Player::HandleKeyInput(int key, int action) {
function(file, "attack=%d\n", fieldPrefix keybinds.keyAttack);
static FILE* OpenPlayerConfigFile(Player* player, Utils::IoMode mode) {
- char path[512];
+ char path[2048];
snprintf(path, sizeof(path), "%s/player%d.txt", AppConfig::dataDir.c_str(), player->GetId());
return Utils::OpenCstdioFile(path, mode);
diff --git a/source/Sprite.cpp b/source/Sprite.cpp
index f855999..0f1a3bb 100644
--- a/source/Sprite.cpp
+++ b/source/Sprite.cpp
@@ -138,7 +138,7 @@ SpriteDefinition* IresSpritesheet::CreateInstance() const {
return nullptr;
}
- char path[256];
+ char path[2048];
snprintf(path, sizeof(path), "%s/%s", AppConfig::assetDir.c_str(), spritesheetFile.c_str());
auto atlas = std::make_unique<Texture>();