CS-C3100 Computer Graphics, Fall 2021 Lehtinen / Aho, Kaskela, Kynka¨a¨nniemi Assignment 2: Curves and Surfaces Due Sun Oct 10th at 23:59. In this assignment, you will be implementing spline curves and mesh subdivision. These are powerful building blocks for animation, 3D modeling and other tasks. The recom- mended extra credit work applies your spline curves to additional ways of modeling 3D objects: swept surfaces and generalized cylinders. This assignment is considerably more challenging than the intro assignment, and the majority of your time will be spent thinking about the theory rather than how to write code. Start early so you have time for debugging, thinking about the issues that come up, and asking for help. Requirements (maximum 10 p) on top of which you can do extra credit 1. Evaluating Bezier curves (2 p) 2. Evaluating B-spline curves (2 p) 3. Subdividing a mesh to smaller triangles (2 p) 4. Computing positions for the new vertices (2 p) 5. Smoothing the mesh by repositioning old vertices (2 p) 1 Getting Started The sample solution example.exe is included in the starter code distribution. Notice that we have added some buttons to the right of the window. First, load up core.swp by clicking the ’Load SWP’ button on the right. 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 what is drawn with the UI buttons. Click the ’Draw normals’ button to see the local coordinate system at points of a curve. Subdivision surfaces are loaded with the ’Load OBJ’ button. (Previously loaded curves will stay visible, and can only be hidden by restarting the application.) Press the ’Refine subdivision’ and ’Coarsen subdivision’ buttons to change the subdivision level. Play with the different models to see what happens. 1 The SWP files under the srev and gcyl subdirectories are for extra credit work. In addition to splines, these files contain sweep curves and generalized cylinders based on those splines. example.exe can draw them; click the ’Draw surface’ button to show or hide the generated surface. Now, build the starter project and launch it. Load the SWP file circles.swp. The starter code is fully functional on this example as-is. If you click the ’Draw normals’ button, you’ll also notice that the coordinate frames are computed for you. The scene can be rotated, panned and zoomed using the mouse and its buttons; try it out. This assignment reuses old code, and the rendering code is very different from the intro assignment. The code you see here is old, ”immediate mode” OpenGL. Please note that you don’t have to touch the rendering code in this assignment, and all the remaining assignments will again use modern style OpenGL. 2 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). The starter code already renders the control points for you. You will need to evaluate the spline individually and build a list of vertices per each curve. Computing points on the spline is sufficient if the only goal is to draw it. For other appli- cations of splines, such as animation and surface modeling, you might need to generate additional information (see extra credit section). 2 R1 Bezier curves (2 p) Implement the evaluation of Bezier curve points (function evalBezier). Start by writing the coreBezier function in curve.cpp, which is the basis for evaluating one 4-point bezier curve at small steps, generating sequences of vertices that can then be used for drawing short straight lines on the screen, ultimately making it look like a smooth line. In the evalBezier function, you generate points along the whole chained-together Bezier curve. Because we chain the individual Beziers together by sharing the last control point of the preceding curve with the first control point of the next curve, this function must get 3n + 1 vertices as input
(n ≥ 1). I.e., the first curve segment is defined by control points {p1, p2, p3, p4}, the second by {p4, p5, p6, p7}, etc. You need to loop over the input control points and feed the proper subsets to the coreBezier function. You need to string the results of coreBezier together in the output vector of tessellated points. The sample function evalCircle shows how to use the CurvePoint and Curve classes. You’ll only need to fill in the V member of the CurvePoints; the normals and others are for extra credit. See the slides on spline curves for more in-depth information about the underlying math! R2 B-splines (2 p) Evaluate a B-spline curve in evalBspline. As you already have the code for evaluating points on a Bezier curve from R1, it’s best to just change the basis from B-spline to Bezier, and use the code you already wrote. This is the benefit of generality we’ve talked about in class! Similar to evalBezier, you should loop over the input control points. Remember that B-splines build the curve with a sliding window that only increases by one at each round; the first segment is built from control points {p1, p2, p3, p4}, but differing from the above, the second uses control points {p2, p3, p4, p5}, etc. For each such subset, put the four control points in a 4×4 matrix and apply the B-spline- to-Bezier conversion matrix (that you know how to build). Then read off the new control points, now in the Bernstein basis, off the columns of the resulting matrix, and pass them on to evalBezier. After completing this part, the camera animation system (example in swp/campath/sponza.swp) works partially – the position is correctly animated, but the camera looks in a constant direction. Fixing this is an extra credit assignment. 3 3 Loop Subdivision Surfaces In this part of the assignment, you are to implement triangle mesh subdivision using the Loop scheme. Algorithms like this are the core building blocks of all modern modeling systems! The basic idea is to subdivide each triangle in the input mesh into four new triangles, and to carefully compute positions for the new vertices and the old vertices in a way that results in a smooth surface when we keep subdividing. In the limit, we get a surface that is actually mathematically smooth almost everywhere. Choosing the appropriate ways of computing the positions is deep and fascinating, but fortunately, implementing a subdivision scheme does not require one to understand why. (Phew.) While you should be able to fulfill the requirements using this document only, we heartily recommend you check out Denis Zorin and his colleagues’ SIGGRAPH course notes on subdivision to find out more. You’ll find that Loop’s algorithm is a popular, but by no means only choice of subdivision algoritm, for instance. You will find the code you need to complete in Subdiv.cpp. The class MeshWith- Connectivity is the one where the heavy lifting happens. 3.1 Debug tools If you run into problems with for example indexing the triangles when generating the one- ring (see R5 description,) it might help to use the provided debug highlight functionality to see what your code does. If you press alt while you have a model loaded, the debug code first finds the vertex your mouse is currently pointing at, then runs the one-ring generation code only for that one vertex. When LoopSubdivision is running in this debug mode, the debugPass boolean will be set to true. You can then push vertex indices into the highlightIndices vector to highlight those vertices as large red points in the GL view. The solution executable demonstrates this by highlighting the one-ring around the chosen vertex. You can naturally customize what happens in the debug pass if you want to debug some other aspect of the subdivision algorithm. 3.2 Warmup: Connectivity Information In subdivision algorithms, it is important that vertices are shared between adjacent triangles. This is easiest if the position for each vertex (as well as its color, nor- mal, etc.) are only stored once, and the triangles refer to these tables by indices. In MeshWithConnectivity, the positions, normals, and colors are stored in the member vectors positions, normals, and colors. Now, we specify each triangle as a sequence of three indices into a vertex table, which would look something like shown in Figure 2, left. We store the index information in the indices member of MeshWithConnectivity, defined as std::vector