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;
}
}
}