Clean up the source tree before reorganizing it.

This commit is contained in:
Zed A. Shaw 2026-02-26 23:43:30 -05:00
parent 779599f030
commit 03be0884a4
32 changed files with 24 additions and 8208 deletions

2
.gitignore vendored
View file

@ -32,3 +32,5 @@ coverage/*
*.log
gdb.txt
releases
WORK
tmp

20
.ttarpit.json Normal file
View file

@ -0,0 +1,20 @@
{
"StartingHP": 100,
"Deadline": "1h0m0s",
"OneErrorPerLine": true,
"Includes": [
"^.*.cpp$",
"^.*.hpp$",
"^.*.json$"
],
"Processes": {
"tester1": {
"Command": "make",
"Args": ["build"]
}
},
"Triggers": [
"^(?<File>.*?):(?<Line>[0-9]+):(?<Col>[0-9]+):\\s*(?<ErrType>.*):\\s*(?<Message>.*)",
"^(?<File>.*?):(?<Line>[0-9]+):(?<Col>[0-9]+):\\s*(?<Message>.*)"
]
}

BIN
assets/sprites/bonfire.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -13,6 +13,7 @@ cpp_args=[
'-Wno-unused-function',
'-Wno-unused-variable',
'-Wno-unused-but-set-variable',
'-Wno-deprecated-declarations',
]
link_args=[]
# these are passed as override_defaults

View file

@ -1,82 +0,0 @@
constexpr const float SCORE_MAX = std::numeric_limits<float>::max()
using AStarPath = std::deque<Point>;
void update_map(Matrix& map, std::deque<Point>& total_path) {
for(auto &point : total_path) {
map[point.y][point.x] = 10;
}
}
AStarPath reconstruct_path(std::unordered_map<Point, Point>& came_from, Point current) {
std::deque<Point> total_path{current};
while(came_from.contains(current)) {
current = came_from[current];
total_path.push_front(current);
}
return total_path;
}
inline float h(Point from, Point to) {
return std::hypot(float(from.x) - float(to.x),
float(from.y) - float(to.y));
}
inline float d(Point current, Point neighbor) {
return std::hypot(float(current.x) - float(neighbor.x),
float(current.y) - float(neighbor.y));
}
inline Point find_lowest(std::unordered_map<Point, float>& open_set) {
dbc::check(!open_set.empty(), "open set can't be empty in find_lowest");
Point result;
float lowest_score = SCORE_MAX;
for(auto [point, score] : open_set) {
if(score < lowest_score) {
lowest_score = score;
result = point;
}
}
return result;
}
std::optional<AStarPath> path_to_player(Matrix& map, Point start, Point goal) {
std::unordered_map<Point, float> open_set;
std::unordered_map<Point, Point> came_from;
std::unordered_map<Point, float> g_score;
g_score[start] = 0;
open_set[start] = g_score[start] + h(start, goal);
while(!open_set.empty()) {
auto current = find_lowest(open_set);
if(current == goal) {
return std::make_optional<AStarPath>(reconstruct_path(came_from, current));
}
open_set.erase(current);
for(matrix::compass it{map, current.x, current.y}; it.next();) {
Point neighbor{it.x, it.y};
float d_score = d(current, neighbor) + map[it.y][it.x] * SCORE_MAX;
float tentative_g_score = g_score[current] + d_score;
float neighbor_g_score = g_score.contains(neighbor) ? g_score[neighbor] : SCORE_MAX;
if(tentative_g_score < neighbor_g_score) {
came_from[neighbor] = current;
g_score[neighbor] = tentative_g_score;
// open_set gets the fScore
open_set[neighbor] = tentative_g_score + h(neighbor, goal);
}
}
}
return std::nullopt;
}

View file

@ -1,93 +0,0 @@
#include "amt/raycaster.hpp"
#include <iostream>
#include <chrono>
#include <numeric>
#include <functional>
#include "constants.hpp"
#include "stats.hpp"
Matrix MAP{
{1,1,1,1,1,1,1,1,1},
{1,0,2,0,0,0,0,0,1},
{1,0,4,0,0,5,2,0,1},
{1,0,0,0,0,0,0,0,1},
{1,1,0,0,0,0,0,1,1},
{1,0,0,1,3,4,0,0,1},
{1,0,0,0,0,0,1,1,1},
{1,0,0,0,0,0,0,0,1},
{1,1,1,1,1,1,1,1,1}
};
void draw_gui(sf::RenderWindow &window, sf::Text &text, Stats &stats) {
sf::RectangleShape rect({SCREEN_WIDTH - RAY_VIEW_WIDTH, SCREEN_HEIGHT});
rect.setPosition({0,0});
rect.setFillColor({50, 50, 50});
window.draw(rect);
text.setString(
fmt::format("FPS\nmean:{:>8.5}\nsdev: {:>8.5}\nmin: {:>8.5}\nmax: {:>8.5}\ncount:{:<10}\n\nVSync? {}\nDebug? {}\n\nHit R to reset.",
stats.mean(), stats.stddev(), stats.min, stats.max, stats.n, VSYNC, DEBUG_BUILD));
window.draw(text);
}
int main() {
sf::RenderWindow window(sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Zed's Ray Caster Game Thing");
sf::Font font{"./assets/text.otf"};
sf::Text text{font};
text.setFillColor({255,255,255});
text.setPosition({10,10});
//ZED this should set with a function
float player_x = MAP.rows() / 2;
float player_y = MAP.cols() / 2;
Raycaster rayview(window, MAP, RAY_VIEW_WIDTH, RAY_VIEW_HEIGHT);
rayview.set_position(RAY_VIEW_X, RAY_VIEW_Y);
rayview.position_camera(player_x, player_y);
double moveSpeed = 0.1;
double rotSpeed = 0.1;
const auto onClose = [&window](const sf::Event::Closed&)
{
window.close();
};
Stats stats;
// NOTE: Enable this to lock frames at 60
window.setFramerateLimit(60);
while(window.isOpen()) {
auto start = std::chrono::high_resolution_clock::now();
rayview.render();
auto end = std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration<double>(end - start);
stats.sample(1/elapsed.count());
draw_gui(window, text, stats);
window.display();
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::W)) {
rayview.run(moveSpeed, 1);
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::S)) {
rayview.run(moveSpeed, -1);
}
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::D)) {
rayview.rotate(rotSpeed, -1);
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::A)) {
rayview.rotate(rotSpeed, 1);
}
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::R)) {
stats.reset();
}
window.handleEvents(onClose);
}
return 0;
}

View file

@ -1,197 +0,0 @@
#pragma once
#include <cassert>
#include <cstddef>
#include <initializer_list>
#include <iterator>
#include <type_traits>
#include <algorithm>
namespace amt {
namespace detail {
[[nodiscard]] constexpr auto cal_index(
std::size_t r,
std::size_t c,
[[maybe_unused]] std::size_t rs,
[[maybe_unused]] std::size_t cs
) -> std::size_t {
return r * cs + c;
}
}
template <typename T>
struct Matrix {
using value_type = T;
using pointer = value_type*;
using const_pointer = value_type const*;
using reference = value_type&;
using const_reference = value_type const&;
using iterator = pointer;
using const_iterator = const_pointer;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
using difference_type = std::ptrdiff_t;
using size_type = std::size_t;
template <bool IsConst>
struct View {
using base_type = std::conditional_t<IsConst, const_pointer, pointer>;
base_type data;
size_type r;
size_type rows;
size_type cols;
constexpr reference operator[](size_type c) noexcept requires (!IsConst) {
assert(c < cols && "Out of bound access");
auto const index = detail::cal_index(r, c, rows, cols);
return data[index];
}
constexpr const_reference operator[](size_type c) const noexcept {
assert(c < cols && "Out of bound access");
auto const index = detail::cal_index(r, c, rows, cols);
return data[index];
}
};
constexpr Matrix() noexcept = default;
Matrix(Matrix const& other)
: Matrix(other.rows(), other.cols())
{
std::copy(other.begin(), other.end(), begin());
}
Matrix& operator=(Matrix const& other) {
if (this == &other) return *this;
auto temp = Matrix(other);
swap(temp, *this);
return *this;
}
constexpr Matrix(Matrix && other) noexcept
: m_data(other.m_data)
, m_row(other.m_row)
, m_col(other.m_col)
{
other.m_data = nullptr;
}
constexpr Matrix& operator=(Matrix && other) noexcept {
if (this == &other) return *this;
swap(*this, other);
return *this;
}
~Matrix() {
if (m_data) delete[] m_data;
}
Matrix(size_type row, size_type col)
: m_data(new value_type[row * col])
, m_row(row)
, m_col(col)
{}
Matrix(size_type row, size_type col, value_type def)
: Matrix(row, col)
{
std::fill(begin(), end(), def);
}
Matrix(std::initializer_list<std::initializer_list<value_type>> li)
: m_row(li.size())
{
for (auto const& row: li) {
m_col = std::max(m_col, row.size());
}
auto const size = m_row * m_col;
if (size == 0) return;
m_data = new value_type[size];
std::fill_n(m_data, size, 0);
for (auto r = 0ul; auto const& row: li) {
for (auto c = 0ul; auto const& col: row) {
this->operator()(r, c++) = col;
}
++r;
}
}
constexpr bool empty() const noexcept { return size() == 0; }
constexpr size_type size() const noexcept { return rows() * cols(); }
constexpr size_type rows() const noexcept { return m_row; }
constexpr size_type cols() const noexcept { return m_col; }
constexpr auto data() noexcept -> pointer { return m_data; }
constexpr auto data() const noexcept -> const_pointer { return m_data; }
constexpr iterator begin() noexcept { return m_data; }
constexpr iterator end() noexcept { return m_data + size(); }
constexpr const_iterator begin() const noexcept { return m_data; }
constexpr const_iterator end() const noexcept { return m_data + size(); }
constexpr reverse_iterator rbegin() noexcept { return std::reverse_iterator(end()); }
constexpr reverse_iterator rend() noexcept { return std::reverse_iterator(begin()); }
constexpr const_reverse_iterator rbegin() const noexcept { return std::reverse_iterator(end()); }
constexpr const_reverse_iterator rend() const noexcept { return std::reverse_iterator(begin()); }
constexpr auto operator()(size_type r, size_type c) noexcept -> reference {
auto const index = detail::cal_index(r, c, rows(), cols());
assert(index < size() && "Out of bound access");
return m_data[index];
}
constexpr auto operator()(size_type r, size_type c) const noexcept -> const_reference {
auto const index = detail::cal_index(r, c, rows(), cols());
assert(index < size() && "Out of bound access");
return m_data[index];
}
constexpr auto operator[](size_type r) noexcept -> View<false> {
assert(r < rows() && "Out of bound access");
return { .data = m_data, .r = r, .rows = m_row, .cols = m_col };
}
constexpr auto operator[](size_type r) const noexcept -> View<true> {
assert(r < rows() && "Out of bound access");
return { .data = m_data, .r = r, .rows = m_row, .cols = m_col };
}
friend void swap(Matrix& lhs, Matrix& rhs) noexcept {
using std::swap;
swap(lhs.m_data, rhs.m_data);
swap(lhs.m_row, rhs.m_row);
swap(lhs.m_col, rhs.m_col);
}
private:
pointer m_data{};
size_type m_row{};
size_type m_col{};
};
} // namespace amt
#if 0
#include <format>
namespace std {
template <typename T>
struct formatter<amt::Matrix<T>> {
constexpr auto parse(format_parse_context& ctx) {
return ctx.begin();
}
auto format(amt::Matrix<T> const& m, auto& ctx) const {
std::string s = "[\n";
for (auto r = std::size_t{}; r < m.rows(); ++r) {
for (auto c = std::size_t{}; c < m.cols(); ++c) {
s += std::format("{}, ", m(r, c));
}
s += '\n';
}
s += "]";
return format_to(ctx.out(), "{}", s);
}
};
} // namespace std
#endif

View file

@ -1,746 +0,0 @@
#ifndef AMT_PIXEL_HPP
#define AMT_PIXEL_HPP
#include "matrix.hpp"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <string>
#include <stdexcept>
#include <type_traits>
namespace amt {
enum class PixelFormat {
rgba,
abgr,
rgb ,
bgr ,
ga , // gray scale and alpha
ag , // alpha and gray scale
g // gray scale
};
inline static constexpr auto get_pixel_format_from_channel(std::size_t c, bool little_endian = false) -> PixelFormat {
switch (c) {
case 1: return PixelFormat::g;
case 2: return little_endian ? PixelFormat::ag : PixelFormat::ga;
case 3: return little_endian ? PixelFormat::bgr : PixelFormat::rgb;
case 4: return little_endian ? PixelFormat::abgr : PixelFormat::abgr;
}
throw std::runtime_error(std::string("get_pixel_format_from_channel: unknown channel ") + std::to_string(c));
}
namespace detail {
static constexpr auto compare_float(float l, float r) noexcept -> bool {
return std::abs(l - r) < std::numeric_limits<float>::epsilon();
}
} // namespace detail
enum class BlendMode {
normal,
multiply,
screen,
overlay,
darken,
lighten,
colorDodge,
colorBurn,
hardLight,
softLight,
difference,
exclusion
};
struct RGBA {
using pixel_t = std::uint8_t;
using pixels_t = std::uint32_t;
constexpr RGBA() noexcept = default;
constexpr RGBA(RGBA const&) noexcept = default;
constexpr RGBA(RGBA &&) noexcept = default;
constexpr RGBA& operator=(RGBA const&) noexcept = default;
constexpr RGBA& operator=(RGBA &&) noexcept = default;
constexpr ~RGBA() noexcept = default;
// NOTE: Accepts RRGGBBAA
explicit constexpr RGBA(pixels_t color) noexcept
: RGBA((color >> (8 * 3)) & 0xff, (color >> (8 * 2)) & 0xff, (color >> (8 * 1)) & 0xff, (color >> (8 * 0)) & 0xff)
{}
constexpr RGBA(pixel_t r, pixel_t g, pixel_t b, pixel_t a = 0xff) noexcept
: m_data {r, g, b, a}
{}
constexpr RGBA(pixel_t color, pixel_t a = 0xff) noexcept
: RGBA(color, color, color, a)
{}
// NOTE: Returns RRGGBBAA
constexpr auto to_hex() const noexcept -> pixels_t {
auto r = static_cast<pixels_t>(this->r());
auto b = static_cast<pixels_t>(this->b());
auto g = static_cast<pixels_t>(this->g());
auto a = static_cast<pixels_t>(this->a());
return (r << (8 * 3)) | (g << (8 * 2)) | (b << (8 * 1)) | (a << (8 * 0));
}
constexpr auto r() const noexcept -> pixel_t { return m_data[0]; }
constexpr auto g() const noexcept -> pixel_t { return m_data[1]; }
constexpr auto b() const noexcept -> pixel_t { return m_data[2]; }
constexpr auto a() const noexcept -> pixel_t { return m_data[3]; }
constexpr auto r() noexcept -> pixel_t& { return m_data[0]; }
constexpr auto g() noexcept -> pixel_t& { return m_data[1]; }
constexpr auto b() noexcept -> pixel_t& { return m_data[2]; }
constexpr auto a() noexcept -> pixel_t& { return m_data[3]; }
/**
* @returns the value is between 0 and 1
*/
constexpr auto brightness() const noexcept -> float {
// 0.299*R + 0.587*G + 0.114*B
auto tr = normalize(r());
auto tg = normalize(g());
auto tb = normalize(b());
return (0.299 * tr + 0.587 * tg + 0.114 * tb);
}
template <typename T>
requires std::is_arithmetic_v<T>
constexpr auto operator/(T val) const noexcept {
auto d = static_cast<float>(val);
return RGBA(
static_cast<pixel_t>(r() / d),
static_cast<pixel_t>(g() / d),
static_cast<pixel_t>(b() / d),
a()
);
}
template <typename T>
requires std::is_arithmetic_v<T>
constexpr auto operator*(T val) const noexcept {
auto d = static_cast<float>(val);
return RGBA(
static_cast<pixel_t>(r() * d),
static_cast<pixel_t>(g() * d),
static_cast<pixel_t>(b() * d),
a()
);
}
private:
static constexpr auto normalize(pixel_t p) noexcept -> float {
return float(p) / 255;
}
static constexpr auto to_pixel(float p) noexcept -> pixel_t {
return static_cast<pixel_t>(p * 255);
}
template <BlendMode M>
static constexpr auto blend_helper() noexcept {
constexpr auto mix_helper = [](float s, float b, float a) -> float {
// (1 - αb) x Cs + αb x B(Cb, Cs)
return (1 - a) * s + a * b;
};
if constexpr (M == BlendMode::normal) {
return [mix_helper](float bg, float fg, float alpha) { return mix_helper(bg, fg, alpha); };
} else if constexpr (M == BlendMode::multiply) {
return [mix_helper](float bg, float fg, float alpha) { return mix_helper(bg, bg * fg, alpha); };
} else if constexpr (M == BlendMode::screen) {
return [mix_helper](float bg, float fg, float alpha) {
// Cb + Cs -(Cb x Cs)
auto bf = bg + fg - (bg * fg);
return mix_helper(bg, bf, alpha);
};
} else if constexpr (M == BlendMode::overlay) {
return [mix_helper](float bg, float fg, float alpha, auto&& hard_light_fn, auto&& multiply_fn, auto&& screen_fn) {
// HardLight(Cs, Cb)
auto hl = hard_light_fn(bg, fg, alpha, multiply_fn, screen_fn);
return mix_helper(bg, hl, alpha);
};
} else if constexpr (M == BlendMode::darken) {
return [mix_helper](float bg, float fg, float alpha) {
return mix_helper(bg, std::min(bg, fg), alpha);
};
} else if constexpr (M == BlendMode::lighten) {
return [mix_helper](float bg, float fg, float alpha) {
return mix_helper(bg, std::max(bg, fg), alpha);
};
} else if constexpr (M == BlendMode::colorDodge) {
return [mix_helper](float bg, float fg, float alpha) {
constexpr auto fn = [](float b, float s) -> float {
if (b == 0) return 0;
if (s == 255) return 255;
return std::min(1.f, b / (1.f - s));
};
auto bf = fn(bg, fg);
return mix_helper(bg, bf, alpha);
};
} else if constexpr (M == BlendMode::colorBurn) {
return [mix_helper](float bg, float fg, float alpha) {
constexpr auto fn = [](float b, float s) -> float {
if (b == 255) return 255;
if (s == 0) return 0;
return 1.f - std::min(1.f, (1.f - b) / s);
};
auto bf = fn(bg, fg);
return mix_helper(bg, bf, alpha);
};
} else if constexpr (M == BlendMode::hardLight) {
return [mix_helper](float bg, float fg, float alpha, auto&& multiply_fn, auto&& screen_fn) {
auto fn = [&multiply_fn, &screen_fn](float b, float s, float a) -> float {
if (s <= 0.5f) {
return multiply_fn(b, 2.f * s, a);
} else {
return screen_fn(b, 2.f * s - 1.f, a);
}
};
auto bf = fn(bg, fg, alpha);
return mix_helper(bg, bf, alpha);
};
} else if constexpr (M == BlendMode::softLight) {
return [mix_helper](float bg, float fg, float alpha) {
constexpr auto fn = [](float b, float s) -> float {
if (s <= 0.5f) {
// B(Cb, Cs) = Cb - (1 - 2 x Cs) x Cb x (1 - Cb)
return b - (1.f - 2.f * s) * b * (1 - b);
} else {
float d{};
if (b <= 0.5f) {
// D(Cb) = ((16 * Cb - 12) x Cb + 4) x Cb
d = ((16 * b - 12) * b + 4) * b;
} else {
// D(Cb) = sqrt(Cb)
d = std::sqrt(b);
}
// B(Cb, Cs) = Cb + (2 x Cs - 1) x (D(Cb) - Cb)
return b + (2 * s - 1) * (d - b);
}
};
auto bf = fn(bg, fg);
return mix_helper(bg, bf, alpha);
};
} else if constexpr (M == BlendMode::difference) {
return [mix_helper](float bg, float fg, float alpha) {
// B(Cb, Cs) = | Cb - Cs |
return mix_helper(bg, (bg > fg ? (bg - fg) : (fg - bg)), alpha);
};
} else if constexpr (M == BlendMode::exclusion) {
return [mix_helper](float bg, float fg, float alpha) {
constexpr auto fn = [](float b, float s) -> float {
// B(Cb, Cs) = Cb + Cs - 2 x Cb x Cs
return b + s - 2 * b * s;
};
auto bf = fn(bg, fg);
return mix_helper(bg, bf, alpha);
};
}
};
public:
template <BlendMode M>
constexpr auto blend(RGBA color) const noexcept -> RGBA {
auto ab = normalize(a());
auto as = normalize(color.a());
// αs x 1 + αb x (1 αs)
auto alpha = to_pixel(as + ab * (1 - as));
auto lr = normalize(r());
auto lg = normalize(g());
auto lb = normalize(b());
auto rr = normalize(color.r());
auto rg = normalize(color.g());
auto rb = normalize(color.b());
auto nr = 0.f;
auto ng = 0.f;
auto nb = 0.f;
if constexpr (M == BlendMode::normal) {
auto fn = blend_helper<BlendMode::normal>();
nr = fn(lr, rr, ab);
ng = fn(lg, rg, ab);
nb = fn(lb, rb, ab);
} else if constexpr (M == BlendMode::multiply) {
auto fn = blend_helper<BlendMode::multiply>();
nr = fn(lr, rr, ab);
ng = fn(lg, rg, ab);
nb = fn(lb, rb, ab);
} else if constexpr (M == BlendMode::screen) {
auto fn = blend_helper<BlendMode::screen>();
nr = fn(lr, rr, ab);
ng = fn(lg, rg, ab);
nb = fn(lb, rb, ab);
} else if constexpr (M == BlendMode::overlay) {
auto fn = blend_helper<BlendMode::overlay>();
auto hard_light_fn = blend_helper<BlendMode::hardLight>();
auto multiply_fn = blend_helper<BlendMode::multiply>();
auto screen_fn = blend_helper<BlendMode::screen>();
nr = fn(lr, rr, ab, hard_light_fn, multiply_fn, screen_fn);
ng = fn(lg, rg, ab, hard_light_fn, multiply_fn, screen_fn);
nb = fn(lb, rb, ab, hard_light_fn, multiply_fn, screen_fn);
} else if constexpr (M == BlendMode::darken) {
auto fn = blend_helper<BlendMode::darken>();
nr = fn(lr, rr, ab);
ng = fn(lg, rg, ab);
nb = fn(lb, rb, ab);
} else if constexpr (M == BlendMode::lighten) {
auto fn = blend_helper<BlendMode::lighten>();
nr = fn(lr, rr, ab);
ng = fn(lg, rg, ab);
nb = fn(lb, rb, ab);
} else if constexpr (M == BlendMode::colorDodge) {
auto fn = blend_helper<BlendMode::colorDodge>();
nr = fn(lr, rr, ab);
ng = fn(lg, rg, ab);
nb = fn(lb, rb, ab);
} else if constexpr (M == BlendMode::colorBurn) {
auto fn = blend_helper<BlendMode::colorDodge>();
nr = fn(lr, rr, ab);
ng = fn(lg, rg, ab);
nb = fn(lb, rb, ab);
} else if constexpr (M == BlendMode::hardLight) {
auto fn = blend_helper<BlendMode::hardLight>();
auto multiply_fn = blend_helper<BlendMode::multiply>();
auto screen_fn = blend_helper<BlendMode::screen>();
nr = fn(lr, rr, ab, multiply_fn, screen_fn);
ng = fn(lg, rg, ab, multiply_fn, screen_fn);
nb = fn(lb, rb, ab, multiply_fn, screen_fn);
} else if constexpr (M == BlendMode::softLight) {
auto fn = blend_helper<BlendMode::softLight>();
nr = fn(lr, rr, ab);
ng = fn(lg, rg, ab);
nb = fn(lb, rb, ab);
} else if constexpr (M == BlendMode::difference) {
auto fn = blend_helper<BlendMode::difference>();
nr = fn(lr, rr, ab);
ng = fn(lg, rg, ab);
nb = fn(lb, rb, ab);
} else if constexpr (M == BlendMode::exclusion) {
auto fn = blend_helper<BlendMode::exclusion>();
nr = fn(lr, rr, ab);
ng = fn(lg, rg, ab);
nb = fn(lb, rb, ab);
}
return RGBA(
to_pixel(nr),
to_pixel(ng),
to_pixel(nb),
alpha
);
}
constexpr auto blend(RGBA color, BlendMode mode) const noexcept -> RGBA {
switch (mode) {
case BlendMode::normal: return blend<BlendMode::normal>(color);
case BlendMode::multiply: return blend<BlendMode::multiply>(color);
case BlendMode::screen: return blend<BlendMode::screen>(color);
case BlendMode::overlay: return blend<BlendMode::overlay>(color);
case BlendMode::darken: return blend<BlendMode::darken>(color);
case BlendMode::lighten: return blend<BlendMode::lighten>(color);
case BlendMode::colorDodge: return blend<BlendMode::colorDodge>(color);
case BlendMode::colorBurn: return blend<BlendMode::colorBurn>(color);
case BlendMode::hardLight: return blend<BlendMode::hardLight>(color);
case BlendMode::softLight: return blend<BlendMode::softLight>(color);
case BlendMode::difference: return blend<BlendMode::difference>(color);
case BlendMode::exclusion: return blend<BlendMode::exclusion>(color);
}
}
private:
pixel_t m_data[4]{};
};
struct HSLA {
using pixel_t = float;
using pixels_t = float[4];
// ensures pixel to be in range
template <unsigned min, unsigned max>
struct PixelWrapper {
pixel_t& p;
constexpr PixelWrapper& operator=(float val) noexcept {
p = std::clamp(val, float(min), float(max));
}
constexpr operator pixel_t() const noexcept { return p; }
};
constexpr HSLA() noexcept = default;
constexpr HSLA(HSLA const&) noexcept = default;
constexpr HSLA(HSLA &&) noexcept = default;
constexpr HSLA& operator=(HSLA const&) noexcept = default;
constexpr HSLA& operator=(HSLA &&) noexcept = default;
constexpr ~HSLA() noexcept = default;
constexpr HSLA(pixel_t h, pixel_t s, pixel_t l, pixel_t a = 100) noexcept
: m_data({ .hsla = { .h = h, .s = s, .l = l, .a = a } })
{}
constexpr HSLA(RGBA color) noexcept {
auto min = std::min({color.r(), color.g(), color.b()});
auto max = std::max({color.r(), color.g(), color.b()});
auto c = (max - min) / 255.f;
auto tr = float(color.r()) / 255;
auto tg = float(color.g()) / 255;
auto tb = float(color.b()) / 255;
auto ta = float(color.a()) / 255;
float hue = 0;
float s = 0;
auto l = ((max + min) / 2.f) / 255.f;
if (min == max) {
if (max == color.r()) {
auto seg = (tg - tb) / c;
auto shift = (seg < 0 ? 360.f : 0.f) / 60.f;
hue = seg + shift;
} else if (max == color.g()) {
auto seg = (tb - tr) / c;
auto shift = 120.f / 60.f;
hue = seg + shift;
} else {
auto seg = (tr - tg) / c;
auto shift = 240.f / 60.f;
hue = seg + shift;
}
s = c / (1 - std::abs(2 * l - 1));
}
hue = hue * 60.f + 360.f;
auto q = static_cast<float>(static_cast<int>(hue / 360.f));
hue -= q * 360.f;
m_data.hsla.h = hue;
m_data.hsla.s = s * 100.f;
m_data.hsla.l = l * 100.f;
m_data.hsla.a = ta * 100.f;
}
constexpr operator RGBA() const noexcept {
auto ts = s() / 100.f;
auto tl = l() / 100.f;
auto ta = a() / 100.f;
if (s() == 0) return RGBA(to_pixel(tl), to_pixel(ta));
auto th = h() / 360.f;
float const q = tl < 0.5 ? tl * (1 + ts) : tl + ts - tl * ts;
float const p = 2 * tl - q;
return RGBA(
to_pixel(convert_hue(p, q, th + 1.f / 3)),
to_pixel(convert_hue(p, q, th)),
to_pixel(convert_hue(p, q, th - 1.f / 3)),
to_pixel(ta)
);
}
constexpr auto blend(HSLA color, BlendMode mode) const noexcept -> HSLA {
auto lhs = RGBA(*this);
auto rhs = RGBA(color);
return HSLA(lhs.blend(rhs, mode));
}
constexpr auto h() const noexcept -> pixel_t { return m_data.hsla.h; }
constexpr auto s() const noexcept -> pixel_t { return m_data.hsla.s; }
constexpr auto l() const noexcept -> pixel_t { return m_data.hsla.l; }
constexpr auto a() const noexcept -> pixel_t { return m_data.hsla.a; }
constexpr auto h() noexcept -> PixelWrapper<0, 360> { return { m_data.hsla.h }; }
constexpr auto s() noexcept -> PixelWrapper<0, 100> { return { m_data.hsla.s }; }
constexpr auto l() noexcept -> PixelWrapper<0, 100> { return { m_data.hsla.l }; }
constexpr auto a() noexcept -> PixelWrapper<0, 100> { return { m_data.hsla.a }; }
private:
static constexpr auto to_pixel(float a) noexcept -> RGBA::pixel_t {
return static_cast<RGBA::pixel_t>(a * 255);
}
static constexpr auto convert_hue(float p, float q, float t) noexcept -> float {
t = t - (t > 1) + (t < 0);
if (t * 6 < 1) return p + (q - p) * 6 * t;
if (t * 2 < 1) return q;
if (t * 3 < 2) return p + (q - p) * (2.f / 3 - t) * 6;
return p;
}
private:
union {
struct {
pixel_t h{}; // hue: 0-360
pixel_t s{}; // saturation: 0-100%
pixel_t l{}; // lightness: 0-100%
pixel_t a{}; // alpha: 0-100%
} hsla;
pixels_t color;
} m_data{};
};
namespace detail {
template <PixelFormat F>
inline static constexpr auto parse_pixel_helper(RGBA color, std::uint8_t* out_ptr) noexcept {
if constexpr (F == PixelFormat::rgba) {
out_ptr[0] = color.r();
out_ptr[1] = color.g();
out_ptr[2] = color.b();
out_ptr[3] = color.a();
} else if constexpr (F == PixelFormat::abgr) {
out_ptr[0] = color.a();
out_ptr[1] = color.b();
out_ptr[2] = color.g();
out_ptr[3] = color.r();
} else if constexpr (F == PixelFormat::rgb) {
out_ptr[0] = color.r();
out_ptr[1] = color.g();
out_ptr[2] = color.b();
} else if constexpr (F == PixelFormat::bgr) {
out_ptr[0] = color.b();
out_ptr[1] = color.g();
out_ptr[2] = color.r();
} else if constexpr (F == PixelFormat::ga) {
out_ptr[0] = color.r();
out_ptr[1] = color.a();
} else if constexpr (F == PixelFormat::ag) {
out_ptr[0] = color.a();
out_ptr[1] = color.r();
} else {
out_ptr[0] = color.r();
}
}
template <PixelFormat F>
inline static constexpr auto parse_pixel_helper(std::uint8_t const* in_ptr) noexcept -> RGBA {
if constexpr (F == PixelFormat::rgba) {
return {
in_ptr[0],
in_ptr[1],
in_ptr[2],
in_ptr[3]
};
} else if constexpr (F == PixelFormat::abgr) {
return {
in_ptr[3],
in_ptr[2],
in_ptr[1],
in_ptr[0]
};
} else if constexpr (F == PixelFormat::rgb) {
return {
in_ptr[0],
in_ptr[1],
in_ptr[2],
0xff
};
} else if constexpr (F == PixelFormat::bgr) {
return {
in_ptr[2],
in_ptr[1],
in_ptr[0],
0xff
};
} else if constexpr (F == PixelFormat::ga) {
return {
in_ptr[0],
in_ptr[1],
};
} else if constexpr (F == PixelFormat::ag) {
return {
in_ptr[1],
in_ptr[0],
};
} else {
return { in_ptr[0], 0xff };
}
}
} // namespace detail
inline static constexpr auto get_pixel_format_channel(PixelFormat format) noexcept -> std::size_t {
switch (format) {
case PixelFormat::rgba: case PixelFormat::abgr: return 4u;
case PixelFormat::rgb: case PixelFormat::bgr: return 3u;
case PixelFormat::ga: case PixelFormat::ag: return 2u;
case PixelFormat::g: return 1u;
}
assert(false && "unreachable");
}
struct PixelBuf {
private:
public:
using value_type = RGBA;
using base_type = Matrix<value_type>;
using pointer = typename base_type::pointer;
using const_pointer = typename base_type::const_pointer;
using reference = typename base_type::reference;
using const_reference = typename base_type::const_reference;
using iterator = typename base_type::iterator;
using const_iterator = typename base_type::const_iterator;
using reverse_iterator = typename base_type::reverse_iterator;
using const_reverse_iterator = typename base_type::const_reverse_iterator;
using difference_type = typename base_type::difference_type;
using size_type = typename base_type::size_type;
PixelBuf(size_type r, size_type c)
: m_data(r, c)
{}
PixelBuf(size_type r, size_type c, RGBA color)
: m_data(r, c, color)
{}
PixelBuf(std::uint8_t const* in, size_type r, size_type c, PixelFormat format = PixelFormat::rgba)
: PixelBuf(r, c)
{
assert(in != nullptr);
switch (format) {
case PixelFormat::rgba: from_helper<PixelFormat::rgba>(in, data(), size()); break;
case PixelFormat::abgr: from_helper<PixelFormat::abgr>(in, data(), size()); break;
case PixelFormat::rgb: from_helper<PixelFormat::rgb >(in, data(), size()); break;
case PixelFormat::bgr: from_helper<PixelFormat::bgr >(in, data(), size()); break;
case PixelFormat::ga: from_helper<PixelFormat::ga >(in, data(), size()); break;
case PixelFormat::ag: from_helper<PixelFormat::ag >(in, data(), size()); break;
case PixelFormat::g: from_helper<PixelFormat::g >(in, data(), size()); break;
}
}
PixelBuf() noexcept = default;
PixelBuf(PixelBuf const&) = default;
PixelBuf(PixelBuf &&) noexcept = default;
PixelBuf& operator=(PixelBuf const&) = default;
PixelBuf& operator=(PixelBuf &&) noexcept = default;
~PixelBuf() = default;
constexpr auto size() const noexcept -> size_type { return m_data.size(); }
constexpr auto rows() const noexcept -> size_type { return m_data.rows(); }
constexpr auto cols() const noexcept -> size_type { return m_data.cols(); }
constexpr auto data() noexcept -> pointer { return m_data.data(); }
constexpr auto data() const noexcept -> const_pointer { return m_data.data(); }
auto to_raw_buf() noexcept -> std::uint8_t* { return reinterpret_cast<std::uint8_t*>(data()); }
auto to_raw_buf() const noexcept -> std::uint8_t const* { return reinterpret_cast<std::uint8_t const*>(data()); }
constexpr auto raw_buf_size() const noexcept { return size() * sizeof(RGBA); }
constexpr auto begin() noexcept -> iterator { return m_data.begin(); }
constexpr auto end() noexcept -> iterator { return m_data.end(); }
constexpr auto begin() const noexcept -> const_iterator { return m_data.begin(); }
constexpr auto end() const noexcept -> const_iterator { return m_data.end(); }
constexpr auto rbegin() noexcept -> reverse_iterator { return m_data.rbegin(); }
constexpr auto rend() noexcept -> reverse_iterator { return m_data.rend(); }
constexpr auto rbegin() const noexcept -> const_reverse_iterator { return m_data.rbegin(); }
constexpr auto rend() const noexcept -> const_reverse_iterator { return m_data.rend(); }
constexpr decltype(auto) operator[](size_type r) noexcept { return m_data[r]; }
constexpr decltype(auto) operator[](size_type r) const noexcept { return m_data[r]; }
constexpr auto operator()(size_type r, size_type c) noexcept -> reference { return m_data(r, c); }
constexpr auto operator()(size_type r, size_type c) const noexcept -> const_reference { return m_data(r, c); }
constexpr auto fill(RGBA color) noexcept -> void {
std::fill(begin(), end(), color);
}
constexpr auto copy_to(std::uint8_t* out, PixelFormat format = PixelFormat::rgba) const noexcept {
assert(out != nullptr);
switch (format) {
case PixelFormat::rgba: copy_to_helper<PixelFormat::rgba>(data(), out, size());return;
case PixelFormat::abgr: copy_to_helper<PixelFormat::abgr>(data(), out, size());return;
case PixelFormat::rgb: copy_to_helper<PixelFormat::rgb >(data(), out, size());return;
case PixelFormat::bgr: copy_to_helper<PixelFormat::bgr >(data(), out, size());return;
case PixelFormat::ga: copy_to_helper<PixelFormat::ga >(data(), out, size());return;
case PixelFormat::ag: copy_to_helper<PixelFormat::ag >(data(), out, size());return;
case PixelFormat::g: copy_to_helper<PixelFormat::g >(data(), out, size());return;
}
assert(false && "unreachable");
}
private:
template <PixelFormat F>
constexpr auto copy_to_helper(const_pointer in, std::uint8_t* out, size_type size) const noexcept -> void {
constexpr auto channels = get_pixel_format_channel(F);
for (auto i = size_type{}; i < size; ++i) {
detail::parse_pixel_helper<F>(in[i], out + i * channels);
}
}
template <PixelFormat F>
constexpr auto from_helper(std::uint8_t const* in, pointer out, size_type size) const noexcept -> void {
constexpr auto channels = get_pixel_format_channel(F);
for (auto i = size_type{}; i < size; ++i) {
out[i] = detail::parse_pixel_helper<F>(in + i * channels);
}
}
private:
base_type m_data;
};
} // namespace amt
#include <format>
namespace std {
template <>
struct formatter<amt::RGBA> {
constexpr auto parse(format_parse_context& ctx) {
return ctx.begin();
}
auto format(amt::RGBA const& color, auto& ctx) const {
return format_to(ctx.out(), "rgba({}, {}, {}, {})", color.r(), color.g(), color.b(), color.a());
}
};
template <>
struct formatter<amt::HSLA> {
constexpr auto parse(format_parse_context& ctx) {
return ctx.begin();
}
auto format(amt::HSLA const& color, auto& ctx) const {
return format_to(ctx.out(), "hsla({:.1f}deg, {:.1f}%, {:.1f}%, {:.1f}%)", color.h(), color.s(), color.l(), color.a());
}
};
template <>
struct formatter<amt::PixelBuf> {
bool hsla = false;
constexpr auto parse(format_parse_context& ctx) {
auto it = ctx.begin();
while (it != ctx.end() && *it != '}') {
if (*it == 'h') hsla = true;
++it;
}
return it;
}
auto format(amt::PixelBuf const& buf, auto& ctx) const {
std::string s = "[\n";
for (auto r = std::size_t{}; r < buf.rows(); ++r) {
for (auto c = std::size_t{}; c < buf.cols(); ++c) {
auto color = buf(r, c);
if (hsla) s += std::format("{}, ", amt::HSLA(color));
else s += std::format("{}, ", color);
}
s += '\n';
}
s += "]";
return format_to(ctx.out(), "{}", s);
}
};
} // namespace std
#endif // AMT_PIXEL_HPP

