Skip to content

Commit

Permalink
Merge pull request #399 from GSharker/mibi/dev/area-of-triangle
Browse files Browse the repository at this point in the history
Area of a triangle
  • Loading branch information
sonomirco committed Feb 4, 2023
2 parents f01109a + 20ad4fb commit 121b6ac
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 106 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/create-release.yml
Expand Up @@ -21,8 +21,6 @@ jobs:
permissions:
contents: write
pull-requests: read
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: ubuntu-latest
steps:
- name: 🛎 Checkout repo
Expand All @@ -31,8 +29,10 @@ jobs:
fetch-depth: 0

- name: 👶 Draft release
uses: release-drafter/release-drafter@v5
id: release
uses: release-drafter/release-drafter@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: 🚧 Setup .NET Core
uses: actions/setup-dotnet@v3
Expand Down
128 changes: 71 additions & 57 deletions src/GShark.Test.XUnit/Core/TrigonometryTests.cs
@@ -1,72 +1,86 @@
using FluentAssertions;
using System.Collections.Generic;
using FluentAssertions;
using GShark.Core;
using GShark.Geometry;
using System.Collections.Generic;
using GShark.Operation;
using Xunit;

namespace GShark.Test.XUnit.Core
namespace GShark.Test.XUnit.Core;

public class TrigonometryTests
{
public class TrigonometryTests
[Fact]
public void It_Returns_True_If_Points_Are_Planar()
{
// Arrange
Point3 pt1 = new Point3(0.0, 0.0, 0.0);
Point3 pt2 = new Point3(10.0, 0.0, 0.0);
Point3 pt3 = new Point3(5.0, 5.0, 0.0);
Point3 pt4 = new Point3(-5.0, -15.0, 0.0);
List<Point3> points = new List<Point3> {pt1, pt2, pt3, pt4};

// Arrange
Trigonometry.ArePointsCoplanar(points).Should().BeTrue();
}

[Fact]
public void It_Returns_True_If_Three_Points_Are_Collinear()
{
[Fact]
public void It_Returns_True_If_Points_Are_Planar()
{
// Arrange
Point3 pt1 = new Point3(0.0, 0.0, 0.0);
Point3 pt2 = new Point3(10.0, 0.0, 0.0);
Point3 pt3 = new Point3(5.0, 5.0, 0.0);
Point3 pt4 = new Point3(-5.0, -15.0, 0.0);
List<Point3> points = new List<Point3> { pt1, pt2, pt3, pt4 };
// Arrange
Point3 pt1 = new Point3(25.923, 27.057, 0.0);
Point3 pt2 = new Point3(35.964, 31.367, 0.0);
Point3 pt3 = new Point3(51.299, 37.950, 0.0);

// Assert
Trigonometry.ArePointsCollinear(pt1, pt2, pt3).Should().BeTrue();
}

// Arrange
Trigonometry.ArePointsCoplanar(points).Should().BeTrue();
}
[Fact]
public void It_Returns_False_If_Three_Points_Are_Not_Collinear()
{
// Arrange
Point3 pt1 = new Point3(25.923, 27.057, 0.0);
Point3 pt2 = new Point3(35.964, 20.451, 0.0);
Point3 pt3 = new Point3(51.299, 37.950, 0.0);

[Fact]
public void It_Returns_True_If_Three_Points_Are_Collinear()
{
// Arrange
Point3 pt1 = new Point3(25.923, 27.057, 0.0);
Point3 pt2 = new Point3(35.964, 31.367, 0.0);
Point3 pt3 = new Point3(51.299, 37.950, 0.0);
// Assert
Trigonometry.ArePointsCollinear(pt1, pt2, pt3).Should().BeFalse();
}

// Assert
Trigonometry.ArePointsCollinear(pt1, pt2, pt3).Should().BeTrue();
}
[Theory]
[InlineData(new double[] {5, 7, 0}, new double[] {6, 6, 0}, 0.2)]
[InlineData(new double[] {7, 6, 0}, new[] {6.5, 6.5, 0}, 0.3)]
[InlineData(new double[] {5, 9, 0}, new double[] {7, 7, 0}, 0.4)]
public void It_Returns_The_Closest_Point_On_A_Segment(double[] ptToCheck, double[] ptExpected, double tValExpected)
{
// Arrange
// We are not passing a segment like a line or ray but the part that compose the segment,
// t values [0 and 1] and start and end point.
var testPt = new Point3(ptToCheck[0], ptToCheck[1], ptToCheck[2]);
var expectedPt = new Point3(ptExpected[0], ptExpected[1], ptExpected[2]);
Point3 pt0 = new Point3(5, 5, 0);
Point3 pt1 = new Point3(10, 10, 0);

[Fact]
public void It_Returns_False_If_Three_Points_Are_Not_Collinear()
{
// Arrange
Point3 pt1 = new Point3(25.923, 27.057, 0.0);
Point3 pt2 = new Point3(35.964, 20.451, 0.0);
Point3 pt3 = new Point3(51.299, 37.950, 0.0);
// Act
(double tValue, Point3 pt) closestPt = Trigonometry.ClosestPointToSegment(testPt, pt0, pt1, 0, 1);

// Assert
Trigonometry.ArePointsCollinear(pt1, pt2, pt3).Should().BeFalse();
}
// Assert
closestPt.tValue.Should().BeApproximately(tValExpected, GSharkMath.MaxTolerance);
closestPt.pt.EpsilonEquals(expectedPt, GSharkMath.Epsilon).Should().BeTrue();
}

[Theory]
[InlineData(new double[] { 5, 7, 0 }, new double[] { 6, 6, 0 }, 0.2)]
[InlineData(new double[] { 7, 6, 0 }, new double[] { 6.5, 6.5, 0 }, 0.3)]
[InlineData(new double[] { 5, 9, 0 }, new double[] { 7, 7, 0 }, 0.4)]
public void It_Returns_The_Closest_Point_On_A_Segment(double[] ptToCheck, double[] ptExpected, double tValExpected)
{
// Arrange
// We are not passing a segment like a line or ray but the part that compose the segment,
// t values [0 and 1] and start and end point.
var testPt = new Point3(ptToCheck[0], ptToCheck[1], ptToCheck[2]);
var expectedPt = new Point3(ptExpected[0], ptExpected[1], ptExpected[2]);
Point3 pt0 = new Point3(5, 5, 0);
Point3 pt1 = new Point3(10, 10, 0);
[Fact]
public void It_Returns_The_Area_Of_A_Triangle_From_A_Polyline()
{
// Arrange
Point3 pt1 = new Point3(-4.27, 5.90, 5.76);
Point3 pt2 = new Point3(-10, -7.69, 0.0);
Point3 pt3 = new Point3(9.93, -2.65, -5.57);
double expectedArea = 150.865499;

// Act
(double tValue, Point3 pt) closestPt = Trigonometry.ClosestPointToSegment(testPt, pt0, pt1, 0, 1);
// Act
double area = Trigonometry.AreaOfTriangle(pt1, pt2, pt3);

// Assert
closestPt.tValue.Should().BeApproximately(tValExpected, GSharkMath.MaxTolerance);
closestPt.pt.EpsilonEquals(expectedPt, GSharkMath.Epsilon).Should().BeTrue();
}
// Assert
area.Should().BeApproximately(expectedArea, GSharkMath.MaxTolerance);
}
}
}
4 changes: 2 additions & 2 deletions src/GShark.Test.XUnit/GShark.Test.XUnit.csproj
Expand Up @@ -18,8 +18,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="FluentAssertions" Version="6.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>
Expand Down
82 changes: 38 additions & 44 deletions src/GShark/Core/Trigonometry.cs
@@ -1,29 +1,24 @@
using GShark.Geometry;
using System;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using GShark.Geometry;

