First cut at a replica of the python raycaster. Left side almost works the same but have to sort out math differences.

master
Zed A. Shaw 4 days ago
parent 6b181382bd
commit ca80736d7c
  1. 40
      dbc.cpp
  2. 29
      dbc.hpp
  3. 34
      main.cpp
  4. 94
      matrix.cpp
  5. 305
      matrix.hpp
  6. 34
      meson.build
  7. 19
      point.hpp
  8. 126
      pycaster.py
  9. 102
      raycaster.cpp
  10. 9
      scratchpad/quickcg.cpp
  11. 0
      scratchpad/quickcg.h
  12. 80
      scratchpad/raycaster_flat.cpp
  13. 471
      scratchpad/raycaster_sprites.cpp
  14. 293
      scratchpad/raycaster_textured.cpp
  15. 0
      scratchpad/sdlprog.cpp
  16. 553
      scratchpad/timcaster.cpp
  17. 13
      wraps/flac.wrap
  18. 13
      wraps/ogg.wrap
  19. 13
      wraps/openal-soft.wrap
  20. 13
      wraps/sfml.wrap
  21. 14
      wraps/vorbis.wrap

@ -0,0 +1,40 @@
#include "dbc.hpp"
void dbc::log(const string &message) {
fmt::print("{}\n", message);
}
void dbc::sentinel(const string &message) {
string err = fmt::format("[SENTINEL!] {}\n", message);
throw dbc::SentinelError{err};
}
void dbc::pre(const string &message, bool test) {
if(!test) {
string err = fmt::format("[PRE!] {}\n", message);
throw dbc::PreCondError{err};
}
}
void dbc::pre(const string &message, std::function<bool()> tester) {
dbc::pre(message, tester());
}
void dbc::post(const string &message, bool test) {
if(!test) {
string err = fmt::format("[POST!] {}\n", message);
throw dbc::PostCondError{err};
}
}
void dbc::post(const string &message, std::function<bool()> tester) {
dbc::post(message, tester());
}
void dbc::check(bool test, const string &message) {
if(!test) {
string err = fmt::format("[CHECK!] {}\n", message);
fmt::println("{}", err);
throw dbc::CheckError{err};
}
}

@ -0,0 +1,29 @@
#pragma once
#include <string>
#include <fmt/core.h>
#include <functional>
using std::string;
namespace dbc {
class Error {
public:
const string message;
Error(string m) : message{m} {}
Error(const char *m) : message{m} {}
};
class CheckError : public Error {};
class SentinelError : public Error {};
class PreCondError : public Error {};
class PostCondError : public Error {};
void log(const string &message);
void sentinel(const string &message);
void pre(const string &message, bool test);
void pre(const string &message, std::function<bool()> tester);
void post(const string &message, bool test);
void post(const string &message, std::function<bool()> tester);
void check(bool test, const string &message);
}

@ -1,34 +0,0 @@
/*
Copyright (c) 2004, Lode Vandevenne
All rights reserved.
*/
#include <cmath>
#include <string>
#include <vector>
#include <iostream>
#include "quickcg.h"
using namespace QuickCG;
using namespace std;
//place the example code below here:
int main(int argc, char *argv[])
{
screen(256, 256, 0, "Small Test Script");
for (int x = 0; x < w; x++)
for (int y = 0; y < h; y++)
{
pset(x, y, ColorRGBA(x, y, 128, 255));
}
print("Hello, world!", 8, 8);
std::string test;
test.resize(20);
redraw();
sleep();
return 0;
}

