The Petzold.Media3D Library: The “Meshes” Classes
August 11, 2007
Roscoe, N.Y.
If you want to write classes that create 3D primitives for use in WPF 3D, and you also want to reference those classes in XAML files, there are basically two ways to do it, as I discuss in Chapter 6 of my book 3D Programming for Windows, "Algorithmic Mesh Geometries."
The first way is this: You write a class that exposes a public property named Geometry (for example) of type MeshGeometry3D. You define an instance of this class as a resource in a XAML file. Later on in the markup, you bind the Geometry property of a GeometryModel3D element with the Geometry property of the resource. Such resources can be shared among multiple GeometryModel3D elements.
That's the concept behind the classes defined in the Meshes directory of the Visual Studio project for the Petzold.Media3D library included with the source code for the book. The hierarchy for these classes begins with MeshGeneratorBase:
-
Object
DispatcherObject
DependencyObject
Freezable
Animatable
MeshGeneratorBase
BoxMesh
CylindricalMeshBase
CylinderMesh
HollowCylinderMesh
TubeMesh
FlatSurfaceMeshBase
PolygonMesh
PolyhedronMeshBase
CubeMesh
DodecahedronMesh
IcosahedronMesh
OctahedronMesh
TetrahedronMesh
SphereMesh
TeapotMesh
TorusMesh
All classes with the word Base in their names are abstract. To a certain extent, the names of these classes and some of the properties were inspired by DirectX 3D. The TeapotMesh class is a WPF 3D version of the famous Utah Teapot that I derived from the DirectX 3D teapot.
Here's a little XAML file that uses the TorusMesh class. You'll need to run this in XamlCruncher 2.0 with the Petzold.Media3D library loaded, as I've described in previous blog entries:
-
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cp="http://schemas.charlespetzold.com/2007/xaml"
Title="Rotating Torus"
WindowTitle="Rotating Torus">
<Page.Resources>
<cp:TorusMesh x:Key="torus"
Radius="1" TubeRadius="0.75"
Slices="100" Stacks="100" />
</Page.Resources>
<Viewport3D>
<ModelVisual3D>
<ModelVisual3D.Content>
<GeometryModel3D
Geometry="{Binding Source={StaticResource torus},
Path=Geometry}">
<GeometryModel3D.Material>
<DiffuseMaterial Brush="Cyan" />
</GeometryModel3D.Material>
<GeometryModel3D.Transform>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D x:Name="rotate"
Axis="-1 0 0" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
</GeometryModel3D.Transform>
</GeometryModel3D>
</ModelVisual3D.Content>
</ModelVisual3D>
<!-- Light sources. -->
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup>
<AmbientLight Color="#404040" />
<DirectionalLight Color="#C0C0C0"
Direction="2 -3 -1" />
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
<!-- Camera. -->
<Viewport3D.Camera>
<PerspectiveCamera Position="0 0 5"
LookDirection="0 0 -1"
UpDirection="0 1 0"
FieldOfView="45" />
</Viewport3D.Camera>
</Viewport3D>
<Page.Triggers>
<EventTrigger RoutedEvent="Page.Loaded">
<BeginStoryboard>
<Storyboard TargetName="rotate"
TargetProperty="Angle">
<DoubleAnimation
From="0" To="360" Duration="0:0:5"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Page.Triggers>
</Page>
Notice the binding from the GeometryModel3D to the resource.
With the exception of TeapotMesh all these classes define a TextureCoordinates collection in a rational manner so that you can apply non-solid brushes to the figures. For example, try replacing the DiffuseMaterial element with the following:
-
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<DrawingBrush TileMode="Tile" Viewport="0 0.05 0.1 0.1">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="AliceBlue">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0 0 10 10" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="Red">
<GeometryDrawing.Geometry>
<EllipseGeometry Center="5 5" RadiusX="2.5" RadiusY="2.5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
To derive from MeshGeneratorBase, you only need to override two methods: Triangulate and CreateInstanceCore, the latter of which must be overridden by all non-abstract classes that derive from Freezable. You'll probably also want to define a few properties backed by dependency properties. The property-changed method for these dependency properties should be the static PropertyChanged method defined in MeshGeneratorBase. If your class displays a valid object with all its properties set to default values, your class's constructor should explicitly call the instance version of PropertyChanged defined in MeshGeneratorBase:
-
PropertyChanged(new DependencyPropertyChangedEventArgs());
You'll override the Triangulate method like so:
-
protected override void Triangulate(DependencyPropertyChangedEventArgs args,
Point3DCollection vertices,
Vector3DCollection normals,
Int32Collection indices,
PointCollection textures)
{
...
}
The first parameter indicates the dependency property that changed to trigger this call. The other four parameters correspond to the four collections of the MeshGeometry3D class. Your responsibility is to clear those collections and then fill them with new values. Preferably you should do this without making any memory allocations. In some cases, you can use the DependencyPropertyChangedEventArgs parameter to avoid regenerating some of the collections. For example, if a figure is only changing its position in 3D space, then probably only the Point3DCollection needs to change, and the other collections can remain as they were.
Buy my book and we'll both be happy! | |||
Amazon.com | BookSense.com | quantumbooks | |
Barnes & Noble | Amazon Canada | Amazon UK | |
Amazon Français | Amazon Deutsch | Amazon Japan |