10

I have a vector of points stored in a std::vector instance. I want to calculate the bounding box of these points. I've tried with this code:

bool _compare1(ofPoint const &p1, ofPoint const &p2) {
    return p1.x < p2.x && p1.y < p2.y;
}
bool _compare4(ofPoint const &p1, ofPoint const &p2) {
    return p1.x > p2.x && p1.y > p2.y;
}
vector<ofPoint> points;

// ...
if(points.size()>1) {
    ofPoint p_min = *std::min_element(points.begin(), points.end(), &_compare1);
    ofPoint p_max = *std::min_element(points.begin(), points.end(), &_compare4);
}

But this code produces weird results. In reality I'm interested only the first and last points of my bounding box:

1------2
|\     |
| \    |
|  \   |
|   \  |
|    \ |
|     \|
3------4

If my points represent the diagonal line I'm interested only in point 1 and 4.

Are there smart ways to get this with the standard libraries or Boost?


CURRENT SOLUTION:

bool _compare_min_x(ofPoint const &p1, ofPoint const &p2) { return p1.x < p2.x; }
bool _compare_min_y(ofPoint const &p1, ofPoint const &p2) { return p1.y < p2.y; }
 
// ....
 
    if(points.size()>1) {
        min_x = (*std::min_element(points.begin(), points.end(), &_compare_min_x)).x;
        min_y = (*std::min_element(points.begin(), points.end(), &_compare_min_y)).y;

        max_x = (*std::max_element(points.begin(), points.end(), &_compare_min_x)).x;
        max_y = (*std::max_element(points.begin(), points.end(), &_compare_min_y)).y;
    }
Community
  • 1
  • 1
nkint
  • 11,513
  • 31
  • 103
  • 174

3 Answers3

16

I think the problem is that your comparison functions are making too strong an assumption about the shape of the bounding box. Consider these two points:

          1
         /
        /
       /
      2

The correct bounding box is

      +---1
      |  /|
      | / |
      |/  |
      2---+

Notice that the bounding box corners aren't actually points in your vector. Instead, they're points formed by combining coordinates from different points in the vector. Moreover, if you look at your two comparison functions, you'll find that given these two points, neither point compares less than or greater than the other point, since each one has one coordinate that is higher than the other and one that is lower than the other.

To get your bounding box, you should do the following:

  1. Find the point with the min x value.
  2. Find the point with the max x value.
  3. Find the point with the min y value.
  4. Find the point with the max y value.
  5. Combine the x and y from the points with the min x and y value into one corner point.
  6. Combine the x and y from the points with the max x and y value into one corner point.

You can do this using the new C++11 std::minmax_element algorithm, along with lambdas:

auto xExtremes = std::minmax_element(v.begin(), v.end(),
                                     [](const ofPoint& lhs, const ofPoint& rhs) {
                                        return lhs.x < rhs.x;
                                     });

auto yExtremes = std::minmax_element(v.begin(), v.end(),
                                     [](const ofPoint& lhs, const ofPoint& rhs) {
                                        return lhs.y < rhs.y;
                                     });

ofPoint upperLeft(xExtremes.first->x, yExtremes.first->y);
ofPoint lowerRight(xExtremes.second->x, yExtremes.second->y);

Hope this helps!

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • alles klar! thanks for good explanation but i don't have C++11 so i used 4 min_element.. – nkint Jan 30 '12 at 21:25
  • 1
    @nkint- Rather than defining four functions and using min_element each time, why not just define two comparison functions and then use max_element where appropriate? This saves you a lot of code and makes the program much easier to read. – templatetypedef Jan 31 '12 at 18:43
3

Simply iterate over all elements and keep track of the current min/max. You can use boost::minmax to update both at the same time. Really no need to iterate twice over your dataset.

Anteru
  • 19,042
  • 12
  • 77
  • 121
  • But that said, even with those changes the code still won't work because the comparison functions will not correctly pick up the corners in all cases. – templatetypedef Jan 30 '12 at 21:12
  • Start with the first point as min/max in both x/y, and in your loop, just update x,y separately. – Anteru Jan 30 '12 at 21:14
2

If you don't have c++11, you can use boost::algorithm::minmax_element.

#include <boost/algorithm/minmax_element.hpp>
bool compareX(ofPoint lhs, ofPoint rhs) { return lhs.x < rhs.x; };
bool compareY(ofPoint lhs, ofPoint rhs) { return lhs.y < rhs.y; };

// ....
    pair<vector<ofPoint>::iterator, vector<ofPoint>::iterator> xExtremes, yExtremes;
    xExtremes = boost::minmax_element(overlap_point.begin(), overlap_point.end(), compareX);
    yExtremes = boost::minmax_element(overlap_point.begin(), overlap_point.end(), compareY);
    ofPoint upperLeft(xExtremes.first->x, yExtremes.first->y);
    ofPoint lowerRight(xExtremes.second->x, yExtremes.second->y);
Gianluigi
  • 167
  • 2
  • 8