WPF 3D Solid Wireframe Transform


Introduction


I have just completed my Graduation in IT and at the very beginning of my career I am facing the projects or problems related to 3D Graphics, Rendering of 3D objects in computer screen and perform some basic operation over 3D object. The result of this was I have done some R&D related to this topics and I found that WPF will help me in Rendering of 3D object and Transformation of 3D objects.

In the very beginning I decided to make a project that (1) renders 3D objects (2) Wireframe object and (3) does basic operations for 3D objects. And this article is the result of this process.

One thing that shocked me was that WPF does not render wireframe objects but thank God that the 3DTool provides the ScreenSpaceLine3D class that allows us to render wireframe objects.

 1.gif

Requirements

  1. VS 2008 or onwards version.
  2. 3DTool DLL.
  3. Basic Mathematical and WPF knowledge.
Now I am starting the explanation.

INDEX
  1. Read Important Definitions
  2. Set Camera and Light
  3. Draw solid cube
  4. Draw Wireframe cube
  5. Transform above cube's that is Solid and Wire frame cube.
1. Read Important Definition

This section contains the important definitions. You should have to come back here another time also when you will draw the solid and wireframe cube.

The following image shows the hierarchy which we have to follow to render 3D object.
 

2.gif
  • VIEWPORT3D:
The Viewport3D provides a rendering surface for 3-D visual content.
  • CAMERA:
Camera used to see the object's in the Viewport3D and it also Projects the 3-D contents to the 2-D surface of the Viewport3D.
  • MODELVISUAL3D:
It contains the 3D models.
  • LIGHT:
It illuminates the scene or more elaborately than in 3-D graphics lights do the same as what lights do in the real world; they make surfaces visible.
  • MODEL3DGROUP:
It contains a collection of 3-D models. And it allows operations such as transformation, animation etc. to groups of 3-D models as if they were a single model.
  • GeometryModel3D:
What we will see in ViewPort3D is 3Dmodels or 3Dobjects and they are created using the GeometryModel3D class.

Two very basic properties of this class will help us in creating 3D objects. And both properties are as below. You can also see the hierarchy in the preceding image

1. Geometry and
2. Material.

1. Geometry

MeshGeometry3D:

Geometry property of GeometryModel3D class contains the instance of the 
MeshGeometry3D class.

MeshGeometry3D is used to create a mesh.

Now what is the mesh and what is the use of it?

You can compare the mesh with the skeleton of our body.

Mesh is skeleton of 3D models.

It is used to create the surface of the 3D models.

And to create a surface it uses a very basic geometry shape and as 
we all know it is a TRIANGLE.

So MESH will use TRIANGLE as the basic building blocks to represent Surface.

The Reason for using a TRIANGLE is that it can represent more information than any other geometrical shape such as square, pentagon, hexagon or any polygon.

Mesh is consisting of Points and Lines.

Points represent the positions in 3d world space and line connects those positions to establish the surface area. The MeshGeometry3D class contains three very basic properties that are frequently used in creating 3D objects and these are as below

1.Position
2.TriangleIndices
3.Normal

1. Position

Position represents the location of a single point in 3D world space.

2. TriangleIndices

The mesh position only describes the three points of the triangle (very basic shape to create mesh) in mesh.

In order to create the mesh, after adding the position we need to define the points of the triangles and that can be done through TriangleIndices.

In WPF, the order in which you add triangle indices is very important.

Because it affects the visibility of triangle represent by triangleindices.

Let's say,

Suppose, we are looking at the triangular surface.

If we define the triangle's indices in a clock-wise direction then the side we are looking at will be invisible or we can say that what we see are the back side of that triangular surface.

If we define triangle indices in a counter-clockwise direction, then the side we are looking at will be visible or what we are seeing is the front face of that triangular surface.

NOTE: in the above case though you give the triangle indices in a clock-wise direction so you can see the surface if you have provided background material.

3. NORMAL:

Mathematically, a vector is normally perpendicular to the surface. 
To clarify the idea.

