Click here to Skip to main content
15,867,835 members
Articles / Programming Languages / C#

Implementing a Basic Camera Model (Pinhole) II

Rate me:
Please Sign up or sign in to vote.
4.90/5 (16 votes)
15 Jul 2016GPL33 min read 39.5K   623   46   5
Extending the camera class and creating basic animations.

Image 1

See animations created with this article code:

Introduction

In my previous article at CodeProject: "Implementing a Basic Camera Model (Pinhole)", we made a basic camera class that is able to display objects from R3.

In order to test the camera, we created a static cube. Now, the idea is to create new objects such as spheres and grids, apply rotations and translations to them, and also apply movement to the camera position between frames, giving us a basic animation.

Background

It is required that you have advanced knowledge in mathematics. Also, read my previous articles on ray tracing on CodeProject. Specially because, here we are just extending the camera class seen previously:

Step 1: Creating Objects

In order to test the camera, we need to create some graphics objects. This it is not part of the article, but I am pasting the code here just for reference. Our focus is the camera class and its positioning. The objects creation such as revolution objects (spheres, ellipsoids, and so...) can be them theme of future articles.

As seen before, our primary element on every object is the R3 Point P represented by P(x,y,z). From this, we are able to create these objects in this article:

  • Cube
  • Sphere
  • Grid

Cube

The cube definition comes from our previous article:

