[nostalgia/core] Upgrade TileSheet format to support subsheets and add conversion system

This commit is contained in:
Gary Talent 2022-02-17 04:27:23 -06:00
parent 3c44c86e91
commit 7ac7909510
11 changed files with 290 additions and 36 deletions

View File

@ -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
)

View File

@ -13,6 +13,7 @@
#include "gfx.hpp"
#include "input.hpp"
#include "media.hpp"
#include "typeconv.hpp"
namespace nostalgia::core {

View File

@ -35,7 +35,7 @@ struct Palette {
ox::Vector<Color16> 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<std::size_t, 4>;
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<SubSheet> 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<uint8_t> 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;

View File

@ -4,27 +4,40 @@
#pragma once
#include <ox/std/defines.hpp>
#include <ox/claw/claw.hpp>
#include <ox/fs/fs.hpp>
#include "context.hpp"
#include "typeconv.hpp"
namespace nostalgia::core {
template<typename T>
ox::Result<AssetRef<T>> readObj(Context *ctx, const ox::FileAddress &file, bool forceLoad = false) noexcept {
#ifndef OX_BARE_METAL
constexpr auto readConvert = [](const ox::Buffer &buff) -> ox::Result<T> {
auto [obj, err] = ox::readClaw<T>(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<T>(buff));
oxRequire(obj, readConvert(buff));
oxRequire(cached, ctx->assetManager.template setAsset(path, obj));
return cached;
} else {
auto [cached, err] = ctx->assetManager.template getAsset<T>(path);
if (err) {
oxRequire(buff, ctx->rom->read(file));
oxRequire(obj, ox::readClaw<T>(buff));
oxRequire(obj, readConvert(buff));
oxReturnError(ctx->assetManager.template setAsset(path, obj).moveTo(&cached));
}
return cached;

View File

@ -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;

View File

@ -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<float>(img().columns) * TileWidth,
pixelSize.y * static_cast<float>(img().rows) * TileHeight);
const ImVec2 sheetSize(pixelSize.x * static_cast<float>(img().columns()) * TileWidth,
pixelSize.y * static_cast<float>(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<float>(img().columns) * TileWidth,
pixelSize.y * static_cast<float>(img().rows) * TileHeight);
const ImVec2 sheetSize(pixelSize.x * static_cast<float>(img().columns()) * TileWidth,
pixelSize.y * static_cast<float>(img().rows()) * TileHeight);
m_scrollOffset.x += wheelh * 0.1f;
m_scrollOffset.x = ox::clamp(m_scrollOffset.x, -(sheetSize.x / 2), 0.f);
}

View File

@ -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;

View File

@ -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<unsigned>(width * height);
const auto tileCnt = static_cast<unsigned>(img.columns * img.rows);
const auto tileCnt = static_cast<unsigned>(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;
}
}

View File

@ -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<int>(i), img.columns);
const auto pt = idxToPt(static_cast<int>(i), img.columns());
const auto fx = static_cast<float>(pt.x);
const auto fy = static_cast<float>(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<unsigned>(width * height);
m_bufferSet.vertices.resize(tiles * VertexVboLength);
m_bufferSet.elements.resize(tiles * VertexEboLength);

View File

@ -0,0 +1,71 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <ox/std/defines.hpp>
#ifndef OX_BARE_METAL
#include <ox/claw/read.hpp>
#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<TileSheetV1>(pV1Buff));
auto v2 = static_cast<TileSheet*>(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<ox::UniquePtr<Converter>, 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<Converter*> {
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

View File

@ -0,0 +1,17 @@
/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/def.hpp>
#include <ox/std/error.hpp>
#include <ox/std/string.hpp>
#include "gfx.hpp"
namespace nostalgia::core {
ox::Error convert(const ox::Buffer &srcBuffer, const ox::String &dstTypeName, int dstTypeVersion, void *dstObj) noexcept;
}