See the image below.
 

3.gif

In order to get the best edge discrimination of a 3D-Object we have to add Normals for each position in the mesh.

Triangleindices decides visibility and invisibility (Front or Back side of Triangle)

Whereas Normals are used by the light sources to decide how much light will take each triangle 
(in mesh).
I have added 24 normals for 8 positions of the cube because each point of position will be used by 3 triangles. So, each position has 3 normals in our case.

2. Material

Material represents the texture of the 3D Geometries. Combined with a light source.

There are four basic materials used in WPF as below.

Diffuse,Specular,emissive,Combined

For more detailed explanation on material go here

2. Set Camera and Light

To set camera and light see the code below and image I think I don't need to write any thing for explaining definitions.

You can also see the same code in demo projects .xaml file


 <Viewport3D Name="mainViewPort"  ClipToBounds="True" Opacity="10">
                <Viewport3D.Camera >
                    <PerspectiveCamera x:Name="camera"
          FarPlaneDistance="100"
          LookDirection="-9,-8,-21"
          UpDirection="0,1,0"
          NearPlaneDistance="1"
          Position="9,8,21"
          FieldOfView="70" />
                </Viewport3D.Camera>
                <ModelVisual3D>
                    <ModelVisual3D.Content>
                        <DirectionalLight x:Name="directionLightOfMainViewPort" Color="White" Direction="-5,-8,-9" />
                    </ModelVisual3D.Content>
                </ModelVisual3D>
           </Viewport3D>

See the image below to make the definition clear. 

                4.gif

3. Draw Solid Cube

In order to draw solid cube follow the steps below.
 

5.gif

EXPLANATION:

 I will explain from bottom to top, which means from GeometryModel3D to Viewport3D

FIRST:   At very first create MeshGeometry3D(meshGeo) with its three basic properties.

After creating the mesh (meshGeo), add MeshGeometry3D(meshGeo) and Material(material) in the GeometryModel3D(geoModel).

See the code below.

 GeometryModel3D geoModel;           

            MeshGeometry3D meshGeo;
            Material material;

            //Create MESH
            meshGeo = new MeshGeometry3D();

            // Add Position in MESH
            meshGeo.Positions.Add(p0);
            meshGeo.Positions.Add(p1);
            meshGeo.Positions.Add(p2);

            // Add triangleIndices in MESH
            meshGeo.TriangleIndices.Add(0);
            meshGeo.TriangleIndices.Add(1);
            meshGeo.TriangleIndices.Add(2);

            // Add normal in MESH
            Vector3D normal = CalculateNormal(p0, p1, p2);
            meshGeo.Normals.Add(normal);
            meshGeo.Normals.Add(normal);
            meshGeo.Normals.Add(normal);

            // Create MATERIAL
            material = new DiffuseMaterial(new SolidColorBrush(Colors.LawnGreen));
            
            // Create GEOMETRYMODEL3D wich tackes MESH adn MATERIAL as arguments
            geoModel = new GeometryModel3D(meshGeo, material);



See the image below.

6.gif

SECOND: Add GeometryModel3D(geoModel) in Model3DGroup(modelGroup)

See the below code.

 Model3DGroup modelGroup;

         ...
            //Create MODEL3DGROUP
            modelGroup = new Model3DGroup();

            //Add GEOMETRYMODEL3D in MODEL3DGROUP
            modelGroup.Children.Add(geoModel);

See the image below.

7.gif       

THIRD: Add collection of Model3DGroup(cubeM3DGroup) in ModelVisual3D(solidCubeMV3D).