C#
public class Cube
{
    public Geometry.Point p1;
    public Geometry.Point p2;
    public Geometry.Point p3;
    public Geometry.Point p4;
    public Geometry.Point p5;
    public Geometry.Point p6;
    public Geometry.Point p7;
    public Geometry.Point p8;
    public Cube()
    {
        p1 = new Geometry.Point(-1, -1, 1);
        p2 = new Geometry.Point(-1, 1, 1);
        p3 = new Geometry.Point(1, 1, 1);
        p4 = new Geometry.Point(1, -1, 1);
        p5 = new Geometry.Point(-1, -1, -1);
        p6 = new Geometry.Point(-1, 1, -1);
        p7 = new Geometry.Point(1, 1, -1);
        p8 = new Geometry.Point(1, -1, -1);
    }
    public void Scale(double scale)
    {
        p1 = new Geometry.Point(p1.x * scale, p1.y * scale, p1.z * scale);
        p2 = new Geometry.Point(p2.x * scale, p2.y * scale, p2.z * scale);
        p3 = new Geometry.Point(p3.x * scale, p3.y * scale, p3.z * scale);
        p4 = new Geometry.Point(p4.x * scale, p4.y * scale, p4.z * scale);
        p5 = new Geometry.Point(p5.x * scale, p5.y * scale, p5.z * scale);
        p6 = new Geometry.Point(p6.x * scale, p6.y * scale, p6.z * scale);
        p7 = new Geometry.Point(p7.x * scale, p7.y * scale, p7.z * scale);
        p8 = new Geometry.Point(p8.x * scale, p8.y * scale, p8.z * scale);
    }
    public void Translate(Vector translate)
    {
        p1 = new Geometry.Point(p1.x + translate.x, p1.y + 
                                translate.y, p1.z + translate.z);
        p2 = new Geometry.Point(p2.x + translate.x, p2.y + 
                                translate.y, p2.z + translate.z);
        p3 = new Geometry.Point(p3.x + translate.x, p3.y + 
                                translate.y, p3.z + translate.z);
        p4 = new Geometry.Point(p4.x + translate.x, p4.y + 
                                translate.y, p4.z + translate.z);
        p5 = new Geometry.Point(p5.x + translate.x, p5.y + 
                                translate.y, p5.z + translate.z);
        p6 = new Geometry.Point(p6.x + translate.x, p6.y + 
                                translate.y, p6.z + translate.z);
        p7 = new Geometry.Point(p7.x + translate.x, p7.y + 
                                translate.y, p7.z + translate.z);
        p8 = new Geometry.Point(p8.x + translate.x, p8.y + 
                                translate.y, p8.z + translate.z);
    }
    internal void Draw(Camera oCamera1, System.Drawing.Graphics g, 
                       Rectangle rect, double fMax, Color color)
    {
        Geometry.Point point = new Geometry.Point(p1.x, p1.y, p1.z);
        Geometry.Point pointAux1 = oCamera1.GetProjectedMappedPoint(point);
        point = new Geometry.Point(p2.x, p2.y, p2.z);
        Geometry.Point pointAux2 = oCamera1.GetProjectedMappedPoint(point);
        point = new Geometry.Point(p3.x, p3.y, p3.z);
        Geometry.Point pointAux3 = oCamera1.GetProjectedMappedPoint(point);
        point = new Geometry.Point(p4.x, p4.y, p4.z);
        Geometry.Point pointAux4 = oCamera1.GetProjectedMappedPoint(point);
        point = new Geometry.Point(p5.x, p5.y, p5.z);
        Geometry.Point pointAux5 = oCamera1.GetProjectedMappedPoint(point);
        point = new Geometry.Point(p6.x, p6.y, p6.z);
        Geometry.Point pointAux6 = oCamera1.GetProjectedMappedPoint(point);
        point = new Geometry.Point(p7.x, p7.y, p7.z);
        Geometry.Point pointAux7 = oCamera1.GetProjectedMappedPoint(point);
        point = new Geometry.Point(p8.x, p8.y, p8.z);
        Geometry.Point pointAux8 = oCamera1.GetProjectedMappedPoint(point);
        if (pointAux1 != null && pointAux2 != null && 
            pointAux3 != null && pointAux4 != null &&
            pointAux5 != null && pointAux6 != null && 
            pointAux7 != null && pointAux8 != null)
        {
            double x1 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux1.x);
            double y1 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux1.y);
            double x2 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux2.x);
            double y2 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux2.y);
            double x3 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux3.x);
            double y3 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux3.y);
            double x4 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux4.x);
            double y4 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux4.y);
            double x5 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux5.x);
            double y5 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux5.y);
            double x6 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux6.x);
            double y6 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux6.y);
            double x7 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux7.x);
            double y7 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux7.y);
            double x8 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux8.x);
            double y8 = rtPoint.GetCoord(-fMax, fMax, rect.Left, rect.Right, pointAux8.y);
            Pen pen = new Pen(color);
            g.DrawLine(pen, (int)x1, (int)y1, (int)x2, (int)y2);
            g.DrawLine(pen, (int)x2, (int)y2, (int)x3, (int)y3);
            g.DrawLine(pen, (int)x3, (int)y3, (int)x4, (int)y4);
            g.DrawLine(pen, (int)x4, (int)y4, (int)x1, (int)y1);
            g.DrawLine(pen, (int)x5, (int)y5, (int)x6, (int)y6);
            g.DrawLine(pen, (int)x6, (int)y6, (int)x7, (int)y7);
            g.DrawLine(pen, (int)x7, (int)y7, (int)x8, (int)y8);
            g.DrawLine(pen, (int)x8, (int)y8, (int)x5, (int)y5);
            g.DrawLine(pen, (int)x1, (int)y1, (int)x5, (int)y5);
            g.DrawLine(pen, (int)x2, (int)y2, (int)x6, (int)y6);
            g.DrawLine(pen, (int)x3, (int)y3, (int)x7, (int)y7);
            g.DrawLine(pen, (int)x4, (int)y4, (int)x8, (int)y8);
            pen.Dispose();
        }
    }
    internal void Rotate(double ax, double ay, double az)
    {
        p1 = rtPoint.RotX(ax, p1);
        p2 = rtPoint.RotX(ax, p2);
        p3 = rtPoint.RotX(ax, p3);
        p4 = rtPoint.RotX(ax, p4);
        p5 = rtPoint.RotX(ax, p5);
        p6 = rtPoint.RotX(ax, p6);
        p7 = rtPoint.RotX(ax, p7);
        p8 = rtPoint.RotX(ax, p8);
        p1 = rtPoint.RotY(ay, p1);
        p2 = rtPoint.RotY(ay, p2);
        p3 = rtPoint.RotY(ay, p3);
        p4 = rtPoint.RotY(ay, p4);
        p5 = rtPoint.RotY(ay, p5);
        p6 = rtPoint.RotY(ay, p6);
        p7 = rtPoint.RotY(ay, p7);
        p8 = rtPoint.RotY(ay, p8);
        p1 = rtPoint.RotZ(az, p1);
        p2 = rtPoint.RotZ(az, p2);
        p3 = rtPoint.RotZ(az, p3);
        p4 = rtPoint.RotZ(az, p4);
        p5 = rtPoint.RotZ(az, p5);
        p6 = rtPoint.RotZ(az, p6);
        p7 = rtPoint.RotZ(az, p7);
        p8 = rtPoint.RotZ(az, p8);
    }
}

Sphere

The sphere created here is based on spherical coordinates running the latitudes and longitudes from 0 to PI and 0 to 2PI, respectively. (Note* Mercator projections are -PI/2 to PI/2 and -PI to PI).

How do we do this? We define a step to integrate from 0 to PI and 0 to 2PI from the number of slices in the latitudes and longitudes we want to have in our sphere model. For example, if we want 10 slices, the step is 2*PI / 10. After that, just apply the spherical coordinates to the theta and phi for each integration step...

C#
public class Sphere
{
    public Geometry.Point[,] points;
    int m_Lat;
    int m_Lon;

    public Sphere(int m, int n)
    {
        m_Lat = m;
        m_Lon = n;
        double di = (Math.PI * 2.0) / (double)m;
        double dt = (Math.PI * 2.0) / (double)n;
        points = new Geometry.Point[m, n];
        double ai = 0;

        for (int i = 0; i < m; i++)
        {
            double at = 0;
            for (int j = 0; j < n; j++)
            {
                double x = Math.Sin(ai) * Math.Cos(at);
                double y = Math.Sin(ai) * Math.Sin(at);
                double z = Math.Cos(ai);
                points[i, j] = new Point(x, y, z);
                at += dt;
            }
            ai += di;
        }
    }

    public void Scale(double scale)
    {
        for (int i = 0; i < m_Lat; i++)
        {
            for (int j = 0; j < m_Lon; j++)
            {
                points[i, j] = new Point(points[i, j].x * scale, 
                  points[i, j].y * scale, points[i, j].z * scale);
            }
        }
    }

    public void Translate(Vector translate)
    {
        for (int i = 0; i < m_Lat; i++)
        {
            for (int j = 0; j < m_Lon; j++)
            {
                points[i, j] = new Point(points[i, j].x + translate.x, 
                  points[i, j].y + translate.y, points[i, j].z + translate.z);
            }
        }
    }

    internal void Draw(Camera oCamera1, System.Drawing.Graphics g, 
                       Rectangle rect, double fMax, Color color)
    {
        Pen pen = new Pen(color);
        for (int i = 0; i < m_Lat; i++)
        {
            for (int j = 0; j < m_Lon; j++)
            {
                int iplus1 = i + 1;
                if (iplus1 == m_Lat)
                    iplus1 = 0;
                int jplus1 = j + 1;
                if (jplus1 == m_Lon)
                    jplus1 = 0;

                Point a = new Point(points[i, j]);
                Point b = new Point(points[iplus1, j]);
                Point c = new Point(points[iplus1, jplus1]);
                Point d = new Point(points[i, jplus1]);
                Geometry.Point pointAux1 = oCamera1.GetProjectedMappedPoint(a);
                Geometry.Point pointAux2 = oCamera1.GetProjectedMappedPoint(b);
                Geometry.Point pointAux3 = oCamera1.GetProjectedMappedPoint(c);
                Geometry.Point pointAux4 = oCamera1.GetProjectedMappedPoint(d);

                if (pointAux1 != null && pointAux2 != null && 
                    pointAux3 != null && pointAux4 != null)

                {
                    double x1 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux1.x);
                    double y1 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux1.y);
                    double x2 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux2.x);
                    double y2 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux2.y);
                    double x3 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux3.x);
                    double y3 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux3.y);
                    double x4 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux4.x);
                    double y4 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux4.y);

                    g.DrawLine(pen, (int)x1, (int)y1, (int)x2, (int)y2);
                    g.DrawLine(pen, (int)x2, (int)y2, (int)x3, (int)y3);
                    g.DrawLine(pen, (int)x3, (int)y3, (int)x4, (int)y4);
                    g.DrawLine(pen, (int)x4, (int)y4, (int)x1, (int)y1);
                }
            }
        }
        pen.Dispose();
    }
}

Grid

The grid objects are created by running the points in a rectangle at the z=o plane from -1 to 1, both for x and y.

How do we do this? First, we define a domain in R3 similar to a grid composed by Points(x,y,z), disposed like a matrix grid. To retrieve the points, we set initial xo = -1, and run by a factor added to the initial point n times, or better xn = xn-1 + k. We do the same for yo.

So we get all the x, y, z coordinates from our grid model...

C#
public class Grid
{
    public Geometry.Point[,] points;
    int m_Lat;
    int m_Lon;

