Geometry Tools

Now that you understand the basics of how CUGL works, it is time to get familiar with some of the more technical features. CUGL has a lot of computational geometry tools for making procedural 2D shapes. These tools are particularly important for when you are working with physics, but they are also relevant to drawing. Indeed, this lab does not have any assets other than the loading screen. Everything in the image below will be created with code.

Geometry

In addition to building shapes, this is also the first lab where you will launch your application on a mobile device. While 90% of the lab can be done on the same desktop platform that you used last time, the last step will involve porting the input commands to mobile. That means you will need an iPhone or an Android device to test your work (or else use an Android emulator on a computer).

Once again, this assignment is graded in one of two ways. If you are a 4152 student, this is graded entirely on effort (did you legitimately make an attempt), and that effort will go towards you participation grade. If you are a 5152 student, you will get a more detailed numerical score.

Table of Contents


Project Files

Download: Geometry.zip

You should download the project for this assignment. This is a self-contained project just like all of the ones available from the demos page. You should pick either XCode or Visual Studio as your primary development environment. That is because these IDEs have the best debugging tools. However, if you plan to port to Android, you will need Android Studio installed as well.

Running the Geometry Lab

Once again, this game will run (i.e. not crash) on all platforms. And you can even successfully press the play button on both iOS and Android. But beyond that it will not do all the much. It just shows a grey screen, regardless of the platform. That is because you are responsible for adding all the code to GameScene.cpp that powers the lab.

Adding Mobile Support

The last activity in the instructions will require you to write code to run on a mobile device. You will need the correct computer for the correct device. Android phones can be run on either platform, but you will need to install Android Studio. iPhones require XCode and a Mac. If you do not have access to a Mac, take advantage of the fact that we are back in person to make a friend. As a last resort, everyone in the class should be able to run an Android simulator within Android Studio (though remember about the potential emulator issues).

On the Mac, you will need switch the build target from Geometry (Mac) to Geometry (iOS). You will also need to plug in your phone or iPad. Finally, you will need to go to Geometry (iOS) and set the team (under Signing & Capabilities) to “Personal Team”. This is where you set your iOS developer account if you already have one. “Personal Team” is the non-commercial option.

To port to Android, you should start Android Studio and select Open. Navigate to the android build directory and select build.gradle. This will initialize the project for you. Android Studio should recognize your phone when you plug it in, provided you have activated developer mode. Note that if you build an Android release without a phone plugged in, it will build for all four platforms (ARM, ARM64, x86, x86_64) instead of simply targetting the phone. This means that compilation will take four times as long.


Geometry Basics

There are several concepts that are important for this assignment. To keep you from having to scour the documents to find them, we highlight them here.

Poly2

This is perhaps the most important class of all of CUGL. A Poly2 object represents a triangulated solid polygon. This polygon can be drawn to the screen using a SpriteBatch. It can also be used to define a box2d physics object. It is a general purpose way of representing solid geometry. Anything that is not a rectangle or a circle is a Poly2 object in CUGL.

When drawing a Poly2 object, you use the fill command in SpriteBatch. It will fill the polygon with the active sprite batch color. You also need to specify the origin. The polygon vertices do not specify where they go on the screen, but are instead relative to an origin. When you draw the polygon on the screen, it is placed using this origin. By default, the origin is (0,0), but that is not necessary. It is also common to have the origin in the centroid (i.e. center of mass) of the polygon.

There are many, many tools for creating Poly2 objects in CUGL. One of the most useful is the class PolyFactory. This class includes methods to construct Poly2 objects for all basic shapes like ellipses, rounded rectangles, and capsules.

Path2

The class Poly2 originally supported both solid polygons and polylines (i.e. the boundary of a polygon). However, this was starting to cause a lot of problems with the architecture, so CUGL 2.1 refactored non-solid polygons into the class Path2. A path is essentially a sequence of line segments. Often a path will be the boundary of a Poly2 object, but Path2 objects are not required to be closed (i.e. first and last points are the same), and so they can represent much more.

It is possible to draw a Path2 object with a SpriteBatch. However, the results are rather unsatisfactory. A Path2 objects is drawn with lines that are one-pixel in width. Unfortunately, most mobile devices are high DPI (what Apple calls Retina Display), so this is too small to be easily seen.

