Files
ox/src/nostalgia/core/gfx.hpp
T

542 lines
15 KiB
C++

/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/array.hpp>
#include <ox/std/types.hpp>
#include <ox/model/def.hpp>
#include <nostalgia/geo/point.hpp>
#include <nostalgia/geo/size.hpp>
#include "color.hpp"
#include "context.hpp"
#include "ptidxconv.hpp"
namespace nostalgia::core {
extern ox::Array<char, 128> charMap;
class Drawer {
public:
virtual ~Drawer() = default;
virtual void draw(Context*) noexcept = 0;
};
enum class TileSheetSpace {
Background,
Sprite
};
struct NostalgiaPalette {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaPalette";
static constexpr auto TypeVersion = 1;
ox::Vector<Color16> colors = {};
};
struct Palette {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette";
static constexpr auto TypeVersion = 1;
ox::Vector<Color16> colors = {};
[[nodiscard]]
constexpr Color16 color(auto idx) const noexcept {
if (idx < colors.size()) [[likely]] {
return colors[idx];
}
return 0;
}
};
// Predecessor to TileSheet, kept for backward compatibility
struct NostalgiaGraphic {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaGraphic";
static constexpr auto TypeVersion = 1;
int8_t bpp = 0;
// rows and columns are really only used by TileSheetEditor
int rows = 1;
int columns = 1;
ox::FileAddress defaultPalette;
Palette pal;
ox::Vector<uint8_t> pixels = {};
};
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 = 1;
ox::String name;
int columns = 0;
int rows = 0;
ox::Vector<SubSheet> subsheets;
ox::Vector<uint8_t> pixels;
constexpr SubSheet() noexcept = default;
constexpr SubSheet(const SubSheet &other) noexcept {
name = other.name;
columns = other.columns;
rows = other.rows;
subsheets = other.subsheets;
pixels = other.pixels;
}
constexpr SubSheet(SubSheet &&other) noexcept {
name = std::move(other.name);
columns = other.columns;
rows = other.rows;
subsheets = std::move(other.subsheets);
pixels = std::move(other.pixels);
other.name = "";
other.columns = 0;
other.rows = 0;
}
constexpr SubSheet(ox::CRStringView pName, int pColumns, int pRows, int bpp) noexcept:
name(pName), columns(pColumns), rows(pRows),
pixels(static_cast<std::size_t>(columns * rows * PixelsPerTile) / (bpp == 4 ? 2u : 1u)) {
}
constexpr SubSheet(ox::CRStringView pName, int pColumns, int pRows, ox::Vector<uint8_t> pPixels) noexcept:
name(pName), columns(pColumns), rows(pRows), pixels(std::move(pPixels)) {
}
constexpr SubSheet &operator=(const SubSheet &other) noexcept {
name = other.name;
columns = other.columns;
rows = other.rows;
subsheets = other.subsheets;
pixels = other.pixels;
return *this;
}
constexpr SubSheet &operator=(SubSheet &&other) noexcept {
name = std::move(other.name);
columns = other.columns;
rows = other.rows;
subsheets = std::move(other.subsheets);
pixels = std::move(other.pixels);
return *this;
}
[[nodiscard]]
constexpr auto idx(const geo::Point &pt) const noexcept {
return ptToIdx(pt, columns);
}
/**
* Reads all pixels of this sheet or its children into the given pixel list
* @param pixels
*/
constexpr void readPixelsTo(ox::Vector<uint8_t> *pPixels, int8_t bpp) const noexcept {
if (subsheets.size()) {
for (auto &s: subsheets) {
s.readPixelsTo(pPixels);
}
} else {
if (bpp == 4) {
for (auto p: this->pixels) {
pPixels->emplace_back(p & 0b1111);
pPixels->emplace_back(p >> 4);
}
} else {
for (auto p: this->pixels) {
pPixels->emplace_back(p);
}
}
}
}
/**
* Reads all pixels of this sheet or its children into the given pixel list
* @param pixels
*/
constexpr void readPixelsTo(ox::Vector<uint8_t> *pPixels) const noexcept {
if (subsheets.size()) {
for (auto &s: subsheets) {
s.readPixelsTo(pPixels);
}
} else {
for (auto p : this->pixels) {
pPixels->emplace_back(p);
}
}
}
[[nodiscard]]
constexpr std::size_t size() const noexcept {
return static_cast<std::size_t>(columns) * static_cast<std::size_t>(rows);
}
[[nodiscard]]
constexpr std::size_t unusedPixels() const noexcept {
std::size_t childrenSize = 0;
for (auto &c : subsheets) {
childrenSize += c.size();
}
return size() - childrenSize;
}
[[nodiscard]]
constexpr uint8_t getPixel4Bpp(std::size_t idx) const noexcept {
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 {
return this->pixels[idx];
}
[[nodiscard]]
constexpr auto getPixel(int8_t pBpp, std::size_t idx) const noexcept {
if (pBpp == 4) {
return getPixel4Bpp(idx);
} else {
return getPixel8Bpp(idx);
}
}
[[nodiscard]]
constexpr auto getPixel4Bpp(const geo::Point &pt) const noexcept {
const auto idx = ptToIdx(pt, columns);
return getPixel4Bpp(idx);
}
[[nodiscard]]
constexpr auto getPixel8Bpp(const geo::Point &pt) const noexcept {
const auto idx = ptToIdx(pt, columns);
return getPixel8Bpp(idx);
}
[[nodiscard]]
constexpr auto getPixel(int8_t pBpp, const geo::Point &pt) const noexcept {
const auto idx = ptToIdx(pt, columns);
return getPixel(pBpp, idx);
}
constexpr auto walkPixels(int8_t pBpp, auto callback) const noexcept {
if (pBpp == 4) {
const auto pixelCnt = ox::min<std::size_t>(static_cast<std::size_t>(columns * rows * PixelsPerTile) / 2,
pixels.size());
//oxAssert(pixels.size() == pixelCnt, "Pixel count does not match rows and columns");
for (std::size_t i = 0; i < pixelCnt; ++i) {
const auto colorIdx1 = pixels[i] & 0xF;
const auto colorIdx2 = pixels[i] >> 4;
callback(i * 2 + 0, colorIdx1);
callback(i * 2 + 1, colorIdx2);
}
} else {
const auto pixelCnt = ox::min<std::size_t>(static_cast<std::size_t>(columns * rows * PixelsPerTile),
pixels.size());
for (std::size_t i = 0; i < pixelCnt; ++i) {
const auto p = pixels[i];
callback(i, p);
}
}
}
constexpr void setPixel(int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept {
auto &pixel = this->pixels[idx / 2];
if (pBpp == 4) {
if (idx & 1) {
pixel = (pixel & 0b0000'1111) | (palIdx << 4);
} else {
pixel = (pixel & 0b1111'0000) | (palIdx);
}
} else {
pixel = palIdx;
}
}
constexpr void setPixel(int8_t pBpp, const geo::Point &pt, uint8_t palIdx) noexcept {
const auto idx = ptToIdx(pt, columns);
setPixel(pBpp, idx, palIdx);
}
constexpr auto setPixelCount(int8_t pBpp, std::size_t cnt) noexcept {
switch (pBpp) {
case 4:
pixels.resize(cnt / 2);
return OxError(0);
case 8:
pixels.resize(cnt);
return OxError(0);
default:
return OxError(1, "Invalid pBpp used for TileSheet::SubSheet::setPixelCount");
}
}
/**
* Gets a count of the pixels in this sheet, and not that of its children.
* @param pBpp bits per pixel, need for knowing how to count the pixels
* @return a count of the pixels in this sheet
*/
[[nodiscard]]
constexpr auto pixelCnt(int8_t pBpp) const noexcept {
return pBpp == 4 ? pixels.size() * 2 : pixels.size();
}
};
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheet";
static constexpr auto TypeVersion = 2;
int8_t bpp = 4;
ox::FileAddress defaultPalette;
SubSheet subsheet{"Root", 1, 1, bpp};
constexpr TileSheet() noexcept = default;
TileSheet(const TileSheet &other) noexcept = default;
inline TileSheet(TileSheet &&other) noexcept:
bpp(other.bpp),
defaultPalette(std::move(other.defaultPalette)),
subsheet(std::move(other.subsheet)) {
}
inline auto &operator=(const TileSheet &other) noexcept {
if (this != &other) {
bpp = other.bpp;
defaultPalette = other.defaultPalette;
subsheet = other.subsheet; } return *this;
}
inline auto &operator=(TileSheet &&other) noexcept {
bpp = other.bpp;
defaultPalette = std::move(other.defaultPalette);
subsheet = std::move(other.subsheet);
return *this;
}
[[nodiscard]]
constexpr auto validateSubSheetIdx(const SubSheetIdx &pIdx, std::size_t pIdxIt, const SubSheet *pSubsheet) noexcept {
if (pIdxIt == pIdx.size()) {
return pIdx;
}
const auto currentIdx = pIdx[pIdxIt];
if (pSubsheet->subsheets.size() <= currentIdx) {
auto out = pIdx;
if (pSubsheet->subsheets.size()) {
out.back().value = pSubsheet->subsheets.size() - 1;
} else {
out.pop_back();
}
return out;
}
return validateSubSheetIdx(pIdx, pIdxIt + 1, &pSubsheet->subsheets[pIdx[pIdxIt]]);
}
/**
* validateSubSheetIdx takes a SubSheetIdx and moves the index to the
* preceding or parent sheet if the current corresponding sheet does
* not exist.
* @param idx SubSheetIdx to validate and correct
* @return a valid version of idx
*/
[[nodiscard]]
constexpr auto validateSubSheetIdx(const SubSheetIdx &idx) noexcept {
return validateSubSheetIdx(idx, 0, &subsheet);
}
[[nodiscard]]
constexpr static const auto &getSubSheet(const SubSheetIdx &idx, std::size_t idxIt,
const SubSheet *pSubsheet) noexcept {
if (idxIt == idx.size()) {
return *pSubsheet;
}
const auto currentIdx = idx[idxIt];
if (pSubsheet->subsheets.size() < currentIdx) {
return *pSubsheet;
}
return getSubSheet(idx, idxIt + 1, &pSubsheet->subsheets[currentIdx]);
}
[[nodiscard]]
constexpr static auto &getSubSheet(const SubSheetIdx &idx, std::size_t idxIt, SubSheet *pSubsheet) 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) noexcept {
return getSubSheet(idx, 0, &subsheet);
}
constexpr ox::Error addSubSheet(const SubSheetIdx &idx) noexcept {
auto &parent = getSubSheet(idx);
if (parent.subsheets.size() < 2) {
parent.subsheets.emplace_back(ox::sfmt("Subsheet {}", parent.subsheets.size()), 1, 1, bpp);
} else {
parent.subsheets.emplace_back("Subsheet 0", parent.columns, parent.rows, bpp);
parent.subsheets.emplace_back("Subsheet 1", 1, 1, bpp);
}
return OxError(0);
}
[[nodiscard]]
constexpr static auto rmSubSheet(const SubSheetIdx &idx, std::size_t idxIt, SubSheet *pSubsheet) noexcept {
if (idxIt == idx.size() - 1) {
return pSubsheet->subsheets.erase(idx[idxIt]).error;
}
return rmSubSheet(idx, idxIt + 1, &pSubsheet->subsheets[idx[idxIt]]);
}
[[nodiscard]]
constexpr auto rmSubSheet(const SubSheetIdx &idx) noexcept {
return rmSubSheet(idx, 0, &subsheet);
}
[[nodiscard]]
constexpr auto getPixel4Bpp(const geo::Point &pt, const SubSheetIdx &subsheetIdx) const noexcept {
oxAssert(bpp == 4, "TileSheetV1::getPixel4Bpp: wrong bpp");
auto &s = this->getSubSheet(subsheetIdx);
const auto idx = ptToIdx(pt, s.columns);
return s.getPixel4Bpp(idx);
}
[[nodiscard]]
constexpr auto getPixel8Bpp(const geo::Point &pt, const SubSheetIdx &subsheetIdx) const noexcept {
oxAssert(bpp == 8, "TileSheetV1::getPixel8Bpp: wrong bpp");
auto &s = this->getSubSheet(subsheetIdx);
const auto idx = ptToIdx(pt, s.columns);
return s.getPixel8Bpp(idx);
}
[[nodiscard]]
auto pixels() const noexcept {
ox::Vector<uint8_t> out;
subsheet.readPixelsTo(&out);
return out;
}
};
struct CompactTileSheet {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.CompactTileSheet";
static constexpr auto TypeVersion = 1;
int8_t bpp = 0;
ox::FileAddress defaultPalette;
ox::Vector<uint8_t> pixels = {};
};
oxModelBegin(NostalgiaPalette)
oxModelField(colors)
oxModelEnd()
oxModelBegin(Palette)
oxModelField(colors)
oxModelEnd()
oxModelBegin(NostalgiaGraphic)
oxModelField(bpp)
oxModelField(rows)
oxModelField(columns)
oxModelField(defaultPalette)
oxModelField(pal)
oxModelField(pixels)
oxModelEnd()
oxModelBegin(TileSheet::SubSheet)
oxModelField(name);
oxModelField(rows);
oxModelField(columns);
oxModelField(subsheets)
oxModelField(pixels)
oxModelEnd()
oxModelBegin(TileSheet)
oxModelField(bpp)
oxModelField(defaultPalette)
oxModelField(subsheet)
oxModelEnd()
oxModelBegin(CompactTileSheet)
oxModelField(bpp)
oxModelField(defaultPalette)
oxModelField(pixels)
oxModelEnd()
struct Sprite {
unsigned idx = 0;
int x = 0;
int y = 0;
unsigned tileIdx = 0;
unsigned spriteShape = 0;
unsigned spriteSize = 0;
unsigned flipX = 0;
};
oxModelBegin(Sprite)
oxModelField(idx)
oxModelField(x)
oxModelField(y)
oxModelField(tileIdx)
oxModelField(spriteShape)
oxModelField(spriteSize)
oxModelField(flipX)
oxModelEnd()
ox::Error initGfx(Context *ctx) noexcept;
void addCustomDrawer(Context *ctx, Drawer *cd) noexcept;
void removeCustomDrawer(Context *ctx, Drawer *cd) noexcept;
void setWindowTitle(Context *ctx, ox::CRStringView title) noexcept;
void focusWindow(Context *ctx) noexcept;
[[nodiscard]]
int getScreenWidth(Context *ctx) noexcept;
[[nodiscard]]
int getScreenHeight(Context *ctx) noexcept;
[[nodiscard]]
geo::Size getScreenSize(Context *ctx) noexcept;
[[nodiscard]]
uint8_t bgStatus(Context *ctx) noexcept;
void setBgStatus(Context *ctx, uint32_t status) noexcept;
bool bgStatus(Context *ctx, unsigned bg) noexcept;
void setBgStatus(Context *ctx, unsigned bg, bool status) noexcept;
ox::Error initConsole(Context *ctx) noexcept;
void setBgCbb(Context *ctx, unsigned bgIdx, unsigned cbb) noexcept;
/**
* @param section describes which section of the selected TileSheetSpace to use (e.g. MEM_PALLETE_BG[section])
*/
ox::Error loadBgTileSheet(Context *ctx, unsigned cbb, const ox::FileAddress &tilesheet,
const ox::FileAddress &palette = nullptr) noexcept;
ox::Error loadSpriteTileSheet(Context *ctx,
const ox::FileAddress &tilesheetAddr,
const ox::FileAddress &paletteAddr) noexcept;
void puts(Context *ctx, int column, int row, ox::CRStringView str) noexcept;
void setTile(Context *ctx, unsigned bgIdx, int column, int row, uint8_t tile) noexcept;
void clearTileLayer(Context *ctx, unsigned bgIdx) noexcept;
void hideSprite(Context *ctx, unsigned) noexcept;
void setSprite(Context *ctx, unsigned idx, int x, int y, unsigned tileIdx,
unsigned spriteShape = 0, unsigned spriteSize = 0, unsigned flipX = 0) noexcept;
void setSprite(Context *ctx, const Sprite &s) noexcept;
}