From aa1f063a6dcc783caf913dc3059fea76632d5871 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sun, 28 Jan 2024 00:00:22 -0600 Subject: [PATCH] [jasper/world] Add WorldObjectEditor --- .../world/include/jasper/world/prefab.hpp | 2 +- .../world/include/jasper/world/world.hpp | 2 +- .../include/jasper/world/worldobject.hpp | 68 ++++ .../include/jasper/world/worldobjectset.hpp | 41 -- .../include/jasper/world/worldstatic.hpp | 8 +- .../modules/world/src/keel/keelmodule.cpp | 5 +- .../modules/world/src/studio/CMakeLists.txt | 2 +- .../modules/world/src/studio/studiomodule.cpp | 10 +- .../studio/worldeditor/worldeditor-imgui.cpp | 4 +- .../studio/worldeditor/worldeditor-imgui.hpp | 2 +- .../studio/worldobjecteditor/CMakeLists.txt | 5 - .../worldobjecteditor-imgui.cpp | 69 ---- .../worldobjecteditor-imgui.hpp | 38 -- .../worldobjecteditor/worldobjecteditor.cpp | 29 -- .../worldobjecteditor/worldobjecteditor.hpp | 34 -- .../worldobjectseteditor/CMakeLists.txt | 12 + .../worldobjectseteditor/collisionmapview.cpp | 175 +++++++++ .../worldobjectseteditor/collisionmapview.hpp | 64 ++++ .../commands/addobject.cpp | 37 ++ .../commands/addobject.hpp | 32 ++ .../commands/addpalette.cpp | 37 ++ .../commands/addpalette.hpp | 27 ++ .../commands/changetilesheet.cpp | 38 ++ .../commands/changetilesheet.hpp | 35 ++ .../commands/commands.hpp | 23 ++ .../commands/editobject.cpp | 114 ++++++ .../commands/editobject.hpp | 72 ++++ .../commands/editpalette.cpp | 28 ++ .../commands/editpalette.hpp | 28 ++ .../commands/rmobject.cpp | 28 ++ .../commands/rmobject.hpp | 28 ++ .../commands/rmpalette.cpp | 33 ++ .../commands/rmpalette.hpp | 28 ++ .../worldobjectseteditor-imgui.cpp | 360 ++++++++++++++++++ .../worldobjectseteditor-imgui.hpp | 86 +++++ src/jasper/modules/world/src/world.cpp | 6 +- 36 files changed, 1375 insertions(+), 235 deletions(-) create mode 100644 src/jasper/modules/world/include/jasper/world/worldobject.hpp delete mode 100644 src/jasper/modules/world/include/jasper/world/worldobjectset.hpp delete mode 100644 src/jasper/modules/world/src/studio/worldobjecteditor/CMakeLists.txt delete mode 100644 src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor-imgui.cpp delete mode 100644 src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor-imgui.hpp delete mode 100644 src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor.cpp delete mode 100644 src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor.hpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/CMakeLists.txt create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/collisionmapview.cpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/collisionmapview.hpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/commands/addobject.cpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/commands/addobject.hpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/commands/addpalette.cpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/commands/addpalette.hpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/commands/changetilesheet.cpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/commands/changetilesheet.hpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/commands/commands.hpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/commands/editobject.cpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/commands/editobject.hpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/commands/editpalette.cpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/commands/editpalette.hpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/commands/rmobject.cpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/commands/rmobject.hpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/commands/rmpalette.cpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/commands/rmpalette.hpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/worldobjectseteditor-imgui.cpp create mode 100644 src/jasper/modules/world/src/studio/worldobjectseteditor/worldobjectseteditor-imgui.hpp diff --git a/src/jasper/modules/world/include/jasper/world/prefab.hpp b/src/jasper/modules/world/include/jasper/world/prefab.hpp index e2714bf..25d5198 100644 --- a/src/jasper/modules/world/include/jasper/world/prefab.hpp +++ b/src/jasper/modules/world/include/jasper/world/prefab.hpp @@ -15,7 +15,7 @@ namespace jasper::world { struct PrefabDoc { - constexpr static auto TypeName = "net.drinkingtea.nostalgia.world.PrefabDoc"; + constexpr static auto TypeName = "net.drinkingtea.jasper.world.PrefabDoc"; constexpr static auto TypeVersion = 1; constexpr static auto Preloadable = true; diff --git a/src/jasper/modules/world/include/jasper/world/world.hpp b/src/jasper/modules/world/include/jasper/world/world.hpp index 4e5a9b1..8b06c64 100644 --- a/src/jasper/modules/world/include/jasper/world/world.hpp +++ b/src/jasper/modules/world/include/jasper/world/world.hpp @@ -8,7 +8,7 @@ #include "consts.hpp" #include "prefab.hpp" -#include "worldobjectset.hpp" +#include "worldobject.hpp" #include "worldstatic.hpp" namespace jasper::world { diff --git a/src/jasper/modules/world/include/jasper/world/worldobject.hpp b/src/jasper/modules/world/include/jasper/world/worldobject.hpp new file mode 100644 index 0000000..a52edcc --- /dev/null +++ b/src/jasper/modules/world/include/jasper/world/worldobject.hpp @@ -0,0 +1,68 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include +#include + +namespace jasper::world { + +namespace ncore = nostalgia::core; + +struct PaletteCycle { + static constexpr auto TypeName = "net.drinkingtea.jasper.world.PaletteCycle"; + static constexpr auto TypeVersion = 1; + static constexpr auto Preloadable = true; + ox::FileAddress palette; + uint16_t intervalMs = 1000; +}; + +oxModelBegin(PaletteCycle) + oxModelField(palette) + oxModelField(intervalMs) +oxModelEnd() + + +struct WorldObject { + static constexpr auto TypeName = "net.drinkingtea.jasper.world.WorldObject"; + static constexpr auto TypeVersion = 1; + static constexpr auto Preloadable = true; + int id{}; + ox::String name; + uint16_t palBank{}; + ncore::SubSheetId subsheetId{}; + uint64_t collisionMap{}; +}; + +oxModelBegin(WorldObject) + oxModelField(id) + oxModelField(name) + oxModelField(palBank) + oxModelField(subsheetId) + oxModelField(collisionMap) +oxModelEnd() + + +struct WorldObjectSet { + static constexpr auto TypeName = "net.drinkingtea.jasper.world.WorldObjectSet"; + static constexpr auto TypeVersion = 1; + static constexpr auto Preloadable = true; + int objIdIdx = 0; + ox::FileAddress tilesheet; + ox::Vector palettes; + ox::Vector objects; +}; + +oxModelBegin(WorldObjectSet) + oxModelField(objIdIdx) + oxModelField(tilesheet) + oxModelField(palettes) + oxModelField(objects) +oxModelEnd() + + +ox::Error loadObjectSet(WorldObjectSet const&os) noexcept; + +} diff --git a/src/jasper/modules/world/include/jasper/world/worldobjectset.hpp b/src/jasper/modules/world/include/jasper/world/worldobjectset.hpp deleted file mode 100644 index 08927cf..0000000 --- a/src/jasper/modules/world/include/jasper/world/worldobjectset.hpp +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. - */ - -#pragma once - -#include - -#include "consts.hpp" -#include "prefab.hpp" -#include "worldstatic.hpp" - -namespace jasper::world { - -namespace ncore = nostalgia::core; - -struct WorldObject { - static constexpr auto TypeName = "net.drinkingtea.nostalgia.world.WorldObject"; - static constexpr auto TypeVersion = 1; - static constexpr auto Preloadable = true; - ox::FileAddress tilesheet; - ncore::SubSheetId subSheetId; -}; - -oxModelBegin(WorldObject) - oxModelField(tilesheet) - oxModelField(subsheetId) -oxModelEnd() - -struct WorldObjectSet { - static constexpr auto TypeName = "net.drinkingtea.nostalgia.world.WorldObjectSet"; - static constexpr auto TypeVersion = 1; - static constexpr auto Preloadable = true; - ox::Vector objects; -}; - -oxModelBegin(WorldObjectSet) - oxModelField(objects) -oxModelEnd() - -} diff --git a/src/jasper/modules/world/include/jasper/world/worldstatic.hpp b/src/jasper/modules/world/include/jasper/world/worldstatic.hpp index 4eebc7b..92a2c5c 100644 --- a/src/jasper/modules/world/include/jasper/world/worldstatic.hpp +++ b/src/jasper/modules/world/include/jasper/world/worldstatic.hpp @@ -18,7 +18,7 @@ namespace ncore = nostalgia::core; struct SpriteDoc { - constexpr static auto TypeName = "net.drinkingtea.nostalgia.world.SpriteDoc"; + constexpr static auto TypeName = "net.drinkingtea.jasper.world.SpriteDoc"; constexpr static auto TypeVersion = 1; constexpr static auto Preloadable = true; @@ -29,7 +29,7 @@ struct SpriteDoc { struct TileDoc { - constexpr static auto TypeName = "net.drinkingtea.nostalgia.world.TileDoc"; + constexpr static auto TypeName = "net.drinkingtea.jasper.world.TileDoc"; constexpr static auto TypeVersion = 1; constexpr static auto Preloadable = true; @@ -72,7 +72,7 @@ struct WorldDoc { using TileMapLayer = ox::Vector; using TileMap = ox::Vector; - constexpr static auto TypeName = "net.drinkingtea.nostalgia.world.WorldDoc"; + constexpr static auto TypeName = "net.drinkingtea.jasper.world.WorldDoc"; constexpr static auto TypeVersion = 1; constexpr static auto Preloadable = true; @@ -141,7 +141,7 @@ constexpr unsigned rightEdge(uint8_t layerAttachments) noexcept { struct WorldStatic { - constexpr static auto TypeName = "net.drinkingtea.nostalgia.world.WorldStatic"; + constexpr static auto TypeName = "net.drinkingtea.jasper.world.WorldStatic"; constexpr static auto TypeVersion = 1; constexpr static auto Preloadable = true; diff --git a/src/jasper/modules/world/src/keel/keelmodule.cpp b/src/jasper/modules/world/src/keel/keelmodule.cpp index 17d58b9..4b377c7 100644 --- a/src/jasper/modules/world/src/keel/keelmodule.cpp +++ b/src/jasper/modules/world/src/keel/keelmodule.cpp @@ -4,6 +4,7 @@ #include +#include #include #include "typeconv.hpp" @@ -23,13 +24,15 @@ class WorldModule: public keel::Module { [[nodiscard]] ox::Vector types() const noexcept override { return { + keel::generateTypeDesc, + keel::generateTypeDesc, keel::generateTypeDesc, keel::generateTypeDesc, }; } [[nodiscard]] - ox::Vector converters() const noexcept override { + ox::Vector converters() const noexcept override { return { &m_worldDocToWorldStaticConverter, }; diff --git a/src/jasper/modules/world/src/studio/CMakeLists.txt b/src/jasper/modules/world/src/studio/CMakeLists.txt index 5ab1a07..23aa93c 100644 --- a/src/jasper/modules/world/src/studio/CMakeLists.txt +++ b/src/jasper/modules/world/src/studio/CMakeLists.txt @@ -16,5 +16,5 @@ install( ${NOSTALGIA_DIST_MODULE} ) -add_subdirectory(worldobjecteditor) +add_subdirectory(worldobjectseteditor) add_subdirectory(worldeditor) diff --git a/src/jasper/modules/world/src/studio/studiomodule.cpp b/src/jasper/modules/world/src/studio/studiomodule.cpp index 9959b51..f9f5cdd 100644 --- a/src/jasper/modules/world/src/studio/studiomodule.cpp +++ b/src/jasper/modules/world/src/studio/studiomodule.cpp @@ -6,22 +6,22 @@ #include -#include "worldobjecteditor/worldobjecteditor-imgui.hpp" +#include "worldobjectseteditor/worldobjectseteditor-imgui.hpp" #include "worldeditor/worldeditor-imgui.hpp" namespace jasper::world { class StudioModule: public studio::Module { public: - ox::Vector editors(turbine::Context &ctx) const noexcept override { + ox::Vector editors(studio::StudioContext &ctx) const noexcept override { return { - studio::editorMaker(ctx, FileExt_jwob), + studio::editorMaker(ctx, FileExt_jwob), studio::editorMaker(ctx, FileExt_jwld), }; } - ox::Vector> itemMakers(turbine::Context&) const noexcept override { + ox::Vector> itemMakers(studio::StudioContext&) const noexcept override { ox::Vector> out; - out.emplace_back(ox::make>("World Object", "WorldObjects", FileExt_jwob, ox::ClawFormat::Organic)); + out.emplace_back(ox::make>("World Object Set", "WorldObjectSets", FileExt_jwob, ox::ClawFormat::Organic)); out.emplace_back(ox::make>("World", "Worlds", FileExt_jwld, ox::ClawFormat::Organic)); return out; } diff --git a/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.cpp b/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.cpp index 8dd9ead..5b39d81 100644 --- a/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.cpp +++ b/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.cpp @@ -10,9 +10,9 @@ namespace jasper::world { -WorldEditorImGui::WorldEditorImGui(turbine::Context &ctx, ox::StringView path): +WorldEditorImGui::WorldEditorImGui(studio::StudioContext &ctx, ox::StringView path): Editor(path), - m_ctx(ctx), + m_ctx(ctx.tctx), m_editor(m_ctx, path), m_view(m_ctx, m_editor.world()) { setRequiresConstantRefresh(false); diff --git a/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.hpp b/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.hpp index 424e4c4..c7a064c 100644 --- a/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.hpp +++ b/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.hpp @@ -21,7 +21,7 @@ class WorldEditorImGui: public studio::Editor { WorldEditorView m_view; public: - WorldEditorImGui(turbine::Context &ctx, ox::StringView path); + WorldEditorImGui(studio::StudioContext &ctx, ox::StringView path); void draw(turbine::Context&) noexcept final; diff --git a/src/jasper/modules/world/src/studio/worldobjecteditor/CMakeLists.txt b/src/jasper/modules/world/src/studio/worldobjecteditor/CMakeLists.txt deleted file mode 100644 index 19ce204..0000000 --- a/src/jasper/modules/world/src/studio/worldobjecteditor/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -target_sources( - JasperWorld-Studio PRIVATE - worldobjecteditor.cpp - worldobjecteditor-imgui.cpp -) diff --git a/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor-imgui.cpp b/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor-imgui.cpp deleted file mode 100644 index e6946e3..0000000 --- a/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor-imgui.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. - */ - -#include "worldobjecteditor-imgui.hpp" - -namespace jasper::world { - -WorldObjectEditorImGui::WorldObjectEditorImGui(turbine::Context &ctx, ox::StringView path): - Editor(path), - m_sctx(*applicationData(ctx)), - m_editor(ctx, path) { -} - -void WorldObjectEditorImGui::draw(turbine::Context&) noexcept { - const auto paneSize = ImGui::GetContentRegionAvail(); - constexpr auto attrEditorWidth = 400.f; - ImGui::BeginChild("Preview", ImVec2(paneSize.x - attrEditorWidth, 0)); - ImGui::EndChild(); - ImGui::SameLine(); - ImGui::BeginChild("AttrEditor", ImVec2(attrEditorWidth, 0), true); - drawAttrEditor(); - drawTileSheetSelector(); - ImGui::EndChild(); -} - -void WorldObjectEditorImGui::onActivated() noexcept { -} - -void WorldObjectEditorImGui::drawAttrEditor() noexcept { - static constexpr auto boundDimension = [](int &v) noexcept { - v = ox::clamp(v, 1, 4); - }; - auto &doc = m_editor.doc(); - ImGui::InputInt("Footprint Width", &doc.footprint.width); - ImGui::InputInt("Footprint Height", &doc.footprint.height); - ImGui::InputInt("Visible Width", &doc.visibleSz.width); - ImGui::InputInt("Visible Height", &doc.visibleSz.height); - boundDimension(doc.footprint.width); - boundDimension(doc.footprint.height); - boundDimension(doc.visibleSz.width); - boundDimension(doc.visibleSz.height); -} - -void WorldObjectEditorImGui::drawTileSheetSelector() noexcept { - auto const&tilesheetList = m_sctx.project->fileList(ncore::FileExt_ng); - auto const first = m_selectedTilesheetIdx < tilesheetList.size() ? - tilesheetList[m_selectedTilesheetIdx].c_str() : ""; - if (ImGui::BeginCombo("Tile Sheet", first, 0)) { - for (auto i = 0u; i < tilesheetList.size(); ++i) { - auto const selected = (m_selectedTilesheetIdx == i); - if (ImGui::Selectable(tilesheetList[i].c_str(), selected) && - m_selectedTilesheetIdx != i) { - m_selectedTilesheetIdx = i; - //oxLogError(m_model.setPalette(tilesheetList[n])); - } - if (selected) { - ImGui::SetItemDefaultFocus(); - } - } - ImGui::EndCombo(); - } -} - -ox::Error WorldObjectEditorImGui::saveItem() noexcept { - return m_editor.saveItem(); -} - -} diff --git a/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor-imgui.hpp b/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor-imgui.hpp deleted file mode 100644 index 7e160c0..0000000 --- a/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor-imgui.hpp +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. - */ - -#pragma once - -#include - -#include - -#include "worldobjecteditor.hpp" - -namespace jasper::world { - -class WorldObjectEditorImGui: public studio::Editor { - - private: - studio::StudioContext &m_sctx; - PrefabEditor m_editor; - uint_t m_selectedTilesheetIdx{}; - - public: - WorldObjectEditorImGui(turbine::Context &ctx, ox::StringView path); - - void draw(turbine::Context&) noexcept final; - - void onActivated() noexcept override; - - protected: - void drawAttrEditor() noexcept; - - void drawTileSheetSelector() noexcept; - - ox::Error saveItem() noexcept final; - -}; - -} diff --git a/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor.cpp b/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor.cpp deleted file mode 100644 index 3328457..0000000 --- a/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor.cpp +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. - */ - -#include - -#include "worldobjecteditor.hpp" - -namespace jasper::world { - -PrefabEditor::PrefabEditor(turbine::Context &ctx, ox::StringView path): - m_ctx(*applicationData(ctx)), - m_itemPath(path), - m_doc(*readObj(keelCtx(ctx), path).unwrapThrow()) { -} - -PrefabDoc const&PrefabEditor::doc() const noexcept { - return m_doc; -} - -PrefabDoc &PrefabEditor::doc() noexcept { - return m_doc; -} - -ox::Error PrefabEditor::saveItem() noexcept { - return m_ctx.project->writeObj(m_itemPath, m_doc, ox::ClawFormat::Organic); -} - -} diff --git a/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor.hpp b/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor.hpp deleted file mode 100644 index b9cf461..0000000 --- a/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor.hpp +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. - */ - -#pragma once - -#include -#include - -#include - -namespace jasper::world { - -class PrefabEditor { - - private: - studio::StudioContext &m_ctx; - ox::String m_itemPath; - PrefabDoc m_doc; - - public: - PrefabEditor(turbine::Context &ctx, ox::StringView path); - - [[nodiscard]] - PrefabDoc const&doc() const noexcept; - - [[nodiscard]] - PrefabDoc &doc() noexcept; - - ox::Error saveItem() noexcept; - -}; - -} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/CMakeLists.txt b/src/jasper/modules/world/src/studio/worldobjectseteditor/CMakeLists.txt new file mode 100644 index 0000000..51945f2 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/CMakeLists.txt @@ -0,0 +1,12 @@ +target_sources( + JasperWorld-Studio PRIVATE + commands/addobject.cpp + commands/rmobject.cpp + commands/editobject.cpp + commands/addpalette.cpp + commands/rmpalette.cpp + commands/editpalette.cpp + commands/changetilesheet.cpp + collisionmapview.cpp + worldobjectseteditor-imgui.cpp +) diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/collisionmapview.cpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/collisionmapview.cpp new file mode 100644 index 0000000..9d33ace --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/collisionmapview.cpp @@ -0,0 +1,175 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include "collisionmapview.hpp" + +namespace jasper::world { + +uint64_t mapIdx(auto x, auto y) noexcept { + return static_cast(y) * 8 + static_cast(x); +} + +const glutils::ProgramSource CollisionView::s_programSrc = { + .shaderParams = { + { + .len = 2, + .name = ox::String("vPosition"), + }, + { + .len = 1, + .name = ox::String("vSelection"), + }, + }, + .vertShader = ox::sfmt(R"( + {} + in vec2 vPosition; + in float vSelection; + out float fSelection; + void main() { + gl_Position = vec4(vPosition, 0.0, 1.0); + fSelection = vSelection; + })", ncore::gl::GlslVersion), + .fragShader = ox::sfmt(R"( + {} + in float fSelection; + out vec4 outColor; + void main() { + outColor = vec4(0.0, 0.7, 1.0, 0.4) * fSelection; + })", ncore::gl::GlslVersion), +}; + +CollisionView::CollisionView(studio::StudioContext &sctx): + m_nctx(ncore::init(sctx.tctx, { + .glInstallDrawer = false, + .glSpriteCount = 0, + }).unwrapThrow()), + m_frameBuffer(glutils::generateFrameBuffer(240 * s_scale, 160 * s_scale)) { + m_bufferSet.vao = glutils::generateVertexArrayObject(); + m_bufferSet.vbo = glutils::generateBuffer(); + m_bufferSet.ebo = glutils::generateBuffer(); + glBindVertexArray(m_bufferSet.vao); + sendVbo(m_bufferSet); + sendEbo(m_bufferSet); + m_shader = glutils::buildShaderProgram(s_programSrc).unwrapThrow(); + glBindVertexArray(0); +} + +ox::Error CollisionView::setup( + ox::FileAddress const&tsAddr, + ox::FileAddress const&palAddr, + int const w, + int h, + uint_t tile, + uint64_t colMap) noexcept { + m_subsheetTilesWidth = w; + m_subsheetTilesHeight = h; + m_sheetTileStart = tile; + ncore::setBgStatus(*m_nctx, 0, true); + oxReturnError(ncore::loadBgTileSheet(*m_nctx, 0, tsAddr)); + oxReturnError(ncore::loadBgPalette(*m_nctx, 0, palAddr)); + buildGlBuffers(colMap); + return {}; +} + +void CollisionView::draw() noexcept { + glutils::FrameBufferBind const frameBufferBind(m_frameBuffer); + ncore::gl::draw(*m_nctx, s_scale); + //glViewport(0, 0, m_frameBuffer.width, m_frameBuffer.height); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glUseProgram(m_shader); + glBindVertexArray(m_bufferSet.vao); + sendVbo(m_bufferSet); + sendEbo(m_bufferSet); + auto const elmCnt = static_cast(m_bufferSet.elements.size()); + glDrawElements(GL_TRIANGLES, elmCnt, GL_UNSIGNED_INT, nullptr); + glBindVertexArray(0); + glUseProgram(0); + glDisable(GL_BLEND); +} + +int CollisionView::scale() const noexcept { + return s_scale; +} + +glutils::FrameBuffer const&CollisionView::framebuffer() const noexcept { + return m_frameBuffer; +} + +void CollisionView::setPixelBufferObject( + unsigned vertexRow, + float x, float y, + bool const selected, + float *vbo, + GLuint *ebo) const noexcept { + auto constexpr xmod = static_cast(ncore::TileWidth) / 240.f * 2; + auto constexpr ymod = static_cast(ncore::TileHeight) / 160.f * 2; + x *= xmod; + y *= -ymod; + x -= 1.0f; + y += 1.0f - ymod; + //x = -1; + //y = -1; + auto const selection = 1.f * static_cast(selected); + // don't worry, these memcpys gets optimized to something much more ideal + std::array const vertices{ + x, y, selection, // bottom left + x + xmod, y, selection, // bottom right + x + xmod, y + ymod, selection, // top right + x, y + ymod, selection, // top left + }; + memcpy(vbo, vertices.data(), sizeof(vertices)); + ox::Array const elms{ + vertexRow + 0, vertexRow + 1, vertexRow + 2, + vertexRow + 2, vertexRow + 3, vertexRow + 0, + }; + memcpy(ebo, elms.data(), sizeof(elms)); +} + +bool CollisionView::click(ox::Vec2 const&pos, uint64_t &colMap) noexcept { + auto const inMap = colMap; + auto const x = static_cast(pos.x * static_cast(m_subsheetTilesWidth)); + auto const y = static_cast(pos.y * static_cast(m_subsheetTilesHeight)); + uint64_t const idx = mapIdx(x, y); + uint64_t const colOn = (colMap >> idx) & 1; + colMap ^= uint64_t{1} << idx; + colMap |= static_cast(!colOn) << idx; + buildGlBuffers(colMap); + return inMap != colMap; +} + +void CollisionView::buildGlBuffers(uint64_t colMap) noexcept { + auto const vboLength = static_cast(s_programSrc.rowLen) * 4; + auto constexpr eboLength = 6; + auto const tileCnt = + static_cast(m_subsheetTilesWidth) * static_cast(m_subsheetTilesHeight); + m_bufferSet.vertices.resize(tileCnt * vboLength); + m_bufferSet.elements.resize(tileCnt * eboLength); + size_t i = 0; + auto tile = m_sheetTileStart; + for (auto y = 0; y < m_subsheetTilesHeight; ++y) { + for (auto x = 0; x < m_subsheetTilesWidth; ++x) { + ncore::setBgTile(*m_nctx, 0, x, y, tile); + auto const vbo = &m_bufferSet.vertices[i * vboLength]; + auto const ebo = &m_bufferSet.elements[i * eboLength]; + setPixelBufferObject( + static_cast(i * 4), + static_cast(x), + static_cast(y), + (colMap >> mapIdx(x, y)) & 1, + vbo, + ebo); + ++i; + ++tile; + } + } + glUseProgram(m_shader); + glBindVertexArray(m_bufferSet.vao); + sendVbo(m_bufferSet); + sendEbo(m_bufferSet); + glBindVertexArray(0); + glUseProgram(0); +} + +} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/collisionmapview.hpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/collisionmapview.hpp new file mode 100644 index 0000000..9652ed1 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/collisionmapview.hpp @@ -0,0 +1,64 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +#include +#include + +namespace jasper::world { + +namespace ncore = nostalgia::core; + +class CollisionView { + private: + static const glutils::ProgramSource s_programSrc; + static constexpr int s_scale = 10; + ncore::ContextUPtr m_nctx; + glutils::FrameBuffer m_frameBuffer; + glutils::GLProgram m_shader; + glutils::BufferSet m_bufferSet; + uint_t m_sheetTileStart{}; + int m_subsheetTilesWidth{}; + int m_subsheetTilesHeight{}; + + public: + CollisionView(studio::StudioContext &sctx); + + ox::Error setup( + ox::FileAddress const&tsAddr, + ox::FileAddress const&palAddr, + int w, + int h, + uint_t tile, + uint64_t colMap) noexcept; + + void draw() noexcept; + + [[nodiscard]] + int scale() const noexcept; + + [[nodiscard]] + glutils::FrameBuffer const&framebuffer() const noexcept; + + void setPixelBufferObject( + unsigned vertexRow, + float x, float y, + bool selected, + float *vbo, + GLuint *ebo) const noexcept; + + /** + * @return true if colMap changes + */ + bool click(ox::Vec2 const&pos, uint64_t &colMap) noexcept; + + private: + void buildGlBuffers(uint64_t colMap) noexcept; + +}; + +} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/addobject.cpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/addobject.cpp new file mode 100644 index 0000000..514b7ad --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/addobject.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include "commands.hpp" +#include "addobject.hpp" + +namespace jasper::world { + +AddObject::AddObject(WorldObjectSet &doc) noexcept: + m_doc(doc) { + m_obj.id = m_doc.objIdIdx + 1; + m_obj.name = ox::sfmt("Object {}", m_obj.id); + for (auto const&o : m_doc.objects) { + if (o.name > m_obj.name) { + break; + } + ++m_insertIdx; + } +} + +void AddObject::redo() noexcept { + ++m_doc.objIdIdx; + m_doc.objects.emplace(m_insertIdx, std::move(m_obj)); +} + +void AddObject::undo() noexcept { + m_obj = std::move(m_doc.objects[m_insertIdx]); + oxIgnoreError(m_doc.objects.erase(m_insertIdx)); + --m_doc.objIdIdx; +} + +int AddObject::commandId() const noexcept { + return static_cast(WorldObjCommand::AddObject); +} + +} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/addobject.hpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/addobject.hpp new file mode 100644 index 0000000..dee0b9c --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/addobject.hpp @@ -0,0 +1,32 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +#include + +#include + +namespace jasper::world { + +class AddObject: public studio::UndoCommand { + private: + WorldObjectSet &m_doc; + size_t m_insertIdx{}; + WorldObject m_obj{}; + public: + AddObject(WorldObjectSet &doc) noexcept; + void redo() noexcept override; + void undo() noexcept override; + [[nodiscard]] + int commandId() const noexcept override; + [[nodiscard]] + inline size_t insertIdx() const noexcept { + return m_insertIdx; + } +}; + +} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/addpalette.cpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/addpalette.cpp new file mode 100644 index 0000000..e1c8d3e --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/addpalette.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#include "commands.hpp" +#include "addpalette.hpp" + +namespace jasper::world { + +AddPalette::AddPalette(WorldObjectSet &doc, ox::FileAddress palAddr) noexcept: + m_doc(doc), + m_palAddr(std::move(palAddr)) { +} + +void AddPalette::redo() noexcept { + m_doc.palettes.emplace_back(PaletteCycle{ + .palette = m_palAddr + }); + std::sort(m_doc.palettes.begin(), m_doc.palettes.end(), [](PaletteCycle const&a, PaletteCycle const&b) { + return a.palette.getPath().or_value("") < b.palette.getPath().or_value(""); + }); +} + +void AddPalette::undo() noexcept { + auto const idx = std::find_if(m_doc.palettes.begin(), m_doc.palettes.end(), [this](PaletteCycle const&v) { + return v.palette == m_palAddr; + }); + oxIgnoreError(m_doc.palettes.erase(idx)); +} + +int AddPalette::commandId() const noexcept { + return static_cast(WorldObjCommand::AddPalette); +} + +} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/addpalette.hpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/addpalette.hpp new file mode 100644 index 0000000..b9d9a91 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/addpalette.hpp @@ -0,0 +1,27 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +#include + +#include + +namespace jasper::world { + +class AddPalette: public studio::UndoCommand { + private: + WorldObjectSet &m_doc; + ox::FileAddress m_palAddr; + public: + AddPalette(WorldObjectSet &doc, ox::FileAddress palAddr) noexcept; + void redo() noexcept override; + void undo() noexcept override; + [[nodiscard]] + int commandId() const noexcept override; +}; + +} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/changetilesheet.cpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/changetilesheet.cpp new file mode 100644 index 0000000..1b74ec0 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/changetilesheet.cpp @@ -0,0 +1,38 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include "commands.hpp" +#include "changetilesheet.hpp" + +namespace jasper::world { + +ChangeTileSheet::ChangeTileSheet( + WorldObjectSet &doc, + ox::FileAddress newVal, + size_t newIdx, + size_t &selectedTilesheetIdx) noexcept: + m_doc(doc), + m_newVal(std::move(newVal)), + m_oldIdx(selectedTilesheetIdx), + m_newIdx(newIdx), + m_selectedTilesheetIdx(selectedTilesheetIdx) { +} + +void ChangeTileSheet::redo() noexcept { + m_oldVal = std::move(m_doc.tilesheet); + m_doc.tilesheet = std::move(m_newVal); + m_selectedTilesheetIdx = m_newIdx; +} + +void ChangeTileSheet::undo() noexcept { + m_newVal = std::move(m_doc.tilesheet); + m_doc.tilesheet = std::move(m_oldVal); + m_selectedTilesheetIdx = m_oldIdx; +} + +int ChangeTileSheet::commandId() const noexcept { + return static_cast(WorldObjCommand::ChangeTileSheet); +} + +} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/changetilesheet.hpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/changetilesheet.hpp new file mode 100644 index 0000000..6fcd358 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/changetilesheet.hpp @@ -0,0 +1,35 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +#include + +#include + +namespace jasper::world { + +class ChangeTileSheet: public studio::UndoCommand { + private: + WorldObjectSet &m_doc; + ox::FileAddress m_newVal; + ox::FileAddress m_oldVal; + size_t m_oldIdx{}; + size_t m_newIdx{}; + size_t &m_selectedTilesheetIdx; + public: + ChangeTileSheet( + WorldObjectSet &doc, + ox::FileAddress newVal, + size_t newIdx, + size_t &selectedTilesheetIdx) noexcept; + void redo() noexcept override; + void undo() noexcept override; + [[nodiscard]] + int commandId() const noexcept override; +}; + +} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/commands.hpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/commands.hpp new file mode 100644 index 0000000..9daadb2 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/commands.hpp @@ -0,0 +1,23 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +namespace jasper::world { + +enum class WorldObjCommand { + None, + AddObject, + RmObject, + EditObjectName, + EditObjectPalette, + EditObjectSubSheet, + EditObjectCollisionMap, + AddPalette, + RmPalette, + EditPalette, + ChangeTileSheet, +}; + +} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/editobject.cpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/editobject.cpp new file mode 100644 index 0000000..22475c1 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/editobject.cpp @@ -0,0 +1,114 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include "commands.hpp" +#include "editobject.hpp" + +#include + +namespace jasper::world { + +EditObjectName::EditObjectName(WorldObjectSet &doc, size_t objIdx, ox::String newName) noexcept: + m_doc(doc), + m_objIdx(objIdx), + m_oldVal(m_doc.objects[objIdx].name), + m_newVal(std::move(newName)) { +} + +void EditObjectName::redo() noexcept { + auto &obj = m_doc.objects[m_objIdx]; + obj.name = std::move(m_newVal); +} + +void EditObjectName::undo() noexcept { + auto &obj = m_doc.objects[m_objIdx]; + m_newVal = std::move(obj.name); + obj.name = m_oldVal; +} + +int EditObjectName::commandId() const noexcept { + return static_cast(WorldObjCommand::EditObjectName); +} + +bool EditObjectName::mergeWith(UndoCommand const*cmd) noexcept { + auto const en = dynamic_cast(cmd); + if (en && m_objIdx == en->m_objIdx) { + m_newVal = en->m_newVal; + return true; + } + return false; +} + + +EditObjectSubSheet::EditObjectSubSheet( + WorldObjectSet &doc, + size_t objIdx, + ncore::SubSheetId subSheetId) noexcept: + m_doc(doc), + m_objIdx(objIdx), + m_newVal(subSheetId), + m_oldVal(m_doc.objects[m_objIdx].subsheetId) { +} + +void EditObjectSubSheet::redo() noexcept { + auto &obj = m_doc.objects[m_objIdx]; + obj.subsheetId = m_newVal; +} + +void EditObjectSubSheet::undo() noexcept { + auto &obj = m_doc.objects[m_objIdx]; + obj.subsheetId = m_oldVal; +} + +int EditObjectSubSheet::commandId() const noexcept { + return static_cast(WorldObjCommand::EditObjectSubSheet); +} + + +EditObjectPalette::EditObjectPalette(WorldObjectSet &doc, size_t objIdx, uint16_t palBank) noexcept: + m_doc(doc), + m_objIdx(objIdx), + m_oldVal(m_doc.objects[m_objIdx].palBank), + m_newVal(palBank) { +} + +void EditObjectPalette::redo() noexcept { + auto &obj = m_doc.objects[m_objIdx]; + obj.palBank = m_newVal; +} + +void EditObjectPalette::undo() noexcept { + auto &obj = m_doc.objects[m_objIdx]; + obj.palBank = m_oldVal; +} + +[[nodiscard]] +int EditObjectPalette::commandId() const noexcept { + return static_cast(WorldObjCommand::EditObjectPalette); +} + + +EditObjectCollisionMap::EditObjectCollisionMap(WorldObjectSet &doc, size_t objIdx, uint64_t colMap) noexcept: + m_doc(doc), + m_objIdx(objIdx), + m_oldVal(m_doc.objects[m_objIdx].collisionMap), + m_newVal(colMap) { +} + +void EditObjectCollisionMap::redo() noexcept { + auto &obj = m_doc.objects[m_objIdx]; + obj.collisionMap = m_newVal; +} + +void EditObjectCollisionMap::undo() noexcept { + auto &obj = m_doc.objects[m_objIdx]; + obj.collisionMap = m_oldVal; +} + +[[nodiscard]] +int EditObjectCollisionMap::commandId() const noexcept { + return static_cast(WorldObjCommand::EditObjectCollisionMap); +} + +} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/editobject.hpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/editobject.hpp new file mode 100644 index 0000000..65a31da --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/editobject.hpp @@ -0,0 +1,72 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +#include + +#include + +namespace jasper::world { + +class EditObjectName: public studio::UndoCommand { + private: + WorldObjectSet &m_doc; + size_t const m_objIdx{}; + ox::String const m_oldVal{}; + ox::String m_newVal{}; + public: + EditObjectName(WorldObjectSet &doc, size_t objIdx, ox::String newName) noexcept; + void redo() noexcept override; + void undo() noexcept override; + [[nodiscard]] + int commandId() const noexcept override; + bool mergeWith(UndoCommand const*cmd) noexcept override; +}; + +class EditObjectSubSheet: public studio::UndoCommand { + private: + WorldObjectSet &m_doc; + size_t const m_objIdx{}; + ncore::SubSheetId const m_newVal{}; + ncore::SubSheetId const m_oldVal{}; + public: + EditObjectSubSheet(WorldObjectSet &doc, size_t objIdx, ncore::SubSheetId subSheetId) noexcept; + void redo() noexcept override; + void undo() noexcept override; + [[nodiscard]] + int commandId() const noexcept override; +}; + +class EditObjectPalette: public studio::UndoCommand { + private: + WorldObjectSet &m_doc; + size_t const m_objIdx{}; + uint16_t const m_oldVal{}; + uint16_t const m_newVal{}; + public: + EditObjectPalette(WorldObjectSet &doc, size_t objIdx, uint16_t palBank) noexcept; + void redo() noexcept override; + void undo() noexcept override; + [[nodiscard]] + int commandId() const noexcept override; +}; + +class EditObjectCollisionMap: public studio::UndoCommand { + private: + WorldObjectSet &m_doc; + size_t const m_objIdx{}; + uint64_t const m_oldVal{}; + uint64_t const m_newVal{}; + public: + EditObjectCollisionMap(WorldObjectSet &doc, size_t objIdx, uint64_t colMap) noexcept; + void redo() noexcept override; + void undo() noexcept override; + [[nodiscard]] + int commandId() const noexcept override; +}; + +} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/editpalette.cpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/editpalette.cpp new file mode 100644 index 0000000..78e1eed --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/editpalette.cpp @@ -0,0 +1,28 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include "commands.hpp" +#include "editpalette.hpp" + +namespace jasper::world { + +EditPalette::EditPalette(PaletteCycle &doc, uint16_t interval) noexcept: + m_doc(doc), + m_oldVal(doc.intervalMs), + m_newVal(interval) { +} + +void EditPalette::redo() noexcept { + m_doc.intervalMs = m_newVal; +} + +void EditPalette::undo() noexcept { + m_doc.intervalMs = m_oldVal; +} + +int EditPalette::commandId() const noexcept { + return static_cast(WorldObjCommand::EditPalette); +} + +} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/editpalette.hpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/editpalette.hpp new file mode 100644 index 0000000..2b7cc5e --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/editpalette.hpp @@ -0,0 +1,28 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +#include + +#include + +namespace jasper::world { + +class EditPalette: public studio::UndoCommand { + private: + PaletteCycle &m_doc; + uint16_t const m_oldVal{}; + uint16_t const m_newVal{}; + public: + EditPalette(PaletteCycle &doc, uint16_t interval) noexcept; + void redo() noexcept override; + void undo() noexcept override; + [[nodiscard]] + int commandId() const noexcept override; +}; + +} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/rmobject.cpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/rmobject.cpp new file mode 100644 index 0000000..0645511 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/rmobject.cpp @@ -0,0 +1,28 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include "commands.hpp" +#include "rmobject.hpp" + +namespace jasper::world { + +RmObject::RmObject(WorldObjectSet &doc, size_t const rmIdx) noexcept: + m_doc(doc), + m_rmIdx(rmIdx) { +} + +void RmObject::redo() noexcept { + m_obj = std::move(m_doc.objects[m_rmIdx]); + oxIgnoreError(m_doc.objects.erase(m_rmIdx)); +} + +void RmObject::undo() noexcept { + m_doc.objects.emplace(m_rmIdx, std::move(m_obj)); +} + +int RmObject::commandId() const noexcept { + return static_cast(WorldObjCommand::RmObject); +} + +} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/rmobject.hpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/rmobject.hpp new file mode 100644 index 0000000..d0a7389 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/rmobject.hpp @@ -0,0 +1,28 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +#include + +#include + +namespace jasper::world { + +class RmObject: public studio::UndoCommand { + private: + WorldObjectSet &m_doc; + size_t const m_rmIdx{}; + WorldObject m_obj{}; + public: + RmObject(WorldObjectSet &doc, size_t rmIdx) noexcept; + void redo() noexcept override; + void undo() noexcept override; + [[nodiscard]] + int commandId() const noexcept override; +}; + +} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/rmpalette.cpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/rmpalette.cpp new file mode 100644 index 0000000..0f12ece --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/rmpalette.cpp @@ -0,0 +1,33 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#include "commands.hpp" +#include "rmpalette.hpp" + +namespace jasper::world { + +RmPalette::RmPalette(WorldObjectSet &doc, size_t rmIdx) noexcept: + m_doc(doc), + m_rmIdx(rmIdx) { +} + +void RmPalette::redo() noexcept { + m_pal = std::move(m_doc.palettes[m_rmIdx]); + oxIgnoreError(m_doc.palettes.erase(m_rmIdx)); +} + +void RmPalette::undo() noexcept { + m_doc.palettes.emplace_back(std::move(m_pal)); + std::sort(m_doc.palettes.begin(), m_doc.palettes.end(), [](PaletteCycle const&a, PaletteCycle const&b) { + return a.palette.getPath().or_value("") < b.palette.getPath().or_value(""); + }); +} + +int RmPalette::commandId() const noexcept { + return static_cast(WorldObjCommand::RmPalette); +} + +} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/rmpalette.hpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/rmpalette.hpp new file mode 100644 index 0000000..5c7ae83 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/commands/rmpalette.hpp @@ -0,0 +1,28 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +#include + +#include + +namespace jasper::world { + +class RmPalette: public studio::UndoCommand { + private: + WorldObjectSet &m_doc; + PaletteCycle m_pal; + size_t const m_rmIdx = 0; + public: + RmPalette(WorldObjectSet &doc, size_t rmIdx) noexcept; + void redo() noexcept override; + void undo() noexcept override; + [[nodiscard]] + int commandId() const noexcept override; +}; + +} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/worldobjectseteditor-imgui.cpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/worldobjectseteditor-imgui.cpp new file mode 100644 index 0000000..ae0132c --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/worldobjectseteditor-imgui.cpp @@ -0,0 +1,360 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#include "commands/addobject.hpp" +#include "commands/rmobject.hpp" +#include "commands/editobject.hpp" +#include "commands/addpalette.hpp" +#include "commands/rmpalette.hpp" +#include "commands/editpalette.hpp" +#include "commands/changetilesheet.hpp" + +#include "worldobjectseteditor-imgui.hpp" + +namespace jasper::world { + +namespace ig = studio::ig; + +constexpr auto btnSize = ImVec2(22, ig::BtnSz.y); + +WorldObjectSetEditorImGui::WorldObjectSetEditorImGui( + studio::StudioContext &ctx, + ox::StringView path): + Editor(path), + m_sctx(ctx), + m_itemPath(path), + m_doc(*readObj(keelCtx(ctx.tctx), path).unwrapThrow()), + m_tileSheet(readObj(keelCtx(m_sctx.tctx), m_doc.tilesheet).unwrapThrow()) { + auto const&tilesheetList = m_sctx.project->fileList(ncore::FileExt_ng); + auto const tsPath = m_doc.tilesheet.getPath().or_value(""); + m_selectedTilesheetIdx = std::find(tilesheetList.begin(), tilesheetList.end(), tsPath).offset(); + loadObj(); + undoStack()->changeTriggered.connect(this, &WorldObjectSetEditorImGui::handleCmd); + buildPaletteDisplayNameList(); +} + +void WorldObjectSetEditorImGui::draw(turbine::Context&) noexcept { + const auto paneSize = ImGui::GetContentRegionAvail(); + constexpr auto resourcesWidth = 300.f; + ImGui::BeginChild("ObjEditor", ImVec2(paneSize.x - resourcesWidth, 0)); + if (m_selectedObj < m_doc.objects.size()) { + drawObjEditor(); + } + ImGui::EndChild(); + ImGui::SameLine(); + ImGui::BeginChild("Resources", ImVec2(0, 0)); + { + { + ig::IDStackItem const idStackItem("TileSheetSelector"); + drawTileSheetSelector(); + } + ImGui::Separator(); + { + ig::IDStackItem const idStackItem("ObjSelector"); + drawObjSelector(); + } + ImGui::Separator(); + { + ig::IDStackItem const idStackItem("PaletteList"); + drawPaletteList(); + } + } + ImGui::EndChild(); + drawAddPalettePopup(); + drawEditPalettePopup(); +} + +void WorldObjectSetEditorImGui::onActivated() noexcept { +} + +WorldObject const&WorldObjectSetEditorImGui::activeObj() const noexcept { + return m_doc.objects[m_selectedObj]; +} + +WorldObject &WorldObjectSetEditorImGui::activeObj() noexcept { + return m_doc.objects[m_selectedObj]; +} + +void WorldObjectSetEditorImGui::buildPaletteDisplayNameList() noexcept { + m_paletteDisplayNames.clear(); + for (auto i = 1u; auto const&pal : m_doc.palettes) { + auto path = pal.palette.getPath().or_value("n/a"); + m_paletteDisplayNames.emplace_back(ox::sfmt("{}: {} - {} ms", i, path, pal.intervalMs)); + ++i; + } +} + +void WorldObjectSetEditorImGui::drawTileSheetSelector() noexcept { + auto const&tilesheetList = m_sctx.project->fileList(ncore::FileExt_ng); + auto sel = m_selectedTilesheetIdx; + if (ig::ComboBox("Tile Sheet", tilesheetList, sel)) { + undoStack()->push(ox::make_unique + (m_doc, ox::FileAddress(tilesheetList[sel]), sel, m_selectedTilesheetIdx)); + loadObj(); + } +} + +void WorldObjectSetEditorImGui::drawObjSelector() noexcept { + ig::IDStackItem const idStackItem1("ObjSelector"); + if (ig::PushButton("+", btnSize)) { + auto cmd = ox::make_unique(m_doc); + m_selectedObj = cmd->insertIdx(); + undoStack()->push(std::move(cmd)); + loadObj(); + } + ImGui::SameLine(); + if (ig::PushButton("-", btnSize)) { + undoStack()->push(ox::make_unique(m_doc, m_selectedObj)); + } + if (ImGui::BeginListBox("Objects")) { + for (auto i = 0u; auto const&obj : m_doc.objects) { + ig::IDStackItem const idStackItem2(static_cast(i)); + if (ImGui::Selectable(obj.name.c_str(), m_selectedObj == i)) { + if (i != m_selectedObj) { + m_selectedObj = i; + loadObj(); + } + } + ++i; + } + ImGui::EndListBox(); + } +} + +void WorldObjectSetEditorImGui::loadObj() noexcept { + if (m_selectedObj >= m_doc.objects.size()) { + return; + } + auto const&obj = activeObj(); + auto &nameBuff = m_objEditor.nameBuff; + ox_strncpy(nameBuff.data(), obj.name.data(), nameBuff.size()); + m_objEditor.palette = obj.palBank; + m_subsheet = getSubsheet(*m_tileSheet, obj.subsheetId); + int w = 0; + int h = 0; + if (m_subsheet) { + w = m_subsheet->columns; + h = m_subsheet->rows; + } + auto const idx = getTileIdx(*m_tileSheet, obj.subsheetId); + oxLogError(m_colView.setup( + m_doc.tilesheet, + m_doc.palettes[obj.palBank].palette, + w, + h, + static_cast(idx), + obj.collisionMap)); +} + +[[nodiscard]] +static ox::Vec2 clickPos(ImVec2 const&imgSz, ImVec2 const&offset, ox::Vec2 clickPos) noexcept { + clickPos.x -= offset.x; + clickPos.y -= offset.y; + clickPos.x /= imgSz.x; + clickPos.y /= imgSz.y; + return clickPos; +} + +void WorldObjectSetEditorImGui::drawObjEditor() noexcept { + ig::IDStackItem const idStackItem("ObjEditor"); + ig::IndentStackItem const indent1(10); + ImGui::NewLine(); + auto &nameBuff = m_objEditor.nameBuff; + if (ImGui::InputText("Name", nameBuff.data(), nameBuff.size())) { + undoStack()->push(ox::make_unique( + m_doc, + m_selectedObj, + ox::String(nameBuff.data(), ox_strnlen(nameBuff.data(), nameBuff.size())))); + } + // SubSheet Selector + { + ig::IDStackItem const subsheetSelectorStackItem("SubsheetSelector"); + if (ig::ComboBox("Palette", m_paletteDisplayNames, m_objEditor.palette)) { + undoStack()->push(ox::make_unique( + m_doc, m_selectedObj, static_cast(m_objEditor.palette))); + } + ImGui::Separator(); + ImGui::Text("Subsheet:"); + ImGui::BeginChild("SubsheetSelector", ImVec2{300, 300}); + { + ImGui::SetNextItemOpen(true); + if (m_tileSheet) { + drawSubSheetNode(m_tileSheet->subsheet); + } + } + ImGui::EndChild(); + } + // collision map + if (m_subsheet) { + ImGui::NewLine(); + ImGui::Text("Collision Map:"); + ig::IndentStackItem const indent2(30); + m_colView.draw(); + auto const&fb = m_colView.framebuffer(); + uintptr_t const buffId = fb.color.id; + auto const scale = static_cast(m_colView.scale()); + auto const width = static_cast(m_subsheet->columns * ncore::TileWidth); + auto const height = static_cast(m_subsheet->rows * ncore::TileHeight); + auto const horzPct = width / 240.f; + auto const vertPct = height / 160.f; + auto const imageSz = ImVec2(width * scale, height * scale); + auto const imagePos = ImGui::GetCursorScreenPos(); + ImGui::Image( + std::bit_cast(buffId), + imageSz, + ImVec2(0, 1.0), + ImVec2(horzPct, 1 - vertPct)); + auto const&io = ImGui::GetIO(); + if (ImGui::IsItemClicked()) { + auto const mousePos = clickPos(imageSz, imagePos, ox::Vec2(io.MousePos)); + auto &obj = activeObj(); + auto intermediate = obj.collisionMap; + if (m_colView.click(mousePos, intermediate)) { + undoStack()->push(ox::make_unique(m_doc, m_selectedObj, intermediate)); + } + } + } +} + +void WorldObjectSetEditorImGui::drawSubSheetNode(ncore::TileSheet::SubSheet const&ss) noexcept { + auto constexpr dirFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; + auto &obj = activeObj(); + auto const selected = ss.id == obj.subsheetId ? ImGuiTreeNodeFlags_Selected : 0; + ig::IDStackItem const idStackItem(ss.name); + if (!ss.subsheets.empty()) { + if (ImGui::TreeNodeEx(ss.name.c_str(), dirFlags)) { + for (auto const&child : ss.subsheets) { + drawSubSheetNode(child); + } + ImGui::TreePop(); + } + } else if (ImGui::TreeNodeEx(ss.name.c_str(), ImGuiTreeNodeFlags_Leaf | selected)) { + if (ImGui::IsItemClicked()) { + undoStack()->push(ox::make_unique(m_doc, m_selectedObj, ss.id)); + } + ImGui::TreePop(); + } +} + +void WorldObjectSetEditorImGui::drawPaletteList() noexcept { + ig::IDStackItem const idStackItem("PaletteList"); + if (ig::PushButton("+", btnSize)) { + addPalette(); + } + ImGui::SameLine(); + if (ig::PushButton("-", btnSize)) { + rmPalette(); + } + ImGui::SameLine(); + if (ig::PushButton("Edit")) { + editPalette(); + } + constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody; + if (ImGui::BeginTable("Subsheets", 2, flags)) { + ImGui::TableSetupColumn("Palette", ImGuiTableColumnFlags_NoHide); + ImGui::TableSetupColumn("Interval", ImGuiTableColumnFlags_WidthFixed, 100); + ImGui::TableHeadersRow(); + drawPaletteListItems(); + ImGui::EndTable(); + } +} + +void WorldObjectSetEditorImGui::drawPaletteListItems() noexcept { + for (auto i = 0u; auto const&pal : m_doc.palettes) { + ig::IDStackItem const idStackItem(static_cast(i)); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s", pal.palette.getPath().value.c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%d ms", pal.intervalMs); + ImGui::SameLine(); + if (ImGui::Selectable("##PaletteSelection", i == m_selectedPal, ImGuiSelectableFlags_SpanAllColumns)) { + m_selectedPal = i; + } + if (ImGui::IsMouseDoubleClicked(0) && ImGui::IsItemHovered()) { + editPalette(); + } + ++i; + } +} + +void WorldObjectSetEditorImGui::drawAddPalettePopup() noexcept { + if (!m_addPalPopup.show) { + return; + } + auto constexpr popupName = ox::CStringView("Add Palette"); + auto constexpr popupSz = ImVec2{285.f, 0}; + ig::IDStackItem const idStackItem("AddPalette"); + if (ig::BeginPopup(m_sctx.tctx, popupName, m_addPalPopup.show, popupSz)) { + auto const&palettes = m_sctx.project->fileList(ncore::FileExt_npal); + ig::ComboBox("Palette", palettes, m_addPalPopup.selectedIdx); + if (ig::PopupControlsOkCancel(popupSz.x, m_addPalPopup.show) == ig::PopupResponse::OK) { + undoStack()->push(ox::make_unique( + m_doc, + ox::FileAddress(palettes[m_addPalPopup.selectedIdx]))); + } + ImGui::EndPopup(); + } +} + +void WorldObjectSetEditorImGui::drawEditPalettePopup() noexcept { + if (!m_editPalPopup.show) { + return; + } + auto constexpr popupName = ox::CStringView("Edit Palette"); + auto constexpr popupSz = ImVec2{300.f, 0}; + ig::IDStackItem const idStackItem("EditPalette"); + if (ig::BeginPopup(m_sctx.tctx, popupName, m_editPalPopup.show, popupSz)) { + auto &pal = m_doc.palettes[m_selectedPal]; + ImGui::InputInt("Interval (ms)", &m_editPalPopup.intervalInputVal, 1000); + m_editPalPopup.intervalInputVal = + static_cast(ox::clamp(m_editPalPopup.intervalInputVal, 0, 60000)); + if (ig::PopupControlsOkCancel(popupSz.x, m_editPalPopup.show) == ig::PopupResponse::OK) { + undoStack()->push(ox::make_unique( + pal, static_cast(m_editPalPopup.intervalInputVal))); + } + ImGui::EndPopup(); + } +} + +void WorldObjectSetEditorImGui::addPalette() noexcept { + m_addPalPopup.show = true; + m_addPalPopup.selectedIdx = 0; +} + +void WorldObjectSetEditorImGui::rmPalette() noexcept { + undoStack()->push(ox::make_unique( + m_doc, m_selectedPal)); +} + +void WorldObjectSetEditorImGui::editPalette() noexcept { + auto &pal = m_doc.palettes[m_selectedPal]; + m_editPalPopup.show = true; + m_editPalPopup.intervalInputVal = pal.intervalMs; +} + +ox::Error WorldObjectSetEditorImGui::handleCmd(studio::UndoCommand const*cmd) noexcept { + if (dynamic_cast(cmd)) { + oxLogError(readObj(keelCtx(m_sctx.tctx), m_doc.tilesheet).moveTo(m_tileSheet)); + } + if (dynamic_cast(cmd) || + dynamic_cast(cmd) || + dynamic_cast(cmd)) { + buildPaletteDisplayNameList(); + } + if (dynamic_cast(cmd)) { + auto const&obj = activeObj(); + m_objEditor.palette = obj.palBank; + } + loadObj(); + return {}; +} + +ox::Error WorldObjectSetEditorImGui::saveItem() noexcept { + return m_sctx.project->writeObj(m_itemPath, m_doc, ox::ClawFormat::Organic); +} + +} diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/worldobjectseteditor-imgui.hpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/worldobjectseteditor-imgui.hpp new file mode 100644 index 0000000..46473bf --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/worldobjectseteditor-imgui.hpp @@ -0,0 +1,86 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include +#include + +#include + +#include "collisionmapview.hpp" + +namespace jasper::world { + +class WorldObjectSetEditorImGui: public studio::Editor { + + private: + studio::StudioContext &m_sctx; + ox::String m_itemPath; + WorldObjectSet m_doc; + keel::AssetRef m_tileSheet; + ncore::TileSheet::SubSheet const*m_subsheet = nullptr; + ox::Vector m_paletteDisplayNames; + size_t m_selectedObj{}; + size_t m_selectedPal{}; + size_t m_selectedTilesheetIdx{}; + struct { + bool show{}; + size_t selectedIdx{}; + } m_addPalPopup; + struct { + bool show{}; + int intervalInputVal{}; + } m_editPalPopup; + CollisionView m_colView{m_sctx}; + struct { + ox::Buffer nameBuff = ox::Buffer(256); + size_t palette{}; + } m_objEditor; + + public: + WorldObjectSetEditorImGui(studio::StudioContext &ctx, ox::StringView path); + + void draw(turbine::Context&) noexcept final; + + void onActivated() noexcept override; + + private: + WorldObject const&activeObj() const noexcept; + + WorldObject &activeObj() noexcept; + + void buildPaletteDisplayNameList() noexcept; + + void drawTileSheetSelector() noexcept; + + void drawObjSelector() noexcept; + + void loadObj() noexcept; + + void drawObjEditor() noexcept; + + void drawSubSheetNode(ncore::TileSheet::SubSheet const&ss) noexcept; + + void drawPaletteList() noexcept; + + void drawPaletteListItems() noexcept; + + void drawAddPalettePopup() noexcept; + + void drawEditPalettePopup() noexcept; + + void addPalette() noexcept; + + void rmPalette() noexcept; + + void editPalette() noexcept; + + ox::Error handleCmd(studio::UndoCommand const*cmd) noexcept; + + ox::Error saveItem() noexcept final; + +}; + +} diff --git a/src/jasper/modules/world/src/world.cpp b/src/jasper/modules/world/src/world.cpp index 246cba8..add1011 100644 --- a/src/jasper/modules/world/src/world.cpp +++ b/src/jasper/modules/world/src/world.cpp @@ -42,9 +42,9 @@ void World::setupLayer( const auto width = m_worldStatic.rows[layerNo]; for (auto const&tile : layer) { const auto tile8 = static_cast(tile); - ncore::setBgTile(ctx, layerNo, x, y, tile8); - ncore::setBgTile(ctx, layerNo, x + 1, y, tile8 + 1); - ncore::setBgTile(ctx, layerNo, x, y + 1, tile8 + 2); + ncore::setBgTile(ctx, layerNo, x + 0, y + 0, tile8 + 0); + ncore::setBgTile(ctx, layerNo, x + 1, y + 0, tile8 + 1); + ncore::setBgTile(ctx, layerNo, x + 0, y + 1, tile8 + 2); ncore::setBgTile(ctx, layerNo, x + 1, y + 1, tile8 + 3); x += 2; if (x >= width * 2) {