Introduction
I found myself doing a lot of drawings recently of simple polygons, such as
outline shapes. The problem was that these had to be displayed in a variety of
rotations. One such shape was a nice compass arrow, which I use in my accompanying
essay on doing self-registering custom controls. The complete control is
reproduced here. What this essay concentrates on is how I made a nice rotating
arrow.
The
idea here is to create a compass that points to where an object is heading,
using geographic coordinates. Much of the detail of the drawing is discussed in
the accompanying essay.
To create these simple monochrome shapes, I spent a couple hours writing a
trivial editor which allows me to enter a set of x,y coordinates. No mousing,
nothing terribly sophisticated, but all I needed was a list of x,y coordinates,
not a metafile or anything else elaborate. The most complex drawing I have has
about 20 endpoints. I used my little vector
editor program, which is described in an accompanying essay.
Here is the set of points that defines the compass needle. The nominal
coordinate system is a 100-unit coordinate system ranging �50.
-14, 30
-5, 38
0, 46
5, 38
14, 30
4, 34
4, -36
14,-46
0, -40
-14, -46
-4,-36
-4, 34
Polygon.h
class CPolygon
{
public:
CPolygon() { heading = 0.0; scaling = 1.0; }
BOOL Read(const CString & filename);
BOOL Load(UINT resid);
BOOL Load(LPCTSTR resource);
CRect GetInputBB();
CRgn * GetRgn();
CRect Transform(double angle, double scale);
void Draw(CDC & dc, CDoublePoint pt);
bool IsDefined() { return points.GetSize() > 0; }
protected:
CArray<"http://www.pgh.net/~newcomer/selfregister.htm#CDoublePoint">CDoublePoint, CDoublePoint> points;
double heading;
double scaling;
CArray<CPoint, CPoint> transformed;
};
CPolygon::CPolygon
CPolygon::CPolygon()
Constructor. Initializes the polygon. The polygon contains no points.
CPolygon::Read
BOOL CPolygon::Read(const CString & filename)
const CString & filename |
The name of a file to read. |
Reads a set of points from the specified file. If there is an error, returns FALSE
.
If there is an error, the contents of the points
array is not defined.
Minimal syntax checking is done on the input, since it is assumed that the input
comes from a program.
CPolygon::Load
BOOL CPolygon::Load(UINT resid)
BOOL CPolygon::Load(LPCTSTR resourcename)
UINT resid |
The resource ID of a POINTS resource |
LPCTSTR resourcename |
The name of a POINTS resource |
Loads a set of points from a resource segment. The resource segment type must
be POINTS
. The two forms determine if the name is a numeric resource ID
or a string name.
To include a set of points in a resource segment, add the following style of
declaration to the \res\projectname.rc2
file:
resource_name POINTS DISCARDABLE "\\res\\filename"
For example, to add the arrow resource, I did
IDP_ARROW POINTS DISCARDABLE "\\res\\arrow.pln"
Because I did not define a symbol such as
#define IDP_ARROW 1101
I use the LPCTSTR
version of the method.
Copy the file (in my case, arrow.pln
) to the \\res
subdirectory
of your project.
CPolygon::GetInputBB
CRect CPolygon::GetInputBB( )
This returns a CRect
which is the bounding box of the polygon as it
was read in, before any rotational transformation is done to it. The
bounding box is the smallest rectangle that completely encloses all the points
of an object.
CPolygon::GetRgn
CRgn * CPolygon::GetRgn( )
Returns the region of the transformed (rotated and scaled) polygon. This can
be used to invalidate the area occupied by the object.
CPolygon::Transform
CRect Transform(double angle, double scale, BOOL force = FALSE)
double angle |
The angle of rotation, expressed in
geographic coordinates |
double scale |
The scaling factor. This is interpreted as
relative to the points read in for the definition. |
BOOL force |
Forces a recomputation, even if the internal
optimizations determine that the existing set of transformed points
would be valid. |
This transforms the polygon points according to the specified angle
(expressed in geographic coordinates, with 0.0 North, 90.0 East, 180.0 South and
270.0 West) and scaling. The result is an internal structure which contains the
transformed points, suitable for the Draw
method. The CRect
returned is the bounding rectangle of the transformed points, or the rectangle (0,0,0,0)
.
As an optimization, if the angle and scale are the same as the previous Transform
call, nothing is computed and (0,0,0,0)
is returned. If you need to force
the recomputation to obtain the bounding box return, use the third parameter, force
,
and set it TRUE
which bypasses the optimization.
Note that the CRect
is in mapped coordinates, with y values
increasing upwards and x values increasing rightwards; the assumption is that
the points are centered around a 0,0 axis which is at the logical center of the
object. Therefore, IsRectEmpty
will always return TRUE
. If you
need to check for an empty rectangle, you may need to first make a copy and call
the NormalizeRect
method on that copy (if you don't need to preserve the CRect
,
you can call NormalizeRect
on the result).
This method must be called before the Draw
method is executed. Draw
will always draw the polygon based on the current transformation established by Transform
.
If Transform
has not been called since the data was loaded, the
transformed points array is empty.
The transformation is a classic rotation around the center point 0,0,
according to the transformation matrix shown below. Given two points x
,y
,
a scaling in x sx
, and a scaling in y sy
, the new
points x'
, y'
are computed according to the
following matrix multiplication
_ _
| cos � sin � 0 |
[x y 0]| -sin � cos � 0 |=[sx*((cos �)*x-(sin �)*y)
|_ 0 0 0 _| sy*((sin �)*x+(cos �)*y) 0]
(The third column is added to make the matrix multiplication work out, and
then discarded).
In my case, I only allow isotropic (sx == sy
) scaling.
CPolygon::IsDefined
BOOL CPolygon::IsDefined( )
Returns TRUE
if the polygon is defined by a set of points. Returns FALSE
if the array of points is empty. Note that if there is an error in reading, the
array of points is usually empty.
CPolygon::Draw
void CPolygon::Draw(CDC & dc, CDoublePoint pt)
Draws the transformed polygon in the specified DC, at the location specified
by the point. The DC is assumed to be in a mapped mode with y
increasing upwards (rather than downwards as in MM_TEXT
mode). The DC is
unmodified upon return. The Transform
method must be called before
calling Draw
to create the set of transformed points. Failing to call Transform
will mean that the last transformation will be used; if no transformation has
been performed, the transformation set is empty and no drawing will appear.