The quick and dirty way of integrating Umbra 3 Optimizer as a part of your world editor or geometry toolchain is by using the Umbra::Task API. The Task API is built on top of the Build Graph API, which can be used to implement more elegant caching, parallelization and distribution schemes.
The Task API can be found from interface/optimizer/umbraTask.hpp. Look at computeVisibility() function in the Tutorial for a simple reference on how to do a very basic integration.
Creating and destroying the Task object
Umbra::Task::create() is used to create the Task object and Umbra::Task::release() to destroy it after a computation has been finished. The Task always needs a complete Umbra::Scene as input so a separate Task object must be created every time a Scene object is modified.
// Create a computation task for the scene Task* task = Task::create(scene);
As the visibility computation is heavily dependent on both the input geometry as well as the given use case for the generated data, the visibility computation parameters typically require some tweaking. This is done by creating an Umbra::ComputationParams object and setting the appropriate parameters by calling Umbra::ComputationParams::setParam(). See Choosing Optimizer computation parameters for a more detailed explanation of the available parameters and their respective effects on the visibility computation and the produced output data.
// Set computation parameters. These parameters affect quality and // length of computation. float smallestHole = SMALLEST_HOLE; float smallestOccluder = SMALLEST_OCCLUDER; ComputationParams params; params.setParam(ComputationParams::SMALLEST_HOLE, smallestHole); params.setParam(ComputationParams::SMALLEST_OCCLUDER, smallestOccluder); task->setComputationParams(params);
Once the appropriate computation parameters have been configured into the ComputationParams object, Umbra::Task::setComputationParams() is called to enable the created Task object to use the parameters in the computation process.
Executing the Task
The Task and the computation is executed by calling Umbra::Task::start(), which takes a string indicating where some intermediate files are stored as a parameter. The Task executes the computation in a separate thread and the computation starts immediately.
// Let's do it task->start("."); task->waitForFinish();
To wait until the computation is finished Umbra::Task::waitForFinish() must be called.
Running the Task in the background
The above should be enough for a simple Optimizer implementation, but there are some useful tricks that can be used to greatly improve the integration. This section briefly outlines a few of them.
Instead of calling Umbra::Task::waitForFinish() the computation can be let run in the background. Umbra::Task::isFinished() Umbra::Task::getProgress() and Umbra::Task::abort() can be used to implement a progress bar and abort functionality within an editor.
Serialize the data
To use Umbra 3 in your game runtime, you have to serialize the pre-computed data to the disk after the computation has finished. This pre-computed data is called the Umbra 3 Tome.
To extract the Umbra 3 Tome data from the computation results you have to use the Tome access methods in the Umbra::Task class. Umbra::Tome class is used in the Umbra 3 Runtime to access and use the data.
If you want to serialize the Umbra 3 Tome as a separate file for instance for debugging purposes you can use the helper function Umbra::Task::writeTomeToFile() for writing the file to a provided file name. If you want to serialize the data within your own file format you must use Umbra::Task::getTome() to get a copy of the Tome data. To use this method, you have to allocate a buffer with a size as returned by Umbra::Task::getTomeSize() and give it as a parameter to getTome(). The Umbra::Tome returned is a pointer to the user allocated memory region and it can be used immediately for e.g. visualization in your editor. Finally, you can serialize the buffer as is with the rest of the data you need in your game runtime.
// Allocate buffer for the Tome and get it from the task UINT32 tomeBufferSize = task->getTomeSize(); UINT8* tomeBuffer = new UINT8 [tomeBufferSize]; task->getTome(tomeBuffer, tomeBufferSize); // At this point you could either manually fwrite tomeBufferSize bytes // starting at address tomeBuffer into a file or use the following helper function: task->writeTomeToFile("tutorial.tome");
After successful integration of the Task API, you are now ready to issue runtime queries. Basic Runtime integation article contains instructions on how to perform visibility queries in your game runtime.