// Copyright (c) 2013-2015 Robert Rouhani and other contributors (see CONTRIBUTORS file). // Licensed under the MIT License - https://raw.github.com/Robmaister/SharpNav/master/LICENSE using System; using System.Collections.Generic; namespace SharpNav { /// /// Flags that can be applied to a region. /// [Flags] public enum RegionFlags { /// /// The border flag /// Border = 0x20000000, /// /// The vertex border flag /// VertexBorder = 0x40000000, /// /// The area border flag /// AreaBorder = unchecked((int)0x80000000) } /// /// A is an identifier with flags marking borders. /// [Serializable] public struct RegionId : IEquatable, IEquatable { /// /// A null region is one with an ID of 0. /// public static readonly RegionId Null = new RegionId(0, 0); /// /// A bitmask /// public const int MaskId = 0x1fffffff; /// /// The internal storage of a . The portion are the most /// significant bits, the integer identifier are the least significant bits, marked by . /// private int bits; /// /// Initializes a new instance of the struct without any flags. /// /// The identifier. public RegionId(int id) : this(id, 0) { } /// /// Initializes a new instance of the struct. /// /// /// public RegionId(int id, RegionFlags flags) { int masked = id & MaskId; if (masked != id) throw new ArgumentOutOfRangeException("id", "The provided id is outside of the valid range. The 3 most significant bits must be 0. Maybe you wanted RegionId.FromRawBits()?"); if ((RegionFlags)((int)flags & ~MaskId) != flags) throw new ArgumentException("flags", "The provide region flags are invalid."); bits = masked | (int)flags; } /// /// Gets the ID of the region without any flags. /// public int Id { get { return bits & MaskId; } } /// /// Gets the flags set for this region. /// public RegionFlags Flags { get { return (RegionFlags)(bits & ~MaskId); } } /// /// Gets a value indicating whether the region is the null region (ID == 0). /// public bool IsNull { get { return (bits & MaskId) == 0; } } /// /// Creates a new from a value that contains both the region ID and the flags. /// /// The int containing data. /// A new instance of the struct with the specified data. public static RegionId FromRawBits(int bits) { RegionId id; id.bits = bits; return id; } /// /// Creates a new with extra flags. /// /// The region to add flags to. /// The flags to add. /// A new instance of the struct with extra flags. public static RegionId WithFlags(RegionId region, RegionFlags flags) { if ((RegionFlags)((int)flags & ~MaskId) != flags) throw new ArgumentException("flags", "The provide region flags are invalid."); RegionFlags newFlags = region.Flags | flags; return RegionId.FromRawBits((region.bits & MaskId) | (int)newFlags); } /// /// Creates a new instance of the class without any flags set. /// /// The region to use. /// A new instance of the struct without any flags set. public static RegionId WithoutFlags(RegionId region) { return new RegionId(region.Id); } /// /// Creates a new instance of the class without certain flags set. /// /// The region to use. /// The flags to unset. /// A new instnace of the struct without certain flags set. public static RegionId WithoutFlags(RegionId region, RegionFlags flags) { if ((RegionFlags)((int)flags & ~MaskId) != flags) throw new ArgumentException("flags", "The provide region flags are invalid."); RegionFlags newFlags = region.Flags & ~flags; return RegionId.FromRawBits((region.bits & MaskId) | (int)newFlags); } /// /// Checks if a region has certain flags. /// /// The region to check. /// The flags to check. /// A value indicating whether the region has all of the specified flags. public static bool HasFlags(RegionId region, RegionFlags flags) { return (region.Flags & flags) != 0; } /// /// Compares an instance of with an integer for equality. /// /// /// This checks for both the ID and flags set on the region. If you want to only compare the IDs, use the /// following code: /// /// RegionId left = ...; /// int right = ...; /// if (left.Id == right) /// { /// // ... /// } /// /// /// An instance of . /// An integer. /// A value indicating whether the two values are equal. public static bool operator ==(RegionId left, int right) { return left.Equals(right); } /// /// Compares an instance of with an integer for inequality. /// /// /// This checks for both the ID and flags set on the region. If you want to only compare the IDs, use the /// following code: /// /// RegionId left = ...; /// int right = ...; /// if (left.Id != right) /// { /// // ... /// } /// /// /// An instance of . /// An integer. /// A value indicating whether the two values are unequal. public static bool operator !=(RegionId left, int right) { return !(left == right); } /// /// Compares two instances of for equality. /// /// /// This checks for both the ID and flags set on the regions. If you want to only compare the IDs, use the /// following code: /// /// RegionId left = ...; /// RegionId right = ...; /// if (left.Id == right.Id) /// { /// // ... /// } /// /// /// An instance of . /// Another instance of . /// A value indicating whether the two instances are equal. public static bool operator ==(RegionId left, RegionId right) { return left.Equals(right); } /// /// Compares two instances of for inequality. /// /// /// This checks for both the ID and flags set on the regions. If you want to only compare the IDs, use the /// following code: /// /// RegionId left = ...; /// RegionId right = ...; /// if (left.Id != right.Id) /// { /// // ... /// } /// /// /// An instance of . /// Another instance of . /// A value indicating whether the two instances are unequal. public static bool operator !=(RegionId left, RegionId right) { return !(left == right); } /// /// Converts an instance of to an integer containing both the ID and the flags. /// /// An instance of . /// An integer. public static explicit operator int(RegionId id) { return id.bits; } /// /// Compares this instance with another instance of for equality, including flags. /// /// An instance of . /// A value indicating whether the two instances are equal. public bool Equals(RegionId other) { bool thisNull = this.IsNull; bool otherNull = other.IsNull; if (thisNull && otherNull) return true; else if (thisNull ^ otherNull) return false; else return this.bits == other.bits; } /// /// Compares this instance with another an intenger for equality, including flags. /// /// An integer. /// A value indicating whether the two instances are equal. public bool Equals(int other) { RegionId otherId; otherId.bits = other; return this.Equals(otherId); } /// /// Compares this instance with an object for equality. /// /// An object /// A value indicating whether the two instances are equal. public override bool Equals(object obj) { var regObj = obj as RegionId?; var intObj = obj as int?; if (regObj.HasValue) return this.Equals(regObj.Value); else if (intObj.HasValue) return this.Equals(intObj.Value); else return false; } /// /// Gets a unique hash code for this instance. /// /// A hash code. public override int GetHashCode() { if (IsNull) return 0; return bits.GetHashCode(); } /// /// Gets a human-readable version of this instance. /// /// A string representing this instance. public override string ToString() { return "{ Id: " + Id + ", Flags: " + Flags + "}"; } } /// /// A Region contains a group of adjacent spans. /// public class Region { private int spanCount; private RegionId id; private Area areaType; private bool remap; private bool visited; private List connections; private List floors; /// /// Initializes a new instance of the class. /// /// The id public Region(int idNum) { spanCount = 0; id = new RegionId(idNum); areaType = 0; remap = false; visited = false; connections = new List(); floors = new List(); } /// /// Gets or sets the number of spans /// public int SpanCount { get { return spanCount; } set { this.spanCount = value; } } /// /// Gets or sets the region id /// public RegionId Id { get { return id; } set { this.id = value; } } /// /// Gets or sets the AreaType of this region /// public Area AreaType { get { return areaType; } set { this.areaType = value; } } /// /// Gets or sets a value indicating whether this region has been remapped or not /// public bool Remap { get { return remap; } set { this.remap = value; } } /// /// Gets or sets a value indicating whether this region has been visited or not /// public bool Visited { get { return visited; } set { this.visited = value; } } /// /// Gets the list of floor regions /// public List FloorRegions { get { return floors; } } /// /// Gets the list of connected regions /// public List Connections { get { return connections; } } /// /// Gets a value indicating whether the region is a border region. /// public bool IsBorder { get { return RegionId.HasFlags(id, RegionFlags.Border); } } /// /// Gets a value indicating whether the region is either a border region or the null region. /// public bool IsBorderOrNull { get { return id.IsNull || IsBorder; } } /// /// Remove adjacent connections if there is a duplicate /// public void RemoveAdjacentNeighbours() { if (connections.Count <= 1) return; // Remove adjacent duplicates. for (int i = 0; i < connections.Count; i++) { //get the next i int ni = (i + 1) % connections.Count; //remove duplicate if found if (connections[i] == connections[ni]) { connections.RemoveAt(i); i--; } } } /// /// Replace all connection and floor values /// /// The value you want to replace /// The new value that will be used public void ReplaceNeighbour(RegionId oldId, RegionId newId) { //replace the connections bool neiChanged = false; for (int i = 0; i < connections.Count; ++i) { if (connections[i] == oldId) { connections[i] = newId; neiChanged = true; } } //replace the floors for (int i = 0; i < floors.Count; ++i) { if (floors[i] == oldId) floors[i] = newId; } //make sure to remove adjacent neighbors if (neiChanged) RemoveAdjacentNeighbours(); } /// /// Determine whether this region can merge with another region. /// /// The other region to merge with /// True if the two regions can be merged, false if otherwise public bool CanMergeWith(Region otherRegion) { //make sure areas are the same if (areaType != otherRegion.areaType) return false; //count the number of connections to the other region int n = 0; for (int i = 0; i < connections.Count; i++) { if (connections[i] == otherRegion.id) n++; } //make sure there's only one connection if (n > 1) return false; //make sure floors are separate if (floors.Contains(otherRegion.id)) return false; return true; } /// /// Only add a floor if it hasn't been added already /// /// The value of the floor public void AddUniqueFloorRegion(RegionId n) { if (!floors.Contains(n)) floors.Add(n); } /// /// Merge two regions into one. Needs good testing /// /// The region to merge with /// True if merged successfully, false if otherwise public bool MergeWithRegion(Region otherRegion) { RegionId thisId = id; RegionId otherId = otherRegion.id; // Duplicate current neighbourhood. List thisConnected = new List(); for (int i = 0; i < connections.Count; ++i) thisConnected.Add(connections[i]); List otherConnected = otherRegion.connections; // Find insertion point on this region int insertInThis = -1; for (int i = 0; i < thisConnected.Count; ++i) { if (thisConnected[i] == otherId) { insertInThis = i; break; } } if (insertInThis == -1) return false; // Find insertion point on the other region int insertInOther = -1; for (int i = 0; i < otherConnected.Count; ++i) { if (otherConnected[i] == thisId) { insertInOther = i; break; } } if (insertInOther == -1) return false; // Merge neighbours. connections = new List(); for (int i = 0, ni = thisConnected.Count; i < ni - 1; ++i) connections.Add(thisConnected[(insertInThis + 1 + i) % ni]); for (int i = 0, ni = otherConnected.Count; i < ni - 1; ++i) connections.Add(otherConnected[(insertInOther + 1 + i) % ni]); RemoveAdjacentNeighbours(); for (int j = 0; j < otherRegion.floors.Count; ++j) AddUniqueFloorRegion(otherRegion.floors[j]); spanCount += otherRegion.spanCount; otherRegion.spanCount = 0; otherRegion.connections.Clear(); return true; } /// /// Test if region is connected to a border /// /// True if connected, false if not public bool IsConnectedToBorder() { // Region is connected to border if // one of the neighbours is null id. for (int i = 0; i < connections.Count; ++i) { if (connections[i] == 0) return true; } return false; } } }