Compare commits
4 commits
bc9770e233
...
b47052aa4d
Author | SHA1 | Date | |
---|---|---|---|
b47052aa4d | |||
cad775f88e | |||
c6b09d668c | |||
be78434a9c |
5 changed files with 218 additions and 40 deletions
25
Player.cpp
25
Player.cpp
|
@ -14,15 +14,32 @@ void Player::move(float dx, float dy)
|
|||
y += dy;
|
||||
}
|
||||
|
||||
void Player::rotate(int alpha)
|
||||
void Player::rotate(float alpha)
|
||||
{
|
||||
orientation += alpha%360;
|
||||
if(orientation >= 360)
|
||||
orientation += fmodf(alpha, 360);
|
||||
if(orientation > 360)
|
||||
{
|
||||
orientation -= 360;
|
||||
}
|
||||
if(orientation <= 360)
|
||||
else if(orientation < 0)
|
||||
{
|
||||
orientation += 360;
|
||||
}
|
||||
|
||||
/*
|
||||
* Rotate the movement vector along the new angle, assumes that the only
|
||||
* speed influencing the player is its own movement.
|
||||
*/
|
||||
float prevSpeedX = currentMoveSpeedX;
|
||||
float prevSpeedY = currentMoveSpeedY;
|
||||
currentMoveSpeedX = cosf(-alpha * deg_to_rad) * prevSpeedX
|
||||
- sinf(-alpha * deg_to_rad) * prevSpeedY;
|
||||
currentMoveSpeedY = sinf(-alpha * deg_to_rad) * prevSpeedX
|
||||
+ cosf(-alpha * deg_to_rad) * prevSpeedY;
|
||||
}
|
||||
|
||||
void Player::updateSpeed(float localX, float localY) {
|
||||
// TODO: Support strafing.
|
||||
currentMoveSpeedX = localX * sinf(orientation*deg_to_rad)*moveSpeed;
|
||||
currentMoveSpeedY = localX * cosf(orientation*deg_to_rad)*moveSpeed;
|
||||
}
|
||||
|
|
17
Player.h
17
Player.h
|
@ -5,6 +5,9 @@
|
|||
#ifndef RAYCASTING_PLAYER_H
|
||||
#define RAYCASTING_PLAYER_H
|
||||
|
||||
#include <cmath>
|
||||
|
||||
static constexpr float deg_to_rad = 3.14159265/180;
|
||||
|
||||
class Player {
|
||||
public:
|
||||
|
@ -14,10 +17,22 @@ public:
|
|||
float y;
|
||||
float orientation;
|
||||
|
||||
float moveSpeed = 5;
|
||||
float rotationSpeed = 180;
|
||||
|
||||
float currentMoveSpeedX = 0;
|
||||
float currentMoveSpeedY = 0;
|
||||
|
||||
float currentRotationSpeed = 0;
|
||||
|
||||
/* 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(int alpha);
|
||||
void rotate(float alpha);
|
||||
void updateSpeed(float localX, float localY);
|
||||
};
|
||||
|
||||
|
||||
|
|
136
World.cpp
136
World.cpp
|
@ -3,6 +3,7 @@
|
|||
//
|
||||
|
||||
#include "World.h"
|
||||
#include <cmath>
|
||||
|
||||
|
||||
World::World(int w, int h, sf::Color groundColor, sf::Color ceilingColor, std::vector<BlockType> 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)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
vTan *= vDir;
|
||||
vOffsetY *= vTan;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
return 0;
|
||||
hCheckX += hTan;
|
||||
hCheckY += hDir;
|
||||
i++;
|
||||
}
|
||||
|
||||
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,25 @@ void World::render(sf::RenderWindow& window) const
|
|||
*/
|
||||
for(int i = 0;i<window.getSize().x;i++)
|
||||
{
|
||||
fillColumn(window, i, 0.5);
|
||||
float deltaAngle = (player.fov/window.getSize().x) * (i-window.getSize().x/2.0);
|
||||
float rayAngle = player.orientation + deltaAngle;
|
||||
if (rayAngle < 0) {
|
||||
rayAngle += 360;
|
||||
} else if (rayAngle > 360) {
|
||||
rayAngle -= 360;
|
||||
}
|
||||
float obstacleScale = castRay(player.x, player.y, rayAngle)/player.sensorSize;
|
||||
fillColumn(window, i, obstacleScale);
|
||||
}
|
||||
}
|
||||
|
||||
void World::step(const float& stepTime) {
|
||||
player.move(player.currentMoveSpeedX*stepTime,
|
||||
player.currentMoveSpeedY*stepTime);
|
||||
/* Undo last move if the player would end up in a wall. */
|
||||
if (getBlock((int)player.x, (int)player.y) != BlockType::AIR) {
|
||||
player.move(-player.currentMoveSpeedX*stepTime,
|
||||
-player.currentMoveSpeedY*stepTime);
|
||||
}
|
||||
player.rotate(player.currentRotationSpeed*stepTime);
|
||||
}
|
||||
|
|
23
World.h
23
World.h
|
@ -23,16 +23,24 @@ class World {
|
|||
public:
|
||||
Player player;
|
||||
|
||||
World(int w, int h, sf::Color groundColor = sf::Color::Green, sf::Color ceilingColor = sf::Color::Blue,
|
||||
World(int w, int h,
|
||||
sf::Color groundColor = sf::Color{67, 137, 39},
|
||||
sf::Color ceilingColor = sf::Color{39, 69, 137},
|
||||
std::vector<BlockType> worldMap = {});
|
||||
int getW() const;
|
||||
int getH() const;
|
||||
|
||||
BlockType getBlock(float x, float y) const;
|
||||
inline BlockType getBlock(float x, float y) const;
|
||||
void setBlock(BlockType block, int x, int y, int width = 1, int height = 1);
|
||||
|
||||
void render(sf::RenderWindow&) const;
|
||||
|
||||
/**
|
||||
* Move the world one step forward.
|
||||
* @param stepTime delta time since last step, in seconds
|
||||
*/
|
||||
void step(const float& stepTime);
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& ostream, World const & world);
|
||||
private:
|
||||
int w;
|
||||
|
@ -42,15 +50,16 @@ private:
|
|||
sf::Color groundColor;
|
||||
sf::Color ceilingColor;
|
||||
|
||||
void fillColumn(sf::RenderWindow&, int column, float scale, sf::Color wallColor = sf::Color(127,127,127)) const;
|
||||
void fillColumn(sf::RenderWindow&, int column, float scale,
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
|
|
55
main.cpp
55
main.cpp
|
@ -1,22 +1,12 @@
|
|||
#include <iostream>
|
||||
#include "SFML/Graphics.hpp"
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
#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);
|
||||
|
@ -27,15 +17,56 @@ int main()
|
|||
world.render(window);
|
||||
window.display();
|
||||
|
||||
sf::Event event{};
|
||||
sf::Clock frameTime;
|
||||
while (window.isOpen())
|
||||
{
|
||||
sf::Event event;
|
||||
while (window.pollEvent(event))
|
||||
{
|
||||
if (event.type == sf::Event::Closed)
|
||||
window.close();
|
||||
else if (event.type == sf::Event::KeyPressed) {
|
||||
switch (event.key.code) {
|
||||
case sf::Keyboard::Key::Escape:
|
||||
window.close();
|
||||
break;
|
||||
case sf::Keyboard::Key::Left:
|
||||
world.player.currentRotationSpeed = -world.player.rotationSpeed;
|
||||
break;
|
||||
case sf::Keyboard::Key::Right:
|
||||
world.player.currentRotationSpeed = world.player.rotationSpeed;
|
||||
break;
|
||||
case sf::Keyboard::Key::Up:
|
||||
world.player.updateSpeed(1, 0);
|
||||
break;
|
||||
case sf::Keyboard::Key::Down:
|
||||
world.player.updateSpeed(-1, 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (event.type == sf::Event::KeyReleased) {
|
||||
switch (event.key.code) {
|
||||
case sf::Keyboard::Key::Left:
|
||||
case sf::Keyboard::Key::Right:
|
||||
world.player.currentRotationSpeed = 0;
|
||||
break;
|
||||
case sf::Keyboard::Key::Up:
|
||||
case sf::Keyboard::Key::Down:
|
||||
world.player.updateSpeed(0, 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
window.clear();
|
||||
|
||||
world.step(frameTime.restart().asSeconds());
|
||||
world.render(window);
|
||||
window.display();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue