using Chernobyl.Mathematics.Vectors; namespace Chernobyl.Mathematics.Movement { /// /// A transformation that removes the scale that was applied by previous /// ancestors in a transformation hierarchy. For example, lets say you have /// two sprites. You want the child sprite to be attached to the parent sprite /// so that the child sprite will "follow" the parent sprite as it moves. You /// also want to scale the parent sprite but don't want the child to be affected /// by this scaling. To do this, you would place a RemoveScaleTransform between /// the parent and the child like so: /// /// Parent /// | /// |---- RemoveScaleTransform /// | /// |------ Child /// /// In code: /// /// RemoveScaleTransform rst = new RemoveScaleTransform(); /// parentSprite.AttachTo(rst); /// rst.AttachTo(childSprite); /// /// /// Note that, a scale can still be applied to the RemoveScaleTransform and /// that scale WILL be applied to descendant transforms in the transform /// hierarchy. /// public class RemoveScaleTransform : MatrixTransform { /// /// Initializes a new instance of the /// class that removes scale from all axis of a transform. /// public RemoveScaleTransform() : this(Axis.All) { } /// /// Initializes a new instance of the class. /// /// The axis of the transform hierarchy whose /// scale should be removed. For example, if you want to remove scale from /// only the X axis then you would specify . If you /// would like to remove scale from the Y and Z axis only, then you would /// specify and like so: /// /// RemoveScaleTransform rms = new RemoveScaleTransform(Axis.Y | Axis.Z); /// /// public RemoveScaleTransform(Axis axisToRemoveScaleFrom) : this(axisToRemoveScaleFrom, DefaultKeepScaleAffectedPosition) { } /// /// Initializes a new instance of the class. /// /// The axis of the transform hierarchy whose /// scale should be removed. For example, if you want to remove scale from /// only the X axis then you would specify . If you /// would like to remove scale from the Y and Z axis only, then you would /// specify and like so: /// /// RemoveScaleTransform rms = new RemoveScaleTransform(Axis.Y | Axis.Z); /// /// /// /// True if the position of this transform should remain unaffected by /// the removal of the scale from it's ancestors, false if otherwise. /// Setting this property to true can help in cases where you want the /// scaled ancestors to not affect the scale of a child but want the /// child's position to be made relative to the scale of the ancestors. /// By default, the value of this property is set by the /// static field . /// /// Performance warning: if this value is true, an additional matrix /// multiplication has to be done in order to find the position affected /// by the scale. This may introduce a small (perhaps insignificant) /// performance degradation. public RemoveScaleTransform(Axis axisToRemoveScaleFrom, bool keepScaleAffectedPosition) { AxisToRemoveScaleFrom = axisToRemoveScaleFrom; KeepScaleAffectedPosition = keepScaleAffectedPosition; } /// /// Orders this transform so that it's world matrix represents it's /// local matrix relative to it's parent's world matrix. /// protected override void Update() { if (IsUpdateNeeded == true) { // if we have a parent: recompute our world matrix using our // local matrix and the world matrix of our parent if (TransformParent != null) { if(AxisToRemoveScaleFrom != Axis.None) { // now we need to take the parent's transformation matrix and // remove the scale from it. After we have done that, we can // use the parent's scale-less matrix like we normally would. Matrix4 parentsWorldMatrix = TransformParent.WorldMatrix; // normalize each axis of the parent's world matrix to remove // the scale... // X axis if ((AxisToRemoveScaleFrom & Axis.X) == Axis.X && parentsWorldMatrix.Row0 != Vector4.Zero) Vector4.Normalize(ref parentsWorldMatrix.Row0, out parentsWorldMatrix.Row0); // Y axis if ((AxisToRemoveScaleFrom & Axis.Y) == Axis.Y && parentsWorldMatrix.Row1 != Vector4.Zero) Vector4.Normalize(ref parentsWorldMatrix.Row1, out parentsWorldMatrix.Row1); // Z axis if ((AxisToRemoveScaleFrom & Axis.Z) == Axis.Z && parentsWorldMatrix.Row2 != Vector4.Zero) Vector4.Normalize(ref parentsWorldMatrix.Row2, out parentsWorldMatrix.Row2); // if necessary, find the "normal" position or the resulting // position of this transform had the scale not been removed // from the ancestor transforms if (KeepScaleAffectedPosition == true) { Matrix4 normalWorldMatrix = LocalMatrix * TransformParent.WorldMatrix; Matrix4 deScaledWorldMatrix = LocalMatrix * parentsWorldMatrix; WorldMatrix = new Matrix4( deScaledWorldMatrix.Row0, deScaledWorldMatrix.Row1, deScaledWorldMatrix.Row2, normalWorldMatrix.Row3); } else WorldMatrix = LocalMatrix * parentsWorldMatrix; } else WorldMatrix = LocalMatrix * TransformParent.WorldMatrix; } else // if we don't have a parent: our local is our world WorldMatrix = LocalMatrix; IsUpdateNeeded = false; // we are "clean" now.. } } /// /// The axis of the transform hierarchy whose /// scale should be removed. For example, if you want to remove scale from /// only the X axis then you would specify . If you /// would like to remove scale from the Y and Z axis only, then you would /// specify and like so: /// /// RemoveScaleTransform rms = new RemoveScaleTransform(Axis.Y | Axis.Z); /// /// /// By default, this value is . public Axis AxisToRemoveScaleFrom { get; set; } /// /// True if the position of this transform should remain unaffected by /// the removal of the scale from it's ancestors, false if otherwise. /// Setting this property to true can help in cases where you want the /// scaled ancestors to not affect the scale of a child but want the /// child's position to be made relative to the scale of the ancestors. /// /// Performance warning: if this value is true, an additional matrix /// multiplication has to be done in order to find the position affected /// by the scale. This may introduce a small (perhaps insignificant) /// performance degradation. /// /// By default, the value of this property is set by the /// static field . public bool KeepScaleAffectedPosition { get; set; } /// /// The default value of the /// property. /// public static bool DefaultKeepScaleAffectedPosition = false; } }