//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 tan_table; std::array inv_tan_table; // step tables used to find next intersection, equivalent to slopes times width and height of cell std::array y_step; std::array 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 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 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 [[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 void printTableDefinition(const char* name, const T table, const size_t size) const noexcept { //std::cout << "std::array " << 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"; }