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
2010
scratchpad/quickcg.cpp
Normal file
2010
scratchpad/quickcg.cpp
Normal file
File diff suppressed because it is too large
Load diff
334
scratchpad/quickcg.h
Normal file
334
scratchpad/quickcg.h
Normal 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
|
||||
|
||||
|
250
scratchpad/raycaster_flat.cpp
Normal file
250
scratchpad/raycaster_flat.cpp
Normal 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;
|
||||
}
|
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;
|
||||
}
|
36
scratchpad/sdlprog.cpp
Normal file
36
scratchpad/sdlprog.cpp
Normal 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
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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue