Clean up the source tree before reorganizing it.
This commit is contained in:
parent
779599f030
commit
03be0884a4
32 changed files with 24 additions and 8208 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -32,3 +32,5 @@ coverage/*
|
||||||
*.log
|
*.log
|
||||||
gdb.txt
|
gdb.txt
|
||||||
releases
|
releases
|
||||||
|
WORK
|
||||||
|
tmp
|
||||||
|
|
|
||||||
20
.ttarpit.json
Normal file
20
.ttarpit.json
Normal 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
BIN
assets/sprites/bonfire.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
BIN
assets/sprites/standing_anvil.png
Normal file
BIN
assets/sprites/standing_anvil.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
|
|
@ -13,6 +13,7 @@ cpp_args=[
|
||||||
'-Wno-unused-function',
|
'-Wno-unused-function',
|
||||||
'-Wno-unused-variable',
|
'-Wno-unused-variable',
|
||||||
'-Wno-unused-but-set-variable',
|
'-Wno-unused-but-set-variable',
|
||||||
|
'-Wno-deprecated-declarations',
|
||||||
]
|
]
|
||||||
link_args=[]
|
link_args=[]
|
||||||
# these are passed as override_defaults
|
# these are passed as override_defaults
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
#include "amt/raycaster.hpp"
|
|
||||||
#include <iostream>
|
|
||||||
#include <chrono>
|
|
||||||
#include <numeric>
|
|
||||||
#include <functional>
|
|
||||||
#include "constants.hpp"
|
|
||||||
#include "stats.hpp"
|
|
||||||
|
|
||||||
Matrix MAP{
|
|
||||||
{1,1,1,1,1,1,1,1,1},
|
|
||||||
{1,0,2,0,0,0,0,0,1},
|
|
||||||
{1,0,4,0,0,5,2,0,1},
|
|
||||||
{1,0,0,0,0,0,0,0,1},
|
|
||||||
{1,1,0,0,0,0,0,1,1},
|
|
||||||
{1,0,0,1,3,4,0,0,1},
|
|
||||||
{1,0,0,0,0,0,1,1,1},
|
|
||||||
{1,0,0,0,0,0,0,0,1},
|
|
||||||
{1,1,1,1,1,1,1,1,1}
|
|
||||||
};
|
|
||||||
|
|
||||||
void draw_gui(sf::RenderWindow &window, sf::Text &text, Stats &stats) {
|
|
||||||
sf::RectangleShape rect({SCREEN_WIDTH - RAY_VIEW_WIDTH, SCREEN_HEIGHT});
|
|
||||||
|
|
||||||
rect.setPosition({0,0});
|
|
||||||
rect.setFillColor({50, 50, 50});
|
|
||||||
window.draw(rect);
|
|
||||||
|
|
||||||
text.setString(
|
|
||||||
fmt::format("FPS\nmean:{:>8.5}\nsdev: {:>8.5}\nmin: {:>8.5}\nmax: {:>8.5}\ncount:{:<10}\n\nVSync? {}\nDebug? {}\n\nHit R to reset.",
|
|
||||||
stats.mean(), stats.stddev(), stats.min, stats.max, stats.n, VSYNC, DEBUG_BUILD));
|
|
||||||
window.draw(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
sf::RenderWindow window(sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Zed's Ray Caster Game Thing");
|
|
||||||
|
|
||||||
sf::Font font{"./assets/text.otf"};
|
|
||||||
sf::Text text{font};
|
|
||||||
text.setFillColor({255,255,255});
|
|
||||||
text.setPosition({10,10});
|
|
||||||
|
|
||||||
//ZED this should set with a function
|
|
||||||
float player_x = MAP.rows() / 2;
|
|
||||||
float player_y = MAP.cols() / 2;
|
|
||||||
|
|
||||||
Raycaster rayview(window, MAP, RAY_VIEW_WIDTH, RAY_VIEW_HEIGHT);
|
|
||||||
rayview.set_position(RAY_VIEW_X, RAY_VIEW_Y);
|
|
||||||
rayview.position_camera(player_x, player_y);
|
|
||||||
|
|
||||||
double moveSpeed = 0.1;
|
|
||||||
double rotSpeed = 0.1;
|
|
||||||
|
|
||||||
const auto onClose = [&window](const sf::Event::Closed&)
|
|
||||||
{
|
|
||||||
window.close();
|
|
||||||
};
|
|
||||||
|
|
||||||
Stats stats;
|
|
||||||
|
|
||||||
// NOTE: Enable this to lock frames at 60
|
|
||||||
window.setFramerateLimit(60);
|
|
||||||
|
|
||||||
while(window.isOpen()) {
|
|
||||||
auto start = std::chrono::high_resolution_clock::now();
|
|
||||||
rayview.render();
|
|
||||||
auto end = std::chrono::high_resolution_clock::now();
|
|
||||||
auto elapsed = std::chrono::duration<double>(end - start);
|
|
||||||
stats.sample(1/elapsed.count());
|
|
||||||
|
|
||||||
draw_gui(window, text, stats);
|
|
||||||
window.display();
|
|
||||||
|
|
||||||
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::W)) {
|
|
||||||
rayview.run(moveSpeed, 1);
|
|
||||||
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::S)) {
|
|
||||||
rayview.run(moveSpeed, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::D)) {
|
|
||||||
rayview.rotate(rotSpeed, -1);
|
|
||||||
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::A)) {
|
|
||||||
rayview.rotate(rotSpeed, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Key::R)) {
|
|
||||||
stats.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.handleEvents(onClose);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,197 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <initializer_list>
|
|
||||||
#include <iterator>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
namespace amt {
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
[[nodiscard]] constexpr auto cal_index(
|
|
||||||
std::size_t r,
|
|
||||||
std::size_t c,
|
|
||||||
[[maybe_unused]] std::size_t rs,
|
|
||||||
[[maybe_unused]] std::size_t cs
|
|
||||||
) -> std::size_t {
|
|
||||||
return r * cs + c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct Matrix {
|
|
||||||
using value_type = T;
|
|
||||||
using pointer = value_type*;
|
|
||||||
using const_pointer = value_type const*;
|
|
||||||
using reference = value_type&;
|
|
||||||
using const_reference = value_type const&;
|
|
||||||
using iterator = pointer;
|
|
||||||
using const_iterator = const_pointer;
|
|
||||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
|
||||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
|
||||||
using difference_type = std::ptrdiff_t;
|
|
||||||
using size_type = std::size_t;
|
|
||||||
|
|
||||||
template <bool IsConst>
|
|
||||||
struct View {
|
|
||||||
using base_type = std::conditional_t<IsConst, const_pointer, pointer>;
|
|
||||||
base_type data;
|
|
||||||
size_type r;
|
|
||||||
size_type rows;
|
|
||||||
size_type cols;
|
|
||||||
|
|
||||||
constexpr reference operator[](size_type c) noexcept requires (!IsConst) {
|
|
||||||
assert(c < cols && "Out of bound access");
|
|
||||||
auto const index = detail::cal_index(r, c, rows, cols);
|
|
||||||
return data[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr const_reference operator[](size_type c) const noexcept {
|
|
||||||
assert(c < cols && "Out of bound access");
|
|
||||||
auto const index = detail::cal_index(r, c, rows, cols);
|
|
||||||
return data[index];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr Matrix() noexcept = default;
|
|
||||||
Matrix(Matrix const& other)
|
|
||||||
: Matrix(other.rows(), other.cols())
|
|
||||||
{
|
|
||||||
std::copy(other.begin(), other.end(), begin());
|
|
||||||
}
|
|
||||||
Matrix& operator=(Matrix const& other) {
|
|
||||||
if (this == &other) return *this;
|
|
||||||
auto temp = Matrix(other);
|
|
||||||
swap(temp, *this);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
constexpr Matrix(Matrix && other) noexcept
|
|
||||||
: m_data(other.m_data)
|
|
||||||
, m_row(other.m_row)
|
|
||||||
, m_col(other.m_col)
|
|
||||||
{
|
|
||||||
other.m_data = nullptr;
|
|
||||||
}
|
|
||||||
constexpr Matrix& operator=(Matrix && other) noexcept {
|
|
||||||
if (this == &other) return *this;
|
|
||||||
swap(*this, other);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
~Matrix() {
|
|
||||||
if (m_data) delete[] m_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Matrix(size_type row, size_type col)
|
|
||||||
: m_data(new value_type[row * col])
|
|
||||||
, m_row(row)
|
|
||||||
, m_col(col)
|
|
||||||
{}
|
|
||||||
|
|
||||||
Matrix(size_type row, size_type col, value_type def)
|
|
||||||
: Matrix(row, col)
|
|
||||||
{
|
|
||||||
std::fill(begin(), end(), def);
|
|
||||||
}
|
|
||||||
|
|
||||||
Matrix(std::initializer_list<std::initializer_list<value_type>> li)
|
|
||||||
: m_row(li.size())
|
|
||||||
{
|
|
||||||
for (auto const& row: li) {
|
|
||||||
m_col = std::max(m_col, row.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto const size = m_row * m_col;
|
|
||||||
|
|
||||||
if (size == 0) return;
|
|
||||||
|
|
||||||
m_data = new value_type[size];
|
|
||||||
std::fill_n(m_data, size, 0);
|
|
||||||
|
|
||||||
for (auto r = 0ul; auto const& row: li) {
|
|
||||||
for (auto c = 0ul; auto const& col: row) {
|
|
||||||
this->operator()(r, c++) = col;
|
|
||||||
}
|
|
||||||
++r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool empty() const noexcept { return size() == 0; }
|
|
||||||
constexpr size_type size() const noexcept { return rows() * cols(); }
|
|
||||||
constexpr size_type rows() const noexcept { return m_row; }
|
|
||||||
constexpr size_type cols() const noexcept { return m_col; }
|
|
||||||
constexpr auto data() noexcept -> pointer { return m_data; }
|
|
||||||
constexpr auto data() const noexcept -> const_pointer { return m_data; }
|
|
||||||
|
|
||||||
constexpr iterator begin() noexcept { return m_data; }
|
|
||||||
constexpr iterator end() noexcept { return m_data + size(); }
|
|
||||||
constexpr const_iterator begin() const noexcept { return m_data; }
|
|
||||||
constexpr const_iterator end() const noexcept { return m_data + size(); }
|
|
||||||
constexpr reverse_iterator rbegin() noexcept { return std::reverse_iterator(end()); }
|
|
||||||
constexpr reverse_iterator rend() noexcept { return std::reverse_iterator(begin()); }
|
|
||||||
constexpr const_reverse_iterator rbegin() const noexcept { return std::reverse_iterator(end()); }
|
|
||||||
constexpr const_reverse_iterator rend() const noexcept { return std::reverse_iterator(begin()); }
|
|
||||||
|
|
||||||
constexpr auto operator()(size_type r, size_type c) noexcept -> reference {
|
|
||||||
auto const index = detail::cal_index(r, c, rows(), cols());
|
|
||||||
assert(index < size() && "Out of bound access");
|
|
||||||
return m_data[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr auto operator()(size_type r, size_type c) const noexcept -> const_reference {
|
|
||||||
auto const index = detail::cal_index(r, c, rows(), cols());
|
|
||||||
assert(index < size() && "Out of bound access");
|
|
||||||
return m_data[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr auto operator[](size_type r) noexcept -> View<false> {
|
|
||||||
assert(r < rows() && "Out of bound access");
|
|
||||||
return { .data = m_data, .r = r, .rows = m_row, .cols = m_col };
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr auto operator[](size_type r) const noexcept -> View<true> {
|
|
||||||
assert(r < rows() && "Out of bound access");
|
|
||||||
return { .data = m_data, .r = r, .rows = m_row, .cols = m_col };
|
|
||||||
}
|
|
||||||
|
|
||||||
friend void swap(Matrix& lhs, Matrix& rhs) noexcept {
|
|
||||||
using std::swap;
|
|
||||||
swap(lhs.m_data, rhs.m_data);
|
|
||||||
swap(lhs.m_row, rhs.m_row);
|
|
||||||
swap(lhs.m_col, rhs.m_col);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
pointer m_data{};
|
|
||||||
size_type m_row{};
|
|
||||||
size_type m_col{};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace amt
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
#include <format>
|
|
||||||
namespace std {
|
|
||||||
template <typename T>
|
|
||||||
struct formatter<amt::Matrix<T>> {
|
|
||||||
constexpr auto parse(format_parse_context& ctx) {
|
|
||||||
return ctx.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto format(amt::Matrix<T> const& m, auto& ctx) const {
|
|
||||||
std::string s = "[\n";
|
|
||||||
for (auto r = std::size_t{}; r < m.rows(); ++r) {
|
|
||||||
for (auto c = std::size_t{}; c < m.cols(); ++c) {
|
|
||||||
s += std::format("{}, ", m(r, c));
|
|
||||||
}
|
|
||||||
s += '\n';
|
|
||||||
}
|
|
||||||
s += "]";
|
|
||||||
return format_to(ctx.out(), "{}", s);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace std
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,746 +0,0 @@
|
||||||
#ifndef AMT_PIXEL_HPP
|
|
||||||
#define AMT_PIXEL_HPP
|
|
||||||
|
|
||||||
#include "matrix.hpp"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <limits>
|
|
||||||
#include <string>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
namespace amt {
|
|
||||||
|
|
||||||
enum class PixelFormat {
|
|
||||||
rgba,
|
|
||||||
abgr,
|
|
||||||
rgb ,
|
|
||||||
bgr ,
|
|
||||||
ga , // gray scale and alpha
|
|
||||||
ag , // alpha and gray scale
|
|
||||||
g // gray scale
|
|
||||||
};
|
|
||||||
|
|
||||||
inline static constexpr auto get_pixel_format_from_channel(std::size_t c, bool little_endian = false) -> PixelFormat {
|
|
||||||
switch (c) {
|
|
||||||
case 1: return PixelFormat::g;
|
|
||||||
case 2: return little_endian ? PixelFormat::ag : PixelFormat::ga;
|
|
||||||
case 3: return little_endian ? PixelFormat::bgr : PixelFormat::rgb;
|
|
||||||
case 4: return little_endian ? PixelFormat::abgr : PixelFormat::abgr;
|
|
||||||
}
|
|
||||||
throw std::runtime_error(std::string("get_pixel_format_from_channel: unknown channel ") + std::to_string(c));
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
static constexpr auto compare_float(float l, float r) noexcept -> bool {
|
|
||||||
return std::abs(l - r) < std::numeric_limits<float>::epsilon();
|
|
||||||
}
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
enum class BlendMode {
|
|
||||||
normal,
|
|
||||||
multiply,
|
|
||||||
screen,
|
|
||||||
overlay,
|
|
||||||
darken,
|
|
||||||
lighten,
|
|
||||||
colorDodge,
|
|
||||||
colorBurn,
|
|
||||||
hardLight,
|
|
||||||
softLight,
|
|
||||||
difference,
|
|
||||||
exclusion
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RGBA {
|
|
||||||
using pixel_t = std::uint8_t;
|
|
||||||
using pixels_t = std::uint32_t;
|
|
||||||
|
|
||||||
constexpr RGBA() noexcept = default;
|
|
||||||
constexpr RGBA(RGBA const&) noexcept = default;
|
|
||||||
constexpr RGBA(RGBA &&) noexcept = default;
|
|
||||||
constexpr RGBA& operator=(RGBA const&) noexcept = default;
|
|
||||||
constexpr RGBA& operator=(RGBA &&) noexcept = default;
|
|
||||||
constexpr ~RGBA() noexcept = default;
|
|
||||||
|
|
||||||
|
|
||||||
// NOTE: Accepts RRGGBBAA
|
|
||||||
explicit constexpr RGBA(pixels_t color) noexcept
|
|
||||||
: RGBA((color >> (8 * 3)) & 0xff, (color >> (8 * 2)) & 0xff, (color >> (8 * 1)) & 0xff, (color >> (8 * 0)) & 0xff)
|
|
||||||
{}
|
|
||||||
|
|
||||||
constexpr RGBA(pixel_t r, pixel_t g, pixel_t b, pixel_t a = 0xff) noexcept
|
|
||||||
: m_data {r, g, b, a}
|
|
||||||
{}
|
|
||||||
|
|
||||||
constexpr RGBA(pixel_t color, pixel_t a = 0xff) noexcept
|
|
||||||
: RGBA(color, color, color, a)
|
|
||||||
{}
|
|
||||||
|
|
||||||
// NOTE: Returns RRGGBBAA
|
|
||||||
constexpr auto to_hex() const noexcept -> pixels_t {
|
|
||||||
auto r = static_cast<pixels_t>(this->r());
|
|
||||||
auto b = static_cast<pixels_t>(this->b());
|
|
||||||
auto g = static_cast<pixels_t>(this->g());
|
|
||||||
auto a = static_cast<pixels_t>(this->a());
|
|
||||||
return (r << (8 * 3)) | (g << (8 * 2)) | (b << (8 * 1)) | (a << (8 * 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr auto r() const noexcept -> pixel_t { return m_data[0]; }
|
|
||||||
constexpr auto g() const noexcept -> pixel_t { return m_data[1]; }
|
|
||||||
constexpr auto b() const noexcept -> pixel_t { return m_data[2]; }
|
|
||||||
constexpr auto a() const noexcept -> pixel_t { return m_data[3]; }
|
|
||||||
|
|
||||||
constexpr auto r() noexcept -> pixel_t& { return m_data[0]; }
|
|
||||||
constexpr auto g() noexcept -> pixel_t& { return m_data[1]; }
|
|
||||||
constexpr auto b() noexcept -> pixel_t& { return m_data[2]; }
|
|
||||||
constexpr auto a() noexcept -> pixel_t& { return m_data[3]; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns the value is between 0 and 1
|
|
||||||
*/
|
|
||||||
constexpr auto brightness() const noexcept -> float {
|
|
||||||
// 0.299*R + 0.587*G + 0.114*B
|
|
||||||
auto tr = normalize(r());
|
|
||||||
auto tg = normalize(g());
|
|
||||||
auto tb = normalize(b());
|
|
||||||
return (0.299 * tr + 0.587 * tg + 0.114 * tb);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
requires std::is_arithmetic_v<T>
|
|
||||||
constexpr auto operator/(T val) const noexcept {
|
|
||||||
auto d = static_cast<float>(val);
|
|
||||||
return RGBA(
|
|
||||||
static_cast<pixel_t>(r() / d),
|
|
||||||
static_cast<pixel_t>(g() / d),
|
|
||||||
static_cast<pixel_t>(b() / d),
|
|
||||||
a()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
requires std::is_arithmetic_v<T>
|
|
||||||
constexpr auto operator*(T val) const noexcept {
|
|
||||||
auto d = static_cast<float>(val);
|
|
||||||
return RGBA(
|
|
||||||
static_cast<pixel_t>(r() * d),
|
|
||||||
static_cast<pixel_t>(g() * d),
|
|
||||||
static_cast<pixel_t>(b() * d),
|
|
||||||
a()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
static constexpr auto normalize(pixel_t p) noexcept -> float {
|
|
||||||
return float(p) / 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr auto to_pixel(float p) noexcept -> pixel_t {
|
|
||||||
return static_cast<pixel_t>(p * 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <BlendMode M>
|
|
||||||
static constexpr auto blend_helper() noexcept {
|
|
||||||
constexpr auto mix_helper = [](float s, float b, float a) -> float {
|
|
||||||
// (1 - αb) x Cs + αb x B(Cb, Cs)
|
|
||||||
return (1 - a) * s + a * b;
|
|
||||||
};
|
|
||||||
|
|
||||||
if constexpr (M == BlendMode::normal) {
|
|
||||||
return [mix_helper](float bg, float fg, float alpha) { return mix_helper(bg, fg, alpha); };
|
|
||||||
} else if constexpr (M == BlendMode::multiply) {
|
|
||||||
return [mix_helper](float bg, float fg, float alpha) { return mix_helper(bg, bg * fg, alpha); };
|
|
||||||
} else if constexpr (M == BlendMode::screen) {
|
|
||||||
return [mix_helper](float bg, float fg, float alpha) {
|
|
||||||
// Cb + Cs -(Cb x Cs)
|
|
||||||
auto bf = bg + fg - (bg * fg);
|
|
||||||
return mix_helper(bg, bf, alpha);
|
|
||||||
};
|
|
||||||
} else if constexpr (M == BlendMode::overlay) {
|
|
||||||
return [mix_helper](float bg, float fg, float alpha, auto&& hard_light_fn, auto&& multiply_fn, auto&& screen_fn) {
|
|
||||||
// HardLight(Cs, Cb)
|
|
||||||
auto hl = hard_light_fn(bg, fg, alpha, multiply_fn, screen_fn);
|
|
||||||
return mix_helper(bg, hl, alpha);
|
|
||||||
};
|
|
||||||
} else if constexpr (M == BlendMode::darken) {
|
|
||||||
return [mix_helper](float bg, float fg, float alpha) {
|
|
||||||
return mix_helper(bg, std::min(bg, fg), alpha);
|
|
||||||
};
|
|
||||||
} else if constexpr (M == BlendMode::lighten) {
|
|
||||||
return [mix_helper](float bg, float fg, float alpha) {
|
|
||||||
return mix_helper(bg, std::max(bg, fg), alpha);
|
|
||||||
};
|
|
||||||
} else if constexpr (M == BlendMode::colorDodge) {
|
|
||||||
return [mix_helper](float bg, float fg, float alpha) {
|
|
||||||
constexpr auto fn = [](float b, float s) -> float {
|
|
||||||
if (b == 0) return 0;
|
|
||||||
if (s == 255) return 255;
|
|
||||||
return std::min(1.f, b / (1.f - s));
|
|
||||||
};
|
|
||||||
auto bf = fn(bg, fg);
|
|
||||||
return mix_helper(bg, bf, alpha);
|
|
||||||
};
|
|
||||||
} else if constexpr (M == BlendMode::colorBurn) {
|
|
||||||
return [mix_helper](float bg, float fg, float alpha) {
|
|
||||||
constexpr auto fn = [](float b, float s) -> float {
|
|
||||||
if (b == 255) return 255;
|
|
||||||
if (s == 0) return 0;
|
|
||||||
return 1.f - std::min(1.f, (1.f - b) / s);
|
|
||||||
};
|
|
||||||
auto bf = fn(bg, fg);
|
|
||||||
return mix_helper(bg, bf, alpha);
|
|
||||||
};
|
|
||||||
} else if constexpr (M == BlendMode::hardLight) {
|
|
||||||
return [mix_helper](float bg, float fg, float alpha, auto&& multiply_fn, auto&& screen_fn) {
|
|
||||||
auto fn = [&multiply_fn, &screen_fn](float b, float s, float a) -> float {
|
|
||||||
if (s <= 0.5f) {
|
|
||||||
return multiply_fn(b, 2.f * s, a);
|
|
||||||
} else {
|
|
||||||
return screen_fn(b, 2.f * s - 1.f, a);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto bf = fn(bg, fg, alpha);
|
|
||||||
return mix_helper(bg, bf, alpha);
|
|
||||||
};
|
|
||||||
} else if constexpr (M == BlendMode::softLight) {
|
|
||||||
return [mix_helper](float bg, float fg, float alpha) {
|
|
||||||
constexpr auto fn = [](float b, float s) -> float {
|
|
||||||
if (s <= 0.5f) {
|
|
||||||
// B(Cb, Cs) = Cb - (1 - 2 x Cs) x Cb x (1 - Cb)
|
|
||||||
return b - (1.f - 2.f * s) * b * (1 - b);
|
|
||||||
} else {
|
|
||||||
float d{};
|
|
||||||
|
|
||||||
if (b <= 0.5f) {
|
|
||||||
// D(Cb) = ((16 * Cb - 12) x Cb + 4) x Cb
|
|
||||||
d = ((16 * b - 12) * b + 4) * b;
|
|
||||||
} else {
|
|
||||||
// D(Cb) = sqrt(Cb)
|
|
||||||
d = std::sqrt(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
// B(Cb, Cs) = Cb + (2 x Cs - 1) x (D(Cb) - Cb)
|
|
||||||
return b + (2 * s - 1) * (d - b);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto bf = fn(bg, fg);
|
|
||||||
return mix_helper(bg, bf, alpha);
|
|
||||||
};
|
|
||||||
} else if constexpr (M == BlendMode::difference) {
|
|
||||||
return [mix_helper](float bg, float fg, float alpha) {
|
|
||||||
// B(Cb, Cs) = | Cb - Cs |
|
|
||||||
return mix_helper(bg, (bg > fg ? (bg - fg) : (fg - bg)), alpha);
|
|
||||||
};
|
|
||||||
} else if constexpr (M == BlendMode::exclusion) {
|
|
||||||
return [mix_helper](float bg, float fg, float alpha) {
|
|
||||||
constexpr auto fn = [](float b, float s) -> float {
|
|
||||||
// B(Cb, Cs) = Cb + Cs - 2 x Cb x Cs
|
|
||||||
return b + s - 2 * b * s;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto bf = fn(bg, fg);
|
|
||||||
return mix_helper(bg, bf, alpha);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
template <BlendMode M>
|
|
||||||
constexpr auto blend(RGBA color) const noexcept -> RGBA {
|
|
||||||
auto ab = normalize(a());
|
|
||||||
auto as = normalize(color.a());
|
|
||||||
// αs x 1 + αb x (1 – αs)
|
|
||||||
auto alpha = to_pixel(as + ab * (1 - as));
|
|
||||||
auto lr = normalize(r());
|
|
||||||
auto lg = normalize(g());
|
|
||||||
auto lb = normalize(b());
|
|
||||||
auto rr = normalize(color.r());
|
|
||||||
auto rg = normalize(color.g());
|
|
||||||
auto rb = normalize(color.b());
|
|
||||||
|
|
||||||
auto nr = 0.f;
|
|
||||||
auto ng = 0.f;
|
|
||||||
auto nb = 0.f;
|
|
||||||
|
|
||||||
if constexpr (M == BlendMode::normal) {
|
|
||||||
auto fn = blend_helper<BlendMode::normal>();
|
|
||||||
nr = fn(lr, rr, ab);
|
|
||||||
ng = fn(lg, rg, ab);
|
|
||||||
nb = fn(lb, rb, ab);
|
|
||||||
} else if constexpr (M == BlendMode::multiply) {
|
|
||||||
auto fn = blend_helper<BlendMode::multiply>();
|
|
||||||
nr = fn(lr, rr, ab);
|
|
||||||
ng = fn(lg, rg, ab);
|
|
||||||
nb = fn(lb, rb, ab);
|
|
||||||
} else if constexpr (M == BlendMode::screen) {
|
|
||||||
auto fn = blend_helper<BlendMode::screen>();
|
|
||||||
nr = fn(lr, rr, ab);
|
|
||||||
ng = fn(lg, rg, ab);
|
|
||||||
nb = fn(lb, rb, ab);
|
|
||||||
} else if constexpr (M == BlendMode::overlay) {
|
|
||||||
auto fn = blend_helper<BlendMode::overlay>();
|
|
||||||
auto hard_light_fn = blend_helper<BlendMode::hardLight>();
|
|
||||||
auto multiply_fn = blend_helper<BlendMode::multiply>();
|
|
||||||
auto screen_fn = blend_helper<BlendMode::screen>();
|
|
||||||
nr = fn(lr, rr, ab, hard_light_fn, multiply_fn, screen_fn);
|
|
||||||
ng = fn(lg, rg, ab, hard_light_fn, multiply_fn, screen_fn);
|
|
||||||
nb = fn(lb, rb, ab, hard_light_fn, multiply_fn, screen_fn);
|
|
||||||
} else if constexpr (M == BlendMode::darken) {
|
|
||||||
auto fn = blend_helper<BlendMode::darken>();
|
|
||||||
nr = fn(lr, rr, ab);
|
|
||||||
ng = fn(lg, rg, ab);
|
|
||||||
nb = fn(lb, rb, ab);
|
|
||||||
} else if constexpr (M == BlendMode::lighten) {
|
|
||||||
auto fn = blend_helper<BlendMode::lighten>();
|
|
||||||
nr = fn(lr, rr, ab);
|
|
||||||
ng = fn(lg, rg, ab);
|
|
||||||
nb = fn(lb, rb, ab);
|
|
||||||
} else if constexpr (M == BlendMode::colorDodge) {
|
|
||||||
auto fn = blend_helper<BlendMode::colorDodge>();
|
|
||||||
nr = fn(lr, rr, ab);
|
|
||||||
ng = fn(lg, rg, ab);
|
|
||||||
nb = fn(lb, rb, ab);
|
|
||||||
} else if constexpr (M == BlendMode::colorBurn) {
|
|
||||||
auto fn = blend_helper<BlendMode::colorDodge>();
|
|
||||||
nr = fn(lr, rr, ab);
|
|
||||||
ng = fn(lg, rg, ab);
|
|
||||||
nb = fn(lb, rb, ab);
|
|
||||||
} else if constexpr (M == BlendMode::hardLight) {
|
|
||||||
auto fn = blend_helper<BlendMode::hardLight>();
|
|
||||||
auto multiply_fn = blend_helper<BlendMode::multiply>();
|
|
||||||
auto screen_fn = blend_helper<BlendMode::screen>();
|
|
||||||
nr = fn(lr, rr, ab, multiply_fn, screen_fn);
|
|
||||||
ng = fn(lg, rg, ab, multiply_fn, screen_fn);
|
|
||||||
nb = fn(lb, rb, ab, multiply_fn, screen_fn);
|
|
||||||
} else if constexpr (M == BlendMode::softLight) {
|
|
||||||
auto fn = blend_helper<BlendMode::softLight>();
|
|
||||||
nr = fn(lr, rr, ab);
|
|
||||||
ng = fn(lg, rg, ab);
|
|
||||||
nb = fn(lb, rb, ab);
|
|
||||||
} else if constexpr (M == BlendMode::difference) {
|
|
||||||
auto fn = blend_helper<BlendMode::difference>();
|
|
||||||
nr = fn(lr, rr, ab);
|
|
||||||
ng = fn(lg, rg, ab);
|
|
||||||
nb = fn(lb, rb, ab);
|
|
||||||
} else if constexpr (M == BlendMode::exclusion) {
|
|
||||||
auto fn = blend_helper<BlendMode::exclusion>();
|
|
||||||
nr = fn(lr, rr, ab);
|
|
||||||
ng = fn(lg, rg, ab);
|
|
||||||
nb = fn(lb, rb, ab);
|
|
||||||
}
|
|
||||||
|
|
||||||
return RGBA(
|
|
||||||
to_pixel(nr),
|
|
||||||
to_pixel(ng),
|
|
||||||
to_pixel(nb),
|
|
||||||
alpha
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr auto blend(RGBA color, BlendMode mode) const noexcept -> RGBA {
|
|
||||||
switch (mode) {
|
|
||||||
case BlendMode::normal: return blend<BlendMode::normal>(color);
|
|
||||||
case BlendMode::multiply: return blend<BlendMode::multiply>(color);
|
|
||||||
case BlendMode::screen: return blend<BlendMode::screen>(color);
|
|
||||||
case BlendMode::overlay: return blend<BlendMode::overlay>(color);
|
|
||||||
case BlendMode::darken: return blend<BlendMode::darken>(color);
|
|
||||||
case BlendMode::lighten: return blend<BlendMode::lighten>(color);
|
|
||||||
case BlendMode::colorDodge: return blend<BlendMode::colorDodge>(color);
|
|
||||||
case BlendMode::colorBurn: return blend<BlendMode::colorBurn>(color);
|
|
||||||
case BlendMode::hardLight: return blend<BlendMode::hardLight>(color);
|
|
||||||
case BlendMode::softLight: return blend<BlendMode::softLight>(color);
|
|
||||||
case BlendMode::difference: return blend<BlendMode::difference>(color);
|
|
||||||
case BlendMode::exclusion: return blend<BlendMode::exclusion>(color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
pixel_t m_data[4]{};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HSLA {
|
|
||||||
using pixel_t = float;
|
|
||||||
using pixels_t = float[4];
|
|
||||||
|
|
||||||
// ensures pixel to be in range
|
|
||||||
template <unsigned min, unsigned max>
|
|
||||||
struct PixelWrapper {
|
|
||||||
pixel_t& p;
|
|
||||||
|
|
||||||
constexpr PixelWrapper& operator=(float val) noexcept {
|
|
||||||
p = std::clamp(val, float(min), float(max));
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr operator pixel_t() const noexcept { return p; }
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr HSLA() noexcept = default;
|
|
||||||
constexpr HSLA(HSLA const&) noexcept = default;
|
|
||||||
constexpr HSLA(HSLA &&) noexcept = default;
|
|
||||||
constexpr HSLA& operator=(HSLA const&) noexcept = default;
|
|
||||||
constexpr HSLA& operator=(HSLA &&) noexcept = default;
|
|
||||||
constexpr ~HSLA() noexcept = default;
|
|
||||||
|
|
||||||
constexpr HSLA(pixel_t h, pixel_t s, pixel_t l, pixel_t a = 100) noexcept
|
|
||||||
: m_data({ .hsla = { .h = h, .s = s, .l = l, .a = a } })
|
|
||||||
{}
|
|
||||||
|
|
||||||
constexpr HSLA(RGBA color) noexcept {
|
|
||||||
auto min = std::min({color.r(), color.g(), color.b()});
|
|
||||||
auto max = std::max({color.r(), color.g(), color.b()});
|
|
||||||
auto c = (max - min) / 255.f;
|
|
||||||
|
|
||||||
auto tr = float(color.r()) / 255;
|
|
||||||
auto tg = float(color.g()) / 255;
|
|
||||||
auto tb = float(color.b()) / 255;
|
|
||||||
auto ta = float(color.a()) / 255;
|
|
||||||
|
|
||||||
float hue = 0;
|
|
||||||
float s = 0;
|
|
||||||
auto l = ((max + min) / 2.f) / 255.f;
|
|
||||||
|
|
||||||
if (min == max) {
|
|
||||||
if (max == color.r()) {
|
|
||||||
auto seg = (tg - tb) / c;
|
|
||||||
auto shift = (seg < 0 ? 360.f : 0.f) / 60.f;
|
|
||||||
hue = seg + shift;
|
|
||||||
} else if (max == color.g()) {
|
|
||||||
auto seg = (tb - tr) / c;
|
|
||||||
auto shift = 120.f / 60.f;
|
|
||||||
hue = seg + shift;
|
|
||||||
} else {
|
|
||||||
auto seg = (tr - tg) / c;
|
|
||||||
auto shift = 240.f / 60.f;
|
|
||||||
hue = seg + shift;
|
|
||||||
}
|
|
||||||
s = c / (1 - std::abs(2 * l - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
hue = hue * 60.f + 360.f;
|
|
||||||
auto q = static_cast<float>(static_cast<int>(hue / 360.f));
|
|
||||||
hue -= q * 360.f;
|
|
||||||
|
|
||||||
m_data.hsla.h = hue;
|
|
||||||
m_data.hsla.s = s * 100.f;
|
|
||||||
m_data.hsla.l = l * 100.f;
|
|
||||||
m_data.hsla.a = ta * 100.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr operator RGBA() const noexcept {
|
|
||||||
auto ts = s() / 100.f;
|
|
||||||
auto tl = l() / 100.f;
|
|
||||||
auto ta = a() / 100.f;
|
|
||||||
if (s() == 0) return RGBA(to_pixel(tl), to_pixel(ta));
|
|
||||||
|
|
||||||
auto th = h() / 360.f;
|
|
||||||
|
|
||||||
float const q = tl < 0.5 ? tl * (1 + ts) : tl + ts - tl * ts;
|
|
||||||
float const p = 2 * tl - q;
|
|
||||||
|
|
||||||
return RGBA(
|
|
||||||
to_pixel(convert_hue(p, q, th + 1.f / 3)),
|
|
||||||
to_pixel(convert_hue(p, q, th)),
|
|
||||||
to_pixel(convert_hue(p, q, th - 1.f / 3)),
|
|
||||||
to_pixel(ta)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr auto blend(HSLA color, BlendMode mode) const noexcept -> HSLA {
|
|
||||||
auto lhs = RGBA(*this);
|
|
||||||
auto rhs = RGBA(color);
|
|
||||||
return HSLA(lhs.blend(rhs, mode));
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr auto h() const noexcept -> pixel_t { return m_data.hsla.h; }
|
|
||||||
constexpr auto s() const noexcept -> pixel_t { return m_data.hsla.s; }
|
|
||||||
constexpr auto l() const noexcept -> pixel_t { return m_data.hsla.l; }
|
|
||||||
constexpr auto a() const noexcept -> pixel_t { return m_data.hsla.a; }
|
|
||||||
|
|
||||||
constexpr auto h() noexcept -> PixelWrapper<0, 360> { return { m_data.hsla.h }; }
|
|
||||||
constexpr auto s() noexcept -> PixelWrapper<0, 100> { return { m_data.hsla.s }; }
|
|
||||||
constexpr auto l() noexcept -> PixelWrapper<0, 100> { return { m_data.hsla.l }; }
|
|
||||||
constexpr auto a() noexcept -> PixelWrapper<0, 100> { return { m_data.hsla.a }; }
|
|
||||||
private:
|
|
||||||
|
|
||||||
static constexpr auto to_pixel(float a) noexcept -> RGBA::pixel_t {
|
|
||||||
return static_cast<RGBA::pixel_t>(a * 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr auto convert_hue(float p, float q, float t) noexcept -> float {
|
|
||||||
t = t - (t > 1) + (t < 0);
|
|
||||||
if (t * 6 < 1) return p + (q - p) * 6 * t;
|
|
||||||
if (t * 2 < 1) return q;
|
|
||||||
if (t * 3 < 2) return p + (q - p) * (2.f / 3 - t) * 6;
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
pixel_t h{}; // hue: 0-360
|
|
||||||
pixel_t s{}; // saturation: 0-100%
|
|
||||||
pixel_t l{}; // lightness: 0-100%
|
|
||||||
pixel_t a{}; // alpha: 0-100%
|
|
||||||
} hsla;
|
|
||||||
pixels_t color;
|
|
||||||
} m_data{};
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
template <PixelFormat F>
|
|
||||||
inline static constexpr auto parse_pixel_helper(RGBA color, std::uint8_t* out_ptr) noexcept {
|
|
||||||
if constexpr (F == PixelFormat::rgba) {
|
|
||||||
out_ptr[0] = color.r();
|
|
||||||
out_ptr[1] = color.g();
|
|
||||||
out_ptr[2] = color.b();
|
|
||||||
out_ptr[3] = color.a();
|
|
||||||
} else if constexpr (F == PixelFormat::abgr) {
|
|
||||||
out_ptr[0] = color.a();
|
|
||||||
out_ptr[1] = color.b();
|
|
||||||
out_ptr[2] = color.g();
|
|
||||||
out_ptr[3] = color.r();
|
|
||||||
} else if constexpr (F == PixelFormat::rgb) {
|
|
||||||
out_ptr[0] = color.r();
|
|
||||||
out_ptr[1] = color.g();
|
|
||||||
out_ptr[2] = color.b();
|
|
||||||
} else if constexpr (F == PixelFormat::bgr) {
|
|
||||||
out_ptr[0] = color.b();
|
|
||||||
out_ptr[1] = color.g();
|
|
||||||
out_ptr[2] = color.r();
|
|
||||||
} else if constexpr (F == PixelFormat::ga) {
|
|
||||||
out_ptr[0] = color.r();
|
|
||||||
out_ptr[1] = color.a();
|
|
||||||
} else if constexpr (F == PixelFormat::ag) {
|
|
||||||
out_ptr[0] = color.a();
|
|
||||||
out_ptr[1] = color.r();
|
|
||||||
} else {
|
|
||||||
out_ptr[0] = color.r();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <PixelFormat F>
|
|
||||||
inline static constexpr auto parse_pixel_helper(std::uint8_t const* in_ptr) noexcept -> RGBA {
|
|
||||||
if constexpr (F == PixelFormat::rgba) {
|
|
||||||
return {
|
|
||||||
in_ptr[0],
|
|
||||||
in_ptr[1],
|
|
||||||
in_ptr[2],
|
|
||||||
in_ptr[3]
|
|
||||||
};
|
|
||||||
} else if constexpr (F == PixelFormat::abgr) {
|
|
||||||
return {
|
|
||||||
in_ptr[3],
|
|
||||||
in_ptr[2],
|
|
||||||
in_ptr[1],
|
|
||||||
in_ptr[0]
|
|
||||||
};
|
|
||||||
} else if constexpr (F == PixelFormat::rgb) {
|
|
||||||
return {
|
|
||||||
in_ptr[0],
|
|
||||||
in_ptr[1],
|
|
||||||
in_ptr[2],
|
|
||||||
0xff
|
|
||||||
};
|
|
||||||
} else if constexpr (F == PixelFormat::bgr) {
|
|
||||||
return {
|
|
||||||
in_ptr[2],
|
|
||||||
in_ptr[1],
|
|
||||||
in_ptr[0],
|
|
||||||
0xff
|
|
||||||
};
|
|
||||||
} else if constexpr (F == PixelFormat::ga) {
|
|
||||||
return {
|
|
||||||
in_ptr[0],
|
|
||||||
in_ptr[1],
|
|
||||||
};
|
|
||||||
} else if constexpr (F == PixelFormat::ag) {
|
|
||||||
return {
|
|
||||||
in_ptr[1],
|
|
||||||
in_ptr[0],
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return { in_ptr[0], 0xff };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
inline static constexpr auto get_pixel_format_channel(PixelFormat format) noexcept -> std::size_t {
|
|
||||||
switch (format) {
|
|
||||||
case PixelFormat::rgba: case PixelFormat::abgr: return 4u;
|
|
||||||
case PixelFormat::rgb: case PixelFormat::bgr: return 3u;
|
|
||||||
case PixelFormat::ga: case PixelFormat::ag: return 2u;
|
|
||||||
case PixelFormat::g: return 1u;
|
|
||||||
}
|
|
||||||
assert(false && "unreachable");
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PixelBuf {
|
|
||||||
private:
|
|
||||||
public:
|
|
||||||
using value_type = RGBA;
|
|
||||||
using base_type = Matrix<value_type>;
|
|
||||||
using pointer = typename base_type::pointer;
|
|
||||||
using const_pointer = typename base_type::const_pointer;
|
|
||||||
using reference = typename base_type::reference;
|
|
||||||
using const_reference = typename base_type::const_reference;
|
|
||||||
using iterator = typename base_type::iterator;
|
|
||||||
using const_iterator = typename base_type::const_iterator;
|
|
||||||
using reverse_iterator = typename base_type::reverse_iterator;
|
|
||||||
using const_reverse_iterator = typename base_type::const_reverse_iterator;
|
|
||||||
using difference_type = typename base_type::difference_type;
|
|
||||||
using size_type = typename base_type::size_type;
|
|
||||||
|
|
||||||
PixelBuf(size_type r, size_type c)
|
|
||||||
: m_data(r, c)
|
|
||||||
{}
|
|
||||||
PixelBuf(size_type r, size_type c, RGBA color)
|
|
||||||
: m_data(r, c, color)
|
|
||||||
{}
|
|
||||||
|
|
||||||
PixelBuf(std::uint8_t const* in, size_type r, size_type c, PixelFormat format = PixelFormat::rgba)
|
|
||||||
: PixelBuf(r, c)
|
|
||||||
{
|
|
||||||
assert(in != nullptr);
|
|
||||||
|
|
||||||
switch (format) {
|
|
||||||
case PixelFormat::rgba: from_helper<PixelFormat::rgba>(in, data(), size()); break;
|
|
||||||
case PixelFormat::abgr: from_helper<PixelFormat::abgr>(in, data(), size()); break;
|
|
||||||
case PixelFormat::rgb: from_helper<PixelFormat::rgb >(in, data(), size()); break;
|
|
||||||
case PixelFormat::bgr: from_helper<PixelFormat::bgr >(in, data(), size()); break;
|
|
||||||
case PixelFormat::ga: from_helper<PixelFormat::ga >(in, data(), size()); break;
|
|
||||||
case PixelFormat::ag: from_helper<PixelFormat::ag >(in, data(), size()); break;
|
|
||||||
case PixelFormat::g: from_helper<PixelFormat::g >(in, data(), size()); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PixelBuf() noexcept = default;
|
|
||||||
PixelBuf(PixelBuf const&) = default;
|
|
||||||
PixelBuf(PixelBuf &&) noexcept = default;
|
|
||||||
PixelBuf& operator=(PixelBuf const&) = default;
|
|
||||||
PixelBuf& operator=(PixelBuf &&) noexcept = default;
|
|
||||||
~PixelBuf() = default;
|
|
||||||
|
|
||||||
constexpr auto size() const noexcept -> size_type { return m_data.size(); }
|
|
||||||
constexpr auto rows() const noexcept -> size_type { return m_data.rows(); }
|
|
||||||
constexpr auto cols() const noexcept -> size_type { return m_data.cols(); }
|
|
||||||
constexpr auto data() noexcept -> pointer { return m_data.data(); }
|
|
||||||
constexpr auto data() const noexcept -> const_pointer { return m_data.data(); }
|
|
||||||
auto to_raw_buf() noexcept -> std::uint8_t* { return reinterpret_cast<std::uint8_t*>(data()); }
|
|
||||||
auto to_raw_buf() const noexcept -> std::uint8_t const* { return reinterpret_cast<std::uint8_t const*>(data()); }
|
|
||||||
constexpr auto raw_buf_size() const noexcept { return size() * sizeof(RGBA); }
|
|
||||||
|
|
||||||
constexpr auto begin() noexcept -> iterator { return m_data.begin(); }
|
|
||||||
constexpr auto end() noexcept -> iterator { return m_data.end(); }
|
|
||||||
constexpr auto begin() const noexcept -> const_iterator { return m_data.begin(); }
|
|
||||||
constexpr auto end() const noexcept -> const_iterator { return m_data.end(); }
|
|
||||||
constexpr auto rbegin() noexcept -> reverse_iterator { return m_data.rbegin(); }
|
|
||||||
constexpr auto rend() noexcept -> reverse_iterator { return m_data.rend(); }
|
|
||||||
constexpr auto rbegin() const noexcept -> const_reverse_iterator { return m_data.rbegin(); }
|
|
||||||
constexpr auto rend() const noexcept -> const_reverse_iterator { return m_data.rend(); }
|
|
||||||
|
|
||||||
constexpr decltype(auto) operator[](size_type r) noexcept { return m_data[r]; }
|
|
||||||
constexpr decltype(auto) operator[](size_type r) const noexcept { return m_data[r]; }
|
|
||||||
constexpr auto operator()(size_type r, size_type c) noexcept -> reference { return m_data(r, c); }
|
|
||||||
constexpr auto operator()(size_type r, size_type c) const noexcept -> const_reference { return m_data(r, c); }
|
|
||||||
|
|
||||||
constexpr auto fill(RGBA color) noexcept -> void {
|
|
||||||
std::fill(begin(), end(), color);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr auto copy_to(std::uint8_t* out, PixelFormat format = PixelFormat::rgba) const noexcept {
|
|
||||||
assert(out != nullptr);
|
|
||||||
|
|
||||||
switch (format) {
|
|
||||||
case PixelFormat::rgba: copy_to_helper<PixelFormat::rgba>(data(), out, size());return;
|
|
||||||
case PixelFormat::abgr: copy_to_helper<PixelFormat::abgr>(data(), out, size());return;
|
|
||||||
case PixelFormat::rgb: copy_to_helper<PixelFormat::rgb >(data(), out, size());return;
|
|
||||||
case PixelFormat::bgr: copy_to_helper<PixelFormat::bgr >(data(), out, size());return;
|
|
||||||
case PixelFormat::ga: copy_to_helper<PixelFormat::ga >(data(), out, size());return;
|
|
||||||
case PixelFormat::ag: copy_to_helper<PixelFormat::ag >(data(), out, size());return;
|
|
||||||
case PixelFormat::g: copy_to_helper<PixelFormat::g >(data(), out, size());return;
|
|
||||||
}
|
|
||||||
assert(false && "unreachable");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
|
||||||
template <PixelFormat F>
|
|
||||||
constexpr auto copy_to_helper(const_pointer in, std::uint8_t* out, size_type size) const noexcept -> void {
|
|
||||||
constexpr auto channels = get_pixel_format_channel(F);
|
|
||||||
for (auto i = size_type{}; i < size; ++i) {
|
|
||||||
detail::parse_pixel_helper<F>(in[i], out + i * channels);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <PixelFormat F>
|
|
||||||
constexpr auto from_helper(std::uint8_t const* in, pointer out, size_type size) const noexcept -> void {
|
|
||||||
constexpr auto channels = get_pixel_format_channel(F);
|
|
||||||
for (auto i = size_type{}; i < size; ++i) {
|
|
||||||
out[i] = detail::parse_pixel_helper<F>(in + i * channels);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
base_type m_data;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace amt
|
|
||||||
|
|
||||||
#include <format>
|
|
||||||
namespace std {
|
|
||||||
template <>
|
|
||||||
struct formatter<amt::RGBA> {
|
|
||||||
constexpr auto parse(format_parse_context& ctx) {
|
|
||||||
return ctx.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto format(amt::RGBA const& color, auto& ctx) const {
|
|
||||||
return format_to(ctx.out(), "rgba({}, {}, {}, {})", color.r(), color.g(), color.b(), color.a());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct formatter<amt::HSLA> {
|
|
||||||
constexpr auto parse(format_parse_context& ctx) {
|
|
||||||
return ctx.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto format(amt::HSLA const& color, auto& ctx) const {
|
|
||||||
return format_to(ctx.out(), "hsla({:.1f}deg, {:.1f}%, {:.1f}%, {:.1f}%)", color.h(), color.s(), color.l(), color.a());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct formatter<amt::PixelBuf> {
|
|
||||||
bool hsla = false;
|
|
||||||
|
|
||||||
constexpr auto parse(format_parse_context& ctx) {
|
|
||||||
auto it = ctx.begin();
|
|
||||||
while (it != ctx.end() && *it != '}') {
|
|
||||||
if (*it == 'h') hsla = true;
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
return it;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto format(amt::PixelBuf const& buf, auto& ctx) const {
|
|
||||||
std::string s = "[\n";
|
|
||||||
for (auto r = std::size_t{}; r < buf.rows(); ++r) {
|
|
||||||
for (auto c = std::size_t{}; c < buf.cols(); ++c) {
|
|
||||||
auto color = buf(r, c);
|
|
||||||
if (hsla) s += std::format("{}, ", amt::HSLA(color));
|
|
||||||
else s += std::format("{}, ", color);
|
|
||||||
}
|
|
||||||
s += '\n';
|
|
||||||
}
|
|
||||||
s += "]";
|
|
||||||
return format_to(ctx.out(), "{}", s);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace std
|
|
||||||
|
|
||||||
#endif // AMT_PIXEL_HPP
|
|
||||||
|
|
@ -1,417 +0,0 @@
|
||||||
#include "amt/raycaster.hpp"
|
|
||||||
#include "amt/texture.hpp"
|
|
||||||
#include "amt/pixel.hpp"
|
|
||||||
#include "constants.hpp"
|
|
||||||
#include "thread.hpp"
|
|
||||||
|
|
||||||
#define AMT_LIGHT
|
|
||||||
|
|
||||||
using namespace fmt;
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef AMT_LIGHT
|
|
||||||
static constexpr auto room_brightness = 0.3f; // increse this to increase the room brightness. Higher value means brighter room.
|
|
||||||
|
|
||||||
inline static constexpr amt::RGBA dumb_lighting(amt::RGBA pixel, float distance, float distance_from_center) {
|
|
||||||
auto const dim_pixel = pixel * room_brightness;
|
|
||||||
if (distance_from_center >= 0) {
|
|
||||||
auto const min_brightness = 1.f / std::max(distance_from_center, 0.5f); // farther away from the center darker it gets
|
|
||||||
auto const max_brightness = 1.f; // brighness should not exceed 1
|
|
||||||
auto const pixel_brightness = std::max(min_brightness, std::min(max_brightness, distance));
|
|
||||||
|
|
||||||
auto const yellow_brightness = float(distance_from_center * 60);
|
|
||||||
amt::RGBA const yellow = amt::HSLA(40, 20, yellow_brightness);
|
|
||||||
|
|
||||||
auto temp = (pixel / pixel_brightness).blend<amt::BlendMode::softLight>(yellow);
|
|
||||||
return temp.brightness() < 0.1f ? dim_pixel : temp;
|
|
||||||
} else {
|
|
||||||
return dim_pixel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
inline static constexpr amt::RGBA dumb_lighting(amt::RGBA pixel, double distance, double distance_from_center) {
|
|
||||||
(void)distance_from_center;
|
|
||||||
if(distance < 0.9) return pixel;
|
|
||||||
return pixel / distance;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
Raycaster::Raycaster(sf::RenderWindow& window, Matrix &map, unsigned width, unsigned height) :
|
|
||||||
view_texture(sf::Vector2u{width, height}),
|
|
||||||
view_sprite(view_texture),
|
|
||||||
$width(static_cast<int>(width)),
|
|
||||||
$height(static_cast<int>(height)),
|
|
||||||
pixels(height, width),
|
|
||||||
$window(window),
|
|
||||||
$map(map),
|
|
||||||
spriteOrder(textures.NUM_SPRITES),
|
|
||||||
spriteDistance(textures.NUM_SPRITES),
|
|
||||||
ZBuffer(width),
|
|
||||||
$radius(std::min($height, $width) / 2),
|
|
||||||
$r_sq($radius * $radius)
|
|
||||||
{
|
|
||||||
$window.setVerticalSyncEnabled(VSYNC);
|
|
||||||
view_sprite.setPosition({0, 0});
|
|
||||||
textures.load_textures();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Raycaster::set_position(int x, int y) {
|
|
||||||
view_sprite.setPosition({(float)x, (float)y});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Raycaster::position_camera(float player_x, float player_y) {
|
|
||||||
// x and y start position
|
|
||||||
posX = player_x;
|
|
||||||
posY = player_y;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Raycaster::draw_pixel_buffer() {
|
|
||||||
view_texture.update(pixels.to_raw_buf(), {(unsigned int)$width, (unsigned int)$height}, {0, 0});
|
|
||||||
$window.draw(view_sprite);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Raycaster::clear() {
|
|
||||||
pixels.fill({});
|
|
||||||
$window.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Raycaster::sprite_casting() {
|
|
||||||
// sort sprites from far to close
|
|
||||||
for(int i = 0; i < textures.NUM_SPRITES; i++) {
|
|
||||||
auto& sprite = textures.get_sprite(i);
|
|
||||||
spriteOrder[i] = i;
|
|
||||||
// this is just the distance calculation
|
|
||||||
spriteDistance[i] = ((posX - sprite.x) *
|
|
||||||
(posX - sprite.x) +
|
|
||||||
(posY - sprite.y) *
|
|
||||||
(posY - sprite.y));
|
|
||||||
}
|
|
||||||
|
|
||||||
sort_sprites(spriteOrder, spriteDistance, textures.NUM_SPRITES);
|
|
||||||
|
|
||||||
/*for(int i = 0; i < textures.NUM_SPRITES; i++) {*/
|
|
||||||
// after sorting the sprites, do the projection
|
|
||||||
// Be careful about capturing stack variables.
|
|
||||||
amt::parallel_for<1>(pool, 0, textures.NUM_SPRITES, [this, textureWidth = textures.TEXTURE_WIDTH, textureHeight = textures.TEXTURE_HEIGHT](size_t i){
|
|
||||||
int sprite_index = spriteOrder[i];
|
|
||||||
Sprite& sprite_rec = textures.get_sprite(sprite_index);
|
|
||||||
auto& sprite_texture = textures.get_texture(sprite_rec.texture);
|
|
||||||
|
|
||||||
double spriteX = sprite_rec.x - posX;
|
|
||||||
double spriteY = sprite_rec.y - posY;
|
|
||||||
|
|
||||||
//transform sprite with the inverse camera matrix
|
|
||||||
// [ planeX dirX ] -1 [ dirY -dirX ]
|
|
||||||
// [ ] = 1/(planeX*dirY-dirX*planeY) * [ ]
|
|
||||||
// [ planeY dirY ] [ -planeY planeX ]
|
|
||||||
|
|
||||||
double invDet = 1.0 / (planeX * dirY - dirX * planeY); // required for correct matrix multiplication
|
|
||||||
|
|
||||||
double transformX = invDet * (dirY * spriteX - dirX * spriteY);
|
|
||||||
//this is actually the depth inside the screen, that what Z is in 3D, the distance of sprite to player, matching sqrt(spriteDistance[i])
|
|
||||||
|
|
||||||
double transformY = invDet * (-planeY * spriteX + planeX * spriteY);
|
|
||||||
|
|
||||||
int spriteScreenX = int(($width / 2) * (1 + transformX / transformY));
|
|
||||||
|
|
||||||
int vMoveScreen = int(sprite_rec.elevation * -1 / transformY);
|
|
||||||
|
|
||||||
// calculate the height of the sprite on screen
|
|
||||||
//using "transformY" instead of the real distance prevents fisheye
|
|
||||||
int spriteHeight = abs(int($height / transformY)) / sprite_rec.vDiv;
|
|
||||||
|
|
||||||
//calculate lowest and highest pixel to fill in current stripe
|
|
||||||
int drawStartY = -spriteHeight / 2 + $height / 2 + vMoveScreen;
|
|
||||||
if(drawStartY < 0) drawStartY = 0;
|
|
||||||
int drawEndY = spriteHeight / 2 + $height / 2 + vMoveScreen;
|
|
||||||
if(drawEndY >= $height) drawEndY = $height - 1;
|
|
||||||
|
|
||||||
// calculate width the the sprite
|
|
||||||
// same as height of sprite, given that it's square
|
|
||||||
int spriteWidth = abs(int($height / transformY)) / sprite_rec.uDiv;
|
|
||||||
int drawStartX = -spriteWidth / 2 + spriteScreenX;
|
|
||||||
if(drawStartX < 0) drawStartX = 0;
|
|
||||||
int drawEndX = spriteWidth / 2 + spriteScreenX;
|
|
||||||
if(drawEndX > $width) drawEndX = $width;
|
|
||||||
|
|
||||||
//loop through every vertical stripe of the sprite on screen
|
|
||||||
for(int stripe = drawStartX; stripe < drawEndX; stripe++) {
|
|
||||||
int texX = int(256 * (stripe - (-spriteWidth / 2 + spriteScreenX)) * textureWidth / spriteWidth) / 256;
|
|
||||||
// the conditions in the if are:
|
|
||||||
// 1) it's in front of the camera plane so you don't see things behind you
|
|
||||||
// 2) ZBuffer, with perpendicular distance
|
|
||||||
if (texX < 0) continue;
|
|
||||||
if(transformY > 0 && transformY < ZBuffer[stripe]) {
|
|
||||||
for(int y = drawStartY; y < drawEndY; y++) {
|
|
||||||
//256 and 128 factors to avoid floats
|
|
||||||
int d = (y - vMoveScreen) * 256 - $height * 128 + spriteHeight * 128;
|
|
||||||
int texY = ((d * textureHeight) / spriteHeight) / 256;
|
|
||||||
if ((size_t)texY >= sprite_texture.rows()) continue;
|
|
||||||
//get current color from the texture
|
|
||||||
auto color = sprite_texture[texY][texX];
|
|
||||||
// poor person's transparency, get current color from the texture
|
|
||||||
if (!(color.to_hex() & 0xffffff00)) continue;
|
|
||||||
auto dist = get_distance_from_center(stripe, y);
|
|
||||||
pixels[y][stripe] = dumb_lighting(color, d, dist);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
float Raycaster::get_distance_from_center(int x, int y) const noexcept {
|
|
||||||
float cx = $width / 2;
|
|
||||||
float cy = $height / 2;
|
|
||||||
auto dx = cx - x;
|
|
||||||
auto dy = cy - y;
|
|
||||||
return ($r_sq - dx * dx - dy * dy) / $r_sq;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Raycaster::cast_rays() {
|
|
||||||
|
|
||||||
// WALL CASTING
|
|
||||||
/*for(int x = 0; x < $width; x++) {*/
|
|
||||||
amt::parallel_for<32>(pool, 0, static_cast<std::size_t>($width), [this](size_t x){
|
|
||||||
double perpWallDist = 0;
|
|
||||||
// calculate ray position and direction
|
|
||||||
double cameraX = 2 * x / double($width) - 1; // x-coord in camera space
|
|
||||||
double rayDirX = dirX + planeX * cameraX;
|
|
||||||
double rayDirY = dirY + planeY * cameraX;
|
|
||||||
|
|
||||||
// which box of the map we're in
|
|
||||||
int mapX = int(posX);
|
|
||||||
int mapY = int(posY);
|
|
||||||
|
|
||||||
// length of ray from current pos to next x or y-side
|
|
||||||
double sideDistX;
|
|
||||||
double sideDistY;
|
|
||||||
|
|
||||||
// length of ray from one x or y-side to next x or y-side
|
|
||||||
double deltaDistX = std::abs(1.0 / rayDirX);
|
|
||||||
double deltaDistY = std::abs(1.0 / rayDirY);
|
|
||||||
|
|
||||||
int stepX = 0;
|
|
||||||
int stepY = 0;
|
|
||||||
int hit = 0;
|
|
||||||
int side = 0;
|
|
||||||
|
|
||||||
// calculate step and initial sideDist
|
|
||||||
if(rayDirX < 0) {
|
|
||||||
stepX = -1;
|
|
||||||
sideDistX = (posX - mapX) * deltaDistX;
|
|
||||||
} else {
|
|
||||||
stepX = 1;
|
|
||||||
sideDistX = (mapX + 1.0 - posX) * deltaDistX;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(rayDirY < 0) {
|
|
||||||
stepY = -1;
|
|
||||||
sideDistY = (posY - mapY) * deltaDistY;
|
|
||||||
} else {
|
|
||||||
stepY = 1;
|
|
||||||
sideDistY = (mapY + 1.0 - posY) * deltaDistY;
|
|
||||||
}
|
|
||||||
|
|
||||||
// perform DDA
|
|
||||||
while(hit == 0) {
|
|
||||||
if(sideDistX < sideDistY) {
|
|
||||||
sideDistX += deltaDistX;
|
|
||||||
mapX += stepX;
|
|
||||||
side = 0;
|
|
||||||
} else {
|
|
||||||
sideDistY += deltaDistY;
|
|
||||||
mapY += stepY;
|
|
||||||
side = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($map[mapY][mapX] > 0) hit = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(side == 0) {
|
|
||||||
perpWallDist = (sideDistX - deltaDistX);
|
|
||||||
} else {
|
|
||||||
perpWallDist = (sideDistY - deltaDistY);
|
|
||||||
}
|
|
||||||
|
|
||||||
int lineHeight = int($height / perpWallDist);
|
|
||||||
|
|
||||||
int drawStart = -lineHeight / 2 + $height / 2 + PITCH;
|
|
||||||
if(drawStart < 0) drawStart = 0;
|
|
||||||
|
|
||||||
int drawEnd = lineHeight / 2 + $height / 2 + PITCH;
|
|
||||||
if(drawEnd >= $height) drawEnd = $height - 1;
|
|
||||||
|
|
||||||
auto &texture = textures.get_texture($map[mapY][mapX] - 1);
|
|
||||||
|
|
||||||
// calculate value of wallX
|
|
||||||
double wallX; // where exactly the wall was hit
|
|
||||||
if(side == 0) {
|
|
||||||
wallX = posY + perpWallDist * rayDirY;
|
|
||||||
} else {
|
|
||||||
wallX = posX + perpWallDist * rayDirX;
|
|
||||||
}
|
|
||||||
wallX -= floor((wallX));
|
|
||||||
|
|
||||||
// x coorindate on the texture
|
|
||||||
int texX = int(wallX * double(textures.TEXTURE_WIDTH));
|
|
||||||
if(side == 0 && rayDirX > 0) texX = textures.TEXTURE_WIDTH - texX - 1;
|
|
||||||
if(side == 1 && rayDirY < 0) texX = textures.TEXTURE_WIDTH - texX - 1;
|
|
||||||
|
|
||||||
// LODE: an integer-only bresenham or DDA like algorithm could make the texture coordinate stepping faster
|
|
||||||
|
|
||||||
// How much to increase the texture coordinate per screen pixel
|
|
||||||
double step = 1.0 * textures.TEXTURE_HEIGHT / lineHeight;
|
|
||||||
// Starting texture coordinate
|
|
||||||
double texPos = (drawStart - PITCH - $height / 2 + lineHeight / 2) * step;
|
|
||||||
|
|
||||||
for(int y = drawStart; y < drawEnd; y++) {
|
|
||||||
int texY = (int)texPos & (textures.TEXTURE_HEIGHT - 1);
|
|
||||||
texPos += step;
|
|
||||||
auto dist = get_distance_from_center(x, y);
|
|
||||||
auto color = dumb_lighting(texture[texY][texX], perpWallDist, dist);
|
|
||||||
pixels[y][x] = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SET THE ZBUFFER FOR THE SPRITE CASTING
|
|
||||||
ZBuffer[x] = perpWallDist;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Raycaster::draw_ceiling_floor() {
|
|
||||||
|
|
||||||
/*for(int y = $height / 2 + 1; y < $height; ++y) {*/
|
|
||||||
|
|
||||||
auto const h = static_cast<size_t>($height);
|
|
||||||
amt::parallel_for<32>(pool, h / 2, h, [this, $height=h](size_t y){
|
|
||||||
const size_t textureWidth = textures.TEXTURE_WIDTH;
|
|
||||||
const size_t textureHeight = textures.TEXTURE_HEIGHT;
|
|
||||||
// rayDir for leftmost ray (x=0) and rightmost (x = w)
|
|
||||||
float rayDirX0 = dirX - planeX;
|
|
||||||
float rayDirY0 = dirY - planeY;
|
|
||||||
float rayDirX1 = dirX + planeX;
|
|
||||||
float rayDirY1 = dirY + planeY;
|
|
||||||
|
|
||||||
// current y position compared to the horizon
|
|
||||||
int p = y - $height / 2;
|
|
||||||
|
|
||||||
// vertical position of the camera
|
|
||||||
// 0.5 will the camera at the center horizon. For a
|
|
||||||
// different value you need a separate loop for ceiling
|
|
||||||
// and floor since they're no longer symmetrical.
|
|
||||||
float posZ = 0.5 * $height;
|
|
||||||
|
|
||||||
// horizontal distance from the camera to the floor for the current row
|
|
||||||
// 0.5 is the z position exactly in the middle between floor and ceiling
|
|
||||||
// See NOTE in Lode's code for more.
|
|
||||||
float rowDistance = posZ / p;
|
|
||||||
|
|
||||||
// calculate the real world step vector we have to add for each x (parallel to camera plane)
|
|
||||||
// adding step by step avoids multiplications with a wight in the inner loop
|
|
||||||
float floorStepX = rowDistance * (rayDirX1 - rayDirX0) / $width;
|
|
||||||
float floorStepY = rowDistance * (rayDirY1 - rayDirY0) / $width;
|
|
||||||
|
|
||||||
|
|
||||||
// real world coordinates of the leftmost column.
|
|
||||||
// This will be updated as we step to the right
|
|
||||||
float floorX = posX + rowDistance * rayDirX0;
|
|
||||||
float floorY = posY + rowDistance * rayDirY0;
|
|
||||||
|
|
||||||
for(int x = 0; x < $width; ++x) {
|
|
||||||
// the cell coord is simply taken from the int parts of
|
|
||||||
// floorX and floorY.
|
|
||||||
int cellX = int(floorX);
|
|
||||||
int cellY = int(floorY);
|
|
||||||
|
|
||||||
// get the texture coordinat from the fractional part
|
|
||||||
int tx = int(textureWidth * (floorX - cellX)) & (textureWidth - 1);
|
|
||||||
int ty = int(textureWidth * (floorY - cellY)) & (textureHeight - 1);
|
|
||||||
|
|
||||||
floorX += floorStepX;
|
|
||||||
floorY += floorStepY;
|
|
||||||
|
|
||||||
// now get the pixel from the texture
|
|
||||||
// this uses the previous ty/tx fractional parts of
|
|
||||||
// floorX cellX to find the texture x/y. How?
|
|
||||||
|
|
||||||
#ifdef AMT_LIGHT
|
|
||||||
// FLOOR
|
|
||||||
auto dist_floor = get_distance_from_center(x, y);
|
|
||||||
pixels[y][x] = dumb_lighting(textures.floor[ty][tx], p, dist_floor);
|
|
||||||
|
|
||||||
// CEILING
|
|
||||||
auto dist_ceiling = get_distance_from_center(x, $height - y - 1);
|
|
||||||
pixels[$height - y - 1][x] = dumb_lighting(textures.ceiling[ty][tx], p, dist_ceiling);
|
|
||||||
#else
|
|
||||||
// FLOOR
|
|
||||||
pixels[y][x] = textures.floor[ty][tx];
|
|
||||||
|
|
||||||
// CEILING
|
|
||||||
pixels[$height - y - 1][x] = textures.ceiling[ty][tx];
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Raycaster::render() {
|
|
||||||
draw_ceiling_floor();
|
|
||||||
// This wait to prevent data-race
|
|
||||||
pool.wait(); // Try to remove this to see unbelievable performance
|
|
||||||
cast_rays();
|
|
||||||
pool.wait(); // Try to remove this too
|
|
||||||
sprite_casting();
|
|
||||||
pool.wait();
|
|
||||||
draw_pixel_buffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Raycaster::empty_space(int new_x, int new_y) {
|
|
||||||
dbc::check((size_t)new_x < $map.cols(),
|
|
||||||
format("x={} too wide={}", new_x, $map.cols()));
|
|
||||||
dbc::check((size_t)new_y < $map.rows(),
|
|
||||||
format("y={} too high={}", new_y, $map.rows()));
|
|
||||||
|
|
||||||
return $map[new_y][new_x] == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Raycaster::sort_sprites(std::vector<int>& order, std::vector<double>& dist, int amount)
|
|
||||||
{
|
|
||||||
std::vector<std::pair<double, int>> sprites(amount);
|
|
||||||
|
|
||||||
for(int i = 0; i < amount; i++) {
|
|
||||||
sprites[i].first = dist[i];
|
|
||||||
sprites[i].second = order[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
std::sort(sprites.begin(), sprites.end());
|
|
||||||
|
|
||||||
// restore in reverse order
|
|
||||||
for(int i = 0; i < amount; i++) {
|
|
||||||
dist[i] = sprites[amount - i - 1].first;
|
|
||||||
order[i] = sprites[amount - i - 1].second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Raycaster::run(double speed, int dir) {
|
|
||||||
double speed_and_dir = speed * dir;
|
|
||||||
if(empty_space(int(posX + dirX * speed_and_dir), int(posY))) {
|
|
||||||
posX += dirX * speed_and_dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(empty_space(int(posX), int(posY + dirY * speed_and_dir))) {
|
|
||||||
posY += dirY * speed_and_dir;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Raycaster::rotate(double speed, int dir) {
|
|
||||||
double speed_and_dir = speed * dir;
|
|
||||||
double oldDirX = dirX;
|
|
||||||
dirX = dirX * cos(speed_and_dir) - dirY * sin(speed_and_dir);
|
|
||||||
dirY = oldDirX * sin(speed_and_dir) + dirY * cos(speed_and_dir);
|
|
||||||
|
|
||||||
double oldPlaneX = planeX;
|
|
||||||
planeX = planeX * cos(speed_and_dir) - planeY * sin(speed_and_dir);
|
|
||||||
planeY = oldPlaneX * sin(speed_and_dir) + planeY * cos(speed_and_dir);
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <fmt/core.h>
|
|
||||||
#include <SFML/Graphics.hpp>
|
|
||||||
#include <SFML/Graphics/Image.hpp>
|
|
||||||
#include <numbers>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
|
||||||
#include "matrix.hpp"
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <array>
|
|
||||||
#include "dbc.hpp"
|
|
||||||
#include "amt/pixel.hpp"
|
|
||||||
#include "amt/texture.hpp"
|
|
||||||
#include <memory>
|
|
||||||
#include "thread.hpp"
|
|
||||||
|
|
||||||
using Matrix = amt::Matrix<int>;
|
|
||||||
|
|
||||||
struct Raycaster {
|
|
||||||
int PITCH=0;
|
|
||||||
|
|
||||||
TexturePack textures;
|
|
||||||
double posX = 0;
|
|
||||||
double posY = 0;
|
|
||||||
|
|
||||||
// initial direction vector
|
|
||||||
double dirX = -1;
|
|
||||||
double dirY = 0;
|
|
||||||
|
|
||||||
// the 2d raycaster version of camera plane
|
|
||||||
double planeX = 0;
|
|
||||||
double planeY = 0.66;
|
|
||||||
sf::Texture view_texture;
|
|
||||||
sf::Sprite view_sprite;
|
|
||||||
|
|
||||||
//ZED: USE smart pointer for this
|
|
||||||
|
|
||||||
int $width;
|
|
||||||
int $height;
|
|
||||||
amt::PixelBuf pixels;
|
|
||||||
sf::RenderWindow& $window;
|
|
||||||
Matrix& $map;
|
|
||||||
std::vector<int> spriteOrder;
|
|
||||||
std::vector<double> spriteDistance;
|
|
||||||
std::vector<double> ZBuffer; // width
|
|
||||||
float $radius; // std::min($height, $width) / 2;
|
|
||||||
float $r_sq; // = radius * radius;
|
|
||||||
amt::thread_pool_t pool;
|
|
||||||
|
|
||||||
Raycaster(sf::RenderWindow& window, Matrix &map, unsigned width, unsigned height);
|
|
||||||
|
|
||||||
void draw_pixel_buffer();
|
|
||||||
void clear();
|
|
||||||
void cast_rays();
|
|
||||||
void draw_ceiling_floor();
|
|
||||||
void sprite_casting();
|
|
||||||
void sort_sprites(std::vector<int>& order, std::vector<double>& dist, int amount);
|
|
||||||
void render();
|
|
||||||
|
|
||||||
bool empty_space(int new_x, int new_y);
|
|
||||||
|
|
||||||
void run(double speed, int dir);
|
|
||||||
void rotate(double speed, int dir);
|
|
||||||
void position_camera(float player_x, float player_y);
|
|
||||||
float get_distance_from_center(int x, int y) const noexcept;
|
|
||||||
|
|
||||||
void set_position(int x, int y);
|
|
||||||
inline size_t pixcoord(int x, int y) {
|
|
||||||
return ((y) * $width) + (x);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
#include <SFML/Graphics/Image.hpp>
|
|
||||||
#include "dbc.hpp"
|
|
||||||
#include <fmt/core.h>
|
|
||||||
#include "config.hpp"
|
|
||||||
#include "amt/texture.hpp"
|
|
||||||
|
|
||||||
Image TexturePack::load_image(std::string filename) {
|
|
||||||
sf::Image img;
|
|
||||||
bool good = img.loadFromFile(filename);
|
|
||||||
dbc::check(good, format("failed to load {}", filename));
|
|
||||||
return amt::PixelBuf(img.getPixelsPtr(), TEXTURE_HEIGHT, TEXTURE_WIDTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TexturePack::load_textures() {
|
|
||||||
Config assets("assets/config.json");
|
|
||||||
for(string tile_path : assets["textures"]) {
|
|
||||||
images.emplace_back(load_image(tile_path));
|
|
||||||
}
|
|
||||||
|
|
||||||
for(string tile_path : assets["sprites"]) {
|
|
||||||
images.emplace_back(load_image(tile_path));
|
|
||||||
}
|
|
||||||
|
|
||||||
floor = load_image(assets["floor"]);
|
|
||||||
ceiling = load_image(assets["ceiling"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Image& TexturePack::get_texture(size_t num) {
|
|
||||||
return images[num];
|
|
||||||
}
|
|
||||||
|
|
||||||
Sprite &TexturePack::get_sprite(size_t sprite_num) {
|
|
||||||
return sprites[sprite_num];
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
#include "amt/pixel.hpp"
|
|
||||||
|
|
||||||
struct Sprite {
|
|
||||||
double x;
|
|
||||||
double y;
|
|
||||||
int texture;
|
|
||||||
// ZED: this should be a separate transform parameter
|
|
||||||
double elevation=0;
|
|
||||||
int uDiv=1;
|
|
||||||
int vDiv=1;
|
|
||||||
};
|
|
||||||
|
|
||||||
using Image = amt::PixelBuf;
|
|
||||||
|
|
||||||
struct TexturePack {
|
|
||||||
int NUM_SPRITES=1;
|
|
||||||
static const int TEXTURE_WIDTH=256; // must be power of two
|
|
||||||
static const int TEXTURE_HEIGHT=256; // must be power of two
|
|
||||||
|
|
||||||
std::vector<amt::PixelBuf> images;
|
|
||||||
std::vector<Sprite> sprites{{4.0, 3.55, 6}};
|
|
||||||
Image floor;
|
|
||||||
Image ceiling;
|
|
||||||
|
|
||||||
void load_textures();
|
|
||||||
amt::PixelBuf load_image(std::string filename);
|
|
||||||
Sprite& get_sprite(size_t sprite_num);
|
|
||||||
Image& get_texture(size_t num);
|
|
||||||
};
|
|
||||||
|
|
@ -1,253 +0,0 @@
|
||||||
#ifndef AMT_THREAD_HPP
|
|
||||||
#define AMT_THREAD_HPP
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <concepts>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <deque>
|
|
||||||
#include <mutex>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <thread>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <atomic>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace amt {
|
|
||||||
|
|
||||||
// NOTE: Could implement lock-free queue.
|
|
||||||
template <typename T>
|
|
||||||
struct Queue {
|
|
||||||
using base_type = std::deque<T>;
|
|
||||||
using value_type = typename base_type::value_type;
|
|
||||||
using pointer = typename base_type::pointer;
|
|
||||||
using const_pointer = typename base_type::const_pointer;
|
|
||||||
using reference = typename base_type::reference;
|
|
||||||
using const_reference = typename base_type::const_reference;
|
|
||||||
using iterator = typename base_type::iterator;
|
|
||||||
using const_iterator = typename base_type::const_iterator;
|
|
||||||
using reverse_iterator = typename base_type::reverse_iterator;
|
|
||||||
using const_reverse_iterator = typename base_type::const_reverse_iterator;
|
|
||||||
using difference_type = typename base_type::difference_type;
|
|
||||||
using size_type = typename base_type::size_type;
|
|
||||||
|
|
||||||
constexpr Queue() noexcept = default;
|
|
||||||
constexpr Queue(Queue const&) noexcept = delete;
|
|
||||||
constexpr Queue(Queue &&) noexcept = default;
|
|
||||||
constexpr Queue& operator=(Queue const&) noexcept = delete;
|
|
||||||
constexpr Queue& operator=(Queue &&) noexcept = default;
|
|
||||||
constexpr ~Queue() noexcept = default;
|
|
||||||
|
|
||||||
template <typename U>
|
|
||||||
requires std::same_as<std::decay_t<U>, value_type>
|
|
||||||
void push(U&& u) {
|
|
||||||
std::lock_guard m(m_mutex);
|
|
||||||
m_data.push_back(std::forward<U>(u));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
void emplace(Args&&... args) {
|
|
||||||
std::lock_guard m(m_mutex);
|
|
||||||
m_data.emplace_back(std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<value_type> pop() {
|
|
||||||
std::lock_guard m(m_mutex);
|
|
||||||
if (empty_unsafe()) return std::nullopt;
|
|
||||||
auto el = std::move(m_data.front());
|
|
||||||
m_data.pop_front();
|
|
||||||
return std::move(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto size() const noexcept -> size_type {
|
|
||||||
std::lock_guard m(m_mutex);
|
|
||||||
return m_data.size();
|
|
||||||
}
|
|
||||||
auto empty() const noexcept -> bool {
|
|
||||||
std::lock_guard m(m_mutex);
|
|
||||||
return m_data.empty();
|
|
||||||
}
|
|
||||||
constexpr auto size_unsafe() const noexcept -> size_type { return m_data.size(); }
|
|
||||||
constexpr auto empty_unsafe() const noexcept -> bool { return m_data.empty(); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
base_type m_data;
|
|
||||||
mutable std::mutex m_mutex;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Fn>
|
|
||||||
struct ThreadPool;
|
|
||||||
|
|
||||||
template <typename Fn>
|
|
||||||
struct Worker {
|
|
||||||
using parent_t = ThreadPool<Fn>*;
|
|
||||||
using work_t = Fn;
|
|
||||||
using size_type = std::size_t;
|
|
||||||
constexpr Worker() noexcept = default;
|
|
||||||
constexpr Worker(Worker const&) noexcept = default;
|
|
||||||
constexpr Worker(Worker &&) noexcept = default;
|
|
||||||
constexpr Worker& operator=(Worker const&) noexcept = default;
|
|
||||||
constexpr Worker& operator=(Worker &&) noexcept = default;
|
|
||||||
~Worker() {
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void start(parent_t pool, size_type id) {
|
|
||||||
assert((m_running.load(std::memory_order::acquire) == false) && "Thread is already running");
|
|
||||||
m_running.store(true);
|
|
||||||
m_parent.store(pool);
|
|
||||||
m_id = id;
|
|
||||||
m_thread = std::thread([this]() {
|
|
||||||
while (m_running.load(std::memory_order::relaxed)) {
|
|
||||||
std::unique_lock lk(m_mutex);
|
|
||||||
m_cv.wait(lk, [this] {
|
|
||||||
return !m_queue.empty_unsafe() || !m_running.load(std::memory_order::relaxed);
|
|
||||||
});
|
|
||||||
auto item = pop_task();
|
|
||||||
if (!item) {
|
|
||||||
item = try_steal();
|
|
||||||
if (!item) continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
process_work(std::move(*item));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void process_work(work_t&& work) const noexcept {
|
|
||||||
std::invoke(std::move(work));
|
|
||||||
auto ptr = m_parent.load();
|
|
||||||
if (ptr) ptr->task_completed();
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop() {
|
|
||||||
if (!m_running.load()) return;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(m_mutex);
|
|
||||||
m_running.store(false);
|
|
||||||
}
|
|
||||||
m_cv.notify_all();
|
|
||||||
m_thread.join();
|
|
||||||
m_parent.store(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void add(work_t&& work) {
|
|
||||||
std::lock_guard<std::mutex> lock(m_mutex);
|
|
||||||
m_queue.push(std::move(work));
|
|
||||||
m_cv.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<work_t> pop_task() noexcept {
|
|
||||||
return m_queue.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
std::optional<work_t> try_steal() noexcept {
|
|
||||||
auto ptr = m_parent.load();
|
|
||||||
if (ptr) return ptr->try_steal(m_id);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool empty() const noexcept { return m_queue.empty_unsafe(); }
|
|
||||||
constexpr size_type size() const noexcept { return m_queue.size_unsafe(); }
|
|
||||||
constexpr size_type id() const noexcept { return m_id; }
|
|
||||||
constexpr bool running() const noexcept { return m_running.load(std::memory_order::relaxed); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
Queue<work_t> m_queue{};
|
|
||||||
std::thread m_thread;
|
|
||||||
std::atomic<bool> m_running{false};
|
|
||||||
std::mutex m_mutex{};
|
|
||||||
std::condition_variable m_cv{};
|
|
||||||
std::atomic<parent_t> m_parent{nullptr};
|
|
||||||
size_type m_id;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Fn>
|
|
||||||
struct ThreadPool {
|
|
||||||
using worker_t = Worker<Fn>;
|
|
||||||
using work_t = typename worker_t::work_t;
|
|
||||||
using size_type = std::size_t;
|
|
||||||
|
|
||||||
constexpr ThreadPool(ThreadPool const&) noexcept = delete;
|
|
||||||
constexpr ThreadPool(ThreadPool &&) noexcept = default;
|
|
||||||
constexpr ThreadPool& operator=(ThreadPool const&) noexcept = delete;
|
|
||||||
constexpr ThreadPool& operator=(ThreadPool &&) noexcept = default;
|
|
||||||
~ThreadPool() {
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadPool(size_type n = std::thread::hardware_concurrency())
|
|
||||||
: m_workers(std::max(n, size_type{1}))
|
|
||||||
{
|
|
||||||
for (auto i = 0ul; i < m_workers.size(); ++i) {
|
|
||||||
m_workers[i].start(this, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop() {
|
|
||||||
for (auto& w: m_workers) w.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void add(Fn&& work) {
|
|
||||||
m_active_tasks.fetch_add(1, std::memory_order::relaxed);
|
|
||||||
m_workers[m_last_added].add(std::move(work));
|
|
||||||
m_last_added = (m_last_added + 1) % m_workers.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<work_t> try_steal(size_type id) {
|
|
||||||
for (auto& w: m_workers) {
|
|
||||||
if (w.id() == id) continue;
|
|
||||||
auto item = w.pop_task();
|
|
||||||
if (item) return item;
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
void task_completed() {
|
|
||||||
if (m_active_tasks.fetch_sub(1, std::memory_order::release) == 1) {
|
|
||||||
m_wait_cv.notify_all();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void wait() {
|
|
||||||
std::unique_lock lock(m_wait_mutex);
|
|
||||||
m_wait_cv.wait(lock, [this] {
|
|
||||||
return m_active_tasks.load(std::memory_order::acquire) == 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<worker_t> m_workers;
|
|
||||||
size_type m_last_added{};
|
|
||||||
std::mutex m_wait_mutex;
|
|
||||||
std::condition_variable m_wait_cv;
|
|
||||||
std::atomic<size_t> m_active_tasks{0};
|
|
||||||
};
|
|
||||||
|
|
||||||
using thread_pool_t = ThreadPool<std::function<void()>>;
|
|
||||||
|
|
||||||
// WARNING: Do not capture the stack variable if you're defering wait on pool.
|
|
||||||
// If you want to capture them, either capture them value or do "pool.wait()" at the end of the scope.
|
|
||||||
template <std::size_t Split, typename Fn>
|
|
||||||
requires (std::is_invocable_v<Fn, std::size_t>)
|
|
||||||
constexpr auto parallel_for(thread_pool_t& pool, std::size_t start, std::size_t end, Fn&& body) noexcept {
|
|
||||||
if (start >= end) return;
|
|
||||||
|
|
||||||
auto const size = (end - start);
|
|
||||||
auto const chunk_size = std::max(size_t{1}, (size + Split - 1) / Split);
|
|
||||||
auto const num_chunks = (size + chunk_size - 1) / chunk_size;
|
|
||||||
|
|
||||||
for (auto chunk = 0ul; chunk < num_chunks; ++chunk) {
|
|
||||||
auto const chunk_start = std::min(start + (chunk * chunk_size), end);
|
|
||||||
auto const chunk_end = std::min(chunk_start + (chunk_size), end);
|
|
||||||
pool.add([chunk_start, chunk_end, body] {
|
|
||||||
for (auto i = chunk_start; i < chunk_end; ++i) {
|
|
||||||
std::invoke(body, i);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // nsmespace amt
|
|
||||||
|
|
||||||
#endif // AMT_THREAD_HPP
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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 */
|
|
||||||
|
|
@ -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
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
1
scripts/animation_thing.ps1
Normal file
1
scripts/animation_thing.ps1
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
magick montage -tile 3x1 -geometry +0+0 -background transparent .\assets\animations\torch_fixture_*.png assets/fixtures/torch_fixture.png
|
||||||
Loading…
Add table
Add a link
Reference in a new issue