Skip to content

Commit

Permalink
Merge pull request #247 from GSharker/dev/guma/curve-split-extension-…
Browse files Browse the repository at this point in the history
…methods

Dev/guma/curve split extension methods
  • Loading branch information
cesarecaoduro authored Aug 18, 2021
2 parents 29b456e + 6cbaefe commit 6a0dbcd
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 77 deletions.
Binary file modified src/GShark.Test.XUnit/DebugFiles/GHDebug_Curves.gh
Binary file not shown.
116 changes: 87 additions & 29 deletions src/GShark.Test.XUnit/Operation/DivideTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using GShark.ExtendedMethods;
using GShark.Geometry;
using GShark.Geometry.Interfaces;
using GShark.Operation;
using GShark.Test.XUnit.Data;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -22,9 +21,11 @@ public DivideTests(ITestOutputHelper testOutput)
}

[Theory]
[InlineData(0.0)]
[InlineData(0.25)]
[InlineData(0.5)]
[InlineData(0.75)]
[InlineData(1.0)]
public void It_Returns_Two_Curves_Splitting_One_Curve(double parameter)
{
// Arrange
Expand All @@ -39,7 +40,7 @@ public void It_Returns_Two_Curves_Splitting_One_Curve(double parameter)
NurbsCurve curve = new NurbsCurve(pts, degree);

// Act
List<ICurve> curves = Divide.SplitCurve(curve, parameter);
List<ICurve> curves = curve.SplitAt(parameter);

// Assert
curves.Should().HaveCount(2);
Expand All @@ -57,34 +58,91 @@ public void It_Returns_Two_Curves_Splitting_One_Curve(double parameter)
}
}

[Fact]
public void It_Returns_A_SubCurve_Defined_By_Domain()
{
// Arrange
Interval domain = new Interval(0.65, 0.85);
int degree = 3;
List<Point3> controlPts = new List<Point3>
{
new Point3(2,2,0),
new Point3(4,12,0),
new Point3(7,12,0),
new Point3(15,2,5)
};
var expectedKnotVector = new KnotVector(new List<double> {0.65, 0.65, 0.65, 0.65, 0.85, 0.85, 0.85, 0.85});
NurbsCurve curve = new NurbsCurve(controlPts, degree);
NurbsCurve expectedSubCurve = new NurbsCurve(
new List<Point3>
{
new (8.266,8.825,1.373125),
new (9.264,8.225,1.795625),
new (10.406,7.225,2.348125),
new (11.724,5.825,3.070625)
},
degree:3);

// Act
ICurve subCurve = curve.SubCurve(domain);

// Assert
subCurve.LocationPoints.SequenceEqual(expectedSubCurve.LocationPoints).Should().BeTrue();
subCurve.ControlPoints.SequenceEqual(expectedSubCurve.ControlPoints).Should().BeTrue();
subCurve.Knots.SequenceEqual(expectedKnotVector).Should().BeTrue();
}

[Fact]
public void It_Splits_A_Curve_Into_Segments_At_Given_Parameters()
{
// Arrange
var parameters = new[]{0.25, 0.5, 0.75};
int degree = 3;
List<Point3> controlPts = new List<Point3>
{
new Point3(2,2,0),
new Point3(4,12,0),
new Point3(7,12,0),
new Point3(15,2,5)
};
KnotVector knots = new KnotVector(degree, controlPts.Count);
NurbsCurve curve = new NurbsCurve(controlPts, degree);

// Act
var segments = curve.SplitAt(parameters.ToArray());

// Assert
segments.Count.Should().Be(4);
}

