Sign in to follow this  
FictionJVR

Raycasting (wolfenstein like 2d game)

Recommended Posts

Hi,

So yesterday I started working on a wolfenstein like 2d game, using raycasting to visualize the world. (by using permadi's tutorial)

But it's giving me ton of headaches, usually I can figure out or solve things myself, but this time, I'm gonna need help from more experienced people.

Small note, it's a bit messy, and just code to test it, so it's not gonna be used like this, just wanted to get the actually running before I start thinking about optimizing it, and writing it more cleanly.

So yes, the problem, the world is basicly just garbage, it's just impossible to tell what is really going on.
When I was trying some solutions I did get it to work when the camera was in a 90 degree angle, but then when trying to fix the fact that the rotation of the camera didn't work it basicly broke down, and it has been getting even worse with every solution I tried.

So this forum is basicly the last hope, before I just scratch this code and start again.

the degrees are in the unit circle, so right = 0 degrees, 90 degrees is up, 180 left, etc
And the coordinate system follows the usual convention for screens, so the Y axis is pointing down.

Initialization:
[code]raycast::raycast(): m_playerFOV(ToRad(60)), m_PlayerViewAngle(ToRad(90)), m_PlayerHeight(BLOCK_DIM/2), m_PlayerPos(8*BLOCK_DIM,11*BLOCK_DIM), m_ProjPlaneDim(800,600)
{
//Find the center of the projection plane
m_ProjPlaneCenter.x = m_ProjPlaneDim.x/2;
m_ProjPlaneCenter.y = m_ProjPlaneDim.y/2;
//Find the distance from the player to the projection plane
m_ProjPlaneDistance = m_ProjPlaneCenter.x / tan(m_playerFOV/2);
//Find the player direction vector, only used for movement purposes
m_PlayerDirection.x = cos(m_PlayerViewAngle);
m_PlayerDirection.y = sin(m_PlayerViewAngle);
m_PlayerDirection = m_PlayerDirection + m_PlayerPos;
//Find the offset for each ray
m_RayAngles = ToRad(m_playerFOV / ToRad(m_ProjPlaneDim.x));
//Initialize the map
InitMap();
}[/code]

Tick Function:
[code]void raycast::Tick()
{
//Add half of the player FOV to get the angel of the first ray
double rayToCastAngle = m_PlayerViewAngle+ (m_playerFOV /2);
//Cast rays equal to the width of the projection plane
for(int rayCounter = 0; rayCounter < int(m_ProjPlaneDim.x); ++rayCounter)
{
//Find the horizontal intersection
DOUBLE2 HorizontalIntersect = HorizontalInterSection(rayToCastAngle);
//Find the vertical Intersection
DOUBLE2 VerticalIntersect = VerticalInterSection(rayToCastAngle);

//Calculate the distances of the rays if the ray has hit a wall
double horizontalRayDist = 0;
if(HorizontalIntersect.x != std::numeric_limits<double>::max() && HorizontalIntersect.y != std::numeric_limits<double>::max())
{
horizontalRayDist = sqrt((m_PlayerPos.x - HorizontalIntersect.x)*(m_PlayerPos.x - HorizontalIntersect.x))+((m_PlayerPos.y - HorizontalIntersect.y)*(m_PlayerPos.y - HorizontalIntersect.y));

//Correct the distance (fishbowl effect)
double angleOffset = rayToCastAngle - m_PlayerViewAngle;
horizontalRayDist *= cos(angleOffset);
}
else
{
horizontalRayDist = std::numeric_limits<double>::max();
}
double verticalRayDist = 0;
if(VerticalIntersect.x != std::numeric_limits<double>::max() && VerticalIntersect.y != std::numeric_limits<double>::max())
{
verticalRayDist = sqrt((m_PlayerPos.x - VerticalIntersect.x)*(m_PlayerPos.x - VerticalIntersect.x))+((m_PlayerPos.y - VerticalIntersect.y)*(m_PlayerPos.y - VerticalIntersect.y));
//Correct the distance (fishbowl effect)
double angleOffset = rayToCastAngle - m_PlayerViewAngle;
verticalRayDist *= cos(angleOffset);
}
else
{
verticalRayDist = std::numeric_limits<double>::max();
}

//Find the smaller of the 2 distances
if(verticalRayDist < horizontalRayDist)
{
m_RayDistances[rayCounter] = double(BLOCK_DIM) / verticalRayDist * m_ProjPlaneDistance;
}
else
{
if(horizontalRayDist < verticalRayDist)
{
m_RayDistances[rayCounter] = double(BLOCK_DIM) / horizontalRayDist * m_ProjPlaneDistance;
}
}
//subtract angle of 1 ray from the angle
rayToCastAngle -= m_RayAngles;
if(rayToCastAngle > ToRad(360))
rayToCastAngle = 0;
if(rayToCastAngle < 0)
rayToCastAngle = ToRad(360);
}
}
[/code]

Horizontal and vertical rays:
[code]DOUBLE2 raycast::HorizontalInterSection(double rayAngle)
{
//If the ray is 0 or 180 degrees there will be no horizontal Intersection
if(rayAngle == 0 || rayAngle == ToRad(180))
return DOUBLE2(std::numeric_limits<double>::max(), std::numeric_limits<double>::max());
//Find the coordinate of the first intersection
DOUBLE2 intersection(0,0);
if(rayAngle < ToRad(180))
{
//ray is facing up
intersection.y = floor(m_PlayerPos.x / BLOCK_DIM) * BLOCK_DIM - 1;
}
else
{
//ray is facing down
intersection.y = floor(m_PlayerPos.x / BLOCK_DIM) * BLOCK_DIM + BLOCK_DIM;
}
intersection.x = m_PlayerPos.x + (m_PlayerPos.x - intersection.y) / tan(rayAngle);
//Check if the ray is outside of the grid
if(intersection.x < 0 || (intersection.x > double(NUM_ROWS*BLOCK_DIM)) || intersection.y < 0 || (intersection.y > double(NUM_ROWS*BLOCK_DIM)))
{
return DOUBLE2(std::numeric_limits<double>::max(), std::numeric_limits<double>::max());
}
//Check if there is a wall on the coordinate
if(m_WorldMapArr[int(intersection.x/BLOCK_DIM)][int(intersection.y/BLOCK_DIM)] > 0)
{
return intersection;
}
else
{
//Find the Offsets
DOUBLE2 offSet(double((BLOCK_DIM)/tan(rayAngle)),BLOCK_DIM);
//if the ray is facing up
if(rayAngle < ToRad(180))
offSet.y = BLOCK_DIM*-1;

//loop until intersection is found or the ray goes outside of the grid
while(true)
{
intersection.x += offSet.x;
intersection.y += offSet.y;
//Check if the ray is outside of the grid
if(intersection.x < 0 || (intersection.x > double(NUM_ROWS*BLOCK_DIM)) || intersection.y < 0 || (intersection.y > double(NUM_ROWS*BLOCK_DIM)))
{
return DOUBLE2(std::numeric_limits<double>::max(), std::numeric_limits<double>::max());
}
//Check if there is a wall on the coordinate
if(m_WorldMapArr[int(intersection.x/BLOCK_DIM)][int(intersection.y/BLOCK_DIM)] > 0)
{
return intersection;
}
}
}
}
DOUBLE2 raycast::VerticalInterSection(double rayAngle)
{
//if the ray is 90 or 270 degrees we can't find a vertical intersection
if(rayAngle == ToRad(90) || rayAngle == ToRad(270))
{
return DOUBLE2(std::numeric_limits<double>::max(), std::numeric_limits<double>::max());
}
//Find the first Intersection
DOUBLE2 intersection(0,0);
if(rayAngle > ToRad(90) && rayAngle < ToRad(270))
{
//ray is facing left
intersection.x = floor(m_PlayerPos.x / double(BLOCK_DIM)) * BLOCK_DIM -1;
}
else
{
//ray is facing right
intersection.x = floor(m_PlayerPos.x / double(BLOCK_DIM)) * BLOCK_DIM + BLOCK_DIM;
}
intersection.y = m_PlayerPos.y + (m_PlayerPos.x - intersection.x) * tan(rayAngle);
//Check if the ray is withing the grid
if(intersection.x < 0 || (intersection.x > double(NUM_ROWS*BLOCK_DIM)) || intersection.y < 0 || (intersection.y > double(NUM_ROWS*BLOCK_DIM)))
{
return DOUBLE2(std::numeric_limits<double>::max(), std::numeric_limits<double>::max());
}
//check if the ray has hit a wall
if(m_WorldMapArr[int(intersection.x/BLOCK_DIM)][int(intersection.y/BLOCK_DIM)] > 0)
{
return intersection;
}
else
{
//find the offsets
DOUBLE2 offset(BLOCK_DIM, (m_PlayerPos.y + (m_PlayerPos.x - intersection.x) * tan(rayAngle)));
//Check if the ray is facing right
if(rayAngle > ToRad(90) && rayAngle < ToRad(270))
offset.x = BLOCK_DIM * -1;
//loop untill a wall has found or the ray is outside the grid
while(true)
{
intersection.x += offset.x;
intersection.y += offset.y;
//Check if the ray is outside of the grid
if(intersection.x < 0 || (intersection.x > double(NUM_ROWS*BLOCK_DIM)) || intersection.y < 0 || (intersection.y > double(NUM_ROWS*BLOCK_DIM)))
{
return DOUBLE2(std::numeric_limits<double>::max(), std::numeric_limits<double>::max());
}
//Check if there is a wall on the coordinate
if(m_WorldMapArr[int(intersection.x/BLOCK_DIM)][int(intersection.y/BLOCK_DIM)] > 0)
{
return intersection;
}
}
}
}
[/code]

Share this post


Link to post
Share on other sites
tldr, but here's what I did:

[CODE]
//find_distance - calculates perpendicular distance from player to wall in his direction + relative angle
float QuantumGameEngine::QuantumGraphics::QuantumRaycast::CRayCast::find_distance(QuantumGameEngine::QuantumGraphics::QuantumRaycast::CRayPlayer* pPlayer,
QuantumGameEngine::QuantumGraphics::QuantumRaycast::CRayMap* pMap,
float ray_angle,
float max_dist){
float angle = fmod(pPlayer->GetDirection() + ray_angle, static_cast<float>(2.0f * D3DX_PI)); //Calculate ray absolute angle based off of player direction and ray angle offset
if(angle == 0.0f || angle == D3DX_PI){
//Angles of 0 and pi are perfectly horizontal, so we can only check vertical
return this->vertical_distance(pPlayer, pMap, ray_angle, max_dist) * cos(ray_angle);
}
else if(angle == 0.5f * D3DX_PI || angle == 1.5f * D3DX_PI){
//Angles of pi/4 and 7pi/4 are perfectly vertical, so we can only check horizontal
return this->horizontal_distance(pPlayer, pMap, ray_angle, max_dist) * cos(ray_angle);
}
else {
//Not in the 4 cardinal directions, so we take the minimum distance
float d1 = this->horizontal_distance(pPlayer, pMap, ray_angle, max_dist);
float d2 = this->vertical_distance(pPlayer, pMap, ray_angle, max_dist);
if(d1 < 0.0f){
return d2 * cos(ray_angle);
}
else if(d2 < 0.0f){
return d1 * cos(ray_angle);
}
else {
return min(d1, d2) * cos(ray_angle);
}
}
}
//horizontal_distance - calculates perpendicular distance from player to wall in his direction + relative angle using horizontal lattice lines
float QuantumGameEngine::QuantumGraphics::QuantumRaycast::CRayCast::horizontal_distance(QuantumGameEngine::QuantumGraphics::QuantumRaycast::CRayPlayer* pPlayer,
QuantumGameEngine::QuantumGraphics::QuantumRaycast::CRayMap* pMap,
float ray_angle,
float max_dist){
float angle = fmod(pPlayer->GetDirection() + ray_angle, static_cast<float>(2.0f * D3DX_PI)); //Calculate ray absolute angle based off of player direction and ray angle offset

//Block size variable
float block = static_cast<float>(BLOCK_SIZE);
//Variables for lattice line points
float x = 0.0f;
float y = 0.0f;
const float px = pPlayer->GetX();
const float py = pPlayer->GetY();
float dx = block / tan(angle);
float dy = block;
float dist = 0.0f;
if(angle > 0.0f && angle < D3DX_PI){
//Upwards facing ray
dy = -dy; //Negate dy
//Calculate first intersection
y = static_cast<float>((static_cast<int>(px) / BLOCK_SIZE) * BLOCK_SIZE) - 1.0f;
x = px + (py - y) / tan(angle);
dist = sqrt((x - px) * (x - px) + (y - py) * (y - py));
if(this->wall_exists(pMap, static_cast<int>(x / block), static_cast<int>(y / block))){
//Return distance if there is a wall here
return dist;
}
//Otherwise continue
do {
x += dx; //Increment x
y += dy; //Increment y
dist = sqrt((x - px) * (x - px) + (y - py) * (y - py)); //Calculate distance
if(dist > max_dist){
//Exceed maximum ray distance
return -1.0f / cos(ray_angle); //-1 for failure
}
} while(!this->wall_exists(pMap, static_cast<int>(x / block), static_cast<int>(y / block)));
//Found wall
return dist;
} else {
//Downwards facing ray
//Calculate first intersection
y = static_cast<float>((static_cast<int>(py) / BLOCK_SIZE) * BLOCK_SIZE) + block;
x = px + (py - y) / tan(angle);
dist = sqrt((x - px) * (x - px) + (y - py) * (y - py));
if(this->wall_exists(pMap, static_cast<int>(x / block), static_cast<int>(y / block))){
//Return distance if there is a wall here
return dist;
}
//Otherwise continue
do {
x += dx; //Increment x
y += dy; //Increment y
dist = sqrt((x - px) * (x - px) + (y - py) * (y - py)); //Calculate distance
if(dist > max_dist){
//Exceed maximum ray distance
return -1.0f / cos(ray_angle); //-1 for failure
}
} while(!this->wall_exists(pMap, static_cast<int>(x / block), static_cast<int>(y / block)));
//Found wall
return dist;
}
return -1.0f / cos(ray_angle); //Could not find wall - return -1 to signify failure
}
//vertical_distance - calculates perpendicular distance from player to wall in his direction + relative angle using vertical lattice lines
float QuantumGameEngine::QuantumGraphics::QuantumRaycast::CRayCast::vertical_distance(QuantumGameEngine::QuantumGraphics::QuantumRaycast::CRayPlayer* pPlayer,
QuantumGameEngine::QuantumGraphics::QuantumRaycast::CRayMap* pMap,
float ray_angle,
float max_dist){
float angle = fmod(pPlayer->GetDirection() + ray_angle, static_cast<float>(2.0f * D3DX_PI)); //Calculate ray absolute angle based off of player direction and ray angle offset

//Block size variable
float block = static_cast<float>(BLOCK_SIZE);
//Variables for lattice line points
float x = 0.0f;
float y = 0.0f;
const float px = pPlayer->GetX();
const float py = pPlayer->GetY();
float dy = block * tan(angle);
float dx = block;
float dist = 0.0f;
if(angle > 0.5f * D3DX_PI && angle < 1.5f * D3DX_PI){
//Left facing ray
dx = -dx; //Negate dx
//Calculate first intersection
x = static_cast<float>((static_cast<int>(px) / BLOCK_SIZE) * BLOCK_SIZE) - 1.0f;
y = py + (px - x) * tan(angle);
dist = sqrt((x - px) * (x - px) + (y - py) * (y - py));
if(this->wall_exists(pMap, static_cast<int>(x / block), static_cast<int>(y / block))){
//Return distance if there is a wall here
return dist;
}
//Otherwise continue
do {
x += dx; //Increment x
y += dy; //Increment y
dist = sqrt((x - px) * (x - px) + (y - py) * (y - py)); //Calculate distance
if(dist > max_dist){
//Exceed maximum ray distance
return -1.0f / cos(ray_angle); //-1 for failure
}
} while(!this->wall_exists(pMap, static_cast<int>(x / block), static_cast<int>(y / block)));
//Found wall
return dist;
} else {
//Right facing ray
//Calculate first intersection
x = static_cast<float>((static_cast<int>(px) / BLOCK_SIZE) * BLOCK_SIZE) + block;
y = py + (px - x) * tan(angle);
dist = sqrt((x - px) * (x - px) + (y - py) * (y - py));
if(this->wall_exists(pMap, static_cast<int>(x / block), static_cast<int>(y / block))){
//Return distance if there is a wall here
return dist;
}
//Otherwise continue
do {
x += dx; //Increment x
y += dy; //Increment y
dist = sqrt((x - px) * (x - px) + (y - py) * (y - py)); //Calculate distance
if(dist > max_dist){
//Exceed maximum ray distance
return -1.0f / cos(ray_angle); //-1 for failure
}
} while(!this->wall_exists(pMap, static_cast<int>(x / block), static_cast<int>(y / block)));
//Found wall
return dist;
}
return -1.0f / cos(ray_angle); //Could not find wall - return -1 to signify failure
}
[/CODE]

Basically what you did. Calculate the first vertical grid intersection and the first horizontal intersection, and take the smaller of the two. I broke it up into 3 cases so I wouldn't get divide by 0 errors: 0 and 180 so I only need to check vertical, 90 and 270 so I only need to check horizontal, and the others where I check both. You can find the complete source [url="https://sourceforge.net/projects/quantumengine/files/"]here[/url]. Download the latest prealpha release and look into quantumgameengine/quantumgraphics/quantumraycast/craycast.cpp

Share this post


Link to post
Share on other sites
I'm not sure if you're having a conceptual understanding problem or a technical implementation problem.

I wrote a ray tracer a few years ago for a 3D graphics class. The camera had the following properties:
Vector3 Position
Vector3 LookAt
Vector3 Up
float NearPlane, FarPlane

Then, we started with a simple scene which had a few objects (planes and spheres) and just tried to render the depth map (in black and white). Later, we added lighting and color, then shadows and reflections, then textures.

I suggest starting simple and letting your technical implementation mirror your conceptual understanding and verifying each step is consistent. Take a piece-meal approach and make sure that each peice functions as required before moving on to more complicated peices. If you do this right, then you should only have conceptual problems or technological limitations...which can be solved with more research and knowledge.

Share this post


Link to post
Share on other sites
@ Sepantium, thanks for the reply, I'll go trough your code, compare it to mine, since the general idea is the same, and check if I haven't done anything weird.

@slayemin, I think you kind of misunderstood what I am trying to achieve, I am using raycasting to create a fake 3D with a 2D engine, as was used in wolfenstein 3D, and some games after that.
I'm not trying to implement raytracing.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this