This step reviews how to select/highlight, reorder and delete layers.
![]() | ![]() |
The soccer application already has a GUI operator which performs a 'window/area selection'. If we take a look at the CSoccerView::OnWindowSelect method, we can see that it sets the current operator to be the HOOPS/MVO operator called HOpSelectArea.
The HOpSelect<selection_type> classes pass mouse information to the appropriate HOOPS/3dGS selection subroutines in order to determine what entities are hit. They use the HSoccerSelectionSet class (created in HSoccerView::Init) to actually select and deselect any hit entities. The HSoccerSelectionSet methods highlight/dehighlight the entities, and place their HOOPS/3dGS keys into a selection list for future access. Note that HSoccerSelectionSet is derived from the HOOPS/MVO HSelectionSet class which provides base-level selection (highlighting) functionality.
The HOpSelectArea operator selects all items that lie within the selection region. However, it's useful to also do a 'single-click' selection, which is referred to as an 'aperture' selection in the HOOPS/3dAF world. HOOPS/MVO already has such an operator, called HOpSelectAperture. Let's extend the app to add this capability. We can easily do this by adding a new button to the GUI which is mapped to the following new method on the MFC view:
We have to also include the include HOpSelectAperture.h file into SoccerView.cpp, and we're done. We can now perform both aperture and area selections.
By default, the HSoccerSelectionSet methods select on geometric entities. If only 1 entity is hit, then only that item is highlighted/selected. Experiment with selecting on the top edge of the soccer field, and note that only the field's border gets selected. However, let's recall that we want to select on entire 'layers' which are denoted by segments, and in this case, the border of the field is part of the 'base_field' layer which includes the inner kickoff circle. We can achieve layer selection by extending the HSoccerSelectionSet class to provide 'segment-level' selection logic.
Let's first abstract the Soccer application's selection functionality to provide 2 levels of selection: 'entity' and 'segment' ('segment' being equivalent to 'layer' in this case). The HOOPS/MVO HSelectionSet class already defines an enumerated type called HSelectLevel which we can utilize. We'll extend the existing HSoccerSelectionSet class to add a new member called m_SelectLevel along with methods to Get/Set the selection level, and overload IsSelected.
(Contents of HSoccerSelectionSet.h)
{private:HSelectLevel m_SelectLevel;public:HSoccerSelectionSet(HBaseView* view);virtual void Select(HC_KEY key, int num_include_keys, HC_KEY * include_keys, bool emit_message = false);virtual void DeSelect(HC_KEY key, int num_include_keys, HC_KEY * include_keys, bool emit_message = false);virtual void SelectFromMessage(HC_KEY key, int num_include_keys, HC_KEY * include_keys, bool emit_message = false);virtual void DeSelectFromMessage(HC_KEY key, int num_include_keys, HC_KEY * include_keys, bool emit_message = false);};
First, we set the starting segment level to 'Segment':
{m_SelectLevel = Segment;}
Next, we overload the HSoccerSelectionSet::Select, HSoccerSelectionSet::DeSelect, and HSoccerSelectionSet::IsSelected methods to first check whether we are in 'segment' selection mode (which essentially means 'layer' selection mode). We check for this because perhaps our application will have a regular 'entity' selection mode to allow the user to select on individual pieces of geometry, in which case we don't want to filter for 'layer' segments.
If we are in 'segment' selection mode, we check if the key refers to a 'layer' segment. If it doesn't, we get it's owner, moving up the tree as necessary. A key value of -1 (invalid key) means we've moved to the top of the tree and thus didn't select on any layer geometry at all, in which case we don't want to try and Select/DeSelect it. The Select method code is as follows:
void HSoccerSelectionSet::Select(HC_KEY key, int num_include_keys, HC_KEY * include_keys, bool emit_message){struct vlist_s *pLayers = pModel->GetLayerList();// if we are in Segment level selection, proceed with key filteringif (m_SelectLevel == Segment){// if the key doesn't refer to a 'layer' segment, get it's owner (moving up the tree as necessary)while (!vlist_item_exists(pLayers, (void *)key) && (key != -1))key = HC_KShow_Owner_By_Key(key);}// Make sure we actually have a layer segment,// If so, then call the base class to select, and bring the selected segment (layer) to the frontif (key != -1){HSelectionSet::Select(key, num_include_keys, include_keys, emit_message);}}
The DeSelect and IsSelected methods will contain similar code as above.
For now, we'll overload the base class' method and just call the base implementation. (We'll need to add some custom code discussed in the next section.)
Now the application can select/deselect layers, and thus manipulate them. Experiment with the the selection operator and note that when selecting on the top edge of the soccer field, both the border and inner-circle geometry gets selected since we are selecting on the 'base_field' layer. However, you may note some issues with the ordering of layers after selecting and deselecting, and we'll address that next.
Recall that we have a 2d scene-graph where all objects have an equivalent z-value, and the item that appears on front is non-deterministic. However, when we select a layer, we might want it drawn on top of all other layers. We can bring the selected layer to the front of all other layers by calling Set_Rendering_Options("depth range = (0.0, 0.25)")
After selecting a layer, it will still be left in front of the other layers after deselecting. This will result in the red-colored 'goals' geometry being hidden after selecting on the 'penalty_areas' layer. To correct this, we would restore the original layer ordering after deselecting geometry by using the depth range that was originally set when the model was created.
Let's add some functionality to delete the selected layers. We will add a GUI button that maps to a CSoccerView::OnDeleteLayers() method, and then perform the work there.
If we only cared about deleting the items in the selection list, we could simply call HSelectionSet::DeleteSelection. However, we must also remove each selected layer from our HSoccerModel::m_pLayers list structure, and if we called HSelectionSet::DeleteSelection first, then there would be no selected layers left to refer to! One option is to first loop through the selection list, find/remove each one from our 'layers' list, then call HSelectionSet::DeleteSelection, but that is inefficient.
A more efficient approach is to utilize the HSelectionSet::DeleteSelection source code itself (which walks through the selection list once), and remove each selected layer from our 'layers' list. Let's create a new method on our CSoccerView object that gets called when the user selects the 'DeleteLayers' button. We'll have that method call a new HSoccerView::DeleteLayers method.
In HSoccerView::DeleteLayers, we get the current selection set which contains the currently selected layer.
Then call DeleteSelection() to delete all the items currently selected layer.
pSelection->DeleteSelection();
After we're all done, we do an Update().
Update();}
Exercise 1: Note that HOOPS/MVO contains additional selection operators that let you select by Polyline and Polygon. (HOpSelectPolyline, etc...) Add one or both of these selection operators to your application and experiment with them.
Exercise 2: Enhance the application so that it draws a selected layer on top of all other layers, and put's it back to it's original depth range after deselecting. In review, this is done by calling Set_Rendering_Options("depth range" = (val1, val2)")
Exercise 3: Enhance the application so that it can 'hide' and 'unhide' the selected layers:
A. Hiding: Do this by adding a new GUI button which will call your new CSoccerView::Hide() method. In this method, you would open up each segment in the selection list, and turn the visibility of everything off. This is achieved by calling Set_Visibility(). Refer to that routine for the proper syntax
B. UnHiding: Clicking on a new GUI button should call a new method called CSoccerView::UnHide() In this method, you could simply take a brute force approach of looping through all the segments stored in the HSoccerModel::m_pLayers list, and setting the line/edge/text visibilities to 'on'. There is a flaw with this method: what if some of the lines/text/edges in the model explicity had their visibilities set to 'off' when the model was created, thus those setting were an attribute ofthe model? Our brute force approach would cause a problem in this case, since it will turn line/edge/text visibility back on regardless of their original value, and the user may suddenly see parts of geometry that should never be visible.
A more robust method for setting/unsetting attributes is to first store away any explicitly set Visibility attributes before modifying them, and then retrieve the stored values when restoring the item back to it's original attributes. (You obtain the current explicitly set attribute, if any, by first calling ::Show_Existence(<attribute>) and then if an attribute setting exists, you then call HC_Show_<attribute>). Further modify your Hide/UnHide logic to use this correct approach.