From 3100a1de4c7d8220de209397fcdde64e2fe0cd94 Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Tue, 10 Jan 2023 12:44:45 +0800 Subject: [PATCH] Add Z to BoundingBox --- Wkx.Tests/BoundingBoxTests.cs | 96 ++++++++++++++++++++++++++++ Wkx.Tests/MathUtilTests.cs | 53 +++++++++++++++ Wkx.Tests/PolyhedralSurfaceTests.cs | 27 ++++++++ Wkx/BoundingBox.cs | 28 ++++++-- Wkx/Extensions/GeometryExtensions.cs | 33 ++++++++-- Wkx/MathUtil.cs | 20 ++++++ Wkx/Point.cs | 2 +- 7 files changed, 247 insertions(+), 12 deletions(-) create mode 100644 Wkx.Tests/BoundingBoxTests.cs create mode 100644 Wkx.Tests/MathUtilTests.cs create mode 100644 Wkx.Tests/PolyhedralSurfaceTests.cs diff --git a/Wkx.Tests/BoundingBoxTests.cs b/Wkx.Tests/BoundingBoxTests.cs new file mode 100644 index 0000000..0b8cc77 --- /dev/null +++ b/Wkx.Tests/BoundingBoxTests.cs @@ -0,0 +1,96 @@ +namespace Wkx.Tests +{ + using System; + using Xunit; + + public static class BoundingBoxTests + { + public class ConstructorTests + { + [Fact] + public void InconsistentZValue_ThrowsException() + { + Assert.Throws(() => new BoundingBox(1, 1, null, 2, 2, 2)); + Assert.Throws(() => new BoundingBox(1, 1, 1, 2, 2, null)); + } + } + + public class EqualsTests + { + [Fact] + public void BoundingBoxes_WithoutZ_Match() + { + var a = new BoundingBox(1, 1, 2, 2); + var b = new BoundingBox(1, 1, 2, 2); + + Assert.True(a.Equals(b)); + } + + [Fact] + public void BoundingBoxes_WithZ_Match() + { + var a = new BoundingBox(1, 1, 1, 2, 2, 2); + var b = new BoundingBox(1, 1, 1, 2, 2, 2); + + Assert.True(a.Equals(b)); + } + + [Fact] + public void BoundingBoxes_ThatAreDifferent_DoNotMatch() + { + var a = new BoundingBox(1, 1, 2, 2); + var b = new BoundingBox(1, 1, 2, 99); + + Assert.False(a.Equals(b)); + } + + [Fact] + public void BoundingBoxes_WithMismatchZ_DoNotMatch() + { + var a = new BoundingBox(1, 1, 1, 2, 2, 2); + var b = new BoundingBox(1, 1, 2, 2); + + Assert.False(a.Equals(b)); + } + } + + public class GetHashCodeTests + { + [Fact] + public void BoundingBoxes_WithoutZ_Match() + { + var a = new BoundingBox(1, 1, 2, 2); + var b = new BoundingBox(1, 1, 2, 2); + + Assert.Equal(a.GetHashCode(), b.GetHashCode()); + } + + [Fact] + public void BoundingBoxes_WithZ_Match() + { + var a = new BoundingBox(1, 1, 1, 2, 2, 2); + var b = new BoundingBox(1, 1, 1, 2, 2, 2); + + Assert.Equal(a.GetHashCode(), b.GetHashCode()); + } + + [Fact] + public void BoundingBoxes_ThatAreDifferent_DoNotMatch() + { + var a = new BoundingBox(1, 1, 2, 2); + var b = new BoundingBox(1, 1, 2, 99); + + Assert.NotEqual(a.GetHashCode(), b.GetHashCode()); + } + + [Fact] + public void BoundingBoxes_WithMismatchZ_DoNotMatch() + { + var a = new BoundingBox(1, 1, 1, 2, 2, 2); + var b = new BoundingBox(1, 1, 2, 2); + + Assert.NotEqual(a.GetHashCode(), b.GetHashCode()); + } + } + } +} diff --git a/Wkx.Tests/MathUtilTests.cs b/Wkx.Tests/MathUtilTests.cs new file mode 100644 index 0000000..162719d --- /dev/null +++ b/Wkx.Tests/MathUtilTests.cs @@ -0,0 +1,53 @@ +namespace Wkx.Tests +{ + using Xunit; + + public static class MathUtilTests + { + public class MinTests + { + [Fact] + public void BothValues_AreNull_ReturnNull() + { + Assert.Null(MathUtil.Min(null, null)); + } + + [Fact] + public void OneValue_IsNull_ReturnNonNullValue() + { + Assert.Equal(0, MathUtil.Min(0, null)); + Assert.Equal(0, MathUtil.Min(null, 0)); + } + + [Fact] + public void NoNullValues_ReturnMinValue() + { + Assert.Equal(1, MathUtil.Min(1, 2)); + Assert.Equal(1, MathUtil.Min(2, 1)); + } + } + + public class MaxTests + { + [Fact] + public void BothValues_AreNull_ReturnNull() + { + Assert.Null(MathUtil.Max(null, null)); + } + + [Fact] + public void OneValue_IsNull_ReturnNonNullValue() + { + Assert.Equal(0, MathUtil.Max(0, null)); + Assert.Equal(0, MathUtil.Max(null, 0)); + } + + [Fact] + public void NoNullValues_ReturnMaxValue() + { + Assert.Equal(2, MathUtil.Max(1, 2)); + Assert.Equal(2, MathUtil.Max(2, 1)); + } + } + } +} diff --git a/Wkx.Tests/PolyhedralSurfaceTests.cs b/Wkx.Tests/PolyhedralSurfaceTests.cs new file mode 100644 index 0000000..04131e4 --- /dev/null +++ b/Wkx.Tests/PolyhedralSurfaceTests.cs @@ -0,0 +1,27 @@ +namespace Wkx.Tests +{ + using Xunit; + + public static class PolyhedralSurfaceTests + { + public class GetBoundingBoxTests + { + [Fact] + public void BoundingBox_Calculated() + { + var target = new PolyhedralSurface( + new[] { + new Polygon(new[] { new Point(0,0,0), new Point(1,1,0), new Point(0,2,0) }), + new Polygon(new[] { new Point(0,0,0), new Point(1,1,0), new Point(1,1,1) }), + new Polygon(new[] { new Point(1,1,0), new Point(1,1,1), new Point(0,2,0) }), + new Polygon(new[] { new Point(0,2,0), new Point(1,1,1), new Point(0,0,0) }) + }); + + var expected = new BoundingBox(0, 0, 0, 1, 2, 1); + var actual = target.GetBoundingBox(); + + Assert.Equal(expected, actual); + } + } + } +} diff --git a/Wkx/BoundingBox.cs b/Wkx/BoundingBox.cs index d8dfe7f..7852dad 100644 --- a/Wkx/BoundingBox.cs +++ b/Wkx/BoundingBox.cs @@ -4,21 +4,37 @@ namespace Wkx { public class BoundingBox : IEquatable { + public Dimension Dimension => ZMin.HasValue ? Dimension.Xyz : Dimension.Xy; + public double XMin { get; private set; } public double YMin { get; private set; } + public double? ZMin { get; private set; } public double XMax { get; private set; } public double YMax { get; private set; } + public double? ZMax { get; private set; } public BoundingBox() { } - public BoundingBox(double xMin, double yMin, double xMax, double yMax) + public BoundingBox(double xMin, double yMin, double? zMin, double xMax, double yMax, double? zMax) { + if (zMin.HasValue != zMax.HasValue) + { + throw new ArgumentNullException(zMin.HasValue ? nameof(zMax) : nameof(zMin)); + } + XMin = xMin; YMin = yMin; + ZMin = zMin; XMax = xMax; YMax = yMax; + ZMax = zMax; + } + + public BoundingBox(double xMin, double yMin, double xMax, double yMax) + :this(xMin, yMin, null, xMax, yMax, null) + { } public override bool Equals(object obj) @@ -33,13 +49,17 @@ public override bool Equals(object obj) public bool Equals(BoundingBox other) { - return XMin == other.XMin && YMin == other.YMin && - XMax == other.XMax && YMax == other.YMax; + return XMin == other.XMin + && YMin == other.YMin + && ZMin == other.ZMin + && XMax == other.XMax + && YMax == other.YMax + && ZMax == other.ZMax; } public override int GetHashCode() { - return new { XMin, YMin, XMax, YMax }.GetHashCode(); + return new { XMin, YMin, ZMin, XMax, YMax, ZMax }.GetHashCode(); } } } diff --git a/Wkx/Extensions/GeometryExtensions.cs b/Wkx/Extensions/GeometryExtensions.cs index e6efebc..b8b057c 100644 --- a/Wkx/Extensions/GeometryExtensions.cs +++ b/Wkx/Extensions/GeometryExtensions.cs @@ -62,36 +62,55 @@ internal static BoundingBox GetBoundingBox(this IEnumerable points) { double xMin = double.MaxValue; double yMin = double.MaxValue; + double? zMin = null; double xMax = -double.MaxValue; double yMax = -double.MaxValue; + double? zMax = null; foreach (Point point in points) { xMin = Math.Min(xMin, point.X.Value); yMin = Math.Min(yMin, point.Y.Value); + zMin = MathUtil.Min(zMin, point.Z); xMax = Math.Max(xMax, point.X.Value); yMax = Math.Max(yMax, point.Y.Value); + zMax = MathUtil.Max(zMax, point.Z); } - return new BoundingBox(xMin, yMin, xMax, yMax); + return new BoundingBox(xMin, yMin, zMin, xMax, yMax, zMax); } internal static BoundingBox GetBoundingBox(this IEnumerable boundingBoxes) { double xMin = double.MaxValue; double yMin = double.MaxValue; + double? zMin = null; double xMax = -double.MaxValue; double yMax = -double.MaxValue; + double? zMax = null; - foreach (BoundingBox boundingBox in boundingBoxes) + var enumerator = boundingBoxes.GetEnumerator(); + if (enumerator.MoveNext()) { - xMin = Math.Min(xMin, boundingBox.XMin); - yMin = Math.Min(yMin, boundingBox.YMin); - xMax = Math.Max(xMax, boundingBox.XMax); - yMax = Math.Max(yMax, boundingBox.YMax); + var dimension = enumerator.Current.Dimension; + do + { + var boundingBox = enumerator.Current; + if (dimension != boundingBox.Dimension) + { + throw new InvalidOperationException("Bounding box Z dimension inconsistent"); + } + + xMin = Math.Min(xMin, boundingBox.XMin); + yMin = Math.Min(yMin, boundingBox.YMin); + zMin = MathUtil.Min(zMin, boundingBox.ZMin); + xMax = Math.Max(xMax, boundingBox.XMax); + yMax = Math.Max(yMax, boundingBox.YMax); + zMax = MathUtil.Max(zMax, boundingBox.ZMax); + } while (enumerator.MoveNext()); } - return new BoundingBox(xMin, yMin, xMax, yMax); + return new BoundingBox(xMin, yMin, zMin, xMax, yMax, zMax); } internal static double AngleBetween(this Point point, Point otherPoint) diff --git a/Wkx/MathUtil.cs b/Wkx/MathUtil.cs index 9c44f3e..52f0a68 100644 --- a/Wkx/MathUtil.cs +++ b/Wkx/MathUtil.cs @@ -99,5 +99,25 @@ internal static double CalculateSweepAngle(Point startPoint, Point intermediateP Point vc = new Point(endPoint.X.Value - cx, endPoint.Y.Value - cy); return va.AngleBetween(vb) + vb.AngleBetween(vc); } + + internal static double? Min(double? a, double? b) + { + if (a == null || b == null) + { + return a ?? b; + } + + return Math.Min(a.Value, b.Value); + } + + internal static double? Max(double? a, double? b) + { + if (a == null || b == null) + { + return a ?? b; + } + + return Math.Max(a.Value, b.Value); + } } } diff --git a/Wkx/Point.cs b/Wkx/Point.cs index 2b5f7c7..6d7256c 100644 --- a/Wkx/Point.cs +++ b/Wkx/Point.cs @@ -61,7 +61,7 @@ public override Point GetCenter() public override BoundingBox GetBoundingBox() { - return new BoundingBox(X.Value, Y.Value, X.Value, Y.Value); + return new BoundingBox(X.Value, Y.Value, Z, X.Value, Y.Value, Z); } public override Geometry CurveToLine(double tolerance)