Chris Pollett > Old Classes > CS216
( Print View )

Student Corner:
  [Grades Sec1]

  [Submit Sec1]

  [Class Sign Up Sec1]

  [
Lecture Notes]
  [Discussion Board]

Course Info:
  [Texts & Links]
  [Topics/Outcomes]
  [Outcomes Matrix]
  [Grading]
  [HW/Quiz Info]
  [Exam Info]
  [Regrades]
  [Honesty]
  [Additional Policies]
  [Announcements]

HWs and Quizzes:
  [Hw1]  [Hw2]  [Hw3]
  [Hw4]  [Quizzes]  [Project]

Practice Exams:
  [Mid]  [Final]

                           












HW2 Solutions Page

To create a solution set, I selected the code of a student who I thought did well on the assignment. For allowing me to use their homework, the student receives 1 bonus point added after curving. If you see your homework used, but do not want me to use it, just ask, and I will choose someone else's homework. They, rather than you, will receive the bonus point. Below are the results of this process for this homework:

sphere.txt

R : 0 : 4
0.0f,  -3.0f,    3.0f 
2.0f,  -1.0f,    1.0f
2.0f,   1.0f,    1.0f
0.0f,   3.0f,    3.0f

Point4f.h


#include <assert.h>
#include <float.h>

/*
 CLASS DEFINITIONS
*/

/*-----------------------------------------------*/
struct Point4fS
/*
 PURPOSE: Provides a static data initializer form for Point4f.
*/
{
   float x, y, z, w;
};

/*-----------------------------------------------*/
class Point4f
/*
 PURPOSE: Encapsulates the notions of a 3D point (x,y,z,1) or
          homogenous 3D point (x,y,z,w).
*/
{
public:
   Point4f();
   Point4f( float ix, float iy );
   Point4f( float ix, float iy, float iz );
   Point4f( float ix, float iy, float iz, float iw );
   Point4f( const Point4fS & p );

   inline float getX() const;
   inline float getY() const;
   inline float getZ() const;
   inline float getW() const;
   inline const float * getRawFloats() const;

   void setW( float iw );
   void set( float ix, float iy, float iz );
   void set( float ix, float iy, float iz, float iw );
   void set( const Point4f & ipoint );

   void startRunningMin();
   void startRunningMax();
   void runningMin( const Point4f & ipoint );
   void runningMax( const Point4f & ipoint );

   Point4f operator * ( float alpha ) const;
   Point4f operator + ( const Point4f & b ) const;
   Point4f operator - ( const Point4f & b ) const;

   void cross( const Point4f & ivector );
   void combine( const Point4f & a, float alpha, const Point4f & b, float beta );
   void lerp( const Point4f & a, const Point4f & b, float alpha );
   float distToLine( const Point4f & a, const Point4f & b ) const;
   float length() const;
   float square_length() const;

	 inline void project();

private:
   float x, y, z, w;
};

#include "Point4f.inl"

#endif

HW2.cpp

/******************************************************
 * Copyright (c):   2010, All Rights Reserved.
 * Project:         CS 216 Homework #2
 * File:            bezier.cpp
 * Purpose:         Code to draw surface of revolution based on a spline curve
 * Start date:      Feb. 1, 2010
 * Programmer:      SJSU Students
 *
 * Remarks: Adapted from Hearn and Baker (3rd edition), and sample code by Chris Pollett
 *
 *******************************************************/

#ifdef WIN32
#include 
#endif

#ifdef __APPLE__ //for MAC
/*
 I created my project under xcode. I chose new C++ tool as the kind of project.
 Then I added the two frameworks:
 OpenGL.framework and GLUT.framework. (Frameworks are in /Library/Frameworks)
 
 */
#include  
#include 
#include 
#endif

#ifdef linux // for linux
/*My compile line was:
 g++ -I /usr/X11R6/include -L /usr/X11R6/lib -lglut -lGL \
 -lGLU -lX11 -lXmu -lXi -lm name.cpp -o name
 */
#include 
#include 
#endif

#include 
#include 
#include 
#include 
#include 

using namespace std;

#include "Point4f.h"

/*
 CONSTANT DEFINITIONS
 */

static const char * FILE_NAME = "sphere.txt";
//static const char * FILE_NAME = "vase_1.txt";
//static const char * FILE_NAME = "fig_7_23_a.txt";
//static const char * FILE_NAME = "fig_7_23_b.txt";
//static const char * FILE_NAME = "fig_7_24_a.txt";
//static const char * FILE_NAME = "fig_7_24_b.txt";

// Defines Pi
static const GLfloat _M_PI = 3.1415926535897932384626433832795f;

// The tessellation resolution for curves and surfaces.
static const int SURFACE_TESSELLATION = 16;

// The number of coordinates in a homogenous 3D vector.
static const int HOMOG_3D_COORDS = 4;

// The degree of a cubic polynomial.
static const int CUBIC_DEGREE = 3;
// The order of a cubic polynomial.
static const int CUBIC_ORDER = CUBIC_DEGREE + 1;
// The number of vertices in a cubic path, in the u-axis direction.
static const int CUBIC_RATIONAL_PATCH_VERTS_U = CUBIC_ORDER;
// The number of vertices in a cubic path, in the v-axis direction.
static const int CUBIC_RATIONAL_PATCH_VERTS_V = CUBIC_ORDER;
// The number of control points to increment for each step in a piecewise cubic
// Bezier curve, where the curve shares start and end points of neighboring 
// segments.
static const int PROFILE_CUBIC_CONTROL_POINT_STEP = CUBIC_DEGREE;

// Short alias for unsigned int.
typedef unsigned int uint;

// Alias for cubic rational curve control point array.
typedef Point4f CubicRational3DBezierCurveControlPoints[ CUBIC_RATIONAL_PATCH_VERTS_U ];
// Alias for cubic rational patch control point array.
typedef Point4f CubicRational3DBezierPatchControlPoints[ CUBIC_RATIONAL_PATCH_VERTS_V ][ CUBIC_RATIONAL_PATCH_VERTS_U ];

// Alias for vector of Point4f.
typedef std::vector< Point4f > Point4fArray;
// Alias for vector of float.
typedef std::vector< float > FloatArray;


