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

@@ -26,16 +26,16 @@ set(libraries

if(CMAKE_BUILD_TYPE STREQUAL Sanitize OR CMAKE_BUILD_TYPE STREQUAL "")
message(STATUS "Build mode: Sanitize")
add_compile_options(-g -fsanitize=address -fsanitize=undefined)
add_compile_options(-g -DDEBUG -fsanitize=address -fsanitize=undefined)
add_link_options(-fsanitize=address -fsanitize=undefined)

elseif(CMAKE_BUILD_TYPE STREQUAL Debug)
message(STATUS "Build mode: Debug")
add_compile_options(-g)
add_compile_options(-g -DDEBUG)

elseif(CMAKE_BUILD_TYPE STREQUAL Optimize)
message(STATUS "Build mode: Optimize")
add_compile_options(-O3 -DNDEBUG -g)
add_compile_options(-O3 -g -DDEBUG)

elseif(CMAKE_BUILD_TYPE STREQUAL Tracy)
message(STATUS "Build mode: Tracy")
@@ -45,7 +45,7 @@ elseif(CMAKE_BUILD_TYPE STREQUAL Tracy)

elseif(CMAKE_BUILD_TYPE STREQUAL Release)
message(STATUS "Build mode: Release")
add_compile_options(-O3 -flto -DNDEBUG -g)
add_compile_options(-O3 -flto -g -DNDEBUG)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -flto")

else()
@@ -72,6 +72,7 @@ add_subdirectory(core.mod)
add_executable(swan
src/main.cc)
target_link_libraries(swan libswan libcygnet ${libraries})
add_dependencies(swan core.mod)

add_executable(perlin-test EXCLUDE_FROM_ALL
src/perlin-test.cc)

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

@@ -12,11 +12,13 @@ static int getStoneLevel(const siv::PerlinNoise &perlin, int x) {
return (int)(perlin.noise(x / 50.0, 10) * 10) + 10;
}

void DefaultWorldGen::drawBackground(const Swan::Context &ctx, Swan::Win &win, Swan::Vec2 pos) {
void DefaultWorldGen::drawBackground(
const Swan::Context &ctx, Cygnet::Renderer &rnd, Swan::Vec2 pos) {
int texmin = 10;
int texmax = 20;
//int texmax = 20;

if (pos.y > texmin) {
/*
SDL_Texture *tex = bgCave_.texture_.get();

Uint8 alpha = std::clamp(
@@ -27,19 +29,21 @@ void DefaultWorldGen::drawBackground(const Swan::Context &ctx, Swan::Win &win, S
Swan::Draw::parallaxBackground(
win, tex, std::nullopt, std::nullopt,
pos.x * Swan::TILE_SIZE, pos.y * Swan::TILE_SIZE, 0.7);
TODO */
}
}

SDL_Color DefaultWorldGen::backgroundColor(Swan::Vec2 pos) {
Cygnet::Color DefaultWorldGen::backgroundColor(Swan::Vec2 pos) {
float y = pos.y;
return Swan::Draw::linearGradient(y, {
{ 0, { 128, 220, 250, 255 } },
{ 70, { 107, 87, 5, 255 } },
{ 100, { 107, 87, 5, 255 } },
{ 200, { 20, 20, 23, 255 } },
{ 300, { 20, 20, 23, 255 } },
{ 500, { 25, 10, 10, 255 } },
{ 1000, { 65, 10, 10, 255 } } });
{ 0, Cygnet::ByteColor{128, 220, 250}},
{ 70, Cygnet::ByteColor{107, 87, 5}},
{ 100, Cygnet::ByteColor{107, 87, 5}},
{ 200, Cygnet::ByteColor{ 20, 20, 23}},
{ 300, Cygnet::ByteColor{ 20, 20, 23}},
{ 500, Cygnet::ByteColor{ 25, 10, 10}},
{1000, Cygnet::ByteColor{ 65, 10, 10}},
});
}

Swan::Tile::ID DefaultWorldGen::genTile(Swan::TilePos pos) {

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

@@ -13,16 +13,17 @@ public:
tAir_(world.getTileID("@::air")),
tTreeTrunk_(world.getTileID("core::tree-trunk")),
tLeaves_(world.getTileID("core::leaves")),
bgCave_(world.resources_.getImage("core/misc/background-cave")) {}
bgCave_(world.getSprite("core::misc/background-cave")) {}

void drawBackground(const Swan::Context &ctx, Swan::Win &win, Swan::Vec2 pos) override;
SDL_Color backgroundColor(Swan::Vec2 pos) override;
void drawBackground(
const Swan::Context &ctx, Cygnet::Renderer &rnd, Swan::Vec2 pos) override;
Cygnet::Color backgroundColor(Swan::Vec2 pos) override;
void genChunk(Swan::WorldPlane &plane, Swan::Chunk &chunk) override;
Swan::EntityRef spawnPlayer(const Swan::Context &ctx) override;

private:
Swan::Tile::ID genTile(Swan::TilePos pos);
Swan::Tile::ID tGrass_, tDirt_, tStone_, tAir_, tTreeTrunk_, tLeaves_;
Swan::ImageResource &bgCave_;
Cygnet::RenderSprite bgCave_;
siv::PerlinNoise perlin_ = siv::PerlinNoise(100);
};

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

@@ -19,14 +19,10 @@ ItemStackEntity::ItemStackEntity(const Swan::Context &ctx, const PackObject &obj
deserialize(ctx, obj);
}

void ItemStackEntity::draw(const Swan::Context &ctx, Swan::Win &win) {
SDL_Rect rect = item_->image.frameRect();

SDL_Texture *tex = item_->image.texture_.get();
Swan::TexColorMod darken(tex, 220, 220, 220);

win.showTexture(body_.pos, tex, &rect,
{ .hscale = 0.5, .vscale = 0.5 });
void ItemStackEntity::draw(const Swan::Context &ctx, Cygnet::Renderer &rnd) {
// TODO: decrease brightness?
rnd.drawTile(item_->id, Cygnet::Mat3gf{}.scale({0.5, 0.5}).translate(body_.pos));
rnd.drawRect(body_.pos, body_.size);
}

void ItemStackEntity::update(const Swan::Context &ctx, float dt) {

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

@@ -7,7 +7,7 @@ public:
ItemStackEntity(const Swan::Context &ctx, Swan::Vec2 pos, const std::string &item);
ItemStackEntity(const Swan::Context &ctx, const PackObject &obj);

void draw(const Swan::Context &ctx, Swan::Win &win) override;
void draw(const Swan::Context &ctx, Cygnet::Renderer &rnd) override;
void update(const Swan::Context &ctx, float dt) override;
void tick(const Swan::Context &ctx, float dt) override;
void deserialize(const Swan::Context &ctx, const PackObject &obj) override;

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

@@ -14,9 +14,20 @@ PlayerEntity::PlayerEntity(const Swan::Context &ctx, const PackObject &obj):
deserialize(ctx, obj);
}

void PlayerEntity::draw(const Swan::Context &ctx, Swan::Win &win) {
body_.outline(win);
anims_[(int)state_].draw(body_.pos - Swan::Vec2(0.2, 0.1), win);
void PlayerEntity::draw(const Swan::Context &ctx, Cygnet::Renderer &rnd) {
Cygnet::Mat3gf mat;

// Currently, there is no sprite for running left.
// Running left is just running right but flipped.
if (state_ == State::RUNNING_L) {
mat.translate({-0.5, 0}).scale({-1, 1}).translate({0.5, 0});
}

anims_[(int)state_].draw(rnd, mat.translate(
body_.pos - Swan::Vec2{0.2, 0.1}));

rnd.drawRect(mouseTile_, {1, 1});
rnd.drawRect(body_.pos, body_.size);
}

void PlayerEntity::update(const Swan::Context &ctx, float dt) {

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

@@ -11,7 +11,7 @@ public:
using PhysicsEntity::get;
Inventory &get(InventoryTrait::Tag) override { return inventory_; }

void draw(const Swan::Context &ctx, Swan::Win &win) override;
void draw(const Swan::Context &ctx, Cygnet::Renderer &rnd) override;
void update(const Swan::Context &ctx, float dt) override;
void tick(const Swan::Context &ctx, float dt) override;
void deserialize(const Swan::Context &ctx, const PackObject &obj) override;
@@ -36,11 +36,9 @@ private:
PlayerEntity(const Swan::Context &ctx):
PhysicsEntity(SIZE),
anims_{
Swan::Animation(ctx.resources.getImage("core/entity/player-still"), 0.8),
Swan::Animation(
ctx.resources.getImage("core/entity/player-running"),
1, SDL_FLIP_HORIZONTAL),
Swan::Animation(ctx.resources.getImage("core/entity/player-running"), 1)
Swan::Animation(ctx.world.getSprite("core::entity/player-still"), 0.8),
Swan::Animation(ctx.world.getSprite("core::entity/player-running"), 1),
Swan::Animation(ctx.world.getSprite("core::entity/player-running"), 1),
} {}

State state_ = State::IDLE;

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

@@ -10,62 +10,56 @@ public:
breakListener_ = world.evtTileBreak_.subscribe(
std::bind_front(&CoreMod::onTileBreak, this));

registerImage("tile/stone");
registerImage("tile/dirt");
registerImage("tile/grass");
registerImage("tile/tree-trunk");
registerImage("tile/leaves");
registerImage("tile/torch");
registerImage("entity/player-running");
registerImage("entity/player-still");
registerImage("misc/background-cave");
registerSprite("entity/player-running");
registerSprite("entity/player-still");
registerSprite("misc/background-cave");

registerTile({
.name = "stone",
.image = "core/tile/stone",
.image = "core::tile/stone",
.droppedItem = "core::stone",
});
registerTile({
.name = "dirt",
.image = "core/tile/dirt",
.image = "core::tile/dirt",
.droppedItem = "core::dirt",
});
registerTile({
.name = "grass",
.image = "core/tile/grass",
.image = "core::tile/grass",
.droppedItem = "core::dirt",
});
registerTile({
.name = "tree-trunk",
.image = "core/tile/tree-trunk",
.image = "core::tile/tree-trunk",
.droppedItem = "core::tree-trunk",
});
registerTile({
.name = "leaves",
.image = "core/tile/leaves",
.image = "core::tile/leaves",
});
registerTile({
.name = "torch",
.image = "core/tile/torch",
.image = "core::tile/torch",
.isSolid = false,
.lightLevel = 80/255.0,
});

registerItem({
.name = "stone",
.image = "core/tile/stone",
.image = "core::tile/stone",
});
registerItem({
.name = "dirt",
.image = "core/tile/dirt",
.image = "core::tile/dirt",
});
registerItem({
.name = "grass",
.image = "core/tile/grass",
.image = "core::tile/grass",
});
registerItem({
.name = "tree-trunk",
.image = "core/tile/tree-trunk",
.image = "core::tile/tree-trunk",
});

registerWorldGen<DefaultWorldGen>("default");

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

@@ -1,6 +1,6 @@
#pragma once

#include <iostream>
#include <ostream>
#include <cmath>
#include <array>

@@ -8,6 +8,9 @@

namespace SwanCommon {

// 3D transformation matrix.
// All the operations assume that the last row remains {0, 0, 1}.
// If the last row is modified, methods like .scale, .translate and .rotate won't work.
template<typename T>
struct Matrix3 {
using Vec = Vector2<T>;
@@ -54,17 +57,25 @@ struct Matrix3 {

constexpr Matrix3<T> &scale(Vec vec) {
at(0, 0) *= vec.x;
at(1, 0) *= vec.x;
at(2, 0) *= vec.x;
at(0, 1) *= vec.y;
at(1, 1) *= vec.y;
at(2, 1) *= vec.y;
return *this;
}

constexpr Matrix3<T> &rotate(T rads) {
T s = std::sin(rads);
T c = std::cos(rads);
at(0, 0) += c;
at(1, 0) -= s;
at(0, 1) += s;
at(1, 1) += c;
T old00 = at(0, 0), old10 = at(1, 0), old20 = at(2, 0);

at(0, 0) = c * old00 + -s * at(0, 1);
at(1, 0) = c * old10 + -s * at(1, 1);
at(2, 0) = c * old20 + -s * at(2, 1);
at(0, 1) = s * old00 + c * at(0, 1);
at(1, 1) = s * old10 + c * at(1, 1);
at(2, 1) = s * old20 + c * at(2, 1);
return *this;
}

@@ -81,8 +92,8 @@ template<typename T>
std::ostream &operator<<(std::ostream &os, const Matrix3<T> &mat) {
os << '('
<< '(' << mat.at(0, 0) << ", " << mat.at(1, 0) << ", " << mat.at(2, 0) << "), "
<< '(' << mat.at(0, 1) << ", " << mat.at(1, 1) << ", " << mat.at(2, 1) << "), "
<< '(' << mat.at(0, 2) << ", " << mat.at(1, 2) << ", " << mat.at(2, 2) << "))";
<< '(' << mat.at(0, 1) << ", " << mat.at(1, 1) << ", " << mat.at(2, 1) << "), "
<< '(' << mat.at(0, 2) << ", " << mat.at(1, 2) << ", " << mat.at(2, 2) << "))";
return os;
}


+ 0
- 1
libcygnet/CMakeLists.txt View File

@@ -1,5 +1,4 @@
add_library(libcygnet SHARED
src/Context.cc
src/GlWrappers.cc
src/Renderer.cc
src/ResourceManager.cc

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

@@ -1,11 +0,0 @@
#pragma once

namespace Cygnet {

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

}

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

@@ -17,20 +17,20 @@ struct RenderChunk {
GLuint tex;
};

struct RenderChunkShadow {
GLuint tex;
};

struct RenderSprite {
GLuint tex;
SwanCommon::Vec2 scale;
int frameCount;
};

struct RenderTile {
uint16_t id;
};

struct RenderCamera {
SwanCommon::Vec2 pos;
SwanCommon::Vec2i size;
float zoom;
SwanCommon::Vec2 pos = {0, 0};
SwanCommon::Vec2i size = {1, 1};
float zoom = 1;
};

class Renderer {
@@ -41,9 +41,10 @@ public:
~Renderer();

void drawChunk(RenderChunk chunk, SwanCommon::Vec2 pos);
void drawChunkShadow(RenderChunkShadow shadow, SwanCommon::Vec2 pos);
void drawTile(TileID id, Mat3gf mat);
void drawSprite(RenderSprite sprite, Mat3gf mat, int y = 0);
void drawSprite(RenderSprite sprite, SwanCommon::Vec2 pos, int y = 0);
void drawSpriteFlipped(RenderSprite chunk, SwanCommon::Vec2 pos, int y = 0);
void drawRect(SwanCommon::Vec2 pos, SwanCommon::Vec2 size);

void draw(const RenderCamera &cam);

@@ -55,42 +56,75 @@ public:
void modifyChunk(RenderChunk chunk, SwanCommon::Vec2i pos, TileID id);
void destroyChunk(RenderChunk chunk);

RenderChunkShadow createChunkShadow(
uint8_t data[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT]);
void modifyChunkShadow(
RenderChunkShadow shadow,
uint8_t data[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT]);
void destroyChunkShadow(RenderChunkShadow chunk);

RenderSprite createSprite(void *data, int width, int height, int fh);
RenderSprite createSprite(void *data, int width, int height);
void destroySprite(RenderSprite sprite);

SwanCommon::Vec2 winScale() { return winScale_; }

private:
struct DrawChunk {
SwanCommon::Vec2 pos;
RenderChunk chunk;
};

struct DrawShadow {
SwanCommon::Vec2 pos;
RenderChunkShadow shadow;
};

struct DrawTile {
Mat3gf transform;
TileID id;
};

struct DrawSprite {
Mat3gf transform;
int frame;
RenderSprite sprite;
};

struct DrawRect {
SwanCommon::Vec2 pos;
SwanCommon::Vec2 size;
};

SwanCommon::Vec2 winScale_ = {1, 1};

std::unique_ptr<RendererState> state_;

std::vector<DrawChunk> drawChunks_;
std::vector<DrawShadow> drawChunkShadows_;
std::vector<DrawTile> drawTiles_;
std::vector<DrawSprite> drawSprites_;
std::vector<DrawRect> drawRects_;
};

inline void Renderer::drawChunk(RenderChunk chunk, SwanCommon::Vec2 pos) {
drawChunks_.emplace_back(pos, chunk);
drawChunks_.push_back({pos, chunk});
}

inline void Renderer::drawSprite(RenderSprite sprite, Mat3gf mat, int frame) {
drawSprites_.emplace_back(mat, frame, sprite);
inline void Renderer::drawChunkShadow(RenderChunkShadow shadow, SwanCommon::Vec2 pos) {
drawChunkShadows_.push_back({pos, shadow});
}

inline void Renderer::drawSprite(RenderSprite sprite, SwanCommon::Vec2 pos, int frame) {
drawSprites_.emplace_back(Mat3gf{}.translate(pos), frame, sprite);
inline void Renderer::drawTile(TileID id, Mat3gf mat) {
drawTiles_.push_back({mat, id});
}

inline void Renderer::drawSprite(RenderSprite sprite, Mat3gf mat, int frame) {
drawSprites_.push_back({mat, frame, sprite});
}

inline void Renderer::drawSpriteFlipped(RenderSprite sprite, SwanCommon::Vec2 pos, int frame) {
drawSprites_.emplace_back(Mat3gf{}.translate(pos).scale({ -1, 1 }), frame, sprite);
inline void Renderer::drawRect(SwanCommon::Vec2 pos, SwanCommon::Vec2 size) {
drawRects_.push_back({pos, size});
}

}

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

@@ -25,7 +25,7 @@ class ResourceBuilder {
public:
ResourceBuilder(Renderer &rnd): rnd_(rnd) {}

RenderSprite addSprite(std::string name, void *data, int width, int height, int fh);
RenderSprite addSprite(std::string name, void *data, int width, int height, int frameHeight);
RenderSprite addSprite(std::string name, void *data, int width, int height);
void addTile(Renderer::TileID id, void *data, int frames = 1);
void addTile(Renderer::TileID id, std::unique_ptr<unsigned char[]> data, int frames = 1);
@@ -44,14 +44,11 @@ public:
ResourceManager(ResourceBuilder &&builder);
~ResourceManager();

RenderSprite getSprite(std::string name) { return sprites_.at(std::move(name)); }

void tick();

private:
Renderer &rnd_;
std::unordered_map<std::string, RenderSprite> sprites_;
std::unordered_map<std::string, RenderTile> tiles_;
std::unordered_map<std::string, Renderer::TileID> tiles_;
std::vector<ResourceTileAnimation> tileAnims_;
};


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

@@ -3,6 +3,10 @@
#include <swan-common/Vector2.h>
#include <memory>

#include "util.h"

struct SDL_Window;

namespace Cygnet {

struct WindowState;
@@ -13,11 +17,13 @@ public:
~Window();

void makeCurrent();
void clear();
void clear(Color color = {});
void flip();
void onResize(int w, int h);
SwanCommon::Vec2i size() { return { w_, h_ }; }

SDL_Window *sdlWindow();

private:
std::unique_ptr<WindowState> state_;
int w_;

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

@@ -2,10 +2,19 @@

namespace Cygnet::Shaders {

extern const char *chunkVx;
extern const char *chunkFr;

extern const char *chunkShadowVx;
extern const char *chunkShadowFr;

extern const char *tileVx;
extern const char *tileFr;

extern const char *spriteVx;
extern const char *spriteFr;

extern const char *chunkVx;
extern const char *chunkFr;
extern const char *rectVx;
extern const char *rectFr;

}

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

@@ -16,6 +16,16 @@ using GLfloat = float;

using Mat3gf = SwanCommon::Matrix3<GLfloat>;

struct Color {
float r = 0, g = 0, b = 0, a = 1;
};

struct ByteColor {
uint8_t r = 0, g = 0, b = 0, a = 0;

operator Color() { return { r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f }; }
};

struct SDLError: public std::exception {
SDLError(std::string msg): message(std::move(msg)) {}
const char *what() const noexcept override { return message.c_str(); }

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

@@ -1,27 +0,0 @@
#include "Context.h"

#include <SDL.h>

#include "util.h"

namespace Cygnet {

Context::Context() {
SDL_Init(SDL_INIT_VIDEO);

SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8);

SDL_GL_SetSwapInterval(1);
}

Context::~Context() {
SDL_Quit();
}

}

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

@@ -73,6 +73,112 @@ struct ChunkProg: public GlProgram {
}
};

struct ChunkShadowProg: public GlProgram {
template<typename... T>
ChunkShadowProg(const T &... shaders): GlProgram(shaders...) { init(); }
~ChunkShadowProg() { deinit(); }

GLint camera = uniformLoc("camera");
GLint pos = uniformLoc("pos");
GLint vertex = attribLoc("vertex");
GLint tex = uniformLoc("tex");

GLuint vbo;

static constexpr float ch = (float)SwanCommon::CHUNK_HEIGHT;
static constexpr float cw = (float)SwanCommon::CHUNK_WIDTH;
static constexpr GLfloat vertexes[] = {
0.0f, 0.0f, // pos 0: top left
0.0f, ch , // pos 1: bottom left
cw, ch, // pos 2: bottom right
cw, ch, // pos 2: bottom right
cw, 0.0f, // pos 3: top right
0.0f, 0.0f, // pos 0: top left
};

void enable() {
glUseProgram(id());
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer(vertex, 2, GL_FLOAT, GL_FALSE, 0, (void *)0);
glEnableVertexAttribArray(vertex);
glCheck();

glUniform1i(tex, 0);
}

void disable() {
glDisableVertexAttribArray(vertex);
glCheck();
}

void init() {
glGenBuffers(1, &vbo);
glCheck();

glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW);
glCheck();
}

void deinit() {
glDeleteBuffers(1, &vbo);
glCheck();
}
};

struct TileProg: public GlProgram {
template<typename... T>
TileProg(const T &... shaders): GlProgram(shaders...) { init(); }
~TileProg() { deinit(); }

GLint camera = uniformLoc("camera");
GLint transform = uniformLoc("transform");
GLint vertex = attribLoc("vertex");
GLint tileAtlas = uniformLoc("tileAtlas");
GLint tileAtlasSize = uniformLoc("tileAtlasSize");
GLint tileID = uniformLoc("tileID");

GLuint vbo;

static constexpr GLfloat vertexes[] = {
0.0f, 0.0f, // pos 0: top left
0.0f, 1.0f, // pos 1: bottom left
1.0f, 1.0f, // pos 2: bottom right
1.0f, 1.0f, // pos 2: bottom right
1.0f, 0.0f, // pos 3: top right
0.0f, 0.0f, // pos 0: top left
};

void enable() {
glUseProgram(id());
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer(vertex, 2, GL_FLOAT, GL_FALSE, 0, (void *)0);
glEnableVertexAttribArray(vertex);
glCheck();

glUniform1i(tileAtlas, 0);
}

void disable() {
glDisableVertexAttribArray(vertex);
glCheck();
}

void init() {
glGenBuffers(1, &vbo);
glCheck();

glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW);
glCheck();
}

void deinit() {
glDeleteBuffers(1, &vbo);
glCheck();
}
};

struct SpriteProg: public GlProgram {
template<typename... T>
SpriteProg(const T &... shaders): GlProgram(shaders...) { init(); }
@@ -80,6 +186,7 @@ struct SpriteProg: public GlProgram {

GLint camera = uniformLoc("camera");
GLint transform = uniformLoc("transform");
GLint frameSize = uniformLoc("frameSize");
GLint frameInfo = uniformLoc("frameInfo");
GLint vertex = attribLoc("vertex");
GLint tex = uniformLoc("tex");
@@ -125,14 +232,72 @@ struct SpriteProg: public GlProgram {
}
};

struct RectProg: public GlProgram {
template<typename... T>
RectProg(const T &... shaders): GlProgram(shaders...) { init(); }
~RectProg() { deinit(); }

GLint camera = uniformLoc("camera");
GLint pos = uniformLoc("pos");
GLint size = uniformLoc("size");
GLint vertex = attribLoc("vertex");

GLuint vbo;

static constexpr GLfloat vertexes[] = {
0.0f, 0.0f, // pos 0: top left
0.0f, 1.0f, // pos 1: bottom left
1.0f, 1.0f, // pos 2: bottom right
1.0f, 1.0f, // pos 2: bottom right
1.0f, 0.0f, // pos 3: top right
0.0f, 0.0f, // pos 0: top left
};

void enable() {
glUseProgram(id());
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer(vertex, 2, GL_FLOAT, GL_FALSE, 0, (void *)0);
glEnableVertexAttribArray(vertex);
glCheck();
}

void disable() {
glDisableVertexAttribArray(vertex);
glCheck();
}

void init() {
glGenBuffers(1, &vbo);
glCheck();

glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW);
glCheck();
}

void deinit() {
glDeleteBuffers(1, &vbo);
glCheck();
}
};

struct RendererState {
GlVxShader spriteVx{Shaders::spriteVx};
GlFrShader spriteFr{Shaders::spriteFr};
GlVxShader chunkVx{Shaders::chunkVx};
GlFrShader chunkFr{Shaders::chunkFr};
GlVxShader chunkShadowVx{Shaders::chunkShadowVx};
GlFrShader chunkShadowFr{Shaders::chunkShadowFr};
GlVxShader tileVx{Shaders::tileVx};
GlFrShader tileFr{Shaders::tileFr};
GlVxShader spriteVx{Shaders::spriteVx};
GlFrShader spriteFr{Shaders::spriteFr};
GlVxShader rectVx{Shaders::rectVx};
GlFrShader rectFr{Shaders::rectFr};

SpriteProg spriteProg{spriteVx, spriteFr};
ChunkProg chunkProg{chunkVx, chunkFr};
ChunkShadowProg chunkShadowProg{chunkShadowVx, chunkShadowFr};
TileProg tileProg{tileVx, tileFr};
SpriteProg spriteProg{spriteVx, spriteFr};
RectProg rectProg{rectVx, rectFr};

GLuint atlasTex;
SwanCommon::Vec2 atlasTexSize;
@@ -148,14 +313,23 @@ Renderer::~Renderer() = default;
void Renderer::draw(const RenderCamera &cam) {
Mat3gf camMat;

// Make the matrix translate to { -camX, -camY }, fix up the aspect ratio,
// flip the Y axis so that positive Y direction is down, and scale according to zoom.
float ratio = (float)cam.size.y / (float)cam.size.x;
camMat.translate(cam.pos.scale(-ratio, 1) * cam.zoom);
camMat.scale({ cam.zoom * ratio, -cam.zoom });
camMat.translate(-cam.pos);

if (cam.size.y > cam.size.x) {
float ratio = (float)cam.size.y / (float)cam.size.x;
winScale_ = {1/ratio, 1};
camMat.scale({cam.zoom * ratio, -cam.zoom});
} else {
float ratio = (float)cam.size.x / (float)cam.size.y;
winScale_ = {1, 1/ratio};
camMat.scale({cam.zoom, -cam.zoom * ratio});
}

auto &chunkProg = state_->chunkProg;
auto &chunkShadowProg = state_->chunkShadowProg;
auto &tileProg = state_->tileProg;
auto &spriteProg = state_->spriteProg;
auto &rectProg = state_->rectProg;

{
chunkProg.enable();
@@ -181,6 +355,30 @@ void Renderer::draw(const RenderCamera &cam) {
chunkProg.disable();
}

{
tileProg.enable();
glUniformMatrix3fv(tileProg.camera, 1, GL_TRUE, camMat.data());
glCheck();

glUniform2f(tileProg.tileAtlasSize, state_->atlasTexSize.x, state_->atlasTexSize.y);
glCheck();

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, state_->atlasTex);
glCheck();

glActiveTexture(GL_TEXTURE1);
for (auto [mat, id]: drawTiles_) {
glUniformMatrix3fv(tileProg.transform, 1, GL_TRUE, mat.data());
glUniform1f(tileProg.tileID, id);
glDrawArrays(GL_TRIANGLES, 0, 6);
glCheck();
}

drawTiles_.clear();
tileProg.disable();
}

{
spriteProg.enable();
glUniformMatrix3fv(spriteProg.camera, 1, GL_TRUE, camMat.data());
@@ -188,9 +386,9 @@ void Renderer::draw(const RenderCamera &cam) {

glActiveTexture(GL_TEXTURE0);
for (auto [mat, frame, sprite]: drawSprites_) {
mat.scale(sprite.scale);
glUniformMatrix3fv(spriteProg.transform, 1, GL_TRUE, mat.data());
glUniform3f(spriteProg.frameInfo, sprite.scale.y, sprite.frameCount, frame);
glUniform2f(spriteProg.frameSize, sprite.scale.x, sprite.scale.y);
glUniform2f(spriteProg.frameInfo, sprite.frameCount, frame);
glBindTexture(GL_TEXTURE_2D, sprite.tex);
glDrawArrays(GL_TRIANGLES, 0, 6);
glCheck();
@@ -199,6 +397,39 @@ void Renderer::draw(const RenderCamera &cam) {
drawSprites_.clear();
spriteProg.disable();
}

{
rectProg.enable();
glUniformMatrix3fv(rectProg.camera, 1, GL_TRUE, camMat.data());
glCheck();

for (auto [pos, size]: drawRects_) {
glUniform2f(rectProg.pos, pos.x, pos.y);
glUniform2f(rectProg.size, size.x, size.y);
glDrawArrays(GL_TRIANGLES, 0, 6);
glCheck();
}

drawRects_.clear();
rectProg.disable();
}

{
chunkShadowProg.enable();
glUniformMatrix3fv(chunkShadowProg.camera, 1, GL_TRUE, camMat.data());
glCheck();

glActiveTexture(GL_TEXTURE0);
for (auto [pos, shadow]: drawChunkShadows_) {
glUniform2f(chunkShadowProg.pos, pos.x, pos.y);
glBindTexture(GL_TEXTURE_2D, shadow.tex);
glDrawArrays(GL_TRIANGLES, 0, 6);
glCheck();
}

drawChunkShadows_.clear();
chunkShadowProg.disable();
}
}

void Renderer::uploadTileAtlas(const void *data, int width, int height) {
@@ -230,10 +461,6 @@ void Renderer::modifyTile(TileID id, const void *data) {

RenderChunk Renderer::createChunk(
TileID tiles[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT]) {
// TODO: Maybe don't do this here? Maybe instead store the buffer and
// upload the texture in the draw method?
// The current approach needs createChunk to be called on the graphics thread.

RenderChunk chunk;
glGenTextures(1, &chunk.tex);
glCheck();
@@ -242,8 +469,8 @@ RenderChunk Renderer::createChunk(
glBindTexture(GL_TEXTURE_2D, chunk.tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glCheck();

static_assert(
@@ -306,6 +533,47 @@ void Renderer::destroyChunk(RenderChunk chunk) {
glCheck();
}

RenderChunkShadow Renderer::createChunkShadow(
uint8_t data[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT]) {
RenderChunkShadow shadow;
glGenTextures(1, &shadow.tex);
glCheck();

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, shadow.tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glCheck();

glTexImage2D(
GL_TEXTURE_2D, 0, GL_LUMINANCE,
SwanCommon::CHUNK_WIDTH, SwanCommon::CHUNK_HEIGHT,
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
glCheck();

return shadow;
}

void Renderer::modifyChunkShadow(
RenderChunkShadow shadow,
uint8_t data[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT]) {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, shadow.tex);

glTexImage2D(
GL_TEXTURE_2D, 0, GL_LUMINANCE,
SwanCommon::CHUNK_WIDTH, SwanCommon::CHUNK_HEIGHT,
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
glCheck();
}

void Renderer::destroyChunkShadow(RenderChunkShadow shadow) {
glDeleteTextures(1, &shadow.tex);
glCheck();
}

RenderSprite Renderer::createSprite(void *data, int width, int height, int fh) {
RenderSprite sprite;
sprite.scale = {

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

@@ -32,7 +32,6 @@ void TileAtlas::addTile(size_t tileId, const void *data) {
const unsigned char *bytes = (const unsigned char *)data;
size_t x = tileId % state_->tilesPerLine;
size_t y = tileId / state_->tilesPerLine;
std::cerr << "Tile " << tileId << " to " << x << ", " << y << '\n';

if (state_->width <= x) {
state_->width = x + 1;

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

@@ -14,6 +14,15 @@ struct WindowState {

Window::Window(const char *name, int w, int h):
state_(std::make_unique<WindowState>()), w_(w), h_(h) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
//SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
//SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8);

SDL_GL_SetSwapInterval(1);
state_->window = SDL_CreateWindow(name,
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h,
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE |
@@ -41,7 +50,8 @@ void Window::makeCurrent() {
glCheck();
}

void Window::clear() {
void Window::clear(Color color) {
glClearColor(color.r, color.g, color.b, color.a);
glClear(GL_COLOR_BUFFER_BIT);
glCheck();
}
@@ -54,8 +64,15 @@ void Window::flip() {
void Window::onResize(int w, int h) {
w_ = w;
h_ = h;
glViewport(0, 0, w, h);

int dw, dh;
SDL_GL_GetDrawableSize(state_->window, &dw, &dh);
glViewport(0, 0, dw, dh);
glCheck();
}

SDL_Window *Window::sdlWindow() {
return state_->window;
}

}

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

@@ -2,72 +2,103 @@

namespace Cygnet::Shaders {

const char *spriteVx = R"glsl(
const char *chunkVx = R"glsl(
precision mediump float;
uniform mat3 camera;
uniform vec2 pos;
attribute vec2 vertex;
varying vec2 v_tileCoord;

void main() {
vec3 pos = camera * vec3(pos + vertex, 1);
gl_Position = vec4(pos.xy, 0, 1);
v_tileCoord = vertex;
}
)glsl";

const char *chunkFr = R"glsl(
precision mediump float;
#define TILE_SIZE 32.0
#define CHUNK_WIDTH 64
#define CHUNK_HEIGHT 64

varying vec2 v_tileCoord;
uniform sampler2D tileAtlas;
uniform vec2 tileAtlasSize;
uniform sampler2D tiles;

void main() {
vec2 tilePos = floor(vec2(v_tileCoord.x, v_tileCoord.y));
vec4 tileColor = texture2D(tiles, tilePos / vec2(CHUNK_WIDTH, CHUNK_HEIGHT));
float tileID = floor((tileColor.r * 256.0 + tileColor.a) * 256.0);

// 1/(TILE_SIZE*16) plays the same role here as in the sprite vertex shader.
vec2 offset = v_tileCoord - tilePos;
vec2 pixoffset = (1.0 - offset * 2.0) / (TILE_SIZE * 16.0);
vec2 atlasPos = vec2(
pixoffset.x + tileID + offset.x,
pixoffset.y + floor(tileID / tileAtlasSize.x) + offset.y);

gl_FragColor = texture2D(tileAtlas, atlasPos / tileAtlasSize);
}
)glsl";

const char *chunkShadowVx = R"glsl(
precision mediump float;
#define CHUNK_WIDTH 64
#define CHUNK_HEIGHT 64

uniform mat3 camera;
uniform mat3 transform;
uniform vec3 frameInfo; // frame height, frame count, frame index
uniform vec2 pos;
attribute vec2 vertex;
varying vec2 v_texCoord;

void main() {
// Here, I'm basically treating 1/(TILE_SIZE*16) as half the size of a "pixel".
// It's just an arbitrary small number, but it works as an offset to make sure
// neighbouring parts of the atlas don't bleed into the frame we actually
// want to draw due to (nearest neighbour) interpolation.
float pixoffset = (1.0 - vertex.y * 2.0) / (frameInfo.x * TILE_SIZE * 16.0);
v_texCoord = vec2(
vertex.x,
(frameInfo.x * frameInfo.z + (frameInfo.x * vertex.y)) /
(frameInfo.x * frameInfo.y) + pixoffset);

vec3 pos = camera * transform * vec3(vertex, 1);
vec3 pos = camera * vec3(pos + vertex, 1);
gl_Position = vec4(pos.xy, 0, 1);
v_texCoord = vertex / vec2(CHUNK_WIDTH, CHUNK_HEIGHT);
}
)glsl";

const char *spriteFr = R"glsl(
const char *chunkShadowFr = R"glsl(
precision mediump float;

varying vec2 v_texCoord;
uniform sampler2D tex;

void main() {
gl_FragColor = texture2D(tex, v_texCoord);
vec4 color = texture2D(tex, v_texCoord);
gl_FragColor = vec4(0, 0, 0, 1.0 - color.r);
}
)glsl";

const char *chunkVx = R"glsl(
const char *tileVx = R"glsl(
precision mediump float;
uniform mat3 camera;
uniform vec2 pos;
uniform mat3 transform;
attribute vec2 vertex;
varying vec2 v_tileCoord;

void main() {
vec3 pos = camera * vec3(pos + vertex, 1);
vec3 pos = camera * transform * vec3(vertex, 1);
gl_Position = vec4(pos.xy, 0, 1);
v_tileCoord = vec2(vertex.x, vertex.y);
v_tileCoord = vertex;
}
)glsl";

const char *chunkFr = R"glsl(
const char *tileFr = R"glsl(
precision mediump float;
#define TILE_SIZE 32.0
#define CHUNK_WIDTH 64
#define CHUNK_HEIGHT 64

varying vec2 v_tileCoord;
uniform sampler2D tileAtlas;
uniform vec2 tileAtlasSize;
uniform sampler2D tiles;
uniform float tileID;

void main() {
vec2 tilePos = floor(vec2(v_tileCoord.x, v_tileCoord.y));
vec4 tileColor = texture2D(tiles, tilePos / vec2(CHUNK_WIDTH, CHUNK_HEIGHT));
float tileID = floor((tileColor.r * 256.0 + tileColor.a) * 256.0);

// 1/(TILE_SIZE*16) plays the same role here as in the sprite vertex shader.
vec2 offset = v_tileCoord - tilePos;
vec2 offset = v_tileCoord;
vec2 pixoffset = (1.0 - offset * 2.0) / (TILE_SIZE * 16.0);
vec2 atlasPos = vec2(
pixoffset.x + tileID + offset.x,
@@ -77,4 +108,70 @@ const char *chunkFr = R"glsl(
}
)glsl";

const char *spriteVx = R"glsl(
precision mediump float;
#define TILE_SIZE 32.0

uniform mat3 camera;
uniform mat3 transform;
uniform vec2 frameSize;
uniform vec2 frameInfo; // frame count, frame index
attribute vec2 vertex;
varying vec2 v_texCoord;

void main() {
// Here, I'm basically treating 1/(TILE_SIZE*16) as half the size of a "pixel".
// It's just an arbitrary small number, but it works as an offset to make sure
// neighbouring parts of the atlas don't bleed into the frame we actually
// want to draw due to (nearest neighbour) interpolation.
float pixoffset = (1.0 - vertex.y * 2.0) / (frameSize.y * TILE_SIZE * 16.0);
v_texCoord = vec2(
vertex.x,
(frameSize.y * frameInfo.y + (frameSize.y * vertex.y)) /
(frameSize.y * frameInfo.x) + pixoffset);

vec3 pos = camera * transform * vec3(vertex * frameSize, 1);
gl_Position = vec4(pos.xy, 0, 1);
}
)glsl";

const char *spriteFr = R"glsl(
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D tex;

void main() {
gl_FragColor = texture2D(tex, v_texCoord);
}
)glsl";

const char *rectVx = R"glsl(
precision mediump float;
uniform mat3 camera;
uniform vec2 pos;
uniform vec2 size;
attribute vec2 vertex;
varying vec2 v_coord;

void main() {
vec3 pos = camera * vec3(pos + vertex * size, 1);
gl_Position = vec4(pos.xy, 0, 1);
v_coord = vertex * size;
}
)glsl";

const char *rectFr = R"glsl(
precision mediump float;
#define THICKNESS 0.02

varying vec2 v_coord;
uniform vec2 size;

void main() {
vec2 invCoord = size - v_coord;
float minDist = min(v_coord.x, min(v_coord.y, min(invCoord.x, invCoord.y)));
gl_FragColor = vec4(0.6, 0.6, 0.6, 0.8) * float(minDist < THICKNESS);
}
)glsl";

}

+ 2
- 5
libswan/CMakeLists.txt View File

@@ -3,26 +3,23 @@ add_library(libswan SHARED
src/traits/InventoryTrait.cc
src/traits/PhysicsTrait.cc
src/Animation.cc
src/assets.cc
src/Chunk.cc
src/Clock.cc
src/drawutil.cc
src/Entity.cc
src/Game.cc
src/gfxutil.cc
src/Item.cc
src/ItemStack.cc
src/LightServer.cc
src/Mod.cc
src/OS.cc
src/Resource.cc
src/Tile.cc
src/World.cc
src/WorldPlane.cc)
target_include_directories(libswan
PUBLIC "include"
PRIVATE "include/swan")
set_target_properties(libswan PROPERTIES OUTPUT_NAME swan)
target_link_libraries(libswan swan-common ${libraries})
target_link_libraries(libswan swan-common libcygnet ${libraries})

install(TARGETS libswan DESTINATION swan/libswan)


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

@@ -1,29 +1,33 @@
#pragma once

#include <SDL.h>
#include <cygnet/Renderer.h>

#include "common.h"
#include "Resource.h"
#include "Clock.h"
#include "Resource.h"

namespace Swan {

class Animation {
public:
Animation(ImageResource &resource, float interval, SDL_RendererFlip flip = SDL_FLIP_NONE):
resource_(resource), interval_(interval), timer_(interval), flip_(flip) {}
Animation(Cygnet::RenderSprite sprite, float interval, Cygnet::Mat3gf mat = {}):
sprite_(sprite), interval_(interval), timer_(interval) {}

void tick(float dt);
void draw(const Vec2 &pos, Win &win);
void reset();
void draw(Cygnet::Renderer &rnd, Cygnet::Mat3gf mat);

private:
ImageResource &resource_;
Cygnet::RenderSprite sprite_;
float interval_;
float timer_;
SDL_RendererFlip flip_;
int frame_ = 0;
};

inline void Animation::draw(Cygnet::Renderer &rnd, Cygnet::Mat3gf mat) {
rnd.drawSprite(sprite_, mat, frame_);
}

}



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

@@ -3,6 +3,7 @@
#include <string.h>
#include <stdint.h>
#include <memory>
#include <cygnet/Renderer.h>

#include "util.h"
#include "common.h"
@@ -47,14 +48,14 @@ public:
return getTileData()[pos.y * CHUNK_WIDTH + pos.x];
}

void setTileID(RelPos pos, Tile::ID id, SDL_Texture *tex) {
void setTileID(RelPos pos, Tile::ID id) {
getTileData()[pos.y * CHUNK_WIDTH + pos.x] = id;
drawList_.push_back({ pos, tex });
changeList_.emplace_back(pos, id);
isModified_ = true;
}

void setTileData(RelPos pos, Tile::ID id) {
getTileData()[pos.y * CHUNK_WIDTH + pos.x] = id;
needRender_ = true;
}

uint8_t getLightLevel(RelPos pos) {
@@ -66,38 +67,33 @@ public:
needLightRender_ = true;
}

void render(const Context &ctx, SDL_Renderer *rnd);
void renderLight(const Context &ctx, SDL_Renderer *rnd);

void compress();
void generateDone();
void keepActive();
void decompress();
void draw(const Context &ctx, Win &win);
void compress(Cygnet::Renderer &rnd);
void destroy(Cygnet::Renderer &rnd) { rnd.destroyChunk(renderChunk_); }
void draw(const Context &ctx, Cygnet::Renderer &rnd);
TickAction tick(float dt);

bool isActive() { return deactivateTimer_ > 0; }
void keepActive();
void markModified() { isModified_ = true; }

ChunkPos pos_;

private:
static constexpr float DEACTIVATE_INTERVAL = 20;

void renderList(SDL_Renderer *rnd);

bool isCompressed() { return compressedSize_ != -1; }

std::unique_ptr<uint8_t[]> data_;
std::vector<std::pair<RelPos, SDL_Texture *>> drawList_;
std::vector<std::pair<RelPos, Tile::ID>> changeList_;

ssize_t compressedSize_ = -1; // -1 if not compressed, a positive number if compressed
bool needRender_ = false;
Cygnet::RenderChunk renderChunk_;
Cygnet::RenderChunkShadow renderChunkShadow_;
bool needChunkRender_ = true;
bool needLightRender_ = false;
float deactivateTimer_ = DEACTIVATE_INTERVAL;
bool isModified_ = false;

CPtr<SDL_Texture, SDL_DestroyTexture> texture_;
CPtr<SDL_Texture, SDL_DestroyTexture> lightTexture_;
};

}

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

@@ -61,7 +61,7 @@ public:
virtual EntityRef spawn(const Context &ctx, const Entity::PackObject &obj) = 0;
virtual void update(const Context &ctx, float dt) = 0;
virtual void tick(const Context &ctx, float dt) = 0;
virtual void draw(const Context &ctx, Win &win) = 0;
virtual void draw(const Context &ctx, Cygnet::Renderer &rnd) = 0;
virtual void erase(size_t idx, size_t generation) = 0;

private:
@@ -84,7 +84,7 @@ public:
EntityRef spawn(const Context &ctx, const Entity::PackObject &obj) override;
void update(const Context &ctx, float dt) override;
void tick(const Context &ctx, float dt) override;
void draw(const Context &ctx, Win &win) override;
void draw(const Context &ctx, Cygnet::Renderer &rnd) override;
void erase(size_t idx, size_t generation) override;

private:
@@ -190,11 +190,11 @@ inline void EntityCollectionImpl<Ent>::tick(const Context &ctx, float dt) {
}

template<typename Ent>
inline void EntityCollectionImpl<Ent>::draw(const Context &ctx, Win &win) {
inline void EntityCollectionImpl<Ent>::draw(const Context &ctx, Cygnet::Renderer &rnd) {
ZoneScopedN(typeid(Ent).name());
for (auto &ent: entities_) {
ZoneScopedN("draw");
ent->draw(ctx, win);
ent->draw(ctx, rnd);
}
}


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

@@ -33,7 +33,7 @@ public:

virtual ~Entity() = default;

virtual void draw(const Context &ctx, Win &win) {}
virtual void draw(const Context &ctx, Cygnet::Renderer &rnd) {}
virtual void update(const Context &ctx, float dt) {}
virtual void tick(const Context &ctx, float dt) {}
virtual void onDespawn(const Context &ctx) {}

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

@@ -5,9 +5,10 @@
#include <string>
#include <optional>
#include <SDL.h>
#include <cygnet/Renderer.h>
#include <cygnet/util.h>

#include "common.h"
#include "Resource.h"
#include "Mod.h"
#include "World.h"

@@ -15,12 +16,7 @@ namespace Swan {

class Game {
public:
Game(Win &win):
win_(win),
mousePos_(0, 0) {}

std::optional<ModWrapper> loadMod(std::string path, World &world);
void createWorld(const std::string &worldgen, const std::vector<std::string> &mods);
void createWorld(const std::string &worldgen, const std::vector<std::string> &modPaths);

void onKeyDown(SDL_Keysym sym) {
pressedKeys_[sym.scancode] = true;
@@ -33,17 +29,17 @@ public:
}

void onMouseMove(Sint32 x, Sint32 y) {
mousePos_ = { x, y };
mousePos_ = (Vec2{(float)x, (float)y} / (Vec2)cam_.size) * renderer_.winScale();
}

void onMouseDown(Sint32 x, Sint32 y, Uint8 button) {
mousePos_ = { x, y };
onMouseMove(x, y);
pressedButtons_[button] = true;
didPressButtons_[button] = true;
}

}
void onMouseUp(Sint32 x, Sint32 y, Uint8 button) {
mousePos_ = { x, y };
onMouseMove(x, y);
pressedButtons_[button] = false;
didReleaseButtons_[button] = true;
}
@@ -55,7 +51,7 @@ public:
bool isKeyPressed(SDL_Scancode code) { return pressedKeys_[code]; }
bool wasKeyPressed(SDL_Scancode code) { return didPressKeys_[code]; }
bool wasKeyReleased(SDL_Scancode code) { return didReleaseKeys_[code]; }
Vec2i getMousePos() { return mousePos_; }
Vec2 getMousePos() { return mousePos_; }
bool isMousePressed(Uint8 button) { return pressedButtons_[button]; }
bool wasMousePressed(Uint8 button) { return didPressButtons_[button]; }
bool wasMouseReleased(Uint8 button) { return didReleaseButtons_[button]; }
@@ -63,23 +59,21 @@ public:

TilePos getMouseTile();

SDL_Color backgroundColor();
Cygnet::Color backgroundColor();
void draw();
void update(float dt);
void tick(float dt);

std::unique_ptr<World> world_ = NULL;
std::unique_ptr<ImageResource> invalidImage_ = NULL;
std::unique_ptr<Tile> invalidTile_ = NULL;
std::unique_ptr<Item> invalidItem_ = NULL;
Win &win_;
Cygnet::Renderer renderer_;
Cygnet::RenderCamera cam_{.zoom = 0.125};

private:
std::bitset<SDL_NUM_SCANCODES> pressedKeys_;
std::bitset<SDL_NUM_SCANCODES> didPressKeys_;
std::bitset<SDL_NUM_SCANCODES> didReleaseKeys_;

Vec2i mousePos_;
Vec2 mousePos_;
std::bitset<SDL_BUTTON_X2> pressedButtons_;
std::bitset<SDL_BUTTON_X2> didPressButtons_;
std::bitset<SDL_BUTTON_X2> didReleaseButtons_;

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

@@ -2,7 +2,7 @@

#include <string>

#include "Resource.h"
#include "Tile.h"

namespace Swan {

@@ -14,25 +14,12 @@ public:
int maxStack = 64;
};

Item(const ResourceManager &resources, const Builder &builder):
name(builder.name), image(resources.getImage(builder.image)),
maxStack(builder.maxStack) {}

const Tile::ID id;
const std::string name;
const ImageResource &image;
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

@@ -50,6 +50,7 @@ public:
void onLightRemoved(TilePos pos, float level);
void onChunkAdded(ChunkPos pos, NewLightChunk &&chunk);
void onChunkRemoved(ChunkPos pos);
void flip();

private:
static constexpr int LIGHT_CUTOFF_DIST = 64;
@@ -82,7 +83,6 @@ private:
void processEvent(const Event &event, std::vector<NewLightChunk> &newChunks);
void run();

LightCallback &cb_;
bool running_ = true;
std::map<std::pair<int, int>, LightChunk> chunks_;
std::set<std::pair<int, int>> updatedChunks_;
@@ -92,33 +92,31 @@ private:
int buffer_ = 0;
std::vector<Event> buffers_[2] = { {}, {} };
std::vector<NewLightChunk> newChunkBuffers_[2] = { {}, {} };
std::thread thread_;
std::condition_variable cond_;
std::mutex mut_;

LightCallback &cb_;
std::thread thread_;
};

inline void LightServer::onSolidBlockAdded(TilePos pos) {
std::lock_guard<std::mutex> lock(mut_);
buffers_[buffer_].push_back({ Event::Tag::BLOCK_ADDED, pos, { .i = 0 } });
cond_.notify_one();
}

inline void LightServer::onSolidBlockRemoved(TilePos pos) {
std::lock_guard<std::mutex> lock(mut_);
buffers_[buffer_].push_back({ Event::Tag::BLOCK_REMOVED, pos, { .i = 0 } });
cond_.notify_one();
}

inline void LightServer::onLightAdded(TilePos pos, float level) {
std::lock_guard<std::mutex> lock(mut_);
buffers_[buffer_].push_back({ Event::Tag::LIGHT_ADDED, pos, { .f = level } });
cond_.notify_one();
}

inline void LightServer::onLightRemoved(TilePos pos, float level) {
std::lock_guard<std::mutex> lock(mut_);
buffers_[buffer_].push_back({ Event::Tag::LIGHT_REMOVED, pos, { .f = level } });
cond_.notify_one();
}

inline void LightServer::onChunkAdded(Vec2i pos, NewLightChunk &&chunk) {
@@ -126,12 +124,14 @@ inline void LightServer::onChunkAdded(Vec2i pos, NewLightChunk &&chunk) {
buffers_[buffer_].push_back({ Event::Tag::CHUNK_ADDED, pos,
{ .i = (int)newChunkBuffers_[buffer_].size() } });
newChunkBuffers_[buffer_].push_back(std::move(chunk));
cond_.notify_one();
}

inline void LightServer::onChunkRemoved(Vec2i pos) {
std::lock_guard<std::mutex> lock(mut_);
buffers_[buffer_].push_back({ Event::Tag::CHUNK_REMOVED, pos, { .i = 0 } });
}

inline void LightServer::flip() {
cond_.notify_one();
}


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

@@ -12,26 +12,26 @@
#include "WorldGen.h"
#include "Entity.h"
#include "Collection.h"
#include "Resource.h"
#include "OS.h"
#include "util.h"

namespace Swan {

class ModWrapper;

class Mod {
public:
Mod(std::string name): name_(std::move(name)) {}
virtual ~Mod() = default;

void registerImage(const std::string &id);
void registerTile(Tile::Builder tile);
void registerItem(Item::Builder item);
void registerWorldGen(const std::string &name, std::unique_ptr<WorldGen::Factory> gen);
void registerTile(Tile::Builder tile) { tiles_.push_back(tile); }
void registerItem(Item::Builder item) { items_.push_back(item); }
void registerSprite(std::string sprite) { sprites_.push_back(sprite); }

template<typename WG>
void registerWorldGen(const std::string &name) {
worldgens_.push_back(WorldGen::Factory{
.name = name_ + "::" + name,
void registerWorldGen(std::string name) {
worldGens_.push_back(WorldGen::Factory{
.name = name,
.create = [](World &world) -> std::unique_ptr<WorldGen> {
return std::make_unique<WG>(world);
}
@@ -44,19 +44,22 @@ public:
std::is_move_constructible_v<Ent>,
"Entities must be movable");
entities_.push_back(EntityCollection::Factory{
.name = name_ + "::" + name,
.name = name,
.create = [](std::string name) -> std::unique_ptr<EntityCollection> {
return std::make_unique<EntityCollectionImpl<Ent>>(std::move(name));
}
});
}

private:
const std::string name_;
std::vector<std::string> images_;
std::vector<Tile::Builder> tiles_;
std::vector<Item::Builder> items_;
std::vector<WorldGen::Factory> worldgens_;
std::vector<std::string> sprites_;
std::vector<WorldGen::Factory> worldGens_;
std::vector<EntityCollection::Factory> entities_;

friend ModWrapper;
};

class ModWrapper {
@@ -72,11 +75,12 @@ public:
mod_.reset();
}

Iter<std::unique_ptr<ImageResource>> buildImages(SDL_Renderer *renderer);
Iter<std::unique_ptr<Tile>> buildTiles(const ResourceManager &resources);
Iter<std::unique_ptr<Item>> buildItems(const ResourceManager &resources);
Iter<WorldGen::Factory> getWorldGens();
Iter<EntityCollection::Factory> getEntities();
const std::string &name() { return mod_->name_; }
const std::vector<Tile::Builder> &tiles() { return mod_->tiles_; }
const std::vector<Item::Builder> &items() { return mod_->items_; }
const std::vector<std::string> &sprites() { return mod_->sprites_; }
const std::vector<WorldGen::Factory> &worldGens() { return mod_->worldGens_; }
const std::vector<EntityCollection::Factory> &entities() { return mod_->entities_; }

std::unique_ptr<Mod> mod_;
std::string path_;

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

@@ -1,53 +0,0 @@
#pragma once

#include <SDL.h>
#include <stdint.h>
#include <string>
#include <memory>
#include <unordered_map>

#include "common.h"

namespace Swan {

class ImageResource {
public:
ImageResource(
SDL_Renderer *renderer, const std::string &modpath, const std::string &id);
ImageResource(
SDL_Renderer *renderer, const std::string &name,
int w, int h, uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255);

void tick(float dt);

SDL_Rect frameRect(int frame = -1) const {
if (frame == -1) frame = frame_;
return SDL_Rect{ 0, frameHeight_ * frame, surface_->w, frameHeight_ };
}

std::unique_ptr<SDL_Surface, void (*)(SDL_Surface *)> surface_{nullptr, &SDL_FreeSurface};
std::unique_ptr<SDL_Texture, void (*)(SDL_Texture *)> texture_{nullptr, &SDL_DestroyTexture};
int frameHeight_;
int numFrames_;
std::string name_;
int frame_ = 0;

private:
float switchInterval_ = 1;
float switchTimer_ = switchInterval_;
};

class ResourceManager {
public:
ResourceManager(Win &win);

void tick(float dt);

ImageResource &getImage(const std::string &name) const;
void addImage(std::unique_ptr<ImageResource> img) { images_[img->name_] = std::move(img); }

private:
std::unordered_map<std::string, std::unique_ptr<ImageResource>> images_;
};

}

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

@@ -3,10 +3,6 @@
#include <stdint.h>
#include <string>
#include <optional>
#include <memory>

#include "Item.h"
#include "Resource.h"

namespace Swan {

@@ -22,20 +18,16 @@ public:
std::optional<std::string> droppedItem = std::nullopt;
};

Tile(const ResourceManager &resources, const Builder &builder):
name(builder.name), image(resources.getImage(builder.image)),
isSolid(builder.isSolid), lightLevel(builder.lightLevel),
droppedItem(builder.droppedItem) {}

const ID id;
const std::string name;
const ImageResource &image;
const bool isSolid;
const float lightLevel;
const std::optional<std::string> droppedItem;

static std::unique_ptr<Tile> createInvalid(const ResourceManager &ctx);
static std::unique_ptr<Tile> createAir(const ResourceManager &ctx);
static ID INVALID_ID;
Tile(ID id, std::string name, const Builder &builder):
id(id), name(name),
isSolid(builder.isSolid), lightLevel(builder.lightLevel),
droppedItem(builder.droppedItem) {}
};

}

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

@@ -1,109 +0,0 @@
#pragma once

#include "log.h"
#include "common.h"

#include <SDL.h>
#include <optional>

namespace Swan {

class Win {
public:
Win(SDL_Window *window, SDL_Renderer *renderer, float scale):
window_(window), renderer_(renderer), scale_(scale) {

if (SDL_GetRendererInfo(renderer_, &rinfo_) < 0) {
panic << "GetRenedrerInfo failed: " << SDL_GetError();
abort();
}

// For HiDPI, we must set the renderer's logical size.
int w, h;
SDL_GetWindowSize(window_, &w, &h);
onResize(w, h);

info << "Using renderer: " << rinfo_.name;
}

Vec2 getPixSize() {
int w, h;
SDL_GetWindowSize(window_, &w, &h);
return Vec2((float)w / scale_, (float)h / scale_);
}

Vec2 getSize() {
int w, h;
SDL_GetWindowSize(window_, &w, &h);
return Vec2(((float)w / (scale_ * zoom_)) / TILE_SIZE, ((float)h / (scale_ * zoom_)) / TILE_SIZE);
}

void onResize(int w, int h) {
SDL_RenderSetLogicalSize(renderer_, (int)((float)w / scale_), (int)((float)h / scale_));
}

SDL_Rect createDestRect(Vec2 pos, Vec2 pixsize) {
return SDL_Rect{
(int)floor((pos.x - cam_.x) * TILE_SIZE * zoom_),
(int)floor((pos.y - cam_.y) * TILE_SIZE * zoom_),
(int)ceil(pixsize.x * zoom_), (int)ceil(pixsize.y * zoom_),
};
}

struct ShowTextureArgs {
SDL_RendererFlip flip = SDL_FLIP_NONE;
double hscale = 1;
double vscale = 1;
double angle = 0;
std::optional<SDL_Point> center = std::nullopt;
};

void showTexture(
const Vec2 &pos, SDL_Texture *tex, SDL_Rect *srcrect,
ShowTextureArgs args) {

SDL_Point *center = args.center ? &*args.center : nullptr;
SDL_Rect destrect = createDestRect(pos, Vec2(srcrect->w * args.hscale, srcrect->h * args.hscale));
if (SDL_RenderCopyEx(renderer_, tex, srcrect, &destrect, args.angle, center, args.flip) < 0)
warn << "RenderCopyEx failed: " << SDL_GetError();
}

void showTexture(const Vec2 &pos, SDL_Texture *tex, SDL_Rect *srcrect,
SDL_Rect *dest, ShowTextureArgs args) {
SDL_Point *center = args.center ? &*args.center : nullptr;
SDL_Rect destrect = createDestRect(pos, Vec2(dest->w * args.hscale, dest->h * args.hscale));
if (SDL_RenderCopyEx(renderer_, tex, srcrect, &destrect, args.angle, center, args.flip) < 0)
warn << "RenderCopyEx failed: " << SDL_GetError();
}

// We want an overload which uses RenderCopy instead of RenderCopyEx,
// because RenderCopy might be faster
void showTexture(const Vec2 &pos, SDL_Texture *tex, SDL_Rect *srcrect) {
SDL_Rect destrect = createDestRect(pos, Vec2(srcrect->w, srcrect->h));
if (SDL_RenderCopy(renderer_, tex, srcrect, &destrect) < 0)
warn << "RenderCopy failed: " << SDL_GetError();
}

// Another overload without RenderCopyEx
void showTexture(const Vec2 &pos, SDL_Texture *tex, SDL_Rect *srcrect,
SDL_Rect *dest) {
SDL_Rect destrect = createDestRect(pos, Vec2(dest->w, dest->h));
if (SDL_RenderCopy(renderer_, tex, srcrect, &destrect) < 0)
warn << "RenderCopy failed: " << SDL_GetError();
}

void drawRect(const Vec2 &pos, const Vec2 &size) {
SDL_Rect destrect = createDestRect(pos, size * TILE_SIZE);
if (SDL_RenderDrawRect(renderer_, &destrect) < 0)
warn << "RenderDrawRect failed: " << SDL_GetError();
}

Vec2 cam_;
float zoom_ = 1;
SDL_Window *window_;
SDL_Renderer *renderer_;
float scale_;
SDL_RendererInfo rinfo_;
};

}

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

@@ -5,6 +5,9 @@
#include <string>
#include <random>
#include <SDL.h>
#include <cygnet/Renderer.h>
#include <cygnet/ResourceManager.h>
#include <cygnet/util.h>

#include "common.h"
#include "Item.h"
@@ -13,7 +16,6 @@
#include "WorldGen.h"
#include "Entity.h"
#include "Collection.h"
#include "Resource.h"
#include "Mod.h"
#include "EventEmitter.h"

@@ -23,9 +25,13 @@ class Game;

class World {
public:
World(Game *game, unsigned long randSeed);
static constexpr Tile::ID INVALID_TILE_ID = 0;
static constexpr char INVALID_TILE_NAME[] = "@::invalid";
static constexpr Tile::ID AIR_TILE_ID = 1;
static constexpr char AIR_TILE_NAME[] = "@::air";

World(Game *game, unsigned long randSeed, std::vector<std::string> modPaths);

void addMod(ModWrapper &&mod);
void setWorldGen(std::string gen);
void spawnPlayer();

@@ -33,37 +39,35 @@ public:
WorldPlane &addPlane(const std::string &gen);
WorldPlane &addPlane() { return addPlane(defaultWorldGen_); }

Tile &getTileByID(Tile::ID id) { return *tiles_[id]; }
Tile &getTileByID(Tile::ID id) { return tiles_[id]; }
Tile::ID getTileID(const std::string &name);
Tile &getTile(const std::string &name);
Item &getItem(const std::string &name);
Cygnet::RenderSprite &getSprite(const std::string &name);

SDL_Color backgroundColor();
void draw(Win &win);
Cygnet::Color backgroundColor();
void draw(Cygnet::Renderer &rnd);
void update(float dt);
void tick(float dt);

// Event emitters
EventEmitter<const Context &, TilePos, Tile &>
evtTileBreak_;

// World owns all mods
std::vector<ModWrapper> mods_;
// These things can be used by the mods as they get initialized in the ctor.
EventEmitter<const Context &, TilePos, Tile &> evtTileBreak_;

// World owns tiles and items, the mod just has Builder objects
std::vector<std::unique_ptr<Tile>> tiles_;
// These things get filled in when the ctor loads mods.
std::vector<Tile> tiles_;
std::unordered_map<std::string, Tile::ID> tilesMap_;
std::unordered_map<std::string, std::unique_ptr<Item>> items_;
std::unordered_map<std::string, Item> items_;
std::unordered_map<std::string, WorldGen::Factory> worldGenFactories_;
std::unordered_map<std::string, EntityCollection::Factory> entCollFactories_;

// Mods give us factories to create new world gens and new entity collections
std::unordered_map<std::string, WorldGen::Factory> worldgenFactories_;
std::vector<EntityCollection::Factory> entCollFactories_;
// These things get initialized in the ctor.
// the above members must be initialized before these.
Game *game_; // TODO: reference, not pointer
std::mt19937 random_;
std::vector<ModWrapper> mods_;
Cygnet::ResourceManager resources_;

BodyTrait::Body *player_;
Game *game_;

std::mt19937 random_;
ResourceManager resources_;

private:
class ChunkRenderer {
@@ -71,6 +75,9 @@ private:
void tick(WorldPlane &plane, ChunkPos abspos);
};

std::vector<ModWrapper> loadMods(std::vector<std::string> paths);
Cygnet::ResourceManager buildResources();

ChunkRenderer chunkRenderer_;
WorldPlane::ID currentPlane_;
std::vector<std::unique_ptr<WorldPlane>> planes_;

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

@@ -2,6 +2,7 @@

#include <memory>
#include <SDL.h>
#include <cygnet/util.h>

#include "common.h"
#include "Chunk.h"
@@ -13,7 +14,6 @@ namespace Swan {

class World;
class WorldPlane;
class ImageResource;

class WorldGen {
public:
@@ -24,8 +24,8 @@ public:

virtual ~WorldGen() = default;

virtual void drawBackground(const Context &ctx, Win &win, Vec2 pos) = 0;
virtual SDL_Color backgroundColor(Vec2 pos) = 0;
virtual void drawBackground(const Context &ctx, Cygnet::Renderer &rnd, Vec2 pos) = 0;
virtual Cygnet::Color backgroundColor(Vec2 pos) = 0;

virtual void genChunk(WorldPlane &plane, Chunk &chunk) = 0;
virtual EntityRef spawnPlayer(const Context &ctx) = 0;

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

@@ -69,8 +69,8 @@ public:
EntityRef spawnPlayer();
void breakTile(TilePos pos);

SDL_Color backgroundColor();
void draw(Win &win);
Cygnet::Color backgroundColor();
void draw(Cygnet::Renderer &rnd);
void update(float dt);
void tick(float dt);

@@ -88,8 +88,6 @@ public:
std::mutex mut_;

private:
std::unique_ptr<LightServer> lighting_;

std::map<std::pair<int, int>, Chunk> chunks_;
std::vector<Chunk *> activeChunks_;
std::vector<std::pair<ChunkPos, Chunk *>> tickChunks_;
@@ -99,6 +97,12 @@ private:

std::deque<Chunk *> chunkInitList_;
std::vector<TilePos> debugBoxes_;

// The lighting server must destruct first. Until it has been destructed,
// it might call onLightChunkUpdated. If that happens after some other
// members have destructed, we have a problem.
// TODO: Rewrite this to not use a callback-based interface.
std::unique_ptr<LightServer> lighting_;
};

/*

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

@@ -0,0 +1,20 @@
#include <memory>
#include <unordered_map>
#include <string>

#include "util.h"

namespace Swan {

struct ImageAsset {
int width;
int frameHeight;
int frameCount;
std::unique_ptr<unsigned char[]> data;
};

Result<ImageAsset> loadImageAsset(
const std::unordered_map<std::string, std::string> modPaths,
std::string path);

}

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

@@ -6,6 +6,13 @@
#include <swan-common/Vector2.h>
#include <swan-common/constants.h>

// Forward declare the Cygnet::Renderer, because lots of functions will need
// to take a reference to it. It's nicer to not have to include Cygnet::Renderer
// in every header.
namespace Cygnet {
class Renderer;
}

namespace Swan {

using namespace SwanCommon;
@@ -16,14 +23,11 @@ using ChunkPos = Vec2i;
class Game;
class World;
class WorldPlane;
class Win;
class ResourceManager;

struct Context {
Game &game;
World &world;
WorldPlane &plane;
ResourceManager &resources;
};

}

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

@@ -4,19 +4,20 @@
#include <optional>
#include <initializer_list>
#include <utility>

#include "Win.h"
#include <cygnet/util.h>

namespace Swan {
namespace Draw {

SDL_Color linearGradient(
float val, std::initializer_list<std::pair<float, SDL_Color>> colors);
Cygnet::Color linearGradient(
float val, std::initializer_list<std::pair<float, Cygnet::Color>> colors);

/*
void parallaxBackground(
Win &win, SDL_Texture *tex,
std::optional<SDL_Rect> srcrect, std::optional<SDL_Rect> destrect,
float x, float y, float factor);
TODO */

}
}

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

@@ -12,10 +12,8 @@
#include <swan/ItemStack.h>
#include <swan/Mod.h>
#include <swan/OS.h>
#include <swan/Resource.h>
#include <swan/SlotVector.h>
#include <swan/Tile.h>
#include <swan/Win.h>
#include <swan/World.h>
#include <swan/WorldGen.h>
#include <swan/WorldPlane.h>

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

@@ -4,8 +4,6 @@

namespace Swan {

class Win;

struct BodyTrait {
struct Body;
struct Tag {};
@@ -32,7 +30,7 @@ struct BodyTrait {
Vec2 midRight() { return { right(), midY() }; }
Vec2 bottomRight() { return { right(), bottom() }; }

void outline(Win &win);
//void outline(Win &win); TODO
};
};


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

@@ -1,11 +1,11 @@

#pragma once
#pragma once

#include <optional>
#include <functional>
#include <memory>
#include <chrono>
#include <type_traits>
#include <string>
#include <stddef.h>

namespace Swan {
@@ -42,6 +42,94 @@ public:
~Deferred() { Func(); }
};

inline struct ResultOk {} Ok;
inline struct ResultErr {} Err;

// Result type for potential errors
template<typename T, typename Err = std::string>
class Result {
public:
Result(ResultOk, T &&val): isOk_(true), v_(ResultOk{}, std::move(val)) {}
Result(ResultErr, Err &&err): isOk_(false), v_(ResultErr{}, std::move(err)) {}

Result(const Result &other): isOk_(other.isOk_) {
if (isOk_) {
new (&v_.val) T(other.v_.val);
} else {
new (&v_.err) T(other.v_.err);
}
}

Result(Result &&other): isOk_(other.isOk_) {
if (other.isOk_) {
new (&v_.val) T(std::move(other.v_.val));
} else {
new (&v_.err) Err(std::move(other.v_.err));
}
}

~Result() {
destroy();
}

Result<T, Err> &operator=(const Result<T, Err> &other) {
destroy();
isOk_ = other.isOk_;
if (isOk_) {
new (&v_.val) T(other.v_.val);
} else {
new (&v_.err) Err(other.v_.err);
}
return *this;
}

Result<T, Err> &operator=(Result<T, Err> &&other) {
destroy();
isOk_ = other.isOk_;
if (other.isOk_) {
new (&v_.val) T(std::move(other.v_.val));
} else {
new (&v_.err) Err(std::move(other.v_.err));
}
return *this;
}

explicit operator bool() { return isOk_; }
bool isOk() { return isOk_; }

Err &err() { return v_.err; }
T &value() { return v_.val; }

T *operator->() {
return &v_.val;
}

T &operator*() {
return v_.val;
}

private:
void destroy() {
if (isOk_) {
v_.val.~T();
} else {
v_.err.~Err();
}
}

bool isOk_;
union U {
U() {}
U(ResultOk, T &&val): val(std::move(val)) {}
U(ResultOk, const T &val): val(val) {}
U(ResultErr, Err &&err): err(std::move(err)) {}
U(ResultErr, const Err &err): err(err) {}
~U() {}
T val;
Err err;
} v_;
};

// Calling begin/end is stupid...
template<typename T>
auto callBegin(T &v) {

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

@@ -1,7 +1,6 @@
#include "Animation.h"

#include "Win.h"
#include "gfxutil.h"
#include <cygnet/Renderer.h>

namespace Swan {

@@ -11,16 +10,12 @@ void Animation::tick(float dt) {
timer_ += interval_;

frame_ += 1;
if (frame_ >= resource_.numFrames_)
if (frame_ >= sprite_.frameCount) {
frame_ = 0;
}
}
}

void Animation::draw(const Vec2 &pos, Win &win) {
SDL_Rect rect = resource_.frameRect(frame_);
win.showTexture(pos, resource_.texture_.get(), &rect, { .flip = flip_ });
}

void Animation::reset() {
timer_ = interval_;
frame_ = 0;

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

@@ -10,11 +10,10 @@
#include "gfxutil.h"
#include "World.h"
#include "Game.h"
#include "Win.h"

namespace Swan {

void Chunk::compress() {
void Chunk::compress(Cygnet::Renderer &rnd) {
if (isCompressed())
return;

@@ -32,7 +31,6 @@ void Chunk::compress() {
data_.reset(new uint8_t[destlen]);
memcpy(data_.get(), dest, destlen);

texture_.reset();
compressedSize_ = destlen;

info
@@ -46,6 +44,9 @@ void Chunk::compress() {
} else {
warn << "Chunk compression error: " << ret << " (Out of memory?)";
}

rnd.destroyChunk(renderChunk_);
rnd.destroyChunkShadow(renderChunkShadow_);
}

void Chunk::decompress() {
@@ -64,130 +65,39 @@ void Chunk::decompress() {
}

data_ = std::move(dest);
needRender_ = true;

info
<< "Decompressed chunk " << pos_ << " from "
<< compressedSize_ << " bytes to "
<< DATA_SIZE << " bytes.";
compressedSize_ = -1;
}

void Chunk::renderLight(const Context &ctx, SDL_Renderer *rnd) {
std::optional<RenderTarget> target;

// The texture might not be created yet
if (!lightTexture_) {
lightTexture_.reset(SDL_CreateTexture(
ctx.game.win_.renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET,
CHUNK_WIDTH, CHUNK_HEIGHT));
SDL_SetTextureBlendMode(lightTexture_.get(), SDL_BLENDMODE_BLEND);

target.emplace(rnd, texture_.get());
} else {
target.emplace(rnd, texture_.get());
}

// Fill light texture
target.emplace(rnd, lightTexture_.get());
RenderBlendMode mode(rnd, SDL_BLENDMODE_NONE);
RenderDrawColor color(rnd, 0, 0, 0, 0);
for (int y = 0; y < CHUNK_HEIGHT; ++y) {
for (int x = 0; x < CHUNK_WIDTH; ++x) {
int b = getLightLevel({ x, y });
color.change(0, 0, 0, 255 - b);
SDL_Rect rect{ x, y, 1, 1 };
SDL_RenderFillRect(rnd, &rect);
}
}

needLightRender_ = false;
needChunkRender_ = true;
}

void Chunk::render(const Context &ctx, SDL_Renderer *rnd) {
std::optional<RenderTarget> target;

// The texture might not be created yet
if (!texture_) {
texture_.reset(SDL_CreateTexture(
ctx.game.win_.renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET,
CHUNK_WIDTH * TILE_SIZE, CHUNK_HEIGHT * TILE_SIZE));
SDL_SetTextureBlendMode(texture_.get(), SDL_BLENDMODE_BLEND);
target.emplace(rnd, texture_.get());

RenderBlendMode mode(rnd, SDL_BLENDMODE_NONE);
RenderDrawColor color(rnd, 0, 0, 0, 0);
SDL_Rect rect{ 0, 0, CHUNK_WIDTH * TILE_SIZE, CHUNK_HEIGHT * TILE_SIZE };
SDL_RenderFillRect(rnd, &rect);
} else {
target.emplace(rnd, texture_.get());
}

// We're caching tiles so we don't have to world.getTileByID() every time
Tile::ID prevID = Tile::INVALID_ID;
Tile *tile = ctx.game.invalidTile_.get();

// Fill tile texture
for (int y = 0; y < CHUNK_HEIGHT; ++y) {
for (int x = 0; x < CHUNK_WIDTH; ++x) {
Tile::ID id = getTileID(RelPos(x, y));
if (id != prevID) {
prevID = id;
tile = &ctx.world.getTileByID(id);
}

SDL_Rect dest{x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE};
SDL_RenderCopy(rnd, tile->image.texture_.get(), nullptr, &dest);
}
}

needRender_ = false;

renderLight(ctx, rnd);
}

void Chunk::renderList(SDL_Renderer *rnd) {
// Here, we know that the texture is created.
// We still wanna render directly to the target texture
RenderTarget target(rnd, texture_.get());

// We must make sure the blend mode is NONE, because we want transparent
// pixels to actually overwrite non-transparent pixels
RenderBlendMode mode(rnd, SDL_BLENDMODE_NONE);

// When we FillRect, we must fill transparency.
RenderDrawColor color(rnd, 0, 0, 0, 0);

for (auto &[pos, tex]: drawList_) {
SDL_Rect dest{pos.x * TILE_SIZE, pos.y * TILE_SIZE, TILE_SIZE, TILE_SIZE};
SDL_RenderFillRect(rnd, &dest);
SDL_RenderCopy(rnd, tex, nullptr, &dest);
}
}

void Chunk::draw(const Context &ctx, Win &win) {
void Chunk::draw(const Context &ctx, Cygnet::Renderer &rnd) {
if (isCompressed())
return;

// The world plane is responsible for managing initial renders
if (needRender_)
return;

// We're responsible for the light level rendering though
if (needLightRender_)
renderLight(ctx, win.renderer_);

if (drawList_.size() > 0) {
renderList(win.renderer_);
drawList_.clear();
if (needChunkRender_) {
renderChunk_ = rnd.createChunk(getTileData());
renderChunkShadow_ = rnd.createChunkShadow(getLightData());
needChunkRender_ = false;
needLightRender_ = false;
} else {
for (auto &change: changeList_) {
rnd.modifyChunk(renderChunk_, change.first, change.second);
}
}

auto chunkpos = pos_ * Vec2i(CHUNK_WIDTH, CHUNK_HEIGHT);
SDL_Rect rect{ 0, 0, CHUNK_WIDTH * TILE_SIZE, CHUNK_HEIGHT * TILE_SIZE };
win.showTexture(chunkpos, texture_.get(), &rect);
if (needLightRender_) {
rnd.modifyChunkShadow(renderChunkShadow_, getLightData());
needLightRender_ = false;
}

SDL_Rect texrect{ 0, 0, CHUNK_WIDTH, CHUNK_HEIGHT };
win.showTexture(chunkpos, lightTexture_.get(), &texrect, &rect);
Vec2 pos = (Vec2)pos_ * Vec2{CHUNK_WIDTH, CHUNK_HEIGHT};
rnd.drawChunk(renderChunk_, pos);
rnd.drawChunkShadow(renderChunkShadow_, pos);
}

Chunk::TickAction Chunk::tick(float dt) {

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

@@ -7,31 +7,11 @@
#include "log.h"
#include "Tile.h"
#include "OS.h"
#include "Win.h"

namespace Swan {

std::optional<ModWrapper> Game::loadMod(std::string path, World &world) {
OS::Dynlib dl(path + "/mod");
auto create = dl.get<Mod *(*)(World &)>("mod_create");
if (create == NULL) {
warn << path << ": No 'mod_create' function!";
return std::nullopt;
}

std::unique_ptr<Mod> mod(create(world));
return std::make_optional<ModWrapper>(
std::move(mod), std::move(path), std::move(dl));
}

void Game::createWorld(const std::string &worldgen, const std::vector<std::string> &modpaths) {
world_.reset(new World(this, time(NULL)));

for (auto &modpath: modpaths) {
auto mod = loadMod(modpath, *world_);
if (mod)
world_->addMod(std::move(*mod));
}
void Game::createWorld(const std::string &worldgen, const std::vector<std::string> &modPaths) {
world_.reset(new World(this, time(NULL), modPaths));

world_->setWorldGen(worldgen);
world_->setCurrentPlane(world_->addPlane());
@@ -39,29 +19,28 @@ void Game::createWorld(const std::string &worldgen, const std::vector<std::strin
}

TilePos Game::getMouseTile() {
auto mousePos = getMousePos();
return TilePos(
(int)floor(win_.cam_.x + mousePos.x / (Swan::TILE_SIZE * win_.zoom_)),
(int)floor(win_.cam_.y + mousePos.y / (Swan::TILE_SIZE * win_.zoom_)));
auto pos = (getMousePos() * 2 - renderer_.winScale()) / cam_.zoom + cam_.pos;
return TilePos{(int)floor(pos.x), (int)floor(pos.y)};
}

SDL_Color Game::backgroundColor() {
Cygnet::Color Game::backgroundColor() {
return world_->backgroundColor();
}

void Game::draw() {
world_->draw(win_);
world_->draw(renderer_);
renderer_.draw(cam_);
}

void Game::update(float dt) {
world_->update(dt);

// Zoom the window using the scroll wheel
win_.zoom_ += (float)wasWheelScrolled() * 0.1f * win_.zoom_;
if (win_.zoom_ > 3)
win_.zoom_ = 3;
else if (win_.zoom_ < 0.3)
win_.zoom_ = 0.3;
cam_.zoom += (float)wasWheelScrolled() * 0.1f * cam_.zoom;
if (cam_.zoom > 1)
cam_.zoom = 1;
else if (cam_.zoom < 0.025)
cam_.zoom = 0.025;

world_->update(dt);

didScroll_ = 0;
didPressKeys_.reset();

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

@@ -1,16 +0,0 @@
#include "Item.h"

#include "Resource.h"
#include "Game.h"
#include "common.h"

namespace Swan {

std::unique_ptr<Item> Item::createInvalid(Context &ctx) {
return std::make_unique<Item>(ctx.resources, Builder{
.name = "@::invalid",
.image = "@::invalid",
});
}

}

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

@@ -161,13 +161,11 @@ void LightServer::processEvent(const Event &evt, std::vector<NewLightChunk> &new
break;

case Event::Tag::LIGHT_ADDED:
info << cpos << ": Add " << evt.f << " light to " << rpos;
ch->lightSources[rpos] += evt.f;
markChunksModified(cpos, rpos, ch->lightSources[rpos]);
break;

case Event::Tag::LIGHT_REMOVED:
info << cpos << ": Remove " << evt.f << " light to " << rpos;
markChunksModified(cpos, rpos, ch->lightSources[rpos]);
ch->lightSources[rpos] -= evt.f;
if (ch->lightSources[rpos] < LIGHT_CUTOFF) {
@@ -534,8 +532,6 @@ void LightServer::run() {
buf.clear();
newChunks.clear();

auto start = std::chrono::steady_clock::now();

for (auto &pos: updatedChunks_) {
auto ch = chunks_.find(pos);
if (ch != chunks_.end()) {
@@ -574,11 +570,6 @@ void LightServer::run() {
}
}

auto end = std::chrono::steady_clock::now();
auto dur = std::chrono::duration<double, std::milli>(end - start);
info << "Generating light for " << updatedChunks_.size()
<< " chunks took " << dur.count() << "ms";

for (auto &pos: updatedChunks_) {
auto ch = chunks_.find(pos);
if (ch != chunks_.end()) {

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

@@ -1,63 +0,0 @@
#include "Mod.h"

#include <stdio.h>
#include <algorithm>

#include "util.h"
#include "log.h"

namespace Swan {

void Mod::registerImage(const std::string &id) {
images_.push_back(name_ + "/" + id);
info << " Adding image: " << images_.back();
}

void Mod::registerTile(Tile::Builder tile) {
tile.name = name_ + "::" + tile.name;
tiles_.push_back(tile);
info << " Adding tile: " << tile.name;
}

void Mod::registerItem(Item::Builder item) {
item.name = name_ + "::" + item.name;
items_.push_back(item);
info << " Adding item: " << item.name;
}

Iter<std::unique_ptr<ImageResource>> ModWrapper::buildImages(SDL_Renderer *renderer) {
return map(begin(mod_->images_), end(mod_->images_),
[renderer, this](const std::string &id) {
return std::make_unique<ImageResource>(renderer, path_, id);
});
}

Iter<std::unique_ptr<Tile>> ModWrapper::buildTiles(const ResourceManager &resources) {
return map(begin(mod_->tiles_), end(mod_->tiles_),
[&](const Tile::Builder &builder) {
return std::make_unique<Tile>(resources, builder);
});
}

Iter<std::unique_ptr<Item>> ModWrapper::buildItems(const ResourceManager &resources) {
return map(begin(mod_->items_), end(mod_->items_),
[&](const Item::Builder &builder) {
return std::make_unique<Item>(resources, builder);
});
}

Iter<WorldGen::Factory> ModWrapper::getWorldGens() {
return map(begin(mod_->worldgens_), end(mod_->worldgens_),
[](WorldGen::Factory &fact) {
return fact;
});
}

Iter<EntityCollection::Factory> ModWrapper::getEntities() {
return map(begin(mod_->entities_), end(mod_->entities_),
[](EntityCollection::Factory &fact){
return fact;
});
}

}

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

@@ -1,132 +0,0 @@
#include "Resource.h"

#include <stdio.h>
#include <SDL_image.h>
#include <regex>
#include <cpptoml.h>
#include <string.h>
#include <errno.h>

#include "log.h"
#include "common.h"
#include "Win.h"

namespace Swan {

ImageResource::ImageResource(
SDL_Renderer *renderer, const std::string &modpath, const std::string &id) {
static std::regex first_part_re("^.*?/");

SDL_RendererInfo rinfo;
if (SDL_GetRendererInfo(renderer, &rinfo) < 0) {
panic << "GetRendererInfo failed: " << SDL_GetError();
abort();
}

uint32_t format = rinfo.texture_formats[0];
int bpp = 32;
uint32_t rmask, gmask, bmask, amask;
if (SDL_PixelFormatEnumToMasks(format, &bpp, &rmask, &gmask, &bmask, &amask) < 0) {
panic << "PixelFormatEnumToMasks failed: " << SDL_GetError();
abort();
}

std::string assetpath = modpath + "/assets/" +
std::regex_replace(id, first_part_re, "");

surface_.reset(IMG_Load((assetpath + ".png").c_str()));

// If we don't have a surface yet (either loading or conversion failed),
// create a placeholder
if (!surface_) {
warn << "Loading image " << id << " failed: " << SDL_GetError();

surface_.reset(SDL_CreateRGBSurface(
0, TILE_SIZE, TILE_SIZE, bpp, rmask, gmask, bmask, amask));
SDL_FillRect(surface_.get(), NULL, SDL_MapRGB(surface_->format,
PLACEHOLDER_RED, PLACEHOLDER_GREEN, PLACEHOLDER_BLUE));
}

frameHeight_ = 32;

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

texture_.reset(SDL_CreateTextureFromSurface(renderer, surface_.get()));
if (!texture_) {
panic << "CreateTexture failed: " << SDL_GetError();
abort();
}

numFrames_ = surface_->h / frameHeight_;
name_ = id;
}

ImageResource::ImageResource(
SDL_Renderer *renderer, const std::string &name,
int w, int h, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {

surface_.reset(SDL_CreateRGBSurface(
0, TILE_SIZE, TILE_SIZE, 32,
0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff));
SDL_FillRect(surface_.get(), NULL, SDL_MapRGBA(surface_->format, r, g, b, a));

texture_.reset(SDL_CreateTextureFromSurface(renderer, surface_.get()));
if (!texture_) {
panic << "CreateTexture failed: " << SDL_GetError();
abort();
}

frameHeight_ = h;
numFrames_ = 1;
name_ = name;
}

void ImageResource::tick(float dt) {
switchTimer_ -= dt;
if (switchTimer_ <= 0) {
switchTimer_ += switchInterval_;
frame_ += 1;
if (frame_ >= numFrames_)
frame_ = 0;
}
}

ResourceManager::ResourceManager(Win &win) {
addImage(std::make_unique<ImageResource>(
win.renderer_, "@::invalid", TILE_SIZE, TILE_SIZE,
PLACEHOLDER_RED, PLACEHOLDER_GREEN, PLACEHOLDER_BLUE));
addImage(std::make_unique<ImageResource>(
win.renderer_, "@::air", TILE_SIZE, TILE_SIZE,
0, 0, 0, 0));
}

void ResourceManager::tick(float dt) {
for (auto &[k, v]: images_) {
v->tick(dt);
}
}

ImageResource &ResourceManager::getImage(const std::string &name) const {
auto it = images_.find(name);
if (it == end(images_)) {
warn << "Couldn't find image " << name << "!";
return getImage("@::invalid");
}
return *it->second;
}

}

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

@@ -1,25 +0,0 @@
#include "Tile.h"

#include "common.h"
#include <Game.h>

namespace Swan {

Tile::ID Tile::INVALID_ID = 0;

std::unique_ptr<Tile> Tile::createInvalid(const ResourceManager &resources) {
return std::make_unique<Tile>(resources, Builder{
.name = "@::invalid",
.image = "@::invalid",
});
}

std::unique_ptr<Tile> Tile::createAir(const ResourceManager &resources) {
return std::make_unique<Tile>(resources, Builder{
.name = "@::air",
.image = "@::air",
.isSolid = false,
});
}

}

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

@@ -1,11 +1,12 @@
#include "World.h"

#include <algorithm>
#include <tuple>

#include "log.h"
#include "Game.h"
#include "Win.h"
#include "Clock.h"
#include "assets.h"

namespace Swan {

@@ -16,21 +17,199 @@ static void chunkLine(int l, WorldPlane &plane, ChunkPos &abspos, const Vec2i &d
}
}

World::World(Game *game, unsigned long randSeed):
game_(game), random_(randSeed), resources_(game->win_) {
std::vector<ModWrapper> World::loadMods(std::vector<std::string> paths) {
std::vector<ModWrapper> mods;
mods.reserve(paths.size());

std::unique_ptr<Tile> invalidTile = Tile::createInvalid(resources_);
tilesMap_[invalidTile->name] = 0;
for (auto &path: paths) {
OS::Dynlib dl(path + "/mod");
auto create = dl.get<Mod *(*)(World &)>("mod_create");
if (create == NULL) {
warn << path << ": No 'mod_create' function!";
continue;
}

// tiles_ is empty, so pushing back now will ensure invalid_tile
// ends up at location 0
tiles_.push_back(std::move(invalidTile));
std::unique_ptr<Mod> mod(create(*this));
mods.push_back(ModWrapper(std::move(mod), std::move(path), std::move(dl)));
}

return mods;
}

Cygnet::ResourceManager World::buildResources() {
Cygnet::ResourceBuilder builder(game_->renderer_);

auto fillTileImage = [&](unsigned char *data, int r, int g, int b, int a) {
for (size_t i = 0; i < TILE_SIZE * TILE_SIZE; ++i) {
data[i * 4 + 0] = r;
data[i * 4 + 1] = g;
data[i * 4 + 2] = b;
data[i * 4 + 2] = a;
}
};

struct ImageAsset fallbackImage = {
.width = 32,
.frameHeight = 32,
.frameCount = 1,
.data = std::make_unique<unsigned char[]>(TILE_SIZE * TILE_SIZE * 4),
};
fillTileImage(fallbackImage.data.get(),
PLACEHOLDER_RED, PLACEHOLDER_GREEN, PLACEHOLDER_BLUE, 255);

auto airImage = std::make_unique<unsigned char[]>(TILE_SIZE * TILE_SIZE * 4);
fillTileImage(airImage.get(),
PLACEHOLDER_RED, PLACEHOLDER_GREEN, PLACEHOLDER_BLUE, 255);

// Let tile ID 0 be the invalid tile
builder.addTile(INVALID_TILE_ID, fallbackImage.data.get());
tilesMap_[INVALID_TILE_NAME] = INVALID_TILE_ID;
tiles_.push_back(Tile(INVALID_TILE_ID, INVALID_TILE_NAME, {
.name = "", .image = "", // Not used in this case
.isSolid = false,
}));
items_.emplace(INVALID_TILE_NAME, Item(INVALID_TILE_ID, INVALID_TILE_NAME, {
.name = "", .image = "", // Not used in this case
}));

// ...And tile ID 1 be the air tile
builder.addTile(AIR_TILE_ID, std::move(airImage));
tilesMap_[AIR_TILE_NAME] = AIR_TILE_ID;
tiles_.push_back(Tile(AIR_TILE_ID, AIR_TILE_NAME, {
.name = "", .image = "", // Not used in this case
.isSolid = false,
}));
items_.emplace(AIR_TILE_NAME, Item(AIR_TILE_ID, AIR_TILE_NAME, {
.name = "", .image = "", // Not used in this case
}));

// Assets are namespaced on the mod, so if something references, say,
// "core::stone", we need to know which directory the "core" mod is in
std::unordered_map<std::string, std::string> modPaths;
for (auto &mod: mods_) {
modPaths[mod.name()] = mod.path_;
}

auto loadTileImage = [&](std::string path) -> Result<ImageAsset> {
// Don't force all tiles/items to have an associated image.
// It could be that some tiles/items exist for a purpose which implies
// it should never actually be visible.
if (path == INVALID_TILE_NAME) {
ImageAsset asset{
.width = 32,
.frameHeight = 32,
.frameCount = 1,
.data = std::make_unique<unsigned char[]>(TILE_SIZE * TILE_SIZE * 4),
};
memcpy(asset.data.get(), fallbackImage.data.get(), TILE_SIZE * TILE_SIZE * 4);
return {Ok, std::move(asset)};
}

auto image = loadImageAsset(modPaths, path);
if (!image) {
warn << '\'' << path << "': " << image.err();
return {Err, '\'' + path + "': " + image.err()};
} else if (image->width != TILE_SIZE) {
warn << '\'' << path << "': Width must be " << TILE_SIZE << " pixels";
return {Err, '\'' + path + "': Width must be " + std::to_string(TILE_SIZE) + " pixels"};
} else {
return image;
}
};

// Need to fill in every tile before we do items,
// because all items will end up after all tiles in the tile atlas.
// In the rendering system, there's no real difference between a tile
// and an item.
for (auto &mod: mods_) {
for (auto &tileBuilder: mod.tiles()) {
auto image = loadTileImage(tileBuilder.image);

std::string tileName = mod.name() + "::" + tileBuilder.name;
Tile::ID tileId = tiles_.size();

if (image) {
builder.addTile(tileId, std::move(image->data));
} else {
warn << image.err();
builder.addTile(tileId, fallbackImage.data.get());
}

tilesMap_[tileName] = tileId;
tiles_.push_back(Tile(tileId, tileName, tileBuilder));

// All tiles should have an item.
// Some items will be overwritten later my mod_->items,
// but if not, this is their default item.
items_.emplace(tileName, Item(tileId, tileName, {
.name = "", .image = "", // Not used in this case
}));
}
}

// Put all items after all the tiles
Tile::ID nextItemId = tiles_.size();

// Load all items which aren't just tiles in disguise.
for (auto &mod: mods_) {
for (auto &itemBuilder: mod.items()) {
auto image = loadTileImage(itemBuilder.image);

std::string itemName = mod.name() + "::" + itemBuilder.name;
Tile::ID itemId = nextItemId++;

if (image) {
builder.addTile(itemId, std::move(image->data));
} else {
warn << image.err();
builder.addTile(itemId, fallbackImage.data.get());
}

items_.emplace(itemName, Item(itemId, itemName, itemBuilder));
}
}

// We're also going to need an air tile at location 1
tiles_.push_back(Tile::createAir(resources_));
tilesMap_["@::air"] = 1;
// Load sprites
for (auto &mod: mods_) {
for (auto spritePath: mod.sprites()) {
std::string path = mod.name() + "::" + spritePath;
auto image = loadImageAsset(modPaths, path);

if (image) {
builder.addSprite(
path, image->data.get(), image->width,
image->frameHeight * image->frameCount,
image->frameHeight);
} else {
warn << '\'' << path << "': " << image.err();
builder.addSprite(
path, fallbackImage.data.get(), fallbackImage.width,
fallbackImage.frameHeight * fallbackImage.frameCount,
fallbackImage.frameHeight);
}
}
}

// Load world gens and entities
for (auto &mod: mods_) {
for (auto &worldGenFactory: mod.worldGens()) {
std::string name = mod.name() + "::" + worldGenFactory.name;
worldGenFactories_.emplace(name, worldGenFactory);
}

for (auto &entCollFactory: mod.entities()) {
std::string name = mod.name() + "::" + entCollFactory.name;
entCollFactories_.emplace(name, entCollFactory);
}
}

return Cygnet::ResourceManager(std::move(builder));
}

World::World(Game *game, unsigned long randSeed, std::vector<std::string> modPaths):
game_(game), random_(randSeed), mods_(loadMods(std::move(modPaths))),
resources_(buildResources()) {}

void World::ChunkRenderer::tick(WorldPlane &plane, ChunkPos abspos) {
ZoneScopedN("World::ChunkRenderer tick");
int l = 0;
@@ -46,37 +225,6 @@ void World::ChunkRenderer::tick(WorldPlane &plane, ChunkPos abspos) {
}
}

void World::addMod(ModWrapper &&mod) {
info << "World: adding mod " << mod.mod_->name_;

for (auto i: mod.buildImages(game_->win_.renderer_)) {
resources_.addImage(std::move(i));
}

for (auto t: mod.buildTiles(resources_)) {
Tile::ID id = tiles_.size();
tilesMap_[t->name] = id;
tiles_.push_back(std::move(t));
}

for (auto i: mod.buildItems(resources_)) {
items_[i->name] = std::move(i);
}

for (auto fact: mod.getWorldGens()) {
worldgenFactories_.emplace(
std::piecewise_construct,
std::forward_as_tuple(fact.name),
std::forward_as_tuple(fact));
}

for (auto fact: mod.getEntities()) {
entCollFactories_.push_back(fact);
}

mods_.push_back(std::move(mod));
}

void World::setWorldGen(std::string gen) {
defaultWorldGen_ = std::move(gen);
}
@@ -92,16 +240,16 @@ void World::setCurrentPlane(WorldPlane &plane) {

WorldPlane &World::addPlane(const std::string &gen) {
WorldPlane::ID id = planes_.size();
auto it = worldgenFactories_.find(gen);
if (it == worldgenFactories_.end()) {
panic << "Tried to add plane with non-existant world gen " << gen << "!";
auto it = worldGenFactories_.find(gen);
if (it == worldGenFactories_.end()) {
panic << "Tried to add plane with non-existent world gen " << gen << "!";
abort();
}

std::vector<std::unique_ptr<EntityCollection>> colls;
colls.reserve(entCollFactories_.size());
for (auto &fact: entCollFactories_) {
colls.emplace_back(fact.create(fact.name));
colls.emplace_back(fact.second.create(fact.second.name));
}

WorldGen::Factory &factory = it->second;
@@ -111,21 +259,11 @@ WorldPlane &World::addPlane(const std::string &gen) {
return *planes_[id];
}

Item &World::getItem(const std::string &name) {
auto iter = items_.find(name);
if (iter == items_.end()) {
warn << "Tried to get non-existant item " << name << "!";
return *game_->invalidItem_;
}

return *iter->second;
}

Tile::ID World::getTileID(const std::string &name) {
auto iter = tilesMap_.find(name);
if (iter == tilesMap_.end()) {
warn << "Tried to get non-existant item " << name << "!";
return Tile::INVALID_ID;
warn << "Tried to get non-existent item " << name << "!";
return INVALID_TILE_ID;
}

return iter->second;
@@ -136,20 +274,41 @@ Tile &World::getTile(const std::string &name) {
return getTileByID(id);
}

SDL_Color World::backgroundColor() {
Item &World::getItem(const std::string &name) {
auto iter = items_.find(name);
if (iter == items_.end()) {
warn << "Tried to get non-existent item " << name << "!";
return items_.at(INVALID_TILE_NAME);
}

return iter->second;
}

Cygnet::RenderSprite &World::getSprite(const std::string &name) {
auto iter = resources_.sprites_.find(name);
if (iter == resources_.sprites_.end()) {
warn << "Tried to get non-existent sprite " << name << "!";
return resources_.sprites_.at(INVALID_TILE_NAME);
}

return iter->second;
}

Cygnet::Color World::backgroundColor() {
return planes_[currentPlane_]->backgroundColor();
}

void World::draw(Win &win) {
void World::draw(Cygnet::Renderer &rnd) {
ZoneScopedN("World draw");
win.cam_ = player_->pos - (win.getSize() / 2) + (player_->size / 2);
planes_[currentPlane_]->draw(win);
planes_[currentPlane_]->draw(rnd);
}

void World::update(float dt) {
ZoneScopedN("World update");
for (auto &plane: planes_)
plane->update(dt);

game_->cam_.pos = player_->pos + player_->size / 2;
}

void World::tick(float dt) {

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

@@ -8,7 +8,6 @@
#include "World.h"
#include "Game.h"
#include "Clock.h"
#include "Win.h"

namespace Swan {

@@ -38,7 +37,6 @@ Context WorldPlane::getContext() {
.game = *world_->game_,
.world = *world_,
.plane = *this,
.resources = world_->resources_
};
}

@@ -46,8 +44,8 @@ WorldPlane::WorldPlane(
ID id, World *world, std::unique_ptr<WorldGen> gen,
std::vector<std::unique_ptr<EntityCollection>> &&colls):
id_(id), world_(world), gen_(std::move(gen)),
lighting_(std::make_unique<LightServer>(*this)),
entColls_(std::move(colls)) {
entColls_(std::move(colls)),
lighting_(std::make_unique<LightServer>(*this)) {

for (auto &coll: entColls_) {
entCollsByType_[coll->type()] = coll.get();
@@ -72,7 +70,7 @@ Chunk &WorldPlane::getChunk(ChunkPos pos) {
}

Chunk &chunk = slowGetChunk(pos);
tickChunks_.push_back({ pos, &chunk });
tickChunks_.push_back({pos, &chunk});
return chunk;
}

@@ -99,7 +97,7 @@ Chunk &WorldPlane::slowGetChunk(ChunkPos pos) {
lc.blocks[y * CHUNK_HEIGHT + x] = true;
}
if (tile.lightLevel > 0) {
lc.lightSources[{ x, y }] = tile.lightLevel;
lc.lightSources[{x, y}] = tile.lightLevel;
}
}
}
@@ -124,8 +122,7 @@ void WorldPlane::setTileID(TilePos pos, Tile::ID id) {
if (id != old) {
Tile &newTile = world_->getTileByID(id);
Tile &oldTile = world_->getTileByID(old);
chunk.setTileID(rp, id, newTile.image.texture_.get());
chunk.markModified();
chunk.setTileID(rp, id);

if (!oldTile.isSolid && newTile.isSolid) {
lighting_->onSolidBlockAdded(pos);
@@ -199,17 +196,17 @@ void WorldPlane::breakTile(TilePos pos) {
world_->evtTileBreak_.emit(getContext(), pos, world_->getTileByID(id));
}

SDL_Color WorldPlane::backgroundColor() {
Cygnet::Color WorldPlane::backgroundColor() {
return gen_->backgroundColor(world_->player_->pos);
}

void WorldPlane::draw(Win &win) {
void WorldPlane::draw(Cygnet::Renderer &rnd) {
ZoneScopedN("WorldPlane draw");
std::lock_guard<std::mutex> lock(mut_);
auto ctx = getContext();
auto &pbody = *(world_->player_);

gen_->drawBackground(ctx, win, pbody.pos);
gen_->drawBackground(ctx, rnd, pbody.pos);

ChunkPos pcpos = ChunkPos(
(int)floor(pbody.pos.x / CHUNK_WIDTH),
@@ -217,27 +214,34 @@ void WorldPlane::draw(Win &win) {

// Just init one chunk per frame
if (chunkInitList_.size() > 0) {
/*
Chunk *chunk = chunkInitList_.front();
chunkInitList_.pop_front();
chunk->render(ctx, win.renderer_);
TODO */
}

for (int x = -1; x <= 1; ++x) {
for (int y = -1; y <= 1; ++y) {
auto iter = chunks_.find(pcpos + ChunkPos(x, y));
if (iter != chunks_.end())
iter->second.draw(ctx, win);
iter->second.draw(ctx, rnd);
}
}

for (auto &coll: entColls_)
coll->draw(ctx, win);
for (auto &coll: entColls_) {
coll->draw(ctx, rnd);
}

lighting_->flip();

/*
if (debugBoxes_.size() > 0) {
for (auto &pos: debugBoxes_) {
win.drawRect(pos, Vec2(1, 1));
rnd.drawRect(pos, Vec2(1, 1));
}
}
TODO */
}

void WorldPlane::update(float dt) {
@@ -273,12 +277,13 @@ void WorldPlane::tick(float dt) {
switch (action) {
case Chunk::TickAction::DEACTIVATE:
info << "Compressing inactive modified chunk " << chunk->pos_;
chunk->compress();
chunk->compress(world_->game_->renderer_);
iter = activeChunks_.erase(iter);
last = activeChunks_.end();
break;
case Chunk::TickAction::DELETE:
info << "Deleting inactive unmodified chunk " << chunk->pos_;
chunk->destroy(world_->game_->renderer_);
chunks_.erase(chunk->pos_);
iter = activeChunks_.erase(iter);
last = activeChunks_.end();

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

@@ -0,0 +1,69 @@
#include "assets.h"

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

namespace Swan {

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

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

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

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

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

int frameHeight = surface->h;

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

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

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

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

}

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

@@ -8,11 +8,11 @@
namespace Swan {
namespace Draw {

static Uint8 linearLine(float from, float to, float frac) {
return (Uint8)std::clamp(to * frac + from * (1 - frac), 0.0f, 255.0f);
static float linearLine(float from, float to, float frac) {
return std::clamp(to * frac + from * (1 - frac), 0.0f, 255.0f);
}

static SDL_Color linearColor(SDL_Color from, SDL_Color to, float frac) {
static Cygnet::Color linearColor(Cygnet::Color from, Cygnet::Color to, float frac) {
return {
.r = linearLine(from.r, to.r, frac),
.g = linearLine(from.g, to.g, frac),
@@ -21,11 +21,11 @@ static SDL_Color linearColor(SDL_Color from, SDL_Color to, float frac) {
};
}

SDL_Color linearGradient(
Cygnet::Color linearGradient(
float val,
std::initializer_list<std::pair<float, SDL_Color>> colors) {
std::initializer_list<std::pair<float, Cygnet::Color>> colors) {

const std::pair<float, SDL_Color> *arr = colors.begin();
const std::pair<float, Cygnet::Color> *arr = colors.begin();
size_t size = colors.size();

if (val < arr[0].first)
@@ -44,6 +44,7 @@ SDL_Color linearGradient(
return arr[size - 1].second;
}

/*
void parallaxBackground(
Win &win, SDL_Texture *tex,
std::optional<SDL_Rect> srcrect, std::optional<SDL_Rect> destrect,
@@ -92,6 +93,7 @@ void parallaxBackground(
}
}
}
TODO */

}
}

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

@@ -1,11 +1,10 @@
#include "traits/BodyTrait.h"

#include "Win.h"

namespace Swan {

/*
void BodyTrait::Body::outline(Win &win) {
win.drawRect(pos, size);
}
} TODO */

}

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

@@ -1,11 +1,10 @@
#include "traits/PhysicsTrait.h"

#include "WorldPlane.h"
#include "Win.h"

namespace Swan {

static float epsilon = 0.001;
static float epsilon = 0.05;

static void collideX(
PhysicsTrait::Physics &phys, BodyTrait::Body &body,
@@ -13,16 +12,16 @@ static void collideX(
bool collided = false;

for (int y = (int)floor(body.top() + epsilon); y <= (int)floor(body.bottom() - epsilon); ++y) {
int lx = (int)floor(body.left() + epsilon);
Tile &left = plane.getTile({ lx, y });
int lx = (int)floor(body.left());
Tile &left = plane.getTile({lx, y});
if (left.isSolid) {
body.pos.x = (float)lx + 1.0;
collided = true;
break;
}

int rx = (int)floor(body.right() - epsilon);
Tile &right = plane.getTile({ rx, y });
int rx = (int)floor(body.right());
Tile &right = plane.getTile({rx, y});
if (right.isSolid) {
body.pos.x = (float)rx - body.size.x;
collided = true;
@@ -44,16 +43,16 @@ static void collideY(
phys.onGround = false;

for (int x = (int)floor(body.left() + epsilon); x <= (int)floor(body.right() - epsilon); ++x) {
int ty = (int)floor(body.top() + epsilon);
Tile &top = plane.getTile({ x, ty });
int ty = (int)floor(body.top());
Tile &top = plane.getTile({x, ty});
if (top.isSolid) {
body.pos.y = (float)ty + 1.0;
collided = true;
break;
}

int by = (int)floor(body.bottom() - epsilon);
Tile &bottom = plane.getTile({ x, by });
int by = (int)floor(body.bottom());
Tile &bottom = plane.getTile({x, by});
if (bottom.isSolid) {
body.pos.y = (float)by - body.size.y;
collided = true;

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

@@ -1,4 +1,3 @@
#include <cygnet/Context.h>
#include <cygnet/Window.h>
#include <cygnet/Renderer.h>
#include <cygnet/ResourceManager.h>
@@ -38,7 +37,7 @@ Cygnet::RenderSprite loadSprite(Cygnet::ResourceBuilder &builder, const char *pa
}

int main() {
Cygnet::Context ctx;
SDL_Init(SDL_INIT_VIDEO);
IMG_Init(IMG_INIT_PNG);
Cygnet::Window win("Cygnet Test", 680, 680);
Cygnet::Renderer rnd;
@@ -166,10 +165,10 @@ int main() {
y += 1 * dt;
}

rnd.drawChunk(chunk, { 0, 0 });
rnd.drawChunk(chunk, {0, 0});

rnd.drawSprite(playerSprite, { x, y }, (int)animAcc % 2);
cam.pos = { x + 0.5f, y + 0.5f };
rnd.drawSprite(playerSprite, Cygnet::Mat3gf{}.translate({x, y}), (int)animAcc % 2);
cam.pos = {x + 0.5f, y + 0.5f};

win.clear();
rnd.draw(cam);
@@ -178,4 +177,5 @@ int main() {

exit:
IMG_Quit();
SDL_Quit();
}

+ 39
- 39
src/main.cc View File

@@ -10,6 +10,8 @@
#include <string.h>
#include <imgui.h>
#include <imgui_sdl/imgui_sdl.h>
#include <cygnet/Renderer.h>
#include <cygnet/Window.h>

#include <swan/swan.h>

@@ -77,31 +79,20 @@ int main(int argc, char **argv) {
imgassert(IMG_Init(imgFlags) == imgFlags, "Could not initialize SDL_Image");
Deferred<IMG_Quit> sdlImage;

// Create the window
CPtr<SDL_Window, SDL_DestroyWindow> window(
SDL_CreateWindow(
"Project: SWAN",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
(int)(640 * guiScale), (int)(480 * guiScale), winFlags));
Cygnet::Window window("Project: SWAN", 640 * guiScale, 480 * guiScale);

// Load and display application icon
CPtr<SDL_Surface, SDL_FreeSurface> icon(
IMG_Load("assets/icon.png"));
sdlassert(icon, "Could not load icon");
SDL_SetWindowIcon(window.get(), icon.get());

CPtr<SDL_Renderer, SDL_DestroyRenderer> renderer(
SDL_CreateRenderer(window.get(), -1, renderFlags));
sdlassert(renderer, "Could not create renderer");
SDL_SetRenderDrawBlendMode(renderer.get(), SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(renderer.get(), 0, 0, 0, 255);

Win win(window.get(), renderer.get(), guiScale);
SDL_SetWindowIcon(window.sdlWindow(), icon.get());

// Init ImGUI and ImGUI_SDL
/*
IMGUI_CHECKVERSION();
CPtr<ImGuiContext, ImGui::DestroyContext> context(
ImGui::CreateContext());

ImGuiSDL::Initialize(renderer.get(), (int)win.getPixSize().x, (int)win.getPixSize().y);
Deferred<ImGuiSDL::Deinitialize> imguiSDL;
info << "Initialized with window size " << win.getPixSize();
@@ -109,9 +100,11 @@ int main(int argc, char **argv) {
// ImGuiIO is to glue SDL and ImGUI together
ImGuiIO& imguiIO = ImGui::GetIO();
imguiIO.BackendPlatformName = "imgui_sdl + Project: SWAN";
TODO */

// Create a world
Game game(win);
Game game;
game.cam_.size = window.size();
std::vector<std::string> mods{ "core.mod" };
game.createWorld("core::default", mods);

@@ -124,7 +117,6 @@ int main(int argc, char **argv) {
int slowFrames = 0;
while (1) {
ZoneScopedN("game loop");
RTClock totalTimeClock;

SDL_Event evt;
while (SDL_PollEvent(&evt)) {
@@ -135,9 +127,9 @@ int main(int argc, char **argv) {

case SDL_WINDOWEVENT:
if (evt.window.event == SDL_WINDOWEVENT_RESIZED) {
imguiIO.DisplaySize.x = (float)evt.window.data1;
imguiIO.DisplaySize.y = (float)evt.window.data2;
win.onResize(evt.window.data1, evt.window.data2);
window.onResize(evt.window.data1, evt.window.data2);
//imguiIO.DisplaySize.x = (float)evt.window.data1;
//imguiIO.DisplaySize.y = (float)evt.window.data2;
}
break;

@@ -150,32 +142,42 @@ int main(int argc, char **argv) {
break;

case SDL_MOUSEMOTION:
/*
imguiIO.MousePos.x = (float)evt.motion.x;
imguiIO.MousePos.y = (float)evt.motion.y;
if (!imguiIO.WantCaptureMouse)
if (!imguiIO.WantCaptureMouse) */
game.onMouseMove(evt.motion.x, evt.motion.y);
break;

case SDL_MOUSEBUTTONDOWN:
/*
imguiIO.MouseDown[sdlButtonToImGuiButton(evt.button.button)] = true;
if (!imguiIO.WantCaptureMouse)
if (!imguiIO.WantCaptureMouse) */
game.onMouseDown(evt.button.x, evt.button.y, evt.button.button);
break;

case SDL_MOUSEBUTTONUP:
/*
imguiIO.MouseDown[sdlButtonToImGuiButton(evt.button.button)] = false;
if (!imguiIO.WantCaptureMouse)
if (!imguiIO.WantCaptureMouse) */
game.onMouseUp(evt.button.x, evt.button.y, evt.button.button);
break;

case SDL_MOUSEWHEEL:
if (evt.wheel.y == 0) {
break;
}

/*
imguiIO.MouseWheel += (float)evt.wheel.y;
if (!imguiIO.WantCaptureMouse)
if (!imguiIO.WantCaptureMouse) */
game.onScrollWheel(evt.wheel.y);
break;
}
}

game.cam_.size = window.size();

auto now = std::chrono::steady_clock::now();
std::chrono::duration<float> dur(now - prevTime);
prevTime = now;
@@ -207,7 +209,6 @@ int main(int argc, char **argv) {
}

// Simple case: we can keep up, only need one physics update
RTClock updateClock;
if (dt <= 1 / 25.0) {
ZoneScopedN("game update");
game.update(dt);
@@ -216,9 +217,13 @@ int main(int argc, char **argv) {
} else {
int count = (int)ceil(dt / (1/30.0));
float delta = dt / (float)count;
info << "Delta time " << dt << "s. Running " << count
<< " updates in one frame, with a delta as if we had "
<< 1.0 / delta << " FPS.";

// Don't be too noisy with the occasional double update
if (count > 2) {
info << "Delta time " << dt << "s. Running " << count
<< " updates in one frame, with a delta as if we had "
<< 1.0 / delta << " FPS.";
}
for (int i = 0; i < count; ++i) {
ZoneScopedN("game update");
game.update(delta);
@@ -230,37 +235,32 @@ int main(int argc, char **argv) {
while (tickAcc >= 1.0 / TICK_RATE) {
ZoneScopedN("game tick");
tickAcc -= 1.0 / TICK_RATE;
RTClock tickClock;
game.tick(1.0 / TICK_RATE);
}

{
auto [r, g, b, a] = game.backgroundColor();
RenderDrawColor c(renderer.get(), r, g, b, a);
SDL_RenderClear(renderer.get());
window.clear(game.backgroundColor());
}

// ImGUI
imguiIO.DeltaTime = dt;
ImGui::NewFrame();
//imguiIO.DeltaTime = dt;
//ImGui::NewFrame();

{
ZoneScopedN("game draw");
RTClock drawClock;
game.draw();
}

// Render ImGUI
{
ZoneScopedN("imgui render");
ImGui::Render();
ImGuiSDL::Render(ImGui::GetDrawData());
//ImGui::Render();
//ImGuiSDL::Render(ImGui::GetDrawData());
}

RTClock presentClock;
{
ZoneScopedN("render present");
SDL_RenderPresent(renderer.get());
window.flip();
}
FrameMark
}

Loading…
Cancel
Save