Transformation
Pipeline
Like all 3D graphics systems, the Heidi API uses a transformation
pipeline. A transformation pipeline
is a sequence of applied coordinate transformations to map the geometry
of 3D models to the display surface. The input to the transformation pipeline
consists of points referred to as object
coordinates. These points are the frame of reference for geometry arguments
to the 3D primitives HT_Renderer::draw_3d_....
The output consists of points referred to as device
coordinates that locate the corresponding images on the display surface.
The transformation pipeline takes the object coordinates through several
intermediate coordinate systems before finishing with device coordinates.
Examples of these coordinate transformations are World Coordinates, viewing
coordinates, and normalized projection coordinates. The several intermediate
coordinate systems are determined by the modeling and viewing parameters
that are defined by the application and contained in the rendition. The
normalized projection coordinates (NPCs) range in value from -1 to +1.
The types of coordinate systems and modeling or viewing parameters are
shown in table 1.
|
|
Modeling matrix |
|
|
Viewing matrix |
|
|
Projection matrix |
(XNPC YNPC ZNPC) |
Screen Matrix |
|
|
- |
where
It follows from this basic convention that transformations are composed (or concatenated) by right multiplication of matrices: that is, if A and B are 4 ´ 4 matrices, then the product matrix AB represents the composite transformation. First apply the transformation represented by A and then the transformation represented by B.
Modeling Matrix
In the Heidi 3D graphics model, a scene has a 3D World
Coordinate System (WCS) in which all of the objects,
lights, and camera can be positioned and related to each other; however,
the coordinates that the application uses to specify the geometry of 3D
drawing primitives are, by definition, object coordinates, which may differ
from the WCS. The rendition specifies a modeling
matrix, which represents the first stage of the transformation pipeline
and is applied to all 3D geometry by the 3D drawing actions in order to
transform them to this WCS.
The application can set or query the modeling transformation in a rendition by HT_Rendition::(set_)modelling_matrix. The HT_Matrix class also provides some utility methods for constructing modeling matrices from elementary transformations, such as rotations, translations, and scale transformations. Note, that these utility methods assume that the fourth row of the matrix is always { 0, 0, 0 1} that is, there are no perspective projection terms.
An essential stage in rendering 3D graphics is a projection of the 3D World space to a plane surface, because all conventional display media, such as workstation screens, paper plots, film in camera, and the retina of the human eye are surfaces with only two dimensions of extent. In Heidi, as in most 3D systems, the transformation from World Coordinates to the results of the viewing projection is divided into several stages for convenience in defining the matrices. A first stage maps from World Coordinates to a reference frame, called viewing coordinates, which is aligned with a model camera.
Specifically, the XVYV plane is the projection plane (or image plane). The origin of the viewing system is the center of the field of view, a rectangle aligned with the XV and YV axes. The ZV axis gives the projection direction in the case of parallel projection and contains the projection center in the case of perspective projection. From this alignment, it follows that the matrix that represents the projection mapping can take a particularly simple "almost diagonal" form when expressed in viewing coordinates.
The Heidi application sets the viewing matrix through methods of the class HT_Camera or one of its derived classes. You can query the viewing matrix with the method HT_Rendition::viewing_matrix.
Projection Matrix
The projection matrix maps the space of the viewing coordinates
to a 3D normalized projection coordinate
system. It combines a 3D-to-2D with a 1D-to-1D mapping. The 3D-to-2D mapping
from viewing coordinates (XV, YV, ZV)
to normalized projection coordinates (XNPC, YNPC)
represents a projection of the 3D viewing space to a 2D image plane subspace.
This is followed by normalization of the scale to the size of a field rectangle
defined by the camera object. The 1D-to-1D mapping normalizes viewing ZV
values to ZNPC values, which are normalized to the
distance of the projection center to the image plane.
The projection may be perspective or parallel. In the perspective case, the projection center lies on the negative ZV axis. In the parallel case, the projection direction is parallel to the ZV axis.
The parameters defining the projection matrix are encapsulated in the HT_Camera class, as described in the following section. You can query the projection matrix with the method HT_Rendition::projection_matrix.
This also means that Heidi is in the left-handed coordinate system by default. Your driver needs to switch to the appropriate coordinate system based on the HT_Rendition::handedness method.
The screen matrix represents the last stage of the transformation pipeline, mapping normalized projection coordinates to device coordinates. This consists of a 2D mapping, which maps the field rectangle in the image plane to a rectangle on the display surface combined with a 1D mapping that remaps the ZN to a ZDC coordinate, which is useful in Z buffering.
The field rectangle in the image plane is determined by the HT_Camera object in the rendition, as described in the following section. The image rectangle on the display surface is determined by the viewport specified in the rendition. The application can set and query this viewport with the method HT_Rendition::viewport. If the aspect ratios of the field rectangle and the viewport are the same, then the field is mapped exactly onto the viewport. If the aspect ratios are different, then the mapping is determined by a stretch flat in the camera object. In the stretched case, the field rectangle is mapped exactly to the viewport, and thus the mapping involves an anisotropic scaling. In the nonstretched case, the scaling of the screen mapping is isotropic and has the maximum value that permits the entire field rectangle to be mapped into the viewport. Of course, in this case, not all the viewport is filled by the image of the field rectangle. In every case, the center of the field rectangle is mapped to the center of the viewport.
Camera Classes
The rendition contains an object of class HT_Camera,
which defines several of the parameters discussed previously in connection
with the viewing matrix, projection matrix, and screen matrix. HT_Camera
is a base class that provides a certain amount of flexibility in the way
that the application programmer can construct the viewing
matrix. The HT_Camera class also provides more service to the
programmer to construct the viewing matrix.
The camera object defines the viewing matrix by several intermediate quantities:
To describe how the viewing coordinate system is determined from these data, it is convenient to introduce an intermediate camera system (XC,YC,ZC), whose origin is at the point target and whose basis vectors for the (XC,YC,ZC) coordinates are respectively horizontal_axis, vertical_axis, viewing_axis. The viewing coordinate system (XV,YV,ZV) is obtained from the camera coordinate system by a shear transformation parallel to the plane. The viewing coordinate frame also has its origin at target and has vertical_axis and horizontal_axis as basis vectors. The two shear coefficients are determined from the members of the obliquity object. Namely, HT_Camera_Obliquity.y gives the angle (measured in the metric of the camera system), between the ZC axis and the projection on the XCZC plane of the Z V axis. HT_Camera_Obliquity.x gives the angle between the ZC axis and the projection on the YCZC plane of the Z V axis.
Note: Except for its specification in terms of angles, the transformation from camera to viewing coordinates is not a rotation. For any constant k, it maps the plane ZC=k onto the plane ZV= k.
In the case that the camera coordinate system is orthonormal,
obliquity has the usual meaning: it measures the deviation of the viewing
axis from orthogonal to the view plane.
In addition to the foregoing parameters used to define
the viewing matrix, the camera object defines these other parameters:
Composite Matrices
This section described the transformation pipeline in
terms of a sequence of matrices: modeling matrix, viewing matrix, projection
matrix, and screen matrix. The HT_Rendition class also has the
methods object_to_device,
which returns the concatenation of all theses matrices, and world_to_device,
which returns the concatenation of the last three.
Clipping
The rendition provides the method hard_clip
to query and to set the clipping limits in device coordinates. The actual
clipping rectangle is the intersection of this hard clip rectangle and
viewport used to define the screen transformation, as previously described.
The default hard clip rectangle and the default viewport are both equal
to the window extent in the renderer configuration options. HT_Rendition
has the methods hard_clip, restrict_hard_clip
and reset_hard_clip
by which the application can query and set the hard clip limits.
A contour set represents an area comprised of one or more
closed loops of points, which may overlap, intersect, and/or form holes.
For example, given the following two arrays of data:
int counts[] = {4, 3};
HT_Point points[] = {
HT_Point (0,0,0), HT_Point (0,10,0),
HT_Point (10,10,0), HT_Point (10,0,0),
HT_Point (5,5,0), HT_Point (7,5,0), HT_Point (5,7,0));
We can construct a contour set representing a square with
a triangular hole:
HT_Contour_Set region (2, counts, points);
There is a simplified constructor when the region is a
single polygon:
HT_Contour_Set square (4, points);
Clipping is performed simply by requesting the area intersection
of the clip region and the polygon (both as Contour_Sets),
with the result also returned as a Contour_Set:
result = clip_region.area_intersection (polygonal_object);
The result is a simple polygon if (result.contours() == 1) and it can be handled directly¾points are accessed from result.points() and the count is result.counts[0]. If there is more than one contour, then the result has holes or disjoint pieces. The easiest way to deal with this case is to, in turn, call the polygonize() member function on the result in order to reduce it to separate, simple, polygonal pieces.
Polyline clipping is handled in much the same manner. Until a more appropriate interface can be implemented, the polyline input and result are also packed into the Contour_Set class.
Note: that the polylines described this way do not close automatically as the polygonal forms do.
The clip is then simply:
result = clip_region.area_intersection (polyline_object);
The output can then be drawn using something like this:
int contours = result.contours(); int const * counts = result.counts(); HT_Point const * points = result.points();
while (contours-- > 0) {
renderer.draw_2d_polyline (rendition, *counts, points);