using System; using Chernobyl.Creation; using Chernobyl.Mathematics.Collision; using Chernobyl.Mathematics.Movement; using Chernobyl.Mathematics.Vectors; using Chernobyl.Measures; namespace Chernobyl.Mathematics.Geometry { /// /// A 2D rectangle that is composed of a bottom left origin and a width and /// height. The has collision capabilities and implements /// the and interfaces. /// public class Rectangle : Shape, ICollidable2D, IEquatable { /// /// Initializes a new instance of the class that /// is set at an of (0,0) with a width and height /// of 1. /// public Rectangle() : this(1.0f, 1.0f) { } /// /// Constructor that sets the X and Y coordinate to 0.0. /// /// The horizontal width of the rectangle. This /// value cannot be negative. /// The vertical height of the rectangle. This /// value cannot be negative. public Rectangle(float width, float height) : this(0.0f, 0.0f, width, height) {} /// /// Constructor. /// /// The horizontal value of the bottom left corner of the /// rectangle. /// The vertical value of the bottom left corner of the /// rectangle. /// The horizontal width of the rectangle. This /// value cannot be negative. /// The vertical height of the rectangle. This /// value cannot be negative. public Rectangle(float bottomLeftX, float bottomLeftY, float width, float height) : this(new Vector2(bottomLeftX, bottomLeftY), width, height) {} /// /// Initializes a new instance of the class. /// /// The to make this /// equal to. public Rectangle(Rectangle rectangle) : this(rectangle.Origin.X, rectangle.Origin.Y, rectangle.Width, rectangle.Height) { } /// /// Constructor. /// /// The bottom left coordinate of the rectangle. /// The horizontal width of the rectangle. This /// value cannot be negative. /// The vertical height of the rectangle. This /// value cannot be negative. public Rectangle(Vector2 bottomLeft, float width, float height) { Width = width; Height = height; Depth = 0; Origin = bottomLeft; } /// /// An event that is raised when a collidable has started colliding with /// this collidable. When the collision has ended, OnCollisionEnded will /// be raised. /// public event EventHandler OnCollisionStarted; /// /// An event that is raised when the collision with a collidable has /// ended. This will event will be raised after the invocation of /// OnCollisionStarted /// public event EventHandler OnCollisionEnded; /// /// Tests for a collision between this rectangle and the point /// passed in. /// /// The point to test for collision against. /// True if there was a collision, false if otherwise. public bool IsColliding(Point2D point) { // check if the point is within the rectangles the horizontal X axis if (Origin.X < point.X && point.X < (Origin.X + Width)) { // check if the point is within the rectangles the vertical Y axis if (Origin.Y < point.Y && point.Y < (Origin.Y + Height)) return true; } return false; } /// /// Tests for a collision between this rectangle and the Rectangle /// passed in. /// /// The Rectangle to test for collision against. /// True if there was a collision, false if otherwise. public bool IsColliding(Rectangle rectangle) { if (Bottom < rectangle.Top && Top > rectangle.Bottom) { if (RightSide > rectangle.LeftSide && LeftSide < rectangle.RightSide) return true; } return false; } /// /// Tests for a collision between this rectangle and the Circle /// passed in. /// /// The Circle to test for collision against. /// True if there was a collision, false if otherwise. public bool IsColliding(Circle circle) { // This algorithm was taken from here (with minor modifications made): // http://stackoverflow.com/a/402010 // Grab the rectangle's center and half width/height, we are going // to them multiple times. Vector2 rectCenter = Center; float halfRectWidth = Width / 2; float halfRectHeight = Height / 2; // Calculate the difference between the center of the rectangle and // the center of the circle in both the X and Y axis. We make them // both absolute value here because we want the differences to be // positive. By doing this we confine any collision detection done // to one quadrant of the rectangle which makes the algorithm simpler // and faster. Vector2 centerDifference = new Vector2(Math.Abs(circle.Position.X - rectCenter.X), Math.Abs(circle.Position.Y - rectCenter.Y)); bool result = false; // First, we will check to see if the circle is to far away from the // rectangle for it to actually hit. Here we check both the X and Y // axis against the difference in the centers. if (centerDifference.X <= (halfRectWidth + circle.Radius)) { if (centerDifference.Y <= (halfRectHeight + circle.Radius)) { // Now we check to see if the circle is close enough that an // intersection will occur. if (centerDifference.X <= halfRectWidth) result = true; else if (centerDifference.Y <= halfRectHeight) result = true; else { // Now we check for collisions with the rectangles corner. // We find the distance from the corner of the rectangle // to the circle's center. We would normally use a square // root here but we can optimize instead and just square // the distance and the radius of the circle and then // compare the results. Vector2 temp = new Vector2(centerDifference.X - halfRectWidth, centerDifference.Y - halfRectHeight); float cornerDistanceSquared = (temp.X * temp.X) + (temp.Y * temp.Y); if (cornerDistanceSquared <= (circle.Radius * circle.Radius)) result = true; } } } return result; } /// /// Determines whether an is completely contained /// within this . /// /// The that is to be /// checked to see if it is entirely contained within this /// . /// True if is actually contained /// within this . public bool Encloses(IShape shape) { float containerHalfWidth = Width * 0.5f; float containerHalfHeight = Width * 0.5f; float containedHalfWidth = shape.Width * 0.5f; float containedHalfHeight = shape.Height * 0.5f; bool result = false; // check left side if ((Position.X - containerHalfWidth) < (shape.Position.X - containedHalfWidth)) { // check top if ((Position.Y + containerHalfHeight) > (shape.Position.Y + containedHalfHeight)) { // check right side if ((Position.X + containerHalfWidth) > (shape.Position.X + containedHalfWidth)) { // check bottom result = ((Position.Y - containerHalfHeight) < (shape.Position.Y - containedHalfHeight)); } } } return result; } /// /// Expands the rectangle by the specified amount in the X and Y axes /// while keeping the at it's /// current location. /// /// The amount to expand by. public void Expand(Vector2 amount) { float newWidth = Width + amount.X; float newHeight = Height + amount.Y; if (newWidth < 0) throw new ArgumentOutOfRangeException("amount", "You specified " + "an expansion amount that would cause the Rectangle to have " + "a negative width, which a Rectangle cannot have. You specified: " + amount.X); if (newHeight < 0) throw new ArgumentOutOfRangeException("amount", "You specified " + "an expansion amount that would cause the Rectangle to have " + "a negative height, which a Rectangle cannot have. You specified: " + amount.Y); // grab the local matrix and set its row 0 (the first row, which // holds the scale in the X axis) to unit vector scaled by our // new width. Matrix4 local = LocalMatrix; local.Row0 = Vector4.UnitX * newWidth; local.Row1 = Vector4.UnitY * newHeight; SetTransformation(ref local); } /// /// Called when this has started colliding /// with another . /// /// The object that was just hit. public void HandleCollisionStarted(ICollidable collider) { if (OnCollisionStarted != null) OnCollisionStarted(this, EventArgs.Empty); } /// /// Called when this . has stopped colliding /// with another .. /// /// The object that was just hit. public void HandleCollisionEnded(ICollidable collider) { if (OnCollisionEnded != null) OnCollisionEnded(this, EventArgs.Empty); } /// /// Rotates this transform around a vector by a specified amount. /// /// The vector to rotate around. /// The amount to rotate around the vector. public override void Rotate(Vector3 vectorToRotateAround, Radian radian) { throw new NotSupportedException("Rectangle does not support rotations " + "since it is implemented as an AABB (Axis Aligned Bounding Box)."); } /// /// Rotates this transform about the X axis a specified amount. /// /// The amount to rotate by. public override void Pitch(Radian amount) { throw new NotSupportedException("Rectangle does not support rotations " + "since it is implemented as an AABB (Axis Aligned Bounding Box)."); } /// /// Rotates this transform about the Y axis a specified amount. /// /// The amount to rotate by. public override void Yaw(Radian amount) { throw new NotSupportedException("Rectangle does not support rotations " + "since it is implemented as an AABB (Axis Aligned Bounding Box)."); } /// /// Rotates this transform about the Z axis a specified amount. /// /// The amount to rotate by. public override void Roll(Radian amount) { throw new NotSupportedException("Rectangle does not support rotations " + "since it is implemented as an AABB (Axis Aligned Bounding Box)."); } /// /// A string that contains the position, width, and height of this /// in the form of {X=n, Y=n, Width=n, Height=n}, /// where 'X' and 'Y' are the 's /// and and 'n' is some number. For example: /// {X=20, Y=20, Width=100, Height=50}. /// /// /// A that represents this instance. /// public override string ToString() { return "{X=" + Origin.X + ", Y=" + Origin.Y + ", Width=" + Width + ", Height=" + Height + "}"; } /// /// Determines whether the specified is equal to /// this instance. In order for two s to be equal /// they both need to have the same , /// , and . /// /// The to compare with this /// instance. /// True if the specified is equal to this /// instance; otherwise, false. /// public override bool Equals(object obj) { return Equals(obj as Rectangle); } /// /// Determines whether the specified is equal to /// this instance. In order for two s to be equal /// they both need to have the same , /// , and . /// /// The to compare with this /// instance. /// True if the specified is equal to this /// instance; otherwise, false. /// public bool Equals(Rectangle rectangle) { return this == rectangle; } /// /// Implements the equality (==) operator. In order for two /// s to be equal they both need to have the same /// , , and /// . /// /// The instance to compare to the /// instance. /// The instance to compare to the /// instance. /// True if the two instances are equal, false if otherwise. public static bool operator ==(Rectangle left, Rectangle right) { return (ReferenceEquals(left, right) || ReferenceEquals(left, null) == false && ReferenceEquals(null, right) == false && (left.Position == right.Position && left.Width == right.Width && left.Height == right.Height)); } /// /// Implements the not-equal (!=) operator. In order for two /// s to be not equal they both need to have /// different , /// , or . /// /// The instance to compare to the /// instance. /// The instance to compare to the /// instance. /// True if the two instances are NOT equal, false if otherwise. public static bool operator !=(Rectangle left, Rectangle right) { return !(left == right); } /// /// Returns a hash code for this instance. /// /// /// A hash code for this instance, suitable for use in hashing algorithms /// and data structures like a hash table. /// public override int GetHashCode() { return Position.GetHashCode() ^ Width.GetHashCode() ^ Height.GetHashCode(); } /// /// The bottom left origin of the rectangle. /// public virtual Vector2 Origin { get { return new Vector2(Position.X - (Width * .5f), Position.Y - (Height * .5f)); } set { Matrix4 local = LocalMatrix; local.Row3 = new Vector4(value.X + (Width * .5f), value.Y + (Height * .5f), local.Row3.Z, local.Row3.W); SetTransformation(ref local); } } /// /// The center coordinate of the rectangle. This is the same as calling /// 's property. /// public Vector2 Center { get { return Position.Xy; } } /// /// The amount of space taken up by an object on a flat plane in /// square metres (m^2). /// public override float Area { get { return Width * Height; } } /// /// The amount of 3D space this object consumes in cubic metres (m^3). /// If this object is 2D, then the value of this property is zero. This /// property will always return 0 for the . /// public override float Volume { get { return 0; } } /// /// Returns the length of the rectangles perimeter as 2 * (Height + Width). /// public override float Perimeter { get { return 2 * (Height + Width); } } /// /// Returns the length of a diagonal of the as /// sqrt(Width ^ 2 + Height ^ 2). A diagonal is the line segment that /// links two opposite vertices (corners) of a . /// public float DiagonalLength { get { return (float)Math.Sqrt((Width * Width) + (Height * Height)); } } /// /// True if this is convex, false if it is concave /// or has 0 (such as a point). A /// is convex if for every pair of points within /// the object, every point on the straight line segment that joins them /// is also within the object. A concave is the /// opposite of this. This property will always return true for the /// . /// public override bool IsConvex { get { return true; } } /// /// The minimum number of coordinates needed to specify each point /// within this object. For example: a point has 0 dimensions, a /// line has 1 dimension, a circle or rectangle has 2 dimensions, a /// cube or sphere has 3 dimensions, and a moving cube or sphere has 4. /// This property will alway return 2 for the . /// public override uint Dimensions { get { return 2; } } /// /// The top left coordinate of the rectangle. /// public Vector2 TopLeft { get { return new Vector2(LeftSide, Top); } } /// /// The top left coordinate of the rectangle. /// public Vector2 BottomLeft { get { return new Vector2(LeftSide, Bottom); } } /// /// The bottom right coordinate of the rectangle. /// public Vector2 BottomRight { get { return new Vector2(RightSide, Bottom); } } /// /// The top right coordinate of the rectangle. /// public Vector2 TopRight { get { return new Vector2(RightSide, Top); } } /// /// Returns the X horizontal coordinate of the leftmost edge of the rectangle. /// public float LeftSide { get { return Origin.X; } } /// /// Returns the X horizontal coordinate of the rightmost edge of the rectangle. /// public float RightSide { get { return Origin.X + Width; } } /// /// Returns the Y horizontal coordinate of the topmost edge of the rectangle. /// public float Top { get { return Origin.Y + Height; } } /// /// Returns the Y vertical coordinate of the bottommost edge of the rectangle. /// public float Bottom { get { return Origin.Y; } } /// /// The rectangle that defines the upper left portion of this rectangle. /// public Rectangle NorthWestQuadrant { get { return new Rectangle(LeftSide, Center.Y, Width * .5f, Height * .5f); } } /// /// The rectangle that defines the upper right portion of this rectangle. /// public Rectangle NorthEastQuadrant { get { return new Rectangle(Center.X, Center.Y, Width * .5f, Height * .5f); } } /// /// The rectangle that defines the lower left portion of this rectangle. /// public Rectangle SouthWestQuadrant { get { return new Rectangle(LeftSide, Bottom, Width * .5f, Height * .5f); } } /// /// The rectangle that defines the lower right portion of this rectangle. /// public Rectangle SouthEastQuadrant { get { return new Rectangle(Center.X, Bottom, Width * .5f, Height * .5f); } } /// /// An that creates /// instances. /// public new class Factory : Builder, IFactory { /// /// Initializes a new instance of the /// class that has an origin of (0, 0). /// /// The horizontal width of the rectangle. This /// value cannot be negative. /// The vertical height of the rectangle. This /// value cannot be negative. /// Thrown if the width or height /// specified is negative. public Factory(float width, float height) : this(0.0f, 0.0f, width, height) { } /// /// Initializes a new instance of the class. /// /// The horizontal value of the bottom left corner of /// the rectangle. /// The vertical value of the bottom left corner of the /// rectangle. /// The horizontal width of the rectangle. This /// value cannot be negative. /// The vertical height of the rectangle. This /// value cannot be negative. /// Thrown if the width or height /// specified is negative. public Factory(float x, float y, float width, float height) : this(new Vector2(x, y), width, height) { } /// /// Initializes a new instance of the class. /// /// The to make this /// equal to. public Factory(Rectangle rectangle) : this(rectangle.Origin.X, rectangle.Origin.Y, rectangle.Width, rectangle.Height) { } /// /// Initializes a new instance of the class. /// /// The bottom left coordinate of the rectangle. /// The horizontal width of the rectangle. This /// value cannot be negative. /// The vertical height of the rectangle. This /// value cannot be negative. /// Thrown if the width or height /// specified is negative. public Factory(Vector2 origin, float width, float height) { Width = width; Height = height; Origin = origin; } /// public Rectangle Create() { var result = new Rectangle(Origin, Width, Height); ReportCreation(result); return result; } /// /// The bottom left origin to give to the /// instances created. /// public Vector2 Origin { get; set; } /// /// The width to give to the instances created. /// /// Thrown if the width specified is /// negative. public float Width { get { return _width; } set { if (value < 0) throw new ArgumentException("The width of a Rectangle " + "cannot be negative. You " + "specified: " + value, "value"); _width = value; } } /// /// The height to give to the instances created. /// /// Thrown if the height specified /// is negative. public float Height { get { return _height; } set { if (value < 0) throw new ArgumentException("The height of a Rectangle " + "cannot be negative. You " + "specified: " + value, "value"); _height = value; } } /// /// The backing field to . /// float _width; /// /// The backing field to . /// float _height; } } }