If you want to see a Path2, it is best to give it a line stroke width. But once you do that, it is no longer a Path2 object. It is now a solid Poly2 object. The actå of giving a path a width is called extrusion. The tool SimpleExtruder is your primary tool for extruding a path. While there is another extruder available, it is extremely slow (as a trade-off for being more precise) and cannot be used at framerate.

If the Path2 object is closed, we can also “fill it in”. We do this by triangulating the path with one of the triangulation classes. Right now there are two such classes: ear clipping and Delaunay (monontone triangulation was not finished in time for the semester). Both have their advantages and disadvantages, but their performance is pretty comparable. If in doubt, use the Delaunay triangulator as it gives more “even” triangles.

Spline2

The class Path2 is a sequence of line segments. But what if we want to create a curved shape? That is the purpose of Spline2. This represents a multi-segmented Bézier spline. Each point on the curve consists of an anchor (the vertex itself) and two control points (the handles). The control points are the endpoints of the tangent line of the anchor, as shown below.

Bezier Curve

A Spline2 object cannot be drawn directly to the screen. You must flatten it first, converting it into a Path2 object. This is done with the class SplinePather. Once you have flattened the Bézier spline, you can extrude it or triangulate it, just like any other path.

PolygonObstacle

The primary physics engine for CUGL is box2d. You are free to add any physics engine you want, like Bullet. But only box2d has integrated support in the engine.

Indeed, those of you from the introductory course remember that box2d makes a distinction between bodies (which represent position) and fixtures (which represent a shape). Having to keep track of both of these things can be difficult if you are not familiar with the engine. We simplified this concept in the last 3152 programming lab by creating the Obstacle class for you. The Obstacle class combines body and fixtures into a single class to make physics substantially easier.

We have also provided support for obstacles in CUGL as part of the cugl::physics2 namespace. CUGL uses sub-namespaces for any feature that is not considered mandatory. There are obstacles for various shapes, such as rectangles and circles. But for this lab you will be using the PolygonObstacle to make arbitrary shaped physics objects. And while this sounds a little scary, it is not. Every single Poly2 object can be converted into a PolygonObstacle with just a few lines of code.

If you choose to use the obstacle classes, you also have to use the ObstacleWorld class. This is a wrapper around the b2world class from box2d that supports obstacles. As with the 3152 lab, this class has the added benefit of greatly simplifying how a box2d system is created and simulated.


Instructions

Throughout this assignment you will modify two files:

  • GLGameScene.cpp and its header
  • GLInputController.cpp and its header

When we completed this assignment, we did not need any more classes than this. In particular, we did not add any custom model classes this time. However, if you feel that you need to add additional classes to complete the assignment, you are welcome to do so.

1. Create a Spline2 and Flatten It

At the top of GameScene is global variable called CIRCLE. This is an array of floats, defining the control points of a Bézier spline. It technically defines a circle, but we will play with that later. You need to initialize the attribute _spline with this data. You do that with the set method. This method should be called in the init method of GameScene.

Technically the set method needs an array of Vec2 objects and not an array of floats. However, that is easy to do with an reinterpret_cast. Just remember that when you convert a float array into a Vec2 array, the array now has half as many elements. That is important for calling the set method.

Once you have defined the spline, it is time to flatten it. For reasons that will be made clear later, we want you to flatten it in the buildScene method. That means this method should be called in init after the spline is initialized.

To flatten the spline you will need a SplinePather. To use this tool you call three different methods

  • The set method to place the Spline2 object in the pather tool
  • The calculate method to perform the flattening
  • The get method to extract a Path2 object for the spline

Why do it this way instead of placing it all in one method? That is to make the code more amenable for multithreaded programming. You can call the calculate method in a separate thread so that it does not slow down your animation. Then you get the results in the main thread.

You may or may not need to add a new attribute to GameScene to store the flattened results. We leave that up to you. If you want to test your work, you can try to draw the flattened path in the render method. But as we said before, paths are very thin. So you will get something that is very hard to see, like the image below.

Path2

