//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
//**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************//
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace BansheeEngine
{
/** @addtogroup Animation
* @{
*/
public partial class Animation
{
private FloatCurvePropertyInfo[] floatProperties;
///
/// Contains mapping for a suffix used by property paths used for curve identifiers, to their index and type.
///
internal static readonly Dictionary PropertySuffixInfos = new Dictionary
{
{".x", new PropertySuffixInfo(0, true)},
{".y", new PropertySuffixInfo(1, true)},
{".z", new PropertySuffixInfo(2, true)},
{".w", new PropertySuffixInfo(3, true)},
{".r", new PropertySuffixInfo(0, false)},
{".g", new PropertySuffixInfo(1, false)},
{".b", new PropertySuffixInfo(2, false)},
{".a", new PropertySuffixInfo(3, false)}
};
///
/// Allows the caller to play an animation clip during edit mode. This form of animation playback is limited as
/// you have no control over clip properties, and features like blending, cross fade or animation events are not
/// supported.
///
/// Caller will need to manually call in order to apply evaluated animation data
/// to relevant float properties (if required).
///
/// Caller will also need to manually call whenever the curves internal to the
/// animation clip change. This should be called before the call to .
///
/// Animation clip to play.
/// Time to start playing at, in seconds.
/// If true, only the frame at the specified time will be shown, without advancing the
/// animation.
internal void EditorPlay(RRef clip, float startTime, bool freeze = false)
{
bool inPreviewMode = Internal__togglePreviewMode(mCachedPtr, true);
if (!inPreviewMode)
return;
if (freeze)
Sample(clip, startTime);
else
{
AnimationClipState clipState = AnimationClipState.Default();
clipState.time = startTime;
SetState(clip, clipState);
}
Internal__refreshClipMappings(mCachedPtr);
}
///
/// Stops playback of animation whose playback what started using .
///
internal void EditorStop()
{
Internal__togglePreviewMode(mCachedPtr, false);
}
///
/// Returns the current time of the currently playing editor animation clip.
///
/// Time in seconds.
internal float EditorGetTime()
{
RRef clip = Internal_getClip(mCachedPtr, 0);
AnimationClipState clipState;
if (clip != null && GetState(clip, out clipState))
return clipState.time;
return 0.0f;
}
///
/// Updates generic float properties on relevant objects, based on the most recently evaluated animation curve
/// values.
///
internal void UpdateFloatProperties()
{
_UpdateFloatProperties();
}
///
/// Searches the scene object hierarchy to find a property at the given path.
///
/// Root scene object to which the path is relative to.
/// Path to the property, where each element of the path is separated with "/".
///
/// Path elements prefixed with "!" signify names of child scene objects (first one relative to
/// . Name of the root element should not be included in the path.
///
/// Path element prefixed with ":" signify names of components. If a path doesn't have a
/// component element, it is assumed the field is relative to the scene object itself (only
/// "Position", "Rotation" and "Scale" fields are supported in such case). Only one component
/// path element per path is allowed.
///
/// Path entries with no prefix are considered regular script object fields. Each path must have
/// at least one such entry.
///
/// A field path can be followed by an indexer [n] where n is a zero-based index. Such paths
/// are assumed to be referencing an index within an array or a list.
///
/// A field path can also be followed by a suffix (after the indexer, if any) separated from the
/// path name with ".". This suffix is not parsed internally, but will be returned as
/// .
///
/// Path examples:
/// :MyComponent/myInt (path to myInt variable on a component attached to the root object)
/// :MyComponent/myArray[0] (path to first element of myArray on the same component as above)
/// !childSO/:MyComponent/myInt (path to myInt variable on a child scene object)
/// !childSO/Position (path to the scene object position)
/// :MyComponent/myVector.z (path to the z component of myVector on the root object)
///
/// Suffix of the last field entry, if it has any. Contains the suffix separator ".".
/// If found, property object you can use for setting and getting the value from the property, otherwise
/// null.
internal static SerializableProperty FindProperty(SceneObject root, string path, out string suffix)
{
suffix = null;
if (string.IsNullOrEmpty(path) || root == null)
return null;
string trimmedPath = path.Trim('/');
string[] entries = trimmedPath.Split('/');
// Find scene object referenced by the path
SceneObject so = root;
int pathIdx = 0;
for (; pathIdx < entries.Length; pathIdx++)
{
string entry = entries[pathIdx];
if (string.IsNullOrEmpty(entry))
continue;
// Not a scene object, break
if (entry[0] != '!')
break;
string childName = entry.Substring(1, entry.Length - 1);
so = so.FindChild(childName);
if (so == null)
break;
}
// Child scene object couldn't be found
if (so == null)
return null;
// Path too short, no field entry
if (pathIdx >= entries.Length)
return null;
// Check if path is referencing a component, and if so find it
Component component = null;
{
string entry = entries[pathIdx];
if (entry[0] == ':')
{
string componentName = entry.Substring(1, entry.Length - 1);
Component[] components = so.GetComponents();
component = Array.Find(components, x => x.GetType().Name == componentName);
// Cannot find component with specified type
if (component == null)
return null;
}
}
// Look for a field within a component
if (component != null)
{
pathIdx++;
if (pathIdx >= entries.Length)
return null;
SerializableObject componentObj = new SerializableObject(component);
StringBuilder pathBuilder = new StringBuilder();
for (; pathIdx < entries.Length - 1; pathIdx++)
pathBuilder.Append(entries[pathIdx] + "/");
// Check last path entry for suffix and remove it
int suffixIdx = entries[pathIdx].LastIndexOf(".");
if (suffixIdx != -1)
{
string entryNoSuffix = entries[pathIdx].Substring(0, suffixIdx);
suffix = entries[pathIdx].Substring(suffixIdx, entries[pathIdx].Length - suffixIdx);
pathBuilder.Append(entryNoSuffix);
}
else
pathBuilder.Append(entries[pathIdx]);
return componentObj.FindProperty(pathBuilder.ToString());
}
else // Field is one of the builtin ones on the SceneObject itself
{
if ((pathIdx + 1) < entries.Length)
return null;
string entry = entries[pathIdx];
if (entry == "Position")
{
SerializableProperty property = new SerializableProperty(
SerializableProperty.FieldType.Vector3,
typeof(Vector3),
() => so.LocalPosition,
(x) => so.LocalPosition = (Vector3)x);
return property;
}
else if (entry == "Rotation")
{
SerializableProperty property = new SerializableProperty(
SerializableProperty.FieldType.Vector3,
typeof(Vector3),
() => so.LocalRotation.ToEuler(),
(x) => so.LocalRotation = Quaternion.FromEuler((Vector3)x));
return property;
}
else if (entry == "Scale")
{
SerializableProperty property = new SerializableProperty(
SerializableProperty.FieldType.Vector3,
typeof(Vector3),
() => so.LocalScale,
(x) => so.LocalScale = (Vector3)x);
return property;
}
return null;
}
}
///
/// Builds a list of properties that will be animated using float animation curves.
///
/// Clip to retrieve the float animation curves from.
partial void RebuildFloatProperties(RRef clip)
{
if (clip == null)
{
floatProperties = null;
return;
}
AnimationCurves curves = clip.Value.Curves;
List newFloatProperties = new List();
for (int i = 0; i < curves.Generic.Length; i++)
{
bool isMorphCurve = curves.Generic[i].flags.HasFlag(AnimationCurveFlags.MorphWeight) ||
curves.Generic[i].flags.HasFlag(AnimationCurveFlags.MorphFrame);
if (isMorphCurve)
continue;
string suffix;
SerializableProperty property = FindProperty(SceneObject, curves.Generic[i].name, out suffix);
if (property == null)
continue;
int elementIdx = 0;
if (!string.IsNullOrEmpty(suffix))
{
PropertySuffixInfo suffixInfo;
if (PropertySuffixInfos.TryGetValue(suffix, out suffixInfo))
elementIdx = suffixInfo.elementIdx;
}
Action setter = null;
Type internalType = property.InternalType;
switch (property.Type)
{
case SerializableProperty.FieldType.Vector2:
if (internalType == typeof(Vector2))
{
setter = f =>
{
Vector2 value = property.GetValue();
value[elementIdx] = f;
property.SetValue(value);
};
}
break;
case SerializableProperty.FieldType.Vector3:
if (internalType == typeof(Vector3))
{
setter = f =>
{
Vector3 value = property.GetValue();
value[elementIdx] = f;
property.SetValue(value);
};
}
break;
case SerializableProperty.FieldType.Vector4:
setter = f =>
{
Vector4 value = property.GetValue();
value[elementIdx] = f;
property.SetValue(value);
};
break;
case SerializableProperty.FieldType.Color:
if (internalType == typeof(Color))
{
setter = f =>
{
Color value = property.GetValue();
value[elementIdx] = f;
property.SetValue(value);
};
}
break;
case SerializableProperty.FieldType.Bool:
setter = f =>
{
bool value = f > 0.0f;
property.SetValue(value);
};
break;
case SerializableProperty.FieldType.Int:
setter = f =>
{
int value = (int)f;
property.SetValue(value);
};
break;
case SerializableProperty.FieldType.Float:
setter = f =>
{
property.SetValue(f);
};
break;
}
if (setter == null)
continue;
FloatCurvePropertyInfo propertyInfo = new FloatCurvePropertyInfo();
propertyInfo.curveIdx = i;
propertyInfo.setter = setter;
newFloatProperties.Add(propertyInfo);
}
floatProperties = newFloatProperties.ToArray();
}
///
/// Called whenever an animation event triggers.
///
/// Clip that the event originated from.
/// Name of the event.
partial void EventTriggered(RRef clip, string name)
{
// Event should be in format "ComponentType/MethodName"
if (string.IsNullOrEmpty(name))
return;
string[] nameEntries = name.Split('/');
if (nameEntries.Length != 2)
return;
string typeName = nameEntries[0];
string methodName = nameEntries[1];
Component[] components = SceneObject.GetComponents();
for (int i = 0; i < components.Length; i++)
{
if (components[i].GetType().Name == typeName)
{
components[i].Invoke(methodName);
break;
}
}
}
///
/// Partial method implementation for
///
partial void _UpdateFloatProperties()
{
// Apply values from generic float curves
if (floatProperties != null)
{
foreach (var entry in floatProperties)
{
float curveValue;
if (Internal__getGenericCurveValue(mCachedPtr, (uint)entry.curveIdx, out curveValue))
entry.setter(curveValue);
}
}
}
///
/// Contains information about a property animated by a generic animation curve.
///
private class FloatCurvePropertyInfo
{
public int curveIdx;
public Action setter;
}
///
/// Information about a suffix used in a property path.
///
internal struct PropertySuffixInfo
{
public PropertySuffixInfo(int elementIdx, bool isVector)
{
this.elementIdx = elementIdx;
this.isVector = isVector;
}
public int elementIdx;
public bool isVector;
}
}
/** @} */
}