To draw a picture of an analog clock, we store in the graphics database the graphics primitives that make up the clock.
A simple analog clock.
The clock consists of three main parts: the clock face and two hands. The face itself is made up of two subparts: a circular rim and a central hub for the hands.
The HOOPS graphics database stores graphics scenes as a hierarchy (a tree), normally depicted with the root at the top. The figure below shows a tree representing one possible configuration of the HOOPS/3dGS database for the clock.
HOOPS graphics-database structure for the clock.
In this hierarchy, the clock face is stored in the top node of the hierarchy (called "clock").
In HOOPS, each node in the tree is a segment. The program below creates the segment hierarchy for the clock. Note that, although this program is a valid HOOPS program, it will not draw anything, because we have not put in the commands to create the geometry.
Segment hierarchy for the clock.
Segments are the primary organizational principle in HOOPS Visualize. A segment can have subsegments (for example, the segment named "clock" has a subsegment named "hands", and the segment named "hands" has subsegments named "minute hand" and "hour hand"). In addition, segments can contain geometry (the segments named "clock", "minute hand", and "hour hand" all contain geometry) and attributes (the segment named "hands" contains the red color attribute set on it). Note that there is no prohibition against a segment that has subsegments also having geometry or attributes; in fact, it is common for segments to have all three:
A segment may contain geometry, attributes, and subsegments.
In the database created by the previous code snippet, the segment named "clock" has both geometry (the clock face) and a subsegment. Note that we could have also created an additional subsegment of "clock" named "face" and placed the geometry for the clock face there:
Alternate segment organization for clock.
In this alternate organization, all segments would contain either geometry or subsegments, but not both. Decisions such as how to organize the graphics database for an application are design issues that you will have to address; good database organization is covered in Section 7.2.
A segment is an object, in the object-oriented programming sense. In particular,
- A segment encapsulates a public interface - the vast majority of HOOPS commands modify the state of a segment, or return information about a segment or its contents.
- A segment uses data hiding to protect the internal details of its representation from the programmer. Hiding unnecessary details makes life simpler for the programmer, and allows HOOPS to modify its internal representation on different platforms for portability, to take advantage of faster algorithms, or to incorporate improved rendering techniques.
- A segment has attributes, which act similarly to the member variables of an object.
- A segment can have one or more subsegments. The hierarchy formed by the segments in the database is like the class hierarchy in an object-oriented language (but is more dynamic).
- Just as member variables are inherited by a class's subclasses, attributes are inherited by a segment's subsegments. This form of inheritance is called attribute inheritance.
- A segment can be reused through the use of include segments (discussed here) and style segments (discussed in in this section).
- A segment contains a single list; this list contains the geometry that belongs to that segment. This list is similar to the list member of a Smalltalk object. A segment can contain any number of pieces of geometry in its list, including zero.
In object-oriented languages, a member variable of an object can have only a single value at a time. Because a segment is an object, any one particular attribute of a segment can have only a single value at a time. For example, if a segment has its color attribute set to green and we then set the color attribute to red, the value green is replaced by the value red. The color attribute for the entire segment now has the value green and this new attribute applies to all geometry in the segment. If you want to draw two lines, one red and one green, you must use two segments, one with a color attribute of red and one with a color attribute of green.
Because each segment can have only a single value for an attribute, it is easy to find the value of an attribute. We simply ask the segment to return the value for that attribute. We can then change the value of the attribute and know exactly to what geometry the new attribute will apply - namely, all the geometry in that segment.
HOOPS Visualize has two kinds of subsegments: regular segments and include segments. Include segments support a form of reuse. Include segments are similar to symbolic (soft) links in the UNIX file system, because they allow multiple links to the same object.
A segment to be included actually lives as a normal segment (with its own geometry, attributes, and subsegments) in another part of the graphics database. For convenience, Visualize provides a place called "?Include Library" to store include segments, but segments to be included can exist anywhere in the HOOPS tree.
We create a link to such a segment, called an instance of the segment, using the Include_Segment command. The instance behaves like a regular subsegment, except that the parent of an included segment is always that segment's real parent, rather than the segment that includes the included segment.
For example, to represent four tires of a car, we can define a segment tree representing a tire, and then include four instances of the tire, as in the following program:
Reuse of a segment with Include_Segment.
The main advantages of include segments are that we can change the tire in one place and have the changes appear in all instances of the tire, and that there is a savings of space, because only one copy of the tire needs to be stored in the database.
To instance specific geometry in a segment, you can use the concept of referenced geometry.
To render the graphics database, call Update_Display. This instructs HOOPS/3dGS to walk the database tree, visiting each node that contains any primitives to be drawn. To draw a segment, HOOPS/3dGS sends to the renderer any geometry that the segment contains, and then recursively draws any subsegments. Consequently, drawing a segment draws the entire branch of the tree with that segment as the root. You would call Update_Display after changes are made to the database (edits, deletions, additions, etc...). Think of this function as causing the display to match the current state of the database.
For each primitive, each and every attribute that is applicable to that primitive must be known before the primitive can be drawn. For example, before we can draw a line, the renderer must know the line's color, its thickness, whether it is dashed, its 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 HOOPS 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. The main difference between attribute inheritance and object-oriented inheritance is that the number of different kinds of attributes about which HOOPS knows is fixed (with the exception of user-defined attributes, described in this section).
When HOOPS renders the database, it needs to know the attribute values for all the geometry in the database. For example, to draw a line, HOOPS needs to know the line color, thickness, and all other attributes that apply to a line. To determine the values of attributes, HOOPS first looks at local attributes set on the current segment. If any required attributes have not been set on the local segment, HOOPS looks in the parent of this segment to see whether a local attribute was set on the parent; HOOPS then looks at the parent's parent, and so on, until it finds a value for the attribute.
The effective value of an attribute - the value used to render the geometry in a segment - is called the net value of the attribute (or just the net attribute). As we shall see, HOOPS guarantees that every segment can determine the net value for every attribute (it must, because we need the net attribute values to render any geometry in a segment).
In the following image, containing the database diagram for the clock example, the color blue has been set on the clock segment, the color red has been set on the hands segment, and no color has been set on the "minute hand" or "hour hand" segments. What is the net attribute value for color for each segment?
Clock database with attributes.
The segments "minute hand" and "hour hand" have no local color attribute set on them, so they inherit their color attribute. Since the "hands" segment has a local color attribute set on it, the "minute hand" and "hour hand" segments will use the color attribute from "hands" as their net color attribute. So the net color attributes used to draw this scene are blue for the "clock" segment, and red for the other three segments.
We can ask HOOPS for the net value of an attribute using one of the HOOPS routines that begin with Show_Net. Below, the PShow_Net_Color command returns the net color value for the segment named "minute hand".
Retrieval of the net color attribute of a segment.
Upon startup, HOOPS/3dGS sets a local value for every attribute on the root segment of the database tree. When it is looking for the value of an attribute, HOOPS will always eventually find its way up to the root of the tree, and will then use the value it finds there. Thus, the local attribute value set on the root segment of the database acts as a default value for the net attribute value. If you do not want to use the default value assigned by HOOPS, you can change it by changing the local attribute value on the root segment of the tree. The HOOPS/3dGS Reference Manual contains a listing of the values for all the local attributes that are set on the root segment. These values are the default values for all the net attributes.
Another way to look at attribute inheritance is that, any time that you set a local attribute on a segment, you are establishing a default value for all segments below that one in the tree. Thus, in a scene with multiple complex graphical objects, each one represented by its own subtree, you can have different default values for each object by setting local attributes on the root segments of their subtrees.
HOOPS does not actually search the database for net attribute values - that would be much too slow. Instead, for each segment, HOOPS constructs a structure called a rendition that caches the net value for every possible net attribute. HOOPS can build renditions because the number of attributes is fixed.
Since the root segment of the graphics database has a value set for every attribute, the rendition for the root segment is simply the value of all these local attributes taken together. A simplified version of the rendition for the root segment might look like:
The rendition for the root segment.
Caching the net attributes in renditions is possible because HOOPS is a declarative graphics system. In a non-declarative system, the net values for the attributes are dependent on the order in which the database tree is walked, so setting a local attribute in one part of the database tree can affect net attributes in completely unrelated parts of the tree, which would invalidate the renditions and force them to be recomputed completely whenever a change was made to the database.
It should be noted that the preceding description of renditions does not describe the way that HOOPS is implemented. Since renditions are not visible to the programmer (only the component net attributes are), HOOPS is free to use an even more efficient scheme to compute and cache net attributes.
Most attributes inherit in the way described previously (in the way the color attribute does), but a few attributes inherit in other ways. Consider a modelling transform: a translate, rotate, or scale. Modelling transformations are always performed with respect to the parent segment. When we rotate an object, for example, we want all its children to rotate too (even if they have their own local modelling transformations). In the clock, when a hand rotates, we want it to rotate in relation to the center of the clock, even when a transformation is used to move (or scale, or reorient) the entire clock.
A segment's local modelling transformation is concatenated (using matrix multiplication) onto the net modelling transformation of that segment's parent - the local modelling transformation does not simply replace the parent's modelling transformation. If a segment has no local modelling transformations, the net transformation for the segment is the same as its parent's net modelling transformation. modelling transforms are discussed in more detail in Section 3.
In the image below, if the clock is rotated 20 degrees, and the hour hand is rotated an additional 90 degrees, then the net transformation for the hand is 110 degrees.
There are also a few attributes that inherit up the tree, such as bounding volumes while a few attributes, such as normal vectors, do not inherit at all.
It is also possible to temporarily override local attributes by using the attribute lock rendering option enabled with a call to Set_Rendering_Options. When a specific attribute is locked in a segment, that attribute applies to all subsegments, even if they have a local attribute set on them for the same attribute. For example, you can use attribute lock to highlight a part of the database by changing the color of everything to a particular highlight color, temporarily ignoring any local color attributes. Let's assume that we have a HOOPS/3dGS database that represents a CAD model with several layers, each layer denoting a particular system (electrical, heating, etc.), and the segments/geometry for each layer are encapsulated underneath high-level 'layer' segments. The following example will change the color to red of any geometry in the specified layer segment (and the segment's subsegments), regardless of the real colors.
Use of attribute lock to highlight a portion of the segment tree.
You can also put an attribute lock on any of the color suboptions. Let's say you only wanted to lock the diffuse color settings so that any transparency or specular settings would not be over-written on objects affected by the attribute lock. This would be achieved by setting:
Note: 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.
Note: Attribute lock provides a feature for temporary rendering instances. We do not recommend that you use this feature on a regular basis. When this feature is enabled, rendering performance could be impacted.
There is one more twist to the story of attribute inheritance. Let us say that, distributed across the hierarchy of our clock, there are numerous screws and nuts. These fasteners may be different lengths, and they may have hex heads, slotted heads, or some other topology, but they all have something in common - we want them all to be the same color. Initially, this color is a shiny steel gray, but we might want to be able to change the color to brass, or black, or some other color.
One way to have them all be the same color would be to set the color and lighting attributes (specular reflection, gloss, and so on) on each segment containing a fastener. That is not only inefficient, but also inconvenient, because changing the color of the fasteners will involve changing attributes on a large number of segments. Instead, HOOPS/3dGS provides a convenient way to take a set of attributes and to make them apply to a large number of segments. We create a segment called a style segment. A style segment is just like any other segment, except that it (usually) does not contain any geometry; it contains only attributes.
We set up the style segment with the local attributes that we want, then we use this segment to style all the other segments. HOOPS provides a convenient place to put style segments, called "?Style Library". Style segments do not have to be located there, but putting a segment in "?Style Library" is a good way to document for what the segment is going to be used. Remember that a segment name that begins with a question mark (such as "?Style Library" or "?Picture") is an alias for the actual segment name (see this section for more information on aliases).
The following program first creates a style segment for the fasteners. This style segment is named "fastener", which is a child of the segment named "?Style Library". To use this segment to style another segment, we use the Style_Segment command.
Style segments themselves can have style segments; in particular, the "fastener" segment could contain a Style_Segment command. Attributes that are "styled" act like local attributes for the purpose of further styling.
Conditionally styled segments are the same as the style segments described earlier, with the exception that these are only applied when a certain set of conditions are met. 'Conditions' are string values which can be applied as an attribute on segments. They follow the same inheritance laws as any other attribute (e.g. color). You can implement some really interesting things with conditional style segments.
Let's say you want to change the color of all the hands in the clock to black when it is 12 o' clock. First create a style segment with its color attribute set to black.
Now, you would set this style segment on the 'hands' segment but with a condition.
The visual_alert style will be applied to the hands when either noon or midnight condition is found as a net attribute on the hands segment. Notice that we have used the OR operator between conditions. AND and NOT operators are also permitted. Now, when it is noon, all you need is to set the condition on the clock segment.
Or when it is midnight
The clock hands will reflect the new style. Once you are past the time, you can unset the condition as below.
Conditional styles are useful in situations where you want the same model in multiple views but with different attributes without having to duplicate the model. Similar to conditional styles, you can conditionally include segments using HC_Conditional_Include API.
In review, rendering the database consists of walking the database tree constructing primitives, and rendering these primitives.
From the viewpoint of the renderer, a segment consists of geometry and net attributes:
A segment from the viewpoint of the renderer.
The geometry and the net attribute values are what is drawn by the renderer.
From the viewpoint of the database tree walker, the net attributes of a segment come from three places: locally set attributes, style segments, and parent segments (this discussion applies to only those attributes that inherit normally).
A segment from the viewpoint of the database tree walker.
Local attribute values are attributes that are explicitly set on a segment (via one of the "Set" commands). Style-segment attribute values come from the local attributes of a segment that is used to "style" the current segment via the Style_Segment command. Inherited net attribute values (typically) come from the net attributes of a parent segment of this segment.
Given any local attributes, style segments, and net attribute values for the parent segment, the net attribute values for the current segment are calculated as follows:
- If attribute lock is turned on for a specific attribute, the net attribute value from the parent (the inherited value) is used for the net attribute value.
- If attribute lock is off, and the attribute has a local value set on the current segment, that value is used as the net attribute value.
- Otherwise, if the current segment has any style segments, the attribute value from the style segment is used for the net attribute value (if a segment has more than one style segment and the same attribute has a local value in more than one of the style segments, the choice is arbitrary).
- Finally, if none of the preceding three methods work, the inherited net value from the current segment's parent segment is used (this method always provides a value, because all segments have a parent, and the parent has a net value for all attributes).
Note that an included segment inherits attributes from the segment that included it, rather than from its "real" parent. This rule allows different instances of an included segment to have different net attributes. Thus, each instance of an included segment has its own rendition (if an include segment is included in more than one place, it will have a set of net attribute values for each place that it is included). For example, if we use includes to include four instances of a tire for an automobile, each tire will have a different modelling transform attribute (to place each tire into the proper position), so the tire segment will have four renditions, one for each instance.
There are several ways to reference a segment: implicit, explicit by name and explicit by key. If you always use a key to refer to a segment, then it is not necessary to name the segment.
This involves relying on the currently open segment. For example, the Set_Color command sets a color attribute on the currently open segment.
If you want to set an attribute on a specific segment, you open the segment by name and then operate on it:
Any command that takes a segment name, can also take an alias. You can define an alias using the Define_Alias command. There are also several predefined aliases in HOOPS, such as "?Picture" and "?Style Library". An alias name always begins with a question mark.
In our clock example, we are going to be moving the hands to keep track of the time. It is bothersome to have to remember the segment hierarchy to get to the minute hand and the hour hand, so instead we can define two aliases:
Then, to advance the hour hand one hour, we can say
to rotate the hour hand 30 degrees clockwise around the z axis. If we ever want to reorganize the segment hierarchy of the database, then we need to change the alias in only one place, rather than in every place that the segment is named explicitly.
There is an alternate mechanism for referring to objects in HOOPS/3dGS, called keys. Keys are often more convenient and are also faster than using names. For example, in a database representing a bicycle, it would be cumbersome to create a name for every spoke on a wheel (let alone for every nut or bolt). Instead, the application would keep track of these objects using an array of keys for simple access. A key in HOOPS is the type HC_KEY. Here is a function that returns an HC_KEY:
Keys are used by commands that end with the phrase "By_Key". For example, to open the hour hand segment by key, we use
Keys are also useful because they can be used to refer to geometry inside of a segment. All the commands that insert geometry return a key. For example,
We can then use the key to edit or delete the new geometry.
Only segments can be named, so keys are the only way to refer to geometry. Commands that operate on geometry, such as Edit_Polygon or Move_Image, take a key argument but are not suffixed with the phrase "By_Key", because geometry is always referred to by key.
Keys should not be thought of as a complete replacement for names. In particular, there are no variants of commands such as Set_Color that take a key as an argument - to set a color on a segment given a key, you have to open the segment using Open_Segment_By_Key, and then do an Set_Color on the open segment.
There is a way to change a key into a segment name. It is useful for those few HOOPS commands that must take a segment name, when all you have is a key. To change a key to a segment name, format the key value as an ASCII hexadecimal string, and put an @ (at sign) on the front of it. The resulting string can be used as a segment name (just be sure that the key refers to a segment, rather than to geometry). One way to create the segment name is to use the x format of sprintf.
A segment does not need to be given a name. No-name segments are useful when the segment will be referred to exclusively by key. To omit naming a segment, use an empty string (not a null pointer) to create the segment such as Open_Segment("").
Segment names in HOOPS are made up from the normal alphanumeric characters A to Z, a to z, and 0 to 9, plus the characters "# + - $ _ ." and space. Segment names are not case sensitive - the name "CLOCK" is the same as "clock" (or "Clock", and so on). If a segment name contains space characters, more than one space character in a row is equivalent to a single space. In addition, leading and trailing spaces are not significant.
Any ASCII character can be used in a segment name if it is enclosed in matching quotation marks (you can use single quotation marks, double quotation marks, or even back quotation marks). For example, if you want a slash inside of a segment name, use the name "abc'/'def". Quoted alphabetic characters are case sensitive. The only time that quoted characters are useful is when a segment name is a string specified by the user, or is a file name. Putting such a string in quotation marks makes characters that have special meaning to HOOPS legal in the segment name (except for quotation marks, of course). Other than that, quoted characters are almost never used, and should be avoided.
The / (slash) character is used to combine segment names into a path name (like a UNIX file-system path name). The name "clock/hands" refers to the hands subsegment of the clock segment. A path name that begins with a slash is an absolute path name, which names a segment with a path that starts at the root segment. Absolute path names uniquely identify a segment. In HOOPS, you will rarely write absolute path names. Instead, HOOPS provides aliases for you to use (such as "?Picture" and "?Include Library") whose values are absolute path names.
A path name (or single name) that does not begin with a slash is a relative path name that names a segment relative to the currently open segment. The currently open segment can be referred to by the name "." (a single period). The name "./hands" (a period followed by a slash, followed by the name "hands") is the same as "hands". The only time the use of a period for a segment name is necessary is for commands that require a segment name - for example, Flush_Contents(".") flushes the contents of the currently open segment. You can refer to the parent of the currently open segment with either "^" (carat) or ".." (two periods).
The root segment is simply named "/" (slash).
A segment name that begins with a "?" (question mark) is an alias that refers to a segment.
Most commands that take a segment name as an argument can take a segment name containing wild cards. A wild card allows a single string to refer to more than one segment at the same time, and consequently allows many HOOPS commands to operate on more than one segment at a time. Wild cards are not used all that often, but they come in handy on a few occasions.
The simplest form of wild card is a set of names separated by commas and enclosed in parentheses. For example "clock/hands/(minute hand, hour hand)" refers to both hands of the clock. The wild card can be part of a name, so "clock/hands/(minute,hour) hand" has the same meaning.
The * (asterisk) character is used to replace zero or more characters in a segment name. For example, "clock/hands/* hand" may or may not refer to the same segments as "clock/hands/(minute hand, hour hand)", depending on whether we have defined any additional hands, such as one to sweep out the seconds.
The % (percent) character is used to replace a single character. For example, if you have defined sister subsegments named "seg#1", "seg#2", and "seg#-1", you can refer to the first two as "seg#%".
HOOPS also has an unusual (and particularly useful) wild card "..." (three periods) that you can use to refer to zero or more segment names in a path name. For example, "clock/.../*hand" refers to any segment name that ends with the word "hand", no matter where it lives in the subtree rooted under the segment named "clock". You can also use this wild card to refer to all segments in a subtree. For example, ::Flush_Geometry("?Picture...") removes all geometry from the HOOPS database, starting with the "?Picture" segment. The command ::Flush_Geometry("?Picture/...") does exactly the same action - the slash is optional. To flush geometry from all subsegments of "?Picture", but not from "?Picture" itself, use ::Flush_Geometry("?Picture/*...").
Wild cards should not be used in commands that require a single segment name. For example, in Open_Segment, there can be only a single currently open segment.
Up to this point, we have discussed how information can be inserted into the HOOPS database.
Once your initial scene graph is created, it is likely that you will want to extract, manipulate and/or edit this information. The first step in accomplishing this task is to find the data. To perform a general search of the database, use the contents search functions. Initiate your search by calling Begin_Contents_Search.
This function sets the criteria for the search. It takes two parameters: segment and filter.
Use the segment parameter to describe the location(s) to search. The segment parameter expects a string whose syntax is similar to the syntax for navigating between directories in Unix. For instance, to search in the currently open segment, pass ".".
Setting the segment parameter to ".." tells the function to search in the parent of the currently open segment. Both of these strings indicate that the search occurs only in the current level of the specified segment. Passing "..." indicates that the search should encompass the specified segment and all its subsegments. Finally, you can pass "...." to increase the scope of the search to cover subsegments and include segments as well.
In addition to these relative paths, you can specify an absolute path like "?Picture/model/". The segment parameter also recognizes the wildcard character '*'.
Thus, if you pass "?Picture/model*", the function will search all segments under "?Picture" that begin with the word "model".
Pass the type of the items in which you want to search for to the filter parameter. The filter parameter expects a list of items separated by commas. You can search for a variety of things including geometry, attributes, and references.
After you have called Begin_Contents_Search, you can use Show_Contents_Count to find out how many items were found based on your search criteria. Then use Find_Contents to retrieve the results of your search one item at a time. When you call Find_Contents, pass by reference a string for the type and an ::HC_KEY to receive the type and key value for your items. Note, when you are finished with retrieving the search information, it is important to call End_Contents_Search to free the memory used by the searching process.
Using the contents search functions provides a simple yet flexible way to find information in the database.
For instance, if your database consists of the assembly of a bicycle and you want to find all the places where you use black screws, you might use the following sample code.
The contents search functions have powerful functionality. However, there are some limitations. Searching does not work to find attributes set at the geometry level, nor can it tell you whether an item is open. Searching for open items is a useful task when you are in an open item but do not know the key of the item. Valid items include segments, geometry, edges, vertices, faces or anything that can be open and closed. The open item search functions work in the same way as the contents search functions. Initiate the search with a call to Begin_Open_Item_Search, get the results of the search via calls to Find_Open_Item and then terminate the search with End_Open_Item_Search.