//CONSTANTS are from: https://github.com/ulfben/ArduboyRayCaster/blob/master/Config.h
// tangent tables equivalent to slopes, used to compute initial intersections with ray
std::array<float, ANGLE_360> tan_table;
std::array<float, ANGLE_360> inv_tan_table;
// step tables used to find next intersection, equivalent to slopes times width and height of cell
std::array<float, ANGLE_360> y_step;
std::array<float, ANGLE_360> x_step;
// 1/cos and 1/sin tables used to compute distance of intersection very quickly
// Optimization: cos(X) == sin(X+90), so for cos lookups we can simply re-use the sin-table with an offset of ANGLE_90.
std::array<float, ANGLE_360 + ANGLE_90> inv_sin_table; //+90 degrees to make room for the tail-end of the offset cos values.
float* inv_cos_table = &inv_sin_table[ANGLE_90]; //cos(X) == sin(X+90).
// cos table used to fix view distortion caused by radial projection (eg: cancel out fishbowl effect)
std::array<float, HALF_FOV_ANGLE * 2> cos_table;
constexpr inline bool isFacingLeft(const int view_angle) const noexcept {
return (view_angle >= ANGLE_90 && view_angle < ANGLE_270);
}
constexpr inline bool isFacingRight(const int view_angle) const noexcept {
return (view_angle < ANGLE_90 || view_angle >= ANGLE_270);
}
constexpr inline bool isFacingDown(const int view_angle) const noexcept {
return (view_angle >= ANGLE_0 && view_angle < ANGLE_180);
}
constexpr inline bool isFacingUp(const int view_angle) const noexcept {
return (view_angle >= ANGLE_180 && view_angle < ANGLE_360);
}
void buildLookupTables() noexcept {
constexpr auto TENTH_OF_A_RADIAN = ANGLE_TO_RADIANS * 0.1f;
for (int ang = ANGLE_0; ang < ANGLE_360; ang++) {
const auto rad_angle = TENTH_OF_A_RADIAN + (ang * ANGLE_TO_RADIANS); //adding a small offset to avoid edge cases with 0.
tan_table[ang] = std::tan(rad_angle);
inv_tan_table[ang] = 1.0f / tan_table[ang];
inv_sin_table[ang] = 1.0f / std::sin(rad_angle);
// tangent has the incorrect signs in all quadrants except 1, so manually fix the signs of each quadrant.
if (isFacingDown(ang)) {
y_step[ang] = std::abs(tan_table[ang] * CELL_SIZE);
} else {
assert(isFacingUp(ang) && "isFacingUp() should be the exact inverse of isFacingDown(). Have you changed the coordinate system?");
y_step[ang] = -std::abs(tan_table[ang] * CELL_SIZE);
}
if (isFacingLeft(ang)) {
x_step[ang] = -std::abs(inv_tan_table[ang] * CELL_SIZE);
} else {
assert(isFacingRight(ang) && "isFacingRight() should be the exact inverse of isFacingDown(). Have you changed the coordinate system?");
x_step[ang] = std::abs(inv_tan_table[ang] * CELL_SIZE);
}
assert(std::fabs(y_step[ang]) != 0.0f && "Potential asymtotic ray on the y-axis produced while building lookup tables.");
assert(std::fabs(x_step[ang]) != 0.0f && "Potential asymtotic ray on the x-axis produced while building lookup tables.");
}
//duplicate the first 90 sin values at the end of the array, to complete the joint sin & cos lookup table.
auto end = std::end(inv_sin_table) - ANGLE_90;
std::copy_n(std::begin(inv_sin_table), ANGLE_90, end);
// create view filter table. Without this we would see a fishbowl effect. There is a cosine wave modulated on top of the view distance as a side effect of casting from a fixed point.
// to cancel this effect out, we multiple by the inverse of the cosine and the result is the proper scale.
// inverse cosine would be 1/cos(rad_angle), but 1 is too small to give us good sized slivers, hence the constant K which is arbitrarily chosen for what looks good.
for (int ang = -HALF_FOV_ANGLE; ang < HALF_FOV_ANGLE; ang++) {
const auto rad_angle = TENTH_OF_A_RADIAN + (ang * ANGLE_TO_RADIANS);
const auto index = ang + HALF_FOV_ANGLE;
cos_table[index] = (K / std::cos(rad_angle));
}
}
template <class Container>
[[nodiscard]] std::string join(const Container values, std::string delimiter = ","s){
using T = typename Container::value_type;
return std::accumulate(
std::begin(values),
std::end(values),
std::string(),
[delimiter](std::string a, T b) -> std::string {
std::string delim(a.length() > 0 ? delimiter : ""s);
return a + delim + std::to_string(b);
});
}
template<typename T>
void printTableDefinition(const char* name, const T table, const size_t size) const noexcept {
//std::cout << "std::array<float, " << size << "> " << name << "{\n"; //PC
std::cout << "constexpr float " << name << "[" << size << "] PROGMEM {\n"; //ArduBoy
std::cout << "\t" << join(table, size, "f,");
std::cout << "f};\n";
}
void prettyPrintLUTs() noexcept {
printTableDefinition("tan_table", tan_table, tan_table.size());
printTableDefinition("y_step", y_step, y_step.size());
printTableDefinition("x_step", x_step, x_step.size());
printTableDefinition("inv_sin_table", inv_sin_table, inv_sin_table.size());
printTableDefinition("inv_tan_table", inv_tan_table, inv_tan_table.size());
printTableDefinition("cos_table", cos_table, cos_table.size());
std::cout << "const float* inv_cos_table = &inv_sin_table[" << ANGLE_90 << "];\n";
}
Comments
0 B
|👍
/👎
0 B
|0 👍
/0 👎