This step reviews the code associated with loading a model into your application.
The CSpheresDoc::OnOpenDocument() method gets called when a new document is opened. Note that this method creates/initializes a new model object, and then calls the the model's Read() method.
BOOL CSpheresDoc::OnOpenDocument(LPCTSTR lpszPathName){if (!CHoopsDoc::OnOpenDocument(lpszPathName))return FALSE;m_pHoopsModel = new HSpheresModel();m_pHoopsModel->Init();if (((HSpheresModel *)m_pHoopsModel)->Read(lpszPathName) != InputOK)return FALSE;return TRUE;}
Let's take a closer look at what actually happens when HSpheresModel::Read() gets called. Reviewing the base class' HBaseModel::Read() method, we can see that it will load the contents of a file if an HInputHandler object has been registered to handle that particular file format; the file extension is used as the basis for determing the file format. Internally, the Read() method calls the HInputHandler::FileInputByKey() method of the registered HInputHandler object, which in turn parses the file contents and maps them to appropriate HOOPS/3dGS scene-graph items (segments, geometry and attributes). HOOPS/MVO contains built in support for reading a variety of file formats whose handlers are listed in the HInputHandler reference. If you wanted to customize the import process for a particular file format, you would overload it's HInputHandler::FileInputByKey() method.
When the user performs a 'File->SaveAs' operation in our sample MFC app, the CSpheresView::OnFileSaveAs() method gets called. If you look at the source to this function, you'll note that it does some GUI work to put up a FileSaveAs dialog with a default file extension type, and then calls a CSpheresView::SaveFile() utility method. This is where the real work happens.
After doing some extension processing and checking that the filename is non-null, SaveFile() handles model-export as follows:
bool CSpheresView::SaveFile( LPCTSTR csFilePathName ){..if (streq(extension, "hsf")) // Write HOOPS Stream File{// do some custom handling if we're exporting an .hsf}else((HSpheresModel*)pDoc->m_pHoopsModel)->Write(csFilePathName, m_pHView, 0, width, height);..}
Note: the code above could have just called the Write method for the .hsf file format as well; it just so happens that the precreated implementation of SaveFile (precreated by the AppWizard) does a bit of special handling of the .hsf file extension prior to export.
Similar to model-import (which calls HSpheresModel::Read() ), model export simply involves calling HSpheresModel::Write(). If we review the base class' HBaseModel::Write() method, we can see that it will export the model to a file if an HOutputHandler object has been registered to handle that particular file type; the file extension is used as the basis for determing the file format. Internally, the Write() method calls the HOutputHandler::FileOutputByKey() method of the registered HOutputHandler object, which will traverse/query the contents of scene-graph underneath the 'model' segment and export the data to the designated file format. HOOPS/MVO contains built in support for writing a variety of file formats whose handlers are listed in the HOutputHandler reference. If you wanted to customize the export process for one of these file formats, you would overload it's HOutputHandler::FileOutputByKey() method.
HBaseModel::Read() and HBaseModel::Write() will automatically handle reading/writing of several file types, and we've seen how we can customize reading/writing of those presupported types, but what if we want to read/write a custom file type? This is achieved by registering a new HInputHandler/HOutputHandler with the HOOPS/MVO HIOManager class. Section 4.0 of the HOOPS/MVO Programming Guide outlines the steps in involved in this process.
Let's extend our 'Sphere' application to read and write files that have the .'sph' extension. However, rather than start from scratch, let's cheat a little bit for ease of demonstration, and define an .sph file as having the exact same format as an .hsf file. Therefore, we can copy the HIOUtilityHsf class' .h and .cpp files (located in <hoops>/dev_tools/hoops_mvo/source), include it in our application's project, and:
Extract from spheres/HIOUtilitySpheres.h:
class HIOUtilitySpheres: public HInputHandler, public HOutputHandler{public:HIOUtilitySpheres() {SetInputOps(HInputOpFileInputByKey);SetOutputOps(HOutputOpFileOutputByKey);};virtual ~HIOUtilitySpheres() {;};/* ! Register this object to serve as the input handler for files with the .sph extension */void RegisterInputHandlerTypes() {HIORegisterInputType("sph",this);};const char * GetInputTypesString() {return "sph";};HInputHandlerStyle GetInputStyle() {return HInputHandlerStyleModel;};HFileInputResult FileInputByKey(const char * FileName, HC_KEY key, HInputHandlerOptions * options);/* ! Register this object to serve as the output handler for files with the .sph extension */void RegisterOutputHandlerTypes() {HIORegisterOutputType("sph",this);};const char * GetOutputTypesString() {return "sph";};HOutputHandlerStyle GetOutputStyle() {return HOutputHandlerStyleModel;};HFileOutputResult FileOutputByKey(const char * FileName, HC_KEY key, HOutputHandlerOptions * options);};
Now, we need to create our new Input/Output handler and register it with MVO, which will enable the HBaseModel::Read() method to automatically dispatach a .sph file to our new handler. We'll first enhance the CSpheresApp object (defined in Spheres.h) to include a pointer to our new I/O handler:
class CSpheresApp : public CHoopsApp{protected:HIOUtilitySpheres * m_pIOUtilitySpheres;};
Next we create the handler and register it in CSpheresApp::InitInstance():
BOOL CSpheresApp::InitInstance(){..HIOUtilitySpheres * m_pIOUtilitySpheres = new HIOUtilitySpheres();HIORegisterInputType("sph", m_pIOUtilitySpheres);HIORegisterOutputType("sph", m_pIOUtilitySpheres);..}
Finally we cleanup:
int CSpheresApp::ExitInstance(){..H_SAFE_DELETE(m_pIOUtilitySpheres);..}
Our application can now read and write .sph files. To test our code, let's take the /datasets/hsf/cad/bnc.hsf sample dataset located in the datasets package (available from the Tech Soft 3D Developer Zone), rename it to have an .sph extension. and then read it in. Then, do a File->SaveAs and save it to a file with a .sph extension. (Remember, since our .sph handler is actually using the same code as the .hsf handler, we can simply rename an .hsf to a .sph and read that in.)
If you wanted to read and/or write your own custom file format (or some other standard format that HOOPS/MVO doesn't currently support), you would register a custom handler as outlined above, and modify it's input and/or output handler methods. In review, these methods are HInputHandler::FileInputByKey() and HOutputHandler::FileOutputByKey() If we take a closer look at the source code for the HIOUtilitySpheres::FileInputByKey and HIOUtilitySpheres::FileOutputByKey methods, we can see that they both open up the segment referred to by the 'Key', and then either read or write an '.hsf' file type by calling an HSF read/write utility function. For the read case, you would replace this utility function with the code necessary to parse your custom file and map it to the HOOPS/3dGS scene-graph. For the write case, you would replace the utility function with code that traverses/queries the HOOPS/3dGS scene-graph and exports it's contents to your custom file type.
Exercise 1: 'Redefine' the .sph file format to be an ASCII file that contains 'spheres', where each line in the file contains a center point (three floats denoting x,y,z), a radius (a float), and number of sides to indicate the tesselation level (an integer). When reading in a sphere, store this parametric information in a data-stucture. Then when exporting, iterate through this data structure and export the parametric information for each sphere.
A. Import
This involves rewriting the HIOUtilitySpheres::FileInputByKey method to parse through the 'spheres' in the .sph file, and insert them into the segment identified by the 'key' argument.
B. Export
You will need to rewrite the HIOUtilitySpheres::FileOutputByKey method to open up the segment specified by the passed in 'key' argument, iterate through all the 'spheres' in our data structure, and export them to an .sph file.
Exercise 2:
Note that the application let's you create new spheres from the GUI. This functionality is hooked up to a HOpCreateSphere operator which is 'disconnected' from our 'sphere' FileIO management code above. In other words, the spheres that we create don't have an associated entry in our data structure, so when we save an 'sph' file, our newly created spheres won't be in the file. Enhance the application so that when it creates new spheres, it adds an entry in our 'spheres' data structure just like above. This will involve taking note of the function that gets called when a new sphere is created and customizing that function to look at the parametric info for that sphere and add an entry to the 'spheres' data structure. (Hint: HUtilityGeometryCreation::CreateSphere gets called, but note that there are 2 different implementations of that function.)
Extra Credit!
If we wanted to the .sph file to accurately reflect what we see on the screen, we'd have to do some further work, and if you've gone through the 2D Tutorial, you are hopefully aware of a limitation with the application.
Specifically, the application supports 'deletion' of selected entities. But by default, the selection code knows NOTHING about our 'sphere' data structure; it simply stores the keys of the selected HOOPS/3dGS graphics primitives (shells, in this case), and deletes them. So, if we deleted some spheres and then saved an .sph file right now, those 'deleted' spheres would still be exported to the file because we haven't update our 'spheres' data structure. But right now, we don't have any associativity between the 'keys' of the HOOPS/3dGS object, and it's corresponding 'sphere' information in our datastructure.
To address this issue, we would have to first enhance the 'sphere' data structure so that each entry also contain the 'key' for the HOOPS/3dGS shell associated with the parametric sphere data. (This would involve updating both the import, and sphere-creation code that we wrote in Exercises 1 and 2. After doing so, we'd then enhance the deletion code so that it looks for the 'key' of the deleted sphere in our 'spheres' data structure and removes its entry from the array. Then, when we export, we'll only export spheres that are actually still in our model.