Saturday, March 3, 2012

Projecting a 3D world co-ordinates into 2D perspective projection Algorithm in c# windows form with source code

We imagine the object in 3D co-ordinates but drawing these object on computer screen we must have to do 2D screen projection. There are different types of projection like parallel ,oblique ,perspective and here I will show you perspective projection. I will try to describe all the procedure that transforms points in 3 dimensional space to screen coordinates given a particular coordinate system, camera and projection plane models.This discussion describes the mathematics required for a perspective projection including clipping to the projection pyramid with a front and back cutting plane. Here projection plane is perpendicular to the view direction vector so,oblique projection is not allowed.

3D Co-ordinates System
It has the positive x-axis to the right, the positive z-axis upward, and the positive y-axis forward on the screen and origin is in middle.



Conversion between this and other coordinate systems simply involves the swapping and/or negation of the appropriate coordinates..
Class for representing 3d co-ordinates is given below
3Dpoint.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Graphics3Dto2D
{
    class _3Dpoint
    {
        public double x, y, z;
        public _3Dpoint(double xx, double yy, double zz)
        {
            x = xx;
            y = yy;
            z = zz;
        }
        public _3Dpoint()
        {
            x = 0;
            y = 0;
            z = 0;
        }
    }
}



Camera Model

The camera is fundamentally defined by its position (from), a point along the positive view direction vector (to), a vector defining "up" (up), and a horizontal and vertical aperture (angleh, anglev). You may confuse with up vector,if your eyes are camera then your direction of head is up vector so view direction must not be collinear with the up vector.


Other somewhat artificial variables in the camera model used here are front and back clipping planes, a perspective/oblique projection flag, and a multiplicative zoom factor. The clipping planes are defined as positive distances along the view direction vector, in other words they are perpendicular to the view direction vector. As expected all geometry before the front plane and beyond the back plane is not visible. All geometry which crosses these planes is clipped to the appropriate plane. Thus geometry visible to a camera as described here lies within a truncated pyramid.


Class for Camera Model is given below

CAMERA.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Graphics3Dto2D
{
    class CAMERA
    {
       public _3Dpoint from;
       public _3Dpoint to;
       public _3Dpoint up;
       public double angleh, anglev;
       public double zoom;
       public double front, back;
       public short projection;
       public CAMERA()
        {
            from = new _3Dpoint(0,-50,0);
            to = new _3Dpoint(0,50,0);
            up = new _3Dpoint(0,0,1);
            angleh = 45.0;
            anglev = 45.0;
            zoom = 1.0;
            front = 1.0;
            back = 200.0;
            projection = 0;
 
        }
    }
}



Screen Model

Actually the screen model means drawing windows rather than whole computer screen. I have used windows form application in c# for drawing lines after converting 3D point into 2D point. I have used 800*800 drawing windows although form size is equal to the computer screen or simply we can say windows form state is maximizes. Line outside the 800*800 window is cliped.


class for 2D point is given below

2Dpint.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Graphics3Dto2D
{
    class _2Dpoint
    {
        public int h, v ;
        public _2Dpoint(int hh, int vv)
        {
            h = hh;
            v = vv;
        }
 
        public _2Dpoint()
        {
            // TODO: Complete member initialization
            h = 0;
            v = 0;
        }
    }
}

Now lets discuss about projection screen.Here the central point, width and height are used. The following will further assume the unfortunate convention, common in computer graphics practice, that the positive vertical axis is downward. The coordinates of the projection space will be referred to as (h,v).

Normally in computer windowing systems the window area is defined as an rectangle between two points (left,top) and (right,bottom).My form size is width=1440 ,height= 870 , left=320 ,top=20,right=1120 and bottom=820.

Now lets calculate the parameter of screen.

horizontal center = (left + right) / 2=720
vertical center = (top + bottom) / 2=420
width = right - left=800
height = bottom - top=800
The units need not be specified although they are generally pixel's, it is assumed that there are drawing routines in the same units. It is also assumed that the computer screen has a 1:1 aspect ratio, a least as far as the drawing routines are concerned
A relationship could be made between the ratio of the horizontal and vertical camera aperture and the horizontal and vertical ratio of the display area. Here it will be assumed that the display area (eg: window) has the same proportions as the ratio of the camera aperture. In practice this simply means that when the camera aperture is modified, the window size is also modified so as to retain the correct proportions.
Class for representing drawing screen is given below