[Fact]
public void Divide_By_Number_Of_Segments_Returns_Points_And_Parameters_Along_Curve()
{
// Arrange
NurbsCurve curve = NurbsCurveCollection.NurbsCurvePlanarExample();
double[] tValuesExpected = {
0,
0.12294081350167592,
0.26515588164329529,
0.4202931821346283,
0.5797068178653717,
0.73484411835670471,
0.877059186498324,
0.12294074023135007,
0.26515583503755935,
0.4202931617987752,
0.5797068382012247,
0.7348441649624406,
0.87705925976865,
1
};

var pointsExpected = tValuesExpected.Select(t => curve.PointAt(t)).ToList();
List<Point3> pointsExpected = tValuesExpected.Select(t => curve.PointAt(t)).ToList();
int segments = 7;

// Act
var (points, parameters) = curve.Divide(segments);
(List<Point3> Points, List<double> Parameters) divideResult = curve.Divide(segments);

// Assert
parameters.Count.Should().Be(tValuesExpected.Length).And.Be(segments + 1);
divideResult.Parameters.Count.Should().Be(tValuesExpected.Length).And.Be(segments + 1);
for (int i = 0; i < tValuesExpected.Length; i++)
{
parameters[i].Should().BeApproximately(tValuesExpected[i], GeoSharkMath.MaxTolerance);
points[i].EpsilonEquals(pointsExpected[i], GeoSharkMath.MaxTolerance).Should().BeTrue();
divideResult.Parameters[i].Should().BeApproximately(tValuesExpected[i], GeoSharkMath.MaxTolerance);
divideResult.Points[i].EpsilonEquals(pointsExpected[i], GeoSharkMath.MaxTolerance).Should().BeTrue();
}
}

Expand All @@ -95,27 +153,27 @@ public void Divide_By_Length_Returns_Points_And_Parameters_Along_Curve()
NurbsCurve curve = NurbsCurveCollection.NurbsCurvePlanarExample();
double[] tValuesExpected = {
0,
0.12294081350167592,
0.26515588164329529,
0.4202931821346283,
0.5797068178653717,
0.73484411835670471,
0.877059186498324,
0.12294074023135007,
0.26515583503755935,
0.4202931617987752,
0.5797068382012247,
0.7348441649624406,
0.87705925976865,
1
};
var pointsExpected = tValuesExpected.Select(t => curve.PointAt(t)).ToList();
List<Point3> pointsExpected = tValuesExpected.Select(t => curve.PointAt(t)).ToList();
int steps = 7;
double length = curve.Length() / steps;

// Act
var (points, parameters) = curve.Divide(length);
(List<Point3> Points, List<double> Parameters) divideResult = curve.Divide(length);

// Assert
parameters.Count.Should().Be(pointsExpected.Count).And.Be(steps + 1);
divideResult.Parameters.Count.Should().Be(pointsExpected.Count).And.Be(steps + 1);
for (int i = 0; i < pointsExpected.Count; i++)
{
parameters[i].Should().BeApproximately(tValuesExpected[i], GeoSharkMath.MaxTolerance);
points[i].EpsilonEquals(pointsExpected[i], GeoSharkMath.MaxTolerance).Should().BeTrue();
divideResult.Parameters[i].Should().BeApproximately(tValuesExpected[i], GeoSharkMath.MaxTolerance);
divideResult.Points[i].EpsilonEquals(pointsExpected[i], GeoSharkMath.MaxTolerance).Should().BeTrue();
}
}

Expand Down Expand Up @@ -219,23 +277,23 @@ public void It_Calculates_Rotation_Minimized_Frames_Along_Curve()
//Act
List<double> uValues = curve.Divide(10).Parameters;

foreach (var uValue in uValues)
foreach (double uValue in uValues)
{
_testOutput.WriteLine(uValue.ToString());
}

List<Plane> perpFrames = curve.PerpendicularFrames(uValues);

foreach (var perpFrame in perpFrames)
foreach (Plane perpFrame in perpFrames)
{
_testOutput.WriteLine(perpFrame.ToString());
}

