Algorithm for edge intersection? - c++

Given Polygon P which I have its verticies in order. and I have a rectangle R with 4 verticies how could I do this:
If any edge of P (line between adjacent vertexes) intersects an edge of R, then return TRUE, otherwise return FALSE.
Thanks
* *
* *

What you want is a quick way to determine if a line-segment intersects an axis-aligned rectangle. Then just check each line segment in the edge list against the rectangle. You can do the following:
1) Project the line onto the X-axis, resulting in an interval Lx.
2) Project the rectangle onto the X-axis, resulting in an interval Rx.
3) If Lx and Rx do not intersect, the line and rectangle do not intersect.
[Repeat for the Y-axis]:
4) Project the line onto the Y-axis, resulting in an interval Ly.
5) Project the rectangle onto the Y-axis, resulting in an interval Ry.
6) If Ly and Ry do not intersect, the line and rectangle do not intersect.
7) ...
8) They intersect.
Note if we reach step 7, the shapes cannot be separated by an axis-aligned line. The thing to determine now is if the line is fully outside the rectangle. We can determine this by checking that all the corner points on the rectangle are on the same side of the line. If they are, the line and rectangle are not intersecting.
The idea behind 1-3 and 4-6 comes from the separating axis theorem; if we cannot find a separating axis, they must be intersecting. All these cases must be tested before we can conclude they are intersecting.
Here's the matching code:
#include <iostream>
#include <utility>
#include <vector>
typedef double number; // number type
struct point
{
number x;
number y;
};
point make_point(number pX, number pY)
{
point r = {pX, pY};
return r;
}
typedef std::pair<number, number> interval; // start, end
typedef std::pair<point, point> segment; // start, end
typedef std::pair<point, point> rectangle; // top-left, bottom-right
namespace classification
{
enum type
{
positive = 1,
same = 0,
negative = -1
};
}
classification::type classify_point(const point& pPoint,
const segment& pSegment)
{
// implicit line equation
number x = (pSegment.first.y - pSegment.second.y) * pPoint.x +
(pSegment.second.x - pSegment.first.x) * pPoint.y +
(pSegment.first.x * pSegment.second.y -
pSegment.second.x * pSegment.first.y);
// careful with floating point types, should use approximation
if (x == 0)
{
return classification::same;
}
else
{
return (x > 0) ? classification::positive :classification::negative;
}
}
bool number_interval(number pX, const interval& pInterval)
{
if (pInterval.first < pInterval.second)
{
return pX > pInterval.first && pX < pInterval.second;
}
else
{
return pX > pInterval.second && pX < pInterval.first;
}
}
bool inteveral_interval(const interval& pFirst, const interval& pSecond)
{
return number_interval(pFirst.first, pSecond) ||
number_interval(pFirst.second, pSecond) ||
number_interval(pSecond.first, pFirst) ||
number_interval(pSecond.second, pFirst);
}
bool segment_rectangle(const segment& pSegment, const rectangle& pRectangle)
{
// project onto x (discard y values)
interval segmentX =
std::make_pair(pSegment.first.x, pSegment.second.x);
interval rectangleX =
std::make_pair(pRectangle.first.x, pRectangle.second.x);
if (!inteveral_interval(segmentX, rectangleX))
return false;
// project onto y (discard x values)
interval segmentY =
std::make_pair(pSegment.first.y, pSegment.second.y);
interval rectangleY =
std::make_pair(pRectangle.first.y, pRectangle.second.y);
if (!inteveral_interval(segmentY, rectangleY))
return false;
// test rectangle location
point p0 = make_point(pRectangle.first.x, pRectangle.first.y);
point p1 = make_point(pRectangle.second.x, pRectangle.first.y);
point p2 = make_point(pRectangle.second.x, pRectangle.second.y);
point p3 = make_point(pRectangle.first.x, pRectangle.second.y);
classification::type c0 = classify_point(p0, pSegment);
classification::type c1 = classify_point(p1, pSegment);
classification::type c2 = classify_point(p2, pSegment);
classification::type c3 = classify_point(p3, pSegment);
// test they all classify the same
return !((c0 == c1) && (c1 == c2) && (c2 == c3));
}
int main(void)
{
rectangle r = std::make_pair(make_point(1, 1), make_point(5, 5));
segment s0 = std::make_pair(make_point(0, 3), make_point(2, -3));
segment s1 = std::make_pair(make_point(0, 0), make_point(3, 0));
segment s2 = std::make_pair(make_point(3, 0), make_point(3, 6));
segment s3 = std::make_pair(make_point(2, 3), make_point(9, 8));
std::cout << std::boolalpha;
std::cout << segment_rectangle(s0, r) << std::endl;
std::cout << segment_rectangle(s1, r) << std::endl;
std::cout << segment_rectangle(s2, r) << std::endl;
std::cout << segment_rectangle(s3, r) << std::endl;
}
Hope that makes sense.

