Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Real mid and more #993

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6952333
init Polyline implementation of IHasArcLength
jamesbradleym Jul 18, 2023
7309fc7
Line implements Interface IHasArcLength
jamesbradleym Jul 18, 2023
2afac10
Circle implements Interface IHasArcLength
jamesbradleym Jul 19, 2023
374fe34
Polish Bezier, IHasCurveLength
jamesbradleym Jul 21, 2023
8a1675a
forgot the Z
jamesbradleym Jul 21, 2023
5324e71
init Polyline implementation of IHasArcLength
jamesbradleym Jul 18, 2023
af5f18d
Line implements Interface IHasArcLength
jamesbradleym Jul 18, 2023
4edac7a
Circle implements Interface IHasArcLength
jamesbradleym Jul 19, 2023
67f1a8f
Polish Bezier, IHasCurveLength
jamesbradleym Jul 21, 2023
9d6908c
forgot the Z
jamesbradleym Jul 21, 2023
4cac172
Merge branch 'real-mid-and-more' of https://github.com/hypar-io/Eleme…
jamesbradleym Oct 31, 2023
0a255a7
forgot the Z
jamesbradleym Nov 13, 2023
132c945
Merge branch 'real-mid-and-more' of https://github.com/hypar-io/Eleme…
jamesbradleym Nov 13, 2023
823f47d
init Polyline implementation of IHasArcLength
jamesbradleym Jul 18, 2023
6c6fe1d
Line implements Interface IHasArcLength
jamesbradleym Jul 18, 2023
c8da721
Circle implements Interface IHasArcLength
jamesbradleym Jul 19, 2023
b45c86d
Polish Bezier, IHasCurveLength
jamesbradleym Jul 21, 2023
cc9cff7
forgot the Z
jamesbradleym Jul 21, 2023
bc1db4e
Merge branch 'real-mid-and-more' of https://github.com/hypar-io/Eleme…
jamesbradleym Nov 13, 2023
51faf29
Merge branch 'master' into real-mid-and-more
jamesbradleym Nov 13, 2023
6960afb
re-implement
jamesbradleym Nov 13, 2023
ea33a75
adress conflicts
jamesbradleym Nov 13, 2023
617d4a3
fix line divide test
jamesbradleym Nov 13, 2023
cb8a1e2
Fix circumference and tests
jamesbradleym Nov 13, 2023
ef80d46
fix line tests
jamesbradleym Nov 13, 2023
42e6a24
conditional bezier arclength for quad beziers only
jamesbradleym Nov 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,28 +83,41 @@
- `Ellipse`
- `EllipticalArc`
- `IndexedPolycurve`
- `IHasArcLength`
- `Grid1d.GetCellDomains`
- `Message.Info`
- `Message.Error`
- `Message.Warning`
- `Topography.Trimmed`
- `new Topography(Topography other)`
- `Topography.TopMesh()`
- `UpdateElementRepresentations` flag to all serialization methods
- `Bezier.PointAtLength()`
- `Bezier.PointAtNormalizedLength()`
- `Bezier.ParameterAt()`
- `Bezier.DivideByLength()`
- `Bezier.Split()`
- `Bezier.SplitAt()`
- `Bezier.SplitByLength()`
- `Bezier.ConstructPiecewiseCubicBezier()`

### Changed