// Rational cubic Bezier hemicircle x^2+y^2 = 1, y > 0
const CubicRational3DBezierCurveControlPoints cubic_xy_py_circ =
{
	Point4f(    1.0f,    0.0f,         0,    1.0f ),
	Point4f(  1.0f/3,  2.0f/3,         0,  1.0f/3 ), 
	Point4f( -1.0f/3,  2.0f/3,         0,  1.0f/3 ), 
	Point4f(   -1.0f,    0.0f,         0,    1.0f ) 
};

// Rational cubic Bezier hemicircle x^2+y^2 = 1, y < 0
const CubicRational3DBezierCurveControlPoints cubic_xy_ny_circ =
{
	Point4f(   -1.0f,    0.0f,         0,    1.0f ), 
	Point4f( -1.0f/3, -2.0f/3,         0,  1.0f/3 ), 
	Point4f(  1.0f/3, -2.0f/3,         0,  1.0f/3 ), 
	Point4f(    1.0f,    0.0f,         0,    1.0f )
};

const GLfloat RED_COLOR[ 3 ] = { 1.0f, 0.0f, 0.0f };
const GLfloat GREEN_COLOR[ 3 ] = { 0.0f, 1.0f, 0.0f };
const GLfloat YELLOW_COLOR[ 3 ] = { 1.0f, 1.0f, 0.0f };

/*
 PROTOTYPES
 */

void drawCubicRationalBezierPatchSOR( const Point4f profile_rcbez[], const GLfloat knot_vector[],
									 uint num_points, float max_angle, GLenum fill_mode, bool draw_control_cage );
void drawCubicRationalCatmullRomPatchSOR( const Point4f profile_points[],
										 uint num_points, float max_angle, GLenum fill_mode, bool draw_control_cage );
void drawCubicRationalBesselOverhauserPatchSOR( const Point4f profile_points[],
											   const GLfloat knot_vector[], uint num_points, float max_angle,
											   GLenum fill_mode, bool draw_control_cage );

#define POINT4F_INL

/*-----------------------------------------------*/
Point4f::Point4f()
/*
 PURPOSE: Default constructor, performing no initialization.
 REMARKS: This constructor does not initialize the data, so use with caution.
 */
{
}

/*-----------------------------------------------*/
Point4f::Point4f( GLfloat ix, GLfloat iy ) : x( ix ), y( iy ), z( 0.0f ), w( 1.0f )
/*
 PURPOSE: Initialize a 2D homogenous point with given coordinates.
 RECEIVES: 2D Coordinates.
 */
{
}

/*-----------------------------------------------*/
Point4f::Point4f( GLfloat ix, GLfloat iy, GLfloat iz ) : x( ix ), y( iy ), z( iz ), w( 1.0f )
/*
 PURPOSE: Initialize a 3D homogenous point with given coordinates.
 RECEIVES: 3D Coordinates.
 */
{
}

/*-----------------------------------------------*/
Point4f::Point4f( GLfloat ix, GLfloat iy, GLfloat iz, GLfloat iw ) : x( ix ), y( iy ), z( iz ), w( iw )
/*
 PURPOSE: Initialize a 3D homogenous point with given coordinates.
 RECEIVES: 4D Coordinates.
 */
{
}

/*-----------------------------------------------*/
Point4f::Point4f( const Point4fS & p ) : x( p.x ), y( p.y ), z( p.z ), w( p.w )
/*
 PURPOSE: Initialize a 3D homogenous point with given coordinates.
 RECEIVES: 4D Coordinates.
 */
{
}


/*-----------------------------------------------*/
inline GLfloat Point4f::getX() const
/*
 PURPOSE: Retrieve the X-coordinate.
 RETURNS: The X-coordinate.
 */
{
	return x;
}

/*-----------------------------------------------*/
inline GLfloat Point4f::getY() const
/*
 PURPOSE: Retrieve the Y-coordinate.
 RETURNS: The Y-coordinate.
 */
{
	return y;
}

/*-----------------------------------------------*/
inline GLfloat Point4f::getZ() const
/*
 PURPOSE: Retrieve the Z-coordinate.
 RETURNS: The Z-coordinate.
 */
{
	return z;
}

/*-----------------------------------------------*/
inline GLfloat Point4f::getW() const
/*
 PURPOSE: Retrieve the W-coordinate.
 RETURNS: The W-coordinate.
 */
{
	return w;
}

/*-----------------------------------------------*/
inline const GLfloat * Point4f::getRawFloats() const
/*
 PURPOSE: Access the coordinates as an array.
 RETURNS: A pointer to a four-element array of coordinates, { X, Y, Z, W }.
 */
{
	assert( &y == &x + 1 && &z == &y + 1 && &w == &z + 1 );
	return &x;
}

/*-----------------------------------------------*/
void Point4f::setW( GLfloat iw )
/*
 PURPOSE: Modifies the W-coordinate.
 RECEIVES: W coordinate.
 */
{
	w = iw;
}


/*-----------------------------------------------*/
void Point4f::set( GLfloat ix, GLfloat iy, GLfloat iz )
/*
 PURPOSE: Initialize a point with given coordinates.
 RECEIVES: Coordinates.
 */
{
	x = ix;
	y = iy;
	z = iz;
	w = 1.0f;
}

/*-----------------------------------------------*/
void Point4f::set( GLfloat ix, GLfloat iy, GLfloat iz, GLfloat iw )
/*
 PURPOSE: Initialize a point with given coordinates.
 RECEIVES: 4D Coordinates.
 */
{
	x = ix;
	y = iy;
	z = iz;
	w = iw;
}

/*-----------------------------------------------*/
void Point4f::set( const Point4f & ipoint )
/*
 PURPOSE: Copy coordinates from another point.
 RECEIVES: A 4D point.
 */
{
	x = ipoint.x;
	y = ipoint.y;
	z = ipoint.z;
	w = ipoint.w;
}

/*-----------------------------------------------*/
void Point4f::startRunningMin()
/*
 PURPOSE: Initialize the point to track a running maximum.
 */
{
	x = FLT_MAX;
	x *= 2.0f;
	y = z = w = x;
}


