New Cygnet is approaching feature completeness. Merging Cygnet into master before the imminent hard work of replacing the SDL renderer with Cygnet.fix/style
@@ -26,12 +26,12 @@ set(libraries | |||
if(CMAKE_BUILD_TYPE STREQUAL Sanitize OR CMAKE_BUILD_TYPE STREQUAL "") | |||
message(STATUS "Build mode: Sanitize") | |||
add_compile_options(-g -Og -fsanitize=address -fsanitize=undefined) | |||
add_compile_options(-g -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 -Og) | |||
add_compile_options(-g) | |||
elseif(CMAKE_BUILD_TYPE STREQUAL Optimize) | |||
message(STATUS "Build mode: Optimize") | |||
@@ -96,3 +96,7 @@ install(TARGETS swan DESTINATION swan) | |||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/assets DESTINATION swan) | |||
add_custom_target(check DEPENDS check_libswan) | |||
add_custom_target(cloc | |||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} | |||
COMMAND cloc core.mod src libcygnet libswan) |
@@ -0,0 +1,89 @@ | |||
#pragma once | |||
#include <iostream> | |||
#include <cmath> | |||
#include <array> | |||
#include "Vector2.h" | |||
namespace SwanCommon { | |||
template<typename T> | |||
struct Matrix3 { | |||
using Vec = Vector2<T>; | |||
static constexpr std::array<T, 9> identity = {1, 0, 0, 0, 1, 0, 0, 0, 1}; | |||
std::array<T, 9> vals; | |||
constexpr Matrix3(): vals(identity) {} | |||
constexpr Matrix3(const Matrix3 &mat): vals(mat.vals) {} | |||
constexpr Matrix3 &operator=(const Matrix3 &mat) { vals = mat.vals; } | |||
constexpr T *data() { | |||
return vals.data(); | |||
} | |||
constexpr const T *data() const { | |||
return vals.data(); | |||
} | |||
constexpr T &at(int x, int y) { | |||
return vals[y * 3 + x]; | |||
} | |||
constexpr const T &at(int x, int y) const { | |||
return vals[y * 3 + x]; | |||
} | |||
constexpr Matrix3<T> &set(std::initializer_list<T> vals) { | |||
this->vals = vals; | |||
return *this; | |||
} | |||
constexpr Matrix3<T> &reset() { | |||
vals = identity; | |||
return *this; | |||
} | |||
constexpr Matrix3<T> &translate(Vec vec) { | |||
at(2, 0) += vec.x; | |||
at(2, 1) += vec.y; | |||
return *this; | |||
} | |||
constexpr Matrix3<T> &scale(Vec vec) { | |||
at(0, 0) *= vec.x; | |||
at(1, 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; | |||
return *this; | |||
} | |||
static const Matrix3<T> IDENTITY; | |||
template<typename U> | |||
friend std::ostream &operator<<(std::ostream &os, const Matrix3<U> &mat); | |||
}; | |||
template<typename T> | |||
const Matrix3<T> Matrix3<T>::IDENTITY = Matrix3<T>(); | |||
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) << "))"; | |||
return os; | |||
} | |||
} |
@@ -23,27 +23,27 @@ struct Vector2 { | |||
return *this; | |||
} | |||
constexpr T length() { | |||
constexpr T length() const { | |||
return (T)std::sqrt((double)squareLength()); | |||
} | |||
constexpr T squareLength() { | |||
constexpr T squareLength() const { | |||
return this->x * this->x + this->y * this->y; | |||
} | |||
constexpr Vector2<T> sign() { | |||
constexpr Vector2<T> sign() const { | |||
return Vector2<T>(x > 0 ? 1 : -1, y > 0 ? 1 : -1); | |||
} | |||
constexpr Vector2<T> scale(T sx, T sy) { | |||
constexpr Vector2<T> scale(T sx, T sy) const { | |||
return Vector2<T>(x * sx, y * sy); | |||
} | |||
constexpr Vector2<T> scale(T s) { | |||
constexpr Vector2<T> scale(T s) const { | |||
return scale(s, s); | |||
} | |||
constexpr Vector2<T> norm() { | |||
constexpr Vector2<T> norm() const { | |||
return *this / length(); | |||
} | |||
@@ -0,0 +1,9 @@ | |||
#pragma once | |||
namespace SwanCommon { | |||
template<typename T> | |||
class Lock { | |||
}; | |||
} |
@@ -2,6 +2,7 @@ add_library(libcygnet SHARED | |||
src/Context.cc | |||
src/GlWrappers.cc | |||
src/Renderer.cc | |||
src/ResourceManager.cc | |||
src/shaders.cc | |||
src/TileAtlas.cc | |||
src/util.cc | |||
@@ -9,7 +10,6 @@ add_library(libcygnet SHARED | |||
target_include_directories(libcygnet | |||
PUBLIC "include" | |||
PRIVATE "include/cygnet") | |||
set_target_properties(libcygnet PROPERTIES OUTPUT_NAME cygnet) | |||
target_link_libraries(libcygnet swan-common GLESv2 ${libraries}) | |||
install(TARGETS libcygnet DESTINATION swan/libcygnet) |
@@ -21,7 +21,7 @@ public: | |||
}; | |||
GlShader(Type type, const char *source); | |||
~GlShader(); | |||
virtual ~GlShader(); | |||
GLuint id() const { return id_; } | |||
@@ -44,13 +44,11 @@ public: | |||
template <typename... T> | |||
GlProgram(const T &... shaders): GlProgram() { (addShader(shaders), ...); link(); } | |||
GlProgram(); | |||
~GlProgram(); | |||
virtual ~GlProgram(); | |||
void use(); | |||
GLuint id() const { return id_; } | |||
protected: | |||
GLint attribLoc(const char *name); | |||
GLint uniformLoc(const char *name); | |||
@@ -61,21 +59,4 @@ private: | |||
GLuint id_; | |||
}; | |||
class GlTexture { | |||
public: | |||
GlTexture(); | |||
void bind(); | |||
void upload(GLsizei width, GLsizei height, void *data, | |||
GLenum format, GLenum type); | |||
GLuint id() { return id_; } | |||
int width() { return w_; } | |||
int height() { return h_; } | |||
private: | |||
GLuint id_; | |||
int w_; | |||
int h_; | |||
}; | |||
} |
@@ -1,23 +1,96 @@ | |||
#pragma once | |||
#include <memory> | |||
#include <vector> | |||
#include <stdint.h> | |||
#include <swan-common/constants.h> | |||
#include <swan-common/Vector2.h> | |||
#include "util.h" | |||
namespace Cygnet { | |||
struct RendererState; | |||
struct RenderChunk { | |||
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; | |||
}; | |||
class Renderer { | |||
public: | |||
using TileID = uint16_t; | |||
Renderer(); | |||
~Renderer(); | |||
void draw(); | |||
void drawChunk(RenderChunk chunk, SwanCommon::Vec2 pos); | |||
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 draw(const RenderCamera &cam); | |||
void uploadTileAtlas(const void *data, int width, int height); | |||
void modifyTile(TileID id, const void *data); | |||
void registerTileTexture(size_t tileId, const void *data, size_t len); | |||
void uploadTileTexture(); | |||
RenderChunk createChunk( | |||
TileID tiles[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT]); | |||
void modifyChunk(RenderChunk chunk, SwanCommon::Vec2i pos, TileID id); | |||
void destroyChunk(RenderChunk chunk); | |||
RenderSprite createSprite(void *data, int width, int height, int fh); | |||
RenderSprite createSprite(void *data, int width, int height); | |||
void destroySprite(RenderSprite sprite); | |||
private: | |||
struct DrawChunk { | |||
SwanCommon::Vec2 pos; | |||
RenderChunk chunk; | |||
}; | |||
struct DrawSprite { | |||
Mat3gf transform; | |||
int frame; | |||
RenderSprite sprite; | |||
}; | |||
std::unique_ptr<RendererState> state_; | |||
std::vector<DrawChunk> draw_chunks_; | |||
std::vector<DrawSprite> draw_sprites_; | |||
}; | |||
inline void Renderer::drawChunk(RenderChunk chunk, SwanCommon::Vec2 pos) { | |||
draw_chunks_.emplace_back(pos, chunk); | |||
} | |||
inline void Renderer::drawSprite(RenderSprite sprite, Mat3gf mat, int frame) { | |||
draw_sprites_.emplace_back(mat, frame, sprite); | |||
} | |||
inline void Renderer::drawSprite(RenderSprite sprite, SwanCommon::Vec2 pos, int frame) { | |||
draw_sprites_.emplace_back(Mat3gf{}.translate(pos), frame, sprite); | |||
} | |||
inline void Renderer::drawSpriteFlipped(RenderSprite sprite, SwanCommon::Vec2 pos, int frame) { | |||
draw_sprites_.emplace_back(Mat3gf{}.translate(pos).scale({ -1, 1 }), frame, sprite); | |||
} | |||
} |
@@ -0,0 +1,91 @@ | |||
#pragma once | |||
#include <unordered_map> | |||
#include <optional> | |||
#include <memory> | |||
#include <stdint.h> | |||
#include <string.h> | |||
#include <swan-common/constants.h> | |||
#include "Renderer.h" | |||
#include "TileAtlas.h" | |||
namespace Cygnet { | |||
struct ResourceTileAnimation { | |||
uint16_t id; | |||
int frames; | |||
int index; | |||
std::unique_ptr<unsigned char[]> data; | |||
}; | |||
class ResourceManager; | |||
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); | |||
void addTile(Renderer::TileID id, void *data, int frames = 1); | |||
void addTile(Renderer::TileID id, std::unique_ptr<unsigned char[]> data, int frames = 1); | |||
private: | |||
Renderer &rnd_; | |||
std::unordered_map<std::string, RenderSprite> sprites_; | |||
std::vector<ResourceTileAnimation> tile_anims_; | |||
TileAtlas atlas_; | |||
friend ResourceManager; | |||
}; | |||
class ResourceManager { | |||
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::vector<ResourceTileAnimation> tile_anims_; | |||
}; | |||
inline RenderSprite ResourceBuilder::addSprite( | |||
std::string name, void *data, int width, int height, int fh) { | |||
return sprites_[std::move(name)] = rnd_.createSprite(data, width, height, fh); | |||
} | |||
inline RenderSprite ResourceBuilder::addSprite( | |||
std::string name, void *data, int width, int height) { | |||
return sprites_[std::move(name)] = rnd_.createSprite(data, width, height); | |||
} | |||
inline void ResourceBuilder::addTile(uint16_t id, void *data, int frames) { | |||
if (frames == 0) { | |||
atlas_.addTile(id, data); | |||
} else { | |||
auto ptr = std::make_unique<unsigned char[]>( | |||
SwanCommon::TILE_SIZE * SwanCommon::TILE_SIZE * 4 * frames); | |||
memcpy(ptr.get(), data, SwanCommon::TILE_SIZE * SwanCommon::TILE_SIZE * 4 * frames); | |||
addTile(id, std::move(ptr), frames); | |||
} | |||
} | |||
inline void ResourceBuilder::addTile(Renderer::TileID id, std::unique_ptr<unsigned char[]> data, int frames) { | |||
atlas_.addTile(id, data.get()); | |||
if (frames > 1) { | |||
tile_anims_.push_back({ | |||
.id = id, | |||
.frames = frames, | |||
.index = 0, | |||
.data = std::move(data), | |||
}); | |||
} | |||
} | |||
} |
@@ -11,7 +11,7 @@ public: | |||
TileAtlas(); | |||
~TileAtlas(); | |||
void addTile(size_t tileId, const void *data, size_t len); | |||
void addTile(size_t tileId, const void *data); | |||
const unsigned char *getImage(size_t *w, size_t *h); | |||
private: |
@@ -1,5 +1,6 @@ | |||
#pragma once | |||
#include <swan-common/Vector2.h> | |||
#include <memory> | |||
namespace Cygnet { | |||
@@ -15,6 +16,7 @@ public: | |||
void clear(); | |||
void flip(); | |||
void onResize(int w, int h); | |||
SwanCommon::Vec2i size() { return { w_, h_ }; } | |||
private: | |||
std::unique_ptr<WindowState> state_; |
@@ -0,0 +1 @@ | |||
#include <SDL_opengles2.h> |
@@ -2,10 +2,10 @@ | |||
namespace Cygnet::Shaders { | |||
extern const char *basicVx; | |||
extern const char *texturedVx; | |||
extern const char *spriteVx; | |||
extern const char *spriteFr; | |||
extern const char *solidColorFr; | |||
extern const char *texturedFr; | |||
extern const char *chunkVx; | |||
extern const char *chunkFr; | |||
} |
@@ -2,7 +2,8 @@ | |||
#include <stdexcept> | |||
#include <stdint.h> | |||
#include <SDL.h> | |||
#include "swan-common/Matrix3.h" | |||
namespace Cygnet { | |||
@@ -11,6 +12,9 @@ using GLint = int32_t; | |||
using GLuint = uint32_t; | |||
using GLsizei = int32_t; | |||
using GLenum = uint32_t; | |||
using GLfloat = float; | |||
using Mat3gf = SwanCommon::Matrix3<GLfloat>; | |||
struct SDLError: public std::exception { | |||
SDLError(std::string msg): message(std::move(msg)) {} |
@@ -2,6 +2,8 @@ | |||
#include <SDL.h> | |||
#include "util.h" | |||
namespace Cygnet { | |||
Context::Context() { | |||
@@ -13,7 +15,9 @@ Context::Context() { | |||
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, 16); | |||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8); | |||
SDL_GL_SetSwapInterval(1); | |||
} | |||
Context::~Context() { |
@@ -1,8 +1,8 @@ | |||
#include "GlWrappers.h" | |||
#include <SDL_opengles2.h> | |||
#include <iostream> | |||
#include "gl.h" | |||
#include "util.h" | |||
namespace Cygnet { | |||
@@ -39,6 +39,7 @@ GlShader::GlShader(Type type, const char *source) { | |||
glGetShaderiv(id_, GL_COMPILE_STATUS, &status); | |||
glCheck(); | |||
if (status == GL_FALSE) { | |||
std::cerr << "Cygnet: Here's the broken shader:\n" << source << '\n'; | |||
throw GlCompileError("GL shader compilation failed."); | |||
} | |||
} | |||
@@ -58,11 +59,6 @@ GlProgram::~GlProgram() { | |||
glCheck(); | |||
} | |||
void GlProgram::use() { | |||
glUseProgram(id_); | |||
glCheck(); | |||
} | |||
void GlProgram::addShader(const GlShader &shader) { | |||
glAttachShader(id_, shader.id()); | |||
glCheck(); | |||
@@ -97,27 +93,4 @@ void GlProgram::link() { | |||
} | |||
} | |||
GlTexture::GlTexture() { | |||
glGenTextures(1, &id_); | |||
glCheck(); | |||
} | |||
void GlTexture::bind() { | |||
glActiveTexture(GL_TEXTURE0); | |||
glBindTexture(GL_TEXTURE_2D, id_); | |||
} | |||
void GlTexture::upload(GLsizei width, GLsizei height, void *data, | |||
GLenum format, GLenum type) { | |||
w_ = width; | |||
h_ = height; | |||
bind(); | |||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, format, type, data); | |||
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(); | |||
} | |||
} |
@@ -1,7 +1,15 @@ | |||
#include "Renderer.h" | |||
#include <SDL_opengles2.h> | |||
#include <iostream> | |||
#include <stdio.h> | |||
#include <swan-common/constants.h> | |||
#include <string.h> | |||
// std::endian was originally in type_traits, was moved to bit | |||
#include <type_traits> | |||
#include <bit> | |||
#include "gl.h" | |||
#include "shaders.h" | |||
#include "GlWrappers.h" | |||
#include "TileAtlas.h" | |||
@@ -9,50 +17,326 @@ | |||
namespace Cygnet { | |||
struct TexturedProg: public GlProgram { | |||
using GlProgram::GlProgram; | |||
struct ChunkProg: public GlProgram { | |||
template<typename... T> | |||
ChunkProg(const T &... shaders): GlProgram(shaders...) { init(); } | |||
~ChunkProg() { deinit(); } | |||
GLint position = attribLoc("position"); | |||
GLint texCoord = attribLoc("texCoord"); | |||
GLint tex = uniformLoc("tex"); | |||
GLint camera = uniformLoc("camera"); | |||
GLint pos = uniformLoc("pos"); | |||
GLint vertex = attribLoc("vertex"); | |||
GLint tileAtlas = uniformLoc("tileAtlas"); | |||
GLint tileAtlasSize = uniformLoc("tileAtlasSize"); | |||
GLint tiles = uniformLoc("tiles"); | |||
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(tileAtlas, 0); | |||
glUniform1i(tiles, 1); | |||
} | |||
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 SolidColorProg: public GlProgram { | |||
using GlProgram::GlProgram; | |||
struct SpriteProg: public GlProgram { | |||
template<typename... T> | |||
SpriteProg(const T &... shaders): GlProgram(shaders...) { init(); } | |||
~SpriteProg() { deinit(); } | |||
GLint camera = uniformLoc("camera"); | |||
GLint transform = uniformLoc("transform"); | |||
GLint frameInfo = uniformLoc("frameInfo"); | |||
GLint vertex = attribLoc("vertex"); | |||
GLint tex = uniformLoc("tex"); | |||
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 | |||
}; | |||
GLint position = attribLoc("position"); | |||
GLint color = uniformLoc("color"); | |||
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 RendererState { | |||
GlVxShader basicVx{Shaders::basicVx}; | |||
GlVxShader texturedVx{Shaders::texturedVx}; | |||
GlFrShader solidColorFr{Shaders::solidColorFr}; | |||
GlFrShader texturedFr{Shaders::texturedFr}; | |||
GlVxShader spriteVx{Shaders::spriteVx}; | |||
GlFrShader spriteFr{Shaders::spriteFr}; | |||
GlVxShader chunkVx{Shaders::chunkVx}; | |||
GlFrShader chunkFr{Shaders::chunkFr}; | |||
TexturedProg texturedProg{texturedVx, texturedFr}; | |||
SolidColorProg solidColorProg{basicVx, solidColorFr}; | |||
SpriteProg spriteProg{spriteVx, spriteFr}; | |||
ChunkProg chunkProg{chunkVx, chunkFr}; | |||
TileAtlas atlas; | |||
GlTexture atlasTex; | |||
GLuint atlasTex; | |||
SwanCommon::Vec2 atlasTexSize; | |||
}; | |||
Renderer::Renderer(): state_(std::make_unique<RendererState>()) {} | |||
Renderer::Renderer(): state_(std::make_unique<RendererState>()) { | |||
glGenTextures(1, &state_->atlasTex); | |||
glCheck(); | |||
} | |||
Renderer::~Renderer() = default; | |||
void Renderer::draw() { | |||
state_->texturedProg.use(); | |||
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 }); | |||
auto &chunkProg = state_->chunkProg; | |||
auto &spriteProg = state_->spriteProg; | |||
{ | |||
chunkProg.enable(); | |||
glUniformMatrix3fv(chunkProg.camera, 1, GL_TRUE, camMat.data()); | |||
glCheck(); | |||
glUniform2f(chunkProg.tileAtlasSize, state_->atlasTexSize.x, state_->atlasTexSize.y); | |||
glCheck(); | |||
glActiveTexture(GL_TEXTURE0); | |||
glBindTexture(GL_TEXTURE_2D, state_->atlasTex); | |||
glCheck(); | |||
glActiveTexture(GL_TEXTURE1); | |||
for (auto [pos, chunk]: draw_chunks_) { | |||
glUniform2f(chunkProg.pos, pos.x, pos.y); | |||
glBindTexture(GL_TEXTURE_2D, chunk.tex); | |||
glDrawArrays(GL_TRIANGLES, 0, 6); | |||
glCheck(); | |||
} | |||
draw_chunks_.clear(); | |||
chunkProg.disable(); | |||
} | |||
{ | |||
spriteProg.enable(); | |||
glUniformMatrix3fv(spriteProg.camera, 1, GL_TRUE, camMat.data()); | |||
glCheck(); | |||
glActiveTexture(GL_TEXTURE0); | |||
for (auto [mat, frame, sprite]: draw_sprites_) { | |||
mat.scale(sprite.scale); | |||
glUniformMatrix3fv(spriteProg.transform, 1, GL_TRUE, mat.data()); | |||
glUniform3f(spriteProg.frameInfo, sprite.scale.y, sprite.frameCount, frame); | |||
glBindTexture(GL_TEXTURE_2D, sprite.tex); | |||
glDrawArrays(GL_TRIANGLES, 0, 6); | |||
glCheck(); | |||
} | |||
draw_sprites_.clear(); | |||
spriteProg.disable(); | |||
} | |||
} | |||
void Renderer::uploadTileAtlas(const void *data, int width, int height) { | |||
glBindTexture(GL_TEXTURE_2D, state_->atlasTex); | |||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); | |||
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); | |||
glCheck(); | |||
state_->atlasTexSize = { | |||
(float)(int)(width / SwanCommon::TILE_SIZE), | |||
(float)(int)(height / SwanCommon::TILE_SIZE) }; | |||
} | |||
void Renderer::modifyTile(TileID id, const void *data) { | |||
int w = (int)state_->atlasTexSize.x; | |||
int x = id % w; | |||
int y = id / w; | |||
glActiveTexture(GL_TEXTURE0); | |||
glBindTexture(GL_TEXTURE_2D, state_->atlasTex); | |||
glTexSubImage2D( | |||
GL_TEXTURE_2D, 0, x * SwanCommon::TILE_SIZE, y * SwanCommon::TILE_SIZE, | |||
SwanCommon::TILE_SIZE, SwanCommon::TILE_SIZE, | |||
GL_RGBA, GL_UNSIGNED_BYTE, data); | |||
glCheck(); | |||
} | |||
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(); | |||
glActiveTexture(GL_TEXTURE0); | |||
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); | |||
glCheck(); | |||
static_assert( | |||
std::endian::native == std::endian::big || | |||
std::endian::native == std::endian::little, | |||
"Expected either big or little endian"); | |||
if constexpr (std::endian::native == std::endian::little) { | |||
glTexImage2D( | |||
GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, | |||
SwanCommon::CHUNK_WIDTH, SwanCommon::CHUNK_HEIGHT, | |||
0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, tiles); | |||
} else if constexpr (std::endian::native == std::endian::big) { | |||
uint8_t buf[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT * 2]; | |||
for (size_t y = 0; y < SwanCommon::CHUNK_HEIGHT; ++y) { | |||
for (size_t x = 0; x < SwanCommon::CHUNK_WIDTH; ++x) { | |||
size_t dst = y * SwanCommon::CHUNK_WIDTH * 2 + x * 2; | |||
size_t src = y * SwanCommon::CHUNK_WIDTH + x; | |||
buf[dst + 0] = tiles[src] & 0xff; | |||
buf[dst + 1] = (tiles[src] & 0xff00) >> 8; | |||
} | |||
} | |||
glTexImage2D( | |||
GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, | |||
SwanCommon::CHUNK_WIDTH, SwanCommon::CHUNK_HEIGHT, | |||
0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, buf); | |||
} | |||
glCheck(); | |||
return chunk; | |||
} | |||
void Renderer::modifyChunk(RenderChunk chunk, SwanCommon::Vec2i pos, TileID id) { | |||
glActiveTexture(GL_TEXTURE0); | |||
glBindTexture(GL_TEXTURE_2D, chunk.tex); | |||
glCheck(); | |||
static_assert( | |||
std::endian::native == std::endian::big || | |||
std::endian::native == std::endian::little, | |||
"Expected either big or little endian"); | |||
if constexpr (std::endian::native == std::endian::little) { | |||
glTexSubImage2D( | |||
GL_TEXTURE_2D, 0, pos.x, pos.y, 1, 1, | |||
GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, &id); | |||
} else if constexpr (std::endian::native == std::endian::big) { | |||
uint8_t buf[] = { (uint8_t)(id & 0xff), (uint8_t)((id & 0xff00) >> 8) }; | |||
glTexSubImage2D( | |||
GL_TEXTURE_2D, 0, pos.x, pos.y, 1, 1, | |||
GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, buf); | |||
} | |||
glCheck(); | |||
} | |||
void Renderer::destroyChunk(RenderChunk chunk) { | |||
glDeleteTextures(1, &chunk.tex); | |||
glCheck(); | |||
} | |||
RenderSprite Renderer::createSprite(void *data, int width, int height, int fh) { | |||
RenderSprite sprite; | |||
sprite.scale = { | |||
(float)width / SwanCommon::TILE_SIZE, | |||
(float)fh / SwanCommon::TILE_SIZE }; | |||
sprite.frameCount = height / fh; | |||
glGenTextures(1, &sprite.tex); | |||
glCheck(); | |||
glActiveTexture(GL_TEXTURE0); | |||
glBindTexture(GL_TEXTURE_2D, sprite.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_RGBA, width, height, | |||
0, GL_RGBA, GL_UNSIGNED_BYTE, data); | |||
glCheck(); | |||
return sprite; | |||
} | |||
void Renderer::registerTileTexture(size_t tileId, const void *data, size_t len) { | |||
state_->atlas.addTile(tileId, data, len); | |||
RenderSprite Renderer::createSprite(void *data, int width, int height) { | |||
return createSprite(data, width, height, height); | |||
} | |||
void Renderer::uploadTileTexture() { | |||
size_t w, h; | |||
const unsigned char *data = state_->atlas.getImage(&w, &h); | |||
state_->atlasTex.upload(w, h, (void *)data, GL_RGBA, GL_UNSIGNED_BYTE); | |||
void Renderer::destroySprite(RenderSprite sprite) { | |||
glDeleteTextures(1, &sprite.tex); | |||
} | |||
} |
@@ -0,0 +1,29 @@ | |||
#include "ResourceManager.h" | |||
namespace Cygnet { | |||
ResourceManager::ResourceManager(ResourceBuilder &&builder): | |||
rnd_(builder.rnd_), sprites_(std::move(builder.sprites_)), | |||
tile_anims_(std::move(builder.tile_anims_)) { | |||
size_t width, height; | |||
const unsigned char *data = builder.atlas_.getImage(&width, &height); | |||
rnd_.uploadTileAtlas(data, width, height); | |||
} | |||
ResourceManager::~ResourceManager() { | |||
for (auto &[name, sprite]: sprites_) { | |||
rnd_.destroySprite(sprite); | |||
} | |||
} | |||
void ResourceManager::tick() { | |||
// TODO: Maybe do a GPU->GPU copy instead of an upload from the CPU? | |||
for (auto &anim: tile_anims_) { | |||
anim.index = (anim.index + 1) % anim.frames; | |||
unsigned char *data = anim.data.get() + | |||
SwanCommon::TILE_SIZE * SwanCommon::TILE_SIZE * 4 * anim.index; | |||
rnd_.modifyTile(anim.id, data); | |||
} | |||
} | |||
} |
@@ -2,11 +2,13 @@ | |||
#include <iostream> | |||
#include <vector> | |||
#include <SDL_opengles2.h> | |||
#include <algorithm> | |||
#include <stdio.h> | |||
#include <string.h> | |||
#include <swan-common/constants.h> | |||
#include "gl.h" | |||
namespace Cygnet { | |||
struct AtlasState { | |||
@@ -20,20 +22,16 @@ struct AtlasState { | |||
TileAtlas::TileAtlas(): state_(std::make_unique<AtlasState>()) { | |||
GLint size; | |||
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &size); | |||
state_->tilesPerLine = size / SwanCommon::TILE_SIZE; | |||
state_->tilesPerLine = std::min(size / SwanCommon::TILE_SIZE, 256); | |||
} | |||
TileAtlas::~TileAtlas() = default; | |||
void TileAtlas::addTile(size_t tileId, const void *data, size_t len) { | |||
size_t rows = len / (SwanCommon::TILE_SIZE * 4); | |||
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; | |||
if (y >= state_->tilesPerLine) { | |||
std::cerr << "Cygnet: Warning: Tile ID " << tileId << " too big for texture atlas\n"; | |||
return; | |||
} | |||
std::cerr << "Tile " << tileId << " to " << x << ", " << y << '\n'; | |||
if (state_->width <= x) { | |||
state_->width = x + 1; | |||
@@ -43,21 +41,21 @@ void TileAtlas::addTile(size_t tileId, const void *data, size_t len) { | |||
state_->height = y + 1; | |||
} | |||
size_t tileImgSize = SwanCommon::TILE_SIZE * SwanCommon::TILE_SIZE * 4; | |||
size_t requiredSize = (x + 1) * (y + 1) * tileImgSize; | |||
size_t requiredSize = state_->tilesPerLine * SwanCommon::TILE_SIZE * | |||
state_->height * SwanCommon::TILE_SIZE * 4; | |||
state_->data.resize(requiredSize); | |||
for (size_t ty = 0; ty < rows; ++ty) { | |||
for (size_t ty = 0; ty < SwanCommon::TILE_SIZE; ++ty) { | |||
const unsigned char *src = bytes + ty * SwanCommon::TILE_SIZE * 4; | |||
unsigned char *dest = state_->data.data() + | |||
((y + ty) * state_->width * SwanCommon::TILE_SIZE * 4) + | |||
(y * SwanCommon::TILE_SIZE + ty) * state_->tilesPerLine * SwanCommon::TILE_SIZE * 4 + | |||
(x * SwanCommon::TILE_SIZE * 4); | |||
const unsigned char *src = bytes + ty * SwanCommon::TILE_SIZE * 4; | |||
memcpy(dest, src, SwanCommon::TILE_SIZE * 4); | |||
} | |||
} | |||
const unsigned char *TileAtlas::getImage(size_t *w, size_t *h) { | |||
*w = state_->width * SwanCommon::TILE_SIZE; | |||
*w = state_->tilesPerLine * SwanCommon::TILE_SIZE; | |||
*h = state_->height * SwanCommon::TILE_SIZE; | |||
return state_->data.data(); | |||
} |
@@ -1,8 +1,8 @@ | |||
#include "Window.h" | |||
#include <SDL.h> | |||
#include <SDL_opengles2.h> | |||
#include "gl.h" | |||
#include "util.h" | |||
namespace Cygnet { | |||
@@ -28,10 +28,13 @@ Window::Window(const char *name, int w, int h): | |||
glEnable(GL_BLEND); | |||
glCheck(); | |||
SDL_GetWindowSize(state_->window, &w, &h); | |||
onResize(w, h); | |||
} | |||
Window::~Window() = default; | |||
Window::~Window() { | |||
SDL_DestroyWindow(state_->window); | |||
} | |||
void Window::makeCurrent() { | |||
SDL_GL_MakeCurrent(state_->window, state_->glctx); |
@@ -2,41 +2,68 @@ | |||
namespace Cygnet::Shaders { | |||
const char *basicVx = R"glsl( | |||
const char *spriteVx = R"glsl( | |||
uniform mat3 camera; | |||
uniform mat3 transform; | |||
attribute vec2 position; | |||
uniform vec3 frameInfo; // frame height, frame count, frame index | |||
attribute vec2 vertex; | |||
varying vec2 v_texCoord; | |||
void main() { | |||
vec3 pos = transform * vec3(position, 0); | |||
gl_Position = vec4(pos.x, pos.y, 0, 1); | |||
v_texCoord = vec2( | |||
vertex.x, | |||
(frameInfo.x * frameInfo.z + (frameInfo.x * vertex.y)) / | |||
(frameInfo.x * frameInfo.y)); | |||
vec3 pos = camera * transform * vec3(vertex, 1); | |||
gl_Position = vec4(pos.xy, 0, 1); | |||
} | |||
)glsl"; | |||
const char *texturedVx = R"glsl( | |||
uniform mat3 transform; | |||
attribute vec2 position; | |||
attribute vec2 texCoord; | |||
const char *spriteFr = R"glsl( | |||
precision mediump float; | |||
varying vec2 v_texCoord; | |||
uniform sampler2D tex; | |||
void main() { | |||
vec3 pos = transform * vec3(position, 0); | |||
gl_Position = vec4(pos.x, pos.y, 0, 1); | |||
v_texCoord = texCoord; | |||
gl_FragColor = texture2D(tex, v_texCoord); | |||
} | |||
)glsl"; | |||
const char *solidColorFr = R"glsl( | |||
precision mediump float; | |||
uniform vec4 color; | |||
const char *chunkVx = R"glsl( | |||
uniform mat3 camera; | |||
uniform vec2 pos; | |||
attribute vec2 vertex; | |||
varying vec2 v_tileCoord; | |||
void main() { | |||
gl_FragColor = color; | |||
vec3 pos = camera * vec3(pos + vertex, 1); | |||
gl_Position = vec4(pos.xy, 0, 1); | |||
v_tileCoord = vec2(vertex.x, vertex.y); | |||
} | |||
)glsl"; | |||
const char *texturedFr = R"glsl( | |||
const char *chunkFr = R"glsl( | |||
precision mediump float; | |||
varying vec2 v_texCoord; | |||
uniform sampler2D tex; | |||
#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() { | |||
gl_FragColor = texture2D(tex, v_texCoord); | |||
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); | |||
vec2 atlasPos = vec2( | |||
tileID + v_tileCoord.x - tilePos.x, | |||
floor(tileID / tileAtlasSize.x) + v_tileCoord.y - tilePos.y); | |||
gl_FragColor = texture2D(tileAtlas, atlasPos / tileAtlasSize); | |||
} | |||
)glsl"; | |||
@@ -1,6 +1,8 @@ | |||
#include "util.h" | |||
#include <SDL_opengles2.h> | |||
#include <SDL.h> | |||
#include "gl.h" | |||
namespace Cygnet { | |||
@@ -1,37 +1,176 @@ | |||
#include <cygnet/Context.h> | |||
#include <cygnet/Window.h> | |||
#include <cygnet/Renderer.h> | |||
#include <cygnet/ResourceManager.h> | |||
#include <swan-common/constants.h> | |||
#include <time.h> | |||
#include <stdint.h> | |||
#include <iostream> | |||
#include <SDL_image.h> | |||
#include <SDL.h> | |||
double getTime() { | |||
struct timespec tv; | |||
clock_gettime(CLOCK_MONOTONIC, &tv); | |||
return tv.tv_sec + tv.tv_nsec / 1000000000.0; | |||
} | |||
void addTile(Cygnet::ResourceBuilder &builder, const char *path) { | |||
static size_t id = 0; | |||
SDL_Surface *surf = IMG_Load(path); | |||
builder.addTile(id++, surf->pixels); | |||
SDL_FreeSurface(surf); | |||
} | |||
Cygnet::RenderSprite loadSprite(Cygnet::ResourceBuilder &builder, const char *path, int fh) { | |||
SDL_Surface *surf = IMG_Load(path); | |||
auto sprite = builder.addSprite(path, surf->pixels, surf->w, surf->h, fh); | |||
SDL_FreeSurface(surf); | |||
return sprite; | |||
} | |||
Cygnet::RenderSprite loadSprite(Cygnet::ResourceBuilder &builder, const char *path) { | |||
SDL_Surface *surf = IMG_Load(path); | |||
auto sprite = builder.addSprite(path, surf->pixels, surf->w, surf->h); | |||
SDL_FreeSurface(surf); | |||
return sprite; | |||
} | |||
int main() { | |||
Cygnet::Context ctx; | |||
Cygnet::Window win("Cygnet Test", 640, 480); | |||
IMG_Init(IMG_INIT_PNG); | |||
Cygnet::Window win("Cygnet Test", 680, 680); | |||
Cygnet::Renderer rnd; | |||
Cygnet::ResourceBuilder rbuilder(rnd); | |||
for (auto path: { | |||
"core.mod/assets/tile/dirt.png", | |||
"core.mod/assets/tile/grass.png", | |||
"core.mod/assets/tile/leaves.png", | |||
"core.mod/assets/tile/stone.png", | |||
"core.mod/assets/tile/torch.png", | |||
"core.mod/assets/tile/tree-trunk.png", | |||
}) addTile(rbuilder, path); | |||
uint32_t img[SwanCommon::TILE_SIZE * SwanCommon::TILE_SIZE]; | |||
for (size_t i = 0; i < sizeof(img) / sizeof(*img); ++i) { | |||
img[i] = 0xff00aaff; | |||
unsigned char lolTexture[32*32*4*3]; | |||
for (size_t i = 0; i < 3; ++i) { | |||
int col = 100 * i + 50;; | |||
for (size_t y = 0; y < 32; ++y) { | |||
for (size_t x = 0; x < 32; ++x) { | |||
lolTexture[i * 32 * 32 * 4 + y * 32 * 4 + x * 4 + 0] = col; | |||
lolTexture[i * 32 * 32 * 4 + y * 32 * 4 + x * 4 + 1] = col; | |||
lolTexture[i * 32 * 32 * 4 + y * 32 * 4 + x * 4 + 2] = col; | |||
lolTexture[i * 32 * 32 * 4 + y * 32 * 4 + x * 4 + 3] = 255; | |||
} | |||
} | |||
} | |||
rnd.registerTileTexture(0, img, sizeof(img)); | |||
rnd.uploadTileTexture(); | |||
rbuilder.addTile(10, lolTexture, 3); | |||
Cygnet::RenderSprite playerSprite = loadSprite( | |||
rbuilder, "core.mod/assets/entity/player-still.png", 64); | |||
Cygnet::ResourceManager resources(std::move(rbuilder)); | |||
Cygnet::RenderChunk chunk; | |||
{ | |||
uint16_t tiles[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT]; | |||
memset(tiles, 0, sizeof(tiles)); | |||
tiles[0] = 1; | |||
tiles[1] = 2; | |||
tiles[2] = 3; | |||
tiles[10] = 10; | |||
chunk = rnd.createChunk(tiles); | |||
} | |||
Cygnet::RenderCamera cam = { | |||
.pos = { 0, 0 }, | |||
.size = win.size(), | |||
.zoom = 1, | |||
}; | |||
float lol = 0; | |||
bool keys[512] = { 0 }; | |||
double tileAnimAcc = 0; | |||
double fpsAcc = 0; | |||
double prevTime = getTime() - 1/60.0; | |||
int frames = 0; | |||
float x = 0, y = 0; | |||
while (true) { | |||
double currTime = getTime(); | |||
double dt = currTime - prevTime; | |||
prevTime = currTime; | |||
lol += dt; | |||
fpsAcc += dt; | |||
frames += 1; | |||
if (fpsAcc >= 2) { | |||
std::cerr << "FPS: " << (frames / 2.0) << '\n'; | |||
fpsAcc -= 2; | |||
frames = 0; | |||
} | |||
tileAnimAcc += dt; | |||
if (tileAnimAcc >= 0.5) { | |||
resources.tick(); | |||
tileAnimAcc -= 0.5; | |||
} | |||
SDL_Event evt; | |||
while (SDL_PollEvent(&evt)) { | |||
switch (evt.type) { | |||
case SDL_QUIT: | |||
goto exit; | |||
case SDL_WINDOWEVENT: | |||
switch (evt.window.event) { | |||
case SDL_WINDOWEVENT_SIZE_CHANGED: | |||
win.onResize(evt.window.data1, evt.window.data2); | |||
cam.size = win.size(); | |||
break; | |||
} | |||
break; | |||
case SDL_MOUSEWHEEL: | |||
cam.zoom += evt.wheel.y * 0.1 * cam.zoom; | |||
break; | |||
case SDL_KEYDOWN: | |||
keys[evt.key.keysym.scancode] = true; | |||
break; | |||
case SDL_KEYUP: | |||
keys[evt.key.keysym.scancode] = false; | |||
break; | |||
} | |||
} | |||
if (keys[SDL_SCANCODE_A]) { | |||
x -= 1 * dt; | |||
} | |||
if (keys[SDL_SCANCODE_D]) { | |||
x += 1 * dt; | |||
} | |||
if (keys[SDL_SCANCODE_W]) { | |||
y -= 1 * dt; | |||
} | |||
if (keys[SDL_SCANCODE_S]) { | |||
y += 1 * dt; | |||
} | |||
rnd.drawChunk(chunk, { 0, 0 }); | |||
rnd.drawSprite(playerSprite, { x, y }, (int)lol % 2); | |||
cam.pos = { x, y }; | |||
win.clear(); | |||
rnd.draw(); | |||
rnd.draw(cam); | |||
win.flip(); | |||
} | |||
exit: | |||
; | |||
IMG_Quit(); | |||
} |