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