Browse Source

some kind of light rendering on the GPU

feature/new-lighting-engine
Martin Dørum 3 years ago
parent
commit
8e585347d2

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

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

+ 6
- 17
libcygnet/include/cygnet/Renderer.h View File

@@ -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
- 2
libcygnet/include/cygnet/shaders.h View File

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

}

+ 128
- 77
libcygnet/src/Renderer.cc View File

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

+ 0
- 4
libcygnet/src/Window.cc View File

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

+ 53
- 23
libcygnet/src/shaders.cc View File

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

}

+ 0
- 1
libswan/CMakeLists.txt View File

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

+ 6
- 22
libswan/include/swan/Chunk.h View File

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

+ 0
- 138
libswan/include/swan/LightServer.h View File

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

}

+ 1
- 14
libswan/include/swan/WorldPlane.h View File

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

/*

+ 46
- 16
libswan/src/Chunk.cc View File

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

}

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

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

}

+ 5
- 46
libswan/src/WorldPlane.cc View File

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

}

Loading…
Cancel
Save