using System; using Chernobyl.Mathematics.Geometry; using Chernobyl.Mathematics.Vectors; using Chernobyl.Values; namespace Chernobyl.Mathematics.Movement { /// /// An that does not move outside a boundary /// specified by . /// public class CagedTransform2D : MatrixTransform { /// /// Initializes a new instance of the class. /// /// The that this /// is not allowed to move outside of. /// Thrown if /// is null. public CagedTransform2D(Rectangle cage) { BorderDistance = DefaultBorderDistance; Cage = cage; } /// /// The that this is /// not allowed to move outside of. /// public Rectangle Cage { get { return _cage; } private set { if(value == null) throw new ArgumentNullException("value", "CagedTransform2D.Cage " + "cannot be null. Please specify a valid Rectangle."); _cage = value; _cage.TransformDirtied += CageTransformDirtied; } } /// /// The default value of the property which /// is ".0001f". /// public const float DefaultBorderDistance = .0001f; /// /// The distance this places between itself and /// the edges of the . The default value of this /// property is given by . /// /// Thrown if client code /// attempts to set a value on this property that is less than or equal /// to 0. public float BorderDistance { get { return _borderDistance; } set { if(value <= 0) throw new ArgumentOutOfRangeException("value", value, "The CagedTransform2D.BorderDistance cannot be zero."); _borderDistance = value; IsUpdateNeeded = true; } } /// /// This method is invoked during the update of the /// right before the /// is actually updated. This /// method ensures the is enclosed /// within the . /// /// The value of the . /// It is stored in a so that the calculation can be /// avoided if necessary. Don't call unless /// required. protected override void PreUpdate(Values.LazyValue world) { base.PreUpdate(world); // Found out where we are in the world. Vector2 worldPosition = world.Value.Translation.Xy; // Now calculate how far outside of the cage we have moved (if // any). float leftDistance = Cage.LeftSide - worldPosition.X; // Positive value if outside the cage. float rightDistance = Cage.RightSide - worldPosition.X; // Negative value if outside the cage. float bottomDistance = Cage.Bottom - worldPosition.Y; // Positive value if outside the cage. float topDistance = Cage.Top - worldPosition.Y; // Negative value if outside the cage. // Move the local position so that we are back in the cage, if // necessary. Matrix4 local = LocalMatrix; Vector2 localPosition = local.Translation.Xy; if (leftDistance > 0) localPosition.X += (leftDistance + BorderDistance); else if (rightDistance < 0) localPosition.X += (rightDistance - BorderDistance); if (bottomDistance > 0) localPosition.Y += (bottomDistance + BorderDistance); else if (topDistance < 0) localPosition.Y += (topDistance - BorderDistance); local.Row3.X = localPosition.X; local.Row3.Y = localPosition.Y; SetTransformation(ref local); } /// /// An event handler that is invoked when the instance's /// event has been invoked. /// This method ensures that, if the moves, this /// will stay inside it. /// /// The instance that generated the event. /// The instance containing the /// event data. void CageTransformDirtied(object sender, EventArgs e) { IsUpdateNeeded = true; } /// /// The backing field to . /// Rectangle _cage; /// /// The backing field to . /// float _borderDistance; } }