 |
» |
|
|
 |
The call_segment
and execute_segment
routines cause a display list segment to be traversed unconditionally;
that is, if the call is there, it will always
pass control to the specified segment. While this is definitely
useful, there are some cases in which you might want a segment to
be traversed only if a certain condition is met. Display List offers
this capability in the three routines cond_call_segment,
cond_execute_segment
and cond_return.
As their names imply, the former two traverse a subordinate segment
if its condition is met, and the latter returns control to the calling
segment if its condition is met. All the conditional commands supported by Display List have
similarities in syntax: in addition to the parameters required by
their unconditional versions, there are two other parameters: - 〈cond_index_select〉
This parameter — the condition
index selector — specifies the condition to
be tested. There are three available conditions: their numeric indexes
are made mnemonic by the tokens CI_FALSE,
CI_PRUNE, and
CI_CULL. These
are explained below. - 〈comp_flag〉
This parameter — the comparison
flag — contains the value with which the specified
condition is to be compared. The value of 〈comp_flag〉
can be either FALSE
or TRUE.
If the boolean value resulting from the condition specified
by 〈cond_index_select〉
is equal to the boolean value specified by 〈comp_flag〉,
the appropriate action is taken; if the values are different, no
action is taken as a result of that element in the segment, and
traversal in the current segment continues at the following element. What Are the Conditions? |  |
There are three conditions — three valid values for
〈cond_index_select〉
— supported by Display List. One can be used for temporarily
making the conditional statement unconditional (sometimes used in
debugging), and the other two allow you to make decisions based
on conditions in Display List itself ("CI"
stands for "condition index"): - CI_FALSE
The CI_FALSE
condition can be useful during debugging; you can cause a segment
to be always traversed or never
traversed, depending on the value you compare with it. In effect,
by specifying 〈comp_flag〉
as FALSE, you
can temporarily change a conditional traversal into an unconditional
traversal; and by specifying 〈comp_flag〉
as TRUE, you
can temporarily "remove" a segment-traversing call without actually
removing the line from your source code. - CI_PRUNE
Suppose you have a segment in which all its primitives
draw their output within a certain volume of Modeling Coordinate
(MC) space. Display List allows you to specify such a three-dimensional
bounding box and if, during segment traversal,
the entire bounding box is outside of the currently active clip
limits, the "prune" condition is set. You can test whether this
prune condition is set, and, if so, avoid calling the whole segment.
Pruning segments off the display list hierarchy can result in a
large performance increase. - CI_CULL
Suppose you have a display list that draws a large
object that is very elaborately defined, down to the smallest details.
These details are necessary when zooming in for a close look, but
the level of detail is such that when you are not
zoomed in, the fineness of detail in the representation merely slows
the rendering time, and the end result is that many objects are
drawn so small that you can't see them anyway. Display List addresses
this problem by allowing you to specify a certain size (in pixels)
that the bounding box must exceed. If the rendered size of the objects
in the segment is smaller than the size of the bounding box, the
"cull" condition is set. Testing the cull condition allows you,
for example, to draw a detailed version of a small object when zoomed
in close, but a very simple, quickly rendered version of the object
when zoomed out. Changing the representation of objects, based on
their ultimate size on the screen, can increase performance significantly.
What are the Actions? |  |
There are three different actions that can be taken conditionally,
according to the above conditions. They are: - cond_call_segment
This statement does a call_segment
if the associated condition comparison evaluates to TRUE. As with the unconditional
call_segment
routine, the Starbase state is neither saved before nor restored
after traversal of the specified segment. - cond_execute_segment
This statement does an execute_segment
if the associated condition comparison evaluates to TRUE. As with the unconditional
execute_segment
routine, the Starbase state is saved before traversal of the specified
segment, and it is restored afterwards. - cond_return
This statement returns control to the calling segment
if the associated condition comparison evaluates to TRUE. This is similar to
the unconditional set_disp_traversal_control
with a value of TRAVERSAL_RETURN.
The syntax for the three routines above are as follows: cond_call_segment(〈fildes〉,
〈cond_index_select〉, 〈comp_flag〉,
〈segno〉); cond_execute_segment(〈fildes〉,
〈cond_index_select〉, 〈comp_flag〉,
〈segno〉); cond_return(〈fildes〉,
〈cond_index_select〉,〈comp_flag〉);
where 〈cond_index_select〉
and 〈comp_flag〉 are
as described above, and 〈segno〉
is the segment number to call. Setting the Conditions |  |
There are two different conditions mentioned above: pruning
and culling. The values needed for these conditions are set by the
routines set_extent
and set_cull_size.
These are discussed below. The concept of "setting the bounding box" was referred to
above, but no mention was made of just how
to do that. The routine that sets the size of the bounding box is
set_extent, and
its syntax is: set_extent(〈fildes〉,
〈mc_extent〉);
where: 〈mc_extent〉
is a 2×3 array of floating-point numbers. The top row contains
the X, Y, and Z minima, respectively, and the bottom row contains
the X, Y, and Z maxima. Note that Fortran uses
column-major order instead of row-major order when defining its
arrays; thus, Fortran users must declare a 3×2
array.
When CI_PRUNE
is specified as a condition, the volume described by the 〈mc_extent〉
matrix is checked against the currently active clip limits. If the
volume is entirely outside the clip limits, the prune condition
is set to TRUE,
and execution proceeds accordingly. When CI_CULL is specified as a
condition, the 3D bounding box is projected onto the display. The
length of the diagonal of the resulting rectangle is checked against
the size specified in the set_cull_size
routine, and execution continues according to whether the diagonal
is larger than the specified cull size or not. Two examples follow shortly, both of which show the use of
the set_extent
routine. The first shows it in relation to pruning; the second,
to culling. When you want your program to do culling, you need to set
the size of the bounding box, as above, but you also need to set
the size limit against which the diagonal of the projected bounding
box is compared. This size limit is set with the set_cull_size routine: set_cull_size(〈fildes〉,
〈cull_size〉);
where: 〈cull_size〉
is a floating-point value that specifies the length, in DCs, of
the diagonal of the bounding box when it is projected onto the display.
Below this size, the CI_CULL
condition is set; above this size, it is not set.
The culling example later in this chapter illustrates the
use of both set_extent
and set_cull_size. A Pruning Example |  |
As described above, "pruning" is the ability to determine
if the objects drawn by a segment are entirely outside the clip
limits and, if so, to circumvent all the segment's Starbase primitives.
This typically results in a greater rendering speed. It is sort
of a "segment clipping" functionality. Note that pruning can be implemented in two slightly different
ways, with virtually identical results. The method you choose depends
on your application and your personal preference. The two methods
are: Define the bounding box in the parent segment, and
traverse (via cond_call_segment
or cond_execute_segment)
the child segment only if CI_PRUNE
is FALSE. Unconditionally call the child segment. Once there,
define the bounding box and, if CI_PRUNE
is TRUE, return
to the parent segment (via cond_return).
The approach you use would probably depend on whether you
want the dimensions of the bounding box to be contained in the parent
segment or the child segment. In the following example program, Display List's pruning capability
is illustrated. The program defines sixteen objects, appropriately
named TimeSink,
whose sole purpose in life is take substantial amounts of time to
render. The objects are 1000-vector polylines, contained in unit
cubes that are arranged in a circular pattern. The camera aims at
the edge of the circle, such that only about three of the cubes
are within the clip limits at any one time. When pruning is not
done, Starbase must process and clip 16,000 vectors, about 13,000
of which are always invisible — outside the clip limits.
When pruning is on, however, those 13,000 vectors, contained in
their 13 cubes, are avoided altogether. This results in a substantial
reduction in rendering time for each frame. The camera flies around the ring twice; one revolution with
pruning off, and one revolution with pruning on. The increase in
rendering speed should be obvious. Here is the program:  |
#include <starbase.c.h> /* get Starbase definitions */ #include <dl.c.h> /* get Display List definitions */ #include <stdio.h> /* get standard I/O functions */ #include <math.h> /* link with library "-lm" */ #define AppendOff FALSE /* sent to "open_segment" */ #define AppendOn TRUE /* sent to "open_segment" */ #define DisplayOff FALSE /* sent to "open_segment" */ #define WholeRingPrune 100 /* segment no. picked arbitrarily */ #define WholeRingNoPrune 101 /* segment no. picked arbitrarily */ #define DoubleBufferOn TRUE /* sent to "double_buffer" */ #define ZbufferOn TRUE /* sent to "hidden_surface" */ #define CullOff FALSE /* sent to "hidden_surface" */ #define FrontOn TRUE /* sent to "depth_indicator" */ #define BackOn TRUE /* sent to "depth_indicator" */ #define NoFlags FALSE /* sent to "polyline2d" */ #define Max 16 /* the number of "time sinks" */ #define Lines 1000 /* number of lines per "time sink" */ #define ObjRadius 7 /* of circular pattern of objects */ #define CamRadius 9 /* of orbit of camera */ #define SemiSize 0.5 /* used in random number generator */ #define deg *M_PI/180 /* convert degrees to radians */ #define randomno(x) ((rand() % 200 - 100) * 0.01 * x) /* range: -x to +x */ main() /* program "Pruning.c" */ { int fildes; /* file descriptor */ float TimeSink[Max][Lines][3];/* time-consuming things to draw */ float Xoffset, Zoffset; /* temporary variables */ float MCextent[2][3]; /* for prune-condition testing */ int Buffer = 0; /* for double buffering */ camera_arg Camera; /* for camera model */ int I, J; /* loop control variables */ float Theta; /* loop control variable */ /*- set up Starbase */ if ((fildes = gopen(getenv("SB_OUTDEV"), OUTDEV, NULL, INIT | THREE_D)) == -1) { fprintf(stderr, "%s %s\\n", "Error: gopen failed using environment", "variable SB_OUTDEV."); exit(-1); } vdc_extent(fildes, 0.0, 0.0, 0.0, 1.25, 1.0, 1.0); double_buffer(fildes, DoubleBufferOn | INIT, 12); hidden_surface(fildes, ZbufferOn, CullOff); clear_control(fildes, CLEAR_DISPLAY_SURFACE | CLEAR_ZBUFFER); depth_indicator(fildes, FrontOn, BackOn); background_color(fildes, 0.4, 0.4, 0.0); /*- define the objects to render -*/ for (I = 0; I < Max; I++) { Xoffset = ObjRadius * cos((I * (360.0 / Max)) deg); Zoffset = ObjRadius * sin((I * (360.0 / Max)) deg); for (J = 0; J < Lines; J++) { TimeSink[I][J][0] = Xoffset + randomno(SemiSize); TimeSink[I][J][1] = randomno(SemiSize); TimeSink[I][J][2] = Zoffset + randomno(SemiSize); } } /*- put the objects into low-level segments */ for (I = 0; I < Max; I++) { open_segment(fildes, I, AppendOff, DisplayOff); line_color_index(fildes, I); polyline3d(fildes, &TimeSink[I][0][0], Lines, NoFlags); close_segment(fildes); } /*- define the high-level segments -*/ for (I = 0; I < Max; I++) { /*- append call of segment "I" to non-pruning segment -*/ open_segment(fildes, WholeRingNoPrune, AppendOn, DisplayOff); execute_segment(fildes, I); close_segment(fildes); /*- append conditional call of segment "I" to pruning segment -*/ Xoffset = ObjRadius * cos((I * (360.0 / Max)) deg); Zoffset = ObjRadius * sin((I * (360.0 / Max)) deg); MCextent[0][0] = Xoffset - SemiSize; MCextent[0][1] = -SemiSize; MCextent[0][2] = Zoffset - SemiSize; MCextent[1][0] = Xoffset + SemiSize; MCextent[1][1] = SemiSize; MCextent[1][2] = Zoffset + SemiSize; open_segment(fildes, WholeRingPrune, AppendOn, DisplayOff); set_extent(fildes, MCextent); cond_execute_segment(fildes, CI_PRUNE, FALSE, I); close_segment(fildes); } /*- set up the camera and draw the image -*/ Camera.upx = 0.0, Camera.upy = 1.0, Camera.upz = 0.0; Camera.projection = CAM_PERSPECTIVE; Camera.field_of_view = 30.0; Camera.front = -CamRadius; Camera.back = CamRadius; printf("Pruning not now active.\\n"); for (Theta = 0.0; Theta < 360.0; Theta += 1.0) { dbuffer_switch(fildes, Buffer = !Buffer); Camera.refx = ObjRadius * 1.8 * cos((Theta + 90) deg); Camera.refy = 0.0; Camera.refz = ObjRadius * 1.8 * sin((Theta + 90) deg); Camera.camx = CamRadius * cos(Theta deg); Camera.camy = 1.0; Camera.camz = CamRadius * sin(Theta deg); view_camera(fildes, &Camera); refresh_segment(fildes, WholeRingNoPrune); } printf("Pruning now active.\\n"); for (Theta = 0.0; Theta < 360.0; Theta += 1.0) { dbuffer_switch(fildes, Buffer = !Buffer); Camera.refx = ObjRadius * 1.8 * cos((Theta + 90) deg); Camera.refy = 0.0; Camera.refz = ObjRadius * 1.8 * sin((Theta + 90) deg); Camera.camx = CamRadius * cos(Theta deg); Camera.camy = 1.0; Camera.camz = CamRadius * sin(Theta deg); view_camera(fildes, &Camera); refresh_segment(fildes, WholeRingPrune); } gclose(fildes); } |
 |