View file

@ -1,417 +0,0 @@
#include "amt/raycaster.hpp"
#include "amt/texture.hpp"
#include "amt/pixel.hpp"
#include "constants.hpp"
#include "thread.hpp"
#define AMT_LIGHT
using namespace fmt;
#ifdef AMT_LIGHT
static constexpr auto room_brightness = 0.3f; // increse this to increase the room brightness. Higher value means brighter room.
inline static constexpr amt::RGBA dumb_lighting(amt::RGBA pixel, float distance, float distance_from_center) {
auto const dim_pixel = pixel * room_brightness;
if (distance_from_center >= 0) {
auto const min_brightness = 1.f / std::max(distance_from_center, 0.5f); // farther away from the center darker it gets
auto const max_brightness = 1.f; // brighness should not exceed 1
auto const pixel_brightness = std::max(min_brightness, std::min(max_brightness, distance));
auto const yellow_brightness = float(distance_from_center * 60);
amt::RGBA const yellow = amt::HSLA(40, 20, yellow_brightness);
auto temp = (pixel / pixel_brightness).blend<amt::BlendMode::softLight>(yellow);
return temp.brightness() < 0.1f ? dim_pixel : temp;
} else {
return dim_pixel;
}
}
#else
inline static constexpr amt::RGBA dumb_lighting(amt::RGBA pixel, double distance, double distance_from_center) {
(void)distance_from_center;
if(distance < 0.9) return pixel;
return pixel / distance;
}
#endif
Raycaster::Raycaster(sf::RenderWindow& window, Matrix &map, unsigned width, unsigned height) :
view_texture(sf::Vector2u{width, height}),
view_sprite(view_texture),
$width(static_cast<int>(width)),
$height(static_cast<int>(height)),
pixels(height, width),
$window(window),
$map(map),
spriteOrder(textures.NUM_SPRITES),
spriteDistance(textures.NUM_SPRITES),
ZBuffer(width),
$radius(std::min($height, $width) / 2),
$r_sq($radius * $radius)
{
$window.setVerticalSyncEnabled(VSYNC);
view_sprite.setPosition({0, 0});
textures.load_textures();
}
void Raycaster::set_position(int x, int y) {
view_sprite.setPosition({(float)x, (float)y});
}
void Raycaster::position_camera(float player_x, float player_y) {
// x and y start position
posX = player_x;
posY = player_y;
}
void Raycaster::draw_pixel_buffer() {
view_texture.update(pixels.to_raw_buf(), {(unsigned int)$width, (unsigned int)$height}, {0, 0});
$window.draw(view_sprite);
}
void Raycaster::clear() {
pixels.fill({});
$window.clear();
}
void Raycaster::sprite_casting() {
// sort sprites from far to close
for(int i = 0; i < textures.NUM_SPRITES; i++) {
auto& sprite = textures.get_sprite(i);
spriteOrder[i] = i;
// this is just the distance calculation
spriteDistance[i] = ((posX - sprite.x) *
(posX - sprite.x) +
(posY - sprite.y) *
(posY - sprite.y));
}
sort_sprites(spriteOrder, spriteDistance, textures.NUM_SPRITES);
/*for(int i = 0; i < textures.NUM_SPRITES; i++) {*/
// after sorting the sprites, do the projection
// Be careful about capturing stack variables.
amt::parallel_for<1>(pool, 0, textures.NUM_SPRITES, [this, textureWidth = textures.TEXTURE_WIDTH, textureHeight = textures.TEXTURE_HEIGHT](size_t i){
int sprite_index = spriteOrder[i];
Sprite& sprite_rec = textures.get_sprite(sprite_index);
auto& sprite_texture = textures.get_texture(sprite_rec.texture);
double spriteX = sprite_rec.x - posX;
double spriteY = sprite_rec.y - posY;
//transform sprite with the inverse camera matrix
// [ planeX dirX ] -1 [ dirY -dirX ]
// [ ] = 1/(planeX*dirY-dirX*planeY) * [ ]
// [ planeY dirY ] [ -planeY planeX ]
double invDet = 1.0 / (planeX * dirY - dirX * planeY); // required for correct matrix multiplication
double transformX = invDet * (dirY * spriteX - dirX * spriteY);
//this is actually the depth inside the screen, that what Z is in 3D, the distance of sprite to player, matching sqrt(spriteDistance[i])
double transformY = invDet * (-planeY * spriteX + planeX * spriteY);
int spriteScreenX = int(($width / 2) * (1 + transformX / transformY));
int vMoveScreen = int(sprite_rec.elevation * -1 / transformY);
// calculate the height of the sprite on screen
//using "transformY" instead of the real distance prevents fisheye
int spriteHeight = abs(int($height / transformY)) / sprite_rec.vDiv;
//calculate lowest and highest pixel to fill in current stripe
int drawStartY = -spriteHeight / 2 + $height / 2 + vMoveScreen;
if(drawStartY < 0) drawStartY = 0;
int drawEndY = spriteHeight / 2 + $height / 2 + vMoveScreen;
if(drawEndY >= $height) drawEndY = $height - 1;
// calculate width the the sprite
// same as height of sprite, given that it's square
int spriteWidth = abs(int($height / transformY)) / sprite_rec.uDiv;
int drawStartX = -spriteWidth / 2 + spriteScreenX;
if(drawStartX < 0) drawStartX = 0;
int drawEndX = spriteWidth / 2 + spriteScreenX;
if(drawEndX > $width) drawEndX = $width;
//loop through every vertical stripe of the sprite on screen
for(int stripe = drawStartX; stripe < drawEndX; stripe++) {
int texX = int(256 * (stripe - (-spriteWidth / 2 + spriteScreenX)) * textureWidth / spriteWidth) / 256;
// the conditions in the if are:
// 1) it's in front of the camera plane so you don't see things behind you
// 2) ZBuffer, with perpendicular distance
if (texX < 0) continue;
if(transformY > 0 && transformY < ZBuffer[stripe]) {
for(int y = drawStartY; y < drawEndY; y++) {
//256 and 128 factors to avoid floats
int d = (y - vMoveScreen) * 256 - $height * 128 + spriteHeight * 128;
int texY = ((d * textureHeight) / spriteHeight) / 256;
if ((size_t)texY >= sprite_texture.rows()) continue;
//get current color from the texture
auto color = sprite_texture[texY][texX];
// poor person's transparency, get current color from the texture
if (!(color.to_hex() & 0xffffff00)) continue;
auto dist = get_distance_from_center(stripe, y);
pixels[y][stripe] = dumb_lighting(color, d, dist);
}
}
}
});
}
float Raycaster::get_distance_from_center(int x, int y) const noexcept {
float cx = $width / 2;
float cy = $height / 2;
auto dx = cx - x;
auto dy = cy - y;
return ($r_sq - dx * dx - dy * dy) / $r_sq;
}
void Raycaster::cast_rays() {
// WALL CASTING
/*for(int x = 0; x < $width; x++) {*/
amt::parallel_for<32>(pool, 0, static_cast<std::size_t>($width), [this](size_t x){
double perpWallDist = 0;
// calculate ray position and direction
double cameraX = 2 * x / double($width) - 1; // x-coord in camera space
double rayDirX = dirX + planeX * cameraX;
double rayDirY = dirY + planeY * cameraX;
// which box of the map we're in
int mapX = int(posX);
int mapY = int(posY);
// length of ray from current pos to next x or y-side
double sideDistX;
double sideDistY;
// length of ray from one x or y-side to next x or y-side
double deltaDistX = std::abs(1.0 / rayDirX);
double deltaDistY = std::abs(1.0 / rayDirY);
int stepX = 0;
int stepY = 0;
int hit = 0;
int side = 0;
// calculate step and initial sideDist
if(rayDirX < 0) {
stepX = -1;
sideDistX = (posX - mapX) * deltaDistX;
} else {
stepX = 1;
sideDistX = (mapX + 1.0 - posX) * deltaDistX;
}
if(rayDirY < 0) {
stepY = -1;
sideDistY = (posY - mapY) * deltaDistY;
} else {
stepY = 1;
sideDistY = (mapY + 1.0 - posY) * deltaDistY;
}
// perform DDA
while(hit == 0) {
if(sideDistX < sideDistY) {
sideDistX += deltaDistX;
mapX += stepX;
side = 0;
} else {
sideDistY += deltaDistY;
mapY += stepY;
side = 1;
}
if($map[mapY][mapX] > 0) hit = 1;
}
if(side == 0) {
perpWallDist = (sideDistX - deltaDistX);
} else {
perpWallDist = (sideDistY - deltaDistY);
}
int lineHeight = int($height / perpWallDist);
int drawStart = -lineHeight / 2 + $height / 2 + PITCH;
if(drawStart < 0) drawStart = 0;
int drawEnd = lineHeight / 2 + $height / 2 + PITCH;
if(drawEnd >= $height) drawEnd = $height - 1;
auto &texture = textures.get_texture($map[mapY][mapX] - 1);
// calculate value of wallX
double wallX; // where exactly the wall was hit
if(side == 0) {
wallX = posY + perpWallDist * rayDirY;
} else {
wallX = posX + perpWallDist * rayDirX;
}
wallX -= floor((wallX));
// x coorindate on the texture
int texX = int(wallX * double(textures.TEXTURE_WIDTH));
if(side == 0 && rayDirX > 0) texX = textures.TEXTURE_WIDTH - texX - 1;
if(side == 1 && rayDirY < 0) texX = textures.TEXTURE_WIDTH - texX - 1;
// LODE: an integer-only bresenham or DDA like algorithm could make the texture coordinate stepping faster
// How much to increase the texture coordinate per screen pixel
double step = 1.0 * textures.TEXTURE_HEIGHT / lineHeight;
// Starting texture coordinate
double texPos = (drawStart - PITCH - $height / 2 + lineHeight / 2) * step;
for(int y = drawStart; y < drawEnd; y++) {
int texY = (int)texPos & (textures.TEXTURE_HEIGHT - 1);
texPos += step;
auto dist = get_distance_from_center(x, y);
auto color = dumb_lighting(texture[texY][texX], perpWallDist, dist);
pixels[y][x] = color;
}
// SET THE ZBUFFER FOR THE SPRITE CASTING
ZBuffer[x] = perpWallDist;
});
}
void Raycaster::draw_ceiling_floor() {
/*for(int y = $height / 2 + 1; y < $height; ++y) {*/
auto const h = static_cast<size_t>($height);
amt::parallel_for<32>(pool, h / 2, h, [this, $height=h](size_t y){
const size_t textureWidth = textures.TEXTURE_WIDTH;
const size_t textureHeight = textures.TEXTURE_HEIGHT;
// rayDir for leftmost ray (x=0) and rightmost (x = w)
float rayDirX0 = dirX - planeX;
float rayDirY0 = dirY - planeY;
float rayDirX1 = dirX + planeX;
float rayDirY1 = dirY + planeY;
// current y position compared to the horizon
int p = y - $height / 2;
// vertical position of the camera
// 0.5 will the camera at the center horizon. For a
// different value you need a separate loop for ceiling
// and floor since they're no longer symmetrical.
float posZ = 0.5 * $height;
// horizontal distance from the camera to the floor for the current row
// 0.5 is the z position exactly in the middle between floor and ceiling
// See NOTE in Lode's code for more.
float rowDistance = posZ / p;
// calculate the real world step vector we have to add for each x (parallel to camera plane)
// adding step by step avoids multiplications with a wight in the inner loop
float floorStepX = rowDistance * (rayDirX1 - rayDirX0) / $width;
float floorStepY = rowDistance * (rayDirY1 - rayDirY0) / $width;
// real world coordinates of the leftmost column.
// This will be updated as we step to the right
float floorX = posX + rowDistance * rayDirX0;
float floorY = posY + rowDistance * rayDirY0;
for(int x = 0; x < $width; ++x) {
// the cell coord is simply taken from the int parts of
// floorX and floorY.
int cellX = int(floorX);
int cellY = int(floorY);
// get the texture coordinat from the fractional part
int tx = int(textureWidth * (floorX - cellX)) & (textureWidth - 1);
int ty = int(textureWidth * (floorY - cellY)) & (textureHeight - 1);
floorX += floorStepX;
floorY += floorStepY;
// now get the pixel from the texture
// this uses the previous ty/tx fractional parts of
// floorX cellX to find the texture x/y. How?
#ifdef AMT_LIGHT
// FLOOR
auto dist_floor = get_distance_from_center(x, y);
pixels[y][x] = dumb_lighting(textures.floor[ty][tx], p, dist_floor);
// CEILING
auto dist_ceiling = get_distance_from_center(x, $height - y - 1);
pixels[$height - y - 1][x] = dumb_lighting(textures.ceiling[ty][tx], p, dist_ceiling);
#else
// FLOOR
pixels[y][x] = textures.floor[ty][tx];
// CEILING
pixels[$height - y - 1][x] = textures.ceiling[ty][tx];
#endif
}
});
}
void Raycaster::render() {
draw_ceiling_floor();
// This wait to prevent data-race
pool.wait(); // Try to remove this to see unbelievable performance
cast_rays();
pool.wait(); // Try to remove this too
sprite_casting();
pool.wait();
draw_pixel_buffer();
}
bool Raycaster::empty_space(int new_x, int new_y) {
dbc::check((size_t)new_x < $map.cols(),
format("x={} too wide={}", new_x, $map.cols()));
dbc::check((size_t)new_y < $map.rows(),
format("y={} too high={}", new_y, $map.rows()));
return $map[new_y][new_x] == 0;
}
void Raycaster::sort_sprites(std::vector<int>& order, std::vector<double>& dist, int amount)
{
std::vector<std::pair<double, int>> sprites(amount);
for(int i = 0; i < amount; i++) {
sprites[i].first = dist[i];
sprites[i].second = order[i];
}
std::sort(sprites.begin(), sprites.end());
// restore in reverse order
for(int i = 0; i < amount; i++) {
dist[i] = sprites[amount - i - 1].first;
order[i] = sprites[amount - i - 1].second;
}
}
void Raycaster::run(double speed, int dir) {
double speed_and_dir = speed * dir;
if(empty_space(int(posX + dirX * speed_and_dir), int(posY))) {
posX += dirX * speed_and_dir;
}
if(empty_space(int(posX), int(posY + dirY * speed_and_dir))) {
posY += dirY * speed_and_dir;
}
}
void Raycaster::rotate(double speed, int dir) {
double speed_and_dir = speed * dir;
double oldDirX = dirX;
dirX = dirX * cos(speed_and_dir) - dirY * sin(speed_and_dir);
dirY = oldDirX * sin(speed_and_dir) + dirY * cos(speed_and_dir);
double oldPlaneX = planeX;
planeX = planeX * cos(speed_and_dir) - planeY * sin(speed_and_dir);
planeY = oldPlaneX * sin(speed_and_dir) + planeY * cos(speed_and_dir);
}