/*-----------------------------------------------*/
void Point4f::startRunningMax()
/*
 PURPOSE: Initialize the point to track a running maximum.
 */
{
	x = FLT_MAX;
	x *= -2.0f;
	y = z = w = x;
}


/*-----------------------------------------------*/
void Point4f::runningMin( const Point4f & ipoint )
/*
 PURPOSE: Accumulate a point into a running minimum in this point.
 RECEIVES: A 4D point to merge into this point.
 */
{
	if ( ipoint.x < x )
		x = ipoint.x;
	if ( ipoint.y < y )
		y = ipoint.y;
	if ( ipoint.z < z )
		z = ipoint.z;
	if ( ipoint.w < w )
		w = ipoint.w;
}

/*-----------------------------------------------*/
void Point4f::runningMax( const Point4f & ipoint )
/*
 PURPOSE: Accumulate a point into a running maximum in this point.
 RECEIVES: A 4D point to merge into this point.
 */
{
	if ( ipoint.x > x )
		x = ipoint.x;
	if ( ipoint.y > y )
		y = ipoint.y;
	if ( ipoint.z > z )
		z = ipoint.z;
	if ( ipoint.w > w )
		w = ipoint.w;
}


/*-----------------------------------------------*/
inline Point4f Point4f::operator * ( GLfloat alpha ) const
/*
 PURPOSE: Scale a point by a scalar.
 RECEIVES: Scale factor.
 REMARKS: Side effect is scaling the coordinates of this point by the given scale factor.
 */
{
	return Point4f( x * alpha, y * alpha, z * alpha, w * alpha );
}

/*-----------------------------------------------*/
inline Point4f Point4f::operator + ( const Point4f & b ) const
/*
 PURPOSE: Sums two points.
 RECEIVES: The right-hand side of the addition expression.
 RETURNS: Sum of this point and the addend
 */
{
	return Point4f( x + b.x, y + b.y, z + b.z, w + b.w );
}

/*-----------------------------------------------*/
inline Point4f Point4f::operator -( const Point4f & b ) const
/*
 PURPOSE: Subtracts two points.
 RECEIVES: The right-hand side of the subtraction expression.
 RETURNS: Difference of this point and the right-hand side point
 */
{
	return Point4f( x - b.x, y - b.y, z - b.z, w - b.w );
}

/*-----------------------------------------------*/
void Point4f::cross( const Point4f & ivector )
/*
 PURPOSE: Generate the cross product of this vector and a right-hand-side vector.
 RECEIVES: The right-hand-side vector.
 REMARKS: Side effect is modifying this point to contain the cross product.
 */
{
	/*
	 |  i  j  k |
	 |  x  y  z |
	 | ix iy iz |
	 */
	set( y * ivector.z - z * ivector.y, z * ivector.x - x * ivector.z, x * ivector.y - y * ivector.x );
}

/*-----------------------------------------------*/
void Point4f::combine( const Point4f & a, GLfloat alpha, const Point4f & b, GLfloat beta )
/*
 PURPOSE: Generate a linear combination of two points.
 RECEIVES: Two points and a weight for each point.
 REMARKS: Side effect is modifying this point to contain the combined coordinates.
 */
{
	*this = a * alpha + b * beta;
}

/*-----------------------------------------------*/
void Point4f::lerp( const Point4f & a, const Point4f & b, GLfloat alpha )
/*
 PURPOSE: Linearly interpolate between two points.
 RECEIVES: Two points and a weight for the second point.
 REMARKS: Side effect is modifying this point to contain the interpolated position.
 */
{
	Point4f v = b - a;
	Point4f u = *this - a;
	*this = a * ( 1 - alpha ) + b * alpha;
}

/*-----------------------------------------------*/
GLfloat Point4f::distToLine( const Point4f & a, const Point4f & b ) const
/*
 PURPOSE: Calculate distance from a point to a line.
 RECEIVES: Two distinct points on the line.
 RETURNS: The distance of this point to two line formed by the two given points.
 */
{
	Point4f v = b - a;
	Point4f u = *this - a;
	u.cross( v );
	return static_cast( sqrt( u.square_length() / v.square_length() ) );
}

/*-----------------------------------------------*/
GLfloat Point4f::length() const
/*
 PURPOSE: Calculate length of vector from origin to the point.
 RETURNS: The distance of this point to the origin.
 */
{
	return static_cast( sqrt( square_length() ) );
}

/*-----------------------------------------------*/
GLfloat Point4f::square_length() const
/*
 PURPOSE: Calculate squared length of (3D) vector from origin to the point.
 RETURNS: The squared distance of this point to the origin.
 */
{
	return x * x + y * y + z * z;
}

/*-----------------------------------------------*/
void Point4f::project()
/*
 PURPOSE: Project the homogenous 3D point.
 REMARKS: Side effect is to modify the point. Point must not be at infinity.
 */
{
	assert( w != 0.0f ); 
	x /= w;
	y /= w;
	z /= w;
	w = 1.0f;
}


/*
 CLASS DEFINITIONS
 */

/*-----------------------------------------------*/
class SurfaceData
/*
 PURPOSE: This class encapsulates the data describing a surface of
 revolution formed by rotating a curve around the Z-axis.
 */
{
private:
	// Stores either the control points or interpolated points of the curve.
	Point4fArray vertices;
	// Stores the knot vector.
	FloatArray knots;
	// A char defined by the CurveType enumeration indicating the kind of curve.
	char type;
	// The maximum sweep angle of the surface of revolution about the Z-axis.
	float angle;
	
	// The minimum extent point of the axis-aligned bounding box of the vertices.
	Point4f min;
	// The maximum extent point of the axis-aligned bounding box of the vertices.
	Point4f max;
	
	// For parsing input files.
	FILE * stream;
	
public:
	SurfaceData();
	
	int readData( const char * file );
	void draw( GLenum fill_mode, bool draw_control_cage );
	
	void getExtent( Point4f & bboxMin, Point4f & bboxMax ) const;
	
	enum CurveType
	{
		CT_Rational_Bezier  = 'R',
		CT_Catmull_Rom = 'C',
		CT_Bessel_Overhauser = 'B',
	};
	
private:
	int ParseError();
	
	char ProcessChar( const char * token );
	float ProcessFloat( const char * token );
	uint ProcessUInt( const char * token );
};

