Let the user select and deselect 3D objects using WPF and C#

select and deselect 3D objects

This example extends the techniques used in Perform hit testing in a 3D program that uses WPF, XAML, and C# to let the user select and deselect 3D 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.XAxisArrow(6);
    model_group.Children.Add(
        mesh_x.SetMaterial(Brushes.Red, false));

    // Y axis.
    MeshGeometry3D mesh_y = MeshExtensions.YAxisArrow(6);
    model_group.Children.Add(
        mesh_y.SetMaterial(Brushes.Green, false));

    // Z axis.
    MeshGeometry3D mesh_z = MeshExtensions.ZAxisArrow(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 materials. 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 XAxisArrow, YAxisArrow, and ZAxisArrow 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;
        }
    }
}

This 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 hits 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 the SelectableModels list, at least if you have a lot of selectable objects.


Download Example   Follow me on Twitter   RSS feed   Donate




This entry was posted in algorithms, drawing, geometry, graphics, mathematics, wpf, XAML and tagged , , , , , , , , , , , , , , , , , , , , , , , . Bookmark the permalink.

4 Responses to Let the user select and deselect 3D objects using WPF and C#

  1. Ardahan says:

    Hi I want to ask a question about using List and Dictionary.
    In “Perform hit testing in a 3D program that uses WPF, XAML, and C#” article you used Dictionary whereas in here you are using List for store models. Which one is the best solutions. I am read about dictionary is much faster than list and I am implement this code using dictionary. What do you suggest I do then?

    And lastly thank you for all of that helper methods, it saves me a lot.

    • RodStephens says:

      Good question. A dictionary is much faster in general, but if the list is small the difference may not matter. In fact for a very small list the List might be faster just because it is simpler. The List is also smaller with less “wasted” space.

      Just to make things even more confusing, there’s also a HashSet class that lets you represent a set of values. It stores values similarly to the very fast way a Dictionary does but it doesn’t associate data values with keys. In that sense it’s more like a List.

      So here’s the difference:

      A Dictionary associates keys with data values. The example “Perform hit testing in a 3D program that uses WPF, XAML, and C#” uses the selected model as a key to look up the model’s name so in that example a Dictionary makes the most sense.

      The current example just needs to know if the clicked model is selectable, as opposed to a coordinate axis or something else that the program doesn’t want to allow you to select. In this case you could use a List (if there aren’t too many models) or a HashSet (if there are a lot of models).

      If you don’t know how big the list will be, you may find the List more intuitive or you could use a HashSet for better performance just in case.

      [.NET Graphics Programming Omnibus]

      My upcoming book .NET Graphics Programming Omnibus will cover this sort of topic and much, much more, if the project is funded! See my Kickstarter project for more information.

  2. Ramesh says:

    can you explain me how to use graphical point plotting in x axis and y axis instead of line plotting with textbox for x axis and y axis?

Leave a Reply

Your email address will not be published. Required fields are marked *