When drawing the path, remember that it is centered at the origin. To display it in the middle of the screen, the sprite batch will need to translate it by getSize()/2.

2. Extrude the Flattened Spline

You should use SimpleExtruder to extrude the given flattened spline in buildScene. The process is very similar to what you did for flattening. The only difference is that set does not take a pointer (for technical reasons we will not mention here) and calculate needs the line width. You should use the constant LINE_WIDTH.

This time you will need a new attribute in GameScene to store the resulting Poly2. This attribute will allow you to draw it. You should draw the polygon in black. You should also put it in the middle of the screen (translate it by getSize()/2). The result will look like the image below.

Extrusion

2. Add Control Handles

Now it is time to add some handles to control the spline. Once again, all of this code will be in buildScene. You need to go back to the Spline2 object. The method getTangent returns the two control points for each point on the curve. For any given anchor point at position pos, 2*pos is the position of the right control point and 2*pos-1 is the position of the left control point. In a closed spline like this one, the left control point of the first point is at position 2*n-1, where n is the number of points.

For each anchor point on the spline you should first get the left and right control points. You should then create a Path2 object that is the line between the two and extrude it into a Poly2 object with width HANDLE_WIDTH. You will need to store all of the these Poly2 objects somewhere, so you will need to create a vector or unordered_set to hold them all (and add this as an attribute to GameScene).

In addition, for each control point (left or right), you should make a circular Poly2 using PolyFactory. This circle should be centered at the control point and have radius KNOB_RADIUS

Finally, to test your results, you should draw them all. The handle lines should be drawn as white and the circles (knobs) should be drawn as red. Everything should be translated by the same transform you applied to the extruded spline itself. The result will look like the image below.

Control Handles

3. Add a Star

We are going to add one more shape in buildScene. At the top of GameScene is global variable called STAR. Use it to create a Path2 object (it is not a spline and already flattened). You may need to reinterpret_cast as before.

Next you want to “fill” this shape by triangulating it. Take the earclip triangular and follow the set, calculate, get pattern as usual.

Except that when you run this program, it will crash. Why? Because the STAR path is clockwise. All geometry shapes should be counterclockwise. The classic computational algorithms are all predicated on this fact. Fortunately Path2 has a reverse method that allows us to fix this problem. Call that method and try it again.

Once you have created the triangulated Poly2 object, store it in an attribute so you can draw it. This shape should be drawn in the center of the screen and using the color blue. The result will look like the image below.

Star Polygon

4. Add Input Controls

We have already created an input controller for you. This input controller responds to mouse controls on desktop. It can tell when the user presses the (left) mouse button, when the users releases it, and when user drags the mouse with the button down. You are going to use this to control the knobs.

In the update method check for a mouse press. If so, get the position. You will need to convert from screen coordinates (the coordinate system of the mouse) to world coordinates (the drawing coordinates). You can do that with the method screenToWorldCoordinates which is part of GameScene (inherited from Scene2). You will also need to remember to subtract getSize()/2 to adjust for the fact that everything is adjusted from the origin to the center of the screen.

Once you have the adjusted position of the mouse, you should check if the position is within KNOB_RADIUS of a control point. If so, that control point is selected. That point remains selected until the user releases the mouse button at some later animation frame.

While the control point is selected, compare the current mouse position with the previous mouse position. Add this difference to the control point. When you update the control point, you will call setTangent. Make sure that symmetric is true so that the opposite control point moves together with it, as shown below.

Controls

However, you will not be able to see this yet. That is because while you may be modifying the spline, you are not updating all the Poly2 objects you created. Whenever you change a control point, you should delete all of the Poly2 objects and call buildScene again. If you do that properly, you should now be able to see the animation.

5. Add Physics Support

It is now time to add physics support. While this is relatively easy, it is important that you do everything in the right place. First of all, you need to add code to the init method of GameScene after the call to buildScene. This is because you need the Poly2 objects to make the physics bodies, but you do not want to create a new physics engine every time you rebuild the scene.

Create a new ObstacleWorld. Gravity should be pointing downwards, so use the value Vec2(0,-GRAVITY). For the size, you want to use getSize()/PHYSICS_SCALE. Those of you from the introductory course will remember that one box2d unit is 1 meter. So if we make each pixel a meter, the objects are huge. That is why we want the box2d coordinate system to be smaller than the screen.