View file

@ -1,73 +0,0 @@
#pragma once
#include <fmt/core.h>
#include <SFML/Graphics.hpp>
#include <SFML/Graphics/Image.hpp>
#include <numbers>
#include <algorithm>
#include <cmath>
#include "matrix.hpp"
#include <cstdlib>
#include <array>
#include "dbc.hpp"
#include "amt/pixel.hpp"
#include "amt/texture.hpp"
#include <memory>
#include "thread.hpp"
using Matrix = amt::Matrix<int>;
struct Raycaster {
int PITCH=0;
TexturePack textures;
double posX = 0;
double posY = 0;
// initial direction vector
double dirX = -1;
double dirY = 0;
// the 2d raycaster version of camera plane
double planeX = 0;
double planeY = 0.66;
sf::Texture view_texture;
sf::Sprite view_sprite;
//ZED: USE smart pointer for this
int $width;
int $height;
amt::PixelBuf pixels;
sf::RenderWindow& $window;
Matrix& $map;
std::vector<int> spriteOrder;
std::vector<double> spriteDistance;
std::vector<double> ZBuffer; // width
float $radius; // std::min($height, $width) / 2;
float $r_sq; // = radius * radius;
amt::thread_pool_t pool;
Raycaster(sf::RenderWindow& window, Matrix &map, unsigned width, unsigned height);
void draw_pixel_buffer();
void clear();
void cast_rays();
void draw_ceiling_floor();
void sprite_casting();
void sort_sprites(std::vector<int>& order, std::vector<double>& dist, int amount);
void render();
bool empty_space(int new_x, int new_y);
void run(double speed, int dir);
void rotate(double speed, int dir);
void position_camera(float player_x, float player_y);
float get_distance_from_center(int x, int y) const noexcept;
void set_position(int x, int y);
inline size_t pixcoord(int x, int y) {
return ((y) * $width) + (x);
}
};

View file

@ -1,34 +0,0 @@
#include <SFML/Graphics/Image.hpp>
#include "dbc.hpp"
#include <fmt/core.h>
#include "config.hpp"
#include "amt/texture.hpp"
Image TexturePack::load_image(std::string filename) {
sf::Image img;
bool good = img.loadFromFile(filename);
dbc::check(good, format("failed to load {}", filename));
return amt::PixelBuf(img.getPixelsPtr(), TEXTURE_HEIGHT, TEXTURE_WIDTH);
}
void TexturePack::load_textures() {
Config assets("assets/config.json");
for(string tile_path : assets["textures"]) {
images.emplace_back(load_image(tile_path));
}
for(string tile_path : assets["sprites"]) {
images.emplace_back(load_image(tile_path));
}
floor = load_image(assets["floor"]);
ceiling = load_image(assets["ceiling"]);
}
Image& TexturePack::get_texture(size_t num) {
return images[num];
}
Sprite &TexturePack::get_sprite(size_t sprite_num) {
return sprites[sprite_num];
}

View file

@ -1,34 +0,0 @@
#pragma once
#include <cstdint>
#include <vector>
#include <string>
#include "amt/pixel.hpp"
struct Sprite {
double x;
double y;
int texture;
// ZED: this should be a separate transform parameter
double elevation=0;
int uDiv=1;
int vDiv=1;
};
using Image = amt::PixelBuf;
struct TexturePack {
int NUM_SPRITES=1;
static const int TEXTURE_WIDTH=256; // must be power of two
static const int TEXTURE_HEIGHT=256; // must be power of two
std::vector<amt::PixelBuf> images;
std::vector<Sprite> sprites{{4.0, 3.55, 6}};
Image floor;
Image ceiling;
void load_textures();
amt::PixelBuf load_image(std::string filename);
Sprite& get_sprite(size_t sprite_num);
Image& get_texture(size_t num);
};

View file

@ -1,253 +0,0 @@
#ifndef AMT_THREAD_HPP
#define AMT_THREAD_HPP
#include <cassert>
#include <concepts>
#include <cstddef>
#include <deque>
#include <mutex>
#include <type_traits>
#include <thread>
#include <condition_variable>
#include <atomic>
#include <functional>
namespace amt {
// NOTE: Could implement lock-free queue.
template <typename T>
struct Queue {
using base_type = std::deque<T>;
using value_type = typename base_type::value_type;
using pointer = typename base_type::pointer;
using const_pointer = typename base_type::const_pointer;
using reference = typename base_type::reference;
using const_reference = typename base_type::const_reference;
using iterator = typename base_type::iterator;
using const_iterator = typename base_type::const_iterator;
using reverse_iterator = typename base_type::reverse_iterator;
using const_reverse_iterator = typename base_type::const_reverse_iterator;
using difference_type = typename base_type::difference_type;
using size_type = typename base_type::size_type;
constexpr Queue() noexcept = default;
constexpr Queue(Queue const&) noexcept = delete;
constexpr Queue(Queue &&) noexcept = default;
constexpr Queue& operator=(Queue const&) noexcept = delete;
constexpr Queue& operator=(Queue &&) noexcept = default;
constexpr ~Queue() noexcept = default;
template <typename U>
requires std::same_as<std::decay_t<U>, value_type>
void push(U&& u) {
std::lock_guard m(m_mutex);
m_data.push_back(std::forward<U>(u));
}
template <typename... Args>
void emplace(Args&&... args) {
std::lock_guard m(m_mutex);
m_data.emplace_back(std::forward<Args>(args)...);
}
std::optional<value_type> pop() {
std::lock_guard m(m_mutex);
if (empty_unsafe()) return std::nullopt;
auto el = std::move(m_data.front());
m_data.pop_front();
return std::move(el);
}
auto size() const noexcept -> size_type {
std::lock_guard m(m_mutex);
return m_data.size();
}
auto empty() const noexcept -> bool {
std::lock_guard m(m_mutex);
return m_data.empty();
}
constexpr auto size_unsafe() const noexcept -> size_type { return m_data.size(); }
constexpr auto empty_unsafe() const noexcept -> bool { return m_data.empty(); }
private:
base_type m_data;
mutable std::mutex m_mutex;
};
template <typename Fn>
struct ThreadPool;
template <typename Fn>
struct Worker {
using parent_t = ThreadPool<Fn>*;
using work_t = Fn;
using size_type = std::size_t;
constexpr Worker() noexcept = default;
constexpr Worker(Worker const&) noexcept = default;
constexpr Worker(Worker &&) noexcept = default;
constexpr Worker& operator=(Worker const&) noexcept = default;
constexpr Worker& operator=(Worker &&) noexcept = default;
~Worker() {
stop();
}
void start(parent_t pool, size_type id) {
assert((m_running.load(std::memory_order::acquire) == false) && "Thread is already running");
m_running.store(true);
m_parent.store(pool);
m_id = id;
m_thread = std::thread([this]() {
while (m_running.load(std::memory_order::relaxed)) {
std::unique_lock lk(m_mutex);
m_cv.wait(lk, [this] {
return !m_queue.empty_unsafe() || !m_running.load(std::memory_order::relaxed);
});
auto item = pop_task();
if (!item) {
item = try_steal();
if (!item) continue;
}
process_work(std::move(*item));
}
});
}
void process_work(work_t&& work) const noexcept {
std::invoke(std::move(work));
auto ptr = m_parent.load();
if (ptr) ptr->task_completed();
}
void stop() {
if (!m_running.load()) return;
{
std::lock_guard<std::mutex> lock(m_mutex);
m_running.store(false);
}
m_cv.notify_all();
m_thread.join();
m_parent.store(nullptr);
}
void add(work_t&& work) {
std::lock_guard<std::mutex> lock(m_mutex);
m_queue.push(std::move(work));
m_cv.notify_one();
}
std::optional<work_t> pop_task() noexcept {
return m_queue.pop();
}
std::optional<work_t> try_steal() noexcept {
auto ptr = m_parent.load();
if (ptr) return ptr->try_steal(m_id);
return {};
}
constexpr bool empty() const noexcept { return m_queue.empty_unsafe(); }
constexpr size_type size() const noexcept { return m_queue.size_unsafe(); }
constexpr size_type id() const noexcept { return m_id; }
constexpr bool running() const noexcept { return m_running.load(std::memory_order::relaxed); }
private:
Queue<work_t> m_queue{};
std::thread m_thread;
std::atomic<bool> m_running{false};
std::mutex m_mutex{};
std::condition_variable m_cv{};
std::atomic<parent_t> m_parent{nullptr};
size_type m_id;
};
template <typename Fn>
struct ThreadPool {
using worker_t = Worker<Fn>;
using work_t = typename worker_t::work_t;
using size_type = std::size_t;
constexpr ThreadPool(ThreadPool const&) noexcept = delete;
constexpr ThreadPool(ThreadPool &&) noexcept = default;
constexpr ThreadPool& operator=(ThreadPool const&) noexcept = delete;
constexpr ThreadPool& operator=(ThreadPool &&) noexcept = default;
~ThreadPool() {
stop();
}
ThreadPool(size_type n = std::thread::hardware_concurrency())
: m_workers(std::max(n, size_type{1}))
{
for (auto i = 0ul; i < m_workers.size(); ++i) {
m_workers[i].start(this, i);
}
}
void stop() {
for (auto& w: m_workers) w.stop();
}
void add(Fn&& work) {
m_active_tasks.fetch_add(1, std::memory_order::relaxed);
m_workers[m_last_added].add(std::move(work));
m_last_added = (m_last_added + 1) % m_workers.size();
}
std::optional<work_t> try_steal(size_type id) {
for (auto& w: m_workers) {
if (w.id() == id) continue;
auto item = w.pop_task();
if (item) return item;
}
return {};
}
void task_completed() {
if (m_active_tasks.fetch_sub(1, std::memory_order::release) == 1) {
m_wait_cv.notify_all();
}
}
void wait() {
std::unique_lock lock(m_wait_mutex);
m_wait_cv.wait(lock, [this] {
return m_active_tasks.load(std::memory_order::acquire) == 0;
});
}
private:
std::vector<worker_t> m_workers;
size_type m_last_added{};
std::mutex m_wait_mutex;
std::condition_variable m_wait_cv;
std::atomic<size_t> m_active_tasks{0};
};
using thread_pool_t = ThreadPool<std::function<void()>>;
// WARNING: Do not capture the stack variable if you're defering wait on pool.
// If you want to capture them, either capture them value or do "pool.wait()" at the end of the scope.
template <std::size_t Split, typename Fn>
requires (std::is_invocable_v<Fn, std::size_t>)
constexpr auto parallel_for(thread_pool_t& pool, std::size_t start, std::size_t end, Fn&& body) noexcept {
if (start >= end) return;
auto const size = (end - start);
auto const chunk_size = std::max(size_t{1}, (size + Split - 1) / Split);
auto const num_chunks = (size + chunk_size - 1) / chunk_size;
for (auto chunk = 0ul; chunk < num_chunks; ++chunk) {
auto const chunk_start = std::min(start + (chunk * chunk_size), end);
auto const chunk_end = std::min(chunk_start + (chunk_size), end);
pool.add([chunk_start, chunk_end, body] {
for (auto i = chunk_start; i < chunk_end; ++i) {
std::invoke(body, i);
}
});
}
}
} // nsmespace amt
#endif // AMT_THREAD_HPP

View file

@ -1,97 +0,0 @@
#include <coroutine>
#include <cstdint>
#include <exception>
#include <fmt/core.h>
template<typename T>
struct Generator {
struct promise_type;
using handle_type = std::coroutine_handle<promise_type>;
struct promise_type {
T value_;
std::exception_ptr exception_;
Generator get_return_object() {
return Generator(handle_type::from_promise(*this));
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { exception_ = std::current_exception(); }
template<std::convertible_to<T> From>
std::suspend_always yield_value(From&& from) {
value_ = std::forward<From>(from);
return {};
}
void return_void() {}
};
handle_type h_;
Generator(handle_type h) : h_(h) {}
~Generator() { h_.destroy(); }
explicit operator bool() {
fill();
return !h_.done();
}
T operator()() {
fill();
full_ = false;
return std::move(h_.promise().value_);
}
private:
bool full_ = false;
void fill() {
if(!full_) {
h_();
if(h_.promise().exception_) {
std::rethrow_exception(h_.promise().exception_);
}
full_ = true;
}
}
};
Generator<std::uint64_t>
fib(unsigned n) {
if(n == 0) co_return;
if(n > 94) {
throw std::runtime_error("Too big");
}
if(n == 1) co_return;
co_yield 1;
if(n == 2) co_return;
std::uint64_t a = 0;
std::uint64_t b = 1;
for(unsigned i = 2; i < n; ++i) {
std::uint64_t s = a + b;
co_yield s;
a = b;
b = s;
}
}
int main() {
try {
auto gen = fib(50);
for(int j = 0; gen; ++j) {
fmt::println("fib({})={}", j, gen());
}
} catch(const std::exception& ex) {
fmt::println("Exception: {}", ex.what());
} catch(...) {
fmt::println("Unknown exception");
}
return 0;
}

View file

@ -1,39 +0,0 @@
#ifndef __dbg_h__
#define __dbg_h__
#include <stdio.h>
#include <errno.h>
#include <string.h>
#ifdef NDEBUG
#define debug(M, ...)
#else
#define debug(M, ...) fprintf(stderr, "DEBUG %s:%d: " M "\n",\
__FILE__, __LINE__, ##__VA_ARGS__)
#endif
#define clean_errno() (errno == 0 ? "None" : strerror(errno))
#define log_err(M, ...) fprintf(stderr,\
"[ERROR] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__,\
clean_errno(), ##__VA_ARGS__)
#define log_warn(M, ...) fprintf(stderr,\
"[WARN] (%s:%d: errno: %s) " M "\n",\
__FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)
#define log_info(M, ...) fprintf(stderr, "[INFO] (%s:%d) " M "\n",\
__FILE__, __LINE__, ##__VA_ARGS__)
#define check(A, M, ...) if(!(A)) {\
log_err(M, ##__VA_ARGS__); errno=0; goto error; }
#define sentinel(M, ...) { log_err(M, ##__VA_ARGS__);\
errno=0; goto error; }
#define check_mem(A) check((A), "Out of memory.")
#define check_debug(A, M, ...) if(!(A)) { debug(M, ##__VA_ARGS__);\
errno=0; goto error; }
#endif

View file

@ -1,436 +0,0 @@
#line 1 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
#include "gui/dnd_loot_2.hpp"
#include "gui/guecstra.hpp"
#include "gui/uisystems.hpp"
#include <guecs/ui.hpp>
#include <fmt/core.h>
#include "dbc.hpp"
#define _log(M, F) {$cur_state = F; fmt::println("| {}:{} action={}, fcurs={}", __FILE_NAME__, __LINE__, #M, F);}
#line 131 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
#line 14 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.cpp"
static const char _DNDLoot_eof_actions[] = {
0, 0, 0, 0, 0, 10, 0, 10
};
static const int DNDLoot_start = 1;
static const int DNDLoot_first_final = 8;
static const int DNDLoot_error = 0;
static const int DNDLoot_en_main = 1;
static const int DNDLoot_en_main_looting = 2;
#line 134 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
namespace gui {
sf::Vector2f DNDLoot2::mouse_position() {
return $window.mapPixelToCoords($router.position);
}
void DNDLoot2::mouse_action(bool hover) {
sf::Vector2f pos = mouse_position();
$status_ui.mouse(pos.x, pos.y, hover);
if($loot_ui.active) $loot_ui.mouse(pos.x, pos.y, hover);
}
DNDLoot2::DNDLoot2(StatusUI& status_ui, LootUI& loot_ui, sf::RenderWindow &window, routing::Router& router) :
$status_ui(status_ui),
$loot_ui(loot_ui),
$window(window),
$router(router)
{
#line 43 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.cpp"
{
cs = DNDLoot_start;
}
#line 153 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
dbc::log("====================================");
event(Event::STARTED);
dbc::log("---------------- END CONSTRICT ------");
}
bool DNDLoot2::event(Event event, std::any data) {
if(event == Event::TICK) return true;
int *p = (int *)&event;
int *pe = p+1;
int *eof = pe;
dbc::log(fmt::format(">>>> DND EVENT {}, state={}, cs={}, end={}",
int(event), $cur_state, cs, $at_end));
#line 62 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.cpp"
{
int _ps = 0;
if ( cs == 0 )
goto _out;
_resume:
switch ( cs ) {
case 1:
if ( (*p) == 0 )
goto tr0;
goto tr1;
case 0:
goto _out;
case 2:
switch( (*p) ) {
case 14: goto tr2;
case 17: goto tr4;
case 19: goto tr5;
}
if ( (*p) < 20 ) {
if ( 15 <= (*p) && (*p) <= 16 )
goto tr3;
} else if ( (*p) > 21 ) {
if ( 22 <= (*p) && (*p) <= 23 )
goto tr5;
} else
goto tr6;
goto tr1;
case 3:
switch( (*p) ) {
case 14: goto tr7;
case 16: goto tr8;
case 17: goto tr9;
case 19: goto tr10;
}
if ( (*p) > 21 ) {
if ( 22 <= (*p) && (*p) <= 23 )
goto tr10;
} else if ( (*p) >= 20 )
goto tr11;
goto tr1;
case 4:
switch( (*p) ) {
case 14: goto tr12;
case 15: goto tr13;
}
goto tr1;
case 5:
switch( (*p) ) {
case 14: goto tr15;
case 17: goto tr17;
case 19: goto tr18;
}
if ( (*p) < 20 ) {
if ( 15 <= (*p) && (*p) <= 16 )
goto tr16;
} else if ( (*p) > 21 ) {
if ( 22 <= (*p) && (*p) <= 23 )
goto tr18;
} else
goto tr19;
goto tr14;
case 6:
switch( (*p) ) {
case 14: goto tr20;
case 16: goto tr21;
case 17: goto tr22;
case 19: goto tr23;
}
if ( (*p) > 21 ) {
if ( 22 <= (*p) && (*p) <= 23 )
goto tr23;
} else if ( (*p) >= 20 )
goto tr24;
goto tr1;
case 7:
switch( (*p) ) {
case 14: goto tr25;
case 16: goto tr26;
case 17: goto tr27;
case 19: goto tr28;
}
if ( (*p) > 21 ) {
if ( 22 <= (*p) && (*p) <= 23 )
goto tr28;
} else if ( (*p) >= 20 )
goto tr29;
goto tr14;
}
tr1: cs = 0; goto _again;
tr14: _ps = cs;cs = 0; goto f9;
tr0: _ps = cs;cs = 2; goto f0;
tr2: _ps = cs;cs = 2; goto f1;
tr8: _ps = cs;cs = 2; goto f2;
tr22: _ps = cs;cs = 2; goto f3;
tr5: _ps = cs;cs = 2; goto f4;
tr6: _ps = cs;cs = 2; goto f5;
tr9: _ps = cs;cs = 2; goto f6;
tr15: _ps = cs;cs = 2; goto f10;
tr26: _ps = cs;cs = 2; goto f11;
tr18: _ps = cs;cs = 2; goto f13;
tr19: _ps = cs;cs = 2; goto f14;
tr21: _ps = cs;cs = 2; goto f15;
tr27: _ps = cs;cs = 2; goto f16;
tr3: _ps = cs;cs = 3; goto f2;
tr10: _ps = cs;cs = 3; goto f4;
tr11: _ps = cs;cs = 3; goto f5;
tr16: _ps = cs;cs = 3; goto f11;
tr28: _ps = cs;cs = 3; goto f13;
tr29: _ps = cs;cs = 3; goto f14;
tr20: _ps = cs;cs = 4; goto f1;
tr7: _ps = cs;cs = 4; goto f2;
tr25: _ps = cs;cs = 4; goto f11;
tr12: _ps = cs;cs = 5; goto f7;
tr4: _ps = cs;cs = 6; goto f3;
tr23: _ps = cs;cs = 6; goto f4;
tr24: _ps = cs;cs = 6; goto f5;
tr17: _ps = cs;cs = 6; goto f12;
tr13: _ps = cs;cs = 7; goto f8;
f9:
#line 17 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
$cur_state = (_ps);
fmt::println("!!! ERROR fcurs={}", (_ps));
}
goto _again;
f0:
#line 22 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(started, (_ps));
{p++; goto _out; }
}
goto _again;
f1:
#line 27 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(loot_close, (_ps));
$loot_ui.active = false;
{p++; goto _out; }
}
goto _again;
f2:
#line 33 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(loot_grab, (_ps));
// NOTE: when grab_source could work to do the if that was here
$grab_source = UISystem::loot_grab($loot_ui.$gui, data);
{p++; goto _out; }
}
goto _again;
f3:
#line 40 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(inv_grab, (_ps));
$grab_source = UISystem::loot_grab($status_ui.$gui, data);
{p++; goto _out; }
}
goto _again;
f15:
#line 46 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(loot_drop, (_ps));
if(UISystem::loot_drop($status_ui.$gui,
$loot_ui.$gui, $grab_source, data))
{
cs = 2;
}
{p++; goto _out; }
}
goto _again;
f6:
#line 57 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(inv_drop, (_ps));
if(UISystem::loot_drop($loot_ui.$gui,
$status_ui.$gui, $grab_source, data))
{
cs = 2;
}
{p++; goto _out; }
}
goto _again;
f7:
#line 68 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(at_end, (_ps));
fmt::println("> AT END");
$grab_source = std::nullopt;
$at_end = true;
{p++; goto _out; }
}
goto _again;
f4:
#line 83 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(mouse_click, (_ps));
mouse_action(false);
{p++; goto _out; }
}
goto _again;
f5:
#line 89 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(mouse_move, (_ps));
if($grab_source) {
auto& source = $loot_ui.$gui.get<guecs::GrabSource>(*$grab_source);
source.move($window.mapPixelToCoords($router.position));
}
mouse_action(true);
{p++; goto _out; }
}
goto _again;
f10:
#line 27 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(loot_close, (_ps));
$loot_ui.active = false;
{p++; goto _out; }
}
#line 76 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(not_end, (_ps));
fmt::println("% NOT_END");
$at_end = false;
{p++; goto _out; }
}
goto _again;
f11:
#line 33 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(loot_grab, (_ps));
// NOTE: when grab_source could work to do the if that was here
$grab_source = UISystem::loot_grab($loot_ui.$gui, data);
{p++; goto _out; }
}
#line 76 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(not_end, (_ps));
fmt::println("% NOT_END");
$at_end = false;
{p++; goto _out; }
}
goto _again;
f12:
#line 40 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(inv_grab, (_ps));
$grab_source = UISystem::loot_grab($status_ui.$gui, data);
{p++; goto _out; }
}
#line 76 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(not_end, (_ps));
fmt::println("% NOT_END");
$at_end = false;
{p++; goto _out; }
}
goto _again;
f16:
#line 57 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(inv_drop, (_ps));
if(UISystem::loot_drop($loot_ui.$gui,
$status_ui.$gui, $grab_source, data))
{
cs = 2;
}
{p++; goto _out; }
}
#line 76 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(not_end, (_ps));
fmt::println("% NOT_END");
$at_end = false;
{p++; goto _out; }
}
goto _again;
f8:
#line 68 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(at_end, (_ps));
fmt::println("> AT END");
$grab_source = std::nullopt;
$at_end = true;
{p++; goto _out; }
}
#line 33 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(loot_grab, (_ps));
// NOTE: when grab_source could work to do the if that was here
$grab_source = UISystem::loot_grab($loot_ui.$gui, data);
{p++; goto _out; }
}
goto _again;
f13:
#line 83 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(mouse_click, (_ps));
mouse_action(false);
{p++; goto _out; }
}
#line 76 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(not_end, (_ps));
fmt::println("% NOT_END");
$at_end = false;
{p++; goto _out; }
}
goto _again;
f14:
#line 89 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(mouse_move, (_ps));
if($grab_source) {
auto& source = $loot_ui.$gui.get<guecs::GrabSource>(*$grab_source);
source.move($window.mapPixelToCoords($router.position));
}
mouse_action(true);
{p++; goto _out; }
}
#line 76 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
_log(not_end, (_ps));
fmt::println("% NOT_END");
$at_end = false;
{p++; goto _out; }
}
goto _again;
_again:
if ( cs == 0 )
goto _out;
p += 1;
goto _resume;
if ( p == eof )
{
switch ( _DNDLoot_eof_actions[cs] ) {
case 10:
#line 17 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
{
$cur_state = (_ps);
fmt::println("!!! ERROR fcurs={}", (_ps));
}
break;
#line 385 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.cpp"
}
}
_out: {}
}
#line 170 "C:/Users/lcthw/Projects/raycaster//gui/dnd_loot_2.rl"
dbc::log(fmt::format("<<<< DND EVENT {}, state={}, cs={}, end={}",
int(event), $cur_state, cs, $at_end));
return $at_end;
}
}