- `Polyline` now inherits from `BoundedCurve`.
- `Polyline` is now parameterized 0->length.
- `Polyline` now implements the `IHasArcLength` interface.
- `Arc` now inherits from `TrimmedCurve<Circle>`.
- `Arc` is now parameterized 0->2Pi
- `Arc` now automatically corrects decreasing angle domains to be increasing, while preserving direction.
- `Arc` is now parameterized 0->2Pi.
- `Line` now inherits from `TrimmedCurve<InfiniteLine>`.
- `Line` is now parameterized 0->length.
- `Line` now implements the `IHasArcLength` interface.
- `Bezier` now inherits from `BoundedCurve`.
- `Bezier` now implements the `IHasArcLength` interface.
- `Bezier.ArcLength()` now uses Gauss quadrature approximation vs linear sampling.
- `Bezier` now implements the `IHasArcLength` interface.
- `Bezier.ArcLength()` now uses Gauss quadrature approximation vs linear sampling.
- `Polyline` is now parameterized 0->length.
- `Circle` is now parameterized 0->2Pi.
- `Line` is now parameterized 0->length.
- `Circle` now implements the `IHasArcLength` interface.
- `Vector3.DistanceTo(Ray ray)` now returns positive infinity instead of throwing.
- `Message`: removed obsolete `FromLine` method.
- `AdaptiveGrid`: removed obsolete `TryGetVertexIndex` with `tolerance` parameter.
Expand Down
4 changes: 2 additions & 2 deletions Elements.MEP/src/Fittings/Reducer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public static Reducer ReducerForPipe(StraightSegment pipe, double reducerLength,

var path = reducerAtEnd ? pipe.Path.Segments()[0].Reversed() : pipe.Path.Segments()[0];

var position = path.DivideByLength(distanceFromEnd)[0].End;
var position = path.DivideByLengthToSegments(distanceFromEnd)[0].End;

var orientation = path.Direction();
// var fittingMaterial = new Material("green", new Color(0, 1, 0, 0.5);
Expand Down Expand Up @@ -133,7 +133,7 @@ public void Move(Vector3 translation)
}

/// <summary>
/// Port with smaller diameter points to the +X axis.
/// Port with smaller diameter points to the +X axis.
/// If there is eccentric transform, the smaller part will be shifted to the -Z axis.
/// We point smaller diameter in the +X direction so that there is one reducer defined in the standard orientation, to which this transformation is then applied.
/// This let's us just have one size 110/90 that is rotated into a 90/110 orientation when needed.
Expand Down
2 changes: 1 addition & 1 deletion Elements/src/Elements.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<Summary>The Elements library provides object types for generating the built environment.</Summary>
<PackageProjectUrl>https://github.com/hypar-io/elements</PackageProjectUrl>
<RepositoryUrl>https://github.com/hypar-io/elements</RepositoryUrl>
<Version>$(Version)</Version>
<Version>21.21.21</Version>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup>
<ItemGroup>
Expand Down
603 changes: 597 additions & 6 deletions Elements/src/Geometry/Bezier.cs

Large diffs are not rendered by default.

178 changes: 175 additions & 3 deletions Elements/src/Geometry/Circle.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Elements.Validators;
using Elements.Geometry.Interfaces;
using Newtonsoft.Json;

