Better lighting and a circle algorithm that works more reliably.

main
Zed A. Shaw 4 days ago
parent 03fe9b3d01
commit 35f2defc11
  1. 12
      assets/tiles.json
  2. 40
      lights.cpp
  3. 19
      lights.hpp
  4. 4
      main.cpp
  5. 48
      matrix.cpp
  6. 14
      matrix.hpp
  7. 13
      systems.cpp
  8. 14
      tests/matrix.cpp
  9. 2
      worldbuilder.cpp

@ -1,32 +1,32 @@
{
"WALL_TILE": {
"foreground": [230, 20, 0],
"background": [230, 20, 0],
"background": [230, 20, 2],
"display": "\ua5b8"
},
"FLOOR_TILE": {
"foreground": [80, 100, 0],
"background": [30, 20, 0],
"background": [30, 20, 2],
"display":"\u2849"
},
"PLAYER_TILE": {
"foreground": [255, 200, 0],
"background": [30, 20, 0],
"background": [30, 20, 2],
"display":"\ua66b"
},
"ENEMY_TILE": {
"foreground": [255, 200, 0],
"background": [30, 20, 0],
"background": [30, 20, 2],
"display":"\u1d5c"
},
"BG_TILE": {
"foreground": [230, 20, 0],
"background": [230, 20, 0],
"background": [230, 20, 2],
"display":"█"
},
"WATER_TILE": {
"foreground": [132, 200, 0],
"background": [147, 220, 0],
"background": [147, 220, 2],
"display":"\u224b"
}
}