@ -0,0 +1,94 @@
#include "matrix.hpp"
#include "dbc.hpp"
#include <fmt/core.h>
#include <cmath>
#include <cstdlib>
using namespace fmt;
using std::min, std::max;
namespace matrix {
flood::flood(Matrix &mat, Point start, int old_val, int new_val) :
mat(mat), start(start), old_val(old_val), new_val(new_val),
x(start.x), y(start.y), dirs{mat, start.x, start.y}
{
dbc::check(old_val != new_val, "what you doing?");
current_loc = start;
q.push(start);
}
bool flood::next() {
if(!q.empty()) {
if(!dirs.next()) {
// box is done reset it
auto current_loc = q.front();
q.pop();
dirs = matrix::compass{mat, current_loc.x, current_loc.y};
dirs.next();
}
// get the next thing
if(mat[dirs.y][dirs.x] <= old_val) {
mat[dirs.y][dirs.x] = new_val;
x = dirs.x;
y = dirs.y;
q.push({.x=dirs.x, .y=dirs.y});
}
return true;
} else {
return false;
}
}
line::line(Point start, Point end) :
x(start.x), y(start.y),
x1(end.x), y1(end.y)
{
dx = std::abs(x1 - x);
sx = x < x1 ? 1 : -1;
dy = std::abs(y1 - y) * -1;
sy = y < y1 ? 1 : -1;
error = dx + dy;
}
bool line::next() {
if(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;
}
return true;
} else {
return false;
}
}
void dump(const std::string &msg, Matrix &map, int show_x, int show_y) {
println("----------------- {}", msg);
for(each_row it{map}; it.next();) {
int cell = map[it.y][it.x];
if(int(it.x) == show_x && int(it.y) == show_y) {
print("{:x}<", cell);
} else if(cell > 15) {
print("* ");
} else {
print("{:x} ", cell);
}
if(it.row) print("\n");
}
}
}

