Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Implementing a Basic Camera Model (Pinhole) II

0.00/5 (No votes)
15 Jul 2016 3  
Extending the camera class and creating basic animations.

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:

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...

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...

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:

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here