Squashed 'deps/nostalgia/' content from commit 9cb6bd4a
git-subtree-dir: deps/nostalgia git-subtree-split: 9cb6bd4a32e9f39a858f72443ff5c6d40489fe22
This commit is contained in:
2
src/CMakeLists.txt
Normal file
2
src/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
add_subdirectory(nostalgia)
|
||||
add_subdirectory(olympic)
|
38
src/nostalgia/CMakeLists.txt
Normal file
38
src/nostalgia/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
||||
|
||||
project(nostalgia CXX)
|
||||
|
||||
#project packages
|
||||
|
||||
set(NOSTALGIA_BUILD_PLAYER ON CACHE BOOL "Build Player")
|
||||
set(NOSTALGIA_BUILD_STUDIO ON CACHE BOOL "Build Studio")
|
||||
|
||||
if(BUILDCORE_TARGET STREQUAL "gba")
|
||||
set(NOSTALGIA_BUILD_STUDIO OFF)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
set(CMAKE_INSTALL_NAME_DIR "@executable_path/../Library/nostalgia")
|
||||
set(NOSTALGIA_DIST_BIN NostalgiaStudio.app/Contents/MacOS)
|
||||
set(NOSTALGIA_DIST_LIB NostalgiaStudio.app/Contents/Library)
|
||||
set(NOSTALGIA_DIST_MODULE NostalgiaStudio.app/Contents/Plugins)
|
||||
set(NOSTALGIA_DIST_RESOURCES NostalgiaStudio.app/Contents/Resources)
|
||||
set(NOSTALGIA_DIST_MAC_APP_CONTENTS NostalgiaStudio.app/Contents)
|
||||
else()
|
||||
set(NOSTALGIA_DIST_BIN bin)
|
||||
set(NOSTALGIA_DIST_LIB lib)
|
||||
set(NOSTALGIA_DIST_MODULE lib)
|
||||
set(NOSTALGIA_DIST_RESOURCES share)
|
||||
endif()
|
||||
|
||||
add_subdirectory(modules)
|
||||
|
||||
if(${NOSTALGIA_BUILD_PLAYER})
|
||||
add_subdirectory(player)
|
||||
endif()
|
||||
|
||||
if(NOT BUILDCORE_TARGET STREQUAL "gba")
|
||||
add_subdirectory(tools)
|
||||
if(${NOSTALGIA_BUILD_STUDIO})
|
||||
add_subdirectory(studio)
|
||||
endif()
|
||||
endif()
|
52
src/nostalgia/modules/CMakeLists.txt
Normal file
52
src/nostalgia/modules/CMakeLists.txt
Normal file
@ -0,0 +1,52 @@
|
||||
# module dir list
|
||||
|
||||
add_subdirectory(core)
|
||||
add_subdirectory(scene)
|
||||
|
||||
# module libraries
|
||||
|
||||
# Keel
|
||||
add_library(
|
||||
NostalgiaKeelModules STATIC
|
||||
keelmodules.cpp
|
||||
)
|
||||
target_link_libraries(
|
||||
NostalgiaKeelModules PUBLIC
|
||||
Keel
|
||||
NostalgiaCore-Keel
|
||||
NostalgiaScene-Keel
|
||||
)
|
||||
install(
|
||||
FILES
|
||||
keelmodules.hpp
|
||||
DESTINATION
|
||||
include/nostalgia/modules
|
||||
)
|
||||
|
||||
# Studio
|
||||
if(NOSTALGIA_BUILD_STUDIO)
|
||||
add_library(
|
||||
NostalgiaStudioModules STATIC
|
||||
studiomodules.cpp
|
||||
)
|
||||
target_link_libraries(
|
||||
NostalgiaStudioModules PUBLIC
|
||||
StudioAppLib
|
||||
NostalgiaCore-Studio-ImGui
|
||||
NostalgiaScene-Studio
|
||||
)
|
||||
install(
|
||||
FILES
|
||||
studiomodules.hpp
|
||||
DESTINATION
|
||||
include/nostalgia/modules
|
||||
)
|
||||
endif()
|
||||
|
||||
add_library(NostalgiaProfile INTERFACE)
|
||||
target_compile_definitions(
|
||||
NostalgiaProfile INTERFACE
|
||||
OLYMPIC_PROJECT_NAME="Nostalgia"
|
||||
OLYMPIC_PROJECT_NAMESPACE=nostalgia
|
||||
OLYMPIC_PROJECT_DATADIR=".nostalgia"
|
||||
)
|
12
src/nostalgia/modules/core/CMakeLists.txt
Normal file
12
src/nostalgia/modules/core/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
||||
add_subdirectory(src)
|
||||
|
||||
if(NOT BUILDCORE_TARGET STREQUAL "gba")
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
install(
|
||||
DIRECTORY
|
||||
include/nostalgia
|
||||
DESTINATION
|
||||
include
|
||||
)
|
154
src/nostalgia/modules/core/include/nostalgia/core/color.hpp
Normal file
154
src/nostalgia/modules/core/include/nostalgia/core/color.hpp
Normal file
@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/types.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
using Color16 = uint16_t;
|
||||
|
||||
/**
|
||||
* Nostalgia Core logically uses 16 bit colors, but must translate that to 32
|
||||
* bit colors in some implementations.
|
||||
*/
|
||||
using Color32 = uint32_t;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr Color32 toColor32(Color16 nc) noexcept {
|
||||
const auto r = static_cast<Color32>(((nc & 0b0000000000011111) >> 0) * 8);
|
||||
const auto g = static_cast<Color32>(((nc & 0b0000001111100000) >> 5) * 8);
|
||||
const auto b = static_cast<Color32>(((nc & 0b0111110000000000) >> 10) * 8);
|
||||
const auto a = static_cast<Color32>(255);
|
||||
return r | (g << 8) | (b << 16) | (a << 24);
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr uint8_t red16(Color16 c) noexcept {
|
||||
return c & 0b0000000000011111;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr uint8_t green16(Color16 c) noexcept {
|
||||
return (c & 0b0000001111100000) >> 5;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr uint8_t blue16(Color16 c) noexcept {
|
||||
return (c & 0b0111110000000000) >> 10;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr uint8_t alpha16(Color16 c) noexcept {
|
||||
return static_cast<uint8_t>(c >> 15);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr uint8_t red32(Color16 c) noexcept {
|
||||
return red16(c) * 8;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr uint8_t green32(Color16 c) noexcept {
|
||||
return green16(c) * 8;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr uint8_t blue32(Color16 c) noexcept {
|
||||
return blue16(c) * 8;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr uint8_t alpha32(Color16 c) noexcept {
|
||||
return static_cast<uint8_t>((c >> 15) * 255);
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr uint8_t red32(Color32 c) noexcept {
|
||||
return (c & 0x000000ff) >> 0;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr uint8_t green32(Color32 c) noexcept {
|
||||
return (c & 0x0000ff00) >> 8;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr uint8_t blue32(Color32 c) noexcept {
|
||||
return (c & 0x00ff0000) >> 16;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr Color32 color32(uint8_t r, uint8_t g, uint8_t b) noexcept {
|
||||
return static_cast<Color32>(r | (g << 8) | (b << 16));
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr Color32 color32(Color16 c) noexcept {
|
||||
return color32(red32(c), green32(c), blue32(c));
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr Color32 color32(float r, float g, float b) noexcept {
|
||||
return static_cast<Color32>(static_cast<uint8_t>(r * 255) | (static_cast<uint8_t>(g * 255) << 8) | (static_cast<uint8_t>(b * 255) << 16));
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr float redf(Color16 c) noexcept {
|
||||
return static_cast<float>(red16(c)) / 31.f;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr float greenf(Color16 c) noexcept {
|
||||
return static_cast<float>(green16(c)) / 31.f;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr float bluef(Color16 c) noexcept {
|
||||
return static_cast<float>(blue16(c)) / 31.f;
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr float redf(Color32 c) noexcept {
|
||||
return static_cast<float>(red32(c)) / 255.f;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr float greenf(Color32 c) noexcept {
|
||||
return static_cast<float>(green32(c)) / 255.f;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr float bluef(Color32 c) noexcept {
|
||||
return static_cast<float>(blue32(c)) / 255.f;
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr Color16 color16(int r, int g, int b, int a = 0) noexcept {
|
||||
return static_cast<Color16>(ox::min<uint8_t>(static_cast<uint8_t>(r), 31))
|
||||
| static_cast<Color16>(ox::min<uint8_t>(static_cast<uint8_t>(g), 31) << 5)
|
||||
| static_cast<Color16>(ox::min<uint8_t>(static_cast<uint8_t>(b), 31) << 10)
|
||||
| static_cast<Color16>(a << 15);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr Color16 color16(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 0) noexcept {
|
||||
return static_cast<Color16>(ox::min<uint8_t>(r, 31))
|
||||
| static_cast<Color16>(ox::min<uint8_t>(g, 31) << 5)
|
||||
| static_cast<Color16>(ox::min<uint8_t>(b, 31) << 10)
|
||||
| static_cast<Color16>(a << 15);
|
||||
}
|
||||
|
||||
static_assert(color16(0, 31, 0) == 992);
|
||||
static_assert(color16(16, 31, 0) == 1008);
|
||||
static_assert(color16(16, 31, 8) == 9200);
|
||||
static_assert(color16(16, 32, 8) == 9200);
|
||||
|
||||
}
|
18
src/nostalgia/modules/core/include/nostalgia/core/consts.hpp
Normal file
18
src/nostalgia/modules/core/include/nostalgia/core/consts.hpp
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/stringliteral.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
constexpr auto TileWidth = 8;
|
||||
constexpr auto TileHeight = 8;
|
||||
constexpr auto PixelsPerTile = TileWidth * TileHeight;
|
||||
|
||||
constexpr ox::StringLiteral FileExt_ng("ng");
|
||||
constexpr ox::StringLiteral FileExt_npal("npal");
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/model/desctypes.hpp>
|
||||
#include <ox/std/buffer.hpp>
|
||||
#include <ox/std/size.hpp>
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
#include "initparams.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class Context;
|
||||
|
||||
struct ContextDeleter {
|
||||
void operator()(Context *p) noexcept;
|
||||
};
|
||||
|
||||
using ContextUPtr = ox::UPtr<Context, ContextDeleter>;
|
||||
|
||||
ox::Result<ContextUPtr> init(turbine::Context &tctx, InitParams const¶ms = {}) noexcept;
|
||||
|
||||
}
|
||||
|
14
src/nostalgia/modules/core/include/nostalgia/core/core.hpp
Normal file
14
src/nostalgia/modules/core/include/nostalgia/core/core.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "color.hpp"
|
||||
#include "context.hpp"
|
||||
#include "gfx.hpp"
|
||||
#include "initparams.hpp"
|
||||
#include "keelmodule.hpp"
|
||||
#include "palette.hpp"
|
||||
#include "ptidxconv.hpp"
|
||||
#include "tilesheet.hpp"
|
159
src/nostalgia/modules/core/include/nostalgia/core/gfx.hpp
Normal file
159
src/nostalgia/modules/core/include/nostalgia/core/gfx.hpp
Normal file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/array.hpp>
|
||||
#include <ox/std/size.hpp>
|
||||
#include <ox/std/types.hpp>
|
||||
#include <ox/model/def.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
struct Sprite {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Sprite";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
bool enabled = false;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
unsigned tileIdx = 0;
|
||||
unsigned spriteShape = 0;
|
||||
unsigned spriteSize = 0;
|
||||
unsigned flipX = 0;
|
||||
unsigned bpp = 0;
|
||||
/**
|
||||
* Valid priorities: 0-3
|
||||
*/
|
||||
unsigned priority = 0;
|
||||
};
|
||||
|
||||
oxModelBegin(Sprite)
|
||||
oxModelField(idx)
|
||||
oxModelField(x)
|
||||
oxModelField(y)
|
||||
oxModelField(enabled)
|
||||
oxModelField(tileIdx)
|
||||
oxModelField(spriteShape)
|
||||
oxModelField(spriteSize)
|
||||
oxModelField(flipX)
|
||||
oxModelField(bpp)
|
||||
oxModelField(priority)
|
||||
oxModelEnd()
|
||||
|
||||
struct TileSheetSetEntrySection {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheetSetEntrySection";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
int32_t begin = 0;
|
||||
int32_t tiles = 0;
|
||||
[[nodiscard]]
|
||||
constexpr auto end() const noexcept {
|
||||
return begin + tiles - 1;
|
||||
}
|
||||
};
|
||||
|
||||
oxModelBegin(TileSheetSetEntrySection)
|
||||
oxModelField(begin)
|
||||
oxModelField(size)
|
||||
oxModelEnd()
|
||||
|
||||
struct TileSheetSetEntry {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheetSetEntry";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
ox::FileAddress tilesheet;
|
||||
ox::Vector<TileSheetSetEntrySection> sections;
|
||||
};
|
||||
|
||||
oxModelBegin(TileSheetSetEntry)
|
||||
oxModelField(tilesheet)
|
||||
oxModelField(sections)
|
||||
oxModelEnd()
|
||||
|
||||
struct TileSheetSet {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheetSet";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
static constexpr auto Preloadable = true;
|
||||
int bpp = 0;
|
||||
ox::Vector<TileSheetSetEntry> entries;
|
||||
};
|
||||
|
||||
oxModelBegin(TileSheetSet)
|
||||
oxModelField(bpp)
|
||||
oxModelField(entries)
|
||||
oxModelEnd()
|
||||
|
||||
ox::Error loadBgPalette(
|
||||
Context &ctx,
|
||||
ox::FileAddress const&paletteAddr) noexcept;
|
||||
|
||||
ox::Error loadSpritePalette(
|
||||
Context &ctx,
|
||||
ox::FileAddress const&paletteAddr) noexcept;
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned cbb,
|
||||
TileSheetSet const&set) noexcept;
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned cbb,
|
||||
ox::FileAddress const&tilesheetAddr,
|
||||
bool loadDefaultPalette = false) noexcept;
|
||||
|
||||
ox::Error loadSpriteTileSheet(
|
||||
Context &ctx,
|
||||
ox::FileAddress const&tilesheetAddr,
|
||||
bool loadDefaultPalette = false) noexcept;
|
||||
|
||||
ox::Error loadSpriteTileSheet(
|
||||
Context &ctx,
|
||||
TileSheetSet const&set) noexcept;
|
||||
|
||||
void setBgTile(Context &ctx, uint_t bgIdx, int column, int row, uint8_t tile) noexcept;
|
||||
|
||||
void clearBg(Context &ctx, uint_t bgIdx) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
uint8_t bgStatus(Context &ctx) noexcept;
|
||||
|
||||
void setBgStatus(Context &ctx, uint32_t status) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
bool bgStatus(Context &ctx, unsigned bg) noexcept;
|
||||
|
||||
void setBgStatus(Context &ctx, unsigned bg, bool status) noexcept;
|
||||
|
||||
void setBgCbb(Context &ctx, unsigned bgIdx, unsigned cbb) noexcept;
|
||||
|
||||
void setBgPriority(Context &ctx, uint_t bgIdx, uint_t priority) noexcept;
|
||||
|
||||
void hideSprite(Context &ctx, unsigned) noexcept;
|
||||
|
||||
void showSprite(Context &ctx, unsigned) noexcept;
|
||||
|
||||
void setSprite(Context &c, uint_t idx, Sprite const&s) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
uint_t spriteCount(Context &ctx) noexcept;
|
||||
|
||||
ox::Error initConsole(Context &ctx) noexcept;
|
||||
|
||||
void puts(Context &ctx, int column, int row, ox::CRStringView str) noexcept;
|
||||
|
||||
}
|
||||
|
||||
namespace nostalgia::core::gl {
|
||||
|
||||
constexpr ox::CStringView GlslVersion = "#version 330";
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Size drawSize(int scale = 5) noexcept;
|
||||
|
||||
void draw(core::Context &ctx, ox::Size const&renderSz) noexcept;
|
||||
|
||||
void draw(core::Context&, int scale = 5) noexcept;
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
struct InitParams {
|
||||
bool glInstallDrawer = true;
|
||||
uint_t glSpriteCount = 128;
|
||||
uint_t glBlocksPerSprite = 64;
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <keel/module.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
const keel::Module *keelModule() noexcept;
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/array.hpp>
|
||||
#include <ox/std/point.hpp>
|
||||
#include <ox/std/size.hpp>
|
||||
#include <ox/std/types.hpp>
|
||||
#include <ox/std/vector.hpp>
|
||||
#include <ox/model/def.hpp>
|
||||
|
||||
|
||||
#include "color.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
oxModelBegin(NostalgiaPalette)
|
||||
oxModelField(colors)
|
||||
oxModelEnd()
|
||||
|
||||
oxModelBegin(Palette)
|
||||
oxModelField(colors)
|
||||
oxModelEnd()
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/point.hpp>
|
||||
|
||||
#include "consts.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr std::size_t ptToIdx(int x, int y, int c, int scale = 1) noexcept {
|
||||
const auto tileWidth = TileWidth * scale;
|
||||
const auto tileHeight = TileHeight * scale;
|
||||
const auto pixelsPerTile = tileWidth * tileHeight;
|
||||
const auto colLength = static_cast<std::size_t>(pixelsPerTile);
|
||||
const auto rowLength = static_cast<std::size_t>(static_cast<std::size_t>(c / tileWidth) * colLength);
|
||||
const auto colStart = static_cast<std::size_t>(colLength * static_cast<std::size_t>(x / tileWidth));
|
||||
const auto rowStart = static_cast<std::size_t>(rowLength * static_cast<std::size_t>(y / tileHeight));
|
||||
const auto colOffset = static_cast<std::size_t>(x % tileWidth);
|
||||
const auto rowOffset = static_cast<std::size_t>((y % tileHeight) * tileHeight);
|
||||
return static_cast<std::size_t>(colStart + colOffset + rowStart + rowOffset);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr std::size_t ptToIdx(const ox::Point &pt, int c, int scale = 1) noexcept {
|
||||
return ptToIdx(pt.x, pt.y, c * TileWidth, scale);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr ox::Point idxToPt(int i, int c, int scale = 1) noexcept {
|
||||
const auto tileWidth = TileWidth * scale;
|
||||
const auto tileHeight = TileHeight * scale;
|
||||
const auto pixelsPerTile = tileWidth * tileHeight;
|
||||
// prevent divide by zeros
|
||||
if (!c) {
|
||||
++c;
|
||||
}
|
||||
const auto t = i / pixelsPerTile; // tile number
|
||||
const auto iti = i % pixelsPerTile; // in tile index
|
||||
const auto tc = t % c; // tile column
|
||||
const auto tr = t / c; // tile row
|
||||
const auto itx = iti % tileWidth; // in tile x
|
||||
const auto ity = iti / tileHeight; // in tile y
|
||||
return {
|
||||
itx + tc * tileWidth,
|
||||
ity + tr * tileHeight,
|
||||
};
|
||||
}
|
||||
|
||||
static_assert(idxToPt(4, 1) == ox::Point{4, 0});
|
||||
static_assert(idxToPt(8, 1) == ox::Point{0, 1});
|
||||
static_assert(idxToPt(8, 2) == ox::Point{0, 1});
|
||||
static_assert(idxToPt(64, 2) == ox::Point{8, 0});
|
||||
static_assert(idxToPt(128, 2) == ox::Point{0, 8});
|
||||
static_assert(idxToPt(129, 2) == ox::Point{1, 8});
|
||||
static_assert(idxToPt(192, 2) == ox::Point{8, 8});
|
||||
static_assert(idxToPt(384, 8) == ox::Point{48, 0});
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
const studio::Module *studioModule() noexcept;
|
||||
|
||||
}
|
336
src/nostalgia/modules/core/include/nostalgia/core/tilesheet.hpp
Normal file
336
src/nostalgia/modules/core/include/nostalgia/core/tilesheet.hpp
Normal file
@ -0,0 +1,336 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/std/array.hpp>
|
||||
#include <ox/std/point.hpp>
|
||||
#include <ox/std/size.hpp>
|
||||
#include <ox/std/span.hpp>
|
||||
#include <ox/std/types.hpp>
|
||||
#include <ox/model/def.hpp>
|
||||
|
||||
#include <nostalgia/core/ptidxconv.hpp>
|
||||
|
||||
#include "palette.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
// Predecessor to TileSheet, kept for backward compatibility
|
||||
struct TileSheetV1 {
|
||||
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 TileSheetV2 {
|
||||
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(ox::CRStringView pName, int pColumns, int pRows, int bpp) noexcept:
|
||||
name(pName),
|
||||
columns(pColumns),
|
||||
rows(pRows),
|
||||
pixels(static_cast<size_t>(columns * rows * PixelsPerTile) / (bpp == 4 ? 2u : 1u)) {
|
||||
}
|
||||
};
|
||||
|
||||
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};
|
||||
|
||||
};
|
||||
|
||||
using SubSheetId = int32_t;
|
||||
|
||||
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 = 3;
|
||||
SubSheetId id = 0;
|
||||
ox::String name;
|
||||
int columns = 0;
|
||||
int rows = 0;
|
||||
ox::Vector<SubSheet> subsheets;
|
||||
ox::Vector<uint8_t> pixels;
|
||||
|
||||
constexpr SubSheet() noexcept = default;
|
||||
constexpr SubSheet(SubSheet const&other) noexcept = default;
|
||||
SubSheet(SubSheet &&other) noexcept;
|
||||
SubSheet(
|
||||
SubSheetId pId,
|
||||
ox::CRStringView pName,
|
||||
int pColumns,
|
||||
int pRows,
|
||||
int bpp) noexcept;
|
||||
SubSheet(
|
||||
SubSheetId pId,
|
||||
ox::CRStringView pName,
|
||||
int pColumns,
|
||||
int pRows,
|
||||
ox::Vector<uint8_t> pPixels) noexcept;
|
||||
|
||||
constexpr SubSheet &operator=(const SubSheet &other) noexcept = default;
|
||||
|
||||
SubSheet &operator=(SubSheet &&other) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
std::size_t idx(ox::Point const&pt) const noexcept;
|
||||
|
||||
/**
|
||||
* Reads all pixels of this sheet or its children into the given pixel list
|
||||
* @param pixels
|
||||
*/
|
||||
void readPixelsTo(ox::Vector<uint8_t> *pPixels, int8_t pBpp) const noexcept;
|
||||
|
||||
/**
|
||||
* Reads all pixels of this sheet or its children into the given pixel list
|
||||
* @param pixels
|
||||
*/
|
||||
void readPixelsTo(ox::Vector<uint8_t> *pPixels) const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr std::size_t size() const noexcept {
|
||||
return static_cast<std::size_t>(columns) * static_cast<std::size_t>(rows);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::size_t unusedPixels() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
uint8_t getPixel4Bpp(std::size_t idx) const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
uint8_t getPixel8Bpp(std::size_t idx) const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
uint8_t getPixel(int8_t pBpp, std::size_t idx) const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
uint8_t getPixel4Bpp(const ox::Point &pt) const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
uint8_t getPixel8Bpp(const ox::Point &pt) const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
uint8_t getPixel(int8_t pBpp, const ox::Point &pt) const noexcept;
|
||||
|
||||
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 = static_cast<uint8_t>(pixels[i] & 0xF);
|
||||
const auto colorIdx2 = static_cast<uint8_t>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setPixel(int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept;
|
||||
|
||||
void setPixel(int8_t pBpp, ox::Point const&pt, uint8_t palIdx) noexcept;
|
||||
|
||||
ox::Error setPixelCount(int8_t pBpp, std::size_t cnt) noexcept;
|
||||
|
||||
/**
|
||||
* 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]]
|
||||
unsigned pixelCnt(int8_t pBpp) const noexcept;
|
||||
|
||||
/**
|
||||
* Gets the offset in tiles of the desired subsheet.
|
||||
*/
|
||||
ox::Result<unsigned> getTileOffset(
|
||||
ox::SpanView<ox::StringView> const&pNamePath,
|
||||
int8_t pBpp,
|
||||
std::size_t pIt = 0,
|
||||
unsigned pCurrentTotal = 0) const noexcept;
|
||||
|
||||
ox::Result<SubSheetId> getIdFor(
|
||||
ox::SpanView<ox::StringView> const&pNamePath,
|
||||
std::size_t pIt = 0) const noexcept;
|
||||
|
||||
ox::Result<ox::StringView> getNameFor(SubSheetId pId) const noexcept;
|
||||
|
||||
};
|
||||
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheet";
|
||||
static constexpr auto TypeVersion = 3;
|
||||
int8_t bpp = 4;
|
||||
SubSheetId idIt = 0;
|
||||
ox::FileAddress defaultPalette;
|
||||
SubSheet subsheet{0, "Root", 1, 1, bpp};
|
||||
|
||||
constexpr TileSheet() noexcept = default;
|
||||
TileSheet(TileSheet const&other) noexcept = default;
|
||||
inline TileSheet(TileSheet &&other) noexcept:
|
||||
bpp(other.bpp),
|
||||
idIt(other.idIt),
|
||||
defaultPalette(std::move(other.defaultPalette)),
|
||||
subsheet(std::move(other.subsheet)) {
|
||||
}
|
||||
|
||||
TileSheet &operator=(TileSheet const&other) noexcept;
|
||||
|
||||
TileSheet &operator=(TileSheet &&other) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
SubSheetIdx validateSubSheetIdx(
|
||||
SubSheetIdx const&pIdx,
|
||||
std::size_t pIdxIt,
|
||||
const SubSheet *pSubsheet) noexcept;
|
||||
|
||||
/**
|
||||
* 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]]
|
||||
SubSheetIdx validateSubSheetIdx(const SubSheetIdx &idx) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
static SubSheet const&getSubSheet(
|
||||
SubSheetIdx const&idx,
|
||||
std::size_t idxIt,
|
||||
const SubSheet *pSubsheet) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
static SubSheet &getSubSheet(
|
||||
SubSheetIdx const&idx,
|
||||
std::size_t idxIt,
|
||||
SubSheet *pSubsheet) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
const SubSheet &getSubSheet(SubSheetIdx const&idx) const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
SubSheet &getSubSheet(SubSheetIdx const&idx) noexcept;
|
||||
|
||||
ox::Error addSubSheet(SubSheetIdx const&idx) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
static ox::Error rmSubSheet(
|
||||
SubSheetIdx const&idx,
|
||||
std::size_t idxIt,
|
||||
SubSheet *pSubsheet) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Error rmSubSheet(SubSheetIdx const&idx) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
uint8_t getPixel4Bpp(
|
||||
ox::Point const&pt,
|
||||
SubSheetIdx const&subsheetIdx) const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
uint8_t getPixel8Bpp(
|
||||
ox::Point const&pt,
|
||||
SubSheetIdx const&subsheetIdx) const noexcept;
|
||||
|
||||
ox::Result<SubSheetId> getIdFor(ox::CRStringView path) const noexcept;
|
||||
|
||||
ox::Result<unsigned> getTileOffset(ox::CRStringView pNamePath) const noexcept;
|
||||
|
||||
ox::Result<ox::StringView> getNameFor(SubSheetId pId) const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Vector<uint8_t> pixels() const noexcept;
|
||||
|
||||
};
|
||||
|
||||
using TileSheetV3 = TileSheet;
|
||||
|
||||
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(TileSheetV1)
|
||||
oxModelField(bpp)
|
||||
oxModelField(rows)
|
||||
oxModelField(columns)
|
||||
oxModelField(defaultPalette)
|
||||
oxModelField(pal)
|
||||
oxModelField(pixels)
|
||||
oxModelEnd()
|
||||
|
||||
oxModelBegin(TileSheetV2::SubSheet)
|
||||
oxModelField(name);
|
||||
oxModelField(rows);
|
||||
oxModelField(columns);
|
||||
oxModelField(subsheets)
|
||||
oxModelField(pixels)
|
||||
oxModelEnd()
|
||||
|
||||
oxModelBegin(TileSheetV2)
|
||||
oxModelField(bpp)
|
||||
oxModelField(defaultPalette)
|
||||
oxModelField(subsheet)
|
||||
oxModelEnd()
|
||||
|
||||
oxModelBegin(TileSheetV3::SubSheet)
|
||||
oxModelField(name);
|
||||
oxModelField(rows);
|
||||
oxModelField(columns);
|
||||
oxModelField(subsheets)
|
||||
oxModelField(pixels)
|
||||
oxModelEnd()
|
||||
|
||||
oxModelBegin(TileSheetV3)
|
||||
oxModelField(bpp)
|
||||
oxModelField(idIt)
|
||||
oxModelField(defaultPalette)
|
||||
oxModelField(subsheet)
|
||||
oxModelEnd()
|
||||
|
||||
oxModelBegin(CompactTileSheet)
|
||||
oxModelField(bpp)
|
||||
oxModelField(defaultPalette)
|
||||
oxModelField(pixels)
|
||||
oxModelEnd()
|
||||
|
||||
ox::Vector<uint32_t> resizeTileSheetData(
|
||||
ox::Vector<uint32_t> const&srcPixels,
|
||||
ox::Size const&srcSize,
|
||||
int scale = 2) noexcept;
|
||||
|
||||
}
|
33
src/nostalgia/modules/core/src/CMakeLists.txt
Normal file
33
src/nostalgia/modules/core/src/CMakeLists.txt
Normal file
@ -0,0 +1,33 @@
|
||||
add_library(
|
||||
NostalgiaCore
|
||||
gfx.cpp
|
||||
tilesheet.cpp
|
||||
)
|
||||
|
||||
add_subdirectory(gba)
|
||||
if(NOT BUILDCORE_TARGET STREQUAL "gba")
|
||||
add_subdirectory(opengl)
|
||||
endif()
|
||||
|
||||
target_include_directories(
|
||||
NostalgiaCore PUBLIC
|
||||
../include
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
NostalgiaCore PUBLIC
|
||||
Turbine
|
||||
)
|
||||
|
||||
add_subdirectory(keel)
|
||||
if(NOSTALGIA_BUILD_STUDIO)
|
||||
add_subdirectory(studio)
|
||||
endif()
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
NostalgiaCore
|
||||
DESTINATION
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
)
|
21
src/nostalgia/modules/core/src/gba/CMakeLists.txt
Normal file
21
src/nostalgia/modules/core/src/gba/CMakeLists.txt
Normal file
@ -0,0 +1,21 @@
|
||||
add_library(
|
||||
NostalgiaCore-GBA OBJECT
|
||||
context.cpp
|
||||
gfx.cpp
|
||||
panic.cpp
|
||||
)
|
||||
target_include_directories(
|
||||
NostalgiaCore-GBA PUBLIC
|
||||
../../include
|
||||
)
|
||||
target_link_libraries(
|
||||
NostalgiaCore-GBA PUBLIC
|
||||
TeaGBA
|
||||
Keel
|
||||
Turbine
|
||||
)
|
||||
|
||||
if(BUILDCORE_TARGET STREQUAL "gba")
|
||||
set_source_files_properties(gfx.cpp PROPERTIES COMPILE_FLAGS -marm)
|
||||
target_link_libraries(NostalgiaCore PUBLIC NostalgiaCore-GBA)
|
||||
endif()
|
26
src/nostalgia/modules/core/src/gba/context.cpp
Normal file
26
src/nostalgia/modules/core/src/gba/context.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
void ContextDeleter::operator()(Context *p) noexcept {
|
||||
ox::safeDelete(p);
|
||||
}
|
||||
|
||||
Context::Context(turbine::Context &tctx) noexcept: turbineCtx(tctx) {
|
||||
}
|
||||
|
||||
ox::Error initGfx(Context &ctx, InitParams const&) noexcept;
|
||||
|
||||
ox::Result<ContextUPtr> init(turbine::Context &tctx, InitParams const¶ms) noexcept {
|
||||
auto ctx = ox::make_unique<Context>(tctx);
|
||||
oxReturnError(initGfx(*ctx, params));
|
||||
return ContextUPtr(std::move(ctx));
|
||||
}
|
||||
|
||||
}
|
34
src/nostalgia/modules/core/src/gba/context.hpp
Normal file
34
src/nostalgia/modules/core/src/gba/context.hpp
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nostalgia/core/context.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
struct BgCbbData {
|
||||
unsigned bpp = 4;
|
||||
};
|
||||
|
||||
class Context {
|
||||
|
||||
public:
|
||||
turbine::Context &turbineCtx;
|
||||
ox::Array<BgCbbData, 4> cbbData;
|
||||
|
||||
explicit Context(turbine::Context &tctx) noexcept;
|
||||
Context(Context &other) noexcept = delete;
|
||||
Context(Context const&other) noexcept = delete;
|
||||
Context(Context const&&other) noexcept = delete;
|
||||
virtual ~Context() noexcept = default;
|
||||
|
||||
[[nodiscard]]
|
||||
ox::MemFS const&rom() const noexcept {
|
||||
return static_cast<ox::MemFS const&>(*turbine::rom(turbineCtx));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
367
src/nostalgia/modules/core/src/gba/gfx.cpp
Normal file
367
src/nostalgia/modules/core/src/gba/gfx.cpp
Normal file
@ -0,0 +1,367 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/mc/mc.hpp>
|
||||
#include <ox/std/array.hpp>
|
||||
|
||||
#include <teagba/addresses.hpp>
|
||||
#include <teagba/gfx.hpp>
|
||||
#include <teagba/registers.hpp>
|
||||
|
||||
#include <turbine/turbine.hpp>
|
||||
|
||||
#include <nostalgia/core/color.hpp>
|
||||
#include <nostalgia/core/context.hpp>
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
#include <nostalgia/core/tilesheet.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
constexpr auto GbaTileColumns = 32;
|
||||
constexpr auto GbaTileRows = 32;
|
||||
constexpr auto SpriteCount = 128;
|
||||
|
||||
struct GbaPaletteTarget {
|
||||
static constexpr auto TypeName = Palette::TypeName;
|
||||
static constexpr auto TypeVersion = Palette::TypeVersion;
|
||||
volatile uint16_t *palette = nullptr;
|
||||
};
|
||||
|
||||
struct GbaTileMapTarget {
|
||||
static constexpr auto TypeName = CompactTileSheet::TypeName;
|
||||
static constexpr auto TypeVersion = CompactTileSheet::TypeVersion;
|
||||
unsigned &bpp;
|
||||
ox::FileAddress defaultPalette;
|
||||
volatile uint16_t *tileMap = nullptr;
|
||||
// the following values are not actually in CompactTileSheet,
|
||||
// and only exist to communicate with the loading process
|
||||
size_t tileWriteIdx = 0;
|
||||
unsigned targetBpp = 0;
|
||||
TileSheetSetEntry const*setEntry = nullptr;
|
||||
};
|
||||
|
||||
constexpr ox::Error model(auto *io, ox::CommonPtrWith<GbaPaletteTarget> auto *t) noexcept {
|
||||
oxReturnError(io->template setTypeInfo<Palette>());
|
||||
if (t->palette) {
|
||||
const auto colorHandler = [t](std::size_t i, const Color16 *c) {
|
||||
t->palette[i] = *c;
|
||||
return ox::Error{};
|
||||
};
|
||||
return io->template field<Color16, decltype(colorHandler)>("colors", colorHandler);
|
||||
} else {
|
||||
constexpr auto colorHandler = [](std::size_t, const Color16*) {
|
||||
return ox::Error{};
|
||||
};
|
||||
return io->template field<Color16, decltype(colorHandler)>("colors", colorHandler);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static bool loadPixel(TileSheetSetEntry const&setEntry, size_t §ionIdx, int tileIdx) noexcept {
|
||||
if (setEntry.sections.size() <= sectionIdx) {
|
||||
return false;
|
||||
}
|
||||
auto §ion = setEntry.sections[sectionIdx];
|
||||
if (tileIdx < section.begin) {
|
||||
return false;
|
||||
}
|
||||
if (tileIdx > section.end()) {
|
||||
if (sectionIdx >= setEntry.sections.size()) {
|
||||
return false;
|
||||
}
|
||||
++sectionIdx;
|
||||
return tileIdx > section.begin && tileIdx <= section.end();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr ox::Error model(auto *io, ox::CommonPtrWith<GbaTileMapTarget> auto *t) noexcept {
|
||||
oxReturnError(io->template setTypeInfo<CompactTileSheet>());
|
||||
oxReturnError(io->field("bpp", &t->bpp));
|
||||
oxReturnError(io->field("defaultPalette", &t->defaultPalette));
|
||||
if (t->targetBpp == 0) {
|
||||
t->targetBpp = t->bpp;
|
||||
}
|
||||
if (t->targetBpp != t->bpp && t->bpp == 8) {
|
||||
return OxError(1, "Cannot load an 8 BPP tilesheet into a 4 BPP CBB");
|
||||
}
|
||||
ox::Error out{};
|
||||
if (t->setEntry) {
|
||||
// The following code is atrocious, but it works.
|
||||
// It might be possible to clean it up a little, but it probably
|
||||
// cannot be seriously optimized without preloading TileSheets.
|
||||
size_t sectionIdx = 0;
|
||||
if (t->targetBpp == t->bpp) {
|
||||
uint16_t intermediate = 0;
|
||||
size_t const fourBpp = t->bpp == 4;
|
||||
const auto handleTileMap = [t, &intermediate, §ionIdx, fourBpp]
|
||||
(std::size_t i, uint8_t const*tile) {
|
||||
auto const tileIdx = static_cast<int>((i * (2 * fourBpp)) / PixelsPerTile);
|
||||
if (!loadPixel(*t->setEntry, sectionIdx, tileIdx)) {
|
||||
return ox::Error{};
|
||||
}
|
||||
if (i & 1) { // i is odd
|
||||
intermediate |= static_cast<uint16_t>(*tile) << 8;
|
||||
t->tileMap[t->tileWriteIdx] = intermediate;
|
||||
++t->tileWriteIdx;
|
||||
} else { // i is even
|
||||
intermediate = *tile & 0x00ff;
|
||||
}
|
||||
return ox::Error{};
|
||||
};
|
||||
out = io->template field<uint8_t, decltype(handleTileMap)>("tileMap", handleTileMap);
|
||||
} else if (t->targetBpp > t->bpp) { // 4 -> 8 bits
|
||||
const auto handleTileMap = [t, §ionIdx](std::size_t i, uint8_t const*tile) {
|
||||
auto constexpr BytesPerTile4Bpp = 32;
|
||||
auto const tileIdx = static_cast<int>(i / BytesPerTile4Bpp);
|
||||
if (!loadPixel(*t->setEntry, sectionIdx, tileIdx)) {
|
||||
return ox::Error{};
|
||||
}
|
||||
uint16_t const px1 = *tile & 0xf;
|
||||
uint16_t const px2 = *tile >> 4;
|
||||
t->tileMap[t->tileWriteIdx] = static_cast<uint16_t>(px1 | (px2 << 8));
|
||||
++t->tileWriteIdx;
|
||||
return ox::Error{};
|
||||
};
|
||||
out = io->template field<uint8_t, decltype(handleTileMap)>("tileMap", handleTileMap);
|
||||
}
|
||||
} else {
|
||||
uint16_t intermediate = 0;
|
||||
const auto handleTileMap = [t, &intermediate](std::size_t i, const uint8_t*tile) {
|
||||
if (i & 1) { // i is odd
|
||||
intermediate |= static_cast<uint16_t>(*tile) << 8;
|
||||
t->tileMap[i / 2] = intermediate;
|
||||
} else { // i is even
|
||||
intermediate = *tile & 0x00ff;
|
||||
}
|
||||
return ox::Error{};
|
||||
};
|
||||
out = io->template field<uint8_t, decltype(handleTileMap)>("tileMap", handleTileMap);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
ox::Error initGfx(Context&, InitParams const&) noexcept {
|
||||
for (auto bgCtl = ®_BG0CTL; bgCtl <= ®_BG3CTL; bgCtl += 2) {
|
||||
teagba::bgSetSbb(*bgCtl, 28);
|
||||
}
|
||||
for (uint16_t i = 0; i < SpriteCount; ++i) {
|
||||
auto &sa = teagba::spriteAttr(i);
|
||||
sa.idx = i;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error loadBgPalette(
|
||||
Context &ctx,
|
||||
ox::FileAddress const&paletteAddr) noexcept {
|
||||
auto &rom = ctx.rom();
|
||||
GbaPaletteTarget const palTarget{.palette = MEM_BG_PALETTE};
|
||||
oxRequire(palStat, rom.stat(paletteAddr));
|
||||
oxRequire(pal, rom.directAccess(paletteAddr));
|
||||
oxReturnError(ox::readMC(pal, static_cast<std::size_t>(palStat.size), &palTarget));
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error loadSpritePalette(
|
||||
Context &ctx,
|
||||
ox::FileAddress const&paletteAddr) noexcept {
|
||||
auto &rom = ctx.rom();
|
||||
GbaPaletteTarget const palTarget{.palette = MEM_SPRITE_PALETTE};
|
||||
oxRequire(palStat, rom.stat(paletteAddr));
|
||||
oxRequire(pal, rom.directAccess(paletteAddr));
|
||||
oxReturnError(ox::readMC(pal, static_cast<std::size_t>(palStat.size), &palTarget));
|
||||
return {};
|
||||
}
|
||||
|
||||
static ox::Error loadTileSheetSet(
|
||||
Context &ctx,
|
||||
uint16_t *tileMapTargetMem,
|
||||
TileSheetSet const&set) noexcept {
|
||||
auto &rom = ctx.rom();
|
||||
size_t tileWriteIdx = 0;
|
||||
for (auto const&entry : set.entries) {
|
||||
oxRequire(tsStat, rom.stat(entry.tilesheet));
|
||||
oxRequire(ts, rom.directAccess(entry.tilesheet));
|
||||
unsigned tilesheetBpp{};
|
||||
GbaTileMapTarget target{
|
||||
.bpp = tilesheetBpp,
|
||||
.defaultPalette = {},
|
||||
.tileMap = tileMapTargetMem + tileWriteIdx,
|
||||
.targetBpp = static_cast<unsigned>(set.bpp),
|
||||
.setEntry = &entry,
|
||||
};
|
||||
oxReturnError(ox::readMC(ts, static_cast<std::size_t>(tsStat.size), &target));
|
||||
tileWriteIdx += target.tileWriteIdx;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned cbb,
|
||||
ox::FileAddress const&tilesheetAddr,
|
||||
bool loadDefaultPalette) noexcept {
|
||||
auto &rom = ctx.rom();
|
||||
oxRequire(tsStat, rom.stat(tilesheetAddr));
|
||||
oxRequire(ts, rom.directAccess(tilesheetAddr));
|
||||
GbaTileMapTarget target{
|
||||
.bpp = ctx.cbbData[cbb].bpp,
|
||||
.defaultPalette = {},
|
||||
.tileMap = MEM_BG_TILES[cbb].data(),
|
||||
};
|
||||
oxReturnError(ox::readMC(ts, static_cast<std::size_t>(tsStat.size), &target));
|
||||
// update bpp of all bgs with the updated cbb
|
||||
const auto bpp = ctx.cbbData[cbb].bpp;
|
||||
teagba::iterateBgCtl([bpp, cbb](volatile BgCtl &bgCtl) {
|
||||
if (teagba::bgCbb(bgCtl) == cbb) {
|
||||
teagba::bgSetBpp(bgCtl, bpp);
|
||||
}
|
||||
});
|
||||
if (loadDefaultPalette && target.defaultPalette) {
|
||||
oxReturnError(loadBgPalette(ctx, target.defaultPalette));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned cbb,
|
||||
TileSheetSet const&set) noexcept {
|
||||
auto const bpp = static_cast<unsigned>(set.bpp);
|
||||
oxReturnError(loadTileSheetSet(ctx, MEM_BG_TILES[cbb].data(), set));
|
||||
// update bpp of all bgs with the updated cbb
|
||||
ctx.cbbData[cbb].bpp = bpp;
|
||||
teagba::iterateBgCtl([bpp, cbb](volatile BgCtl &bgCtl) {
|
||||
if (teagba::bgCbb(bgCtl) == cbb) {
|
||||
teagba::bgSetBpp(bgCtl, bpp);
|
||||
}
|
||||
});
|
||||
return {};
|
||||
}
|
||||
|
||||
static void setSpritesBpp(unsigned const bpp) noexcept {
|
||||
auto const eightBpp = static_cast<uint16_t >(bpp == 8);
|
||||
for (auto i = 0u; i < SpriteCount; ++i) {
|
||||
auto &sa = teagba::spriteAttr(i);
|
||||
auto &a = sa.attr0;
|
||||
a |= static_cast<uint16_t>((a & ~static_cast<uint16_t>(1u << 13)) | (eightBpp << 13));
|
||||
}
|
||||
}
|
||||
|
||||
ox::Error loadSpriteTileSheet(
|
||||
Context &ctx,
|
||||
ox::FileAddress const&tilesheetAddr,
|
||||
bool loadDefaultPalette) noexcept {
|
||||
auto &rom = ctx.rom();
|
||||
oxRequire(tsStat, rom.stat(tilesheetAddr));
|
||||
oxRequire(ts, rom.directAccess(tilesheetAddr));
|
||||
unsigned bpp{};
|
||||
GbaTileMapTarget target{
|
||||
.bpp = bpp,
|
||||
.defaultPalette = {},
|
||||
.tileMap = MEM_SPRITE_TILES,
|
||||
};
|
||||
oxReturnError(ox::readMC(ts, static_cast<std::size_t>(tsStat.size), &target));
|
||||
if (loadDefaultPalette && target.defaultPalette) {
|
||||
oxReturnError(loadSpritePalette(ctx, target.defaultPalette));
|
||||
}
|
||||
setSpritesBpp(bpp);
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error loadSpriteTileSheet(
|
||||
Context &ctx,
|
||||
TileSheetSet const&set) noexcept {
|
||||
auto const bpp = static_cast<unsigned>(set.bpp);
|
||||
oxReturnError(loadTileSheetSet(ctx, MEM_SPRITE_TILES, set));
|
||||
setSpritesBpp(bpp);
|
||||
return {};
|
||||
}
|
||||
|
||||
void setBgTile(Context&, uint_t bgIdx, int column, int row, uint8_t tile) noexcept {
|
||||
const auto tileIdx = static_cast<std::size_t>(row * GbaTileColumns + column);
|
||||
MEM_BG_MAP[bgIdx][tileIdx] = tile;
|
||||
}
|
||||
|
||||
void clearBg(Context&, uint_t bgIdx) noexcept {
|
||||
memset(MEM_BG_MAP[bgIdx].data(), 0, GbaTileRows * GbaTileColumns);
|
||||
}
|
||||
|
||||
uint8_t bgStatus(Context&) noexcept {
|
||||
return (REG_DISPCTL >> 8u) & 0b1111u;
|
||||
}
|
||||
|
||||
void setBgStatus(Context&, uint32_t status) noexcept {
|
||||
constexpr auto BgStatus = 8;
|
||||
REG_DISPCTL = (REG_DISPCTL & ~0b111100000000u) | status << BgStatus;
|
||||
}
|
||||
|
||||
bool bgStatus(Context&, unsigned bg) noexcept {
|
||||
return (REG_DISPCTL >> (8 + bg)) & 1;
|
||||
}
|
||||
|
||||
void setBgStatus(Context&, unsigned bg, bool status) noexcept {
|
||||
constexpr auto Bg0Status = 8;
|
||||
const auto mask = static_cast<uint32_t>(status) << (Bg0Status + bg);
|
||||
REG_DISPCTL = REG_DISPCTL | ((REG_DISPCTL & ~mask) | mask);
|
||||
}
|
||||
|
||||
void setBgCbb(Context &ctx, unsigned bgIdx, unsigned cbb) noexcept {
|
||||
auto &bgCtl = regBgCtl(bgIdx);
|
||||
const auto &cbbData = ctx.cbbData[cbb];
|
||||
teagba::bgSetBpp(bgCtl, cbbData.bpp);
|
||||
teagba::bgSetCbb(bgCtl, cbb);
|
||||
}
|
||||
|
||||
void setBgPriority(Context&, uint_t bgIdx, uint_t priority) noexcept {
|
||||
auto &bgCtl = regBgCtl(bgIdx);
|
||||
bgCtl = (bgCtl & 0b1111'1111'1111'1100u) | (priority & 0b11);
|
||||
}
|
||||
|
||||
void hideSprite(Context&, unsigned idx) noexcept {
|
||||
//oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow");
|
||||
teagba::addSpriteUpdate({
|
||||
.attr0 = uint16_t{0b11 << 8},
|
||||
.idx = static_cast<uint16_t>(idx),
|
||||
});
|
||||
}
|
||||
|
||||
void showSprite(Context&, unsigned idx) noexcept {
|
||||
//oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow");
|
||||
teagba::addSpriteUpdate({
|
||||
.attr0 = 0,
|
||||
.idx = static_cast<uint16_t>(idx),
|
||||
});
|
||||
}
|
||||
|
||||
void setSprite(Context&, uint_t idx, Sprite const&s) noexcept {
|
||||
//oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow");
|
||||
uint16_t const eightBpp = s.bpp == 8;
|
||||
teagba::addSpriteUpdate({
|
||||
.attr0 = static_cast<uint16_t>(
|
||||
(static_cast<uint16_t>(s.y & ox::onMask<uint8_t>(0b111'1111)))
|
||||
| (static_cast<uint16_t>(1) << 10) // enable alpha
|
||||
| (static_cast<uint16_t>(eightBpp) << 13)
|
||||
| (static_cast<uint16_t>(s.spriteShape) << 14)),
|
||||
.attr1 = static_cast<uint16_t>(
|
||||
(static_cast<uint16_t>(s.x) & ox::onMask<uint8_t>(8))
|
||||
| (static_cast<uint16_t>(s.flipX) << 12)
|
||||
| (static_cast<uint16_t>(s.spriteSize) << 14)),
|
||||
.attr2 = static_cast<uint16_t>(
|
||||
// double tileIdx if 8 bpp
|
||||
(static_cast<uint16_t>((s.tileIdx * (1 + eightBpp)) & ox::onMask<uint16_t>(8)))
|
||||
| (static_cast<uint16_t>(s.priority & 0b11) << 10)),
|
||||
.idx = static_cast<uint16_t>(idx),
|
||||
});
|
||||
}
|
||||
|
||||
uint_t spriteCount(Context&) noexcept {
|
||||
return SpriteCount;
|
||||
}
|
||||
|
||||
}
|
11
src/nostalgia/modules/core/src/gba/gfx.hpp
Normal file
11
src/nostalgia/modules/core/src/gba/gfx.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nostalgia/core/context.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
ox::Error initGfx(Context &ctx, InitParams const&) noexcept;
|
||||
}
|
58
src/nostalgia/modules/core/src/gba/panic.cpp
Normal file
58
src/nostalgia/modules/core/src/gba/panic.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#include <ox/std/def.hpp>
|
||||
|
||||
#include <keel/media.hpp>
|
||||
#include <turbine/turbine.hpp>
|
||||
|
||||
#include <teagba/addresses.hpp>
|
||||
#include <teagba/bios.hpp>
|
||||
|
||||
#include <nostalgia/core/core.hpp>
|
||||
|
||||
#include "gfx.hpp"
|
||||
|
||||
#define HEAP_BEGIN (reinterpret_cast<char*>(MEM_EWRAM_BEGIN))
|
||||
#define HEAP_SIZE ((MEM_EWRAM_END - MEM_EWRAM_BEGIN) / 2)
|
||||
#define HEAP_END (reinterpret_cast<char*>(MEM_EWRAM_BEGIN + HEAP_SIZE))
|
||||
|
||||
namespace ox {
|
||||
|
||||
using namespace nostalgia::core;
|
||||
|
||||
void panic(const char *file, int line, const char *panicMsg, ox::Error const&err) noexcept {
|
||||
// reset heap to make sure we have enough memory to allocate context data
|
||||
ox::heapmgr::initHeap(HEAP_BEGIN, HEAP_END);
|
||||
auto tctx = turbine::init(keel::loadRomFs("").unwrap(), "Nostalgia").unwrap();
|
||||
auto ctx = init(*tctx).unwrap();
|
||||
oxIgnoreError(initGfx(*ctx, {}));
|
||||
oxIgnoreError(initConsole(*ctx));
|
||||
setBgStatus(*ctx, 0, true);
|
||||
clearBg(*ctx, 0);
|
||||
ox::BString<23> serr = "Error code: ";
|
||||
serr += static_cast<int64_t>(err);
|
||||
puts(*ctx, 32 + 1, 1, "SADNESS...");
|
||||
puts(*ctx, 32 + 1, 4, "UNEXPECTED STATE:");
|
||||
puts(*ctx, 32 + 2, 6, panicMsg);
|
||||
if (err) {
|
||||
puts(*ctx, 32 + 2, 8, serr);
|
||||
}
|
||||
puts(*ctx, 32 + 1, 15, "PLEASE RESTART THE SYSTEM");
|
||||
// print to terminal if in mGBA
|
||||
oxErrf("\033[31;1;1mPANIC:\033[0m [{}:{}]: {}\n", file, line, panicMsg);
|
||||
if (err.msg) {
|
||||
oxErrf("\tError Message:\t{}\n", err.msg);
|
||||
}
|
||||
oxErrf("\tError Code:\t{}\n", static_cast<ErrorCode>(err));
|
||||
if (err.file != nullptr) {
|
||||
oxErrf("\tError Location:\t{}:{}\n", err.file, err.line);
|
||||
}
|
||||
// disable all interrupt handling and IntrWait on no interrupts
|
||||
REG_IE = 0;
|
||||
teagba::intrwait(0, 0);
|
||||
}
|
||||
|
||||
}
|
165
src/nostalgia/modules/core/src/gfx.cpp
Normal file
165
src/nostalgia/modules/core/src/gfx.cpp
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
// map ASCII values to the nostalgia charset
|
||||
constexpr ox::Array<char, 128> charMap = {
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0, // space
|
||||
38, // !
|
||||
0, // "
|
||||
49, // #
|
||||
0, // $
|
||||
0, // %
|
||||
0, // &
|
||||
0, // '
|
||||
42, // (
|
||||
43, // )
|
||||
0, // *
|
||||
0, // +
|
||||
37, // ,
|
||||
0, // -
|
||||
39, // .
|
||||
0, // /
|
||||
27, // 0
|
||||
28, // 1
|
||||
29, // 2
|
||||
30, // 3
|
||||
31, // 4
|
||||
32, // 5
|
||||
33, // 6
|
||||
34, // 7
|
||||
35, // 8
|
||||
36, // 9
|
||||
40, // :
|
||||
0, // ;
|
||||
0, // <
|
||||
41, // =
|
||||
0, // >
|
||||
0, // ?
|
||||
0, // @
|
||||
1, // A
|
||||
2, // B
|
||||
3, // C
|
||||
4, // D
|
||||
5, // E
|
||||
6, // F
|
||||
7, // G
|
||||
8, // H
|
||||
9, // I
|
||||
10, // J
|
||||
11, // K
|
||||
12, // L
|
||||
13, // M
|
||||
14, // N
|
||||
15, // O
|
||||
16, // P
|
||||
17, // Q
|
||||
18, // R
|
||||
19, // S
|
||||
20, // T
|
||||
21, // U
|
||||
22, // V
|
||||
23, // W
|
||||
24, // X
|
||||
25, // Y
|
||||
26, // Z
|
||||
44, // [
|
||||
0, // backslash
|
||||
45, // ]
|
||||
0, // ^
|
||||
0, // _
|
||||
0, // `
|
||||
1, // a
|
||||
2, // b
|
||||
3, // c
|
||||
4, // d
|
||||
5, // e
|
||||
6, // f
|
||||
7, // g
|
||||
8, // h
|
||||
9, // i
|
||||
10, // j
|
||||
11, // k
|
||||
12, // l
|
||||
13, // m
|
||||
14, // n
|
||||
15, // o
|
||||
16, // p
|
||||
17, // q
|
||||
18, // r
|
||||
19, // s
|
||||
20, // t
|
||||
21, // u
|
||||
22, // v
|
||||
23, // w
|
||||
24, // x
|
||||
25, // y
|
||||
26, // z
|
||||
46, // {
|
||||
51, // |
|
||||
48, // }
|
||||
50, // ~
|
||||
};
|
||||
|
||||
ox::Error initConsole(Context &ctx) noexcept {
|
||||
constexpr ox::FileAddress TilesheetAddr = ox::StringLiteral("/TileSheets/Charset.ng");
|
||||
constexpr ox::FileAddress PaletteAddr = ox::StringLiteral("/Palettes/Charset.npal");
|
||||
setBgStatus(ctx, 0b0001);
|
||||
setBgCbb(ctx, 0, 0);
|
||||
oxReturnError(loadBgTileSheet(ctx, 0, TilesheetAddr));
|
||||
return loadBgPalette(ctx, PaletteAddr);
|
||||
}
|
||||
|
||||
void puts(
|
||||
Context &ctx,
|
||||
int const column,
|
||||
int const row,
|
||||
ox::CRStringView str) noexcept {
|
||||
auto const col = static_cast<uint_t>(column);
|
||||
for (auto i = 0u; i < str.bytes(); ++i) {
|
||||
setBgTile(
|
||||
ctx,
|
||||
0,
|
||||
static_cast<int>(col + i),
|
||||
row,
|
||||
static_cast<uint8_t>(charMap[static_cast<uint8_t>(str[i])]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
19
src/nostalgia/modules/core/src/keel/CMakeLists.txt
Normal file
19
src/nostalgia/modules/core/src/keel/CMakeLists.txt
Normal file
@ -0,0 +1,19 @@
|
||||
add_library(
|
||||
NostalgiaCore-Keel
|
||||
keelmodule.cpp
|
||||
typeconv.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
NostalgiaCore-Keel PUBLIC
|
||||
Keel
|
||||
NostalgiaCore
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
NostalgiaCore-Keel
|
||||
DESTINATION
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
)
|
76
src/nostalgia/modules/core/src/keel/keelmodule.cpp
Normal file
76
src/nostalgia/modules/core/src/keel/keelmodule.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/model/model.hpp>
|
||||
|
||||
#include <keel/asset.hpp>
|
||||
#include <keel/module.hpp>
|
||||
|
||||
#include <nostalgia/core/palette.hpp>
|
||||
#include <nostalgia/core/tilesheet.hpp>
|
||||
|
||||
#include "typeconv.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class KeelModule: public keel::Module {
|
||||
private:
|
||||
NostalgiaPaletteToPaletteConverter m_nostalgiaPaletteToPaletteConverter;
|
||||
TileSheetV1ToTileSheetV2Converter m_tileSheetV1ToTileSheetV2Converter;
|
||||
TileSheetV2ToTileSheetV3Converter m_tileSheetV2ToTileSheetConverter;
|
||||
TileSheetToCompactTileSheetConverter m_tileSheetToCompactTileSheetConverter;
|
||||
|
||||
public:
|
||||
[[nodiscard]]
|
||||
ox::String id() const noexcept override {
|
||||
return ox::String("net.drinkingtea.nostalgia.core");
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Vector<keel::TypeDescGenerator> types() const noexcept final {
|
||||
return {
|
||||
keel::generateTypeDesc<TileSheetV1>,
|
||||
keel::generateTypeDesc<TileSheetV2>,
|
||||
keel::generateTypeDesc<TileSheet>,
|
||||
keel::generateTypeDesc<CompactTileSheet>,
|
||||
keel::generateTypeDesc<Palette>,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Vector<const keel::BaseConverter*> converters() const noexcept final {
|
||||
return {
|
||||
&m_nostalgiaPaletteToPaletteConverter,
|
||||
&m_tileSheetV1ToTileSheetV2Converter,
|
||||
&m_tileSheetV2ToTileSheetConverter,
|
||||
&m_tileSheetToCompactTileSheetConverter,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Vector<keel::PackTransform> packTransforms() const noexcept final {
|
||||
return {
|
||||
// convert tilesheets to CompactTileSheets
|
||||
[](keel::Context &ctx, ox::Buffer &buff) -> ox::Error {
|
||||
oxRequire(hdr, keel::readAssetHeader(buff));
|
||||
const auto typeId = ox::buildTypeId(
|
||||
hdr.clawHdr.typeName, hdr.clawHdr.typeVersion, hdr.clawHdr.typeParams);
|
||||
if (typeId == ox::buildTypeId<TileSheetV1>() ||
|
||||
typeId == ox::buildTypeId<TileSheetV2>() ||
|
||||
typeId == ox::buildTypeId<TileSheet>()) {
|
||||
oxReturnError(keel::convertBuffToBuff<core::CompactTileSheet>(
|
||||
ctx, buff, ox::ClawFormat::Metal).moveTo(buff));
|
||||
}
|
||||
return {};
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
static const KeelModule mod;
|
||||
const keel::Module *keelModule() noexcept {
|
||||
return &mod;
|
||||
}
|
||||
|
||||
}
|
66
src/nostalgia/modules/core/src/keel/typeconv.cpp
Normal file
66
src/nostalgia/modules/core/src/keel/typeconv.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "typeconv.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
ox::Error NostalgiaPaletteToPaletteConverter::convert(
|
||||
keel::Context&,
|
||||
NostalgiaPalette &src,
|
||||
Palette &dst) const noexcept {
|
||||
dst.colors = std::move(src.colors);
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error TileSheetV1ToTileSheetV2Converter::convert(
|
||||
keel::Context&,
|
||||
TileSheetV1 &src,
|
||||
TileSheetV2 &dst) const noexcept {
|
||||
dst.bpp = src.bpp;
|
||||
dst.defaultPalette = std::move(src.defaultPalette);
|
||||
dst.subsheet.name = "Root";
|
||||
dst.subsheet.rows = src.rows;
|
||||
dst.subsheet.columns = src.columns;
|
||||
dst.subsheet.pixels = std::move(src.pixels);
|
||||
return {};
|
||||
}
|
||||
|
||||
void TileSheetV2ToTileSheetV3Converter::convertSubsheet(
|
||||
TileSheetV2::SubSheet &src,
|
||||
TileSheetV3::SubSheet &dst,
|
||||
SubSheetId &idIt) noexcept {
|
||||
dst.id = idIt;
|
||||
dst.name = std::move(src.name);
|
||||
dst.columns = src.columns;
|
||||
dst.rows = src.rows;
|
||||
dst.pixels = std::move(src.pixels);
|
||||
++idIt;
|
||||
dst.subsheets.resize(src.subsheets.size());
|
||||
for (auto i = 0u; i < src.subsheets.size(); ++i) {
|
||||
convertSubsheet(src.subsheets[i], dst.subsheets[i], idIt);
|
||||
}
|
||||
}
|
||||
|
||||
ox::Error TileSheetV2ToTileSheetV3Converter::convert(
|
||||
keel::Context&,
|
||||
TileSheetV2 &src,
|
||||
TileSheetV3 &dst) const noexcept {
|
||||
dst.bpp = src.bpp;
|
||||
dst.defaultPalette = std::move(src.defaultPalette);
|
||||
convertSubsheet(src.subsheet, dst.subsheet, dst.idIt);
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error TileSheetToCompactTileSheetConverter::convert(
|
||||
keel::Context&,
|
||||
TileSheet &src,
|
||||
CompactTileSheet &dst) const noexcept {
|
||||
dst.bpp = src.bpp;
|
||||
dst.defaultPalette = std::move(src.defaultPalette);
|
||||
dst.pixels = src.pixels();
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
39
src/nostalgia/modules/core/src/keel/typeconv.hpp
Normal file
39
src/nostalgia/modules/core/src/keel/typeconv.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/def.hpp>
|
||||
|
||||
#include <keel/typeconv.hpp>
|
||||
|
||||
#include <nostalgia/core/context.hpp>
|
||||
#include <nostalgia/core/palette.hpp>
|
||||
#include <nostalgia/core/tilesheet.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
// Type converters
|
||||
|
||||
class NostalgiaPaletteToPaletteConverter: public keel::Converter<NostalgiaPalette, Palette> {
|
||||
ox::Error convert(keel::Context&, NostalgiaPalette &src, Palette &dst) const noexcept final;
|
||||
};
|
||||
|
||||
class TileSheetV1ToTileSheetV2Converter: public keel::Converter<TileSheetV1, TileSheetV2> {
|
||||
ox::Error convert(keel::Context&, TileSheetV1 &src, TileSheetV2 &dst) const noexcept final;
|
||||
};
|
||||
|
||||
class TileSheetV2ToTileSheetV3Converter: public keel::Converter<TileSheetV2, TileSheetV3> {
|
||||
static void convertSubsheet(
|
||||
TileSheetV2::SubSheet &src,
|
||||
TileSheetV3::SubSheet &dst,
|
||||
SubSheetId &idIt) noexcept;
|
||||
ox::Error convert(keel::Context&, TileSheetV2 &src, TileSheetV3 &dst) const noexcept final;
|
||||
};
|
||||
|
||||
class TileSheetToCompactTileSheetConverter: public keel::Converter<TileSheet, CompactTileSheet> {
|
||||
ox::Error convert(keel::Context&, TileSheet &src, CompactTileSheet &dst) const noexcept final;
|
||||
};
|
||||
|
||||
}
|
9
src/nostalgia/modules/core/src/opengl/CMakeLists.txt
Normal file
9
src/nostalgia/modules/core/src/opengl/CMakeLists.txt
Normal file
@ -0,0 +1,9 @@
|
||||
target_sources(
|
||||
NostalgiaCore PRIVATE
|
||||
context.cpp
|
||||
gfx.cpp
|
||||
)
|
||||
target_link_libraries(
|
||||
NostalgiaCore PUBLIC
|
||||
GlUtils
|
||||
)
|
32
src/nostalgia/modules/core/src/opengl/context.cpp
Normal file
32
src/nostalgia/modules/core/src/opengl/context.cpp
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "context.hpp"
|
||||
#include "gfx.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
void ContextDeleter::operator()(Context *p) noexcept {
|
||||
ox::safeDelete(p);
|
||||
}
|
||||
|
||||
Context::Context(turbine::Context &tctx, InitParams const¶ms) noexcept:
|
||||
turbineCtx(tctx),
|
||||
spriteBlocks(params.glSpriteCount, params.glBlocksPerSprite),
|
||||
drawer(*this),
|
||||
spriteCount(params.glSpriteCount),
|
||||
blocksPerSprite(params.glBlocksPerSprite) {
|
||||
}
|
||||
|
||||
Context::~Context() noexcept {
|
||||
shutdownGfx(*this);
|
||||
}
|
||||
|
||||
ox::Result<ContextUPtr> init(turbine::Context &tctx, InitParams const¶ms) noexcept {
|
||||
auto ctx = ox::make_unique<Context>(tctx, params);
|
||||
oxReturnError(initGfx(*ctx, params));
|
||||
return ContextUPtr(ctx.release());
|
||||
}
|
||||
|
||||
}
|
39
src/nostalgia/modules/core/src/opengl/context.hpp
Normal file
39
src/nostalgia/modules/core/src/opengl/context.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/types.hpp>
|
||||
|
||||
#include <glutils/glutils.hpp>
|
||||
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
#include <nostalgia/core/context.hpp>
|
||||
|
||||
#include "gfx.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class Context {
|
||||
|
||||
public:
|
||||
turbine::Context &turbineCtx;
|
||||
glutils::GLProgram bgShader;
|
||||
glutils::GLProgram spriteShader;
|
||||
ox::Array<renderer::CBB, 4> cbbs;
|
||||
renderer::SpriteBlockset spriteBlocks;
|
||||
ox::Array<Sprite, 128> spriteStates;
|
||||
ox::Array<renderer::Background, 4> backgrounds;
|
||||
renderer::Drawer drawer;
|
||||
uint_t spriteCount = 0;
|
||||
uint_t blocksPerSprite = 0;
|
||||
explicit Context(turbine::Context &tctx, InitParams const¶ms) noexcept;
|
||||
Context(Context const&) = delete;
|
||||
Context(Context&&) = delete;
|
||||
Context &operator=(Context const&) = delete;
|
||||
Context &operator=(Context&&) = delete;
|
||||
~Context() noexcept;
|
||||
};
|
||||
|
||||
}
|
694
src/nostalgia/modules/core/src/opengl/gfx.cpp
Normal file
694
src/nostalgia/modules/core/src/opengl/gfx.cpp
Normal file
@ -0,0 +1,694 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/std/array.hpp>
|
||||
#include <ox/std/fmt.hpp>
|
||||
#include <ox/std/vec.hpp>
|
||||
|
||||
#include <keel/media.hpp>
|
||||
|
||||
#include <glutils/glutils.hpp>
|
||||
|
||||
#include <nostalgia/core/context.hpp>
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
#include <nostalgia/core/tilesheet.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
#include "gfx.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
namespace renderer {
|
||||
|
||||
constexpr auto Scale = 1;
|
||||
constexpr auto PriorityScale = 0.01f;
|
||||
|
||||
Drawer::Drawer(Context &ctx) noexcept: m_ctx(ctx) {}
|
||||
|
||||
void Drawer::draw(turbine::Context &tctx) noexcept {
|
||||
core::gl::draw(m_ctx, turbine::getScreenSize(tctx));
|
||||
}
|
||||
|
||||
constexpr ox::CStringView bgvshadTmpl = R"glsl(
|
||||
{}
|
||||
in vec2 vTexCoord;
|
||||
in vec3 vPosition;
|
||||
in float vTileIdx;
|
||||
out vec2 fTexCoord;
|
||||
uniform float vXScale;
|
||||
uniform float vTileHeight;
|
||||
uniform float vBgIdx;
|
||||
void main() {
|
||||
float xScaleInvert = 1.0 - vXScale;
|
||||
gl_Position = vec4(
|
||||
vPosition.x * vXScale - xScaleInvert,
|
||||
vPosition.y,
|
||||
vPosition.z - 0.001 * vBgIdx,
|
||||
1.0);
|
||||
fTexCoord = vec2(
|
||||
vTexCoord.x,
|
||||
vTexCoord.y * vTileHeight + vTileIdx * vTileHeight);
|
||||
})glsl";
|
||||
|
||||
constexpr ox::CStringView bgfshadTmpl = R"glsl(
|
||||
{}
|
||||
out vec4 outColor;
|
||||
in vec2 fTexCoord;
|
||||
uniform sampler2D image;
|
||||
uniform vec2 fSrcImgSz;
|
||||
uniform vec4 fPalette[256];
|
||||
void main() {
|
||||
outColor = fPalette[int(texture(image, fTexCoord).rgb.r * 256)];
|
||||
//outColor = vec4(0.0, 0.7, 1.0, 1.0);
|
||||
if (outColor.a == 0) {
|
||||
discard;
|
||||
}
|
||||
})glsl";
|
||||
|
||||
constexpr ox::CStringView spritevshadTmpl = R"glsl(
|
||||
{}
|
||||
in float vEnabled;
|
||||
in vec3 vPosition;
|
||||
in vec2 vTexCoord;
|
||||
out vec2 fTexCoord;
|
||||
uniform float vXScale;
|
||||
uniform float vTileHeight;
|
||||
void main() {
|
||||
float xScaleInvert = 1.0 - vXScale;
|
||||
gl_Position = vec4(
|
||||
vPosition.x * vXScale - xScaleInvert,
|
||||
vPosition.y,
|
||||
// offset to ensure sprites draw on top of BGs by default
|
||||
vPosition.z - 0.004,
|
||||
1.0) * vEnabled;
|
||||
fTexCoord = vTexCoord * vec2(1, vTileHeight);
|
||||
})glsl";
|
||||
|
||||
constexpr ox::CStringView spritefshadTmpl = R"glsl(
|
||||
{}
|
||||
out vec4 outColor;
|
||||
in vec2 fTexCoord;
|
||||
uniform sampler2D image;
|
||||
uniform vec2 fSrcImgSz;
|
||||
uniform vec4 fPalette[256];
|
||||
void main() {
|
||||
outColor = fPalette[int(texture(image, fTexCoord).rgb.r * 256)];
|
||||
//outColor = vec4(0.0, 0.7, 1.0, 1.0);
|
||||
if (outColor.a == 0) {
|
||||
discard;
|
||||
}
|
||||
})glsl";
|
||||
|
||||
[[nodiscard]]
|
||||
static constexpr auto bgVertexRow(uint_t x, uint_t y) noexcept {
|
||||
return y * TileRows + x;
|
||||
}
|
||||
|
||||
static void setSpriteBufferObject(
|
||||
uint_t vi,
|
||||
float enabled,
|
||||
float x,
|
||||
float y,
|
||||
uint_t textureRow,
|
||||
uint_t flipX,
|
||||
uint_t priority,
|
||||
float *vbo,
|
||||
GLuint *ebo) noexcept {
|
||||
// don't worry, this memcpy gets optimized to something much more ideal
|
||||
constexpr float xmod = 0.1f;
|
||||
constexpr float ymod = 0.1f;
|
||||
x *= xmod;
|
||||
y *= -ymod;
|
||||
x -= 1.f;
|
||||
y += 1.f - ymod;
|
||||
auto const prif = static_cast<float>(priority) * PriorityScale;
|
||||
auto const textureRowf = static_cast<float>(textureRow);
|
||||
float const L = flipX ? 1 : 0;
|
||||
float const R = flipX ? 0 : 1;
|
||||
ox::Array<float, SpriteVertexVboLength> const vertices {
|
||||
// vEnabled| vPosition | vTexCoord
|
||||
enabled, x, y, prif, L, textureRowf + 1, // bottom left
|
||||
enabled, x + xmod, y, prif, R, textureRowf + 1, // bottom right
|
||||
enabled, x + xmod, y + ymod, prif, R, textureRowf + 0, // top right
|
||||
enabled, x, y + ymod, prif, L, textureRowf + 0, // top left
|
||||
};
|
||||
memcpy(vbo, vertices.data(), sizeof(vertices));
|
||||
ox::Array<GLuint, SpriteVertexEboLength> const elms {
|
||||
vi + 0, vi + 1, vi + 2,
|
||||
vi + 2, vi + 3, vi + 0,
|
||||
};
|
||||
memcpy(ebo, elms.data(), sizeof(elms));
|
||||
}
|
||||
|
||||
static void setTileBufferObject(
|
||||
uint_t vi,
|
||||
float x,
|
||||
float y,
|
||||
float textureTileIdx,
|
||||
float priority,
|
||||
float *vbo,
|
||||
GLuint *ebo) noexcept {
|
||||
// don't worry, this memcpy gets optimized to something much more ideal
|
||||
constexpr float ymod = 0.1f;
|
||||
constexpr float xmod = 0.1f;
|
||||
x *= xmod;
|
||||
y *= -ymod;
|
||||
x -= 1.0f;
|
||||
y += 1.0f - ymod;
|
||||
auto const prif = priority * PriorityScale;
|
||||
ox::Array<float, BgVertexVboLength> const vertices {
|
||||
x, y, prif, 0, 1, textureTileIdx, // bottom left
|
||||
x + xmod, y, prif, 1, 1, textureTileIdx, // bottom right
|
||||
x + xmod, y + ymod, prif, 1, 0, textureTileIdx, // top right
|
||||
x, y + ymod, prif, 0, 0, textureTileIdx, // top left
|
||||
};
|
||||
memcpy(vbo, vertices.data(), sizeof(vertices));
|
||||
ox::Array<GLuint, BgVertexEboLength> const elms {
|
||||
vi + 0, vi + 1, vi + 2,
|
||||
vi + 2, vi + 3, vi + 0,
|
||||
};
|
||||
memcpy(ebo, elms.data(), sizeof(elms));
|
||||
}
|
||||
|
||||
static void initSpriteBufferObjects(Context &ctx, glutils::BufferSet &bs) noexcept {
|
||||
for (auto i = 0u; i < ctx.spriteCount; ++i) {
|
||||
auto vbo = &bs.vertices[i * static_cast<std::size_t>(SpriteVertexVboLength)];
|
||||
auto ebo = &bs.elements[i * static_cast<std::size_t>(SpriteVertexEboLength)];
|
||||
setSpriteBufferObject(i * static_cast<uint_t>(SpriteVertexVboRows) * ctx.blocksPerSprite, 0, 0, 0, 0, false, 0, vbo, ebo);
|
||||
}
|
||||
}
|
||||
|
||||
static void initBackgroundBufferObjects(glutils::BufferSet &bs) noexcept {
|
||||
for (auto x = 0u; x < TileColumns; ++x) {
|
||||
for (auto y = 0u; y < TileRows; ++y) {
|
||||
const auto i = bgVertexRow(x, y);
|
||||
auto vbo = &bs.vertices[i * static_cast<std::size_t>(BgVertexVboLength)];
|
||||
auto ebo = &bs.elements[i * static_cast<std::size_t>(BgVertexEboLength)];
|
||||
setTileBufferObject(
|
||||
static_cast<uint_t>(i * BgVertexVboRows),
|
||||
static_cast<float>(x),
|
||||
static_cast<float>(y),
|
||||
0,
|
||||
0,
|
||||
vbo,
|
||||
ebo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void initSpritesBufferset(Context &ctx) noexcept {
|
||||
auto const shader = ctx.spriteShader.id;
|
||||
auto &bs = ctx.spriteBlocks;
|
||||
// vao
|
||||
bs.vao = glutils::generateVertexArrayObject();
|
||||
glBindVertexArray(bs.vao);
|
||||
// vbo & ebo
|
||||
bs.vbo = glutils::generateBuffer();
|
||||
bs.ebo = glutils::generateBuffer();
|
||||
initSpriteBufferObjects(ctx, bs);
|
||||
glutils::sendVbo(bs);
|
||||
glutils::sendEbo(bs);
|
||||
// vbo layout
|
||||
// in float vEnabled;
|
||||
auto const enabledAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vEnabled"));
|
||||
glEnableVertexAttribArray(enabledAttr);
|
||||
glVertexAttribPointer(enabledAttr, 1, GL_FLOAT, GL_FALSE, SpriteVertexVboRowLength * sizeof(float), nullptr);
|
||||
// in vec3 vPosition;
|
||||
auto const posAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vPosition"));
|
||||
glEnableVertexAttribArray(posAttr);
|
||||
glVertexAttribPointer(posAttr, 3, GL_FLOAT, GL_FALSE, SpriteVertexVboRowLength * sizeof(float),
|
||||
std::bit_cast<void*>(uintptr_t{1 * sizeof(float)}));
|
||||
// in vec2 vTexCoord;
|
||||
auto const texCoordAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vTexCoord"));
|
||||
glEnableVertexAttribArray(texCoordAttr);
|
||||
glVertexAttribPointer(texCoordAttr, 2, GL_FLOAT, GL_FALSE, SpriteVertexVboRowLength * sizeof(float),
|
||||
std::bit_cast<void*>(uintptr_t{4 * sizeof(float)}));
|
||||
}
|
||||
|
||||
static void initBackgroundBufferset(
|
||||
GLuint shader,
|
||||
glutils::BufferSet &bs) noexcept {
|
||||
// vao
|
||||
bs.vao = glutils::generateVertexArrayObject();
|
||||
glBindVertexArray(bs.vao);
|
||||
// vbo & ebo
|
||||
bs.vbo = glutils::generateBuffer();
|
||||
bs.ebo = glutils::generateBuffer();
|
||||
initBackgroundBufferObjects(bs);
|
||||
glutils::sendVbo(bs);
|
||||
glutils::sendEbo(bs);
|
||||
// vbo layout
|
||||
auto const posAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vPosition"));
|
||||
glEnableVertexAttribArray(posAttr);
|
||||
glVertexAttribPointer(posAttr, 3, GL_FLOAT, GL_FALSE, BgVertexVboRowLength * sizeof(float), nullptr);
|
||||
auto const texCoordAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vTexCoord"));
|
||||
glEnableVertexAttribArray(texCoordAttr);
|
||||
glVertexAttribPointer(
|
||||
texCoordAttr, 2, GL_FLOAT, GL_FALSE, BgVertexVboRowLength * sizeof(float),
|
||||
std::bit_cast<void*>(uintptr_t{3 * sizeof(float)}));
|
||||
auto const heightMultAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vTileIdx"));
|
||||
glEnableVertexAttribArray(heightMultAttr);
|
||||
glVertexAttribPointer(
|
||||
heightMultAttr, 1, GL_FLOAT, GL_FALSE, BgVertexVboRowLength * sizeof(float),
|
||||
std::bit_cast<void*>(uintptr_t{5 * sizeof(float)}));
|
||||
}
|
||||
|
||||
static glutils::GLTexture createTexture(
|
||||
GLsizei w,
|
||||
GLsizei h,
|
||||
const void *pixels) noexcept {
|
||||
GLuint texId = 0;
|
||||
glGenTextures(1, &texId);
|
||||
glutils::GLTexture tex(texId);
|
||||
tex.width = w;
|
||||
tex.height = h;
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, tex.id);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.width, tex.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
return tex;
|
||||
}
|
||||
|
||||
static void drawBackground(CBB &cbb) noexcept {
|
||||
glBindVertexArray(cbb.vao);
|
||||
if (cbb.updated) {
|
||||
cbb.updated = false;
|
||||
glutils::sendVbo(cbb);
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, cbb.tex);
|
||||
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(cbb.elements.size()), GL_UNSIGNED_INT, nullptr);
|
||||
}
|
||||
|
||||
static void drawBackgrounds(
|
||||
Context &ctx,
|
||||
ox::Size const&renderSz) noexcept {
|
||||
// load background shader and its uniforms
|
||||
glUseProgram(ctx.bgShader);
|
||||
const auto uniformSrcImgSz = glGetUniformLocation(ctx.bgShader, "fSrcImgSz");
|
||||
const auto uniformXScale = static_cast<GLint>(glGetUniformLocation(ctx.bgShader, "vXScale"));
|
||||
const auto uniformTileHeight = static_cast<GLint>(glGetUniformLocation(ctx.bgShader, "vTileHeight"));
|
||||
const auto uniformBgIdx = static_cast<GLint>(glGetUniformLocation(ctx.bgShader, "vBgIdx"));
|
||||
const auto [wi, hi] = renderSz;
|
||||
const auto wf = static_cast<float>(wi);
|
||||
const auto hf = static_cast<float>(hi);
|
||||
glUniform1f(uniformXScale, hf / wf);
|
||||
auto bgIdx = 0.f;
|
||||
for (const auto &bg : ctx.backgrounds) {
|
||||
if (bg.enabled) {
|
||||
auto &cbb = ctx.cbbs[bg.cbbIdx];
|
||||
const auto tileRows = cbb.tex.height / (TileHeight * Scale);
|
||||
glUniform1f(uniformTileHeight, 1.0f / static_cast<float>(tileRows));
|
||||
glUniform2f(
|
||||
uniformSrcImgSz,
|
||||
static_cast<float>(cbb.tex.width),
|
||||
static_cast<float>(cbb.tex.height));
|
||||
glUniform1f(uniformBgIdx, bgIdx);
|
||||
drawBackground(cbb);
|
||||
++bgIdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void drawSprites(Context &ctx, ox::Size const&renderSz) noexcept {
|
||||
glUseProgram(ctx.spriteShader);
|
||||
auto &sb = ctx.spriteBlocks;
|
||||
const auto uniformXScale = glGetUniformLocation(ctx.bgShader, "vXScale");
|
||||
const auto uniformTileHeight = glGetUniformLocation(ctx.spriteShader, "vTileHeight");
|
||||
const auto [wi, hi] = renderSz;
|
||||
const auto wf = static_cast<float>(wi);
|
||||
const auto hf = static_cast<float>(hi);
|
||||
glUniform1f(uniformXScale, hf / wf);
|
||||
// update vbo
|
||||
glBindVertexArray(sb.vao);
|
||||
if (sb.updated) {
|
||||
sb.updated = false;
|
||||
glutils::sendVbo(sb);
|
||||
}
|
||||
// set vTileHeight uniform
|
||||
const auto tileRows = sb.tex.height / (TileHeight * Scale);
|
||||
glUniform1f(uniformTileHeight, 1.0f / static_cast<float>(tileRows));
|
||||
// draw
|
||||
glBindTexture(GL_TEXTURE_2D, sb.tex);
|
||||
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(sb.elements.size()), GL_UNSIGNED_INT, nullptr);
|
||||
}
|
||||
|
||||
static void loadPalette(
|
||||
GLuint shaderPgrm,
|
||||
Palette const&pal) noexcept {
|
||||
static constexpr std::size_t ColorCnt = 256;
|
||||
ox::Array<GLfloat, ColorCnt * 4> palette{};
|
||||
for (auto i = 0u; const auto c : pal.colors) {
|
||||
palette[i++] = redf(c);
|
||||
palette[i++] = greenf(c);
|
||||
palette[i++] = bluef(c);
|
||||
palette[i++] = 255;
|
||||
}
|
||||
// make first color transparent
|
||||
palette[3] = 0;
|
||||
glUseProgram(shaderPgrm);
|
||||
const auto uniformPalette = static_cast<GLint>(glGetUniformLocation(shaderPgrm, "fPalette"));
|
||||
glUniform4fv(uniformPalette, ColorCnt, palette.data());
|
||||
}
|
||||
|
||||
static void setSprite(
|
||||
Context &ctx,
|
||||
uint_t const idx,
|
||||
Sprite const&s) noexcept {
|
||||
// Tonc Table 8.4
|
||||
static constexpr ox::Array<ox::Vec<uint_t>, 12> dimensions{
|
||||
// col 0
|
||||
{1, 1}, // 0, 0
|
||||
{2, 2}, // 0, 1
|
||||
{4, 4}, // 0, 2
|
||||
{8, 8}, // 0, 3
|
||||
// col 1
|
||||
{2, 1}, // 1, 0
|
||||
{4, 1}, // 1, 1
|
||||
{4, 2}, // 1, 2
|
||||
{8, 4}, // 1, 3
|
||||
// col 2
|
||||
{1, 1}, // 2, 0
|
||||
{1, 4}, // 2, 1
|
||||
{2, 4}, // 2, 2
|
||||
{4, 8}, // 2, 3
|
||||
};
|
||||
oxAssert(idx < ctx.spriteStates.size(), "overflow");
|
||||
auto const dim = dimensions[(s.spriteShape << 2) | s.spriteSize];
|
||||
auto const uX = static_cast<int>(s.x) % 255;
|
||||
auto const uY = static_cast<int>(s.y + 8) % 255 - 8;
|
||||
oxAssert(1 < ctx.spriteBlocks.vertices.size(), "vbo overflow");
|
||||
oxAssert(1 < ctx.spriteBlocks.elements.size(), "ebo overflow");
|
||||
const auto spriteVboSz = ctx.blocksPerSprite * renderer::SpriteVertexVboLength;
|
||||
const auto spriteEboSz = ctx.blocksPerSprite * renderer::SpriteVertexEboLength;
|
||||
auto const vboBase = spriteVboSz * idx;
|
||||
auto const eboBase = spriteEboSz * idx;
|
||||
auto i = 0u;
|
||||
const auto set = [&](int xIt, int yIt, bool enabled) {
|
||||
auto const fX = static_cast<float>(uX + xIt * 8) / 8;
|
||||
auto const fY = static_cast<float>(uY + yIt * 8) / 8;
|
||||
auto const vboIdx = vboBase + renderer::SpriteVertexVboLength * i;
|
||||
auto const eboIdx = eboBase + renderer::SpriteVertexEboLength * i;
|
||||
oxAssert(vboIdx < ctx.spriteBlocks.vertices.size(), "vbo overflow");
|
||||
oxAssert(eboIdx < ctx.spriteBlocks.elements.size(), "ebo overflow");
|
||||
auto const vbo = &ctx.spriteBlocks.vertices[vboIdx];
|
||||
auto const ebo = &ctx.spriteBlocks.elements[eboIdx];
|
||||
renderer::setSpriteBufferObject(
|
||||
static_cast<uint_t>(vboIdx),
|
||||
enabled,
|
||||
fX,
|
||||
fY,
|
||||
s.tileIdx + i,
|
||||
s.flipX,
|
||||
s.priority & 0b11,
|
||||
vbo,
|
||||
ebo);
|
||||
++i;
|
||||
};
|
||||
if (!s.flipX) {
|
||||
for (auto yIt = 0; yIt < static_cast<int>(dim.y); ++yIt) {
|
||||
for (auto xIt = 0u; xIt < dim.x; ++xIt) {
|
||||
set(static_cast<int>(xIt), static_cast<int>(yIt), s.enabled);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto yIt = 0u; yIt < dim.y; ++yIt) {
|
||||
for (auto xIt = dim.x - 1; xIt < ~0u; --xIt) {
|
||||
set(static_cast<int>(xIt), static_cast<int>(yIt), s.enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
// clear remaining blocks in the sprite
|
||||
for (; i < ctx.blocksPerSprite; ++i) {
|
||||
set(0, 0, false);
|
||||
}
|
||||
ctx.spriteBlocks.updated = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ox::Error initGfx(
|
||||
Context &ctx,
|
||||
InitParams const&initParams) noexcept {
|
||||
const auto bgVshad = ox::sfmt(renderer::bgvshadTmpl, gl::GlslVersion);
|
||||
const auto bgFshad = ox::sfmt(renderer::bgfshadTmpl, gl::GlslVersion);
|
||||
const auto spriteVshad = ox::sfmt(renderer::spritevshadTmpl, gl::GlslVersion);
|
||||
const auto spriteFshad = ox::sfmt(renderer::spritefshadTmpl, gl::GlslVersion);
|
||||
oxReturnError(glutils::buildShaderProgram(bgVshad, bgFshad).moveTo(ctx.bgShader));
|
||||
oxReturnError(
|
||||
glutils::buildShaderProgram(spriteVshad, spriteFshad).moveTo(ctx.spriteShader));
|
||||
for (auto &cbb : ctx.cbbs) {
|
||||
initBackgroundBufferset(ctx.bgShader, cbb);
|
||||
}
|
||||
renderer::initSpritesBufferset(ctx);
|
||||
if (initParams.glInstallDrawer) {
|
||||
turbine::gl::addDrawer(ctx.turbineCtx, &ctx.drawer);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void shutdownGfx(Context &ctx) noexcept {
|
||||
turbine::gl::removeDrawer(ctx.turbineCtx, &ctx.drawer);
|
||||
}
|
||||
|
||||
struct TileSheetData {
|
||||
ox::Vector<uint32_t> pixels;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
[[nodiscard]]
|
||||
constexpr ox::Size size() const noexcept {
|
||||
return {width, height};
|
||||
}
|
||||
};
|
||||
|
||||
static ox::Result<TileSheetData> normalizeTileSheet(
|
||||
CompactTileSheet const&tilesheet) noexcept {
|
||||
const uint_t bytesPerTile = tilesheet.bpp == 8 ? PixelsPerTile : PixelsPerTile / 2;
|
||||
const auto tiles = tilesheet.pixels.size() / bytesPerTile;
|
||||
constexpr int width = 8;
|
||||
const int height = 8 * static_cast<int>(tiles);
|
||||
ox::Vector<uint32_t> pixels;
|
||||
if (bytesPerTile == 64) { // 8 BPP
|
||||
pixels.resize(tilesheet.pixels.size());
|
||||
for (std::size_t i = 0; i < tilesheet.pixels.size(); ++i) {
|
||||
pixels[i] = tilesheet.pixels[i];
|
||||
}
|
||||
} else { // 4 BPP
|
||||
pixels.resize(tilesheet.pixels.size() * 2);
|
||||
for (std::size_t i = 0; i < tilesheet.pixels.size(); ++i) {
|
||||
pixels[i * 2 + 0] = tilesheet.pixels[i] & 0xF;
|
||||
pixels[i * 2 + 1] = tilesheet.pixels[i] >> 4;
|
||||
}
|
||||
}
|
||||
return TileSheetData{std::move(pixels), width, height};
|
||||
}
|
||||
|
||||
ox::Error loadBgPalette(
|
||||
Context &ctx,
|
||||
ox::FileAddress const&paletteAddr) noexcept {
|
||||
auto &kctx = keelCtx(ctx.turbineCtx);
|
||||
oxRequire(palette, readObj<Palette>(kctx, paletteAddr));
|
||||
renderer::loadPalette(ctx.bgShader, *palette);
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error loadSpritePalette(
|
||||
Context &ctx,
|
||||
ox::FileAddress const&paletteAddr) noexcept {
|
||||
auto &kctx = keelCtx(ctx.turbineCtx);
|
||||
oxRequire(palette, readObj<Palette>(kctx, paletteAddr));
|
||||
renderer::loadPalette(ctx.spriteShader, *palette);
|
||||
return {};
|
||||
}
|
||||
|
||||
static ox::Result<TileSheetData> buildSetTsd(
|
||||
Context &ctx,
|
||||
TileSheetSet const&set) noexcept {
|
||||
auto &kctx = keelCtx(ctx.turbineCtx);
|
||||
TileSheetData setTsd;
|
||||
setTsd.width = TileWidth;
|
||||
for (auto const&entry : set.entries) {
|
||||
oxRequire(tilesheet, readObj<CompactTileSheet>(kctx, entry.tilesheet));
|
||||
oxRequire(tsd, normalizeTileSheet(*tilesheet));
|
||||
for (auto const&s : entry.sections) {
|
||||
auto const size = s.tiles * PixelsPerTile;
|
||||
for (auto i = 0; i < size; ++i) {
|
||||
auto const srcIdx = static_cast<size_t>(i) + static_cast<size_t>(s.begin * PixelsPerTile);
|
||||
setTsd.pixels.push_back(tsd.pixels[srcIdx]);
|
||||
}
|
||||
setTsd.height += TileHeight * s.tiles;
|
||||
}
|
||||
}
|
||||
return setTsd;
|
||||
}
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
uint_t cbb,
|
||||
ox::FileAddress const&tilesheetAddr,
|
||||
bool loadDefaultPalette) noexcept {
|
||||
auto &kctx = keelCtx(ctx.turbineCtx);
|
||||
oxRequire(tilesheet, readObj<CompactTileSheet>(kctx, tilesheetAddr));
|
||||
oxRequire(tsd, normalizeTileSheet(*tilesheet));
|
||||
oxTracef("nostalgia.core.gfx.gl", "loadBgTexture: { cbbIdx: {}, w: {}, h: {} }", cbb, tsd.width, tsd.height);
|
||||
ctx.cbbs[cbb].tex = renderer::createTexture(tsd.width, tsd.height, tsd.pixels.data());
|
||||
if (loadDefaultPalette) {
|
||||
oxReturnError(loadBgPalette(ctx, tilesheet->defaultPalette));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned cbb,
|
||||
TileSheetSet const&set) noexcept {
|
||||
oxRequire(setTsd, buildSetTsd(ctx, set));
|
||||
ctx.cbbs[cbb].tex = renderer::createTexture(setTsd.width, setTsd.height, setTsd.pixels.data());
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error loadSpriteTileSheet(
|
||||
Context &ctx,
|
||||
ox::FileAddress const&tilesheetAddr,
|
||||
bool loadDefaultPalette) noexcept {
|
||||
auto &kctx = keelCtx(ctx.turbineCtx);
|
||||
oxRequire(tilesheet, readObj<CompactTileSheet>(kctx, tilesheetAddr));
|
||||
oxRequire(tsd, normalizeTileSheet(*tilesheet));
|
||||
oxTracef("nostalgia.core.gfx.gl", "loadSpriteTexture: { w: {}, h: {} }", tsd.width, tsd.height);
|
||||
ctx.spriteBlocks.tex = renderer::createTexture(tsd.width, tsd.height, tsd.pixels.data());
|
||||
if (loadDefaultPalette) {
|
||||
oxReturnError(loadSpritePalette(ctx, tilesheet->defaultPalette));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error loadSpriteTileSheet(
|
||||
Context &ctx,
|
||||
TileSheetSet const&set) noexcept {
|
||||
oxRequire(setTsd, buildSetTsd(ctx, set));
|
||||
ctx.spriteBlocks.tex = renderer::createTexture(setTsd.width, setTsd.height, setTsd.pixels.data());
|
||||
return {};
|
||||
}
|
||||
|
||||
void setBgTile(
|
||||
Context &ctx,
|
||||
uint_t bgIdx,
|
||||
int column,
|
||||
int row,
|
||||
uint8_t tile) noexcept {
|
||||
oxTracef(
|
||||
"nostalgia.core.gfx.setBgTile",
|
||||
"bgIdx: {}, column: {}, row: {}, tile: {}",
|
||||
bgIdx, column, row, tile);
|
||||
const auto z = static_cast<uint_t>(bgIdx);
|
||||
const auto y = static_cast<uint_t>(row);
|
||||
const auto x = static_cast<uint_t>(column);
|
||||
const auto i = renderer::bgVertexRow(x, y);
|
||||
auto &cbb = ctx.cbbs[z];
|
||||
const auto vbo = &cbb.vertices[i * renderer::BgVertexVboLength];
|
||||
const auto ebo = &cbb.elements[i * renderer::BgVertexEboLength];
|
||||
auto &bg = ctx.backgrounds[bgIdx];
|
||||
renderer::setTileBufferObject(
|
||||
static_cast<uint_t>(i * renderer::BgVertexVboRows),
|
||||
static_cast<float>(x),
|
||||
static_cast<float>(y),
|
||||
static_cast<float>(tile),
|
||||
bg.priority,
|
||||
vbo,
|
||||
ebo);
|
||||
cbb.updated = true;
|
||||
}
|
||||
|
||||
void clearBg(Context &ctx, uint_t bgIdx) noexcept {
|
||||
auto &cbb = ctx.cbbs[static_cast<std::size_t>(bgIdx)];
|
||||
initBackgroundBufferObjects(cbb);
|
||||
cbb.updated = true;
|
||||
auto &bg = ctx.backgrounds[static_cast<std::size_t>(bgIdx)];
|
||||
bg.priority = 0;
|
||||
}
|
||||
|
||||
uint8_t bgStatus(Context &ctx) noexcept {
|
||||
uint8_t out = 0;
|
||||
for (uint_t i = 0; i < ctx.cbbs.size(); ++i) {
|
||||
out |= static_cast<uint8_t>(static_cast<uint_t>(ctx.backgrounds[i].enabled) << i);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void setBgStatus(Context &ctx, uint32_t status) noexcept {
|
||||
for (uint_t i = 0; i < ctx.cbbs.size(); ++i) {
|
||||
ctx.backgrounds[i].enabled = (status >> i) & 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool bgStatus(Context &ctx, uint_t bg) noexcept {
|
||||
return ctx.backgrounds[bg].enabled;
|
||||
}
|
||||
|
||||
void setBgStatus(Context &ctx, uint_t bg, bool status) noexcept {
|
||||
ctx.backgrounds[bg].enabled = status;
|
||||
}
|
||||
|
||||
void setBgCbb(Context &ctx, uint_t bgIdx, uint_t cbbIdx) noexcept {
|
||||
auto &bg = ctx.backgrounds[bgIdx];
|
||||
bg.cbbIdx = cbbIdx;
|
||||
}
|
||||
|
||||
void setBgPriority(Context &ctx, uint_t bgIdx, uint_t priority) noexcept {
|
||||
auto &bg = ctx.backgrounds[bgIdx];
|
||||
bg.priority = static_cast<float>(priority & 0b11);
|
||||
}
|
||||
|
||||
void hideSprite(Context &ctx, uint_t idx) noexcept {
|
||||
auto &s = ctx.spriteStates[idx];
|
||||
s.enabled = false;
|
||||
renderer::setSprite(ctx, idx, s);
|
||||
}
|
||||
|
||||
void showSprite(Context &ctx, uint_t idx) noexcept {
|
||||
auto &s = ctx.spriteStates[idx];
|
||||
s.enabled = true;
|
||||
renderer::setSprite(ctx, idx, s);
|
||||
}
|
||||
|
||||
void setSprite(Context &ctx, uint_t idx, Sprite const&sprite) noexcept {
|
||||
auto &s = ctx.spriteStates[idx];
|
||||
s = sprite;
|
||||
renderer::setSprite(ctx, idx, s);
|
||||
}
|
||||
|
||||
uint_t spriteCount(Context &ctx) noexcept {
|
||||
return ctx.spriteCount;
|
||||
}
|
||||
|
||||
namespace gl {
|
||||
|
||||
ox::Size drawSize(int scale) noexcept {
|
||||
return {240 * scale, 160 * scale};
|
||||
}
|
||||
|
||||
void draw(core::Context &ctx, ox::Size const&renderSz) noexcept {
|
||||
glViewport(0, 0, renderSz.width, renderSz.height);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glClearColor(0, 0, 0, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
renderer::drawBackgrounds(ctx, renderSz);
|
||||
if (ctx.spriteBlocks.tex) {
|
||||
renderer::drawSprites(ctx, renderSz);
|
||||
}
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
void draw(core::Context &ctx, int scale) noexcept {
|
||||
draw(ctx, drawSize(scale));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
64
src/nostalgia/modules/core/src/opengl/gfx.hpp
Normal file
64
src/nostalgia/modules/core/src/opengl/gfx.hpp
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/types.hpp>
|
||||
|
||||
#include <turbine/gfx.hpp>
|
||||
|
||||
#include <glutils/glutils.hpp>
|
||||
|
||||
#include <nostalgia/core/context.hpp>
|
||||
|
||||
namespace nostalgia::core::renderer {
|
||||
|
||||
constexpr uint64_t TileRows = 128;
|
||||
constexpr uint64_t TileColumns = 128;
|
||||
constexpr uint64_t TileCount = TileRows * TileColumns;
|
||||
constexpr uint64_t BgVertexVboRows = 4;
|
||||
constexpr uint64_t BgVertexVboRowLength = 6;
|
||||
constexpr uint64_t BgVertexVboLength = BgVertexVboRows * BgVertexVboRowLength;
|
||||
constexpr uint64_t BgVertexEboLength = 6;
|
||||
constexpr uint64_t SpriteVertexVboRows = 4;
|
||||
constexpr uint64_t SpriteVertexVboRowLength = 6;
|
||||
constexpr uint64_t SpriteVertexVboLength = SpriteVertexVboRows * SpriteVertexVboRowLength;
|
||||
constexpr uint64_t SpriteVertexEboLength = 6;
|
||||
|
||||
struct CBB: public glutils::BufferSet {
|
||||
bool updated = false;
|
||||
constexpr CBB() noexcept {
|
||||
vertices.resize(TileCount * BgVertexVboLength);
|
||||
elements.resize(TileCount * BgVertexEboLength);
|
||||
}
|
||||
};
|
||||
|
||||
struct SpriteBlockset: public glutils::BufferSet {
|
||||
bool updated = false;
|
||||
constexpr SpriteBlockset(uint64_t spriteCount, uint64_t blocksPerSprite) noexcept {
|
||||
vertices.resize(spriteCount * SpriteVertexVboLength * blocksPerSprite);
|
||||
elements.resize(spriteCount * SpriteVertexEboLength * blocksPerSprite);
|
||||
}
|
||||
};
|
||||
|
||||
struct Background {
|
||||
float priority = 0;
|
||||
bool enabled = false;
|
||||
unsigned cbbIdx = 0;
|
||||
};
|
||||
|
||||
class Drawer: public turbine::gl::Drawer {
|
||||
private:
|
||||
Context &m_ctx;
|
||||
public:
|
||||
explicit Drawer(Context &ctx) noexcept;
|
||||
void draw(turbine::Context&) noexcept final;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace nostalgia::core {
|
||||
ox::Error initGfx(Context &ctx, InitParams const&) noexcept;
|
||||
void shutdownGfx(Context &ctx) noexcept;
|
||||
}
|
30
src/nostalgia/modules/core/src/studio/CMakeLists.txt
Normal file
30
src/nostalgia/modules/core/src/studio/CMakeLists.txt
Normal file
@ -0,0 +1,30 @@
|
||||
add_library(NostalgiaCore-Studio)
|
||||
|
||||
add_library(
|
||||
NostalgiaCore-Studio-ImGui
|
||||
studiomodule.cpp
|
||||
tilesheeteditor/tilesheeteditor-imgui.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
NostalgiaCore-Studio PUBLIC
|
||||
NostalgiaCore
|
||||
Studio
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
NostalgiaCore-Studio-ImGui PUBLIC
|
||||
NostalgiaCore-Studio
|
||||
Studio
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
NostalgiaCore-Studio-ImGui
|
||||
NostalgiaCore-Studio
|
||||
LIBRARY DESTINATION
|
||||
${NOSTALGIA_DIST_MODULE}
|
||||
)
|
||||
|
||||
add_subdirectory(paletteeditor)
|
||||
add_subdirectory(tilesheeteditor)
|
@ -0,0 +1,5 @@
|
||||
target_sources(
|
||||
NostalgiaCore-Studio PRIVATE
|
||||
paletteeditor.cpp
|
||||
paletteeditor-imgui.cpp
|
||||
)
|
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <ox/std/memory.hpp>
|
||||
|
||||
#include <keel/media.hpp>
|
||||
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
|
||||
#include "paletteeditor.hpp"
|
||||
#include "paletteeditor-imgui.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
PaletteEditorImGui::PaletteEditorImGui(turbine::Context &ctx, ox::CRStringView path):
|
||||
Editor(path),
|
||||
m_ctx(ctx),
|
||||
m_pal(*keel::readObj<Palette>(keelCtx(m_ctx), ox::FileAddress(itemPath())).unwrapThrow()) {
|
||||
}
|
||||
|
||||
void PaletteEditorImGui::draw(turbine::Context&) noexcept {
|
||||
static constexpr auto flags = ImGuiTableFlags_RowBg;
|
||||
const auto paneSize = ImGui::GetContentRegionAvail();
|
||||
ImGui::BeginChild("Colors", ImVec2(paneSize.x - 208, paneSize.y), true);
|
||||
{
|
||||
const auto colorsSz = ImGui::GetContentRegionAvail();
|
||||
static constexpr auto toolbarHeight = 40;
|
||||
{
|
||||
const auto sz = ImVec2(70, 24);
|
||||
if (ImGui::Button("Add", sz)) {
|
||||
const auto colorSz = static_cast<int>(m_pal.colors.size());
|
||||
constexpr Color16 c = 0;
|
||||
undoStack()->push(ox::make_unique<AddColorCommand>(&m_pal, c, colorSz));
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(m_selectedRow >= m_pal.colors.size());
|
||||
{
|
||||
if (ImGui::Button("Remove", sz)) {
|
||||
undoStack()->push(
|
||||
ox::make_unique<RemoveColorCommand>(
|
||||
&m_pal,
|
||||
m_pal.colors[static_cast<std::size_t>(m_selectedRow)],
|
||||
static_cast<int>(m_selectedRow)));
|
||||
m_selectedRow = ox::min(m_pal.colors.size() - 1, m_selectedRow);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(m_selectedRow <= 0);
|
||||
{
|
||||
if (ImGui::Button("Move Up", sz)) {
|
||||
undoStack()->push(ox::make_unique<MoveColorCommand>(&m_pal, m_selectedRow, -1));
|
||||
--m_selectedRow;
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(m_selectedRow >= m_pal.colors.size() - 1);
|
||||
{
|
||||
if (ImGui::Button("Move Down", sz)) {
|
||||
undoStack()->push(ox::make_unique<MoveColorCommand>(&m_pal, m_selectedRow, 1));
|
||||
++m_selectedRow;
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
ImGui::BeginTable("Colors", 5, flags, ImVec2(colorsSz.x, colorsSz.y - (toolbarHeight + 5)));
|
||||
{
|
||||
ImGui::TableSetupColumn("Idx", ImGuiTableColumnFlags_WidthFixed, 25);
|
||||
ImGui::TableSetupColumn("Red", ImGuiTableColumnFlags_WidthFixed, 50);
|
||||
ImGui::TableSetupColumn("Green", ImGuiTableColumnFlags_WidthFixed, 50);
|
||||
ImGui::TableSetupColumn("Blue", ImGuiTableColumnFlags_WidthFixed, 50);
|
||||
ImGui::TableSetupColumn("Color Preview", ImGuiTableColumnFlags_NoHide);
|
||||
ImGui::TableHeadersRow();
|
||||
constexpr auto colVal = [] (unsigned num) {
|
||||
ox::Array<char, 4> numStr;
|
||||
ImGui::TableNextColumn();
|
||||
ox_itoa(num, numStr.data());
|
||||
ImGui::SetCursorPosX(
|
||||
ImGui::GetCursorPosX() + ImGui::GetColumnWidth() - ImGui::CalcTextSize(numStr.data()).x);
|
||||
ImGui::Text("%s", numStr.data());
|
||||
};
|
||||
for (auto i = 0u; const auto c : m_pal.colors) {
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
ImGui::TableNextRow();
|
||||
// Color No.
|
||||
colVal(i);
|
||||
// Red
|
||||
colVal(red16(c));
|
||||
// Green
|
||||
colVal(green16(c));
|
||||
// Blue
|
||||
colVal(blue16(c));
|
||||
// ColorPreview
|
||||
ImGui::TableNextColumn();
|
||||
const auto ic = ImGui::GetColorU32(ImVec4(redf(c), greenf(c), bluef(c), 1));
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
|
||||
if (ImGui::Selectable("##ColorRow", i == m_selectedRow, ImGuiSelectableFlags_SpanAllColumns)) {
|
||||
m_selectedRow = i;
|
||||
}
|
||||
ImGui::PopID();
|
||||
++i;
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
if (m_selectedRow < m_pal.colors.size()) {
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginChild("ColorEditor", ImVec2(200, paneSize.y), true);
|
||||
{
|
||||
const auto c = m_pal.colors[m_selectedRow];
|
||||
int r = red16(c);
|
||||
int g = green16(c);
|
||||
int b = blue16(c);
|
||||
int const a = alpha16(c);
|
||||
ImGui::InputInt("Red", &r, 1, 5);
|
||||
ImGui::InputInt("Green", &g, 1, 5);
|
||||
ImGui::InputInt("Blue", &b, 1, 5);
|
||||
const auto newColor = color16(r, g, b, a);
|
||||
if (c != newColor) {
|
||||
undoStack()->push(ox::make_unique<UpdateColorCommand>(&m_pal, static_cast<int>(m_selectedRow), c, newColor));
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
ox::Error PaletteEditorImGui::saveItem() noexcept {
|
||||
const auto sctx = applicationData<studio::StudioContext>(m_ctx);
|
||||
return sctx->project->writeObj(itemPath(), m_pal);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
#include <nostalgia/core/palette.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class PaletteEditorImGui: public studio::Editor {
|
||||
|
||||
private:
|
||||
turbine::Context &m_ctx;
|
||||
Palette m_pal;
|
||||
std::size_t m_selectedRow = 0;
|
||||
|
||||
public:
|
||||
PaletteEditorImGui(turbine::Context &ctx, ox::CRStringView path);
|
||||
|
||||
void draw(turbine::Context&) noexcept final;
|
||||
|
||||
protected:
|
||||
ox::Error saveItem() noexcept final;
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "paletteeditor.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
AddColorCommand::AddColorCommand(Palette *pal, Color16 color, int idx) noexcept {
|
||||
m_pal = pal;
|
||||
m_color = color;
|
||||
m_idx = idx;
|
||||
}
|
||||
|
||||
int AddColorCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::AddColor);
|
||||
}
|
||||
|
||||
void AddColorCommand::redo() noexcept {
|
||||
m_pal->colors.insert(static_cast<std::size_t>(m_idx), m_color);
|
||||
}
|
||||
|
||||
void AddColorCommand::undo() noexcept {
|
||||
oxIgnoreError(m_pal->colors.erase(static_cast<std::size_t>(m_idx)));
|
||||
}
|
||||
|
||||
|
||||
RemoveColorCommand::RemoveColorCommand(Palette *pal, Color16 color, int idx) noexcept {
|
||||
m_pal = pal;
|
||||
m_color = color;
|
||||
m_idx = idx;
|
||||
}
|
||||
|
||||
int RemoveColorCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::RemoveColor);
|
||||
}
|
||||
|
||||
void RemoveColorCommand::redo() noexcept {
|
||||
oxIgnoreError(m_pal->colors.erase(static_cast<std::size_t>(m_idx)));
|
||||
}
|
||||
|
||||
void RemoveColorCommand::undo() noexcept {
|
||||
m_pal->colors.insert(static_cast<std::size_t>(m_idx), m_color);
|
||||
}
|
||||
|
||||
|
||||
UpdateColorCommand::UpdateColorCommand(Palette *pal, int idx, Color16 oldColor, Color16 newColor) noexcept {
|
||||
m_pal = pal;
|
||||
m_idx = idx;
|
||||
m_oldColor = oldColor;
|
||||
m_newColor = newColor;
|
||||
//setObsolete(m_oldColor == m_newColor);
|
||||
}
|
||||
|
||||
bool UpdateColorCommand::mergeWith(const UndoCommand *cmd) noexcept {
|
||||
if (cmd->commandId() != static_cast<int>(PaletteEditorCommandId::UpdateColor)) {
|
||||
return false;
|
||||
}
|
||||
auto ucCmd = static_cast<const UpdateColorCommand*>(cmd);
|
||||
if (m_idx != ucCmd->m_idx) {
|
||||
return false;
|
||||
}
|
||||
m_newColor = ucCmd->m_newColor;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
int UpdateColorCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::UpdateColor);
|
||||
}
|
||||
|
||||
void UpdateColorCommand::redo() noexcept {
|
||||
m_pal->colors[static_cast<std::size_t>(m_idx)] = m_newColor;
|
||||
}
|
||||
|
||||
void UpdateColorCommand::undo() noexcept {
|
||||
m_pal->colors[static_cast<std::size_t>(m_idx)] = m_oldColor;
|
||||
}
|
||||
|
||||
|
||||
MoveColorCommand::MoveColorCommand(Palette *pal, std::size_t idx, int offset) noexcept {
|
||||
m_pal = pal;
|
||||
m_idx = idx;
|
||||
m_offset = offset;
|
||||
}
|
||||
|
||||
int MoveColorCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::MoveColor);
|
||||
}
|
||||
|
||||
void MoveColorCommand::redo() noexcept {
|
||||
moveColor(static_cast<int>(m_idx), m_offset);
|
||||
}
|
||||
|
||||
void MoveColorCommand::undo() noexcept {
|
||||
moveColor(static_cast<int>(m_idx) + m_offset, -m_offset);
|
||||
}
|
||||
|
||||
void MoveColorCommand::moveColor(int idx, int offset) noexcept {
|
||||
const auto c = m_pal->colors[static_cast<std::size_t>(idx)];
|
||||
oxIgnoreError(m_pal->colors.erase(static_cast<std::size_t>(idx)));
|
||||
m_pal->colors.insert(static_cast<std::size_t>(idx + offset), c);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include <nostalgia/core/color.hpp>
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
#include <nostalgia/core/palette.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
enum class PaletteEditorCommandId {
|
||||
AddColor,
|
||||
RemoveColor,
|
||||
UpdateColor,
|
||||
MoveColor,
|
||||
};
|
||||
|
||||
|
||||
class AddColorCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette *m_pal = nullptr;
|
||||
Color16 m_color = 0;
|
||||
int m_idx = -1;
|
||||
|
||||
public:
|
||||
AddColorCommand(Palette *pal, Color16 color, int idx) noexcept;
|
||||
|
||||
~AddColorCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept override;
|
||||
|
||||
void redo() noexcept override;
|
||||
|
||||
void undo() noexcept override;
|
||||
|
||||
};
|
||||
|
||||
class RemoveColorCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette *m_pal = nullptr;
|
||||
Color16 m_color = 0;
|
||||
int m_idx = -1;
|
||||
|
||||
public:
|
||||
RemoveColorCommand(Palette *pal, Color16 color, int idx) noexcept;
|
||||
|
||||
~RemoveColorCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept override;
|
||||
|
||||
void redo() noexcept override;
|
||||
|
||||
void undo() noexcept override;
|
||||
|
||||
};
|
||||
|
||||
class UpdateColorCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette *m_pal = nullptr;
|
||||
Color16 m_oldColor = 0;
|
||||
Color16 m_newColor = 0;
|
||||
int m_idx = -1;
|
||||
|
||||
public:
|
||||
UpdateColorCommand(Palette *pal, int idx, Color16 oldColor, Color16 newColor) noexcept;
|
||||
|
||||
~UpdateColorCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
bool mergeWith(const UndoCommand *cmd) noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
void redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
|
||||
};
|
||||
|
||||
class MoveColorCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette *m_pal = nullptr;
|
||||
std::size_t m_idx = 0;
|
||||
int m_offset = 0;
|
||||
|
||||
public:
|
||||
MoveColorCommand(Palette *pal, std::size_t idx, int offset) noexcept;
|
||||
|
||||
~MoveColorCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept override;
|
||||
|
||||
public:
|
||||
void redo() noexcept override;
|
||||
|
||||
void undo() noexcept override;
|
||||
|
||||
private:
|
||||
void moveColor(int idx, int offset) noexcept;
|
||||
};
|
||||
|
||||
}
|
35
src/nostalgia/modules/core/src/studio/studiomodule.cpp
Normal file
35
src/nostalgia/modules/core/src/studio/studiomodule.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/std/memory.hpp>
|
||||
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include "paletteeditor/paletteeditor-imgui.hpp"
|
||||
#include "tilesheeteditor/tilesheeteditor-imgui.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class StudioModule: public studio::Module {
|
||||
ox::Vector<studio::EditorMaker> editors(turbine::Context &ctx) const noexcept final {
|
||||
return {
|
||||
studio::editorMaker<TileSheetEditorImGui>(ctx, FileExt_ng),
|
||||
studio::editorMaker<PaletteEditorImGui>(ctx, FileExt_npal),
|
||||
};
|
||||
}
|
||||
|
||||
ox::Vector<ox::UPtr<studio::ItemMaker>> itemMakers(turbine::Context&) const noexcept final {
|
||||
ox::Vector<ox::UniquePtr<studio::ItemMaker>> out;
|
||||
out.emplace_back(ox::make<studio::ItemMakerT<core::TileSheet>>("Tile Sheet", "TileSheets", FileExt_ng));
|
||||
out.emplace_back(ox::make<studio::ItemMakerT<core::Palette>>("Palette", "Palettes", FileExt_npal));
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
static StudioModule mod;
|
||||
const studio::Module *studioModule() noexcept {
|
||||
return &mod;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
target_sources(
|
||||
NostalgiaCore-Studio PRIVATE
|
||||
tilesheeteditorview.cpp
|
||||
tilesheeteditormodel.cpp
|
||||
tilesheetpixelgrid.cpp
|
||||
tilesheetpixels.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
NostalgiaCore-Studio-ImGui PUBLIC
|
||||
lodepng
|
||||
)
|
||||
|
||||
add_subdirectory(commands)
|
@ -0,0 +1,11 @@
|
||||
target_sources(
|
||||
NostalgiaCore-Studio PRIVATE
|
||||
addsubsheetcommand.cpp
|
||||
cutpastecommand.cpp
|
||||
deletetilescommand.cpp
|
||||
drawcommand.cpp
|
||||
inserttilescommand.cpp
|
||||
palettechangecommand.cpp
|
||||
rmsubsheetcommand.cpp
|
||||
updatesubsheetcommand.cpp
|
||||
)
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "addsubsheetcommand.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
AddSubSheetCommand::AddSubSheetCommand(
|
||||
TileSheet &img,
|
||||
TileSheet::SubSheetIdx parentIdx) noexcept:
|
||||
m_img(img), m_parentIdx(std::move(parentIdx)) {
|
||||
auto &parent = m_img.getSubSheet(m_parentIdx);
|
||||
if (!parent.subsheets.empty()) {
|
||||
auto idx = m_parentIdx;
|
||||
idx.emplace_back(parent.subsheets.size());
|
||||
m_addedSheets.push_back(idx);
|
||||
} else {
|
||||
auto idx = m_parentIdx;
|
||||
idx.emplace_back(0u);
|
||||
m_addedSheets.push_back(idx);
|
||||
*idx.back().value = 1;
|
||||
m_addedSheets.push_back(idx);
|
||||
}
|
||||
}
|
||||
|
||||
void AddSubSheetCommand::redo() noexcept {
|
||||
auto &parent = m_img.getSubSheet(m_parentIdx);
|
||||
if (m_addedSheets.size() < 2) {
|
||||
auto i = parent.subsheets.size();
|
||||
parent.subsheets.emplace_back(m_img.idIt++, ox::sfmt("Subsheet {}", i), 1, 1, m_img.bpp);
|
||||
} else {
|
||||
parent.subsheets.emplace_back(m_img.idIt++, "Subsheet 0", parent.columns, parent.rows, std::move(parent.pixels));
|
||||
parent.rows = 0;
|
||||
parent.columns = 0;
|
||||
parent.subsheets.emplace_back(m_img.idIt++, "Subsheet 1", 1, 1, m_img.bpp);
|
||||
}
|
||||
}
|
||||
|
||||
void AddSubSheetCommand::undo() noexcept {
|
||||
auto &parent = m_img.getSubSheet(m_parentIdx);
|
||||
if (parent.subsheets.size() == 2) {
|
||||
auto s = parent.subsheets[0];
|
||||
parent.rows = s.rows;
|
||||
parent.columns = s.columns;
|
||||
parent.pixels = std::move(s.pixels);
|
||||
parent.subsheets.clear();
|
||||
} else {
|
||||
for (auto idx = m_addedSheets.rbegin(); idx != m_addedSheets.rend(); ++idx) {
|
||||
oxLogError(m_img.rmSubSheet(*idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int AddSubSheetCommand::commandId() const noexcept {
|
||||
return static_cast<int>(CommandId::AddSubSheet);
|
||||
}
|
||||
|
||||
TileSheet::SubSheetIdx const&AddSubSheetCommand::subsheetIdx() const noexcept {
|
||||
return m_parentIdx;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "commands.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class AddSubSheetCommand: public TileSheetCommand {
|
||||
private:
|
||||
TileSheet &m_img;
|
||||
TileSheet::SubSheetIdx m_parentIdx;
|
||||
ox::Vector<TileSheet::SubSheetIdx, 2> m_addedSheets;
|
||||
|
||||
public:
|
||||
AddSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx parentIdx) noexcept;
|
||||
|
||||
void redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <studio/undostack.hpp>
|
||||
#include <nostalgia/core/tilesheet.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
// Command IDs to use with QUndoCommand::id()
|
||||
enum class CommandId {
|
||||
Draw = 1,
|
||||
AddSubSheet = 2,
|
||||
RmSubSheet = 3,
|
||||
DeleteTile = 4,
|
||||
InsertTile = 4,
|
||||
UpdateSubSheet = 5,
|
||||
Cut = 6,
|
||||
Paste = 7,
|
||||
PaletteChange = 8,
|
||||
};
|
||||
|
||||
constexpr bool operator==(CommandId c, int i) noexcept {
|
||||
return static_cast<int>(c) == i;
|
||||
}
|
||||
|
||||
constexpr bool operator==(int i, CommandId c) noexcept {
|
||||
return static_cast<int>(c) == i;
|
||||
}
|
||||
|
||||
class TileSheetCommand: public studio::UndoCommand {
|
||||
public:
|
||||
[[nodiscard]]
|
||||
virtual TileSheet::SubSheetIdx const&subsheetIdx() const noexcept = 0;
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "cutpastecommand.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
TileSheetClipboard::Pixel::Pixel(uint16_t pColorIdx, ox::Point pPt) noexcept {
|
||||
colorIdx = pColorIdx;
|
||||
pt = pPt;
|
||||
}
|
||||
|
||||
|
||||
void TileSheetClipboard::addPixel(const ox::Point &pt, uint16_t colorIdx) noexcept {
|
||||
m_pixels.emplace_back(colorIdx, pt);
|
||||
}
|
||||
|
||||
const ox::Vector<TileSheetClipboard::Pixel> &TileSheetClipboard::pixels() const noexcept {
|
||||
return m_pixels;
|
||||
}
|
||||
|
||||
|
||||
CutPasteCommand::CutPasteCommand(
|
||||
CommandId commandId,
|
||||
TileSheet &img,
|
||||
TileSheet::SubSheetIdx subSheetIdx,
|
||||
const ox::Point &dstStart,
|
||||
const ox::Point &dstEnd,
|
||||
const TileSheetClipboard &cb) noexcept:
|
||||
m_commandId(commandId),
|
||||
m_img(img),
|
||||
m_subSheetIdx(std::move(subSheetIdx)) {
|
||||
const auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
|
||||
for (const auto &p : cb.pixels()) {
|
||||
const auto dstPt = p.pt + dstStart;
|
||||
if (dstPt.x <= dstEnd.x && dstPt.y <= dstEnd.y) {
|
||||
const auto idx = subsheet.idx(dstPt);
|
||||
m_changes.emplace_back(static_cast<uint32_t>(idx), p.colorIdx, subsheet.getPixel(m_img.bpp, idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CutPasteCommand::redo() noexcept {
|
||||
auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
|
||||
for (const auto &c : m_changes) {
|
||||
subsheet.setPixel(m_img.bpp, c.idx, static_cast<uint8_t>(c.newPalIdx));
|
||||
}
|
||||
}
|
||||
|
||||
void CutPasteCommand::undo() noexcept {
|
||||
auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
|
||||
for (const auto &c : m_changes) {
|
||||
subsheet.setPixel(m_img.bpp, c.idx, static_cast<uint8_t>(c.oldPalIdx));
|
||||
}
|
||||
}
|
||||
|
||||
int CutPasteCommand::commandId() const noexcept {
|
||||
return static_cast<int>(m_commandId);
|
||||
}
|
||||
|
||||
TileSheet::SubSheetIdx const&CutPasteCommand::subsheetIdx() const noexcept {
|
||||
return m_subSheetIdx;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <turbine/clipboard.hpp>
|
||||
|
||||
#include "commands.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class TileSheetClipboard: public turbine::ClipboardObject<TileSheetClipboard> {
|
||||
public:
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetClipboard";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
|
||||
oxModelFriend(TileSheetClipboard);
|
||||
|
||||
struct Pixel {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetClipboard.Pixel";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
uint16_t colorIdx = 0;
|
||||
ox::Point pt;
|
||||
Pixel(uint16_t pColorIdx, ox::Point pPt) noexcept;
|
||||
};
|
||||
protected:
|
||||
ox::Vector<Pixel> m_pixels;
|
||||
|
||||
public:
|
||||
void addPixel(ox::Point const&pt, uint16_t colorIdx) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Vector<Pixel> const&pixels() const noexcept;
|
||||
};
|
||||
|
||||
oxModelBegin(TileSheetClipboard::Pixel)
|
||||
oxModelField(colorIdx)
|
||||
oxModelField(pt)
|
||||
oxModelEnd()
|
||||
|
||||
oxModelBegin(TileSheetClipboard)
|
||||
oxModelFieldRename(pixels, m_pixels)
|
||||
oxModelEnd()
|
||||
|
||||
class CutPasteCommand: public TileSheetCommand {
|
||||
private:
|
||||
struct Change {
|
||||
uint32_t idx = 0;
|
||||
uint16_t newPalIdx = 0;
|
||||
uint16_t oldPalIdx = 0;
|
||||
constexpr Change(uint32_t pIdx, uint16_t pNewPalIdx, uint16_t pOldPalIdx) noexcept {
|
||||
idx = pIdx;
|
||||
newPalIdx = pNewPalIdx;
|
||||
oldPalIdx = pOldPalIdx;
|
||||
}
|
||||
};
|
||||
CommandId m_commandId;
|
||||
TileSheet &m_img;
|
||||
TileSheet::SubSheetIdx m_subSheetIdx;
|
||||
ox::Vector<Change> m_changes;
|
||||
|
||||
public:
|
||||
CutPasteCommand(
|
||||
CommandId commandId,
|
||||
TileSheet &img,
|
||||
TileSheet::SubSheetIdx subSheetIdx,
|
||||
ox::Point const&dstStart,
|
||||
ox::Point const&dstEnd,
|
||||
TileSheetClipboard const&cb) noexcept;
|
||||
|
||||
void redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "deletetilescommand.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
core::DeleteTilesCommand::DeleteTilesCommand(
|
||||
TileSheet &img,
|
||||
TileSheet::SubSheetIdx idx,
|
||||
std::size_t tileIdx,
|
||||
std::size_t tileCnt) noexcept:
|
||||
m_img(img),
|
||||
m_idx(std::move(idx)) {
|
||||
const unsigned bytesPerTile = m_img.bpp == 4 ? PixelsPerTile / 2 : PixelsPerTile;
|
||||
m_deletePos = tileIdx * bytesPerTile;
|
||||
m_deleteSz = tileCnt * bytesPerTile;
|
||||
m_deletedPixels.resize(m_deleteSz);
|
||||
// copy pixels to be erased
|
||||
{
|
||||
auto &s = m_img.getSubSheet(m_idx);
|
||||
auto &p = s.pixels;
|
||||
auto dst = m_deletedPixels.data();
|
||||
auto src = p.data() + m_deletePos;
|
||||
const auto sz = m_deleteSz * sizeof(decltype(p[0]));
|
||||
ox_memcpy(dst, src, sz);
|
||||
}
|
||||
}
|
||||
|
||||
void core::DeleteTilesCommand::redo() noexcept {
|
||||
auto &s = m_img.getSubSheet(m_idx);
|
||||
auto &p = s.pixels;
|
||||
auto srcPos = m_deletePos + m_deleteSz;
|
||||
const auto src = p.data() + srcPos;
|
||||
const auto dst1 = p.data() + m_deletePos;
|
||||
const auto dst2 = p.data() + (p.size() - m_deleteSz);
|
||||
ox_memmove(dst1, src, p.size() - srcPos);
|
||||
ox_memset(dst2, 0, m_deleteSz * sizeof(decltype(p[0])));
|
||||
}
|
||||
|
||||
void DeleteTilesCommand::undo() noexcept {
|
||||
auto &s = m_img.getSubSheet(m_idx);
|
||||
auto &p = s.pixels;
|
||||
const auto src = p.data() + m_deletePos;
|
||||
const auto dst1 = p.data() + m_deletePos + m_deleteSz;
|
||||
const auto dst2 = src;
|
||||
const auto sz = p.size() - m_deletePos - m_deleteSz;
|
||||
ox_memmove(dst1, src, sz);
|
||||
ox_memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size());
|
||||
}
|
||||
|
||||
int DeleteTilesCommand::commandId() const noexcept {
|
||||
return static_cast<int>(CommandId::DeleteTile);
|
||||
}
|
||||
|
||||
TileSheet::SubSheetIdx const&DeleteTilesCommand::subsheetIdx() const noexcept {
|
||||
return m_idx;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "commands.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class DeleteTilesCommand: public TileSheetCommand {
|
||||
private:
|
||||
TileSheet &m_img;
|
||||
TileSheet::SubSheetIdx m_idx;
|
||||
std::size_t m_deletePos = 0;
|
||||
std::size_t m_deleteSz = 0;
|
||||
ox::Vector<uint8_t> m_deletedPixels = {};
|
||||
|
||||
public:
|
||||
DeleteTilesCommand(
|
||||
TileSheet &img,
|
||||
TileSheet::SubSheetIdx idx,
|
||||
std::size_t tileIdx,
|
||||
std::size_t tileCnt) noexcept;
|
||||
|
||||
void redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "drawcommand.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
DrawCommand::DrawCommand(
|
||||
TileSheet &img,
|
||||
TileSheet::SubSheetIdx subSheetIdx,
|
||||
std::size_t idx,
|
||||
int palIdx) noexcept:
|
||||
m_img(img),
|
||||
m_subSheetIdx(std::move(subSheetIdx)),
|
||||
m_palIdx(palIdx) {
|
||||
auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
|
||||
m_changes.emplace_back(static_cast<uint32_t>(idx), subsheet.getPixel(m_img.bpp, idx));
|
||||
}
|
||||
|
||||
DrawCommand::DrawCommand(
|
||||
TileSheet &img,
|
||||
TileSheet::SubSheetIdx subSheetIdx,
|
||||
const ox::Vector<std::size_t> &idxList,
|
||||
int palIdx) noexcept:
|
||||
m_img(img),
|
||||
m_subSheetIdx(std::move(subSheetIdx)),
|
||||
m_palIdx(palIdx) {
|
||||
auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
|
||||
for (const auto idx : idxList) {
|
||||
m_changes.emplace_back(static_cast<uint32_t>(idx), subsheet.getPixel(m_img.bpp, idx));
|
||||
}
|
||||
}
|
||||
|
||||
bool DrawCommand::append(std::size_t idx) noexcept {
|
||||
auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
|
||||
if (m_changes.back().value->idx != idx && subsheet.getPixel(m_img.bpp, idx) != m_palIdx) {
|
||||
// duplicate entries are bad
|
||||
auto existing = ox::find_if(m_changes.cbegin(), m_changes.cend(), [idx](const auto &c) {
|
||||
return c.idx == idx;
|
||||
});
|
||||
if (existing == m_changes.cend()) {
|
||||
m_changes.emplace_back(static_cast<uint32_t>(idx), subsheet.getPixel(m_img.bpp, idx));
|
||||
subsheet.setPixel(m_img.bpp, idx, static_cast<uint8_t>(m_palIdx));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DrawCommand::append(const ox::Vector<std::size_t> &idxList) noexcept {
|
||||
auto out = false;
|
||||
for (auto idx : idxList) {
|
||||
out = append(idx) || out;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void DrawCommand::redo() noexcept {
|
||||
auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
|
||||
for (const auto &c : m_changes) {
|
||||
subsheet.setPixel(m_img.bpp, c.idx, static_cast<uint8_t>(m_palIdx));
|
||||
}
|
||||
}
|
||||
|
||||
void DrawCommand::undo() noexcept {
|
||||
auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
|
||||
for (const auto &c : m_changes) {
|
||||
subsheet.setPixel(m_img.bpp, c.idx, static_cast<uint8_t>(c.oldPalIdx));
|
||||
}
|
||||
}
|
||||
|
||||
int DrawCommand::commandId() const noexcept {
|
||||
return static_cast<int>(CommandId::Draw);
|
||||
}
|
||||
|
||||
TileSheet::SubSheetIdx const&DrawCommand::subsheetIdx() const noexcept {
|
||||
return m_subSheetIdx;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "commands.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class DrawCommand: public TileSheetCommand {
|
||||
private:
|
||||
struct Change {
|
||||
uint32_t idx = 0;
|
||||
uint16_t oldPalIdx = 0;
|
||||
constexpr Change(uint32_t pIdx, uint16_t pOldPalIdx) noexcept {
|
||||
idx = pIdx;
|
||||
oldPalIdx = pOldPalIdx;
|
||||
}
|
||||
};
|
||||
TileSheet &m_img;
|
||||
TileSheet::SubSheetIdx m_subSheetIdx;
|
||||
ox::Vector<Change, 8> m_changes;
|
||||
int m_palIdx = 0;
|
||||
|
||||
public:
|
||||
DrawCommand(
|
||||
TileSheet &img,
|
||||
TileSheet::SubSheetIdx subSheetIdx,
|
||||
std::size_t idx,
|
||||
int palIdx) noexcept;
|
||||
|
||||
DrawCommand(
|
||||
TileSheet &img,
|
||||
TileSheet::SubSheetIdx subSheetIdx,
|
||||
const ox::Vector<std::size_t> &idxList,
|
||||
int palIdx) noexcept;
|
||||
|
||||
bool append(std::size_t idx) noexcept;
|
||||
|
||||
bool append(const ox::Vector<std::size_t> &idxList) noexcept;
|
||||
|
||||
void redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "inserttilescommand.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
core::InsertTilesCommand::InsertTilesCommand(
|
||||
TileSheet &img,
|
||||
TileSheet::SubSheetIdx idx,
|
||||
std::size_t tileIdx,
|
||||
std::size_t tileCnt) noexcept:
|
||||
m_img(img),
|
||||
m_idx(std::move(idx)) {
|
||||
const unsigned bytesPerTile = m_img.bpp == 4 ? PixelsPerTile / 2 : PixelsPerTile;
|
||||
m_insertPos = tileIdx * bytesPerTile;
|
||||
m_insertCnt = tileCnt * bytesPerTile;
|
||||
m_deletedPixels.resize(m_insertCnt);
|
||||
// copy pixels to be erased
|
||||
{
|
||||
auto &s = m_img.getSubSheet(m_idx);
|
||||
auto &p = s.pixels;
|
||||
auto dst = m_deletedPixels.data();
|
||||
auto src = p.data() + p.size() - m_insertCnt;
|
||||
const auto sz = m_insertCnt * sizeof(decltype(p[0]));
|
||||
ox_memcpy(dst, src, sz);
|
||||
}
|
||||
}
|
||||
|
||||
void InsertTilesCommand::redo() noexcept {
|
||||
auto &s = m_img.getSubSheet(m_idx);
|
||||
auto &p = s.pixels;
|
||||
auto dstPos = m_insertPos + m_insertCnt;
|
||||
const auto dst = p.data() + dstPos;
|
||||
const auto src = p.data() + m_insertPos;
|
||||
ox_memmove(dst, src, p.size() - dstPos);
|
||||
ox_memset(src, 0, m_insertCnt * sizeof(decltype(p[0])));
|
||||
}
|
||||
|
||||
void InsertTilesCommand::undo() noexcept {
|
||||
auto &s = m_img.getSubSheet(m_idx);
|
||||
auto &p = s.pixels;
|
||||
const auto srcIdx = m_insertPos + m_insertCnt;
|
||||
const auto src = p.data() + srcIdx;
|
||||
const auto dst1 = p.data() + m_insertPos;
|
||||
const auto dst2 = p.data() + p.size() - m_insertCnt;
|
||||
const auto sz = p.size() - srcIdx;
|
||||
ox_memmove(dst1, src, sz);
|
||||
ox_memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size());
|
||||
}
|
||||
|
||||
int InsertTilesCommand::commandId() const noexcept {
|
||||
return static_cast<int>(CommandId::InsertTile);
|
||||
}
|
||||
|
||||
TileSheet::SubSheetIdx const&InsertTilesCommand::subsheetIdx() const noexcept {
|
||||
return m_idx;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "commands.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class InsertTilesCommand: public TileSheetCommand {
|
||||
private:
|
||||
TileSheet &m_img;
|
||||
TileSheet::SubSheetIdx m_idx;
|
||||
std::size_t m_insertPos = 0;
|
||||
std::size_t m_insertCnt = 0;
|
||||
ox::Vector<uint8_t> m_deletedPixels = {};
|
||||
|
||||
public:
|
||||
InsertTilesCommand(
|
||||
TileSheet &img,
|
||||
TileSheet::SubSheetIdx idx,
|
||||
std::size_t tileIdx,
|
||||
std::size_t tileCnt) noexcept;
|
||||
|
||||
void redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "palettechangecommand.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
core::PaletteChangeCommand::PaletteChangeCommand(
|
||||
TileSheet::SubSheetIdx idx,
|
||||
TileSheet &img,
|
||||
ox::CRStringView newPalette) noexcept:
|
||||
m_img(img),
|
||||
m_idx(std::move(idx)),
|
||||
m_oldPalette(m_img.defaultPalette),
|
||||
m_newPalette(ox::FileAddress(ox::sfmt<ox::BString<43>>("uuid://{}", newPalette))) {
|
||||
}
|
||||
|
||||
void PaletteChangeCommand::redo() noexcept {
|
||||
m_img.defaultPalette = m_newPalette;
|
||||
}
|
||||
|
||||
void PaletteChangeCommand::undo() noexcept {
|
||||
m_img.defaultPalette = m_oldPalette;
|
||||
}
|
||||
|
||||
int PaletteChangeCommand::commandId() const noexcept {
|
||||
return static_cast<int>(CommandId::PaletteChange);
|
||||
}
|
||||
|
||||
TileSheet::SubSheetIdx const&PaletteChangeCommand::subsheetIdx() const noexcept {
|
||||
return m_idx;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "commands.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class PaletteChangeCommand: public TileSheetCommand {
|
||||
private:
|
||||
TileSheet &m_img;
|
||||
TileSheet::SubSheetIdx m_idx;
|
||||
ox::FileAddress m_oldPalette;
|
||||
ox::FileAddress m_newPalette;
|
||||
|
||||
public:
|
||||
PaletteChangeCommand(
|
||||
TileSheet::SubSheetIdx idx,
|
||||
TileSheet &img,
|
||||
ox::CRStringView newPalette) noexcept;
|
||||
|
||||
void redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "rmsubsheetcommand.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
core::RmSubSheetCommand::RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx idx) noexcept:
|
||||
m_img(img),
|
||||
m_idx(std::move(idx)),
|
||||
m_parentIdx(m_idx) {
|
||||
m_parentIdx.pop_back();
|
||||
auto &parent = m_img.getSubSheet(m_parentIdx);
|
||||
m_sheet = parent.subsheets[*m_idx.back().value];
|
||||
}
|
||||
|
||||
void RmSubSheetCommand::redo() noexcept {
|
||||
auto &parent = m_img.getSubSheet(m_parentIdx);
|
||||
oxLogError(parent.subsheets.erase(*m_idx.back().value).error);
|
||||
}
|
||||
|
||||
void RmSubSheetCommand::undo() noexcept {
|
||||
auto &parent = m_img.getSubSheet(m_parentIdx);
|
||||
auto i = *m_idx.back().value;
|
||||
parent.subsheets.insert(i, m_sheet);
|
||||
}
|
||||
|
||||
int RmSubSheetCommand::commandId() const noexcept {
|
||||
return static_cast<int>(CommandId::RmSubSheet);
|
||||
}
|
||||
|
||||
TileSheet::SubSheetIdx const&RmSubSheetCommand::subsheetIdx() const noexcept {
|
||||
return m_idx;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "commands.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class RmSubSheetCommand: public TileSheetCommand {
|
||||
private:
|
||||
TileSheet &m_img;
|
||||
TileSheet::SubSheetIdx m_idx;
|
||||
TileSheet::SubSheetIdx m_parentIdx;
|
||||
TileSheet::SubSheet m_sheet;
|
||||
|
||||
public:
|
||||
RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx idx) noexcept;
|
||||
|
||||
void redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "updatesubsheetcommand.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
core::UpdateSubSheetCommand::UpdateSubSheetCommand(
|
||||
TileSheet &img,
|
||||
TileSheet::SubSheetIdx idx,
|
||||
ox::String name,
|
||||
int cols,
|
||||
int rows) noexcept:
|
||||
m_img(img),
|
||||
m_idx(std::move(idx)),
|
||||
m_sheet(m_img.getSubSheet(m_idx)),
|
||||
m_newName(std::move(name)),
|
||||
m_newCols(cols),
|
||||
m_newRows(rows) {
|
||||
}
|
||||
|
||||
void UpdateSubSheetCommand::redo() noexcept {
|
||||
auto &sheet = m_img.getSubSheet(m_idx);
|
||||
sheet.name = m_newName;
|
||||
sheet.columns = m_newCols;
|
||||
sheet.rows = m_newRows;
|
||||
oxLogError(sheet.setPixelCount(m_img.bpp, static_cast<std::size_t>(PixelsPerTile * m_newCols * m_newRows)));
|
||||
}
|
||||
|
||||
void UpdateSubSheetCommand::undo() noexcept {
|
||||
auto &sheet = m_img.getSubSheet(m_idx);
|
||||
sheet = m_sheet;
|
||||
}
|
||||
|
||||
int UpdateSubSheetCommand::commandId() const noexcept {
|
||||
return static_cast<int>(CommandId::UpdateSubSheet);
|
||||
}
|
||||
|
||||
TileSheet::SubSheetIdx const&UpdateSubSheetCommand::subsheetIdx() const noexcept {
|
||||
return m_idx;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "commands.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class UpdateSubSheetCommand: public TileSheetCommand {
|
||||
private:
|
||||
TileSheet &m_img;
|
||||
TileSheet::SubSheetIdx m_idx;
|
||||
TileSheet::SubSheet m_sheet;
|
||||
ox::String m_newName;
|
||||
int m_newCols = 0;
|
||||
int m_newRows = 0;
|
||||
|
||||
public:
|
||||
UpdateSubSheetCommand(
|
||||
TileSheet &img,
|
||||
TileSheet::SubSheetIdx idx,
|
||||
ox::String name,
|
||||
int cols,
|
||||
int rows) noexcept;
|
||||
|
||||
void redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,512 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <imgui.h>
|
||||
#include <lodepng.h>
|
||||
|
||||
#include <ox/std/point.hpp>
|
||||
#include <keel/media.hpp>
|
||||
|
||||
#include "tilesheeteditor-imgui.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
static ox::Vector<uint32_t> normalizePixelSizes(
|
||||
ox::Vector<uint8_t> const&inPixels,
|
||||
int const bpp) noexcept {
|
||||
uint_t const bytesPerTile = bpp == 8 ? PixelsPerTile : PixelsPerTile / 2;
|
||||
ox::Vector<uint32_t> outPixels;
|
||||
if (bytesPerTile == 64) { // 8 BPP
|
||||
outPixels.resize(inPixels.size());
|
||||
for (std::size_t i = 0; i < inPixels.size(); ++i) {
|
||||
outPixels[i] = inPixels[i];
|
||||
}
|
||||
} else { // 4 BPP
|
||||
outPixels.resize(inPixels.size() * 2);
|
||||
for (std::size_t i = 0; i < inPixels.size(); ++i) {
|
||||
outPixels[i * 2 + 0] = inPixels[i] & 0xF;
|
||||
outPixels[i * 2 + 1] = inPixels[i] >> 4;
|
||||
}
|
||||
}
|
||||
return outPixels;
|
||||
}
|
||||
|
||||
static ox::Vector<uint32_t> normalizePixelArrangement(
|
||||
ox::Vector<uint32_t> const&inPixels,
|
||||
int cols,
|
||||
int scale) {
|
||||
auto const scalePt = ox::Point{scale, scale};
|
||||
auto const width = cols * TileWidth;
|
||||
auto const height = static_cast<int>(inPixels.size()) / width;
|
||||
auto const dstWidth = width * scale;
|
||||
ox::Vector<uint32_t> outPixels(static_cast<size_t>((width * scale) * (height * scale)));
|
||||
for (std::size_t dstIdx = 0; dstIdx < outPixels.size(); ++dstIdx) {
|
||||
auto const dstPt = ox::Point{
|
||||
static_cast<int>(dstIdx) % dstWidth,
|
||||
static_cast<int>(dstIdx) / dstWidth};
|
||||
auto const srcPt = dstPt / scalePt;
|
||||
auto const srcIdx = ptToIdx(srcPt, cols);
|
||||
outPixels[dstIdx] = inPixels[srcIdx];
|
||||
}
|
||||
return outPixels;
|
||||
}
|
||||
|
||||
static ox::Error toPngFile(
|
||||
ox::CStringView const&path,
|
||||
ox::Vector<uint32_t> &&pixels,
|
||||
Palette const&pal,
|
||||
unsigned width,
|
||||
unsigned height) noexcept {
|
||||
for (auto &c : pixels) {
|
||||
c = color32(pal.color(c)) | static_cast<Color32>(0XFF << 24);
|
||||
}
|
||||
constexpr auto fmt = LCT_RGBA;
|
||||
return OxError(static_cast<ox::ErrorCode>(
|
||||
lodepng_encode_file(
|
||||
path.c_str(),
|
||||
reinterpret_cast<uint8_t const*>(pixels.data()),
|
||||
width,
|
||||
height,
|
||||
fmt,
|
||||
8)));
|
||||
}
|
||||
|
||||
TileSheetEditorImGui::TileSheetEditorImGui(turbine::Context &ctx, ox::CRStringView path):
|
||||
Editor(path),
|
||||
m_ctx(ctx),
|
||||
m_view(m_ctx, path, *undoStack()),
|
||||
m_model(m_view.model()) {
|
||||
oxIgnoreError(setPaletteSelection());
|
||||
// connect signal/slots
|
||||
undoStack()->changeTriggered.connect(this, &TileSheetEditorImGui::markUnsavedChanges);
|
||||
m_subsheetEditor.inputSubmitted.connect(this, &TileSheetEditorImGui::updateActiveSubsheet);
|
||||
m_exportMenu.inputSubmitted.connect(this, &TileSheetEditorImGui::exportSubhseetToPng);
|
||||
m_model.paletteChanged.connect(this, &TileSheetEditorImGui::setPaletteSelection);
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::exportFile() {
|
||||
m_exportMenu.show();
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::cut() {
|
||||
m_model.cut();
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::copy() {
|
||||
m_model.copy();
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::paste() {
|
||||
m_model.paste();
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
|
||||
if (!down) {
|
||||
return;
|
||||
}
|
||||
if (key == turbine::Key::Escape) {
|
||||
m_subsheetEditor.close();
|
||||
m_exportMenu.close();
|
||||
}
|
||||
auto pal = m_model.pal();
|
||||
if (pal) {
|
||||
const auto colorCnt = pal->colors.size();
|
||||
if (key == turbine::Key::Alpha_D) {
|
||||
m_tool = Tool::Draw;
|
||||
setCopyEnabled(false);
|
||||
setCutEnabled(false);
|
||||
setPasteEnabled(false);
|
||||
m_model.clearSelection();
|
||||
} else if (key == turbine::Key::Alpha_S) {
|
||||
m_tool = Tool::Select;
|
||||
setCopyEnabled(true);
|
||||
setCutEnabled(true);
|
||||
setPasteEnabled(true);
|
||||
} else if (key == turbine::Key::Alpha_F) {
|
||||
m_tool = Tool::Fill;
|
||||
setCopyEnabled(false);
|
||||
setCutEnabled(false);
|
||||
setPasteEnabled(false);
|
||||
m_model.clearSelection();
|
||||
} else if (key >= turbine::Key::Num_1 && key <= turbine::Key::Num_9 && key <= turbine::Key::Num_0 + colorCnt) {
|
||||
auto idx = ox::min<std::size_t>(static_cast<uint32_t>(key - turbine::Key::Num_1), colorCnt - 1);
|
||||
m_view.setPalIdx(idx);
|
||||
} else if (key == turbine::Key::Num_0 && colorCnt >= 10) {
|
||||
auto idx = ox::min<std::size_t>(static_cast<uint32_t>(key - turbine::Key::Num_1 + 9), colorCnt - 1);
|
||||
m_view.setPalIdx(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::draw(turbine::Context&) noexcept {
|
||||
const auto paneSize = ImGui::GetContentRegionAvail();
|
||||
const auto tileSheetParentSize = ImVec2(paneSize.x - m_palViewWidth, paneSize.y);
|
||||
const auto fbSize = ox::Vec2(tileSheetParentSize.x - 16, tileSheetParentSize.y - 16);
|
||||
ImGui::BeginChild("TileSheetView", tileSheetParentSize, true);
|
||||
{
|
||||
drawTileSheet(fbSize);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginChild("Controls", ImVec2(m_palViewWidth - 8, paneSize.y), true);
|
||||
{
|
||||
const auto controlsSize = ImGui::GetContentRegionAvail();
|
||||
ImGui::BeginChild("ToolBox", ImVec2(m_palViewWidth - 24, 30), true);
|
||||
{
|
||||
const auto btnSz = ImVec2(45, 14);
|
||||
if (ImGui::Selectable("Select", m_tool == Tool::Select, 0, btnSz)) {
|
||||
m_tool = Tool::Select;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Selectable("Draw", m_tool == Tool::Draw, 0, btnSz)) {
|
||||
m_tool = Tool::Draw;
|
||||
m_model.clearSelection();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Selectable("Fill", m_tool == Tool::Fill, 0, btnSz)) {
|
||||
m_tool = Tool::Fill;
|
||||
m_model.clearSelection();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
const auto ySize = controlsSize.y - 38;
|
||||
// draw palette/color picker
|
||||
ImGui::BeginChild("Palette", ImVec2(m_palViewWidth - 24, ySize / 2.f), true);
|
||||
{
|
||||
drawPaletteSelector();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::BeginChild("SubSheets", ImVec2(m_palViewWidth - 24, ySize / 2.f), true);
|
||||
{
|
||||
static constexpr auto btnHeight = 18;
|
||||
const auto btnSize = ImVec2(18, btnHeight);
|
||||
if (ImGui::Button("+", btnSize)) {
|
||||
auto insertOnIdx = m_model.activeSubSheetIdx();
|
||||
const auto &parent = *m_model.activeSubSheet();
|
||||
m_model.addSubsheet(insertOnIdx);
|
||||
insertOnIdx.emplace_back(parent.subsheets.size() - 1);
|
||||
m_model.setActiveSubsheet(insertOnIdx);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("-", btnSize)) {
|
||||
const auto &activeSubsheetIdx = m_model.activeSubSheetIdx();
|
||||
if (activeSubsheetIdx.size() > 0) {
|
||||
m_model.rmSubsheet(activeSubsheetIdx);
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Edit", ImVec2(51, btnHeight))) {
|
||||
showSubsheetEditor();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Export", ImVec2(51, btnHeight))) {
|
||||
m_exportMenu.show();
|
||||
}
|
||||
TileSheet::SubSheetIdx path;
|
||||
static constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;
|
||||
if (ImGui::BeginTable("Subsheets", 3, flags)) {
|
||||
ImGui::TableSetupColumn("Subsheet", ImGuiTableColumnFlags_NoHide);
|
||||
ImGui::TableSetupColumn("Columns", ImGuiTableColumnFlags_WidthFixed, 50);
|
||||
ImGui::TableSetupColumn("Rows", ImGuiTableColumnFlags_WidthFixed, 50);
|
||||
ImGui::TableHeadersRow();
|
||||
drawSubsheetSelector(&m_view.img().subsheet, &path);
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
m_subsheetEditor.draw();
|
||||
m_exportMenu.draw();
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::drawSubsheetSelector(TileSheet::SubSheet *subsheet, TileSheet::SubSheetIdx *path) {
|
||||
ImGui::TableNextRow(0, 5);
|
||||
using Str = ox::BasicString<100>;
|
||||
auto pathStr = ox::join<Str>("##", *path).value;
|
||||
auto lbl = ox::sfmt<Str>("{}##{}", subsheet->name, pathStr);
|
||||
const auto rowSelected = *path == m_model.activeSubSheetIdx();
|
||||
const auto flags = ImGuiTreeNodeFlags_SpanFullWidth
|
||||
| ImGuiTreeNodeFlags_OpenOnArrow
|
||||
| ImGuiTreeNodeFlags_DefaultOpen
|
||||
| (subsheet->subsheets.empty() ? ImGuiTreeNodeFlags_Leaf : 0)
|
||||
| (rowSelected ? ImGuiTreeNodeFlags_Selected : 0);
|
||||
ImGui::TableNextColumn();
|
||||
const auto open = ImGui::TreeNodeEx(lbl.c_str(), flags);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::IsItemClicked()) {
|
||||
m_model.setActiveSubsheet(*path);
|
||||
}
|
||||
if (ImGui::IsMouseDoubleClicked(0) && ImGui::IsItemHovered()) {
|
||||
showSubsheetEditor();
|
||||
}
|
||||
if (subsheet->subsheets.empty()) {
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", subsheet->columns);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", subsheet->rows);
|
||||
} else {
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("--");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("--");
|
||||
}
|
||||
if (open) {
|
||||
for (auto i = 0ul; auto &child : subsheet->subsheets) {
|
||||
path->push_back(i);
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
drawSubsheetSelector(&child, path);
|
||||
ImGui::PopID();
|
||||
path->pop_back();
|
||||
++i;
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Vec2 TileSheetEditorImGui::clickPos(ImVec2 const&winPos, ox::Vec2 clickPos) noexcept {
|
||||
clickPos.x -= winPos.x + 10;
|
||||
clickPos.y -= winPos.y + 10;
|
||||
return clickPos;
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorImGui::saveItem() noexcept {
|
||||
return m_model.saveFile();
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::showSubsheetEditor() noexcept {
|
||||
const auto sheet = m_model.activeSubSheet();
|
||||
if (!sheet->subsheets.empty()) {
|
||||
m_subsheetEditor.show(sheet->name, -1, -1);
|
||||
} else {
|
||||
m_subsheetEditor.show(sheet->name, sheet->columns, sheet->rows);
|
||||
}
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorImGui::exportSubhseetToPng(int scale) noexcept {
|
||||
oxRequire(path, studio::saveFile({{"PNG", "png"}}));
|
||||
// subsheet to png
|
||||
auto const&img = m_model.img();
|
||||
auto const&s = *m_model.activeSubSheet();
|
||||
auto const&pal = m_model.pal();
|
||||
auto const width = s.columns * TileWidth;
|
||||
auto const height = s.rows * TileHeight;
|
||||
auto pixels = normalizePixelSizes(s.pixels, img.bpp);
|
||||
pixels = normalizePixelArrangement(pixels, s.columns, scale);
|
||||
auto const err = toPngFile(
|
||||
path,
|
||||
std::move(pixels),
|
||||
*pal,
|
||||
static_cast<unsigned>(width * scale),
|
||||
static_cast<unsigned>(height * scale));
|
||||
if (err) {
|
||||
oxErrorf("Tilesheet export failed: {}", toStr(err));
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
|
||||
const auto winPos = ImGui::GetWindowPos();
|
||||
const auto fbSizei = ox::Size(static_cast<int>(fbSize.x), static_cast<int>(fbSize.y));
|
||||
if (m_framebuffer.width != fbSizei.width || m_framebuffer.height != fbSizei.height) {
|
||||
glutils::resizeInitFrameBuffer(m_framebuffer, fbSizei.width, fbSizei.height);
|
||||
m_view.resizeView(fbSize);
|
||||
} else if (m_view.updated()) {
|
||||
m_view.ackUpdate();
|
||||
}
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
|
||||
// clear screen and draw
|
||||
glViewport(0, 0, fbSizei.width, fbSizei.height);
|
||||
m_view.draw();
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
ImGui::Image(
|
||||
std::bit_cast<ImTextureID>(uintptr_t{m_framebuffer.color.id}),
|
||||
static_cast<ImVec2>(fbSize),
|
||||
ImVec2(0, 1),
|
||||
ImVec2(1, 0));
|
||||
// handle input, this must come after drawing
|
||||
const auto &io = ImGui::GetIO();
|
||||
const auto mousePos = ox::Vec2(io.MousePos);
|
||||
if (ImGui::IsItemHovered()) {
|
||||
const auto wheel = io.MouseWheel;
|
||||
const auto wheelh = io.MouseWheelH;
|
||||
if (wheel != 0) {
|
||||
const auto zoomMod = ox::defines::OS == ox::OS::Darwin ?
|
||||
io.KeySuper : turbine::buttonDown(m_ctx, turbine::Key::Mod_Ctrl);
|
||||
m_view.scrollV(fbSize, wheel, zoomMod);
|
||||
}
|
||||
if (wheelh != 0) {
|
||||
m_view.scrollH(fbSize, wheelh);
|
||||
}
|
||||
if (io.MouseDown[0] && m_prevMouseDownPos != mousePos) {
|
||||
m_prevMouseDownPos = mousePos;
|
||||
switch (m_tool) {
|
||||
case Tool::Draw:
|
||||
m_view.clickDraw(fbSize, clickPos(winPos, mousePos));
|
||||
break;
|
||||
case Tool::Fill:
|
||||
m_view.clickFill(fbSize, clickPos(winPos, mousePos));
|
||||
break;
|
||||
case Tool::Select:
|
||||
m_view.clickSelect(fbSize, clickPos(winPos, mousePos));
|
||||
break;
|
||||
case Tool::None:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ImGui::BeginPopupContextItem("TileMenu", ImGuiPopupFlags_MouseButtonRight)) {
|
||||
const auto popupPos = ox::Vec2(ImGui::GetWindowPos());
|
||||
if (ImGui::MenuItem("Insert Tile")) {
|
||||
m_view.insertTile(fbSize, clickPos(winPos, popupPos));
|
||||
}
|
||||
if (ImGui::MenuItem("Delete Tile")) {
|
||||
m_view.deleteTile(fbSize, clickPos(winPos, popupPos));
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
if (io.MouseReleased[0]) {
|
||||
m_prevMouseDownPos = {-1, -1};
|
||||
m_view.releaseMouseButton();
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::drawPaletteSelector() noexcept {
|
||||
auto &sctx = *applicationData<studio::StudioContext>(m_ctx);
|
||||
const auto &files = sctx.project->fileList(core::FileExt_npal);
|
||||
const auto first = m_selectedPaletteIdx < files.size() ?
|
||||
files[m_selectedPaletteIdx].c_str() : "";
|
||||
if (ImGui::BeginCombo("Palette", first, 0)) {
|
||||
for (auto n = 0u; n < files.size(); n++) {
|
||||
const auto selected = (m_selectedPaletteIdx == n);
|
||||
if (ImGui::Selectable(files[n].c_str(), selected) && m_selectedPaletteIdx != n) {
|
||||
m_selectedPaletteIdx = n;
|
||||
oxLogError(m_model.setPalette(files[n]));
|
||||
}
|
||||
if (selected) {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
// header
|
||||
if (ImGui::BeginTable("PaletteTable", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) {
|
||||
ImGui::TableSetupColumn("No.", 0, 0.45f);
|
||||
ImGui::TableSetupColumn("", 0, 0.22f);
|
||||
ImGui::TableSetupColumn("Color16", 0, 3);
|
||||
ImGui::TableHeadersRow();
|
||||
if (auto pal = m_view.pal()) {
|
||||
for (auto i = 0u; auto c: pal->colors) {
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
// Column: color idx
|
||||
ImGui::TableNextColumn();
|
||||
const auto label = ox::BString<8>() + (i + 1);
|
||||
const auto rowSelected = i == m_view.palIdx();
|
||||
if (ImGui::Selectable(label.c_str(), rowSelected, ImGuiSelectableFlags_SpanAllColumns)) {
|
||||
m_view.setPalIdx(i);
|
||||
}
|
||||
// Column: color RGB
|
||||
ImGui::TableNextColumn();
|
||||
auto ic = ImGui::GetColorU32(ImVec4(redf(c), greenf(c), bluef(c), 1));
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("(%02d, %02d, %02d)", red16(c), green16(c), blue16(c));
|
||||
ImGui::TableNextRow();
|
||||
ImGui::PopID();
|
||||
++i;
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorImGui::updateActiveSubsheet(ox::StringView const&name, int cols, int rows) noexcept {
|
||||
return m_model.updateSubsheet(m_model.activeSubSheetIdx(), name, cols, rows);
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorImGui::setPaletteSelection() noexcept {
|
||||
const auto &palPath = m_model.palPath();
|
||||
auto &sctx = *applicationData<studio::StudioContext>(m_ctx);
|
||||
const auto &palList = sctx.project->fileList(core::FileExt_npal);
|
||||
for (std::size_t i = 0; const auto &pal : palList) {
|
||||
if (palPath == pal) {
|
||||
m_selectedPaletteIdx = i;
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorImGui::markUnsavedChanges(const studio::UndoCommand*) noexcept {
|
||||
setUnsavedChanges(true);
|
||||
return {};
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::SubSheetEditor::draw() noexcept {
|
||||
constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
|
||||
constexpr auto popupName = "Edit Subsheet";
|
||||
if (!m_show) {
|
||||
return;
|
||||
}
|
||||
ImGui::OpenPopup(popupName);
|
||||
const auto modSize = m_cols > 0;
|
||||
const auto popupHeight = modSize ? 125.f : 80.f;
|
||||
ImGui::SetNextWindowSize(ImVec2(235, popupHeight));
|
||||
if (ImGui::BeginPopupModal(popupName, &m_show, modalFlags)) {
|
||||
ImGui::InputText("Name", m_name.data(), m_name.cap());
|
||||
if (modSize) {
|
||||
ImGui::InputInt("Columns", &m_cols);
|
||||
ImGui::InputInt("Rows", &m_rows);
|
||||
}
|
||||
if (ImGui::Button("OK", ImVec2{50, 20})) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
m_show = false;
|
||||
inputSubmitted.emit(m_name, m_cols, m_rows);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel", ImVec2{50, 20})) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
m_show = false;
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::SubSheetEditor::close() noexcept {
|
||||
m_show = false;
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::ExportMenu::draw() noexcept {
|
||||
constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
|
||||
constexpr auto popupName = "Export Tile Sheet";
|
||||
if (!m_show) {
|
||||
return;
|
||||
}
|
||||
ImGui::OpenPopup(popupName);
|
||||
constexpr auto popupHeight = 80.f;
|
||||
ImGui::SetNextWindowSize(ImVec2(235, popupHeight));
|
||||
if (ImGui::BeginPopupModal(popupName, &m_show, modalFlags)) {
|
||||
ImGui::InputInt("Scale", &m_scale);
|
||||
m_scale = ox::clamp(m_scale, 1, 20);
|
||||
if (ImGui::Button("OK", ImVec2{50, 20})) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
m_show = false;
|
||||
inputSubmitted.emit(m_scale);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel", ImVec2{50, 20})) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
m_show = false;
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::ExportMenu::close() noexcept {
|
||||
m_show = false;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/model/def.hpp>
|
||||
#include <ox/std/vec.hpp>
|
||||
|
||||
#include <glutils/glutils.hpp>
|
||||
#include <studio/editor.hpp>
|
||||
|
||||
#include "tilesheetpixelgrid.hpp"
|
||||
#include "tilesheetpixels.hpp"
|
||||
#include "tilesheeteditorview.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
enum class Tool {
|
||||
None,
|
||||
Draw,
|
||||
Fill,
|
||||
Select,
|
||||
};
|
||||
|
||||
class TileSheetEditorImGui: public studio::Editor {
|
||||
|
||||
private:
|
||||
class SubSheetEditor {
|
||||
ox::BString<100> m_name;
|
||||
int m_cols = 0;
|
||||
int m_rows = 0;
|
||||
bool m_show = false;
|
||||
public:
|
||||
ox::Signal<ox::Error(ox::StringView const&name, int cols, int rows)> inputSubmitted;
|
||||
constexpr void show(ox::StringView const&name, int cols, int rows) noexcept {
|
||||
m_show = true;
|
||||
m_name = name;
|
||||
m_cols = cols;
|
||||
m_rows = rows;
|
||||
}
|
||||
void draw() noexcept;
|
||||
void close() noexcept;
|
||||
};
|
||||
class ExportMenu {
|
||||
int m_scale = 0;
|
||||
bool m_show = false;
|
||||
public:
|
||||
ox::Signal<ox::Error(int scale)> inputSubmitted;
|
||||
constexpr void show() noexcept {
|
||||
m_show = true;
|
||||
m_scale = 5;
|
||||
}
|
||||
void draw() noexcept;
|
||||
void close() noexcept;
|
||||
};
|
||||
std::size_t m_selectedPaletteIdx = 0;
|
||||
turbine::Context &m_ctx;
|
||||
ox::Vector<ox::String> m_paletteList;
|
||||
SubSheetEditor m_subsheetEditor;
|
||||
ExportMenu m_exportMenu;
|
||||
glutils::FrameBuffer m_framebuffer;
|
||||
TileSheetEditorView m_view;
|
||||
TileSheetEditorModel &m_model;
|
||||
float m_palViewWidth = 300;
|
||||
ox::Vec2 m_prevMouseDownPos;
|
||||
Tool m_tool = Tool::Draw;
|
||||
|
||||
public:
|
||||
TileSheetEditorImGui(turbine::Context &ctx, ox::CRStringView path);
|
||||
|
||||
~TileSheetEditorImGui() override = default;
|
||||
|
||||
void exportFile() override;
|
||||
|
||||
void cut() override;
|
||||
|
||||
void copy() override;
|
||||
|
||||
void paste() override;
|
||||
|
||||
void keyStateChanged(turbine::Key key, bool down) override;
|
||||
|
||||
void draw(turbine::Context&) noexcept override;
|
||||
|
||||
void drawSubsheetSelector(TileSheet::SubSheet*, TileSheet::SubSheetIdx *path);
|
||||
|
||||
[[nodiscard]]
|
||||
static ox::Vec2 clickPos(ImVec2 const&winPos, ox::Vec2 clickPos) noexcept;
|
||||
|
||||
protected:
|
||||
ox::Error saveItem() noexcept override;
|
||||
|
||||
private:
|
||||
void showSubsheetEditor() noexcept;
|
||||
|
||||
ox::Error exportSubhseetToPng(int scale) noexcept;
|
||||
|
||||
void drawTileSheet(ox::Vec2 const&fbSize) noexcept;
|
||||
|
||||
void drawPaletteSelector() noexcept;
|
||||
|
||||
ox::Error updateActiveSubsheet(ox::StringView const&name, int cols, int rows) noexcept;
|
||||
|
||||
ox::Error setPaletteSelection() noexcept;
|
||||
|
||||
// slots
|
||||
private:
|
||||
ox::Error markUnsavedChanges(const studio::UndoCommand*) noexcept;
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,290 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#include <ox/claw/read.hpp>
|
||||
#include <ox/std/buffer.hpp>
|
||||
#include <ox/std/memory.hpp>
|
||||
|
||||
#include <turbine/clipboard.hpp>
|
||||
#include <keel/media.hpp>
|
||||
|
||||
#include <nostalgia/core/ptidxconv.hpp>
|
||||
|
||||
#include "commands/commands.hpp"
|
||||
#include "commands/addsubsheetcommand.hpp"
|
||||
#include "commands/cutpastecommand.hpp"
|
||||
#include "commands/deletetilescommand.hpp"
|
||||
#include "commands/drawcommand.hpp"
|
||||
#include "commands/inserttilescommand.hpp"
|
||||
#include "commands/palettechangecommand.hpp"
|
||||
#include "commands/rmsubsheetcommand.hpp"
|
||||
#include "commands/updatesubsheetcommand.hpp"
|
||||
#include "tilesheeteditormodel.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
const Palette TileSheetEditorModel::s_defaultPalette = {
|
||||
.colors = ox::Vector<Color16>(128),
|
||||
};
|
||||
|
||||
TileSheetEditorModel::TileSheetEditorModel(turbine::Context &ctx, ox::StringView path, studio::UndoStack &undoStack):
|
||||
m_ctx(ctx),
|
||||
m_path(path),
|
||||
m_img(*readObj<TileSheet>(keelCtx(m_ctx), m_path).unwrapThrow()),
|
||||
// ignore failure to load palette
|
||||
m_pal(readObj<Palette>(keelCtx(m_ctx), m_img.defaultPalette).value),
|
||||
m_undoStack(undoStack) {
|
||||
m_pal.updated.connect(this, &TileSheetEditorModel::markUpdated);
|
||||
m_undoStack.changeTriggered.connect(this, &TileSheetEditorModel::markUpdatedCmdId);
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::cut() {
|
||||
TileSheetClipboard blankCb;
|
||||
auto cb = ox::make_unique<TileSheetClipboard>();
|
||||
const auto s = activeSubSheet();
|
||||
for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) {
|
||||
for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) {
|
||||
auto pt = ox::Point(x, y);
|
||||
const auto idx = s->idx(pt);
|
||||
const auto c = s->getPixel(m_img.bpp, idx);
|
||||
pt.x -= m_selectionBounds.x;
|
||||
pt.y -= m_selectionBounds.y;
|
||||
cb->addPixel(pt, c);
|
||||
blankCb.addPixel(pt, 0);
|
||||
}
|
||||
}
|
||||
const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin;
|
||||
const auto pt2 = ox::Point(s->columns * TileWidth, s->rows * TileHeight);
|
||||
turbine::setClipboardObject(m_ctx, std::move(cb));
|
||||
pushCommand(ox::make<CutPasteCommand>(CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb));
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::copy() {
|
||||
auto cb = ox::make_unique<TileSheetClipboard>();
|
||||
for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) {
|
||||
for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) {
|
||||
auto pt = ox::Point(x, y);
|
||||
const auto s = activeSubSheet();
|
||||
const auto idx = s->idx(pt);
|
||||
const auto c = s->getPixel(m_img.bpp, idx);
|
||||
pt.x -= m_selectionBounds.x;
|
||||
pt.y -= m_selectionBounds.y;
|
||||
cb->addPixel(pt, c);
|
||||
}
|
||||
}
|
||||
turbine::setClipboardObject(m_ctx, std::move(cb));
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::paste() {
|
||||
auto [cb, err] = turbine::getClipboardObject<TileSheetClipboard>(m_ctx);
|
||||
if (err) {
|
||||
oxLogError(err);
|
||||
oxErrf("Could not read clipboard: {}", toStr(err));
|
||||
return;
|
||||
}
|
||||
const auto s = activeSubSheet();
|
||||
const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin;
|
||||
const auto pt2 = ox::Point(s->columns * TileWidth, s->rows * TileHeight);
|
||||
pushCommand(ox::make<CutPasteCommand>(CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb));
|
||||
}
|
||||
|
||||
ox::StringView TileSheetEditorModel::palPath() const noexcept {
|
||||
auto [path, err] = m_img.defaultPalette.getPath();
|
||||
if (err) {
|
||||
return {};
|
||||
}
|
||||
constexpr ox::StringView uuidPrefix = "uuid://";
|
||||
if (ox::beginsWith(path, uuidPrefix)) {
|
||||
auto uuid = ox::StringView(path.data() + uuidPrefix.bytes(), path.bytes() - uuidPrefix.bytes());
|
||||
auto out = keelCtx(m_ctx).uuidToPath.at(uuid);
|
||||
if (out.error) {
|
||||
return {};
|
||||
}
|
||||
return *out.value;
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorModel::setPalette(ox::StringView path) noexcept {
|
||||
oxRequire(uuid, keelCtx(m_ctx).pathToUuid.at(path));
|
||||
pushCommand(ox::make<PaletteChangeCommand>(activeSubSheetIdx(), m_img, uuid->toString()));
|
||||
return {};
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t palIdx) noexcept {
|
||||
const auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx);
|
||||
if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) {
|
||||
return;
|
||||
}
|
||||
const auto idx = activeSubSheet.idx(pt);
|
||||
if (m_ongoingDrawCommand) {
|
||||
m_updated = m_updated || m_ongoingDrawCommand->append(idx);
|
||||
} else if (activeSubSheet.getPixel(m_img.bpp, idx) != palIdx) {
|
||||
pushCommand(ox::make<DrawCommand>(m_img, m_activeSubsSheetIdx, idx, static_cast<int>(palIdx)));
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::endDrawCommand() noexcept {
|
||||
m_ongoingDrawCommand = nullptr;
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept {
|
||||
pushCommand(ox::make<AddSubSheetCommand>(m_img, parentIdx));
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::rmSubsheet(TileSheet::SubSheetIdx const&idx) noexcept {
|
||||
pushCommand(ox::make<RmSubSheetCommand>(m_img, idx));
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::insertTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept {
|
||||
pushCommand(ox::make<InsertTilesCommand>(m_img, idx, tileIdx, tileCnt));
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::deleteTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept {
|
||||
pushCommand(ox::make<DeleteTilesCommand>(m_img, idx, tileIdx, tileCnt));
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorModel::updateSubsheet(TileSheet::SubSheetIdx const&idx, const ox::StringView &name, int cols, int rows) noexcept {
|
||||
pushCommand(ox::make<UpdateSubSheetCommand>(m_img, idx, ox::String(name), cols, rows));
|
||||
return {};
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::setActiveSubsheet(TileSheet::SubSheetIdx const&idx) noexcept {
|
||||
m_activeSubsSheetIdx = idx;
|
||||
this->activeSubsheetChanged.emit(m_activeSubsSheetIdx);
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::fill(ox::Point const&pt, int palIdx) noexcept {
|
||||
const auto &s = m_img.getSubSheet(m_activeSubsSheetIdx);
|
||||
// build idx list
|
||||
ox::Array<bool, PixelsPerTile> updateMap = {};
|
||||
const auto oldColor = s.getPixel(m_img.bpp, pt);
|
||||
if (pt.x >= s.columns * TileWidth || pt.y >= s.rows * TileHeight) {
|
||||
return;
|
||||
}
|
||||
getFillPixels(updateMap.data(), pt, oldColor);
|
||||
ox::Vector<std::size_t> idxList;
|
||||
auto i = s.idx(pt) / PixelsPerTile * PixelsPerTile;
|
||||
for (auto u : updateMap) {
|
||||
if (u) {
|
||||
idxList.emplace_back(i);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
// do updates to sheet
|
||||
if (m_ongoingDrawCommand) {
|
||||
m_updated = m_updated || m_ongoingDrawCommand->append(idxList);
|
||||
} else if (s.getPixel(m_img.bpp, pt) != palIdx) {
|
||||
pushCommand(ox::make<DrawCommand>(m_img, m_activeSubsSheetIdx, idxList, palIdx));
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::select(ox::Point const&pt) noexcept {
|
||||
if (!m_selectionOngoing) {
|
||||
m_selectionOrigin = pt;
|
||||
m_selectionOngoing = true;
|
||||
m_selectionBounds = {pt, pt};
|
||||
m_updated = true;
|
||||
} else if (m_selectionBounds.pt2() != pt) {
|
||||
m_selectionBounds = {m_selectionOrigin, pt};
|
||||
m_updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::completeSelection() noexcept {
|
||||
m_selectionOngoing = false;
|
||||
auto s = activeSubSheet();
|
||||
auto pt = m_selectionBounds.pt2();
|
||||
pt.x = ox::min(s->columns * TileWidth, pt.x);
|
||||
pt.y = ox::min(s->rows * TileHeight, pt.y);
|
||||
m_selectionBounds.setPt2(pt);
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::clearSelection() noexcept {
|
||||
m_updated = true;
|
||||
m_selectionOrigin = {-1, -1};
|
||||
m_selectionBounds = {{-1, -1}, {-1, -1}};
|
||||
}
|
||||
|
||||
bool TileSheetEditorModel::updated() const noexcept {
|
||||
return m_updated;
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorModel::markUpdatedCmdId(const studio::UndoCommand *cmd) noexcept {
|
||||
m_updated = true;
|
||||
const auto cmdId = cmd->commandId();
|
||||
if (static_cast<CommandId>(cmdId) == CommandId::PaletteChange) {
|
||||
oxReturnError(readObj<Palette>(keelCtx(m_ctx), m_img.defaultPalette).moveTo(m_pal));
|
||||
paletteChanged.emit();
|
||||
}
|
||||
auto tsCmd = dynamic_cast<const TileSheetCommand*>(cmd);
|
||||
auto idx = m_img.validateSubSheetIdx(tsCmd->subsheetIdx());
|
||||
if (idx != m_activeSubsSheetIdx) {
|
||||
setActiveSubsheet(idx);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorModel::markUpdated() noexcept {
|
||||
m_updated = true;
|
||||
return {};
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::ackUpdate() noexcept {
|
||||
m_updated = false;
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorModel::saveFile() noexcept {
|
||||
const auto sctx = applicationData<studio::StudioContext>(m_ctx);
|
||||
return sctx->project->writeObj(m_path, m_img);
|
||||
}
|
||||
|
||||
bool TileSheetEditorModel::pixelSelected(std::size_t idx) const noexcept {
|
||||
const auto s = activeSubSheet();
|
||||
const auto pt = idxToPt(static_cast<int>(idx), s->columns);
|
||||
return m_selectionBounds.contains(pt);
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::getFillPixels(bool *pixels, ox::Point const&pt, int oldColor) const noexcept {
|
||||
const auto &activeSubSheet = *this->activeSubSheet();
|
||||
const auto tileIdx = [activeSubSheet](const ox::Point &pt) noexcept {
|
||||
return ptToIdx(pt, activeSubSheet.columns) / PixelsPerTile;
|
||||
};
|
||||
// get points
|
||||
const auto leftPt = pt + ox::Point(-1, 0);
|
||||
const auto rightPt = pt + ox::Point(1, 0);
|
||||
const auto topPt = pt + ox::Point(0, -1);
|
||||
const auto bottomPt = pt + ox::Point(0, 1);
|
||||
// calculate indices
|
||||
const auto idx = ptToIdx(pt, activeSubSheet.columns);
|
||||
const auto leftIdx = ptToIdx(leftPt, activeSubSheet.columns);
|
||||
const auto rightIdx = ptToIdx(rightPt, activeSubSheet.columns);
|
||||
const auto topIdx = ptToIdx(topPt, activeSubSheet.columns);
|
||||
const auto bottomIdx = ptToIdx(bottomPt, activeSubSheet.columns);
|
||||
const auto tile = tileIdx(pt);
|
||||
// mark pixels to update
|
||||
pixels[idx % PixelsPerTile] = true;
|
||||
if (!pixels[leftIdx % PixelsPerTile] && tile == tileIdx(leftPt) && activeSubSheet.getPixel(m_img.bpp, leftIdx) == oldColor) {
|
||||
getFillPixels(pixels, leftPt, oldColor);
|
||||
}
|
||||
if (!pixels[rightIdx % PixelsPerTile] && tile == tileIdx(rightPt) && activeSubSheet.getPixel(m_img.bpp, rightIdx) == oldColor) {
|
||||
getFillPixels(pixels, rightPt, oldColor);
|
||||
}
|
||||
if (!pixels[topIdx % PixelsPerTile] && tile == tileIdx(topPt) && activeSubSheet.getPixel(m_img.bpp, topIdx) == oldColor) {
|
||||
getFillPixels(pixels, topPt, oldColor);
|
||||
}
|
||||
if (!pixels[bottomIdx % PixelsPerTile] && tile == tileIdx(bottomPt) && activeSubSheet.getPixel(m_img.bpp, bottomIdx) == oldColor) {
|
||||
getFillPixels(pixels, bottomPt, oldColor);
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept {
|
||||
m_undoStack.push(ox::UPtr<studio::UndoCommand>(cmd));
|
||||
m_ongoingDrawCommand = dynamic_cast<DrawCommand*>(cmd);
|
||||
m_updated = true;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/bounds.hpp>
|
||||
#include <ox/std/point.hpp>
|
||||
#include <ox/std/trace.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
#include <nostalgia/core/tilesheet.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class TileSheetEditorModel: public ox::SignalHandler {
|
||||
|
||||
public:
|
||||
ox::Signal<ox::Error(const TileSheet::SubSheetIdx&)> activeSubsheetChanged;
|
||||
ox::Signal<ox::Error()> paletteChanged;
|
||||
|
||||
private:
|
||||
static const Palette s_defaultPalette;
|
||||
turbine::Context &m_ctx;
|
||||
ox::String m_path;
|
||||
TileSheet m_img;
|
||||
TileSheet::SubSheetIdx m_activeSubsSheetIdx;
|
||||
keel::AssetRef<Palette> m_pal;
|
||||
studio::UndoStack &m_undoStack;
|
||||
class DrawCommand *m_ongoingDrawCommand = nullptr;
|
||||
bool m_updated = false;
|
||||
bool m_selectionOngoing = false;
|
||||
ox::Point m_selectionOrigin = {-1, -1};
|
||||
ox::Bounds m_selectionBounds = {{-1, -1}, {-1, -1}};
|
||||
|
||||
public:
|
||||
TileSheetEditorModel(turbine::Context &ctx, ox::StringView path, studio::UndoStack &undoStack);
|
||||
|
||||
~TileSheetEditorModel() override = default;
|
||||
|
||||
void cut();
|
||||
|
||||
void copy();
|
||||
|
||||
void paste();
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr const TileSheet &img() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr TileSheet &img() noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr const Palette *pal() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ox::StringView palPath() const noexcept;
|
||||
|
||||
ox::Error setPalette(ox::StringView path) noexcept;
|
||||
|
||||
void drawCommand(const ox::Point &pt, std::size_t palIdx) noexcept;
|
||||
|
||||
void endDrawCommand() noexcept;
|
||||
|
||||
void addSubsheet(const TileSheet::SubSheetIdx &parentIdx) noexcept;
|
||||
|
||||
void rmSubsheet(const TileSheet::SubSheetIdx &idx) noexcept;
|
||||
|
||||
void insertTiles(const TileSheet::SubSheetIdx &idx, std::size_t tileIdx, std::size_t tileCnt) noexcept;
|
||||
|
||||
void deleteTiles(const TileSheet::SubSheetIdx &idx, std::size_t tileIdx, std::size_t tileCnt) noexcept;
|
||||
|
||||
ox::Error updateSubsheet(const TileSheet::SubSheetIdx &idx, const ox::StringView &name, int cols, int rows) noexcept;
|
||||
|
||||
void setActiveSubsheet(const TileSheet::SubSheetIdx&) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
const TileSheet::SubSheet *activeSubSheet() const noexcept {
|
||||
auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx);
|
||||
return &activeSubSheet;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
TileSheet::SubSheet *activeSubSheet() noexcept {
|
||||
auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx);
|
||||
return &activeSubSheet;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr TileSheet::SubSheetIdx const&activeSubSheetIdx() const noexcept {
|
||||
return m_activeSubsSheetIdx;
|
||||
}
|
||||
|
||||
void fill(ox::Point const&pt, int palIdx) noexcept;
|
||||
|
||||
void select(ox::Point const&pt) noexcept;
|
||||
|
||||
void completeSelection() noexcept;
|
||||
|
||||
void clearSelection() noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
bool updated() const noexcept;
|
||||
|
||||
ox::Error markUpdatedCmdId(const studio::UndoCommand *cmd) noexcept;
|
||||
|
||||
ox::Error markUpdated() noexcept;
|
||||
|
||||
void ackUpdate() noexcept;
|
||||
|
||||
ox::Error saveFile() noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr studio::UndoStack *undoStack() noexcept;
|
||||
|
||||
bool pixelSelected(std::size_t idx) const noexcept;
|
||||
|
||||
protected:
|
||||
void getFillPixels(bool *pixels, ox::Point const&pt, int oldColor) const noexcept;
|
||||
|
||||
private:
|
||||
void pushCommand(studio::UndoCommand *cmd) noexcept;
|
||||
|
||||
};
|
||||
|
||||
constexpr const TileSheet &TileSheetEditorModel::img() const noexcept {
|
||||
return m_img;
|
||||
}
|
||||
|
||||
constexpr TileSheet &TileSheetEditorModel::img() noexcept {
|
||||
return m_img;
|
||||
}
|
||||
|
||||
constexpr const Palette *TileSheetEditorModel::pal() const noexcept {
|
||||
if (m_pal) {
|
||||
return m_pal.get();
|
||||
}
|
||||
return &s_defaultPalette;
|
||||
}
|
||||
|
||||
constexpr studio::UndoStack *TileSheetEditorModel::undoStack() noexcept {
|
||||
return &m_undoStack;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/std/point.hpp>
|
||||
#include <keel/media.hpp>
|
||||
|
||||
#include <nostalgia/core/consts.hpp>
|
||||
|
||||
#include "tilesheeteditorview.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
TileSheetEditorView::TileSheetEditorView(turbine::Context &ctx, ox::StringView path, studio::UndoStack &undoStack):
|
||||
m_model(ctx, path, undoStack),
|
||||
m_pixelsDrawer(&m_model) {
|
||||
// build shaders
|
||||
oxThrowError(m_pixelsDrawer.buildShader());
|
||||
oxThrowError(m_pixelGridDrawer.buildShader());
|
||||
m_model.activeSubsheetChanged.connect(this, &TileSheetEditorView::setActiveSubsheet);
|
||||
}
|
||||
|
||||
void TileSheetEditorView::draw() noexcept {
|
||||
constexpr Color32 bgColor = 0x717d7e;
|
||||
glClearColor(redf(bgColor), greenf(bgColor), bluef(bgColor), 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
m_pixelsDrawer.draw(updated(), m_scrollOffset);
|
||||
m_pixelGridDrawer.draw(updated(), m_scrollOffset);
|
||||
}
|
||||
|
||||
void TileSheetEditorView::scrollV(ox::Vec2 const&paneSz, float wheel, bool zoomMod) noexcept {
|
||||
const auto pixelSize = m_pixelsDrawer.pixelSize(paneSz);
|
||||
const ImVec2 sheetSize(pixelSize.x * static_cast<float>(m_model.activeSubSheet()->columns) * TileWidth,
|
||||
pixelSize.y * static_cast<float>(m_model.activeSubSheet()->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);
|
||||
}
|
||||
|
||||
void TileSheetEditorView::scrollH(ox::Vec2 const&paneSz, float wheelh) noexcept {
|
||||
const auto pixelSize = m_pixelsDrawer.pixelSize(paneSz);
|
||||
const ImVec2 sheetSize(pixelSize.x * static_cast<float>(m_model.activeSubSheet()->columns) * TileWidth,
|
||||
pixelSize.y * static_cast<float>(m_model.activeSubSheet()->rows) * TileHeight);
|
||||
m_scrollOffset.x += wheelh * 0.1f;
|
||||
m_scrollOffset.x = ox::clamp(m_scrollOffset.x, -(sheetSize.x / 2), 0.f);
|
||||
}
|
||||
|
||||
void TileSheetEditorView::insertTile(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept {
|
||||
const auto pt = clickPoint(paneSize, clickPos);
|
||||
const auto s = m_model.activeSubSheet();
|
||||
const auto tileIdx = ptToIdx(pt, s->columns) / PixelsPerTile;
|
||||
m_model.insertTiles(m_model.activeSubSheetIdx(), tileIdx, 1);
|
||||
}
|
||||
|
||||
void TileSheetEditorView::deleteTile(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept {
|
||||
const auto pt = clickPoint(paneSize, clickPos);
|
||||
const auto s = m_model.activeSubSheet();
|
||||
const auto tileIdx = ptToIdx(pt, s->columns) / PixelsPerTile;
|
||||
m_model.deleteTiles(m_model.activeSubSheetIdx(), tileIdx, 1);
|
||||
}
|
||||
|
||||
void TileSheetEditorView::clickDraw(ox::Vec2 const &paneSize, ox::Vec2 const&clickPos) noexcept {
|
||||
const auto pt = clickPoint(paneSize, clickPos);
|
||||
m_model.drawCommand(pt, m_palIdx);
|
||||
}
|
||||
|
||||
void TileSheetEditorView::clickSelect(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept {
|
||||
const auto pt = clickPoint(paneSize, clickPos);
|
||||
m_model.select(pt);
|
||||
}
|
||||
|
||||
void TileSheetEditorView::clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept {
|
||||
const auto pt = clickPoint(paneSize, clickPos);
|
||||
m_model.fill(pt, static_cast<int>(m_palIdx));
|
||||
}
|
||||
|
||||
void TileSheetEditorView::releaseMouseButton() noexcept {
|
||||
m_model.endDrawCommand();
|
||||
m_model.completeSelection();
|
||||
}
|
||||
|
||||
void TileSheetEditorView::resizeView(ox::Vec2 const&sz) noexcept {
|
||||
m_viewSize = sz;
|
||||
initView();
|
||||
}
|
||||
|
||||
bool TileSheetEditorView::updated() const noexcept {
|
||||
return m_updated || m_model.updated();
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorView::markUpdated() noexcept {
|
||||
m_updated = true;
|
||||
return {};
|
||||
}
|
||||
|
||||
void TileSheetEditorView::ackUpdate() noexcept {
|
||||
m_updated = false;
|
||||
m_pixelsDrawer.update(m_viewSize);
|
||||
m_pixelGridDrawer.update(m_viewSize, *m_model.activeSubSheet());
|
||||
m_model.ackUpdate();
|
||||
}
|
||||
|
||||
void TileSheetEditorView::initView() noexcept {
|
||||
m_pixelsDrawer.initBufferSet(m_viewSize);
|
||||
m_pixelGridDrawer.initBufferSet(m_viewSize, *m_model.activeSubSheet());
|
||||
}
|
||||
|
||||
ox::Point TileSheetEditorView::clickPoint(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) const noexcept {
|
||||
auto [x, y] = clickPos;
|
||||
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;
|
||||
return {static_cast<int>(x * 2), static_cast<int>(y * 2)};
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorView::setActiveSubsheet(TileSheet::SubSheetIdx const&) noexcept {
|
||||
initView();
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/vec.hpp>
|
||||
#include <ox/model/def.hpp>
|
||||
|
||||
#include <glutils/glutils.hpp>
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
|
||||
#include "tilesheeteditormodel.hpp"
|
||||
#include "tilesheetpixelgrid.hpp"
|
||||
#include "tilesheetpixels.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
enum class TileSheetTool: int {
|
||||
Select,
|
||||
Draw,
|
||||
Fill,
|
||||
};
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto toString(TileSheetTool t) noexcept {
|
||||
switch (t) {
|
||||
case TileSheetTool::Select:
|
||||
return "Select";
|
||||
case TileSheetTool::Draw:
|
||||
return "Draw";
|
||||
case TileSheetTool::Fill:
|
||||
return "Fill";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
class TileSheetEditorView: public ox::SignalHandler {
|
||||
|
||||
private:
|
||||
TileSheetEditorModel m_model;
|
||||
TileSheetGrid m_pixelGridDrawer;
|
||||
TileSheetPixels m_pixelsDrawer;
|
||||
ox::Vec2 m_viewSize;
|
||||
float m_pixelSizeMod = 1;
|
||||
bool m_updated = false;
|
||||
ox::Vec2 m_scrollOffset;
|
||||
std::size_t m_palIdx = 0;
|
||||
|
||||
public:
|
||||
TileSheetEditorView(turbine::Context &ctx, ox::StringView path, studio::UndoStack &undoStack);
|
||||
|
||||
~TileSheetEditorView() override = default;
|
||||
|
||||
void draw() noexcept;
|
||||
|
||||
void insertTile(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
|
||||
|
||||
void deleteTile(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
|
||||
|
||||
void clickDraw(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
|
||||
|
||||
void clickSelect(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
|
||||
|
||||
void clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
|
||||
|
||||
void releaseMouseButton() noexcept;
|
||||
|
||||
void scrollV(ox::Vec2 const&paneSz, float wheel, bool zoomMod) noexcept;
|
||||
|
||||
void scrollH(ox::Vec2 const&paneSz, float wheel) noexcept;
|
||||
|
||||
void resizeView(ox::Vec2 const&sz) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr TileSheet const&img() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr TileSheet &img() noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr const Palette *pal() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto &model() noexcept {
|
||||
return m_model;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto &model() const noexcept {
|
||||
return m_model;
|
||||
}
|
||||
|
||||
constexpr auto setPalIdx(auto palIdx) noexcept {
|
||||
m_palIdx = palIdx;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto palIdx() const noexcept {
|
||||
return m_palIdx;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool updated() const noexcept;
|
||||
|
||||
ox::Error markUpdated() noexcept;
|
||||
|
||||
void ackUpdate() noexcept;
|
||||
|
||||
private:
|
||||
void initView() noexcept;
|
||||
|
||||
ox::Point clickPoint(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) const noexcept;
|
||||
|
||||
ox::Error setActiveSubsheet(TileSheet::SubSheetIdx const&idx) noexcept;
|
||||
|
||||
};
|
||||
|
||||
constexpr TileSheet const&TileSheetEditorView::img() const noexcept {
|
||||
return m_model.img();
|
||||
}
|
||||
|
||||
constexpr TileSheet &TileSheetEditorView::img() noexcept {
|
||||
return m_model.img();
|
||||
}
|
||||
|
||||
constexpr const Palette *TileSheetEditorView::pal() const noexcept {
|
||||
return m_model.pal();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/claw/write.hpp>
|
||||
|
||||
#include <nostalgia/core/consts.hpp>
|
||||
|
||||
#include "tilesheetpixelgrid.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
void TileSheetGrid::setPixelSizeMod(float sm) noexcept {
|
||||
m_pixelSizeMod = sm;
|
||||
}
|
||||
|
||||
ox::Error TileSheetGrid::buildShader() noexcept {
|
||||
const auto pixelLineVshad = ox::sfmt(VShad, gl::GlslVersion);
|
||||
const auto pixelLineFshad = ox::sfmt(FShad, gl::GlslVersion);
|
||||
const auto pixelLineGshad = ox::sfmt(GShad, gl::GlslVersion);
|
||||
return glutils::buildShaderProgram(pixelLineVshad, pixelLineFshad, pixelLineGshad).moveTo(m_shader);
|
||||
}
|
||||
|
||||
void TileSheetGrid::draw(bool update, ox::Vec2 const&scroll) noexcept {
|
||||
glLineWidth(3 * m_pixelSizeMod * 0.5f);
|
||||
glUseProgram(m_shader);
|
||||
glBindVertexArray(m_bufferSet.vao);
|
||||
if (update) {
|
||||
glutils::sendVbo(m_bufferSet);
|
||||
}
|
||||
const auto uniformScroll = glGetUniformLocation(m_shader, "gScroll");
|
||||
glUniform2f(uniformScroll, scroll.x, scroll.y);
|
||||
glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(m_bufferSet.vertices.size() / VertexVboRowLength));
|
||||
glBindVertexArray(0);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
void TileSheetGrid::initBufferSet(ox::Vec2 const&paneSize, TileSheet::SubSheet const&subsheet) noexcept {
|
||||
// vao
|
||||
m_bufferSet.vao = glutils::generateVertexArrayObject();
|
||||
glBindVertexArray(m_bufferSet.vao);
|
||||
// vbo
|
||||
m_bufferSet.vbo = glutils::generateBuffer();
|
||||
setBufferObjects(paneSize, subsheet);
|
||||
glutils::sendVbo(m_bufferSet);
|
||||
// vbo layout
|
||||
const auto pt1Attr = static_cast<GLuint>(glGetAttribLocation(m_shader, "vPt1"));
|
||||
glEnableVertexAttribArray(pt1Attr);
|
||||
glVertexAttribPointer(pt1Attr, 2, GL_FLOAT, GL_FALSE, VertexVboRowLength * sizeof(float), nullptr);
|
||||
const auto pt2Attr = static_cast<GLuint>(glGetAttribLocation(m_shader, "vPt2"));
|
||||
glEnableVertexAttribArray(pt2Attr);
|
||||
glVertexAttribPointer(pt2Attr, 2, GL_FLOAT, GL_FALSE, VertexVboRowLength * sizeof(float),
|
||||
std::bit_cast<void*>(uintptr_t{2 * sizeof(float)}));
|
||||
const auto colorAttr = static_cast<GLuint>(glGetAttribLocation(m_shader, "vColor"));
|
||||
glEnableVertexAttribArray(colorAttr);
|
||||
glVertexAttribPointer(colorAttr, 3, GL_FLOAT, GL_FALSE, VertexVboRowLength * sizeof(float),
|
||||
std::bit_cast<void*>(uintptr_t{4 * sizeof(float)}));
|
||||
}
|
||||
|
||||
void TileSheetGrid::update(ox::Vec2 const&paneSize, TileSheet::SubSheet const&subsheet) noexcept {
|
||||
glBindVertexArray(m_bufferSet.vao);
|
||||
setBufferObjects(paneSize, subsheet);
|
||||
glutils::sendVbo(m_bufferSet);
|
||||
glutils::sendEbo(m_bufferSet);
|
||||
}
|
||||
|
||||
void TileSheetGrid::setBufferObject(ox::Point pt1, ox::Point pt2, Color32 c, float *vbo, ox::Vec2 const&pixSize) noexcept {
|
||||
const auto x1 = static_cast<float>(pt1.x) * pixSize.x - 1.f;
|
||||
const auto y1 = 1.f - static_cast<float>(pt1.y) * pixSize.y;
|
||||
const auto x2 = static_cast<float>(pt2.x) * pixSize.x - 1.f;
|
||||
const auto y2 = 1.f - static_cast<float>(pt2.y) * pixSize.y;
|
||||
// don't worry, this memcpy gets optimized to something much more ideal
|
||||
const ox::Array<float, VertexVboLength> vertices = {x1, y1, x2, y2, redf(c), greenf(c), bluef(c)};
|
||||
memcpy(vbo, vertices.data(), sizeof(vertices));
|
||||
}
|
||||
|
||||
void TileSheetGrid::setBufferObjects(ox::Vec2 const&paneSize, TileSheet::SubSheet const&subsheet) noexcept {
|
||||
const auto pixSize = pixelSize(paneSize);
|
||||
const auto set = [&](std::size_t i, ox::Point pt1, ox::Point pt2, Color32 c) {
|
||||
const auto vbo = &m_bufferSet.vertices[i * VertexVboLength];
|
||||
setBufferObject(pt1, pt2, c, vbo, pixSize);
|
||||
};
|
||||
// set buffer length
|
||||
const auto width = subsheet.columns * TileWidth;
|
||||
const auto height = subsheet.rows * TileHeight;
|
||||
const auto tileCnt = static_cast<unsigned>(subsheet.columns + subsheet.rows);
|
||||
const auto pixelCnt = static_cast<unsigned>(width + height);
|
||||
m_bufferSet.vertices.resize(static_cast<std::size_t>(tileCnt + pixelCnt + 4) * VertexVboLength);
|
||||
// set buffer
|
||||
std::size_t i = 0;
|
||||
// pixel outlines
|
||||
constexpr auto pixOutlineColor = color32(0.4431f, 0.4901f, 0.4941f);
|
||||
for (auto x = 0; x < subsheet.columns * TileWidth + 1; ++x) {
|
||||
set(i, {x, 0}, {x, subsheet.rows * TileHeight}, pixOutlineColor);
|
||||
++i;
|
||||
}
|
||||
for (auto y = 0; y < subsheet.rows * TileHeight + 1; ++y) {
|
||||
set(i, {0, y}, {subsheet.columns * TileWidth, y}, pixOutlineColor);
|
||||
++i;
|
||||
}
|
||||
// tile outlines
|
||||
constexpr auto tileOutlineColor = color32(0.f, 0.f, 0.f);
|
||||
for (auto x = 0; x < subsheet.columns * TileWidth + 1; x += TileWidth) {
|
||||
set(i, {x, 0}, {x, subsheet.rows * TileHeight}, tileOutlineColor);
|
||||
++i;
|
||||
}
|
||||
for (auto y = 0; y < subsheet.rows * TileHeight + 1; y += TileHeight) {
|
||||
set(i, {0, y}, {subsheet.columns * TileWidth, y}, tileOutlineColor);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
ox::Vec2 TileSheetGrid::pixelSize(ox::Vec2 const&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};
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glutils/glutils.hpp>
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
#include <nostalgia/core/tilesheet.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class TileSheetGrid {
|
||||
|
||||
private:
|
||||
static constexpr auto VertexVboRows = 1;
|
||||
static constexpr auto VertexVboRowLength = 7;
|
||||
static constexpr auto VertexVboLength = VertexVboRows * VertexVboRowLength;
|
||||
|
||||
static constexpr auto VShad = R"glsl(
|
||||
{}
|
||||
in vec2 vPt1;
|
||||
in vec2 vPt2;
|
||||
in vec3 vColor;
|
||||
out vec2 gPt2;
|
||||
out vec3 gColor;
|
||||
void main() {
|
||||
gColor = vColor;
|
||||
gl_Position = vec4(vPt1, 0.0, 1.0);
|
||||
gPt2 = vPt2;
|
||||
})glsl";
|
||||
|
||||
static constexpr auto FShad = R"glsl(
|
||||
{}
|
||||
in vec3 fColor;
|
||||
out vec4 outColor;
|
||||
void main() {
|
||||
outColor = vec4(fColor, 1);
|
||||
//outColor = vec4(0.4431, 0.4901, 0.4941, 1.0);
|
||||
})glsl";
|
||||
|
||||
static constexpr auto GShad = R"glsl(
|
||||
{}
|
||||
layout(points) in;
|
||||
layout(line_strip, max_vertices = 2) out;
|
||||
in vec3 gColor[];
|
||||
in vec2 gPt2[];
|
||||
out vec3 fColor;
|
||||
uniform vec2 gScroll;
|
||||
void main() {
|
||||
fColor = gColor[0];
|
||||
gl_Position = gl_in[0].gl_Position + vec4(gScroll, 0, 0);
|
||||
EmitVertex();
|
||||
gl_Position = vec4(gPt2[0] + gScroll, 0, 1);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
})glsl";
|
||||
|
||||
glutils::GLProgram m_shader;
|
||||
glutils::BufferSet m_bufferSet;
|
||||
float m_pixelSizeMod = 1;
|
||||
|
||||
public:
|
||||
void setPixelSizeMod(float sm) noexcept;
|
||||
|
||||
ox::Error buildShader() noexcept;
|
||||
|
||||
void draw(bool update, ox::Vec2 const&scroll) noexcept;
|
||||
|
||||
void initBufferSet(ox::Vec2 const&paneSize, TileSheet::SubSheet const&subsheet) noexcept;
|
||||
|
||||
void update(ox::Vec2 const&paneSize, TileSheet::SubSheet const&subsheet) noexcept;
|
||||
|
||||
private:
|
||||
static void setBufferObject(ox::Point pt1, ox::Point pt2, Color32 c, float *vbo, ox::Vec2 const&pixSize) noexcept;
|
||||
|
||||
void setBufferObjects(ox::Vec2 const&paneSize, TileSheet::SubSheet const&subsheet) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Vec2 pixelSize(ox::Vec2 const&paneSize) const noexcept;
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <nostalgia/core/consts.hpp>
|
||||
#include <nostalgia/core/ptidxconv.hpp>
|
||||
#include "tilesheeteditormodel.hpp"
|
||||
#include "tilesheetpixels.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
TileSheetPixels::TileSheetPixels(TileSheetEditorModel *model) noexcept: m_model(model) {
|
||||
}
|
||||
|
||||
void TileSheetPixels::setPixelSizeMod(float sm) noexcept {
|
||||
m_pixelSizeMod = sm;
|
||||
}
|
||||
|
||||
ox::Error TileSheetPixels::buildShader() noexcept {
|
||||
const auto Vshad = ox::sfmt(VShad, gl::GlslVersion);
|
||||
const auto Fshad = ox::sfmt(FShad, gl::GlslVersion);
|
||||
return glutils::buildShaderProgram(Vshad, Fshad).moveTo(m_shader);
|
||||
}
|
||||
|
||||
void TileSheetPixels::draw(bool update, ox::Vec2 const&scroll) noexcept {
|
||||
glUseProgram(m_shader);
|
||||
glBindVertexArray(m_bufferSet.vao);
|
||||
if (update) {
|
||||
glutils::sendVbo(m_bufferSet);
|
||||
}
|
||||
const auto uniformScroll = glGetUniformLocation(m_shader, "vScroll");
|
||||
glUniform2f(uniformScroll, scroll.x, scroll.y);
|
||||
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(m_bufferSet.elements.size()), GL_UNSIGNED_INT, nullptr);
|
||||
glBindVertexArray(0);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
void TileSheetPixels::initBufferSet(ox::Vec2 const&paneSize) noexcept {
|
||||
m_bufferSet.vao = glutils::generateVertexArrayObject();
|
||||
m_bufferSet.vbo = glutils::generateBuffer();
|
||||
m_bufferSet.ebo = glutils::generateBuffer();
|
||||
update(paneSize);
|
||||
// vbo layout
|
||||
const auto posAttr = static_cast<GLuint>(glGetAttribLocation(m_shader, "vPosition"));
|
||||
glEnableVertexAttribArray(posAttr);
|
||||
glVertexAttribPointer(posAttr, 2, GL_FLOAT, GL_FALSE, VertexVboRowLength * sizeof(float), nullptr);
|
||||
const auto colorAttr = static_cast<GLuint>(glGetAttribLocation(m_shader, "vColor"));
|
||||
glEnableVertexAttribArray(colorAttr);
|
||||
glVertexAttribPointer(colorAttr, 3, GL_FLOAT, GL_FALSE, VertexVboRowLength * sizeof(float),
|
||||
std::bit_cast<void*>(uintptr_t{2 * sizeof(float)}));
|
||||
}
|
||||
|
||||
void TileSheetPixels::update(ox::Vec2 const&paneSize) noexcept {
|
||||
glBindVertexArray(m_bufferSet.vao);
|
||||
setBufferObjects(paneSize);
|
||||
glutils::sendVbo(m_bufferSet);
|
||||
glutils::sendEbo(m_bufferSet);
|
||||
}
|
||||
|
||||
ox::Vec2 TileSheetPixels::pixelSize(ox::Vec2 const&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(
|
||||
ox::Vec2 const&paneSize,
|
||||
unsigned vertexRow,
|
||||
float x, float y,
|
||||
Color16 color,
|
||||
float *vbo,
|
||||
GLuint *ebo) const noexcept {
|
||||
const auto [xmod, ymod] = pixelSize(paneSize);
|
||||
x *= xmod;
|
||||
y *= -ymod;
|
||||
x -= 1.0f;
|
||||
y += 1.0f - ymod;
|
||||
const auto r = redf(color), g = greenf(color), b = bluef(color);
|
||||
// don't worry, these memcpys gets optimized to something much more ideal
|
||||
const ox::Array<float, VertexVboLength> vertices = {
|
||||
x, y, r, g, b, // bottom left
|
||||
x + xmod, y, r, g, b, // bottom right
|
||||
x + xmod, y + ymod, r, g, b, // top right
|
||||
x, y + ymod, r, g, b, // top left
|
||||
};
|
||||
memcpy(vbo, vertices.data(), sizeof(vertices));
|
||||
const ox::Array<GLuint, VertexEboLength> elms = {
|
||||
vertexRow + 0, vertexRow + 1, vertexRow + 2,
|
||||
vertexRow + 2, vertexRow + 3, vertexRow + 0,
|
||||
};
|
||||
memcpy(ebo, elms.data(), sizeof(elms));
|
||||
}
|
||||
|
||||
void TileSheetPixels::setBufferObjects(ox::Vec2 const&paneSize) noexcept {
|
||||
// set buffer lengths
|
||||
const auto subSheet = m_model->activeSubSheet();
|
||||
const auto pal = m_model->pal();
|
||||
const auto width = subSheet->columns * TileWidth;
|
||||
const auto height = subSheet->rows * TileHeight;
|
||||
const auto pixels = static_cast<unsigned>(width * height);
|
||||
m_bufferSet.vertices.resize(pixels * VertexVboLength);
|
||||
m_bufferSet.elements.resize(pixels * VertexEboLength);
|
||||
// set pixels
|
||||
subSheet->walkPixels(m_model->img().bpp, [&](std::size_t i, uint8_t p) {
|
||||
auto color = pal->color(p);
|
||||
const auto pt = idxToPt(static_cast<int>(i), subSheet->columns);
|
||||
const auto fx = static_cast<float>(pt.x);
|
||||
const auto fy = static_cast<float>(pt.y);
|
||||
const auto vbo = &m_bufferSet.vertices[i * VertexVboLength];
|
||||
const auto ebo = &m_bufferSet.elements[i * VertexEboLength];
|
||||
if (i * VertexVboLength + VertexVboLength > m_bufferSet.vertices.size()) {
|
||||
return;
|
||||
}
|
||||
if (i * VertexEboLength + VertexEboLength > m_bufferSet.elements.size()) {
|
||||
return;
|
||||
}
|
||||
if (m_model->pixelSelected(i)) {
|
||||
const auto r = red16(color) / 2;
|
||||
const auto g = (green16(color) + 20) / 2;
|
||||
const auto b = (blue16(color) + 31) / 2;
|
||||
color = color16(r, g, b);
|
||||
}
|
||||
setPixelBufferObject(paneSize, static_cast<unsigned>(i * VertexVboRows), fx, fy, color, vbo, ebo);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/vec.hpp>
|
||||
|
||||
#include <glutils/glutils.hpp>
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include <nostalgia/core/color.hpp>
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class TileSheetPixels {
|
||||
|
||||
private:
|
||||
static constexpr auto VertexVboRows = 4;
|
||||
static constexpr auto VertexVboRowLength = 5;
|
||||
static constexpr auto VertexVboLength = VertexVboRows * VertexVboRowLength;
|
||||
static constexpr auto VertexEboLength = 6;
|
||||
static constexpr auto VShad = R"(
|
||||
{}
|
||||
in vec2 vPosition;
|
||||
in vec3 vColor;
|
||||
out vec3 fColor;
|
||||
uniform vec2 vScroll;
|
||||
void main() {
|
||||
gl_Position = vec4(vPosition + vScroll, 0.0, 1.0);
|
||||
fColor = vColor;
|
||||
})";
|
||||
static constexpr auto FShad = R"(
|
||||
{}
|
||||
in vec3 fColor;
|
||||
out vec4 outColor;
|
||||
void main() {
|
||||
//outColor = vec4(0.0, 0.7, 1.0, 1.0);
|
||||
outColor = vec4(fColor, 1.0);
|
||||
})";
|
||||
|
||||
float m_pixelSizeMod = 1;
|
||||
glutils::GLProgram m_shader;
|
||||
glutils::BufferSet m_bufferSet;
|
||||
const class TileSheetEditorModel *m_model = nullptr;
|
||||
|
||||
public:
|
||||
explicit TileSheetPixels(class TileSheetEditorModel *model) noexcept;
|
||||
|
||||
void setPixelSizeMod(float sm) noexcept;
|
||||
|
||||
ox::Error buildShader() noexcept;
|
||||
|
||||
void draw(bool update, ox::Vec2 const&scroll) noexcept;
|
||||
|
||||
void initBufferSet(ox::Vec2 const&paneSize) noexcept;
|
||||
|
||||
void update(ox::Vec2 const&paneSize) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Vec2 pixelSize(ox::Vec2 const&paneSize) const noexcept;
|
||||
|
||||
private:
|
||||
void setPixelBufferObject(ox::Vec2 const&paneS, unsigned vertexRow, float x, float y, Color16 color, float *vbo, GLuint *ebo) const noexcept;
|
||||
|
||||
void setBufferObjects(ox::Vec2 const&paneS) noexcept;
|
||||
|
||||
};
|
||||
|
||||
}
|
380
src/nostalgia/modules/core/src/tilesheet.cpp
Normal file
380
src/nostalgia/modules/core/src/tilesheet.cpp
Normal file
@ -0,0 +1,380 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/std/size.hpp>
|
||||
#include <ox/std/vector.hpp>
|
||||
|
||||
#include <nostalgia/core/ptidxconv.hpp>
|
||||
#include <nostalgia/core/tilesheet.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
TileSheet::SubSheet::SubSheet(SubSheet &&other) noexcept:
|
||||
id (other.id),
|
||||
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 = {};
|
||||
other.rows = {};
|
||||
}
|
||||
|
||||
TileSheet::SubSheet::SubSheet(
|
||||
SubSheetId pId,
|
||||
ox::CRStringView pName,
|
||||
int pColumns,
|
||||
int pRows,
|
||||
int bpp) noexcept:
|
||||
id(pId),
|
||||
name(pName),
|
||||
columns(pColumns),
|
||||
rows(pRows),
|
||||
pixels(static_cast<std::size_t>(columns * rows * PixelsPerTile) / (bpp == 4 ? 2u : 1u)) {
|
||||
}
|
||||
|
||||
TileSheet::SubSheet::SubSheet(
|
||||
SubSheetId pId,
|
||||
ox::CRStringView pName,
|
||||
int pColumns,
|
||||
int pRows,
|
||||
ox::Vector<uint8_t> pPixels) noexcept:
|
||||
id(pId),
|
||||
name(pName),
|
||||
columns(pColumns),
|
||||
rows(pRows),
|
||||
pixels(std::move(pPixels)) {
|
||||
}
|
||||
|
||||
TileSheet::SubSheet &TileSheet::SubSheet::operator=(TileSheet::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;
|
||||
}
|
||||
|
||||
std::size_t TileSheet::SubSheet::idx(ox::Point const&pt) const noexcept {
|
||||
return ptToIdx(pt, columns);
|
||||
}
|
||||
|
||||
void TileSheet::SubSheet::readPixelsTo(ox::Vector<uint8_t> *pPixels, int8_t pBpp) const noexcept {
|
||||
if (!subsheets.empty()) {
|
||||
for (auto &s: subsheets) {
|
||||
s.readPixelsTo(pPixels);
|
||||
}
|
||||
} else {
|
||||
if (pBpp == 4) {
|
||||
for (auto p: this->pixels) {
|
||||
pPixels->emplace_back(static_cast<uint8_t>(p & 0b1111));
|
||||
pPixels->emplace_back(static_cast<uint8_t>(p >> 4));
|
||||
}
|
||||
} else {
|
||||
for (auto p: this->pixels) {
|
||||
pPixels->emplace_back(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheet::SubSheet::readPixelsTo(ox::Vector<uint8_t> *pPixels) const noexcept {
|
||||
if (!subsheets.empty()) {
|
||||
for (auto &s: subsheets) {
|
||||
s.readPixelsTo(pPixels);
|
||||
}
|
||||
} else {
|
||||
for (auto p : this->pixels) {
|
||||
pPixels->emplace_back(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t TileSheet::SubSheet::unusedPixels() const noexcept {
|
||||
std::size_t childrenSize = 0;
|
||||
for (auto &c : subsheets) {
|
||||
childrenSize += c.size();
|
||||
}
|
||||
return size() - childrenSize;
|
||||
}
|
||||
|
||||
uint8_t TileSheet::SubSheet::getPixel4Bpp(std::size_t idx) const noexcept {
|
||||
if (idx & 1) {
|
||||
return this->pixels[idx / 2] >> 4;
|
||||
} else {
|
||||
return this->pixels[idx / 2] & 0b0000'1111;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t TileSheet::SubSheet::getPixel8Bpp(std::size_t idx) const noexcept {
|
||||
return this->pixels[idx];
|
||||
}
|
||||
|
||||
uint8_t TileSheet::SubSheet::getPixel(int8_t pBpp, std::size_t idx) const noexcept {
|
||||
if (pBpp == 4) {
|
||||
return getPixel4Bpp(idx);
|
||||
} else {
|
||||
return getPixel8Bpp(idx);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t TileSheet::SubSheet::getPixel4Bpp(ox::Point const&pt) const noexcept {
|
||||
const auto idx = ptToIdx(pt, columns);
|
||||
return getPixel4Bpp(idx);
|
||||
}
|
||||
|
||||
uint8_t TileSheet::SubSheet::getPixel8Bpp(ox::Point const&pt) const noexcept {
|
||||
const auto idx = ptToIdx(pt, columns);
|
||||
return getPixel8Bpp(idx);
|
||||
}
|
||||
|
||||
uint8_t TileSheet::SubSheet::getPixel(int8_t pBpp, ox::Point const&pt) const noexcept {
|
||||
const auto idx = ptToIdx(pt, columns);
|
||||
return getPixel(pBpp, idx);
|
||||
}
|
||||
|
||||
void TileSheet::SubSheet::setPixel(int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept {
|
||||
auto &pixel = this->pixels[static_cast<std::size_t>(idx / 2)];
|
||||
if (pBpp == 4) {
|
||||
if (idx & 1) {
|
||||
pixel = static_cast<uint8_t>((pixel & 0b0000'1111) | (palIdx << 4));
|
||||
} else {
|
||||
pixel = (pixel & 0b1111'0000) | (palIdx);
|
||||
}
|
||||
} else {
|
||||
pixel = palIdx;
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheet::SubSheet::setPixel(int8_t pBpp, ox::Point const&pt, uint8_t palIdx) noexcept {
|
||||
const auto idx = ptToIdx(pt, columns);
|
||||
setPixel(pBpp, idx, palIdx);
|
||||
}
|
||||
|
||||
ox::Error TileSheet::SubSheet::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");
|
||||
}
|
||||
}
|
||||
|
||||
unsigned TileSheet::SubSheet::pixelCnt(int8_t pBpp) const noexcept {
|
||||
const auto pixelsSize = static_cast<unsigned>(pixels.size());
|
||||
return pBpp == 4 ? pixelsSize * 2 : pixelsSize;
|
||||
}
|
||||
|
||||
ox::Result<unsigned> TileSheet::SubSheet::getTileOffset(
|
||||
ox::SpanView<ox::StringView> const&pNamePath,
|
||||
int8_t pBpp,
|
||||
std::size_t pIt,
|
||||
unsigned pCurrentTotal) const noexcept {
|
||||
// pIt == pNamePath.size() - 1 &&
|
||||
if (name != pNamePath[pIt]) {
|
||||
return OxError(2, "Wrong branch");
|
||||
}
|
||||
if (pIt == pNamePath.size() - 1) {
|
||||
return pCurrentTotal;
|
||||
}
|
||||
for (auto &sub : subsheets) {
|
||||
auto [offset, err] = sub.getTileOffset(
|
||||
pNamePath, pBpp, pIt + 1, pCurrentTotal);
|
||||
if (!err) {
|
||||
return offset;
|
||||
}
|
||||
pCurrentTotal += sub.pixelCnt(pBpp) / PixelsPerTile;
|
||||
}
|
||||
return OxError(1, "SubSheet not found");
|
||||
}
|
||||
|
||||
ox::Result<SubSheetId> TileSheet::SubSheet::getIdFor(
|
||||
ox::SpanView<ox::StringView> const&pNamePath,
|
||||
std::size_t pIt) const noexcept {
|
||||
for (auto &sub : subsheets) {
|
||||
if (sub.name == pNamePath[pIt]) {
|
||||
if (pIt == pNamePath.size()) {
|
||||
return id;
|
||||
}
|
||||
return getIdFor(pNamePath, pIt + 1);
|
||||
}
|
||||
}
|
||||
return OxError(1, "SubSheet not found");
|
||||
}
|
||||
|
||||
ox::Result<ox::StringView> TileSheet::SubSheet::getNameFor(SubSheetId pId) const noexcept {
|
||||
if (id == pId) {
|
||||
return ox::StringView(name);
|
||||
}
|
||||
for (const auto &sub : subsheets) {
|
||||
const auto [name, err] = sub.getNameFor(pId);
|
||||
if (!err) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return OxError(1, "SubSheet not found");
|
||||
}
|
||||
|
||||
|
||||
TileSheet &TileSheet::operator=(TileSheet const&other) noexcept {
|
||||
if (this != &other) {
|
||||
bpp = other.bpp;
|
||||
idIt = other.idIt;
|
||||
defaultPalette = other.defaultPalette;
|
||||
subsheet = other.subsheet;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
TileSheet &TileSheet::operator=(TileSheet &&other) noexcept {
|
||||
bpp = other.bpp;
|
||||
idIt = other.idIt;
|
||||
defaultPalette = std::move(other.defaultPalette);
|
||||
subsheet = std::move(other.subsheet);
|
||||
return *this;
|
||||
}
|
||||
|
||||
TileSheet::SubSheetIdx TileSheet::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.empty()) {
|
||||
*out.back().value = pSubsheet->subsheets.size() - 1;
|
||||
} else {
|
||||
out.pop_back();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
return validateSubSheetIdx(pIdx, pIdxIt + 1, &pSubsheet->subsheets[pIdx[pIdxIt]]);
|
||||
}
|
||||
|
||||
TileSheet::SubSheetIdx TileSheet::validateSubSheetIdx(const SubSheetIdx &idx) noexcept {
|
||||
return validateSubSheetIdx(idx, 0, &subsheet);
|
||||
}
|
||||
|
||||
const TileSheet::SubSheet &TileSheet::getSubSheet(
|
||||
TileSheet::SubSheetIdx const&idx,
|
||||
std::size_t idxIt,
|
||||
SubSheet const*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]);
|
||||
}
|
||||
|
||||
TileSheet::SubSheet &TileSheet::getSubSheet(
|
||||
TileSheet::SubSheetIdx const&idx,
|
||||
std::size_t idxIt,
|
||||
TileSheet::SubSheet *pSubsheet) noexcept {
|
||||
if (idxIt == idx.size()) {
|
||||
return *pSubsheet;
|
||||
}
|
||||
return getSubSheet(idx, idxIt + 1, &pSubsheet->subsheets[idx[idxIt]]);
|
||||
}
|
||||
|
||||
const TileSheet::SubSheet &TileSheet::getSubSheet(TileSheet::SubSheetIdx const&idx) const noexcept {
|
||||
return getSubSheet(idx, 0, &subsheet);
|
||||
}
|
||||
|
||||
TileSheet::SubSheet &TileSheet::getSubSheet(TileSheet::SubSheetIdx const&idx) noexcept {
|
||||
return getSubSheet(idx, 0, &subsheet);
|
||||
}
|
||||
|
||||
ox::Error TileSheet::addSubSheet(TileSheet::SubSheetIdx const&idx) noexcept {
|
||||
auto &parent = getSubSheet(idx);
|
||||
if (parent.subsheets.size() < 2) {
|
||||
parent.subsheets.emplace_back(idIt++, ox::sfmt("Subsheet {}", parent.subsheets.size()), 1, 1, bpp);
|
||||
} else {
|
||||
parent.subsheets.emplace_back(idIt++, "Subsheet 0", parent.columns, parent.rows, bpp);
|
||||
parent.subsheets.emplace_back(idIt++, "Subsheet 1", 1, 1, bpp);
|
||||
}
|
||||
return OxError(0);
|
||||
}
|
||||
|
||||
ox::Error TileSheet::rmSubSheet(
|
||||
SubSheetIdx const&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]]);
|
||||
}
|
||||
|
||||
ox::Error TileSheet::rmSubSheet(TileSheet::SubSheetIdx const&idx) noexcept {
|
||||
return rmSubSheet(idx, 0, &subsheet);
|
||||
}
|
||||
|
||||
uint8_t TileSheet::getPixel4Bpp(
|
||||
ox::Point const&pt,
|
||||
TileSheet::SubSheetIdx const&subsheetIdx) const noexcept {
|
||||
oxAssert(bpp == 4, "TileSheet::getPixel4Bpp: wrong bpp");
|
||||
auto &s = this->getSubSheet(subsheetIdx);
|
||||
const auto idx = ptToIdx(pt, s.columns);
|
||||
return s.getPixel4Bpp(idx);
|
||||
}
|
||||
|
||||
uint8_t TileSheet::getPixel8Bpp(
|
||||
ox::Point const&pt,
|
||||
TileSheet::SubSheetIdx const&subsheetIdx) const noexcept {
|
||||
oxAssert(bpp == 8, "TileSheet::getPixel8Bpp: wrong bpp");
|
||||
auto &s = this->getSubSheet(subsheetIdx);
|
||||
const auto idx = ptToIdx(pt, s.columns);
|
||||
return s.getPixel8Bpp(idx);
|
||||
}
|
||||
|
||||
ox::Result<SubSheetId> TileSheet::getIdFor(ox::CRStringView path) const noexcept {
|
||||
return subsheet.getIdFor(ox::split<8>(path, '.'));
|
||||
}
|
||||
|
||||
ox::Result<unsigned> TileSheet::getTileOffset(ox::CRStringView pNamePath) const noexcept {
|
||||
return subsheet.getTileOffset(ox::split<8>(pNamePath, '.'), bpp);
|
||||
}
|
||||
|
||||
ox::Result<ox::StringView> TileSheet::getNameFor(SubSheetId pId) const noexcept {
|
||||
return subsheet.getNameFor(pId);
|
||||
}
|
||||
|
||||
ox::Vector<uint8_t> TileSheet::pixels() const noexcept {
|
||||
ox::Vector<uint8_t> out;
|
||||
subsheet.readPixelsTo(&out);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
ox::Vector<uint32_t> resizeTileSheetData(
|
||||
ox::Vector<uint32_t> const&srcPixels,
|
||||
ox::Size const&srcSize,
|
||||
int scale) noexcept {
|
||||
ox::Vector<uint32_t> dst;
|
||||
auto dstWidth = srcSize.width * scale;
|
||||
auto dstHeight = srcSize.height * scale;
|
||||
const auto pixelCnt = dstWidth * dstHeight;
|
||||
dst.resize(static_cast<std::size_t>(pixelCnt));
|
||||
for (auto i = 0; i < pixelCnt; ++i) {
|
||||
const auto dstPt = idxToPt(i, 1, scale);
|
||||
const auto srcPt = dstPt / ox::Point{scale, scale};
|
||||
const auto srcIdx = ptToIdx(srcPt, 1);
|
||||
const auto srcPixel = srcPixels[srcIdx];
|
||||
dst[static_cast<std::size_t>(i)] = srcPixel;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
}
|
11
src/nostalgia/modules/core/test/CMakeLists.txt
Normal file
11
src/nostalgia/modules/core/test/CMakeLists.txt
Normal file
@ -0,0 +1,11 @@
|
||||
add_executable(
|
||||
NostalgiaCoreTest
|
||||
tests.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
NostalgiaCoreTest
|
||||
NostalgiaCore
|
||||
)
|
||||
|
||||
add_test("[NostalgiaCore] readWriteTileSheet" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/NostalgiaCoreTest readWriteTileSheet)
|
38
src/nostalgia/modules/core/test/tests.cpp
Normal file
38
src/nostalgia/modules/core/test/tests.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#undef NDEBUG
|
||||
|
||||
#include <map>
|
||||
#include <ox/std/error.hpp>
|
||||
#include <ox/mc/mc.hpp>
|
||||
#include <nostalgia/core/core.hpp>
|
||||
|
||||
using namespace nostalgia;
|
||||
|
||||
static std::map<ox::StringView, ox::Error(*)()> tests = {
|
||||
{
|
||||
"readWriteTileSheet",
|
||||
[]() -> ox::Error {
|
||||
core::TileSheet in;
|
||||
oxRequire(buff, ox::writeMC(in));
|
||||
oxRequire(out, ox::readMC<core::TileSheet>(buff));
|
||||
oxAssert(in.subsheet.name == out.subsheet.name, "subsheet.name serialization broken");
|
||||
return {};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
int main(int argc, const char **args) {
|
||||
int retval = -1;
|
||||
if (argc > 0) {
|
||||
auto const testName = ox::StringView(args[1]);
|
||||
if (tests.find(testName) != tests.end()) {
|
||||
retval = static_cast<int>(tests[testName]());
|
||||
} else {
|
||||
retval = 1;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
20
src/nostalgia/modules/keelmodules.cpp
Normal file
20
src/nostalgia/modules/keelmodules.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <nostalgia/core/keelmodule.hpp>
|
||||
#include <nostalgia/scene/keelmodule.hpp>
|
||||
|
||||
namespace nostalgia {
|
||||
|
||||
static bool modulesRegistered = false;
|
||||
void registerKeelModules() noexcept {
|
||||
if (modulesRegistered) {
|
||||
return;
|
||||
}
|
||||
modulesRegistered = true;
|
||||
keel::registerModule(core::keelModule());
|
||||
keel::registerModule(scene::keelModule());
|
||||
}
|
||||
|
||||
}
|
11
src/nostalgia/modules/keelmodules.hpp
Normal file
11
src/nostalgia/modules/keelmodules.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace nostalgia {
|
||||
|
||||
void registerKeelModules() noexcept;
|
||||
|
||||
}
|
13
src/nostalgia/modules/scene/CMakeLists.txt
Normal file
13
src/nostalgia/modules/scene/CMakeLists.txt
Normal file
@ -0,0 +1,13 @@
|
||||
add_subdirectory(src)
|
||||
|
||||
target_include_directories(
|
||||
NostalgiaScene PUBLIC
|
||||
include
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY
|
||||
include/nostalgia
|
||||
DESTINATION
|
||||
include
|
||||
)
|
@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <keel/module.hpp>
|
||||
|
||||
namespace nostalgia::scene {
|
||||
|
||||
const keel::Module *keelModule() noexcept;
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nostalgia/core/context.hpp>
|
||||
|
||||
#include "scenestatic.hpp"
|
||||
|
||||
namespace nostalgia::scene {
|
||||
|
||||
class Scene {
|
||||
private:
|
||||
SceneStatic const&m_sceneStatic;
|
||||
|
||||
public:
|
||||
explicit Scene(SceneStatic const&sceneStatic) noexcept;
|
||||
|
||||
ox::Error setupDisplay(core::Context &ctx) const noexcept;
|
||||
|
||||
private:
|
||||
void setupLayer(core::Context&, ox::Vector<uint16_t> const&layer, unsigned layerNo) const noexcept;
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/std/error.hpp>
|
||||
#include <ox/std/size.hpp>
|
||||
#include <ox/std/types.hpp>
|
||||
#include <ox/std/vector.hpp>
|
||||
|
||||
#include <nostalgia/core/tilesheet.hpp>
|
||||
|
||||
namespace nostalgia::scene {
|
||||
|
||||
struct SpriteDoc {
|
||||
|
||||
constexpr static auto TypeName = "net.drinkingtea.nostalgia.scene.SpriteDoc";
|
||||
constexpr static auto TypeVersion = 1;
|
||||
constexpr static auto Preloadable = true;
|
||||
|
||||
ox::String tilesheetPath;
|
||||
ox::Vector<core::SubSheetId> subsheetId;
|
||||
|
||||
};
|
||||
|
||||
struct TileDoc {
|
||||
|
||||
constexpr static auto TypeName = "net.drinkingtea.nostalgia.scene.TileDoc";
|
||||
constexpr static auto TypeVersion = 1;
|
||||
constexpr static auto Preloadable = true;
|
||||
|
||||
core::SubSheetId subsheetId = -1;
|
||||
ox::String subsheetPath;
|
||||
uint8_t type = 0;
|
||||
ox::Array<uint8_t, 4> layerAttachments;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr ox::Result<core::SubSheetId> getSubsheetId(core::TileSheet const&ts) const noexcept {
|
||||
// prefer the already present ID
|
||||
if (subsheetId > -1) {
|
||||
return subsheetId;
|
||||
}
|
||||
return ts.getIdFor(subsheetPath);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr ox::Result<ox::StringView> getSubsheetPath(
|
||||
core::TileSheet const&ts) const noexcept {
|
||||
// prefer the already present path
|
||||
if (!subsheetPath.len()) {
|
||||
return ts.getNameFor(subsheetId);
|
||||
}
|
||||
return ox::StringView(subsheetPath);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
oxModelBegin(TileDoc)
|
||||
oxModelFieldRename(subsheet_id, subsheetId)
|
||||
oxModelFieldRename(subsheet_path, subsheetPath)
|
||||
oxModelField(type)
|
||||
oxModelFieldRename(layer_attachments, layerAttachments)
|
||||
oxModelEnd()
|
||||
|
||||
struct SceneDoc {
|
||||
|
||||
using TileMapRow = ox::Vector<TileDoc>;
|
||||
using TileMapLayer = ox::Vector<TileMapRow>;
|
||||
using TileMap = ox::Vector<TileMapLayer>;
|
||||
|
||||
constexpr static auto TypeName = "net.drinkingtea.nostalgia.scene.SceneDoc";
|
||||
constexpr static auto TypeVersion = 1;
|
||||
constexpr static auto Preloadable = true;
|
||||
|
||||
ox::String tilesheet; // path
|
||||
ox::Vector<ox::String> palettes; // paths
|
||||
TileMap tiles;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr ox::Size size(std::size_t layerIdx) const noexcept {
|
||||
const auto &layer = this->tiles[layerIdx];
|
||||
const auto rowCnt = static_cast<int>(layer.size());
|
||||
if (!rowCnt) {
|
||||
return {};
|
||||
}
|
||||
auto colCnt = layer[0].size();
|
||||
// find shortest row (they should all be the same, but you know this data
|
||||
// could come from a file)
|
||||
for (auto const&row : layer) {
|
||||
colCnt = ox::min(colCnt, row.size());
|
||||
}
|
||||
return {static_cast<int>(colCnt), rowCnt};
|
||||
}
|
||||
};
|
||||
|
||||
oxModelBegin(SceneDoc)
|
||||
oxModelField(tilesheet)
|
||||
oxModelField(palettes)
|
||||
oxModelField(tiles)
|
||||
oxModelEnd()
|
||||
|
||||
|
||||
constexpr void setTopEdge(uint8_t &layerAttachments, unsigned val) noexcept {
|
||||
const auto val8 = static_cast<uint8_t>(val);
|
||||
layerAttachments = (layerAttachments & 0b11111100) | val8;
|
||||
}
|
||||
constexpr void setBottomEdge(uint8_t &layerAttachments, unsigned val) noexcept {
|
||||
const auto val8 = static_cast<uint8_t>(val);
|
||||
layerAttachments = (layerAttachments & 0b11110011) | static_cast<uint8_t>(val8 << 2);
|
||||
}
|
||||
constexpr void setLeftEdge(uint8_t &layerAttachments, unsigned val) noexcept {
|
||||
const auto val8 = static_cast<uint8_t>(val);
|
||||
layerAttachments = (layerAttachments & 0b11001111) | static_cast<uint8_t>(val8 << 4);
|
||||
}
|
||||
constexpr void setRightEdge(uint8_t &layerAttachments, unsigned val) noexcept {
|
||||
const auto val8 = static_cast<uint8_t>(val);
|
||||
layerAttachments = (layerAttachments & 0b00111111) | static_cast<uint8_t>(val8 << 6);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr unsigned topEdge(uint8_t layerAttachments) noexcept {
|
||||
return layerAttachments & 0b11;
|
||||
}
|
||||
[[nodiscard]]
|
||||
constexpr unsigned bottomEdge(uint8_t layerAttachments) noexcept {
|
||||
return (layerAttachments >> 2) & 0b11;
|
||||
}
|
||||
[[nodiscard]]
|
||||
constexpr unsigned leftEdge(uint8_t layerAttachments) noexcept {
|
||||
return (layerAttachments >> 4) & 0b11;
|
||||
}
|
||||
[[nodiscard]]
|
||||
constexpr unsigned rightEdge(uint8_t layerAttachments) noexcept {
|
||||
return (layerAttachments >> 6) & 0b11;
|
||||
}
|
||||
|
||||
|
||||
struct SceneStatic {
|
||||
|
||||
constexpr static auto TypeName = "net.drinkingtea.nostalgia.scene.SceneStatic";
|
||||
constexpr static auto TypeVersion = 1;
|
||||
constexpr static auto Preloadable = true;
|
||||
|
||||
struct Tile {
|
||||
uint16_t &tileMapIdx;
|
||||
uint8_t &tileType;
|
||||
uint8_t &layerAttachments;
|
||||
constexpr Tile(uint16_t &pTileMapIdx, uint8_t &pTileType, uint8_t &pLayerAttachments) noexcept:
|
||||
tileMapIdx(pTileMapIdx),
|
||||
tileType(pTileType),
|
||||
layerAttachments(pLayerAttachments) {
|
||||
}
|
||||
};
|
||||
struct Layer {
|
||||
uint16_t &columns;
|
||||
uint16_t &rows;
|
||||
ox::Vector<uint16_t> &tileMapIdx;
|
||||
ox::Vector<uint8_t> &tileType;
|
||||
ox::Vector<uint8_t> &layerAttachments;
|
||||
constexpr Layer(
|
||||
uint16_t &pColumns,
|
||||
uint16_t &pRows,
|
||||
ox::Vector<uint16_t> &pTileMapIdx,
|
||||
ox::Vector<uint8_t> &pTileType,
|
||||
ox::Vector<uint8_t> &pLayerAttachments) noexcept:
|
||||
columns(pColumns),
|
||||
rows(pRows),
|
||||
tileMapIdx(pTileMapIdx),
|
||||
tileType(pTileType),
|
||||
layerAttachments(pLayerAttachments) {
|
||||
}
|
||||
[[nodiscard]]
|
||||
constexpr Tile tile(std::size_t i) noexcept {
|
||||
return {tileMapIdx[i], tileType[i], layerAttachments[i]};
|
||||
}
|
||||
constexpr auto setDimensions(ox::Size dim) noexcept {
|
||||
columns = static_cast<uint16_t>(dim.width);
|
||||
rows = static_cast<uint16_t>(dim.height);
|
||||
const auto tileCnt = static_cast<unsigned>(columns * rows);
|
||||
tileMapIdx.resize(tileCnt);
|
||||
tileType.resize(tileCnt);
|
||||
layerAttachments.resize(tileCnt);
|
||||
}
|
||||
};
|
||||
|
||||
ox::FileAddress tilesheet;
|
||||
ox::Vector<ox::FileAddress> palettes;
|
||||
// tile layer data
|
||||
ox::Vector<uint16_t> columns;
|
||||
ox::Vector<uint16_t> rows;
|
||||
ox::Vector<ox::Vector<uint16_t>> tileMapIdx;
|
||||
ox::Vector<ox::Vector<uint8_t>> tileType;
|
||||
ox::Vector<ox::Vector<uint8_t>> layerAttachments;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr Layer layer(std::size_t i) noexcept {
|
||||
return {
|
||||
columns[i],
|
||||
rows[i],
|
||||
tileMapIdx[i],
|
||||
tileType[i],
|
||||
layerAttachments[i],
|
||||
};
|
||||
}
|
||||
|
||||
constexpr auto setLayerCnt(std::size_t layerCnt) noexcept {
|
||||
this->layerAttachments.resize(layerCnt);
|
||||
this->columns.resize(layerCnt);
|
||||
this->rows.resize(layerCnt);
|
||||
this->tileMapIdx.resize(layerCnt);
|
||||
this->tileType.resize(layerCnt);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
oxModelBegin(SceneStatic)
|
||||
oxModelField(tilesheet)
|
||||
oxModelField(palettes)
|
||||
oxModelField(columns)
|
||||
oxModelField(rows)
|
||||
oxModelField(tileMapIdx)
|
||||
oxModelField(tileType)
|
||||
oxModelField(layerAttachments)
|
||||
oxModelEnd()
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
namespace nostalgia::scene {
|
||||
|
||||
const studio::Module *studioModule() noexcept;
|
||||
|
||||
}
|
29
src/nostalgia/modules/scene/src/CMakeLists.txt
Normal file
29
src/nostalgia/modules/scene/src/CMakeLists.txt
Normal file
@ -0,0 +1,29 @@
|
||||
|
||||
add_library(
|
||||
NostalgiaScene
|
||||
scene.cpp
|
||||
scenestatic.cpp
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
NostalgiaScene PUBLIC
|
||||
../include
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
NostalgiaScene PUBLIC
|
||||
NostalgiaCore
|
||||
)
|
||||
|
||||
add_subdirectory(keel)
|
||||
if(NOSTALGIA_BUILD_STUDIO)
|
||||
add_subdirectory(studio)
|
||||
endif()
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
NostalgiaScene
|
||||
DESTINATION
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
)
|
17
src/nostalgia/modules/scene/src/keel/CMakeLists.txt
Normal file
17
src/nostalgia/modules/scene/src/keel/CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
||||
add_library(
|
||||
NostalgiaScene-Keel
|
||||
keelmodule.cpp
|
||||
typeconv.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
NostalgiaScene-Keel PUBLIC
|
||||
Keel
|
||||
NostalgiaScene
|
||||
)
|
||||
install(
|
||||
TARGETS
|
||||
NostalgiaScene-Keel
|
||||
LIBRARY DESTINATION
|
||||
${NOSTALGIA_DIST_MODULE}
|
||||
)
|
52
src/nostalgia/modules/scene/src/keel/keelmodule.cpp
Normal file
52
src/nostalgia/modules/scene/src/keel/keelmodule.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <keel/module.hpp>
|
||||
|
||||
#include <nostalgia/scene/scenestatic.hpp>
|
||||
|
||||
#include "typeconv.hpp"
|
||||
|
||||
namespace nostalgia::scene {
|
||||
|
||||
class SceneModule: public keel::Module {
|
||||
private:
|
||||
SceneDocToSceneStaticConverter m_sceneDocToSceneStaticConverter;
|
||||
|
||||
public:
|
||||
[[nodiscard]]
|
||||
ox::String id() const noexcept override {
|
||||
return ox::String("net.drinkingtea.nostalgia.scene");
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Vector<keel::TypeDescGenerator> types() const noexcept override {
|
||||
return {
|
||||
keel::generateTypeDesc<SceneDoc>,
|
||||
keel::generateTypeDesc<SceneStatic>,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Vector<const keel::BaseConverter*> converters() const noexcept override {
|
||||
return {
|
||||
&m_sceneDocToSceneStaticConverter,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Vector<keel::PackTransform> packTransforms() const noexcept override {
|
||||
return {
|
||||
keel::transformRule<SceneDoc, SceneStatic>,
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
static const SceneModule mod;
|
||||
const keel::Module *keelModule() noexcept {
|
||||
return &mod;
|
||||
}
|
||||
|
||||
}
|
68
src/nostalgia/modules/scene/src/keel/typeconv.cpp
Normal file
68
src/nostalgia/modules/scene/src/keel/typeconv.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
#include <keel/media.hpp>
|
||||
|
||||
#include "typeconv.hpp"
|
||||
|
||||
namespace nostalgia::scene {
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr unsigned adjustLayerAttachment(unsigned layer, unsigned attachment) noexcept {
|
||||
if (attachment == 0) {
|
||||
return layer;
|
||||
} else {
|
||||
return attachment - 1;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void setLayerAttachments(unsigned layer, TileDoc const&srcTile, SceneStatic::Tile &dstTile) noexcept {
|
||||
setTopEdge(
|
||||
dstTile.layerAttachments,
|
||||
adjustLayerAttachment(layer, srcTile.layerAttachments[0]));
|
||||
setBottomEdge(
|
||||
dstTile.layerAttachments,
|
||||
adjustLayerAttachment(layer, srcTile.layerAttachments[1]));
|
||||
setLeftEdge(
|
||||
dstTile.layerAttachments,
|
||||
adjustLayerAttachment(layer, srcTile.layerAttachments[2]));
|
||||
setRightEdge(
|
||||
dstTile.layerAttachments,
|
||||
adjustLayerAttachment(layer, srcTile.layerAttachments[3]));
|
||||
}
|
||||
|
||||
ox::Error SceneDocToSceneStaticConverter::convert(
|
||||
keel::Context &ctx,
|
||||
SceneDoc &src,
|
||||
SceneStatic &dst) const noexcept {
|
||||
oxRequire(ts, keel::readObj<core::TileSheet>(ctx, src.tilesheet));
|
||||
const auto layerCnt = src.tiles.size();
|
||||
dst.setLayerCnt(layerCnt);
|
||||
dst.tilesheet = ox::FileAddress(src.tilesheet);
|
||||
dst.palettes.reserve(src.palettes.size());
|
||||
for (const auto &pal : src.palettes) {
|
||||
dst.palettes.emplace_back(pal);
|
||||
}
|
||||
for (auto layerIdx = 0u; const auto &layer : src.tiles) {
|
||||
const auto layerDim = src.size(layerIdx);
|
||||
auto dstLayer = dst.layer(layerIdx);
|
||||
dstLayer.setDimensions(layerDim);
|
||||
for (auto tileIdx = 0u; const auto &row : layer) {
|
||||
for (const auto &srcTile : row) {
|
||||
auto dstTile = dstLayer.tile(tileIdx);
|
||||
dstTile.tileType = srcTile.type;
|
||||
oxRequire(path, srcTile.getSubsheetPath(*ts));
|
||||
oxRequire(mapIdx, ts->getTileOffset(path));
|
||||
dstTile.tileMapIdx = static_cast<uint16_t>(mapIdx);
|
||||
setLayerAttachments(layerIdx, srcTile, dstTile);
|
||||
++tileIdx;
|
||||
}
|
||||
}
|
||||
++layerIdx;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
17
src/nostalgia/modules/scene/src/keel/typeconv.hpp
Normal file
17
src/nostalgia/modules/scene/src/keel/typeconv.hpp
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <keel/typeconv.hpp>
|
||||
|
||||
#include <nostalgia/scene/scenestatic.hpp>
|
||||
|
||||
namespace nostalgia::scene {
|
||||
|
||||
class SceneDocToSceneStaticConverter: public keel::Converter<SceneDoc, SceneStatic> {
|
||||
ox::Error convert(keel::Context&, SceneDoc &src, SceneStatic &dst) const noexcept final;
|
||||
};
|
||||
|
||||
}
|
54
src/nostalgia/modules/scene/src/scene.cpp
Normal file
54
src/nostalgia/modules/scene/src/scene.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
|
||||
#include <nostalgia/scene/scene.hpp>
|
||||
|
||||
namespace nostalgia::scene {
|
||||
|
||||
Scene::Scene(SceneStatic const&sceneStatic) noexcept:
|
||||
m_sceneStatic(sceneStatic) {
|
||||
}
|
||||
|
||||
ox::Error Scene::setupDisplay(core::Context &ctx) const noexcept {
|
||||
if (m_sceneStatic.palettes.empty()) {
|
||||
return OxError(1, "Scene has no palettes");
|
||||
}
|
||||
auto const&palette = m_sceneStatic.palettes[0];
|
||||
oxReturnError(core::loadBgTileSheet(ctx, 0, m_sceneStatic.tilesheet));
|
||||
oxReturnError(core::loadBgPalette(ctx, palette));
|
||||
// disable all backgrounds
|
||||
core::setBgStatus(ctx, 0);
|
||||
for (auto layerNo = 0u; auto const&layer : m_sceneStatic.tileMapIdx) {
|
||||
setupLayer(ctx, layer, layerNo);
|
||||
++layerNo;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void Scene::setupLayer(
|
||||
core::Context &ctx,
|
||||
ox::Vector<uint16_t> const&layer,
|
||||
unsigned layerNo) const noexcept {
|
||||
core::setBgStatus(ctx, layerNo, true);
|
||||
core::setBgCbb(ctx, layerNo, 0);
|
||||
auto x = 0;
|
||||
auto y = 0;
|
||||
const auto width = m_sceneStatic.rows[layerNo];
|
||||
for (auto const&tile : layer) {
|
||||
const auto tile8 = static_cast<uint8_t>(tile);
|
||||
core::setBgTile(ctx, layerNo, x, y, tile8);
|
||||
core::setBgTile(ctx, layerNo, x + 1, y, tile8 + 1);
|
||||
core::setBgTile(ctx, layerNo, x, y + 1, tile8 + 2);
|
||||
core::setBgTile(ctx, layerNo, x + 1, y + 1, tile8 + 3);
|
||||
x += 2;
|
||||
if (x >= width * 2) {
|
||||
x = 0;
|
||||
y += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
11
src/nostalgia/modules/scene/src/scenestatic.cpp
Normal file
11
src/nostalgia/modules/scene/src/scenestatic.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <nostalgia/scene/scenestatic.hpp>
|
||||
|
||||
namespace nostalgia::scene {
|
||||
|
||||
|
||||
|
||||
}
|
20
src/nostalgia/modules/scene/src/studio/CMakeLists.txt
Normal file
20
src/nostalgia/modules/scene/src/studio/CMakeLists.txt
Normal file
@ -0,0 +1,20 @@
|
||||
add_library(
|
||||
NostalgiaScene-Studio
|
||||
studiomodule.cpp
|
||||
sceneeditor-imgui.cpp
|
||||
sceneeditor.cpp
|
||||
sceneeditorview.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
NostalgiaScene-Studio PUBLIC
|
||||
NostalgiaScene
|
||||
Studio
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
NostalgiaScene-Studio
|
||||
LIBRARY DESTINATION
|
||||
${NOSTALGIA_DIST_MODULE}
|
||||
)
|
57
src/nostalgia/modules/scene/src/studio/sceneeditor-imgui.cpp
Normal file
57
src/nostalgia/modules/scene/src/studio/sceneeditor-imgui.cpp
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <keel/media.hpp>
|
||||
|
||||
#include "sceneeditor-imgui.hpp"
|
||||
|
||||
namespace nostalgia::scene {
|
||||
|
||||
SceneEditorImGui::SceneEditorImGui(turbine::Context &ctx, ox::StringView path):
|
||||
Editor(path),
|
||||
m_ctx(ctx),
|
||||
m_editor(m_ctx, path),
|
||||
m_view(m_ctx, m_editor.scene()) {
|
||||
setRequiresConstantRefresh(false);
|
||||
}
|
||||
|
||||
void SceneEditorImGui::draw(turbine::Context&) noexcept {
|
||||
auto const paneSize = ImGui::GetContentRegionAvail();
|
||||
m_view.draw(ox::Size{static_cast<int>(paneSize.x), static_cast<int>(paneSize.y)});
|
||||
auto &fb = m_view.framebuffer();
|
||||
auto const srcH = static_cast<float>(fb.height) / static_cast<float>(fb.width);
|
||||
auto const dstH = paneSize.y / paneSize.x;
|
||||
float xScale{}, yScale{};
|
||||
if (dstH > srcH) {
|
||||
// crop off width
|
||||
xScale = srcH / dstH;
|
||||
yScale = 1;
|
||||
} else {
|
||||
auto const srcW = static_cast<float>(fb.width) / static_cast<float>(fb.height);
|
||||
auto const dstW = (paneSize.x / paneSize.y);
|
||||
xScale = 1;
|
||||
yScale = srcW / dstW;
|
||||
}
|
||||
uintptr_t const buffId = fb.color.id;
|
||||
ImGui::Image(
|
||||
std::bit_cast<void*>(buffId),
|
||||
paneSize,
|
||||
ImVec2(0, 1),
|
||||
ImVec2(xScale, 1 - yScale));
|
||||
}
|
||||
|
||||
void SceneEditorImGui::onActivated() noexcept {
|
||||
oxLogError(m_view.setupScene());
|
||||
}
|
||||
|
||||
ox::Error SceneEditorImGui::saveItem() noexcept {
|
||||
const auto sctx = applicationData<studio::StudioContext>(m_ctx);
|
||||
oxReturnError(sctx->project->writeObj(itemPath(), m_editor.scene()));
|
||||
oxReturnError(keelCtx(m_ctx).assetManager.setAsset(itemPath(), m_editor.scene()));
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
35
src/nostalgia/modules/scene/src/studio/sceneeditor-imgui.hpp
Normal file
35
src/nostalgia/modules/scene/src/studio/sceneeditor-imgui.hpp
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
#include "sceneeditor.hpp"
|
||||
#include "sceneeditorview.hpp"
|
||||
|
||||
namespace nostalgia::scene {
|
||||
|
||||
class SceneEditorImGui: public studio::Editor {
|
||||
|
||||
private:
|
||||
turbine::Context &m_ctx;
|
||||
SceneEditor m_editor;
|
||||
SceneEditorView m_view;
|
||||
|
||||
public:
|
||||
SceneEditorImGui(turbine::Context &ctx, ox::StringView path);
|
||||
|
||||
void draw(turbine::Context&) noexcept final;
|
||||
|
||||
void onActivated() noexcept override;
|
||||
|
||||
protected:
|
||||
ox::Error saveItem() noexcept final;
|
||||
|
||||
};
|
||||
|
||||
}
|
16
src/nostalgia/modules/scene/src/studio/sceneeditor.cpp
Normal file
16
src/nostalgia/modules/scene/src/studio/sceneeditor.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <keel/keel.hpp>
|
||||
|
||||
#include "sceneeditor.hpp"
|
||||
|
||||
namespace nostalgia::scene {
|
||||
|
||||
SceneEditor::SceneEditor(turbine::Context &ctx, ox::CRStringView path):
|
||||
m_ctx(ctx),
|
||||
m_scene(*keel::readObj<SceneStatic>(keelCtx(m_ctx), path).unwrapThrow()) {
|
||||
}
|
||||
|
||||
}
|
34
src/nostalgia/modules/scene/src/studio/sceneeditor.hpp
Normal file
34
src/nostalgia/modules/scene/src/studio/sceneeditor.hpp
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
#include <nostalgia/scene/scene.hpp>
|
||||
|
||||
namespace nostalgia::scene {
|
||||
|
||||
class SceneEditor {
|
||||
|
||||
private:
|
||||
turbine::Context &m_ctx;
|
||||
ox::String m_itemName;
|
||||
ox::String m_itemPath;
|
||||
SceneStatic m_scene;
|
||||
|
||||
public:
|
||||
SceneEditor(turbine::Context &ctx, ox::CRStringView path);
|
||||
|
||||
[[nodiscard]]
|
||||
SceneStatic const&scene() const noexcept {
|
||||
return m_scene;
|
||||
}
|
||||
|
||||
protected:
|
||||
ox::Error saveItem() noexcept;
|
||||
|
||||
};
|
||||
|
||||
}
|
36
src/nostalgia/modules/scene/src/studio/sceneeditorview.cpp
Normal file
36
src/nostalgia/modules/scene/src/studio/sceneeditorview.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
|
||||
#include "sceneeditorview.hpp"
|
||||
|
||||
namespace nostalgia::scene {
|
||||
|
||||
SceneEditorView::SceneEditorView(turbine::Context &tctx, SceneStatic const&sceneStatic):
|
||||
m_cctx(core::init(tctx, {.glInstallDrawer = false}).unwrapThrow()),
|
||||
m_sceneStatic(sceneStatic),
|
||||
m_scene(m_sceneStatic) {
|
||||
}
|
||||
|
||||
ox::Error SceneEditorView::setupScene() noexcept {
|
||||
glutils::resizeInitFrameBuffer(m_frameBuffer, core::gl::drawSize(m_scale));
|
||||
return m_scene.setupDisplay(*m_cctx);
|
||||
}
|
||||
|
||||
void SceneEditorView::draw(ox::Size const&targetSz) noexcept {
|
||||
auto const scaleSz = targetSz / core::gl::drawSize(1);
|
||||
if (m_scaleSz != scaleSz) [[unlikely]] {
|
||||
m_scale = ox::max(1, ox::max(scaleSz.width, scaleSz.height));
|
||||
glutils::resizeInitFrameBuffer(m_frameBuffer, core::gl::drawSize(m_scale));
|
||||
}
|
||||
glutils::FrameBufferBind const frameBufferBind(m_frameBuffer);
|
||||
core::gl::draw(*m_cctx, m_scale);
|
||||
}
|
||||
|
||||
glutils::FrameBuffer const&SceneEditorView::framebuffer() const noexcept {
|
||||
return m_frameBuffer;
|
||||
}
|
||||
|
||||
}
|
37
src/nostalgia/modules/scene/src/studio/sceneeditorview.hpp
Normal file
37
src/nostalgia/modules/scene/src/studio/sceneeditorview.hpp
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glutils/glutils.hpp>
|
||||
|
||||
#include <nostalgia/core/context.hpp>
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
#include <nostalgia/scene/scene.hpp>
|
||||
|
||||
namespace nostalgia::scene {
|
||||
|
||||
class SceneEditorView {
|
||||
|
||||
private:
|
||||
core::ContextUPtr m_cctx;
|
||||
SceneStatic const&m_sceneStatic;
|
||||
Scene m_scene;
|
||||
glutils::FrameBuffer m_frameBuffer;
|
||||
int m_scale = 1;
|
||||
ox::Size m_scaleSz = core::gl::drawSize(m_scale);
|
||||
|
||||
public:
|
||||
SceneEditorView(turbine::Context &ctx, SceneStatic const&sceneStatic);
|
||||
|
||||
ox::Error setupScene() noexcept;
|
||||
|
||||
void draw(ox::Size const&targetSz) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
glutils::FrameBuffer const&framebuffer() const noexcept;
|
||||
|
||||
};
|
||||
|
||||
}
|
31
src/nostalgia/modules/scene/src/studio/studiomodule.cpp
Normal file
31
src/nostalgia/modules/scene/src/studio/studiomodule.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include "sceneeditor-imgui.hpp"
|
||||
|
||||
namespace nostalgia::scene {
|
||||
|
||||
constexpr ox::StringLiteral FileExt_nscn("nscn");
|
||||
|
||||
class StudioModule: public studio::Module {
|
||||
public:
|
||||
ox::Vector<studio::EditorMaker> editors(turbine::Context &ctx) const noexcept override {
|
||||
return {
|
||||
studio::editorMaker<SceneEditorImGui>(ctx, FileExt_nscn),
|
||||
};
|
||||
}
|
||||
ox::Vector<ox::UPtr<studio::ItemMaker>> itemMakers(turbine::Context&) const noexcept override {
|
||||
ox::Vector<ox::UPtr<studio::ItemMaker>> out;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
static StudioModule const mod;
|
||||
const studio::Module *studioModule() noexcept {
|
||||
return &mod;
|
||||
}
|
||||
|
||||
}
|
22
src/nostalgia/modules/studiomodules.cpp
Normal file
22
src/nostalgia/modules/studiomodules.cpp
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <studioapp/studioapp.hpp>
|
||||
|
||||
#include <nostalgia/core/studiomodule.hpp>
|
||||
#include <nostalgia/scene/studiomodule.hpp>
|
||||
|
||||
namespace nostalgia {
|
||||
|
||||
static bool modulesRegistered = false;
|
||||
void registerStudioModules() noexcept {
|
||||
if (modulesRegistered) {
|
||||
return;
|
||||
}
|
||||
modulesRegistered = true;
|
||||
studio::registerModule(core::studioModule());
|
||||
studio::registerModule(scene::studioModule());
|
||||
}
|
||||
|
||||
}
|
11
src/nostalgia/modules/studiomodules.hpp
Normal file
11
src/nostalgia/modules/studiomodules.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace nostalgia {
|
||||
|
||||
void registerStudioModules() noexcept;
|
||||
|
||||
}
|
35
src/nostalgia/player/CMakeLists.txt
Normal file
35
src/nostalgia/player/CMakeLists.txt
Normal file
@ -0,0 +1,35 @@
|
||||
add_executable(
|
||||
nostalgia WIN32
|
||||
app.cpp
|
||||
main.cpp
|
||||
)
|
||||
|
||||
# enable LTO
|
||||
if(NOT WIN32)
|
||||
set_property(TARGET nostalgia PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
endif()
|
||||
|
||||
if(COMMAND OBJCOPY_FILE)
|
||||
set_target_properties(nostalgia
|
||||
PROPERTIES
|
||||
LINK_FLAGS ${LINKER_FLAGS}
|
||||
COMPILER_FLAGS "-mthumb -mthumb-interwork"
|
||||
)
|
||||
|
||||
OBJCOPY_FILE(nostalgia)
|
||||
#PADBIN_FILE(nostalgia)
|
||||
endif()
|
||||
|
||||
target_link_libraries(
|
||||
nostalgia
|
||||
NostalgiaKeelModules
|
||||
OxLogConn
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
nostalgia
|
||||
DESTINATION
|
||||
bin
|
||||
)
|
||||
|
160
src/nostalgia/player/app.cpp
Normal file
160
src/nostalgia/player/app.cpp
Normal file
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <keel/media.hpp>
|
||||
#include <turbine/turbine.hpp>
|
||||
|
||||
#include <nostalgia/core/core.hpp>
|
||||
#include <nostalgia/scene/scene.hpp>
|
||||
|
||||
using namespace nostalgia;
|
||||
|
||||
static int spriteX{};
|
||||
static int spriteY{};
|
||||
static bool s_paused = false;
|
||||
|
||||
[[maybe_unused]]
|
||||
static int testUpdateHandler(turbine::Context &tctx) noexcept {
|
||||
auto &cctx = *turbine::applicationData<core::Context>(tctx);
|
||||
constexpr auto sleepTime = 16;
|
||||
if (s_paused) {
|
||||
return sleepTime;
|
||||
}
|
||||
int xmod = 0;
|
||||
int ymod = 0;
|
||||
if (buttonDown(tctx, turbine::Alpha_D) || buttonDown(tctx, turbine::GamePad_Right)) {
|
||||
xmod = 2;
|
||||
} else if (buttonDown(tctx, turbine::Alpha_A) || buttonDown(tctx, turbine::GamePad_Left)) {
|
||||
xmod = -2;
|
||||
}
|
||||
if (buttonDown(tctx, turbine::Alpha_S) || buttonDown(tctx, turbine::GamePad_Down)) {
|
||||
ymod = 2;
|
||||
} else if (buttonDown(tctx, turbine::Alpha_W) || buttonDown(tctx, turbine::GamePad_Up)) {
|
||||
ymod = -2;
|
||||
}
|
||||
if (!xmod && !ymod) {
|
||||
spriteX += 1;
|
||||
}
|
||||
spriteX += xmod;
|
||||
spriteY += ymod;
|
||||
constexpr ox::StringView sprites = "nostalgia";
|
||||
for (unsigned i = 0; i < sprites.len(); ++i) {
|
||||
auto const c = static_cast<unsigned>(sprites[i] - ('a' - 1));
|
||||
core::setSprite(cctx, i, {
|
||||
.enabled = true,
|
||||
.x = spriteX + 8 * (static_cast<int>(i) + 1),
|
||||
.y = spriteY,
|
||||
.tileIdx = c,
|
||||
.priority = 1,
|
||||
});
|
||||
}
|
||||
return sleepTime;
|
||||
}
|
||||
|
||||
[[maybe_unused]]
|
||||
static void testKeyEventHandler(turbine::Context &tctx, turbine::Key key, bool down) noexcept {
|
||||
if (down) {
|
||||
if (key == turbine::Key::Alpha_Q) {
|
||||
turbine::requestShutdown(tctx);
|
||||
} else if (key == turbine::Key::Alpha_P) {
|
||||
s_paused = !s_paused;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[maybe_unused]]
|
||||
static ox::Error runTest(turbine::Context &tctx) {
|
||||
constexpr ox::FileAddress TileSheetAddr = ox::StringLiteral("/TileSheets/Charset.ng");
|
||||
constexpr ox::FileAddress PaletteAddr = ox::StringLiteral("/Palettes/Chester.npal");
|
||||
oxRequireM(cctx, core::init(tctx));
|
||||
turbine::setApplicationData(tctx, cctx.get());
|
||||
oxRequire(tsStat, turbine::rom(tctx)->stat(PaletteAddr));
|
||||
oxReturnError(core::loadSpriteTileSheet(*cctx, TileSheetAddr));
|
||||
oxReturnError(core::loadSpritePalette(*cctx, PaletteAddr));
|
||||
oxReturnError(core::initConsole(*cctx));
|
||||
core::puts(*cctx, 10, 9, "DOPENESS!!!");
|
||||
turbine::setUpdateHandler(tctx, testUpdateHandler);
|
||||
turbine::setKeyEventHandler(tctx, testKeyEventHandler);
|
||||
return turbine::run(tctx);
|
||||
}
|
||||
|
||||
|
||||
[[maybe_unused]]
|
||||
static ox::Error runTileSheetSetTest(turbine::Context &tctx) {
|
||||
// this should make the screen display 'ABCDB'
|
||||
constexpr ox::FileAddress PaletteAddr = ox::StringLiteral("/Palettes/Charset.npal");
|
||||
oxRequireM(cctx, core::init(tctx));
|
||||
turbine::setApplicationData(tctx, cctx.get());
|
||||
oxRequire(tsStat, turbine::rom(tctx)->stat(PaletteAddr));
|
||||
core::TileSheetSet const set{
|
||||
.bpp = 4,
|
||||
.entries = {
|
||||
{ .tilesheet = ox::StringLiteral("/TileSheets/Chester.ng"), .sections{{.begin = 0, .tiles = 1}} },
|
||||
{ .tilesheet = ox::StringLiteral("/TileSheets/AB.ng"), .sections{{.begin = 0, .tiles = 2}} },
|
||||
{ .tilesheet = ox::StringLiteral("/TileSheets/CD.ng"), .sections{{.begin = 0, .tiles = 2}} },
|
||||
{ .tilesheet = ox::StringLiteral("/TileSheets/AB.ng"), .sections{{.begin = 1, .tiles = 1}} },
|
||||
},
|
||||
};
|
||||
oxReturnError(core::loadBgTileSheet(*cctx, 0, set));
|
||||
oxReturnError(core::loadSpriteTileSheet(*cctx, set));
|
||||
oxReturnError(core::loadBgPalette(*cctx, PaletteAddr));
|
||||
oxReturnError(core::loadSpritePalette(*cctx, PaletteAddr));
|
||||
core::setBgStatus(*cctx, 0, true);
|
||||
core::setBgTile(*cctx, 0, 10, 9, 1);
|
||||
core::setBgTile(*cctx, 0, 11, 9, 2);
|
||||
core::setBgTile(*cctx, 0, 13, 9, 4);
|
||||
core::setSprite(*cctx, 16, {
|
||||
.enabled = true,
|
||||
.x = 12 * 8,
|
||||
.y = 9 * 8,
|
||||
.tileIdx = 3,
|
||||
.bpp = static_cast<unsigned>(set.bpp),
|
||||
});
|
||||
core::setSprite(*cctx, 17, {
|
||||
.enabled = true,
|
||||
.x = 14 * 8,
|
||||
.y = 9 * 8,
|
||||
.tileIdx = 5,
|
||||
.bpp = static_cast<unsigned>(set.bpp),
|
||||
});
|
||||
turbine::setKeyEventHandler(tctx, testKeyEventHandler);
|
||||
return turbine::run(tctx);
|
||||
}
|
||||
|
||||
|
||||
static int sceneUpdateHandler(turbine::Context&) noexcept {
|
||||
constexpr auto sleepTime = 16;
|
||||
if (s_paused) {
|
||||
return sleepTime;
|
||||
}
|
||||
// do stuff
|
||||
return sleepTime;
|
||||
}
|
||||
|
||||
static void sceneKeyEventHandler(turbine::Context &tctx, turbine::Key key, bool down) noexcept {
|
||||
if (down) {
|
||||
if (key == turbine::Key::Alpha_Q) {
|
||||
turbine::requestShutdown(tctx);
|
||||
} else if (key == turbine::Key::Alpha_P) {
|
||||
s_paused = !s_paused;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[maybe_unused]]
|
||||
static ox::Error runScene(turbine::Context &tctx) {
|
||||
constexpr ox::FileAddress SceneAddr = ox::StringLiteral("/Scenes/Chester.nscn");
|
||||
oxRequireM(cctx, core::init(tctx));
|
||||
oxRequire(scn, keel::readObj<scene::SceneStatic>(keelCtx(tctx), SceneAddr));
|
||||
turbine::setUpdateHandler(tctx, sceneUpdateHandler);
|
||||
turbine::setKeyEventHandler(tctx, sceneKeyEventHandler);
|
||||
scene::Scene const scene(*scn);
|
||||
oxReturnError(scene.setupDisplay(*cctx));
|
||||
return turbine::run(tctx);
|
||||
}
|
||||
|
||||
ox::Error run(ox::UniquePtr<ox::FileSystem> &&fs) noexcept {
|
||||
oxRequireM(tctx, turbine::init(std::move(fs), "Nostalgia"));
|
||||
return runTileSheetSetTest(*tctx);
|
||||
}
|
8
src/nostalgia/player/app.hpp
Normal file
8
src/nostalgia/player/app.hpp
Normal file
@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/std/memory.hpp>
|
||||
#include <ox/fs/fs.hpp>
|
||||
|
||||
typename ox::Error run(ox::UniquePtr<ox::FileSystem> &&fs) noexcept;
|
43
src/nostalgia/player/main.cpp
Normal file
43
src/nostalgia/player/main.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/logconn/def.hpp>
|
||||
#include <ox/logconn/logconn.hpp>
|
||||
|
||||
#include <keel/media.hpp>
|
||||
|
||||
#include "../modules/keelmodules.hpp"
|
||||
|
||||
#include "app.hpp"
|
||||
|
||||
static ox::Error run(int argc, const char **argv) noexcept {
|
||||
#ifndef OX_BARE_METAL
|
||||
// GBA doesn't need the modules and calling this doubles the size of the
|
||||
// binary.
|
||||
nostalgia::registerKeelModules();
|
||||
#endif
|
||||
if (argc < 2) {
|
||||
return OxError(1, "Please provide path to project directory or OxFS file.");
|
||||
}
|
||||
const auto path = argv[1];
|
||||
oxRequireM(fs, keel::loadRomFs(path));
|
||||
return run(std::move(fs));
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int WinMain() {
|
||||
auto const argc = __argc;
|
||||
auto const argv = const_cast<const char**>(__argv);
|
||||
#else
|
||||
int main(int argc, const char **argv) {
|
||||
#endif
|
||||
OX_INIT_DEBUG_LOGGER(loggerConn, "Nostalgia Player")
|
||||
ox::Error err;
|
||||
err = run(argc, argv);
|
||||
oxAssert(err, "Something went wrong...");
|
||||
if (err) {
|
||||
oxErrf("Failure: {}\n", toStr(err));
|
||||
}
|
||||
return static_cast<int>(err);
|
||||
}
|
34
src/nostalgia/studio/CMakeLists.txt
Normal file
34
src/nostalgia/studio/CMakeLists.txt
Normal file
@ -0,0 +1,34 @@
|
||||
add_executable(NostalgiaStudio WIN32 MACOSX_BUNDLE)
|
||||
|
||||
target_link_libraries(
|
||||
NostalgiaStudio
|
||||
NostalgiaProfile
|
||||
NostalgiaStudioModules
|
||||
NostalgiaKeelModules
|
||||
StudioAppLib
|
||||
OlympicApplib
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
NostalgiaStudio
|
||||
RUNTIME DESTINATION
|
||||
${NOSTALGIA_DIST_BIN}
|
||||
BUNDLE DESTINATION .
|
||||
)
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Release" AND NOT WIN32)
|
||||
# enable LTO
|
||||
set_property(TARGET NostalgiaStudio PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
set_target_properties(NostalgiaStudio PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
|
||||
endif()
|
||||
|
||||
install(
|
||||
FILES
|
||||
ns.icns
|
||||
DESTINATION
|
||||
${NOSTALGIA_DIST_RESOURCES}/icons
|
||||
)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user