@ -0,0 +1,305 @@
#pragma once
#include <vector>
#include <queue>
#include <string>
#include <array>
#include <fmt/core.h>
#include "point.hpp"
namespace matrix {
using std::vector, std::queue, std::array;
using std::min, std::max, std::floor;
typedef vector<int> Row;
typedef vector<Row> Matrix;
/*
* Just a quick thing to reset a matrix to a value.
*/
template<typename MAT, typename VAL>
inline void assign(MAT &out, VAL new_value) {
for(auto &row : out) {
row.assign(row.size(), new_value);
}
}
template<typename MAT>
inline bool inbounds(MAT &mat, size_t x, size_t y) {
// since Point.x and Point.y are size_t any negatives are massive
bool res = (y < mat.size()) && (x < mat[0].size());
return res;
}
template<typename MAT>
inline size_t width(MAT &mat) {
return mat[0].size();
}
template<typename MAT>
inline size_t height(MAT &mat) {
return mat.size();
}
inline size_t next_x(size_t x, size_t width) {
return (x + 1) * ((x + 1) < width);
}
inline size_t next_y(size_t x, size_t y) {
return y + (x == 0);
}
inline bool at_end(size_t y, size_t height) {
return y < height;
}
inline bool end_row(size_t x, size_t width) {
return x == width - 1;
}
void dump(const std::string &msg, Matrix &map, int show_x=-1, int show_y=-1);
template<typename MAT>
struct each_cell_t {
size_t x = ~0;
size_t y = ~0;
size_t width = 0;
size_t height = 0;
each_cell_t(MAT &mat)
{
height = matrix::height(mat);
width = matrix::width(mat);
}
bool next() {
x = next_x(x, width);
y = next_y(x, y);
return at_end(y, height);
}
};
template<typename MAT>
struct viewport_t {
Point start;
// this is the point in the map
size_t x;
size_t y;
// this is the point inside the box, start at 0
size_t view_x = ~0;
size_t view_y = ~0;
// viewport width/height
size_t width;
size_t height;
viewport_t(MAT &mat, Point start, int max_x, int max_y) :
start(start),
x(start.x-1),
y(start.y-1)
{
width = std::min(size_t(max_x), matrix::width(mat) - start.x);
height = std::min(size_t(max_y), matrix::height(mat) - start.y);
fmt::println("viewport_t max_x, max_y {},{} vs matrix {},{}, x={}, y={}",
max_x, max_y, matrix::width(mat), matrix::height(mat), x, y);
}
bool next() {
y = next_y(x, y);
x = next_x(x, width);
view_x = next_x(view_x, width);
view_y = next_y(view_x, view_y);
return at_end(y, height);
}
};
using viewport = viewport_t<Matrix>;
using each_cell = each_cell_t<Matrix>;
template<typename MAT>
struct each_row_t {
size_t x = ~0;
size_t y = ~0;
size_t width = 0;
size_t height = 0;
bool row = false;
each_row_t(MAT &mat) {
height = matrix::height(mat);
width = matrix::width(mat);
}
bool next() {
x = next_x(x, width);
y = next_y(x, y);
row = end_row(x, width);
return at_end(y, height);
}
};
using each_row = each_row_t<Matrix>;
template<typename MAT>
struct box_t {
size_t from_x;
size_t from_y;
size_t x = 0; // these are set in constructor
size_t y = 0; // again, no fancy ~ trick needed
size_t left = 0;
size_t top = 0;
size_t right = 0;
size_t bottom = 0;
box_t(MAT &mat, size_t at_x, size_t at_y, size_t size) :
from_x(at_x), from_y(at_y)
{
size_t h = matrix::height(mat);
size_t w = matrix::width(mat);
// keeps it from going below zero
// need extra -1 to compensate for the first next()
left = max(from_x, size) - size;
x = left - 1; // must be -1 for next()
// keeps it from going above width
right = min(from_x + size + 1, w);
// same for these two
top = max(from_y, size) - size;
y = top - (left == 0);
bottom = min(from_y + size + 1, h);
}
bool next() {
// calc next but allow to go to 0 for next
x = next_x(x, right);
// x will go to 0, which signals new line
y = next_y(x, y); // this must go here
// if x==0 then this moves it to min_x
x = max(x, left);
// and done
return at_end(y, bottom);
}
float distance() {
int dx = from_x - x;
int dy = from_y - y;
return sqrt((dx * dx) + (dy * dy));
}
};
using box = box_t<Matrix>;
template<typename MAT>
struct compass_t {
size_t x = 0; // these are set in constructor
size_t y = 0; // again, no fancy ~ trick needed
array<int, 4> x_dirs{0, 1, 0, -1};
array<int, 4> y_dirs{-1, 0, 1, 0};
size_t max_dirs=0;
size_t dir = ~0;
compass_t(MAT &mat, size_t x, size_t y) :
x(x), y(y)
{
array<int, 4> x_in{0, 1, 0, -1};
array<int, 4> y_in{-1, 0, 1, 0};
for(size_t i = 0; i < 4; i++) {
int nx = x + x_in[i];
int ny = y + y_in[i];
if(matrix::inbounds(mat, nx, ny)) {
x_dirs[max_dirs] = nx;
y_dirs[max_dirs] = ny;
max_dirs++;
}
}
}
bool next() {
dir++;
if(dir < max_dirs) {
x = x_dirs[dir];
y = y_dirs[dir];
return true;
} else {
return false;
}
}
};
using compass = compass_t<Matrix>;
struct flood {
Matrix &mat;
Point start;
int old_val;
int new_val;
queue<Point> q;
Point current_loc;
int x;
int y;
matrix::compass dirs;
flood(Matrix &mat, Point start, int old_val, int new_val);
bool next();
bool next_working();
};
struct line {
int x;
int y;
int x1;
int y1;
int sx;
int sy;
int dx;
int dy;
int error;
line(Point start, Point end);
bool next();
};
template<typename MAT>
struct circle_t {
float center_x;
float center_y;
float radius = 0.0f;
int y = 0;
int dx = 0;
int dy = 0;
int left = 0;
int right = 0;
int top = 0;
int bottom = 0;
int width = 0;
int height = 0;
circle_t(MAT &mat, Point center, float radius) :
center_x(center.x), center_y(center.y), radius(radius)
{
width = matrix::width(mat);
height = matrix::height(mat);
top = max(int(floor(center_y - radius)), 0);
bottom = min(int(floor(center_y + radius)), height - 1);
y = top;
}
bool next() {
y++;
if(y <= bottom) {
dy = y - center_y;
dx = floor(sqrt(radius * radius - dy * dy));
left = max(0, int(center_x) - dx);
right = min(width, int(center_x) + dx + 1);
return true;
} else {
return false;
}
}
};
using circle = circle_t<Matrix>;
}

@ -1,27 +1,25 @@
project('lodecaster', 'cpp',
default_options: ['cpp_std=c++20'])
project('raycaster', 'cpp',
default_options: ['cpp_std=c++20'])
catch2 = dependency('catch2-with-main')
fmt = dependency('fmt')
json = dependency('nlohmann_json')
sdl2 = dependency('sdl2')
sdl2_main = dependency('sdl2main')
sfml = dependency('sfml')
dependencies = [
sdl2, sdl2_main,
fmt, json
]
fmt, json, sfml
]
executable('runtests', [
'quickcg.cpp',
'main.cpp',
],
win_subsystem: 'windows',
dependencies: dependencies)
'dbc.cpp',
'matrix.cpp',
'tests/base.cpp',
],
dependencies: dependencies + [catch2])
executable('lodecaster', [
'quickcg.cpp',
'raycaster_flat.cpp',
],
win_subsystem: 'windows',
dependencies: dependencies)
executable('raycaster', [
'dbc.cpp',
'matrix.cpp',
'raycaster.cpp',
],
dependencies: dependencies)

