Don't add wiggle to the things in the player's square since that moves them into the player eyes.

master
Zed A. Shaw 23 hours ago
parent a4a4389281
commit 077f0e84ea
  1. 93
      amt/main.cpp
  2. 197
      amt/matrix.hpp
  3. 746
      amt/pixel.hpp
  4. 417
      amt/raycaster.cpp
  5. 73
      amt/raycaster.hpp
  6. 34
      amt/texture.cpp
  7. 34
      amt/texture.hpp
  8. 253
      amt/thread.hpp
  9. 2
      spatialmap.cpp

@ -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

@ -101,7 +101,7 @@ inline void update_sorted(SortedEntities& sprite_distance, PointEntityMap& table
int inside = (from.x - sprite.x) * (from.x - sprite.x) +
(from.y - sprite.y) * (from.y - sprite.y);
if(sprite == seen) {
if(sprite == seen && from != sprite) {
wiggle += 0.02f;
} else {
wiggle = 0.0f;

Loading…
Cancel
Save