using System;
using Chernobyl.Mathematics.Collision;
using Chernobyl.Mathematics.Movement;
using Chernobyl.Mathematics.Vectors;
namespace Chernobyl.Mathematics.Geometry
{
// TODO: Circle should derive from Shape instead of MatrixTransform. Do this after we've implemented Circles tests.
///
/// A 2D circle that is composed of a center origin and a radius. The
/// has collision capabilities and implements the
/// and interfaces.
///
public class Circle : MatrixTransform, ICollidable2D, IEquatable
{
///
/// Constructor that sets the origin of this circle to (0.0, 0.0).
///
/// The radius of the circle.
public Circle(float radius) : this(0.0f, 0.0f, radius)
{}
///
/// Constructor.
///
/// The center X coordinate of the circle.
/// /// The center Y coordinate of the circle.
/// The distance from the origin of the circle to
/// the outside edge of the circle.
public Circle(float x, float y, float radius)
: this(new Vector2(x, y), radius)
{ }
///
/// Constructor.
///
/// The center coordinate of the circle.
/// The distance from the origin of the circle to
/// the outside edge of the circle.
public Circle(Vector2 origin, float radius)
{
Position = new Vector3(origin);
Radius = radius;
}
///
/// 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 circle and the circle passed in.
///
/// The collidable to test for collision against.
/// True if there was a collision, false if otherwise.
public bool IsColliding(Circle circle)
{
// The two circles are colliding if the distance between the first
// circles origin and the origin of the second circle are less than
// the radius of the two circles combined. Normally, we would use the
// distance equation here but there is an optimization we can use
// called the "distance squared". With this equation we will also
// have to square the combined radii.
float sumOfRadii = Radius + circle.Radius;
Vector2 originToOriginVector = new Vector2(Position.X - circle.Position.X, Position.Y - circle.Position.Y);
if (sumOfRadii * sumOfRadii > (originToOriginVector.X * originToOriginVector.X) + (originToOriginVector.Y * originToOriginVector.Y))
return true;
return false;
}
///
/// Tests for a collision between this circle and the Point2D passed in.
///
/// The point to test for collision against.
/// True if there was a collision, false if otherwise.
public bool IsColliding(Point2D point)
{
return point.IsColliding(this);
}
///
/// Tests for a collision between this circle 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)
{
return rectangle.IsColliding(this);
}
///
/// Called when this collidable has started colliding with another
/// collidable. This Circle just invokes its OnCollision instance when
/// this method is invoked.
///
/// The object that was just hit.
public void HandleCollisionStarted(ICollidable collider)
{
if (OnCollisionStarted != null)
OnCollisionStarted(this, EventArgs.Empty);
}
///
/// Called when this collidable has stopped colliding with another
/// collidable.
///
/// The object that was just hit.
public void HandleCollisionEnded(ICollidable collider)
{
if (OnCollisionEnded != null)
OnCollisionEnded(this, EventArgs.Empty);
}
///
/// This method is invoked during the update of the
/// right after the
/// has been updated. This method
/// ensures the radius has been calculatted from the current
/// .
///
/// The new value of the
/// .
protected override void PostUpdate(ref Matrix4 world)
{
base.PostUpdate(ref world);
// store the radius. Note that, we use the right vector here
// just because we can. In a circle, the length of the right and
// up vector will all be the same.
_radiusCache = WorldMatrix.Right.Length;
}
///
/// Scales the transform along the X axis. Note that, scaling a circle
/// along the X axis also causes the same scale to be applied to the Y
/// axis because circles cannot have different X/Y scales.
///
/// The amount to scale in the X axis.
public override void ScaleX(float amount)
{
// circles cannot be scaled differently in the X or Y (otherwise it
// it is not a true circle). So if the user attempts to scale the X
// we are also going to scale the Y by the same amount.
base.Scale(amount, amount);
}
///
/// Scales the transform in the Y axis. Note that, scaling a circle
/// along the Y axis also causes the same scale to be applied to the X
/// axis because circles cannot have different X/Y scales.
///
/// The amount to scale in the Y axis.
public override void ScaleY(float amount)
{
// circles cannot be scaled differently in the X or Y (otherwise it
// it is not a true circle). So if the user attempts to scale the Y
// we are also going to scale the X by the same amount.
base.Scale(amount, amount);
}
///
/// A string that contains the center and radius of this
/// in the form of {X=n, Y=n, Radius=n},
/// where 'X' and 'Y' are the 's
/// and and 'n' is some
/// number. For example: {X=20, Y=20, Radius=100}.
///
///
/// A that represents this instance.
///
public override string ToString()
{
return "{X=" + Position.X + ", Y=" + Position.Y + ", Radius=" + Radius + "}";
}
///
/// 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 Circle);
}
///
/// 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(Circle circle)
{
return ReferenceEquals(this, circle) ||
(Position == circle.Position &&
Radius == circle.Radius);
}
///
/// 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 ==(Circle left, Circle right)
{
return left.Equals(right);
}
///
/// Implements the not-equal (!=) operator. In order for two
/// s to not be equal they both need to have
/// different and .
///
/// 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 !=(Circle left, Circle 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() ^ Radius.GetHashCode();
}
///
/// The distance from the origin of the circle to it's outer edge.
///
public float Radius
{
get
{
// ensure the radius cache is up to date
Update();
return _radiusCache;
}
set
{
if (value < 0)
throw new ArgumentException("The radius of the Circle cannot be negative. You specified: " + value, "value");
// Grab the local matrix and set its column 0 (the first column,
// which holds the scale in the X axis) and column 1 (the second
// column, which holds the scale in the Y axis) to unit vector
// scaled by our new radius multiplied by two. We multiply be
// two because we assume the circle has a diameter of 1 by
// default, therefore, if we wanted to give the circle a radius
// of 1, we would need to scale the circle by 2 because that
// would that would make the circles diameter 2 with a radius
// of 1.
float diameter = value * 2;
Matrix4 local = LocalMatrix;
Vector3 xAxis = Vector3.UnitX * diameter;
local.M11 = xAxis.X;
local.M21 = xAxis.Y;
local.M31 = xAxis.Z;
Vector3 yAxis = Vector3.UnitY * diameter;
local.M12 = yAxis.X;
local.M22 = yAxis.Y;
local.M32 = yAxis.Z;
SetTransformation(ref local);
}
}
///
/// The largest distance between any two points on the circle and the
/// distance that passes through the circles origin.
///
public float Diameter
{
get { return Radius * 2; }
}
///
/// Returns the total area of the circle as PI * Radius ^ 2.
///
public float Area
{
get { return (float)Math.PI * (Radius * Radius); }
}
///
/// Returns the length of the circles perimeter (the circumference) as
/// 2 * PI * Radius.
///
public float Perimeter
{
get { return 2 * (float)Math.PI * Radius; }
}
///
/// 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 alway return 0 for the .
///
public float Volume
{
get { return 0; }
}
///
/// 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 alway return true for the
/// .
///
public 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 uint Dimensions
{
get { return 2; }
}
///
/// The largest width of the geometric primitive. Always returns the
/// of the circle.
///
public float Width { get { return Diameter; } }
///
/// The largest height of the geometric primitive. Always returns the
/// of the circle.
///
public float Height { get { return Diameter; } }
///
/// The largest depth of the object in the object's Z axis. This class
/// will always return 0 for depth as it is a 2D object.
///
public float Depth { get { return 0; } }
///
/// The radius of this held for speed of access.
///
float _radiusCache;
}
}