View file

@ -1,175 +0,0 @@
#include "gui/dnd_loot_2.hpp"
#include "gui/guecstra.hpp"
#include "gui/uisystems.hpp"
#include <guecs/ui.hpp>
#include <fmt/core.h>
#include "dbc.hpp"
#define _log(M, F) {$cur_state = F; fmt::println("| {}:{} action={}, fcurs={}", __FILE_NAME__, __LINE__, #M, F);}
%%{
machine DNDLoot;
alphtype int;
import "gui/fsm_events.hpp";
action error {
$cur_state = fcurs;
fmt::println("!!! ERROR fcurs={}", fcurs);
}
action started {
_log(started, fcurs);
fbreak;
}
action loot_close {
_log(loot_close, fcurs);
$loot_ui.active = false;
fbreak;
}
action loot_grab {
_log(loot_grab, fcurs);
// NOTE: when grab_source could work to do the if that was here
$grab_source = UISystem::loot_grab($loot_ui.$gui, data);
fbreak;
}
action inv_grab {
_log(inv_grab, fcurs);
$grab_source = UISystem::loot_grab($status_ui.$gui, data);
fbreak;
}
action loot_drop {
_log(loot_drop, fcurs);
if(UISystem::loot_drop($status_ui.$gui,
$loot_ui.$gui, $grab_source, data))
{
fnext looting;
}
fbreak;
}
action inv_drop {
_log(inv_drop, fcurs);
if(UISystem::loot_drop($loot_ui.$gui,
$status_ui.$gui, $grab_source, data))
{
fnext looting;
}
fbreak;
}
action at_end {
_log(at_end, fcurs);
fmt::println("> AT END");
$grab_source = std::nullopt;
$at_end = true;
fbreak;
}
action not_end {
_log(not_end, fcurs);
fmt::println("% NOT_END");
$at_end = false;
fbreak;
}
action mouse_click {
_log(mouse_click, fcurs);
mouse_action(false);
fbreak;
}
action mouse_move {
_log(mouse_move, fcurs);
if($grab_source) {
auto& source = $loot_ui.$gui.get<guecs::GrabSource>(*$grab_source);
source.move($window.mapPixelToCoords($router.position));
}
mouse_action(true);
fbreak;
}
mouse_click = (MOUSE_DRAG_START | MOUSE_CLICK | MOUSE_DROP);
mouse_move = (MOUSE_MOVE | MOUSE_DRAG);
main := start: (
STARTED @started -> looting
),
looting: (
LOOT_OPEN @loot_close -> looting |
LOOT_ITEM @loot_grab -> loot_grab |
LOOT_SELECT @loot_grab -> loot_grab |
INV_SELECT @inv_grab -> inv_grab |
mouse_click @mouse_click -> looting |
mouse_move @mouse_move -> looting
),
loot_grab: (
LOOT_OPEN @loot_grab -> end |
LOOT_SELECT @loot_grab -> looting |
INV_SELECT @inv_drop -> looting |
mouse_click @mouse_click -> loot_grab |
mouse_move @mouse_move -> loot_grab
),
inv_grab: (
LOOT_OPEN @loot_close -> end |
LOOT_SELECT @loot_drop -> looting |
INV_SELECT @inv_grab -> looting |
mouse_click @mouse_click -> inv_grab |
mouse_move @mouse_move -> inv_grab
),
end: (
LOOT_ITEM @loot_grab -> loot_grab |
LOOT_OPEN -> looting
) >at_end %not_end %err(error);
}%%
%% write data;
namespace gui {
sf::Vector2f DNDLoot2::mouse_position() {
return $window.mapPixelToCoords($router.position);
}
void DNDLoot2::mouse_action(bool hover) {
sf::Vector2f pos = mouse_position();
$status_ui.mouse(pos.x, pos.y, hover);
if($loot_ui.active) $loot_ui.mouse(pos.x, pos.y, hover);
}
DNDLoot2::DNDLoot2(StatusUI& status_ui, LootUI& loot_ui, sf::RenderWindow &window, routing::Router& router) :
$status_ui(status_ui),
$loot_ui(loot_ui),
$window(window),
$router(router)
{
%%write init;
dbc::log("====================================");
event(Event::STARTED);
dbc::log("---------------- END CONSTRICT ------");
}
bool DNDLoot2::event(Event event, std::any data) {
if(event == Event::TICK) return true;
int *p = (int *)&event;
int *pe = p+1;
int *eof = pe;
dbc::log(fmt::format(">>>> DND EVENT {}, state={}, cs={}, end={}",
int(event), $cur_state, cs, $at_end));
%%write exec noend;
dbc::log(fmt::format("<<<< DND EVENT {}, state={}, cs={}, end={}",
int(event), $cur_state, cs, $at_end));
return $at_end;
}
}

View file

@ -1,359 +0,0 @@
#include <fmt/core.h>
#include <numbers>
#include <algorithm>
#include <cmath>
#include "matrix.hpp"
#include <cstdlib>
#include "fenster/fenster.h"
#include "dbc.hpp"
using matrix::Matrix;
using namespace fmt;
Matrix MAP{
{2,2,2,2,2,2,2,2,2},
{2,0,8,0,0,0,0,0,2},
{2,0,7,0,0,5,6,0,2},
{2,0,0,0,0,0,0,0,2},
{2,2,0,0,0,0,0,2,2},
{2,0,0,1,3,4,0,0,2},
{2,0,0,0,0,0,2,2,2},
{2,2,2,2,2,2,2,2,2}
};
const int SCREEN_HEIGHT=480;
const int SCREEN_WIDTH=SCREEN_HEIGHT * 2;
const int THREED_VIEW_WIDTH=480;
const int THREED_VIEW_HEIGHT=480;
const int MAP_SIZE=matrix::width(MAP);
const int TILE_SIZE=(SCREEN_WIDTH/2) / MAP_SIZE;
const float FOV = std::numbers::pi / 3.0;
const float HALF_FOV = FOV / 2;
const int CASTED_RAYS=120;
const float STEP_ANGLE = FOV / CASTED_RAYS;
const int MAX_DEPTH = MAP_SIZE * TILE_SIZE;
const float SCALE = (SCREEN_WIDTH / 2) / CASTED_RAYS;
int PITCH=25;
float player_x = SCREEN_WIDTH / 4;
float player_y = SCREEN_WIDTH / 4;
// x and y start position
double posX = player_x / TILE_SIZE;
double posY = player_y / TILE_SIZE;
// initial direction vector
double dirX = -1;
double dirY = 0;
// the 2d raycaster version of camera plane
double planeX = 0;
double planeY = 0.66;
#define rgba_color(r,g,b,a) (b<<(0*8))|(g<<(1*8))|(r<<(2*8))|(a<<(3*8))
#define gray_color(c) rgba_color(c, c, c, 255)
std::vector<uint32_t> texture[8];
#define texWidth 64
#define texHeight 64
void load_textures() {
for(int i = 0; i < 8; i++) {
texture[i].resize(texWidth * texHeight);
}
for(int x = 0; x < texWidth; x++) {
for(int y = 0; y < texHeight; y++)
{
int xorcolor = (x * 256 / texWidth) ^ (y * 256 / texHeight);
//int xcolor = x * 256 / texWidth;
int ycolor = y * 256 / texHeight;
int xycolor = y * 128 / texHeight + x * 128 / texWidth;
texture[0][texWidth * y + x] = 65536 * 254 * (x != y && x != texWidth - y); //flat red texture with black cross
texture[1][texWidth * y + x] = xycolor + 256 * xycolor + 65536 * xycolor; //sloped greyscale
texture[2][texWidth * y + x] = 256 * xycolor + 65536 * xycolor; //sloped yellow gradient
texture[3][texWidth * y + x] = xorcolor + 256 * xorcolor + 65536 * xorcolor; //xor greyscale
texture[4][texWidth * y + x] = 256 * xorcolor; //xor green
texture[5][texWidth * y + x] = 65536 * 192 * (x % 16 && y % 16); //red bricks
texture[6][texWidth * y + x] = 65536 * ycolor; //red gradient
texture[7][texWidth * y + x] = 128 + 256 * 128 + 65536 * 128; //flat grey texture
}
}
}
void draw_rect(Fenster &window, Point pos, Point size, uint32_t color) {
size_t x_start = size_t(pos.x);
size_t y_start = size_t(pos.y);
size_t width = size_t(size.x);
size_t height = size_t(size.y);
dbc::check(x_start <= size_t(window.f.width), format("pos.x {} is greater than width {}", x_start, window.f.width));
dbc::check(y_start <= size_t(window.f.height), format("pos.y {} is greater than height {}", y_start, window.f.height));
dbc::check(x_start + width <= size_t(window.f.width), format("size width {} is greater than width {}", x_start + width, window.f.width));
dbc::check(y_start + height <= size_t(window.f.height), format("size height {} is greater than height {}", y_start + height, window.f.height));
for(size_t y = y_start; y < y_start + height; y++) {
for(size_t x = x_start; x < x_start + width; x++) {
window.px(x, y) = color;
}
}
}
void draw_map_rect(Fenster &window, int x, int y, uint32_t color) {
draw_rect(window,
{size_t(x * TILE_SIZE), size_t(y * TILE_SIZE)},
{size_t(TILE_SIZE-1), size_t(TILE_SIZE-1)},
color);
}
void draw_map(Fenster &window, Matrix &map) {
uint32_t light_grey = gray_color(191);
uint32_t dark_grey = gray_color(65);
for(size_t y = 0; y < matrix::height(map); y++) {
for(size_t x = 0; x < matrix::width(map); x++) {
draw_map_rect(window, x, y, map[y][x] == 0 ? dark_grey : light_grey);
}
}
}
void draw_line(Fenster &window, Point start, Point end, uint32_t color) {
int x = int(start.x);
int y = int(start.y);
int x1 = int(end.x);
int y1 = int(end.y);
int dx = std::abs(x1 - x);
int sx = x < x1 ? 1 : -1;
int dy = std::abs(y1 - y) * -1;
int sy = y < y1 ? 1 : -1;
int error = dx + dy;
while(x != x1 || y != y1) {
int e2 = 2 * error;
if(e2 >= dy) {
error = error + dy;
x = x + sx;
}
if(e2 <= dx) {
error = error + dx;
y = y + sy;
}
window.px(x, y) = color;
}
}
void clear(Fenster &window) {
for(int y = 0; y < window.f.height; y++) {
for(int x = 0; x < window.f.width; x++) {
window.px(x, y) = 0;
}
}
}
void draw_map_blocks(Fenster &window, int col, int row) {
draw_map_rect(window, col, row, rgba_color(100, 20, 20, 255));
}
void ray_casting(Fenster &window, Matrix& map) {
int w = THREED_VIEW_WIDTH;
int h = THREED_VIEW_HEIGHT;
for(int x = 0; x < w; x++) {
// calculate ray position and direction
double cameraX = 2 * x / double(w) - 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);
double perpWallDist;
int stepX = 0;
int stepY = 0;
int hit = 0;
int side = 0;
// calculate step and initial sideDist
if(rayDirX < 0) {
stepX = -1;
sideDistX = (posX - mapX) * deltaDistX;
} else {
stepX = 1;
sideDistX = (mapX + 1.0 - posX) * deltaDistX;
}
if(rayDirY < 0) {
stepY = -1;
sideDistY = (posY - mapY) * deltaDistY;
} else {
stepY = 1;
sideDistY = (mapY + 1.0 - posY) * deltaDistY;
}
// perform DDA
while(hit == 0) {
if(sideDistX < sideDistY) {
sideDistX += deltaDistX;
mapX += stepX;
side = 0;
} else {
sideDistY += deltaDistY;
mapY += stepY;
side = 1;
}
if(map[mapY][mapX] > 0) hit = 1;
}
if(side == 0) {
perpWallDist = (sideDistX - deltaDistX);
} else {
perpWallDist = (sideDistY - deltaDistY);
}
draw_map_blocks(window, mapX, mapY);
// player direction ray
draw_line(window, {size_t(posX * TILE_SIZE), size_t(posY * TILE_SIZE)},
{(size_t)mapX * TILE_SIZE, (size_t)mapY * TILE_SIZE}, rgba_color(0, 255, 0, 255));
int lineHeight = int(h / perpWallDist);
int drawStart = -lineHeight / 2 + h / 2 + PITCH;
if(drawStart < 0) drawStart = 0;
int drawEnd = lineHeight / 2 + h / 2 + PITCH;
if(drawEnd >= h) drawEnd = h - 1;
int texNum = MAP[mapY][mapX] - 1;
// calculate value of wallX
double wallX; // where exactly the wall was hit
if(side == 0) {
wallX = posY + perpWallDist * rayDirY;
} else {
wallX = posX + perpWallDist * rayDirX;
}
wallX -= floor((wallX));
// x coorindate on the texture
int texX = int(wallX * double(texWidth));
if(side == 0 && rayDirX > 0) texX = texWidth - texX - 1;
if(side == 1 && rayDirY < 0) texX = texWidth - 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 * texHeight / lineHeight;
// Starting texture coordinate
double texPos = (drawStart - PITCH - h / 2 + lineHeight / 2) * step;
for(int y = drawStart; y < drawEnd; y++) {
// BUG? Why bitwise and here?
int texY = (int)texPos & (texHeight - 1);
texPos += step;
uint32_t color = texture[texNum][texHeight * texY + texX];
if(side == 1) color = (color >> 1) & 8355711;
window.px(x + THREED_VIEW_WIDTH, y) = color;
}
}
}
void draw_ceiling_floor(Fenster &window) {
draw_rect(window,
{size_t(window.width() / 2), size_t(window.height() / 2)},
{size_t(window.width() / 2), size_t(window.height() / 2)},
gray_color(200));
draw_rect(window,
{size_t(window.width() / 2), 0},
{size_t(window.height()), size_t(window.height() / 2 + PITCH)},
gray_color(100));
}
void draw_everything(Fenster &window) {
clear(window);
draw_map(window, MAP);
draw_ceiling_floor(window);
ray_casting(window, MAP);
}
bool empty_space(int new_x, int new_y) {
dbc::check((size_t)new_x < matrix::width(MAP),
format("x={} too wide={}", new_x, matrix::width(MAP)));
dbc::check((size_t)new_y < matrix::height(MAP),
format("y={} too high={}", new_y, matrix::height(MAP)));
return MAP[new_y][new_x] == 0;
}
int main() {
Fenster window(SCREEN_WIDTH, SCREEN_HEIGHT, "Fenscaster");
const int fps = 60;
double moveSpeed = 0.1;
double rotSpeed = 0.1;
load_textures();
while(window.loop(fps)) {
draw_everything(window);
if(window.key('W')) {
if(empty_space(int(posX + dirX * moveSpeed), int(posY))) posX += dirX * moveSpeed;
if(empty_space(int(posX), int(posY + dirY * moveSpeed))) posY += dirY * moveSpeed;
} else if(window.key('S')) {
if(empty_space(int(posX - dirX * moveSpeed), int(posY))) posX -= dirX * moveSpeed;
if(empty_space(int(posX), int(posY - dirY * moveSpeed))) posY -= dirY * moveSpeed;
}
if(window.key('D')) {
double oldDirX = dirX;
dirX = dirX * cos(-rotSpeed) - dirY * sin(-rotSpeed);
dirY = oldDirX * sin(-rotSpeed) + dirY * cos(-rotSpeed);
double oldPlaneX = planeX;
planeX = planeX * cos(-rotSpeed) - planeY * sin(-rotSpeed);
planeY = oldPlaneX * sin(-rotSpeed) + planeY * cos(-rotSpeed);
} else if(window.key('A')) {
double oldDirX = dirX;
dirX = dirX * cos(rotSpeed) - dirY * sin(rotSpeed);
dirY = oldDirX * sin(rotSpeed) + dirY * cos(rotSpeed);
double oldPlaneX = planeX;
planeX = planeX * cos(rotSpeed) - planeY * sin(rotSpeed);
planeY = oldPlaneX * sin(rotSpeed) + planeY * cos(rotSpeed);
}
if(window.key('E')) {
PITCH = std::clamp(PITCH + 10, -60, 240);
} else if(window.key('Q')) {
PITCH = std::clamp(PITCH - 10, -60, 240);
}
}
return 0;
}
#if defined(_WIN32)
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine,
int nCmdShow) {
(void)hInstance, (void)hPrevInstance, (void)pCmdLine, (void)nCmdShow;
return main();
}
#endif

View file

