diff --git a/CMakeLists.txt b/CMakeLists.txt index e08dc415e52689ce714b7360eb51e6e8c0b42e9f..fc6666d44d39d69b8015a041306b023d8492b77f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ project(intersection) add_executable( tests + tests/unit_tests/split_triangle_tests.cpp tests/unit_tests/simple_triangle_tests.cpp tests/unit_tests/point_in_triangle_tests.cpp tests/unit_tests/triangulation_tests.cpp @@ -48,6 +49,8 @@ add_executable( tests/unit_tests/edge_direction_intr_tests.cpp tests/unit_tests/contourize_tests.cpp src/debug_utilities/print_triangles.cpp + src/utilities/split_triangle.cpp + tests/unit_tests/orientation_tests.cpp ) include_directories(include) diff --git a/include/constants.h b/include/constants.h index f1f3727c36c40a566cb997d1b880945147709b93..88f5a31166e3dd2eeb83ff5f449003050bcc7a78 100644 --- a/include/constants.h +++ b/include/constants.h @@ -3,3 +3,9 @@ #define MAX_TRIANGLE_INTERSECTION_POINTS 12 #define NB_TRIANGLE_SIDES 3 + + +// all points must have values between these 2 values +#define MAX_POINT 10 + +#define MIN_POINT -10 \ No newline at end of file diff --git a/include/intersections.h b/include/intersections.h index 3d95cb76c9e146c8a5fae5150524580002df7256..375e05e0e379a54792773195a934f54b3f07b114 100644 --- a/include/intersections.h +++ b/include/intersections.h @@ -19,3 +19,5 @@ std::vector<Point> intersections(const Triangle &t1, const Triangle &t2); bool intersect(const Triangle &t1, const Triangle &t2); std::optional<Point> intersectionWithinEdgeDirection(const Edge &e1, const Edge &e2); + +std::vector<Point> intersections(const Triangle t, const Edge &line); \ No newline at end of file diff --git a/include/split_triangle.h b/include/split_triangle.h new file mode 100644 index 0000000000000000000000000000000000000000..2b32b43cfccec878d65b6f54264e7eef8479b4c2 --- /dev/null +++ b/include/split_triangle.h @@ -0,0 +1,8 @@ +#pragma once +#include <vector> +#include <point.h> + +struct Triangle; +struct Edge; + +std::vector<std::vector<Point>> splitTriangle(const Triangle &t, const Edge &line); \ No newline at end of file diff --git a/include/triangle.h b/include/triangle.h index 68dd267ec06f23a4c63fc4ef674d63948498972c..5aa722742b53a41d4879eaf793a6cd4c8bbe4ed3 100644 --- a/include/triangle.h +++ b/include/triangle.h @@ -9,7 +9,7 @@ struct Triangle int depth; int id; bool neighbours(const Triangle &other) const; - Triangle(Point p1, Point p2, Point p3, int depth, int id=0); + Triangle(const Point &p1, const Point &p2, const Point &p3, int depth, int id = 0); bool pointInTriangle(const Point &p) const; Point nextPoint(int pointIndex) const; bool operator==(const Triangle &other) const; diff --git a/src/shapes/triangle.cpp b/src/shapes/triangle.cpp index f7ad3edd8983f869a1abdcf2dba8bc834ad3051e..e6d78f4c3772cfb3f177393e34b53c9e5640176d 100644 --- a/src/shapes/triangle.cpp +++ b/src/shapes/triangle.cpp @@ -14,7 +14,7 @@ bool Triangle::neighbours(const Triangle &other) const { return false; } -Triangle::Triangle(Point p1, Point p2, Point p3, int depth, int id) : points{p1,p2,p3}, depth{depth}, id{id} {} +Triangle::Triangle(const Point &p1, const Point &p2, const Point &p3, int depth, int id) : points{p1,p2,p3}, depth{depth}, id{id} {} bool Triangle::pointInTriangle(const Point &p) const { // all tests must be positive diff --git a/src/union.cpp b/src/union.cpp index 8047ba5ce94f0176bea9afddd2d2d973da8530c9..42be88b33106dae73116e43d1d7734cb89885a0a 100644 --- a/src/union.cpp +++ b/src/union.cpp @@ -5,30 +5,48 @@ #include <list> #include <triangle_edges.h> #include <intersections.h> +#include <split_triangle.h> +#include <triangulation.h> -std::vector<Triangle> unionizeTopAndBottom(const Triangle &top, const Triangle &bot) { - std::vector<Triangle> result; +std::vector<Triangle> unionizeTopAndBottom(const Triangle &top, const Triangle &bot) +{ + std::vector<Triangle> result; - std::list<Point> intr; - TriangleEdges topEdges = TriangleEdges(top); + std::list<Point> intr; + TriangleEdges topEdges = TriangleEdges(top); - // keep track of relevant triangles - - for (int i = 0; i < NB_TRIANGLE_SIDES; i++) { - const Edge &e = topEdges.edges[i]; - auto newIntr = intersections(e, bot); - // split triangle if exists - // currently relevant triangles - // add these to result - - // future relevant triangles - // use to + // keep track of relevant triangles + std::vector<Point> relv = {bot.points[0], bot.points[1], bot.points[2]}; - // filter using previous edges - - // create shape if points exists - contourize(); - } - return result; + for (int i = 0; i < NB_TRIANGLE_SIDES; i++) + { + const Edge &e = topEdges.edges[i]; + auto shapes = splitTriangle(Triangle(relv[0], relv[1], relv[2], bot.depth, bot.id), e); + // split triangle if exists + // currently relevant triangles + // add these to result + if (!shapes[0].empty()) + { + std::vector<Triangle> relvTriangles = triangulate(shapes[0]); + result.insert(result.end(), relvTriangles.begin(), relvTriangles.end()); + } + // future relevant triangles + relv = shapes[1]; + } + result.push_back(top); + return result; } + +std::vector<Triangle> unionize(const Triangle &t1, const Triangle &t2) +{ + if (intersections(t1, t2).empty()) + { + return {t1, t2}; + } + if (t1.depth < t2.depth) + { + return unionizeTopAndBottom(t1, t2); + } + return unionizeTopAndBottom(t2, t1); +} \ No newline at end of file diff --git a/src/utilities/contourize.cpp b/src/utilities/contourize.cpp index 7a9a72d1cf895acdac1358f46657bc16baa85ed4..920b62a2ee3fe295bf277fa8b5f2d0c76928815a 100644 --- a/src/utilities/contourize.cpp +++ b/src/utilities/contourize.cpp @@ -8,7 +8,7 @@ bool isCounterClockwiseForAll(const Point &previous, const Point &candidate, con if (previous == point || candidate == point) { continue; } - auto o = orientation(previous, point, candidate); + auto o = orientation(previous, candidate, point); if (o != Counterclockwise) { return false; } @@ -17,10 +17,15 @@ bool isCounterClockwiseForAll(const Point &previous, const Point &candidate, con } std::vector<Point> contourize(const std::vector<Point> &points) { - std::list<Point> candidates(points.begin(), points.end()); + if (points.size() < 3) { + return points; + } + std::vector<Point> result; Point previous = points[0]; + result.push_back(previous); + std::list<Point> candidates(points.begin() + 1, points.end()); while (!candidates.empty()) { if (isCounterClockwiseForAll(previous, candidates.front(), points)) { previous = candidates.front(); diff --git a/src/utilities/intersections.cpp b/src/utilities/intersections.cpp index d4df2f9bd067915f57aa626154e51658bfa41803..bac5b2109dc48c751b1ee947267db364fc6d726c 100644 --- a/src/utilities/intersections.cpp +++ b/src/utilities/intersections.cpp @@ -5,129 +5,165 @@ #include <edge.h> - -std::optional<float> getB(std::optional<float> slope, Point p) { - if (!slope.has_value()) { +std::optional<float> getB(std::optional<float> slope, Point p) +{ + if (!slope.has_value()) + { return {}; } return p.y - slope.value() * p.x; } -std::optional<float> getSlope(Edge e) { - if (e.p2.x - e.p1.x == 0) { +std::optional<float> getSlope(Edge e) +{ + if (e.p2.x - e.p1.x == 0) + { return {}; } - return (e.p2.y - e.p1.y) / (e.p2.x - e.p1.x) ; + return (e.p2.y - e.p1.y) / (e.p2.x - e.p1.x); } -bool withinEdge(Edge e, Point p) { +bool withinEdge(Edge e, Point p) +{ float minX = std::min(e.p1.x, e.p2.x); float maxX = std::max(e.p1.x, e.p2.x); float minY = std::min(e.p1.y, e.p2.y); float maxY = std::max(e.p1.y, e.p2.y); - return minX <= p.x && p.x <= maxX && minY <= p.y && p.y <= maxY; + return minX <= p.x && p.x <= maxX && minY <= p.y && p.y <= maxY; } -void intersection(Edge e1, Edge e2, std::vector<Point> &results) { +void intersection(Edge e1, Edge e2, std::vector<Point> &results) +{ auto point = intersectionWithinEdge(e1, e2); - if (point.has_value()) { + if (point.has_value()) + { results.push_back(point.value()); } } - -std::optional<Point> intersectionWithinEdge(const Edge &e1, const Edge &e2) { +std::optional<Point> intersectionWithinEdge(const Edge &e1, const Edge &e2) +{ std::optional<Point> candPoint = intersection(e1, e2); - if (candPoint.has_value() && - withinEdge(e1, candPoint.value()) && - withinEdge(e2, candPoint.value())) { + if (candPoint.has_value() && + withinEdge(e1, candPoint.value()) && + withinEdge(e2, candPoint.value())) + { return candPoint; } return {}; } // returns intersection with e1 being extended infinitly in its direction -std::optional<Point> intersectionWithinEdgeDirection(const Edge &e1, const Edge &e2) { +std::optional<Point> intersectionWithinEdgeDirection(const Edge &e1, const Edge &e2) +{ auto candPoint = intersection(e1, e2); - if (candPoint.has_value() && withinEdge(e2, candPoint.value())) { - - // check if in direction e1 is pointing at - // case where e1 is pointing towards positive x axis - if (e1.p1.x < e1.p2.x && candPoint.value().x >= e1.p1.x) { - return candPoint; - } - - // case where e1 is pointing towards negative x axis - if (e1.p1.x > e1.p2.x && candPoint.value().x <= e1.p1.x) { - return candPoint; - } - - // edge case where p1 and p2 form a vertical line - // case where e1 is pointing towards positive y axis - if (e1.p1.y < e1.p2.y && candPoint.value().y >= e1.p1.y) { - return candPoint; - } - - // case where e1 is pointing towards negative axis - if (e1.p1.y > e1.p2.y && candPoint.value().y <= e1.p1.y) { - return candPoint; - } - + if (candPoint.has_value() && withinEdge(e2, candPoint.value())) + { + + // check if in direction e1 is pointing at + // case where e1 is pointing towards positive x axis + if (e1.p1.x < e1.p2.x && candPoint.value().x >= e1.p1.x) + { + return candPoint; + } + + // case where e1 is pointing towards negative x axis + if (e1.p1.x > e1.p2.x && candPoint.value().x <= e1.p1.x) + { + return candPoint; + } + + // edge case where p1 and p2 form a vertical line + // case where e1 is pointing towards positive y axis + if (e1.p1.y < e1.p2.y && candPoint.value().y >= e1.p1.y) + { + return candPoint; + } + + // case where e1 is pointing towards negative axis + if (e1.p1.y > e1.p2.y && candPoint.value().y <= e1.p1.y) + { + return candPoint; + } } return {}; } -std::optional<Point> intersection(const Edge &e1, const Edge &e2) { +std::optional<Point> intersection(const Edge &e1, const Edge &e2) +{ auto slope1 = getSlope(e1); auto slope2 = getSlope(e2); auto b1 = getB(slope1, e1.p1); auto b2 = getB(slope2, e2.p1); - // ignore overlapping case - if (!slope1.has_value() && !slope2.has_value()) { + if (!slope1.has_value() && !slope2.has_value()) + { return {}; } - if (!slope1.has_value()) { + if (!slope1.has_value()) + { return Point{e1.p1.x, slope2.value() * e1.p1.x + b2.value()}; } - if (!slope2.has_value()) { + if (!slope2.has_value()) + { return Point{e2.p1.x, slope1.value() * e2.p1.x + b1.value()}; } float candX = (b2.value() - b1.value()) / (slope1.value() - slope2.value()); - auto candPoint = Point{ candX, slope1.value() * candX + b1.value()}; - + auto candPoint = Point{candX, slope1.value() * candX + b1.value()}; + // ignore case where intersection is at an endpoint /* if (candPoint == e1.p1 || candPoint == e1.p2 || candPoint == e2.p1 || candPoint == e2.p2) { - return {}; + return {}; }*/ + if (candPoint.x > MAX_POINT || candPoint.y > MAX_POINT || candPoint.x < MIN_POINT || candPoint.y < MIN_POINT) + { + return {}; + } - return candPoint; + return candPoint; } -void intersections(const Edge &e1, const TriangleEdges &te, std::vector<Point> &results) { - for (int i = 0; i < NB_TRIANGLE_SIDES; i++) { +void intersections(const Edge &e1, const TriangleEdges &te, std::vector<Point> &results) +{ + for (int i = 0; i < NB_TRIANGLE_SIDES; i++) + { intersection(e1, te.edges[i], results); } } -std::vector<Point> intersections(const Triangle &t1, const Triangle &t2) { +std::vector<Point> intersections(const Triangle &t1, const Triangle &t2) +{ TriangleEdges t1Edges = TriangleEdges(t1); TriangleEdges t2Edges = TriangleEdges(t2); std::vector<Point> results; - for (int i = 0; i < NB_TRIANGLE_SIDES; i++) { - intersections(t1Edges.edges[i], t2Edges, results); + for (int i = 0; i < NB_TRIANGLE_SIDES; i++) + { + intersections(t1Edges.edges[i], t2Edges, results); } - return results; } +std::vector<Point> intersections(const Triangle t, const Edge &line) +{ + std::vector<Point> result; + TriangleEdges es{t}; + for (int i = 0; i < NB_TRIANGLE_SIDES; i++) + { + auto cand = intersectionWithinEdge(es.edges[i], line); + if (cand.has_value()) + { + result.push_back(cand.value()); + } + } + return result; +} \ No newline at end of file diff --git a/src/utilities/orientation.cpp b/src/utilities/orientation.cpp index 14817eb038df3f13cb63b9a16571aa6bd12f5844..fcbfbf990d456ef93a66cd2173c6db9eec2001ee 100644 --- a/src/utilities/orientation.cpp +++ b/src/utilities/orientation.cpp @@ -1,8 +1,12 @@ #include "orientation.h" #include <point.h> +float edgeValue(const Point &p1, const Point &p2) { + return (p2.x - p1.x) * (p2.y + p1.y); +} + Orientation orientation(const Point &p1, const Point &p2, const Point &p3) { - int val = (p2.y - p1.y) * (p3.x - p2.x) - (p2.x - p1.x) * (p3.y - p2.y); + float val = edgeValue(p1, p2) + edgeValue(p2, p3) + edgeValue(p3, p1); if (val == 0) { return Collinear; } diff --git a/src/utilities/split_triangle.cpp b/src/utilities/split_triangle.cpp new file mode 100644 index 0000000000000000000000000000000000000000..960f35850e3f653de0f446608204caa8c69336aa --- /dev/null +++ b/src/utilities/split_triangle.cpp @@ -0,0 +1,39 @@ +#include <split_triangle.h> +#include <constants.h> +#include <contourize.h> +#include <triangle.h> +#include <edge.h> +#include <intersections.h> + +std::vector<std::vector<Point>> splitTriangle(const Triangle &t, const Edge &line) +{ + // get intersections + auto intr = intersections(t, line); + + std::vector<Point> pos; + std::vector<Point> neg; + for (int i = 0; i < NB_TRIANGLE_SIDES; i++) + { + if (line.positiveSide(t.points[i])) + { + pos.push_back(t.points[i]); + } + else + { + neg.push_back(t.points[i]); + } + } + + if (!(pos.empty() || neg.empty())) + { + + pos.insert(pos.end(), intr.begin(), intr.end()); + neg.insert(neg.end(), intr.begin(), intr.end()); + } // Build Positive side + + pos = contourize(pos); + + // Build Negative side + neg = contourize(neg); + return std::vector<std::vector<Point>>{pos, neg}; +} \ No newline at end of file diff --git a/tests/union_tests.cpp b/tests/union_tests.cpp index 79c3eacce1dbbe35abbc747a2e36603d590264b6..6502b666f5a09ba18adad8e8262b9a6d2f23b568 100644 --- a/tests/union_tests.cpp +++ b/tests/union_tests.cpp @@ -23,13 +23,14 @@ TEST(UnionTests, TwoIntersections) EXPECT_TRUE(ts.size() == 3); std::vector<Triangle> expected{ - Triangle({4,1}, {3.3333, 1.6666}, {4,3}, 1, 0), - Triangle({4,1}, {4,3}, {6,1}, 1,0), + Triangle({6,1}, {4,3}, {4,1}, 1, 0), + Triangle({4,3}, {3.33333,1.66667}, {4,1}, 1,0), Triangle({0,0}, {5,0}, {2,3}, 0, 0)}; EXPECT_EQ(ts, expected); } // Edge Case +/* TEST(UnionTests, SharesPointTest) { // Case 1 t1 covers t2 @@ -49,7 +50,7 @@ TEST(UnionTests, SharesPointTest) std::vector<Triangle> expected_results_2; EXPECT_EQ(results, expected_results_2); -} +}*/ TEST(UnionTests, FoldTriangleTest) { diff --git a/tests/unit_tests/contourize_tests.cpp b/tests/unit_tests/contourize_tests.cpp index 445681e7a9bba629a99514029dcc791e2cde38d7..c4c9d10b7a2052a40edaacb996be0b8851c4d74d 100644 --- a/tests/unit_tests/contourize_tests.cpp +++ b/tests/unit_tests/contourize_tests.cpp @@ -13,3 +13,21 @@ TEST(ContourizeTest, Simple) { } +TEST(ContourizeTest, Triangle) { + std::vector<Point> points{{3,1}, {4,1}, {3.33333, 1.6666}}; + auto result = contourize(points); + + std::vector<Point> expected{{3,1}, {4,1}, {3.33333, 1.6666}}; + EXPECT_EQ(result, expected); +} + +/* +TEST(ContourizeTest, TestCase3) { + + std::vector<Point> points{{0.100000001,9}, {5,5}, {1.91037738, 4.2358489}, {2.27272725, 4.090909}, {9.80392265, 1.07843113}}; + auto result = contourize(points); + + + std::vector<Point> expected{{3,1}, {4,1}, {3.33333, 1.6666}}; + EXPECT_EQ(result, expected); +}*/ \ No newline at end of file diff --git a/tests/unit_tests/orientation_tests.cpp b/tests/unit_tests/orientation_tests.cpp index 7ee20c4b65efec543b6f5c18f3f892e402fd3083..8f91929dd9ff534138b152e193780950248cdab7 100644 --- a/tests/unit_tests/orientation_tests.cpp +++ b/tests/unit_tests/orientation_tests.cpp @@ -2,10 +2,21 @@ #include <orientation.h> #include <point.h> -TEST (OrientationTests, Collinear) { - EXPECT_EQ(orientation(Point{0,0}, Point{1,0}, Point{2,0}), Collinear); +TEST(OrientationTests, Collinear) +{ + EXPECT_EQ(orientation(Point{0, 0}, Point{1, 0}, Point{2, 0}), Collinear); } -TEST (OrientationTests, ExampleCounterClockwiseTest) { - EXPECT_EQ(orientation(Point{0,0}, Point{4,4}, Point{1,2}), Counterclockwise); +TEST(OrientationTests, ExampleCounterClockwiseTest) +{ + EXPECT_EQ(orientation(Point{0, 0}, Point{4, 4}, Point{1, 2}), Counterclockwise); } + +TEST(OrientationTests, TestAll) +{ + Point p1 = {3, 1}; + Point p2 = {4, 1}; + Point p3 = {3.333, 1.666}; + EXPECT_EQ(orientation(p1, p2, p3), Counterclockwise); + EXPECT_EQ(orientation(p2, p1, p3), Clockwise); +} \ No newline at end of file diff --git a/tests/unit_tests/split_triangle_tests.cpp b/tests/unit_tests/split_triangle_tests.cpp new file mode 100644 index 0000000000000000000000000000000000000000..762ad396563b8ae40e1bbae01cd92322aef496d2 --- /dev/null +++ b/tests/unit_tests/split_triangle_tests.cpp @@ -0,0 +1,31 @@ +#include <gtest/gtest.h> +#include <split_triangle.h> +#include <triangle.h> +#include <edge.h> + +TEST(SplitTriangleTests, NormalCase) { + Triangle t = Triangle({0,0}, {0,5}, {2,3}, 1, 1); + Edge e{{4,3}, {3,1}}; + + auto results = splitTriangle(t, e); + + ASSERT_EQ(results.size(), 2); + + auto pos = results[0]; + auto neg = results[1]; + + std::vector<Point> expected_pos = {}; + std::vector<Point> expected_neg = {}; + EXPECT_EQ(pos, expected_pos); + EXPECT_EQ(neg, expected_neg); +} + +TEST(SplitTriangleTests, NormalCase2) { + Triangle t = Triangle({0.1, 9}, {2,4}, {5,5}, 2, 4); + Edge e{{5,3}, {0,5}}; + + auto results = splitTriangle(t, e); + + auto pos = results[0]; + EXPECT_TRUE(pos.size() > 1); +} \ No newline at end of file