Creating and Rendering Parasolid Entities
Rendering Parasolid entities requires a sequence of steps, most of which are encapsulated by the HOOPS/Parasolid Integration functions.
First, a ‘Frustum’, which includes a set of Graphical Output (GO) routines, must be implemented and registered with Parasolid.
Second, the tessellated geometric data intercepted by the GO routines must be stored and displayed to various devices (rendered).
Once these are accomplished, the Parasolid render routines (PK_TOPOL_render_xxx) must be called in order that the geometry associated with each Parasolid object can be rendered via the frustum.
The HOOPS/Parasolid Integration fully encapsulates the first two items. Additionally, when a Parasolid file is read via the HP_Read_Xmt_File()
function, all the necessary PK_TOPOL_render_xxx
calls are made automatically as each Parasolid part/entity is parsed. However, the HP_Render_Entity
routine needs to be manually called when Parasolid entities are being dynamically created and edited via the application’s user-interface and underlying logic.
As an example, we will go through the process for inserting a cylinder object into Parasolid, and passing the tessellated information to HOOPS for subsequent rendering and interaction. This and future sections assume familiarity with the types of Parasolid entities, denoted by the Parasolid type PK_CLASS_t
.
Defining Parasolid Geometry
A cylinder, along with many other predefined Parasolid objects, is represented by a PK_BODY entity, and is created via the routine:
<error_code> = PK_BODY_create_solid_cyl(<radius>, <height>, <cyl_basis_set>, <cyl_body>);
Parasolid entities are referred to by a ‘TagID’, so this function populates the Parasolid kernel with a cylinder of the specified radius and basis_set, and returns the TagID of the associated Parasolid body. However, the cylinder will not actually be rendered (mapped to corresponding HOOPS segments and geometry) until a call is made to the HP_Rendering_Entity()
routine, discussed in the next section.
The following code demonstrates how to create a Parasolid cylinder. Note that it calls HP_Render_Entity()
:
// create a parasolid cylinder and return the cylinder's body id
PK_BODY_t HOpCreateSolidCylinder::CreateSolidCylinder(HPoint ¢er, HVector radius_vector, HVector height_vector)
{
PK_ERROR_code_t error_code;
PK_AXIS2_sf_t cyl_basis_set;
PK_BODY_t cyl_body;
float radius, height;
double view[3], up[3];
HPoint add, subtract;
HUtility::GetViewplaneVectors(m_pView->GetSceneKey(), view, up);
radius = (float) HC_Compute_Vector_Length(&radius_vector);
height = (float) HC_Compute_Vector_Length(&height_vector);
// to determine orientation add and subtract the view plane up
// vector and the cylinder up vector. By measuring the size of
// each vector we can determine the direction of the cylinder
add.Set(height_vector.x + (float) up[0],
height_vector.y + (float) up[1],
height_vector.z + (float) up[2]);
subtract.Set(height_vector.x - (float) up[0],
height_vector.y - (float) up[1],
height_vector.z - (float) up[2]);
cyl_basis_set.location.coord[0] = (double) center.x;
cyl_basis_set.location.coord[1] = (double) center.y;
cyl_basis_set.location.coord[2] = (double) center.z;
if (HC_Compute_Vector_Length(&add) > HC_Compute_Vector_Length(&subtract))
{
cyl_basis_set.axis.coord[0] = up[0];
cyl_basis_set.axis.coord[1] = up[1];
cyl_basis_set.axis.coord[2] = up[2];
}
else
{
cyl_basis_set.axis.coord[0] = -up[0];
cyl_basis_set.axis.coord[1] = -up[1];
cyl_basis_set.axis.coord[2] = -up[2];
}
cyl_basis_set.ref_direction.coord[0] = view[0];
cyl_basis_set.ref_direction.coord[1] = view[1];
cyl_basis_set.ref_direction.coord[2] = view[2];
HSolidModel * model = (HSolidModel*)(m_pView->GetModel());
PK_PARTITION_set_current(model->GetPartition());
error_code = PK_BODY_create_solid_cyl(radius, height, &cyl_basis_set,&cyl_body);
if ( report_ifail(error_code,0) != 0)
{
char error_message[256];
sprintf(error_message, "failed rendering cylinder\n Error Code %d", error_code);
MessageBox(NULL, error_message, 0, MB_OK);
return 0;
}
// now put it in the HOOPS database
HP_Render_Entity(cyl_body)
return cyl_body;
}
Rendering Parasolid Geometry
After a Parasolid entity is defined and inserted into the Parasolid database, the tessellated geometric information that represents the entity must be generated. PK_BODY
entities (along with many others) are composed of PK_FACE
and PK_EDGE
entities. These entities denote the logical surface and edge/boundary information associated with the body. Specifically, a cylinder body object is composed of three PK_FACE
entities, one for the top, one for the bottom, and one for the ‘tubular’ part of the cylinder. It is composed of two PK_EDGE
entities, one for a circle denoting the top edge, and one for a circle denoting the bottom edge. The tessellated geometric information associated with the PK_FACE
and PK_EDGE
entities would typically need to be generated by manually calling the following Parasolid routines:
<error_code> = PK_TOPOL_render_facet(<number_of_bodies>, <bodies>, <topol_transfs>, <view_transf>, <rendering_options>);
<error_code> = PK_TOPOL_render_line(<number_of_bodies>, <bodies>, <topol_transfs>, <view_transf>, <line_options>);
In addition to tessellating the body into facets and lines, these functions will send the tessellated information to the graphical output routines.
The HOOPS/Parasolid Integration includes a function which automatically makes the above PK_TOPOL_render_facet
calls, and because the HOOPS/Parasolid Integration implements the underlying GO routines, the geometric data that represents the cylinder is automatically mapped to a group of geometric primitives in the HOOPS database. By default, these primitives are inserted in the currently open HOOPS segmen. The HP_Set_Rendering_Options
function discussed later in this document, provides for control of this behavior, including creation of subsegments and optimization of the geometry.
To render Parasolid entities and have their tessellated representation automatically mapped to HOOPS graphical primitives, you may simply call:
HP_Render_Entities (count, entities);
where ‘count’ is the number of entities, and ‘entities’ is the array of Parasolid entities (of type PK_Entity_t). This function also updates the bi-directional mapping (which uses a hash table) maintained by the HOOPS/Parasolid integration.<br>
Inserting Parasolid Geometry Into the HOOPS Database
Recall that the HOOPS geometry and any additional segments generated as a result of the HP_Render_Entity and HP_Render_Entities routines will be placed in the currently open HOOPS segment. The following code ties together the code necessary to insert a cylinder object into both Parasolid and HOOPS. It assumes that we have obtained the creation parameters for a cylinder from the UI, opens a segment and then calls the CreateSolidCylinder
function discussed previously. After CreateSolidCylinder
is called (which calls HP_Render_Entity
, discussed above), the Parasolid database will contain a parametric representation of the cylinder (a Parasolid ‘body’), and the HOOPS database will contain the corresponding geometric representation (a HOOPS ‘shell’):
// open up a HOOPS segment which will contain the cylinder geometry
HC_Open_Segment_By_Key(GetView()->GetModel()->GetModelKey());
// put each entity in a separate segment so that we can easily apply
// transformations to separately created entities
HC_Open_Segment("");
PK_BODY_t cyl_tag;
cyl_tag = CreateSolidCylinder(m_ptFirst, m_world_radius_vector,height_vector);
HC_Close_Segment();
HC_Close_Segment();
The Parasolid ‘body’ entity that represents the cylinder is accessible via the cyl_tag
TagID. Details on how to identify the HOOPS geometric primitives associated with the Parasolid entity are covered later in this document, in the section entitled ‘Operating on Parasolid Entities’.
Deleting Parasolid and HOOPS Geometry
We will recall that the HOOPS/Parasolid Integration maintains a mapping between Parasolid entities and HOOPS Geometry, which enables the developer to determine the Parasolid entities associated with a given HOOPS geometric primitive or segment, and vice versa. The HP_Render_Entities function causes this mapping to be created/updated each time it is called to populate the HOOPS database with the tessellated representation of Parasolid entities.
As a result, another HOOPS/Parasolid function must be called when deleting HOOPS geometry associated with Parasolid entities to ensure that the internal mapping is kept up to date. The function is called HP_Delete_Entity_Geometry, and would be used to delete the HOOPS geometry associated with a Parasolid entity after the latter is deleted. For example, the following function is a wrapper function which deletes a Parasolid entity along with its associated HOOPS geometry:
void HSolidModel::DeleteParasolidEntity( PK_ENTITY_t entity)
{
HP_Delete_Entity_Geometry(1, &entity);
PK_ENTITY_delete(1, &entity);
}
It is important to use the HP_Delete_Entity_Geometry
function whenever deleting HOOPS geometric primitives associated with deleted Parasolid entities. If only the normal HOOPS/3DGS deletion functions were called (HC_Delete_By_Key
, etc…), then the HOOPS/Parasolid mapping would not be correct, and problems would occur during subsequent operations.
Retesselation of Geometry Into the Same Segment Structure
A call to HP_Delete_Entity_Geometry()
normally deletes all associated entities from HOOPS including segments. Sometimes however it is desirable to preserve the segment structure if e.g. extra information with no connection to the solid modeller has been previously associated to a body. To allow that there is a third parameter in the call to HP_Delete_Entity_Geometry()
called PreserveSegments
. If this Boolean is set to true``(it defaults to ``false
) then only true HOOPS geometry is deleted and keys that are associated with segments are disregarded. This means that if a body is deleted it will be retesselated into the same segment again on the next call to HP_Render_Entity()
.
However, let’s consider the following scenario. Say we have a simple cylinder body which contains (among other things) two edges. After normal tesselation we want to move one of the edges (consisting of two HOOPS ellipses) into a separate segment (let’s call it “special”) which contains e.g. some text annotation and a different line weight attribute. We can do this with HC_Move_By_Key()
. Unfortunately, after a retesselation (even when using HP_Delete_Entity_Geometry
with PreserveSegments = true
) the two HOOPS ellipses are recreated in their default segment again and the newly created “special” segment is empty.<br>
To avoid this, HP_Associate_Entity_To_Key()
can be used which associates a HOOPS keys to a Parasolid entity. In our example we use it to associate the key of the “special” segment to the Parasolid edge entity that we want to store underneath it. Whenever the Parasolid body needs to be rerendered we delete it’s associated geometry by using HP_Delete_Entity_Geometry
with the PreserveSegment
flag set to true. This way even though all the HOOPS geometry of the body is deleted there is still the connection to the “special” segment in the database.
Here is some example code:
// first we render the object let's assume it is a cylinder) normally
HC_Open_Segment_By_Key(((HBaseModel*)my_view->GetModel())->GetModelKey());
HP_Render_Entities(1, &body);
// this code simply queries all edges of the cylinder and moves the
// first edge into a segment called special which contains a new line
// weight attribute setting
long list[10000];
int count;
int num_edges;
PK_EDGE_t *edges;
PK_BODY_ask_edges(body,&num_edges, &edges);
count = HP_Compute_Geometry_Keys(edges[0], 10000, list, "edges");
long key = HC_KOpen_Segment("special");
for(int i = 0; i < count; i++)
{
HC_Move_By_Key(list[i],".");
HC_Set_Line_Weight(8.0);
}
HC_Close_Segment();
//we are associating the edge entity to the key of the segment where
//it's geometry now resides in
HP_Associate_Key_To_Entity(edges[0],key);
// ...
// assuming the body has changed (e.g. it was the target of a boolean
// operation) we are first deleting all the bodies that are completely
// gone normally
HP_Delete_Entity_Geometry(number_of_tools, tools);
// the target body (let's assume it's the one that we have created
// above) survives the operation so we delete it's geometry but
// preserve it's segment structure
HP_Delete_Entity_Geometry(1, &target,true);
// ... (some boolean operation applied)
// the body is now rerendered in the same segment as it was sitting in
// before and (important!) the edge that we have put into the "special"
// segment is again in the special segment in it's new form (it might
// be partly cut due to the boolean)
HP_Render_Entities(1, &target);
View-Dependent Tesselation
HP_Render_Entity()
and HP_Render_Entities()
have a third parameter which is a Parasolid transformation entity (0 by default) that represents the view matrix for view dependent tessellation. It is the responsibility of the caller to set the appropriate tessellation options for the frustum routines. This example demonstrates how to temporarily overwrite tesselation options to render the body in a view dependent way:
// create the PS transform from a supplied matrix
PK_TRANSF_t view_transf;
PK_TRANSF_create(&rot_m,&view_transf);
// temporarily set tesselation options
// for silhouette+hidden line output
PK_TOPOL_render_line_o_t g_lineOptions,g_lineOptionsSav;
HP_Show_Tessellation_Options(&g_lineOptions, NULL, NULL);
HP_Show_Tessellation_Options(&g_lineOptionsSav, NULL, NULL);
g_lineOptions.edge = PK_render_edge_yes_c;
g_lineOptions.silhouette = PK_render_silhouette_yes_c;
g_lineOptions.visibility = PK_render_vis_hid_c;
HP_Set_Tessellation_Options(&g_lineOptions, NULL,NULL);
// rerender the bodies with the supplied view transformation
HC_Open_Segment_By_Key(GetModel()->GetModelKey());
HSolidModel * model = (HSolidModel *)(GetModel());
PK_PARTITION_set_current(model->GetPartition());
HP_Render_Entities(numbodies, bodies, view_transf);
HC_Close_Segment();
// restore old tessellation settings
HP_Set_Tessellation_Options(&g_lineOptionsSav, NULL, NULL);