SCREEN.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Graphics3Dto2D
{
    class SCREEN
    {
       public _2Dpoint center;
       public _2Dpoint size;
        public SCREEN()
        {
            center = new _2Dpoint(720,420);
            size = new _2Dpoint(800, 800);
        }
    }
}


Projection Algorithm

Transforming a line segment involves determining which piece, if any, of the line segment intersects the view volume. The logic and algorithm is shown below



Class for implementing Projection algorithm is given below

projection.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
//using System.Math; 
namespace Graphics3Dto2D
{
    class projection
    {
        private _3Dpoint origin;
        private _3Dpoint e1, e2, n1, n2;
        private CAMERA camera;
        private SCREEN screen;
        private double tanthetah, tanthetav;
        private _3Dpoint basisa, basisb, basisc;
        private double EPSILON;
        private double DTOR;// 0.01745329252
        public _2Dpoint p1;
        public _2Dpoint p2;
        public projection()
        {
            EPSILON = 0.001;
            DTOR = 0.01745329252;
            camera = new CAMERA();
            screen = new SCREEN();
            origin = new _3Dpoint();
            basisa = new _3Dpoint();
            basisb = new _3Dpoint();
            basisc = new _3Dpoint();
            p1 = new _2Dpoint();
            p2 = new _2Dpoint();
            
            e1 = new _3Dpoint();
            e2 = new _3Dpoint();
            n1 = new _3Dpoint();
            n2 = new _3Dpoint();
            if (Trans_Initialise() != true)
            {
                MessageBox.Show("Error in initializing variable");
            }
        }
 
        public bool Trans_Initialise()
        {
         
          
           /* Is the camera position and view vector coincident ? */
           if (EqualVertex(camera.to, camera.from))
           {
               return (false);
           }
 
           /* Is there a legal camera up vector ? */
           if (EqualVertex(camera.up, origin))
           {
               return (false);
           }
 
           basisb.x = camera.to.x - camera.from.x;
           basisb.y = camera.to.y - camera.from.y;
           basisb.z = camera.to.z - camera.from.z;
           Normalise(basisb);
 
           basisa= CrossProduct(camera.up, basisb);
           Normalise(basisa);
 
           /* Are the up vector and view direction colinear */
           if (EqualVertex(basisa, origin))
           {
               return (false);
           }
 
           basisc=CrossProduct(basisb, basisa);
 
           /* Do we have legal camera apertures ? */
           if (camera.angleh < EPSILON || camera.anglev < EPSILON)
           {
               return (false);
           }
 
           /* Calculate camera aperture statics, note: angles in degrees */
           tanthetah = Math.Tan(camera.angleh * DTOR / 2);
           tanthetav = Math.Tan(camera.anglev * DTOR / 2);
 
           /* Do we have a legal camera zoom ? */
           if (camera.zoom < EPSILON)
           {
               return (false);
           }
 
           /* Are the clipping planes legal ? */
           if (camera.front < 0 || camera.back < 0 || camera.back <= camera.front)
           {
               return (false);
           }
 
           return true;
        }
 
 
        public void Trans_World2Eye(_3Dpoint w, _3Dpoint e)
        {
            /* Translate world so that the camera is at the origin */
            w.x -= camera.from.x;
            w.y -= camera.from.y;
            w.z -= camera.from.z;
 
            /* Convert to eye coordinates using basis vectors */
            e.x = w.x * basisa.x + w.y * basisa.y + w.z * basisa.z;
 
            e.y = w.x * basisb.x + w.y * basisb.y + w.z * basisb.z;
            e.z = w.x * basisc.x + w.y * basisc.y + w.z * basisc.z;
        }
 
