Finally have a name for my little matrix shape iterator library. I present to you, the SHITErators. Are they THE shit? Shit? Or just Shite?
parent
ffdea41faa
commit
73b85e8057
@ -0,0 +1,530 @@ |
|||||||
|
#pragma once |
||||||
|
#include <vector> |
||||||
|
#include <queue> |
||||||
|
#include <string> |
||||||
|
#include <array> |
||||||
|
#include <numeric> |
||||||
|
#include <algorithm> |
||||||
|
#include <fmt/core.h> |
||||||
|
#include "point.hpp" |
||||||
|
#include "rand.hpp" |
||||||
|
#include "dbc.hpp" |
||||||
|
|
||||||
|
/*
|
||||||
|
* # What is This Shit? |
||||||
|
* |
||||||
|
* Announcing the Shape Iterators, or `shiterators` for short. You could also say these are Shaw's Iterators, but |
||||||
|
* either way they are the _shit_. Or are they shit? You decide. Maybe they're "shite"? |
||||||
|
* |
||||||
|
* A shiterator is a simple generator that converts 2D shapes into a 1D stream of x/y coordinates. You give it a matrix, some parameters like start, end, etc. and each time you call `next()` you the next viable x/y coordinate to complete the shape. |
||||||
|
* |
||||||
|
* A shiterator tries to ensure a few things: |
||||||
|
* |
||||||
|
* 1. All x/y values will be within the Matrix you give it. |
||||||
|
* 2. They try to not store anything and only calculate the math necessary to linearlize the shape. |
||||||
|
* 3. You can store them and incrementally call next to get the next value. |
||||||
|
* 4. You should be able to compose them together on the same Matrix or different matrices of the same dimensions. |
||||||
|
* 5. Most of them will only require 1 for-loop, the few that require 2 only do this so you can draw the inside of a shape. `circle` is like this. |
||||||
|
* 6. They don't assume any particular classes or require subclassing. As long as the type given enables `mat[y][x]` (row major) access then it'll work. |
||||||
|
* 7. The matrix given to a shiterator isn't actually attached to it, so you can use one matrix to setup an iterator, then apply the x/y values to any other matrix of the same dimensions. |
||||||
|
* 8. More importantly, shiterators _do not return any values from the matrix_. They only do the math for coordinates and leave it to you to work your matrix. |
||||||
|
* |
||||||
|
* These shiterators are used all over the game to do map rendering, randomization, drawing, nearly everything that involves a shape. |
||||||
|
* |
||||||
|
* ## Algorithms I Need |
||||||
|
* |
||||||
|
* I'm currently looking for a few algorithms, so if you know how to do these let me know: |
||||||
|
* |
||||||
|
* 1. _Flood fill_ This turns out to be really hard because most algorithms require keeping track of visited cells with a queue, recursion, etc. |
||||||
|
* 2. _Random rectangle fill_ I have something that mostly works but it's really only random across each y-axis, then separate y-axes are randomized. |
||||||
|
* 3. _Dijkstra Map_ I have a Dijkstra algorithm but it's not in this style yet. Look in `worldbuilder.cpp` for my current implementation. |
||||||
|
* 4. _Viewport_ Currently working on this but I need to have a rectangle I can move around as a viewport. |
||||||
|
* |
||||||
|
* |
||||||
|
* ## Usage |
||||||
|
* |
||||||
|
* Check the `matrix.hpp` for an example if you want to make it more conventient for your own type. |
||||||
|
* |
||||||
|
* ## Thanks |
||||||
|
* |
||||||
|
* Special thanks for Amit and hirdrac for their help with the math and for |
||||||
|
* giving me the initial idea. hirdrac doesn't want to be held responsible for |
||||||
|
* this travesty but he showed me that you can do iteration and _not_ use the |
||||||
|
* weird C++ iterators. Amit did a lot to show me how to do these calculations |
||||||
|
* without branching. Thanks to you both and everyone helping me while I |
||||||
|
* stream my development. |
||||||
|
*/ |
||||||
|
namespace shiterator { using std::vector, std::queue, std::array; using |
||||||
|
std::min, std::max, std::floor; |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
using BaseRow = vector<T>; |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
using Base = vector<BaseRow<T>>; |
||||||
|
|
||||||
|
template<typename T> |
||||||
|
inline Base<T> make(size_t width, size_t height) { |
||||||
|
Base<T> result(height, BaseRow<T>(width)); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Just a quick thing to reset a matrix to a value. |
||||||
|
*/ |
||||||
|
template<typename MAT, typename VAL> |
||||||
|
inline void assign(MAT &out, VAL new_value) { |
||||||
|
for(auto &row : out) { |
||||||
|
row.assign(row.size(), new_value); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tells you if a coordinate is in bounds of the matrix |
||||||
|
* and therefore safe to use. |
||||||
|
*/ |
||||||
|
template<typename MAT> |
||||||
|
inline bool inbounds(MAT &mat, size_t x, size_t y) { |
||||||
|
// since Point.x and Point.y are size_t any negatives are massive
|
||||||
|
return (y < mat.size()) && (x < mat[0].size()); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Gives the width of a matrix. Assumes row major (y/x) |
||||||
|
* and vector API .size(). |
||||||
|
*/ |
||||||
|
template<typename MAT> |
||||||
|
inline size_t width(MAT &mat) { |
||||||
|
return mat[0].size(); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Same as shiterator::width but just the height. |
||||||
|
*/ |
||||||
|
template<typename MAT> |
||||||
|
inline size_t height(MAT &mat) { |
||||||
|
return mat.size(); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* These are internal calculations that help |
||||||
|
* with keeping track of the next x coordinate. |
||||||
|
*/ |
||||||
|
inline size_t next_x(size_t x, size_t width) { |
||||||
|
return (x + 1) * ((x + 1) < width); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Same as next_x but updates the next y coordinate. |
||||||
|
* It uses the fact that when x==0 you have a new |
||||||
|
* line so increment y. |
||||||
|
*/ |
||||||
|
inline size_t next_y(size_t x, size_t y) { |
||||||
|
return y + (x == 0); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Figures out if you're at the end of the shape, |
||||||
|
* which is usually when y > height. |
||||||
|
*/ |
||||||
|
inline bool at_end(size_t y, size_t height) { |
||||||
|
return y < height; |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Determines if you're at the end of a row. |
||||||
|
*/ |
||||||
|
inline bool end_row(size_t x, size_t width) { |
||||||
|
return x == width - 1; |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Most basic shiterator. It just goes through |
||||||
|
* every cell in the matrix in linear order |
||||||
|
* with not tracking of anything else. |
||||||
|
*/ |
||||||
|
template<typename MAT> |
||||||
|
struct each_cell_t { |
||||||
|
size_t x = ~0; |
||||||
|
size_t y = ~0; |
||||||
|
size_t width = 0; |
||||||
|
size_t height = 0; |
||||||
|
|
||||||
|
each_cell_t(MAT &mat) |
||||||
|
{ |
||||||
|
height = shiterator::height(mat); |
||||||
|
width = shiterator::width(mat); |
||||||
|
} |
||||||
|
|
||||||
|
bool next() { |
||||||
|
x = next_x(x, width); |
||||||
|
y = next_y(x, y); |
||||||
|
return at_end(y, height); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
/*
|
||||||
|
* This is just each_cell_t but it sets |
||||||
|
* a boolean value `bool row` so you can |
||||||
|
* tell when you've reached the end of a |
||||||
|
* row. This is mostly used for printing |
||||||
|
* out a matrix and similar just drawing the |
||||||
|
* whole thing with its boundaries. |
||||||
|
*/ |
||||||
|
template<typename MAT> |
||||||
|
struct each_row_t { |
||||||
|
size_t x = ~0; |
||||||
|
size_t y = ~0; |
||||||
|
size_t width = 0; |
||||||
|
size_t height = 0; |
||||||
|
bool row = false; |
||||||
|
|
||||||
|
each_row_t(MAT &mat) { |
||||||
|
height = shiterator::height(mat); |
||||||
|
width = shiterator::width(mat); |
||||||
|
} |
||||||
|
|
||||||
|
bool next() { |
||||||
|
x = next_x(x, width); |
||||||
|
y = next_y(x, y); |
||||||
|
row = end_row(x, width); |
||||||
|
return at_end(y, height); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
/*
|
||||||
|
* This is a CENTERED box, that will create |
||||||
|
* a centered rectangle around a point of a |
||||||
|
* certain dimension. This kind of needs a |
||||||
|
* rewrite but if you want a rectangle from |
||||||
|
* a upper corner then use rectangle_t type. |
||||||
|
* |
||||||
|
* Passing 1 parameter for the size will make |
||||||
|
* a square. |
||||||
|
*/ |
||||||
|
template<typename MAT> |
||||||
|
struct box_t { |
||||||
|
size_t from_x; |
||||||
|
size_t from_y; |
||||||
|
size_t x = 0; // these are set in constructor
|
||||||
|
size_t y = 0; // again, no fancy ~ trick needed
|
||||||
|
size_t left = 0; |
||||||
|
size_t top = 0; |
||||||
|
size_t right = 0; |
||||||
|
size_t bottom = 0; |
||||||
|
|
||||||
|
box_t(MAT &mat, size_t at_x, size_t at_y, size_t size) : |
||||||
|
box_t(mat, at_x, at_y, size, size) { |
||||||
|
} |
||||||
|
|
||||||
|
box_t(MAT &mat, size_t at_x, size_t at_y, size_t width, size_t height) : |
||||||
|
from_x(at_x), from_y(at_y) |
||||||
|
{ |
||||||
|
size_t h = shiterator::height(mat); |
||||||
|
size_t w = shiterator::width(mat); |
||||||
|
|
||||||
|
// keeps it from going below zero
|
||||||
|
// need extra -1 to compensate for the first next()
|
||||||
|
left = max(from_x, width) - width; |
||||||
|
x = left - 1; // must be -1 for next()
|
||||||
|
// keeps it from going above width
|
||||||
|
right = min(from_x + width + 1, w); |
||||||
|
|
||||||
|
// same for these two
|
||||||
|
top = max(from_y, height) - height; |
||||||
|
y = top - (left == 0); |
||||||
|
bottom = min(from_y + height + 1, h); |
||||||
|
} |
||||||
|
|
||||||
|
bool next() { |
||||||
|
// calc next but allow to go to 0 for next
|
||||||
|
x = next_x(x, right); |
||||||
|
// x will go to 0, which signals new line
|
||||||
|
y = next_y(x, y); // this must go here
|
||||||
|
// if x==0 then this moves it to min_x
|
||||||
|
x = max(x, left); |
||||||
|
// and done
|
||||||
|
|
||||||
|
return at_end(y, bottom); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* This was useful for doing quick lighting |
||||||
|
* calculations, and I might need to implement |
||||||
|
* it in other shiterators. It gives the distance |
||||||
|
* to the center from the current x/y. |
||||||
|
*/ |
||||||
|
float distance() { |
||||||
|
int dx = from_x - x; |
||||||
|
int dy = from_y - y; |
||||||
|
|
||||||
|
return sqrt((dx * dx) + (dy * dy)); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Stupid simple compass shape North/South/East/West. |
||||||
|
* This comes up a _ton_ when doing searching, flood |
||||||
|
* algorithms, collision, etc. Probably not the |
||||||
|
* fastest way to do it but good enough. |
||||||
|
*/ |
||||||
|
template<typename MAT> |
||||||
|
struct compass_t { |
||||||
|
size_t x = 0; // these are set in constructor
|
||||||
|
size_t y = 0; // again, no fancy ~ trick needed
|
||||||
|
array<int, 4> x_dirs{0, 1, 0, -1}; |
||||||
|
array<int, 4> y_dirs{-1, 0, 1, 0}; |
||||||
|
size_t max_dirs=0; |
||||||
|
size_t dir = ~0; |
||||||
|
|
||||||
|
compass_t(MAT &mat, size_t x, size_t y) : |
||||||
|
x(x), y(y) |
||||||
|
{ |
||||||
|
array<int, 4> x_in{0, 1, 0, -1}; |
||||||
|
array<int, 4> y_in{-1, 0, 1, 0}; |
||||||
|
|
||||||
|
for(size_t i = 0; i < 4; i++) { |
||||||
|
int nx = x + x_in[i]; |
||||||
|
int ny = y + y_in[i]; |
||||||
|
if(shiterator::inbounds(mat, nx, ny)) { |
||||||
|
x_dirs[max_dirs] = nx; |
||||||
|
y_dirs[max_dirs] = ny; |
||||||
|
max_dirs++; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
bool next() { |
||||||
|
dir++; |
||||||
|
if(dir < max_dirs) { |
||||||
|
x = x_dirs[dir]; |
||||||
|
y = y_dirs[dir]; |
||||||
|
return true; |
||||||
|
} else { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Draws a line from start to end using a algorithm from |
||||||
|
* https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
|
||||||
|
* No idea if the one I picked is best but it's the one |
||||||
|
* that works in the shiterator requirements and produced |
||||||
|
* good results. |
||||||
|
* |
||||||
|
* _WARNING_: This one doesn't check if the start/end are |
||||||
|
* within your Matrix, as it's assumed _you_ did that |
||||||
|
* already. |
||||||
|
*/ |
||||||
|
struct line { |
||||||
|
int x; |
||||||
|
int y; |
||||||
|
int x1; |
||||||
|
int y1; |
||||||
|
int sx; |
||||||
|
int sy; |
||||||
|
int dx; |
||||||
|
int dy; |
||||||
|
int error; |
||||||
|
|
||||||
|
line(Point start, Point end) : |
||||||
|
x(start.x), y(start.y), |
||||||
|
x1(end.x), y1(end.y) |
||||||
|
{ |
||||||
|
dx = std::abs(x1 - x); |
||||||
|
sx = x < x1 ? 1 : -1; |
||||||
|
dy = std::abs(y1 - y) * -1; |
||||||
|
sy = y < y1 ? 1 : -1; |
||||||
|
error = dx + dy; |
||||||
|
} |
||||||
|
|
||||||
|
bool next() { |
||||||
|
if(x != x1 || y != y1) { |
||||||
|
int e2 = 2 * error; |
||||||
|
|
||||||
|
if(e2 >= dy) { |
||||||
|
error = error + dy; |
||||||
|
x = x + sx; |
||||||
|
} |
||||||
|
|
||||||
|
if(e2 <= dx) { |
||||||
|
error = error + dx; |
||||||
|
y = y + sy; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} else { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Draws a simple circle using a fairly naive algorithm |
||||||
|
* but one that actually worked. So, so, so, so many |
||||||
|
* circle drawing algorithms described online don't work |
||||||
|
* or are flat wrong. Even the very best I could find |
||||||
|
* did overdrawing of multiple lines or simply got the |
||||||
|
* math wrong. Keep in mind, _I_ am bad at this trig math |
||||||
|
* so if I'm finding errors in your circle drawing then |
||||||
|
* you got problems. |
||||||
|
* |
||||||
|
* This one is real simple, and works. If you got better |
||||||
|
* then take the challenge but be ready to get it wrong. |
||||||
|
*/ |
||||||
|
template<typename MAT> |
||||||
|
struct circle_t { |
||||||
|
float center_x; |
||||||
|
float center_y; |
||||||
|
float radius = 0.0f; |
||||||
|
int y = 0; |
||||||
|
int dx = 0; |
||||||
|
int dy = 0; |
||||||
|
int left = 0; |
||||||
|
int right = 0; |
||||||
|
int top = 0; |
||||||
|
int bottom = 0; |
||||||
|
int width = 0; |
||||||
|
int height = 0; |
||||||
|
|
||||||
|
circle_t(MAT &mat, Point center, float radius) : |
||||||
|
center_x(center.x), center_y(center.y), radius(radius) |
||||||
|
{ |
||||||
|
width = shiterator::width(mat); |
||||||
|
height = shiterator::height(mat); |
||||||
|
top = max(int(floor(center_y - radius)), 0); |
||||||
|
bottom = min(int(floor(center_y + radius)), height - 1); |
||||||
|
|
||||||
|
y = top; |
||||||
|
} |
||||||
|
|
||||||
|
bool next() { |
||||||
|
y++; |
||||||
|
if(y <= bottom) { |
||||||
|
dy = y - center_y; |
||||||
|
dx = floor(sqrt(radius * radius - dy * dy)); |
||||||
|
left = max(0, int(center_x) - dx); |
||||||
|
right = min(width, int(center_x) + dx + 1); |
||||||
|
return true; |
||||||
|
} else { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Basic rectangle shiterator, and like box and rando_rect_t you can |
||||||
|
* pass only 1 parameter for size to do a square. |
||||||
|
*/ |
||||||
|
template<typename MAT> |
||||||
|
struct rectangle_t { |
||||||
|
int x; |
||||||
|
int y; |
||||||
|
int top; |
||||||
|
int left; |
||||||
|
int width; |
||||||
|
int height; |
||||||
|
int right; |
||||||
|
int bottom; |
||||||
|
|
||||||
|
rectangle_t(MAT &mat, size_t start_x, size_t start_y, size_t size) : |
||||||
|
rectangle_t(mat, start_x, start_y, size, size) { |
||||||
|
} |
||||||
|
|
||||||
|
rectangle_t(MAT &mat, size_t start_x, size_t start_y, size_t width, size_t height) : |
||||||
|
top(start_y), |
||||||
|
left(start_x), |
||||||
|
width(width), |
||||||
|
height(height) |
||||||
|
{ |
||||||
|
size_t h = shiterator::height(mat); |
||||||
|
size_t w = shiterator::width(mat); |
||||||
|
y = start_y - 1; |
||||||
|
x = left - 1; // must be -1 for next()
|
||||||
|
right = min(start_x + width, w); |
||||||
|
|
||||||
|
y = start_y; |
||||||
|
bottom = min(start_y + height, h); |
||||||
|
} |
||||||
|
|
||||||
|
bool next() { |
||||||
|
x = next_x(x, right); |
||||||
|
y = next_y(x, y); |
||||||
|
x = max(x, left); |
||||||
|
return at_end(y, bottom); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
/*
|
||||||
|
* WIP: This one is used to place entities randomly but |
||||||
|
* could be used for effects like random destruction of floors. |
||||||
|
* It simply "wraps" the rectangle_t but randomizes the x/y values |
||||||
|
* using a random starting point. This makes it random across the |
||||||
|
* x-axis but only partially random across the y. |
||||||
|
*/ |
||||||
|
template<typename MAT> |
||||||
|
struct rando_rect_t { |
||||||
|
int x; |
||||||
|
int y; |
||||||
|
int x_offset; |
||||||
|
int y_offset; |
||||||
|
rectangle_t<MAT> it; |
||||||
|
|
||||||
|
rando_rect_t(MAT &mat, size_t start_x, size_t start_y, size_t size) : |
||||||
|
rando_rect_t(mat, start_x, start_y, size, size) { |
||||||
|
} |
||||||
|
|
||||||
|
rando_rect_t(MAT &mat, size_t start_x, size_t start_y, size_t width, size_t height) : |
||||||
|
it{mat, start_x, start_y, width, height} |
||||||
|
{ |
||||||
|
x_offset = Random::uniform(0, int(width)); |
||||||
|
y_offset = Random::uniform(0, int(height)); |
||||||
|
} |
||||||
|
|
||||||
|
bool next() { |
||||||
|
bool done = it.next(); |
||||||
|
x = it.left + ((it.x + x_offset) % it.width); |
||||||
|
y = it.top + ((it.y + y_offset) % it.height); |
||||||
|
return done; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
/*
|
||||||
|
* BROKEN: I'm actually not sure what I'm trying to |
||||||
|
* do here yet. |
||||||
|
*/ |
||||||
|
template<typename MAT> |
||||||
|
struct viewport_t { |
||||||
|
Point start; |
||||||
|
// this is the point in the map
|
||||||
|
size_t x; |
||||||
|
size_t y; |
||||||
|
// this is the point inside the box, start at 0
|
||||||
|
size_t view_x = ~0; |
||||||
|
size_t view_y = ~0; |
||||||
|
// viewport width/height
|
||||||
|
size_t width; |
||||||
|
size_t height; |
||||||
|
|
||||||
|
viewport_t(MAT &mat, Point start, int max_x, int max_y) : |
||||||
|
start(start), |
||||||
|
x(start.x-1), |
||||||
|
y(start.y-1) |
||||||
|
{ |
||||||
|
width = std::min(size_t(max_x), shiterator::width(mat) - start.x); |
||||||
|
height = std::min(size_t(max_y), shiterator::height(mat) - start.y); |
||||||
|
fmt::println("viewport_t max_x, max_y {},{} vs matrix {},{}, x={}, y={}", |
||||||
|
max_x, max_y, shiterator::width(mat), shiterator::height(mat), x, y); |
||||||
|
} |
||||||
|
|
||||||
|
bool next() { |
||||||
|
y = next_y(x, y); |
||||||
|
x = next_x(x, width); |
||||||
|
view_x = next_x(view_x, width); |
||||||
|
view_y = next_y(view_x, view_y); |
||||||
|
return at_end(y, height); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue