master
parent
a4a4389281
commit
077f0e84ea
@ -1,93 +0,0 @@ |
|||||||
#include "amt/raycaster.hpp" |
|
||||||
#include <iostream> |
|
||||||
#include <chrono> |
|
||||||
#include <numeric> |
|
||||||
#include <functional> |
|
||||||
#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<double>(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; |
|
||||||
} |
|
@ -1,197 +0,0 @@ |
|||||||
#pragma once |
|
||||||
|
|
||||||
#include <cassert> |
|
||||||
#include <cstddef> |
|
||||||
#include <initializer_list> |
|
||||||
#include <iterator> |
|
||||||
#include <type_traits> |
|
||||||
#include <algorithm> |
|
||||||
|
|
||||||
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 <typename T> |
|
||||||
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<iterator>; |
|
||||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>; |
|
||||||
using difference_type = std::ptrdiff_t; |
|
||||||
using size_type = std::size_t; |
|
||||||
|
|
||||||
template <bool IsConst> |
|
||||||
struct View { |
|
||||||
using base_type = std::conditional_t<IsConst, const_pointer, pointer>; |
|
||||||
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<std::initializer_list<value_type>> 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<false> { |
|
||||||
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<true> { |
|
||||||
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 <format> |
|
||||||
namespace std { |
|
||||||
template <typename T> |
|
||||||
struct formatter<amt::Matrix<T>> { |
|
||||||
constexpr auto parse(format_parse_context& ctx) { |
|
||||||
return ctx.begin(); |
|
||||||
} |
|
||||||
|
|
||||||
auto format(amt::Matrix<T> 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 |
|
@ -1,746 +0,0 @@ |
|||||||
#ifndef AMT_PIXEL_HPP |
|
||||||
#define AMT_PIXEL_HPP |
|
||||||
|
|
||||||
#include "matrix.hpp" |
|
||||||
#include <algorithm> |
|
||||||
#include <cmath> |
|
||||||
#include <cstddef> |
|
||||||
#include <cstdint> |
|
||||||
#include <limits> |
|
||||||
#include <string> |
|
||||||
#include <stdexcept> |
|
||||||
#include <type_traits> |
|
||||||
|
|
||||||
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<float>::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<pixels_t>(this->r()); |
|
||||||
auto b = static_cast<pixels_t>(this->b()); |
|
||||||
auto g = static_cast<pixels_t>(this->g()); |
|
||||||
auto a = static_cast<pixels_t>(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 <typename T> |
|
||||||
requires std::is_arithmetic_v<T> |
|
||||||
constexpr auto operator/(T val) const noexcept { |
|
||||||
auto d = static_cast<float>(val); |
|
||||||
return RGBA( |
|
||||||
static_cast<pixel_t>(r() / d), |
|
||||||
static_cast<pixel_t>(g() / d), |
|
||||||
static_cast<pixel_t>(b() / d), |
|
||||||
a() |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
template <typename T> |
|
||||||
requires std::is_arithmetic_v<T> |
|
||||||
constexpr auto operator*(T val) const noexcept { |
|
||||||
auto d = static_cast<float>(val); |
|
||||||
return RGBA( |
|
||||||
static_cast<pixel_t>(r() * d), |
|
||||||
static_cast<pixel_t>(g() * d), |
|
||||||
static_cast<pixel_t>(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<pixel_t>(p * 255); |
|
||||||
} |
|
||||||
|
|
||||||
template <BlendMode M> |
|
||||||
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 <BlendMode M> |
|
||||||
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<BlendMode::normal>(); |
|
||||||
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<BlendMode::multiply>(); |
|
||||||
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<BlendMode::screen>(); |
|
||||||
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<BlendMode::overlay>(); |
|
||||||
auto hard_light_fn = blend_helper<BlendMode::hardLight>(); |
|
||||||
auto multiply_fn = blend_helper<BlendMode::multiply>(); |
|
||||||
auto screen_fn = blend_helper<BlendMode::screen>(); |
|
||||||
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<BlendMode::darken>(); |
|
||||||
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<BlendMode::lighten>(); |
|
||||||
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<BlendMode::colorDodge>(); |
|
||||||
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<BlendMode::colorDodge>(); |
|
||||||
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<BlendMode::hardLight>(); |
|
||||||
auto multiply_fn = blend_helper<BlendMode::multiply>(); |
|
||||||
auto screen_fn = blend_helper<BlendMode::screen>(); |
|
||||||
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<BlendMode::softLight>(); |
|
||||||
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<BlendMode::difference>(); |
|
||||||
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<BlendMode::exclusion>(); |
|
||||||
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<BlendMode::normal>(color); |
|
||||||
case BlendMode::multiply: return blend<BlendMode::multiply>(color); |
|
||||||
case BlendMode::screen: return blend<BlendMode::screen>(color); |
|
||||||
case BlendMode::overlay: return blend<BlendMode::overlay>(color); |
|
||||||
case BlendMode::darken: return blend<BlendMode::darken>(color); |
|
||||||
case BlendMode::lighten: return blend<BlendMode::lighten>(color); |
|
||||||
case BlendMode::colorDodge: return blend<BlendMode::colorDodge>(color); |
|
||||||
case BlendMode::colorBurn: return blend<BlendMode::colorBurn>(color); |
|
||||||
case BlendMode::hardLight: return blend<BlendMode::hardLight>(color); |
|
||||||
case BlendMode::softLight: return blend<BlendMode::softLight>(color); |
|
||||||
case BlendMode::difference: return blend<BlendMode::difference>(color); |
|
||||||
case BlendMode::exclusion: return blend<BlendMode::exclusion>(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 <unsigned min, unsigned max> |
|
||||||
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<float>(static_cast<int>(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<RGBA::pixel_t>(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 <PixelFormat F> |
|
||||||
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 <PixelFormat F> |
|
||||||
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<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; |
|
||||||
|
|
||||||
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<PixelFormat::rgba>(in, data(), size()); break; |
|
||||||
case PixelFormat::abgr: from_helper<PixelFormat::abgr>(in, data(), size()); break; |
|
||||||
case PixelFormat::rgb: from_helper<PixelFormat::rgb >(in, data(), size()); break; |
|
||||||
case PixelFormat::bgr: from_helper<PixelFormat::bgr >(in, data(), size()); break; |
|
||||||
case PixelFormat::ga: from_helper<PixelFormat::ga >(in, data(), size()); break; |
|
||||||
case PixelFormat::ag: from_helper<PixelFormat::ag >(in, data(), size()); break; |
|
||||||
case PixelFormat::g: from_helper<PixelFormat::g >(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<std::uint8_t*>(data()); } |
|
||||||
auto to_raw_buf() const noexcept -> std::uint8_t const* { return reinterpret_cast<std::uint8_t const*>(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<PixelFormat::rgba>(data(), out, size());return; |
|
||||||
case PixelFormat::abgr: copy_to_helper<PixelFormat::abgr>(data(), out, size());return; |
|
||||||
case PixelFormat::rgb: copy_to_helper<PixelFormat::rgb >(data(), out, size());return; |
|
||||||
case PixelFormat::bgr: copy_to_helper<PixelFormat::bgr >(data(), out, size());return; |
|
||||||
case PixelFormat::ga: copy_to_helper<PixelFormat::ga >(data(), out, size());return; |
|
||||||
case PixelFormat::ag: copy_to_helper<PixelFormat::ag >(data(), out, size());return; |
|
||||||
case PixelFormat::g: copy_to_helper<PixelFormat::g >(data(), out, size());return; |
|
||||||
} |
|
||||||
assert(false && "unreachable"); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
private: |
|
||||||
template <PixelFormat F> |
|
||||||
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<F>(in[i], out + i * channels); |
|
||||||
} |
|
||||||
}
|
|
||||||
|
|
||||||
template <PixelFormat F> |
|
||||||
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<F>(in + i * channels); |
|
||||||
} |
|
||||||
}
|
|
||||||
|
|
||||||
private: |
|
||||||
base_type m_data; |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace amt
|
|
||||||
|
|
||||||
#include <format> |
|
||||||
namespace std { |
|
||||||
template <> |
|
||||||
struct formatter<amt::RGBA> { |
|
||||||
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<amt::HSLA> { |
|
||||||
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<amt::PixelBuf> { |
|
||||||
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
|
|
@ -1,417 +0,0 @@ |
|||||||
#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<amt::BlendMode::softLight>(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<int>(width)), |
|
||||||
$height(static_cast<int>(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<std::size_t>($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<size_t>($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<int>& order, std::vector<double>& dist, int amount) |
|
||||||
{ |
|
||||||
std::vector<std::pair<double, int>> 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); |
|
||||||
} |
|
@ -1,73 +0,0 @@ |
|||||||
#pragma once |
|
||||||
|
|
||||||
#include <fmt/core.h> |
|
||||||
#include <SFML/Graphics.hpp> |
|
||||||
#include <SFML/Graphics/Image.hpp> |
|
||||||
#include <numbers> |
|
||||||
#include <algorithm> |
|
||||||
#include <cmath> |
|
||||||
#include "matrix.hpp" |
|
||||||
#include <cstdlib> |
|
||||||
#include <array> |
|
||||||
#include "dbc.hpp" |
|
||||||
#include "amt/pixel.hpp" |
|
||||||
#include "amt/texture.hpp" |
|
||||||
#include <memory> |
|
||||||
#include "thread.hpp" |
|
||||||
|
|
||||||
using Matrix = amt::Matrix<int>; |
|
||||||
|
|
||||||
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<int> spriteOrder; |
|
||||||
std::vector<double> spriteDistance; |
|
||||||
std::vector<double> 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<int>& order, std::vector<double>& 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); |
|
||||||
} |
|
||||||
|
|
||||||
}; |
|
@ -1,34 +0,0 @@ |
|||||||
#include <SFML/Graphics/Image.hpp> |
|
||||||
#include "dbc.hpp" |
|
||||||
#include <fmt/core.h> |
|
||||||
#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]; |
|
||||||
} |
|
@ -1,34 +0,0 @@ |
|||||||
#pragma once |
|
||||||
|
|
||||||
#include <cstdint> |
|
||||||
#include <vector> |
|
||||||
#include <string> |
|
||||||
#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<amt::PixelBuf> images; |
|
||||||
std::vector<Sprite> 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); |
|
||||||
}; |
|
@ -1,253 +0,0 @@ |
|||||||
#ifndef AMT_THREAD_HPP |
|
||||||
#define AMT_THREAD_HPP |
|
||||||
|
|
||||||
#include <cassert> |
|
||||||
#include <concepts> |
|
||||||
#include <cstddef> |
|
||||||
#include <deque> |
|
||||||
#include <mutex> |
|
||||||
#include <type_traits> |
|
||||||
#include <thread> |
|
||||||
#include <condition_variable> |
|
||||||
#include <atomic> |
|
||||||
#include <functional> |
|
||||||
|
|
||||||
namespace amt { |
|
||||||
|
|
||||||
// NOTE: Could implement lock-free queue.
|
|
||||||
template <typename T> |
|
||||||
struct Queue { |
|
||||||
using base_type = std::deque<T>; |
|
||||||
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 <typename U> |
|
||||||
requires std::same_as<std::decay_t<U>, value_type> |
|
||||||
void push(U&& u) { |
|
||||||
std::lock_guard m(m_mutex); |
|
||||||
m_data.push_back(std::forward<U>(u)); |
|
||||||
} |
|
||||||
|
|
||||||
template <typename... Args> |
|
||||||
void emplace(Args&&... args) { |
|
||||||
std::lock_guard m(m_mutex); |
|
||||||
m_data.emplace_back(std::forward<Args>(args)...); |
|
||||||
} |
|
||||||
|
|
||||||
std::optional<value_type> 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 <typename Fn> |
|
||||||
struct ThreadPool; |
|
||||||
|
|
||||||
template <typename Fn> |
|
||||||
struct Worker { |
|
||||||
using parent_t = ThreadPool<Fn>*; |
|
||||||
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<std::mutex> 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<std::mutex> lock(m_mutex); |
|
||||||
m_queue.push(std::move(work)); |
|
||||||
m_cv.notify_one(); |
|
||||||
} |
|
||||||
|
|
||||||
std::optional<work_t> pop_task() noexcept { |
|
||||||
return m_queue.pop(); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
std::optional<work_t> 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<work_t> m_queue{}; |
|
||||||
std::thread m_thread; |
|
||||||
std::atomic<bool> m_running{false}; |
|
||||||
std::mutex m_mutex{}; |
|
||||||
std::condition_variable m_cv{}; |
|
||||||
std::atomic<parent_t> m_parent{nullptr}; |
|
||||||
size_type m_id; |
|
||||||
}; |
|
||||||
|
|
||||||
template <typename Fn> |
|
||||||
struct ThreadPool { |
|
||||||
using worker_t = Worker<Fn>; |
|
||||||
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<work_t> 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<worker_t> m_workers; |
|
||||||
size_type m_last_added{}; |
|
||||||
std::mutex m_wait_mutex; |
|
||||||
std::condition_variable m_wait_cv; |
|
||||||
std::atomic<size_t> m_active_tasks{0}; |
|
||||||
}; |
|
||||||
|
|
||||||
using thread_pool_t = ThreadPool<std::function<void()>>; |
|
||||||
|
|
||||||
// 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 <std::size_t Split, typename Fn> |
|
||||||
requires (std::is_invocable_v<Fn, std::size_t>) |
|
||||||
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
|
|
Loading…
Reference in new issue