/*-----------------------------------------------*/
SurfaceData::SurfaceData() : type( CT_Rational_Bezier ), angle( 0.0f ), stream( NULL )
/*
 PURPOSE: Construct a SurfaceData object read for use with readData.
 REMARKS: The curve is "empty" after the constructor. Initialize it with readData.
 */
{
}

/*-----------------------------------------------*/
int SurfaceData::readData( const char * file )
/*
 PURPOSE: Parse a profile curve file.
 RECEIVES: A NULL-terminated filename indicating the curve data file.
 RETURNS: Non-zero indicates success; zero indicates failure and the curve 
 may be in an indeterminate state.
 REMARKS: The function reads curve data into the object from the given file.
 The file format is as given in the homework assignment:
 
 X:angle_in_radians:num_points
 point_1
 point_2
 
 X: one of {R, C, B} with interpretation defined by the CurveType enumeration.
 angle_in_radians: a floating-point number.
 
 num_points: a positive integer indicating the number of points on lines 
 following the header.
 
 point_1, point_2, etc: comma-delimited pairs or triples indicating the XZ shape 
 of the profile curve, but prepended with a knot value for the Bessel-Overhauser 
 spline type.
 */
{
	stream = fopen( file, "r" );
	if ( !stream )
	{
		printf( "Missing file %s\n", file );
		return 0;
	}
	
	uint num_points = 0;
	char text[ 256 ];
	if ( !fgets( text, 256, stream ) )
		return ParseError();
	
	type = ProcessChar( strtok( text, ": \t" ) );
	angle = ProcessFloat( strtok( NULL, ": \t" ) );
	num_points = ProcessUInt( strtok( NULL, ": \t" ) );
	
	min.startRunningMin();
	max.startRunningMax();
	
	Point4f data;
	for ( num_points; num_points && fgets( text, 256, stream ) != NULL; --num_points )
	{
		float a = 0.0f, b = 0.0f, c = 0.0f;
		a = ProcessFloat( strtok( text, ", \t" ) );
		b = ProcessFloat( strtok( NULL, ", \t" ) );
		
		if ( type == CT_Rational_Bezier )
		{
			c = ProcessFloat( strtok( NULL, ", \t" ) );
			data.set( a, 0.0f, b, c );
		}
		else
			if ( type == CT_Catmull_Rom )
			{
				data.set( a, 0.0f, b, 1.0f );
			}
			else
			{
				knots.push_back( a );
				c = ProcessFloat( strtok( NULL, ", \t" ) );
				data.set( b, 0.0f, c, 1.0f );
			}
		
		min.runningMin( data );
		max.runningMax( data );
		vertices.push_back( data );
	}
	
	if ( num_points != 0 )
		return ParseError();
	
	fclose( stream );
	return 1;
}

/*-----------------------------------------------*/
int SurfaceData::ParseError()
/*
 PURPOSE: Cancel parsing and return an error code of zero.
 REMARKS: The curve is in an indeterminate state after a parse error, except
 the input stream is guaranteed to be closed.
 */
{
	fclose( stream );
	stream = NULL;
	
	printf( "Parse error!\n" );
	return 0;
}

/*-----------------------------------------------*/
char SurfaceData::ProcessChar( const char * token )
/*
 PURPOSE: Process a single-character token.
 RECEIVES: An input string token.
 RETURNS: The character value, or NULL in case of a parse error such as an 
 invalid (non-character) token.
 REMARKS: The function issues a parse error if the input is not a string
 containing a single character.
 */
{
	if ( strlen( token ) != 1 )
	{
		ParseError();
		return 0;
	}
	
	return token[ 0 ];
}

/*-----------------------------------------------*/
float SurfaceData::ProcessFloat( const char * token )
/*
 PURPOSE: Process a floating-point number token.
 RECEIVES: An input string token containing a floating-point number.
 RETURNS: The floating-point number value.
 REMARKS: The function does not perform any validation of the token, and
 silently fails if the number is malformed.
 */
{
	return atof( token );
}

/*-----------------------------------------------*/
uint SurfaceData::ProcessUInt( const char * token )
/*
 PURPOSE: Process an unsigned integer number token.
 RECEIVES: An input string token containing a non-negative number.
 RETURNS: The integer number value.
 REMARKS: The function does not perform any validation of the token, and
 silently fails if the number is malformed.
 */
{
	uint temp = 0;
	sscanf( token, "%u", &temp );
	return temp;
}

/*-----------------------------------------------*/
void SurfaceData::draw( GLenum fill_mode, bool draw_control_cage )
/*
 PURPOSE: Displays a surface of revolution given the profile curve defined in 
 the SurfaceData object, over the sweep angle limit given by the angle data member.
 RECEIVES: fill_mode: An OpenGL evaluator fill mode.
 draw_control_cage: true indicates draw the surface and the control polygon 
 mesh; false indicates draw only the surface.
 REMARKS: Side effect is to modify OpenGL state to initialize surface tessellation
 and to send the surface to OpenGL.
 */
{
	switch ( type )
	{
		case CT_Rational_Bezier:
			drawCubicRationalBezierPatchSOR( &vertices[ 0 ], NULL,
											vertices.size(), angle, fill_mode, draw_control_cage );
			break;
			
		case CT_Catmull_Rom:
			drawCubicRationalCatmullRomPatchSOR( &vertices[ 0 ], 
												vertices.size(), angle, fill_mode, draw_control_cage );
			break;
			
		case CT_Bessel_Overhauser:
			drawCubicRationalBesselOverhauserPatchSOR( &vertices[ 0 ],
													  knots.size() ? &knots[ 0 ] : NULL, vertices.size(), angle, fill_mode,
													  draw_control_cage );
			break;
	}
}

/*-----------------------------------------------*/
void SurfaceData::getExtent( Point4f & bboxMin, Point4f & bboxMax ) const
/*
 PURPOSE: Retrieves extent of the profile curve. 
 RECEIVES: bboxMin: The method fills this with the axis-aligned bounding box minimum.
 bboxMax: The method fills this with the axis-aligned bounding box maximum.
 */
{
	bboxMin = min;
	bboxMax = max;
}


/*
 GLOBAL VARIABLES
 */

// This data encapsulates the surface data for use by the GLUT callback functions.
SurfaceData surface;

