From 180ddab31eaf39ef78ed9c1056bdfec3c8c7925f Mon Sep 17 00:00:00 2001 From: Pieter Geerkens Date: Mon, 8 Apr 2019 06:32:08 -0400 Subject: [PATCH] Merge with HexgridUtilitiesExamples - Checkpoint #5 - FOV complete --- HexUtilities/FieldOfView/FovFactory.cs | 21 ++- HexUtilities/FieldOfView/IFovBoard.cs | 2 +- HexUtilities/FieldOfView/RiseRun.cs | 44 +++---- HexUtilities/FieldOfView/ShadowCastingFov.cs | 31 +++-- .../FieldOfView/ShadowCastingFov_Utilities.cs | 124 +++++++----------- 5 files changed, 97 insertions(+), 125 deletions(-) diff --git a/HexUtilities/FieldOfView/FovFactory.cs b/HexUtilities/FieldOfView/FovFactory.cs index c7483db..9d878fe 100644 --- a/HexUtilities/FieldOfView/FovFactory.cs +++ b/HexUtilities/FieldOfView/FovFactory.cs @@ -3,7 +3,6 @@ // THis software may be used under the terms of attached file License.md (The MIT License). /////////////////////////////////////////////////////////////////////////////////////////// #endregion -using System; using System.Threading.Tasks; using PGNapoleonics.HexUtilities.Common; @@ -11,40 +10,35 @@ namespace PGNapoleonics.HexUtilities.FieldOfView { /// Extension methods for interface IFovBoard {IHex}. public static class FovFactory { - /// Returns the field-of-view on board from the hex specified by coordinates coords. - [Obsolete("Use GetFieldOfView(HexCoords origin, int fovRadius) instead.")] - public static IShadingMask GetFov(this IFovBoard @this, HexCoords origin, int fovRadius) - => @this.GetFieldOfView(origin, fovRadius, FovTargetMode.EqualHeights, 1, 0); - /// Gets a Field-of-View for this board asynchronously, assuming a flat earth. public static Task GetFieldOfViewAsync(this IFovBoard @this, HexCoords origin, int fovRadius) - => Task.Run(() => @this.GetFieldOfView(origin, fovRadius, FovTargetMode.EqualHeights, 1, 0)); + => Task.Run(() => @this.GetFieldOfView(origin, fovRadius, FovTargetMode.EqualHeights, 1, 0)); /// Gets a Field-of-View for this board asynchronously, assuming a flat earth. public static Task GetFieldOfViewAsync(this IFovBoard @this, HexCoords origin, int fovRadius, int height) - => Task.Run(() => @this.GetFieldOfView(origin, fovRadius, FovTargetMode.EqualHeights, 1, 0)); + => Task.Run(() => @this.GetFieldOfView(origin, fovRadius, FovTargetMode.EqualHeights, 1, 0)); /// Gets a Field-of-View for this board asynchronously, assuming a flat earth. public static Task GetFieldOfViewAsync(this IFovBoard @this, HexCoords origin, int fovRadius, FovTargetMode targetMode) - => Task.Run(() => @this.GetFieldOfView(origin, fovRadius, targetMode, 1, 0)); + => Task.Run(() => @this.GetFieldOfView(origin, fovRadius, targetMode, 1, 0)); /// Gets a Field-of-View for this board asynchronously, assuming a flat earth. public static Task GetFieldOfViewAsync(this IFovBoard @this, HexCoords origin, int fovRadius, FovTargetMode targetMode, int height) - => Task.Run(() => @this.GetFieldOfView(origin, fovRadius, targetMode, height, 0)); + => Task.Run(() => @this.GetFieldOfView(origin, fovRadius, targetMode, height, 0)); /// Gets a Field-of-View for this board asynchronously. public static Task GetFieldOfViewAsync(this IFovBoard @this, HexCoords origin, int fovRadius, FovTargetMode targetMode, int height, int hexesPerMile) - => Task.Run(() => @this.GetFieldOfView(origin, fovRadius, targetMode, height, hexesPerMile)); + => Task.Run(() => @this.GetFieldOfView(origin, fovRadius, targetMode, height, hexesPerMile)); /// Gets a Field-of-View for this board synchronously, assuming a flat earth. public static IShadingMask GetFieldOfView(this IFovBoard @this, HexCoords origin, int fovRadius) - => @this.GetFieldOfView(origin, fovRadius, FovTargetMode.EqualHeights, 1, 0); + => @this.GetFieldOfView(origin, fovRadius, FovTargetMode.EqualHeights, 1, 0); /// Gets a Field-of-View for this board synchronously. public static IShadingMask GetFieldOfView(this IFovBoard @this, HexCoords origin, @@ -52,7 +46,8 @@ public static IShadingMask GetFov(this IFovBoard @this, HexCoords origin, int fo Tracing.FieldOfView.Trace("GetFieldOfView"); var fov = new ArrayFieldOfView(@this); if (@this.IsOverseeable(origin)) - ShadowCasting.ComputeFieldOfView(@this, origin, fovRadius, targetMode, coords => fov[coords] = true, heightOfMan, hexesPerMile); + ShadowCasting.ComputeFieldOfView(@this, origin, fovRadius, targetMode, + coords => fov[coords] = true, heightOfMan, hexesPerMile); return fov; } diff --git a/HexUtilities/FieldOfView/IFovBoard.cs b/HexUtilities/FieldOfView/IFovBoard.cs index ac69292..0820314 100644 --- a/HexUtilities/FieldOfView/IFovBoard.cs +++ b/HexUtilities/FieldOfView/IFovBoard.cs @@ -12,7 +12,7 @@ namespace PGNapoleonics.HexUtilities.FieldOfView { /// Enumeration of line-of-sight modes public enum FovTargetMode { /// Target height and observer height both set to the same constant value - /// (ShadowCasting.DefaultHeight) above ground eleevation + /// (ShadowCasting.DefaultHeight) above ground elevation EqualHeights, /// Use actual observer height and ground level as target height. TargetHeightEqualZero, diff --git a/HexUtilities/FieldOfView/RiseRun.cs b/HexUtilities/FieldOfView/RiseRun.cs index 63a0485..9496744 100644 --- a/HexUtilities/FieldOfView/RiseRun.cs +++ b/HexUtilities/FieldOfView/RiseRun.cs @@ -33,7 +33,6 @@ namespace PGNapoleonics.HexUtilities.FieldOfView { /// TODO [DebuggerDisplay("RiseRun: ({Rise} over {Run})")] public struct RiseRun : IEquatable, IComparable { - #region Constructors /// Creates a new instance of the RiseRUn struct. /// /// @@ -41,17 +40,29 @@ public struct RiseRun : IEquatable, IComparable { Rise = rise; Run = run; } - #endregion - #region Properties - /// Delta-height in units of elevation: meters. + /// Delta-height in units of elevation: feet. public int Rise { get; } /// Delta-width in units of distance: hexes. public int Run { get; } - #endregion - /// - public override string ToString() => $"Rise={Rise}; Run={Run}"; + #region Operators and IComparable implementations: + /// Less Than operator + public static bool operator < (RiseRun lhs, RiseRun rhs) => lhs.CompareTo(rhs) < 0; + + /// Less Than or Equals operator + public static bool operator <= (RiseRun lhs, RiseRun rhs) => lhs.CompareTo(rhs) <= 0; + + /// Greater Thanoperator + public static bool operator > (RiseRun lhs, RiseRun rhs) => lhs.CompareTo(rhs) > 0; + + /// Greater Than or Equals operator + public static bool operator >= (RiseRun lhs, RiseRun rhs) => lhs.CompareTo(rhs) >= 0; + + /// Less-Than comparaator. + public int CompareTo(RiseRun other) => (Rise * other.Run).CompareTo(other.Rise * Run); + + #endregion #region Value Equality with IEquatable /// @@ -71,22 +82,7 @@ public override int GetHashCode() => Run != 0 ? (Rise / Run).GetHashCode() public static bool operator == (RiseRun lhs, RiseRun rhs) => lhs.CompareTo(rhs) == 0; #endregion - #region Operators and IComparable implementations: - /// Less Than operator - public static bool operator < (RiseRun lhs, RiseRun rhs) => lhs.CompareTo(rhs) < 0; - - /// Less Than or Equals operator - public static bool operator <= (RiseRun lhs, RiseRun rhs) => lhs.CompareTo(rhs) <= 0; - - /// Greater Thanoperator - public static bool operator > (RiseRun lhs, RiseRun rhs) => lhs.CompareTo(rhs) > 0; - - /// Greater Than or Equals operator - public static bool operator >= (RiseRun lhs, RiseRun rhs) => lhs.CompareTo(rhs) >= 0; - - /// Less-Than comparaator. - public int CompareTo(RiseRun other) => (Rise * other.Run).CompareTo(other.Rise * Run); - - #endregion + /// + public override string ToString() => $"Rise={Rise}; Run={Run}"; } } diff --git a/HexUtilities/FieldOfView/ShadowCastingFov.cs b/HexUtilities/FieldOfView/ShadowCastingFov.cs index e0d8493..c4f80d5 100644 --- a/HexUtilities/FieldOfView/ShadowCastingFov.cs +++ b/HexUtilities/FieldOfView/ShadowCastingFov.cs @@ -12,7 +12,6 @@ namespace PGNapoleonics.HexUtilities.FieldOfView { /// Credit: Eric Lippert - /// Shadow Casting in C# Part Six /// /// It is important for realiism that observerHeight > board[observerCoords].ElevationASL, /// or no parallax over the current ground elevation will be observed. TerrainHeight is the @@ -20,6 +19,9 @@ namespace PGNapoleonics.HexUtilities.FieldOfView { /// but sometimes occuppying units will block vision as well. Control this in the implementation /// of IFovBoard {IHex} with the definition of the observerHeight, targetHeight, and /// terrainHeight delegates. + /// + /// See: + /// Shadow Casting in C# Part Six /// public static partial class ShadowCasting { /// Get or set whether to force serial execution of FOV calculation. @@ -129,11 +131,11 @@ int calculationHeightUnits if (hexesPerMile == 0) { return coords => 0; } else if(UseMetric) { - return ( coords => (coordsObserver.Range(coords) * coordsObserver.Range(coords)) - * calculationHeightUnits * 2 / (9 * hexesPerMile * hexesPerMile) ); + return coords => coordsObserver.Range(coords) * coordsObserver.Range(coords) + * calculationHeightUnits * 2 / (9 * hexesPerMile * hexesPerMile); } else { - return ( coords => (coordsObserver.Range(coords) * coordsObserver.Range(coords)) - * calculationHeightUnits * 2 / (3 * hexesPerMile * hexesPerMile) ); + return coords => coordsObserver.Range(coords) * coordsObserver.Range(coords) + * calculationHeightUnits * 2 / (3 * hexesPerMile * hexesPerMile); } } @@ -154,14 +156,14 @@ int calculationHeightUnits Func heightTerrain, Action setFieldOfView ) { - FieldOfViewTrace(true, " - Coords = " + coordsObserver.User.ToString()); + FieldOfViewTrace(true, $" - Coords = {coordsObserver.User.ToString()}"); var matrixOrigin = new IntMatrix2D(coordsObserver.Canon); if (radius >= 0) setFieldOfView(coordsObserver); // Always visible to self! - Action dodecantFov = d => { + void dodecantFov(Dodecant d) { var dodecant = new Dodecant(d,matrixOrigin); - _mapCoordsDodecant = hex => dodecant.TranslateDodecant(v=>v)(hex); + _mapCoordsDodecant = hex => dodecant.TranslateDodecant(v => v)(hex); ComputeFieldOfViewInDodecantZero( radius, @@ -171,7 +173,7 @@ Action setFieldOfView dodecant.TranslateDodecant(heightTerrain), dodecant.TranslateDodecant(setFieldOfView) ); - }; + } #if DEBUG InSerial = true; #endif @@ -258,7 +260,7 @@ FovCone cone // track the overlap-cone between adjacent hexes as we move down. var overlapVector = cone.VectorTop; var hexX = XFromVector(range, topVector); - FieldOfViewTrace(false, "DQ: ({0}) from {1}", cone, hexX); + FieldOfViewTrace(false, $"DQ: ({cone}) from {hexX}"); do { while (overlapVector.GT(bottomVector)) { @@ -278,8 +280,8 @@ FovCone cone && bottomVector.LE(coordsCurrent.Canon) && coordsCurrent.Canon.LE(topVector) ) { setFieldOfView(coordsCurrent); - FieldOfViewTrace(false," Set visible: {0} / {1}; {2} >= {3}", - _mapCoordsDodecant(coordsCurrent), coordsCurrent.ToString(), riseRun, cone.RiseRun); + FieldOfViewTrace(false, + $" Set visible: {_mapCoordsDodecant(coordsCurrent)} / {coordsCurrent.ToString()}; {riseRun} >= {cone.RiseRun}"); } #endregion @@ -312,7 +314,7 @@ FovCone cone topVector = LogAndEnqueue(enqueue, range, topVector, bottomVector, topRiseRun, 4); break; } - FieldOfViewTrace(false, "DQ: ({0}) from {1}", cone, hexX); + FieldOfViewTrace(false, $"DQ: ({cone}) from {hexX}"); } #endregion @@ -343,7 +345,9 @@ FovCone cone static Func _mapCoordsDodecant; static partial void FieldOfViewTrace(string format, params object[] paramArgs); + static partial void FieldOfViewTrace(bool newline, string format, params object[] paramArgs); + [Conditional("TRACE")] static partial void FieldOfViewTrace(string format, params object[] paramArgs) => Tracing.FieldOfView.Trace(format, paramArgs); @@ -351,7 +355,6 @@ FovCone cone [Conditional("TRACE")] static partial void FieldOfViewTrace(bool newline, string format, params object[] paramArgs) => Tracing.FieldOfView.Trace(newline, format, paramArgs); - #endregion } } diff --git a/HexUtilities/FieldOfView/ShadowCastingFov_Utilities.cs b/HexUtilities/FieldOfView/ShadowCastingFov_Utilities.cs index d6492af..29d2a30 100644 --- a/HexUtilities/FieldOfView/ShadowCastingFov_Utilities.cs +++ b/HexUtilities/FieldOfView/ShadowCastingFov_Utilities.cs @@ -1,83 +1,61 @@ -#region The MIT License - Copyright (C) 2012-2019 Pieter Geerkens -///////////////////////////////////////////////////////////////////////////////////////// -// PG Software Solutions - Hex-Grid Utilities -///////////////////////////////////////////////////////////////////////////////////////// -// The MIT License: -// ---------------- -// -// Copyright (c) 2012-2019 Pieter Geerkens (email: pgeerkens@users.noreply.github.com) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this -// software and associated documentation files (the "Software"), to deal in the Software -// without restriction, including without limitation the rights to use, copy, modify, -// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to the following -// conditions: -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -///////////////////////////////////////////////////////////////////////////////////////// +#region Copyright (c) 2012-2019 Pieter Geerkens (email: pgeerkens@users.noreply.github.com) +/////////////////////////////////////////////////////////////////////////////////////////// +// THis software may be used under the terms of attached file License.md (The MIT License). +/////////////////////////////////////////////////////////////////////////////////////////// #endregion using System; using System.Diagnostics.CodeAnalysis; namespace PGNapoleonics.HexUtilities.FieldOfView { - public static partial class ShadowCasting { - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "code")] - static IntVector2D LogAndEnqueue(Action enqueue, int range, IntVector2D top, - IntVector2D bottom, RiseRun riseRun, int code - ) { - if( top.GT(bottom)) { - var cone = new FovCone(range+1, top, bottom, riseRun); - FieldOfViewTrace(false, " EQ: ({0}) code: {1}", cone, code); - enqueue(cone); - return bottom; - } else { - return top; - } - } + public static partial class ShadowCasting { + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "code")] + static IntVector2D LogAndEnqueue(Action enqueue, int range, IntVector2D top, + IntVector2D bottom, RiseRun riseRun, int code + ) { + if( top.GT(bottom)) { + var cone = new FovCone(range+1, top, bottom, riseRun); + FieldOfViewTrace(false, " EQ: ({0}) code: {1}", cone, code); + enqueue(cone); + return bottom; + } else { + return top; + } + } - static int XFromVector(int y, IntVector2D v) { - return (-2 * v.Y + v.X * (3 * y + 1) + (3 * v.Y) - 1) / (3 * v.Y); - } - /// Helper IntMatrix2D for VectorHexTop. - static IntMatrix2D matrixHexTop = new IntMatrix2D(3,0, 0,3, 2, 1); - /// Helper IntMatrix2D for VectorHexBottom. - static IntMatrix2D matrixHexBottom = new IntMatrix2D(3,0, 0,3, -2,-1); + static int XFromVector(int y,IntVector2D v) => (-2 * v.Y + v.X * (3 * y + 1) + (3 * v.Y) - 1) / (3 * v.Y); + + /// Helper IntMatrix2D for VectorHexTop. + static IntMatrix2D matrixHexTop = new IntMatrix2D(3,0, 0,3, 2, 1); + + /// Helper IntMatrix2D for VectorHexBottom. + static IntMatrix2D matrixHexBottom = new IntMatrix2D(3,0, 0,3, -2,-1); + + /// IntVector2D for top corner of cell Canon(i,j). + /// + /// In first dodecant; The top corner for hex (i,j) is determined + /// (from close visual inspection) as: + /// (i,j) + 1/3 * (2,1) + /// which reduces to: + /// (i + 2/3, j + 1/3) == 1/3 * (3x + 2, 3y + 1) + /// + static IntVector2D VectorHexTop(HexCoords hex) => hex.Canon * matrixHexTop; + + /// IntVector2D for bottom corner of cell Canon(i,j). + /// + /// In first dodecant; The bottom corner for hex (i,j) is determined + /// (from close visual inspection) as: + /// (i,j) + 1/3 * (-2,-1) + /// which reduces to: + /// (i - 2/3, j - 1/3) == 1/3 * (3x - 2, 3y - 1) + /// + static IntVector2D VectorHexBottom(HexCoords hex) => hex.Canon * matrixHexBottom; + + #region These are here (instead of IntVector2D.cs) because they are "upside-down" for regular use. + static IntVector2D VectorMax(IntVector2D lhs,IntVector2D rhs) => lhs.GT(rhs) ? lhs : rhs; - /// IntVector2D for top corner of cell Canon(i,j). - /// - /// In first dodecant; The top corner for hex (i,j) is determined - /// (from close visual inspection) as: - /// (i,j) + 1/3 * (2,1) - /// which reduces to: - /// (i + 2/3, j + 1/3) == 1/3 * (3x + 2, 3y + 1) - /// - static IntVector2D VectorHexTop(HexCoords hex) { return hex.Canon * matrixHexTop; } - /// IntVector2D for bottom corner of cell Canon(i,j). - /// - /// In first dodecant; The bottom corner for hex (i,j) is determined - /// (from close visual inspection) as: - /// (i,j) + 1/3 * (-2,-1) - /// which reduces to: - /// (i - 2/3, j - 1/3) == 1/3 * (3x - 2, 3y - 1) - /// - static IntVector2D VectorHexBottom(HexCoords hex) { return hex.Canon * matrixHexBottom; } + static bool GT(this IntVector2D lhs,IntVector2D rhs) => lhs.X*rhs.Y > lhs.Y*rhs.X; - #region These are here (instead of IntVector2D.cs) because they are "upside-down" for regular use. - static IntVector2D VectorMax(IntVector2D lhs, IntVector2D rhs) { - return lhs.GT(rhs) ? lhs : rhs; + static bool LE(this IntVector2D lhs,IntVector2D rhs) => !lhs.GT(rhs); + #endregion } - static bool GT(this IntVector2D lhs, IntVector2D rhs) { return lhs.X*rhs.Y > lhs.Y*rhs.X; } - static bool LE(this IntVector2D lhs, IntVector2D rhs) { return ! lhs.GT(rhs); } - #endregion - } }