if(CMAKE_BUILD_TYPE STREQUAL Sanitize OR CMAKE_BUILD_TYPE STREQUAL "") | if(CMAKE_BUILD_TYPE STREQUAL Sanitize OR CMAKE_BUILD_TYPE STREQUAL "") | ||||
message(STATUS "Build mode: Sanitize") | 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) | add_link_options(-fsanitize=address -fsanitize=undefined) | ||||
elseif(CMAKE_BUILD_TYPE STREQUAL Debug) | elseif(CMAKE_BUILD_TYPE STREQUAL Debug) | ||||
message(STATUS "Build mode: Debug") | message(STATUS "Build mode: Debug") | ||||
add_compile_options(-g) | |||||
add_compile_options(-g -DDEBUG) | |||||
elseif(CMAKE_BUILD_TYPE STREQUAL Optimize) | elseif(CMAKE_BUILD_TYPE STREQUAL Optimize) | ||||
message(STATUS "Build mode: Optimize") | message(STATUS "Build mode: Optimize") | ||||
add_compile_options(-O3 -DNDEBUG -g) | |||||
add_compile_options(-O3 -g -DDEBUG) | |||||
elseif(CMAKE_BUILD_TYPE STREQUAL Tracy) | elseif(CMAKE_BUILD_TYPE STREQUAL Tracy) | ||||
message(STATUS "Build mode: Tracy") | message(STATUS "Build mode: Tracy") | ||||
elseif(CMAKE_BUILD_TYPE STREQUAL Release) | elseif(CMAKE_BUILD_TYPE STREQUAL Release) | ||||
message(STATUS "Build mode: 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") | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -flto") | ||||
else() | else() | ||||
add_executable(swan | add_executable(swan | ||||
src/main.cc) | src/main.cc) | ||||
target_link_libraries(swan libswan libcygnet ${libraries}) | target_link_libraries(swan libswan libcygnet ${libraries}) | ||||
add_dependencies(swan core.mod) | |||||
add_executable(perlin-test EXCLUDE_FROM_ALL | add_executable(perlin-test EXCLUDE_FROM_ALL | ||||
src/perlin-test.cc) | src/perlin-test.cc) |
return (int)(perlin.noise(x / 50.0, 10) * 10) + 10; | 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 texmin = 10; | ||||
int texmax = 20; | |||||
//int texmax = 20; | |||||
if (pos.y > texmin) { | if (pos.y > texmin) { | ||||
/* | |||||
SDL_Texture *tex = bgCave_.texture_.get(); | SDL_Texture *tex = bgCave_.texture_.get(); | ||||
Uint8 alpha = std::clamp( | Uint8 alpha = std::clamp( | ||||
Swan::Draw::parallaxBackground( | Swan::Draw::parallaxBackground( | ||||
win, tex, std::nullopt, std::nullopt, | win, tex, std::nullopt, std::nullopt, | ||||
pos.x * Swan::TILE_SIZE, pos.y * Swan::TILE_SIZE, 0.7); | 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; | float y = pos.y; | ||||
return Swan::Draw::linearGradient(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) { | Swan::Tile::ID DefaultWorldGen::genTile(Swan::TilePos pos) { |
tAir_(world.getTileID("@::air")), | tAir_(world.getTileID("@::air")), | ||||
tTreeTrunk_(world.getTileID("core::tree-trunk")), | tTreeTrunk_(world.getTileID("core::tree-trunk")), | ||||
tLeaves_(world.getTileID("core::leaves")), | 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; | void genChunk(Swan::WorldPlane &plane, Swan::Chunk &chunk) override; | ||||
Swan::EntityRef spawnPlayer(const Swan::Context &ctx) override; | Swan::EntityRef spawnPlayer(const Swan::Context &ctx) override; | ||||
private: | private: | ||||
Swan::Tile::ID genTile(Swan::TilePos pos); | Swan::Tile::ID genTile(Swan::TilePos pos); | ||||
Swan::Tile::ID tGrass_, tDirt_, tStone_, tAir_, tTreeTrunk_, tLeaves_; | Swan::Tile::ID tGrass_, tDirt_, tStone_, tAir_, tTreeTrunk_, tLeaves_; | ||||
Swan::ImageResource &bgCave_; | |||||
Cygnet::RenderSprite bgCave_; | |||||
siv::PerlinNoise perlin_ = siv::PerlinNoise(100); | siv::PerlinNoise perlin_ = siv::PerlinNoise(100); | ||||
}; | }; |
deserialize(ctx, 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) { | void ItemStackEntity::update(const Swan::Context &ctx, float dt) { |
ItemStackEntity(const Swan::Context &ctx, Swan::Vec2 pos, const std::string &item); | ItemStackEntity(const Swan::Context &ctx, Swan::Vec2 pos, const std::string &item); | ||||
ItemStackEntity(const Swan::Context &ctx, const PackObject &obj); | 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 update(const Swan::Context &ctx, float dt) override; | ||||
void tick(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; | void deserialize(const Swan::Context &ctx, const PackObject &obj) override; |
deserialize(ctx, 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) { | void PlayerEntity::update(const Swan::Context &ctx, float dt) { |
using PhysicsEntity::get; | using PhysicsEntity::get; | ||||
Inventory &get(InventoryTrait::Tag) override { return inventory_; } | 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 update(const Swan::Context &ctx, float dt) override; | ||||
void tick(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; | void deserialize(const Swan::Context &ctx, const PackObject &obj) override; | ||||
PlayerEntity(const Swan::Context &ctx): | PlayerEntity(const Swan::Context &ctx): | ||||
PhysicsEntity(SIZE), | PhysicsEntity(SIZE), | ||||
anims_{ | 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; | State state_ = State::IDLE; |
breakListener_ = world.evtTileBreak_.subscribe( | breakListener_ = world.evtTileBreak_.subscribe( | ||||
std::bind_front(&CoreMod::onTileBreak, this)); | 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({ | registerTile({ | ||||
.name = "stone", | .name = "stone", | ||||
.image = "core/tile/stone", | |||||
.image = "core::tile/stone", | |||||
.droppedItem = "core::stone", | .droppedItem = "core::stone", | ||||
}); | }); | ||||
registerTile({ | registerTile({ | ||||
.name = "dirt", | .name = "dirt", | ||||
.image = "core/tile/dirt", | |||||
.image = "core::tile/dirt", | |||||
.droppedItem = "core::dirt", | .droppedItem = "core::dirt", | ||||
}); | }); | ||||
registerTile({ | registerTile({ | ||||
.name = "grass", | .name = "grass", | ||||
.image = "core/tile/grass", | |||||
.image = "core::tile/grass", | |||||
.droppedItem = "core::dirt", | .droppedItem = "core::dirt", | ||||
}); | }); | ||||
registerTile({ | registerTile({ | ||||
.name = "tree-trunk", | .name = "tree-trunk", | ||||
.image = "core/tile/tree-trunk", | |||||
.image = "core::tile/tree-trunk", | |||||
.droppedItem = "core::tree-trunk", | .droppedItem = "core::tree-trunk", | ||||
}); | }); | ||||
registerTile({ | registerTile({ | ||||
.name = "leaves", | .name = "leaves", | ||||
.image = "core/tile/leaves", | |||||
.image = "core::tile/leaves", | |||||
}); | }); | ||||
registerTile({ | registerTile({ | ||||
.name = "torch", | .name = "torch", | ||||
.image = "core/tile/torch", | |||||
.image = "core::tile/torch", | |||||
.isSolid = false, | .isSolid = false, | ||||
.lightLevel = 80/255.0, | .lightLevel = 80/255.0, | ||||
}); | }); | ||||
registerItem({ | registerItem({ | ||||
.name = "stone", | .name = "stone", | ||||
.image = "core/tile/stone", | |||||
.image = "core::tile/stone", | |||||
}); | }); | ||||
registerItem({ | registerItem({ | ||||
.name = "dirt", | .name = "dirt", | ||||
.image = "core/tile/dirt", | |||||
.image = "core::tile/dirt", | |||||
}); | }); | ||||
registerItem({ | registerItem({ | ||||
.name = "grass", | .name = "grass", | ||||
.image = "core/tile/grass", | |||||
.image = "core::tile/grass", | |||||
}); | }); | ||||
registerItem({ | registerItem({ | ||||
.name = "tree-trunk", | .name = "tree-trunk", | ||||
.image = "core/tile/tree-trunk", | |||||
.image = "core::tile/tree-trunk", | |||||
}); | }); | ||||
registerWorldGen<DefaultWorldGen>("default"); | registerWorldGen<DefaultWorldGen>("default"); |
#pragma once | #pragma once | ||||
#include <iostream> | |||||
#include <ostream> | |||||
#include <cmath> | #include <cmath> | ||||
#include <array> | #include <array> | ||||
namespace SwanCommon { | 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> | template<typename T> | ||||
struct Matrix3 { | struct Matrix3 { | ||||
using Vec = Vector2<T>; | using Vec = Vector2<T>; | ||||
constexpr Matrix3<T> &scale(Vec vec) { | constexpr Matrix3<T> &scale(Vec vec) { | ||||
at(0, 0) *= vec.x; | 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(1, 1) *= vec.y; | ||||
at(2, 1) *= vec.y; | |||||
return *this; | return *this; | ||||
} | } | ||||
constexpr Matrix3<T> &rotate(T rads) { | constexpr Matrix3<T> &rotate(T rads) { | ||||
T s = std::sin(rads); | T s = std::sin(rads); | ||||
T c = std::cos(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; | return *this; | ||||
} | } | ||||
std::ostream &operator<<(std::ostream &os, const Matrix3<T> &mat) { | std::ostream &operator<<(std::ostream &os, const Matrix3<T> &mat) { | ||||
os << '(' | os << '(' | ||||
<< '(' << mat.at(0, 0) << ", " << mat.at(1, 0) << ", " << mat.at(2, 0) << "), " | << '(' << 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; | return os; | ||||
} | } | ||||
add_library(libcygnet SHARED | add_library(libcygnet SHARED | ||||
src/Context.cc | |||||
src/GlWrappers.cc | src/GlWrappers.cc | ||||
src/Renderer.cc | src/Renderer.cc | ||||
src/ResourceManager.cc | src/ResourceManager.cc |
#pragma once | |||||
namespace Cygnet { | |||||
class Context { | |||||
public: | |||||
Context(); | |||||
~Context(); | |||||
}; | |||||
} |
GLuint tex; | GLuint tex; | ||||
}; | }; | ||||
struct RenderChunkShadow { | |||||
GLuint tex; | |||||
}; | |||||
struct RenderSprite { | struct RenderSprite { | ||||
GLuint tex; | GLuint tex; | ||||
SwanCommon::Vec2 scale; | SwanCommon::Vec2 scale; | ||||
int frameCount; | int frameCount; | ||||
}; | }; | ||||
struct RenderTile { | |||||
uint16_t id; | |||||
}; | |||||
struct RenderCamera { | 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 { | class Renderer { | ||||
~Renderer(); | ~Renderer(); | ||||
void drawChunk(RenderChunk chunk, SwanCommon::Vec2 pos); | 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, 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); | void draw(const RenderCamera &cam); | ||||
void modifyChunk(RenderChunk chunk, SwanCommon::Vec2i pos, TileID id); | void modifyChunk(RenderChunk chunk, SwanCommon::Vec2i pos, TileID id); | ||||
void destroyChunk(RenderChunk chunk); | 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, int fh); | ||||
RenderSprite createSprite(void *data, int width, int height); | RenderSprite createSprite(void *data, int width, int height); | ||||
void destroySprite(RenderSprite sprite); | void destroySprite(RenderSprite sprite); | ||||
SwanCommon::Vec2 winScale() { return winScale_; } | |||||
private: | private: | ||||
struct DrawChunk { | struct DrawChunk { | ||||
SwanCommon::Vec2 pos; | SwanCommon::Vec2 pos; | ||||
RenderChunk chunk; | RenderChunk chunk; | ||||
}; | }; | ||||
struct DrawShadow { | |||||
SwanCommon::Vec2 pos; | |||||
RenderChunkShadow shadow; | |||||
}; | |||||
struct DrawTile { | |||||
Mat3gf transform; | |||||
TileID id; | |||||
}; | |||||
struct DrawSprite { | struct DrawSprite { | ||||
Mat3gf transform; | Mat3gf transform; | ||||
int frame; | int frame; | ||||
RenderSprite sprite; | RenderSprite sprite; | ||||
}; | }; | ||||
struct DrawRect { | |||||
SwanCommon::Vec2 pos; | |||||
SwanCommon::Vec2 size; | |||||
}; | |||||
SwanCommon::Vec2 winScale_ = {1, 1}; | |||||
std::unique_ptr<RendererState> state_; | std::unique_ptr<RendererState> state_; | ||||
std::vector<DrawChunk> drawChunks_; | std::vector<DrawChunk> drawChunks_; | ||||
std::vector<DrawShadow> drawChunkShadows_; | |||||
std::vector<DrawTile> drawTiles_; | |||||
std::vector<DrawSprite> drawSprites_; | std::vector<DrawSprite> drawSprites_; | ||||
std::vector<DrawRect> drawRects_; | |||||
}; | }; | ||||
inline void Renderer::drawChunk(RenderChunk chunk, SwanCommon::Vec2 pos) { | 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}); | |||||
} | } | ||||
} | } |
public: | public: | ||||
ResourceBuilder(Renderer &rnd): rnd_(rnd) {} | 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); | 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, void *data, int frames = 1); | ||||
void addTile(Renderer::TileID id, std::unique_ptr<unsigned char[]> data, int frames = 1); | void addTile(Renderer::TileID id, std::unique_ptr<unsigned char[]> data, int frames = 1); | ||||
ResourceManager(ResourceBuilder &&builder); | ResourceManager(ResourceBuilder &&builder); | ||||
~ResourceManager(); | ~ResourceManager(); | ||||
RenderSprite getSprite(std::string name) { return sprites_.at(std::move(name)); } | |||||
void tick(); | void tick(); | ||||
private: | |||||
Renderer &rnd_; | Renderer &rnd_; | ||||
std::unordered_map<std::string, RenderSprite> sprites_; | 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_; | std::vector<ResourceTileAnimation> tileAnims_; | ||||
}; | }; | ||||
#include <swan-common/Vector2.h> | #include <swan-common/Vector2.h> | ||||
#include <memory> | #include <memory> | ||||
#include "util.h" | |||||
struct SDL_Window; | |||||
namespace Cygnet { | namespace Cygnet { | ||||
struct WindowState; | struct WindowState; | ||||
~Window(); | ~Window(); | ||||
void makeCurrent(); | void makeCurrent(); | ||||
void clear(); | |||||
void clear(Color color = {}); | |||||
void flip(); | void flip(); | ||||
void onResize(int w, int h); | void onResize(int w, int h); | ||||
SwanCommon::Vec2i size() { return { w_, h_ }; } | SwanCommon::Vec2i size() { return { w_, h_ }; } | ||||
SDL_Window *sdlWindow(); | |||||
private: | private: | ||||
std::unique_ptr<WindowState> state_; | std::unique_ptr<WindowState> state_; | ||||
int w_; | int w_; |
namespace Cygnet::Shaders { | 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 *spriteVx; | ||||
extern const char *spriteFr; | extern const char *spriteFr; | ||||
extern const char *chunkVx; | |||||
extern const char *chunkFr; | |||||
extern const char *rectVx; | |||||
extern const char *rectFr; | |||||
} | } |
using Mat3gf = SwanCommon::Matrix3<GLfloat>; | 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 { | struct SDLError: public std::exception { | ||||
SDLError(std::string msg): message(std::move(msg)) {} | SDLError(std::string msg): message(std::move(msg)) {} | ||||
const char *what() const noexcept override { return message.c_str(); } | const char *what() const noexcept override { return message.c_str(); } |
#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(); | |||||
} | |||||
} |
} | } | ||||
}; | }; | ||||
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 { | struct SpriteProg: public GlProgram { | ||||
template<typename... T> | template<typename... T> | ||||
SpriteProg(const T &... shaders): GlProgram(shaders...) { init(); } | SpriteProg(const T &... shaders): GlProgram(shaders...) { init(); } | ||||
GLint camera = uniformLoc("camera"); | GLint camera = uniformLoc("camera"); | ||||
GLint transform = uniformLoc("transform"); | GLint transform = uniformLoc("transform"); | ||||
GLint frameSize = uniformLoc("frameSize"); | |||||
GLint frameInfo = uniformLoc("frameInfo"); | GLint frameInfo = uniformLoc("frameInfo"); | ||||
GLint vertex = attribLoc("vertex"); | GLint vertex = attribLoc("vertex"); | ||||
GLint tex = uniformLoc("tex"); | GLint tex = uniformLoc("tex"); | ||||
} | } | ||||
}; | }; | ||||
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 { | struct RendererState { | ||||
GlVxShader spriteVx{Shaders::spriteVx}; | |||||
GlFrShader spriteFr{Shaders::spriteFr}; | |||||
GlVxShader chunkVx{Shaders::chunkVx}; | GlVxShader chunkVx{Shaders::chunkVx}; | ||||
GlFrShader chunkFr{Shaders::chunkFr}; | 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}; | ChunkProg chunkProg{chunkVx, chunkFr}; | ||||
ChunkShadowProg chunkShadowProg{chunkShadowVx, chunkShadowFr}; | |||||
TileProg tileProg{tileVx, tileFr}; | |||||
SpriteProg spriteProg{spriteVx, spriteFr}; | |||||
RectProg rectProg{rectVx, rectFr}; | |||||
GLuint atlasTex; | GLuint atlasTex; | ||||
SwanCommon::Vec2 atlasTexSize; | SwanCommon::Vec2 atlasTexSize; | ||||
void Renderer::draw(const RenderCamera &cam) { | void Renderer::draw(const RenderCamera &cam) { | ||||
Mat3gf camMat; | 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 &chunkProg = state_->chunkProg; | ||||
auto &chunkShadowProg = state_->chunkShadowProg; | |||||
auto &tileProg = state_->tileProg; | |||||
auto &spriteProg = state_->spriteProg; | auto &spriteProg = state_->spriteProg; | ||||
auto &rectProg = state_->rectProg; | |||||
{ | { | ||||
chunkProg.enable(); | chunkProg.enable(); | ||||
chunkProg.disable(); | 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(); | spriteProg.enable(); | ||||
glUniformMatrix3fv(spriteProg.camera, 1, GL_TRUE, camMat.data()); | glUniformMatrix3fv(spriteProg.camera, 1, GL_TRUE, camMat.data()); | ||||
glActiveTexture(GL_TEXTURE0); | glActiveTexture(GL_TEXTURE0); | ||||
for (auto [mat, frame, sprite]: drawSprites_) { | for (auto [mat, frame, sprite]: drawSprites_) { | ||||
mat.scale(sprite.scale); | |||||
glUniformMatrix3fv(spriteProg.transform, 1, GL_TRUE, mat.data()); | 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); | glBindTexture(GL_TEXTURE_2D, sprite.tex); | ||||
glDrawArrays(GL_TRIANGLES, 0, 6); | glDrawArrays(GL_TRIANGLES, 0, 6); | ||||
glCheck(); | glCheck(); | ||||
drawSprites_.clear(); | drawSprites_.clear(); | ||||
spriteProg.disable(); | 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) { | void Renderer::uploadTileAtlas(const void *data, int width, int height) { | ||||
RenderChunk Renderer::createChunk( | RenderChunk Renderer::createChunk( | ||||
TileID tiles[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT]) { | 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; | RenderChunk chunk; | ||||
glGenTextures(1, &chunk.tex); | glGenTextures(1, &chunk.tex); | ||||
glCheck(); | glCheck(); | ||||
glBindTexture(GL_TEXTURE_2D, chunk.tex); | glBindTexture(GL_TEXTURE_2D, chunk.tex); | ||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | 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_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(); | glCheck(); | ||||
static_assert( | static_assert( | ||||
glCheck(); | 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 Renderer::createSprite(void *data, int width, int height, int fh) { | ||||
RenderSprite sprite; | RenderSprite sprite; | ||||
sprite.scale = { | sprite.scale = { |
const unsigned char *bytes = (const unsigned char *)data; | const unsigned char *bytes = (const unsigned char *)data; | ||||
size_t x = tileId % state_->tilesPerLine; | size_t x = tileId % state_->tilesPerLine; | ||||
size_t y = tileId / state_->tilesPerLine; | size_t y = tileId / state_->tilesPerLine; | ||||
std::cerr << "Tile " << tileId << " to " << x << ", " << y << '\n'; | |||||
if (state_->width <= x) { | if (state_->width <= x) { | ||||
state_->width = x + 1; | state_->width = x + 1; |
Window::Window(const char *name, int w, int h): | Window::Window(const char *name, int w, int h): | ||||
state_(std::make_unique<WindowState>()), w_(w), h_(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, | state_->window = SDL_CreateWindow(name, | ||||
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, | SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, | ||||
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | | ||||
glCheck(); | glCheck(); | ||||
} | } | ||||
void Window::clear() { | |||||
void Window::clear(Color color) { | |||||
glClearColor(color.r, color.g, color.b, color.a); | |||||
glClear(GL_COLOR_BUFFER_BIT); | glClear(GL_COLOR_BUFFER_BIT); | ||||
glCheck(); | glCheck(); | ||||
} | } | ||||
void Window::onResize(int w, int h) { | void Window::onResize(int w, int h) { | ||||
w_ = w; | w_ = w; | ||||
h_ = h; | h_ = h; | ||||
glViewport(0, 0, w, h); | |||||
int dw, dh; | |||||
SDL_GL_GetDrawableSize(state_->window, &dw, &dh); | |||||
glViewport(0, 0, dw, dh); | |||||
glCheck(); | glCheck(); | ||||
} | } | ||||
SDL_Window *Window::sdlWindow() { | |||||
return state_->window; | |||||
} | |||||
} | } |
namespace Cygnet::Shaders { | 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 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 camera; | ||||
uniform mat3 transform; | |||||
uniform vec3 frameInfo; // frame height, frame count, frame index | |||||
uniform vec2 pos; | |||||
attribute vec2 vertex; | attribute vec2 vertex; | ||||
varying vec2 v_texCoord; | varying vec2 v_texCoord; | ||||
void main() { | 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); | gl_Position = vec4(pos.xy, 0, 1); | ||||
v_texCoord = vertex / vec2(CHUNK_WIDTH, CHUNK_HEIGHT); | |||||
} | } | ||||
)glsl"; | )glsl"; | ||||
const char *spriteFr = R"glsl( | |||||
const char *chunkShadowFr = R"glsl( | |||||
precision mediump float; | precision mediump float; | ||||
varying vec2 v_texCoord; | varying vec2 v_texCoord; | ||||
uniform sampler2D tex; | uniform sampler2D tex; | ||||
void main() { | 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"; | )glsl"; | ||||
const char *chunkVx = R"glsl( | |||||
const char *tileVx = R"glsl( | |||||
precision mediump float; | |||||
uniform mat3 camera; | uniform mat3 camera; | ||||
uniform vec2 pos; | |||||
uniform mat3 transform; | |||||
attribute vec2 vertex; | attribute vec2 vertex; | ||||
varying vec2 v_tileCoord; | varying vec2 v_tileCoord; | ||||
void main() { | void main() { | ||||
vec3 pos = camera * vec3(pos + vertex, 1); | |||||
vec3 pos = camera * transform * vec3(vertex, 1); | |||||
gl_Position = vec4(pos.xy, 0, 1); | gl_Position = vec4(pos.xy, 0, 1); | ||||
v_tileCoord = vec2(vertex.x, vertex.y); | |||||
v_tileCoord = vertex; | |||||
} | } | ||||
)glsl"; | )glsl"; | ||||
const char *chunkFr = R"glsl( | |||||
const char *tileFr = R"glsl( | |||||
precision mediump float; | precision mediump float; | ||||
#define TILE_SIZE 32.0 | #define TILE_SIZE 32.0 | ||||
#define CHUNK_WIDTH 64 | |||||
#define CHUNK_HEIGHT 64 | |||||
varying vec2 v_tileCoord; | varying vec2 v_tileCoord; | ||||
uniform sampler2D tileAtlas; | uniform sampler2D tileAtlas; | ||||
uniform vec2 tileAtlasSize; | uniform vec2 tileAtlasSize; | ||||
uniform sampler2D tiles; | |||||
uniform float tileID; | |||||
void main() { | 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. | // 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 pixoffset = (1.0 - offset * 2.0) / (TILE_SIZE * 16.0); | ||||
vec2 atlasPos = vec2( | vec2 atlasPos = vec2( | ||||
pixoffset.x + tileID + offset.x, | pixoffset.x + tileID + offset.x, | ||||
} | } | ||||
)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"; | |||||
} | } |
src/traits/InventoryTrait.cc | src/traits/InventoryTrait.cc | ||||
src/traits/PhysicsTrait.cc | src/traits/PhysicsTrait.cc | ||||
src/Animation.cc | src/Animation.cc | ||||
src/assets.cc | |||||
src/Chunk.cc | src/Chunk.cc | ||||
src/Clock.cc | src/Clock.cc | ||||
src/drawutil.cc | src/drawutil.cc | ||||
src/Entity.cc | src/Entity.cc | ||||
src/Game.cc | src/Game.cc | ||||
src/gfxutil.cc | src/gfxutil.cc | ||||
src/Item.cc | |||||
src/ItemStack.cc | src/ItemStack.cc | ||||
src/LightServer.cc | src/LightServer.cc | ||||
src/Mod.cc | |||||
src/OS.cc | src/OS.cc | ||||
src/Resource.cc | |||||
src/Tile.cc | |||||
src/World.cc | src/World.cc | ||||
src/WorldPlane.cc) | src/WorldPlane.cc) | ||||
target_include_directories(libswan | target_include_directories(libswan | ||||
PUBLIC "include" | PUBLIC "include" | ||||
PRIVATE "include/swan") | PRIVATE "include/swan") | ||||
set_target_properties(libswan PROPERTIES OUTPUT_NAME 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) | install(TARGETS libswan DESTINATION swan/libswan) | ||||
#pragma once | #pragma once | ||||
#include <SDL.h> | #include <SDL.h> | ||||
#include <cygnet/Renderer.h> | |||||
#include "common.h" | #include "common.h" | ||||
#include "Resource.h" | |||||
#include "Clock.h" | #include "Clock.h" | ||||
#include "Resource.h" | |||||
namespace Swan { | namespace Swan { | ||||
class Animation { | class Animation { | ||||
public: | 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 tick(float dt); | ||||
void draw(const Vec2 &pos, Win &win); | |||||
void reset(); | void reset(); | ||||
void draw(Cygnet::Renderer &rnd, Cygnet::Mat3gf mat); | |||||
private: | private: | ||||
ImageResource &resource_; | |||||
Cygnet::RenderSprite sprite_; | |||||
float interval_; | float interval_; | ||||
float timer_; | float timer_; | ||||
SDL_RendererFlip flip_; | |||||
int frame_ = 0; | int frame_ = 0; | ||||
}; | }; | ||||
inline void Animation::draw(Cygnet::Renderer &rnd, Cygnet::Mat3gf mat) { | |||||
rnd.drawSprite(sprite_, mat, frame_); | |||||
} | } | ||||
} | |||||
#include <string.h> | #include <string.h> | ||||
#include <stdint.h> | #include <stdint.h> | ||||
#include <memory> | #include <memory> | ||||
#include <cygnet/Renderer.h> | |||||
#include "util.h" | #include "util.h" | ||||
#include "common.h" | #include "common.h" | ||||
return getTileData()[pos.y * CHUNK_WIDTH + pos.x]; | 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; | 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) { | void setTileData(RelPos pos, Tile::ID id) { | ||||
getTileData()[pos.y * CHUNK_WIDTH + pos.x] = id; | getTileData()[pos.y * CHUNK_WIDTH + pos.x] = id; | ||||
needRender_ = true; | |||||
} | } | ||||
uint8_t getLightLevel(RelPos pos) { | uint8_t getLightLevel(RelPos pos) { | ||||
needLightRender_ = true; | 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 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); | TickAction tick(float dt); | ||||
bool isActive() { return deactivateTimer_ > 0; } | bool isActive() { return deactivateTimer_ > 0; } | ||||
void keepActive(); | |||||
void markModified() { isModified_ = true; } | |||||
ChunkPos pos_; | ChunkPos pos_; | ||||
private: | private: | ||||
static constexpr float DEACTIVATE_INTERVAL = 20; | static constexpr float DEACTIVATE_INTERVAL = 20; | ||||
void renderList(SDL_Renderer *rnd); | |||||
bool isCompressed() { return compressedSize_ != -1; } | bool isCompressed() { return compressedSize_ != -1; } | ||||
std::unique_ptr<uint8_t[]> data_; | 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 | 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; | bool needLightRender_ = false; | ||||
float deactivateTimer_ = DEACTIVATE_INTERVAL; | float deactivateTimer_ = DEACTIVATE_INTERVAL; | ||||
bool isModified_ = false; | bool isModified_ = false; | ||||
CPtr<SDL_Texture, SDL_DestroyTexture> texture_; | |||||
CPtr<SDL_Texture, SDL_DestroyTexture> lightTexture_; | |||||
}; | }; | ||||
} | } |
virtual EntityRef spawn(const Context &ctx, const Entity::PackObject &obj) = 0; | virtual EntityRef spawn(const Context &ctx, const Entity::PackObject &obj) = 0; | ||||
virtual void update(const Context &ctx, float dt) = 0; | virtual void update(const Context &ctx, float dt) = 0; | ||||
virtual void tick(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; | virtual void erase(size_t idx, size_t generation) = 0; | ||||
private: | private: | ||||
EntityRef spawn(const Context &ctx, const Entity::PackObject &obj) override; | EntityRef spawn(const Context &ctx, const Entity::PackObject &obj) override; | ||||
void update(const Context &ctx, float dt) override; | void update(const Context &ctx, float dt) override; | ||||
void tick(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; | void erase(size_t idx, size_t generation) override; | ||||
private: | private: | ||||
} | } | ||||
template<typename Ent> | 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()); | ZoneScopedN(typeid(Ent).name()); | ||||
for (auto &ent: entities_) { | for (auto &ent: entities_) { | ||||
ZoneScopedN("draw"); | ZoneScopedN("draw"); | ||||
ent->draw(ctx, win); | |||||
ent->draw(ctx, rnd); | |||||
} | } | ||||
} | } | ||||
virtual ~Entity() = default; | 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 update(const Context &ctx, float dt) {} | ||||
virtual void tick(const Context &ctx, float dt) {} | virtual void tick(const Context &ctx, float dt) {} | ||||
virtual void onDespawn(const Context &ctx) {} | virtual void onDespawn(const Context &ctx) {} |
#include <string> | #include <string> | ||||
#include <optional> | #include <optional> | ||||
#include <SDL.h> | #include <SDL.h> | ||||
#include <cygnet/Renderer.h> | |||||
#include <cygnet/util.h> | |||||
#include "common.h" | #include "common.h" | ||||
#include "Resource.h" | |||||
#include "Mod.h" | #include "Mod.h" | ||||
#include "World.h" | #include "World.h" | ||||
class Game { | class Game { | ||||
public: | 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) { | void onKeyDown(SDL_Keysym sym) { | ||||
pressedKeys_[sym.scancode] = true; | pressedKeys_[sym.scancode] = true; | ||||
} | } | ||||
void onMouseMove(Sint32 x, Sint32 y) { | 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) { | void onMouseDown(Sint32 x, Sint32 y, Uint8 button) { | ||||
mousePos_ = { x, y }; | |||||
onMouseMove(x, y); | |||||
pressedButtons_[button] = true; | pressedButtons_[button] = true; | ||||
didPressButtons_[button] = true; | didPressButtons_[button] = true; | ||||
} | |||||
} | |||||
void onMouseUp(Sint32 x, Sint32 y, Uint8 button) { | void onMouseUp(Sint32 x, Sint32 y, Uint8 button) { | ||||
mousePos_ = { x, y }; | |||||
onMouseMove(x, y); | |||||
pressedButtons_[button] = false; | pressedButtons_[button] = false; | ||||
didReleaseButtons_[button] = true; | didReleaseButtons_[button] = true; | ||||
} | } | ||||
bool isKeyPressed(SDL_Scancode code) { return pressedKeys_[code]; } | bool isKeyPressed(SDL_Scancode code) { return pressedKeys_[code]; } | ||||
bool wasKeyPressed(SDL_Scancode code) { return didPressKeys_[code]; } | bool wasKeyPressed(SDL_Scancode code) { return didPressKeys_[code]; } | ||||
bool wasKeyReleased(SDL_Scancode code) { return didReleaseKeys_[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 isMousePressed(Uint8 button) { return pressedButtons_[button]; } | ||||
bool wasMousePressed(Uint8 button) { return didPressButtons_[button]; } | bool wasMousePressed(Uint8 button) { return didPressButtons_[button]; } | ||||
bool wasMouseReleased(Uint8 button) { return didReleaseButtons_[button]; } | bool wasMouseReleased(Uint8 button) { return didReleaseButtons_[button]; } | ||||
TilePos getMouseTile(); | TilePos getMouseTile(); | ||||
SDL_Color backgroundColor(); | |||||
Cygnet::Color backgroundColor(); | |||||
void draw(); | void draw(); | ||||
void update(float dt); | void update(float dt); | ||||
void tick(float dt); | void tick(float dt); | ||||
std::unique_ptr<World> world_ = NULL; | 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: | private: | ||||
std::bitset<SDL_NUM_SCANCODES> pressedKeys_; | std::bitset<SDL_NUM_SCANCODES> pressedKeys_; | ||||
std::bitset<SDL_NUM_SCANCODES> didPressKeys_; | std::bitset<SDL_NUM_SCANCODES> didPressKeys_; | ||||
std::bitset<SDL_NUM_SCANCODES> didReleaseKeys_; | std::bitset<SDL_NUM_SCANCODES> didReleaseKeys_; | ||||
Vec2i mousePos_; | |||||
Vec2 mousePos_; | |||||
std::bitset<SDL_BUTTON_X2> pressedButtons_; | std::bitset<SDL_BUTTON_X2> pressedButtons_; | ||||
std::bitset<SDL_BUTTON_X2> didPressButtons_; | std::bitset<SDL_BUTTON_X2> didPressButtons_; | ||||
std::bitset<SDL_BUTTON_X2> didReleaseButtons_; | std::bitset<SDL_BUTTON_X2> didReleaseButtons_; |
#include <string> | #include <string> | ||||
#include "Resource.h" | |||||
#include "Tile.h" | |||||
namespace Swan { | namespace Swan { | ||||
int maxStack = 64; | 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 std::string name; | ||||
const ImageResource ℑ | |||||
const int maxStack; | 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) {} | |||||
}; | }; | ||||
} | } |
void onLightRemoved(TilePos pos, float level); | void onLightRemoved(TilePos pos, float level); | ||||
void onChunkAdded(ChunkPos pos, NewLightChunk &&chunk); | void onChunkAdded(ChunkPos pos, NewLightChunk &&chunk); | ||||
void onChunkRemoved(ChunkPos pos); | void onChunkRemoved(ChunkPos pos); | ||||
void flip(); | |||||
private: | private: | ||||
static constexpr int LIGHT_CUTOFF_DIST = 64; | static constexpr int LIGHT_CUTOFF_DIST = 64; | ||||
void processEvent(const Event &event, std::vector<NewLightChunk> &newChunks); | void processEvent(const Event &event, std::vector<NewLightChunk> &newChunks); | ||||
void run(); | void run(); | ||||
LightCallback &cb_; | |||||
bool running_ = true; | bool running_ = true; | ||||
std::map<std::pair<int, int>, LightChunk> chunks_; | std::map<std::pair<int, int>, LightChunk> chunks_; | ||||
std::set<std::pair<int, int>> updatedChunks_; | std::set<std::pair<int, int>> updatedChunks_; | ||||
int buffer_ = 0; | int buffer_ = 0; | ||||
std::vector<Event> buffers_[2] = { {}, {} }; | std::vector<Event> buffers_[2] = { {}, {} }; | ||||
std::vector<NewLightChunk> newChunkBuffers_[2] = { {}, {} }; | std::vector<NewLightChunk> newChunkBuffers_[2] = { {}, {} }; | ||||
std::thread thread_; | |||||
std::condition_variable cond_; | std::condition_variable cond_; | ||||
std::mutex mut_; | std::mutex mut_; | ||||
LightCallback &cb_; | |||||
std::thread thread_; | |||||
}; | }; | ||||
inline void LightServer::onSolidBlockAdded(TilePos pos) { | inline void LightServer::onSolidBlockAdded(TilePos pos) { | ||||
std::lock_guard<std::mutex> lock(mut_); | std::lock_guard<std::mutex> lock(mut_); | ||||
buffers_[buffer_].push_back({ Event::Tag::BLOCK_ADDED, pos, { .i = 0 } }); | buffers_[buffer_].push_back({ Event::Tag::BLOCK_ADDED, pos, { .i = 0 } }); | ||||
cond_.notify_one(); | |||||
} | } | ||||
inline void LightServer::onSolidBlockRemoved(TilePos pos) { | inline void LightServer::onSolidBlockRemoved(TilePos pos) { | ||||
std::lock_guard<std::mutex> lock(mut_); | std::lock_guard<std::mutex> lock(mut_); | ||||
buffers_[buffer_].push_back({ Event::Tag::BLOCK_REMOVED, pos, { .i = 0 } }); | buffers_[buffer_].push_back({ Event::Tag::BLOCK_REMOVED, pos, { .i = 0 } }); | ||||
cond_.notify_one(); | |||||
} | } | ||||
inline void LightServer::onLightAdded(TilePos pos, float level) { | inline void LightServer::onLightAdded(TilePos pos, float level) { | ||||
std::lock_guard<std::mutex> lock(mut_); | std::lock_guard<std::mutex> lock(mut_); | ||||
buffers_[buffer_].push_back({ Event::Tag::LIGHT_ADDED, pos, { .f = level } }); | buffers_[buffer_].push_back({ Event::Tag::LIGHT_ADDED, pos, { .f = level } }); | ||||
cond_.notify_one(); | |||||
} | } | ||||
inline void LightServer::onLightRemoved(TilePos pos, float level) { | inline void LightServer::onLightRemoved(TilePos pos, float level) { | ||||
std::lock_guard<std::mutex> lock(mut_); | std::lock_guard<std::mutex> lock(mut_); | ||||
buffers_[buffer_].push_back({ Event::Tag::LIGHT_REMOVED, pos, { .f = level } }); | buffers_[buffer_].push_back({ Event::Tag::LIGHT_REMOVED, pos, { .f = level } }); | ||||
cond_.notify_one(); | |||||
} | } | ||||
inline void LightServer::onChunkAdded(Vec2i pos, NewLightChunk &&chunk) { | inline void LightServer::onChunkAdded(Vec2i pos, NewLightChunk &&chunk) { | ||||
buffers_[buffer_].push_back({ Event::Tag::CHUNK_ADDED, pos, | buffers_[buffer_].push_back({ Event::Tag::CHUNK_ADDED, pos, | ||||
{ .i = (int)newChunkBuffers_[buffer_].size() } }); | { .i = (int)newChunkBuffers_[buffer_].size() } }); | ||||
newChunkBuffers_[buffer_].push_back(std::move(chunk)); | newChunkBuffers_[buffer_].push_back(std::move(chunk)); | ||||
cond_.notify_one(); | |||||
} | } | ||||
inline void LightServer::onChunkRemoved(Vec2i pos) { | inline void LightServer::onChunkRemoved(Vec2i pos) { | ||||
std::lock_guard<std::mutex> lock(mut_); | std::lock_guard<std::mutex> lock(mut_); | ||||
buffers_[buffer_].push_back({ Event::Tag::CHUNK_REMOVED, pos, { .i = 0 } }); | buffers_[buffer_].push_back({ Event::Tag::CHUNK_REMOVED, pos, { .i = 0 } }); | ||||
} | |||||
inline void LightServer::flip() { | |||||
cond_.notify_one(); | cond_.notify_one(); | ||||
} | } | ||||
#include "WorldGen.h" | #include "WorldGen.h" | ||||
#include "Entity.h" | #include "Entity.h" | ||||
#include "Collection.h" | #include "Collection.h" | ||||
#include "Resource.h" | |||||
#include "OS.h" | #include "OS.h" | ||||
#include "util.h" | #include "util.h" | ||||
namespace Swan { | namespace Swan { | ||||
class ModWrapper; | |||||
class Mod { | class Mod { | ||||
public: | public: | ||||
Mod(std::string name): name_(std::move(name)) {} | Mod(std::string name): name_(std::move(name)) {} | ||||
virtual ~Mod() = default; | 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> | 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> { | .create = [](World &world) -> std::unique_ptr<WorldGen> { | ||||
return std::make_unique<WG>(world); | return std::make_unique<WG>(world); | ||||
} | } | ||||
std::is_move_constructible_v<Ent>, | std::is_move_constructible_v<Ent>, | ||||
"Entities must be movable"); | "Entities must be movable"); | ||||
entities_.push_back(EntityCollection::Factory{ | entities_.push_back(EntityCollection::Factory{ | ||||
.name = name_ + "::" + name, | |||||
.name = name, | |||||
.create = [](std::string name) -> std::unique_ptr<EntityCollection> { | .create = [](std::string name) -> std::unique_ptr<EntityCollection> { | ||||
return std::make_unique<EntityCollectionImpl<Ent>>(std::move(name)); | return std::make_unique<EntityCollectionImpl<Ent>>(std::move(name)); | ||||
} | } | ||||
}); | }); | ||||
} | } | ||||
private: | |||||
const std::string name_; | const std::string name_; | ||||
std::vector<std::string> images_; | |||||
std::vector<Tile::Builder> tiles_; | std::vector<Tile::Builder> tiles_; | ||||
std::vector<Item::Builder> items_; | 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_; | std::vector<EntityCollection::Factory> entities_; | ||||
friend ModWrapper; | |||||
}; | }; | ||||
class ModWrapper { | class ModWrapper { | ||||
mod_.reset(); | 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::unique_ptr<Mod> mod_; | ||||
std::string path_; | std::string path_; |
#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_; | |||||
}; | |||||
} |
#include <stdint.h> | #include <stdint.h> | ||||
#include <string> | #include <string> | ||||
#include <optional> | #include <optional> | ||||
#include <memory> | |||||
#include "Item.h" | |||||
#include "Resource.h" | |||||
namespace Swan { | namespace Swan { | ||||
std::optional<std::string> droppedItem = std::nullopt; | 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 std::string name; | ||||
const ImageResource ℑ | |||||
const bool isSolid; | const bool isSolid; | ||||
const float lightLevel; | const float lightLevel; | ||||
const std::optional<std::string> droppedItem; | 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) {} | |||||
}; | }; | ||||
} | } |
#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_; | |||||
}; | |||||
} |
#include <string> | #include <string> | ||||
#include <random> | #include <random> | ||||
#include <SDL.h> | #include <SDL.h> | ||||
#include <cygnet/Renderer.h> | |||||
#include <cygnet/ResourceManager.h> | |||||
#include <cygnet/util.h> | |||||
#include "common.h" | #include "common.h" | ||||
#include "Item.h" | #include "Item.h" | ||||
#include "WorldGen.h" | #include "WorldGen.h" | ||||
#include "Entity.h" | #include "Entity.h" | ||||
#include "Collection.h" | #include "Collection.h" | ||||
#include "Resource.h" | |||||
#include "Mod.h" | #include "Mod.h" | ||||
#include "EventEmitter.h" | #include "EventEmitter.h" | ||||
class World { | class World { | ||||
public: | 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 setWorldGen(std::string gen); | ||||
void spawnPlayer(); | void spawnPlayer(); | ||||
WorldPlane &addPlane(const std::string &gen); | WorldPlane &addPlane(const std::string &gen); | ||||
WorldPlane &addPlane() { return addPlane(defaultWorldGen_); } | 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::ID getTileID(const std::string &name); | ||||
Tile &getTile(const std::string &name); | Tile &getTile(const std::string &name); | ||||
Item &getItem(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 update(float dt); | ||||
void tick(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, 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_; | BodyTrait::Body *player_; | ||||
Game *game_; | |||||
std::mt19937 random_; | |||||
ResourceManager resources_; | |||||
private: | private: | ||||
class ChunkRenderer { | class ChunkRenderer { | ||||
void tick(WorldPlane &plane, ChunkPos abspos); | void tick(WorldPlane &plane, ChunkPos abspos); | ||||
}; | }; | ||||
std::vector<ModWrapper> loadMods(std::vector<std::string> paths); | |||||
Cygnet::ResourceManager buildResources(); | |||||
ChunkRenderer chunkRenderer_; | ChunkRenderer chunkRenderer_; | ||||
WorldPlane::ID currentPlane_; | WorldPlane::ID currentPlane_; | ||||
std::vector<std::unique_ptr<WorldPlane>> planes_; | std::vector<std::unique_ptr<WorldPlane>> planes_; |
#include <memory> | #include <memory> | ||||
#include <SDL.h> | #include <SDL.h> | ||||
#include <cygnet/util.h> | |||||
#include "common.h" | #include "common.h" | ||||
#include "Chunk.h" | #include "Chunk.h" | ||||
class World; | class World; | ||||
class WorldPlane; | class WorldPlane; | ||||
class ImageResource; | |||||
class WorldGen { | class WorldGen { | ||||
public: | public: | ||||
virtual ~WorldGen() = default; | 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 void genChunk(WorldPlane &plane, Chunk &chunk) = 0; | ||||
virtual EntityRef spawnPlayer(const Context &ctx) = 0; | virtual EntityRef spawnPlayer(const Context &ctx) = 0; |
EntityRef spawnPlayer(); | EntityRef spawnPlayer(); | ||||
void breakTile(TilePos pos); | void breakTile(TilePos pos); | ||||
SDL_Color backgroundColor(); | |||||
void draw(Win &win); | |||||
Cygnet::Color backgroundColor(); | |||||
void draw(Cygnet::Renderer &rnd); | |||||
void update(float dt); | void update(float dt); | ||||
void tick(float dt); | void tick(float dt); | ||||
std::mutex mut_; | std::mutex mut_; | ||||
private: | private: | ||||
std::unique_ptr<LightServer> lighting_; | |||||
std::map<std::pair<int, int>, Chunk> chunks_; | std::map<std::pair<int, int>, Chunk> chunks_; | ||||
std::vector<Chunk *> activeChunks_; | std::vector<Chunk *> activeChunks_; | ||||
std::vector<std::pair<ChunkPos, Chunk *>> tickChunks_; | std::vector<std::pair<ChunkPos, Chunk *>> tickChunks_; | ||||
std::deque<Chunk *> chunkInitList_; | std::deque<Chunk *> chunkInitList_; | ||||
std::vector<TilePos> debugBoxes_; | 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_; | |||||
}; | }; | ||||
/* | /* |
#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); | |||||
} |
#include <swan-common/Vector2.h> | #include <swan-common/Vector2.h> | ||||
#include <swan-common/constants.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 { | namespace Swan { | ||||
using namespace SwanCommon; | using namespace SwanCommon; | ||||
class Game; | class Game; | ||||
class World; | class World; | ||||
class WorldPlane; | class WorldPlane; | ||||
class Win; | |||||
class ResourceManager; | |||||
struct Context { | struct Context { | ||||
Game &game; | Game &game; | ||||
World &world; | World &world; | ||||
WorldPlane &plane; | WorldPlane &plane; | ||||
ResourceManager &resources; | |||||
}; | }; | ||||
} | } |
#include <optional> | #include <optional> | ||||
#include <initializer_list> | #include <initializer_list> | ||||
#include <utility> | #include <utility> | ||||
#include "Win.h" | |||||
#include <cygnet/util.h> | |||||
namespace Swan { | namespace Swan { | ||||
namespace Draw { | 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( | void parallaxBackground( | ||||
Win &win, SDL_Texture *tex, | Win &win, SDL_Texture *tex, | ||||
std::optional<SDL_Rect> srcrect, std::optional<SDL_Rect> destrect, | std::optional<SDL_Rect> srcrect, std::optional<SDL_Rect> destrect, | ||||
float x, float y, float factor); | float x, float y, float factor); | ||||
TODO */ | |||||
} | } | ||||
} | } |
#include <swan/ItemStack.h> | #include <swan/ItemStack.h> | ||||
#include <swan/Mod.h> | #include <swan/Mod.h> | ||||
#include <swan/OS.h> | #include <swan/OS.h> | ||||
#include <swan/Resource.h> | |||||
#include <swan/SlotVector.h> | #include <swan/SlotVector.h> | ||||
#include <swan/Tile.h> | #include <swan/Tile.h> | ||||
#include <swan/Win.h> | |||||
#include <swan/World.h> | #include <swan/World.h> | ||||
#include <swan/WorldGen.h> | #include <swan/WorldGen.h> | ||||
#include <swan/WorldPlane.h> | #include <swan/WorldPlane.h> |
namespace Swan { | namespace Swan { | ||||
class Win; | |||||
struct BodyTrait { | struct BodyTrait { | ||||
struct Body; | struct Body; | ||||
struct Tag {}; | struct Tag {}; | ||||
Vec2 midRight() { return { right(), midY() }; } | Vec2 midRight() { return { right(), midY() }; } | ||||
Vec2 bottomRight() { return { right(), bottom() }; } | Vec2 bottomRight() { return { right(), bottom() }; } | ||||
void outline(Win &win); | |||||
//void outline(Win &win); TODO | |||||
}; | }; | ||||
}; | }; | ||||
#pragma once | |||||
#pragma once | |||||
#include <optional> | #include <optional> | ||||
#include <functional> | #include <functional> | ||||
#include <memory> | #include <memory> | ||||
#include <chrono> | #include <chrono> | ||||
#include <type_traits> | #include <type_traits> | ||||
#include <string> | |||||
#include <stddef.h> | #include <stddef.h> | ||||
namespace Swan { | namespace Swan { | ||||
~Deferred() { Func(); } | ~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... | // Calling begin/end is stupid... | ||||
template<typename T> | template<typename T> | ||||
auto callBegin(T &v) { | auto callBegin(T &v) { |
#include "Animation.h" | #include "Animation.h" | ||||
#include "Win.h" | |||||
#include "gfxutil.h" | |||||
#include <cygnet/Renderer.h> | |||||
namespace Swan { | namespace Swan { | ||||
timer_ += interval_; | timer_ += interval_; | ||||
frame_ += 1; | frame_ += 1; | ||||
if (frame_ >= resource_.numFrames_) | |||||
if (frame_ >= sprite_.frameCount) { | |||||
frame_ = 0; | 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() { | void Animation::reset() { | ||||
timer_ = interval_; | timer_ = interval_; | ||||
frame_ = 0; | frame_ = 0; |
#include "gfxutil.h" | #include "gfxutil.h" | ||||
#include "World.h" | #include "World.h" | ||||
#include "Game.h" | #include "Game.h" | ||||
#include "Win.h" | |||||
namespace Swan { | namespace Swan { | ||||
void Chunk::compress() { | |||||
void Chunk::compress(Cygnet::Renderer &rnd) { | |||||
if (isCompressed()) | if (isCompressed()) | ||||
return; | return; | ||||
data_.reset(new uint8_t[destlen]); | data_.reset(new uint8_t[destlen]); | ||||
memcpy(data_.get(), dest, destlen); | memcpy(data_.get(), dest, destlen); | ||||
texture_.reset(); | |||||
compressedSize_ = destlen; | compressedSize_ = destlen; | ||||
info | info | ||||
} else { | } else { | ||||
warn << "Chunk compression error: " << ret << " (Out of memory?)"; | warn << "Chunk compression error: " << ret << " (Out of memory?)"; | ||||
} | } | ||||
rnd.destroyChunk(renderChunk_); | |||||
rnd.destroyChunkShadow(renderChunkShadow_); | |||||
} | } | ||||
void Chunk::decompress() { | void Chunk::decompress() { | ||||
} | } | ||||
data_ = std::move(dest); | data_ = std::move(dest); | ||||
needRender_ = true; | |||||
info | info | ||||
<< "Decompressed chunk " << pos_ << " from " | << "Decompressed chunk " << pos_ << " from " | ||||
<< compressedSize_ << " bytes to " | << compressedSize_ << " bytes to " | ||||
<< DATA_SIZE << " bytes."; | << DATA_SIZE << " bytes."; | ||||
compressedSize_ = -1; | 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()) | if (isCompressed()) | ||||
return; | 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) { | Chunk::TickAction Chunk::tick(float dt) { |
#include "log.h" | #include "log.h" | ||||
#include "Tile.h" | #include "Tile.h" | ||||
#include "OS.h" | #include "OS.h" | ||||
#include "Win.h" | |||||
namespace Swan { | 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_->setWorldGen(worldgen); | ||||
world_->setCurrentPlane(world_->addPlane()); | world_->setCurrentPlane(world_->addPlane()); | ||||
} | } | ||||
TilePos Game::getMouseTile() { | 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(); | return world_->backgroundColor(); | ||||
} | } | ||||
void Game::draw() { | void Game::draw() { | ||||
world_->draw(win_); | |||||
world_->draw(renderer_); | |||||
renderer_.draw(cam_); | |||||
} | } | ||||
void Game::update(float dt) { | void Game::update(float dt) { | ||||
world_->update(dt); | |||||
// Zoom the window using the scroll wheel | // 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; | didScroll_ = 0; | ||||
didPressKeys_.reset(); | didPressKeys_.reset(); |
#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", | |||||
}); | |||||
} | |||||
} |
break; | break; | ||||
case Event::Tag::LIGHT_ADDED: | case Event::Tag::LIGHT_ADDED: | ||||
info << cpos << ": Add " << evt.f << " light to " << rpos; | |||||
ch->lightSources[rpos] += evt.f; | ch->lightSources[rpos] += evt.f; | ||||
markChunksModified(cpos, rpos, ch->lightSources[rpos]); | markChunksModified(cpos, rpos, ch->lightSources[rpos]); | ||||
break; | break; | ||||
case Event::Tag::LIGHT_REMOVED: | case Event::Tag::LIGHT_REMOVED: | ||||
info << cpos << ": Remove " << evt.f << " light to " << rpos; | |||||
markChunksModified(cpos, rpos, ch->lightSources[rpos]); | markChunksModified(cpos, rpos, ch->lightSources[rpos]); | ||||
ch->lightSources[rpos] -= evt.f; | ch->lightSources[rpos] -= evt.f; | ||||
if (ch->lightSources[rpos] < LIGHT_CUTOFF) { | if (ch->lightSources[rpos] < LIGHT_CUTOFF) { | ||||
buf.clear(); | buf.clear(); | ||||
newChunks.clear(); | newChunks.clear(); | ||||
auto start = std::chrono::steady_clock::now(); | |||||
for (auto &pos: updatedChunks_) { | for (auto &pos: updatedChunks_) { | ||||
auto ch = chunks_.find(pos); | auto ch = chunks_.find(pos); | ||||
if (ch != chunks_.end()) { | if (ch != chunks_.end()) { | ||||
} | } | ||||
} | } | ||||
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_) { | for (auto &pos: updatedChunks_) { | ||||
auto ch = chunks_.find(pos); | auto ch = chunks_.find(pos); | ||||
if (ch != chunks_.end()) { | if (ch != chunks_.end()) { |
#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; | |||||
}); | |||||
} | |||||
} |
#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; | |||||
} | |||||
} |
#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, | |||||
}); | |||||
} | |||||
} |
#include "World.h" | #include "World.h" | ||||
#include <algorithm> | #include <algorithm> | ||||
#include <tuple> | |||||
#include "log.h" | #include "log.h" | ||||
#include "Game.h" | #include "Game.h" | ||||
#include "Win.h" | |||||
#include "Clock.h" | #include "Clock.h" | ||||
#include "assets.h" | |||||
namespace Swan { | namespace Swan { | ||||
} | } | ||||
} | } | ||||
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) { | void World::ChunkRenderer::tick(WorldPlane &plane, ChunkPos abspos) { | ||||
ZoneScopedN("World::ChunkRenderer tick"); | ZoneScopedN("World::ChunkRenderer tick"); | ||||
int l = 0; | int l = 0; | ||||
} | } | ||||
} | } | ||||
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) { | void World::setWorldGen(std::string gen) { | ||||
defaultWorldGen_ = std::move(gen); | defaultWorldGen_ = std::move(gen); | ||||
} | } | ||||
WorldPlane &World::addPlane(const std::string &gen) { | WorldPlane &World::addPlane(const std::string &gen) { | ||||
WorldPlane::ID id = planes_.size(); | 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(); | abort(); | ||||
} | } | ||||
std::vector<std::unique_ptr<EntityCollection>> colls; | std::vector<std::unique_ptr<EntityCollection>> colls; | ||||
colls.reserve(entCollFactories_.size()); | colls.reserve(entCollFactories_.size()); | ||||
for (auto &fact: entCollFactories_) { | 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; | WorldGen::Factory &factory = it->second; | ||||
return *planes_[id]; | 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) { | Tile::ID World::getTileID(const std::string &name) { | ||||
auto iter = tilesMap_.find(name); | auto iter = tilesMap_.find(name); | ||||
if (iter == tilesMap_.end()) { | 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; | return iter->second; | ||||
return getTileByID(id); | 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(); | return planes_[currentPlane_]->backgroundColor(); | ||||
} | } | ||||
void World::draw(Win &win) { | |||||
void World::draw(Cygnet::Renderer &rnd) { | |||||
ZoneScopedN("World draw"); | 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) { | void World::update(float dt) { | ||||
ZoneScopedN("World update"); | ZoneScopedN("World update"); | ||||
for (auto &plane: planes_) | for (auto &plane: planes_) | ||||
plane->update(dt); | plane->update(dt); | ||||
game_->cam_.pos = player_->pos + player_->size / 2; | |||||
} | } | ||||
void World::tick(float dt) { | void World::tick(float dt) { |
#include "World.h" | #include "World.h" | ||||
#include "Game.h" | #include "Game.h" | ||||
#include "Clock.h" | #include "Clock.h" | ||||
#include "Win.h" | |||||
namespace Swan { | namespace Swan { | ||||
.game = *world_->game_, | .game = *world_->game_, | ||||
.world = *world_, | .world = *world_, | ||||
.plane = *this, | .plane = *this, | ||||
.resources = world_->resources_ | |||||
}; | }; | ||||
} | } | ||||
ID id, World *world, std::unique_ptr<WorldGen> gen, | ID id, World *world, std::unique_ptr<WorldGen> gen, | ||||
std::vector<std::unique_ptr<EntityCollection>> &&colls): | std::vector<std::unique_ptr<EntityCollection>> &&colls): | ||||
id_(id), world_(world), gen_(std::move(gen)), | 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_) { | for (auto &coll: entColls_) { | ||||
entCollsByType_[coll->type()] = coll.get(); | entCollsByType_[coll->type()] = coll.get(); | ||||
} | } | ||||
Chunk &chunk = slowGetChunk(pos); | Chunk &chunk = slowGetChunk(pos); | ||||
tickChunks_.push_back({ pos, &chunk }); | |||||
tickChunks_.push_back({pos, &chunk}); | |||||
return chunk; | return chunk; | ||||
} | } | ||||
lc.blocks[y * CHUNK_HEIGHT + x] = true; | lc.blocks[y * CHUNK_HEIGHT + x] = true; | ||||
} | } | ||||
if (tile.lightLevel > 0) { | if (tile.lightLevel > 0) { | ||||
lc.lightSources[{ x, y }] = tile.lightLevel; | |||||
lc.lightSources[{x, y}] = tile.lightLevel; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
if (id != old) { | if (id != old) { | ||||
Tile &newTile = world_->getTileByID(id); | Tile &newTile = world_->getTileByID(id); | ||||
Tile &oldTile = world_->getTileByID(old); | Tile &oldTile = world_->getTileByID(old); | ||||
chunk.setTileID(rp, id, newTile.image.texture_.get()); | |||||
chunk.markModified(); | |||||
chunk.setTileID(rp, id); | |||||
if (!oldTile.isSolid && newTile.isSolid) { | if (!oldTile.isSolid && newTile.isSolid) { | ||||
lighting_->onSolidBlockAdded(pos); | lighting_->onSolidBlockAdded(pos); | ||||
world_->evtTileBreak_.emit(getContext(), pos, world_->getTileByID(id)); | world_->evtTileBreak_.emit(getContext(), pos, world_->getTileByID(id)); | ||||
} | } | ||||
SDL_Color WorldPlane::backgroundColor() { | |||||
Cygnet::Color WorldPlane::backgroundColor() { | |||||
return gen_->backgroundColor(world_->player_->pos); | return gen_->backgroundColor(world_->player_->pos); | ||||
} | } | ||||
void WorldPlane::draw(Win &win) { | |||||
void WorldPlane::draw(Cygnet::Renderer &rnd) { | |||||
ZoneScopedN("WorldPlane draw"); | ZoneScopedN("WorldPlane draw"); | ||||
std::lock_guard<std::mutex> lock(mut_); | std::lock_guard<std::mutex> lock(mut_); | ||||
auto ctx = getContext(); | auto ctx = getContext(); | ||||
auto &pbody = *(world_->player_); | auto &pbody = *(world_->player_); | ||||
gen_->drawBackground(ctx, win, pbody.pos); | |||||
gen_->drawBackground(ctx, rnd, pbody.pos); | |||||
ChunkPos pcpos = ChunkPos( | ChunkPos pcpos = ChunkPos( | ||||
(int)floor(pbody.pos.x / CHUNK_WIDTH), | (int)floor(pbody.pos.x / CHUNK_WIDTH), | ||||
// Just init one chunk per frame | // Just init one chunk per frame | ||||
if (chunkInitList_.size() > 0) { | if (chunkInitList_.size() > 0) { | ||||
/* | |||||
Chunk *chunk = chunkInitList_.front(); | Chunk *chunk = chunkInitList_.front(); | ||||
chunkInitList_.pop_front(); | chunkInitList_.pop_front(); | ||||
chunk->render(ctx, win.renderer_); | chunk->render(ctx, win.renderer_); | ||||
TODO */ | |||||
} | } | ||||
for (int x = -1; x <= 1; ++x) { | for (int x = -1; x <= 1; ++x) { | ||||
for (int y = -1; y <= 1; ++y) { | for (int y = -1; y <= 1; ++y) { | ||||
auto iter = chunks_.find(pcpos + ChunkPos(x, y)); | auto iter = chunks_.find(pcpos + ChunkPos(x, y)); | ||||
if (iter != chunks_.end()) | 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) { | if (debugBoxes_.size() > 0) { | ||||
for (auto &pos: debugBoxes_) { | for (auto &pos: debugBoxes_) { | ||||
win.drawRect(pos, Vec2(1, 1)); | |||||
rnd.drawRect(pos, Vec2(1, 1)); | |||||
} | } | ||||
} | } | ||||
TODO */ | |||||
} | } | ||||
void WorldPlane::update(float dt) { | void WorldPlane::update(float dt) { | ||||
switch (action) { | switch (action) { | ||||
case Chunk::TickAction::DEACTIVATE: | case Chunk::TickAction::DEACTIVATE: | ||||
info << "Compressing inactive modified chunk " << chunk->pos_; | info << "Compressing inactive modified chunk " << chunk->pos_; | ||||
chunk->compress(); | |||||
chunk->compress(world_->game_->renderer_); | |||||
iter = activeChunks_.erase(iter); | iter = activeChunks_.erase(iter); | ||||
last = activeChunks_.end(); | last = activeChunks_.end(); | ||||
break; | break; | ||||
case Chunk::TickAction::DELETE: | case Chunk::TickAction::DELETE: | ||||
info << "Deleting inactive unmodified chunk " << chunk->pos_; | info << "Deleting inactive unmodified chunk " << chunk->pos_; | ||||
chunk->destroy(world_->game_->renderer_); | |||||
chunks_.erase(chunk->pos_); | chunks_.erase(chunk->pos_); | ||||
iter = activeChunks_.erase(iter); | iter = activeChunks_.erase(iter); | ||||
last = activeChunks_.end(); | last = activeChunks_.end(); |
#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)}; | |||||
} | |||||
} |
namespace Swan { | namespace Swan { | ||||
namespace Draw { | 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 { | return { | ||||
.r = linearLine(from.r, to.r, frac), | .r = linearLine(from.r, to.r, frac), | ||||
.g = linearLine(from.g, to.g, frac), | .g = linearLine(from.g, to.g, frac), | ||||
}; | }; | ||||
} | } | ||||
SDL_Color linearGradient( | |||||
Cygnet::Color linearGradient( | |||||
float val, | 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(); | size_t size = colors.size(); | ||||
if (val < arr[0].first) | if (val < arr[0].first) | ||||
return arr[size - 1].second; | return arr[size - 1].second; | ||||
} | } | ||||
/* | |||||
void parallaxBackground( | void parallaxBackground( | ||||
Win &win, SDL_Texture *tex, | Win &win, SDL_Texture *tex, | ||||
std::optional<SDL_Rect> srcrect, std::optional<SDL_Rect> destrect, | std::optional<SDL_Rect> srcrect, std::optional<SDL_Rect> destrect, | ||||
} | } | ||||
} | } | ||||
} | } | ||||
TODO */ | |||||
} | } | ||||
} | } |
#include "traits/BodyTrait.h" | #include "traits/BodyTrait.h" | ||||
#include "Win.h" | |||||
namespace Swan { | namespace Swan { | ||||
/* | |||||
void BodyTrait::Body::outline(Win &win) { | void BodyTrait::Body::outline(Win &win) { | ||||
win.drawRect(pos, size); | win.drawRect(pos, size); | ||||
} | |||||
} TODO */ | |||||
} | } |
#include "traits/PhysicsTrait.h" | #include "traits/PhysicsTrait.h" | ||||
#include "WorldPlane.h" | #include "WorldPlane.h" | ||||
#include "Win.h" | |||||
namespace Swan { | namespace Swan { | ||||
static float epsilon = 0.001; | |||||
static float epsilon = 0.05; | |||||
static void collideX( | static void collideX( | ||||
PhysicsTrait::Physics &phys, BodyTrait::Body &body, | PhysicsTrait::Physics &phys, BodyTrait::Body &body, | ||||
bool collided = false; | bool collided = false; | ||||
for (int y = (int)floor(body.top() + epsilon); y <= (int)floor(body.bottom() - epsilon); ++y) { | 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) { | if (left.isSolid) { | ||||
body.pos.x = (float)lx + 1.0; | body.pos.x = (float)lx + 1.0; | ||||
collided = true; | collided = true; | ||||
break; | 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) { | if (right.isSolid) { | ||||
body.pos.x = (float)rx - body.size.x; | body.pos.x = (float)rx - body.size.x; | ||||
collided = true; | collided = true; | ||||
phys.onGround = false; | phys.onGround = false; | ||||
for (int x = (int)floor(body.left() + epsilon); x <= (int)floor(body.right() - epsilon); ++x) { | 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) { | if (top.isSolid) { | ||||
body.pos.y = (float)ty + 1.0; | body.pos.y = (float)ty + 1.0; | ||||
collided = true; | collided = true; | ||||
break; | 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) { | if (bottom.isSolid) { | ||||
body.pos.y = (float)by - body.size.y; | body.pos.y = (float)by - body.size.y; | ||||
collided = true; | collided = true; |
#include <cygnet/Context.h> | |||||
#include <cygnet/Window.h> | #include <cygnet/Window.h> | ||||
#include <cygnet/Renderer.h> | #include <cygnet/Renderer.h> | ||||
#include <cygnet/ResourceManager.h> | #include <cygnet/ResourceManager.h> | ||||
} | } | ||||
int main() { | int main() { | ||||
Cygnet::Context ctx; | |||||
SDL_Init(SDL_INIT_VIDEO); | |||||
IMG_Init(IMG_INIT_PNG); | IMG_Init(IMG_INIT_PNG); | ||||
Cygnet::Window win("Cygnet Test", 680, 680); | Cygnet::Window win("Cygnet Test", 680, 680); | ||||
Cygnet::Renderer rnd; | Cygnet::Renderer rnd; | ||||
y += 1 * dt; | 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(); | win.clear(); | ||||
rnd.draw(cam); | rnd.draw(cam); | ||||
exit: | exit: | ||||
IMG_Quit(); | IMG_Quit(); | ||||
SDL_Quit(); | |||||
} | } |
#include <string.h> | #include <string.h> | ||||
#include <imgui.h> | #include <imgui.h> | ||||
#include <imgui_sdl/imgui_sdl.h> | #include <imgui_sdl/imgui_sdl.h> | ||||
#include <cygnet/Renderer.h> | |||||
#include <cygnet/Window.h> | |||||
#include <swan/swan.h> | #include <swan/swan.h> | ||||
imgassert(IMG_Init(imgFlags) == imgFlags, "Could not initialize SDL_Image"); | imgassert(IMG_Init(imgFlags) == imgFlags, "Could not initialize SDL_Image"); | ||||
Deferred<IMG_Quit> sdlImage; | 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 | // Load and display application icon | ||||
CPtr<SDL_Surface, SDL_FreeSurface> icon( | CPtr<SDL_Surface, SDL_FreeSurface> icon( | ||||
IMG_Load("assets/icon.png")); | IMG_Load("assets/icon.png")); | ||||
sdlassert(icon, "Could not load icon"); | 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 | // Init ImGUI and ImGUI_SDL | ||||
/* | |||||
IMGUI_CHECKVERSION(); | IMGUI_CHECKVERSION(); | ||||
CPtr<ImGuiContext, ImGui::DestroyContext> context( | CPtr<ImGuiContext, ImGui::DestroyContext> context( | ||||
ImGui::CreateContext()); | ImGui::CreateContext()); | ||||
ImGuiSDL::Initialize(renderer.get(), (int)win.getPixSize().x, (int)win.getPixSize().y); | ImGuiSDL::Initialize(renderer.get(), (int)win.getPixSize().x, (int)win.getPixSize().y); | ||||
Deferred<ImGuiSDL::Deinitialize> imguiSDL; | Deferred<ImGuiSDL::Deinitialize> imguiSDL; | ||||
info << "Initialized with window size " << win.getPixSize(); | info << "Initialized with window size " << win.getPixSize(); | ||||
// ImGuiIO is to glue SDL and ImGUI together | // ImGuiIO is to glue SDL and ImGUI together | ||||
ImGuiIO& imguiIO = ImGui::GetIO(); | ImGuiIO& imguiIO = ImGui::GetIO(); | ||||
imguiIO.BackendPlatformName = "imgui_sdl + Project: SWAN"; | imguiIO.BackendPlatformName = "imgui_sdl + Project: SWAN"; | ||||
TODO */ | |||||
// Create a world | // Create a world | ||||
Game game(win); | |||||
Game game; | |||||
game.cam_.size = window.size(); | |||||
std::vector<std::string> mods{ "core.mod" }; | std::vector<std::string> mods{ "core.mod" }; | ||||
game.createWorld("core::default", mods); | game.createWorld("core::default", mods); | ||||
int slowFrames = 0; | int slowFrames = 0; | ||||
while (1) { | while (1) { | ||||
ZoneScopedN("game loop"); | ZoneScopedN("game loop"); | ||||
RTClock totalTimeClock; | |||||
SDL_Event evt; | SDL_Event evt; | ||||
while (SDL_PollEvent(&evt)) { | while (SDL_PollEvent(&evt)) { | ||||
case SDL_WINDOWEVENT: | case SDL_WINDOWEVENT: | ||||
if (evt.window.event == SDL_WINDOWEVENT_RESIZED) { | 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; | break; | ||||
break; | break; | ||||
case SDL_MOUSEMOTION: | case SDL_MOUSEMOTION: | ||||
/* | |||||
imguiIO.MousePos.x = (float)evt.motion.x; | imguiIO.MousePos.x = (float)evt.motion.x; | ||||
imguiIO.MousePos.y = (float)evt.motion.y; | imguiIO.MousePos.y = (float)evt.motion.y; | ||||
if (!imguiIO.WantCaptureMouse) | |||||
if (!imguiIO.WantCaptureMouse) */ | |||||
game.onMouseMove(evt.motion.x, evt.motion.y); | game.onMouseMove(evt.motion.x, evt.motion.y); | ||||
break; | break; | ||||
case SDL_MOUSEBUTTONDOWN: | case SDL_MOUSEBUTTONDOWN: | ||||
/* | |||||
imguiIO.MouseDown[sdlButtonToImGuiButton(evt.button.button)] = true; | imguiIO.MouseDown[sdlButtonToImGuiButton(evt.button.button)] = true; | ||||
if (!imguiIO.WantCaptureMouse) | |||||
if (!imguiIO.WantCaptureMouse) */ | |||||
game.onMouseDown(evt.button.x, evt.button.y, evt.button.button); | game.onMouseDown(evt.button.x, evt.button.y, evt.button.button); | ||||
break; | break; | ||||
case SDL_MOUSEBUTTONUP: | case SDL_MOUSEBUTTONUP: | ||||
/* | |||||
imguiIO.MouseDown[sdlButtonToImGuiButton(evt.button.button)] = false; | imguiIO.MouseDown[sdlButtonToImGuiButton(evt.button.button)] = false; | ||||
if (!imguiIO.WantCaptureMouse) | |||||
if (!imguiIO.WantCaptureMouse) */ | |||||
game.onMouseUp(evt.button.x, evt.button.y, evt.button.button); | game.onMouseUp(evt.button.x, evt.button.y, evt.button.button); | ||||
break; | break; | ||||
case SDL_MOUSEWHEEL: | case SDL_MOUSEWHEEL: | ||||
if (evt.wheel.y == 0) { | |||||
break; | |||||
} | |||||
/* | |||||
imguiIO.MouseWheel += (float)evt.wheel.y; | imguiIO.MouseWheel += (float)evt.wheel.y; | ||||
if (!imguiIO.WantCaptureMouse) | |||||
if (!imguiIO.WantCaptureMouse) */ | |||||
game.onScrollWheel(evt.wheel.y); | game.onScrollWheel(evt.wheel.y); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
game.cam_.size = window.size(); | |||||
auto now = std::chrono::steady_clock::now(); | auto now = std::chrono::steady_clock::now(); | ||||
std::chrono::duration<float> dur(now - prevTime); | std::chrono::duration<float> dur(now - prevTime); | ||||
prevTime = now; | prevTime = now; | ||||
} | } | ||||
// Simple case: we can keep up, only need one physics update | // Simple case: we can keep up, only need one physics update | ||||
RTClock updateClock; | |||||
if (dt <= 1 / 25.0) { | if (dt <= 1 / 25.0) { | ||||
ZoneScopedN("game update"); | ZoneScopedN("game update"); | ||||
game.update(dt); | game.update(dt); | ||||
} else { | } else { | ||||
int count = (int)ceil(dt / (1/30.0)); | int count = (int)ceil(dt / (1/30.0)); | ||||
float delta = dt / (float)count; | 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) { | for (int i = 0; i < count; ++i) { | ||||
ZoneScopedN("game update"); | ZoneScopedN("game update"); | ||||
game.update(delta); | game.update(delta); | ||||
while (tickAcc >= 1.0 / TICK_RATE) { | while (tickAcc >= 1.0 / TICK_RATE) { | ||||
ZoneScopedN("game tick"); | ZoneScopedN("game tick"); | ||||
tickAcc -= 1.0 / TICK_RATE; | tickAcc -= 1.0 / TICK_RATE; | ||||
RTClock tickClock; | |||||
game.tick(1.0 / TICK_RATE); | 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 | // ImGUI | ||||
imguiIO.DeltaTime = dt; | |||||
ImGui::NewFrame(); | |||||
//imguiIO.DeltaTime = dt; | |||||
//ImGui::NewFrame(); | |||||
{ | { | ||||
ZoneScopedN("game draw"); | ZoneScopedN("game draw"); | ||||
RTClock drawClock; | |||||
game.draw(); | game.draw(); | ||||
} | } | ||||
// Render ImGUI | // Render ImGUI | ||||
{ | { | ||||
ZoneScopedN("imgui render"); | ZoneScopedN("imgui render"); | ||||
ImGui::Render(); | |||||
ImGuiSDL::Render(ImGui::GetDrawData()); | |||||
//ImGui::Render(); | |||||
//ImGuiSDL::Render(ImGui::GetDrawData()); | |||||
} | } | ||||
RTClock presentClock; | |||||
{ | { | ||||
ZoneScopedN("render present"); | ZoneScopedN("render present"); | ||||
SDL_RenderPresent(renderer.get()); | |||||
window.flip(); | |||||
} | } | ||||
FrameMark | FrameMark | ||||
} | } |