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
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; double m_fFocalLenght = 1.0; Geometry.Point m_oTarget = new Geometry.Point(5, 5, 5);
double m_fVirtualSize = 1;
Bitmap newBitmap = new Bitmap(m_iScreenPixels,
m_iScreenPixels,
PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(newBitmap);
Camera oCamera1 = new Camera();
Rectangle rect = new Rectangle(0, 0, m_iScreenPixels,
m_iScreenPixels);
double fMax = m_fVirtualSize;
g.Clear(Color.Black);
Geometry.Point eye = new Geometry.Point(m_oCameraPos);
m_oCameraPos = rtPoint.Translate(eye, new Vector(0.001, 0, -0.35));
oCamera1.Setup(m_fFocalLenght, new Geometry.Point(eye.x,
eye.y, eye.z), new Vector(eye, m_oTarget));
...
Sphere s1 = new Sphere(10, 10);
s1.Scale(2); s1.Translate(new Vector(5, 5, 5)); s1.Draw(oCamera1, g, rect, fMax, Color.Yellow);
Grid g1 = new Grid(50, 50);
g1.Scale(50); g1.Rotate(3.14, 0, 0); g1.Draw(oCamera1, g, rect, fMax, Color.White);
Cube c1 = new Cube();
c1.Translate(new Vector(10, 3, 0));
c1.Rotate(angle, 0, 0); c1.Draw(oCamera1, g, rect, fMax, Color.Red);
Cube c2 = new Cube();
c2.Translate(new Vector(-3, 10, 0));
c2.Rotate(0, angle, 0); c2.Draw(oCamera1, g, rect, fMax, Color.Green);
Cube c3 = new Cube();
c3.Translate(new Vector(0, -3, 10));
c3.Rotate(0, 0, angle); c3.Draw(oCamera1, g, rect, fMax, Color.Blue);
newBitmap.Save("c:\\temp\\bitmap1.png");
pictureBox1.Load("c:\\temp\\bitmap1.png");
angle += 0.2; angle2 += 0.1; }
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.