@ -1,373 +0,0 @@
#ifndef FENSTER_H
#define FENSTER_H
#if defined(__APPLE__)
#include <CoreGraphics/CoreGraphics.h>
#include <objc/NSObjCRuntime.h>
#include <objc/objc-runtime.h>
#elif defined(_WIN32)
#include <windows.h>
#else
#define _DEFAULT_SOURCE 1
#include <X11/XKBlib.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <time.h>
#endif
#include <stdint.h>
#include <stdlib.h>
struct fenster {
const char *title;
const int width;
const int height;
uint32_t *buf;
int keys[256]; /* keys are mostly ASCII, but arrows are 17..20 */
int mod; /* mod is 4 bits mask, ctrl=1, shift=2, alt=4, meta=8 */
int x;
int y;
int mouse;
#if defined(__APPLE__)
id wnd;
#elif defined(_WIN32)
HWND hwnd;
#else
Display *dpy;
Window w;
GC gc;
XImage *img;
#endif
};
#ifndef FENSTER_API
#define FENSTER_API extern
#endif
FENSTER_API int fenster_open(struct fenster *f);
FENSTER_API int fenster_loop(struct fenster *f);
FENSTER_API void fenster_close(struct fenster *f);
FENSTER_API void fenster_sleep(int64_t ms);
FENSTER_API int64_t fenster_time(void);
#define fenster_pixel(f, x, y) ((f)->buf[((y) * (f)->width) + (x)])
#ifndef FENSTER_HEADER
#if defined(__APPLE__)
#define msg(r, o, s) ((r(*)(id, SEL))objc_msgSend)(o, sel_getUid(s))
#define msg1(r, o, s, A, a) \
((r(*)(id, SEL, A))objc_msgSend)(o, sel_getUid(s), a)
#define msg2(r, o, s, A, a, B, b) \
((r(*)(id, SEL, A, B))objc_msgSend)(o, sel_getUid(s), a, b)
#define msg3(r, o, s, A, a, B, b, C, c) \
((r(*)(id, SEL, A, B, C))objc_msgSend)(o, sel_getUid(s), a, b, c)
#define msg4(r, o, s, A, a, B, b, C, c, D, d) \
((r(*)(id, SEL, A, B, C, D))objc_msgSend)(o, sel_getUid(s), a, b, c, d)
#define cls(x) ((id)objc_getClass(x))
extern id const NSDefaultRunLoopMode;
extern id const NSApp;
static void fenster_draw_rect(id v, SEL s, CGRect r) {
(void)r, (void)s;
struct fenster *f = (struct fenster *)objc_getAssociatedObject(v, "fenster");
CGContextRef context =
msg(CGContextRef, msg(id, cls("NSGraphicsContext"), "currentContext"),
"graphicsPort");
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGDataProviderRef provider = CGDataProviderCreateWithData(
NULL, f->buf, f->width * f->height * 4, NULL);
CGImageRef img =
CGImageCreate(f->width, f->height, 8, 32, f->width * 4, space,
kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little,
provider, NULL, false, kCGRenderingIntentDefault);
CGColorSpaceRelease(space);
CGDataProviderRelease(provider);
CGContextDrawImage(context, CGRectMake(0, 0, f->width, f->height), img);
CGImageRelease(img);
}
static BOOL fenster_should_close(id v, SEL s, id w) {
(void)v, (void)s, (void)w;
msg1(void, NSApp, "terminate:", id, NSApp);
return YES;
}
FENSTER_API int fenster_open(struct fenster *f) {
msg(id, cls("NSApplication"), "sharedApplication");
msg1(void, NSApp, "setActivationPolicy:", NSInteger, 0);
f->wnd = msg4(id, msg(id, cls("NSWindow"), "alloc"),
"initWithContentRect:styleMask:backing:defer:", CGRect,
CGRectMake(0, 0, f->width, f->height), NSUInteger, 3,
NSUInteger, 2, BOOL, NO);
Class windelegate =
objc_allocateClassPair((Class)cls("NSObject"), "FensterDelegate", 0);
class_addMethod(windelegate, sel_getUid("windowShouldClose:"),
(IMP)fenster_should_close, "c@:@");
objc_registerClassPair(windelegate);
msg1(void, f->wnd, "setDelegate:", id,
msg(id, msg(id, (id)windelegate, "alloc"), "init"));
Class c = objc_allocateClassPair((Class)cls("NSView"), "FensterView", 0);
class_addMethod(c, sel_getUid("drawRect:"), (IMP)fenster_draw_rect, "i@:@@");
objc_registerClassPair(c);
id v = msg(id, msg(id, (id)c, "alloc"), "init");
msg1(void, f->wnd, "setContentView:", id, v);
objc_setAssociatedObject(v, "fenster", (id)f, OBJC_ASSOCIATION_ASSIGN);
id title = msg1(id, cls("NSString"), "stringWithUTF8String:", const char *,
f->title);
msg1(void, f->wnd, "setTitle:", id, title);
msg1(void, f->wnd, "makeKeyAndOrderFront:", id, nil);
msg(void, f->wnd, "center");
msg1(void, NSApp, "activateIgnoringOtherApps:", BOOL, YES);
return 0;
}
FENSTER_API void fenster_close(struct fenster *f) {
msg(void, f->wnd, "close");
}
// clang-format off
static const uint8_t FENSTER_KEYCODES[128] = {65,83,68,70,72,71,90,88,67,86,0,66,81,87,69,82,89,84,49,50,51,52,54,53,61,57,55,45,56,48,93,79,85,91,73,80,10,76,74,39,75,59,92,44,47,78,77,46,9,32,96,8,0,27,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,2,3,127,0,5,0,4,0,20,19,18,17,0};
// clang-format on
FENSTER_API int fenster_loop(struct fenster *f) {
msg1(void, msg(id, f->wnd, "contentView"), "setNeedsDisplay:", BOOL, YES);
id ev = msg4(id, NSApp,
"nextEventMatchingMask:untilDate:inMode:dequeue:", NSUInteger,
NSUIntegerMax, id, NULL, id, NSDefaultRunLoopMode, BOOL, YES);
if (!ev)
return 0;
NSUInteger evtype = msg(NSUInteger, ev, "type");
switch (evtype) {
case 1: /* NSEventTypeMouseDown */
f->mouse |= 1;
break;
case 2: /* NSEventTypeMouseUp*/
f->mouse &= ~1;
break;
case 5:
case 6: { /* NSEventTypeMouseMoved */
CGPoint xy = msg(CGPoint, ev, "locationInWindow");
f->x = (int)xy.x;
f->y = (int)(f->height - xy.y);
return 0;
}
case 10: /*NSEventTypeKeyDown*/
case 11: /*NSEventTypeKeyUp:*/ {
NSUInteger k = msg(NSUInteger, ev, "keyCode");
f->keys[k < 127 ? FENSTER_KEYCODES[k] : 0] = evtype == 10;
NSUInteger mod = msg(NSUInteger, ev, "modifierFlags") >> 17;
f->mod = (mod & 0xc) | ((mod & 1) << 1) | ((mod >> 1) & 1);
return 0;
}
}
msg1(void, NSApp, "sendEvent:", id, ev);
return 0;
}
#elif defined(_WIN32)
// clang-format off
static const uint8_t FENSTER_KEYCODES[] = {0,27,49,50,51,52,53,54,55,56,57,48,45,61,8,9,81,87,69,82,84,89,85,73,79,80,91,93,10,0,65,83,68,70,71,72,74,75,76,59,39,96,0,92,90,88,67,86,66,78,77,44,46,47,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,17,3,0,20,0,19,0,5,18,4,26,127};
// clang-format on
typedef struct BINFO{
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[3];
}BINFO;
static LRESULT CALLBACK fenster_wndproc(HWND hwnd, UINT msg, WPARAM wParam,
LPARAM lParam) {
struct fenster *f = (struct fenster *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
switch (msg) {
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP hbmp = CreateCompatibleBitmap(hdc, f->width, f->height);
HBITMAP oldbmp = (HBITMAP)SelectObject(memdc, hbmp);
BINFO bi = {{sizeof(bi), f->width, -f->height, 1, 32, BI_BITFIELDS}};
bi.bmiColors[0].rgbRed = 0xff;
bi.bmiColors[1].rgbGreen = 0xff;
bi.bmiColors[2].rgbBlue = 0xff;
SetDIBitsToDevice(memdc, 0, 0, f->width, f->height, 0, 0, 0, f->height,
f->buf, (BITMAPINFO *)&bi, DIB_RGB_COLORS);
BitBlt(hdc, 0, 0, f->width, f->height, memdc, 0, 0, SRCCOPY);
SelectObject(memdc, oldbmp);
DeleteObject(hbmp);
DeleteDC(memdc);
EndPaint(hwnd, &ps);
} break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
f->mouse = (msg == WM_LBUTTONDOWN);
break;
case WM_MOUSEMOVE:
f->y = HIWORD(lParam), f->x = LOWORD(lParam);
break;
case WM_KEYDOWN:
case WM_KEYUP: {
f->mod = ((GetKeyState(VK_CONTROL) & 0x8000) >> 15) |
((GetKeyState(VK_SHIFT) & 0x8000) >> 14) |
((GetKeyState(VK_MENU) & 0x8000) >> 13) |
(((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & 0x8000) >> 12);
f->keys[FENSTER_KEYCODES[HIWORD(lParam) & 0x1ff]] = !((lParam >> 31) & 1);
} break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
FENSTER_API int fenster_open(struct fenster *f) {
HINSTANCE hInstance = GetModuleHandle(NULL);
WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_VREDRAW | CS_HREDRAW;
wc.lpfnWndProc = fenster_wndproc;
wc.hInstance = hInstance;
wc.lpszClassName = f->title;
RegisterClassEx(&wc);
f->hwnd = CreateWindowEx(WS_EX_CLIENTEDGE, f->title, f->title,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
f->width, f->height, NULL, NULL, hInstance, NULL);
if (f->hwnd == NULL)
return -1;
SetWindowLongPtr(f->hwnd, GWLP_USERDATA, (LONG_PTR)f);
ShowWindow(f->hwnd, SW_NORMAL);
UpdateWindow(f->hwnd);
return 0;
}
FENSTER_API void fenster_close(struct fenster *f) { (void)f; }
FENSTER_API int fenster_loop(struct fenster *f) {
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT)
return -1;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
InvalidateRect(f->hwnd, NULL, TRUE);
return 0;
}
#else
// clang-format off
static int FENSTER_KEYCODES[124] = {XK_BackSpace,8,XK_Delete,127,XK_Down,18,XK_End,5,XK_Escape,27,XK_Home,2,XK_Insert,26,XK_Left,20,XK_Page_Down,4,XK_Page_Up,3,XK_Return,10,XK_Right,19,XK_Tab,9,XK_Up,17,XK_apostrophe,39,XK_backslash,92,XK_bracketleft,91,XK_bracketright,93,XK_comma,44,XK_equal,61,XK_grave,96,XK_minus,45,XK_period,46,XK_semicolon,59,XK_slash,47,XK_space,32,XK_a,65,XK_b,66,XK_c,67,XK_d,68,XK_e,69,XK_f,70,XK_g,71,XK_h,72,XK_i,73,XK_j,74,XK_k,75,XK_l,76,XK_m,77,XK_n,78,XK_o,79,XK_p,80,XK_q,81,XK_r,82,XK_s,83,XK_t,84,XK_u,85,XK_v,86,XK_w,87,XK_x,88,XK_y,89,XK_z,90,XK_0,48,XK_1,49,XK_2,50,XK_3,51,XK_4,52,XK_5,53,XK_6,54,XK_7,55,XK_8,56,XK_9,57};
// clang-format on
FENSTER_API int fenster_open(struct fenster *f) {
f->dpy = XOpenDisplay(NULL);
int screen = DefaultScreen(f->dpy);
f->w = XCreateSimpleWindow(f->dpy, RootWindow(f->dpy, screen), 0, 0, f->width,
f->height, 0, BlackPixel(f->dpy, screen),
WhitePixel(f->dpy, screen));
f->gc = XCreateGC(f->dpy, f->w, 0, 0);
XSelectInput(f->dpy, f->w,
ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask |
ButtonReleaseMask | PointerMotionMask);
XStoreName(f->dpy, f->w, f->title);
XMapWindow(f->dpy, f->w);
XSync(f->dpy, f->w);
f->img = XCreateImage(f->dpy, DefaultVisual(f->dpy, 0), 24, ZPixmap, 0,
(char *)f->buf, f->width, f->height, 32, 0);
return 0;
}
FENSTER_API void fenster_close(struct fenster *f) { XCloseDisplay(f->dpy); }
FENSTER_API int fenster_loop(struct fenster *f) {
XEvent ev;
XPutImage(f->dpy, f->w, f->gc, f->img, 0, 0, 0, 0, f->width, f->height);
XFlush(f->dpy);
while (XPending(f->dpy)) {
XNextEvent(f->dpy, &ev);
switch (ev.type) {
case ButtonPress:
case ButtonRelease:
f->mouse = (ev.type == ButtonPress);
break;
case MotionNotify:
f->x = ev.xmotion.x, f->y = ev.xmotion.y;
break;
case KeyPress:
case KeyRelease: {
int m = ev.xkey.state;
int k = XkbKeycodeToKeysym(f->dpy, ev.xkey.keycode, 0, 0);
for (unsigned int i = 0; i < 124; i += 2) {
if (FENSTER_KEYCODES[i] == k) {
f->keys[FENSTER_KEYCODES[i + 1]] = (ev.type == KeyPress);
break;
}
}
f->mod = (!!(m & ControlMask)) | (!!(m & ShiftMask) << 1) |
(!!(m & Mod1Mask) << 2) | (!!(m & Mod4Mask) << 3);
} break;
}
}
return 0;
}
#endif
#ifdef _WIN32
FENSTER_API void fenster_sleep(int64_t ms) { Sleep(ms); }
FENSTER_API int64_t fenster_time() {
LARGE_INTEGER freq, count;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&count);
return (int64_t)(count.QuadPart * 1000.0 / freq.QuadPart);
}
#else
FENSTER_API void fenster_sleep(int64_t ms) {
struct timespec ts;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000;
nanosleep(&ts, NULL);
}
FENSTER_API int64_t fenster_time(void) {
struct timespec time;
clock_gettime(CLOCK_REALTIME, &time);
return time.tv_sec * 1000 + (time.tv_nsec / 1000000);
}
#endif
#ifdef __cplusplus
class Fenster {
public:
struct fenster f;
int64_t now;
Fenster(const int w, const int h, const char *title)
: f{.title = title, .width = w, .height = h} {
this->f.buf = new uint32_t[w * h];
this->now = fenster_time();
fenster_open(&this->f);
}
~Fenster() {
fenster_close(&this->f);
delete[] this->f.buf;
}
bool loop(const int fps) {
int64_t t = fenster_time();
if (t - this->now < 1000 / fps) {
fenster_sleep(t - now);
}
this->now = t;
return fenster_loop(&this->f) == 0;
}
inline uint32_t &px(const int x, const int y) {
return fenster_pixel(&this->f, x, y);
}
bool key(int c) { return c >= 0 && c < 128 ? this->f.keys[c] : false; }
int x() { return this->f.x; }
int y() { return this->f.y; }
int mouse() { return this->f.mouse; }
int mod() { return this->f.mod; }
int width() { return this->f.width; }
int height() { return this->f.height; }
};
#endif /* __cplusplus */
#endif /* !FENSTER_HEADER */
#endif /* FENSTER_H */

View file

@ -1,32 +0,0 @@
#include "fenster/fenster.h"
#include "miniaudio.h"
#define W 480*2
#define H 480
static int run() {
Fenster f(W, H, "hello c++");
int t = 0;
while (f.loop(60)) {
for (int i = 0; i < W; i++) {
for (int j = 0; j < H; j++) {
f.px(i, j) = i ^ j ^ t;
}
}
if (f.key(0x1b)) {
break;
}
t++;
}
return 0;
}
#if defined(_WIN32)
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine,
int nCmdShow) {
(void)hInstance, (void)hPrevInstance, (void)pCmdLine, (void)nCmdShow;
return run();
}
#else
int main() { return run(); }
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,334 +0,0 @@
/*
QuickCG SDL2 20190709
Copyright (c) 2004-2007, Lode Vandevenne
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
QuickCG is an SDL 2.0 codebase that wraps some of the SDL 2.0 functionality.
It's used by Lode's Computer Graphics Tutorial to work with simple function calls
to demonstrate graphical programs. It may or may not be of industrial strength
for games, though I've actually used it for some.
QuickCG can handle some things that standard C++ does not but that are useful, such as:
-drawing graphics
-a bitmap font
-simplified saving and loading of files
-reading keyboard and mouse input
-playing sound
-color models
-loading images
Contact info:
My email address is (puzzle the account and domain together with an @ symbol):
Domain: gmail dot com.
Account: lode dot vandevenne.
*/
#ifndef _quickcg_h_included
#define _quickcg_h_included
#include <SDL.h>
#include <string>
#include <sstream>
#include <iomanip>
#include <vector>
#include <algorithm> //std::min and std::max
namespace QuickCG
{
////////////////////////////////////////////////////////////////////////////////
//useful templates//////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//don't know why, but the standard C++ abs sometimes gives cryptic errors? if so use this :D
template<typename T>
const T template_abs(const T &a)
{
return (a < 0) ? -a : a;
}
//usage: std::string str = valtostr(25454.91654654f);
template<typename T>
std::string valtostr(const T& val)
{
std::ostringstream sstream;
sstream << val;
return sstream.str();
}
//usage: double val = strtoval<double>("465498.654");
template<typename T>
T strtoval(const std::string& s)
{
std::istringstream sstream(s);
T val;
sstream >> val;
return val;
}
//length is decimal precision of the floating point number
template<typename T>
std::string valtostr(const T& val, int length, bool fixed = true)
{
std::ostringstream sstream;
if(fixed) sstream << std::fixed;
sstream << std::setprecision(length) << val;
return sstream.str();
}
////////////////////////////////////////////////////////////////////////////////
//COLOR STRUCTS/////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
struct ColorRGBA8bit;
//a color with 4 components: r, g, b and a
struct ColorRGBA
{
int r;
int g;
int b;
int a;
ColorRGBA(Uint8 r, Uint8 g, Uint8 b, Uint8 a);
ColorRGBA(const ColorRGBA8bit& color);
ColorRGBA();
};
ColorRGBA operator+(const ColorRGBA& color, const ColorRGBA& color2);
ColorRGBA operator-(const ColorRGBA& color, const ColorRGBA& color2);
ColorRGBA operator*(const ColorRGBA& color, int a);
ColorRGBA operator*(int a, const ColorRGBA& color);
ColorRGBA operator/(const ColorRGBA& color, int a);
ColorRGBA overlay(const ColorRGBA& color, const ColorRGBA& color2);
bool operator==(const ColorRGBA& color, const ColorRGBA& color2);
bool operator!=(const ColorRGBA& color, const ColorRGBA& color2);
static const ColorRGBA RGB_Black ( 0, 0, 0, 255);
static const ColorRGBA RGB_Red (255, 0, 0, 255);
static const ColorRGBA RGB_Green ( 0, 255, 0, 255);
static const ColorRGBA RGB_Blue ( 0, 0, 255, 255);
static const ColorRGBA RGB_Cyan ( 0, 255, 255, 255);
static const ColorRGBA RGB_Magenta (255, 0, 255, 255);
static const ColorRGBA RGB_Yellow (255, 255, 0, 255);
static const ColorRGBA RGB_White (255, 255, 255, 255);
static const ColorRGBA RGB_Gray (128, 128, 128, 255);
static const ColorRGBA RGB_Grey (192, 192, 192, 255);
static const ColorRGBA RGB_Maroon (128, 0, 0, 255);
static const ColorRGBA RGB_Darkgreen( 0, 128, 0, 255);
static const ColorRGBA RGB_Navy ( 0, 0, 128, 255);
static const ColorRGBA RGB_Teal ( 0, 128, 128, 255);
static const ColorRGBA RGB_Purple (128, 0, 128, 255);
static const ColorRGBA RGB_Olive (128, 128, 0, 255);
//a color with 4 components: r, g, b and a
struct ColorRGBA8bit
{
Uint8 r;
Uint8 g;
Uint8 b;
Uint8 a;
ColorRGBA8bit(Uint8 r, Uint8 g, Uint8 b, Uint8 a);
ColorRGBA8bit(const ColorRGBA& color);
ColorRGBA8bit();
};
//a color with 3 components: h, s and l
struct ColorHSL
{
int h;
int s;
int l;
int a;
ColorHSL(Uint8 h, Uint8 s, Uint8 l, Uint8 a);
ColorHSL();
};
//a color with 3 components: h, s and v
struct ColorHSV
{
int h;
int s;
int v;
int a;
ColorHSV(Uint8 h, Uint8 s, Uint8 v, Uint8 a);
ColorHSV();
};
////////////////////////////////////////////////////////////////////////////////
//GLOBAL VARIABLES//////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
extern int w;
extern int h;
////////////////////////////////////////////////////////////////////////////////
//KEYBOARD FUNCTIONS////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
bool keyDown(int key); //this checks if the key is held down, returns true all the time until the key is up
bool keyPressed(int key); //this checks if the key is *just* pressed, returns true only once until the key is up again
////////////////////////////////////////////////////////////////////////////////
//BASIC SCREEN FUNCTIONS////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void screen(int width = 640, int height = 400, bool fullscreen = 0, const std::string& text = " ");
void lock();
void unlock();
void redraw();
void cls(const ColorRGBA& color = RGB_Black);
void pset(int x, int y, const ColorRGBA& color);
ColorRGBA pget(int x, int y);
void drawBuffer(Uint32* buffer);
bool onScreen(int x, int y);
////////////////////////////////////////////////////////////////////////////////
//NON GRAPHICAL FUNCTIONS///////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void sleep();
void sleep(double seconds);
void waitFrame(double oldTime, double frameDuration); //in seconds
bool done(bool quit_if_esc = true, bool delay = true);
void end();
void readKeys();
void getMouseState(int& mouseX, int& mouseY);
void getMouseState(int& mouseX, int& mouseY, bool& LMB, bool& RMB);
unsigned long getTicks(); //ticks in milliseconds
inline double getTime() { return getTicks() / 1000.0; } //time in seconds
////////////////////////////////////////////////////////////////////////////////
//2D SHAPES/////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
bool horLine(int y, int x1, int x2, const ColorRGBA& color);
bool verLine(int x, int y1, int y2, const ColorRGBA& color);
bool drawLine(int x1, int y1, int x2, int y2, const ColorRGBA& color);
bool drawCircle(int xc, int yc, int radius, const ColorRGBA& color);
bool drawDisk(int xc, int yc, int radius, const ColorRGBA& color);
bool drawRect(int x1, int y1, int x2, int y2, const ColorRGBA& color);
bool clipLine(int x1,int y1,int x2, int y2, int & x3, int & y3, int & x4, int & y4);
////////////////////////////////////////////////////////////////////////////////
//COLOR CONVERSIONS/////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
ColorHSL RGBtoHSL(const ColorRGBA& ColorRGBA);
ColorRGBA HSLtoRGB(const ColorHSL& colorHSL);
ColorHSV RGBtoHSV(const ColorRGBA& ColorRGBA);
ColorRGBA HSVtoRGB(const ColorHSV& colorHSV);
Uint32 RGBtoINT(const ColorRGBA& ColorRGBA);
ColorRGBA INTtoRGB(Uint32 colorINT);
////////////////////////////////////////////////////////////////////////////////
//FILE FUNCTIONS////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void loadFile(std::vector<unsigned char>& buffer, const std::string& filename);
void saveFile(const std::vector<unsigned char>& buffer, const std::string& filename);
////////////////////////////////////////////////////////////////////////////////
//IMAGE FUNCTIONS///////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int loadImage(std::vector<ColorRGBA>& out, unsigned long& w, unsigned long& h, const std::string& filename);
int loadImage(std::vector<Uint32>& out, unsigned long& w, unsigned long& h, const std::string& filename);
int decodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32 = true);
int decodePNG(std::vector<unsigned char>& out_image_32bit, unsigned long& image_width, unsigned long& image_height, const std::vector<unsigned char>& in_png);
////////////////////////////////////////////////////////////////////////////////
//TEXT FUNCTIONS////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
extern bool font[256][8][8];
void drawLetter(unsigned char n, int x, int y, const ColorRGBA& color = RGB_White, bool bg = 0, const ColorRGBA& color2 = RGB_Black);
int printString(const std::string& text, int x = 0, int y = 0, const ColorRGBA& color = RGB_White, bool bg = 0, const ColorRGBA& color2 = RGB_Black, int forceLength = 0);
//print something (string, int, float, ...)
template<typename T>
int print(const T& val, int x = 0, int y = 0, const ColorRGBA& color = RGB_White, bool bg = 0, const ColorRGBA& color2 = RGB_Black, int forceLength = 0)
{
std::string text = valtostr(val);
return printString(text, x, y, color, bg, color2, forceLength);
}
//print some floating point number, this one allows printing floating point numbers with limited length
template<typename T>
int fprint(const T& val, int length, int x = 0, int y = 0, const ColorRGBA& color = RGB_White, bool bg = 0, const ColorRGBA& color2 = RGB_Black, int forceLength = 0)
{
std::string text = valtostr(val, length, true);
return printString(text, x, y, color, bg, color2, forceLength);
}
////////////////////////////////////////////////////////////////////////////////
//TEXT INPUT FUNCTIONS//////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
Uint8 getInputCharacter();
void getInputString(std::string& text, const std::string& message = "", bool clear = false, int x = 0, int y = 0, const ColorRGBA& color = RGB_White, bool bg = 0, const ColorRGBA& color2 = RGB_Black);
template<typename T>
T getInput(const std::string& message = "", bool clear = false, int x = 0, int y = 0, const ColorRGBA& color = RGB_White, bool bg = 0, const ColorRGBA& color2 = RGB_Black)
{
std::string text;
getInputString(text, message, clear, x, y, color, bg, color2);
return strtoval<T>(text);
}
////////////////////////////////////////////////////////////////////////////////
//SOUNDCARD FUNCTIONS///////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int audioOpen(int samplerate, int framesize); //always 16-bit mono sound for now; returns 0 if no error happened
void audioClose();
int audioReOpen(); //closes and opens again with same parameters
/*
push samples to the soundcard, making sure not to cause shortage or overflow
pos and end are the range in the samples vector that you want to push to the audio card
*/
void audioPushSamples(const std::vector<double>& samples, size_t pos, size_t end);
size_t audioSamplesShortage(); //returns value > 0 if the soundcard is consuming more samples than you're producing
size_t audioSamplesOverflow(); //returns value > 0 if you're producing more samples than the soundard is consuming - so take it easy a bit
void audioSetBufferSamplesRange(size_t min_samples, size_t max_samples); //set shortage and overflow values. E.g. 4096 and 8192.
/*
This plays the sound starting at this time, until it's done
The difference with audioPushSamples is:
audioPlay allows playing multiple sounds at the same time: it doesn't push at the end,
but elementwise-adds or pushes back samples if needed.
The duration depends on samplerate, make sure the samples in the vector have the correct samplerate.
*/
void audioPlay(const std::vector<double>& samples);
void audioSetMode(int mode); //0: silent, 1: full (no volume calculations ==> faster), 2: volume-controlled (= default value)
void audioSetVolume(double volume); //multiplier used if mode is 2 (volume-controlled). Default value is 1.0.
} //end of namespace QuickCG
#endif

View file

@ -1,187 +0,0 @@
#include <fmt/core.h>
#include <SFML/Graphics.hpp>
#include <numbers>
#include <cmath>
#include "matrix.hpp"
#include <cstdlib>
using matrix::Matrix;
using namespace fmt;
Matrix MAP{
{1,1,1,1,1,1,1,1,1},
{1,0,1,0,0,0,0,0,1},
{1,0,1,0,0,1,1,0,1},
{1,0,0,0,0,0,0,0,1},
{1,1,0,0,0,0,0,0,1},
{1,0,0,1,1,1,0,0,1},
{1,0,0,0,1,0,0,0,1},
{1,0,0,0,0,0,1,1,1},
{1,1,1,1,1,1,1,1,1}
};
const int SCREEN_HEIGHT=480;
const int SCREEN_WIDTH=SCREEN_HEIGHT * 2;
const int MAP_SIZE=matrix::width(MAP);
const int TILE_SIZE=(SCREEN_WIDTH/2) / MAP_SIZE;
const float FOV = std::numbers::pi / 3.0;
const float HALF_FOV = FOV / 2;
const int CASTED_RAYS=30;
const float STEP_ANGLE = FOV / CASTED_RAYS;
const int MAX_DEPTH = MAP_SIZE * TILE_SIZE;
const float SCALE = (SCREEN_WIDTH / 2) / CASTED_RAYS;
float player_x = SCREEN_WIDTH / 4;
float player_y = SCREEN_WIDTH / 4;
float player_angle = std::numbers::pi;
void draw_rect(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size, uint8_t color) {
sf::RectangleShape rect(size);
rect.setFillColor({color, color, color});
rect.setPosition(pos);
window.draw(rect);
}
void draw_map_rect(sf::RenderWindow &window, int x, int y, uint8_t color) {
draw_rect(window,
{float(x * TILE_SIZE), float(y * TILE_SIZE)},
{float(TILE_SIZE-1), float(TILE_SIZE-1)},
color);
}
void draw_map(sf::RenderWindow &window, Matrix &map) {
uint8_t light_grey = 191;
uint8_t dark_grey = 65;
for(size_t y = 0; y < matrix::height(map); y++) {
for(size_t x = 0; x < matrix::width(map); x++) {
draw_map_rect(window, x, y, map[y][x] == 1 ? light_grey : dark_grey);
}
}
}
void draw_line(sf::RenderWindow &window, sf::Vector2f start, sf::Vector2f end) {
sf::Vertex line[] = {
sf::Vertex(start),
sf::Vertex(end)
};
window.draw(line, 2, sf::Lines);
}
void draw_map_rays(sf::RenderWindow &window, int col, int row, sf::Vector2f target) {
draw_map_rect(window, col, row, 100);
draw_line(window, {player_x, player_y}, target);
}
void draw_3d_view(sf::RenderWindow &window, int depth, float start_angle, int ray) {
uint8_t color = 255 / (1 + depth * depth * 0.0001);
float fixed_depth = depth * std::cos(player_angle - start_angle);
float wall_height = 21000 / fixed_depth;
if(wall_height > SCREEN_HEIGHT){
wall_height = SCREEN_HEIGHT;
}
draw_rect(window,
{SCREEN_HEIGHT + ray * SCALE, (SCREEN_HEIGHT / 2) - wall_height / 2},
{SCALE, wall_height},
color);
}
void ray_casting(sf::RenderWindow &window, Matrix& map) {
float start_angle = player_angle - HALF_FOV;
for(int ray = 0; ray < CASTED_RAYS; ray++, start_angle += STEP_ANGLE)
{
for(int depth = 1; depth < MAX_DEPTH; depth++) {
float target_x = player_x - std::sin(start_angle) * depth;
float target_y = player_y + std::cos(start_angle) * depth;
int col = int(target_x / TILE_SIZE);
int row = int(target_y / TILE_SIZE);
if(map[row][col] == 1) {
draw_map_rays(window, col, row, {target_x, target_y});
draw_3d_view(window, depth, start_angle, ray);
break;
}
}
}
}
void draw_ceiling_floor(sf::RenderWindow &window) {
draw_rect(window,
{SCREEN_HEIGHT, SCREEN_HEIGHT /2},
{SCREEN_HEIGHT, SCREEN_HEIGHT},
100);
draw_rect(window,
{SCREEN_HEIGHT, (SCREEN_HEIGHT * -1) / 2},
{SCREEN_HEIGHT, SCREEN_HEIGHT},
200);
}
void draw_everything(sf::RenderWindow &window) {
draw_map(window, MAP);
draw_ceiling_floor(window);
ray_casting(window, MAP);
window.display();
}
bool collision(float x, float y) {
int col = int(x / TILE_SIZE);
int row = int(y / TILE_SIZE);
return MAP[row][col] == 1;
}
int main() {
using KB = sf::Keyboard;
sf::RenderWindow window(sf::VideoMode(SCREEN_WIDTH, SCREEN_HEIGHT), "Raycaster");
window.setVerticalSyncEnabled(true);
while(window.isOpen()) {
draw_everything(window);
float x = player_x;
float y = player_y;
if(KB::isKeyPressed(KB::Q)) {
player_angle -= 0.1;
} else if(KB::isKeyPressed(KB::E)) {
player_angle += 0.1;
}
if(KB::isKeyPressed(KB::W)) {
x += -1 * std::sin(player_angle) * 5;
y += std::cos(player_angle) * 5;
} else if(KB::isKeyPressed(KB::S)) {
x -= -1 * std::sin(player_angle) * 5;
y -= std::cos(player_angle) * 5;
}
if(KB::isKeyPressed(KB::D)) {
x += -1 * std::sin(player_angle + std::numbers::pi * 0.5) * 5;
y += std::cos(player_angle + std::numbers::pi * 0.5) * 5;
} else if(KB::isKeyPressed(KB::A)) {
x -= -1 * std::sin(player_angle + std::numbers::pi * 0.5) * 5;
y -= std::cos(player_angle + std::numbers::pi * 0.5) * 5;
}
if(!collision(x, y)) {
player_x = x;
player_y = y;
}
sf::Event event;
while(window.pollEvent(event)) {
if(event.type == sf::Event::Closed) {
window.close();
}
}
}
return 0;
}

