|
matf-rg-engine
1.0.0
Base for project for the Computer Graphics course at Faculty of Mathematics, University of Belgrade
|
matf-rg-project is the base project for the Computer Graphics course at the Faculty of Mathematics, University of Belgrade for the school year of 2024/2025.
To setup the necessary libraries, run:
./setup.sh
To generate docs, run:
doxygen Doxyfile
Open the documentation file in your browser: docs/html/index.html
engine_setup - here, the engine controllers are setupapp_setup - the function that the user of the App overrides and implements a custom setup for the Appinitialize - App should gather whatever Resources it needs and initialize its state.loop - App can check whether it should continue running. If the loop method returns false, the Main loop stops, and the App terminates.poll_events - App collects information about the events that happened at the Platform and collects user input for the upcoming frame.update - App updates the world state, processes physics, events, and world logic, and reacts to the user inputs.draw - App uses OpenGL and draws the current state of the world.terminate - App terminates its stateon_exit - do a final cleanup, and return an exit codeHere is the interface of the engine/core/App.hpp class:
Here is how the Engine is structured. You only need to include <engine/core/Engine.hpp> in your part of the project and all the header files will be available.
For a basic app setup, you need to:
MyApp, in the app/src/.engine::core::App and implement app_setup().MyApp object in the main function and call run on it.Controllers are a way to hook into the engine execution. To create a custom controller:
engine::core::Controllerinitialize, loop, poll_events, update, begin_draw, draw, end_draw, terminate) for which you want to execute custom code.MainApp::app_setup.Here is the example of creating the MainController that enables depth testing.
Resources currently include: textures, shaders, models, skyboxes. The ResourcesController manages the loading, storing, and accessing the resource objects. During the App::initialize, the ResourcesController will load all the resources in the resources directory.
For every type of resource, the ResourcesController has a corresponding function that retrieves it:
Model* ResourcesController::model("backpack")Shader* ResourcesController::shader("basic")Texture* ResourcesController::texture("awesomeface")Skybox* ResourcesController::skybox("skybox")The argument is always the resource name without the file extension. For textures and shaders, the name is just the name of the file without the extension. For models and skyboxes, it's the name of the directory, because they have multiple files associated with them.
The pointer to the resource that the ResourcesController returns is a non-owning pointer, meaning you should never call delete on it. All the memory is managed internally by the ResourcesController.
The resources/models/ directory stores all the models. Let's add a backpack model from the course.
resources/models/backpack.backpack model files into the resources/models/backpack.ResourcesController will automatically load this model during ResourcesController::initialize(); you should see a log:awesomeface.png to the resources/textures directoryResourcesController will automatically load it)your_shader.glsl in the resources/shaders/your_shader.glsl.vertex, fragment, and geometry (optional), shaders in the same file.Vertex, fragment, and geometry shaders are written in the same file. Use the // #shader vertex|fragment|geometry to declare the start of the shader. Here is an example:
ResourcesController will load and compile all the shaders in the resources/shaders directory.
Engine uses the imgui library to draw a GUI. See the library page for more examples. For GUI to be visible it should be drawn last, after all the world objects. Here is an example of displaying camera info in a GUI.

The Engine defines a base Error type with two subclasses, EngineError and UserError. They serve as a graceful way to terminate the application and provide the user with some helpful information on how to fix the error.
For example, the ResourcesController will throw AssetLoadingError if it can't read the asset file.
Exceptions shouldn't be used as a control-flow mechanism, instead they should be used to inform the user of an exceptional event that the program can't do anything about, like the missing asset file.
The PlatformController differentiates between four types of button/key states:
engine::platform::Key::State::JustPressed -> Only in the first frame when the button was registered as pressedengine::platform::Key::State::Pressed -> Every subsequent frame if the button is still heldengine::platform::Key::State::JustReleased -> Just the first frame in which the button was registered as releasedengine::platform::Key::State::Released -> Button is not pressedUse the PlatformController::key method to get the state of the key in a given frame:
Here is an example of turning GUI on/off drawing using the F2 key:
Keys have a unique identifier: via engine::platform::KeyId.
engine::platform::PlatformEventObserver, and override methods you'd like to have a custom operation executed once the event happensobserver instance in the PlatformController.Now, for every keyboard event, the PlatformController will call MainPlatformEventObserver::on_keyboard and pass the key on which the event occurred as an argument.
PlatformController initializes and stores the Window handle, which you can access via:
Also, the PlatformController will update the window properties if the size of the window changes.
Rendering actions that require more than one OpenGL call should be abstracted in the engine::graphics::OpenGL class. For example, OpenGL::compile_shaer compiles a GLSL shader and returns the shader_id.
If the OpenGL call fails, the CHECKED_GL_CALL macro throws an OpenGLError in DEBUG mode. The engine will print the error description of and the source location in which it occurred.
Why this way? It's less error-prone and more straightforward to add debugging assertions and error checks if needed.
You can configure some parts of the engine in the config.json. For example, we can configure the size and the title of the window when the application starts.
You can also add your custom configuration options:
and use them:
The Configuration class uses nlohmann/json library to load and store json files, see the library page for documentation.
You use the ArgParser to parse the command line arguments anywhere from the program. For example, for the invocation command: ./matf-rg-engine ... --fps 120, the parser->arg("--fps") will return the value 60. If the argument is not present, it will return the default value passed as the second argument to the arg method.
Here you can find a walkthrough tutorial for recreating the engine/test/app that demonstrates how to use different engine systems.