Browse Source

Merge branch 'feature/replace-renderer'

feature/meson
Martin Dørum 3 years ago
parent
commit
61e19b42ca
58 changed files with 1241 additions and 1023 deletions
  1. 5
    4
      CMakeLists.txt
  2. 14
    10
      core.mod/src/DefaultWorldGen.cc
  3. 5
    4
      core.mod/src/DefaultWorldGen.h
  4. 4
    8
      core.mod/src/entities/ItemStackEntity.cc
  5. 1
    1
      core.mod/src/entities/ItemStackEntity.h
  6. 14
    3
      core.mod/src/entities/PlayerEntity.cc
  7. 4
    6
      core.mod/src/entities/PlayerEntity.h
  8. 13
    19
      core.mod/src/main.cc
  9. 18
    7
      include/swan-common/Matrix3.h
  10. 0
    1
      libcygnet/CMakeLists.txt
  11. 0
    11
      libcygnet/include/cygnet/Context.h
  12. 50
    16
      libcygnet/include/cygnet/Renderer.h
  13. 2
    5
      libcygnet/include/cygnet/ResourceManager.h
  14. 7
    1
      libcygnet/include/cygnet/Window.h
  15. 11
    2
      libcygnet/include/cygnet/shaders.h
  16. 10
    0
      libcygnet/include/cygnet/util.h
  17. 0
    27
      libcygnet/src/Context.cc
  18. 284
    16
      libcygnet/src/Renderer.cc
  19. 0
    1
      libcygnet/src/TileAtlas.cc
  20. 19
    2
      libcygnet/src/Window.cc
  21. 125
    28
      libcygnet/src/shaders.cc
  22. 2
    5
      libswan/CMakeLists.txt
  23. 11
    7
      libswan/include/swan/Animation.h
  24. 13
    17
      libswan/include/swan/Chunk.h
  25. 4
    4
      libswan/include/swan/Collection.h
  26. 1
    1
      libswan/include/swan/Entity.h
  27. 12
    18
      libswan/include/swan/Game.h
  28. 4
    17
      libswan/include/swan/Item.h
  29. 7
    7
      libswan/include/swan/LightServer.h
  30. 20
    16
      libswan/include/swan/Mod.h
  31. 0
    53
      libswan/include/swan/Resource.h
  32. 5
    13
      libswan/include/swan/Tile.h
  33. 0
    109
      libswan/include/swan/Win.h
  34. 29
    22
      libswan/include/swan/World.h
  35. 3
    3
      libswan/include/swan/WorldGen.h
  36. 8
    4
      libswan/include/swan/WorldPlane.h
  37. 20
    0
      libswan/include/swan/assets.h
  38. 7
    3
      libswan/include/swan/common.h
  39. 5
    4
      libswan/include/swan/drawutil.h
  40. 0
    2
      libswan/include/swan/swan.h
  41. 1
    3
      libswan/include/swan/traits/BodyTrait.h
  42. 90
    2
      libswan/include/swan/util.h
  43. 3
    8
      libswan/src/Animation.cc
  44. 22
    112
      libswan/src/Chunk.cc
  45. 14
    35
      libswan/src/Game.cc
  46. 0
    16
      libswan/src/Item.cc
  47. 0
    9
      libswan/src/LightServer.cc
  48. 0
    63
      libswan/src/Mod.cc
  49. 0
    132
      libswan/src/Resource.cc
  50. 0
    25
      libswan/src/Tile.cc
  51. 221
    62
      libswan/src/World.cc
  52. 21
    16
      libswan/src/WorldPlane.cc
  53. 69
    0
      libswan/src/assets.cc
  54. 8
    6
      libswan/src/drawutil.cc
  55. 2
    3
      libswan/src/traits/BodyTrait.cc
  56. 9
    10
      libswan/src/traits/PhysicsTrait.cc
  57. 5
    5
      src/cygnet-test.cc
  58. 39
    39
      src/main.cc

+ 5
- 4
CMakeLists.txt View File



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)

+ 14
- 10
core.mod/src/DefaultWorldGen.cc View File

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) {

+ 5
- 4
core.mod/src/DefaultWorldGen.h View File

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);
}; };

+ 4
- 8
core.mod/src/entities/ItemStackEntity.cc View File

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) {

+ 1
- 1
core.mod/src/entities/ItemStackEntity.h View File

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;

+ 14
- 3
core.mod/src/entities/PlayerEntity.cc View File

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) {

+ 4
- 6
core.mod/src/entities/PlayerEntity.h View File

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;

+ 13
- 19
core.mod/src/main.cc View File

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");

+ 18
- 7
include/swan-common/Matrix3.h View File

#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;
} }



+ 0
- 1
libcygnet/CMakeLists.txt View File

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

+ 0
- 11
libcygnet/include/cygnet/Context.h View File

#pragma once

namespace Cygnet {

class Context {
public:
Context();
~Context();
};

}

+ 50
- 16
libcygnet/include/cygnet/Renderer.h View File

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});
} }


} }

+ 2
- 5
libcygnet/include/cygnet/ResourceManager.h View File

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_;
}; };



+ 7
- 1
libcygnet/include/cygnet/Window.h View File

#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_;

+ 11
- 2
libcygnet/include/cygnet/shaders.h View File



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;


} }

+ 10
- 0
libcygnet/include/cygnet/util.h View File



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(); }

+ 0
- 27
libcygnet/src/Context.cc View File

#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();
}

}