View file

@ -1,250 +0,0 @@
/*
Copyright (c) 2004-2021, Lode Vandevenne
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <cmath>
#include <string>
#include <vector>
#include <iostream>
#include "quickcg.h"
using namespace QuickCG;
/*
g++ *.cpp -lSDL -O3 -W -Wall -ansi -pedantic
g++ *.cpp -lSDL
*/
//place the example code below here:
#define screenWidth 640
#define screenHeight 480
#define mapWidth 24
#define mapHeight 24
int worldMap[mapWidth][mapHeight]=
{
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,2,2,2,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1},
{1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,3,0,0,0,3,0,0,0,1},
{1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,2,2,0,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,4,0,0,0,0,5,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,4,0,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
};
int main(int /*argc*/, char */*argv*/[])
{
double posX = 22, posY = 12; //x and y start position
double dirX = -1, dirY = 0; //initial direction vector
double planeX = 0, planeY = 0.66; //the 2d raycaster version of camera plane
double time = 0; //time of current frame
double oldTime = 0; //time of previous frame
screen(screenWidth, screenHeight, 0, "Raycaster");
while(!done())
{
for(int x = 0; x < w; x++)
{
//calculate ray position and direction
double cameraX = 2 * x / (double)w - 1; //x-coordinate 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 position 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
//these are derived as:
//deltaDistX = sqrt(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX))
//deltaDistY = sqrt(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY))
//which can be simplified to abs(|rayDir| / rayDirX) and abs(|rayDir| / rayDirY)
//where |rayDir| is the length of the vector (rayDirX, rayDirY). Its length,
//unlike (dirX, dirY) is not 1, however this does not matter, only the
//ratio between deltaDistX and deltaDistY matters, due to the way the DDA
//stepping further below works. So the values can be computed as below.
// Division through zero is prevented, even though technically that's not
// needed in C++ with IEEE 754 floating point values.
double deltaDistX = (rayDirX == 0) ? 1e30 : std::abs(1 / rayDirX);
double deltaDistY = (rayDirY == 0) ? 1e30 : std::abs(1 / rayDirY);
double perpWallDist;
//what direction to step in x or y-direction (either +1 or -1)
int stepX;
int stepY;
int hit = 0; //was there a wall hit?
int side; //was a NS or a EW wall hit?
//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)
{
//jump to next map square, either in x-direction, or in y-direction
if(sideDistX < sideDistY)
{
sideDistX += deltaDistX;
mapX += stepX;
side = 0;
}
else
{
sideDistY += deltaDistY;
mapY += stepY;
side = 1;
}
//Check if ray has hit a wall
if(worldMap[mapX][mapY] > 0) hit = 1;
}
//Calculate distance projected on camera direction. This is the shortest distance from the point where the wall is
//hit to the camera plane. Euclidean to center camera point would give fisheye effect!
//This can be computed as (mapX - posX + (1 - stepX) / 2) / rayDirX for side == 0, or same formula with Y
//for size == 1, but can be simplified to the code below thanks to how sideDist and deltaDist are computed:
//because they were left scaled to |rayDir|. sideDist is the entire length of the ray above after the multiple
//steps, but we subtract deltaDist once because one step more into the wall was taken above.
if(side == 0) perpWallDist = (sideDistX - deltaDistX);
else perpWallDist = (sideDistY - deltaDistY);
//Calculate height of line to draw on screen
int lineHeight = (int)(h / perpWallDist);
//calculate lowest and highest pixel to fill in current stripe
int drawStart = -lineHeight / 2 + h / 2;
if(drawStart < 0) drawStart = 0;
int drawEnd = lineHeight / 2 + h / 2;
if(drawEnd >= h) drawEnd = h - 1;
//choose wall color
ColorRGBA color;
switch(worldMap[mapX][mapY])
{
case 1: color = RGB_Red; break; //red
case 2: color = RGB_Green; break; //green
case 3: color = RGB_Blue; break; //blue
case 4: color = RGB_White; break; //white
default: color = RGB_Yellow; break; //yellow
}
//give x and y sides different brightness
if(side == 1) {color = color / 2;}
//draw the pixels of the stripe as a vertical line
verLine(x, drawStart, drawEnd, color);
}
//timing for input and FPS counter
oldTime = time;
time = getTicks();
double frameTime = (time - oldTime) / 1000.0; //frameTime is the time this frame has taken, in seconds
print(1.0 / frameTime); //FPS counter
redraw();
//speed modifiers
double moveSpeed = frameTime * 5.0; //the constant value is in squares/second
double rotSpeed = frameTime * 3.0; //the constant value is in radians/second
SDL_Event event;
while(SDL_PollEvent(&event)) {
if(event.type != SDL_KEYDOWN) continue;
cls();
//move forward if no wall in front of you
if(event.key.keysym.sym == SDLK_UP)
{
if(worldMap[int(posX + dirX * moveSpeed)][int(posY)] == false) posX += dirX * moveSpeed;
if(worldMap[int(posX)][int(posY + dirY * moveSpeed)] == false) posY += dirY * moveSpeed;
}
//move backwards if no wall behind you
if(event.key.keysym.sym == SDLK_DOWN)
{
if(worldMap[int(posX - dirX * moveSpeed)][int(posY)] == false) posX -= dirX * moveSpeed;
if(worldMap[int(posX)][int(posY - dirY * moveSpeed)] == false) posY -= dirY * moveSpeed;
}
//rotate to the right
if(event.key.keysym.sym == SDLK_RIGHT)
{
//both camera direction and camera plane must be rotated
double oldDirX = dirX;
dirX = dirX * cos(-rotSpeed) - dirY * sin(-rotSpeed);
dirY = oldDirX * sin(-rotSpeed) + dirY * cos(-rotSpeed);
double oldPlaneX = planeX;
planeX = planeX * cos(-rotSpeed) - planeY * sin(-rotSpeed);
planeY = oldPlaneX * sin(-rotSpeed) + planeY * cos(-rotSpeed);
}
//rotate to the left
if(event.key.keysym.sym == SDLK_LEFT)
{
//both camera direction and camera plane must be rotated
double oldDirX = dirX;
dirX = dirX * cos(rotSpeed) - dirY * sin(rotSpeed);
dirY = oldDirX * sin(rotSpeed) + dirY * cos(rotSpeed);
double oldPlaneX = planeX;
planeX = planeX * cos(rotSpeed) - planeY * sin(rotSpeed);
planeY = oldPlaneX * sin(rotSpeed) + planeY * cos(rotSpeed);
}
}
}
return 0;
}

View file

@ -1,406 +0,0 @@
/*
Copyright (c) 2004-2019, Lode Vandevenne
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <cmath>
#include <string>
#include <vector>
#include <iostream>
#include "quickcg.h"
using namespace QuickCG;
/*
g++ *.cpp -lSDL -O3 -W -Wall -ansi -pedantic
g++ *.cpp -lSDL
*/
// set to 1 to use the horizontal floor algorithm (contributed by Ádám Tóth in 2019),
// or to 0 to use the slower vertical floor algorithm.
#define FLOOR_HORIZONTAL 1
#define screenWidth 640
#define screenHeight 480
#define texWidth 64 // must be power of two
#define texHeight 64 // must be power of two
#define mapWidth 24
#define mapHeight 24
int worldMap[mapWidth][mapHeight] =
{
{8,8,8,8,8,8,8,8,8,8,8,4,4,6,4,4,6,4,6,4,4,4,6,4},
{8,0,0,0,0,0,0,0,0,0,8,4,0,0,0,0,0,0,0,0,0,0,0,4},
{8,0,3,3,0,0,0,0,0,8,8,4,0,0,0,0,0,0,0,0,0,0,0,6},
{8,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6},
{8,0,3,3,0,0,0,0,0,8,8,4,0,0,0,0,0,0,0,0,0,0,0,4},
{8,0,0,0,0,0,0,0,0,0,8,4,0,0,0,0,0,6,6,6,0,6,4,6},
{8,8,8,8,0,8,8,8,8,8,8,4,4,4,4,4,4,6,0,0,0,0,0,6},
{7,7,7,7,0,7,7,7,7,0,8,0,8,0,8,0,8,4,0,4,0,6,0,6},
{7,7,0,0,0,0,0,0,7,8,0,8,0,8,0,8,8,6,0,0,0,0,0,6},
{7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,6,0,0,0,0,0,4},
{7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,6,0,6,0,6,0,6},
{7,7,0,0,0,0,0,0,7,8,0,8,0,8,0,8,8,6,4,6,0,6,6,6},
{7,7,7,7,0,7,7,7,7,8,8,4,0,6,8,4,8,3,3,3,0,3,3,3},
{2,2,2,2,0,2,2,2,2,4,6,4,0,0,6,0,6,3,0,0,0,0,0,3},
{2,2,0,0,0,0,0,2,2,4,0,0,0,0,0,0,4,3,0,0,0,0,0,3},
{2,0,0,0,0,0,0,0,2,4,0,0,0,0,0,0,4,3,0,0,0,0,0,3},
{1,0,0,0,0,0,0,0,1,4,4,4,4,4,6,0,6,3,3,0,0,0,3,3},
{2,0,0,0,0,0,0,0,2,2,2,1,2,2,2,6,6,0,0,5,0,5,0,5},
{2,2,0,0,0,0,0,2,2,2,0,0,0,2,2,0,5,0,5,0,0,0,5,5},
{2,0,0,0,0,0,0,0,2,0,0,0,0,0,2,5,0,5,0,5,0,5,0,5},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5},
{2,0,0,0,0,0,0,0,2,0,0,0,0,0,2,5,0,5,0,5,0,5,0,5},
{2,2,0,0,0,0,0,2,2,2,0,0,0,2,2,0,5,0,5,0,0,0,5,5},
{2,2,2,2,1,2,2,2,2,2,2,1,2,2,2,5,5,5,5,5,5,5,5,5}
};
Uint32 buffer[screenHeight][screenWidth]; // y-coordinate first because it works per scanline
int main(int /*argc*/, char */*argv*/[])
{
double posX = 22.0, posY = 11.5; //x and y start position
double dirX = -1.0, dirY = 0.0; //initial direction vector
double planeX = 0.0, planeY = 0.66; //the 2d raycaster version of camera plane
double time = 0; //time of current frame
double oldTime = 0; //time of previous frame
std::vector<Uint32> texture[8];
for(int i = 0; i < 8; i++) texture[i].resize(texWidth * texHeight);
screen(screenWidth,screenHeight, 0, "Raycaster");
//load some textures
unsigned long tw, th, error = 0;
error |= loadImage(texture[0], tw, th, "pics/eagle.png");
error |= loadImage(texture[1], tw, th, "pics/redbrick.png");
error |= loadImage(texture[2], tw, th, "pics/purplestone.png");
error |= loadImage(texture[3], tw, th, "pics/greystone.png");
error |= loadImage(texture[4], tw, th, "pics/bluestone.png");
error |= loadImage(texture[5], tw, th, "pics/mossy.png");
error |= loadImage(texture[6], tw, th, "pics/wood.png");
error |= loadImage(texture[7], tw, th, "pics/colorstone.png");
if(error) { std::cout << "error loading images" << std::endl; return 1; }
//start the main loop
while(!done())
{
#if FLOOR_HORIZONTAL
//FLOOR CASTING
for(int y = screenHeight / 2 + 1; y < screenHeight; ++y)
{
// rayDir for leftmost ray (x = 0) and rightmost ray (x = w)
float rayDirX0 = dirX - planeX;
float rayDirY0 = dirY - planeY;
float rayDirX1 = dirX + planeX;
float rayDirY1 = dirY + planeY;
// Current y position compared to the center of the screen (the horizon)
int p = y - screenHeight / 2;
// Vertical position of the camera.
// NOTE: with 0.5, it's exactly in the center between floor and ceiling,
// matching also how the walls are being raycasted. For different values
// than 0.5, a separate loop must be done for ceiling and floor since
// they're no longer symmetrical.
float posZ = 0.5 * screenHeight;
// 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.
// NOTE: this is affine texture mapping, which is not perspective correct
// except for perfectly horizontal and vertical surfaces like the floor.
// NOTE: this formula is explained as follows: The camera ray goes through
// the following two points: the camera itself, which is at a certain
// height (posZ), and a point in front of the camera (through an imagined
// vertical plane containing the screen pixels) with horizontal distance
// 1 from the camera, and vertical position p lower than posZ (posZ - p). When going
// through that point, the line has vertically traveled by p units and
// horizontally by 1 unit. To hit the floor, it instead needs to travel by
// posZ units. It will travel the same ratio horizontally. The ratio was
// 1 / p for going through the camera plane, so to go posZ times farther
// to reach the floor, we get that the total horizontal distance is posZ / p.
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 weight in the inner loop
float floorStepX = rowDistance * (rayDirX1 - rayDirX0) / screenWidth;
float floorStepY = rowDistance * (rayDirY1 - rayDirY0) / screenWidth;
// 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 < screenWidth; ++x)
{
// the cell coord is simply got from the integer parts of floorX and floorY
int cellX = (int)(floorX);
int cellY = (int)(floorY);
// get the texture coordinate from the fractional part
int tx = (int)(texWidth * (floorX - cellX)) & (texWidth - 1);
int ty = (int)(texHeight * (floorY - cellY)) & (texHeight - 1);
floorX += floorStepX;
floorY += floorStepY;
// choose texture and draw the pixel
int checkerBoardPattern = (int(cellX + cellY)) & 1;
int floorTexture;
if(checkerBoardPattern == 0) floorTexture = 3;
else floorTexture = 4;
int ceilingTexture = 6;
Uint32 color;
// floor
color = texture[floorTexture][texWidth * ty + tx];
color = (color >> 1) & 8355711; // make a bit darker
buffer[y][x] = color;
//ceiling (symmetrical, at screenHeight - y - 1 instead of y)
color = texture[ceilingTexture][texWidth * ty + tx];
color = (color >> 1) & 8355711; // make a bit darker
buffer[screenHeight - y - 1][x] = color;
}
}
#endif // FLOOR_HORIZONTAL
// WALL CASTING
for(int x = 0; x < w; x++)
{
//calculate ray position and direction
double cameraX = 2 * x / double(w) - 1; //x-coordinate 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 position 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 = (rayDirX == 0) ? 1e30 : std::abs(1 / rayDirX);
double deltaDistY = (rayDirY == 0) ? 1e30 : std::abs(1 / rayDirY);
double perpWallDist;
//what direction to step in x or y-direction (either +1 or -1)
int stepX;
int stepY;
int hit = 0; //was there a wall hit?
int side; //was a NS or a EW wall hit?
//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)
{
//jump to next map square, either in x-direction, or in y-direction
if(sideDistX < sideDistY)
{
sideDistX += deltaDistX;
mapX += stepX;
side = 0;
}
else
{
sideDistY += deltaDistY;
mapY += stepY;
side = 1;
}
//Check if ray has hit a wall
if(worldMap[mapX][mapY] > 0) hit = 1;
}
//Calculate distance of perpendicular ray (Euclidean distance would give fisheye effect!)
if(side == 0) perpWallDist = (sideDistX - deltaDistX);
else perpWallDist = (sideDistY - deltaDistY);
//Calculate height of line to draw on screen
int lineHeight = (int)(h / perpWallDist);
//calculate lowest and highest pixel to fill in current stripe
int drawStart = -lineHeight / 2 + h / 2;
if(drawStart < 0) drawStart = 0;
int drawEnd = lineHeight / 2 + h / 2;
if(drawEnd >= h) drawEnd = h - 1;
//texturing calculations
int texNum = worldMap[mapX][mapY] - 1; //1 subtracted from it so that texture 0 can be used!
//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 coordinate on the texture
int texX = int(wallX * double(texWidth));
if(side == 0 && rayDirX > 0) texX = texWidth - texX - 1;
if(side == 1 && rayDirY < 0) texX = texWidth - texX - 1;
// TODO: 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 * texHeight / lineHeight;
// Starting texture coordinate
double texPos = (drawStart - h / 2 + lineHeight / 2) * step;
for(int y = drawStart; y < drawEnd; y++)
{
// Cast the texture coordinate to integer, and mask with (texHeight - 1) in case of overflow
int texY = (int)texPos & (texHeight - 1);
texPos += step;
Uint32 color = texture[texNum][texHeight * texY + texX];
//make color darker for y-sides: R, G and B byte each divided through two with a "shift" and an "and"
if(side == 1) color = (color >> 1) & 8355711;
buffer[y][x] = color;
}
#if !FLOOR_HORIZONTAL
//FLOOR CASTING
double floorXWall, floorYWall; //x, y position of the floor texel at the bottom of the wall
//4 different wall directions possible
if(side == 0 && rayDirX > 0)
{
floorXWall = mapX;
floorYWall = mapY + wallX;
}
else if(side == 0 && rayDirX < 0)
{
floorXWall = mapX + 1.0;
floorYWall = mapY + wallX;
}
else if(side == 1 && rayDirY > 0)
{
floorXWall = mapX + wallX;
floorYWall = mapY;
}
else
{
floorXWall = mapX + wallX;
floorYWall = mapY + 1.0;
}
double distWall, distPlayer, currentDist;
distWall = perpWallDist;
distPlayer = 0.0;
if(drawEnd < 0) drawEnd = h; //becomes < 0 when the integer overflows
//draw the floor from drawEnd to the bottom of the screen
for(int y = drawEnd + 1; y < h; y++)
{
currentDist = h / (2.0 * y - h); //you could make a small lookup table for this instead
double weight = (currentDist - distPlayer) / (distWall - distPlayer);
double currentFloorX = weight * floorXWall + (1.0 - weight) * posX;
double currentFloorY = weight * floorYWall + (1.0 - weight) * posY;
int floorTexX, floorTexY;
floorTexX = int(currentFloorX * texWidth) & (texWidth - 1);
floorTexY = int(currentFloorY * texHeight) & (texHeight - 1);
int checkerBoardPattern = ((int)currentFloorX + (int)currentFloorY) & 1;
int floorTexture;
if(checkerBoardPattern == 0) floorTexture = 3;
else floorTexture = 4;
//floor
buffer[y][x] = (texture[floorTexture][texWidth * floorTexY + floorTexX] >> 1) & 8355711;
//ceiling (symmetrical)
buffer[h - y][x] = texture[6][texWidth * floorTexY + floorTexX];
}
#endif // !FLOOR_HORIZONTAL
}
drawBuffer(buffer[0]);
// No need to clear the screen here, since everything is overdrawn with floor and ceiling
//timing for input and FPS counter
oldTime = time;
time = getTicks();
double frameTime = (time - oldTime) / 1000.0; //frametime is the time this frame has taken, in seconds
print(1.0 / frameTime); //FPS counter
redraw();
//speed modifiers
double moveSpeed = frameTime * 3.0; //the constant value is in squares/second
double rotSpeed = frameTime * 2.0; //the constant value is in radians/second
readKeys();
//move forward if no wall in front of you
if (keyDown(SDLK_UP))
{
if(worldMap[int(posX + dirX * moveSpeed)][int(posY)] == false) posX += dirX * moveSpeed;
if(worldMap[int(posX)][int(posY + dirY * moveSpeed)] == false) posY += dirY * moveSpeed;
}
//move backwards if no wall behind you
if(keyDown(SDLK_DOWN))
{
if(worldMap[int(posX - dirX * moveSpeed)][int(posY)] == false) posX -= dirX * moveSpeed;
if(worldMap[int(posX)][int(posY - dirY * moveSpeed)] == false) posY -= dirY * moveSpeed;
}
//rotate to the right
if(keyDown(SDLK_RIGHT))
{
//both camera direction and camera plane must be rotated
double oldDirX = dirX;
dirX = dirX * cos(-rotSpeed) - dirY * sin(-rotSpeed);
dirY = oldDirX * sin(-rotSpeed) + dirY * cos(-rotSpeed);
double oldPlaneX = planeX;
planeX = planeX * cos(-rotSpeed) - planeY * sin(-rotSpeed);
planeY = oldPlaneX * sin(-rotSpeed) + planeY * cos(-rotSpeed);
}
//rotate to the left
if(keyDown(SDLK_LEFT))
{
//both camera direction and camera plane must be rotated
double oldDirX = dirX;
dirX = dirX * cos(rotSpeed) - dirY * sin(rotSpeed);
dirY = oldDirX * sin(rotSpeed) + dirY * cos(rotSpeed);
double oldPlaneX = planeX;
planeX = planeX * cos(rotSpeed) - planeY * sin(rotSpeed);
planeY = oldPlaneX * sin(rotSpeed) + planeY * cos(rotSpeed);
}
}
}

View file

