SlideShare a Scribd company logo
C R A F T I N G I N T R I C A T E 3 D M O D E L S W I T H
A D V A N C E D T E C H N I Q U E S
3D Geometry Unleashed:
Building with Splines and
Surfaces
For any Assignment related queries, Call us at : - +1(315) 557-6473
You can mail us at : - support@programmingassignmenthelper.com
Reach us at : - https://www.programmingassignmenthelper.com/
Computer Graphics
Curves and Surfaces
In this assignment, you will be implementing splines and swept surfaces to model interesting shapes. The primary goal of
this assignment is to introduce you to splines and coordinate systems. Upon successful completion of this assignment,
you will be rewarded with a powerful tool for modeling 3D shapes.
To get you motivated, here is an image that was generated using Maya from one of the swept surfaces that your
program will create.
1 Getting Started
The sample solution a1soln is included in the starter code distribution. You
may need to change the its permissions to be able to run it (type chmod u+x
a1soln at the Athena prompt).
First, Note that this is a different way of reading the contents of a file than the
method used in assignment zero. In that assignment, we used the < operator
to put the contents of the file into standard input. Now, we will tell the program
the filename as a command line argument, and the program will open and
read the file itself (the starter code does this already for you). If you look at
main(...), you’ll see that an ifstream object is created from the filename given
on the command line, and the curves files are read from this stream. In the
function parseFile (in parse.cpp), the curve data is read from the file into data
structures that the code you write will use. For this assignment, parsing the files
is already done for you.
The file core.swp contains four curves. Their control points are shown in
yellow, and the resulting curves are shown in white. You can toggle the display
of local coordinate frames using the c key, and you can toggle the display of
control points using the p key.
Then, load up weird.swp. This displays a generalized cylinder. You can toggle
the display mode of the surface with the s key. By default, it starts with smooth
shading, but you can also turn the surface off to get a better look at the curves,
or render a wireframe surface with normals.
Try viewing some of the other SWP files to get an idea of the sorts of curves and
surfaces that these techniques can generate.
2 Summary of Requirements
This section summarizes the core requirements of this assignment. You will
find more details regarding the implementation of these requirements later in
this document. Note that, for this and most future assignments, you are free to
start your implementation from scratch (in C++). However, the provided
starter code implements a number of these requirements already, so we
encourage you to either start from that or read through the files to see the
implementation.
1. File Input
This is fully implemented in the starter code. Your program must read in a
specific file format (SWP) that allows users to specify curves (Bezier, B-Spline,
and circles) and surfaces (surfaces of revolution and generalized cylinders).
Many SWP files are provided in the swp subdirectory. The specification of the file
format is given in the header file of the provided parser (parse.h).
The starter code distribution also includes a number of SWP files that your code
must be able to read and process (if you implement the assignment from scratch
and file input is broken, you will receive no credit for the assignment). Note that,
for grading, we may be using SWP files that are not included with the starter
code.
2. User Interface
This is fully implemented in the starter code. Your program must provide
functionality to rotate the model using mouse input and select the display mode
of the curves and surfaces. If you choose to implement the assignment from
scratch, play with the sample solution and support equivalent functionality.
Again, if you choose to implement the assignment from scratch and it fails to
support the user interface requirements, you will receive no credit.
3. Curves (45% of grade)
Your program must be able to generate and display piecewise cubic Bezier and B-
spline curves. In addition, it must correctly compute local coordinate frames
along the curve (as described elsewhere in this document) and display them. You
should render the curve in white, and the N, B, and T vectors in red, green, and
blue, respectively. If you use the starter code, you can fill in the evalBezier and
evalBspline functions in curve.cpp and the display will be handled for you.
You will receive 30% of the total number of points if you implement one type
of curve correctly, and 15% for the other type (since you can use one curve type
to implement the other using a simple transformation).
4. Surfaces (50% of grade)
Your program must be able to generate and display surfaces of revolution (of a
curve on the xy-plane around the y-axis) and generalized cylinders. It must
compute surface normals correctly. To demonstrate this in your program,
you should have two modes of display. One mode should display the surface is
drawn in wire- frame mode with the vertex normals drawn pointing outwards
from the surface. The other should display the surface with smooth shading.
The display functions are already implemented for you in the starter code. To
generate surfaces, you can fill in the makeSurfRev and makeGenCyl functions
in surf.cpp. Note that, for generalized cylinders, you are not required to
precisely match the results of the sample solution, as it includes a somewhat
arbitrary choice of the initial coordinate frame.
You will receive 20% of the total number of points for mesh generation of
each type of surface (surface of revolution and generalized cylinders). For
proper computation and display of normals, you will receive 10%.
5. Artifact (5% of grade)
Finally, you are to use your program to create at least two complex geometric
models, which are substan tially different from those distributed with the
assignment. They can model real-life objects or they can be abstract forms. Also
note that the SWP file format supports multiple surfaces, so you may wish to
exploit this functionality.
In addition to the two or more SWP files, you should submit screenshots of
these shapes in either PNG (preferred), JPEG, or GIF format. To take a
screenshot on an Athena system, type add graphics;
/mit/graphics/bin/xv, hit the Grab button, and read the directions.
Alternatively, you may create your artifact by exporting your surfaces and
rendering them using other software. The sample code provided can export your
surfaces to OBJ files by passing in a prefix as the second argument on the
command line. It will produce with one file per named surface with the prefix
prepended (e.g., ./a1 swp/weird.swp foo, will create foo weird.obj).
3Starter Code
To build the starter code, type make. This will compile a barely-working version
of the application into a1. Go ahead and run this on swp/circles.swp. The
starter code, as-is, is fully functional on this example. If you press c, you’ll also
notice that the coordinate frames are computed for you.
If you try and load any of the other SWP files, the program will complain (and
often verbosely; try running the program on swp/core.swp). Your job is to
replace these complaints with real code. In particu lar, all the code you should
need to write can be placed in curve.cpp and surf.cpp (the starter code was
generated from the solution code by deleting parts from these files only).
You can see what functions you need to implement, as well as what they
should do, in those files and the associated header files curve.h and surf.h. We
also encourage you to take a look at the other parts of the code, especially the
functions that draw the curves and surfaces. These functions give a very good
idea of how to access and modify the various data structures that are used.
The starter code uses the vecmath vector library extensively. It provides classes
such as Vector3f, Vector4f and Matrix4f, and all sorts of helpful functions that
operate on vectors and matrices (not only stuff like addition and multiplication,
but dot products, cross products, and so on).
Implementing Curves
Your first task is to implement piecewise cubic Bezier splines and B-splines.
Given an array of control points, you are to generate a set of points that lie on the
spline curve (which can then be used to draw the spline by connecting them with
line segments). For instance, the control points shown in the picture on the left
will result in the piecewise uniform cubic B-spline on the right (in this example,
the first three control points are identical to the last three, which results in a
closed curve).
Computing points on the spline is sufficient if the only goal is to draw it. For
other applications of splines, such as animation and surface modeling, it is
necessary to generate more information. We’ll go over these details first in two
dimensions and then go up to three.
4.1 Two Dimensions
Consider a simple example. Suppose we wanted to animate a car driving
around a curve q(t) on the xy-plane. Evaluating q(t) tells us where the car
should be at any given time, but not which direction the car should be
pointing. A natural way to choose this direction is with the first derivative of
curve: q'(t).
To determine the appropriate transformation at some t, we introduce some
shorthand notation. First, we define the position V as:
V = q(t)
We define the unit tangent vector T as:
T = q'(t)/||q'(t)||
Then we define the normal vector N as a unit vector that is orthogonal to T.
One such vector that will satisfy this property is:
N = T'/||T'||
Then, if we assume that the car is pointed up its positive y-axis, the
appropriate homogeneous transfor mation can computed as:
Unfortunately, there is a problem with this formulation. Specifically, if q
has an infection point, the direction of the normal will flip. In other words,
the car will instantaneously change orientation by 180 de grees.
Furthermore, if the car is traveling along a straight line, N is zero, and the
coordinate system is lost. This is clearly undesirable behavior, so we adopt a
different approach to finding an N that is orthogonal to T.
We introduce a new vector orthogonal to T that we’ll call B. This is known
as the binormal, and we’ll arbitrarily select it to be in pointing in the positive
z-direction. Given B, we can compute the appropriate normal as:
N = B × T
Note that N will be unit and orthogonal to both B and T, because B and
T are orthogonal unit vectors. This may not seem like the most intuitive
method to compute the desired local coordinate frames in two dimensions,
but we have described it because it generalizes quite nicely to three.
4.2 Three Dimensions
To find appropriate coordinate systems along three dimensions, we can’t
just use the old trick of selecting a fixed binormal B. One reason is that the
curve might turn in the direction of the binormal (i.e., it may happen that T
). In this case, the normal N becomes undefined, and we lose our coordinate
system. So here is the solution that we suggest.
We will rely on the fact that we will be stepping along q(t) to generate
discrete points along the curve. In other words, we might step along q(t) at t1
= 0, t2 = 0.1, and so on. At step i, we can compute the following values (just
as in the two-dimensional case):
Vi = q(ti)
T i = q' (ti).normalized()
To select Ni and Bi, we use the following recursive update equations:
Ni = (Bi− 1 × T i).normalized()
Bi = (T i × Ni).normalized()
In these equations, normalized() is a method of Vector3f that returns a
unit-length copy of the instance (i.e., v.normalized() returns v/||v||). We
can initialize the recursion by selecting an arbitrary B0 (well, almost
arbitrary; it can’t be parallel to T1). This can then be plugged into the
update equations to choose N1 and B1. Intuitively, this recursive update
allows the the normal vector at ti to be as close as possible to the normal
vector at ti−1, while still retaining the necessary property that the normal is
orthogonal to the tangent.
tt with the two-dimensional case, we can use these three vectors to define a
local coordinate system at each point along the curve. So, let’s say that we
wanted to animate this airplane:
And let’s say that we wanted to align the z-axis of the airplane with the tangent T,
the x-axis with the normal N, the y-direction with the binormal B, and have the
plane at position V. This can be achieved with the following transformation
matrix:
M = N B T V
0 0 0 1
And that summarizes one way to compute local coordinate systems along a
piecewise spline. Although the recursive update method that we proposed works
fairly well, it has its share of problems. For one, it’s an incremental technique,
and so it doesn’t really give you an analytical solution for any value of t that
you provide. Another problem with this technique is that there’s no guarantee that
closed curves will have matching coordinate systems at the beginning and end.
While this is perfectly acceptable for animating an airplane, it is undesirable
when implementing closed generalized cylinders.
5 Implementing Surfaces
In this assignment, you will be using the curves that you generate to create swept
surfaces. Specifically, the type of surfaces you are to handle are surfaces of
revolution and generalized cylinders.
The following images show an example of a surface of revolution. On the left is
the profile curve on the xy-plane, and on the right is the result of sweeping the
surface about the y-axis.
The images below show an example of a generalized cylinder. On the left is what
we’ll call the sweep curve, and the surface is defined by sweeping a profile curve
along the sweep curve. Here, the profile curve is chosen to be a circle, but your
implementation should also support arbitrary two-dimensional curves.
Surfaces of Revolution
For this assignment, we define a surface of revolution as the product of sweeping
a curve on the xy-plane counterclockwise around the positive y-axis. The
specific direction of the revolution will be important, as you will soon see.
Suppose that you have already evaluated the control points along the profile
curve. Then, you can gen erate the vertices of the surface by simply duplicating
the evaluated curve points at evenly sampled values of rotation. This should be
your first task during the implementation process.
However, vertices alone do not define a surface. As we saw from the previous
assignment, we also need to define normals and faces. This is where things get a
little more challenging. First, let’s discuss the normals.
Well, we already have normal vectors N from the evaluation of the curve. So, we
can just rotate these normal vectors using the same transformation as we used
for the vertices, right? Yes, and no. It turns out that, if we transform a vertex
by a homogeneous transformation matrix M, its normal should be
transformed by the inverse transpose of the top-left 3 × 3 submatrix of M. A
discussion of why this is the case appears in the Red Book. You can take
comfort in the fact that the inverse transpose of a rotation matrix is itself (since
rotation is a rigid transformation).
Another thing that you’ll need to worry about is the orientation of the normals.
For OpenGL to perform proper lighting calculations, the normals need to be
facing out of the surface. So, you can’t just blindly rotate the normals of any
curve and expect things to work.
To appreciate this issue, observe the following Bezier curves. The difference
between them is that the normals (the red lines) are reversed. This is the result
of just reversing the order of the control points. In other words, even though
the curves are the same, the normals depend on the direction of travel.
In this assignment, we will assume that, for two-dimensional curves, the
normals will always point to the left of the direction of travel (the direction of
travel is indicated by the blue lines). In other words, the image on the left is
correct. This is to be consistent with the fact that, when you’re drawing a circle
in a counterclockwise direction, the analytical normals will always point at the
origin. With this convention, you will actually want to reverse the orientation of
the curve normals when you are applying them to the surface of revolution. Note
that, if you implement two-dimensional curves as we described previously, you
will automatically get this behavior.
We must also keep in mind that translating the curve may disrupt our
convention. Consider what hap pens if we just take the curve on the left side,
and translate it so that it is on the other side of the y-axis. This is shown below
(the y-axis is the thick green line).
Here, the normals that were once facing towards the y-axis are now facing away!
Rather than try to handle both cases, we will assume for this assignment that the
profile curve is always on the left of the y-axis (that is, all points on the curve
will have a negative x-coordinate).
So far, we have ignored the faces. Your task will be to generate triangles that
connect each repetition of the profile curve, as shown in the following image. The
basic strategy is to zigzag back and forth between adjacent repetitions to build
the triangles.
In OpenGL, you are required to specify the vertices in a specific order. They
must form a triangle in counterclockwise order (assuming that you are looking
at the front of the triangle). If you generate your triangle faces backwards, your
triangles will have incorrect lighting calculations, or even worse, not appear at
all. So, when you are generating these triangle meshes, keep this in mind. In
particular, this is where our assumption of counterclockwise rotation about the
y-axis comes in handy.
5.2 Generalized Cylinders
By now, you should be ready to implement generalized cylinders. They are very
much like surfaces of rev olution; they are formed by repeating a profile curve
and connecting each copy of the profile curve with triangles. The difference is
that, rather than sweeping the two-dimensional profile curve around the y-axis,
you’ll be sweeping it along a three-dimensional sweep curve.
Just as with surfaces of revolution, each copy of the profile curve is
independently transformed. With surfaces of revolution, we used a rotation.
With generalized cylinders, we will use the coordinate system defined by the N,
B, T, and V vectors of the sweep curve. To put it in the context of a previous
discussion, imagine dragging a copy of the profile curve behind an airplane
that’s flying along the sweep curve, thus leaving a trail of surface.
The process of selecting the correct normals and faces is very similar to how it
is done for surfaces of revolution. We recommend that you write your triangle
meshing code as a separate function so that it may be reused.
Here is a neat example of a generalized cylinder. First, let’s see the profile
curve and the sweep curve. Both of these are shown with local coordinate
systems at points along the curves. Specifically, the blue lines are the Ts, the
red lines are the Ns, and the green lines are the Bs.
The small curve is chosen to be the profile curve, and the large one is chosen
to be the sweep curve. The resulting generalized cylinder is shown below.
#ifdef WIN32
#include <windows.h>
#endif
#include <cmath>
#include <iostream>
#include <cstdlib>
#include <fstream>
#include <vector>
#include <GL/glut.h>
#include <vecmath.h>
#include "parse.h"
#include "curve.h"
#include "surf.h"
#include "extra.h"
#include "camera.h"
using namespace std;
// If you're really interested in what "namespace" means, see
// Stroustup. But basically, the functionality of putting all the
// globals in an "unnamed namespace" is to ensure that everything in
// here is only accessible to code in this file.
namespace
{
// Global variables here.
// This is the camera
Camera camera;
// These are state variables for the UI
bool gMousePressed = false;
int gCurveMode = 1;
int gSurfaceMode = 1;
int gPointMode = 1;
// This detemines how big to draw the normals
const float gLineLen = 0.1f;
// These are arrays for display lists for each drawing mode. The
// convention is that drawmode 0 is "blank", and other drawmodes
// just call the appropriate display lists.
GLuint gCurveLists[3];
GLuint gSurfaceLists[3];
GLuint gAxisList;
GLuint gPointList;
// These STL Vectors store the control points, curves, and
// surfaces that will end up being drawn. In addition, parallel
// STL vectors store the names for the curves and surfaces (as
// given by the files).
vector<vector<Vector3f> > gCtrlPoints;
vector<Curve> gCurves;
vector<string> gCurveNames;
vector<Surface> gSurfaces;
vector<string> gSurfaceNames;
// Declarations of functions whose implementations occur later.
void arcballRotation(int endX, int endY);
void keyboardFunc( unsigned char key, int x, int y);
void specialFunc( int key, int x, int y );
void mouseFunc(int button, int state, int x, int y);
void motionFunc(int x, int y);
void reshapeFunc(int w, int h);
void drawScene(void);
void initRendering();
void loadObjects(int argc, char *argv[]);
void makeDisplayLists();
// This function is called whenever a "Normal" key press is
// received.
void keyboardFunc( unsigned char key, int x, int y )
{
switch ( key )
{
case 27: // Escape key
exit(0);
break;
case ' ':
{
Matrix4f eye = Matrix4f::identity();
camera.SetRotation(eye);
camera.SetCenter(Vector3f(0,0,0));
break;
}
case 'c':
case 'C':
gCurveMode = (gCurveMode+1)%3;
break;
case 's':
case 'S':
gSurfaceMode = (gSurfaceMode+1)%3;
break;
case 'p':
case 'P':
gPointMode = (gPointMode+1)%2;
break;
default:
cout << "Unhandled key press " << key << "." << endl;
}
glutPostRedisplay();
}
// This function is called whenever a "Special" key press is
// received. Right now, it does nothing.
void specialFunc( int key, int x, int y )
{
/*
switch ( key )
{
default:
break;
}
*/
//glutPostRedisplay();
}
// Called when mouse button is pressed.
void mouseFunc(int button, int state, int x, int y)
{
if (state == GLUT_DOWN)
{
gMousePressed = true;
switch (button)
{
case GLUT_LEFT_BUTTON:
camera.MouseClick(Camera::LEFT, x, y);
break;
case GLUT_MIDDLE_BUTTON:
camera.MouseClick(Camera::MIDDLE, x, y);
break;
case GLUT_RIGHT_BUTTON:
camera.MouseClick(Camera::RIGHT, x,y);
default:
break;
}
}
else
{
camera.MouseRelease(x,y);
gMousePressed = false;
}
glutPostRedisplay();
}
// Called when mouse is moved while button pressed.
void motionFunc(int x, int y)
{
camera.MouseDrag(x,y);
glutPostRedisplay();
}
// Called when the window is resized
// w, h - width and height of the window in pixels.
void reshapeFunc(int w, int h)
{
camera.SetDimensions(w,h);
camera.SetViewport(0,0,w,h);
camera.ApplyViewport();
// Set up a perspective view, with square aspect ratio
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
camera.SetPerspective(50);
camera.ApplyPerspective();
// This function is responsible for displaying the object.
void drawScene(void)
{
// Clear the rendering window
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
// Light color (RGBA)
GLfloat Lt0diff[] = {1.0,1.0,1.0,1.0};
GLfloat Lt0pos[] = {3.0,3.0,5.0,1.0};
glLightfv(GL_LIGHT0, GL_DIFFUSE, Lt0diff);
glLightfv(GL_LIGHT0, GL_POSITION, Lt0pos);
camera.ApplyModelview();
// Call the relevant display lists.
if (gSurfaceMode)
glCallList(gSurfaceLists[gSurfaceMode]);
if (gCurveMode)
glCallList(gCurveLists[gCurveMode]);
// This draws the coordinate axes when you're rotating, to
// keep yourself oriented.
if (gMousePressed)
{
glPushMatrix();
glTranslated(camera.GetCenter()[0], camera.GetCenter()[1],
camera.GetCenter()[2]);
glCallList(gAxisList);
glPopMatrix();
}
if (gPointMode)
glCallList(gPointList);
// Dump the image to the screen.
glutSwapBuffers();
}
// Initialize OpenGL's rendering modes
void initRendering()
{
glEnable(GL_DEPTH_TEST); // Depth testing must be turned on
glEnable(GL_LIGHTING); // Enable lighting calculations
glEnable(GL_LIGHT0); // Turn on light #0.
// Setup polygon drawing
glShadeModel(GL_SMOOTH);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
// Antialiasing
// This looks like crap
/*
glEnable(GL_BLEND);
glEnable(GL_POINT_SMOOTH);
glEnable(GL_LINE_SMOOTH);
glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
*/
// Clear to black
glClearColor(0,0,0,1);
// Base material colors (they don't change)
GLfloat diffColor[] = {0.4, 0.4, 0.4, 1};
GLfloat specColor[] = {0.9, 0.9, 0.9, 1};
GLfloat shininess[] = {50.0};
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
diffColor);
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specColor);
glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, shininess);
}
// Load in objects from standard input into the global variables:
// gCtrlPoints, gCurves, gCurveNames, gSurfaces, gSurfaceNames. If
// loading fails, this will exit the program.
void loadObjects(int argc, char *argv[])
{
if (argc < 2)
{
cerr<< "usage: " << argv[0] << " SWPFILE [OBJPREFIX] " << endl;
exit(0);
}
ifstream in(argv[1]);
if (!in)
{
cerr<< argv[1] << " not founda" << endl;
exit(0);
}
cerr << endl << "*** loading and constructing curves and surfaces ***" << endl;
if (!parseFile(in, gCtrlPoints,
gCurves, gCurveNames,
gSurfaces, gSurfaceNames))
{
cerr << "aerror in file formata" << endl;
in.close();
exit(-1);
}
in.close();
// This does OBJ file output
if (argc > 2)
{
cerr << endl << "*** writing obj files ***" << endl;
string prefix(argv[2]);
for (unsigned i=0; i<gSurfaceNames.size(); i++)
{
if (gSurfaceNames[i] != ".")
{
string filename =
prefix + string("_")
+ gSurfaceNames[i]
+ string(".obj");
ofstream out(filename.c_str());
if (!out)
{
cerr << "acould not open file " << filename << ", skipping"<<
endl;
out.close();
continue;
}
else
{
outputObjFile(out, gSurfaces[i]);
cerr << "wrote " << filename << endl;
}
}
}
}
cerr << endl << "*** done ***" << endl;
}
void makeDisplayLists()
{
gCurveLists[1] = glGenLists(1);
gCurveLists[2] = glGenLists(1);
gSurfaceLists[1] = glGenLists(1);
gSurfaceLists[2] = glGenLists(1);
gAxisList = glGenLists(1);
gPointList = glGenLists(1);
// Compile the display lists
glNewList(gCurveLists[1], GL_COMPILE);
{
for (unsigned i=0; i<gCurves.size(); i++)
drawCurve(gCurves[i], 0.0);
}
glEndList();
glNewList(gCurveLists[2], GL_COMPILE);
{
for (unsigned i=0; i<gCurves.size(); i++)
drawCurve(gCurves[i], gLineLen);
}
glEndList();
glNewList(gSurfaceLists[1], GL_COMPILE);
{
for (unsigned i=0; i<gSurfaces.size(); i++)
drawSurface(gSurfaces[i], true);
}
glEndList();
glNewList(gSurfaceLists[2], GL_COMPILE);
{
for (unsigned i=0; i<gSurfaces.size(); i++)
{
drawSurface(gSurfaces[i], false);
drawNormals(gSurfaces[i], gLineLen);
}
}
glEndList();
glNewList(gAxisList, GL_COMPILE);
{
// Save current state of OpenGL
glPushAttrib(GL_ALL_ATTRIB_BITS);
// This is to draw the axes when the mouse button is down
glDisable(GL_LIGHTING);
glLineWidth(3);
glPushMatrix();
glScaled(5.0,5.0,5.0);
glBegin(GL_LINES);
glColor4f(1,0.5,0.5,1); glVertex3d(0,0,0); glVertex3d(1,0,0);
glColor4f(0.5,1,0.5,1); glVertex3d(0,0,0); glVertex3d(0,1,0);
glColor4f(0.5,0.5,1,1); glVertex3d(0,0,0); glVertex3d(0,0,1);
glColor4f(0.5,0.5,0.5,1);
glVertex3d(0,0,0); glVertex3d(-1,0,0);
glVertex3d(0,0,0); glVertex3d(0,-1,0);
glVertex3d(0,0,0); glVertex3d(0,0,-1);
glEnd();
glPopMatrix();
glPopAttrib();
}
glEndList();
glNewList(gPointList, GL_COMPILE);
{
// Save current state of OpenGL
glPushAttrib(GL_ALL_ATTRIB_BITS);
// Setup for point drawing
glDisable(GL_LIGHTING);
glColor4f(1,1,0.0,1);
glPointSize(4);
glLineWidth(1);
for (unsigned i=0; i<gCtrlPoints.size(); i++)
{
glBegin(GL_POINTS);
for (unsigned j=0; j<gCtrlPoints[i].size(); j++)
glVertex(gCtrlPoints[i][j]);
glEnd();
glBegin(GL_LINE_STRIP);
for (unsigned j=0; j<gCtrlPoints[i].size(); j++)
glVertex(gCtrlPoints[i][j]);
glEnd();
}
glPopAttrib();
}
glEndList();
}
// Main routine.
// Set up OpenGL, define the callbacks and start the main loop
int main( int argc, char* argv[] )
{
// Load in from standard input
loadObjects(argc, argv);
glutInit(&argc,argv);
// We're going to animate it, so double buffer
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
// Initial parameters for window position and size
glutInitWindowPosition( 60, 60 );
glutInitWindowSize( 600, 600 );
camera.SetDimensions(600, 600);
camera.SetDistance(10);
camera.SetCenter(Vector3f(0,0,0));
glutCreateWindow("Assignment 1");
// Initialize OpenGL parameters.
initRendering();
// Set up callback functions for key presses
glutKeyboardFunc(keyboardFunc); // Handles "normal" ascii symbols
glutSpecialFunc(specialFunc); // Handles "special" keyboard keys
// Set up callback functions for mouse
glutMouseFunc(mouseFunc);
glutMotionFunc(motionFunc);
// Set up the callback function for resizing windows
glutReshapeFunc( reshapeFunc );
// Call this whenever window needs redrawing
glutDisplayFunc( drawScene );
// Trigger timerFunc every 20 msec
// glutTimerFunc(20, timerFunc, 0);
makeDisplayLists();
#include "curve.h"
#include "extra.h"
#ifdef WIN32
#include <windows.h>
#endif
#include <GL/gl.h>
using namespace std;
namespace
{
// Approximately equal to. We don't want to use == because of
// precision issues with floating point.
inline bool approx( const Vector3f& lhs, const Vector3f& rhs )
{
const float eps = 1e-8f;
return ( lhs - rhs ).absSquared() < eps;
}
}
Curve evalBezier( const vector< Vector3f >& P, unsigned steps )
{
// Check
if( P.size() < 4 || P.size() % 3 != 1 )
{
cerr << "evalBezier must be called with 3n+1 control points." << endl;
exit( 0 );
}
// TODO:
// You should implement this function so that it returns a Curve
// (e.g., a vector< CurvePoint >). The variable "steps" tells you
// the number of points to generate on each piece of the spline.
// At least, that's how the sample solution is implemented and how
// the SWP files are written. But you are free to interpret this
// variable however you want, so long as you can control the
// "resolution" of the discretized spline curve with it.
// Make sure that this function computes all the appropriate
// Vector3fs for each CurvePoint: V,T,N,B.
// [NBT] should be unit and orthogonal.
// Also note that you may assume that all Bezier curves that you
// receive have G1 continuity. Otherwise, the TNB will not be
// be defined at points where this does not hold.
cerr << "t>>> evalBezier has been called with the following input:" << endl;
cerr << "t>>> Control points (type vector< Vector3f >): "<< endl;
for( unsigned i = 0; i < P.size(); ++i )
{
cerr << "t>>> " << P[i] << endl;
}
cerr << "t>>> Steps (type steps): " << steps << endl;
cerr << "t>>> Returning empty curve." << endl;
// Right now this will just return this empty curve.
return Curve();
Curve evalBspline( const vector< Vector3f >& P, unsigned steps )
{
// Check
if( P.size() < 4 )
{
cerr << "evalBspline must be called with 4 or more control points." << endl;
exit( 0 );
// TODO:
// It is suggested that you implement this function by changing
// basis from B-spline to Bezier. That way, you can just call
// your evalBezier function.
cerr << "t>>> evalBSpline has been called with the following input:" << endl;
cerr << "t>>> Control points (type vector< Vector3f >): "<< endl;
for( unsigned i = 0; i < P.size(); ++i )
{
cerr << "t>>> " << P[i] << endl;
}
cerr << "t>>> Steps (type steps): " << steps << endl;
cerr << "t>>> Returning empty curve." << endl;
// Return an empty curve right now.
return Curve();
}
Curve evalCircle( float radius, unsigned steps )
{
// This is a sample function on how to properly initialize a Curve
// (which is a vector< CurvePoint >).
// Preallocate a curve with steps+1 CurvePoints
Curve R( steps+1 );
// Fill it in counterclockwise
for( unsigned i = 0; i <= steps; ++i )
{
// step from 0 to 2pi
float t = 2.0f * M_PI * float( i ) / steps;
// Initialize position
// We're pivoting counterclockwise around the y-axis
R[i].V = radius * Vector3f( cos(t), sin(t), 0 );
// Tangent vector is first derivative
R[i].T = Vector3f( -sin(t), cos(t), 0 );
// Normal vector is second derivative
R[i].N = Vector3f( -cos(t), -sin(t), 0 );
// Finally, binormal is facing up.
R[i].B = Vector3f( 0, 0, 1 );
}
return R;
}
void drawCurve( const Curve& curve, float framesize )
{
// Save current state of OpenGL
glPushAttrib( GL_ALL_ATTRIB_BITS );
// Setup for line drawing
glDisable( GL_LIGHTING );
glColor4f( 1, 1, 1, 1 );
glLineWidth( 1 );
// Draw curve
glBegin( GL_LINE_STRIP );
for( unsigned i = 0; i < curve.size(); ++i )
{
glVertex( curve[ i ].V );
}
glEnd();
glLineWidth( 1 );
// Draw coordinate frames if framesize nonzero
if( framesize != 0.0f )
{
Matrix4f M;
for( unsigned i = 0; i < curve.size(); ++i )
{
M.setCol( 0, Vector4f( curve[i].N, 0 ) );
M.setCol( 1, Vector4f( curve[i].B, 0 ) );
M.setCol( 2, Vector4f( curve[i].T, 0 ) );
M.setCol( 3, Vector4f( curve[i].V, 1 ) );
glPushMatrix();
glMultMatrixf( M );
glScaled( framesize, framesize, framesize );
glBegin( GL_LINES );
glColor3f( 1, 0, 0 ); glVertex3d( 0, 0, 0 ); glVertex3d( 1, 0, 0 );
glColor3f( 0, 1, 0 ); glVertex3d( 0, 0, 0 ); glVertex3d( 0, 1, 0 );
glColor3f( 0, 0, 1 ); glVertex3d( 0, 0, 0 ); glVertex3d( 0, 0, 1 );
glEnd();
glPopMatrix();
}
}
// Pop state
glPopAttrib();
}
#include "camera.h"
#include "extra.h"
#include <GL/glu.h>
#include <iostream>
using namespace std;
Camera::Camera()
{
mStartRot = Matrix4f::identity();
mCurrentRot = Matrix4f::identity();
}
void Camera::SetDimensions(int w, int h)
{
mDimensions[0] = w;
mDimensions[1] = h;
}
void Camera::SetPerspective(float fovy)
{
mPerspective[0] = fovy;
}
void Camera::SetViewport(int x, int y, int w, int h)
{
mViewport[0] = x;
mViewport[1] = y;
mViewport[2] = w;
mViewport[3] = h;
mPerspective[1] = float( w ) / h;
}
void Camera::SetCenter(const Vector3f& center)
{
mStartCenter = mCurrentCenter = center;
}
void Camera::SetRotation(const Matrix4f& rotation)
{
mStartRot = mCurrentRot = rotation;
}
void Camera::SetDistance(const float distance)
{
mStartDistance = mCurrentDistance = distance;
}
void Camera::MouseClick(Button button, int x, int y)
{
mStartClick[0] = x;
mStartClick[1] = y;
mButtonState = button;
switch (button)
{
case LEFT:
mCurrentRot = mStartRot;
break;
case MIDDLE:
mCurrentCenter = mStartCenter;
break;
case RIGHT:
mCurrentDistance = mStartDistance;
break;
default:
break;
}
}
void Camera::MouseDrag(int x, int y)
{
switch (mButtonState)
{
case LEFT:
ArcBallRotation(x,y);
break;
case MIDDLE:
PlaneTranslation(x,y);
break;
case RIGHT:
DistanceZoom(x,y);
break;
default:
break;
}
}
void Camera::MouseRelease(int x, int y)
{
mStartRot = mCurrentRot;
mStartCenter = mCurrentCenter;
mStartDistance = mCurrentDistance;
mButtonState = NONE;
}
void Camera::ArcBallRotation(int x, int y)
{
float sx, sy, sz, ex, ey, ez;
float scale;
float sl, el;
float dotprod;
// find vectors from center of window
sx = mStartClick[0] - ( mDimensions[0] / 2.f );
sy = mStartClick[1] - ( mDimensions[1] / 2.f );
ex = x - ( mDimensions[0] / 2.f );
ey = y - ( mDimensions[1] / 2.f );
// invert y coordinates (raster versus device coordinates)
sy = -sy;
ey = -ey;
// scale by inverse of size of window and magical sqrt2 factor
if (mDimensions[0] > mDimensions[1]) {
scale = (float) mDimensions[1];
} else {
scale = (float) mDimensions[0];
}
scale = 1.f / scale;
sx *= scale;
sy *= scale;
ex *= scale;
ey *= scale;
// project points to unit circle
sl = hypot(sx, sy);
el = hypot(ex, ey);
if (sl > 1.f) {
sx /= sl;
sy /= sl;
sl = 1.0;
}
if (el > 1.f) {
ex /= el;
ey /= el;
el = 1.f;
}
// project up to unit sphere - find Z coordinate
sz = sqrt(1.0f - sl * sl);
ez = sqrt(1.0f - el * el);
// compute angle from dot-product of unit vectors (and double it).
dotprod = sx * ex + sy * ey + sz * ez;
if( dotprod != 1 )
{
Vector3f axis( sy * ez - ey * sz, sz * ex - ez * sx, sx * ey - ex * sy );
axis.normalize();
float angle = 2.0f * acos( dotprod );
mCurrentRot = Matrix4f::rotation( axis, angle );
mCurrentRot = mCurrentRot * mStartRot;
}
else
{
mCurrentRot = mStartRot;
}
}
void Camera::PlaneTranslation(int x, int y)
{
// map window x,y into viewport x,y
// start
int sx = mStartClick[0] - mViewport[0];
int sy = mStartClick[1] - mViewport[1];
// current
int cx = x - mViewport[0];
int cy = y - mViewport[1];
// compute "distance" of image plane (wrt projection matrix)
float d = float(mViewport[3])/2.0f / tan(mPerspective[0]*M_PI / 180.0f / 2.0f);
// compute up plane intersect of clickpoint (wrt fovy)
float su = -sy + mViewport[3]/2.0f;
float cu = -cy + mViewport[3]/2.0f;
// compute right plane intersect of clickpoint (ASSUMED FOVY is 1)
float sr = (sx - mViewport[2]/2.0f);
float cr = (cx - mViewport[2]/2.0f);
Vector2f move(cr-sr, cu-su);
// this maps move
move *= -mCurrentDistance/d;
mCurrentCenter = mStartCenter +
+ move[0] *
Vector3f(mCurrentRot(0,0),mCurrentRot(0,1),mCurrentRot(0,2))
+ move[1] *
Vector3f(mCurrentRot(1,0),mCurrentRot(1,1),mCurrentRot(1,2));
}
void Camera::ApplyViewport() const
{
glViewport(mViewport[0],mViewport[1],mViewport[2],mViewport[3]);
}
void Camera::ApplyPerspective() const
{
gluPerspective(mPerspective[0], mPerspective[1], 1.0, 1000.0);
}
void Camera::ApplyModelview() const
{
// back up distance
gluLookAt(0,0,mCurrentDistance,
0,0,0,
0.0, 1.0, 0.0);
// rotate object
glMultMatrixf(mCurrentRot);
//translate object to center
glTranslatef(-mCurrentCenter[0],-mCurrentCenter[1],-mCurrentCenter[2]);
}
cout << " Strength: S = " ; i >> c.strength ; }while(c.area<=0 ||
c.elasticModulus<=0 || c.length<=0 || c.strength<0);
return i; }
ostream& operator << (ostream &o, Cable &c) { cout << "n Area: A = " ;
o << c.area ;
cout << "n Modulus of elasticity: E = " ;
o << c.elasticModulus ;
cout << "n Length: L = " ;
o << c.length ;
cout << "n Strength: S = " ;
o << c.strength << endl;
return o; }
For information about citing these materials or our Terms of Use, visit:
https://www.programmingassignmenthelper.com/

More Related Content

DOCX
map design by ArcGIS program
PDF
Design the implementation of trajectory path of the robot using parallel loop...
PDF
CAD CAM Lab MANNUAL Dr.BRAU.pdf
PDF
2D & 3D Modelling with Mathematica
PDF
Casa lab manual
DOCX
School of Computing, Science & EngineeringAssessment Briefin.docx
PDF
Ex32018.pdf
PPTX
OpenGL Fixed Function to Shaders - Porting a fixed function application to “m...
 
map design by ArcGIS program
Design the implementation of trajectory path of the robot using parallel loop...
CAD CAM Lab MANNUAL Dr.BRAU.pdf
2D & 3D Modelling with Mathematica
Casa lab manual
School of Computing, Science & EngineeringAssessment Briefin.docx
Ex32018.pdf
OpenGL Fixed Function to Shaders - Porting a fixed function application to “m...
 

Similar to 3D Geometry Unleashed: Building with Splines and Surfaces (20)

PDF
Maths&programming forartists wip
PDF
PPTX
Computation Assignment Help
PPTX
Applet and graphics programming
PPT
Flowcharts and Introduction to computers
PPT
Flowchart presentation that can be useful
PDF
Rhinoceros.pdf
PDF
Using matlab simulink
PDF
Using matlab simulink
PPT
Training Storyboard
PDF
Modeling composites in ansys workbench
PPTX
Autocad 131102050945-phpapp02
DOC
Autocad
PDF
Computer graphics system project report..pdf
PDF
Autocad
DOCX
The basics of c programming
PDF
Summer training matlab
PPTX
Summer training matlab
PDF
Acad civil3 d_points_manual
PDF
AUTOCAD NOTES
Maths&programming forartists wip
Computation Assignment Help
Applet and graphics programming
Flowcharts and Introduction to computers
Flowchart presentation that can be useful
Rhinoceros.pdf
Using matlab simulink
Using matlab simulink
Training Storyboard
Modeling composites in ansys workbench
Autocad 131102050945-phpapp02
Autocad
Computer graphics system project report..pdf
Autocad
The basics of c programming
Summer training matlab
Summer training matlab
Acad civil3 d_points_manual
AUTOCAD NOTES
Ad

Recently uploaded (20)

PDF
Supply Chain Operations Speaking Notes -ICLT Program
PPTX
Pharma ospi slides which help in ospi learning
PDF
Physiotherapy_for_Respiratory_and_Cardiac_Problems WEBBER.pdf
PPTX
human mycosis Human fungal infections are called human mycosis..pptx
PPTX
school management -TNTEU- B.Ed., Semester II Unit 1.pptx
PDF
Classroom Observation Tools for Teachers
PDF
STATICS OF THE RIGID BODIES Hibbelers.pdf
PDF
01-Introduction-to-Information-Management.pdf
PPTX
Pharmacology of Heart Failure /Pharmacotherapy of CHF
PDF
Abdominal Access Techniques with Prof. Dr. R K Mishra
PDF
The Lost Whites of Pakistan by Jahanzaib Mughal.pdf
PPTX
Renaissance Architecture: A Journey from Faith to Humanism
PDF
3rd Neelam Sanjeevareddy Memorial Lecture.pdf
PPTX
PPH.pptx obstetrics and gynecology in nursing
PDF
Basic Mud Logging Guide for educational purpose
PDF
Module 4: Burden of Disease Tutorial Slides S2 2025
PPTX
Introduction_to_Human_Anatomy_and_Physiology_for_B.Pharm.pptx
PDF
Complications of Minimal Access Surgery at WLH
PDF
Computing-Curriculum for Schools in Ghana
PDF
O5-L3 Freight Transport Ops (International) V1.pdf
Supply Chain Operations Speaking Notes -ICLT Program
Pharma ospi slides which help in ospi learning
Physiotherapy_for_Respiratory_and_Cardiac_Problems WEBBER.pdf
human mycosis Human fungal infections are called human mycosis..pptx
school management -TNTEU- B.Ed., Semester II Unit 1.pptx
Classroom Observation Tools for Teachers
STATICS OF THE RIGID BODIES Hibbelers.pdf
01-Introduction-to-Information-Management.pdf
Pharmacology of Heart Failure /Pharmacotherapy of CHF
Abdominal Access Techniques with Prof. Dr. R K Mishra
The Lost Whites of Pakistan by Jahanzaib Mughal.pdf
Renaissance Architecture: A Journey from Faith to Humanism
3rd Neelam Sanjeevareddy Memorial Lecture.pdf
PPH.pptx obstetrics and gynecology in nursing
Basic Mud Logging Guide for educational purpose
Module 4: Burden of Disease Tutorial Slides S2 2025
Introduction_to_Human_Anatomy_and_Physiology_for_B.Pharm.pptx
Complications of Minimal Access Surgery at WLH
Computing-Curriculum for Schools in Ghana
O5-L3 Freight Transport Ops (International) V1.pdf
Ad

3D Geometry Unleashed: Building with Splines and Surfaces

  • 1. C R A F T I N G I N T R I C A T E 3 D M O D E L S W I T H A D V A N C E D T E C H N I Q U E S 3D Geometry Unleashed: Building with Splines and Surfaces For any Assignment related queries, Call us at : - +1(315) 557-6473 You can mail us at : - support@programmingassignmenthelper.com Reach us at : - https://www.programmingassignmenthelper.com/
  • 2. Computer Graphics Curves and Surfaces In this assignment, you will be implementing splines and swept surfaces to model interesting shapes. The primary goal of this assignment is to introduce you to splines and coordinate systems. Upon successful completion of this assignment, you will be rewarded with a powerful tool for modeling 3D shapes. To get you motivated, here is an image that was generated using Maya from one of the swept surfaces that your program will create.
  • 3. 1 Getting Started The sample solution a1soln is included in the starter code distribution. You may need to change the its permissions to be able to run it (type chmod u+x a1soln at the Athena prompt). First, Note that this is a different way of reading the contents of a file than the method used in assignment zero. In that assignment, we used the < operator to put the contents of the file into standard input. Now, we will tell the program the filename as a command line argument, and the program will open and read the file itself (the starter code does this already for you). If you look at main(...), you’ll see that an ifstream object is created from the filename given on the command line, and the curves files are read from this stream. In the function parseFile (in parse.cpp), the curve data is read from the file into data structures that the code you write will use. For this assignment, parsing the files is already done for you. The file core.swp contains four curves. Their control points are shown in yellow, and the resulting curves are shown in white. You can toggle the display of local coordinate frames using the c key, and you can toggle the display of control points using the p key.
  • 4. Then, load up weird.swp. This displays a generalized cylinder. You can toggle the display mode of the surface with the s key. By default, it starts with smooth shading, but you can also turn the surface off to get a better look at the curves, or render a wireframe surface with normals. Try viewing some of the other SWP files to get an idea of the sorts of curves and surfaces that these techniques can generate. 2 Summary of Requirements This section summarizes the core requirements of this assignment. You will find more details regarding the implementation of these requirements later in this document. Note that, for this and most future assignments, you are free to start your implementation from scratch (in C++). However, the provided starter code implements a number of these requirements already, so we encourage you to either start from that or read through the files to see the implementation. 1. File Input This is fully implemented in the starter code. Your program must read in a specific file format (SWP) that allows users to specify curves (Bezier, B-Spline, and circles) and surfaces (surfaces of revolution and generalized cylinders). Many SWP files are provided in the swp subdirectory. The specification of the file format is given in the header file of the provided parser (parse.h).
  • 5. The starter code distribution also includes a number of SWP files that your code must be able to read and process (if you implement the assignment from scratch and file input is broken, you will receive no credit for the assignment). Note that, for grading, we may be using SWP files that are not included with the starter code. 2. User Interface This is fully implemented in the starter code. Your program must provide functionality to rotate the model using mouse input and select the display mode of the curves and surfaces. If you choose to implement the assignment from scratch, play with the sample solution and support equivalent functionality. Again, if you choose to implement the assignment from scratch and it fails to support the user interface requirements, you will receive no credit. 3. Curves (45% of grade) Your program must be able to generate and display piecewise cubic Bezier and B- spline curves. In addition, it must correctly compute local coordinate frames along the curve (as described elsewhere in this document) and display them. You should render the curve in white, and the N, B, and T vectors in red, green, and blue, respectively. If you use the starter code, you can fill in the evalBezier and evalBspline functions in curve.cpp and the display will be handled for you.
  • 6. You will receive 30% of the total number of points if you implement one type of curve correctly, and 15% for the other type (since you can use one curve type to implement the other using a simple transformation). 4. Surfaces (50% of grade) Your program must be able to generate and display surfaces of revolution (of a curve on the xy-plane around the y-axis) and generalized cylinders. It must compute surface normals correctly. To demonstrate this in your program, you should have two modes of display. One mode should display the surface is drawn in wire- frame mode with the vertex normals drawn pointing outwards from the surface. The other should display the surface with smooth shading. The display functions are already implemented for you in the starter code. To generate surfaces, you can fill in the makeSurfRev and makeGenCyl functions in surf.cpp. Note that, for generalized cylinders, you are not required to precisely match the results of the sample solution, as it includes a somewhat arbitrary choice of the initial coordinate frame. You will receive 20% of the total number of points for mesh generation of each type of surface (surface of revolution and generalized cylinders). For proper computation and display of normals, you will receive 10%.
  • 7. 5. Artifact (5% of grade) Finally, you are to use your program to create at least two complex geometric models, which are substan tially different from those distributed with the assignment. They can model real-life objects or they can be abstract forms. Also note that the SWP file format supports multiple surfaces, so you may wish to exploit this functionality. In addition to the two or more SWP files, you should submit screenshots of these shapes in either PNG (preferred), JPEG, or GIF format. To take a screenshot on an Athena system, type add graphics; /mit/graphics/bin/xv, hit the Grab button, and read the directions. Alternatively, you may create your artifact by exporting your surfaces and rendering them using other software. The sample code provided can export your surfaces to OBJ files by passing in a prefix as the second argument on the command line. It will produce with one file per named surface with the prefix prepended (e.g., ./a1 swp/weird.swp foo, will create foo weird.obj). 3Starter Code To build the starter code, type make. This will compile a barely-working version of the application into a1. Go ahead and run this on swp/circles.swp. The starter code, as-is, is fully functional on this example. If you press c, you’ll also notice that the coordinate frames are computed for you.
  • 8. If you try and load any of the other SWP files, the program will complain (and often verbosely; try running the program on swp/core.swp). Your job is to replace these complaints with real code. In particu lar, all the code you should need to write can be placed in curve.cpp and surf.cpp (the starter code was generated from the solution code by deleting parts from these files only). You can see what functions you need to implement, as well as what they should do, in those files and the associated header files curve.h and surf.h. We also encourage you to take a look at the other parts of the code, especially the functions that draw the curves and surfaces. These functions give a very good idea of how to access and modify the various data structures that are used. The starter code uses the vecmath vector library extensively. It provides classes such as Vector3f, Vector4f and Matrix4f, and all sorts of helpful functions that operate on vectors and matrices (not only stuff like addition and multiplication, but dot products, cross products, and so on).
  • 9. Implementing Curves Your first task is to implement piecewise cubic Bezier splines and B-splines. Given an array of control points, you are to generate a set of points that lie on the spline curve (which can then be used to draw the spline by connecting them with line segments). For instance, the control points shown in the picture on the left will result in the piecewise uniform cubic B-spline on the right (in this example, the first three control points are identical to the last three, which results in a closed curve). Computing points on the spline is sufficient if the only goal is to draw it. For other applications of splines, such as animation and surface modeling, it is necessary to generate more information. We’ll go over these details first in two dimensions and then go up to three.
  • 10. 4.1 Two Dimensions Consider a simple example. Suppose we wanted to animate a car driving around a curve q(t) on the xy-plane. Evaluating q(t) tells us where the car should be at any given time, but not which direction the car should be pointing. A natural way to choose this direction is with the first derivative of curve: q'(t). To determine the appropriate transformation at some t, we introduce some shorthand notation. First, we define the position V as: V = q(t) We define the unit tangent vector T as: T = q'(t)/||q'(t)|| Then we define the normal vector N as a unit vector that is orthogonal to T. One such vector that will satisfy this property is: N = T'/||T'|| Then, if we assume that the car is pointed up its positive y-axis, the appropriate homogeneous transfor mation can computed as:
  • 11. Unfortunately, there is a problem with this formulation. Specifically, if q has an infection point, the direction of the normal will flip. In other words, the car will instantaneously change orientation by 180 de grees. Furthermore, if the car is traveling along a straight line, N is zero, and the coordinate system is lost. This is clearly undesirable behavior, so we adopt a different approach to finding an N that is orthogonal to T. We introduce a new vector orthogonal to T that we’ll call B. This is known as the binormal, and we’ll arbitrarily select it to be in pointing in the positive z-direction. Given B, we can compute the appropriate normal as: N = B × T Note that N will be unit and orthogonal to both B and T, because B and T are orthogonal unit vectors. This may not seem like the most intuitive method to compute the desired local coordinate frames in two dimensions, but we have described it because it generalizes quite nicely to three. 4.2 Three Dimensions To find appropriate coordinate systems along three dimensions, we can’t just use the old trick of selecting a fixed binormal B. One reason is that the curve might turn in the direction of the binormal (i.e., it may happen that T
  • 12. ). In this case, the normal N becomes undefined, and we lose our coordinate system. So here is the solution that we suggest. We will rely on the fact that we will be stepping along q(t) to generate discrete points along the curve. In other words, we might step along q(t) at t1 = 0, t2 = 0.1, and so on. At step i, we can compute the following values (just as in the two-dimensional case): Vi = q(ti) T i = q' (ti).normalized() To select Ni and Bi, we use the following recursive update equations: Ni = (Bi− 1 × T i).normalized() Bi = (T i × Ni).normalized() In these equations, normalized() is a method of Vector3f that returns a unit-length copy of the instance (i.e., v.normalized() returns v/||v||). We can initialize the recursion by selecting an arbitrary B0 (well, almost arbitrary; it can’t be parallel to T1). This can then be plugged into the update equations to choose N1 and B1. Intuitively, this recursive update allows the the normal vector at ti to be as close as possible to the normal vector at ti−1, while still retaining the necessary property that the normal is orthogonal to the tangent.
  • 13. tt with the two-dimensional case, we can use these three vectors to define a local coordinate system at each point along the curve. So, let’s say that we wanted to animate this airplane: And let’s say that we wanted to align the z-axis of the airplane with the tangent T, the x-axis with the normal N, the y-direction with the binormal B, and have the plane at position V. This can be achieved with the following transformation matrix: M = N B T V 0 0 0 1
  • 14. And that summarizes one way to compute local coordinate systems along a piecewise spline. Although the recursive update method that we proposed works fairly well, it has its share of problems. For one, it’s an incremental technique, and so it doesn’t really give you an analytical solution for any value of t that you provide. Another problem with this technique is that there’s no guarantee that closed curves will have matching coordinate systems at the beginning and end. While this is perfectly acceptable for animating an airplane, it is undesirable when implementing closed generalized cylinders. 5 Implementing Surfaces In this assignment, you will be using the curves that you generate to create swept surfaces. Specifically, the type of surfaces you are to handle are surfaces of revolution and generalized cylinders. The following images show an example of a surface of revolution. On the left is the profile curve on the xy-plane, and on the right is the result of sweeping the surface about the y-axis.
  • 15. The images below show an example of a generalized cylinder. On the left is what we’ll call the sweep curve, and the surface is defined by sweeping a profile curve along the sweep curve. Here, the profile curve is chosen to be a circle, but your implementation should also support arbitrary two-dimensional curves. Surfaces of Revolution For this assignment, we define a surface of revolution as the product of sweeping a curve on the xy-plane counterclockwise around the positive y-axis. The specific direction of the revolution will be important, as you will soon see. Suppose that you have already evaluated the control points along the profile curve. Then, you can gen erate the vertices of the surface by simply duplicating the evaluated curve points at evenly sampled values of rotation. This should be your first task during the implementation process.
  • 16. However, vertices alone do not define a surface. As we saw from the previous assignment, we also need to define normals and faces. This is where things get a little more challenging. First, let’s discuss the normals. Well, we already have normal vectors N from the evaluation of the curve. So, we can just rotate these normal vectors using the same transformation as we used for the vertices, right? Yes, and no. It turns out that, if we transform a vertex by a homogeneous transformation matrix M, its normal should be transformed by the inverse transpose of the top-left 3 × 3 submatrix of M. A discussion of why this is the case appears in the Red Book. You can take comfort in the fact that the inverse transpose of a rotation matrix is itself (since rotation is a rigid transformation). Another thing that you’ll need to worry about is the orientation of the normals. For OpenGL to perform proper lighting calculations, the normals need to be facing out of the surface. So, you can’t just blindly rotate the normals of any curve and expect things to work. To appreciate this issue, observe the following Bezier curves. The difference between them is that the normals (the red lines) are reversed. This is the result of just reversing the order of the control points. In other words, even though the curves are the same, the normals depend on the direction of travel.
  • 17. In this assignment, we will assume that, for two-dimensional curves, the normals will always point to the left of the direction of travel (the direction of travel is indicated by the blue lines). In other words, the image on the left is correct. This is to be consistent with the fact that, when you’re drawing a circle in a counterclockwise direction, the analytical normals will always point at the origin. With this convention, you will actually want to reverse the orientation of the curve normals when you are applying them to the surface of revolution. Note that, if you implement two-dimensional curves as we described previously, you will automatically get this behavior. We must also keep in mind that translating the curve may disrupt our convention. Consider what hap pens if we just take the curve on the left side, and translate it so that it is on the other side of the y-axis. This is shown below (the y-axis is the thick green line).
  • 18. Here, the normals that were once facing towards the y-axis are now facing away! Rather than try to handle both cases, we will assume for this assignment that the profile curve is always on the left of the y-axis (that is, all points on the curve will have a negative x-coordinate). So far, we have ignored the faces. Your task will be to generate triangles that connect each repetition of the profile curve, as shown in the following image. The basic strategy is to zigzag back and forth between adjacent repetitions to build the triangles. In OpenGL, you are required to specify the vertices in a specific order. They must form a triangle in counterclockwise order (assuming that you are looking at the front of the triangle). If you generate your triangle faces backwards, your triangles will have incorrect lighting calculations, or even worse, not appear at all. So, when you are generating these triangle meshes, keep this in mind. In particular, this is where our assumption of counterclockwise rotation about the y-axis comes in handy.
  • 19. 5.2 Generalized Cylinders By now, you should be ready to implement generalized cylinders. They are very much like surfaces of rev olution; they are formed by repeating a profile curve and connecting each copy of the profile curve with triangles. The difference is that, rather than sweeping the two-dimensional profile curve around the y-axis, you’ll be sweeping it along a three-dimensional sweep curve. Just as with surfaces of revolution, each copy of the profile curve is independently transformed. With surfaces of revolution, we used a rotation. With generalized cylinders, we will use the coordinate system defined by the N, B, T, and V vectors of the sweep curve. To put it in the context of a previous discussion, imagine dragging a copy of the profile curve behind an airplane that’s flying along the sweep curve, thus leaving a trail of surface. The process of selecting the correct normals and faces is very similar to how it is done for surfaces of revolution. We recommend that you write your triangle meshing code as a separate function so that it may be reused. Here is a neat example of a generalized cylinder. First, let’s see the profile curve and the sweep curve. Both of these are shown with local coordinate systems at points along the curves. Specifically, the blue lines are the Ts, the red lines are the Ns, and the green lines are the Bs.
  • 20. The small curve is chosen to be the profile curve, and the large one is chosen to be the sweep curve. The resulting generalized cylinder is shown below.
  • 21. #ifdef WIN32 #include <windows.h> #endif #include <cmath> #include <iostream> #include <cstdlib> #include <fstream> #include <vector> #include <GL/glut.h> #include <vecmath.h> #include "parse.h" #include "curve.h" #include "surf.h" #include "extra.h" #include "camera.h"
  • 22. using namespace std; // If you're really interested in what "namespace" means, see // Stroustup. But basically, the functionality of putting all the // globals in an "unnamed namespace" is to ensure that everything in // here is only accessible to code in this file. namespace { // Global variables here. // This is the camera Camera camera; // These are state variables for the UI bool gMousePressed = false; int gCurveMode = 1; int gSurfaceMode = 1; int gPointMode = 1; // This detemines how big to draw the normals const float gLineLen = 0.1f;
  • 23. // These are arrays for display lists for each drawing mode. The // convention is that drawmode 0 is "blank", and other drawmodes // just call the appropriate display lists. GLuint gCurveLists[3]; GLuint gSurfaceLists[3]; GLuint gAxisList; GLuint gPointList; // These STL Vectors store the control points, curves, and // surfaces that will end up being drawn. In addition, parallel // STL vectors store the names for the curves and surfaces (as // given by the files). vector<vector<Vector3f> > gCtrlPoints; vector<Curve> gCurves; vector<string> gCurveNames; vector<Surface> gSurfaces; vector<string> gSurfaceNames; // Declarations of functions whose implementations occur later. void arcballRotation(int endX, int endY); void keyboardFunc( unsigned char key, int x, int y); void specialFunc( int key, int x, int y );
  • 24. void mouseFunc(int button, int state, int x, int y); void motionFunc(int x, int y); void reshapeFunc(int w, int h); void drawScene(void); void initRendering(); void loadObjects(int argc, char *argv[]); void makeDisplayLists(); // This function is called whenever a "Normal" key press is // received. void keyboardFunc( unsigned char key, int x, int y ) { switch ( key ) { case 27: // Escape key exit(0); break; case ' ': { Matrix4f eye = Matrix4f::identity(); camera.SetRotation(eye); camera.SetCenter(Vector3f(0,0,0)); break;
  • 25. } case 'c': case 'C': gCurveMode = (gCurveMode+1)%3; break; case 's': case 'S': gSurfaceMode = (gSurfaceMode+1)%3; break; case 'p': case 'P': gPointMode = (gPointMode+1)%2; break; default: cout << "Unhandled key press " << key << "." << endl; } glutPostRedisplay(); } // This function is called whenever a "Special" key press is // received. Right now, it does nothing. void specialFunc( int key, int x, int y )
  • 26. { /* switch ( key ) { default: break; } */ //glutPostRedisplay(); } // Called when mouse button is pressed. void mouseFunc(int button, int state, int x, int y) { if (state == GLUT_DOWN) { gMousePressed = true; switch (button) {
  • 27. case GLUT_LEFT_BUTTON: camera.MouseClick(Camera::LEFT, x, y); break; case GLUT_MIDDLE_BUTTON: camera.MouseClick(Camera::MIDDLE, x, y); break; case GLUT_RIGHT_BUTTON: camera.MouseClick(Camera::RIGHT, x,y); default: break; } } else { camera.MouseRelease(x,y); gMousePressed = false; } glutPostRedisplay(); }
  • 28. // Called when mouse is moved while button pressed. void motionFunc(int x, int y) { camera.MouseDrag(x,y); glutPostRedisplay(); } // Called when the window is resized // w, h - width and height of the window in pixels. void reshapeFunc(int w, int h) { camera.SetDimensions(w,h); camera.SetViewport(0,0,w,h); camera.ApplyViewport(); // Set up a perspective view, with square aspect ratio glMatrixMode(GL_PROJECTION); glLoadIdentity(); camera.SetPerspective(50); camera.ApplyPerspective();
  • 29. // This function is responsible for displaying the object. void drawScene(void) { // Clear the rendering window glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); // Light color (RGBA) GLfloat Lt0diff[] = {1.0,1.0,1.0,1.0}; GLfloat Lt0pos[] = {3.0,3.0,5.0,1.0}; glLightfv(GL_LIGHT0, GL_DIFFUSE, Lt0diff); glLightfv(GL_LIGHT0, GL_POSITION, Lt0pos); camera.ApplyModelview(); // Call the relevant display lists. if (gSurfaceMode) glCallList(gSurfaceLists[gSurfaceMode]); if (gCurveMode) glCallList(gCurveLists[gCurveMode]);
  • 30. // This draws the coordinate axes when you're rotating, to // keep yourself oriented. if (gMousePressed) { glPushMatrix(); glTranslated(camera.GetCenter()[0], camera.GetCenter()[1], camera.GetCenter()[2]); glCallList(gAxisList); glPopMatrix(); } if (gPointMode) glCallList(gPointList); // Dump the image to the screen. glutSwapBuffers(); } // Initialize OpenGL's rendering modes void initRendering() {
  • 31. glEnable(GL_DEPTH_TEST); // Depth testing must be turned on glEnable(GL_LIGHTING); // Enable lighting calculations glEnable(GL_LIGHT0); // Turn on light #0. // Setup polygon drawing glShadeModel(GL_SMOOTH); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // Antialiasing // This looks like crap /* glEnable(GL_BLEND); glEnable(GL_POINT_SMOOTH); glEnable(GL_LINE_SMOOTH); glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); */ // Clear to black glClearColor(0,0,0,1);
  • 32. // Base material colors (they don't change) GLfloat diffColor[] = {0.4, 0.4, 0.4, 1}; GLfloat specColor[] = {0.9, 0.9, 0.9, 1}; GLfloat shininess[] = {50.0}; glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, diffColor); glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specColor); glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, shininess); } // Load in objects from standard input into the global variables: // gCtrlPoints, gCurves, gCurveNames, gSurfaces, gSurfaceNames. If // loading fails, this will exit the program. void loadObjects(int argc, char *argv[]) { if (argc < 2) { cerr<< "usage: " << argv[0] << " SWPFILE [OBJPREFIX] " << endl; exit(0); }
  • 33. ifstream in(argv[1]); if (!in) { cerr<< argv[1] << " not founda" << endl; exit(0); } cerr << endl << "*** loading and constructing curves and surfaces ***" << endl; if (!parseFile(in, gCtrlPoints, gCurves, gCurveNames, gSurfaces, gSurfaceNames)) { cerr << "aerror in file formata" << endl; in.close(); exit(-1); } in.close(); // This does OBJ file output if (argc > 2) {
  • 34. cerr << endl << "*** writing obj files ***" << endl; string prefix(argv[2]); for (unsigned i=0; i<gSurfaceNames.size(); i++) { if (gSurfaceNames[i] != ".") { string filename = prefix + string("_") + gSurfaceNames[i] + string(".obj"); ofstream out(filename.c_str()); if (!out) { cerr << "acould not open file " << filename << ", skipping"<< endl; out.close(); continue; } else {
  • 35. outputObjFile(out, gSurfaces[i]); cerr << "wrote " << filename << endl; } } } } cerr << endl << "*** done ***" << endl; } void makeDisplayLists() { gCurveLists[1] = glGenLists(1); gCurveLists[2] = glGenLists(1); gSurfaceLists[1] = glGenLists(1); gSurfaceLists[2] = glGenLists(1); gAxisList = glGenLists(1); gPointList = glGenLists(1);
  • 36. // Compile the display lists glNewList(gCurveLists[1], GL_COMPILE); { for (unsigned i=0; i<gCurves.size(); i++) drawCurve(gCurves[i], 0.0); } glEndList(); glNewList(gCurveLists[2], GL_COMPILE); { for (unsigned i=0; i<gCurves.size(); i++) drawCurve(gCurves[i], gLineLen); } glEndList(); glNewList(gSurfaceLists[1], GL_COMPILE); { for (unsigned i=0; i<gSurfaces.size(); i++) drawSurface(gSurfaces[i], true); } glEndList();
  • 37. glNewList(gSurfaceLists[2], GL_COMPILE); { for (unsigned i=0; i<gSurfaces.size(); i++) { drawSurface(gSurfaces[i], false); drawNormals(gSurfaces[i], gLineLen); } } glEndList(); glNewList(gAxisList, GL_COMPILE); { // Save current state of OpenGL glPushAttrib(GL_ALL_ATTRIB_BITS); // This is to draw the axes when the mouse button is down glDisable(GL_LIGHTING); glLineWidth(3); glPushMatrix(); glScaled(5.0,5.0,5.0); glBegin(GL_LINES);
  • 38. glColor4f(1,0.5,0.5,1); glVertex3d(0,0,0); glVertex3d(1,0,0); glColor4f(0.5,1,0.5,1); glVertex3d(0,0,0); glVertex3d(0,1,0); glColor4f(0.5,0.5,1,1); glVertex3d(0,0,0); glVertex3d(0,0,1); glColor4f(0.5,0.5,0.5,1); glVertex3d(0,0,0); glVertex3d(-1,0,0); glVertex3d(0,0,0); glVertex3d(0,-1,0); glVertex3d(0,0,0); glVertex3d(0,0,-1); glEnd(); glPopMatrix(); glPopAttrib(); } glEndList(); glNewList(gPointList, GL_COMPILE); { // Save current state of OpenGL glPushAttrib(GL_ALL_ATTRIB_BITS); // Setup for point drawing
  • 39. glDisable(GL_LIGHTING); glColor4f(1,1,0.0,1); glPointSize(4); glLineWidth(1); for (unsigned i=0; i<gCtrlPoints.size(); i++) { glBegin(GL_POINTS); for (unsigned j=0; j<gCtrlPoints[i].size(); j++) glVertex(gCtrlPoints[i][j]); glEnd(); glBegin(GL_LINE_STRIP); for (unsigned j=0; j<gCtrlPoints[i].size(); j++) glVertex(gCtrlPoints[i][j]); glEnd(); } glPopAttrib(); } glEndList(); }
  • 40. // Main routine. // Set up OpenGL, define the callbacks and start the main loop int main( int argc, char* argv[] ) { // Load in from standard input loadObjects(argc, argv); glutInit(&argc,argv); // We're going to animate it, so double buffer glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH ); // Initial parameters for window position and size glutInitWindowPosition( 60, 60 ); glutInitWindowSize( 600, 600 ); camera.SetDimensions(600, 600); camera.SetDistance(10); camera.SetCenter(Vector3f(0,0,0)); glutCreateWindow("Assignment 1");
  • 41. // Initialize OpenGL parameters. initRendering(); // Set up callback functions for key presses glutKeyboardFunc(keyboardFunc); // Handles "normal" ascii symbols glutSpecialFunc(specialFunc); // Handles "special" keyboard keys // Set up callback functions for mouse glutMouseFunc(mouseFunc); glutMotionFunc(motionFunc); // Set up the callback function for resizing windows glutReshapeFunc( reshapeFunc ); // Call this whenever window needs redrawing glutDisplayFunc( drawScene ); // Trigger timerFunc every 20 msec // glutTimerFunc(20, timerFunc, 0); makeDisplayLists();
  • 42. #include "curve.h" #include "extra.h" #ifdef WIN32 #include <windows.h> #endif #include <GL/gl.h> using namespace std; namespace { // Approximately equal to. We don't want to use == because of // precision issues with floating point. inline bool approx( const Vector3f& lhs, const Vector3f& rhs ) { const float eps = 1e-8f; return ( lhs - rhs ).absSquared() < eps; } }
  • 43. Curve evalBezier( const vector< Vector3f >& P, unsigned steps ) { // Check if( P.size() < 4 || P.size() % 3 != 1 ) { cerr << "evalBezier must be called with 3n+1 control points." << endl; exit( 0 ); } // TODO: // You should implement this function so that it returns a Curve // (e.g., a vector< CurvePoint >). The variable "steps" tells you // the number of points to generate on each piece of the spline. // At least, that's how the sample solution is implemented and how // the SWP files are written. But you are free to interpret this // variable however you want, so long as you can control the // "resolution" of the discretized spline curve with it. // Make sure that this function computes all the appropriate // Vector3fs for each CurvePoint: V,T,N,B. // [NBT] should be unit and orthogonal.
  • 44. // Also note that you may assume that all Bezier curves that you // receive have G1 continuity. Otherwise, the TNB will not be // be defined at points where this does not hold. cerr << "t>>> evalBezier has been called with the following input:" << endl; cerr << "t>>> Control points (type vector< Vector3f >): "<< endl; for( unsigned i = 0; i < P.size(); ++i ) { cerr << "t>>> " << P[i] << endl; } cerr << "t>>> Steps (type steps): " << steps << endl; cerr << "t>>> Returning empty curve." << endl; // Right now this will just return this empty curve. return Curve(); Curve evalBspline( const vector< Vector3f >& P, unsigned steps ) { // Check if( P.size() < 4 ) { cerr << "evalBspline must be called with 4 or more control points." << endl; exit( 0 );
  • 45. // TODO: // It is suggested that you implement this function by changing // basis from B-spline to Bezier. That way, you can just call // your evalBezier function. cerr << "t>>> evalBSpline has been called with the following input:" << endl; cerr << "t>>> Control points (type vector< Vector3f >): "<< endl; for( unsigned i = 0; i < P.size(); ++i ) { cerr << "t>>> " << P[i] << endl; } cerr << "t>>> Steps (type steps): " << steps << endl; cerr << "t>>> Returning empty curve." << endl; // Return an empty curve right now. return Curve(); }
  • 46. Curve evalCircle( float radius, unsigned steps ) { // This is a sample function on how to properly initialize a Curve // (which is a vector< CurvePoint >). // Preallocate a curve with steps+1 CurvePoints Curve R( steps+1 ); // Fill it in counterclockwise for( unsigned i = 0; i <= steps; ++i ) { // step from 0 to 2pi float t = 2.0f * M_PI * float( i ) / steps; // Initialize position // We're pivoting counterclockwise around the y-axis R[i].V = radius * Vector3f( cos(t), sin(t), 0 ); // Tangent vector is first derivative R[i].T = Vector3f( -sin(t), cos(t), 0 ); // Normal vector is second derivative R[i].N = Vector3f( -cos(t), -sin(t), 0 );
  • 47. // Finally, binormal is facing up. R[i].B = Vector3f( 0, 0, 1 ); } return R; } void drawCurve( const Curve& curve, float framesize ) { // Save current state of OpenGL glPushAttrib( GL_ALL_ATTRIB_BITS ); // Setup for line drawing glDisable( GL_LIGHTING ); glColor4f( 1, 1, 1, 1 ); glLineWidth( 1 ); // Draw curve glBegin( GL_LINE_STRIP ); for( unsigned i = 0; i < curve.size(); ++i ) { glVertex( curve[ i ].V );
  • 48. } glEnd(); glLineWidth( 1 ); // Draw coordinate frames if framesize nonzero if( framesize != 0.0f ) { Matrix4f M; for( unsigned i = 0; i < curve.size(); ++i ) { M.setCol( 0, Vector4f( curve[i].N, 0 ) ); M.setCol( 1, Vector4f( curve[i].B, 0 ) ); M.setCol( 2, Vector4f( curve[i].T, 0 ) ); M.setCol( 3, Vector4f( curve[i].V, 1 ) ); glPushMatrix(); glMultMatrixf( M ); glScaled( framesize, framesize, framesize ); glBegin( GL_LINES );
  • 49. glColor3f( 1, 0, 0 ); glVertex3d( 0, 0, 0 ); glVertex3d( 1, 0, 0 ); glColor3f( 0, 1, 0 ); glVertex3d( 0, 0, 0 ); glVertex3d( 0, 1, 0 ); glColor3f( 0, 0, 1 ); glVertex3d( 0, 0, 0 ); glVertex3d( 0, 0, 1 ); glEnd(); glPopMatrix(); } } // Pop state glPopAttrib(); } #include "camera.h" #include "extra.h" #include <GL/glu.h> #include <iostream> using namespace std; Camera::Camera() {
  • 50. mStartRot = Matrix4f::identity(); mCurrentRot = Matrix4f::identity(); } void Camera::SetDimensions(int w, int h) { mDimensions[0] = w; mDimensions[1] = h; } void Camera::SetPerspective(float fovy) { mPerspective[0] = fovy; } void Camera::SetViewport(int x, int y, int w, int h) { mViewport[0] = x; mViewport[1] = y; mViewport[2] = w; mViewport[3] = h; mPerspective[1] = float( w ) / h; }
  • 51. void Camera::SetCenter(const Vector3f& center) { mStartCenter = mCurrentCenter = center; } void Camera::SetRotation(const Matrix4f& rotation) { mStartRot = mCurrentRot = rotation; } void Camera::SetDistance(const float distance) { mStartDistance = mCurrentDistance = distance; } void Camera::MouseClick(Button button, int x, int y) { mStartClick[0] = x; mStartClick[1] = y;
  • 52. mButtonState = button; switch (button) { case LEFT: mCurrentRot = mStartRot; break; case MIDDLE: mCurrentCenter = mStartCenter; break; case RIGHT: mCurrentDistance = mStartDistance; break; default: break; } } void Camera::MouseDrag(int x, int y) { switch (mButtonState) { case LEFT:
  • 53. ArcBallRotation(x,y); break; case MIDDLE: PlaneTranslation(x,y); break; case RIGHT: DistanceZoom(x,y); break; default: break; } } void Camera::MouseRelease(int x, int y) { mStartRot = mCurrentRot; mStartCenter = mCurrentCenter; mStartDistance = mCurrentDistance; mButtonState = NONE; }
  • 54. void Camera::ArcBallRotation(int x, int y) { float sx, sy, sz, ex, ey, ez; float scale; float sl, el; float dotprod; // find vectors from center of window sx = mStartClick[0] - ( mDimensions[0] / 2.f ); sy = mStartClick[1] - ( mDimensions[1] / 2.f ); ex = x - ( mDimensions[0] / 2.f ); ey = y - ( mDimensions[1] / 2.f ); // invert y coordinates (raster versus device coordinates) sy = -sy; ey = -ey; // scale by inverse of size of window and magical sqrt2 factor if (mDimensions[0] > mDimensions[1]) { scale = (float) mDimensions[1]; } else { scale = (float) mDimensions[0]; }
  • 55. scale = 1.f / scale; sx *= scale; sy *= scale; ex *= scale; ey *= scale; // project points to unit circle sl = hypot(sx, sy); el = hypot(ex, ey); if (sl > 1.f) { sx /= sl; sy /= sl; sl = 1.0; } if (el > 1.f) { ex /= el; ey /= el; el = 1.f; }
  • 56. // project up to unit sphere - find Z coordinate sz = sqrt(1.0f - sl * sl); ez = sqrt(1.0f - el * el); // compute angle from dot-product of unit vectors (and double it). dotprod = sx * ex + sy * ey + sz * ez; if( dotprod != 1 ) { Vector3f axis( sy * ez - ey * sz, sz * ex - ez * sx, sx * ey - ex * sy ); axis.normalize(); float angle = 2.0f * acos( dotprod ); mCurrentRot = Matrix4f::rotation( axis, angle ); mCurrentRot = mCurrentRot * mStartRot; } else { mCurrentRot = mStartRot; }
  • 57. } void Camera::PlaneTranslation(int x, int y) { // map window x,y into viewport x,y // start int sx = mStartClick[0] - mViewport[0]; int sy = mStartClick[1] - mViewport[1]; // current int cx = x - mViewport[0]; int cy = y - mViewport[1]; // compute "distance" of image plane (wrt projection matrix) float d = float(mViewport[3])/2.0f / tan(mPerspective[0]*M_PI / 180.0f / 2.0f); // compute up plane intersect of clickpoint (wrt fovy) float su = -sy + mViewport[3]/2.0f; float cu = -cy + mViewport[3]/2.0f;
  • 58. // compute right plane intersect of clickpoint (ASSUMED FOVY is 1) float sr = (sx - mViewport[2]/2.0f); float cr = (cx - mViewport[2]/2.0f); Vector2f move(cr-sr, cu-su); // this maps move move *= -mCurrentDistance/d; mCurrentCenter = mStartCenter + + move[0] * Vector3f(mCurrentRot(0,0),mCurrentRot(0,1),mCurrentRot(0,2)) + move[1] * Vector3f(mCurrentRot(1,0),mCurrentRot(1,1),mCurrentRot(1,2)); } void Camera::ApplyViewport() const { glViewport(mViewport[0],mViewport[1],mViewport[2],mViewport[3]);
  • 59. } void Camera::ApplyPerspective() const { gluPerspective(mPerspective[0], mPerspective[1], 1.0, 1000.0); } void Camera::ApplyModelview() const { // back up distance gluLookAt(0,0,mCurrentDistance, 0,0,0, 0.0, 1.0, 0.0); // rotate object glMultMatrixf(mCurrentRot); //translate object to center glTranslatef(-mCurrentCenter[0],-mCurrentCenter[1],-mCurrentCenter[2]); }
  • 60. cout << " Strength: S = " ; i >> c.strength ; }while(c.area<=0 || c.elasticModulus<=0 || c.length<=0 || c.strength<0); return i; } ostream& operator << (ostream &o, Cable &c) { cout << "n Area: A = " ; o << c.area ; cout << "n Modulus of elasticity: E = " ; o << c.elasticModulus ; cout << "n Length: L = " ; o << c.length ; cout << "n Strength: S = " ; o << c.strength << endl; return o; } For information about citing these materials or our Terms of Use, visit: https://www.programmingassignmenthelper.com/