+ 284
- 16
libcygnet/src/Renderer.cc View File

} }
}; };


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 = {

+ 0
- 1
libcygnet/src/TileAtlas.cc View File

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;

+ 19
- 2
libcygnet/src/Window.cc View File



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;
}

} }

+ 125
- 28
libcygnet/src/shaders.cc View File



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";

} }

+ 2
- 5
libswan/CMakeLists.txt View File

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)



+ 11
- 7
libswan/include/swan/Animation.h View File

#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_);
} }

}



+ 13
- 17
libswan/include/swan/Chunk.h View File

#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_;
}; };


} }

+ 4
- 4
libswan/include/swan/Collection.h View File

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);
} }
} }



+ 1
- 1
libswan/include/swan/Entity.h View File



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) {}

+ 12
- 18
libswan/include/swan/Game.h View File

#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_;

+ 4
- 17
libswan/include/swan/Item.h View File



#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 &image;
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) {}
}; };


} }

+ 7
- 7
libswan/include/swan/LightServer.h View File

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();
} }



+ 20
- 16
libswan/include/swan/Mod.h View File

#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_;

+ 0
- 53
libswan/include/swan/Resource.h View File

#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_;
};

}

+ 5
- 13
libswan/include/swan/Tile.h View File

#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 &image;
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) {}
}; };


} }

+ 0
- 109
libswan/include/swan/Win.h View File

#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_;
};

}

+ 29
- 22
libswan/include/swan/World.h View File

#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_;

+ 3
- 3
libswan/include/swan/WorldGen.h View File



#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;

+ 8
- 4
libswan/include/swan/WorldPlane.h View File

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_;
}; };


/* /*

+ 20
- 0
libswan/include/swan/assets.h View File

#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);

}

+ 7
- 3
libswan/include/swan/common.h View File

#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;
}; };


} }

+ 5
- 4
libswan/include/swan/drawutil.h View File

#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 */


} }
} }

+ 0
- 2
libswan/include/swan/swan.h View File

#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>

+ 1
- 3
libswan/include/swan/traits/BodyTrait.h View File



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
}; };
}; };



+ 90
- 2
libswan/include/swan/util.h View File


#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) {

+ 3
- 8
libswan/src/Animation.cc View File

#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;

+ 22
- 112
libswan/src/Chunk.cc View File

#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) {

+ 14
- 35
libswan/src/Game.cc View File

#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();

+ 0
- 16
libswan/src/Item.cc View File

#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",
});
}

}

+ 0
- 9
libswan/src/LightServer.cc View File

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()) {

+ 0
- 63
libswan/src/Mod.cc View File

#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;
});
}

}

+ 0
- 132
libswan/src/Resource.cc View File

#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;
}

}

+ 0
- 25
libswan/src/Tile.cc View File

#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,
});
}

}

+ 221
- 62
libswan/src/World.cc View File

#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) {

+ 21
- 16
libswan/src/WorldPlane.cc View File

#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();

+ 69
- 0
libswan/src/assets.cc View File

#include "assets.h"

#include <SDL.h>
#include <SDL_image.h>
#include <cpptoml.h>
#include <string.h>

namespace Swan {

Result<ImageAsset> loadImageAsset(
const std::unordered_map<std::string, std::string> modPaths,
std::string path) {
auto sep = path.find("::");
if (sep == std::string::npos) {
return {Err, "No '::' mod separator"};
}

auto modPart = path.substr(0, sep);
auto pathPart = path.substr(sep + 2, path.size() - sep - 2);

auto modPath = modPaths.find(modPart);
if (modPath == modPaths.end()) {
return {Err, "No mod named '" + modPart + '\''};
}

std::string assetPath = modPath->second + "/assets/" + pathPart;
std::string pngPath = assetPath + ".png";
std::string tomlPath = assetPath + ".toml";

CPtr<SDL_Surface, SDL_FreeSurface> surface(IMG_Load(pngPath.c_str()));
if (!surface) {
return {Err, "Loading image " + pngPath + " failed: " + SDL_GetError()};
}

int frameHeight = surface->h;

// Load TOML if it exists
errno = ENOENT; // I don't know if ifstream is guaranteed to set errno
std::ifstream tomlFile(tomlPath);
if (tomlFile) {
cpptoml::parser parser(tomlFile);
try {
auto toml = parser.parse();
frameHeight = toml->get_as<int>("height").value_or(frameHeight);
} catch (cpptoml::parse_exception &exc) {
return {Err, "Failed to parse toml file " + tomlPath + ": " + exc.what()};
}
} else if (errno != ENOENT) {
return {Err, "Couldn't open " + tomlPath + ": " + strerror(errno)};
}

ImageAsset asset{
.width = surface->w,
.frameHeight = frameHeight,
.frameCount = surface->h / frameHeight,
.data = std::make_unique<unsigned char[]>(surface->w * surface->h * 4),
};

// TODO: Pixel formats?
for (size_t y = 0; y < (size_t)surface->h; ++y) {
unsigned char *src = (unsigned char *)surface->pixels + y * surface->pitch;
unsigned char *dest = asset.data.get() + y * surface->w * 4;
memcpy(dest, src, surface->w * 4);
}

return {Ok, std::move(asset)};
}

}

+ 8
- 6
libswan/src/drawutil.cc View File

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 */


} }
} }

+ 2
- 3
libswan/src/traits/BodyTrait.cc View File

#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 */


} }

+ 9
- 10
libswan/src/traits/PhysicsTrait.cc View File

#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;

+ 5
- 5
src/cygnet-test.cc View File

#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();
} }

+ 39
- 39
src/main.cc View File

#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
} }

Loading…
Cancel
Save