Now make PolygonObstacle objects for the spline and the star. Create a copy of their Poly2 objects and divide the objects by PHYSICS_SCALE (this uniformly divides all vertices by this amount). Pass this to the alloc method and use Vec2::ZERO as the polygon origin. You will also need to set a few properties.

  • The spline body type should be b2_staticBody
  • The star body type should be b2_dynamicBody
  • The star density should be 1.
  • Both objects should have position getSize()/(2*PHYSICS_SCALE)

The last step ensures once again that the objects will centered be in the screen. Once you have created the objects, call addObstacle to add each obstacle to the world.

To run physics, you should add the call

_world->update(timestep);

to the update method in GameScene. This method should only be called when no control point is selected with the mouse. If the user is playing with the mouse, the animation should pause.

To test your physics, you will also need to update the drawing code. The spline and all of its handles/knobs correspond to the spline physics object (i.e. they are transformed uniformly) and the star corresponds to the star physics objects. For each group, create a transform that rotated by getAngle and translated by getPosition()*PHYSICS_SCALE. Now when you run the program the star should drop to the bottom of the circle.

There is one last thing to fix with the physics. As we said, the physics should only be updated when no control point is selected with the mouse. If the user does select a control point, you should immediately delete the spline and star physics objects with clear. You should recreate them again only when the mouse is released. This means the star will reset to the center of the screen as the user drags the mouse, and it will only drop when the user lets go.

6. Add Touch Support

It is now time to add mobile support. You do not need to modify GameScene to do this. Instead, you need to modify InputController to add touch support. So all of your for this last step will be confined to that file (though you may have to add attributes to the header).

You will notice that in InputController we get the mouse and check if it is nullptr. That is because it might be on a computer that does not have a mouse (like a mobile tablet). Mobile devices use Touchscreen instead. For the part, most Touchscreen works exactly like a mouse. But is has one major difference. With the mouse, we use the event to check which button is pressed. With touch we use the event to check which finger is used.

The finger is identified by the TouchID. While the number means nothing by itself, this is how we separate fingers from each other when multitouch is active. In particular, your code should only register a press if no other fingers are down. If a new touch id appears, it is a new finger and should be ignored. In addition, when you are checking the finger motion, you should only recognize the motion if it is the same touch id as the initial press. When that finger is finally released, then and only then can you respond to a new touch.

Of course this means a new set of callback functions to register, similar to the ones we created for the mouse. And sometimes the mouse and touch code does not get along. So how we tell the computer to use the right one? The answer is compiler directives. You can tell the compiler to check the platform you are compiling for and only compile code that runs on that platform.

For example, in GeometryApp you have the following code

#ifdef CU_TOUCH_SCREEN
	// Start-up basic input for loading screen (MOBILE ONLY)
	Input::activate<Touchscreen>();
#else
	// Start-up basic input for loading screen (DESKTOP ONLY)
	Input::activate<Mouse>();
#endif

This tells the compiler to active the touch screen if you are on mobile device. The line to active the mouse is not compiled. This is more than a simple if-statement. The code is not skipped over in execution. The compiler does not see it at all! Using this technique you can create code specifically for Windows, Mac, iOS, or Android. See CUBase.h for the various compiler options.

You should use this technique to add touch screen support. Once you complete this you are done with the assignment.


Submission

Due: Fri, Feb 10 at 11:59 PM

This assignment is a little harder than the first one. But by now you should be familiar with compiling CUGL, and have a passing understanding of C++. Once again this should be within your grasp.

When submitting the assignment do not submit the entire project. That is too large and CMS will crash under the load. Instead we want you create a zip file containing the files:

  • GLGameScene.h
  • GLGameScene.cpp
  • GLInputController.h
  • GLInputController.cpp
  • readme.txt

If you created new classes, you should add those as well. The file readme.txt should tell us if you worked in Visual Studio or in XCode for the desktop version. You should also tell us what mobile device you used to test the touch code. If you used a simulator on a computer, tell us that.

Submit this zip file as lab2code.zip to CMS