        public bool Trans_ClipEye(_3Dpoint e1, _3Dpoint e2)
        {
            double mu;
 
            /* Is the vector totally in front of the front cutting plane ? */
            if (e1.y <= camera.front && e2.y <= camera.front)
            {
 
                return (false);
            }
 
            /* Is the vector totally behind the back cutting plane ? */
            if (e1.y >= camera.back && e2.y >= camera.back)
            {
 
                return (false);
            }
 
            /* Is the vector partly in front of the front cutting plane ? */
            if ((e1.y < camera.front && e2.y > camera.front) ||
               (e1.y > camera.front && e2.y < camera.front))
            {
 
                mu = (camera.front - e1.y) / (e2.y - e1.y);
                if (e1.y < camera.front)
                {
                    e1.x = e1.x + mu * (e2.x - e1.x);
                    e1.z = e1.z + mu * (e2.z - e1.z);
                    e1.y = camera.front;
                }
                else
                {
                    e2.x = e1.x + mu * (e2.x - e1.x);
                    e2.z = e1.z + mu * (e2.z - e1.z);
                    e2.y = camera.front;
                }
            }
            /* Is the vector partly behind the back cutting plane ? */
            if ((e1.y < camera.back && e2.y > camera.back) ||
               (e1.y > camera.back && e2.y < camera.back))
            {
                mu = (camera.back - e1.y) / (e2.y - e1.y);
                if (e1.y < camera.back)
                {
                    e2.x = e1.x + mu * (e2.x - e1.x);
                    e2.z = e1.z + mu * (e2.z - e1.z);
                    e2.y = camera.back;
                }
                else
                {
                    e1.x = e1.x + mu * (e2.x - e1.x);
                    e1.z = e1.z + mu * (e2.z - e1.z);
                    e1.y = camera.back;
                }
            }
 
            return (true);
        }
        public void Trans_Eye2Norm(_3Dpoint e, _3Dpoint n)
        {
            double d;
 
            if (camera.projection == 0)
            {
 
                d = camera.zoom / e.y;
                n.x = d * e.x / tanthetah;
                n.y = e.y; 
                n.z = d * e.z / tanthetav;
                
            }
            else
            {
                
                n.x = camera.zoom * e.x / tanthetah;
                n.y = e.y;
                n.z = camera.zoom * e.z / tanthetav;
            }
        }
        public bool Trans_ClipNorm(_3Dpoint n1, _3Dpoint n2)
        {
            double mu;
 
            /* Is the line segment totally right of x = 1 ? */
            if (n1.x >= 1 && n2.x >= 1)
                return (false);
            
 
            /* Is the line segment totally left of x = -1 ? */
            if (n1.x <= -1 && n2.x <= -1)
                return (false);
           
 
            /* Does the vector cross x = 1 ? */
            if ((n1.x > 1 && n2.x < 1) || (n1.x < 1 && n2.x > 1))
            {
 
                mu = (1 - n1.x) / (n2.x - n1.x);
                if (n1.x < 1)
                {
                    n2.z = n1.z + mu * (n2.z - n1.z);
                    n2.x = 1;
                }
                else
                {
                    n1.z = n1.z + mu * (n2.z - n1.z);
                    n1.x = 1;
                }
            }
 
            /* Does the vector cross x = -1 ? */
            if ((n1.x < -1 && n2.x > -1) || (n1.x > -1 && n2.x < -1))
            {
 
                mu = (-1 - n1.x) / (n2.x - n1.x);
                if (n1.x > -1)
                {
                    n2.z = n1.z + mu * (n2.z - n1.z);
                    n2.x = -1;
                }
                else
                {
                    n1.z = n1.z + mu * (n2.z - n1.z);
                    n1.x = -1;
                }
            }
 
            /* Is the line segment totally above z = 1 ? */
            if (n1.z >= 1 && n2.z >= 1)
                return (false);
            
 
            /* Is the line segment totally below z = -1 ? */
            if (n1.z <= -1 && n2.z <= -1)
                return (false);
           
            /* Does the vector cross z = 1 ? */
            if ((n1.z > 1 && n2.z < 1) || (n1.z < 1 && n2.z > 1))
            {
 
                mu = (1 - n1.z) / (n2.z - n1.z);
                if (n1.z < 1)
                {
                    n2.x = n1.x + mu * (n2.x - n1.x);
                    n2.z = 1;
                }
                else
                {
                    n1.x = n1.x + mu * (n2.x - n1.x);
                    n1.z = 1;
                }
            }
 
            /* Does the vector cross z = -1 ? */
            if ((n1.z < -1 && n2.z > -1) || (n1.z > -1 && n2.z < -1))
            {
 
                mu = (-1 - n1.z) / (n2.z - n1.z);
                if (n1.z > -1)
                {
                    n2.x = n1.x + mu * (n2.x - n1.x);
                    n2.z = -1;
                }
                else
                {
                    n1.x = n1.x + mu * (n2.x - n1.x);
                    n1.z = -1;
                }
 
            }
 
            return (true);
        }
        public void Trans_Norm2Screen(_3Dpoint norm, _2Dpoint projected)
        {
            //MessageBox.Show("the value of  are");
            projected.h = Convert.ToInt32(screen.center.h - screen.size.h * norm.x / 2);
            projected.v = Convert.ToInt32(screen.center.v - screen.size.v * norm.z / 2);
           
        }
        //public bool Trans_Point();
        public bool Trans_Line(_3Dpoint w1, _3Dpoint w2)
        {
            
 
 
            Trans_World2Eye(w1, e1);
            Trans_World2Eye(w2, e2);
            if (Trans_ClipEye(e1, e2))
            {
                Trans_Eye2Norm(e1, n1);
                Trans_Eye2Norm(e2, n2);
                if (Trans_ClipNorm(n1, n2))
                {
                    Trans_Norm2Screen(n1, p1);
                    Trans_Norm2Screen(n2, p2);
                    return (true);
                }
               
            }
           
            return (true);
        }
        public void Normalise(_3Dpoint v)
        {
            double length;
 
            length = Math.Sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
            v.x /= length;
            v.y /= length;
            v.z /= length;
        }
        public _3Dpoint CrossProduct(_3Dpoint p1,_3Dpoint p2)
        {
            _3Dpoint p3;
            p3 = new _3Dpoint(0,0,0);
            p3.x = p1.y * p2.z - p1.z * p2.y;
            p3.y = p1.z * p2.x - p1.x * p2.z;
            p3.z = p1.x * p2.y - p1.y * p2.x;
            return p3;
        }
        public bool EqualVertex(_3Dpoint p1, _3Dpoint p2)
        {
             if (Math.Abs(p1.x - p2.x) > EPSILON)
             return(false);
             if (Math.Abs(p1.y - p2.y) > EPSILON)
             return(false);
             if (Math.Abs(p1.z - p2.z) > EPSILON)
             return(false);
 
             return(true);
        }
            
    }
 
}


