diff --git a/Data/resources/scripts/build-cylinder.scm b/Data/resources/scripts/build-cylinder.scm new file mode 100644 index 0000000..8303a95 --- /dev/null +++ b/Data/resources/scripts/build-cylinder.scm @@ -0,0 +1,17 @@ +; Routine to build a circle/cylinder shape :3 +(progn +; CONFIG + (define RESOLUTION 20.0) + (define (build-cylinder sx sy res radius) + (let* ((angle-increment (/ (* 2 PI) res)) + (points ())) + (do ((i 0 (+1 i))) + ((>= i res) (progn (print points) + (make-sector-from-points 0.0 4.0 points))) + (let* ((angle (* i angle-increment)) + (x (+ sx (* radius (cos angle)))) + (y (+ sy (* radius (sin angle))))) + (set! points (cons (x y) points)))))) + (fill-selection + (lambda (x1 y1 x2 y2 nh1 nh2 dir) + (build-cylinder (/ (+ x1 x2) 2) (/ (+ y1 y2) 2) RESOLUTION (* (abs (- x2 x1)) 0.5) )))) diff --git a/Data/sandbox-log.txt b/Data/sandbox-log.txt index e69de29..44f16d5 100644 --- a/Data/sandbox-log.txt +++ b/Data/sandbox-log.txt @@ -0,0 +1,70 @@ +[11:46:03 PM] Info: Starting... + + KP3D version 2 +=============================== + Copyright (C) kpworld.xyz 2018-2024 + Contact me! @kp_cftsz + +[11:46:03 PM] Info: Initializing SDL +[11:46:03 PM] Info: Initializing OpenGL +[11:46:03 PM] Info: OpenGL version: 4.6.0 NVIDIA 536.23 +[11:46:03 PM] Info: Initializing GLEW +[11:46:03 PM] Info: Initializing SDL_mixer +[11:46:03 PM] Info: Reticulating splines... +[11:46:03 PM] Info: Ready! +[11:46:03 PM] Info: Loading script: build-cylinder.scm +[11:46:03 PM] Info: Loading script: build-stairs.scm +[11:46:03 PM] Info: Loading material resource: block.png +[11:46:03 PM] Info: Found normal map texture: materials/block_n.png +[11:46:03 PM] Info: Loading material resource: brick2.jpg +[11:46:03 PM] Info: Found normal map texture: materials/brick2_n.jpg +[11:46:03 PM] Info: Loading material resource: bricks.jpg +[11:46:03 PM] Info: Found normal map texture: materials/bricks_n.jpg +[11:46:03 PM] Info: Loading material resource: FLAT5_7.png +[11:46:03 PM] Info: Found normal map texture: materials/FLAT5_7_n.png +[11:46:03 PM] Info: Loading material resource: floor0.png +[11:46:03 PM] Info: Found normal map texture: materials/floor0_n.png +[11:46:03 PM] Info: Loading material resource: floor1.png +[11:46:03 PM] Info: Found normal map texture: materials/floor1_n.png +[11:46:03 PM] Info: Loading material resource: GRASS2.png +[11:46:03 PM] Info: Found normal map texture: materials/GRASS2_n.png +[11:46:03 PM] Info: Loading material resource: hardwood.jpg +[11:46:03 PM] Info: Found normal map texture: materials/hardwood_n.jpg +[11:46:03 PM] Info: Map init +[11:46:03 PM] Info: Finalized mesh with 49 batches +[11:46:06 PM] Info: Finalized mesh with 54 batches +[11:46:07 PM] Info: Finalized mesh with 48 batches +[11:46:08 PM] Info: Finalized mesh with 54 batches +[11:46:09 PM] Info: Finalized mesh with 48 batches +[11:46:09 PM] Info: Finalized mesh with 54 batches +[11:46:09 PM] Info: Finalized mesh with 48 batches +[11:46:09 PM] Info: Finalized mesh with 54 batches +[11:46:09 PM] Info: Finalized mesh with 48 batches +[11:46:09 PM] Info: Finalized mesh with 54 batches +[11:46:09 PM] Info: Finalized mesh with 48 batches +[11:46:09 PM] Info: Finalized mesh with 54 batches +[11:46:10 PM] Info: Finalized mesh with 48 batches +[11:46:10 PM] Info: Finalized mesh with 54 batches +[11:46:10 PM] Info: Finalized mesh with 48 batches +[11:46:10 PM] Info: Finalized mesh with 54 batches +[11:46:10 PM] Info: Finalized mesh with 48 batches +[11:46:10 PM] Info: Finalized mesh with 54 batches +[11:46:10 PM] Info: Finalized mesh with 48 batches +[11:46:10 PM] Info: Finalized mesh with 54 batches +[11:46:11 PM] Info: Finalized mesh with 48 batches +[11:46:12 PM] Info: Finalized mesh with 54 batches +[11:46:12 PM] Info: Finalized mesh with 48 batches +[11:46:14 PM] Info: Finalized mesh with 54 batches +[11:46:14 PM] Info: Finalized mesh with 48 batches +[11:46:15 PM] Info: Finalized mesh with 54 batches +[11:46:15 PM] Info: Finalized mesh with 48 batches +[11:46:15 PM] Info: Finalized mesh with 54 batches +[11:46:15 PM] Info: Finalized mesh with 48 batches +[11:46:16 PM] Info: Finalized mesh with 54 batches +[11:46:16 PM] Info: Finalized mesh with 54 batches +[11:46:17 PM] Info: Finalized mesh with 54 batches +[11:46:17 PM] Info: Finalized mesh with 48 batches +[11:46:17 PM] Info: Finalized mesh with 54 batches +[11:46:18 PM] Info: Finalized mesh with 54 batches +[11:46:19 PM] Info: Finalized mesh with 54 batches +[11:46:19 PM] Info: Finalized mesh with 54 batches diff --git a/KP3Dii/ext/src/imgui/TextEditor.cpp b/KP3Dii/ext/src/imgui/TextEditor.cpp index ecea3ba..02c05c4 100644 --- a/KP3Dii/ext/src/imgui/TextEditor.cpp +++ b/KP3Dii/ext/src/imgui/TextEditor.cpp @@ -3005,7 +3005,9 @@ const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Lisp() langDef.mKeywords.insert(k); static const char* const identifiers[] = { - "sin", "cos", "sqrt", "abs", "log", "log10", "floor", "ceil", "atan2", "PI", "E", "car", "cdr", "cons", "list", "list-ref", "print", "exit", "+1", "-1", "null?", + // KSI + "sin", "cos", "sqrt", "abs", "log", "log10", "floor", "ceil", "atan2", "PI", "E", "car", "cdr", "cons", "list", + "list-ref", "print", "exit", "+1", "-1", "null?", // Map editor specific "make-sector-from-points", "fill-selection", @@ -3014,7 +3016,7 @@ const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Lisp() for (auto& k : identifiers) { Identifier id; - id.mDeclaration = "Built-in function"; + id.mDeclaration = "Built-in function/symbol"; langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); } diff --git a/KP3Dii/src/KP3D_SystemUtils.cpp b/KP3Dii/src/KP3D_SystemUtils.cpp index d6e74af..b2e3496 100644 --- a/KP3Dii/src/KP3D_SystemUtils.cpp +++ b/KP3Dii/src/KP3D_SystemUtils.cpp @@ -82,6 +82,11 @@ std::string GetAudioDir() return GetResourceDir() + "audio/"; } +std::string GetScriptsDir() +{ + return GetResourceDir() + "scripts/"; +} + ErrCode CreateDir(const std::string& path) { return static_cast(!std::filesystem::create_directory(path)); diff --git a/KP3Dii/src/KP3D_SystemUtils.h b/KP3Dii/src/KP3D_SystemUtils.h index 5a854f3..f0d1aa4 100644 --- a/KP3Dii/src/KP3D_SystemUtils.h +++ b/KP3Dii/src/KP3D_SystemUtils.h @@ -18,6 +18,7 @@ std::string GetModelDir(); std::string GetBitmapDir(); std::string GetFontDir(); std::string GetAudioDir(); +std::string GetScriptsDir(); ErrCode CreateDir(const std::string& path); ErrCode DeleteDir(const std::string& path); diff --git a/KP3Dii/src/ksi/KSI_Environment.cpp b/KP3Dii/src/ksi/KSI_Environment.cpp index 51e946e..2f8ab0f 100644 --- a/KP3Dii/src/ksi/KSI_Environment.cpp +++ b/KP3Dii/src/ksi/KSI_Environment.cpp @@ -533,7 +533,8 @@ Environment::Environment(Environment* outer_env): { } -Environment::Environment(const Cell& source) +Environment::Environment(const Cell& source): + m_outer_env(nullptr) { std::list list = std::get>(source.data); @@ -557,6 +558,11 @@ void Environment::Set(const std::string& sym_key, Cell cell_value, bool force) if (m_sym_table.count(sym_key) && !force) return; + // I realllllly don't think this is the behavior we want. + // It's just a hack to work around let* not handling nested environments correctly + if (m_outer_env) + m_outer_env->Set(sym_key, cell_value, force); + m_sym_table[sym_key] = cell_value; } @@ -564,7 +570,8 @@ Environment* Environment::Find(const std::string& sym_key) { if (m_sym_table.count(sym_key) != 0) return this; - else if (m_outer_env != nullptr) + + if (m_outer_env != nullptr) return m_outer_env->Find(sym_key); return nullptr; diff --git a/Sandbox/src/Editor.cpp b/Sandbox/src/Editor.cpp index 0031038..baf2f26 100644 --- a/Sandbox/src/Editor.cpp +++ b/Sandbox/src/Editor.cpp @@ -1,5 +1,7 @@ #include "Editor.h" +#include + #include #include #include @@ -14,6 +16,7 @@ #include "KP3D_StringUtils.h" #include "KP3D_SystemUtils.h" #include "KP3D_Resources.h" +#include "KP3D_Time.h" #include "Sandbox.h" @@ -84,6 +87,7 @@ ksi::Cell FMakeSectorFromPoints() s->walls.push_back(wall); } + sandbox->editor.PushAction(); sandbox->map.sectors.push_back(s); sandbox->editor.RebuildMap(); @@ -184,7 +188,6 @@ Editor::Editor() auto lang = TextEditor::LanguageDefinition::Lisp(); build_text_editor.SetLanguageDefinition(lang); - build_text_editor.SetText("()"); kp3d::EventBus::Subscribe(this, &Editor::OnScrollWheel); kp3d::EventBus::Subscribe(this, &Editor::OnKeyPress); @@ -192,6 +195,29 @@ Editor::Editor() // Put in our fancy Lisp integration stuff kp3d::console::environment.Set("make-sector-from-points", FMakeSectorFromPoints()); kp3d::console::environment.Set("fill-selection", FFillSelection()); + + // Load user scripts + m_scripts.emplace("New Script", "()"); + m_selected_script = "New Script"; + build_text_editor.SetText(m_scripts[m_selected_script]); + for (const auto& entry: std::filesystem::recursive_directory_iterator(kp3d::sys::GetScriptsDir())) + { + if (entry.is_directory()) + continue; + std::string path_str = entry.path().string(); + if (!kp3d::str::EndsWith(path_str, ".scm")) + continue; + std::string filename = std::filesystem::proximate(path_str, kp3d::sys::GetScriptsDir()).string(); + KP3D_LOG_INFO("Loading script: {}", filename); + std::string script; + kp3d::ErrCode status = kp3d::sys::LoadTextFile(path_str, script); + if (status == kp3d::FAILURE) + { + KP3D_LOG_ERROR("Failed to load script: {}", path_str); + continue; + } + m_scripts.emplace(filename, script); + } } Editor::~Editor() @@ -212,6 +238,16 @@ void Editor::Update() KEY_SHORTCUT(SPACE, m_mode = MODE_NORMAL); KEY_SHORTCUT(V, m_mode = MODE_BUILD); + KEY_SHORTCUT(Z, { + if (sandbox->IsKeyDown(kp3d::KEY_LCTRL)) + Undo(); + }); + + KEY_SHORTCUT(Y, { + if (sandbox->IsKeyDown(kp3d::KEY_LCTRL)) + Redo(); + }); + switch (m_mode) { case MODE_BUILD: UpdateModeBuild(); break; @@ -258,7 +294,10 @@ void Editor::RenderStem(kp3d::Vec3 position) void Editor::RebuildMap() { + double start = kp3d::CurrentTimeInMilliseconds().count(); sandbox->map.Rebuild(kp3d::GEN_NORMALS); + + m_build_time = kp3d::CurrentTimeInMilliseconds().count() - start; } void Editor::UpdateModeBuild() @@ -290,6 +329,7 @@ void Editor::UpdateModeBuild() s->walls.push_back(wall); } + sandbox->editor.PushAction(); sandbox->map.sectors.push_back(s); RebuildMap(); @@ -317,7 +357,9 @@ void Editor::UpdateModeBuild() { if (sandbox->IsKeyDown(kp3d::KEY_LCTRL)) { - build_show_options = true; + if (!points.empty()) + build_show_options = true; + // build_text_editor.SetText(m_scripts[m_selected_script]); } else { @@ -346,6 +388,7 @@ void Editor::UpdateModeBuild() s->walls.push_back(wall); } + sandbox->editor.PushAction(); sandbox->map.sectors.push_back(s); RebuildMap(); points.clear(); @@ -380,27 +423,50 @@ void Editor::RenderModeBuild() if (build_show_options) { - ImGui::SetNextWindowSize({320, 240}, ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize({500, 400}, ImGuiCond_FirstUseEver); if (ImGui::Begin("Build Options", &build_show_options)) { - ImGui::SeparatorText("Shortcuts"); - if (ImGui::Button("build-box")) {} ImGui::SameLine(); - if (ImGui::Button("build-stairs")) {} ImGui::SameLine(); - if (ImGui::Button("build-slope")) {} + // ImGui::SeparatorText("Shortcuts"); + // if (ImGui::Button("build-box")) {} ImGui::SameLine(); + // if (ImGui::Button("build-stairs")) {} ImGui::SameLine(); + // if (ImGui::Button("build-slope")) {} - ImGui::SeparatorText("Custom Routine"); - if (ImGui::Button("Evaluate")) + // Left + ImGui::BeginChild("left pane", ImVec2(150, 0), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeX); + for (const auto& [k, v]: m_scripts) + { + if (ImGui::Selectable(k.c_str(), m_selected_script == k)) + { + m_selected_script = k; + build_text_editor.SetText(m_scripts[m_selected_script]); + } + } + ImGui::EndChild(); + ImGui::SameLine(); + + // Right + ImGui::BeginGroup(); + ImGui::BeginChild("item view", ImVec2(0, -ImGui::GetFrameHeightWithSpacing())); // Leave room for 1 line below us + ImGui::Text("Script: %s", m_selected_script.c_str()); + ImGui::Separator(); + + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]); + build_text_editor.Render("Lisp:"); + ImGui::PopFont(); + + ImGui::EndChild(); + if (ImGui::Button("Revert")) + build_text_editor.SetText(m_scripts[m_selected_script]); + ImGui::SameLine(); + if (ImGui::Button("Run Script")) { std::string txt = build_text_editor.GetText(); kp3d::console::SendKSI(txt); points.clear(); build_has_start_pos = false; build_show_options = false; - // KP3D_LOG_INFO("EVAL: {}", txt); } - ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[1]); - build_text_editor.Render("Lisp:"); - ImGui::PopFont(); + ImGui::EndGroup(); ImGui::End(); } @@ -542,6 +608,7 @@ void Editor::UpdateModeNormal() const auto& info = std::any_cast(kp3d::editor_hovered_batch[0]->userdata); if (info.wall) { + sandbox->editor.PushAction(); // Remove a wall // This is kinda risky, might wanna pop open a warning or make a backup or something LOL for (const auto& sp : sandbox->map.sectors) @@ -567,6 +634,7 @@ void Editor::UpdateModeNormal() else if (info.sector) { // Remove a sector. We'll want to remove any portals first + sandbox->editor.PushAction(); for (const auto& sp: sandbox->map.sectors) { if (sp.get() == info.sector) @@ -612,6 +680,7 @@ void Editor::UpdateModeNormal() try { + sandbox->editor.PushAction(); const auto& info = std::any_cast(kp3d::editor_hovered_batch[0]->userdata); if (info.wall) { @@ -650,6 +719,7 @@ void Editor::UpdateModeNormal() { try { + sandbox->editor.PushAction(); const auto& info = std::any_cast(kp3d::editor_hovered_batch[0]->userdata); if (info.wall)// && !(info.wall->portal && (info.wall->flags & Wall::FLAG_OPENING) && !(info.wall->flags & Wall::FLAG_SUBSECTOR_OPENING))) { @@ -794,8 +864,8 @@ void Editor::RenderUI() } if (ImGui::BeginMenu("Edit")) { - if (ImGui::MenuItem("Undo")) {} - if (ImGui::MenuItem("Redo")) {} + if (ImGui::MenuItem("Undo")) { Undo(); } + if (ImGui::MenuItem("Redo")) { Redo(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("View")) @@ -926,6 +996,8 @@ void Editor::RenderUIInfo() ImGui::EndPopup(); } } + ImGui::Separator(); + ImGui::Text("Map built in %fms\n", m_build_time); ImGui::End(); } } @@ -1121,6 +1193,46 @@ void Editor::OnKeyPress(const kp3d::KeyPressEvent* e) { } +void Editor::PushAction() +{ + const size_t MAX_UNDOS = 50; + previous_maps.push_back({sandbox->map.sectors}); + if (previous_maps.size() > MAX_UNDOS) + previous_maps.erase(previous_maps.begin()); + changes++; +} + +void Editor::Undo() +{ + if (previous_maps.empty()) + return; + + next_maps.push_back({sandbox->map.sectors}); + sandbox->map.sectors = previous_maps.back().sectors; + previous_maps.pop_back(); + changes--; + RebuildMap(); +} + +void Editor::Redo() +{ + if (next_maps.empty()) + return; + + PushAction(); + sandbox->map.sectors = next_maps.back().sectors; + next_maps.pop_back(); + changes++; + RebuildMap(); +} + +void Editor::ResetActionStack() +{ + changes = 0; + previous_maps.clear(); + next_maps.clear(); +} + // void Editor::RenderLine(kp3d::Vec3 p0, kp3d::Vec3 p1, kp3d::uint color) diff --git a/Sandbox/src/Editor.h b/Sandbox/src/Editor.h index bf755d4..d051755 100644 --- a/Sandbox/src/Editor.h +++ b/Sandbox/src/Editor.h @@ -55,6 +55,21 @@ public: void OnScrollWheel(const kp3d::ScrollWheelEvent* e); void OnKeyPress(const kp3d::KeyPressEvent* e); + + // Undo/redo stack + struct MapData + { + std::vector> sectors; + // things... + }; + std::vector previous_maps; + std::vector next_maps; + int changes = 0; + void PushAction(); + void Undo(); + void Redo(); + void ResetActionStack(); + private: void RenderLine(kp3d::Vec3 start, kp3d::Vec3 end, kp3d::uint color); @@ -89,4 +104,9 @@ private: bool build_show_options = false; TextEditor build_text_editor; + std::map m_scripts; + std::string m_selected_script; + + double m_build_time = 0.0; + };