//Assert
for (var i = 0; i < perpFrames.Count; i++)
for (int i = 0; i < perpFrames.Count; i++)
{
var perpFrame = perpFrames[i];
var expectedPerpFrame = expectedPerpFrames[i];
Plane perpFrame = perpFrames[i];
Plane expectedPerpFrame = expectedPerpFrames[i];
perpFrame.Origin.EpsilonEquals(expectedPerpFrame.Origin, GeoSharkMath.MaxTolerance);
perpFrame.XAxis.EpsilonEquals(expectedPerpFrame.XAxis, GeoSharkMath.MaxTolerance);
perpFrame.YAxis.EpsilonEquals(expectedPerpFrame.YAxis, GeoSharkMath.MaxTolerance);
Expand Down
4 changes: 3 additions & 1 deletion src/GShark/Core/BoundingBoxTree/LazyCurveBBT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using GShark.Operation;
using System;
using System.Collections.Generic;
using GShark.ExtendedMethods;
using GShark.Geometry.Interfaces;

namespace GShark.Core.BoundingBoxTree
{
Expand Down Expand Up @@ -35,7 +37,7 @@ public Tuple<IBoundingBoxTree<ICurve>, IBoundingBoxTree<ICurve>> Split()
Random r = new Random();
double t = (_curve.Knots[_curve.Knots.Count - 1] + _curve.Knots[0]) / 2.0 +
(_curve.Knots[_curve.Knots.Count - 1] - _curve.Knots[0]) * 0.1 * r.NextDouble();
List<ICurve> curves = Divide.SplitCurve(_curve, t);
List<ICurve> curves = _curve.SplitAt(t);

return new Tuple<IBoundingBoxTree<ICurve>, IBoundingBoxTree<ICurve>>
(new LazyCurveBBT(curves[0], _knotTolerance), new LazyCurveBBT(curves[1], _knotTolerance));
Expand Down
134 changes: 129 additions & 5 deletions src/GShark/ExtendedMethods/Curve.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
using GShark.Geometry;
using GShark.Geometry.Interfaces;
using GShark.Operation;
using System;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GShark.Core;
using GShark.Geometry;
using GShark.Geometry.Interfaces;
using GShark.Operation;

namespace GShark.ExtendedMethods
{
Expand Down Expand Up @@ -119,11 +123,131 @@ public static List<Plane> PerpendicularFrames(this ICurve curve, List<double> uV
var sNext = Vector3.CrossProduct(pointsOnCurveTan[i + 1], rNext); //compute vector s[i+1] of next frame

//create output frame
var frameNext = new Plane { Origin = pointsOnCurve[i + 1], XAxis = rNext, YAxis = sNext };
var frameNext = new Plane();
frameNext.Origin = pointsOnCurve[i + 1];
frameNext.XAxis = rNext;
frameNext.YAxis = sNext;
perpFrames[i + 1] = frameNext; //output frame
}

return perpFrames.ToList();
}

/// <summary>
/// Splits a curve into two parts at a given parameter.
/// </summary>
/// <param name="curve">The curve object.</param>
/// <param name="t">The parameter at which to split the curve.</param>
/// <returns>Two NurbsCurve objects.</returns>
public static List<ICurve> SplitAt(this ICurve curve, double t)
{
int degree = curve.Degree;

List<double> knotsToInsert = Sets.RepeatData(t, degree + 1);

ICurve refinedCurve = Modify.CurveKnotRefine(curve, knotsToInsert);

int s = curve.Knots.Span(degree, t);

KnotVector knots0 = refinedCurve.Knots.ToList().GetRange(0, s + degree + 2).ToKnot();
KnotVector knots1 = refinedCurve.Knots.GetRange(s + 1, refinedCurve.Knots.Count - (s + 1)).ToKnot();

List<Point4> controlPoints0 = refinedCurve.ControlPoints.GetRange(0, s + 1);
List<Point4> controlPoints1 = refinedCurve.ControlPoints.GetRange(s + 1, refinedCurve.LocationPoints.Count - (s + 1));

return new List<ICurve> { new NurbsCurve(degree, knots0, controlPoints0), new NurbsCurve(degree, knots1, controlPoints1) };
}

/// <summary>
/// Splits a curve at given parameters and returns the segments as curves.
/// </summary>
/// <param name="curve">The curve to split.</param>
/// <param name="parameters">The parameters at which to split the curve. Values should be between 0 and 1.</param>
/// <returns>Collection of curve segments.</returns>
//TODO: Should input parameters be between 0 and 1, or should we have a normalization function on ICurve?
public static List<ICurve> SplitAt(this ICurve curve, double[] parameters)
{
var curves = new List<ICurve>();
if (parameters.Length == 0)
{
curves.Add(curve);
return curves;
}

//TODO: sort in increasing order or throw if not?
var sortedParameters = parameters.OrderBy(x => x).ToArray();

//TODO: Always including ends of domain if not included. Could also just ignore and assume they are in input parameters.
if (Math.Abs(sortedParameters[0] - curve.Domain.T0) > GeoSharkMath.MaxTolerance)
{
var tempParams = new double[sortedParameters.Length + 1];
tempParams[0] = curve.Domain.T0;
for (var i = 0; i < sortedParameters.Length; i++)
{
tempParams[i+1] = sortedParameters[i];
}
sortedParameters = tempParams;
}

if (Math.Abs(sortedParameters[sortedParameters.Length - 1] - curve.Domain.T1) > GeoSharkMath.MaxTolerance)
{
Array.Resize(ref sortedParameters, sortedParameters.Length + 1);
sortedParameters[sortedParameters.Length - 1] = curve.Domain.T1;
}

for (int i = 0; i < sortedParameters.Length - 1; i++)
{
curves.Add(SubCurve(curve, new Interval(sortedParameters[i], sortedParameters[i+1])));
}

return curves;
}

/// <summary>
/// Extract sub-curve defined by domain.
/// </summary>
/// <param name="curve">The curve from which to extract the sub-curve.</param>
/// <param name="domain">Domain of sub-curve</param>
/// <returns>NurbsCurve.</returns>
public static ICurve SubCurve(this ICurve curve, Interval domain)
{
int degree = curve.Degree;
int order = degree + 1;
Interval subCurveDomain = domain;

//NOTE: Handling decreasing domain by flipping it to maintain direction of original curve in sub-curve. Is this what we want?
if (domain.IsDecreasing)
{
subCurveDomain = new Interval(domain.T1, domain.T0);
}

var isT0AtStart = Math.Abs(subCurveDomain.T0 - curve.Knots[0]) < GeoSharkMath.MaxTolerance;
var isT1AtEnd = Math.Abs(subCurveDomain.T1 - curve.Knots[curve.Knots.Count-1]) < GeoSharkMath.MaxTolerance;

if (isT0AtStart && isT1AtEnd)
{
return curve;
}

if (isT0AtStart || isT1AtEnd)
{
return isT0AtStart ? curve.SplitAt(subCurveDomain.T1)[0] : curve.SplitAt(subCurveDomain.T0)[1];
}

KnotVector subCurveKnotVector = new KnotVector();
List<double> knotsToInsert = Sets.RepeatData(domain.T0, order).Concat(Sets.RepeatData(domain.T1, degree + 1)).ToList();
ICurve refinedCurve = Modify.CurveKnotRefine(curve, knotsToInsert);
var multiplicityAtT0 = refinedCurve.Knots.Multiplicity(subCurveDomain.T0);
var multiplicityAtT1 = refinedCurve.Knots.Multiplicity(subCurveDomain.T1);
var t0Idx = refinedCurve.Knots.IndexOf(subCurveDomain.T0);

subCurveKnotVector.AddRange(refinedCurve.Knots.GetRange(t0Idx, multiplicityAtT0 + multiplicityAtT1));

var subCurveControlPoints = refinedCurve.ControlPoints.GetRange(order, order);

var subCurve = new NurbsCurve(curve.Degree, subCurveKnotVector, subCurveControlPoints);

return subCurve;
}
}
}
2 changes: 1 addition & 1 deletion src/GShark/ExtendedMethods/Surface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public static NurbsSurface[] Split(this NurbsSurface surface, double parameter,
{
throw new ArgumentNullException(nameof(surface));
}

return Divide.SplitSurface(surface, parameter, direction);
}
}
Expand Down
Loading

0 comments on commit 6a0dbcd

Please sign in to comment.