COMPSCI 373 S1 C – Assignment 2 – Sample Solution Computer Science 1 of 11 COMPSCI 373 S1 C - Assignment 2 Sample Solution This assignment is worth 8.3333% of your final grade. 1. Modelling with “Blender” (10 Marks) In this question you will design a rounded 3D model of the first two letters of your UPI (i.e. your initials). Please use capital letters. For example, my UPI is “bwue001”and hence I create a model of “B W”. Please make absolutely sure that you model the first two letters of your UPI – modeling different letters will result in zero marks. NOTE: If your name is written with non-Latin characters (e.g. Chinese or Korean characters) then please feel free to model instead your name using your native language. The level of complexity must be at least the same. E.g. for a Chinese name model at least one character (e.g. 陳) and for a Korean name at least two characters (e.g. 은진). (a) [6 Marks] Model the outline of the 2D shape of your initials using Bezier curves as described in the Blender tutorial (http://www.cs.auckland.ac.nz/~jli023/opengl/blender3dtutorial.htm , section “curves”). NOTE: The interface of the latest version of blender looks very different from the tutorial. However, most of the commands seem to be the same. One difference I found is that you need to press “ALT+c” to close a Bezier curve. In order to get a 2D shape formed by two nested curves go to the “Bezier curve” tab on the right and press “Shapes -> 2D”. TIP: make regular backups of your model. At the end your model should look similar to the picture below. Save your model as a file Ass2Q1Outline.blend. COMPSCI 373 S1 C – Assignment 2 – Sample Solution 2 of 11 (b) [4 Marks] Extrude the 2D shape into a 3D model and create smoothly rounded edges using the beveling tool. Save your model as a file Ass1Q1Final.blend, position it appropriately with respect to the camera and create a screenshot and save it as Ass2Q1Final.jpg. The images below give an example. Solution: The modelling process is the same as for the example “Curves” in the Blender tutorial: http://www.cs.auckland.ac.nz/~jli023/opengl/blender3dtutorial.htm . In my case I modelled the outline of the characters without any visual aid, but you can also upload a background image with the characters and trace their outlines using Bezier curves. Since the outlines are closed curved you might want to use a Bezier circle and then insert new points and add discontinuities (sharp corners) until you get the desired shape. 2. 3D Modelling and Texture Mapping with OpenGL (6 Marks) In this question you will create a 3D scene containing a plane with grass, 12 trees, a large mirror, two bouncing balls, a Greek monument, and 20 flowers. Most of the code is already completed and you have to implement several methods related to our lecture. Download the file Ass2Modelling.zip which contains a .NET solution including all necessary source files. Unzip the files and compile and run them. You should see a grass covered plane, 2 bouncing balls, and a reflecting mirror. NOTE: In order to make debugging easier and improve the understanding of texture maps, all texture images are stored in Portable Pixel Map format (ppm). This format is very inefficient to load and your program might take a long time to load when run in DEBUG mode. You can avoid this by compiling your program in RELEASE mode. (a) [6 Marks] Complete the function draw of the class CFlower which draws a flower at position (x, -1, z). The stem of a flower is represented by a cone oriented in y-direction (i.e. upwards) with a radius of stemRadius and a height of stemHeight. The flower has two leaves, each of which is an ellipsoid with major axis leafLength/2 and a medium and minor axis which are 40% and 10%, respectively, of these values. The leaves are attached to the stem at the heights leafHeight1 and leafHeight2 and have an angle with COMPSCI 373 S1 C – Assignment 2 – Sample Solution respect to the stem of leafAngle and -leafAngle degrees. The leaves of each flower lie in a plane which has an angle of angleWithXAxis degrees with the x-axis. Centred on the top of the stem is a blossom which is an ellipsoid with the major and medium radius blossomCentreRadius and a minor radius of 0.5 times the major radius (i.e. it’s a squashed sphere with a height of 50% of its diameter). The blossom has numPetals each of which is a longitudinal ellipsoid with a major radius of petalLength/2 and a medium and minor radius of 0.5 times and 0.2 times the major radius, respectively. The petals are distributed over the top half of the blossom using a spiral point function which is defined as follows: A spiral point set of size N, distributed over the surface of a sphere, is defined by1 k cos 1 hk , hk 1 2(k 1) , 1 k N ( N 1) mod 2 1 hk2 where , , 0 , 0 2 are the spherical coordinates of the points. k k 1 3.6 N 1 You can use these coordinates to rotate the petals into the correct positions. At the end you should obtain a solution similar to the images below. Note that you can use different methods to generate the flower petals as long as the results look similar. Please use the materials defined in the files Flower.h and Flower.cpp For the stem and leaves use “mat_stem” (light green) For the blossom centre use “”mat_blossom_centre” (yellow) For the blossom petals use “”mat_petal” (red) Your final result should look similar to the images below. 1 E. A. Rakhmanov, E. B. Saff, and Y. M. Zhou. Minimal discrete energy on the sphere. Mathematical Research Letters, 1(6):647-662, November 1994. 3 of 11 COMPSCI 373 S1 C – Assignment 2 – Sample Solution Solution: The easiest solution is to construct a flower aligned with the z-axis and based on the origin and then rotate and translate it into the correct position on the ground plane. void CFlower::draw() { glEnable(GL_NORMALIZE); glPushMatrix(); glTranslatef(x,-1,z); glRotatef(90-angleWithXAxis,0,1,0); glRotatef(-90,1,0,0); // move flower onto the ground plane // rotate flower within the ground plane // rotate flower upright // draw entire flower aligned with z axis glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular); glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess); // draw stem glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mat_stem); glutSolidCone(stemRadius, stemHeight, nSlices, nStacks); // draw leaf 1 glPushMatrix(); glTranslatef(0, 0, leafHeight1); glRotatef(leafAngle, 1, 0, 0); glTranslatef(0, 0, leafLength/2); glScalef(0.1, 0.4, 1); glutSolidSphere(leafLength/2, nSlices, nStacks); glPopMatrix(); // draw leaf 2 glPushMatrix(); glTranslatef(0, 0, leafHeight2); glRotatef(-leafAngle, 1, 0, 0); glTranslatef(0, 0, leafLength/2); glScalef(0.1, 0.4, 1); glutSolidSphere(leafLength/2, nSlices, nStacks); glPopMatrix(); //draw blossom glTranslatef(0, 0, stemHeight); // translates blossom AND pedals glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mat_blossom_centre); glPushMatrix(); glScalef(1,1,0.5); glutSolidSphere(blossomCentreRadius, nSlices, nStacks); glPopMatrix(); // draw petals glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mat_petal); double h,theta,phi=0,twoPi=2*Pi; for(int kminus1=0;kminus1<numPetals;kminus1++) { glPushMatrix(); 4 of 11 COMPSCI 373 S1 C – Assignment 2 – Sample Solution if (kminus1==0) { theta=0; phi=0; } else{ h=-1+(double) (2*kminus1)/(numPetals-1); theta=acos(h); phi+=3.6/sqrt(numPetals*(1-h*h)); phi-=twoPi*((floor) (phi/twoPi)); } glRotatef(-90,0,1,0); glRotatef(phi/twoPi*360.0f,1,0,0); glRotatef(theta/twoPi*360.0f,0,1,0); glTranslatef(0, 0, petalLength/2); glScalef(0.2, 0.5, 1); glutSolidSphere(petalLength/2, nSlices, nStacks); glPopMatrix(); } glPopMatrix(); glDisable(GL_NORMALIZE); } (b) [6 Marks] Complete the class CMonument so that it draws a Greek monument. Currently only the top slap of the monument is defined and the displayed scene will look as indicated on the right. You should modify the class such that it draws the five pillars of the monument. Define a pillar as a surface of revolution (with correct surface normals!). Then draw this surface (using quad strips) five times in the draw method and perform appropriate translations such that all pillars are correctly positioned under the top slap. The surface of revolution should be subdivided in circumferential direction using NUM_CIRCUMFERENTIAL_VERTICES vertices and in horizontal direction using NUM_HORIZONTAL_VERTICES vertices evenly distributed over the two quarter circles of the profile curve. The resulting scene should look as indicated in the image below on the right. 5 of 11 COMPSCI 373 S1 C – Assignment 2 – Sample Solution Solution: The surface of revolution is defined by creating first a profile curve. We could use the formula in the lecture notes (which uses the z-axis as rotation axis) and then rotate the pillar upright. However, it’s easy to modify the formula such that the y-axis is the rotation axis. We first define a profile curve c(t)=(x(t),y(t),0). For simplicity let r=RADIUS_PROFILE_CURVE_PILLAR R=RADIUS_PILLAR h=HEIGHT_PILLAR Then r cos t R cbottom (t ) r sin t r 0 t [ ,1.5 ] R r cmiddle (t ) t 0 t [r , h r ] r cos t R ctop (t ) r sin t ( h r ) 0 t [0.5 , ] How do we get these equations? Consider the bottom quarter circle. A unit circle has the equation (cos t, sin t) where t[0,2π]. We are only interested in the third quadrant (bottom-left quarter) of the unit circle which has the parameters t[ π,1.5π]. Furthermore our quarter circle has the radius r and the centre (R,r). Hence we must scale the unit quarter circle with r and we must translate it by (R,r). The resulting equation is (r cos t + R, r sin t + r) where t[ π,1.5π]. The surface of revolution is obtained by rotating each point of the profile curve around the x-axis, i.e. x(t ) sin s p( s, t ) y (t ) x(t ) cos s s [0,2 ] The normals are x(t ) cos s x '(t ) sin s y '(t ) sin s p p 0 n( s, t ) y '(t ) x(t ) x '(t ) s t y '(t ) cos s x(t ) sin s x '(t ) cos s Since the straight part of the profile curve is defined by connecting the top point of the bottom quarter circle and the bottom point of the top quarter circle it’s enough to discretize the parametric equations for the surfaces generated by the two quarter circles. For each circle we generate 8 points along each quarter circle of the profile curve and 16 points in circumferential direction which gives us a 16x16 array of vertices and normals. 6 of 11 COMPSCI 373 S1 C – Assignment 2 – Sample Solution Here is an alternative “geometric” solution obtained by analysing the geometry rather than creating parametric equations (more efficient but harder to understand) // Definition of a pilllar int i,j; double radius, height; double normalY; // y-coordinate of unit normal double normalScale; // scale factor which must be applied to x and z-coordinate // of normal in order to get a unit normal for(i=0;i<NUM_HORIZONTAL_VERTICES;i++) { const int nVertices=NUM_HORIZONTAL_VERTICES/2; if (i<nVertices) // the first half of vertices are on the bottom quarter circle { radius=RADIUS_PILLAR-sin(Pi/2*((double) i/(nVertices-1))) *RADIUS_PROFILE_CURVE_PILLAR; height=RADIUS_PROFILE_CURVE_PILLAR-cos(Pi/2* ((double) i/(nVertices-1)))*RADIUS_PROFILE_CURVE_PILLAR; normalY=cos(Pi/2*((double) i/(nVertices-1))); } else // all other vertices are on the top quarter circle { radius=RADIUS_PILLAR-cos(Pi/2*(((double) i-nVertices) /(nVertices-1)))*RADIUS_PROFILE_CURVE_PILLAR; height=HEIGHT_PILLAR-RADIUS_PROFILE_CURVE_PILLAR+sin(Pi/2* (((double) i-nVertices)/(nVertices-1)))*RADIUS_PROFILE_CURVE_PILLAR; normalY=-sin(Pi/2*(((double) i-nVertices)/(nVertices-1))); } for(j=0;j<NUM_CIRCUMFERENTIALL_VERTICES;j++) { // create for each vertex a set of vertices in circumferential // direction and store them in the 2D array vertices[i][j][1]=(float) height; vertices[i][j][0]=(float) -(cos(2*Pi*j /(double) (NUM_CIRCUMFERENTIALL_VERTICES-1))*radius); vertices[i][j][2]=(float) (sin(2*Pi*j /(double) (NUM_CIRCUMFERENTIALL_VERTICES-1))*radius); normals[i][j][1]=(float) normalY; normalScale=1-normalY*normalY; normals[i][j][0]=(float) -(cos(2*Pi*j /(double) (NUM_CIRCUMFERENTIALL_VERTICES-1))*normalScale); normals[i][j][2]=(float) (sin(2*Pi*j /(double) (NUM_CIRCUMFERENTIALL_VERTICES-1))*normalScale); } } The draw method is shown in the answer to the next question! 7 of 11 COMPSCI 373 S1 C – Assignment 2 – Sample Solution (c) [5 Marks] Map a stone texture onto the pillars. You may use any image of a stone texture, but remember that the dimensions of a texture must be a power of 2. You are allowed to use the image “stone.ppm” shown below on the left. The result should look similar to the image on the right. In order to complete this exercise you have to compute the texture coordinates in the constructor of the CMonument class (approximately 2 lines of code) and you have to bind and apply the texture in the draw method of that class (approximately 6 lines of code). If you want to use a different texture please change the file name in the constructor and don’t forget to submit the ppm-file with that texture. Note: When rendering the texture image use texture coordinates and vertex normals and use glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); This way the texture is modulated with the shaded polygons, which gives an illuminated texture. Solution: Since the circumference is shorter than the height of the pillar we map the s-coordinate of the texture onto the s-coordinate of the surface and similar for the t-coordinates. The vertices in circumferential direction have equal distance from each other so that we can divide the texture coordinates in s-direction into equal sized steps. However, in t-direction we have closely spaced vertices along the quarter circles but a large gap between vertices where the pillar is cylindrical. As an approximation we choose as texture coordinates the relative height of a vertex (y-coordinate divided by the height of a pillar). [NOTE: An even better solution is to use the arclength of the profile curve.] 8 of 11 COMPSCI 373 S1 C – Assignment 2 – Sample Solution Generation of texture coordinates: // variable height defined during vertex creation – see answer to previous question for(j=0;j<NUM_CIRCUMFERENTIALL_VERTICES;j++) { // create for each vertex a set of vertices in circumferential // direction and store them in the 2D array vertices[i][j][1]=(float) height; vertices[i][j][0]=(float) -(cos(2*Pi*j /(double) (NUM_CIRCUMFERENTIALL_VERTICES-1))*radius); vertices[i][j][2]=(float) (sin(2*Pi*j /(double) (NUM_CIRCUMFERENTIALL_VERTICES-1))*radius); normals[i][j][1]=(float) normalY; normalScale=1-normalY*normalY; normals[i][j][0]=(float) -(cos(2*Pi*j /(double) (NUM_CIRCUMFERENTIALL_VERTICES-1))*normalScale); normals[i][j][2]=(float) (sin(2*Pi*j /(double) (NUM_CIRCUMFERENTIALL_VERTICES-1))*normalScale); texture_coordinates[i][j][0]=j/(float) (NUM_CIRCUMFERENTIALL_VERTICES-1); texture_coordinates[i][j][1]=height/HEIGHT_PILLAR; } Draw method: void CMonument::draw() { glMaterialfv(GL_FRONT, glMaterialfv(GL_FRONT, glMaterialfv(GL_FRONT, glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular); GL_SHININESS, mat_shininess); GL_AMBIENT, mat_ambient); GL_DIFFUSE, mat_diffuse); glBindTexture(GL_TEXTURE_2D, texName); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, textureWidth, textureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texName); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); int i,j; for(int k=0;k<NUM_PILLARS;k++) { glPushMatrix(); glTranslatef(-2.2, -1, -1.5+(1+3*k)*RADIUS_PILLAR); // draw a single pillar for(i=0;i<NUM_HORIZONTAL_VERTICES-1;i++) { glBegin(GL_TRIANGLE_STRIP); // could also use quad strip for(j=0;j<NUM_CIRCUMFERENTIALL_VERTICES;j++) { glNormal3fv(normals[i][j]); glTexCoord2fv(texture_coordinates[i][j]); glVertex3fv(vertices[i][j]); glNormal3fv(normals[i+1][j]); glTexCoord2fv(texture_coordinates[i+1][j]); glVertex3fv(vertices[i+1][j]); } glEnd(); } glPopMatrix(); } glDisable(GL_TEXTURE_2D); // draw the top of the monument … } 9 of 11 COMPSCI 373 S1 C – Assignment 2 – Sample Solution 3. Sketch-based Modelling 10 of 11 (11 Marks) In this question you will create a sketch-based modelling tool to create a surface-of-revolution from a sketched curve. Download the file Ass2Sketch.zip which contains a .NET solution including all necessary source files. Unzip the files and compile and run them. You should see the picture on the left. Please sketch a profile curve by pressing the right mouse button and moving the mouse. The curve needs to lie on the left side of the blue rotation axis (as indicated in the second image from the left). If you press “s” the surface-of-revolution is computed. The program skeleton has not yet code for constructing the curve and surface and you will hence only see the control points of the Catmull-Rom spline curve (in green). You need to define the actual curve and surface in the next two steps of this question. Note that after pressing the ‘s’ key you get into “surface mode” and you can rotate the surface by pressing the left mouse button and moving the mouse. You can get back to the sketch mode by pressing “r” (reset) two times. (a) [5 Marks] Insert into the file CatmullRom.cpp the missing code for computing and displaying the Catmull-Rom spline curve defined by the n control points stored in the array ctrlPoints. After completing this part you should be see a green curve smoothly interpolating the red points on your sketch. COMPSCI 373 S1 C – Assignment 2 – Sample Solution Solution: In order to simplify the code and make it more readable it makes sense to define the Catmull-Rom basis functions first and then use them in the code. The polynomial coefficients of the basis functions are given by then entries of the Catmull-Rom basis matrix (see lecture notes). Each curve segment is defined as the sum of the product of the corresponding control points and basis functions. The resulting parametric curve is then sampled and drawn as polyline connecting the sample points. double double double double double double double double basis1(double t){ return -0.5*t*t*t+1.0*t*t-0.5*t;} basis2(double t){ return 1.5*t*t*t-2.5*t*t+1.0;} basis3(double t){ return -1.5*t*t*t+2.0*t*t+0.5*t;} basis4(double t){ return 0.5*t*t*t-0.5*t*t;} db1dt(double t){ return -1.5*t*t+2.0*t-0.5;} db2dt(double t){ return 4.5*t*t-5.0*t;} db3dt(double t){ return -4.5*t*t+4.0*t+0.5;} db4dt(double t){ return 1.5*t*t-t;} typedef double (*basisFunctionType)(double); basisFunctionType CRBasisFunction[]={&basis1,&basis2,&basis3,&basis4}; basisFunctionType CRBasisFunctionDerivative[]={&db1dt,&db2dt,&db3dt,&db4dt}; void CCatmullRom::_computeCurvePoint(int segment, float t, CVec3df& p) { p.setVector(0.0, 0.0, 0.0); for(int i=0;i<ORDER;i++) p+=*ctrlPoints[segment+i]*CRBasisFunction[i](t); } // The tangent is used for computing the surface normal in part (b) void CCatmullRom::_computeCurveTangent(int segment, float t, CVec3df& v) { v.setVector(0.0, 0.0, 0.0); for(int i=0;i<ORDER;i++) v+=*ctrlPoints[segment+i]*CRBasisFunctionDerivative[i](t); } void CCatmullRom::computeCurveAndSurface(int& numPoints, CVec3df**& points) { n = numPoints; ctrlPoints = points; int i,j; // compute NUM_CURVE_POINTS points on the Catmull-Rom spline // defined by the "n" control points in the array "ctrlPoints" int numCurveSegments=n-3; float t; int segment; for(i=0;i<NUM_CURVE_POINTS;i++) { t=((float) numCurveSegments)* ((float) i)/((float) (NUM_CURVE_POINTS-1)); segment=(int) floor(t); t=t-segment; if (segment<0) segment=0; if (segment>=numCurveSegments) segment=numCurveSegments-1; // the curve segment 'segment' has the control points with // the indices segment, segment+1, segment+2, segment+3 _computeCurvePoint(segment, t, curvePoints[i]); _computeCurveTangent(segment, t, curveTangents[i]); } // compute surface created by rotating Catmull-Rom spline around the x-axis } 11 of 11 COMPSCI 373 S1 C – Assignment 2 – Sample Solution void CCatmullRom::displayCurve() { // display the Catmull-Rom spline representing the profile curve using // a green line (RGB=(0,1,0)) with a width of WIDTH_CURVE glLineWidth(WIDTH_CURVE); glColor3f(0,1,0); glBegin(GL_LINE_STRIP); for(int i=0;i<NUM_CURVE_POINTS;i++) glVertex3fv(curvePoints[i].getArray()); glEnd(); } (b) [6 Marks] Insert into the file CatmullRom.cpp the missing code for computing and displaying the surface-of-revolution defined by the Catmull-Rom spline profile curve defined in the previous step. After completing this part you should be able to create nicely shaded 3D models as indicated in the picture below. Solution: Same as for a surface of revolution, i.e. compute curve points (x(t), y(t), 0) and rotate them around the y-axis. void CCatmullRom::computeCurveAndSurface(int& numPoints, CVec3df**& points) { n = numPoints; ctrlPoints = points; int i,j; // compute NUM_CURVE_POINTS points on the Catmull-Rom spline // defined by the "n" control points in the array "ctrlPoints" 12 of 11 COMPSCI 373 S1 C – Assignment 2 – Sample Solution // compute surface created by rotating Catmull-Rom spline around the x-axis double x,y,z,r,angle; CVec3df v1, v2, n; for(i=0;i<NUM_CURVE_POINTS;i++){ cout << "\n curveTangents[i]=" << curveTangents[i]; for(j=0;j<NUM_CIRCUMFERENTIAL_POINTS;j++){ angle=2.0*Pi*j/((double) (NUM_CIRCUMFERENTIAL_POINTS-1)); r=curvePoints[i][X]; y=curvePoints[i][Y]; x=r*cos(angle); z=r*sin(angle); surfacePoints[i][j].setVector(x,y,z); n.setVector(curveTangents[i][Y]*cos(angle), -curveTangents[i][X], curveTangents[i][Y]*sin(angle)); n.normaliseDestructiveNoError(); surfaceNormals[i][j]=-n; } } } void CCatmullRom::displaySurface() { // display the surface using the materials properties defined below // Remember to use Gouraud shading (GL_SMOOTH) // Please use the arrays "surfacePoints" and "surfaceNormals" and glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular); glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mat_ambient_and_diffuse); glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess); glShadeModel(GL_SMOOTH); int i,j; for(i=0;i<(NUM_CURVE_POINTS-1);i++){ glBegin(GL_QUAD_STRIP); for(j=0;j<NUM_CIRCUMFERENTIAL_POINTS;j++){ glNormal3fv(surfaceNormals[i+1][j].getArray()); glVertex3fv(surfacePoints[i+1][j].getArray()); glNormal3fv(surfaceNormals[i][j].getArray()); glVertex3fv(surfacePoints[i][j].getArray()); } glEnd(); } } 13 of 11
© Copyright 2024