@ -1,228 +0,0 @@
#include <fmt/core.h>
#include <SFML/Graphics.hpp>
#include <numbers>
#include <cmath>
#include "matrix.hpp"
#include <cstdlib>
using matrix::Matrix;
using namespace fmt;
Matrix MAP{
{1,1,1,1,1,1,1,1,1},
{1,0,1,0,0,0,0,0,1},
{1,0,1,0,0,1,1,0,1},
{1,0,0,0,0,0,0,0,1},
{1,1,0,0,0,0,0,0,1},
{1,0,0,1,1,1,0,0,1},
{1,0,0,0,1,0,0,0,1},
{1,0,0,0,0,0,1,1,1},
{1,1,1,1,1,1,1,1,1}
};
const int SCREEN_HEIGHT=480;
const int THREED_VIEW_WIDTH=480;
const int THREED_VIEW_HEIGHT=480;
const int SCREEN_WIDTH=SCREEN_HEIGHT * 2;
const int MAP_SIZE=matrix::width(MAP);
const int TILE_SIZE=(SCREEN_WIDTH/2) / MAP_SIZE;
const float FOV = std::numbers::pi / 3.0;
const float HALF_FOV = FOV / 2;
const int CASTED_RAYS=120;
const float STEP_ANGLE = FOV / CASTED_RAYS;
const int MAX_DEPTH = MAP_SIZE * TILE_SIZE;
const float SCALE = (SCREEN_WIDTH / 2) / CASTED_RAYS;
float player_x = SCREEN_WIDTH / 4;
float player_y = SCREEN_WIDTH / 4;
float player_angle = std::numbers::pi;
struct RGBA {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
RGBA pixels[SCREEN_HEIGHT * SCREEN_HEIGHT] = {{0,0,0,0}};
sf::Texture view_texture;
sf::Sprite view_sprite;
void draw_rect(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size, uint8_t color) {
sf::RectangleShape rect(size);
rect.setFillColor({color, color, color});
rect.setPosition(pos);
window.draw(rect);
}
void draw_pixel_buffer(sf::RenderWindow &window) {
view_texture.update((uint8_t *)pixels, SCREEN_HEIGHT, SCREEN_HEIGHT, 0, 0);
view_sprite.setTexture(view_texture);
view_sprite.setPosition(SCREEN_HEIGHT, 0);
window.draw(view_sprite);
}
void draw_pixel_rect(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size, uint8_t color) {
size_t x_start = size_t(pos.x - SCREEN_HEIGHT);
size_t y_start = size_t(pos.y);
size_t width = size_t(size.x);
size_t height = size_t(size.y);
for(size_t y = y_start; y < y_start + height; y++) {
for(size_t x = x_start; x < x_start + width; x++) {
size_t pixel_index = (y * SCREEN_HEIGHT) + x;
pixels[pixel_index] = {color, color, color, 255};
}
}
}
void draw_map_rect(sf::RenderWindow &window, int x, int y, uint8_t color) {
draw_rect(window,
{float(x * TILE_SIZE), float(y * TILE_SIZE)},
{float(TILE_SIZE-1), float(TILE_SIZE-1)},
color);
}
void draw_map(sf::RenderWindow &window, Matrix &map) {
uint8_t light_grey = 191;
uint8_t dark_grey = 65;
for(size_t y = 0; y < matrix::height(map); y++) {
for(size_t x = 0; x < matrix::width(map); x++) {
draw_map_rect(window, x, y, map[y][x] == 1 ? light_grey : dark_grey);
}
}
}
void draw_line(sf::RenderWindow &window, sf::Vector2f start, sf::Vector2f end) {
sf::Vertex line[] = {
sf::Vertex(start),
sf::Vertex(end)
};
window.draw(line, 2, sf::Lines);
}
void draw_map_rays(sf::RenderWindow &window, int col, int row, sf::Vector2f target) {
draw_map_rect(window, col, row, 100);
draw_line(window, {player_x, player_y}, target);
}
void draw_3d_view(sf::RenderWindow &window, int depth, float start_angle, int ray) {
uint8_t color = 255 / (1 + depth * depth * 0.0001);
float fixed_depth = depth * std::cos(player_angle - start_angle);
float wall_height = 21000 / fixed_depth;
if(wall_height > SCREEN_HEIGHT){
wall_height = SCREEN_HEIGHT;
}
draw_pixel_rect(window,
{SCREEN_HEIGHT + ray * SCALE, (SCREEN_HEIGHT / 2) - wall_height / 2},
{SCALE, wall_height},
color);
}
void clear_pixel_buffer() {
std::fill_n(pixels, SCREEN_HEIGHT * SCREEN_HEIGHT, RGBA{});
}
void ray_casting(sf::RenderWindow &window, Matrix& map) {
clear_pixel_buffer();
float start_angle = player_angle - HALF_FOV;
for(int ray = 0; ray < CASTED_RAYS; ray++, start_angle += STEP_ANGLE)
{
for(int depth = 1; depth < MAX_DEPTH; depth++) {
float target_x = player_x - std::sin(start_angle) * depth;
float target_y = player_y + std::cos(start_angle) * depth;
int col = int(target_x / TILE_SIZE);
int row = int(target_y / TILE_SIZE);
if(map[row][col] == 1) {
draw_map_rays(window, col, row, {target_x, target_y});
draw_3d_view(window, depth, start_angle, ray);
break;
}
}
}
}
void draw_ceiling_floor(sf::RenderWindow &window) {
draw_rect(window,
{SCREEN_HEIGHT, SCREEN_HEIGHT /2},
{SCREEN_HEIGHT, SCREEN_HEIGHT},
100);
draw_rect(window,
{SCREEN_HEIGHT, (SCREEN_HEIGHT * -1) / 2},
{SCREEN_HEIGHT, SCREEN_HEIGHT},
200);
}
void draw_everything(sf::RenderWindow &window) {
draw_map(window, MAP);
draw_ceiling_floor(window);
ray_casting(window, MAP);
draw_pixel_buffer(window);
window.display();
}
bool collision(float x, float y) {
int col = int(x / TILE_SIZE);
int row = int(y / TILE_SIZE);
return MAP[row][col] == 1;
}
int main() {
using KB = sf::Keyboard;
sf::RenderWindow window(sf::VideoMode(SCREEN_WIDTH, SCREEN_HEIGHT), "Raycaster");
window.setVerticalSyncEnabled(true);
view_texture.create(SCREEN_HEIGHT, SCREEN_HEIGHT);
while(window.isOpen()) {
draw_everything(window);
float x = player_x;
float y = player_y;
if(KB::isKeyPressed(KB::Q)) {
player_angle -= 0.1;
} else if(KB::isKeyPressed(KB::E)) {
player_angle += 0.1;
}
if(KB::isKeyPressed(KB::W)) {
x += -1 * std::sin(player_angle) * 5;
y += std::cos(player_angle) * 5;
} else if(KB::isKeyPressed(KB::S)) {
x -= -1 * std::sin(player_angle) * 5;
y -= std::cos(player_angle) * 5;
}
if(KB::isKeyPressed(KB::D)) {
x += -1 * std::sin(player_angle + std::numbers::pi * 0.5) * 5;
y += std::cos(player_angle + std::numbers::pi * 0.5) * 5;
} else if(KB::isKeyPressed(KB::A)) {
x -= -1 * std::sin(player_angle + std::numbers::pi * 0.5) * 5;
y -= std::cos(player_angle + std::numbers::pi * 0.5) * 5;
}
if(!collision(x, y)) {
player_x = x;
player_y = y;
}
sf::Event event;
while(window.pollEvent(event)) {
if(event.type == sf::Event::Closed) {
window.close();
}
}
}
return 0;
}

View file

@ -1,471 +0,0 @@
/*
Copyright (c) 2004-2020, Lode Vandevenne
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <cmath>
#include <string>
#include <vector>
#include <iostream>
#include "quickcg.h"
using namespace QuickCG;
/*
g++ *.cpp -lSDL -O3 -W -Wall -ansi -pedantic
g++ *.cpp -lSDL
*/
#define screenWidth 640
#define screenHeight 480
#define texWidth 64 // must be power of two
#define texHeight 64 // must be power of two
#define mapWidth 24
#define mapHeight 24
int worldMap[mapWidth][mapHeight] =
{
{8,8,8,8,8,8,8,8,8,8,8,4,4,6,4,4,6,4,6,4,4,4,6,4},
{8,0,0,0,0,0,0,0,0,0,8,4,0,0,0,0,0,0,0,0,0,0,0,4},
{8,0,3,3,0,0,0,0,0,8,8,4,0,0,0,0,0,0,0,0,0,0,0,6},
{8,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6},
{8,0,3,3,0,0,0,0,0,8,8,4,0,0,0,0,0,0,0,0,0,0,0,4},
{8,0,0,0,0,0,0,0,0,0,8,4,0,0,0,0,0,6,6,6,0,6,4,6},
{8,8,8,8,0,8,8,8,8,8,8,4,4,4,4,4,4,6,0,0,0,0,0,6},
{7,7,7,7,0,7,7,7,7,0,8,0,8,0,8,0,8,4,0,4,0,6,0,6},
{7,7,0,0,0,0,0,0,7,8,0,8,0,8,0,8,8,6,0,0,0,0,0,6},
{7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,6,0,0,0,0,0,4},
{7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,6,0,6,0,6,0,6},
{7,7,0,0,0,0,0,0,7,8,0,8,0,8,0,8,8,6,4,6,0,6,6,6},
{7,7,7,7,0,7,7,7,7,8,8,4,0,6,8,4,8,3,3,3,0,3,3,3},
{2,2,2,2,0,2,2,2,2,4,6,4,0,0,6,0,6,3,0,0,0,0,0,3},
{2,2,0,0,0,0,0,2,2,4,0,0,0,0,0,0,4,3,0,0,0,0,0,3},
{2,0,0,0,0,0,0,0,2,4,0,0,0,0,0,0,4,3,0,0,0,0,0,3},
{1,0,0,0,0,0,0,0,1,4,4,4,4,4,6,0,6,3,3,0,0,0,3,3},
{2,0,0,0,0,0,0,0,2,2,2,1,2,2,2,6,6,0,0,5,0,5,0,5},
{2,2,0,0,0,0,0,2,2,2,0,0,0,2,2,0,5,0,5,0,0,0,5,5},
{2,0,0,0,0,0,0,0,2,0,0,0,0,0,2,5,0,5,0,5,0,5,0,5},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5},
{2,0,0,0,0,0,0,0,2,0,0,0,0,0,2,5,0,5,0,5,0,5,0,5},
{2,2,0,0,0,0,0,2,2,2,0,0,0,2,2,0,5,0,5,0,0,0,5,5},
{2,2,2,2,1,2,2,2,2,2,2,1,2,2,2,5,5,5,5,5,5,5,5,5}
};
struct Sprite
{
double x;
double y;
int texture;
};
#define numSprites 19
Sprite sprite[numSprites] =
{
{20.5, 11.5, 10}, //green light in front of playerstart
//green lights in every room
{18.5,4.5, 10},
{10.0,4.5, 10},
{10.0,12.5,10},
{3.5, 6.5, 10},
{3.5, 20.5,10},
{3.5, 14.5,10},
{14.5,20.5,10},
//row of pillars in front of wall: fisheye test
{18.5, 10.5, 9},
{18.5, 11.5, 9},
{18.5, 12.5, 9},
//some barrels around the map
{21.5, 1.5, 8},
{15.5, 1.5, 8},
{16.0, 1.8, 8},
{16.2, 1.2, 8},
{3.5, 2.5, 8},
{9.5, 15.5, 8},
{10.0, 15.1,8},
{10.5, 15.8,8},
};
Uint32 buffer[screenHeight][screenWidth]; // y-coordinate first because it works per scanline
//1D Zbuffer
double ZBuffer[screenWidth];
//arrays used to sort the sprites
int spriteOrder[numSprites];
double spriteDistance[numSprites];
//function used to sort the sprites
void sortSprites(int* order, double* dist, int amount);
int main(int /*argc*/, char */*argv*/[])
{
double posX = 22.0, posY = 11.5; //x and y start position
double dirX = -1.0, dirY = 0.0; //initial direction vector
double planeX = 0.0, planeY = 0.66; //the 2d raycaster version of camera plane
double time = 0; //time of current frame
double oldTime = 0; //time of previous frame
std::vector<Uint32> texture[11];
for(int i = 0; i < 11; i++) texture[i].resize(texWidth * texHeight);
screen(screenWidth,screenHeight, 0, "Raycaster");
//load some textures
unsigned long tw, th, error = 0;
error |= loadImage(texture[0], tw, th, "pics/eagle.png");
error |= loadImage(texture[1], tw, th, "pics/redbrick.png");
error |= loadImage(texture[2], tw, th, "pics/purplestone.png");
error |= loadImage(texture[3], tw, th, "pics/greystone.png");
error |= loadImage(texture[4], tw, th, "pics/bluestone.png");
error |= loadImage(texture[5], tw, th, "pics/mossy.png");
error |= loadImage(texture[6], tw, th, "pics/wood.png");
error |= loadImage(texture[7], tw, th, "pics/colorstone.png");
if(error) { std::cout << "error loading images" << std::endl; return 1; }
//load some sprite textures
error |= loadImage(texture[8], tw, th, "pics/barrel.png");
error |= loadImage(texture[9], tw, th, "pics/pillar.png");
error |= loadImage(texture[10], tw, th, "pics/greenlight.png");
if(error) { std::cout << "error loading images" << std::endl; return 1; }
//start the main loop
while(!done())
{
//FLOOR CASTING
for(int y = screenHeight / 2 + 1; y < screenHeight; ++y)
{
// rayDir for leftmost ray (x = 0) and rightmost ray (x = w)
float rayDirX0 = dirX - planeX;
float rayDirY0 = dirY - planeY;
float rayDirX1 = dirX + planeX;
float rayDirY1 = dirY + planeY;
// Current y position compared to the center of the screen (the horizon)
int p = y - screenHeight / 2;
// Vertical position of the camera.
float posZ = 0.5 * screenHeight;
// 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.
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 weight in the inner loop
float floorStepX = rowDistance * (rayDirX1 - rayDirX0) / screenWidth;
float floorStepY = rowDistance * (rayDirY1 - rayDirY0) / screenWidth;
// 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 < screenWidth; ++x)
{
// the cell coord is simply got from the integer parts of floorX and floorY
int cellX = (int)(floorX);
int cellY = (int)(floorY);
// get the texture coordinate from the fractional part
int tx = (int)(texWidth * (floorX - cellX)) & (texWidth - 1);
int ty = (int)(texHeight * (floorY - cellY)) & (texHeight - 1);
floorX += floorStepX;
floorY += floorStepY;
// choose texture and draw the pixel
int checkerBoardPattern = (int(cellX + cellY)) & 1;
int floorTexture;
if(checkerBoardPattern == 0) floorTexture = 3;
else floorTexture = 4;
int ceilingTexture = 6;
Uint32 color;
// floor
color = texture[floorTexture][texWidth * ty + tx];
color = (color >> 1) & 8355711; // make a bit darker
buffer[y][x] = color;
//ceiling (symmetrical, at screenHeight - y - 1 instead of y)
color = texture[ceilingTexture][texWidth * ty + tx];
color = (color >> 1) & 8355711; // make a bit darker
buffer[screenHeight - y - 1][x] = color;
}
}
// WALL CASTING
for(int x = 0; x < w; x++)
{
//calculate ray position and direction
double cameraX = 2 * x / double(w) - 1; //x-coordinate 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 position 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 = (rayDirX == 0) ? 1e30 : std::abs(1 / rayDirX);
double deltaDistY = (rayDirY == 0) ? 1e30 : std::abs(1 / rayDirY);
double perpWallDist;
//what direction to step in x or y-direction (either +1 or -1)
int stepX;
int stepY;
int hit = 0; //was there a wall hit?
int side; //was a NS or a EW wall hit?
//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)
{
//jump to next map square, either in x-direction, or in y-direction
if(sideDistX < sideDistY)
{
sideDistX += deltaDistX;
mapX += stepX;
side = 0;
}
else
{
sideDistY += deltaDistY;
mapY += stepY;
side = 1;
}
//Check if ray has hit a wall
if(worldMap[mapX][mapY] > 0) hit = 1;
}
//Calculate distance of perpendicular ray (Euclidean distance would give fisheye effect!)
if(side == 0) perpWallDist = (sideDistX - deltaDistX);
else perpWallDist = (sideDistY - deltaDistY);
//Calculate height of line to draw on screen
int lineHeight = (int)(h / perpWallDist);
//calculate lowest and highest pixel to fill in current stripe
int drawStart = -lineHeight / 2 + h / 2;
if(drawStart < 0) drawStart = 0;
int drawEnd = lineHeight / 2 + h / 2;
if(drawEnd >= h) drawEnd = h - 1;
//texturing calculations
int texNum = worldMap[mapX][mapY] - 1; //1 subtracted from it so that texture 0 can be used!
//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 coordinate on the texture
int texX = int(wallX * double(texWidth));
if(side == 0 && rayDirX > 0) texX = texWidth - texX - 1;
if(side == 1 && rayDirY < 0) texX = texWidth - texX - 1;
// TODO: 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 * texHeight / lineHeight;
// Starting texture coordinate
double texPos = (drawStart - h / 2 + lineHeight / 2) * step;
for(int y = drawStart; y < drawEnd; y++)
{
// Cast the texture coordinate to integer, and mask with (texHeight - 1) in case of overflow
int texY = (int)texPos & (texHeight - 1);
texPos += step;
Uint32 color = texture[texNum][texHeight * texY + texX];
//make color darker for y-sides: R, G and B byte each divided through two with a "shift" and an "and"
if(side == 1) color = (color >> 1) & 8355711;
buffer[y][x] = color;
}
//SET THE ZBUFFER FOR THE SPRITE CASTING
ZBuffer[x] = perpWallDist; //perpendicular distance is used
}
//SPRITE CASTING
//sort sprites from far to close
for(int i = 0; i < numSprites; i++)
{
spriteOrder[i] = i;
spriteDistance[i] = ((posX - sprite[i].x) * (posX - sprite[i].x) + (posY - sprite[i].y) * (posY - sprite[i].y)); //sqrt not taken, unneeded
}
sortSprites(spriteOrder, spriteDistance, numSprites);
//after sorting the sprites, do the projection and draw them
for(int i = 0; i < numSprites; i++)
{
//translate sprite position to relative to camera
double spriteX = sprite[spriteOrder[i]].x - posX;
double spriteY = sprite[spriteOrder[i]].y - posY;
//transform sprite with the inverse camera matrix
// [ planeX dirX ] -1 [ dirY -dirX ]
// [ ] = 1/(planeX*dirY-dirX*planeY) * [ ]
// [ planeY dirY ] [ -planeY planeX ]
double invDet = 1.0 / (planeX * dirY - dirX * planeY); //required for correct matrix multiplication
double transformX = invDet * (dirY * spriteX - dirX * spriteY);
double transformY = invDet * (-planeY * spriteX + planeX * 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])
int spriteScreenX = int((w / 2) * (1 + transformX / transformY));
//parameters for scaling and moving the sprites
#define uDiv 1
#define vDiv 1
#define vMove 0.0
int vMoveScreen = int(vMove / transformY);
//calculate height of the sprite on screen
int spriteHeight = abs(int(h / (transformY))) / vDiv; //using "transformY" instead of the real distance prevents fisheye
//calculate lowest and highest pixel to fill in current stripe
int drawStartY = -spriteHeight / 2 + h / 2 + vMoveScreen;
if(drawStartY < 0) drawStartY = 0;
int drawEndY = spriteHeight / 2 + h / 2 + vMoveScreen;
if(drawEndY >= h) drawEndY = h - 1;
//calculate width of the sprite
int spriteWidth = abs(int (h / (transformY))) / uDiv; // same as height of sprite, given that it's square
int drawStartX = -spriteWidth / 2 + spriteScreenX;
if(drawStartX < 0) drawStartX = 0;
int drawEndX = spriteWidth / 2 + spriteScreenX;
if(drawEndX > w) drawEndX = w;
//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)) * texWidth / spriteWidth) / 256;
//the conditions in the if are:
//1) it's in front of 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++) //for every pixel of the current stripe
{
int d = (y - vMoveScreen) * 256 - h * 128 + spriteHeight * 128; //256 and 128 factors to avoid floats
int texY = ((d * texHeight) / spriteHeight) / 256;
Uint32 color = texture[sprite[spriteOrder[i]].texture][texWidth * texY + texX]; //get current color from the texture
if((color & 0x00FFFFFF) != 0) buffer[y][stripe] = color; //paint pixel if it isn't black, black is the invisible color
}
}
}
}
drawBuffer(buffer[0]);
// No need to clear the screen here, since everything is overdrawn with floor and ceiling
//timing for input and FPS counter
oldTime = time;
time = getTicks();
double frameTime = (time - oldTime) / 1000.0; //frametime is the time this frame has taken, in seconds
print(1.0 / frameTime); //FPS counter
redraw();
//speed modifiers
double moveSpeed = frameTime * 3.0; //the constant value is in squares/second
double rotSpeed = frameTime * 2.0; //the constant value is in radians/second
readKeys();
//move forward if no wall in front of you
if (keyDown(SDLK_UP))
{
if(worldMap[int(posX + dirX * moveSpeed)][int(posY)] == false) posX += dirX * moveSpeed;
if(worldMap[int(posX)][int(posY + dirY * moveSpeed)] == false) posY += dirY * moveSpeed;
}
//move backwards if no wall behind you
if(keyDown(SDLK_DOWN))
{
if(worldMap[int(posX - dirX * moveSpeed)][int(posY)] == false) posX -= dirX * moveSpeed;
if(worldMap[int(posX)][int(posY - dirY * moveSpeed)] == false) posY -= dirY * moveSpeed;
}
//rotate to the right
if(keyDown(SDLK_RIGHT))
{
//both camera direction and camera plane must be rotated
double oldDirX = dirX;
dirX = dirX * cos(-rotSpeed) - dirY * sin(-rotSpeed);
dirY = oldDirX * sin(-rotSpeed) + dirY * cos(-rotSpeed);
double oldPlaneX = planeX;
planeX = planeX * cos(-rotSpeed) - planeY * sin(-rotSpeed);
planeY = oldPlaneX * sin(-rotSpeed) + planeY * cos(-rotSpeed);
}
//rotate to the left
if(keyDown(SDLK_LEFT))
{
//both camera direction and camera plane must be rotated
double oldDirX = dirX;
dirX = dirX * cos(rotSpeed) - dirY * sin(rotSpeed);
dirY = oldDirX * sin(rotSpeed) + dirY * cos(rotSpeed);
double oldPlaneX = planeX;
planeX = planeX * cos(rotSpeed) - planeY * sin(rotSpeed);
planeY = oldPlaneX * sin(rotSpeed) + planeY * cos(rotSpeed);
}
if(keyDown(SDLK_ESCAPE))
{
break;
}
}
}
//sort the sprites based on distance
void sortSprites(int* order, 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 to go from farthest to nearest
for(int i = 0; i < amount; i++) {
dist[i] = sprites[amount - i - 1].first;
order[i] = sprites[amount - i - 1].second;
}
}

View file

@ -1,293 +0,0 @@
/*
Copyright (c) 2004-2019, Lode Vandevenne
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <cmath>
#include <string>
#include <vector>
#include <iostream>
#include "quickcg.h"
using namespace QuickCG;
/*
g++ *.cpp -lSDL -O3 -W -Wall -ansi -pedantic
g++ *.cpp -lSDL
*/
#define screenWidth 640
#define screenHeight 480
#define texWidth 64
#define texHeight 64
#define mapWidth 24
#define mapHeight 24
int worldMap[mapWidth][mapHeight]=
{
{4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,7,7,7,7,7,7,7,7},
{4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,7},
{4,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7},
{4,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7},
{4,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,7},
{4,0,4,0,0,0,0,5,5,5,5,5,5,5,5,5,7,7,0,7,7,7,7,7},
{4,0,5,0,0,0,0,5,0,5,0,5,0,5,0,5,7,0,0,0,7,7,7,1},
{4,0,6,0,0,0,0,5,0,0,0,0,0,0,0,5,7,0,0,0,0,0,0,8},
{4,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,1},
{4,0,8,0,0,0,0,5,0,0,0,0,0,0,0,5,7,0,0,0,0,0,0,8},
{4,0,0,0,0,0,0,5,0,0,0,0,0,0,0,5,7,0,0,0,7,7,7,1},
{4,0,0,0,0,0,0,5,5,5,5,0,5,5,5,5,7,7,7,7,7,7,7,1},
{6,6,6,6,6,6,6,6,6,6,6,0,6,6,6,6,6,6,6,6,6,6,6,6},
{8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4},
{6,6,6,6,6,6,0,6,6,6,6,0,6,6,6,6,6,6,6,6,6,6,6,6},
{4,4,4,4,4,4,0,4,4,4,6,0,6,2,2,2,2,2,2,2,3,3,3,3},
{4,0,0,0,0,0,0,0,0,4,6,0,6,2,0,0,0,0,0,2,0,0,0,2},
{4,0,0,0,0,0,0,0,0,0,0,0,6,2,0,0,5,0,0,2,0,0,0,2},
{4,0,0,0,0,0,0,0,0,4,6,0,6,2,0,0,0,0,0,2,2,0,2,2},
{4,0,6,0,6,0,0,0,0,4,6,0,0,0,0,0,5,0,0,0,0,0,0,2},
{4,0,0,5,0,0,0,0,0,4,6,0,6,2,0,0,0,0,0,2,2,0,2,2},
{4,0,6,0,6,0,0,0,0,4,6,0,6,2,0,0,5,0,0,2,0,0,0,2},
{4,0,0,0,0,0,0,0,0,4,6,0,6,2,0,0,0,0,0,2,0,0,0,2},
{4,4,4,4,4,4,4,4,4,4,1,1,1,2,2,2,2,2,2,3,3,3,3,3}
};
Uint32 buffer[screenHeight][screenWidth];
int main(int /*argc*/, char */*argv*/[])
{
double posX = 22.0, posY = 11.5; //x and y start position
double dirX = -1.0, dirY = 0.0; //initial direction vector
double planeX = 0.0, planeY = 0.66; //the 2d raycaster version of camera plane
double time = 0; //time of current frame
double oldTime = 0; //time of previous frame
std::vector<Uint32> texture[8];
for(int i = 0; i < 8; i++) texture[i].resize(texWidth * texHeight);
screen(screenWidth, screenHeight, 0, "Raycaster");
//generate some textures
#if 1
for(int x = 0; x < texWidth; x++)
for(int y = 0; y < texHeight; y++)
{
int xorcolor = (x * 256 / texWidth) ^ (y * 256 / texHeight);
//int xcolor = x * 256 / texWidth;
int ycolor = y * 256 / texHeight;
int xycolor = y * 128 / texHeight + x * 128 / texWidth;
texture[0][texWidth * y + x] = 65536 * 254 * (x != y && x != texWidth - y); //flat red texture with black cross
texture[1][texWidth * y + x] = xycolor + 256 * xycolor + 65536 * xycolor; //sloped greyscale
texture[2][texWidth * y + x] = 256 * xycolor + 65536 * xycolor; //sloped yellow gradient
texture[3][texWidth * y + x] = xorcolor + 256 * xorcolor + 65536 * xorcolor; //xor greyscale
texture[4][texWidth * y + x] = 256 * xorcolor; //xor green
texture[5][texWidth * y + x] = 65536 * 192 * (x % 16 && y % 16); //red bricks
texture[6][texWidth * y + x] = 65536 * ycolor; //red gradient
texture[7][texWidth * y + x] = 128 + 256 * 128 + 65536 * 128; //flat grey texture
}
#else
//generate some textures
unsigned long tw, th;
loadImage(texture[0], tw, th, "assets/eagle.png");
loadImage(texture[1], tw, th, "assets/redbrick.png");
loadImage(texture[2], tw, th, "assets/purplestone.png");
loadImage(texture[3], tw, th, "assets/greystone.png");
loadImage(texture[4], tw, th, "assets/bluestone.png");
loadImage(texture[5], tw, th, "assets/mossy.png");
loadImage(texture[6], tw, th, "assets/wood.png");
loadImage(texture[7], tw, th, "assets/colorstone.png");
#endif
//start the main loop
while(!done())
{
for(int x = 0; x < w; x++)
{
//calculate ray position and direction
double cameraX = 2 * x / (double)w - 1; //x-coordinate 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 position 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 = (rayDirX == 0) ? 1e30 : std::abs(1 / rayDirX);
double deltaDistY = (rayDirY == 0) ? 1e30 : std::abs(1 / rayDirY);
double perpWallDist;
//what direction to step in x or y-direction (either +1 or -1)
int stepX;
int stepY;
int hit = 0; //was there a wall hit?
int side; //was a NS or a EW wall hit?
//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)
{
//jump to next map square, either in x-direction, or in y-direction
if(sideDistX < sideDistY)
{
sideDistX += deltaDistX;
mapX += stepX;
side = 0;
}
else
{
sideDistY += deltaDistY;
mapY += stepY;
side = 1;
}
//Check if ray has hit a wall
if(worldMap[mapX][mapY] > 0) hit = 1;
}
//Calculate distance of perpendicular ray (Euclidean distance would give fisheye effect!)
if(side == 0) perpWallDist = (sideDistX - deltaDistX);
else perpWallDist = (sideDistY - deltaDistY);
//Calculate height of line to draw on screen
int lineHeight = (int)(h / perpWallDist);
int pitch = 100;
//calculate lowest and highest pixel to fill in current stripe
int drawStart = -lineHeight / 2 + h / 2 + pitch;
if(drawStart < 0) drawStart = 0;
int drawEnd = lineHeight / 2 + h / 2 + pitch;
if(drawEnd >= h) drawEnd = h - 1;
//texturing calculations
int texNum = worldMap[mapX][mapY] - 1; //1 subtracted from it so that texture 0 can be used!
//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 coordinate on the texture
int texX = int(wallX * double(texWidth));
if(side == 0 && rayDirX > 0) texX = texWidth - texX - 1;
if(side == 1 && rayDirY < 0) texX = texWidth - texX - 1;
// TODO: 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 * texHeight / lineHeight;
// Starting texture coordinate
double texPos = (drawStart - pitch - h / 2 + lineHeight / 2) * step;
for(int y = drawStart; y < drawEnd; y++)
{
// Cast the texture coordinate to integer, and mask with (texHeight - 1) in case of overflow
int texY = (int)texPos & (texHeight - 1);
texPos += step;
Uint32 color = texture[texNum][texHeight * texY + texX];
//make color darker for y-sides: R, G and B byte each divided through two with a "shift" and an "and"
if(side == 1) color = (color >> 1) & 8355711;
buffer[y][x] = color;
}
}
for(int y = 0; y < h; y++) for(int x = 0; x < w; x++) buffer[y][x] = 0; //clear the buffer instead of cls()
drawBuffer(buffer[0]);
//timing for input and FPS counter
oldTime = time;
time = getTicks();
double frameTime = (time - oldTime) / 1000.0; //frametime is the time this frame has taken, in seconds
print(1.0 / frameTime); //FPS counter
redraw();
//speed modifiers
double moveSpeed = frameTime * 5.0; //the constant value is in squares/second
double rotSpeed = frameTime * 3.0; //the constant value is in radians/second
SDL_Event event;
while(SDL_PollEvent(&event)) {
if(event.type != SDL_KEYDOWN) continue;
//move forward if no wall in front of you
if(event.key.keysym.sym == SDLK_UP)
{
if(worldMap[int(posX + dirX * moveSpeed)][int(posY)] == false) posX += dirX * moveSpeed;
if(worldMap[int(posX)][int(posY + dirY * moveSpeed)] == false) posY += dirY * moveSpeed;
}
//move backwards if no wall behind you
if(event.key.keysym.sym == SDLK_DOWN)
{
if(worldMap[int(posX - dirX * moveSpeed)][int(posY)] == false) posX -= dirX * moveSpeed;
if(worldMap[int(posX)][int(posY - dirY * moveSpeed)] == false) posY -= dirY * moveSpeed;
}
//rotate to the right
if(event.key.keysym.sym == SDLK_RIGHT)
{
//both camera direction and camera plane must be rotated
double oldDirX = dirX;
dirX = dirX * cos(-rotSpeed) - dirY * sin(-rotSpeed);
dirY = oldDirX * sin(-rotSpeed) + dirY * cos(-rotSpeed);
double oldPlaneX = planeX;
planeX = planeX * cos(-rotSpeed) - planeY * sin(-rotSpeed);
planeY = oldPlaneX * sin(-rotSpeed) + planeY * cos(-rotSpeed);
}
//rotate to the left
if(event.key.keysym.sym == SDLK_LEFT)
{
//both camera direction and camera plane must be rotated
double oldDirX = dirX;
dirX = dirX * cos(rotSpeed) - dirY * sin(rotSpeed);
dirY = oldDirX * sin(rotSpeed) + dirY * cos(rotSpeed);
double oldPlaneX = planeX;
planeX = planeX * cos(rotSpeed) - planeY * sin(rotSpeed);
planeY = oldPlaneX * sin(rotSpeed) + planeY * cos(rotSpeed);
}
}
}
return 0;
}