    public Grid(int m, int n)
    {
        m_Lat = m;
        m_Lon = n;

        double di = 2.0 / (double)m;
        double dt = 2.0 / (double)n;
        points = new Geometry.Point[m, n];

        double ai = -1;
        for (int i = 0; i < m; i++)
        {
            double at = -1;
            for (int j = 0; j < n; j++)
            {
                double x = ai;
                double y = at;
                double z = 0;
                points[i, j] = new Point(x, y, z);
                at += dt;
            }
            ai += di;
        }
    }

    public void Scale(double scale)
    {
        for (int i = 0; i < m_Lat; i++)
        {
            for (int j = 0; j < m_Lon; j++)
            {
                points[i, j] = new Point(points[i, j].x * scale, 
                  points[i, j].y * scale, points[i, j].z * scale);
            }
        }
    }

    public void Translate(Vector translate)
    {
        for (int i = 0; i < m_Lat; i++)
        {
            for (int j = 0; j < m_Lon; j++)
            {
                points[i, j] = new Point(points[i, j].x + translate.x, 
                  points[i, j].y + translate.y, points[i, j].z + translate.z);
            }
        }
    }

    internal void Draw(Camera oCamera1, System.Drawing.Graphics g, 
                       Rectangle rect, double fMax, Color color)
    {
        Pen pen = new Pen(color);
        for (int i = 0; i < m_Lat; i++)
        {
            for (int j = 0; j < m_Lon; j++)
            {
                int iplus1 = i + 1;
                if (iplus1 == m_Lat)
                    iplus1 = 0;
                int jplus1 = j + 1;
                if (jplus1 == m_Lon)
                    jplus1 = 0;

                Point a = new Point(points[i, j]);
                Point b = new Point(points[iplus1, j]);
                Point c = new Point(points[iplus1, jplus1]);
                Point d = new Point(points[i, jplus1]);
                Geometry.Point pointAux1 = oCamera1.GetProjectedMappedPoint(a);
                Geometry.Point pointAux2 = oCamera1.GetProjectedMappedPoint(b);
                Geometry.Point pointAux3 = oCamera1.GetProjectedMappedPoint(c);
                Geometry.Point pointAux4 = oCamera1.GetProjectedMappedPoint(d);

                if (pointAux1 != null && pointAux2 != null && 
                    pointAux3 != null && pointAux4 != null)
                {
                    double x1 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux1.x);
                    double y1 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux1.y);
                    double x2 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux2.x);
                    double y2 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux2.y);
                    double x3 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux3.x);
                    double y3 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux3.y);
                    double x4 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux4.x);
                    double y4 = rtPoint.GetCoord(-fMax, fMax, rect.Left, 
                                                 rect.Right, pointAux4.y);

                    g.DrawLine(pen, (int)x1, (int)y1, (int)x2, (int)y2);
                    g.DrawLine(pen, (int)x2, (int)y2, (int)x3, (int)y3);
                    g.DrawLine(pen, (int)x3, (int)y3, (int)x4, (int)y4);
                    g.DrawLine(pen, (int)x4, (int)y4, (int)x1, (int)y1);
                }
            }
        }

        pen.Dispose();
    }

    internal void Rotate(double ax, double ay, double az)
    {
        for (int i = 0; i < m_Lat; i++)
        {
            for (int j = 0; j < m_Lon; j++)
            {
                points[i, j] = rtPoint.RotX(ax, points[i, j]);
                points[i, j] = rtPoint.RotY(ay, points[i, j]);
                points[i, j] = rtPoint.RotZ(az, points[i, j]);
            }
        }
    }
}

Putting it all together

Now we are able to create some different objects, and from them, apply rotations and move the camera. Extending the very basic testing project, I have added a timer which updates all the objects and render the scenes.

Following is the final code:

