/* * This file is part of INAV. * * INAV 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 3 of the License, or * (at your option) any later version. * * INAV 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 INAV. If not, see . */ #pragma once #include #include #include #include "common/vector.h" #include "navigation/navigation_private.h" #define K_EPSILON 1e-8 static bool isPointInCircle(const fpVector2_t *point, const fpVector2_t *center, const float radius) { return calculateDistance2(point, center) < radius; } static bool isPointInPloygon(const fpVector2_t *point, fpVector2_t* vertices, const uint8_t verticesNum) { bool result = false; fpVector2_t *p1, *p2; fpVector2_t* prev = &vertices[verticesNum - 1]; fpVector2_t *current; for (uint8_t i = 0; i < verticesNum; i++) { current = &vertices[i]; if (current->x > prev->x) { p1 = prev; p2 = current; } else { p1 = current; p2 = prev; } if ((current->x < point->x) == (point->x <= prev->x) && (point->y - p1->y) * (p2->x - p1->x) < (p2->y - p1->y) * (point->x - p1->x)) { result = !result; } prev = current; } return result; } static float angelFromSideLength(const float a, const float b, const float c) { return acos_approx((sq(b) + sq(c) - sq(a)) / (2 * b * c)); } static bool isPointRightFromLine(const fpVector2_t *lineStart, const fpVector2_t *lineEnd, const fpVector2_t *point) { return (lineEnd->x - lineStart->x) * (point->y - lineStart->y) - (lineEnd->y - lineStart->y) * (point->x - lineStart->x) > 0; } static float calcAngelFrom3Points(const fpVector2_t* a, const fpVector2_t* b, const fpVector2_t* c) { float ab = calculateDistance2(a, b); float ac = calculateDistance2(a, c); float bc = calculateDistance2(b, c); return RADIANS_TO_DEGREES(angelFromSideLength(ab, ac, bc)); } static void calcPointOnLine(fpVector2_t *result, const fpVector2_t *start, fpVector2_t *end, float distance) { fpVector2_t dir, a; float fac; vector2Sub(&dir, end, start); fac = distance / fast_fsqrtf(vector2NormSquared(&dir)); vector2Scale(&a, &dir, fac); vector2Add(result, start, &a); } // Intersection of two lines https://en.wikipedia.org/wiki/Line-line_intersection bool calcLineLineIntersection(fpVector2_t* intersection, const fpVector2_t* line1Start, const fpVector2_t* line1End, const fpVector2_t* line2Start, const fpVector2_t* line2End, bool isSegment) { intersection->x = intersection->y = 0; // Double precision is needed here double s1 = line1End->x - line1Start->x; double t1 = -(line2End->x - line2Start->x); double r1 = line2Start->x - line1Start->x; double s2 = line1End->y - line1Start->y; double t2 = -(line2End->y - line2Start->y); double r2 = line2Start->y - line1Start->y; // Use Cramer's rule for the solution of the system of linear equations double determ = s1 * t2 - t1 * s2; if (determ == 0) { // No solution return false; } double s0 = (r1 * t2 - t1 * r2) / determ; double t0 = (s1 * r2 - r1 * s2) / determ; if (s0 == 0 && t0 == 0) { return false; } // No intersection if (isSegment && (s0 <= 0 || s0 >= 1 || t0 <= 0 || t0 >= 1)) { return false; } intersection->x = (line1Start->x + (float)s0 * (line1End->x - line1Start->x)); intersection->y = (line1Start->y + (float)s0 * (line1End->y - line1Start->y)); return true; } float calculateDistance3(const fpVector3_t* startPos, const fpVector3_t* destinationPos) { return fast_fsqrtf(sq(destinationPos->x - startPos->x) + sq(destinationPos->y - startPos->y) + sq(destinationPos->z - startPos->z)); } static fpVector3_t calcPlaneNormalFromPoints(const fpVector3_t* p1, const fpVector3_t* p2, const fpVector3_t* p3) { fpVector3_t result, a, b; vectorSub(&a, p2, p1); vectorSub(&b, p3, p1); vectorCrossProduct(&result, &a, &b); return result; } static fpVector3_t calcDirVectorFromPoints(const fpVector3_t* p1, const fpVector3_t* p2) { fpVector3_t result; vectorSub(&result, p1, p2); return result; } static void generatePolygonFromCircle(fpVector2_t* polygon, const fpVector2_t* center, const float radius, const uint8_t sides) { for (uint8_t i = 0, j = sides -1; i < sides; i++, j--) { polygon[i].x = radius * cos_approx(2 * M_PIf * j / sides) + center->x; polygon[i].y = radius * sin_approx(2 * M_PIf * j / sides) + center->y; } } // TRUE if point is in the same direction from pos as ref static bool isInFront(const fpVector3_t *pos, const fpVector3_t *point, const fpVector3_t *ref) { fpVector3_t refDir = calcDirVectorFromPoints(pos, ref); fpVector3_t dir = calcDirVectorFromPoints(point, pos); return vectorDotProduct(&refDir, &dir) < 0.0f; } static fpVector2_t calcNearestPointOnLine(const fpVector2_t *lineStart, const fpVector2_t *lineEnd, const fpVector2_t *point) { fpVector2_t ap, ab, prod2, result; float distance, magAb, prod; vector2Sub(&ap, point, lineStart); vector2Sub(&ab, lineEnd, lineStart); magAb = vector2NormSquared(&ab); prod = vector2DotProduct(&ap, &ab); distance = prod / magAb; if (distance < 0) { return *lineStart; } else if (distance > 1) { return *lineEnd; } vector2Scale(&prod2, &ab, distance); vector2Add(&result, lineStart, &prod2); return result; } static bool isPointOnLine2(const fpVector2_t *lineStart, const fpVector2_t *lineEnd, const fpVector2_t *linepoint) { float a = roundf(calculateDistance2(linepoint, lineStart)); float b = roundf(calculateDistance2(linepoint, lineEnd)); float c = roundf(calculateDistance2(lineStart, lineEnd)); return ABS(a + b - c) <= 1; } static bool isPointOnLine3(const fpVector3_t *lineStart, const fpVector3_t *lineEnd, const fpVector3_t *linepoint) { float a = roundf(calculateDistance3(linepoint, lineStart)); float b = roundf(calculateDistance3(linepoint, lineEnd)); float c = roundf(calculateDistance3(lineStart, lineEnd)); return ABS(a + b - c) <= 1; } static bool calcIntersectionLinePlane(fpVector3_t* result, const fpVector3_t* lineVector, const fpVector3_t* linePoint, const fpVector3_t* planeNormal, const fpVector3_t* planePoint) { if (vectorDotProduct(linePoint, planeNormal) == 0) { return false; } fpVector3_t diff, p1, p4; float p2 = 0, p3 = 0; vectorSub(&diff, linePoint, planePoint); vectorAdd(&p1, &diff, planePoint); p2 = vectorDotProduct(&diff, planeNormal); p3 = vectorDotProduct(lineVector, planeNormal); vectorScale(&p4, lineVector, -p2 / p3); vectorAdd(result, &p1, &p4); return true; } // Möller–Trumbore intersection algorithm static bool calcLineTriangleIntersection(fpVector3_t *intersection, const fpVector3_t* org, const fpVector3_t* dir, const fpVector3_t* v0, const fpVector3_t* v1, const fpVector3_t* v2) { fpVector3_t v0v1, v0v2, pvec, tvec, quvec, prod; float det, invDet, t, u, v; vectorSub(&v0v1, v1, v0); vectorSub(&v0v2, v2, v0); vectorCrossProduct(&pvec, dir, &v0v2); det = vectorDotProduct(&v0v1, &pvec); if (fabsf(det) < K_EPSILON) { return false; } invDet = 1.f / det; vectorSub(&tvec, org, v0); u = vectorDotProduct(&tvec, &pvec) * invDet; if (u < 0 || u > 1) { return false; } vectorCrossProduct(&quvec, &tvec, &v0v1); v = vectorDotProduct(dir, &quvec) * invDet; if (v < 0 || u + v > 1) { return false; } t = vectorDotProduct(&v0v2, &quvec) * invDet; vectorScale(&prod, dir, t); vectorAdd(intersection, &prod, org); return true; } static bool calcLinePolygonIntersection(fpVector3_t *intersect, const fpVector3_t *pos, const fpVector3_t *pos2, const float height, fpVector2_t* vertices, const uint8_t verticesNum) { if (verticesNum < 3) { return false; } fpVector3_t p1 = { .x = vertices[0].x, .y = vertices[0].y, .z = height }; fpVector3_t p2 = { .x = vertices[1].x, .y = vertices[1].y, .z = height }; fpVector3_t p3 = { .x = vertices[2].x, .y = vertices[2].y, .z = height }; fpVector3_t normale = calcPlaneNormalFromPoints(&p1, &p2, &p3); fpVector3_t dir = calcDirVectorFromPoints(pos, pos2); fpVector3_t tmp; if (calcIntersectionLinePlane(&tmp, &dir, pos2, &normale, &p1)) { if (isPointInPloygon((fpVector2_t*)&tmp, vertices, verticesNum)) { memcpy(intersect, &tmp, sizeof(fpVector3_t)); return true; } } return false; } static bool calcLineCircleIntersection(fpVector2_t *intersection1, fpVector2_t *intersection2, const fpVector2_t *startPos, const fpVector2_t *endPos, const fpVector2_t *circleCenter, const float radius) { // Unfortunately, we need double precision here double slope = (double)((endPos->y - startPos->y) / (endPos->x - startPos->x)); double yIntercept = (double)startPos->y - slope * (double)startPos->x; double a = (double)1.0 + sq(slope); double b = (double)-2.0 * (double)circleCenter->x + (double)2.0 * slope * (yIntercept - (double)circleCenter->y); double c = sq((double)circleCenter->x) + (yIntercept - (double)circleCenter->y) * (yIntercept - (double)circleCenter->y) - sq((double)radius); double discr = sq(b) - (double)4.0 * a * c; if (discr > 0) { double x1 = (-b + (double)fast_fsqrtf(discr)) / ((double)2.0 * a); double y1 = slope * x1 + yIntercept; double x2 = (-b - (double)fast_fsqrtf(discr)) / ((double)2.0 * a); double y2 = slope * x2 + yIntercept; intersection1->x = (float)x1; intersection1->y = (float)y1; intersection2->x = (float)x2; intersection2->y = (float)y2; return true; } return false; } static void generateOffsetPolygon(fpVector2_t* verticesNew, fpVector2_t* verticesOld, const uint8_t numVertices, const float offset) { fpVector2_t* prev = &verticesOld[numVertices - 1]; fpVector2_t* current; fpVector2_t* next; for (uint8_t i = 0; i < numVertices; i++) { current = &verticesOld[i]; if (i + 1 < numVertices) { next = &verticesOld[i + 1]; } else { next = &verticesOld[0]; } fpVector2_t v, vn, vs, pcp1, pcp2, pcn1, pcn2, intersect; vector2Sub(&v, current, prev); vector2Normalize(&vn, &v); vector2Scale(&vs, &vn, offset); pcp1.x = prev->x - vs.y; pcp1.y = prev->y + vs.x; pcp2.x = current->x - vs.y; pcp2.y = current->y + vs.x; vector2Sub(&v, next, current); vector2Normalize(&vn, &v); vector2Scale(&vs, &vn, offset); pcn1.x = current->x - vs.y; pcn1.y = current->y + vs.x; pcn2.x = next->x - vs.y; pcn2.y = next->y + vs.x; if (calcLineLineIntersection(&intersect, &pcp1, &pcp2, &pcn1, &pcn2, false)) { verticesNew[i].x = intersect.x; verticesNew[i].y = intersect.y; } prev = current; } } // Calculates the nearest intersection point // Inspired by raytracing algortyhms static bool calcLineCylinderIntersection(fpVector3_t* intersection, float* distance, const fpVector3_t* startPos, const fpVector3_t* endPos, const fpVector3_t* circleCenter, const float radius, const float height, const bool inside) { float distToIntersection = FLT_MAX; fpVector3_t intersect; fpVector2_t i1, i2; if (!calcLineCircleIntersection(&i1, &i2, (fpVector2_t*)startPos, (fpVector2_t*)endPos, (fpVector2_t*)circleCenter, radius)) { return false; } if (calculateDistance2((fpVector2_t*)startPos, &i1) < calculateDistance2((fpVector2_t*)startPos, &i2)) { intersect.x = i1.x; intersect.y = i1.y; } else { intersect.x = i2.x; intersect.y = i2.y; } float dist1 = calculateDistance2((fpVector2_t*)endPos, (fpVector2_t*)&intersect); float dist2 = calculateDistance2((fpVector2_t*)startPos, (fpVector2_t*)endPos); fpVector2_t p4, p5, p6, p7; p4.x = 0; p4.y = endPos->z; p5.x = dist2; p5.y = startPos->z; p6.x = dist1; p6.y = circleCenter->z; p7.x = dist1; p7.y = circleCenter->z + height; fpVector2_t heightIntersection; if (calcLineLineIntersection(&heightIntersection, &p4, &p5, &p6, &p7, true)) { intersect.z = heightIntersection.y; if (isInFront(startPos, &intersect, endPos)) { distToIntersection = calculateDistance3(startPos, &intersect); } } fpVector3_t intersectCap; fpVector3_t dir = calcDirVectorFromPoints(startPos, endPos); if (startPos->z < circleCenter->z || (inside && circleCenter->z != 0)) { fpVector3_t p1 = *circleCenter; p1.x = circleCenter->x + radius; fpVector3_t p2 = *circleCenter; p2.y = circleCenter->y + radius; fpVector3_t normal = calcPlaneNormalFromPoints(circleCenter, &p1, &p2); if (calcIntersectionLinePlane(&intersectCap, &dir, endPos, &normal, circleCenter) && isPointInCircle((fpVector2_t*)&intersectCap, (fpVector2_t*)circleCenter, radius) && isInFront(startPos, &intersectCap, endPos)) { float distanceCap = calculateDistance3(startPos, &intersectCap); if (distanceCap < distToIntersection) { distToIntersection = distanceCap; intersect = intersectCap; } } } if (startPos->z > circleCenter->z + height || inside) { fpVector3_t p1 = { .x = circleCenter->x + radius, .y = circleCenter->y, .z = circleCenter->z + height }; fpVector3_t p2 = { .x = circleCenter->x, .y = circleCenter->y + radius, .z = circleCenter->z + height }; fpVector3_t p3 = *circleCenter; p3.z = circleCenter->z + height; fpVector3_t normal = calcPlaneNormalFromPoints(&p3, &p1, &p2); if (calcIntersectionLinePlane(&intersectCap, &dir, startPos, &normal, &p3) && isPointInCircle((fpVector2_t*)&intersectCap, (fpVector2_t*)circleCenter, radius) && isInFront(startPos, &intersectCap, endPos)) { float distanceCap = calculateDistance3(startPos, &intersectCap); if (distanceCap < distToIntersection) { distToIntersection = distanceCap; intersect = intersectCap; } } } if (distToIntersection < FLT_MAX) { *distance = distToIntersection; memcpy(intersection, &intersect, sizeof(fpVector3_t)); return true; } return false; } static bool calcLine3dPolygonIntersection(fpVector3_t *intersection, float *distance, const fpVector3_t *startPos, const fpVector3_t *endPos, fpVector2_t *vertices, const uint8_t numVertices, const float minHeight, const float maxHeight, const bool isInclusiveZone) { float distToIntersection = FLT_MAX; fpVector3_t intersect; fpVector2_t* prev = &vertices[numVertices - 1]; fpVector2_t* current; for (uint8_t i = 0; i < numVertices; i++) { current = &vertices[i]; fpVector3_t p1 = { .x = prev->x, .y = prev->y, .z = minHeight }; fpVector3_t p2 = { .x = prev->x, .y = prev->y, .z = maxHeight }; fpVector3_t p3 = { .x = current->x, .y = current->y, .z = minHeight }; fpVector3_t p4 = { .x = current->x, .y = current->y, .z = maxHeight}; fpVector3_t dir = calcDirVectorFromPoints(startPos, endPos); fpVector3_t intersectCurrent; if ((calcLineTriangleIntersection(&intersectCurrent, startPos, &dir, &p1, &p2, &p3) || calcLineTriangleIntersection(&intersectCurrent, startPos, &dir, &p3, &p4, &p2)) && isInFront(startPos, &intersectCurrent, endPos) ) { float distWall = calculateDistance3(startPos, &intersectCurrent); if (distWall < distToIntersection) { distToIntersection = distWall; intersect = intersectCurrent; } } prev = current; } fpVector3_t intersectCap; if (startPos->z < minHeight || (isInclusiveZone && minHeight != 0)) { if (calcLinePolygonIntersection(&intersectCap, startPos, endPos, minHeight, vertices, numVertices) && isInFront(startPos, &intersectCap, endPos)) { float distanceCap = calculateDistance3(startPos, &intersectCap); if (distanceCap < distToIntersection) { distToIntersection = distanceCap; intersect = intersectCap; } } } if (startPos->z > maxHeight || isInclusiveZone) { if (calcLinePolygonIntersection(&intersectCap, startPos, endPos, maxHeight, vertices, numVertices) && isInFront(startPos, &intersectCap, endPos)) { float distanceCap = calculateDistance3(startPos, &intersectCap); if (distanceCap < distToIntersection) { distToIntersection = distanceCap; intersect = intersectCap; } } } if (distToIntersection < FLT_MAX) { *distance = distToIntersection; memcpy(intersection, &intersect, sizeof(fpVector3_t)); return true; } return false; }