/*
 FUNCTIONS
 */


/*-----------------------------------------------*/
void deCasteljeauCubicRationalBezier( const Point4f & p0, const Point4f & p1,
									 const Point4f & p2, const Point4f & p3, Point4f & r0, Point4f & r1, Point4f & r2,
									 Point4f & s0, Point4f & s1, Point4f & t0, float u )
/*
 PURPOSE: Construct the de Casteljeau interpolation points.
 RECEIVES: p0, p1, p2, p3: The four Bezier curve control points.
 r0, r1, r2: The method constructs the first stage interpolation points in these points.
 s0, s1: The method constructs the second stage interpolation points in these points.
 t0: The method constructs the final stage point on the curve in this point.
 RETURNS: The function modifies the input ri, si, and t0.
 */
{
	r0.lerp( p0, p1, u );
	r1.lerp( p1, p2, u );
	r2.lerp( p2, p3, u );
	
	s0.lerp( r0, r1, u );
	s1.lerp( r1, r2, u );
	
	t0.lerp( s0, s1, u );
}

/*-----------------------------------------------*/
void subdivideCubicRationalBezierFirstHalf( const Point4f & p0, Point4f & p1,
										   Point4f & p2, Point4f & p3, float u )
/*
 PURPOSE: Subdivide a cubic rational curve at the given parameter and return the
 control points for the first half of the subdivided curve.
 RECEIVES: The four Bezier curve control points.
 RETURNS: The function modifies the last three of the input control points to contain
 the subdivided curve. The initial point remains the same.
 */
{
	Point4f r0, r1, r2, s0, s1, t0;
	deCasteljeauCubicRationalBezier( p0, p1, p2, p3, r0, r1, r2, s0, s1, t0, u );
	
	// take the first half
	p1 = r0;
	p2 = s0;
	p3 = t0;
}

/*-----------------------------------------------*/
float findBezierParameterRS( Point4f p0, Point4f p1,
							Point4f p2, Point4f p3, const Point4f & root, float distance_epsilon )
/*
 PURPOSE: Search a 2D cubic rational Bezier curve for a root.
 RECEIVES: p0, p1, p2, p3: The four Bezier curve control points
 root: the root point
 distance_epsilon: the root accuracy distance tolerance
 RETURNS: The parameter value of the root.
 */
{
	Point4f r0, r1, r2, s0, s1, t0, t0_p;
	float u0 = 0.0f, u1 = 1.0f;
	bool bRootFound = false;
	
	distance_epsilon = distance_epsilon * distance_epsilon;
	
	do
	{
		deCasteljeauCubicRationalBezier( p0, p1, p2, p3, r0, r1, r2, s0, s1, t0, 0.5f );
		
		// project the midpoint to compare with the root point
		t0_p.set( t0 );
		t0_p.project();
		
		if ( !( bRootFound = ( t0_p - root ).square_length() <= distance_epsilon ) )
		{
			// decide which half of the curve to subdivide into
			if ( root.getX() > t0_p.getX() )
			{
				// take the first half
				p1 = r0;
				p2 = s0;
				p3 = t0;
				u1 = ( u0 + u1 ) * 0.5f;
			}
			else
			{
				// take the second half
				p0 = t0;
				p1 = s1;
				p2 = r2;
				u0 = ( u0 + u1 ) * 0.5f;
			}
		}
	}
	while ( !bRootFound );
	
	return ( u0 + u1 ) * 0.5f;
}


/*-----------------------------------------------*/
float findBezierCircleParameter( const Point4f & root, float distance_epsilon )
/*
 PURPOSE: Search a 2D cubic rational Bezier circle curve for a root.
 RECEIVES: root: The root point
 distance_epsilon: The root accuracy distance tolerance
 RETURNS: The parameter value of the root.
 */
{
	// Note the point order here gives a hemi-ciricle curve that sweeps from -X towards +Y
	// then to the +X axis. This is opposite the sweep of positive
	// angles in Cartesian coordinates, but doesn't affect the root finding
	return findBezierParameterRS(
								 cubic_xy_py_circ[ 0 ], cubic_xy_py_circ[ 1 ], cubic_xy_py_circ[ 2 ], cubic_xy_py_circ[ 3 ],
								 root, distance_epsilon );
}

/*-----------------------------------------------*/
void initializeCubicSolidOfRevPatch( const CubicRational3DBezierCurveControlPoints circle_arc,
									const Point4f profile[], CubicRational3DBezierPatchControlPoints patch )
/*
 PURPOSE: Given a rational cubic Bezier profile curve to rotate about the
 Z-axis, and a rational cubic Bezier circle arc section, generate a rational
 cubic Bezier patch for the correspondig part of the surface.
 RECEIVES: circle_arc: an arc of the circle (on the Z = 0 plane) defined by a rational cubic Bezier.
 profile: a cubic Bezier profile curve on the Y = 0 plane.
 patch: the function stores the patch control points into this 2D array.
 */
{
	for ( int y = 0; y < CUBIC_RATIONAL_PATCH_VERTS_V; ++y )
	{
		for ( int v = 0; v < CUBIC_RATIONAL_PATCH_VERTS_U; ++v )
		{
			// scale and translate the rational unit circle curve to form a 
			// rational circle around the axis, interpolating the desired
			// homogeneous profile control point. The X- and Y-axis have the
			// same scale factor, at least for a circular solid of revolution.
			float xy_s = profile[ y ].getX();
			float z_s = profile[ y ].getX();
			float w_s = profile[ y ].getW();
			// Only Z-axis has translation
			float z_t = profile[ y ].getZ();
			
			// Perform a homogenous affine transform on the vertices
			patch[ v ][ y ].set(
								circle_arc[ v ].getX() * xy_s,
								circle_arc[ v ].getY() * xy_s,
								circle_arc[ v ].getZ() * z_s + circle_arc[ v ].getW() * z_t,
								circle_arc[ v ].getW() * w_s );
		}
	}
}


