BLOG.CSHARPHELPER.COM: Let the user select and deselect objects in a 3D program that uses WPF, XAML, and C#
Let the user select and deselect objects in a 3D program that uses WPF, XAML, and C#
The example Perform hit testing in a 3D program that uses WPF, XAML, and C# shows how to tell when the user clicks an object in a 3D WPF program. This example uses similar techniques to let the user select and deselect objects.
This program contains several refinements over previous 3D WPF examples. In particular, its static MeshExtensions class includes more methods for adding simple shapes to a mesh. This example uses the AddBox extension method to add a bunch of cubes to the 3D model. That method simply creates the triangles needed to build a box. It's straightforward so it isn't shown here.
The following code shows how the program builds its three-dimensional objects.
// The currently selected model.
private GeometryModel3D SelectedModel = null;
// Materials used for normal and selected models.
private Material NormalMaterial, SelectedMaterial;
// The list of selectable models.
private List<GeometryModel3D> SelectableModels =
new List<GeometryModel3D>();
// Add the model to the Model3DGroup.
private void DefineModel(Model3DGroup model_group)
{
// Make the normal and selected materials.
NormalMaterial = new DiffuseMaterial(Brushes.LightGreen);
SelectedMaterial = new DiffuseMaterial(Brushes.Red);
// Create some cubes.
for (int x = -5; x <= 3; x += 4)
{
for (int y = -5; y <= 3; y += 4)
{
for (int z = -5; z <= 3; z += 4)
{
// Make a cube with lower left corner (x, y, z).
MeshGeometry3D mesh = new MeshGeometry3D();
mesh.AddBox(x, y, z, 2, 2, 2);
GeometryModel3D model = new GeometryModel3D(mesh, NormalMaterial);
model_group.Children.Add(model);
// Remember that this model is selectable.
SelectableModels.Add(model);
}
}
}
// X axis.
MeshGeometry3D mesh_x = MeshExtensions.XAxis(6);
model_group.Children.Add(mesh_x.SetMaterial(Brushes.Red, false));
// Y axis.
MeshGeometry3D mesh_y = MeshExtensions.YAxis(6);
model_group.Children.Add(mesh_y.SetMaterial(Brushes.Green, false));
// Z axis.
MeshGeometry3D mesh_z = MeshExtensions.ZAxis(6);
model_group.Children.Add(mesh_z.SetMaterial(Brushes.Blue, false));
}
The code first defines the variable SelectedModel to store a reference to the object that it currently selected. Initially it sets that object to null.
Next the program defines two materials: one to use for normal objects and one to use for selected objects. It then makes a List to hold the models that will be selectable.
The DefineModel method starts by initializing the normal and selected material. It then uses three nested for loops to create 27 cubes. It gives them all the normal material and saves them in the SelectableModels list.
The method then uses the static XAxis, YAxis, and ZAxis methods to add axis arrows to the model. Note that it doesn't save those objects in the SelectableModels list.
When the user clicks on the viewport, the following code executes.
// See what was clicked.
private void MainViewport_MouseDown(object sender, MouseButtonEventArgs e)
{
// Deselect the prevously selected model.
if (SelectedModel != null)
{
SelectedModel.Material = NormalMaterial;
SelectedModel = null;
}
// Get the mouse's position relative to the viewport.
Point mouse_pos = e.GetPosition(MainViewport);
// Perform the hit test.
HitTestResult result =
VisualTreeHelper.HitTest(MainViewport, mouse_pos);
// See if we hit a model.
RayMeshGeometry3DHitTestResult mesh_result =
result as RayMeshGeometry3DHitTestResult;
if (mesh_result != null)
{
GeometryModel3D model = (GeometryModel3D)mesh_result.ModelHit;
if (SelectableModels.Contains(model))
{
SelectedModel = model;
SelectedModel.Material = SelectedMaterial;
}
}
}
The code begins by deselecting the previously selected model. If SelectedModel is not null, the code sets that model's Material property to the normal material. It then sets SelectedModel to null.
Next the method gets the mouse's current position and performs the hit test. If the test hit an object, the program gets the GeometryModel3D object that was hit. If that model is in the SelectableModels list, the code saves a reference to the object in the SelectedModel variable and sets the model's Material to the selected material.
That's about all there is to this example. The rest of the details are the same as those used in previous examples. You can download the code to see how they work.
In a more complicated program such as a game, you would probably need to do more than just change the selected object's material to show that it is selected. For example, you might need to look up the clicked object to see what it is. Then if it's a door, potion, or medallion, the program can take appropriate action. To do that, you could replace the SelectableModels list with a dictionary that uses models as keys and some sort of class or structure as values. When the user clicks an object, you could look it up in the dictionary to get the associated data so you could figure out what to do.
Because dictionaries are so fast, that would even be more efficient than this version that uses SelectableModels list, at least if you have a lot of selectable objects.
Comments