Homework 3: Exploring and Finishing the BoundingBox
Class
Background: The Design of Abstractions
Often when coding we realize that a concept comes up repeatedly. When this happens, it's often a good idea to create a new abstraction to represent that concept, thinking about what it is (and thus what to name it) and what it can do. In C++, we typically represent abstractions using classes.
When we first wrote the code for this assignment, in various places in the code we had x and y coordinates, widths, and heights represented as separate int
variables. Everything worked fine, but the code was a bit clunky and repetitive. These variables cropped up in various places, like the position and size of animated objects and the movie display area. So it made sense to create a new abstraction to represent it. In this case, we didn't have to think of a name for the abstraction, because the concept is already well-known: a bounding box.
Bounding boxes are commonly used in computer graphics and game development to represent the area occupied by an object, which is useful for tasks like collision detection and spatial organization. The moment we introduced the BoundingBox
class, our code became cleaner and easier to read. Instead of passing around four separate integers, we could just pass around a single BoundingBox
object.
The most important thing the BoundingBox
class does is just store the information about a bounding box. This is such a fundamental aspect of the bounding box that we did not make these data members private and provide getters and setters. Instead, we made them public so that other classes could access them directly.
Some people call a class like this a ”plain old data” (POD) class.
I'm scandalized. I thought private data members were a fundamental part of encapsulation! I was taught to always make getters and setters.
Sometimes it's better to just keep things simple. If we made these private, we'd have to write a lot of boilerplate code for getters and setters that would just clutter up the class without adding much value. The point of getters and setters is to allow us the option to change the internal representation later without affecting users of the class. But in this case, we don't expect to change how we represent a bounding box, so it's simpler to just make the data members public.
And this gives us MORE speed, right? Because we don't have the function call overhead of getters and setters?
Meh. Less code to write and read is more important than a few nanoseconds of speed.
But besides the core functionality of just storing the bounding box's position and size, there are useful operations that we can perform on bounding boxes, such as checking whether two bounding boxes overlap. When we added these operations, our code became even cleaner and easier to read. Instead of writing out the logic for checking overlap every time we needed it, we could just call a member function from the BoundingBox
class.
Specifically, we added the following member functions to the BoundingBox
class:
overlaps(const BoundingBox& other)
: returns true if this bounding box overlaps with theother
bounding box.isContainedIn(const BoundingBox& container)
: returns true if this bounding box is completely contained within thecontainer
bounding box.isOutside(const BoundingBox& container)
: returns true if this bounding box is partially or completely outside thecontainer
bounding box.wrapWithin(const BoundingBox& bounds)
: modifies this bounding box's position to wrap around within thebounds
bounding box (useful for scrolling effects).
Your Task
Code Reading
Most of this class is complete, but there are a few gaps. Your first task is to read through the provided code in boundingbox.hpp
and boundingbox.cpp
to understand how the class is structured and what each member function does.
Also, read over boundingbox-test.cpp
to see how the class is tested. This will help you understand the expected behavior of each function and also get a sense of how to write tests for your own classes in the future.
Implement wrapWithin
Currently, the wrapWithin
function does nothing. Your task is to implement this function so that it modifies the bounding box's position to wrap around within the given bounds
bounding box. This means that if the bounding box goes off one edge of the bounds
, it should reappear so that it is just about to appear on the opposite edge.
For example, if the left edge of our bounding box moves beyond the right edge of the bounds
(so object is no longer inside the bounds), we don't just set it back to appear at bounds.x
(the left edge) as that would make the entire bounding box appear at once. Instead, we set it to bounds.x - width_
, so that the right edge of our bounding box is just about to appear on the left edge of the bounds
. This way, as the bounding box continues to move right, it will smoothly reappear on the left side.
Test Your Implementation
Once you've implemented wrapWithin
, you should be able to compile and run the tests in boundingbox-test.cpp
. These tests will check that your implementation behaves as expected. If any tests fail, review your implementation and the test cases to identify and fix any issues.
Helpful Hints
These hints will help you write complete the
BoundingBox
class!
Understanding Bounding Box Coordinates
The bounding box is defined by its top-left corner (x_, y_)
, its width_
, and its height_
.
It is worth remembering that x_ + width_
gives first coordinate that is not part of the bounding box (i.e., the column just to the right of the bounding box). Similarly, y_ + height_
gives the first row that is not part of the bounding box (i.e., the row just below the bounding box). This is property actually quite useful when doing comparisons.
Implementing wrapWithin
When implementing wrapWithin
, consider the following cases separately for horizontal and vertical wrapping:
-
Horizontal Wrapping:
- If the left edge of our object's bounding box (
x_
) is beyond the right edge ofbounds
, our object moves to the left side ofbounds
, so it's just off the left edge. - If the left edge of
bounds
's bounding box (bounds.x_
) is beyond the right edge of our object's bounding box, our object moves to the right side ofbounds
, so it's just off the right edge.
- If the left edge of our object's bounding box (
-
Vertical Wrapping:
- If the top edge of our object's bounding box (
y_
) is beyond the bottom edge ofbounds
, our object moves to the top side ofbounds
, so it's just off the top edge. - If the top edge of
bounds
's bounding box (bounds.y_
) is beyond the bottom edge of our object's bounding box, our object moves to the bottom side ofbounds
, so it's just off the bottom edge.
- If the top edge of our object's bounding box (
(When logged in, completion status appears here.)