![]() |
The C++ ProjPoint Class |
Instances of class ProjPoint encapsulate the notion of points in a 4D projective space. Every point (x,y,z,w) in this projective space where w!=0 is associated with a unique point (x/w, y/w, z/w) in an associated affine space.
Definition: An (n+1)-dimensional projective space is the space in which the points of an n-dimensional affine space are embedded. We denote the extra coordinate dimension as w and say that the affine points lie in the w = 1 plane of the projective space. |
Definition: All projective space points on the line from the projective space origin through an affine point on the w = 1 plane are said to be projectively equivalent to the affine space point. |
Before using this class and its methods, be sure you understand the definition and use of points in n-dimensional affine spaces as well as vectors in n-dimensional vector spaces.
In the following, assume that instances of ProjPoint called P, Q, R, and S have been declared and initialized.
Any of the symbolic constants X, Y, Z, or W can be used. Note that this operation is read-only. That is, the following is illegal:
Making this a read-only operator may seem aggravating at first. Why not allow assignment? A major argument for the use of vector geometric techniques is that it makes it easier to write coordinate-free, coordinate system-independent code. Ideally one should rarely, if ever, need even to access directly individual coordinates, let alone modify them. Being read-only is something of a deterrent against using coordinate-based techniques. For those cases where it is really necessary to modify one or two components of a point in isolation after it has been constructed, the following is the best approach:
ProjPoint R = ProjPoint( -1.1, 2.3, 3.7, 4.2 ); ... processing ... double x = R[X]; double y = R[Y]; double z = R[Z]; double w = R[W]; ... mess with x, y, z, and/or w ... R = ProjPoint(x,y,z,w);
Since ProjPoint is a C++ class, it cannot be passed directly to OpenGL functions. The indexing operator provides one way to pass projective space points to OpenGL as illustrated in the example below. See the method pCoords below for a preferred method when dealing with arrays of ProjPoint instances.
glBegin(GL_TRIANGLES); glVertex4d(P[X],P[Y],P[Z],P[W]); glVertex4d(Q[X],Q[Y],Q[Z],Q[W]); glVertex4d(R[X],R[Y],R[Z],R[W]); glEnd();
... Dxyz c; // equivalent to "double c[3];" // Refer to header: Basic.h Q.aCoords(c); glVertex3dv(c); ... |
... double c[3]; // equivalent to "Dxyz c;" // Refer to header: Basic.h glVertex3dv(Q.aCoords(c)); ... |
Oftentimes we have arrays of points which we wish to pass into OpenGL. For example, I may have a pair of ProjPoint arrays which contain points sampled along two lines of constant parameter on a freeform surface. The function drawTriangleStrip illustrates how to draw an OpenGL triangle strip defined by these two arrays of sampled points.
void drawTriangleStrip(ProjPoint Row1[], ProjPoint Row2[], int nPoints) { double c[3]; glBegin(GL_TRIANGLE_STRIP); for (int i=0 ; i<nPoints ; i++) { glVertex3dv(Row1[i].aCoords(c)); glVertex3dv(Row2[i].aCoords(c)); } glEnd(); }
(See also the method pCoords below.)
... Fxyz c; // equivalent to "float c[3];" // Refer to header: Basic.h Q.aCoords(c); glVertex3fv(c); ... |
... float c[3]; // equivalent to "Fxyz c;" // Refer to header: Basic.h glVertex3fv(Q.aCoords(c)); ... |
Similarly:
void drawTriangleStrip(ProjPoint Row1[], ProjPoint Row2[], int nPoints) { float c[3]; glBegin(GL_TRIANGLE_STRIP); for (int i=0 ; i<nPoints ; i++) { glVertex3fv(Row1[i].aCoords(c)); glVertex3fv(Row2[i].aCoords(c)); } glEnd(); }
This method is frequently used in OpenGL applications when defining control points for rational Bezier and B-Spline (i.e., NURBS) curves and surfaces. For example, the following implementation of circularArc can be used to compute the control points for a rational Bezier curve representing a 90 degree section of the unit circle centered at the origin of the xy plane.
void circularArc() { // define the rational control points double w = 0.5*sqrt(2.0); ProjPoint Pts[] = { ProjPoint(1.0, 0.0, 0.0, 1.0), ProjPoint( w, w, 0.0, w), ProjPoint(0.0, 1.0, 0.0, 1.0) }; // Define the control points to OpenGL GLdouble cPts[3][4]; for (int k=0 ; k<3 ; k++) Pts[k].pCoords(cPts[k]); glMap1d(GL_MAP1_VERTEX_4, 0.0, 1.0, 4, 3, (const GLdouble*) cPts); int nPoints = 20; GLdouble u = 0.0; GLdouble du = 1.0 / (GLdouble(nPoints-1); glBegin(GL_LINE_STRIP); for (int i=0 ; i<=nPoints ; i++) { glEvalCoord1d(u); u += du; } glEnd(); } |
(See also the method aCoords above.)
void circularArc() { // define the affine representation of the control points double w = 0.5*sqrt(2.0); ProjPoint Pts[] = { ProjPoint(1.0, 0.0, 0.0, 1.0), ProjPoint( w, w, 0.0, w), ProjPoint(0.0, 1.0, 0.0, 1.0) }; // Define the control points to OpenGL GLfloat cPts[3][4]; for (int k=0 ; k<3 ; k++) Pts[k].pCoords(cPts[k]); glMap1f(GL_MAP1_VERTEX_4, 0.0, 1.0, 4, 3, (const GLfloat*) cPts); int nPoints = 20; GLfloat u = 0.0; GLfloat du = 1.0 / (GLfloat(nPoints-1); glBegin(GL_LINE_STRIP); for (int i=0 ; i<=nPoints ; i++) { glEvalCoord1d(u); u += du; } glEnd(); } |
See the description of the class methods ioFormat, inputFormat, and outputFormat below for an example of the use of these public constants.
cin >> myPnt; cout << "myPnt = " << myPnt << '\n';
Assuming the default conditions specified, if the input is:
13.2 -1.2 -0.334 1.2
then the output will be:
myPnt = [13.2, -1.2, -0.334, 1.2]
The methods inputFormat and outputFormat allow you to change these assumptions for the input and output operators, respectively. The ioFormat method is equivalent to invoking both inputFormat and outputFormat with the given parameters.
The two valid values for "ioAspect" are ProjPoint::openDelimiter and ProjPoint::separator. The former allows you to specify which, if any, opening and closing symbols are used. The latter allows you to specify what separator is to appear between the x, y, z, and w coordinates. Consider the following code:
// On input, assume a comma between coordinates: ProjPoint::inputFormat(ProjPoint::separator, ','); // On output, use angle brackets around the coordinates ProjPoint::outputFormat(ProjPoint::openDelimiter, '<'); ProjPoint myPnt; cin >> myPnt; cout << "myPnt = " << myPnt << '\n';
If the input is:
13.2, -1.2, -0.334 1.2
then the output will be:
myPnt = <13.2, -1.2, -0.334, 1.2>
Any character string can be specified for any of these formatting options (subject to a maximum length). For the "open" strings, a matching closing character string will be generated by matching each input character with an "inverse". If, for example, the string "(*\n" is specified as the openDelimiter, then "\n*)" is generated as the closing delimiter.
The input operator does not actually check for an exact match on delimiters and separators. Instead it knows the number of nonblank characters in each, and it simply skips that many nonblank characters on input. While it is not recommended that you exploit this, the implication is that, given the code in the first column of the table below, the input as indicated in the second column, the output will be as shown in the third column.
ProjPoint::ioFormat( ProjPoint::separator,", "); ProjPoint::ioFormat( ProjPoint::openDelimiter, "(** "); ProjPoint P; cin >> P; cout << P; |
x yz 1.1 r -2.1 # 3.7 @ $ % |
(** 1.1, -2.1, 3.7 **) |
As with the open delimiter, more general character strings can be specified such as " ,\n".