Browse Source

lighting system work

fix/style
Martin Dørum 3 years ago
parent
commit
102be7ec37

+ 4
- 0
CMakeLists.txt View File

@@ -73,6 +73,10 @@ add_executable(perlin-test EXCLUDE_FROM_ALL
src/perlin-test.cc)
target_link_libraries(perlin-test libswan PNG::PNG ${libraries})

add_executable(lighting-test EXCLUDE_FROM_ALL
src/lighting-test.cc)
target_link_libraries(lighting-test libswan PNG::PNG ${libraries})

set(assets
assets/icon.png
assets/music/happy-1.wav)

+ 1
- 0
libswan/CMakeLists.txt View File

@@ -11,6 +11,7 @@ add_library(libswan SHARED
src/gfxutil.cc
src/Item.cc
src/ItemStack.cc
src/LightingThread.cc
src/Mod.cc
src/OS.cc
src/Resource.cc

+ 124
- 0
libswan/include/swan/LightingThread.h View File

@@ -0,0 +1,124 @@
#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>, uint8_t> light_sources;
};

struct LightChunk {
LightChunk() = default;
LightChunk(NewLightChunk &&ch):
blocks(std::move(ch.blocks)), light_sources(std::move(ch.light_sources)) {}

std::bitset<CHUNK_WIDTH * CHUNK_HEIGHT> blocks;
uint8_t light_levels[CHUNK_WIDTH * CHUNK_HEIGHT] = { 0 };
uint8_t blocks_line[CHUNK_WIDTH] = { 0 };
std::map<std::pair<int, int>, uint8_t> light_sources;

bool was_updated = false;
};

class LightingCallback {
public:
virtual void onLightChunkUpdated(const LightChunk &chunk, Vec2i pos) = 0;
};

class LightingThread {
public:
LightingThread(LightingCallback &cb);
~LightingThread();

void onSolidBlockAdded(TilePos pos);
void onSolidBlockRemoved(TilePos pos);
void onLightAdded(TilePos pos, uint8_t level);
void onLightRemoved(TilePos pos, uint8_t level);
void onChunkAdded(Vec2i pos, NewLightChunk &&chunk);
void onChunkRemoved(Vec2i pos);

private:
struct Event {
enum class Tag {
BLOCK_ADDED, BLOCK_REMOVED, LIGHT_ADDED, LIGHT_REMOVED,
CHUNK_ADDED, CHUNK_REMOVED,
} tag;

TilePos pos;
union {
size_t num;
};
};

bool tileIsSolid(TilePos pos);
LightChunk *getChunk(Vec2i cpos);

int recalcTile(LightChunk &chunk, Vec2i cpos, Vec2i rpos, TilePos base);
void processUpdatedChunk(LightChunk &chunk, Vec2i cpos);
void processEvent(const Event &event, std::vector<NewLightChunk> &newChunks);
void run();

LightingCallback &cb_;
bool running_ = true;
std::map<std::pair<int, int>, LightChunk> chunks_;
std::set<std::pair<int, int>> updated_chunks_;
LightChunk *cached_chunk_ = nullptr;
Vec2i cached_chunk_pos_;

int buffer_ = 0;
std::vector<Event> buffers_[2] = { {}, {} };
std::vector<NewLightChunk> new_chunk_buffers_[2] = { {}, {} };
std::thread thread_;
std::condition_variable cond_;
std::mutex mut_;
};

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

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

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

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

inline void LightingThread::onChunkAdded(Vec2i pos, NewLightChunk &&chunk) {
std::lock_guard<std::mutex> lock(mut_);
buffers_[buffer_].push_back({ Event::Tag::CHUNK_ADDED, pos,
{ .num = new_chunk_buffers_[buffer_].size() } });
new_chunk_buffers_[buffer_].push_back(std::move(chunk));
cond_.notify_one();
}

inline void LightingThread::onChunkRemoved(Vec2i pos) {
std::lock_guard<std::mutex> lock(mut_);
buffers_[buffer_].push_back({ Event::Tag::CHUNK_ADDED, pos, { 0 } });
cond_.notify_one();
}

}

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

@@ -16,13 +16,14 @@
#include "WorldGen.h"
#include "Entity.h"
#include "Collection.h"
#include "LightingThread.h"

