@@ -26,16 +26,16 @@ set(libraries | |||
if(CMAKE_BUILD_TYPE STREQUAL Sanitize OR CMAKE_BUILD_TYPE STREQUAL "") | |||
message(STATUS "Build mode: Sanitize") | |||
add_compile_options(-g -fsanitize=address -fsanitize=undefined) | |||
add_compile_options(-g -DDEBUG -fsanitize=address -fsanitize=undefined) | |||
add_link_options(-fsanitize=address -fsanitize=undefined) | |||
elseif(CMAKE_BUILD_TYPE STREQUAL Debug) | |||
message(STATUS "Build mode: Debug") | |||
add_compile_options(-g) | |||
add_compile_options(-g -DDEBUG) | |||
elseif(CMAKE_BUILD_TYPE STREQUAL Optimize) | |||
message(STATUS "Build mode: Optimize") | |||
add_compile_options(-O3 -DNDEBUG -g) | |||
add_compile_options(-O3 -g -DDEBUG) | |||
elseif(CMAKE_BUILD_TYPE STREQUAL Tracy) | |||
message(STATUS "Build mode: Tracy") | |||
@@ -45,7 +45,7 @@ elseif(CMAKE_BUILD_TYPE STREQUAL Tracy) | |||
elseif(CMAKE_BUILD_TYPE STREQUAL Release) | |||
message(STATUS "Build mode: Release") | |||
add_compile_options(-O3 -flto -DNDEBUG -g) | |||
add_compile_options(-O3 -flto -g -DNDEBUG) | |||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -flto") | |||
else() | |||
@@ -72,6 +72,7 @@ add_subdirectory(core.mod) | |||
add_executable(swan | |||
src/main.cc) | |||
target_link_libraries(swan libswan libcygnet ${libraries}) | |||
add_dependencies(swan core.mod) | |||
add_executable(perlin-test EXCLUDE_FROM_ALL | |||
src/perlin-test.cc) |
@@ -12,11 +12,13 @@ static int getStoneLevel(const siv::PerlinNoise &perlin, int x) { | |||
return (int)(perlin.noise(x / 50.0, 10) * 10) + 10; | |||
} | |||
void DefaultWorldGen::drawBackground(const Swan::Context &ctx, Swan::Win &win, Swan::Vec2 pos) { | |||
void DefaultWorldGen::drawBackground( | |||
const Swan::Context &ctx, Cygnet::Renderer &rnd, Swan::Vec2 pos) { | |||
int texmin = 10; | |||
int texmax = 20; | |||
//int texmax = 20; | |||
if (pos.y > texmin) { | |||
/* | |||
SDL_Texture *tex = bgCave_.texture_.get(); | |||
Uint8 alpha = std::clamp( | |||
@@ -27,19 +29,21 @@ void DefaultWorldGen::drawBackground(const Swan::Context &ctx, Swan::Win &win, S | |||
Swan::Draw::parallaxBackground( | |||
win, tex, std::nullopt, std::nullopt, | |||
pos.x * Swan::TILE_SIZE, pos.y * Swan::TILE_SIZE, 0.7); | |||
TODO */ | |||
} | |||
} | |||
SDL_Color DefaultWorldGen::backgroundColor(Swan::Vec2 pos) { | |||
Cygnet::Color DefaultWorldGen::backgroundColor(Swan::Vec2 pos) { | |||
float y = pos.y; | |||
return Swan::Draw::linearGradient(y, { | |||
{ 0, { 128, 220, 250, 255 } }, | |||
{ 70, { 107, 87, 5, 255 } }, | |||
{ 100, { 107, 87, 5, 255 } }, | |||
{ 200, { 20, 20, 23, 255 } }, | |||
{ 300, { 20, 20, 23, 255 } }, | |||
{ 500, { 25, 10, 10, 255 } }, | |||
{ 1000, { 65, 10, 10, 255 } } }); | |||
{ 0, Cygnet::ByteColor{128, 220, 250}}, | |||
{ 70, Cygnet::ByteColor{107, 87, 5}}, | |||
{ 100, Cygnet::ByteColor{107, 87, 5}}, | |||
{ 200, Cygnet::ByteColor{ 20, 20, 23}}, | |||
{ 300, Cygnet::ByteColor{ 20, 20, 23}}, | |||
{ 500, Cygnet::ByteColor{ 25, 10, 10}}, | |||
{1000, Cygnet::ByteColor{ 65, 10, 10}}, | |||
}); | |||
} | |||
Swan::Tile::ID DefaultWorldGen::genTile(Swan::TilePos pos) { |
@@ -13,16 +13,17 @@ public: | |||
tAir_(world.getTileID("@::air")), | |||
tTreeTrunk_(world.getTileID("core::tree-trunk")), | |||
tLeaves_(world.getTileID("core::leaves")), | |||
bgCave_(world.resources_.getImage("core/misc/background-cave")) {} | |||
bgCave_(world.getSprite("core::misc/background-cave")) {} | |||
void drawBackground(const Swan::Context &ctx, Swan::Win &win, Swan::Vec2 pos) override; | |||
SDL_Color backgroundColor(Swan::Vec2 pos) override; | |||
void drawBackground( | |||
const Swan::Context &ctx, Cygnet::Renderer &rnd, Swan::Vec2 pos) override; | |||
Cygnet::Color backgroundColor(Swan::Vec2 pos) override; | |||
void genChunk(Swan::WorldPlane &plane, Swan::Chunk &chunk) override; | |||
Swan::EntityRef spawnPlayer(const Swan::Context &ctx) override; | |||
private: | |||
Swan::Tile::ID genTile(Swan::TilePos pos); | |||
Swan::Tile::ID tGrass_, tDirt_, tStone_, tAir_, tTreeTrunk_, tLeaves_; | |||
Swan::ImageResource &bgCave_; | |||
Cygnet::RenderSprite bgCave_; | |||
siv::PerlinNoise perlin_ = siv::PerlinNoise(100); | |||
}; |
@@ -19,14 +19,10 @@ ItemStackEntity::ItemStackEntity(const Swan::Context &ctx, const PackObject &obj | |||
deserialize(ctx, obj); | |||
} | |||
void ItemStackEntity::draw(const Swan::Context &ctx, Swan::Win &win) { | |||
SDL_Rect rect = item_->image.frameRect(); | |||
SDL_Texture *tex = item_->image.texture_.get(); | |||
Swan::TexColorMod darken(tex, 220, 220, 220); | |||
win.showTexture(body_.pos, tex, &rect, | |||
{ .hscale = 0.5, .vscale = 0.5 }); | |||
void ItemStackEntity::draw(const Swan::Context &ctx, Cygnet::Renderer &rnd) { | |||
// TODO: decrease brightness? | |||
rnd.drawTile(item_->id, Cygnet::Mat3gf{}.scale({0.5, 0.5}).translate(body_.pos)); | |||
rnd.drawRect(body_.pos, body_.size); | |||
} | |||
void ItemStackEntity::update(const Swan::Context &ctx, float dt) { |
@@ -7,7 +7,7 @@ public: | |||
ItemStackEntity(const Swan::Context &ctx, Swan::Vec2 pos, const std::string &item); | |||
ItemStackEntity(const Swan::Context &ctx, const PackObject &obj); | |||
void draw(const Swan::Context &ctx, Swan::Win &win) override; | |||
void draw(const Swan::Context &ctx, Cygnet::Renderer &rnd) override; | |||
void update(const Swan::Context &ctx, float dt) override; | |||
void tick(const Swan::Context &ctx, float dt) override; | |||
void deserialize(const Swan::Context &ctx, const PackObject &obj) override; |
@@ -14,9 +14,20 @@ PlayerEntity::PlayerEntity(const Swan::Context &ctx, const PackObject &obj): | |||
deserialize(ctx, obj); | |||
} | |||
void PlayerEntity::draw(const Swan::Context &ctx, Swan::Win &win) { | |||
body_.outline(win); | |||
anims_[(int)state_].draw(body_.pos - Swan::Vec2(0.2, 0.1), win); | |||
void PlayerEntity::draw(const Swan::Context &ctx, Cygnet::Renderer &rnd) { | |||
Cygnet::Mat3gf mat; | |||
// Currently, there is no sprite for running left. | |||
// Running left is just running right but flipped. | |||
if (state_ == State::RUNNING_L) { | |||
mat.translate({-0.5, 0}).scale({-1, 1}).translate({0.5, 0}); | |||
} | |||
anims_[(int)state_].draw(rnd, mat.translate( | |||
body_.pos - Swan::Vec2{0.2, 0.1})); | |||
rnd.drawRect(mouseTile_, {1, 1}); | |||
rnd.drawRect(body_.pos, body_.size); | |||
} | |||
void PlayerEntity::update(const Swan::Context &ctx, float dt) { |
@@ -11,7 +11,7 @@ public: | |||
using PhysicsEntity::get; | |||
Inventory &get(InventoryTrait::Tag) override { return inventory_; } | |||
void draw(const Swan::Context &ctx, Swan::Win &win) override; | |||
void draw(const Swan::Context &ctx, Cygnet::Renderer &rnd) override; | |||
void update(const Swan::Context &ctx, float dt) override; | |||
void tick(const Swan::Context &ctx, float dt) override; | |||
void deserialize(const Swan::Context &ctx, const PackObject &obj) override; | |||
@@ -36,11 +36,9 @@ private: | |||
PlayerEntity(const Swan::Context &ctx): | |||
PhysicsEntity(SIZE), | |||
anims_{ | |||
Swan::Animation(ctx.resources.getImage("core/entity/player-still"), 0.8), | |||
Swan::Animation( | |||
ctx.resources.getImage("core/entity/player-running"), | |||
1, SDL_FLIP_HORIZONTAL), | |||
Swan::Animation(ctx.resources.getImage("core/entity/player-running"), 1) | |||
Swan::Animation(ctx.world.getSprite("core::entity/player-still"), 0.8), | |||
Swan::Animation(ctx.world.getSprite("core::entity/player-running"), 1), | |||
Swan::Animation(ctx.world.getSprite("core::entity/player-running"), 1), | |||
} {} | |||
State state_ = State::IDLE; |
@@ -10,62 +10,56 @@ public: | |||
breakListener_ = world.evtTileBreak_.subscribe( | |||
std::bind_front(&CoreMod::onTileBreak, this)); | |||
registerImage("tile/stone"); | |||
registerImage("tile/dirt"); | |||
registerImage("tile/grass"); | |||
registerImage("tile/tree-trunk"); | |||
registerImage("tile/leaves"); | |||
registerImage("tile/torch"); | |||
registerImage("entity/player-running"); | |||
registerImage("entity/player-still"); | |||
registerImage("misc/background-cave"); | |||
registerSprite("entity/player-running"); | |||
registerSprite("entity/player-still"); | |||
registerSprite("misc/background-cave"); | |||
registerTile({ | |||
.name = "stone", | |||
.image = "core/tile/stone", | |||
.image = "core::tile/stone", | |||
.droppedItem = "core::stone", | |||
}); | |||
registerTile({ | |||
.name = "dirt", | |||
.image = "core/tile/dirt", | |||
.image = "core::tile/dirt", | |||
.droppedItem = "core::dirt", | |||
}); | |||
registerTile({ | |||
.name = "grass", | |||
.image = "core/tile/grass", | |||
.image = "core::tile/grass", | |||
.droppedItem = "core::dirt", | |||
}); | |||
registerTile({ | |||
.name = "tree-trunk", | |||
.image = "core/tile/tree-trunk", | |||
.image = "core::tile/tree-trunk", | |||
.droppedItem = "core::tree-trunk", | |||
}); | |||
registerTile({ | |||
.name = "leaves", | |||
.image = "core/tile/leaves", | |||
.image = "core::tile/leaves", | |||
}); | |||
registerTile({ | |||
.name = "torch", | |||
.image = "core/tile/torch", | |||
.image = "core::tile/torch", | |||
.isSolid = false, | |||
.lightLevel = 80/255.0, | |||
}); | |||
registerItem({ | |||
.name = "stone", | |||
.image = "core/tile/stone", | |||
.image = "core::tile/stone", | |||
}); | |||
registerItem({ | |||
.name = "dirt", | |||
.image = "core/tile/dirt", | |||
.image = "core::tile/dirt", | |||
}); | |||
registerItem({ | |||
.name = "grass", | |||
.image = "core/tile/grass", | |||
.image = "core::tile/grass", | |||
}); | |||
registerItem({ | |||
.name = "tree-trunk", | |||
.image = "core/tile/tree-trunk", | |||
.image = "core::tile/tree-trunk", | |||
}); | |||
registerWorldGen<DefaultWorldGen>("default"); |
@@ -1,6 +1,6 @@ | |||
#pragma once | |||
#include <iostream> | |||
#include <ostream> | |||
#include <cmath> | |||
#include <array> | |||
@@ -8,6 +8,9 @@ | |||
namespace SwanCommon { | |||
// 3D transformation matrix. | |||
// All the operations assume that the last row remains {0, 0, 1}. | |||
// If the last row is modified, methods like .scale, .translate and .rotate won't work. | |||
template<typename T> | |||
struct Matrix3 { | |||
using Vec = Vector2<T>; | |||
@@ -54,17 +57,25 @@ struct Matrix3 { | |||
constexpr Matrix3<T> &scale(Vec vec) { | |||
at(0, 0) *= vec.x; | |||
at(1, 0) *= vec.x; | |||
at(2, 0) *= vec.x; | |||
at(0, 1) *= vec.y; | |||
at(1, 1) *= vec.y; | |||
at(2, 1) *= vec.y; | |||
return *this; | |||
} | |||
constexpr Matrix3<T> &rotate(T rads) { | |||
T s = std::sin(rads); | |||
T c = std::cos(rads); | |||
at(0, 0) += c; | |||
at(1, 0) -= s; | |||
at(0, 1) += s; | |||
at(1, 1) += c; | |||
T old00 = at(0, 0), old10 = at(1, 0), old20 = at(2, 0); | |||
at(0, 0) = c * old00 + -s * at(0, 1); | |||
at(1, 0) = c * old10 + -s * at(1, 1); | |||
at(2, 0) = c * old20 + -s * at(2, 1); | |||
at(0, 1) = s * old00 + c * at(0, 1); | |||
at(1, 1) = s * old10 + c * at(1, 1); | |||
at(2, 1) = s * old20 + c * at(2, 1); | |||
return *this; | |||
} | |||
@@ -81,8 +92,8 @@ template<typename T> | |||
std::ostream &operator<<(std::ostream &os, const Matrix3<T> &mat) { | |||
os << '(' | |||
<< '(' << mat.at(0, 0) << ", " << mat.at(1, 0) << ", " << mat.at(2, 0) << "), " | |||
<< '(' << mat.at(0, 1) << ", " << mat.at(1, 1) << ", " << mat.at(2, 1) << "), " | |||
<< '(' << mat.at(0, 2) << ", " << mat.at(1, 2) << ", " << mat.at(2, 2) << "))"; | |||
<< '(' << mat.at(0, 1) << ", " << mat.at(1, 1) << ", " << mat.at(2, 1) << "), " | |||
<< '(' << mat.at(0, 2) << ", " << mat.at(1, 2) << ", " << mat.at(2, 2) << "))"; | |||
return os; | |||
} | |||
@@ -1,5 +1,4 @@ | |||
add_library(libcygnet SHARED | |||
src/Context.cc | |||
src/GlWrappers.cc | |||
src/Renderer.cc | |||
src/ResourceManager.cc |
@@ -1,11 +0,0 @@ | |||
#pragma once | |||
namespace Cygnet { | |||
class Context { | |||
public: | |||
Context(); | |||
~Context(); | |||
}; | |||
} |
@@ -17,20 +17,20 @@ struct RenderChunk { | |||
GLuint tex; | |||
}; | |||
struct RenderChunkShadow { | |||
GLuint tex; | |||
}; | |||
struct RenderSprite { | |||
GLuint tex; | |||
SwanCommon::Vec2 scale; | |||
int frameCount; | |||
}; | |||
struct RenderTile { | |||
uint16_t id; | |||
}; | |||
struct RenderCamera { | |||
SwanCommon::Vec2 pos; | |||
SwanCommon::Vec2i size; | |||
float zoom; | |||
SwanCommon::Vec2 pos = {0, 0}; | |||
SwanCommon::Vec2i size = {1, 1}; | |||
float zoom = 1; | |||
}; | |||
class Renderer { | |||
@@ -41,9 +41,10 @@ public: | |||
~Renderer(); | |||
void drawChunk(RenderChunk chunk, SwanCommon::Vec2 pos); | |||
void drawChunkShadow(RenderChunkShadow shadow, SwanCommon::Vec2 pos); | |||
void drawTile(TileID id, Mat3gf mat); | |||
void drawSprite(RenderSprite sprite, Mat3gf mat, int y = 0); | |||
void drawSprite(RenderSprite sprite, SwanCommon::Vec2 pos, int y = 0); | |||
void drawSpriteFlipped(RenderSprite chunk, SwanCommon::Vec2 pos, int y = 0); | |||
void drawRect(SwanCommon::Vec2 pos, SwanCommon::Vec2 size); | |||
void draw(const RenderCamera &cam); | |||
@@ -55,42 +56,75 @@ public: | |||
void modifyChunk(RenderChunk chunk, SwanCommon::Vec2i pos, TileID id); | |||
void destroyChunk(RenderChunk chunk); | |||
RenderChunkShadow createChunkShadow( | |||
uint8_t data[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT]); | |||
void modifyChunkShadow( | |||
RenderChunkShadow shadow, | |||
uint8_t data[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT]); | |||
void destroyChunkShadow(RenderChunkShadow chunk); | |||
RenderSprite createSprite(void *data, int width, int height, int fh); | |||
RenderSprite createSprite(void *data, int width, int height); | |||
void destroySprite(RenderSprite sprite); | |||
SwanCommon::Vec2 winScale() { return winScale_; } | |||
private: | |||
struct DrawChunk { | |||
SwanCommon::Vec2 pos; | |||
RenderChunk chunk; | |||
}; | |||
struct DrawShadow { | |||
SwanCommon::Vec2 pos; | |||
RenderChunkShadow shadow; | |||
}; | |||
struct DrawTile { | |||
Mat3gf transform; | |||
TileID id; | |||
}; | |||
struct DrawSprite { | |||
Mat3gf transform; | |||
int frame; | |||
RenderSprite sprite; | |||
}; | |||
struct DrawRect { | |||
SwanCommon::Vec2 pos; | |||
SwanCommon::Vec2 size; | |||
}; | |||
SwanCommon::Vec2 winScale_ = {1, 1}; | |||
std::unique_ptr<RendererState> state_; | |||
std::vector<DrawChunk> drawChunks_; | |||
std::vector<DrawShadow> drawChunkShadows_; | |||
std::vector<DrawTile> drawTiles_; | |||
std::vector<DrawSprite> drawSprites_; | |||
std::vector<DrawRect> drawRects_; | |||
}; | |||
inline void Renderer::drawChunk(RenderChunk chunk, SwanCommon::Vec2 pos) { | |||
drawChunks_.emplace_back(pos, chunk); | |||
drawChunks_.push_back({pos, chunk}); | |||
} | |||
inline void Renderer::drawSprite(RenderSprite sprite, Mat3gf mat, int frame) { | |||
drawSprites_.emplace_back(mat, frame, sprite); | |||
inline void Renderer::drawChunkShadow(RenderChunkShadow shadow, SwanCommon::Vec2 pos) { | |||
drawChunkShadows_.push_back({pos, shadow}); | |||
} | |||
inline void Renderer::drawSprite(RenderSprite sprite, SwanCommon::Vec2 pos, int frame) { | |||
drawSprites_.emplace_back(Mat3gf{}.translate(pos), frame, sprite); | |||
inline void Renderer::drawTile(TileID id, Mat3gf mat) { | |||
drawTiles_.push_back({mat, id}); | |||
} | |||
inline void Renderer::drawSprite(RenderSprite sprite, Mat3gf mat, int frame) { | |||
drawSprites_.push_back({mat, frame, sprite}); | |||
} | |||
inline void Renderer::drawSpriteFlipped(RenderSprite sprite, SwanCommon::Vec2 pos, int frame) { | |||
drawSprites_.emplace_back(Mat3gf{}.translate(pos).scale({ -1, 1 }), frame, sprite); | |||
inline void Renderer::drawRect(SwanCommon::Vec2 pos, SwanCommon::Vec2 size) { | |||
drawRects_.push_back({pos, size}); | |||
} | |||
} |
@@ -25,7 +25,7 @@ class ResourceBuilder { | |||
public: | |||
ResourceBuilder(Renderer &rnd): rnd_(rnd) {} | |||
RenderSprite addSprite(std::string name, void *data, int width, int height, int fh); | |||
RenderSprite addSprite(std::string name, void *data, int width, int height, int frameHeight); | |||
RenderSprite addSprite(std::string name, void *data, int width, int height); | |||
void addTile(Renderer::TileID id, void *data, int frames = 1); | |||
void addTile(Renderer::TileID id, std::unique_ptr<unsigned char[]> data, int frames = 1); | |||
@@ -44,14 +44,11 @@ public: | |||
ResourceManager(ResourceBuilder &&builder); | |||
~ResourceManager(); | |||
RenderSprite getSprite(std::string name) { return sprites_.at(std::move(name)); } | |||
void tick(); | |||
private: | |||
Renderer &rnd_; | |||
std::unordered_map<std::string, RenderSprite> sprites_; | |||
std::unordered_map<std::string, RenderTile> tiles_; | |||
std::unordered_map<std::string, Renderer::TileID> tiles_; | |||
std::vector<ResourceTileAnimation> tileAnims_; | |||
}; | |||
@@ -3,6 +3,10 @@ | |||
#include <swan-common/Vector2.h> | |||
#include <memory> | |||
#include "util.h" | |||
struct SDL_Window; | |||
namespace Cygnet { | |||
struct WindowState; | |||
@@ -13,11 +17,13 @@ public: | |||
~Window(); | |||
void makeCurrent(); | |||
void clear(); | |||
void clear(Color color = {}); | |||
void flip(); | |||
void onResize(int w, int h); | |||
SwanCommon::Vec2i size() { return { w_, h_ }; } | |||
SDL_Window *sdlWindow(); | |||
private: | |||
std::unique_ptr<WindowState> state_; | |||
int w_; |
@@ -2,10 +2,19 @@ | |||
namespace Cygnet::Shaders { | |||
extern const char *chunkVx; | |||
extern const char *chunkFr; | |||
extern const char *chunkShadowVx; | |||
extern const char *chunkShadowFr; | |||
extern const char *tileVx; | |||
extern const char *tileFr; | |||
extern const char *spriteVx; | |||
extern const char *spriteFr; | |||
extern const char *chunkVx; | |||
extern const char *chunkFr; | |||
extern const char *rectVx; | |||
extern const char *rectFr; | |||
} |
@@ -16,6 +16,16 @@ using GLfloat = float; | |||
using Mat3gf = SwanCommon::Matrix3<GLfloat>; | |||
struct Color { | |||
float r = 0, g = 0, b = 0, a = 1; | |||
}; | |||
struct ByteColor { | |||
uint8_t r = 0, g = 0, b = 0, a = 0; | |||
operator Color() { return { r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f }; } | |||
}; | |||
struct SDLError: public std::exception { | |||
SDLError(std::string msg): message(std::move(msg)) {} | |||
const char *what() const noexcept override { return message.c_str(); } |
@@ -1,27 +0,0 @@ | |||
#include "Context.h" | |||
#include <SDL.h> | |||
#include "util.h" | |||
namespace Cygnet { | |||
Context::Context() { | |||
SDL_Init(SDL_INIT_VIDEO); | |||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); | |||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); | |||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); | |||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); | |||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); | |||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); | |||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8); | |||
SDL_GL_SetSwapInterval(1); | |||
} | |||
Context::~Context() { | |||
SDL_Quit(); | |||
} | |||
} |
@@ -73,6 +73,112 @@ struct ChunkProg: public GlProgram { | |||
} | |||
}; | |||
struct ChunkShadowProg: public GlProgram { | |||
template<typename... T> | |||
ChunkShadowProg(const T &... shaders): GlProgram(shaders...) { init(); } | |||
~ChunkShadowProg() { deinit(); } | |||
GLint camera = uniformLoc("camera"); | |||
GLint pos = uniformLoc("pos"); | |||
GLint vertex = attribLoc("vertex"); | |||
GLint tex = uniformLoc("tex"); | |||
GLuint vbo; | |||
static constexpr float ch = (float)SwanCommon::CHUNK_HEIGHT; | |||
static constexpr float cw = (float)SwanCommon::CHUNK_WIDTH; | |||
static constexpr GLfloat vertexes[] = { | |||
0.0f, 0.0f, // pos 0: top left | |||
0.0f, ch , // pos 1: bottom left | |||
cw, ch, // pos 2: bottom right | |||
cw, ch, // pos 2: bottom right | |||
cw, 0.0f, // pos 3: top right | |||
0.0f, 0.0f, // pos 0: top left | |||
}; | |||
void enable() { | |||
glUseProgram(id()); | |||
glBindBuffer(GL_ARRAY_BUFFER, vbo); | |||
glVertexAttribPointer(vertex, 2, GL_FLOAT, GL_FALSE, 0, (void *)0); | |||
glEnableVertexAttribArray(vertex); | |||
glCheck(); | |||
glUniform1i(tex, 0); | |||
} | |||
void disable() { | |||
glDisableVertexAttribArray(vertex); | |||
glCheck(); | |||
} | |||
void init() { | |||
glGenBuffers(1, &vbo); | |||
glCheck(); | |||
glBindBuffer(GL_ARRAY_BUFFER, vbo); | |||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW); | |||
glCheck(); | |||
} | |||
void deinit() { | |||
glDeleteBuffers(1, &vbo); | |||
glCheck(); | |||
} | |||
}; | |||
struct TileProg: public GlProgram { | |||
template<typename... T> | |||
TileProg(const T &... shaders): GlProgram(shaders...) { init(); } | |||
~TileProg() { deinit(); } | |||
GLint camera = uniformLoc("camera"); | |||
GLint transform = uniformLoc("transform"); | |||
GLint vertex = attribLoc("vertex"); | |||
GLint tileAtlas = uniformLoc("tileAtlas"); | |||
GLint tileAtlasSize = uniformLoc("tileAtlasSize"); | |||
GLint tileID = uniformLoc("tileID"); | |||
GLuint vbo; | |||
static constexpr GLfloat vertexes[] = { | |||
0.0f, 0.0f, // pos 0: top left | |||
0.0f, 1.0f, // pos 1: bottom left | |||
1.0f, 1.0f, // pos 2: bottom right | |||
1.0f, 1.0f, // pos 2: bottom right | |||
1.0f, 0.0f, // pos 3: top right | |||
0.0f, 0.0f, // pos 0: top left | |||
}; | |||
void enable() { | |||
glUseProgram(id()); | |||
glBindBuffer(GL_ARRAY_BUFFER, vbo); | |||
glVertexAttribPointer(vertex, 2, GL_FLOAT, GL_FALSE, 0, (void *)0); | |||
glEnableVertexAttribArray(vertex); | |||
glCheck(); | |||
glUniform1i(tileAtlas, 0); | |||
} | |||
void disable() { | |||
glDisableVertexAttribArray(vertex); | |||
glCheck(); | |||
} | |||
void init() { | |||
glGenBuffers(1, &vbo); | |||
glCheck(); | |||
glBindBuffer(GL_ARRAY_BUFFER, vbo); | |||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW); | |||
glCheck(); | |||
} | |||
void deinit() { | |||
glDeleteBuffers(1, &vbo); | |||
glCheck(); | |||
} | |||
}; | |||
struct SpriteProg: public GlProgram { | |||
template<typename... T> | |||
SpriteProg(const T &... shaders): GlProgram(shaders...) { init(); } | |||
@@ -80,6 +186,7 @@ struct SpriteProg: public GlProgram { | |||
GLint camera = uniformLoc("camera"); | |||
GLint transform = uniformLoc("transform"); | |||
GLint frameSize = uniformLoc("frameSize"); | |||
GLint frameInfo = uniformLoc("frameInfo"); | |||
GLint vertex = attribLoc("vertex"); | |||
GLint tex = uniformLoc("tex"); | |||
@@ -125,14 +232,72 @@ struct SpriteProg: public GlProgram { | |||
} | |||
}; | |||
struct RectProg: public GlProgram { | |||
template<typename... T> | |||
RectProg(const T &... shaders): GlProgram(shaders...) { init(); } | |||
~RectProg() { deinit(); } | |||
GLint camera = uniformLoc("camera"); | |||
GLint pos = uniformLoc("pos"); | |||
GLint size = uniformLoc("size"); | |||
GLint vertex = attribLoc("vertex"); | |||
GLuint vbo; | |||
static constexpr GLfloat vertexes[] = { | |||
0.0f, 0.0f, // pos 0: top left | |||
0.0f, 1.0f, // pos 1: bottom left | |||
1.0f, 1.0f, // pos 2: bottom right | |||
1.0f, 1.0f, // pos 2: bottom right | |||
1.0f, 0.0f, // pos 3: top right | |||
0.0f, 0.0f, // pos 0: top left | |||
}; | |||
void enable() { | |||
glUseProgram(id()); | |||
glBindBuffer(GL_ARRAY_BUFFER, vbo); | |||
glVertexAttribPointer(vertex, 2, GL_FLOAT, GL_FALSE, 0, (void *)0); | |||
glEnableVertexAttribArray(vertex); | |||
glCheck(); | |||
} | |||
void disable() { | |||
glDisableVertexAttribArray(vertex); | |||
glCheck(); | |||
} | |||
void init() { | |||
glGenBuffers(1, &vbo); | |||
glCheck(); | |||
glBindBuffer(GL_ARRAY_BUFFER, vbo); | |||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW); | |||
glCheck(); | |||
} | |||
void deinit() { | |||
glDeleteBuffers(1, &vbo); | |||
glCheck(); | |||
} | |||
}; | |||
struct RendererState { | |||
GlVxShader spriteVx{Shaders::spriteVx}; | |||
GlFrShader spriteFr{Shaders::spriteFr}; | |||
GlVxShader chunkVx{Shaders::chunkVx}; | |||
GlFrShader chunkFr{Shaders::chunkFr}; | |||
GlVxShader chunkShadowVx{Shaders::chunkShadowVx}; | |||
GlFrShader chunkShadowFr{Shaders::chunkShadowFr}; | |||
GlVxShader tileVx{Shaders::tileVx}; | |||
GlFrShader tileFr{Shaders::tileFr}; | |||
GlVxShader spriteVx{Shaders::spriteVx}; | |||
GlFrShader spriteFr{Shaders::spriteFr}; | |||
GlVxShader rectVx{Shaders::rectVx}; | |||
GlFrShader rectFr{Shaders::rectFr}; | |||
SpriteProg spriteProg{spriteVx, spriteFr}; | |||
ChunkProg chunkProg{chunkVx, chunkFr}; | |||
ChunkShadowProg chunkShadowProg{chunkShadowVx, chunkShadowFr}; | |||
TileProg tileProg{tileVx, tileFr}; | |||
SpriteProg spriteProg{spriteVx, spriteFr}; | |||
RectProg rectProg{rectVx, rectFr}; | |||
GLuint atlasTex; | |||
SwanCommon::Vec2 atlasTexSize; | |||
@@ -148,14 +313,23 @@ Renderer::~Renderer() = default; | |||
void Renderer::draw(const RenderCamera &cam) { | |||
Mat3gf camMat; | |||
// Make the matrix translate to { -camX, -camY }, fix up the aspect ratio, | |||
// flip the Y axis so that positive Y direction is down, and scale according to zoom. | |||
float ratio = (float)cam.size.y / (float)cam.size.x; | |||
camMat.translate(cam.pos.scale(-ratio, 1) * cam.zoom); | |||
camMat.scale({ cam.zoom * ratio, -cam.zoom }); | |||
camMat.translate(-cam.pos); | |||
if (cam.size.y > cam.size.x) { | |||
float ratio = (float)cam.size.y / (float)cam.size.x; | |||
winScale_ = {1/ratio, 1}; | |||
camMat.scale({cam.zoom * ratio, -cam.zoom}); | |||
} else { | |||
float ratio = (float)cam.size.x / (float)cam.size.y; | |||
winScale_ = {1, 1/ratio}; | |||
camMat.scale({cam.zoom, -cam.zoom * ratio}); | |||
} | |||
auto &chunkProg = state_->chunkProg; | |||
auto &chunkShadowProg = state_->chunkShadowProg; | |||
auto &tileProg = state_->tileProg; | |||
auto &spriteProg = state_->spriteProg; | |||
auto &rectProg = state_->rectProg; | |||
{ | |||
chunkProg.enable(); | |||
@@ -181,6 +355,30 @@ void Renderer::draw(const RenderCamera &cam) { | |||
chunkProg.disable(); | |||
} | |||
{ | |||
tileProg.enable(); | |||
glUniformMatrix3fv(tileProg.camera, 1, GL_TRUE, camMat.data()); | |||
glCheck(); | |||
glUniform2f(tileProg.tileAtlasSize, state_->atlasTexSize.x, state_->atlasTexSize.y); | |||
glCheck(); | |||
glActiveTexture(GL_TEXTURE0); | |||
glBindTexture(GL_TEXTURE_2D, state_->atlasTex); | |||
glCheck(); | |||
glActiveTexture(GL_TEXTURE1); | |||
for (auto [mat, id]: drawTiles_) { | |||
glUniformMatrix3fv(tileProg.transform, 1, GL_TRUE, mat.data()); | |||
glUniform1f(tileProg.tileID, id); | |||
glDrawArrays(GL_TRIANGLES, 0, 6); | |||
glCheck(); | |||
} | |||
drawTiles_.clear(); | |||
tileProg.disable(); | |||
} | |||
{ | |||
spriteProg.enable(); | |||
glUniformMatrix3fv(spriteProg.camera, 1, GL_TRUE, camMat.data()); | |||
@@ -188,9 +386,9 @@ void Renderer::draw(const RenderCamera &cam) { | |||
glActiveTexture(GL_TEXTURE0); | |||
for (auto [mat, frame, sprite]: drawSprites_) { | |||
mat.scale(sprite.scale); | |||
glUniformMatrix3fv(spriteProg.transform, 1, GL_TRUE, mat.data()); | |||
glUniform3f(spriteProg.frameInfo, sprite.scale.y, sprite.frameCount, frame); | |||
glUniform2f(spriteProg.frameSize, sprite.scale.x, sprite.scale.y); | |||
glUniform2f(spriteProg.frameInfo, sprite.frameCount, frame); | |||
glBindTexture(GL_TEXTURE_2D, sprite.tex); | |||
glDrawArrays(GL_TRIANGLES, 0, 6); | |||
glCheck(); | |||
@@ -199,6 +397,39 @@ void Renderer::draw(const RenderCamera &cam) { | |||
drawSprites_.clear(); | |||
spriteProg.disable(); | |||
} | |||
{ | |||
rectProg.enable(); | |||
glUniformMatrix3fv(rectProg.camera, 1, GL_TRUE, camMat.data()); | |||
glCheck(); | |||
for (auto [pos, size]: drawRects_) { | |||
glUniform2f(rectProg.pos, pos.x, pos.y); | |||
glUniform2f(rectProg.size, size.x, size.y); | |||
glDrawArrays(GL_TRIANGLES, 0, 6); | |||
glCheck(); | |||
} | |||
drawRects_.clear(); | |||
rectProg.disable(); | |||
} | |||
{ | |||
chunkShadowProg.enable(); | |||
glUniformMatrix3fv(chunkShadowProg.camera, 1, GL_TRUE, camMat.data()); | |||
glCheck(); | |||
glActiveTexture(GL_TEXTURE0); | |||
for (auto [pos, shadow]: drawChunkShadows_) { | |||
glUniform2f(chunkShadowProg.pos, pos.x, pos.y); | |||
glBindTexture(GL_TEXTURE_2D, shadow.tex); | |||
glDrawArrays(GL_TRIANGLES, 0, 6); | |||
glCheck(); | |||
} | |||
drawChunkShadows_.clear(); | |||
chunkShadowProg.disable(); | |||
} | |||
} | |||
void Renderer::uploadTileAtlas(const void *data, int width, int height) { | |||
@@ -230,10 +461,6 @@ void Renderer::modifyTile(TileID id, const void *data) { | |||
RenderChunk Renderer::createChunk( | |||
TileID tiles[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT]) { | |||
// TODO: Maybe don't do this here? Maybe instead store the buffer and | |||
// upload the texture in the draw method? | |||
// The current approach needs createChunk to be called on the graphics thread. | |||
RenderChunk chunk; | |||
glGenTextures(1, &chunk.tex); | |||
glCheck(); | |||
@@ -242,8 +469,8 @@ RenderChunk Renderer::createChunk( | |||
glBindTexture(GL_TEXTURE_2D, chunk.tex); | |||
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_REPEAT); | |||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); | |||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |||
glCheck(); | |||
static_assert( | |||
@@ -306,6 +533,47 @@ void Renderer::destroyChunk(RenderChunk chunk) { | |||
glCheck(); | |||
} | |||
RenderChunkShadow Renderer::createChunkShadow( | |||
uint8_t data[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT]) { | |||
RenderChunkShadow shadow; | |||
glGenTextures(1, &shadow.tex); | |||
glCheck(); | |||
glActiveTexture(GL_TEXTURE0); | |||
glBindTexture(GL_TEXTURE_2D, shadow.tex); | |||
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); | |||
glCheck(); | |||
glTexImage2D( | |||
GL_TEXTURE_2D, 0, GL_LUMINANCE, | |||
SwanCommon::CHUNK_WIDTH, SwanCommon::CHUNK_HEIGHT, | |||
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); | |||
glCheck(); | |||
return shadow; | |||
} | |||
void Renderer::modifyChunkShadow( | |||
RenderChunkShadow shadow, | |||
uint8_t data[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT]) { | |||
glActiveTexture(GL_TEXTURE0); | |||
glBindTexture(GL_TEXTURE_2D, shadow.tex); | |||
glTexImage2D( | |||
GL_TEXTURE_2D, 0, GL_LUMINANCE, | |||
SwanCommon::CHUNK_WIDTH, SwanCommon::CHUNK_HEIGHT, | |||
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); | |||
glCheck(); | |||
} | |||
void Renderer::destroyChunkShadow(RenderChunkShadow shadow) { | |||
glDeleteTextures(1, &shadow.tex); | |||
glCheck(); | |||
} | |||
RenderSprite Renderer::createSprite(void *data, int width, int height, int fh) { | |||
RenderSprite sprite; | |||
sprite.scale = { |
@@ -32,7 +32,6 @@ void TileAtlas::addTile(size_t tileId, const void *data) { | |||
const unsigned char *bytes = (const unsigned char *)data; | |||
size_t x = tileId % state_->tilesPerLine; | |||
size_t y = tileId / state_->tilesPerLine; | |||
std::cerr << "Tile " << tileId << " to " << x << ", " << y << '\n'; | |||
if (state_->width <= x) { | |||
state_->width = x + 1; |
@@ -14,6 +14,15 @@ struct WindowState { | |||
Window::Window(const char *name, int w, int h): | |||
state_(std::make_unique<WindowState>()), w_(w), h_(h) { | |||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); | |||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); | |||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); | |||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); | |||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); | |||
//SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); | |||
//SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8); | |||
SDL_GL_SetSwapInterval(1); | |||
state_->window = SDL_CreateWindow(name, | |||
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, | |||
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | | |||
@@ -41,7 +50,8 @@ void Window::makeCurrent() { | |||
glCheck(); | |||
} | |||
void Window::clear() { | |||
void Window::clear(Color color) { | |||
glClearColor(color.r, color.g, color.b, color.a); | |||
glClear(GL_COLOR_BUFFER_BIT); | |||
glCheck(); | |||
} | |||
@@ -54,8 +64,15 @@ void Window::flip() { | |||
void Window::onResize(int w, int h) { | |||
w_ = w; | |||
h_ = h; | |||
glViewport(0, 0, w, h); | |||
int dw, dh; | |||
SDL_GL_GetDrawableSize(state_->window, &dw, &dh); | |||
glViewport(0, 0, dw, dh); | |||
glCheck(); | |||
} | |||
SDL_Window *Window::sdlWindow() { | |||
return state_->window; | |||
} | |||
} |
@@ -2,72 +2,103 @@ | |||
namespace Cygnet::Shaders { | |||
const char *spriteVx = R"glsl( | |||
const char *chunkVx = R"glsl( | |||
precision mediump float; | |||
uniform mat3 camera; | |||
uniform vec2 pos; | |||
attribute vec2 vertex; | |||
varying vec2 v_tileCoord; | |||
void main() { | |||
vec3 pos = camera * vec3(pos + vertex, 1); | |||
gl_Position = vec4(pos.xy, 0, 1); | |||
v_tileCoord = vertex; | |||
} | |||
)glsl"; | |||
const char *chunkFr = R"glsl( | |||
precision mediump float; | |||
#define TILE_SIZE 32.0 | |||
#define CHUNK_WIDTH 64 | |||
#define CHUNK_HEIGHT 64 | |||
varying vec2 v_tileCoord; | |||
uniform sampler2D tileAtlas; | |||
uniform vec2 tileAtlasSize; | |||
uniform sampler2D tiles; | |||
void main() { | |||
vec2 tilePos = floor(vec2(v_tileCoord.x, v_tileCoord.y)); | |||
vec4 tileColor = texture2D(tiles, tilePos / vec2(CHUNK_WIDTH, CHUNK_HEIGHT)); | |||
float tileID = floor((tileColor.r * 256.0 + tileColor.a) * 256.0); | |||
// 1/(TILE_SIZE*16) plays the same role here as in the sprite vertex shader. | |||
vec2 offset = v_tileCoord - tilePos; | |||
vec2 pixoffset = (1.0 - offset * 2.0) / (TILE_SIZE * 16.0); | |||
vec2 atlasPos = vec2( | |||
pixoffset.x + tileID + offset.x, | |||
pixoffset.y + floor(tileID / tileAtlasSize.x) + offset.y); | |||
gl_FragColor = texture2D(tileAtlas, atlasPos / tileAtlasSize); | |||
} | |||
)glsl"; | |||
const char *chunkShadowVx = R"glsl( | |||
precision mediump float; | |||
#define CHUNK_WIDTH 64 | |||
#define CHUNK_HEIGHT 64 | |||
uniform mat3 camera; | |||
uniform mat3 transform; | |||
uniform vec3 frameInfo; // frame height, frame count, frame index | |||
uniform vec2 pos; | |||
attribute vec2 vertex; | |||
varying vec2 v_texCoord; | |||
void main() { | |||
// Here, I'm basically treating 1/(TILE_SIZE*16) as half the size of a "pixel". | |||
// It's just an arbitrary small number, but it works as an offset to make sure | |||
// neighbouring parts of the atlas don't bleed into the frame we actually | |||
// want to draw due to (nearest neighbour) interpolation. | |||
float pixoffset = (1.0 - vertex.y * 2.0) / (frameInfo.x * TILE_SIZE * 16.0); | |||
v_texCoord = vec2( | |||
vertex.x, | |||
(frameInfo.x * frameInfo.z + (frameInfo.x * vertex.y)) / | |||
(frameInfo.x * frameInfo.y) + pixoffset); | |||
vec3 pos = camera * transform * vec3(vertex, 1); | |||
vec3 pos = camera * vec3(pos + vertex, 1); | |||
gl_Position = vec4(pos.xy, 0, 1); | |||
v_texCoord = vertex / vec2(CHUNK_WIDTH, CHUNK_HEIGHT); | |||
} | |||
)glsl"; | |||
const char *spriteFr = R"glsl( | |||
const char *chunkShadowFr = R"glsl( | |||
precision mediump float; | |||
varying vec2 v_texCoord; | |||
uniform sampler2D tex; | |||
void main() { | |||
gl_FragColor = texture2D(tex, v_texCoord); | |||
vec4 color = texture2D(tex, v_texCoord); | |||
gl_FragColor = vec4(0, 0, 0, 1.0 - color.r); | |||
} | |||
)glsl"; | |||
const char *chunkVx = R"glsl( | |||
const char *tileVx = R"glsl( | |||
precision mediump float; | |||
uniform mat3 camera; | |||
uniform vec2 pos; | |||
uniform mat3 transform; | |||
attribute vec2 vertex; | |||
varying vec2 v_tileCoord; | |||
void main() { | |||
vec3 pos = camera * vec3(pos + vertex, 1); | |||
vec3 pos = camera * transform * vec3(vertex, 1); | |||
gl_Position = vec4(pos.xy, 0, 1); | |||
v_tileCoord = vec2(vertex.x, vertex.y); | |||
v_tileCoord = vertex; | |||
} | |||
)glsl"; | |||
const char *chunkFr = R"glsl( | |||
const char *tileFr = R"glsl( | |||
precision mediump float; | |||
#define TILE_SIZE 32.0 | |||
#define CHUNK_WIDTH 64 | |||
#define CHUNK_HEIGHT 64 | |||
varying vec2 v_tileCoord; | |||
uniform sampler2D tileAtlas; | |||
uniform vec2 tileAtlasSize; | |||
uniform sampler2D tiles; | |||
uniform float tileID; | |||
void main() { | |||
vec2 tilePos = floor(vec2(v_tileCoord.x, v_tileCoord.y)); | |||
vec4 tileColor = texture2D(tiles, tilePos / vec2(CHUNK_WIDTH, CHUNK_HEIGHT)); | |||
float tileID = floor((tileColor.r * 256.0 + tileColor.a) * 256.0); | |||
// 1/(TILE_SIZE*16) plays the same role here as in the sprite vertex shader. | |||
vec2 offset = v_tileCoord - tilePos; | |||
vec2 offset = v_tileCoord; | |||
vec2 pixoffset = (1.0 - offset * 2.0) / (TILE_SIZE * 16.0); | |||
vec2 atlasPos = vec2( | |||
pixoffset.x + tileID + offset.x, | |||
@@ -77,4 +108,70 @@ const char *chunkFr = R"glsl( | |||
} | |||
)glsl"; | |||
const char *spriteVx = R"glsl( | |||
precision mediump float; | |||
#define TILE_SIZE 32.0 | |||
uniform mat3 camera; | |||
uniform mat3 transform; | |||
uniform vec2 frameSize; | |||
uniform vec2 frameInfo; // frame count, frame index | |||
attribute vec2 vertex; | |||
varying vec2 v_texCoord; | |||
void main() { | |||
// Here, I'm basically treating 1/(TILE_SIZE*16) as half the size of a "pixel". | |||
// It's just an arbitrary small number, but it works as an offset to make sure | |||
// neighbouring parts of the atlas don't bleed into the frame we actually | |||
// want to draw due to (nearest neighbour) interpolation. | |||
float pixoffset = (1.0 - vertex.y * 2.0) / (frameSize.y * TILE_SIZE * 16.0); | |||
v_texCoord = vec2( | |||
vertex.x, | |||
(frameSize.y * frameInfo.y + (frameSize.y * vertex.y)) / | |||
(frameSize.y * frameInfo.x) + pixoffset); | |||
vec3 pos = camera * transform * vec3(vertex * frameSize, 1); | |||
gl_Position = vec4(pos.xy, 0, 1); | |||
} | |||
)glsl"; | |||
const char *spriteFr = R"glsl( | |||
precision mediump float; | |||
varying vec2 v_texCoord; | |||
uniform sampler2D tex; | |||
void main() { | |||
gl_FragColor = texture2D(tex, v_texCoord); | |||
} | |||
)glsl"; | |||
const char *rectVx = R"glsl( | |||
precision mediump float; | |||
uniform mat3 camera; | |||
uniform vec2 pos; | |||
uniform vec2 size; | |||
attribute vec2 vertex; | |||
varying vec2 v_coord; | |||
void main() { | |||
vec3 pos = camera * vec3(pos + vertex * size, 1); | |||
gl_Position = vec4(pos.xy, 0, 1); | |||
v_coord = vertex * size; | |||
} | |||
)glsl"; | |||
const char *rectFr = R"glsl( | |||
precision mediump float; | |||
#define THICKNESS 0.02 | |||
varying vec2 v_coord; | |||
uniform vec2 size; | |||
void main() { | |||
vec2 invCoord = size - v_coord; | |||
float minDist = min(v_coord.x, min(v_coord.y, min(invCoord.x, invCoord.y))); | |||
gl_FragColor = vec4(0.6, 0.6, 0.6, 0.8) * float(minDist < THICKNESS); | |||
} | |||
)glsl"; | |||
} |
@@ -3,26 +3,23 @@ add_library(libswan SHARED | |||
src/traits/InventoryTrait.cc | |||
src/traits/PhysicsTrait.cc | |||
src/Animation.cc | |||
src/assets.cc | |||
src/Chunk.cc | |||
src/Clock.cc | |||
src/drawutil.cc | |||
src/Entity.cc | |||
src/Game.cc | |||
src/gfxutil.cc | |||
src/Item.cc | |||
src/ItemStack.cc | |||
src/LightServer.cc | |||
src/Mod.cc | |||
src/OS.cc | |||
src/Resource.cc | |||
src/Tile.cc | |||
src/World.cc | |||
src/WorldPlane.cc) | |||
target_include_directories(libswan | |||
PUBLIC "include" | |||
PRIVATE "include/swan") | |||
set_target_properties(libswan PROPERTIES OUTPUT_NAME swan) | |||
target_link_libraries(libswan swan-common ${libraries}) | |||
target_link_libraries(libswan swan-common libcygnet ${libraries}) | |||
install(TARGETS libswan DESTINATION swan/libswan) | |||
@@ -1,29 +1,33 @@ | |||
#pragma once | |||
#include <SDL.h> | |||
#include <cygnet/Renderer.h> | |||
#include "common.h" | |||
#include "Resource.h" | |||
#include "Clock.h" | |||
#include "Resource.h" | |||
namespace Swan { | |||
class Animation { | |||
public: | |||
Animation(ImageResource &resource, float interval, SDL_RendererFlip flip = SDL_FLIP_NONE): | |||
resource_(resource), interval_(interval), timer_(interval), flip_(flip) {} | |||
Animation(Cygnet::RenderSprite sprite, float interval, Cygnet::Mat3gf mat = {}): | |||
sprite_(sprite), interval_(interval), timer_(interval) {} | |||
void tick(float dt); | |||
void draw(const Vec2 &pos, Win &win); | |||
void reset(); | |||
void draw(Cygnet::Renderer &rnd, Cygnet::Mat3gf mat); | |||
private: | |||
ImageResource &resource_; | |||
Cygnet::RenderSprite sprite_; | |||
float interval_; | |||
float timer_; | |||
SDL_RendererFlip flip_; | |||
int frame_ = 0; | |||
}; | |||
inline void Animation::draw(Cygnet::Renderer &rnd, Cygnet::Mat3gf mat) { | |||
rnd.drawSprite(sprite_, mat, frame_); | |||
} | |||
} | |||
@@ -3,6 +3,7 @@ | |||
#include <string.h> | |||
#include <stdint.h> | |||
#include <memory> | |||
#include <cygnet/Renderer.h> | |||
#include "util.h" | |||
#include "common.h" | |||
@@ -47,14 +48,14 @@ public: | |||
return getTileData()[pos.y * CHUNK_WIDTH + pos.x]; | |||
} | |||
void setTileID(RelPos pos, Tile::ID id, SDL_Texture *tex) { | |||
void setTileID(RelPos pos, Tile::ID id) { | |||
getTileData()[pos.y * CHUNK_WIDTH + pos.x] = id; | |||
drawList_.push_back({ pos, tex }); | |||
changeList_.emplace_back(pos, id); | |||
isModified_ = true; | |||
} | |||
void setTileData(RelPos pos, Tile::ID id) { | |||
getTileData()[pos.y * CHUNK_WIDTH + pos.x] = id; | |||
needRender_ = true; | |||
} | |||
uint8_t getLightLevel(RelPos pos) { | |||
@@ -66,38 +67,33 @@ public: | |||
needLightRender_ = true; | |||
} | |||
void render(const Context &ctx, SDL_Renderer *rnd); | |||
void renderLight(const Context &ctx, SDL_Renderer *rnd); | |||
void compress(); | |||
void generateDone(); | |||
void keepActive(); | |||
void decompress(); | |||
void draw(const Context &ctx, Win &win); | |||
void compress(Cygnet::Renderer &rnd); | |||
void destroy(Cygnet::Renderer &rnd) { rnd.destroyChunk(renderChunk_); } | |||
void draw(const Context &ctx, Cygnet::Renderer &rnd); | |||
TickAction tick(float dt); | |||
bool isActive() { return deactivateTimer_ > 0; } | |||
void keepActive(); | |||
void markModified() { isModified_ = true; } | |||
ChunkPos pos_; | |||
private: | |||
static constexpr float DEACTIVATE_INTERVAL = 20; | |||
void renderList(SDL_Renderer *rnd); | |||
bool isCompressed() { return compressedSize_ != -1; } | |||
std::unique_ptr<uint8_t[]> data_; | |||
std::vector<std::pair<RelPos, SDL_Texture *>> drawList_; | |||
std::vector<std::pair<RelPos, Tile::ID>> changeList_; | |||
ssize_t compressedSize_ = -1; // -1 if not compressed, a positive number if compressed | |||
bool needRender_ = false; | |||
Cygnet::RenderChunk renderChunk_; | |||
Cygnet::RenderChunkShadow renderChunkShadow_; | |||
bool needChunkRender_ = true; | |||
bool needLightRender_ = false; | |||
float deactivateTimer_ = DEACTIVATE_INTERVAL; | |||
bool isModified_ = false; | |||
CPtr<SDL_Texture, SDL_DestroyTexture> texture_; | |||
CPtr<SDL_Texture, SDL_DestroyTexture> lightTexture_; | |||
}; | |||
} |
@@ -61,7 +61,7 @@ public: | |||
virtual EntityRef spawn(const Context &ctx, const Entity::PackObject &obj) = 0; | |||
virtual void update(const Context &ctx, float dt) = 0; | |||
virtual void tick(const Context &ctx, float dt) = 0; | |||
virtual void draw(const Context &ctx, Win &win) = 0; | |||
virtual void draw(const Context &ctx, Cygnet::Renderer &rnd) = 0; | |||
virtual void erase(size_t idx, size_t generation) = 0; | |||
private: | |||
@@ -84,7 +84,7 @@ public: | |||
EntityRef spawn(const Context &ctx, const Entity::PackObject &obj) override; | |||
void update(const Context &ctx, float dt) override; | |||
void tick(const Context &ctx, float dt) override; | |||
void draw(const Context &ctx, Win &win) override; | |||
void draw(const Context &ctx, Cygnet::Renderer &rnd) override; | |||
void erase(size_t idx, size_t generation) override; | |||
private: | |||
@@ -190,11 +190,11 @@ inline void EntityCollectionImpl<Ent>::tick(const Context &ctx, float dt) { | |||
} | |||
template<typename Ent> | |||
inline void EntityCollectionImpl<Ent>::draw(const Context &ctx, Win &win) { | |||
inline void EntityCollectionImpl<Ent>::draw(const Context &ctx, Cygnet::Renderer &rnd) { | |||
ZoneScopedN(typeid(Ent).name()); | |||
for (auto &ent: entities_) { | |||
ZoneScopedN("draw"); | |||
ent->draw(ctx, win); | |||
ent->draw(ctx, rnd); | |||
} | |||
} | |||
@@ -33,7 +33,7 @@ public: | |||
virtual ~Entity() = default; | |||
virtual void draw(const Context &ctx, Win &win) {} | |||
virtual void draw(const Context &ctx, Cygnet::Renderer &rnd) {} | |||
virtual void update(const Context &ctx, float dt) {} | |||
virtual void tick(const Context &ctx, float dt) {} | |||
virtual void onDespawn(const Context &ctx) {} |
@@ -5,9 +5,10 @@ | |||
#include <string> | |||
#include <optional> | |||
#include <SDL.h> | |||
#include <cygnet/Renderer.h> | |||
#include <cygnet/util.h> | |||
#include "common.h" | |||
#include "Resource.h" | |||
#include "Mod.h" | |||
#include "World.h" | |||
@@ -15,12 +16,7 @@ namespace Swan { | |||
class Game { | |||
public: | |||
Game(Win &win): | |||
win_(win), | |||
mousePos_(0, 0) {} | |||
std::optional<ModWrapper> loadMod(std::string path, World &world); | |||
void createWorld(const std::string &worldgen, const std::vector<std::string> &mods); | |||
void createWorld(const std::string &worldgen, const std::vector<std::string> &modPaths); | |||
void onKeyDown(SDL_Keysym sym) { | |||
pressedKeys_[sym.scancode] = true; | |||
@@ -33,17 +29,17 @@ public: | |||
} | |||
void onMouseMove(Sint32 x, Sint32 y) { | |||
mousePos_ = { x, y }; | |||
mousePos_ = (Vec2{(float)x, (float)y} / (Vec2)cam_.size) * renderer_.winScale(); | |||
} | |||
void onMouseDown(Sint32 x, Sint32 y, Uint8 button) { | |||
mousePos_ = { x, y }; | |||
onMouseMove(x, y); | |||
pressedButtons_[button] = true; | |||
didPressButtons_[button] = true; | |||
} | |||
} | |||
void onMouseUp(Sint32 x, Sint32 y, Uint8 button) { | |||
mousePos_ = { x, y }; | |||
onMouseMove(x, y); | |||
pressedButtons_[button] = false; | |||
didReleaseButtons_[button] = true; | |||
} | |||
@@ -55,7 +51,7 @@ public: | |||
bool isKeyPressed(SDL_Scancode code) { return pressedKeys_[code]; } | |||
bool wasKeyPressed(SDL_Scancode code) { return didPressKeys_[code]; } | |||
bool wasKeyReleased(SDL_Scancode code) { return didReleaseKeys_[code]; } | |||
Vec2i getMousePos() { return mousePos_; } | |||
Vec2 getMousePos() { return mousePos_; } | |||
bool isMousePressed(Uint8 button) { return pressedButtons_[button]; } | |||
bool wasMousePressed(Uint8 button) { return didPressButtons_[button]; } | |||
bool wasMouseReleased(Uint8 button) { return didReleaseButtons_[button]; } | |||
@@ -63,23 +59,21 @@ public: | |||
TilePos getMouseTile(); | |||
SDL_Color backgroundColor(); | |||
Cygnet::Color backgroundColor(); | |||
void draw(); | |||
void update(float dt); | |||
void tick(float dt); | |||
std::unique_ptr<World> world_ = NULL; | |||
std::unique_ptr<ImageResource> invalidImage_ = NULL; | |||
std::unique_ptr<Tile> invalidTile_ = NULL; | |||
std::unique_ptr<Item> invalidItem_ = NULL; | |||
Win &win_; | |||
Cygnet::Renderer renderer_; | |||
Cygnet::RenderCamera cam_{.zoom = 0.125}; | |||
private: | |||
std::bitset<SDL_NUM_SCANCODES> pressedKeys_; | |||
std::bitset<SDL_NUM_SCANCODES> didPressKeys_; | |||
std::bitset<SDL_NUM_SCANCODES> didReleaseKeys_; | |||
Vec2i mousePos_; | |||
Vec2 mousePos_; | |||
std::bitset<SDL_BUTTON_X2> pressedButtons_; | |||
std::bitset<SDL_BUTTON_X2> didPressButtons_; | |||
std::bitset<SDL_BUTTON_X2> didReleaseButtons_; |
@@ -2,7 +2,7 @@ | |||
#include <string> | |||
#include "Resource.h" | |||
#include "Tile.h" | |||
namespace Swan { | |||
@@ -14,25 +14,12 @@ public: | |||
int maxStack = 64; | |||
}; | |||
Item(const ResourceManager &resources, const Builder &builder): | |||
name(builder.name), image(resources.getImage(builder.image)), | |||
maxStack(builder.maxStack) {} | |||
const Tile::ID id; | |||
const std::string name; | |||
const ImageResource ℑ | |||
const int maxStack; | |||
static std::unique_ptr<Item> createInvalid(Context &ctx); | |||
// For testing, we want to be able to create Items without an actual ImageResource. | |||
// Tests can create a MockItem class which inherits from Item and uses this ctor, | |||
// as long as the test never does anything which tries to follow the image_ member. | |||
// Eventually, this should become unnecessary, because we don't need to require | |||
// a complete ImageResource for a headless server, but for now, this will suffice. | |||
protected: | |||
Item(const Builder &builder): | |||
name(builder.name), image(*(ImageResource *)this), | |||
maxStack(builder.maxStack) {} | |||
Item(Tile::ID id, std::string name, const Builder &builder): | |||
id(id), name(name), maxStack(builder.maxStack) {} | |||
}; | |||
} |
@@ -50,6 +50,7 @@ public: | |||
void onLightRemoved(TilePos pos, float level); | |||
void onChunkAdded(ChunkPos pos, NewLightChunk &&chunk); | |||
void onChunkRemoved(ChunkPos pos); | |||
void flip(); | |||
private: | |||
static constexpr int LIGHT_CUTOFF_DIST = 64; | |||
@@ -82,7 +83,6 @@ private: | |||
void processEvent(const Event &event, std::vector<NewLightChunk> &newChunks); | |||
void run(); | |||
LightCallback &cb_; | |||
bool running_ = true; | |||
std::map<std::pair<int, int>, LightChunk> chunks_; | |||
std::set<std::pair<int, int>> updatedChunks_; | |||
@@ -92,33 +92,31 @@ private: | |||
int buffer_ = 0; | |||
std::vector<Event> buffers_[2] = { {}, {} }; | |||
std::vector<NewLightChunk> newChunkBuffers_[2] = { {}, {} }; | |||
std::thread thread_; | |||
std::condition_variable cond_; | |||
std::mutex mut_; | |||
LightCallback &cb_; | |||
std::thread thread_; | |||
}; | |||
inline void LightServer::onSolidBlockAdded(TilePos pos) { | |||
std::lock_guard<std::mutex> lock(mut_); | |||
buffers_[buffer_].push_back({ Event::Tag::BLOCK_ADDED, pos, { .i = 0 } }); | |||
cond_.notify_one(); | |||
} | |||
inline void LightServer::onSolidBlockRemoved(TilePos pos) { | |||
std::lock_guard<std::mutex> lock(mut_); | |||
buffers_[buffer_].push_back({ Event::Tag::BLOCK_REMOVED, pos, { .i = 0 } }); | |||
cond_.notify_one(); | |||
} | |||
inline void LightServer::onLightAdded(TilePos pos, float level) { | |||
std::lock_guard<std::mutex> lock(mut_); | |||
buffers_[buffer_].push_back({ Event::Tag::LIGHT_ADDED, pos, { .f = level } }); | |||
cond_.notify_one(); | |||
} | |||
inline void LightServer::onLightRemoved(TilePos pos, float level) { | |||
std::lock_guard<std::mutex> lock(mut_); | |||
buffers_[buffer_].push_back({ Event::Tag::LIGHT_REMOVED, pos, { .f = level } }); | |||
cond_.notify_one(); | |||
} | |||
inline void LightServer::onChunkAdded(Vec2i pos, NewLightChunk &&chunk) { | |||
@@ -126,12 +124,14 @@ inline void LightServer::onChunkAdded(Vec2i pos, NewLightChunk &&chunk) { | |||
buffers_[buffer_].push_back({ Event::Tag::CHUNK_ADDED, pos, | |||
{ .i = (int)newChunkBuffers_[buffer_].size() } }); | |||
newChunkBuffers_[buffer_].push_back(std::move(chunk)); | |||
cond_.notify_one(); | |||
} | |||
inline void LightServer::onChunkRemoved(Vec2i pos) { | |||
std::lock_guard<std::mutex> lock(mut_); | |||
buffers_[buffer_].push_back({ Event::Tag::CHUNK_REMOVED, pos, { .i = 0 } }); | |||
} | |||
inline void LightServer::flip() { | |||
cond_.notify_one(); | |||
} | |||
@@ -12,26 +12,26 @@ | |||
#include "WorldGen.h" | |||
#include "Entity.h" | |||
#include "Collection.h" | |||
#include "Resource.h" | |||
#include "OS.h" | |||
#include "util.h" | |||
namespace Swan { | |||
class ModWrapper; | |||
class Mod { | |||
public: | |||
Mod(std::string name): name_(std::move(name)) {} | |||
virtual ~Mod() = default; | |||
void registerImage(const std::string &id); | |||
void registerTile(Tile::Builder tile); | |||
void registerItem(Item::Builder item); | |||
void registerWorldGen(const std::string &name, std::unique_ptr<WorldGen::Factory> gen); | |||
void registerTile(Tile::Builder tile) { tiles_.push_back(tile); } | |||
void registerItem(Item::Builder item) { items_.push_back(item); } | |||
void registerSprite(std::string sprite) { sprites_.push_back(sprite); } | |||
template<typename WG> | |||
void registerWorldGen(const std::string &name) { | |||
worldgens_.push_back(WorldGen::Factory{ | |||
.name = name_ + "::" + name, | |||
void registerWorldGen(std::string name) { | |||
worldGens_.push_back(WorldGen::Factory{ | |||
.name = name, | |||
.create = [](World &world) -> std::unique_ptr<WorldGen> { | |||
return std::make_unique<WG>(world); | |||
} | |||
@@ -44,19 +44,22 @@ public: | |||
std::is_move_constructible_v<Ent>, | |||
"Entities must be movable"); | |||
entities_.push_back(EntityCollection::Factory{ | |||
.name = name_ + "::" + name, | |||
.name = name, | |||
.create = [](std::string name) -> std::unique_ptr<EntityCollection> { | |||
return std::make_unique<EntityCollectionImpl<Ent>>(std::move(name)); | |||
} | |||
}); | |||
} | |||
private: | |||
const std::string name_; | |||
std::vector<std::string> images_; | |||
std::vector<Tile::Builder> tiles_; | |||
std::vector<Item::Builder> items_; | |||
std::vector<WorldGen::Factory> worldgens_; | |||
std::vector<std::string> sprites_; | |||
std::vector<WorldGen::Factory> worldGens_; | |||
std::vector<EntityCollection::Factory> entities_; | |||
friend ModWrapper; | |||
}; | |||
class ModWrapper { | |||
@@ -72,11 +75,12 @@ public: | |||
mod_.reset(); | |||
} | |||
Iter<std::unique_ptr<ImageResource>> buildImages(SDL_Renderer *renderer); | |||
Iter<std::unique_ptr<Tile>> buildTiles(const ResourceManager &resources); | |||
Iter<std::unique_ptr<Item>> buildItems(const ResourceManager &resources); | |||
Iter<WorldGen::Factory> getWorldGens(); | |||
Iter<EntityCollection::Factory> getEntities(); | |||
const std::string &name() { return mod_->name_; } | |||
const std::vector<Tile::Builder> &tiles() { return mod_->tiles_; } | |||
const std::vector<Item::Builder> &items() { return mod_->items_; } | |||
const std::vector<std::string> &sprites() { return mod_->sprites_; } | |||
const std::vector<WorldGen::Factory> &worldGens() { return mod_->worldGens_; } | |||
const std::vector<EntityCollection::Factory> &entities() { return mod_->entities_; } | |||
std::unique_ptr<Mod> mod_; | |||
std::string path_; |
@@ -1,53 +0,0 @@ | |||
#pragma once | |||
#include <SDL.h> | |||
#include <stdint.h> | |||
#include <string> | |||
#include <memory> | |||
#include <unordered_map> | |||
#include "common.h" | |||
namespace Swan { | |||
class ImageResource { | |||
public: | |||
ImageResource( | |||
SDL_Renderer *renderer, const std::string &modpath, const std::string &id); | |||
ImageResource( | |||
SDL_Renderer *renderer, const std::string &name, | |||
int w, int h, uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255); | |||
void tick(float dt); | |||
SDL_Rect frameRect(int frame = -1) const { | |||
if (frame == -1) frame = frame_; | |||
return SDL_Rect{ 0, frameHeight_ * frame, surface_->w, frameHeight_ }; | |||
} | |||
std::unique_ptr<SDL_Surface, void (*)(SDL_Surface *)> surface_{nullptr, &SDL_FreeSurface}; | |||
std::unique_ptr<SDL_Texture, void (*)(SDL_Texture *)> texture_{nullptr, &SDL_DestroyTexture}; | |||
int frameHeight_; | |||
int numFrames_; | |||
std::string name_; | |||
int frame_ = 0; | |||
private: | |||
float switchInterval_ = 1; | |||
float switchTimer_ = switchInterval_; | |||
}; | |||
class ResourceManager { | |||
public: | |||
ResourceManager(Win &win); | |||
void tick(float dt); | |||
ImageResource &getImage(const std::string &name) const; | |||
void addImage(std::unique_ptr<ImageResource> img) { images_[img->name_] = std::move(img); } | |||
private: | |||
std::unordered_map<std::string, std::unique_ptr<ImageResource>> images_; | |||
}; | |||
} |
@@ -3,10 +3,6 @@ | |||
#include <stdint.h> | |||
#include <string> | |||
#include <optional> | |||
#include <memory> | |||
#include "Item.h" | |||
#include "Resource.h" | |||
namespace Swan { | |||
@@ -22,20 +18,16 @@ public: | |||
std::optional<std::string> droppedItem = std::nullopt; | |||
}; | |||
Tile(const ResourceManager &resources, const Builder &builder): | |||
name(builder.name), image(resources.getImage(builder.image)), | |||
isSolid(builder.isSolid), lightLevel(builder.lightLevel), | |||
droppedItem(builder.droppedItem) {} | |||
const ID id; | |||
const std::string name; | |||
const ImageResource ℑ | |||
const bool isSolid; | |||
const float lightLevel; | |||
const std::optional<std::string> droppedItem; | |||
static std::unique_ptr<Tile> createInvalid(const ResourceManager &ctx); | |||
static std::unique_ptr<Tile> createAir(const ResourceManager &ctx); | |||
static ID INVALID_ID; | |||
Tile(ID id, std::string name, const Builder &builder): | |||
id(id), name(name), | |||
isSolid(builder.isSolid), lightLevel(builder.lightLevel), | |||
droppedItem(builder.droppedItem) {} | |||
}; | |||
} |
@@ -1,109 +0,0 @@ | |||
#pragma once | |||
#include "log.h" | |||
#include "common.h" | |||
#include <SDL.h> | |||
#include <optional> | |||
namespace Swan { | |||
class Win { | |||
public: | |||
Win(SDL_Window *window, SDL_Renderer *renderer, float scale): | |||
window_(window), renderer_(renderer), scale_(scale) { | |||
if (SDL_GetRendererInfo(renderer_, &rinfo_) < 0) { | |||
panic << "GetRenedrerInfo failed: " << SDL_GetError(); | |||
abort(); | |||
} | |||
// For HiDPI, we must set the renderer's logical size. | |||
int w, h; | |||
SDL_GetWindowSize(window_, &w, &h); | |||
onResize(w, h); | |||
info << "Using renderer: " << rinfo_.name; | |||
} | |||
Vec2 getPixSize() { | |||
int w, h; | |||
SDL_GetWindowSize(window_, &w, &h); | |||
return Vec2((float)w / scale_, (float)h / scale_); | |||
} | |||
Vec2 getSize() { | |||
int w, h; | |||
SDL_GetWindowSize(window_, &w, &h); | |||
return Vec2(((float)w / (scale_ * zoom_)) / TILE_SIZE, ((float)h / (scale_ * zoom_)) / TILE_SIZE); | |||
} | |||
void onResize(int w, int h) { | |||
SDL_RenderSetLogicalSize(renderer_, (int)((float)w / scale_), (int)((float)h / scale_)); | |||
} | |||
SDL_Rect createDestRect(Vec2 pos, Vec2 pixsize) { | |||
return SDL_Rect{ | |||
(int)floor((pos.x - cam_.x) * TILE_SIZE * zoom_), | |||
(int)floor((pos.y - cam_.y) * TILE_SIZE * zoom_), | |||
(int)ceil(pixsize.x * zoom_), (int)ceil(pixsize.y * zoom_), | |||
}; | |||
} | |||
struct ShowTextureArgs { | |||
SDL_RendererFlip flip = SDL_FLIP_NONE; | |||
double hscale = 1; | |||
double vscale = 1; | |||
double angle = 0; | |||
std::optional<SDL_Point> center = std::nullopt; | |||
}; | |||
void showTexture( | |||
const Vec2 &pos, SDL_Texture *tex, SDL_Rect *srcrect, | |||
ShowTextureArgs args) { | |||
SDL_Point *center = args.center ? &*args.center : nullptr; | |||
SDL_Rect destrect = createDestRect(pos, Vec2(srcrect->w * args.hscale, srcrect->h * args.hscale)); | |||
if (SDL_RenderCopyEx(renderer_, tex, srcrect, &destrect, args.angle, center, args.flip) < 0) | |||
warn << "RenderCopyEx failed: " << SDL_GetError(); | |||
} | |||
void showTexture(const Vec2 &pos, SDL_Texture *tex, SDL_Rect *srcrect, | |||
SDL_Rect *dest, ShowTextureArgs args) { | |||
SDL_Point *center = args.center ? &*args.center : nullptr; | |||
SDL_Rect destrect = createDestRect(pos, Vec2(dest->w * args.hscale, dest->h * args.hscale)); | |||
if (SDL_RenderCopyEx(renderer_, tex, srcrect, &destrect, args.angle, center, args.flip) < 0) | |||
warn << "RenderCopyEx failed: " << SDL_GetError(); | |||
} | |||
// We want an overload which uses RenderCopy instead of RenderCopyEx, | |||
// because RenderCopy might be faster | |||
void showTexture(const Vec2 &pos, SDL_Texture *tex, SDL_Rect *srcrect) { | |||
SDL_Rect destrect = createDestRect(pos, Vec2(srcrect->w, srcrect->h)); | |||
if (SDL_RenderCopy(renderer_, tex, srcrect, &destrect) < 0) | |||
warn << "RenderCopy failed: " << SDL_GetError(); | |||
} | |||
// Another overload without RenderCopyEx | |||
void showTexture(const Vec2 &pos, SDL_Texture *tex, SDL_Rect *srcrect, | |||
SDL_Rect *dest) { | |||
SDL_Rect destrect = createDestRect(pos, Vec2(dest->w, dest->h)); | |||
if (SDL_RenderCopy(renderer_, tex, srcrect, &destrect) < 0) | |||
warn << "RenderCopy failed: " << SDL_GetError(); | |||
} | |||
void drawRect(const Vec2 &pos, const Vec2 &size) { | |||
SDL_Rect destrect = createDestRect(pos, size * TILE_SIZE); | |||
if (SDL_RenderDrawRect(renderer_, &destrect) < 0) | |||
warn << "RenderDrawRect failed: " << SDL_GetError(); | |||
} | |||
Vec2 cam_; | |||
float zoom_ = 1; | |||
SDL_Window *window_; | |||
SDL_Renderer *renderer_; | |||
float scale_; | |||
SDL_RendererInfo rinfo_; | |||
}; | |||
} |
@@ -5,6 +5,9 @@ | |||
#include <string> | |||
#include <random> | |||
#include <SDL.h> | |||
#include <cygnet/Renderer.h> | |||
#include <cygnet/ResourceManager.h> | |||
#include <cygnet/util.h> | |||
#include "common.h" | |||
#include "Item.h" | |||
@@ -13,7 +16,6 @@ | |||
#include "WorldGen.h" | |||
#include "Entity.h" | |||
#include "Collection.h" | |||
#include "Resource.h" | |||
#include "Mod.h" | |||
#include "EventEmitter.h" | |||
@@ -23,9 +25,13 @@ class Game; | |||
class World { | |||
public: | |||
World(Game *game, unsigned long randSeed); | |||
static constexpr Tile::ID INVALID_TILE_ID = 0; | |||
static constexpr char INVALID_TILE_NAME[] = "@::invalid"; | |||
static constexpr Tile::ID AIR_TILE_ID = 1; | |||
static constexpr char AIR_TILE_NAME[] = "@::air"; | |||
World(Game *game, unsigned long randSeed, std::vector<std::string> modPaths); | |||
void addMod(ModWrapper &&mod); | |||
void setWorldGen(std::string gen); | |||
void spawnPlayer(); | |||
@@ -33,37 +39,35 @@ public: | |||
WorldPlane &addPlane(const std::string &gen); | |||
WorldPlane &addPlane() { return addPlane(defaultWorldGen_); } | |||
Tile &getTileByID(Tile::ID id) { return *tiles_[id]; } | |||
Tile &getTileByID(Tile::ID id) { return tiles_[id]; } | |||
Tile::ID getTileID(const std::string &name); | |||
Tile &getTile(const std::string &name); | |||
Item &getItem(const std::string &name); | |||
Cygnet::RenderSprite &getSprite(const std::string &name); | |||
SDL_Color backgroundColor(); | |||
void draw(Win &win); | |||
Cygnet::Color backgroundColor(); | |||
void draw(Cygnet::Renderer &rnd); | |||
void update(float dt); | |||
void tick(float dt); | |||
// Event emitters | |||
EventEmitter<const Context &, TilePos, Tile &> | |||
evtTileBreak_; | |||
// World owns all mods | |||
std::vector<ModWrapper> mods_; | |||
// These things can be used by the mods as they get initialized in the ctor. | |||
EventEmitter<const Context &, TilePos, Tile &> evtTileBreak_; | |||
// World owns tiles and items, the mod just has Builder objects | |||
std::vector<std::unique_ptr<Tile>> tiles_; | |||
// These things get filled in when the ctor loads mods. | |||
std::vector<Tile> tiles_; | |||
std::unordered_map<std::string, Tile::ID> tilesMap_; | |||
std::unordered_map<std::string, std::unique_ptr<Item>> items_; | |||
std::unordered_map<std::string, Item> items_; | |||
std::unordered_map<std::string, WorldGen::Factory> worldGenFactories_; | |||
std::unordered_map<std::string, EntityCollection::Factory> entCollFactories_; | |||
// Mods give us factories to create new world gens and new entity collections | |||
std::unordered_map<std::string, WorldGen::Factory> worldgenFactories_; | |||
std::vector<EntityCollection::Factory> entCollFactories_; | |||
// These things get initialized in the ctor. | |||
// the above members must be initialized before these. | |||
Game *game_; // TODO: reference, not pointer | |||
std::mt19937 random_; | |||
std::vector<ModWrapper> mods_; | |||
Cygnet::ResourceManager resources_; | |||
BodyTrait::Body *player_; | |||
Game *game_; | |||
std::mt19937 random_; | |||
ResourceManager resources_; | |||
private: | |||
class ChunkRenderer { | |||
@@ -71,6 +75,9 @@ private: | |||
void tick(WorldPlane &plane, ChunkPos abspos); | |||
}; | |||
std::vector<ModWrapper> loadMods(std::vector<std::string> paths); | |||
Cygnet::ResourceManager buildResources(); | |||
ChunkRenderer chunkRenderer_; | |||
WorldPlane::ID currentPlane_; | |||
std::vector<std::unique_ptr<WorldPlane>> planes_; |
@@ -2,6 +2,7 @@ | |||
#include <memory> | |||
#include <SDL.h> | |||
#include <cygnet/util.h> | |||
#include "common.h" | |||
#include "Chunk.h" | |||
@@ -13,7 +14,6 @@ namespace Swan { | |||
class World; | |||
class WorldPlane; | |||
class ImageResource; | |||
class WorldGen { | |||
public: | |||
@@ -24,8 +24,8 @@ public: | |||
virtual ~WorldGen() = default; | |||
virtual void drawBackground(const Context &ctx, Win &win, Vec2 pos) = 0; | |||
virtual SDL_Color backgroundColor(Vec2 pos) = 0; | |||
virtual void drawBackground(const Context &ctx, Cygnet::Renderer &rnd, Vec2 pos) = 0; | |||
virtual Cygnet::Color backgroundColor(Vec2 pos) = 0; | |||
virtual void genChunk(WorldPlane &plane, Chunk &chunk) = 0; | |||
virtual EntityRef spawnPlayer(const Context &ctx) = 0; |
@@ -69,8 +69,8 @@ public: | |||
EntityRef spawnPlayer(); | |||
void breakTile(TilePos pos); | |||
SDL_Color backgroundColor(); | |||
void draw(Win &win); | |||
Cygnet::Color backgroundColor(); | |||
void draw(Cygnet::Renderer &rnd); | |||
void update(float dt); | |||
void tick(float dt); | |||
@@ -88,8 +88,6 @@ public: | |||
std::mutex mut_; | |||
private: | |||
std::unique_ptr<LightServer> lighting_; | |||
std::map<std::pair<int, int>, Chunk> chunks_; | |||
std::vector<Chunk *> activeChunks_; | |||
std::vector<std::pair<ChunkPos, Chunk *>> tickChunks_; | |||
@@ -99,6 +97,12 @@ private: | |||
std::deque<Chunk *> chunkInitList_; | |||
std::vector<TilePos> debugBoxes_; | |||
// The lighting server must destruct first. Until it has been destructed, | |||
// it might call onLightChunkUpdated. If that happens after some other | |||
// members have destructed, we have a problem. | |||
// TODO: Rewrite this to not use a callback-based interface. | |||
std::unique_ptr<LightServer> lighting_; | |||
}; | |||
/* |
@@ -0,0 +1,20 @@ | |||
#include <memory> | |||
#include <unordered_map> | |||
#include <string> | |||
#include "util.h" | |||
namespace Swan { | |||
struct ImageAsset { | |||
int width; | |||
int frameHeight; | |||
int frameCount; | |||
std::unique_ptr<unsigned char[]> data; | |||
}; | |||
Result<ImageAsset> loadImageAsset( | |||
const std::unordered_map<std::string, std::string> modPaths, | |||
std::string path); | |||
} |
@@ -6,6 +6,13 @@ | |||
#include <swan-common/Vector2.h> | |||
#include <swan-common/constants.h> | |||
// Forward declare the Cygnet::Renderer, because lots of functions will need | |||
// to take a reference to it. It's nicer to not have to include Cygnet::Renderer | |||
// in every header. | |||
namespace Cygnet { | |||
class Renderer; | |||
} | |||
namespace Swan { | |||
using namespace SwanCommon; | |||
@@ -16,14 +23,11 @@ using ChunkPos = Vec2i; | |||
class Game; | |||
class World; | |||
class WorldPlane; | |||
class Win; | |||
class ResourceManager; | |||
struct Context { | |||
Game &game; | |||
World &world; | |||
WorldPlane &plane; | |||
ResourceManager &resources; | |||
}; | |||
} |
@@ -4,19 +4,20 @@ | |||
#include <optional> | |||
#include <initializer_list> | |||
#include <utility> | |||
#include "Win.h" | |||
#include <cygnet/util.h> | |||
namespace Swan { | |||
namespace Draw { | |||
SDL_Color linearGradient( | |||
float val, std::initializer_list<std::pair<float, SDL_Color>> colors); | |||
Cygnet::Color linearGradient( | |||
float val, std::initializer_list<std::pair<float, Cygnet::Color>> colors); | |||
/* | |||
void parallaxBackground( | |||
Win &win, SDL_Texture *tex, | |||
std::optional<SDL_Rect> srcrect, std::optional<SDL_Rect> destrect, | |||
float x, float y, float factor); | |||
TODO */ | |||
} | |||
} |
@@ -12,10 +12,8 @@ | |||
#include <swan/ItemStack.h> | |||
#include <swan/Mod.h> | |||
#include <swan/OS.h> | |||
#include <swan/Resource.h> | |||
#include <swan/SlotVector.h> | |||
#include <swan/Tile.h> | |||
#include <swan/Win.h> | |||
#include <swan/World.h> | |||
#include <swan/WorldGen.h> | |||
#include <swan/WorldPlane.h> |
@@ -4,8 +4,6 @@ | |||
namespace Swan { | |||
class Win; | |||
struct BodyTrait { | |||
struct Body; | |||
struct Tag {}; | |||
@@ -32,7 +30,7 @@ struct BodyTrait { | |||
Vec2 midRight() { return { right(), midY() }; } | |||
Vec2 bottomRight() { return { right(), bottom() }; } | |||
void outline(Win &win); | |||
//void outline(Win &win); TODO | |||
}; | |||
}; | |||
@@ -1,11 +1,11 @@ | |||
#pragma once | |||
#pragma once | |||
#include <optional> | |||
#include <functional> | |||
#include <memory> | |||
#include <chrono> | |||
#include <type_traits> | |||
#include <string> | |||
#include <stddef.h> | |||
namespace Swan { | |||
@@ -42,6 +42,94 @@ public: | |||
~Deferred() { Func(); } | |||
}; | |||
inline struct ResultOk {} Ok; | |||
inline struct ResultErr {} Err; | |||
// Result type for potential errors | |||
template<typename T, typename Err = std::string> | |||
class Result { | |||
public: | |||
Result(ResultOk, T &&val): isOk_(true), v_(ResultOk{}, std::move(val)) {} | |||
Result(ResultErr, Err &&err): isOk_(false), v_(ResultErr{}, std::move(err)) {} | |||
Result(const Result &other): isOk_(other.isOk_) { | |||
if (isOk_) { | |||
new (&v_.val) T(other.v_.val); | |||
} else { | |||
new (&v_.err) T(other.v_.err); | |||
} | |||
} | |||
Result(Result &&other): isOk_(other.isOk_) { | |||
if (other.isOk_) { | |||
new (&v_.val) T(std::move(other.v_.val)); | |||
} else { | |||
new (&v_.err) Err(std::move(other.v_.err)); | |||
} | |||
} | |||
~Result() { | |||
destroy(); | |||
} | |||
Result<T, Err> &operator=(const Result<T, Err> &other) { | |||
destroy(); | |||
isOk_ = other.isOk_; | |||
if (isOk_) { | |||
new (&v_.val) T(other.v_.val); | |||
} else { | |||
new (&v_.err) Err(other.v_.err); | |||
} | |||
return *this; | |||
} | |||
Result<T, Err> &operator=(Result<T, Err> &&other) { | |||
destroy(); | |||
isOk_ = other.isOk_; | |||
if (other.isOk_) { | |||
new (&v_.val) T(std::move(other.v_.val)); | |||
} else { | |||
new (&v_.err) Err(std::move(other.v_.err)); | |||
} | |||
return *this; | |||
} | |||
explicit operator bool() { return isOk_; } | |||
bool isOk() { return isOk_; } | |||
Err &err() { return v_.err; } | |||
T &value() { return v_.val; } | |||
T *operator->() { | |||
return &v_.val; | |||
} | |||
T &operator*() { | |||
return v_.val; | |||
} | |||
private: | |||
void destroy() { | |||
if (isOk_) { | |||
v_.val.~T(); | |||
} else { | |||
v_.err.~Err(); | |||
} | |||
} | |||
bool isOk_; | |||
union U { | |||
U() {} | |||
U(ResultOk, T &&val): val(std::move(val)) {} | |||
U(ResultOk, const T &val): val(val) {} | |||
U(ResultErr, Err &&err): err(std::move(err)) {} | |||
U(ResultErr, const Err &err): err(err) {} | |||
~U() {} | |||
T val; | |||
Err err; | |||
} v_; | |||
}; | |||
// Calling begin/end is stupid... | |||
template<typename T> | |||
auto callBegin(T &v) { |
@@ -1,7 +1,6 @@ | |||
#include "Animation.h" | |||
#include "Win.h" | |||
#include "gfxutil.h" | |||
#include <cygnet/Renderer.h> | |||
namespace Swan { | |||
@@ -11,16 +10,12 @@ void Animation::tick(float dt) { | |||
timer_ += interval_; | |||
frame_ += 1; | |||
if (frame_ >= resource_.numFrames_) | |||
if (frame_ >= sprite_.frameCount) { | |||
frame_ = 0; | |||
} | |||
} | |||
} | |||
void Animation::draw(const Vec2 &pos, Win &win) { | |||
SDL_Rect rect = resource_.frameRect(frame_); | |||
win.showTexture(pos, resource_.texture_.get(), &rect, { .flip = flip_ }); | |||
} | |||
void Animation::reset() { | |||
timer_ = interval_; | |||
frame_ = 0; |
@@ -10,11 +10,10 @@ | |||
#include "gfxutil.h" | |||
#include "World.h" | |||
#include "Game.h" | |||
#include "Win.h" | |||
namespace Swan { | |||
void Chunk::compress() { | |||
void Chunk::compress(Cygnet::Renderer &rnd) { | |||
if (isCompressed()) | |||
return; | |||
@@ -32,7 +31,6 @@ void Chunk::compress() { | |||
data_.reset(new uint8_t[destlen]); | |||
memcpy(data_.get(), dest, destlen); | |||
texture_.reset(); | |||
compressedSize_ = destlen; | |||
info | |||
@@ -46,6 +44,9 @@ void Chunk::compress() { | |||
} else { | |||
warn << "Chunk compression error: " << ret << " (Out of memory?)"; | |||
} | |||
rnd.destroyChunk(renderChunk_); | |||
rnd.destroyChunkShadow(renderChunkShadow_); | |||
} | |||
void Chunk::decompress() { | |||
@@ -64,130 +65,39 @@ void Chunk::decompress() { | |||
} | |||
data_ = std::move(dest); | |||
needRender_ = true; | |||
info | |||
<< "Decompressed chunk " << pos_ << " from " | |||
<< compressedSize_ << " bytes to " | |||
<< DATA_SIZE << " bytes."; | |||
compressedSize_ = -1; | |||
} | |||
void Chunk::renderLight(const Context &ctx, SDL_Renderer *rnd) { | |||
std::optional<RenderTarget> target; | |||
// The texture might not be created yet | |||
if (!lightTexture_) { | |||
lightTexture_.reset(SDL_CreateTexture( | |||
ctx.game.win_.renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, | |||
CHUNK_WIDTH, CHUNK_HEIGHT)); | |||
SDL_SetTextureBlendMode(lightTexture_.get(), SDL_BLENDMODE_BLEND); | |||
target.emplace(rnd, texture_.get()); | |||
} else { | |||
target.emplace(rnd, texture_.get()); | |||
} | |||
// Fill light texture | |||
target.emplace(rnd, lightTexture_.get()); | |||
RenderBlendMode mode(rnd, SDL_BLENDMODE_NONE); | |||
RenderDrawColor color(rnd, 0, 0, 0, 0); | |||
for (int y = 0; y < CHUNK_HEIGHT; ++y) { | |||
for (int x = 0; x < CHUNK_WIDTH; ++x) { | |||
int b = getLightLevel({ x, y }); | |||
color.change(0, 0, 0, 255 - b); | |||
SDL_Rect rect{ x, y, 1, 1 }; | |||
SDL_RenderFillRect(rnd, &rect); | |||
} | |||
} | |||
needLightRender_ = false; | |||
needChunkRender_ = true; | |||
} | |||
void Chunk::render(const Context &ctx, SDL_Renderer *rnd) { | |||
std::optional<RenderTarget> target; | |||
// The texture might not be created yet | |||
if (!texture_) { | |||
texture_.reset(SDL_CreateTexture( | |||
ctx.game.win_.renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, | |||
CHUNK_WIDTH * TILE_SIZE, CHUNK_HEIGHT * TILE_SIZE)); | |||
SDL_SetTextureBlendMode(texture_.get(), SDL_BLENDMODE_BLEND); | |||
target.emplace(rnd, texture_.get()); | |||
RenderBlendMode mode(rnd, SDL_BLENDMODE_NONE); | |||
RenderDrawColor color(rnd, 0, 0, 0, 0); | |||
SDL_Rect rect{ 0, 0, CHUNK_WIDTH * TILE_SIZE, CHUNK_HEIGHT * TILE_SIZE }; | |||
SDL_RenderFillRect(rnd, &rect); | |||
} else { | |||
target.emplace(rnd, texture_.get()); | |||
} | |||
// We're caching tiles so we don't have to world.getTileByID() every time | |||
Tile::ID prevID = Tile::INVALID_ID; | |||
Tile *tile = ctx.game.invalidTile_.get(); | |||
// Fill tile texture | |||
for (int y = 0; y < CHUNK_HEIGHT; ++y) { | |||
for (int x = 0; x < CHUNK_WIDTH; ++x) { | |||
Tile::ID id = getTileID(RelPos(x, y)); | |||
if (id != prevID) { | |||
prevID = id; | |||
tile = &ctx.world.getTileByID(id); | |||
} | |||
SDL_Rect dest{x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE}; | |||
SDL_RenderCopy(rnd, tile->image.texture_.get(), nullptr, &dest); | |||
} | |||
} | |||
needRender_ = false; | |||
renderLight(ctx, rnd); | |||
} | |||
void Chunk::renderList(SDL_Renderer *rnd) { | |||
// Here, we know that the texture is created. | |||
// We still wanna render directly to the target texture | |||
RenderTarget target(rnd, texture_.get()); | |||
// We must make sure the blend mode is NONE, because we want transparent | |||
// pixels to actually overwrite non-transparent pixels | |||
RenderBlendMode mode(rnd, SDL_BLENDMODE_NONE); | |||
// When we FillRect, we must fill transparency. | |||
RenderDrawColor color(rnd, 0, 0, 0, 0); | |||
for (auto &[pos, tex]: drawList_) { | |||
SDL_Rect dest{pos.x * TILE_SIZE, pos.y * TILE_SIZE, TILE_SIZE, TILE_SIZE}; | |||
SDL_RenderFillRect(rnd, &dest); | |||
SDL_RenderCopy(rnd, tex, nullptr, &dest); | |||
} | |||
} | |||
void Chunk::draw(const Context &ctx, Win &win) { | |||
void Chunk::draw(const Context &ctx, Cygnet::Renderer &rnd) { | |||
if (isCompressed()) | |||
return; | |||
// The world plane is responsible for managing initial renders | |||
if (needRender_) | |||
return; | |||
// We're responsible for the light level rendering though | |||
if (needLightRender_) | |||
renderLight(ctx, win.renderer_); | |||
if (drawList_.size() > 0) { | |||
renderList(win.renderer_); | |||
drawList_.clear(); | |||
if (needChunkRender_) { | |||
renderChunk_ = rnd.createChunk(getTileData()); | |||
renderChunkShadow_ = rnd.createChunkShadow(getLightData()); | |||
needChunkRender_ = false; | |||
needLightRender_ = false; | |||
} else { | |||
for (auto &change: changeList_) { | |||
rnd.modifyChunk(renderChunk_, change.first, change.second); | |||
} | |||
} | |||
auto chunkpos = pos_ * Vec2i(CHUNK_WIDTH, CHUNK_HEIGHT); | |||
SDL_Rect rect{ 0, 0, CHUNK_WIDTH * TILE_SIZE, CHUNK_HEIGHT * TILE_SIZE }; | |||
win.showTexture(chunkpos, texture_.get(), &rect); | |||
if (needLightRender_) { | |||
rnd.modifyChunkShadow(renderChunkShadow_, getLightData()); | |||
needLightRender_ = false; | |||
} | |||
SDL_Rect texrect{ 0, 0, CHUNK_WIDTH, CHUNK_HEIGHT }; | |||
win.showTexture(chunkpos, lightTexture_.get(), &texrect, &rect); | |||
Vec2 pos = (Vec2)pos_ * Vec2{CHUNK_WIDTH, CHUNK_HEIGHT}; | |||
rnd.drawChunk(renderChunk_, pos); | |||
rnd.drawChunkShadow(renderChunkShadow_, pos); | |||
} | |||
Chunk::TickAction Chunk::tick(float dt) { |
@@ -7,31 +7,11 @@ | |||
#include "log.h" | |||
#include "Tile.h" | |||
#include "OS.h" | |||
#include "Win.h" | |||
namespace Swan { | |||
std::optional<ModWrapper> Game::loadMod(std::string path, World &world) { | |||
OS::Dynlib dl(path + "/mod"); | |||
auto create = dl.get<Mod *(*)(World &)>("mod_create"); | |||
if (create == NULL) { | |||
warn << path << ": No 'mod_create' function!"; | |||
return std::nullopt; | |||
} | |||
std::unique_ptr<Mod> mod(create(world)); | |||
return std::make_optional<ModWrapper>( | |||
std::move(mod), std::move(path), std::move(dl)); | |||
} | |||
void Game::createWorld(const std::string &worldgen, const std::vector<std::string> &modpaths) { | |||
world_.reset(new World(this, time(NULL))); | |||
for (auto &modpath: modpaths) { | |||
auto mod = loadMod(modpath, *world_); | |||
if (mod) | |||
world_->addMod(std::move(*mod)); | |||
} | |||
void Game::createWorld(const std::string &worldgen, const std::vector<std::string> &modPaths) { | |||
world_.reset(new World(this, time(NULL), modPaths)); | |||
world_->setWorldGen(worldgen); | |||
world_->setCurrentPlane(world_->addPlane()); | |||
@@ -39,29 +19,28 @@ void Game::createWorld(const std::string &worldgen, const std::vector<std::strin | |||
} | |||
TilePos Game::getMouseTile() { | |||
auto mousePos = getMousePos(); | |||
return TilePos( | |||
(int)floor(win_.cam_.x + mousePos.x / (Swan::TILE_SIZE * win_.zoom_)), | |||
(int)floor(win_.cam_.y + mousePos.y / (Swan::TILE_SIZE * win_.zoom_))); | |||
auto pos = (getMousePos() * 2 - renderer_.winScale()) / cam_.zoom + cam_.pos; | |||
return TilePos{(int)floor(pos.x), (int)floor(pos.y)}; | |||
} | |||
SDL_Color Game::backgroundColor() { | |||
Cygnet::Color Game::backgroundColor() { | |||
return world_->backgroundColor(); | |||
} | |||
void Game::draw() { | |||
world_->draw(win_); | |||
world_->draw(renderer_); | |||
renderer_.draw(cam_); | |||
} | |||
void Game::update(float dt) { | |||
world_->update(dt); | |||
// Zoom the window using the scroll wheel | |||
win_.zoom_ += (float)wasWheelScrolled() * 0.1f * win_.zoom_; | |||
if (win_.zoom_ > 3) | |||
win_.zoom_ = 3; | |||
else if (win_.zoom_ < 0.3) | |||
win_.zoom_ = 0.3; | |||
cam_.zoom += (float)wasWheelScrolled() * 0.1f * cam_.zoom; | |||
if (cam_.zoom > 1) | |||
cam_.zoom = 1; | |||
else if (cam_.zoom < 0.025) | |||
cam_.zoom = 0.025; | |||
world_->update(dt); | |||
didScroll_ = 0; | |||
didPressKeys_.reset(); |
@@ -1,16 +0,0 @@ | |||
#include "Item.h" | |||
#include "Resource.h" | |||
#include "Game.h" | |||
#include "common.h" | |||
namespace Swan { | |||
std::unique_ptr<Item> Item::createInvalid(Context &ctx) { | |||
return std::make_unique<Item>(ctx.resources, Builder{ | |||
.name = "@::invalid", | |||
.image = "@::invalid", | |||
}); | |||
} | |||
} |
@@ -161,13 +161,11 @@ void LightServer::processEvent(const Event &evt, std::vector<NewLightChunk> &new | |||
break; | |||
case Event::Tag::LIGHT_ADDED: | |||
info << cpos << ": Add " << evt.f << " light to " << rpos; | |||
ch->lightSources[rpos] += evt.f; | |||
markChunksModified(cpos, rpos, ch->lightSources[rpos]); | |||
break; | |||
case Event::Tag::LIGHT_REMOVED: | |||
info << cpos << ": Remove " << evt.f << " light to " << rpos; | |||
markChunksModified(cpos, rpos, ch->lightSources[rpos]); | |||
ch->lightSources[rpos] -= evt.f; | |||
if (ch->lightSources[rpos] < LIGHT_CUTOFF) { | |||
@@ -534,8 +532,6 @@ void LightServer::run() { | |||
buf.clear(); | |||
newChunks.clear(); | |||
auto start = std::chrono::steady_clock::now(); | |||
for (auto &pos: updatedChunks_) { | |||
auto ch = chunks_.find(pos); | |||
if (ch != chunks_.end()) { | |||
@@ -574,11 +570,6 @@ void LightServer::run() { | |||
} | |||
} | |||
auto end = std::chrono::steady_clock::now(); | |||
auto dur = std::chrono::duration<double, std::milli>(end - start); | |||
info << "Generating light for " << updatedChunks_.size() | |||
<< " chunks took " << dur.count() << "ms"; | |||
for (auto &pos: updatedChunks_) { | |||
auto ch = chunks_.find(pos); | |||
if (ch != chunks_.end()) { |
@@ -1,63 +0,0 @@ | |||
#include "Mod.h" | |||
#include <stdio.h> | |||
#include <algorithm> | |||
#include "util.h" | |||
#include "log.h" | |||
namespace Swan { | |||
void Mod::registerImage(const std::string &id) { | |||
images_.push_back(name_ + "/" + id); | |||
info << " Adding image: " << images_.back(); | |||
} | |||
void Mod::registerTile(Tile::Builder tile) { | |||
tile.name = name_ + "::" + tile.name; | |||
tiles_.push_back(tile); | |||
info << " Adding tile: " << tile.name; | |||
} | |||
void Mod::registerItem(Item::Builder item) { | |||
item.name = name_ + "::" + item.name; | |||
items_.push_back(item); | |||
info << " Adding item: " << item.name; | |||
} | |||
Iter<std::unique_ptr<ImageResource>> ModWrapper::buildImages(SDL_Renderer *renderer) { | |||
return map(begin(mod_->images_), end(mod_->images_), | |||
[renderer, this](const std::string &id) { | |||
return std::make_unique<ImageResource>(renderer, path_, id); | |||
}); | |||
} | |||
Iter<std::unique_ptr<Tile>> ModWrapper::buildTiles(const ResourceManager &resources) { | |||
return map(begin(mod_->tiles_), end(mod_->tiles_), | |||
[&](const Tile::Builder &builder) { | |||
return std::make_unique<Tile>(resources, builder); | |||
}); | |||
} | |||
Iter<std::unique_ptr<Item>> ModWrapper::buildItems(const ResourceManager &resources) { | |||
return map(begin(mod_->items_), end(mod_->items_), | |||
[&](const Item::Builder &builder) { | |||
return std::make_unique<Item>(resources, builder); | |||
}); | |||
} | |||
Iter<WorldGen::Factory> ModWrapper::getWorldGens() { | |||
return map(begin(mod_->worldgens_), end(mod_->worldgens_), | |||
[](WorldGen::Factory &fact) { | |||
return fact; | |||
}); | |||
} | |||
Iter<EntityCollection::Factory> ModWrapper::getEntities() { | |||
return map(begin(mod_->entities_), end(mod_->entities_), | |||
[](EntityCollection::Factory &fact){ | |||
return fact; | |||
}); | |||
} | |||
} |
@@ -1,132 +0,0 @@ | |||
#include "Resource.h" | |||
#include <stdio.h> | |||
#include <SDL_image.h> | |||
#include <regex> | |||
#include <cpptoml.h> | |||
#include <string.h> | |||
#include <errno.h> | |||
#include "log.h" | |||
#include "common.h" | |||
#include "Win.h" | |||
namespace Swan { | |||
ImageResource::ImageResource( | |||
SDL_Renderer *renderer, const std::string &modpath, const std::string &id) { | |||
static std::regex first_part_re("^.*?/"); | |||
SDL_RendererInfo rinfo; | |||
if (SDL_GetRendererInfo(renderer, &rinfo) < 0) { | |||
panic << "GetRendererInfo failed: " << SDL_GetError(); | |||
abort(); | |||
} | |||
uint32_t format = rinfo.texture_formats[0]; | |||
int bpp = 32; | |||
uint32_t rmask, gmask, bmask, amask; | |||
if (SDL_PixelFormatEnumToMasks(format, &bpp, &rmask, &gmask, &bmask, &amask) < 0) { | |||
panic << "PixelFormatEnumToMasks failed: " << SDL_GetError(); | |||
abort(); | |||
} | |||
std::string assetpath = modpath + "/assets/" + | |||
std::regex_replace(id, first_part_re, ""); | |||
surface_.reset(IMG_Load((assetpath + ".png").c_str())); | |||
// If we don't have a surface yet (either loading or conversion failed), | |||
// create a placeholder | |||
if (!surface_) { | |||
warn << "Loading image " << id << " failed: " << SDL_GetError(); | |||
surface_.reset(SDL_CreateRGBSurface( | |||
0, TILE_SIZE, TILE_SIZE, bpp, rmask, gmask, bmask, amask)); | |||
SDL_FillRect(surface_.get(), NULL, SDL_MapRGB(surface_->format, | |||
PLACEHOLDER_RED, PLACEHOLDER_GREEN, PLACEHOLDER_BLUE)); | |||
} | |||
frameHeight_ = 32; | |||
// Load TOML if it exists | |||
errno = ENOENT; // I don't know if ifstream is guaranteed to set errno | |||
std::ifstream tomlfile(assetpath + ".toml"); | |||
if (tomlfile) { | |||
cpptoml::parser parser(tomlfile); | |||
try { | |||
auto toml = parser.parse(); | |||
frameHeight_ = toml->get_as<int>("height").value_or(frameHeight_); | |||
} catch (cpptoml::parse_exception &exc) { | |||
warn << "Failed to parse toml file " << assetpath << ".toml: " | |||
<< exc.what(); | |||
} | |||
} else if (errno != ENOENT) { | |||
warn << "Couldn't open " << assetpath << ".toml: " << strerror(errno); | |||
} | |||
texture_.reset(SDL_CreateTextureFromSurface(renderer, surface_.get())); | |||
if (!texture_) { | |||
panic << "CreateTexture failed: " << SDL_GetError(); | |||
abort(); | |||
} | |||
numFrames_ = surface_->h / frameHeight_; | |||
name_ = id; | |||
} | |||
ImageResource::ImageResource( | |||
SDL_Renderer *renderer, const std::string &name, | |||
int w, int h, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { | |||
surface_.reset(SDL_CreateRGBSurface( | |||
0, TILE_SIZE, TILE_SIZE, 32, | |||
0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff)); | |||
SDL_FillRect(surface_.get(), NULL, SDL_MapRGBA(surface_->format, r, g, b, a)); | |||
texture_.reset(SDL_CreateTextureFromSurface(renderer, surface_.get())); | |||
if (!texture_) { | |||
panic << "CreateTexture failed: " << SDL_GetError(); | |||
abort(); | |||
} | |||
frameHeight_ = h; | |||
numFrames_ = 1; | |||
name_ = name; | |||
} | |||
void ImageResource::tick(float dt) { | |||
switchTimer_ -= dt; | |||
if (switchTimer_ <= 0) { | |||
switchTimer_ += switchInterval_; | |||
frame_ += 1; | |||
if (frame_ >= numFrames_) | |||
frame_ = 0; | |||
} | |||
} | |||
ResourceManager::ResourceManager(Win &win) { | |||
addImage(std::make_unique<ImageResource>( | |||
win.renderer_, "@::invalid", TILE_SIZE, TILE_SIZE, | |||
PLACEHOLDER_RED, PLACEHOLDER_GREEN, PLACEHOLDER_BLUE)); | |||
addImage(std::make_unique<ImageResource>( | |||
win.renderer_, "@::air", TILE_SIZE, TILE_SIZE, | |||
0, 0, 0, 0)); | |||
} | |||
void ResourceManager::tick(float dt) { | |||
for (auto &[k, v]: images_) { | |||
v->tick(dt); | |||
} | |||
} | |||
ImageResource &ResourceManager::getImage(const std::string &name) const { | |||
auto it = images_.find(name); | |||
if (it == end(images_)) { | |||
warn << "Couldn't find image " << name << "!"; | |||
return getImage("@::invalid"); | |||
} | |||
return *it->second; | |||
} | |||
} |
@@ -1,25 +0,0 @@ | |||
#include "Tile.h" | |||
#include "common.h" | |||
#include <Game.h> | |||
namespace Swan { | |||
Tile::ID Tile::INVALID_ID = 0; | |||
std::unique_ptr<Tile> Tile::createInvalid(const ResourceManager &resources) { | |||
return std::make_unique<Tile>(resources, Builder{ | |||
.name = "@::invalid", | |||
.image = "@::invalid", | |||
}); | |||
} | |||
std::unique_ptr<Tile> Tile::createAir(const ResourceManager &resources) { | |||
return std::make_unique<Tile>(resources, Builder{ | |||
.name = "@::air", | |||
.image = "@::air", | |||
.isSolid = false, | |||
}); | |||
} | |||
} |
@@ -1,11 +1,12 @@ | |||
#include "World.h" | |||
#include <algorithm> | |||
#include <tuple> | |||
#include "log.h" | |||
#include "Game.h" | |||
#include "Win.h" | |||
#include "Clock.h" | |||
#include "assets.h" | |||
namespace Swan { | |||
@@ -16,21 +17,199 @@ static void chunkLine(int l, WorldPlane &plane, ChunkPos &abspos, const Vec2i &d | |||
} | |||
} | |||
World::World(Game *game, unsigned long randSeed): | |||
game_(game), random_(randSeed), resources_(game->win_) { | |||
std::vector<ModWrapper> World::loadMods(std::vector<std::string> paths) { | |||
std::vector<ModWrapper> mods; | |||
mods.reserve(paths.size()); | |||
std::unique_ptr<Tile> invalidTile = Tile::createInvalid(resources_); | |||
tilesMap_[invalidTile->name] = 0; | |||
for (auto &path: paths) { | |||
OS::Dynlib dl(path + "/mod"); | |||
auto create = dl.get<Mod *(*)(World &)>("mod_create"); | |||
if (create == NULL) { | |||
warn << path << ": No 'mod_create' function!"; | |||
continue; | |||
} | |||
// tiles_ is empty, so pushing back now will ensure invalid_tile | |||
// ends up at location 0 | |||
tiles_.push_back(std::move(invalidTile)); | |||
std::unique_ptr<Mod> mod(create(*this)); | |||
mods.push_back(ModWrapper(std::move(mod), std::move(path), std::move(dl))); | |||
} | |||
return mods; | |||
} | |||
Cygnet::ResourceManager World::buildResources() { | |||
Cygnet::ResourceBuilder builder(game_->renderer_); | |||
auto fillTileImage = [&](unsigned char *data, int r, int g, int b, int a) { | |||
for (size_t i = 0; i < TILE_SIZE * TILE_SIZE; ++i) { | |||
data[i * 4 + 0] = r; | |||
data[i * 4 + 1] = g; | |||
data[i * 4 + 2] = b; | |||
data[i * 4 + 2] = a; | |||
} | |||
}; | |||
struct ImageAsset fallbackImage = { | |||
.width = 32, | |||
.frameHeight = 32, | |||
.frameCount = 1, | |||
.data = std::make_unique<unsigned char[]>(TILE_SIZE * TILE_SIZE * 4), | |||
}; | |||
fillTileImage(fallbackImage.data.get(), | |||
PLACEHOLDER_RED, PLACEHOLDER_GREEN, PLACEHOLDER_BLUE, 255); | |||
auto airImage = std::make_unique<unsigned char[]>(TILE_SIZE * TILE_SIZE * 4); | |||
fillTileImage(airImage.get(), | |||
PLACEHOLDER_RED, PLACEHOLDER_GREEN, PLACEHOLDER_BLUE, 255); | |||
// Let tile ID 0 be the invalid tile | |||
builder.addTile(INVALID_TILE_ID, fallbackImage.data.get()); | |||
tilesMap_[INVALID_TILE_NAME] = INVALID_TILE_ID; | |||
tiles_.push_back(Tile(INVALID_TILE_ID, INVALID_TILE_NAME, { | |||
.name = "", .image = "", // Not used in this case | |||
.isSolid = false, | |||
})); | |||
items_.emplace(INVALID_TILE_NAME, Item(INVALID_TILE_ID, INVALID_TILE_NAME, { | |||
.name = "", .image = "", // Not used in this case | |||
})); | |||
// ...And tile ID 1 be the air tile | |||
builder.addTile(AIR_TILE_ID, std::move(airImage)); | |||
tilesMap_[AIR_TILE_NAME] = AIR_TILE_ID; | |||
tiles_.push_back(Tile(AIR_TILE_ID, AIR_TILE_NAME, { | |||
.name = "", .image = "", // Not used in this case | |||
.isSolid = false, | |||
})); | |||
items_.emplace(AIR_TILE_NAME, Item(AIR_TILE_ID, AIR_TILE_NAME, { | |||
.name = "", .image = "", // Not used in this case | |||
})); | |||
// Assets are namespaced on the mod, so if something references, say, | |||
// "core::stone", we need to know which directory the "core" mod is in | |||
std::unordered_map<std::string, std::string> modPaths; | |||
for (auto &mod: mods_) { | |||
modPaths[mod.name()] = mod.path_; | |||
} | |||
auto loadTileImage = [&](std::string path) -> Result<ImageAsset> { | |||
// Don't force all tiles/items to have an associated image. | |||
// It could be that some tiles/items exist for a purpose which implies | |||
// it should never actually be visible. | |||
if (path == INVALID_TILE_NAME) { | |||
ImageAsset asset{ | |||
.width = 32, | |||
.frameHeight = 32, | |||
.frameCount = 1, | |||
.data = std::make_unique<unsigned char[]>(TILE_SIZE * TILE_SIZE * 4), | |||
}; | |||
memcpy(asset.data.get(), fallbackImage.data.get(), TILE_SIZE * TILE_SIZE * 4); | |||
return {Ok, std::move(asset)}; | |||
} | |||
auto image = loadImageAsset(modPaths, path); | |||
if (!image) { | |||
warn << '\'' << path << "': " << image.err(); | |||
return {Err, '\'' + path + "': " + image.err()}; | |||
} else if (image->width != TILE_SIZE) { | |||
warn << '\'' << path << "': Width must be " << TILE_SIZE << " pixels"; | |||
return {Err, '\'' + path + "': Width must be " + std::to_string(TILE_SIZE) + " pixels"}; | |||
} else { | |||
return image; | |||
} | |||
}; | |||
// Need to fill in every tile before we do items, | |||
// because all items will end up after all tiles in the tile atlas. | |||
// In the rendering system, there's no real difference between a tile | |||
// and an item. | |||
for (auto &mod: mods_) { | |||
for (auto &tileBuilder: mod.tiles()) { | |||
auto image = loadTileImage(tileBuilder.image); | |||
std::string tileName = mod.name() + "::" + tileBuilder.name; | |||
Tile::ID tileId = tiles_.size(); | |||
if (image) { | |||
builder.addTile(tileId, std::move(image->data)); | |||
} else { | |||
warn << image.err(); | |||
builder.addTile(tileId, fallbackImage.data.get()); | |||
} | |||
tilesMap_[tileName] = tileId; | |||
tiles_.push_back(Tile(tileId, tileName, tileBuilder)); | |||
// All tiles should have an item. | |||
// Some items will be overwritten later my mod_->items, | |||
// but if not, this is their default item. | |||
items_.emplace(tileName, Item(tileId, tileName, { | |||
.name = "", .image = "", // Not used in this case | |||
})); | |||
} | |||
} | |||
// Put all items after all the tiles | |||
Tile::ID nextItemId = tiles_.size(); | |||
// Load all items which aren't just tiles in disguise. | |||
for (auto &mod: mods_) { | |||
for (auto &itemBuilder: mod.items()) { | |||
auto image = loadTileImage(itemBuilder.image); | |||
std::string itemName = mod.name() + "::" + itemBuilder.name; | |||
Tile::ID itemId = nextItemId++; | |||
if (image) { | |||
builder.addTile(itemId, std::move(image->data)); | |||
} else { | |||
warn << image.err(); | |||
builder.addTile(itemId, fallbackImage.data.get()); | |||
} | |||
items_.emplace(itemName, Item(itemId, itemName, itemBuilder)); | |||
} | |||
} | |||
// We're also going to need an air tile at location 1 | |||
tiles_.push_back(Tile::createAir(resources_)); | |||
tilesMap_["@::air"] = 1; | |||
// Load sprites | |||
for (auto &mod: mods_) { | |||
for (auto spritePath: mod.sprites()) { | |||
std::string path = mod.name() + "::" + spritePath; | |||
auto image = loadImageAsset(modPaths, path); | |||
if (image) { | |||
builder.addSprite( | |||
path, image->data.get(), image->width, | |||
image->frameHeight * image->frameCount, | |||
image->frameHeight); | |||
} else { | |||
warn << '\'' << path << "': " << image.err(); | |||
builder.addSprite( | |||
path, fallbackImage.data.get(), fallbackImage.width, | |||
fallbackImage.frameHeight * fallbackImage.frameCount, | |||
fallbackImage.frameHeight); | |||
} | |||
} | |||
} | |||
// Load world gens and entities | |||
for (auto &mod: mods_) { | |||
for (auto &worldGenFactory: mod.worldGens()) { | |||
std::string name = mod.name() + "::" + worldGenFactory.name; | |||
worldGenFactories_.emplace(name, worldGenFactory); | |||
} | |||
for (auto &entCollFactory: mod.entities()) { | |||
std::string name = mod.name() + "::" + entCollFactory.name; | |||
entCollFactories_.emplace(name, entCollFactory); | |||
} | |||
} | |||
return Cygnet::ResourceManager(std::move(builder)); | |||
} | |||
World::World(Game *game, unsigned long randSeed, std::vector<std::string> modPaths): | |||
game_(game), random_(randSeed), mods_(loadMods(std::move(modPaths))), | |||
resources_(buildResources()) {} | |||
void World::ChunkRenderer::tick(WorldPlane &plane, ChunkPos abspos) { | |||
ZoneScopedN("World::ChunkRenderer tick"); | |||
int l = 0; | |||
@@ -46,37 +225,6 @@ void World::ChunkRenderer::tick(WorldPlane &plane, ChunkPos abspos) { | |||
} | |||
} | |||
void World::addMod(ModWrapper &&mod) { | |||
info << "World: adding mod " << mod.mod_->name_; | |||
for (auto i: mod.buildImages(game_->win_.renderer_)) { | |||
resources_.addImage(std::move(i)); | |||
} | |||
for (auto t: mod.buildTiles(resources_)) { | |||
Tile::ID id = tiles_.size(); | |||
tilesMap_[t->name] = id; | |||
tiles_.push_back(std::move(t)); | |||
} | |||
for (auto i: mod.buildItems(resources_)) { | |||
items_[i->name] = std::move(i); | |||
} | |||
for (auto fact: mod.getWorldGens()) { | |||
worldgenFactories_.emplace( | |||
std::piecewise_construct, | |||
std::forward_as_tuple(fact.name), | |||
std::forward_as_tuple(fact)); | |||
} | |||
for (auto fact: mod.getEntities()) { | |||
entCollFactories_.push_back(fact); | |||
} | |||
mods_.push_back(std::move(mod)); | |||
} | |||
void World::setWorldGen(std::string gen) { | |||
defaultWorldGen_ = std::move(gen); | |||
} | |||
@@ -92,16 +240,16 @@ void World::setCurrentPlane(WorldPlane &plane) { | |||
WorldPlane &World::addPlane(const std::string &gen) { | |||
WorldPlane::ID id = planes_.size(); | |||
auto it = worldgenFactories_.find(gen); | |||
if (it == worldgenFactories_.end()) { | |||
panic << "Tried to add plane with non-existant world gen " << gen << "!"; | |||
auto it = worldGenFactories_.find(gen); | |||
if (it == worldGenFactories_.end()) { | |||
panic << "Tried to add plane with non-existent world gen " << gen << "!"; | |||
abort(); | |||
} | |||
std::vector<std::unique_ptr<EntityCollection>> colls; | |||
colls.reserve(entCollFactories_.size()); | |||
for (auto &fact: entCollFactories_) { | |||
colls.emplace_back(fact.create(fact.name)); | |||
colls.emplace_back(fact.second.create(fact.second.name)); | |||
} | |||
WorldGen::Factory &factory = it->second; | |||
@@ -111,21 +259,11 @@ WorldPlane &World::addPlane(const std::string &gen) { | |||
return *planes_[id]; | |||
} | |||
Item &World::getItem(const std::string &name) { | |||
auto iter = items_.find(name); | |||
if (iter == items_.end()) { | |||
warn << "Tried to get non-existant item " << name << "!"; | |||
return *game_->invalidItem_; | |||
} | |||
return *iter->second; | |||
} | |||
Tile::ID World::getTileID(const std::string &name) { | |||
auto iter = tilesMap_.find(name); | |||
if (iter == tilesMap_.end()) { | |||
warn << "Tried to get non-existant item " << name << "!"; | |||
return Tile::INVALID_ID; | |||
warn << "Tried to get non-existent item " << name << "!"; | |||
return INVALID_TILE_ID; | |||
} | |||
return iter->second; | |||
@@ -136,20 +274,41 @@ Tile &World::getTile(const std::string &name) { | |||
return getTileByID(id); | |||
} | |||
SDL_Color World::backgroundColor() { | |||
Item &World::getItem(const std::string &name) { | |||
auto iter = items_.find(name); | |||
if (iter == items_.end()) { | |||
warn << "Tried to get non-existent item " << name << "!"; | |||
return items_.at(INVALID_TILE_NAME); | |||
} | |||
return iter->second; | |||
} | |||
Cygnet::RenderSprite &World::getSprite(const std::string &name) { | |||
auto iter = resources_.sprites_.find(name); | |||
if (iter == resources_.sprites_.end()) { | |||
warn << "Tried to get non-existent sprite " << name << "!"; | |||
return resources_.sprites_.at(INVALID_TILE_NAME); | |||
} | |||
return iter->second; | |||
} | |||
Cygnet::Color World::backgroundColor() { | |||
return planes_[currentPlane_]->backgroundColor(); | |||
} | |||
void World::draw(Win &win) { | |||
void World::draw(Cygnet::Renderer &rnd) { | |||
ZoneScopedN("World draw"); | |||
win.cam_ = player_->pos - (win.getSize() / 2) + (player_->size / 2); | |||
planes_[currentPlane_]->draw(win); | |||
planes_[currentPlane_]->draw(rnd); | |||
} | |||
void World::update(float dt) { | |||
ZoneScopedN("World update"); | |||
for (auto &plane: planes_) | |||
plane->update(dt); | |||
game_->cam_.pos = player_->pos + player_->size / 2; | |||
} | |||
void World::tick(float dt) { |
@@ -8,7 +8,6 @@ | |||
#include "World.h" | |||
#include "Game.h" | |||
#include "Clock.h" | |||
#include "Win.h" | |||
namespace Swan { | |||
@@ -38,7 +37,6 @@ Context WorldPlane::getContext() { | |||
.game = *world_->game_, | |||
.world = *world_, | |||
.plane = *this, | |||
.resources = world_->resources_ | |||
}; | |||
} | |||
@@ -46,8 +44,8 @@ WorldPlane::WorldPlane( | |||
ID id, World *world, std::unique_ptr<WorldGen> gen, | |||
std::vector<std::unique_ptr<EntityCollection>> &&colls): | |||
id_(id), world_(world), gen_(std::move(gen)), | |||
lighting_(std::make_unique<LightServer>(*this)), | |||
entColls_(std::move(colls)) { | |||
entColls_(std::move(colls)), | |||
lighting_(std::make_unique<LightServer>(*this)) { | |||
for (auto &coll: entColls_) { | |||
entCollsByType_[coll->type()] = coll.get(); | |||
@@ -72,7 +70,7 @@ Chunk &WorldPlane::getChunk(ChunkPos pos) { | |||
} | |||
Chunk &chunk = slowGetChunk(pos); | |||
tickChunks_.push_back({ pos, &chunk }); | |||
tickChunks_.push_back({pos, &chunk}); | |||
return chunk; | |||
} | |||
@@ -99,7 +97,7 @@ Chunk &WorldPlane::slowGetChunk(ChunkPos pos) { | |||
lc.blocks[y * CHUNK_HEIGHT + x] = true; | |||
} | |||
if (tile.lightLevel > 0) { | |||
lc.lightSources[{ x, y }] = tile.lightLevel; | |||
lc.lightSources[{x, y}] = tile.lightLevel; | |||
} | |||
} | |||
} | |||
@@ -124,8 +122,7 @@ void WorldPlane::setTileID(TilePos pos, Tile::ID id) { | |||
if (id != old) { | |||
Tile &newTile = world_->getTileByID(id); | |||
Tile &oldTile = world_->getTileByID(old); | |||
chunk.setTileID(rp, id, newTile.image.texture_.get()); | |||
chunk.markModified(); | |||
chunk.setTileID(rp, id); | |||
if (!oldTile.isSolid && newTile.isSolid) { | |||
lighting_->onSolidBlockAdded(pos); | |||
@@ -199,17 +196,17 @@ void WorldPlane::breakTile(TilePos pos) { | |||
world_->evtTileBreak_.emit(getContext(), pos, world_->getTileByID(id)); | |||
} | |||
SDL_Color WorldPlane::backgroundColor() { | |||
Cygnet::Color WorldPlane::backgroundColor() { | |||
return gen_->backgroundColor(world_->player_->pos); | |||
} | |||
void WorldPlane::draw(Win &win) { | |||
void WorldPlane::draw(Cygnet::Renderer &rnd) { | |||
ZoneScopedN("WorldPlane draw"); | |||
std::lock_guard<std::mutex> lock(mut_); | |||
auto ctx = getContext(); | |||
auto &pbody = *(world_->player_); | |||
gen_->drawBackground(ctx, win, pbody.pos); | |||
gen_->drawBackground(ctx, rnd, pbody.pos); | |||
ChunkPos pcpos = ChunkPos( | |||
(int)floor(pbody.pos.x / CHUNK_WIDTH), | |||
@@ -217,27 +214,34 @@ void WorldPlane::draw(Win &win) { | |||
// Just init one chunk per frame | |||
if (chunkInitList_.size() > 0) { | |||
/* | |||
Chunk *chunk = chunkInitList_.front(); | |||
chunkInitList_.pop_front(); | |||
chunk->render(ctx, win.renderer_); | |||
TODO */ | |||
} | |||
for (int x = -1; x <= 1; ++x) { | |||
for (int y = -1; y <= 1; ++y) { | |||
auto iter = chunks_.find(pcpos + ChunkPos(x, y)); | |||
if (iter != chunks_.end()) | |||
iter->second.draw(ctx, win); | |||
iter->second.draw(ctx, rnd); | |||
} | |||
} | |||
for (auto &coll: entColls_) | |||
coll->draw(ctx, win); | |||
for (auto &coll: entColls_) { | |||
coll->draw(ctx, rnd); | |||
} | |||
lighting_->flip(); | |||
/* | |||
if (debugBoxes_.size() > 0) { | |||
for (auto &pos: debugBoxes_) { | |||
win.drawRect(pos, Vec2(1, 1)); | |||
rnd.drawRect(pos, Vec2(1, 1)); | |||
} | |||
} | |||
TODO */ | |||
} | |||
void WorldPlane::update(float dt) { | |||
@@ -273,12 +277,13 @@ void WorldPlane::tick(float dt) { | |||
switch (action) { | |||
case Chunk::TickAction::DEACTIVATE: | |||
info << "Compressing inactive modified chunk " << chunk->pos_; | |||
chunk->compress(); | |||
chunk->compress(world_->game_->renderer_); | |||
iter = activeChunks_.erase(iter); | |||
last = activeChunks_.end(); | |||
break; | |||
case Chunk::TickAction::DELETE: | |||
info << "Deleting inactive unmodified chunk " << chunk->pos_; | |||
chunk->destroy(world_->game_->renderer_); | |||
chunks_.erase(chunk->pos_); | |||
iter = activeChunks_.erase(iter); | |||
last = activeChunks_.end(); |
@@ -0,0 +1,69 @@ | |||
#include "assets.h" | |||
#include <SDL.h> | |||
#include <SDL_image.h> | |||
#include <cpptoml.h> | |||
#include <string.h> | |||
namespace Swan { | |||
Result<ImageAsset> loadImageAsset( | |||
const std::unordered_map<std::string, std::string> modPaths, | |||
std::string path) { | |||
auto sep = path.find("::"); | |||
if (sep == std::string::npos) { | |||
return {Err, "No '::' mod separator"}; | |||
} | |||
auto modPart = path.substr(0, sep); | |||
auto pathPart = path.substr(sep + 2, path.size() - sep - 2); | |||
auto modPath = modPaths.find(modPart); | |||
if (modPath == modPaths.end()) { | |||
return {Err, "No mod named '" + modPart + '\''}; | |||
} | |||
std::string assetPath = modPath->second + "/assets/" + pathPart; | |||
std::string pngPath = assetPath + ".png"; | |||
std::string tomlPath = assetPath + ".toml"; | |||
CPtr<SDL_Surface, SDL_FreeSurface> surface(IMG_Load(pngPath.c_str())); | |||
if (!surface) { | |||
return {Err, "Loading image " + pngPath + " failed: " + SDL_GetError()}; | |||
} | |||
int frameHeight = surface->h; | |||
// Load TOML if it exists | |||
errno = ENOENT; // I don't know if ifstream is guaranteed to set errno | |||
std::ifstream tomlFile(tomlPath); | |||
if (tomlFile) { | |||
cpptoml::parser parser(tomlFile); | |||
try { | |||
auto toml = parser.parse(); | |||
frameHeight = toml->get_as<int>("height").value_or(frameHeight); | |||
} catch (cpptoml::parse_exception &exc) { | |||
return {Err, "Failed to parse toml file " + tomlPath + ": " + exc.what()}; | |||
} | |||
} else if (errno != ENOENT) { | |||
return {Err, "Couldn't open " + tomlPath + ": " + strerror(errno)}; | |||
} | |||
ImageAsset asset{ | |||
.width = surface->w, | |||
.frameHeight = frameHeight, | |||
.frameCount = surface->h / frameHeight, | |||
.data = std::make_unique<unsigned char[]>(surface->w * surface->h * 4), | |||
}; | |||
// TODO: Pixel formats? | |||
for (size_t y = 0; y < (size_t)surface->h; ++y) { | |||
unsigned char *src = (unsigned char *)surface->pixels + y * surface->pitch; | |||
unsigned char *dest = asset.data.get() + y * surface->w * 4; | |||
memcpy(dest, src, surface->w * 4); | |||
} | |||
return {Ok, std::move(asset)}; | |||
} | |||
} |
@@ -8,11 +8,11 @@ | |||
namespace Swan { | |||
namespace Draw { | |||
static Uint8 linearLine(float from, float to, float frac) { | |||
return (Uint8)std::clamp(to * frac + from * (1 - frac), 0.0f, 255.0f); | |||
static float linearLine(float from, float to, float frac) { | |||
return std::clamp(to * frac + from * (1 - frac), 0.0f, 255.0f); | |||
} | |||
static SDL_Color linearColor(SDL_Color from, SDL_Color to, float frac) { | |||
static Cygnet::Color linearColor(Cygnet::Color from, Cygnet::Color to, float frac) { | |||
return { | |||
.r = linearLine(from.r, to.r, frac), | |||
.g = linearLine(from.g, to.g, frac), | |||
@@ -21,11 +21,11 @@ static SDL_Color linearColor(SDL_Color from, SDL_Color to, float frac) { | |||
}; | |||
} | |||
SDL_Color linearGradient( | |||
Cygnet::Color linearGradient( | |||
float val, | |||
std::initializer_list<std::pair<float, SDL_Color>> colors) { | |||
std::initializer_list<std::pair<float, Cygnet::Color>> colors) { | |||
const std::pair<float, SDL_Color> *arr = colors.begin(); | |||
const std::pair<float, Cygnet::Color> *arr = colors.begin(); | |||
size_t size = colors.size(); | |||
if (val < arr[0].first) | |||
@@ -44,6 +44,7 @@ SDL_Color linearGradient( | |||
return arr[size - 1].second; | |||
} | |||
/* | |||
void parallaxBackground( | |||
Win &win, SDL_Texture *tex, | |||
std::optional<SDL_Rect> srcrect, std::optional<SDL_Rect> destrect, | |||
@@ -92,6 +93,7 @@ void parallaxBackground( | |||
} | |||
} | |||
} | |||
TODO */ | |||
} | |||
} |
@@ -1,11 +1,10 @@ | |||
#include "traits/BodyTrait.h" | |||
#include "Win.h" | |||
namespace Swan { | |||
/* | |||
void BodyTrait::Body::outline(Win &win) { | |||
win.drawRect(pos, size); | |||
} | |||
} TODO */ | |||
} |
@@ -1,11 +1,10 @@ | |||
#include "traits/PhysicsTrait.h" | |||
#include "WorldPlane.h" | |||
#include "Win.h" | |||
namespace Swan { | |||
static float epsilon = 0.001; | |||
static float epsilon = 0.05; | |||
static void collideX( | |||
PhysicsTrait::Physics &phys, BodyTrait::Body &body, | |||
@@ -13,16 +12,16 @@ static void collideX( | |||
bool collided = false; | |||
for (int y = (int)floor(body.top() + epsilon); y <= (int)floor(body.bottom() - epsilon); ++y) { | |||
int lx = (int)floor(body.left() + epsilon); | |||
Tile &left = plane.getTile({ lx, y }); | |||
int lx = (int)floor(body.left()); | |||
Tile &left = plane.getTile({lx, y}); | |||
if (left.isSolid) { | |||
body.pos.x = (float)lx + 1.0; | |||
collided = true; | |||
break; | |||
} | |||
int rx = (int)floor(body.right() - epsilon); | |||
Tile &right = plane.getTile({ rx, y }); | |||
int rx = (int)floor(body.right()); | |||
Tile &right = plane.getTile({rx, y}); | |||
if (right.isSolid) { | |||
body.pos.x = (float)rx - body.size.x; | |||
collided = true; | |||
@@ -44,16 +43,16 @@ static void collideY( | |||
phys.onGround = false; | |||
for (int x = (int)floor(body.left() + epsilon); x <= (int)floor(body.right() - epsilon); ++x) { | |||
int ty = (int)floor(body.top() + epsilon); | |||
Tile &top = plane.getTile({ x, ty }); | |||
int ty = (int)floor(body.top()); | |||
Tile &top = plane.getTile({x, ty}); | |||
if (top.isSolid) { | |||
body.pos.y = (float)ty + 1.0; | |||
collided = true; | |||
break; | |||
} | |||
int by = (int)floor(body.bottom() - epsilon); | |||
Tile &bottom = plane.getTile({ x, by }); | |||
int by = (int)floor(body.bottom()); | |||
Tile &bottom = plane.getTile({x, by}); | |||
if (bottom.isSolid) { | |||
body.pos.y = (float)by - body.size.y; | |||
collided = true; |
@@ -1,4 +1,3 @@ | |||
#include <cygnet/Context.h> | |||
#include <cygnet/Window.h> | |||
#include <cygnet/Renderer.h> | |||
#include <cygnet/ResourceManager.h> | |||
@@ -38,7 +37,7 @@ Cygnet::RenderSprite loadSprite(Cygnet::ResourceBuilder &builder, const char *pa | |||
} | |||
int main() { | |||
Cygnet::Context ctx; | |||
SDL_Init(SDL_INIT_VIDEO); | |||
IMG_Init(IMG_INIT_PNG); | |||
Cygnet::Window win("Cygnet Test", 680, 680); | |||
Cygnet::Renderer rnd; | |||
@@ -166,10 +165,10 @@ int main() { | |||
y += 1 * dt; | |||
} | |||
rnd.drawChunk(chunk, { 0, 0 }); | |||
rnd.drawChunk(chunk, {0, 0}); | |||
rnd.drawSprite(playerSprite, { x, y }, (int)animAcc % 2); | |||
cam.pos = { x + 0.5f, y + 0.5f }; | |||
rnd.drawSprite(playerSprite, Cygnet::Mat3gf{}.translate({x, y}), (int)animAcc % 2); | |||
cam.pos = {x + 0.5f, y + 0.5f}; | |||
win.clear(); | |||
rnd.draw(cam); | |||
@@ -178,4 +177,5 @@ int main() { | |||
exit: | |||
IMG_Quit(); | |||
SDL_Quit(); | |||
} |
@@ -10,6 +10,8 @@ | |||
#include <string.h> | |||
#include <imgui.h> | |||
#include <imgui_sdl/imgui_sdl.h> | |||
#include <cygnet/Renderer.h> | |||
#include <cygnet/Window.h> | |||
#include <swan/swan.h> | |||
@@ -77,31 +79,20 @@ int main(int argc, char **argv) { | |||
imgassert(IMG_Init(imgFlags) == imgFlags, "Could not initialize SDL_Image"); | |||
Deferred<IMG_Quit> sdlImage; | |||
// Create the window | |||
CPtr<SDL_Window, SDL_DestroyWindow> window( | |||
SDL_CreateWindow( | |||
"Project: SWAN", | |||
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, | |||
(int)(640 * guiScale), (int)(480 * guiScale), winFlags)); | |||
Cygnet::Window window("Project: SWAN", 640 * guiScale, 480 * guiScale); | |||
// Load and display application icon | |||
CPtr<SDL_Surface, SDL_FreeSurface> icon( | |||
IMG_Load("assets/icon.png")); | |||
sdlassert(icon, "Could not load icon"); | |||
SDL_SetWindowIcon(window.get(), icon.get()); | |||
CPtr<SDL_Renderer, SDL_DestroyRenderer> renderer( | |||
SDL_CreateRenderer(window.get(), -1, renderFlags)); | |||
sdlassert(renderer, "Could not create renderer"); | |||
SDL_SetRenderDrawBlendMode(renderer.get(), SDL_BLENDMODE_BLEND); | |||
SDL_SetRenderDrawColor(renderer.get(), 0, 0, 0, 255); | |||
Win win(window.get(), renderer.get(), guiScale); | |||
SDL_SetWindowIcon(window.sdlWindow(), icon.get()); | |||
// Init ImGUI and ImGUI_SDL | |||
/* | |||
IMGUI_CHECKVERSION(); | |||
CPtr<ImGuiContext, ImGui::DestroyContext> context( | |||
ImGui::CreateContext()); | |||
ImGuiSDL::Initialize(renderer.get(), (int)win.getPixSize().x, (int)win.getPixSize().y); | |||
Deferred<ImGuiSDL::Deinitialize> imguiSDL; | |||
info << "Initialized with window size " << win.getPixSize(); | |||
@@ -109,9 +100,11 @@ int main(int argc, char **argv) { | |||
// ImGuiIO is to glue SDL and ImGUI together | |||
ImGuiIO& imguiIO = ImGui::GetIO(); | |||
imguiIO.BackendPlatformName = "imgui_sdl + Project: SWAN"; | |||
TODO */ | |||
// Create a world | |||
Game game(win); | |||
Game game; | |||
game.cam_.size = window.size(); | |||
std::vector<std::string> mods{ "core.mod" }; | |||
game.createWorld("core::default", mods); | |||
@@ -124,7 +117,6 @@ int main(int argc, char **argv) { | |||
int slowFrames = 0; | |||
while (1) { | |||
ZoneScopedN("game loop"); | |||
RTClock totalTimeClock; | |||
SDL_Event evt; | |||
while (SDL_PollEvent(&evt)) { | |||
@@ -135,9 +127,9 @@ int main(int argc, char **argv) { | |||
case SDL_WINDOWEVENT: | |||
if (evt.window.event == SDL_WINDOWEVENT_RESIZED) { | |||
imguiIO.DisplaySize.x = (float)evt.window.data1; | |||
imguiIO.DisplaySize.y = (float)evt.window.data2; | |||
win.onResize(evt.window.data1, evt.window.data2); | |||
window.onResize(evt.window.data1, evt.window.data2); | |||
//imguiIO.DisplaySize.x = (float)evt.window.data1; | |||
//imguiIO.DisplaySize.y = (float)evt.window.data2; | |||
} | |||
break; | |||
@@ -150,32 +142,42 @@ int main(int argc, char **argv) { | |||
break; | |||
case SDL_MOUSEMOTION: | |||
/* | |||
imguiIO.MousePos.x = (float)evt.motion.x; | |||
imguiIO.MousePos.y = (float)evt.motion.y; | |||
if (!imguiIO.WantCaptureMouse) | |||
if (!imguiIO.WantCaptureMouse) */ | |||
game.onMouseMove(evt.motion.x, evt.motion.y); | |||
break; | |||
case SDL_MOUSEBUTTONDOWN: | |||
/* | |||
imguiIO.MouseDown[sdlButtonToImGuiButton(evt.button.button)] = true; | |||
if (!imguiIO.WantCaptureMouse) | |||
if (!imguiIO.WantCaptureMouse) */ | |||
game.onMouseDown(evt.button.x, evt.button.y, evt.button.button); | |||
break; | |||
case SDL_MOUSEBUTTONUP: | |||
/* | |||
imguiIO.MouseDown[sdlButtonToImGuiButton(evt.button.button)] = false; | |||
if (!imguiIO.WantCaptureMouse) | |||
if (!imguiIO.WantCaptureMouse) */ | |||
game.onMouseUp(evt.button.x, evt.button.y, evt.button.button); | |||
break; | |||
case SDL_MOUSEWHEEL: | |||
if (evt.wheel.y == 0) { | |||
break; | |||
} | |||
/* | |||
imguiIO.MouseWheel += (float)evt.wheel.y; | |||
if (!imguiIO.WantCaptureMouse) | |||
if (!imguiIO.WantCaptureMouse) */ | |||
game.onScrollWheel(evt.wheel.y); | |||
break; | |||
} | |||
} | |||
game.cam_.size = window.size(); | |||
auto now = std::chrono::steady_clock::now(); | |||
std::chrono::duration<float> dur(now - prevTime); | |||
prevTime = now; | |||
@@ -207,7 +209,6 @@ int main(int argc, char **argv) { | |||
} | |||
// Simple case: we can keep up, only need one physics update | |||
RTClock updateClock; | |||
if (dt <= 1 / 25.0) { | |||
ZoneScopedN("game update"); | |||
game.update(dt); | |||
@@ -216,9 +217,13 @@ int main(int argc, char **argv) { | |||
} else { | |||
int count = (int)ceil(dt / (1/30.0)); | |||
float delta = dt / (float)count; | |||
info << "Delta time " << dt << "s. Running " << count | |||
<< " updates in one frame, with a delta as if we had " | |||
<< 1.0 / delta << " FPS."; | |||
// Don't be too noisy with the occasional double update | |||
if (count > 2) { | |||
info << "Delta time " << dt << "s. Running " << count | |||
<< " updates in one frame, with a delta as if we had " | |||
<< 1.0 / delta << " FPS."; | |||
} | |||
for (int i = 0; i < count; ++i) { | |||
ZoneScopedN("game update"); | |||
game.update(delta); | |||
@@ -230,37 +235,32 @@ int main(int argc, char **argv) { | |||
while (tickAcc >= 1.0 / TICK_RATE) { | |||
ZoneScopedN("game tick"); | |||
tickAcc -= 1.0 / TICK_RATE; | |||
RTClock tickClock; | |||
game.tick(1.0 / TICK_RATE); | |||
} | |||
{ | |||
auto [r, g, b, a] = game.backgroundColor(); | |||
RenderDrawColor c(renderer.get(), r, g, b, a); | |||
SDL_RenderClear(renderer.get()); | |||
window.clear(game.backgroundColor()); | |||
} | |||
// ImGUI | |||
imguiIO.DeltaTime = dt; | |||
ImGui::NewFrame(); | |||
//imguiIO.DeltaTime = dt; | |||
//ImGui::NewFrame(); | |||
{ | |||
ZoneScopedN("game draw"); | |||
RTClock drawClock; | |||
game.draw(); | |||
} | |||
// Render ImGUI | |||
{ | |||
ZoneScopedN("imgui render"); | |||
ImGui::Render(); | |||
ImGuiSDL::Render(ImGui::GetDrawData()); | |||
//ImGui::Render(); | |||
//ImGuiSDL::Render(ImGui::GetDrawData()); | |||
} | |||
RTClock presentClock; | |||
{ | |||
ZoneScopedN("render present"); | |||
SDL_RenderPresent(renderer.get()); | |||
window.flip(); | |||
} | |||
FrameMark | |||
} |