This chapter gives an overview of the Shape Engine with an example and details.
A shape engine can optionally provide functions to
generate the appropriate shape structures. The default structures
are
essentially enumerations. The shape engine writer can derive
more
complex structures to pass other information to the engine.
Example
The following example and notes should make most of this
clear:
First define a simple shape engine.
class Simple_Shape_Engine : public HT_Shape_Engine_Data {
public:
Simple_Shape_Engine(){}
This engine only provides for line shapes.
HT_Shape_Types types (void) const {return HT_Shape_Types::Line;}
Next, provide the base function for dealing with polylines.
This is a simple case to draw a railroad-track style pattern, consisting
of a simple polyline along the original path with cross-bars going out
to the original line width.
void draw_2d_polyline (HT_Renderer_Data const * rd, HT_Rendition const * hr, int count, HT_DCI_Point const *points,All of the optional parameters required by the interface are ignored in this simple example.
HT_RGB32 const *colors = null,
HT_Shape_Color const & mode = HT_Shape_Color::Undefined,
HT_Parameter const *parameters = null,
float const *ws = null,
HT_Vector const *normals = null) const {
HT_Rendition nhr (*hr);
HT_DC_Point start[2];
HT_DC_Point dcpts[2];
The new rendition draws solid, single weight lines. In general,
it just needs to be different to avoid infinite recursion.
nhr.use_plain_form();Next, draw a simple polyline through the points.
rd->draw_2d_polyline (nhr, count, points);Compute the size for cross-bars and their spacing.
float r = (hr->line_weight() - 1.0f) * 0.5f; float l = 10.0f * hr->line_pattern_scale(); if (r < 3) r = 3;Step along each segment of the polyline, drawing crossbars at regular intervals.
float accumulated = 0.0f;
while (--count > 0) {
HT_Vector path;
float factor;
path.x = (float) (points[1].x - points[0].x);
path.y = (float) (points[1].y - points[0].y);
path.z = (float) (points[1].z - points[0].z);
factor = (float) (path.x * path.x + path.y * path.y);
if (factor > 0.0f) {
float length = float (sqrt (factor));
factor = 1.0f / length;
path.x *= factor;
path.y *= factor;
path.z *= factor;
float xdel = path.x * r;
float ydel = path.y * r;
start[0].x = (float)points[0].x;
start[0].y = (float)points[0].y;
start[1] = start[0];
start[0].x -= ydel;
start[0].y += xdel;
start[1].x += ydel;
start[1].y -= xdel;
while (accumulated < length) {
dcpts[0].x = start[0].x + accumulated * path.x;
dcpts[0].y = start[0].y + accumulated * path.y;
dcpts[1].x = start[1].x + accumulated * path.x;
dcpts[1].y = start[1].y + accumulated * path.y;
rd->draw_2d_polyline (nhr, 2, dcpts);
accumulated += l;
}
accumulated -= length;
}
++points;
}
}
};
Then the program can use the engine in this fashion:
HT_Shape_Engine engine = new Simple_Shape_Engine; HT_Line_Shape shape = engine.line_shape(); // no particular shape ... rendition.set_line_weight (7); rendition.set_line_pattern (shape); renderer.draw_dc_polyline(rendition, num_points, points_array); ...In this case, since the engine only supports one shape, railroad, you don't need to specify a value when you ask for the shape ¾ it defaults to 0. If the code supported different types, you would need to specify the desired type when you request the shape or modify it later, before you attach it to the rendition. This code also ignores all of the other parameters. For example, if the program made a call to draw_dc_gouraud_polyline with the shape set on the rendition, the result would not be "gouraud" railroad tracks. The code would need to be modified to make calls to draw_2d_gouraud_polyline with the appropriate colors (interpolated for the crossbars), in order to get that affect.
Drawing
Calls
The interface for the drawing calls is consolidated from
that in the normal part of
Heidi. All of the
calls to the engine are 2D, regardless of whether Heidi was in a 2D or
DC
call, and the engine functions should follow the normal rules for calling
back
to Heidi. That is, the calls are 2D unless it
is certain that the points are within the hard clip and then
DC
may be called.
The points may be specified in either float, HT_DC_Point, or integer, HT_DCI_Point. The default routines for the float version simply convert the data and call the integer version. Default routines are provided for the integer versions as well, they simply disable the shape and call back to the normal Heidi routines.
Instead of a variety of call types for colorized, gouraud, and so forth, the interface allows for optional arguments. The only one which is not obvious to a regular Heidi programmer should be the Shape Color. If colors is passed to the routine, this argument indicates how, or if, it should be interpolated. Uniform indicates a single color to be used and simply overrides the rendition color. Colorized means there should be a color per segment. Gouraud indicates a color per vertex to be interpolated, and so fourth.
See
Also
For more information, see classes HT_Shape_Engine,
HT_Shape, HT_Shape_Color,
HT_Shape_Data,
HT_Shape_Types,
HT_Marker_Shape
HT_Line_Shape
and HT_Fill_Shape.
Font
Engines
For comparison, a Font
Engine can be thought of as a special form of Shape Engine.
The
biggest difference is that Heidi renderers don't bother with an interface
for
drawing text. Since text would always just
redirect to the Font Engine, we let the
programmer
handle it directly. Otherwise, the differences between the engine
types are just the interface and the fact that most users
wouldn't
want to write a font engine. For example, text is normally a bunch of shapes
(character glyphs) that are drawn following a point,
instead
of a bunch of points with an associated shape.