@ -0,0 +1,19 @@
#pragma once
#include <vector>
struct Point {
size_t x = 0;
size_t y = 0;
bool operator==(const Point& other) const {
return other.x == x && other.y == y;
}
};
typedef std::vector<Point> PointList;
struct PointHash {
size_t operator()(const Point& p) const {
return std::hash<int>()(p.x) ^ std::hash<int>()(p.y);
}
};

@ -0,0 +1,126 @@
import pygame
import sys
import math
SCREEN_HEIGHT=480
SCREEN_WIDTH=SCREEN_HEIGHT * 2
MAP_SIZE=8
TILE_SIZE=int((SCREEN_WIDTH / 2) / MAP_SIZE)
FOV=math.pi / 3
HALF_FOV = FOV / 2
CASTED_RAYS=30
STEP_ANGLE = FOV / CASTED_RAYS
MAX_DEPTH = int(MAP_SIZE * TILE_SIZE)
SCALE = (SCREEN_WIDTH / 2) / CASTED_RAYS
player_x = (SCREEN_WIDTH/2)/2
player_y = (SCREEN_WIDTH/2)/2
player_angle = math.pi
MAP = ('########'
'# # #'
'# # ###'
'# #'
'## #'
'# ### #'
'# # #'
'########')
pygame.init()
win = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Ray-Casting")
clock = pygame.time.Clock()
def draw_map():
light_grey = (191, 191, 191)
dark_grey = (65,65,65)
for i in range(MAP_SIZE):
for j in range(MAP_SIZE):
square = i * MAP_SIZE + j
pygame.draw.rect(win,
light_grey if MAP[square] == '#' else dark_grey,
(j * TILE_SIZE, i * TILE_SIZE, TILE_SIZE -1, TILE_SIZE - 1))
def ray_casting():
# left angle of FOV
start_angle = player_angle - HALF_FOV
for ray in range(CASTED_RAYS):
for depth in range(1,MAX_DEPTH):
target_x = player_x - math.sin(start_angle) * depth
target_y = player_y + math.cos(start_angle) * depth
col = int(target_x / TILE_SIZE)
row = int(target_y / TILE_SIZE)
square = row * MAP_SIZE + col
if MAP[square] == '#':
pygame.draw.rect(win,
(195, 137, 38),
(col * TILE_SIZE,
row * TILE_SIZE,
TILE_SIZE -1, TILE_SIZE-1))
pygame.draw.line(win, (233, 166, 49),
(player_x, player_y),
(target_x, target_y))
# wall shading
color = 255 / (1 + depth * depth * 0.0001)
# fix fish eye effect
depth *= math.cos(player_angle - start_angle)
# calculate wall height
wall_height = 21000 / (depth)
if wall_height > SCREEN_HEIGHT:
wall_height = SCREEN_HEIGHT
pygame.draw.rect(win,
(color, color, color),
(SCREEN_HEIGHT + ray * SCALE,
(SCREEN_HEIGHT / 2) - wall_height/2,
SCALE, wall_height))
break
start_angle += STEP_ANGLE
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit(0)
# update 2d background
pygame.draw.rect(win, (0,0,0), (0, 0, SCREEN_HEIGHT, SCREEN_HEIGHT))
# update 3d background
pygame.draw.rect(win, (100, 100, 100), (480, SCREEN_HEIGHT / 2, SCREEN_HEIGHT, SCREEN_HEIGHT))
pygame.draw.rect(win, (200, 200, 200), (480, -SCREEN_HEIGHT / 2, SCREEN_HEIGHT, SCREEN_HEIGHT))
draw_map()
ray_casting()
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
# working with radians, not degrees
player_angle -= 0.1
elif keys[pygame.K_RIGHT]:
player_angle += 0.1
elif keys[pygame.K_UP]:
forward = True
player_x += -1 * math.sin(player_angle) * 5
player_y += math.cos(player_angle) * 5
elif keys[pygame.K_DOWN]:
forward = False
player_x -= -1 * math.sin(player_angle) * 5
player_y -= math.cos(player_angle) * 5
# update the display
pygame.display.flip()
clock.tick(30)

