Click here to Skip to main content
15,886,919 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Hi, I' m pretty new to C++ and OpenGL and was coding a C++ OpenGL version of the 2d Raycasting Challenge from the Coding Train YouTube channel(ep.145). The entire thing seems to work well except for one thing. In the project whenever the 0 deg ray (the ray which points directly to the right) doesn't cast itself i.e. it doesn't touch a boundary(wall), a random ray appears out of nowhere which goes on till infinity.
Here is the code so that you can see this.

main.cpp
C++
#include "Raycasting.hpp"
#include <iostream>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <cmath>

void framebuffer_size_callback(GLFWwindow* window, int w, int h);

int width, height;
std::vector<Boundary> walls;
Particle particle = Particle(width, height);
double mx, my;
int main()
{
  srand(time(NULL));
  GLFWwindow* window;
  if (!glfwInit())
    return 1;
  width  = 600;
  height = 600;
  window = glfwCreateWindow(width, height, "Window", NULL, NULL);
  if (!window) {
    glfwTerminate();
    return 1;
  }
  for(int i = 0; i < 5; i++){
    walls.push_back(Boundary(rand()%width, rand()%width, rand()%height, rand()%height));
  }
  glfwMakeContextCurrent(window);
  if(glewInit()!=GLEW_OK)
    std::cout<<"Error"<<std::endl;
  glEnable(GL_DEPTH_TEST);
  glMatrixMode(GL_PROJECTION);
  glfwGetFramebufferSize(window, &width, &height);
  glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
  Particle particle = Particle(width, height);
  glOrtho(0, width*(width/height), height, 0, -2, 2);
  while(!glfwWindowShouldClose(window)) {
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    glClearColor(0.11, 0.14, 0.17, 1);
    glfwGetCursorPos(window, &mx, &my);
    for(Boundary &wall : walls)
      wall.show();
    particle.update((float)mx, (float)my);
    particle.show();
    particle.look(walls);
    glfwSwapBuffers(window);
    glfwPollEvents();
  }
  glfwTerminate();
}