Clipping

Two separate clipping processes occur. The first is clipping to the front and back clipping planes and is done after transforming to eye coordinates. The second is clipping to the view pyramid and is performed after transforming to normalized coordinates at which point it is necessary to clip 2D line segments to a square centered at the origin of length and height of 2.


Drawing Objects using lines

Here, for example showing implementation of above algorithm  I have drawn wired frame cube.For drawing one line we need two points and for drawing wired frame cube we need 12 lines or 24 points but I have used 16 lines or 32 points to draw.

Windows form application in c# is used for drawing these lines.I assume that properties of your window state is in maximized.

Source code is given below

Form1.cs


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
 
namespace Graphics3Dto2D
{ 
    public partial class Form1 : Form
    {
        
        public Form1()
        {
            InitializeComponent();
            CenterToScreen();
            SetStyle(ControlStyles.ResizeRedraw, true);
        }
        private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
        {
 
            double neg = -20;
            double pov =  20;
            double near = 100;
            double far =  140;
            projection proc;
            proc = new projection();
 
            //create a graphics object from the form
            Graphics g = this.CreateGraphics();
            // Create font and brush.
            Pen blackPen = new Pen(Color.Black);
            Font drawFont = new Font("Arial", 16);
            Pen p = new Pen(Color.Red, 1);
            Pen boder = new Pen(Color.Black ,4);
 
            //draw boder for window
            g.DrawLine(boder, 320, 20, 1120, 20);
            g.DrawLine(boder, 320, 820, 1120, 820);
            g.DrawLine(boder, 320, 20, 320, 820);
            g.DrawLine(boder, 1120, 20, 1120, 820);
 
     
 
 
          
//59 68 65
 
            _3Dpoint Dpoint1, Dpoint2, Dpoint3, Dpoint4, Dpoint5, Dpoint6, Dpoint7, Dpoint8;
                  
                     
 
            //draw front
            Dpoint1 = new _3Dpoint(neg, near, pov);
            Dpoint2 = new _3Dpoint(pov, near, pov);
            Dpoint3 = new _3Dpoint(pov, near, pov);
            Dpoint4 = new _3Dpoint(pov, near, neg);
            Dpoint5 = new _3Dpoint(pov, near, neg);
            Dpoint6 = new _3Dpoint(neg, near,neg);
            Dpoint7 = new _3Dpoint(neg, near, neg);
            Dpoint8 = new _3Dpoint(neg, near, pov);
 
            proc.Trans_Line(Dpoint1, Dpoint2);
            g.DrawLine(p, proc.p1.h, proc.p1.v, proc.p2.h, proc.p2.v);
 
            proc.Trans_Line(Dpoint3, Dpoint4);
            g.DrawLine(p, proc.p1.h, proc.p1.v, proc.p2.h, proc.p2.v);
 
            proc.Trans_Line(Dpoint5, Dpoint6);
            g.DrawLine(p, proc.p1.h, proc.p1.v, proc.p2.h, proc.p2.v);
 
            proc.Trans_Line(Dpoint7, Dpoint8);
            g.DrawLine(p, proc.p1.h, proc.p1.v, proc.p2.h, proc.p2.v);
 
 
 
            //draw back
            Dpoint1 = new _3Dpoint(neg + 20, far, pov + 20);
            Dpoint2 = new _3Dpoint(pov + 20, far, pov + 20);
            Dpoint3 = new _3Dpoint(pov + 20, far, pov + 20);
            Dpoint4 = new _3Dpoint(pov + 20, far, neg + 20);
            Dpoint5 = new _3Dpoint(pov + 20, far, neg + 20);
            Dpoint6 = new _3Dpoint(neg + 20, far, neg + 20);
            Dpoint7 = new _3Dpoint(neg + 20, far, neg + 20);
            Dpoint8 = new _3Dpoint(neg + 20, far, pov + 20);
 
            proc.Trans_Line(Dpoint1, Dpoint2);
            g.DrawLine(p, proc.p1.h, proc.p1.v, proc.p2.h, proc.p2.v);
 
            proc.Trans_Line(Dpoint3, Dpoint4);
            g.DrawLine(p, proc.p1.h, proc.p1.v, proc.p2.h, proc.p2.v);
 
            proc.Trans_Line(Dpoint5, Dpoint6);
            g.DrawLine(p, proc.p1.h, proc.p1.v, proc.p2.h, proc.p2.v);
 
            proc.Trans_Line(Dpoint7, Dpoint8);
            g.DrawLine(p, proc.p1.h, proc.p1.v, proc.p2.h, proc.p2.v);
 
 
            //draw left side
            Dpoint1 = new _3Dpoint(neg, near, pov);
            Dpoint2 = new _3Dpoint(neg + 20, far, pov + 20);
            Dpoint3 = new _3Dpoint(neg + 20, far, pov + 20);
            Dpoint4 = new _3Dpoint(neg + 20, far, -pov + 20);
            Dpoint5 = new _3Dpoint(neg + 20, far, -pov + 20);
            Dpoint6 = new _3Dpoint(neg + 20, near, -pov + 20);
            Dpoint7 = new _3Dpoint(neg + 20, near, -pov + 20);
            Dpoint8 = new _3Dpoint(neg, near, -pov);
 
            proc.Trans_Line(Dpoint1, Dpoint2);
            g.DrawLine(p, proc.p1.h, proc.p1.v, proc.p2.h, proc.p2.v);
 
            proc.Trans_Line(Dpoint3, Dpoint4);
            g.DrawLine(p, proc.p1.h, proc.p1.v, proc.p2.h, proc.p2.v);
 
            proc.Trans_Line(Dpoint5, Dpoint6);
            g.DrawLine(p, proc.p1.h, proc.p1.v, proc.p2.h, proc.p2.v);
 
            proc.Trans_Line(Dpoint7, Dpoint8);
            g.DrawLine(p, proc.p1.h, proc.p1.v, proc.p2.h, proc.p2.v);
 
 
 
            //draw right side
            Dpoint1 = new _3Dpoint(pov, near, pov);
            Dpoint2 = new _3Dpoint(pov + 20, far, pov + 20);
            Dpoint3 = new _3Dpoint(pov + 20, far, pov + 20);
            Dpoint4 = new _3Dpoint(pov + 20, far, -pov + 20);
            Dpoint5 = new _3Dpoint(pov + 20, far, -pov + 20);
            Dpoint6 = new _3Dpoint(pov , near, -pov);
            Dpoint7 = new _3Dpoint(pov , near, -pov);
            Dpoint8 = new _3Dpoint(pov, near, -pov);
 
            proc.Trans_Line(Dpoint1, Dpoint2);
            g.DrawLine(p, proc.p1.h, proc.p1.v, proc.p2.h, proc.p2.v);
 
            proc.Trans_Line(Dpoint3, Dpoint4);
            g.DrawLine(p, proc.p1.h, proc.p1.v, proc.p2.h, proc.p2.v);
 
            proc.Trans_Line(Dpoint5, Dpoint6);
            g.DrawLine(p, proc.p1.h, proc.p1.v, proc.p2.h, proc.p2.v);
 
            proc.Trans_Line(Dpoint7, Dpoint8);
            g.DrawLine(p, proc.p1.h, proc.p1.v, proc.p2.h, proc.p2.v);
 
            
            
 
        }
      
    }
}



