diff --git a/Player.h b/Player.h index fd9cf84..d39e571 100644 --- a/Player.h +++ b/Player.h @@ -5,6 +5,9 @@ #ifndef RAYCASTING_PLAYER_H #define RAYCASTING_PLAYER_H +#include + +static constexpr float deg_to_rad = 3.14159265/180; class Player { public: @@ -14,7 +17,10 @@ public: float y; float orientation; + /* View properties. */ float fov = 70; + float sensorSize = 0.035; /* 35mm, about equivalent to human eye ? */ + float focalLength = sensorSize / (2*tanf((fov*deg_to_rad)/2)); void move(float dx, float dy); void rotate(float alpha); diff --git a/World.cpp b/World.cpp index 9c8ccb0..84be37c 100644 --- a/World.cpp +++ b/World.cpp @@ -3,6 +3,7 @@ // #include "World.h" +#include World::World(int w, int h, sf::Color groundColor, sf::Color ceilingColor, std::vector worldMap) : w(w), h(h), @@ -92,32 +93,119 @@ void World::fillColumn(sf::RenderWindow& window, int column, float scale, sf::Co window.draw(pixelColumn); } -float World::castRay(float originX, float originY, float orientation) +float World::castRay(float originX, float originY, float orientation) const { /* * Reference used for ray intersection computations : * https://web.archive.org/web/20220628034315/https://yunes.informatique.univ-paris-diderot.fr/wp-content/uploads/cours/INFOGRAPHIE/08-Raycasting.pdf + * The logic is as follows : + * - This computes one set of point per edge crossings (horizontal/vertical) + * - The origin not being confined to the grid, offsets are computed to + * align the intersections properly + * - The intersections are at multiples of the tangent of the relevant + * angle for the axis of interest, and simply on successive edges of + * the grid for the other one + * - Depending on the orientation, signs must be taken into account + * to work 360° */ - float deltaX; - float deltaY; - if(orientation < 45 || orientation > 315) - { + /* Offsets to get back on the grid from the ray's origin. */ + float hOffsetX; + float hOffsetY; + float vOffsetX; + float vOffsetY; + /* Signs controlling the direction of travel. */ + float hDir; + float vDir; + /* Need offset for rounding in the right direction ? */ + float hRound; + float vRound; + + float rads = orientation * deg_to_rad; + /* Used for vertical intersections. */ + float rads_offset = (90 - orientation) * deg_to_rad; + + /* Tangents used for the different axes. */ + float hTan = tanf(rads); + float vTan = tanf(rads_offset); + + /* Check if cos > 0 for horizontal hits formulas. */ + if (orientation < 90 || orientation > 270) { + hOffsetX = ceilf(originY) - originY; + hOffsetY = ceilf(originY); + hDir = +1; + hRound = 0; + } else { + hOffsetX = originY - floorf(originY); + hOffsetY = floorf(originY); + hDir = -1; + hRound = -1; } - else if(orientation < 135) - { + hTan *= hDir; + hOffsetX *= hTan; + /* Check if sin > 0 for vertical hits formulas. */ + if (orientation < 180) { + vOffsetX = ceilf(originX); + vOffsetY = ceilf(originX) - originX; + vDir = +1; + vRound = 0; + } else { + vOffsetX = floorf(originX); + vOffsetY = originX - floorf(originX); + vDir = -1; + vRound = -1; } - else if(orientation < 225) - { + vTan *= vDir; + vOffsetY *= vTan; - } - else - { + /* + * Now we have all the constants and deltas to work with, cast the ray. + * Generated points follow the formulas : + * - h-intersect : (originX + hOffsetX + hTan*i, hOffsetY + hDir*i) + * - v-intersect : (vOffsetX + vDir*i, originY + vOffsetY + vTan*i) + */ + int i = 0; + float hCheckX = originX + hOffsetX; + float hCheckY = hOffsetY; + /* Bounds + sanity check. */ + while (hCheckX >= 0 && hCheckX <= w && hCheckY >= 0 && hCheckY <= h && i < h) { + if (getBlock(floorf(hCheckX), floorf(hCheckY) + hRound) == BlockType::WALL) { + break; + } + hCheckX += hTan; + hCheckY += hDir; + i++; } - return 0; + i = 0; + float vCheckX = vOffsetX; + float vCheckY = originY + vOffsetY; + + /* Bounds + sanity check. */ + while (vCheckX >= 0 && vCheckX < w && vCheckY >= 0 && vCheckY < h && i < w) { + if (getBlock(floorf(vCheckX) + vRound, floorf(vCheckY)) == BlockType::WALL) { + break; + } + + vCheckX += vDir; + vCheckY += vTan; + i++; + } + + /* + * We may or may not have hit something. Check which coordinates are closest + * and use those for computing the apparent size on screen. + */ + float hDist = sqrtf((originX - hCheckX)*(originX - hCheckX) + + (originY - hCheckY)*(originY - hCheckY)); + float vDist = sqrtf((originX - vCheckX)*(originX - vCheckX) + + (originY - vCheckY)*(originY - vCheckY)); + float finalDist = hDist > vDist ? vDist : hDist; + + /* 2 Is wall height in meters. */ + return player.focalLength*2/finalDist; } void World::render(sf::RenderWindow& window) const @@ -143,7 +231,16 @@ void World::render(sf::RenderWindow& window) const */ for(int i = 0;i 360) { + rayAngle -= 360; + } + float obstacleScale = castRay(player.x, player.y, rayAngle)/player.sensorSize; + fillColumn(window, i, obstacleScale); } +} } diff --git a/World.h b/World.h index c008cc0..91236c0 100644 --- a/World.h +++ b/World.h @@ -48,12 +48,12 @@ private: sf::Color wallColor = sf::Color(84,56,34)) const; /** * Cast a ray from a given position and return the on-screen scale. - * @param originX Ray X origin - * @param originY Ray Y origin - * @param orientation Angle to cast to, in degrees + * @param originX Ray X origin, strictly positive + * @param originY Ray Y origin, strictly positive + * @param orientation Angle to cast to, in degrees between 0 and 360 * @return Scale on the screen of the hit wall. */ - float castRay(float originX, float originY, float orientation); + float castRay(float originX, float originY, float orientation) const; }; diff --git a/main.cpp b/main.cpp index 7b883d3..19879b2 100644 --- a/main.cpp +++ b/main.cpp @@ -1,22 +1,13 @@ #include -#include "SFML/Graphics.hpp" +#include #include "World.h" // TODO: Handle inputs to move player -// TODO: Raycast from player position -// TODO: Fix raycasts and use it correctly -// TODO: Render world in event loop -// TODO: Understand what the code was doing (looks like scale from fill column is the output of the raycast) -// Ref: https://lodev.org/cgtutor/raycasting.html -// Ref: http://yunes.informatique.univ-paris-diderot.fr/wp-content/uploads/cours/INFOGRAPHIE/08-Raycasting.pdf -// TODO: Update player for camera plane -// TODO: Camera plane represents screen, rays go through it proportional to the screen // TODO: Find a way to go to edges instead of just equally split (?) int main() { - std::cout << "Hello, World!" << std::endl; World world(10,10); world.setBlock(BlockType::AIR,1,1,8,8); world.setBlock(BlockType::WALL,4,4,2,2); @@ -35,6 +26,10 @@ int main() if (event.type == sf::Event::Closed) window.close(); } + window.clear(); + + world.render(window); + window.display(); } return 0;