First cut at a replica of the python raycaster. Left side almost works the same but have to sort out math differences.
This commit is contained in:
parent
6b181382bd
commit
ca80736d7c
21 changed files with 2165 additions and 90 deletions
40
dbc.cpp
Normal file
40
dbc.cpp
Normal file
|
@ -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};
|
||||
}
|
||||
}
|
29
dbc.hpp
Normal file
29
dbc.hpp
Normal file
|
@ -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);
|
||||
}
|
34
main.cpp
34
main.cpp
|
@ -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;
|
||||
}
|
94
matrix.cpp
Normal file
94
matrix.cpp
Normal file
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
305
matrix.hpp
Normal file
305
matrix.hpp
Normal file
|
@ -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>;
|
||||
}
|
24
meson.build
24
meson.build
|
@ -1,27 +1,25 @@
|
|||
project('lodecaster', 'cpp',
|
||||
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',
|
||||
'dbc.cpp',
|
||||
'matrix.cpp',
|
||||
'tests/base.cpp',
|
||||
],
|
||||
win_subsystem: 'windows',
|
||||
dependencies: dependencies)
|
||||
dependencies: dependencies + [catch2])
|
||||
|
||||
executable('lodecaster', [
|
||||
'quickcg.cpp',
|
||||
'raycaster_flat.cpp',
|
||||
executable('raycaster', [
|
||||
'dbc.cpp',
|
||||
'matrix.cpp',
|
||||
'raycaster.cpp',
|
||||
],
|
||||
win_subsystem: 'windows',
|
||||
dependencies: dependencies)
|
||||
|
|
19
point.hpp
Normal file
19
point.hpp
Normal file
|
@ -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);
|
||||
}
|
||||
};
|
126
pycaster.py
Normal file
126
pycaster.py
Normal file
|
@ -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)
|
102
raycaster.cpp
Normal file
102
raycaster.cpp
Normal file
|
@ -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,26 +199,30 @@ 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();
|
||||
|
||||
SDL_Event event;
|
||||
while(SDL_PollEvent(&event)) {
|
||||
if(event.type != SDL_KEYDOWN) continue;
|
||||
|
||||
cls();
|
||||
//move forward if no wall in front of you
|
||||
if(keyDown(SDLK_UP))
|
||||
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(keyDown(SDLK_DOWN))
|
||||
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(keyDown(SDLK_RIGHT))
|
||||
if(event.key.keysym.sym == SDLK_RIGHT)
|
||||
{
|
||||
//both camera direction and camera plane must be rotated
|
||||
double oldDirX = dirX;
|
||||
|
@ -228,7 +233,7 @@ int main(int /*argc*/, char */*argv*/[])
|
|||
planeY = oldPlaneX * sin(-rotSpeed) + planeY * cos(-rotSpeed);
|
||||
}
|
||||
//rotate to the left
|
||||
if(keyDown(SDLK_LEFT))
|
||||
if(event.key.keysym.sym == SDLK_LEFT)
|
||||
{
|
||||
//both camera direction and camera plane must be rotated
|
||||
double oldDirX = dirX;
|
||||
|
@ -240,3 +245,6 @@ int main(int /*argc*/, char */*argv*/[])
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
471
scratchpad/raycaster_sprites.cpp
Normal file
471
scratchpad/raycaster_sprites.cpp
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
293
scratchpad/raycaster_textured.cpp
Normal file
293
scratchpad/raycaster_textured.cpp
Normal file
|
@ -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;
|
||||
}
|
553
scratchpad/timcaster.cpp
Normal file
553
scratchpad/timcaster.cpp
Normal file
|
@ -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;
|
||||
}
|
13
wraps/flac.wrap
Normal file
13
wraps/flac.wrap
Normal file
|
@ -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
|
13
wraps/ogg.wrap
Normal file
13
wraps/ogg.wrap
Normal file
|
@ -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
|
13
wraps/openal-soft.wrap
Normal file
13
wraps/openal-soft.wrap
Normal file
|
@ -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
|
13
wraps/sfml.wrap
Normal file
13
wraps/sfml.wrap
Normal file
|
@ -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
|
14
wraps/vorbis.wrap
Normal file
14
wraps/vorbis.wrap
Normal file
|
@ -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…
Add table
Add a link
Reference in a new issue