This step reviews creation of a simple 2D scene-graph that represents a soccer field. It discusses the concept of layers, and reviews how to force certain layers to be drawn on top of others by using the depth range option of Set_Rendering_Options.
As the AppWizard documentation diagrams, the CSoccerDoc object points to an HSoccerModel object. If we also review Section 5.0 of the HOOPS/MVO Technical Overview (specifically the diagram of the starting HOOPS/3dGS segment hierarchy encapsulated by the HOOPS/MVO objects), we see that the HSoccerModel object in turn serves as a wrapper for the HOOPS/3dGS segment at the root of the segment tree representing the model. The CSoccerDoc::OnNewDocument method creates a new HSoccerModel object (which is derived from HBaseModel) and calls HSoccerModel::Init(). We will need to customize the model object to define our scene-graph.
(Extracts from HSoccerModel.h)
class HSoccerModel : public HBaseModel { public: struct vlist_s * GetLayerList(){return m_pLayers; }; protected: // keys of the segments that represent the layers in the scene-graph struct vlist_s *m_pLayers; };
We've added a pointer to a list data structure, which will hold the keys of the segments that represent each main part of our model. This list of segment keys will come in handy down the road.
#include "vlist.h"
We simply add a line to initialize our list structure pointer.
HSoccerModel::HSoccerModel() { m_bSolidModel = false; SetBRepGeometry(false); m_pLayers = 0; }
2D-oriented applications usually have the concept of layers, where each layer contains a set of geometric entities that represent a part of the drawing/model. For example, in an architectural drawing, the heating, ventilation and electrical systems may each be represented by a layer. A layer is most commonly represented by a segment (along with its underlying segment tree) in the HOOPS/3dGS scene-graph. We'll assume that each segment in our soccer application represents a layer. Our 'text' geometry can include both text primitives and associated entities like polylines to denote arrows, thus we'll place all text geometry into 'text' subsegments of each layer. This allows us to easily modify the text information; we may want to hide it, change the line attributes, etc...
For our example, we create 3 parts of the soccer field; the 'base_field', the 'penalty_areas', and the 'goals'. The segments are created using the HOOPS/3dGS Open_Segment routine that returns a key, and that key is stored in the m_pLayers list.
First we call the base class' Init method and allocate our list structure. Then we move onto creating the scene-graph. The 'title' segment contains a text string describing the drawing, and the 'base_field' segment contains geometry representing the border and inner circle of the field. (HUtility is a utility class provided included with HOOPS/MVO, and HUtility::InsertRectangle inserts a HOOPS/3dGS polyline into the currently open segment.)
void HSoccerModel::Init() { HC_KEY seg_key; HPoint points[3]; HPoint center; center.x = 0.0; center.y = 0.0; center.z = 0.0; HBaseModel::Init(); m_pLayers = new_vlist(malloc, free); HC_Open_Segment_By_Key(GetModelKey()); HC_Open_Segment("title"); HC_Insert_Text(0.0, 0.8, 0.0, "Sample Soccer Field"); HC_Close_Segment(); seg_key = HC_Open_Segment("base_field"); vlist_add_last(m_pLayers, (void*)seg_key); HUtility::InsertRectangle(".", -0.75, -0.5, 0.75, 0.5); HC_Insert_Line(0.0, 0.5, 0.0, 0.0, -0.5, 0.0); bool success = HUtilityGeometryCreation::GeneratePointsOnCircle(points, center, 0.2f, HUtility::XY, 3, false); if (!success) return; HC_Insert_Circle(&points[0], &points[1], &points[2]); HC_Open_Segment("text"); HC_Insert_Line(-0.85, 0.5, 0.0, -0.85, 0.1, 0.0); HC_Insert_Line(-0.85, -0.5, 0.0, -0.85, -0.1, 0.0); HC_Insert_Line(-0.90, 0.5, 0.0, -0.80, 0.5, 0.0); HC_Insert_Line(-0.90, -0.5, 0.0, -0.80, -0.5, 0.0); HC_Insert_Text(-0.85, 0.0, 0.0, "75m"); HC_Insert_Line(-0.75, 0.6, 0.0, -0.1, 0.6, 0.0); HC_Insert_Line(0.75, 0.6, 0.0, 0.1, 0.6, 0.0); HC_Insert_Line(-0.75, 0.55, 0.0, -0.75, 0.65, 0.0); HC_Insert_Line(0.75, 0.55, 0.0, 0.75, 0.65, 0.0); HC_Insert_Text(0.0, 0.6, 0.0, "120m"); HC_Set_Text_Alignment("**"); HC_Close_Segment(); HC_Close_Segment();
Next, we create a subsegment containing geometry denoting the penalty areas. We want a separate segment for this geomety since we will want to independently control its drawing order, as we'll see below. We'll need to create a new local utility method called HSoccerModel::InsertFilledRectangle, to insert a HOOPS/3dGS polygon into the currently open segment. It would be defined as follows:
1. declare HSoccerModel::InsertFilledRectangle within HSoccerModel.h and include #include "HUtilityGeometryCreation.h":
#include "HUtilityGeometryCreation.h" static void InsertFilledRectangle (const char * seg, float x0, float y0, float x1, float y1);
2. define the implementation in HSoccerModel.cpp :
void HSoccerModel::InsertFilledRectangle ( const char *seg, float x0, float y0, float x1, float y1) { HPoint pts[5]; pts[0].x = x0; pts[0].y = y0; pts[0].z = 0.0F; pts[1].x = x0; pts[1].y = y1; pts[1].z = 0.0F; pts[2].x = x1; pts[2].y = y1; pts[2].z = 0.0F; pts[3].x = x1; pts[3].y = y0; pts[3].z = 0.0F; pts[4].x = x0; pts[4].y = y0; pts[4].z = 0.0F; HC_QInsert_Polygon (seg, 5, pts); }
The call to Set_Visibility will hardcode face visibility to be on. Alternately, the visibility setting could have been omitted, and the face visibility setting could have been made at a higher level. However, we decided to make the face visibility a 'property' of the penalty area geometry (a property of the model itself) rather than making it a viewing property that could be toggled higher up the tree. However, we will see later how this setting can be overridden from a higher level.
seg_key = HC_Open_Segment("penalty_areas"); vlist_add_last(m_pLayers, (void*)seg_key); HSoccerModel::InsertFilledRectangle(".", -0.75, -0.3, -0.50, 0.3); HSoccerModel::InsertFilledRectangle(".", 0.75, -0.3, 0.50, 0.3); HC_Set_Visibility("faces = on"); HC_Open_Segment("text"); HC_Insert_Line(-0.40, 0.3, 0.0, -0.40, 0.05, 0.0); HC_Insert_Line(-0.40, -0.3, 0.0, -0.40, -0.05, 0.0); HC_Insert_Line(-0.45, 0.3, 0.0, -0.35, 0.3, 0.0); HC_Insert_Line(-0.45, -0.3, 0.0, -0.35, -0.3, 0.0); HC_Insert_Text(-0.40, 0.0, 0.0, "25m"); HC_Insert_Line(-0.75, 0.4, 0.0, -0.70, 0.4, 0.0); HC_Insert_Line(-0.50, 0.4, 0.0, -0.55, 0.4, 0.0); HC_Insert_Line(-0.50, 0.35, 0.0, -0.50, 0.45, 0.0); HC_Insert_Text(-0.625, 0.4, 0.0, "18m"); HC_Set_Text_Alignment("**"); HC_Close_Segment(); HC_Close_Segment();
At the bottom of this page, we'll review setting the render mode of the MVO view object to 'wireframe'. The default implementation of the wireframe setting is to still perform z-buffer hidden surface removal (since in 3d scenes, it can be important to have the 'edges/lines' for objects correctly drawn in front or behind those of other objects). However, since all the geometry has a z-value of 0, there's no guarantee of any drawing order.
To set a drawing order when hidden surface removal is enabled, we can set the depth range Rendering Option which will tell HOOPS/3dGS to draw geometry within a specific z-depth range. We'll set this in the penalty areas segment to make sure they get drawn on top of the 'base_field' geometry:
seg_key = HC_Open_Segment("penalty_areas"); HC_Set_Rendering_Options("depth range=(0.3,0.6)"); HC_Close_Segment();
The next code block opens up a segment that contains geometry to represent the goals, and sets a depth range which will ensure that they get drawn in front of the 'penalty_areas' geometry. It also hardcodes face visibility to 'on', and makes the goal geometry red.
seg_key = HC_Open_Segment("goals"); HC_Set_Rendering_Options("depth range=(0.0,0.25)"); vlist_add_last(m_pLayers, (void*)seg_key); HSoccerModel::InsertFilledRectangle(".", -0.75, -0.15, -0.70, 0.15); HSoccerModel::InsertFilledRectangle(".", 0.75, -0.15, 0.70, 0.15); HC_Set_Visibility("faces = on"); HC_Set_Color("faces = red"); HC_Open_Segment("text"); HC_Insert_Line(0.85, 0.15, 0.0, 0.85, 0.05, 0.0); HC_Insert_Line(0.85, -0.15, 0.0, 0.85, -0.05, 0.0); HC_Insert_Line(0.80, 0.15, 0.0, 0.90, 0.15, 0.0); HC_Insert_Line(0.80, -0.15, 0.0, 0.90, -0.15, 0.0); HC_Insert_Text(0.85, 0.0, 0.0, "6m"); HC_Set_Text_Alignment("**"); HC_Close_Segment(); HC_Close_Segment(); HC_Close_Segment(); }
It should be noted that the above scenario (where a predefined scene-graph is created when the user performs a 'NewDocument' operation) is not necessarily what you may want to do in your application. More commonly, your application will read in it's own custom file format (discussed in the '3D' tutorial), and the user beings interacting with it. However, in some cases an application may provide a 'model template' when the user creates a new document, in which case the HBaseModel::Init() method would be customize to create a 'starting' scene-graph as outlined above.
Exercise: Rather than explicity (and painstakingly!) defining each line that represent the field measurements, write a utility method for the HSoccerModel class that takes 3 arguments (2 endpoints and a text string) and inserts the appropriate polyline and text geometry representing a 'measurement entity'. Then call that function to insert each measurement.
The AppWizard-generated application has predefined a method called HSoccerModel::DeleteAllEntities, which will get called from the destructor. This is where we delete any information associated with the model. We could clean up the scene-graph by iterating through our vlist data structure and individually deleting each segment that still exists, but it's easier to just call HBaseModel::Flush which cleans out the scene-graph for us. We also need to reset our list structure.
void HSoccerModel::DeleteAllEntities() { HBaseModel::Flush(); delete_vlist(m_pLayers); m_pLayers = new_vlist(malloc, free); }
When destructing, we call HSoccerModel::DeleteAllEntities to clean out the scene graph, and we also delete/initialize the list structure.
HSoccerModel::~HSoccerModel() { DeleteAllEntities(); delete_vlist(m_pLayers); m_pLayers = 0; }
The CSoccerView::OnInitialUpdate() method creates and initializes the CSoccerView object, and sets the default render mode to 'gouraud'. Since we have a 2D scene, let's modify it to set the initial rendering mode to wireframe.
void CSoccerView::OnInitialUpdate() { . . //m_pHView->RenderGouraud(); m_pHView->RenderWireframe(); . . }
We could also customize the initial view attributes in the HBaseView object's Init() method, which in our case is HSoccerView::Init() However, one advantage of setting startup view attributes in the MFC GUI method is the fact that we have access to both the GUI view object (CSoccerView) AND the HOOPS/MVO view object (HSoccerView). If we only put startup code in HSoccerView::Init(), we'd have no access to the MFC View object. The choice of where to put startup viewing code really depends on the needs of your application.
Let's put some additional view initialization code in the HOOPS/MVO view object's Init() method just for further example. We'll set the color of edges to blue.
void HSoccerView::Init() { HBaseView::Init(); m_pSelection = new HSoccerSelectionSet(this); m_pSelection->Init(); // set up some scene defaults HC_Open_Segment_By_Key(m_SceneKey); HC_Set_Color("edges = blue"); HC_Close_Segment(); }
Now we've defined the scene graph and have stored away the keys for each main segment.