parent
2dfe5417b1
commit
5e63272f24
@ -0,0 +1,67 @@ |
|||||||
|
#include "amt/raycaster.hpp" |
||||||
|
|
||||||
|
#define RAY_VIEW_WIDTH 960 |
||||||
|
#define RAY_VIEW_HEIGHT 720 |
||||||
|
#define RAY_VIEW_X (1280 - RAY_VIEW_WIDTH) |
||||||
|
#define RAY_VIEW_Y 0 |
||||||
|
|
||||||
|
static const int SCREEN_HEIGHT=720; |
||||||
|
static const int SCREEN_WIDTH=1280; |
||||||
|
|
||||||
|
using Matrix = amt::Matrix<int>; |
||||||
|
|
||||||
|
Matrix MAP{ |
||||||
|
{8,8,8,8,8,8,8,8,8}, |
||||||
|
{8,0,2,0,0,0,0,0,8}, |
||||||
|
{8,0,7,0,0,5,6,0,8}, |
||||||
|
{8,0,0,0,0,0,0,0,8}, |
||||||
|
{8,8,0,0,0,0,0,8,8}, |
||||||
|
{8,0,0,1,3,4,0,0,8}, |
||||||
|
{8,0,0,0,0,0,8,8,8}, |
||||||
|
{8,0,0,0,0,0,0,0,8}, |
||||||
|
{8,8,8,8,8,8,8,8,8} |
||||||
|
}; |
||||||
|
|
||||||
|
int main() { |
||||||
|
using KB = sf::Keyboard; |
||||||
|
|
||||||
|
sf::RenderWindow window(sf::VideoMode(SCREEN_WIDTH, SCREEN_HEIGHT), "Zed's Ray Caster Game Thing"); |
||||||
|
|
||||||
|
//ZED this should set with a function
|
||||||
|
float player_x = MAP.cols() / 2; |
||||||
|
float player_y = MAP.rows() / 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; |
||||||
|
|
||||||
|
while(window.isOpen()) { |
||||||
|
rayview.render(); |
||||||
|
// DRAW GUI
|
||||||
|
window.display(); |
||||||
|
|
||||||
|
if(KB::isKeyPressed(KB::W)) { |
||||||
|
rayview.run(moveSpeed, 1); |
||||||
|
} else if(KB::isKeyPressed(KB::S)) { |
||||||
|
rayview.run(moveSpeed, -1); |
||||||
|
} |
||||||
|
|
||||||
|
if(KB::isKeyPressed(KB::D)) { |
||||||
|
rayview.rotate(rotSpeed, -1); |
||||||
|
} else if(KB::isKeyPressed(KB::A)) { |
||||||
|
rayview.rotate(rotSpeed, 1); |
||||||
|
} |
||||||
|
|
||||||
|
sf::Event event; |
||||||
|
while(window.pollEvent(event)) { |
||||||
|
if(event.type == sf::Event::Closed) { |
||||||
|
window.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
@ -0,0 +1,184 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cassert> |
||||||
|
#include <cstddef> |
||||||
|
#include <initializer_list> |
||||||
|
#include <iterator> |
||||||
|
#include <type_traits> |
||||||
|
#include <algorithm> |
||||||
|
|
||||||
|
namespace amt { |
||||||
|
|
||||||
|
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 size; |
||||||
|
|
||||||
|
constexpr reference operator[](size_type k) noexcept requires (!IsConst) { |
||||||
|
assert(k < size && "Out of bound access"); |
||||||
|
return data[k]; |
||||||
|
} |
||||||
|
|
||||||
|
constexpr const_reference operator[](size_type k) const noexcept { |
||||||
|
assert(k < size && "Out of bound access"); |
||||||
|
return data[k]; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
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 = r * m_col + c; // column-major;
|
||||||
|
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 = r * m_col + c; // column-major;
|
||||||
|
assert(index < size() && "Out of bound access"); |
||||||
|
return m_data[index]; |
||||||
|
} |
||||||
|
|
||||||
|
constexpr auto operator[](size_type r) noexcept -> View<false> { |
||||||
|
auto const base = r * m_col; |
||||||
|
assert(r < rows() && "Out of bound access"); |
||||||
|
return { .data = m_data + base, .size = m_col }; |
||||||
|
} |
||||||
|
|
||||||
|
constexpr auto operator[](size_type r) const noexcept -> View<true> { |
||||||
|
auto const base = r * m_col; |
||||||
|
assert(r < rows() && "Out of bound access"); |
||||||
|
return { .data = m_data + base, .size = 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 |
@ -0,0 +1,582 @@ |
|||||||
|
#ifndef AMT_PIXEL_HPP |
||||||
|
#define AMT_PIXEL_HPP |
||||||
|
|
||||||
|
#include "matrix.hpp" |
||||||
|
#include <algorithm> |
||||||
|
#include <cmath> |
||||||
|
#include <cstddef> |
||||||
|
#include <cstdint> |
||||||
|
#include <limits> |
||||||
|
#include <stdexcept> |
||||||
|
|
||||||
|
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; |
||||||
|
pixel_t r{}; // 0-255
|
||||||
|
pixel_t g{}; // 0-255
|
||||||
|
pixel_t b{}; // 0-255
|
||||||
|
pixel_t a{}; // 0-255
|
||||||
|
|
||||||
|
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; |
||||||
|
|
||||||
|
constexpr RGBA(pixel_t r, pixel_t g, pixel_t b, pixel_t a = 0xff) noexcept |
||||||
|
: r(r) |
||||||
|
, g(g) |
||||||
|
, b(b) |
||||||
|
, a(a) |
||||||
|
{} |
||||||
|
|
||||||
|
constexpr RGBA(pixel_t color, pixel_t a = 0xff) noexcept |
||||||
|
: RGBA(color, color, color, a) |
||||||
|
{} |
||||||
|
|
||||||
|
// NOTE: RRGGBBAA
|
||||||
|
constexpr static auto from_hex(std::uint32_t color) noexcept -> RGBA { |
||||||
|
return RGBA( |
||||||
|
((color >> (8 * 3)) & 0xff), |
||||||
|
((color >> (8 * 2)) & 0xff), |
||||||
|
((color >> (8 * 1)) & 0xff), |
||||||
|
((color >> (8 * 0)) & 0xff) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// NOTE: RRGGBBAA
|
||||||
|
constexpr auto to_hex() const noexcept -> std::uint32_t { |
||||||
|
auto r = static_cast<std::uint32_t>(this->r); |
||||||
|
auto b = static_cast<std::uint32_t>(this->b); |
||||||
|
auto g = static_cast<std::uint32_t>(this->g); |
||||||
|
auto a = static_cast<std::uint32_t>(this->a); |
||||||
|
return (r << (8 * 3)) | (g << (8 * 2)) | (b << (8 * 1)) | (a << (8 * 0)); |
||||||
|
} |
||||||
|
|
||||||
|
constexpr auto blend(RGBA color, BlendMode mode) 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 nr = blend_helper(normalize(r), normalize(color.r), normalize(a), mode); |
||||||
|
auto ng = blend_helper(normalize(g), normalize(color.g), normalize(a), mode); |
||||||
|
auto nb = blend_helper(normalize(b), normalize(color.b), normalize(a), mode); |
||||||
|
|
||||||
|
return RGBA( |
||||||
|
to_pixel(nr), |
||||||
|
to_pixel(ng), |
||||||
|
to_pixel(nb), |
||||||
|
alpha |
||||||
|
); |
||||||
|
} |
||||||
|
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>(std::clamp(p, 0.f, 1.f) * 255); |
||||||
|
} |
||||||
|
|
||||||
|
static constexpr auto apply_op(pixel_t l, pixel_t r, auto&& fn) noexcept -> pixel_t { |
||||||
|
return RGBA::to_pixel(fn(RGBA::normalize(l), RGBA::normalize(r))); |
||||||
|
} |
||||||
|
|
||||||
|
static constexpr auto blend_helper(float bg, float fg, float alpha, BlendMode mode) noexcept -> float { |
||||||
|
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; |
||||||
|
}; |
||||||
|
|
||||||
|
switch (mode) { |
||||||
|
case BlendMode::normal: return mix_helper(bg, fg, alpha); |
||||||
|
case BlendMode::multiply: { |
||||||
|
return mix_helper(bg, bg * fg, alpha); |
||||||
|
} |
||||||
|
case BlendMode::screen: { |
||||||
|
constexpr auto fn = [](float b, float s) -> float { |
||||||
|
// Cb + Cs -(Cb x Cs)
|
||||||
|
return b + s - (b * s); |
||||||
|
}; |
||||||
|
auto bf = fn(bg, fg); |
||||||
|
return mix_helper(bg, bf, alpha); |
||||||
|
} |
||||||
|
case BlendMode::overlay: { |
||||||
|
// HardLight(Cs, Cb)
|
||||||
|
auto hl = blend_helper(bg, fg, alpha, BlendMode::hardLight); |
||||||
|
return mix_helper(bg, hl, alpha); |
||||||
|
} |
||||||
|
case BlendMode::darken: return mix_helper(bg, std::min(bg, fg), alpha); |
||||||
|
case BlendMode::lighten: return mix_helper(bg, std::max(bg, fg), alpha); |
||||||
|
case BlendMode::colorDodge: { |
||||||
|
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); |
||||||
|
} |
||||||
|
case BlendMode::colorBurn: { |
||||||
|
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); |
||||||
|
} |
||||||
|
case BlendMode::hardLight: { |
||||||
|
constexpr auto fn = [](float b, float s, float a) -> float { |
||||||
|
if (s <= 0.5f) { |
||||||
|
return RGBA::blend_helper(b, 2.f * s, a, BlendMode::multiply); |
||||||
|
} else { |
||||||
|
return RGBA::blend_helper(b, 2.f * s - 1.f, a, BlendMode::screen); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
auto bf = fn(bg, fg, alpha); |
||||||
|
return mix_helper(bg, bf, alpha); |
||||||
|
} |
||||||
|
case BlendMode::softLight: { |
||||||
|
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); |
||||||
|
} |
||||||
|
case BlendMode::difference: { |
||||||
|
// B(Cb, Cs) = | Cb - Cs |
|
||||||
|
return mix_helper(bg, (bg > fg ? (bg - fg) : (fg - bg)), alpha); |
||||||
|
} |
||||||
|
case BlendMode::exclusion: { |
||||||
|
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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
struct HSLA { |
||||||
|
using pixel_t = float; |
||||||
|
pixel_t h{}; // hue: 0-360
|
||||||
|
pixel_t s{}; // saturation: 0-100%
|
||||||
|
pixel_t l{}; // lightness: 0-100%
|
||||||
|
pixel_t a{}; // alpha: 0-100%
|
||||||
|
|
||||||
|
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 |
||||||
|
: h(h) |
||||||
|
, s(s) |
||||||
|
, l(l) |
||||||
|
, a(a) |
||||||
|
{} |
||||||
|
|
||||||
|
constexpr HSLA(RGBA color) noexcept { |
||||||
|
auto tr = float(color.r) / 255; |
||||||
|
auto tg = float(color.g) / 255; |
||||||
|
auto tb = float(color.b) / 255; |
||||||
|
auto ta = float(color.a) / 255; |
||||||
|
|
||||||
|
auto min = std::min({tr, tg, tb}); |
||||||
|
auto max = std::max({tr, tg, tb}); |
||||||
|
auto c = max - min; |
||||||
|
|
||||||
|
float hue = 0; |
||||||
|
float s = 0; |
||||||
|
auto l = (max + min) / 2; |
||||||
|
|
||||||
|
if (!detail::compare_float(c, 0)) { |
||||||
|
auto temp_max = std::max({color.r, color.g, color.b}); |
||||||
|
if (temp_max == color.r) { |
||||||
|
auto seg = (tg - tb) / c; |
||||||
|
auto shift = (seg < 0 ? 360.f : 0.f) / 60.f; |
||||||
|
hue = seg + shift; |
||||||
|
} else if (temp_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; |
||||||
|
|
||||||
|
this->h = hue; |
||||||
|
this->s = s * 100.f; |
||||||
|
this->l = l * 100.f; |
||||||
|
this->a = ta * 100.f; |
||||||
|
} |
||||||
|
|
||||||
|
constexpr operator RGBA() const noexcept { |
||||||
|
auto th = std::clamp(h, 0.f, 360.f) / 360; |
||||||
|
auto ts = std::clamp(s, 0.f, 100.f) / 100; |
||||||
|
auto tl = std::clamp(l, 0.f, 100.f) / 100; |
||||||
|
auto ta = std::clamp(a, 0.f, 100.f) / 100; |
||||||
|
if (detail::compare_float(ts, 0)) return RGBA(to_int(tl), to_int(ta)); |
||||||
|
|
||||||
|
float const q = tl < 0.5 ? tl * (1 + ts) : tl + ts - tl * ts; |
||||||
|
float const p = 2 * tl - q; |
||||||
|
|
||||||
|
return RGBA( |
||||||
|
to_int(convert_hue(p, q, th + 1.f / 3)), |
||||||
|
to_int(convert_hue(p, q, th)), |
||||||
|
to_int(convert_hue(p, q, th - 1.f / 3)), |
||||||
|
to_int(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)); |
||||||
|
} |
||||||
|
private: |
||||||
|
|
||||||
|
static constexpr auto to_int(float a, float max = 1) noexcept -> std::uint8_t { |
||||||
|
return static_cast<std::uint8_t>((a / max) * 255); |
||||||
|
} |
||||||
|
|
||||||
|
static constexpr auto convert_hue(float p, float q, float t) noexcept -> float { |
||||||
|
if (t < 0) t += 1; |
||||||
|
if (t > 1) t -= 1; |
||||||
|
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; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
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(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 auto operator[](size_type r) noexcept { return m_data[r]; } |
||||||
|
constexpr 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
|
@ -0,0 +1,395 @@ |
|||||||
|
#include "amt/raycaster.hpp" |
||||||
|
|
||||||
|
using namespace fmt; |
||||||
|
using std::make_unique; |
||||||
|
|
||||||
|
#define rgba_color(r,g,b,a) (r<<(0*8))|(g<<(1*8))|(b<<(2*8))|(a<<(3*8)) |
||||||
|
#define gray_color(c) rgba_color(c, c, c, 255) |
||||||
|
|
||||||
|
|
||||||
|
std::vector<uint32_t> TexturePack::load_image(const char *filename) { |
||||||
|
std::vector<uint32_t> texture(TEXTURE_WIDTH * TEXTURE_HEIGHT); |
||||||
|
sf::Image img; |
||||||
|
bool good = img.loadFromFile(filename); |
||||||
|
dbc::check(good, format("failed to load {}", filename)); |
||||||
|
|
||||||
|
uint32_t *pixbuf = (uint32_t *)img.getPixelsPtr(); |
||||||
|
std::copy_n(pixbuf, texture.size(), texture.begin()); |
||||||
|
|
||||||
|
return texture; |
||||||
|
} |
||||||
|
|
||||||
|
void TexturePack::load_textures() { |
||||||
|
images.emplace_back(load_image("assets/tile16.png")); |
||||||
|
images.emplace_back(load_image("assets/tile02.png")); |
||||||
|
images.emplace_back(load_image("assets/tile03.png")); |
||||||
|
images.emplace_back(load_image("assets/tile32.png")); |
||||||
|
images.emplace_back(load_image("assets/tile05.png")); |
||||||
|
images.emplace_back(load_image("assets/tile17.png")); |
||||||
|
images.emplace_back(load_image("assets/tile10.png")); |
||||||
|
images.emplace_back(load_image("assets/tile01.png")); |
||||||
|
images.emplace_back(load_image("assets/portal.png")); |
||||||
|
} |
||||||
|
|
||||||
|
std::vector<uint32_t>& TexturePack::get(size_t num) { |
||||||
|
return images[num]; |
||||||
|
} |
||||||
|
|
||||||
|
Sprite &TexturePack::get_sprite(size_t sprite_num) { |
||||||
|
return SPRITE[sprite_num]; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Raycaster::Raycaster(sf::RenderWindow& window, Matrix &map, int width, int height) : |
||||||
|
$width(width), $height(height), |
||||||
|
$window(window), |
||||||
|
$map(map), |
||||||
|
spriteOrder(textures.NUM_SPRITES), |
||||||
|
spriteDistance(textures.NUM_SPRITES), |
||||||
|
ZBuffer(width) |
||||||
|
{ |
||||||
|
$window.setVerticalSyncEnabled(true); |
||||||
|
view_texture.create($width, $height); |
||||||
|
view_sprite.setTexture(view_texture); |
||||||
|
view_sprite.setPosition(0, 0); |
||||||
|
pixels = make_unique<RGBA[]>($width * $height); |
||||||
|
textures.load_textures(); |
||||||
|
} |
||||||
|
|
||||||
|
void Raycaster::set_position(int x, int y) { |
||||||
|
view_sprite.setPosition(x, 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((uint8_t *)pixels.get(), $width, $height, 0, 0); |
||||||
|
// BUG: can I do this once and just update it?
|
||||||
|
$window.draw(view_sprite); |
||||||
|
} |
||||||
|
|
||||||
|
void Raycaster::clear() { |
||||||
|
std::fill_n(pixels.get(), $width * $height, 0); |
||||||
|
$window.clear(); |
||||||
|
} |
||||||
|
|
||||||
|
void Raycaster::sprite_casting() { |
||||||
|
const int textureWidth = textures.TEXTURE_WIDTH; |
||||||
|
const int textureHeight = textures.TEXTURE_HEIGHT; |
||||||
|
|
||||||
|
// sort sprites from far to close
|
||||||
|
for(int i = 0; i < textures.NUM_SPRITES; i++) { |
||||||
|
spriteOrder[i] = i; |
||||||
|
// this is just the distance calculation
|
||||||
|
spriteDistance[i] = ((posX - textures.SPRITE[i].x) * |
||||||
|
(posX - textures.SPRITE[i].x) + |
||||||
|
(posY - textures.SPRITE[i].y) * |
||||||
|
(posY - textures.SPRITE[i].y)); |
||||||
|
} |
||||||
|
|
||||||
|
sort_sprites(spriteOrder, spriteDistance, textures.NUM_SPRITES); |
||||||
|
|
||||||
|
// after sorting the sprites, do the projection
|
||||||
|
for(int i = 0; i < textures.NUM_SPRITES; i++) { |
||||||
|
int sprite_index = spriteOrder[i]; |
||||||
|
Sprite& sprite_rec = textures.get_sprite(sprite_index); |
||||||
|
double spriteX = sprite_rec.x - posX; |
||||||
|
double spriteY = sprite_rec.y - posY; |
||||||
|
auto& sprite_texture = textures.get(sprite_rec.texture); |
||||||
|
|
||||||
|
//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(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; |
||||||
|
//get current color from the texture
|
||||||
|
// BUG: this crashes sometimes when the math goes out of bounds
|
||||||
|
uint32_t color = sprite_texture[textureWidth * texY + texX]; |
||||||
|
// poor person's transparency, get current color from the texture
|
||||||
|
if((color & 0x00FFFFFF) != 0) { |
||||||
|
RGBA pixel = color; |
||||||
|
pixels[pixcoord(stripe, y)] = pixel; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void Raycaster::cast_rays() { |
||||||
|
double perpWallDist; |
||||||
|
|
||||||
|
// WALL CASTING
|
||||||
|
for(int x = 0; x < $width; x++) { |
||||||
|
// 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[mapX][mapY] > 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($map[mapX][mapY] - 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; |
||||||
|
RGBA pixel = texture[textures.TEXTURE_HEIGHT * texY + texX]; |
||||||
|
pixels[pixcoord(x, y)] = pixel; |
||||||
|
} |
||||||
|
|
||||||
|
// SET THE ZBUFFER FOR THE SPRITE CASTING
|
||||||
|
ZBuffer[x] = perpWallDist; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void Raycaster::draw_ceiling_floor() { |
||||||
|
const int textureWidth = textures.TEXTURE_WIDTH; |
||||||
|
const int textureHeight = textures.TEXTURE_HEIGHT; |
||||||
|
|
||||||
|
auto& floorTexture = textures.get(textures.floor); |
||||||
|
auto& ceilingTexture = textures.get(textures.ceiling); |
||||||
|
|
||||||
|
for(int y = $height / 2 + 1; y < $height; ++y) { |
||||||
|
// 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
|
||||||
|
uint32_t color; |
||||||
|
// this uses the previous ty/tx fractional parts of
|
||||||
|
// floorX cellX to find the texture x/y. How?
|
||||||
|
|
||||||
|
// FLOOR
|
||||||
|
color = floorTexture[textureWidth * ty + tx]; |
||||||
|
pixels[pixcoord(x, y)] = color; |
||||||
|
|
||||||
|
// CEILING
|
||||||
|
color = ceilingTexture[textureWidth * ty + tx]; |
||||||
|
pixels[pixcoord(x, $height - y - 1)] = color; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void Raycaster::render() { |
||||||
|
draw_ceiling_floor(); |
||||||
|
cast_rays(); |
||||||
|
sprite_casting(); |
||||||
|
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_x][new_y] == 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); |
||||||
|
} |
@ -0,0 +1,95 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <fmt/core.h> |
||||||
|
#include <SFML/Graphics.hpp> |
||||||
|
#include <SFML/Graphics/Image.hpp> |
||||||
|
#include <numbers> |
||||||
|
#include <algorithm> |
||||||
|
#include <cmath> |
||||||
|
#include "amt/matrix.hpp" |
||||||
|
#include <cstdlib> |
||||||
|
#include <array> |
||||||
|
#include "dbc.hpp" |
||||||
|
#include <memory> |
||||||
|
|
||||||
|
using Matrix = amt::Matrix<int>; |
||||||
|
|
||||||
|
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 RGBA = uint32_t; |
||||||
|
|
||||||
|
struct TexturePack { |
||||||
|
int NUM_SPRITES=1; |
||||||
|
int NUM_TEXTURES=11; |
||||||
|
int TEXTURE_WIDTH=256; // must be power of two
|
||||||
|
int TEXTURE_HEIGHT=256; // must be power of two
|
||||||
|
|
||||||
|
std::vector<std::vector<uint32_t>> images; |
||||||
|
std::vector<Sprite> SPRITE{{4.0, 3.55, 8}}; |
||||||
|
const int floor = 3; |
||||||
|
const int ceiling = 6; |
||||||
|
|
||||||
|
void load_textures(); |
||||||
|
std::vector<uint32_t> load_image(const char *filename); |
||||||
|
Sprite &get_sprite(size_t sprite_num); |
||||||
|
std::vector<uint32_t>& get(size_t num); |
||||||
|
}; |
||||||
|
|
||||||
|
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
|
||||||
|
std::unique_ptr<RGBA[]> pixels = nullptr; |
||||||
|
|
||||||
|
int $width; |
||||||
|
int $height; |
||||||
|
sf::RenderWindow& $window; |
||||||
|
Matrix& $map; |
||||||
|
std::vector<int> spriteOrder; |
||||||
|
std::vector<double> spriteDistance; |
||||||
|
std::vector<double> ZBuffer; // width
|
||||||
|
|
||||||
|
Raycaster(sf::RenderWindow& window, Matrix &map, int width, int 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); |
||||||
|
|
||||||
|
void set_position(int x, int y); |
||||||
|
inline size_t pixcoord(int x, int y) { |
||||||
|
return ((y) * $width) + (x); |
||||||
|
} |
||||||
|
|
||||||
|
}; |
@ -0,0 +1,74 @@ |
|||||||
|
#include <cstdint> |
||||||
|
#include <print> |
||||||
|
#include "matrix.hpp" |
||||||
|
#include "pixel.hpp" |
||||||
|
|
||||||
|
using namespace amt; |
||||||
|
|
||||||
|
int main() { |
||||||
|
|
||||||
|
auto const format = PixelFormat::abgr; |
||||||
|
PixelBuf b(2, 2, RGBA(0x01, 0x02, 0x03, 0x04)); |
||||||
|
b[1][1] = HSLA(280, 20, 50, 80); |
||||||
|
std::println("{:h}", b); |
||||||
|
|
||||||
|
|
||||||
|
std::uint8_t ps[4 * 4] = {}; |
||||||
|
b.copy_to(ps, format); |
||||||
|
|
||||||
|
/*for (auto i = 0zu; i < sizeof(ps); ++i) {*/ |
||||||
|
/* std::println("[{}]: 0x{:0x}", i, ps[i]);*/ |
||||||
|
/*}*/ |
||||||
|
|
||||||
|
PixelBuf test(ps, 2, 2, format); |
||||||
|
|
||||||
|
for (auto i = 0zu; auto color: test) { |
||||||
|
std::println("[{}]: {}", i++, color); |
||||||
|
} |
||||||
|
|
||||||
|
auto m = Matrix<int>{ |
||||||
|
{0, 1}, |
||||||
|
{3, 4} |
||||||
|
}; |
||||||
|
|
||||||
|
std::println("{}", m); |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{ |
||||||
|
auto ca = RGBA::from_hex(0x333333ff); |
||||||
|
auto cb = RGBA::from_hex(0xaabbccff); |
||||||
|
std::println("========= RGBA ==========="); |
||||||
|
std::println("Normal: 0x{:0x}", ca.blend(cb, BlendMode::normal).to_hex()); |
||||||
|
std::println("Multiply: 0x{:0x}", ca.blend(cb, BlendMode::multiply).to_hex()); |
||||||
|
std::println("Screen: 0x{:0x}", ca.blend(cb, BlendMode::screen).to_hex()); |
||||||
|
std::println("Overlay: 0x{:0x}", ca.blend(cb, BlendMode::overlay).to_hex()); |
||||||
|
std::println("darken: 0x{:0x}", ca.blend(cb, BlendMode::darken).to_hex()); |
||||||
|
std::println("lighten: 0x{:0x}", ca.blend(cb, BlendMode::lighten).to_hex()); |
||||||
|
std::println("Dodge: 0x{:0x}", ca.blend(cb, BlendMode::colorDodge).to_hex()); |
||||||
|
std::println("Burn: 0x{:0x}", ca.blend(cb, BlendMode::colorBurn).to_hex()); |
||||||
|
std::println("hard light: 0x{:0x}", ca.blend(cb, BlendMode::hardLight).to_hex()); |
||||||
|
std::println("soft light: 0x{:0x}", ca.blend(cb, BlendMode::softLight).to_hex()); |
||||||
|
std::println("difference: 0x{:0x}", ca.blend(cb, BlendMode::difference).to_hex()); |
||||||
|
std::println("exclusion: 0x{:0x}", ca.blend(cb, BlendMode::exclusion).to_hex()); |
||||||
|
} |
||||||
|
|
||||||
|
{ |
||||||
|
HSLA ca = RGBA::from_hex(0x333333ff); |
||||||
|
HSLA cb = RGBA::from_hex(0xaabbccff); |
||||||
|
std::println("========= HSLA ==========="); |
||||||
|
std::println("Normal: {}", ca.blend(cb, BlendMode::normal)); |
||||||
|
std::println("Multiply: {}", ca.blend(cb, BlendMode::multiply)); |
||||||
|
std::println("Screen: {}", ca.blend(cb, BlendMode::screen)); |
||||||
|
std::println("Overlay: {}", ca.blend(cb, BlendMode::overlay)); |
||||||
|
std::println("darken: {}", ca.blend(cb, BlendMode::darken)); |
||||||
|
std::println("lighten: {}", ca.blend(cb, BlendMode::lighten)); |
||||||
|
std::println("Dodge: {}", ca.blend(cb, BlendMode::colorDodge)); |
||||||
|
std::println("Burn: {}", ca.blend(cb, BlendMode::colorBurn)); |
||||||
|
std::println("hard light: {}", ca.blend(cb, BlendMode::hardLight)); |
||||||
|
std::println("soft light: {}", ca.blend(cb, BlendMode::softLight)); |
||||||
|
std::println("difference: {}", ca.blend(cb, BlendMode::difference)); |
||||||
|
std::println("exclusion: {}", ca.blend(cb, BlendMode::exclusion)); |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue