//-----------------------------------------------------------------------------
// EditCurve.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Xml;
using System.Drawing;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;
namespace Xna.Tools
{
///
/// This class contains curve editting related information.
///
public class EditCurve
{
#region Properties
///
/// Sets/Gets Control that owns this EditCurve.
///
public Control Owner { get { return owner; } set { owner = value; } }
///
/// Sets/Gets Id of this EditCurve.
///
public long Id { get { return id; } set { id = value; } }
///
/// Sets/Gets Name of this EditCurve.
///
public string Name
{
get { return state.Name; }
set { state.Name = value; FireStateChangeEvent(); }
}
///
/// Sets/Gets Color of this EditCurve.
///
public System.Drawing.Color Color
{
get { return color; }
set { color = value; FireStateChangeEvent(); }
}
///
/// Gets EditCurveKeyCollection.
///
public EditCurveKeyCollection Keys { get { return keys; } }
///
/// Sets/Gets PreLoop
///
public CurveLoopType PreLoop
{
get { return OriginalCurve.PreLoop; }
set
{
state.PreLoop = OriginalCurve.PreLoop = value;
FireStateChangeEvent();
}
}
///
/// Sets/Gets PostLoop
///
public CurveLoopType PostLoop
{
get { return OriginalCurve.PostLoop; }
set {
state.PostLoop = OriginalCurve.PostLoop = value;
FireStateChangeEvent();
}
}
///
/// Gets original curve object.
///
public Curve OriginalCurve { get { return originalCurve; } }
///
/// Sets/Gets Visible that represents this EditCurve rendered or not
/// in CurveControl.
///
public bool Visible { get { return visible; } set { visible = value; } }
///
/// Sets/Gets Editable that represents this EditCurve could update states.
///
public bool Editable { get { return editable; } set { editable = value; } }
///
/// Sets/Gets Dirty flag for file saving.
///
public bool Dirty { get { return dirty; } set { dirty = value; } }
///
/// Get selections.
///
public EditCurveKeySelection Selection { get { return selection; } }
///
/// Occures after EditCurve state (Name, Visible, Editable, PreLoop,
/// and PostLoop) changed.
///
public event EventHandler StateChanged;
#endregion
#region Constructors
public EditCurve(string name, System.Drawing.Color curveColor, CommandHistory commandHistory)
{
originalCurve = new Curve();
this.color = curveColor;
keys = new EditCurveKeyCollection(this);
state.Name = name;
state.PreLoop = OriginalCurve.PreLoop;
state.PostLoop = OriginalCurve.PostLoop;
this.commandHistory = commandHistory;
}
public EditCurve(string name, System.Drawing.Color curveColor, Curve curve,
CommandHistory commandHistory)
{
originalCurve = curve;
this.color = curveColor;
keys = new EditCurveKeyCollection(this);
state.Name = name;
state.PreLoop = OriginalCurve.PreLoop;
state.PostLoop = OriginalCurve.PostLoop;
this.commandHistory = commandHistory;
}
#endregion
public override string ToString()
{
return state.Name;
}
#region Public Methods
///
/// Evaluate this curve at given position.
///
///
///
public float Evaluate(float position)
{
return OriginalCurve.Evaluate(position);
}
///
/// Begin update curve parameters.
///
/// It records curve satte and key values modification between
/// BeginUpdate and EndUpdate method.
public void BeginUpdate()
{
if (inUpdating)
throw new InvalidOperationException("BeginUpdate called twice.");
modifiedKeys = new Dictionary();
savedState = (EditCurveState)state.Clone();
inUpdating = true;
}
///
/// EndUpdate and generate Undo/Redo command buffer if there is any
/// modification happend since BeginUpdate called.
///
public void EndUpdate()
{
if (!inUpdating)
throw new InvalidOperationException(
"You must call BeginUpdate before call EndUpdate.");
// Compare modified key values.
if (modifiedKeys != null && modifiedKeys.Count > 0)
{
List oldKeyValues =
new List(modifiedKeys.Count);
List newKeyValues =
new List(modifiedKeys.Count);
foreach (EditCurveKey savedKey in modifiedKeys.Values)
{
EditCurveKey curKey;
if (keys.TryGetValue(savedKey.Id, out curKey))
{
if (!curKey.Equals(savedKey))
{
// Saved value is already cloned.
oldKeyValues.Add(savedKey);
newKeyValues.Add(curKey.Clone());
}
}
}
if (newKeyValues.Count != 0)
{
dirty = true;
if (commandHistory != null)
commandHistory.Add(new EditCurveKeyUpdateCommand(
this, oldKeyValues, newKeyValues));
}
}
modifiedKeys = null;
// Compare states
bool stateChanged = state != savedState;
if (commandHistory != null && stateChanged)
commandHistory.Add(new EditCurveStateChangeCommand(
this, savedState, state));
savedState = null;
inUpdating = false;
if (stateChanged) FireStateChangeEvent();
}
///
/// Select keys and key tangents.
///
/// Selection region in unit coordinate.
/// Tangent scale in unit coordinate.
///
///
///
///
public void Select(BoundingBox selectRegion, Vector2 tangentScale,
EditCurveView keyView, EditCurveView tangentView,
bool toggleSelection, bool singleSelect)
{
if (!Editable) return;
EditCurveKeySelection newSelection = new EditCurveKeySelection();
// Check Intersection of Keys and Tangents.
if (keyView != EditCurveView.Never)
{
ICollection targetKeys =
(keyView == EditCurveView.Always) ?
(ICollection)keys :
(ICollection)selectedKeys.Values;
newSelection.SelectKeys(targetKeys, selectRegion, singleSelect);
}
// Check Tangents if any keys are not selected.
if (newSelection.Count == 0 && tangentView != EditCurveView.Never)
{
ICollection targetKeys
= (tangentView == EditCurveView.Always) ?
(ICollection)keys :
(ICollection)selectedKeys.Values;
newSelection.SelectTangents(targetKeys, selectRegion, tangentScale,
singleSelect);
}
if (toggleSelection)
newSelection = EditCurveKeySelection.ToggleSelection(
selection, newSelection);
ApplySelection(newSelection, true);
}
///
/// Clear selection.
///
public void ClearSelection()
{
ApplySelection(new EditCurveKeySelection(), true);
}
///
/// Apply key selection.
///
///
///
public void ApplySelection(EditCurveKeySelection newSelection,
bool generateCommand)
{
// Re-create selected keys and store selection information from
// new selection.
selectedKeys.Clear();;
foreach (long id in newSelection.Keys)
{
EditCurveKey key = keys.GetValue(id);
key.Selection = newSelection[id];
selectedKeys.Add(key.Id, key);
}
// Clear de-selected keys selection information.
foreach (long id in selection.Keys)
{
if (!newSelection.ContainsKey(id))
{
EditCurveKey key;
if ( keys.TryGetValue(id, out key))
key.Selection = EditCurveSelections.None;
}
}
// Invoke selection change event.
if ( generateCommand == true && !newSelection.Equals(selection) &&
commandHistory != null)
commandHistory.Add(new SelectCommand(this, newSelection, selection));
// Update selection.
selection = newSelection;
}
///
/// Move selected keys or tangents.
///
///
///
public void Move(Vector2 newPos, Vector2 prevPos)
{
if (!Editable || !Visible) return;
Vector2 delta = newPos - prevPos;
foreach (EditCurveKey key in selectedKeys.Values)
{
MarkModify(key);
int oldIdx = keys.IndexOf(key);
// Move tangents.
if ((key.Selection & (EditCurveSelections.TangentIn |
EditCurveSelections.TangentOut)) != 0)
{
// Compute delta angle.
Vector2 pos = new Vector2(key.Position, key.Value);
float minAngle = MathHelper.ToRadians(MinTangentAngle);
float maxAngle = MathHelper.ToRadians(MaxTangentAngle);
const float epsilon = 1e-5f;
if ((key.Selection & EditCurveSelections.TangentIn) != 0)
{
// Compute delta angle.
double prevAngle =
Math.Atan2(prevPos.Y - pos.Y, pos.X - prevPos.X);
double newAngle =
Math.Atan2(newPos.Y - pos.Y, pos.X - newPos.X);
double da = prevAngle - newAngle;
float d = GetDistanceOfKeys(oldIdx, 0);
if (Math.Abs(d) > epsilon)
{
float tn = key.TangentIn / d;
key.TangentIn = (float)Math.Tan(MathHelper.Clamp(
(float)(Math.Atan(tn) + da), minAngle, maxAngle)) * d;
if (Single.IsNaN(key.TangentIn))
key.TangentIn = key.TangentIn;
key.TangentInType = EditCurveTangent.Fixed;
}
}
if ((key.Selection & EditCurveSelections.TangentOut) != 0)
{
// Compute delta angle.
double prevAngle =
Math.Atan2(prevPos.Y - pos.Y, prevPos.X - pos.X);
double newAngle =
Math.Atan2(newPos.Y - pos.Y, newPos.X - pos.X);
double da = newAngle - prevAngle;
float d = GetDistanceOfKeys(oldIdx, 1);
if (Math.Abs(d) > epsilon)
{
float tn = key.TangentOut / d;
key.TangentOut = (float)Math.Tan(MathHelper.Clamp(
(float)(Math.Atan(tn) + da), minAngle, maxAngle)) * d;
key.TangentOutType = EditCurveTangent.Fixed;
}
}
}
// Move key position.
keys.RemoveAt(oldIdx); // remove key from curve once.
if ((key.Selection & EditCurveSelections.Key) != 0)
{
key.OriginalKey = new CurveKey(
key.Position + delta.X, key.Value + delta.Y,
key.TangentIn, key.TangentOut, key.Continuity);
}
// Then store updated node back to the curve.
keys.Add(key);
// Compute auto-generated tangents.
int newIdx = keys.IndexOf(key);
ComputeTangents(newIdx);
if (newIdx != oldIdx)
ComputeTangents(oldIdx);
}
}
///
/// Updated specified key values.
///
public void UpdateKey( long keyId, float newPosition, float newValue)
{
if (!Editable || !Visible) return;
EditCurveKey key;
keys.TryGetValue(keyId, out key);
MarkModify(key);
int oldIdx = keys.IndexOf(key);
// Move key position.
keys.RemoveAt(oldIdx); // remove key from curve once.
key.OriginalKey = new CurveKey( newPosition, newValue,
key.TangentIn, key.TangentOut, key.Continuity);
// Then store updated node back to the curve.
keys.Add(key);
// Compute auto-generated tangents.
int newIdx = keys.IndexOf(key);
ComputeTangents(newIdx);
if (newIdx != oldIdx)
ComputeTangents(oldIdx);
dirty = true;
}
///
/// Add new key at given position.
///
///
public void AddKey(Vector2 pos)
{
EnsureUpdating("AddKey");
// Create new key.
EditCurveKey key = new EditCurveKey(EditCurveKey.GenerateUniqueId(),
new CurveKey(pos.X, pos.Y));
key.Selection = EditCurveSelections.Key;
// Generate add key command and execute it.
EditCurveKeyAddRemoveCommand command =
new EditCurveKeyAddRemoveCommand(this, key, selection);
command.Execute();
if (commandHistory != null) commandHistory.Add(command);
}
///
/// Remove selected keys.
///
public void RemoveKeys()
{
if (!Editable) return;
if (selectedKeys.Count != 0)
{
// Generate Remove keys command and execute it.
EditCurveKeyAddRemoveCommand command =
new EditCurveKeyAddRemoveCommand(this, selectedKeys.Values);
command.Execute();
if (commandHistory != null) commandHistory.Add(command);
// Clear selection
selection.Clear();
selectedKeys.Clear();
dirty = true;
}
}
///
/// Apply new EditCurveState.
///
///
public void ApplyState(EditCurveState newState)
{
if (newState == null) throw new ArgumentNullException("newState");
inUpdating = true;
Name = newState.Name;
PreLoop = newState.PreLoop;
PostLoop = newState.PostLoop;
inUpdating = false;
FireStateChangeEvent();
}
///
/// Apply given key values.
///
///
public void ApplyKeyValues(ICollection newKeyValues)
{
foreach (EditCurveKey newKeyValue in newKeyValues)
{
EditCurveKey key = newKeyValue.Clone();
// Update key value.
keys.Remove(keys.GetValue(key.Id));
keys.Add(key);
// Also, update key values if that key is selected.
if (selectedKeys.ContainsKey(key.Id))
{
selectedKeys.Remove(key.Id);
selectedKeys.Add(key.Id, key);
}
dirty = true;
}
}
///
/// Set specfied tangent type to selected tangents.
///
/// target tangent (In/Out)
///
public void SetTangents(EditCurveSelections targetTangent,
EditCurveTangent tangentType)
{
if (!Editable) return;
EnsureUpdating("SetTangents");
// Change tangent type of selcted nodes.
foreach (EditCurveKey key in selectedKeys.Values)
{
MarkModify(key);
if (tangentType == EditCurveTangent.Stepped)
{
SetKeyContinuity(key, CurveContinuity.Step);
}
else
{
SetKeyContinuity(key, CurveContinuity.Smooth);
if ((targetTangent & EditCurveSelections.TangentIn) != 0)
key.TangentInType = tangentType;
if ((targetTangent & EditCurveSelections.TangentOut) != 0)
key.TangentOutType = tangentType;
}
}
// Then, compute tangents.
foreach (EditCurveKey key in selectedKeys.Values)
ComputeTangents(keys.IndexOf(key));
}
///
/// Compute specfied index key tangents.
///
///
public void ComputeTangents(int keyIndex)
{
if (keyIndex < 0 || keyIndex >keys.Count ||keyIndex > Int32.MaxValue - 2)
throw new ArgumentOutOfRangeException("keyIndex");
// Compute neighbors tangents too.
for (int i = keyIndex - 1; i < keyIndex + 2; ++i)
{
if (i >= 0 && i < keys.Count)
{
EditCurveKey key = keys[i];
MarkModify(key);
float tangentInValue = key.TangentIn;
float tangentOutValue = key.TangentOut;
CurveTangent tangentIn = Convert(key.TangentInType);
CurveTangent tangentOut = Convert(key.TangentOutType);
OriginalCurve.ComputeTangent(i, tangentIn, tangentOut);
if (Single.IsNaN(key.TangentIn)) key.TangentIn = 0.0f;
if (Single.IsNaN(key.TangentOut)) key.TangentOut = 0.0f;
// Restore original value if EditCurveTanget is fixed.
if (key.TangentInType == EditCurveTangent.Fixed)
key.TangentIn = tangentInValue;
if (key.TangentOutType == EditCurveTangent.Fixed)
key.TangentOut = tangentOutValue;
}
}
}
///
/// Get distance between given index key position and previous/next key.
///
///
/// 0:previeous key, 1:next key
///
public float GetDistanceOfKeys(int index, int direction)
{
float result = 1.0f;
if (direction == 0)
{
// From previous key.
if (index > 0)
{
result = keys[index].Position - keys[index - 1].Position;
}
else if (OriginalCurve.PreLoop == CurveLoopType.Oscillate &&
keys.Count > 1)
{
result = keys[1].Position - keys[0].Position;
}
}
else
{
// From next key.
if (index < keys.Count - 1)
{
result = keys[index + 1].Position - keys[index].Position;
}
else if (OriginalCurve.PostLoop == CurveLoopType.Oscillate &&
keys.Count > 1)
{
result = keys[index].Position - keys[index - 1].Position;
}
}
return result;
}
///
/// Returns selected key list.
///
///
public EditCurveKey[] GetSelectedKeys()
{
EditCurveKey[] keys = new EditCurveKey[selectedKeys.Count];
int idx = 0;
foreach (EditCurveKey key in selectedKeys.Values)
keys[idx++] = key;
return keys;
}
///
/// Load a Curve from given filename.
///
///
///
///
///
///
public static EditCurve LoadFromFile(string filename, string name,
System.Drawing.Color color, CommandHistory commandHistory)
{
EditCurve editCurve = null;
using (XmlReader xr = XmlReader.Create(filename))
{
Curve curve = IntermediateSerializer.Deserialize(xr,
Path.GetDirectoryName(filename));
editCurve = new EditCurve(name, color, curve, commandHistory);
}
return editCurve;
}
///
/// Save this curve to given filename.
///
///
public void Save(string filename)
{
using (XmlWriter xw = XmlWriter.Create(filename))
{
IntermediateSerializer.Serialize(xw, originalCurve,
Path.GetDirectoryName(filename));
dirty = false;
}
}
#endregion
#region Private methods
///
/// Mark modified key.
///
private void MarkModify(EditCurveKey key)
{
// Clone and save current EditCurveKey to modified keys.
if (modifiedKeys != null && !modifiedKeys.ContainsKey(key.Id))
{
modifiedKeys.Add(key.Id, key.Clone());
dirty = true;
}
}
///
///
///
///
///
private void SetKeyContinuity(EditCurveKey key, CurveContinuity continuity)
{
if (key.Continuity != continuity)
{
MarkModify(key);
key.Continuity = continuity;
if (continuity == CurveContinuity.Step)
{
key.TangentIn = key.TangentOut = 0;
key.TangentInType = key.TangentInType = EditCurveTangent.Flat;
}
}
}
///
/// Convert EditCurveTanget to CurveTangent.
///
///
///
private static CurveTangent Convert(EditCurveTangent tangent)
{
return (tangent == EditCurveTangent.Fixed) ?
CurveTangent.Flat : (CurveTangent)tangent;
}
private void FireStateChangeEvent()
{
dirty = true;
if (inUpdating) return;
if (StateChanged != null) StateChanged(this, EventArgs.Empty);
if (Owner != null)
Owner.Invalidate();
}
private void EnsureUpdating(string operationName)
{
if (!inUpdating)
throw new InvalidOperationException(String.Format(
"You have to call BeginUpdate before call {0}", operationName));
}
#endregion
#region Private Constants
const float MinTangentAngle = -89.99999f;
const float MaxTangentAngle = +89.99999f;
#endregion
#region Properties wrap members
private Control owner;
private long id;
private System.Drawing.Color color = System.Drawing.Color.Red;
private Curve originalCurve;
private EditCurveKeyCollection keys;
private bool editable = true;
private bool visible = true;
private bool dirty = false;
#endregion
#region Private members
private CommandHistory commandHistory;
private Dictionary selectedKeys =
new Dictionary();
private EditCurveKeySelection selection = new EditCurveKeySelection();
private Dictionary modifiedKeys;
private EditCurveState state = new EditCurveState();
private EditCurveState savedState;
private bool inUpdating;
#endregion
}
}