Are you Math smart? solve this then

You will like this code. plug it in your NETMF emulator and you will have a 3D engine! The problem is that I get divide by zero exception when drawing the very center line. Can you fix this and you will win the challenge?

// Original code was found online by an artice done by a university professor over a year ago.
// I searched the web and not finding the original sources to attribute thsi code properly
// If you are teh one who wrote this code, THANK YOU. We owe you a beer!
// porting to C#/NETMF and further improvements are copyright GHI Electronics

using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Media;

namespace engine3d
{
    public class Program
    {
        static int w = 320;//width
        static int h = 240;//height
        const int mapWidth = 24;
        const int mapHeight = 24;
        static int[] worldMap = new int[mapWidth * mapHeight]{
  2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  2,0,0,0,0,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,0,0,0,0,1,
  1,0,0,0,0,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,2,2,2,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1,
  1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1,
  1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,3,0,0,0,3,0,0,0,1,
  1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1,
  1,0,0,0,0,0,2,2,0,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1,
  1,0,0,0,0,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,0,0,0,0,1,
  1,0,0,0,0,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,0,0,0,0,1,
  1,0,0,0,0,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,0,0,0,0,1,
  1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,
  1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,
  1,4,0,0,0,0,5,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
  1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
  1,4,0,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
  1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
  1,4,4,4,4,4,4,4,4,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,1,1,1,1
};

        static Bitmap LCD = new Bitmap(320, 240);
        public static void Main()
        {

            double posX = 20, posY = 10;  //x and y start position
            double dirX = -1, dirY = 0; //initial direction vector
            double planeX = 0, planeY = 0.66; //the 2d raycaster version of camera plane

            Color color;

            while (true)
            {       
                LCD.Clear();
                for (int x = 0; x < w; x++)
                {
                    //calculate ray position and direction 
                    double cameraX = 2 * x / (double)(w) - 1; //x-coordinate in camera space
                    
                    
                    if (cameraX == 0)////////// REMOVE this to see center line but then your get divide by zero exception
                        continue;
                    
                    
                    double rayPosX = posX;
                    double rayPosY = posY;
                    double rayDirX = dirX + planeX * cameraX;
                    double rayDirY = dirY + planeY * cameraX;
                    //which box of the map we're in  
                    int mapX = (int)rayPosX;
                    int mapY = (int)rayPosY;

                    //length of ray from current position to next x or y-side
                    double sideDistX;
                    double sideDistY;

                    //length of ray from one x or y-side to next x or y-side
                    double deltaDistX = System.Math.Sqrt(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX));
                    double deltaDistY = System.Math.Sqrt(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY));
                    double perpWallDist;

                    //what direction to step in x or y-direction (either +1 or -1)
                    int stepX;
                    int stepY;

                    int hit = 0; //was there a wall hit?
                    int side = 0; //was a NS or a EW wall hit?
                    //calculate step and initial sideDist
                    if (rayDirX < 0)
                    {
                        stepX = -1;
                        sideDistX = (rayPosX - mapX) * deltaDistX;
                    }
                    else
                    {
                        stepX = 1;
                        sideDistX = (mapX + 1.0 - rayPosX) * deltaDistX;
                    }
                    if (rayDirY < 0)
                    {
                        stepY = -1;
                        sideDistY = (rayPosY - mapY) * deltaDistY;
                    }
                    else
                    {
                        stepY = 1;
                        sideDistY = (mapY + 1.0 - rayPosY) * deltaDistY;
                    }
                    //perform DDA
                    while (hit == 0)
                    {
                        //jump to next map square, OR in x-direction, OR in y-direction
                        if (sideDistX < sideDistY)
                        {
                            sideDistX += deltaDistX;
                            mapX += stepX;
                            side = 0;
                        }
                        else
                        {
                            sideDistY += deltaDistY;
                            mapY += stepY;
                            side = 1;
                        }
                        //Check if ray has hit a wall
                        if (worldMap[mapX * mapWidth + mapY] > 0) hit = 1;
                    }
                    //Calculate distance projected on camera direction (oblique distance will give fisheye effect!)
                    if (side == 0)
                        perpWallDist = System.Math.Abs((mapX - rayPosX + (1 - stepX) / 2) / rayDirX);
                    else
                        perpWallDist = System.Math.Abs((mapY - rayPosY + (1 - stepY) / 2) / rayDirY);

                    //Calculate height of line to draw on screen
                    int lineHeight = System.Math.Abs((int)(h / perpWallDist));

                    //calculate lowest and highest pixel to fill in current stripe
                    int drawStart = -lineHeight / 2 + h / 2;
                    if (drawStart < 0) drawStart = 0;
                    int drawEnd = lineHeight / 2 + h / 2;
                    if (drawEnd >= h) drawEnd = h - 1;

                    //choose wall color
                    switch (worldMap[mapX * mapWidth + mapY])
                    {
                        //select the color
                        case 1: color = ColorUtility.ColorFromRGB(200, 0, 0); break; //red
                        case 2: color = ColorUtility.ColorFromRGB(0, 200, 0); break; //green
                        case 3: color = ColorUtility.ColorFromRGB(0, 0, 200); break; //blue
                        case 4: color = ColorUtility.ColorFromRGB(200, 200, 200); break; //white
                        default: color = ColorUtility.ColorFromRGB(200, 200, 0); break; //yellow
                    }

                    //give x and y sides different brightness
                    if (side == 1)
                    {
                        color = ColorUtility.ColorFromRGB(
                        (byte)(ColorUtility.GetRValue(color)/2),
                        (byte)(ColorUtility.GetGValue(color) / 2),
                        (byte)(ColorUtility.GetBValue(color) / 2));
                    }

                    //draw the pixels of the stripe as a vertical line
                    LCD.DrawLine(color, 1, x, drawStart, x, drawEnd);//wall
                    LCD.DrawLine(ColorUtility.ColorFromRGB(200, 200, 40), 1, x, drawEnd, x, h-1);//floor
                    //LCD.Flush();//add this to see line by line!
                }