/*-----------------------------------------------*/
void drawCubicBezierControlCage( const CubicRational3DBezierPatchControlPoints patch_verts )
/*
 PURPOSE: Displays the wireframe of the control polygon mesh or cage of the patch.
 RECEIVES: The patch vertices.
 REMARKS: The function sends line data to OpenGL.
 */
{
	glColor3fv( GREEN_COLOR );
	
	glBegin( GL_LINES );
	
	for ( int i = 0; i < CUBIC_RATIONAL_PATCH_VERTS_U; ++i )
		for ( int j = 0; j < CUBIC_RATIONAL_PATCH_VERTS_V - 1; ++j )
		{
			glVertex4fv( patch_verts[j][i].getRawFloats() );
			glVertex4fv( patch_verts[j+1][i].getRawFloats() );
		}
	
	for ( int j = 0; j < CUBIC_RATIONAL_PATCH_VERTS_V; ++j )
		for ( int i = 0; i < CUBIC_RATIONAL_PATCH_VERTS_U - 1; ++i )
		{
			glVertex4fv( patch_verts[j][i].getRawFloats() );
			glVertex4fv( patch_verts[j][i+1].getRawFloats() );
		}
	
	glEnd();
	
	glColor3fv( YELLOW_COLOR );
	glPointSize( 3.0f );
	
	glBegin( GL_POINTS );
	
	for ( int i = 0; i < CUBIC_RATIONAL_PATCH_VERTS_U; ++i )
		for ( int j = 0; j < CUBIC_RATIONAL_PATCH_VERTS_V; ++j )
		{
			glVertex4fv( patch_verts[j][i].getRawFloats() );
		}
	
	glEnd();
	
	glColor3fv( RED_COLOR );
	glPointSize( 1.0f );
}

/*-----------------------------------------------*/
bool initializeCircleArcs( float max_angle, Point4f cross_section_1[],
						  Point4f cross_section_2[] )
/*
 PURPOSE: Initialize up-to two rational cubic Bezier curves to cover the sweep
 of angle from zero to max_angle radians around the Z-axis.
 RECEIVES: max_angle: The curves rotate around the Z-axis from zero radians 
 to this angle in radians, so 2*Pi radians specifies a complete surface.
 cross_section_1: The curve covering zero to up-to Pi radians.
 cross_section_2: The curve covering from Pi radians to 2*Pi radians,
 if max_angle > Pi.
 RETURNS: True indicates the second curve is necessary to complete the entire arc
 of the circle; false indicates only the first curve is required.
 */
{
	bool bIncludeSecondHalf = true;
	memcpy( cross_section_1, cubic_xy_py_circ, sizeof( cubic_xy_py_circ ) );
	memcpy( cross_section_2, cubic_xy_ny_circ, sizeof( cubic_xy_ny_circ ) );
	
	if ( max_angle && max_angle < 2 * _M_PI )
	{
		if ( max_angle > _M_PI )
		{
			max_angle -= _M_PI;
			float max_angle_u = findBezierCircleParameter( Point4f( cos( max_angle ), sin( max_angle ), 0.0f ), 1.0e-5f );
			
			subdivideCubicRationalBezierFirstHalf( cross_section_2[ 0 ], cross_section_2[ 1 ], 
												  cross_section_2[ 2 ], cross_section_2[ 3 ], max_angle_u );
		}
		else
		{
			bIncludeSecondHalf = false;
			
			if ( max_angle < _M_PI )
			{
				float max_angle_u = findBezierCircleParameter( Point4f( cos( max_angle ), sin( max_angle ), 0.0f ), 1.0e-5f );
				subdivideCubicRationalBezierFirstHalf( cross_section_1[ 0 ], cross_section_1[ 1 ], 
													  cross_section_1[ 2 ], cross_section_1[ 3 ], max_angle_u );
			}
		}
	}
	return bIncludeSecondHalf;
}

/*-----------------------------------------------*/
void drawCubicRationalBezierPatchSegment( const Point4f cross_section[],
										 const Point4f profile[], GLfloat u0, GLfloat u1, GLenum fill_mode,
										 bool draw_control_cage )
/*
 PURPOSE: Draw a solid-of-revolution surface using piecewise cubic rational 
 Bezier patches. Optionally displays control point frame.
 RECEIVES: cross_section: A cubic rational Bezier circle arc around the Z-axis.
 profile: The rational cubic Bezier segment rotating around the circle.
 u0: The initial knot value.
 u1: The ending knot value.
 fill_mode: One of GL_LINE or GL_FILL, indicating wireframe or filled
 patch.
 draw_control_cage: true indicates draw the cage, false indicates draw
 only the surface patch.
 REMARKS: Requires GL_MAP2_VERTEX_4 enabled.
 */
{
	CubicRational3DBezierPatchControlPoints patch_verts;
	
	initializeCubicSolidOfRevPatch( cross_section, profile, patch_verts );
	glMapGrid2f( SURFACE_TESSELLATION, u0, u1, SURFACE_TESSELLATION, 0.0f, 1.0f );
	glMap2f( GL_MAP2_VERTEX_4,
			u0, u1, HOMOG_3D_COORDS, CUBIC_ORDER,
			0.0f, 1.0f, HOMOG_3D_COORDS * CUBIC_RATIONAL_PATCH_VERTS_U, CUBIC_ORDER,
			patch_verts[ 0 ][ 0 ].getRawFloats() );
	glEvalMesh2( fill_mode, 0, SURFACE_TESSELLATION, 0, SURFACE_TESSELLATION );
	
	if ( draw_control_cage )
		drawCubicBezierControlCage( patch_verts );
}

/*-----------------------------------------------*/
void drawCubicRationalBezierPatchSOR( const Point4f profile_rcbez[], const GLfloat knot_vector[],
									 uint num_points, float max_angle, GLenum fill_mode, bool draw_control_cage )
