//********************************** 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; } } /** @} */ }