diff --git a/CMakeLists.txt b/CMakeLists.txt index 18851a2..ef48dee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ project(plush # options option(PLUSH_BUILD_EXAMPLES "Build Plush Examples" ON) +option(PLUSH_BUILD_EDITOR "Build Plush Editor" ON) option(PLUSH_USE_ASAN "Use Address Sanitizer" OFF) set(PLUSH_MAX_LIGHTS "32" CACHE STRING "Maximum number of lights in a single render pass") set(PLUSH_MAX_TRIANGLES "16384" CACHE STRING "Maximum number of triangles in a single render pass") @@ -77,6 +78,17 @@ endif() target_link_libraries(plush PUBLIC $<$:ASAN::ASAN>) +# editor + +if(PLUSH_BUILD_EDITOR) + find_package(SDL3 CONFIG COMPONENTS SDL3) + if(SDL3_FOUND) + add_subdirectory(editor) + else() + message(WARNING "SDL3 not found, cannot build editor") + endif() +endif() + # examples if(PLUSH_BUILD_EXAMPLES) diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt new file mode 100644 index 0000000..20a8408 --- /dev/null +++ b/editor/CMakeLists.txt @@ -0,0 +1,33 @@ +project(plush-editor LANGUAGES C CXX) + +include(FetchContent) + +FetchContent_Declare( + imgui + GIT_REPOSITORY https://github.com/ocornut/imgui.git + GIT_TAG v1.92.3 +) + +FetchContent_MakeAvailable(imgui) + +add_library(imgui STATIC + ${imgui_SOURCE_DIR}/imgui_demo.cpp + ${imgui_SOURCE_DIR}/imgui_draw.cpp + ${imgui_SOURCE_DIR}/imgui_tables.cpp + ${imgui_SOURCE_DIR}/imgui_widgets.cpp + ${imgui_SOURCE_DIR}/imgui.cpp + ${imgui_SOURCE_DIR}/backends/imgui_impl_sdl3.cpp + ${imgui_SOURCE_DIR}/backends/imgui_impl_sdlrenderer3.cpp +) + +target_include_directories(imgui PUBLIC + ${imgui_SOURCE_DIR} + ${imgui_SOURCE_DIR}/backends +) + +add_executable(plush-editor) +target_sources(plush-editor PRIVATE + ${PROJECT_SOURCE_DIR}/main.cpp +) + +target_link_libraries(plush-editor PUBLIC plush SDL3::SDL3 imgui) diff --git a/editor/main.cpp b/editor/main.cpp new file mode 100644 index 0000000..eef0616 --- /dev/null +++ b/editor/main.cpp @@ -0,0 +1,196 @@ + +#include +#include +#include +#include + +#include +#include + +#include "imgui.h" +#include "imgui_impl_sdl3.h" +#include "imgui_impl_sdlrenderer3.h" + +#define SDL_MAIN_USE_CALLBACKS 1 +#include + +static void quit() +{ + SDL_Event event; + event.quit.type = SDL_EVENT_QUIT; + event.quit.timestamp = SDL_GetTicksNS(); + SDL_PushEvent(&event); +} + +static SDL_AppResult die(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_CRITICAL, fmt, ap); + va_end(ap); + + SDL_Event event; + event.quit.type = SDL_EVENT_QUIT; + event.quit.timestamp = SDL_GetTicksNS(); + SDL_PushEvent(&event); + + return SDL_APP_FAILURE; +} + +class app_t { +public: + void EditorMain() + { + // setup flags + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; + + // setup size + int screen_w, screen_h; + SDL_GetRenderOutputSize(renderer, &screen_w, &screen_h); + ImGui::SetNextWindowSize(ImVec2(screen_w, screen_h), ImGuiCond_Always); + ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); + + // do it + if (ImGui::Begin("Main Menu", nullptr, window_flags)) + { + // main menu bar + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem("Load")) + ; + + if (ImGui::MenuItem("Save", "Ctrl+S")) + ; + + if (ImGui::MenuItem("Save As")) + ; + + if (ImGui::MenuItem("Quit", "Alt+F4")) + quit(); + + ImGui::EndMenu(); + } + + ImGui::EndMenuBar(); + } + + ImGui::End(); + } + + ImGui::ShowDemoWindow(nullptr); + } + + SDL_AppResult Iterate() + { + ImGui_ImplSDLRenderer3_NewFrame(); + ImGui_ImplSDL3_NewFrame(); + ImGui::NewFrame(); + + EditorMain(); + + ImGui::Render(); + ImGuiIO &io = ImGui::GetIO(); + SDL_SetRenderScale(renderer, io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); + SDL_SetRenderDrawColorFloat(renderer, clear_color.x, clear_color.y, clear_color.z, clear_color.w); + SDL_RenderClear(renderer); + ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), renderer); + SDL_RenderPresent(renderer); + + return SDL_APP_CONTINUE; + } + + SDL_AppResult Event(SDL_Event *event) + { + if ((event->type == SDL_EVENT_QUIT) || (event->type == SDL_EVENT_WINDOW_CLOSE_REQUESTED && event->window.windowID == SDL_GetWindowID(window))) + return SDL_APP_SUCCESS; + + ImGui_ImplSDL3_ProcessEvent(event); + + return SDL_APP_CONTINUE; + } + + app_t(const char *title, int w=1280, int h=720) : windowTitle(title), windowWidth(w), windowHeight(h) + { + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) + throw std::runtime_error(SDL_GetError()); + + if (!SDL_CreateWindowAndRenderer(windowTitle.c_str(), windowWidth, windowHeight, SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_MAXIMIZED, &window, &renderer)) + throw std::runtime_error(SDL_GetError()); + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO &io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; + io.IniFilename = nullptr; + io.LogFilename = nullptr; + + ImGui::StyleColorsDark(); + + ImGui_ImplSDL3_InitForSDLRenderer(window, renderer); + ImGui_ImplSDLRenderer3_Init(renderer); + + SDL_ShowWindow(window); + } + + ~app_t() + { + ImGui_ImplSDLRenderer3_Shutdown(); + ImGui_ImplSDL3_Shutdown(); + ImGui::DestroyContext(); + + if (renderer) SDL_DestroyRenderer(renderer); + if (window) SDL_DestroyWindow(window); + + SDL_Quit(); + } +private: + // SDL video state + SDL_Window *window; + SDL_Renderer *renderer; + + int windowWidth; + int windowHeight; + std::string windowTitle; + + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); +}; + +SDLMAIN_DECLSPEC SDL_AppResult SDLCALL SDL_AppInit(void **appstate, SDL_UNUSED int argc, SDL_UNUSED char *argv[]) +{ + app_t *app = NULL; + + try { + app = new app_t("Plush Scene Editor"); + } catch (const std::runtime_error &error) { + return die("%s", error.what()); + } + + *appstate = reinterpret_cast(app); + + return SDL_APP_CONTINUE; +} + +SDLMAIN_DECLSPEC SDL_AppResult SDLCALL SDL_AppIterate(void *appstate) +{ + app_t *app = reinterpret_cast(appstate); + + return app->Iterate(); +} + +SDLMAIN_DECLSPEC SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, SDL_Event *event) +{ + app_t *app = reinterpret_cast(appstate); + + return app->Event(event); +} + +SDLMAIN_DECLSPEC void SDLCALL SDL_AppQuit(void *appstate, SDL_UNUSED SDL_AppResult result) +{ + app_t *app = reinterpret_cast(appstate); + + delete app; +} diff --git a/editor/scenefmt.txt b/editor/scenefmt.txt new file mode 100644 index 0000000..b2a612a --- /dev/null +++ b/editor/scenefmt.txt @@ -0,0 +1,54 @@ +Namespace = "Plush"; +Name = "Test Scene"; +DefaultCamera = 0; + +// palette 0 +palette +{ + FirstColor = 0; + LastColor = 255; + File = "palette.pcx"; +} + +// camera 0 +camera +{ + Width = 640; + Height = 480; + Fov = 90; + zBuffer = true; + + Origin = [64, 64, -384]; + Angles = [15, 10, 0]; + + Palette = 0; +} + +// material 0 +material +{ + Name = "Fallback Material"; + Fallback = true; + + Ambient = [44, 8, 8]; + Diffuse = [156, 25, 25]; + Specular = [185, 159, 159]; +} + +// mesh 0 +mesh +{ + Name = "Polyrobo"; + + File = "polyrobo.obj"; + + Material = 0; +} + +// object 0 +object +{ + Name = "Polyrobo"; + + Mesh = 0; +}