@ -5,18 +5,50 @@
using std::vector;
namespace lighting {
void LightRender::render_light(LightSource source, Point at) {
Point min, max;
clear_light_target(at);
vector<Point> has_light;
void LightRender::render_circle_light(LightSource source, Point at, PointList &has_light) {
for(matrix::circle it{at, source.distance + 1}; it.next();) {
for(int x = it.left; x < it.right; x++) {
if(matrix::inbounds($paths.$paths, x, it.y) &&
$paths.$paths[it.y][x] != WALL_PATH_LIMIT)
{
$lightmap[it.y][x] = light_level(source.strength, x, it.y);
has_light.push_back({(size_t)x, (size_t)it.y});
}
}
}
}
void LightRender::render_compass_light(LightSource source, Point at, PointList &has_light) {
for(matrix::compass it{$lightmap, at.x, at.y}; it.next();) {
if($paths.$paths[it.y][it.x] != WALL_PATH_LIMIT) {
$lightmap[it.y][it.x] = light_level(source.strength, it.x, it.y);
has_light.push_back({it.x, it.y});
}
}
}
void LightRender::render_square_light(LightSource source, Point at, PointList &has_light) {
for(matrix::in_box it{$lightmap, at.x, at.y, (size_t)source.distance}; it.next();) {
if($paths.$paths[it.y][it.x] != WALL_PATH_LIMIT) {
$lightmap[it.y][it.x] = light_level(source.strength, it.x, it.y);
has_light.push_back({it.x, it.y});
}
}
}
void LightRender::render_light(LightSource source, Point at) {
Point min, max;
clear_light_target(at);
PointList has_light;
if(source.distance == 0) {
render_compass_light(source, at, has_light);
} else if(source.distance == 1) {
render_square_light(source, at, has_light);
} else {
render_circle_light(source, at, has_light);
}
const int wall_light = source.strength + WALL_LIGHT_LEVEL;
for(auto point : has_light) {

@ -13,20 +13,20 @@ namespace lighting {
int distance = 1; // higher is farther, in squares
};
const int MIN = 40;
const int MAX = 220;
const int MID = 140;
const int MIN = 50;
const int MAX = 170;
const int MID = 130;
const std::array<int, 10> LEVELS{
MAX,
200,
180,
160,
150,
140,
MID,
120,
100,
80,
60,
110,
90,
70,
MIN,
};
@ -51,6 +51,9 @@ namespace lighting {
void light_box(LightSource source, Point from, Point &min_out, Point &max_out);
int light_level(int level, size_t x, size_t y);
void render_light(LightSource source, Point at);
void render_square_light(LightSource source, Point at, PointList &has_light);
void render_compass_light(LightSource source, Point at, PointList &has_light);
void render_circle_light(LightSource source, Point at, PointList &has_light);
Matrix &lighting() { return $lightmap; }
};
}

@ -46,7 +46,7 @@ void configure_world(DinkyECS::World &world, Map &game_map) {
world.set<Motion>(enemy2, {0,0});
world.set<Combat>(enemy2, {20, 10});
world.set<Tile>(enemy2, {"*"});
world.set<LightSource>(enemy2, {7,1});
world.set<LightSource>(enemy2, {7,0});
auto gold = world.entity();
world.set<Position>(gold, {game_map.place_entity(3)});
@ -55,7 +55,7 @@ void configure_world(DinkyECS::World &world, Map &game_map) {
auto wall_torch = world.entity();
world.set<Position>(wall_torch, {game_map.place_entity(4)});
world.set<LightSource>(wall_torch, {2,3});
world.set<LightSource>(wall_torch, {3,4});
world.set<Tile>(wall_torch, {""});
}

@ -183,48 +183,18 @@ namespace matrix {
circle::circle(Point center, int radius) :
center(center), radius(radius)
{
xi = 0;
yi = radius;
m = 5 - 4 * radius;
step = 0;
}
void circle::update() {
if(m > 0) {
yi--;
m -= 8 * yi;
}
xi++;
m += 8 * xi + 4;
top = max(center.y - radius, size_t(0));
bottom = center.y + radius;
y = top;
}
bool circle::next() {
if(xi <= yi) {
switch(step % 4) {
case 0:
x0 = center.x - xi;
y = center.y - yi;
x1 = center.x + xi;
break;
case 1:
x0 = center.x - yi;
y = center.y - xi;
x1 = center.x + yi;
break;
case 2:
x0 = center.x - yi;
y = center.y + xi;
x1 = center.x + yi;
break;
case 3:
x0 = center.x - xi;
y = center.y + yi;
x1 = center.x + xi;
update();
break;
}
step++;
y++;
if(y <= bottom) {
dy = y - center.y;
dx = floor(sqrt(radius * radius - dy * dy));
left = center.x - dx;
right = center.x + dx;
return true;
} else {
return false;

@ -120,13 +120,13 @@ namespace matrix {
struct circle {
Point center;
int radius = 0;
int xi = 0;
int yi = 0;
int m = 0;
int step = 0;
int x0;
int x1;
int y;
int y = 0;
int dx = 0;
int dy = 0;
int left = 0;
int right = 0;
int top = 0;
int bottom = 0;
circle(Point center, int radius);
void update();

@ -189,20 +189,19 @@ void System::draw_map(DinkyECS::World &world, Map &game_map, const Matrix &light
for(size_t x = 0; x < end_x; ++x) {
const TileCell& tile = tiles.at(start.x+x, start.y+y);
int light_value = debug.LIGHT ? 160 : lighting[start.y+y][start.x+x];
int dnum = debug.PATHS ? paths[start.y+y][start.x+x] : WALL_PATH_LIMIT;
if(debug.PATHS) {
int dnum = paths[start.y+y][start.x+x];
string num = format("{:x}", dnum);
num = num.size() > 2 ? "*" : num;
if(debug.PATHS && dnum != WALL_PATH_LIMIT) {
string num = dnum > 15 ? "*" : format("{:x}", dnum);
canvas.DrawText(x * 2, y * 4, num, [dnum, light_value](auto &pixel) {
pixel.foreground_color = Color::HSV(dnum * 20, 150, 200);
pixel.background_color = Color::HSV(30, 20, light_value / 5);
pixel.background_color = Color::HSV(30, 20, light_value / 2);
});
} else {
canvas.DrawText(x * 2, y * 4, tile.display, [tile, light_value](auto &pixel) {
pixel.foreground_color = Color::HSV(tile.fg_h, tile.fg_s, light_value);
pixel.background_color = Color::HSV(tile.bg_h, tile.bg_s, light_value / 2);
pixel.foreground_color = Color::HSV(tile.fg_h, tile.fg_s, light_value - tile.fg_v);
pixel.background_color = Color::HSV(tile.bg_h, tile.bg_s, light_value / tile.bg_v);
});
}
}

@ -226,22 +226,28 @@ TEST_CASE("prototype line algorithm", "[matrix:line]") {
}
TEST_CASE("prototype circle algorithm", "[matrix:circle]") {
for(int count = 0; count < 20; count++) {
size_t width = Random::uniform<size_t>(10, 13);
size_t height = Random::uniform<size_t>(10, 15);
int pos_mod = Random::uniform<int>(-3,3);
Map map(width,height);
// create a target for the paths
Point start{.x=map.width() / 2, .y=map.height()/2};
Point start{.x=map.width() / 2 + pos_mod, .y=map.height()/2 + pos_mod};
for(int radius = 2; radius < 5; radius++) {
for(int radius = 2; radius < 10; radius++) {
// use an empty map
Matrix result = map.walls();
for(matrix::circle it{start, radius}; it.next();) {
for(int i = it.x0; i < it.x1; i++) {
result[it.y][i] += 1;
for(int x = it.left; x < it.right; x++) {
// println("top={}, bottom={}, center.y={}, dy={}, left={}, right={}, x={}, y={}", it.top, it.bottom, it.center.y, it.dy, it.left, it.right, x, it.y);
if(matrix::inbounds(result, x, it.y)) {
result[it.y][x] += 1;
}
}
}
// matrix::dump("RESULT AFTER CIRCLE", result, start.x, start.y);
}
}
}

@ -143,7 +143,7 @@ void WorldBuilder::generate() {
Point center = $map.place_entity(1);
for(matrix::circle it{center, 3}; it.next();) {
for(int x = it.x0; x < it.x1; x++) {
for(int x = it.left; x < it.right; x++) {
if($map.inmap(x, it.y) && !$map.iswall(x, it.y)) {
$map.$tiles.set_tile(x, it.y, "WATER_TILE");
}

Loading…
Cancel
Save