                //move forward till hit a wall
                // =========NOTE we will crash at the end but this is okay for now
                posX+= -0.1;
                
                //flush
                LCD.Flush();

            }

        }
    }
}

By the way, if you plug this on an actual board, let me know how fast it runs please.

@ Gus - This is a very common problem when working with 3D rendeding. The typical solution is to check for 0 and then assign a very small float to the value, this avoids the divide by 0 and generally does not cause much of a visual discrepancy since working with floats/double is already inaccurate. I use this same technique in the ray casting engine that I demo on the Spider.


if (cameraX == 0) cameraX = 0.00001;

1 Like

@ Gus - This is what you are aiming for :slight_smile:

http://www.tinyclr.com/forum/topic?id=7045&page=3#msg69378

It runs ok on hydra & ChipworkX; Chris’ engine is much better for the Hyrda though. :slight_smile:

My engine was running on the Spider but I cheated, I did it using RLP.

How did I forget about this! But i aiming to not use textures. Did you share the code?

I’ve got one Wouter created for me for GameSlate. Haven’t shared the source yet though and it’s RLP.

I was thinking about porting the non textured bit to Cerb with the new RLP PicoMax (now called Lucid Micro).

Which board(s) are you wanting this for?

oh man! I feel dumb :slight_smile: I sent you an email

I used Double.Epsilon and float.Epsilon for the same purpose in XNA for a double-precision physics engine.
Appears to be implemented in MF too.

Well, I had forgotten about it until I saw your post… Can’t believe it has been nearly a year since I started with FEZ, time flies when you having fun.

I did not share the code, but for no reason other than I did not move much with it after those initial demos. If I get a chance I will clean it up, update it for 4.2 and put on code share.

Definitely an option, I like to use a simple number for debugging purposes. For example, if I have a bug I can easily tell if/when this value might have affected the numbers because the scaling effect is easy to identify. But that might be more psychological than anything else :).

The Gusbot never forgets, there was probably just a power glitch. :slight_smile: