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:
Zed A. Shaw 2025-01-04 12:20:41 -05:00
parent 6b181382bd
commit ca80736d7c
21 changed files with 2165 additions and 90 deletions

2010
scratchpad/quickcg.cpp Normal file

File diff suppressed because it is too large Load diff

334
scratchpad/quickcg.h Normal file
View file

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

View file

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

View file

@ -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;
}
}

View 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;
}

36
scratchpad/sdlprog.cpp Normal file
View file

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

553
scratchpad/timcaster.cpp Normal file
View 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;
}