|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
Introduction.NET 3.5 introduced a few new langauge features, oh and there was that little thing called LINQ. But with all the excitement, sometimes a few things get missed. Such as the new 3D elements that are available to WPF developers in .NET 3.5. This article will discuss the use of some of these new 3D related elements that are now in .NET 3.5. In order to demonstrate some (yeah I dont use all of the new elements in the
attached demo app) I picked something simple. I decided to have a bunch of 3D
meshes within a Here is what this article contains : A Video Of The Demo AppDue to the nature of 3D, the only way that I can do the attached demo application any justice, is to show you a video, which shows it in action. As such please click on the image below to see a video of the demo application in action
The New .NET 3.5 ElementsThere are 3 new 3D related elements in .NET 3.5, each of which is described
below. But in order to understand why these new elements are so cool, you need
to go back to .NET 3.0 land. In .NET 3.0, This worked something like this: private void viewport_MouseDown(object sender, MouseButtonEventArgs e)
{
Viewport3D viewport = (Viewport3D)sender;
Point location = e.GetPosition(viewport);
HitTestResult hitResult = VisualTreeHelper.HitTest(viewport, location);
if (hitResult != null && hitResult.VisualHit == SOME_VISUAL)
{
// Hit the visual.
}
RayMeshGeometry3DHitTestResult meshHitResult = hitResult as RayMeshGeometry3DHitTestResult;
if (meshHitResult != null && meshHitResult.ModelHit == SOME_MODEL)
{
// Hit the model.
}
if (meshHitResult != null && meshHitResult.MeshHit == SOME_MESH)
{
//do something
}
}
Now this isn't that bad, for one ContainerUIElement3D
<Viewport3D>
<Viewport3D.Camera>
<PerspectiveCamera Position="8,3,0" LookDirection="-8,-3,0" />
</Viewport3D.Camera>
<!-- The container has the two cubes as its children -->
<ContainerUIElement3D MouseDown="ContainerMouseDown">
<ContainerUIElement3D.Transform>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D x:Name="containerRotation" Axis="0, 1, 0" Angle="0" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
</ContainerUIElement3D.Transform>
<!-- Cube 1 -->
<ModelUIElement3D MouseDown="Cube1MouseDown">
<ModelUIElement3D.Transform>
<TranslateTransform3D OffsetZ="1.5" />
</ModelUIElement3D.Transform>
<ModelUIElement3D.Model>
<GeometryModel3D Geometry="{StaticResource CubeMesh}">
<GeometryModel3D.Material>
<DiffuseMaterial x:Name="cube1Material" Brush="Blue" />
</GeometryModel3D.Material>
</GeometryModel3D>
</ModelUIElement3D.Model>
</ModelUIElement3D>
<!-- Cube 2 -->
<ModelUIElement3D MouseDown="Cube2MouseDown">
<ModelUIElement3D.Transform>
<TranslateTransform3D OffsetZ="-1.5" />
</ModelUIElement3D.Transform>
<ModelUIElement3D.Model>
<GeometryModel3D Geometry="{StaticResource CubeMesh}">
<GeometryModel3D.Material>
<DiffuseMaterial x:Name="cube2Material" Brush="Green" />
</GeometryModel3D.Material>
</GeometryModel3D>
</ModelUIElement3D.Model>
</ModelUIElement3D>
</ContainerUIElement3D>
<!-- Lights -->
<ModelVisual3D>
<ModelVisual3D.Content>
<PointLight Color="White" Position="3, 10, 4" />
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D>
ModelUIElement3DAs previously stated these are new elements that provide rendering of a 3-D
model, that supports input, focus, and events. Using the same example as before,
noticing the <!-- Cube 2 -->
<ModelUIElement3D MouseDown="Cube2MouseDown">
<ModelUIElement3D.Transform>
<TranslateTransform3D OffsetZ="-1.5" />
</ModelUIElement3D.Transform>
<ModelUIElement3D.Model>
<GeometryModel3D Geometry="{StaticResource CubeMesh}">
<GeometryModel3D.Material>
<DiffuseMaterial x:Name="cube2Material" Brush="Green" />
</GeometryModel3D.Material>
</GeometryModel3D>
</ModelUIElement3D.Model>
</ModelUIElement3D>
</ContainerUIElement3D>
Viewport2DVisual3DAlthough the demo application doesn't actually use this, I can say a few words
about this new element. Basically the <Viewport3D>
<!-- Button on 3D -->
<Viewport2DVisual3D>
<!-- Give the plane a slight rotation -->
<Viewport2DVisual3D.Transform>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Angle="40" Axis="0, 1, 0" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
</Viewport2DVisual3D.Transform>
<!-- The Geometry, Material, and Visual for the Viewport2DVisual3D -->
<Viewport2DVisual3D.Geometry>
<MeshGeometry3D Positions="-1,1,0 -1,-1,0 1,-1,0 1,1,0"
TextureCoordinates="0,0 0,1 1,1 1,0"
TriangleIndices="0 1 2 0 2 3"/>
</Viewport2DVisual3D.Geometry>
<Viewport2DVisual3D.Material>
<DiffuseMaterial Viewport2DVisual3D.IsVisualHostMaterial="True"
Brush="White"/>
</Viewport2DVisual3D.Material>
<Button>Hello, 3D</Button>
</Viewport2DVisual3D>
</Viewport3D>
Demo AppSo what does the attached demo app actually do. Well it looks like the following diagram when it first starts
Where a number of 3d objects ( This is achieved as follows: <Tools:TrackballDecorator >
<Viewport3D>
<Viewport3D.Camera>
<PerspectiveCamera x:Name="camera" Position="-2,2,40"
LookDirection="2,-2,-40" FieldOfView="90" />
</Viewport3D.Camera>
<ContainerUIElement3D x:Name="container" />
<ModelVisual3D>
<ModelVisual3D.Content>
<DirectionalLight Color="White" Direction="-1,-1,-1"/>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D>
</Tools:TrackballDecorator>
So this sets up the Viewport3D ( The only other thing to note here is that I am using a This So in the code behind a number of this.Resources.Add("sphereMesh", Tesselate.Create(10, 10, 5));
.....
.....
ModelUIElement3D sphere1 = CreateSphere(brushes[1], points3D[0].X, points3D[0].Y, points3D[0].Z);
container.Children.Add(sphere1);
feedsForShapes.Add(sphere1, feeds[0]);
.....
.....
private ModelUIElement3D CreateSphere(Brush materialBrush, double OffsetX,
double OffsetY, double OffsetZ)
{
ModelUIElement3D sphere3D = new ModelUIElement3D();
sphere3D.MouseEnter += new MouseEventHandler(Sphere_MouseEnter);
sphere3D.MouseLeave += new MouseEventHandler(Sphere_MouseLeave);
sphere3D.MouseDown += new MouseButtonEventHandler(sphere3D_MouseDown);
GeometryModel3D sphere3D_Geom = new GeometryModel3D(
this.Resources["sphereMesh"] as MeshGeometry3D,
new DiffuseMaterial(materialBrush));
sphere3D.Model = sphere3D_Geom;
Transform3DGroup transGroup = new Transform3DGroup();
ScaleTransform3D scaleTrans = new ScaleTransform3D(1, 1, 1);
TranslateTransform3D translateTrans =
new TranslateTransform3D(OffsetX, OffsetY, OffsetZ);
RotateTransform3D rotateTrans = new RotateTransform3D();
rotateTrans.Rotation = new AxisAngleRotation3D(new Vector3D(0, 1, 0), 1);
transGroup.Children.Add(scaleTrans);
transGroup.Children.Add(translateTrans);
transGroup.Children.Add(rotateTrans);
sphere3D.Transform = transGroup;
return sphere3D;
}
The actual blog entries are read using a very simple bit of XLINQ, which is as follows: public List
The next section talks a little bit more about how the tesselates are created. Tesselate CreationWithin WPF there are no standard meshes that can be used as <!-- Simple flat, square surface -->
<GeometryModel3D.Geometry>
<MeshGeometry3D
TriangleIndices="0,1,2 2,3,0"
TextureCoordinates="0,1 1,1 1,0 0,0"
Positions="-0.5,-0.5,0 0.5,-0.5,0 0.5,0.5,0 -0.5,0.5,0" />
</GeometryModel3D.Geometry>
Perhaps this could do with a little explanation. The Positions property is the positions in 3D space X,Y,Z planes. So we can see if this were mapped out we would get something like
And the TriangleIndices property is the indices of the triangles that make up the GeometryModel3D.Geometry, in this case a simple square, which is made from 2 seperate triangles. This is how 3D works. Lets see these 2 triangles (basically triangles are the building blocks of any 3d mesh) :
But what I wanted for this article was some nice sphere. Or to be more precise a tessalate. Tesselation : A tessellation or tiling of the plane is a collection of plane figures that fills the plane with no overlaps and no gaps. One may also speak of tessellations of the parts of the plane or of other surfaces. http://en.wikipedia.org/wiki/Tesselate But what does that really mean in terms of a 3D mesh. Well consider the following image
We can see that dividing the sphere into divisions both around (theta) and
up from the bottom pole to the top pole (phi), we can create rectangles. And
we can treat each rectangle as we did with the above simple square
There is a helper class called /// <summary>
/// Tessellates the sphere and returns a MeshGeometry3D representing the
/// tessellation based on the given parameters
/// </summary>
/// <param name="tDiv">theta Divisions</param>
/// <param name="pDiv">phi divisions</param>
/// <param name="radius">radius</param>
public static MeshGeometry3D Create(int tDiv, int pDiv, double radius)
{
double dt = DegToRad(360.0) / tDiv;
double dp = DegToRad(180.0) / pDiv;
MeshGeometry3D mesh = new MeshGeometry3D();
for (int pi = 0; pi <= pDiv; pi++)
{
double phi = pi * dp;
for (int ti = 0; ti <= tDiv; ti++)
{
// we want to start the mesh on the x axis
double theta = ti * dt;
mesh.Positions.Add(GetPosition(theta, phi, radius));
mesh.Normals.Add(GetNormal(theta, phi));
mesh.TextureCoordinates.Add(GetTextureCoordinate(theta, phi));
}
}
for (int pi = 0; pi < pDiv; pi++)
{
for (int ti = 0; ti < tDiv; ti++)
{
int x0 = ti;
int x1 = (ti + 1);
int y0 = pi * (tDiv + 1);
int y1 = (pi + 1) * (tDiv + 1);
mesh.TriangleIndices.Add(x0 + y0);
mesh.TriangleIndices.Add(x0 + y1);
mesh.TriangleIndices.Add(x1 + y0);
mesh.TriangleIndices.Add(x1 + y0);
mesh.TriangleIndices.Add(x0 + y1);
mesh.TriangleIndices.Add(x1 + y1);
}
}
mesh.Freeze();
return mesh;
}
Cant remember exactly where this code came from, but it was than likely the WPF 3D teams blog http://blogs.msdn.com/wpf3d/ History27/03/08 : Initial release | ||||||||||||||||||||||