LCOV - code coverage report
Current view: top level - src/utils/geom - Triangle.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 1.7 % 116 2
Test Date: 2025-12-06 15:35:27 Functions: 9.5 % 21 2

            Line data    Source code
       1              : /****************************************************************************/
       2              : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
       3              : // Copyright (C) 2001-2025 German Aerospace Center (DLR) and others.
       4              : // This program and the accompanying materials are made available under the
       5              : // terms of the Eclipse Public License 2.0 which is available at
       6              : // https://www.eclipse.org/legal/epl-2.0/
       7              : // This Source Code may also be made available under the following Secondary
       8              : // Licenses when the conditions for such availability set forth in the Eclipse
       9              : // Public License 2.0 are satisfied: GNU General Public License, version 2
      10              : // or later which is available at
      11              : // https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
      12              : // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
      13              : /****************************************************************************/
      14              : /// @file    Triangle.cpp
      15              : /// @author  Pablo Alvarez Lopez
      16              : /// @date    Jan 2025
      17              : ///
      18              : // A simple triangle defined in 3D
      19              : /****************************************************************************/
      20              : #include <config.h>
      21              : 
      22              : #include <algorithm>
      23              : #include "Triangle.h"
      24              : 
      25              : 
      26              : // ===========================================================================
      27              : // static member definitions
      28              : // ===========================================================================
      29              : const Triangle Triangle::INVALID = Triangle();
      30              : 
      31              : 
      32              : // ===========================================================================
      33              : // method definitions
      34              : // ===========================================================================
      35        17224 : Triangle::Triangle() {}
      36              : 
      37              : 
      38            0 : Triangle::Triangle(const Position& positionA, const Position& positionB, const Position& positionC) :
      39            0 :     myA(positionA),
      40            0 :     myB(positionB),
      41            0 :     myC(positionC) {
      42              :     // calculate boundary
      43            0 :     myBoundary.add(positionA);
      44            0 :     myBoundary.add(positionB);
      45            0 :     myBoundary.add(positionC);
      46            0 : }
      47              : 
      48              : 
      49        17224 : Triangle::~Triangle() {}
      50              : 
      51              : 
      52              : bool
      53            0 : Triangle::isPositionWithin(const Position& pos) const {
      54            0 :     return isPositionWithin(myA, myB, myC, pos);
      55              : }
      56              : 
      57              : 
      58              : bool
      59            0 : Triangle::isBoundaryFullWithin(const Boundary& boundary) const {
      60            0 :     return isPositionWithin(Position(boundary.xmax(), boundary.ymax())) &&
      61            0 :            isPositionWithin(Position(boundary.xmin(), boundary.ymin())) &&
      62            0 :            isPositionWithin(Position(boundary.xmax(), boundary.ymin())) &&
      63            0 :            isPositionWithin(Position(boundary.xmin(), boundary.ymax()));
      64              : }
      65              : 
      66              : 
      67              : bool
      68            0 : Triangle::intersectWithShape(const PositionVector& shape) const {
      69            0 :     return intersectWithShape(shape, shape.getBoxBoundary());
      70              : }
      71              : 
      72              : 
      73              : bool
      74            0 : Triangle::intersectWithShape(const PositionVector& shape, const Boundary& shapeBoundary) const {
      75              :     // check if triangle is within shape
      76            0 :     if (shape.around(myA) || shape.around(myB) || shape.around(myC)) {
      77            0 :         return true;
      78              :     }
      79              :     // check if at leas two corners of the shape boundary are within triangle
      80            0 :     const int cornerA = isPositionWithin(Position(shapeBoundary.xmax(), shapeBoundary.ymax()));
      81            0 :     const int cornerB = isPositionWithin(Position(shapeBoundary.xmin(), shapeBoundary.ymin()));
      82            0 :     if ((cornerA + cornerB) == 2) {
      83              :         return true;
      84              :     }
      85            0 :     const int cornerC = isPositionWithin(Position(shapeBoundary.xmax(), shapeBoundary.ymin()));
      86            0 :     if ((cornerA + cornerB + cornerC) == 2) {
      87              :         return true;
      88              :     }
      89            0 :     const int cornerD = isPositionWithin(Position(shapeBoundary.xmin(), shapeBoundary.ymax()));
      90            0 :     if ((cornerA + cornerB + cornerC + cornerD) == 2) {
      91              :         return true;
      92              :     }
      93              :     // on this point, whe need to check if every shape line intersect with triangle
      94            0 :     for (int i = 0; i < ((int)shape.size() - 1); i++) {
      95            0 :         if (lineIntersectsTriangle(shape[i], shape[i + 1])) {
      96              :             return true;
      97              :         }
      98              :     }
      99              :     return false;
     100              : }
     101              : 
     102              : 
     103              : bool
     104            0 : Triangle::intersectWithCircle(const Position& center, const double radius) const {
     105            0 :     const auto squaredRadius = radius * radius;
     106            0 :     return ((center.distanceSquaredTo2D(myA) <= squaredRadius) ||
     107            0 :             (center.distanceSquaredTo2D(myB) <= squaredRadius) ||
     108            0 :             (center.distanceSquaredTo2D(myC) <= squaredRadius) ||
     109            0 :             isPositionWithin(center) ||
     110            0 :             lineIntersectCircle(myA, myB, center, radius) ||
     111            0 :             lineIntersectCircle(myB, myC, center, radius) ||
     112            0 :             lineIntersectCircle(myC, myA, center, radius));
     113              : }
     114              : 
     115              : 
     116              : const Boundary&
     117            0 : Triangle::getBoundary() const {
     118            0 :     return myBoundary;
     119              : }
     120              : 
     121              : 
     122              : const PositionVector
     123            0 : Triangle::getShape() const {
     124            0 :     return PositionVector({myA, myB, myC});
     125              : }
     126              : 
     127              : 
     128              : std::vector<Triangle>
     129            0 : Triangle::triangulate(PositionVector shape) {
     130              :     std::vector<Triangle> triangles;
     131              :     // first open polygon
     132            0 :     shape.openPolygon();
     133              :     // we need at leas three vertex
     134            0 :     if (shape.size() >= 3) {
     135              :         // greedy algorithm
     136            0 :         while (shape.size() > 3) {
     137            0 :             int shapeSize = (int)shape.size();
     138              :             int earIndex = -1;
     139              :             // first find an "ear"
     140            0 :             for (int i = 0; (i < shapeSize) && (earIndex == -1); i++) {
     141            0 :                 const auto& earA = shape[(i + shapeSize - 1) % shapeSize];
     142            0 :                 const auto& earB = shape[i];
     143            0 :                 const auto& earC = shape[(i + 1) % shapeSize];
     144            0 :                 if (isEar(earA, earB, earC, shape)) {
     145              :                     earIndex = i;
     146              :                 }
     147              :             }
     148            0 :             if (earIndex != -1) {
     149            0 :                 triangles.push_back(Triangle(
     150            0 :                                         shape[(earIndex + shapeSize - 1) % shapeSize],
     151            0 :                                         shape[earIndex],
     152            0 :                                         shape[(earIndex + 1) % shapeSize])
     153              :                                    );
     154            0 :                 shape.erase(shape.begin() + earIndex);
     155              :             } else {
     156              :                 // simply remove the first three
     157            0 :                 triangles.push_back(Triangle(shape[0], shape[1], shape[2]));
     158            0 :                 shape.erase(shape.begin() + 1);
     159              :             }
     160              :         }
     161              :         // add last triangle
     162            0 :         triangles.push_back(Triangle(shape[0], shape[1], shape[2]));
     163              :     }
     164            0 :     return triangles;
     165            0 : }
     166              : 
     167              : 
     168              : bool
     169            0 : Triangle::operator==(const Triangle& other) const {
     170            0 :     return myA == other.myA && myB == other.myB && myC == other.myC;
     171              : }
     172              : 
     173              : 
     174              : bool
     175            0 : Triangle::operator!=(const Triangle& other) const {
     176            0 :     return !(*this == other);
     177              : }
     178              : 
     179              : 
     180              : bool
     181            0 : Triangle::isPositionWithin(const Position& A, const Position& B, const Position& C, const Position& pos) {
     182              :     // Calculate cross products for each edge of the triangle
     183            0 :     const double crossAB = crossProduct(A, B, pos);
     184            0 :     const double crossBC = crossProduct(B, C, pos);
     185            0 :     const double crossCA = crossProduct(C, A, pos);
     186              :     // Check if all cross products have the same sign
     187            0 :     return ((crossAB >= 0) && (crossBC >= 0) && (crossCA >= 0)) ||
     188            0 :            ((crossAB <= 0) && (crossBC <= 0) && (crossCA <= 0));
     189              : }
     190              : 
     191              : 
     192              : bool
     193            0 : Triangle::isEar(const Position& a, const Position& b, const Position& c, const PositionVector& shape) {
     194              :     // Check if triangle ABC is counter-clockwise
     195            0 :     if (crossProduct(a, b, c) <= 0) {
     196              :         return false;
     197              :     }
     198              :     // Check if any other point in the polygon lies inside the triangle
     199            0 :     for (const auto& pos : shape) {
     200            0 :         if ((pos != a) && (pos != b) && (pos != c) && isPositionWithin(a, b, c, pos)) {
     201              :             return false;
     202              :         }
     203              :     }
     204              :     return true;
     205              : }
     206              : 
     207              : 
     208              : double
     209            0 : Triangle::crossProduct(const Position& a, const Position& b, const Position& c) {
     210            0 :     return (b.x() - a.x()) * (c.y() - a.y()) - (b.y() - a.y()) * (c.x() - a.x());
     211              : }
     212              : 
     213              : 
     214              : int
     215            0 : Triangle::orientation(const Position& p, const Position& q, const Position& r) const {
     216            0 :     const double val = (q.y() - p.y()) * (r.x() - q.x()) - (q.x() - p.x()) * (r.y() - q.y());
     217            0 :     if (val > 0) {
     218              :         // Clockwise
     219              :         return 1;
     220            0 :     } else if (val < 0) {
     221              :         // Counterclockwise
     222              :         return -1;
     223              :     } else {
     224              :         // Collinear
     225            0 :         return 0;
     226              :     }
     227              : }
     228              : 
     229              : 
     230              : bool
     231            0 : Triangle::onSegment(const Position& p, const Position& q, const Position& r) const {
     232            0 :     return (q.x() >= std::min(p.x(), r.x()) && q.x() <= std::max(p.x(), r.x()) &&
     233            0 :             q.y() >= std::min(p.y(), r.y()) && q.y() <= std::max(p.y(), r.y()));
     234              : }
     235              : 
     236              : 
     237              : bool
     238            0 : Triangle::segmentsIntersect(const Position& p1, const Position& q1, const Position& p2, const Position& q2) const {
     239            0 :     const int o1 = orientation(p1, q1, p2);
     240            0 :     const int o2 = orientation(p1, q1, q2);
     241            0 :     const int o3 = orientation(p2, q2, p1);
     242            0 :     const int o4 = orientation(p2, q2, q1);
     243              :     // General case: segments intersect if they have different orientations
     244              :     // Special cases: checking if points are collinear and on segment
     245            0 :     if ((o1 != o2) && (o3 != o4)) {
     246              :         return true;
     247            0 :     } else if ((o1 == 0) && onSegment(p1, p2, q1)) {
     248              :         return true;
     249            0 :     } else if ((o2 == 0) && onSegment(p1, q2, q1)) {
     250              :         return true;
     251            0 :     } else if ((o3 == 0) && onSegment(p2, p1, q2)) {
     252              :         return true;
     253            0 :     } else if ((o4 == 0) && onSegment(p2, q1, q2)) {
     254              :         return true;
     255              :     } else {
     256            0 :         return false;
     257              :     }
     258              : }
     259              : 
     260              : 
     261              : bool
     262            0 : Triangle::lineIntersectsTriangle(const Position& p1, const Position& p2) const {
     263            0 :     return segmentsIntersect(p1, p2, myA, myB) ||
     264            0 :            segmentsIntersect(p1, p2, myB, myC) ||
     265            0 :            segmentsIntersect(p1, p2, myC, myA);
     266              : }
     267              : 
     268              : 
     269              : bool
     270            0 : Triangle::lineIntersectCircle(const Position& posA, const Position& posB, const Position& center, const double radius) const {
     271              :     // Calculate coefficients of the quadratic equation
     272            0 :     const double dx = posB.x() - posA.x();
     273            0 :     const double dy = posB.y() - posA.y();
     274            0 :     const double a = dx * dx + dy * dy;
     275            0 :     const double b = 2 * (dx * (posA.x() - center.x()) + dy * (posA.y() - center.y()));
     276            0 :     const double c = (posA.x() - center.x()) * (posA.x() - center.x()) + (posA.y() - center.y()) * (posA.y() - center.y()) - radius * radius;
     277              :     // Calculate the discriminant
     278            0 :     const double discriminant = (b * b - 4 * a * c);
     279              :     // Check the discriminant to determine the intersection
     280            0 :     if (discriminant >= 0) {
     281              :         // Calculate the two possible values of t
     282            0 :         const double sqrtDiscriminant = sqrt(discriminant);
     283            0 :         const double t1 = (-b + sqrtDiscriminant) / (2 * a);
     284            0 :         const double t2 = (-b - sqrtDiscriminant) / (2 * a);
     285              :         // if at least t1 or t2 is between [0,1], then intersect
     286            0 :         return (t1 >= 0 && t1 <= 1) || (t2 >= 0 && t2 <= 1);
     287              :     } else {
     288              :         return false;
     289              :     }
     290              : }
     291              : 
     292              : /****************************************************************************/
        

Generated by: LCOV version 2.0-1