CS 70

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.

  • Rabbit speaking

    Some people call a class like this a ”plain old data” (POD) class.

  • Duck speaking

    I'm scandalized. I thought private data members were a fundamental part of encapsulation! I was taught to always make getters and setters.

  • LHS Cow speaking

    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.

  • Pig speaking

    And this gives us MORE speed, right? Because we don't have the function call overhead of getters and setters?

  • Goat speaking

    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 the other bounding box.
  • isContainedIn(const BoundingBox& container): returns true if this bounding box is completely contained within the container bounding box.
  • isOutside(const BoundingBox& container): returns true if this bounding box is partially or completely outside the container bounding box.
  • wrapWithin(const BoundingBox& bounds): modifies this bounding box's position to wrap around within the bounds 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

  • LHS Cow speaking

    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 of bounds, our object moves to the left side of bounds, 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 of bounds, so it's just off the right edge.
  • Vertical Wrapping:

    • If the top edge of our object's bounding box (y_) is beyond the bottom edge of bounds, our object moves to the top side of bounds, 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 of bounds, so it's just off the bottom edge.

To Complete This Part of the Assignment...

You'll know you're done with this part of the assignment when you've done all of the following:

(When logged in, completion status appears here.)