diff --git a/scratchpad/amt/main.cpp b/scratchpad/amt/main.cpp new file mode 100644 index 0000000..6ed7227 --- /dev/null +++ b/scratchpad/amt/main.cpp @@ -0,0 +1,93 @@ +#include "amt/raycaster.hpp" +#include +#include +#include +#include +#include "constants.hpp" +#include "stats.hpp" + +Matrix MAP{ + {1,1,1,1,1,1,1,1,1}, + {1,0,2,0,0,0,0,0,1}, + {1,0,4,0,0,5,2,0,1}, + {1,0,0,0,0,0,0,0,1}, + {1,1,0,0,0,0,0,1,1}, + {1,0,0,1,3,4,0,0,1}, + {1,0,0,0,0,0,1,1,1}, + {1,0,0,0,0,0,0,0,1}, + {1,1,1,1,1,1,1,1,1} +}; + +void draw_gui(sf::RenderWindow &window, sf::Text &text, Stats &stats) { + sf::RectangleShape rect({SCREEN_WIDTH - RAY_VIEW_WIDTH, SCREEN_HEIGHT}); + + rect.setPosition({0,0}); + rect.setFillColor({50, 50, 50}); + window.draw(rect); + + text.setString( + fmt::format("FPS\nmean:{:>8.5}\nsdev: {:>8.5}\nmin: {:>8.5}\nmax: {:>8.5}\ncount:{:<10}\n\nVSync? {}\nDebug? {}\n\nHit R to reset.", + stats.mean(), stats.stddev(), stats.min, stats.max, stats.n, VSYNC, DEBUG_BUILD)); + window.draw(text); +} + +int main() { + sf::RenderWindow window(sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Zed's Ray Caster Game Thing"); + + sf::Font font{"./assets/text.otf"}; + sf::Text text{font}; + text.setFillColor({255,255,255}); + text.setPosition({10,10}); + + //ZED this should set with a function + float player_x = MAP.rows() / 2; + float player_y = MAP.cols() / 2; + + Raycaster rayview(window, MAP, RAY_VIEW_WIDTH, RAY_VIEW_HEIGHT); + rayview.set_position(RAY_VIEW_X, RAY_VIEW_Y); + rayview.position_camera(player_x, player_y); + + double moveSpeed = 0.1; + double rotSpeed = 0.1; + + const auto onClose = [&window](const sf::Event::Closed&) + { + window.close(); + }; + + Stats stats; + + // NOTE: Enable this to lock frames at 60 + window.setFramerateLimit(60); + + while(window.isOpen()) { + auto start = std::chrono::high_resolution_clock::now(); + rayview.render(); + auto end = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration(end - start); + stats.sample(1/elapsed.count()); + + draw_gui(window, text, stats); + window.display(); + + if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::W)) { + rayview.run(moveSpeed, 1); + } else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::S)) { + rayview.run(moveSpeed, -1); + } + + if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::D)) { + rayview.rotate(rotSpeed, -1); + } else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::A)) { + rayview.rotate(rotSpeed, 1); + } + + if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::R)) { + stats.reset(); + } + + window.handleEvents(onClose); + } + + return 0; +} diff --git a/scratchpad/amt/matrix.hpp b/scratchpad/amt/matrix.hpp new file mode 100644 index 0000000..7309b94 --- /dev/null +++ b/scratchpad/amt/matrix.hpp @@ -0,0 +1,197 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace amt { + + namespace detail { + [[nodiscard]] constexpr auto cal_index( + std::size_t r, + std::size_t c, + [[maybe_unused]] std::size_t rs, + [[maybe_unused]] std::size_t cs + ) -> std::size_t { + return r * cs + c; + } + } + + template + struct Matrix { + using value_type = T; + using pointer = value_type*; + using const_pointer = value_type const*; + using reference = value_type&; + using const_reference = value_type const&; + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using difference_type = std::ptrdiff_t; + using size_type = std::size_t; + + template + struct View { + using base_type = std::conditional_t; + base_type data; + size_type r; + size_type rows; + size_type cols; + + constexpr reference operator[](size_type c) noexcept requires (!IsConst) { + assert(c < cols && "Out of bound access"); + auto const index = detail::cal_index(r, c, rows, cols); + return data[index]; + } + + constexpr const_reference operator[](size_type c) const noexcept { + assert(c < cols && "Out of bound access"); + auto const index = detail::cal_index(r, c, rows, cols); + return data[index]; + } + }; + + constexpr Matrix() noexcept = default; + Matrix(Matrix const& other) + : Matrix(other.rows(), other.cols()) + { + std::copy(other.begin(), other.end(), begin()); + } + Matrix& operator=(Matrix const& other) { + if (this == &other) return *this; + auto temp = Matrix(other); + swap(temp, *this); + return *this; + } + constexpr Matrix(Matrix && other) noexcept + : m_data(other.m_data) + , m_row(other.m_row) + , m_col(other.m_col) + { + other.m_data = nullptr; + } + constexpr Matrix& operator=(Matrix && other) noexcept { + if (this == &other) return *this; + swap(*this, other); + return *this; + } + ~Matrix() { + if (m_data) delete[] m_data; + } + + + Matrix(size_type row, size_type col) + : m_data(new value_type[row * col]) + , m_row(row) + , m_col(col) + {} + + Matrix(size_type row, size_type col, value_type def) + : Matrix(row, col) + { + std::fill(begin(), end(), def); + } + + Matrix(std::initializer_list> li) + : m_row(li.size()) + { + for (auto const& row: li) { + m_col = std::max(m_col, row.size()); + } + + auto const size = m_row * m_col; + + if (size == 0) return; + + m_data = new value_type[size]; + std::fill_n(m_data, size, 0); + + for (auto r = 0ul; auto const& row: li) { + for (auto c = 0ul; auto const& col: row) { + this->operator()(r, c++) = col; + } + ++r; + } + } + + constexpr bool empty() const noexcept { return size() == 0; } + constexpr size_type size() const noexcept { return rows() * cols(); } + constexpr size_type rows() const noexcept { return m_row; } + constexpr size_type cols() const noexcept { return m_col; } + constexpr auto data() noexcept -> pointer { return m_data; } + constexpr auto data() const noexcept -> const_pointer { return m_data; } + + constexpr iterator begin() noexcept { return m_data; } + constexpr iterator end() noexcept { return m_data + size(); } + constexpr const_iterator begin() const noexcept { return m_data; } + constexpr const_iterator end() const noexcept { return m_data + size(); } + constexpr reverse_iterator rbegin() noexcept { return std::reverse_iterator(end()); } + constexpr reverse_iterator rend() noexcept { return std::reverse_iterator(begin()); } + constexpr const_reverse_iterator rbegin() const noexcept { return std::reverse_iterator(end()); } + constexpr const_reverse_iterator rend() const noexcept { return std::reverse_iterator(begin()); } + + constexpr auto operator()(size_type r, size_type c) noexcept -> reference { + auto const index = detail::cal_index(r, c, rows(), cols()); + assert(index < size() && "Out of bound access"); + return m_data[index]; + } + + constexpr auto operator()(size_type r, size_type c) const noexcept -> const_reference { + auto const index = detail::cal_index(r, c, rows(), cols()); + assert(index < size() && "Out of bound access"); + return m_data[index]; + } + + constexpr auto operator[](size_type r) noexcept -> View { + assert(r < rows() && "Out of bound access"); + return { .data = m_data, .r = r, .rows = m_row, .cols = m_col }; + } + + constexpr auto operator[](size_type r) const noexcept -> View { + assert(r < rows() && "Out of bound access"); + return { .data = m_data, .r = r, .rows = m_row, .cols = m_col }; + } + + friend void swap(Matrix& lhs, Matrix& rhs) noexcept { + using std::swap; + swap(lhs.m_data, rhs.m_data); + swap(lhs.m_row, rhs.m_row); + swap(lhs.m_col, rhs.m_col); + } + + private: + pointer m_data{}; + size_type m_row{}; + size_type m_col{}; + }; + +} // namespace amt + +#if 0 +#include +namespace std { + template + struct formatter> { + constexpr auto parse(format_parse_context& ctx) { + return ctx.begin(); + } + + auto format(amt::Matrix const& m, auto& ctx) const { + std::string s = "[\n"; + for (auto r = std::size_t{}; r < m.rows(); ++r) { + for (auto c = std::size_t{}; c < m.cols(); ++c) { + s += std::format("{}, ", m(r, c)); + } + s += '\n'; + } + s += "]"; + return format_to(ctx.out(), "{}", s); + } + }; + +} // namespace std +#endif diff --git a/scratchpad/amt/pixel.hpp b/scratchpad/amt/pixel.hpp new file mode 100644 index 0000000..13bba72 --- /dev/null +++ b/scratchpad/amt/pixel.hpp @@ -0,0 +1,746 @@ +#ifndef AMT_PIXEL_HPP +#define AMT_PIXEL_HPP + +#include "matrix.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace amt { + + enum class PixelFormat { + rgba, + abgr, + rgb , + bgr , + ga , // gray scale and alpha + ag , // alpha and gray scale + g // gray scale + }; + + inline static constexpr auto get_pixel_format_from_channel(std::size_t c, bool little_endian = false) -> PixelFormat { + switch (c) { + case 1: return PixelFormat::g; + case 2: return little_endian ? PixelFormat::ag : PixelFormat::ga; + case 3: return little_endian ? PixelFormat::bgr : PixelFormat::rgb; + case 4: return little_endian ? PixelFormat::abgr : PixelFormat::abgr; + } + throw std::runtime_error(std::string("get_pixel_format_from_channel: unknown channel ") + std::to_string(c)); + } + + namespace detail { + static constexpr auto compare_float(float l, float r) noexcept -> bool { + return std::abs(l - r) < std::numeric_limits::epsilon(); + } + } // namespace detail + + enum class BlendMode { + normal, + multiply, + screen, + overlay, + darken, + lighten, + colorDodge, + colorBurn, + hardLight, + softLight, + difference, + exclusion + }; + + struct RGBA { + using pixel_t = std::uint8_t; + using pixels_t = std::uint32_t; + + constexpr RGBA() noexcept = default; + constexpr RGBA(RGBA const&) noexcept = default; + constexpr RGBA(RGBA &&) noexcept = default; + constexpr RGBA& operator=(RGBA const&) noexcept = default; + constexpr RGBA& operator=(RGBA &&) noexcept = default; + constexpr ~RGBA() noexcept = default; + + + // NOTE: Accepts RRGGBBAA + explicit constexpr RGBA(pixels_t color) noexcept + : RGBA((color >> (8 * 3)) & 0xff, (color >> (8 * 2)) & 0xff, (color >> (8 * 1)) & 0xff, (color >> (8 * 0)) & 0xff) + {} + + constexpr RGBA(pixel_t r, pixel_t g, pixel_t b, pixel_t a = 0xff) noexcept + : m_data {r, g, b, a} + {} + + constexpr RGBA(pixel_t color, pixel_t a = 0xff) noexcept + : RGBA(color, color, color, a) + {} + + // NOTE: Returns RRGGBBAA + constexpr auto to_hex() const noexcept -> pixels_t { + auto r = static_cast(this->r()); + auto b = static_cast(this->b()); + auto g = static_cast(this->g()); + auto a = static_cast(this->a()); + return (r << (8 * 3)) | (g << (8 * 2)) | (b << (8 * 1)) | (a << (8 * 0)); + } + + constexpr auto r() const noexcept -> pixel_t { return m_data[0]; } + constexpr auto g() const noexcept -> pixel_t { return m_data[1]; } + constexpr auto b() const noexcept -> pixel_t { return m_data[2]; } + constexpr auto a() const noexcept -> pixel_t { return m_data[3]; } + + constexpr auto r() noexcept -> pixel_t& { return m_data[0]; } + constexpr auto g() noexcept -> pixel_t& { return m_data[1]; } + constexpr auto b() noexcept -> pixel_t& { return m_data[2]; } + constexpr auto a() noexcept -> pixel_t& { return m_data[3]; } + + /** + * @returns the value is between 0 and 1 + */ + constexpr auto brightness() const noexcept -> float { + // 0.299*R + 0.587*G + 0.114*B + auto tr = normalize(r()); + auto tg = normalize(g()); + auto tb = normalize(b()); + return (0.299 * tr + 0.587 * tg + 0.114 * tb); + } + + template + requires std::is_arithmetic_v + constexpr auto operator/(T val) const noexcept { + auto d = static_cast(val); + return RGBA( + static_cast(r() / d), + static_cast(g() / d), + static_cast(b() / d), + a() + ); + } + + template + requires std::is_arithmetic_v + constexpr auto operator*(T val) const noexcept { + auto d = static_cast(val); + return RGBA( + static_cast(r() * d), + static_cast(g() * d), + static_cast(b() * d), + a() + ); + } + private: + static constexpr auto normalize(pixel_t p) noexcept -> float { + return float(p) / 255; + } + + static constexpr auto to_pixel(float p) noexcept -> pixel_t { + return static_cast(p * 255); + } + + template + static constexpr auto blend_helper() noexcept { + constexpr auto mix_helper = [](float s, float b, float a) -> float { + // (1 - αb) x Cs + αb x B(Cb, Cs) + return (1 - a) * s + a * b; + }; + + if constexpr (M == BlendMode::normal) { + return [mix_helper](float bg, float fg, float alpha) { return mix_helper(bg, fg, alpha); }; + } else if constexpr (M == BlendMode::multiply) { + return [mix_helper](float bg, float fg, float alpha) { return mix_helper(bg, bg * fg, alpha); }; + } else if constexpr (M == BlendMode::screen) { + return [mix_helper](float bg, float fg, float alpha) { + // Cb + Cs -(Cb x Cs) + auto bf = bg + fg - (bg * fg); + return mix_helper(bg, bf, alpha); + }; + } else if constexpr (M == BlendMode::overlay) { + return [mix_helper](float bg, float fg, float alpha, auto&& hard_light_fn, auto&& multiply_fn, auto&& screen_fn) { + // HardLight(Cs, Cb) + auto hl = hard_light_fn(bg, fg, alpha, multiply_fn, screen_fn); + return mix_helper(bg, hl, alpha); + }; + } else if constexpr (M == BlendMode::darken) { + return [mix_helper](float bg, float fg, float alpha) { + return mix_helper(bg, std::min(bg, fg), alpha); + }; + } else if constexpr (M == BlendMode::lighten) { + return [mix_helper](float bg, float fg, float alpha) { + return mix_helper(bg, std::max(bg, fg), alpha); + }; + } else if constexpr (M == BlendMode::colorDodge) { + return [mix_helper](float bg, float fg, float alpha) { + constexpr auto fn = [](float b, float s) -> float { + if (b == 0) return 0; + if (s == 255) return 255; + return std::min(1.f, b / (1.f - s)); + }; + auto bf = fn(bg, fg); + return mix_helper(bg, bf, alpha); + }; + } else if constexpr (M == BlendMode::colorBurn) { + return [mix_helper](float bg, float fg, float alpha) { + constexpr auto fn = [](float b, float s) -> float { + if (b == 255) return 255; + if (s == 0) return 0; + return 1.f - std::min(1.f, (1.f - b) / s); + }; + auto bf = fn(bg, fg); + return mix_helper(bg, bf, alpha); + }; + } else if constexpr (M == BlendMode::hardLight) { + return [mix_helper](float bg, float fg, float alpha, auto&& multiply_fn, auto&& screen_fn) { + auto fn = [&multiply_fn, &screen_fn](float b, float s, float a) -> float { + if (s <= 0.5f) { + return multiply_fn(b, 2.f * s, a); + } else { + return screen_fn(b, 2.f * s - 1.f, a); + } + }; + + auto bf = fn(bg, fg, alpha); + return mix_helper(bg, bf, alpha); + }; + } else if constexpr (M == BlendMode::softLight) { + return [mix_helper](float bg, float fg, float alpha) { + constexpr auto fn = [](float b, float s) -> float { + if (s <= 0.5f) { + // B(Cb, Cs) = Cb - (1 - 2 x Cs) x Cb x (1 - Cb) + return b - (1.f - 2.f * s) * b * (1 - b); + } else { + float d{}; + + if (b <= 0.5f) { + // D(Cb) = ((16 * Cb - 12) x Cb + 4) x Cb + d = ((16 * b - 12) * b + 4) * b; + } else { + // D(Cb) = sqrt(Cb) + d = std::sqrt(b); + } + + // B(Cb, Cs) = Cb + (2 x Cs - 1) x (D(Cb) - Cb) + return b + (2 * s - 1) * (d - b); + } + }; + + auto bf = fn(bg, fg); + return mix_helper(bg, bf, alpha); + }; + } else if constexpr (M == BlendMode::difference) { + return [mix_helper](float bg, float fg, float alpha) { + // B(Cb, Cs) = | Cb - Cs | + return mix_helper(bg, (bg > fg ? (bg - fg) : (fg - bg)), alpha); + }; + } else if constexpr (M == BlendMode::exclusion) { + return [mix_helper](float bg, float fg, float alpha) { + constexpr auto fn = [](float b, float s) -> float { + // B(Cb, Cs) = Cb + Cs - 2 x Cb x Cs + return b + s - 2 * b * s; + }; + + auto bf = fn(bg, fg); + return mix_helper(bg, bf, alpha); + }; + } + }; + + public: + template + constexpr auto blend(RGBA color) const noexcept -> RGBA { + auto ab = normalize(a()); + auto as = normalize(color.a()); + // αs x 1 + αb x (1 – αs) + auto alpha = to_pixel(as + ab * (1 - as)); + auto lr = normalize(r()); + auto lg = normalize(g()); + auto lb = normalize(b()); + auto rr = normalize(color.r()); + auto rg = normalize(color.g()); + auto rb = normalize(color.b()); + + auto nr = 0.f; + auto ng = 0.f; + auto nb = 0.f; + + if constexpr (M == BlendMode::normal) { + auto fn = blend_helper(); + nr = fn(lr, rr, ab); + ng = fn(lg, rg, ab); + nb = fn(lb, rb, ab); + } else if constexpr (M == BlendMode::multiply) { + auto fn = blend_helper(); + nr = fn(lr, rr, ab); + ng = fn(lg, rg, ab); + nb = fn(lb, rb, ab); + } else if constexpr (M == BlendMode::screen) { + auto fn = blend_helper(); + nr = fn(lr, rr, ab); + ng = fn(lg, rg, ab); + nb = fn(lb, rb, ab); + } else if constexpr (M == BlendMode::overlay) { + auto fn = blend_helper(); + auto hard_light_fn = blend_helper(); + auto multiply_fn = blend_helper(); + auto screen_fn = blend_helper(); + nr = fn(lr, rr, ab, hard_light_fn, multiply_fn, screen_fn); + ng = fn(lg, rg, ab, hard_light_fn, multiply_fn, screen_fn); + nb = fn(lb, rb, ab, hard_light_fn, multiply_fn, screen_fn); + } else if constexpr (M == BlendMode::darken) { + auto fn = blend_helper(); + nr = fn(lr, rr, ab); + ng = fn(lg, rg, ab); + nb = fn(lb, rb, ab); + } else if constexpr (M == BlendMode::lighten) { + auto fn = blend_helper(); + nr = fn(lr, rr, ab); + ng = fn(lg, rg, ab); + nb = fn(lb, rb, ab); + } else if constexpr (M == BlendMode::colorDodge) { + auto fn = blend_helper(); + nr = fn(lr, rr, ab); + ng = fn(lg, rg, ab); + nb = fn(lb, rb, ab); + } else if constexpr (M == BlendMode::colorBurn) { + auto fn = blend_helper(); + nr = fn(lr, rr, ab); + ng = fn(lg, rg, ab); + nb = fn(lb, rb, ab); + } else if constexpr (M == BlendMode::hardLight) { + auto fn = blend_helper(); + auto multiply_fn = blend_helper(); + auto screen_fn = blend_helper(); + nr = fn(lr, rr, ab, multiply_fn, screen_fn); + ng = fn(lg, rg, ab, multiply_fn, screen_fn); + nb = fn(lb, rb, ab, multiply_fn, screen_fn); + } else if constexpr (M == BlendMode::softLight) { + auto fn = blend_helper(); + nr = fn(lr, rr, ab); + ng = fn(lg, rg, ab); + nb = fn(lb, rb, ab); + } else if constexpr (M == BlendMode::difference) { + auto fn = blend_helper(); + nr = fn(lr, rr, ab); + ng = fn(lg, rg, ab); + nb = fn(lb, rb, ab); + } else if constexpr (M == BlendMode::exclusion) { + auto fn = blend_helper(); + nr = fn(lr, rr, ab); + ng = fn(lg, rg, ab); + nb = fn(lb, rb, ab); + } + + return RGBA( + to_pixel(nr), + to_pixel(ng), + to_pixel(nb), + alpha + ); + } + + constexpr auto blend(RGBA color, BlendMode mode) const noexcept -> RGBA { + switch (mode) { + case BlendMode::normal: return blend(color); + case BlendMode::multiply: return blend(color); + case BlendMode::screen: return blend(color); + case BlendMode::overlay: return blend(color); + case BlendMode::darken: return blend(color); + case BlendMode::lighten: return blend(color); + case BlendMode::colorDodge: return blend(color); + case BlendMode::colorBurn: return blend(color); + case BlendMode::hardLight: return blend(color); + case BlendMode::softLight: return blend(color); + case BlendMode::difference: return blend(color); + case BlendMode::exclusion: return blend(color); + } + } + private: + pixel_t m_data[4]{}; + }; + + struct HSLA { + using pixel_t = float; + using pixels_t = float[4]; + + // ensures pixel to be in range + template + struct PixelWrapper { + pixel_t& p; + + constexpr PixelWrapper& operator=(float val) noexcept { + p = std::clamp(val, float(min), float(max)); + } + + constexpr operator pixel_t() const noexcept { return p; } + }; + + constexpr HSLA() noexcept = default; + constexpr HSLA(HSLA const&) noexcept = default; + constexpr HSLA(HSLA &&) noexcept = default; + constexpr HSLA& operator=(HSLA const&) noexcept = default; + constexpr HSLA& operator=(HSLA &&) noexcept = default; + constexpr ~HSLA() noexcept = default; + + constexpr HSLA(pixel_t h, pixel_t s, pixel_t l, pixel_t a = 100) noexcept + : m_data({ .hsla = { .h = h, .s = s, .l = l, .a = a } }) + {} + + constexpr HSLA(RGBA color) noexcept { + auto min = std::min({color.r(), color.g(), color.b()}); + auto max = std::max({color.r(), color.g(), color.b()}); + auto c = (max - min) / 255.f; + + auto tr = float(color.r()) / 255; + auto tg = float(color.g()) / 255; + auto tb = float(color.b()) / 255; + auto ta = float(color.a()) / 255; + + float hue = 0; + float s = 0; + auto l = ((max + min) / 2.f) / 255.f; + + if (min == max) { + if (max == color.r()) { + auto seg = (tg - tb) / c; + auto shift = (seg < 0 ? 360.f : 0.f) / 60.f; + hue = seg + shift; + } else if (max == color.g()) { + auto seg = (tb - tr) / c; + auto shift = 120.f / 60.f; + hue = seg + shift; + } else { + auto seg = (tr - tg) / c; + auto shift = 240.f / 60.f; + hue = seg + shift; + } + s = c / (1 - std::abs(2 * l - 1)); + } + + hue = hue * 60.f + 360.f; + auto q = static_cast(static_cast(hue / 360.f)); + hue -= q * 360.f; + + m_data.hsla.h = hue; + m_data.hsla.s = s * 100.f; + m_data.hsla.l = l * 100.f; + m_data.hsla.a = ta * 100.f; + } + + constexpr operator RGBA() const noexcept { + auto ts = s() / 100.f; + auto tl = l() / 100.f; + auto ta = a() / 100.f; + if (s() == 0) return RGBA(to_pixel(tl), to_pixel(ta)); + + auto th = h() / 360.f; + + float const q = tl < 0.5 ? tl * (1 + ts) : tl + ts - tl * ts; + float const p = 2 * tl - q; + + return RGBA( + to_pixel(convert_hue(p, q, th + 1.f / 3)), + to_pixel(convert_hue(p, q, th)), + to_pixel(convert_hue(p, q, th - 1.f / 3)), + to_pixel(ta) + ); + } + + constexpr auto blend(HSLA color, BlendMode mode) const noexcept -> HSLA { + auto lhs = RGBA(*this); + auto rhs = RGBA(color); + return HSLA(lhs.blend(rhs, mode)); + } + + constexpr auto h() const noexcept -> pixel_t { return m_data.hsla.h; } + constexpr auto s() const noexcept -> pixel_t { return m_data.hsla.s; } + constexpr auto l() const noexcept -> pixel_t { return m_data.hsla.l; } + constexpr auto a() const noexcept -> pixel_t { return m_data.hsla.a; } + + constexpr auto h() noexcept -> PixelWrapper<0, 360> { return { m_data.hsla.h }; } + constexpr auto s() noexcept -> PixelWrapper<0, 100> { return { m_data.hsla.s }; } + constexpr auto l() noexcept -> PixelWrapper<0, 100> { return { m_data.hsla.l }; } + constexpr auto a() noexcept -> PixelWrapper<0, 100> { return { m_data.hsla.a }; } + private: + + static constexpr auto to_pixel(float a) noexcept -> RGBA::pixel_t { + return static_cast(a * 255); + } + + static constexpr auto convert_hue(float p, float q, float t) noexcept -> float { + t = t - (t > 1) + (t < 0); + if (t * 6 < 1) return p + (q - p) * 6 * t; + if (t * 2 < 1) return q; + if (t * 3 < 2) return p + (q - p) * (2.f / 3 - t) * 6; + return p; + } + private: + union { + struct { + pixel_t h{}; // hue: 0-360 + pixel_t s{}; // saturation: 0-100% + pixel_t l{}; // lightness: 0-100% + pixel_t a{}; // alpha: 0-100% + } hsla; + pixels_t color; + } m_data{}; + }; + + namespace detail { + template + inline static constexpr auto parse_pixel_helper(RGBA color, std::uint8_t* out_ptr) noexcept { + if constexpr (F == PixelFormat::rgba) { + out_ptr[0] = color.r(); + out_ptr[1] = color.g(); + out_ptr[2] = color.b(); + out_ptr[3] = color.a(); + } else if constexpr (F == PixelFormat::abgr) { + out_ptr[0] = color.a(); + out_ptr[1] = color.b(); + out_ptr[2] = color.g(); + out_ptr[3] = color.r(); + } else if constexpr (F == PixelFormat::rgb) { + out_ptr[0] = color.r(); + out_ptr[1] = color.g(); + out_ptr[2] = color.b(); + } else if constexpr (F == PixelFormat::bgr) { + out_ptr[0] = color.b(); + out_ptr[1] = color.g(); + out_ptr[2] = color.r(); + } else if constexpr (F == PixelFormat::ga) { + out_ptr[0] = color.r(); + out_ptr[1] = color.a(); + } else if constexpr (F == PixelFormat::ag) { + out_ptr[0] = color.a(); + out_ptr[1] = color.r(); + } else { + out_ptr[0] = color.r(); + } + } + + template + inline static constexpr auto parse_pixel_helper(std::uint8_t const* in_ptr) noexcept -> RGBA { + if constexpr (F == PixelFormat::rgba) { + return { + in_ptr[0], + in_ptr[1], + in_ptr[2], + in_ptr[3] + }; + } else if constexpr (F == PixelFormat::abgr) { + return { + in_ptr[3], + in_ptr[2], + in_ptr[1], + in_ptr[0] + }; + } else if constexpr (F == PixelFormat::rgb) { + return { + in_ptr[0], + in_ptr[1], + in_ptr[2], + 0xff + }; + } else if constexpr (F == PixelFormat::bgr) { + return { + in_ptr[2], + in_ptr[1], + in_ptr[0], + 0xff + }; + } else if constexpr (F == PixelFormat::ga) { + return { + in_ptr[0], + in_ptr[1], + }; + } else if constexpr (F == PixelFormat::ag) { + return { + in_ptr[1], + in_ptr[0], + }; + } else { + return { in_ptr[0], 0xff }; + } + } + } // namespace detail + + inline static constexpr auto get_pixel_format_channel(PixelFormat format) noexcept -> std::size_t { + switch (format) { + case PixelFormat::rgba: case PixelFormat::abgr: return 4u; + case PixelFormat::rgb: case PixelFormat::bgr: return 3u; + case PixelFormat::ga: case PixelFormat::ag: return 2u; + case PixelFormat::g: return 1u; + } + assert(false && "unreachable"); + } + + struct PixelBuf { + private: + public: + using value_type = RGBA; + using base_type = Matrix; + using pointer = typename base_type::pointer; + using const_pointer = typename base_type::const_pointer; + using reference = typename base_type::reference; + using const_reference = typename base_type::const_reference; + using iterator = typename base_type::iterator; + using const_iterator = typename base_type::const_iterator; + using reverse_iterator = typename base_type::reverse_iterator; + using const_reverse_iterator = typename base_type::const_reverse_iterator; + using difference_type = typename base_type::difference_type; + using size_type = typename base_type::size_type; + + PixelBuf(size_type r, size_type c) + : m_data(r, c) + {} + PixelBuf(size_type r, size_type c, RGBA color) + : m_data(r, c, color) + {} + + PixelBuf(std::uint8_t const* in, size_type r, size_type c, PixelFormat format = PixelFormat::rgba) + : PixelBuf(r, c) + { + assert(in != nullptr); + + switch (format) { + case PixelFormat::rgba: from_helper(in, data(), size()); break; + case PixelFormat::abgr: from_helper(in, data(), size()); break; + case PixelFormat::rgb: from_helper(in, data(), size()); break; + case PixelFormat::bgr: from_helper(in, data(), size()); break; + case PixelFormat::ga: from_helper(in, data(), size()); break; + case PixelFormat::ag: from_helper(in, data(), size()); break; + case PixelFormat::g: from_helper(in, data(), size()); break; + } + } + + PixelBuf() noexcept = default; + PixelBuf(PixelBuf const&) = default; + PixelBuf(PixelBuf &&) noexcept = default; + PixelBuf& operator=(PixelBuf const&) = default; + PixelBuf& operator=(PixelBuf &&) noexcept = default; + ~PixelBuf() = default; + + constexpr auto size() const noexcept -> size_type { return m_data.size(); } + constexpr auto rows() const noexcept -> size_type { return m_data.rows(); } + constexpr auto cols() const noexcept -> size_type { return m_data.cols(); } + constexpr auto data() noexcept -> pointer { return m_data.data(); } + constexpr auto data() const noexcept -> const_pointer { return m_data.data(); } + auto to_raw_buf() noexcept -> std::uint8_t* { return reinterpret_cast(data()); } + auto to_raw_buf() const noexcept -> std::uint8_t const* { return reinterpret_cast(data()); } + constexpr auto raw_buf_size() const noexcept { return size() * sizeof(RGBA); } + + constexpr auto begin() noexcept -> iterator { return m_data.begin(); } + constexpr auto end() noexcept -> iterator { return m_data.end(); } + constexpr auto begin() const noexcept -> const_iterator { return m_data.begin(); } + constexpr auto end() const noexcept -> const_iterator { return m_data.end(); } + constexpr auto rbegin() noexcept -> reverse_iterator { return m_data.rbegin(); } + constexpr auto rend() noexcept -> reverse_iterator { return m_data.rend(); } + constexpr auto rbegin() const noexcept -> const_reverse_iterator { return m_data.rbegin(); } + constexpr auto rend() const noexcept -> const_reverse_iterator { return m_data.rend(); } + + constexpr decltype(auto) operator[](size_type r) noexcept { return m_data[r]; } + constexpr decltype(auto) operator[](size_type r) const noexcept { return m_data[r]; } + constexpr auto operator()(size_type r, size_type c) noexcept -> reference { return m_data(r, c); } + constexpr auto operator()(size_type r, size_type c) const noexcept -> const_reference { return m_data(r, c); } + + constexpr auto fill(RGBA color) noexcept -> void { + std::fill(begin(), end(), color); + } + + constexpr auto copy_to(std::uint8_t* out, PixelFormat format = PixelFormat::rgba) const noexcept { + assert(out != nullptr); + + switch (format) { + case PixelFormat::rgba: copy_to_helper(data(), out, size());return; + case PixelFormat::abgr: copy_to_helper(data(), out, size());return; + case PixelFormat::rgb: copy_to_helper(data(), out, size());return; + case PixelFormat::bgr: copy_to_helper(data(), out, size());return; + case PixelFormat::ga: copy_to_helper(data(), out, size());return; + case PixelFormat::ag: copy_to_helper(data(), out, size());return; + case PixelFormat::g: copy_to_helper(data(), out, size());return; + } + assert(false && "unreachable"); + } + + + private: + template + constexpr auto copy_to_helper(const_pointer in, std::uint8_t* out, size_type size) const noexcept -> void { + constexpr auto channels = get_pixel_format_channel(F); + for (auto i = size_type{}; i < size; ++i) { + detail::parse_pixel_helper(in[i], out + i * channels); + } + } + + template + constexpr auto from_helper(std::uint8_t const* in, pointer out, size_type size) const noexcept -> void { + constexpr auto channels = get_pixel_format_channel(F); + for (auto i = size_type{}; i < size; ++i) { + out[i] = detail::parse_pixel_helper(in + i * channels); + } + } + + private: + base_type m_data; + }; + + + +} // namespace amt + +#include +namespace std { + template <> + struct formatter { + constexpr auto parse(format_parse_context& ctx) { + return ctx.begin(); + } + + auto format(amt::RGBA const& color, auto& ctx) const { + return format_to(ctx.out(), "rgba({}, {}, {}, {})", color.r(), color.g(), color.b(), color.a()); + } + }; + + template <> + struct formatter { + constexpr auto parse(format_parse_context& ctx) { + return ctx.begin(); + } + + auto format(amt::HSLA const& color, auto& ctx) const { + return format_to(ctx.out(), "hsla({:.1f}deg, {:.1f}%, {:.1f}%, {:.1f}%)", color.h(), color.s(), color.l(), color.a()); + } + }; + + template <> + struct formatter { + bool hsla = false; + + constexpr auto parse(format_parse_context& ctx) { + auto it = ctx.begin(); + while (it != ctx.end() && *it != '}') { + if (*it == 'h') hsla = true; + ++it; + } + return it; + } + + auto format(amt::PixelBuf const& buf, auto& ctx) const { + std::string s = "[\n"; + for (auto r = std::size_t{}; r < buf.rows(); ++r) { + for (auto c = std::size_t{}; c < buf.cols(); ++c) { + auto color = buf(r, c); + if (hsla) s += std::format("{}, ", amt::HSLA(color)); + else s += std::format("{}, ", color); + } + s += '\n'; + } + s += "]"; + return format_to(ctx.out(), "{}", s); + } + }; +} // namespace std + +#endif // AMT_PIXEL_HPP diff --git a/scratchpad/amt/raycaster.cpp b/scratchpad/amt/raycaster.cpp new file mode 100644 index 0000000..c440355 --- /dev/null +++ b/scratchpad/amt/raycaster.cpp @@ -0,0 +1,417 @@ +#include "amt/raycaster.hpp" +#include "amt/texture.hpp" +#include "amt/pixel.hpp" +#include "constants.hpp" +#include "thread.hpp" + +#define AMT_LIGHT + +using namespace fmt; + + +#ifdef AMT_LIGHT +static constexpr auto room_brightness = 0.3f; // increse this to increase the room brightness. Higher value means brighter room. + +inline static constexpr amt::RGBA dumb_lighting(amt::RGBA pixel, float distance, float distance_from_center) { + auto const dim_pixel = pixel * room_brightness; + if (distance_from_center >= 0) { + auto const min_brightness = 1.f / std::max(distance_from_center, 0.5f); // farther away from the center darker it gets + auto const max_brightness = 1.f; // brighness should not exceed 1 + auto const pixel_brightness = std::max(min_brightness, std::min(max_brightness, distance)); + + auto const yellow_brightness = float(distance_from_center * 60); + amt::RGBA const yellow = amt::HSLA(40, 20, yellow_brightness); + + auto temp = (pixel / pixel_brightness).blend(yellow); + return temp.brightness() < 0.1f ? dim_pixel : temp; + } else { + return dim_pixel; + } +} +#else +inline static constexpr amt::RGBA dumb_lighting(amt::RGBA pixel, double distance, double distance_from_center) { + (void)distance_from_center; + if(distance < 0.9) return pixel; + return pixel / distance; +} +#endif + + +Raycaster::Raycaster(sf::RenderWindow& window, Matrix &map, unsigned width, unsigned height) : + view_texture(sf::Vector2u{width, height}), + view_sprite(view_texture), + $width(static_cast(width)), + $height(static_cast(height)), + pixels(height, width), + $window(window), + $map(map), + spriteOrder(textures.NUM_SPRITES), + spriteDistance(textures.NUM_SPRITES), + ZBuffer(width), + $radius(std::min($height, $width) / 2), + $r_sq($radius * $radius) +{ + $window.setVerticalSyncEnabled(VSYNC); + view_sprite.setPosition({0, 0}); + textures.load_textures(); +} + +void Raycaster::set_position(int x, int y) { + view_sprite.setPosition({(float)x, (float)y}); +} + +void Raycaster::position_camera(float player_x, float player_y) { + // x and y start position + posX = player_x; + posY = player_y; +} + +void Raycaster::draw_pixel_buffer() { + view_texture.update(pixels.to_raw_buf(), {(unsigned int)$width, (unsigned int)$height}, {0, 0}); + $window.draw(view_sprite); +} + +void Raycaster::clear() { + pixels.fill({}); + $window.clear(); +} + +void Raycaster::sprite_casting() { + // sort sprites from far to close + for(int i = 0; i < textures.NUM_SPRITES; i++) { + auto& sprite = textures.get_sprite(i); + spriteOrder[i] = i; + // this is just the distance calculation + spriteDistance[i] = ((posX - sprite.x) * + (posX - sprite.x) + + (posY - sprite.y) * + (posY - sprite.y)); + } + + sort_sprites(spriteOrder, spriteDistance, textures.NUM_SPRITES); + + /*for(int i = 0; i < textures.NUM_SPRITES; i++) {*/ + // after sorting the sprites, do the projection + // Be careful about capturing stack variables. + amt::parallel_for<1>(pool, 0, textures.NUM_SPRITES, [this, textureWidth = textures.TEXTURE_WIDTH, textureHeight = textures.TEXTURE_HEIGHT](size_t i){ + int sprite_index = spriteOrder[i]; + Sprite& sprite_rec = textures.get_sprite(sprite_index); + auto& sprite_texture = textures.get_texture(sprite_rec.texture); + + double spriteX = sprite_rec.x - posX; + double spriteY = sprite_rec.y - posY; + + //transform sprite with the inverse camera matrix + // [ planeX dirX ] -1 [ dirY -dirX ] + // [ ] = 1/(planeX*dirY-dirX*planeY) * [ ] + // [ planeY dirY ] [ -planeY planeX ] + + double invDet = 1.0 / (planeX * dirY - dirX * planeY); // required for correct matrix multiplication + + double transformX = invDet * (dirY * spriteX - dirX * spriteY); + //this is actually the depth inside the screen, that what Z is in 3D, the distance of sprite to player, matching sqrt(spriteDistance[i]) + + double transformY = invDet * (-planeY * spriteX + planeX * spriteY); + + int spriteScreenX = int(($width / 2) * (1 + transformX / transformY)); + + int vMoveScreen = int(sprite_rec.elevation * -1 / transformY); + + // calculate the height of the sprite on screen + //using "transformY" instead of the real distance prevents fisheye + int spriteHeight = abs(int($height / transformY)) / sprite_rec.vDiv; + + //calculate lowest and highest pixel to fill in current stripe + int drawStartY = -spriteHeight / 2 + $height / 2 + vMoveScreen; + if(drawStartY < 0) drawStartY = 0; + int drawEndY = spriteHeight / 2 + $height / 2 + vMoveScreen; + if(drawEndY >= $height) drawEndY = $height - 1; + + // calculate width the the sprite + // same as height of sprite, given that it's square + int spriteWidth = abs(int($height / transformY)) / sprite_rec.uDiv; + int drawStartX = -spriteWidth / 2 + spriteScreenX; + if(drawStartX < 0) drawStartX = 0; + int drawEndX = spriteWidth / 2 + spriteScreenX; + if(drawEndX > $width) drawEndX = $width; + + //loop through every vertical stripe of the sprite on screen + for(int stripe = drawStartX; stripe < drawEndX; stripe++) { + int texX = int(256 * (stripe - (-spriteWidth / 2 + spriteScreenX)) * textureWidth / spriteWidth) / 256; + // the conditions in the if are: + // 1) it's in front of the camera plane so you don't see things behind you + // 2) ZBuffer, with perpendicular distance + if (texX < 0) continue; + if(transformY > 0 && transformY < ZBuffer[stripe]) { + for(int y = drawStartY; y < drawEndY; y++) { + //256 and 128 factors to avoid floats + int d = (y - vMoveScreen) * 256 - $height * 128 + spriteHeight * 128; + int texY = ((d * textureHeight) / spriteHeight) / 256; + if ((size_t)texY >= sprite_texture.rows()) continue; + //get current color from the texture + auto color = sprite_texture[texY][texX]; + // poor person's transparency, get current color from the texture + if (!(color.to_hex() & 0xffffff00)) continue; + auto dist = get_distance_from_center(stripe, y); + pixels[y][stripe] = dumb_lighting(color, d, dist); + } + } + } + }); +} + +float Raycaster::get_distance_from_center(int x, int y) const noexcept { + float cx = $width / 2; + float cy = $height / 2; + auto dx = cx - x; + auto dy = cy - y; + return ($r_sq - dx * dx - dy * dy) / $r_sq; +} + +void Raycaster::cast_rays() { + + // WALL CASTING + /*for(int x = 0; x < $width; x++) {*/ + amt::parallel_for<32>(pool, 0, static_cast($width), [this](size_t x){ + double perpWallDist = 0; + // calculate ray position and direction + double cameraX = 2 * x / double($width) - 1; // x-coord in camera space + double rayDirX = dirX + planeX * cameraX; + double rayDirY = dirY + planeY * cameraX; + + // which box of the map we're in + int mapX = int(posX); + int mapY = int(posY); + + // length of ray from current pos to next x or y-side + double sideDistX; + double sideDistY; + + // length of ray from one x or y-side to next x or y-side + double deltaDistX = std::abs(1.0 / rayDirX); + double deltaDistY = std::abs(1.0 / rayDirY); + + int stepX = 0; + int stepY = 0; + int hit = 0; + int side = 0; + + // calculate step and initial sideDist + if(rayDirX < 0) { + stepX = -1; + sideDistX = (posX - mapX) * deltaDistX; + } else { + stepX = 1; + sideDistX = (mapX + 1.0 - posX) * deltaDistX; + } + + if(rayDirY < 0) { + stepY = -1; + sideDistY = (posY - mapY) * deltaDistY; + } else { + stepY = 1; + sideDistY = (mapY + 1.0 - posY) * deltaDistY; + } + + // perform DDA + while(hit == 0) { + if(sideDistX < sideDistY) { + sideDistX += deltaDistX; + mapX += stepX; + side = 0; + } else { + sideDistY += deltaDistY; + mapY += stepY; + side = 1; + } + + if($map[mapY][mapX] > 0) hit = 1; + } + + if(side == 0) { + perpWallDist = (sideDistX - deltaDistX); + } else { + perpWallDist = (sideDistY - deltaDistY); + } + + int lineHeight = int($height / perpWallDist); + + int drawStart = -lineHeight / 2 + $height / 2 + PITCH; + if(drawStart < 0) drawStart = 0; + + int drawEnd = lineHeight / 2 + $height / 2 + PITCH; + if(drawEnd >= $height) drawEnd = $height - 1; + + auto &texture = textures.get_texture($map[mapY][mapX] - 1); + + // calculate value of wallX + double wallX; // where exactly the wall was hit + if(side == 0) { + wallX = posY + perpWallDist * rayDirY; + } else { + wallX = posX + perpWallDist * rayDirX; + } + wallX -= floor((wallX)); + + // x coorindate on the texture + int texX = int(wallX * double(textures.TEXTURE_WIDTH)); + if(side == 0 && rayDirX > 0) texX = textures.TEXTURE_WIDTH - texX - 1; + if(side == 1 && rayDirY < 0) texX = textures.TEXTURE_WIDTH - texX - 1; + + // LODE: an integer-only bresenham or DDA like algorithm could make the texture coordinate stepping faster + + // How much to increase the texture coordinate per screen pixel + double step = 1.0 * textures.TEXTURE_HEIGHT / lineHeight; + // Starting texture coordinate + double texPos = (drawStart - PITCH - $height / 2 + lineHeight / 2) * step; + + for(int y = drawStart; y < drawEnd; y++) { + int texY = (int)texPos & (textures.TEXTURE_HEIGHT - 1); + texPos += step; + auto dist = get_distance_from_center(x, y); + auto color = dumb_lighting(texture[texY][texX], perpWallDist, dist); + pixels[y][x] = color; + } + + // SET THE ZBUFFER FOR THE SPRITE CASTING + ZBuffer[x] = perpWallDist; + }); +} + +void Raycaster::draw_ceiling_floor() { + + /*for(int y = $height / 2 + 1; y < $height; ++y) {*/ + + auto const h = static_cast($height); + amt::parallel_for<32>(pool, h / 2, h, [this, $height=h](size_t y){ + const size_t textureWidth = textures.TEXTURE_WIDTH; + const size_t textureHeight = textures.TEXTURE_HEIGHT; + // rayDir for leftmost ray (x=0) and rightmost (x = w) + float rayDirX0 = dirX - planeX; + float rayDirY0 = dirY - planeY; + float rayDirX1 = dirX + planeX; + float rayDirY1 = dirY + planeY; + + // current y position compared to the horizon + int p = y - $height / 2; + + // vertical position of the camera + // 0.5 will the camera at the center horizon. For a + // different value you need a separate loop for ceiling + // and floor since they're no longer symmetrical. + float posZ = 0.5 * $height; + + // horizontal distance from the camera to the floor for the current row + // 0.5 is the z position exactly in the middle between floor and ceiling + // See NOTE in Lode's code for more. + float rowDistance = posZ / p; + + // calculate the real world step vector we have to add for each x (parallel to camera plane) + // adding step by step avoids multiplications with a wight in the inner loop + float floorStepX = rowDistance * (rayDirX1 - rayDirX0) / $width; + float floorStepY = rowDistance * (rayDirY1 - rayDirY0) / $width; + + + // real world coordinates of the leftmost column. + // This will be updated as we step to the right + float floorX = posX + rowDistance * rayDirX0; + float floorY = posY + rowDistance * rayDirY0; + + for(int x = 0; x < $width; ++x) { + // the cell coord is simply taken from the int parts of + // floorX and floorY. + int cellX = int(floorX); + int cellY = int(floorY); + + // get the texture coordinat from the fractional part + int tx = int(textureWidth * (floorX - cellX)) & (textureWidth - 1); + int ty = int(textureWidth * (floorY - cellY)) & (textureHeight - 1); + + floorX += floorStepX; + floorY += floorStepY; + + // now get the pixel from the texture + // this uses the previous ty/tx fractional parts of + // floorX cellX to find the texture x/y. How? + + #ifdef AMT_LIGHT + // FLOOR + auto dist_floor = get_distance_from_center(x, y); + pixels[y][x] = dumb_lighting(textures.floor[ty][tx], p, dist_floor); + + // CEILING + auto dist_ceiling = get_distance_from_center(x, $height - y - 1); + pixels[$height - y - 1][x] = dumb_lighting(textures.ceiling[ty][tx], p, dist_ceiling); + #else + // FLOOR + pixels[y][x] = textures.floor[ty][tx]; + + // CEILING + pixels[$height - y - 1][x] = textures.ceiling[ty][tx]; + #endif + + } + }); + +} + +void Raycaster::render() { + draw_ceiling_floor(); + // This wait to prevent data-race + pool.wait(); // Try to remove this to see unbelievable performance + cast_rays(); + pool.wait(); // Try to remove this too + sprite_casting(); + pool.wait(); + draw_pixel_buffer(); +} + +bool Raycaster::empty_space(int new_x, int new_y) { + dbc::check((size_t)new_x < $map.cols(), + format("x={} too wide={}", new_x, $map.cols())); + dbc::check((size_t)new_y < $map.rows(), + format("y={} too high={}", new_y, $map.rows())); + + return $map[new_y][new_x] == 0; +} + + +void Raycaster::sort_sprites(std::vector& order, std::vector& dist, int amount) +{ + std::vector> sprites(amount); + + for(int i = 0; i < amount; i++) { + sprites[i].first = dist[i]; + sprites[i].second = order[i]; + } + + std::sort(sprites.begin(), sprites.end()); + + // restore in reverse order + for(int i = 0; i < amount; i++) { + dist[i] = sprites[amount - i - 1].first; + order[i] = sprites[amount - i - 1].second; + } +} + +void Raycaster::run(double speed, int dir) { + double speed_and_dir = speed * dir; + if(empty_space(int(posX + dirX * speed_and_dir), int(posY))) { + posX += dirX * speed_and_dir; + } + + if(empty_space(int(posX), int(posY + dirY * speed_and_dir))) { + posY += dirY * speed_and_dir; + } +} + +void Raycaster::rotate(double speed, int dir) { + double speed_and_dir = speed * dir; + double oldDirX = dirX; + dirX = dirX * cos(speed_and_dir) - dirY * sin(speed_and_dir); + dirY = oldDirX * sin(speed_and_dir) + dirY * cos(speed_and_dir); + + double oldPlaneX = planeX; + planeX = planeX * cos(speed_and_dir) - planeY * sin(speed_and_dir); + planeY = oldPlaneX * sin(speed_and_dir) + planeY * cos(speed_and_dir); +} diff --git a/scratchpad/amt/raycaster.hpp b/scratchpad/amt/raycaster.hpp new file mode 100644 index 0000000..b497b0d --- /dev/null +++ b/scratchpad/amt/raycaster.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "matrix.hpp" +#include +#include +#include "dbc.hpp" +#include "amt/pixel.hpp" +#include "amt/texture.hpp" +#include +#include "thread.hpp" + +using Matrix = amt::Matrix; + +struct Raycaster { + int PITCH=0; + + TexturePack textures; + double posX = 0; + double posY = 0; + + // initial direction vector + double dirX = -1; + double dirY = 0; + + // the 2d raycaster version of camera plane + double planeX = 0; + double planeY = 0.66; + sf::Texture view_texture; + sf::Sprite view_sprite; + + //ZED: USE smart pointer for this + + int $width; + int $height; + amt::PixelBuf pixels; + sf::RenderWindow& $window; + Matrix& $map; + std::vector spriteOrder; + std::vector spriteDistance; + std::vector ZBuffer; // width + float $radius; // std::min($height, $width) / 2; + float $r_sq; // = radius * radius; + amt::thread_pool_t pool; + + Raycaster(sf::RenderWindow& window, Matrix &map, unsigned width, unsigned height); + + void draw_pixel_buffer(); + void clear(); + void cast_rays(); + void draw_ceiling_floor(); + void sprite_casting(); + void sort_sprites(std::vector& order, std::vector& dist, int amount); + void render(); + + bool empty_space(int new_x, int new_y); + + void run(double speed, int dir); + void rotate(double speed, int dir); + void position_camera(float player_x, float player_y); + float get_distance_from_center(int x, int y) const noexcept; + + void set_position(int x, int y); + inline size_t pixcoord(int x, int y) { + return ((y) * $width) + (x); + } + +}; diff --git a/scratchpad/amt/texture.cpp b/scratchpad/amt/texture.cpp new file mode 100644 index 0000000..2fe78cf --- /dev/null +++ b/scratchpad/amt/texture.cpp @@ -0,0 +1,34 @@ +#include +#include "dbc.hpp" +#include +#include "config.hpp" +#include "amt/texture.hpp" + +Image TexturePack::load_image(std::string filename) { + sf::Image img; + bool good = img.loadFromFile(filename); + dbc::check(good, format("failed to load {}", filename)); + return amt::PixelBuf(img.getPixelsPtr(), TEXTURE_HEIGHT, TEXTURE_WIDTH); +} + +void TexturePack::load_textures() { + Config assets("assets/config.json"); + for(string tile_path : assets["textures"]) { + images.emplace_back(load_image(tile_path)); + } + + for(string tile_path : assets["sprites"]) { + images.emplace_back(load_image(tile_path)); + } + + floor = load_image(assets["floor"]); + ceiling = load_image(assets["ceiling"]); +} + +Image& TexturePack::get_texture(size_t num) { + return images[num]; +} + +Sprite &TexturePack::get_sprite(size_t sprite_num) { + return sprites[sprite_num]; +} diff --git a/scratchpad/amt/texture.hpp b/scratchpad/amt/texture.hpp new file mode 100644 index 0000000..179fb36 --- /dev/null +++ b/scratchpad/amt/texture.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include "amt/pixel.hpp" + +struct Sprite { + double x; + double y; + int texture; + // ZED: this should be a separate transform parameter + double elevation=0; + int uDiv=1; + int vDiv=1; +}; + +using Image = amt::PixelBuf; + +struct TexturePack { + int NUM_SPRITES=1; + static const int TEXTURE_WIDTH=256; // must be power of two + static const int TEXTURE_HEIGHT=256; // must be power of two + + std::vector images; + std::vector sprites{{4.0, 3.55, 6}}; + Image floor; + Image ceiling; + + void load_textures(); + amt::PixelBuf load_image(std::string filename); + Sprite& get_sprite(size_t sprite_num); + Image& get_texture(size_t num); +}; diff --git a/scratchpad/amt/thread.hpp b/scratchpad/amt/thread.hpp new file mode 100644 index 0000000..841199b --- /dev/null +++ b/scratchpad/amt/thread.hpp @@ -0,0 +1,253 @@ +#ifndef AMT_THREAD_HPP +#define AMT_THREAD_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace amt { + + // NOTE: Could implement lock-free queue. + template + struct Queue { + using base_type = std::deque; + using value_type = typename base_type::value_type; + using pointer = typename base_type::pointer; + using const_pointer = typename base_type::const_pointer; + using reference = typename base_type::reference; + using const_reference = typename base_type::const_reference; + using iterator = typename base_type::iterator; + using const_iterator = typename base_type::const_iterator; + using reverse_iterator = typename base_type::reverse_iterator; + using const_reverse_iterator = typename base_type::const_reverse_iterator; + using difference_type = typename base_type::difference_type; + using size_type = typename base_type::size_type; + + constexpr Queue() noexcept = default; + constexpr Queue(Queue const&) noexcept = delete; + constexpr Queue(Queue &&) noexcept = default; + constexpr Queue& operator=(Queue const&) noexcept = delete; + constexpr Queue& operator=(Queue &&) noexcept = default; + constexpr ~Queue() noexcept = default; + + template + requires std::same_as, value_type> + void push(U&& u) { + std::lock_guard m(m_mutex); + m_data.push_back(std::forward(u)); + } + + template + void emplace(Args&&... args) { + std::lock_guard m(m_mutex); + m_data.emplace_back(std::forward(args)...); + } + + std::optional pop() { + std::lock_guard m(m_mutex); + if (empty_unsafe()) return std::nullopt; + auto el = std::move(m_data.front()); + m_data.pop_front(); + return std::move(el); + } + + auto size() const noexcept -> size_type { + std::lock_guard m(m_mutex); + return m_data.size(); + } + auto empty() const noexcept -> bool { + std::lock_guard m(m_mutex); + return m_data.empty(); + } + constexpr auto size_unsafe() const noexcept -> size_type { return m_data.size(); } + constexpr auto empty_unsafe() const noexcept -> bool { return m_data.empty(); } + + private: + base_type m_data; + mutable std::mutex m_mutex; + }; + + template + struct ThreadPool; + + template + struct Worker { + using parent_t = ThreadPool*; + using work_t = Fn; + using size_type = std::size_t; + constexpr Worker() noexcept = default; + constexpr Worker(Worker const&) noexcept = default; + constexpr Worker(Worker &&) noexcept = default; + constexpr Worker& operator=(Worker const&) noexcept = default; + constexpr Worker& operator=(Worker &&) noexcept = default; + ~Worker() { + stop(); + } + + void start(parent_t pool, size_type id) { + assert((m_running.load(std::memory_order::acquire) == false) && "Thread is already running"); + m_running.store(true); + m_parent.store(pool); + m_id = id; + m_thread = std::thread([this]() { + while (m_running.load(std::memory_order::relaxed)) { + std::unique_lock lk(m_mutex); + m_cv.wait(lk, [this] { + return !m_queue.empty_unsafe() || !m_running.load(std::memory_order::relaxed); + }); + auto item = pop_task(); + if (!item) { + item = try_steal(); + if (!item) continue; + } + + process_work(std::move(*item)); + } + }); + } + + void process_work(work_t&& work) const noexcept { + std::invoke(std::move(work)); + auto ptr = m_parent.load(); + if (ptr) ptr->task_completed(); + } + + void stop() { + if (!m_running.load()) return; + { + std::lock_guard lock(m_mutex); + m_running.store(false); + } + m_cv.notify_all(); + m_thread.join(); + m_parent.store(nullptr); + } + + void add(work_t&& work) { + std::lock_guard lock(m_mutex); + m_queue.push(std::move(work)); + m_cv.notify_one(); + } + + std::optional pop_task() noexcept { + return m_queue.pop(); + } + + + std::optional try_steal() noexcept { + auto ptr = m_parent.load(); + if (ptr) return ptr->try_steal(m_id); + return {}; + } + + constexpr bool empty() const noexcept { return m_queue.empty_unsafe(); } + constexpr size_type size() const noexcept { return m_queue.size_unsafe(); } + constexpr size_type id() const noexcept { return m_id; } + constexpr bool running() const noexcept { return m_running.load(std::memory_order::relaxed); } + + private: + Queue m_queue{}; + std::thread m_thread; + std::atomic m_running{false}; + std::mutex m_mutex{}; + std::condition_variable m_cv{}; + std::atomic m_parent{nullptr}; + size_type m_id; + }; + + template + struct ThreadPool { + using worker_t = Worker; + using work_t = typename worker_t::work_t; + using size_type = std::size_t; + + constexpr ThreadPool(ThreadPool const&) noexcept = delete; + constexpr ThreadPool(ThreadPool &&) noexcept = default; + constexpr ThreadPool& operator=(ThreadPool const&) noexcept = delete; + constexpr ThreadPool& operator=(ThreadPool &&) noexcept = default; + ~ThreadPool() { + stop(); + } + + ThreadPool(size_type n = std::thread::hardware_concurrency()) + : m_workers(std::max(n, size_type{1})) + { + for (auto i = 0ul; i < m_workers.size(); ++i) { + m_workers[i].start(this, i); + } + } + + void stop() { + for (auto& w: m_workers) w.stop(); + } + + void add(Fn&& work) { + m_active_tasks.fetch_add(1, std::memory_order::relaxed); + m_workers[m_last_added].add(std::move(work)); + m_last_added = (m_last_added + 1) % m_workers.size(); + } + + std::optional try_steal(size_type id) { + for (auto& w: m_workers) { + if (w.id() == id) continue; + auto item = w.pop_task(); + if (item) return item; + } + return {}; + } + + void task_completed() { + if (m_active_tasks.fetch_sub(1, std::memory_order::release) == 1) { + m_wait_cv.notify_all(); + } + } + + void wait() { + std::unique_lock lock(m_wait_mutex); + m_wait_cv.wait(lock, [this] { + return m_active_tasks.load(std::memory_order::acquire) == 0; + }); + } + + + private: + std::vector m_workers; + size_type m_last_added{}; + std::mutex m_wait_mutex; + std::condition_variable m_wait_cv; + std::atomic m_active_tasks{0}; + }; + + using thread_pool_t = ThreadPool>; + + // WARNING: Do not capture the stack variable if you're defering wait on pool. + // If you want to capture them, either capture them value or do "pool.wait()" at the end of the scope. + template + requires (std::is_invocable_v) + constexpr auto parallel_for(thread_pool_t& pool, std::size_t start, std::size_t end, Fn&& body) noexcept { + if (start >= end) return; + + auto const size = (end - start); + auto const chunk_size = std::max(size_t{1}, (size + Split - 1) / Split); + auto const num_chunks = (size + chunk_size - 1) / chunk_size; + + for (auto chunk = 0ul; chunk < num_chunks; ++chunk) { + auto const chunk_start = std::min(start + (chunk * chunk_size), end); + auto const chunk_end = std::min(chunk_start + (chunk_size), end); + pool.add([chunk_start, chunk_end, body] { + for (auto i = chunk_start; i < chunk_end; ++i) { + std::invoke(body, i); + } + }); + } + } +} // nsmespace amt + +#endif // AMT_THREAD_HPP