namespace GShark.Core
{
/// <summary>
/// Provides basic trigonometry methods.
/// Provides basic trigonometry methods.
/// </summary>
public class Trigonometry
{
/// <summary>
/// Determines if the provide points are on the same plane within specified tolerance.
/// Determines if the given points lie on the same plane within the specified tolerance.
/// </summary>
/// <param name="points">Points to check.</param>
/// <param name="tolerance">Tolerance.</param>
/// <returns>True if all points are coplanar.</returns>
public static bool ArePointsCoplanar(IList<Point3> points, double tolerance = GSharkMath.Epsilon)
{
// https://en.wikipedia.org/wiki/Triple_product
if (points.Count <= 3)
{
return true;
}
if (points.Count <= 3) return true;

var vec1 = points[1] - points[0];
var vec2 = points[2] - points[0];
Expand All @@ -32,19 +27,16 @@ public static bool ArePointsCoplanar(IList<Point3> points, double tolerance = GS
{
var vec3 = points[i] - points[0];
double tripleProduct = Vector3.DotProduct(Vector3.CrossProduct(vec3, vec2), vec1);
if (Math.Abs(tripleProduct) > GSharkMath.Epsilon)
{
return false;
}
if (Math.Abs(tripleProduct) > GSharkMath.Epsilon) return false;
}

return true;
}

/// <summary>
/// Determines if three points form a straight line (are collinear) within a given tolerance.<br/>
/// Find the area of the triangle without using square root and multiply it for 0.5.<br/>
/// http://www.stumblingrobot.com/2016/05/01/use-cross-product-compute-area-triangles-given-vertices/
/// Determines if three points form a straight line (are collinear) within a given tolerance.<br />
/// Find the area of the triangle without using square root and multiply it for 0.5.<br />
/// http://www.stumblingrobot.com/2016/05/01/use-cross-product-compute-area-triangles-given-vertices/
/// </summary>
/// <param name="pt1">First point.</param>
/// <param name="pt2">Second point.</param>
Expand All @@ -62,7 +54,8 @@ public static bool ArePointsCollinear(Point3 pt1, Point3 pt2, Point3 pt3, double
}

/// <summary>
/// Calculates the point at the equal distance from the three points, it can be also described as the center of a circle.
/// Calculates the point at the equal distance from the three points, it can be also described as the center of a
/// circle.
/// </summary>
/// <param name="pt1">First point.</param>
/// <param name="pt2">Second point.</param>
Expand All @@ -88,8 +81,8 @@ public static Point3 EquidistantPoint(Point3 pt1, Point3 pt2, Point3 pt3)
}

/// <summary>
/// Gets the orientation between tree points in the plane.<br/>
/// https://math.stackexchange.com/questions/2386810/orientation-of-three-points-in-3d-space
/// Gets the orientation between three points in the plane.<br />
/// https://math.stackexchange.com/questions/2386810/orientation-of-three-points-in-3d-space
/// </summary>
/// <param name="pt1">First point.</param>
/// <param name="pt2">Second point.</param>
Expand All @@ -101,54 +94,55 @@ public static int PointOrder(Point3 pt1, Point3 pt2, Point3 pt3)
Vector3 n = Vector3.CrossProduct(pt2 - pt1, pt3 - pt1);
double result = Vector3.DotProduct(pl.ZAxis, n.Unitize());

if (Math.Abs(result) < GSharkMath.Epsilon)
{
return 0;
}
if (Math.Abs(result) < GSharkMath.Epsilon) return 0;

return (result < 0) ? 1 : 2;
return result < 0 ? 1 : 2;
}

/// <summary>
/// Finds the closest point on a segment.<br/>
/// It is not a segment like a line or ray but the part that compose the segment.<br/>
/// The segment is deconstruct in two points and two t values.
/// Finds the closest point on a segment.<br />
/// It is not a segment like a line or ray but the part that compose the segment.<br />
/// The segment is deconstruct in two points and two t values.
/// </summary>
/// <param name="point">Point to project.</param>
/// <param name="segmentPt0">First point of the segment.</param>
/// <param name="segmentPt1">Second point of the segment.</param>
/// <param name="valueT0">First t value of the segment.</param>
/// <param name="valueT1">Second t value of the segment.</param>
/// <returns>Tuple with the closest point its corresponding tValue on the curve.</returns>
public static (double tValue, Point3 pt) ClosestPointToSegment(Point3 point, Point3 segmentPt0, Point3 segmentPt1, double valueT0, double valueT1)
public static (double tValue, Point3 pt) ClosestPointToSegment(Point3 point, Point3 segmentPt0,
Point3 segmentPt1, double valueT0, double valueT1)
{
Vector3 direction = segmentPt1 - segmentPt0;
double length = direction.Length;

if (length < GSharkMath.Epsilon)
{
return (tValue: valueT0, pt: segmentPt0);
}
if (length < GSharkMath.Epsilon) return (tValue: valueT0, pt: segmentPt0);

Vector3 vecUnitized = direction.Unitize();
Vector3 ptToSegPt0 = point - segmentPt0;
double dotResult = Vector3.DotProduct(ptToSegPt0, vecUnitized);

if (dotResult < 0.0)
{
return (tValue: valueT0, pt: segmentPt0);
}
if (dotResult < 0.0) return (tValue: valueT0, pt: segmentPt0);

if (dotResult > length)
{
return (tValue: valueT1, pt: segmentPt1);
}
if (dotResult > length) return (tValue: valueT1, pt: segmentPt1);

Point3 pointResult = segmentPt0 + (vecUnitized * dotResult);
Point3 pointResult = segmentPt0 + vecUnitized * dotResult;
double tValueResult = valueT0 + (valueT1 - valueT0) * dotResult / length;
return (tValue: tValueResult, pt: pointResult);
}


/// <summary>
/// Calculates the area of a triangle.
/// </summary>
/// <param name="pt1">First point of the triangle.</param>
/// <param name="pt2">Second point of the triangle.</param>
/// <param name="pt3">Third point of the triangle.</param>
/// <returns>The area of the triangle.</returns>
public static double AreaOfTriangle(Point3 pt1, Point3 pt2, Point3 pt3)
{
Vector3 v1 = pt1 - pt2;
Vector3 v2 = pt1 - pt3;
return 0.5 * Vector3.CrossProduct(v1, v2).Length;
}
}
}
}

0 comments on commit 121b6ac

Please sign in to comment.