void framebuffer_size_callback(GLFWwindow* window, int w, int h)
{
  //float ratio = width/height;
  width = w;
  height = h;
  float ratio = width/height;
  if(width<height)
    ratio = height/width;
  else
    ratio = width/height;
  //std::cout<<"Width: "<<width<<" "<<"Height: "<<height<<std::endl;
  glViewport(0,0,width,height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if(width>height)
    glOrtho(0, width*ratio, height, 0, -2, 2);
  else
    glOrtho(0, width, height*ratio, 0, -2, 2);
}

Raycasting.hpp
C++
#ifndef RAYCASTING
#define RAYCASTING

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <cmath>
#include <vector>
/**DRAWING FUNCTIONS, no need to check these**/
void DrawEllipse(float cx, float cy, float rx, float ry, int num_segments) 
{ 
    float theta = 2 * 3.1415926 / float(num_segments); 
    float c = cosf(theta);//precalculate the sine and cosine
    float s = sinf(theta);
    float t;

    float x = 1;//we start at angle = 0 
    float y = 0; 

    glBegin(GL_LINE_LOOP); 
    for(int ii = 0; ii < num_segments; ii++) 
    { 
        //apply radius and offset
        glVertex2f(x * rx + cx, y * ry + cy);//output vertex 

        //apply the rotation matrix
        t = x;
        x = c * x - s * y;
        y = s * t + c * y;
    } 
    glEnd(); 
}


void stroke(int value){
    float v = (float)value/255;
    glColor3f(v,v,v);
}

void line(float x1, float y1, float x2, float y2){
    glBegin(GL_LINES);
    glVertex2f(x1,y1);
    glVertex2f(x2,y2);
    glEnd();
}

void ellipse(float x, float y, float width, float height){
    DrawEllipse(x, y, width/2, height/2, 10800);
}
/**END OF DRAWING FUNCTIONS**/


class V2D{
public:
    V2D(){}
    V2D(float _x, float _y){
        x = _x;
        y = _y;
    }
    V2D(long int n){
        isNull = true;
    }
    bool isNull = false;
    float x;
    float y;
    float magnitude(){
        return sqrt(x*x + y*y);
    }
    void normalize(){
        float mag = magnitude();
        x /= mag;
        y /= mag;
    }
};

float dist(V2D start, V2D end){
    return sqrt((start.x-end.x)*(start.x-end.x) + (start.y-end.y)*(start.y-end.y));
}

class Boundary{
public:
    Boundary(float x1, float y1, float x2, float y2){
        a = V2D(x1, y1);
        b = V2D(x2, y2);
    }
    V2D a,b;
    void show(){
        stroke(255);
        line(a.x, a.y, b.x, b.y);
    }
};

class Ray{
public:
    Ray(V2D _pos, float angle){
        pos = _pos;
        dir = V2D(cos(angle), sin(angle));
    }
    V2D pos,dir;
    void lookAt(float x, float y){
        dir.x = x - pos.x;
        dir.y = y - pos.y;
        dir.normalize();

    }
    void show(){
        stroke(255);
        line(pos.x, pos.y, pos.x + dir.x * 10, pos.y + dir.y * 10);
    }
    V2D cast(Boundary wall){
        const float x1 = wall.a.x;
        const float y1 = wall.a.y;
        const float x2 = wall.b.x;
        const float y2 = wall.b.y;

        const float x3 = pos.x;
        const float y3 = pos.y;
        const float x4 = pos.x + dir.x;
        const float y4 = pos.y + dir.y;

        const float den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
        if(den==0){
            return V2D(NULL);
        }
        const float t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den;
        const float u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den;
        if(t > 0 && t < 1 && u > 0){
            float X = x1 + t * (x2 - x1);
            float Y = y1 + t * (y2 - y1);
            return V2D(X, Y);
        }
        else
            return V2D(NULL);
    }
};

class Particle{
public:
    Particle(int width, int height){
        pos = V2D(width/2, height/2);
        rays = {};
        for(int i = 0; i < 360; i+=1){
            rays.push_back(Ray(pos, i*M_PI/180));
        }
    }
    V2D pos;
    std::vector<Ray> rays = {};
    void update(float x, float y){
        for(Ray &ray : rays){
            ray.pos = V2D(x,y);
        }
    }
    void show(){
        for(int i = 0; i <rays.size(); i++){
            rays[i].show();
        }
    }
    void look(std::vector<Boundary> walls){
        for(int i = 0; i < rays.size(); i++){
            V2D closest;
            float record = 100000;
            for(Boundary &wall : walls){
                V2D pt = rays[i].cast(wall);
                if(!pt.isNull){
                    const float d = dist(rays[i].pos, pt);
                    if(d < record){
                        record = d;
                        closest = pt;
                    }
                }  
            }
            if(!closest.isNull){
                line(rays[i].pos.x, rays[i].pos.y, closest.x, closest.y);
            }
        }
    }
};

#endif

Makefile
main.o: main.cpp
	g++ main.cpp -lglfw -I/usr/include/libdrm -L/usr/lib64 -lGLEW -lGLU -lGL -o app


What I have tried:

I do not know what to do in particular, but any sort of help/explanation will be thoroughly appreciated
Posted
Updated 15-Jul-19 0:20am
v2

1 solution

This is just a guess, as the code is too long to fully analyze, but judging by your problem description this is just a corner case, and there is a piece of code in Ray::cast() that is likely to cause issues in corner cases:
C++
const float den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if(den==0){
I suspect the calculation of den suffers from rounding error effects, and may not be exactly 0, even if it should be so mathematically. Instead you'll get a value of 0.123e-8 or similar, and then, when you try to divde by that number you suddenly get huge values, resulting in the 'infinite' line you've observed.

Try replacing that if condition with something like
C++
const float epsilon=1e-6;
if(std::abs(den)<epsilon)


P.S. another shot in the dark:
In function Ray::look(), you do not initialize the variable closest. If your ray doesn't hit any of the walls, you call closest.IsNull(), but the result may be undefined when it isn't initialized. You might end up drawing a line with an undefined end point.
 
Share this answer
 
v2
Comments
Member 14074655 15-Jul-19 7:34am    
@Stefan_Lang Sorry I didn't think about that float thing. However this doesn't unfortunately remove that extra line that comes from nowhere. The major tell is that if the 0 deg ray doesn't cast on a wall, then this error occurs.
Stefan_Lang 15-Jul-19 7:36am    
See my P.S. statement: you forgot to initialize 'closest' in 'Ray::look()'
Member 14074655 15-Jul-19 7:42am    
Yes!! That works, Thank you so much!

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900