Jump to content



Raycasting (wolfenstein like 2d game)

  • You cannot reply to this topic
4 replies to this topic

#1 FictionJ   Members   -  Reputation: 100

Like
0Likes
Like

Posted 19 February 2012 - 12:10 PM

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:
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();
}

Tick Function:
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);
}
}

Horizontal and vertical rays:
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;
   }
  }
}
}


Ad:

#2 Sepiantum   Members   -  Reputation: 102

Like
0Likes
Like

Posted 19 February 2012 - 09:01 PM

tldr, but here's what I did:

//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
}

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 here. Download the latest prealpha release and look into quantumgameengine/quantumgraphics/quantumraycast/craycast.cpp
Follow and support my game engine (still in very basic development)? Link

#3 slayemin   Members   -  Reputation: 579

Like
0Likes
Like

Posted 20 February 2012 - 11:16 AM

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.
Eric Nevala
Hobby: Game Developer
Currently employed as: Sr. Sharepoint Developer in Afghanistan
Former Marine, two tours in Iraq

#4 FictionJ   Members   -  Reputation: 100

Like
0Likes
Like

Posted 20 February 2012 - 05:05 PM

@ 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.

#5 slayemin   Members   -  Reputation: 579

Like
0Likes
Like

Posted 21 February 2012 - 11:28 AM

Oh, my apologies. You're right. I misread what you wrote.
Eric Nevala
Hobby: Game Developer
Currently employed as: Sr. Sharepoint Developer in Afghanistan
Former Marine, two tours in Iraq






We are working on generating results for this topic
PARTNERS