diff --git a/src/nostalgia/core/CMakeLists.txt b/src/nostalgia/core/CMakeLists.txt index 0bc67d33..1131fc73 100644 --- a/src/nostalgia/core/CMakeLists.txt +++ b/src/nostalgia/core/CMakeLists.txt @@ -49,6 +49,7 @@ add_library( NostalgiaCore gfx.cpp media.cpp + typeconv.cpp ${NOSTALGIA_CORE_IMPL_SRC} ) @@ -79,6 +80,7 @@ install( gfx.hpp input.hpp media.hpp + typeconv.hpp DESTINATION include/nostalgia/core ) diff --git a/src/nostalgia/core/core.hpp b/src/nostalgia/core/core.hpp index c7af77ea..5c2cca97 100644 --- a/src/nostalgia/core/core.hpp +++ b/src/nostalgia/core/core.hpp @@ -13,6 +13,7 @@ #include "gfx.hpp" #include "input.hpp" #include "media.hpp" +#include "typeconv.hpp" namespace nostalgia::core { diff --git a/src/nostalgia/core/gfx.hpp b/src/nostalgia/core/gfx.hpp index 6d20f86a..8db44fd0 100644 --- a/src/nostalgia/core/gfx.hpp +++ b/src/nostalgia/core/gfx.hpp @@ -35,7 +35,7 @@ struct Palette { ox::Vector colors; }; -struct TileSheet { +struct TileSheetV1 { static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaGraphic"; static constexpr auto TypeVersion = 1; int8_t bpp = 0; @@ -48,7 +48,7 @@ struct TileSheet { [[nodiscard]] constexpr uint8_t getPixel4Bpp(std::size_t idx) const noexcept { - oxAssert(bpp == 4, "NostalgiaGraphic::getPixel4Bpp: wrong bpp"); + oxAssert(bpp == 4, "TileSheetV1::getPixel4Bpp: wrong bpp"); if (idx & 1) { return this->pixels[idx / 2] >> 4; } else { @@ -58,7 +58,7 @@ struct TileSheet { [[nodiscard]] constexpr uint8_t getPixel8Bpp(std::size_t idx) const noexcept { - oxAssert(bpp == 8, "NostalgiaGraphic::getPixel8Bpp: wrong bpp"); + oxAssert(bpp == 8, "TileSheetV1::getPixel8Bpp: wrong bpp"); return this->pixels[idx]; } @@ -73,14 +73,14 @@ struct TileSheet { [[nodiscard]] constexpr auto getPixel4Bpp(const geo::Point &pt) const noexcept { - oxAssert(bpp == 4, "NostalgiaGraphic::getPixel4Bpp: wrong bpp"); + oxAssert(bpp == 4, "TileSheetV1::getPixel4Bpp: wrong bpp"); const auto idx = ptToIdx(pt, this->columns); return getPixel4Bpp(idx); } [[nodiscard]] constexpr auto getPixel8Bpp(const geo::Point &pt) const noexcept { - oxAssert(bpp == 8, "NostalgiaGraphic::getPixel8Bpp: wrong bpp"); + oxAssert(bpp == 8, "TileSheetV1::getPixel8Bpp: wrong bpp"); const auto idx = ptToIdx(pt, this->columns); return getPixel8Bpp(idx); } @@ -92,7 +92,7 @@ struct TileSheet { } constexpr void setPixel(uint64_t idx, uint8_t palIdx) noexcept { - auto &pixel = this->pixels[idx / 2]; + auto &pixel = this->pixels[idx / 2]; if (bpp == 4) { if (idx & 1) { pixel = (pixel & 0b0000'1111) | (palIdx << 4); @@ -110,11 +110,144 @@ struct TileSheet { } }; +struct TileSheet { + using SubSheetIdx = ox::Vector; + + struct SubSheet { + static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheet.SubSheet"; + static constexpr auto TypeVersion = "net.drinkingtea.nostalgia.core.TileSheet.SubSheet"; + ox::BString<32> name; + std::size_t begin = 0; + std::size_t size = 0; + int rows = 1; + int columns = 1; + ox::Vector subsheets; + }; + + static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheet"; + static constexpr auto TypeVersion = 2; + int8_t bpp = 0; + // rows and columns are really only used by TileSheetEditor + ox::FileAddress defaultPalette; + Palette pal; + ox::Vector pixels; + SubSheet subsheet; + + [[nodiscard]] + constexpr const auto &getSubSheet(const SubSheetIdx &idx, std::size_t idxIt, const SubSheet *pSubsheet) const noexcept { + if (idxIt == idx.size()) { + return *pSubsheet; + } + return getSubSheet(idx, idxIt + 1, &pSubsheet->subsheets[idx[idxIt]]); + } + + [[nodiscard]] + constexpr const auto &getSubSheet(const SubSheetIdx &idx) const noexcept { + return getSubSheet(idx, 0, &subsheet); + } + + [[nodiscard]] + constexpr auto &getSubSheet(const SubSheetIdx &idx, std::size_t idxIt, const SubSheet *pSubsheet) noexcept { + if (idxIt == idx.size()) { + return *pSubsheet; + } + return getSubSheet(idx, idxIt + 1, &pSubsheet->subsheets[idx[idxIt]]); + } + + [[nodiscard]] + constexpr auto &getSubSheet(const SubSheetIdx &idx) noexcept { + return getSubSheet(idx, 0, &subsheet); + } + + [[nodiscard]] + constexpr const auto &columns(const SubSheetIdx &idx = {}) const noexcept { + return getSubSheet(idx).columns; + } + + [[nodiscard]] + constexpr const auto &rows(const SubSheetIdx &idx = {}) const noexcept { + return getSubSheet(idx).rows; + } + + [[nodiscard]] + constexpr auto &columns(const SubSheetIdx &idx = {}) noexcept { + return getSubSheet(idx).columns; + } + + [[nodiscard]] + constexpr auto &rows(const SubSheetIdx &idx = {}) noexcept { + return getSubSheet(idx).rows; + } + + [[nodiscard]] + constexpr uint8_t getPixel4Bpp(std::size_t idx) const noexcept { + oxAssert(bpp == 4, "TileSheetV1::getPixel4Bpp: wrong bpp"); + if (idx & 1) { + return this->pixels[idx / 2] >> 4; + } else { + return this->pixels[idx / 2] & 0b0000'1111; + } + } + + [[nodiscard]] + constexpr uint8_t getPixel8Bpp(std::size_t idx) const noexcept { + oxAssert(bpp == 8, "TileSheetV1::getPixel8Bpp: wrong bpp"); + return this->pixels[idx]; + } + + [[nodiscard]] + constexpr auto getPixel(std::size_t idx) const noexcept { + if (this->bpp == 4) { + return getPixel4Bpp(idx); + } else { + return getPixel8Bpp(idx); + } + } + + [[nodiscard]] + constexpr auto getPixel4Bpp(const geo::Point &pt, const SubSheetIdx &subsheetIdx) const noexcept { + oxAssert(bpp == 4, "TileSheetV1::getPixel4Bpp: wrong bpp"); + const auto idx = ptToIdx(pt, this->getSubSheet(subsheetIdx).columns); + return getPixel4Bpp(idx); + } + + [[nodiscard]] + constexpr auto getPixel8Bpp(const geo::Point &pt, const SubSheetIdx &subsheetIdx) const noexcept { + oxAssert(bpp == 8, "TileSheetV1::getPixel8Bpp: wrong bpp"); + const auto idx = ptToIdx(pt, this->getSubSheet(subsheetIdx).columns); + return getPixel8Bpp(idx); + } + + [[nodiscard]] + constexpr auto getPixel(const geo::Point &pt, const SubSheetIdx &subsheetIdx) const noexcept { + const auto idx = ptToIdx(pt, this->getSubSheet(subsheetIdx).columns); + return getPixel(idx); + } + + constexpr void setPixel(uint64_t idx, uint8_t palIdx) noexcept { + auto &pixel = this->pixels[idx / 2]; + if (bpp == 4) { + if (idx & 1) { + pixel = (pixel & 0b0000'1111) | (palIdx << 4); + } else { + pixel = (pixel & 0b1111'0000) | (palIdx); + } + } else { + pixel = palIdx; + } + } + + constexpr void setPixel(const SubSheetIdx &subsheetIdx, const geo::Point &pt, uint8_t palIdx) noexcept { + const auto idx = ptToIdx(pt, this->getSubSheet(subsheetIdx).columns); + setPixel(idx, palIdx); + } +}; + oxModelBegin(Palette) oxModelField(colors) oxModelEnd() -oxModelBegin(TileSheet) +oxModelBegin(TileSheetV1) oxModelField(bpp) oxModelField(rows) oxModelField(columns) @@ -123,6 +256,23 @@ oxModelBegin(TileSheet) oxModelField(pixels) oxModelEnd() +oxModelBegin(TileSheet) + oxModelField(bpp) + oxModelField(defaultPalette) + oxModelField(pal) + oxModelField(pixels) + oxModelField(subsheet) +oxModelEnd() + +oxModelBegin(TileSheet::SubSheet) + oxModelField(name); + oxModelField(begin); + oxModelField(size); + oxModelField(rows); + oxModelField(columns); + oxModelField(subsheets) +oxModelEnd() + struct Sprite { unsigned idx = 0; unsigned x = 0; diff --git a/src/nostalgia/core/media.hpp b/src/nostalgia/core/media.hpp index 6bd0f047..b1d43bab 100644 --- a/src/nostalgia/core/media.hpp +++ b/src/nostalgia/core/media.hpp @@ -4,27 +4,40 @@ #pragma once +#include + #include #include #include "context.hpp" +#include "typeconv.hpp" namespace nostalgia::core { template ox::Result> readObj(Context *ctx, const ox::FileAddress &file, bool forceLoad = false) noexcept { #ifndef OX_BARE_METAL + constexpr auto readConvert = [](const ox::Buffer &buff) -> ox::Result { + auto [obj, err] = ox::readClaw(buff); + if (err) { + if (err != ox::Error_ClawTypeVersionMismatch && err != ox::Error_ClawTypeMismatch) { + return err; + } + oxReturnError(convert(buff, T::TypeName, T::TypeVersion, &obj)); + } + return obj; + }; oxRequire(path, file.getPath()); if (forceLoad) { oxRequire(buff, ctx->rom->read(file)); - oxRequire(obj, ox::readClaw(buff)); + oxRequire(obj, readConvert(buff)); oxRequire(cached, ctx->assetManager.template setAsset(path, obj)); return cached; } else { auto [cached, err] = ctx->assetManager.template getAsset(path); if (err) { oxRequire(buff, ctx->rom->read(file)); - oxRequire(obj, ox::readClaw(buff)); + oxRequire(obj, readConvert(buff)); oxReturnError(ctx->assetManager.template setAsset(path, obj).moveTo(&cached)); } return cached; diff --git a/src/nostalgia/core/studio/tilesheeteditor-imgui.hpp b/src/nostalgia/core/studio/tilesheeteditor-imgui.hpp index 196ee6f6..f63d6547 100644 --- a/src/nostalgia/core/studio/tilesheeteditor-imgui.hpp +++ b/src/nostalgia/core/studio/tilesheeteditor-imgui.hpp @@ -47,7 +47,7 @@ class TileSheetEditorImGui: public studio::Editor { void draw(core::Context*) noexcept override; - virtual studio::UndoStack *undoStack() noexcept override; + studio::UndoStack *undoStack() noexcept final; protected: void saveItem() override; diff --git a/src/nostalgia/core/studio/tilesheeteditor.cpp b/src/nostalgia/core/studio/tilesheeteditor.cpp index 64d274a3..428b2757 100644 --- a/src/nostalgia/core/studio/tilesheeteditor.cpp +++ b/src/nostalgia/core/studio/tilesheeteditor.cpp @@ -40,8 +40,8 @@ void TileSheetEditor::draw() noexcept { void TileSheetEditor::scrollV(const geo::Vec2 &paneSz, float wheel, bool zoomMod) noexcept { const auto pixelSize = m_pixelsDrawer.pixelSize(paneSz); - const ImVec2 sheetSize(pixelSize.x * static_cast(img().columns) * TileWidth, - pixelSize.y * static_cast(img().rows) * TileHeight); + const ImVec2 sheetSize(pixelSize.x * static_cast(img().columns()) * TileWidth, + pixelSize.y * static_cast(img().rows()) * TileHeight); if (zoomMod) { m_pixelSizeMod = ox::clamp(m_pixelSizeMod + wheel * 0.02f, 0.55f, 2.f); m_pixelsDrawer.setPixelSizeMod(m_pixelSizeMod); @@ -57,8 +57,8 @@ void TileSheetEditor::scrollV(const geo::Vec2 &paneSz, float wheel, bool zoomMod void TileSheetEditor::scrollH(const geo::Vec2 &paneSz, float wheelh) noexcept { const auto pixelSize = m_pixelsDrawer.pixelSize(paneSz); - const ImVec2 sheetSize(pixelSize.x * static_cast(img().columns) * TileWidth, - pixelSize.y * static_cast(img().rows) * TileHeight); + const ImVec2 sheetSize(pixelSize.x * static_cast(img().columns()) * TileWidth, + pixelSize.y * static_cast(img().rows()) * TileHeight); m_scrollOffset.x += wheelh * 0.1f; m_scrollOffset.x = ox::clamp(m_scrollOffset.x, -(sheetSize.x / 2), 0.f); } diff --git a/src/nostalgia/core/studio/tilesheeteditormodel.cpp b/src/nostalgia/core/studio/tilesheeteditormodel.cpp index 973f170d..20fe3c6f 100644 --- a/src/nostalgia/core/studio/tilesheeteditormodel.cpp +++ b/src/nostalgia/core/studio/tilesheeteditormodel.cpp @@ -25,9 +25,9 @@ void TileSheetEditorModel::paste() { void TileSheetEditorModel::drawCommand(const geo::Point &pt, std::size_t palIdx) noexcept { if (m_ongoingDrawCommand) { - m_updated = m_ongoingDrawCommand->append(ptToIdx(pt, m_img.columns)); + m_updated = m_ongoingDrawCommand->append(ptToIdx(pt, m_img.columns())); } else { - const auto idx = ptToIdx(pt, m_img.columns); + const auto idx = ptToIdx(pt, m_img.columns()); if (m_img.getPixel(idx) != palIdx) { pushCommand(new DrawCommand(&m_updated, &m_img, idx, palIdx)); } @@ -48,7 +48,7 @@ void TileSheetEditorModel::ackUpdate() noexcept { void TileSheetEditorModel::getFillPixels(bool *pixels, const geo::Point &pt, int oldColor) const noexcept { const auto tileIdx = [this](const geo::Point &pt) noexcept { - return ptToIdx(pt, img().columns) / PixelsPerTile; + return ptToIdx(pt, img().columns()) / PixelsPerTile; }; // get points const auto leftPt = pt + geo::Point(-1, 0); @@ -56,11 +56,11 @@ void TileSheetEditorModel::getFillPixels(bool *pixels, const geo::Point &pt, int const auto topPt = pt + geo::Point(0, -1); const auto bottomPt = pt + geo::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 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; diff --git a/src/nostalgia/core/studio/tilesheetpixelgrid.cpp b/src/nostalgia/core/studio/tilesheetpixelgrid.cpp index 534ec425..208ebd25 100644 --- a/src/nostalgia/core/studio/tilesheetpixelgrid.cpp +++ b/src/nostalgia/core/studio/tilesheetpixelgrid.cpp @@ -69,31 +69,31 @@ void TileSheetGrid::setBufferObjects(const geo::Vec2 &paneSize, const TileSheet setBufferObject(pt1, pt2, c, vbo, pixSize); }; // set buffer length - const auto width = img.columns * TileWidth; - const auto height = img.rows * TileHeight; + const auto width = img.columns() * TileWidth; + const auto height = img.rows() * TileHeight; const auto pixelCnt = static_cast(width * height); - const auto tileCnt = static_cast(img.columns * img.rows); + const auto tileCnt = static_cast(img.columns() * img.rows()); m_bufferSet.vertices.resize((tileCnt + pixelCnt) * VertexVboLength); // set buffer auto i = 0ull; // pixel outlines constexpr auto pixOutlineColor = color32(0.4431f, 0.4901f, 0.4941f); - for (auto x = 0; x < img.columns * TileWidth + 1; ++x) { - set(i, {x, 0}, {x, img.rows * TileHeight}, pixOutlineColor); + for (auto x = 0; x < img.columns() * TileWidth + 1; ++x) { + set(i, {x, 0}, {x, img.rows() * TileHeight}, pixOutlineColor); ++i; } - for (auto y = 0; y < img.rows * TileHeight + 1; ++y) { - set(i, {0, y}, {img.columns * TileWidth, y}, pixOutlineColor); + for (auto y = 0; y < img.rows() * TileHeight + 1; ++y) { + set(i, {0, y}, {img.columns() * TileWidth, y}, pixOutlineColor); ++i; } // tile outlines constexpr auto tileOutlineColor = color32(0.f, 0.f, 0.f); - for (auto x = 0; x < img.columns * TileWidth + 1; x += TileWidth) { - set(i, {x, 0}, {x, img.rows * TileHeight}, tileOutlineColor); + for (auto x = 0; x < img.columns() * TileWidth + 1; x += TileWidth) { + set(i, {x, 0}, {x, img.rows() * TileHeight}, tileOutlineColor); ++i; } - for (auto y = 0; y < img.rows * TileHeight + 1; y += TileHeight) { - set(i, {0, y}, {img.columns * TileWidth, y}, tileOutlineColor); + for (auto y = 0; y < img.rows() * TileHeight + 1; y += TileHeight) { + set(i, {0, y}, {img.columns() * TileWidth, y}, tileOutlineColor); ++i; } } diff --git a/src/nostalgia/core/studio/tilesheetpixels.cpp b/src/nostalgia/core/studio/tilesheetpixels.cpp index a0b7060f..b080c8df 100644 --- a/src/nostalgia/core/studio/tilesheetpixels.cpp +++ b/src/nostalgia/core/studio/tilesheetpixels.cpp @@ -81,7 +81,7 @@ void TileSheetPixels::setPixelBufferObject(const geo::Vec2 &paneSize, unsigned v void TileSheetPixels::setBufferObjects(const geo::Vec2 &paneSize, const TileSheet &img, const Palette &pal, glutils::BufferSet *bg) noexcept { const auto setPixel = [this, paneSize, bg, img, pal](std::size_t i, uint8_t p) { const auto color = pal.colors[p]; - const auto pt = idxToPt(static_cast(i), img.columns); + const auto pt = idxToPt(static_cast(i), img.columns()); const auto fx = static_cast(pt.x); const auto fy = static_cast(pt.y); const auto vbo = &bg->vertices[i * VertexVboLength]; @@ -89,8 +89,8 @@ void TileSheetPixels::setBufferObjects(const geo::Vec2 &paneSize, const TileShee setPixelBufferObject(paneSize, i * VertexVboRows, fx, fy, color, vbo, ebo); }; // set buffer lengths - const auto width = img.columns * TileWidth; - const auto height = img.rows * TileHeight; + const auto width = img.columns() * TileWidth; + const auto height = img.rows() * TileHeight; const auto tiles = static_cast(width * height); m_bufferSet.vertices.resize(tiles * VertexVboLength); m_bufferSet.elements.resize(tiles * VertexEboLength); diff --git a/src/nostalgia/core/typeconv.cpp b/src/nostalgia/core/typeconv.cpp new file mode 100644 index 00000000..56c0571a --- /dev/null +++ b/src/nostalgia/core/typeconv.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#ifndef OX_BARE_METAL +#include + +#include "typeconv.hpp" + +namespace nostalgia::core { + +struct Converter { + virtual bool matches(const ox::String &srcTypeName, int srcTypeVersion, const ox::String &dstTypeName, int dstTypeVersion) noexcept = 0; + + virtual ~Converter() noexcept = default; + + virtual ox::Error convert(const ox::Buffer &pV1Buff, void *pV2) noexcept = 0; +}; + +struct TileSheetV1ToV2Converter: public Converter { + constexpr TileSheetV1ToV2Converter() noexcept = default; + + virtual ~TileSheetV1ToV2Converter() noexcept = default; + + bool matches(const ox::String &srcTypeName, int srcTypeVersion, const ox::String &dstTypeName, int dstTypeVersion) noexcept final { + return srcTypeName == TileSheetV1::TypeName + && srcTypeVersion == TileSheetV1::TypeVersion + && dstTypeName == TileSheet::TypeName + && dstTypeVersion == TileSheet::TypeVersion; + } + + ox::Error convert(const ox::Buffer &pV1Buff, void *pV2) noexcept final { + oxRequire(v1, ox::readClaw(pV1Buff)); + auto v2 = static_cast(pV2); + v2->bpp = v1.bpp; + v2->subsheet.name = "Root"; + v2->subsheet.rows = v1.rows; + v2->subsheet.columns = v1.columns; + v2->subsheet.size = v1.pixels.size(); + v2->defaultPalette = v1.defaultPalette; + v2->pal = v1.pal; + v2->pixels = v1.pixels; + return OxError(0); + } +}; + +static const auto converters = [] { + ox::Vector, 1> converters; + converters.emplace_back(new TileSheetV1ToV2Converter()); + return converters; +}(); + +static auto findConverter(const ox::String &srcTypeName, int srcTypeVersion, const ox::String &dstTypeName, int dstTypeVersion) noexcept -> ox::Result { + for (auto &c : converters) { + if (c->matches(srcTypeName, srcTypeVersion, dstTypeName, dstTypeVersion)) { + return c.get(); + } + } + return OxError(1, "Could not find converter"); +}; + +ox::Error convert(const ox::Buffer &srcBuffer, const ox::String &dstTypeName, int dstTypeVersion, void *dstObj) noexcept { + oxRequire(hdr, ox::readClawHeader(srcBuffer)); + oxRequire(c, findConverter(hdr.typeName, hdr.typeVersion, dstTypeName, dstTypeVersion)); + return c->convert(srcBuffer, dstObj); +} + +} +#endif diff --git a/src/nostalgia/core/typeconv.hpp b/src/nostalgia/core/typeconv.hpp new file mode 100644 index 00000000..c6b63012 --- /dev/null +++ b/src/nostalgia/core/typeconv.hpp @@ -0,0 +1,17 @@ +/* + * Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include +#include +#include + +#include "gfx.hpp" + +namespace nostalgia::core { + +ox::Error convert(const ox::Buffer &srcBuffer, const ox::String &dstTypeName, int dstTypeVersion, void *dstObj) noexcept; + +}