I think your problem is equivalent to convex polygon intersection, in which case this might help. See also: How do I determine if two convex polygons intersect?

Untested, obviously, but in rough pseudocode:
// test two points against an edge
function intersects ( side, lower, upper, pt1Perp, pt1Par, pt2Perp, pt2Par )
{
if ( ( pt1Perp < side and pt2Perp > side ) or ( pt1Perp > side and pt2Perp < side )
{
intersection = (side - pt1Perp) * (pt2Par - pt1Par) / (pt2Perp - pt1Perp);
return (intersection >= lower and intersection <= higher);
}
else
{
return false;
}
}
// left, right, bottom, top are the bounds of R
for pt1, pt2 adjacent in P // don't forget to do last,first
{
if ( intersects ( left, bottom, top, pt1.x, pt1.y, pt2.x, pt2.y )
or intersects ( right, bottom, top, pt1.x, pt1.y, pt2.x, pt2.y )
or intersects ( top, left, right, pt1.y, pt1.x, pt2.y, pt2.x )
or intersects ( bottom, left, right, pt1.y, pt1.x, pt2.y, pt2.x ) )
{
return true;
}
}
Basically, if two adjacent P vertices are on opposite sides of one of the R's edges, check whether the intersection point falls in range.

Just FYI, geometrictools is a great resource for such things (especially the Math section)

Related

2D Delaunay, Divide and Conquer - Rare bug

I have been working on a two dimensional Divide and Conquer algorithm written in c++, a language I am rather new to. I used this page to base my algorithm on. The Y-axis is inverted in my example images.
I create a grid with a chosen width and height. The program then selects a random coordinate within each cell, these are the points for the Delaunay Triangulation.
The algorithm works as follows
Sort the coordinates in ascending order on the x-axis (similar x values; sort on y)
Divide into subgroups, until each group contains no more than 3 coordinates. Connect each coordinate with a line (I call them 'Edges'). This is in a binary tree structure. The Leaves contain a vector of these edges.
Recursively merge the leaves upwards
# Select the starting edge: select the coordinates, L and R, with the lowest y value from the left and right subgroup. If the edge L-R does not intersect with any edges of either group: select that edge. Else, select the next coordinate. (If the L-R edge intersects with an edge from the left subgroup select the next left coordinate. Ditto with the right group)
Based on the starting edge ('base' in my code) select the next candidate from both groups, left_C and right_C (the following is the left subgroup selection process, the right subgroup is the same but only mirrored):
Select the neighboring coordinates: all coordinates that have a direct connection to the base edge
Sort the neighbors by increasing clockwise angles.
# The candidate selection process: Select first (i = 0) coordinate as the candidate C, the second (i + 1) as the next candidate nC.
Check if the angle between the L-R and C is bigger than 180° or pi. If so: no candidate is selected, else move on to the next condition.
& Check if, the nC is within the circumcircle defined by triangle L-R-C (distance between circumcircle center and nC < radius circumcircle). delete the edge defined by L-C. nC becomes the new candidate C, the candidate with the next highest angle becomes nC, repeat check.
return candidate at the position i
If neither left nor right groups return a candidate, then the merge is complete.
Else if only one group returns a candidate, obviously that is the to-be-used candidate
Else both subgroups return a candidate, then select the candidate where the circumcircle defined by it and the base edge does not contain the other.
A triangle is created using the selected candidate and the base edge: an edge is created between the candidate and base-coordinate from the other subgroup ( eg. candidate of the left-subgroup: R-left_C ). This becomes the base edge for the next iteration.
iterate until the merge is complete (neither subgroup offers a candidate)
And so the tree is merged recursively.
So TL;DR.
The issue that I am having is two steps of the D&C algorithm coming into conflict. the two are the selection of the starting base edge (see step #) and the selection of the candidate coordinates (see step # and &)
The bug (read: feature) occurs with this orientation of the subgroups:
Animated Gif
It skips one of the coordinates of the right group in the first merger. This is because the algorithm specifies that you take the lowest y values of both groups that do not intersect with any other edges as the base edge. Then all the right group candidates, which are attached to the base edge, are selected with their counter-clockwise angle, the left with the clockwise angles. This skips one coordinate. leading to the following result.
I have no idea how to solve this.
Here is my code:
triangulate():
Node* Branch::triangulate() {
if (l != nullptr && r == nullptr) {
return l;
}
else {
// recursive merge
Node* left = l->triangulate();
Node* right = r->triangulate();
vector<Edge> left_edges = left->getEdges();
vector<Edge> right_edges = right->getEdges();
Edge base = selectBase(left_edges, right_edges);
bool merge = true;
vector<Edge> mergedEdges;
vector<Edge> newEdges;
newEdges.push_back(base);
while (merge) {
Coordinate left_candidate = selectCandidateFromSubgroup(left_edges, base, true);
Coordinate right_candidate = selectCandidateFromSubgroup(right_edges, base, false);
Coordinate null(DBL_MIN, DBL_MIN);
if (left_candidate == null && right_candidate == null) {
merge = false;
}
else {
Edge new_base;
if (right_candidate == null) {
new_base = Edge(left_candidate, *base.p2());
}
else if (left_candidate == null) {
new_base = Edge(*base.p1(), right_candidate);
}
else {
double left_a, left_b, left_c,
base_a, base_b, base_c;
functionFromEdge(base, base_a, base_b, base_c);
Edge left_edge(left_candidate, *base.p2());
functionFromEdge(left_edge, left_a, left_b, left_c);
perpendicularBisector(base, base_a, base_b, base_c);
perpendicularBisector(left_edge, left_a, left_b, left_c);
Coordinate left_circumcircle_center = intersection(left_a, left_b, left_c,
base_a, base_b, base_c);
new_base = (Coordinate::distance(left_circumcircle_center, left_candidate)
< Coordinate::distance(left_circumcircle_center, right_candidate))
? left_edge : Edge(*base.p1(), right_candidate);
}
newEdges.push_back(new_base);
base = new_base;
}
}
mergedEdges.reserve(newEdges.size() + left_edges.size() + right_edges.size());
for (Edge e : left_edges) {
mergedEdges.push_back(e);
}
for (Edge e : right_edges) {
mergedEdges.push_back(e);
}
for (Edge e : newEdges) {
mergedEdges.push_back(e);
}
return new Leaf(mergedEdges,i);
}
}
selectBase():
vector<Coordinate> left_points = extractCoordinates(left);
vector<Coordinate> right_points = extractCoordinates(right);
Edge* out = nullptr;
bool found = false;
int i = 0;
int j = 0;
while (!found) {
Coordinate l = left_points.at(i), r = right_points.at(j);
Edge base(l, r);
out = &base;
for (Edge e : left) {
if (out!= nullptr && e.intersects(base)) {
//cout << e << " intersects with " << *out << " i++" << endl;
i++;
out = nullptr;
}
if (out != nullptr && left_points.size() ==2 && (*e.p1() == *base.p1() || *e.p2() == *base.p1())) {
Coordinate other = (*e.p1() == *base.p1()) ? *e.p2() : *e.p1();
if (Angle::calcClockAngle(l, r, other, true).rad() > M_PI) {
out = nullptr; i++;
}
}
}
for (Edge e : right) {
if (out != nullptr && e.intersects(base)) {
//cout << e << " intersects with " << *out << " j++" << endl;
j++;
out = nullptr;
}
if (out != nullptr && right_points.size() == 2 && (*e.p1() == *base.p2() || *e.p2() == *base.p2())) {
Coordinate other = (*e.p1() == *base.p2()) ? *e.p2() : *e.p1();
if (Angle::calcClockAngle(r, l, other, false).rad() > M_PI) {
out = nullptr; j++;
}
}
}
if (out != nullptr) found = true;
}
return Edge(left_points.at(i), right_points.at(j));
}
In the above function I tried to make a check that checks if there is a skipped point, by checking in subgroups of size 2 if there is an angle larger than pi.
It worked for smaller groups. But the issue still occurs.
selectCandidateFromSubgroup:
Coordinate selectCandidateFromSubgroup(vector<Edge>& es, Edge& base, bool left) {
// select neighboring edges from base
Coordinate* shared_point = (left)? base.p1() : base.p2();
Coordinate* other_point = (left)? base.p2() : base.p1();
vector<Coordinate> candidates;
for (Edge e : es) {
if (*shared_point == *e.p2() || *shared_point == *e.p1()) {
Coordinate c = (*shared_point == *e.p2()) ? *e.p1() : *e.p2();
candidates.push_back(c);
}
}
if (candidates.size() != 0) {
algorithm::quicksort(candidates, 0, candidates.size() -1,
[&shared_point,&other_point,&left](Coordinate a, Coordinate b) {
double angle = Angle::calcClockAngle(*shared_point, *other_point, a, left).getDegrees();
double angle2 = Angle::calcClockAngle(*shared_point, *other_point, b, left).getDegrees();
return (angle < angle2 || (angle == angle2 && a.y() < b.y()));
});
double base_a, base_b, base_c;
functionFromEdge(base, base_a, base_b, base_c);
perpendicularBisector(base, base_a, base_b, base_c);
Coordinate* candidate=nullptr;
Coordinate* next_candidate = nullptr;
int i = 0;
bool checking_candidates = true;
while (checking_candidates) {
candidate = &candidates.at(i);
double angle = Angle::calcClockAngle(*shared_point,
*other_point,
*candidate,
left).rad();
if (angle < M_PI) {
Edge candidate_edge;
int cei = 0;
int j = 0;
for (Edge e : es) {
if ((*e.p1() == *shared_point && *e.p2() == *candidate) || (*e.p2() == *shared_point && *e.p1() == *candidate)) {
candidate_edge = e;
cei = j;
break;
}
j++;
}
if (i + 1 == candidates.size()) return *candidate;
next_candidate= &candidates.at(i+1);
double candidate_edge_a, candidate_edge_b, candidate_edge_c;
functionFromEdge(candidate_edge, candidate_edge_a,
candidate_edge_b, candidate_edge_c);
perpendicularBisector(candidate_edge, candidate_edge_a,
candidate_edge_b, candidate_edge_c);
Coordinate circumcicle_center = intersection(base_a, base_b, base_c,
candidate_edge_a, candidate_edge_b, candidate_edge_c);
double radius = Coordinate::distance(circumcicle_center, *shared_point);
double distance_center_nCandidate = Coordinate::distance(circumcicle_center, *next_candidate);
if (radius < distance_center_nCandidate) {
return *candidate;
}
else {
i++;
es.erase(es.begin() + cei);
}
}
else {
return Coordinate(DBL_MIN, DBL_MIN);
}
}
}
return Coordinate(DBL_MIN, DBL_MIN);
}
extractCoordinates(vector) extracts and sorts all coordiates on the y axis ascendingly.
functionFromEdge(Edge,double a,double b,double c) takes an edge and makes a functional representation of the lines like 'ax+by=c', which is used by
perpendicularBisector(Edge, double a, double b, double c) converts the a, b, and c values to represent the function of the perpendicular bisector of the Edge so that the algorithm can calulate the
intersection(double a1, double b1, double c1, double a2, double b2, double c2) of two functions.
the calcClockAngle(Coordinate a, Coordinate b, Coordinate c, bool clockwise) calulates the angle in a bewteen the vectors |AB| and |AC|, depending on the clockwise boolean, it calculates the cloclwise or counter clockwise angle.
If I forgot something please let me know.
Sorry for the long question. but I am seriously at a loss here.
I get results like this:

Separating Axis thereom: Calculating the MTV for Polygon & Line Segment

I've been attempting, for months now, to write a function to return the minimum translation needed to be applied to a line segment in order to separate it from an polygon in which intersects. I'm using the separating axis theorem and it seems I'm able to calculate the magnitude correctly however, the direction returned is sometimes wrong. Yet, when the returned translation is incorrect, the inverse is always correct.
In the pictures below, the yellow line is the one used in calculations, the purple line is the yellow line + translation and the red line is the yellow line minus the translation. As you can see either the purple or the red line is correct in different positions but I'm not sure under what conditions to return which line.
So my question is: On what condition does the translation actually need to be flipped so that my function always returns a translation with the correct direction?
const Projection Polygon::Project(const Axis &a) const
{
float min = a.Dot(GetPoint(0));
float max = min;
for (unsigned i = 1; i < GetPointCount(); i++)
{
float prj = a.Dot(GetPoint(i));
if (prj < min)
min = prj;
else if (prj > max)
max = prj;
}
return Projection(min, max);
}
const Projection Segment::Project(const Axis &a) const
{
const float dot0 = a.Dot(GetPoint(0));
const float dot1 = a.Dot(GetPoint(1));
return Projection(std::min(dot0, dot1), std::max(dot0, dot1));
}
const float Projection::GetOverlap(const Projection &p) const
{
// x = min & y = max
return std::min(y - p.x, p.y - x);
}
const Vector2 Segment::GetTranslation(const Polygon &p) const
{
float Overlap = std::numeric_limits<float>::infinity();
Axis smallest;
Vector2 translation;
AxesVec axes(p.GetAxes());
axes.push_back(GetAxis());
for (auto && axis : axes)
{
const Projection pA = p.Project(axis);
const Projection pB = Project(axis);
if (pA.IsOverlap(pB))
{
const float o = pA.GetOverlap(pB);
if (o < Overlap)
{
Overlap = o;
smallest = axis;
}
}
}
translation = smallest * (Overlap + 1);
return translation;
}
The trouble is that your GetOverlap function returns the magnitude of the overlap, but not the sense (left or right).
You could change it to this:
if(y - p.x < p.y - x)
return y - p.x;
else
return x - p.y;
Then in GetTranslation:
const float o = pA.GetOverlap(pB);
if (abs(o) < abs(Overlap))
{
...
}
if (Overlap > 0)
++Overlap;
else
--Overlap;
translation = smallest * Overlap;

sorting points: concave polygon

I have a set of points that I'm trying to sort in ccw order or cw order from their angle. I want the points to be sorted in a way that they could form a polygon with no splits in its region or intersections. This is difficult because in most cases, it would be a concave polygon.
point centroid;
int main( int argc, char** argv )
{
// I read a set of points into a struct point array: points[n]
// Find centroid
double sx = 0; double sy = 0;
for (int i = 0; i < n; i++)
{
sx += points[i].x;
sy += points[i].y;
}
centroid.x = sx/n;
centroid.y = sy/n;
// sort points using in polar order using centroid as reference
std::qsort(&points, n, sizeof(point), polarOrder);
}
// -1 ccw, 1 cw, 0 collinear
int orientation(point a, point b, point c)
{
double area2 = (b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x);
if (area2 < 0) return -1;
else if (area2 > 0) return +1;
else return 0;
}
// compare other points relative to polar angle they make with this point
// (where the polar angle is between 0 and 2pi)
int polarOrder(const void *vp1, const void *vp2)
{
point *p1 = (point *)vp1;
point *p2 = (point *)vp2;
// translation
double dx1 = p1->x - centroid.x;
double dy1 = p1->y - centroid.y;
double dx2 = p2->x - centroid.x;
double dy2 = p2->y - centroid.y;
if (dy1 >= 0 && dy2 < 0) { return -1; } // p1 above and p2 below
else if (dy2 >= 0 && dy1 < 0) { return 1; } // p1 below and p2 above
else if (dy1 == 0 && dy2 ==0) { // 3-collinear and horizontal
if (dx1 >= 0 && dx2 < 0) { return -1; }
else if (dx2 >= 0 && dx1 < 0) { return 1; }
else { return 0; }
}
else return -orientation(centroid,*p1,*p2); // both above or below
}
It looks like the points are sorted accurately(pink) until they "cave" in, in which case the algorithm skips over these points then continues.. Can anyone point me into the right direction to sort the points so that they form the polygon I'm looking for?
Raw Point Plot - Blue, Pink Points - Sorted
Point List: http://pastebin.com/N0Wdn2sm (You can ignore the 3rd component, since all these points lie on the same plane.)
The code below (sorry it's C rather than C++) sorts correctly as you wish with atan2.
The problem with your code may be that it attempts to use the included angle between the two vectors being compared. This is doomed to fail. The array is not circular. It has a first and a final element. With respect to the centroid, sorting an array requires a total polar order: a range of angles such that each point corresponds to a unique angle regardless of the other point. The angles are the total polar order, and comparing them as scalars provides the sort comparison function.
In this manner, the algorithm you proposed is guaranteed to produce a star-shaped polyline. It may oscillate wildly between different radii (...which your data do! Is this what you meant by "caved in"? If so, it's a feature of your algorithm and data, not an implementation error), and points corresponding to exactly the same angle might produce edges that coincide (lie directly on top of each other), but the edges won't cross.
I believe that your choice of centroid as the polar origin is sufficient to guarantee that connecting the ends of the polyline generated as above will produce a full star-shaped polygon, however, I don't have a proof.
Result plotted with Excel
Note you can guess from the nearly radial edges where the centroid is! This is the "star shape" I referred to above.
To illustrate this is really a star-shaped polygon, here is a zoom in to the confusing lower left corner:
If you want a polygon that is "nicer" in some sense, you will need a fancier (probably much fancier) algorithm, e.g. the Delaunay triangulation-based ones others have referred to.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
struct point {
double x, y;
};
void print(FILE *f, struct point *p) {
fprintf(f, "%f,%f\n", p->x, p->y);
}
// Return polar angle of p with respect to origin o
double to_angle(const struct point *p, const struct point *o) {
return atan2(p->y - o->y, p->x - o->x);
}
void find_centroid(struct point *c, struct point *pts, int n_pts) {
double x = 0, y = 0;
for (int i = 0; i < n_pts; i++) {
x += pts[i].x;
y += pts[i].y;
}
c->x = x / n_pts;
c->y = y / n_pts;
}
static struct point centroid[1];
int by_polar_angle(const void *va, const void *vb) {
double theta_a = to_angle(va, centroid);
double theta_b = to_angle(vb, centroid);
return theta_a < theta_b ? -1 : theta_a > theta_b ? 1 : 0;
}
void sort_by_polar_angle(struct point *pts, int n_pts) {
find_centroid(centroid, pts, n_pts);
qsort(pts, n_pts, sizeof pts[0], by_polar_angle);
}
int main(void) {
FILE *f = fopen("data.txt", "r");
if (!f) return 1;
struct point pts[10000];
int n_pts, n_read;
for (n_pts = 0;
(n_read = fscanf(f, "%lf%lf%*f", &pts[n_pts].x, &pts[n_pts].y)) != EOF;
++n_pts)
if (n_read != 2) return 2;
fclose(f);
sort_by_polar_angle(pts, n_pts);
for (int i = 0; i < n_pts; i++)
print(stdout, pts + i);
return 0;
}
Well, first and foremost, I see centroid declared as a local variable in main. Yet inside polarOrder you are also accessing some centroid variable.
Judging by the code you posted, that second centroid is a file-scope variable that you never initialized to any specific value. Hence the meaningless results from your comparison function.
The second strange detail in your code is that you do return -orientation(centroid,*p1,*p2) if both points are above or below. Since orientation returns -1 for CCW and +1 for CW, it should be just return orientation(centroid,*p1,*p2). Why did you feel the need to negate the result of orientation?
Your original points don't appear form a convex polygon, so simply ordering them by angle around a fixed centroid will not necessarily result in a clean polygon. This is a non-trivial problem, you may want to research Delaunay triangulation and/or gift wrapping algorithms, although both would have to be modified because your polygon is concave. The answer here is an interesting example of a modified gift wrapping algorithm for concave polygons. There is also a C++ library called PCL that may do what you need.
But...if you really do want to do a polar sort, your sorting functions seem more complex than necessary. I would sort using atan2 first, then optimize it later once you get the result you want if necessary. Here is an example using lambda functions:
#include <algorithm>
#include <math.h>
#include <vector>
int main()
{
struct point
{
double x;
double y;
};
std::vector< point > points;
point centroid;
// fill in your data...
auto sort_predicate = [&centroid] (const point& a, const point& b) -> bool {
return atan2 (a.x - centroid.x, a.y - centroid.y) <
atan2 (b.x - centroid.x, b.y - centroid.y);
};
std::sort (points.begin(), points.end(), sort_predicate);
}

Clip line to screen coordinates

I have line that is defined as two points.
start = (xs,ys)
end = (xe, ye)
Drawing function that I'm using Only accepts lines that are fully in screen coordinates.
Screen size is (xSize, ySize).
Top left corner is (0,0). Bottom right corner is (xSize, ySize).
Some other funcions gives me line that that is defined for example as start(-50, -15) end(5000, 200). So it's ends are outside of screen size.
In C++
struct Vec2
{
int x, y
};
Vec2 start, end //This is all little bit pseudo code
Vec2 screenSize;//You can access coordinates like start.x end.y
How can I calculate new start and endt that is at the screen edge, not outside screen.
I know how to do it on paper. But I can't transfer it to c++.
On paper I'm sershing for point that belongs to edge and line. But it is to much calculations for c++.
Can you help?
There are many line clipping algorithms like:
Cohen–Sutherland wikipedia page with implementation
Liang–Barsky wikipedia page
Nicholl–Lee–Nicholl (NLN)
and many more. see Line Clipping on wikipedia
[EDIT1]
See below figure:
there are 3 kinds of start point:
sx > 0 and sy < 0 (red line)
sx < 0 and sy > 0 (yellow line)
sx < 0 and sy < 0 (green and violet lines)
In situations 1 and 2 simply find Xintersect and Yintersect respectively and choose them as new start point.
As you can see, there are 2 kinds of lines in situation 3. In this situation find Xintersect and Yintersect and choose the intersect point near the end point which is the point that has minimum distance to endPoint.
min(distance(Xintersect, endPoint), distance(Yintersect, endPoint))
[EDIT2]
// Liang-Barsky function by Daniel White # http://www.skytopia.com/project/articles/compsci/clipping.html
// This function inputs 8 numbers, and outputs 4 new numbers (plus a boolean value to say whether the clipped line is drawn at all).
//
bool LiangBarsky (double edgeLeft, double edgeRight, double edgeBottom, double edgeTop, // Define the x/y clipping values for the border.
double x0src, double y0src, double x1src, double y1src, // Define the start and end points of the line.
double &x0clip, double &y0clip, double &x1clip, double &y1clip) // The output values, so declare these outside.
{
double t0 = 0.0; double t1 = 1.0;
double xdelta = x1src-x0src;
double ydelta = y1src-y0src;
double p,q,r;
for(int edge=0; edge<4; edge++) { // Traverse through left, right, bottom, top edges.
if (edge==0) { p = -xdelta; q = -(edgeLeft-x0src); }
if (edge==1) { p = xdelta; q = (edgeRight-x0src); }
if (edge==2) { p = -ydelta; q = -(edgeBottom-y0src);}
if (edge==3) { p = ydelta; q = (edgeTop-y0src); }
r = q/p;
if(p==0 && q<0) return false; // Don't draw line at all. (parallel line outside)
if(p<0) {
if(r>t1) return false; // Don't draw line at all.
else if(r>t0) t0=r; // Line is clipped!
} else if(p>0) {
if(r<t0) return false; // Don't draw line at all.
else if(r<t1) t1=r; // Line is clipped!
}
}
x0clip = x0src + t0*xdelta;
y0clip = y0src + t0*ydelta;
x1clip = x0src + t1*xdelta;
y1clip = y0src + t1*ydelta;
return true; // (clipped) line is drawn
}
Here is a function I wrote. It cycles through all 4 planes (left, top, right, bottom) and clips each point by the plane.
// Clips a line segment to an axis-aligned rectangle
// Returns true if clipping is successful
// Returns false if line segment lies outside the rectangle
bool clipLineToRect(int a[2], int b[2],
int xmin, int ymin, int xmax, int ymax)
{
int mins[2] = {xmin, ymin};
int maxs[2] = {xmax, ymax};
int normals[2] = {1, -1};
for (int axis=0; axis<2; axis++) {
for (int plane=0; plane<2; plane++) {
// Check both points
for (int pt=1; pt<=2; pt++) {
int* pt1 = pt==1 ? a : b;
int* pt2 = pt==1 ? b : a;
// If both points are outside the same plane, the line is
// outside the rectangle
if ( (a[0]<xmin && b[0]<xmin) || (a[0]>xmax && b[0]>xmax) ||
(a[1]<ymin && b[1]<ymin) || (a[1]>ymax && b[1]>ymax)) {
return false;
}
const int n = normals[plane];
if ( (n==1 && pt1[axis]<mins[axis]) || // check left/top plane
(n==-1 && pt1[axis]>maxs[axis]) ) { // check right/bottom plane
// Calculate interpolation factor t using ratio of signed distance
// of each point from the plane
const float p = (n==1) ? mins[axis] : maxs[axis];
const float q1 = pt1[axis];
const float q2 = pt2[axis];
const float d1 = n * (q1-p);
const float d2 = n * (q2-p);
const float t = d1 / (d1-d2);
// t should always be between 0 and 1
if (t<0 || t >1) {
return false;
}
// Interpolate to find the new point
pt1[0] = (int)(pt1[0] + (pt2[0] - pt1[0]) * t );
pt1[1] = (int)(pt1[1] + (pt2[1] - pt1[1]) * t );
}
}
}
}
return true;
}
Example Usage:
void testClipLineToRect()
{
int screenWidth = 320;
int screenHeight = 240;
int xmin=0;
int ymin=0;
int xmax=screenWidth-1;
int ymax=screenHeight-1;
int a[2] = {-10, 10};
int b[2] = {300, 250};
printf("Before clipping:\n\ta={%d, %d}\n\tb=[%d, %d]\n",
a[0], a[1], b[0], b[1]);
if (clipLineToRect(a, b, xmin, ymin, xmax, ymax)) {
printf("After clipping:\n\ta={%d, %d}\n\tb=[%d, %d]\n",
a[0], a[1], b[0], b[1]);
}
else {
printf("clipLineToRect returned false\n");
}
}
Output:
Before clipping:
a={-10, 10}
b=[300, 250]
After clipping:
a={0, 17}
b=[285, 239]

Sort points by angle from given axis?

How can I sort an array of points/vectors by counter-clockwise increasing angle from a given axis vector?
For example:
If 0 is the axis vector I would expect the sorted array to be in the order 2, 3, 1.
I'm reasonably sure it's possible to do this with cross products, a custom comparator, and std::sort().
Yes, you can do it with a custom comparator based on the cross-product. The only problem is that a naive comparator won't have the transitivity property. So an extra step is needed, to prevent angles either side of the reference from being considered close.
This will be MUCH faster than anything involving trig. There's not even any need to normalize first.
Here's the comparator:
class angle_sort
{
point m_origin;
point m_dreference;
// z-coordinate of cross-product, aka determinant
static double xp(point a, point b) { return a.x * b.y - a.y * b.x; }
public:
angle_sort(const point origin, const point reference) : m_origin(origin), m_dreference(reference - origin) {}
bool operator()(const point a, const point b) const
{
const point da = a - m_origin, db = b - m_origin;
const double detb = xp(m_dreference, db);
// nothing is less than zero degrees
if (detb == 0 && db.x * m_dreference.x + db.y * m_dreference.y >= 0) return false;
const double deta = xp(m_dreference, da);
// zero degrees is less than anything else
if (deta == 0 && da.x * m_dreference.x + da.y * m_dreference.y >= 0) return true;
if (deta * detb >= 0) {
// both on same side of reference, compare to each other
return xp(da, db) > 0;
}
// vectors "less than" zero degrees are actually large, near 2 pi
return deta > 0;
}
};
Demo: http://ideone.com/YjmaN
Most straightforward, but possibly not the optimal way is to shift the cartesian coordinates to be relative to center point and then convert them to polar coordinates. Then just subtract the angle of the "starting vector" modulo 360, and finally sort by angle.
Or, you could make a custom comparator for just handling all the possible slopes and configurations, but I think the polar coordinates are little more transparent.
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
struct Point {
static double base_angle;
static void set_base_angle(double angle){
base_angle = angle;
}
double x;
double y;
Point(double x, double y):x(x),y(y){}
double Angle(Point o = Point(0.0, 0.0)){
double dx = x - o.x;
double dy = y - o.y;
double r = sqrt(dx * dx + dy * dy);
double angle = atan2(dy , dx);
angle -= base_angle;
if(angle < 0) angle += M_PI * 2;
return angle;
}
};
double Point::base_angle = 0;
ostream& operator<<(ostream& os, Point& p){
return os << "Point(" << p.x << "," << p.y << ")";
}
bool comp(Point a, Point b){
return a.Angle() < b.Angle();
}
int main(){
Point p[] = { Point(-4., -4.), Point(-6., 3.), Point(2., -4.), Point(1., 5.) };
Point::set_base_angle(p[0].Angle());
sort(p, p + 4, comp);
Point::set_base_angle(0.0);
for(int i = 0;i< 4;++i){
cout << p[i] << " angle:" << p[i].Angle() << endl;
}
}
DEMO
Point(-4,-4) angle:3.92699
Point(2,-4) angle:5.17604
Point(1,5) angle:1.3734
Point(-6,3) angle:2.67795
Assuming they are all the same length and have the same origin, you can sort on
struct sorter {
operator()(point a, point b) const {
if (a.y > 0) { //a between 0 and 180
if (b.y < 0) //b between 180 and 360
return false;
return a.x < b.x;
} else { // a between 180 and 360
if (b.y > 0) //b between 0 and 180
return true;
return a.x > b.x;
}
}
//for comparison you don't need exact angles, simply relative.
}
This will quickly sort them from 0->360 degress. Then you find your vector 0 (at position N), and std::rotate the results left N elements. (Thanks TomSirgedas!)
This is an example of how I went about solving this. It converts to polar to get the angle and then is used to compare them. You should be able to use this in a sort function like so:
std::sort(vectors.begin(), vectors.end(), VectorComp(centerPoint));
Below is the code for comparing
struct VectorComp : std::binary_function<sf::Vector2f, sf::Vector2f, bool>
{
sf::Vector2f M;
IntersectComp(sf::Vector2f v) : M(v) {}
bool operator() ( sf::Vector2f o1, sf::Vector2f o2)
{
float ang1 = atan( ((o1.y - M.y)/(o1.x - M.x) ) * M_PI / 180);
float ang2 = atan( (o2.y - M.y)/(o2.x - M.x) * M_PI / 180);
if(ang1 < ang2) return true;
else if (ang1 > ang2) return false;
return true;
}
};
It uses sfml library but you can switch any vector/point class instead of sf::Vector2f. M would be the center point. It works great if your looking to draw a triangle fan of some sort.
You should first normalize each vector, so each point is in (cos(t_n), sin(t_n)) format.
Then calculating the cos and sin of the angles between each points and you reference point. Of course:
cos(t_n-t_0)=cos(t_n)cos(t_0)+sin(t_n)sin(t_0) (this is equivalent to dot product)
sin(t_n-t_0)=sin(t_n)cos(t_0)-cos(t_n)sin(t_0)
Only based on both values, you can determine the exact angles (-pi to pi) between points and reference point. If just using dot product, clockwise and counter-clockwise of same angle have same values. One you determine the angle, sort them.
I know this question is quite old, and the accepted answer helped me get to this, still I think I have a more elegant solution which also covers equality (so returns -1 for lowerThan, 0 for equals, and 1 for greaterThan).
It is based on the division of the plane to 2 halves, one from the positive ref axis (inclusive) to the negative ref axis (exclusive), and the other is its complement.
Inside each half, comparison can be done by right hand rule (cross product sign), or in other words - sign of sine of angle between the 2 vectors.
If the 2 points come from different halves, then the comparison is trivial and is done between the halves themselves.
For an adequately uniform distribution, this test should perform on average 4 comparisons, 1 subtraction, and 1 multiplication, besides the 4 subtractions done with ref, that in my opinion should be precalculated.
int compareAngles(Point const & A, Point const & B, Point const & ref = Point(0,0)) {
typedef decltype(Point::x) T; // for generality. this would not appear in real code.
const T sinA = A.y - ref.y; // |A-ref|.sin(angle between A and positive ref-axis)
const T sinB = B.y - ref.y; // |B-ref|.sin(angle between B and positive ref-axis)
const T cosA = A.x - ref.x; // |A-ref|.cos(angle between A and positive ref-axis)
const T cosB = B.x - ref.x; // |B-ref|.cos(angle between B and positive ref-axis)
bool hA = ( (sinA < 0) || ((sinA == 0) && (cosA < 0)) ); // 0 for [0,180). 1 for [180,360).
bool hB = ( (sinB < 0) || ((sinB == 0) && (cosB < 0)) ); // 0 for [0,180). 1 for [180,360).
if (hA == hB) {
// |A-ref|.|B-ref|.sin(angle going from (B-ref) to (A-ref))
T sinBA = sinA * cosB - sinB * cosA;
// if T is int, or return value is changed to T, it can be just "return sinBA;"
return ((sinBA > 0) ? 1 : ((sinBA < 0) ? (-1) : 0));
}
return (hA - hB);
}
If S is an array of PointF, and mid is the PointF in the centre:
S = S.OrderBy(s => -Math.Atan2((s.Y - mid.Y), (s.X - mid.X))).ToArray();
will sort the list in order of rotation around mid, starting at the point closest to (-inf,0) and go ccw (clockwise if you leave out the negative sign before Math).