/Utilities/Datatypes/Rectangle.cs
C# | 1481 lines | 890 code | 112 blank | 479 comment | 23 complexity | bfc9aba220937da6f8153edaab1a74cc MD5 | raw file
Possible License(s): Apache-2.0
- using System;
- using System.ComponentModel;
- using System.Diagnostics;
- using System.Globalization;
- using System.IO;
- using System.Runtime.InteropServices;
- using Delta.Utilities.Datatypes.Advanced;
- using Delta.Utilities.Helpers;
- using Delta.Utilities.Profiling;
- using NUnit.Framework;
-
- // Disable warnings for some tests where we don't use created values
- #pragma warning disable 219
-
- namespace Delta.Utilities.Datatypes
- {
- /// <summary>
- /// Rectangle class, just consists of x, y, width and height (all float
- /// values). There is a possibility to rotate rectangles with the Rotate
- /// method, but the rotation data is not kept. This is mostly because
- /// passing big structures around in the engine is too slow. It is actually
- /// faster just to calculate the rotation if really needed (it is not
- /// needed much anyway). Performance testing is at the end of this file!
- /// </summary>
- [Serializable]
- [StructLayout(LayoutKind.Explicit)]
- [DebuggerDisplay("Rectangle(X={X}, Y={Y}, Width={Width}, Height={Height})")]
- [Description("Expand to edit this Rectangle")]
- [TypeConverter(typeof(ExpandableObjectConverter))]
- public struct Rectangle : ISaveLoadBinary, IEquatable<Rectangle>
- {
- #region Constants
- /// <summary>
- /// Returns a rectangle at the position (0,0) with the size (0,0).
- /// -> Can be used to determine if a rectangle is "used" or not.
- /// </summary>
- public static readonly Rectangle Zero =
- new Rectangle(0.0f, 0.0f, 0.0f, 0.0f);
-
- /// <summary>
- /// Returns a rectangle at the position (0,0) and with the size (1,1).
- /// -> Can be used for fullscreen rendering or for the full UV layout.
- /// </summary>
- public static readonly Rectangle One =
- new Rectangle(0.0f, 0.0f, 1.0f, 1.0f);
- #endregion
-
- #region FromCenter (Static)
- /// <summary>
- /// Create a new rectangle from a center position and given size.
- /// </summary>
- /// <param name="setCenterX">
- /// The x coordinate of the rectangle center.
- /// </param>
- /// <param name="setCenterY">
- /// The y coordinate of the rectangle center.
- /// </param>
- /// <param name="setWidth">Width of the new rectangle.</param>
- /// <param name="setHeight">Height of the new rectangle.</param>
- /// <returns>Rectangle</returns>
- public static Rectangle FromCenter(float setCenterX, float setCenterY,
- float setWidth, float setHeight)
- {
- return new Rectangle(
- setCenterX - setWidth * 0.5f,
- setCenterY - setHeight * 0.5f,
- setWidth,
- setHeight);
- }
-
- /// <summary>
- /// Create a new rectangle from a center position and given size.
- /// </summary>
- /// <param name="setCenterPosition">
- /// The center position of the rectangle.
- /// </param>
- /// <param name="setDimension">
- /// The dimension of the quadratic rectangle.
- /// </param>
- /// <returns>New rectangle created from center</returns>
- public static Rectangle FromCenter(Point setCenterPosition,
- float setDimension)
- {
- return new Rectangle(
- setCenterPosition.X - setDimension * 0.5f,
- setCenterPosition.Y - setDimension * 0.5f,
- setDimension,
- setDimension);
- }
-
- /// <summary>
- /// Create a new rectangle from a center position and given size.
- /// </summary>
- /// <param name="setCenterPosition">
- /// The center position of the rectangle.
- /// </param>
- /// <param name="setSize">
- /// The size of the rectangle.
- /// </param>
- /// <returns>New rectangle created from center</returns>
- public static Rectangle FromCenter(Point setCenterPosition, Size setSize)
- {
- return new Rectangle(
- setCenterPosition.X - setSize.WidthHalf,
- setCenterPosition.Y - setSize.HeightHalf,
- setSize.Width,
- setSize.Height);
- }
- #endregion
-
- #region FromCorners (Static)
- /// <summary>
- /// Creates a rectangle based on the given corner coordinates.
- /// </summary>
- /// <param name="TopLeft">Top left</param>
- /// <param name="BottomRight">Bottom right</param>
- public static Rectangle FromCorners(Point TopLeft, Point BottomRight)
- {
- return new Rectangle(
- TopLeft.X, TopLeft.Y,
- BottomRight.X - TopLeft.X,
- BottomRight.Y - TopLeft.Y);
- }
- #endregion
-
- #region FromColladaString (Static)
- /// <summary>
- /// From collada string, the opposite of the ToColladaString method.
- /// </summary>
- public static Rectangle FromColladaString(string colladaString)
- {
- if (String.IsNullOrEmpty(colladaString))
- {
- return Zero;
- }
-
- string[] splittedValues = colladaString.Split(new[]
- {
- ' '
- });
- if (splittedValues.Length != 4)
- {
- Log.Warning("Unable to convert colladaString=" + colladaString +
- " to Rectangle because it does not have 4 values separated by " +
- "spaces!");
- return Zero;
- }
-
- CultureInfo invariantCulture = CultureInfo.InvariantCulture;
- return new Rectangle(
- Convert.ToSingle(splittedValues[0], invariantCulture),
- Convert.ToSingle(splittedValues[1], invariantCulture),
- Convert.ToSingle(splittedValues[2], invariantCulture),
- Convert.ToSingle(splittedValues[3], invariantCulture));
- }
- #endregion
-
- #region FromCommaString (Static)
- /// <summary>
- /// From comma string, the opposite of the ToCommaString method.
- /// </summary>
- /// <param name="commaString">CommaString</param>
- /// <returns>Rectangle created from the commaString</returns>
- public static Rectangle FromCommaString(string commaString)
- {
- if (String.IsNullOrEmpty(commaString))
- {
- return Zero;
- }
-
- string[] splittedValues = commaString.Split(new[]
- {
- ','
- });
- if (splittedValues.Length != 4)
- {
- Log.Warning(
- "Unable to convert colladaString=" + commaString +
- " to Rectangle because it does not have 4 values separated by " +
- "commas!");
- return Zero;
- }
-
- CultureInfo invariantCulture = CultureInfo.InvariantCulture;
- return new Rectangle(
- Convert.ToSingle(splittedValues[0], invariantCulture),
- Convert.ToSingle(splittedValues[1], invariantCulture),
- Convert.ToSingle(splittedValues[2], invariantCulture),
- Convert.ToSingle(splittedValues[3], invariantCulture));
- }
- #endregion
-
- #region BuildUVRectangle (Static)
- /// <summary>
- /// Build UV rectangle for a given image width and height. Will also
- /// take care of 0.5 pixel offseting, which is very important for 2d
- /// rendering and font rendering in particular. The offset is to make sure
- /// all pixels are offseted by 0.5, 0.5 (a little less actually) to make
- /// rendering work fine in XNA and DirectX modes. For OpenTK it works
- /// mostly without, but this seems to be driver specific, so with this
- /// offset everything still looks fine and won't hurt.
- /// </summary>
- /// <param name="x">X position</param>
- /// <param name="y">Y position</param>
- /// <param name="width">Width</param>
- /// <param name="height">Heigth</param>
- /// <param name="imageHeight">Total image height</param>
- /// <param name="imageWidth">Total image width</param>
- /// <returns>Created UV Rectangle from the given data</returns>
- public static Rectangle BuildUVRectangle(float x, float y, float width,
- float height, float imageWidth, float imageHeight)
- {
- // Warn if wrong or invalid imageWidth or imageHeight
- if (imageWidth == 0 ||
- imageHeight == 0)
- {
- Log.Warning("Image size=" + imageWidth + "*" + imageHeight +
- " is invalid for BuildUVRectangle, we need a valid image size!");
- return One;
- }
-
- return new Rectangle(
- x / imageWidth,
- y / imageHeight,
- width / imageWidth,
- height / imageHeight);
- }
-
- /// <summary>
- /// Build UV rectangle for a given uv pixel rect and imageSize. Will also
- /// take care of 0.5 pixel offseting, which is very important for 2d
- /// rendering and font rendering in particular. The offset is to make sure
- /// all pixels are offseted by 0.5, 0.5 (a little less actually) to make
- /// rendering work fine in XNA and DirectX modes. For OpenTK it works
- /// mostly without, but this seems to be driver specific, so with this
- /// offset everything still looks fine and won't hurt.
- /// <para>
- /// Warning: Do not use that for converting pixel space into quadratic
- /// space, use the Screen class for that.
- /// </para>
- /// </summary>
- /// <param name="uvInPixels">UV rectangle in pixels</param>
- /// <param name="bitmapSize">Bitmap size (to devide through)</param>
- /// <returns>Created UV Rectangle from the given data</returns>
- public static Rectangle BuildUVRectangle(Rectangle uvInPixels,
- Size bitmapSize)
- {
- return new Rectangle(
- uvInPixels.X / bitmapSize.Width,
- uvInPixels.Y / bitmapSize.Height,
- uvInPixels.Width / bitmapSize.Width,
- uvInPixels.Height / bitmapSize.Height);
- }
- #endregion
-
- #region X (Public)
- /// <summary>
- /// X coordinate of the position.
- /// </summary>
- [FieldOffset(0)]
- public float X;
- #endregion
-
- #region Y (Public)
- /// <summary>
- /// Y coordinate of the position.
- /// </summary>
- [FieldOffset(4)]
- public float Y;
- #endregion
-
- #region Width (Public)
- /// <summary>
- /// Width
- /// </summary>
- [FieldOffset(8)]
- public float Width;
- #endregion
-
- #region Height (Public)
- /// <summary>
- /// Height
- /// </summary>
- [FieldOffset(12)]
- public float Height;
- #endregion
-
- #region XProperty (Public)
- /// <summary>
- /// Property-wrapper for using the X field in the editor.
- /// </summary>
- [Browsable(true)]
- [DisplayName("X")]
- public float XProperty
- {
- get
- {
- return X;
- }
- set
- {
- X = value;
- }
- }
- #endregion
-
- #region YProperty (Public)
- /// <summary>
- /// Property-wrapper for using the Y field in the editor
- /// </summary>
- [Browsable(true)]
- [DisplayName("Y")]
- public float YProperty
- {
- get
- {
- return Y;
- }
- set
- {
- Y = value;
- }
- }
- #endregion
-
- #region WidthProperty (Public)
- /// <summary>
- /// Property-wrapper for using the X field in the editor.
- /// </summary>
- [Browsable(true)]
- [DisplayName("Width")]
- public float WidthProperty
- {
- get
- {
- return Width;
- }
- set
- {
- Width = value;
- }
- }
- #endregion
-
- #region HeightProperty (Public)
- /// <summary>
- /// Property-wrapper for using the Y field in the editor
- /// </summary>
- [Browsable(true)]
- [DisplayName("Height")]
- public float HeightProperty
- {
- get
- {
- return Height;
- }
- set
- {
- Height = value;
- }
- }
- #endregion
-
- #region Position (Public)
- /// <summary>
- /// Position of the rectangle, just X and Y
- /// </summary>
- [FieldOffset(0)]
- public Point Position;
- #endregion
-
- #region Size (Public)
- /// <summary>
- /// Gets or sets the Size of the rectangle, just Width and Height
- /// </summary>
- [FieldOffset(8)]
- public Size Size;
- #endregion
-
- #region IsZero (Public)
- /// <summary>
- /// Is zero
- /// </summary>
- [Browsable(false)]
- public bool IsZero
- {
- get
- {
- return Size.IsZero;
- }
- }
- #endregion
-
- #region Left (Public)
- /// <summary>
- /// Left edge, same as X
- /// </summary>
- [FieldOffset(0)]
- public float Left;
- #endregion
-
- #region Right (Public)
- /// <summary>
- /// Right edge, which is at X+Width
- /// </summary>
- [Browsable(false)]
- public float Right
- {
- get
- {
- return X + Width;
- }
- set
- {
- X = value - Width;
- }
- }
- #endregion
-
- #region Top (Public)
- /// <summary>
- /// Top edge, same as Y
- /// </summary>
- [FieldOffset(4)]
- public float Top;
- #endregion
-
- #region Bottom (Public)
- /// <summary>
- /// Bottom edge, which is at Y+Height
- /// </summary>
- [Browsable(false)]
- public float Bottom
- {
- get
- {
- return Y + Height;
- }
- set
- {
- Y = value - Height;
- }
- }
- #endregion
-
- #region Center (Public)
- /// <summary>
- /// The center point of the rectangle at X+Width/2, Y+Height/2.
- /// </summary>
- [Browsable(false)]
- public Point Center
- {
- get
- {
- return new Point(X + Width * 0.5f, Y + Height * 0.5f);
- }
- set
- {
- // Position = Center - HalfSize
- X = value.X - Width * 0.5f;
- Y = value.Y - Height * 0.5f;
- }
- }
- #endregion
-
- #region TopLeft (Public)
- /// <summary>
- /// Returns the top left position, which is just X, Y again (as Position)
- /// </summary>
- [FieldOffset(0)]
- public Point TopLeft;
- #endregion
-
- #region TopRight (Public)
- /// <summary>
- /// Returns the top right position.
- /// </summary>
- [Browsable(false)]
- public Point TopRight
- {
- get
- {
- return new Point(Right, Top);
- }
- }
- #endregion
-
- #region BottomLeft (Public)
- /// <summary>
- /// Returns the bottom left position.
- /// </summary>
- [Browsable(false)]
- public Point BottomLeft
- {
- get
- {
- return new Point(Left, Bottom);
- }
- }
- #endregion
-
- #region BottomRight (Public)
- /// <summary>
- /// Returns the bottom right position.
- /// </summary>
- [Browsable(false)]
- public Point BottomRight
- {
- get
- {
- return new Point(Right, Bottom);
- }
- }
- #endregion
-
- #region Private
-
- #region lastRotationAngle (Private)
- /// <summary>
- /// Helpers for the Rotate method.
- /// </summary>
- private static float lastRotationAngle;
- #endregion
-
- #region lastRotationSin (Private)
- private static float lastRotationSin;
- #endregion
-
- #region lastRotationCos (Private)
- private static float lastRotationCos;
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Creates a rectangle.
- /// </summary>
- /// <param name="setHeight">setHeight</param>
- /// <param name="setLeft">setLeft</param>
- /// <param name="setTop">setTop</param>
- /// <param name="setWidth">setWidth</param>
- public Rectangle(float setLeft, float setTop, float setWidth,
- float setHeight)
- : this()
- {
- Left = setLeft;
- Top = setTop;
- Width = setWidth;
- Height = setHeight;
- }
-
- /// <summary>
- /// Creates a rectangle.
- /// </summary>
- /// <param name="setPosition">setPosition</param>
- /// <param name="setSize">setSize</param>
- public Rectangle(Point setPosition, Size setSize)
- : this(setPosition.X, setPosition.Y, setSize.Width, setSize.Height)
- {
- }
-
- /// <summary>
- /// Create rectangle
- /// </summary>
- /// <param name="setData">Set data</param>
- public Rectangle(BinaryReader setData)
- : this()
- {
- Load(setData);
- }
- #endregion
-
- #region IEquatable<Rectangle> Members
- /// <summary>
- /// Check if two rectangles are nearly equal (using MathHelper.Epsilon).
- /// </summary>
- /// <param name="other">other</param>
- /// <returns>True if the other rectangle has the same values (allowing
- /// MathHelper.Espilon difference between the rectangles).</returns>
- public bool Equals(Rectangle other)
- {
- return
- other.Left >= (Left - MathHelper.Epsilon) &&
- other.Left <= (Left + MathHelper.Epsilon) &&
- other.Top >= (Top - MathHelper.Epsilon) &&
- other.Top <= (Top + MathHelper.Epsilon) &&
- other.Width >= (Width - MathHelper.Epsilon) &&
- other.Width <= (Width + MathHelper.Epsilon) &&
- other.Height >= (Height - MathHelper.Epsilon) &&
- other.Height <= (Height + MathHelper.Epsilon);
- }
- #endregion
-
- #region ISaveLoadBinary Members
- /// <summary>
- /// Load rectangle from a binary stream (16 bytes, 4 floats).
- /// </summary>
- /// <param name="reader">reader</param>
- public void Load(BinaryReader reader)
- {
- X = reader.ReadSingle();
- Y = reader.ReadSingle();
- Width = reader.ReadSingle();
- Height = reader.ReadSingle();
- }
-
- /// <summary>
- /// Save rectangle to a binary stream (16 bytes, 4 floats).
- /// </summary>
- /// <param name="writer">writer</param>
- public void Save(BinaryWriter writer)
- {
- writer.Write(X);
- writer.Write(Y);
- writer.Write(Width);
- writer.Write(Height);
- }
- #endregion
-
- #region op_Equality (Operator)
- /// <summary>
- /// Check for equality, will check if both rectangles are almost equal.
- /// </summary>
- /// <param name="value1">Rectangle 1</param>
- /// <param name="value2">Rectangle 2</param>
- /// <returns>True if both rectangles are almost equal.</returns>
- public static bool operator ==(Rectangle value1, Rectangle value2)
- {
- return
- MathHelper.Abs(value1.X - value2.X) <= MathHelper.Epsilon &&
- MathHelper.Abs(value1.Y - value2.Y) <= MathHelper.Epsilon &&
- MathHelper.Abs(value1.Width - value2.Width) <= MathHelper.Epsilon &&
- MathHelper.Abs(value1.Height - value2.Height) <= MathHelper.Epsilon;
- }
- #endregion
-
- #region op_Inequality (Operator)
- /// <summary>
- /// Check for inequality, will check if both rectangles are almost equal.
- /// </summary>
- /// <param name="value1">value1</param>
- /// <param name="value2">value2</param>
- /// <returns>True if both rectangles are not equal.</returns>
- public static bool operator !=(Rectangle value1, Rectangle value2)
- {
- return
- MathHelper.Abs(value1.X - value2.X) > MathHelper.Epsilon ||
- MathHelper.Abs(value1.Y - value2.Y) > MathHelper.Epsilon ||
- MathHelper.Abs(value1.Width - value2.Width) > MathHelper.Epsilon ||
- MathHelper.Abs(value1.Height - value2.Height) > MathHelper.Epsilon;
- }
- #endregion
-
- #region Equals (Public)
- /// <summary>
- /// Check for equality, will check if both rectangles are almost equal.
- /// </summary>
- /// <param name="obj">Object to compare to</param>
- /// <returns>True if obj is a rectangle and almost equal</returns>
- public override bool Equals(object obj)
- {
- return (obj is Rectangle)
- ? Equals((Rectangle)obj)
- : base.Equals(obj);
- }
- #endregion
-
- #region GetHashCode (Public)
- /// <summary>
- /// Get hash code for this rectangle
- /// </summary>
- /// <returns>Hash code, build from X, Y, Width and Height</returns>
- public override int GetHashCode()
- {
- return X.GetHashCode() ^ Y.GetHashCode() ^ Width.GetHashCode() ^
- Height.GetHashCode();
- }
- #endregion
-
- #region Shrink (Public)
- /// <summary>
- /// Shrinks the rectangle by the given quad space amount, this will
- /// reduce the size by quadSpaceValue * 2 (all borders will be reduced
- /// by quadSpaceValue). Note that the original rectangle is unchanged,
- /// only the returned rectangle has the new size.
- /// </summary>
- /// <param name="quadSpaceValue">Quad space value for reducing</param>
- /// <returns>Reduced rectangle</returns>
- public Rectangle Shrink(float quadSpaceValue)
- {
- return new Rectangle(X + quadSpaceValue, Y + quadSpaceValue,
- Width - (quadSpaceValue * 2.0f), Height - (quadSpaceValue * 2.0f));
- }
- #endregion
-
- #region Grow (Public)
- /// <summary>
- /// Grows the rectangle by the given quad space amount, this will
- /// increase the size by quadSpaceValue * 2 (all borders will be increased
- /// by quadSpaceValue). Note that the original rectangle is unchanged,
- /// only the returned rectangle has the new size.
- /// </summary>
- /// <param name="quadSpaceValue">Quad space value for increasing</param>
- /// <returns>Increased rectangle</returns>
- public Rectangle Grow(float quadSpaceValue)
- {
- return new Rectangle(X - quadSpaceValue, Y - quadSpaceValue,
- Width + (quadSpaceValue * 2.0f), Height + (quadSpaceValue * 2.0f));
- }
- #endregion
-
- #region ScaleCentered (Public)
- /// <summary>
- /// Scale a rectangle centered. Note that the original rectangle is
- /// unchanged, only the returned rectangle has the new size.
- /// </summary>
- /// <param name="scaleFactor">Scale factor to increase at all sides</param>
- /// <returns>Increased rectangle</returns>
- public Rectangle ScaleCentered(float scaleFactor)
- {
- return ScaleCentered(scaleFactor, scaleFactor);
- }
-
- /// <summary>
- /// Scale a rectangle centered. Note that the original rectangle is
- /// unchanged, only the returned rectangle has the new size.
- /// </summary>
- /// <param name="scaleHeight">Scale factor to increase height</param>
- /// <param name="scaleWidth">Scale factor to increase width</param>
- /// <returns>Increased rectangle</returns>
- public Rectangle ScaleCentered(float scaleWidth, float scaleHeight)
- {
- Size orgSize = Size;
- Size scaledSize = new Size(orgSize.Width * scaleWidth,
- orgSize.Height * scaleHeight);
- Size halfOffset = (orgSize - scaledSize) * 0.5f;
- return new Rectangle(Position + halfOffset, scaledSize);
- }
- #endregion
-
- #region Contains (Public)
- /// <summary>
- /// Determines whether a point lies inside a rectangle or not.
- /// </summary>
- /// <param name="position">The position we want to check if it
- /// intersects with the rectangle.</param>
- /// <returns>
- /// <c>true</c> if the Rectangle contains the point; otherwise,
- /// <c>false</c>.
- /// </returns>
- public bool Contains(Point position)
- {
- return
- position.X >= Left &&
- position.X < Right &&
- position.Y >= Top &&
- position.Y < Bottom;
- }
-
- /// <summary>
- /// Intersects with another rectangle? This means rectangle should
- /// be inside or touching one border. If not, rectangle is out of
- /// our rectangle, which is useful for ScreenArea visibility checks.
- /// </summary>
- /// <param name="rectangle">The rectangle.</param>
- /// <returns>
- /// Fully: if the rectangle is inside this Rectangle
- /// Partially: if the two rectangles intersect
- /// None: The rectangles are far apart
- /// </returns>
- public ContainmentType Contains(Rectangle rectangle)
- {
- if (Right < rectangle.X ||
- Left > rectangle.Right ||
- Bottom < rectangle.Top ||
- Top > rectangle.Bottom)
- {
- return ContainmentType.None;
- }
-
- if (Left <= rectangle.X &&
- Right >= rectangle.Right &&
- Top < rectangle.Top &&
- Bottom >= rectangle.Bottom)
- {
- return ContainmentType.Fully;
- }
-
- return ContainmentType.Partial;
- }
- #endregion
-
- #region Move (Public)
- /// <summary>
- /// Will return a copy of the current rectangle which is moved by the given
- /// offset.
- /// </summary>
- /// <param name="offset">Offset to move</param>
- /// <returns>Moved rectangle</returns>
- public Rectangle Move(Point offset)
- {
- return Move(offset.X, offset.Y);
- }
-
- /// <summary>
- /// Will return a copy of the current rectangle which is moved by the given
- /// offsets.
- /// </summary>
- /// <param name="xOffset">X offset to move</param>
- /// <param name="yOffset">Y offset to move</param>
- /// <returns>Moved rectangle</returns>
- public Rectangle Move(float xOffset, float yOffset)
- {
- return new Rectangle(X + xOffset, Y + yOffset, Width, Height);
- }
- #endregion
-
- #region ToString (Public)
- /// <summary>
- /// To string, will provide a string about the position and size of this
- /// rectangle. Use ToColladaString or ToCommaString for storing this
- /// Rectangle as a string in text or xml files (because we have
- /// FromColladaString and FromCommaString methods, but we got no
- /// FromString method).
- /// </summary>
- /// <returns>string</returns>
- public override string ToString()
- {
- return "(Position=" + Position + ", Size=" + Size + ")";
- }
- #endregion
-
- #region ToColladaString (Public)
- /// <summary>
- /// Returns the vector as a string that can be used in a Collada file.
- /// Note: Use FromColladaString to load this Rectangle again.
- /// </summary>
- /// <returns>
- /// String with the X, Y, Width and Height values just separated by spaces.
- /// </returns>
- public string ToColladaString()
- {
- return X.ToInvariantString() + " " +
- Y.ToInvariantString() + " " +
- Width.ToInvariantString() + " " +
- Height.ToInvariantString();
- }
- #endregion
-
- #region ToCommaString (Public)
- /// <summary>
- /// Returns the vector as a string with commas (x, y, width, height).
- /// Note: Use FromCommaString to load this Rectangle again.
- /// </summary>
- /// <returns>
- /// String with the X, Y, Width and Height values separated by commas.
- /// </returns>
- public string ToCommaString()
- {
- return X.ToInvariantString() + "," +
- Y.ToInvariantString() + "," +
- Width.ToInvariantString() + "," +
- Height.ToInvariantString();
- }
- #endregion
-
- #region Rotate (Public)
- /// <summary>
- /// Rotate rectangle around its center and return the 4 corner points in
- /// this order: TopLeft, TopRight, BottomRight, BottomLeft. This way
- /// these points can be rendered directly by a shader. Used for material
- /// drawing (rendering). Will return the original rectangle if
- /// rotationAngle is 0 (then executing this is much faster).
- /// Note: This method is not returning anything new, it will only change
- /// the content of rotPoints because of performance. Creating an array of
- /// 4 points (8 floats) costs a lot of performance on mobile platforms. If
- /// you do it hundred or thousands of times per frame you don't want all
- /// these newly created point arrays hanging around. Instead each caller
- /// uses his own point array (only initialized once).
- /// </summary>
- /// <param name="rotationAngle">rotationAngle</param>
- /// <param name="rotPoints">
- /// Preinitialized array of 4 Points used to store the rotation points
- /// resulting in the rectangle rotation with rotationAngle.
- /// </param>
- public void Rotate(float rotationAngle, Point[] rotPoints)
- {
- // Next we need to do some calculations, but only if rotationAngle is
- // used. If rotationAngle is 0, we can just return the rectangle.
- if (rotationAngle == 0)
- {
- float X2 = X + Width;
- float Y2 = Y + Height;
- rotPoints[0].X = X;
- rotPoints[0].Y = Y;
- rotPoints[1].X = X2;
- rotPoints[1].Y = Y;
- rotPoints[2].X = X2;
- rotPoints[2].Y = Y2;
- rotPoints[3].X = X;
- rotPoints[3].Y = Y2;
- }
- else
- {
- // Create rotation matrix. Note: This code might look ugly, but it
- // is incredibly fast, except for the cos and sin everything else
- // is just adding and multiplying some floats, which is very fast!
- // Cache the cos/sin results here because we often have rotations
- // with the same value, but using many draw calls with different
- // rectangles on it (e.g. a font rotating around).
- if (lastRotationAngle != rotationAngle)
- {
- lastRotationAngle = rotationAngle;
- lastRotationSin = MathHelper.Sin(rotationAngle);
- lastRotationCos = MathHelper.Cos(rotationAngle);
- }
-
- // Little Matrix2x2 :)
- float m11 = lastRotationCos;
- float m12 = lastRotationSin;
- float m21 = -lastRotationSin;
- //Note: Same as m11: float m22 = lastRotationCos;
- // Rotation formula is: X * M11 + Y * M21, X * M12 + Y * M22
-
- // Now transform each point relative to the center of the rectangle!
- float centerX = X + Width * 0.5f;
- float centerY = Y + Height * 0.5f;
- // X1, Y1, X2 and Y2 are all relative to the center and easier to
- // rotate this way, but we need to add CenterX and CenterY to the
- // results below again.
- float x1 = X - centerX;
- float y1 = Y - centerY;
- float x2 = x1 + Width;
- float y2 = y1 + Height;
- // X1, Y1, X2 and Y2 are all used multiple times, pre-calculate M11
- // to M22 matrix values with them to save even more instructions :)
- float x1m11 = x1 * m11;
- float x1m12 = x1 * m12;
- float y1m21 = y1 * m21;
- float y1m22 = y1 * m11; //m22;
- float x2m11 = x2 * m11;
- float x2m12 = x2 * m12;
- float y2m21 = y2 * m21;
- float y2m22 = y2 * m11; //m22;
- // TopLeft is X1, Y1
- rotPoints[0].X = x1m11 + y1m21 + centerX;
- rotPoints[0].Y = x1m12 + y1m22 + centerY;
- // TopRight is X2, Y1
- rotPoints[1].X = x2m11 + y1m21 + centerX;
- rotPoints[1].Y = x2m12 + y1m22 + centerY;
- // BottomRight is X2, Y1
- rotPoints[2].X = x2m11 + y2m21 + centerX;
- rotPoints[2].Y = x2m12 + y2m22 + centerY;
- // BottomLeft is X1, Y2
- rotPoints[3].X = x1m11 + y2m21 + centerX;
- rotPoints[3].Y = x1m12 + y2m22 + centerY;
- }
- }
-
- /// <summary>
- /// Rotate rectangle around its center and return the 4 corner points in
- /// this order: TopLeft, TopRight, BottomRight, BottomLeft. This way
- /// these points can be rendered directly by a shader. Used for material
- /// drawing (rendering). Will return the original rectangle if
- /// rotationAngle is 0 (then executing this is much faster).
- /// Special version of this method with a given center rotation point!
- /// </summary>
- /// <param name="center">center</param>
- /// <param name="rotationAngle">rotationAngle</param>
- /// <param name="rotPoints">rotPoints</param>
- public void Rotate(float rotationAngle, Point[] rotPoints, Point center)
- {
- // Next we need to do some calculations, but only if rotationAngle is
- // used. If rotationAngle is 0, we can just return the rectangle.
- if (rotationAngle == 0)
- {
- float X2 = X + Width;
- float Y2 = Y + Height;
- rotPoints[0].X = X;
- rotPoints[0].Y = Y;
- rotPoints[1].X = X2;
- rotPoints[1].Y = Y;
- rotPoints[2].X = X2;
- rotPoints[2].Y = Y2;
- rotPoints[3].X = X;
- rotPoints[3].Y = Y2;
- }
- else
- {
- // Create rotation matrix. Note: This code might look ugly, but it
- // is incredibly fast, except for the cos and sin everything else
- // is just adding and multiplying some floats, which is very fast!
- // Cache the cos/sin results here because we often have rotations
- // with the same value, but using many draw calls with different
- // rectangles on it (e.g. a font rotating around).
- if (lastRotationAngle != rotationAngle)
- {
- lastRotationAngle = rotationAngle;
- lastRotationSin = MathHelper.Sin(rotationAngle);
- lastRotationCos = MathHelper.Cos(rotationAngle);
- }
-
- // Little Matrix2x2 :)
- float m11 = lastRotationCos;
- float m12 = lastRotationSin;
- float m21 = -lastRotationSin;
- //Note: Same as m11: float m22 = lastRotationCos;
- // Rotation formula is: X * M11 + Y * M21, X * M12 + Y * M22
-
- // Now transform each point relative to the center of the rectangle!
- float centerX = center.X;
- float centerY = center.Y;
- // X1, Y1, X2 and Y2 are all relative to the center and easier to
- // rotate this way, but we need to add CenterX and CenterY to the
- // results below again.
- float x1 = X - centerX;
- float y1 = Y - centerY;
- float x2 = x1 + Width;
- float y2 = y1 + Height;
- // X1, Y1, X2 and Y2 are all used multiple times, pre-calculate M11
- // to M22 matrix values with them to save even more instructions :)
- float x1m11 = x1 * m11;
- float x1m12 = x1 * m12;
- float y1m21 = y1 * m21;
- float y1m22 = y1 * m11; //m22;
- float x2m11 = x2 * m11;
- float x2m12 = x2 * m12;
- float y2m21 = y2 * m21;
- float y2m22 = y2 * m11; //m22;
- // TopLeft is X1, Y1
- rotPoints[0].X = x1m11 + y1m21 + centerX;
- rotPoints[0].Y = x1m12 + y1m22 + centerY;
- // TopRight is X2, Y1
- rotPoints[1].X = x2m11 + y1m21 + centerX;
- rotPoints[1].Y = x2m12 + y1m22 + centerY;
- // BottomRight is X2, Y1
- rotPoints[2].X = x2m11 + y2m21 + centerX;
- rotPoints[2].Y = x2m12 + y2m22 + centerY;
- // BottomLeft is X1, Y2
- rotPoints[3].X = x1m11 + y2m21 + centerX;
- rotPoints[3].Y = x1m12 + y2m22 + centerY;
- }
- }
- #endregion
-
- #region GetInnerPosition (Public)
- /// <summary>
- /// Get inner position helper method. Used by FbxFile.GetMeshData to
- /// remap UVs from the FBX Model file to our new atlas texture UVs.
- /// </summary>
- /// <param name="relativePosition">Relative position (0-1)</param>
- /// <returns>
- /// Point inside this rectangle based on the relative position.
- /// </returns>
- public Point GetInnerPosition(Point relativePosition)
- {
- return new Point(
- X + Width * relativePosition.X,
- Y + Height * relativePosition.Y);
- }
- #endregion
-
- #region GetInnerRectangle (Public)
- /// <summary>
- /// Get inner position helper method out of a relative rectangle. This
- /// can be used to position stuff inside controls, mini-maps, for aligning
- /// and re-mapping UVs from model files and much more.
- /// </summary>
- /// <param name="relativeRectangle">Relative rectangle, if this is
- /// Rectangle.One, the current rectangle will be returned.</param>
- /// <returns>Returns the current rectangle multiplied by the
- /// relativeRectangle (usually smaller, inside of it)</returns>
- public Rectangle GetInnerRectangle(Rectangle relativeRectangle)
- {
- return new Rectangle(
- X + Width * relativeRectangle.X,
- Y + Height * relativeRectangle.Y,
- Width * relativeRectangle.Width,
- Height * relativeRectangle.Height);
- }
- #endregion
-
- /// <summary>
- /// Rectangle performance class to figure out performance.
- /// </summary>
- public class RectanglePerformance
- {
- #region TestCreation Performance
- /// <summary>
- /// Test the creation of a rectangle.
- /// </summary>
- public static void TestCreation()
- {
- Rectangle testRect;
- PerformanceTester.Profile10MilionTimes("Rectangle constructor",
- delegate
- {
- testRect = new Rectangle(10, 20, 30, 40);
- });
- }
- #endregion
-
- #region TestCopy Performance
- /// <summary>
- /// Test the copy method of the rectangle struct.
- /// </summary>
- public static void TestCopy()
- {
- Rectangle testRect = new Rectangle(10, 20, 30, 40);
- PerformanceTester.Profile10MilionTimes("Rectangle copy",
- delegate
- {
- Rectangle anotherRect = testRect;
- });
- }
- #endregion
-
- #region TestMove Performance
- /// <summary>
- /// Test the move method of the rectangle struct.
- /// </summary>
- public static void TestMove()
- {
- Rectangle testRect = new Rectangle(1, 2, 5, 5);
- PerformanceTester.Profile10MilionTimes("Rectangle.Move",
- delegate
- {
- Rectangle anotherRect = testRect.Move(4, 3);
- });
- }
- #endregion
-
- #region TestIntersects Performance
- /// <summary>
- /// Test the intersects method of the rectangle struct.
- /// </summary>
- public static void TestIntersects()
- {
- Rectangle testRect = One;
- Point testPoint = new Point(1.3f, 0.7f);
- PerformanceTester.Profile10MilionTimes("Rectangle.Intersects",
- delegate
- {
- testRect.Contains(testPoint);
- });
- }
- #endregion
-
- #region TestRotate Performance
- /// <summary>
- /// Test the rotate method of the rectangle struct.
- /// </summary>
- public static void TestRotate()
- {
- Rectangle testRect = One;
- Point[] rotPoints = new Point[4];
-
- PerformanceTester.Profile10MilionTimes("Rectangle.Rotate(0)",
- delegate
- {
- testRect.Rotate(0, rotPoints);
- });
-
- PerformanceTester.Profile10MilionTimes("Rectangle.Rotate(1.4f)",
- delegate
- {
- testRect.Rotate(1.4f, rotPoints);
- });
-
- int num = 0;
- PerformanceTester.Profile10MilionTimes("Rectangle.Rotate(0.1f*num)",
- delegate
- {
- testRect.Rotate(0.1f * num, rotPoints);
- num++;
- });
- }
- #endregion
-
- #region ExecuteAllForPerformanceOverview Performance
- /// <summary>
- /// Execute all rectangle tests for a performance overview.
- /// </summary>
- public static void ExecuteAllForPerformanceOverview()
- {
- // Warm up the CPU with some stress testing (for several secs) :)
- TestCreation();
- PerformanceTester.ShowTotalProfileRuns();
- Log.Test("Starting testing again after warming up!");
-
- Log.Test("Delta Rectangle test:");
- TestCreation();
- TestCopy();
- TestMove();
- TestIntersects();
- TestRotate();
- PerformanceTester.ShowTotalProfileRuns();
-
- // Result: Rectangle is pretty quick now, even doing rotation is no
- // problem at all, there is no need for RotatableRectangle anymore!
- /* Delta Rectangle test:
- 121ms for Rectangle constructor (10 million calls)
- 34ms for Rectangle copy (10 million calls)
- 93ms for Rectangle.Move (10 million calls)
- 95ms for Rectangle.Intersects (10 million calls)
- 86ms for Rectangle.Rotate(0) (10 million calls)
- 183ms for Rectangle.Rotate(1.4f) (10 million calls)
- 990ms for Rectangle.Rotate(0.1f*num) (10 million calls)
- In total 7 Tests were executed. Total time=1602ms, Average time=228.8ms.
- */
- }
- #endregion
- }
-
- /// <summary>
- /// Tests
- /// </summary>
- internal class RectangleTests
- {
- #region SizeOf
- /// <summary>
- /// Checks if the size of Point is exactly 8 bytes (2 floats: X and Y)
- /// </summary>
- [Test]
- public void SizeOf()
- {
- // Rectangle has 4 floats: X, Y, Width, Height
- Assert.Equal(4 * 4, Marshal.SizeOf(typeof(Rectangle)));
- }
- #endregion
-
- #region Properties
- /// <summary>
- /// Properties
- /// </summary>
- [Test]
- public void Properties()
- {
- Rectangle testRect = new Rectangle(5, 5, 20, 10);
-
- // All edges
- Assert.Equal(5, testRect.X);
- Assert.Equal(5, testRect.Y);
- Assert.Equal(25, testRect.Right);
- Assert.Equal(15, testRect.Bottom);
-
- // Position and Size
- Assert.Equal(new Point(5, 5), testRect.Position);
- Assert.Equal(new Size(20, 10), testRect.Size);
- }
- #endregion
-
- #region FromCenter
- /// <summary>
- /// From center
- /// </summary>
- [Test]
- public void FromCenter()
- {
- Rectangle testRect = Rectangle.FromCenter(new Point(20, 10), 10);
- Assert.Equal(new Point(15, 5), testRect.Position);
- Assert.Equal(new Size(10, 10), testRect.Size);
- }
- #endregion
-
- #region FromCorners
- /// <summary>
- /// From corners
- /// </summary>
- [Test]
- public void FromCorners()
- {
- Assert.Equal(new Rectangle(30, 40, 120, 80),
- Rectangle.FromCorners(new Point(30, 40), new Point(150, 120)));
- }
- #endregion
-
- #region FromColladaString
- /// <summary>
- /// From collada string
- /// </summary>
- [Test]
- public void FromColladaString()
- {
- Assert.Equal(new Rectangle(30, 40, 150, 120),
- Rectangle.FromColladaString("30 40 150 120"));
- }
- #endregion
-
- #region FromCommaString
- /// <summary>
- /// From comma string
- /// </summary>
- [Test]
- public void FromCommaString()
- {
- Assert.Equal(new Rectangle(30, 40, 150, 120),
- Rectangle.FromCommaString("30,40,150,120"));
- Assert.Equal(new Rectangle(30, 40, 150, 120),
- Rectangle.FromCommaString("30, 40, 150, 120"));
- }
- #endregion
-
- #region EqualOperator
- /// <summary>
- /// Equal Operator
- /// </summary>
- [Test]
- public void EqualOperator()
- {
- Rectangle testRect1 = new Rectangle(5, 5, 20, 10);
- Rectangle testRect2 = new Rectangle(5, 5, 20, 10);
- Rectangle testRect3 = new Rectangle(5, 0, 20, 10);
-
- Assert.True(testRect1 == testRect2);
- Assert.False(testRect1 == testRect3);
- }
- #endregion
-
- #region InequalOperator
- /// <summary>
- /// Inequal Operator
- /// </summary>
- [Test]
- public void InequalOperator()
- {
- Rectangle testRect1 = new Rectangle(5, 5, 20, 10);
- Rectangle testRect2 = new Rectangle(5, 0, 20, 10);
- Rectangle testRect3 = new Rectangle(5, 5, 20, 10);
-
- Assert.True(testRect1 != testRect2);
- Assert.False(testRect1 != testRect3);
- }
- #endregion
-
- #region NearlyEquals
- /// <summary>
- /// Nearly equals
- /// </summary>
- [Test]
- public void NearlyEquals()
- {
- Rectangle testRect = new Rectangle(5, 5, 20, 10);
-
- // We need here for testing a smaller epsilon, because of the float
- // incorrectness
- const float testEpsilon = MathHelper.Epsilon * 0.99f;
-
- // Check the point directly
- Assert.True(testRect.Equals(testRect));
- // by the "object" overload from .NET
- Assert.True(testRect.Equals((object)testRect));
-
- // and the nearly equal check
- Assert.True(testRect.Equals(new Rectangle(
- 5 + testEpsilon, 5 - testEpsilon,
- 20 - testEpsilon, 10 + testEpsilon)));
-
- // Finally check the "bad" false cases with unequal values
- Assert.False(testRect.Equals(new Rectangle(4, 3, 10, 40)));
- // and a too big epsilon
- Assert.False(testRect.Equals(
- new Rectangle(5 + (2 * testEpsilon), 5, 20, 10)));
- }
- #endregion
-
- #region ScaleCentered
- /// <summary>
- /// Scale centered
- /// </summary>
- [Test]
- public void ScaleCentered()
- {
- Assert.Equal(new Rectangle(30, 30, 80, 80),
- new Rectangle(20, 20, 100, 100).ScaleCentered(0.8f));
-
- Assert.Equal(new Rectangle(20, 30, 100, 80),
- new Rectangle(20, 20, 100, 100).ScaleCentered(1.0f, 0.8f));
-
- Assert.Equal(new Rectangle(10, 20, 120, 100),
- new Rectangle(20, 20, 100, 100).ScaleCentered(1.2f, 1.0f));
- }
- #endregion
-
- #region TestContains
- /// <summary>
- /// Tests Rectangle.Contains(Point)
- /// </summary>
- [Test]
- public void TestContains()
- {
- // We use here for testing a rect from (0,0) to (1,1)
- Rectangle testRect = One;
-
- // Check that the border positions of the rectangle still belongs to it
- Assert.False(testRect.Contains(Point.One));
- Assert.True(testRect.Contains(Point.Zero));
- // Now a simple check of a point that is in the rectangle
- Assert.True(testRect.Contains(new Point(0.3f, 0.7f)));
- // and two points which are not in
- Assert.False(testRect.Contains(new Point(0.3f, 1.7f)));
- Assert.False(testRect.Contains(new Point(1.3f, 0.7f)));
- }
- #endregion
-
- #region TestContiansRectangle
- /// <summary>
- /// Tests rectangle.Contains(rectangle)
- /// </summary>
- [Test]
- public void TestContiansRectangle()
- {
- Rectangle testRect = One;
- Assert.Equal(testRect.Contains(new Rectangle(0.1f, 0.1f, 0.1f, 0.2f))
- , ContainmentType.Fully);
- Assert.Equal(testRect.Contains(new Rectangle(0f, 0f, 2f, 0.2f))
- , ContainmentType.Partial);
- Assert.Equal(testRect.Contains(new Rectangle(1.001f, 1.001f,
- 0.2f, 0.2f)), ContainmentType.None);
- Assert.Equal(testRect.Contains(new Rectangle(2f, 2f,
- 0.2f, 0.2f)), ContainmentType.None);
- }
- #endregion
-
- #region Move
- /// <summary>
- /// Move
- /// </summary>
- [Test]
- public void Move()
- {
- Assert.Equal(new Rectangle(5, 5, 5, 5),
- new Rectangle(1, 2, 5, 5).Move(4, 3));
- }
- #endregion
-
- #region ToString
- /// <summary>
- /// To string
- /// </summary>
- [Test]
- public new void ToString()
- {
- Point rectPos = new Point(2, 1);
- Size rectSize = new Size(10, 5);
-
- Assert.Equal("(Position=" + rectPos + ", Size=" + rectSize + ")",
- new Rectangle(rectPos, rectSize).ToString());
- }
- #endregion
-
- #region ToColladaString
- /// <summary>
- /// </summary>
- [Test]
- public void ToColladaString()
- {
- Rectangle testRect = new Rectangle(1, 2, 3, 4);
- Assert.Equal("1 2 3 4", testRect.ToColladaString());
- Assert.Equal(testRect,
- Rectangle.FromColladaString(testRect.ToColladaString()));
- }
- #endregion
-
- #region ToCommaString
- /// <summary>
- /// </summary>
- [Test]
- public void ToCommaString()
- {
- Rectangle testRect = new Rectangle(1, 2, 3, 4);
- Assert.Equal("1,2,3,4", testRect.ToCommaString());
- Assert.Equal(testRect,
- Rectangle.FromCommaString(testRect.ToCommaString()));
- }
- #endregion
- }
- }
- }
-