.obj and .dae model loader and renderer with basic controls
Repository is at https://github.com/tobysmith568/SOFT-356_Model_Loader
- Visual Studio 2019 v16.3.9
- GLM v0.9.9.600
- nupenGL v0.1.0.1
The only setup for compiling this solution is to restore the NuGet packages. This software has been written only with Windows in mind.
The .exe can be run by double-clicking it, but should any errors occur and the program shut down then you will not be able to see the error message. As such, it is recommended that you open a command prompt, navigate to the directory of the .exe and run it from within that prompt. This will keep the window open when the program closes and any error messages can be read.
All the functionality of this software is configurable via the config.dat file located in the /media folder next to the .exe. Should the folder or file not exist then the program will attempt to create them on startup. If the program has to create the config.dat file then it will also create the default shaders alongside it.
Config within that file is a list of key/value pairs and sits in the following categories:
- Window options
- Background colour
- Render options
- Shaders
- Keybindings
- Model Rotation
- Model Translation
- Model Scale
- Model Options
- Window Options
- Current Model Swapping
- Exporting
Boolean options evaluate to true if they are equal to 1, else they are false.
All of the key bindings within the config file begin with the prefix KeyBinding_. In this README, key bindings are shown without the prefix and then give the default key in brackets, eg, MoveForward (W) or FirstModel (1). In the config file, the bound key is represented by a number, the relationships between the keys on a keyboard and these numbers can be found here on the GLFW website.
When the program opens you should see an empty window showing only the configured background colour, by default, this is a pale blue. To add a model you need to press the NewModel (N) key. This will take the context away from the OpenGL window and back to the console window where it will prompt you for a file path to a model; the path can be absolute or relative to the .exe. On pressing enter, the context will then return to the OpenGL window. If the model is large then the program will lock up while it loads in the new model. On a modern machine, files like creeper.obj should load instantly, files like low_poly_boat.obj should take around 10 seconds, and files like low_poly_boat.dae should take just over 2 minutes.
Any model which is loaded into the scene can be manipulated while it is the currently active model. The FirstModel (1), SecondModel (2) etc., keybindings can be used to swap the active model. Those 9 key bindings represent the first 9 models which are loaded into the scene, should there be more than 9 models loaded into the scene at a single time then any additional models past the 9th cannot be manipulated.
The DeleteModel (Delete) key binding can be used to remove models from the scene. It will always remove the model which was most recently added, not the currently active model.
The Reset (R) key binding can be used to reset the position, rotation, and scale of the currently active model.
The PolygonMode (P) key binding can be used to toggle between drawing faces and drawing lines.
The SaveModelAsBasic (F1) key binding can be used to export the currently active model into a file format I have developed called a .basic. Pressing this key binding will switch the context to the console window where it will ask you to enter a file location for where the model should be saved to; this path can be absolute or relative to the .exe. Please note that exporting will overwrite any existing file with the same name without warning. The file location you supply needs to contain the .basic file extension. Once a file has been exported it can be re-imported like any other .obj or .dae. It should be noted that the exported .basic requires the same texture images as the .obj or .dae it was created from - the export process will not duplicate the texture image file. If you export to a different folder or do not copy the image file yourself, the new .basic will not be able to load in.
While .basic file types are large in size due to the lack of compression, they can be advantageous. Because they have their indices in ascending order they are easy for developers to use in order to get to grips with how to load data into OpenGL. They store all the data in the order that it is needed programmatically, an advantage over the .dae file format when reading in the file a single line at a time without storing the whole file in memory. Another advantage to the .basic file format is that it stores geometric and material data in the same file, this is an advantage over the .obj/.mtl file format specifically which requires both files to be transported together.
Large amounts of functionality within the program is broken down and encapsulated within stateless utility classes:
ConfigUtil- used to read from the config.dat fileConsoleUtil- used to read from and write to the console windowFileUtils- many different actions for interacting with files, folder, and file pathsGLEWUtil- used to interact with the GLEW libraryGLFWUtil- used to interact with the GLFW libraryInputManager- used to register keyboard interactions and execute callback functions when those actions occur. (Note that this class is not stateless like the others as it stores the keyboard interactions to be executed)ModelLoaderFactory- used to return a model loader based on the file path it is given
These utility classes are all instantiated once within the main method and are then passed around wherever they are needed via constructor injection. Many of these classes rely on each other.
Each model loader class within the program extends the abstract class IModelLoader. When the ModelLoaderFactory is handed a file path, it returns an instance of the IModelLoader which is designed to handle the file in that path. Currently, there are three implementations:
- .obj (and .mtl and .png)
- .dae (and .png)
- .basic (and .png)
The core program is broken down into the following structure:
- Scene
- Shader Program
- Models[]
- Materials[]
- MVP
- Objects[]
- Meshes[]
- VAO
- Vertices VBO
- Indices EBO
- Material&
- Shader Program&
- Meshes[]
Details:
- There is a single
Scenewithin the program, this represents what the user sees - The
Shader Programsits at theScenelevel and remains constant after init - The different
Models all sit within theScene. Once per game tick, when theSceneis updated it must also call an update method on all of theModels - A
Modelholds all the differentMaterials that any of its child objects might use - A
Modelhas an MVP and it sets this as the current one each game tick before it then calls an update method on all of it'sObjects Objectsonly act as a container forMeshes. They exist to mirror the structure found within different model file types. When anObjectis updated it must update all of it'sMeshes- A
Meshcontains the data required for OpenGL to render something. It also has references to a singleMaterialand theShader program