Create LNMarker, SpriteSheet
Refactor Marker to use SpriteSheet
This commit is contained in:
parent
eab99cf7b1
commit
43f66c2888
40
src/Resources/LNMarker.cpp
Normal file
40
src/Resources/LNMarker.cpp
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#include "LNMarker.hpp"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace Resources {
|
||||||
|
LNMarker::LNMarker(const fs::path& t_folder) :
|
||||||
|
folder(t_folder)
|
||||||
|
{
|
||||||
|
if (not fs::is_directory(folder)) {
|
||||||
|
throw std::invalid_argument(folder.string()+" is not a folder");
|
||||||
|
}
|
||||||
|
if (not fs::exists(folder/"marker.json")) {
|
||||||
|
throw std::invalid_argument("LNMarker folder ( "+folder.string()+" ) has no long.json file");
|
||||||
|
}
|
||||||
|
std::ifstream marker_json{folder/"long.json"};
|
||||||
|
nlohmann::json j;
|
||||||
|
marker_json >> j;
|
||||||
|
j.get_to(*this);
|
||||||
|
background.load_and_check(folder, size, fps, {16, 30});
|
||||||
|
outline.load_and_check(folder, size, fps, {16, 30});
|
||||||
|
highlight.load_and_check(folder, size, fps, {16, 30});
|
||||||
|
tail.load_and_check(folder, size, fps, {16, 30});
|
||||||
|
tip_appearance.load_and_check(folder, size, fps, {16, 30});
|
||||||
|
tip_begin_cycle.load_and_check(folder, size, fps, {8, 30});
|
||||||
|
tip_cycle.load_and_check(folder, size, fps, {16, 30});
|
||||||
|
}
|
||||||
|
|
||||||
|
void from_json(const nlohmann::json& j, LNMarker& m) {
|
||||||
|
j.at("name").get_to(m.name);
|
||||||
|
j.at("fps").get_to(m.fps);
|
||||||
|
j.at("size").get_to(m.size);
|
||||||
|
j.at("note").at("background").get_to(m.background);
|
||||||
|
j.at("note").at("outline").get_to(m.outline);
|
||||||
|
j.at("note").at("highlight").get_to(m.highlight);
|
||||||
|
j.at("tail").get_to(m.tail);
|
||||||
|
j.at("tip").at("appearance").get_to(m.tip_appearance);
|
||||||
|
j.at("tip").at("begin cycle").get_to(m.tip_begin_cycle);
|
||||||
|
j.at("tip").at("cycle").get_to(m.tip_cycle);
|
||||||
|
}
|
||||||
|
}
|
28
src/Resources/LNMarker.hpp
Normal file
28
src/Resources/LNMarker.hpp
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ghc/filesystem.hpp>
|
||||||
|
#include <SFML/Graphics.hpp>
|
||||||
|
|
||||||
|
#include "SpriteSheet.hpp"
|
||||||
|
|
||||||
|
namespace fs = ghc::filesystem;
|
||||||
|
|
||||||
|
namespace Resources {
|
||||||
|
struct LNMarker {
|
||||||
|
LNMarker(const fs::path& folder);
|
||||||
|
|
||||||
|
fs::path folder;
|
||||||
|
std::string name;
|
||||||
|
std::size_t fps;
|
||||||
|
std::size_t size;
|
||||||
|
SpriteSheet background;
|
||||||
|
SpriteSheet outline;
|
||||||
|
SpriteSheet highlight;
|
||||||
|
SpriteSheet tail;
|
||||||
|
SpriteSheet tip_appearance;
|
||||||
|
SpriteSheet tip_begin_cycle;
|
||||||
|
SpriteSheet tip_cycle;
|
||||||
|
};
|
||||||
|
|
||||||
|
void from_json(const nlohmann::json& j, LNMarker& m);
|
||||||
|
}
|
@ -11,174 +11,58 @@ namespace fs = ghc::filesystem;
|
|||||||
|
|
||||||
namespace Resources {
|
namespace Resources {
|
||||||
|
|
||||||
void to_json(nlohmann::json& j, const MarkerAnimationMetadata& mam) {
|
void from_json(const nlohmann::json& j, Marker& m) {
|
||||||
j = nlohmann::json{
|
j.at("name").get_to(m.name);
|
||||||
{"sprite_sheet", mam.sprite_sheet.string()},
|
j.at("fps").get_to(m.fps);
|
||||||
{"count", mam.count},
|
j.at("size").get_to(m.size);
|
||||||
{"columns", mam.columns},
|
j.at("approach").get_to(m.approach);
|
||||||
{"rows", mam.rows}
|
j.at("miss").get_to(m.miss);
|
||||||
};
|
j.at("poor").get_to(m.poor);
|
||||||
}
|
j.at("good").get_to(m.good);
|
||||||
|
j.at("great").get_to(m.great);
|
||||||
void from_json(const nlohmann::json& j, MarkerAnimationMetadata& mam) {
|
j.at("perfect").get_to(m.perfect);
|
||||||
mam.sprite_sheet = ghc::filesystem::path{j.at("sprite_sheet").get<std::string>()};
|
|
||||||
j.at("count").get_to(mam.count);
|
|
||||||
j.at("columns").get_to(mam.columns);
|
|
||||||
j.at("rows").get_to(mam.rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
void to_json(nlohmann::json& j, const MarkerMetadata& mm) {
|
|
||||||
j = nlohmann::json{
|
|
||||||
{"name", mm.name},
|
|
||||||
{"size", mm.size},
|
|
||||||
{"fps", mm.fps},
|
|
||||||
{"approach", mm.approach},
|
|
||||||
{"miss", mm.miss},
|
|
||||||
{"poor", mm.poor},
|
|
||||||
{"good", mm.good},
|
|
||||||
{"great", mm.great},
|
|
||||||
{"perfect", mm.perfect}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void from_json(const nlohmann::json& j, MarkerMetadata& mm) {
|
|
||||||
j.at("name").get_to(mm.name);
|
|
||||||
j.at("size").get_to(mm.size);
|
|
||||||
j.at("fps").get_to(mm.fps);
|
|
||||||
j.at("approach").get_to(mm.approach);
|
|
||||||
j.at("miss").get_to(mm.miss);
|
|
||||||
j.at("poor").get_to(mm.poor);
|
|
||||||
j.at("good").get_to(mm.good);
|
|
||||||
j.at("great").get_to(mm.great);
|
|
||||||
j.at("perfect").get_to(mm.perfect);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Marker::Marker(const fs::path& marker_folder) :
|
Marker::Marker(const fs::path& marker_folder) :
|
||||||
m_folder(marker_folder),
|
folder(marker_folder)
|
||||||
m_metadata(),
|
|
||||||
m_approach(),
|
|
||||||
m_miss(),
|
|
||||||
m_poor(),
|
|
||||||
m_good(),
|
|
||||||
m_great(),
|
|
||||||
m_perfect()
|
|
||||||
{
|
{
|
||||||
if (not fs::is_directory(m_folder)) {
|
if (not fs::is_directory(folder)) {
|
||||||
throw std::invalid_argument(m_folder.string()+" is not a folder");
|
throw std::invalid_argument(folder.string()+" is not a folder");
|
||||||
}
|
}
|
||||||
if (not fs::exists(m_folder/"marker.json")) {
|
if (not fs::exists(folder/"marker.json")) {
|
||||||
throw std::invalid_argument("Marker folder ( "+m_folder.string()+" ) has no marker.json file");
|
throw std::invalid_argument("Marker folder ( "+folder.string()+" ) has no marker.json file");
|
||||||
}
|
}
|
||||||
std::ifstream marker_json{m_folder/"marker.json"};
|
std::ifstream marker_json{folder/"marker.json"};
|
||||||
nlohmann::json j;
|
nlohmann::json j;
|
||||||
marker_json >> j;
|
marker_json >> j;
|
||||||
j.get_to(m_metadata);
|
j.get_to(*this);
|
||||||
load_and_check(m_approach, m_metadata.approach);
|
approach.load_and_check(folder, size, fps, {16, 30});
|
||||||
load_and_check(m_miss, m_metadata.miss);
|
miss.load_and_check(folder, size, fps, {16, 30});
|
||||||
load_and_check(m_poor, m_metadata.poor);
|
poor.load_and_check(folder, size, fps, {16, 30});
|
||||||
load_and_check(m_good, m_metadata.good);
|
good.load_and_check(folder, size, fps, {16, 30});
|
||||||
load_and_check(m_great, m_metadata.great);
|
great.load_and_check(folder, size, fps, {16, 30});
|
||||||
load_and_check(m_perfect, m_metadata.perfect);
|
perfect.load_and_check(folder, size, fps, {16, 30});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Marker::load_and_check(sf::Texture& sprite_sheet, const MarkerAnimationMetadata& metadata) {
|
const SpriteSheet& Marker::get_sprite_sheet_from_enum(const MarkerAnimation& state) const {
|
||||||
// File Load & Check
|
|
||||||
if (not sprite_sheet.loadFromFile(m_folder/metadata.sprite_sheet)) {
|
|
||||||
throw std::runtime_error(
|
|
||||||
"Cannot open marker sprite sheet "
|
|
||||||
+(m_folder/metadata.sprite_sheet).string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
sprite_sheet.setSmooth(true);
|
|
||||||
|
|
||||||
// Sprite sheet size check
|
|
||||||
// throw if the texture size does not match what's announced by the metadata
|
|
||||||
auto sheet_size = sprite_sheet.getSize();
|
|
||||||
auto expected_size = sf::Vector2u(metadata.columns, metadata.rows) * static_cast<unsigned int>(m_metadata.size);
|
|
||||||
if (sheet_size != expected_size) {
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Marker sprite sheet ";
|
|
||||||
ss << (m_folder/metadata.sprite_sheet).string();
|
|
||||||
ss << " should be " << expected_size.x << "×" << expected_size.y << " pixels";
|
|
||||||
ss << " but is " << sheet_size.x << "×" << sheet_size.y;
|
|
||||||
throw std::invalid_argument(ss.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprite count check
|
|
||||||
// throw if the count calls for more sprites than possible according to the 'columns' and 'rows' fields
|
|
||||||
if (metadata.count > metadata.columns * metadata.rows) {
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Metadata for marker sprite sheet ";
|
|
||||||
ss << (m_folder/metadata.sprite_sheet).string();
|
|
||||||
ss << " indicates that it holds " << metadata.count << " sprites";
|
|
||||||
ss << " when it can only hold a maximum of " << metadata.columns * metadata.rows;
|
|
||||||
ss << " according to the 'columns' and 'rows' fields";
|
|
||||||
throw std::invalid_argument(ss.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Duration check
|
|
||||||
// We do not allow any marker animation to take longer than the jubeat standard of 16 frames at 30 fps
|
|
||||||
// For that we make sure that :
|
|
||||||
// count/fps <= 16/30
|
|
||||||
// Which is mathematically equivalent to checking that :
|
|
||||||
// count*30 <= 16*fps
|
|
||||||
// Which allows us to avoid having to cast to float
|
|
||||||
if (metadata.count*30 > 16*m_metadata.fps) {
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Marker animation for sprite sheet ";
|
|
||||||
ss << (m_folder/metadata.sprite_sheet).string();
|
|
||||||
ss << " lasts " << metadata.count/static_cast<float>(m_metadata.fps)*1000.f << "ms";
|
|
||||||
ss << " (" << metadata.count << "f @ " << m_metadata.fps << "fps)";
|
|
||||||
ss << " which is more than the maximum of " << 16.f/30.f*1000.f << "ms";
|
|
||||||
ss << " (16f @ 30fps)";
|
|
||||||
throw std::invalid_argument(ss.str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sf::Texture& Marker::get_sprite_sheet_from_enum(const MarkerAnimation& state) const {
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case MarkerAnimation::APPROACH:
|
case MarkerAnimation::APPROACH:
|
||||||
return m_approach;
|
return approach;
|
||||||
break;
|
break;
|
||||||
case MarkerAnimation::MISS:
|
case MarkerAnimation::MISS:
|
||||||
return m_miss;
|
return miss;
|
||||||
break;
|
break;
|
||||||
case MarkerAnimation::POOR:
|
case MarkerAnimation::POOR:
|
||||||
return m_poor;
|
return poor;
|
||||||
break;
|
break;
|
||||||
case MarkerAnimation::GOOD:
|
case MarkerAnimation::GOOD:
|
||||||
return m_good;
|
return good;
|
||||||
break;
|
break;
|
||||||
case MarkerAnimation::GREAT:
|
case MarkerAnimation::GREAT:
|
||||||
return m_great;
|
return great;
|
||||||
break;
|
break;
|
||||||
case MarkerAnimation::PERFECT:
|
case MarkerAnimation::PERFECT:
|
||||||
return m_perfect;
|
return perfect;
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw std::runtime_error("wtf ?");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const MarkerAnimationMetadata& Marker::get_metadata_from_enum(const MarkerAnimation& state) const {
|
|
||||||
switch (state) {
|
|
||||||
case MarkerAnimation::APPROACH:
|
|
||||||
return m_metadata.approach;
|
|
||||||
break;
|
|
||||||
case MarkerAnimation::MISS:
|
|
||||||
return m_metadata.miss;
|
|
||||||
break;
|
|
||||||
case MarkerAnimation::POOR:
|
|
||||||
return m_metadata.poor;
|
|
||||||
break;
|
|
||||||
case MarkerAnimation::GOOD:
|
|
||||||
return m_metadata.good;
|
|
||||||
break;
|
|
||||||
case MarkerAnimation::GREAT:
|
|
||||||
return m_metadata.great;
|
|
||||||
break;
|
|
||||||
case MarkerAnimation::PERFECT:
|
|
||||||
return m_metadata.perfect;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error("wtf ?");
|
throw std::runtime_error("wtf ?");
|
||||||
@ -191,7 +75,7 @@ namespace Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::optional<sf::Sprite> Marker::get_sprite(const MarkerAnimation& state, const float seconds) const {
|
std::optional<sf::Sprite> Marker::get_sprite(const MarkerAnimation& state, const float seconds) const {
|
||||||
auto raw_frame = static_cast<int>(std::floor(seconds*m_metadata.fps));
|
auto raw_frame = static_cast<int>(std::floor(seconds*fps));
|
||||||
if (raw_frame >= 0) {
|
if (raw_frame >= 0) {
|
||||||
if (state == MarkerAnimation::APPROACH) {
|
if (state == MarkerAnimation::APPROACH) {
|
||||||
return get_sprite(MarkerAnimation::MISS, static_cast<std::size_t>(raw_frame));
|
return get_sprite(MarkerAnimation::MISS, static_cast<std::size_t>(raw_frame));
|
||||||
@ -199,29 +83,27 @@ namespace Resources {
|
|||||||
return get_sprite(state, static_cast<std::size_t>(raw_frame));
|
return get_sprite(state, static_cast<std::size_t>(raw_frame));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
auto approach_frame_count = get_metadata_from_enum(MarkerAnimation::APPROACH).count;
|
|
||||||
return get_sprite(
|
return get_sprite(
|
||||||
MarkerAnimation::APPROACH,
|
MarkerAnimation::APPROACH,
|
||||||
static_cast<std::size_t>(raw_frame+static_cast<int>(approach_frame_count))
|
static_cast<std::size_t>(raw_frame+static_cast<int>(approach.count))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<sf::Sprite> Marker::get_sprite(const MarkerAnimation& state, const std::size_t frame) const {
|
std::optional<sf::Sprite> Marker::get_sprite(const MarkerAnimation& state, const std::size_t frame) const {
|
||||||
auto& meta = get_metadata_from_enum(state);
|
auto& sprite_sheet = get_sprite_sheet_from_enum(state);
|
||||||
if (frame >= meta.count) {
|
if (frame >= sprite_sheet.count) {
|
||||||
return {};
|
return {};
|
||||||
} else {
|
} else {
|
||||||
auto& tex = get_sprite_sheet_from_enum(state);
|
sf::Sprite sprite{sprite_sheet.tex};
|
||||||
sf::Sprite sprite{tex};
|
|
||||||
sf::IntRect rect{
|
sf::IntRect rect{
|
||||||
sf::Vector2i{
|
sf::Vector2i{
|
||||||
static_cast<int>(frame % meta.columns),
|
static_cast<int>(frame % sprite_sheet.columns),
|
||||||
static_cast<int>(frame / meta.columns)
|
static_cast<int>(frame / sprite_sheet.columns)
|
||||||
} * static_cast<int>(m_metadata.size),
|
} * static_cast<int>(size),
|
||||||
sf::Vector2i{
|
sf::Vector2i{
|
||||||
static_cast<int>(m_metadata.size),
|
static_cast<int>(size),
|
||||||
static_cast<int>(m_metadata.size)
|
static_cast<int>(size)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
sprite.setTextureRect(rect);
|
sprite.setTextureRect(rect);
|
||||||
@ -237,7 +119,7 @@ namespace Resources {
|
|||||||
if (p.is_directory()) {
|
if (p.is_directory()) {
|
||||||
try {
|
try {
|
||||||
Marker m{p.path()};
|
Marker m{p.path()};
|
||||||
res.emplace(m.m_metadata.name, m);
|
res.emplace(m.name, m);
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
std::cerr << "Unable to load marker folder "
|
std::cerr << "Unable to load marker folder "
|
||||||
<< p.path().filename().string() << " : "
|
<< p.path().filename().string() << " : "
|
||||||
|
@ -7,6 +7,10 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <SFML/Graphics.hpp>
|
#include <SFML/Graphics.hpp>
|
||||||
|
|
||||||
|
#include "SpriteSheet.hpp"
|
||||||
|
|
||||||
|
namespace fs = ghc::filesystem;
|
||||||
|
|
||||||
namespace Resources {
|
namespace Resources {
|
||||||
enum class MarkerAnimation {
|
enum class MarkerAnimation {
|
||||||
APPROACH,
|
APPROACH,
|
||||||
@ -17,51 +21,27 @@ namespace Resources {
|
|||||||
PERFECT
|
PERFECT
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MarkerAnimationMetadata {
|
|
||||||
ghc::filesystem::path sprite_sheet;
|
|
||||||
std::size_t count; // how many sprites total on the sheet
|
|
||||||
std::size_t columns; // how many horizontally
|
|
||||||
std::size_t rows; // how many vertically
|
|
||||||
};
|
|
||||||
|
|
||||||
void to_json(nlohmann::json& j, const MarkerAnimationMetadata& mam);
|
|
||||||
void from_json(const nlohmann::json& j, MarkerAnimationMetadata& mam);
|
|
||||||
|
|
||||||
// Represents what's held in marker.json
|
|
||||||
struct MarkerMetadata {
|
|
||||||
std::string name;
|
|
||||||
std::size_t size; // the side length in pixels
|
|
||||||
std::size_t fps; // classic jubeat markers are 30 fps
|
|
||||||
MarkerAnimationMetadata approach;
|
|
||||||
MarkerAnimationMetadata miss;
|
|
||||||
MarkerAnimationMetadata poor;
|
|
||||||
MarkerAnimationMetadata good;
|
|
||||||
MarkerAnimationMetadata great;
|
|
||||||
MarkerAnimationMetadata perfect;
|
|
||||||
};
|
|
||||||
|
|
||||||
void to_json(nlohmann::json& j, const MarkerMetadata& mm);
|
|
||||||
void from_json(const nlohmann::json& j, MarkerMetadata& mm);
|
|
||||||
|
|
||||||
struct Marker {
|
struct Marker {
|
||||||
explicit Marker(const ghc::filesystem::path& marker_folder);
|
explicit Marker(const fs::path& marker_folder);
|
||||||
std::optional<sf::Sprite> get_sprite(const MarkerAnimation& state, const sf::Time seconds) const;
|
std::optional<sf::Sprite> get_sprite(const MarkerAnimation& state, const sf::Time seconds) const;
|
||||||
std::optional<sf::Sprite> get_sprite(const MarkerAnimation& state, const float seconds) const;
|
std::optional<sf::Sprite> get_sprite(const MarkerAnimation& state, const float seconds) const;
|
||||||
std::optional<sf::Sprite> get_sprite(const MarkerAnimation& state, const std::size_t frame) const;
|
std::optional<sf::Sprite> get_sprite(const MarkerAnimation& state, const std::size_t frame) const;
|
||||||
void load_and_check(sf::Texture& spritesheet, const MarkerAnimationMetadata& metadata);
|
const SpriteSheet& get_sprite_sheet_from_enum(const MarkerAnimation& state) const;
|
||||||
const sf::Texture& get_sprite_sheet_from_enum(const MarkerAnimation& state) const;
|
|
||||||
const MarkerAnimationMetadata& get_metadata_from_enum(const MarkerAnimation& state) const;
|
|
||||||
|
|
||||||
ghc::filesystem::path m_folder;
|
fs::path folder;
|
||||||
MarkerMetadata m_metadata;
|
std::string name;
|
||||||
sf::Texture m_approach;
|
std::size_t size; // the side length in pixels
|
||||||
sf::Texture m_miss;
|
std::size_t fps; // classic jubeat markers are 30 fps
|
||||||
sf::Texture m_poor;
|
SpriteSheet approach;
|
||||||
sf::Texture m_good;
|
SpriteSheet miss;
|
||||||
sf::Texture m_great;
|
SpriteSheet poor;
|
||||||
sf::Texture m_perfect;
|
SpriteSheet good;
|
||||||
|
SpriteSheet great;
|
||||||
|
SpriteSheet perfect;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void from_json(const nlohmann::json& j, Marker& m);
|
||||||
|
|
||||||
using Markers = std::map<std::string, Marker>;
|
using Markers = std::map<std::string, Marker>;
|
||||||
Markers load_markers(const ghc::filesystem::path& jujube_path);
|
Markers load_markers(const ghc::filesystem::path& jujube_path);
|
||||||
}
|
}
|
||||||
|
67
src/Resources/SpriteSheet.cpp
Normal file
67
src/Resources/SpriteSheet.cpp
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#include "SpriteSheet.hpp"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace Resources {
|
||||||
|
void from_json(const nlohmann::json& j, SpriteSheet& s) {
|
||||||
|
s.tex_path = fs::path{j.at("sprite_sheet").get<std::string>()};
|
||||||
|
j.at("count").get_to(s.count);
|
||||||
|
j.at("columns").get_to(s.columns);
|
||||||
|
j.at("rows").get_to(s.rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteSheet::load_and_check(const fs::path& folder, std::size_t size, std::size_t fps, const DurationInFrames& max_duration) {
|
||||||
|
// File Load & Check
|
||||||
|
if (not tex.loadFromFile(folder/tex_path)) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Cannot open marker sprite sheet "
|
||||||
|
+(folder/tex_path).string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
tex.setSmooth(true);
|
||||||
|
|
||||||
|
// Sprite sheet size check
|
||||||
|
// throw if the texture size does not match what's announced by the metadata
|
||||||
|
auto sheet_size = tex.getSize();
|
||||||
|
auto expected_size = sf::Vector2u(columns, rows) * static_cast<unsigned int>(size);
|
||||||
|
if (sheet_size != expected_size) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Marker sprite sheet ";
|
||||||
|
ss << (folder/tex_path).string();
|
||||||
|
ss << " should be " << expected_size.x << "×" << expected_size.y << " pixels";
|
||||||
|
ss << " but is " << sheet_size.x << "×" << sheet_size.y;
|
||||||
|
throw std::invalid_argument(ss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprite count check
|
||||||
|
// throw if the count calls for more sprites than possible according to the 'columns' and 'rows' fields
|
||||||
|
if (count > columns * rows) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Metadata for marker sprite sheet ";
|
||||||
|
ss << (folder/tex_path).string();
|
||||||
|
ss << " indicates that it holds " << count << " sprites";
|
||||||
|
ss << " when it can only hold a maximum of " << columns * rows;
|
||||||
|
ss << " according to the 'columns' and 'rows' fields";
|
||||||
|
throw std::invalid_argument(ss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duration check
|
||||||
|
// We do not allow any marker animation to take longer than the jubeat standard of 16 frames at 30 fps
|
||||||
|
// For that we make sure that :
|
||||||
|
// frames/fps <= max_frames/reference_fps
|
||||||
|
// Which is mathematically equivalent to checking that :
|
||||||
|
// count*reference_fps <= max_frames*fps
|
||||||
|
// Which allows us to avoid having to cast to float
|
||||||
|
if (count*max_duration.fps > max_duration.frames*fps) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Marker animation for sprite sheet ";
|
||||||
|
ss << (folder/tex_path).string();
|
||||||
|
ss << " lasts " << count/static_cast<float>(fps)*1000.f << "ms";
|
||||||
|
ss << " (" << count << "f @ " << fps << "fps)";
|
||||||
|
ss << " which is more than the maximum of ";
|
||||||
|
ss << max_duration.frames/static_cast<float>(max_duration.fps)*1000.f << "ms";
|
||||||
|
ss << " (16f @ 30fps)";
|
||||||
|
throw std::invalid_argument(ss.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
src/Resources/SpriteSheet.hpp
Normal file
32
src/Resources/SpriteSheet.hpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ghc/filesystem.hpp>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <SFML/Graphics/Texture.hpp>
|
||||||
|
#include <SFML/Graphics/Sprite.hpp>
|
||||||
|
|
||||||
|
namespace fs = ghc::filesystem;
|
||||||
|
|
||||||
|
namespace Resources {
|
||||||
|
struct SpriteSheet {
|
||||||
|
sf::Texture tex;
|
||||||
|
fs::path tex_path;
|
||||||
|
std::size_t count;
|
||||||
|
std::size_t columns;
|
||||||
|
std::size_t rows;
|
||||||
|
|
||||||
|
void load_and_check(
|
||||||
|
const fs::path& folder,
|
||||||
|
std::size_t size,
|
||||||
|
std::size_t fps,
|
||||||
|
const DurationInFrames& max_duration
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DurationInFrames {
|
||||||
|
std::size_t frames;
|
||||||
|
std::size_t fps;
|
||||||
|
};
|
||||||
|
|
||||||
|
void from_json(const nlohmann::json& j, SpriteSheet& s);
|
||||||
|
}
|
27
utils/spritesheet_maker.py
Normal file
27
utils/spritesheet_maker.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from math import sqrt, ceil
|
||||||
|
from PIL import Image
|
||||||
|
from path import Path
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("files", type=Path, nargs="+")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
images = [Image.open(file) for file in args.files]
|
||||||
|
assert len(set(image.size for image in images)) == 1
|
||||||
|
sprite_size = images[0].size
|
||||||
|
sheet_square_side = ceil(sqrt(len(images)))
|
||||||
|
sheet = Image.new(
|
||||||
|
'RGBA',
|
||||||
|
(
|
||||||
|
sheet_square_side*sprite_size[0],
|
||||||
|
sheet_square_side*sprite_size[1]
|
||||||
|
),
|
||||||
|
(0,0,0,0)
|
||||||
|
)
|
||||||
|
for index, sprite in enumerate(images):
|
||||||
|
x = (index % sheet_square_side)*sprite_size[0]
|
||||||
|
y = (index // sheet_square_side)*sprite_size[1]
|
||||||
|
sheet.paste(sprite, (x,y))
|
||||||
|
sheet.save("sheet.png")
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user