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 such as 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 information 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.
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:
isOverlappingWith(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.findUnion(const BoundingBox& other)
: returns a new bounding box that is the union of this bounding box and theother
bounding box.findIntersection(const BoundingBox& other)
: returns a new bounding box that is the intersection of this bounding box and theother
bounding box.includesPoint(int x, int y)
: returns true if the point(x, y)
is inside this 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
You have both some code reading and some implementation to do.
Code Reading
Most of this class is complete, but there is one gap. 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, which 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
's bounding box. Thus, if the bounding box goes off one edge of the bounds
(no matter how far off the edge it is, so long as it is entirely outside bounds
), it should be set 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
box. Thus, as our bounding box continues to move rightwards, it will smoothly reappear on the left side of the bounds
box.
See the Helpful Hints section below for more guidance on how to implement this function.
Possible Code Revision
Although the existing code works, there is an interesting debate to have with your partner about how isOverlappingWith
is implemented. It has a long expression with many conditionals in it which makes it hard to be sure it is correct. Contrast this code with the code for findIntersection
, which is longer, but whose use of variables makes it easier to read and understand. So you might consider rewriting isOverlappingWith
in a similar style. Or whether it could be written so that it uses findIntersection
to determine whether there is an overlap.
Discuss these options with your partner and decide whether you want to leave isOverlappingWith
as-is or rewrite it. If you do decide to rewrite it, make sure that the tests in boundingbox-test.cpp
still pass!
Wouldn't calling
findIntersection
be inefficient, since it creates a newBoundingBox
object and also has the overhead of a function call?Honestly, it's not possible to say without measuring. Human intuition about performance is often wrong. But in this case, the performance difference is likely to be negligible compared to the benefits of having clearer code. Remember that code is read many more times than it is written, so clarity is often more important than micro-optimizations.
And, of course, writing clear, correct, readable code is a focus of this course.
Meh. I'm not changing working code. Unless I'll lose a ton of points or something.
There's no penalty for leaving it as is. The point of the exercise isn't the credit, it's the discussion and honing your sense of code elegance and clarity.
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 of the 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 you the first x-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 you the first row that is not part of the bounding box (i.e., the row just below the bounding box). This property is 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 (
And here's another tip: Human beings aren't great at abstract reasoning about coordinates and dimensions. A good way to make this way easier to think about is to draw a picture! A concrete visualization can make it much easier to understand the relationships between the bounding boxes and how they should wrap around.
There's an even easier way! Take two pieces of regular letter-sized paper. Fold one in half and half again the opposite way to make a smaller rectangle. The big piece of paper is
bounds
and the smaller, folded, piece is your bounding box. Now move the small piece around on the big piece to see what happens when it goes off one edge or the other.I like this idea! But I can use my tablet and a drawing app to do this digitally.
Whatever works for you! The point is to make it easier to visualize the problem.
(When logged in, completion status appears here.)