Search code examples
javascriptcanvasframe-rateraycasting

How to render raycasted wall textures on HTML-Canvas


I am trying to build a raycasting-engine. i have successfully rendered a scene column by column using ctx.fillRect() as follows.

canvas-raycasting.png

demo code

the code i wrote for above render:

var scene = [];// this contains distance of wall from player for an perticular ray 
var points = [];// this contains point at which ray hits the wall 

/*
i have a Raycaster class which does all math required for ray casting and returns 
an object which contains two arrays 
1 > scene : array of numbers representing distance of wall from player.
2 > points : contains objects of type { x , y } representing point where ray hits the wall.
*/

var data = raycaster.cast(wall);
/* 
raycaster : instance of Raycaster class , 
walls : array of boundries constains object of type { x1 , y1 , x2 , y2 } where 
(x1,y1) represent start point ,
(x2,y2) represent end point.
*/
scene = data.scene; 

var scene_width = 800;
var scene_height = 400; 
var w = scene_width / scene.length;
for(var i=0;i<scene.length;++i){
    var c = scene[i] == Infinity ? 500 : scene[i] ;

    var s = map(c,0,500,255,0);// how dark/bright the wall should be 
    var h = map(c,0,500,scene_height,10); // relative height of wall (farther the smaller)

    ctx.beginPath();
    ctx.fillStyle = 'rgb('+s+','+s+','+s+')';
    ctx.fillRect(i*w,200-h*0.5,w+1,h);
    ctx.closePath();
}

Now i am trying to build an web based FPS(First Person Shooter) and stucked on rendering wall-textures on canvas.

ctx.drawImage() mehthod takes arguments as follows

void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

ctx.drawImage_arguments

but ctx.drawImage() method draws image as a rectangle with no 3D effect like Wolfenstein 3D

i have no idea how to do it.

should i use ctx.tranform()? if yes, How ? if no, What should i do?

I am looking for the Maths used to produce pseudo 3d effect using 2D raycasting.

some pseudo 3d games are Wolfenstein 3D Doom

i am trying to build something like this

THANK YOU : )


Solution

  • The way that you're mapping (or not, as the case may be) texture coordinates isn't working as intended.

    I am looking for the Maths used to produce pseudo 3d effect using 2D raycasting

    The Wikipedia Entry for Texture Mapping has a nice section with the specific maths of how Doom performs texture mapping. From the entry:

    The Doom engine restricted the world to vertical walls and horizontal floors/ceilings, with a camera that could only rotate about the vertical axis. This meant the walls would be a constant depth coordinate along a vertical line and the floors/ceilings would have a constant depth along a horizontal line. A fast affine mapping could be used along those lines because it would be correct.

    A "fast affine mapping" is just a simple 2D interpolation of texture coordinates, and would be an appropriate operation for what you're attempting. A limitation of the Doom engine was also that

    Doom renders vertical and horizontal spans with affine texture mapping, and is therefore unable to draw ramped floors or slanted walls.

    It doesn't appear that your logic contains any code for transforming coordinates between various coordinate spaces. You'll need to apply transforms between a given raytraced coordinate and texture coordinate spaces in the very least. This typically involves matrix math and is very common and can also be referred to as Projection, as in projecting points from one space/surface to another. With affine transformations you can avoid using matrices in favor of linear interpolation.

    The coordinate equation for this adapted to your variables (see above) might look like the following:

    u = (1 - a) * wallStart + a * wallEnd
    
    where 0 <= *a* <= 1
    

    Alternatively, you could use a Weak Perspective projection, since you have much of the data already computed. From wikipedia again:

    To determine which screen x-coordinate corresponds to a point at
    
    A_x_,A_z_
    multiply the point coordinates by:
    
    B_x = A_x * B_z / A_z
    where
    
    B_x
    is the screen x coordinate
    A_x
    is the model x coordinate
    B_z
    is the focal length—the axial distance from the camera center *to the image plane*
    A_z
    is the subject distance.
    Because the camera is in 3D, the same works for the screen y-coordinate, substituting y for x in the above diagram and equation.
    

    In your case A_x is the location of the wall, in worldspace. B_z is the focal length, which will be 1. A_z is the distance you calculated using the ray trace. The result is the x or y coordinate representing a translation to viewspace.

    The main draw routine for W3D documents the techniques used to raytrace and transform coordinates for rendering the game. The code is quite readable even if you're not familiar with C/ASM and is a great way to learn more about your topics of interest. For more reading, I would suggest performing a search in your engine of choice for things like "matrix transformation of coordinates for texture mapping", or search the GameDev SE site for similar. A specific area of that file to zero-in on would be this section starting ln 267:

    > ========================
    > =
    > = TransformTile
    > =
    > = Takes paramaters:
    > =   tx,ty     : tile the object is centered in
    > =
    > = globals:
    > =   viewx,viewy       : point of view
    > =   viewcos,viewsin   : sin/cos of viewangle
    > =   scale     : conversion from global value to screen value
    > =
    > = sets:
    > =   screenx,transx,transy,screenheight: projected edge location and size
    > =
    > = Returns true if the tile is withing getting distance
    > =
    

    A great book on "teh Maths" is this one - I would highly recommend it for anyone seeking to create or improve upon these skills.

    Update: Essentially, you'll be mapping pixels (points) from the image onto points on your rectangular wall-tile, as reported by the ray trace.

    Pseudo(ish)-code:

    var image = getImage(someImage); // get the image however you want. make sure it finishes loading before drawing
    var iWidth = image.width, iHeight = image.height;
    var sX = 0, sY = 0; // top-left corner of image. Adjust when using e.g., sprite sheets
    
    for(var i=0;i<scene.length;++i){
        var c = scene[i] == Infinity ? 500 : scene[i];
    
        var s = map(c,0,500,255,0);// how dark/bright the wall should be 
        var h = map(c,0,500,scene_height,10); // relative height of wall (farther the smaller)
        var wX = i*w, wY = 200 - h * 0.5;
        var wWidth = w + 1, wHeight = h;
    //... render the rectangle shape
        /* we are using the same image, but we are scaling it to the size of the rectangle
         and placing it at the same location as the wall. 
    
        */
        var u, v, uW, vH; // texture x- and y- values and sizes. compute these.
    
        ctx.drawImage(image, sX, sY, iWidth, iHeight, u, v, uW, vH); 
    }
    

    Since I'm not familiar with your code performing the raytrace, its' coordinate system, etc, you may need to further adjust the values for wX, wY, wWidth, and wHeight (e.g., translate points from center to top-left corner).