C#
private void timer1_Tick(object sender, EventArgs e)
{
    int m_iScreenPixels = 400;     // the size of our screen
    double m_fFocalLenght = 1.0;   // the camera focal distance
    // camera target position...
    Geometry.Point m_oTarget = new Geometry.Point(5, 5, 5);
    double m_fVirtualSize = 1;     // R3 Domain reference
    
    // creates a bitmap
    Bitmap newBitmap = new Bitmap(m_iScreenPixels, 
                       m_iScreenPixels,
                       PixelFormat.Format32bppArgb);
    // creates a graphics
    Graphics g = Graphics.FromImage(newBitmap);

    // creates a camera
    Camera oCamera1 = new Camera();
    
    // creates an image rectangle reference
    Rectangle rect = new Rectangle(0, 0, m_iScreenPixels, 
                                   m_iScreenPixels);

    // Creates auxiliar parameter to the R3 reference size
    double fMax = m_fVirtualSize;

    
    // cleans the graphics background to black 
    g.Clear(Color.Black);

    // Defines the camera position
    Geometry.Point eye = new Geometry.Point(m_oCameraPos);

    // translate camera position (note this updates each time) 
    m_oCameraPos = rtPoint.Translate(eye, new Vector(0.001, 0, -0.35));

    // setup the camera, initializing all parameters
    oCamera1.Setup(m_fFocalLenght, new Geometry.Point(eye.x, 
                   eye.y, eye.z), new Vector(eye, m_oTarget));

    ...

    // creates a sphere with 10 slices latitudes and 10 slices longitudes
    Sphere s1 = new Sphere(10, 10);
    s1.Scale(2); // scales the sphere by 2
    s1.Translate(new Vector(5, 5, 5));  // translates the sphere by(5,5,5)
    s1.Draw(oCamera1, g, rect, fMax, Color.Yellow); // draws the sphere

    // creates a grid with 50 points in each direction
    Grid g1 = new Grid(50, 50);
    g1.Scale(50);  // scales the grid by 50
    g1.Rotate(3.14, 0, 0);  // rotates the grid around z axis by ~PI
    g1.Draw(oCamera1, g, rect, fMax, Color.White);  // draws the grid

    // creates a cube centered at 10,3,0
    Cube c1 = new Cube();
    c1.Translate(new Vector(10, 3, 0));
    c1.Rotate(angle, 0, 0); // rotates the cube around x axis
    c1.Draw(oCamera1, g, rect, fMax, Color.Red); // draws the cube 

    // creates a cube centered at -3,10,0
    Cube c2 = new Cube();
    c2.Translate(new Vector(-3, 10, 0));
    c2.Rotate(0, angle, 0);  // rotates the cube around y axis
    c2.Draw(oCamera1, g, rect, fMax, Color.Green); // draws the cube 

    // creates a cube centered at 0,-3,10
    Cube c3 = new Cube();
    c3.Translate(new Vector(0, -3, 10));
    c3.Rotate(0, 0, angle); // rotates the cube around z axis 
    c3.Draw(oCamera1, g, rect, fMax, Color.Blue); // draws the cube  

    // saves the bitmap
    newBitmap.Save("c:\\temp\\bitmap1.png");
    // reload the image and displays
    pictureBox1.Load("c:\\temp\\bitmap1.png");

    angle += 0.2; // update rotation angle
    angle2 += 0.1; // update rotation angle2
}

Using the Code

All the required code is in a project in the zip file at the top of the article; just download and compile.

Points of Interest

It was necessary to detect if the projected point is in front of the camera or behind it. One way to get the calculation was to use the dot product between the view direction and the camera (position-given point) vector from the formula: dot = |v1|*|v2|*Cos(t).

Since Cos returns us -PI/2 to PI/2, we can find out if the point is into a viewing frustrum. The frustrum angle used is PI/3, so the calculation is, if abs(t) <= PI/3, the given point is visible.

Conclusion

Once all my previous articles are linked with ray tracing, I'll try to get the camera class into the ray tracing models in my future articles and try to get some animations from them.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
CEO
Brazil Brazil
"A well written code is self explanatory" - Anonymous Programmer
Founder @TIHUNTER.COM.BR
Linkedin Profile

Comments and Discussions

 
GeneralSubject is interesing but ... Pin
ZTransform10-Feb-10 0:05
ZTransform10-Feb-10 0:05 
GeneralRe: Subject is interesing but ... Pin
andalmeida10-Feb-10 0:12
andalmeida10-Feb-10 0:12 
GeneralRe: Subject is interesing but ... Pin
torial10-Feb-10 6:18
torial10-Feb-10 6:18 
GeneralRe: Subject is interesing but ... Pin
andalmeida10-Feb-10 6:38
andalmeida10-Feb-10 6:38 
yes, sorry about this... by some reason I put the link there and it keeps referenced to the unedited path,

follows the link

http://www.codeproject.com/script/Articles/MemberArticles.aspx?amid=3589667[^]

Thanks!
Anderson J. Almeida
Systems Analyst
TIHunter
http://www.tihunter.com

GeneralWow Pin
Marcelo Ricardo de Oliveira9-Feb-10 15:32
mvaMarcelo Ricardo de Oliveira9-Feb-10 15:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.