0

Most of You interested in Raycasting probably know the Lodev and Permadi tutorials: https://lodev.org/cgtutor/raycasting2.html

https://permadi.com/1996/05/ray-casting-tutorial-11/

At first I implemented so called "vertical floor/ceiling" raycast, it continues drawig column by column wall routine, it just starst drawing floors when the wall is done, that optimized thinking, but the algorithm itself is very, very slow.

So I tried Lodevs "horizontal floor/ceiling" raycast and it was huuuuge difference and speed up.. everything would be OK, but this algorithm, despite that is fast, wastes performance on filling up the whole screen with floor and ceiling, and after that it draws walls.

I would like to optimize that feature, so the floor and ceiiling would be drawn after walls are drawn and fill only the empty spaces.

Maybe the solution would be to remember blank spaces during wall casting, and then create array containing that x, y coords, so during floor and ceil casting we already know where to draw.. what do you think. Do you know better aproaches, maybe some hints, learing sources, algorithms? Thanks in advance...

ps. I am using mouse to look around, so the horizon is changing.

I am developing on Windows but pararell I am porting my code to faster Amigas with m68k 060/080 cpus with RTG in 320x240x32 or 640x480x32.. I got nice results so far.. so trying to optimize az much as I can everything.

Below some of my tests, and progresses...

PC <-> AMIGA (WIN UAE)

https://www.youtube.com/watch?v=hcFBPfDYZig

AMIGA, V600 080/78 Mhz - 320x240x32 no textures (sorry for quality)

https://www.youtube.com/watch?v=6dv46hT1A_Y

ryyker
  • 22,849
  • 3
  • 43
  • 87
Mateusz
  • 119
  • 10
  • Sounds like a sensible idea. Remember that if you only have one ceiling height (not like Doom) then each X coordinate can only have one wall. So you don't need a bitmap, just an array of the top and bottom coordinates of the wall. – user253751 Mar 17 '21 at 11:35
  • Yes, only one height floors and ceilings.. but I don't exactly understand your idea, could you please share more information? – Mateusz Mar 17 '21 at 12:05
  • You may be using `C`, but there is nothing relevant shown in question regarding a `C` problem. I removed the tag. – ryyker Mar 17 '21 at 12:34
  • @Mateusz why can't you just do it? Remember the blank spaces during the wall casting, and draw floors and ceilings in that space? The blank spaces can be remembered as an array of Y values indexed by X value. – user253751 Mar 17 '21 at 13:06

1 Answers1

1

Since the question is not related to any language, I answer from a Javascript perspective. I implemented the so called "vertical floor/ceiling" technique as well. But instead of drawing pixels per pixel with ctx.drawImage() I use putImageData.

First I get the data from the tiles I want to render using a temporary canvas:

var tempCanvas = document.createElement('canvas');
var tempCtx = tempCanvas.getContext('2d');

tempCanvas.width = 64;
tempCanvas.height = 64;

var wallsSprite = new Image();

wallsSprite.onload = function () {
  tempCtx.drawImage(wallsSprite, 0, 128, 64, 64, 0, 0, 64, 64);
  floorData = tempCtx.getImageData(0, 0, 64, 64);

  tempCtx.drawImage(wallsSprite, 0, 192, 64, 64, 0, 0, 64, 64);
  ceilData = tempCtx.getImageData(0, 0, 64, 64);
}

wallsSprite.src = "./walls_2.png";

I create an empty imageData:

var floorSprite = this.ctx.createImageData(600, 400);

Then I do my "vertical floor/ceiling" raycasting:

 //we check if the wall reaches the bottom of the canvas
 // this.wallToBorder = (400 - wallHeight) / 2;
        
 if (this.wallToBorder > 0) {
    
 // we calculate how many pixels we have from bottom of wall to border of canvas
 var pixelsToBottom = Math.floor(this.wallToBorder);
    
 //we calculate the distance between the first pixel at the bottom of the wall and the player eyes (canvas.height / 2) 
 var pixelRowHeight = 200 - pixelsToBottom;
         
 // then we loop through every pixels until we reach the border of the canvas  
    
 for (let i = pixelRowHeight; i < 200; i += 1) {
    
   // we calculate the straight distance between the player and the pixel
      var directDistFloor = (this.screenDist * 200) / (Math.floor(i));
    
   // we calculate it's real world distance with the angle relative to the player
      var realDistance = (directDistFloor / Math.cos(this.angleR));
    
   // we calculate it's real world coordinates with the player angle
      this.floorPointx = this.player.x + Math.cos(this.angle) * realDistance / (this.screenDist / 100);
    this.floorPointy = this.player.y + Math.sin(this.angle) * realDistance / (this.screenDist / 100);
    
    // we map the texture
            var textY = Math.floor(this.floorPointx % 64);
            var textX = Math.floor(this.floorPointy % 64);

    // we modify floorSprite array:
            if (floorData && ceilData) {
    
              floorSprite.data[(this.index * 4) + (i + 200) * 4 * 600] = floorData.data[textY * 4 * 64 + textX * 4]
              floorSprite.data[(this.index * 4) + (i + 200) * 4 * 600 + 1] = floorData.data[textY * 4 * 64 + textX * 4 + 1]
              floorSprite.data[(this.index * 4) + (i + 200) * 4 * 600 + 2] = floorData.data[textY * 4 * 64 + textX * 4 + 2]
              floorSprite.data[(this.index * 4) + (i + 200) * 4 * 600 + 3] = 255;
    
              floorSprite.data[(this.index * 4) + (200 - i) * 4 * 600] = ceilData.data[textY * 4 * 64 + textX * 4]
              floorSprite.data[(this.index * 4) + (200 - i) * 4 * 600 + 1] = ceilData.data[textY * 4 * 64 + textX * 4 + 1]
              floorSprite.data[(this.index * 4) + (200 - i) * 4 * 600 + 2] = ceilData.data[textY * 4 * 64 + textX * 4 + 2]
              floorSprite.data[(this.index * 4) + (200 - i) * 4 * 600 + 3] = 255;
            }
          }
        }
      }
    }

finally we draw the floor and ceiling before the walls are rendered:

this.ctx.putImageData(floorSprite, 0, 0);

The result is super fast since:

  • we don't need to calculate ceiling texture coordinates since we deduce them from the floor coordinates.
  • we draw the ceiling/floor only once per loop, not pixels per pixel.
  • only the pixels that are visible are redrawn so it doesn't wastes performance on filling up the whole screen with floor and ceiling, and after that it draws walls.

Maybe it could be optimized with mixing horizontal raysting and putImageData put the game speed with wall/ceiling rendering or without is almost the same.

Here is the result

Toto Briac
  • 908
  • 9
  • 29
  • Thanks for the answer. What is this.screenDist and how is it calculated? Also, it'd be good to explain some of the 200 and 100 constants..what they represent? Is the height of the screen 400? Half height 200? And is 100 1/4 height? – texta83 Apr 14 '23 at 04:11
  • Actually it'd be super helpful to see a full working code sample via a github link etc. – texta83 Apr 14 '23 at 06:34
  • 1
    repo: https://github.com/Totobriac/dino_canvas/tree/master/js/levels/9_dinoStein_level working exemple: vincentcailly.com (choose the level with the gun) – Toto Briac Apr 15 '23 at 05:40