6

I am building a ray-casting engine in JavaScript using the canvas, I've built the ray-caster and its fully working, except for one problem, there is a fish-eye effect, I tried doing corrections

var d = (Math.sqrt( Math.pow((rays[i].x - rays[i].newX),2 )+Math.pow((rays[i].y - rays[i].newY),2 )))

// my attempted corection
var correctedDis = d * Math.cos((rays[i].rayAngle - player.rot)) 

But it doesn't yield any results :

The raycaster when i run it

I double checked the math, and i didn't see any problems, but i may still be doing this wrong because i am in seventh grade and have not learned much about trigonometry yet,(only took the khan academy course) so can someone let me know if there's a better or easier way to do it, Thank you in advance.

(I also think i may be doing the shading wrong, if anyone can help with that.)

EDIT: I forgot to include you can use the WASD keys to control the demo, and the image is flipped but that will be an easy fix. My full code if you need it:

const scale = (num, in_min, in_max, out_min, out_max) => {
  return (num - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

tex_stone = document.getElementById("stone")
c3 = document.getElementById("c3");
c = document.getElementById("c");
w = c.width;
h = c.height;
ctx = c.getContext("2d");
fctx = c3.getContext("2d")
var tilesize = 30;
var walls = [];
var rays = [];
rayangle = 0.5;
lowrayangle = -0.5;

var player = {
  x: 80,
  y: 80,
  size: 10,
  speed: 1.3,
  dir: 0,
  rot: 0,
  rotSpeed: 0.05,
  rotDir: 0,
}

map = [];



for (y = 0; y < map.length; y++) {
  for (x = 0; x < map[y].length; x++) {

    ctx.fillStyle = ["white", "black"][map[y][x]];
    ctx.fillRect(x * tilesize, y * tilesize, tilesize, tilesize);


  }
}





function keydown(event) {
  switch (event.keyCode) {
    case 87:
      player.dir = 1;
      break;
    case 83:
      player.dir = -1;
      break;
    case 68:
      player.rotDir = -1;
      break;
    case 65:
      player.rotDir = 1;
      break;
  }

}

function keyup(event) {
  switch (event.keyCode) {
    case 87:
      player.dir = 0;
      break;
    case 83:
      player.dir = 0;
      break;
    case 68:
      player.rotDir = 0;
      break;
    case 65:
      player.rotDir = 0;
      break;
  }

}

window.requestAnimationFrame(update)

function update() {
  if (player.rot == 6.28319) {
    player.rot = 0;
  }
  var map = [
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],

  ]

  fctx.clearRect(0, 0, w, h)

  ctx.clearRect(0, 0, w, h);
  rays = [];
  //move


  rotstep = player.rotSpeed * player.rotDir
  movestep = player.speed * player.dir;
  player.rot += rotstep;
  rayangle += rotstep;
  lowrayangle += rotstep;
  newY = player.y - Math.cos(player.rot) * movestep;
  newX = player.x - Math.sin(player.rot) * movestep;

  leftbY = (player.y - 10) - Math.cos(player.rot) * movestep;
  leftbX = (player.x - 10) - Math.sin(player.rot) * movestep;
  rightbY = (player.y + 10) - Math.cos(player.rot) * movestep;
  rightbX = (player.x + 10) - Math.sin(player.rot) * movestep;



  //borders 
  var leftcol = Math.floor((leftbX) / tilesize)
  var leftrow = Math.floor((leftbY) / tilesize)
  var rightcol = Math.floor((rightbX) / tilesize)
  var rightrow = Math.floor((rightbY) / tilesize)
  if (map[rightcol][rightrow] == 0 && map[leftcol][leftrow] == 0) {
    player.y = newY;
    player.x = newX;
  }

  //map
  for (y = 0; y < map.length; y++) {
    for (x = 0; x < map[y].length; x++) {

      ctx.fillStyle = ["white", "black"][map[y][x]];
      ctx.fillRect(x * tilesize, y * tilesize, tilesize, tilesize);

    }
  }



  //rays  

  for (i = lowrayangle; i < rayangle; i += 0.01) {

    var detail = 0.5;
    var rayLn = 1000;
    var memX;
    var memY;
    for (j = 0; j < rayLn; j += detail) {
      var lY = player.y - Math.cos(i) * j;
      var lX = player.x - Math.sin(i) * j;

      memX = lX;
      memY = lY;

      lX = lX - (lX % tilesize);
      lY = lY - (lY % tilesize);

      if (map[lY / tilesize][lX / tilesize] == 1) {
        break;
      }
    }

    var ray = {
      rayAngle: i,
      newY: memY,
      newX: memX,
      x: player.x,
      y: player.y
    }
    rays.push(ray)
  }

  for (i = 0; i < rays.length; i++) {
    if (rays[i].rayAngle == 6.28319) {
      rays[i].rayAngle = 0;
    }
  }
  for (i = 0; i < rays.length; i++) {
    ctx.beginPath();
    ctx.moveTo(rays[i].x, rays[i].y)
    ctx.lineTo(rays[i].newX, rays[i].newY)
    ctx.stroke();

    var d = (Math.sqrt(Math.pow((rays[i].x - rays[i].newX), 2) + Math.pow((rays[i].y - rays[i].newY), 2)))

    // my attempted corection
    var correctedD = d * Math.cos((rays[i].rayAngle - player.rot))

    var rayW = w / rays.length
    var light = scale(correctedD, 0, 700, 255, 0)
    var threeDRayHeight = scale(correctedD, 0, 700, h, 10)
    fctx.fillStyle = 'rgb(' + light + ',' + light + ',' + light + ')';
    fctx.fillRect(i * rayW, 200 - threeDRayHeight * 0.5, rayW + 1, threeDRayHeight)
    fctx.fillStyle = "blue"
    fctx.fillRect(i * rayW, (200 - threeDRayHeight * 0.5) + threeDRayHeight, rayW + 1, 1000)
    fctx.fillStyle = "red"
    fctx.fillRect(i * rayW, (200 - threeDRayHeight * 0.5) - 1000, rayW + 1, 1000)


  }
  //player
  markerY = player.y - Math.cos(player.rot) * 30;
  markerX = player.x - Math.sin(player.rot) * 30;
  ctx.fillStyle = "red";
  ctx.beginPath();
  ctx.ellipse(player.x, player.y, player.size, player.size, player.rot * (Math.PI / 180), 0, 2 * Math.PI);
  ctx.fill();

  window.requestAnimationFrame(update)
}

document.addEventListener("keydown", function(event) {
  keydown(event);
})
document.addEventListener("keyup", function(event) {
  keyup(event);
})
#c3 {
  background-color: black;
}
<img src="https://i.pinimg.com/originals/16/e1/6b/16e16b80294d07b81cfafaefdf1d2909.jpg" width=4 00px; style="display: none;" id="stone">
<canvas width="600px" height="450px" id="c3"></canvas>
<canvas width="600px" height="450px" id="c"></canvas>
Ryan Grube
  • 185
  • 1
  • 11
  • 2
    For others looking at the demo, you can use the AWSD keys to move around. This is neat! – Sean Feb 03 '21 at 16:41
  • 2
    The image seems to be horizontally flipped, too. – Sean Feb 03 '21 at 16:42
  • 1
    I'm looking at the width of the vertical columns. I notice a point on a wall far from the camera has the same on-screen width as a point on a near wall. The far columns should be more narrow. That's why roads look like they disappear into a point - the further away stuff, while still the same size, occupies a smaller portion of our field of view. I'll nab the code and have a look for any surprises. – enhzflep Feb 03 '21 at 17:09
  • 2
    You might like to take a look at the article found here: https://lodev.org/cgtutor/raycasting.html It talks about the fisheye effect, the reason behind it and a fix. I've just tried implementing the flat-shading code in javascript and the results look fine – enhzflep Feb 07 '21 at 06:04

0 Answers0