I have started looking at 3D again in WPF. I have in the past blogged about the Viewport2DVisual3D 3D WPF element. Well for what I am working on, I didn’t need to be able to put 2D interactive elements on a 3D surface, but I did want the 3D object to able to respond to Mouse
events. Now in the past, what you would have done is use a MouseDown
event on the Viewport3D
and do some sort of hit testing. Which was ok….
But now, there is the wonderful new .NET 3.5 element ModelUIElement3D
which is a fully fledged element that supports events. Hooray!
And there is also a new container element to host 1 or more ModelUIElement3D
elements.
Let's see an example:
<Window x:Class="Shapes.Window1″
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Shapes"
Title="Window1″ Height="610.122″ Width="633.46″>
<Window.Resources>
<!– The 3D cube –>
<MeshGeometry3D x:Key="CubeMesh"
TriangleIndices = "0,1,2 2,3,0
4,7,6 6,5,4
8,11,10 10,9,8
12,13,14 14,15,12
16,17,18 18,19,16
20,23,22 22,21,20″
Positions = "-1,-1,1 -1,-1,-1 1,-1,-1 1,-1,1
-1,1,1 -1,1,-1 1,1,-1 1,1,1
-1,-1,1 -1,1,1 1,1,1 1,-1,1
-1,-1,-1 -1,1,-1 1,1,-1 1,-1,-1
-1,-1,1 -1,1,1 -1,1,-1 -1,-1,-1
1,-1,1 1,1,1 1,1,-1 1,-1,-1″ />
</Window.Resources>
<Viewport3D>
<Viewport3D.Camera>
<PerspectiveCamera x:Name="camera" Position="-2,2,5″
LookDirection="2,-2,-5″ FieldOfView="90″ />
</Viewport3D.Camera>
<!– Container for 3D Elements –>
<ContainerUIElement3D x:Name="container">
<ContainerUIElement3D.Transform>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="0, 1, 0″ Angle="0″ />
</RotateTransform3D.Rotation>
</RotateTransform3D>
</ContainerUIElement3D.Transform>
<!– A fully Fledged 3d element complete with routed events –>
<ModelUIElement3D MouseDown="Cube1_MouseDown">
<ModelUIElement3D.Transform>
<Transform3DGroup>
<TranslateTransform3D OffsetZ="1.5″ />
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="0, 1, 0″ Angle="0″ />
</RotateTransform3D.Rotation>
</RotateTransform3D>
</Transform3DGroup>
</ModelUIElement3D.Transform>
<ModelUIElement3D.Model>
<GeometryModel3D Geometry="{StaticResource CubeMesh}">
<GeometryModel3D.Material>
<DiffuseMaterial x:Name="cube1Material" Brush="Blue" />
</GeometryModel3D.Material>
</GeometryModel3D>
</ModelUIElement3D.Model>
</ModelUIElement3D>
</ContainerUIElement3D>
<ModelVisual3D>
<ModelVisual3D.Content>
<DirectionalLight Color="White" Direction="-1,-1,-1″/>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D>
</Window>
And here is the C# code that has the event for the ModelUIElement3D
.
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Windows;
6: using System.Windows.Controls;
7: using System.Windows.Data;
8: using System.Windows.Documents;
9: using System.Windows.Input;
10: using System.Windows.Media;
11: using System.Windows.Media.Animation;
12: using System.Windows.Media.Media3D;
13: using System.Windows.Media.Imaging;
14: using System.Windows.Navigation;
15: using System.Windows.Shapes;
16:
17: namespace Shapes
18: {
19: 20: 21: 22: public partial class Window1 : Window
23: {
24:
25:
26: public Window1()
27: {
28: InitializeComponent();
29: }
30:
31:
32: 33: 34: 35: private void Cube1_MouseDown(object sender, MouseButtonEventArgs e)
36: {
37: ModelUIElement3D currentObject = sender as ModelUIElement3D;
38: if (currentObject.Transform is Transform3DGroup)
39: {
40: RotateTransform3D rotateTrans = null;
41:
42: Transform3DGroup transGroup =
43: currentObject.Transform as Transform3DGroup;
44: rotateTrans = TryFindChild<RotateTransform3D>(transGroup);
45: if (rotateTrans != null)
46: {
47:
48: DoubleAnimation doubleAnimation =
49: new DoubleAnimation(0,360,
50: new Duration(TimeSpan.FromSeconds(0.5)));
51: rotateTrans.Rotation.BeginAnimation(
52: AxisAngleRotation3D.AngleProperty, doubleAnimation);
53: }
54: }
55: }
56:
57:
58: 59: 60: 61: public static T TryFindChild<T>(Transform3DGroup parent)
62: where T : DependencyObject
63: {
64: foreach (DependencyObject child in parent.Children)
65: {
66: if (child is T)
67: {
68: return child as T;
69: }
70: }
71: return null;
72: }
73:
74:
75: }
76: }
And that is all there is to it. So in this example, if the cube gets a MouseDown
, I lookup its RotateTransform3D
and spin it around by 360 degrees.
And here is a zip file with the demo project code.