|
|
@@ -1,7 +1,9 @@
|
|
|
//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
|
|
|
//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
|
|
|
using System;
|
|
|
+using System.Collections.Generic;
|
|
|
using System.Runtime.InteropServices;
|
|
|
+using System.Text;
|
|
|
|
|
|
namespace BansheeEngine
|
|
|
{
|
|
|
@@ -104,8 +106,25 @@ namespace BansheeEngine
|
|
|
{
|
|
|
private NativeAnimation _native;
|
|
|
|
|
|
- [SerializeField]
|
|
|
- private SerializableData serializableData = new SerializableData();
|
|
|
+ [SerializeField] private SerializableData serializableData = new SerializableData();
|
|
|
+
|
|
|
+ private FloatCurvePropertyInfo[] floatProperties;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Contains mapping for a suffix used by property paths used for curve identifiers, to their index and type.
|
|
|
+ /// </summary>
|
|
|
+ internal static readonly Dictionary<string, PropertySuffixInfo> PropertySuffixInfos = new Dictionary
|
|
|
+ <string, PropertySuffixInfo>
|
|
|
+ {
|
|
|
+ {".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)}
|
|
|
+ };
|
|
|
|
|
|
/// <summary>
|
|
|
/// Returns the non-component version of Animation that is wrapped by this component.
|
|
|
@@ -129,7 +148,10 @@ namespace BansheeEngine
|
|
|
serializableData.defaultClip = value;
|
|
|
|
|
|
if (value != null && _native != null)
|
|
|
+ {
|
|
|
+ RebuildFloatProperties(value);
|
|
|
_native.Play(value);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -185,8 +207,11 @@ namespace BansheeEngine
|
|
|
/// <param name="clip">Clip to play.</param>
|
|
|
public void Play(AnimationClip clip)
|
|
|
{
|
|
|
- if(_native != null)
|
|
|
+ if (_native != null)
|
|
|
+ {
|
|
|
+ RebuildFloatProperties(clip);
|
|
|
_native.Play(clip);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -285,6 +310,158 @@ namespace BansheeEngine
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Searches the scene object hierarchy to find a property at the given path.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="root">Root scene object to which the path is relative to.</param>
|
|
|
+ /// <param name="path">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
|
|
|
+ /// <paramref name="root"/>. 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
|
|
|
+ /// "Translation", "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. Last field entry can optionally have a suffix separated from the
|
|
|
+ /// path name with ".". This suffix is not parsed internally, but will be returned as
|
|
|
+ /// <paramref name="suffix"/>.
|
|
|
+ ///
|
|
|
+ /// Path examples:
|
|
|
+ /// :MyComponent/myInt (path to myInt variable on a component attached to this object)
|
|
|
+ /// !childSO/:MyComponent/myInt (path to myInt variable on a child scene object)
|
|
|
+ /// !childSO/Translation (path to the scene object translation)
|
|
|
+ /// :MyComponent/myVector.z (path to the z component of myVector on this object)
|
|
|
+ /// </param>
|
|
|
+ /// <param name="suffix">Suffix of the last field entry, if it has any. Contains the suffix separator ".".</param>
|
|
|
+ /// <returns>If found, property object you can use for setting and getting the value from the property, otherwise
|
|
|
+ /// null.</returns>
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Changes the state of a playing animation clip. If animation clip is not currently playing the state change is
|
|
|
/// ignored.
|
|
|
@@ -297,6 +474,22 @@ namespace BansheeEngine
|
|
|
_native.SetState(clip, state);
|
|
|
}
|
|
|
|
|
|
+ private void OnUpdate()
|
|
|
+ {
|
|
|
+ // TODO: There could currently be a mismatch between the serialized properties and the active animation clip
|
|
|
+ // - Add PrimaryClip field to NativeAnimation, then compare to the active clip and update if needed
|
|
|
+ // TODO: If primary animation clip isn't playing, don't update float properties
|
|
|
+ // - Add dirty flags to each curve value and don't update unless they changed
|
|
|
+
|
|
|
+ // Apply values from generic float curves
|
|
|
+ foreach (var entry in floatProperties)
|
|
|
+ {
|
|
|
+ float curveValue;
|
|
|
+ if (_native.GetGenericCurveValue(entry.curveIdx, out curveValue))
|
|
|
+ entry.setter(curveValue);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private void OnEnable()
|
|
|
{
|
|
|
RestoreNative();
|
|
|
@@ -330,6 +523,8 @@ namespace BansheeEngine
|
|
|
if (serializableData.defaultClip != null)
|
|
|
_native.Play(serializableData.defaultClip);
|
|
|
|
|
|
+ RebuildFloatProperties(serializableData.defaultClip);
|
|
|
+
|
|
|
Renderable renderable = SceneObject.GetComponent<Renderable>();
|
|
|
if (renderable == null)
|
|
|
return;
|
|
|
@@ -360,6 +555,130 @@ namespace BansheeEngine
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Builds a list of properties that will be animated using float animation curves.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="clip">Clip to retrieve the float animation curves from.</param>
|
|
|
+ private void RebuildFloatProperties(AnimationClip clip)
|
|
|
+ {
|
|
|
+ if (clip == null)
|
|
|
+ {
|
|
|
+ floatProperties = null;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ AnimationCurves curves = clip.Curves;
|
|
|
+
|
|
|
+ List<FloatCurvePropertyInfo> newFloatProperties = new List<FloatCurvePropertyInfo>();
|
|
|
+ for (int i = 0; i < curves.FloatCurves.Length; i++)
|
|
|
+ {
|
|
|
+ string suffix;
|
|
|
+ SerializableProperty property = FindProperty(SceneObject, curves.FloatCurves[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<float> setter = null;
|
|
|
+
|
|
|
+ Type internalType = property.InternalType;
|
|
|
+ switch (property.Type)
|
|
|
+ {
|
|
|
+ case SerializableProperty.FieldType.Vector2:
|
|
|
+ if (internalType == typeof(Vector2))
|
|
|
+ {
|
|
|
+ setter = f =>
|
|
|
+ {
|
|
|
+ Vector2 value = property.GetValue<Vector2>();
|
|
|
+ value[elementIdx] = f;
|
|
|
+ property.SetValue(value);
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ case SerializableProperty.FieldType.Vector3:
|
|
|
+ if (internalType == typeof(Vector3))
|
|
|
+ {
|
|
|
+ setter = f =>
|
|
|
+ {
|
|
|
+ Vector3 value = property.GetValue<Vector3>();
|
|
|
+ value[elementIdx] = f;
|
|
|
+ property.SetValue(value);
|
|
|
+ };
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case SerializableProperty.FieldType.Vector4:
|
|
|
+ if (internalType == typeof(Vector4))
|
|
|
+ {
|
|
|
+ setter = f =>
|
|
|
+ {
|
|
|
+ Vector4 value = property.GetValue<Vector4>();
|
|
|
+ value[elementIdx] = f;
|
|
|
+ property.SetValue(value);
|
|
|
+ };
|
|
|
+ }
|
|
|
+ else if (internalType == typeof(Quaternion))
|
|
|
+ {
|
|
|
+ setter = f =>
|
|
|
+ {
|
|
|
+ Quaternion value = property.GetValue<Quaternion>();
|
|
|
+ value[elementIdx] = f;
|
|
|
+ property.SetValue(value);
|
|
|
+ };
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case SerializableProperty.FieldType.Color:
|
|
|
+ if (internalType == typeof(Color))
|
|
|
+ {
|
|
|
+ setter = f =>
|
|
|
+ {
|
|
|
+ Color value = property.GetValue<Color>();
|
|
|
+ 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();
|
|
|
+ }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Called whenever an animation event triggers.
|
|
|
/// </summary>
|
|
|
@@ -380,6 +699,30 @@ namespace BansheeEngine
|
|
|
public AnimWrapMode wrapMode = AnimWrapMode.Loop;
|
|
|
public float speed = 1.0f;
|
|
|
}
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Contains information about a property animated by a generic animation curve.
|
|
|
+ /// </summary>
|
|
|
+ private class FloatCurvePropertyInfo
|
|
|
+ {
|
|
|
+ public int curveIdx;
|
|
|
+ public Action<float> setter;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Information about a suffix used in a property path.
|
|
|
+ /// </summary>
|
|
|
+ internal struct PropertySuffixInfo
|
|
|
+ {
|
|
|
+ public PropertySuffixInfo(int elementIdx, bool isVector)
|
|
|
+ {
|
|
|
+ this.elementIdx = elementIdx;
|
|
|
+ this.isVector = isVector;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int elementIdx;
|
|
|
+ public bool isVector;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/** @} */
|