diff --git a/src/nostalgia/core/gfx.hpp b/src/nostalgia/core/gfx.hpp index 71b415d3..ebc797bb 100644 --- a/src/nostalgia/core/gfx.hpp +++ b/src/nostalgia/core/gfx.hpp @@ -6,10 +6,12 @@ #include #include +#include #include #include "color.hpp" #include "context.hpp" +#include "ptidxconv.hpp" namespace nostalgia::core { @@ -36,6 +38,46 @@ struct NostalgiaGraphic { ox::FileAddress defaultPalette; NostalgiaPalette pal; ox::Vector pixels; + + [[nodiscard]] + constexpr auto getPixel4Bpp(const common::Point &pt) const noexcept { + oxAssert(bpp == 4, "NostalgiaGraphic::getPixel4Bpp: wrong bpp"); + const auto idx = ptToIdx(pt, this->columns); + if (idx & 1) { + return this->pixels[idx / 2]; + } else { + return this->pixels[idx / 2]; + } + } + + [[nodiscard]] + constexpr auto getPixel8Bpp(const common::Point &pt) const noexcept { + oxAssert(bpp == 8, "NostalgiaGraphic::getPixel8Bpp: wrong bpp"); + const auto idx = ptToIdx(pt, this->columns); + return this->pixels[idx]; + } + + [[nodiscard]] + constexpr auto getPixel(const common::Point &pt) const noexcept { + if (this->bpp == 4) { + return getPixel4Bpp(pt); + } else { + return getPixel8Bpp(pt); + } + } + + constexpr void setPixel(const common::Point &pt, uint8_t palIdx) noexcept { + const auto idx = ptToIdx(pt, this->columns); + if (bpp == 4) { + if (idx & 1) { + pixels[idx / 2] &= 0b0000'1111 | (palIdx << 4); + } else { + pixels[idx / 2] &= 0b1111'0000 | (palIdx); + } + } else { + this->pixels[idx] = palIdx; + } + } }; oxModelBegin(NostalgiaPalette) diff --git a/src/nostalgia/core/studio/ptidxconv.hpp b/src/nostalgia/core/ptidxconv.hpp similarity index 58% rename from src/nostalgia/core/studio/ptidxconv.hpp rename to src/nostalgia/core/ptidxconv.hpp index f4c0491a..3c19227c 100644 --- a/src/nostalgia/core/studio/ptidxconv.hpp +++ b/src/nostalgia/core/ptidxconv.hpp @@ -6,26 +6,28 @@ #include +#include "consts.hpp" + namespace nostalgia::core { [[nodiscard]] -constexpr int pointToIdx(int w, int x, int y) noexcept { - constexpr auto colLength = PixelsPerTile; - const auto rowLength = (w / TileWidth) * colLength; - const auto colStart = colLength * (x / TileWidth); - const auto rowStart = rowLength * (y / TileHeight); - const auto colOffset = x % TileWidth; - const auto rowOffset = (y % TileHeight) * TileHeight; - return colStart + colOffset + rowStart + rowOffset; +constexpr std::size_t pointToIdx(int w, int x, int y) noexcept { + constexpr auto colLength = static_cast(PixelsPerTile); + const auto rowLength = static_cast(static_cast(w / TileWidth) * colLength); + const auto colStart = static_cast(colLength * static_cast(x / TileWidth)); + const auto rowStart = static_cast(rowLength * static_cast(y / TileHeight)); + const auto colOffset = static_cast(x % TileWidth); + const auto rowOffset = static_cast((y % TileHeight) * TileHeight); + return static_cast(colStart + colOffset + rowStart + rowOffset); } [[nodiscard]] -constexpr int ptToIdx(int x, int y, int c) noexcept { +constexpr std::size_t ptToIdx(int x, int y, int c) noexcept { return pointToIdx(c * TileWidth, x, y); } [[nodiscard]] -constexpr int ptToIdx(common::Point pt, int c) noexcept { +constexpr std::size_t ptToIdx(common::Point pt, int c) noexcept { return pointToIdx(c * TileWidth, pt.x, pt.y); } diff --git a/src/nostalgia/core/studio/tilesheeteditor.cpp b/src/nostalgia/core/studio/tilesheeteditor.cpp index 7ec82d5d..34d3677b 100644 --- a/src/nostalgia/core/studio/tilesheeteditor.cpp +++ b/src/nostalgia/core/studio/tilesheeteditor.cpp @@ -7,7 +7,9 @@ #include #include +#include #include +#include #include "tilesheeteditor.hpp" @@ -15,12 +17,11 @@ namespace nostalgia::core { TileSheetEditor::TileSheetEditor(Context *ctx, const ox::String &path) { m_itemPath = path; - const auto lastSlash = std::find(m_itemPath.crbegin(), m_itemPath.crend(), '/').offset(); + const auto lastSlash = std::find(m_itemPath.rbegin(), m_itemPath.rend(), '/').offset(); m_itemName = m_itemPath.substr(lastSlash + 1); - m_ctx = ctx; // build shaders - oxThrowError(m_pixels.buildShader()); - oxThrowError(m_pixelGrid.buildShader()); + oxThrowError(m_pixelsDrawer.buildShader()); + oxThrowError(m_pixelGridDrawer.buildShader()); oxRequireT(img, readObj(ctx, m_itemPath.c_str())); oxThrowError(readObj(ctx, img->defaultPalette).moveTo(&m_pal)); m_img = *img; @@ -47,36 +48,56 @@ void TileSheetEditor::paste() { } void TileSheetEditor::draw(core::Context*) noexcept { - const auto size = ImGui::GetContentRegionAvail(); - const auto sizei = common::Size(static_cast(size.x), static_cast(size.y)); - if (m_updated || m_framebuffer.width != sizei.width || m_framebuffer.height != sizei.height) { - if (!m_framebuffer) { - m_framebuffer = glutils::generateFrameBuffer(sizei.width, sizei.height); - } - m_pixels.initBufferSet(m_img, *m_pal); - m_pixelGrid.initBufferSet(m_img); + const auto paneSize = ImGui::GetContentRegionAvail(); + const auto sizei = common::Size(static_cast(paneSize.x), static_cast(paneSize.y)); + if (m_framebuffer.width != sizei.width || m_framebuffer.height != sizei.height) { + m_framebuffer = glutils::generateFrameBuffer(sizei.width, sizei.height); + m_pixelsDrawer.initBufferSet(m_img, *m_pal); + m_pixelGridDrawer.initBufferSet(m_img); + } else if (m_updated) { + m_pixelsDrawer.initBufferSet(m_img, *m_pal); + m_pixelGridDrawer.initBufferSet(m_img); } glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); glViewport(0, 0, sizei.width, sizei.height); glDraw(); glBindFramebuffer(GL_FRAMEBUFFER, 0); - ImGui::Image(reinterpret_cast(m_framebuffer.color.id), size, ImVec2(0, 1), ImVec2(1, 0)); + ImGui::Image(reinterpret_cast(m_framebuffer.color.id), paneSize, ImVec2(0, 1), ImVec2(1, 0)); + // handle input, this must come after drawing if (ImGui::IsItemHovered()) { const auto &io = ImGui::GetIO(); - const auto wheel = io.MouseWheel * 0.01f; - const auto zoomMod = ox::defines::OS == ox::defines::OS::Darwin ? io.KeySuper : io.KeyCtrl; - if (zoomMod && wheel != 0) { - m_pixelSizeMod = ox::clamp(m_pixelSizeMod + wheel, 0.55f, 2.f); - m_pixels.setPixelSizeMod(m_pixelSizeMod); - m_pixelGrid.setPixelSizeMod(m_pixelSizeMod); - m_updated = true; + const auto wheel = io.MouseWheel; + const auto wheelh = io.MouseWheelH; + if (wheel != 0) { + const auto zoomMod = ox::defines::OS == ox::defines::OS::Darwin ? io.KeySuper : io.KeyCtrl; + const auto pixelSize = m_pixelsDrawer.pixelSize(paneSize); + const ImVec2 sheetSize(pixelSize.x * static_cast(m_img.columns) * TileWidth, + pixelSize.y * static_cast(m_img.rows) * TileHeight); + if (zoomMod) { + m_pixelSizeMod = ox::clamp(m_pixelSizeMod + wheel * 0.02f, 0.55f, 2.f); + m_pixelsDrawer.setPixelSizeMod(m_pixelSizeMod); + m_pixelGridDrawer.setPixelSizeMod(m_pixelSizeMod); + m_updated = true; + } else { + m_scrollOffset.y -= wheel * 0.1f; + } + // adjust scroll offset in both cases because the image can be zoomed + // or scrolled off screen + m_scrollOffset.y = ox::clamp(m_scrollOffset.y, 0.f, sheetSize.y / 2); } - if (io.MouseClicked[0]) { - auto clickPos = io.MouseClickedPos[0]; + if (wheelh != 0) { + const auto pixelSize = m_pixelsDrawer.pixelSize(paneSize); + const ImVec2 sheetSize(pixelSize.x * static_cast(m_img.columns) * TileWidth, + pixelSize.y * static_cast(m_img.rows) * TileHeight); + m_scrollOffset.x += wheelh * 0.1f; + m_scrollOffset.x = ox::clamp(m_scrollOffset.x, -(sheetSize.x / 2), 0.f); + } + if (io.MouseDown[0]) { + auto clickPos = io.MousePos; const auto &winPos = ImGui::GetWindowPos(); - clickPos.x -= winPos.x; - clickPos.y -= winPos.y; - std::cout << clickPos.x - 8 << ", " << clickPos.y - 31 << '\n'; + clickPos.x -= winPos.x + 8; + clickPos.y -= winPos.y + 31; + clickPixel(paneSize, clickPos.x, clickPos.y); } } } @@ -86,8 +107,8 @@ void TileSheetEditor::glDraw() noexcept { constexpr Color32 bgColor = 0x717d7e; glClearColor(redf(bgColor), greenf(bgColor), bluef(bgColor), 1.f); glClear(GL_COLOR_BUFFER_BIT); - m_pixels.draw(m_updated); - m_pixelGrid.draw(m_updated); + m_pixelsDrawer.draw(m_updated, m_scrollOffset); + m_pixelGridDrawer.draw(m_updated, m_scrollOffset); m_updated = false; //oxAssert(glGetError() == 0, "There was an OpenGL error"); } @@ -95,4 +116,50 @@ void TileSheetEditor::glDraw() noexcept { void TileSheetEditor::saveItem() { } +void TileSheetEditor::getFillPixels(bool *pixels, common::Point pt, int oldColor) const { + const auto tileIdx = [this](const common::Point &pt) noexcept { + return ptToIdx(pt, m_img.columns) / PixelsPerTile; + }; + // get points + const auto leftPt = pt + common::Point(-1, 0); + const auto rightPt = pt + common::Point(1, 0); + const auto topPt = pt + common::Point(0, -1); + const auto bottomPt = pt + common::Point(0, 1); + // calculate indices + const auto idx = ptToIdx(pt, m_img.columns); + const auto leftIdx = ptToIdx(leftPt, m_img.columns); + const auto rightIdx = ptToIdx(rightPt, m_img.columns); + const auto topIdx = ptToIdx(topPt, m_img.columns); + const auto bottomIdx = ptToIdx(bottomPt, m_img.columns); + const auto tile = tileIdx(pt); + // mark pixels to update + pixels[idx % PixelsPerTile] = true; + if (!pixels[leftIdx % PixelsPerTile] && tile == tileIdx(leftPt) && m_img.pixels[leftIdx] == oldColor) { + getFillPixels(pixels, leftPt, oldColor); + } + if (!pixels[rightIdx % PixelsPerTile] && tile == tileIdx(rightPt) && m_img.pixels[rightIdx] == oldColor) { + getFillPixels(pixels, rightPt, oldColor); + } + if (!pixels[topIdx % PixelsPerTile] && tile == tileIdx(topPt) && m_img.pixels[topIdx] == oldColor) { + getFillPixels(pixels, topPt, oldColor); + } + if (!pixels[bottomIdx % PixelsPerTile] && tile == tileIdx(bottomPt) && m_img.pixels[bottomIdx] == oldColor) { + getFillPixels(pixels, bottomPt, oldColor); + } +} + +void TileSheetEditor::clickPixel(const ImVec2 &paneSize, float x, float y) noexcept { + const auto pixDrawSz = m_pixelsDrawer.pixelSize(paneSize); + x /= paneSize.x; + y /= paneSize.y; + x += m_scrollOffset.x / 2; + y += m_scrollOffset.y / 2; + x /= pixDrawSz.x; + y /= pixDrawSz.y; + const auto pt = common::Point(static_cast(x * 2), static_cast(y * 2)); + const uint8_t palIdx = 0; + m_img.setPixel(pt, palIdx); + m_updated = true; +} + } diff --git a/src/nostalgia/core/studio/tilesheeteditor.hpp b/src/nostalgia/core/studio/tilesheeteditor.hpp index 627d048c..00dd5fda 100644 --- a/src/nostalgia/core/studio/tilesheeteditor.hpp +++ b/src/nostalgia/core/studio/tilesheeteditor.hpp @@ -100,13 +100,13 @@ class TileSheetEditor: public studio::Editor { ox::String m_itemPath; ox::String m_itemName; glutils::FrameBuffer m_framebuffer; - TileSheetGrid m_pixelGrid; - TileSheetPixels m_pixels; + TileSheetGrid m_pixelGridDrawer; + TileSheetPixels m_pixelsDrawer; bool m_updated = false; NostalgiaGraphic m_img; AssetRef m_pal; - Context *m_ctx = nullptr; float m_pixelSizeMod = 1; + ImVec2 m_scrollOffset; public: TileSheetEditor(Context *ctx, const ox::String &path); @@ -132,6 +132,8 @@ class TileSheetEditor: public studio::Editor { protected: void saveItem() override; + void getFillPixels(bool *pixels, common::Point pt, int oldColor) const; + private: void setPalette(); @@ -145,6 +147,8 @@ class TileSheetEditor: public studio::Editor { [[nodiscard]] ox::String palettePath(const ox::String &palettePath) const; + void clickPixel(const ImVec2 &paneSize, float x, float y) noexcept; + // slots public: ox::Error colorSelected() noexcept; diff --git a/src/nostalgia/core/studio/tilesheetpixelgrid.cpp b/src/nostalgia/core/studio/tilesheetpixelgrid.cpp index a80d7ea0..6fe7aed6 100644 --- a/src/nostalgia/core/studio/tilesheetpixelgrid.cpp +++ b/src/nostalgia/core/studio/tilesheetpixelgrid.cpp @@ -18,13 +18,15 @@ ox::Error TileSheetGrid::buildShader() noexcept { return glutils::buildShaderProgram(pixelLineVshad, pixelLineFshad, pixelLineGshad).moveTo(&m_shader); } -void TileSheetGrid::draw(bool update) noexcept { - glLineWidth(3); +void TileSheetGrid::draw(bool update, const ImVec2 &scroll) noexcept { + glLineWidth(3 * m_pixelSizeMod * 0.5f); glUseProgram(m_shader); glBindVertexArray(m_bufferSet.vao); if (update) { glutils::sendVbo(m_bufferSet); } + const auto uniformScroll = static_cast(glGetUniformLocation(m_shader, "gScroll")); + glUniform2f(uniformScroll, scroll.x, scroll.y); glDrawArrays(GL_POINTS, 0, m_bufferSet.vertices.size() / VertexVboRowLength); } @@ -96,8 +98,8 @@ void TileSheetGrid::setBufferObjects(const NostalgiaGraphic &img, glutils::Buffe } } -ImVec2 TileSheetGrid::pixelSize() const noexcept { - const auto [sw, sh] = ImGui::GetContentRegionAvail(); +ImVec2 TileSheetGrid::pixelSize(const ImVec2 &paneSize) const noexcept { + const auto [sw, sh] = paneSize; constexpr float ymod = 0.35f / 10.0f; const auto xmod = ymod * sh / sw; return {xmod * m_pixelSizeMod, ymod * m_pixelSizeMod}; diff --git a/src/nostalgia/core/studio/tilesheetpixelgrid.hpp b/src/nostalgia/core/studio/tilesheetpixelgrid.hpp index bffaa0ca..3bc474a9 100644 --- a/src/nostalgia/core/studio/tilesheetpixelgrid.hpp +++ b/src/nostalgia/core/studio/tilesheetpixelgrid.hpp @@ -49,11 +49,12 @@ class TileSheetGrid { in vec3 gColor[]; in vec2 gPt2[]; out vec3 fColor; + uniform vec2 gScroll; void main() { fColor = gColor[0]; - gl_Position = gl_in[0].gl_Position; + gl_Position = gl_in[0].gl_Position + vec4(gScroll, 0, 0); EmitVertex(); - gl_Position = vec4(gPt2[0], 0, 1); + gl_Position = vec4(gPt2[0] + gScroll, 0, 1); EmitVertex(); EndPrimitive(); })glsl"; @@ -67,7 +68,7 @@ class TileSheetGrid { ox::Error buildShader() noexcept; - void draw(bool update) noexcept; + void draw(bool update, const ImVec2 &scroll) noexcept; void initBufferSet(const NostalgiaGraphic &img) noexcept; @@ -77,7 +78,7 @@ class TileSheetGrid { void setBufferObjects(const NostalgiaGraphic &img, glutils::BufferSet *bg) noexcept; [[nodiscard]] - class ImVec2 pixelSize() const noexcept; + class ImVec2 pixelSize(const ImVec2 &paneSize = ImGui::GetContentRegionAvail()) const noexcept; }; diff --git a/src/nostalgia/core/studio/tilesheetpixels.cpp b/src/nostalgia/core/studio/tilesheetpixels.cpp index c069dc40..457b22d8 100644 --- a/src/nostalgia/core/studio/tilesheetpixels.cpp +++ b/src/nostalgia/core/studio/tilesheetpixels.cpp @@ -3,7 +3,7 @@ */ #include -#include "ptidxconv.hpp" +#include #include "tilesheetpixels.hpp" namespace nostalgia::core { @@ -18,12 +18,14 @@ ox::Error TileSheetPixels::buildShader() noexcept { return glutils::buildShaderProgram(Vshad, Fshad).moveTo(&m_shader); } -void TileSheetPixels::draw(bool update) noexcept { +void TileSheetPixels::draw(bool update, const ImVec2 &scroll) noexcept { glUseProgram(m_shader); glBindVertexArray(m_bufferSet.vao); if (update) { glutils::sendVbo(m_bufferSet); } + const auto uniformScroll = static_cast(glGetUniformLocation(m_shader, "vScroll")); + glUniform2f(uniformScroll, scroll.x, scroll.y); glDrawElements(GL_TRIANGLES, static_cast(m_bufferSet.elements.size()), GL_UNSIGNED_INT, nullptr); } @@ -47,6 +49,13 @@ void TileSheetPixels::initBufferSet(const NostalgiaGraphic &img, const Nostalgia reinterpret_cast(2 * sizeof(float))); } +ImVec2 TileSheetPixels::pixelSize(const ImVec2 &paneSize) const noexcept { + const auto [sw, sh] = paneSize; + constexpr float ymod = 0.35f / 10.0f; + const auto xmod = ymod * sh / sw; + return {xmod * m_pixelSizeMod, ymod * m_pixelSizeMod}; +} + void TileSheetPixels::setPixelBufferObject(unsigned vertexRow, float x, float y, Color16 color, float *vbo, GLuint *ebo) noexcept { const auto [xmod, ymod] = pixelSize(); x *= xmod; @@ -102,11 +111,4 @@ void TileSheetPixels::setBufferObjects(const NostalgiaGraphic &img, const Nostal } } -ImVec2 TileSheetPixels::pixelSize() const noexcept { - const auto [sw, sh] = ImGui::GetContentRegionAvail(); - constexpr float ymod = 0.35f / 10.0f; - const auto xmod = ymod * sh / sw; - return {xmod * m_pixelSizeMod, ymod * m_pixelSizeMod}; -} - } diff --git a/src/nostalgia/core/studio/tilesheetpixels.hpp b/src/nostalgia/core/studio/tilesheetpixels.hpp index fe7ad09d..d99858f5 100644 --- a/src/nostalgia/core/studio/tilesheetpixels.hpp +++ b/src/nostalgia/core/studio/tilesheetpixels.hpp @@ -24,8 +24,9 @@ class TileSheetPixels { in vec2 vPosition; in vec3 vColor; out vec3 fColor; + uniform vec2 vScroll; void main() { - gl_Position = vec4(vPosition, 0.0, 1.0); + gl_Position = vec4(vPosition + vScroll, 0.0, 1.0); fColor = vColor; })"; static constexpr auto FShad = R"( @@ -46,18 +47,18 @@ class TileSheetPixels { ox::Error buildShader() noexcept; - void draw(bool update) noexcept; + void draw(bool update, const ImVec2 &scroll) noexcept; void initBufferSet(const NostalgiaGraphic &img, const NostalgiaPalette &pal) noexcept; + [[nodiscard]] + ImVec2 pixelSize(const ImVec2 &paneSize = ImGui::GetContentRegionAvail()) const noexcept; + private: void setPixelBufferObject(unsigned vertexRow, float x, float y, Color16 color, float *vbo, GLuint *ebo) noexcept; void setBufferObjects(const NostalgiaGraphic &img, const NostalgiaPalette &pal, glutils::BufferSet *bg) noexcept; - [[nodiscard]] - ImVec2 pixelSize() const noexcept; - }; }