namespace Swan {

class World;
class Game;

class WorldPlane: NonCopyable {
class WorldPlane final: NonCopyable, public LightingCallback {
public:
using ID = uint16_t;

@@ -74,6 +75,9 @@ public:

void debugBox(TilePos pos);

// LightingCallback implementation
void onLightChunkUpdated(const LightChunk &chunk, Vec2i pos) final { /* TODO */ };

ID id_;
World *world_;
std::unique_ptr<WorldGen> gen_;
@@ -82,6 +86,8 @@ private:
void addLight(TilePos pos, uint8_t level);
void removeLight(TilePos pos, uint8_t level);

std::unique_ptr<LightingThread> lighting_;

std::map<std::pair<int, int>, Chunk> chunks_;
std::vector<Chunk *> active_chunks_;
std::vector<std::pair<ChunkPos, Chunk *>> tick_chunks_;

+ 239
- 0
libswan/src/LightingThread.cc View File

@@ -0,0 +1,239 @@
#include "LightingThread.h"

#include "log.h"

namespace Swan {

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

LightingThread::LightingThread(LightingCallback &cb):
cb_(cb), thread_(&LightingThread::run, this) {}

LightingThread::~LightingThread() {
running_ = false;
cond_.notify_one();
thread_.join();
}

bool LightingThread::tileIsSolid(TilePos pos) {
Vec2i 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 *LightingThread::getChunk(Vec2i cpos) {
if (cached_chunk_ && cached_chunk_pos_ == cpos) {
return cached_chunk_;
}

auto it = chunks_.find(cpos);
if (it != chunks_.end()) {
cached_chunk_ = &it->second;
cached_chunk_pos_ = cpos;
return &it->second;
}

return nullptr;
}

void LightingThread::processEvent(const Event &evt, std::vector<NewLightChunk> &newChunks) {
info << "event " << (int)evt.tag;

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.num])));
LightChunk &ch = chunks_[evt.pos]; // Create and default initialize
ch.was_updated = true;
updated_chunks_.insert(evt.pos);
return;
} else if (evt.tag == Event::Tag::CHUNK_REMOVED) {
chunks_.erase(evt.pos);
return;
}

Vec2i cpos = lightChunkPos(evt.pos);
LightChunk *ch = getChunk(cpos);
if (!ch) return;
ch->was_updated = true;
updated_chunks_.insert(cpos);
Vec2i rpos = lightRelPos(evt.pos);

// TODO: Mark neighbouring chunks as updated

switch (evt.tag) {
case Event::Tag::BLOCK_ADDED:
ch->blocks.set(rpos.y * CHUNK_WIDTH + rpos.x, true);
ch->blocks_line[rpos.x] += 1;
break;

case Event::Tag::BLOCK_REMOVED:
ch->blocks.set(rpos.y * CHUNK_WIDTH + rpos.x, false);
ch->blocks_line[rpos.x] -= 1;
break;

case Event::Tag::LIGHT_ADDED:
ch->light_sources[rpos] += evt.num;
break;

case Event::Tag::LIGHT_REMOVED:
ch->light_sources[rpos] -= evt.num;
break;

// These were handled earlier
case Event::Tag::CHUNK_ADDED:
case Event::Tag::CHUNK_REMOVED:
break;
}
}

int LightingThread::recalcTile(LightChunk &chunk, Vec2i cpos, Vec2i rpos, TilePos base) {
std::vector<std::pair<Vec2i, uint8_t>> lights;
TilePos pos = rpos + base;

// TODO: Gather light sources from other chunks oo
for (auto &[lightrel, level]: chunk.light_sources) {
TilePos lightpos = base + Vec2i(lightrel.first, lightrel.second);
Vec2i diff = lightpos - pos;
if (diff.x * diff.x + diff.y * diff.y > level * level) {
continue;
}

lights.push_back({ lightpos, level });
}

chunk.light_levels[rpos.y * CHUNK_WIDTH + rpos.x] = 0;

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;
Vec2i currtile = Vec2i(floor(currpos.x), floor(currpos.y));
auto proceed = [&]() {
Vec2i t;
while ((t = Vec2i(floor(currpos.x), floor(currpos.y))) == currtile) {
currpos += step;
}
currtile = t;
};

proceed();

bool hit = false;
while ((currpos - from).squareLength() <= diff.squareLength()) {
if (tileIsSolid(currtile)) {
hit = true;
break;
}

proceed();
}

return hit;
};

int acc = 0;
for (auto &[lightpos, level]: lights) {
if (lightpos == pos) {
acc += level;
continue;
}

float dist = ((Vec2)(lightpos - pos)).length();
int light = level - (int)dist;

int hit =
raycast(
Vec2(pos.x + 0.3, pos.y + 0.3),
Vec2(lightpos.x + 0.3, lightpos.y + 0.3)) +
raycast(
Vec2(pos.x + 0.7, pos.y + 0.3),
Vec2(lightpos.x + 0.7, lightpos.y + 0.3)) +
raycast(
Vec2(pos.x + 0.3, pos.y + 0.7),
Vec2(lightpos.x + 0.3, lightpos.y + 0.7)) +
raycast(
Vec2(pos.x + 0.7, pos.y + 0.7),
Vec2(lightpos.x + 0.7, lightpos.y + 0.7));

acc += (light * (4 - hit)) / 4;
if (acc >= 255) {
return 255;
}
}

return acc;
}