/*
 PURPOSE: Draw a solid-of-revolution surface using piecewise cubic rational 
 Bezier patches. Optionally displays control point frame.
 RECEIVES: profile_rcbez: A piecewise cubic rational Bezier curve profile.
 num_points: The number of control points in the curve. Neighboring 
 segments share a common vertex, so there must be ( N * 3 + 1 ) 
 points for N piecewise segments in the profile curve.
 max_angle: The surface rotates around the Z-axis from zero radians 
 to this angle in radians, so 2*Pi radians specifies a complete surface.
 fill_mode: One of GL_LINE or GL_FILL, indicating wireframe or filled
 patch.
 draw_control_cage: true indicates draw the cage, false indicates draw
 only the surface patch.
 REMARKS: Requires GL_MAP2_VERTEX_4 enabled.
 */
{
	CubicRational3DBezierCurveControlPoints cross_section_1;
	CubicRational3DBezierCurveControlPoints cross_section_2;
	
	bool bIncludeSecondHalf = initializeCircleArcs( max_angle, cross_section_1, cross_section_2 );
	
	GLfloat u0 = 0.0f, u1 = 1.0f;
	for ( int knot = 0, profile_point = 0; profile_point < num_points - 1;
		 ++knot, profile_point += PROFILE_CUBIC_CONTROL_POINT_STEP )
	{
		if ( knot_vector )
		{
			u0 = knot_vector[ knot ];
			u1 = knot_vector[ knot + 1 ];
		}
		
		drawCubicRationalBezierPatchSegment( cross_section_1, profile_rcbez + profile_point,
											u0, u1, fill_mode, draw_control_cage );
		if ( bIncludeSecondHalf )
			drawCubicRationalBezierPatchSegment( cross_section_2, profile_rcbez + profile_point,
												u0, u1, fill_mode, draw_control_cage );
	}
}

/*-----------------------------------------------*/
void drawCubicRationalCatmullRomPatchSOR( const Point4f profile_points[],
										 uint num_points, float max_angle, GLenum fill_mode, bool draw_control_cage )
/*
 PURPOSE: Draw a solid-of-revolution surface using piecewise cubic rational 
 Bezier patches. Optionally displays control point frame.
 RECEIVES: profile_points: The function interpolates these points with a 
 Catmull-Rom spline, and uses this spline as the profile curve.
 num_points: The number of interpolated points in the curve.
 max_angle: The surface rotates around the Z-axis from zero radians 
 to this angle in radians, so 2*Pi radians specifies a complete surface.
 fill_mode: One of GL_LINE or GL_FILL, indicating wireframe or filled
 patch.
 draw_control_cage: true indicates draw the cage, false indicates draw
 only the surface patch.
 REMARKS: Requires GL_MAP2_VERTEX_4 enabled.
 */
{
	CubicRational3DBezierCurveControlPoints profile_bez_verts;
	
	for ( int profile_point = 1; profile_point < num_points - 2; ++profile_point )
	{
		// p_i
		profile_bez_verts[ 0 ].set( profile_points[ profile_point ] );
		
		Point4f l;
		l = ( profile_points[ profile_point + 1 ] - profile_points[ profile_point - 1 ] ) * ( 1.0f / 2 );
		
		// p^+_i
		profile_bez_verts[ 1 ].set( profile_points[ profile_point ] + l * ( 1.0f / 3 ) );
		profile_bez_verts[ 1 ].setW( 1.0f );
		
		// p^-_i+1
		l = ( profile_points[ profile_point + 2 ] - profile_points[ profile_point ] ) * ( 1.0f / 2 );
		profile_bez_verts[ 2 ].set( profile_points[ profile_point + 1 ] - l * ( 1.0f / 3 ) );
		profile_bez_verts[ 2 ].setW( 1.0f );
		
		// p_i+1
		profile_bez_verts[ 3 ].set( profile_points[ profile_point + 1 ] );
		
		GLfloat knot_vector[ 2 ] = { profile_point, profile_point + 1 };
		drawCubicRationalBezierPatchSOR( profile_bez_verts, knot_vector, 4, max_angle,
										fill_mode, draw_control_cage );
	}
}

/*-----------------------------------------------*/
static void calculateBesselOverhauserHalfKnotDerivative( const Point4f profile_points[], const GLfloat knot_vector[], 
														int i, Point4f & v_i_p_1o2 )
/*
 PURPOSE: Calculate v_i+1/2 for a Bessel-Overhauser spline.
 RECEIVES: profile_points: The points, p_i, the spline interpolates.
 knot_vector: The knot array, u_i.
 i: The function calculates the velocity at the half-knot point of this interval.
 v_i_p_1o2: The function returns the calculated v_i+1/2 in this point.
 RETURNS: The function modifies v_i_p_1o2, but does not otherwise return a value.
 */
{
	v_i_p_1o2.set( ( profile_points[ i + 1 ] - profile_points[ i ] ) * ( 1.0f / ( knot_vector[ i + 1 ] - knot_vector[ i ] ) ) );
}

/*-----------------------------------------------*/
static void calculateBesselOverhauserDerivative( const Point4f profile_points[], const GLfloat knot_vector[], 
												int i, Point4f & v_i, const Point4f & v_im1o2, const Point4f & v_ip1o2 )
/*
 PURPOSE: Calculate v_i for a Bessel-Overhauser spline.
 RECEIVES: profile_points: The points, p_i, the spline interpolates.
 knot_vector: The knot array, u_i.
 i: The function calculates the approximate velocity at this knot.
 v_i: The function returns the approimated velocity in this point.
 v_im1o2: The function uses the prior half knot velocity, v_i-1/2 in 
 the formula approximating the velocity at the knot.
 v_ip1o2: The function uses the next half knot velocity, v_i+1/2 in 
 the formula approximating the velocity at the knot.
 RETURNS: The function modifies v_i_p_1o2, but does not otherwise return a value.
 */
{
	v_i = v_im1o2 * ( knot_vector[ i + 1 ] - knot_vector[ i ] ) + 
	v_ip1o2 * ( knot_vector[ i ] - knot_vector[ i - 1 ] );
	v_i = v_i * ( 1.0f / ( knot_vector[ i + 1 ] - knot_vector[ i - 1 ] ) );
}

/*-----------------------------------------------*/
void drawCubicRationalBesselOverhauserPatchSOR( const Point4f profile_points[],
											   const GLfloat knot_vector[], uint num_points, float max_angle,
											   GLenum fill_mode, bool draw_control_cage )
