Advertisement

Raycast DDA: Wall rendering and floor rendering out of sync.

Started by December 08, 2024 02:14 PM
0 comments, last by dirt 1 week, 2 days ago

Hello y'all, im new around here, hope everything is going sweet for all of you. I come for theory help with a classic raycast algorithm (currently, based on DDA). The problem is not with the algorithm itself, but with the floor rendering code.

I left the code of my raycast render method below (actual DDA implementation its left out, i think that's working as expected). The basic idea its use PLAYER_HEIGHT (position of camera, currently set to HORIZON), and the formula PLAYER_HEIGHT/(y- HORIZON). In the canvas, the bottom pixel of y its equal to SCREEN_HEIGHT, and goes “up” until it reach HORIZON - 1 (canvas element pixels goes from 0,0/topleftcorner to screen_width,screen_height/bottomrightcorner). Thats the distance that will be used for interpolating the floor coordinates x and y according to the vector direction of the player and a camera plane perpendicular to the direction vector.

function render(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, currentTime: number) {
  const deltaTime = (currentTime - lastTime) / 1000;
  lastTime = currentTime;
  movePlayer(deltaTime);
  renderingActions(deltaTime);
  // Clear the canvas
  ctx.fillStyle = "black";
  ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

  const buffer = FLOOR_TEXTURE_IMAGE_DATA.data;
  const rayIncrement = FOV / SCREEN_WIDTH;
  const wallsArray: {wallHeight: number,distance:number}[] = [];
  const rayDirsArray: {rayDirX: number,rayDirY: number, distance: number, normRayAngle: number, wallHeight: number, side:number, wallStart:number}[] = [];
  
  const dirX = Math.cos(normalizeAngle(playerAngle));
  const dirY = Math.sin(normalizeAngle(playerAngle));
  const planeX = -dirY ;
  const planeY = dirX;

    nearPlaneX1 = dirX - planeX;
    nearPlaneX2 = dirX + planeX;
    nearPlaneY1 = dirY - planeY;
    nearPlaneY2 = dirY + planeY;
    const dotDirLeft = (dirX * nearPlaneX1 + dirY * nearPlaneY1);
    const dotDirRight = (dirX * nearPlaneX2 + dirY * nearPlaneY2);
    const angleLeft = Math.acos(dotDirLeft);
    const angleRight = Math.acos(dotDirRight);
    

    const normalizedLeftX = nearPlaneX1/Math.sqrt((nearPlaneX1*nearPlaneX1) + (nearPlaneY1*nearPlaneY1));
    const normalizedLeftY = nearPlaneY1/Math.sqrt((nearPlaneX1*nearPlaneX1) + (nearPlaneY1*nearPlaneY1));

    const normalizedRightX = nearPlaneX2/Math.sqrt((nearPlaneX2*nearPlaneX2) + (nearPlaneY2*nearPlaneY2));
    const normalizedRightY = nearPlaneY2/Math.sqrt((nearPlaneX2*nearPlaneX2) + (nearPlaneY2*nearPlaneY2));

  for (let x = 0; x < SCREEN_WIDTH; x++) {
      const rayAngle = playerAngle - FOV / 2 + x * rayIncrement;
      const {distance, rayDirX, rayDirY, normRayAngle, side} = castRay(rayAngle);
      
      // Calculate wall height once
      const wallHeight = Math.min(SCREEN_HEIGHT,
          (SCREEN_HEIGHT * WALL_SCALE) / (distance * DISTANCE_SCALE / WORLD_SCALE));
          

      const wallStart = (HORIZON - wallHeight) / 2  
      wallsArray.push({wallHeight, distance});  
      rayDirsArray.push({rayDirX, rayDirY, distance, normRayAngle, wallHeight, side, wallStart});
  }

  for(let y = SCREEN_HEIGHT ; y > HORIZON; y--) {

      const yDistance = ((HORIZON / (y - HORIZON)));
      const  floorXDirInc = yDistance*(normalizedRightX - normalizedLeftX)/ (SCREEN_WIDTH);
      const floorYDirInc =  yDistance*(normalizedRightY - normalizedLeftY) / (SCREEN_WIDTH);
      floorYStart = worldToGrid(playerY) + yDistance*(normalizedLeftY);
      floorXStart = worldToGrid(playerX) + yDistance*(normalizedLeftX);


      for(let x = 0; x < SCREEN_WIDTH; x++){

        const cellX = Math.floor(Math.abs(floorXStart));
        const cellY = Math.floor(Math.abs(floorYStart));


        floorXStart+= floorXDirInc;
        floorYStart+=floorYDirInc;


        const pixelDestIndex = (y * SCREEN_WIDTH + x) * 4;

        if (debug) {
          console.log(`FloorStartX ${floorXStart} FloorStartY ${floorYStart}`)
          console.log(`CellX ${cellX} CellY ${cellY}`)
          console.log(`WORLD posX ${playerX} posY${playerY} GRID posX ${worldToGrid(playerX)} posY ${worldToGrid(playerY)}`)
          console.log(`FloorXInd ${floorXDirInc} FloorYInc ${floorYDirInc}`)
          console.log(`MapRatio ${mapRatio} MapAcc ${mapAcc}`)
          debug = false;
        }
        if((cellX + cellY) % 2){

            buffer[pixelDestIndex] = 0;     // Using fixed color for testing
            buffer[pixelDestIndex + 1] = 0;
            buffer[pixelDestIndex + 2] = 0;
            buffer[pixelDestIndex + 3] = 255;
        }else
            buffer[pixelDestIndex] = 255;     // Using fixed color for testing
            buffer[pixelDestIndex + 1] = 255;
            buffer[pixelDestIndex + 2] = 255;
            buffer[pixelDestIndex + 3] = 255;
        
      }
  }
  ctx.putImageData(FLOOR_TEXTURE_IMAGE_DATA, 0,0);
  if(RENDER_WALLS)
    wallsArray.forEach((wallProperties, index) => {
      const brightness = Math.max(255 - (wallProperties.distance * DISTANCE_SCALE / WORLD_SCALE) *  BRIGHT_FACTOR, 0);
      ctx.fillStyle = `rgb(${brightness}, 0, 0)`;
      ctx.fillRect(index, (SCREEN_HEIGHT - wallProperties.wallHeight) / 2, 1, wallProperties.wallHeight);
    })
  requestAnimationFrame((time) => render(canvas, ctx, time));
}             

Here i left images of how the floor looks:



The main problem its in my rendering floor technique. New tiles “appears” below the walls. The desired effect would be, if there is a wall in a map position (7,4) the floor tiles around that should be always the same. But right now, as the player moves, “new” tiles slide from behind the wall, so depending of the player position and angle, near to (7,4) the floor tile may be appear white or blue, so its not “sync”.

I think the problem its the distance of the wall i get using DDA and the distance related to the difference between the current row in the screen space and my HORIZON its different, they are not “sync” and its normal because the are two different “types” of distances? I'm lost here. Thank you all for taking your time with this.

Advertisement