View file

@ -1,36 +0,0 @@
#include "SDL.h"
int main(int argc, char *argv[])
{
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Surface *surface;
SDL_Event event;
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError());
return 3;
}
if (SDL_CreateWindowAndRenderer(320, 240, SDL_WINDOW_RESIZABLE, &window, &renderer)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window and renderer: %s", SDL_GetError());
return 3;
}
while (1) {
SDL_PollEvent(&event);
if (event.type == SDL_QUIT) {
break;
}
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00);
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
}
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}

View file

@ -1,553 +0,0 @@
#include <math.h>
#include <stdio.h>
#include <stdint.h>
#include <sys/time.h>
#include <SDL.h>
#define ASSERT(_e, ...) if (!(_e)) { fprintf(stderr, __VA_ARGS__); exit(1); }
typedef float f32;
typedef double f64;
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
typedef size_t usize;
typedef ssize_t isize;
#define SCREEN_SIZE_X 640
#define SCREEN_SIZE_Y 360
#define TILE_WIDTH 1.0f
#define WALL_HEIGHT 1.2f
typedef struct v2_s {f32 x, y;} v2;
typedef struct v2i_s { i32 x, y;} v2i;
#define dot(v0, v1) \
({ const v2 _v0 = (v0), _v1 = (v1); (_v0.x * _v1.x) + (_v0.y * _v1.y); })
#define length(v) ({ const v2 _v = (v); sqrtf(dot(_v, _v)); })
#define normalize(u) ({ \
const v2 _u = (u); \
const f32 l = length(_u); \
(v2) { _u.x/l, _u.y/l }; \
})
#define rotr(v) ({ const v2 _v = (v); (v2) { -_v.y, _v.x }; })
#define min(a, b) ({ __typeof__(a) _a = (a), _b = (b); _a < _b ? _a : _b; })
#define max(a, b) ({ __typeof__(a) _a = (a), _b = (b); _a > _b ? _a : _b; })
static u8 MAPDATA[8*13] = {
1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 1,
1, 0, 0, 3, 0, 0, 4, 1,
1, 0, 0, 0, 0, 0, 0, 1,
1, 0, 0, 0, 0, 0, 4, 1,
1, 0, 2, 0, 0, 0, 0, 1,
1, 0, 0, 0, 0, 0, 0, 1,
1, 0, 0, 0, 0, 0, 0, 1,
1, 0, 2, 2, 0, 0, 0, 1,
1, 0, 2, 2, 0, 3, 0, 1,
1, 0, 0, 0, 0, 3, 0, 1,
1, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1,
};
enum KeyboardKeyState {
KeyboardKeyState_Depressed = 0, // No recent event, key is still up
KeyboardKeyState_Released = 1, // Last event was a released event
KeyboardKeyState_Held = 2, // No recent event, key is still down
KeyboardKeyState_Pressed = 3, // Last event was a pressed event
KeyboardKeyState_COUNT = 4
};
struct KeyBoardState {
enum KeyboardKeyState up;
enum KeyboardKeyState down;
enum KeyboardKeyState right;
enum KeyboardKeyState left;
enum KeyboardKeyState a;
enum KeyboardKeyState s;
enum KeyboardKeyState d;
enum KeyboardKeyState w;
enum KeyboardKeyState q;
enum KeyboardKeyState e;
enum KeyboardKeyState one;
enum KeyboardKeyState two;
enum KeyboardKeyState three;
enum KeyboardKeyState four;
enum KeyboardKeyState five;
enum KeyboardKeyState six;
enum KeyboardKeyState seven;
enum KeyboardKeyState eight;
};
void clear_keyboard_state(struct KeyBoardState* kbs) {
kbs->up = KeyboardKeyState_Depressed;
kbs->down = KeyboardKeyState_Depressed;
kbs->right = KeyboardKeyState_Depressed;
kbs->left = KeyboardKeyState_Depressed;
kbs->a = KeyboardKeyState_Depressed;
kbs->s = KeyboardKeyState_Depressed;
kbs->d = KeyboardKeyState_Depressed;
kbs->w = KeyboardKeyState_Depressed;
kbs->q = KeyboardKeyState_Depressed;
kbs->e = KeyboardKeyState_Depressed;
kbs->one = KeyboardKeyState_Depressed;
kbs->two = KeyboardKeyState_Depressed;
kbs->three = KeyboardKeyState_Depressed;
kbs->four = KeyboardKeyState_Depressed;
kbs->five = KeyboardKeyState_Depressed;
kbs->six = KeyboardKeyState_Depressed;
kbs->seven = KeyboardKeyState_Depressed;
kbs->eight = KeyboardKeyState_Depressed;
}
void decay_keyboard_state(struct KeyBoardState* kbs) {
static enum KeyboardKeyState to_depressed_state[KeyboardKeyState_COUNT] = {
KeyboardKeyState_Depressed,
KeyboardKeyState_Depressed,
KeyboardKeyState_Held,
KeyboardKeyState_Held
};
kbs->up = to_depressed_state[kbs->up];
kbs->down = to_depressed_state[kbs->down];
kbs->right = to_depressed_state[kbs->right];
kbs->left = to_depressed_state[kbs->left];
kbs->a = to_depressed_state[kbs->a];
kbs->s = to_depressed_state[kbs->s];
kbs->d = to_depressed_state[kbs->d];
kbs->w = to_depressed_state[kbs->w];
kbs->q = to_depressed_state[kbs->q];
kbs->e = to_depressed_state[kbs->e];
kbs->one = to_depressed_state[kbs->one];
kbs->two = to_depressed_state[kbs->two];
kbs->three = to_depressed_state[kbs->three];
kbs->four = to_depressed_state[kbs->four];
kbs->five = to_depressed_state[kbs->five];
kbs->six = to_depressed_state[kbs->six];
kbs->seven = to_depressed_state[kbs->seven];
kbs->eight = to_depressed_state[kbs->eight];
}
bool is_pressed(enum KeyboardKeyState state) {
static bool lookup[KeyboardKeyState_COUNT] = {0, 0, 1, 1};
return lookup[state];
}
// TODO: Could we store the pixels in column-major? We're always rendering
// in vertical lines, so I suspect that would be more efficient.
struct {
SDL_Window *window;
SDL_Texture *texture;
SDL_Renderer *renderer;
u32 pixels[SCREEN_SIZE_X * SCREEN_SIZE_Y];
bool quit;
v2 camera_pos;
v2 camera_dir;
v2 camera_dir_rotr;
f32 camera_width;
f32 camera_height;
f32 camera_z;
v2 player_speed;
f32 player_omega;
struct KeyBoardState keyboard_state;
} state;
static void tick(f32 dt) {
v2 input_dir = {0.0, 0.0}; // In the body frame, which is right-handed, so y points left.
if (is_pressed(state.keyboard_state.w)) {
input_dir.x += 1.0;
}
if (is_pressed(state.keyboard_state.s)) {
input_dir.x -= 1.0;
}
if (is_pressed(state.keyboard_state.d)) {
input_dir.y -= 1.0;
}
if (is_pressed(state.keyboard_state.a)) {
input_dir.y += 1.0;
}
int input_rot_dir = 0; // Right-hand rotation in plane (CCW)
if (is_pressed(state.keyboard_state.q)) {
input_rot_dir += 1;
}
if (is_pressed(state.keyboard_state.e)) {
input_rot_dir -= 1;
}
if (is_pressed(state.keyboard_state.three)) {
state.camera_z *= 0.95;
printf("camera z: %.3f\n", state.camera_z);
}
if (is_pressed(state.keyboard_state.four)) {
state.camera_z /= 0.95;
printf("camera z: %.3f\n", state.camera_z);
}
if (is_pressed(state.keyboard_state.five)) {
state.camera_height *= 0.95;
printf("camera height: %.3f\n", state.camera_height);
}
if (is_pressed(state.keyboard_state.six)) {
state.camera_height /= 0.95;
printf("camera height: %.3f\n", state.camera_height);
}
if (is_pressed(state.keyboard_state.seven)) {
state.camera_width *= 0.95;
printf("camera width: %.3f\n", state.camera_width);
}
if (is_pressed(state.keyboard_state.eight)) {
state.camera_width /= 0.95;
printf("camera width: %.3f\n", state.camera_width);
}
// Update the player's velocity
const f32 kPlayerInputAccel = 7.5;
const f32 kPlayerInputAngularAccel = 9.5;
const f32 kPlayerMaxSpeed = 7.0;
const f32 kPlayerMaxOmega = 7.0;
const f32 kAirFriction = 0.9;
const f32 kAirFrictionRot = 0.85;
// Note: Speed is in the global frame
state.player_speed.x += (state.camera_dir.x*input_dir.x + state.camera_dir_rotr.x*input_dir.y) * kPlayerInputAccel * dt;
state.player_speed.y += (state.camera_dir.y*input_dir.x + state.camera_dir_rotr.y*input_dir.y) * kPlayerInputAccel * dt;
state.player_omega += input_rot_dir * kPlayerInputAngularAccel * dt;
// Clamp the velocity to a maximum magnitude
f32 speed = length(state.player_speed);
if (speed > kPlayerMaxSpeed) {
state.player_speed.x *= kPlayerMaxSpeed / speed;
state.player_speed.y *= kPlayerMaxSpeed / speed;
}
if (state.player_omega > kPlayerMaxOmega) {
state.player_omega *= kPlayerMaxOmega / state.player_omega;
} else if (state.player_omega < -kPlayerMaxOmega) {
state.player_omega *= - kPlayerMaxOmega / state.player_omega;
}
// Update the player's position
state.camera_pos.x += state.player_speed.x * dt;
state.camera_pos.y += state.player_speed.y * dt;
// Update the player's rotational heading
float theta = atan2(state.camera_dir.y, state.camera_dir.x);
theta += state.player_omega * dt;
state.camera_dir = ((v2) {cos(theta), sin(theta)});
state.camera_dir_rotr = rotr((state.camera_dir));
// Apply air friction
state.player_speed.x *= kAirFriction;
state.player_speed.y *= kAirFriction;
state.player_omega *= kAirFrictionRot;
}
// Fill all pixels in the vertical line at x between y0 and y1 with the given color.
static void draw_column(int x, int y0, int y1, u32 color) {
for (int y = y0; y <= y1; y++) {
state.pixels[(y * SCREEN_SIZE_X) + x] = color;
}
}
static void render() {
static u32 color_wall[4] = {
0xFFFF0000,
0xFF00FF00,
0xFF00FFFF,
0xFF0000FF
};
static u32 color_wall_light[4] = {
0xFFFF3333,
0xFF66FF66,
0xFF88FFFF,
0xFF3333FF
};
const u32 color_floor = 0xFF666666;
const u32 color_ceil = 0xFF444444;
// Get camera location's cell coordinates
int x_ind_cam = (int)(floorf(state.camera_pos.x / TILE_WIDTH));
int y_ind_cam = (int)(floorf(state.camera_pos.y / TILE_WIDTH));
f32 x_rem_cam = state.camera_pos.x - TILE_WIDTH*x_ind_cam;
f32 y_rem_cam = state.camera_pos.y - TILE_WIDTH*y_ind_cam;
for (int x = 0; x < SCREEN_SIZE_X; x++) {
// Camera to pixel column
const f32 dw = state.camera_width/2 - (state.camera_width*x)/SCREEN_SIZE_X;
const v2 cp = {
state.camera_dir.x + dw*state.camera_dir_rotr.x,
state.camera_dir.y + dw*state.camera_dir_rotr.y
};
// Distance from the camera to the column
const f32 cam_len = length( (cp) );
// Ray direction through this column
const v2 dir = {cp.x / cam_len, cp.y /cam_len};
// Start at the camera pos
int x_ind = x_ind_cam;
int y_ind = y_ind_cam;
f32 x_rem = x_rem_cam;
f32 y_rem = y_rem_cam;
// We will be raycasting through cells of unit width.
// Our ray's position vs time is:
// x(t) = x_rem + dir.x * dt
// y(t) = y_rem + dir.y * dt
// We cross x = 0 if dir.x < 0, at dt = -x_rem/dir.x
// We cross x = TILE_WIDTH if dir.x > 0, at dt = (TILE_WIDTH-x_rem)/dir.x
// We cross y = 0 if dir.y < 0, at dt = -y_rem/dir.y
// We cross y = TILE_WIDTH if dir.y > 0, at dt = (TILE_WIDTH-y_rem)/dir.y
// We can generalize this to:
// dx_ind_dir = -1 if dir.x < 0, at dt = -1/dir.x * x_rem + 0.0
// dx_ind_dir = 1 if dir.x > 0, at dt = -1/dir.x * x_rem + TILE_WIDTH/dir.x
// dx_ind_dir = 0 if dir.x = 0, at dt = 0 * x_rem + INFINITY
// dy_ind_dir = -1 if dir.y < 0, at dt = -1/dir.y * y_rem + 0.0
// dy_ind_dir = 1 if dir.y > 0, at dt = -1/dir.y * y_rem + TILE_WIDTH/dir.y
// dy_ind_dir = 0 if dir.x = 0, at dt = 0 * y_rem + INFINITY
int dx_ind_dir = 0;
f32 dx_a = 0.0;
f32 dx_b = INFINITY;
if (dir.x < 0) {
dx_ind_dir = -1;
dx_a = -1.0f/dir.x;
dx_b = 0.0;
} else if (dir.x > 0) {
dx_ind_dir = 1;
dx_a = -1.0f/dir.x;
dx_b = TILE_WIDTH/dir.x;
}
int dy_ind_dir = 0;
f32 dy_a = 0.0;
f32 dy_b = INFINITY;
if (dir.y < 0) {
dy_ind_dir = -1;
dy_a = -1.0f/dir.y;
dy_b = 0.0;
} else if (dir.y > 0) {
dy_ind_dir = 1;
dy_a = -1.0f/dir.y;
dy_b = TILE_WIDTH/dir.y;
}
// Step through cells until we hit an occupied cell
int n_steps = 0;
int dx_ind, dy_ind;
while (n_steps < 100) {
n_steps += 1;
f32 dt_best = INFINITY;
dx_ind = 0;
dy_ind = 0;
f32 dt_x = dx_a*x_rem + dx_b;
f32 dt_y = dy_a*y_rem + dy_b;
if (dt_x < dt_y) {
dt_best = dt_x;
dx_ind = dx_ind_dir;
dy_ind = 0;
} else {
dt_best = dt_y;
dx_ind = 0;
dy_ind = dy_ind_dir;
}
// Move up to the next cell
x_ind += dx_ind;
y_ind += dy_ind;
x_rem += dir.x * dt_best - TILE_WIDTH*dx_ind;
y_rem += dir.y * dt_best - TILE_WIDTH*dy_ind;
// Check to see if the new cell is solid
if (MAPDATA[y_ind*8 + x_ind] > 0) {
break;
}
}
// Calculate the collision location
const v2 collision = {
TILE_WIDTH*x_ind + x_rem,
TILE_WIDTH*y_ind + y_rem
};
// Calculate the ray length
const f32 ray_len = length( ((v2) {collision.x - state.camera_pos.x, collision.y - state.camera_pos.y}) );
// Calculate the pixel bounds that we fill the wall in for
int y_lo = (int)(SCREEN_SIZE_Y/2.0f - cam_len*state.camera_z/ray_len * SCREEN_SIZE_Y / state.camera_height);
int y_hi = (int)(SCREEN_SIZE_Y/2.0f + cam_len*(WALL_HEIGHT - state.camera_z)/ray_len * SCREEN_SIZE_Y / state.camera_height);
y_lo = max(y_lo, 0);
y_hi = min(y_hi, SCREEN_SIZE_Y-1);
u32 color_wall_to_render = (dx_ind == 0) ? color_wall[MAPDATA[y_ind*8 + x_ind]-1] : color_wall_light[MAPDATA[y_ind*8 + x_ind]-1];
draw_column(x, 0, y_lo-1, color_floor);
draw_column(x, y_lo, y_hi, color_wall_to_render);
draw_column(x, y_hi + 1, SCREEN_SIZE_Y-1, color_ceil);
}
}
int main(int argc, char *argv[]) {
// Initialize SDL
ASSERT(
SDL_Init(SDL_INIT_VIDEO) == 0,
"SDL initialization failed: %s\n",
SDL_GetError()
);
// Create a window
state.window = SDL_CreateWindow(
"TOOM",
SDL_WINDOWPOS_CENTERED_DISPLAY(1),
SDL_WINDOWPOS_CENTERED_DISPLAY(1),
SCREEN_SIZE_X,
SCREEN_SIZE_Y,
SDL_WINDOW_ALLOW_HIGHDPI);
ASSERT(state.window, "Error creating SDL window: %s\n", SDL_GetError());
// Create a renderer
state.renderer = SDL_CreateRenderer(state.window, -1, SDL_RENDERER_PRESENTVSYNC);
ASSERT(state.renderer, "Error creating SDL renderer: %s\n", SDL_GetError());
// Create a texture
state.texture = SDL_CreateTexture(
state.renderer,
SDL_PIXELFORMAT_ABGR8888,
SDL_TEXTUREACCESS_STREAMING,
SCREEN_SIZE_X,
SCREEN_SIZE_Y);
ASSERT(state.texture, "Error creating SDL texture: %s\n", SDL_GetError());
// Init camera
state.camera_pos = (v2) { 5.0f, 5.0f };
state.camera_dir = ((v2) {cos(0.0), sin(0.0)});
state.camera_dir_rotr = rotr((state.camera_dir));
state.camera_width = 1.5f;
state.camera_height = state.camera_width * SCREEN_SIZE_Y / SCREEN_SIZE_X;
state.camera_z = 0.4;
// Init player state
state.player_speed = (v2) { 0.0f, 0.0f };
state.player_omega = 0.0f;
// Init keyboard
clear_keyboard_state(&state.keyboard_state);
// Time structs
struct timeval timeval_start, timeval_end;
// Main loop
u32 time_prev_tick = SDL_GetTicks();
state.quit = 0;
while (state.quit == 0) {
const u32 time_start = SDL_GetTicks();
gettimeofday(&timeval_start, NULL);
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
state.quit = 1;
break;
} else if (event.type == SDL_KEYDOWN) {
switch (event.key.keysym.sym) {
case (SDLK_UP) : state.keyboard_state.up = KeyboardKeyState_Pressed; break;
case (SDLK_DOWN) : state.keyboard_state.down = KeyboardKeyState_Pressed; break;
case (SDLK_LEFT) : state.keyboard_state.left = KeyboardKeyState_Pressed; break;
case (SDLK_RIGHT) : state.keyboard_state.right = KeyboardKeyState_Pressed; break;
case (SDLK_a) : state.keyboard_state.a = KeyboardKeyState_Pressed; break;
case (SDLK_s) : state.keyboard_state.s = KeyboardKeyState_Pressed; break;
case (SDLK_d) : state.keyboard_state.d = KeyboardKeyState_Pressed; break;
case (SDLK_w) : state.keyboard_state.w = KeyboardKeyState_Pressed; break;
case (SDLK_q) : state.keyboard_state.q = KeyboardKeyState_Pressed; break;
case (SDLK_e) : state.keyboard_state.e = KeyboardKeyState_Pressed; break;
case (SDLK_1) : state.keyboard_state.one = KeyboardKeyState_Pressed; break;
case (SDLK_2) : state.keyboard_state.two = KeyboardKeyState_Pressed; break;
case (SDLK_3) : state.keyboard_state.three = KeyboardKeyState_Pressed; break;
case (SDLK_4) : state.keyboard_state.four = KeyboardKeyState_Pressed; break;
case (SDLK_5) : state.keyboard_state.five = KeyboardKeyState_Pressed; break;
case (SDLK_6) : state.keyboard_state.six = KeyboardKeyState_Pressed; break;
case (SDLK_7) : state.keyboard_state.seven = KeyboardKeyState_Pressed; break;
case (SDLK_8) : state.keyboard_state.eight = KeyboardKeyState_Pressed; break;
}
} else if (event.type == SDL_KEYUP) {
switch (event.key.keysym.sym) {
case (SDLK_UP) : state.keyboard_state.up = KeyboardKeyState_Released; break;
case (SDLK_DOWN) : state.keyboard_state.down = KeyboardKeyState_Released; break;
case (SDLK_LEFT) : state.keyboard_state.left = KeyboardKeyState_Released; break;
case (SDLK_RIGHT) : state.keyboard_state.right = KeyboardKeyState_Released; break;
case (SDLK_a) : state.keyboard_state.a = KeyboardKeyState_Released; break;
case (SDLK_s) : state.keyboard_state.s = KeyboardKeyState_Released; break;
case (SDLK_d) : state.keyboard_state.d = KeyboardKeyState_Released; break;
case (SDLK_w) : state.keyboard_state.w = KeyboardKeyState_Released; break;
case (SDLK_q) : state.keyboard_state.q = KeyboardKeyState_Released; break;
case (SDLK_e) : state.keyboard_state.e = KeyboardKeyState_Released; break;
case (SDLK_1) : state.keyboard_state.one = KeyboardKeyState_Released; break;
case (SDLK_2) : state.keyboard_state.two = KeyboardKeyState_Released; break;
case (SDLK_3) : state.keyboard_state.three = KeyboardKeyState_Released; break;
case (SDLK_4) : state.keyboard_state.four = KeyboardKeyState_Released; break;
case (SDLK_5) : state.keyboard_state.five = KeyboardKeyState_Released; break;
case (SDLK_6) : state.keyboard_state.six = KeyboardKeyState_Released; break;
case (SDLK_7) : state.keyboard_state.seven = KeyboardKeyState_Released; break;
case (SDLK_8) : state.keyboard_state.eight = KeyboardKeyState_Released; break;
}
}
}
// TODO: Move to more accurate timing?
const u32 time_tick_start = SDL_GetTicks();
const f32 dt = (time_tick_start - time_prev_tick) / 1000.0f;
tick(dt);
time_prev_tick = time_tick_start;
render();
decay_keyboard_state(&state.keyboard_state);
// Get timer end for all the non-SDL stuff
gettimeofday(&timeval_end, NULL);
f64 game_ms_elapsed = (timeval_end.tv_sec - timeval_start.tv_sec) * 1000.0; // sec to ms
game_ms_elapsed += (timeval_end.tv_usec - timeval_start.tv_usec) / 1000.0; // us to ms
// printf("Game: %.3f ms, %.1f fps\n", game_ms_elapsed, 1000.0f / max(1.0f, game_ms_elapsed));
SDL_UpdateTexture(state.texture, NULL, state.pixels, SCREEN_SIZE_X * 4);
SDL_RenderCopyEx(
state.renderer,
state.texture,
NULL,
NULL,
0.0,
NULL,
SDL_FLIP_VERTICAL);
// SDL_RENDERER_PRESENTVSYNC means this is syncronized with the monitor refresh rate. (30Hz)
SDL_RenderPresent(state.renderer);
const u32 time_end = SDL_GetTicks();
const u32 ms_elapsed = time_end - time_start;
const f32 fps = 1000.0f / max(1, ms_elapsed);
// printf("FPS: %.1f\n", fps);
}
SDL_DestroyWindow(state.window);
return 0;
}

View file

@ -0,0 +1 @@
magick montage -tile 3x1 -geometry +0+0 -background transparent .\assets\animations\torch_fixture_*.png assets/fixtures/torch_fixture.png