/*
 PURPOSE: Draw a solid-of-revolution surface using piecewise cubic rational 
 Bezier patches. Optionally displays control point frame.
 RECEIVES: profile_points: The function interpolates these points with a 
 Catmull-Rom spline, and uses this spline as the profile curve.
 knot_vector: The knot vector for the curve, or NULL to automatically
 use the chord-length parameterization.
 num_points: The number of interpolated points in the curve.
 max_angle: The surface rotates around the Z-axis from zero radians 
 to this angle in radians, so 2*Pi radians specifies a complete surface.
 fill_mode: One of GL_LINE or GL_FILL, indicating wireframe or filled
 patch.
 draw_control_cage: true indicates draw the cage, false indicates draw
 only the surface patch.
 REMARKS: Requires GL_MAP2_VERTEX_4 enabled.
 */
{
	CubicRational3DBezierCurveControlPoints profile_bez_verts;
	if ( !knot_vector )
	{
		GLfloat * chord_length_knots = (GLfloat*)alloca( sizeof( GLfloat ) * ( num_points + 1 ) );
		chord_length_knots[ 0 ] = 0.0f;
		for ( int profile_point = 1; profile_point < num_points; ++profile_point )
			chord_length_knots[ profile_point ] = ( profile_points[ profile_point ] - profile_points[ profile_point - 1 ] ).length() + chord_length_knots[ profile_point - 1 ];
		knot_vector = chord_length_knots;
	}
	
	Point4f v_im1o2, v_ip1o2;
	Point4f v_i, v_ip1;
	calculateBesselOverhauserHalfKnotDerivative( profile_points, knot_vector, 0, v_im1o2 );
	calculateBesselOverhauserHalfKnotDerivative( profile_points, knot_vector, 1, v_ip1o2 );
	calculateBesselOverhauserDerivative( profile_points, knot_vector, 1, v_i, v_im1o2, v_ip1o2 );
	
	for ( int profile_point = 1; profile_point < num_points - 2; ++profile_point )
	{
		// p_i
		profile_bez_verts[ 0 ].set( profile_points[ profile_point ] );
		
		
		// p^+_i
		profile_bez_verts[ 1 ].set( profile_points[ profile_point ] + v_i * ( ( knot_vector[ profile_point ] - knot_vector[ profile_point - 1 ] ) / 3 ) );
		profile_bez_verts[ 1 ].setW( 1.0f );
		
		
		v_im1o2.set( v_ip1o2 );
		calculateBesselOverhauserHalfKnotDerivative( profile_points, knot_vector, profile_point + 1, v_ip1o2 );
		calculateBesselOverhauserDerivative( profile_points, knot_vector, profile_point + 1, v_ip1, v_im1o2, v_ip1o2 );
		v_i.set( v_ip1 );
		
		// p^-_i+1
		profile_bez_verts[ 2 ].set( profile_points[ profile_point + 1 ] - v_ip1 * ( ( knot_vector[ profile_point + 1 ] - knot_vector[ profile_point ] ) / 3 ) );
		profile_bez_verts[ 2 ].setW( 1.0f );
		
		// p_i+1
		profile_bez_verts[ 3 ].set( profile_points[ profile_point + 1 ] );
		
		GLfloat temp_knot[ 2 ] = { 0.0f, 5.0f };
		drawCubicRationalBezierPatchSOR( profile_bez_verts, 
										//knot_vector + profile_point,
										temp_knot,
										4, max_angle,
										fill_mode, draw_control_cage );
	}
}

/*-----------------------------------------------*/
void drawBezierPatchSOR(void)
/*
 PURPOSE: Draw a solid-of-revolution surface using piecewise cubic rational Bezier patches.
 */
{
	glColor3fv( RED_COLOR );
	
	glEnable( GL_MAP2_VERTEX_4 );
	glEnable( GL_AUTO_NORMAL );
	
	surface.draw( GL_LINE, true );
	
	
	glDisable( GL_MAP2_VERTEX_4 );
	glDisable( GL_AUTO_NORMAL );
}


/*
 GLUT AND DRIVER CODE
 */

/*-----------------------------------------------*/
void init(void)
/*
 PURPOSE: Used to initializes our OpenGL window
 */
{
	glClearColor(1.0, 1.0, 1.0, 0.0);
	
	surface.readData( FILE_NAME );
}

/*-----------------------------------------------*/
void drawFn(void)
/*
 PURPOSE: GLUT display callback function for this program.
 Draw an ampersand using piecewise cubic Bezier curves.
 */
{
	glClear(GL_COLOR_BUFFER_BIT);
	
	drawBezierPatchSOR();
	
	glFlush();
}

/*-----------------------------------------------*/
void winReshapeFn(int newWidth, int newHeight)
/*
 PURPOSE: Resizes/redisplays the contents of the current window
 RECEIVES: newWidth, newHeight -- the new dimensions (ignored)
 REMARKS: Selects an orthographic projection surrounding the ampersand.
 */
{   
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	
	Point4f min, max, center;
	surface.getExtent( min, max );
	//center.lerp( min, max, 0.5f );
	center.set( 0.0f, 0.0f, 0.0f );
	max = max - min;
	float majorAxis = max.getX();
	if ( majorAxis < max.getZ() )
		majorAxis = max.getZ();
	majorAxis *= 1.1666667f;
	min = center - Point4f( majorAxis, majorAxis, majorAxis );
	max = center + Point4f( majorAxis, majorAxis, majorAxis );
	
	glOrtho(
			min.getX(), max.getX(),
			min.getZ(), max.getZ(),
			-100.0f, 100.0f );
	
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	
	glRotatef( -60, 1.0f, 0.0f, 0.0f );
	
	glClear(GL_COLOR_BUFFER_BIT);
}

int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
	
	const int WINDOW_INITIAL_POSITION_X = 50;
	const int WINDOW_INITIAL_POSITION_Y = WINDOW_INITIAL_POSITION_X;
	const int WINDOW_INITIAL_SIZE_X = 500;
	const int WINDOW_INITIAL_SIZE_Y = WINDOW_INITIAL_SIZE_X;
	glutInitWindowPosition(WINDOW_INITIAL_POSITION_X, WINDOW_INITIAL_POSITION_Y);
	glutInitWindowSize(WINDOW_INITIAL_SIZE_X, WINDOW_INITIAL_SIZE_Y);
	glutCreateWindow("Recursive Subdivision of Bezier Curve");
	
	init();
	glutDisplayFunc(drawFn);
	glutReshapeFunc(winReshapeFn);
	glutMainLoop();
	return 0;
}