# Implementing a Basic Camera Model (Pinhole) II

, 9 Apr 2013 GPL3
 Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

## About the Author

Engineer IBM
Brazil
Senior Analyst

Founder of TIHunter Vagas de TI

Linkedin Profile
Follow on

## Comments and Discussions

 First Prev Next
 Subject is interesing but ... ZTransform 10-Feb-10 1:05
 Re: Subject is interesing but ... andalmeida 10-Feb-10 1:12
 Re: Subject is interesing but ... torial 10-Feb-10 7:18
 Re: Subject is interesing but ... andalmeida 10-Feb-10 7: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
 Wow Marcelo Ricardo de Oliveira 9-Feb-10 16:32
 Last Visit: 31-Dec-99 19:00     Last Update: 30-Mar-15 16:57 Refresh 1

General    News    Suggestion    Question    Bug    Answer    Joke    Rant    Admin

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

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150327.1 | Last Updated 9 Apr 2013
Article Copyright 2010 by andalmeida
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid