/* * Copyright 2018,2022 Jacob Lifshay * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ // originally from https://github.com/programmerjake/rv32/tree/v0.1.0.1-alpha/software #include #include #ifdef EMULATE_TARGET #include #include #include #include #include #include #include static inline void usb_putchar(int ch) noexcept { unsigned char buf = ch; while(write(STDOUT_FILENO, static_cast(&buf), 1) < 0) { int err = errno; switch(err) { #if EAGAIN != EWOULDBLOCK case EWOULDBLOCK: #endif case EAGAIN: { // stdin and stdout might be the same file, so we need to handle // O_NONBLOCK stuff here too pollfd fd = {.fd = STDOUT_FILENO, .events = POLLOUT, .revents = 0}; while(poll(&fd, 1, -1) < 0) { err = errno; if(err != EINTR) exit(1); } break; } case EINTR: break; default: exit(1); } } } static termios original_tios; static void handle_exit() { tcsetattr(0, TCSADRAIN, &original_tios); } static void handle_signal(int sig) { signal(sig, SIG_DFL); handle_exit(); raise(sig); } static void usb_console_init() noexcept { struct termios tios; if(tcgetattr(0, &tios) < 0) { int err = errno; if(err != ENOTTY) exit(1); } else { original_tios = tios; atexit(handle_exit); cfmakeraw(&tios); tios.c_lflag |= ISIG; if(tcsetattr(0, TCSADRAIN, &tios) < 0) exit(1); if(signal(SIGINT, handle_signal) == SIG_IGN) signal(SIGINT, SIG_IGN); if(signal(SIGTERM, handle_signal) == SIG_IGN) signal(SIGTERM, SIG_IGN); } int flags = fcntl(STDIN_FILENO, F_GETFL); if(flags < 0) exit(1); flags |= O_NONBLOCK; if(fcntl(STDIN_FILENO, F_SETFL, flags) < 0) exit(1); } static int usb_peek_buf = -1; static inline void usb_fill_buf() noexcept { if(usb_peek_buf != -1) return; unsigned char buf; int result = read(STDIN_FILENO, static_cast(&buf), 1); if(result < 0) { int err = errno; switch(err) { #if EAGAIN != EWOULDBLOCK case EWOULDBLOCK: #endif case EAGAIN: case EINTR: break; default: exit(1); } } else if(result > 0) usb_peek_buf = buf; } static inline bool usb_havechar() noexcept { usb_fill_buf(); return usb_peek_buf != -1; } static inline int usb_getchar() noexcept { // we don't need to ever bother to block, since the code always checks usb_havechar first usb_fill_buf(); int retval = usb_peek_buf; usb_peek_buf = -1; return retval; } #else #include "console.h" #include "liteuart_console.h" #endif #if USE_CP437 #define USE_CP437 1 #define USE_UTF8 0 #else #define USE_CP437 0 #define USE_UTF8 1 #endif static inline void my_putchar_inner(unsigned char ch) noexcept { usb_putchar(ch); #ifndef EMULATE_TARGET putchar(ch); #endif } static inline void my_puts_inner(const char *s) noexcept { while(*s) my_putchar_inner(*s++); } static inline void my_putchar(int ch) noexcept { ch = static_cast(ch); #if USE_UTF8 switch(ch) { case 0xB2: my_puts_inner("\u2593"); break; case 0xB1: my_puts_inner("\u2592"); break; case 0xB0: my_puts_inner("\u2591"); break; case 0xCE: my_puts_inner("\u256C"); break; default: my_putchar_inner(ch); } #else my_putchar_inner(ch); #endif } static inline void write_hex_digit(int value) { my_putchar("0123456789ABCDEF"[value]); } static inline void write_hex_u8(std::uint8_t value) { write_hex_digit(value >> 4); write_hex_digit(value & 0xF); } static inline void write_hex_u16(std::uint16_t value) { write_hex_u8(value >> 8); write_hex_u8(value & 0xFF); } static inline void write_hex_u32(std::uint32_t value) { write_hex_u16(value >> 16); write_hex_u16(value & 0xFFFF); } static inline void my_puts(const char *str) { while(*str) my_putchar(*str++); } constexpr std::size_t screen_x_size = 800 / 8; constexpr std::size_t screen_y_size = 600 / 8; template struct get_double_length_type; template <> struct get_double_length_type { typedef std::uint16_t type; }; template <> struct get_double_length_type { typedef std::uint32_t type; }; template <> struct get_double_length_type { typedef std::uint64_t type; }; template <> struct get_double_length_type { typedef std::int16_t type; }; template <> struct get_double_length_type { typedef std::int32_t type; }; template <> struct get_double_length_type { typedef std::int64_t type; }; template constexpr T bidirectional_shift_left(T value, int amount) noexcept { int max_shift = std::numeric_limits::digits; if(amount <= -max_shift) return value < 0 ? -1 : 0; if(amount < 0) return value >> -amount; return value << amount; } template constexpr T bidirectional_shift_right(T value, int amount) noexcept { return bidirectional_shift_left(value, -amount); } template class Fixed { public: typedef T underlying_type; typedef typename get_double_length_type::type double_length_type; static constexpr std::size_t total_bits = std::numeric_limits::digits; static constexpr std::size_t fractional_bits = FractionalBits; static constexpr std::size_t integer_bits = total_bits - fractional_bits; static constexpr T fraction_mask = (static_cast(1) << fractional_bits) - 1; static constexpr T integer_mask = ~fraction_mask; static_assert(total_bits >= fractional_bits, ""); private: underlying_type value; public: constexpr Fixed() noexcept : value(0) { } constexpr Fixed(signed char v) noexcept : value(static_cast(v) << fractional_bits) { } constexpr Fixed(short v) noexcept : value(static_cast(v) << fractional_bits) { } constexpr Fixed(int v) noexcept : value(static_cast(v) << fractional_bits) { } constexpr Fixed(long v) noexcept : value(static_cast(v) << fractional_bits) { } constexpr Fixed(long long v) noexcept : value(static_cast(v) << fractional_bits) { } constexpr Fixed(unsigned char v) noexcept : value(static_cast(v) << fractional_bits) { } constexpr Fixed(char v) noexcept : value(static_cast(v) << fractional_bits) { } constexpr Fixed(unsigned short v) noexcept : value(static_cast(v) << fractional_bits) { } constexpr Fixed(unsigned v) noexcept : value(static_cast(v) << fractional_bits) { } constexpr Fixed(unsigned long v) noexcept : value(static_cast(v) << fractional_bits) { } constexpr Fixed(unsigned long long v) noexcept : value(static_cast(v) << fractional_bits) { } constexpr Fixed(float v) noexcept : value(static_cast(static_cast(1ULL << fractional_bits) * v)) { } constexpr Fixed(double v) noexcept : value(static_cast(static_cast(1ULL << fractional_bits) * v)) { } constexpr explicit operator T() const noexcept { if(value < 0) return (value + fraction_mask) >> fractional_bits; return value >> fractional_bits; } constexpr explicit operator double() const noexcept { return value * (1.0 / (1ULL << fractional_bits)); } static constexpr Fixed make(T underlying_value) noexcept { Fixed retval; retval.value = underlying_value; return retval; } constexpr Fixed operator+() const noexcept { return *this; } constexpr Fixed operator-() const noexcept { return make(-value); } friend constexpr Fixed operator+(Fixed a, Fixed b) noexcept { return make(a.value + b.value); } friend constexpr Fixed operator-(Fixed a, Fixed b) noexcept { return make(a.value - b.value); } friend constexpr Fixed operator*(Fixed a, Fixed b) noexcept { return make(static_cast(a.value) * b.value >> fractional_bits); } friend constexpr Fixed operator/(Fixed a, Fixed b) noexcept { if(b.value == 0) { b.value = 1; } return make((static_cast(a.value) << fractional_bits) / b.value); } constexpr Fixed &operator+=(Fixed rt) noexcept { return *this = *this + rt; } constexpr Fixed &operator-=(Fixed rt) noexcept { return *this = *this - rt; } constexpr Fixed &operator*=(Fixed rt) noexcept { return *this = *this * rt; } constexpr Fixed &operator/=(Fixed rt) noexcept { return *this = *this / rt; } constexpr T underlying_value() const noexcept { return value; } friend constexpr bool operator==(Fixed a, Fixed b) noexcept { return a.value == b.value; } friend constexpr bool operator!=(Fixed a, Fixed b) noexcept { return a.value != b.value; } friend constexpr bool operator<=(Fixed a, Fixed b) noexcept { return a.value <= b.value; } friend constexpr bool operator>=(Fixed a, Fixed b) noexcept { return a.value >= b.value; } friend constexpr bool operator<(Fixed a, Fixed b) noexcept { return a.value < b.value; } friend constexpr bool operator>(Fixed a, Fixed b) noexcept { return a.value > b.value; } friend constexpr Fixed floor(Fixed v) noexcept { v.value &= integer_mask; return v; } friend constexpr Fixed fracf(Fixed v) noexcept { v.value &= fraction_mask; return v; } friend constexpr Fixed ceil(Fixed v) noexcept { v.value += fraction_mask; return floor(v); } friend constexpr Fixed round(Fixed v) noexcept { constexpr Fixed one_half = 0.5; v += one_half; return floor(v); } friend constexpr T floori(Fixed v) noexcept { return v.value >> fractional_bits; } friend constexpr T ceili(Fixed v) noexcept { v.value += fraction_mask; return floori(v); } friend constexpr T roundi(Fixed v) noexcept { constexpr Fixed one_half = 0.5; v += one_half; return floori(v); } friend constexpr Fixed abs(Fixed v) noexcept { if(v.value < 0) return -v; return v; } friend constexpr Fixed sqrt(Fixed v) noexcept { if(v <= 0) return 0; Fixed guess = 0; double_length_type guess_squared = 0; for(int bit_index = (integer_bits + 1) / 2; bit_index >= -static_cast(fractional_bits); bit_index--) { Fixed new_guess = guess + make(static_cast(1) << (bit_index + fractional_bits)); double_length_type new_guess_squared = guess_squared; new_guess_squared += bidirectional_shift_left( static_cast(guess.value), bit_index + 1); new_guess_squared += bidirectional_shift_left( static_cast(Fixed(1).value), 2 * bit_index); if(new_guess_squared < v.value) { guess = new_guess; guess_squared = new_guess_squared; } else if(new_guess_squared == v.value) return new_guess; } return guess; } }; enum class Block : char { Empty = ' ', Wall = '|', End = 'X' }; constexpr double constexpr_sin2pi(double x) noexcept { x -= static_cast(x); if(x < 0) x += 1; if(x == 0) return 0; if(x == 0.25) return 1; if(x == 0.5) return 0; if(x == 0.75) return -1; double x2 = x * x; const double coefficients[] = { 1.5873670538243229332222957023504872028033458258785e-8, -3.2649283479971170585768247133750680886632233028762e-7, 5.8056524029499061679627827975252772363553363262495e-6, -8.8235335992430051344844841671401871742374913922057e-5, 1.1309237482517961877702180414488525515732161905954e-3, -1.2031585942120627233202567845286556653885737182738e-2, 1.0422916220813984117271044898760411097029995316417e-1, -7.1812230177850051223174027860686238053986168884284e-1, 3.8199525848482821277337920673404661254406128731422, -1.5094642576822990391826616232531520514481435107371e1, 4.205869394489765314498681114813355254161277992845e1, -7.6705859753061385841630641093893125889966539055122e1, 8.1605249276075054203397682678249495061413521767487e1, -4.1341702240399760233968420089468526936300384754514e1, 6.2831853071795864769252867665590057683943387987502, }; double v = 0; for(double coeff : coefficients) v = v * x2 + coeff; return x * v; } constexpr double constexpr_cos2pi(double x) noexcept { x -= static_cast(x); x += 0.25; return constexpr_sin2pi(x); } template struct SinCosList { static_assert(N > 1, ""); constexpr std::size_t size() const noexcept { return N; } Fixed<> sin_table[N]; constexpr SinCosList() noexcept : sin_table{} { for(std::size_t i = 0; i < N; i++) { double rotations = i / (4.0 * (N - 1)); sin_table[i] = constexpr_sin2pi(rotations); } } constexpr void get(Fixed<> &sin_out, Fixed<> &cos_out, Fixed<> rotations) const noexcept { rotations = fracf(rotations) * 4; int quadrent = floori(rotations); rotations = (N - 1) * fracf(rotations); auto int_part = floori(rotations); auto fraction = fracf(rotations); auto sin_value = sin_table[int_part] + fraction * (sin_table[int_part + 1] - sin_table[int_part]); auto cos_value = sin_table[N - 1 - int_part] + fraction * (sin_table[N - 1 - int_part - 1] - sin_table[N - 1 - int_part]); switch(quadrent) { case 1: sin_out = cos_value; cos_out = -sin_value; break; case 2: sin_out = -sin_value; cos_out = -cos_value; break; case 3: sin_out = -cos_value; cos_out = sin_value; break; default: sin_out = sin_value; cos_out = cos_value; break; } } constexpr Fixed<> get_sin(Fixed<> rotations) const noexcept { Fixed<> sin, cos; get(sin, cos, rotations); return sin; } constexpr Fixed<> get_cos(Fixed<> rotations) const noexcept { Fixed<> sin, cos; get(sin, cos, rotations); return cos; } }; constexpr auto sin_cos_list = SinCosList<>(); constexpr void rotate(Fixed<> &x, Fixed<> &y, Fixed<> rotations) { Fixed<> sin, cos; sin_cos_list.get(sin, cos, rotations); auto new_x = x * cos - y * sin; auto new_y = x * sin + y * cos; x = new_x; y = new_y; } inline void write_fixed(Fixed<> v) { write_hex_u32(floori(v)); my_putchar('.'); write_hex_u16(floori(fracf(v) * 0x10000)); } template struct Vec2D { typedef T element_type; T x, y; constexpr Vec2D() noexcept : x(), y() { } constexpr explicit Vec2D(T v) noexcept : x(v), y(v) { } constexpr Vec2D(T x, T y) noexcept : x(x), y(y) { } friend constexpr Vec2D operator+(Vec2D a, Vec2D b) noexcept { return Vec2D(a.x + b.x, a.y + b.y); } constexpr Vec2D operator-() const noexcept { return Vec2D(-x, -y); } friend constexpr Vec2D operator-(Vec2D a, Vec2D b) noexcept { return Vec2D(a.x - b.x, a.y - b.y); } friend constexpr Vec2D operator*(T a, Vec2D b) noexcept { return Vec2D(a * b.x, a * b.y); } friend constexpr Vec2D operator*(Vec2D a, T b) noexcept { return Vec2D(a.x * b, a.y * b); } friend constexpr Vec2D operator/(Vec2D a, T b) noexcept { return Vec2D(a.x / b, a.y / b); } constexpr Vec2D &operator+=(Vec2D rt) noexcept { return *this = *this + rt; } constexpr Vec2D &operator-=(Vec2D rt) noexcept { return *this = *this - rt; } constexpr Vec2D &operator*=(T rt) noexcept { return *this = *this * rt; } constexpr Vec2D &operator/=(T rt) noexcept { return *this = *this / rt; } }; constexpr Vec2D> rotate(Vec2D> v, Fixed<> rotations) noexcept { rotate(v.x, v.y, rotations); return v; } constexpr void init_ray_cast_dimension(Fixed<> ray_direction, Fixed<> ray_start_position, Fixed<> &next_t, Fixed<> &step_t, std::int32_t &delta_position) { if(ray_direction == 0) return; auto inverse_direction = 1 / ray_direction; step_t = abs(inverse_direction); std::int32_t target_position{}; if(ray_direction < 0) { target_position = ceili(ray_start_position) - 1; delta_position = -1; } else { target_position = floori(ray_start_position) + 1; delta_position = 1; } next_t = (target_position - ray_start_position) * inverse_direction; } struct RayCaster { Vec2D> ray_start_position; Vec2D> ray_direction; Vec2D current_position; Fixed<> current_t; Vec2D> next_t; Vec2D> step_t; Vec2D delta_position; int last_hit_dimension = -1; constexpr RayCaster(Vec2D> ray_start_position, Vec2D> ray_direction) noexcept : ray_start_position(ray_start_position), ray_direction(ray_direction), current_position(floori(ray_start_position.x), floori(ray_start_position.y)), current_t(Fixed<>::make(1)), next_t(0), step_t(0), delta_position(0) { init_ray_cast_dimension( ray_direction.x, ray_start_position.x, next_t.x, step_t.x, delta_position.x); init_ray_cast_dimension( ray_direction.y, ray_start_position.y, next_t.y, step_t.y, delta_position.y); } constexpr void step() noexcept { if(ray_direction.x != 0 && (ray_direction.y == 0 || next_t.x < next_t.y)) { current_t = next_t.x; next_t.x += step_t.x; current_position.x += delta_position.x; last_hit_dimension = 0; } else if(ray_direction.y != 0) { current_t = next_t.y; next_t.y += step_t.y; current_position.y += delta_position.y; last_hit_dimension = 1; } } }; struct KeyboardStatus { std::int32_t right_count = 0; // positive for right, negative for left std::int32_t up_count = 0; // positive for up, negative for down bool reset = false; void operator+=(KeyboardStatus rt) noexcept { right_count += rt.right_count; up_count += rt.up_count; reset |= rt.reset; } }; template class KeyboardReader { private: enum class State { Initial, GotEsc, GotLBracket, }; State state = State::Initial; public: KeyboardStatus poll() noexcept { KeyboardStatus status; for(int i = 0; i < 32; i++) { if(!HAVECHAR()) break; std::uint8_t ch = GETCHAR(); if(ch == 0x3) // Ctrl+C { *this = KeyboardReader(); status = KeyboardStatus(); status.reset = true; break; } switch(state) { case State::Initial: switch(ch) { case 0x1B: // Esc state = State::GotEsc; break; case 'w': case 'W': status.up_count++; break; case 'a': case 'A': status.right_count--; break; case 's': case 'S': status.up_count--; break; case 'd': case 'D': status.right_count++; break; } break; case State::GotEsc: switch(ch) { case 0x1B: break; case '[': state = State::GotLBracket; break; default: state = State::Initial; } break; case State::GotLBracket: state = State::Initial; switch(ch) { case 0x1B: state = State::GotEsc; break; case 'D': status.right_count--; break; case 'C': status.right_count++; break; case 'A': status.up_count++; break; case 'B': status.up_count--; break; } break; } } return status; } }; int main() { usb_console_init(); #ifndef EMULATE_TARGET console_init(); #endif static std::uint8_t start_col[screen_x_size] = {}, end_col[screen_x_size] = {}; static char col_color[screen_x_size] = {}; constexpr std::size_t world_x_size = 16, world_z_size = 16; static const char world[world_x_size][world_z_size] = { // clang-format off {'|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|', 'X', 'X'}, {'|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|', ' ', ' ', ' ', 'X'}, {'|', ' ', '|', '|', '|', '|', '|', '|', '|', ' ', ' ', '|', ' ', ' ', ' ', 'X'}, {'|', ' ', ' ', ' ', ' ', '|', ' ', ' ', '|', ' ', ' ', '|', ' ', '|', 'X', 'X'}, {'|', ' ', ' ', ' ', ' ', '|', ' ', ' ', '|', ' ', ' ', '|', ' ', '|', '|', '|'}, {'|', ' ', '|', ' ', ' ', '|', ' ', ' ', '|', ' ', ' ', '|', ' ', ' ', ' ', '|'}, {'|', ' ', '|', ' ', ' ', '|', ' ', ' ', '|', ' ', ' ', '|', ' ', ' ', ' ', '|'}, {'|', ' ', '|', ' ', ' ', ' ', ' ', ' ', '|', ' ', ' ', '|', '|', '|', ' ', '|'}, {'|', ' ', '|', ' ', ' ', ' ', ' ', ' ', '|', ' ', ' ', '|', ' ', ' ', ' ', '|'}, {'|', ' ', '|', '|', '|', '|', '|', '|', '|', ' ', ' ', '|', ' ', ' ', ' ', '|'}, {'|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|', ' ', ' ', ' ', '|'}, {'|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|', ' ', ' ', ' ', '|'}, {'|', ' ', '|', '|', '|', '|', '|', '|', '|', ' ', ' ', '|', ' ', ' ', ' ', '|'}, {'|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|'}, {'|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|'}, {'|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|', '|'}, // clang-format on }; constexpr Vec2D> initial_view_position(1.5, 1.5); constexpr Fixed<> initial_view_angle(0); constexpr std::uint32_t initial_flash_counter = 0; auto view_position = initial_view_position; auto view_angle = initial_view_angle; auto flash_counter = initial_flash_counter; constexpr std::uint32_t flash_period = 10; KeyboardReader usb_reader; #ifndef EMULATE_TARGET KeyboardReader console_reader; #endif while(true) { flash_counter++; if(flash_counter >= flash_period) flash_counter = 0; auto status = usb_reader.poll(); #ifndef EMULATE_TARGET status += console_reader.poll(); #endif if(status.reset) { view_position = initial_view_position; view_angle = initial_view_angle; flash_counter = initial_flash_counter; } while(status.right_count != 0) { if(status.right_count > 0) { view_angle -= 0.01; view_angle = fracf(view_angle); status.right_count--; } else { view_angle += 0.01; view_angle = fracf(view_angle); status.right_count++; } } while(status.up_count != 0) { Vec2D> forward(0, 0.2); if(status.up_count > 0) { status.up_count--; } else { forward = -forward; status.up_count++; } forward = rotate(forward, view_angle); auto new_view_position = view_position + forward; Vec2D new_block_position(floori(new_view_position.x), floori(new_view_position.y)); #if 1 auto block = world[new_block_position.x][new_block_position.y]; if(block == ' ') view_position = new_view_position; #else Fixed<> closest_distance(100); for(int dx = -1; dx <= 1; dx++) { for(int dy = -1; dy <= 1; dy++) { auto block_position = new_block_position; block_position.x += dx; block_position.y += dy; auto block = world[block_position.x][block_position.y]; if(block == ' ') continue; auto closest_position = new_view_position; if(closest_position.x < block_position.x) closest_position.x = block_position.x; else if(closest_position.x > block_position.x + 1) closest_position.x = block_position.x + 1; if(closest_position.y < block_position.y) closest_position.y = block_position.y; else if(closest_position.y > block_position.y + 1) closest_position.y = block_position.y + 1; auto current_distance_x = abs(closest_position.x - block_position.x); auto current_distance_y = abs(closest_position.y - block_position.y); auto current_distance = current_distance_x; if(current_distance < current_distance_y) current_distance = current_distance_y; if(current_distance < closest_distance) closest_distance = current_distance; } } if(closest_distance >= 0.1) view_position = new_view_position; #endif } for(std::size_t x = 0; x < screen_x_size; x++) { Vec2D> ray_direction( (Fixed<>(x) + (0.5 - screen_x_size / 2.0)) * (2.0 / screen_x_size), 1); ray_direction = rotate(ray_direction, view_angle); RayCaster ray_caster(view_position, ray_direction); auto hit_block = world[ray_caster.current_position.x][ray_caster.current_position.y]; while(hit_block == ' ') { ray_caster.step(); hit_block = world[ray_caster.current_position.x][ray_caster.current_position.y]; } constexpr Fixed<> max_height = 10; Fixed<> height = ray_caster.current_t != Fixed<>::make(1) ? 1 / ray_caster.current_t : max_height; if(height > max_height) height = max_height; height *= screen_x_size / 2.0; auto iheight = roundi(height); if(iheight > static_cast(screen_y_size)) iheight = screen_y_size; else if(iheight < 0) iheight = 0; start_col[x] = screen_y_size / 2 - iheight / 2; end_col[x] = screen_y_size / 2 + (iheight + 1) / 2; bool odd = (ray_caster.current_position.x + ray_caster.current_position.y) % 2; if(hit_block == 'X' && flash_counter >= flash_period / 2) { col_color[x] = '#'; if(ray_caster.last_hit_dimension == 0) col_color[x] = 'X'; } else if(ray_caster.last_hit_dimension == 0) { col_color[x] = odd ? 0xB2 : 0xB1; } else { col_color[x] = odd ? 0xB1 : 0xB0; } } my_puts("\x1B[H"); for(std::size_t y = 0; y < screen_y_size; y++) { for(std::size_t x = 0, x_end = (y == screen_y_size - 1 ? screen_x_size - 1 : screen_x_size); x < x_end; x++) { if(y >= end_col[x]) my_putchar(0xCE); else if(y >= start_col[x]) my_putchar(col_color[x]); else my_putchar(0x20); } my_puts("\r\n"); } } }