@ -0,0 +1,102 @@
#include <fmt/core.h>
#include <SFML/Graphics.hpp>
#include <numbers>
#include <cmath>
#include "matrix.hpp"
using matrix::Matrix;
using namespace fmt;
const int SCREEN_HEIGHT=480;
const int SCREEN_WIDTH=SCREEN_HEIGHT * 2;
const int MAP_SIZE=8;
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;
Matrix MAP{
{1,1,1,1,1,1,1,1},
{1,0,1,0,0,0,0,1},
{1,0,1,0,0,1,1,1},
{1,0,0,0,0,0,0,1},
{1,1,0,0,0,0,0,1},
{1,0,0,1,1,1,0,1},
{1,0,0,0,1,0,0,1},
{1,1,1,1,1,1,1,1}
};
float player_x = SCREEN_WIDTH / 4;
float player_y = SCREEN_WIDTH / 4;
float player_angle = std::numbers::pi;
void draw_map_rect(sf::RenderWindow &window, sf::Color color, int x, int y) {
sf::RectangleShape rect({TILE_SIZE-1, TILE_SIZE-1});
rect.setFillColor(color);
rect.setPosition(x * TILE_SIZE, y * TILE_SIZE);
window.draw(rect);
}
void draw_map(sf::RenderWindow &window, Matrix &map) {
sf::Color light_grey{191, 191, 191};
sf::Color dark_grey{65,65,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, map[y][x] == 1 ? light_grey : dark_grey, x, y);
}
}
}
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 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_rect(window, {195, 137, 38}, col & TILE_SIZE, row * TILE_SIZE);
draw_line(window, {player_x, player_y}, {target_x, target_y});
break;
}
}
}
}
int main() {
sf::RenderWindow window(sf::VideoMode(SCREEN_WIDTH, SCREEN_HEIGHT), "Raycaster");
while(window.isOpen()) {
sf::Event event;
draw_map(window, MAP);
ray_casting(window, MAP);
window.display();
while(window.pollEvent(event)) {
if(event.type == sf::Event::Closed) {
window.close();
}
}
}
return 0;
}

@ -46,6 +46,7 @@ QuickCG can handle some things that standard C++ doesn't but that are commonly u
#include <map>
#include <iostream>
#include <fstream>
#include <cassert>
namespace QuickCG
{
@ -65,7 +66,7 @@ namespace QuickCG
SDL_Renderer* render;
SDL_Texture* tex;
SDL_PixelFormat *fmt;
const Uint8* inkeys;
const Uint8* inkeys = NULL;
SDL_Event event = { 0 };
@ -76,7 +77,9 @@ namespace QuickCG
bool keyDown(int key) //this checks if the key is held down, returns true all the time until the key is up
{
return (inkeys[key] != 0);
assert(inkeys != NULL && "inkeys is not initialized!");
// return (inkeys[key] != 0);
return false;
}
bool keyPressed(int key) //this checks if the key is *just* pressed, returns true only once until the key is up again
@ -171,6 +174,8 @@ namespace QuickCG
SDL_Quit();
std::exit(1);
}
inkeys = SDL_GetKeyboardState(NULL);
}
//Locks the screen

@ -79,6 +79,7 @@ int main(int /*argc*/, char */*argv*/[])
double oldTime = 0; //time of previous frame
screen(screenWidth, screenHeight, 0, "Raycaster");
while(!done())
{
for(int x = 0; x < w; x++)
@ -176,7 +177,7 @@ int main(int /*argc*/, char */*argv*/[])
if(drawEnd >= h) drawEnd = h - 1;
//choose wall color
ColorRGB color;
ColorRGBA color;
switch(worldMap[mapX][mapY])
{
case 1: color = RGB_Red; break; //red
@ -198,45 +199,52 @@ int main(int /*argc*/, char */*argv*/[])
double frameTime = (time - oldTime) / 1000.0; //frameTime is the time this frame has taken, in seconds
print(1.0 / frameTime); //FPS counter
redraw();
cls();
//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
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);
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;
}

