using System; using Chernobyl.Mathematics.Vectors; using Chernobyl.Values; namespace Chernobyl.Mathematics.Movement { /// <summary> /// An <see cref="ITransform"/> that rotates itself so that it will stare /// at another <see cref="ITransform"/>. In other words, it's forward /// vector (-Z axis) will always point at the position of the target /// <see cref="ITransform"/>. /// </summary> public class LookAtTransform : MatrixTransform { /// <summary> /// Initializes a new instance of the <see cref="LookAtTransform"/> /// class that starts out enabled. /// </summary> /// <param name="target">The <see cref="ITransform"/> that this /// <see cref="LookAtTransform"/> will look at (i.e. point its -Z axis /// at).</param> public LookAtTransform(ITransform target) : this(target, true) { } /// <summary> /// Initializes a new instance of the <see cref="LookAtTransform"/> class. /// </summary> /// <param name="target">The <see cref="ITransform"/> that this /// <see cref="LookAtTransform"/> will look at (i.e. point its -Z axis /// at).</param> /// <param name="enable">True if this instance should start looking in /// the direction of the <paramref name="target"/> <see cref="ITransform"/>, /// false if otherwise.</param> public LookAtTransform(ITransform target, bool enable) { // Note that we must set the _isEnabled field first since the set of // the Target property relies on the value of that flag. Also note // that we set the _isEnabled field and not the property. This is // because the IsEnabled property relies on the value of the Target // which is null at the moment. _isEnabled = enable; Target = target; } /// <summary> /// An event handler that can be given to an event. This method, when /// invoked, enables this instance (sets <see cref="IsEnabled"/> to /// true) so that this <see cref="LookAtTransform"/> will look in the /// direction of the <see cref="Target"/>. /// </summary> /// <param name="sender">The instance that generated the event.</param> /// <param name="e">The <see cref="System.EventArgs"/> instance /// containing the event data.</param> public void Enable(object sender, EventArgs e) { IsEnabled = true; } /// <summary> /// An event handler that can be given to an event. This method, when /// invoked, disables this instance (sets <see cref="IsEnabled"/> to /// false) so that this <see cref="LookAtTransform"/> will stop looking /// in the direction of the <see cref="Target"/>. /// </summary> /// <param name="sender">The instance that generated the event.</param> /// <param name="e">The <see cref="System.EventArgs"/> instance /// containing the event data.</param> public void Disable(object sender, EventArgs e) { IsEnabled = false; } /// <summary> /// The <see cref="ITransform"/> that this <see cref="LookAtTransform"/> /// will look at (i.e. point its -Z axis at). /// </summary> public ITransform Target { get { return _target; } set { if(value == null) throw new ArgumentNullException("value", "LookAtTransform.Target cannot be null."); // If we have an old value, detach our event handler from it // (but only if we haven't already done so). if (_target != null && IsEnabled != false) _target.TransformDirtied -= TargetTransformDirtied; _target = value; // We don't need to know when the targets position has changed // if we are disabled. if(IsEnabled == true) { _target.TransformDirtied += TargetTransformDirtied; IsUpdateNeeded = true; } } } /// <summary> /// True if this instance should start looking in the direction of the /// <see cref="Target"/> <see cref="ITransform"/>, false if otherwise. /// </summary> public bool IsEnabled { get { return _isEnabled; } set { bool previousValue = _isEnabled; _isEnabled = value; // We don't need to know when the targets position has changed // if we are disabled. if(_isEnabled == false) { if(previousValue == true) Target.TransformDirtied -= TargetTransformDirtied; } else if(previousValue == false) { Target.TransformDirtied += TargetTransformDirtied; IsUpdateNeeded = true; } } } /// <summary> /// This method is invoked during the update of the /// <see cref="ITransform.WorldMatrix"/> right before the /// <see cref="ITransform.WorldMatrix"/> is actually updated. This method /// ensures that this <see cref="ITransform"/> continually looks in the /// direction of <see cref="Target"/>. /// </summary> /// <param name="world">The value of the <see cref="ITransform.WorldMatrix"/>. /// It is stored in a <see cref="LazyValue{T}"/> so that the calculation can be /// avoided if necessary. Don't call <see cref="LazyValue{T}.Value"/> unless /// required.</param> protected override void PreUpdate(Values.LazyValue<Matrix4> world) { base.PreUpdate(world); if (IsEnabled == true) { // Calculate the new axis of the new local matrix. Vector3 zAxis = Vector3.Normalize(LocalMatrix.Translation - Target.Position); Vector3 xAxis = Vector3.Normalize(Vector3.Cross(LocalMatrix.Up, zAxis)); Vector3 yAxis = Vector3.Normalize(Vector3.Cross(zAxis, xAxis)); LocalMatrix = new Matrix4(new Vector4(xAxis.X, yAxis.X, zAxis.X, 0.0f), new Vector4(xAxis.Y, yAxis.Y, zAxis.Y, 0.0f), new Vector4(xAxis.Z, yAxis.Z, zAxis.Z, 0.0f), LocalMatrix.Row3); } } /// <summary> /// An event handler that is invoked by the /// <see cref="ITransform.TransformDirtied"/> event of the /// <see cref="Target"/> transform. When invoked, this method forces /// this <see cref="ITransform"/> to update its /// <see cref="ITransform.LocalMatrix"/> (i.e. point it in the direction /// requested). /// </summary> /// <param name="sender">The instance that generated the event.</param> /// <param name="e">The <see cref="System.EventArgs"/> instance /// containing the event data.</param> void TargetTransformDirtied(object sender, EventArgs e) { IsUpdateNeeded = true; } /// <summary> /// The backing field to <see cref="Target"/>. /// </summary> ITransform _target; /// <summary> /// The backing field to <see cref="IsEnabled"/>. /// </summary> bool _isEnabled; } }