namespace Elements.Geometry
{
/// <summary>
/// A circle.
/// A circle.
/// Parameterization of the circle is 0 -> 2PI.
/// </summary>
public class Circle : Curve, IConic
Expand All @@ -27,6 +28,11 @@ public Vector3 Center
[System.ComponentModel.DataAnnotations.Range(0.0D, double.MaxValue)]
public double Radius { get; protected set; }

/// <summary>The circumference of the circle.</summary>
[JsonIgnore]
[System.ComponentModel.DataAnnotations.Range(0.0D, double.MaxValue)]
public double Circumference { get; protected set; }

/// <summary>
/// The coordinate system of the plane containing the circle.
/// </summary>
Expand All @@ -50,7 +56,15 @@ public Vector3 Normal
[JsonConstructor]
public Circle(Vector3 center, double radius = 1.0)
{
if (!Validator.DisableValidationOnConstruction)
{
if (Math.Abs(radius - 0.0) < double.Epsilon ? true : false)
{
throw new ArgumentException($"The circle could not be created. The radius of the circle cannot be the zero: radius {radius}");
}
}
this.Radius = radius;
this.Circumference = 2 * Math.PI * this.Radius;
this.Transform = new Transform(center);
}

Expand All @@ -60,7 +74,15 @@ public Circle(Vector3 center, double radius = 1.0)
/// <param name="radius">The radius of the circle.</param>
public Circle(double radius = 1.0)
{
if (!Validator.DisableValidationOnConstruction)
{
if (Math.Abs(radius - 0.0) < double.Epsilon ? true : false)
{
throw new ArgumentException($"The circle could not be created. The radius of the circle cannot be the zero: radius {radius}");
}
}
this.Radius = radius;
this.Circumference = 2 * Math.PI * this.Radius;
this.Transform = new Transform();
}

Expand All @@ -69,8 +91,65 @@ public Circle(double radius = 1.0)
/// </summary>
public Circle(Transform transform, double radius = 1.0)
{
if (!Validator.DisableValidationOnConstruction)
{
if (Math.Abs(radius - 0.0) < double.Epsilon ? true : false)
{
throw new ArgumentException($"The circle could not be created. The radius of the circle cannot be the zero: radius {radius}");
}
}
this.Transform = transform;
this.Radius = radius;
this.Circumference = 2 * Math.PI * this.Radius;
}

/// <summary>
/// Calculate the length of the circle between two parameters.
/// </summary>
public double ArcLength(double start, double end)
{
// Convert start and end parameters from radians to degrees
double _startAngle = start * 180.0 / Math.PI;
double _endAngle = end * 180.0 / Math.PI;

// Ensure the start angle is within the valid domain range of 0 to 360 degrees
double startAngle = _startAngle % 360;
if (startAngle < 0)
{
startAngle += 360;
}

// Ensure the end angle is within the valid domain range of 0 to 360 degrees
double endAngle = _endAngle % 360;
if (endAngle < 0)
{
endAngle += 360;
}
else if (endAngle == 0 && Math.Abs(_endAngle) >= 2 * Math.PI)
{
endAngle = 360;
}

// Calculate the difference in angles
double angleDifference = endAngle - startAngle;

// Adjust the angle difference if it crosses the 360-degree boundary
if (angleDifference < 0)
{
angleDifference += 360;
}
else if (angleDifference >= 2 * Math.PI)
{
return Circumference; // Full circle, return circumference
}

// Convert the angle difference back to radians
double angleDifferenceRadians = angleDifference * Math.PI / 180.0;

// Calculate the arc length using the formula: arc length = radius * angle
double arcLength = Radius * angleDifferenceRadians;

return arcLength;
}

/// <summary>
Expand All @@ -94,6 +173,14 @@ public Polygon ToPolygon(int divisions = 10)
return new Polygon(pts, true);
}

/// <summary>
/// Are the two circles almost equal?
/// </summary>
public bool IsAlmostEqualTo(Circle other, double tolerance = Vector3.EPSILON)
{
return (Center.IsAlmostEqualTo(other.Center, tolerance) && Math.Abs(Radius - other.Radius) < tolerance ? true : false);
}

/// <summary>
/// Convert a circle to a circular arc.
/// </summary>
Expand All @@ -105,6 +192,15 @@ public Polygon ToPolygon(int divisions = 10)
/// <param name="c">The bounded curve to convert.</param>
public static implicit operator ModelCurve(Circle c) => new ModelCurve(c);

/// <summary>
/// Calculates and returns the midpoint of the circle.
/// </summary>
/// <returns>The midpoint of the circle.</returns>
public Vector3 MidPoint()
{
return PointAt(Math.PI);
}

/// <summary>
/// Return the point at parameter u on the arc.
/// </summary>
Expand All @@ -122,6 +218,46 @@ private Vector3 PointAtUntransformed(double u)
return new Vector3(x, y);
}

/// <summary>
/// Calculates and returns the point on the circle at a specific arc length.
/// </summary>
/// <param name="length">The arc length along the circumference of the circle.</param>
/// <returns>The point on the circle at the specified arc length.</returns>
public Vector3 PointAtLength(double length)
{
double parameter = (length / Circumference) * 2 * Math.PI;
return PointAt(parameter);
}

/// <summary>
/// Calculates and returns the point on the circle at a normalized arc length.
/// </summary>
/// <param name="normalizedLength">The normalized arc length between 0 and 1.</param>
/// <returns>The point on the circle at the specified normalized arc length.</returns>
public Vector3 PointAtNormalizedLength(double normalizedLength)
{
double parameter = normalizedLength * 2 * Math.PI;
return PointAt(parameter);
}

/// <summary>
/// Calculates the parameter within the range of 0 to 2π at a given point on the circle.
/// </summary>
/// <param name="point">The point on the circle.</param>
/// <returns>The parameter within the range of 0 to 2π at the given point on the circle.</returns>
public double GetParameterAt(Vector3 point)
{
Vector3 relativePoint = point - Center;

double theta = Math.Atan2(relativePoint.Y, relativePoint.X);

if (theta < 0)
{
theta += 2 * Math.PI;
}
return theta;
}