@ -0,0 +1,471 @@
/*
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
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);
}
}
}
}
//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;
}
}

@ -0,0 +1,293 @@
/*
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 0
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, "pics/eagle.png");
loadImage(texture[1], tw, th, "pics/redbrick.png");
loadImage(texture[2], tw, th, "pics/purplestone.png");
loadImage(texture[3], tw, th, "pics/greystone.png");
loadImage(texture[4], tw, th, "pics/bluestone.png");
loadImage(texture[5], tw, th, "pics/mossy.png");
loadImage(texture[6], tw, th, "pics/wood.png");
loadImage(texture[7], tw, th, "pics/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;
}

@ -0,0 +1,553 @@
#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;
}

@ -0,0 +1,13 @@
[wrap-file]
directory = flac-1.4.3
source_url = https://github.com/xiph/flac/releases/download/1.4.3/flac-1.4.3.tar.xz
source_filename = flac-1.4.3.tar.xz
source_hash = 6c58e69cd22348f441b861092b825e591d0b822e106de6eb0ee4d05d27205b70
patch_filename = flac_1.4.3-2_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/flac_1.4.3-2/get_patch
patch_hash = 3eace1bd0769d3e0d4ff099960160766a5185d391c8f583293b087a1f96c2a9c
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/flac_1.4.3-2/flac-1.4.3.tar.xz
wrapdb_version = 1.4.3-2
[provide]
flac = flac_dep

@ -0,0 +1,13 @@
[wrap-file]
directory = libogg-1.3.5
source_url = https://downloads.xiph.org/releases/ogg/libogg-1.3.5.tar.xz
source_filename = libogg-1.3.5.tar.xz
source_hash = c4d91be36fc8e54deae7575241e03f4211eb102afb3fc0775fbbc1b740016705
patch_filename = ogg_1.3.5-6_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/ogg_1.3.5-6/get_patch
patch_hash = 8be6dcd5f93bbf9c0b9c8ec1fa29810226a60f846383074ca05b313a248e78b2
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/ogg_1.3.5-6/libogg-1.3.5.tar.xz
wrapdb_version = 1.3.5-6
[provide]
ogg = libogg_dep

@ -0,0 +1,13 @@
[wrap-file]
directory = openal-soft-1.23.1
source_url = https://github.com/kcat/openal-soft/archive/refs/tags/1.23.1.tar.gz
source_filename = openal-soft-1.23.1.tar.gz
source_hash = dfddf3a1f61059853c625b7bb03de8433b455f2f79f89548cbcbd5edca3d4a4a
patch_filename = openal-soft_1.23.1-2_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/openal-soft_1.23.1-2/get_patch
patch_hash = e03c3afe0bb40a931d25d41d92a08b90e3c33b217d1b47210b26ca6627eb3aa3
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/openal-soft_1.23.1-2/openal-soft-1.23.1.tar.gz
wrapdb_version = 1.23.1-2
[provide]
openal = openal_dep

@ -0,0 +1,13 @@
[wrap-file]
directory = SFML-2.6.2
source_url = https://github.com/SFML/SFML/archive/refs/tags/2.6.2.tar.gz
source_filename = 2.6.2.tar.gz
source_hash = 15ff4d608a018f287c6a885db0a2da86ea389e516d2323629e4d4407a7ce047f
patch_filename = sfml_2.6.2-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/sfml_2.6.2-1/get_patch
patch_hash = 36737f7fc6d616be791c6901b15414315b3a77df82dabc80b151d628e5d48386
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/sfml_2.6.2-1/2.6.2.tar.gz
wrapdb_version = 2.6.2-1
[provide]
sfml = sfml_dep

@ -0,0 +1,14 @@
[wrap-file]
directory = libvorbis-1.3.7
source_url = https://downloads.xiph.org/releases/vorbis/libvorbis-1.3.7.tar.xz
source_filename = libvorbis-1.3.7.tar.xz
source_hash = b33cc4934322bcbf6efcbacf49e3ca01aadbea4114ec9589d1b1e9d20f72954b
patch_filename = vorbis_1.3.7-4_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/vorbis_1.3.7-4/get_patch
patch_hash = 979e22b24b16c927040700dfd8319cd6ba29bf52a14dbc66b1cb4ea60504f14a
wrapdb_version = 1.3.7-4
[provide]
vorbis = vorbis_dep
vorbisfile = vorbisfile_dep
vorbisenc = vorbisenc_dep
Loading…
Cancel
Save