Form1.Designer.cs


namespace Graphics3Dto2D
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;
 
        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
 
        #region Windows Form Designer generated code
 
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(1424, 832);
            this.Name = "Form1";
            this.Text = "Form1";
            this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
            this.ResumeLayout(false);
 
        }
 
        #endregion
    }
}
 



Program.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
 
namespace Graphics3Dto2D
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}








Copy all the classes and create a project in windows form application you will find nice perspective projection of 3D wired frame cube.



While giving world co-ordinates of object here range of x-ordinate is –65 to 68 and z-ordinate is –59 to 60 if you keep y-ordinate to 100 fixed.The range will change according to the value of y-ordinate.Maximum range of y-ordinate is 1to 200 units.

10 comments:

  1. I'm getting the error "Error in Initializing variable". Can you explain what the EqualVertex is doing? and what EPSILON is?

    ReplyDelete
  2. Awesome broski!!! searching for the 3d projection and got this wonderful site

    ReplyDelete
  3. This was the only place I found a start to finish real working actually complete 3d to 2d translation example. I've integrated it seamlessly into a program of mine and it works great! Thank you!

    ReplyDelete
  4. Absolutely amazing. This is the best and by far the most comprehensive C# tutorial I've found on this topic. Thank you so much!

    ReplyDelete
  5. can u send complete code to my account pallapunagaraj@gmail.com

    ReplyDelete
    Replies
    1. copy and paste in notepad with corresponding name and create project in visual studio with existing source code.

      Delete
  6. Hi Dinesh,
    I know it's been a long time, just wondering if this code works with orthographic projection.
    when changing the Camera.projection value to 1 I receive error: "Conversion is invalid" with the sample.
    it works fine with 0
    appreciate if you can help here :)
    cheers,

    ReplyDelete
    Replies
    1. Since there is no warning in the sample code about "Conversion is invalid", you might post full error message you've encountered.
      By a wild guess, you get an overflow exception.
      Because you changed it to orthographic mode, the normal vector x and z were no longer divided by eye vector y.

      Delete