Database Fundamentals
NOTE: This programming guide introduces the HOOPS Visualize API. For a high-level overview of what HOOPS Visualize is, as well as a discussion of its major components, see the Technical Overview.
The foundation of HOOPS Visualize is the Visualize graphics database. The database is where all scene information is stored and organized.
Your application will probably integrate many different libraries to provide all of its functionality. To your application, Visualize simply appears as another library containing functions which can be called to insert and delete objects from the graphics database, to initiate and control rendering, and to receive input events. The general order in which these operations occur is described below:
Your application code makes changes to the graphics database.
When it is finished making changes to the graphics database, your application tells Visualize to update the display (either by explicitly calling Update() or by waiting for input).
Visualize walks the database and sends the primitives to be drawn to the renderer.
The renderer draws the scene and sends it to some output device.
The user sees the new picture, thinks for a while, and then generates input through the user interface.
Your application code receives this input and makes further changes to the Visualize database.
Although called a ‘database’, the Visualize database does not use a relational model like a SQL database. Instead, information is organized in a tree-like scene graph composed of nodes. Nodes have a parent/child relationship where one parent can have many children but each child only has one parent. In the Visualize database, a node is called a segment. Segments are the building blocks of the scene graph - they contain all geometry and attributes and are used to organize your scene hierarchically.
Describing a scene graph as a data structure is beyond the scope of this document, but understanding how the HOOPS Visualize database works is critical in order to work with Visualize efficiently.
Structure of a scene graph
The database and all objects within it depend on a HPS::World
object. You can think of HPS::World
as the context within which Visualize is able to manage your scene and to execute all its internal commands. You must create a HPS::World
before using HOOPS Visualize.
The HPS::World
object requires one parameter, your license key. The Tech Soft 3D Developer Zone provides this in the form of a header file, hoops_license.h. The license key is inside that file.
HPS::World world("[my license key]");
HPS.World world = new HPS.World("[my license key]");
C++ users may forgo setting the license key in the HPS::World
constructor and instead simply use the macro HOOPS_LICENSE
, which is defined in hoops_license.h. In order to make this work, you have to generate your own hoops_license.h from the Tech Soft 3D Developer Zone and copy it to <VISUALIZE_INSTALL_DIR>/include/.
There are a number of different ways to connect HOOPS Visualize to a window. The simplest way uses a stand-alone window:
#include "hps.h"
using namespace HPS;
int main(int argc, char* argv[])
{
HPS::World world("[my license key]");
HPS::StandAloneWindowKey sawk = HPS::Database::CreateStandAloneWindow();
sawk.Update();
return 0;
}
namespace HPS_020612_3
{
class Program
{
// it is important to have a static reference to avoid garbage collector
static HPS.World world = new HPS.World("[my license key]");
static void Main(string[] args)
{
HPS.StandAloneWindowKey sawk = HPS.Database.CreateStandAloneWindow();
sawk.Update();
}
}
}
Despite its brevity, the code in the snippet above is all that is needed to get HOOPS Visualize running in an empty window. For further information on the other ways to connect HOOPS Visualize to your application window, see this section.
Segments
When you instantiate a HPS::World
object, Visualize initializes the graphical database. To begin building your scene graph, you must utilize at least one root segment. There are two ways to do this. You may use a window segment as a root segment, or you can create one independently using HPS::Database::CreateRootSegment
and then associate it with a window segment using an include link. Any number of root segments can be created this way. This method is useful, for example, when you have multiple geometries or assemblies you would like to swap in and out of the scene.
Regardless of which method you choose, it is important to understand that only those segments that have a window segment ancestor will be drawn at render-time. With a root segment, you can set attributes, insert geometry and create subsegments. A window segment is similar to a root segment but has slightly different behavior and attributes. For example, every window segment is associated with a Visualize rendering context while a plain root segment is not (a plain root segment may not even be attached to the scene hierarchy). Another difference is that window segments are the only place to set window-level rendering directives such as anti-aliasing. It is important to understand these differences to determine which one to use and when to use it.
// creating a root segment
HPS::SegmentKey myModel = HPS::Database::CreateRootSegment();
// creating a window segment
HPS::ApplicationWindowKey myWindow = HPS::Database::CreateApplicationWindow(myWindowHandle);
// creating a root segment
HPS.SegmentKey myModel = HPS.Database.CreateRootSegment();
// creating a window segment
HPS.ApplicationWindowKey myWindow = HPS.Database.CreateApplicationWindow(myWindowHandle);
If a root segment does not need to be attached to the main scene graph, then what is its purpose? A root segment may be used to organize a group of related segments into a model subassembly to which attributes such as visibility or modelling matrix may be applied. For example, to temporarily make a subassembly invisible, you could detach the corresponding branch from the scene tree. The root of the branch is now a root segment. Another example would be to create a new root segment using Database::CreateRootSegment
, fill its branches with model data, and attach it to the main tree when it is ready to be rendered.
In the sample code above, both a generic root segment and a window segment are created using static methods in HPS::Database
. Both creation methods return a HPS::SegmentKey
or subclass thereof. Note that a HPS::SegmentKey
is not actually a segment but a handle that lets you interact with a specific instance of a segment in the database. A HPS::SegmentKey
provides access to the attributes and geometry within a segment and allows you to perform actions upon the segment. It also lets you traverse the segment tree allowing you to access and manipulate child segments.
In summary, all segments are inherently part of the database - they do not exist in isolation. All segments have a parent except root segments which exhibit slightly different behavioral relationships. Each segment has a handle called a key. The key allows you to locate the segment, find its type, and find its relationship to other segments like its parent and children. The following code snippet demonstrates some examples of these interactions:
SegmentKeyArray children; // all C++ array types are typedef'd to STL vectors
parentSegment.ShowSubsegments(children);
for (size_t i = 0; i < children.size(); i++) {
HPS::SegmentKey sk = children.at(i); // get reference to a child
HPS::UTF8 s = sk.Name(); // get child's name
HPS::SegmentKey parent = sk.Owner(); // a new reference to the parent segment
}
HPS.SegmentKey[] children;
mySegmentKey.ShowSubsegments(out children);
for (int i = 0; i < children.Length; i++)
{
HPS.SegmentKey sk = children[i]; // get reference to a child
String s = sk.Name(); // get child's name
HPS.SegmentKey parent = sk.Owner(); // a new reference to the parent segment
}
A segment contains data and also has data attached to it. The way you structure your scene graph is very important, because a segment always inherits a set of attributes from its parent segment. Isolated root segments do not contain initialized attributes, but they will inherit attributes from a parent when attached. In Visualize, attaching is called inclusion. In addition to inserting geometry into a segment and creating child segments, it is possible to attach or detach existing segments. However, since normal segments cannot exist in an orphaned state, when detaching segments, they must be either deleted or moved to another segment (this restriction does not apply to root segments or include segments).
// creating two independent segments
HPS::SegmentKey mySegmentKey = HPS::Database::CreateRootSegment();
HPS::SegmentKey anotherSegmentKey = HPS::Database::CreateRootSegment();
// ...other logic
// "anotherSegmentKey" becomes a child of "mySegmentKey"
anotherSegmentKey.MoveTo(mySegmentKey);
// delete everything in the segment
anotherSegmentKey.Flush();
// delete the segment itself
anotherSegmentKey.Delete();
// creating two independent segments
HPS.SegmentKey mySegmentKey = HPS.Database.CreateRootSegment();
HPS.SegmentKey anotherSegmentKey = HPS.Database.CreateRootSegment();
// ...other logic
// "anotherSegmentKey" becomes a child of "mySegmentKey"
anotherSegmentKey.MoveTo(mySegmentKey);
// delete everything in the segment
anotherSegmentKey.Flush();
// delete the segment itself
anotherSegmentKey.Delete();
Segment Structure Detail
Segment Attributes
One of the main purposes of segments is to group attributes at the segment level. For performance reasons, segments are intended to be used to group items with similar attributes. Attributes are descriptors that tell Visualize how to render graphical information. Most (but not all) attributes exist at the segment level, which means they apply to all geometry in the segment. For any attribute set, there exists exactly one value for each attribute. If you do not explicitly specify the value for an attribute on a segment, the effective value will be inherited from the segment’s parent (or ancestor, if the parent has no setting of its own). This also means that assigning a value to an attribute will cause all subsegments to inherit that value, unless they override it with a value of their own. Visualize determines a set of default values for all attributes and initializes each window segment with those defaults. Thus, due to the nature of inheritance, your attribute will receive the default value if you do not explicitly set a value for it in an ancestor segment.
A further discussion on attributes, as well as examples on setting attributes at the segment level, can be found in the attributes section. Default values for all attributes can be found in the appendix.
Geometry
When a segment is created, it contains no geometry. You can insert any type of geometry into a segment. When geometry is inserted into a segment, it becomes part of the database. Each insert function returns a key which is used as the geometry handle (see that snippet for a basic example of geometry insertion). Keys are reference counted smart pointers that do not delete the database object when they go out of scope. The key helps locate the geometry in the database letting us know its relationships to parent and children. Geometry is always part of a segment, but not all segments must contain geometry.
Subsegments
Segments can contain other segments, thus, child segments can have their own subsegments. This data model is designed to allow you to build a segment tree that logically describes your graphical scene. Common use of Visualize involves creating a segment hierarchy by attaching segments to other segments (see Performance Considerations for information about recommended segment organization). To create a subsegment:
HPS::SegmentKey mySegmentKey = HPS::Database::CreateRootSegment();
HPS::SegmentKey newSegmentKey = mySegmentKey.Down("new segment", true);
// alternatively, you may use the Subsegment function
HPS::SegmentKey anotherKey = mySegmentKey.Subsegment();
HPS.SegmentKey mySegmentKey = HPS.Database.CreateRootSegment();
HPS.SegmentKey newSegmentKey = mySegmentKey.Down("new segment", true);
// alternatively, you may use the Subsegment function
HPS.SegmentKey anotherKey = mySegmentKey.Subsegment();
When calling Down
or Subsegment
, you may supply a name for the segment. Any UTF-8 encoded name is legal. You can pass an empty string for an anonymous name, and in this case Visualize will guarantee a unique segment.
Include Segments
Graphical information inserted into a window segment ultimately goes into the rendering pipeline. However, sometimes it may be desirable to create and manipulate data without drawing immediately. For instance, imagine creating a scene graph for a turbine engine. Such a model would likely be very complex. You know that you will want to display your turbine engine in multiple windows at different times. Additionally, you might also want to export the information to a PDF file or print it to hardcopy. However, duplicating a scene graph is tedious, wastes memory, and is prone to errors. Additionally, if modifications are made to the scene, it must be made in multiple locations in order to maintain synchronicity with all scene graphs.
A more efficient way to manage the model data would be to build one scene graph which can be referenced by multiple segments. To begin constructing your scene graph, request a root segment from the Visualize database. With this root segment, you can build a segment tree that represents your model without associating it with any particular window.
// creating the root segment for the turbine model
HPS::SegmentKey myTurbineModel = Database::CreateRootSegment();
// creating the root segment for the turbine model
HPS.SegmentKey myTurbineModel = HPS.Database.CreateRootSegment();
When you request a root segment, Visualize creates a parentless segment and returns a HPS::SegmentKey
. You can then use the key to build your model’s information hierarchy - setting attributes and inserting geometry. An isolated root segment and the information associated is never drawn unless it has a Visualize window as an ancestor. Therefore, to display your model, you must include your subtree into a window segment.
// including the turbine root segment
HPS::IncludeKey includeKey = windowKey.IncludeSegment(myTurbineModel);
// including the turbine root segment
HPS.IncludeKey includeKey = windowKey.IncludeSegment(myTurbineModel);
Once a segment is included by a window key, it will enter the rendering pipeline for that window. Multiple segments can include the model, thus, copies of the geometry can be drawn at multiple locations in a scene or in multiple windows. A change in the model will be reflected in all instances. You can also export your model information to a file or print to hardcopy.
The segments representing a drawing context ‘include’ your model
The HPS::WindowKey
is not the only type of key that can include other segments. Any normal HPS::SegmentKey
can as well.
This paradigm for creating and storing model information can also be used to organize and aggregate data. For instance, you can create multiple model segments and then use them to construct a master assembly that is itself a root segment. As an example, let’s take the turbine engine. You may want to display the turbine engine by itself. However, you may also want to display it as part of the design for a whole aircraft. In each aircraft’s scene graph, you can include the turbine engine using its HPS::SegmentKey
. Any changes in the turbine engine will be reflected in all models that include the turbine engine.
Judicious use of model segments in conjunction with window segments will help you efficiently manage and render your graphical data with minimal effort.
Filtering Include Links
When an include link is employed, all geometry and attributes from the target segment are included by the parent segment. It is possible to set a filter on the include key so that certain geometry or attributes can be excluded.
// filters out lighting from an include segment
includeKey.SetFilter(HPS::AttributeLock::Type::VisibilityLights);
// filters out lighting from an include segment
includeKey.SetFilter(HPS.AttributeLock.Type.VisibilityLights);
Deleting Include Links
To sever the link between the parent key and its included segment, you must delete the include link key:
includeKey.Delete();
includeKey.Delete();
Conditional Include Links
Sometimes, it is useful to include a branch of a tree only when certain conditions are met. The conditional include mechanic provides this functionality. A condition is really just a flag that is set on a segment. When Visualize detects that the flag is set, the included segment is made part of the main tree and thus its geometry is rendered.
// here, the condition is created
HPS::ConditionalExpression condition1("c1");
// including a segment subject to the condition
mySegmentKey.IncludeSegment(someSpecialSegment, condition1);
// the condition is set in the following line, and 'someSpecialSegment' becomes part of the tree
mySegmentKey.SetCondition("c1");
// here, the condition is created
HPS.ConditionalExpression condition1 = new HPS.ConditionalExpression("c1");
// including a segment subject to the condition
mySegmentKey.IncludeSegment(someSpecialSegment, condition1);
// the condition is set in the following line, and 'someSpecialSegment' becomes part of the tree
mySegmentKey.SetCondition("c1");
While the code above is a trivial example of conditional include links, the real power of conditional includes becomes more apparent when many links need to be made at multiple locations in the tree. Conditions are inheritable attributes, and thus if set at the root segment, many links can be automatically included in one simple command if they all depend on the same condition.
Segment References
In some situations, you may find it necessary to instantiate geometric information at multiple places in a scene without having to worry about how the geometry is styled. Segment references enable you to do this. However, unlike include segments, segment references only import geometry - attributes and subsegments are excluded. A segment may be referenced any number of times.
mySegmentKey.ReferenceGeometry(anotherSegment); // brings in just the geometry from 'anotherSegment'
mySegmentKey.ReferenceGeometry(anotherSegment); // brings in just the geometry from 'anotherSegment'
Keys
In addition to segments, all other database objects in Visualize have an associated key. For example, a HPS::TextKey
identifies a text string, and a HPS::SphereKey
identifies a sphere. All Database modifications involve keys at some level. For example, to modify the font of a text string, you would use its key. In the code below, a simple segment is constructed. Note the relationships between the keys of the root segment, the sphere, the text, and the text font:
HPS::SegmentKey mySegmentKey = HPS::Database::CreateRootSegment();
// insert a sphere into the segment via SegmentKey
HPS::SphereKey sphereKey = mySegmentKey.InsertSphere(Point(0.0f, 0.0f, 0.0f), 2.0f);
// insert text into the segment via SegmentKey
HPS::TextKey myText = mySegmentKey.InsertText(Point(0.0f, 0.0f, 0.0f), "My Segment");
// set font on the text using the TextKey
myText.SetFont("times new roman");
HPS.SegmentKey mySegmentKey = HPS.Database.CreateRootSegment();
// insert a sphere into the segment via SegmentKey
HPS.SphereKey sphereKey = mySegmentKey.InsertSphere(new HPS.Point(0.0f, 0.0f, 0.0f), 2.0f);
// insert text into the segment via SegmentKey
HPS.TextKey myText = mySegmentKey.InsertText(new HPS.Point(0.0f, 0.0f, 0.0f), "My segment");
// set font on the text using the TextKey
myText.SetFont("times new roman");
Behind the scenes, all Visualize keys are reference-counted smart pointers. You don’t have to worry about initializing or deleting them. When a key goes out of scope, the object still exists in the Database.
You can use an object’s key to set its fields. For example, you may use a HPS::CircleKey
to set the radius and center point of a circle:
myCircleKey.SetRadius(2.0f);
myCircleKey.SetCenter(Point(1.0f, 2.5f, 3.2f));
myCircleKey.SetRadius(2.0f);
myCircleKey.SetCenter(new HPS.Point(1.0f, 2.5f, 3.2f));
Note that setting attributes at the geometry level as shown in the snippet above will affect only the geometry represented by myCircleKey
. However, most attributes in Visualize are set at the segment level and use attribute inheritance to propagate values to geometry in child segments. An easy way to set attributes on a segment is to use an attribute control, as discussed in the following section.
Attributes
An attribute is a setting that is used to style or alter how a segment’s geometry is rendered. For each primitive, every applicable attribute must be known before the primitive can be drawn. For example, before a line can be drawn, the renderer must know the line’s color, thickness, style, modelling transformation, how it is lit, and so on. A primitive can have dozens - or even hundreds - of attributes; specifying them all would be substantial work. The concept of attribute inheritance addresses this issue.
Attribute inheritance in Visualize works like inheritance in an object-oriented language. If an attribute value is not set locally on a segment, then it is inherited from a parent segment. When the database is rendered, it needs to know the attribute values for all the geometry in the database. If any required attributes have not been set on a segment, the value in the parent segment will be used. If the parent has no value, then Visualize looks at the parent’s parent, and so on, until it finds a value for the attribute. The window segment of every scene graph always has a set of default attributes which will be used if no other value is set. Default values for all attributes can be found in the appendix.
Some attributes, such as color and visibility, can be set at the segment level, geometry level, or subentity level. While this gives developers the maximum amount of flexibility in building a scene, setting attributes at the segment level is recommended for optimum performance. General performance guidelines are discussed in our section on performance considerations.
IMPORTANT: Generally speaking, attribute inheritance only applies to segment-level attributes. Geometry attributes, such as the radius of a sphere, only apply to a single piece of geometry and do not inherit. If you can alter an attribute using an attribute control obtained from a HPS::SegmentKey
, you know this is a segment-level attribute. For example, this snippet shows inheritable line attributes being set using a HPS::LineAttributeControl
. This code sample shows non-inheritable geometry attributes being set on a circle using a HPS::CircleKey
.
Attribute controls are discussed in the following subsection.
Setting and Getting Attributes Using Attribute Controls
Unlike HPS::CircleKey
, not every type of geometry key has its own set of attribute accessor functions. For these objects, attributes are specified using an attribute control. Attribute controls are available for all entities that are set at the segment level. When a segment-level attribute is set, all geometry within that segment and within all subsegments will inherit that setting. Of course, it is possible to override this by setting another attribute further down in the tree, or by using an attribute lock.
In this example, the edge and camera controls are demonstrated, however, the other controls follow the same pattern. All of the controls are listed with the other classes in the Class Index. For an in-depth example on setting attributes and using inheritance, see Tutorial 1.
Example: Edge Attribute Control
Edge attributes are controlled using the HPS::EdgeAttributeControl
. The attributes that can be modified are edge weight and edge pattern. Edge patterns and line patterns can be used interchangeably (line patterns are discussed in detail in the line patterns page). Continuing from the circle example, the results of the following code can be seen below:
mySegmentKey.GetLineAttributeControl().SetPattern("my_line_pattern");
mySegmentKey.GetEdgeAttributeControl().SetWeight(3.0f);
mySegmentKey.GetLineAttributeControl().SetPattern("my_line_pattern");
mySegmentKey.GetEdgeAttributeControl().SetWeight(3.0f);
The circles drawn with the “dashdot” pattern and a heavy weight
Example: Camera Attribute Control
The HPS::CameraControl
is another control that is commonly used for manipulating the scene view. The camera is complex inheritable attribute - see this section for details on camera inheritance.
Camera::Projection proj;
mySegmentKey.GetCameraControl().ShowProjection(proj); // get the current projection type
mySegmentKey.GetCameraControl().Orbit(20, 30); // moves 20 degrees right, 30 up
mySegmentKey.GetCameraControl().Zoom(10); // zoom 10x
HPS.Camera.Projection proj;
mySegmentKey.GetCameraControl().ShowProjection(out proj); // get the current projection type
mySegmentKey.GetCameraControl().Orbit(20, 30); // moves 20 degrees right, 30 up
mySegmentKey.GetCameraControl().Zoom(10); // zoom 10x
Getting Attribute Values
Note that you can also use attribute controls to get a segment’s locally set attributes. For example, in the code snippet above, ShowProjection
is used to get the camera projection. Getting attributes this way only works for attributes explicitly set on a segment. To find the value of inherited attributes, you need to use a HPS::KeyPath
. That procedure is documented here.
Using Kits to Instantiate Geometry
Kits and geometry keys are related. They share the same settings. For most (but not all) types of keys, there is a corresponding kit type. Key objects always represent an object inside the Visualize database while kits exist entirely outside the database. You can think of a kit as a non-instanced key. Kits are useful because you can set all attributes for a future object in an independent way, without affecting the scene. Then, it can be inserted as a finished object into the database, which offers a modest performance benefit over building the same object piece-wise. Note that the kit and the resulting geometry are two separate objects. If you insert geometry using a kit, the geometry will take on the attributes of kit in the state it is in at the time of the insertion. If the kit is later changed, there will be no effect on the inserted object. Some other uses for kits include:
inserting multiple identical or nearly-identical copies of an object into the database
retrieving database information into one convenient object
inserting a complex object into the database in a transactional way
The code sample below uses a HPS::CircleKit
to insert two different circles using the same kit.
HPS::CircleKit circleKit;
// setting the attributes on the kit
circleKit.SetCenter(Point(0, 0, 0));
circleKit.SetNormal(Vector(0, 0, 1));
circleKit.SetRadius(0.75f);
// inserting first circle of radius 0.75, plus all other attributes committed at once:
HPS::CircleKey circleKey = mySegmentKey.InsertCircle(circleKit);
// change radius for second insert
circleKit.SetRadius(0.25f);
// same CircleKit used for second circle of radius 0.25 - all other attributes are the same
mySegmentKey.InsertCircle(circleKit);
// Circle instance parameters can be copied out of the database to another CircleKit for use elsewhere
circleKey.Show(anotherCircleKit);
HPS.CircleKit circleKit = new HPS.CircleKit();
// setting the attributes on the kit
circleKit.SetCenter(new HPS.Point(0, 0, 0));
circleKit.SetNormal(new HPS.Vector(0, 0, 1));
circleKit.SetRadius(0.75f);
// inserting first circle of radius 0.75, plus all other attributes committed at once:
HPS.CircleKey circleKey = mySegmentKey.InsertCircle(circleKit);
// change radius for second insert
circleKit.SetRadius(0.25f);
// same CircleKit used for second circle of radius 0.25 - all other attributes are the same
mySegmentKey.InsertCircle(circleKit);
// Circle instance parameters can be copied out of the database to another CircleKit for use elsewhere
circleKey.Show(out anotherCircleKit);
The result is show below. Note that face visibility has also been set to ‘false’ and a marker has been placed at the origin.
Two basic circles generated by the code above
Attributes can also be set without the use of a kit. In that case, the Set functions would be called directly on the object itself. Note that this method incurs a performance penalty when compared to using a kit, as each attribute must be inserted into the database atomically instead of in a transactional way.
circleKey.SetCenter(Point(0, 0, 0));
circleKey.SetNormal(Vector(0, 0, 1));
circleKey.SetRadius(0.5f);
// alternatively, the attributes can be set in one line of code using
// chaining, although the effect on performance is unchanged
circleKey.SetCenter(Point(0, 0, 0)).SetNormal(Vector(0, 0, 1)).SetRadius(0.5f);
circleKey.SetCenter(new HPS.Point(0, 0, 0));
circleKey.SetNormal(new HPS.Vector(0, 0, 1));
circleKey.SetRadius(0.5f);
// alternatively, the attributes can be set in one line of code using
// chaining, although the effect on performance is unchanged
circleKey.SetCenter(new HPS.Point(0, 0, 0)).SetNormal(new HPS.Vector(0, 0, 1)).SetRadius(0.5f);
Attribute Locks
It is possible to temporarily override attributes by using an attribute lock. When a specific attribute is locked in a segment, that attribute will apply to all subsegments, even if they have a setting for the same attribute. Attribute locks may be useful when editing the attributes in a model. For example, you may display a model subassembly which already has its own attributes - but you want a ‘parent’ model object to have its attributes take precedence and override its children. Iterating through all the associated segments to change their attributes individually would be cumbersome - this problem is solved by attribute locks.
HPS::SegmentKey childSegment = parentSegment.Subsegment();
// setting the color to red
parentSegment.GetMaterialMappingControl().SetFaceColor(HPS::RGBAColor(1, 0, 0));
parentSegment.GetAttributeLockControl().SetLock(HPS::AttributeLock::Type::Everything);
// setting this blue color below has no effect because of the attribute lock
childSegment.GetMaterialMappingControl().SetFaceColor(HPS::RGBAColor(0, 0, 1));
// sphere appears in red
childSegment.InsertSphere(Point(0, 0, 0), 0.5f);
HPS.SegmentKey childSegment = parentSegment.Subsegment();
// setting the color to red
parentSegment.GetMaterialMappingControl().SetFaceColor(new HPS.RGBAColor(1, 0, 0));
parentSegment.GetAttributeLockControl().SetLock(HPS.AttributeLock.Type.Everything);
// setting this blue color below has no effect because of the attribute lock
childSegment.GetMaterialMappingControl().SetFaceColor(new HPS.RGBAColor(0, 0, 1));
// sphere appears in red
childSegment.InsertSphere(new HPS.Point(0, 0, 0), 0.5f);
Please note that an attribute lock is itself an attribute. So, to override a lock set by an ancestor segment, set an attribute lock on a subsegment. Additionally, when setting an attribute lock on a segment which also has an applied style, the lock will be applied to the naturally inherited attributes - not the styled attributes.
IMPORTANT: Attribute lock provides a feature for temporary rendering instances. We do not recommend that you use this feature in place of setting attributes in the normal way. When this feature is enabled, rendering performance can be impacted.
Interally, HOOPS Visualize stores attribute settings in related groups. For example, all backface diffuse settings (BackFaceDiffuseColor
, BackFaceDiffuseTexture
, BackFaceDiffuseAlpha
) are assigned to the BackFaceDiffuse
attribute lock group.
As a result, when you inspect which locks are currently set, the expected result is affected by which settings are active. If all settings of a lock group are active, HOOPS Visualize will return the group setting. Let’s say you set locks for ``BackFaceDiffuseColor``
and BackFaceDiffuseAlpha
. If you use HPS::AttributeLockControl::ShowLock
, it will return those two locks as expected.
However, when showing active locks in a situation where BackFaceDiffuseColor
, BackFaceDiffuseTexture
, BackFaceDiffuseAlpha
are all set, only BackFaceDiffuse
would be returned. This is because in this context, BackFaceDiffuse = BackFaceDiffuseColor + BackFaceDiffuseTexture + BackFaceDiffuseAlpha
. The analogous situation is true for all types of material locks (FrontFaceDiffuse
, CutFaceDiffuse
, etc).
Connecting Visualize to a Window
From a user perspective, the conventional concept of a window is a logical division of screen space controlled by one application. However, in Visualize, the term “window” is akin to a rendering context - the GUI part of the window you see on the screen is still handled by the native GUI framework. Behind the scenes, a Visualize window is represented by a segment in the scene graph. As such, each window has a unique key to identify it. Visualize offers three different types of windows, as discussed below. Regardless of the actual type of window, all window keys are derived from HPS::WindowKey
.
Window segments are the top-level node of a particular scene. The window segment is also where scene-level attributes, such as anti-aliasing, bloom, depth of field (see HPS::WindowKey::SetPostProcessEffects
), and other rendering algorithms are set. Some options need to be set before window creation, and for that case, each window type has its own associated window options kit.
A window is always associated with a driver interface, through which all rendering is performed. For on-screen drivers, Visualize includes implementations of DirectX11 and OpenGL. While possible to create a multi-windowed Visualize application that uses different display drivers for each window, there can only be one driver per window. It is also not possible to change drivers after a window has been created.
While drivers can be manually specified, Visualize can also perform runtime query of the 3D capabilities of the operating system and GPU, and automatically choose the most appropriate display driver. Under Windows, preference will be given to DirectX API, followed by OpenGL. Under all other platforms, only the OpenGL API is supported. To use this capability, choose HPS::Window::Driver::Default3D
when creating your window (window creation is covered below).
Application Window
The application window is the most common type of window used in a Visualize application. It is represented by HPS::ApplicationWindowKey
. For all but the simplest programs, this is the appropriate construct to use. For example, an application window can be hosted by a .NET UserControl, an MFC dialog, or even a plain Win32 window.
In fact, Visualize can render to any window for which you provide a handle. In Windows, the handle is the HWND
parameter and is passed to the Database as shown below. Note the returned key from CreateApplicationWindow
, which is used to control the scene at a high level. For example, window keys are used to set selection options and trigger updates.
// creating a window segment based on an OpenGL2 rendering context
HPS::ApplicationWindowKey windowKey =
HPS::Database::CreateApplicationWindow(reinterpret_cast<WindowHandle>(hWnd), Window::Driver::OpenGL2);
// after the window is created, you can build your tree and update it normally
windowKey.IncludeSegment(modelKey);
windowKey.Update();
// creating a window segment based on an OpenGL2 rendering context
HPS.ApplicationWindowKey appWindowKey = HPS.Database.CreateApplicationWindow(myWindowHandle, HPS.Window.Driver.OpenGL2);
// after the window is created, you can build your tree and update it normally
appWindowKey.IncludeSegment(modelKey);
appWindowKey.Update();
Off-Screen Window
Although technically a window in Visualize, the offscreen window isn’t visible to the user. This type of window is a memory buffer. However, it is still a completely functional Visualize rendering context. Offscreen windows are discussed in detail here.
Stand-Alone Window
The standalone window is used when no host window is provided to host Visualize. In this situation, Visualize will create a default window and use it for rendering. The standalone window may be useful for quick prototyping, testing, or for very simple applications without GUI controls. The Visualize rendering context will use the entire window for rendering. The code below is a complete program that will result in a blank Visualize window:
#include "hps.h"
using namespace HPS;
int _tmain(int argc, _TCHAR* argv[])
{
World world();
StandAloneWindowKey sawk = Database::CreateStandAloneWindow();
sawk.Update();
return 0;
}
using HPS;
namespace HPS_020612_3
{
class Program
{
static World world = new World(); // it is important to have a static reference to avoid garbage collector
static void Main(string[] args)
{
StandAloneWindowKey sawk = Database.CreateStandAloneWindow();
sawk.Update();
}
}
}
To set the size of a standalone window, use the HPS::StandAloneWindowOptionsKit
to set the subscreen:
HPS::StandAloneWindowOptionsKit sawok;
// parameters are in window coordinates
sawok.SetSubscreen(HPS::Rectangle(0.25f, 0.75f, -0.35f, 0.75f));
// fixes the aspect ratio
sawok.SetMobility(HPS::Window::Mobility::FixedRatio);
StandAloneWindowKey windowKey = HPS::Database::CreateStandAloneWindow(sawok);
HPS.StandAloneWindowOptionsKit sawok = new HPS.StandAloneWindowOptionsKit();
// parameters are in window coordinates
sawok.SetSubscreen(new HPS.Rectangle(0.25f, 0.75f, -0.35f, 0.75f));
// fixes the aspect ratio
sawok.SetMobility(HPS.Window.Mobility.FixedRatio);
HPS.StandAloneWindowKey windowKey = HPS.Database.CreateStandAloneWindow(sawok);
Portfolios and definitions overview
In Visualize, a definition is an entity that encapsulates a type of styling. Unlike segments, they do not have parents, children or siblings. Instead, they belong to a portfolio. A portfolio is a named area where definitions are created and stored. An example of a definition is a texture, line pattern, or custom glyph. Organizing these resources into a portfolio makes reuse relatively simple.
Like all other database elements, every portfolio has a unique key associated with it called a HPS::PortfolioKey
. Portfolios can be shared among segments. Once the styles have been organized into different portfolios, they can be used in a scene graph. A single segment can use only one portfolio at a time, however, child segments inherit all definitions from their parents’ portfolios and can augment or replace them by definitions in their own portfolio.
Portfolios include the resources used to style a segment
To learn more about portfolios and definitions, please see the portfolios and definitions discussion beginning in section 4.
Database Traversal
Using a HPS::SegmentKey
, it is possible to get the parent and children segments using the Up()
and Down()
methods.
// returns parent segment key
HPS::SegmentKey parent = mySegmentKey.Up();
// returns child key for segment named "child_segment_name". invalid key returned if segment is not present
HPS::SegmentKey child1 = mySegmentKey.Down("child_segment_name");
// overloaded Down() method will create the child segment if it doesn't exist
HPS::SegmentKey child2 = mySegmentKey.Down("new_segment", true);
// returns parent segment key
HPS.SegmentKey parent = mySegmentKey.Up();
// returns child key for segment named "child_segment_name". invalid key returned if segment is not present
HPS.SegmentKey child1 = mySegmentKey.Down("child_segment_name");
// overloaded Down() method will create the child segment if it doesn't exist
HPS.SegmentKey child2 = mySegmentKey.Down("new_segment", true);
Note that if Up()
is called on a segment that has no parent, such as a root segment or a window key, the returned key will be invalid. If you try to use an invalid key an HPS::InvalidObjectException
will be thrown. Therefore, if there is any doubt about the validity of a HPS::SegmentKey
, use the HPS::SegmentKey::Type()
function:
if (mySegmentKey.Type() == HPS::Type::None) {
// mySegmentKey is invalid
}
if (mySegmentKey.Type() == HPS.Type.None)
{
// mySegmentKey is invalid
}
If you need a child HPS::SegmentKey
but you don’t explicitly know the name associated with it, you can use HPS::SegmentKey::ShowSubsegments
to get an array of all children:
// the basic ShowSubsegments() gets the number of child segments
size_t numChildren = mySegmentKey.ShowSubsegments();
// the overloaded ShowSubsegments returns the count and also fills in the array with child SegmentKeys
SegmentKeyArray childArray;
numChildren = mySegmentKey.ShowSubsegments(childArray);
// the basic ShowSubsegments() gets the number of child segments
ulong numChildren = mySegmentKey.ShowSubsegments();
// the overloaded ShowSubsegments returns the count and also fills in the array with child SegmentKeys
HPS.SegmentKey[] childArray;
numChildren = mySegmentKey.ShowSubsegments(out childArray);
Database Search
Often, HPS::Database
traversal is too limited or cumbersome to use when you already know what you’re looking for, especially when the scene graph is extensive. In this case, a Database search is more appropriate. Searches are always done using the HPS::SegmentKey::Find
function, and can be used to find objects within a segment, any subsegment, or all subsegments and included segments. The results are returned in a HPS::SearchResults
object. Imagine you are using a Database search to find all circles in your scene. One way to do this would be the following:
HPS::SearchResults searchResults;
size_t numResults = mySegmentKey.Find(HPS::Search::Type::Circle, // searching for circles
HPS::Search::Space::Subsegments, // within all subsegments
searchResults); // search results returned here
HPS::SearchResultsIterator it = searchResults.GetIterator();
while (it.IsValid()) {
Key key = it.GetItem();
if (key.Type() == HPS::Type::CircleKey) {
// do something with this object
}
it.Next();
}
HPS.SearchResults searchResults;
ulong numResults = mySegmentKey.Find(HPS.Search.Type.Circle, // searching for circles
HPS.Search.Space.Subsegments, // within all subsegments
out searchResults); // search results returned here
HPS.SearchResultsIterator it = searchResults.GetIterator();
while (it.IsValid())
{
HPS.Key key = it.GetItem();
if (key.Type() == HPS.Type.CircleKey)
{
// do something with this object
}
it.Next();
}
IMPORTANT: Using the Type()
function has performance implications. Please see our section on using keys for details.
Most types of geometry and attributes can be specified as a search parameter. To search for everything, use the search parameter HPS::Search::Type::Everything
(or HPS.Search.Type.Everything
in C#).
IMPORTANT: Attributes and user data set on geometry is not searched using the database search. If your application depends on the search feature, you should plan to set attributes at the segment level.
Search Behavior
An overload to HPS::SegmentKey::Find
accepts a HPS::SearchOptionsKit
. In addition to the criteria and search space parameters offered by the example above, behavior is also an option when using the search options kit. The search behavior can be set to HPS::Search::Behavior::FirstMatch
or HPS::Search::Behavior::Exhaustive
. Exhaustive
will return all matches in the subtree you specify whereas FirstMatch
only returns the first item that matches your search criteria.
A third option, ExhaustivePaths
, will instruct the system to record the paths to objects and return multiple copies if found. Normally, this is not the case - the search will only return one copy of instanced geometry. This option would most commonly would be used with the search space set to SubsegmentsAndIncludes
and is particularly useful when you want each individual instance of an included object to be returned in the search results set (along with its path). While looking at the results of such a search, HPS::KeyPath HPS::SearchResultsIterator::GetPath() const;
may be used to query the path associated with an item.
User Data
Mapping scene graph data to application data can be useful in a variety of situations - especially when performing a query or responding to a selection. The HOOPS Visualize database allows you to associate your external data with segments or with individual pieces of geometry. Visualize calls this data user data. User data is optional, and is always supplied and interpreted by the developer.
All user data is stored in the Database as a byte array. Any HPS::GeometryKey
or HPS::SegmentKey
can have user data. User data does not conform to the a traditional attribute rules in that it does not inherit to child segments.
Any number of user data entries can be present on a particular segment or geometry key. When setting the data, you choose an index on which the data is placed. Indices do not have to be sequential, but must be unique within the segment (indices that are already in use will be overwritten). The code snippet below demonstrates how to set, retrieve, and unset user data.
// set the data "HOOPS" on this segment at index 33
mySegmentKey.SetUserData(33, 6, (HPS::byte*)"HOOPS");
// get the user data into a ByteArray
ByteArray myByteArray(100);
mySegmentKey.ShowUserData(33, myByteArray);
// get the count of the number of user data on this segment
size_t data_count = mySegmentKey.ShowUserDataCount();
// deletes the user data from index 33
mySegmentKey.UnsetUserData(33);
byte [] myData = new byte[] { (byte) 'H', (byte) 'O', (byte) 'O', (byte) 'P', (byte) 'S' } ;
// set the data "HOOPS" on this segment at index 33
mySegmentKey.SetUserData(new IntPtr(33), 5, myData);
// get the user data into a ByteArray
byte[] myByteArray = new byte[100];
mySegmentKey.ShowUserData(new IntPtr(33), out myByteArray);
// get the count of the number of user data on this segment
ulong data_count = mySegmentKey.ShowUserDataCount();
// deletes the user data from index 33
mySegmentKey.UnsetUserData(new IntPtr(33));
NOTE: When loading an HSF file, any user options present in the file will be UTF8 encoded and stored as user data.