/// <summary>
/// Check if certain point is on the circle.
/// </summary>
Expand All @@ -143,6 +279,22 @@ public bool ParameterAt(Vector3 pt, out double t)
return false;
}

/// <summary>
/// Checks if a given point lies on a circle within a specified tolerance.
/// </summary>
/// <param name="point">The point to be checked.</param>
/// <param name="circle">The circle to check against.</param>
/// <param name="tolerance">The tolerance value (optional). Default is 1E-05.</param>
/// <returns>True if the point lies on the circle within the tolerance, otherwise false.</returns>
public static bool PointOnCircle(Vector3 point, Circle circle, double tolerance = 1E-05)
{
Vector3 centerToPoint = point - circle.Center;
double distanceToCenter = centerToPoint.Length();

// Check if the distance from the point to the center is within the tolerance of the circle's radius
return Math.Abs(distanceToCenter - circle.Radius) < tolerance;
}

private double ParameterAtUntransformed(Vector3 pt)
{
var v = pt / Radius;
Expand Down Expand Up @@ -187,14 +339,34 @@ public override double ParameterAtDistanceFromParameter(double distance, double
return start + theta;
}

/// <summary>
/// Divides the circle into segments of the specified length and returns a list of points representing the division.
/// </summary>
/// <param name="length">The length of each segment.</param>
/// <returns>A list of points representing the division of the circle.</returns>
public Vector3[] DivideByLength(double length)
{
List<Vector3> points = new List<Vector3>();
int segmentCount = (int)Math.Ceiling(Circumference / length);
double segmentLength = Circumference / segmentCount;

for (int i = 0; i < segmentCount; i++)
{
double parameter = i * segmentLength / Circumference;
points.Add(PointAtNormalizedLength(parameter));
}

return points.ToArray();
}

/// <inheritdoc/>
public override bool Intersects(ICurve curve, out List<Vector3> results)
{
switch (curve)
{
case BoundedCurve boundedCurve:
return boundedCurve.Intersects(this, out results);
case InfiniteLine line :
case InfiniteLine line:
return Intersects(line, out results);
case Circle circle:
return Intersects(circle, out results);
Expand All @@ -219,7 +391,7 @@ public bool Intersects(Circle other, out List<Vector3> results)
Plane planeA = new Plane(Center, Normal);
Plane planeB = new Plane(other.Center, other.Normal);

// Check if two circles are on the same plane.
// Check if two circles are on the same plane.
if (Normal.IsParallelTo(other.Normal, Vector3.EPSILON * Vector3.EPSILON) &&
other.Center.DistanceTo(planeA).ApproximatelyEquals(0))
{
Expand Down
38 changes: 38 additions & 0 deletions Elements/src/Geometry/Interfaces/IHasArcLength.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
namespace Elements.Geometry.Interfaces
{
/// <summary>
/// Represents a curve with arc length-based operations.
/// Implementing classes define methods for computing points and performing operations based on arc length.
/// </summary>
public interface IHasArcLength
{
/// <summary>
/// Returns the point on the curve at the specified arc length.
/// </summary>
/// <param name="length">The arc length along the curve.</param>
/// <returns>The point on the curve at the specified arc length.</returns>
Vector3 PointAtLength(double length);

/// <summary>
/// Returns the point on the curve at the specified normalized length.
/// The normalized length is a value between 0 and 1 representing the relative position along the curve.
/// </summary>
/// <param name="normalizedLength">The normalized length along the curve.</param>
/// <returns>The point on the curve at the specified normalized length.</returns>
Vector3 PointAtNormalizedLength(double normalizedLength);

/// <summary>
/// Returns the midpoint of the curve.
/// </summary>
/// <returns>The midpoint of the curve.</returns>
Vector3 MidPoint();

/// <summary>
/// Divides the curve into segments of the specified length and returns the points along the curve at those intervals.
/// </summary>
/// <param name="length">The desired length for dividing the curve.</param>
/// <returns>A list of points representing the divisions along the curve.</returns>
Vector3[] DivideByLength(double length);
}

}
Loading