Here is one typical frame during the execution of the program.
You can see that the circular arrangement of the line-filled cubes
extends off the screen to the left. Your application's performance may also benefit by using adaptive
clipping — where you can temporarily expand
the clip limits to encompass the entire VDC extent. See adapt_clip_to_extent(3G)
in the Reference section for further information. A Culling Example |  |
The second method of conditional traversal of display list
segments has to do with "culling." This is basically specifying
a certain size in pixels, below which, the CI_CULL flag is set. This
is typically used as follows: when an object would be rendered smaller
than a certain size, a simpler, more quickly executed version is
drawn instead. Or, if you prefer, nothing at all is drawn. In the following example, a telephone keypad is drawn. There
are two versions of it: A detailed version, for close-ups.
This uses the bold sans serif font (the spline-edged, filled-polygon
font) for accuracy of detail. A rougher version, for more distant shots. This
uses the simplex sans serif font for speed of rendering.
At the beginning of the program, the keypad is drawn in the
detailed version. It gradually recedes from you, and at a certain
threshold, the simpler version is rendered in its place. It recedes
still further, and then starts coming back. When it again crosses
the threshold, the detailed version is once again rendered. The
threshold point, set by the routine set_cull_size,
is defined to be 500 pixels[16]. Culling, like pruning, is a performance enhancer; when the
keypad is being drawn, notice the increase in speed when the simpler
version is used. Here is the program:  |
#include <starbase.c.h> /* get Starbase definitions */ #include <dl.c.h> /* get Display List definitions */ #include <stdio.h> /* get standard I/O functions */ #include <math.h> /* link with library "-lm" */ #define AppendOff FALSE /* sent to "open_segment" */ #define DisplayOff FALSE /* sent to "open_segment" */ #define DetailedKeypad 1 /* for close-ups */ #define RoughKeypad 2 /* for far shots */ #define KeySize 10.0 /* millimeters */ #define KeySemiSize (KeySize / 2) #define KeyEdge 1.0 /* millimeter */ #define KeyExtent (KeySize + 2 * KeyEdge) #define KeyGap 3.0 /* millimeters */ #define KeypadWidth (3 * KeyExtent + 2 * KeyGap) #define KeypadHeight (4 * KeyExtent + 3 * KeyGap) #define WholeKeypad 3 /* the parent segment */ #define DoubleBufferOn TRUE /* sent to "double_buffer" */ #define BoldSansSerif 6 /* sent to "text_font_index" */ #define SimplexSansSerif 4 /* sent to "text_font_index" */ #define NoEdges FALSE /* sent to "interior_style" */ #define Edges TRUE /* sent to "interior_style" */ #define EndOfLine FALSE /* sent to "text2d" */ #define FrontOn TRUE /* sent to "depth_indicator" */ #define BackOn TRUE /* sent to "depth_indicator" */ #define deg *M_PI/180 /* convert degrees to radians */ int fildes; /* file descriptor */ main() /* program "Culling.c" */ { static struct Key { /* the text on telephone keypads */ char Letters[4]; char Label[2]; } Keypad[12] = { " ", "1", "ABC", "2", "DEF", "3", "GHI", "4", "JKL", "5", "MNO", "6", "PRS", "7", "TUV", "8", "WXY", "9", " ", "*", " ", "0", " ", "#"}; float MCextent[2][3]; /* for prune-condition testing */ int Buffer = 0; /* for double buffering */ camera_arg Camera; /* for camera model */ int I, Row, Col; /* loop control variables */ float X, Y, Theta; /* loop control variables */ if ((fildes = gopen(getenv("SB_OUTDEV"), OUTDEV, NULL, INIT | THREE_D)) == -1) { fprintf(stderr, "%s %s\\n", "Error: gopen failed using environment", "variable SB_OUTDEV."); exit(-1); } vdc_extent(fildes, 0.0, 0.0, 0.0, 1.25, 1.0, 0.0); depth_indicator(fildes, FrontOn, BackOn); double_buffer(fildes, DoubleBufferOn | INIT, 12); background_color(fildes, 0.4, 0.4, 0.0); /*- put the object into low-level segments */ open_segment(fildes, DetailedKeypad, AppendOff, DisplayOff); text_font_index(fildes, BoldSansSerif); for (I = 0; I < 12; I++) { Row = I / 3, Col = I % 3; X = -KeypadWidth / 2 + (KeyExtent + KeyGap) * Col + KeyExtent / 2; Y = KeypadHeight / 2 - (KeyExtent + KeyGap) * Row - KeyExtent / 2; DrawDetailedKey(X, Y, Keypad[I].Letters, Keypad[I].Label, (I == 9 || I == 11)); } close_segment(fildes); open_segment(fildes, RoughKeypad, AppendOff, DisplayOff); text_font_index(fildes, SimplexSansSerif); for (I = 0; I < 12; I++) { Row = I / 3, Col = I % 3; X = -KeypadWidth / 2 + (KeyExtent + KeyGap) * Col + KeyExtent / 2; Y = KeypadHeight / 2 - (KeyExtent + KeyGap) * Row - KeyExtent / 2; DrawRoughKey(X, Y, Keypad[I].Letters, Keypad[I].Label, (I == 9 || I == 11)); } close_segment(fildes); /*- define the high-level segment */ MCextent[0][0] = -KeypadWidth / 2; MCextent[0][1] = -KeypadHeight / 2; MCextent[0][2] = 0.0; MCextent[1][0] = KeypadWidth / 2; MCextent[1][1] = KeypadHeight / 2; MCextent[1][2] = 0.0; open_segment(fildes, WholeKeypad, AppendOff, DisplayOff); set_extent(fildes, MCextent); set_cull_size(fildes, 500.0); cond_execute_segment(fildes, CI_CULL, FALSE, DetailedKeypad); cond_execute_segment(fildes, CI_CULL, TRUE, RoughKeypad); close_segment(fildes); /*- set up the camera and draw the image -*/ Camera.refx = Camera.refy = Camera.refz = 0.0; Camera.upx = 0.0, Camera.upy = 1.0, Camera.upz = 0.0; Camera.field_of_view = 45.0; Camera.back = 1.0, Camera.front = -1.0; Camera.projection = CAM_PERSPECTIVE; for (Theta = 0.0; Theta < 360.0 * 3; Theta += 5.0) { dbuffer_switch(fildes, Buffer = !Buffer); Camera.camx = Camera.camy = 0.0; Camera.camz = -((sin(Theta deg) / 2 + 0.5) * 150.0 + 100.0); view_camera(fildes, &Camera); refresh_segment(fildes, WholeKeypad); } gclose(fildes); } /**********************************************************************/ DrawDetailedKey(X, Y, Letters, Label, Special) float X, Y; /* location of CENTER of key */ char *Letters; /* the letters above main key label */ char *Label; /* the main label of the key */ int Special; /* a "special" keys ("*" or "#")? */ { /*- draw the key itself */ interior_style(fildes, INT_HOLLOW, Edges); rectangle(fildes, X - KeySemiSize, Y + KeySemiSize, X + KeySemiSize, Y - KeySemiSize); rectangle(fildes, X - KeySemiSize - KeyEdge, Y + KeySemiSize + KeyEdge, X + KeySemiSize + KeyEdge, Y - KeySemiSize - KeyEdge); /*- draw the key labels */ interior_style(fildes, INT_SOLID, NoEdges); if (Special) { text_alignment(fildes, TA_CENTER, TA_HALF, 0.0, 0.0); character_height(fildes, 9.0); text2d(fildes, X, Y, Label, WORLD_COORDINATE_TEXT, EndOfLine); } else { Y = Y - KeySemiSize + KeySize * 0.6; text_alignment(fildes, TA_CENTER, TA_BOTTOM, 0.0, 0.0); character_height(fildes, 4.0); text2d(fildes, X, Y, Letters, WORLD_COORDINATE_TEXT, EndOfLine); text_alignment(fildes, TA_CENTER, TA_CAP, 0.0, 0.0); character_height(fildes, 9.0); text2d(fildes, X, Y, Label, WORLD_COORDINATE_TEXT, EndOfLine); } }/*****************************************************************/ DrawRoughKey(X, Y, Letters, Label, Special) float X, Y; /* location of CENTER of key */ char *Letters; /* the letters above main key label */ char *Label; /* the main label of the key */ int Special; /* a "special" keys ("*" or "#")? */ { /*- draw the key itself */ interior_style(fildes, INT_HOLLOW, Edges); rectangle(fildes, X - KeySemiSize, Y + KeySemiSize, X + KeySemiSize, Y - KeySemiSize); rectangle(fildes, X - KeySemiSize - KeyEdge, Y + KeySemiSize + KeyEdge, X + KeySemiSize + KeyEdge, Y - KeySemiSize - KeyEdge); /*- draw the key labels */ if (Special) { text_alignment(fildes, TA_CENTER, TA_HALF, 0.0, 0.0); character_height(fildes, 9.0); text2d(fildes, X, Y, Label, WORLD_COORDINATE_TEXT, EndOfLine); } else { Y = Y - KeySemiSize + KeySize * 0.6; text_alignment(fildes, TA_CENTER, TA_BOTTOM, 0.0, 0.0); character_height(fildes, 4.0); text2d(fildes, X, Y, Letters, WORLD_COORDINATE_TEXT, EndOfLine); text_alignment(fildes, TA_CENTER, TA_CAP, 0.0, 0.0); character_height(fildes, 9.0); text2d(fildes, X, Y, Label, WORLD_COORDINATE_TEXT, EndOfLine); } } |
 |
Here is one typical frame during the execution of the program.
The text shown here is the bold sans serif font — the more
detailed, and therefore slower, font.
|