zoneminder/src/zm_poly.cpp

122 lines
4.0 KiB
C++

//
// ZoneMinder Polygon Class Implementation, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "zm_poly.h"
#include "zm_line.h"
#include <cmath>
Polygon::Polygon(std::vector<Vector2> vertices) : vertices_(std::move(vertices)), area(0) {
UpdateExtent();
UpdateArea();
UpdateCentre();
}
void Polygon::UpdateExtent() {
if (vertices_.empty())
return;
int min_x = vertices_[0].x_;
int max_x = 0;
int min_y = vertices_[0].y_;
int max_y = 0;
for (const Vector2 &vertex : vertices_) {
min_x = std::min(min_x, vertex.x_);
max_x = std::max(max_x, vertex.x_);
min_y = std::min(min_y, vertex.y_);
max_y = std::max(max_y, vertex.y_);
}
extent = Box({min_x, min_y}, {max_x, max_y});
}
void Polygon::UpdateArea() {
double float_area = 0.0;
for (size_t i = 0, j = vertices_.size() - 1; i < vertices_.size(); j = i++) {
double trap_area = ((vertices_[i].x_ - vertices_[j].x_) * ((vertices_[i].y_ + vertices_[j].y_))) / 2.0;
float_area += trap_area;
}
area = static_cast<int32>(std::lround(std::fabs(float_area)));
}
void Polygon::UpdateCentre() {
if (!area && !vertices_.empty())
UpdateArea();
double float_x = 0.0;
double float_y = 0.0;
for (size_t i = 0, j = vertices_.size() - 1; i < vertices_.size(); j = i++) {
float_x += ((vertices_[i].y_ - vertices_[j].y_)
* ((vertices_[i].x_ * 2) + (vertices_[i].x_ * vertices_[j].x_) + (vertices_[j].x_ * 2)));
float_y += ((vertices_[j].x_ - vertices_[i].x_)
* ((vertices_[i].y_ * 2) + (vertices_[i].y_ * vertices_[j].y_) + (vertices_[j].y_ * 2)));
}
float_x /= (6 * area);
float_y /= (6 * area);
centre = Vector2(static_cast<int32>(std::lround(float_x)), static_cast<int32>(std::lround(float_y)));
}
bool Polygon::Contains(const Vector2 &coord) const {
bool inside = false;
for (size_t i = 0, j = vertices_.size() - 1; i < vertices_.size(); j = i++) {
if ((((vertices_[i].y_ <= coord.y_) && (coord.y_ < vertices_[j].y_)) || ((vertices_[j].y_ <= coord.y_) && (coord.y_ < vertices_[i].y_)))
&& (coord.x_ < (vertices_[j].x_ - vertices_[i].x_) * (coord.y_ - vertices_[i].y_) / (vertices_[j].y_ - vertices_[i].y_) + vertices_[i].x_)) {
inside = !inside;
}
}
return inside;
}
// Clip the polygon to a rectangular boundary box using the Sutherland-Hodgman algorithm
void Polygon::Clip(const Box &boundary) {
std::vector<Vector2> clipped_vertices = vertices_;
for (LineSegment const &clip_edge : boundary.Edges()) {
// convert our line segment to an infinite line
Line clip_line = Line(clip_edge);
std::vector<Vector2> to_clip = clipped_vertices;
clipped_vertices.clear();
for (size_t i = 0; i < to_clip.size(); ++i) {
Vector2 vert1 = to_clip[i];
Vector2 vert2 = to_clip[(i + 1) % to_clip.size()];
bool vert1_left = clip_line.IsPointLeftOfOrColinear(vert1);
bool vert2_left = clip_line.IsPointLeftOfOrColinear(vert2);
if (vert2_left) {
if (!vert1_left) {
clipped_vertices.push_back(Line(vert1, vert2).Intersection(clip_line));
}
clipped_vertices.push_back(vert2);
} else if (vert1_left) {
clipped_vertices.push_back(Line(vert1, vert2).Intersection(clip_line));
}
}
}
vertices_ = clipped_vertices;
UpdateExtent();
UpdateArea();
UpdateCentre();
}