using System; using System.Collections.Generic; using System.Windows; namespace AIStudio.Wpf.DiagramDesigner.Geometrys { [Serializable] public struct RectangleBase : IShape, IFormattable { public static RectangleBase Zero { get; } = new RectangleBase(0, 0, 0, 0, true); public RectangleBase(double left, double top, double right, double bottom, bool lefttoprightbottom) { _x = left; _y = top; _width = Math.Abs(left - right); _height = Math.Abs(top - bottom); } public RectangleBase(double x,double y,double width, double height) { if (width < 0 || height < 0) { throw new System.ArgumentException("Size_WidthAndHeightCannotBeNegative"); } _x = x; _y = y; _width = width; _height = height; } public RectangleBase(PointBase location, SizeBase size) { if (size.IsEmpty) { this = s_empty; } else { _x = location._x; _y = location._y; _width = size._width; _height = size._height; } } public RectangleBase(PointBase point1, PointBase point2) { _x = Math.Min(point1._x, point2._x); _y = Math.Min(point1._y, point2._y); // Max with 0 to prevent double weirdness from causing us to be (-epsilon..0) _width = Math.Max(Math.Max(point1._x, point2._x) - _x, 0); _height = Math.Max(Math.Max(point1._y, point2._y) - _y, 0); } public RectangleBase(SizeBase size) { if (size.IsEmpty) { this = s_empty; } else { _x = _y = 0; _width = size.Width; _height = size.Height; } } public bool Overlap(RectangleBase r) => Left < r.Right && Right > r.Left && Top < r.Bottom && Bottom > r.Top; public bool Intersects(RectangleBase r) { var thisX = Left; var thisY = Top; var thisW = Width; var thisH = Height; var rectX = r.Left; var rectY = r.Top; var rectW = r.Width; var rectH = r.Height; return rectX < thisX + thisW && thisX < rectX + rectW && rectY < thisY + thisH && thisY < rectY + rectH; } public RectangleBase InflateRectangle(double horizontal, double vertical) => new RectangleBase(Left - horizontal, Top - vertical, Right + horizontal, Bottom + vertical, true); public RectangleBase UnionRectangle(RectangleBase r) { var x1 = Math.Min(Left, r.Left); var x2 = Math.Max(Left + Width, r.Left + r.Width); var y1 = Math.Min(Top, r.Top); var y2 = Math.Max(Top + Height, r.Top + r.Height); return new RectangleBase(x1, y1, x2, y2, true); } public bool ContainsPoint(PointBase point) => ContainsPoint(point.X, point.Y); public bool ContainsPoint(double x, double y) => x >= Left && x <= Right && y >= Top && y <= Bottom; public IEnumerable GetIntersectionsWithLine(LineBase line) { var borders = new[] { new LineBase(NorthWest, NorthEast), new LineBase(NorthEast, SouthEast), new LineBase(SouthWest, SouthEast), new LineBase(NorthWest, SouthWest) }; for (var i = 0; i < borders.Length; i++) { var intersectionPt = borders[i].GetIntersection(line); if (intersectionPt != null) yield return intersectionPt.Value; } } public PointBase Center => new PointBase(Left + Width / 2, Top + Height / 2); public PointBase NorthEast => new PointBase(Right, Top); public PointBase SouthEast => new PointBase(Right, Bottom); public PointBase SouthWest => new PointBase(Left, Bottom); public PointBase NorthWest => new PointBase(Left, Top); public PointBase East => new PointBase(Right, Top + Height / 2); public PointBase North => new PointBase(Left + Width / 2, Top); public PointBase South => new PointBase(Left + Width / 2, Bottom); public PointBase West => new PointBase(Left, Top + Height / 2); //public bool Equals(Rectangle other) //{ // return other != null && Left == other.Left && Right == other.Right && Top == other.Top && // Bottom == other.Bottom && Width == other.Width && Height == other.Height; //} //public override string ToString() // => $"Rectangle(width={Width}, height={Height}, top={Top}, right={Right}, bottom={Bottom}, left={Left})"; #region Statics /// /// Empty - a static property which provides an Empty rectangle. X and Y are positive-infinity /// and Width and Height are negative infinity. This is the only situation where Width or /// Height can be negative. /// public static RectangleBase Empty { get { return s_empty; } } #endregion Statics #region Public Properties /// /// IsEmpty - this returns true if this rect is the Empty rectangle. /// Note: If width or height are 0 this Rectangle still contains a 0 or 1 dimensional set /// of points, so this method should not be used to check for 0 area. /// public bool IsEmpty { get { // The funny width and height tests are to handle NaNs //Debug.Assert((!(_width < 0) && !(_height < 0)) || (this == Empty)); return _width < 0; } } /// /// Location - The PointBase representing the origin of the Rectangle /// public PointBase Location { get { return new PointBase(_x, _y); } set { if (IsEmpty) { throw new System.InvalidOperationException("Rect_CannotModifyEmptyRect"); } _x = value._x; _y = value._y; } } /// /// Size - The Size representing the area of the Rectangle /// public SizeBase Size { get { if (IsEmpty) return SizeBase.Empty; return new SizeBase(_width, _height); } set { if (value.IsEmpty) { this = s_empty; } else { if (IsEmpty) { throw new System.InvalidOperationException("Rect_CannotModifyEmptyRect"); } _width = value._width; _height = value._height; } } } internal double _x; /// /// X - The X coordinate of the Location. /// If this is the empty rectangle, the value will be positive infinity. /// If this rect is Empty, setting this property is illegal. /// public double X { get { return _x; } set { if (IsEmpty) { throw new System.InvalidOperationException("Rect_CannotModifyEmptyRect"); } _x = value; } } internal double _y; /// /// Y - The Y coordinate of the Location /// If this is the empty rectangle, the value will be positive infinity. /// If this rect is Empty, setting this property is illegal. /// public double Y { get { return _y; } set { if (IsEmpty) { throw new System.InvalidOperationException("Rect_CannotModifyEmptyRect"); } _y = value; } } internal double _width; /// /// Width - The Width component of the Size. This cannot be set to negative, and will only /// be negative if this is the empty rectangle, in which case it will be negative infinity. /// If this rect is Empty, setting this property is illegal. /// public double Width { get { return _width; } set { if (IsEmpty) { throw new System.InvalidOperationException("Rect_CannotModifyEmptyRect"); } if (value < 0) { throw new System.ArgumentException("Size_WidthCannotBeNegative"); } _width = value; } } internal double _height; /// /// Height - The Height component of the Size. This cannot be set to negative, and will only /// be negative if this is the empty rectangle, in which case it will be negative infinity. /// If this rect is Empty, setting this property is illegal. /// public double Height { get { return _height; } set { if (IsEmpty) { throw new System.InvalidOperationException("Rect_CannotModifyEmptyRect"); } if (value < 0) { throw new System.ArgumentException("Size_HeightCannotBeNegative"); } _height = value; } } /// /// Left Property - This is a read-only alias for X /// If this is the empty rectangle, the value will be positive infinity. /// public double Left { get { return _x; } } /// /// Top Property - This is a read-only alias for Y /// If this is the empty rectangle, the value will be positive infinity. /// public double Top { get { return _y; } } /// /// Right Property - This is a read-only alias for X + Width /// If this is the empty rectangle, the value will be negative infinity. /// public double Right { get { if (IsEmpty) { return Double.NegativeInfinity; } return _x + _width; } } /// /// Bottom Property - This is a read-only alias for Y + Height /// If this is the empty rectangle, the value will be negative infinity. /// public double Bottom { get { if (IsEmpty) { return Double.NegativeInfinity; } return _y + _height; } } /// /// TopLeft Property - This is a read-only alias for the PointBase which is at X, Y /// If this is the empty rectangle, the value will be positive infinity, positive infinity. /// public PointBase TopLeft { get { return new PointBase(Left, Top); } } /// /// TopRight Property - This is a read-only alias for the PointBase which is at X + Width, Y /// If this is the empty rectangle, the value will be negative infinity, positive infinity. /// public PointBase TopRight { get { return new PointBase(Right, Top); } } /// /// BottomLeft Property - This is a read-only alias for the PointBase which is at X, Y + Height /// If this is the empty rectangle, the value will be positive infinity, negative infinity. /// public PointBase BottomLeft { get { return new PointBase(Left, Bottom); } } /// /// BottomRight Property - This is a read-only alias for the PointBase which is at X + Width, Y + Height /// If this is the empty rectangle, the value will be negative infinity, negative infinity. /// public PointBase BottomRight { get { return new PointBase(Right, Bottom); } } #endregion Public Properties #region Public Methods /// /// Contains - Returns true if the PointBase is within the rectangle, inclusive of the edges. /// Returns false otherwise. /// /// The point which is being tested /// /// Returns true if the PointBase is within the rectangle. /// Returns false otherwise /// public bool Contains(PointBase point) { return Contains(point._x, point._y); } /// /// Contains - Returns true if the PointBase represented by x,y is within the rectangle inclusive of the edges. /// Returns false otherwise. /// /// X coordinate of the point which is being tested /// Y coordinate of the point which is being tested /// /// Returns true if the PointBase represented by x,y is within the rectangle. /// Returns false otherwise. /// public bool Contains(double x, double y) { if (IsEmpty) { return false; } return ContainsInternal(x, y); } /// /// Contains - Returns true if the Rectangle non-Empty and is entirely contained within the /// rectangle, inclusive of the edges. /// Returns false otherwise /// public bool Contains(RectangleBase rect) { if (IsEmpty || rect.IsEmpty) { return false; } return (_x <= rect._x && _y <= rect._y && _x + _width >= rect._x + rect._width && _y + _height >= rect._y + rect._height); } /// /// IntersectsWith - Returns true if the Rectangle intersects with this rectangle /// Returns false otherwise. /// Note that if one edge is coincident, this is considered an intersection. /// /// /// Returns true if the Rectangle intersects with this rectangle /// Returns false otherwise. /// or Height /// /// Rectangle public bool IntersectsWith(RectangleBase rect) { if (IsEmpty || rect.IsEmpty) { return false; } return (rect.Left <= Right) && (rect.Right >= Left) && (rect.Top <= Bottom) && (rect.Bottom >= Top); } /// /// Intersect - Update this rectangle to be the intersection of this and rect /// If either this or rect are Empty, the result is Empty as well. /// /// The rect to intersect with this public void Intersect(RectangleBase rect) { if (!this.IntersectsWith(rect)) { //this = Empty; } else { double left = Math.Max(Left, rect.Left); double top = Math.Max(Top, rect.Top); // Max with 0 to prevent double weirdness from causing us to be (-epsilon..0) _width = Math.Max(Math.Min(Right, rect.Right) - left, 0); _height = Math.Max(Math.Min(Bottom, rect.Bottom) - top, 0); _x = left; _y = top; } } /// /// Intersect - Return the result of the intersection of rect1 and rect2. /// If either this or rect are Empty, the result is Empty as well. /// public static RectangleBase Intersect(RectangleBase rect1, RectangleBase rect2) { rect1.Intersect(rect2); return rect1; } /// /// Union - Update this rectangle to be the union of this and rect. /// public void Union(RectangleBase rect) { if (IsEmpty) { //this = rect; } else if (!rect.IsEmpty) { double left = Math.Min(Left, rect.Left); double top = Math.Min(Top, rect.Top); // We need this check so that the math does not result in NaN if ((rect.Width == Double.PositiveInfinity) || (Width == Double.PositiveInfinity)) { _width = Double.PositiveInfinity; } else { // Max with 0 to prevent double weirdness from causing us to be (-epsilon..0) double maxRight = Math.Max(Right, rect.Right); _width = Math.Max(maxRight - left, 0); } // We need this check so that the math does not result in NaN if ((rect.Height == Double.PositiveInfinity) || (Height == Double.PositiveInfinity)) { _height = Double.PositiveInfinity; } else { // Max with 0 to prevent double weirdness from causing us to be (-epsilon..0) double maxBottom = Math.Max(Bottom, rect.Bottom); _height = Math.Max(maxBottom - top, 0); } _x = left; _y = top; } } /// /// Union - Return the result of the union of rect1 and rect2. /// public static RectangleBase Union(RectangleBase rect1, RectangleBase rect2) { rect1.Union(rect2); return rect1; } /// /// Union - Update this rectangle to be the union of this and point. /// public void Union(PointBase point) { Union(new RectangleBase(point, point)); } /// /// Union - Return the result of the union of rect and point. /// public static RectangleBase Union(RectangleBase rect, PointBase point) { rect.Union(new RectangleBase(point, point)); return rect; } /// /// Offset - translate the Location by the offset provided. /// If this is Empty, this method is illegal. /// public void Offset(VectorBase offsetVector) { if (IsEmpty) { throw new System.InvalidOperationException("Rect_CannotCallMethod"); } _x += offsetVector._x; _y += offsetVector._y; } /// /// Offset - translate the Location by the offset provided /// If this is Empty, this method is illegal. /// public void Offset(double offsetX, double offsetY) { if (IsEmpty) { throw new System.InvalidOperationException("Rect_CannotCallMethod"); } _x += offsetX; _y += offsetY; } /// /// Offset - return the result of offsetting rect by the offset provided /// If this is Empty, this method is illegal. /// public static RectangleBase Offset(RectangleBase rect, VectorBase offsetVector) { rect.Offset(offsetVector.X, offsetVector.Y); return rect; } /// /// Offset - return the result of offsetting rect by the offset provided /// If this is Empty, this method is illegal. /// public static RectangleBase Offset(RectangleBase rect, double offsetX, double offsetY) { rect.Offset(offsetX, offsetY); return rect; } /// /// Inflate - inflate the bounds by the size provided, in all directions /// If this is Empty, this method is illegal. /// public void Inflate(SizeBase size) { Inflate(size._width, size._height); } /// /// Inflate - inflate the bounds by the size provided, in all directions. /// If -width is > Width / 2 or -height is > Height / 2, this Rectangle becomes Empty /// If this is Empty, this method is illegal. /// public void Inflate(double width, double height) { if (IsEmpty) { throw new System.InvalidOperationException("Rect_CannotCallMethod"); } _x -= width; _y -= height; // Do two additions rather than multiplication by 2 to avoid spurious overflow // That is: (A + 2 * B) != ((A + B) + B) if 2*B overflows. // Note that multiplication by 2 might work in this case because A should start // positive & be "clamped" to positive after, but consider A = Inf & B = -MAX. _width += width; _width += width; _height += height; _height += height; // We catch the case of inflation by less than -width/2 or -height/2 here. This also // maintains the invariant that either the Rectangle is Empty or _width and _height are // non-negative, even if the user parameters were NaN, though this isn't strictly maintained // by other methods. if (!(_width >= 0 && _height >= 0)) { this = s_empty; } } /// /// Inflate - return the result of inflating rect by the size provided, in all directions /// If this is Empty, this method is illegal. /// public static RectangleBase Inflate(RectangleBase rect, SizeBase size) { rect.Inflate(size._width, size._height); return rect; } /// /// Inflate - return the result of inflating rect by the size provided, in all directions /// If this is Empty, this method is illegal. /// public static RectangleBase Inflate(RectangleBase rect, double width, double height) { rect.Inflate(width, height); return rect; } /// /// Returns the bounds of the transformed rectangle. /// The Empty Rectangle is not affected by this call. /// /// /// The rect which results from the transformation. /// /// The Rectangle to transform. /// The Matrix by which to transform. //public static Rectangle Transform(Rectangle rect, Matrix matrix) //{ // MatrixUtil.TransformRect(ref rect, ref matrix); // return rect; //} /// /// Updates rectangle to be the bounds of the original value transformed /// by the matrix. /// The Empty Rectangle is not affected by this call. /// /// Matrix //public void Transform(Matrix matrix) //{ // MatrixUtil.TransformRect(ref this, ref matrix); //} /// /// Scale the rectangle in the X and Y directions /// /// The scale in X /// The scale in Y public void Scale(double scaleX, double scaleY) { if (IsEmpty) { return; } _x *= scaleX; _y *= scaleY; _width *= scaleX; _height *= scaleY; // If the scale in the X dimension is negative, we need to normalize X and Width if (scaleX < 0) { // Make X the left-most edge again _x += _width; // and make Width positive _width *= -1; } // Do the same for the Y dimension if (scaleY < 0) { // Make Y the top-most edge again _y += _height; // and make Height positive _height *= -1; } } #endregion Public Methods #region Private Methods /// /// ContainsInternal - Performs just the "point inside" logic /// /// /// bool - true if the point is inside the rect /// /// The x-coord of the point to test /// The y-coord of the point to test private bool ContainsInternal(double x, double y) { // We include points on the edge as "contained". // We do "x - _width <= _x" instead of "x <= _x + _width" // so that this check works when _width is PositiveInfinity // and _x is NegativeInfinity. return ((x >= _x) && (x - _width <= _x) && (y >= _y) && (y - _height <= _y)); } static private RectangleBase CreateEmptyRect() { RectangleBase rect = new RectangleBase(); // We can't set these via the property setters because negatives widths // are rejected in those APIs. rect._x = Double.PositiveInfinity; rect._y = Double.PositiveInfinity; rect._width = Double.NegativeInfinity; rect._height = Double.NegativeInfinity; return rect; } #endregion Private Methods #region Private Fields private readonly static RectangleBase s_empty = CreateEmptyRect(); #endregion Private Fields #region Internal Properties /// /// Creates a string representation of this object based on the current culture. /// /// /// A string representation of this object. /// public override string ToString() { // Delegate to the internal method which implements all ToString calls. return ConvertToString(null /* format string */, null /* format provider */); } /// /// Creates a string representation of this object based on the IFormatProvider /// passed in. If the provider is null, the CurrentCulture is used. /// /// /// A string representation of this object. /// public string ToString(IFormatProvider provider) { // Delegate to the internal method which implements all ToString calls. return ConvertToString(null /* format string */, provider); } /// /// Creates a string representation of this object based on the format string /// and IFormatProvider passed in. /// If the provider is null, the CurrentCulture is used. /// See the documentation for IFormattable for more information. /// /// /// A string representation of this object. /// string IFormattable.ToString(string format, IFormatProvider provider) { // Delegate to the internal method which implements all ToString calls. return ConvertToString(format, provider); } /// /// Creates a string representation of this object based on the format string /// and IFormatProvider passed in. /// If the provider is null, the CurrentCulture is used. /// See the documentation for IFormattable for more information. /// /// /// A string representation of this object. /// internal string ConvertToString(string format, IFormatProvider provider) { if (IsEmpty) { return "Empty"; } // Helper to get the numeric list separator for a given culture. char separator = ','; return String.Format(provider, "{1:" + format + "}{0}{2:" + format + "}{0}{3:" + format + "}{0}{4:" + format + "}", separator, _x, _y, _width, _height); } #endregion Internal Properties #region Public Methods /// /// Compares two Rectangle instances for exact equality. /// Note that double values can acquire error when operated upon, such that /// an exact comparison between two values which are logically equal may fail. /// Furthermore, using this equality operator, Double.NaN is not equal to itself. /// /// /// bool - true if the two Rectangle instances are exactly equal, false otherwise /// /// The first Rectangle to compare /// The second Rectangle to compare public static bool operator ==(RectangleBase rect1, RectangleBase rect2) { return rect1.X == rect2.X && rect1.Y == rect2.Y && rect1.Width == rect2.Width && rect1.Height == rect2.Height; } /// /// Compares two Rectangle instances for exact inequality. /// Note that double values can acquire error when operated upon, such that /// an exact comparison between two values which are logically equal may fail. /// Furthermore, using this equality operator, Double.NaN is not equal to itself. /// /// /// bool - true if the two Rectangle instances are exactly unequal, false otherwise /// /// The first Rectangle to compare /// The second Rectangle to compare public static bool operator !=(RectangleBase rect1, RectangleBase rect2) { return !(rect1 == rect2); } /// /// Compares two Rectangle instances for object equality. In this equality /// Double.NaN is equal to itself, unlike in numeric equality. /// Note that double values can acquire error when operated upon, such that /// an exact comparison between two values which /// are logically equal may fail. /// /// /// bool - true if the two Rectangle instances are exactly equal, false otherwise /// /// The first Rectangle to compare /// The second Rectangle to compare public static bool Equals(RectangleBase rect1, RectangleBase rect2) { if (rect1.IsEmpty) { return rect2.IsEmpty; } else { return rect1.X.Equals(rect2.X) && rect1.Y.Equals(rect2.Y) && rect1.Width.Equals(rect2.Width) && rect1.Height.Equals(rect2.Height); } } /// /// Equals - compares this Rectangle with the passed in object. In this equality /// Double.NaN is equal to itself, unlike in numeric equality. /// Note that double values can acquire error when operated upon, such that /// an exact comparison between two values which /// are logically equal may fail. /// /// /// bool - true if the object is an instance of Rectangle and if it's equal to "this". /// /// The object to compare to "this" public override bool Equals(object o) { if ((null == o) || !(o is RectangleBase)) { return false; } RectangleBase value = (RectangleBase)o; return RectangleBase.Equals(this, value); } /// /// Equals - compares this Rectangle with the passed in object. In this equality /// Double.NaN is equal to itself, unlike in numeric equality. /// Note that double values can acquire error when operated upon, such that /// an exact comparison between two values which /// are logically equal may fail. /// /// /// bool - true if "value" is equal to "this". /// /// The Rectangle to compare to "this" public bool Equals(RectangleBase value) { return RectangleBase.Equals(this, value); } /// /// Returns the HashCode for this Rectangle /// /// /// int - the HashCode for this Rectangle /// public override int GetHashCode() { if (IsEmpty) { return 0; } else { // Perform field-by-field XOR of HashCodes return X.GetHashCode() ^ Y.GetHashCode() ^ Width.GetHashCode() ^ Height.GetHashCode(); } } /// /// Parse - returns an instance converted from the provided string using /// the culture "en-US" /// string with Rectangle data /// //public static Rectangle Parse(string source) //{ // IFormatProvider formatProvider = System.Windows.Markup.TypeConverterHelper.InvariantEnglishUS; // TokenizerHelper th = new TokenizerHelper(source, formatProvider); // Rectangle value; // String firstToken = th.NextTokenRequired(); // // The token will already have had whitespace trimmed so we can do a // // simple string compare. // if (firstToken == "Empty") // { // value = Empty; // } // else // { // value = new Rectangle( // Convert.ToDouble(firstToken, formatProvider), // Convert.ToDouble(th.NextTokenRequired(), formatProvider), // Convert.ToDouble(th.NextTokenRequired(), formatProvider), // Convert.ToDouble(th.NextTokenRequired(), formatProvider)); // } // // There should be no more tokens in this string. // th.LastTokenRequired(); // return value; //} public static implicit operator RectangleBase(Rect rect) { return new RectangleBase(rect.Left, rect.Top, rect.Width, rect.Height); } public static implicit operator Rect(RectangleBase rect) { return new Rect(rect.Left, rect.Top, rect.Width, rect.Height); } #endregion Public Methods } }