See the code below.

 solidCubeMV3D = new ModelVisual3D();


            Model3DGroup cubeM3DGroup = new Model3DGroup();
 
            //front side triangles
            cubeM3DGroup.Children.Add(CreateTriangleFacet(p3, p2, p6));
            cubeM3DGroup.Children.Add(CreateTriangleFacet(p3, p6, p7));
            //right side triangles
            cubeM3DGroup.Children.Add(CreateTriangleFacet(p2, p1, p5));
            cubeM3DGroup.Children.Add(CreateTriangleFacet(p2, p5, p6));
            //back side triangles
            cubeM3DGroup.Children.Add(CreateTriangleFacet(p1, p0, p4));
            cubeM3DGroup.Children.Add(CreateTriangleFacet(p1, p4, p5));
            //left side triangles
            cubeM3DGroup.Children.Add(CreateTriangleFacet(p0, p3, p7));
            cubeM3DGroup.Children.Add(CreateTriangleFacet(p0, p7, p4));
            //top side triangles
            cubeM3DGroup.Children.Add(CreateTriangleFacet(p7, p6, p5));
            cubeM3DGroup.Children.Add(CreateTriangleFacet(p7, p5, p4));
            //bottom side triangles
            cubeM3DGroup.Children.Add(CreateTriangleFacet(p2, p3, p0));
            cubeM3DGroup.Children.Add(CreateTriangleFacet(p2, p0, p1));

            //Add MODEL3DGROUP in MODELVISUAL3D
            solidCubeMV3D.Content = cubeM3DGroup;

            // Store the instance of Transform3DGroup class in Transform                  Property of "solidCubeMV3D"
            // it will later use to rotate the object
            solidCubeMV3D.Transform = new Transform3DGroup();

NOTE: Assign transform property of (solidCubeMV3D) to later use in rotation of object.

See the below image.

    8.gif

FOURTH: Add ModelVisual3D(solidCubeMV3D) in the Viewport3D(mainViewPort)


See the code below.
          

    //Add MODELVISUAL3D in VIEWPORT3D
            this.mainViewPort.Children.Add(solidCube);

See the image below.

9.gif 

4. Draw Wireframe Cube

In order to draw wireframe object you need 3DTool.dll.

I have added it in my DemoProject "3DTOOL DLL" folder.

So, just add reference to that .dll if you don't have.

To add reference of 3DTool in your project Following the steps:

1.Go to the Solution Explorer

2.Right Click on References

3.Select: Add References… will open new dialogue box

4.Click on Browse

5.Navigate to 3DTool.dll

6.Click on OK. That's it

Now, how to draw wireframe cube.

10.gif

FIRST: Create the instance of ScreenSpaceLines3D(wireFrameCube) and set it's three properties and that is "Thickness", "Color", "Points".

And also set the "Transform" property to later use in Rotation of "wireFrameCube".
See the code below.

 wireFrameCube = new ScreenSpaceLines3D();

            Color c = Colors.Orange;
            int width = 5;

            wireFrameCube.Thickness = width;
            wireFrameCube.Color = c;

            wireFrameCube.Points.Add(p0);
            wireFrameCube.Points.Add(p1);

            wireFrameCube.Points.Add(p1);
            wireFrameCube.Points.Add(p2);

            wireFrameCube.Points.Add(p2);
            wireFrameCube.Points.Add(p3);

            wireFrameCube.Points.Add(p3);
            wireFrameCube.Points.Add(p0);

            wireFrameCube.Points.Add(p4);
            wireFrameCube.Points.Add(p5);

            wireFrameCube.Points.Add(p5);
            wireFrameCube.Points.Add(p6);

            wireFrameCube.Points.Add(p6);
            wireFrameCube.Points.Add(p7);

            wireFrameCube.Points.Add(p7);
            wireFrameCube.Points.Add(p4);

            wireFrameCube.Points.Add(p0);
            wireFrameCube.Points.Add(p4);

            wireFrameCube.Points.Add(p1);
            wireFrameCube.Points.Add(p5);

            wireFrameCube.Points.Add(p2);
            wireFrameCube.Points.Add(p6);
 
            wireFrameCube.Points.Add(p3);
            wireFrameCube.Points.Add(p7);
 
         wireFrameCube.Transform = new Transform3DGroup();

See the image below.

11.gif


SECOND: Simply and directly add the ScreenSpaceLines3D(wireFrameCube) to the Viewport3D(mainViewPort).

