Brought in some of amit's code to study and try out. amt/ has it.
This commit is contained in:
		
							parent
							
								
									2dfe5417b1
								
							
						
					
					
						commit
						5e63272f24
					
				
					 7 changed files with 1404 additions and 0 deletions
				
			
		
							
								
								
									
										67
									
								
								amt/main.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								amt/main.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||
| } | ||||
							
								
								
									
										184
									
								
								amt/matrix.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								amt/matrix.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||
							
								
								
									
										582
									
								
								amt/pixel.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										582
									
								
								amt/pixel.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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
 | ||||
							
								
								
									
										395
									
								
								amt/raycaster.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										395
									
								
								amt/raycaster.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||
| } | ||||
							
								
								
									
										95
									
								
								amt/raycaster.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								amt/raycaster.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||
|   } | ||||
| 
 | ||||
| }; | ||||
|  | @ -28,3 +28,10 @@ executable('zedcaster', [ | |||
|   'main.cpp' | ||||
|   ], | ||||
|   dependencies: dependencies) | ||||
| 
 | ||||
| executable('amtcaster', [ | ||||
|   'dbc.cpp', | ||||
|   'amt/raycaster.cpp', | ||||
|   'amt/main.cpp' | ||||
|   ], | ||||
|   dependencies: dependencies) | ||||
|  |  | |||
							
								
								
									
										74
									
								
								tests/amt_matrix.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								tests/amt_matrix.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Zed A. Shaw
						Zed A. Shaw