Using the Point Cloud API
HOOPS Visualize provides a set of API functions for point cloud operations. To use the point cloud API, you must include the file PointCloudAPI.h. All point cloud classes are members of the ooc
namespace.
This API provides:
A means to determine if a segment is an OOC root
Mappings between segments and nodes
Persistent deletion of entire nodes
Persistent deletion of specific node points
Writing out such deletions to disk and being able to read them back in
Iteration of point cloud points
IMPORTANT: In order for the HOOPS/OOC module to work properly you need to enable multithreading:
HC_Define_System_Options("multi-threading = full");
Architecture Overview
Once an OOC file and its associated directory of node files have been preprocessed, the OOC file can be loaded into the HOOPS Visualize scene graph.
The loading and rendering of point cloud data
The following workflow describes what happens when an OOC file is loaded into HOOPS Visualize:
The OOC file is read by the HOOPS Visualize.
With the information from the OOC file,
HIOUtilityOOC
can build a scene graph in the HOOPS/3DGS database. This scene graph is a spatial representation of the point cloud. Each node in the tree corresponds a portion of the point cloud spatially.At render time, HOOPS/3DGS traverses the segment tree. For each node, it determines if that portion of the point cloud will be drawn to the screen. If a specific area of the point cloud is visible to the camera, then HOOPS/3DGS will request data for that node from
HIOUtiltyOOC
.HIOUtilityOOC
will then find the data file corresponding to the correct node and load it into memory. At this point, the data is put into a queue which is used for performance and memory management.HIOUtilityOOC
works with HOOPS/3DGS to determine when node data can be loaded into the database.HOOPS/3DGS draws the point cloud data on the screen.
Because the loading and storing of data in memory can be a highly intensive process, HOOPS implements several mechanisms to balance the throughput of data with system performance. HOOPS/3DGS and HOOPS/OOC work in concert to control the amount of data loaded at any given time. Additionally, HOOPS/3DGS also carefully manages the amount of memory used. As parts of the model move out of the view frustum or are extent culled, HOOPS/3DGS depopulates the data from the associated segments. Geometry is flushed from memory. As areas become visible again, data is reloaded. HOOPS/3DGS uses caching logic so that in situations where the camera is panning back and forth or zooming in and out, data is not disposed of prematurely.
Loading OOC Files
The HOOPS/OOC loader is built as an HIO plug-in. If you are familiar with the HIO paradigm or use other HIO plug-ins, you can use this method to keep all your module loaders consistent. Alternatively, you may skip the OOC loader and read the file directly using FileInputByKey
. Both methods are described below.
Using the HOOPS/OOC HIO Plug-In
The HOOPS/OOC HIO plug-in lets you import OOC files into HOOPS Visualize. It is part of the Plug-in architecture. To use this integration, you must be using the HOOPS/MVO module.
To use the HOOPS/OOC HIO plug-in, please follow the steps below:
Create a hio_plugins directory in your application’s working directory.
Copy the hio_ooc.hio file found in <hoops>/bin/<platform>/hio_plugins/hio_ooc*to the *hio_plugins directory created in the previous step.
Ensure that the hoops_ooc.dll is installed in your the HOOPS bin directory.
Run your application.
During start-up, when HOOPS/MVO finds the HOOPS/OOC HIO plug-in in your application’s path, it will perform the following steps:
Load in the necessary HOOPS/OOC libraries.
Create the appropriate input handler.
Register this handler and the associated file extensions to the
HIOManager
.
The input handler that is registered with the HIOManager is HIOUtilityOOC
. You can load files directly with this input handler as shown in the sample code below:
HIOUtilityOOC ooc_reader;
HInputHandlerOptions ooc_opt;
ooc_opt.m_pHBaseView = view;
HFileInputResult result = ooc_reader.FileInputByKey(filename, &ooc_opt);
Using FileInputByKey
If you do not wish to use the HIO plug-in to import your file, you may alternatively choose to import it directly using ooc::io::FileInputByImageKey
. Both methods require the OOC model to be associated with a HBaseView
and both provide identical results. Example:
HInputHandlerOptions input_options;
input_options.m_pHBaseView = view;
ooc::io::IOResult result = ooc::io::FileInputByKey(filename, dest_segment_key, input_options);
API Usage
Point Cloud Database Structure
The point cloud data of a single file is stored in a HOOPS scene graph under a segment called “root”, which is the root of all its point cloud data. No two point clouds may share the same point cloud root. That is, the following scene graph structure is not allowed:
./ooc-file-1/root/ooc-file-2/root
To work around this, one may store the roots in different segments as follows:
./ooc-file-1/root
./ooc-file-2/root
Alternatively, you can preprocess a set of point clouds into a single OOC file.
Hiding OOC Data
In some cases, it may be necessary to hide OOC data instead of displaying it. Because OOC data is handled differently than normal geometry, using HC_Set_Visibility()
is not sufficient. Use HC_Set_Conditional_Action()
instead:
HC_Set_Conditional_Action("prune segment", "xyz");
prune_segment
forces the segment to be ignored and avoids callbacks. xyz
is the condition to be tested and must evaluate to true for the segment to be pruned.
Point Cloud Environment and Node Handles
Each point cloud instance has its own corresponding environment, represented by the ooc::Env
object. The Env
is a logical group of nodes and is used in many of the API functions to identify which point cloud the function should operate on. The developer will never instantiate their own Env
- HOOPS/OOC will create it automatically.
As part of the preprocessor algorithm, HOOPS Visualize will logically divide a point cloud into nodes. A node contains a set of spatially-related points. ooc::NodeHandle
is the interface to use to interact with each node. Using NodeHandle
, you can interact with individual points by performing operations such as counting and deleting.
Populating Memory Before Rendering
HOOPS/OOC will only load points into the scene graph if they are within the frustum of the camera. However, it is not possible to know which points are viewable before performing viewing calculations, which are computed as part of the normal update process. Therefore, in order to cause a point cloud to be displayed, you must trigger an update, wait for the OOC module to populate its segments with points, then call update again to actually display the points.
The ooc::PollNodesLoadingOrHaveBeenLoaded
is available to determine whether all viewable points have been loaded or if any are still waiting to load. One possible method for performing this task is shown in the following code snippet:
static void update (HBaseView & view, ooc::Env env)
{
view.Update(); // trigger OOC nodes to start populating.
// test if nodes are finished loading
while (ooc::PollNodesLoadingOrHaveBeenLoaded(env)) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
view.Update(); // render any newly populated OOC nodes
}
}
Remember that if you have loaded multiple point clouds, you need to call this sequence for each ooc::Env
object.
HOOPS Visualize has a limit to the amount of memory it will use when populating point clouds. If the available memory is exhausted, no more points will be loaded for that particular view orientation (although, the loaded points are not locked - if the camera is moved, some points will be removed and others will take their place). While this mechanic is an inherent feature of using the OOC system, you are able to adjust the memory limit by using the system option “populate memory limit”. However, such an adjustment is not recommended. Contact Tech Soft technical support for advice on this subject if you feel there is a problem with the memory limit.
Synchronization Callbacks When Editing Points
The HOOPS/OOC module takes advantage of multithreading to provide its functionality. It follows that modifying the point cloud is a potentially dangerous operation because data modified in one thread must conform to the expectations of all other threads. Therefore, HOOPS Visualize requires your code to be synchronized when performing edits. SynchronizeWith
is the mechanism provided to address this scenario.
SynchronizeWith
accepts a callback function which executes your editing logic. SynchronizeWith
acquires a write lock on the scene graph, executes your code, then releases the lock when finished. For example, the following code snippet demonstrates how you would call a function to decimate nodes:
SyncResult sync_res = SynchronizeWith(env, [&](SyncToken const & sync_token) {
for (size_t i = 0; i < node_handles.size(); ++i) {
ooc::NodeHandle node_handle = node_handles[i];
if (!decimate_node(fraction, sync_token, node_handle)) {
success = false;
return;
}
}
});
See example ooc-4-delete-points.cpp for a full code sample.
Using OOC Operators
HOOPS Visualize provides two operators for use with OOC models: HOpSelectAreaOOC
and HOpSelectPolygonOOC
. These operators work similarly to HOpSelectArea
and HOpSelectPolygon
, but should be used with OOC models only. If you try to select non-OOC data with these operators, the functionality is undefined.
OOC operators differ from their corresponding operators in one significant way: they collect selection results into a special object called HSelectionSetOOC
. This selection set has a few extra methods pertinent to OOC models. For example, you can use the HSelectionSetOOC::SetDeepSelection
function to allow your operator to select points that are unloaded. The operators can select by rectangular area or by polygonal area. To add a selection set manually, you can call HSelectionSetOOC::AddRectangleWindow
(rectangle selection) or HSelectionSetOOC::AddTriangleWindow
(polygonal selection). If you have a complex polygon shape you want to select against, you should divide it into triangles and call AddTriangleWindow
multiple times.
If you need to extend the functionality of the OOC operators, you are encouraged to do so by deriving from the existing OOC operators. See MVO Programming Guide for details about creating a custom operator object. The source code for the OOC operators can be found in the MVO source directory.
IMPORTANT: The OOC operators are included with the MVO source, but are not built as part of the HOOPS/MVO package. In order to use the OOC operators, you need to include the following files in your project:
HOpSelectAreaOOC.h
HOpSelectPolygonOOC.h
HSelectionSetOOC.h
You also need to build these supporting files:
HWindowFilterOOC.cpp
HWindowInfoOOC.cpp
HMutexOOC.cpp
Filtering
Filtering is a way to create a subset of points based on criteria you specify.
Step 1: Create the filter class
Any class that performs filtering must derive from ooc::query::Filter
. You must override the filter’s virtual methods to create logic that will satisfy your requirements. The following is a basic filter that will accept all points:
class CollectAllFilter : public ooc::query::Filter {
// rejects an entire node of points
virtual bool RejectNode (ooc::NodeHandle const & node_handle) {
return false;
}
// reject point based on bounding parameters of a node
virtual bool RejectBounding (ooc::Point const & min, ooc::Point const & max) {
return false;
}
// rejects all points currently in view - only called if the node is in memory
virtual bool RejectPointsInMemory () {
return false;
}
// rejects all points not currently in view - only called if the node is on disk
virtual bool RejectPointsOnDisk () {
return false;
}
// accepts point
virtual bool AcceptPoint (ooc::Point const & point, size_t point_index) {
return true;
}
};
HOOPS/OOC will execute the filter in the order the functions are listed above. The return value for each function determines whether a point or set of points are accepted or rejected. For example, if you want the filter to only accept points that are currently in memory, you would set the return value of RejectPointOnDisk
to true
.
Step 2: Execute the filter
After the filter is created, it must be instantiated and subsequently executed by calling QueryPoints
. Note that this function returns an iterator to the set of points that meet the filter’s criteria:
CollectAllFilter filter;
ooc::query::QueryIterator it = ooc::query::QueryPoints(env, filter);
Step 3: Iterate over the filtered points
Lastly, you use the iterator to operate on the point subset. The GetStatus
function determines whether the iterator is finished looping over the point set. The loop below is simply adding all the points together:
while (true) {
ooc::query::QueryIterator::Status status = it.GetStatus();
if (status == ooc::query::QueryIterator::Status_Dead) {
break; // No more points to iterate.
}
if (status != ooc::query::QueryIterator::Status_Alive) {
return ExitCode::OOC_Failure;
}
sum_of_all_coordinates = sum_of_all_coordinates + it->GetNodePoint();
++num_points;
it.Advance();
}
For a complete example of the filtering process, see the sample code ooc-5-query-all.cpp.
Saving Point Clouds
The OOC module performs view-dependent loading and unloading of points into the scene graph. After an OOC file has been loaded, saving it to another file type will include the subset of the OOC data that happens to currently be loaded, in addition to any other scene graph contents.
If edits of a point cloud are saved to disk, they are saved out as OOCD (OOC delta) files. These delta files keep track of changes made between the original OOC file and the current point cloud state. They do so by storing current changes as well as a reference to any previous changes made. If no previous changes have been made, the OOCD file refers to the original OOC file. Thus, a collection of OOCD files may reference one another, accumulating all the changes made from them. It is possible to skip loading the most recent OOCD file in the OOCD file chain, in which case, only the change history up to that specific delta is used. To commit point cloud edits and create the OOCD file, you need to call ``ooc::io::CommitDeltasToFile``.
If you have a need to combine delta files into a single file, you should perform a query on the entire point cloud and write those points to a new file. Then, process that file using ooc.exe.
Releasing Memory
It is important to understand the difference between conventional HOOPS Visualize database entities and OOC entities. The OOC module will allocate its own memory and data structures in order to provide the functionality demanded by large point cloud visualization. Much of the infrastructure associated with OOC rendering is logically separate from conventional Visualize infrastructure. Therefore, when you are finished with a point cloud, you must clean up using ooc::Release
or ooc::Destroy
, but not both. Release
will clean up all OOC infrastructure but will leave the currently active points and scene graph in memory. All the points in view and their segments will remain and can be accessed and manipulated using conventional HC_
calls - but no OOC functionality will be available. Calling Destroy
automatically calls Release
and also removes all points that were loaded as OOC points as well as their segments.
Another way to release memory is to allow the HBaseView
to be destructed. When the HBaseView
destructor is called, a Release
will be triggered. However, this will only occur if the load option HInputHandlerOptions::m_bOOCAutoCleanup == true
(this is the default).
Optimizing Rendering
In addition to the intensive memory and data management HOOPS performs to ensure visual quality and performance for point cloud rendering, there are additional options developers can set that further assist in the optimization of point cloud visualization.
Within HOOPS/MVO, you can use HBaseView::SetFramerateMode
passing FrameRateMode::FramerateFixed
indicating a frame rate as well as a culling threshold. Setting the frame rate lets HOOPS understand how best to balance performance with visual acuity.
By default, the data points will be rendered as vertex markers, but you can enable point splatting so that the points give an appearance of a smooth surface. To enable this feature, use the function HBaseView::SetSplatRendering
. With HBaseView::SetSplatSize
and HBaseView::SetSplatSymbol
, you can determine the size and symbol of your splats respectively. If hardware acceleration is available, you can allow HOOPS to leverage it if you call HBaseView::SetFastMarkerDrawing
.
Within HOOPS/3DGS, you set several options that will improve rendering performance. We recommend that for point cloud data, shadows should be disabled. In HOOPS, there are two kinds of shadows:
ref Set_Rendering_Options_simple_shadow “Simple shadows” and ref Set_Rendering_Options_shadow_map “shadow maps”. These are both rendering options and can be disabled by calling Set_Rendering_Options
. The following code snippets shows how you can turn these options off:
HC_Set_Rendering_Options("no simple shadow, shadow map = off");
There also is another rendering option you can use to improve performance: ref Set_Rendering_Options_vertex_decimation “vertex decimation”. Although HOOPS does use culling logic in the fixed frame rate mode, ref Set_Rendering_Options_vertex_decimation “vertex decimation” is a quick and direct way to tell HOOPS to only draw a percentage of the vertices in the scene. If you are decimated vertices, the ref Set_Rendering_Options_randomize_vertices “randomize vertices” rendering option will cause vertices that are compiled into display lists to be inserted in random order. This option is intended for a more uniform point distribution when applying vertex decimation to non-randomized data. You should be aware that using vertex decimation does not decrease the number of vertices loaded into memory - it only instructs HOOPS Visualize to draw fewer of them.