This tutorial explains how to use the Silverlight 3D library used in my 3D Dice Simulator and Cube Tetris game to draw a rotating cube.
NB: This 3D library has become more or less redundant since Silverlight 5 supports 3D natively using the XNA framework.
This tutorial explains how to use the Silverlight 3D library used in my 3D Dice Simulator and Cube Tetris game to draw a rotating cube.
First, you'll need to create a new solution using File > New Project > Silverlight Application. Then do a File > Add Existing Project, and select the Silverlight 3D project (wherever you downloaded it to). Then add a reference to it, by right clicking on References in your Silverlight project, going to Add Reference, selecting the Projects tab, then Silverlight3D.
Next, you need to add a Canvas to your MainPage.xaml. Give it width 600 and height 450. Also, update d:DesignWidth to 600 and d:DesignHeight to 450.
A cube has 8 corners and 6 faces. The diagram below shows the coordinates that a cube has. It assumes that the cube is centred on (0,0,0), and is 2x2x2.
There is no built-in Cube class in my Silverlight3D library, so create a class called Cube in your project as follows:
namespace Silverlight3D_Tutorial_01
{
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Silverlight3D;
public class Cube
{
public Vector center;
public double edgeLength;
private IList<Vector> corners;
public Cube(Vector center, double edgeLength)
{
this.center = center;
this.edgeLength = edgeLength;
this.corners = new List<Vector>();
this.corners.Add(center + new Vector(edgeLength / 2, edgeLength / 2, edgeLength / 2));
this.corners.Add(center + new Vector(-edgeLength / 2, edgeLength / 2, edgeLength / 2));
this.corners.Add(center + new Vector(-edgeLength / 2, -edgeLength / 2, edgeLength / 2));
this.corners.Add(center + new Vector(edgeLength / 2, -edgeLength / 2, edgeLength / 2));
this.corners.Add(center + new Vector(edgeLength / 2, -edgeLength / 2, -edgeLength / 2));
this.corners.Add(center + new Vector(-edgeLength / 2, -edgeLength / 2, -edgeLength / 2));
this.corners.Add(center + new Vector(-edgeLength / 2, edgeLength / 2, -edgeLength / 2));
this.corners.Add(center + new Vector(edgeLength / 2, edgeLength / 2, -edgeLength / 2));
}
}
}
As you can see, this demo Cube constructor takes a centre point (defined as a Vector) and an edge length parameter.
The Vector class is part of Silverlight3D, and contains X, Y and Z properties. It also contains methods to get the magnitude (length) of the Vector, and also operator definitions. So as an example, you could write code as follows:
Vector position1 = new Vector(0, 0, 0);
Vector position2 = new Vector(100, 25, 32);
Vector delta = position2 - position1;
double distance = delta.Magnitude();
This gets the distance between the two positions. You could also do this:
Vector position = new Vector(0, 0, 0);
Vector velocity = new Vector(0.5, 0.2, 0.3);
while (true)
{
position += velocity;
// draw something based on position
}
which is an easy way to move a 3D point through space, e.g. if you are animating a moving 3D object.
So now we have the cube's 8 corners, we need to use those corners to create sides. Add the following to the Cube class definition:
private VectorPolygon GetVectorPolygonSide(int corner0, int corner1, int corner2, int corner3)
{
var side = new VectorPolygon();
side.Vectors.Add(this.corners[corner0]);
side.Vectors.Add(this.corners[corner1]);
side.Vectors.Add(this.corners[corner2]);
side.Vectors.Add(this.corners[corner3]);
side.StrokeColor = Color.FromArgb(255, 224, 224, 255);
side.StrokeThickness = 1.0;
side.FillColor = Color.FromArgb(255, 32, 32, 220);
side.BrightnessFactor = 0.65;
side.HideIfBackFacing = true;
return side;
}
This method will create a side based on four corners. The side is a VectorPolygon, which is basically a polygon where each point is a Vector. The side gets a stroke colour and thickness, a fill colour, and a brightness factor. The brightness factor determines to what extent the side is affected by the light source. If it is set to 0, the side will only be illuminated if it is facing the light source. If set to 1, the side will be at its full brightness more or less all the time. Set somewhere inbetween, the sides will appear to light up as they start to face the light source, and go into darkness as they face away.
The HideIfBackFacing property lets you prevent sides which face away from the camera from being plotted. This relies on every VectorPolygon's vectors being specified in anti-clockwise order when the VectorPolygon is created. The Cube class code above does specify each side's vectors in anti-clockwise order, so this will work correctly.
As far as my Silverlight 3D library is concerned, a 'Scene' is just a collection of Vector Objects. In other words, Scene just has a List<VectorObject>, that it can output to a standard Canvas control.
In order to render our cube, we need to add the 6 sides to the scene. The following code does this. To make it easier to see what's going on, here's the cube with the corner ID's beside each corner:
public void AddToSceneWithVectorPolygons(Scene scene)
{
var side1 = this.GetVectorPolygonSide(0, 1, 2, 3);
var side2 = this.GetVectorPolygonSide(4, 5, 6, 7);
var side3 = this.GetVectorPolygonSide(7, 6, 1, 0);
var side4 = this.GetVectorPolygonSide(5, 4, 3, 2);
var side5 = this.GetVectorPolygonSide(6, 5, 2, 1);
var side6 = this.GetVectorPolygonSide(0, 3, 4, 7);
scene.Add(side1);
scene.Add(side2);
scene.Add(side3);
scene.Add(side4);
scene.Add(side5);
scene.Add(side6);
}
Each call to GetVectorPolygonSide() identifies which four corners to use.
The final step is to add some setup code to MainPage.xaml.cs:
public partial class MainPage : UserControl
{
private Scene scene;
private Projector projector;
private IShader shader;
private Cube cube;
public MainPage()
{
InitializeComponent();
this.scene = new Scene();
this.projector = new Projector();
this.projector.ViewPosition = new Vector(0, 0, -40);
this.projector.ViewFarDistance = 1000;
this.projector.Zoom = 5000;
this.projector.LightSourcePosition = new Vector(-100, -500, 0);
this.projector.Origin = new Point(300, 260);
this.shader = new Shader();
this.cube = new Cube(new Vector(0, -10, 100), 50);
// Causes Redraw() to be called on every frame.
CompositionTarget.Rendering += Redraw;
}
///
/// Is called on every frame. Updates and redraws everything.
///
private void Redraw(object sender, EventArgs e)
{
// Add some random angles to these to give rotation
this.projector.RxTheta += 0.01;
this.projector.RyTheta += 0.013;
this.projector.RzTheta += 0.006;
// Add the Cube to the Scene
this.scene.Clear();
this.cube.AddToSceneWithVectorPolygons(this.scene);
// Clear the Canvas and redraw the Scene
this.Canvas.Children.Clear();
this.scene.Draw(this.projector, this.Canvas, this.shader, this.cube.center);
}
}
The above code is mainly concerned with creating the Projector object. This is the class that does most of the 3D calculations. The various properties are explained in the XML comments in the Silverlight 3D project's code, but you can also experiment to see what they do.
The code above also sets up a IShader, which controls how a colour/brightness combination is mapped to a resulting colour. The implementation used here is the Shader class included in the Silverlight3D library, which just multiplies the given colour by the brightness factor (which must be between 0.0 and 1.0). You can of course create your own implementation.
A Scene object is also created, which the cube will be added to.
The line CompositionTarget.Rendering += Redraw; causes Redraw() to be called on each Silverlight frame, which means the Redraw event will be called many times per second and the animation will be smooth.
This first updates the rotation angles of the projector. RxTheta is rotation around the x-axis, RyTheta is rotation around the y-axis, and so on. I've just added some random values here, so that on each frame the cube is rotated a bit more. These angles are in radians, not degrees, which means they go from 0 to 2π. The angle can go higher, and will automatically wrap back round to zero.
The next section clears the Scene of all objects, then calls the method on Cube that adds the cube to the scene again (with its updated angles).
The last section clears the Canvas, then tells the Scene to render itself onto the Canvas.
This tutorial showed how to create a Cube class containing 8 corners and 6 sides, and give it methods that add VectorPolygon objects to a Scene. Then, you saw how to add code to MainPage.xaml.cs to set up a Projector, Shader and Scene, and a Redraw method to change the angles of rotation and render the Scene to a Canvas on each frame.