void LightingThread::processUpdatedChunk(LightChunk &chunk, Vec2i cpos) {
auto start = std::chrono::steady_clock::now();

TilePos base = cpos * Vec2i(CHUNK_WIDTH * CHUNK_HEIGHT);
for (int y = 0; y < CHUNK_HEIGHT; ++y) {
for (int x = 0; x < CHUNK_WIDTH; ++x) {
chunk.light_levels[y * CHUNK_WIDTH + x] =
recalcTile(chunk, cpos, Vec2i(x, y), base);
}
}

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

cb_.onLightChunkUpdated(chunk, cpos);
}

void LightingThread::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 = new_chunk_buffers_[buffer_];
buffer_ = (buffer_ + 1) % 2;
lock.unlock();

updated_chunks_.clear();
for (auto &evt: buf) {
processEvent(evt, newChunks);
}

buf.clear();
newChunks.clear();

for (auto &pos: updated_chunks_) {
auto ch = chunks_.find(pos);
if (ch != chunks_.end()) {
processUpdatedChunk(ch->second, Vec2i(pos.first, pos.second));
}
}

updated_chunks_.clear();
}
}

}

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

@@ -45,7 +45,9 @@ Context WorldPlane::getContext() {
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)), ent_colls_(std::move(colls)) {
id_(id), world_(world), gen_(std::move(gen)),
lighting_(std::make_unique<LightingThread>(*this)),
ent_colls_(std::move(colls)) {

for (auto &coll: ent_colls_) {
ent_colls_by_type_[coll->type()] = coll.get();
@@ -108,12 +110,22 @@ void WorldPlane::setTileID(TilePos pos, Tile::ID id) {
chunk.setTileID(rp, id, newTile.image_.texture_.get());
chunk.markModified();

if (oldTile.light_level_ > 0) {
removeLight(pos, oldTile.light_level_);
if (!oldTile.is_solid_ && newTile.is_solid_) {
lighting_->onSolidBlockAdded(pos);
} else if (oldTile.is_solid_ && !newTile.is_solid_) {
lighting_->onSolidBlockRemoved(pos);
}

if (newTile.light_level_ > 0) {
addLight(pos, newTile.light_level_);
if (newTile.light_level_ != oldTile.light_level_) {
if (oldTile.light_level_ > 0) {
lighting_->onLightRemoved(pos, oldTile.light_level_);
removeLight(pos, oldTile.light_level_);
}

if (newTile.light_level_ > 0) {
lighting_->onLightAdded(pos, newTile.light_level_);
addLight(pos, newTile.light_level_);
}
}
}
}

+ 78
- 0
src/lighting-test.cc View File

@@ -0,0 +1,78 @@
#include <swan/LightingThread.h>
#include <swan/log.h>
#include <png++/png.hpp>
#include <chrono>

class CB final: public Swan::LightingCallback {
public:
void onLightChunkUpdated(const Swan::LightChunk &chunk, Swan::Vec2i pos) final {
Swan::info << "light chunk at " << pos;
chunk_ = chunk;
done_ = true;
cond_.notify_one();
}

Swan::LightChunk chunk_;
bool done_ = false;
std::mutex mut_;
std::condition_variable cond_;
};

int main() {
CB cb;
Swan::LightingThread lt(cb);

Swan::NewLightChunk nc;
auto set = [&](int x, int y) { nc.blocks[y * Swan::CHUNK_WIDTH + x] = true; };
set(0, 0);
set(18, 3);
set(12, 13);
set(28, 22);
set(22, 12);
for (int x = 4; x < 28; ++x) {
set(x, 24);
}
for (int x = 12; x < 20; ++x) {
set(x, 26);
}
nc.light_sources = {
{ { 20, 10 }, 20 },
{ { 16, 30 }, 20 },
{ { 5, 27 }, 20 },
};

lt.onChunkAdded({0, 0}, std::move(nc));

std::unique_lock<std::mutex> lock(cb.mut_);
cb.cond_.wait(lock, [&] { return cb.done_; });

png::image<png::rgb_pixel> image(Swan::CHUNK_WIDTH, Swan::CHUNK_HEIGHT);
for (int y = 0; y < Swan::CHUNK_HEIGHT; ++y) {
for (int x = 0; x < Swan::CHUNK_WIDTH; ++x) {
uint8_t light = cb.chunk_.light_levels[y * Swan::CHUNK_WIDTH + x];
bool block = false;
if (cb.chunk_.blocks[y * Swan::CHUNK_WIDTH + x]) {
block = true;
}

bool isLight =
(x == 20 && y == 10) ||
(x == 16 && y == 30) ||
(x == 5 && y == 27);

unsigned char lightcol = (unsigned char)(sqrt(light) * 30);
if (block) {
image[y][x] = {
lightcol, lightcol, lightcol };
} else if (isLight) {
image[y][x] = {
255, 255, 64 };
} else {
image[y][x] = {
lightcol, 0, 0 };
}
}
}

image.write("lighting-test.png");
}

Loading…
Cancel
Save