@@ -28,6 +28,9 @@ void PlayerEntity::draw(const Swan::Context &ctx, Cygnet::Renderer &rnd) { | |||
rnd.drawRect(mouseTile_, {1, 1}); | |||
rnd.drawRect(body_.pos, body_.size); | |||
rnd.drawLight(1, body_.topMid() + Swan::Vec2{0, 0.3}); | |||
rnd.drawLight(1, {0, 0}); | |||
} | |||
void PlayerEntity::update(const Swan::Context &ctx, float dt) { | |||
@@ -81,19 +84,6 @@ void PlayerEntity::update(const Swan::Context &ctx, float dt) { | |||
anims_[(int)state_].tick(dt); | |||
physics(ctx, dt, { .mass = MASS }); | |||
// Do this after moving so that it's not behind | |||
Swan::Vec2 headPos = body_.topMid() + Swan::Vec2(0, 0.5); | |||
Swan::TilePos tilePos = Swan::Vec2i(floor(headPos.x), floor(headPos.y)); | |||
if (!placedLight_) { | |||
ctx.plane.addLight(tilePos, LIGHT_LEVEL); | |||
placedLight_ = true; | |||
lightTile_ = tilePos; | |||
} else if (tilePos != lightTile_) { | |||
ctx.plane.removeLight(lightTile_, LIGHT_LEVEL); | |||
ctx.plane.addLight(tilePos, LIGHT_LEVEL); | |||
lightTile_ = tilePos; | |||
} | |||
} | |||
void PlayerEntity::tick(const Swan::Context &ctx, float dt) { |
@@ -17,10 +17,6 @@ struct RenderChunk { | |||
GLuint tex; | |||
}; | |||
struct RenderChunkShadow { | |||
GLuint tex; | |||
}; | |||
struct RenderSprite { | |||
GLuint tex; | |||
SwanCommon::Vec2 scale; | |||
@@ -41,7 +37,7 @@ public: | |||
~Renderer(); | |||
void drawChunk(RenderChunk chunk, SwanCommon::Vec2 pos); | |||
void drawChunkShadow(RenderChunkShadow shadow, SwanCommon::Vec2 pos); | |||
void drawLight(float level, SwanCommon::Vec2 pos); | |||
void drawTile(TileID id, Mat3gf mat); | |||
void drawSprite(RenderSprite sprite, Mat3gf mat, int y = 0); | |||
void drawRect(SwanCommon::Vec2 pos, SwanCommon::Vec2 size); | |||
@@ -56,13 +52,6 @@ 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); | |||
@@ -75,9 +64,9 @@ private: | |||
RenderChunk chunk; | |||
}; | |||
struct DrawShadow { | |||
struct DrawLight { | |||
SwanCommon::Vec2 pos; | |||
RenderChunkShadow shadow; | |||
float level; | |||
}; | |||
struct DrawTile { | |||
@@ -101,7 +90,7 @@ private: | |||
std::unique_ptr<RendererState> state_; | |||
std::vector<DrawChunk> drawChunks_; | |||
std::vector<DrawShadow> drawChunkShadows_; | |||
std::vector<DrawLight> drawLights_; | |||
std::vector<DrawTile> drawTiles_; | |||
std::vector<DrawSprite> drawSprites_; | |||
std::vector<DrawRect> drawRects_; | |||
@@ -111,8 +100,8 @@ inline void Renderer::drawChunk(RenderChunk chunk, SwanCommon::Vec2 pos) { | |||
drawChunks_.push_back({pos, chunk}); | |||
} | |||
inline void Renderer::drawChunkShadow(RenderChunkShadow shadow, SwanCommon::Vec2 pos) { | |||
drawChunkShadows_.push_back({pos, shadow}); | |||
inline void Renderer::drawLight(float level, SwanCommon::Vec2 pos) { | |||
drawLights_.push_back({pos, level}); | |||
} | |||
inline void Renderer::drawTile(TileID id, Mat3gf mat) { |
@@ -5,6 +5,9 @@ namespace Cygnet::Shaders { | |||
extern const char *chunkVx; | |||
extern const char *chunkFr; | |||
extern const char *lightingVx; | |||
extern const char *lightingFr; | |||
extern const char *tileVx; | |||
extern const char *tileFr; | |||
@@ -14,7 +17,7 @@ extern const char *spriteFr; | |||
extern const char *rectVx; | |||
extern const char *rectFr; | |||
extern const char *blendVx; | |||
extern const char *blendFr; | |||
extern const char *blendLightingVx; | |||
extern const char *blendLightingFr; | |||
} |
@@ -73,6 +73,55 @@ struct ChunkProg: public GlProgram { | |||
} | |||
}; | |||
struct LightingProg: public GlProgram { | |||
template<typename... T> | |||
LightingProg(const T &... shaders): GlProgram(shaders...) { init(); } | |||
~LightingProg() { deinit(); } | |||
GLint inverseCamera = uniformLoc("inverseCamera"); | |||
GLint lightPos = uniformLoc("lightPos"); | |||
GLint lightLevel = uniformLoc("lightLevel"); | |||
GLint vertex = attribLoc("vertex"); | |||
GLuint vbo; | |||
static constexpr GLfloat vertexes[] = { | |||
-1.0f, -1.0f, // pos 0: top left | |||
-1.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, -1.0f, // pos 3: top right | |||
-1.0f, -1.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 TileProg: public GlProgram { | |||
template<typename... T> | |||
TileProg(const T &... shaders): GlProgram(shaders...) { init(); } | |||
@@ -228,10 +277,10 @@ struct RectProg: public GlProgram { | |||
} | |||
}; | |||
struct BlendProg: public GlProgram { | |||
struct BlendLightingProg: public GlProgram { | |||
template<typename... T> | |||
BlendProg(const T &... shaders): GlProgram(shaders...) { init(); } | |||
~BlendProg() { deinit(); } | |||
BlendLightingProg(const T &... shaders): GlProgram(shaders...) { init(); } | |||
~BlendLightingProg() { deinit(); } | |||
GLint vertex = attribLoc("vertex"); | |||
GLint texCoord = attribLoc("texCoord"); | |||
@@ -284,24 +333,27 @@ struct BlendProg: public GlProgram { | |||
struct RendererState { | |||
GlVxShader chunkVx{Shaders::chunkVx}; | |||
GlFrShader chunkFr{Shaders::chunkFr}; | |||
GlVxShader lightingVx{Shaders::lightingVx}; | |||
GlFrShader lightingFr{Shaders::lightingFr}; | |||
GlVxShader tileVx{Shaders::tileVx}; | |||
GlFrShader tileFr{Shaders::tileFr}; | |||
GlVxShader spriteVx{Shaders::spriteVx}; | |||
GlFrShader spriteFr{Shaders::spriteFr}; | |||
GlVxShader rectVx{Shaders::rectVx}; | |||
GlFrShader rectFr{Shaders::rectFr}; | |||
GlVxShader blendVx{Shaders::blendVx}; | |||
GlFrShader blendFr{Shaders::blendFr}; | |||
GlVxShader blendLightingVx{Shaders::blendLightingVx}; | |||
GlFrShader blendLightingFr{Shaders::blendLightingFr}; | |||
ChunkProg chunkProg{chunkVx, chunkFr}; | |||
LightingProg lightingProg{lightingVx, lightingFr}; | |||
TileProg tileProg{tileVx, tileFr}; | |||
SpriteProg spriteProg{spriteVx, spriteFr}; | |||
RectProg rectProg{rectVx, rectFr}; | |||
BlendProg blendProg{blendVx, blendFr}; | |||
BlendLightingProg blendLightingProg{blendLightingVx, blendLightingFr}; | |||
SwanCommon::Vec2i screenSize; | |||
GLuint offscreenFramebuffer = 0; | |||
GLuint offscreenTex = 0; | |||
GLuint lightingFramebuffer = 0; | |||
GLuint lightingTex = 0; | |||
GLuint opacityFramebuffer = 0; | |||
GLuint opacityTex = 0; | |||
GLuint atlasTex = 0; | |||
@@ -309,12 +361,16 @@ struct RendererState { | |||
}; | |||
Renderer::Renderer(): state_(std::make_unique<RendererState>()) { | |||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | |||
glEnable(GL_BLEND); | |||
glCheck(); | |||
glGenTextures(1, &state_->atlasTex); | |||
glCheck(); | |||
glGenFramebuffers(1, &state_->offscreenFramebuffer); | |||
glGenFramebuffers(1, &state_->lightingFramebuffer); | |||
glCheck(); | |||
glGenTextures(1, &state_->offscreenTex); | |||
glGenTextures(1, &state_->lightingTex); | |||
glCheck(); | |||
glGenFramebuffers(1, &state_->opacityFramebuffer); | |||
@@ -324,36 +380,47 @@ Renderer::Renderer(): state_(std::make_unique<RendererState>()) { | |||
} | |||
Renderer::~Renderer() { | |||
glDeleteFramebuffers(1, &state_->offscreenFramebuffer); | |||
glDeleteTextures(1, &state_->offscreenFramebuffer); | |||
glDeleteFramebuffers(1, &state_->lightingFramebuffer); | |||
glDeleteTextures(1, &state_->lightingTex); | |||
glDeleteFramebuffers(1, &state_->opacityFramebuffer); | |||
glDeleteTextures(1, &state_->opacityTex); | |||
glDeleteTextures(1, &state_->atlasTex); | |||
} | |||
void Renderer::draw(const RenderCamera &cam) { | |||
Mat3gf camMat; | |||
Mat3gf invCamMat; | |||
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}); | |||
float sx = cam.zoom * ratio, sy = -cam.zoom; | |||
camMat.scale({sx, sy}); | |||
invCamMat.scale({1.0f / sx, 1.0f / sy}); | |||
} else { | |||
float ratio = (float)cam.size.x / (float)cam.size.y; | |||
winScale_ = {1, 1/ratio}; | |||
camMat.scale({cam.zoom, -cam.zoom * ratio}); | |||
float sx = cam.zoom, sy = -cam.zoom * ratio; | |||
camMat.scale({sx, sy}); | |||
invCamMat.scale({1.0f / sx, 1.0f / sy}); | |||
} | |||
invCamMat.translate(cam.pos); | |||
auto &chunkProg = state_->chunkProg; | |||
auto &lightingProg = state_->lightingProg; | |||
auto &tileProg = state_->tileProg; | |||
auto &spriteProg = state_->spriteProg; | |||
auto &rectProg = state_->rectProg; | |||
auto &blendProg = state_->blendProg; | |||
auto &blendLightingProg = state_->blendLightingProg; | |||
if (state_->screenSize != cam.size) { | |||
state_->screenSize = cam.size; | |||
glBindTexture(GL_TEXTURE_2D, state_->offscreenTex); | |||
glBindTexture(GL_TEXTURE_2D, state_->lightingTex); | |||
glCheck(); | |||
glTexImage2D( | |||
GL_TEXTURE_2D, 0, GL_RGBA, cam.size.x, cam.size.y, 0, | |||
@@ -372,17 +439,18 @@ void Renderer::draw(const RenderCamera &cam) { | |||
glCheck(); | |||
} | |||
glBindFramebuffer(GL_FRAMEBUFFER, state_->opacityFramebuffer); | |||
glFramebufferTexture2D( | |||
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, | |||
state_->opacityTex, 0); | |||
glClearColor(0, 0, 0, 0); | |||
glClear(GL_COLOR_BUFFER_BIT); | |||
glCheck(); | |||
{ // Draw opacity/color data to opacityTex | |||
glBindFramebuffer(GL_FRAMEBUFFER, state_->opacityFramebuffer); | |||
glFramebufferTexture2D( | |||
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, | |||
state_->opacityTex, 0); | |||
glClearColor(0, 0, 0, 0); | |||
glClear(GL_COLOR_BUFFER_BIT); | |||
glCheck(); | |||
{ | |||
Mat3gf scaledCamMat = camMat; | |||
scaledCamMat.scale({0.5, 0.5}); | |||
chunkProg.enable(); | |||
glUniformMatrix3fv(chunkProg.camera, 1, GL_TRUE, scaledCamMat.data()); | |||
glCheck(); | |||
@@ -402,15 +470,37 @@ void Renderer::draw(const RenderCamera &cam) { | |||
glCheck(); | |||
} | |||
// Don't clear drawChunks_ because we'll use it later | |||
chunkProg.disable(); | |||
} | |||
glBindFramebuffer(GL_FRAMEBUFFER, state_->offscreenFramebuffer); | |||
glFramebufferTexture2D( | |||
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, | |||
state_->offscreenTex, 0); | |||
glClearColor(0, 0, 0, 0); | |||
glClear(GL_COLOR_BUFFER_BIT); | |||
{ // Draw lighting data to lightingTex, using opacityTex | |||
glBindFramebuffer(GL_FRAMEBUFFER, state_->lightingFramebuffer); | |||
glFramebufferTexture2D( | |||
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, | |||
state_->lightingTex, 0); | |||
glClearColor(0, 0, 0, 0); | |||
glClear(GL_COLOR_BUFFER_BIT); | |||
glCheck(); | |||
glBlendFunc(GL_ONE, GL_ONE); | |||
lightingProg.enable(); | |||
glUniformMatrix3fv(lightingProg.inverseCamera, 1, GL_TRUE, invCamMat.data()); | |||
glCheck(); | |||
for (auto [pos, level]: drawLights_) { | |||
glUniform2f(lightingProg.lightPos, pos.x, pos.y); | |||
glUniform1f(lightingProg.lightLevel, level); | |||
glDrawArrays(GL_TRIANGLES, 0, 6); | |||
glCheck(); | |||
} | |||
drawLights_.clear(); | |||
lightingProg.disable(); | |||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | |||
} | |||
glBindFramebuffer(GL_FRAMEBUFFER, 0); | |||
{ | |||
chunkProg.enable(); | |||
@@ -495,15 +585,17 @@ void Renderer::draw(const RenderCamera &cam) { | |||
rectProg.disable(); | |||
} | |||
glBindFramebuffer(GL_FRAMEBUFFER, 0); | |||
{ | |||
blendProg.enable(); | |||
glBlendFunc(GL_DST_COLOR, GL_ZERO); | |||
blendLightingProg.enable(); | |||
glActiveTexture(GL_TEXTURE0); | |||
glBindTexture(GL_TEXTURE_2D, state_->opacityTex); | |||
glBindTexture(GL_TEXTURE_2D, state_->lightingTex); | |||
glDrawArrays(GL_TRIANGLES, 0, 6); | |||
glCheck(); | |||
blendProg.disable(); | |||
blendLightingProg.disable(); | |||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | |||
} | |||
} | |||
@@ -608,47 +700,6 @@ 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 = { |
@@ -33,10 +33,6 @@ Window::Window(const char *name, int w, int h): | |||
glCheck(); | |||
makeCurrent(); | |||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | |||
glEnable(GL_BLEND); | |||
glCheck(); | |||
SDL_GetWindowSize(state_->window, &w, &h); | |||
onResize(w, h); | |||
} |
@@ -43,6 +43,34 @@ const char *chunkFr = R"glsl( | |||
} | |||
)glsl"; | |||
const char *lightingVx = R"glsl( | |||
precision mediump float; | |||
uniform mat3 inverseCamera; | |||
attribute vec2 vertex; | |||
varying vec2 v_pos; | |||
void main() { | |||
v_pos = (inverseCamera * vec3(vertex, 1)).xy; | |||
gl_Position = vec4(vertex.xy, 0, 1); | |||
} | |||
)glsl"; | |||
const char *lightingFr = R"glsl( | |||
precision mediump float; | |||
varying vec2 v_pos; | |||
uniform vec2 lightPos; | |||
uniform float lightLevel; | |||
void main() { | |||
vec2 diff = lightPos - v_pos; | |||
float squareDist = diff.x * diff.x + diff.y * diff.y; | |||
float dist = sqrt(squareDist); | |||
float attenuated = 1.0 / (1.0 + 1.0 * dist + 0.2 * squareDist); | |||
float light = lightLevel * attenuated; | |||
gl_FragColor = vec4(light, light, light, light); | |||
} | |||
)glsl"; | |||
const char *tileVx = R"glsl( | |||
precision mediump float; | |||
uniform mat3 camera; | |||
@@ -57,6 +85,31 @@ const char *tileVx = R"glsl( | |||
} | |||
)glsl"; | |||
const char *blendLightingVx = R"glsl( | |||
precision mediump float; | |||
attribute vec2 vertex; | |||
attribute vec2 texCoord; | |||
varying vec2 v_texCoord; | |||
void main() { | |||
gl_Position = vec4(vertex.xy, 0, 1); | |||
v_texCoord = texCoord; | |||
} | |||
)glsl"; | |||
const char *blendLightingFr = R"glsl( | |||
precision mediump float; | |||
varying vec2 v_texCoord; | |||
uniform sampler2D tex; | |||
const float power = 1.0 / 2.4; | |||
void main() { | |||
vec4 linear = texture2D(tex, v_texCoord); | |||
vec4 srgb = 1.055 * pow(linear, vec4(power, power, power, power)) - 0.055; | |||
gl_FragColor = srgb; | |||
} | |||
)glsl"; | |||
const char *tileFr = R"glsl( | |||
precision mediump float; | |||
#define TILE_SIZE 32.0 | |||
@@ -145,27 +198,4 @@ const char *rectFr = R"glsl( | |||
} | |||
)glsl"; | |||
const char *blendVx = R"glsl( | |||
precision mediump float; | |||
attribute vec2 vertex; | |||
attribute vec2 texCoord; | |||
varying vec2 v_texCoord; | |||
void main() { | |||
gl_Position = vec4(vertex.xy, 0, 1); | |||
v_texCoord = texCoord; | |||
} | |||
)glsl"; | |||
const char *blendFr = R"glsl( | |||
precision mediump float; | |||
varying vec2 v_texCoord; | |||
uniform sampler2D tex; | |||
void main() { | |||
//gl_FragColor = vec4(v_texCoord.x, v_texCoord.y, 0, 1); | |||
gl_FragColor = texture2D(tex, v_texCoord); | |||
} | |||
)glsl"; | |||
} |
@@ -11,7 +11,6 @@ add_library(libswan SHARED | |||
src/Game.cc | |||
src/gfxutil.cc | |||
src/ItemStack.cc | |||
src/LightServer.cc | |||
src/OS.cc | |||
src/World.cc | |||
src/WorldPlane.cc) |
@@ -19,8 +19,7 @@ public: | |||
using RelPos = TilePos; | |||
static constexpr size_t DATA_SIZE = | |||
CHUNK_WIDTH * CHUNK_HEIGHT * sizeof(Tile::ID) + // Tiles | |||
CHUNK_WIDTH * CHUNK_HEIGHT; // Light levels | |||
CHUNK_WIDTH * CHUNK_HEIGHT * sizeof(Tile::ID); | |||
// What does this chunk want the world gen to do after a tick? | |||
enum class TickAction { | |||
@@ -29,21 +28,13 @@ public: | |||
NOTHING, | |||
}; | |||
Chunk(ChunkPos pos): pos_(pos) { | |||
data_.reset(new uint8_t[DATA_SIZE]); | |||
memset(getLightData(), 0, CHUNK_WIDTH * CHUNK_HEIGHT); | |||
} | |||
Chunk(ChunkPos pos): pos_(pos), data_(new uint8_t[DATA_SIZE]) {} | |||
Tile::ID *getTileData() { | |||
assert(isActive()); | |||
return (Tile::ID *)data_.get(); | |||
} | |||
uint8_t *getLightData() { | |||
assert(isActive()); | |||
return data_.get() + CHUNK_WIDTH * CHUNK_HEIGHT * sizeof(Tile::ID); | |||
} | |||
Tile::ID getTileID(RelPos pos) { | |||
return getTileData()[pos.y * CHUNK_WIDTH + pos.x]; | |||
} | |||
@@ -58,16 +49,10 @@ public: | |||
getTileData()[pos.y * CHUNK_WIDTH + pos.x] = id; | |||
} | |||
uint8_t getLightLevel(RelPos pos) { | |||
return getLightData()[pos.y * CHUNK_WIDTH + pos.x]; | |||
} | |||
void setLightData(const uint8_t *data) { | |||
memcpy(getLightData(), data, CHUNK_WIDTH * CHUNK_HEIGHT); | |||
needLightRender_ = true; | |||
} | |||
void addLight(RelPos pos, float level) { lights_.push_back({pos, level}); } | |||
void removeLight(RelPos pos); | |||
void generateDone(); | |||
void generateDone(World &world); | |||
void keepActive(); | |||
void decompress(); | |||
void compress(Cygnet::Renderer &rnd); | |||
@@ -85,13 +70,12 @@ private: | |||
bool isCompressed() { return compressedSize_ != -1; } | |||
std::unique_ptr<uint8_t[]> data_; | |||
std::vector<std::pair<RelPos, float>> lights_; | |||
std::vector<std::pair<RelPos, Tile::ID>> changeList_; | |||
ssize_t compressedSize_ = -1; // -1 if not compressed, a positive number if compressed | |||
Cygnet::RenderChunk renderChunk_; | |||
Cygnet::RenderChunkShadow renderChunkShadow_; | |||
bool needChunkRender_ = true; | |||
bool needLightRender_ = false; | |||
float deactivateTimer_ = DEACTIVATE_INTERVAL; | |||
bool isModified_ = false; | |||
}; |
@@ -1,138 +0,0 @@ | |||
#pragma once | |||
#include <thread> | |||
#include <vector> | |||
#include <unordered_map> | |||
#include <mutex> | |||
#include <condition_variable> | |||
#include <utility> | |||
#include <bitset> | |||
#include "common.h" | |||
namespace Swan { | |||
struct NewLightChunk { | |||
std::bitset<CHUNK_WIDTH * CHUNK_HEIGHT> blocks; | |||
std::map<std::pair<int, int>, float> lightSources; | |||
}; | |||
struct LightChunk { | |||
LightChunk() = default; | |||
LightChunk(NewLightChunk &&ch); | |||
std::bitset<CHUNK_WIDTH * CHUNK_HEIGHT> blocks; | |||
uint8_t lightLevels[CHUNK_WIDTH * CHUNK_HEIGHT] = { 0 }; | |||
float lightBuffers[CHUNK_WIDTH * CHUNK_HEIGHT * 2] = { 0 }; | |||
int buffer = 0; | |||
uint8_t blocksLine[CHUNK_WIDTH] = { 0 }; | |||
std::map<std::pair<int, int>, float> lightSources; | |||
std::vector<std::pair<TilePos, float>> bounces; | |||
float *lightBuffer() { return lightBuffers + CHUNK_WIDTH * CHUNK_HEIGHT * buffer; } | |||
bool wasUpdated = false; | |||
}; | |||
class LightCallback { | |||
public: | |||
virtual void onLightChunkUpdated(const LightChunk &chunk, ChunkPos pos) = 0; | |||
}; | |||
class LightServer { | |||
public: | |||
LightServer(LightCallback &cb); | |||
~LightServer(); | |||
void onSolidBlockAdded(TilePos pos); | |||
void onSolidBlockRemoved(TilePos pos); | |||
void onLightAdded(TilePos pos, float level); | |||
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; | |||
static constexpr float LIGHT_CUTOFF = 0.001; | |||
struct Event { | |||
enum class Tag { | |||
BLOCK_ADDED, BLOCK_REMOVED, LIGHT_ADDED, LIGHT_REMOVED, | |||
CHUNK_ADDED, CHUNK_REMOVED, | |||
} tag; | |||
TilePos pos; | |||
union { | |||
float f; | |||
int i; | |||
}; | |||
}; | |||
bool tileIsSolid(TilePos pos); | |||
LightChunk *getChunk(ChunkPos cpos); | |||
float recalcTile( | |||
LightChunk &chunk, ChunkPos cpos, Vec2i rpos, TilePos base, | |||
std::vector<std::pair<TilePos, float>> &lights); | |||
void processChunkSun(LightChunk &chunk, ChunkPos cpos); | |||
void processChunkLights(LightChunk &chunk, ChunkPos cpos); | |||
void processChunkBounces(LightChunk &chunk, ChunkPos cpos); | |||
void processChunkSmoothing(LightChunk &chunk, ChunkPos cpos); | |||
void finalizeChunk(LightChunk &chunk); | |||
void processEvent(const Event &event, std::vector<NewLightChunk> &newChunks); | |||
void run(); | |||
bool running_ = true; | |||
std::map<std::pair<int, int>, LightChunk> chunks_; | |||
std::set<std::pair<int, int>> updatedChunks_; | |||
LightChunk *cachedChunk_ = nullptr; | |||
Vec2i cachedChunkPos_; | |||
int buffer_ = 0; | |||
std::vector<Event> buffers_[2] = { {}, {} }; | |||
std::vector<NewLightChunk> newChunkBuffers_[2] = { {}, {} }; | |||
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 } }); | |||
} | |||
inline void LightServer::onSolidBlockRemoved(TilePos pos) { | |||
std::lock_guard<std::mutex> lock(mut_); | |||
buffers_[buffer_].push_back({ Event::Tag::BLOCK_REMOVED, pos, { .i = 0 } }); | |||
} | |||
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 } }); | |||
} | |||
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 } }); | |||
} | |||
inline void LightServer::onChunkAdded(Vec2i pos, NewLightChunk &&chunk) { | |||
std::lock_guard<std::mutex> lock(mut_); | |||
buffers_[buffer_].push_back({ Event::Tag::CHUNK_ADDED, pos, | |||
{ .i = (int)newChunkBuffers_[buffer_].size() } }); | |||
newChunkBuffers_[buffer_].push_back(std::move(chunk)); | |||
} | |||
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(); | |||
} | |||
} |
@@ -17,14 +17,13 @@ | |||
#include "WorldGen.h" | |||
#include "Entity.h" | |||
#include "Collection.h" | |||
#include "LightServer.h" | |||
namespace Swan { | |||
class World; | |||
class Game; | |||
class WorldPlane final: NonCopyable, public LightCallback { | |||
class WorldPlane final: NonCopyable { | |||
public: | |||
using ID = uint16_t; | |||
@@ -76,12 +75,6 @@ public: | |||
void debugBox(TilePos pos); | |||
void addLight(TilePos pos, float level); | |||
void removeLight(TilePos pos, float level); | |||
// LightingCallback implementation | |||
void onLightChunkUpdated(const LightChunk &chunk, Vec2i pos) final; | |||
ID id_; | |||
World *world_; | |||
std::unique_ptr<WorldGen> gen_; | |||
@@ -97,12 +90,6 @@ 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_; | |||
}; | |||
/* |
@@ -13,6 +13,45 @@ | |||
namespace Swan { | |||
void Chunk::removeLight(RelPos pos) { | |||
for (size_t i = 0; i < lights_.size(); ++i) { | |||
if (lights_[i].first == pos) { | |||
lights_[i] = lights_.back(); | |||
lights_.pop_back(); | |||
return; | |||
} | |||
} | |||
warn << "Asked to remove light at position " << pos << ", but there was none"; | |||
} | |||
void Chunk::generateDone(World &world) { | |||
int prevTileId = -1; | |||
float prevLight = 0; | |||
for (int y = 0; y < CHUNK_HEIGHT; ++y) { | |||
for (int x = 0; x < CHUNK_WIDTH; ++x) { | |||
int id = getTileID({x, y}); | |||
float light = 0; | |||
if (prevTileId == id) { | |||
light = prevLight; | |||
} else { | |||
light = world.getTileByID(id).lightLevel; | |||
} | |||
if (light == 0) { | |||
continue; | |||
} | |||
lights_.push_back({{x, y}, light}); | |||
} | |||
} | |||
} | |||
void Chunk::keepActive() { | |||
deactivateTimer_ = DEACTIVATE_INTERVAL; | |||
decompress(); | |||
} | |||
void Chunk::compress(Cygnet::Renderer &rnd) { | |||
if (isCompressed()) | |||
return; | |||
@@ -46,7 +85,6 @@ void Chunk::compress(Cygnet::Renderer &rnd) { | |||
} | |||
rnd.destroyChunk(renderChunk_); | |||
rnd.destroyChunkShadow(renderChunkShadow_); | |||
} | |||
void Chunk::decompress() { | |||
@@ -81,23 +119,20 @@ void Chunk::draw(const Context &ctx, Cygnet::Renderer &rnd) { | |||
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); | |||
for (auto [pos, id]: changeList_) { | |||
rnd.modifyChunk(renderChunk_, pos, id); | |||
} | |||
} | |||
if (needLightRender_) { | |||
rnd.modifyChunkShadow(renderChunkShadow_, getLightData()); | |||
needLightRender_ = false; | |||
} | |||
Vec2 pos = (Vec2)pos_ * Vec2{CHUNK_WIDTH, CHUNK_HEIGHT}; | |||
rnd.drawChunk(renderChunk_, pos); | |||
rnd.drawChunkShadow(renderChunkShadow_, pos); | |||
for (auto [rp, level]: lights_) { | |||
Vec2 lightPos = pos + (Vec2)rp + Vec2{0.5, 0.5}; | |||
rnd.drawLight(level, lightPos); | |||
} | |||
} | |||
Chunk::TickAction Chunk::tick(float dt) { | |||
@@ -114,9 +149,4 @@ Chunk::TickAction Chunk::tick(float dt) { | |||
return TickAction::NOTHING; | |||
} | |||
void Chunk::keepActive() { | |||
deactivateTimer_ = DEACTIVATE_INTERVAL; | |||
decompress(); | |||
} | |||
} |
@@ -1,582 +0,0 @@ | |||
#include "LightServer.h" | |||
#include <algorithm> | |||
#include "log.h" | |||
namespace Swan { | |||
static ChunkPos lightChunkPos(TilePos pos) { | |||
// Same logic as in WorldPlane.cc | |||
return Vec2i( | |||
((size_t)pos.x + (LLONG_MAX / 2) + 1) / CHUNK_WIDTH - | |||
((LLONG_MAX / 2) / CHUNK_WIDTH) - 1, | |||
((size_t)pos.y + (LLONG_MAX / 2) + 1) / CHUNK_HEIGHT - | |||
((LLONG_MAX / 2) / CHUNK_HEIGHT) - 1); | |||
} | |||
static Vec2i lightRelPos(TilePos pos) { | |||
// Same logic as in WorldPlane.cc | |||
return Vec2i( | |||
(pos.x + (size_t)CHUNK_WIDTH * ((LLONG_MAX / 2) / | |||
CHUNK_WIDTH)) % CHUNK_WIDTH, | |||
(pos.y + (size_t)CHUNK_HEIGHT * ((LLONG_MAX / 2) / | |||
CHUNK_HEIGHT)) % CHUNK_HEIGHT); | |||
} | |||
static uint8_t linToSRGB(float lin) { | |||
float s; | |||
if (lin <= 0.0031308) { | |||
s = lin / 12.92; | |||
} else { | |||
s = 1.055 * std::pow(lin, 1/2.4) - 0.055; | |||
} | |||
return std::clamp((int)round(s * 255), 0, 255); | |||
} | |||
static float attenuate(float dist, float squareDist) { | |||
return 1 / (1 + 1 * dist + 0.02 * squareDist); | |||
} | |||
static float attenuate(float dist) { | |||
return attenuate(dist, dist * dist); | |||
} | |||
static float attenuateSquared(float squareDist) { | |||
return attenuate(sqrt(squareDist), squareDist); | |||
} | |||
LightChunk::LightChunk(NewLightChunk &&ch): | |||
blocks(std::move(ch.blocks)), lightSources(std::move(ch.lightSources)) { | |||
for (int y = 0; y < CHUNK_HEIGHT; ++y) { | |||
for (int x = 0; x < CHUNK_WIDTH; ++x) { | |||
if (blocks[y * CHUNK_WIDTH + x]) { | |||
blocksLine[x] += 1; | |||
} | |||
} | |||
} | |||
} | |||
LightServer::LightServer(LightCallback &cb): | |||
cb_(cb), thread_(&LightServer::run, this) {} | |||
LightServer::~LightServer() { | |||
running_ = false; | |||
cond_.notify_one(); | |||
thread_.join(); | |||
} | |||
bool LightServer::tileIsSolid(TilePos pos) { | |||
ChunkPos cpos = lightChunkPos(pos); | |||
LightChunk *chunk = getChunk(cpos); | |||
if (chunk == nullptr) { | |||
return true; | |||
} | |||
Vec2i rpos = lightRelPos(pos); | |||
return chunk->blocks[rpos.y * CHUNK_WIDTH + rpos.x]; | |||
} | |||
LightChunk *LightServer::getChunk(ChunkPos cpos) { | |||
if (cachedChunk_ && cachedChunkPos_ == cpos) { | |||
return cachedChunk_; | |||
} | |||
auto it = chunks_.find(cpos); | |||
if (it != chunks_.end()) { | |||
cachedChunk_ = &it->second; | |||
cachedChunkPos_ = cpos; | |||
return &it->second; | |||
} | |||
return nullptr; | |||
} | |||
void LightServer::processEvent(const Event &evt, std::vector<NewLightChunk> &newChunks) { | |||
auto markAdjacentChunksModified = [&](ChunkPos cpos) { | |||
for (int y = -1; y <= 1; ++y) { | |||
for (int x = -1; x <= 1; ++x) { | |||
updatedChunks_.insert(cpos + Vec2i(x, y)); | |||
} | |||
} | |||
}; | |||
auto markChunks = [&](ChunkPos cpos, Vec2i rpos, bool l, bool r, bool t, bool b) { | |||
updatedChunks_.insert(cpos); | |||
if (l) updatedChunks_.insert(cpos + Vec2i(-1, 0)); | |||
if (r) updatedChunks_.insert(cpos + Vec2i(1, 0)); | |||
if (t) updatedChunks_.insert(cpos + Vec2i(0, -1)); | |||
if (b) updatedChunks_.insert(cpos + Vec2i(0, 1)); | |||
if (l && t) updatedChunks_.insert(cpos + Vec2i(-1, -1)); | |||
if (r && t) updatedChunks_.insert(cpos + Vec2i(1, -1)); | |||
if (l && b) updatedChunks_.insert(cpos + Vec2i(-1, 1)); | |||
if (r && b) updatedChunks_.insert(cpos + Vec2i(1, 1)); | |||
}; | |||
auto markChunksModified = [&](ChunkPos cpos, Vec2i rpos, float light) { | |||
markChunks(cpos, rpos, | |||
light * attenuate(std::max(rpos.x - 5, 0)) >= LIGHT_CUTOFF, | |||
light * attenuate(std::max(CHUNK_WIDTH - rpos.x - 5, 0)) >= LIGHT_CUTOFF, | |||
light * attenuate(std::max(rpos.y - 5, 0)) >= LIGHT_CUTOFF, | |||
light * attenuate(std::max(CHUNK_HEIGHT - rpos.y - 5, 0)) >= LIGHT_CUTOFF); | |||
}; | |||
auto markChunksModifiedRange = [&](ChunkPos cpos, Vec2i rpos, int range) { | |||
markChunks(cpos, rpos, | |||
rpos.x <= range, | |||
CHUNK_WIDTH - rpos.x <= range, | |||
rpos.y <= range, | |||
CHUNK_WIDTH - rpos.y <= range); | |||
}; | |||
if (evt.tag == Event::Tag::CHUNK_ADDED) { | |||
chunks_.emplace(std::piecewise_construct, | |||
std::forward_as_tuple(evt.pos), | |||
std::forward_as_tuple(std::move(newChunks[evt.i]))); | |||
markAdjacentChunksModified(evt.pos); | |||
return; | |||
} else if (evt.tag == Event::Tag::CHUNK_REMOVED) { | |||
chunks_.erase(evt.pos); | |||
markAdjacentChunksModified(evt.pos); | |||
return; | |||
} | |||
ChunkPos cpos = lightChunkPos(evt.pos); | |||
LightChunk *ch = getChunk(cpos); | |||
if (!ch) return; | |||
Vec2i rpos = lightRelPos(evt.pos); | |||
switch (evt.tag) { | |||
case Event::Tag::BLOCK_ADDED: | |||
ch->blocks.set(rpos.y * CHUNK_WIDTH + rpos.x, true); | |||
ch->blocksLine[rpos.x] += 1; | |||
markChunksModifiedRange(cpos, rpos, LIGHT_CUTOFF_DIST); | |||
break; | |||
case Event::Tag::BLOCK_REMOVED: | |||
ch->blocks.set(rpos.y * CHUNK_WIDTH + rpos.x, false); | |||
ch->blocksLine[rpos.x] -= 1; | |||
markChunksModifiedRange(cpos, rpos, LIGHT_CUTOFF_DIST); | |||
break; | |||
case Event::Tag::LIGHT_ADDED: | |||
ch->lightSources[rpos] += evt.f; | |||
markChunksModified(cpos, rpos, ch->lightSources[rpos]); | |||
break; | |||
case Event::Tag::LIGHT_REMOVED: | |||
markChunksModified(cpos, rpos, ch->lightSources[rpos]); | |||
ch->lightSources[rpos] -= evt.f; | |||
if (ch->lightSources[rpos] < LIGHT_CUTOFF) { | |||
ch->lightSources.erase(rpos); | |||
} | |||
break; | |||
// These were handled earlier | |||
case Event::Tag::CHUNK_ADDED: | |||
case Event::Tag::CHUNK_REMOVED: | |||
break; | |||
} | |||
} | |||
float LightServer::recalcTile( | |||
LightChunk &chunk, ChunkPos cpos, Vec2i rpos, TilePos base, | |||
std::vector<std::pair<TilePos, float>> &lights) { | |||
TilePos pos = rpos + base; | |||
constexpr int accuracy = 4; | |||
auto raycast = [&](Vec2 from, Vec2 to) { | |||
auto diff = to - from; | |||
float dist = ((Vec2)diff).length(); | |||
Vec2 step = (Vec2)diff / (dist * accuracy); | |||
Vec2 currpos = from; | |||
TilePos currtile = TilePos(floor(currpos.x), floor(currpos.y)); | |||
auto proceed = [&]() { | |||
TilePos t; | |||
while ((t = TilePos(floor(currpos.x), floor(currpos.y))) == currtile) { | |||
currpos += step; | |||
} | |||
currtile = t; | |||
}; | |||
bool hit = false; | |||
Vec2i target = TilePos(floor(to.x), floor(to.y)); | |||
while (currtile != target && (currpos - from).squareLength() <= diff.squareLength()) { | |||
if (tileIsSolid(currtile)) { | |||
hit = true; | |||
break; | |||
} | |||
proceed(); | |||
} | |||
return hit; | |||
}; | |||
auto diffusedRaycast = [&](Vec2 from, Vec2 to, Vec2 norm) { | |||
if (raycast(from, to)) { | |||
return 0.0f; | |||
} | |||
float dot = (to - from).norm().dot(norm); | |||
if (dot > 1) dot = 1; | |||
else if (dot < 0) dot = 0; | |||
return dot; | |||
}; | |||
bool isSolid = tileIsSolid(pos); | |||
bool culled = | |||
tileIsSolid(pos + Vec2i(-1, 0)) && | |||
tileIsSolid(pos + Vec2i(1, 0)) && | |||
tileIsSolid(pos + Vec2i(0, -1)) && | |||
tileIsSolid(pos + Vec2i(0, 1)); | |||
float acc = 0; | |||
for (auto &[lightpos, level]: lights) { | |||
if (lightpos == pos) { | |||
acc += level; | |||
continue; | |||
} | |||
if (culled) { | |||
continue; | |||
} | |||
int squareDist = (lightpos - pos).squareLength(); | |||
if (squareDist > LIGHT_CUTOFF_DIST * LIGHT_CUTOFF_DIST) { | |||
continue; | |||
} | |||
float light = level * attenuateSquared(squareDist); | |||
if (light < LIGHT_CUTOFF) { | |||
continue; | |||
} | |||
if (!isSolid) { | |||
bool hit = raycast( | |||
Vec2(pos.x + 0.5, pos.y + 0.5), | |||
Vec2(lightpos.x + 0.5, lightpos.y + 0.5)); | |||
if (!hit) { | |||
acc += light; | |||
} | |||
continue; | |||
} | |||
float frac = 0; | |||
float f; | |||
if ((f = diffusedRaycast( | |||
Vec2(pos.x + 0.5, pos.y - 0.1), | |||
Vec2(lightpos.x + 0.5, lightpos.y + 0.5), | |||
Vec2(0, -1))) > frac) { | |||
frac = f; | |||
} | |||
if ((f = diffusedRaycast( | |||
Vec2(pos.x + 0.5, pos.y + 1.1), | |||
Vec2(lightpos.x + 0.5, lightpos.y + 0.5), | |||
Vec2(0, 1))) > frac) { | |||
frac = f; | |||
} | |||
if ((f = diffusedRaycast( | |||
Vec2(pos.x - 0.1, pos.y + 0.5), | |||
Vec2(lightpos.x + 0.5, lightpos.y + 0.5), | |||
Vec2(-1, 0))) > frac) { | |||
frac = f; | |||
} | |||
if ((f = diffusedRaycast( | |||
Vec2(pos.x + 1.1, pos.y + 0.5), | |||
Vec2(lightpos.x + 0.5, lightpos.y + 0.5), | |||
Vec2(1, 0))) > frac) { | |||
frac = f; | |||
} | |||
acc += light * frac; | |||
} | |||
return acc; | |||
} | |||
void LightServer::processChunkSun(LightChunk &chunk, ChunkPos cpos) { | |||
LightChunk *tc = getChunk(cpos + Vec2i(0, -1)); | |||
int base = cpos.y * CHUNK_HEIGHT; | |||
std::bitset<CHUNK_WIDTH> line; | |||
for (int ry = 0; ry < CHUNK_HEIGHT; ++ry) { | |||
int y = base + ry; | |||
float light; | |||
if (y <= 20) { | |||
light = 1; | |||
} else { | |||
light = attenuate(y - 20); | |||
if (light < LIGHT_CUTOFF) { | |||
light = 0; | |||
} | |||
} | |||
for (int rx = 0; rx < CHUNK_WIDTH; ++rx) { | |||
bool lit = light > 0 && tc && tc->blocksLine[rx] == 0 && !line[rx]; | |||
if (lit) { | |||
chunk.lightBuffer()[ry * CHUNK_WIDTH + rx] = light; | |||
if (chunk.blocks[ry * CHUNK_WIDTH + rx]) { | |||
line[rx] = true; | |||
} | |||
} else { | |||
chunk.lightBuffer()[ry * CHUNK_WIDTH + rx] = 0; | |||
} | |||
} | |||
} | |||
} | |||
void LightServer::processChunkLights(LightChunk &chunk, ChunkPos cpos) { | |||
TilePos base = cpos * Vec2i(CHUNK_WIDTH, CHUNK_HEIGHT); | |||
std::vector<std::pair<TilePos, float>> lights; | |||
for (auto &[pos, level]: chunk.lightSources) { | |||
lights.emplace_back(Vec2i(pos) + base, level); | |||
} | |||
auto addLightFromChunk = [&](LightChunk *chunk, int dx, int dy) { | |||
if (chunk == nullptr) { | |||
return; | |||
} | |||
TilePos b = base + Vec2i(dx * CHUNK_WIDTH, dy * CHUNK_HEIGHT); | |||
for (auto &[pos, level]: chunk->lightSources) { | |||
lights.emplace_back(TilePos(pos) + b, level); | |||
} | |||
}; | |||
for (int y = -1; y <= 1; ++y) { | |||
for (int x = -1; x <= 1; ++x) { | |||
if (y == 0 && x == 0) continue; | |||
addLightFromChunk(getChunk(cpos + Vec2i(x, y)), x, y); | |||
} | |||
} | |||
chunk.bounces.clear(); | |||
for (int y = 0; y < CHUNK_HEIGHT; ++y) { | |||
for (int x = 0; x < CHUNK_WIDTH; ++x) { | |||
float light = recalcTile(chunk, cpos, Vec2i(x, y), base, lights); | |||
chunk.lightBuffer()[y * CHUNK_WIDTH + x] += light; | |||
if (light > 0 && chunk.blocks[y * CHUNK_WIDTH + x]) { | |||
chunk.bounces.emplace_back(base + Vec2i(x, y), light * 0.1); | |||
} | |||
} | |||
} | |||
} | |||
void LightServer::processChunkBounces(LightChunk &chunk, ChunkPos cpos) { | |||
TilePos base = cpos * Vec2i(CHUNK_WIDTH, CHUNK_HEIGHT); | |||
std::vector<std::pair<TilePos, float>> lights; | |||
for (auto &light: chunk.bounces) { | |||
lights.emplace_back(light); | |||
} | |||
auto addLightFromChunk = [&](LightChunk *chunk) { | |||
if (chunk == nullptr) { | |||
return; | |||
} | |||
for (auto &light: chunk->bounces) { | |||
lights.emplace_back(light); | |||
} | |||
}; | |||
for (int y = -1; y <= 1; ++y) { | |||
for (int x = -1; x <= 1; ++x) { | |||
if (y == 0 && x == 0) continue; | |||
addLightFromChunk(getChunk(cpos + Vec2i(x, y))); | |||
} | |||
} | |||
for (int y = 0; y < CHUNK_HEIGHT; ++y) { | |||
for (int x = 0; x < CHUNK_WIDTH; ++x) { | |||
float light = recalcTile(chunk, cpos, Vec2i(x, y), base, lights); | |||
float sum = chunk.lightBuffer()[y * CHUNK_WIDTH + x] + light; | |||
chunk.lightBuffer()[y * CHUNK_WIDTH + x] = sum; | |||
} | |||
} | |||
} | |||
void LightServer::processChunkSmoothing(LightChunk &chunk, ChunkPos cpos) { | |||
LightChunk *tc = getChunk(cpos + Vec2i(0, -1)); | |||
LightChunk *bc = getChunk(cpos + Vec2i(0, 1)); | |||
LightChunk *lc = getChunk(cpos + Vec2i(-1, 0)); | |||
LightChunk *rc = getChunk(cpos + Vec2i(1, 0)); | |||
auto getLight = [&](LightChunk &chunk, int x, int y) { | |||
return chunk.lightBuffer()[y * CHUNK_WIDTH + x]; | |||
}; | |||
auto calc = [&](int x1, int x2, int y1, int y2, auto tf, auto bf, auto lf, auto rf) { | |||
float *dest = chunk.lightBuffers + CHUNK_WIDTH * CHUNK_HEIGHT * ((chunk.buffer + 1) % 2); | |||
for (int y = y1; y < y2; ++y) { | |||
for (int x = x1; x < x2; ++x) { | |||
float t = tf(x, y); | |||
float b = bf(x, y); | |||
float l = lf(x, y); | |||
float r = rf(x, y); | |||
float light = chunk.lightBuffer()[y * CHUNK_WIDTH + x]; | |||
int count = 1; | |||
if (t > light) { light += t; count += 1; } | |||
if (b > light) { light += b; count += 1; } | |||
if (l > light) { light += l; count += 1; } | |||
if (r > light) { light += r; count += 1; } | |||
light /= count; | |||
dest[y * CHUNK_WIDTH + x] = light; | |||
} | |||
} | |||
}; | |||
calc(1, CHUNK_WIDTH - 1, 1, CHUNK_HEIGHT - 1, | |||
[&](int x, int y) { return getLight(chunk, x, y - 1); }, | |||
[&](int x, int y) { return getLight(chunk, x, y + 1); }, | |||
[&](int x, int y) { return getLight(chunk, x - 1, y); }, | |||
[&](int x, int y) { return getLight(chunk, x + 1, y); }); | |||
if (tc) { | |||
calc(1, CHUNK_WIDTH - 1, 0, 1, | |||
[&](int x, int y) { return tc->lightBuffer()[(CHUNK_HEIGHT - 1) * CHUNK_WIDTH + x]; }, | |||
[&](int x, int y) { return getLight(chunk, x, y + 1); }, | |||
[&](int x, int y) { return getLight(chunk, x - 1, y); }, | |||
[&](int x, int y) { return getLight(chunk, x + 1, y); }); | |||
} | |||
if (bc) { | |||
calc(1, CHUNK_WIDTH - 1, CHUNK_HEIGHT - 1, CHUNK_HEIGHT, | |||
[&](int x, int y) { return getLight(chunk, x, y - 1); }, | |||
[&](int x, int y) { return bc->lightBuffer()[x]; }, | |||
[&](int x, int y) { return getLight(chunk, x - 1, y); }, | |||
[&](int x, int y) { return getLight(chunk, x + 1, y); }); | |||
} | |||
if (lc) { | |||
calc(0, 1, 1, CHUNK_HEIGHT - 1, | |||
[&](int x, int y) { return getLight(chunk, x, y - 1); }, | |||
[&](int x, int y) { return getLight(chunk, x, y + 1); }, | |||
[&](int x, int y) { return lc->lightBuffer()[y * CHUNK_WIDTH + CHUNK_WIDTH - 1]; }, | |||
[&](int x, int y) { return getLight(chunk, x + 1, y); }); | |||
} | |||
if (rc) { | |||
calc(CHUNK_WIDTH - 1, CHUNK_WIDTH, 1, CHUNK_HEIGHT - 1, | |||
[&](int x, int y) { return getLight(chunk, x, y - 1); }, | |||
[&](int x, int y) { return getLight(chunk, x, y + 1); }, | |||
[&](int x, int y) { return getLight(chunk, x - 1, y); }, | |||
[&](int x, int y) { return rc->lightBuffer()[y * CHUNK_WIDTH]; }); | |||
} | |||
if (tc && lc) { | |||
calc(0, 1, 0, 1, | |||
[&](int x, int y) { return tc->lightBuffer()[(CHUNK_HEIGHT - 1) * CHUNK_WIDTH + x]; }, | |||
[&](int x, int y) { return getLight(chunk, x, y + 1); }, | |||
[&](int x, int y) { return lc->lightBuffer()[y * CHUNK_WIDTH + CHUNK_WIDTH - 1]; }, | |||
[&](int x, int y) { return getLight(chunk, x + 1, y); }); | |||
} | |||
if (tc && rc) { | |||
calc(CHUNK_WIDTH - 1, CHUNK_WIDTH, 0, 1, | |||
[&](int x, int y) { return tc->lightBuffer()[(CHUNK_HEIGHT - 1) * CHUNK_WIDTH + x]; }, | |||
[&](int x, int y) { return getLight(chunk, x, y + 1); }, | |||
[&](int x, int y) { return getLight(chunk, x - 1, y); }, | |||
[&](int x, int y) { return rc->lightBuffer()[y * CHUNK_WIDTH]; }); | |||
} | |||
if (bc && lc) { | |||
calc(0, 1, CHUNK_HEIGHT - 1, CHUNK_HEIGHT, | |||
[&](int x, int y) { return getLight(chunk, x, y - 1); }, | |||
[&](int x, int y) { return bc->lightBuffer()[x]; }, | |||
[&](int x, int y) { return lc->lightBuffer()[y * CHUNK_WIDTH + CHUNK_WIDTH - 1]; }, | |||
[&](int x, int y) { return getLight(chunk, x + 1, y); }); | |||
} | |||
if (bc && rc) { | |||
calc(CHUNK_WIDTH - 1, CHUNK_WIDTH, CHUNK_HEIGHT - 1, CHUNK_HEIGHT, | |||
[&](int x, int y) { return getLight(chunk, x, y - 1); }, | |||
[&](int x, int y) { return bc->lightBuffer()[x]; }, | |||
[&](int x, int y) { return getLight(chunk, x - 1, y); }, | |||
[&](int x, int y) { return rc->lightBuffer()[y * CHUNK_WIDTH]; }); | |||
} | |||
} | |||
void LightServer::finalizeChunk(LightChunk &chunk) { | |||
for (int y = 0; y < CHUNK_HEIGHT; ++y) { | |||
for (int x = 0; x < CHUNK_WIDTH; ++x) { | |||
chunk.lightLevels[y * CHUNK_WIDTH + x] = | |||
linToSRGB(chunk.lightBuffer()[y * CHUNK_HEIGHT + x]); | |||
} | |||
} | |||
} | |||
void LightServer::run() { | |||
std::unique_lock<std::mutex> lock(mut_, std::defer_lock); | |||
while (running_) { | |||
lock.lock(); | |||
cond_.wait(lock, [&] { return buffers_[buffer_].size() > 0 || !running_; }); | |||
std::vector<Event> &buf = buffers_[buffer_]; | |||
std::vector<NewLightChunk> &newChunks = newChunkBuffers_[buffer_]; | |||
buffer_ = (buffer_ + 1) % 2; | |||
lock.unlock(); | |||
updatedChunks_.clear(); | |||
for (auto &evt: buf) { | |||
processEvent(evt, newChunks); | |||
} | |||
buf.clear(); | |||
newChunks.clear(); | |||
for (auto &pos: updatedChunks_) { | |||
auto ch = chunks_.find(pos); | |||
if (ch != chunks_.end()) { | |||
processChunkSun(ch->second, ChunkPos(pos.first, pos.second)); | |||
} | |||
} | |||
for (auto &pos: updatedChunks_) { | |||
auto ch = chunks_.find(pos); | |||
if (ch != chunks_.end()) { | |||
processChunkLights(ch->second, ChunkPos(pos.first, pos.second)); | |||
} | |||
} | |||
for (auto &pos: updatedChunks_) { | |||
auto ch = chunks_.find(pos); | |||
if (ch != chunks_.end()) { | |||
processChunkBounces(ch->second, ChunkPos(pos.first, pos.second)); | |||
} | |||
} | |||
for (int i = 0; i < 4; ++i) { | |||
for (auto &pos: updatedChunks_) { | |||
auto ch = chunks_.find(pos); | |||
if (ch != chunks_.end()) { | |||
processChunkSmoothing(ch->second, ChunkPos(pos.first, pos.second)); | |||
ch->second.buffer = (ch->second.buffer + 1) % 2; | |||
} | |||
} | |||
} | |||
for (auto &pos: updatedChunks_) { | |||
auto ch = chunks_.find(pos); | |||
if (ch != chunks_.end()) { | |||
finalizeChunk(ch->second); | |||
} | |||
} | |||
for (auto &pos: updatedChunks_) { | |||
auto ch = chunks_.find(pos); | |||
if (ch != chunks_.end()) { | |||
cb_.onLightChunkUpdated(ch->second, pos); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -44,8 +44,7 @@ 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)), | |||
entColls_(std::move(colls)), | |||
lighting_(std::make_unique<LightServer>(*this)) { | |||
entColls_(std::move(colls)) { | |||
for (auto &coll: entColls_) { | |||
entCollsByType_[coll->type()] = coll.get(); | |||
@@ -78,7 +77,7 @@ Chunk &WorldPlane::slowGetChunk(ChunkPos pos) { | |||
ZoneScopedN("WorldPlane slowGetChunk"); | |||
auto iter = chunks_.find(pos); | |||
// Create chunk if that turns out to be necessary | |||
// Generate chunk if that turns out to be necessary | |||
if (iter == chunks_.end()) { | |||
iter = chunks_.emplace(pos, Chunk(pos)).first; | |||
Chunk &chunk = iter->second; | |||
@@ -86,23 +85,7 @@ Chunk &WorldPlane::slowGetChunk(ChunkPos pos) { | |||
gen_->genChunk(*this, chunk); | |||
activeChunks_.push_back(&chunk); | |||
chunkInitList_.push_back(&chunk); | |||
// Need to tell the light engine too | |||
NewLightChunk lc; | |||
for (int y = 0; y < CHUNK_HEIGHT; ++y) { | |||
for (int x = 0; x < CHUNK_WIDTH; ++x) { | |||
Tile::ID id = chunk.getTileID({ x, y }); | |||
Tile &tile = world_->getTileByID(id); | |||
if (tile.isSolid) { | |||
lc.blocks[y * CHUNK_HEIGHT + x] = true; | |||
} | |||
if (tile.lightLevel > 0) { | |||
lc.lightSources[{x, y}] = tile.lightLevel; | |||
} | |||
} | |||
} | |||
lighting_->onChunkAdded(pos, std::move(lc)); | |||
chunk.generateDone(*world_); | |||
// Otherwise, it might not be active, so let's activate it | |||
} else if (!iter->second.isActive()) { | |||
@@ -124,19 +107,13 @@ void WorldPlane::setTileID(TilePos pos, Tile::ID id) { | |||
Tile &oldTile = world_->getTileByID(old); | |||
chunk.setTileID(rp, id); | |||
if (!oldTile.isSolid && newTile.isSolid) { | |||
lighting_->onSolidBlockAdded(pos); | |||
} else if (oldTile.isSolid && !newTile.isSolid) { | |||
lighting_->onSolidBlockRemoved(pos); | |||
} | |||
if (newTile.lightLevel != oldTile.lightLevel) { | |||
if (oldTile.lightLevel > 0) { | |||
removeLight(pos, oldTile.lightLevel); | |||
chunk.removeLight(rp); | |||
} | |||
if (newTile.lightLevel > 0) { | |||
addLight(pos, newTile.lightLevel); | |||
chunk.addLight(rp, newTile.lightLevel); | |||
} | |||
} | |||
} | |||
@@ -233,8 +210,6 @@ void WorldPlane::draw(Cygnet::Renderer &rnd) { | |||
coll->draw(ctx, rnd); | |||
} | |||
lighting_->flip(); | |||
/* | |||
if (debugBoxes_.size() > 0) { | |||
for (auto &pos: debugBoxes_) { | |||
@@ -299,20 +274,4 @@ void WorldPlane::debugBox(TilePos pos) { | |||
debugBoxes_.push_back(pos); | |||
} | |||
void WorldPlane::addLight(TilePos pos, float level) { | |||
getChunk(chunkPos(pos)); | |||
lighting_->onLightAdded(pos, level); | |||
} | |||
void WorldPlane::removeLight(TilePos pos, float level) { | |||
getChunk(chunkPos(pos)); | |||
lighting_->onLightRemoved(pos, level); | |||
} | |||
void WorldPlane::onLightChunkUpdated(const LightChunk &chunk, ChunkPos pos) { | |||
std::lock_guard<std::mutex> lock(mut_); | |||
Chunk &realChunk = getChunk(pos); | |||
realChunk.setLightData(chunk.lightLevels); | |||
} | |||
} |