See the code below.

 this.mainViewPort.Children.Add(wireFrameCube);

That's it you have created wireframe cube.

See the image below.

12.gif


NOTE: Suppose a line is being drawn using four points then two intermediate points will be used two times or we add intermediate points twice.

See the below image to clsrify the idea.

13.gif

Code to draw this line in viewport is as below.

NOTE: after using the code below you just see straight line in ViewPort3D.

You will not see Red points and labels of points.

 ScreenSpaceLines3D wireFrameCube = new ScreenSpaceLines3D();


            ScreenSpaceLines3D wireFrameCube = new ScreenSpaceLines3D();
            Color c = Colors.Orange;
            int width = 5;

            wireFrameCube.Thickness = width;
            wireFrameCube.Color = c;

            wireFrameCube.Points.Add(P1);
            wireFrameCube.Points.Add(P2);

            wireFrameCube.Points.Add(P2);
            wireFrameCube.Points.Add(P3);

            wireFrameCube.Points.Add(P3);
            wireFrameCube.Points.Add(P4);
 
         this.mainViewPort.Children.Add(wireFrameCube);


You can see that points P3 and P4 are used twise. To represent straight line.

5.TRANSFORM

To transform objects in Viewport3D we have two options.

5.1.      Transformation of object or

5.2.      Transformation of camera.

To make it clear let's take one example.

Suppose,

a. One cube is in your hand and you are rotating it with your fingers.

Then that is the first one---> [Transformation of object]

b. One cube is on table and you are rotating around the cube or table, 

Then that this is the second one ---> [Transformation of camera]

Here, you can compare yourself as a camera.

5.1  Transformation of object

In order to transform an object you have to decide two things

1.Rotation Axis and

2.Angle to rotate around specified axis.

We can decide Rotation Axis and angle using the mouse movement and some calculations.

 See the image below...

                                           

                                    14.gif

RotationAxis:

In ordert to calculate rotationAxis we will first calculate mouseMoveAngle after adding 90 degree in it; that will give us rotationAxisAngle and then as shown in the image below using some basic mathematics we can find rotationAxis.

                                   15.gif
 
Angle of Rotation or StepAngle:

You can give any constant value to Angle of Rotation (stepAngle).

Or you can also use the following formula.


 double stepAngle = 0.01 * Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2));

Now, we have Axis and Angle so we can now rotate the object using any one of Code Block below.

A.    Transform using QuaternionRotation3D:

 Transform3DGroup group = solidCubeMV3D.Transform as Transform3DGroup;

QuaternionRotation3D qr = new QuaternionRotation3D(new Quaternion(axisArg, stepAngleArg* 180 Math.PI));

RotateTransform3D r = new RotateTransform3D(qr);

group.Children.Add(r);

B.  Transform Using AxisAngleRotation3D:

 Transform3DGroup group = solidCubeMV3D.Transform as Transform3DGroup;

AxisAngleRotation3D a = new AxisAngleRotation3D();
               
                a.Axis = axisArg;
                a.Angle = stepAngleArg *(180 / Math.PI);

   RotateTransform3D r1 = new RotateTransform3D(a);

 group.Children.Add(r1);


5.2   Transformation of camera.

We can transform objects using camera's position and up direction also.

I have put one button in my demo project for transforming an object using camera.

And as above you can use QuaternionRotation3D or AxisAngleRotation3D.

 Vector3D rotationAxis = Vector3D.CrossProduct((Vector3D)camera.Position, camera.UpDirection);

             #region -  QuaternionRotation3D  -
            //RotateTransform3D rotationTransform = new RotateTransform3D(new QuaternionRotation3D(new Quaternion(rotationAxis, 1.0)));
            #endregion

            #region -  AxisAngleRotation3D  -
            RotateTransform3D rotationTransform = new RotateTransform3D(new AxisAngleRotation3D(rotationAxis, 1.0));
            #endregion

            camera.Position = rotationTransform.Transform(camera.Position);
            camera.UpDirection = rotationTransform.Transform(camera.UpDirection);